{"id":16241668,"url":"https://github.com/getlarge/nx-heroku","last_synced_at":"2025-03-19T17:30:46.566Z","repository":{"id":71310385,"uuid":"603018176","full_name":"getlarge/nx-heroku","owner":"getlarge","description":"Nx plugin to quickly deploy and promote your Nx apps on Heroku","archived":false,"fork":false,"pushed_at":"2024-05-02T20:46:28.000Z","size":735,"stargazers_count":3,"open_issues_count":9,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-12T21:28:22.455Z","etag":null,"topics":["automation","deployment","heroku","nodejs","nx","nx-plugin","ops"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","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/getlarge.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-02-17T12:55:12.000Z","updated_at":"2024-08-07T19:31:58.000Z","dependencies_parsed_at":"2024-05-01T22:28:50.077Z","dependency_job_id":"0d1a9eeb-709c-41d6-8d45-0e55acf81017","html_url":"https://github.com/getlarge/nx-heroku","commit_stats":null,"previous_names":[],"tags_count":12,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/getlarge%2Fnx-heroku","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/getlarge%2Fnx-heroku/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/getlarge%2Fnx-heroku/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/getlarge%2Fnx-heroku/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/getlarge","download_url":"https://codeload.github.com/getlarge/nx-heroku/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243885990,"owners_count":20363649,"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","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":["automation","deployment","heroku","nodejs","nx","nx-plugin","ops"],"created_at":"2024-10-10T14:08:17.867Z","updated_at":"2025-03-19T17:30:46.233Z","avatar_url":"https://github.com/getlarge.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"![Nx](https://img.shields.io/badge/nx-143055?style=for-the-badge\u0026logo=nx\u0026logoColor=white)\n❤\n![Heroku](https://img.shields.io/badge/heroku-%23430098.svg?style=for-the-badge\u0026logo=heroku\u0026logoColor=white)\n\n![test](https://github.com/getlarge/nx-heroku/actions/workflows/test.yml/badge.svg)\n[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=getlarge_nx-heroku\u0026metric=alert_status)](https://sonarcloud.io/summary/new_code?id=getlarge_nx-heroku)\n\n# nx-heroku\n\nThis plugin allows you to deploy any Nx application to Heroku. It is based on the [Heroku CLI](https://devcenter.heroku.com/articles/heroku-cli) and should help you to achieve simple or complex deployments.\n\n- It supports multi-procfile buildpacks so that each app in your Nx workspace can be deployed to a different Heroku app.\n- It supports Heroku pipelines and multi-stage deployments (development, staging, production) for each app.\n- It can be used in CI/CD pipelines (Github Actions, Gitlab CI, etc) or locally to deploy your apps to Heroku.\n- It can create Heroku apps, addons, webhooks and drains automatically if they don't exist.\n\n## Setup\n\nTo deploy your application to Heroku, you need to have the [Heroku CLI](https://devcenter.heroku.com/articles/heroku-cli) installed.\nIn Github Actions, it comes already installed in the runners.\n\nWhen running the executor, it will authenticate to Heroku with the credentials (`email`, `apiKey`) provided via the executors options.\n\nTo install the plugin, run the following command:\n\n```bash\n# with npm\nnpm install -D @getlarge/nx-heroku\n\n# or with yarn\nyarn add -D @getlarge/nx-heroku\n```\n\n## Generate target\n\n### Deploy\n\nTo generate a target for your application, run the following command:\n\n```bash\nnpx nx generate @getlarge/nx-heroku:deploy --projectName=my-app --org=your-heroku-team --appNamePrefix=your-app-prefix\n\n# or to be prompted for the project name, omit specifying it\nnpx nx g @getlarge/nx-heroku:deploy\n\n```\n\nThis will generate a `deploy` target in your `project.json` file.\n\n### Promote\n\nTo generate a target for your application, run the following command:\n\n```bash\nnpx nx generate @getlarge/nx-heroku:promote --projectName=my-app --org=your-heroku-team --appNamePrefix=your-app-prefix\n\n# or to be prompted for the project name, omit specifying it\nnpx nx g @getlarge/nx-heroku:promote\n\n```\n\nThis will generate a `promote` target in your `project.json` file.\n\n## Execute target\n\n### Deploy\n\nThe [`nx-heroku:deploy`](./packages/nx-heroku/src//executors/deploy/executor.ts) executor allows the deployment of an Nx application to a targeted Heroku app. The deployment will be done for each pipeline stage declared via the option `config` (default: ['development'])\n\nYou can look at the executor [schema](./packages/nx-heroku/src/executors/deploy/schema.json) to see all the options available.\n\nWhen deploying an application, the following steps are executed:\n\n1. Set internal variables that are prefixed with HD to avoid conflicts with variables provided by the user (`variables` option)\n2. Authentification to Heroku via .netrc file\n3. Set default options (branch to current branch, environment to development, watchDelay to 0)\n4. Set the Heroku app name.\n   The Heroku app will be named after the pattern described in [Conventions](#naming-conventions).\n5. Create project 'Procfile'\n6. Create static **buildpack** config (optional)\n7. Create **Aptfile**, to install extra Ubuntu dependencies before build (optional)\n8. Ensure the remote is added (and that the application is created).\n9. Merge `HD_` prefixed variables with the one provided in the options and set Heroku app `config vars`.\n   You can provide your variables that will be available at build time. They should be prefixed by `HD_`, they will be added (without the prefix) to the Heroku app config automatically.\n   The environment variables `HD_PROJECT_NAME`,`HD_PROJECT_ENV`, `HD_NODE_ENV` and `HD_PROCFILE` will automatically be defined based on the project name and environment being deployed.\n   `PROCFILE` is required when using [multi-procfile buildpack](https://elements.heroku.com/buildpacks/heroku/heroku-buildpack-multi-procfile), it should be defined in each Heroku app to indicate the Procfile path for the given project.\n10. Cleanup and register buildpacks.\n    Extra buildpacks can be provided by using `buildPacks` option, they will be installed in **the order they are provided in the array**.\n11. Ensure the app is attached to a pipeline with a stage matching the environment provided in options\n    If the Heroku app doesn't exist, it will be created and attach to an existing or new pipeline.\n12. Assign management member (optional)\n13. Register addons (optional)\n14. Register drain (optional)\n15. Register webhook (optional)\n16. Deploy (trigger build and release)\n17. Run health check (optional)\n18. Rollback if health check failed (optional)\n\n```mermaid\nsequenceDiagram\n\tparticipant Nx as Nx Plugin\n\tparticipant CLI as Heroku\n\tparticipant Git as Git\n\tparticipant App as Application\n\n  note over Nx,Nx: Set internal variables and default options\n  Nx-\u003e\u003eNx: Setup\n  Nx-\u003e\u003eCLI: Heroku authentication with .netrc file\n  Nx-\u003e\u003eGit: Add and commit Procfile\n  opt heroku-community/static is in buildPacks\n    Nx-\u003e\u003eGit: Add and commit static.json config\n  end\n  opt heroku-community/apt is in buildPacks\n    Nx-\u003e\u003eGit: Add and commit Aptfile buildpack config\n  end\n  Nx-\u003e\u003eCLI: Create app remote branch\n  opt app does not exists\n    Nx-\u003e\u003eCLI: Create app and bind remote branch\n  end\n  Nx-\u003e\u003eCLI: Fetch app config vars\n  note over Nx,CLI: Merge HD_ prefixed variables, options variables\u003cbr/\u003eand the config vars from the Heroku app\n  Nx-\u003e\u003eCLI: Set app config vars\n  Nx-\u003e\u003eCLI: Clear buildpacks\n  Nx-\u003e\u003eCLI: Add buildpacks\n  Nx-\u003e\u003eCLI: Check pipeline exists\n  opt pipeline does not exists\n    Nx-\u003e\u003eCLI: Create pipeline\n     opt repositoryName is provided in options\n      Nx-\u003e\u003eCLI: Connect the pipeline to the repository\n    end\n  end\n  Nx-\u003e\u003eCLI: Attach the app to a pipeline\n  opt serviceUser is provided in options\n    Nx-\u003e\u003eCLI: Add management member to the app\n  end\n  opt addons is provided in options\n    Nx-\u003e\u003eCLI: Add addons to the app\n  end\n  opt drain is provided in options\n    Nx-\u003e\u003eCLI: Add drain to the app\n  end\n  opt webhook is provided in options\n    Nx-\u003e\u003eCLI: Add webhook to the app\n  end\n  opt resetRepo is set to true in options\n    Nx-\u003e\u003eCLI: Reset the app repository\n  end\n  Nx-\u003e\u003eGit: Build and release app\n  opt watchDelay is set to \u003e 0\n    Nx-\u003e\u003eGit: Wait for the app to be deployed until the timeout is reached\n  end\n  opt healthcheck (url) is provided in options\n    Nx-\u003e\u003eApp: Run healthcheck\n    opt healthcheck failed and rollbackOnHealthcheckFailed is set to true\n      Nx-\u003e\u003eCLI: Rollback\n      CLI-\u003e\u003eApp: Restore app to previous state\n    end\n  end\n\n```\n\n### Example\n\nFor the given example project config:\n\n```json\n{\n  \"name\": \"frontend\",\n  \"$schema\": \"../../node_modules/nx/schemas/project-schema.json\",\n  \"projectType\": \"application\",\n  \"sourceRoot\": \"apps/frontend/src\",\n  \"targets\": {\n    ...,\n    \"deploy\": {\n      \"executor\": \"@getlarge/nx-heroku:deploy\",\n      \"options\": {\n        \"appNamePrefix\": \"aloes\",\n        \"procfile\": \"web: bin/start-nginx-solo\",\n        \"buildPacks\": [\n          \"heroku/nodejs\",\n          \"heroku-community/multi-procfile\",\n          \"heroku-community/nginx\"\n        ],\n        \"variables\": {\n          \"NGINX_APP_ROOT\": \"dist/apps/frontend\",\n          \"YARN2_SKIP_PRUNING\": \"true\"\n        },\n        \"useForce\": true,\n        \"debug\": true\n      }\n    },\n  }\n}\n\n```\n\nYou can run the deployment with :\n\n```bash\nexport HEROKU_API_KEY=\u003cyour_heroku_api_key\u003e\nexport HEROKU_EMAIL=\u003cyour_heroku_account_email\u003e\n\n#  this will build and release the applications `my-app-frontend-development` and `my-app-frontend-staging` to Heroku\nnpx nx run frontend:deploy --config 'development,staging' --appPrefixName my-app --apiKey $HEROKU_API_KEY --email $HEROKU_EMAIL\n```\n\n### Promote\n\nThe [`nx-heroku:promote`](./packages/nx-heroku/src//executors/promote/executor.ts) executor allows to promote an existing Heroku app from a pipeline. The promotion will be done for each pipeline stage declared via the option `config` (default: 'staging')\n\nThe promotion can be done :\n\n- from development to staging by setting the config to 'staging',\n- from staging to production by setting the config to 'production'\n\nWhen promoting an application, the following steps are executed:\n\n1. Check that pipeline exists\n2. Check that the app to promote is attached to the pipeline, if not create it and attach it.\n3. Merge config vars from the promoted app with the `variables` option.\n4. Promote the app to the next stage\n5. Assign management member (optional)\n\nYou can run the promotion with :\n\n```bash\nexport HEROKU_API_KEY=\u003cyour_heroku_api_key\u003e\nexport HEROKU_EMAIL=\u003cyour_heroku_account_email\u003e\n\n#  this will promote the application `my-app-frontend-development` to `my-app-frontend-staging` to Heroku\nnpx nx run frontend:promote --config staging --appPrefixName my-app --apiKey $HEROKU_API_KEY --email $HEROKU_EMAIL\n```\n\n\u003ccode\u003e \u003cb\u003eBeware with frontend applications, the app is not being rebuilt so variables set in the build from down stream stage are used. \u003c/b\u003e \u003c/code\u003e\n\n## Naming conventions\n\n### Pipeline name\n\nThe pipeline name deployed on Heroku is composed with the pattern `${appPrefixName}-${projectName}`, where :\n\n- `appPrefixName` is the prefix name of the Heroku app, it can be customized via the `appNamePrefix` option.\n- `projectName` is the name of the Nx project.\n\nExamples:\n\n- `aloes-my-service`\n- `aloes-frontend`\n\n### Application name\n\nThe application names deployed on Heroku are composed with the pattern `${appPrefixName}-${projectName}-${environment}`, where :\n\n- `appPrefixName` is the prefix name of the Heroku app, it can be customized via the `appNamePrefix` option.\n- `projectName` is the name of the Nx project.\n- `environment` is the Heroku pipeline stage (development, staging or production), it can be customized via the `config` option.\n\nDue to some length limitations (32 characters), the environment name is shortened and the application name might be shortened as well.\n\nExamples:\n\n- `aloes-my-service-dev`\n- `aloes-frontend-staging`\n- `aloes-myapp-prod`\n\nThis logic is applied in this [Heroku helpers module](./packages/nx-heroku/src/executors/common/heroku/apps.ts)\n\n---\n\n## Hooks\n\nHeroku allows to run scripts called during the deployment process, for node projects we can make use of package.json scripts to run these hooks.\nSee the [Heroku documentation](https://devcenter.heroku.com/articles/nodejs-support#customizing-the-build-process) for more details.\n\nFor example, we can use the `heroku-postbuild` script to provide our own application build process.\n\n```json\n{\n  \"scripts\": {\n    \"heroku-postbuild\": \"node tools/heroku/postbuild.js $PROJECT_NAME $PROJECT_ENV\",\n    \"heroku-cleanup\": \"node tools/heroku/cleanup.js $PROJECT_NAME $PROJECT_ENV\"\n  }\n}\n```\n\nI will provide some examples based on my experience with Nx apps deployment on Heroku.\n\n### Custom build process\n\nThe `heroku-postbuild` script is used to build the application, it is executed after the `npm install` command.\n\n`tools/heroku/postbuild.js`\n\n```js\nconst { createPackageJson, createLockFile } = require('@nx/devkit');\nconst { execSync } = require('child_process');\nconst { writeFileSync } = require('fs');\n\nasync function refreshPackageJson(implicitDeps = [], skipDev = false) {\n  const projectGraph = await createProjectGraphAsync();\n  const { root: projectRoot } = data;\n  const options = {\n    projectRoot: data.root,\n    root: process.cwd(),\n  };\n  const packageJson = createPackageJson(projectName, projectGraph, options);\n  for (const dep of implicitDeps) {\n    packageJson.dependencies[dep] =\n      rootPackageJson.dependencies[dep] || rootPackageJson.devDependencies[dep];\n  }\n  if (skipDev) {\n    delete packageJson.devDependencies;\n  }\n  // we could sort dependencies here\n  // packageJson.dependencies = sortObjectByKeys(packageJson.dependencies);\n  // packageJson.devDependencies = sortObjectByKeys(packageJson.devDependencies);\n  const packageJsonPath = `apps/${projectName}/package.json`;\n  existsSync(packageJsonPath) \u0026\u0026 unlinkSync(packageJsonPath);\n  writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));\n\n  // generate and store lock file\n  execSync(`npm i --prefix apps/${projectName} --package-lock-only`, {\n    stdio: 'inherit',\n  });\n  // or when using nx \u003e= 15.x\n  const lockFile = createLockFile(packageJson);\n  const packageLockJsonPath = `apps/${projectName}/package-lock.json`;\n  writeFileSync(packageLockJsonPath, lockFile);\n}\n\nasync function postbuild(argv) {\n  const projectName = argv[2] || process.env.PROJECT_NAME;\n  const projectEnv = argv[3] || process.env.PROJECT_ENV || 'production';\n  const implicitDeps = (argv[4] || process.env.IMPLICIT_DEPS || '').split(',');\n  console.log(`Heroku custom postbuild hook, ${projectName}:${projectEnv}`);\n  // refresh package-lock to be reused in cleanup phase\n  await refreshPackageJson(implicitDeps, true);\n\n  execSync(`npx nx build ${projectName} --c ${projectEnv} `, {\n    stdio: 'inherit',\n  });\n}\n\npostbuild(process.argv).catch((e) =\u003e {\n  console.error(e);\n  process.exit(1);\n});\n```\n\n### Custom cleanup\n\nThe `heroku-clean` script is used to cleanup the application before the deployment, it is executed after the `heroku-postbuild` script.\nIn this case we can remove the `node_modules` folder and only install the given project dependencies to respect the slug size limitation.\n\n`tools/heroku/cleanup.js`\n\n```js\n/* eslint-disable @typescript-eslint/no-var-requires */\nconst { execSync } = require('child_process');\nconst { copyFileSync, existsSync, rmSync, writeFileSync } = require('fs');\nconst { resolve } = require('path');\n\nfunction cleanup(argv) {\n  const projectName = argv[2] || process.env.PROJECT_NAME;\n  const projectEnv = argv[3] || process.env.PROJECT_ENV;\n  console.log(`Heroku custom cleanup hook, ${projectName}:${projectEnv}`);\n  // optionally you can authenticate on NPM here if you need to install private packages\n  const npmrc = resolve(process.cwd(), '.npmrc');\n  const registryUrl = `//registry.npmjs.org/`;\n  const authString =\n    registryUrl.replace(/(^\\w+:|^)/, '') + ':_authToken=${NPM_TOKEN}';\n  const contents = `${authString}${os.EOL}`;\n  writeFileSync(npmrc, contents);\n\n  const packageJsonPath = 'package.json';\n  const packageLockJsonPath = 'package-lock.json';\n  // declare package and package-lock json file paths generated at postbuild phase\n  const appPackageJsonPath = `apps/${projectName}/${packageJsonPath}`;\n  const appPackageLockJsonPath = `apps/${projectName}/${packageLockJsonPath}`;\n  // remove all project dependencies and cache to respect slug size limitation\n  if (existsSync('node_modules')) {\n    rmSync('node_modules', { recursive: true, force: true });\n  }\n  if (existsSync('.yarn/cache')) {\n    rmSync('.yarn/cache', { recursive: true, force: true });\n  }\n\n  // only backend apps should have generated a custom package.json\n  if (existsSync(appPackageJsonPath)) {\n    console.log('Found generated package.json');\n    // reinstall production deps only\n    copyFileSync(appPackageJsonPath, packageJsonPath);\n    if (existsSync(appPackageLockJsonPath)) {\n      copyFileSync(appPackageLockJsonPath, packageLockJsonPath);\n      console.log(`Install dependencies with \"npm ci\"`);\n      execSync('npm ci --production --loglevel=error', { stdio: 'inherit' });\n    } else {\n      console.log(`Install dependencies with \"npm install\"`);\n      execSync('npm install --production --loglevel=error', {\n        stdio: 'inherit',\n      });\n    }\n\n    // try to remove unnecessary dependencies\n    console.log(\n      `Install and run node-prune | https://github.com/tj/node-prune`\n    );\n    execSync('curl -sf https://gobinaries.com/tj/node-prune | PREFIX=. sh');\n    execSync('./node-prune', { stdio: 'inherit' });\n    rmSync('node-prune');\n  }\n  // remove .npmrc file if exists\n  rmSync('.npmrc');\n}\n\ncleanup(process.argv);\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgetlarge%2Fnx-heroku","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgetlarge%2Fnx-heroku","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgetlarge%2Fnx-heroku/lists"}