{"id":21466543,"url":"https://github.com/permafrost-dev/stackup","last_synced_at":"2025-10-14T00:46:20.143Z","repository":{"id":182996176,"uuid":"669422381","full_name":"permafrost-dev/stackup","owner":"permafrost-dev","description":"a single application to spin up your entire dev stack.","archived":false,"fork":false,"pushed_at":"2025-08-27T04:08:16.000Z","size":1408,"stargazers_count":19,"open_issues_count":4,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-08-27T12:36:21.125Z","etag":null,"topics":["cli","command-line","developer-tools","development-stack","development-tools","task-runner","task-scheduler","tasks","utilities"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/permafrost-dev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":".github/CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":".github/SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"github":"permafrost-dev","custom":"https://permafrost-dev/open-source"}},"created_at":"2023-07-22T08:18:01.000Z","updated_at":"2025-08-26T13:12:02.000Z","dependencies_parsed_at":"2024-02-19T21:26:11.164Z","dependency_job_id":"fc7fd62f-eaf1-46b1-b1e2-9adb54a9cfcb","html_url":"https://github.com/permafrost-dev/stackup","commit_stats":null,"previous_names":["permafrost-dev/stackup"],"tags_count":24,"template":false,"template_full_name":null,"purl":"pkg:github/permafrost-dev/stackup","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/permafrost-dev%2Fstackup","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/permafrost-dev%2Fstackup/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/permafrost-dev%2Fstackup/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/permafrost-dev%2Fstackup/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/permafrost-dev","download_url":"https://codeload.github.com/permafrost-dev/stackup/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/permafrost-dev%2Fstackup/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279017365,"owners_count":26086052,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-13T02:00:06.723Z","response_time":61,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cli","command-line","developer-tools","development-stack","development-tools","task-runner","task-scheduler","tasks","utilities"],"created_at":"2024-11-23T08:14:25.392Z","updated_at":"2025-10-14T00:46:20.099Z","avatar_url":"https://github.com/permafrost-dev.png","language":"Go","readme":"\u003cp align=\"center\"\u003e\n    \u003cimg src=\"assets/stackup-app-512px.png\" alt=\"logo\" height=\"150\" style=\"display: block; height: 150px;\"\u003e\n\u003c/p\u003e\n\n# StackUp\n\n---\n\nSpin up your entire dev stack with one command.  \n\n`StackUp` offers many features and advanced functionality. Here are some of the highlights:\n\n- Define tasks that run on startup, shutdown, or on a schedule.\n- Customize tasks and preconditions using javascript.\n- Run tasks on a cron schedule, i.e. running `php artisan schedule:run` once every minute.\n- Load remote configurations via http or S3.\n- Fast in-memory cache skips http requests when possible.\n- Http request gateway prevents unwanted access to remote urls, domains and content types.\n- Send notifications with Telegram and Slack integrations.\n\n## Table of Contents\n\n- [StackUp](#stackup)\n  - [Table of Contents](#table-of-contents)\n  - [About](#about)\n  - [Running StackUp](#running-stackup)\n  - [Configuration](#configuration)\n    - [Configuration: Settings](#configuration-settings)\n      - [Configuration: Settings: Gateway](#configuration-settings-gateway)\n      - [Configuration: Settings: Domains](#configuration-settings-domains)\n      - [Configuration: Settings: Notifications](#configuration-settings-notifications)\n      - [Configuration: Settings: Notifications: Telegram](#configuration-settings-notifications-telegram)\n      - [Configuration: Settings: Notifications: Slack](#configuration-settings-notifications-slack)\n    - [Configuration: Environment Variables](#configuration-environment-variables)\n    - [Configuration: Includes](#configuration-includes)\n    - [Configuration: Preconditions](#configuration-preconditions)\n    - [Configuration: Tasks](#configuration-tasks)\n    - [Configuration: Startup \\\u0026 Shutdown](#configuration-startup--shutdown)\n    - [Configuration: Servers](#configuration-servers)\n    - [Configuration: Scheduler](#configuration-scheduler)\n    - [Example Configurations](#example-configurations)\n  - [Integrations](#integrations)\n    - [Integration: dotenv-vault](#integration-dotenv-vault)\n    - [Integration: Telegram Notifications](#integration-telegram-notifications)\n    - [Integration: Slack Notifications](#integration-slack-notifications)\n    - [Integration: Desktop Notifications](#integration-desktop-notifications)\n  - [Scripting](#scripting)\n    - [Available Functions](#available-functions)\n    - [Script Classes](#script-classes)\n      - [`ComposerJson`](#composerjson)\n      - [`PackageJson`](#packagejson)\n      - [`RequirementsTxt`](#requirementstxt)\n      - [`SemVer`](#semver)\n    - [Environment Variables](#environment-variables)\n  - [Dynamic Tasks](#dynamic-tasks)\n    - [Initialization Script](#initialization-script)\n  - [Setup](#setup)\n  - [Building the project](#building-the-project)\n  - [Contributing](#contributing)\n  - [Security Vulnerabilities](#security-vulnerabilities)\n  - [Credits](#credits)\n  - [License](#license)\n\n## About\n\n`StackUp` is a scriptable tool for developers that automates the process of spinning up complicated development environments.  It allows you to defines a series of steps that execute in order on startup and shutdown, as well as a list of server processes that should be started.  Additionally, `StackUp` runs an event loop while the server processes are running, allowing you to run tasks on a cron schedule.\n\nOne of the key features of this application is its ability to automate routine tasks. With a simple configuration, you can define a sequence of tasks that your project requires, such as starting containers, running database migrations, or seeding data. This automation not only saves you time but also ensures consistency across your development environment.\n\nIt also includes a robust, scriptable precondition system. Before doing anything, checks can be performed to ensure everything is set up correctly. This feature helps prevent common issues that occur when the environment is not properly configured.\n\n## Running StackUp\n\nTo run `StackUp`, simply run the binary in a directory containing a `stackup.yaml` or `stackup.dist.yaml` configuration file:\n\n```bash\nstackup\n```\n\nor, specify a configuration filename:\n\n```bash\nstackup --config stackup.dev.yaml\n```\n\nTo generate a new configuration file to get started, run `init`: \n\n```bash\nstackup init\n```\n\n`StackUp` checks if it is running the latest version on startup.  To disable this behavior, use the `--no-update-check` flag:\n\n```bash\nstackup --no-update-check\n```\n\n## Configuration\n\nThe application is configured using a YAML file named `stackup.yaml` and contains five required sections: `preconditions`, `tasks`, `startup`, `shutdown`, and `scheduler`.\nThere are also optional `settings`, `includes` and `init` sections that can be used to configure and initialize the application.\n\n### Configuration: Settings\n\nThe `settings` section of the configuration file is used to configure the application.  The following settings are available:\n\n| field     | description                        | required? |\n|-----------|------------------------------------|-----------|\n| `anonymous-stats` | `boolean` value specifying whether to send anonymous usage statistics, defaults to `false` | no |\n| `defaults.tasks.path` | default path for tasks | no |\n| `defaults.tasks.platforms` | default platforms for tasks | no |\n| `defaults.tasks.silent` | default silent setting for tasks | no |\n| `domains.allowed` | array of domain names that can be accessed (downloads/urls/includes), wildcards are supported. | no |\n| `domains.hosts` | array of host settings, such as headers, wildcards are supported. | no |\n| `dotenv`  | array of `.env` filenames to load  | no        |\n| `cache.ttl-minutes` | number of minutes to cache remote files | no |\n| `checksum-verification` | `boolean` value specifying if remote file checksums should be verified, defaults to `true` | no |\n| `exit-on-checksum-mismatch` | `boolean` value specifying whether to exit if a checksum mismatch occurs when including a remote file | no |\n\nExample `settings` section:\n\n```yaml\nname: my stack\nversion: 1.0.0\n\nsettings:\n  anonymous-stats: true # opt-in to sending anonymous usage statistics, default is false.\n  dotenv: ['.env', '.env.local'] # loads both `.env` and `.env.local` files, defaults to `.env`.\n  exit-on-checksum-mismatch: false # do not exit if a checksum mismatch occurs, defaults to true.\n  checksum-verification: false # do not verify checksums, defaults to true.\n  cache:\n    ttl-minutes: 60 # cache remote files for 60 minutes, defaults to 5 minutes.\n  domains:\n    allowed:\n      # domains allowed for remote file downloads and remote file includes.\n      - raw.githubusercontent.com\n      - api.github.com\n      - app.posthog.com # allow anonymous usage statistics to be sent\n    hosts:\n    - hostname: api.github.com\n      gateway: allow\n      headers:\n        - 'Authorization: token $GITHUB_TOKEN'\n        - 'Accept: application/vnd.github.v3+json'\n    - hostname: '*.githubusercontent.com'\n      gateway: allow\n      headers:\n        - 'Authorization: token $GITHUB_TOKEN'\n        - 'Accept: application/vnd.github.v3+json'\n  defaults:\n    tasks:\n      silent: true\n      path: $LOCAL_BACKEND_PROJECT_PATH\n      platforms: ['windows']\n\ntasks:\n  - id: task-1\n    command: printf \"hello world\\n\"\n    # path: defaults to $LOCAL_BACKEND_PROJECT_PATH\n    # silent: defaults to true\n    # platforms: defaults to ['windows']\n\n  - id: task-2\n    command: printf \"goodbye world\\n\"\n    path: $FRONTEND_PROJECT_PATH # overrides the default\n    platforms: ['linux', 'darwin'] # overrides the default\n```\n\n#### Configuration: Settings: Gateway\n\nThe `gateway` field of the `settings` section of the configuration file is used to customize the behavior of the http request gateway when accessing remote files or urls.  Both `blocked` and `allowed` content types can be specified as exact strings or wildcards, but both fields are optional.\n\nThe default value for `blocked` is to not block any content types, and the default value for `allowed` is to allow all content types.\n\n```yaml\nsettings:\n  gateway:\n    content-types:\n      blocked:\n        - audio/*\n        - font/*\n        - image/*\n        - multipart/*\n        - video/*\n      allowed:\n        - application/json\n        - application/vnd.api+json\n        - application/vnd.github.*\n        - application/xml\n        - application/yaml\n        - text/*\n```\n\n#### Configuration: Settings: Domains\n\nThe `domains` section of the configuration file is used to specify a list of domain names that can be accessed when downloading remote files\nor including remote files.  Wildcards are supported, such as `*.github.com`.  \nIf the `domains` section is not specified, default values of `raw.githubusercontent.com` and `api.github.com` are used for the allow list,\nand all other domains are blocked.\n\nThe `hosts` section of `domains` allows the configuration of headers to send with requests to specific hosts. Hostnames may be fully-qualified\nhostnames, or may contain wildcards.  For example, `*.githubusercontent.com` will match `raw.githubusercontent.com` and `gist.githubusercontent.com`.\nIf the `gateway` field is set to `allow`, the hostname will be added to the list of allowed domains automatically, and defaults to `true`.\n\n\u003e The domain allow list is applied to all url access, including when `StackUp` checks to see if it is running the latest version,\n\u003e or when sending anonymous opt-in analytics.\n\n```yaml\ndomains:\n  allowed:\n    - raw.githubusercontent.com\n    - '*.github.com'\n  hosts:\n    - hostname: api.github.com\n      gateway: allow\n      headers:\n        - 'Authorization: token $GITHUB_TOKEN'\n        - 'Accept: application/vnd.github.v3+json'\n    - hostname: '*.githubusercontent.com'\n      gateway: allow\n      headers:\n        - 'Authorization: token $GITHUB_TOKEN'\n        - 'Accept: application/vnd.github.v3+json'\n```\n\n#### Configuration: Settings: Notifications\n\nStackUp provides the ability to send notifications via several integrations.\n\n```yaml\nsettings:\n  notifications:\n    # integration1 settings\n    # integration2 settings, etc.\n```\n\n#### Configuration: Settings: Notifications: Telegram\n\nTo send notifications via Telegram, add a `telegram` section to the `notifications` section of the configuration file.  The `telegram` section should contain an `api-key` and a `chat-ids` field.  The `api-key` field should contain the Telegram bot token, and the `chat-ids` field should be an array of chat ids of the users or groups to send notifications to.  The chat ids may either be a string, number, or an environment variable that contains a chat id.\n\n```yaml\nsettings:\n  notifications:\n    telegram:\n      api-key: $TELEGRAM_API_KEY\n      chat-ids: [$TELEGRAM_CHAT_ID1, $TELEGRAM_CHAT_ID2]\n```\n\nFor more information on the Telegram integration, see the [Telegram Notifications](#integration-telegram-notifications) section of the [Integrations](#integrations) documentation.\n\n#### Configuration: Settings: Notifications: Slack\n\nTo send notifications via Slack, add a `slack` section to the `notifications` section of the configuration file.  The `slack` section should contain `webhook-url` and `channel-ids` fields.  The `webhook-url` field should contain the Slack webhook url to send notifications to, and the `channel-ids` field should be an array of channel names to send notifications to.\n\n```yaml\nsettings:\n  notifications:\n    slack:\n      webhook-url: $SLACK_WEBHOOK_URL\n      channel-ids: [$SLACK_CHANNEL_1, $SLACK_CHANNEL_2]\n```\n\nFor more information about the Slack integration, see the [Slack Notifications](#integration-slack-notifications) section of the [Integrations](#integrations) documentation.\n\n### Configuration: Environment Variables\n\nEnvironment variables can be defined in the optional `env` section of the configuration file.  These variables can be referenced in other sections of the configuration file using the `env()` function or by prefixing the variable name with `$` (e.g. `$MY_VAR`).\n\n```yaml\nenv:\n  - MY_ENV_VAR_ONE=test1234\n  - MY_ENV_VAR_TWO=1234test\n```\n\n### Configuration: Includes\n\nThe `includes` section of the configuration file is used to specify a list of filenames, file urls, or s3 urls that should be merged with the configuration.  This is useful for splitting up a large configuration file into smaller, more manageable files or reusing commonly-used tasks, init scripts, or preconditions. Startup, shutdown, servers, and scheduled tasks are not merged from the included files.\n\nIncluded urls can be prefixed with `gh:` to indicate that the file should be fetched from GitHub.  For example, `gh:permafrost-dev/stackup/main/templates/stackup.dist.yaml` will fetch the `stackup.dist.yaml` file from the `permafrost-dev/stackup` repository on GitHub.\nAdd a `headers` field to the `url` entry to specify headers to send with the request.  The `headers` field should be an array of strings, where each string is a header to send with the request.  The header value can be a javascript expression if wrapped in double braces.  For example:\n\n```yaml\n  - url: gh:permafrost-dev/stackup/main/templates/remote-includes/node.yaml\n    headers:\n      - 'Authorization: token $GITHUB_TOKEN' \n\n  - url: gh:permafrost-dev/stackup/main/templates/remote-includes/php.yaml\n    headers:\n      - '{{ \"Authorization: token \" + $myGithubTokenVar }}'\n```\n\nTo import a file from an S3 bucket, prefix the url with `s3:`. For example, `s3:hostname/my-bucket-name/my-config.yaml` will fetch the `my-config.yaml` file from the `my-bucket-name` bucket on `hostname`. Amazon S3 and Minio are supported.\n\nIncluded files can be specified with either a relative or absolute pathname.  Relative pathnames are relative to the directory containing the configuration file.  Absolute pathnames are relative to the current working directory.\n\n```yaml\nincludes:\n  # include a remote file from github\n  - url: gh:permafrost-dev/stackup/main/templates/remote-includes/containers.yaml\n    verify: false # optional, defaults to true\n\n  - url: gh:permafrost-dev/stackup/main/templates/remote-includes/node.yaml\n    headers:\n      # headers to send with the request, javascript must be wrapped in double braces\n      - 'Authorization: token $GITHUB_TOKEN'\n      - '{{ \"X-Some-Header: \" + getEnv(\"GITHUB_TOKEN\") }}'\n\n  # include a local file\n  - file: python.yaml\n\n  # include a remote file from a minio/s3 bucket\n  - url: s3:127.0.0.1:9000/stackup-includes/python.yaml\n    access-key: $S3_KEY # access key loaded from `.env` or `env` section\n    secret-key: $S3_SECRET # secret key env loaded from `.env` or `env` section\n    secure: false # optional, defaults to true\n```\n\nIf the optional field `verify` is set to `false`, the application will not attempt to verify the checksum of the file before fetching it.  This may be useful for files that are frequently updated, but is not recommended.\n\nIf the optional `checksum-url` field is not specified, the application will attempt to fetch the checksum file from the same location as the included file, but with the `.sha256` extension appended to the filename.  For example, if the included file is `gh:permafrost-dev/stackup/main/templates/remote-includes/containers.yaml`, the checksum file will be fetched from `gh:permafrost-dev/stackup/main/templates/remote-includes/containers.yaml.sha256`.\n\nAlternatively, a single `checksums.sha256.txt` or `checksums.sha512.txt` file can exist instead of separate `*.sha256/sha512` files for each file in the `includes` section.  The file should contain a list of checksums for each included file, one per line, in the format `checksum filename` (where `filename` is the base filename only). This is the format generated by the `sha256sum/sha512sum` command-line utilities.  For example:\n\n```text\n9e0d9fea90950908c356734df89bfdff4984de4a6143fe32c404cfbc91984fb7  containers.yaml\n69b87009a87e38e5470191a9e40c441ce963fb4cf260fd44cf5f032b9566454a  laravel.yaml\n```\n\nSee the [example configuration](./templates/stackup.dist.yaml) for an example of using includes, the [example remote includes](./templates/remote-includes) for examples of remote include templates, and the [example checksum file](./templates/remote-includes/checksums.sha256.txt) for an example of a checksum file.\n\n\nValid algorithms are `sha256` or `sha512`, and checksum files may be generated with the `sha256sum` or `sha512sum` command line utilities.\n\n### Configuration: Preconditions\n\nThe `preconditions` section of the configuration file is used to specify a list of conditions that must be met before the tasks and servers can run. Each precondition is defined by a `name` and a `check`. The `name` is a human-readable description of the precondition, and the `check` is a javascript expression that returns a boolean value indicating whether the precondition is met. Unlike other fields, the `check` field does not need to be wrapped in double braces; it is always interpreted as a javascript expression.\n\nHere is an example of the `preconditions` section:\n\n```yaml\npreconditions:\n    - name: frontend project exists\n      check: fs.Exists($FRONTEND_PROJECT_PATH)\n\n    - name: backend project has docker-compose file\n      check: fs.Exists($LOCAL_BACKEND_PROJECT_PATH + \"/docker-compose.yml\")\n\n    - name: backend project is laravel project\n      check: fs.Exists($LOCAL_BACKEND_PROJECT_PATH + \"/artisan\")\n```\n\nPreconditions can be configured to run a task or script on failure. If a `on-fail` attribute is specified for a precondition, the application will run the task or script when the precondition fails.  The `on-fail` attribute can be a task `id` or a javascript expression.  If specified, the precondition will re-run in an attempt to successfully pass the check. The maximum number of retries is specified by the `max-retries` attribute, and defaults to 0 (no retries).\n\n```yaml\npreconditions:\n    - name: check for missing text file\n      check: fs.Exists(\"missing.txt\")\n      on-fail: '{{ fs.WriteFile(\"missing.txt\", \"test\") }}'\n      max-retries: 1\n```\n\nThis functionality can be used to configure projects, install dependencies, or perform other tasks that are required for the project to run.  Consider the following:\n\n```yaml\npreconditions:\n  - name: ensure php dependencies are installed\n    check: fs.Exists(\"vendor\") \u0026\u0026 fs.IsDirectory(\"vendor\")\n    on-fail: install-composer-deps\n    max-retries: 1\n\ntasks:\n  - name: install composer dependencies\n    id: install-composer-deps\n    command: composer install --no-interaction\n```\n\n### Configuration: Tasks\n\nThe `tasks` section of the configuration file is used to specify all tasks that can be run during startup, shutdown, as a server, or as a scheduled task.\n\nItems in `tasks` follow this structure:\n\n| field     | description                                                                                                | required? |\n|-----------|------------------------------------------------------------------------------------------------------------|-----------|\n| `name`      | The name of the task (e.g. `spin up containers`)                                                           | no        |\n| `id`        | A unique identifier for the task (e.g. `start-containers`)                                                 | yes       |\n| `if`        | A condition that must be true for the task to run (e.g. `hasFlag('seed')`)                                 | no        |\n| `command`   | The command to run for the task (e.g. `podman-compose up -d`)                                              | yes       |\n| `path`      | The path to the directory where the command should be run `(default: current directory)`. this may be a reference to an environment variable without wrapping it in braces, e.g. `$BACKEND_PROJECT_PATH` | no        |\n| `silent`    | Whether to suppress output from the command `(default: false)`                                               | no        |\n| `platforms` | A list of platforms where the task should be run `(default: all platforms)`                                  | no        |\n| `maxRuns`   | The maximum number of times the task can run (0 means always run) `(default: 0)`                             | no        |\n\nNote that the `command` and `path` values can be wrapped in double braces to be interpreted as a javascript expression.\n\nHere is an example of the `tasks` section:\n\n```yaml\ntasks:\n  - name: spin up containers\n    id: start-containers\n    command: podman-compose up -d\n    path: $LOCAL_BACKEND_PROJECT_PATH\n    silent: true\n\n  - name: run migrations (rebuild db)\n    id: run-migrations-fresh\n    if: hasFlag(\"seed\")\n    command: php artisan migrate:fresh --seed\n    path: $LOCAL_BACKEND_PROJECT_PATH\n\n  - name: run migrations (no seeding)\n    id: run-migrations-no-seed\n    if: '!hasFlag(\"seed\")'\n    command: php artisan migrate\n    path: '{{ env(\"LOCAL_BACKEND_PROJECT_PATH\") }}'\n\n  - name: frontend httpd (linux, macos)\n    id: frontend-httpd-linux\n    command: node ./node_modules/.bin/next dev\n    path: '{{ env(\"FRONTEND_PROJECT_PATH\") }}'\n    platforms: ['linux', 'darwin']\n```\n\nHowever the only required fields are `id` and `command`:\n\n```yaml\ntasks:\n  - id: start-containers\n    command: docker-compose up -d\n\n  - id: stop-containers\n    command: docker-compose down\n```\n\n### Configuration: Startup \u0026 Shutdown\n\nThe `startup` and `shutdown` sections of the configuration define the tasks that should be run synchronously during either startup or shutdown.  The values listed must match a defined task `id`.\n\n```yaml\nstartup:\n  - task: start-containers\n  - task: run-migrations\n\nshutdown:\n  - task: stop-containers\n```\n\n### Configuration: Servers\n\nThe `servers` section of the configuration file is used to specify a list of tasks that the application should start as server processes. The values listed must match a defined task `id`.\n\nServer processes are started in the order that they are defined, however the application does not wait for the process to start before starting the next task.  If you need to wait for a task to complete, it should be run in the `startup` configuration section.\n\n```yaml\nservers:\n  - task: frontend-httpd\n  - task: backend-httpd\n  - task: horizon-queue\n```\n\n### Configuration: Scheduler\n\nThe `scheduler` section of the configuration file is used to specify a list of tasks that the application should run on a schedule.\nEach entry should contain a `task` id and a `cron` expression.  The `task` value must be equal to the `id` of a `task` that has been defined.\n\nHere is an example of the `scheduler` section and its associated `tasks` section:\n\n```yaml\ntasks:\n  - id: run-artisan-scheduler\n    command: php artisan schedule:run\n\nscheduler:\n    - task: run-artisan-scheduler\n      cron: '* * * * *'\n```\n\n### Example Configurations\n\nSee the [example configuration](./templates/stackup.dist.yaml) for a more complex example that brings up a Laravel-based backend and a Next.js frontend stack.\n\nWorking on a standalone Laravel application? Check out the [example laravel configuration](./templates/stackup.laravel.yaml).\n\n## Integrations\n\nStackUp supports several integrations that provide additional functionality.\n\n### Integration: dotenv-vault\n\n`StackUp` includes an integration for `dotenv-vault` and loading encrypted values from `.env.vault` files (see the [dotenv-vault website](https://vault.dotenv.org)).\n\nTo load a `.env.vault` file, add an entry to the `env` section named `dotenv://vault`.  This item will cause the `.env.vault` file to\nbe loaded into the environment, if it exists.  If it does not exist, no action is taken.\n\n```yaml\nenv:\n  - MY_ENV_VAR_ONE=test1234\n  - dotenv://vault # loads .env.vault, if it exists\n```\n\n### Integration: Telegram Notifications\n\n`StackUp` includes an integration for sending notifications via Telegram.  To configure the integration, see the [Telegram Notifications](#configuration-settings-notifications-telegram) section of the [Configuration: Settings](#configuration-settings) documentation.\n\nNotifications are sent using javascript:\n\n```js\n// send a notification to all configured chat ids\nnotifications.Telegram().Message(\"hello from stackup, test 123\").Send()\n\n// send a notification to a specific chat id\nnotifications.Telegram().Message(\"hello from stackup, test 456\").To($TELEGRAM_CHAT_ID_1).Send()\n```\n\n### Integration: Slack Notifications\n\n`StackUp` includes an integration for sending messages to Slack channels.  To configure the integration, see the [Slack Notifications](#configuration-settings-notifications-slack) section of the [Configuration: Settings](#configuration-settings) documentation.\n\nNotifications are sent using javascript:\n\n```js\n// send a notification to all configured chat ids\nnotifications.Slack().Message(\"hello from stackup, test 123\").Send()\n\n// send a notification to a specific chat id\nnotifications.Slack().Message(\"hello from stackup, test 456\").To($SLACK_CHANNEL_1).Send()\n```\n\n### Integration: Desktop Notifications\n\n`StackUp` includes an integration for displaying desktop notifications. To configure the integration, see the [Desktop Notifications](#configuration-settings-notifications-desktop) section of the [Configuration: Settings](#configuration-settings) documentation.\n\nNotifications are sent using javascript:\n\n```js\n// display a desktop notification\nnotifications.Desktop().Message(\"hello from stackup\").Send()\n\n// optionally provide a notification title\nnotifications.Desktop().Message(\"hello from stackup\", \"some title\").Send()\n```\n\n## Scripting\n\nMany of the fields in a `Task` can be defined using javascript. To specify an expression to be evaluated, wrap the content in double braces: `{{ env(\"HOME\") }}`.\n\n### Available Functions\n\n\n| Function   | Arguments         | Description                                                                 |\n|----------- |------------------ |---------------------------------------------------------------------------- |\n| `binaryExists()`| `name: string`   | returns true if the specified binary exists in `$PATH`, otherwise false       |\n| `env()`      | `name: string`      | returns the string value of environment variable `name                        |\n| `exists()`   | `filename: string`  | returns true if `filename` exists, false otherwise                          |\n| `fetch()`    | `url: string`       | returns the contents of the url `url` as a string; gateway rules apply      |\n| `fetchJson()`| `url: string`       | returns the contents of the url `url` as a JSON object; gateway rules apply |\n| `fileContains()`| `filename: string, search: string` | returns true if `filename` contains `search`, false otherwise |\n| `getCwd()`   | --                | returns the directory stackup was run from                                  |\n| `hasEnv()`   | `name: string`      | returns true if the specified environment variable exists, otherwise false  |\n| `hasFlag()`  | `name: string`      | returns true if the flag `name` was specified when running the application  |\n| `outputOf()`   | `command: string`   | returns the output of the command `command` with spaces trimmed           |\n| `platform()` | --                | returns the operating system, one of `windows`, `linux` or `darwin` (macOS) |\n| `script()`   | `filename: string`  | returns the output of the javascript located in `filename`                  |\n| `selectTaskWhen()` | `conditional: boolean, trueTaskId: string falseTaskId: string` | returns a `Task` object based on the value of `conditional` |\n| `semver()` | `version: string` | returns a `SemVer` object based on the value of `version` |\n| `statusMessage()` | `message: string` | prints a status message to stdout, without a trailing new line |\n| `task()`     | `taskId: string`    | returns a `Task` object with the id `taskId`                                |\n| `app.FailureMessage()` | `message: string` | prints a failure message with an X to stdout with a trailing new line |\n| `app.StatusLine()` | `message: string` | prints a status message to stdout, with a trailing new line |\n| `app.StatusMessage()` | `message: string` | prints a status message to stdout, without a trailing new line |\n| `app.SuccessMessage()` | `message: string` | prints a success message with a checkmark to stdout with a trailing new line |\n| `app.WarningMessage()` | `message: string` | prints a warning message to stdout with a trailing new line \n| `app.Version()` | -- | returns the current version of `StackUp` |\n| `dev.ComposerJson()`| `filename: string` | returns the contents of a composer.json file (`filename`) as a `ComposerJson` object |\n| `dev.PackageJson()` | `filename: string` | returns the contents of a package.json file (`filename`) as a `PackageJson` object |\n| `dev.RequirementsTxt()` | `filename: string` | returns a requirements.txt file (`filename`) as a `RequirementsTxt` object |\n| `fs.Exists()`| `filename: string`  | returns true if `filename` exists, false otherwise                          |\n| `fs.GetFiles()` | `path: string`   | returns a list of files in `path`                                           |\n| `fs.IsDirectory()` | `pathname: string` | returns true if `pathname` is a directory, false otherwise                  |\n| `fs.IsFile()` | `filename: string`  | returns true if `filename` is a file, false otherwise                       |\n| `fs.ReadFile()`| `filename: string`  | returns the contents of `filename` as a string                              |\n| `fs.ReadJSON()` | `filename: string` | returns the contents of `filename` as a JSON object                         |\n| `fs.WriteFile()`| `filename: string, contents: string` | writes `contents` to `filename` |\n| `fs.WriteJSON()` | `filename: string, obj: Object` | writes `obj` to `filename` as a JSON object |\n| `vars.Get()` | `name: string` | returns the value of the application variable `name` |\n| `vars.Has()` | `name: string` | returns true if the application variable `name` exists, otherwise false |\n| `vars.Set()` | `name: string, value: any` | sets an application variable `name` to the value `value` |\n\n\n### Script Classes\n\n#### `ComposerJson`\n\nThe `ComposerJson` class is returned by the `dev.ComposerJson()` function and was designed for working with `composer.json` files.  It has the following methods and attributes:\n\n| Name | Arguments | Description |\n|--------|-----------|-------------|\n| `.GetDependencies()` | -- | returns an array of dependencies |\n| `.HasDependency()` | `name: string` | returns true if the composer.json file has dependency named `name`, otherwise false |\n| `.GetDependency()` | `name: string` | returns the dependency named `name`, if it exists |\n| `.GetDevDependency()` | `name: string` | returns the dev dependency named `name`, if it exists |\n\n#### `PackageJson`\n\nThe `PackageJson` class is returned by the `dev.PackageJson()` function and was designed for working with `package.json` files.  It has the following methods and attributes:\n\n| Name | Arguments | Description |\n|--------|-----------|-------------|\n| `.GetDependencies()` | -- | returns an array of dependencies |\n| `.GetDependency()` | `name: string` | returns the dependency named `name`, if it exists |\n| `.GetDevDependency()` | `name: string` | returns the dev dependency named `name`, if it exists |\n| `.GetScript()` | `name: string` | returns the script named `name`, if it exists |\n| `.HasDependency()` | `name: string` | returns true if the package.json file has dependency named `name`, otherwise false |\n| `.HasDevDependency()` | `name: string` | returns true if the package.json file has dev dependency named `name`, otherwise false |\n| `.HasScript()` | `name: string` | returns true if the package.json file has a script named `name`, otherwise false |\n\n#### `RequirementsTxt`\n\nThe `RequirementsTxt` class is returned by the `dev.RequirementsTxt()` function and was designed for working with `requirements.txt` files.  It has the following methods and attributes:\n\n| Name | Arguments | Description |\n|--------|-----------|-------------|\n| `.GetDependencies()` | -- | returns an array of dependencies |\n| `.HasDependency()` | `name: string` | returns true if the requirements.txt file has dependency named `name`, otherwise false |\n| `.GetDependency()` | `name: string` | returns the dependency named `name`, if it exists |\n\n#### `SemVer`\n\nThe `SemVer` class is returned by the `semver()` function And is used to parse and compare semantic version strings.  It has the following methods and attributes:\n\n| Name | Arguments | Description |\n|--------|-----------|-------------|\n| `.Compare()` | `version: string` | returns 1 if `version` is greater than the current version, -1 if `version` is less than the current version, and 0 if they are equal |\n| `.GreaterThan()` | `version: string` | returns true if `version` is greater than the current version, otherwise false |\n| `.Gte()` | `version: string` | returns true if `version` is greater than or equal to the current version, otherwise false |\n| `.LessThan()` | `version: string` | returns true if `version` is less than the current version, otherwise false |\n| `.Lte()` | `version: string` | returns true if `version` is less than or equal to the current version, otherwise false |\n| `.Equals()` | `version: string` | returns true if `version` is equal to the current version, otherwise false |\n| `.Major` | -- | value of the major version number |\n| `.Minor` | -- | value of the minor version number |\n| `.Patch` | -- | value of the patch version number |\n| `.String` | -- | the original version string |\n\n### Environment Variables\n\nEnvironment variables can be accessed using the `env()` function or referenced directly as variables by prefixing the variable name with `$` (e.g. `$HOME`).\n\n```yaml\npreconditions:\n    - name: backend project has a docker-compose file\n      check: fs.Exists($BACKEND_PROJECT_PATH + \"/docker-compose.yml\")\n\ntasks:\n  - name: horizon queue\n    id: horizon-queue\n    if: dev.composerJson($BACKEND_PROJECT_PATH).HasDependency(\"laravel/horizon\");\n    command: php artisan horizon\n    path: '{{ $BACKEND_PROJECT_PATH }}'\n    platforms: ['linux', 'darwin']\n```\n\n## Dynamic Tasks\n\nYou can create dynamic tasks using either the `selectTaskWhen()` or `task()` function:\n\n```yaml\ntasks:\n  - name: frontend httpd (linux, macos)\n    id: httpd-linux\n    command: node ./node_modules/.bin/next dev\n    path: '{{ $FRONTEND_PROJECT_PATH }}'\n    platforms: ['linux', 'darwin']\n\n  - name: frontend httpd (windows)\n    id: httpd-win\n    command: npm run dev\n    path: '{{ $FRONTEND_PROJECT_PATH }}'\n    platforms: ['windows']\n\n  - name: '{{ selectTaskWhen(platform() == \"windows\", \"httpd-win\", \"httpd-linux\").Name }}'\n    id: frontend-httpd\n    command: '{{ selectTaskWhen(platform() == \"windows\", \"httpd-win\", \"httpd-linux\").Command }}'\n    path: '{{ selectTaskWhen(platform() == \"windows\", \"httpd-win\", \"httpd-linux\").Path }}'\n```\n\nThis example defines tasks with different commands for each operating system, then defines a `frontend-httpd` task that dynamically selects the correct one:\n\n```yaml\ntasks:\n  - name: frontend httpd (linux)\n    id: frontend-httpd-linux\n    command: node ./node_modules/.bin/next dev\n    path: '{{ $FRONTEND_PROJECT_PATH }}'\n\n  - name: frontend httpd (macOS)\n    id: frontend-httpd-darwin\n    command: node ./node_modules/.bin/next dev\n    path: '{{ $FRONTEND_PROJECT_PATH }}'\n\n  - name: frontend httpd (windows)\n    id: frontend-httpd-windows\n    command: npm run dev\n    path: '{{ $FRONTEND_PROJECT_PATH }}'\n    \n  - name: '{{ task(\"frontend-httpd-\" + platform()).Name }}'\n    id: frontend-httpd\n    command: '{{ task(\"frontend-httpd-\" + platform()).Command }}'\n    path: '{{ task(\"frontend-httpd-\" + platform()).Path }}'\n```\n\n### Initialization Script\n\nYou may add an `init` section to the configuration file to run javascript before the `preconditions` section executes:\n\n```yaml\nname: my stack\nversion: 1.0.0\n\ninit: |\n  if (binaryExists(\"podman-compose\")) {\n    vars.Set(\"containerEngineBinary\", \"podman-compose\");\n  } else {\n    vars.Set(\"containerEngineBinary\", \"docker-compose\");\n  }\n\n  app.SuccessMessage(\"container engine selected: * \" + vars.Get(\"containerEngineBinary\"));\n```\n\n## Setup\n\n```bash\ngo mod tidy\n```\n\n## Building the project\n\n`StackUp` uses [task](https://github.com/go-task/task) for running tasks, which is a tool similar to `make`. \n\n```bash\ntask build\n```\n\n---\n\n## Contributing\n\nPlease see [CONTRIBUTING](.github/CONTRIBUTING.md) for details.\n\n## Security Vulnerabilities\n\nPlease review [our security policy](../../security/policy) on how to report security vulnerabilities.\n\n## Credits\n\n- [Patrick Organ](https://github.com/patinthehat)\n- [All Contributors](../../contributors)\n\n## License\n\nThe MIT License (MIT). Please see [License File](LICENSE) for more information.\n","funding_links":["https://github.com/sponsors/permafrost-dev","https://permafrost-dev/open-source"],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpermafrost-dev%2Fstackup","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpermafrost-dev%2Fstackup","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpermafrost-dev%2Fstackup/lists"}