From 8e153cce88694ef3b501d6f468b6d777c172bac6 Mon Sep 17 00:00:00 2001 From: Badisi Date: Fri, 21 Jun 2024 16:56:20 +0200 Subject: [PATCH] ci(release): add sync-peer-deps step --- .github/workflows/ci_release.yml | 21 ++++++ nx.json | 46 +++++++++++-- projects/numeric-stepper/package.json | 2 +- scripts/sync-peer-deps.mjs | 97 +++++++++++++++++++++++++++ 4 files changed, 160 insertions(+), 6 deletions(-) create mode 100644 scripts/sync-peer-deps.mjs diff --git a/.github/workflows/ci_release.yml b/.github/workflows/ci_release.yml index 3592a830..87bd174b 100644 --- a/.github/workflows/ci_release.yml +++ b/.github/workflows/ci_release.yml @@ -26,6 +26,7 @@ on: env: HUSKY: 0 + FORCE_COLOR: 3 jobs: ci_release: @@ -39,3 +40,23 @@ 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: + persist-credentials: false + + - 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 diff --git a/nx.json b/nx.json index 772492fd..c35248dd 100644 --- a/nx.json +++ b/nx.json @@ -14,19 +14,55 @@ }, "conventionalCommits": { "types": { - "refactor": { + "feat": { + "semverBump": "minor", + "changelog": { + "title": "🚀 Features" + } + }, + "fix": { "semverBump": "patch", - "changelog": false + "changelog": { + "title": "🐛 Fixes" + } }, - "styles": { + "perf": { "semverBump": "patch", - "changelog": false + "changelog": { + "title": "🔥 Performance" + } }, "docs": { "semverBump": "patch", "changelog": { - "title": "Documentation Changes" + "title": "📖 Documentation" } + }, + "chore": { + "semverBump": "patch", + "changelog": { + "title": "🛠️ Chore" + } + }, + "refactor": { + "semverBump": "patch", + "changelog": false + }, + "types": { + "semverBump": "patch", + "changelog": false + }, + "style": { + "semverBump": "patch", + "changelog": false + }, + "test": { + "semverBump": "patch", + "changelog": false + }, + "revert": { + "semverBump": "patch", + "changelog": false } } }, diff --git a/projects/numeric-stepper/package.json b/projects/numeric-stepper/package.json index 2d7b1d04..e3146a9f 100644 --- a/projects/numeric-stepper/package.json +++ b/projects/numeric-stepper/package.json @@ -45,4 +45,4 @@ "dependencies": { "tslib": "^2.6.3" } -} +} \ No newline at end of file diff --git a/scripts/sync-peer-deps.mjs b/scripts/sync-peer-deps.mjs new file mode 100644 index 00000000..b27413a9 --- /dev/null +++ b/scripts/sync-peer-deps.mjs @@ -0,0 +1,97 @@ +/** + * 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; + 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}`; + + console.log(blue.bold(`\n[${workspace2.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' }); + } + + console.log(`\n${bgWhite(' > ')} Staging changed files with git${dryRun ? yellow(' [dry-run]') : ''}`); + execCommand('git', ['add', workspace2.packageJsonPath]); + + console.log(`\n${bgWhite(' > ')} Committing changes with git${dryRun ? yellow(' [dry-run]') : ''}`); + execCommand('git', ['commit', '--message', `chore(deps): upgrade ${workspace.packageJson.name} to v${workspace.packageJson.version}`]); + } + } + }); + }); + + 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')); + } +})();