diff --git a/.github/workflows/ci_release.yml b/.github/workflows/ci_release.yml index 560c4af7..a2d26dcf 100644 --- a/.github/workflows/ci_release.yml +++ b/.github/workflows/ci_release.yml @@ -46,54 +46,3 @@ jobs: working-directory: 'projects/${{ inputs.package }}' dry-run: ${{ inputs.dry-run }} release: true - - ci_sync_peer_deps: - needs: ci_release - runs-on: 'ubuntu-latest' - steps: - - name: Checkout sources - uses: actions/checkout@v4 - with: - ref: ${{ github.ref_name }} # https://github.com/actions/checkout/issues/1327 - persist-credentials: false # https://github.com/semantic-release/semantic-release/issues/2636 - - - name: Synchronize peer dependencies - working-directory: 'scripts' - env: - GITHUB_TOKEN: ${{ secrets.DSI_HUG_BOT_GITHUB_TOKEN }} - run: | - git config user.name 'dsi-hug-bot' - git config user.email 'dsi-hug-bot@users.noreply.github.com' - git remote set-url origin https://x-access-token:${{ secrets.DSI_HUG_BOT_GITHUB_TOKEN }}@github.com/${{ github.repository }} - npm --prefix . i chalk - node ./sync-peer-deps.mjs - - # - # nx does not update package-lock file correctly. - # @see https://github.com/nrwl/nx/issues/26660 - # - # This results in the following error, next time deps are installed: - # “npm error `npm ci` can only install packages when your package.json and package-lock.json or - # npm-shrinkwrap.json are in sync. Please update your lock file with `npm install` before continuing." - # - ci_update_package_lock_file: - needs: ci_sync_peer_deps - runs-on: 'ubuntu-latest' - steps: - - name: Checkout sources - uses: actions/checkout@v4 - with: - ref: ${{ github.ref_name }} # https://github.com/actions/checkout/issues/1327 - persist-credentials: false # https://github.com/semantic-release/semantic-release/issues/2636 - - - name: Synchronize package.json and package-lock.json - env: - GITHUB_TOKEN: ${{ secrets.DSI_HUG_BOT_GITHUB_TOKEN }} - run: | - git config user.name 'dsi-hug-bot' - git config user.email 'dsi-hug-bot@users.noreply.github.com' - git remote set-url origin https://x-access-token:${{ secrets.DSI_HUG_BOT_GITHUB_TOKEN }}@github.com/${{ github.repository }} - npm install - git add package.json package-lock.json - git commit --message "chore: synchronize package.json and package-lock.json" - git push --follow-tags --no-verify --atomic diff --git a/nx.json b/nx.json index 67adef24..0e9e9393 100644 --- a/nx.json +++ b/nx.json @@ -5,21 +5,10 @@ "dependsOn": [ "^build:ng" ] - }, - "nx-release-publish": { - "dependsOn": [ - "build" - ], - "options": { - "packageRoot": "{workspaceRoot}/dist/{projectRoot}" - } } }, "release": { "projectsRelationship": "independent", - "git": { - "commitMessage": "chore({projectName}): release version {version} [skip ci]" - }, "version": { "conventionalCommits": true }, diff --git a/package.json b/package.json index 6c35ea57..c2965601 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "lint": "eslint . --fix", "prepare": "husky || true", "new-package": "ng g .:new-package", - "release:dry-run": "nx release --verbose --dry-run", + "release:dry-run": "node -r @swc-node/register ./scripts/release.ts --verbose --dry-run", "postinstall": "patch-package" }, "workspaces": [ diff --git a/projects/core/package.json b/projects/core/package.json index cc3912c2..0fdc01c8 100644 --- a/projects/core/package.json +++ b/projects/core/package.json @@ -31,8 +31,8 @@ "test:ci": "ng test core --watch=false --browsers=ChromeHeadless", "build:ng": "ng build core -c=production", "build": "nx build:ng @hug/ngx-core --verbose", - "release": "nx release -p=@hug/ngx-core --yes --verbose", - "release:dry-run": "nx release -p=@hug/ngx-core --verbose --dry-run" + "release": "node -r @swc-node/register ../../scripts/release.ts --projects=@hug/ngx-core --verbose", + "release:dry-run": "npm run release -- --dry-run" }, "peerDependencies": { "@angular/common": ">= 14", diff --git a/projects/layout/package.json b/projects/layout/package.json index 1b698d8c..056d04c6 100644 --- a/projects/layout/package.json +++ b/projects/layout/package.json @@ -26,8 +26,8 @@ "test:ci": "ng test layout --watch=false --browsers=ChromeHeadless", "build:ng": "ng build layout -c=production", "build": "nx build:ng @hug/ngx-layout --verbose", - "release": "nx release -p=@hug/ngx-layout --yes --verbose", - "release:dry-run": "nx release -p=@hug/ngx-layout --verbose --dry-run" + "release": "node -r @swc-node/register ../../scripts/release.ts --projects=@hug/ngx-layout --verbose", + "release:dry-run": "npm run release -- --dry-run" }, "peerDependencies": { "@angular/common": ">= 14", diff --git a/projects/list-loader/package.json b/projects/list-loader/package.json index 1dd28eb6..f57b2bdc 100644 --- a/projects/list-loader/package.json +++ b/projects/list-loader/package.json @@ -31,8 +31,8 @@ "test:ci": "ng test list-loader --watch=false --browsers=ChromeHeadless", "build:ng": "ng build list-loader -c=production", "build": "nx build:ng @hug/ngx-list-loader --verbose", - "release": "nx release -p=@hug/ngx-list-loader --yes --verbose", - "release:dry-run": "nx release -p=@hug/ngx-list-loader --verbose --dry-run" + "release": "node -r @swc-node/register ../../scripts/release.ts --projects=@hug/ngx-list-loader --verbose", + "release:dry-run": "npm run release -- --dry-run" }, "peerDependencies": { "@angular/common": ">= 14", diff --git a/projects/message-box-dialog/package.json b/projects/message-box-dialog/package.json index 329a955e..32f79737 100644 --- a/projects/message-box-dialog/package.json +++ b/projects/message-box-dialog/package.json @@ -26,8 +26,8 @@ "test:ci": "ng test message-box-dialog --watch=false --browsers=ChromeHeadless", "build:ng": "ng build message-box-dialog -c=production", "build": "nx build:ng @hug/ngx-message-box-dialog --verbose", - "release": "nx release -p=@hug/ngx-message-box-dialog --yes --verbose", - "release:dry-run": "nx release -p=@hug/ngx-message-box-dialog --verbose --dry-run" + "release": "node -r @swc-node/register ../../scripts/release.ts --projects=@hug/ngx-message-box-dialog --verbose", + "release:dry-run": "npm run release -- --dry-run" }, "peerDependencies": { "@angular/common": ">= 14", diff --git a/projects/message-box/package.json b/projects/message-box/package.json index 09857b44..aeb32b86 100644 --- a/projects/message-box/package.json +++ b/projects/message-box/package.json @@ -31,8 +31,8 @@ "test:ci": "ng test message-box --watch=false --browsers=ChromeHeadless", "build:ng": "ng build message-box -c=production", "build": "nx build:ng @hug/ngx-message-box --verbose", - "release": "nx release -p=@hug/ngx-message-box --yes --verbose", - "release:dry-run": "nx release -p=@hug/ngx-message-box --verbose --dry-run" + "release": "node -r @swc-node/register ../../scripts/release.ts --projects=@hug/ngx-message-box --verbose", + "release:dry-run": "npm run release -- --dry-run" }, "peerDependencies": { "@angular/common": ">= 14", diff --git a/projects/numeric-stepper/package.json b/projects/numeric-stepper/package.json index 1fed2400..9a1864cc 100644 --- a/projects/numeric-stepper/package.json +++ b/projects/numeric-stepper/package.json @@ -31,8 +31,8 @@ "test:ci": "ng test numeric-stepper --watch=false --browsers=ChromeHeadless", "build:ng": "ng build numeric-stepper -c=production", "build": "nx build:ng @hug/ngx-numeric-stepper --verbose", - "release": "nx release -p=@hug/ngx-numeric-stepper --yes --verbose", - "release:dry-run": "nx release -p=@hug/ngx-numeric-stepper --verbose --dry-run" + "release": "node -r @swc-node/register ../../scripts/release.ts --projects=@hug/ngx-numeric-stepper --verbose", + "release:dry-run": "npm run release -- --dry-run" }, "peerDependencies": { "@angular/common": ">= 14", diff --git a/projects/overlay/package.json b/projects/overlay/package.json index a0732d58..8f350b04 100644 --- a/projects/overlay/package.json +++ b/projects/overlay/package.json @@ -26,8 +26,8 @@ "test:ci": "ng test overlay --watch=false --browsers=ChromeHeadless", "build:ng": "ng build overlay -c=production", "build": "nx build:ng @hug/ngx-overlay --verbose", - "release": "nx release -p=@hug/ngx-overlay --yes --verbose", - "release:dry-run": "nx release -p=@hug/ngx-overlay --verbose --dry-run" + "release": "node -r @swc-node/register ../../scripts/release.ts --projects=@hug/ngx-overlay --verbose", + "release:dry-run": "npm run release -- --dry-run" }, "peerDependencies": { "@angular/common": ">= 14", diff --git a/projects/search-container/package.json b/projects/search-container/package.json index 00964aec..1da87c1f 100644 --- a/projects/search-container/package.json +++ b/projects/search-container/package.json @@ -26,8 +26,8 @@ "test:ci": "ng test search-container --watch=false --browsers=ChromeHeadless", "build:ng": "ng build search-container -c=production", "build": "nx build:ng @hug/ngx-search-container --verbose", - "release": "nx release -p=@hug/ngx-search-container --yes --verbose", - "release:dry-run": "nx release -p=@hug/ngx-search-container --verbose --dry-run" + "release": "node -r @swc-node/register ../../scripts/release.ts --projects=@hug/ngx-search-container --verbose", + "release:dry-run": "npm run release -- --dry-run" }, "peerDependencies": { "@angular/common": ">= 14", diff --git a/projects/sidenav/package.json b/projects/sidenav/package.json index e7348d99..fd5dde71 100644 --- a/projects/sidenav/package.json +++ b/projects/sidenav/package.json @@ -26,8 +26,8 @@ "test:ci": "ng test sidenav --watch=false --browsers=ChromeHeadless", "build:ng": "ng build sidenav -c=production", "build": "nx build:ng @hug/ngx-sidenav --verbose", - "release": "nx release -p=@hug/ngx-sidenav --yes --verbose", - "release:dry-run": "nx release -p=@hug/ngx-sidenav --verbose --dry-run" + "release": "node -r @swc-node/register ../../scripts/release.ts --projects=@hug/ngx-sidenav --verbose", + "release:dry-run": "npm run release -- --dry-run" }, "peerDependencies": { "@angular/common": ">= 14", diff --git a/projects/snackbar/package.json b/projects/snackbar/package.json index ad45ca3e..cba433ab 100644 --- a/projects/snackbar/package.json +++ b/projects/snackbar/package.json @@ -26,8 +26,8 @@ "test:ci": "ng test snackbar --watch=false --browsers=ChromeHeadless", "build:ng": "ng build snackbar -c=production", "build": "nx build:ng @hug/ngx-snackbar --verbose", - "release": "nx release -p=@hug/ngx-snackbar --yes --verbose", - "release:dry-run": "nx release -p=@hug/ngx-snackbar --verbose --dry-run" + "release": "node -r @swc-node/register ../../scripts/release.ts --projects=@hug/ngx-snackbar --verbose", + "release:dry-run": "npm run release -- --dry-run" }, "peerDependencies": { "@angular/common": ">= 14", diff --git a/projects/splitter/package.json b/projects/splitter/package.json index b9f6dce0..165de1b5 100644 --- a/projects/splitter/package.json +++ b/projects/splitter/package.json @@ -31,8 +31,8 @@ "test:ci": "ng test splitter --watch=false --browsers=ChromeHeadless", "build:ng": "ng build splitter -c=production", "build": "nx build:ng @hug/ngx-splitter --verbose", - "release": "nx release -p=@hug/ngx-splitter --yes --verbose", - "release:dry-run": "nx release -p=@hug/ngx-splitter --verbose --dry-run" + "release": "node -r @swc-node/register ../../scripts/release.ts --projects=@hug/ngx-splitter --verbose", + "release:dry-run": "npm run release -- --dry-run" }, "peerDependencies": { "@angular/common": ">= 14", diff --git a/projects/status/package.json b/projects/status/package.json index f29845f2..80838a93 100644 --- a/projects/status/package.json +++ b/projects/status/package.json @@ -31,8 +31,8 @@ "test:ci": "ng test status --watch=false --browsers=ChromeHeadless", "build:ng": "ng build status -c=production", "build": "nx build:ng @hug/ngx-status --verbose", - "release": "nx release -p=@hug/ngx-status --yes --verbose", - "release:dry-run": "nx release -p=@hug/ngx-status --verbose --dry-run" + "release": "node -r @swc-node/register ../../scripts/release.ts --projects=@hug/ngx-status --verbose", + "release:dry-run": "npm run release -- --dry-run" }, "peerDependencies": { "@angular/common": ">= 14", diff --git a/projects/time-picker/package.json b/projects/time-picker/package.json index 3e195931..93c14ca2 100644 --- a/projects/time-picker/package.json +++ b/projects/time-picker/package.json @@ -26,8 +26,8 @@ "test:ci": "ng test time-picker --watch=false --browsers=ChromeHeadless", "build:ng": "ng build time-picker -c=production", "build": "nx build:ng @hug/ngx-time-picker --verbose", - "release": "nx release -p=@hug/ngx-time-picker --yes --verbose", - "release:dry-run": "nx release -p=@hug/ngx-time-picker --verbose --dry-run" + "release": "node -r @swc-node/register ../../scripts/release.ts --projects=@hug/ngx-time-picker --verbose", + "release:dry-run": "npm run release -- --dry-run" }, "peerDependencies": { "@angular/common": ">= 14", diff --git a/projects/tooltip/package.json b/projects/tooltip/package.json index efc776b6..bde9b13f 100644 --- a/projects/tooltip/package.json +++ b/projects/tooltip/package.json @@ -26,8 +26,8 @@ "test:ci": "ng test tooltip --watch=false --browsers=ChromeHeadless", "build:ng": "ng build tooltip -c=production", "build": "nx build:ng @hug/ngx-tooltip --verbose", - "release": "nx release -p=@hug/ngx-tooltip --yes --verbose", - "release:dry-run": "nx release -p=@hug/ngx-tooltip --verbose --dry-run" + "release": "node -r @swc-node/register ../../scripts/release.ts --projects=@hug/ngx-tooltip --verbose", + "release:dry-run": "npm run release -- --dry-run" }, "peerDependencies": { "@angular/common": ">= 14", diff --git a/projects/user-card/package.json b/projects/user-card/package.json index ed3be12c..d76f58f2 100644 --- a/projects/user-card/package.json +++ b/projects/user-card/package.json @@ -26,8 +26,8 @@ "test:ci": "ng test user-card --watch=false --browsers=ChromeHeadless", "build:ng": "ng build user-card -c=production", "build": "nx build:ng @hug/ngx-user-card --verbose", - "release": "nx release -p=@hug/ngx-user-card --yes --verbose", - "release:dry-run": "nx release -p=@hug/ngx-user-card --verbose --dry-run" + "release": "node -r @swc-node/register ../../scripts/release.ts --projects=@hug/ngx-user-card --verbose", + "release:dry-run": "npm run release -- --dry-run" }, "peerDependencies": { "@angular/common": ">= 14", diff --git a/projects/user-tooltip/package.json b/projects/user-tooltip/package.json index 3711d4b6..76fc3121 100644 --- a/projects/user-tooltip/package.json +++ b/projects/user-tooltip/package.json @@ -26,8 +26,8 @@ "test:ci": "ng test user-tooltip --watch=false --browsers=ChromeHeadless", "build:ng": "ng build user-tooltip -c=production", "build": "nx build:ng @hug/ngx-user-tooltip --verbose", - "release": "nx release -p=@hug/ngx-user-tooltip --yes --verbose", - "release:dry-run": "nx release -p=@hug/ngx-user-tooltip --verbose --dry-run" + "release": "node -r @swc-node/register ../../scripts/release.ts --projects=@hug/ngx-user-tooltip --verbose", + "release:dry-run": "npm run release -- --dry-run" }, "peerDependencies": { "@angular/common": ">= 14", diff --git a/scripts/release.ts b/scripts/release.ts new file mode 100644 index 00000000..00979c2d --- /dev/null +++ b/scripts/release.ts @@ -0,0 +1,237 @@ +/* eslint-disable @typescript-eslint/naming-convention, no-loops/no-loops, camelcase */ + +import chalk from 'chalk'; +import { execSync } from 'node:child_process'; +import { copyFileSync, readFileSync, writeFileSync } from 'node:fs'; +import { join } from 'node:path'; +import { releaseChangelog, releasePublish, releaseVersion } from 'nx/release'; +import { PublishOptions } from 'nx/src/command-line/release/command-object'; +import { createProjectGraphAsync, readProjectsConfigurationFromProjectGraph } from 'nx/src/project-graph/project-graph'; +import { PackageJson } from 'nx/src/utils/package-json'; +import { workspaceRoot } from 'nx/src/utils/workspace-root'; +import * as yargs from 'yargs'; + +const { yellow, blue, red, green, gray, white, bgBlue } = chalk; + +void (async (): Promise => { + const options = await yargs + .version(false) + .option('projects', { + description: 'Projects filter to use for the release script', + type: 'array', + string: true, + default: [] + }) + .option('dry-run', { + description: 'Whether or not to perform a dry-run of the release process, defaults to false', + type: 'boolean', + default: false + }) + .option('verbose', { + description: 'Whether or not to enable verbose logging, defaults to false', + type: 'boolean', + default: false + }) + .parseAsync(); + + /** + * 1. Resolve new project version + * 2. Update `projects//package.json` with new version + * 3. Update npm lock file + * 4. Stage changed files with git + */ + const { workspaceVersion, projectsVersionData } = await releaseVersion({ + projects: options.projects, + stageChanges: true, + gitCommit: false, + dryRun: options.dryRun, + verbose: options.verbose + }); + + /** + * 5. Determine affected projects and exit if none + */ + const projectsToRelease = Object.keys(projectsVersionData).filter(key => { + const { newVersion, currentVersion } = projectsVersionData[key]; + return (newVersion && (newVersion !== currentVersion)); + }); + if (projectsToRelease.length === 0) { + console.log('No affected projects found to be published'); + return process.exit(0); + } + + /** + * 6. Update `projects//CHANGELOG.md` + * 7. Stage changed files with git + * 8. Commit all previously staged files in git + * chore(release): publish [skip ci] + * - project: @hug/ngx-xyz 1.2.3 + * 9. Tag commit with git + * @hug/ngx-xyz@1.2.3 + * 10. Push to git remote + * 11. Create GitHub release + */ + await releaseChangelog({ + projects: options.projects, + version: workspaceVersion, + versionData: projectsVersionData, + stageChanges: true, + gitCommit: true, + gitCommitMessage: 'chore(release): publish [skip ci]', + gitTag: true, + dryRun: options.dryRun, + verbose: options.verbose + }); + + /** + * Ensures consistent versioning across interdependent packages in the monorepo. + * + * It reads the current version of each package and updates any other packages that reference + * it in their `peerDependencies` to maintain version synchronization. + * + * TODO: remove this script if one day this feature is supported by `nx release` directly + * @see https://github.com/nrwl/nx/issues/22776 + * @see https://github.com/nrwl/nx/discussions/23388 + * + * 12. Synchronize interdependencies + * - Update project's package.json file + * - Update npm lock file + * - Stage changed files with git + * - Commit all previously staged files in git + * deps(@hug/ngx-xyz): upgrade to v1.2.3 + * [skip ci] + * 13. Push to git remote + */ + const exec = (message: string, cmd: string): void => { + console.log(message); + if (options.verbose) { + console.log(cmd); + } + if (!options.dryRun) { + execSync(cmd, { cwd: workspaceRoot, stdio: 'inherit' }); + } + }; + const projectGraph = await createProjectGraphAsync({ exitOnError: true }); + const { projects } = readProjectsConfigurationFromProjectGraph(projectGraph); + const workspaces = Object.values(projects).map(project => ({ + packageJsonPath: join(project.root, 'package.json'), + packageJson: JSON.parse(readFileSync(join(workspaceRoot, project.root, 'package.json'), 'utf8')) as PackageJson + })); + let packageJsonFiles: string[] = []; + let changesDetected = false; + + console.log(`\n${bgBlue(' HUG ')} ${blue('Synchronizing peer interdependencies')}${options.dryRun ? yellow(' [dry-run]') : ''}`); + workspaces.forEach(workspace => { + workspaces.forEach(workspace2 => { + const peerDependencies = workspace2.packageJson.peerDependencies ?? {}; + if (Object.prototype.hasOwnProperty.call(peerDependencies, workspace.packageJson.name)) { + const version = peerDependencies[workspace.packageJson.name]; + if (!version.includes(workspace.packageJson.version)) { + changesDetected = true; + + const versionRange = version.match(/(^[^\d]*)\d.*/)?.[1] ?? ''; + const newVersion = `${versionRange}${workspace.packageJson.version}`; + + if (!packageJsonFiles.length) { + console.log(`\n- ${blue(workspace.packageJson.name)}`); + } + + console.log(`\n${white('UPDATE')} ${workspace2.packageJsonPath}${options.dryRun ? yellow(' [dry-run]') : ''}\n`); + console.log(gray(' "peerDependencies": {')); + console.log(red(`- "${workspace.packageJson.name}": "${version}"`)); + console.log(green(`+ "${workspace.packageJson.name}": "${newVersion}"`)); + console.log(gray(' }')); + if (!options.dryRun) { + peerDependencies[workspace.packageJson.name] = newVersion; + workspace2.packageJson.peerDependencies = peerDependencies; + writeFileSync(join(workspaceRoot, workspace2.packageJsonPath), JSON.stringify(workspace2.packageJson, null, 4), { encoding: 'utf8' }); + } + + packageJsonFiles.push(workspace2.packageJsonPath); + } + } + }); + + if (packageJsonFiles.length) { + exec( + `\n${bgBlue(' HUG ')} ${blue('Updating npm lock file')}${options.dryRun ? yellow(' [dry-run]') : ''}\n`, + 'npm install' + ); + exec( + `\n${bgBlue(' HUG ')} ${blue('Staging changed files with git')}${options.dryRun ? yellow(' [dry-run]') : ''}\n`, + `git add package-lock.json ${packageJsonFiles.join(' ')}` + ); + exec( + `\n${bgBlue(' HUG ')} ${blue('Comitting changes with git')}${options.dryRun ? yellow(' [dry-run]') : ''}\n`, + `git commit --message deps(${workspace.packageJson.name}): upgrade to v${workspace.packageJson.version} --message [skip ci]` + ); + packageJsonFiles = []; + } + }); + if (changesDetected) { + exec( + `\n${bgBlue(' HUG ')} ${blue('Pushing to git remote')}${options.dryRun ? yellow(' [dry-run]') : ''}\n`, + 'git push --follow-tags --no-verify --atomic' + ); + } else { + console.log('\nNo changes were needed, versions already in sync.'); + } + + /** + * Currently `nx release` publishes packages from their source directory by default. + * + * So we need to make sure `dist` are in sync and published instead. + * + * TODO: remove this script if one day this feature is supported by `nx release` directly + * @see https://github.com/nrwl/nx/issues/21855#issuecomment-1977360480 + * + * 14. Update project(s) `package.json` and `CHANGELOG.md` in dist + */ + console.log(`\n${bgBlue(' HUG ')} ${blue('Synchronizing dist packages')}${options.dryRun ? yellow(' [dry-run]') : ''}`); + projectsToRelease.forEach(project => { + const projectRoot = projects[project].root; + const projectName = projectRoot.substring('projects/'.length); + const projectNewVersion = projectsVersionData[project].newVersion ?? ''; + const distPackageJsonPath = join('dist', projectName, 'package.json'); + const distChangelogPath = join('dist', projectName, 'CHANGELOG.md'); + + console.log(`\n${blue(projects[project].name ?? '')} New version ${projectNewVersion} written to ${distPackageJsonPath}`); + if (!options.dryRun) { + const distPackageJson = JSON.parse(readFileSync(join(workspaceRoot, projectRoot, 'package.json'), 'utf8')) as PackageJson; + distPackageJson.name = projectNewVersion; + writeFileSync(join(workspaceRoot, distPackageJsonPath), JSON.stringify(distPackageJson, null, 4), { encoding: 'utf8' }); + } + + console.log(`${blue(projects[project].name ?? '')} Changelog updated in ${distChangelogPath}`); + if (!options.dryRun) { + copyFileSync(join(workspaceRoot, projectRoot, 'CHANGELOG.md'), join(workspaceRoot, distChangelogPath)); + } + }); + + /** + * Currently `nx release` does not allow to easily change the folder to be published. + * + * `nx.json#targetDefaults.nx-release-publish.options.packageRoot` could be used but can only interpolate: + * - {projectName}: which resolved to '@hug/ngx-xyz' (ie. package.json#name) + * - {projectRoot}: which resolved to 'projects/xyz' + * And what we need is actually `xyz` because Angular generates projects in `dist/xyz`. + * So to make it work, we use the hidden option (__overrides_unparsed__) and publish each project individually. + * + * 15. Publish to npm + */ + let processStatus = 0; + for (const project of projectsToRelease) { + const projectName = projects[project].root.substring('projects/'.length); + const publishStatus = await releasePublish({ + __overrides_unparsed__: `--packageRoot=./dist/${projectName}`, + projects: [project], + dryRun: options.dryRun, + verbose: options.verbose + } as PublishOptions); + if (publishStatus !== 0) { + processStatus = publishStatus; + } + } + + return process.exit(processStatus); +})(); diff --git a/scripts/sync-peer-deps.mjs b/scripts/sync-peer-deps.mjs deleted file mode 100644 index 5d34732c..00000000 --- a/scripts/sync-peer-deps.mjs +++ /dev/null @@ -1,110 +0,0 @@ -/** - * Automated script that ensures consistent versioning across interdependent packages in a monorepo. - * - * This script reads the current version of each package and updates any other packages that reference - * it in their `peerDependencies` to maintain version synchronization. - * - * @example: $ node ./sync-peer-deps.mjs [--dry-run] - * - * TODO: remove this script if one day this feature is supported by `nx release` directly - * @see https://github.com/nrwl/nx/issues/22776 - * @see https://github.com/nrwl/nx/discussions/23388 - */ - -import chalk from 'chalk'; -import { spawnSync } from 'node:child_process'; -import { readFileSync, writeFileSync } from 'node:fs'; -import { dirname, resolve } from 'node:path'; -import { fileURLToPath } from 'node:url'; - -const { yellow, blue, red, green, gray, white, bgBlue, bgWhite } = chalk; - -const __dirname = dirname(fileURLToPath(import.meta.url)); -const rootPath = resolve(__dirname, '..'); - -const dryRun = (process.argv[2] === '--dry-run'); - -const execCommand = (command, args) => { - console.log(`\n${command} ${args.join(' ')}`); - if (!dryRun) { - const result = spawnSync(command, args, { stdio: 'inherit' }); - if (result.error) { - throw result.error; - } - if (result.status !== 0) { - throw new Error(`Command failed with exit code ${result.status}`); - } - } -}; - -const getWorkspaces = () => { - const rootPackageJson = JSON.parse(readFileSync(resolve(rootPath, 'package.json'), 'utf8')); - if (!rootPackageJson.workspaces) { - console.error(red('No workspaces found in package.json')); - } - return (rootPackageJson.workspaces || []).map(workspace => ({ - packageJsonPath: resolve(rootPath, workspace, 'package.json'), - packageJson: JSON.parse(readFileSync(resolve(rootPath, workspace, 'package.json'), 'utf8')) - })); -}; - -(() => { - console.log(`\n${bgBlue(' > ')} Synchronizing peer dependencies`); - let changesDetected = false; - let packageJsonFiles = []; - const workspaces = getWorkspaces(); - workspaces.forEach(workspace => { - workspaces.forEach(workspace2 => { - const peerDependencies = workspace2.packageJson.peerDependencies || {}; - if (Object.hasOwn(peerDependencies, workspace.packageJson.name)) { - const version = peerDependencies[workspace.packageJson.name]; - if (!version.includes(workspace.packageJson.version)) { - changesDetected = true; - - const versionRange = version.match(/(^[^\d]*)\d.*/)[1]; - const newVersion = `${versionRange}${workspace.packageJson.version}`; - - if (!packageJsonFiles.length) { - console.log(`\n${blue(workspace.packageJson.name)}`); - } - - console.log(`\n${bgWhite(' > ')} ${white('UPDATE')} ${workspace2.packageJsonPath}${dryRun ? yellow(' [dry-run]') : ''}\n`); - console.log(gray(' "peerDependencies": {')); - console.log(red(`- "${workspace.packageJson.name}": "${version}"`)); - console.log(green(`+ "${workspace.packageJson.name}": "${newVersion}"`)); - console.log(gray(' }')); - if (!dryRun) { - workspace2.packageJson.peerDependencies[workspace.packageJson.name] = newVersion; - writeFileSync(workspace2.packageJsonPath, JSON.stringify(workspace2.packageJson, null, 4), { encoding: 'utf8' }); - } - - packageJsonFiles.push(workspace2.packageJsonPath); - } - } - }); - - if (packageJsonFiles.length) { - console.log(`\n${bgWhite(' > ')} Updating npm lock file${dryRun ? yellow(' [dry-run]') : ''}`); - execCommand('npm', ['install']); - - console.log(`\n${bgWhite(' > ')} Staging changed files with git${dryRun ? yellow(' [dry-run]') : ''}`); - execCommand('git', ['add', resolve(rootPath, 'package-lock.json'), ...packageJsonFiles]); - - console.log(`\n${bgWhite(' > ')} Committing changes with git${dryRun ? yellow(' [dry-run]') : ''}`); - execCommand('git', ['commit', '--message', `deps(${workspace.packageJson.name}): upgrade to v${workspace.packageJson.version} [skip ci]`]); - - packageJsonFiles = []; - } - }); - - if (changesDetected) { - console.log(`\n${bgWhite(' > ')} Pushing to git remote${dryRun ? yellow(' [dry-run]') : ''}`); - execCommand('git', ['push', '--follow-tags', '--no-verify', '--atomic']); - } else { - console.log('\nNo changes were needed, versions already in sync.'); - } - - if (dryRun) { - console.log(yellow('\nNOTE: The "--dry-run" flag means no changes were made.\n')); - } -})();