From 7b85d912bab825691cbfd1fc11cf88bb8e0c056c Mon Sep 17 00:00:00 2001 From: James Henry Date: Tue, 8 Apr 2025 15:17:19 +0100 Subject: [PATCH] feat(release): revamped nx release version implementation (#30418) --- docs/generated/cli/release.md | 2 + docs/generated/devkit/ProjectConfiguration.md | 6 +- .../js/generators/release-version.json | 27 +- .../packages/nx/documents/release.md | 2 + e2e/release/src/circular-dependencies.test.ts | 133 +- .../src/conventional-commits-config.test.ts | 120 +- e2e/release/src/custom-registries.test.ts | 48 +- e2e/release/src/first-release.test.ts | 16 +- e2e/release/src/independent-projects.test.ts | 96 +- .../src/multiple-release-branches.test.ts | 161 +- e2e/release/src/pre-version-command.test.ts | 4 +- ...reserve-local-dependency-protocols.test.ts | 47 +- e2e/release/src/private-js-packages.test.ts | 8 +- ...-publishable-libraries-ts-solution.test.ts | 21 +- .../src/release-publishable-libraries.test.ts | 28 +- e2e/release/src/release.test.ts | 224 +- e2e/release/src/version-plans-check.test.ts | 2 + .../src/version-plans-only-touched.test.ts | 4 +- e2e/release/src/version-plans.test.ts | 156 +- e2e/utils/command-utils.ts | 8 + package.json | 1 + .../src/generators/library/lib/add-project.ts | 13 +- packages/js/src/generators/library/library.ts | 7 +- .../library/utils/add-release-config.spec.ts | 86 +- .../library/utils/add-release-config.ts | 26 +- ...ase-version-workspace-root-project.spec.ts | 5 +- .../release-version/release-version.spec.ts | 265 +- .../release-version/release-version.ts | 17 +- .../generators/release-version/schema.d.ts | 2 +- .../generators/release-version/schema.json | 27 +- .../utils/update-lock-file.ts | 31 +- packages/js/src/release/version-actions.ts | 273 ++ packages/nx/.eslintrc.json | 4 +- packages/nx/release/index.ts | 2 + packages/nx/schemas/nx-schema.json | 166 +- .../nx/src/command-line/release/changelog.ts | 39 +- .../command-line/release/command-object.ts | 6 + .../release/config/config.spec.ts | 535 +++- .../src/command-line/release/config/config.ts | 230 +- .../config/filter-release-groups.spec.ts | 4 + .../release/config/use-legacy-versioning.ts | 10 + packages/nx/src/command-line/release/index.ts | 8 + .../nx/src/command-line/release/plan-check.ts | 14 +- packages/nx/src/command-line/release/plan.ts | 15 +- .../nx/src/command-line/release/publish.ts | 19 +- .../nx/src/command-line/release/release.ts | 16 +- .../batch-projects-by-generator-config.ts | 12 +- .../nx/src/command-line/release/utils/git.ts | 11 +- .../src/command-line/release/utils/github.ts | 4 +- .../release/utils/resolve-semver-specifier.ts | 8 +- .../src/command-line/release/utils/semver.ts | 8 + .../release/utils/shared-legacy.ts | 36 + .../command-line/release/utils/shared.spec.ts | 1 + .../src/command-line/release/utils/shared.ts | 54 +- .../command-line/release/version-legacy.ts | 756 +++++ .../nx/src/command-line/release/version.ts | 616 +--- ...ive-specifier-from-conventional-commits.ts | 85 + .../deriver-specifier-from-version-plans.ts | 93 + .../version/multiple-release-groups.spec.ts | 892 ++++++ .../release/version/project-logger.ts | 52 + .../version/release-group-processor.spec.ts | 1676 +++++++++++ .../version/release-group-processor.ts | 1635 ++++++++++ .../release/version/release-version.spec.ts | 2646 +++++++++++++++++ .../version/resolve-current-version.spec.ts | 206 ++ .../version/resolve-current-version.ts | 410 +++ .../release/version/test-utils.spec.ts | 157 + .../release/version/test-utils.ts | 644 ++++ .../release/version/topological-sort.spec.ts | 174 ++ .../release/version/topological-sort.ts | 48 + .../release/version/version-actions.ts | 440 +++ packages/nx/src/config/nx-json.ts | 135 +- .../src/config/workspace-json-project-json.ts | 19 +- .../react/src/generators/library/library.ts | 46 +- .../vue/src/generators/library/library.ts | 40 +- pnpm-lock.yaml | 671 ++--- 75 files changed, 12676 insertions(+), 1833 deletions(-) rename packages/js/src/{generators/release-version => release}/utils/update-lock-file.ts (80%) create mode 100644 packages/js/src/release/version-actions.ts create mode 100644 packages/nx/src/command-line/release/config/use-legacy-versioning.ts create mode 100644 packages/nx/src/command-line/release/utils/shared-legacy.ts create mode 100644 packages/nx/src/command-line/release/version-legacy.ts create mode 100644 packages/nx/src/command-line/release/version/derive-specifier-from-conventional-commits.ts create mode 100644 packages/nx/src/command-line/release/version/deriver-specifier-from-version-plans.ts create mode 100644 packages/nx/src/command-line/release/version/multiple-release-groups.spec.ts create mode 100644 packages/nx/src/command-line/release/version/project-logger.ts create mode 100644 packages/nx/src/command-line/release/version/release-group-processor.spec.ts create mode 100644 packages/nx/src/command-line/release/version/release-group-processor.ts create mode 100644 packages/nx/src/command-line/release/version/release-version.spec.ts create mode 100644 packages/nx/src/command-line/release/version/resolve-current-version.spec.ts create mode 100644 packages/nx/src/command-line/release/version/resolve-current-version.ts create mode 100644 packages/nx/src/command-line/release/version/test-utils.spec.ts create mode 100644 packages/nx/src/command-line/release/version/test-utils.ts create mode 100644 packages/nx/src/command-line/release/version/topological-sort.spec.ts create mode 100644 packages/nx/src/command-line/release/version/topological-sort.ts create mode 100644 packages/nx/src/command-line/release/version/version-actions.ts diff --git a/docs/generated/cli/release.md b/docs/generated/cli/release.md index 15292c91ab..ac1dd0ac1d 100644 --- a/docs/generated/cli/release.md +++ b/docs/generated/cli/release.md @@ -66,6 +66,7 @@ nx release version [specifier] | `--git-commit-args` | string | Additional arguments (added after the --message argument, which may or may not be customized with --git-commit-message) to pass to the `git commit` command invoked behind the scenes. | | `--git-commit-message` | string | Custom git commit message to use when committing the changes made by this command. {version} will be dynamically interpolated when performing fixed releases, interpolated tags will be appended to the commit body when performing independent releases. | | `--git-push` | boolean | Whether or not to automatically push the changes made by this command to the remote git repository. | +| `--git-push-args` | string | Additional arguments to pass to the `git push` command invoked behind the scenes. | | `--git-remote` | string | Alternate git remote to push commits and tags to (can be useful for testing). (Default: `origin`) | | `--git-tag` | boolean | Whether or not to automatically tag the changes made by this command. | | `--git-tag-args` | string | Additional arguments to pass to the `git tag` command invoked behind the scenes. | @@ -94,6 +95,7 @@ nx release changelog [version] | `--git-commit-args` | string | Additional arguments (added after the --message argument, which may or may not be customized with --git-commit-message) to pass to the `git commit` command invoked behind the scenes. | | `--git-commit-message` | string | Custom git commit message to use when committing the changes made by this command. {version} will be dynamically interpolated when performing fixed releases, interpolated tags will be appended to the commit body when performing independent releases. | | `--git-push` | boolean | Whether or not to automatically push the changes made by this command to the remote git repository. | +| `--git-push-args` | string | Additional arguments to pass to the `git push` command invoked behind the scenes. | | `--git-remote` | string | Alternate git remote to push commits and tags to (can be useful for testing). (Default: `origin`) | | `--git-tag` | boolean | Whether or not to automatically tag the changes made by this command. | | `--git-tag-args` | string | Additional arguments to pass to the `git tag` command invoked behind the scenes. | diff --git a/docs/generated/devkit/ProjectConfiguration.md b/docs/generated/devkit/ProjectConfiguration.md index a086b3e3d5..e61c35921a 100644 --- a/docs/generated/devkit/ProjectConfiguration.md +++ b/docs/generated/devkit/ProjectConfiguration.md @@ -98,9 +98,9 @@ Project specific configuration for `nx release` #### Type declaration -| Name | Type | -| :--------- | :------------------------------------------------------------------------------- | -| `version?` | `Pick`\<`NxReleaseVersionConfiguration`, `"generator"` \| `"generatorOptions"`\> | +| Name | Type | +| :--------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `version?` | `Pick`\<`NxReleaseVersionConfiguration`, `"generator"` \| `"generatorOptions"`\> \| `Pick`\<`NxReleaseVersionV2Configuration`, `"versionActions"` \| `"versionActionsOptions"` \| `"manifestRootsToUpdate"` \| `"currentVersionResolver"` \| `"currentVersionResolverMetadata"` \| `"fallbackCurrentVersionResolver"` \| `"versionPrefix"` \| `"preserveLocalDependencyProtocols"`\> | --- diff --git a/docs/generated/packages/js/generators/release-version.json b/docs/generated/packages/js/generators/release-version.json index 1e53ed5a61..09a1b496d4 100644 --- a/docs/generated/packages/js/generators/release-version.json +++ b/docs/generated/packages/js/generators/release-version.json @@ -11,21 +11,40 @@ "properties": { "projects": { "type": "array", + "hidden": true, + "$comment": "This is not configured/passed by the user. It is provided by the version command.", "description": "The ProjectGraphProjectNodes being versioned in the current execution.", "items": { "type": "object" } }, "projectGraph": { "type": "object", + "hidden": true, + "$comment": "This is not configured/passed by the user. It is provided by the version command.", "description": "ProjectGraph instance" }, + "releaseGroup": { + "type": "object", + "hidden": true, + "$comment": "This is not configured/passed by the user. It is provided by the version command.", + "description": "The resolved release group configuration, including name, relevant to all projects in the current execution." + }, + "conventionalCommitsConfig": { + "type": "object", + "hidden": true, + "$comment": "This is not configured/passed by the user. It is provided by the version command.", + "description": "The conventional commits configuration to use when determining the next version of the project.", + "default": {} + }, + "firstRelease": { + "type": "boolean", + "hidden": true, + "$comment": "This is not configured/passed by the user. It is provided by the version command.", + "description": "Whether this is the first release of the project (which skips some validation within the generator)." + }, "specifier": { "type": "string", "description": "Exact version or semver keyword to apply to the selected release group. Overrides specifierSource." }, - "releaseGroup": { - "type": "object", - "description": "The resolved release group configuration, including name, relevant to all projects in the current execution." - }, "specifierSource": { "type": "string", "default": "prompt", diff --git a/docs/generated/packages/nx/documents/release.md b/docs/generated/packages/nx/documents/release.md index 15292c91ab..ac1dd0ac1d 100644 --- a/docs/generated/packages/nx/documents/release.md +++ b/docs/generated/packages/nx/documents/release.md @@ -66,6 +66,7 @@ nx release version [specifier] | `--git-commit-args` | string | Additional arguments (added after the --message argument, which may or may not be customized with --git-commit-message) to pass to the `git commit` command invoked behind the scenes. | | `--git-commit-message` | string | Custom git commit message to use when committing the changes made by this command. {version} will be dynamically interpolated when performing fixed releases, interpolated tags will be appended to the commit body when performing independent releases. | | `--git-push` | boolean | Whether or not to automatically push the changes made by this command to the remote git repository. | +| `--git-push-args` | string | Additional arguments to pass to the `git push` command invoked behind the scenes. | | `--git-remote` | string | Alternate git remote to push commits and tags to (can be useful for testing). (Default: `origin`) | | `--git-tag` | boolean | Whether or not to automatically tag the changes made by this command. | | `--git-tag-args` | string | Additional arguments to pass to the `git tag` command invoked behind the scenes. | @@ -94,6 +95,7 @@ nx release changelog [version] | `--git-commit-args` | string | Additional arguments (added after the --message argument, which may or may not be customized with --git-commit-message) to pass to the `git commit` command invoked behind the scenes. | | `--git-commit-message` | string | Custom git commit message to use when committing the changes made by this command. {version} will be dynamically interpolated when performing fixed releases, interpolated tags will be appended to the commit body when performing independent releases. | | `--git-push` | boolean | Whether or not to automatically push the changes made by this command to the remote git repository. | +| `--git-push-args` | string | Additional arguments to pass to the `git push` command invoked behind the scenes. | | `--git-remote` | string | Alternate git remote to push commits and tags to (can be useful for testing). (Default: `origin`) | | `--git-tag` | boolean | Whether or not to automatically tag the changes made by this command. | | `--git-tag-args` | string | Additional arguments to pass to the `git tag` command invoked behind the scenes. | diff --git a/e2e/release/src/circular-dependencies.test.ts b/e2e/release/src/circular-dependencies.test.ts index 93bfa346e9..e9ee2d3bbe 100644 --- a/e2e/release/src/circular-dependencies.test.ts +++ b/e2e/release/src/circular-dependencies.test.ts @@ -104,9 +104,7 @@ describe('nx release circular dependencies', () => { nxJson.release = { projectsRelationship: 'fixed', version: { - generatorOptions: { - updateDependents: 'never', - }, + updateDependents: 'never', }, changelog: { // Enable project level changelogs for all examples @@ -124,19 +122,17 @@ describe('nx release circular dependencies', () => { NX Running release version for project: {project-name} - {project-name} 🔍 Reading data for package "@proj/{project-name}" from {project-name}/package.json - {project-name} 📄 Resolved the current version as 1.0.0 from {project-name}/package.json - {project-name} 📄 Using the provided version specifier "major". - {project-name} ✍️ New version 2.0.0 written to {project-name}/package.json - {project-name} ✍️ Applying new version 2.0.0 to 1 package which depends on {project-name} + {project-name} 📄 Resolved the current version as 1.0.0 from manifest: {project-name}/package.json + {project-name} ❓ Applied semver relative bump "major", from the given specifier, to get new version 2.0.0 + {project-name} ✍️ New version 2.0.0 written to manifest: {project-name}/package.json + {project-name} ✍️ Updated 1 dependency in manifest: {project-name}/package.json NX Running release version for project: {project-name} - {project-name} 🔍 Reading data for package "@proj/{project-name}" from {project-name}/package.json - {project-name} 📄 Resolved the current version as 1.0.0 from {project-name}/package.json - {project-name} 📄 Using the provided version specifier "major". - {project-name} ✍️ New version 2.0.0 written to {project-name}/package.json - {project-name} ✍️ Applying new version 2.0.0 to 1 package which depends on {project-name} + {project-name} 📄 Resolved the current version as 1.0.0 from manifest: {project-name}/package.json + {project-name} ❓ Applied version 2.0.0 directly, because the project is a member of a fixed release group containing {project-name} + {project-name} ✍️ New version 2.0.0 written to manifest: {project-name}/package.json + {project-name} ✍️ Updated 1 dependency in manifest: {project-name}/package.json "name": "@proj/{project-name}", @@ -289,9 +285,7 @@ describe('nx release circular dependencies', () => { nxJson.release = { projectsRelationship: 'fixed', version: { - generatorOptions: { - updateDependents: 'auto', - }, + updateDependents: 'auto', }, changelog: { // Enable project level changelogs for all examples @@ -305,23 +299,22 @@ describe('nx release circular dependencies', () => { `release major --verbose --first-release -y -d` ); + // TODO: Work on a way to remove some of the log noise in the circular dependency case (and in general the multiple updates to the same projects) expect(releaseOutput).toMatchInlineSnapshot(` NX Running release version for project: {project-name} - {project-name} 🔍 Reading data for package "@proj/{project-name}" from {project-name}/package.json - {project-name} 📄 Resolved the current version as 1.0.0 from {project-name}/package.json - {project-name} 📄 Using the provided version specifier "major". - {project-name} ✍️ New version 2.0.0 written to {project-name}/package.json - {project-name} ✍️ Applying new version 2.0.0 to 1 package which depends on {project-name} + {project-name} 📄 Resolved the current version as 1.0.0 from manifest: {project-name}/package.json + {project-name} ❓ Applied semver relative bump "major", from the given specifier, to get new version 2.0.0 + {project-name} ✍️ New version 2.0.0 written to manifest: {project-name}/package.json + {project-name} ✍️ Updated 1 dependency in manifest: {project-name}/package.json NX Running release version for project: {project-name} - {project-name} 🔍 Reading data for package "@proj/{project-name}" from {project-name}/package.json - {project-name} 📄 Resolved the current version as 1.0.0 from {project-name}/package.json - {project-name} 📄 Using the provided version specifier "major". - {project-name} ✍️ New version 2.0.0 written to {project-name}/package.json - {project-name} ✍️ Applying new version 2.0.0 to 1 package which depends on {project-name} + {project-name} 📄 Resolved the current version as 1.0.0 from manifest: {project-name}/package.json + {project-name} ❓ Applied version 2.0.0 directly, because the project is a member of a fixed release group containing {project-name} + {project-name} ✍️ New version 2.0.0 written to manifest: {project-name}/package.json + {project-name} ✍️ Updated 1 dependency in manifest: {project-name}/package.json "name": "@proj/{project-name}", @@ -474,9 +467,7 @@ describe('nx release circular dependencies', () => { nxJson.release = { projectsRelationship: 'independent', version: { - generatorOptions: { - updateDependents: 'never', - }, + updateDependents: 'never', }, changelog: { // Enable project level changelogs for all examples @@ -494,19 +485,19 @@ describe('nx release circular dependencies', () => { NX Running release version for project: {project-name} - {project-name} 🔍 Reading data for package "@proj/{project-name}" from {project-name}/package.json - {project-name} 📄 Resolved the current version as 1.0.0 from {project-name}/package.json - {project-name} 📄 Using the provided version specifier "major". - {project-name} ✍️ New version 2.0.0 written to {project-name}/package.json - {project-name} ✍️ Applying new version 2.0.0 to 1 package which depends on {project-name} + {project-name} 📄 Resolved the current version as 1.0.0 from manifest: {project-name}/package.json + {project-name} ❓ Applied semver relative bump "major", from the given specifier, to get new version 2.0.0 + {project-name} ✍️ New version 2.0.0 written to manifest: {project-name}/package.json + {project-name} ⏩ Skipping dependent updates as "updateDependents" is not "auto" + {project-name} ✍️ Updated 1 dependency in manifest: {project-name}/package.json NX Running release version for project: {project-name} - {project-name} 🔍 Reading data for package "@proj/{project-name}" from {project-name}/package.json - {project-name} 📄 Resolved the current version as 1.0.0 from {project-name}/package.json - {project-name} 📄 Using the provided version specifier "major". - {project-name} ✍️ New version 2.0.0 written to {project-name}/package.json - {project-name} ✍️ Applying new version 2.0.0 to 1 package which depends on {project-name} + {project-name} 📄 Resolved the current version as 1.0.0 from manifest: {project-name}/package.json + {project-name} ❓ Applied semver relative bump "major", from the given specifier, to get new version 2.0.0 + {project-name} ✍️ New version 2.0.0 written to manifest: {project-name}/package.json + {project-name} ⏩ Skipping dependent updates as "updateDependents" is not "auto" + {project-name} ✍️ Updated 1 dependency in manifest: {project-name}/package.json "name": "@proj/{project-name}", @@ -656,9 +647,7 @@ describe('nx release circular dependencies', () => { nxJson.release = { projectsRelationship: 'independent', version: { - generatorOptions: { - updateDependents: 'never', - }, + updateDependents: 'never', }, changelog: { // Enable project level changelogs for all examples @@ -682,13 +671,10 @@ describe('nx release circular dependencies', () => { NX Running release version for project: {project-name} - {project-name} 🔍 Reading data for package "@proj/{project-name}" from {project-name}/package.json - {project-name} 📄 Resolved the current version as 1.0.0 from {project-name}/package.json - {project-name} 📄 Using the provided version specifier "major". - {project-name} ⚠️ Warning, the following packages depend on "{project-name}" but have been filtered out via --projects, and therefore will not be updated: - - {project-name} - => You can adjust this behavior by removing the usage of \`version.generatorOptions.updateDependents\` with "never" - {project-name} ✍️ New version 2.0.0 written to {project-name}/package.json + {project-name} 📄 Resolved the current version as 1.0.0 from manifest: {project-name}/package.json + {project-name} ❓ Applied semver relative bump "major", from the given specifier, to get new version 2.0.0 + {project-name} ✍️ New version 2.0.0 written to manifest: {project-name}/package.json + {project-name} ⏩ Skipping dependent updates as "updateDependents" is not "auto" "name": "@proj/{project-name}", @@ -716,6 +702,7 @@ describe('nx release circular dependencies', () => { + + This was a version bump only for {project-name} to align it with other projects, there were no code changes. + Determined --from ref for {project-name} from the first commit in which it exists: {COMMIT_SHA} NX Staging changed files with git @@ -780,9 +767,7 @@ describe('nx release circular dependencies', () => { nxJson.release = { projectsRelationship: 'independent', version: { - generatorOptions: { - updateDependents: 'auto', - }, + updateDependents: 'auto', }, changelog: { // Enable project level changelogs for all examples @@ -800,19 +785,22 @@ describe('nx release circular dependencies', () => { NX Running release version for project: {project-name} - {project-name} 🔍 Reading data for package "@proj/{project-name}" from {project-name}/package.json - {project-name} 📄 Resolved the current version as 1.0.0 from {project-name}/package.json - {project-name} 📄 Using the provided version specifier "major". - {project-name} ✍️ New version 2.0.0 written to {project-name}/package.json - {project-name} ✍️ Applying new version 2.0.0 to 1 package which depends on {project-name} + {project-name} 📄 Resolved the current version as 1.0.0 from manifest: {project-name}/package.json + {project-name} ❓ Applied semver relative bump "major", from the given specifier, to get new version 2.0.0 + {project-name} ✍️ New version 2.0.0 written to manifest: {project-name}/package.json + {project-name} ✍️ Updated 1 dependency in manifest: {project-name}/package.json + {project-name} ✍️ Updated 1 dependency in manifest: {project-name}/package.json + {project-name} ✍️ Updated 1 dependency in manifest: {project-name}/package.json NX Running release version for project: {project-name} - {project-name} 🔍 Reading data for package "@proj/{project-name}" from {project-name}/package.json - {project-name} 📄 Resolved the current version as 1.0.0 from {project-name}/package.json - {project-name} 📄 Using the provided version specifier "major". - {project-name} ✍️ New version 2.0.0 written to {project-name}/package.json - {project-name} ✍️ Applying new version 2.0.0 to 1 package which depends on {project-name} + {project-name} 📄 Resolved the current version as 1.0.0 from manifest: {project-name}/package.json + {project-name} ✍️ Updated 1 dependency in manifest: {project-name}/package.json + {project-name} ❓ Applied semver relative bump "patch", because a dependency was bumped, to get new version 1.0.1 + {project-name} ✍️ New version 1.0.1 written to manifest: {project-name}/package.json + {project-name} ❓ Applied semver relative bump "major", from the given specifier, to get new version 2.0.0 + {project-name} ✍️ New version 2.0.0 written to manifest: {project-name}/package.json + {project-name} ✍️ Updated 1 dependency in manifest: {project-name}/package.json "name": "@proj/{project-name}", @@ -962,9 +950,7 @@ describe('nx release circular dependencies', () => { nxJson.release = { projectsRelationship: 'independent', version: { - generatorOptions: { - updateDependents: 'auto', - }, + updateDependents: 'auto', }, changelog: { // Enable project level changelogs for all examples @@ -988,11 +974,18 @@ describe('nx release circular dependencies', () => { NX Running release version for project: {project-name} - {project-name} 🔍 Reading data for package "@proj/{project-name}" from {project-name}/package.json - {project-name} 📄 Resolved the current version as 1.0.0 from {project-name}/package.json - {project-name} 📄 Using the provided version specifier "major". - {project-name} ✍️ New version 2.0.0 written to {project-name}/package.json - {project-name} ✍️ Applying new version 2.0.0 to 1 package which depends on {project-name} + {project-name} 📄 Resolved the current version as 1.0.0 from manifest: {project-name}/package.json + {project-name} ❓ Applied semver relative bump "major", from the given specifier, to get new version 2.0.0 + {project-name} ✍️ New version 2.0.0 written to manifest: {project-name}/package.json + {project-name} ✍️ Updated 1 dependency in manifest: {project-name}/package.json + {project-name} ✍️ Updated 1 dependency in manifest: {project-name}/package.json + + NX Running release version for project: {project-name} + + {project-name} 📄 Resolved the current version as 1.0.0 from manifest: {project-name}/package.json + {project-name} ✍️ Updated 1 dependency in manifest: {project-name}/package.json + {project-name} ❓ Applied semver relative bump "patch", because a dependency was bumped, to get new version 1.0.1 + {project-name} ✍️ New version 1.0.1 written to manifest: {project-name}/package.json "name": "@proj/{project-name}", diff --git a/e2e/release/src/conventional-commits-config.test.ts b/e2e/release/src/conventional-commits-config.test.ts index 4369022f4c..1c22800d91 100644 --- a/e2e/release/src/conventional-commits-config.test.ts +++ b/e2e/release/src/conventional-commits-config.test.ts @@ -165,22 +165,22 @@ describe('nx release conventional commits config', () => { const versionResultNoChanges = runCLI(`release version -d`); expect(versionResultNoChanges).toContain( - `${pkg1} 🚫 Skipping versioning "@proj/${pkg1}" as no changes were detected.` + `${pkg1} 🚫 No changes were detected using git history and the conventional commits standard` ); expect(versionResultNoChanges).toContain( - `${pkg2} 🚫 Skipping versioning "@proj/${pkg2}" as no changes were detected.` + `${pkg2} 🚫 No changes were detected using git history and the conventional commits standard` ); expect(versionResultNoChanges).toContain( - `${pkg3} 🚫 Skipping versioning "@proj/${pkg3}" as no changes were detected.` + `${pkg3} 🚫 No changes were detected using git history and the conventional commits standard` ); expect(versionResultNoChanges).toContain( - `${pkg4} 🚫 Skipping versioning "@proj/${pkg4}" as no changes were detected.` + `${pkg4} 🚫 No changes were detected using git history and the conventional commits standard` ); expect(versionResultNoChanges).toContain( - `${pkg5} 🚫 Skipping versioning "@proj/${pkg5}" as no changes were detected.` + `${pkg5} 🚫 No changes were detected using git history and the conventional commits standard` ); expect(versionResultNoChanges).toContain( - `${pkg6} 🚫 Skipping versioning "@proj/${pkg6}" as no changes were detected.` + `${pkg6} 🚫 No changes were detected using git history and the conventional commits standard` ); // Do an invalid conventional commit to ensure that it is not included in the changelog @@ -194,22 +194,22 @@ describe('nx release conventional commits config', () => { const versionResultInvalidConventionalCommit = runCLI(`release version -d`); expect(versionResultInvalidConventionalCommit).toContain( - `${pkg1} 🚫 Skipping versioning "@proj/${pkg1}" as no changes were detected.` + `${pkg1} 🚫 No changes were detected using git history and the conventional commits standard` ); expect(versionResultInvalidConventionalCommit).toContain( - `${pkg2} 🚫 Skipping versioning "@proj/${pkg2}" as no changes were detected.` + `${pkg2} 🚫 No changes were detected using git history and the conventional commits standard` ); expect(versionResultInvalidConventionalCommit).toContain( - `${pkg3} 🚫 Skipping versioning "@proj/${pkg3}" as no changes were detected.` + `${pkg3} 🚫 No changes were detected using git history and the conventional commits standard` ); expect(versionResultInvalidConventionalCommit).toContain( - `${pkg4} 🚫 Skipping versioning "@proj/${pkg4}" as no changes were detected.` + `${pkg4} 🚫 No changes were detected using git history and the conventional commits standard` ); expect(versionResultInvalidConventionalCommit).toContain( - `${pkg5} 🚫 Skipping versioning "@proj/${pkg5}" as no changes were detected.` + `${pkg5} 🚫 No changes were detected using git history and the conventional commits standard` ); expect(versionResultInvalidConventionalCommit).toContain( - `${pkg6} 🚫 Skipping versioning "@proj/${pkg6}" as no changes were detected.` + `${pkg6} 🚫 No changes were detected using git history and the conventional commits standard` ); // update my-pkg-3 with a fix commit @@ -223,22 +223,22 @@ describe('nx release conventional commits config', () => { const versionResultDocsChanges = runCLI(`release version -d`); expect(versionResultDocsChanges).toContain( - `${pkg1} 🚫 Skipping versioning "@proj/${pkg1}" as no changes were detected.` + `${pkg1} 🚫 No changes were detected using git history and the conventional commits standard` ); expect(versionResultDocsChanges).toContain( - `${pkg2} 🚫 Skipping versioning "@proj/${pkg2}" as no changes were detected.` + `${pkg2} 🚫 No changes were detected using git history and the conventional commits standard` ); expect(versionResultDocsChanges).toContain( - `${pkg3} ✍️ New version 0.0.2 written to ${pkg3}/package.json` + `${pkg3} ✍️ New version 0.0.2 written to manifest: ${pkg3}/package.json` ); expect(versionResultDocsChanges).toContain( - `${pkg4} 🚫 Skipping versioning "@proj/${pkg4}" as no changes were detected.` + `${pkg4} 🚫 No changes were detected using git history and the conventional commits standard` ); expect(versionResultDocsChanges).toContain( - `${pkg5} 🚫 Skipping versioning "@proj/${pkg5}" as no changes were detected.` + `${pkg5} 🚫 No changes were detected using git history and the conventional commits standard` ); expect(versionResultDocsChanges).toContain( - `${pkg6} 🚫 Skipping versioning "@proj/${pkg6}" as no changes were detected.` + `${pkg6} 🚫 No changes were detected using git history and the conventional commits standard` ); // update my-pkg-2 with a fix commit @@ -253,22 +253,22 @@ describe('nx release conventional commits config', () => { const versionResultCustomTypeChanges = runCLI(`release version -d`); expect(versionResultCustomTypeChanges).toContain( - `${pkg1} 🚫 Skipping versioning "@proj/${pkg1}" as no changes were detected.` + `${pkg1} 🚫 No changes were detected using git history and the conventional commits standard` ); expect(versionResultCustomTypeChanges).toContain( - `${pkg2} ✍️ New version 0.1.0 written to ${pkg2}/package.json` + `${pkg2} ✍️ New version 0.1.0 written to manifest: ${pkg2}/package.json` ); expect(versionResultCustomTypeChanges).toContain( - `${pkg3} ✍️ New version 0.0.2 written to ${pkg3}/package.json` + `${pkg3} ✍️ New version 0.0.2 written to manifest: ${pkg3}/package.json` ); expect(versionResultCustomTypeChanges).toContain( - `${pkg4} 🚫 Skipping versioning "@proj/${pkg4}" as no changes were detected.` + `${pkg4} 🚫 No changes were detected using git history and the conventional commits standard` ); expect(versionResultCustomTypeChanges).toContain( - `${pkg5} 🚫 Skipping versioning "@proj/${pkg5}" as no changes were detected.` + `${pkg5} 🚫 No changes were detected using git history and the conventional commits standard` ); expect(versionResultCustomTypeChanges).toContain( - `${pkg6} 🚫 Skipping versioning "@proj/${pkg6}" as no changes were detected.` + `${pkg6} 🚫 No changes were detected using git history and the conventional commits standard` ); updateJson(`${pkg1}/package.json`, (json) => ({ @@ -282,22 +282,22 @@ describe('nx release conventional commits config', () => { const versionResultCustomTypeBreakingChanges = runCLI(`release version -d`); expect(versionResultCustomTypeBreakingChanges).toContain( - `${pkg1} ✍️ New version 1.0.0 written to ${pkg1}/package.json` + `${pkg1} ✍️ New version 1.0.0 written to manifest: ${pkg1}/package.json` ); expect(versionResultCustomTypeBreakingChanges).toContain( - `${pkg2} ✍️ New version 0.1.0 written to ${pkg2}/package.json` + `${pkg2} ✍️ New version 0.1.0 written to manifest: ${pkg2}/package.json` ); expect(versionResultCustomTypeBreakingChanges).toContain( - `${pkg3} ✍️ New version 0.0.2 written to ${pkg3}/package.json` + `${pkg3} ✍️ New version 0.0.2 written to manifest: ${pkg3}/package.json` ); expect(versionResultCustomTypeBreakingChanges).toContain( - `${pkg4} 🚫 Skipping versioning "@proj/${pkg4}" as no changes were detected.` + `${pkg4} 🚫 No changes were detected using git history and the conventional commits standard` ); expect(versionResultCustomTypeBreakingChanges).toContain( - `${pkg5} 🚫 Skipping versioning "@proj/${pkg5}" as no changes were detected.` + `${pkg5} 🚫 No changes were detected using git history and the conventional commits standard` ); expect(versionResultCustomTypeBreakingChanges).toContain( - `${pkg6} 🚫 Skipping versioning "@proj/${pkg6}" as no changes were detected.` + `${pkg6} 🚫 No changes were detected using git history and the conventional commits standard` ); updateJson(`${pkg4}/package.json`, (json) => ({ @@ -309,22 +309,22 @@ describe('nx release conventional commits config', () => { const versionResultChoreChanges = runCLI(`release version -d`); expect(versionResultChoreChanges).toContain( - `${pkg1} ✍️ New version 1.0.0 written to ${pkg1}/package.json` + `${pkg1} ✍️ New version 1.0.0 written to manifest: ${pkg1}/package.json` ); expect(versionResultChoreChanges).toContain( - `${pkg2} ✍️ New version 0.1.0 written to ${pkg2}/package.json` + `${pkg2} ✍️ New version 0.1.0 written to manifest: ${pkg2}/package.json` ); expect(versionResultChoreChanges).toContain( - `${pkg3} ✍️ New version 0.0.2 written to ${pkg3}/package.json` + `${pkg3} ✍️ New version 0.0.2 written to manifest: ${pkg3}/package.json` ); expect(versionResultChoreChanges).toContain( - `${pkg4} ✍️ New version 0.0.2 written to ${pkg4}/package.json` + `${pkg4} ✍️ New version 0.0.2 written to manifest: ${pkg4}/package.json` ); expect(versionResultChoreChanges).toContain( - `${pkg5} 🚫 Skipping versioning "@proj/${pkg5}" as no changes were detected.` + `${pkg5} 🚫 No changes were detected using git history and the conventional commits standard` ); expect(versionResultChoreChanges).toContain( - `${pkg6} 🚫 Skipping versioning "@proj/${pkg6}" as no changes were detected.` + `${pkg6} 🚫 No changes were detected using git history and the conventional commits standard` ); updateJson(`${pkg5}/package.json`, (json) => ({ @@ -338,22 +338,22 @@ describe('nx release conventional commits config', () => { const versionResultPerfChanges = runCLI(`release version -d`); expect(versionResultPerfChanges).toContain( - `${pkg1} ✍️ New version 1.0.0 written to ${pkg1}/package.json` + `${pkg1} ✍️ New version 1.0.0 written to manifest: ${pkg1}/package.json` ); expect(versionResultPerfChanges).toContain( - `${pkg2} ✍️ New version 0.1.0 written to ${pkg2}/package.json` + `${pkg2} ✍️ New version 0.1.0 written to manifest: ${pkg2}/package.json` ); expect(versionResultPerfChanges).toContain( - `${pkg3} ✍️ New version 0.0.2 written to ${pkg3}/package.json` + `${pkg3} ✍️ New version 0.0.2 written to manifest: ${pkg3}/package.json` ); expect(versionResultPerfChanges).toContain( - `${pkg4} ✍️ New version 0.0.2 written to ${pkg4}/package.json` + `${pkg4} ✍️ New version 0.0.2 written to manifest: ${pkg4}/package.json` ); expect(versionResultPerfChanges).toContain( - `${pkg5} ✍️ New version 0.0.2 written to ${pkg5}/package.json` + `${pkg5} ✍️ New version 0.0.2 written to manifest: ${pkg5}/package.json` ); expect(versionResultPerfChanges).toContain( - `${pkg6} 🚫 Skipping versioning "@proj/${pkg6}" as no changes were detected.` + `${pkg6} 🚫 No changes were detected using git history and the conventional commits standard` ); updateJson(`${pkg6}/package.json`, (json) => ({ @@ -365,22 +365,22 @@ describe('nx release conventional commits config', () => { const versionResultRefactorChanges = runCLI(`release version -d`); expect(versionResultRefactorChanges).toContain( - `${pkg1} ✍️ New version 1.0.0 written to ${pkg1}/package.json` + `${pkg1} ✍️ New version 1.0.0 written to manifest: ${pkg1}/package.json` ); expect(versionResultRefactorChanges).toContain( - `${pkg2} ✍️ New version 0.1.0 written to ${pkg2}/package.json` + `${pkg2} ✍️ New version 0.1.0 written to manifest: ${pkg2}/package.json` ); expect(versionResultRefactorChanges).toContain( - `${pkg3} ✍️ New version 0.0.2 written to ${pkg3}/package.json` + `${pkg3} ✍️ New version 0.0.2 written to manifest: ${pkg3}/package.json` ); expect(versionResultRefactorChanges).toContain( - `${pkg4} ✍️ New version 0.0.2 written to ${pkg4}/package.json` + `${pkg4} ✍️ New version 0.0.2 written to manifest: ${pkg4}/package.json` ); expect(versionResultRefactorChanges).toContain( - `${pkg5} ✍️ New version 0.0.2 written to ${pkg5}/package.json` + `${pkg5} ✍️ New version 0.0.2 written to manifest: ${pkg5}/package.json` ); expect(versionResultRefactorChanges).toContain( - `${pkg6} ✍️ New version 0.0.2 written to ${pkg6}/package.json` + `${pkg6} ✍️ New version 0.0.2 written to manifest: ${pkg6}/package.json` ); // Normally, users would use `nx release` or the programmatic api to ensure that @@ -493,22 +493,22 @@ describe('nx release conventional commits config', () => { const versionResultInvalidConventionalCommit = runCLI(`release version -d`); expect(versionResultInvalidConventionalCommit).toContain( - `${pkg1} ✍️ New version 0.0.2 written to ${pkg1}/package.json` + `${pkg1} ✍️ New version 0.0.2 written to manifest: ${pkg1}/package.json` ); expect(versionResultInvalidConventionalCommit).toContain( - `${pkg2} ✍️ New version 0.0.2 written to ${pkg2}/package.json` + `${pkg2} ✍️ New version 0.0.2 written to manifest: ${pkg2}/package.json` ); expect(versionResultInvalidConventionalCommit).toContain( - `${pkg3} 🚫 Skipping versioning "@proj/${pkg3}" as no changes were detected.` + `${pkg3} 🚫 No changes were detected using git history and the conventional commits standard` ); expect(versionResultInvalidConventionalCommit).toContain( - `${pkg4} 🚫 Skipping versioning "@proj/${pkg4}" as no changes were detected.` + `${pkg4} 🚫 No changes were detected using git history and the conventional commits standard` ); expect(versionResultInvalidConventionalCommit).toContain( - `${pkg5} 🚫 Skipping versioning "@proj/${pkg5}" as no changes were detected.` + `${pkg5} 🚫 No changes were detected using git history and the conventional commits standard` ); expect(versionResultInvalidConventionalCommit).toContain( - `${pkg6} 🚫 Skipping versioning "@proj/${pkg6}" as no changes were detected.` + `${pkg6} 🚫 No changes were detected using git history and the conventional commits standard` ); // update my-pkg-1 with a feature commit @@ -522,22 +522,22 @@ describe('nx release conventional commits config', () => { const versionResultFixCommit = runCLI(`release version -d`); expect(versionResultFixCommit).toContain( - `${pkg1} ✍️ New version 0.1.0 written to ${pkg1}/package.json` + `${pkg1} ✍️ New version 0.1.0 written to manifest: ${pkg1}/package.json` ); expect(versionResultFixCommit).toContain( - `${pkg2} ✍️ New version 0.0.2 written to ${pkg2}/package.json` + `${pkg2} ✍️ New version 0.0.2 written to manifest: ${pkg2}/package.json` ); expect(versionResultFixCommit).toContain( - `${pkg3} 🚫 Skipping versioning "@proj/${pkg3}" as no changes were detected.` + `${pkg3} 🚫 No changes were detected using git history and the conventional commits standard` ); expect(versionResultFixCommit).toContain( - `${pkg4} 🚫 Skipping versioning "@proj/${pkg4}" as no changes were detected.` + `${pkg4} 🚫 No changes were detected using git history and the conventional commits standard` ); expect(versionResultFixCommit).toContain( - `${pkg5} 🚫 Skipping versioning "@proj/${pkg5}" as no changes were detected.` + `${pkg5} 🚫 No changes were detected using git history and the conventional commits standard` ); expect(versionResultFixCommit).toContain( - `${pkg6} 🚫 Skipping versioning "@proj/${pkg6}" as no changes were detected.` + `${pkg6} 🚫 No changes were detected using git history and the conventional commits standard` ); // Normally, users would use `nx release` or the programmatic api to ensure that diff --git a/e2e/release/src/custom-registries.test.ts b/e2e/release/src/custom-registries.test.ts index 3afd01b5f5..846831546c 100644 --- a/e2e/release/src/custom-registries.test.ts +++ b/e2e/release/src/custom-registries.test.ts @@ -11,6 +11,7 @@ import { updateJson, } from '@nx/e2e/utils'; import { execSync } from 'child_process'; +import { NxReleaseVersionV2Configuration } from 'nx/src/config/nx-json'; import type { PackageJson } from 'nx/src/utils/package-json'; describe('nx release - custom npm registries', () => { @@ -306,7 +307,7 @@ describe('nx release - custom npm registries', () => { expect( versionResult.match( new RegExp( - `Resolved the current version as 0.0.0 for tag "alpha" from registry ${e2eRegistryUrl}`, + `Resolved the current version as 0.0.0 from the remote registry: "@scope:registry=${e2eRegistryUrl}" tag=alpha`, 'g' ) ).length @@ -315,7 +316,7 @@ describe('nx release - custom npm registries', () => { expect( versionResult.match( new RegExp( - `Resolved the current version as 0.0.0 for tag "next" from registry ${customRegistryUrl}`, + `Resolved the current version as 0.0.0 from the remote registry: "@scope:registry=${customRegistryUrl}" tag=next`, 'g' ) ).length @@ -324,7 +325,7 @@ describe('nx release - custom npm registries', () => { expect( versionResult.match( new RegExp( - `Resolved the current version as 0.0.0 for tag "beta" from registry ${customRegistryUrl}`, + `Resolved the current version as 0.0.0 from the remote registry: "registry=${customRegistryUrl}" tag=beta`, 'g' ) ).length @@ -333,7 +334,7 @@ describe('nx release - custom npm registries', () => { expect( versionResult.match( new RegExp( - `Resolved the current version as 0.0.0 for tag "prev" from registry ${e2eRegistryUrl}`, + `Resolved the current version as 0.0.0 from the remote registry: "@scope:registry=${e2eRegistryUrl}" tag=prev`, 'g' ) ).length @@ -376,14 +377,14 @@ describe('nx release - custom npm registries', () => { }); updateJson(`${projectName}/project.json`, (json) => { + const releaseConfig = (json.release ?? {}) as { + version: NxReleaseVersionV2Configuration; + }; + if (options.projectConfig) { - json.release = { - version: { - generatorOptions: { - currentVersionResolver: 'registry', - currentVersionResolverMetadata: {}, - }, - }, + releaseConfig.version = { + currentVersionResolver: 'registry', + currentVersionResolverMetadata: {}, }; json.targets = { ...json.targets, @@ -396,16 +397,20 @@ describe('nx release - custom npm registries', () => { json.targets['nx-release-publish'].options.registry = options.projectConfig.registry; ( - json.release.version.generatorOptions - .currentVersionResolverMetadata as Record + releaseConfig.version.currentVersionResolverMetadata as Record< + string, + string + > ).registry = options.projectConfig.registry; } if (options.projectConfig?.tag) { json.targets['nx-release-publish'].options.tag = options.projectConfig.tag; ( - json.release.version.generatorOptions - .currentVersionResolverMetadata as Record + releaseConfig.version.currentVersionResolverMetadata as Record< + string, + string + > ).tag = options.projectConfig.tag; } if (options.projectConfig?.publish?.registry) { @@ -418,16 +423,21 @@ describe('nx release - custom npm registries', () => { } if (options.projectConfig?.version?.registry) { ( - json.release.version.generatorOptions - .currentVersionResolverMetadata as Record + releaseConfig.version.currentVersionResolverMetadata as Record< + string, + string + > ).registry = options.projectConfig.version.registry; } if (options.projectConfig?.version?.tag) { ( - json.release.version.generatorOptions - .currentVersionResolverMetadata as Record + releaseConfig.version.currentVersionResolverMetadata as Record< + string, + string + > ).tag = options.projectConfig.version.tag; } + json.release = releaseConfig; return json; }); diff --git a/e2e/release/src/first-release.test.ts b/e2e/release/src/first-release.test.ts index 653104a76e..91640cdbdd 100644 --- a/e2e/release/src/first-release.test.ts +++ b/e2e/release/src/first-release.test.ts @@ -242,9 +242,7 @@ describe('nx release first run', () => { nxJson.release = { projects: [pkg1, pkg2, pkg3], version: { - generatorOptions: { - fallbackCurrentVersionResolver: 'disk', - }, + fallbackCurrentVersionResolver: 'disk', }, }; @@ -268,9 +266,7 @@ describe('nx release first run', () => { nxJson.release = { projects: [pkg1, pkg2, pkg3], version: { - generatorOptions: { - fallbackCurrentVersionResolver: 'disk', - }, + fallbackCurrentVersionResolver: 'disk', }, changelog: { automaticFromRef: true, @@ -302,9 +298,7 @@ describe('nx release first run', () => { projects: [pkg1, pkg2, pkg3], projectsRelationship: 'independent', version: { - generatorOptions: { - fallbackCurrentVersionResolver: 'disk', - }, + fallbackCurrentVersionResolver: 'disk', }, changelog: { projectChangelogs: {}, @@ -332,9 +326,7 @@ describe('nx release first run', () => { projects: [pkg1, pkg2, pkg3], projectsRelationship: 'independent', version: { - generatorOptions: { - fallbackCurrentVersionResolver: 'disk', - }, + fallbackCurrentVersionResolver: 'disk', }, changelog: { automaticFromRef: true, diff --git a/e2e/release/src/independent-projects.test.ts b/e2e/release/src/independent-projects.test.ts index 52e89f769b..81318d5b34 100644 --- a/e2e/release/src/independent-projects.test.ts +++ b/e2e/release/src/independent-projects.test.ts @@ -126,10 +126,9 @@ describe('nx release - independent projects', () => { NX Running release version for project: {project-name} - {project-name} 🔍 Reading data for package "@proj/{project-name}" from {project-name}/package.json - {project-name} 📄 Resolved the current version as 0.0.0 from {project-name}/package.json - {project-name} 📄 Using the provided version specifier "999.9.9-package.1". - {project-name} ✍️ New version 999.9.9-package.1 written to {project-name}/package.json + {project-name} 📄 Resolved the current version as 0.0.0 from manifest: {project-name}/package.json + {project-name} ❓ Applied explicit semver value "999.9.9-package.1", from the given specifier, to get new version 999.9.9-package.1 + {project-name} ✍️ New version 999.9.9-package.1 written to manifest: {project-name}/package.json "name": "@proj/{project-name}", @@ -155,10 +154,9 @@ describe('nx release - independent projects', () => { NX Running release version for project: {project-name} - {project-name} 🔍 Reading data for package "@proj/{project-name}" from {project-name}/package.json - {project-name} 📄 Resolved the current version as 0.0.0 from {project-name}/package.json - {project-name} 📄 Using the provided version specifier "999.9.9-package.2". - {project-name} ✍️ New version 999.9.9-package.2 written to {project-name}/package.json + {project-name} 📄 Resolved the current version as 0.0.0 from manifest: {project-name}/package.json + {project-name} ❓ Applied explicit semver value "999.9.9-package.2", from the given specifier, to get new version 999.9.9-package.2 + {project-name} ✍️ New version 999.9.9-package.2 written to manifest: {project-name}/package.json "name": "@proj/{project-name}", @@ -187,11 +185,16 @@ describe('nx release - independent projects', () => { NX Running release version for project: {project-name} - {project-name} 🔍 Reading data for package "@proj/{project-name}" from {project-name}/package.json - {project-name} 📄 Resolved the current version as 0.0.0 from {project-name}/package.json - {project-name} 📄 Using the provided version specifier "999.9.9-package.3". - {project-name} ✍️ New version 999.9.9-package.3 written to {project-name}/package.json - {project-name} ✍️ Applying new version 999.9.9-package.3 to 1 package which depends on {project-name} + {project-name} 📄 Resolved the current version as 999.9.9-package.2 from manifest: {project-name}/package.json + {project-name} ✍️ Updated 1 dependency in manifest: {project-name}/package.json + {project-name} ❓ Applied semver relative bump "patch", because a dependency was bumped, to get new version 999.9.9 + {project-name} ✍️ New version 999.9.9 written to manifest: {project-name}/package.json + + NX Running release version for project: {project-name} + + {project-name} 📄 Resolved the current version as 0.0.0 from manifest: {project-name}/package.json + {project-name} ❓ Applied explicit semver value "999.9.9-package.3", from the given specifier, to get new version 999.9.9-package.3 + {project-name} ✍️ New version 999.9.9-package.3 written to manifest: {project-name}/package.json "name": "@proj/{project-name}", @@ -238,10 +241,9 @@ describe('nx release - independent projects', () => { NX Running release version for project: {project-name} - {project-name} 🔍 Reading data for package "@proj/{project-name}" from {project-name}/package.json - {project-name} 📄 Resolved the current version as 999.9.9-version-git-operations-test.1 from {project-name}/package.json - {project-name} 📄 Using the provided version specifier "999.9.9-version-git-operations-test.2". - {project-name} ✍️ New version 999.9.9-version-git-operations-test.2 written to {project-name}/package.json + {project-name} 📄 Resolved the current version as 999.9.9-version-git-operations-test.1 from manifest: {project-name}/package.json + {project-name} ❓ Applied explicit semver value "999.9.9-version-git-operations-test.2", from the given specifier, to get new version 999.9.9-version-git-operations-test.2 + {project-name} ✍️ New version 999.9.9-version-git-operations-test.2 written to manifest: {project-name}/package.json "name": "@proj/{project-name}", @@ -317,28 +319,26 @@ describe('nx release - independent projects', () => { NX Running release version for project: {project-name} - {project-name} 🔍 Reading data for package "@proj/{project-name}" from {project-name}/package.json - {project-name} 📄 Resolved the current version as 999.9.9-version-git-operations-test.2 from {project-name}/package.json - {project-name} 📄 Using the provided version specifier "999.9.9-version-git-operations-test.3". - {project-name} ✍️ New version 999.9.9-version-git-operations-test.3 written to {project-name}/package.json + {project-name} 📄 Resolved the current version as 999.9.9-package.3 from manifest: {project-name}/package.json + {project-name} ❓ Applied explicit semver value "999.9.9-version-git-operations-test.3", from the given specifier, to get new version 999.9.9-version-git-operations-test.3 + {project-name} ✍️ New version 999.9.9-version-git-operations-test.3 written to manifest: {project-name}/package.json NX Running release version for project: {project-name} - {project-name} 🔍 Reading data for package "@proj/{project-name}" from {project-name}/package.json - {project-name} 📄 Resolved the current version as 999.9.9 from {project-name}/package.json - {project-name} 📄 Using the provided version specifier "999.9.9-version-git-operations-test.3". - {project-name} ✍️ New version 999.9.9-version-git-operations-test.3 written to {project-name}/package.json + {project-name} 📄 Resolved the current version as 999.9.9-version-git-operations-test.2 from manifest: {project-name}/package.json + {project-name} ❓ Applied explicit semver value "999.9.9-version-git-operations-test.3", from the given specifier, to get new version 999.9.9-version-git-operations-test.3 + {project-name} ✍️ New version 999.9.9-version-git-operations-test.3 written to manifest: {project-name}/package.json NX Running release version for project: {project-name} - {project-name} 🔍 Reading data for package "@proj/{project-name}" from {project-name}/package.json - {project-name} 📄 Resolved the current version as 999.9.9-package.3 from {project-name}/package.json - {project-name} 📄 Using the provided version specifier "999.9.9-version-git-operations-test.3". - {project-name} ✍️ New version 999.9.9-version-git-operations-test.3 written to {project-name}/package.json + {project-name} 📄 Resolved the current version as 999.9.9 from manifest: {project-name}/package.json + {project-name} ❓ Applied explicit semver value "999.9.9-version-git-operations-test.3", from the given specifier, to get new version 999.9.9-version-git-operations-test.3 + {project-name} ✍️ New version 999.9.9-version-git-operations-test.3 written to manifest: {project-name}/package.json + {project-name} ✍️ Updated 1 dependency in manifest: {project-name}/package.json "name": "@proj/{project-name}", - - "version": "999.9.9-version-git-operations-test.2", + - "version": "999.9.9-package.3", + "version": "999.9.9-version-git-operations-test.3", "scripts": { @@ -348,15 +348,18 @@ describe('nx release - independent projects', () => { + "version": "999.9.9-version-git-operations-test.3", "scripts": { + "dependencies": { + - "@proj/{project-name}": "999.9.9-package.3" + + "@proj/{project-name}": "999.9.9-version-git-operations-test.3" + } + "name": "@proj/{project-name}", - - "version": "999.9.9-package.3", + - "version": "999.9.9-version-git-operations-test.2", + "version": "999.9.9-version-git-operations-test.3", "scripts": { - Skipped lock file update because {package-manager} workspaces are not enabled. - Skipped lock file update because {package-manager} workspaces are not enabled. NX Committing changes with git @@ -877,9 +880,7 @@ describe('nx release - independent projects', () => { projectsRelationship: 'independent', releaseTagPattern: '{projectName}@v{version}', version: { - generatorOptions: { - currentVersionResolver: 'git-tag', - }, + currentVersionResolver: 'git-tag', }, changelog: { projectChangelogs: true, @@ -933,11 +934,8 @@ describe('nx release - independent projects', () => { projectsRelationship: 'independent', releaseTagPattern: '{projectName}@v{version}', version: { - generatorOptions: { - // added specifierSource to ensure conventional commits are used - specifierSource: 'conventional-commits', - currentVersionResolver: 'git-tag', - }, + specifierSource: 'conventional-commits', + currentVersionResolver: 'git-tag', }, changelog: { projectChangelogs: true, @@ -985,14 +983,17 @@ describe('nx release - independent projects', () => { expect( releaseOutput.match( new RegExp( - `Resolved the specifier as "minor" using git history and the conventional commits standard.`, + `Resolved the specifier as "minor" using git history and the conventional commits standard`, 'g' ) ).length ).toEqual(1); expect( releaseOutput.match( - new RegExp(`New version 1\\.4\\.0 written to my-pkg-1\\d*`, 'g') + new RegExp( + `New version 1\\.4\\.0 written to manifest: my-pkg-1\\d*`, + 'g' + ) ).length ).toEqual(1); expect( @@ -1004,14 +1005,17 @@ describe('nx release - independent projects', () => { expect( releaseOutput.match( new RegExp( - `Resolved the specifier as "patch" using git history and the conventional commits standard.`, + `Resolved the specifier as "patch" using git history and the conventional commits standard`, 'g' ) ).length ).toEqual(1); expect( releaseOutput.match( - new RegExp(`New version 1\\.8\\.1 written to my-pkg-3\\d*`, 'g') + new RegExp( + `New version 1\\.8\\.1 written to manifest: my-pkg-3\\d*`, + 'g' + ) ).length ).toEqual(1); expect( @@ -1021,7 +1025,7 @@ describe('nx release - independent projects', () => { expect( releaseOutput.match(new RegExp(`Generating an entry in `, 'g')).length - ).toEqual(2); + ).toEqual(3); expect( releaseOutput.match( diff --git a/e2e/release/src/multiple-release-branches.test.ts b/e2e/release/src/multiple-release-branches.test.ts index 0c2a2ea257..a2b67a0723 100644 --- a/e2e/release/src/multiple-release-branches.test.ts +++ b/e2e/release/src/multiple-release-branches.test.ts @@ -83,9 +83,7 @@ describe('nx release multiple release branches', () => { commit: true, tag: true, }, - generatorOptions: { - currentVersionResolver: 'git-tag', - }, + currentVersionResolver: 'git-tag', }, }; @@ -125,24 +123,21 @@ describe('nx release multiple release branches', () => { NX Running release version for project: {project-name} - {project-name} 🔍 Reading data for package "@proj/{project-name}" from {project-name}/package.json - {project-name} 📄 Unable to resolve the current version from git tag using pattern "v{version}". Falling back to the version on disk of 0.0.0 - {project-name} 📄 Using the provided version specifier "0.0.7". - {project-name} ✍️ New version 0.0.7 written to {project-name}/package.json + {project-name} ⚠️ Unable to resolve the current version from git tags using pattern "v{version}". Falling back to the version 0.0.0 in manifest: {project-name}/package.json + {project-name} ❓ Applied explicit semver value "0.0.7", from the given specifier, to get new version 0.0.7 + {project-name} ✍️ New version 0.0.7 written to manifest: {project-name}/package.json NX Running release version for project: {project-name} - {project-name} 🔍 Reading data for package "@proj/{project-name}" from {project-name}/package.json - {project-name} 📄 Using the current version 0.0.0 already resolved from disk fallback. - {project-name} 📄 Using the provided version specifier "0.0.7". - {project-name} ✍️ New version 0.0.7 written to {project-name}/package.json + {project-name} 🔄 Reusing the current version 0.0.0 already resolved for {project-name} from the disk fallback + {project-name} ❓ Applied version 0.0.7 directly, because the project is a member of a fixed release group containing {project-name} + {project-name} ✍️ New version 0.0.7 written to manifest: {project-name}/package.json NX Running release version for project: {project-name} - {project-name} 🔍 Reading data for package "@proj/{project-name}" from {project-name}/package.json - {project-name} 📄 Using the current version 0.0.0 already resolved from disk fallback. - {project-name} 📄 Using the provided version specifier "0.0.7". - {project-name} ✍️ New version 0.0.7 written to {project-name}/package.json + {project-name} 🔄 Reusing the current version 0.0.0 already resolved for {project-name} from the disk fallback + {project-name} ❓ Applied version 0.0.7 directly, because the project is a member of a fixed release group containing {project-name} + {project-name} ✍️ New version 0.0.7 written to manifest: {project-name}/package.json "name": "@proj/{project-name}", @@ -174,24 +169,33 @@ describe('nx release multiple release branches', () => { NX Running release version for project: {project-name} - {project-name} 🔍 Reading data for package "@proj/{project-name}" from {project-name}/package.json - {project-name} 📄 Resolved the current version as 0.0.7 from git tag "v0.0.7". - {project-name} 📄 Using the provided version specifier "minor". - {project-name} ✍️ New version 0.1.0 written to {project-name}/package.json + {project-name} 🏷️ Resolved the current version as 0.0.7 from git tag "v0.0.7", based on releaseTagPattern "v{version}" + {project-name} ❓ Applied semver relative bump "minor", from the given specifier, to get new version 0.1.0 + {project-name} ✍️ New version 0.1.0 written to manifest: {project-name}/package.json NX Running release version for project: {project-name} - {project-name} 🔍 Reading data for package "@proj/{project-name}" from {project-name}/package.json - {project-name} 📄 Using the current version 0.0.7 already resolved from git tag "v0.0.7". - {project-name} 📄 Using the provided version specifier "minor". - {project-name} ✍️ New version 0.1.0 written to {project-name}/package.json + {project-name} 🔄 Reusing the current version 0.0.7 already resolved for {project-name} from git tag "v0.0.7" + {project-name} ❓ Applied version 0.1.0 directly, because the project is a member of a fixed release group containing {project-name} + {project-name} ✍️ New version 0.1.0 written to manifest: {project-name}/package.json NX Running release version for project: {project-name} - {project-name} 🔍 Reading data for package "@proj/{project-name}" from {project-name}/package.json - {project-name} 📄 Using the current version 0.0.7 already resolved from git tag "v0.0.7". - {project-name} 📄 Using the provided version specifier "minor". - {project-name} ✍️ New version 0.1.0 written to {project-name}/package.json + {project-name} 🔄 Reusing the current version 0.0.7 already resolved for {project-name} from git tag "v0.0.7" + {project-name} ❓ Applied version 0.1.0 directly, because the project is a member of a fixed release group containing {project-name} + {project-name} ✍️ New version 0.1.0 written to manifest: {project-name}/package.json + + + "name": "@proj/{project-name}", + - "version": "0.0.7", + + "version": "0.1.0", + "scripts": { + + + "name": "@proj/{project-name}", + - "version": "0.0.7", + + "version": "0.1.0", + "scripts": { "name": "@proj/{project-name}", @@ -203,18 +207,6 @@ describe('nx release multiple release branches', () => { + - "name": "@proj/{project-name}", - - "version": "0.0.7", - + "version": "0.1.0", - "scripts": { - - - "name": "@proj/{project-name}", - - "version": "0.0.7", - + "version": "0.1.0", - "scripts": { - - NX Committing changes with git @@ -226,24 +218,21 @@ describe('nx release multiple release branches', () => { NX Running release version for project: {project-name} - {project-name} 🔍 Reading data for package "@proj/{project-name}" from {project-name}/package.json - {project-name} 📄 Resolved the current version as 0.0.7 from git tag "v0.0.7". - {project-name} 📄 Using the provided version specifier "patch". - {project-name} ✍️ New version 0.0.8 written to {project-name}/package.json + {project-name} 🏷️ Resolved the current version as 0.0.7 from git tag "v0.0.7", based on releaseTagPattern "v{version}" + {project-name} ❓ Applied semver relative bump "patch", from the given specifier, to get new version 0.0.8 + {project-name} ✍️ New version 0.0.8 written to manifest: {project-name}/package.json NX Running release version for project: {project-name} - {project-name} 🔍 Reading data for package "@proj/{project-name}" from {project-name}/package.json - {project-name} 📄 Using the current version 0.0.7 already resolved from git tag "v0.0.7". - {project-name} 📄 Using the provided version specifier "patch". - {project-name} ✍️ New version 0.0.8 written to {project-name}/package.json + {project-name} 🔄 Reusing the current version 0.0.7 already resolved for {project-name} from git tag "v0.0.7" + {project-name} ❓ Applied version 0.0.8 directly, because the project is a member of a fixed release group containing {project-name} + {project-name} ✍️ New version 0.0.8 written to manifest: {project-name}/package.json NX Running release version for project: {project-name} - {project-name} 🔍 Reading data for package "@proj/{project-name}" from {project-name}/package.json - {project-name} 📄 Using the current version 0.0.7 already resolved from git tag "v0.0.7". - {project-name} 📄 Using the provided version specifier "patch". - {project-name} ✍️ New version 0.0.8 written to {project-name}/package.json + {project-name} 🔄 Reusing the current version 0.0.7 already resolved for {project-name} from git tag "v0.0.7" + {project-name} ❓ Applied version 0.0.8 directly, because the project is a member of a fixed release group containing {project-name} + {project-name} ✍️ New version 0.0.8 written to manifest: {project-name}/package.json "name": "@proj/{project-name}", @@ -281,9 +270,7 @@ describe('nx release multiple release branches', () => { commit: true, tag: true, }, - generatorOptions: { - currentVersionResolver: 'git-tag', - }, + currentVersionResolver: 'git-tag', }, }; @@ -321,24 +308,33 @@ describe('nx release multiple release branches', () => { NX Running release version for project: {project-name} - {project-name} 🔍 Reading data for package "@proj/{project-name}" from {project-name}/package.json - {project-name} 📄 Unable to resolve the current version from git tag using pattern "v{version}". Falling back to the version on disk of 0.0.0 - {project-name} 📄 Using the provided version specifier "minor". - {project-name} ✍️ New version 0.1.0 written to {project-name}/package.json + {project-name} ⚠️ Unable to resolve the current version from git tags using pattern "v{version}". Falling back to the version 0.0.0 in manifest: {project-name}/package.json + {project-name} ❓ Applied semver relative bump "minor", from the given specifier, to get new version 0.1.0 + {project-name} ✍️ New version 0.1.0 written to manifest: {project-name}/package.json NX Running release version for project: {project-name} - {project-name} 🔍 Reading data for package "@proj/{project-name}" from {project-name}/package.json - {project-name} 📄 Using the current version 0.0.0 already resolved from disk fallback. - {project-name} 📄 Using the provided version specifier "minor". - {project-name} ✍️ New version 0.1.0 written to {project-name}/package.json + {project-name} 🔄 Reusing the current version 0.0.0 already resolved for {project-name} from the disk fallback + {project-name} ❓ Applied version 0.1.0 directly, because the project is a member of a fixed release group containing {project-name} + {project-name} ✍️ New version 0.1.0 written to manifest: {project-name}/package.json NX Running release version for project: {project-name} - {project-name} 🔍 Reading data for package "@proj/{project-name}" from {project-name}/package.json - {project-name} 📄 Using the current version 0.0.0 already resolved from disk fallback. - {project-name} 📄 Using the provided version specifier "minor". - {project-name} ✍️ New version 0.1.0 written to {project-name}/package.json + {project-name} 🔄 Reusing the current version 0.0.0 already resolved for {project-name} from the disk fallback + {project-name} ❓ Applied version 0.1.0 directly, because the project is a member of a fixed release group containing {project-name} + {project-name} ✍️ New version 0.1.0 written to manifest: {project-name}/package.json + + + "name": "@proj/{project-name}", + - "version": "0.0.0", + + "version": "0.1.0", + "scripts": { + + + "name": "@proj/{project-name}", + - "version": "0.0.0", + + "version": "0.1.0", + "scripts": { "name": "@proj/{project-name}", @@ -350,18 +346,6 @@ describe('nx release multiple release branches', () => { + - "name": "@proj/{project-name}", - - "version": "0.0.0", - + "version": "0.1.0", - "scripts": { - - - "name": "@proj/{project-name}", - - "version": "0.0.0", - + "version": "0.1.0", - "scripts": { - - NX Committing changes with git @@ -373,24 +357,21 @@ describe('nx release multiple release branches', () => { NX Running release version for project: {project-name} - {project-name} 🔍 Reading data for package "@proj/{project-name}" from {project-name}/package.json - {project-name} 📄 Resolved the current version as 0.1.0 from git tag "v0.1.0". - {project-name} 📄 Using the provided version specifier "major". - {project-name} ✍️ New version 1.0.0 written to {project-name}/package.json + {project-name} 🏷️ Resolved the current version as 0.1.0 from git tag "v0.1.0", based on releaseTagPattern "v{version}" + {project-name} ❓ Applied semver relative bump "major", from the given specifier, to get new version 1.0.0 + {project-name} ✍️ New version 1.0.0 written to manifest: {project-name}/package.json NX Running release version for project: {project-name} - {project-name} 🔍 Reading data for package "@proj/{project-name}" from {project-name}/package.json - {project-name} 📄 Using the current version 0.1.0 already resolved from git tag "v0.1.0". - {project-name} 📄 Using the provided version specifier "major". - {project-name} ✍️ New version 1.0.0 written to {project-name}/package.json + {project-name} 🔄 Reusing the current version 0.1.0 already resolved for {project-name} from git tag "v0.1.0" + {project-name} ❓ Applied version 1.0.0 directly, because the project is a member of a fixed release group containing {project-name} + {project-name} ✍️ New version 1.0.0 written to manifest: {project-name}/package.json NX Running release version for project: {project-name} - {project-name} 🔍 Reading data for package "@proj/{project-name}" from {project-name}/package.json - {project-name} 📄 Using the current version 0.1.0 already resolved from git tag "v0.1.0". - {project-name} 📄 Using the provided version specifier "major". - {project-name} ✍️ New version 1.0.0 written to {project-name}/package.json + {project-name} 🔄 Reusing the current version 0.1.0 already resolved for {project-name} from git tag "v0.1.0" + {project-name} ❓ Applied version 1.0.0 directly, because the project is a member of a fixed release group containing {project-name} + {project-name} ✍️ New version 1.0.0 written to manifest: {project-name}/package.json "name": "@proj/{project-name}", diff --git a/e2e/release/src/pre-version-command.test.ts b/e2e/release/src/pre-version-command.test.ts index ef46bec5bb..928758f093 100644 --- a/e2e/release/src/pre-version-command.test.ts +++ b/e2e/release/src/pre-version-command.test.ts @@ -61,9 +61,9 @@ describe('nx release pre-version command', () => { silenceError: true, }); - // command should fail because @nx/js:library configures the packageRoot to be dist/{project-name}, which doesn't exist yet + // command should fail because @nx/js:library configures the manifestRootsToUpdate to be ['dist/{project-name}'], which doesn't exist yet expect(result1).toContain( - `NX The project "${pkg1}" does not have a package.json available at dist/${pkg1}/package.json.` + `NX The project "${pkg1}" does not have a package.json file available in ./dist/${pkg1}` ); updateJson(`nx.json`, (json) => { diff --git a/e2e/release/src/preserve-local-dependency-protocols.test.ts b/e2e/release/src/preserve-local-dependency-protocols.test.ts index 0c3b7b6a38..6c69e8c59c 100644 --- a/e2e/release/src/preserve-local-dependency-protocols.test.ts +++ b/e2e/release/src/preserve-local-dependency-protocols.test.ts @@ -120,12 +120,16 @@ describe('nx release preserve local dependency protocols', () => { return { workspacePath: tmpProjPath(), pkg1, pkg2 }; }; - it('should replace local dependency protocols with the actual version number when version.generatorOptions.preserveLocalDependencyProtocols is not set to true', async () => { + it('should replace local dependency protocols with the actual version number when version.preserveLocalDependencyProtocols is set to false', async () => { // The package manager currently does not matter for the versioning behavior, it's imperatively controlled by the user const { workspacePath } = await initializeProject('pnpm'); updateJson('nx.json', (nxJson) => { - nxJson.release = {}; + nxJson.release = { + version: { + preserveLocalDependencyProtocols: false, + }, + }; return nxJson; }); @@ -133,16 +137,14 @@ describe('nx release preserve local dependency protocols', () => { expect(runCLI(`release version minor -d --verbose`, { cwd: workspacePath })) .toMatchInlineSnapshot(` NX Running release version for project: {project-name} - {project-name} 🔍 Reading data for package "@proj/{project-name}" from {project-name}/package.json - {project-name} 📄 Resolved the current version as 0.0.0 from {project-name}/package.json - {project-name} 📄 Using the provided version specifier "minor". - {project-name} ✍️ New version 0.1.0 written to {project-name}/package.json + {project-name} 📄 Resolved the current version as 0.0.0 from manifest: {project-name}/package.json + {project-name} ❓ Applied semver relative bump "minor", from the given specifier, to get new version 0.1.0 + {project-name} ✍️ New version 0.1.0 written to manifest: {project-name}/package.json + {project-name} ✍️ Updated 1 dependency in manifest: {project-name}/package.json NX Running release version for project: {project-name} - {project-name} 🔍 Reading data for package "@proj/{project-name}" from {project-name}/package.json - {project-name} 📄 Resolved the current version as 0.0.0 from {project-name}/package.json - {project-name} 📄 Using the provided version specifier "minor". - {project-name} ✍️ New version 0.1.0 written to {project-name}/package.json - {project-name} ✍️ Applying new version 0.1.0 to 1 package which depends on {project-name} + {project-name} 📄 Resolved the current version as 0.0.0 from manifest: {project-name}/package.json + {project-name} ❓ Applied version 0.1.0 directly, because the project is a member of a fixed release group containing {project-name} + {project-name} ✍️ New version 0.1.0 written to manifest: {project-name}/package.json "name": "@proj/{project-name}", - "version": "0.0.0", + "version": "0.1.0", @@ -166,17 +168,13 @@ describe('nx release preserve local dependency protocols', () => { `); }); - it('should preserve local dependency protocols when version.generatorOptions.preserveLocalDependencyProtocols is set to true', async () => { + it('should preserve local dependency protocols when version.preserveLocalDependencyProtocols is not set to false', async () => { // The package manager currently does not matter for the versioning behavior, it's imperatively controlled by the user const { workspacePath } = await initializeProject('pnpm'); updateJson('nx.json', (nxJson) => { nxJson.release = { - version: { - generatorOptions: { - preserveLocalDependencyProtocols: true, - }, - }, + version: {}, }; return nxJson; }); @@ -185,16 +183,13 @@ describe('nx release preserve local dependency protocols', () => { expect(runCLI(`release version minor -d --verbose`, { cwd: workspacePath })) .toMatchInlineSnapshot(` NX Running release version for project: {project-name} - {project-name} 🔍 Reading data for package "@proj/{project-name}" from {project-name}/package.json - {project-name} 📄 Resolved the current version as 0.0.0 from {project-name}/package.json - {project-name} 📄 Using the provided version specifier "minor". - {project-name} ✍️ New version 0.1.0 written to {project-name}/package.json + {project-name} 📄 Resolved the current version as 0.0.0 from manifest: {project-name}/package.json + {project-name} ❓ Applied semver relative bump "minor", from the given specifier, to get new version 0.1.0 + {project-name} ✍️ New version 0.1.0 written to manifest: {project-name}/package.json NX Running release version for project: {project-name} - {project-name} 🔍 Reading data for package "@proj/{project-name}" from {project-name}/package.json - {project-name} 📄 Resolved the current version as 0.0.0 from {project-name}/package.json - {project-name} 📄 Using the provided version specifier "minor". - {project-name} ✍️ New version 0.1.0 written to {project-name}/package.json - {project-name} ✍️ Applying new version 0.1.0 to 1 package which depends on {project-name} + {project-name} 📄 Resolved the current version as 0.0.0 from manifest: {project-name}/package.json + {project-name} ❓ Applied version 0.1.0 directly, because the project is a member of a fixed release group containing {project-name} + {project-name} ✍️ New version 0.1.0 written to manifest: {project-name}/package.json "name": "@proj/{project-name}", - "version": "0.0.0", + "version": "0.1.0", diff --git a/e2e/release/src/private-js-packages.test.ts b/e2e/release/src/private-js-packages.test.ts index 47351dc545..f08f2048cd 100644 --- a/e2e/release/src/private-js-packages.test.ts +++ b/e2e/release/src/private-js-packages.test.ts @@ -222,8 +222,8 @@ describe('nx release - private JS packages', () => { ).toEqual('999.9.9'); // The private package should have never been published - expect(() => execSync(`npm view @proj/${privatePkg} version`)).toThrowError( - /npm ERR! code E404/ + expect(() => execSync(`npm view @proj/${privatePkg} version`)).toThrow( + /npm (ERR!|error) code E404/ ); }, 500000); @@ -310,8 +310,8 @@ describe('nx release - private JS packages', () => { ).toEqual('999.9.10'); // The private package should have never been published - expect(() => execSync(`npm view @proj/${privatePkg} version`)).toThrowError( - /npm ERR! code E404/ + expect(() => execSync(`npm view @proj/${privatePkg} version`)).toThrow( + /npm (ERR!|error) code E404/ ); }, 500000); }); diff --git a/e2e/release/src/release-publishable-libraries-ts-solution.test.ts b/e2e/release/src/release-publishable-libraries-ts-solution.test.ts index 4f0a977c5b..c63fe882ac 100644 --- a/e2e/release/src/release-publishable-libraries-ts-solution.test.ts +++ b/e2e/release/src/release-publishable-libraries-ts-solution.test.ts @@ -89,10 +89,9 @@ describe('release publishable libraries in workspace with ts solution setup', () expect(releaseOutput).toMatchInlineSnapshot(` NX Executing pre-version command NX Running release version for project: @proj/{project-name} - @proj/{project-name} 🔍 Reading data for package "@proj/{project-name}" from packages/{project-name}/package.json - @proj/{project-name} 📄 Resolved the current version as 0.0.1 from packages/{project-name}/package.json - @proj/{project-name} 📄 Using the provided version specifier "0.0.2". - @proj/{project-name} ✍️ New version 0.0.2 written to packages/{project-name}/package.json + @proj/{project-name} 📄 Resolved the current version as 0.0.1 from manifest: packages/{project-name}/package.json + @proj/{project-name} ❓ Applied explicit semver value "0.0.2", from the given specifier, to get new version 0.0.2 + @proj/{project-name} ✍️ New version 0.0.2 written to manifest: packages/{project-name}/package.json "name": "@proj/{project-name}", - "version": "0.0.1", + "version": "0.0.2", @@ -144,10 +143,9 @@ describe('release publishable libraries in workspace with ts solution setup', () expect(releaseOutput).toMatchInlineSnapshot(` NX Executing pre-version command NX Running release version for project: @proj/{project-name} - @proj/{project-name} 🔍 Reading data for package "@proj/{project-name}" from packages/{project-name}/package.json - @proj/{project-name} 📄 Resolved the current version as 0.0.1 from packages/{project-name}/package.json - @proj/{project-name} 📄 Using the provided version specifier "0.0.3". - @proj/{project-name} ✍️ New version 0.0.3 written to packages/{project-name}/package.json + @proj/{project-name} 📄 Resolved the current version as 0.0.1 from manifest: packages/{project-name}/package.json + @proj/{project-name} ❓ Applied explicit semver value "0.0.3", from the given specifier, to get new version 0.0.3 + @proj/{project-name} ✍️ New version 0.0.3 written to manifest: packages/{project-name}/package.json "name": "@proj/{project-name}", - "version": "0.0.1", + "version": "0.0.3", @@ -204,10 +202,9 @@ describe('release publishable libraries in workspace with ts solution setup', () expect(releaseOutput).toMatchInlineSnapshot(` NX Executing pre-version command NX Running release version for project: @proj/{project-name} - @proj/{project-name} 🔍 Reading data for package "@proj/{project-name}" from packages/{project-name}/package.json - @proj/{project-name} 📄 Resolved the current version as 0.0.1 from packages/{project-name}/package.json - @proj/{project-name} 📄 Using the provided version specifier "0.0.4". - @proj/{project-name} ✍️ New version 0.0.4 written to packages/{project-name}/package.json + @proj/{project-name} 📄 Resolved the current version as 0.0.1 from manifest: packages/{project-name}/package.json + @proj/{project-name} ❓ Applied explicit semver value "0.0.4", from the given specifier, to get new version 0.0.4 + @proj/{project-name} ✍️ New version 0.0.4 written to manifest: packages/{project-name}/package.json "name": "@proj/{project-name}", - "version": "0.0.1", + "version": "0.0.4", diff --git a/e2e/release/src/release-publishable-libraries.test.ts b/e2e/release/src/release-publishable-libraries.test.ts index 0396a4f76d..a5372d1692 100644 --- a/e2e/release/src/release-publishable-libraries.test.ts +++ b/e2e/release/src/release-publishable-libraries.test.ts @@ -88,10 +88,9 @@ describe('release publishable libraries', () => { expect(releaseOutput).toMatchInlineSnapshot(` NX Executing pre-version command NX Running release version for project: {project-name} - {project-name} 🔍 Reading data for package "@proj/{project-name}" from dist/packages/{project-name}/package.json - {project-name} 📄 Resolved the current version as 0.0.0 from git tag "v0.0.0". - {project-name} 📄 Using the provided version specifier "0.0.2". - {project-name} ✍️ New version 0.0.2 written to dist/packages/{project-name}/package.json + {project-name} 🏷️ Resolved the current version as 0.0.0 from git tag "v0.0.0", based on releaseTagPattern "v{version}" + {project-name} ❓ Applied explicit semver value "0.0.2", from the given specifier, to get new version 0.0.2 + {project-name} ✍️ New version 0.0.2 written to manifest: dist/packages/{project-name}/package.json "name": "@proj/{project-name}", - "version": "0.0.1", + "version": "0.0.2", @@ -144,10 +143,9 @@ describe('release publishable libraries', () => { expect(releaseOutput).toMatchInlineSnapshot(` NX Executing pre-version command NX Running release version for project: {project-name} - {project-name} 🔍 Reading data for package "@proj/{project-name}" from dist/packages/{project-name}/package.json - {project-name} 📄 Resolved the current version as 0.0.2 from git tag "v0.0.2". - {project-name} 📄 Using the provided version specifier "0.0.3". - {project-name} ✍️ New version 0.0.3 written to dist/packages/{project-name}/package.json + {project-name} 🏷️ Resolved the current version as 0.0.2 from git tag "v0.0.2", based on releaseTagPattern "v{version}" + {project-name} ❓ Applied explicit semver value "0.0.3", from the given specifier, to get new version 0.0.3 + {project-name} ✍️ New version 0.0.3 written to manifest: dist/packages/{project-name}/package.json "name": "@proj/{project-name}", - "version": "0.0.1", + "version": "0.0.3", @@ -202,10 +200,9 @@ describe('release publishable libraries', () => { expect(releaseOutput).toMatchInlineSnapshot(` NX Executing pre-version command NX Running release version for project: {project-name} - {project-name} 🔍 Reading data for package "@proj/{project-name}" from dist/packages/{project-name}/package.json - {project-name} 📄 Resolved the current version as 0.0.3 from git tag "v0.0.3". - {project-name} 📄 Using the provided version specifier "0.0.4". - {project-name} ✍️ New version 0.0.4 written to dist/packages/{project-name}/package.json + {project-name} 🏷️ Resolved the current version as 0.0.3 from git tag "v0.0.3", based on releaseTagPattern "v{version}" + {project-name} ❓ Applied explicit semver value "0.0.4", from the given specifier, to get new version 0.0.4 + {project-name} ✍️ New version 0.0.4 written to manifest: dist/packages/{project-name}/package.json "name": "@proj/{project-name}", - "version": "0.0.1", + "version": "0.0.4", @@ -260,10 +257,9 @@ describe('release publishable libraries', () => { expect(releaseOutput).toMatchInlineSnapshot(` NX Executing pre-version command NX Running release version for project: {project-name} - {project-name} 🔍 Reading data for package "@proj/{project-name}" from dist/packages/{project-name}/package.json - {project-name} 📄 Resolved the current version as 0.0.4 from git tag "v0.0.4". - {project-name} 📄 Using the provided version specifier "0.0.5". - {project-name} ✍️ New version 0.0.5 written to dist/packages/{project-name}/package.json + {project-name} 🏷️ Resolved the current version as 0.0.4 from git tag "v0.0.4", based on releaseTagPattern "v{version}" + {project-name} ❓ Applied explicit semver value "0.0.5", from the given specifier, to get new version 0.0.5 + {project-name} ✍️ New version 0.0.5 written to manifest: dist/packages/{project-name}/package.json "name": "@proj/{project-name}", - "version": "0.0.1", + "version": "0.0.5", diff --git a/e2e/release/src/release.test.ts b/e2e/release/src/release.test.ts index 32f15e23da..0ab62afa1d 100644 --- a/e2e/release/src/release.test.ts +++ b/e2e/release/src/release.test.ts @@ -1,4 +1,8 @@ -import type { NxJsonConfiguration } from '@nx/devkit'; +import { + type NxJsonConfiguration, + detectPackageManager, + getPackageManagerCommand, +} from '@nx/devkit'; import { cleanupProject, createFile, @@ -6,13 +10,16 @@ import { killProcessAndPorts, newProject, readFile, + readJson, runCLI, runCommandAsync, runCommandUntil, + tmpProjPath, uniq, updateJson, } from '@nx/e2e/utils'; -import { execSync } from 'child_process'; +import { execSync } from 'node:child_process'; +import type { NxReleaseVersionV2Configuration } from 'nx/src/config/nx-json'; expect.addSnapshotSerializer({ serialize(str: string) { @@ -49,8 +56,13 @@ describe('nx release', () => { let pkg1: string; let pkg2: string; let pkg3: string; + let previousPackageManager: string; beforeAll(() => { + previousPackageManager = process.env.SELECTED_PM; + // Ensure consistent package manager usage in all environments for this file + process.env.SELECTED_PM = 'npm'; + newProject({ packages: ['@nx/js'], }); @@ -71,7 +83,10 @@ describe('nx release', () => { return json; }); }); - afterAll(() => cleanupProject()); + afterAll(() => { + cleanupProject(); + process.env.SELECTED_PM = previousPackageManager; + }); it('should version and publish multiple related npm packages with zero config', async () => { // Normalize git committer information so it is deterministic in snapshots @@ -91,6 +106,10 @@ describe('nx release', () => { `git remote add origin https://github.com/nrwl/fake-repo.git` ); + const packageManager = detectPackageManager(tmpProjPath()); + const updateLockFileCommand = + getPackageManagerCommand(packageManager).updateLockFile; + const versionOutput = runCLI(`release version 999.9.9`); /** @@ -104,17 +123,24 @@ describe('nx release', () => { ).toEqual(3); expect( versionOutput.match( - /Reading data for package "@proj\/my-pkg-\d*" from my-pkg-\d*\/package.json/g + /Resolved the current version as 0\.0\.0 from manifest: my-pkg-\d*\/package\.json/g ).length ).toEqual(3); + // First project expect( versionOutput.match( - /Resolved the current version as 0.0.0 from my-pkg-\d*\/package.json/g + /Applied explicit semver value "999\.9\.9", from the given specifier, to get new version 999\.9\.9/g ).length - ).toEqual(3); + ).toEqual(1); + // Other projects expect( versionOutput.match( - /New version 999.9.9 written to my-pkg-\d*\/package.json/g + /Applied version 999\.9\.9 directly, because the project is a member of a fixed release group containing my-pkg-\d*/g + ).length + ).toEqual(2); + expect( + versionOutput.match( + /New version 999\.9\.9 written to manifest: my-pkg-\d*\/package\.json/g ).length ).toEqual(3); @@ -265,13 +291,12 @@ describe('nx release', () => { projects: ['*', '!@proj/source'], version: { generator: '@nx/js:release-version', - generatorOptions: { - // Resolve the latest version from the custom registry instance, therefore finding the previously published versions - currentVersionResolver: 'registry', - currentVersionResolverMetadata: { - registry: e2eRegistryUrl, - tag: 'latest', - }, + + // Resolve the latest version from the custom registry instance, therefore finding the previously published versions + currentVersionResolver: 'registry', + currentVersionResolverMetadata: { + registry: e2eRegistryUrl, + tag: 'latest', }, }, }, @@ -296,17 +321,12 @@ describe('nx release', () => { versionOutput2.match(/Running release version for project: my-pkg-\d*/g) .length ).toEqual(3); - expect( - versionOutput2.match( - /Reading data for package "@proj\/my-pkg-\d*" from my-pkg-\d*\/package.json/g - ).length - ).toEqual(3); - // It should resolve the current version from the registry once... + // It should resolve the current version from the registry once because it is a fixed release group... expect( versionOutput2.match( new RegExp( - `Resolved the current version as 999.9.9 for tag "latest" from registry ${e2eRegistryUrl}`, + `Resolved the current version as 999.9.9 from the remote registry: "@proj:registry=${e2eRegistryUrl}" tag=latest`, 'g' ) ).length @@ -315,15 +335,28 @@ describe('nx release', () => { expect( versionOutput2.match( new RegExp( - `Using the current version 999.9.9 already resolved from the registry ${e2eRegistryUrl}`, + `Reusing the current version 999.9.9 already resolved for my-pkg-\\d* from the registry: "@proj:registry=${e2eRegistryUrl}" tag=latest`, 'g' ) ).length ).toEqual(2); + // First project expect( versionOutput2.match( - /New version 1000.0.0-next.0 written to my-pkg-\d*\/package.json/g + /Applied semver relative bump "premajor", from the given specifier, to get new version 1000\.0\.0-next\.0/g + ).length + ).toEqual(1); + // Other projects + expect( + versionOutput2.match( + /Applied version 1000\.0\.0-next\.0 directly, because the project is a member of a fixed release group containing my-pkg-\d*/g + ).length + ).toEqual(2); + + expect( + versionOutput2.match( + /New version 1000\.0\.0-next\.0 written to manifest: my-pkg-\d*\/package\.json/g ).length ).toEqual(3); @@ -421,17 +454,17 @@ describe('nx release', () => { execSync( `npm view @proj/${pkg1}@next version --registry=${customRegistryUrl}` ) - ).toThrowError(/npm ERR! code E404/); + ).toThrow(/npm (ERR!|error) code E404/); expect(() => execSync( `npm view @proj/${pkg2}@next version --registry=${customRegistryUrl}` ) - ).toThrowError(/npm ERR! code E404/); + ).toThrow(/npm (ERR!|error) code E404/); expect(() => execSync( `npm view @proj/${pkg3}@next version --registry=${customRegistryUrl}` ) - ).toThrowError(/npm ERR! code E404/); + ).toThrow(/npm (ERR!|error) code E404/); // Actually publish to the custom registry (not e2e registry), and a custom dist tag of "next" const publishOutput3 = runCLI(publishToNext); @@ -739,10 +772,8 @@ describe('nx release', () => { releaseTagPattern: 'xx{version}', version: { generator: '@nx/js:release-version', - generatorOptions: { - // Resolve the latest version from the git tag - currentVersionResolver: 'git-tag', - }, + // Resolve the latest version from the git tag + currentVersionResolver: 'git-tag', }, }, }, @@ -758,11 +789,6 @@ describe('nx release', () => { versionOutput3.match(/Running release version for project: my-pkg-\d*/g) .length ).toEqual(3); - expect( - versionOutput3.match( - /Reading data for package "@proj\/my-pkg-\d*" from my-pkg-\d*\/package.json/g - ).length - ).toEqual(3); // It should resolve the current version from the git tag once... expect( @@ -777,15 +803,28 @@ describe('nx release', () => { expect( versionOutput3.match( new RegExp( - `Using the current version 1100.0.0 already resolved from git tag "xx1100.0.0"`, + `Reusing the current version 1100.0.0 already resolved for my-pkg-\\d* from git tag "xx1100.0.0"`, 'g' ) ).length ).toEqual(2); + // First project expect( versionOutput3.match( - /New version 1100.1.0 written to my-pkg-\d*\/package.json/g + /Applied semver relative bump "minor", from the given specifier, to get new version 1100\.1\.0/g + ).length + ).toEqual(1); + // Other projects + expect( + versionOutput3.match( + /Applied version 1100\.1\.0 directly, because the project is a member of a fixed release group containing my-pkg-\d*/g + ).length + ).toEqual(2); + + expect( + versionOutput3.match( + /New version 1100\.1\.0 written to manifest: my-pkg-\d*\/package\.json/g ).length ).toEqual(3); @@ -803,11 +842,8 @@ describe('nx release', () => { projects: ['*', '!@proj/source'], releaseTagPattern: 'xx{version}', version: { - generator: '@nx/js:release-version', - generatorOptions: { - specifierSource: 'conventional-commits', - currentVersionResolver: 'git-tag', - }, + specifierSource: 'conventional-commits', + currentVersionResolver: 'git-tag', }, }, }, @@ -821,11 +857,6 @@ describe('nx release', () => { versionOutput4.match(/Running release version for project: my-pkg-\d*/g) .length ).toEqual(3); - expect( - versionOutput4.match( - /Reading data for package "@proj\/my-pkg-\d*" from my-pkg-\d*\/package.json/g - ).length - ).toEqual(3); // It should resolve the current version from the git tag once... expect( @@ -840,13 +871,24 @@ describe('nx release', () => { expect( versionOutput4.match( new RegExp( - `Using the current version 1100.0.0 already resolved from git tag "xx1100.0.0"`, + `Reusing the current version 1100.0.0 already resolved for my-pkg-\\d* from git tag "xx1100.0.0"`, 'g' ) ).length ).toEqual(2); - expect(versionOutput4.match(/Skipping versioning/g).length).toEqual(3); + // First project + expect( + versionOutput4.match( + /No changes were detected using git history and the conventional commits standard/g + ).length + ).toEqual(1); + // Other projects + expect( + versionOutput4.match( + /Skipping versioning for my-pkg-\d* as it is a part of a fixed release group with my-pkg-\d* and no dependency bumps were detected/g + ).length + ).toEqual(2); await runCommandAsync( `git add ${pkg1}/my-file.txt && git commit -m "feat!: add new file"` @@ -856,7 +898,7 @@ describe('nx release', () => { expect( versionOutput5.match( - /New version 1101.0.0 written to my-pkg-\d*\/package.json/g + /New version 1101\.0\.0 written to manifest: my-pkg-\d*\/package\.json/g ).length ).toEqual(3); @@ -916,10 +958,8 @@ describe('nx release', () => { tag: true, }, version: { - generatorOptions: { - specifierSource: 'conventional-commits', - currentVersionResolver: 'git-tag', - }, + specifierSource: 'conventional-commits', + currentVersionResolver: 'git-tag', }, changelog: { projectChangelogs: {}, @@ -927,6 +967,13 @@ describe('nx release', () => { }; return nxJson; }); + // Remove the dependency from pkg2 on pkg1, it is not applicable to this group configuration where pkg1 and pkg2 are in separate groups + updateJson(`${pkg2}/package.json`, (json) => { + json.dependencies = {}; + return json; + }); + // Update lockfile + await runCommandAsync(updateLockFileCommand); await runCommandAsync(`git tag zz-1300.0.0`); await runCommandAsync(`git tag xx-1400.0.0`); @@ -954,7 +1001,7 @@ describe('nx release', () => { expect( releaseOutput2.match( new RegExp( - `Resolved the specifier as "minor" using git history and the conventional commits standard.`, + `Resolved the specifier as "minor" using git history and the conventional commits standard`, 'g' ) ).length @@ -977,7 +1024,7 @@ describe('nx release', () => { expect( releaseOutput3.match( new RegExp( - `Resolved the specifier as "minor" using git history and the conventional commits standard.`, + `Resolved the specifier as "minor" using git history and the conventional commits standard`, 'g' ) ).length @@ -996,7 +1043,7 @@ describe('nx release', () => { expect( releaseOutput3.match( new RegExp( - `Resolved the specifier as "patch" using git history and the conventional commits standard.`, + `Resolved the specifier as "patch" using git history and the conventional commits standard`, 'g' ) ).length @@ -1031,13 +1078,21 @@ describe('nx release', () => { tag: false, }, version: { - generatorOptions: { - currentVersionResolver: 'git-tag', - }, + currentVersionResolver: 'git-tag', }, }; return nxJson; }); + // Add back the dependency on pkg1 for pkg2 + updateJson(`${pkg2}/package.json`, (json) => { + json.dependencies = {}; + json.dependencies[`@proj/${pkg1}`] = readJson( + `${pkg1}/package.json` + ).version; + return json; + }); + // Update lockfile + await runCommandAsync(updateLockFileCommand); const releaseOutput4a = runCLI(`release patch --skip-publish`, { silenceError: true, @@ -1045,11 +1100,9 @@ describe('nx release', () => { expect(releaseOutput4a).toMatchInlineSnapshot(` - NX Running release version for project: {project-name} + NX No git tags matching pattern ">{version}" for project "{project-name}" were found. You will need to create an initial matching tag to use as a base for determining the next version. Alternatively, you can use the --first-release option or set "release.version.fallbackCurrentVersionResolver" to "disk" in order to fallback to the version on disk when no matching git tags are found. - {project-name} 🔍 Reading data for package "@proj/{project-name}" from {project-name}/package.json - - NX No git tags matching pattern ">{version}" for project "{project-name}" were found. You will need to create an initial matching tag to use as a base for determining the next version. Alternatively, you can use the --first-release option or set "release.version.generatorOptions.fallbackCurrentVersionResolver" to "disk" in order to fallback to the version on disk when no matching git tags are found. + Pass --verbose to see the stacktrace. `); @@ -1062,44 +1115,48 @@ describe('nx release', () => { ); expect(releaseOutput4b).toMatch( - `📄 Unable to resolve the current version from git tag using pattern ">{version}". Falling back to the version on disk of 1400.1.0` + /Unable to resolve the current version from git tags using pattern ">{version}". Falling back to the version 1400\.1\.0 in manifest: my-pkg-\d*\/package\.json/g ); expect( releaseOutput4b.match( new RegExp( - `📄 Using the current version 1400\\.1\\.0 already resolved from disk fallback\\.`, + `Reusing the current version 1400\\.1\\.0 already resolved for my-pkg-\\d* from the disk fallback`, 'g' ) ).length ).toEqual(2); updateJson('nx.json', (nxJson) => { - nxJson.release.version.generatorOptions.fallbackCurrentVersionResolver = - 'disk'; + ( + nxJson.release.version as NxReleaseVersionV2Configuration + ).fallbackCurrentVersionResolver = 'disk'; return nxJson; }); const releaseOutput5 = runCLI(`release patch --skip-publish`); expect(releaseOutput5).toMatch( - `📄 Unable to resolve the current version from git tag using pattern ">{version}". Falling back to the version on disk of 1400.1.1` + /Unable to resolve the current version from git tags using pattern ">{version}". Falling back to the version 1400\.1\.1 in manifest: my-pkg-\d*\/package\.json/g ); expect( releaseOutput5.match( new RegExp( - `📄 Using the current version 1400\\.1\\.1 already resolved from disk fallback\\.`, + `Reusing the current version 1400\\.1\\.1 already resolved for my-pkg-\\d* from the disk fallback`, 'g' ) ).length ).toEqual(2); updateJson('nx.json', (nxJson) => { - nxJson.release.version.generatorOptions.currentVersionResolver = - 'registry'; - nxJson.release.version.generatorOptions.currentVersionResolverMetadata = { + ( + nxJson.release.version as NxReleaseVersionV2Configuration + ).currentVersionResolver = 'registry'; + ( + nxJson.release.version as NxReleaseVersionV2Configuration + ).currentVersionResolverMetadata = { tag: 'other', }; - delete nxJson.release.version.generatorOptions + delete (nxJson.release.version as NxReleaseVersionV2Configuration) .fallbackCurrentVersionResolver; return nxJson; }); @@ -1111,7 +1168,7 @@ describe('nx release', () => { expect( releaseOutput6a.match( new RegExp( - `NX Unable to resolve the current version from the registry ${e2eRegistryUrl}. Please ensure that the package exists in the registry in order to use the "registry" currentVersionResolver. Alternatively, you can use the --first-release option or set "release.version.generatorOptions.fallbackCurrentVersionResolver" to "disk" in order to fallback to the version on disk when the registry lookup fails.`, + `NX Unable to resolve the current version from the registry: "@proj:registry=${e2eRegistryUrl}" tag=other. Please ensure that the package exists in the registry in order to use the "registry" currentVersionResolver. Alternatively, you can use the --first-release option or set "release.version.fallbackCurrentVersionResolver" to "disk" in order to fallback to the version on disk when the registry lookup fails.`, 'g' ) ).length @@ -1125,32 +1182,37 @@ describe('nx release', () => { ); expect(releaseOutput6b).toMatch( - `📄 Unable to resolve the current version from the registry ${e2eRegistryUrl}. Falling back to the version on disk of 1400.1.2` + new RegExp( + `Unable to resolve the current version from the registry: "@proj:registry=${e2eRegistryUrl}" tag=other. Falling back to the version 1400\\.1\\.2 in manifest: my-pkg-\\d*\\/package\\.json` + ) ); expect( releaseOutput6b.match( new RegExp( - `📄 Using the current version 1400\\.1\\.2 already resolved from disk fallback\\.`, + `Reusing the current version 1400\\.1\\.2 already resolved for my-pkg-\\d* from the disk fallback`, 'g' ) ).length ).toEqual(2); updateJson('nx.json', (nxJson) => { - nxJson.release.version.generatorOptions.fallbackCurrentVersionResolver = - 'disk'; + ( + nxJson.release.version as NxReleaseVersionV2Configuration + ).fallbackCurrentVersionResolver = 'disk'; return nxJson; }); const releaseOutput7 = runCLI(`release patch --skip-publish --verbose`); expect(releaseOutput7).toMatch( - `📄 Unable to resolve the current version from the registry ${e2eRegistryUrl}. Falling back to the version on disk of 1400.1.3` + new RegExp( + `Unable to resolve the current version from the registry: "@proj:registry=${e2eRegistryUrl}" tag=other. Falling back to the version 1400\\.1\\.3 in manifest: my-pkg-\\d*\\/package\\.json` + ) ); expect( releaseOutput7.match( new RegExp( - `📄 Using the current version 1400\\.1\\.3 already resolved from disk fallback\\.`, + `Reusing the current version 1400\\.1\\.3 already resolved for my-pkg-\\d* from the disk fallback`, 'g' ) ).length diff --git a/e2e/release/src/version-plans-check.test.ts b/e2e/release/src/version-plans-check.test.ts index f42c90422e..46ef193e4d 100644 --- a/e2e/release/src/version-plans-check.test.ts +++ b/e2e/release/src/version-plans-check.test.ts @@ -89,6 +89,8 @@ describe('nx release version plans check command', () => { Please ensure at least one release group has version plans enabled in your Nx release configuration if you want to use this command. + Learn more about version plans here: https://nx.dev/recipes/nx-release/file-based-versioning-version-plans + `); diff --git a/e2e/release/src/version-plans-only-touched.test.ts b/e2e/release/src/version-plans-only-touched.test.ts index ec193dae4e..9ea122a779 100644 --- a/e2e/release/src/version-plans-only-touched.test.ts +++ b/e2e/release/src/version-plans-only-touched.test.ts @@ -92,9 +92,7 @@ describe('nx release version plans only touched', () => { }, }, version: { - generatorOptions: { - specifierSource: 'version-plans', - }, + specifierSource: 'version-plans', }, changelog: { projectChangelogs: true, diff --git a/e2e/release/src/version-plans.test.ts b/e2e/release/src/version-plans.test.ts index 02741c9188..4e02d2d885 100644 --- a/e2e/release/src/version-plans.test.ts +++ b/e2e/release/src/version-plans.test.ts @@ -101,9 +101,7 @@ describe('nx release version plans', () => { }, }, version: { - generatorOptions: { - specifierSource: 'version-plans', - }, + specifierSource: 'version-plans', }, changelog: { projectChangelogs: true, @@ -145,21 +143,29 @@ Here is another line in the message. silenceError: true, }); - expect(result).toContain( - `${pkg1} 📄 Resolved the specifier as "minor" using version plans.` + expect(result).toMatch( + new RegExp( + `${pkg1} ❓ Applied semver relative bump "minor", read from version plan \\.nx\\/version-plans\\/version-plan-\\d+\\.md, to get new version 0\\.1\\.0` + ) ); // pkg2 uses the previously resolved specifier from pkg1 expect(result).toContain( - `${pkg2} ✍️ New version 0.1.0 written to ${pkg2}/package.json` + `${pkg2} ✍️ New version 0.1.0 written to manifest: ${pkg2}/package.json` ); - expect(result).toContain( - `${pkg3} 📄 Resolved the specifier as "patch" using version plans.` + expect(result).toMatch( + new RegExp( + `${pkg3} ❓ Applied semver relative bump "patch", read from version plan \\.nx\\/version-plans\\/bump-independent\\.md, to get new version 0\\.0\\.1` + ) ); - expect(result).toContain( - `${pkg4} 📄 Resolved the specifier as "preminor" using version plans.` + expect(result).toMatch( + new RegExp( + `${pkg4} ❓ Applied semver relative bump "preminor", read from version plan \\.nx\\/version-plans\\/bump-independent\\.md, to get new version 0\\.1\\.0-0` + ) ); - expect(result).toContain( - `${pkg5} 📄 Resolved the specifier as "prerelease" using version plans.` + expect(result).toMatch( + new RegExp( + `${pkg5} ❓ Applied semver relative bump "prerelease", read from version plan \\.nx\\/version-plans\\/bump-independent\\.md, to get new version 0\\.0\\.1-0` + ) ); // replace the date with a placeholder to make the snapshot deterministic @@ -283,21 +289,29 @@ Update packages in both groups with a mix #2 silenceError: true, }); - expect(result2).toContain( - `${pkg1} 📄 Resolved the specifier as "minor" using version plans.` + expect(result2).toMatch( + new RegExp( + `${pkg1} ❓ Applied semver relative bump "minor", read from version plan \\.nx\\/version-plans\\/bump-mixed1\\.md, to get new version 0\\.2\\.0` + ) ); // pkg2 uses the previously resolved specifier from pkg1 expect(result2).toContain( - `${pkg2} ✍️ New version 0.2.0 written to ${pkg2}/package.json` + `${pkg2} ✍️ New version 0.2.0 written to manifest: ${pkg2}/package.json` ); - expect(result2).toContain( - `${pkg3} 📄 Resolved the specifier as "patch" using version plans.` + expect(result2).toMatch( + new RegExp( + `${pkg3} ❓ Applied semver relative bump "patch", read from version plan \\.nx\\/version-plans\\/bump-mixed1\\.md, to get new version 0\\.0\\.2` + ) ); - expect(result2).toContain( - `${pkg4} 📄 Resolved the specifier as "preminor" using version plans.` + expect(result2).toMatch( + new RegExp( + `${pkg4} ❓ Applied semver relative bump "preminor", read from version plan \\.nx\\/version-plans\\/bump-mixed2\\.md, to get new version 0\\.2\\.0-0` + ) ); - expect(result2).toContain( - `${pkg5} 📄 Resolved the specifier as "patch" using version plans.` + expect(result2).toMatch( + new RegExp( + `${pkg5} ❓ Applied semver relative bump "patch", read from version plan \\.nx\\/version-plans\\/bump-mixed2\\.md, to get new version 0\\.0\\.1` + ) ); // replace the date with a placeholder to make the snapshot deterministic @@ -407,9 +421,7 @@ Update packages in both groups with a mix #2 }, }, version: { - generatorOptions: { - specifierSource: 'version-plans', - }, + specifierSource: 'version-plans', }, changelog: { projectChangelogs: true, @@ -516,21 +528,29 @@ const yargs = require('yargs'); failOnError: false, }); - expect(result).toContain( - `${pkg1} 📄 Resolved the specifier as "minor" using version plans.` + expect(result).toMatch( + new RegExp( + `${pkg1} ❓ Applied semver relative bump "minor", read from version plan \\.nx\\/version-plans\\/bump-fixed\\.md, to get new version 0\\.1\\.0` + ) ); // pkg2 uses the previously resolved specifier from pkg1 expect(result).toContain( - `${pkg2} ✍️ New version 0.1.0 written to ${pkg2}/package.json` + `${pkg2} ✍️ New version 0.1.0 written to manifest: ${pkg2}/package.json` ); - expect(result).toContain( - `${pkg3} 📄 Resolved the specifier as "patch" using version plans.` + expect(result).toMatch( + new RegExp( + `${pkg3} ❓ Applied semver relative bump "patch", read from version plan \\.nx\\/version-plans\\/bump-independent\\.md, to get new version 0\\.0\\.1` + ) ); - expect(result).toContain( - `${pkg4} 📄 Resolved the specifier as "preminor" using version plans.` + expect(result).toMatch( + new RegExp( + `${pkg4} ❓ Applied semver relative bump "preminor", read from version plan \\.nx\\/version-plans\\/bump-independent\\.md, to get new version 0\\.1\\.0-0` + ) ); - expect(result).toContain( - `${pkg5} 📄 Resolved the specifier as "prerelease" using version plans.` + expect(result).toMatch( + new RegExp( + `${pkg5} ❓ Applied semver relative bump "prerelease", read from version plan \\.nx\\/version-plans\\/bump-independent\\.md, to get new version 0\\.0\\.1-0` + ) ); // replace the date with a placeholder to make the snapshot deterministic @@ -651,21 +671,29 @@ Update packages in both groups with a mix #2 silenceError: true, }); - expect(result2).toContain( - `${pkg1} 📄 Resolved the specifier as "minor" using version plans.` + expect(result2).toMatch( + new RegExp( + `${pkg1} ❓ Applied semver relative bump "minor", read from version plan \\.nx\\/version-plans\\/bump-mixed1\\.md, to get new version 0\\.2\\.0` + ) ); // pkg2 uses the previously resolved specifier from pkg1 expect(result2).toContain( - `${pkg2} ✍️ New version 0.2.0 written to ${pkg2}/package.json` + `${pkg2} ✍️ New version 0.2.0 written to manifest: ${pkg2}/package.json` ); - expect(result2).toContain( - `${pkg3} 📄 Resolved the specifier as "patch" using version plans.` + expect(result2).toMatch( + new RegExp( + `${pkg3} ❓ Applied semver relative bump "patch", read from version plan \\.nx\\/version-plans\\/bump-mixed1\\.md, to get new version 0\\.0\\.2` + ) ); - expect(result2).toContain( - `${pkg4} 📄 Resolved the specifier as "preminor" using version plans.` + expect(result2).toMatch( + new RegExp( + `${pkg4} ❓ Applied semver relative bump "preminor", read from version plan \\.nx\\/version-plans\\/bump-mixed2\\.md, to get new version 0\\.2\\.0-0` + ) ); - expect(result2).toContain( - `${pkg5} 📄 Resolved the specifier as "patch" using version plans.` + expect(result2).toMatch( + new RegExp( + `${pkg5} ❓ Applied semver relative bump "patch", read from version plan \\.nx\\/version-plans\\/bump-mixed2\\.md, to get new version 0\\.0\\.1` + ) ); // replace the date with a placeholder to make the snapshot deterministic @@ -789,12 +817,14 @@ Update packages in both groups with a mix #2 silenceError: true, }); - expect(versionResult).toContain( - `${pkg1} 📄 Resolved the specifier as "minor" using version plans.` + expect(versionResult).toMatch( + new RegExp( + `${pkg1} ❓ Applied semver relative bump "minor", read from version plan \\.nx\\/version-plans\\/version-plan-\\d+\\.md, to get new version 0\\.1\\.0` + ) ); // pkg2 uses the previously resolved specifier from pkg1 expect(versionResult).toContain( - `${pkg2} ✍️ New version 0.1.0 written to ${pkg2}/package.json` + `${pkg2} ✍️ New version 0.1.0 written to manifest: ${pkg2}/package.json` ); const changelogResult = runCLI('release changelog 0.1.0 --verbose', { @@ -845,9 +875,7 @@ Update packages in both groups with a mix #2 }, }, version: { - generatorOptions: { - specifierSource: 'version-plans', - }, + specifierSource: 'version-plans', }, changelog: { projectChangelogs: true, @@ -895,24 +923,34 @@ Update packages in both groups with a mix #2 expect(versionResult).toContain( 'Skipping version plan discovery as a specifier was provided' ); - expect(versionResult).toContain( - `${pkg1} 📄 Using the provided version specifier "major".` + expect(versionResult).toMatch( + new RegExp( + `${pkg1} ❓ Applied semver relative bump "major", from the given specifier, to get new version 1\\.0\\.0` + ) ); - expect(versionResult).toContain( - `${pkg2} 📄 Using the provided version specifier "major".` + expect(versionResult).toMatch( + new RegExp( + `${pkg2} ❓ Applied version 1.0.0 directly, because the project is a member of a fixed release group containing ${pkg1}` + ) ); - expect(versionResult).toContain( - `${pkg3} 📄 Using the provided version specifier "major".` + expect(versionResult).toMatch( + new RegExp( + `${pkg3} ❓ Applied semver relative bump "major", from the given specifier, to get new version 1\\.0\\.0` + ) ); - expect(versionResult).toContain( - `${pkg4} 📄 Using the provided version specifier "major".` + expect(versionResult).toMatch( + new RegExp( + `${pkg4} ❓ Applied semver relative bump "major", from the given specifier, to get new version 1\\.0\\.0` + ) ); - expect(versionResult).toContain( - `${pkg5} 📄 Using the provided version specifier "major".` + expect(versionResult).toMatch( + new RegExp( + `${pkg5} ❓ Applied semver relative bump "major", from the given specifier, to get new version 1\\.0\\.0` + ) ); expect(versionResult).toContain( - `git add ${pkg1}/package.json ${pkg2}/package.json ${pkg3}/package.json ${pkg4}/package.json ${pkg5}/package.json` + `git add ${pkg5}/package.json ${pkg4}/package.json ${pkg3}/package.json ${pkg2}/package.json ${pkg1}/package.json` ); expect(readdirSync(versionPlansDir).length).toEqual(2); diff --git a/e2e/utils/command-utils.ts b/e2e/utils/command-utils.ts index dcf65f9650..c995f89e97 100644 --- a/e2e/utils/command-utils.ts +++ b/e2e/utils/command-utils.ts @@ -70,6 +70,8 @@ export function runCommand( cwd: tmpProjPath(), stdio: 'pipe', env: { + // Use new versioning by default in e2e tests + NX_INTERNAL_USE_LEGACY_VERSIONING: 'false', ...getStrippedEnvironmentVariables(), ...childProcessOptions?.env, FORCE_COLOR: 'false', @@ -235,6 +237,8 @@ export function runCommandAsync( cwd: opts.cwd || tmpProjPath(), env: { CI: 'true', + // Use new versioning by default in e2e tests + NX_INTERNAL_USE_LEGACY_VERSIONING: 'false', ...(opts.env || getStrippedEnvironmentVariables()), FORCE_COLOR: 'false', }, @@ -279,6 +283,8 @@ export function runCommandUntil( encoding: 'utf-8', env: { CI: 'true', + // Use new versioning by default in e2e tests + NX_INTERNAL_USE_LEGACY_VERSIONING: 'false', ...getStrippedEnvironmentVariables(), ...opts.env, FORCE_COLOR: 'false', @@ -392,6 +398,8 @@ export function runCLI( cwd: opts.cwd || tmpProjPath(), env: { CI: 'true', + // Use new versioning by default in e2e tests + NX_INTERNAL_USE_LEGACY_VERSIONING: 'false', ...getStrippedEnvironmentVariables(), ...opts.env, }, diff --git a/package.json b/package.json index 80d3d6c4a6..efe7ee42a0 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "@jest/reporters": "29.7.0", "@jest/test-result": "29.7.0", "@jest/types": "29.6.3", + "@ltd/j-toml": "^1.38.0", "@module-federation/enhanced": "^0.9.0", "@module-federation/sdk": "^0.9.0", "@monodon/rust": "2.1.1", diff --git a/packages/angular/src/generators/library/lib/add-project.ts b/packages/angular/src/generators/library/lib/add-project.ts index 40c8a65959..8847448790 100644 --- a/packages/angular/src/generators/library/lib/add-project.ts +++ b/packages/angular/src/generators/library/lib/add-project.ts @@ -1,9 +1,14 @@ import type { Tree } from '@nx/devkit'; -import { addProjectConfiguration, joinPathFragments } from '@nx/devkit'; -import type { AngularProjectConfiguration } from '../../../utils/types'; -import type { NormalizedSchema } from './normalized-schema'; +import { + addProjectConfiguration, + joinPathFragments, + readJson, +} from '@nx/devkit'; import { addBuildTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils'; import { addReleaseConfigForNonTsSolution } from '@nx/js/src/generators/library/utils/add-release-config'; +import { shouldUseLegacyVersioning } from 'nx/src/command-line/release/config/use-legacy-versioning'; +import type { AngularProjectConfiguration } from '../../../utils/types'; +import type { NormalizedSchema } from './normalized-schema'; export async function addProject( tree: Tree, @@ -44,7 +49,9 @@ export async function addProject( }; if (libraryOptions.publishable) { + const nxJson = readJson(tree, 'nx.json'); await addReleaseConfigForNonTsSolution( + shouldUseLegacyVersioning(nxJson.release), tree, libraryOptions.name, project diff --git a/packages/js/src/generators/library/library.ts b/packages/js/src/generators/library/library.ts index de6bbf1ab0..8b0bb72df7 100644 --- a/packages/js/src/generators/library/library.ts +++ b/packages/js/src/generators/library/library.ts @@ -28,10 +28,12 @@ import { import { promptWhenInteractive } from '@nx/devkit/src/generators/prompt'; import { addBuildTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils'; import { logShowProjectCommand } from '@nx/devkit/src/utils/log-show-project-command'; +import { shouldUseLegacyVersioning } from 'nx/src/command-line/release/config/use-legacy-versioning'; import { type PackageJson } from 'nx/src/utils/package-json'; import { join } from 'path'; import type { CompilerOptions } from 'typescript'; import { normalizeLinterOption } from '../../utils/generator-prompts'; +import { sortPackageJsonFields } from '../../utils/package-json/sort-fields'; import { getUpdatedPackageJsonContent } from '../../utils/package-json/update-package-json'; import { addSwcConfig } from '../../utils/swc/add-swc-config'; import { getSwcDependencies } from '../../utils/swc/add-swc-dependencies'; @@ -63,7 +65,6 @@ import type { LibraryGeneratorSchema, NormalizedLibraryGeneratorOptions, } from './schema'; -import { sortPackageJsonFields } from '../../utils/package-json/sort-fields'; import { addReleaseConfigForNonTsSolution, addReleaseConfigForTsSolution, @@ -264,8 +265,9 @@ async function configureProject( tree: Tree, options: NormalizedLibraryGeneratorOptions ) { + const nxJson = readNxJson(tree); + if (options.hasPlugin) { - const nxJson = readNxJson(tree); ensureProjectIsIncludedInPluginRegistrations( nxJson, options.projectRoot, @@ -343,6 +345,7 @@ async function configureProject( ); } else { await addReleaseConfigForNonTsSolution( + shouldUseLegacyVersioning(nxJson.release), tree, options.name, projectConfiguration, diff --git a/packages/js/src/generators/library/utils/add-release-config.spec.ts b/packages/js/src/generators/library/utils/add-release-config.spec.ts index c006635c59..91f89bc657 100644 --- a/packages/js/src/generators/library/utils/add-release-config.spec.ts +++ b/packages/js/src/generators/library/utils/add-release-config.spec.ts @@ -12,6 +12,8 @@ import { addReleaseConfigForTsSolution, } from './add-release-config'; +const USE_LEGACY_VERSIONING = true; + describe('add release config', () => { let tree: Tree; @@ -23,7 +25,12 @@ describe('add release config', () => { describe('addReleaseConfigForNonTsSolution', () => { it('should update the nx-release-publish target to specify dist/{projectRoot} as the package root', async () => { const projectConfig: ProjectConfiguration = { root: 'libs/my-lib' }; - await addReleaseConfigForNonTsSolution(tree, 'my-lib', projectConfig); + await addReleaseConfigForNonTsSolution( + USE_LEGACY_VERSIONING, + tree, + 'my-lib', + projectConfig + ); expect(projectConfig.targets?.['nx-release-publish']).toEqual({ options: { packageRoot: 'dist/{projectRoot}', @@ -42,7 +49,12 @@ describe('add release config', () => { }); const projectConfig = { root: 'libs/my-lib' }; - await addReleaseConfigForNonTsSolution(tree, 'my-lib', projectConfig); + await addReleaseConfigForNonTsSolution( + USE_LEGACY_VERSIONING, + tree, + 'my-lib', + projectConfig + ); const nxJson = readJson(tree, 'nx.json'); expect(nxJson.release).toEqual({ @@ -59,7 +71,12 @@ describe('add release config', () => { }); const projectConfig = { root: 'libs/my-lib' }; - await addReleaseConfigForNonTsSolution(tree, 'my-lib', projectConfig); + await addReleaseConfigForNonTsSolution( + USE_LEGACY_VERSIONING, + tree, + 'my-lib', + projectConfig + ); const nxJson = readJson(tree, 'nx.json'); expect(nxJson.release).toEqual({ @@ -86,7 +103,12 @@ describe('add release config', () => { }); const projectConfig = { root: 'libs/my-lib' }; - await addReleaseConfigForNonTsSolution(tree, 'my-lib', projectConfig); + await addReleaseConfigForNonTsSolution( + USE_LEGACY_VERSIONING, + tree, + 'my-lib', + projectConfig + ); const nxJson = readJson(tree, 'nx.json'); expect(nxJson.release).toEqual({ @@ -109,7 +131,12 @@ describe('add release config', () => { }); const projectConfig = { root: 'libs/my-lib' }; - await addReleaseConfigForNonTsSolution(tree, 'my-lib', projectConfig); + await addReleaseConfigForNonTsSolution( + USE_LEGACY_VERSIONING, + tree, + 'my-lib', + projectConfig + ); const nxJson = readJson(tree, 'nx.json'); expect(nxJson.release).toEqual({ @@ -131,7 +158,12 @@ describe('add release config', () => { }); const projectConfig = { root: 'libs/my-lib' }; - await addReleaseConfigForNonTsSolution(tree, 'my-lib', projectConfig); + await addReleaseConfigForNonTsSolution( + USE_LEGACY_VERSIONING, + tree, + 'my-lib', + projectConfig + ); const nxJson = readJson(tree, 'nx.json'); expect(nxJson.release).toEqual({ @@ -156,7 +188,12 @@ describe('add release config', () => { root: 'libs/my-lib', tags: ['one', 'two'], }; - await addReleaseConfigForNonTsSolution(tree, 'my-lib', projectConfig); + await addReleaseConfigForNonTsSolution( + USE_LEGACY_VERSIONING, + tree, + 'my-lib', + projectConfig + ); const nxJson = readJson(tree, 'nx.json'); expect(nxJson.release).toEqual({ @@ -178,7 +215,12 @@ describe('add release config', () => { }); const projectConfig = { root: 'packages/my-lib' }; - await addReleaseConfigForNonTsSolution(tree, 'my-lib', projectConfig); + await addReleaseConfigForNonTsSolution( + USE_LEGACY_VERSIONING, + tree, + 'my-lib', + projectConfig + ); const nxJson = readJson(tree, 'nx.json'); expect(nxJson.release).toEqual({ @@ -200,7 +242,12 @@ describe('add release config', () => { }); const projectConfig = { root: 'libs/my-lib' }; - await addReleaseConfigForNonTsSolution(tree, 'my-lib', projectConfig); + await addReleaseConfigForNonTsSolution( + USE_LEGACY_VERSIONING, + tree, + 'my-lib', + projectConfig + ); const nxJson = readJson(tree, 'nx.json'); expect(nxJson.release).toEqual({ @@ -222,7 +269,12 @@ describe('add release config', () => { }); const projectConfig = { root: 'libs/my-lib' }; - await addReleaseConfigForNonTsSolution(tree, 'my-lib', projectConfig); + await addReleaseConfigForNonTsSolution( + USE_LEGACY_VERSIONING, + tree, + 'my-lib', + projectConfig + ); const nxJson = readJson(tree, 'nx.json'); expect(nxJson.release).toEqual({ @@ -252,7 +304,12 @@ describe('add release config', () => { }); const projectConfig = { root: 'libs/my-lib' }; - await addReleaseConfigForNonTsSolution(tree, 'my-lib', projectConfig); + await addReleaseConfigForNonTsSolution( + USE_LEGACY_VERSIONING, + tree, + 'my-lib', + projectConfig + ); const nxJson = readJson(tree, 'nx.json'); expect(nxJson.release).toEqual({ @@ -288,7 +345,12 @@ describe('add release config', () => { }); const projectConfig = { root: 'libs/my-lib' }; - await addReleaseConfigForNonTsSolution(tree, 'my-lib', projectConfig); + await addReleaseConfigForNonTsSolution( + USE_LEGACY_VERSIONING, + tree, + 'my-lib', + projectConfig + ); const nxJson = readJson(tree, 'nx.json'); expect(nxJson.release).toEqual({ diff --git a/packages/js/src/generators/library/utils/add-release-config.ts b/packages/js/src/generators/library/utils/add-release-config.ts index fa2865bf03..cb399444b7 100644 --- a/packages/js/src/generators/library/utils/add-release-config.ts +++ b/packages/js/src/generators/library/utils/add-release-config.ts @@ -114,6 +114,7 @@ export async function addReleaseConfigForTsSolution( * Add release option in project.json and add packageRoot to nx-release-publish target */ export async function addReleaseConfigForNonTsSolution( + useLegacyVersioning: boolean, tree: Tree, projectName: string, projectConfiguration: ProjectConfiguration, @@ -131,17 +132,30 @@ export async function addReleaseConfigForNonTsSolution( }, }; - projectConfiguration.release = { - version: { - generatorOptions: { - packageRoot, + if (useLegacyVersioning) { + projectConfiguration.release = { + version: { + generatorOptions: { + packageRoot, + // using git tags to determine the current version is required here because + // the version in the package root is overridden with every build + currentVersionResolver: 'git-tag', + fallbackCurrentVersionResolver: 'disk', + }, + }, + }; + } else { + // TODO: re-evaluate this config in new versions + projectConfiguration.release = { + version: { + manifestRootsToUpdate: [packageRoot], // using git tags to determine the current version is required here because // the version in the package root is overridden with every build currentVersionResolver: 'git-tag', fallbackCurrentVersionResolver: 'disk', }, - }, - }; + }; + } await addReleaseConfigForTsSolution(tree, projectName, projectConfiguration); diff --git a/packages/js/src/generators/release-version/release-version-workspace-root-project.spec.ts b/packages/js/src/generators/release-version/release-version-workspace-root-project.spec.ts index bc99409760..99b9f966a8 100644 --- a/packages/js/src/generators/release-version/release-version-workspace-root-project.spec.ts +++ b/packages/js/src/generators/release-version/release-version-workspace-root-project.spec.ts @@ -102,8 +102,9 @@ describe('release-version-workspace-root-project', () => { }); }); - // TODO This will not pass until NXC-573 is resolved - // Until then, this test will error because the version generator is incorrectly + // NOTE: The ported version of this test passes in "versioning v2" + // + // NXC-573: this test will error because the version generator is incorrectly // looking for 'dist/libs/depends-on-my-lib/package.json' when it doesn't exist. it.skip('should not error when run with custom packageRoot containing {projectRoot}', async () => { projectGraph = createWorkspaceWithPackageDependencies(tree, { diff --git a/packages/js/src/generators/release-version/release-version.spec.ts b/packages/js/src/generators/release-version/release-version.spec.ts index 32bb988760..3805d4f27e 100644 --- a/packages/js/src/generators/release-version/release-version.spec.ts +++ b/packages/js/src/generators/release-version/release-version.spec.ts @@ -1821,139 +1821,6 @@ Valid values are: "auto", "", "~", "^", "="`, } `); }); - - it('should not double patch transitive dependents that are already direct dependents', async () => { - projectGraph = createWorkspaceWithPackageDependencies(tree, { - '@slateui/core': { - projectRoot: 'packages/core', - packageName: '@slateui/core', - version: '1.0.0', - packageJsonPath: 'packages/core/package.json', - localDependencies: [], - }, - // buttons depends on core - '@slateui/buttons': { - projectRoot: 'packages/buttons', - packageName: '@slateui/buttons', - version: '1.0.0', - packageJsonPath: 'packages/buttons/package.json', - localDependencies: [ - { - projectName: '@slateui/core', - dependencyCollection: 'dependencies', - version: '1.0.0', - }, - ], - }, - // forms depends on both core and buttons, making it both a direct and transitive dependent of core - '@slateui/forms': { - projectRoot: 'packages/forms', - packageName: '@slateui/forms', - version: '1.0.0', - packageJsonPath: 'packages/forms/package.json', - localDependencies: [ - { - projectName: '@slateui/core', - dependencyCollection: 'dependencies', - version: '1.0.0', - }, - { - projectName: '@slateui/buttons', - dependencyCollection: 'dependencies', - version: '1.0.0', - }, - ], - }, - }); - - expect( - await releaseVersionGenerator(tree, { - projects: [projectGraph.nodes['@slateui/core']], - releaseGroup: createReleaseGroup('independent'), - projectGraph, - // Bump core to 2.0.0, which will cause buttons and forms to be patched to 1.0.1 - // This prevents a regression against an issue where forms would end up being patched twice to 1.0.2 in this scenario - specifier: '2.0.0', - currentVersionResolver: 'disk', - specifierSource: 'prompt', - }) - ).toMatchInlineSnapshot(` - { - "callback": [Function], - "data": { - "@slateui/buttons": { - "currentVersion": "1.0.0", - "dependentProjects": [ - { - "dependencyCollection": "dependencies", - "rawVersionSpec": "1.0.0", - "source": "@slateui/forms", - "target": "@slateui/buttons", - "type": "static", - }, - ], - "newVersion": "1.0.1", - }, - "@slateui/core": { - "currentVersion": "1.0.0", - "dependentProjects": [ - { - "dependencyCollection": "dependencies", - "rawVersionSpec": "1.0.0", - "source": "@slateui/buttons", - "target": "@slateui/core", - "type": "static", - }, - { - "dependencyCollection": "dependencies", - "rawVersionSpec": "1.0.0", - "source": "@slateui/forms", - "target": "@slateui/core", - "type": "static", - }, - ], - "newVersion": "2.0.0", - }, - "@slateui/forms": { - "currentVersion": "1.0.0", - "dependentProjects": [], - "newVersion": "1.0.1", - }, - }, - } - `); - - expect(readJson(tree, 'packages/core/package.json')) - .toMatchInlineSnapshot(` - { - "name": "@slateui/core", - "version": "2.0.0", - } - `); - - expect(readJson(tree, 'packages/buttons/package.json')) - .toMatchInlineSnapshot(` - { - "dependencies": { - "@slateui/core": "2.0.0", - }, - "name": "@slateui/buttons", - "version": "1.0.1", - } - `); - - expect(readJson(tree, 'packages/forms/package.json')) - .toMatchInlineSnapshot(` - { - "dependencies": { - "@slateui/buttons": "1.0.1", - "@slateui/core": "2.0.0", - }, - "name": "@slateui/forms", - "version": "1.0.1", - } - `); - }); }); }); @@ -2159,6 +2026,138 @@ Valid values are: "auto", "", "~", "^", "="`, `); }); }); + + it('should not double patch transitive dependents that are already direct dependents', async () => { + projectGraph = createWorkspaceWithPackageDependencies(tree, { + '@slateui/core': { + projectRoot: 'packages/core', + packageName: '@slateui/core', + version: '1.0.0', + packageJsonPath: 'packages/core/package.json', + localDependencies: [], + }, + // buttons depends on core + '@slateui/buttons': { + projectRoot: 'packages/buttons', + packageName: '@slateui/buttons', + version: '1.0.0', + packageJsonPath: 'packages/buttons/package.json', + localDependencies: [ + { + projectName: '@slateui/core', + dependencyCollection: 'dependencies', + version: '1.0.0', + }, + ], + }, + // forms depends on both core and buttons, making it both a direct and transitive dependent of core + '@slateui/forms': { + projectRoot: 'packages/forms', + packageName: '@slateui/forms', + version: '1.0.0', + packageJsonPath: 'packages/forms/package.json', + localDependencies: [ + { + projectName: '@slateui/core', + dependencyCollection: 'dependencies', + version: '1.0.0', + }, + { + projectName: '@slateui/buttons', + dependencyCollection: 'dependencies', + version: '1.0.0', + }, + ], + }, + }); + + expect( + await releaseVersionGenerator(tree, { + projects: [projectGraph.nodes['@slateui/core']], + releaseGroup: createReleaseGroup('independent'), + projectGraph, + // Bump core to 2.0.0, which will cause buttons and forms to be patched to 1.0.1 + // This prevents a regression against an issue where forms would end up being patched twice to 1.0.2 in this scenario + specifier: '2.0.0', + currentVersionResolver: 'disk', + specifierSource: 'prompt', + }) + ).toMatchInlineSnapshot(` + { + "callback": [Function], + "data": { + "@slateui/buttons": { + "currentVersion": "1.0.0", + "dependentProjects": [ + { + "dependencyCollection": "dependencies", + "rawVersionSpec": "1.0.0", + "source": "@slateui/forms", + "target": "@slateui/buttons", + "type": "static", + }, + ], + "newVersion": "1.0.1", + }, + "@slateui/core": { + "currentVersion": "1.0.0", + "dependentProjects": [ + { + "dependencyCollection": "dependencies", + "rawVersionSpec": "1.0.0", + "source": "@slateui/buttons", + "target": "@slateui/core", + "type": "static", + }, + { + "dependencyCollection": "dependencies", + "rawVersionSpec": "1.0.0", + "source": "@slateui/forms", + "target": "@slateui/core", + "type": "static", + }, + ], + "newVersion": "2.0.0", + }, + "@slateui/forms": { + "currentVersion": "1.0.0", + "dependentProjects": [], + "newVersion": "1.0.1", + }, + }, + } + `); + + expect(readJson(tree, 'packages/core/package.json')).toMatchInlineSnapshot(` + { + "name": "@slateui/core", + "version": "2.0.0", + } + `); + + expect(readJson(tree, 'packages/buttons/package.json')) + .toMatchInlineSnapshot(` + { + "dependencies": { + "@slateui/core": "2.0.0", + }, + "name": "@slateui/buttons", + "version": "1.0.1", + } + `); + + expect(readJson(tree, 'packages/forms/package.json')) + .toMatchInlineSnapshot(` + { + "dependencies": { + "@slateui/buttons": "1.0.1", + "@slateui/core": "2.0.0", + }, + "name": "@slateui/forms", + "version": "1.0.1", + } + `); + }); }); function createReleaseGroup( diff --git a/packages/js/src/generators/release-version/release-version.ts b/packages/js/src/generators/release-version/release-version.ts index 592a410873..c55c67c752 100644 --- a/packages/js/src/generators/release-version/release-version.ts +++ b/packages/js/src/generators/release-version/release-version.ts @@ -33,10 +33,11 @@ import { VersionData, deriveNewSemverVersion, validReleaseVersionPrefixes, -} from 'nx/src/command-line/release/version'; +} from 'nx/src/command-line/release/version-legacy'; import { interpolate } from 'nx/src/tasks-runner/utils'; import * as ora from 'ora'; import { ReleaseType, gt, inc, prerelease } from 'semver'; +import { updateLockFile } from '../../release/utils/update-lock-file'; import { isLocallyLinkedPackageVersion } from '../../utils/is-locally-linked-package-version'; import { parseRegistryOptions } from '../../utils/npm-config'; import { ReleaseVersionGeneratorSchema } from './schema'; @@ -45,7 +46,6 @@ import { resolveLocalPackageDependencies, } from './utils/resolve-local-package-dependencies'; import { sortProjectsTopologically } from './utils/sort-projects-topologically'; -import { updateLockFile } from './utils/update-lock-file'; function resolvePreIdSpecifier(currentSpecifier: string, preid?: string) { if (!currentSpecifier.startsWith('pre') && preid) { @@ -103,7 +103,6 @@ Valid values are: ${validReleaseVersionPrefixes ) as ReleaseType; // Sort the projects topologically if update dependents is enabled - // TODO: maybe move this sorting to the command level? const projects = updateDependents === 'never' || options.releaseGroup.projectsRelationship !== 'independent' @@ -446,8 +445,6 @@ To fix this you will either need to add a package.json file at that location, or break; } - // TODO: reevaluate this prerelease logic/workflow for independent projects - // // Always assume that if the current version is a prerelease, then the next version should be a prerelease. // Users must manually graduate from a prerelease to a release by providing an explicit specifier. if (prerelease(currentVersion ?? '')) { @@ -1012,7 +1009,12 @@ To fix this you will either need to add a package.json file at that location, or } const cwd = tree.root; - changedFiles.push(...(await updateLockFile(cwd, opts))); + changedFiles.push( + ...(await updateLockFile(cwd, { + ...opts, + useLegacyVersioning: true, + })) + ); return { changedFiles, deletedFiles }; }, }; @@ -1044,7 +1046,8 @@ function createResolvePackageRoot(customPackageRoot?: string) { return projectNode.data.root; } if (projectNode.data.root === '.') { - // TODO This is a temporary workaround to fix NXC-574 until NXC-573 is resolved + // This is a temporary workaround to fix NXC-574 until NXC-573 is resolved. + // This has been fixed in "versioning v2" return projectNode.data.root; } return interpolate(customPackageRoot, { diff --git a/packages/js/src/generators/release-version/schema.d.ts b/packages/js/src/generators/release-version/schema.d.ts index 0bd430fb7f..e2eb0e9002 100644 --- a/packages/js/src/generators/release-version/schema.d.ts +++ b/packages/js/src/generators/release-version/schema.d.ts @@ -1 +1 @@ -export { ReleaseVersionGeneratorSchema } from 'nx/src/command-line/release/version'; +export { ReleaseVersionGeneratorSchema } from 'nx/src/command-line/release/version-legacy'; diff --git a/packages/js/src/generators/release-version/schema.json b/packages/js/src/generators/release-version/schema.json index df303fe607..078c3951be 100644 --- a/packages/js/src/generators/release-version/schema.json +++ b/packages/js/src/generators/release-version/schema.json @@ -8,6 +8,8 @@ "properties": { "projects": { "type": "array", + "hidden": true, + "$comment": "This is not configured/passed by the user. It is provided by the version command.", "description": "The ProjectGraphProjectNodes being versioned in the current execution.", "items": { "type": "object" @@ -15,16 +17,33 @@ }, "projectGraph": { "type": "object", + "hidden": true, + "$comment": "This is not configured/passed by the user. It is provided by the version command.", "description": "ProjectGraph instance" }, + "releaseGroup": { + "type": "object", + "hidden": true, + "$comment": "This is not configured/passed by the user. It is provided by the version command.", + "description": "The resolved release group configuration, including name, relevant to all projects in the current execution." + }, + "conventionalCommitsConfig": { + "type": "object", + "hidden": true, + "$comment": "This is not configured/passed by the user. It is provided by the version command.", + "description": "The conventional commits configuration to use when determining the next version of the project.", + "default": {} + }, + "firstRelease": { + "type": "boolean", + "hidden": true, + "$comment": "This is not configured/passed by the user. It is provided by the version command.", + "description": "Whether this is the first release of the project (which skips some validation within the generator)." + }, "specifier": { "type": "string", "description": "Exact version or semver keyword to apply to the selected release group. Overrides specifierSource." }, - "releaseGroup": { - "type": "object", - "description": "The resolved release group configuration, including name, relevant to all projects in the current execution." - }, "specifierSource": { "type": "string", "default": "prompt", diff --git a/packages/js/src/generators/release-version/utils/update-lock-file.ts b/packages/js/src/release/utils/update-lock-file.ts similarity index 80% rename from packages/js/src/generators/release-version/utils/update-lock-file.ts rename to packages/js/src/release/utils/update-lock-file.ts index 3d4f38e0c1..b34ccaa8ec 100644 --- a/packages/js/src/generators/release-version/utils/update-lock-file.ts +++ b/packages/js/src/release/utils/update-lock-file.ts @@ -16,14 +16,20 @@ export async function updateLockFile( { dryRun, verbose, - generatorOptions, + useLegacyVersioning, + options, }: { dryRun?: boolean; verbose?: boolean; - generatorOptions?: Record; + useLegacyVersioning?: boolean; + options?: { + skipLockFileUpdate?: boolean; + installArgs?: string; + installIgnoreScripts?: boolean; + }; } ) { - if (generatorOptions?.skipLockFileUpdate) { + if (options?.skipLockFileUpdate) { if (verbose) { console.log( '\nSkipped lock file update because skipLockFileUpdate was set.' @@ -65,13 +71,13 @@ export async function updateLockFile( const packageManagerCommands = getPackageManagerCommand(packageManager); - let installArgs = generatorOptions?.installArgs || ''; + let installArgs = options?.installArgs || ''; output.logSingleLine(`Updating ${packageManager} lock file`); let env: object = {}; - if (generatorOptions?.installIgnoreScripts) { + if (options?.installIgnoreScripts) { if (packageManager === 'yarn') { env = { YARN_ENABLE_SCRIPTS: 'false' }; } else { @@ -99,7 +105,7 @@ export async function updateLockFile( return []; } - execLockFileUpdate(command, cwd, env); + execLockFileUpdate(command, cwd, env, useLegacyVersioning); if (isDaemonEnabled) { try { @@ -122,7 +128,8 @@ export async function updateLockFile( function execLockFileUpdate( command: string, cwd: string, - env: object = {} + env: object, + useLegacyVersioning: boolean ): void { try { const LARGE_BUFFER = 1024 * 1000000; @@ -136,13 +143,17 @@ function execLockFileUpdate( windowsHide: false, }); } catch (e) { + const configPathStart = useLegacyVersioning + ? 'release.version.generatorOptions' + : 'release.version.versionActionsOptions'; + output.error({ title: `Error updating lock file with command '${command}'`, bodyLines: [ `Verify that '${command}' succeeds when run from the workspace root.`, - `To configure a string of arguments to be passed to this command, set the 'release.version.generatorOptions.installArgs' property in nx.json.`, - `To ignore install lifecycle scripts, set 'release.version.generatorOptions.installIgnoreScripts' to true in nx.json.`, - `To disable this step entirely, set 'release.version.generatorOptions.skipLockFileUpdate' to true in nx.json.`, + `To configure a string of arguments to be passed to this command, set the '${configPathStart}.installArgs' property in nx.json.`, + `To ignore install lifecycle scripts, set '${configPathStart}.installIgnoreScripts' to true in nx.json.`, + `To disable this step entirely, set '${configPathStart}.skipLockFileUpdate' to true in nx.json.`, ], }); throw e; diff --git a/packages/js/src/release/version-actions.ts b/packages/js/src/release/version-actions.ts new file mode 100644 index 0000000000..56ab0d2b50 --- /dev/null +++ b/packages/js/src/release/version-actions.ts @@ -0,0 +1,273 @@ +import { + detectPackageManager, + PackageManager, + ProjectGraph, + readJson, + Tree, + updateJson, + workspaceRoot, +} from '@nx/devkit'; +import { exec } from 'node:child_process'; +import { join } from 'node:path'; +import { AfterAllProjectsVersioned, VersionActions } from 'nx/release'; +import type { NxReleaseVersionV2Configuration } from 'nx/src/config/nx-json'; +import { parseRegistryOptions } from '../utils/npm-config'; +import { updateLockFile } from './utils/update-lock-file'; +import chalk = require('chalk'); + +export const afterAllProjectsVersioned: AfterAllProjectsVersioned = async ( + cwd: string, + opts: { + dryRun?: boolean; + verbose?: boolean; + rootVersionActionsOptions?: Record; + } +) => { + return { + changedFiles: await updateLockFile(cwd, { + ...opts, + useLegacyVersioning: false, + }), + deletedFiles: [], + }; +}; + +// Cache at the module level to avoid re-detecting the package manager for each instance +let pm: PackageManager | undefined; + +export default class JsVersionActions extends VersionActions { + validManifestFilenames = ['package.json']; + + async readCurrentVersionFromSourceManifest(tree: Tree): Promise<{ + currentVersion: string; + manifestPath: string; + }> { + const sourcePackageJsonPath = join( + this.projectGraphNode.data.root, + 'package.json' + ); + try { + const packageJson = readJson(tree, sourcePackageJsonPath); + return { + manifestPath: sourcePackageJsonPath, + currentVersion: packageJson.version, + }; + } catch { + throw new Error( + `Unable to determine the current version for project "${this.projectGraphNode.name}" from ${sourcePackageJsonPath}, please ensure that the "version" field is set within the package.json file` + ); + } + } + + async readCurrentVersionFromRegistry( + tree: Tree, + currentVersionResolverMetadata: NxReleaseVersionV2Configuration['currentVersionResolverMetadata'] + ): Promise<{ + currentVersion: string; + logText: string; + }> { + const sourcePackageJsonPath = join( + this.projectGraphNode.data.root, + 'package.json' + ); + const packageJson = readJson(tree, sourcePackageJsonPath); + const packageName = packageJson.name; + + const metadata = currentVersionResolverMetadata; + const registryArg = + typeof metadata?.registry === 'string' ? metadata.registry : undefined; + const tagArg = typeof metadata?.tag === 'string' ? metadata.tag : undefined; + + const warnFn = (message: string) => { + console.log(chalk.keyword('orange')(message)); + }; + const { registry, tag, registryConfigKey } = await parseRegistryOptions( + workspaceRoot, + { + packageRoot: this.projectGraphNode.data.root, + packageJson, + }, + { + registry: registryArg, + tag: tagArg, + }, + warnFn + ); + + let currentVersion = null; + try { + // Must be non-blocking async to allow spinner to render + currentVersion = await new Promise((resolve, reject) => { + exec( + `npm view ${packageName} version --"${registryConfigKey}=${registry}" --tag=${tag}`, + { + windowsHide: false, + }, + (error, stdout, stderr) => { + if (error) { + return reject(error); + } + if (stderr) { + return reject(stderr); + } + return resolve(stdout.trim()); + } + ); + }); + } catch {} + + return { + currentVersion, + // Make troubleshooting easier by including the registry and tag data in the log text + logText: `"${registryConfigKey}=${registry}" tag=${tag}`, + }; + } + + async readCurrentVersionOfDependency( + tree: Tree, + projectGraph: ProjectGraph, + dependencyProjectName: string + ): Promise<{ + currentVersion: string | null; + dependencyCollection: string | null; + }> { + const sourcePackageJsonPath = join( + this.projectGraphNode.data.root, + 'package.json' + ); + const json = readJson(tree, sourcePackageJsonPath); + // Resolve the package name from the project graph metadata, as it may not match the project name + const dependencyPackageName = + projectGraph.nodes[dependencyProjectName].data.metadata?.js?.packageName; + const dependencyTypes = [ + 'dependencies', + 'devDependencies', + 'peerDependencies', + 'optionalDependencies', + ]; + + let currentVersion = null; + let dependencyCollection = null; + for (const depType of dependencyTypes) { + if (json[depType] && json[depType][dependencyPackageName]) { + currentVersion = json[depType][dependencyPackageName]; + dependencyCollection = depType; + break; + } + } + return { + currentVersion, + dependencyCollection, + }; + } + + // NOTE: The TODOs were carried over from the original implementation, they are not yet implemented + async isLocalDependencyProtocol(versionSpecifier: string): Promise { + const localPackageProtocols = [ + 'file:', // all package managers + 'workspace:', // not npm + // TODO: Support portal protocol at the project graph level before enabling here + // 'portal:', // modern yarn only + ]; + + // Not using a supported local protocol + if ( + !localPackageProtocols.some((protocol) => + versionSpecifier.startsWith(protocol) + ) + ) { + return false; + } + // Supported by all package managers + if (versionSpecifier.startsWith('file:')) { + return true; + } + // Determine specific package manager in use + if (!pm) { + pm = detectPackageManager(); + // pmVersion = getPackageManagerVersion(pm); + } + if (pm === 'npm' && versionSpecifier.startsWith('workspace:')) { + throw new Error( + `The "workspace:" protocol is not yet supported by npm (https://github.com/npm/rfcs/issues/765). Please ensure you have a valid setup according to your package manager before attempting to release packages.` + ); + } + // TODO: Support portal protocol at the project graph level before enabling here + // if ( + // version.startsWith('portal:') && + // (pm !== 'yarn' || lt(pmVersion, '2.0.0')) + // ) { + // throw new Error( + // `The "portal:" protocol is only supported by yarn@2.0.0 and above. Please ensure you have a valid setup according to your package manager before attempting to release packages.` + // ); + // } + return true; + } + + async updateProjectVersion( + tree: Tree, + newVersion: string + ): Promise { + const logMessages: string[] = []; + for (const manifestPath of this.manifestsToUpdate) { + updateJson(tree, manifestPath, (json) => { + json.version = newVersion; + return json; + }); + logMessages.push( + `✍️ New version ${newVersion} written to manifest: ${manifestPath}` + ); + } + return logMessages; + } + + async updateProjectDependencies( + tree: Tree, + projectGraph: ProjectGraph, + dependenciesToUpdate: Record + ): Promise { + const numDependenciesToUpdate = Object.keys(dependenciesToUpdate).length; + const depText = + numDependenciesToUpdate === 1 ? 'dependency' : 'dependencies'; + if (numDependenciesToUpdate === 0) { + return []; + } + + const logMessages: string[] = []; + for (const manifestPath of this.manifestsToUpdate) { + updateJson(tree, manifestPath, (json) => { + const dependencyTypes = [ + 'dependencies', + 'devDependencies', + 'peerDependencies', + 'optionalDependencies', + ]; + + for (const depType of dependencyTypes) { + if (json[depType]) { + for (const [dep, version] of Object.entries(dependenciesToUpdate)) { + // Resolve the package name from the project graph metadata, as it may not match the project name + const packageName = + projectGraph.nodes[dep].data.metadata?.js?.packageName; + if (!packageName) { + throw new Error( + `Unable to determine the package name for project "${dep}" from the project graph metadata, please ensure that the "@nx/js" plugin is installed and the project graph has been built. If the issue persists, please report this issue on https://github.com/nrwl/nx/issues` + ); + } + if (json[depType][packageName]) { + json[depType][packageName] = version; + } + } + } + } + + return json; + }); + + logMessages.push( + `✍️ Updated ${numDependenciesToUpdate} ${depText} in manifest: ${manifestPath}` + ); + } + return logMessages; + } +} diff --git a/packages/nx/.eslintrc.json b/packages/nx/.eslintrc.json index 8ed6c6cf45..642712f288 100644 --- a/packages/nx/.eslintrc.json +++ b/packages/nx/.eslintrc.json @@ -134,7 +134,9 @@ "@nx/key", // Powerpack plugin conditionally available dynamically at runtime "@nx/powerpack-conformance", - "@nx/conformance" + "@nx/conformance", + // Only used in test-utils at the time of writing + "@ltd/j-toml" ] } ] diff --git a/packages/nx/release/index.ts b/packages/nx/release/index.ts index 43a3c6072e..e441c4d84d 100644 --- a/packages/nx/release/index.ts +++ b/packages/nx/release/index.ts @@ -7,4 +7,6 @@ export { releaseChangelog, releasePublish, releaseVersion, + VersionActions, + AfterAllProjectsVersioned, } from '../src/command-line/release'; diff --git a/packages/nx/schemas/nx-schema.json b/packages/nx/schemas/nx-schema.json index c516cbce33..be8ce4ab7a 100644 --- a/packages/nx/schemas/nx-schema.json +++ b/packages/nx/schemas/nx-schema.json @@ -684,50 +684,150 @@ }, "NxReleaseVersionConfiguration": { "type": "object", - "properties": { - "conventionalCommits": { - "type": "boolean", - "description": "Shorthand for enabling the current version of projects to be resolved from git tags, and the next version to be determined by analyzing commit messages according to the Conventional Commits specification.", - "default": false - }, - "generator": { - "type": "string" - }, - "generatorOptions": { - "type": "object", - "additionalProperties": true - }, - "git": { - "$ref": "#/definitions/NxReleaseGitConfiguration" - }, - "preVersionCommand": { - "type": "string", - "description": "A command to run after validation of nx release configuration, but before versioning begins. Useful for preparing build artifacts. If --dry-run is passed, the command is still executed, but with the NX_DRY_RUN environment variable set to 'true'." + "$comment": "The configuration for versioning is dynamic depending on the value of the useLegacyVersioning property. Through trial and error the best in editor DX seems to come from having the if/else at the top level and explicitly include all possible properties and apply additionalProperties false in each case.", + "description": "Configuration for the versioning phase of releases.", + "if": { + "$comment": "When using the latest versioning implementation a lot of configuration has been able to move directly onto the version property.", + "properties": { + "useLegacyVersioning": { + "const": true + } } }, - "additionalProperties": false + "then": { + "properties": { + "useLegacyVersioning": { + "type": "boolean", + "description": "Whether to use the legacy versioning strategy. This value will be true in Nx v20 and false in Nx v21. The legacy versioning implementation will be removed in Nx v22, as will this flag.", + "default": true + }, + "conventionalCommits": { + "type": "boolean", + "description": "Shorthand for enabling the current version of projects to be resolved from git tags, and the next version to be determined by analyzing commit messages according to the Conventional Commits specification.", + "default": false + }, + "git": { + "$ref": "#/definitions/NxReleaseGitConfiguration" + }, + "preVersionCommand": { + "type": "string", + "description": "A command to run after validation of nx release configuration, but before versioning begins. Useful for preparing build artifacts. If --dry-run is passed, the command is still executed, but with the NX_DRY_RUN environment variable set to 'true'." + }, + "generator": { + "type": "string", + "description": "The generator implementation to use for versioning.", + "default": "@nx/js:release-version" + }, + "generatorOptions": { + "type": "object", + "description": "These options will be passed to the configured \"release.version.generator\" (which will be \"@nx/js:release-version\" if not set explicitly).", + "additionalProperties": true + } + }, + "additionalProperties": false + }, + "else": { + "additionalProperties": false, + "properties": { + "useLegacyVersioning": { + "type": "boolean", + "description": "Whether to use the legacy versioning strategy. This value will be true in Nx v20 and false in Nx v21. The legacy versioning implementation will be removed in Nx v22, as will this flag.", + "default": true + }, + "conventionalCommits": { + "type": "boolean", + "description": "Shorthand for enabling the current version of projects to be resolved from git tags, and the next version to be determined by analyzing commit messages according to the Conventional Commits specification.", + "default": false + }, + "git": { + "$ref": "#/definitions/NxReleaseGitConfiguration" + }, + "preVersionCommand": { + "type": "string", + "description": "A command to run after validation of nx release configuration, but before versioning begins. Useful for preparing build artifacts. If --dry-run is passed, the command is still executed, but with the NX_DRY_RUN environment variable set to 'true'." + }, + "specifierSource": { + "type": "string", + "enum": ["prompt", "conventional-commits", "version-plans"], + "default": "prompt", + "description": "The source to use for determining the specifier to use when versioning. 'prompt' is the default and will interactively prompt the user for an explicit/imperative specifier. 'conventional-commits' will attempt determine a specifier from commit messages conforming to the Conventional Commits specification. 'version-plans' will determine the specifier from the version plan files available on disk." + }, + "manifestRootsToUpdate": { + "type": "array", + "items": { + "type": "string" + }, + "description": "A list of directories containing manifest files (such as package.json) to apply updates to when versioning. By default, only the project root will be used, but you could customize this to only version a manifest in a dist directory, or even version multiple manifests in different directories, such as both source and dist." + }, + "currentVersionResolver": { + "type": "string", + "enum": ["registry", "disk", "git-tag", "none"], + "description": "The resolver to use for determining the current version of a project during versioning. This is needed for versioning approaches which involve relatively modifying a current version to arrive at a new version, such as semver bumps like 'patch', 'minor' etc. Using 'none' explicitly declares that the current version is not needed to compute the new version, and should only be used with appropriate version actions implementations that support it." + }, + "currentVersionResolverMetadata": { + "type": "object", + "additionalProperties": true, + "description": "Metadata to provide to the configured currentVersionResolver to help it in determining the current version. What to pass here is specific to each resolver." + }, + "fallbackCurrentVersionResolver": { + "type": "string", + "enum": ["disk"], + "description": "The fallback version resolver to use when the configured currentVersionResolver fails to resolve the current version." + }, + "firstRelease": { + "type": "boolean", + "description": "Whether or not this is the first release of one of more projects. This removes certain validation checks that are not possible to enforce if the project has never been released before." + }, + "versionPrefix": { + "type": "string", + "enum": ["auto", "", "~", "^", "="], + "default": "auto", + "description": "The prefix to use when versioning dependencies. This can be one of the following: auto, '', '~', '^', '=', where auto means the existing prefix will be preserved." + }, + "deleteVersionPlans": { + "type": "boolean", + "description": "Whether to delete the processed version plans file after versioning is complete. This is false by default because the version plans are also needed for changelog generation.", + "default": false + }, + "updateDependents": { + "type": "string", + "enum": ["never", "auto"], + "default": "auto", + "description": "When versioning independent projects, this controls whether to update their dependents (i.e. the things that depend on them). 'never' means no dependents will be updated (unless they happen to be versioned directly as well). 'auto' is the default and will cause dependents to be updated (a patch version bump) when a dependency is versioned." + }, + "logUnchangedProjects": { + "type": "boolean", + "description": "Whether to log projects that have not changed during versioning.", + "default": true + }, + "preserveLocalDependencyProtocols": { + "type": "boolean", + "description": "Whether to preserve local dependency protocols (e.g. file references, or the `workspace:` protocol in package.json files) of local dependencies when updating them during versioning. This was false by default in legacy versioning, but is true by default now.", + "default": true + }, + "versionActions": { + "type": "string", + "description": "The path to the version actions implementation to use for releasing all projects by default. This can also be overridden on the release group and project levels.", + "default": "@nx/js/src/release" + }, + "versionActionsOptions": { + "type": "object", + "description": "The specific options that are defined by each version actions implementation. They will be passed to the version actions implementation when running a release.", + "additionalProperties": true + } + } + } }, "NxReleaseGroupVersionConfiguration": { "type": "object", + "$comment": "We need to improve this configuration definition to be more precise once legacy versioning is removed. Right now it needs to be left open and runtime validation will ensure correct behavior.", "properties": { - "conventionalCommits": { - "type": "boolean", - "description": "Shorthand for enabling the current version of projects to be resolved from git tags, and the next version to be determined by analyzing commit messages according to the Conventional Commits specification.", - "default": false - }, - "generator": { - "type": "string" - }, - "generatorOptions": { - "type": "object", - "additionalProperties": true - }, "groupPreVersionCommand": { "type": "string", "description": "A command to run after validation of nx release configuration AND after the release.version.preVersionCommand (if any), but before versioning begins for this specific group. Useful for preparing build artifacts for the group. If --dry-run is passed, the command is still executed, but with the NX_DRY_RUN environment variable set to 'true'." } }, - "additionalProperties": false + "additionalProperties": true }, "NxReleaseChangelogConfiguration": { "type": "object", diff --git a/packages/nx/src/command-line/release/changelog.ts b/packages/nx/src/command-line/release/changelog.ts index 3aa9965ae6..dadf2e70b4 100644 --- a/packages/nx/src/command-line/release/changelog.ts +++ b/packages/nx/src/command-line/release/changelog.ts @@ -4,11 +4,7 @@ import { readFileSync, rmSync, writeFileSync } from 'node:fs'; import { ReleaseType, valid } from 'semver'; import { dirSync } from 'tmp'; import type { DependencyBump } from '../../../release/changelog-renderer'; -import { - NxReleaseChangelogConfiguration, - NxReleaseConfiguration, - readNxJson, -} from '../../config/nx-json'; +import { NxReleaseConfiguration, readNxJson } from '../../config/nx-json'; import { FileData, ProjectFileMap, @@ -22,9 +18,9 @@ import { } from '../../project-graph/file-map-utils'; import { createProjectGraphAsync } from '../../project-graph/project-graph'; import { interpolate } from '../../tasks-runner/utils'; +import { handleErrors } from '../../utils/handle-errors'; import { isCI } from '../../utils/is-ci'; import { output } from '../../utils/output'; -import { handleErrors } from '../../utils/handle-errors'; import { joinPathFragments } from '../../utils/path'; import { workspaceRoot } from '../../utils/workspace-root'; import { ChangelogOptions } from './command-object'; @@ -39,6 +35,7 @@ import { ReleaseGroupWithName, filterReleaseGroups, } from './config/filter-release-groups'; +import { shouldUseLegacyVersioning } from './config/use-legacy-versioning'; import { GroupVersionPlan, ProjectsVersionPlan, @@ -129,7 +126,13 @@ export function createAPI(overrideReleaseConfig: NxReleaseConfiguration) { userProvidedReleaseConfig ); if (configError) { - return await handleNxReleaseConfigError(configError); + const USE_LEGACY_VERSIONING = shouldUseLegacyVersioning( + userProvidedReleaseConfig + ); + return await handleNxReleaseConfigError( + configError, + USE_LEGACY_VERSIONING + ); } // --print-config exits directly as it is not designed to be combined with any other programmatic operations if (args.printConfig) { @@ -275,7 +278,7 @@ export function createAPI(overrideReleaseConfig: NxReleaseConfiguration) { const postGitTasks: PostGitTask[] = []; let workspaceChangelogChanges: ChangelogChange[] = []; - // TODO: remove this after the changelog renderer is refactored to remove coupling with git commits + // TODO(v22): remove this after the changelog renderer is refactored to remove coupling with git commits let workspaceChangelogCommits: GitCommit[] = []; // If there are multiple release groups, we'll just skip the workspace changelog anyway. @@ -393,7 +396,7 @@ export function createAPI(overrideReleaseConfig: NxReleaseConfiguration) { nxReleaseConfig, workspaceChangelogVersion, changes: workspaceChangelogChanges, - // TODO: remove this after the changelog renderer is refactored to remove coupling with git commits + // TODO(v22): remove this after the changelog renderer is refactored to remove coupling with git commits commits: filterHiddenCommits( workspaceChangelogCommits, nxReleaseConfig.conventionalCommits @@ -449,7 +452,7 @@ export function createAPI(overrideReleaseConfig: NxReleaseConfiguration) { .map((dep) => { return { dependencyName: dep.source, - newVersion: projectsVersionData[dep.source].newVersion, + newVersion: projectsVersionData[dep.source]?.newVersion ?? null, }; }) .filter((b) => b.newVersion !== null); @@ -500,7 +503,7 @@ export function createAPI(overrideReleaseConfig: NxReleaseConfiguration) { if (releaseGroup.projectsRelationship === 'independent') { for (const project of projectNodes) { let changes: ChangelogChange[] | null = null; - // TODO: remove this after the changelog renderer is refactored to remove coupling with git commits + // TODO(v22): remove this after the changelog renderer is refactored to remove coupling with git commits let commits: GitCommit[]; if (releaseGroup.resolvedVersionPlans) { @@ -646,7 +649,7 @@ export function createAPI(overrideReleaseConfig: NxReleaseConfiguration) { } } else { let changes: ChangelogChange[] = []; - // TODO: remove this after the changelog renderer is refactored to remove coupling with git commits + // TODO(v22): remove this after the changelog renderer is refactored to remove coupling with git commits let commits: GitCommit[] = []; if (releaseGroup.resolvedVersionPlans) { changes = (releaseGroup.resolvedVersionPlans as GroupVersionPlan[]) @@ -994,11 +997,15 @@ async function applyChangesAndExit( } if (args.gitPush ?? nxReleaseConfig.changelog.git.push) { - output.logSingleLine(`Pushing to git remote "${args.gitRemote}"`); + output.logSingleLine( + `Pushing to git remote "${args.gitRemote ?? 'origin'}"` + ); await gitPush({ gitRemote: args.gitRemote, dryRun: args.dryRun, verbose: args.verbose, + additionalArgs: + args.gitPushArgs || nxReleaseConfig.changelog.git.pushArgs, }); } @@ -1368,7 +1375,7 @@ function filterHiddenChanges( }); } -// TODO: remove this after the changelog renderer is refactored to remove coupling with git commits +// TODO(v22): remove this after the changelog renderer is refactored to remove coupling with git commits function filterHiddenCommits( commits: GitCommit[], conventionalCommitsConfig: NxReleaseConfig['conventionalCommits'] @@ -1414,7 +1421,9 @@ async function promptForGitHubRelease(): Promise { }, ]); return result.confirmation; - } catch (e) { + } catch { + // Ensure the cursor is always restored + process.stdout.write('\u001b[?25h'); // Handle the case where the user exits the prompt with ctrl+c return false; } diff --git a/packages/nx/src/command-line/release/command-object.ts b/packages/nx/src/command-line/release/command-object.ts index c8eb6f4dad..a82a51987e 100644 --- a/packages/nx/src/command-line/release/command-object.ts +++ b/packages/nx/src/command-line/release/command-object.ts @@ -34,6 +34,7 @@ interface GitOptions { gitTagMessage?: string; gitTagArgs?: string | string[]; gitPush?: boolean; + gitPushArgs?: string | string[]; gitRemote?: string; } @@ -459,6 +460,11 @@ function withGitOptions(yargs: Argv): Argv { 'Whether or not to automatically push the changes made by this command to the remote git repository.', type: 'boolean', }) + .option('git-push-args', { + describe: + 'Additional arguments to pass to the `git push` command invoked behind the scenes.', + type: 'string', + }) .option('git-remote', { type: 'string', description: diff --git a/packages/nx/src/command-line/release/config/config.spec.ts b/packages/nx/src/command-line/release/config/config.spec.ts index 8defa66d7a..e0078c47e6 100644 --- a/packages/nx/src/command-line/release/config/config.spec.ts +++ b/packages/nx/src/command-line/release/config/config.spec.ts @@ -1,4 +1,5 @@ import { join } from 'path'; +import { NxReleaseVersionV2Configuration } from '../../../config/nx-json'; import { ProjectFileMap, ProjectGraph } from '../../../config/project-graph'; import { TempFs } from '../../../internal-testing-utils/temp-fs'; import { createNxReleaseConfig } from './config'; @@ -138,6 +139,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": true, "tagArgs": "", @@ -264,6 +266,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": false, "tagArgs": "", @@ -285,6 +288,7 @@ describe('createNxReleaseConfig()', () => { "generator": "@nx/js:release-version", "generatorOptions": {}, "groupPreVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -301,12 +305,14 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": true, "tag": false, "tagArgs": "", "tagMessage": "", }, "preVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -326,6 +332,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": true, "tagArgs": "", @@ -452,6 +459,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": false, "tagArgs": "", @@ -473,6 +481,7 @@ describe('createNxReleaseConfig()', () => { "generator": "@nx/js:release-version", "generatorOptions": {}, "groupPreVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -489,12 +498,14 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": true, "tag": false, "tagArgs": "", "tagMessage": "", }, "preVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -517,6 +528,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": true, "tagArgs": "", @@ -643,6 +655,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": false, "tagArgs": "", @@ -664,6 +677,7 @@ describe('createNxReleaseConfig()', () => { "generator": "@nx/js:release-version", "generatorOptions": {}, "groupPreVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -680,12 +694,14 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": true, "tag": false, "tagArgs": "", "tagMessage": "", }, "preVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -739,6 +755,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": true, "tagArgs": "", @@ -865,6 +882,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": false, "tagArgs": "", @@ -886,6 +904,7 @@ describe('createNxReleaseConfig()', () => { "generator": "@nx/js:release-version", "generatorOptions": {}, "groupPreVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -902,12 +921,14 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": true, "tag": false, "tagArgs": "", "tagMessage": "", }, "preVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -945,6 +966,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": true, "tagArgs": "", @@ -1071,6 +1093,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": false, "tagArgs": "", @@ -1092,6 +1115,7 @@ describe('createNxReleaseConfig()', () => { "generator": "@nx/js:release-version", "generatorOptions": {}, "groupPreVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -1108,12 +1132,14 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": true, "tag": false, "tagArgs": "", "tagMessage": "", }, "preVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -1160,6 +1186,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": true, "tagArgs": "", @@ -1286,6 +1313,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": false, "tagArgs": "", @@ -1306,6 +1334,7 @@ describe('createNxReleaseConfig()', () => { "generator": "@nx/js:release-version", "generatorOptions": {}, "groupPreVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -1322,12 +1351,14 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": true, "tag": false, "tagArgs": "", "tagMessage": "", }, "preVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -1373,6 +1404,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": true, "tagArgs": "", @@ -1499,6 +1531,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": false, "tagArgs": "", @@ -1521,6 +1554,7 @@ describe('createNxReleaseConfig()', () => { "generator": "@nx/js:release-version", "generatorOptions": {}, "groupPreVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -1537,12 +1571,14 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": true, "tag": false, "tagArgs": "", "tagMessage": "", }, "preVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -1571,6 +1607,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": true, "tagArgs": "", @@ -1697,6 +1734,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": false, "tagArgs": "", @@ -1716,6 +1754,7 @@ describe('createNxReleaseConfig()', () => { "generator": "@nx/js:release-version", "generatorOptions": {}, "groupPreVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -1732,12 +1771,14 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": true, "tag": false, "tagArgs": "", "tagMessage": "", }, "preVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -1764,6 +1805,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": true, "tagArgs": "", @@ -1890,6 +1932,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": false, "tagArgs": "", @@ -1910,6 +1953,7 @@ describe('createNxReleaseConfig()', () => { "generator": "@nx/js:release-version", "generatorOptions": {}, "groupPreVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -1926,12 +1970,14 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": true, "tag": false, "tagArgs": "", "tagMessage": "", }, "preVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -1970,6 +2016,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": true, "tagArgs": "", @@ -2085,6 +2132,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": false, "tagArgs": "", @@ -2106,6 +2154,7 @@ describe('createNxReleaseConfig()', () => { "optionsOverride": "something", }, "groupPreVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -2122,6 +2171,7 @@ describe('createNxReleaseConfig()', () => { "generator": "@custom/generator-alternative", "generatorOptions": {}, "groupPreVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -2138,12 +2188,14 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": true, "tag": false, "tagArgs": "", "tagMessage": "", }, "preVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -2171,6 +2223,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": true, "tagArgs": "", @@ -2297,6 +2350,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": false, "tagArgs": "", @@ -2327,6 +2381,7 @@ describe('createNxReleaseConfig()', () => { "generator": "@nx/js:release-version", "generatorOptions": {}, "groupPreVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -2343,12 +2398,14 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": true, "tag": false, "tagArgs": "", "tagMessage": "", }, "preVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -2381,6 +2438,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": true, "tagArgs": "", @@ -2496,6 +2554,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": false, "tagArgs": "", @@ -2526,6 +2585,7 @@ describe('createNxReleaseConfig()', () => { "generator": "@nx/js:release-version", "generatorOptions": {}, "groupPreVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -2553,6 +2613,7 @@ describe('createNxReleaseConfig()', () => { "generator": "@nx/js:release-version", "generatorOptions": {}, "groupPreVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -2569,12 +2630,14 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": true, "tag": false, "tagArgs": "", "tagMessage": "", }, "preVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -2604,6 +2667,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": true, "tagArgs": "", @@ -2719,6 +2783,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": false, "tagArgs": "", @@ -2750,6 +2815,7 @@ describe('createNxReleaseConfig()', () => { "generator": "@nx/js:release-version", "generatorOptions": {}, "groupPreVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -2766,12 +2832,14 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": true, "tag": false, "tagArgs": "", "tagMessage": "", }, "preVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -2801,6 +2869,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": true, "tagArgs": "", @@ -2927,6 +2996,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": false, "tagArgs": "", @@ -2950,6 +3020,7 @@ describe('createNxReleaseConfig()', () => { "foo": "bar", }, "groupPreVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -2968,12 +3039,14 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": true, "tag": false, "tagArgs": "", "tagMessage": "", }, "preVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -2999,6 +3072,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "--no-verify", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": true, "tagArgs": "", @@ -3125,6 +3199,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "--no-verify", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": false, "tagArgs": "", @@ -3146,6 +3221,7 @@ describe('createNxReleaseConfig()', () => { "generator": "@nx/js:release-version", "generatorOptions": {}, "groupPreVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -3162,12 +3238,14 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "--no-verify", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": true, "tag": false, "tagArgs": "", "tagMessage": "", }, "preVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -3196,6 +3274,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": true, "tagArgs": "", @@ -3322,6 +3401,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": false, "tagArgs": "", @@ -3343,6 +3423,7 @@ describe('createNxReleaseConfig()', () => { "generator": "@nx/js:release-version", "generatorOptions": {}, "groupPreVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -3359,12 +3440,14 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "--no-verify", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": true, "tag": true, "tagArgs": "", "tagMessage": "", }, "preVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -3389,6 +3472,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": true, "tagArgs": "", @@ -3515,6 +3599,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": false, "tagArgs": "", @@ -3536,6 +3621,7 @@ describe('createNxReleaseConfig()', () => { "generator": "@nx/js:release-version", "generatorOptions": {}, "groupPreVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -3552,12 +3638,14 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": true, "tag": false, "tagArgs": "", "tagMessage": "", }, "preVersionCommand": "nx run-many -t build", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -3587,6 +3675,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": true, "tagArgs": "", @@ -3713,6 +3802,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": false, "tagArgs": "", @@ -3732,6 +3822,7 @@ describe('createNxReleaseConfig()', () => { "generator": "@nx/js:release-version", "generatorOptions": {}, "groupPreVersionCommand": "nx run-many -t build -p lib-a", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -3748,12 +3839,14 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": true, "tag": false, "tagArgs": "", "tagMessage": "", }, "preVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -3798,6 +3891,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": true, "tagArgs": "", @@ -3924,6 +4018,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": false, "tagArgs": "", @@ -3943,6 +4038,7 @@ describe('createNxReleaseConfig()', () => { "generator": "@nx/js:release-version", "generatorOptions": {}, "groupPreVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -3959,12 +4055,14 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": true, "tag": false, "tagArgs": "", "tagMessage": "", }, "preVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -3989,6 +4087,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": true, "tagArgs": "", @@ -4115,6 +4214,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": false, "tagArgs": "", @@ -4136,6 +4236,7 @@ describe('createNxReleaseConfig()', () => { "generator": "@nx/js:release-version", "generatorOptions": {}, "groupPreVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -4152,12 +4253,14 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": true, "tag": false, "tagArgs": "", "tagMessage": "", }, "preVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -4192,6 +4295,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": true, "tagArgs": "", @@ -4318,6 +4422,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": false, "tagArgs": "", @@ -4351,6 +4456,7 @@ describe('createNxReleaseConfig()', () => { "specifierSource": "conventional-commits", }, "groupPreVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -4367,12 +4473,14 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": true, "tag": false, "tagArgs": "", "tagMessage": "", }, "preVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -4400,6 +4508,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": true, "tagArgs": "", @@ -4515,6 +4624,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": false, "tagArgs": "", @@ -4536,6 +4646,7 @@ describe('createNxReleaseConfig()', () => { "generator": "@nx/js:release-version", "generatorOptions": {}, "groupPreVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -4552,12 +4663,14 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": true, "tag": false, "tagArgs": "", "tagMessage": "", }, "preVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -4593,6 +4706,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": true, "tagArgs": "", @@ -4730,6 +4844,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": false, "tagArgs": "", @@ -4762,6 +4877,7 @@ describe('createNxReleaseConfig()', () => { "generator": "@nx/js:release-version", "generatorOptions": {}, "groupPreVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -4778,12 +4894,14 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": true, "tag": false, "tagArgs": "", "tagMessage": "", }, "preVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -4809,6 +4927,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": true, "tagArgs": "", @@ -4946,6 +5065,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": false, "tagArgs": "", @@ -4978,6 +5098,7 @@ describe('createNxReleaseConfig()', () => { "generator": "@nx/js:release-version", "generatorOptions": {}, "groupPreVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -4994,12 +5115,14 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": true, "tag": false, "tagArgs": "", "tagMessage": "", }, "preVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -5025,6 +5148,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": false, "tagArgs": "", @@ -5151,6 +5275,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": false, "tagArgs": "", @@ -5172,6 +5297,7 @@ describe('createNxReleaseConfig()', () => { "generator": "@nx/js:release-version", "generatorOptions": {}, "groupPreVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -5188,12 +5314,14 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": true, "tag": false, "tagArgs": "", "tagMessage": "", }, "preVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -5223,6 +5351,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": true, + "pushArgs": "", "stageChanges": false, "tag": true, "tagArgs": "", @@ -5353,6 +5482,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": false, "tagArgs": "", @@ -5374,6 +5504,7 @@ describe('createNxReleaseConfig()', () => { "generator": "@nx/js:release-version", "generatorOptions": {}, "groupPreVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -5390,12 +5521,14 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": true, "tag": false, "tagArgs": "", "tagMessage": "", }, "preVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -5426,6 +5559,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": true, + "pushArgs": "", "stageChanges": false, "tag": true, "tagArgs": "", @@ -5556,6 +5690,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": false, "tagArgs": "", @@ -5577,6 +5712,7 @@ describe('createNxReleaseConfig()', () => { "generator": "@nx/js:release-version", "generatorOptions": {}, "groupPreVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -5593,12 +5729,14 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": true, "tag": false, "tagArgs": "", "tagMessage": "", }, "preVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -5691,6 +5829,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": true, + "pushArgs": "", "stageChanges": false, "tag": true, "tagArgs": "", @@ -5821,6 +5960,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": false, "tagArgs": "", @@ -5857,6 +5997,7 @@ describe('createNxReleaseConfig()', () => { "generator": "@nx/js:release-version", "generatorOptions": {}, "groupPreVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -5873,12 +6014,14 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": true, "tag": false, "tagArgs": "", "tagMessage": "", }, "preVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -5910,6 +6053,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": true, + "pushArgs": "", "stageChanges": false, "tag": true, "tagArgs": "", @@ -6040,6 +6184,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": false, "tagArgs": "", @@ -6076,6 +6221,7 @@ describe('createNxReleaseConfig()', () => { "generator": "@nx/js:release-version", "generatorOptions": {}, "groupPreVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -6092,12 +6238,14 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": true, "tag": false, "tagArgs": "", "tagMessage": "", }, "preVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -6313,6 +6461,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": true, "tagArgs": "", @@ -6439,6 +6588,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": false, "tagArgs": "", @@ -6460,6 +6610,7 @@ describe('createNxReleaseConfig()', () => { "generator": "@nx/js:release-version", "generatorOptions": {}, "groupPreVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -6476,12 +6627,14 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": true, "tag": false, "tagArgs": "", "tagMessage": "", }, "preVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -6505,6 +6658,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": true, "tagArgs": "", @@ -6631,6 +6785,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": false, "tagArgs": "", @@ -6652,6 +6807,7 @@ describe('createNxReleaseConfig()', () => { "generator": "@nx/js:release-version", "generatorOptions": {}, "groupPreVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -6668,12 +6824,14 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": true, "tag": false, "tagArgs": "", "tagMessage": "", }, "preVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -6719,6 +6877,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": true, "tagArgs": "", @@ -6859,6 +7018,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": false, "tagArgs": "", @@ -6880,6 +7040,7 @@ describe('createNxReleaseConfig()', () => { "generator": "@nx/js:release-version", "generatorOptions": {}, "groupPreVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -6896,12 +7057,14 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": true, "tag": false, "tagArgs": "", "tagMessage": "", }, "preVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -6930,6 +7093,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": true, "tagArgs": "", @@ -7063,6 +7227,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": false, "tagArgs": "", @@ -7084,6 +7249,7 @@ describe('createNxReleaseConfig()', () => { "generator": "@nx/js:release-version", "generatorOptions": {}, "groupPreVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -7100,12 +7266,14 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": true, "tag": false, "tagArgs": "", "tagMessage": "", }, "preVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -7137,6 +7305,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": true, "tagArgs": "", @@ -7270,6 +7439,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": false, "tagArgs": "", @@ -7291,6 +7461,7 @@ describe('createNxReleaseConfig()', () => { "generator": "@nx/js:release-version", "generatorOptions": {}, "groupPreVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -7307,12 +7478,14 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": true, "tag": false, "tagArgs": "", "tagMessage": "", }, "preVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -7345,6 +7518,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": true, "tagArgs": "", @@ -7478,6 +7652,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": false, "tagArgs": "", @@ -7499,6 +7674,7 @@ describe('createNxReleaseConfig()', () => { "generator": "@nx/js:release-version", "generatorOptions": {}, "groupPreVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -7515,12 +7691,14 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": true, "tag": false, "tagArgs": "", "tagMessage": "", }, "preVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -7556,6 +7734,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": true, "tagArgs": "", @@ -7689,6 +7868,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": false, "tagArgs": "", @@ -7710,6 +7890,7 @@ describe('createNxReleaseConfig()', () => { "generator": "@nx/js:release-version", "generatorOptions": {}, "groupPreVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -7726,12 +7907,14 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": true, "tag": false, "tagArgs": "", "tagMessage": "", }, "preVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -7786,6 +7969,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": true, + "pushArgs": "", "stageChanges": false, "tag": true, "tagArgs": "", @@ -7912,6 +8096,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": false, "tagArgs": "", @@ -7946,6 +8131,7 @@ describe('createNxReleaseConfig()', () => { "generator": "@nx/js:release-version", "generatorOptions": {}, "groupPreVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -7962,6 +8148,7 @@ describe('createNxReleaseConfig()', () => { "generator": "@nx/js:release-version", "generatorOptions": {}, "groupPreVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -7989,6 +8176,7 @@ describe('createNxReleaseConfig()', () => { "generator": "@nx/js:release-version", "generatorOptions": {}, "groupPreVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -8005,12 +8193,14 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": true, "tag": false, "tagArgs": "", "tagMessage": "", }, "preVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -8048,6 +8238,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": true, + "pushArgs": "", "stageChanges": false, "tag": true, "tagArgs": "", @@ -8193,6 +8384,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": false, "tagArgs": "", @@ -8227,6 +8419,7 @@ describe('createNxReleaseConfig()', () => { "generator": "@nx/js:release-version", "generatorOptions": {}, "groupPreVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -8243,12 +8436,14 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": true, "tag": false, "tagArgs": "", "tagMessage": "", }, "preVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -8302,6 +8497,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": true, + "pushArgs": "", "stageChanges": false, "tag": true, "tagArgs": "", @@ -8428,6 +8624,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": false, "tagArgs": "", @@ -8462,6 +8659,7 @@ describe('createNxReleaseConfig()', () => { "generator": "@nx/js:release-version", "generatorOptions": {}, "groupPreVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -8478,12 +8676,14 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": true, "tag": false, "tagArgs": "", "tagMessage": "", }, "preVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -8518,6 +8718,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": true, + "pushArgs": "", "stageChanges": false, "tag": true, "tagArgs": "", @@ -8644,6 +8845,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": false, "tagArgs": "", @@ -8678,6 +8880,7 @@ describe('createNxReleaseConfig()', () => { "generator": "@nx/js:release-version", "generatorOptions": {}, "groupPreVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -8694,12 +8897,14 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": true, "tag": false, "tagArgs": "", "tagMessage": "", }, "preVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -8967,6 +9172,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": true, "tagArgs": "", @@ -9093,6 +9299,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": false, "tagArgs": "", @@ -9114,6 +9321,7 @@ describe('createNxReleaseConfig()', () => { "generator": "@nx/js:release-version", "generatorOptions": {}, "groupPreVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -9130,12 +9338,14 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": true, "tag": false, "tagArgs": "", "tagMessage": "", }, "preVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -9353,6 +9563,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": true, "tagArgs": "", @@ -9468,6 +9679,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": false, "tagArgs": "", @@ -9487,6 +9699,7 @@ describe('createNxReleaseConfig()', () => { "generator": "@nx/js:release-version", "generatorOptions": {}, "groupPreVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -9504,6 +9717,7 @@ describe('createNxReleaseConfig()', () => { "generator": "@nx/js:release-version", "generatorOptions": {}, "groupPreVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -9520,12 +9734,14 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": true, "tag": false, "tagArgs": "", "tagMessage": "", }, "preVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -9550,6 +9766,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": true, "tagArgs": "", @@ -9665,6 +9882,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": false, "tagArgs": "", @@ -9685,6 +9903,7 @@ describe('createNxReleaseConfig()', () => { "generator": "@nx/js:release-version", "generatorOptions": {}, "groupPreVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -9701,12 +9920,14 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": true, "tag": false, "tagArgs": "", "tagMessage": "", }, "preVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -9736,6 +9957,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": true, "tagArgs": "", @@ -9862,6 +10084,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": false, "tagArgs": "", @@ -9886,6 +10109,7 @@ describe('createNxReleaseConfig()', () => { "specifierSource": "conventional-commits", }, "groupPreVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -9905,12 +10129,14 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": true, "tag": false, "tagArgs": "", "tagMessage": "", }, "preVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -9936,6 +10162,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": true, "tagArgs": "", @@ -10062,6 +10289,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": false, "tagArgs": "", @@ -10086,6 +10314,7 @@ describe('createNxReleaseConfig()', () => { "specifierSource": "prompt", }, "groupPreVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -10105,12 +10334,14 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": true, "tag": false, "tagArgs": "", "tagMessage": "", }, "preVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -10135,6 +10366,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": true, "tagArgs": "", @@ -10261,6 +10493,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": false, "tagArgs": "", @@ -10285,6 +10518,7 @@ describe('createNxReleaseConfig()', () => { "specifierSource": "conventional-commits", }, "groupPreVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -10304,12 +10538,14 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": true, "tag": false, "tagArgs": "", "tagMessage": "", }, "preVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -10321,6 +10557,7 @@ describe('createNxReleaseConfig()', () => { const res = await createNxReleaseConfig(projectGraph, projectFileMap, { version: { conventionalCommits: true, + fallbackCurrentVersionResolver: 'disk', }, groups: { 'group-1': { @@ -10342,6 +10579,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": true, "tagArgs": "", @@ -10468,6 +10706,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": false, "tagArgs": "", @@ -10484,9 +10723,11 @@ describe('createNxReleaseConfig()', () => { "releaseTagPatternCheckAllBranchesWhen": undefined, "version": { "conventionalCommits": false, + "fallbackCurrentVersionResolver": "disk", "generator": "@nx/js:release-version", "generatorOptions": {}, "groupPreVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -10496,6 +10737,7 @@ describe('createNxReleaseConfig()', () => { "releaseTagPatternCheckAllBranchesWhen": undefined, "version": { "conventionalCommits": true, + "fallbackCurrentVersionResolver": "disk", "generator": "@nx/js:release-version", "generatorOptions": { "currentVersionResolver": "git-tag", @@ -10506,12 +10748,14 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": true, "tag": false, "tagArgs": "", "tagMessage": "", }, "preVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -10539,6 +10783,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": true, "tagArgs": "", @@ -10665,6 +10910,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": false, "tagArgs": "", @@ -10690,6 +10936,7 @@ describe('createNxReleaseConfig()', () => { "specifierSource": "conventional-commits", }, "groupPreVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -10710,12 +10957,14 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": true, "tag": false, "tagArgs": "", "tagMessage": "", }, "preVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -10735,7 +10984,7 @@ describe('createNxReleaseConfig()', () => { expect(res1).toMatchInlineSnapshot(` { "error": { - "code": "CONVENTIONAL_COMMITS_SHORTHAND_MIXED_WITH_OVERLAPPING_GENERATOR_OPTIONS", + "code": "CONVENTIONAL_COMMITS_SHORTHAND_MIXED_WITH_OVERLAPPING_OPTIONS", "data": {}, }, "nxReleaseConfig": null, @@ -10753,7 +11002,7 @@ describe('createNxReleaseConfig()', () => { expect(res2).toMatchInlineSnapshot(` { "error": { - "code": "CONVENTIONAL_COMMITS_SHORTHAND_MIXED_WITH_OVERLAPPING_GENERATOR_OPTIONS", + "code": "CONVENTIONAL_COMMITS_SHORTHAND_MIXED_WITH_OVERLAPPING_OPTIONS", "data": {}, }, "nxReleaseConfig": null, @@ -10783,6 +11032,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": true, "tagArgs": "", @@ -10909,6 +11159,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": false, "tagArgs": "", @@ -10932,6 +11183,7 @@ describe('createNxReleaseConfig()', () => { "specifierSource": "version-plans", }, "groupPreVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": true, }, @@ -10950,12 +11202,14 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": true, "tag": false, "tagArgs": "", "tagMessage": "", }, "preVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": true, }, @@ -10983,6 +11237,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": true, "tagArgs": "", @@ -11109,6 +11364,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": false, "tagArgs": "", @@ -11132,6 +11388,7 @@ describe('createNxReleaseConfig()', () => { "specifierSource": "version-plans", }, "groupPreVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": { "ignorePatternsForPlanCheck": [ @@ -11154,12 +11411,14 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": true, "tag": false, "tagArgs": "", "tagMessage": "", }, "preVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": { "ignorePatternsForPlanCheck": [ @@ -11200,6 +11459,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": true, "tagArgs": "", @@ -11315,6 +11575,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": false, "tagArgs": "", @@ -11336,6 +11597,7 @@ describe('createNxReleaseConfig()', () => { "specifierSource": "version-plans", }, "groupPreVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": true, }, @@ -11352,6 +11614,7 @@ describe('createNxReleaseConfig()', () => { "generator": "@nx/js:release-version", "generatorOptions": {}, "groupPreVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -11368,12 +11631,14 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": true, "tag": false, "tagArgs": "", "tagMessage": "", }, "preVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -11410,6 +11675,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": true, "tagArgs": "", @@ -11525,6 +11791,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": false, "tagArgs": "", @@ -11546,6 +11813,7 @@ describe('createNxReleaseConfig()', () => { "specifierSource": "version-plans", }, "groupPreVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": { "ignorePatternsForPlanCheck": [ @@ -11566,6 +11834,7 @@ describe('createNxReleaseConfig()', () => { "generator": "@nx/js:release-version", "generatorOptions": {}, "groupPreVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -11582,12 +11851,14 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": true, "tag": false, "tagArgs": "", "tagMessage": "", }, "preVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -11624,6 +11895,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": true, "tagArgs": "", @@ -11739,6 +12011,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": false, "tagArgs": "", @@ -11758,6 +12031,7 @@ describe('createNxReleaseConfig()', () => { "generator": "@nx/js:release-version", "generatorOptions": {}, "groupPreVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -11776,6 +12050,7 @@ describe('createNxReleaseConfig()', () => { "specifierSource": "version-plans", }, "groupPreVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": true, }, @@ -11794,12 +12069,14 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": true, "tag": false, "tagArgs": "", "tagMessage": "", }, "preVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": true, }, @@ -11838,6 +12115,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": true, "tagArgs": "", @@ -11953,6 +12231,7 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": false, "tag": false, "tagArgs": "", @@ -11972,6 +12251,7 @@ describe('createNxReleaseConfig()', () => { "generator": "@nx/js:release-version", "generatorOptions": {}, "groupPreVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": false, }, @@ -11990,6 +12270,7 @@ describe('createNxReleaseConfig()', () => { "specifierSource": "version-plans", }, "groupPreVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": { "ignorePatternsForPlanCheck": [ @@ -12012,12 +12293,14 @@ describe('createNxReleaseConfig()', () => { "commitArgs": "", "commitMessage": "chore(release): publish {version}", "push": false, + "pushArgs": "", "stageChanges": true, "tag": false, "tagArgs": "", "tagMessage": "", }, "preVersionCommand": "", + "useLegacyVersioning": true, }, "versionPlans": { "ignorePatternsForPlanCheck": [ @@ -12029,4 +12312,252 @@ describe('createNxReleaseConfig()', () => { `); }); }); + + describe('useLegacyVersioning', () => { + it('should be true by default', async () => { + const res = await createNxReleaseConfig(projectGraph, projectFileMap); + expect(res.nxReleaseConfig.version.useLegacyVersioning).toBe(true); + }); + + it('should be respected if set to false by the user', async () => { + const res = await createNxReleaseConfig(projectGraph, projectFileMap, { + version: { useLegacyVersioning: false }, + }); + expect(res.nxReleaseConfig.version.useLegacyVersioning).toBe(false); + }); + + it('should set the versionActions and versionActionsOptions, and not set generator or generatorOptions, if set to false by the user', async () => { + const res = await createNxReleaseConfig(projectGraph, projectFileMap, { + version: { useLegacyVersioning: false }, + }); + const version = res.nxReleaseConfig + .version as NxReleaseVersionV2Configuration; + expect(version.versionActions).toBe('@nx/js/src/release/version-actions'); + expect(version.versionActionsOptions).toEqual({}); + + expect((version as any).generator).toBeUndefined(); + expect((version as any).generatorOptions).toBeUndefined(); + }); + + it('should respect custom versionActions and versionActionsOptions if set by the user', async () => { + const res = await createNxReleaseConfig(projectGraph, projectFileMap, { + version: { + useLegacyVersioning: false, + versionActions: '/path/to/custom-version-actions', + versionActionsOptions: { + foo: true, + baz: ['bar'], + }, + }, + }); + expect(res).toMatchInlineSnapshot(` + { + "error": null, + "nxReleaseConfig": { + "changelog": { + "automaticFromRef": false, + "git": { + "commit": true, + "commitArgs": "", + "commitMessage": "chore(release): publish {version}", + "push": false, + "pushArgs": "", + "stageChanges": false, + "tag": true, + "tagArgs": "", + "tagMessage": "", + }, + "projectChangelogs": false, + "workspaceChangelog": { + "createRelease": false, + "entryWhenNoChanges": "This was a version bump only, there were no code changes.", + "file": "{workspaceRoot}/CHANGELOG.md", + "renderOptions": { + "authors": true, + "commitReferences": true, + "mapAuthorsToGitHubUsernames": true, + "versionTitleDate": true, + }, + "renderer": "/release/changelog-renderer", + }, + }, + "conventionalCommits": { + "types": { + "__INVALID__": { + "changelog": { + "hidden": true, + "title": "Invalid based on conventional commits specification", + }, + "semverBump": "none", + }, + "build": { + "changelog": { + "hidden": true, + "title": "📦 Build", + }, + "semverBump": "none", + }, + "chore": { + "changelog": { + "hidden": true, + "title": "🏡 Chore", + }, + "semverBump": "none", + }, + "ci": { + "changelog": { + "hidden": true, + "title": "🤖 CI", + }, + "semverBump": "none", + }, + "docs": { + "changelog": { + "hidden": true, + "title": "📖 Documentation", + }, + "semverBump": "none", + }, + "examples": { + "changelog": { + "hidden": true, + "title": "🏀 Examples", + }, + "semverBump": "none", + }, + "feat": { + "changelog": { + "hidden": false, + "title": "🚀 Features", + }, + "semverBump": "minor", + }, + "fix": { + "changelog": { + "hidden": false, + "title": "🩹 Fixes", + }, + "semverBump": "patch", + }, + "perf": { + "changelog": { + "hidden": false, + "title": "🔥 Performance", + }, + "semverBump": "none", + }, + "refactor": { + "changelog": { + "hidden": true, + "title": "💅 Refactors", + }, + "semverBump": "none", + }, + "revert": { + "changelog": { + "hidden": true, + "title": "⏪ Revert", + }, + "semverBump": "none", + }, + "style": { + "changelog": { + "hidden": true, + "title": "🎨 Styles", + }, + "semverBump": "none", + }, + "test": { + "changelog": { + "hidden": true, + "title": "✅ Tests", + }, + "semverBump": "none", + }, + "types": { + "changelog": { + "hidden": true, + "title": "🌊 Types", + }, + "semverBump": "none", + }, + }, + }, + "git": { + "commit": false, + "commitArgs": "", + "commitMessage": "chore(release): publish {version}", + "push": false, + "pushArgs": "", + "stageChanges": false, + "tag": false, + "tagArgs": "", + "tagMessage": "", + }, + "groups": { + "__default__": { + "changelog": false, + "projects": [ + "lib-a", + "lib-b", + "nx", + ], + "projectsRelationship": "fixed", + "releaseTagPattern": "v{version}", + "releaseTagPatternCheckAllBranchesWhen": undefined, + "version": { + "conventionalCommits": false, + "groupPreVersionCommand": "", + "logUnchangedProjects": true, + "preserveLocalDependencyProtocols": true, + "updateDependents": "auto", + "useLegacyVersioning": false, + "versionActions": "/path/to/custom-version-actions", + "versionActionsOptions": { + "baz": [ + "bar", + ], + "foo": true, + }, + }, + "versionPlans": false, + }, + }, + "projectsRelationship": "fixed", + "releaseTagPattern": "v{version}", + "releaseTagPatternCheckAllBranchesWhen": undefined, + "version": { + "conventionalCommits": false, + "currentVersionResolver": undefined, + "git": { + "commit": false, + "commitArgs": "", + "commitMessage": "chore(release): publish {version}", + "push": false, + "pushArgs": "", + "stageChanges": true, + "tag": false, + "tagArgs": "", + "tagMessage": "", + }, + "logUnchangedProjects": true, + "preVersionCommand": "", + "preserveLocalDependencyProtocols": true, + "specifierSource": undefined, + "updateDependents": "auto", + "useLegacyVersioning": false, + "versionActions": "/path/to/custom-version-actions", + "versionActionsOptions": { + "baz": [ + "bar", + ], + "foo": true, + }, + }, + "versionPlans": false, + }, + } + `); + }); + }); }); diff --git a/packages/nx/src/command-line/release/config/config.ts b/packages/nx/src/command-line/release/config/config.ts index 3b8f87341b..4d7b12cbe9 100644 --- a/packages/nx/src/command-line/release/config/config.ts +++ b/packages/nx/src/command-line/release/config/config.ts @@ -14,19 +14,24 @@ import { join, relative } from 'node:path'; import { URL } from 'node:url'; import { + LegacyNxReleaseVersionConfiguration, NxJsonConfiguration, NxReleaseChangelogConfiguration, + NxReleaseConfiguration, + NxReleaseGitConfiguration, + NxReleaseVersionV2Configuration, } from '../../../config/nx-json'; import { ProjectFileMap, ProjectGraph } from '../../../config/project-graph'; import { readJsonFile } from '../../../utils/fileutils'; import { findMatchingProjects } from '../../../utils/find-matching-projects'; import { output } from '../../../utils/output'; import { PackageJson } from '../../../utils/package-json'; -import { workspaceRoot } from '../../../utils/workspace-root'; import { normalizePath } from '../../../utils/path'; +import { workspaceRoot } from '../../../utils/workspace-root'; import { resolveChangelogRenderer } from '../utils/resolve-changelog-renderer'; import { resolveNxJsonConfigErrorMessage } from '../utils/resolve-nx-json-error-message'; import { DEFAULT_CONVENTIONAL_COMMITS_CONFIG } from './conventional-commits'; +import { shouldUseLegacyVersioning } from './use-legacy-versioning'; type DeepRequired = Required<{ [K in keyof T]: T[K] extends Required ? T[K] : DeepRequired; @@ -55,6 +60,9 @@ type RemoveBooleanFromPropertiesOnEach = { export const IMPLICIT_DEFAULT_RELEASE_GROUP = '__default__'; +export const DEFAULT_VERSION_ACTIONS_PATH = + '@nx/js/src/release/version-actions'; + /** * Our source of truth is a deeply required variant of the user-facing config interface, so that command * implementations can be sure that properties will exist and do not need to repeat the same checks over @@ -66,16 +74,16 @@ export const IMPLICIT_DEFAULT_RELEASE_GROUP = '__default__'; */ export type NxReleaseConfig = Omit< DeepRequired< - NxJsonConfiguration['release'] & { + NxReleaseConfiguration & { groups: DeepRequired< RemoveTrueFromPropertiesOnEach< - EnsureProjectsArray, + EnsureProjectsArray, 'changelog' > >; // Remove the true shorthand from the changelog config types, it will be normalized to a default object changelog: RemoveTrueFromProperties< - DeepRequired, + DeepRequired, 'workspaceChangelog' | 'projectChangelogs' >; // Remove the false shorthand from the conventionalCommits config types, it will be normalized to a semver bump of "none" and to be hidden on the changelog @@ -84,7 +92,7 @@ export type NxReleaseConfig = Omit< DeepRequired< RemoveBooleanFromProperties< DeepRequired< - NxJsonConfiguration['release']['conventionalCommits']['types'] + NxReleaseConfiguration['conventionalCommits']['types'] >, string > @@ -105,7 +113,7 @@ export interface CreateNxReleaseConfigError { | 'RELEASE_GROUP_MATCHES_NO_PROJECTS' | 'RELEASE_GROUP_RELEASE_TAG_PATTERN_VERSION_PLACEHOLDER_MISSING_OR_EXCESSIVE' | 'PROJECT_MATCHES_MULTIPLE_GROUPS' - | 'CONVENTIONAL_COMMITS_SHORTHAND_MIXED_WITH_OVERLAPPING_GENERATOR_OPTIONS' + | 'CONVENTIONAL_COMMITS_SHORTHAND_MIXED_WITH_OVERLAPPING_OPTIONS' | 'GLOBAL_GIT_CONFIG_MIXED_WITH_GRANULAR_GIT_CONFIG' | 'CANNOT_RESOLVE_CHANGELOG_RENDERER' | 'INVALID_CHANGELOG_CREATE_RELEASE_PROVIDER' @@ -147,13 +155,15 @@ export async function createNxReleaseConfig( if (hasInvalidConventionalCommitsConfig(userConfig)) { return { error: { - code: 'CONVENTIONAL_COMMITS_SHORTHAND_MIXED_WITH_OVERLAPPING_GENERATOR_OPTIONS', + code: 'CONVENTIONAL_COMMITS_SHORTHAND_MIXED_WITH_OVERLAPPING_OPTIONS', data: {}, }, nxReleaseConfig: null, }; } + const USE_LEGACY_VERSIONING = shouldUseLegacyVersioning(userConfig); + const gitDefaults = { commit: false, commitMessage: 'chore(release): publish {version}', @@ -163,6 +173,7 @@ export async function createNxReleaseConfig( tagArgs: '', stageChanges: false, push: false, + pushArgs: '', }; const versionGitDefaults = { ...gitDefaults, @@ -259,12 +270,41 @@ export async function createNxReleaseConfig( projectsRelationship: workspaceProjectsRelationship, git: gitDefaults, version: { + useLegacyVersioning: USE_LEGACY_VERSIONING, git: versionGitDefaults, conventionalCommits: userConfig.version?.conventionalCommits || false, - generator: '@nx/js:release-version', - generatorOptions: defaultGeneratorOptions, preVersionCommand: userConfig.version?.preVersionCommand || '', - }, + ...(USE_LEGACY_VERSIONING + ? { + generator: '@nx/js:release-version', + generatorOptions: defaultGeneratorOptions, + } + : { + versionActions: DEFAULT_VERSION_ACTIONS_PATH, + versionActionsOptions: {}, + currentVersionResolver: + defaultGeneratorOptions.currentVersionResolver, + specifierSource: defaultGeneratorOptions.specifierSource, + preserveLocalDependencyProtocols: + ( + userConfig.version as + | NxReleaseVersionV2Configuration + | undefined + )?.preserveLocalDependencyProtocols ?? true, + logUnchangedProjects: + ( + userConfig.version as + | NxReleaseVersionV2Configuration + | undefined + )?.logUnchangedProjects ?? true, + updateDependents: + ( + userConfig.version as + | NxReleaseVersionV2Configuration + | undefined + )?.updateDependents ?? 'auto', + }), + } as DeepRequired, changelog: { git: changelogGitDefaults, workspaceChangelog: disableWorkspaceChangelog @@ -318,12 +358,23 @@ export async function createNxReleaseConfig( const GROUP_DEFAULTS: Omit = { projectsRelationship: groupProjectsRelationship, - version: { - conventionalCommits: false, - generator: '@nx/js:release-version', - generatorOptions: {}, - groupPreVersionCommand: '', - }, + version: USE_LEGACY_VERSIONING + ? ({ + conventionalCommits: false, + generator: '@nx/js:release-version', + generatorOptions: {}, + groupPreVersionCommand: '', + } as DeepRequired< + NxReleaseConfiguration['groups']['string']['version'] + >) + : ({ + conventionalCommits: false, + versionActions: DEFAULT_VERSION_ACTIONS_PATH, + versionActionsOptions: {}, + groupPreVersionCommand: '', + } as DeepRequired< + NxReleaseConfiguration['groups']['string']['version'] + >), changelog: { createRelease: false, entryWhenNoChanges: @@ -359,7 +410,9 @@ export async function createNxReleaseConfig( [ WORKSPACE_DEFAULTS.version, // Merge in the git defaults from the top level - { git: versionGitDefaults } as NxReleaseConfig['version'], + { + git: versionGitDefaults, + } as NxReleaseConfig['version'], { git: userConfig.git as Partial, } as NxReleaseConfig['version'], @@ -407,35 +460,62 @@ export async function createNxReleaseConfig( ); // these options are not supported at the group level, only the root/command level - const rootVersionWithoutGlobalOptions = { + let rootVersionWithoutGlobalOptions = { ...rootVersionConfig, - }; + } as DeepRequired< + { + useLegacyVersioning?: boolean; + git?: NxReleaseGitConfiguration; + preVersionCommand?: string; + } & LegacyNxReleaseVersionConfiguration + >; delete rootVersionWithoutGlobalOptions.git; delete rootVersionWithoutGlobalOptions.preVersionCommand; // Apply conventionalCommits shorthand to the final group defaults if explicitly configured in the original user config if (userConfig.version?.conventionalCommits === true) { - rootVersionWithoutGlobalOptions.generatorOptions = { - ...rootVersionWithoutGlobalOptions.generatorOptions, - currentVersionResolver: 'git-tag', - specifierSource: 'conventional-commits', - }; + if (USE_LEGACY_VERSIONING) { + rootVersionWithoutGlobalOptions.generatorOptions = { + ...rootVersionWithoutGlobalOptions.generatorOptions, + currentVersionResolver: 'git-tag', + specifierSource: 'conventional-commits', + }; + } else { + ( + rootVersionWithoutGlobalOptions as NxReleaseVersionV2Configuration + ).currentVersionResolver = 'git-tag'; + ( + rootVersionWithoutGlobalOptions as NxReleaseVersionV2Configuration + ).specifierSource = 'conventional-commits'; + } } if (userConfig.version?.conventionalCommits === false) { delete rootVersionWithoutGlobalOptions.generatorOptions .currentVersionResolver; delete rootVersionWithoutGlobalOptions.generatorOptions.specifierSource; + delete (rootVersionWithoutGlobalOptions as NxReleaseVersionV2Configuration) + .currentVersionResolver; + delete (rootVersionWithoutGlobalOptions as NxReleaseVersionV2Configuration) + .specifierSource; } // Apply versionPlans shorthand to the final group defaults if explicitly configured in the original user config if (userConfig.versionPlans) { - rootVersionWithoutGlobalOptions.generatorOptions = { - ...rootVersionWithoutGlobalOptions.generatorOptions, - specifierSource: 'version-plans', - }; + if (USE_LEGACY_VERSIONING) { + rootVersionWithoutGlobalOptions.generatorOptions = { + ...rootVersionWithoutGlobalOptions.generatorOptions, + specifierSource: 'version-plans', + }; + } else { + ( + rootVersionWithoutGlobalOptions as NxReleaseVersionV2Configuration + ).specifierSource = 'version-plans'; + } } if (userConfig.versionPlans === false) { delete rootVersionWithoutGlobalOptions.generatorOptions.specifierSource; + delete (rootVersionWithoutGlobalOptions as NxReleaseVersionV2Configuration) + .specifierSource; } const groups: NxReleaseConfig['groups'] = @@ -462,9 +542,9 @@ export async function createNxReleaseConfig( * be the valid source of truth for that type of config. */ version: deepMergeDefaults( - [GROUP_DEFAULTS.version], + [GROUP_DEFAULTS.version] as any, rootVersionWithoutGlobalOptions - ), + ) as any, // If the user has set something custom for releaseTagPattern at the top level, respect it for the implicit default group releaseTagPattern: userConfig.releaseTagPattern || GROUP_DEFAULTS.releaseTagPattern, @@ -551,7 +631,7 @@ export async function createNxReleaseConfig( version: deepMergeDefaults( // First apply any group level defaults, then apply actual root level config, then group level config [ - GROUP_DEFAULTS.version, + GROUP_DEFAULTS.version as any, { ...rootVersionWithoutGlobalOptions, groupPreVersionCommand: '' }, ], releaseGroup.version @@ -584,13 +664,29 @@ export async function createNxReleaseConfig( projects: matchingProjects, }); + finalReleaseGroup.version = + finalReleaseGroup.version as unknown as DeepRequired< + LegacyNxReleaseVersionConfiguration & { + groupPreVersionCommand?: string; + } + >; + // Apply conventionalCommits shorthand to the final group if explicitly configured in the original group if (releaseGroup.version?.conventionalCommits === true) { - finalReleaseGroup.version.generatorOptions = { - ...finalReleaseGroup.version.generatorOptions, - currentVersionResolver: 'git-tag', - specifierSource: 'conventional-commits', - }; + if (USE_LEGACY_VERSIONING) { + finalReleaseGroup.version.generatorOptions = { + ...finalReleaseGroup.version.generatorOptions, + currentVersionResolver: 'git-tag', + specifierSource: 'conventional-commits', + }; + } else { + ( + finalReleaseGroup.version as NxReleaseVersionV2Configuration + ).currentVersionResolver = 'git-tag'; + ( + finalReleaseGroup.version as NxReleaseVersionV2Configuration + ).specifierSource = 'conventional-commits'; + } } if ( releaseGroup.version?.conventionalCommits === false && @@ -598,25 +694,36 @@ export async function createNxReleaseConfig( ) { delete finalReleaseGroup.version.generatorOptions.currentVersionResolver; delete finalReleaseGroup.version.generatorOptions.specifierSource; + delete (finalReleaseGroup.version as NxReleaseVersionV2Configuration) + .currentVersionResolver; + delete (finalReleaseGroup.version as NxReleaseVersionV2Configuration) + .specifierSource; } // Apply versionPlans shorthand to the final group if explicitly configured in the original group if (releaseGroup.versionPlans) { - finalReleaseGroup.version = { - ...finalReleaseGroup.version, - generatorOptions: { - ...finalReleaseGroup.version?.generatorOptions, - specifierSource: 'version-plans', - }, - }; + if (USE_LEGACY_VERSIONING) { + finalReleaseGroup.version = { + ...finalReleaseGroup.version, + generatorOptions: { + ...finalReleaseGroup.version?.generatorOptions, + specifierSource: 'version-plans', + }, + }; + } else { + ( + finalReleaseGroup.version as NxReleaseVersionV2Configuration + ).specifierSource = 'version-plans'; + } } if ( releaseGroup.versionPlans === false && releaseGroupName !== IMPLICIT_DEFAULT_RELEASE_GROUP ) { delete finalReleaseGroup.version.generatorOptions.specifierSource; + delete (finalReleaseGroup.version as NxReleaseVersionV2Configuration) + .specifierSource; } - releaseGroups[releaseGroupName] = finalReleaseGroup; } @@ -755,7 +862,8 @@ function fillUnspecifiedConventionalCommitsProperties( } export async function handleNxReleaseConfigError( - error: CreateNxReleaseConfigError + error: CreateNxReleaseConfigError, + useLegacyVersioning: boolean ): Promise { switch (error.code) { case 'PROJECTS_AND_GROUPS_DEFINED': @@ -808,13 +916,16 @@ export async function handleNxReleaseConfigError( }); } break; - case 'CONVENTIONAL_COMMITS_SHORTHAND_MIXED_WITH_OVERLAPPING_GENERATOR_OPTIONS': + case 'CONVENTIONAL_COMMITS_SHORTHAND_MIXED_WITH_OVERLAPPING_OPTIONS': { const nxJsonMessage = await resolveNxJsonConfigErrorMessage([ 'release', ]); + const text = useLegacyVersioning + ? '"version.generatorOptions"' + : 'configuration options'; output.error({ - title: `You have configured both the shorthand "version.conventionalCommits" and one or more of the related "version.generatorOptions" that it sets for you. Please use one or the other:`, + title: `You have configured both the shorthand "version.conventionalCommits" and one or more of the related ${text} that it sets for you. Please use one or the other:`, bodyLines: [nxJsonMessage], }); } @@ -990,7 +1101,7 @@ function deepMergeDefaults( /** * We want to prevent users from setting both the conventionalCommits shorthand and any of the related - * generatorOptions at the same time, since it is at best redundant, and at worst invalid. + * configuration options at the same time, since it is at best redundant, and at worst invalid. */ function hasInvalidConventionalCommitsConfig( userConfig: NxJsonConfiguration['release'] @@ -998,8 +1109,16 @@ function hasInvalidConventionalCommitsConfig( // at the root if ( userConfig.version?.conventionalCommits === true && - (userConfig.version?.generatorOptions?.currentVersionResolver || - userConfig.version?.generatorOptions?.specifierSource) + // v2 config - directly on version config + ((userConfig.version as NxReleaseVersionV2Configuration) + ?.currentVersionResolver || + (userConfig.version as NxReleaseVersionV2Configuration) + ?.specifierSource || + // Legacy config - on generatorOptions + (userConfig.version as LegacyNxReleaseVersionConfiguration) + ?.generatorOptions?.currentVersionResolver || + (userConfig.version as LegacyNxReleaseVersionConfiguration) + ?.generatorOptions?.specifierSource) ) { return true; } @@ -1008,8 +1127,15 @@ function hasInvalidConventionalCommitsConfig( for (const group of Object.values(userConfig.groups)) { if ( group.version?.conventionalCommits === true && - (group.version?.generatorOptions?.currentVersionResolver || - group.version?.generatorOptions?.specifierSource) + // v2 config - directly on version config + ((group.version as NxReleaseVersionV2Configuration) + ?.currentVersionResolver || + (group.version as NxReleaseVersionV2Configuration)?.specifierSource || + // Legacy config - on generatorOptions + (group.version as LegacyNxReleaseVersionConfiguration) + ?.generatorOptions?.currentVersionResolver || + (group.version as LegacyNxReleaseVersionConfiguration) + ?.generatorOptions?.specifierSource) ) { return true; } diff --git a/packages/nx/src/command-line/release/config/filter-release-groups.spec.ts b/packages/nx/src/command-line/release/config/filter-release-groups.spec.ts index f1bced45ac..39abbdcede 100644 --- a/packages/nx/src/command-line/release/config/filter-release-groups.spec.ts +++ b/packages/nx/src/command-line/release/config/filter-release-groups.spec.ts @@ -21,12 +21,14 @@ describe('filterReleaseGroups()', () => { tagArgs: '', stageChanges: false, push: false, + pushArgs: '', }, workspaceChangelog: false, projectChangelogs: false, automaticFromRef: false, }, version: { + useLegacyVersioning: true, conventionalCommits: false, generator: '', generatorOptions: {}, @@ -39,6 +41,7 @@ describe('filterReleaseGroups()', () => { tagMessage: '', tagArgs: '', push: false, + pushArgs: '', }, preVersionCommand: '', }, @@ -53,6 +56,7 @@ describe('filterReleaseGroups()', () => { tagArgs: '', stageChanges: false, push: false, + pushArgs: '', }, conventionalCommits: DEFAULT_CONVENTIONAL_COMMITS_CONFIG, versionPlans: false, diff --git a/packages/nx/src/command-line/release/config/use-legacy-versioning.ts b/packages/nx/src/command-line/release/config/use-legacy-versioning.ts new file mode 100644 index 0000000000..7ced46398b --- /dev/null +++ b/packages/nx/src/command-line/release/config/use-legacy-versioning.ts @@ -0,0 +1,10 @@ +import type { NxJsonConfiguration } from '../../../config/nx-json'; + +export function shouldUseLegacyVersioning( + releaseConfig: NxJsonConfiguration['release'] | undefined +) { + return process.env.NX_INTERNAL_USE_LEGACY_VERSIONING === 'false' + ? false + : // TODO(v21): switch this to false by default in Nx v21 and remove this function in v22 + releaseConfig?.version?.useLegacyVersioning ?? true; +} diff --git a/packages/nx/src/command-line/release/index.ts b/packages/nx/src/command-line/release/index.ts index 0fb8e0a208..2ee3c992cf 100644 --- a/packages/nx/src/command-line/release/index.ts +++ b/packages/nx/src/command-line/release/index.ts @@ -49,3 +49,11 @@ export const releaseVersion = defaultClient.releaseVersion.bind( export const release = defaultClient.release.bind( defaultClient ) as typeof defaultClient.release; + +/** + * @public + */ +export { + AfterAllProjectsVersioned, + VersionActions, +} from './version/version-actions'; diff --git a/packages/nx/src/command-line/release/plan-check.ts b/packages/nx/src/command-line/release/plan-check.ts index ac112adaca..4a3f013584 100644 --- a/packages/nx/src/command-line/release/plan-check.ts +++ b/packages/nx/src/command-line/release/plan-check.ts @@ -6,8 +6,8 @@ import { parseFiles, splitArgsIntoNxArgsAndOverrides, } from '../../utils/command-line-utils'; -import { output } from '../../utils/output'; import { handleErrors } from '../../utils/handle-errors'; +import { output } from '../../utils/output'; import { PlanCheckOptions, PlanOptions } from './command-object'; import { createNxReleaseConfig, @@ -16,6 +16,7 @@ import { } from './config/config'; import { deepMergeJson } from './config/deep-merge-json'; import { filterReleaseGroups } from './config/filter-release-groups'; +import { shouldUseLegacyVersioning } from './config/use-legacy-versioning'; import { readRawVersionPlans, setResolvedVersionPlansOnGroups, @@ -42,7 +43,13 @@ export function createAPI(overrideReleaseConfig: NxReleaseConfiguration) { userProvidedReleaseConfig ); if (configError) { - return await handleNxReleaseConfigError(configError); + const USE_LEGACY_VERSIONING = shouldUseLegacyVersioning( + userProvidedReleaseConfig + ); + return await handleNxReleaseConfigError( + configError, + USE_LEGACY_VERSIONING + ); } // --print-config exits directly as it is not designed to be combined with any other programmatic operations if (args.printConfig) { @@ -69,7 +76,8 @@ export function createAPI(overrideReleaseConfig: NxReleaseConfiguration) { title: 'Version plans are not enabled', bodyLines: [ 'Please ensure at least one release group has version plans enabled in your Nx release configuration if you want to use this command.', - // TODO: Add docs link here once it is available + '', + 'Learn more about version plans here: https://nx.dev/recipes/nx-release/file-based-versioning-version-plans', ], }); return 1; diff --git a/packages/nx/src/command-line/release/plan.ts b/packages/nx/src/command-line/release/plan.ts index 4c10b2cc7d..4039b36a2c 100644 --- a/packages/nx/src/command-line/release/plan.ts +++ b/packages/nx/src/command-line/release/plan.ts @@ -12,8 +12,8 @@ import { parseFiles, splitArgsIntoNxArgsAndOverrides, } from '../../utils/command-line-utils'; -import { output } from '../../utils/output'; import { handleErrors } from '../../utils/handle-errors'; +import { output } from '../../utils/output'; import { PlanOptions } from './command-object'; import { createNxReleaseConfig, @@ -22,6 +22,7 @@ import { } from './config/config'; import { deepMergeJson } from './config/deep-merge-json'; import { filterReleaseGroups } from './config/filter-release-groups'; +import { shouldUseLegacyVersioning } from './config/use-legacy-versioning'; import { getVersionPlansAbsolutePath } from './config/version-plans'; import { generateVersionPlanContent } from './utils/generate-version-plan-content'; import { createGetTouchedProjectsForGroup } from './utils/get-touched-projects-for-group'; @@ -50,7 +51,13 @@ export function createAPI(overrideReleaseConfig: NxReleaseConfiguration) { userProvidedReleaseConfig ); if (configError) { - return await handleNxReleaseConfigError(configError); + const USE_LEGACY_VERSIONING = shouldUseLegacyVersioning( + userProvidedReleaseConfig + ); + return await handleNxReleaseConfigError( + configError, + USE_LEGACY_VERSIONING + ); } // --print-config exits directly as it is not designed to be combined with any other programmatic operations if (args.printConfig) { @@ -319,10 +326,12 @@ async function promptForVersion(message: string): Promise { }, ]); return reply.version; - } catch (e) { + } catch { output.log({ title: 'Cancelled version plan creation.', }); + // Ensure the cursor is always restored before exiting + process.stdout.write('\u001b[?25h'); process.exit(0); } } diff --git a/packages/nx/src/command-line/release/publish.ts b/packages/nx/src/command-line/release/publish.ts index b30a176bfa..1a27e4f7a7 100644 --- a/packages/nx/src/command-line/release/publish.ts +++ b/packages/nx/src/command-line/release/publish.ts @@ -8,6 +8,10 @@ import { ProjectGraphProjectNode, } from '../../config/project-graph'; import { createProjectFileMapUsingProjectGraph } from '../../project-graph/file-map-utils'; +import { + runPostTasksExecution, + runPreTasksExecution, +} from '../../project-graph/plugins/tasks-execution-hooks'; import { createProjectGraphAsync } from '../../project-graph/project-graph'; import { runCommandForTasks } from '../../tasks-runner/run-command'; import { @@ -17,6 +21,7 @@ import { import { handleErrors } from '../../utils/handle-errors'; import { output } from '../../utils/output'; import { projectHasTarget } from '../../utils/project-graph-utils'; +import { workspaceRoot } from '../../utils/workspace-root'; import { generateGraph } from '../graph/graph'; import { PublishOptions } from './command-object'; import { @@ -25,12 +30,8 @@ import { } from './config/config'; import { deepMergeJson } from './config/deep-merge-json'; import { filterReleaseGroups } from './config/filter-release-groups'; +import { shouldUseLegacyVersioning } from './config/use-legacy-versioning'; import { printConfigAndExit } from './utils/print-config'; -import { workspaceRoot } from '../../utils/workspace-root'; -import { - runPostTasksExecution, - runPreTasksExecution, -} from '../../project-graph/plugins/tasks-execution-hooks'; export interface PublishProjectsResult { [projectName: string]: { @@ -83,7 +84,13 @@ export function createAPI(overrideReleaseConfig: NxReleaseConfiguration) { userProvidedReleaseConfig ); if (configError) { - return await handleNxReleaseConfigError(configError); + const USE_LEGACY_VERSIONING = shouldUseLegacyVersioning( + userProvidedReleaseConfig + ); + return await handleNxReleaseConfigError( + configError, + USE_LEGACY_VERSIONING + ); } // --print-config exits directly as it is not designed to be combined with any other programmatic operations if (args.printConfig) { diff --git a/packages/nx/src/command-line/release/release.ts b/packages/nx/src/command-line/release/release.ts index 66e0373eb3..d96b071b6f 100644 --- a/packages/nx/src/command-line/release/release.ts +++ b/packages/nx/src/command-line/release/release.ts @@ -3,8 +3,8 @@ import { rmSync } from 'node:fs'; import { NxReleaseConfiguration, readNxJson } from '../../config/nx-json'; import { createProjectFileMapUsingProjectGraph } from '../../project-graph/file-map-utils'; import { createProjectGraphAsync } from '../../project-graph/project-graph'; -import { output } from '../../utils/output'; import { handleErrors } from '../../utils/handle-errors'; +import { output } from '../../utils/output'; import { createAPI as createReleaseChangelogAPI, shouldCreateGitHubRelease, @@ -17,6 +17,7 @@ import { } from './config/config'; import { deepMergeJson } from './config/deep-merge-json'; import { filterReleaseGroups } from './config/filter-release-groups'; +import { shouldUseLegacyVersioning } from './config/use-legacy-versioning'; import { readRawVersionPlans, setResolvedVersionPlansOnGroups, @@ -79,7 +80,13 @@ export function createAPI(overrideReleaseConfig: NxReleaseConfiguration) { userProvidedReleaseConfig ); if (configError) { - return await handleNxReleaseConfigError(configError); + const USE_LEGACY_VERSIONING = shouldUseLegacyVersioning( + userProvidedReleaseConfig + ); + return await handleNxReleaseConfigError( + configError, + USE_LEGACY_VERSIONING + ); } // --print-config exits directly as it is not designed to be combined with any other programmatic operations if (args.printConfig) { @@ -254,6 +261,7 @@ export function createAPI(overrideReleaseConfig: NxReleaseConfiguration) { await gitPush({ dryRun: args.dryRun, verbose: args.verbose, + additionalArgs: nxReleaseConfig.git.pushArgs, }); hasPushedChanges = true; } @@ -361,7 +369,9 @@ async function promptForPublish(): Promise { }, ]); return reply.confirmation; - } catch (e) { + } catch { + // Ensure the cursor is always restored before exiting + process.stdout.write('\u001b[?25h'); // Handle the case where the user exits the prompt with ctrl+c return false; } diff --git a/packages/nx/src/command-line/release/utils/batch-projects-by-generator-config.ts b/packages/nx/src/command-line/release/utils/batch-projects-by-generator-config.ts index 2549077992..2e065b92f5 100644 --- a/packages/nx/src/command-line/release/utils/batch-projects-by-generator-config.ts +++ b/packages/nx/src/command-line/release/utils/batch-projects-by-generator-config.ts @@ -1,3 +1,4 @@ +import { LegacyNxReleaseVersionConfiguration } from '../../../config/nx-json'; import { ProjectGraph } from '../../../config/project-graph'; import { deepEquals } from '../../../utils/json-diff'; import { ReleaseGroupWithName } from '../config/filter-release-groups'; @@ -15,11 +16,14 @@ export function batchProjectsByGeneratorConfig( for (const projectName of projectNamesToBatch) { const project = projectGraph.nodes[projectName]; const generator = - project.data.release?.version?.generator || - releaseGroup.version.generator; + (project.data.release?.version as LegacyNxReleaseVersionConfiguration) + ?.generator || + (releaseGroup.version as LegacyNxReleaseVersionConfiguration).generator; const generatorOptions = { - ...releaseGroup.version.generatorOptions, - ...project.data.release?.version?.generatorOptions, + ...(releaseGroup.version as LegacyNxReleaseVersionConfiguration) + .generatorOptions, + ...(project.data.release?.version as LegacyNxReleaseVersionConfiguration) + ?.generatorOptions, }; let found = false; diff --git a/packages/nx/src/command-line/release/utils/git.ts b/packages/nx/src/command-line/release/utils/git.ts index 749de08e6a..3eff95e136 100644 --- a/packages/nx/src/command-line/release/utils/git.ts +++ b/packages/nx/src/command-line/release/utils/git.ts @@ -416,10 +416,12 @@ export async function gitPush({ gitRemote, dryRun, verbose, + additionalArgs, }: { gitRemote?: string; dryRun?: boolean; verbose?: boolean; + additionalArgs?: string | string[]; }) { const commandArgs = [ 'push', @@ -430,6 +432,13 @@ export async function gitPush({ // Set custom git remote if provided ...(gitRemote ? [gitRemote] : []), ]; + if (additionalArgs) { + if (Array.isArray(additionalArgs)) { + commandArgs.push(...additionalArgs); + } else { + commandArgs.push(...additionalArgs.split(' ')); + } + } if (verbose) { console.log( @@ -534,7 +543,7 @@ export function parseGitCommit( commit.shortHash ), // The commit message is not the source of truth for a breaking (major) change in version plans, so the value is not relevant - // TODO(v20): Make the current GitCommit interface more clearly tied to conventional commits + // TODO(v22): Make the current GitCommit interface more clearly tied to conventional commits isBreaking: false, authors: getAllAuthorsForCommit(commit), // Not applicable to version plans diff --git a/packages/nx/src/command-line/release/utils/github.ts b/packages/nx/src/command-line/release/utils/github.ts index 1de5e4578c..5323ce2855 100644 --- a/packages/nx/src/command-line/release/utils/github.ts +++ b/packages/nx/src/command-line/release/utils/github.ts @@ -296,7 +296,9 @@ async function promptForContinueInGitHub(): Promise { }, ]); return reply.open === 'Yes'; - } catch (e) { + } catch { + // Ensure the cursor is always restored before exiting + process.stdout.write('\u001b[?25h'); // Handle the case where the user exits the prompt with ctrl+c process.exit(1); } diff --git a/packages/nx/src/command-line/release/utils/resolve-semver-specifier.ts b/packages/nx/src/command-line/release/utils/resolve-semver-specifier.ts index 84e8ba28e2..bb8145e5c4 100644 --- a/packages/nx/src/command-line/release/utils/resolve-semver-specifier.ts +++ b/packages/nx/src/command-line/release/utils/resolve-semver-specifier.ts @@ -2,6 +2,7 @@ import { prompt } from 'enquirer'; import { RELEASE_TYPES, valid } from 'semver'; import { ProjectGraph } from '../../../config/project-graph'; import { NxReleaseConfig } from '../config/config'; +import { SemverBumpType } from '../version/version-actions'; import { getGitDiff, parseCommits } from './git'; import { determineSemverChange } from './semver'; import { getCommitsRelevantToProjects } from './shared'; @@ -25,7 +26,7 @@ export async function resolveSemverSpecifierFromConventionalCommits( export async function resolveSemverSpecifierFromPrompt( selectionMessage: string, customVersionMessage: string -): Promise { +): Promise { try { const reply = await prompt<{ specifier: string }>([ { @@ -42,7 +43,7 @@ export async function resolveSemverSpecifierFromPrompt( }, ]); if (reply.specifier !== 'custom') { - return reply.specifier; + return reply.specifier as SemverBumpType; } else { const reply = await prompt<{ specifier: string }>([ { @@ -60,7 +61,8 @@ export async function resolveSemverSpecifierFromPrompt( return reply.specifier; } } catch { - // TODO: log the error to the user? + // Ensure the cursor is always restored before exiting + process.stdout.write('\u001b[?25h'); // We need to catch the error from enquirer prompt, otherwise yargs will print its help process.exit(1); } diff --git a/packages/nx/src/command-line/release/utils/semver.ts b/packages/nx/src/command-line/release/utils/semver.ts index eab81e82d2..85cd279d63 100644 --- a/packages/nx/src/command-line/release/utils/semver.ts +++ b/packages/nx/src/command-line/release/utils/semver.ts @@ -17,6 +17,14 @@ export function isValidSemverSpecifier(specifier: string): boolean { ); } +/** + * TODO: We would be able to make the logging for the conventional commits use-case + * for fixed release groups more clear/precise if we passed through which project + * the conventional commits were detected for. + * + * It would then flow up through deriveSpecifierFromConventionalCommits back to + * ReleaseGroupProcessor. + */ // https://github.com/unjs/changelogen/blob/main/src/semver.ts export function determineSemverChange( commits: GitCommit[], diff --git a/packages/nx/src/command-line/release/utils/shared-legacy.ts b/packages/nx/src/command-line/release/utils/shared-legacy.ts new file mode 100644 index 0000000000..004bea94df --- /dev/null +++ b/packages/nx/src/command-line/release/utils/shared-legacy.ts @@ -0,0 +1,36 @@ +import { Tree } from '../../../generators/tree'; + +export type ReleaseVersionGeneratorResult = { + data: VersionData; + callback: ( + tree: Tree, + opts: { + dryRun?: boolean; + verbose?: boolean; + generatorOptions?: Record; + } + ) => Promise< + | string[] + | { + changedFiles: string[]; + deletedFiles: string[]; + } + >; +}; + +export type VersionData = Record< + string, + { + /** + * newVersion will be null in the case that no changes are detected for the project, + * e.g. when using conventional commits + */ + newVersion: string | null; + currentVersion: string; + /** + * The list of projects which depend upon the current project. + * NOTE: This is more strictly typed in versioning v2. + */ + dependentProjects: any[]; + } +>; diff --git a/packages/nx/src/command-line/release/utils/shared.spec.ts b/packages/nx/src/command-line/release/utils/shared.spec.ts index 412154db5b..2e26f3f5f2 100644 --- a/packages/nx/src/command-line/release/utils/shared.spec.ts +++ b/packages/nx/src/command-line/release/utils/shared.spec.ts @@ -199,6 +199,7 @@ describe('shared', () => { target: 'core', type: 'static', dependencyCollection: 'devDependencies', + rawVersionSpec: '1.0.0-canary.1', }, ], newVersion: '1.0.0-canary.2', diff --git a/packages/nx/src/command-line/release/utils/shared.ts b/packages/nx/src/command-line/release/utils/shared.ts index 1eb598482c..01d3ae72f8 100644 --- a/packages/nx/src/command-line/release/utils/shared.ts +++ b/packages/nx/src/command-line/release/utils/shared.ts @@ -1,7 +1,6 @@ import * as chalk from 'chalk'; import { prerelease } from 'semver'; import { ProjectGraph } from '../../../config/project-graph'; -import { Tree } from '../../../generators/tree'; import { createFileMapUsingProjectGraph } from '../../../project-graph/file-map-utils'; import { interpolate } from '../../../tasks-runner/utils'; import { output } from '../../../utils/output'; @@ -12,40 +11,27 @@ export const noDiffInChangelogMessage = chalk.yellow( `NOTE: There was no diff detected for the changelog entry. Maybe you intended to pass alternative git references via --from and --to?` ); -export type ReleaseVersionGeneratorResult = { - data: VersionData; - callback: ( - tree: Tree, - opts: { - dryRun?: boolean; - verbose?: boolean; - generatorOptions?: Record; - } - ) => Promise< - | string[] - | { - changedFiles: string[]; - deletedFiles: string[]; - } - >; -}; +// project name -> version data entry +export type VersionData = Record; -export type VersionData = Record< - string, - { - /** - * newVersion will be null in the case that no changes are detected for the project, - * e.g. when using conventional commits - */ - newVersion: string | null; - currentVersion: string; - /** - * The list of projects which depend upon the current project. - * TODO: investigate generic type for this once more ecosystems are explored - */ - dependentProjects: any[]; - } ->; +export interface VersionDataEntry { + currentVersion: string; + /** + * newVersion will be null in the case that no changes are detected for the project, + * e.g. when using conventional commits + */ + newVersion: string | null; + /** + * The list of projects which depend upon the current project. + */ + dependentProjects: { + source: string; + target: string; + type: string; + dependencyCollection: string; + rawVersionSpec: string; + }[]; +} function isPrerelease(version: string): boolean { // prerelease returns an array of matching prerelease "components", or null if the version is not a prerelease diff --git a/packages/nx/src/command-line/release/version-legacy.ts b/packages/nx/src/command-line/release/version-legacy.ts new file mode 100644 index 0000000000..129374527a --- /dev/null +++ b/packages/nx/src/command-line/release/version-legacy.ts @@ -0,0 +1,756 @@ +import * as chalk from 'chalk'; +import { execSync } from 'node:child_process'; +import { readFileSync } from 'node:fs'; +import { relative } from 'node:path'; +import { Generator } from '../../config/misc-interfaces'; +import { + NxJsonConfiguration, + NxReleaseConfiguration, +} from '../../config/nx-json'; +import { + ProjectGraph, + ProjectGraphProjectNode, +} from '../../config/project-graph'; +import { FsTree, Tree, flushChanges } from '../../generators/tree'; +import { readProjectsConfigurationFromProjectGraph } from '../../project-graph/project-graph'; +import { output } from '../../utils/output'; +import { combineOptionsForGenerator } from '../../utils/params'; +import { joinPathFragments } from '../../utils/path'; +import { workspaceRoot } from '../../utils/workspace-root'; +import { parseGeneratorString } from '../generate/generate'; +import { getGeneratorInformation } from '../generate/generator-utils'; +import { VersionOptions } from './command-object'; +import { NxReleaseConfig } from './config/config'; +import { + ReleaseGroupWithName, + filterReleaseGroups, +} from './config/filter-release-groups'; +import { + readRawVersionPlans, + setResolvedVersionPlansOnGroups, +} from './config/version-plans'; +import { batchProjectsByGeneratorConfig } from './utils/batch-projects-by-generator-config'; +import { gitAdd, gitPush, gitTag } from './utils/git'; +import { printDiff } from './utils/print-changes'; +import { resolveNxJsonConfigErrorMessage } from './utils/resolve-nx-json-error-message'; +import { + commitChanges, + createCommitMessageValues, + createGitTagValues, + handleDuplicateGitTags, +} from './utils/shared'; +import type { + ReleaseVersionGeneratorResult, + VersionData, +} from './utils/shared-legacy'; +import { NxReleaseVersionResult } from './version'; + +// Re-export some utils for use in plugin release-version generator implementations +export { deriveNewSemverVersion } from './utils/semver'; +export type { ReleaseVersionGeneratorResult, VersionData }; + +export const validReleaseVersionPrefixes = ['auto', '', '~', '^', '='] as const; + +export interface ReleaseVersionGeneratorSchema { + // The projects being versioned in the current execution + projects: ProjectGraphProjectNode[]; + releaseGroup: ReleaseGroupWithName; + projectGraph: ProjectGraph; + specifier?: string; + specifierSource?: 'prompt' | 'conventional-commits' | 'version-plans'; + preid?: string; + packageRoot?: string; + currentVersionResolver?: 'registry' | 'disk' | 'git-tag'; + currentVersionResolverMetadata?: Record; + fallbackCurrentVersionResolver?: 'disk'; + firstRelease?: boolean; + // auto means the existing prefix will be preserved, and is the default behavior + versionPrefix?: (typeof validReleaseVersionPrefixes)[number]; + skipLockFileUpdate?: boolean; + installArgs?: string; + installIgnoreScripts?: boolean; + conventionalCommitsConfig?: NxReleaseConfig['conventionalCommits']; + deleteVersionPlans?: boolean; + /** + * 'auto' is the default and will cause dependents to be updated (a patch version bump) when a dependency is versioned. + * This is only applicable to independently released projects. 'never' will cause dependents to not be updated. + */ + updateDependents?: 'auto' | 'never'; + /** + * Whether or not to completely omit project logs when that project has no applicable changes. This can be useful for + * large monorepos which have a large number of projects, especially when only a subset are released together. + */ + logUnchangedProjects?: boolean; + /** + * Whether or not to keep local dependency protocols (e.g. file:, workspace:) when updating dependencies in + * package.json files. This is `false` by default as not all package managers support publishing with these protocols + * still present in the package.json. + */ + preserveLocalDependencyProtocols?: boolean; +} + +const LARGE_BUFFER = 1024 * 1000000; + +export async function releaseVersionLegacy( + projectGraph: ProjectGraph, + args: VersionOptions, + nxJson: NxJsonConfiguration, + nxReleaseConfig: NxReleaseConfig, + userProvidedReleaseConfig: NxReleaseConfiguration +): Promise { + const { projects } = readProjectsConfigurationFromProjectGraph(projectGraph); + + // The nx release top level command will always override these three git args. This is how we can tell + // if the top level release command was used or if the user is using the changelog subcommand. + // If the user explicitly overrides these args, then it doesn't matter if the top level config is set, + // as all of the git options would be overridden anyway. + if ( + (args.gitCommit === undefined || + args.gitTag === undefined || + args.stageChanges === undefined) && + userProvidedReleaseConfig.git + ) { + const nxJsonMessage = await resolveNxJsonConfigErrorMessage([ + 'release', + 'git', + ]); + output.error({ + title: `The "release.git" property in nx.json may not be used with the "nx release version" subcommand or programmatic API. Instead, configure git options for subcommands directly with "release.version.git" and "release.changelog.git".`, + bodyLines: [nxJsonMessage], + }); + process.exit(1); + } + + const { + error: filterError, + filterLog, + releaseGroups, + releaseGroupToFilteredProjects, + } = filterReleaseGroups( + projectGraph, + nxReleaseConfig, + args.projects, + args.groups + ); + if (filterError) { + output.error(filterError); + process.exit(1); + } + if ( + filterLog && + process.env.NX_RELEASE_INTERNAL_SUPPRESS_FILTER_LOG !== 'true' + ) { + output.note(filterLog); + } + + if (!args.specifier) { + const rawVersionPlans = await readRawVersionPlans(); + await setResolvedVersionPlansOnGroups( + rawVersionPlans, + releaseGroups, + Object.keys(projectGraph.nodes), + args.verbose + ); + } else { + if (args.verbose && releaseGroups.some((g) => !!g.versionPlans)) { + console.log( + `Skipping version plan discovery as a specifier was provided` + ); + } + } + + if (args.deleteVersionPlans === undefined) { + // default to not delete version plans after versioning as they may be needed for changelog generation + args.deleteVersionPlans = false; + } + + runPreVersionCommand(nxReleaseConfig.version.preVersionCommand, { + dryRun: args.dryRun, + verbose: args.verbose, + }); + + const tree = new FsTree(workspaceRoot, args.verbose); + + const versionData: VersionData = {}; + const commitMessage: string | undefined = + args.gitCommitMessage || nxReleaseConfig.version.git.commitMessage; + const generatorCallbacks: (() => Promise)[] = []; + + /** + * additionalChangedFiles are files which need to be updated as a side-effect of versioning (such as package manager lock files), + * and need to get staged and committed as part of the existing commit, if applicable. + */ + const additionalChangedFiles = new Set(); + const additionalDeletedFiles = new Set(); + + if (args.projects?.length) { + /** + * Run versioning for all remaining release groups and filtered projects within them + */ + for (const releaseGroup of releaseGroups) { + const releaseGroupName = releaseGroup.name; + const releaseGroupProjectNames = Array.from( + releaseGroupToFilteredProjects.get(releaseGroup) + ); + const projectBatches = batchProjectsByGeneratorConfig( + projectGraph, + releaseGroup, + // Only batch based on the filtered projects within the release group + releaseGroupProjectNames + ); + + for (const [ + generatorConfigString, + projectNames, + ] of projectBatches.entries()) { + const [generatorName, generatorOptions] = JSON.parse( + generatorConfigString + ); + // Resolve the generator for the batch and run versioning on the projects within the batch + const generatorData = resolveGeneratorData({ + ...extractGeneratorCollectionAndName( + `batch "${JSON.stringify( + projectNames + )}" for release-group "${releaseGroupName}"`, + generatorName + ), + configGeneratorOptions: generatorOptions, + // all project data from the project graph (not to be confused with projectNamesToRunVersionOn) + projects, + }); + const generatorCallback = await runVersionOnProjects( + projectGraph, + nxJson, + args, + tree, + generatorData, + args.generatorOptionsOverrides, + projectNames, + releaseGroup, + versionData, + nxReleaseConfig.conventionalCommits + ); + // Capture the callback so that we can run it after flushing the changes to disk + generatorCallbacks.push(async () => { + const result = await generatorCallback(tree, { + dryRun: !!args.dryRun, + verbose: !!args.verbose, + generatorOptions: { + ...generatorOptions, + ...args.generatorOptionsOverrides, + }, + }); + const { changedFiles, deletedFiles } = + parseGeneratorCallbackResult(result); + changedFiles.forEach((f) => additionalChangedFiles.add(f)); + deletedFiles.forEach((f) => additionalDeletedFiles.add(f)); + }); + } + } + + // Resolve any git tags as early as possible so that we can hard error in case of any duplicates before reaching the actual git command + const gitTagValues: string[] = + args.gitTag ?? nxReleaseConfig.version.git.tag + ? createGitTagValues( + releaseGroups, + releaseGroupToFilteredProjects, + versionData + ) + : []; + handleDuplicateGitTags(gitTagValues); + + printAndFlushChanges(tree, !!args.dryRun); + + for (const generatorCallback of generatorCallbacks) { + await generatorCallback(); + } + + const changedFiles = [ + ...tree.listChanges().map((f) => f.path), + ...additionalChangedFiles, + ]; + + // No further actions are necessary in this scenario (e.g. if conventional commits detected no changes) + if (!changedFiles.length) { + return { + // An overall workspace version cannot be relevant when filtering to independent projects + workspaceVersion: undefined, + projectsVersionData: versionData, + }; + } + + if (args.gitCommit ?? nxReleaseConfig.version.git.commit) { + await commitChanges({ + changedFiles, + deletedFiles: Array.from(additionalDeletedFiles), + isDryRun: !!args.dryRun, + isVerbose: !!args.verbose, + gitCommitMessages: createCommitMessageValues( + releaseGroups, + releaseGroupToFilteredProjects, + versionData, + commitMessage + ), + gitCommitArgs: + args.gitCommitArgs || nxReleaseConfig.version.git.commitArgs, + }); + } else if (args.stageChanges ?? nxReleaseConfig.version.git.stageChanges) { + output.logSingleLine(`Staging changed files with git`); + await gitAdd({ + changedFiles, + dryRun: args.dryRun, + verbose: args.verbose, + }); + } + + if (args.gitTag ?? nxReleaseConfig.version.git.tag) { + output.logSingleLine(`Tagging commit with git`); + for (const tag of gitTagValues) { + await gitTag({ + tag, + message: args.gitTagMessage || nxReleaseConfig.version.git.tagMessage, + additionalArgs: + args.gitTagArgs || nxReleaseConfig.version.git.tagArgs, + dryRun: args.dryRun, + verbose: args.verbose, + }); + } + } + + if (args.gitPush ?? nxReleaseConfig.version.git.push) { + output.logSingleLine(`Pushing to git remote "${args.gitRemote}"`); + await gitPush({ + gitRemote: args.gitRemote, + dryRun: args.dryRun, + verbose: args.verbose, + additionalArgs: + args.gitPushArgs || nxReleaseConfig.version.git.pushArgs, + }); + } + + return { + // An overall workspace version cannot be relevant when filtering to independent projects + workspaceVersion: undefined, + projectsVersionData: versionData, + }; + } + + /** + * Run versioning for all remaining release groups + */ + for (const releaseGroup of releaseGroups) { + const releaseGroupName = releaseGroup.name; + + runPreVersionCommand( + releaseGroup.version.groupPreVersionCommand, + { + dryRun: args.dryRun, + verbose: args.verbose, + }, + releaseGroup + ); + + const projectBatches = batchProjectsByGeneratorConfig( + projectGraph, + releaseGroup, + // Batch based on all projects within the release group + releaseGroup.projects + ); + + for (const [ + generatorConfigString, + projectNames, + ] of projectBatches.entries()) { + const [generatorName, generatorOptions] = JSON.parse( + generatorConfigString + ); + // Resolve the generator for the batch and run versioning on the projects within the batch + const generatorData = resolveGeneratorData({ + ...extractGeneratorCollectionAndName( + `batch "${JSON.stringify( + projectNames + )}" for release-group "${releaseGroupName}"`, + generatorName + ), + configGeneratorOptions: generatorOptions, + // all project data from the project graph (not to be confused with projectNamesToRunVersionOn) + projects, + }); + const generatorCallback = await runVersionOnProjects( + projectGraph, + nxJson, + args, + tree, + generatorData, + args.generatorOptionsOverrides, + projectNames, + releaseGroup, + versionData, + nxReleaseConfig.conventionalCommits + ); + // Capture the callback so that we can run it after flushing the changes to disk + generatorCallbacks.push(async () => { + const result = await generatorCallback(tree, { + dryRun: !!args.dryRun, + verbose: !!args.verbose, + generatorOptions: { + ...generatorOptions, + ...args.generatorOptionsOverrides, + }, + }); + const { changedFiles, deletedFiles } = + parseGeneratorCallbackResult(result); + changedFiles.forEach((f) => additionalChangedFiles.add(f)); + deletedFiles.forEach((f) => additionalDeletedFiles.add(f)); + }); + } + } + + // Resolve any git tags as early as possible so that we can hard error in case of any duplicates before reaching the actual git command + const gitTagValues: string[] = + args.gitTag ?? nxReleaseConfig.version.git.tag + ? createGitTagValues( + releaseGroups, + releaseGroupToFilteredProjects, + versionData + ) + : []; + handleDuplicateGitTags(gitTagValues); + + printAndFlushChanges(tree, !!args.dryRun); + + for (const generatorCallback of generatorCallbacks) { + await generatorCallback(); + } + + // Only applicable when there is a single release group with a fixed relationship + let workspaceVersion: string | null | undefined = undefined; + if (releaseGroups.length === 1) { + const releaseGroup = releaseGroups[0]; + if (releaseGroup.projectsRelationship === 'fixed') { + const releaseGroupProjectNames = Array.from( + releaseGroupToFilteredProjects.get(releaseGroup) + ); + workspaceVersion = versionData[releaseGroupProjectNames[0]].newVersion; // all projects have the same version so we can just grab the first + } + } + + const changedFiles = [ + ...tree.listChanges().map((f) => f.path), + ...additionalChangedFiles, + ]; + const deletedFiles = Array.from(additionalDeletedFiles); + + // No further actions are necessary in this scenario (e.g. if conventional commits detected no changes) + if (!changedFiles.length && !deletedFiles.length) { + return { + workspaceVersion, + projectsVersionData: versionData, + }; + } + + if (args.gitCommit ?? nxReleaseConfig.version.git.commit) { + await commitChanges({ + changedFiles, + deletedFiles, + isDryRun: !!args.dryRun, + isVerbose: !!args.verbose, + gitCommitMessages: createCommitMessageValues( + releaseGroups, + releaseGroupToFilteredProjects, + versionData, + commitMessage + ), + gitCommitArgs: + args.gitCommitArgs || nxReleaseConfig.version.git.commitArgs, + }); + } else if (args.stageChanges ?? nxReleaseConfig.version.git.stageChanges) { + output.logSingleLine(`Staging changed files with git`); + await gitAdd({ + changedFiles, + deletedFiles, + dryRun: args.dryRun, + verbose: args.verbose, + }); + } + + if (args.gitTag ?? nxReleaseConfig.version.git.tag) { + output.logSingleLine(`Tagging commit with git`); + for (const tag of gitTagValues) { + await gitTag({ + tag, + message: args.gitTagMessage || nxReleaseConfig.version.git.tagMessage, + additionalArgs: args.gitTagArgs || nxReleaseConfig.version.git.tagArgs, + dryRun: args.dryRun, + verbose: args.verbose, + }); + } + } + + if (args.gitPush ?? nxReleaseConfig.version.git.push) { + output.logSingleLine(`Pushing to git remote "${args.gitRemote}"`); + await gitPush({ + gitRemote: args.gitRemote, + dryRun: args.dryRun, + verbose: args.verbose, + additionalArgs: args.gitPushArgs || nxReleaseConfig.version.git.pushArgs, + }); + } + + return { + workspaceVersion, + projectsVersionData: versionData, + }; +} + +function appendVersionData( + existingVersionData: VersionData, + newVersionData: VersionData +): VersionData { + // Mutate the existing version data + for (const [key, value] of Object.entries(newVersionData)) { + if (existingVersionData[key]) { + throw new Error( + `Version data key "${key}" already exists in version data. This is likely a bug, please report your use-case on https://github.com/nrwl/nx` + ); + } + existingVersionData[key] = value; + } + return existingVersionData; +} + +async function runVersionOnProjects( + projectGraph: ProjectGraph, + nxJson: NxJsonConfiguration, + args: VersionOptions, + tree: Tree, + generatorData: GeneratorData, + generatorOverrides: Record | undefined, + projectNames: string[], + releaseGroup: ReleaseGroupWithName, + versionData: VersionData, + conventionalCommitsConfig: NxReleaseConfig['conventionalCommits'] +): Promise { + const generatorOptions: ReleaseVersionGeneratorSchema = { + // Always ensure a string to avoid generator schema validation errors + specifier: args.specifier ?? '', + preid: args.preid ?? '', + ...generatorData.configGeneratorOptions, + ...(generatorOverrides ?? {}), + // The following are not overridable by user config + projects: projectNames.map((p) => projectGraph.nodes[p]), + projectGraph, + releaseGroup, + firstRelease: args.firstRelease ?? false, + conventionalCommitsConfig, + deleteVersionPlans: args.deleteVersionPlans, + }; + + // Apply generator defaults from schema.json file etc + const combinedOpts = await combineOptionsForGenerator( + generatorOptions as any, + generatorData.collectionName, + generatorData.normalizedGeneratorName, + readProjectsConfigurationFromProjectGraph(projectGraph), + nxJson, + generatorData.schema, + false, + null, + relative(process.cwd(), workspaceRoot), + args.verbose + ); + + const releaseVersionGenerator = generatorData.implementationFactory(); + + // We expect all version generator implementations to return a ReleaseVersionGeneratorResult object, rather than a GeneratorCallback + const versionResult = (await releaseVersionGenerator( + tree, + combinedOpts + )) as unknown as ReleaseVersionGeneratorResult; + + if (typeof versionResult === 'function') { + throw new Error( + `The version generator ${generatorData.collectionName}:${generatorData.normalizedGeneratorName} returned a function instead of an expected ReleaseVersionGeneratorResult` + ); + } + + // Merge the extra version data into the existing + appendVersionData(versionData, versionResult.data); + + return versionResult.callback; +} + +function printAndFlushChanges(tree: Tree, isDryRun: boolean) { + const changes = tree.listChanges(); + + console.log(''); + + // Print the changes + changes.forEach((f) => { + if (f.type === 'CREATE') { + console.error( + `${chalk.green('CREATE')} ${f.path}${ + isDryRun ? chalk.keyword('orange')(' [dry-run]') : '' + }` + ); + printDiff('', f.content?.toString() || ''); + } else if (f.type === 'UPDATE') { + console.error( + `${chalk.white('UPDATE')} ${f.path}${ + isDryRun ? chalk.keyword('orange')(' [dry-run]') : '' + }` + ); + const currentContentsOnDisk = readFileSync( + joinPathFragments(tree.root, f.path) + ).toString(); + printDiff(currentContentsOnDisk, f.content?.toString() || ''); + } else if (f.type === 'DELETE' && !f.path.includes('.nx')) { + throw new Error( + 'Unexpected DELETE change, please report this as an issue' + ); + } + }); + + if (!isDryRun) { + flushChanges(workspaceRoot, changes); + } +} + +function extractGeneratorCollectionAndName( + description: string, + generatorString: string +) { + let collectionName: string; + let generatorName: string; + const parsedGeneratorString = parseGeneratorString(generatorString); + collectionName = parsedGeneratorString.collection; + generatorName = parsedGeneratorString.generator; + + if (!collectionName || !generatorName) { + throw new Error( + `Invalid generator string: ${generatorString} used for ${description}. Must be in the format of [collectionName]:[generatorName]` + ); + } + + return { collectionName, generatorName }; +} + +interface GeneratorData { + collectionName: string; + generatorName: string; + configGeneratorOptions: Record; + normalizedGeneratorName: string; + schema: any; + implementationFactory: () => Generator; +} + +function resolveGeneratorData({ + collectionName, + generatorName, + configGeneratorOptions, + projects, +}): GeneratorData { + try { + const { normalizedGeneratorName, schema, implementationFactory } = + getGeneratorInformation( + collectionName, + generatorName, + workspaceRoot, + projects + ); + + return { + collectionName, + generatorName, + configGeneratorOptions, + normalizedGeneratorName, + schema, + implementationFactory, + }; + } catch (err) { + if (err.message.startsWith('Unable to resolve')) { + // See if it is because the plugin is not installed + try { + require.resolve(collectionName); + // is installed + throw new Error( + `Unable to resolve the generator called "${generatorName}" within the "${collectionName}" package` + ); + } catch { + /** + * Special messaging for the most common case (especially as the user is unlikely to explicitly have + * the @nx/js generator config in their nx.json so we need to be clear about what the problem is) + */ + if (collectionName === '@nx/js') { + throw new Error( + 'The @nx/js plugin is required in order to version your JavaScript packages. Run "nx add @nx/js" to add it to your workspace.' + ); + } + throw new Error( + `Unable to resolve the package ${collectionName} in order to load the generator called ${generatorName}. Is the package installed?` + ); + } + } + // Unexpected error, rethrow + throw err; + } +} +function runPreVersionCommand( + preVersionCommand: string, + { dryRun, verbose }: { dryRun: boolean; verbose: boolean }, + releaseGroup?: ReleaseGroupWithName +) { + if (!preVersionCommand) { + return; + } + + output.logSingleLine( + releaseGroup + ? `Executing release group pre-version command for "${releaseGroup.name}"` + : `Executing pre-version command` + ); + if (verbose) { + console.log(`Executing the following pre-version command:`); + console.log(preVersionCommand); + } + + let env: Record = { + ...process.env, + }; + if (dryRun) { + env.NX_DRY_RUN = 'true'; + } + + const stdio = verbose ? 'inherit' : 'pipe'; + try { + execSync(preVersionCommand, { + encoding: 'utf-8', + maxBuffer: LARGE_BUFFER, + stdio, + env, + windowsHide: false, + }); + } catch (e) { + const title = verbose + ? `The pre-version command failed. See the full output above.` + : `The pre-version command failed. Retry with --verbose to see the full output of the pre-version command.`; + output.error({ + title, + bodyLines: [preVersionCommand, e], + }); + process.exit(1); + } +} + +function parseGeneratorCallbackResult( + result: string[] | { changedFiles: string[]; deletedFiles: string[] } +): { changedFiles: string[]; deletedFiles: string[] } { + if (Array.isArray(result)) { + return { + changedFiles: result, + deletedFiles: [], + }; + } else { + return result; + } +} diff --git a/packages/nx/src/command-line/release/version.ts b/packages/nx/src/command-line/release/version.ts index c54cb442f6..3bca6d2e42 100644 --- a/packages/nx/src/command-line/release/version.ts +++ b/packages/nx/src/command-line/release/version.ts @@ -1,32 +1,21 @@ import * as chalk from 'chalk'; import { execSync } from 'node:child_process'; import { readFileSync } from 'node:fs'; -import { relative } from 'node:path'; -import { Generator } from '../../config/misc-interfaces'; import { - NxJsonConfiguration, NxReleaseConfiguration, + NxReleaseVersionV2Configuration, readNxJson, } from '../../config/nx-json'; -import { - ProjectGraph, - ProjectGraphProjectNode, -} from '../../config/project-graph'; +import { formatChangedFilesWithPrettierIfAvailable } from '../../generators/internal-utils/format-changed-files-with-prettier-if-available'; import { FsTree, Tree, flushChanges } from '../../generators/tree'; import { createProjectFileMapUsingProjectGraph } from '../../project-graph/file-map-utils'; -import { - createProjectGraphAsync, - readProjectsConfigurationFromProjectGraph, -} from '../../project-graph/project-graph'; +import { createProjectGraphAsync } from '../../project-graph/project-graph'; +import { handleErrors } from '../../utils/handle-errors'; import { output } from '../../utils/output'; -import { combineOptionsForGenerator } from '../../utils/params'; import { joinPathFragments } from '../../utils/path'; import { workspaceRoot } from '../../utils/workspace-root'; -import { parseGeneratorString } from '../generate/generate'; -import { getGeneratorInformation } from '../generate/generator-utils'; import { VersionOptions } from './command-object'; import { - NxReleaseConfig, createNxReleaseConfig, handleNxReleaseConfigError, } from './config/config'; @@ -39,70 +28,28 @@ import { readRawVersionPlans, setResolvedVersionPlansOnGroups, } from './config/version-plans'; -import { batchProjectsByGeneratorConfig } from './utils/batch-projects-by-generator-config'; import { gitAdd, gitPush, gitTag } from './utils/git'; import { printDiff } from './utils/print-changes'; import { printConfigAndExit } from './utils/print-config'; import { resolveNxJsonConfigErrorMessage } from './utils/resolve-nx-json-error-message'; import { - ReleaseVersionGeneratorResult, VersionData, commitChanges, createCommitMessageValues, createGitTagValues, handleDuplicateGitTags, } from './utils/shared'; -import { handleErrors } from '../../utils/handle-errors'; +import { releaseVersionLegacy } from './version-legacy'; +import { ReleaseGroupProcessor } from './version/release-group-processor'; +import { SemverBumpType } from './version/version-actions'; +import { shouldUseLegacyVersioning } from './config/use-legacy-versioning'; const LARGE_BUFFER = 1024 * 1000000; // Reexport some utils for use in plugin release-version generator implementations -export { deriveNewSemverVersion } from './utils/semver'; -export type { - ReleaseVersionGeneratorResult, - VersionData, -} from './utils/shared'; export const validReleaseVersionPrefixes = ['auto', '', '~', '^', '='] as const; -export interface ReleaseVersionGeneratorSchema { - // The projects being versioned in the current execution - projects: ProjectGraphProjectNode[]; - releaseGroup: ReleaseGroupWithName; - projectGraph: ProjectGraph; - specifier?: string; - specifierSource?: 'prompt' | 'conventional-commits' | 'version-plans'; - preid?: string; - packageRoot?: string; - currentVersionResolver?: 'registry' | 'disk' | 'git-tag'; - currentVersionResolverMetadata?: Record; - fallbackCurrentVersionResolver?: 'disk'; - firstRelease?: boolean; - // auto means the existing prefix will be preserved, and is the default behavior - versionPrefix?: (typeof validReleaseVersionPrefixes)[number]; - skipLockFileUpdate?: boolean; - installArgs?: string; - installIgnoreScripts?: boolean; - conventionalCommitsConfig?: NxReleaseConfig['conventionalCommits']; - deleteVersionPlans?: boolean; - /** - * 'auto' is the default and will cause dependents to be updated (a patch version bump) when a dependency is versioned. - * This is only applicable to independently released projects. 'never' will cause dependents to not be updated. - */ - updateDependents?: 'auto' | 'never'; - /** - * Whether or not to completely omit project logs when that project has no applicable changes. This can be useful for - * large monorepos which have a large number of projects, especially when only a subset are released together. - */ - logUnchangedProjects?: boolean; - /** - * Whether or not to keep local dependency protocols (e.g. file:, workspace:) when updating dependencies in - * package.json files. This is `false` by default as not all package managers support publishing with these protocols - * still present in the package.json. - */ - preserveLocalDependencyProtocols?: boolean; -} - export interface NxReleaseVersionResult { /** * In one specific (and very common) case, an overall workspace version is relevant, for example when there is @@ -131,13 +78,14 @@ export function createAPI(overrideReleaseConfig: NxReleaseConfiguration) { args: VersionOptions ): Promise { const projectGraph = await createProjectGraphAsync({ exitOnError: true }); - const { projects } = - readProjectsConfigurationFromProjectGraph(projectGraph); const nxJson = readNxJson(); const userProvidedReleaseConfig = deepMergeJson( nxJson.release ?? {}, overrideReleaseConfig ?? {} ); + const USE_LEGACY_VERSIONING = shouldUseLegacyVersioning( + userProvidedReleaseConfig + ); // Apply default configuration to any optional user configuration const { error: configError, nxReleaseConfig } = await createNxReleaseConfig( @@ -146,7 +94,10 @@ export function createAPI(overrideReleaseConfig: NxReleaseConfiguration) { userProvidedReleaseConfig ); if (configError) { - return await handleNxReleaseConfigError(configError); + return await handleNxReleaseConfigError( + configError, + USE_LEGACY_VERSIONING + ); } // --print-config exits directly as it is not designed to be combined with any other programmatic operations if (args.printConfig) { @@ -157,6 +108,17 @@ export function createAPI(overrideReleaseConfig: NxReleaseConfiguration) { }); } + // TODO(v22): Remove support for the legacy versioning implementation in Nx v22 + if (USE_LEGACY_VERSIONING) { + return await releaseVersionLegacy( + projectGraph, + args, + nxJson, + nxReleaseConfig, + userProvidedReleaseConfig + ); + } + // The nx release top level command will always override these three git args. This is how we can tell // if the top level release command was used or if the user is using the changelog subcommand. // If the user explicitly overrides these args, then it doesn't matter if the top level config is set, @@ -221,185 +183,18 @@ export function createAPI(overrideReleaseConfig: NxReleaseConfiguration) { args.deleteVersionPlans = false; } + /** + * Run any configured top level pre-version command + */ runPreVersionCommand(nxReleaseConfig.version.preVersionCommand, { dryRun: args.dryRun, verbose: args.verbose, }); - const tree = new FsTree(workspaceRoot, args.verbose); - - const versionData: VersionData = {}; - const commitMessage: string | undefined = - args.gitCommitMessage || nxReleaseConfig.version.git.commitMessage; - const generatorCallbacks: (() => Promise)[] = []; - /** - * additionalChangedFiles are files which need to be updated as a side-effect of versioning (such as package manager lock files), - * and need to get staged and committed as part of the existing commit, if applicable. - */ - const additionalChangedFiles = new Set(); - const additionalDeletedFiles = new Set(); - - if (args.projects?.length) { - /** - * Run versioning for all remaining release groups and filtered projects within them - */ - for (const releaseGroup of releaseGroups) { - const releaseGroupName = releaseGroup.name; - const releaseGroupProjectNames = Array.from( - releaseGroupToFilteredProjects.get(releaseGroup) - ); - const projectBatches = batchProjectsByGeneratorConfig( - projectGraph, - releaseGroup, - // Only batch based on the filtered projects within the release group - releaseGroupProjectNames - ); - - for (const [ - generatorConfigString, - projectNames, - ] of projectBatches.entries()) { - const [generatorName, generatorOptions] = JSON.parse( - generatorConfigString - ); - // Resolve the generator for the batch and run versioning on the projects within the batch - const generatorData = resolveGeneratorData({ - ...extractGeneratorCollectionAndName( - `batch "${JSON.stringify( - projectNames - )}" for release-group "${releaseGroupName}"`, - generatorName - ), - configGeneratorOptions: generatorOptions, - // all project data from the project graph (not to be confused with projectNamesToRunVersionOn) - projects, - }); - const generatorCallback = await runVersionOnProjects( - projectGraph, - nxJson, - args, - tree, - generatorData, - args.generatorOptionsOverrides, - projectNames, - releaseGroup, - versionData, - nxReleaseConfig.conventionalCommits - ); - // Capture the callback so that we can run it after flushing the changes to disk - generatorCallbacks.push(async () => { - const result = await generatorCallback(tree, { - dryRun: !!args.dryRun, - verbose: !!args.verbose, - generatorOptions: { - ...generatorOptions, - ...args.generatorOptionsOverrides, - }, - }); - const { changedFiles, deletedFiles } = - parseGeneratorCallbackResult(result); - changedFiles.forEach((f) => additionalChangedFiles.add(f)); - deletedFiles.forEach((f) => additionalDeletedFiles.add(f)); - }); - } - } - - // Resolve any git tags as early as possible so that we can hard error in case of any duplicates before reaching the actual git command - const gitTagValues: string[] = - args.gitTag ?? nxReleaseConfig.version.git.tag - ? createGitTagValues( - releaseGroups, - releaseGroupToFilteredProjects, - versionData - ) - : []; - handleDuplicateGitTags(gitTagValues); - - printAndFlushChanges(tree, !!args.dryRun); - - for (const generatorCallback of generatorCallbacks) { - await generatorCallback(); - } - - const changedFiles = [ - ...tree.listChanges().map((f) => f.path), - ...additionalChangedFiles, - ]; - - // No further actions are necessary in this scenario (e.g. if conventional commits detected no changes) - if (!changedFiles.length) { - return { - // An overall workspace version cannot be relevant when filtering to independent projects - workspaceVersion: undefined, - projectsVersionData: versionData, - }; - } - - if (args.gitCommit ?? nxReleaseConfig.version.git.commit) { - await commitChanges({ - changedFiles, - deletedFiles: Array.from(additionalDeletedFiles), - isDryRun: !!args.dryRun, - isVerbose: !!args.verbose, - gitCommitMessages: createCommitMessageValues( - releaseGroups, - releaseGroupToFilteredProjects, - versionData, - commitMessage - ), - gitCommitArgs: - args.gitCommitArgs || nxReleaseConfig.version.git.commitArgs, - }); - } else if ( - args.stageChanges ?? - nxReleaseConfig.version.git.stageChanges - ) { - output.logSingleLine(`Staging changed files with git`); - await gitAdd({ - changedFiles, - dryRun: args.dryRun, - verbose: args.verbose, - }); - } - - if (args.gitTag ?? nxReleaseConfig.version.git.tag) { - output.logSingleLine(`Tagging commit with git`); - for (const tag of gitTagValues) { - await gitTag({ - tag, - message: - args.gitTagMessage || nxReleaseConfig.version.git.tagMessage, - additionalArgs: - args.gitTagArgs || nxReleaseConfig.version.git.tagArgs, - dryRun: args.dryRun, - verbose: args.verbose, - }); - } - } - - if (args.gitPush ?? nxReleaseConfig.version.git.push) { - output.logSingleLine(`Pushing to git remote "${args.gitRemote}"`); - await gitPush({ - gitRemote: args.gitRemote, - dryRun: args.dryRun, - verbose: args.verbose, - }); - } - - return { - // An overall workspace version cannot be relevant when filtering to independent projects - workspaceVersion: undefined, - projectsVersionData: versionData, - }; - } - - /** - * Run versioning for all remaining release groups + * Run any configured pre-version command for the selected release groups */ for (const releaseGroup of releaseGroups) { - const releaseGroupName = releaseGroup.name; - runPreVersionCommand( releaseGroup.version.groupPreVersionCommand, { @@ -408,63 +203,62 @@ export function createAPI(overrideReleaseConfig: NxReleaseConfiguration) { }, releaseGroup ); - - const projectBatches = batchProjectsByGeneratorConfig( - projectGraph, - releaseGroup, - // Batch based on all projects within the release group - releaseGroup.projects - ); - - for (const [ - generatorConfigString, - projectNames, - ] of projectBatches.entries()) { - const [generatorName, generatorOptions] = JSON.parse( - generatorConfigString - ); - // Resolve the generator for the batch and run versioning on the projects within the batch - const generatorData = resolveGeneratorData({ - ...extractGeneratorCollectionAndName( - `batch "${JSON.stringify( - projectNames - )}" for release-group "${releaseGroupName}"`, - generatorName - ), - configGeneratorOptions: generatorOptions, - // all project data from the project graph (not to be confused with projectNamesToRunVersionOn) - projects, - }); - const generatorCallback = await runVersionOnProjects( - projectGraph, - nxJson, - args, - tree, - generatorData, - args.generatorOptionsOverrides, - projectNames, - releaseGroup, - versionData, - nxReleaseConfig.conventionalCommits - ); - // Capture the callback so that we can run it after flushing the changes to disk - generatorCallbacks.push(async () => { - const result = await generatorCallback(tree, { - dryRun: !!args.dryRun, - verbose: !!args.verbose, - generatorOptions: { - ...generatorOptions, - ...args.generatorOptionsOverrides, - }, - }); - const { changedFiles, deletedFiles } = - parseGeneratorCallbackResult(result); - changedFiles.forEach((f) => additionalChangedFiles.add(f)); - deletedFiles.forEach((f) => additionalDeletedFiles.add(f)); - }); - } } + const tree = new FsTree(workspaceRoot, args.verbose); + + const commitMessage: string | undefined = + args.gitCommitMessage || nxReleaseConfig.version.git.commitMessage; + + /** + * additionalChangedFiles are files which need to be updated as a side-effect of versioning (such as package manager lock files), + * and need to get staged and committed as part of the existing commit, if applicable. + */ + const additionalChangedFiles = new Set(); + const additionalDeletedFiles = new Set(); + + const processor = new ReleaseGroupProcessor( + tree, + projectGraph, + nxReleaseConfig, + releaseGroups, + releaseGroupToFilteredProjects, + { + dryRun: args.dryRun, + verbose: args.verbose, + firstRelease: args.firstRelease, + preid: args.preid ?? '', + userGivenSpecifier: args.specifier as SemverBumpType | undefined, + filters: { + projects: args.projects, + groups: args.groups, + }, + } + ); + + try { + await processor.init(); + await processor.processGroups(); + + // Delete processed version plan files if applicable + if (args.deleteVersionPlans) { + processor.deleteProcessedVersionPlanFiles(); + } + } catch (err: any) { + // Flush any pending project logs before printing the error to make troubleshooting easier + processor.flushAllProjectLoggers(); + // Bubble up the error so that the CLI can print the error and exit, or the programmatic API can handle it + throw err; + } + + /** + * Ensure that formatting is applied so that version bump diffs are as minimal as possible + * within the context of the user's workspace. + */ + await formatChangedFilesWithPrettierIfAvailable(tree, { silent: true }); + + const versionData = processor.getVersionData(); + // Resolve any git tags as early as possible so that we can hard error in case of any duplicates before reaching the actual git command const gitTagValues: string[] = args.gitTag ?? nxReleaseConfig.version.git.tag @@ -478,9 +272,13 @@ export function createAPI(overrideReleaseConfig: NxReleaseConfiguration) { printAndFlushChanges(tree, !!args.dryRun); - for (const generatorCallback of generatorCallbacks) { - await generatorCallback(); - } + const { changedFiles: changed, deletedFiles: deleted } = + await processor.afterAllProjectsVersioned( + (nxReleaseConfig.version as NxReleaseVersionV2Configuration) + .versionActionsOptions + ); + changed.forEach((f) => additionalChangedFiles.add(f)); + deleted.forEach((f) => additionalDeletedFiles.add(f)); // Only applicable when there is a single release group with a fixed relationship let workspaceVersion: string | null | undefined = undefined; @@ -548,11 +346,15 @@ export function createAPI(overrideReleaseConfig: NxReleaseConfiguration) { } if (args.gitPush ?? nxReleaseConfig.version.git.push) { - output.logSingleLine(`Pushing to git remote "${args.gitRemote}"`); + output.logSingleLine( + `Pushing to git remote "${args.gitRemote ?? 'origin'}"` + ); await gitPush({ gitRemote: args.gitRemote, dryRun: args.dryRun, verbose: args.verbose, + additionalArgs: + args.gitPushArgs || nxReleaseConfig.version.git.pushArgs, }); } @@ -563,198 +365,49 @@ export function createAPI(overrideReleaseConfig: NxReleaseConfiguration) { }; } -function appendVersionData( - existingVersionData: VersionData, - newVersionData: VersionData -): VersionData { - // Mutate the existing version data - for (const [key, value] of Object.entries(newVersionData)) { - if (existingVersionData[key]) { - throw new Error( - `Version data key "${key}" already exists in version data. This is likely a bug, please report your use-case on https://github.com/nrwl/nx` - ); - } - existingVersionData[key] = value; - } - return existingVersionData; -} - -async function runVersionOnProjects( - projectGraph: ProjectGraph, - nxJson: NxJsonConfiguration, - args: VersionOptions, - tree: Tree, - generatorData: GeneratorData, - generatorOverrides: Record | undefined, - projectNames: string[], - releaseGroup: ReleaseGroupWithName, - versionData: VersionData, - conventionalCommitsConfig: NxReleaseConfig['conventionalCommits'] -): Promise { - const generatorOptions: ReleaseVersionGeneratorSchema = { - // Always ensure a string to avoid generator schema validation errors - specifier: args.specifier ?? '', - preid: args.preid ?? '', - ...generatorData.configGeneratorOptions, - ...(generatorOverrides ?? {}), - // The following are not overridable by user config - projects: projectNames.map((p) => projectGraph.nodes[p]), - projectGraph, - releaseGroup, - firstRelease: args.firstRelease ?? false, - conventionalCommitsConfig, - deleteVersionPlans: args.deleteVersionPlans, - }; - - // Apply generator defaults from schema.json file etc - const combinedOpts = await combineOptionsForGenerator( - generatorOptions as any, - generatorData.collectionName, - generatorData.normalizedGeneratorName, - readProjectsConfigurationFromProjectGraph(projectGraph), - nxJson, - generatorData.schema, - false, - null, - relative(process.cwd(), workspaceRoot), - args.verbose - ); - - const releaseVersionGenerator = generatorData.implementationFactory(); - - // We expect all version generator implementations to return a ReleaseVersionGeneratorResult object, rather than a GeneratorCallback - const versionResult = (await releaseVersionGenerator( - tree, - combinedOpts - )) as unknown as ReleaseVersionGeneratorResult; - - if (typeof versionResult === 'function') { - throw new Error( - `The version generator ${generatorData.collectionName}:${generatorData.normalizedGeneratorName} returned a function instead of an expected ReleaseVersionGeneratorResult` - ); - } - - // Merge the extra version data into the existing - appendVersionData(versionData, versionResult.data); - - return versionResult.callback; -} - function printAndFlushChanges(tree: Tree, isDryRun: boolean) { const changes = tree.listChanges(); console.log(''); - // Print the changes - changes.forEach((f) => { - if (f.type === 'CREATE') { - console.error( - `${chalk.green('CREATE')} ${f.path}${ - isDryRun ? chalk.keyword('orange')(' [dry-run]') : '' - }` - ); - printDiff('', f.content?.toString() || ''); - } else if (f.type === 'UPDATE') { - console.error( - `${chalk.white('UPDATE')} ${f.path}${ - isDryRun ? chalk.keyword('orange')(' [dry-run]') : '' - }` - ); - const currentContentsOnDisk = readFileSync( - joinPathFragments(tree.root, f.path) - ).toString(); - printDiff(currentContentsOnDisk, f.content?.toString() || ''); - } else if (f.type === 'DELETE' && !f.path.includes('.nx')) { - throw new Error( - 'Unexpected DELETE change, please report this as an issue' - ); - } - }); + if (changes.length > 0) { + // Print the changes + changes.forEach((f) => { + if (f.type === 'CREATE') { + console.error( + `${chalk.green('CREATE')} ${f.path}${ + isDryRun ? chalk.keyword('orange')(' [dry-run]') : '' + }` + ); + printDiff('', f.content?.toString() || ''); + } else if (f.type === 'UPDATE') { + console.error( + `${chalk.white('UPDATE')} ${f.path}${ + isDryRun ? chalk.keyword('orange')(' [dry-run]') : '' + }` + ); + const currentContentsOnDisk = readFileSync( + joinPathFragments(tree.root, f.path) + ).toString(); + printDiff(currentContentsOnDisk, f.content?.toString() || ''); + } else if (f.type === 'DELETE' && !f.path.includes('.nx')) { + throw new Error( + 'Unexpected DELETE change, please report this as an issue' + ); + } + }); + } else { + let text = isDryRun ? ' would be ' : ' '; + output.warn({ + title: `No files${text}changed as a result of running versioning`, + }); + } if (!isDryRun) { flushChanges(workspaceRoot, changes); } } -function extractGeneratorCollectionAndName( - description: string, - generatorString: string -) { - let collectionName: string; - let generatorName: string; - const parsedGeneratorString = parseGeneratorString(generatorString); - collectionName = parsedGeneratorString.collection; - generatorName = parsedGeneratorString.generator; - - if (!collectionName || !generatorName) { - throw new Error( - `Invalid generator string: ${generatorString} used for ${description}. Must be in the format of [collectionName]:[generatorName]` - ); - } - - return { collectionName, generatorName }; -} - -interface GeneratorData { - collectionName: string; - generatorName: string; - configGeneratorOptions: NxJsonConfiguration['release']['groups'][number]['version']['generatorOptions']; - normalizedGeneratorName: string; - schema: any; - implementationFactory: () => Generator; -} - -function resolveGeneratorData({ - collectionName, - generatorName, - configGeneratorOptions, - projects, -}): GeneratorData { - try { - const { normalizedGeneratorName, schema, implementationFactory } = - getGeneratorInformation( - collectionName, - generatorName, - workspaceRoot, - projects - ); - - return { - collectionName, - generatorName, - configGeneratorOptions, - normalizedGeneratorName, - schema, - implementationFactory, - }; - } catch (err) { - if (err.message.startsWith('Unable to resolve')) { - // See if it is because the plugin is not installed - try { - require.resolve(collectionName); - // is installed - throw new Error( - `Unable to resolve the generator called "${generatorName}" within the "${collectionName}" package` - ); - } catch { - /** - * Special messaging for the most common case (especially as the user is unlikely to explicitly have - * the @nx/js generator config in their nx.json so we need to be clear about what the problem is) - */ - if (collectionName === '@nx/js') { - throw new Error( - 'The @nx/js plugin is required in order to version your JavaScript packages. Run "nx add @nx/js" to add it to your workspace.' - ); - } - throw new Error( - `Unable to resolve the package ${collectionName} in order to load the generator called ${generatorName}. Is the package installed?` - ); - } - } - // Unexpected error, rethrow - throw err; - } -} function runPreVersionCommand( preVersionCommand: string, { dryRun, verbose }: { dryRun: boolean; verbose: boolean }, @@ -801,16 +454,3 @@ function runPreVersionCommand( process.exit(1); } } - -function parseGeneratorCallbackResult( - result: string[] | { changedFiles: string[]; deletedFiles: string[] } -): { changedFiles: string[]; deletedFiles: string[] } { - if (Array.isArray(result)) { - return { - changedFiles: result, - deletedFiles: [], - }; - } else { - return result; - } -} diff --git a/packages/nx/src/command-line/release/version/derive-specifier-from-conventional-commits.ts b/packages/nx/src/command-line/release/version/derive-specifier-from-conventional-commits.ts new file mode 100644 index 0000000000..3cc14df18f --- /dev/null +++ b/packages/nx/src/command-line/release/version/derive-specifier-from-conventional-commits.ts @@ -0,0 +1,85 @@ +import type { + ProjectGraph, + ProjectGraphProjectNode, +} from '../../../config/project-graph'; +import { NxReleaseConfig } from '../config/config'; +import { ReleaseGroupWithName } from '../config/filter-release-groups'; +import { getFirstGitCommit, getLatestGitTagForPattern } from '../utils/git'; +import { resolveSemverSpecifierFromConventionalCommits } from '../utils/resolve-semver-specifier'; +import { ProjectLogger } from './project-logger'; +import { SemverBumpType } from './version-actions'; + +export async function deriveSpecifierFromConventionalCommits( + nxReleaseConfig: NxReleaseConfig, + projectGraph: ProjectGraph, + projectLogger: ProjectLogger, + releaseGroup: ReleaseGroupWithName, + projectGraphNode: ProjectGraphProjectNode, + // NOTE: This TODO was carried over from the original version generator. + // + // TODO: reevaluate this prerelease logic/workflow for independent projects + // Always assume that if the current version is a prerelease, then the next version should be a prerelease. + // Users must manually graduate from a prerelease to a release by providing an explicit specifier. + isPrerelease: boolean, + latestMatchingGitTag: + | Awaited> + | undefined, + fallbackCurrentVersionResolver?: 'disk', + preid?: string +): Promise { + const affectedProjects = + releaseGroup.projectsRelationship === 'independent' + ? [projectGraphNode.name] + : releaseGroup.projects; + + // latestMatchingGitTag will be undefined if the current version was resolved from the disk fallback. + // In this case, we want to use the first commit as the ref to be consistent with the changelog command. + const previousVersionRef = latestMatchingGitTag + ? latestMatchingGitTag.tag + : fallbackCurrentVersionResolver === 'disk' + ? await getFirstGitCommit() + : undefined; + + if (!previousVersionRef) { + // This should never happen since the checks above should catch if the current version couldn't be resolved + throw new Error( + `Unable to determine previous version ref for the projects ${affectedProjects.join( + ', ' + )}. This is likely a bug in Nx.` + ); + } + + let specifier = await resolveSemverSpecifierFromConventionalCommits( + previousVersionRef, + projectGraph, + affectedProjects, + nxReleaseConfig.conventionalCommits + ); + + if (!specifier) { + projectLogger.buffer( + `🚫 No changes were detected using git history and the conventional commits standard` + ); + return 'none'; + } + + // NOTE: This TODO was carried over from the original version generator. + // TODO: reevaluate this prerelease logic/workflow for independent projects + if (isPrerelease) { + specifier = 'prerelease'; + projectLogger.buffer( + `📄 Resolved the specifier as "${specifier}" since the current version is a prerelease` + ); + } else { + let extraText = ''; + if (preid && !specifier.startsWith('pre')) { + specifier = `pre${specifier}`; + extraText = `, combined with your given preid "${preid}"`; + } + projectLogger.buffer( + `📄 Resolved the specifier as "${specifier}" using git history and the conventional commits standard${extraText}` + ); + } + + return specifier as SemverBumpType; +} diff --git a/packages/nx/src/command-line/release/version/deriver-specifier-from-version-plans.ts b/packages/nx/src/command-line/release/version/deriver-specifier-from-version-plans.ts new file mode 100644 index 0000000000..5bd1d2d2ee --- /dev/null +++ b/packages/nx/src/command-line/release/version/deriver-specifier-from-version-plans.ts @@ -0,0 +1,93 @@ +import { gt, inc, ReleaseType } from 'semver'; +import type { ProjectGraphProjectNode } from '../../../config/project-graph'; +import { ReleaseGroupWithName } from '../config/filter-release-groups'; +import { GroupVersionPlan, ProjectsVersionPlan } from '../config/version-plans'; +import { SemverBumpType } from './version-actions'; +import { ProjectLogger } from './project-logger'; + +export async function deriveSpecifierFromVersionPlan( + projectLogger: ProjectLogger, + releaseGroup: ReleaseGroupWithName, + projectGraphNode: ProjectGraphProjectNode, + currentVersion: string +): Promise<{ + bumpType: SemverBumpType; + versionPlanPath: string; +}> { + const projectName = projectGraphNode.name; + + let bumpType: ReleaseType | null = null; + let versionPlanPath: string | null = null; + + if (releaseGroup.projectsRelationship === 'independent') { + const result = ( + releaseGroup.resolvedVersionPlans as ProjectsVersionPlan[] + ).reduce( + ( + acc: { spec: ReleaseType | null; path: string | null }, + plan: ProjectsVersionPlan + ) => { + if (!acc.spec) { + return { + spec: plan.projectVersionBumps[projectName], + path: plan.relativePath, + }; + } + if (plan.projectVersionBumps[projectName]) { + const prevNewVersion = inc(currentVersion, acc.spec); + const nextNewVersion = inc( + currentVersion, + plan.projectVersionBumps[projectName] + ); + return gt(nextNewVersion, prevNewVersion) + ? { + spec: plan.projectVersionBumps[projectName], + path: plan.relativePath, + } + : acc; + } + return acc; + }, + { spec: null, path: null } + ); + bumpType = result.spec; + versionPlanPath = result.path; + } else { + const result = ( + releaseGroup.resolvedVersionPlans as GroupVersionPlan[] + ).reduce( + ( + acc: { spec: ReleaseType | null; path: string | null }, + plan: GroupVersionPlan + ) => { + if (!acc.spec) { + return { + spec: plan.groupVersionBump, + path: plan.relativePath, + }; + } + + const prevNewVersion = inc(currentVersion, acc.spec); + const nextNewVersion = inc(currentVersion, plan.groupVersionBump); + return gt(nextNewVersion, prevNewVersion) + ? { + spec: plan.groupVersionBump, + path: plan.relativePath, + } + : acc; + }, + { spec: null, path: null } + ); + bumpType = result.spec; + versionPlanPath = result.path; + } + + if (!bumpType) { + projectLogger.buffer(`🚫 No changes were detected within version plans`); + } + + return { + bumpType: bumpType ?? 'none', + versionPlanPath, + }; +} diff --git a/packages/nx/src/command-line/release/version/multiple-release-groups.spec.ts b/packages/nx/src/command-line/release/version/multiple-release-groups.spec.ts new file mode 100644 index 0000000000..491e08aaf7 --- /dev/null +++ b/packages/nx/src/command-line/release/version/multiple-release-groups.spec.ts @@ -0,0 +1,892 @@ +let mockDeriveSpecifierFromConventionalCommits = jest.fn(); +let mockDeriveSpecifierFromVersionPlan = jest.fn(); +let mockResolveVersionActionsForProject = jest.fn(); + +jest.doMock('./derive-specifier-from-conventional-commits', () => ({ + deriveSpecifierFromConventionalCommits: + mockDeriveSpecifierFromConventionalCommits, +})); + +jest.doMock('./version-actions', () => ({ + ...jest.requireActual('./version-actions'), + deriveSpecifierFromVersionPlan: mockDeriveSpecifierFromVersionPlan, + resolveVersionActionsForProject: mockResolveVersionActionsForProject, +})); + +jest.doMock('./project-logger', () => ({ + ...jest.requireActual('./project-logger'), + // Don't slow down or add noise to unit tests output unnecessarily + ProjectLogger: class ProjectLogger { + buffer() {} + flush() {} + }, +})); + +let mockResolveCurrentVersion = jest.fn(); +jest.doMock('./resolve-current-version', () => ({ + resolveCurrentVersion: mockResolveCurrentVersion, +})); + +import { createTreeWithEmptyWorkspace } from '../../../generators/testing-utils/create-tree-with-empty-workspace'; +import type { Tree } from '../../../generators/tree'; +import { + createNxReleaseConfigAndPopulateWorkspace, + mockResolveVersionActionsForProjectImplementation, +} from './test-utils'; + +const { ReleaseGroupProcessor } = require('./release-group-processor') as { + ReleaseGroupProcessor: typeof import('./release-group-processor').ReleaseGroupProcessor; +}; + +// Using the daemon in unit tests would cause jest to never exit +process.env.NX_DAEMON = 'false'; + +describe('Multiple Release Groups', () => { + let tree: Tree; + + beforeEach(() => { + tree = createTreeWithEmptyWorkspace(); + jest.resetAllMocks(); + + mockResolveVersionActionsForProject.mockImplementation( + mockResolveVersionActionsForProjectImplementation + ); + }); + + describe('Two unrelated groups, both fixed relationship, just JS', () => { + it('should correctly version projects using mocked conventional commits', async () => { + const { + nxReleaseConfig, + projectGraph, + releaseGroups, + releaseGroupToFilteredProjects, + filters, + } = await createNxReleaseConfigAndPopulateWorkspace( + tree, + ` + group1 ({ "projectsRelationship": "fixed" }): + - pkg-a@1.0.0 [js] + - pkg-b@1.0.0 [js] + -> depends on pkg-a + group2 ({ "projectsRelationship": "fixed" }): + - pkg-c@2.0.0 [js] + - pkg-d@2.0.0 [js] + -> depends on pkg-c + `, + { + version: { + conventionalCommits: true, + }, + }, + mockResolveCurrentVersion + ); + + mockDeriveSpecifierFromConventionalCommits.mockImplementation( + (_, __, ___, ____, { name: projectName }) => { + // Should cause pkg-a to become 1.1.0 and pkg-b as well because they are in a fixed group. pkg-b dependency on pkg-a should also be updated to 1.1.0 + if (projectName === 'pkg-a') return 'minor'; + if (projectName === 'pkg-b') return 'minor'; + // Should cause pkg-c to become 2.0.1 and pkg-d as well because they are in a fixed group. pkg-d dependency on pkg-c should also be updated to 2.0.1 + if (projectName === 'pkg-c') return 'patch'; + if (projectName === 'pkg-d') return 'patch'; + return 'none'; + } + ); + + const processor = new ReleaseGroupProcessor( + tree, + projectGraph, + nxReleaseConfig, + releaseGroups, + releaseGroupToFilteredProjects, + { + dryRun: false, + verbose: false, + firstRelease: false, + preid: undefined, + filters, + } + ); + + await processor.init(); + await processor.processGroups(); + + // Called for each project + expect(mockResolveVersionActionsForProject).toHaveBeenCalledTimes(4); + + expect(tree.read('pkg-a/package.json', 'utf-8')).toMatchInlineSnapshot(` + "{ + "name": "pkg-a", + "version": "1.1.0" + } + " + `); + expect(tree.read('pkg-b/package.json', 'utf-8')).toMatchInlineSnapshot(` + "{ + "name": "pkg-b", + "version": "1.1.0", + "dependencies": { + "pkg-a": "1.1.0" + } + } + " + `); + expect(tree.read('pkg-c/package.json', 'utf-8')).toMatchInlineSnapshot(` + "{ + "name": "pkg-c", + "version": "2.0.1" + } + " + `); + expect(tree.read('pkg-d/package.json', 'utf-8')).toMatchInlineSnapshot(` + "{ + "name": "pkg-d", + "version": "2.0.1", + "dependencies": { + "pkg-c": "2.0.1" + } + } + " + `); + }); + }); + + describe('Two unrelated groups, both independent relationship, just JS', () => { + it('should correctly version projects using mocked conventional commits', async () => { + const { + nxReleaseConfig, + projectGraph, + releaseGroups, + releaseGroupToFilteredProjects, + filters, + } = await createNxReleaseConfigAndPopulateWorkspace( + tree, + ` + group1 ({ "projectsRelationship": "independent" }): + - pkg-a@1.0.0 [js] + - pkg-b@1.1.0 [js] + -> depends on pkg-a + group2 ({ "projectsRelationship": "independent" }): + - pkg-c@2.0.0 [js] + - pkg-d@2.1.0 [js] + -> depends on pkg-c + `, + { + version: { + conventionalCommits: true, + }, + }, + mockResolveCurrentVersion + ); + + mockDeriveSpecifierFromConventionalCommits.mockImplementation( + (_, __, ___, ____, { name: projectName }) => { + if (projectName === 'pkg-a') return 'minor'; + if (projectName === 'pkg-b') return 'patch'; + if (projectName === 'pkg-c') return 'major'; + if (projectName === 'pkg-d') return 'none'; + return 'none'; + } + ); + + const processor = new ReleaseGroupProcessor( + tree, + projectGraph, + nxReleaseConfig, + releaseGroups, + releaseGroupToFilteredProjects, + { + dryRun: false, + verbose: false, + firstRelease: false, + preid: undefined, + filters, + } + ); + + await processor.init(); + await processor.processGroups(); + + // Called for each project + expect(mockResolveVersionActionsForProject).toHaveBeenCalledTimes(4); + + expect(tree.read('pkg-a/package.json', 'utf-8')).toMatchInlineSnapshot(` + "{ + "name": "pkg-a", + "version": "1.1.0" + } + " + `); + expect(tree.read('pkg-b/package.json', 'utf-8')).toMatchInlineSnapshot(` + "{ + "name": "pkg-b", + "version": "1.1.1", + "dependencies": { + "pkg-a": "1.1.0" + } + } + " + `); + expect(tree.read('pkg-c/package.json', 'utf-8')).toMatchInlineSnapshot(` + "{ + "name": "pkg-c", + "version": "3.0.0" + } + " + `); + // Patch bump due to dependency update + expect(tree.read('pkg-d/package.json', 'utf-8')).toMatchInlineSnapshot(` + "{ + "name": "pkg-d", + "version": "2.1.1", + "dependencies": { + "pkg-c": "3.0.0" + } + } + " + `); + }); + }); + + describe('Two unrelated groups, one fixed one independent, just JS', () => { + it('should correctly version projects using mocked conventional commits', async () => { + const { + nxReleaseConfig, + projectGraph, + releaseGroups, + releaseGroupToFilteredProjects, + filters, + } = await createNxReleaseConfigAndPopulateWorkspace( + tree, + ` + group1 ({ "projectsRelationship": "fixed" }): + - pkg-a@1.0.0 [js] + - pkg-b@1.0.0 [js] + -> depends on pkg-a + group2 ({ "projectsRelationship": "independent" }): + - pkg-c@2.0.0 [js] + - pkg-d@2.1.0 [js] + -> depends on pkg-c + `, + { + version: { + conventionalCommits: true, + }, + }, + mockResolveCurrentVersion + ); + + mockDeriveSpecifierFromConventionalCommits.mockImplementation( + (_, __, ___, ____, { name: projectName }) => { + if (projectName === 'pkg-a') return 'minor'; + if (projectName === 'pkg-b') return 'minor'; + if (projectName === 'pkg-c') return 'patch'; + if (projectName === 'pkg-d') return 'minor'; + return 'none'; + } + ); + + const processor = new ReleaseGroupProcessor( + tree, + projectGraph, + nxReleaseConfig, + releaseGroups, + releaseGroupToFilteredProjects, + { + dryRun: false, + verbose: false, + firstRelease: false, + preid: undefined, + filters, + } + ); + + await processor.init(); + await processor.processGroups(); + + // Called for each project + expect(mockResolveVersionActionsForProject).toHaveBeenCalledTimes(4); + + expect(tree.read('pkg-a/package.json', 'utf-8')).toMatchInlineSnapshot(` + "{ + "name": "pkg-a", + "version": "1.1.0" + } + " + `); + expect(tree.read('pkg-b/package.json', 'utf-8')).toMatchInlineSnapshot(` + "{ + "name": "pkg-b", + "version": "1.1.0", + "dependencies": { + "pkg-a": "1.1.0" + } + } + " + `); + expect(tree.read('pkg-c/package.json', 'utf-8')).toMatchInlineSnapshot(` + "{ + "name": "pkg-c", + "version": "2.0.1" + } + " + `); + expect(tree.read('pkg-d/package.json', 'utf-8')).toMatchInlineSnapshot(` + "{ + "name": "pkg-d", + "version": "2.2.0", + "dependencies": { + "pkg-c": "2.0.1" + } + } + " + `); + }); + }); + + describe('Two related groups, both fixed relationship, just JS', () => { + it('should correctly version projects across group boundaries', async () => { + const { + nxReleaseConfig, + projectGraph, + releaseGroups, + releaseGroupToFilteredProjects, + filters, + } = await createNxReleaseConfigAndPopulateWorkspace( + tree, + ` + group1 ({ "projectsRelationship": "fixed" }): + - pkg-a@1.0.0 [js] + -> depends on pkg-c + - pkg-b@1.0.0 [js] + group2 ({ "projectsRelationship": "fixed" }): + - pkg-c@2.0.0 [js] + - pkg-d@2.0.0 [js] + `, + { + version: { + conventionalCommits: true, + }, + }, + mockResolveCurrentVersion + ); + + mockDeriveSpecifierFromConventionalCommits.mockImplementation( + (_, __, ___, ____, { name: projectName }) => { + // pkg-c has a bump, which should cause pkg-d to bump because they are in a fixed group + // and pkg-a depends on pkg-c so should also bump, and pkg-b is in a fixed group with pkg-a so should also bump + if (projectName === 'pkg-c') return 'patch'; + return 'none'; + } + ); + + const processor = new ReleaseGroupProcessor( + tree, + projectGraph, + nxReleaseConfig, + releaseGroups, + releaseGroupToFilteredProjects, + { + dryRun: false, + verbose: false, + firstRelease: false, + preid: undefined, + filters, + } + ); + + await processor.init(); + await processor.processGroups(); + + expect(mockResolveVersionActionsForProject).toHaveBeenCalledTimes(4); + + expect(tree.read('pkg-a/package.json', 'utf-8')).toMatchInlineSnapshot(` + "{ + "name": "pkg-a", + "version": "1.0.1", + "dependencies": { + "pkg-c": "2.0.1" + } + } + " + `); + expect(tree.read('pkg-b/package.json', 'utf-8')).toMatchInlineSnapshot(` + "{ + "name": "pkg-b", + "version": "1.0.1" + } + " + `); + expect(tree.read('pkg-c/package.json', 'utf-8')).toMatchInlineSnapshot(` + "{ + "name": "pkg-c", + "version": "2.0.1" + } + " + `); + expect(tree.read('pkg-d/package.json', 'utf-8')).toMatchInlineSnapshot(` + "{ + "name": "pkg-d", + "version": "2.0.1" + } + " + `); + }); + }); + + describe('Two related groups, both independent relationship, just JS', () => { + const graphDefinition = ` + group1 ({ "projectsRelationship": "independent" }): + - pkg-a@1.0.0 [js] + -> depends on pkg-c + - pkg-b@1.1.0 [js] + group2 ({ "projectsRelationship": "independent" }): + - pkg-c@2.0.0 [js] + - pkg-d@2.1.0 [js] + `; + + it('should correctly version projects across group boundaries', async () => { + const { + nxReleaseConfig, + projectGraph, + releaseGroups, + releaseGroupToFilteredProjects, + filters, + } = await createNxReleaseConfigAndPopulateWorkspace( + tree, + graphDefinition, + { + version: { + conventionalCommits: true, + }, + }, + mockResolveCurrentVersion + ); + + mockDeriveSpecifierFromConventionalCommits.mockImplementation( + (_, __, ___, ____, { name: projectName }) => { + // pkg-a should still be bumped to 1.0.1 purely because of its dependency on pkg-c from the other group + if (projectName === 'pkg-a') return 'none'; + // pkg-b should not be bumped because it is in an independent group and has no specifier of its own + if (projectName === 'pkg-b') return 'none'; + // pkg-c should be bumped to 3.0.0 from its own specifier + if (projectName === 'pkg-c') return 'major'; + // pkg-d should not be bumped because it is in an independent group and has no specifier of its own + if (projectName === 'pkg-d') return 'none'; + return 'none'; + } + ); + + const processor = new ReleaseGroupProcessor( + tree, + projectGraph, + nxReleaseConfig, + releaseGroups, + releaseGroupToFilteredProjects, + { + dryRun: false, + verbose: false, + firstRelease: false, + preid: undefined, + filters, + } + ); + + await processor.init(); + await processor.processGroups(); + + expect(mockResolveVersionActionsForProject).toHaveBeenCalledTimes(4); + + expect(tree.read('pkg-a/package.json', 'utf-8')).toMatchInlineSnapshot(` + "{ + "name": "pkg-a", + "version": "1.0.1", + "dependencies": { + "pkg-c": "3.0.0" + } + } + " + `); + expect(tree.read('pkg-b/package.json', 'utf-8')).toMatchInlineSnapshot(` + "{ + "name": "pkg-b", + "version": "1.1.0" + } + " + `); + expect(tree.read('pkg-c/package.json', 'utf-8')).toMatchInlineSnapshot(` + "{ + "name": "pkg-c", + "version": "3.0.0" + } + " + `); + expect(tree.read('pkg-d/package.json', 'utf-8')).toMatchInlineSnapshot(` + "{ + "name": "pkg-d", + "version": "2.1.0" + } + " + `); + }); + + it('should pick an appropriate overall version across group boundaries when a project is influenced by both a direct specifier and a dependency bump', async () => { + const { + nxReleaseConfig, + projectGraph, + releaseGroups, + releaseGroupToFilteredProjects, + filters, + } = await createNxReleaseConfigAndPopulateWorkspace( + tree, + graphDefinition, + { + version: { + conventionalCommits: true, + }, + }, + mockResolveCurrentVersion + ); + + mockDeriveSpecifierFromConventionalCommits.mockImplementation( + (_, __, ___, ____, { name: projectName }) => { + // pkg-a should be bumped to 1.1.0 because its dependency on pkg-c causing a patch is lower than its own specifier of minor + if (projectName === 'pkg-a') return 'minor'; + // pkg-b should not be bumped because it is in an independent group and has no specifier of its own + if (projectName === 'pkg-b') return 'none'; + // pkg-c should be bumped to 3.0.0 from its own specifier + if (projectName === 'pkg-c') return 'major'; + // pkg-d should not be bumped because it is in an independent group and has no specifier of its own + if (projectName === 'pkg-d') return 'none'; + return 'none'; + } + ); + + const processor = new ReleaseGroupProcessor( + tree, + projectGraph, + nxReleaseConfig, + releaseGroups, + releaseGroupToFilteredProjects, + { + dryRun: false, + verbose: false, + firstRelease: false, + preid: undefined, + filters, + } + ); + + await processor.init(); + await processor.processGroups(); + + expect(mockResolveVersionActionsForProject).toHaveBeenCalledTimes(4); + + expect(tree.read('pkg-a/package.json', 'utf-8')).toMatchInlineSnapshot(` + "{ + "name": "pkg-a", + "version": "1.1.0", + "dependencies": { + "pkg-c": "3.0.0" + } + } + " + `); + expect(tree.read('pkg-b/package.json', 'utf-8')).toMatchInlineSnapshot(` + "{ + "name": "pkg-b", + "version": "1.1.0" + } + " + `); + expect(tree.read('pkg-c/package.json', 'utf-8')).toMatchInlineSnapshot(` + "{ + "name": "pkg-c", + "version": "3.0.0" + } + " + `); + expect(tree.read('pkg-d/package.json', 'utf-8')).toMatchInlineSnapshot(` + "{ + "name": "pkg-d", + "version": "2.1.0" + } + " + `); + }); + }); + + describe('Mixed JS and Rust projects within groups', () => { + describe('Two unrelated groups, both fixed relationship, mixed JS and Rust', () => { + it('should correctly version projects using mocked conventional commits', async () => { + const { + nxReleaseConfig, + projectGraph, + releaseGroups, + releaseGroupToFilteredProjects, + filters, + } = await createNxReleaseConfigAndPopulateWorkspace( + tree, + ` + group1 ({ "projectsRelationship": "fixed" }): + - pkg-a@1.0.0 [js] + - pkg-b@1.0.0 [rust] + group2 ({ "projectsRelationship": "fixed" }): + - pkg-c@2.0.0 [rust] + - pkg-d@2.0.0 [js] + `, + { + version: { + conventionalCommits: true, + }, + }, + mockResolveCurrentVersion + ); + + mockDeriveSpecifierFromConventionalCommits.mockImplementation( + (_, __, ___, ____, { name: projectName }) => { + if (projectName === 'pkg-a') return 'minor'; + if (projectName === 'pkg-b') return 'minor'; + if (projectName === 'pkg-c') return 'patch'; + if (projectName === 'pkg-d') return 'patch'; + return 'none'; + } + ); + + const processor = new ReleaseGroupProcessor( + tree, + projectGraph, + nxReleaseConfig, + releaseGroups, + releaseGroupToFilteredProjects, + { + dryRun: false, + verbose: false, + firstRelease: false, + preid: undefined, + filters, + } + ); + + await processor.init(); + await processor.processGroups(); + + // Called for each project + expect(mockResolveVersionActionsForProject).toHaveBeenCalledTimes(4); + + expect(tree.read('pkg-a/package.json', 'utf-8')).toMatchInlineSnapshot(` + "{ + "name": "pkg-a", + "version": "1.1.0" + } + " + `); + expect(tree.read('pkg-b/Cargo.toml', 'utf-8')).toMatchInlineSnapshot(` + " + [package] + name = 'pkg-b' + version = '1.1.0' + " + `); + expect(tree.read('pkg-c/Cargo.toml', 'utf-8')).toMatchInlineSnapshot(` + " + [package] + name = 'pkg-c' + version = '2.0.1' + " + `); + expect(tree.read('pkg-d/package.json', 'utf-8')).toMatchInlineSnapshot(` + "{ + "name": "pkg-d", + "version": "2.0.1" + } + " + `); + }); + }); + + describe('Two unrelated groups, both independent relationship, mixed JS and Rust', () => { + it('should correctly version projects using mocked conventional commits', async () => { + const { + nxReleaseConfig, + projectGraph, + releaseGroups, + releaseGroupToFilteredProjects, + filters, + } = await createNxReleaseConfigAndPopulateWorkspace( + tree, + ` + group1 ({ "projectsRelationship": "independent" }): + - pkg-a@1.0.0 [js] + - pkg-b@1.1.0 [rust] + group2 ({ "projectsRelationship": "independent" }): + - pkg-c@2.0.0 [rust] + - pkg-d@2.1.0 [js] + `, + { + version: { + conventionalCommits: true, + }, + }, + mockResolveCurrentVersion + ); + + mockDeriveSpecifierFromConventionalCommits.mockImplementation( + (_, __, ___, ____, { name: projectName }) => { + if (projectName === 'pkg-a') return 'minor'; + if (projectName === 'pkg-b') return 'patch'; + if (projectName === 'pkg-c') return 'major'; + if (projectName === 'pkg-d') return 'none'; + return 'none'; + } + ); + + const processor = new ReleaseGroupProcessor( + tree, + projectGraph, + nxReleaseConfig, + releaseGroups, + releaseGroupToFilteredProjects, + { + dryRun: false, + verbose: false, + firstRelease: false, + preid: undefined, + filters, + } + ); + + await processor.init(); + await processor.processGroups(); + + // Called for each project + expect(mockResolveVersionActionsForProject).toHaveBeenCalledTimes(4); + + expect(tree.read('pkg-a/package.json', 'utf-8')).toMatchInlineSnapshot(` + "{ + "name": "pkg-a", + "version": "1.1.0" + } + " + `); + expect(tree.read('pkg-b/Cargo.toml', 'utf-8')).toMatchInlineSnapshot(` + " + [package] + name = 'pkg-b' + version = '1.1.1' + " + `); + expect(tree.read('pkg-c/Cargo.toml', 'utf-8')).toMatchInlineSnapshot(` + " + [package] + name = 'pkg-c' + version = '3.0.0' + " + `); + expect(tree.read('pkg-d/package.json', 'utf-8')).toMatchInlineSnapshot(` + "{ + "name": "pkg-d", + "version": "2.1.0" + } + " + `); + }); + }); + + describe('Two related groups, both fixed relationship, mixed JS and Rust', () => { + it('should correctly version projects across group boundaries', async () => { + const { + nxReleaseConfig, + projectGraph, + releaseGroups, + releaseGroupToFilteredProjects, + filters, + } = await createNxReleaseConfigAndPopulateWorkspace( + tree, + ` + group1 ({ "projectsRelationship": "fixed" }): + - pkg-a@1.0.0 [js] + - pkg-b@1.0.0 [rust] + -> depends on pkg-c + group2 ({ "projectsRelationship": "fixed" }): + - pkg-c@2.0.0 [rust] + - pkg-d@2.0.0 [js] + `, + { + version: { + conventionalCommits: true, + }, + }, + mockResolveCurrentVersion + ); + + mockDeriveSpecifierFromConventionalCommits.mockImplementation( + (_, __, ___, ____, { name: projectName }) => { + // pkg-a should be bumped to 1.0.1 because it is in a fixed group with pkg-b and has no specifier of its own + if (projectName === 'pkg-a') return 'none'; + // pkg-b should be bumped to 1.0.1 because it depends on pkg-c which is being bumped + if (projectName === 'pkg-b') return 'none'; + // pkg-c should be bumped to 2.0.1 because of its specifier + if (projectName === 'pkg-c') return 'patch'; + // pkg-d should be bumped to 2.0.1 because it is in a fixed group with pkg-c and has no specifier of its own + if (projectName === 'pkg-d') return 'none'; + return 'none'; + } + ); + + const processor = new ReleaseGroupProcessor( + tree, + projectGraph, + nxReleaseConfig, + releaseGroups, + releaseGroupToFilteredProjects, + { + dryRun: false, + verbose: false, + firstRelease: false, + preid: undefined, + filters, + } + ); + + await processor.init(); + await processor.processGroups(); + + // Called for each project + expect(mockResolveVersionActionsForProject).toHaveBeenCalledTimes(4); + + expect(tree.read('pkg-a/package.json', 'utf-8')).toMatchInlineSnapshot(` + "{ + "name": "pkg-a", + "version": "1.0.1" + } + " + `); + expect(tree.read('pkg-b/Cargo.toml', 'utf-8')).toMatchInlineSnapshot(` + " + [package] + name = 'pkg-b' + version = '1.0.1' + + [dependencies] + pkg-c = '2.0.1' + " + `); + expect(tree.read('pkg-c/Cargo.toml', 'utf-8')).toMatchInlineSnapshot(` + " + [package] + name = 'pkg-c' + version = '2.0.1' + " + `); + expect(tree.read('pkg-d/package.json', 'utf-8')).toMatchInlineSnapshot(` + "{ + "name": "pkg-d", + "version": "2.0.1" + } + " + `); + }); + }); + }); +}); diff --git a/packages/nx/src/command-line/release/version/project-logger.ts b/packages/nx/src/command-line/release/version/project-logger.ts new file mode 100644 index 0000000000..d924694277 --- /dev/null +++ b/packages/nx/src/command-line/release/version/project-logger.ts @@ -0,0 +1,52 @@ +import * as chalk from 'chalk'; +import { output } from '../../../utils/output'; + +const colors = [ + { instance: chalk.green, spinnerColor: 'green' }, + { instance: chalk.greenBright, spinnerColor: 'green' }, + { instance: chalk.red, spinnerColor: 'red' }, + { instance: chalk.redBright, spinnerColor: 'red' }, + { instance: chalk.cyan, spinnerColor: 'cyan' }, + { instance: chalk.cyanBright, spinnerColor: 'cyan' }, + { instance: chalk.yellow, spinnerColor: 'yellow' }, + { instance: chalk.yellowBright, spinnerColor: 'yellow' }, + { instance: chalk.magenta, spinnerColor: 'magenta' }, + { instance: chalk.magentaBright, spinnerColor: 'magenta' }, +] as const; + +function getColor(projectName: string) { + let code = 0; + for (let i = 0; i < projectName.length; ++i) { + code += projectName.charCodeAt(i); + } + const colorIndex = code % colors.length; + + return colors[colorIndex]; +} + +export class ProjectLogger { + private logs: string[] = []; + private color: (typeof colors)[number]; + + constructor(private projectName: string) { + this.color = getColor(projectName); + } + + buffer(msg: string) { + this.logs.push(msg); + } + + flush() { + if (this.logs.length === 0) { + return; + } + output.logSingleLine( + `Running release version for project: ${this.color.instance.bold( + this.projectName + )}` + ); + this.logs.forEach((msg) => { + console.log(this.color.instance.bold(this.projectName) + ' ' + msg); + }); + } +} diff --git a/packages/nx/src/command-line/release/version/release-group-processor.spec.ts b/packages/nx/src/command-line/release/version/release-group-processor.spec.ts new file mode 100644 index 0000000000..8e6f560c87 --- /dev/null +++ b/packages/nx/src/command-line/release/version/release-group-processor.spec.ts @@ -0,0 +1,1676 @@ +let mockDeriveSpecifierFromConventionalCommits = jest.fn(); +let mockDeriveSpecifierFromVersionPlan = jest.fn(); +let mockResolveVersionActionsForProject = jest.fn(); + +jest.doMock('./derive-specifier-from-conventional-commits', () => ({ + deriveSpecifierFromConventionalCommits: + mockDeriveSpecifierFromConventionalCommits, +})); + +jest.doMock('./version-actions', () => ({ + ...jest.requireActual('./version-actions'), + deriveSpecifierFromVersionPlan: mockDeriveSpecifierFromVersionPlan, + resolveVersionActionsForProject: mockResolveVersionActionsForProject, +})); + +jest.doMock('./project-logger', () => ({ + ...jest.requireActual('./project-logger'), + // Don't slow down or add noise to unit tests output unnecessarily + ProjectLogger: class ProjectLogger { + buffer() {} + flush() {} + }, +})); + +let mockResolveCurrentVersion = jest.fn(); +jest.doMock('./resolve-current-version', () => ({ + resolveCurrentVersion: mockResolveCurrentVersion, +})); + +import { createTreeWithEmptyWorkspace } from '../../../generators/testing-utils/create-tree-with-empty-workspace'; +import type { Tree } from '../../../generators/tree'; +import { readJson, updateJson } from '../../../generators/utils/json'; +import { + createNxReleaseConfigAndPopulateWorkspace, + mockResolveVersionActionsForProjectImplementation, +} from './test-utils'; + +// This does not work with the mocking if we use import +const { ReleaseGroupProcessor } = require('./release-group-processor') as { + ReleaseGroupProcessor: typeof import('./release-group-processor').ReleaseGroupProcessor; +}; + +// Using the daemon in unit tests would cause jest to never exit +process.env.NX_DAEMON = 'false'; + +describe('ReleaseGroupProcessor', () => { + let tree: Tree; + + beforeEach(() => { + tree = createTreeWithEmptyWorkspace(); + updateJson(tree, 'nx.json', (json) => { + json.release = {}; + return json; + }); + mockResolveVersionActionsForProject.mockImplementation( + mockResolveVersionActionsForProjectImplementation + ); + }); + + afterEach(() => { + jest.restoreAllMocks(); + jest.resetAllMocks(); + }); + + it('should handle a single default group with fixed versioning, with no project dependency relationships', async () => { + const { + nxReleaseConfig, + projectGraph, + releaseGroups, + releaseGroupToFilteredProjects, + filters, + } = await createNxReleaseConfigAndPopulateWorkspace( + tree, + ` + __default__ ({ "projectsRelationship": "fixed" }): + - projectA@1.0.0 [js] + - projectB@1.0.0 [js] + - projectC@1.0.0 [js] + `, + { + version: { + conventionalCommits: true, + }, + }, + mockResolveCurrentVersion + ); + + const processor = new ReleaseGroupProcessor( + tree, + projectGraph, + nxReleaseConfig, + releaseGroups, + releaseGroupToFilteredProjects, + { + dryRun: false, + verbose: false, + firstRelease: false, + preid: undefined, + filters, + } + ); + await processor.init(); + + mockDeriveSpecifierFromConventionalCommits.mockImplementation(() => { + // This should only be called once for this group (for the first project) + return 'minor'; + }); + await processor.processGroups(); + + // All projects should be bumped to the same version as required by the first project + expect(mockDeriveSpecifierFromConventionalCommits).toHaveBeenCalledTimes(1); + expect(readJson(tree, 'projectA/package.json')).toMatchInlineSnapshot(` + { + "name": "projectA", + "version": "1.1.0", + } + `); + expect(readJson(tree, 'projectB/package.json')).toMatchInlineSnapshot(` + { + "name": "projectB", + "version": "1.1.0", + } + `); + expect(readJson(tree, 'projectC/package.json')).toMatchInlineSnapshot(` + { + "name": "projectC", + "version": "1.1.0", + } + `); + }); + + it('should handle a single default group with fixed versioning, with project dependency relationships', async () => { + const { + nxReleaseConfig, + projectGraph, + releaseGroups, + releaseGroupToFilteredProjects, + filters, + } = await createNxReleaseConfigAndPopulateWorkspace( + tree, + ` + __default__ ({ "projectsRelationship": "fixed" }): + - projectD@1.0.0 [js] + -> depends on projectE + - projectE@1.0.0 [js] + -> depends on projectF + - projectF@1.0.0 [js] + `, + { + version: { + conventionalCommits: true, + }, + }, + mockResolveCurrentVersion + ); + + const processor = new ReleaseGroupProcessor( + tree, + projectGraph, + nxReleaseConfig, + releaseGroups, + releaseGroupToFilteredProjects, + { + dryRun: false, + verbose: false, + firstRelease: false, + preid: undefined, + filters, + } + ); + await processor.init(); + + mockDeriveSpecifierFromConventionalCommits.mockImplementation(() => { + // This should only be called once for this group (for the first project) + return 'minor'; + }); + await processor.processGroups(); + + // All projects should be bumped to the same version as required by the first project (the project dependency relationships do not affect this) + expect(mockDeriveSpecifierFromConventionalCommits).toHaveBeenCalledTimes(1); + expect(readJson(tree, 'projectD/package.json')).toMatchInlineSnapshot(` + { + "dependencies": { + "projectE": "1.1.0", + }, + "name": "projectD", + "version": "1.1.0", + } + `); + expect(readJson(tree, 'projectE/package.json')).toMatchInlineSnapshot(` + { + "dependencies": { + "projectF": "1.1.0", + }, + "name": "projectE", + "version": "1.1.0", + } + `); + expect(readJson(tree, 'projectF/package.json')).toMatchInlineSnapshot(` + { + "name": "projectF", + "version": "1.1.0", + } + `); + }); + + it('should handle a single default group with fixed versioning, with project dependency relationships when using scoped packages which do not match their project names', async () => { + const { + nxReleaseConfig, + projectGraph, + releaseGroups, + releaseGroupToFilteredProjects, + filters, + } = await createNxReleaseConfigAndPopulateWorkspace( + tree, + ` + __default__ ({ "projectsRelationship": "fixed" }): + - projectD@1.0.0 [js:@myorg/projectD] + -> depends on projectE + - projectE@1.0.0 [js:@myorg/projectE] + -> depends on projectF + - projectF@1.0.0 [js:@myorg/projectF] + `, + { + version: { + conventionalCommits: true, + }, + }, + mockResolveCurrentVersion + ); + + const processor = new ReleaseGroupProcessor( + tree, + projectGraph, + nxReleaseConfig, + releaseGroups, + releaseGroupToFilteredProjects, + { + dryRun: false, + verbose: false, + firstRelease: false, + preid: undefined, + filters, + } + ); + await processor.init(); + + mockDeriveSpecifierFromConventionalCommits.mockImplementation(() => { + // This should only be called once for this group (for the first project) + return 'minor'; + }); + await processor.processGroups(); + + // All projects should be bumped to the same version as required by the first project (the project dependency relationships do not affect this) + expect(mockDeriveSpecifierFromConventionalCommits).toHaveBeenCalledTimes(1); + expect(readJson(tree, 'projectD/package.json')).toMatchInlineSnapshot(` + { + "dependencies": { + "@myorg/projectE": "1.1.0", + }, + "name": "@myorg/projectD", + "version": "1.1.0", + } + `); + expect(readJson(tree, 'projectE/package.json')).toMatchInlineSnapshot(` + { + "dependencies": { + "@myorg/projectF": "1.1.0", + }, + "name": "@myorg/projectE", + "version": "1.1.0", + } + `); + expect(readJson(tree, 'projectF/package.json')).toMatchInlineSnapshot(` + { + "name": "@myorg/projectF", + "version": "1.1.0", + } + `); + }); + + it('should handle a single default group with independent versioning, with no project dependency relationships', async () => { + const { + nxReleaseConfig, + projectGraph, + releaseGroups, + releaseGroupToFilteredProjects, + filters, + } = await createNxReleaseConfigAndPopulateWorkspace( + tree, + ` + __default__ ({ "projectsRelationship": "independent" }): + - projectG@1.0.0 [js] + - projectH@2.0.0 [js] + - projectI@3.0.0 [js] + `, + { + version: { + conventionalCommits: true, + }, + }, + mockResolveCurrentVersion + ); + + const processor = new ReleaseGroupProcessor( + tree, + projectGraph, + nxReleaseConfig, + releaseGroups, + releaseGroupToFilteredProjects, + { + dryRun: false, + verbose: false, + firstRelease: false, + preid: undefined, + filters, + } + ); + + await processor.init(); + + mockDeriveSpecifierFromConventionalCommits.mockImplementation( + (_, __, ___, ____, { name: projectName }) => { + if (projectName === 'projectG') return 'minor'; + if (projectName === 'projectH') return 'patch'; + return 'none'; + } + ); + await processor.processGroups(); + + // Each project should have its own specifier independently resolved + expect(mockDeriveSpecifierFromConventionalCommits).toHaveBeenCalledTimes(3); + + // The new versions are based on the individually derived specifiers and previous versions + expect(readJson(tree, 'projectG/package.json')).toMatchInlineSnapshot(` + { + "name": "projectG", + "version": "1.1.0", + } + `); + expect(readJson(tree, 'projectH/package.json')).toMatchInlineSnapshot(` + { + "name": "projectH", + "version": "2.0.1", + } + `); + expect(readJson(tree, 'projectI/package.json')).toMatchInlineSnapshot(` + { + "name": "projectI", + "version": "3.0.0", + } + `); + }); + + describe('independent group with project dependency relationships within it', () => { + let processor: import('./release-group-processor').ReleaseGroupProcessor; + let tree: Tree; + + // Share a tree, project graph and release groups setup between these tests + beforeEach(async () => { + tree = createTreeWithEmptyWorkspace(); + const { + nxReleaseConfig, + projectGraph, + releaseGroups, + releaseGroupToFilteredProjects, + filters, + } = await createNxReleaseConfigAndPopulateWorkspace( + tree, + ` + __default__ ({ "projectsRelationship": "independent" }): + - projectJ@1.0.0 [js] + -> depends on projectK + - projectK@2.0.0 [js] + -> depends on projectL + - projectL@3.0.0 [js] + `, + { + version: { + conventionalCommits: true, + }, + }, + mockResolveCurrentVersion + ); + + processor = new ReleaseGroupProcessor( + tree, + projectGraph, + nxReleaseConfig, + releaseGroups, + releaseGroupToFilteredProjects, + { + dryRun: false, + verbose: false, + firstRelease: false, + preid: undefined, + filters, + } + ); + await processor.init(); + }); + + it('should not bump anything when no specifiers are resolved', async () => { + mockDeriveSpecifierFromConventionalCommits.mockImplementation(() => { + return 'none'; + }); + await processor.processGroups(); + + // Called for each project + expect(mockDeriveSpecifierFromConventionalCommits).toHaveBeenCalledTimes( + 3 + ); + + // All projects unchanged + expect(readJson(tree, 'projectJ/package.json')).toMatchInlineSnapshot(` + { + "dependencies": { + "projectK": "2.0.0", + }, + "name": "projectJ", + "version": "1.0.0", + } + `); + expect(readJson(tree, 'projectK/package.json')).toMatchInlineSnapshot(` + { + "dependencies": { + "projectL": "3.0.0", + }, + "name": "projectK", + "version": "2.0.0", + } + `); + expect(readJson(tree, 'projectL/package.json')).toMatchInlineSnapshot(` + { + "name": "projectL", + "version": "3.0.0", + } + `); + }); + + it('should only bump projects based on their own specifiers if no dependencies have resolved specifiers', async () => { + mockDeriveSpecifierFromConventionalCommits.mockImplementation( + (_, __, ___, ____, { name: projectName }) => { + // Only projectJ has a specifier, it is not depended on by anything else + if (projectName === 'projectJ') return 'minor'; + return 'none'; + } + ); + await processor.processGroups(); + + // Called for each project + expect(mockDeriveSpecifierFromConventionalCommits).toHaveBeenCalledTimes( + 3 + ); + + // Only projectJ is bumped + expect(readJson(tree, 'projectJ/package.json')).toMatchInlineSnapshot(` + { + "dependencies": { + "projectK": "2.0.0", + }, + "name": "projectJ", + "version": "1.1.0", + } + `); + expect(readJson(tree, 'projectK/package.json')).toMatchInlineSnapshot(` + { + "dependencies": { + "projectL": "3.0.0", + }, + "name": "projectK", + "version": "2.0.0", + } + `); + expect(readJson(tree, 'projectL/package.json')).toMatchInlineSnapshot(` + { + "name": "projectL", + "version": "3.0.0", + } + `); + }); + + it('should handle projects with mixed dependency types', async () => { + const { + nxReleaseConfig, + projectGraph, + releaseGroups, + releaseGroupToFilteredProjects, + filters, + } = await createNxReleaseConfigAndPopulateWorkspace( + tree, + ` + __default__ ({ "projectsRelationship": "independent" }): + - projectM@1.0.0 [js] + -> depends on projectN + -> depends on projectO {devDependencies} + -> depends on projectP {peerDependencies} + - projectN@1.0.0 [js] + - projectO@1.0.0 [js] + - projectP@1.0.0 [js] + `, + { + version: { + conventionalCommits: true, + }, + }, + mockResolveCurrentVersion + ); + + const processor = new ReleaseGroupProcessor( + tree, + projectGraph, + nxReleaseConfig, + releaseGroups, + releaseGroupToFilteredProjects, + { + dryRun: false, + verbose: false, + firstRelease: false, + preid: undefined, + filters, + } + ); + + await processor.init(); + + mockDeriveSpecifierFromConventionalCommits.mockImplementation( + (_, __, ___, ____, { name: projectName }) => { + if (projectName === 'projectM') return 'minor'; + if (projectName === 'projectN') return 'patch'; + if (projectName === 'projectO') return 'major'; + return 'none'; + } + ); + await processor.processGroups(); + + // projectM is bumped by minor, and its dependencies are updated accordingly + expect(readJson(tree, 'projectM/package.json')).toMatchInlineSnapshot(` + { + "dependencies": { + "projectN": "1.0.1", + }, + "devDependencies": { + "projectO": "2.0.0", + }, + "name": "projectM", + "peerDependencies": { + "projectP": "1.0.0", + }, + "version": "1.1.0", + } + `); + // projectN is bumped by patch + expect(readJson(tree, 'projectN/package.json')).toMatchInlineSnapshot(` + { + "name": "projectN", + "version": "1.0.1", + } + `); + // projectO is bumped by major + expect(readJson(tree, 'projectO/package.json')).toMatchInlineSnapshot(` + { + "name": "projectO", + "version": "2.0.0", + } + `); + // projectP is not bumped + expect(readJson(tree, 'projectP/package.json')).toMatchInlineSnapshot(` + { + "name": "projectP", + "version": "1.0.0", + } + `); + }); + + describe('mixed ecosystems', () => { + it('should handle a single fixed group containing both rust and js projects with separate dependency relationships', async () => { + const { + nxReleaseConfig, + projectGraph, + releaseGroups, + releaseGroupToFilteredProjects, + filters, + } = await createNxReleaseConfigAndPopulateWorkspace( + tree, + ` + __default__ ({ "projectsRelationship": "fixed" }): + - rustLibA@1.0.0 [rust] + - rustLibB@1.0.0 [rust] + -> depends on rustLibA + - jsPackageX@1.0.0 [js] + - jsPackageY@1.0.0 [js] + -> depends on jsPackageX + `, + { + version: { + conventionalCommits: true, + }, + }, + mockResolveCurrentVersion + ); + + // Initial state of rustLibA + expect(tree.read('rustLibA/Cargo.toml', 'utf-8')) + .toMatchInlineSnapshot(` + " + [package] + name = 'rustLibA' + version = '1.0.0' + " + `); + + // Initial state of rustLibB + expect(tree.read('rustLibB/Cargo.toml', 'utf-8')) + .toMatchInlineSnapshot(` + " + [package] + name = 'rustLibB' + version = '1.0.0' + + [dependencies] + rustLibA = { version = '1.0.0' } + " + `); + + const processor = new ReleaseGroupProcessor( + tree, + projectGraph, + nxReleaseConfig, + releaseGroups, + releaseGroupToFilteredProjects, + { + dryRun: false, + verbose: false, + firstRelease: false, + preid: undefined, + filters, + } + ); + await processor.init(); + + mockDeriveSpecifierFromConventionalCommits.mockImplementation(() => { + // This should only be called once for this group (for the first project) + return 'minor'; + }); + await processor.processGroups(); + + // All projects should be bumped to the same version as required by the first project + expect( + mockDeriveSpecifierFromConventionalCommits + ).toHaveBeenCalledTimes(1); + + // rustLibA is bumped by minor + expect(tree.read('rustLibA/Cargo.toml', 'utf-8')) + .toMatchInlineSnapshot(` + " + [package] + name = 'rustLibA' + version = '1.1.0' + " + `); + + // rustLibB is bumped by minor, and its dependency on rustLibA is updated + expect(tree.read('rustLibB/Cargo.toml', 'utf-8')) + .toMatchInlineSnapshot(` + " + [package] + name = 'rustLibB' + version = '1.1.0' + + [dependencies] + rustLibA = '1.1.0' + " + `); + + // jsPackageX is bumped by minor + expect(readJson(tree, 'jsPackageX/package.json')) + .toMatchInlineSnapshot(` + { + "name": "jsPackageX", + "version": "1.1.0", + } + `); + + // jsPackageY is bumped by minor, and its dependency on jsPackageX is updated + expect(readJson(tree, 'jsPackageY/package.json')) + .toMatchInlineSnapshot(` + { + "dependencies": { + "jsPackageX": "1.1.0", + }, + "name": "jsPackageY", + "version": "1.1.0", + } + `); + }); + + it('should handle a single independent group containing both rust and js projects with separate dependency relationships', async () => { + const { + nxReleaseConfig, + projectGraph, + releaseGroups, + releaseGroupToFilteredProjects, + filters, + } = await createNxReleaseConfigAndPopulateWorkspace( + tree, + ` + __default__ ({ "projectsRelationship": "independent" }): + - rustLibA@1.0.0 [rust] + - rustLibB@2.0.0 [rust] + -> depends on rustLibA + - jsPackageX@3.0.0 [js] + - jsPackageY@4.0.0 [js] + -> depends on jsPackageX + `, + { + version: { + conventionalCommits: true, + }, + }, + mockResolveCurrentVersion + ); + + // Initial state of rustLibA + expect(tree.read('rustLibA/Cargo.toml', 'utf-8')) + .toMatchInlineSnapshot(` + " + [package] + name = 'rustLibA' + version = '1.0.0' + " + `); + + // Initial state of rustLibB + expect(tree.read('rustLibB/Cargo.toml', 'utf-8')) + .toMatchInlineSnapshot(` + " + [package] + name = 'rustLibB' + version = '2.0.0' + + [dependencies] + rustLibA = { version = '1.0.0' } + " + `); + + const processor = new ReleaseGroupProcessor( + tree, + projectGraph, + nxReleaseConfig, + releaseGroups, + releaseGroupToFilteredProjects, + { + dryRun: false, + verbose: false, + firstRelease: false, + preid: undefined, + filters, + } + ); + await processor.init(); + + mockDeriveSpecifierFromConventionalCommits.mockImplementation( + (_, __, ___, ____, { name: projectName }) => { + if (projectName === 'rustLibA') return 'minor'; + if (projectName === 'rustLibB') return 'patch'; + if (projectName === 'jsPackageX') return 'major'; + if (projectName === 'jsPackageY') return 'minor'; + return 'none'; + } + ); + await processor.processGroups(); + + // Called for each project + expect( + mockDeriveSpecifierFromConventionalCommits + ).toHaveBeenCalledTimes(4); + + // rustLibA is bumped by minor + expect(tree.read('rustLibA/Cargo.toml', 'utf-8')) + .toMatchInlineSnapshot(` + " + [package] + name = 'rustLibA' + version = '1.1.0' + " + `); + + // rustLibB is bumped by updateDependents default of "patch", and its dependency on rustLibA is updated + expect(tree.read('rustLibB/Cargo.toml', 'utf-8')) + .toMatchInlineSnapshot(` + " + [package] + name = 'rustLibB' + version = '2.0.1' + + [dependencies] + rustLibA = '1.1.0' + " + `); + + // jsPackageX is bumped by major + expect(readJson(tree, 'jsPackageX/package.json')) + .toMatchInlineSnapshot(` + { + "name": "jsPackageX", + "version": "4.0.0", + } + `); + + // jsPackageY is bumped by minor, and its dependency on jsPackageX is updated + expect(readJson(tree, 'jsPackageY/package.json')) + .toMatchInlineSnapshot(` + { + "dependencies": { + "jsPackageX": "4.0.0", + }, + "name": "jsPackageY", + "version": "4.1.0", + } + `); + }); + }); + + describe('updateDependents', () => { + it('should bump projects if their dependencies have resolved specifiers, even when they have not resolved their own specifiers, when updateDependents is set to its default of "auto" - SINGLE LEVEL DEPENDENCY', async () => { + mockDeriveSpecifierFromConventionalCommits.mockImplementation( + (_, __, ___, ____, { name: projectName }) => { + // Only projectK has a specifier. This should cause both itself and projectJ to be bumped. + if (projectName === 'projectK') return 'minor'; + return 'none'; + } + ); + await processor.processGroups(); + + // Called for each project + expect( + mockDeriveSpecifierFromConventionalCommits + ).toHaveBeenCalledTimes(3); + + // projectJ is bumped by the default updateDependents bump of "patch" + expect(readJson(tree, 'projectJ/package.json')).toMatchInlineSnapshot(` + { + "dependencies": { + "projectK": "2.1.0", + }, + "name": "projectJ", + "version": "1.0.1", + } + `); + // projectK is bumped based on its own specifier of minor + expect(readJson(tree, 'projectK/package.json')).toMatchInlineSnapshot(` + { + "dependencies": { + "projectL": "3.0.0", + }, + "name": "projectK", + "version": "2.1.0", + } + `); + // projectL is not bumped because it has no specifier and does not depend on projects that do + expect(readJson(tree, 'projectL/package.json')).toMatchInlineSnapshot(` + { + "name": "projectL", + "version": "3.0.0", + } + `); + }); + + it('should bump projects if their dependencies have resolved specifiers, even when they have not resolved their own specifiers, when updateDependents is set to its default of "auto" - TRANSITIVE DEPENDENCY', async () => { + // This time bump projectL which should cause a cascade of bumps across projectK and projectJ + mockDeriveSpecifierFromConventionalCommits.mockImplementation( + (_, __, ___, ____, { name: projectName }) => { + // Only projectL has a specifier. This should cause itself to be bumped by a major, and projectK and projectJ to be bumped by a patch. + if (projectName === 'projectL') return 'major'; + return 'none'; + } + ); + await processor.processGroups(); + + // Called for each project + expect( + mockDeriveSpecifierFromConventionalCommits + ).toHaveBeenCalledTimes(3); + + // projectJ is bumped by the default updateDependents bump of "patch" + expect(readJson(tree, 'projectJ/package.json')).toMatchInlineSnapshot(` + { + "dependencies": { + "projectK": "2.0.1", + }, + "name": "projectJ", + "version": "1.0.1", + } + `); + // projectK is bumped by the default updateDependents bump of "patch" + expect(readJson(tree, 'projectK/package.json')).toMatchInlineSnapshot(` + { + "dependencies": { + "projectL": "4.0.0", + }, + "name": "projectK", + "version": "2.0.1", + } + `); + // projectL is bumped by a major + expect(readJson(tree, 'projectL/package.json')).toMatchInlineSnapshot(` + { + "name": "projectL", + "version": "4.0.0", + } + `); + }); + + it('should bump projects if their dependencies have resolved specifiers, even when they have not resolved their own specifiers, when updateDependents is set to its default of "auto" - TRANSITIVE DEPENDENCY MANY LEVELS AWAY', async () => { + const { + nxReleaseConfig, + projectGraph, + releaseGroups, + releaseGroupToFilteredProjects, + filters, + } = await createNxReleaseConfigAndPopulateWorkspace( + tree, + ` + __default__ ({ "projectsRelationship": "independent" }): + - projectJ@1.0.0 [js] + -> depends on projectK {devDependencies} + - projectK@2.0.0 [js] + -> depends on projectL {optionalDependencies} + - projectL@3.0.0 [js] + -> depends on projectM {dependencies} + - projectM@4.0.0 [js] + -> depends on projectN {peerDependencies} + - projectN@5.0.0 [js] + -> depends on projectO {dependencies} + - projectO@6.0.0 [js] + `, + { + version: { + conventionalCommits: true, + }, + }, + mockResolveCurrentVersion + ); + + processor = new ReleaseGroupProcessor( + tree, + projectGraph, + nxReleaseConfig, + releaseGroups, + releaseGroupToFilteredProjects, + { + dryRun: false, + verbose: false, + firstRelease: false, + preid: undefined, + filters, + } + ); + await processor.init(); + + mockDeriveSpecifierFromConventionalCommits.mockImplementation( + (_, __, ___, ____, { name: projectName }) => { + // only projectO has a specifier, all other projects have no specifier but should be bumped as a cascading side effect + if (projectName === 'projectO') return 'major'; + return 'none'; + } + ); + await processor.processGroups(); + + // Called for each project + expect( + mockDeriveSpecifierFromConventionalCommits + ).toHaveBeenCalledTimes(6); + + // projectJ is bumped because of its dependency on projectK + expect(readJson(tree, 'projectJ/package.json')).toMatchInlineSnapshot(` + { + "devDependencies": { + "projectK": "2.0.1", + }, + "name": "projectJ", + "version": "1.0.1", + } + `); + // projectK is bumped because of its dependency on projectL + expect(readJson(tree, 'projectK/package.json')).toMatchInlineSnapshot(` + { + "name": "projectK", + "optionalDependencies": { + "projectL": "3.0.1", + }, + "version": "2.0.1", + } + `); + // projectL is bumped because of its dependency on projectM + expect(readJson(tree, 'projectL/package.json')).toMatchInlineSnapshot(` + { + "dependencies": { + "projectM": "4.0.1", + }, + "name": "projectL", + "version": "3.0.1", + } + `); + // projectM is bumped because of its dependency on projectN + expect(readJson(tree, 'projectM/package.json')).toMatchInlineSnapshot(` + { + "name": "projectM", + "peerDependencies": { + "projectN": "5.0.1", + }, + "version": "4.0.1", + } + `); + // projectN is bumped because of its dependency on projectO + expect(readJson(tree, 'projectN/package.json')).toMatchInlineSnapshot(` + { + "dependencies": { + "projectO": "7.0.0", + }, + "name": "projectN", + "version": "5.0.1", + } + `); + // projectO is bumped because of its own specifier of major + expect(readJson(tree, 'projectO/package.json')).toMatchInlineSnapshot(` + { + "name": "projectO", + "version": "7.0.0", + } + `); + }); + + it('should bump projects by the maximum of their own specifier and the updateDependents bump but not both, when updateDependents is set to its default of "auto"', async () => { + mockDeriveSpecifierFromConventionalCommits.mockImplementation( + (_, __, ___, ____, { name: projectName }) => { + // projectL has a specifier, this will cause projectK and projectJ to need to be bumped. + if (projectName === 'projectL') return 'major'; + // projectK has its own specifier which is higher than the default updateDependents bump of "patch", so this is what should be applied + if (projectName === 'projectK') return 'minor'; + // projectJ also has its own specifier which is higher than the default updateDependents bump of "patch", so this is what should be applied + if (projectName === 'projectJ') return 'major'; + return 'none'; + } + ); + await processor.processGroups(); + + // Called for each project + expect( + mockDeriveSpecifierFromConventionalCommits + ).toHaveBeenCalledTimes(3); + + // projectJ is bumped based on its own specifier of major, the patch bump from updateDependents is not applied on top + expect(readJson(tree, 'projectJ/package.json')).toMatchInlineSnapshot(` + { + "dependencies": { + "projectK": "2.1.0", + }, + "name": "projectJ", + "version": "2.0.0", + } + `); + // projectK is bumped based on its own specifier of minor, the patch bump from updateDependents is not applied on top + expect(readJson(tree, 'projectK/package.json')).toMatchInlineSnapshot(` + { + "dependencies": { + "projectL": "4.0.0", + }, + "name": "projectK", + "version": "2.1.0", + } + `); + // projectL gets a major bump via its own specifier + expect(readJson(tree, 'projectL/package.json')).toMatchInlineSnapshot(` + { + "name": "projectL", + "version": "4.0.0", + } + `); + }); + + it('should not bump dependents if their dependencies have resolved specifiers, if updateDependents is set to "never" - SINGLE LEVEL DEPENDENCY', async () => { + const { + nxReleaseConfig, + projectGraph, + releaseGroups, + releaseGroupToFilteredProjects, + filters, + } = await createNxReleaseConfigAndPopulateWorkspace( + tree, + ` + __default__ ({ "projectsRelationship": "independent" }): + - projectJ@1.0.0 [js] + -> depends on projectK + - projectK@2.0.0 [js] + -> depends on projectL + - projectL@3.0.0 [js] + `, + { + version: { + conventionalCommits: true, + updateDependents: 'never', + }, + }, + mockResolveCurrentVersion + ); + + processor = new ReleaseGroupProcessor( + tree, + projectGraph, + nxReleaseConfig, + releaseGroups, + releaseGroupToFilteredProjects, + { + dryRun: false, + verbose: false, + firstRelease: false, + preid: undefined, + filters, + } + ); + await processor.init(); + + mockDeriveSpecifierFromConventionalCommits.mockImplementation( + (_, __, ___, ____, { name: projectName }) => { + // Only projectK has a specifier. This should cause only itself to be bumped and not projectJ, because updateDependents is set to "never" + if (projectName === 'projectK') return 'minor'; + return 'none'; + } + ); + await processor.processGroups(); + + // Called for each project + expect( + mockDeriveSpecifierFromConventionalCommits + ).toHaveBeenCalledTimes(3); + + // projectJ is not bumped because updateDependents is set to "never", and the dependency on projectK is therefore also not updated + expect(readJson(tree, 'projectJ/package.json')).toMatchInlineSnapshot(` + { + "dependencies": { + "projectK": "2.0.0", + }, + "name": "projectJ", + "version": "1.0.0", + } + `); + // projectK is bumped based on its own specifier of minor + expect(readJson(tree, 'projectK/package.json')).toMatchInlineSnapshot(` + { + "dependencies": { + "projectL": "3.0.0", + }, + "name": "projectK", + "version": "2.1.0", + } + `); + // projectL is not bumped + expect(readJson(tree, 'projectL/package.json')).toMatchInlineSnapshot(` + { + "name": "projectL", + "version": "3.0.0", + } + `); + }); + + it('should not bump dependents if their dependencies have resolved specifiers, if updateDependents is set to "never" - TRANSITIVE DEPENDENCY', async () => { + const { + nxReleaseConfig, + projectGraph, + releaseGroups, + releaseGroupToFilteredProjects, + filters, + } = await createNxReleaseConfigAndPopulateWorkspace( + tree, + ` + __default__ ({ "projectsRelationship": "independent" }): + - projectJ@1.0.0 [js] + -> depends on projectK + - projectK@2.0.0 [js] + -> depends on projectL + - projectL@3.0.0 [js] + `, + { + version: { + conventionalCommits: true, + updateDependents: 'never', + }, + }, + mockResolveCurrentVersion + ); + + processor = new ReleaseGroupProcessor( + tree, + projectGraph, + nxReleaseConfig, + releaseGroups, + releaseGroupToFilteredProjects, + { + dryRun: false, + verbose: false, + firstRelease: false, + preid: undefined, + filters, + } + ); + await processor.init(); + + // This time bump projectL which would otherwise cause a cascade of bumps across projectK and projectJ, but should not here because updateDependents is set to "never" + mockDeriveSpecifierFromConventionalCommits.mockImplementation( + (_, __, ___, ____, { name: projectName }) => { + // Only projectL has a specifier + if (projectName === 'projectL') return 'major'; + return 'none'; + } + ); + await processor.processGroups(); + + // Called for each project + expect( + mockDeriveSpecifierFromConventionalCommits + ).toHaveBeenCalledTimes(3); + + // projectJ is not bumped because updateDependents is set to "never" + expect(readJson(tree, 'projectJ/package.json')).toMatchInlineSnapshot(` + { + "dependencies": { + "projectK": "2.0.0", + }, + "name": "projectJ", + "version": "1.0.0", + } + `); + // projectK is not bumped because updateDependents is set to "never", and the dependency on projectL is therefore also not updated + expect(readJson(tree, 'projectK/package.json')).toMatchInlineSnapshot(` + { + "dependencies": { + "projectL": "3.0.0", + }, + "name": "projectK", + "version": "2.0.0", + } + `); + // projectL is bumped by a major + expect(readJson(tree, 'projectL/package.json')).toMatchInlineSnapshot(` + { + "name": "projectL", + "version": "4.0.0", + } + `); + }); + }); + }); + + /** + * NOTE: Fundamental issues with filters, like trying to filter to a single project within a fixed release group, + * will have already been caught and handled by filterReleaseGroups(), so that is not repeated here. + */ + describe('filters', () => { + it('should filter out projects with no dependency relationships within a single independent release group based on the provided user filter', async () => { + const { + nxReleaseConfig, + projectGraph, + releaseGroups, + releaseGroupToFilteredProjects, + filters, + } = await createNxReleaseConfigAndPopulateWorkspace( + tree, + ` + __default__ ({ "projectsRelationship": "independent" }): + - projectJ@1.0.0 [js] + - projectK@2.0.0 [js] + - projectL@3.0.0 [js] + `, + { + version: { + conventionalCommits: true, + }, + }, + mockResolveCurrentVersion, + { + // Apply the projects filter to only include projectJ in versioning + projects: ['projectJ'], + } + ); + + const processor = new ReleaseGroupProcessor( + tree, + projectGraph, + nxReleaseConfig, + releaseGroups, + releaseGroupToFilteredProjects, + { + dryRun: false, + verbose: false, + firstRelease: false, + preid: undefined, + filters, + } + ); + await processor.init(); + + mockDeriveSpecifierFromConventionalCommits.mockImplementation( + () => 'minor' + ); + await processor.processGroups(); + + // The mock should only be called once, for the filtered projectJ + expect(mockDeriveSpecifierFromConventionalCommits).toHaveBeenCalledTimes( + 1 + ); + + // Only projectJ should be bumped + expect(readJson(tree, 'projectJ/package.json')).toMatchInlineSnapshot(` + { + "name": "projectJ", + "version": "1.1.0", + } + `); + // projectK should not be bumped + expect(readJson(tree, 'projectK/package.json')).toMatchInlineSnapshot(` + { + "name": "projectK", + "version": "2.0.0", + } + `); + // projectL should not be bumped + expect(readJson(tree, 'projectL/package.json')).toMatchInlineSnapshot(` + { + "name": "projectL", + "version": "3.0.0", + } + `); + }); + + it('should filter out projects when applying a user provided projects filter to independent projects which do have dependency relationships within a single independent release group, but which have no dependents', async () => { + const { + nxReleaseConfig, + projectGraph, + releaseGroups, + releaseGroupToFilteredProjects, + filters, + } = await createNxReleaseConfigAndPopulateWorkspace( + tree, + ` + __default__ ({ "projectsRelationship": "independent" }): + - projectJ@1.0.0 [js] + -> depends on projectK + - projectK@2.0.0 [js] + -> depends on projectL + - projectL@3.0.0 [js] + `, + { + version: { + conventionalCommits: true, + }, + }, + mockResolveCurrentVersion, + { + // Apply the projects filter to only include projectJ in versioning. + // projectJ does not have any DEPENDENTS, so it is safe to filter everything else out. + projects: ['projectJ'], + } + ); + + const processor = new ReleaseGroupProcessor( + tree, + projectGraph, + nxReleaseConfig, + releaseGroups, + releaseGroupToFilteredProjects, + { + dryRun: false, + verbose: false, + firstRelease: false, + preid: undefined, + filters, + } + ); + await processor.init(); + + mockDeriveSpecifierFromConventionalCommits.mockImplementation( + () => 'minor' + ); + await processor.processGroups(); + + // The mock should only be called once, for the filtered projectJ + expect(mockDeriveSpecifierFromConventionalCommits).toHaveBeenCalledTimes( + 1 + ); + + // Only projectJ should be bumped + expect(readJson(tree, 'projectJ/package.json')).toMatchInlineSnapshot(` + { + "dependencies": { + "projectK": "2.0.0", + }, + "name": "projectJ", + "version": "1.1.0", + } + `); + // projectK should not be bumped + expect(readJson(tree, 'projectK/package.json')).toMatchInlineSnapshot(` + { + "dependencies": { + "projectL": "3.0.0", + }, + "name": "projectK", + "version": "2.0.0", + } + `); + // projectL should not be bumped + expect(readJson(tree, 'projectL/package.json')).toMatchInlineSnapshot(` + { + "name": "projectL", + "version": "3.0.0", + } + `); + }); + + it('should filter out projects when applying a user provided projects filter to independent projects which do have dependency relationships within a single independent release group, and which have dependents, as long as updateDependents is set to "never"', async () => { + const { + nxReleaseConfig, + projectGraph, + releaseGroups, + releaseGroupToFilteredProjects, + filters, + } = await createNxReleaseConfigAndPopulateWorkspace( + tree, + ` + __default__ ({ "projectsRelationship": "independent" }): + - projectJ@1.0.0 [js] + -> depends on projectK + - projectK@2.0.0 [js] + -> depends on projectL + - projectL@3.0.0 [js] + `, + { + version: { + conventionalCommits: true, + updateDependents: 'never', + }, + }, + mockResolveCurrentVersion, + { + // Apply the projects filter to only include projectL in versioning. + projects: ['projectL'], + } + ); + + const processor = new ReleaseGroupProcessor( + tree, + projectGraph, + nxReleaseConfig, + releaseGroups, + releaseGroupToFilteredProjects, + { + dryRun: false, + verbose: false, + firstRelease: false, + preid: undefined, + filters, + } + ); + await processor.init(); + + mockDeriveSpecifierFromConventionalCommits.mockImplementation( + () => 'minor' + ); + await processor.processGroups(); + + // The mock should only be called once, for the filtered projectL + expect(mockDeriveSpecifierFromConventionalCommits).toHaveBeenCalledTimes( + 1 + ); + // projectJ should not be bumped because updateDependents is set to "never" + expect(readJson(tree, 'projectJ/package.json')).toMatchInlineSnapshot(` + { + "dependencies": { + "projectK": "2.0.0", + }, + "name": "projectJ", + "version": "1.0.0", + } + `); + // projectK should not be bumped because updateDependents is set to "never" + expect(readJson(tree, 'projectK/package.json')).toMatchInlineSnapshot(` + { + "dependencies": { + "projectL": "3.0.0", + }, + "name": "projectK", + "version": "2.0.0", + } + `); + // Only projectL should be bumped + expect(readJson(tree, 'projectL/package.json')).toMatchInlineSnapshot(` + { + "name": "projectL", + "version": "3.1.0", + } + `); + }); + }); + + describe('non-semver versioning', () => { + it('should handle non-semver versioning for a simple fixed release group with no dependencies', async () => { + const { + nxReleaseConfig, + projectGraph, + releaseGroups, + releaseGroupToFilteredProjects, + filters, + } = await createNxReleaseConfigAndPopulateWorkspace( + tree, + // projectB also intentionally has no current version + ` + __default__ ({ "projectsRelationship": "fixed" }): + - projectA@1.0 [non-semver] + - projectB [non-semver] + `, + {}, + mockResolveCurrentVersion + ); + + expect(tree.read('projectA/version.txt', 'utf-8')).toMatchInlineSnapshot( + `"1.0"` + ); + // projectB has no version currently + expect(tree.read('projectB/version.txt', 'utf-8')).toMatchInlineSnapshot( + `""` + ); + + const processor = new ReleaseGroupProcessor( + tree, + projectGraph, + nxReleaseConfig, + releaseGroups, + releaseGroupToFilteredProjects, + { + dryRun: false, + verbose: false, + firstRelease: false, + preid: undefined, + filters, + userGivenSpecifier: '2.0', + } + ); + await processor.init(); + await processor.processGroups(); + + expect(tree.read('projectA/version.txt', 'utf-8')).toMatchInlineSnapshot( + `"2.0"` + ); + expect(tree.read('projectB/version.txt', 'utf-8')).toMatchInlineSnapshot( + `"2.0"` + ); + }); + + it('should handle non-semver versioning for a simple independent release group with no dependencies', async () => { + const { + nxReleaseConfig, + projectGraph, + releaseGroups, + releaseGroupToFilteredProjects, + filters, + } = await createNxReleaseConfigAndPopulateWorkspace( + tree, + ` + __default__ ({ "projectsRelationship": "independent" }): + - projectA@abc123 [non-semver] + - projectB@2099-01-01.build1 [non-semver] + `, + {}, + mockResolveCurrentVersion, + { + // Update version projectB + projects: ['projectB'], + } + ); + + expect(tree.read('projectA/version.txt', 'utf-8')).toMatchInlineSnapshot( + `"abc123"` + ); + expect(tree.read('projectB/version.txt', 'utf-8')).toMatchInlineSnapshot( + `"2099-01-01.build1"` + ); + + const processor = new ReleaseGroupProcessor( + tree, + projectGraph, + nxReleaseConfig, + releaseGroups, + releaseGroupToFilteredProjects, + { + dryRun: false, + verbose: false, + firstRelease: false, + preid: undefined, + filters, + userGivenSpecifier: '2099-01-01.build2', + } + ); + await processor.init(); + await processor.processGroups(); + + // Unchanged + expect(tree.read('projectA/version.txt', 'utf-8')).toMatchInlineSnapshot( + `"abc123"` + ); + // New version + expect(tree.read('projectB/version.txt', 'utf-8')).toMatchInlineSnapshot( + `"2099-01-01.build2"` + ); + }); + + it('should handle non-semver versioning for a simple independent release group with dependencies', async () => { + const { + nxReleaseConfig, + projectGraph, + releaseGroups, + releaseGroupToFilteredProjects, + filters, + } = await createNxReleaseConfigAndPopulateWorkspace( + tree, + ` + __default__ ({ "projectsRelationship": "independent" }): + - projectA@abc123 [non-semver] + -> depends on projectB + - projectB@2099-01-01.build1 [non-semver] + `, + {}, + mockResolveCurrentVersion, + { + // Update version projectB + projects: ['projectB'], + } + ); + + expect(tree.read('projectA/version.txt', 'utf-8')).toMatchInlineSnapshot( + `"abc123"` + ); + expect(tree.read('projectB/version.txt', 'utf-8')).toMatchInlineSnapshot( + `"2099-01-01.build1"` + ); + + const processor = new ReleaseGroupProcessor( + tree, + projectGraph, + nxReleaseConfig, + releaseGroups, + releaseGroupToFilteredProjects, + { + dryRun: false, + verbose: false, + firstRelease: false, + preid: undefined, + filters, + userGivenSpecifier: '2099-01-01.build2', + } + ); + await processor.init(); + await processor.processGroups(); + + // projectA changed its version in some arbitrary way as a side effect of the dependency bump (as dictated by its version actions implementation) + expect(tree.read('projectA/version.txt', 'utf-8')).toMatchInlineSnapshot( + `"{SOME_NEW_VERSION_DERIVED_AS_A_SIDE_EFFECT_OF_DEPENDENCY_BUMP}"` + ); + // New version + expect(tree.read('projectB/version.txt', 'utf-8')).toMatchInlineSnapshot( + `"2099-01-01.build2"` + ); + }); + }); +}); diff --git a/packages/nx/src/command-line/release/version/release-group-processor.ts b/packages/nx/src/command-line/release/version/release-group-processor.ts new file mode 100644 index 0000000000..f8c441a330 --- /dev/null +++ b/packages/nx/src/command-line/release/version/release-group-processor.ts @@ -0,0 +1,1635 @@ +import * as semver from 'semver'; +import { NxReleaseVersionV2Configuration } from '../../../config/nx-json'; +import { + ProjectGraph, + ProjectGraphProjectNode, +} from '../../../config/project-graph'; +import { Tree } from '../../../generators/tree'; +import { + IMPLICIT_DEFAULT_RELEASE_GROUP, + type NxReleaseConfig, +} from '../config/config'; +import type { ReleaseGroupWithName } from '../config/filter-release-groups'; +import { getLatestGitTagForPattern } from '../utils/git'; +import { resolveSemverSpecifierFromPrompt } from '../utils/resolve-semver-specifier'; +import type { VersionData, VersionDataEntry } from '../utils/shared'; +import { validReleaseVersionPrefixes } from '../version'; +import { deriveSpecifierFromConventionalCommits } from './derive-specifier-from-conventional-commits'; +import { deriveSpecifierFromVersionPlan } from './deriver-specifier-from-version-plans'; +import { ProjectLogger } from './project-logger'; +import { resolveCurrentVersion } from './resolve-current-version'; +import { topologicalSort } from './topological-sort'; +import { + AfterAllProjectsVersioned, + resolveVersionActionsForProject, + SemverBumpType, + VersionActions, +} from './version-actions'; + +/** + * The final configuration for a project after applying release group and project level overrides, + * as well as default values. This will be passed to the relevant version actions implementation, + * and referenced throughout the versioning process. + */ +export interface FinalConfigForProject { + specifierSource: NxReleaseVersionV2Configuration['specifierSource']; + currentVersionResolver: NxReleaseVersionV2Configuration['currentVersionResolver']; + currentVersionResolverMetadata: NxReleaseVersionV2Configuration['currentVersionResolverMetadata']; + fallbackCurrentVersionResolver: NxReleaseVersionV2Configuration['fallbackCurrentVersionResolver']; + versionPrefix: NxReleaseVersionV2Configuration['versionPrefix']; + preserveLocalDependencyProtocols: NxReleaseVersionV2Configuration['preserveLocalDependencyProtocols']; + versionActionsOptions: NxReleaseVersionV2Configuration['versionActionsOptions']; + manifestRootsToUpdate: NxReleaseVersionV2Configuration['manifestRootsToUpdate']; +} + +interface GroupNode { + group: ReleaseGroupWithName; + dependencies: Set; + dependents: Set; +} + +// Any semver version string such as "1.2.3" or "1.2.3-beta.1" +type SemverVersion = string; + +export const BUMP_TYPE_REASON_TEXT = { + DEPENDENCY_WAS_BUMPED: ', because a dependency was bumped, ', + USER_SPECIFIER: ', from the given specifier, ', + PROMPTED_USER_SPECIFIER: ', from the prompted specifier, ', + CONVENTIONAL_COMMITS: ', derived from conventional commits data, ', + VERSION_PLANS: ', read from version plan {versionPlanPath}, ', + DEPENDENCY_ACROSS_GROUPS_WAS_BUMPED: + ', because a dependency project belonging to another release group was bumped, ', + OTHER_PROJECT_IN_FIXED_GROUP_WAS_BUMPED_DUE_TO_DEPENDENCY: + ', because of a dependency-only bump to another project in the same fixed release group, ', +} as const; + +interface ReleaseGroupProcessorOptions { + dryRun: boolean; + verbose: boolean; + firstRelease: boolean; + preid: string; + // This could be a semver bump type, an exact semver version, or any arbitrary string to use as a new version + userGivenSpecifier?: string; + projectsToProcess?: string[]; + /** + * The optional results of applying the --project or --group filters. + * These will be empty if there is no filtering, or contain the subset of projects or groups that + * are being versioned if one of the (mutually exclusive) filters is set. + */ + filters: { + projects?: string[]; + groups?: string[]; + }; +} + +export class ReleaseGroupProcessor { + /** + * Stores the relationships between release groups, including their dependencies + * and dependents. This is used for determining processing order and propagating + * version changes between related groups. + */ + private groupGraph: Map = new Map(); + + /** + * Tracks which release groups have already been processed to avoid + * processing them multiple times. Used during the group traversal. + */ + private processedGroups: Set = new Set(); + + /** + * Keeps track of which projects have already had their versions bumped. + * This is used to avoid redundant version bumping and to determine which + * projects need their dependencies updated. + */ + private bumpedProjects: Set = new Set(); + + /** + * Cache of release groups sorted in topological order to ensure dependencies + * are processed before dependents. Computed once and reused throughout processing. + */ + private sortedReleaseGroups: string[] = []; + + /** + * Maps each release group to its projects sorted in topological order. + * Ensures projects are processed after their dependencies within each group. + */ + private sortedProjects: Map = new Map(); + + /** + * Track the unique afterAllProjectsVersioned functions involved in the current versioning process, + * so that we can ensure they are only invoked once per versioning execution. + */ + private uniqueAfterAllProjectsVersioned: Map< + string, + AfterAllProjectsVersioned + > = new Map(); + + /** + * Track the versionActions for each project so that we can invoke certain instance methods. + */ + private projectsToVersionActions: Map = new Map(); + + /** + * versionData that will ultimately be returned to the nx release version handler by getVersionData() + */ + private versionData: Map< + string, // project name + VersionDataEntry + > = new Map(); + + /** + * Set of all projects that are configured in the nx release config. + * Used to validate dependencies and identify projects that should be updated. + */ + private allProjectsConfiguredForNxRelease: Set = new Set(); + + /** + * Set of projects that will be processed in the current run. + * This is potentially a subset of allProjectsConfiguredForNxRelease based on filters + * and dependency relationships. + */ + private allProjectsToProcess: Set = new Set(); + + /** + * If the user provided a specifier at the time of versioning we store it here so that it can take priority + * over any configuration. + */ + private userGivenSpecifier: string | undefined; + + /** + * Caches the current version of each project to avoid repeated disk/registry/git tag lookups. + * Often used during new version calculation. Will be null if the current version resolver is set to 'none'. + */ + private cachedCurrentVersions: Map< + string, // project name + string | null // current version + > = new Map(); + + /** + * Caches git tag information for projects that resolve their version from git tags. + * This avoids performing expensive git operations multiple times for the same project. + */ + private cachedLatestMatchingGitTag: Map< + string, // project name + Awaited> + > = new Map(); + + /** + * Temporary storage for dependent project names while building the dependency graph. + * This is used as an intermediate step before creating the full dependent projects data. + */ + private tmpCachedDependentProjects: Map< + string, // project name + string[] // dependent project names + > = new Map(); + + /** + * Resolve the data regarding dependent projects for each project upfront so that it remains accurate + * even after updates are applied to manifests. + */ + private originalDependentProjectsPerProject: Map< + string, // project name + VersionDataEntry['dependentProjects'] + > = new Map(); + + /** + * In the case of fixed release groups that are configured to resolve the current version from a registry + * or a git tag, it would be a waste of time and resources to resolve the current version for each individual + * project, therefore we maintain a cache of the current version for each applicable fixed release group here. + */ + private currentVersionsPerFixedReleaseGroup: Map< + string, // release group name + { + currentVersion: string; + originatingProjectName: string; + logText: string; + } + > = new Map(); + + /** + * Cache of project loggers for each project. + */ + private projectLoggers: Map = new Map(); + + /** + * Track any version plan files that have been processed so that we can delete them after versioning is complete, + * while leaving any unprocessed files in place. + */ + private processedVersionPlanFiles = new Set(); + + /** + * Certain configuration options can be overridden at the project level, and otherwise fall back to the release group level. + * Many also have a specific default value if nothing is set at either level. To avoid applying this hierarchy for each project + * every time such a configuration option is needed, we cache the result per project here. + */ + private finalConfigsByProject: Map = new Map(); + + /** + * Maps each project to its release group for quick O(1) lookups. + * This avoids having to scan through all release groups to find a project. + */ + private projectToReleaseGroup: Map = new Map(); + + /** + * Maps each project to its dependents (projects that depend on it). + * This is the inverse of the projectToDependencies map and enables + * efficient lookup of dependent projects for propagating version changes. + */ + private projectToDependents: Map> = new Map(); + + /** + * Maps each project to its dependencies (projects it depends on). + * Used for building dependency graphs and determining processing order. + */ + private projectToDependencies: Map> = new Map(); + + /** + * Caches the updateDependents setting for each project to avoid repeated + * lookups and calculations. This determines if dependent projects should + * be automatically updated when a dependency changes. + */ + private projectToUpdateDependentsSetting: Map = + new Map(); + + constructor( + private tree: Tree, + private projectGraph: ProjectGraph, + private nxReleaseConfig: NxReleaseConfig, + private releaseGroups: ReleaseGroupWithName[], + private releaseGroupToFilteredProjects: Map< + ReleaseGroupWithName, + Set + >, + private options: ReleaseGroupProcessorOptions + ) { + /** + * To match legacy versioning behavior in the case of semver versions with leading "v" characters, + * e.g. "v1.0.0", we strip the leading "v" and use the rest of the string as the user given specifier + * to ensure that it is valid. If it is a non-semver version, we just use the string as is. + * + * TODO: re-evaluate if this is definitely what we want... Maybe we can just delegate isValid to the + * version actions implementation during prompting? + */ + if (options.userGivenSpecifier?.startsWith('v')) { + const userGivenSpecifierWithoutLeadingV = + options.userGivenSpecifier?.replace(/^v/, ''); + if (semver.valid(userGivenSpecifierWithoutLeadingV)) { + this.userGivenSpecifier = userGivenSpecifierWithoutLeadingV; + } + } else { + this.userGivenSpecifier = options.userGivenSpecifier; + } + } + + /** + * Initialize the processor by building the group graph and preparing for processing. + * This method must be called before processGroups(). + */ + async init(): Promise { + // Precompute project to release group mapping for O(1) lookups + this.setupProjectReleaseGroupMapping(); + + // Setup projects to process and resolve version actions + await this.setupProjectsToProcess(); + + // Precompute dependency relationships + await this.precomputeDependencyRelationships(); + + // Process dependency graph to find dependents to process + this.findDependentsToProcess(); + + // Build the group graph structure + for (const group of this.releaseGroups) { + this.groupGraph.set(group.name, { + group, + dependencies: new Set(), + dependents: new Set(), + }); + } + + // Process each project within each release group + for (const [, releaseGroupNode] of this.groupGraph) { + for (const projectName of releaseGroupNode.group.projects) { + const projectGraphNode = this.projectGraph.nodes[projectName]; + + // Check if the project has been filtered out of explicit versioning before continuing any further + if (!this.allProjectsToProcess.has(projectName)) { + continue; + } + + const versionActions = this.getVersionActionsForProject(projectName); + const finalConfigForProject = + this.getFinalConfigForProject(projectName); + + let latestMatchingGitTag: + | Awaited> + | undefined; + const releaseTagPattern = releaseGroupNode.group.releaseTagPattern; + // Cache the last matching git tag for relevant projects + if (finalConfigForProject.currentVersionResolver === 'git-tag') { + latestMatchingGitTag = await getLatestGitTagForPattern( + releaseTagPattern, + { + projectName: projectGraphNode.name, + }, + releaseGroupNode.group.releaseTagPatternCheckAllBranchesWhen + ); + this.cachedLatestMatchingGitTag.set( + projectName, + latestMatchingGitTag + ); + } + + // Cache the current version for the project + const currentVersion = await resolveCurrentVersion( + this.tree, + projectGraphNode, + releaseGroupNode.group, + versionActions, + this.projectLoggers.get(projectName)!, + this.currentVersionsPerFixedReleaseGroup, + finalConfigForProject, + releaseTagPattern, + latestMatchingGitTag + ); + this.cachedCurrentVersions.set(projectName, currentVersion); + } + } + + // Build the dependency relationships between groups + this.buildGroupDependencyGraph(); + + // Topologically sort the release groups and projects for efficient processing + this.sortedReleaseGroups = this.topologicallySortReleaseGroups(); + + // Sort projects within each release group + for (const group of this.releaseGroups) { + this.sortedProjects.set( + group.name, + this.topologicallySortProjects(group) + ); + } + + // Populate the dependent projects data + await this.populateDependentProjectsData(); + } + + /** + * Setup mapping from project to release group and cache updateDependents settings + */ + private setupProjectReleaseGroupMapping(): void { + for (const group of this.releaseGroups) { + for (const project of group.projects) { + this.projectToReleaseGroup.set(project, group); + + // Cache updateDependents setting relevant for each project + const updateDependents = + ((group.version as NxReleaseVersionV2Configuration) + ?.updateDependents as 'auto' | 'never') || 'auto'; + this.projectToUpdateDependentsSetting.set(project, updateDependents); + } + } + } + + /** + * Determine which projects should be processed and resolve their version actions + */ + private async setupProjectsToProcess(): Promise { + // Track the projects being directly versioned + let projectsToProcess = new Set(); + + // Precompute all projects in nx release config + for (const [groupName, group] of Object.entries( + this.nxReleaseConfig.groups + )) { + for (const project of group.projects) { + this.allProjectsConfiguredForNxRelease.add(project); + // Create a project logger for the project + this.projectLoggers.set(project, new ProjectLogger(project)); + + // If group filtering is applied and the current group is captured by the filter, add the project to the projectsToProcess + if (this.options.filters.groups?.includes(groupName)) { + projectsToProcess.add(project); + // Otherwise, if project filtering is applied and the current project is captured by the filter, add the project to the projectsToProcess + } else if (this.options.filters.projects?.includes(project)) { + projectsToProcess.add(project); + } + + const projectGraphNode = this.projectGraph.nodes[project]; + + /** + * Try and resolve a cached ReleaseGroupWithName for the project. It may not be present + * if the user filtered by group and excluded this parent group from direct versioning, + * so fallback to the release group config and apply the name manually. + */ + let releaseGroup = this.projectToReleaseGroup.get(project); + if (!releaseGroup) { + releaseGroup = { + ...group, + name: groupName, + resolvedVersionPlans: false, + }; + } + + // Resolve the final configuration for the project + const finalConfigForProject = this.resolveFinalConfigForProject( + releaseGroup, + projectGraphNode + ); + this.finalConfigsByProject.set(project, finalConfigForProject); + + const { + versionActionsPath, + versionActions, + afterAllProjectsVersioned, + } = await resolveVersionActionsForProject( + this.tree, + releaseGroup, + projectGraphNode, + finalConfigForProject + ); + if (!this.uniqueAfterAllProjectsVersioned.has(versionActionsPath)) { + this.uniqueAfterAllProjectsVersioned.set( + versionActionsPath, + afterAllProjectsVersioned + ); + } + this.projectsToVersionActions.set(project, versionActions); + } + } + + // If no filters are applied, process all projects + if ( + !this.options.filters.groups?.length && + !this.options.filters.projects?.length + ) { + projectsToProcess = this.allProjectsConfiguredForNxRelease; + } + + // If no projects are set to be processed, throw an error. This should be impossible because the filter validation in version.ts should have already caught this + if (projectsToProcess.size === 0) { + throw new Error( + 'No projects are set to be processed, please report this as a bug on https://github.com/nrwl/nx/issues' + ); + } + + this.allProjectsToProcess = new Set(projectsToProcess); + } + + /** + * Find all dependents that should be processed due to dependency updates + */ + private findDependentsToProcess(): void { + const projectsToProcess = Array.from(this.allProjectsToProcess); + const allTrackedDependents = new Set(); + const dependentsToProcess = new Set(); + + // BFS traversal of dependency graph to find all transitive dependents + let currentLevel = [...projectsToProcess]; + + while (currentLevel.length > 0) { + const nextLevel: string[] = []; + + // Get all dependents for the current level at once + const dependents = this.getAllNonImplicitDependents(currentLevel); + + // Process each dependent + for (const dep of dependents) { + // Skip if we've already seen this dependent or it's already in projectsToProcess + if ( + allTrackedDependents.has(dep) || + this.allProjectsToProcess.has(dep) + ) { + continue; + } + + // Track that we've seen this dependent + allTrackedDependents.add(dep); + + // If both the dependent and its dependency have updateDependents='auto', + // add the dependent to the projects to process + if (this.hasAutoUpdateDependents(dep)) { + // Check if any of its dependencies in the current level have auto update + const hasDependencyWithAutoUpdate = currentLevel.some( + (proj) => + this.hasAutoUpdateDependents(proj) && + this.getProjectDependents(proj).has(dep) + ); + + if (hasDependencyWithAutoUpdate) { + dependentsToProcess.add(dep); + } + } + + // Add to next level for traversal + nextLevel.push(dep); + } + + // Move to next level + currentLevel = nextLevel; + } + + // Add all dependents that should be processed to allProjectsToProcess + dependentsToProcess.forEach((dep) => this.allProjectsToProcess.add(dep)); + } + + private buildGroupDependencyGraph(): void { + for (const [releaseGroupName, releaseGroupNode] of this.groupGraph) { + for (const projectName of releaseGroupNode.group.projects) { + const projectDeps = this.getProjectDependencies(projectName); + for (const dep of projectDeps) { + const dependencyGroup = this.getReleaseGroupNameForProject(dep); + if (dependencyGroup && dependencyGroup !== releaseGroupName) { + releaseGroupNode.dependencies.add(dependencyGroup); + this.groupGraph + .get(dependencyGroup)! + .dependents.add(releaseGroupName); + } + } + } + } + } + + private async populateDependentProjectsData(): Promise { + for (const [projectName, dependentProjectNames] of this + .tmpCachedDependentProjects) { + const dependentProjectsData: VersionDataEntry['dependentProjects'] = []; + + for (const dependentProjectName of dependentProjectNames) { + const versionActions = + this.getVersionActionsForProject(dependentProjectName); + const { currentVersion, dependencyCollection } = + await versionActions.readCurrentVersionOfDependency( + this.tree, + this.projectGraph, + projectName + ); + dependentProjectsData.push({ + source: dependentProjectName, + target: projectName, + type: 'static', + dependencyCollection, + rawVersionSpec: currentVersion, + }); + } + + this.originalDependentProjectsPerProject.set( + projectName, + dependentProjectsData + ); + } + } + + getReleaseGroupNameForProject(projectName: string): string | null { + const group = this.projectToReleaseGroup.get(projectName); + return group ? group.name : null; + } + + getNextGroup(): string | null { + for (const [groupName, groupNode] of this.groupGraph) { + if ( + !this.processedGroups.has(groupName) && + Array.from(groupNode.dependencies).every((dep) => + this.processedGroups.has(dep) + ) + ) { + return groupName; + } + } + return null; + } + + async processGroups(): Promise { + const processOrder: string[] = []; + + // Use the topologically sorted groups instead of getNextGroup + for (const nextGroup of this.sortedReleaseGroups) { + // Skip groups that have already been processed (could happen with circular dependencies) + if (this.processedGroups.has(nextGroup)) { + continue; + } + + const allDependenciesProcessed = Array.from( + this.groupGraph.get(nextGroup)!.dependencies + ).every((dep) => this.processedGroups.has(dep)); + + if (!allDependenciesProcessed) { + // If we encounter a group whose dependencies aren't all processed, + // it means there's a circular dependency that our topological sort broke. + // We need to process any unprocessed dependencies first. + for (const dep of this.groupGraph.get(nextGroup)!.dependencies) { + if (!this.processedGroups.has(dep)) { + await this.processGroup(dep); + this.processedGroups.add(dep); + processOrder.push(dep); + } + } + } + + await this.processGroup(nextGroup); + this.processedGroups.add(nextGroup); + processOrder.push(nextGroup); + } + + return processOrder; + } + + flushAllProjectLoggers() { + for (const projectLogger of this.projectLoggers.values()) { + projectLogger.flush(); + } + } + + deleteProcessedVersionPlanFiles(): void { + for (const versionPlanPath of this.processedVersionPlanFiles) { + this.tree.delete(versionPlanPath); + } + } + + getVersionData(): VersionData { + return Object.fromEntries(this.versionData); + } + + /** + * Invoke the afterAllProjectsVersioned functions for each unique versionActions type. + * This can be useful for performing actions like updating a workspace level lock file. + * + * Because the tree has already been flushed to disk at this point, each afterAllProjectsVersioned + * function is responsible for returning the list of changed and deleted files that it affected. + * + * The root level `release.version.versionActionsOptions` is what is passed in here because this + * is a one time action for the whole workspace. Release group and project level overrides are + * not applicable. + */ + async afterAllProjectsVersioned( + rootVersionActionsOptions: Record + ): Promise<{ + changedFiles: string[]; + deletedFiles: string[]; + }> { + const changedFiles = new Set(); + const deletedFiles = new Set(); + + for (const [, afterAllProjectsVersioned] of this + .uniqueAfterAllProjectsVersioned) { + const { + changedFiles: changedFilesForVersionActions, + deletedFiles: deletedFilesForVersionActions, + } = await afterAllProjectsVersioned(this.tree.root, { + dryRun: this.options.dryRun, + verbose: this.options.verbose, + rootVersionActionsOptions, + }); + + for (const file of changedFilesForVersionActions) { + changedFiles.add(file); + } + for (const file of deletedFilesForVersionActions) { + deletedFiles.add(file); + } + } + + return { + changedFiles: Array.from(changedFiles), + deletedFiles: Array.from(deletedFiles), + }; + } + + private async processGroup(releaseGroupName: string): Promise { + const groupNode = this.groupGraph.get(releaseGroupName)!; + const bumped = await this.bumpVersions(groupNode.group); + + // Flush the project loggers for the group + for (const project of groupNode.group.projects) { + const projectLogger = this.getProjectLoggerForProject(project); + projectLogger.flush(); + } + + if (bumped) { + await this.propagateChangesToDependentGroups(releaseGroupName); + } + } + + private async propagateChangesToDependentGroups( + changedReleaseGroupName: string + ): Promise { + const changedGroupNode = this.groupGraph.get(changedReleaseGroupName)!; + for (const depGroupName of changedGroupNode.dependents) { + if (!this.processedGroups.has(depGroupName)) { + await this.propagateChanges(depGroupName, changedReleaseGroupName); + } + } + } + + private async bumpVersions( + releaseGroup: ReleaseGroupWithName + ): Promise { + if (releaseGroup.projectsRelationship === 'fixed') { + return this.bumpFixedVersionGroup(releaseGroup); + } else { + return this.bumpIndependentVersionGroup(releaseGroup); + } + } + + private async bumpFixedVersionGroup( + releaseGroup: ReleaseGroupWithName + ): Promise { + if (releaseGroup.projects.length === 0) { + return false; + } + + let bumped = false; + + const firstProject = releaseGroup.projects[0]; + const { + newVersionInput, + newVersionInputReason, + newVersionInputReasonData, + } = await this.determineVersionBumpForProject(releaseGroup, firstProject); + + if (newVersionInput === 'none') { + // No direct bump for this group, but we may still need to bump if a dependency group has been bumped + let bumpedByDependency = false; + + // Use sorted projects to check for dependencies in processed groups + const sortedProjects = this.sortedProjects.get(releaseGroup.name) || []; + + // Iterate through each project in the release group in topological order + for (const project of sortedProjects) { + const dependencies = this.projectGraph.dependencies[project] || []; + for (const dep of dependencies) { + const depGroup = this.getReleaseGroupNameForProject(dep.target); + if ( + depGroup && + depGroup !== releaseGroup.name && + this.processedGroups.has(depGroup) + ) { + const depGroupBumpType = await this.getFixedReleaseGroupBumpType( + depGroup + ); + + // If a dependency group has been bumped, determine if it should trigger a bump in this group + if (depGroupBumpType !== 'none') { + bumpedByDependency = true; + const depBumpType = this.determineSideEffectBump( + releaseGroup, + depGroupBumpType as SemverBumpType + ); + await this.bumpVersionForProject( + project, + depBumpType, + 'DEPENDENCY_ACROSS_GROUPS_WAS_BUMPED', + {} + ); + this.bumpedProjects.add(project); + + // Update any dependencies in the manifest + await this.updateDependenciesForProject(project); + } + } + } + } + + // If any project in the group was bumped due to dependency changes, we must bump all projects in the fixed group + if (bumpedByDependency) { + // Update all projects in topological order + for (const project of sortedProjects) { + if (!this.bumpedProjects.has(project)) { + await this.bumpVersionForProject( + project, + 'patch', + 'OTHER_PROJECT_IN_FIXED_GROUP_WAS_BUMPED_DUE_TO_DEPENDENCY', + {} + ); + // Ensure the bump for remaining projects + this.bumpedProjects.add(project); + await this.updateDependenciesForProject(project); + } + } + } else { + /** + * No projects in the group are being bumped, but as it stands only the first project would have an appropriate log, + * therefore add in an extra log for each additional project in the group, and we also need to make sure that the + * versionData is fully populated. + */ + for (const project of releaseGroup.projects) { + this.versionData.set(project, { + currentVersion: this.getCurrentCachedVersionForProject(project), + newVersion: null, + dependentProjects: this.getOriginalDependentProjects(project), + }); + if (project === firstProject) { + continue; + } + const projectLogger = this.getProjectLoggerForProject(project); + projectLogger.buffer( + `🚫 Skipping versioning for ${project} as it is a part of a fixed release group with ${firstProject} and no dependency bumps were detected` + ); + } + } + + return bumpedByDependency; + } + + const { newVersion } = await this.calculateNewVersion( + firstProject, + newVersionInput, + newVersionInputReason, + newVersionInputReasonData + ); + + // Use sorted projects for processing projects in the right order + const sortedProjects = + this.sortedProjects.get(releaseGroup.name) || releaseGroup.projects; + + // First, update versions for all projects in the fixed group in topological order + for (const project of sortedProjects) { + const versionActions = this.getVersionActionsForProject(project); + const projectLogger = this.getProjectLoggerForProject(project); + const currentVersion = this.getCurrentCachedVersionForProject(project); + + // The first project's version was determined above, so this log is only appropriate for the remaining projects + if (project !== firstProject) { + projectLogger.buffer( + `❓ Applied version ${newVersion} directly, because the project is a member of a fixed release group containing ${firstProject}` + ); + } + + /** + * Update the project's version based on the implementation details of the configured VersionActions + * and display any returned log messages to the user. + */ + const logMessages = await versionActions.updateProjectVersion( + this.tree, + newVersion + ); + for (const logMessage of logMessages) { + projectLogger.buffer(logMessage); + } + + this.bumpedProjects.add(project); + bumped = true; + + // Populate version data for each project + this.versionData.set(project, { + currentVersion, + newVersion, + dependentProjects: this.getOriginalDependentProjects(project), + }); + } + + // Then, update dependencies for all projects in the fixed group, also in topological order + if (bumped) { + for (const project of sortedProjects) { + await this.updateDependenciesForProject(project); + } + } + + return bumped; + } + + private async bumpIndependentVersionGroup( + releaseGroup: ReleaseGroupWithName + ): Promise { + const releaseGroupFilteredProjects = + this.releaseGroupToFilteredProjects.get(releaseGroup); + + let bumped = false; + const projectBumpTypes = new Map< + string, + { + bumpType: SemverBumpType | SemverVersion; + bumpTypeReason: keyof typeof BUMP_TYPE_REASON_TEXT; + bumpTypeReasonData: Record; + } + >(); + const projectsToUpdate = new Set(); + + // First pass: Determine bump types + for (const project of this.allProjectsToProcess) { + const { + newVersionInput: bumpType, + newVersionInputReason: bumpTypeReason, + newVersionInputReasonData: bumpTypeReasonData, + } = await this.determineVersionBumpForProject(releaseGroup, project); + projectBumpTypes.set(project, { + bumpType, + bumpTypeReason, + bumpTypeReasonData, + }); + if (bumpType !== 'none') { + projectsToUpdate.add(project); + } + } + + // Second pass: Update versions using topologically sorted projects + // This ensures dependencies are processed before dependents + const sortedProjects = this.sortedProjects.get(releaseGroup.name) || []; + + // Process projects in topological order + for (const project of sortedProjects) { + if ( + projectsToUpdate.has(project) && + releaseGroupFilteredProjects.has(project) + ) { + const { + bumpType: finalBumpType, + bumpTypeReason: finalBumpTypeReason, + bumpTypeReasonData: finalBumpTypeReasonData, + } = projectBumpTypes.get(project)!; + if (finalBumpType !== 'none') { + await this.bumpVersionForProject( + project, + finalBumpType, + finalBumpTypeReason, + finalBumpTypeReasonData + ); + this.bumpedProjects.add(project); + bumped = true; + } + } + } + + // Third pass: Update dependencies also in topological order + for (const project of sortedProjects) { + if ( + projectsToUpdate.has(project) && + releaseGroupFilteredProjects.has(project) + ) { + await this.updateDependenciesForProject(project); + } + } + + return bumped; + } + + private async determineVersionBumpForProject( + releaseGroup: ReleaseGroupWithName, + projectName: string + ): Promise<{ + newVersionInput: SemverBumpType | SemverVersion; + newVersionInputReason: keyof typeof BUMP_TYPE_REASON_TEXT; + newVersionInputReasonData: Record; + }> { + // User given specifier has the highest precedence + if (this.userGivenSpecifier) { + return { + newVersionInput: this.userGivenSpecifier as SemverBumpType, + newVersionInputReason: 'USER_SPECIFIER', + newVersionInputReasonData: {}, + }; + } + + const projectGraphNode = this.projectGraph.nodes[projectName]; + const projectLogger = this.getProjectLoggerForProject(projectName); + const cachedFinalConfigForProject = + this.getCachedFinalConfigForProject(projectName); + + if ( + cachedFinalConfigForProject.specifierSource === 'conventional-commits' + ) { + const currentVersion = + this.getCurrentCachedVersionForProject(projectName); + const bumpType = await deriveSpecifierFromConventionalCommits( + this.nxReleaseConfig, + this.projectGraph, + projectLogger, + releaseGroup, + projectGraphNode, + !!semver.prerelease(currentVersion ?? ''), + this.cachedLatestMatchingGitTag.get(projectName), + cachedFinalConfigForProject.fallbackCurrentVersionResolver, + this.options.preid + ); + return { + newVersionInput: bumpType, + newVersionInputReason: 'CONVENTIONAL_COMMITS', + newVersionInputReasonData: {}, + }; + } + + // Resolve the semver relative bump via version-plans + if (releaseGroup.versionPlans) { + const currentVersion = + this.getCurrentCachedVersionForProject(projectName); + const { bumpType, versionPlanPath } = + await deriveSpecifierFromVersionPlan( + projectLogger, + releaseGroup, + projectGraphNode, + currentVersion + ); + if (bumpType !== 'none') { + this.processedVersionPlanFiles.add(versionPlanPath); + } + return { + newVersionInput: bumpType, + newVersionInputReason: 'VERSION_PLANS', + newVersionInputReasonData: { + versionPlanPath, + }, + }; + } + + // Only add the release group name to the log if it is one set by the user, otherwise it is useless noise + const maybeLogReleaseGroup = (log: string): string => { + if (releaseGroup.name === IMPLICIT_DEFAULT_RELEASE_GROUP) { + return log; + } + return `${log} within release group "${releaseGroup.name}"`; + }; + if (cachedFinalConfigForProject.specifierSource === 'prompt') { + let specifier: SemverBumpType | SemverVersion; + if (releaseGroup.projectsRelationship === 'independent') { + specifier = await resolveSemverSpecifierFromPrompt( + `${maybeLogReleaseGroup( + `What kind of change is this for project "${projectName}"` + )}?`, + `${maybeLogReleaseGroup( + `What is the exact version for project "${projectName}"` + )}?` + ); + } else { + specifier = await resolveSemverSpecifierFromPrompt( + `${maybeLogReleaseGroup( + `What kind of change is this for the ${releaseGroup.projects.length} matched projects(s)` + )}?`, + `${maybeLogReleaseGroup( + `What is the exact version for the ${releaseGroup.projects.length} matched project(s)` + )}?` + ); + } + return { + newVersionInput: specifier, + newVersionInputReason: 'PROMPTED_USER_SPECIFIER', + newVersionInputReasonData: {}, + }; + } + + throw new Error( + `Unhandled version bump config, please report this as a bug on https://github.com/nrwl/nx/issues` + ); + } + + private getVersionActionsForProject(projectName: string): VersionActions { + const versionActions = this.projectsToVersionActions.get(projectName); + if (!versionActions) { + throw new Error( + `No versionActions found for project ${projectName}, please report this as a bug on https://github.com/nrwl/nx/issues` + ); + } + return versionActions; + } + + private getFinalConfigForProject(projectName: string): FinalConfigForProject { + const finalConfig = this.finalConfigsByProject.get(projectName); + if (!finalConfig) { + throw new Error( + `No final config found for project ${projectName}, please report this as a bug on https://github.com/nrwl/nx/issues` + ); + } + return finalConfig; + } + + private getProjectLoggerForProject(projectName: string): ProjectLogger { + const projectLogger = this.projectLoggers.get(projectName); + if (!projectLogger) { + throw new Error( + `No project logger found for project ${projectName}, please report this as a bug on https://github.com/nrwl/nx/issues` + ); + } + return projectLogger; + } + + private getCurrentCachedVersionForProject( + projectName: string + ): string | null { + return this.cachedCurrentVersions.get(projectName); + } + + private getCachedFinalConfigForProject( + projectName: string + ): NxReleaseVersionV2Configuration { + const cachedFinalConfig = this.finalConfigsByProject.get(projectName); + if (!cachedFinalConfig) { + throw new Error( + `Unexpected error: No cached config found for project ${projectName}, please report this as a bug on https://github.com/nrwl/nx/issues` + ); + } + return cachedFinalConfig; + } + + /** + * Apply project and release group precedence and default values, as well as validate the final configuration, + * ready to be cached. + */ + private resolveFinalConfigForProject( + releaseGroup: ReleaseGroupWithName, + projectGraphNode: ProjectGraphProjectNode + ): FinalConfigForProject { + const releaseGroupVersionConfig = releaseGroup.version as + | NxReleaseVersionV2Configuration + | undefined; + const projectVersionConfig = projectGraphNode.data.release?.version as + | NxReleaseVersionV2Configuration + | undefined; + + /** + * specifierSource + * + * If the user has provided a specifier, it always takes precedence, + * so the effective specifier source is 'prompt', regardless of what + * the project or release group config says. + */ + const specifierSource = this.userGivenSpecifier + ? 'prompt' + : projectVersionConfig?.specifierSource ?? + releaseGroupVersionConfig?.specifierSource ?? + 'prompt'; + + /** + * versionPrefix, defaults to auto + */ + const versionPrefix = + projectVersionConfig?.versionPrefix ?? + releaseGroupVersionConfig?.versionPrefix ?? + 'auto'; + if (versionPrefix && !validReleaseVersionPrefixes.includes(versionPrefix)) { + throw new Error( + `Invalid value for versionPrefix: "${versionPrefix}" + +Valid values are: ${validReleaseVersionPrefixes + .map((s) => `"${s}"`) + .join(', ')}` + ); + } + + /** + * currentVersionResolver, defaults to disk + */ + const currentVersionResolver = + projectVersionConfig?.currentVersionResolver ?? + releaseGroupVersionConfig?.currentVersionResolver ?? + 'disk'; + if ( + specifierSource === 'conventional-commits' && + currentVersionResolver !== 'git-tag' + ) { + throw new Error( + `Invalid currentVersionResolver "${currentVersionResolver}" provided for project "${projectGraphNode.name}". Must be "git-tag" when "specifierSource" is "conventional-commits"` + ); + } + + /** + * currentVersionResolverMetadata, defaults to {} + */ + const currentVersionResolverMetadata = + projectVersionConfig?.currentVersionResolverMetadata ?? + releaseGroupVersionConfig?.currentVersionResolverMetadata ?? + {}; + + /** + * preserveLocalDependencyProtocols + * + * This was false by default in legacy versioning, but is true by default now. + */ + const preserveLocalDependencyProtocols = + projectVersionConfig?.preserveLocalDependencyProtocols ?? + releaseGroupVersionConfig?.preserveLocalDependencyProtocols ?? + true; + + /** + * fallbackCurrentVersionResolver, defaults to disk when performing a first release, otherwise undefined + */ + const fallbackCurrentVersionResolver = + projectVersionConfig?.fallbackCurrentVersionResolver ?? + releaseGroupVersionConfig?.fallbackCurrentVersionResolver ?? + // Always fall back to disk if this is the first release + (this.options.firstRelease ? 'disk' : undefined); + + /** + * versionActionsOptions, defaults to {} + */ + const versionActionsOptions = + projectVersionConfig?.versionActionsOptions ?? + releaseGroupVersionConfig?.versionActionsOptions ?? + {}; + + const manifestRootsToUpdate = + projectVersionConfig?.manifestRootsToUpdate ?? + releaseGroupVersionConfig?.manifestRootsToUpdate ?? + []; + + return { + specifierSource, + currentVersionResolver, + currentVersionResolverMetadata, + fallbackCurrentVersionResolver, + versionPrefix, + preserveLocalDependencyProtocols, + versionActionsOptions, + manifestRootsToUpdate, + }; + } + + private async calculateNewVersion( + project: string, + newVersionInput: string, // any arbitrary string, whether or not it is valid is dependent upon the version actions implementation + newVersionInputReason: keyof typeof BUMP_TYPE_REASON_TEXT, + newVersionInputReasonData: Record + ): Promise<{ currentVersion: string | null; newVersion: string }> { + const currentVersion = this.getCurrentCachedVersionForProject(project); + const versionActions = this.getVersionActionsForProject(project); + const { newVersion, logText } = await versionActions.calculateNewVersion( + currentVersion, + newVersionInput, + newVersionInputReason, + newVersionInputReasonData, + this.options.preid + ); + const projectLogger = this.getProjectLoggerForProject(project); + projectLogger.buffer(logText); + return { currentVersion, newVersion }; + } + + private async updateDependenciesForProject( + projectName: string + ): Promise { + if (!this.allProjectsToProcess.has(projectName)) { + throw new Error( + `Unable to find ${projectName} in allProjectsToProcess, please report this as a bug on https://github.com/nrwl/nx/issues` + ); + } + + const versionActions = this.getVersionActionsForProject(projectName); + const cachedFinalConfigForProject = + this.getCachedFinalConfigForProject(projectName); + + const dependenciesToUpdate: Record = {}; + const dependencies = this.projectGraph.dependencies[projectName] || []; + + for (const dep of dependencies) { + if ( + this.allProjectsToProcess.has(dep.target) && + this.bumpedProjects.has(dep.target) + ) { + const targetVersionData = this.versionData.get(dep.target); + if (targetVersionData) { + const { currentVersion: currentDependencyVersion } = + await versionActions.readCurrentVersionOfDependency( + this.tree, + this.projectGraph, + dep.target + ); + if (!currentDependencyVersion) { + continue; + } + // If preserveLocalDependencyProtocols is true, and the dependency uses a local dependency protocol, skip updating the dependency + if ( + cachedFinalConfigForProject.preserveLocalDependencyProtocols && + (await versionActions.isLocalDependencyProtocol( + currentDependencyVersion + )) + ) { + continue; + } + + let finalPrefix = ''; + if (cachedFinalConfigForProject.versionPrefix === 'auto') { + const prefixMatch = currentDependencyVersion?.match(/^([~^=])/); + finalPrefix = prefixMatch ? prefixMatch[1] : ''; + } else if ( + ['~', '^', '='].includes(cachedFinalConfigForProject.versionPrefix) + ) { + finalPrefix = cachedFinalConfigForProject.versionPrefix; + } + + // Remove any existing prefix from the new version before applying the finalPrefix + const cleanNewVersion = targetVersionData.newVersion.replace( + /^[~^=]/, + '' + ); + dependenciesToUpdate[dep.target] = `${finalPrefix}${cleanNewVersion}`; + } + } + } + + const projectLogger = this.getProjectLoggerForProject(projectName); + const logMessages = await versionActions.updateProjectDependencies( + this.tree, + this.projectGraph, + dependenciesToUpdate + ); + for (const logMessage of logMessages) { + projectLogger.buffer(logMessage); + } + } + + private async bumpVersionForProject( + projectName: string, + bumpType: SemverBumpType | SemverVersion, + bumpTypeReason: keyof typeof BUMP_TYPE_REASON_TEXT, + bumpTypeReasonData: Record + ): Promise { + const projectLogger = this.getProjectLoggerForProject(projectName); + + if (bumpType === 'none') { + projectLogger.buffer( + `⏩ Skipping bump for ${projectName} as bump type is "none"` + ); + return; + } + + const versionActions = this.getVersionActionsForProject(projectName); + + const { currentVersion, newVersion } = await this.calculateNewVersion( + projectName, + bumpType, + bumpTypeReason, + bumpTypeReasonData + ); + + /** + * Update the project's version based on the implementation details of the configured VersionActions + * and display any returned log messages to the user. + */ + const logMessages = await versionActions.updateProjectVersion( + this.tree, + newVersion + ); + for (const logMessage of logMessages) { + projectLogger.buffer(logMessage); + } + + // Update version data and bumped projects + this.versionData.set(projectName, { + currentVersion, + newVersion, + dependentProjects: this.getOriginalDependentProjects(projectName), + }); + this.bumpedProjects.add(projectName); + + // Find the release group for this project + const releaseGroupName = this.getReleaseGroupNameForProject(projectName); + if (!releaseGroupName) { + projectLogger.buffer( + `⚠️ Cannot find release group for ${projectName}, skipping dependent updates` + ); + return; + } + + const releaseGroup = this.groupGraph.get(releaseGroupName)!.group; + const releaseGroupVersionConfig = + releaseGroup.version as NxReleaseVersionV2Configuration; + + // Get updateDependents from the release group level config + const updateDependents = + (releaseGroupVersionConfig?.updateDependents as 'auto' | 'never') || + 'auto'; + + // Only update dependencies for dependents if the group's updateDependents is 'auto' + if (updateDependents === 'auto') { + const dependents = this.getNonImplicitDependentsForProject(projectName); + await this.updateDependenciesForDependents(dependents); + + for (const dependent of dependents) { + if ( + this.allProjectsToProcess.has(dependent) && + !this.bumpedProjects.has(dependent) + ) { + await this.bumpVersionForProject( + dependent, + 'patch', + 'DEPENDENCY_WAS_BUMPED', + {} + ); + } + } + } else { + const releaseGroupText = + releaseGroupName !== IMPLICIT_DEFAULT_RELEASE_GROUP + ? ` in release group "${releaseGroupName}" ` + : ' '; + projectLogger.buffer( + `⏩ Skipping dependent updates as "updateDependents"${releaseGroupText}is not "auto"` + ); + } + } + + private async updateDependenciesForDependents( + dependents: string[] + ): Promise { + for (const dependent of dependents) { + if (!this.allProjectsToProcess.has(dependent)) { + throw new Error( + `Unable to find project "${dependent}" in allProjectsToProcess, please report this as a bug on https://github.com/nrwl/nx/issues` + ); + } + await this.updateDependenciesForProject(dependent); + } + } + + private getOriginalDependentProjects( + project: string + ): VersionDataEntry['dependentProjects'] { + return this.originalDependentProjectsPerProject.get(project) || []; + } + + private async propagateChanges( + releaseGroupName: string, + changedDependencyGroup: string + ): Promise { + const releaseGroup = this.groupGraph.get(releaseGroupName)!.group; + const releaseGroupFilteredProjects = + this.releaseGroupToFilteredProjects.get(releaseGroup); + + // Get updateDependents from the release group level config + const releaseGroupVersionConfig = + releaseGroup.version as NxReleaseVersionV2Configuration; + const updateDependents = + (releaseGroupVersionConfig?.updateDependents as 'auto' | 'never') || + 'auto'; + + // If updateDependents is not 'auto', skip propagating changes to this group + if (updateDependents !== 'auto') { + const projectLogger = this.getProjectLoggerForProject( + releaseGroupFilteredProjects.values().next().value + ); + projectLogger.buffer( + `⏩ Skipping dependency updates for release group "${releaseGroupName}" as "updateDependents" is not "auto"` + ); + return; + } + + let groupBumped = false; + let bumpType: SemverBumpType = 'none'; + + if (releaseGroup.projectsRelationship === 'fixed') { + // For fixed groups, we only need to check one project + const project = releaseGroupFilteredProjects[0]; + const dependencies = this.projectGraph.dependencies[project] || []; + const hasDependencyInChangedGroup = dependencies.some( + (dep) => + this.getReleaseGroupNameForProject(dep.target) === + changedDependencyGroup + ); + + if (hasDependencyInChangedGroup) { + const dependencyBumpType = await this.getFixedReleaseGroupBumpType( + changedDependencyGroup + ); + + bumpType = this.determineSideEffectBump( + releaseGroup, + dependencyBumpType as SemverBumpType + ); + groupBumped = bumpType !== 'none'; + } + } + + if (groupBumped) { + for (const project of releaseGroupFilteredProjects) { + if (!this.bumpedProjects.has(project)) { + await this.bumpVersionForProject( + project, + bumpType, + 'DEPENDENCY_ACROSS_GROUPS_WAS_BUMPED', + {} + ); + this.bumpedProjects.add(project); + } + } + } + } + + private async getFixedReleaseGroupBumpType( + releaseGroupName: string + ): Promise { + const releaseGroup = this.groupGraph.get(releaseGroupName)!.group; + const releaseGroupFilteredProjects = + this.releaseGroupToFilteredProjects.get(releaseGroup); + if (releaseGroupFilteredProjects.size === 0) { + return 'none'; + } + const { newVersionInput } = await this.determineVersionBumpForProject( + releaseGroup, + // It's a fixed release group, so we can just pick any project in the group + releaseGroupFilteredProjects.values().next().value + ); + return newVersionInput; + } + + // TODO: Support influencing the side effect bump in a future version, always patch for now like in legacy versioning + private determineSideEffectBump( + releaseGroup: ReleaseGroupWithName, + dependencyBumpType: SemverBumpType + ): SemverBumpType { + const sideEffectBump = 'patch'; + return sideEffectBump as SemverBumpType; + } + + private getProjectDependents(project: string): Set { + return this.projectToDependents.get(project) || new Set(); + } + + private getAllNonImplicitDependents(projects: string[]): string[] { + return projects + .flatMap((project) => { + const dependentProjectNames = + this.getNonImplicitDependentsForProject(project); + this.tmpCachedDependentProjects.set(project, dependentProjectNames); + return dependentProjectNames; + }) + .filter((dep) => !this.allProjectsToProcess.has(dep)); + } + + private getNonImplicitDependentsForProject(project: string): string[] { + // Use the cached dependents for O(1) lookup instead of O(n) scan + return Array.from(this.getProjectDependents(project)); + } + + private hasAutoUpdateDependents(projectName: string): boolean { + return this.projectToUpdateDependentsSetting.get(projectName) === 'auto'; + } + + private topologicallySortReleaseGroups(): string[] { + // Get all release group names + const groupNames = Array.from(this.groupGraph.keys()); + + // Function to get dependencies of a group + const getGroupDependencies = (groupName: string): string[] => { + const groupNode = this.groupGraph.get(groupName); + if (!groupNode) { + return []; + } + return Array.from(groupNode.dependencies); + }; + + // Perform topological sort + return topologicalSort(groupNames, getGroupDependencies); + } + + private topologicallySortProjects( + releaseGroup: ReleaseGroupWithName + ): string[] { + // For fixed relationship groups, the order doesn't matter since all projects will + // be versioned identically, but we still sort them for consistency + const projects = releaseGroup.projects.filter((p) => + // Only include projects that are in allProjectsToProcess + this.allProjectsToProcess.has(p) + ); + + // Function to get dependencies of a project that are in the same release group + const getProjectDependenciesInSameGroup = (project: string): string[] => { + const deps = this.getProjectDependencies(project); + // Only include dependencies that are in the same release group and in allProjectsToProcess + return Array.from(deps).filter( + (dep) => + this.getReleaseGroupNameForProject(dep) === releaseGroup.name && + this.allProjectsToProcess.has(dep) + ); + }; + + // Perform topological sort + return topologicalSort(projects, getProjectDependenciesInSameGroup); + } + + /** + * Precompute project -> dependents/dependencies relationships for O(1) lookups + */ + private async precomputeDependencyRelationships(): Promise { + for (const projectName of this.allProjectsConfiguredForNxRelease) { + const versionActions = this.projectsToVersionActions.get(projectName); + + // Create a new set for this project's dependencies + if (!this.projectToDependencies.has(projectName)) { + this.projectToDependencies.set(projectName, new Set()); + } + + const deps = await versionActions.readDependencies( + this.tree, + this.projectGraph + ); + + for (const dep of deps) { + // Skip dependencies not covered by nx release + if (!this.allProjectsConfiguredForNxRelease.has(dep.target)) { + continue; + } + + // Add this dependency to the project's dependencies + this.projectToDependencies.get(projectName)!.add(dep.target); + + // Add this project as a dependent of the target + if (!this.projectToDependents.has(dep.target)) { + this.projectToDependents.set(dep.target, new Set()); + } + this.projectToDependents.get(dep.target)!.add(projectName); + } + } + } + + private getProjectDependencies(project: string): Set { + return this.projectToDependencies.get(project) || new Set(); + } +} diff --git a/packages/nx/src/command-line/release/version/release-version.spec.ts b/packages/nx/src/command-line/release/version/release-version.spec.ts new file mode 100644 index 0000000000..8d8382f440 --- /dev/null +++ b/packages/nx/src/command-line/release/version/release-version.spec.ts @@ -0,0 +1,2646 @@ +import * as enquirer from 'enquirer'; +import { NxReleaseVersionV2Configuration } from '../../../config/nx-json'; +import type { ProjectGraph } from '../../../config/project-graph'; +import { createTreeWithEmptyWorkspace } from '../../../generators/testing-utils/create-tree-with-empty-workspace'; +import { Tree } from '../../../generators/tree'; +import { + readJson, + updateJson, + writeJson, +} from '../../../generators/utils/json'; +import { output } from '../../../utils/output'; +import { NxReleaseConfig } from '../config/config'; +import { ReleaseGroupWithName } from '../config/filter-release-groups'; +import { VersionData } from '../utils/shared'; +import { ReleaseGroupProcessor } from './release-group-processor'; +import { + createNxReleaseConfigAndPopulateWorkspace, + mockResolveVersionActionsForProjectImplementation, +} from './test-utils'; +import { SemverBumpType } from './version-actions'; + +const originalExit = process.exit; +let stubProcessExit = false; + +const processExitSpy = jest + .spyOn(process, 'exit') + .mockImplementation((...args) => { + if (stubProcessExit) { + return undefined as never; + } + return originalExit(...args); + }); + +const mockDetectPackageManager = jest.fn(); +jest.doMock('nx/src/devkit-exports', () => { + const devkit = jest.requireActual('nx/src/devkit-exports'); + return { + ...devkit, + detectPackageManager: mockDetectPackageManager, + }; +}); + +jest.mock('enquirer'); + +let mockDeriveSpecifierFromConventionalCommits = jest.fn(); +let mockDeriveSpecifierFromVersionPlan = jest.fn(); +let mockResolveVersionActionsForProject = jest.fn(); + +jest.doMock('./derive-specifier-from-conventional-commits', () => ({ + deriveSpecifierFromConventionalCommits: + mockDeriveSpecifierFromConventionalCommits, +})); + +jest.doMock('./version-actions', () => ({ + ...jest.requireActual('./version-actions'), + deriveSpecifierFromVersionPlan: mockDeriveSpecifierFromVersionPlan, + resolveVersionActionsForProject: mockResolveVersionActionsForProject, +})); + +jest.mock('./project-logger', () => ({ + ...jest.requireActual('./project-logger'), + // Don't slow down or add noise to unit tests output unnecessarily + ProjectLogger: class ProjectLogger { + buffer() {} + flush() {} + }, +})); + +// Using the daemon in unit tests would cause jest to never exit +process.env.NX_DAEMON = 'false'; + +type ReleaseVersionGeneratorResult = { + data: VersionData; + callback: ( + tree: Tree, + opts: { + dryRun?: boolean; + verbose?: boolean; + versionActionsOptions?: Record; + } + ) => Promise< + | string[] + | { + changedFiles: string[]; + deletedFiles: string[]; + } + >; +}; + +/** + * Wrapper around the new logic to allow it to provide the same interface as the old logic for the JS version generator + * so that we can assert that the new logic is behaving as before where expected. + */ +async function releaseVersionGeneratorForTest( + tree: Tree, + { + nxReleaseConfig, + projectGraph, + releaseGroups, + releaseGroupToFilteredProjects, + userGivenSpecifier, + filters, + preid, + }: { + nxReleaseConfig: NxReleaseConfig; + projectGraph: ProjectGraph; + releaseGroups: ReleaseGroupWithName[]; + releaseGroupToFilteredProjects: Map>; + userGivenSpecifier: SemverBumpType | undefined; + filters?: { + projects?: string[]; + groups?: string[]; + }; + preid?: string; + } +): Promise { + const processor = new ReleaseGroupProcessor( + tree, + projectGraph, + nxReleaseConfig, + releaseGroups, + releaseGroupToFilteredProjects, + { + dryRun: false, + verbose: false, + firstRelease: false, + preid, + userGivenSpecifier, + filters, + } + ); + + try { + await processor.init(); + await processor.processGroups(); + + return { + callback: async () => { + /** + * Pass in the root level release.version.versionActionsOptions (release group and project level options are not respected here + * because this takes place after all projects have been versioned) + */ + return processor.afterAllProjectsVersioned( + (nxReleaseConfig.version as NxReleaseVersionV2Configuration) + .versionActionsOptions + ); + }, + data: processor.getVersionData(), + }; + } catch (e: any) { + // Flush any pending logs before printing the error to make troubleshooting easier + processor.flushAllProjectLoggers(); + + if (process.env.NX_VERBOSE_LOGGING === 'true') { + output.error({ + title: e.message, + }); + // Dump the full stack trace in verbose mode + console.error(e); + } else { + output.error({ + title: e.message, + }); + } + process.exit(1); + } +} + +describe('releaseVersionGenerator (ported tests)', () => { + let tree: Tree; + + beforeEach(() => { + // @ts-expect-error read-only property + process.exit = processExitSpy; + + tree = createTreeWithEmptyWorkspace(); + updateJson(tree, 'nx.json', (json) => { + json.release = {}; + return json; + }); + + mockResolveVersionActionsForProject.mockImplementation( + mockResolveVersionActionsForProjectImplementation + ); + }); + + afterEach(() => { + jest.clearAllMocks(); + stubProcessExit = false; + }); + afterAll(() => { + process.exit = originalExit; + }); + + it('should return a versionData object', async () => { + const { + nxReleaseConfig, + projectGraph, + releaseGroups, + releaseGroupToFilteredProjects, + filters, + } = await createNxReleaseConfigAndPopulateWorkspace( + tree, + ` + __default__ ({ "projectsRelationship": "fixed" }): + - my-lib@0.0.1 [js] + - project-with-dependency-on-my-pkg@0.0.1 [js] + -> depends on my-lib + - project-with-devDependency-on-my-pkg@0.0.1 [js] + -> depends on my-lib {devDependencies} + `, + { + version: { + specifierSource: 'prompt', + }, + } + ); + + expect( + await releaseVersionGeneratorForTest(tree, { + nxReleaseConfig, + projectGraph, + filters, + userGivenSpecifier: 'major', + releaseGroups, + releaseGroupToFilteredProjects, + }) + ).toMatchInlineSnapshot(` + { + "callback": [Function], + "data": { + "my-lib": { + "currentVersion": "0.0.1", + "dependentProjects": [ + { + "dependencyCollection": "dependencies", + "rawVersionSpec": "0.0.1", + "source": "project-with-dependency-on-my-pkg", + "target": "my-lib", + "type": "static", + }, + { + "dependencyCollection": "devDependencies", + "rawVersionSpec": "0.0.1", + "source": "project-with-devDependency-on-my-pkg", + "target": "my-lib", + "type": "static", + }, + ], + "newVersion": "1.0.0", + }, + "project-with-dependency-on-my-pkg": { + "currentVersion": "0.0.1", + "dependentProjects": [], + "newVersion": "1.0.0", + }, + "project-with-devDependency-on-my-pkg": { + "currentVersion": "0.0.1", + "dependentProjects": [], + "newVersion": "1.0.0", + }, + }, + } + `); + }); + + describe('not all given projects have package.json files', () => { + it(`should exit with code one and print guidance when not all of the given projects are appropriate for JS versioning`, async () => { + stubProcessExit = true; + + const { + nxReleaseConfig, + projectGraph, + releaseGroups, + releaseGroupToFilteredProjects, + filters, + } = await createNxReleaseConfigAndPopulateWorkspace( + tree, + ` + __default__ ({ "projectsRelationship": "fixed" }): + - my-lib@0.0.1 [js] + - project-with-dependency-on-my-pkg@0.0.1 [js] + -> depends on my-lib + - project-with-devDependency-on-my-pkg@0.0.1 [js] + -> depends on my-lib {devDependencies} + `, + { + version: { + specifierSource: 'prompt', + }, + } + ); + + tree.delete('my-lib/package.json'); + + const outputSpy = jest + .spyOn(output, 'error') + .mockImplementation(() => {}); + + await releaseVersionGeneratorForTest(tree, { + nxReleaseConfig, + projectGraph, + filters, + releaseGroups, + releaseGroupToFilteredProjects, + userGivenSpecifier: 'major', + }); + + expect(outputSpy).toHaveBeenCalledWith({ + title: expect.stringContaining( + 'The project "my-lib" does not have a package.json file available in ./my-lib' + ), + }); + + outputSpy.mockRestore(); + expect(processExitSpy).toHaveBeenCalledWith(1); + + stubProcessExit = false; + }); + }); + + describe('package with mixed "prod" and "dev" dependencies', () => { + it('should update local dependencies only where it needs to', async () => { + const { + projectGraph, + nxReleaseConfig, + releaseGroups, + releaseGroupToFilteredProjects, + filters, + } = await createNxReleaseConfigAndPopulateWorkspace( + tree, + ` + __default__ ({ "projectsRelationship": "fixed" }): + - my-app@0.0.1 [js] + -> depends on my-lib-1 + -> depends on my-lib-2 {devDependencies} + - my-lib-1@0.0.1 [js] + - my-lib-2@0.0.1 [js] + `, + { + version: { + specifierSource: 'prompt', + }, + } + ); + + await releaseVersionGeneratorForTest(tree, { + nxReleaseConfig, + releaseGroups, + releaseGroupToFilteredProjects, + filters, + projectGraph, + userGivenSpecifier: 'major', + }); + + expect(readJson(tree, 'my-app/package.json')).toMatchInlineSnapshot(` + { + "dependencies": { + "my-lib-1": "1.0.0", + }, + "devDependencies": { + "my-lib-2": "1.0.0", + }, + "name": "my-app", + "version": "1.0.0", + } + `); + }); + }); + + describe('fixed release group', () => { + it(`should work with semver keywords and exact semver versions`, async () => { + const { + nxReleaseConfig, + projectGraph, + releaseGroups, + releaseGroupToFilteredProjects, + filters, + } = await createNxReleaseConfigAndPopulateWorkspace( + tree, + ` + __default__ ({ "projectsRelationship": "fixed" }): + - my-lib@0.0.1 [js] + - project-with-dependency-on-my-pkg@0.0.1 [js] + -> depends on my-lib + - project-with-devDependency-on-my-pkg@0.0.1 [js] + -> depends on my-lib {devDependencies} + `, + { + version: { + specifierSource: 'prompt', + }, + } + ); + + expect(readJson(tree, 'my-lib/package.json').version).toEqual('0.0.1'); + + await releaseVersionGeneratorForTest(tree, { + nxReleaseConfig, + projectGraph, + filters, + userGivenSpecifier: 'major', + releaseGroups, + releaseGroupToFilteredProjects, + }); + expect(readJson(tree, 'my-lib/package.json').version).toEqual('1.0.0'); + + await releaseVersionGeneratorForTest(tree, { + nxReleaseConfig, + projectGraph, + filters, + userGivenSpecifier: 'minor', + releaseGroups, + releaseGroupToFilteredProjects, + }); + expect(readJson(tree, 'my-lib/package.json').version).toEqual('1.1.0'); + + await releaseVersionGeneratorForTest(tree, { + nxReleaseConfig, + projectGraph, + filters, + userGivenSpecifier: 'patch', + releaseGroups, + releaseGroupToFilteredProjects, + }); + expect(readJson(tree, 'my-lib/package.json').version).toEqual('1.1.1'); + + await releaseVersionGeneratorForTest(tree, { + nxReleaseConfig, + projectGraph, + filters, + userGivenSpecifier: '1.2.3' as SemverBumpType, + releaseGroups, + releaseGroupToFilteredProjects, + }); + expect(readJson(tree, 'my-lib/package.json').version).toEqual('1.2.3'); + }); + + it(`should apply the updated version to the projects, including updating dependents`, async () => { + const { + nxReleaseConfig, + projectGraph, + releaseGroups, + releaseGroupToFilteredProjects, + filters, + } = await createNxReleaseConfigAndPopulateWorkspace( + tree, + ` + __default__ ({ "projectsRelationship": "fixed" }): + - my-lib@0.0.1 [js] + - project-with-dependency-on-my-pkg@0.0.1 [js] + -> depends on my-lib + - project-with-devDependency-on-my-pkg@0.0.1 [js] + -> depends on my-lib {devDependencies} + `, + { + version: { + specifierSource: 'prompt', + }, + } + ); + + await releaseVersionGeneratorForTest(tree, { + nxReleaseConfig, + projectGraph, + filters, + userGivenSpecifier: 'major', + releaseGroups, + releaseGroupToFilteredProjects, + }); + + expect(readJson(tree, 'my-lib/package.json')).toMatchInlineSnapshot(` + { + "name": "my-lib", + "version": "1.0.0", + } + `); + + expect(readJson(tree, 'project-with-dependency-on-my-pkg/package.json')) + .toMatchInlineSnapshot(` + { + "dependencies": { + "my-lib": "1.0.0", + }, + "name": "project-with-dependency-on-my-pkg", + "version": "1.0.0", + } + `); + expect( + readJson(tree, 'project-with-devDependency-on-my-pkg/package.json') + ).toMatchInlineSnapshot(` + { + "devDependencies": { + "my-lib": "1.0.0", + }, + "name": "project-with-devDependency-on-my-pkg", + "version": "1.0.0", + } + `); + }); + }); + + describe('independent release group', () => { + describe('specifierSource: prompt', () => { + it(`should appropriately prompt for each project independently and apply the version updates across all manifest files`, async () => { + // @ts-ignore + enquirer.prompt = jest + .fn() + // First project will be minor + .mockReturnValueOnce(Promise.resolve({ specifier: 'minor' })) + // Next project will be patch + .mockReturnValueOnce(Promise.resolve({ specifier: 'patch' })) + // Final project will be custom explicit version + .mockReturnValueOnce(Promise.resolve({ specifier: 'custom' })) + .mockReturnValueOnce(Promise.resolve({ specifier: '1.2.3' })); + + const { + nxReleaseConfig, + projectGraph, + releaseGroups, + releaseGroupToFilteredProjects, + filters, + } = await createNxReleaseConfigAndPopulateWorkspace( + tree, + ` + __default__ ({ "projectsRelationship": "independent" }): + - my-lib@0.0.1 [js] + - project-with-dependency-on-my-pkg@0.0.1 [js] + -> depends on my-lib + - project-with-devDependency-on-my-pkg@0.0.1 [js] + -> depends on my-lib {devDependencies} + `, + { + version: { + specifierSource: 'prompt', + }, + } + ); + + await releaseVersionGeneratorForTest(tree, { + nxReleaseConfig, + projectGraph, + filters, + userGivenSpecifier: undefined, + releaseGroups, + releaseGroupToFilteredProjects, + }); + + expect(readJson(tree, 'my-lib/package.json')).toMatchInlineSnapshot(` + { + "name": "my-lib", + "version": "0.1.0", + } + `); + + expect(readJson(tree, 'project-with-dependency-on-my-pkg/package.json')) + .toMatchInlineSnapshot(` + { + "dependencies": { + "my-lib": "0.1.0", + }, + "name": "project-with-dependency-on-my-pkg", + "version": "0.0.2", + } + `); + expect( + readJson(tree, 'project-with-devDependency-on-my-pkg/package.json') + ).toMatchInlineSnapshot(` + { + "devDependencies": { + "my-lib": "0.1.0", + }, + "name": "project-with-devDependency-on-my-pkg", + "version": "1.2.3", + } + `); + }); + it(`should respect an explicit user CLI specifier for all, even when projects are independent, and apply the version updates across all manifest files`, async () => { + const { + nxReleaseConfig, + projectGraph, + releaseGroups, + releaseGroupToFilteredProjects, + filters, + } = await createNxReleaseConfigAndPopulateWorkspace( + tree, + ` + __default__ ({ "projectsRelationship": "independent" }): + - my-lib@0.0.1 [js] + - project-with-dependency-on-my-pkg@0.0.1 [js] + -> depends on my-lib + - project-with-devDependency-on-my-pkg@0.0.1 [js] + -> depends on my-lib {devDependencies} + `, + { + version: { + specifierSource: 'prompt', + }, + } + ); + + await releaseVersionGeneratorForTest(tree, { + nxReleaseConfig, + projectGraph, + filters, + userGivenSpecifier: '4.5.6' as SemverBumpType, + releaseGroups, + releaseGroupToFilteredProjects, + }); + + expect(readJson(tree, 'my-lib/package.json')).toMatchInlineSnapshot(` + { + "name": "my-lib", + "version": "4.5.6", + } + `); + + expect(readJson(tree, 'project-with-dependency-on-my-pkg/package.json')) + .toMatchInlineSnapshot(` + { + "dependencies": { + "my-lib": "4.5.6", + }, + "name": "project-with-dependency-on-my-pkg", + "version": "4.5.6", + } + `); + expect( + readJson(tree, 'project-with-devDependency-on-my-pkg/package.json') + ).toMatchInlineSnapshot(` + { + "devDependencies": { + "my-lib": "4.5.6", + }, + "name": "project-with-devDependency-on-my-pkg", + "version": "4.5.6", + } + `); + }); + + describe('updateDependentsOptions', () => { + it(`should update dependents even when filtering to a subset of projects which do not include those dependents, by default`, async () => { + const { + nxReleaseConfig, + projectGraph, + releaseGroups, + releaseGroupToFilteredProjects, + filters, + } = await createNxReleaseConfigAndPopulateWorkspace( + tree, + ` + __default__ ({ "projectsRelationship": "independent" }): + - my-lib@0.0.1 [js] + - project-with-dependency-on-my-pkg@0.0.1 [js] + -> depends on my-lib + - project-with-devDependency-on-my-pkg@0.0.1 [js] + -> depends on my-lib {devDependencies} + `, + { + version: { + specifierSource: 'prompt', + // No value for updateDependents, should default to 'auto' + }, + }, + undefined, + { + // version only my-lib + projects: ['my-lib'], + } + ); + + expect(readJson(tree, 'my-lib/package.json').version).toEqual( + '0.0.1' + ); + expect( + readJson(tree, 'project-with-dependency-on-my-pkg/package.json') + ).toMatchInlineSnapshot(` + { + "dependencies": { + "my-lib": "0.0.1", + }, + "name": "project-with-dependency-on-my-pkg", + "version": "0.0.1", + } + `); + expect( + readJson(tree, 'project-with-devDependency-on-my-pkg/package.json') + ).toMatchInlineSnapshot(` + { + "devDependencies": { + "my-lib": "0.0.1", + }, + "name": "project-with-devDependency-on-my-pkg", + "version": "0.0.1", + } + `); + + await releaseVersionGeneratorForTest(tree, { + nxReleaseConfig, + projectGraph, + filters, + userGivenSpecifier: '9.9.9' as SemverBumpType, + releaseGroups, + releaseGroupToFilteredProjects, + }); + + expect(readJson(tree, 'my-lib/package.json')).toMatchInlineSnapshot(` + { + "name": "my-lib", + "version": "9.9.9", + } + `); + + expect( + readJson(tree, 'project-with-dependency-on-my-pkg/package.json') + ).toMatchInlineSnapshot(` + { + "dependencies": { + "my-lib": "9.9.9", + }, + "name": "project-with-dependency-on-my-pkg", + "version": "0.0.2", + } + `); + expect( + readJson(tree, 'project-with-devDependency-on-my-pkg/package.json') + ).toMatchInlineSnapshot(` + { + "devDependencies": { + "my-lib": "9.9.9", + }, + "name": "project-with-devDependency-on-my-pkg", + "version": "0.0.2", + } + `); + }); + + it(`should not update dependents when filtering to a subset of projects by default, if "updateDependents" is set to "never"`, async () => { + const { + nxReleaseConfig, + projectGraph, + releaseGroups, + releaseGroupToFilteredProjects, + filters, + } = await createNxReleaseConfigAndPopulateWorkspace( + tree, + ` + __default__ ({ "projectsRelationship": "independent" }): + - my-lib@0.0.1 [js] + - project-with-dependency-on-my-pkg@0.0.1 [js] + -> depends on my-lib + - project-with-devDependency-on-my-pkg@0.0.1 [js] + -> depends on my-lib {devDependencies} + `, + { + version: { + specifierSource: 'prompt', + updateDependents: 'never', + }, + }, + undefined, + { + projects: ['my-lib'], + } + ); + + await releaseVersionGeneratorForTest(tree, { + nxReleaseConfig, + projectGraph, + filters, + userGivenSpecifier: '9.9.9' as SemverBumpType, + releaseGroups, + releaseGroupToFilteredProjects, + }); + + expect(readJson(tree, 'my-lib/package.json')).toMatchInlineSnapshot(` + { + "name": "my-lib", + "version": "9.9.9", + } + `); + + expect( + readJson(tree, 'project-with-dependency-on-my-pkg/package.json') + ).toMatchInlineSnapshot(` + { + "dependencies": { + "my-lib": "0.0.1", + }, + "name": "project-with-dependency-on-my-pkg", + "version": "0.0.1", + } + `); + expect( + readJson(tree, 'project-with-devDependency-on-my-pkg/package.json') + ).toMatchInlineSnapshot(` + { + "devDependencies": { + "my-lib": "0.0.1", + }, + "name": "project-with-devDependency-on-my-pkg", + "version": "0.0.1", + } + `); + }); + + it(`should update dependents even when filtering to a subset of projects which do not include those dependents, if "updateDependents" is "auto"`, async () => { + const { + nxReleaseConfig, + projectGraph, + releaseGroups, + releaseGroupToFilteredProjects, + filters, + } = await createNxReleaseConfigAndPopulateWorkspace( + tree, + ` + __default__ ({ "projectsRelationship": "independent" }): + - my-lib@0.0.1 [js] + - project-with-dependency-on-my-pkg@0.0.1 [js] + -> depends on my-lib + - project-with-devDependency-on-my-pkg@0.0.1 [js] + -> depends on my-lib {devDependencies} + `, + { + version: { + specifierSource: 'prompt', + updateDependents: 'auto', + }, + }, + undefined, + { + projects: ['my-lib'], + } + ); + + await releaseVersionGeneratorForTest(tree, { + nxReleaseConfig, + filters, + projectGraph, + userGivenSpecifier: '9.9.9' as SemverBumpType, + releaseGroups, + releaseGroupToFilteredProjects, + }); + + expect(readJson(tree, 'my-lib/package.json')).toMatchInlineSnapshot(` + { + "name": "my-lib", + "version": "9.9.9", + } + `); + + expect( + readJson(tree, 'project-with-dependency-on-my-pkg/package.json') + ).toMatchInlineSnapshot(` + { + "dependencies": { + "my-lib": "9.9.9", + }, + "name": "project-with-dependency-on-my-pkg", + "version": "0.0.2", + } + `); + expect( + readJson(tree, 'project-with-devDependency-on-my-pkg/package.json') + ).toMatchInlineSnapshot(` + { + "devDependencies": { + "my-lib": "9.9.9", + }, + "name": "project-with-devDependency-on-my-pkg", + "version": "0.0.2", + } + `); + }); + + it('should update dependents with a prepatch when creating a pre-release version', async () => { + const { + nxReleaseConfig, + projectGraph, + releaseGroups, + releaseGroupToFilteredProjects, + filters, + } = await createNxReleaseConfigAndPopulateWorkspace( + tree, + ` + __default__ ({ "projectsRelationship": "independent" }): + - my-lib@0.0.1 [js] + - project-with-dependency-on-my-pkg@0.0.1 [js] + -> depends on my-lib + - project-with-devDependency-on-my-pkg@0.0.1 [js] + -> depends on my-lib {devDependencies} + `, + { + version: { + specifierSource: 'prompt', + }, + } + ); + + expect(readJson(tree, 'my-lib/package.json').version).toEqual( + '0.0.1' + ); + expect( + readJson(tree, 'project-with-dependency-on-my-pkg/package.json') + .version + ).toEqual('0.0.1'); + expect( + readJson(tree, 'project-with-devDependency-on-my-pkg/package.json') + .version + ).toEqual('0.0.1'); + + await releaseVersionGeneratorForTest(tree, { + nxReleaseConfig, + filters, + projectGraph, + userGivenSpecifier: 'prepatch' as SemverBumpType, + releaseGroups, + releaseGroupToFilteredProjects, + preid: 'alpha', + }); + + expect(readJson(tree, 'my-lib/package.json')).toMatchInlineSnapshot(` + { + "name": "my-lib", + "version": "0.0.2-alpha.0", + } + `); + + expect( + readJson(tree, 'project-with-dependency-on-my-pkg/package.json') + ).toMatchInlineSnapshot(` + { + "dependencies": { + "my-lib": "0.0.2-alpha.0", + }, + "name": "project-with-dependency-on-my-pkg", + "version": "0.0.2-alpha.0", + } + `); + expect( + readJson(tree, 'project-with-devDependency-on-my-pkg/package.json') + ).toMatchInlineSnapshot(` + { + "devDependencies": { + "my-lib": "0.0.2-alpha.0", + }, + "name": "project-with-devDependency-on-my-pkg", + "version": "0.0.2-alpha.0", + } + `); + }); + }); + }); + }); + + describe('leading v in version', () => { + it(`should strip a leading v from the provided specifier`, async () => { + const { + nxReleaseConfig, + projectGraph, + releaseGroups, + releaseGroupToFilteredProjects, + filters, + } = await createNxReleaseConfigAndPopulateWorkspace( + tree, + ` + __default__ ({ "projectsRelationship": "fixed" }): + - my-lib@0.0.1 [js] + - project-with-dependency-on-my-pkg@0.0.1 [js] + -> depends on my-lib + - project-with-devDependency-on-my-pkg@0.0.1 [js] + -> depends on my-lib {devDependencies} + `, + { + version: { + specifierSource: 'prompt', + }, + } + ); + + await releaseVersionGeneratorForTest(tree, { + nxReleaseConfig, + projectGraph, + filters, + userGivenSpecifier: 'v8.8.8' as SemverBumpType, + releaseGroups, + releaseGroupToFilteredProjects, + }); + + expect(readJson(tree, 'my-lib/package.json')).toMatchInlineSnapshot(` + { + "name": "my-lib", + "version": "8.8.8", + } + `); + + expect(readJson(tree, 'project-with-dependency-on-my-pkg/package.json')) + .toMatchInlineSnapshot(` + { + "dependencies": { + "my-lib": "8.8.8", + }, + "name": "project-with-dependency-on-my-pkg", + "version": "8.8.8", + } + `); + expect( + readJson(tree, 'project-with-devDependency-on-my-pkg/package.json') + ).toMatchInlineSnapshot(` + { + "devDependencies": { + "my-lib": "8.8.8", + }, + "name": "project-with-devDependency-on-my-pkg", + "version": "8.8.8", + } + `); + }); + }); + + describe('dependent version prefix', () => { + const graphDefinition = ` + __default__ ({ "projectsRelationship": "fixed" }): + - my-lib@0.0.1 [js] + - project-with-dependency-on-my-pkg@0.0.1 [js] + -> depends on my-lib + - project-with-devDependency-on-my-pkg@0.0.1 [js] + -> depends on my-lib {devDependencies} + - another-project-with-devDependency-on-my-pkg@0.0.1 [js] + -> depends on my-lib {devDependencies} + `; + + function setDifferentVersionPrefixes(tree: Tree) { + // Manually set different version prefixes + const pkgWithDep = readJson( + tree, + 'project-with-dependency-on-my-pkg/package.json' + ); + pkgWithDep.dependencies['my-lib'] = '~0.0.1'; + writeJson( + tree, + 'project-with-dependency-on-my-pkg/package.json', + pkgWithDep + ); + + const pkgWithDevDep = readJson( + tree, + 'project-with-devDependency-on-my-pkg/package.json' + ); + pkgWithDevDep.devDependencies['my-lib'] = '^0.0.1'; + writeJson( + tree, + 'project-with-devDependency-on-my-pkg/package.json', + pkgWithDevDep + ); + } + + it('should work with an empty prefix', async () => { + const { + projectGraph, + nxReleaseConfig, + releaseGroups, + releaseGroupToFilteredProjects, + filters, + } = await createNxReleaseConfigAndPopulateWorkspace( + tree, + graphDefinition, + { + version: { + specifierSource: 'prompt', + versionPrefix: '', + }, + } + ); + + // Manually set different version prefixes + setDifferentVersionPrefixes(tree); + + await releaseVersionGeneratorForTest(tree, { + nxReleaseConfig, + projectGraph, + filters, + releaseGroups, + releaseGroupToFilteredProjects, + userGivenSpecifier: '9.9.9' as SemverBumpType, + }); + + expect(readJson(tree, 'my-lib/package.json')).toMatchInlineSnapshot(` + { + "name": "my-lib", + "version": "9.9.9", + } + `); + + expect(readJson(tree, 'project-with-dependency-on-my-pkg/package.json')) + .toMatchInlineSnapshot(` + { + "dependencies": { + "my-lib": "9.9.9", + }, + "name": "project-with-dependency-on-my-pkg", + "version": "9.9.9", + } + `); + expect( + readJson(tree, 'project-with-devDependency-on-my-pkg/package.json') + ).toMatchInlineSnapshot(` + { + "devDependencies": { + "my-lib": "9.9.9", + }, + "name": "project-with-devDependency-on-my-pkg", + "version": "9.9.9", + } + `); + expect( + readJson( + tree, + 'another-project-with-devDependency-on-my-pkg/package.json' + ) + ).toMatchInlineSnapshot(` + { + "devDependencies": { + "my-lib": "9.9.9", + }, + "name": "another-project-with-devDependency-on-my-pkg", + "version": "9.9.9", + } + `); + }); + + it('should work with a ^ prefix', async () => { + const { + projectGraph, + nxReleaseConfig, + releaseGroups, + releaseGroupToFilteredProjects, + filters, + } = await createNxReleaseConfigAndPopulateWorkspace( + tree, + graphDefinition, + { + version: { + specifierSource: 'prompt', + versionPrefix: '^', + }, + } + ); + + // Manually set different version prefixes + setDifferentVersionPrefixes(tree); + + await releaseVersionGeneratorForTest(tree, { + nxReleaseConfig, + projectGraph, + filters, + releaseGroups, + releaseGroupToFilteredProjects, + userGivenSpecifier: '9.9.9' as SemverBumpType, + }); + + expect(readJson(tree, 'my-lib/package.json')).toMatchInlineSnapshot(` + { + "name": "my-lib", + "version": "9.9.9", + } + `); + + expect(readJson(tree, 'project-with-dependency-on-my-pkg/package.json')) + .toMatchInlineSnapshot(` + { + "dependencies": { + "my-lib": "^9.9.9", + }, + "name": "project-with-dependency-on-my-pkg", + "version": "9.9.9", + } + `); + expect( + readJson(tree, 'project-with-devDependency-on-my-pkg/package.json') + ).toMatchInlineSnapshot(` + { + "devDependencies": { + "my-lib": "^9.9.9", + }, + "name": "project-with-devDependency-on-my-pkg", + "version": "9.9.9", + } + `); + expect( + readJson( + tree, + 'another-project-with-devDependency-on-my-pkg/package.json' + ) + ).toMatchInlineSnapshot(` + { + "devDependencies": { + "my-lib": "^9.9.9", + }, + "name": "another-project-with-devDependency-on-my-pkg", + "version": "9.9.9", + } + `); + }); + + it('should work with a ~ prefix', async () => { + const { + projectGraph, + nxReleaseConfig, + releaseGroups, + releaseGroupToFilteredProjects, + filters, + } = await createNxReleaseConfigAndPopulateWorkspace( + tree, + graphDefinition, + { + version: { + specifierSource: 'prompt', + versionPrefix: '~', + }, + } + ); + + // Manually set different version prefixes + setDifferentVersionPrefixes(tree); + + await releaseVersionGeneratorForTest(tree, { + nxReleaseConfig, + projectGraph, + filters, + releaseGroups, + releaseGroupToFilteredProjects, + userGivenSpecifier: '9.9.9' as SemverBumpType, + }); + + expect(readJson(tree, 'my-lib/package.json')).toMatchInlineSnapshot(` + { + "name": "my-lib", + "version": "9.9.9", + } + `); + + expect(readJson(tree, 'project-with-dependency-on-my-pkg/package.json')) + .toMatchInlineSnapshot(` + { + "dependencies": { + "my-lib": "~9.9.9", + }, + "name": "project-with-dependency-on-my-pkg", + "version": "9.9.9", + } + `); + expect( + readJson(tree, 'project-with-devDependency-on-my-pkg/package.json') + ).toMatchInlineSnapshot(` + { + "devDependencies": { + "my-lib": "~9.9.9", + }, + "name": "project-with-devDependency-on-my-pkg", + "version": "9.9.9", + } + `); + expect( + readJson( + tree, + 'another-project-with-devDependency-on-my-pkg/package.json' + ) + ).toMatchInlineSnapshot(` + { + "devDependencies": { + "my-lib": "~9.9.9", + }, + "name": "another-project-with-devDependency-on-my-pkg", + "version": "9.9.9", + } + `); + }); + + it('should respect any existing prefix when explicitly set to "auto"', async () => { + const { + projectGraph, + nxReleaseConfig, + releaseGroups, + releaseGroupToFilteredProjects, + filters, + } = await createNxReleaseConfigAndPopulateWorkspace( + tree, + graphDefinition, + { + version: { + specifierSource: 'prompt', + versionPrefix: 'auto', + }, + } + ); + + // Manually set different version prefixes + setDifferentVersionPrefixes(tree); + + await releaseVersionGeneratorForTest(tree, { + nxReleaseConfig, + projectGraph, + filters, + releaseGroups, + releaseGroupToFilteredProjects, + userGivenSpecifier: '9.9.9' as SemverBumpType, + }); + + expect(readJson(tree, 'my-lib/package.json')).toMatchInlineSnapshot(` + { + "name": "my-lib", + "version": "9.9.9", + } + `); + + expect(readJson(tree, 'project-with-dependency-on-my-pkg/package.json')) + .toMatchInlineSnapshot(` + { + "dependencies": { + "my-lib": "~9.9.9", + }, + "name": "project-with-dependency-on-my-pkg", + "version": "9.9.9", + } + `); + expect( + readJson(tree, 'project-with-devDependency-on-my-pkg/package.json') + ).toMatchInlineSnapshot(` + { + "devDependencies": { + "my-lib": "^9.9.9", + }, + "name": "project-with-devDependency-on-my-pkg", + "version": "9.9.9", + } + `); + expect( + readJson( + tree, + 'another-project-with-devDependency-on-my-pkg/package.json' + ) + ).toMatchInlineSnapshot(` + { + "devDependencies": { + "my-lib": "9.9.9", + }, + "name": "another-project-with-devDependency-on-my-pkg", + "version": "9.9.9", + } + `); + }); + + it('should use the behavior of "auto" by default', async () => { + const { + projectGraph, + nxReleaseConfig, + releaseGroups, + releaseGroupToFilteredProjects, + filters, + } = await createNxReleaseConfigAndPopulateWorkspace( + tree, + graphDefinition, + { + version: { + specifierSource: 'prompt', + // No value, should default to "auto" + versionPrefix: undefined, + }, + } + ); + + // Manually set different version prefixes + setDifferentVersionPrefixes(tree); + + await releaseVersionGeneratorForTest(tree, { + nxReleaseConfig, + projectGraph, + filters, + releaseGroups, + releaseGroupToFilteredProjects, + userGivenSpecifier: '9.9.9' as SemverBumpType, + }); + + expect(readJson(tree, 'my-lib/package.json')).toMatchInlineSnapshot(` + { + "name": "my-lib", + "version": "9.9.9", + } + `); + + expect(readJson(tree, 'project-with-dependency-on-my-pkg/package.json')) + .toMatchInlineSnapshot(` + { + "dependencies": { + "my-lib": "~9.9.9", + }, + "name": "project-with-dependency-on-my-pkg", + "version": "9.9.9", + } + `); + expect( + readJson(tree, 'project-with-devDependency-on-my-pkg/package.json') + ).toMatchInlineSnapshot(` + { + "devDependencies": { + "my-lib": "^9.9.9", + }, + "name": "project-with-devDependency-on-my-pkg", + "version": "9.9.9", + } + `); + expect( + readJson( + tree, + 'another-project-with-devDependency-on-my-pkg/package.json' + ) + ).toMatchInlineSnapshot(` + { + "devDependencies": { + "my-lib": "9.9.9", + }, + "name": "another-project-with-devDependency-on-my-pkg", + "version": "9.9.9", + } + `); + }); + + it(`should exit with code one and print guidance for invalid prefix values`, async () => { + stubProcessExit = true; + + const { + projectGraph, + nxReleaseConfig, + releaseGroups, + releaseGroupToFilteredProjects, + filters, + } = await createNxReleaseConfigAndPopulateWorkspace( + tree, + graphDefinition, + { + version: { + specifierSource: 'prompt', + versionPrefix: '$' as any, + }, + } + ); + + const outputSpy = jest + .spyOn(output, 'error') + .mockImplementationOnce(() => { + return undefined as never; + }); + + await releaseVersionGeneratorForTest(tree, { + nxReleaseConfig, + projectGraph, + filters, + releaseGroups, + releaseGroupToFilteredProjects, + userGivenSpecifier: 'major' as SemverBumpType, + }); + + expect(outputSpy).toHaveBeenCalledWith({ + title: `Invalid value for versionPrefix: "$" + +Valid values are: "auto", "", "~", "^", "="`, + }); + + outputSpy.mockRestore(); + expect(processExitSpy).toHaveBeenCalledWith(1); + + stubProcessExit = false; + }); + }); + + describe('transitive updateDependents', () => { + it('should not update transitive dependents when updateDependents is set to "never" and the transitive dependents are not in the same batch', async () => { + const { + nxReleaseConfig, + projectGraph, + releaseGroups, + releaseGroupToFilteredProjects, + filters, + } = await createNxReleaseConfigAndPopulateWorkspace( + tree, + ` + __default__ ({ "projectsRelationship": "independent" }): + - my-lib@0.0.1 [js] + - project-with-dependency-on-my-lib@0.0.1 [js] + -> depends on my-lib + - project-with-transitive-dependency-on-my-lib@0.0.1 [js] + -> depends on project-with-dependency-on-my-lib + `, + { + version: { + updateDependents: 'never', + }, + }, + undefined, + { + projects: ['my-lib'], + } + ); + + const result = await releaseVersionGeneratorForTest(tree, { + nxReleaseConfig, + projectGraph, + filters, + userGivenSpecifier: '9.9.9' as SemverBumpType, + releaseGroups, + releaseGroupToFilteredProjects, + }); + + expect(result).toMatchInlineSnapshot(` + { + "callback": [Function], + "data": { + "my-lib": { + "currentVersion": "0.0.1", + "dependentProjects": [ + { + "dependencyCollection": "dependencies", + "rawVersionSpec": "0.0.1", + "source": "project-with-dependency-on-my-lib", + "target": "my-lib", + "type": "static", + }, + ], + "newVersion": "9.9.9", + }, + }, + } + `); + + expect(readJson(tree, 'my-lib/package.json')).toMatchInlineSnapshot(` + { + "name": "my-lib", + "version": "9.9.9", + } + `); + + expect(readJson(tree, 'project-with-dependency-on-my-lib/package.json')) + .toMatchInlineSnapshot(` + { + "dependencies": { + "my-lib": "0.0.1", + }, + "name": "project-with-dependency-on-my-lib", + "version": "0.0.1", + } + `); + + expect( + readJson( + tree, + 'project-with-transitive-dependency-on-my-lib/package.json' + ) + ).toMatchInlineSnapshot(` + { + "dependencies": { + "project-with-dependency-on-my-lib": "0.0.1", + }, + "name": "project-with-transitive-dependency-on-my-lib", + "version": "0.0.1", + } + `); + }); + + it('should always update transitive dependents when updateDependents is set to "auto"', async () => { + const { + nxReleaseConfig, + projectGraph, + releaseGroups, + releaseGroupToFilteredProjects, + filters, + } = await createNxReleaseConfigAndPopulateWorkspace( + tree, + ` + __default__ ({ "projectsRelationship": "independent" }): + - my-lib@0.0.1 [js] + - project-with-dependency-on-my-lib@0.0.1 [js] + -> depends on ~my-lib + - project-with-transitive-dependency-on-my-lib@0.0.1 [js] + -> depends on ^project-with-dependency-on-my-lib {devDependencies} + `, + { + version: { + updateDependents: 'auto', + }, + }, + undefined, + { + projects: ['my-lib'], + } + ); + + const result = await releaseVersionGeneratorForTest(tree, { + nxReleaseConfig, + projectGraph, + filters, + userGivenSpecifier: '9.9.9' as SemverBumpType, + releaseGroups, + releaseGroupToFilteredProjects, + }); + + expect(result).toMatchInlineSnapshot(` + { + "callback": [Function], + "data": { + "my-lib": { + "currentVersion": "0.0.1", + "dependentProjects": [ + { + "dependencyCollection": "dependencies", + "rawVersionSpec": "~0.0.1", + "source": "project-with-dependency-on-my-lib", + "target": "my-lib", + "type": "static", + }, + ], + "newVersion": "9.9.9", + }, + "project-with-dependency-on-my-lib": { + "currentVersion": "0.0.1", + "dependentProjects": [ + { + "dependencyCollection": "devDependencies", + "rawVersionSpec": "^0.0.1", + "source": "project-with-transitive-dependency-on-my-lib", + "target": "project-with-dependency-on-my-lib", + "type": "static", + }, + ], + "newVersion": "0.0.2", + }, + "project-with-transitive-dependency-on-my-lib": { + "currentVersion": "0.0.1", + "dependentProjects": [], + "newVersion": "0.0.2", + }, + }, + } + `); + + expect(readJson(tree, 'my-lib/package.json')).toMatchInlineSnapshot(` + { + "name": "my-lib", + "version": "9.9.9", + } + `); + + expect(readJson(tree, 'project-with-dependency-on-my-lib/package.json')) + .toMatchInlineSnapshot(` + { + "dependencies": { + "my-lib": "~9.9.9", + }, + "name": "project-with-dependency-on-my-lib", + "version": "0.0.2", + } + `); + + expect( + readJson( + tree, + 'project-with-transitive-dependency-on-my-lib/package.json' + ) + ).toMatchInlineSnapshot(` + { + "devDependencies": { + "project-with-dependency-on-my-lib": "^0.0.2", + }, + "name": "project-with-transitive-dependency-on-my-lib", + "version": "0.0.2", + } + `); + }); + }); + + describe('circular dependencies', () => { + // a <-> b + const circularGraphDefinition = ` + __default__ ({ "projectsRelationship": "independent" }): + - package-a@1.0.0 [js] + -> depends on package-b + - package-b@1.0.0 [js] + -> depends on package-a + `; + + describe("updateDependents: 'never'", () => { + it('should allow versioning of circular dependencies when not all projects are included in the current batch', async () => { + const { + nxReleaseConfig, + projectGraph, + releaseGroups, + releaseGroupToFilteredProjects, + filters, + } = await createNxReleaseConfigAndPopulateWorkspace( + tree, + circularGraphDefinition, + { + version: { + updateDependents: 'never', + }, + }, + undefined, + { + // version only package-a + projects: ['package-a'], + } + ); + + expect(readJson(tree, 'package-a/package.json')).toMatchInlineSnapshot(` + { + "dependencies": { + "package-b": "1.0.0", + }, + "name": "package-a", + "version": "1.0.0", + } + `); + expect(readJson(tree, 'package-b/package.json')).toMatchInlineSnapshot(` + { + "dependencies": { + "package-a": "1.0.0", + }, + "name": "package-b", + "version": "1.0.0", + } + `); + + expect( + await releaseVersionGeneratorForTest(tree, { + nxReleaseConfig, + releaseGroups, + releaseGroupToFilteredProjects, + filters, + projectGraph, + userGivenSpecifier: '2.0.0' as SemverBumpType, + }) + /** + * Note that this one captures a breaking change in versioning v2. + * + * In the legacy versioning, the dependentProjects array would be empty when only versioning package-a + * with updateDependents: 'never'. Now the dependentProjects array contains the dependent project even + * though it is isn't versioned (because they are kind of separate concerns). + */ + ).toMatchInlineSnapshot(` + { + "callback": [Function], + "data": { + "package-a": { + "currentVersion": "1.0.0", + "dependentProjects": [ + { + "dependencyCollection": "dependencies", + "rawVersionSpec": "1.0.0", + "source": "package-b", + "target": "package-a", + "type": "static", + }, + ], + "newVersion": "2.0.0", + }, + }, + } + `); + + expect(readJson(tree, 'package-a/package.json')).toMatchInlineSnapshot(` + { + "dependencies": { + "package-b": "1.0.0", + }, + "name": "package-a", + "version": "2.0.0", + } + `); + // package-b is unchanged + expect(readJson(tree, 'package-b/package.json')).toMatchInlineSnapshot(` + { + "dependencies": { + "package-a": "1.0.0", + }, + "name": "package-b", + "version": "1.0.0", + } + `); + }); + + it('should allow versioning of circular dependencies when all projects are included in the current batch', async () => { + const { + nxReleaseConfig, + projectGraph, + releaseGroups, + releaseGroupToFilteredProjects, + filters, + } = await createNxReleaseConfigAndPopulateWorkspace( + tree, + circularGraphDefinition, + { + version: { + updateDependents: 'never', + }, + }, + undefined, + { + // version both packages + projects: ['package-a', 'package-b'], + } + ); + + expect(readJson(tree, 'package-a/package.json')).toMatchInlineSnapshot(` + { + "dependencies": { + "package-b": "1.0.0", + }, + "name": "package-a", + "version": "1.0.0", + } + `); + expect(readJson(tree, 'package-b/package.json')).toMatchInlineSnapshot(` + { + "dependencies": { + "package-a": "1.0.0", + }, + "name": "package-b", + "version": "1.0.0", + } + `); + + expect( + await releaseVersionGeneratorForTest(tree, { + nxReleaseConfig, + releaseGroups, + releaseGroupToFilteredProjects, + filters, + projectGraph, + userGivenSpecifier: '2.0.0' as SemverBumpType, + }) + ).toMatchInlineSnapshot(` + { + "callback": [Function], + "data": { + "package-a": { + "currentVersion": "1.0.0", + "dependentProjects": [ + { + "dependencyCollection": "dependencies", + "rawVersionSpec": "1.0.0", + "source": "package-b", + "target": "package-a", + "type": "static", + }, + ], + "newVersion": "2.0.0", + }, + "package-b": { + "currentVersion": "1.0.0", + "dependentProjects": [ + { + "dependencyCollection": "dependencies", + "rawVersionSpec": "1.0.0", + "source": "package-a", + "target": "package-b", + "type": "static", + }, + ], + "newVersion": "2.0.0", + }, + }, + } + `); + + // Both the version of package-a, and the dependency on package-b are updated to 2.0.0 + expect(readJson(tree, 'package-a/package.json')).toMatchInlineSnapshot(` + { + "dependencies": { + "package-b": "2.0.0", + }, + "name": "package-a", + "version": "2.0.0", + } + `); + // Both the version of package-b, and the dependency on package-a are updated to 2.0.0 + expect(readJson(tree, 'package-b/package.json')).toMatchInlineSnapshot(` + { + "dependencies": { + "package-a": "2.0.0", + }, + "name": "package-b", + "version": "2.0.0", + } + `); + }); + }); + + describe("updateDependents: 'auto'", () => { + it('should allow versioning of circular dependencies when not all projects are included in the current batch', async () => { + const { + nxReleaseConfig, + projectGraph, + releaseGroups, + releaseGroupToFilteredProjects, + filters, + } = await createNxReleaseConfigAndPopulateWorkspace( + tree, + circularGraphDefinition, + { + version: { + updateDependents: 'auto', + }, + }, + undefined, + { + // version only package-a + projects: ['package-a'], + } + ); + + expect(readJson(tree, 'package-a/package.json')).toMatchInlineSnapshot(` + { + "dependencies": { + "package-b": "1.0.0", + }, + "name": "package-a", + "version": "1.0.0", + } + `); + expect(readJson(tree, 'package-b/package.json')).toMatchInlineSnapshot(` + { + "dependencies": { + "package-a": "1.0.0", + }, + "name": "package-b", + "version": "1.0.0", + } + `); + + expect( + await releaseVersionGeneratorForTest(tree, { + nxReleaseConfig, + releaseGroups, + releaseGroupToFilteredProjects, + filters, + projectGraph, + userGivenSpecifier: '2.0.0' as SemverBumpType, + }) + ).toMatchInlineSnapshot(` + { + "callback": [Function], + "data": { + "package-a": { + "currentVersion": "1.0.0", + "dependentProjects": [ + { + "dependencyCollection": "dependencies", + "rawVersionSpec": "1.0.0", + "source": "package-b", + "target": "package-a", + "type": "static", + }, + ], + "newVersion": "2.0.0", + }, + "package-b": { + "currentVersion": "1.0.0", + "dependentProjects": [ + { + "dependencyCollection": "dependencies", + "rawVersionSpec": "1.0.0", + "source": "package-a", + "target": "package-b", + "type": "static", + }, + ], + "newVersion": "1.0.1", + }, + }, + } + `); + + // The version of package-a has been updated to 2.0.0, and the dependency on package-b has been updated to 1.0.1 + expect(readJson(tree, 'package-a/package.json')).toMatchInlineSnapshot(` + { + "dependencies": { + "package-b": "1.0.1", + }, + "name": "package-a", + "version": "2.0.0", + } + `); + // The version of package-b has been patched to 1.0.1, and the dependency on package-a has been updated to 2.0.0 + expect(readJson(tree, 'package-b/package.json')).toMatchInlineSnapshot(` + { + "dependencies": { + "package-a": "2.0.0", + }, + "name": "package-b", + "version": "1.0.1", + } + `); + }); + + it('should allow versioning of circular dependencies when all projects are included in the current batch', async () => { + const { + nxReleaseConfig, + projectGraph, + releaseGroups, + releaseGroupToFilteredProjects, + filters, + } = await createNxReleaseConfigAndPopulateWorkspace( + tree, + circularGraphDefinition, + { + version: { + updateDependents: 'auto', + }, + }, + undefined, + { + // version both packages + projects: ['package-a', 'package-b'], + } + ); + + expect(readJson(tree, 'package-a/package.json')).toMatchInlineSnapshot(` + { + "dependencies": { + "package-b": "1.0.0", + }, + "name": "package-a", + "version": "1.0.0", + } + `); + expect(readJson(tree, 'package-b/package.json')).toMatchInlineSnapshot(` + { + "dependencies": { + "package-a": "1.0.0", + }, + "name": "package-b", + "version": "1.0.0", + } + `); + + expect( + await releaseVersionGeneratorForTest(tree, { + nxReleaseConfig, + releaseGroups, + releaseGroupToFilteredProjects, + filters, + projectGraph, + userGivenSpecifier: '2.0.0' as SemverBumpType, + }) + ).toMatchInlineSnapshot(` + { + "callback": [Function], + "data": { + "package-a": { + "currentVersion": "1.0.0", + "dependentProjects": [ + { + "dependencyCollection": "dependencies", + "rawVersionSpec": "1.0.0", + "source": "package-b", + "target": "package-a", + "type": "static", + }, + ], + "newVersion": "2.0.0", + }, + "package-b": { + "currentVersion": "1.0.0", + "dependentProjects": [ + { + "dependencyCollection": "dependencies", + "rawVersionSpec": "1.0.0", + "source": "package-a", + "target": "package-b", + "type": "static", + }, + ], + "newVersion": "2.0.0", + }, + }, + } + `); + + // Both the version of package-a, and the dependency on package-b are updated to 2.0.0 + expect(readJson(tree, 'package-a/package.json')).toMatchInlineSnapshot(` + { + "dependencies": { + "package-b": "2.0.0", + }, + "name": "package-a", + "version": "2.0.0", + } + `); + // Both the version of package-b, and the dependency on package-a are updated to 2.0.0 + expect(readJson(tree, 'package-b/package.json')).toMatchInlineSnapshot(` + { + "dependencies": { + "package-a": "2.0.0", + }, + "name": "package-b", + "version": "2.0.0", + } + `); + }); + }); + }); + + describe('preserveLocalDependencyProtocols', () => { + it('should preserve local `workspace:` references when preserveLocalDependencyProtocols is true', async () => { + const { + nxReleaseConfig, + projectGraph, + releaseGroups, + releaseGroupToFilteredProjects, + filters, + } = await createNxReleaseConfigAndPopulateWorkspace( + tree, + ` + __default__ ({ "projectsRelationship": "independent" }): + - package-a@1.0.0 [js] + -> depends on package-b(workspace:*) + - package-b@1.0.0 [js] + `, + { + version: { + specifierSource: 'prompt', + preserveLocalDependencyProtocols: true, + }, + }, + undefined, + { + // version only package-b + projects: ['package-b'], + } + ); + + expect( + await releaseVersionGeneratorForTest(tree, { + nxReleaseConfig, + projectGraph, + filters, + releaseGroups, + releaseGroupToFilteredProjects, + userGivenSpecifier: '2.0.0' as SemverBumpType, + }) + ).toMatchInlineSnapshot(` + { + "callback": [Function], + "data": { + "package-a": { + "currentVersion": "1.0.0", + "dependentProjects": [], + "newVersion": "1.0.1", + }, + "package-b": { + "currentVersion": "1.0.0", + "dependentProjects": [ + { + "dependencyCollection": "dependencies", + "rawVersionSpec": "workspace:*", + "source": "package-a", + "target": "package-b", + "type": "static", + }, + ], + "newVersion": "2.0.0", + }, + }, + } + `); + + expect(readJson(tree, 'package-a/package.json')).toMatchInlineSnapshot(` + { + "dependencies": { + "package-b": "workspace:*", + }, + "name": "package-a", + "version": "1.0.1", + } + `); + + expect(readJson(tree, 'package-b/package.json')).toMatchInlineSnapshot(` + { + "name": "package-b", + "version": "2.0.0", + } + `); + }); + + it('should preserve local `file:` references when preserveLocalDependencyProtocols is true', async () => { + const { + nxReleaseConfig, + projectGraph, + releaseGroups, + releaseGroupToFilteredProjects, + filters, + } = await createNxReleaseConfigAndPopulateWorkspace( + tree, + ` + __default__ ({ "projectsRelationship": "independent" }): + - package-a@1.0.0 [js] + -> depends on package-b(file:../package-b) + - package-b@1.0.0 [js] + `, + { + version: { + specifierSource: 'prompt', + preserveLocalDependencyProtocols: true, + }, + }, + undefined, + { + // version only package-b + projects: ['package-b'], + } + ); + + expect( + await releaseVersionGeneratorForTest(tree, { + nxReleaseConfig, + projectGraph, + filters, + releaseGroups, + releaseGroupToFilteredProjects, + userGivenSpecifier: '2.0.0' as SemverBumpType, + }) + ).toMatchInlineSnapshot(` + { + "callback": [Function], + "data": { + "package-a": { + "currentVersion": "1.0.0", + "dependentProjects": [], + "newVersion": "1.0.1", + }, + "package-b": { + "currentVersion": "1.0.0", + "dependentProjects": [ + { + "dependencyCollection": "dependencies", + "rawVersionSpec": "file:../package-b", + "source": "package-a", + "target": "package-b", + "type": "static", + }, + ], + "newVersion": "2.0.0", + }, + }, + } + `); + + expect(readJson(tree, 'package-a/package.json')).toMatchInlineSnapshot(` + { + "dependencies": { + "package-b": "file:../package-b", + }, + "name": "package-a", + "version": "1.0.1", + } + `); + + expect(readJson(tree, 'package-b/package.json')).toMatchInlineSnapshot(` + { + "name": "package-b", + "version": "2.0.0", + } + `); + }); + }); + + it('should not double patch transitive dependents that are already direct dependents', async () => { + const { + nxReleaseConfig, + projectGraph, + releaseGroups, + releaseGroupToFilteredProjects, + filters, + } = await createNxReleaseConfigAndPopulateWorkspace( + tree, + ` + __default__ ({ "projectsRelationship": "independent" }): + - core@1.0.0 [js:@slateui/core] + - buttons@1.0.0 [js:@slateui/buttons] + -> depends on core + - forms@1.0.0 [js:@slateui/forms] + -> depends on core + -> depends on buttons + `, + { + version: { + specifierSource: 'prompt', + }, + }, + undefined, + { + projects: ['core'], + } + ); + + expect(readJson(tree, 'core/package.json')).toMatchInlineSnapshot(` + { + "name": "@slateui/core", + "version": "1.0.0", + } + `); + + expect(readJson(tree, 'buttons/package.json')).toMatchInlineSnapshot(` + { + "dependencies": { + "@slateui/core": "1.0.0", + }, + "name": "@slateui/buttons", + "version": "1.0.0", + } + `); + + expect(readJson(tree, 'forms/package.json')).toMatchInlineSnapshot(` + { + "dependencies": { + "@slateui/buttons": "1.0.0", + "@slateui/core": "1.0.0", + }, + "name": "@slateui/forms", + "version": "1.0.0", + } + `); + + expect( + await releaseVersionGeneratorForTest(tree, { + nxReleaseConfig, + projectGraph, + filters, + releaseGroups, + releaseGroupToFilteredProjects, + // Bump core to 2.0.0, which will cause buttons and forms to be patched to 1.0.1 + // This prevents a regression against an issue where forms would end up being patched twice to 1.0.2 in this scenario + userGivenSpecifier: '2.0.0' as SemverBumpType, + }) + ).toMatchInlineSnapshot(` + { + "callback": [Function], + "data": { + "buttons": { + "currentVersion": "1.0.0", + "dependentProjects": [ + { + "dependencyCollection": "dependencies", + "rawVersionSpec": "1.0.0", + "source": "forms", + "target": "buttons", + "type": "static", + }, + ], + "newVersion": "1.0.1", + }, + "core": { + "currentVersion": "1.0.0", + "dependentProjects": [ + { + "dependencyCollection": "dependencies", + "rawVersionSpec": "1.0.0", + "source": "buttons", + "target": "core", + "type": "static", + }, + { + "dependencyCollection": "dependencies", + "rawVersionSpec": "1.0.0", + "source": "forms", + "target": "core", + "type": "static", + }, + ], + "newVersion": "2.0.0", + }, + "forms": { + "currentVersion": "1.0.0", + "dependentProjects": [], + "newVersion": "1.0.1", + }, + }, + } + `); + + expect(readJson(tree, 'core/package.json')).toMatchInlineSnapshot(` + { + "name": "@slateui/core", + "version": "2.0.0", + } + `); + + expect(readJson(tree, 'buttons/package.json')).toMatchInlineSnapshot(` + { + "dependencies": { + "@slateui/core": "2.0.0", + }, + "name": "@slateui/buttons", + "version": "1.0.1", + } + `); + + expect(readJson(tree, 'forms/package.json')).toMatchInlineSnapshot(` + { + "dependencies": { + "@slateui/buttons": "1.0.1", + "@slateui/core": "2.0.0", + }, + "name": "@slateui/forms", + "version": "1.0.1", + } + `); + }); + + describe('release-version-workspace-root-project', () => { + describe('independent projects relationship', () => { + describe('with workspace root as a project in the graph', () => { + it('should not error when run with custom manifestRootsToUpdate containing {projectRoot}', async () => { + // Create the additional expected manifests in dist (would have been created by some build process) + writeJson(tree, 'dist/my-lib/package.json', { + name: 'my-lib', + version: '0.0.1', + }); + writeJson( + tree, + 'dist/project-with-dependency-on-my-pkg/package.json', + { + name: 'project-with-dependency-on-my-pkg', + version: '0.0.1', + dependencies: { + 'my-lib': '0.0.1', + }, + } + ); + writeJson( + tree, + 'dist/project-with-devDependency-on-my-pkg/package.json', + { + name: 'project-with-devDependency-on-my-pkg', + version: '0.0.1', + devDependencies: { + 'my-lib': '0.0.1', + }, + } + ); + + const { + nxReleaseConfig, + projectGraph, + releaseGroups, + releaseGroupToFilteredProjects, + filters, + } = await createNxReleaseConfigAndPopulateWorkspace( + tree, + ` + myReleaseGroup ({ "projectsRelationship": "independent" }): + - my-lib@0.0.1 [js] + - root[.]@0.0.1 [js] + - project-with-dependency-on-my-pkg@0.0.1 [js] + -> depends on my-lib + - project-with-devDependency-on-my-pkg@0.0.1 [js] + -> depends on my-lib {devDependencies} + `, + { + version: { + manifestRootsToUpdate: ['dist/{projectRoot}'], + currentVersionResolver: 'disk', + }, + }, + undefined + ); + + expect( + await releaseVersionGeneratorForTest(tree, { + nxReleaseConfig, + projectGraph, + filters, + releaseGroups, + releaseGroupToFilteredProjects, + userGivenSpecifier: 'patch', + }) + ).toMatchInlineSnapshot(` + { + "callback": [Function], + "data": { + "my-lib": { + "currentVersion": "0.0.1", + "dependentProjects": [ + { + "dependencyCollection": "dependencies", + "rawVersionSpec": "0.0.1", + "source": "project-with-dependency-on-my-pkg", + "target": "my-lib", + "type": "static", + }, + { + "dependencyCollection": "devDependencies", + "rawVersionSpec": "0.0.1", + "source": "project-with-devDependency-on-my-pkg", + "target": "my-lib", + "type": "static", + }, + ], + "newVersion": "0.0.2", + }, + "project-with-dependency-on-my-pkg": { + "currentVersion": "0.0.1", + "dependentProjects": [], + "newVersion": "0.0.2", + }, + "project-with-devDependency-on-my-pkg": { + "currentVersion": "0.0.1", + "dependentProjects": [], + "newVersion": "0.0.2", + }, + }, + } + `); + }); + + it('should not error when run with custom manifestRootsToUpdate containing {projectRoot} when one project does not match the others', async () => { + // Create the additional expected manifests in dist (would have been created by some build process) + writeJson(tree, 'dist/my-lib/package.json', { + name: 'my-lib', + version: '0.0.1', + }); + writeJson(tree, 'dist/my-lib-2/package.json', { + name: 'my-lib-2', + version: '0.0.1', + }); + + const { + nxReleaseConfig, + projectGraph, + releaseGroups, + releaseGroupToFilteredProjects, + filters, + } = await createNxReleaseConfigAndPopulateWorkspace( + tree, + ` + myReleaseGroup ({ "projectsRelationship": "independent" }): + - depends-on-my-lib@0.0.1 [js] + -> depends on my-lib + -> release config overrides { "version": { "manifestRootsToUpdate": ["dist/pkgs/depends-on-my-lib"] } } + - my-lib@0.0.1 [js] + - root[.]@0.0.1 [js:@proj/source] + - my-lib-2@0.0.1 [js] + `, + { + version: { + manifestRootsToUpdate: ['dist/{projectRoot}'], + currentVersionResolver: 'disk', + }, + }, + undefined, + { + // depends-on-my-lib will get its dependencies updated in package.json because my-lib is being versioned + // this will happen regardless of if depends-on-my-lib should be versioned + projects: ['my-lib', 'my-lib-2'], + } + ); + + expect( + await releaseVersionGeneratorForTest(tree, { + nxReleaseConfig, + projectGraph, + filters, + releaseGroups, + releaseGroupToFilteredProjects, + userGivenSpecifier: 'patch', + }) + ).toMatchInlineSnapshot(` + { + "callback": [Function], + "data": { + "depends-on-my-lib": { + "currentVersion": "0.0.1", + "dependentProjects": [], + "newVersion": "0.0.2", + }, + "my-lib": { + "currentVersion": "0.0.1", + "dependentProjects": [ + { + "dependencyCollection": "dependencies", + "rawVersionSpec": "0.0.1", + "source": "depends-on-my-lib", + "target": "my-lib", + "type": "static", + }, + ], + "newVersion": "0.0.2", + }, + "my-lib-2": { + "currentVersion": "0.0.1", + "dependentProjects": [], + "newVersion": "0.0.2", + }, + }, + } + `); + + expect(readJson(tree, 'dist/pkgs/depends-on-my-lib/package.json')) + .toMatchInlineSnapshot(` + { + "dependencies": { + "my-lib": "0.0.2", + }, + "name": "depends-on-my-lib", + "version": "0.0.2", + } + `); + + expect(readJson(tree, 'dist/my-lib/package.json')) + .toMatchInlineSnapshot(` + { + "name": "my-lib", + "version": "0.0.2", + } + `); + + expect(readJson(tree, 'dist/my-lib-2/package.json')) + .toMatchInlineSnapshot(` + { + "name": "my-lib-2", + "version": "0.0.2", + } + `); + + expect(readJson(tree, 'package.json')).toMatchInlineSnapshot(` + { + "dependencies": {}, + "devDependencies": {}, + "name": "@proj/source", + } + `); + }); + }); + }); + }); +}); diff --git a/packages/nx/src/command-line/release/version/resolve-current-version.spec.ts b/packages/nx/src/command-line/release/version/resolve-current-version.spec.ts new file mode 100644 index 0000000000..3ec5ec4a2e --- /dev/null +++ b/packages/nx/src/command-line/release/version/resolve-current-version.spec.ts @@ -0,0 +1,206 @@ +import type { ProjectGraphProjectNode } from '../../../config/project-graph'; +import { createTreeWithEmptyWorkspace } from '../../../generators/testing-utils/create-tree-with-empty-workspace'; +import type { Tree } from '../../../generators/tree'; +import { ReleaseGroupWithName } from '../config/filter-release-groups'; +import { ProjectLogger } from './project-logger'; +import type { FinalConfigForProject } from './release-group-processor'; +import { resolveCurrentVersion } from './resolve-current-version'; +import { VersionActions } from './version-actions'; + +// TODO: Add unit test coverage for the other currentVersionResolver options +describe('resolveCurrentVersion', () => { + let tree: Tree; + + beforeEach(() => { + tree = createTreeWithEmptyWorkspace(); + }); + + describe('disk', () => { + const finalConfigForProject: FinalConfigForProject = { + specifierSource: 'prompt', + currentVersionResolver: 'disk', + currentVersionResolverMetadata: {}, + fallbackCurrentVersionResolver: 'disk', + versionPrefix: 'auto', + preserveLocalDependencyProtocols: true, + manifestRootsToUpdate: [], + versionActionsOptions: {}, + }; + + class TestVersionActions extends VersionActions { + validManifestFilenames = ['package.json']; + + async readCurrentVersionFromSourceManifest() { + return { + currentVersion: '1.2.3', + manifestPath: 'package.json', + }; + } + async readCurrentVersionFromRegistry() { + return { + currentVersion: '1.2.3', + logText: 'https://example.com/fake-registry', + }; + } + async updateProjectVersion() { + return []; + } + async readCurrentVersionOfDependency() { + return { + currentVersion: '1.2.3', + dependencyCollection: 'dependencies', + }; + } + async isLocalDependencyProtocol() { + return false; + } + async updateProjectDependencies() { + return []; + } + } + + class TestProjectLogger extends ProjectLogger { + constructor(projectName: string) { + super(projectName); + } + override buffer(message: string) {} + } + + it('should resolve the current version from disk based on the provided versionActions instance, when currentVersionResolver is set to disk on the releaseGroup and nothing is set on the project node', async () => { + const projectGraphNode: ProjectGraphProjectNode = { + name: 'test', + type: 'lib' as const, + data: { + root: tree.root, + }, + // No release config, should use the releaseGroup config + }; + const releaseGroup = { + name: 'release-group', + version: { + currentVersionResolver: 'disk', + }, + } as unknown as ReleaseGroupWithName; + + const currentVersion = await resolveCurrentVersion( + tree, + projectGraphNode, + releaseGroup, + new TestVersionActions( + releaseGroup, + projectGraphNode, + finalConfigForProject + ), + new TestProjectLogger(projectGraphNode.name), + new Map(), + finalConfigForProject, + undefined + ); + expect(currentVersion).toBe('1.2.3'); + }); + + it('should resolve the current version from disk based on the provided versionActions instance, when currentVersionResolver is set to disk on the project node, regardless of what is set on the releaseGroup', async () => { + const projectGraphNode: ProjectGraphProjectNode = { + name: 'test', + type: 'lib' as const, + data: { + root: tree.root, + release: { + version: { + currentVersionResolver: 'disk', + }, + }, + }, + }; + const releaseGroup = { + name: 'release-group', + version: { + // Should be ignored in favor of the project node + currentVersionResolver: 'SOMETHING_ELSE', + }, + } as unknown as ReleaseGroupWithName; + + const currentVersion = await resolveCurrentVersion( + tree, + projectGraphNode, + releaseGroup, + new TestVersionActions( + releaseGroup, + projectGraphNode, + finalConfigForProject + ), + new TestProjectLogger(projectGraphNode.name), + new Map(), + finalConfigForProject, + undefined + ); + expect(currentVersion).toBe('1.2.3'); + }); + + it('should throw an error if the currentVersionResolver is set to disk but the configured versionActions does not support a manifest file', async () => { + const projectGraphNode: ProjectGraphProjectNode = { + name: 'test', + type: 'lib' as const, + data: { + root: tree.root, + release: { + version: { + currentVersionResolver: 'disk', + }, + }, + }, + }; + const releaseGroup = { + name: 'release-group', + } as unknown as ReleaseGroupWithName; + + class TestVersionActionsWithoutManifest extends VersionActions { + validManifestFilenames = null; + + async readCurrentVersionFromSourceManifest() { + return null; + } + async readCurrentVersionFromRegistry() { + return { + currentVersion: '1.2.3', + logText: 'https://example.com/fake-registry', + }; + } + async updateProjectVersion() { + return []; + } + async readCurrentVersionOfDependency() { + return { + currentVersion: '1.2.3', + dependencyCollection: 'dependencies', + }; + } + async isLocalDependencyProtocol() { + return false; + } + async updateProjectDependencies() { + return []; + } + } + + await expect( + resolveCurrentVersion( + tree, + projectGraphNode, + releaseGroup, + new TestVersionActionsWithoutManifest( + releaseGroup, + projectGraphNode, + finalConfigForProject + ), + new TestProjectLogger(projectGraphNode.name), + new Map(), + finalConfigForProject, + undefined + ) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"For project "test", the "currentVersionResolver" is set to "disk" but it is using "versionActions" of type "TestVersionActionsWithoutManifest". This is invalid because "TestVersionActionsWithoutManifest" does not support a manifest file. You should use a different "currentVersionResolver" or use a different "versionActions" implementation that supports a manifest file"` + ); + }); + }); +}); diff --git a/packages/nx/src/command-line/release/version/resolve-current-version.ts b/packages/nx/src/command-line/release/version/resolve-current-version.ts new file mode 100644 index 0000000000..250cfc4f88 --- /dev/null +++ b/packages/nx/src/command-line/release/version/resolve-current-version.ts @@ -0,0 +1,410 @@ +import chalk = require('chalk'); +import { prompt } from 'enquirer'; +import * as ora from 'ora'; +import { NxReleaseVersionV2Configuration } from '../../../config/nx-json'; +import type { ProjectGraphProjectNode } from '../../../config/project-graph'; +import type { Tree } from '../../../generators/tree'; +import type { ReleaseGroupWithName } from '../config/filter-release-groups'; +import { getLatestGitTagForPattern } from '../utils/git'; +import { ProjectLogger } from './project-logger'; +import type { FinalConfigForProject } from './release-group-processor'; +import { VersionActions } from './version-actions'; + +export async function resolveCurrentVersion( + tree: Tree, + projectGraphNode: ProjectGraphProjectNode, + releaseGroup: ReleaseGroupWithName, + versionActions: VersionActions, + logger: ProjectLogger, + cachedCurrentVersionsPerFixedReleaseGroup: Map< + string, // release group name + { + currentVersion: string; + originatingProjectName: string; + logText: string; + } + >, + finalConfigForProject: FinalConfigForProject, + releaseTagPattern: string, + latestMatchingGitTag?: Awaited> +): Promise { + switch (finalConfigForProject.currentVersionResolver) { + case 'none': + return null; + case 'disk': { + return resolveCurrentVersionFromDisk( + tree, + projectGraphNode, + versionActions, + logger + ); + } + case 'registry': { + return resolveCurrentVersionFromRegistry( + tree, + projectGraphNode, + releaseGroup, + versionActions, + logger, + cachedCurrentVersionsPerFixedReleaseGroup, + finalConfigForProject + ); + } + case 'git-tag': { + return resolveCurrentVersionFromGitTag( + tree, + projectGraphNode, + releaseGroup, + versionActions, + logger, + cachedCurrentVersionsPerFixedReleaseGroup, + finalConfigForProject, + releaseTagPattern, + latestMatchingGitTag + ); + } + default: + throw new Error( + `Invalid value for "currentVersionResolver": ${finalConfigForProject.currentVersionResolver}` + ); + } +} + +/** + * Attempt to resolve the current version from the manifest file on disk. + * + * Not all VersionActions implementations support a manifest file, in which case the logic will handle either thrown errors + * or null values being returned from the readCurrentVersionFromSourceManifest method and throw a clear user-facing error. + */ +export async function resolveCurrentVersionFromDisk( + tree: Tree, + projectGraphNode: ProjectGraphProjectNode, + versionActions: VersionActions, + logger: ProjectLogger +): Promise { + if (!versionActions.validManifestFilenames?.length) { + throw new Error( + `For project "${projectGraphNode.name}", the "currentVersionResolver" is set to "disk" but it is using "versionActions" of type "${versionActions.constructor.name}". This is invalid because "${versionActions.constructor.name}" does not support a manifest file. You should use a different "currentVersionResolver" or use a different "versionActions" implementation that supports a manifest file` + ); + } + const nullVersionError = new Error( + `For project "${projectGraphNode.name}", the "currentVersionResolver" is set to "disk" and it is using "versionActions" of type "${versionActions.constructor.name}" which failed to resolve the current version from the manifest file on disk` + ); + try { + const res = await versionActions.readCurrentVersionFromSourceManifest(tree); + if (!res) { + throw nullVersionError; + } + const { currentVersion, manifestPath } = res; + logger.buffer( + `📄 Resolved the current version as ${currentVersion} from manifest: ${manifestPath}` + ); + return currentVersion; + } catch (err) { + if (err === nullVersionError) { + throw err; + } + throw new Error( + `The project "${ + projectGraphNode.name + }" does not have a ${versionActions.validManifestFilenames.join( + ' or ' + )} file available in ./${projectGraphNode.data.root}. + +To fix this you will either need to add a ${versionActions.validManifestFilenames.join( + ' or ' + )} file at that location, or configure "release" within your nx.json to use a different "currentVersionResolver" or "versionActions" implementation that supports this setup` + ); + } +} + +export async function resolveCurrentVersionFromRegistry( + tree: Tree, + projectGraphNode: ProjectGraphProjectNode, + releaseGroup: ReleaseGroupWithName, + versionActions: VersionActions, + logger: ProjectLogger, + cachedCurrentVersionsPerFixedReleaseGroup: Map< + string, // release group name + { + currentVersion: string; + originatingProjectName: string; + logText?: string; + } + >, + finalConfigForProject: FinalConfigForProject +): Promise { + /** + * In the case of fixed release groups that are configured to resolve the current version from a registry, + * it would be a waste of time and resources to make requests to the registry, or resolve one of the fallbacks, + * for each individual project, therefore we maintain a cache of the current version for each applicable release group here. + */ + const cached = cachedCurrentVersionsPerFixedReleaseGroup.get( + releaseGroup.name + ); + if (cached) { + const logText = cached.logText ? ` ${cached.logText}` : ''; + logger.buffer( + `🔄 Reusing the current version ${cached.currentVersion} already resolved for ${cached.originatingProjectName}${logText}` + ); + return cached.currentVersion; + } + + let registryTxt = ''; + + const spinner = ora( + `Resolving the current version for ${projectGraphNode.name} from the configured registry...` + ); + spinner.color = 'cyan'; + spinner.start(); + + try { + const res = await versionActions.readCurrentVersionFromRegistry( + tree, + finalConfigForProject.currentVersionResolverMetadata + ); + if (!res) { + // Not a user-facing error + throw new Error( + 'Registry not applicable for this version actions implementation' + ); + } + const { currentVersion, logText } = res; + spinner.stop(); + + registryTxt = logText?.length > 0 ? `: ${logText}` : ''; + if (!currentVersion) { + // Not a user-facing error + throw new Error('No version found in the registry'); + } + logger.buffer( + `🔍 Resolved the current version as ${currentVersion} from the remote registry${registryTxt}` + ); + // Write to the cache if the release group is fixed + if (releaseGroup.projectsRelationship === 'fixed') { + cachedCurrentVersionsPerFixedReleaseGroup.set(releaseGroup.name, { + currentVersion, + originatingProjectName: projectGraphNode.name, + logText: `from the registry${registryTxt}`, + }); + } + return currentVersion; + } catch { + spinner.stop(); + + if (finalConfigForProject.fallbackCurrentVersionResolver === 'disk') { + if (!versionActions.validManifestFilenames?.length) { + throw new Error( + `For project "${projectGraphNode.name}", the "currentVersionResolver" is set to "registry" with a "fallbackCurrentVersionResolver" of "disk" but it is using "versionActions" of type "${versionActions.constructor.name}". This is invalid because "${versionActions.constructor.name}" does not support a manifest file. You should use a different "fallbackCurrentVersionResolver" or use a different "versionActions" implementation that supports a manifest file` + ); + } + + const fromDiskRes = + await versionActions.readCurrentVersionFromSourceManifest(tree); + // Fallback on disk is available, return it directly + if (fromDiskRes && fromDiskRes.currentVersion) { + logger.buffer( + `⚠️ Unable to resolve the current version from the registry${registryTxt}. Falling back to the version ${fromDiskRes.currentVersion} in manifest: ${fromDiskRes.manifestPath}` + ); + // Write to the cache if the release group is fixed + if (releaseGroup.projectsRelationship === 'fixed') { + cachedCurrentVersionsPerFixedReleaseGroup.set(releaseGroup.name, { + currentVersion: fromDiskRes.currentVersion, + originatingProjectName: projectGraphNode.name, + logText: `from the disk fallback`, + }); + } + return fromDiskRes.currentVersion; + } + } + + // At this point the fallback on disk is also not available/configured, allow one final interactive fallback, but only when using version-plans or conventional-commits + if (finalConfigForProject.specifierSource === 'prompt') { + throw new Error( + `Unable to resolve the current version from the registry${registryTxt}. Please ensure that the package exists in the registry in order to use the "registry" currentVersionResolver. Alternatively, you can use the --first-release option or set "release.version.fallbackCurrentVersionResolver" to "disk" in order to fallback to the version on disk when the registry lookup fails.` + ); + } + const currentVersionFromPromptFallback = + await handleNoAvailableDiskFallback({ + logger, + projectName: projectGraphNode.name, + versionActions, + specifierSource: finalConfigForProject.specifierSource, + currentVersionSourceMessage: `from the registry${registryTxt}`, + resolutionSuggestion: `you should publish an initial version to the registry`, + }); + // Write to the cache if the release group is fixed + if (releaseGroup.projectsRelationship === 'fixed') { + cachedCurrentVersionsPerFixedReleaseGroup.set(releaseGroup.name, { + currentVersion: currentVersionFromPromptFallback, + originatingProjectName: projectGraphNode.name, + logText: `from the prompt fallback`, + }); + } + return currentVersionFromPromptFallback; + } +} + +export async function resolveCurrentVersionFromGitTag( + tree: Tree, + projectGraphNode: ProjectGraphProjectNode, + releaseGroup: ReleaseGroupWithName, + versionActions: VersionActions, + logger: ProjectLogger, + cachedCurrentVersionsPerFixedReleaseGroup: Map< + string, // release group name + { + currentVersion: string; + originatingProjectName: string; + logText: string; + } + >, + finalConfigForProject: FinalConfigForProject, + releaseTagPattern: string, + latestMatchingGitTag?: Awaited> +): Promise { + /** + * In the case of fixed release groups that are configured to resolve the current version from a git tag, + * it would be a waste of time and resources to figure out the git tag, or resolve one of the fallbacks, + * for each individual project, therefore we maintain a cache of the current version for each applicable release group here. + */ + const cached = cachedCurrentVersionsPerFixedReleaseGroup.get( + releaseGroup.name + ); + if (cached) { + const logText = cached.logText ? ` ${cached.logText}` : ''; + logger.buffer( + `🔄 Reusing the current version ${cached.currentVersion} already resolved for ${cached.originatingProjectName}${logText}` + ); + return cached.currentVersion; + } + + // The latest matching git tag was found in release-group-processor and has an extracted version, return it directly + if (latestMatchingGitTag && latestMatchingGitTag.extractedVersion) { + const currentVersion = latestMatchingGitTag.extractedVersion; + logger.buffer( + `🏷️ Resolved the current version as ${currentVersion} from git tag "${latestMatchingGitTag.tag}", based on releaseTagPattern "${releaseTagPattern}"` + ); + // Write to the cache if the release group is fixed + if (releaseGroup.projectsRelationship === 'fixed') { + cachedCurrentVersionsPerFixedReleaseGroup.set(releaseGroup.name, { + currentVersion, + originatingProjectName: projectGraphNode.name, + logText: `from git tag "${latestMatchingGitTag.tag}"`, + }); + } + return currentVersion; + } + + const noMatchingGitTagsError = new Error( + `No git tags matching pattern "${releaseTagPattern}" for project "${projectGraphNode.name}" were found. You will need to create an initial matching tag to use as a base for determining the next version. Alternatively, you can use the --first-release option or set "release.version.fallbackCurrentVersionResolver" to "disk" in order to fallback to the version on disk when no matching git tags are found.` + ); + if (finalConfigForProject.fallbackCurrentVersionResolver !== 'disk') { + throw noMatchingGitTagsError; + } + + const fromDiskRes = await versionActions.readCurrentVersionFromSourceManifest( + tree + ); + // Fallback on disk is available, return it directly + if (fromDiskRes && fromDiskRes.currentVersion) { + logger.buffer( + `⚠️ Unable to resolve the current version from git tags using pattern "${releaseTagPattern}". Falling back to the version ${fromDiskRes.currentVersion} in manifest: ${fromDiskRes.manifestPath}` + ); + // Write to the cache if the release group is fixed + if (releaseGroup.projectsRelationship === 'fixed') { + cachedCurrentVersionsPerFixedReleaseGroup.set(releaseGroup.name, { + currentVersion: fromDiskRes.currentVersion, + originatingProjectName: projectGraphNode.name, + logText: `from the disk fallback`, + }); + } + return fromDiskRes.currentVersion; + } + + // At this point the fallback on disk is also not available/configured, allow one final interactive fallback, but only when using version-plans or conventional-commits + if (finalConfigForProject.specifierSource === 'prompt') { + throw noMatchingGitTagsError; + } + const currentVersionFromPromptFallback = await handleNoAvailableDiskFallback({ + logger, + projectName: projectGraphNode.name, + versionActions, + specifierSource: finalConfigForProject.specifierSource, + currentVersionSourceMessage: `from git tag using pattern "${releaseTagPattern}"`, + resolutionSuggestion: `you should set an initial git tag on a relevant commit`, + }); + // Write to the cache if the release group is fixed + if (releaseGroup.projectsRelationship === 'fixed') { + cachedCurrentVersionsPerFixedReleaseGroup.set(releaseGroup.name, { + currentVersion: currentVersionFromPromptFallback, + originatingProjectName: projectGraphNode.name, + logText: `from the prompt fallback`, + }); + } + return currentVersionFromPromptFallback; +} + +/** + * Allow users to be unblocked when locally running releases for the very first time with certain combinations that require an initial + * version in order to function (e.g. a relative semver bump derived via conventional-commits or version-plans) by providing an interactive + * prompt to let them opt into using 0.0.0 as the implied current version. + */ +async function handleNoAvailableDiskFallback({ + logger, + projectName, + versionActions, + specifierSource, + currentVersionSourceMessage, + resolutionSuggestion, +}: { + logger: ProjectLogger; + projectName: string; + versionActions: VersionActions; + specifierSource: Exclude< + NxReleaseVersionV2Configuration['specifierSource'], + 'prompt' + >; + currentVersionSourceMessage: string; + resolutionSuggestion: string; +}): Promise { + if (!versionActions.validManifestFilenames?.length) { + throw new Error( + `Unable to resolve the current version ${currentVersionSourceMessage} and there is no version on disk to fall back to. This is invalid with ${specifierSource} because the new version is determined by relatively bumping the current version. To resolve this, ${resolutionSuggestion}, or set use a versionActions implementation that supports a manifest file` + ); + } + + const validManifestFilenames = + versionActions.validManifestFilenames.join(' or '); + + const unresolvableCurrentVersionError = new Error( + `Unable to resolve the current version ${currentVersionSourceMessage} and there is no version on disk to fall back to. This is invalid with ${specifierSource} because the new version is determined by relatively bumping the current version. To resolve this, ${resolutionSuggestion}, or set an appropriate version in a supported manifest file such as ${validManifestFilenames}` + ); + if (process.env.CI === 'true') { + // We can't prompt in CI, so error immediately + throw unresolvableCurrentVersionError; + } + try { + const reply = await prompt<{ useZero: boolean }>([ + { + name: 'useZero', + message: `\n${chalk.yellow( + `Warning: Unable to resolve the current version for "${projectName}" ${currentVersionSourceMessage} and there is no version on disk to fall back to. This is invalid with ${specifierSource} because the new version is determined by relatively bumping the current version.\n\nTo resolve this, ${resolutionSuggestion}, or set an appropriate version in a supported manifest file such as ${validManifestFilenames}` + )}. \n\nAlternatively, would you like to continue now by using 0.0.0 as the current version?`, + type: 'confirm', + initial: false, + }, + ]); + if (!reply.useZero) { + // Throw any error to skip the fallback to 0.0.0, may as well use the one we already have + throw unresolvableCurrentVersionError; + } + const currentVersion = '0.0.0'; + logger.buffer( + `⚠ Forcibly resolved the current version as "${currentVersion}" based on your response to the prompt above` + ); + return currentVersion; + } catch { + throw unresolvableCurrentVersionError; + } +} diff --git a/packages/nx/src/command-line/release/version/test-utils.spec.ts b/packages/nx/src/command-line/release/version/test-utils.spec.ts new file mode 100644 index 0000000000..7114433949 --- /dev/null +++ b/packages/nx/src/command-line/release/version/test-utils.spec.ts @@ -0,0 +1,157 @@ +import { parseGraphDefinition } from './test-utils'; + +describe('parseGraphDefinition', () => { + it('should produce an appropriate test graph based on the input definition', () => { + const testGraph = parseGraphDefinition( + ` + __default__ ({ "projectsRelationship": "fixed" }): + - projectD@1.0.0 [js] + -> depends on projectE(workspace:*) + - projectE@1.0.0 [js:@myorg/projectE] + -> depends on projectF + - projectF[custom/project/root/for/projectF]@1.0.0 [js] + -> release config overrides { "version": { "manifestRootsToUpdate": ["dist/something-custom/package.json"] } } + ` + ); + expect(testGraph).toMatchInlineSnapshot(` + { + "projects": { + "projectD": { + "alternateNameInManifest": undefined, + "data": {}, + "dependsOn": [ + { + "collection": "dependencies", + "prefix": "", + "project": "projectE", + "versionSpecifier": "workspace:*", + }, + ], + "group": "__default__", + "language": "js", + "relationship": "fixed", + "version": "1.0.0", + }, + "projectE": { + "alternateNameInManifest": "@myorg/projectE", + "data": {}, + "dependsOn": [ + { + "collection": "dependencies", + "prefix": "", + "project": "projectF", + "versionSpecifier": undefined, + }, + ], + "group": "__default__", + "language": "js", + "relationship": "fixed", + "version": "1.0.0", + }, + "projectF": { + "alternateNameInManifest": undefined, + "data": { + "root": "custom/project/root/for/projectF", + }, + "dependsOn": [], + "group": "__default__", + "language": "js", + "relationship": "fixed", + "releaseConfigOverrides": { + "version": { + "manifestRootsToUpdate": [ + "dist/something-custom/package.json", + ], + }, + }, + "version": "1.0.0", + }, + }, + } + `); + }); + + it('should support non-semver versioning and projects with no current version', () => { + const testGraph = parseGraphDefinition( + ` + __default__ ({ "projectsRelationship": "independent" }): + - projectA@1.0 [non-semver] + - projectB [non-semver] + ` + ); + expect(testGraph).toMatchInlineSnapshot(` + { + "projects": { + "projectA": { + "alternateNameInManifest": undefined, + "data": { + "release": { + "versionActions": "__EXAMPLE_NON_SEMVER_VERSION_ACTIONS__", + }, + }, + "dependsOn": [], + "group": "__default__", + "language": "non-semver", + "relationship": "independent", + "version": "1.0", + }, + "projectB": { + "alternateNameInManifest": undefined, + "data": { + "release": { + "versionActions": "__EXAMPLE_NON_SEMVER_VERSION_ACTIONS__", + }, + }, + "dependsOn": [], + "group": "__default__", + "language": "non-semver", + "relationship": "independent", + "version": null, + }, + }, + } + `); + }); + + it('should support complex non-semver version values', () => { + const testGraph = parseGraphDefinition( + ` + __default__ ({ "projectsRelationship": "independent" }): + - projectA@abc123 [non-semver] + - projectB@2099-01-01.build1 [non-semver] + ` + ); + expect(testGraph).toMatchInlineSnapshot(` + { + "projects": { + "projectA": { + "alternateNameInManifest": undefined, + "data": { + "release": { + "versionActions": "__EXAMPLE_NON_SEMVER_VERSION_ACTIONS__", + }, + }, + "dependsOn": [], + "group": "__default__", + "language": "non-semver", + "relationship": "independent", + "version": "abc123", + }, + "projectB": { + "alternateNameInManifest": undefined, + "data": { + "release": { + "versionActions": "__EXAMPLE_NON_SEMVER_VERSION_ACTIONS__", + }, + }, + "dependsOn": [], + "group": "__default__", + "language": "non-semver", + "relationship": "independent", + "version": "2099-01-01.build1", + }, + }, + } + `); + }); +}); diff --git a/packages/nx/src/command-line/release/version/test-utils.ts b/packages/nx/src/command-line/release/version/test-utils.ts new file mode 100644 index 0000000000..13df1bf161 --- /dev/null +++ b/packages/nx/src/command-line/release/version/test-utils.ts @@ -0,0 +1,644 @@ +import TOML from '@ltd/j-toml'; +import { join } from 'node:path'; +import type { + NxJsonConfiguration, + NxReleaseVersionV2Configuration, +} from '../../../config/nx-json'; +import type { + ProjectGraph, + ProjectGraphProjectNode, +} from '../../../config/project-graph'; +import type { Tree } from '../../../generators/tree'; +import { writeJson } from '../../../generators/utils/json'; +import { createProjectFileMapUsingProjectGraph } from '../../../project-graph/file-map-utils'; +import { + createNxReleaseConfig, + DEFAULT_VERSION_ACTIONS_PATH, + NxReleaseConfig, +} from '../config/config'; +import { filterReleaseGroups } from '../config/filter-release-groups'; +import { FinalConfigForProject } from './release-group-processor'; +import { VersionActions } from './version-actions'; + +export async function createNxReleaseConfigAndPopulateWorkspace( + tree: Tree, + graphDefinition: string, + additionalNxReleaseConfig: Exclude, + mockResolveCurrentVersion?: any, + filters: { + projects?: string[]; + groups?: string[]; + } = {} +) { + const graph = parseGraphDefinition(graphDefinition); + const { groups, projectGraph } = setupGraph(tree, graph); + + const { error: configError, nxReleaseConfig } = await createNxReleaseConfig( + projectGraph, + await createProjectFileMapUsingProjectGraph(projectGraph), + { + ...additionalNxReleaseConfig, + groups, + version: { + ...additionalNxReleaseConfig.version, + useLegacyVersioning: false, + }, + } + ); + if (configError) { + throw configError; + } + + let { + error: filterError, + releaseGroups, + releaseGroupToFilteredProjects, + } = filterReleaseGroups( + projectGraph, + nxReleaseConfig!, + filters.projects, + filters.groups + ); + if (filterError) { + throw filterError; + } + + // Mock the implementation of resolveCurrentVersion to reliably return the version of the project based on our graph definition + mockResolveCurrentVersion?.mockImplementation((_, { name }) => { + for (const [projectName, project] of Object.entries(graph.projects)) { + if (projectName === name) { + return (project as any).version ?? null; + } + } + throw new Error(`Unknown project name in test utils: ${name}`); + }); + + return { + projectGraph, + nxReleaseConfig: nxReleaseConfig!, + releaseGroups, + releaseGroupToFilteredProjects, + filters, + }; +} + +/** + * A non-production grade rust implementation to prove out loading multiple different versionActions in various setups + */ +interface CargoToml { + workspace?: { members: string[] }; + package: { name: string; version: string }; + dependencies?: Record< + string, + string | { version: string; features?: string[]; optional?: boolean } + >; + 'dev-dependencies'?: Record< + string, + string | { version: string; features: string[] } + >; + [key: string]: any; +} + +export class ExampleRustVersionActions extends VersionActions { + validManifestFilenames = ['Cargo.toml']; + + private parseCargoToml(cargoString: string): CargoToml { + return TOML.parse(cargoString, { + x: { comment: true }, + }) as CargoToml; + } + + static stringifyCargoToml(cargoToml: CargoToml): string { + const tomlString = TOML.stringify(cargoToml, { + newlineAround: 'section', + }); + return Array.isArray(tomlString) ? tomlString.join('\n') : tomlString; + } + + static modifyCargoTable( + toml: CargoToml, + section: string, + key: string, + value: string | object | Array | (() => any) + ) { + toml[section] ??= TOML.Section({}); + toml[section][key] = + typeof value === 'object' && !Array.isArray(value) + ? TOML.inline(value as any) + : typeof value === 'function' + ? value() + : value; + } + + async readCurrentVersionFromSourceManifest(tree: Tree): Promise<{ + currentVersion: string; + manifestPath: string; + }> { + const cargoTomlPath = join(this.projectGraphNode.data.root, 'Cargo.toml'); + const cargoTomlString = tree.read(cargoTomlPath, 'utf-8')!.toString(); + const cargoToml = this.parseCargoToml(cargoTomlString); + const currentVersion = cargoToml.package?.version || '0.0.0'; + return { + currentVersion, + manifestPath: cargoTomlPath, + }; + } + + async readCurrentVersionFromRegistry( + tree: Tree, + _currentVersionResolverMetadata: NxReleaseVersionV2Configuration['currentVersionResolverMetadata'] + ): Promise<{ + currentVersion: string; + logText: string; + }> { + // Real registry resolver not needed for this test example + return { + currentVersion: (await this.readCurrentVersionFromSourceManifest(tree)) + .currentVersion, + logText: 'https://example.com/fake-registry', + }; + } + + async updateProjectVersion(tree: Tree, newVersion: string) { + const logMessages: string[] = []; + for (const manifestPath of this.manifestsToUpdate) { + const cargoTomlString = tree.read(manifestPath, 'utf-8')!.toString(); + const cargoToml = this.parseCargoToml(cargoTomlString); + ExampleRustVersionActions.modifyCargoTable( + cargoToml, + 'package', + 'version', + newVersion + ); + const updatedCargoTomlString = + ExampleRustVersionActions.stringifyCargoToml(cargoToml); + tree.write(manifestPath, updatedCargoTomlString); + logMessages.push( + `✍️ New version ${newVersion} written to manifest: ${manifestPath}` + ); + } + return logMessages; + } + + async readCurrentVersionOfDependency( + tree: Tree, + _projectGraph: ProjectGraph, + dependencyProjectName: string + ): Promise<{ currentVersion: string; dependencyCollection: string }> { + const cargoTomlPath = join(this.projectGraphNode.data.root, 'Cargo.toml'); + const cargoTomlString = tree.read(cargoTomlPath, 'utf-8')!.toString(); + const cargoToml = this.parseCargoToml(cargoTomlString); + const dependencyVersion = cargoToml.dependencies?.[dependencyProjectName]; + if (typeof dependencyVersion === 'string') { + return { + currentVersion: dependencyVersion, + dependencyCollection: 'dependencies', + }; + } + return { + currentVersion: dependencyVersion?.version || '0.0.0', + dependencyCollection: 'dependencies', + }; + } + + async isLocalDependencyProtocol(_versionSpecifier: string): Promise { + return false; + } + + async updateProjectDependencies( + tree: Tree, + _projectGraph: ProjectGraph, + dependenciesToUpdate: Record + ): Promise { + const numDependenciesToUpdate = Object.keys(dependenciesToUpdate).length; + const depText = + numDependenciesToUpdate === 1 ? 'dependency' : 'dependencies'; + if (numDependenciesToUpdate === 0) { + return []; + } + + const logMessages: string[] = []; + for (const manifestPath of this.manifestsToUpdate) { + const cargoTomlString = tree.read(manifestPath, 'utf-8')!.toString(); + const cargoToml = this.parseCargoToml(cargoTomlString); + + for (const [dep, version] of Object.entries(dependenciesToUpdate)) { + ExampleRustVersionActions.modifyCargoTable( + cargoToml, + 'dependencies', + dep, + version + ); + } + + const updatedCargoTomlString = + ExampleRustVersionActions.stringifyCargoToml(cargoToml); + tree.write(manifestPath, updatedCargoTomlString); + + logMessages.push( + `✍️ Updated ${numDependenciesToUpdate} ${depText} in manifest: ${manifestPath}` + ); + } + return logMessages; + } +} + +export class ExampleNonSemverVersionActions extends VersionActions { + validManifestFilenames = null; + + async readCurrentVersionFromSourceManifest() { + return null; + } + + async readCurrentVersionFromRegistry() { + return null; + } + + async readCurrentVersionOfDependency() { + return { + currentVersion: null, + dependencyCollection: null, + }; + } + + async isLocalDependencyProtocol() { + return false; + } + + async updateProjectVersion(tree, newVersion) { + tree.write( + join(this.projectGraphNode.data.root, 'version.txt'), + newVersion + ); + return []; + } + + async updateProjectDependencies() { + return []; + } + + // Overwrite the default calculateNewVersion method to return the new version directly and not consider semver + async calculateNewVersion( + currentVersion: string | null, + newVersionInput: string, + newVersionInputReason: string, + newVersionInputReasonData: Record, + preid: string + ): Promise<{ newVersion: string; logText: string }> { + if (newVersionInput === 'patch') { + return { + newVersion: + '{SOME_NEW_VERSION_DERIVED_AS_A_SIDE_EFFECT_OF_DEPENDENCY_BUMP}', + logText: `Determined new version as a side effect of dependency bump: ${newVersionInput}`, + }; + } + + return { + newVersion: newVersionInput, + logText: `Applied new version directly: ${newVersionInput}`, + }; + } +} + +export function parseGraphDefinition(definition: string) { + const graph = { projects: {} as any }; + const lines = definition.trim().split('\n'); + let currentGroup = ''; + let groupConfig = {}; + let groupRelationship = ''; + + let lastProjectName = ''; + + lines.forEach((line) => { + line = line.trim(); + if (!line) { + // Skip empty lines + return; + } + + // Match group definitions with JSON config + const groupMatch = line.match(/^(\w+)\s*\(\s*(\{.*?\})\s*\):$/); + if (groupMatch) { + currentGroup = groupMatch[1]; + groupConfig = JSON.parse(groupMatch[2]); + groupRelationship = groupConfig['projectsRelationship'] || 'independent'; + return; + } + + // Match project definitions with optional per-project JSON config + const projectMatch = line.match( + /^- ([\w-]+)(?:\[([\w\/-]+)\])?(?:@([\w\.-]+))? \[([\w-]+)(?::([^[\]]+))?\](?:\s*\(\s*(\{.*?\})\s*\))?$/ + ); + if (projectMatch) { + const [ + _, + name, + customProjectRoot, + version, + language, + alternateNameInManifest, + configJson, + ] = projectMatch; + + // Automatically add data for Rust projects + let projectData = {} as any; + if (customProjectRoot) { + projectData.root = customProjectRoot; + } + if (language === 'rust') { + projectData = { + release: { versionActions: exampleRustVersionActions }, + }; + } else if (language === 'non-semver') { + projectData = { + release: { versionActions: exampleNonSemverVersionActions }, + }; + } + + // Merge explicit per-project config if present + if (configJson) { + const explicitConfig = JSON.parse(configJson); + projectData = { ...projectData, ...explicitConfig }; + } + + graph.projects[name] = { + version: version ?? null, + language, + group: currentGroup, + relationship: groupRelationship, + dependsOn: [], + data: projectData, + // E.g. package name in package.json doesn't necessarily match the name of the nx project + alternateNameInManifest, + }; + lastProjectName = name; + return; + } + + // Match release config overrides + const releaseConfigMatch = line.match( + /^-> release config overrides (\{.*\})$/ + ); + if (releaseConfigMatch) { + const [_, releaseConfigJson] = releaseConfigMatch; + const releaseConfigOverrides = JSON.parse(releaseConfigJson); + if (!graph.projects[lastProjectName].releaseConfigOverrides) { + graph.projects[lastProjectName].releaseConfigOverrides = {}; + } + graph.projects[lastProjectName].releaseConfigOverrides = { + ...graph.projects[lastProjectName].releaseConfigOverrides, + ...releaseConfigOverrides, + }; + return; + } + + // Match dependencies + const dependsMatch = line.match( + /^-> depends on ([~^=]?)([\w-]+)(?:\((.*?)\))?(?:\s*\{(\w+)\})?$/ + ); + if (dependsMatch) { + const [ + _, + prefix, + depProject, + versionSpecifier, + depCollection = 'dependencies', + ] = dependsMatch; + // Add the dependency to the last added project + if (!graph.projects[lastProjectName].dependsOn) { + graph.projects[lastProjectName].dependsOn = []; + } + graph.projects[lastProjectName].dependsOn.push({ + project: depProject, + collection: depCollection, + prefix: prefix || '', // Store the prefix (empty string if not specified) + versionSpecifier: versionSpecifier || undefined, // Store exact version specifier if provided + }); + return; + } + + // Ignore unrecognized lines + }); + + return graph; +} + +function setupGraph(tree: any, graph: any) { + const groups: NxReleaseConfig['groups'] = {}; + const projectGraph: ProjectGraph = { nodes: {}, dependencies: {} }; + + for (const [projectName, projectData] of Object.entries(graph.projects)) { + const { + version, + language, + group, + relationship, + dependsOn, + data, + alternateNameInManifest, + releaseConfigOverrides, + } = projectData as any; + + const packageName = alternateNameInManifest ?? projectName; + + // Write project files based on language + if (language === 'js') { + const packageJson: any = { + name: packageName, + version, + }; + if (dependsOn) { + dependsOn.forEach( + (dep: { + project: string; + collection: string; + prefix: string; + versionSpecifier: string | undefined; + }) => { + if (!packageJson[dep.collection]) { + packageJson[dep.collection] = {}; + } + const depNode = graph.projects[dep.project]; + const depVersion = dep.versionSpecifier ?? depNode.version; + packageJson[dep.collection][ + depNode.alternateNameInManifest ?? dep.project + ] = `${dep.prefix}${depVersion}`; + } + ); + } + writeJson( + tree, + join(data.root ?? projectName, 'package.json'), + packageJson + ); + // Write extra manifest files if specified + if (releaseConfigOverrides?.version?.manifestRootsToUpdate) { + releaseConfigOverrides.version.manifestRootsToUpdate.forEach((root) => { + writeJson(tree, join(root, 'package.json'), packageJson); + }); + } + } else if (language === 'rust') { + const cargoToml: CargoToml = {} as any; + ExampleRustVersionActions.modifyCargoTable( + cargoToml, + 'package', + 'name', + projectName + ); + ExampleRustVersionActions.modifyCargoTable( + cargoToml, + 'package', + 'version', + version + ); + + if (dependsOn) { + dependsOn.forEach( + (dep: { + project: string; + collection: string; + prefix: string; + versionSpecifier: string | undefined; + }) => { + ExampleRustVersionActions.modifyCargoTable( + cargoToml, + dep.collection, + dep.project, + { + version: + dep.versionSpecifier ?? graph.projects[dep.project].version, + } + ); + } + ); + } + + const contents = ExampleRustVersionActions.stringifyCargoToml(cargoToml); + tree.write(join(data.root ?? projectName, 'Cargo.toml'), contents); + // Write extra manifest files if specified + if (releaseConfigOverrides?.version?.manifestRootsToUpdate) { + releaseConfigOverrides.version.manifestRootsToUpdate.forEach((root) => { + tree.write(join(root, 'Cargo.toml'), contents); + }); + } + } else if (language === 'non-semver') { + tree.write(join(data.root ?? projectName, 'version.txt'), version ?? ''); + } + + // Add to projectGraph nodes + const projectGraphProjectNode: ProjectGraphProjectNode = { + name: projectName, + type: 'lib', + data: { + root: projectName, + ...data, // Merge any additional data from project config + }, + }; + if (language === 'js') { + // Always add the js package metadata to match the @nx/js plugin + projectGraphProjectNode.data.metadata = { + js: { + packageName, + }, + }; + } + + // Add project level release config overrides + if (releaseConfigOverrides) { + projectGraphProjectNode.data.release = { + ...projectGraphProjectNode.data.release, + ...releaseConfigOverrides, + }; + } + + projectGraph.nodes[projectName] = projectGraphProjectNode; + + // Initialize dependencies + projectGraph.dependencies[projectName] = []; + + // Handle dependencies + if (dependsOn) { + dependsOn.forEach((dep: { project: string; collection: string }) => { + projectGraph.dependencies[projectName].push({ + source: projectName, + target: dep.project, + type: 'static', + }); + }); + } + + // Add to releaseGroups + if (!groups[group]) { + groups[group] = { + projectsRelationship: relationship, + projects: [], + } as any; + } + groups[group].projects.push(projectName); + } + + return { groups, projectGraph }; +} + +const exampleRustVersionActions = '__EXAMPLE_RUST_VERSION_ACTIONS__'; +const exampleNonSemverVersionActions = '__EXAMPLE_NON_SEMVER_VERSION_ACTIONS__'; + +export async function mockResolveVersionActionsForProjectImplementation( + tree: Tree, + releaseGroup: any, + projectGraphNode: any, + finalConfigForProject: FinalConfigForProject +) { + if ( + projectGraphNode.data.release?.versionActions === + exampleRustVersionActions || + releaseGroup.versionActions === exampleRustVersionActions + ) { + const versionActions = new ExampleRustVersionActions( + releaseGroup, + projectGraphNode, + finalConfigForProject + ); + // Initialize the versionActions with all the required manifest paths etc + await versionActions.init(tree); + return { + versionActionsPath: exampleRustVersionActions, + versionActions, + }; + } + + if ( + projectGraphNode.data.release?.versionActions === + exampleNonSemverVersionActions || + releaseGroup.versionActions === exampleNonSemverVersionActions + ) { + const versionActions = new ExampleNonSemverVersionActions( + releaseGroup, + projectGraphNode, + finalConfigForProject + ); + // Initialize the versionActions with all the required manifest paths etc + await versionActions.init(tree); + return { + versionActionsPath: exampleNonSemverVersionActions, + versionActions, + }; + } + + const versionActionsPath = DEFAULT_VERSION_ACTIONS_PATH; + // @ts-ignore + const loaded = jest.requireActual(versionActionsPath); + const JsVersionActions = loaded.default; + const versionActions: VersionActions = new JsVersionActions( + releaseGroup, + projectGraphNode, + finalConfigForProject + ); + // Initialize the versionActions with all the required manifest paths etc + await versionActions.init(tree); + return { + versionActionsPath, + versionActions: versionActions, + afterAllProjectsVersioned: loaded.afterAllProjectsVersioned, + }; +} diff --git a/packages/nx/src/command-line/release/version/topological-sort.spec.ts b/packages/nx/src/command-line/release/version/topological-sort.spec.ts new file mode 100644 index 0000000000..152fab9d70 --- /dev/null +++ b/packages/nx/src/command-line/release/version/topological-sort.spec.ts @@ -0,0 +1,174 @@ +import { topologicalSort } from './topological-sort'; + +describe('topologicalSort', () => { + it('should return nodes in topological order for a simple acyclic graph', () => { + // A -> B -> C + // | ^ + // v | + // D --------| + + const nodes = ['A', 'B', 'C', 'D']; + const edges = { + A: ['B', 'D'], + B: ['C'], + C: [], + D: ['C'], + }; + + const getEdges = (node: string) => edges[node]; + + const result = topologicalSort(nodes, getEdges); + + // Verify that dependencies come before dependents + const indexA = result.indexOf('A'); + const indexB = result.indexOf('B'); + const indexC = result.indexOf('C'); + const indexD = result.indexOf('D'); + + expect(indexA).toBeLessThan(indexB); // A before B + expect(indexA).toBeLessThan(indexD); // A before D + expect(indexB).toBeLessThan(indexC); // B before C + expect(indexD).toBeLessThan(indexC); // D before C + }); + + it('should handle cycles by breaking them', () => { + // A -> B -> C -> A (cycle) + // | + // v + // D + + const nodes = ['A', 'B', 'C', 'D']; + const edges = { + A: ['B', 'D'], + B: ['C'], + C: ['A'], // Creates cycle + D: [], + }; + + const getEdges = (node: string) => edges[node]; + + const result = topologicalSort(nodes, getEdges); + + // Even with a cycle, we should have all nodes + expect(result.length).toBe(4); + + // All nodes should be in the result + expect(result).toContain('A'); + expect(result).toContain('B'); + expect(result).toContain('C'); + expect(result).toContain('D'); + }); + + it('should handle complex examples with multiple cycles', () => { + // A -> B -> C -> A (cycle 1) + // | ^ + // v | + // D -> E -> F -> D (cycle 2) + + const nodes = ['A', 'B', 'C', 'D', 'E', 'F']; + const edges = { + A: ['B', 'D'], + B: ['C'], + C: ['A'], // Cycle 1 + D: ['E'], + E: ['F', 'C'], + F: ['D'], // Cycle 2 + }; + + const getEdges = (node: string) => edges[node]; + + const result = topologicalSort(nodes, getEdges); + + // Even with cycles, we should have all nodes + expect(result.length).toBe(6); + + // All nodes should be in the result + expect(result).toContain('A'); + expect(result).toContain('B'); + expect(result).toContain('C'); + expect(result).toContain('D'); + expect(result).toContain('E'); + expect(result).toContain('F'); + }); + + it('should handle self-dependencies', () => { + // A -> A (self-cycle) + // B -> B (self-cycle) + + const nodes = ['A', 'B']; + const edges = { + A: ['A'], // Self-cycle + B: ['B'], // Self-cycle + }; + + const getEdges = (node: string) => edges[node]; + + const result = topologicalSort(nodes, getEdges); + + // All nodes should be in the result + expect(result.length).toBe(2); + expect(result).toContain('A'); + expect(result).toContain('B'); + }); + + it('should handle empty input', () => { + const result = topologicalSort([], () => []); + expect(result).toEqual([]); + }); + + it('should handle disconnected nodes', () => { + // A C E + // | | + // v v + // B D + + const nodes = ['A', 'B', 'C', 'D', 'E']; + const edges = { + A: ['B'], + B: [], + C: ['D'], + D: [], + E: [], + }; + + const getEdges = (node: string) => edges[node]; + + const result = topologicalSort(nodes, getEdges); + + // All nodes should be in the result + expect(result.length).toBe(5); + expect(result).toContain('A'); + expect(result).toContain('B'); + expect(result).toContain('C'); + expect(result).toContain('D'); + expect(result).toContain('E'); + + // Check partial ordering + const indexA = result.indexOf('A'); + const indexB = result.indexOf('B'); + const indexC = result.indexOf('C'); + const indexD = result.indexOf('D'); + + expect(indexA).toBeLessThan(indexB); // A before B + expect(indexC).toBeLessThan(indexD); // C before D + }); + + it('should handle circular dependencies between two nodes', () => { + // A <-> B + + const nodes = ['A', 'B']; + const edges = { + A: ['B'], + B: ['A'], + }; + + const getEdges = (node: string) => edges[node]; + + const result = topologicalSort(nodes, getEdges); + + // All nodes should be in the result + expect(result.length).toBe(2); + expect(result).toContain('A'); + expect(result).toContain('B'); + }); +}); diff --git a/packages/nx/src/command-line/release/version/topological-sort.ts b/packages/nx/src/command-line/release/version/topological-sort.ts new file mode 100644 index 0000000000..8c8cc02b69 --- /dev/null +++ b/packages/nx/src/command-line/release/version/topological-sort.ts @@ -0,0 +1,48 @@ +/** + * Topologically sorts a directed graph, returning the sorted nodes. + * Handles cycles by breaking them where needed. + * + * @param nodes All nodes in the graph + * @param getEdges Function that returns outgoing edges for a node + * @returns Topologically sorted list of nodes + */ +export function topologicalSort( + nodes: T[], + getEdges: (node: T) => T[] +): T[] { + const result: T[] = []; + const visited = new Set(); + const temp = new Set(); + + function visit(node: T): void { + // Node is already in result + if (visited.has(node)) { + return; + } + + // Cycle detected, skip this edge to break the cycle + if (temp.has(node)) { + return; + } + + temp.add(node); + + // Visit all dependencies first + for (const dep of getEdges(node)) { + visit(dep); + } + + temp.delete(node); + visited.add(node); + result.push(node); + } + + // Visit all nodes + for (const node of nodes) { + if (!visited.has(node)) { + visit(node); + } + } + + return result.reverse(); +} diff --git a/packages/nx/src/command-line/release/version/version-actions.ts b/packages/nx/src/command-line/release/version/version-actions.ts new file mode 100644 index 0000000000..597fb6c28d --- /dev/null +++ b/packages/nx/src/command-line/release/version/version-actions.ts @@ -0,0 +1,440 @@ +import { join } from 'node:path'; +import { ReleaseType } from 'semver'; +import { NxReleaseVersionV2Configuration } from '../../../config/nx-json'; +import type { + ProjectGraph, + ProjectGraphDependency, + ProjectGraphProjectNode, +} from '../../../config/project-graph'; +import type { Tree } from '../../../generators/tree'; +import { registerTsProject } from '../../../plugins/js/utils/register'; +import { getRootTsConfigPath } from '../../../plugins/js/utils/typescript'; +import { interpolate } from '../../../tasks-runner/utils'; +import { workspaceRoot } from '../../../utils/workspace-root'; +import { DEFAULT_VERSION_ACTIONS_PATH } from '../config/config'; +import { ReleaseGroupWithName } from '../config/filter-release-groups'; +import { + deriveNewSemverVersion, + isRelativeVersionKeyword, +} from '../utils/semver'; +import { + BUMP_TYPE_REASON_TEXT, + FinalConfigForProject, +} from './release-group-processor'; + +export type SemverBumpType = ReleaseType | 'none'; + +function resolveVersionActionsPath( + path: string, + projectGraphNode: ProjectGraphProjectNode +): string { + try { + return require.resolve(path); + } catch { + try { + return require.resolve(join(workspaceRoot, path)); + } catch { + if (path === DEFAULT_VERSION_ACTIONS_PATH) { + throw new Error( + `Unable to resolve the default "versionActions" implementation for project "${projectGraphNode.name}" at path: "${path}" + +- If this is a JavaScript/TypeScript project, it is likely that you simply need to install the "@nx/js" plugin. + +- If this not a JavaScript/TypeScript project, you can should provide an alternative "versionActions" implementation path via the "release.version.versionActions" configuration option. +` + ); + } + throw new Error( + `Unable to resolve the "versionActions" implementation for project "${projectGraphNode.name}" at the configured path: "${path}"` + ); + } + } +} + +/** + * Implementation details of performing any actions after all projects have been versioned. + * An example might be updating a workspace level lock file. + * + * NOTE: By the time this function is invoked, the tree will have been flushed back to disk, + * so it is not accessible here. + * + * The function should return lists of changed and deleted files so that they can be staged + * and committed if appropriate. + * + * NOTE: The versionActionsOptions passed here are the ones at the root of release.version config, + * different values per release group or project will not be respected here because this takes + * place after all projects have been versioned. + */ +export type AfterAllProjectsVersioned = ( + cwd: string, + opts: { + dryRun?: boolean; + verbose?: boolean; + rootVersionActionsOptions?: Record; + } +) => Promise<{ + changedFiles: string[]; + deletedFiles: string[]; +}>; + +type VersionActionsConstructor = { + new ( + releaseGroup: ReleaseGroupWithName, + projectGraphNode: ProjectGraphProjectNode, + finalConfigForProject: FinalConfigForProject + ): VersionActions; +}; + +const versionActionsResolutionCache = new Map< + string, + { + VersionActionsClass: VersionActionsConstructor; + afterAllProjectsVersioned: AfterAllProjectsVersioned; + } +>(); + +export async function resolveVersionActionsForProject( + tree: Tree, + releaseGroup: ReleaseGroupWithName, + projectGraphNode: ProjectGraphProjectNode, + finalConfigForProject: FinalConfigForProject +): Promise<{ + versionActionsPath: string; + versionActions: VersionActions; + afterAllProjectsVersioned: AfterAllProjectsVersioned; +}> { + // Project level release version config takes priority, if set + const projectVersionConfig = projectGraphNode.data.release?.version as + | Pick< + NxReleaseVersionV2Configuration, + 'versionActions' | 'versionActionsOptions' + > + | undefined; + const releaseGroupVersionConfig = releaseGroup.version as + | Pick< + NxReleaseVersionV2Configuration, + 'versionActions' | 'versionActionsOptions' + > + | undefined; + + const versionActionsPathConfig = + projectVersionConfig?.versionActions ?? + releaseGroupVersionConfig?.versionActions ?? + null; + if (!versionActionsPathConfig) { + // Should be an impossible state, as we should have defaulted to the JS implementation during config processing + throw new Error( + `No versionActions implementation found for project "${projectGraphNode.name}", please report this on https://github.com/nrwl/nx/issues` + ); + } + + let cachedData = versionActionsResolutionCache.get(versionActionsPathConfig); + const versionActionsPath = resolveVersionActionsPath( + versionActionsPathConfig, + projectGraphNode + ); + + let VersionActionsClass: VersionActionsConstructor | undefined; + let afterAllProjectsVersioned: AfterAllProjectsVersioned | undefined; + + if (cachedData) { + VersionActionsClass = cachedData.VersionActionsClass; + afterAllProjectsVersioned = cachedData.afterAllProjectsVersioned; + } else { + let cleanupTranspiler: () => void; + if (versionActionsPath.endsWith('.ts')) { + cleanupTranspiler = registerTsProject(getRootTsConfigPath()); + } + const loaded = require(versionActionsPath); + cleanupTranspiler?.(); + VersionActionsClass = loaded.default ?? loaded; + if (!VersionActionsClass) { + throw new Error( + `For project "${projectGraphNode.name}" it was not possible to resolve the VersionActions implementation from: "${versionActionsPath}"` + ); + } + afterAllProjectsVersioned = + loaded.afterAllProjectsVersioned ?? + // no-op fallback for ecosystems/use-cases where it is not applicable + (() => + Promise.resolve({ + changedFiles: [], + deletedFiles: [], + })); + versionActionsResolutionCache.set(versionActionsPath, { + VersionActionsClass, + afterAllProjectsVersioned, + }); + } + const versionActions: VersionActions = new VersionActionsClass( + releaseGroup, + projectGraphNode, + finalConfigForProject + ); + // Initialize the version actions with all the required manifest paths etc + await versionActions.init(tree); + return { + versionActionsPath, + versionActions, + afterAllProjectsVersioned, + }; +} + +export abstract class VersionActions { + /** + * The available valid filenames of the manifest file relevant to the current versioning use-case. + * + * E.g. for JavaScript projects this would be ["package.json"], but for Gradle it would be + * ["build.gradle", "build.gradle.kts"]. + * + * If a manifest file is not applicable to the current versioning use-case, this should be set to null. + */ + abstract validManifestFilenames: string[] | null; + /** + * The interpolated manifest paths to update, if applicable based on the user's configuration, when new + * versions and dependencies are determined. If no manifest files should be updated based on the user's + * configuration, this will be an empty array. + */ + manifestsToUpdate: string[] = []; + + constructor( + public releaseGroup: ReleaseGroupWithName, + public projectGraphNode: ProjectGraphProjectNode, + public finalConfigForProject: FinalConfigForProject + ) {} + + /** + * Asynchronous initialization of the version actions and validation of certain configuration options. + */ + async init(tree: Tree): Promise { + // Default to the first available source manifest root, if applicable, if no custom manifest roots are provided + if ( + this.validManifestFilenames?.length && + this.finalConfigForProject.manifestRootsToUpdate.length === 0 + ) { + for (const manifestFilename of this.validManifestFilenames) { + if ( + tree.exists(join(this.projectGraphNode.data.root, manifestFilename)) + ) { + this.finalConfigForProject.manifestRootsToUpdate.push( + this.projectGraphNode.data.root + ); + break; + } + } + } + + const interpolatedManifestRoots = + this.finalConfigForProject.manifestRootsToUpdate.map((manifestRoot) => { + return interpolate(manifestRoot, { + workspaceRoot: '', + projectRoot: this.projectGraphNode.data.root, + projectName: this.projectGraphNode.name, + }); + }); + + for (const interpolatedManifestRoot of interpolatedManifestRoots) { + let hasValidManifest = false; + for (const manifestFilename of this.validManifestFilenames) { + const manifestPath = join(interpolatedManifestRoot, manifestFilename); + if (tree.exists(manifestPath)) { + this.manifestsToUpdate.push(manifestPath); + hasValidManifest = true; + break; + } + } + if (!hasValidManifest) { + const validManifestFilenames = + this.validManifestFilenames?.join(' or '); + + throw new Error( + `The project "${this.projectGraphNode.name}" does not have a ${validManifestFilenames} file available in ./${interpolatedManifestRoot}. + +To fix this you will either need to add a ${validManifestFilenames} file at that location, or configure "release" within your nx.json to exclude "${this.projectGraphNode.name}" from the current release group, or amend the "release.version.manifestRootsToUpdate" configuration to point to where the relevant manifest should be.` + ); + } + } + } + + /** + * The default implementation will calculate the new version based on semver. If semver is not applicable to a + * particular versioning use-case, this method should be overridden with custom logic. + * + * @param {string | null} currentVersion - The current version of the project, or null if the current version resolver is set to 'none' + * @param {string} newVersionInput - The new version input provided by the user, such as a semver relative bump type, or an explicit version + * @param {string} newVersionInputReason - The reason for the new version input used to inform the log message to show to the user + * @param {Record} newVersionInputReasonData - The data to interpolate into the new version input reason + * @param {string} preid - The preid to use for the new version, if applicable + */ + async calculateNewVersion( + currentVersion: string | null, + newVersionInput: string, + newVersionInputReason: string, + newVersionInputReasonData: Record, + preid: string + ): Promise<{ + newVersion: string; + logText: string; + }> { + const isSemverRelativeBump = isRelativeVersionKeyword(newVersionInput); + const newVersionReasonText = BUMP_TYPE_REASON_TEXT[newVersionInputReason]; + if (!newVersionReasonText) { + throw new Error( + `Unhandled bump type reason for ${this.projectGraphNode.name} with newVersionInput ${newVersionInput} and newVersionInputReason ${newVersionInputReason}, please report this as a bug on https://github.com/nrwl/nx/issues` + ); + } + const interpolatedNewVersionInputReasonText = interpolate( + newVersionReasonText, + newVersionInputReasonData + ); + if (isSemverRelativeBump && !currentVersion) { + throw new Error( + `There was no current version resolved for project "${this.projectGraphNode.name}", but it was configured to be bumped via a semver relative keyword "${newVersionInput}"${interpolatedNewVersionInputReasonText}. This is not a valid combination, please review your release configuration and CLI arguments` + ); + } + + const newVersion = deriveNewSemverVersion( + currentVersion, + newVersionInput, + preid + ); + + const newVersionInputText = isRelativeVersionKeyword(newVersionInput) + ? `semver relative bump "${newVersionInput}"` + : `explicit semver value "${newVersionInput}"`; + + return { + newVersion, + logText: `❓ Applied ${newVersionInputText}${interpolatedNewVersionInputReasonText}to get new version ${newVersion}`, + }; + } + + /** + * Implementation details of resolving a project's current version from a valid manifest file. It should + * return an object with the current version and the filename of the resolved manifest path so that the + * logs provided to the user are as specific as possible. + * + * This method will only be called if the user has configured their currentVersionResolver to be "disk". + * + * If the version actions implementation does not support a manifest file, this method can either throw + * an error or return null. In this case, nx release will handle showing the user a relevant error about + * their currentVersionResolver configuration being fundamentally incompatible with the current version + * actions implementation resolved for the project being versioned and they can change it to something else + * (e.g. "registry" or "git-tag"). + * + * NOTE: The version actions implementation does not need to provide the method for handling resolution + * from git tags, this is done directly by nx release. + */ + abstract readCurrentVersionFromSourceManifest(tree: Tree): Promise<{ + currentVersion: string; + manifestPath: string; + } | null>; + + /** + * Implementation details of resolving a project's current version from a remote registry. + * + * The specific logText it returns will be combined with the generic remote registry log text and allows + * the implementation to provide more specific information to the user about what registry URL + * was used, what dist-tag etc. + * + * If the version actions implementation does not support resolving from a remote registry, this method + * can either throw an error or return null. In this case, nx release will handle showing the user a relevant + * error about their currentVersionResolver configuration being fundamentally incompatible with the current + * version actions implementation resolved for the project being versioned and they can change it to something + * else (e.g. "disk" or "git-tag"). + * + * NOTE: The version actions implementation does not need to provide the method for handling resolution + * from git tags, this is done directly by nx release. + */ + abstract readCurrentVersionFromRegistry( + tree: Tree, + currentVersionResolverMetadata: NxReleaseVersionV2Configuration['currentVersionResolverMetadata'] + ): Promise<{ + currentVersion: string | null; + logText: string; + } | null>; + + /** + * Implementation details of resolving the dependencies of a project. + * + * The default implementation will read dependencies from the Nx project graph. In many cases this will be sufficient, + * because the project graph will have been constructed using plugins from relevant ecosystems that should have applied + * any and all relevant metadata to the project nodes and dependency edges. + * + * If, however, the project graph cannot be used as the source of truth for whatever reason, then this default method + * can simply be overridden in the final version actions implementation. + */ + async readDependencies( + tree: Tree, + projectGraph: ProjectGraph + ): Promise { + return (projectGraph.dependencies[this.projectGraphNode.name] ?? []).filter( + // Skip implicit dependencies for now to match legacy versioning behavior + // TODO: holistically figure out how to handle implicit dependencies with nx release + (dep) => dep.type !== 'implicit' + ); + } + + /** + * Implementation details of resolving the current version of a specific dependency of the project. + * + * The dependency collection is the type of dependency collection to which the dependency belongs, such as 'dependencies', + * 'devDependencies', 'peerDependencies', 'optionalDependencies', etc. This is ecosystem and use-case specific. + * + * The currentVersion and dependencyCollection fields will be used to populate the rawVersionSpec and dependencyCollection + * fields on the VersionData that gets returned from the programmatic API. `null` values are accepted for these if the current + * version or dependencyCollection is not applicable/resolvable at all, but they should be provided if possible. + * + * The currentVersion will also be used when calculating the final versionPrefix to apply for the new dependency + * version, based on the user's configuration, if applicable. + */ + abstract readCurrentVersionOfDependency( + tree: Tree, + projectGraph: ProjectGraph, + dependencyProjectName: string + ): Promise<{ + currentVersion: string | null; + dependencyCollection: string | null; + }>; + + /** + * Implementation details of determining if a version specifier uses a local dependency protocol that is relevant to this + * specific project. E.g. in a package.json context, `file:` and `workspace:` protocols should return true here. + */ + abstract isLocalDependencyProtocol( + versionSpecifier: string + ): Promise; + + /** + * Implementation details of updating a newly derived version in some source of truth. + * + * For libraries/packages, this will usually involve writing to one or more manifest files + * (e.g. potentially both src and dist), such as a package.json/Cargo.toml/etc, but for + * application deployments it might involve updating something else instead, it depends on + * the type of application. + * + * It should return an array of log messages that will be displayed unmodified to the user + * after the version has been updated. + */ + abstract updateProjectVersion( + tree: Tree, + newVersion: string + ): Promise; + + /** + * Implementation details of updating dependencies in some source of truth. + * + * For libraries/packages, this will usually involve writing to one or more manifest files + * (e.g. potentially both src and dist), such as a package.json/Cargo.toml/etc, + * with new dependency versions, but for application deployments it might involve + * updating something else instead, it depends on the type of application. + * + * It should return an array of log messages that will be displayed unmodified to the user + * after the dependencies have been updated. + */ + abstract updateProjectDependencies( + tree: Tree, + projectGraph: ProjectGraph, + dependenciesToUpdate: Record + ): Promise; +} diff --git a/packages/nx/src/config/nx-json.ts b/packages/nx/src/config/nx-json.ts index 6133781a4e..56a246f818 100644 --- a/packages/nx/src/config/nx-json.ts +++ b/packages/nx/src/config/nx-json.ts @@ -2,16 +2,16 @@ import { existsSync } from 'fs'; import { dirname, join } from 'path'; import type { ChangelogRenderOptions } from '../../release/changelog-renderer'; +import { validReleaseVersionPrefixes } from '../command-line/release/version'; +import { readJsonFile } from '../utils/fileutils'; import type { PackageManager } from '../utils/package-manager'; +import { workspaceRoot } from '../utils/workspace-root'; import type { InputDefinition, TargetConfiguration, TargetDependencyConfig, } from './workspace-json-project-json'; -import { readJsonFile } from '../utils/fileutils'; -import { workspaceRoot } from '../utils/workspace-root'; - export type ImplicitDependencyEntry = { [key: string]: T | ImplicitJsonSubsetDependency; }; @@ -56,6 +56,12 @@ interface NxInstallationConfiguration { plugins?: Record; } +/** + * This named configuration interface will be changing in Nx v21. This interface will be made available + * under LegacyNxReleaseVersionConfiguration, which is already available as an alias. + * + * In Nx v22, this configuration interface will no longer be valid. + */ export interface NxReleaseVersionConfiguration { generator?: string; generatorOptions?: Record; @@ -71,6 +77,102 @@ export interface NxReleaseVersionConfiguration { */ conventionalCommits?: boolean; } +export type LegacyNxReleaseVersionConfiguration = NxReleaseVersionConfiguration; + +// NOTE: It's important to keep the nx-schema.json in sync with this interface. If you make changes here, make sure they are reflected in the schema. +export interface NxReleaseVersionV2Configuration { + /** + * Whether to use the legacy versioning strategy. This value will be true in Nx v20 and false in Nx v21. + * The legacy versioning implementation will be removed in Nx v22, as will this flag. + */ + useLegacyVersioning?: boolean; + /** + * Shorthand for enabling the current version of projects to be resolved from git tags, + * and the next version to be determined by analyzing commit messages according to the + * Conventional Commits specification. + */ + conventionalCommits?: boolean; + /** + * A command to run after validation of nx release configuration, but before versioning begins. + * Useful for preparing build artifacts. If --dry-run is passed, the command is still executed, + * but with the NX_DRY_RUN environment variable set to 'true'. + */ + preVersionCommand?: string; + /** + * The source to use for determining the specifier to use when versioning. + * 'prompt' is the default and will interactively prompt the user for an explicit/imperative specifier. + * 'conventional-commits' will attempt determine a specifier from commit messages conforming to the Conventional Commits specification. + * 'version-plans' will determine the specifier from the version plan files available on disk. + */ + specifierSource?: 'prompt' | 'conventional-commits' | 'version-plans'; + /** + * A list of directories containing manifest files (such as package.json) to apply updates to when versioning. + * + * By default, only the project root will be used, but you could customize this to only version a manifest in a + * dist directory, or even version multiple manifests in different directories, such as both source and dist. + */ + manifestRootsToUpdate?: string[]; + /** + * The resolver to use for determining the current version of a project during versioning. + * This is needed for versioning approaches which involve relatively modifying a current version + * to arrive at a new version, such as semver bumps like 'patch', 'minor' etc. + * + * Using 'none' explicitly declares that the current version is not needed to compute the new version, and + * should only be used with appropriate version actions implementations that support it. + */ + currentVersionResolver?: 'registry' | 'disk' | 'git-tag' | 'none'; + /** + * Metadata to provide to the configured currentVersionResolver to help it in determining the current version. + * What to pass here is specific to each resolver. + */ + currentVersionResolverMetadata?: Record; + /** + * The fallback version resolver to use when the configured currentVersionResolver fails to resolve the current version. + */ + fallbackCurrentVersionResolver?: 'disk'; + /** + * Whether or not this is the first release of one of more projects. + * This removes certain validation checks that are not possible to enforce if the project has never been released before. + */ + firstRelease?: boolean; + /** + * The prefix to use when versioning dependencies. + * This can be one of the following: auto, '', '~', '^', '=', where auto means the existing prefix will be preserved. + */ + versionPrefix?: (typeof validReleaseVersionPrefixes)[number]; + /** + * Whether to delete the processed version plans file after versioning is complete. This is false by default because the + * version plans are also needed for changelog generation. + */ + deleteVersionPlans?: boolean; + /** + * When versioning independent projects, this controls whether to update their dependents (i.e. the things that depend on them). + * 'never' means no dependents will be updated (unless they happen to be versioned directly as well). + * 'auto' is the default and will cause dependents to be updated (a patch version bump) when a dependency is versioned. + */ + updateDependents?: 'auto' | 'never'; + /** + * Whether to log projects that have not changed during versioning. + */ + logUnchangedProjects?: boolean; + /** + * The path to the version actions implementation to use for releasing all projects by default. + * This can also be overridden on the release group and project levels. + */ + versionActions?: string; + /** + * The specific options that are defined by each version actions implementation. + * They will be passed to the version actions implementation when running a release. + */ + versionActionsOptions?: Record; + /** + * Whether to preserve local dependency protocols (e.g. file references, or the `workspace:` protocol in package.json files) + * of local dependencies when updating them during versioning. + * + * This was false by default in legacy versioning, but is true by default now. + */ + preserveLocalDependencyProtocols?: boolean; +} export interface NxReleaseChangelogConfiguration { /** @@ -157,6 +259,10 @@ export interface NxReleaseGitConfiguration { * Whether or not to automatically push the changes made by this command to the remote git repository. */ push?: boolean; + /** + * Additional arguments to pass to the `git push` command invoked behind the scenes. May be a string or array of strings. + */ + pushArgs?: string | string[]; } export interface NxReleaseConventionalCommitsConfiguration { @@ -225,7 +331,10 @@ export interface NxReleaseConfiguration { * * NOTE: git configuration is not supported at the group level, only the root/command level */ - version?: NxReleaseVersionConfiguration & { + version?: ( + | LegacyNxReleaseVersionConfiguration + | NxReleaseVersionV2Configuration + ) & { /** * A command to run after validation of nx release configuration, but before versioning begins. * Used for preparing build artifacts. If --dry-run is passed, the command is still executed, but @@ -300,19 +409,15 @@ export interface NxReleaseConfiguration { automaticFromRef?: boolean; }; /** - * If no version config is provided, we will assume that @nx/js:release-version - * is the desired generator implementation, allowing for terser config for the common case. + * If no version configuration is provided, we will assume that TypeScript/JavaScript experience is what is desired, + * allowing for terser release configuration for the common case. */ - version?: NxReleaseVersionConfiguration & { - /** - * Enable or override configuration for git operations as part of the version subcommand - */ + version?: ( + | LegacyNxReleaseVersionConfiguration + | NxReleaseVersionV2Configuration + ) & { + useLegacyVersioning?: boolean; git?: NxReleaseGitConfiguration; - /** - * A command to run after validation of nx release configuration, but before versioning begins. - * Used for preparing build artifacts. If --dry-run is passed, the command is still executed, but - * with the NX_DRY_RUN environment variable set to 'true'. - */ preVersionCommand?: string; }; /** diff --git a/packages/nx/src/config/workspace-json-project-json.ts b/packages/nx/src/config/workspace-json-project-json.ts index 78c56cf71a..951852e782 100644 --- a/packages/nx/src/config/workspace-json-project-json.ts +++ b/packages/nx/src/config/workspace-json-project-json.ts @@ -2,6 +2,7 @@ import type { PackageJson } from '../utils/package-json'; import type { NxJsonConfiguration, NxReleaseVersionConfiguration, + NxReleaseVersionV2Configuration, } from './nx-json'; /** @@ -107,10 +108,20 @@ export interface ProjectConfiguration { * Project specific configuration for `nx release` */ release?: { - version?: Pick< - NxReleaseVersionConfiguration, - 'generator' | 'generatorOptions' - >; + version?: + | Pick + | Pick< + // Expose a subset of version config options at the project level + NxReleaseVersionV2Configuration, + | 'versionActions' + | 'versionActionsOptions' + | 'manifestRootsToUpdate' + | 'currentVersionResolver' + | 'currentVersionResolverMetadata' + | 'fallbackCurrentVersionResolver' + | 'versionPrefix' + | 'preserveLocalDependencyProtocols' + >; }; /** diff --git a/packages/react/src/generators/library/library.ts b/packages/react/src/generators/library/library.ts index 5dd8751f3d..a884386f63 100644 --- a/packages/react/src/generators/library/library.ts +++ b/packages/react/src/generators/library/library.ts @@ -1,4 +1,3 @@ -import { relative } from 'path'; import { addProjectConfiguration, ensurePackage, @@ -6,6 +5,7 @@ import { GeneratorCallback, installPackagesTask, joinPathFragments, + readNxJson, readProjectConfiguration, runTasksInSerial, Tree, @@ -15,33 +15,35 @@ import { import { getRelativeCwd } from '@nx/devkit/src/generators/artifact-name-and-directory-utils'; import { logShowProjectCommand } from '@nx/devkit/src/utils/log-show-project-command'; import { addTsConfigPath, initGenerator as jsInitGenerator } from '@nx/js'; +import { relative } from 'path'; -import { nxVersion } from '../../utils/versions'; -import { maybeJs } from '../../utils/maybe-js'; -import componentGenerator from '../component/component'; -import initGenerator from '../init/init'; -import { Schema } from './schema'; -import { updateJestConfigContent } from '../../utils/jest-utils'; -import { normalizeOptions } from './lib/normalize-options'; -import { addRollupBuildTarget } from './lib/add-rollup-build-target'; -import { addLinting } from './lib/add-linting'; -import { updateAppRoutes } from './lib/update-app-routes'; -import { createFiles } from './lib/create-files'; -import { extractTsConfigBase } from '../../utils/create-ts-config'; -import { installCommonDependencies } from './lib/install-common-dependencies'; -import { setDefaults } from './lib/set-defaults'; -import { - addProjectToTsSolutionWorkspace, - updateTsconfigFiles, -} from '@nx/js/src/utils/typescript/ts-solution-setup'; -import { determineEntryFields } from './lib/determine-entry-fields'; -import { sortPackageJsonFields } from '@nx/js/src/utils/package-json/sort-fields'; import { addReleaseConfigForNonTsSolution, addReleaseConfigForTsSolution, releaseTasks, } from '@nx/js/src/generators/library/utils/add-release-config'; +import { sortPackageJsonFields } from '@nx/js/src/utils/package-json/sort-fields'; +import { + addProjectToTsSolutionWorkspace, + updateTsconfigFiles, +} from '@nx/js/src/utils/typescript/ts-solution-setup'; +import { shouldUseLegacyVersioning } from 'nx/src/command-line/release/config/use-legacy-versioning'; import type { PackageJson } from 'nx/src/utils/package-json'; +import { extractTsConfigBase } from '../../utils/create-ts-config'; +import { updateJestConfigContent } from '../../utils/jest-utils'; +import { maybeJs } from '../../utils/maybe-js'; +import { nxVersion } from '../../utils/versions'; +import componentGenerator from '../component/component'; +import initGenerator from '../init/init'; +import { addLinting } from './lib/add-linting'; +import { addRollupBuildTarget } from './lib/add-rollup-build-target'; +import { createFiles } from './lib/create-files'; +import { determineEntryFields } from './lib/determine-entry-fields'; +import { installCommonDependencies } from './lib/install-common-dependencies'; +import { normalizeOptions } from './lib/normalize-options'; +import { setDefaults } from './lib/set-defaults'; +import { updateAppRoutes } from './lib/update-app-routes'; +import { Schema } from './schema'; export async function libraryGenerator(host: Tree, schema: Schema) { return await libraryGeneratorInternal(host, { @@ -258,7 +260,9 @@ export async function libraryGeneratorInternal(host: Tree, schema: Schema) { projectConfiguration ); } else { + const nxJson = readNxJson(host); await addReleaseConfigForNonTsSolution( + shouldUseLegacyVersioning(nxJson.release), host, options.name, projectConfiguration diff --git a/packages/vue/src/generators/library/library.ts b/packages/vue/src/generators/library/library.ts index e2d100a6dd..08b9cd1e6f 100644 --- a/packages/vue/src/generators/library/library.ts +++ b/packages/vue/src/generators/library/library.ts @@ -4,6 +4,7 @@ import { GeneratorCallback, installPackagesTask, joinPathFragments, + readJson, readProjectConfiguration, runTasksInSerial, toJS, @@ -11,31 +12,32 @@ import { updateProjectConfiguration, writeJson, } from '@nx/devkit'; -import { addTsConfigPath, initGenerator as jsInitGenerator } from '@nx/js'; -import { vueInitGenerator } from '../init/init'; -import { Schema } from './schema'; -import { normalizeOptions } from './lib/normalize-options'; -import { addLinting } from '../../utils/add-linting'; -import { createLibraryFiles } from './lib/create-library-files'; -import { extractTsConfigBase } from '../../utils/create-ts-config'; -import componentGenerator from '../component/component'; -import { addVite } from './lib/add-vite'; -import { ensureDependencies } from '../../utils/ensure-dependencies'; -import { logShowProjectCommand } from '@nx/devkit/src/utils/log-show-project-command'; import { getRelativeCwd } from '@nx/devkit/src/generators/artifact-name-and-directory-utils'; -import { relative } from 'path'; -import { - addProjectToTsSolutionWorkspace, - updateTsconfigFiles, -} from '@nx/js/src/utils/typescript/ts-solution-setup'; -import { determineEntryFields } from './lib/determine-entry-fields'; -import { sortPackageJsonFields } from '@nx/js/src/utils/package-json/sort-fields'; +import { logShowProjectCommand } from '@nx/devkit/src/utils/log-show-project-command'; +import { addTsConfigPath, initGenerator as jsInitGenerator } from '@nx/js'; import { addReleaseConfigForNonTsSolution, addReleaseConfigForTsSolution, releaseTasks, } from '@nx/js/src/generators/library/utils/add-release-config'; +import { sortPackageJsonFields } from '@nx/js/src/utils/package-json/sort-fields'; +import { + addProjectToTsSolutionWorkspace, + updateTsconfigFiles, +} from '@nx/js/src/utils/typescript/ts-solution-setup'; +import { shouldUseLegacyVersioning } from 'nx/src/command-line/release/config/use-legacy-versioning'; import type { PackageJson } from 'nx/src/utils/package-json'; +import { relative } from 'path'; +import { addLinting } from '../../utils/add-linting'; +import { extractTsConfigBase } from '../../utils/create-ts-config'; +import { ensureDependencies } from '../../utils/ensure-dependencies'; +import componentGenerator from '../component/component'; +import { vueInitGenerator } from '../init/init'; +import { addVite } from './lib/add-vite'; +import { createLibraryFiles } from './lib/create-library-files'; +import { determineEntryFields } from './lib/determine-entry-fields'; +import { normalizeOptions } from './lib/normalize-options'; +import { Schema } from './schema'; export function libraryGenerator(tree: Tree, schema: Schema) { return libraryGeneratorInternal(tree, { @@ -178,7 +180,9 @@ export async function libraryGeneratorInternal(tree: Tree, schema: Schema) { projectConfig ); } else { + const nxJson = readJson(tree, 'nx.json'); await addReleaseConfigForNonTsSolution( + shouldUseLegacyVersioning(nxJson.release), tree, options.projectName, projectConfig diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cfa382c4a4..7f27ce1adb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -171,7 +171,7 @@ importers: version: 0.1902.0(chokidar@3.6.0) '@angular-devkit/build-angular': specifier: 19.2.0 - version: 19.2.0(stndy3yfcthbju7r3pdqp75gku) + version: 19.2.0(fiob6yppbuzqqpw7bpjs7r5k6m) '@angular-devkit/core': specifier: 19.2.0 version: 19.2.0(chokidar@3.6.0) @@ -244,9 +244,12 @@ importers: '@jest/types': specifier: 29.6.3 version: 29.6.3 + '@ltd/j-toml': + specifier: ^1.38.0 + version: 1.38.0 '@module-federation/enhanced': specifier: ^0.9.0 - version: 0.9.0(@rspack/core@1.2.6(@swc/helpers@0.5.11))(bufferutil@4.0.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.3)(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) + version: 0.9.1(@rspack/core@1.2.6(@swc/helpers@0.5.11))(bufferutil@4.0.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.3)(webpack@5.98.0) '@module-federation/sdk': specifier: ^0.9.0 version: 0.9.0 @@ -261,7 +264,7 @@ importers: version: 0.2.4 '@nestjs/cli': specifier: ^10.0.2 - version: 10.4.5(@swc/cli@0.6.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(chokidar@3.6.0))(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)) + version: 10.4.5(@swc/cli@0.6.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(chokidar@3.6.0))(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4) '@nestjs/common': specifier: ^9.0.0 version: 9.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1) @@ -276,10 +279,10 @@ importers: version: 9.2.0(chokidar@3.6.0)(typescript@5.7.3) '@nestjs/swagger': specifier: ^6.0.0 - version: 6.3.0(@nestjs/common@9.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@9.4.3(@nestjs/common@9.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@9.4.3)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2) + version: 6.3.0(@nestjs/common@9.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@9.4.3)(reflect-metadata@0.2.2) '@nestjs/testing': specifier: ^9.0.0 - version: 9.4.3(@nestjs/common@9.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@9.4.3(@nestjs/common@9.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@9.4.3)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@9.4.3(@nestjs/common@9.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@9.4.3)) + version: 9.4.3(@nestjs/common@9.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@9.4.3)(@nestjs/platform-express@9.4.3) '@ngrx/router-store': specifier: 19.0.0 version: 19.0.0(@angular/common@19.2.0(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10))(rxjs@7.8.1))(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10))(@angular/router@19.2.0(@angular/common@19.2.0(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10))(rxjs@7.8.1))(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10))(@angular/platform-browser@18.2.5(@angular/common@19.2.0(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10))(rxjs@7.8.1))(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10)))(rxjs@7.8.1))(@ngrx/store@19.0.0(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10))(rxjs@7.8.1))(rxjs@7.8.1) @@ -297,7 +300,7 @@ importers: version: 3.13.2(rollup@4.22.0)(webpack-sources@3.2.3) '@nx/angular': specifier: 20.7.0-beta.3 - version: 20.7.0-beta.3(tzslubwrec4apcoypka5lfsxri) + version: 20.7.0-beta.3(ryoth37z7gtdyc75iokmmmptla) '@nx/cypress': specifier: 20.7.0-beta.3 version: 20.7.0-beta.3(@babel/traverse@7.26.9)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@20.16.10)(@zkochan/js-yaml@0.0.7)(cypress@13.13.0)(eslint@8.57.0)(nx@20.7.0-beta.3(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.7.3)(verdaccio@6.0.5(encoding@0.1.13)(typanion@3.14.0)) @@ -324,7 +327,7 @@ importers: version: 1.3.0-beta.11 '@nx/next': specifier: 20.7.0-beta.3 - version: 20.7.0-beta.3(h5y73l76peitcbgx2i2ni7n6ou) + version: 20.7.0-beta.3(@babel/core@7.25.2)(@babel/traverse@7.26.9)(@rspack/core@1.2.6(@swc/helpers@0.5.11))(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@20.16.10)(@zkochan/js-yaml@0.0.7)(bufferutil@4.0.7)(esbuild@0.25.0)(eslint@8.57.0)(html-webpack-plugin@5.5.0(webpack@5.98.0))(next@14.2.26(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.47.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.55.0))(nx@20.7.0-beta.3(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.3)(verdaccio@6.0.5(encoding@0.1.13)(typanion@3.14.0))(webpack-cli@5.1.4)(webpack@5.98.0) '@nx/playwright': specifier: 20.7.0-beta.3 version: 20.7.0-beta.3(@babel/traverse@7.26.9)(@playwright/test@1.47.1)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@20.16.10)(@zkochan/js-yaml@0.0.7)(eslint@8.57.0)(nx@20.7.0-beta.3(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.7.3)(verdaccio@6.0.5(encoding@0.1.13)(typanion@3.14.0)) @@ -339,13 +342,13 @@ importers: version: 1.3.0-beta.11 '@nx/react': specifier: 20.7.0-beta.3 - version: 20.7.0-beta.3(@babel/traverse@7.26.9)(@rspack/core@1.2.6(@swc/helpers@0.5.11))(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@20.16.10)(@zkochan/js-yaml@0.0.7)(bufferutil@4.0.7)(esbuild@0.25.0)(eslint@8.57.0)(next@14.2.26(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.47.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.55.0))(nx@20.7.0-beta.3(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.3)(verdaccio@6.0.5(encoding@0.1.13)(typanion@3.14.0))(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) + version: 20.7.0-beta.3(@babel/traverse@7.26.9)(@rspack/core@1.2.6(@swc/helpers@0.5.11))(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@20.16.10)(@zkochan/js-yaml@0.0.7)(bufferutil@4.0.7)(esbuild@0.25.0)(eslint@8.57.0)(next@14.2.26(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.47.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.55.0))(nx@20.7.0-beta.3(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.3)(verdaccio@6.0.5(encoding@0.1.13)(typanion@3.14.0))(webpack-cli@5.1.4)(webpack@5.98.0) '@nx/rsbuild': specifier: 20.7.0-beta.3 version: 20.7.0-beta.3(@babel/traverse@7.26.9)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@20.16.10)(nx@20.7.0-beta.3(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.7.3)(verdaccio@6.0.5(encoding@0.1.13)(typanion@3.14.0)) '@nx/rspack': specifier: 20.7.0-beta.3 - version: 20.7.0-beta.3(fld5masubvtf5unq5sgvap2d2a) + version: 20.7.0-beta.3(vtmcegwkmmmnniq2i67f4x3n7u) '@nx/storybook': specifier: 20.7.0-beta.3 version: 20.7.0-beta.3(@babel/traverse@7.26.9)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@20.16.10)(@zkochan/js-yaml@0.0.7)(cypress@13.13.0)(eslint@8.57.0)(nx@20.7.0-beta.3(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.7.3)(verdaccio@6.0.5(encoding@0.1.13)(typanion@3.14.0)) @@ -357,7 +360,7 @@ importers: version: 20.7.0-beta.3(@babel/traverse@7.26.9)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@20.16.10)(nx@20.7.0-beta.3(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.7.3)(verdaccio@6.0.5(encoding@0.1.13)(typanion@3.14.0)) '@nx/webpack': specifier: 20.7.0-beta.3 - version: 20.7.0-beta.3(@babel/traverse@7.26.9)(@rspack/core@1.2.6(@swc/helpers@0.5.11))(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@20.16.10)(bufferutil@4.0.7)(esbuild@0.25.0)(html-webpack-plugin@5.5.0(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))))(nx@20.7.0-beta.3(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.7.3)(verdaccio@6.0.5(encoding@0.1.13)(typanion@3.14.0))(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)) + version: 20.7.0-beta.3(@babel/traverse@7.26.9)(@rspack/core@1.2.6(@swc/helpers@0.5.11))(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@20.16.10)(bufferutil@4.0.7)(esbuild@0.25.0)(html-webpack-plugin@5.5.0(webpack@5.98.0))(nx@20.7.0-beta.3(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.7.3)(verdaccio@6.0.5(encoding@0.1.13)(typanion@3.14.0))(webpack-cli@5.1.4) '@phenomnomnominal/tsquery': specifier: ~5.0.1 version: 5.0.1(typescript@5.7.3) @@ -366,7 +369,7 @@ importers: version: 1.47.1 '@pmmmwh/react-refresh-webpack-plugin': specifier: ^0.5.7 - version: 0.5.15(react-refresh@0.10.0)(type-fest@3.13.1)(webpack-dev-server@5.2.1(bufferutil@4.0.7)(webpack-cli@5.1.4)(webpack@5.98.0))(webpack-hot-middleware@2.26.1)(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) + version: 0.5.15(react-refresh@0.10.0)(type-fest@3.13.1)(webpack-dev-server@5.2.1)(webpack-hot-middleware@2.26.1)(webpack@5.98.0) '@pnpm/lockfile-types': specifier: ^6.0.0 version: 6.0.0 @@ -405,7 +408,7 @@ importers: version: 1.2.6(@swc/helpers@0.5.11) '@rspack/dev-server': specifier: 1.0.9 - version: 1.0.9(@rspack/core@1.2.6(@swc/helpers@0.5.11))(@types/express@4.17.21)(bufferutil@4.0.7)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) + version: 1.0.9(@rspack/core@1.2.6(@swc/helpers@0.5.11))(@types/express@4.17.21)(bufferutil@4.0.7)(webpack-cli@5.1.4)(webpack@5.98.0) '@rspack/plugin-minify': specifier: ^0.7.5 version: 0.7.5 @@ -432,7 +435,7 @@ importers: version: 8.4.6(@storybook/test@8.4.6(storybook@8.4.6(bufferutil@4.0.7)(prettier@2.8.8)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.22.0)(storybook@8.4.6(bufferutil@4.0.7)(prettier@2.8.8))(typescript@5.7.3)(vite@6.2.0(@types/node@20.16.10)(jiti@1.21.6)(less@4.1.3)(sass-embedded@1.85.1)(sass@1.55.0)(stylus@0.64.0)(terser@5.39.0)(yaml@2.6.1))(webpack-sources@3.2.3) '@storybook/react-webpack5': specifier: 8.4.6 - version: 8.4.6(@rspack/core@1.2.6(@swc/helpers@0.5.11))(@storybook/test@8.4.6(storybook@8.4.6(bufferutil@4.0.7)(prettier@2.8.8)))(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.4.6(bufferutil@4.0.7)(prettier@2.8.8))(typescript@5.7.3)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)) + version: 8.4.6(@rspack/core@1.2.6(@swc/helpers@0.5.11))(@storybook/test@8.4.6(storybook@8.4.6(bufferutil@4.0.7)(prettier@2.8.8)))(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.4.6(bufferutil@4.0.7)(prettier@2.8.8))(typescript@5.7.3)(webpack-cli@5.1.4) '@storybook/types': specifier: ^8.4.6 version: 8.4.6(storybook@8.4.6(bufferutil@4.0.7)(prettier@2.8.8)) @@ -576,7 +579,7 @@ importers: version: 29.7.0(@babel/core@7.25.2) babel-loader: specifier: ^9.1.2 - version: 9.2.1(@babel/core@7.25.2)(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) + version: 9.2.1(@babel/core@7.25.2)(webpack@5.98.0) browserslist: specifier: ^4.21.4 version: 4.23.3 @@ -603,10 +606,10 @@ importers: version: 2.0.0 copy-webpack-plugin: specifier: ^10.2.4 - version: 10.2.4(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) + version: 10.2.4(webpack@5.98.0) css-minimizer-webpack-plugin: specifier: ^5.0.0 - version: 5.0.1(esbuild@0.25.0)(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) + version: 5.0.1(esbuild@0.25.0)(webpack@5.98.0) cypress: specifier: 13.13.0 version: 13.13.0 @@ -696,7 +699,7 @@ importers: version: 5.0.2 fork-ts-checker-webpack-plugin: specifier: 7.2.13 - version: 7.2.13(typescript@5.7.3)(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) + version: 7.2.13(typescript@5.7.3)(webpack@5.98.0) fs-extra: specifier: ^11.1.0 version: 11.2.0 @@ -714,7 +717,7 @@ importers: version: 4.7.7 html-webpack-plugin: specifier: 5.5.0 - version: 5.5.0(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) + version: 5.5.0(webpack@5.98.0) http-proxy-middleware: specifier: ^3.0.3 version: 3.0.3 @@ -795,10 +798,10 @@ importers: version: 4.1.3 less-loader: specifier: 11.1.0 - version: 11.1.0(less@4.1.3)(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) + version: 11.1.0(less@4.1.3)(webpack@5.98.0) license-webpack-plugin: specifier: ^4.0.2 - version: 4.0.2(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) + version: 4.0.2(webpack@5.98.0) lines-and-columns: specifier: 2.0.3 version: 2.0.3 @@ -831,7 +834,7 @@ importers: version: 0.80.12 mini-css-extract-plugin: specifier: ~2.4.7 - version: 2.4.7(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) + version: 2.4.7(webpack@5.98.0) minimatch: specifier: 9.0.3 version: 9.0.3 @@ -936,13 +939,13 @@ importers: version: 1.85.1 sass-loader: specifier: 16.0.5 - version: 16.0.5(@rspack/core@1.2.6(@swc/helpers@0.5.11))(sass-embedded@1.85.1)(sass@1.55.0)(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) + version: 16.0.5(@rspack/core@1.2.6(@swc/helpers@0.5.11))(sass-embedded@1.85.1)(sass@1.55.0)(webpack@5.98.0) semver: specifier: ^7.6.3 version: 7.6.3 source-map-loader: specifier: ^5.0.0 - version: 5.0.0(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) + version: 5.0.0(webpack@5.98.0) source-map-support: specifier: 0.5.19 version: 0.5.19 @@ -954,7 +957,7 @@ importers: version: 4.0.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.4.6(bufferutil@4.0.7)(prettier@2.8.8)) style-loader: specifier: ^3.3.0 - version: 3.3.4(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) + version: 3.3.4(webpack@5.98.0) tar-stream: specifier: ~2.2.0 version: 2.2.0 @@ -963,7 +966,7 @@ importers: version: 1.0.2 terser-webpack-plugin: specifier: ^5.3.3 - version: 5.3.10(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) + version: 5.3.10(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack@5.98.0) tmp: specifier: ~0.2.1 version: 0.2.3 @@ -975,7 +978,7 @@ importers: version: 29.1.0(@babel/core@7.25.2)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.2))(esbuild@0.25.0)(jest@29.7.0(@types/node@20.16.10)(babel-plugin-macros@3.1.0)(ts-node@10.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@20.16.10)(typescript@5.7.3)))(typescript@5.7.3) ts-loader: specifier: ^9.3.1 - version: 9.5.1(typescript@5.7.3)(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) + version: 9.5.1(typescript@5.7.3)(webpack@5.98.0) ts-node: specifier: 10.9.1 version: 10.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@20.16.10)(typescript@5.7.3) @@ -1014,10 +1017,10 @@ importers: version: 3.0.5(@types/debug@4.1.12)(@types/node@20.16.10)(jiti@1.21.6)(jsdom@20.0.3(bufferutil@4.0.7))(less@4.1.3)(sass-embedded@1.85.1)(sass@1.55.0)(stylus@0.64.0)(terser@5.39.0)(yaml@2.6.1) webpack: specifier: 5.98.0 - version: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)) + version: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4) webpack-dev-server: specifier: 5.2.1 - version: 5.2.1(bufferutil@4.0.7)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) + version: 5.2.1(bufferutil@4.0.7)(webpack-cli@5.1.4)(webpack@5.98.0) webpack-merge: specifier: ^5.8.0 version: 5.10.0 @@ -1026,7 +1029,7 @@ importers: version: 3.0.0 webpack-subresource-integrity: specifier: ^5.1.0 - version: 5.1.0(html-webpack-plugin@5.5.0(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))))(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) + version: 5.1.0(html-webpack-plugin@5.5.0(webpack@5.98.0))(webpack@5.98.0) xstate: specifier: 4.34.0 version: 4.34.0 @@ -4321,33 +4324,15 @@ packages: '@mediapipe/tasks-vision@0.10.8': resolution: {integrity: sha512-Rp7ll8BHrKB3wXaRFKhrltwZl1CiXGdibPxuWXvqGnKTnv8fqa/nvftYNuSbf+pbJWKYCXdBtYTITdAUTGGh0Q==} - '@module-federation/bridge-react-webpack-plugin@0.9.0': - resolution: {integrity: sha512-EaUMMmNVx5IRXB8o39tPU05uWuDN38noQbUjwE2xi8Y5xTx3CFZ9tkgXX+tvzcWc+bu4RnaziaI0RtyBfHhHKw==} - '@module-federation/bridge-react-webpack-plugin@0.9.1': resolution: {integrity: sha512-znN/Qm6M0U1t3iF10gu1hSxDkk18yz78yvk+AMB34UDzpXHiC1zbpIeV2CQNV5GCeafmCICmcn9y1qh7F54KTg==} - '@module-federation/data-prefetch@0.9.0': - resolution: {integrity: sha512-KFC3lOzW/Hwvcgm/tn7075fqC/ONhA+5zPqDz090EsCQqnWQHodzx0XF88/tTvWdfc3mYK9BGYfsnpjlVGT1zA==} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - '@module-federation/data-prefetch@0.9.1': resolution: {integrity: sha512-rS1AsgRvIMAWK8oMprEBF0YQ3WvsqnumjinvAZU1Dqut5DICmpQMTPEO1OrAKyjO+PQgEhmq13HggzN6ebGLrQ==} peerDependencies: react: '>=16.9.0' react-dom: '>=16.9.0' - '@module-federation/dts-plugin@0.9.0': - resolution: {integrity: sha512-2Uu1sPRT9/uB+pG4vQGkLdpsMVM6FK7howulTdy42u6Dz0c2Ivc//od/noM0KEgWf1p54FAtWzpjkqjO5PyciA==} - peerDependencies: - typescript: ^4.9.0 || ^5.0.0 - vue-tsc: '>=1.0.24' - peerDependenciesMeta: - vue-tsc: - optional: true - '@module-federation/dts-plugin@0.9.1': resolution: {integrity: sha512-DezBrFaIKfDcEY7UhqyO1WbYocERYsR/CDN8AV6OvMnRlQ8u0rgM8qBUJwx0s+K59f+CFQFKEN4C8p7naCiHrw==} peerDependencies: @@ -4357,20 +4342,6 @@ packages: vue-tsc: optional: true - '@module-federation/enhanced@0.9.0': - resolution: {integrity: sha512-/fiIc+lZvP7pEiVWe0/0v2ueVcEumew2UNewguHFj3bxyyU5/+/5rHPRV5QIKcLzydrVLvTiS4/dB2Mqd7CVAg==} - peerDependencies: - typescript: ^4.9.0 || ^5.0.0 - vue-tsc: '>=1.0.24' - webpack: ^5.0.0 - peerDependenciesMeta: - typescript: - optional: true - vue-tsc: - optional: true - webpack: - optional: true - '@module-federation/enhanced@0.9.1': resolution: {integrity: sha512-c9siKVjcgT2gtDdOTqEr+GaP2X/PWAS0OV424ljKLstFL1lcS/BIsxWFDmxPPl5hDByAH+1q4YhC1LWY4LNDQw==} peerDependencies: @@ -4388,31 +4359,17 @@ packages: '@module-federation/error-codes@0.8.4': resolution: {integrity: sha512-55LYmrDdKb4jt+qr8qE8U3al62ZANp3FhfVaNPOaAmdTh0jHdD8M3yf5HKFlr5xVkVO4eV/F/J2NCfpbh+pEXQ==} - '@module-federation/error-codes@0.9.0': - resolution: {integrity: sha512-dNqIs5cQfE4p+WIdiZ64cTSRJ5KjGaV+epvZkGttrNjXW9XAAtE7zgpo7cMQ8GWA3wCGaKnFw7Dn48XcU5ZMNw==} - '@module-federation/error-codes@0.9.1': resolution: {integrity: sha512-q8spCvlwUzW42iX1irnlBTcwcZftRNHyGdlaoFO1z/fW4iphnBIfijzkigWQzOMhdPgzqN/up7XN+g5hjBGBtw==} - '@module-federation/inject-external-runtime-core-plugin@0.9.0': - resolution: {integrity: sha512-gDvYmcJyuXy1qzBZ6c+AndQUm50iOXmc56C2GN+F9ILAOQ/dcWFfD+OxyjXr80Pvej6qawW9CTEEWHNF4m4a+w==} - peerDependencies: - '@module-federation/runtime-tools': 0.9.0 - '@module-federation/inject-external-runtime-core-plugin@0.9.1': resolution: {integrity: sha512-BPfzu1cqDU5BhM493enVF1VfxJWmruen0ktlHrWdJJlcddhZzyFBGaLAGoGc+83fS75aEllvJTEthw4kMViMQQ==} peerDependencies: '@module-federation/runtime-tools': 0.9.1 - '@module-federation/managers@0.9.0': - resolution: {integrity: sha512-km8fVdS/r3TRLbSCV8IytKaQVuMbXbHvdZsgK4HRIWqGyBbA2q+gbXfWSC90OhdxNA9hhPEipmx+4hvuXDwaFg==} - '@module-federation/managers@0.9.1': resolution: {integrity: sha512-8hpIrvGfiODxS1qelTd7eaLRVF7jrp17RWgeH1DWoprxELANxm5IVvqUryB+7j+BhoQzamog9DL5q4MuNfGgIA==} - '@module-federation/manifest@0.9.0': - resolution: {integrity: sha512-o6+AyKdznP/Pyd6x/1staafUKNxIuXbmmpZxeL3VDvg8uR+6QM5bd/iXnBUgxaznIGQvUwvW/F1JixdgXwp7ug==} - '@module-federation/manifest@0.9.1': resolution: {integrity: sha512-+GteKBXrAUkq49i2CSyWZXM4vYa+mEVXxR9Du71R55nXXxgbzAIoZj9gxjRunj9pcE8+YpAOyfHxLEdWngxWdg==} @@ -4431,18 +4388,6 @@ packages: react-dom: optional: true - '@module-federation/rspack@0.9.0': - resolution: {integrity: sha512-Cw5/OGknuPwmkWc3jVj+T/uqtQ33cxDVMuaFIA6qGAiy/WfMyElKnOBe0n3JRaigH/A8qkyN/rNvMynYlTczLA==} - peerDependencies: - '@rspack/core': '>=0.7' - typescript: ^4.9.0 || ^5.0.0 - vue-tsc: '>=1.0.24' - peerDependenciesMeta: - typescript: - optional: true - vue-tsc: - optional: true - '@module-federation/rspack@0.9.1': resolution: {integrity: sha512-ZJqG75dWHhyTMa9I0YPJEV2XRt0MFxnDiuMOpI92esdmwWY633CBKyNh1XxcLd629YVeTv03+whr+Fz/f91JEw==} peerDependencies: @@ -4455,9 +4400,6 @@ packages: vue-tsc: optional: true - '@module-federation/runtime-core@0.6.21': - resolution: {integrity: sha512-CLQiPP3kpcPbgPkiu/A1VURI2v4geFnEdizlB1tq0c6eDZqb5aLzvp87ZCGDVSuwY7DCq6jh1k+CM2WGge/2xA==} - '@module-federation/runtime-core@0.9.1': resolution: {integrity: sha512-r61ufhKt5pjl81v7TkmhzeIoSPOaNtLynW6+aCy3KZMa3RfRevFxmygJqv4Nug1L0NhqUeWtdLejh4VIglNy5Q==} @@ -4467,9 +4409,6 @@ packages: '@module-federation/runtime-tools@0.8.4': resolution: {integrity: sha512-fjVOsItJ1u5YY6E9FnS56UDwZgqEQUrWFnouRiPtK123LUuqUI9FH4redZoKWlE1PB0ir1Z3tnqy8eFYzPO38Q==} - '@module-federation/runtime-tools@0.9.0': - resolution: {integrity: sha512-ioYVUsUfQjS7tgRBpZ6KFbz8YET2wD/2KmrO4zVaVHZ3RPyJzwVP9sLlDDdyf/Cn/1sQjNLg57FpyBADmY5M1Q==} - '@module-federation/runtime-tools@0.9.1': resolution: {integrity: sha512-JQZ//ab+lEXoU2DHAH+JtYASGzxEjXB0s4rU+6VJXc8c+oUPxH3kWIwzjdncg2mcWBmC1140DCk+K+kDfOZ5CQ==} @@ -4479,9 +4418,6 @@ packages: '@module-federation/runtime@0.8.4': resolution: {integrity: sha512-yZeZ7z2Rx4gv/0E97oLTF3V6N25vglmwXGgoeju/W2YjsFvWzVtCDI7zRRb0mJhU6+jmSM8jP1DeQGbea/AiZQ==} - '@module-federation/runtime@0.9.0': - resolution: {integrity: sha512-WByDEbJ/9fEUBOQILQRYX9CpAjCEmiU1MBSRoTg0emRKBcE9Ms5vTBN0XVuO+3gZSeyk08SfmaLtnCaeHK8ABA==} - '@module-federation/runtime@0.9.1': resolution: {integrity: sha512-jp7K06weabM5BF5sruHr/VLyalO+cilvRDy7vdEBqq88O9mjc0RserD8J+AP4WTl3ZzU7/GRqwRsiwjjN913dA==} @@ -4497,9 +4433,6 @@ packages: '@module-federation/sdk@0.9.1': resolution: {integrity: sha512-YQonPTImgnCqZjE/A+3N2g3J5ypR6kx1tbBzc9toUANKr/dw/S63qlh/zHKzWQzxjjNNVMdXRtTMp07g3kgEWg==} - '@module-federation/third-party-dts-extractor@0.9.0': - resolution: {integrity: sha512-gNMiFxSAkyy85KmdcdapKOP0RVqfuhzosdj92T9xXxR9juuILNS0TtgKbgYwNwvDs/WjOhTON5gz2lsX0kFKRg==} - '@module-federation/third-party-dts-extractor@0.9.1': resolution: {integrity: sha512-KeIByP718hHyq+Mc53enZ419pZZ1fh9Ns6+/bYLkc3iCoJr/EDBeiLzkbMwh2AS4Qk57WW0yNC82xzf7r0Zrrw==} @@ -4524,9 +4457,6 @@ packages: '@module-federation/webpack-bundler-runtime@0.8.4': resolution: {integrity: sha512-HggROJhvHPUX7uqBD/XlajGygMNM1DG0+4OAkk8MBQe4a18QzrRNzZt6XQbRTSG4OaEoyRWhQHvYD3Yps405tQ==} - '@module-federation/webpack-bundler-runtime@0.9.0': - resolution: {integrity: sha512-banXhVcgNYTqH13C6E9KrpwJ+UHaTvHfkvCRPs1y2EVvB9guPO6MnQ9Fd1DCwVMfpq3zMtJpPHobAqF5AmkCHQ==} - '@module-federation/webpack-bundler-runtime@0.9.1': resolution: {integrity: sha512-CxySX01gT8cBowKl9xZh+voiHvThMZ471icasWnlDIZb14KasZoX1eCh9wpGvwoOdIk9rIRT7h70UvW9nmop6w==} @@ -18472,11 +18402,11 @@ snapshots: transitivePeerDependencies: - chokidar - '@angular-devkit/build-angular@19.2.0(stndy3yfcthbju7r3pdqp75gku)': + '@angular-devkit/build-angular@19.2.0(fiob6yppbuzqqpw7bpjs7r5k6m)': dependencies: '@ampproject/remapping': 2.3.0 '@angular-devkit/architect': 0.1902.0(chokidar@3.6.0) - '@angular-devkit/build-webpack': 0.1902.0(chokidar@3.6.0)(webpack-dev-server@5.2.0(bufferutil@4.0.7)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))))(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) + '@angular-devkit/build-webpack': 0.1902.0(chokidar@3.6.0)(webpack-dev-server@5.2.0(bufferutil@4.0.7)(webpack-cli@5.1.4)(webpack@5.98.0))(webpack@5.98.0) '@angular-devkit/core': 19.2.0(chokidar@3.6.0) '@angular/build': 19.2.0(cxjrqumqlcnastohrhciqzxvse) '@angular/compiler-cli': 19.2.0(@angular/compiler@19.2.0(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.7.3) @@ -18490,14 +18420,14 @@ snapshots: '@babel/preset-env': 7.26.9(@babel/core@7.26.9) '@babel/runtime': 7.26.9 '@discoveryjs/json-ext': 0.6.3 - '@ngtools/webpack': 19.2.0(@angular/compiler-cli@19.2.0(@angular/compiler@19.2.0(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.7.3))(typescript@5.7.3)(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) + '@ngtools/webpack': 19.2.0(@angular/compiler-cli@19.2.0(@angular/compiler@19.2.0(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.7.3))(typescript@5.7.3)(webpack@5.98.0) '@vitejs/plugin-basic-ssl': 1.2.0(vite@6.2.0(@types/node@20.16.10)(jiti@1.21.6)(less@4.1.3)(sass-embedded@1.85.1)(sass@1.55.0)(stylus@0.64.0)(terser@5.39.0)(yaml@2.6.1)) ansi-colors: 4.1.3 autoprefixer: 10.4.20(postcss@8.5.2) - babel-loader: 9.2.1(@babel/core@7.26.9)(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) + babel-loader: 9.2.1(@babel/core@7.26.9)(webpack@5.98.0) browserslist: 4.24.4 - copy-webpack-plugin: 12.0.2(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) - css-loader: 7.1.2(@rspack/core@1.2.6(@swc/helpers@0.5.11))(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) + copy-webpack-plugin: 12.0.2(webpack@5.98.0) + css-loader: 7.1.2(@rspack/core@1.2.6(@swc/helpers@0.5.11))(webpack@5.98.0) esbuild-wasm: 0.25.0 fast-glob: 3.3.3 http-proxy-middleware: 3.0.3 @@ -18505,32 +18435,32 @@ snapshots: jsonc-parser: 3.3.1 karma-source-map-support: 1.4.0 less: 4.2.2 - less-loader: 12.2.0(@rspack/core@1.2.6(@swc/helpers@0.5.11))(less@4.2.2)(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) - license-webpack-plugin: 4.0.2(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) + less-loader: 12.2.0(@rspack/core@1.2.6(@swc/helpers@0.5.11))(less@4.2.2)(webpack@5.98.0) + license-webpack-plugin: 4.0.2(webpack@5.98.0) loader-utils: 3.3.1 - mini-css-extract-plugin: 2.9.2(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) + mini-css-extract-plugin: 2.9.2(webpack@5.98.0) open: 10.1.0 ora: 5.4.1 picomatch: 4.0.2 piscina: 4.8.0 postcss: 8.5.2 - postcss-loader: 8.1.1(@rspack/core@1.2.6(@swc/helpers@0.5.11))(postcss@8.5.2)(typescript@5.7.3)(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) + postcss-loader: 8.1.1(@rspack/core@1.2.6(@swc/helpers@0.5.11))(postcss@8.5.2)(typescript@5.7.3)(webpack@5.98.0) resolve-url-loader: 5.0.0 rxjs: 7.8.1 sass: 1.85.0 - sass-loader: 16.0.5(@rspack/core@1.2.6(@swc/helpers@0.5.11))(sass-embedded@1.85.1)(sass@1.85.0)(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) + sass-loader: 16.0.5(@rspack/core@1.2.6(@swc/helpers@0.5.11))(sass-embedded@1.85.1)(sass@1.85.0)(webpack@5.98.0) semver: 7.7.1 - source-map-loader: 5.0.0(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) + source-map-loader: 5.0.0(webpack@5.98.0) source-map-support: 0.5.21 terser: 5.39.0 tree-kill: 1.2.2 tslib: 2.8.1 typescript: 5.7.3 - webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)) - webpack-dev-middleware: 7.4.2(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) - webpack-dev-server: 5.2.0(bufferutil@4.0.7)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) + webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4) + webpack-dev-middleware: 7.4.2(webpack@5.98.0) + webpack-dev-server: 5.2.0(bufferutil@4.0.7)(webpack-cli@5.1.4)(webpack@5.98.0) webpack-merge: 6.0.1 - webpack-subresource-integrity: 5.1.0(html-webpack-plugin@5.5.0(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))))(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) + webpack-subresource-integrity: 5.1.0(html-webpack-plugin@5.5.0(webpack@5.98.0))(webpack@5.98.0) optionalDependencies: '@angular/ssr': 19.2.0(@angular/common@19.2.0(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10))(rxjs@7.8.1))(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10))(@angular/router@19.2.0(@angular/common@19.2.0(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10))(rxjs@7.8.1))(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10))(@angular/platform-browser@18.2.5(@angular/common@19.2.0(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10))(rxjs@7.8.1))(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10)))(rxjs@7.8.1)) esbuild: 0.25.0 @@ -18561,12 +18491,12 @@ snapshots: - webpack-cli - yaml - '@angular-devkit/build-webpack@0.1902.0(chokidar@3.6.0)(webpack-dev-server@5.2.0(bufferutil@4.0.7)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))))(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)))': + '@angular-devkit/build-webpack@0.1902.0(chokidar@3.6.0)(webpack-dev-server@5.2.0(bufferutil@4.0.7)(webpack-cli@5.1.4)(webpack@5.98.0))(webpack@5.98.0)': dependencies: '@angular-devkit/architect': 0.1902.0(chokidar@3.6.0) rxjs: 7.8.1 - webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)) - webpack-dev-server: 5.2.0(bufferutil@4.0.7)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) + webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4) + webpack-dev-server: 5.2.0(bufferutil@4.0.7)(webpack-cli@5.1.4)(webpack@5.98.0) transitivePeerDependencies: - chokidar @@ -22185,26 +22115,12 @@ snapshots: '@mediapipe/tasks-vision@0.10.8': {} - '@module-federation/bridge-react-webpack-plugin@0.9.0': - dependencies: - '@module-federation/sdk': 0.9.0 - '@types/semver': 7.5.8 - semver: 7.6.3 - '@module-federation/bridge-react-webpack-plugin@0.9.1': dependencies: '@module-federation/sdk': 0.9.1 '@types/semver': 7.5.8 semver: 7.6.3 - '@module-federation/data-prefetch@0.9.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@module-federation/runtime': 0.9.0 - '@module-federation/sdk': 0.9.0 - fs-extra: 9.1.0 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - '@module-federation/data-prefetch@0.9.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@module-federation/runtime': 0.9.1 @@ -22213,31 +22129,6 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - '@module-federation/dts-plugin@0.9.0(bufferutil@4.0.7)(typescript@5.7.3)': - dependencies: - '@module-federation/error-codes': 0.9.0 - '@module-federation/managers': 0.9.0 - '@module-federation/sdk': 0.9.0 - '@module-federation/third-party-dts-extractor': 0.9.0 - adm-zip: 0.5.16 - ansi-colors: 4.1.3 - axios: 1.8.3 - chalk: 3.0.0 - fs-extra: 9.1.0 - isomorphic-ws: 5.0.0(ws@8.18.0(bufferutil@4.0.7)) - koa: 2.15.4 - lodash.clonedeepwith: 4.5.0 - log4js: 6.9.1 - node-schedule: 2.1.1 - rambda: 9.3.0 - typescript: 5.7.3 - ws: 8.18.0(bufferutil@4.0.7) - transitivePeerDependencies: - - bufferutil - - debug - - supports-color - - utf-8-validate - '@module-federation/dts-plugin@0.9.1(bufferutil@4.0.7)(typescript@5.7.3)': dependencies: '@module-federation/error-codes': 0.9.1 @@ -22263,33 +22154,7 @@ snapshots: - supports-color - utf-8-validate - '@module-federation/enhanced@0.9.0(@rspack/core@1.2.6(@swc/helpers@0.5.11))(bufferutil@4.0.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.3)(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)))': - dependencies: - '@module-federation/bridge-react-webpack-plugin': 0.9.0 - '@module-federation/data-prefetch': 0.9.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@module-federation/dts-plugin': 0.9.0(bufferutil@4.0.7)(typescript@5.7.3) - '@module-federation/error-codes': 0.9.0 - '@module-federation/inject-external-runtime-core-plugin': 0.9.0(@module-federation/runtime-tools@0.9.0) - '@module-federation/managers': 0.9.0 - '@module-federation/manifest': 0.9.0(bufferutil@4.0.7)(typescript@5.7.3) - '@module-federation/rspack': 0.9.0(@rspack/core@1.2.6(@swc/helpers@0.5.11))(bufferutil@4.0.7)(typescript@5.7.3) - '@module-federation/runtime-tools': 0.9.0 - '@module-federation/sdk': 0.9.0 - btoa: 1.2.1 - upath: 2.0.1 - optionalDependencies: - typescript: 5.7.3 - webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)) - transitivePeerDependencies: - - '@rspack/core' - - bufferutil - - debug - - react - - react-dom - - supports-color - - utf-8-validate - - '@module-federation/enhanced@0.9.1(@rspack/core@1.2.6(@swc/helpers@0.5.11))(bufferutil@4.0.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.3)(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)))': + '@module-federation/enhanced@0.9.1(@rspack/core@1.2.6(@swc/helpers@0.5.11))(bufferutil@4.0.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.3)(webpack@5.98.0)': dependencies: '@module-federation/bridge-react-webpack-plugin': 0.9.1 '@module-federation/data-prefetch': 0.9.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -22305,7 +22170,7 @@ snapshots: upath: 2.0.1 optionalDependencies: typescript: 5.7.3 - webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)) + webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4) transitivePeerDependencies: - '@rspack/core' - bufferutil @@ -22317,45 +22182,18 @@ snapshots: '@module-federation/error-codes@0.8.4': {} - '@module-federation/error-codes@0.9.0': {} - '@module-federation/error-codes@0.9.1': {} - '@module-federation/inject-external-runtime-core-plugin@0.9.0(@module-federation/runtime-tools@0.9.0)': - dependencies: - '@module-federation/runtime-tools': 0.9.0 - '@module-federation/inject-external-runtime-core-plugin@0.9.1(@module-federation/runtime-tools@0.9.1)': dependencies: '@module-federation/runtime-tools': 0.9.1 - '@module-federation/managers@0.9.0': - dependencies: - '@module-federation/sdk': 0.9.0 - find-pkg: 2.0.0 - fs-extra: 9.1.0 - '@module-federation/managers@0.9.1': dependencies: '@module-federation/sdk': 0.9.1 find-pkg: 2.0.0 fs-extra: 9.1.0 - '@module-federation/manifest@0.9.0(bufferutil@4.0.7)(typescript@5.7.3)': - dependencies: - '@module-federation/dts-plugin': 0.9.0(bufferutil@4.0.7)(typescript@5.7.3) - '@module-federation/managers': 0.9.0 - '@module-federation/sdk': 0.9.0 - chalk: 3.0.0 - find-pkg: 2.0.0 - transitivePeerDependencies: - - bufferutil - - debug - - supports-color - - typescript - - utf-8-validate - - vue-tsc - '@module-federation/manifest@0.9.1(bufferutil@4.0.7)(typescript@5.7.3)': dependencies: '@module-federation/dts-plugin': 0.9.1(bufferutil@4.0.7)(typescript@5.7.3) @@ -22371,16 +22209,16 @@ snapshots: - utf-8-validate - vue-tsc - '@module-federation/node@2.6.27(@rspack/core@1.2.6(@swc/helpers@0.5.11))(bufferutil@4.0.7)(next@14.2.26(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.47.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.55.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.3)(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)))': + '@module-federation/node@2.6.27(@rspack/core@1.2.6(@swc/helpers@0.5.11))(bufferutil@4.0.7)(next@14.2.26(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.47.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.55.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.3)(webpack@5.98.0)': dependencies: - '@module-federation/enhanced': 0.9.1(@rspack/core@1.2.6(@swc/helpers@0.5.11))(bufferutil@4.0.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.3)(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) + '@module-federation/enhanced': 0.9.1(@rspack/core@1.2.6(@swc/helpers@0.5.11))(bufferutil@4.0.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.3)(webpack@5.98.0) '@module-federation/runtime': 0.9.1 '@module-federation/sdk': 0.9.1 - '@module-federation/utilities': 3.1.45(next@14.2.26(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.47.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.55.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) + '@module-federation/utilities': 3.1.45(next@14.2.26(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.47.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.55.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(webpack@5.98.0) btoa: 1.2.1 encoding: 0.1.13 node-fetch: 2.7.0(encoding@0.1.13) - webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)) + webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4) optionalDependencies: next: 14.2.26(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.47.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.55.0) react: 18.3.1 @@ -22394,24 +22232,6 @@ snapshots: - utf-8-validate - vue-tsc - '@module-federation/rspack@0.9.0(@rspack/core@1.2.6(@swc/helpers@0.5.11))(bufferutil@4.0.7)(typescript@5.7.3)': - dependencies: - '@module-federation/bridge-react-webpack-plugin': 0.9.0 - '@module-federation/dts-plugin': 0.9.0(bufferutil@4.0.7)(typescript@5.7.3) - '@module-federation/inject-external-runtime-core-plugin': 0.9.0(@module-federation/runtime-tools@0.9.0) - '@module-federation/managers': 0.9.0 - '@module-federation/manifest': 0.9.0(bufferutil@4.0.7)(typescript@5.7.3) - '@module-federation/runtime-tools': 0.9.0 - '@module-federation/sdk': 0.9.0 - '@rspack/core': 1.2.6(@swc/helpers@0.5.11) - optionalDependencies: - typescript: 5.7.3 - transitivePeerDependencies: - - bufferutil - - debug - - supports-color - - utf-8-validate - '@module-federation/rspack@0.9.1(@rspack/core@1.2.6(@swc/helpers@0.5.11))(bufferutil@4.0.7)(typescript@5.7.3)': dependencies: '@module-federation/bridge-react-webpack-plugin': 0.9.1 @@ -22430,11 +22250,6 @@ snapshots: - supports-color - utf-8-validate - '@module-federation/runtime-core@0.6.21': - dependencies: - '@module-federation/error-codes': 0.9.0 - '@module-federation/sdk': 0.9.0 - '@module-federation/runtime-core@0.9.1': dependencies: '@module-federation/error-codes': 0.9.1 @@ -22450,11 +22265,6 @@ snapshots: '@module-federation/runtime': 0.8.4 '@module-federation/webpack-bundler-runtime': 0.8.4 - '@module-federation/runtime-tools@0.9.0': - dependencies: - '@module-federation/runtime': 0.9.0 - '@module-federation/webpack-bundler-runtime': 0.9.0 - '@module-federation/runtime-tools@0.9.1': dependencies: '@module-federation/runtime': 0.9.1 @@ -22469,12 +22279,6 @@ snapshots: '@module-federation/error-codes': 0.8.4 '@module-federation/sdk': 0.8.4 - '@module-federation/runtime@0.9.0': - dependencies: - '@module-federation/error-codes': 0.9.0 - '@module-federation/runtime-core': 0.6.21 - '@module-federation/sdk': 0.9.0 - '@module-federation/runtime@0.9.1': dependencies: '@module-federation/error-codes': 0.9.1 @@ -22493,22 +22297,16 @@ snapshots: '@module-federation/sdk@0.9.1': {} - '@module-federation/third-party-dts-extractor@0.9.0': - dependencies: - find-pkg: 2.0.0 - fs-extra: 9.1.0 - resolve: 1.22.8 - '@module-federation/third-party-dts-extractor@0.9.1': dependencies: find-pkg: 2.0.0 fs-extra: 9.1.0 resolve: 1.22.8 - '@module-federation/utilities@3.1.45(next@14.2.26(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.47.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.55.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)))': + '@module-federation/utilities@3.1.45(next@14.2.26(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.47.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.55.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(webpack@5.98.0)': dependencies: '@module-federation/sdk': 0.9.1 - webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)) + webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4) optionalDependencies: next: 14.2.26(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.47.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.55.0) react: 18.3.1 @@ -22524,11 +22322,6 @@ snapshots: '@module-federation/runtime': 0.8.4 '@module-federation/sdk': 0.8.4 - '@module-federation/webpack-bundler-runtime@0.9.0': - dependencies: - '@module-federation/runtime': 0.9.0 - '@module-federation/sdk': 0.9.0 - '@module-federation/webpack-bundler-runtime@0.9.1': dependencies: '@module-federation/runtime': 0.9.1 @@ -22928,7 +22721,7 @@ snapshots: '@napi-rs/wasm-tools-win32-ia32-msvc': 0.0.2 '@napi-rs/wasm-tools-win32-x64-msvc': 0.0.2 - '@nestjs/cli@10.4.5(@swc/cli@0.6.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(chokidar@3.6.0))(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))': + '@nestjs/cli@10.4.5(@swc/cli@0.6.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(chokidar@3.6.0))(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4)': dependencies: '@angular-devkit/core': 17.3.8(chokidar@3.6.0) '@angular-devkit/schematics': 17.3.8(chokidar@3.6.0) @@ -22938,7 +22731,7 @@ snapshots: chokidar: 3.6.0 cli-table3: 0.6.5 commander: 4.1.1 - fork-ts-checker-webpack-plugin: 9.0.2(typescript@5.3.3)(webpack@5.94.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) + fork-ts-checker-webpack-plugin: 9.0.2(typescript@5.3.3)(webpack@5.94.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4)) glob: 10.4.2 inquirer: 8.2.6 node-emoji: 1.11.0 @@ -22947,7 +22740,7 @@ snapshots: tsconfig-paths: 4.2.0 tsconfig-paths-webpack-plugin: 4.1.0 typescript: 5.3.3 - webpack: 5.94.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)) + webpack: 5.94.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4) webpack-node-externals: 3.0.0 optionalDependencies: '@swc/cli': 0.6.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(chokidar@3.6.0) @@ -23019,7 +22812,7 @@ snapshots: transitivePeerDependencies: - chokidar - '@nestjs/swagger@6.3.0(@nestjs/common@9.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@9.4.3(@nestjs/common@9.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@9.4.3)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2)': + '@nestjs/swagger@6.3.0(@nestjs/common@9.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@9.4.3)(reflect-metadata@0.2.2)': dependencies: '@nestjs/common': 9.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1) '@nestjs/core': 9.4.3(@nestjs/common@9.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@9.4.3)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1) @@ -23030,7 +22823,7 @@ snapshots: reflect-metadata: 0.2.2 swagger-ui-dist: 4.18.2 - '@nestjs/testing@9.4.3(@nestjs/common@9.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@9.4.3(@nestjs/common@9.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@9.4.3)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@9.4.3(@nestjs/common@9.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@9.4.3))': + '@nestjs/testing@9.4.3(@nestjs/common@9.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@9.4.3)(@nestjs/platform-express@9.4.3)': dependencies: '@nestjs/common': 9.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1) '@nestjs/core': 9.4.3(@nestjs/common@9.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@9.4.3)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1) @@ -23097,11 +22890,11 @@ snapshots: rxjs: 7.8.1 tslib: 2.8.1 - '@ngtools/webpack@19.2.0(@angular/compiler-cli@19.2.0(@angular/compiler@19.2.0(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.7.3))(typescript@5.7.3)(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)))': + '@ngtools/webpack@19.2.0(@angular/compiler-cli@19.2.0(@angular/compiler@19.2.0(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.7.3))(typescript@5.7.3)(webpack@5.98.0)': dependencies: '@angular/compiler-cli': 19.2.0(@angular/compiler@19.2.0(@angular/core@19.2.0(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.7.3) typescript: 5.7.3 - webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)) + webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4) '@nodelib/fs.scandir@2.1.5': dependencies: @@ -23448,18 +23241,18 @@ snapshots: transitivePeerDependencies: - encoding - '@nx/angular@20.7.0-beta.3(tzslubwrec4apcoypka5lfsxri)': + '@nx/angular@20.7.0-beta.3(ryoth37z7gtdyc75iokmmmptla)': dependencies: - '@angular-devkit/build-angular': 19.2.0(stndy3yfcthbju7r3pdqp75gku) + '@angular-devkit/build-angular': 19.2.0(fiob6yppbuzqqpw7bpjs7r5k6m) '@angular-devkit/core': 19.2.0(chokidar@3.6.0) '@angular-devkit/schematics': 19.2.0(chokidar@3.6.0) '@nx/devkit': 20.7.0-beta.3(nx@20.7.0-beta.3(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11))) '@nx/eslint': 20.7.0-beta.3(@babel/traverse@7.26.9)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@20.16.10)(@zkochan/js-yaml@0.0.7)(eslint@8.57.0)(nx@20.7.0-beta.3(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(verdaccio@6.0.5(encoding@0.1.13)(typanion@3.14.0)) '@nx/js': 20.7.0-beta.3(@babel/traverse@7.26.9)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@20.16.10)(nx@20.7.0-beta.3(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.7.3)(verdaccio@6.0.5(encoding@0.1.13)(typanion@3.14.0)) - '@nx/module-federation': 20.7.0-beta.3(@babel/traverse@7.26.9)(@rspack/core@1.2.6(@swc/helpers@0.5.11))(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@20.16.10)(bufferutil@4.0.7)(esbuild@0.25.0)(next@14.2.26(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.47.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.55.0))(nx@20.7.0-beta.3(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.3)(verdaccio@6.0.5(encoding@0.1.13)(typanion@3.14.0))(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)) - '@nx/rspack': 20.7.0-beta.3(fld5masubvtf5unq5sgvap2d2a) + '@nx/module-federation': 20.7.0-beta.3(@babel/traverse@7.26.9)(@rspack/core@1.2.6(@swc/helpers@0.5.11))(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@20.16.10)(bufferutil@4.0.7)(esbuild@0.25.0)(next@14.2.26(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.47.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.55.0))(nx@20.7.0-beta.3(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.3)(verdaccio@6.0.5(encoding@0.1.13)(typanion@3.14.0))(webpack-cli@5.1.4) + '@nx/rspack': 20.7.0-beta.3(vtmcegwkmmmnniq2i67f4x3n7u) '@nx/web': 20.7.0-beta.3(@babel/traverse@7.26.9)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@20.16.10)(nx@20.7.0-beta.3(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.7.3)(verdaccio@6.0.5(encoding@0.1.13)(typanion@3.14.0)) - '@nx/webpack': 20.7.0-beta.3(@babel/traverse@7.26.9)(@rspack/core@1.2.6(@swc/helpers@0.5.11))(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@20.16.10)(bufferutil@4.0.7)(esbuild@0.25.0)(html-webpack-plugin@5.5.0(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))))(nx@20.7.0-beta.3(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.7.3)(verdaccio@6.0.5(encoding@0.1.13)(typanion@3.14.0))(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)) + '@nx/webpack': 20.7.0-beta.3(@babel/traverse@7.26.9)(@rspack/core@1.2.6(@swc/helpers@0.5.11))(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@20.16.10)(bufferutil@4.0.7)(esbuild@0.25.0)(html-webpack-plugin@5.5.0(webpack@5.98.0))(nx@20.7.0-beta.3(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.7.3)(verdaccio@6.0.5(encoding@0.1.13)(typanion@3.14.0))(webpack-cli@5.1.4) '@nx/workspace': 20.7.0-beta.3(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11)) '@phenomnomnominal/tsquery': 5.0.1(typescript@5.7.3) '@schematics/angular': 19.2.0(chokidar@3.6.0) @@ -23788,10 +23581,10 @@ snapshots: transitivePeerDependencies: - debug - '@nx/module-federation@20.7.0-beta.3(@babel/traverse@7.26.9)(@rspack/core@1.2.6(@swc/helpers@0.5.11))(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@20.16.10)(bufferutil@4.0.7)(esbuild@0.25.0)(next@14.2.26(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.47.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.55.0))(nx@20.7.0-beta.3(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.3)(verdaccio@6.0.5(encoding@0.1.13)(typanion@3.14.0))(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))': + '@nx/module-federation@20.7.0-beta.3(@babel/traverse@7.26.9)(@rspack/core@1.2.6(@swc/helpers@0.5.11))(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@20.16.10)(bufferutil@4.0.7)(esbuild@0.25.0)(next@14.2.26(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.47.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.55.0))(nx@20.7.0-beta.3(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.3)(verdaccio@6.0.5(encoding@0.1.13)(typanion@3.14.0))(webpack-cli@5.1.4)': dependencies: - '@module-federation/enhanced': 0.9.1(@rspack/core@1.2.6(@swc/helpers@0.5.11))(bufferutil@4.0.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.3)(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) - '@module-federation/node': 2.6.27(@rspack/core@1.2.6(@swc/helpers@0.5.11))(bufferutil@4.0.7)(next@14.2.26(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.47.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.55.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.3)(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) + '@module-federation/enhanced': 0.9.1(@rspack/core@1.2.6(@swc/helpers@0.5.11))(bufferutil@4.0.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.3)(webpack@5.98.0) + '@module-federation/node': 2.6.27(@rspack/core@1.2.6(@swc/helpers@0.5.11))(bufferutil@4.0.7)(next@14.2.26(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.47.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.55.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.3)(webpack@5.98.0) '@module-federation/sdk': 0.9.1 '@nx/devkit': 20.7.0-beta.3(nx@20.7.0-beta.3(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11))) '@nx/js': 20.7.0-beta.3(@babel/traverse@7.26.9)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@20.16.10)(nx@20.7.0-beta.3(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.7.3)(verdaccio@6.0.5(encoding@0.1.13)(typanion@3.14.0)) @@ -23801,7 +23594,7 @@ snapshots: http-proxy-middleware: 3.0.3 picocolors: 1.1.1 tslib: 2.8.1 - webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)) + webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4) transitivePeerDependencies: - '@babel/traverse' - '@swc-node/register' @@ -23823,19 +23616,19 @@ snapshots: - vue-tsc - webpack-cli - '@nx/next@20.7.0-beta.3(h5y73l76peitcbgx2i2ni7n6ou)': + '@nx/next@20.7.0-beta.3(@babel/core@7.25.2)(@babel/traverse@7.26.9)(@rspack/core@1.2.6(@swc/helpers@0.5.11))(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@20.16.10)(@zkochan/js-yaml@0.0.7)(bufferutil@4.0.7)(esbuild@0.25.0)(eslint@8.57.0)(html-webpack-plugin@5.5.0(webpack@5.98.0))(next@14.2.26(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.47.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.55.0))(nx@20.7.0-beta.3(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.3)(verdaccio@6.0.5(encoding@0.1.13)(typanion@3.14.0))(webpack-cli@5.1.4)(webpack@5.98.0)': dependencies: '@babel/plugin-proposal-decorators': 7.24.7(@babel/core@7.25.2) '@nx/devkit': 20.7.0-beta.3(nx@20.7.0-beta.3(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11))) '@nx/eslint': 20.7.0-beta.3(@babel/traverse@7.26.9)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@20.16.10)(@zkochan/js-yaml@0.0.7)(eslint@8.57.0)(nx@20.7.0-beta.3(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(verdaccio@6.0.5(encoding@0.1.13)(typanion@3.14.0)) '@nx/js': 20.7.0-beta.3(@babel/traverse@7.26.9)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@20.16.10)(nx@20.7.0-beta.3(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.7.3)(verdaccio@6.0.5(encoding@0.1.13)(typanion@3.14.0)) - '@nx/react': 20.7.0-beta.3(@babel/traverse@7.26.9)(@rspack/core@1.2.6(@swc/helpers@0.5.11))(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@20.16.10)(@zkochan/js-yaml@0.0.7)(bufferutil@4.0.7)(esbuild@0.25.0)(eslint@8.57.0)(next@14.2.26(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.47.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.55.0))(nx@20.7.0-beta.3(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.3)(verdaccio@6.0.5(encoding@0.1.13)(typanion@3.14.0))(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) + '@nx/react': 20.7.0-beta.3(@babel/traverse@7.26.9)(@rspack/core@1.2.6(@swc/helpers@0.5.11))(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@20.16.10)(@zkochan/js-yaml@0.0.7)(bufferutil@4.0.7)(esbuild@0.25.0)(eslint@8.57.0)(next@14.2.26(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.47.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.55.0))(nx@20.7.0-beta.3(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.3)(verdaccio@6.0.5(encoding@0.1.13)(typanion@3.14.0))(webpack-cli@5.1.4)(webpack@5.98.0) '@nx/web': 20.7.0-beta.3(@babel/traverse@7.26.9)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@20.16.10)(nx@20.7.0-beta.3(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.7.3)(verdaccio@6.0.5(encoding@0.1.13)(typanion@3.14.0)) - '@nx/webpack': 20.7.0-beta.3(@babel/traverse@7.26.9)(@rspack/core@1.2.6(@swc/helpers@0.5.11))(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@20.16.10)(bufferutil@4.0.7)(esbuild@0.25.0)(html-webpack-plugin@5.5.0(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))))(nx@20.7.0-beta.3(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.7.3)(verdaccio@6.0.5(encoding@0.1.13)(typanion@3.14.0))(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)) + '@nx/webpack': 20.7.0-beta.3(@babel/traverse@7.26.9)(@rspack/core@1.2.6(@swc/helpers@0.5.11))(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@20.16.10)(bufferutil@4.0.7)(esbuild@0.25.0)(html-webpack-plugin@5.5.0(webpack@5.98.0))(nx@20.7.0-beta.3(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.7.3)(verdaccio@6.0.5(encoding@0.1.13)(typanion@3.14.0))(webpack-cli@5.1.4) '@phenomnomnominal/tsquery': 5.0.1(typescript@5.7.3) '@svgr/webpack': 8.1.0(typescript@5.7.3) - copy-webpack-plugin: 10.2.4(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) - file-loader: 6.2.0(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) + copy-webpack-plugin: 10.2.4(webpack@5.98.0) + file-loader: 6.2.0(webpack@5.98.0) ignore: 5.3.2 next: 14.2.26(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.47.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.55.0) semver: 7.7.1 @@ -23949,17 +23742,17 @@ snapshots: transitivePeerDependencies: - debug - '@nx/react@20.7.0-beta.3(@babel/traverse@7.26.9)(@rspack/core@1.2.6(@swc/helpers@0.5.11))(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@20.16.10)(@zkochan/js-yaml@0.0.7)(bufferutil@4.0.7)(esbuild@0.25.0)(eslint@8.57.0)(next@14.2.26(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.47.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.55.0))(nx@20.7.0-beta.3(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.3)(verdaccio@6.0.5(encoding@0.1.13)(typanion@3.14.0))(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)))': + '@nx/react@20.7.0-beta.3(@babel/traverse@7.26.9)(@rspack/core@1.2.6(@swc/helpers@0.5.11))(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@20.16.10)(@zkochan/js-yaml@0.0.7)(bufferutil@4.0.7)(esbuild@0.25.0)(eslint@8.57.0)(next@14.2.26(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.47.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.55.0))(nx@20.7.0-beta.3(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.3)(verdaccio@6.0.5(encoding@0.1.13)(typanion@3.14.0))(webpack-cli@5.1.4)(webpack@5.98.0)': dependencies: '@nx/devkit': 20.7.0-beta.3(nx@20.7.0-beta.3(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11))) '@nx/eslint': 20.7.0-beta.3(@babel/traverse@7.26.9)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@20.16.10)(@zkochan/js-yaml@0.0.7)(eslint@8.57.0)(nx@20.7.0-beta.3(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(verdaccio@6.0.5(encoding@0.1.13)(typanion@3.14.0)) '@nx/js': 20.7.0-beta.3(@babel/traverse@7.26.9)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@20.16.10)(nx@20.7.0-beta.3(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.7.3)(verdaccio@6.0.5(encoding@0.1.13)(typanion@3.14.0)) - '@nx/module-federation': 20.7.0-beta.3(@babel/traverse@7.26.9)(@rspack/core@1.2.6(@swc/helpers@0.5.11))(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@20.16.10)(bufferutil@4.0.7)(esbuild@0.25.0)(next@14.2.26(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.47.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.55.0))(nx@20.7.0-beta.3(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.3)(verdaccio@6.0.5(encoding@0.1.13)(typanion@3.14.0))(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)) + '@nx/module-federation': 20.7.0-beta.3(@babel/traverse@7.26.9)(@rspack/core@1.2.6(@swc/helpers@0.5.11))(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@20.16.10)(bufferutil@4.0.7)(esbuild@0.25.0)(next@14.2.26(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.47.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.55.0))(nx@20.7.0-beta.3(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.3)(verdaccio@6.0.5(encoding@0.1.13)(typanion@3.14.0))(webpack-cli@5.1.4) '@nx/web': 20.7.0-beta.3(@babel/traverse@7.26.9)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@20.16.10)(nx@20.7.0-beta.3(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.7.3)(verdaccio@6.0.5(encoding@0.1.13)(typanion@3.14.0)) '@phenomnomnominal/tsquery': 5.0.1(typescript@5.7.3) '@svgr/webpack': 8.1.0(typescript@5.7.3) express: 4.21.2 - file-loader: 6.2.0(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) + file-loader: 6.2.0(webpack@5.98.0) http-proxy-middleware: 3.0.3 minimatch: 9.0.3 picocolors: 1.1.1 @@ -24010,39 +23803,39 @@ snapshots: - typescript - verdaccio - '@nx/rspack@20.7.0-beta.3(fld5masubvtf5unq5sgvap2d2a)': + '@nx/rspack@20.7.0-beta.3(vtmcegwkmmmnniq2i67f4x3n7u)': dependencies: - '@module-federation/enhanced': 0.9.0(@rspack/core@1.2.6(@swc/helpers@0.5.11))(bufferutil@4.0.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.3)(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) - '@module-federation/node': 2.6.27(@rspack/core@1.2.6(@swc/helpers@0.5.11))(bufferutil@4.0.7)(next@14.2.26(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.47.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.55.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.3)(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) + '@module-federation/enhanced': 0.9.1(@rspack/core@1.2.6(@swc/helpers@0.5.11))(bufferutil@4.0.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.3)(webpack@5.98.0) + '@module-federation/node': 2.6.27(@rspack/core@1.2.6(@swc/helpers@0.5.11))(bufferutil@4.0.7)(next@14.2.26(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.47.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.55.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.3)(webpack@5.98.0) '@nx/devkit': 20.7.0-beta.3(nx@20.7.0-beta.3(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11))) '@nx/js': 20.7.0-beta.3(@babel/traverse@7.26.9)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@20.16.10)(nx@20.7.0-beta.3(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.7.3)(verdaccio@6.0.5(encoding@0.1.13)(typanion@3.14.0)) - '@nx/module-federation': 20.7.0-beta.3(@babel/traverse@7.26.9)(@rspack/core@1.2.6(@swc/helpers@0.5.11))(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@20.16.10)(bufferutil@4.0.7)(esbuild@0.25.0)(next@14.2.26(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.47.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.55.0))(nx@20.7.0-beta.3(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.3)(verdaccio@6.0.5(encoding@0.1.13)(typanion@3.14.0))(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)) + '@nx/module-federation': 20.7.0-beta.3(@babel/traverse@7.26.9)(@rspack/core@1.2.6(@swc/helpers@0.5.11))(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@20.16.10)(bufferutil@4.0.7)(esbuild@0.25.0)(next@14.2.26(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.47.1)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.55.0))(nx@20.7.0-beta.3(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.3)(verdaccio@6.0.5(encoding@0.1.13)(typanion@3.14.0))(webpack-cli@5.1.4) '@nx/web': 20.7.0-beta.3(@babel/traverse@7.26.9)(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@20.16.10)(nx@20.7.0-beta.3(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.7.3)(verdaccio@6.0.5(encoding@0.1.13)(typanion@3.14.0)) '@phenomnomnominal/tsquery': 5.0.1(typescript@5.7.3) '@rspack/core': 1.2.6(@swc/helpers@0.5.11) - '@rspack/dev-server': 1.0.9(@rspack/core@1.2.6(@swc/helpers@0.5.11))(@types/express@4.17.21)(bufferutil@4.0.7)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) + '@rspack/dev-server': 1.0.9(@rspack/core@1.2.6(@swc/helpers@0.5.11))(@types/express@4.17.21)(bufferutil@4.0.7)(webpack-cli@5.1.4)(webpack@5.98.0) '@rspack/plugin-react-refresh': 1.0.0(react-refresh@0.10.0) autoprefixer: 10.4.13(postcss@8.4.38) browserslist: 4.24.4 - css-loader: 6.11.0(@rspack/core@1.2.6(@swc/helpers@0.5.11))(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) + css-loader: 6.11.0(@rspack/core@1.2.6(@swc/helpers@0.5.11))(webpack@5.98.0) enquirer: 2.3.6 express: 4.21.2 http-proxy-middleware: 3.0.3 - less-loader: 11.1.0(less@4.1.3)(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) - license-webpack-plugin: 4.0.2(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) + less-loader: 11.1.0(less@4.1.3)(webpack@5.98.0) + license-webpack-plugin: 4.0.2(webpack@5.98.0) loader-utils: 2.0.3 picocolors: 1.1.1 postcss: 8.4.38 postcss-import: 14.1.0(postcss@8.4.38) - postcss-loader: 8.1.1(@rspack/core@1.2.6(@swc/helpers@0.5.11))(postcss@8.4.38)(typescript@5.7.3)(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) + postcss-loader: 8.1.1(@rspack/core@1.2.6(@swc/helpers@0.5.11))(postcss@8.4.38)(typescript@5.7.3)(webpack@5.98.0) sass: 1.85.0 sass-embedded: 1.85.1 - sass-loader: 16.0.5(@rspack/core@1.2.6(@swc/helpers@0.5.11))(sass-embedded@1.85.1)(sass@1.85.0)(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) - source-map-loader: 5.0.0(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) - style-loader: 3.3.4(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) + sass-loader: 16.0.5(@rspack/core@1.2.6(@swc/helpers@0.5.11))(sass-embedded@1.85.1)(sass@1.85.0)(webpack@5.98.0) + source-map-loader: 5.0.0(webpack@5.98.0) + style-loader: 3.3.4(webpack@5.98.0) ts-checker-rspack-plugin: 1.1.1(@rspack/core@1.2.6(@swc/helpers@0.5.11))(typescript@5.7.3) tslib: 2.8.1 - webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)) + webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4) webpack-node-externals: 3.0.0 transitivePeerDependencies: - '@babel/traverse' @@ -24139,7 +23932,7 @@ snapshots: - typescript - verdaccio - '@nx/webpack@20.7.0-beta.3(@babel/traverse@7.26.9)(@rspack/core@1.2.6(@swc/helpers@0.5.11))(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@20.16.10)(bufferutil@4.0.7)(esbuild@0.25.0)(html-webpack-plugin@5.5.0(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))))(nx@20.7.0-beta.3(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.7.3)(verdaccio@6.0.5(encoding@0.1.13)(typanion@3.14.0))(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))': + '@nx/webpack@20.7.0-beta.3(@babel/traverse@7.26.9)(@rspack/core@1.2.6(@swc/helpers@0.5.11))(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@20.16.10)(bufferutil@4.0.7)(esbuild@0.25.0)(html-webpack-plugin@5.5.0(webpack@5.98.0))(nx@20.7.0-beta.3(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(typescript@5.7.3)(verdaccio@6.0.5(encoding@0.1.13)(typanion@3.14.0))(webpack-cli@5.1.4)': dependencies: '@babel/core': 7.26.9 '@nx/devkit': 20.7.0-beta.3(nx@20.7.0-beta.3(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.7.3))(@swc/core@1.5.7(@swc/helpers@0.5.11))) @@ -24147,38 +23940,38 @@ snapshots: '@phenomnomnominal/tsquery': 5.0.1(typescript@5.7.3) ajv: 8.17.1 autoprefixer: 10.4.13(postcss@8.4.38) - babel-loader: 9.2.1(@babel/core@7.26.9)(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) + babel-loader: 9.2.1(@babel/core@7.26.9)(webpack@5.98.0) browserslist: 4.24.4 - copy-webpack-plugin: 10.2.4(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) - css-loader: 6.11.0(@rspack/core@1.2.6(@swc/helpers@0.5.11))(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) - css-minimizer-webpack-plugin: 5.0.1(esbuild@0.25.0)(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) - fork-ts-checker-webpack-plugin: 7.2.13(typescript@5.7.3)(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) + copy-webpack-plugin: 10.2.4(webpack@5.98.0) + css-loader: 6.11.0(@rspack/core@1.2.6(@swc/helpers@0.5.11))(webpack@5.98.0) + css-minimizer-webpack-plugin: 5.0.1(esbuild@0.25.0)(webpack@5.98.0) + fork-ts-checker-webpack-plugin: 7.2.13(typescript@5.7.3)(webpack@5.98.0) less: 4.1.3 - less-loader: 11.1.0(less@4.1.3)(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) - license-webpack-plugin: 4.0.2(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) + less-loader: 11.1.0(less@4.1.3)(webpack@5.98.0) + license-webpack-plugin: 4.0.2(webpack@5.98.0) loader-utils: 2.0.3 - mini-css-extract-plugin: 2.4.7(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) + mini-css-extract-plugin: 2.4.7(webpack@5.98.0) parse5: 4.0.0 picocolors: 1.1.1 postcss: 8.4.38 postcss-import: 14.1.0(postcss@8.4.38) - postcss-loader: 6.2.1(postcss@8.4.38)(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) + postcss-loader: 6.2.1(postcss@8.4.38)(webpack@5.98.0) rxjs: 7.8.1 sass: 1.85.0 sass-embedded: 1.85.1 - sass-loader: 16.0.5(@rspack/core@1.2.6(@swc/helpers@0.5.11))(sass-embedded@1.85.1)(sass@1.85.0)(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) - source-map-loader: 5.0.0(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) - style-loader: 3.3.4(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) + sass-loader: 16.0.5(@rspack/core@1.2.6(@swc/helpers@0.5.11))(sass-embedded@1.85.1)(sass@1.85.0)(webpack@5.98.0) + source-map-loader: 5.0.0(webpack@5.98.0) + style-loader: 3.3.4(webpack@5.98.0) stylus: 0.64.0 - stylus-loader: 7.1.3(stylus@0.64.0)(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) - terser-webpack-plugin: 5.3.11(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) - ts-loader: 9.5.1(typescript@5.7.3)(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) + stylus-loader: 7.1.3(stylus@0.64.0)(webpack@5.98.0) + terser-webpack-plugin: 5.3.11(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack@5.98.0) + ts-loader: 9.5.1(typescript@5.7.3)(webpack@5.98.0) tsconfig-paths-webpack-plugin: 4.0.0 tslib: 2.8.1 - webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)) - webpack-dev-server: 5.2.1(bufferutil@4.0.7)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) + webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4) + webpack-dev-server: 5.2.1(bufferutil@4.0.7)(webpack-cli@5.1.4)(webpack@5.98.0) webpack-node-externals: 3.0.0 - webpack-subresource-integrity: 5.1.0(html-webpack-plugin@5.5.0(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))))(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) + webpack-subresource-integrity: 5.1.0(html-webpack-plugin@5.5.0(webpack@5.98.0))(webpack@5.98.0) transitivePeerDependencies: - '@babel/traverse' - '@parcel/css' @@ -24652,7 +24445,7 @@ snapshots: dependencies: playwright: 1.47.1 - '@pmmmwh/react-refresh-webpack-plugin@0.5.15(react-refresh@0.10.0)(type-fest@3.13.1)(webpack-dev-server@5.2.1(bufferutil@4.0.7)(webpack-cli@5.1.4)(webpack@5.98.0))(webpack-hot-middleware@2.26.1)(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)))': + '@pmmmwh/react-refresh-webpack-plugin@0.5.15(react-refresh@0.10.0)(type-fest@3.13.1)(webpack-dev-server@5.2.1)(webpack-hot-middleware@2.26.1)(webpack@5.98.0)': dependencies: ansi-html: 0.0.9 core-js-pure: 3.38.1 @@ -24662,10 +24455,10 @@ snapshots: react-refresh: 0.10.0 schema-utils: 4.2.0 source-map: 0.7.3 - webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)) + webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4) optionalDependencies: type-fest: 3.13.1 - webpack-dev-server: 5.2.1(bufferutil@4.0.7)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) + webpack-dev-server: 5.2.1(bufferutil@4.0.7)(webpack-cli@5.1.4)(webpack@5.98.0) webpack-hot-middleware: 2.26.1 '@pnpm/lockfile-types@6.0.0': @@ -25453,7 +25246,7 @@ snapshots: optionalDependencies: '@swc/helpers': 0.5.11 - '@rspack/dev-server@1.0.9(@rspack/core@1.2.6(@swc/helpers@0.5.11))(@types/express@4.17.21)(bufferutil@4.0.7)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)))': + '@rspack/dev-server@1.0.9(@rspack/core@1.2.6(@swc/helpers@0.5.11))(@types/express@4.17.21)(bufferutil@4.0.7)(webpack-cli@5.1.4)(webpack@5.98.0)': dependencies: '@rspack/core': 1.2.6(@swc/helpers@0.5.11) chokidar: 3.6.0 @@ -25462,8 +25255,8 @@ snapshots: http-proxy-middleware: 2.0.7(@types/express@4.17.21) mime-types: 2.1.35 p-retry: 4.6.2 - webpack-dev-middleware: 7.4.2(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) - webpack-dev-server: 5.0.4(bufferutil@4.0.7)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) + webpack-dev-middleware: 7.4.2(webpack@5.98.0) + webpack-dev-server: 5.0.4(bufferutil@4.0.7)(webpack-cli@5.1.4)(webpack@5.98.0) ws: 8.18.0(bufferutil@4.0.7) transitivePeerDependencies: - '@types/express' @@ -25658,7 +25451,7 @@ snapshots: transitivePeerDependencies: - webpack-sources - '@storybook/builder-webpack5@8.4.6(@rspack/core@1.2.6(@swc/helpers@0.5.11))(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(storybook@8.4.6(bufferutil@4.0.7)(prettier@2.8.8))(typescript@5.7.3)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))': + '@storybook/builder-webpack5@8.4.6(@rspack/core@1.2.6(@swc/helpers@0.5.11))(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(storybook@8.4.6(bufferutil@4.0.7)(prettier@2.8.8))(typescript@5.7.3)(webpack-cli@5.1.4)': dependencies: '@storybook/core-webpack': 8.4.6(storybook@8.4.6(bufferutil@4.0.7)(prettier@2.8.8)) '@types/node': 22.5.5 @@ -25667,23 +25460,23 @@ snapshots: case-sensitive-paths-webpack-plugin: 2.4.0 cjs-module-lexer: 1.4.1 constants-browserify: 1.0.0 - css-loader: 6.11.0(@rspack/core@1.2.6(@swc/helpers@0.5.11))(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) + css-loader: 6.11.0(@rspack/core@1.2.6(@swc/helpers@0.5.11))(webpack@5.98.0) es-module-lexer: 1.5.4 - fork-ts-checker-webpack-plugin: 8.0.0(typescript@5.7.3)(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) - html-webpack-plugin: 5.5.0(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) + fork-ts-checker-webpack-plugin: 8.0.0(typescript@5.7.3)(webpack@5.98.0) + html-webpack-plugin: 5.5.0(webpack@5.98.0) magic-string: 0.30.17 path-browserify: 1.0.1 process: 0.11.10 semver: 7.7.1 storybook: 8.4.6(bufferutil@4.0.7)(prettier@2.8.8) - style-loader: 3.3.4(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) - terser-webpack-plugin: 5.3.10(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) + style-loader: 3.3.4(webpack@5.98.0) + terser-webpack-plugin: 5.3.10(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack@5.98.0) ts-dedent: 2.2.0 url: 0.11.4 util: 0.12.5 util-deprecate: 1.0.2 - webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)) - webpack-dev-middleware: 6.1.3(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) + webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4) + webpack-dev-middleware: 6.1.3(webpack@5.98.0) webpack-hot-middleware: 2.26.1 webpack-virtual-modules: 0.6.2 optionalDependencies: @@ -25773,11 +25566,11 @@ snapshots: dependencies: storybook: 8.4.6(bufferutil@4.0.7)(prettier@2.8.8) - '@storybook/preset-react-webpack@8.4.6(@storybook/test@8.4.6(storybook@8.4.6(bufferutil@4.0.7)(prettier@2.8.8)))(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.4.6(bufferutil@4.0.7)(prettier@2.8.8))(typescript@5.7.3)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))': + '@storybook/preset-react-webpack@8.4.6(@storybook/test@8.4.6(storybook@8.4.6(bufferutil@4.0.7)(prettier@2.8.8)))(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.4.6(bufferutil@4.0.7)(prettier@2.8.8))(typescript@5.7.3)(webpack-cli@5.1.4)': dependencies: '@storybook/core-webpack': 8.4.6(storybook@8.4.6(bufferutil@4.0.7)(prettier@2.8.8)) '@storybook/react': 8.4.6(@storybook/test@8.4.6(storybook@8.4.6(bufferutil@4.0.7)(prettier@2.8.8)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.4.6(bufferutil@4.0.7)(prettier@2.8.8))(typescript@5.7.3) - '@storybook/react-docgen-typescript-plugin': 1.0.6--canary.9.0c3f3b7.0(typescript@5.7.3)(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) + '@storybook/react-docgen-typescript-plugin': 1.0.6--canary.9.0c3f3b7.0(typescript@5.7.3)(webpack@5.98.0) '@types/node': 22.5.5 '@types/semver': 7.5.8 find-up: 5.0.0 @@ -25789,7 +25582,7 @@ snapshots: semver: 7.7.1 storybook: 8.4.6(bufferutil@4.0.7)(prettier@2.8.8) tsconfig-paths: 4.2.0 - webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)) + webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4) optionalDependencies: typescript: 5.7.3 transitivePeerDependencies: @@ -25804,7 +25597,7 @@ snapshots: dependencies: storybook: 8.4.6(bufferutil@4.0.7)(prettier@2.8.8) - '@storybook/react-docgen-typescript-plugin@1.0.6--canary.9.0c3f3b7.0(typescript@5.7.3)(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)))': + '@storybook/react-docgen-typescript-plugin@1.0.6--canary.9.0c3f3b7.0(typescript@5.7.3)(webpack@5.98.0)': dependencies: debug: 4.4.0 endent: 2.1.0 @@ -25814,7 +25607,7 @@ snapshots: react-docgen-typescript: 2.2.2(typescript@5.7.3) tslib: 2.8.1 typescript: 5.7.3 - webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)) + webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4) transitivePeerDependencies: - supports-color @@ -25846,10 +25639,10 @@ snapshots: - typescript - webpack-sources - '@storybook/react-webpack5@8.4.6(@rspack/core@1.2.6(@swc/helpers@0.5.11))(@storybook/test@8.4.6(storybook@8.4.6(bufferutil@4.0.7)(prettier@2.8.8)))(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.4.6(bufferutil@4.0.7)(prettier@2.8.8))(typescript@5.7.3)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))': + '@storybook/react-webpack5@8.4.6(@rspack/core@1.2.6(@swc/helpers@0.5.11))(@storybook/test@8.4.6(storybook@8.4.6(bufferutil@4.0.7)(prettier@2.8.8)))(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.4.6(bufferutil@4.0.7)(prettier@2.8.8))(typescript@5.7.3)(webpack-cli@5.1.4)': dependencies: - '@storybook/builder-webpack5': 8.4.6(@rspack/core@1.2.6(@swc/helpers@0.5.11))(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(storybook@8.4.6(bufferutil@4.0.7)(prettier@2.8.8))(typescript@5.7.3)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)) - '@storybook/preset-react-webpack': 8.4.6(@storybook/test@8.4.6(storybook@8.4.6(bufferutil@4.0.7)(prettier@2.8.8)))(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.4.6(bufferutil@4.0.7)(prettier@2.8.8))(typescript@5.7.3)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)) + '@storybook/builder-webpack5': 8.4.6(@rspack/core@1.2.6(@swc/helpers@0.5.11))(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(storybook@8.4.6(bufferutil@4.0.7)(prettier@2.8.8))(typescript@5.7.3)(webpack-cli@5.1.4) + '@storybook/preset-react-webpack': 8.4.6(@storybook/test@8.4.6(storybook@8.4.6(bufferutil@4.0.7)(prettier@2.8.8)))(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.4.6(bufferutil@4.0.7)(prettier@2.8.8))(typescript@5.7.3)(webpack-cli@5.1.4) '@storybook/react': 8.4.6(@storybook/test@8.4.6(storybook@8.4.6(bufferutil@4.0.7)(prettier@2.8.8)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.4.6(bufferutil@4.0.7)(prettier@2.8.8))(typescript@5.7.3) '@types/node': 22.5.5 react: 18.3.1 @@ -27466,22 +27259,22 @@ snapshots: '@webassemblyjs/ast': 1.14.1 '@xtuc/long': 4.2.2 - '@webpack-cli/configtest@2.1.1(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)))': + '@webpack-cli/configtest@2.1.1(webpack-cli@5.1.4)(webpack@5.98.0)': dependencies: - webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)) + webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4) webpack-cli: 5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0) - '@webpack-cli/info@2.0.2(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)))': + '@webpack-cli/info@2.0.2(webpack-cli@5.1.4)(webpack@5.98.0)': dependencies: - webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)) + webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4) webpack-cli: 5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0) - '@webpack-cli/serve@2.0.5(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))(webpack-dev-server@5.2.1(bufferutil@4.0.7)(webpack-cli@5.1.4)(webpack@5.98.0))(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)))': + '@webpack-cli/serve@2.0.5(webpack-cli@5.1.4)(webpack-dev-server@5.2.1)(webpack@5.98.0)': dependencies: - webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)) + webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4) webpack-cli: 5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0) optionalDependencies: - webpack-dev-server: 5.2.1(bufferutil@4.0.7)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) + webpack-dev-server: 5.2.1(bufferutil@4.0.7)(webpack-cli@5.1.4)(webpack@5.98.0) '@xhmikosr/archive-type@7.0.0': dependencies: @@ -28065,19 +27858,19 @@ snapshots: transitivePeerDependencies: - supports-color - babel-loader@9.2.1(@babel/core@7.25.2)(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))): + babel-loader@9.2.1(@babel/core@7.25.2)(webpack@5.98.0): dependencies: '@babel/core': 7.25.2 find-cache-dir: 4.0.0 schema-utils: 4.2.0 - webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)) + webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4) - babel-loader@9.2.1(@babel/core@7.26.9)(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))): + babel-loader@9.2.1(@babel/core@7.26.9)(webpack@5.98.0): dependencies: '@babel/core': 7.26.9 find-cache-dir: 4.0.0 schema-utils: 4.2.0 - webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)) + webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4) babel-plugin-const-enum@1.2.0(@babel/core@7.26.9): dependencies: @@ -29100,7 +28893,7 @@ snapshots: dependencies: toggle-selection: 1.0.6 - copy-webpack-plugin@10.2.4(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))): + copy-webpack-plugin@10.2.4(webpack@5.98.0): dependencies: fast-glob: 3.2.7 glob-parent: 6.0.2 @@ -29108,9 +28901,9 @@ snapshots: normalize-path: 3.0.0 schema-utils: 4.2.0 serialize-javascript: 6.0.2 - webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)) + webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4) - copy-webpack-plugin@12.0.2(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))): + copy-webpack-plugin@12.0.2(webpack@5.98.0): dependencies: fast-glob: 3.3.3 glob-parent: 6.0.2 @@ -29118,7 +28911,7 @@ snapshots: normalize-path: 3.0.0 schema-utils: 4.3.0 serialize-javascript: 6.0.2 - webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)) + webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4) core-js-compat@3.38.1: dependencies: @@ -29257,7 +29050,7 @@ snapshots: postcss: 8.4.38 postcss-selector-parser: 6.1.2 - css-loader@6.11.0(@rspack/core@1.2.6(@swc/helpers@0.5.11))(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))): + css-loader@6.11.0(@rspack/core@1.2.6(@swc/helpers@0.5.11))(webpack@5.98.0): dependencies: icss-utils: 5.1.0(postcss@8.4.38) postcss: 8.4.38 @@ -29269,9 +29062,9 @@ snapshots: semver: 7.7.1 optionalDependencies: '@rspack/core': 1.2.6(@swc/helpers@0.5.11) - webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)) + webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4) - css-loader@7.1.2(@rspack/core@1.2.6(@swc/helpers@0.5.11))(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))): + css-loader@7.1.2(@rspack/core@1.2.6(@swc/helpers@0.5.11))(webpack@5.98.0): dependencies: icss-utils: 5.1.0(postcss@8.4.38) postcss: 8.4.38 @@ -29283,9 +29076,9 @@ snapshots: semver: 7.7.1 optionalDependencies: '@rspack/core': 1.2.6(@swc/helpers@0.5.11) - webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)) + webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4) - css-minimizer-webpack-plugin@5.0.1(esbuild@0.25.0)(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))): + css-minimizer-webpack-plugin@5.0.1(esbuild@0.25.0)(webpack@5.98.0): dependencies: '@jridgewell/trace-mapping': 0.3.25 cssnano: 6.1.2(postcss@8.4.38) @@ -29293,7 +29086,7 @@ snapshots: postcss: 8.4.38 schema-utils: 4.2.0 serialize-javascript: 6.0.2 - webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)) + webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4) optionalDependencies: esbuild: 0.25.0 @@ -30384,20 +30177,20 @@ snapshots: debug: 4.3.7(supports-color@8.1.1) enhanced-resolve: 5.17.1 eslint: 8.57.0 - eslint-module-utils: 2.11.0(@typescript-eslint/parser@8.6.0(eslint@8.57.0)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.6.0(eslint@8.57.0)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.0))(eslint@8.57.0) + eslint-module-utils: 2.11.0(@typescript-eslint/parser@8.6.0(eslint@8.57.0)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0) fast-glob: 3.3.3 get-tsconfig: 4.8.1 is-bun-module: 1.2.1 is-glob: 4.0.3 optionalDependencies: - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.6.0(eslint@8.57.0)(typescript@5.7.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.20.0(eslint@8.57.0)(typescript@5.7.3))(eslint@8.57.0) transitivePeerDependencies: - '@typescript-eslint/parser' - eslint-import-resolver-node - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.11.0(@typescript-eslint/parser@8.6.0(eslint@8.57.0)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.6.0(eslint@8.57.0)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.0))(eslint@8.57.0): + eslint-module-utils@2.11.0(@typescript-eslint/parser@8.6.0(eslint@8.57.0)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0): dependencies: debug: 3.2.7(supports-color@8.1.1) optionalDependencies: @@ -30418,7 +30211,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@8.6.0(eslint@8.57.0)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.6.0(eslint@8.57.0)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.0))(eslint@8.57.0): + eslint-module-utils@2.12.0(@typescript-eslint/parser@8.6.0(eslint@8.57.0)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0): dependencies: debug: 3.2.7(supports-color@8.1.1) optionalDependencies: @@ -30474,7 +30267,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.6.0(eslint@8.57.0)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.6.0(eslint@8.57.0)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.0))(eslint@8.57.0) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.6.0(eslint@8.57.0)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3 @@ -30988,11 +30781,11 @@ snapshots: dependencies: flat-cache: 3.2.0 - file-loader@6.2.0(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))): + file-loader@6.2.0(webpack@5.98.0): dependencies: loader-utils: 2.0.3 schema-utils: 3.3.0 - webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)) + webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4) file-type@16.5.4: dependencies: @@ -31128,7 +30921,7 @@ snapshots: forever-agent@0.6.1: {} - fork-ts-checker-webpack-plugin@7.2.13(typescript@5.7.3)(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))): + fork-ts-checker-webpack-plugin@7.2.13(typescript@5.7.3)(webpack@5.98.0): dependencies: '@babel/code-frame': 7.24.7 chalk: 4.1.2 @@ -31143,9 +30936,9 @@ snapshots: semver: 7.6.3 tapable: 2.2.1 typescript: 5.7.3 - webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)) + webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4) - fork-ts-checker-webpack-plugin@8.0.0(typescript@5.7.3)(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))): + fork-ts-checker-webpack-plugin@8.0.0(typescript@5.7.3)(webpack@5.98.0): dependencies: '@babel/code-frame': 7.26.2 chalk: 4.1.2 @@ -31160,9 +30953,9 @@ snapshots: semver: 7.7.1 tapable: 2.2.1 typescript: 5.7.3 - webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)) + webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4) - fork-ts-checker-webpack-plugin@9.0.2(typescript@5.3.3)(webpack@5.94.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))): + fork-ts-checker-webpack-plugin@9.0.2(typescript@5.3.3)(webpack@5.94.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4)): dependencies: '@babel/code-frame': 7.26.2 chalk: 4.1.2 @@ -31177,7 +30970,7 @@ snapshots: semver: 7.6.3 tapable: 2.2.1 typescript: 5.3.3 - webpack: 5.94.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)) + webpack: 5.94.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4) form-data-encoder@1.7.2: {} @@ -31748,14 +31541,14 @@ snapshots: html-tags@3.3.1: {} - html-webpack-plugin@5.5.0(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))): + html-webpack-plugin@5.5.0(webpack@5.98.0): dependencies: '@types/html-minifier-terser': 6.1.0 html-minifier-terser: 6.1.0 lodash: 4.17.21 pretty-error: 4.0.0 tapable: 2.2.1 - webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)) + webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4) htmlparser2@6.1.0: dependencies: @@ -33096,18 +32889,18 @@ snapshots: dependencies: readable-stream: 2.3.8 - less-loader@11.1.0(less@4.1.3)(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))): + less-loader@11.1.0(less@4.1.3)(webpack@5.98.0): dependencies: klona: 2.0.6 less: 4.1.3 - webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)) + webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4) - less-loader@12.2.0(@rspack/core@1.2.6(@swc/helpers@0.5.11))(less@4.2.2)(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))): + less-loader@12.2.0(@rspack/core@1.2.6(@swc/helpers@0.5.11))(less@4.2.2)(webpack@5.98.0): dependencies: less: 4.2.2 optionalDependencies: '@rspack/core': 1.2.6(@swc/helpers@0.5.11) - webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)) + webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4) less@4.1.3: dependencies: @@ -33159,11 +32952,11 @@ snapshots: transitivePeerDependencies: - supports-color - license-webpack-plugin@4.0.2(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))): + license-webpack-plugin@4.0.2(webpack@5.98.0): dependencies: webpack-sources: 3.2.3 optionalDependencies: - webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)) + webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4) lie@3.3.0: dependencies: @@ -34140,16 +33933,16 @@ snapshots: min-indent@1.0.1: {} - mini-css-extract-plugin@2.4.7(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))): + mini-css-extract-plugin@2.4.7(webpack@5.98.0): dependencies: schema-utils: 4.2.0 - webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)) + webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4) - mini-css-extract-plugin@2.9.2(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))): + mini-css-extract-plugin@2.9.2(webpack@5.98.0): dependencies: schema-utils: 4.3.0 tapable: 2.2.1 - webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)) + webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4) mini-svg-data-uri@1.4.4: {} @@ -35662,15 +35455,15 @@ snapshots: postcss: 8.4.38 ts-node: 10.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@20.16.10)(typescript@5.7.3) - postcss-loader@6.2.1(postcss@8.4.38)(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))): + postcss-loader@6.2.1(postcss@8.4.38)(webpack@5.98.0): dependencies: cosmiconfig: 7.1.0 klona: 2.0.6 postcss: 8.4.38 semver: 7.7.1 - webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)) + webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4) - postcss-loader@8.1.1(@rspack/core@1.2.6(@swc/helpers@0.5.11))(postcss@8.4.38)(typescript@5.7.3)(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))): + postcss-loader@8.1.1(@rspack/core@1.2.6(@swc/helpers@0.5.11))(postcss@8.4.38)(typescript@5.7.3)(webpack@5.98.0): dependencies: cosmiconfig: 9.0.0(typescript@5.7.3) jiti: 1.21.6 @@ -35678,11 +35471,11 @@ snapshots: semver: 7.7.1 optionalDependencies: '@rspack/core': 1.2.6(@swc/helpers@0.5.11) - webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)) + webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4) transitivePeerDependencies: - typescript - postcss-loader@8.1.1(@rspack/core@1.2.6(@swc/helpers@0.5.11))(postcss@8.5.2)(typescript@5.7.3)(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))): + postcss-loader@8.1.1(@rspack/core@1.2.6(@swc/helpers@0.5.11))(postcss@8.5.2)(typescript@5.7.3)(webpack@5.98.0): dependencies: cosmiconfig: 9.0.0(typescript@5.7.3) jiti: 1.21.6 @@ -35690,7 +35483,7 @@ snapshots: semver: 7.7.1 optionalDependencies: '@rspack/core': 1.2.6(@swc/helpers@0.5.11) - webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)) + webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4) transitivePeerDependencies: - typescript @@ -37163,23 +36956,23 @@ snapshots: sass-embedded-win32-ia32: 1.85.1 sass-embedded-win32-x64: 1.85.1 - sass-loader@16.0.5(@rspack/core@1.2.6(@swc/helpers@0.5.11))(sass-embedded@1.85.1)(sass@1.55.0)(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))): + sass-loader@16.0.5(@rspack/core@1.2.6(@swc/helpers@0.5.11))(sass-embedded@1.85.1)(sass@1.55.0)(webpack@5.98.0): dependencies: neo-async: 2.6.2 optionalDependencies: '@rspack/core': 1.2.6(@swc/helpers@0.5.11) sass: 1.55.0 sass-embedded: 1.85.1 - webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)) + webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4) - sass-loader@16.0.5(@rspack/core@1.2.6(@swc/helpers@0.5.11))(sass-embedded@1.85.1)(sass@1.85.0)(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))): + sass-loader@16.0.5(@rspack/core@1.2.6(@swc/helpers@0.5.11))(sass-embedded@1.85.1)(sass@1.85.0)(webpack@5.98.0): dependencies: neo-async: 2.6.2 optionalDependencies: '@rspack/core': 1.2.6(@swc/helpers@0.5.11) sass: 1.85.0 sass-embedded: 1.85.1 - webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)) + webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4) sass@1.55.0: dependencies: @@ -37530,11 +37323,11 @@ snapshots: source-map-js@1.2.1: {} - source-map-loader@5.0.0(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))): + source-map-loader@5.0.0(webpack@5.98.0): dependencies: iconv-lite: 0.6.3 source-map-js: 1.2.1 - webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)) + webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4) source-map-support@0.5.13: dependencies: @@ -37867,9 +37660,9 @@ snapshots: style-inject@0.3.0: {} - style-loader@3.3.4(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))): + style-loader@3.3.4(webpack@5.98.0): dependencies: - webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)) + webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4) style-to-object@0.4.4: dependencies: @@ -37901,12 +37694,12 @@ snapshots: postcss: 8.5.3 postcss-selector-parser: 6.1.2 - stylus-loader@7.1.3(stylus@0.64.0)(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))): + stylus-loader@7.1.3(stylus@0.64.0)(webpack@5.98.0): dependencies: fast-glob: 3.3.3 normalize-path: 3.0.0 stylus: 0.64.0 - webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)) + webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4) stylus@0.64.0: dependencies: @@ -38108,38 +37901,38 @@ snapshots: temp-dir: 2.0.0 uuid: 3.4.0 - terser-webpack-plugin@5.3.10(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack@5.94.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))): + terser-webpack-plugin@5.3.10(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack@5.94.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4)): dependencies: '@jridgewell/trace-mapping': 0.3.25 jest-worker: 27.5.1 schema-utils: 3.3.0 serialize-javascript: 6.0.2 terser: 5.33.0 - webpack: 5.94.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)) + webpack: 5.94.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4) optionalDependencies: '@swc/core': 1.5.7(@swc/helpers@0.5.11) esbuild: 0.25.0 - terser-webpack-plugin@5.3.10(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))): + terser-webpack-plugin@5.3.10(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack@5.98.0): dependencies: '@jridgewell/trace-mapping': 0.3.25 jest-worker: 27.5.1 schema-utils: 3.3.0 serialize-javascript: 6.0.2 terser: 5.33.0 - webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)) + webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4) optionalDependencies: '@swc/core': 1.5.7(@swc/helpers@0.5.11) esbuild: 0.25.0 - terser-webpack-plugin@5.3.11(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))): + terser-webpack-plugin@5.3.11(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack@5.98.0): dependencies: '@jridgewell/trace-mapping': 0.3.25 jest-worker: 27.5.1 schema-utils: 4.3.0 serialize-javascript: 6.0.2 terser: 5.39.0 - webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)) + webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4) optionalDependencies: '@swc/core': 1.5.7(@swc/helpers@0.5.11) esbuild: 0.25.0 @@ -38382,7 +38175,7 @@ snapshots: babel-jest: 29.7.0(@babel/core@7.25.2) esbuild: 0.25.0 - ts-loader@9.5.1(typescript@5.7.3)(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))): + ts-loader@9.5.1(typescript@5.7.3)(webpack@5.98.0): dependencies: chalk: 4.1.2 enhanced-resolve: 5.17.1 @@ -38390,7 +38183,7 @@ snapshots: semver: 7.6.3 source-map: 0.7.4 typescript: 5.7.3 - webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)) + webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4) ts-node@10.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@types/node@20.16.10)(typescript@5.7.3): dependencies: @@ -39362,9 +39155,9 @@ snapshots: webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0): dependencies: '@discoveryjs/json-ext': 0.5.7 - '@webpack-cli/configtest': 2.1.1(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) - '@webpack-cli/info': 2.0.2(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) - '@webpack-cli/serve': 2.0.5(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))(webpack-dev-server@5.2.1(bufferutil@4.0.7)(webpack-cli@5.1.4)(webpack@5.98.0))(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) + '@webpack-cli/configtest': 2.1.1(webpack-cli@5.1.4)(webpack@5.98.0) + '@webpack-cli/info': 2.0.2(webpack-cli@5.1.4)(webpack@5.98.0) + '@webpack-cli/serve': 2.0.5(webpack-cli@5.1.4)(webpack-dev-server@5.2.1)(webpack@5.98.0) colorette: 2.0.20 commander: 10.0.1 cross-spawn: 7.0.3 @@ -39373,12 +39166,12 @@ snapshots: import-local: 3.2.0 interpret: 3.1.1 rechoir: 0.8.0 - webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)) + webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4) webpack-merge: 5.10.0 optionalDependencies: - webpack-dev-server: 5.2.1(bufferutil@4.0.7)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) + webpack-dev-server: 5.2.1(bufferutil@4.0.7)(webpack-cli@5.1.4)(webpack@5.98.0) - webpack-dev-middleware@6.1.3(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))): + webpack-dev-middleware@6.1.3(webpack@5.98.0): dependencies: colorette: 2.0.20 memfs: 3.6.0 @@ -39386,9 +39179,9 @@ snapshots: range-parser: 1.2.1 schema-utils: 4.3.0 optionalDependencies: - webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)) + webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4) - webpack-dev-middleware@7.4.2(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))): + webpack-dev-middleware@7.4.2(webpack@5.98.0): dependencies: colorette: 2.0.20 memfs: 4.12.0 @@ -39397,9 +39190,9 @@ snapshots: range-parser: 1.2.1 schema-utils: 4.3.0 optionalDependencies: - webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)) + webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4) - webpack-dev-server@5.0.4(bufferutil@4.0.7)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))): + webpack-dev-server@5.0.4(bufferutil@4.0.7)(webpack-cli@5.1.4)(webpack@5.98.0): dependencies: '@types/bonjour': 3.5.13 '@types/connect-history-api-fallback': 1.5.4 @@ -39429,10 +39222,10 @@ snapshots: serve-index: 1.9.1 sockjs: 0.3.24 spdy: 4.0.2 - webpack-dev-middleware: 7.4.2(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) + webpack-dev-middleware: 7.4.2(webpack@5.98.0) ws: 8.18.0(bufferutil@4.0.7) optionalDependencies: - webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)) + webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4) webpack-cli: 5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0) transitivePeerDependencies: - bufferutil @@ -39440,7 +39233,7 @@ snapshots: - supports-color - utf-8-validate - webpack-dev-server@5.2.0(bufferutil@4.0.7)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))): + webpack-dev-server@5.2.0(bufferutil@4.0.7)(webpack-cli@5.1.4)(webpack@5.98.0): dependencies: '@types/bonjour': 3.5.13 '@types/connect-history-api-fallback': 1.5.4 @@ -39467,10 +39260,10 @@ snapshots: serve-index: 1.9.1 sockjs: 0.3.24 spdy: 4.0.2 - webpack-dev-middleware: 7.4.2(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) + webpack-dev-middleware: 7.4.2(webpack@5.98.0) ws: 8.18.0(bufferutil@4.0.7) optionalDependencies: - webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)) + webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4) webpack-cli: 5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0) transitivePeerDependencies: - bufferutil @@ -39478,7 +39271,7 @@ snapshots: - supports-color - utf-8-validate - webpack-dev-server@5.2.1(bufferutil@4.0.7)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))): + webpack-dev-server@5.2.1(bufferutil@4.0.7)(webpack-cli@5.1.4)(webpack@5.98.0): dependencies: '@types/bonjour': 3.5.13 '@types/connect-history-api-fallback': 1.5.4 @@ -39506,10 +39299,10 @@ snapshots: serve-index: 1.9.1 sockjs: 0.3.24 spdy: 4.0.2 - webpack-dev-middleware: 7.4.2(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) + webpack-dev-middleware: 7.4.2(webpack@5.98.0) ws: 8.18.0(bufferutil@4.0.7) optionalDependencies: - webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)) + webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4) webpack-cli: 5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0) transitivePeerDependencies: - bufferutil @@ -39539,16 +39332,16 @@ snapshots: webpack-sources@3.2.3: {} - webpack-subresource-integrity@5.1.0(html-webpack-plugin@5.5.0(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))))(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))): + webpack-subresource-integrity@5.1.0(html-webpack-plugin@5.5.0(webpack@5.98.0))(webpack@5.98.0): dependencies: typed-assert: 1.0.9 - webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)) + webpack: 5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4) optionalDependencies: - html-webpack-plugin: 5.5.0(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) + html-webpack-plugin: 5.5.0(webpack@5.98.0) webpack-virtual-modules@0.6.2: {} - webpack@5.94.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)): + webpack@5.94.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4): dependencies: '@types/estree': 1.0.6 '@webassemblyjs/ast': 1.12.1 @@ -39570,7 +39363,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 3.3.0 tapable: 2.2.1 - terser-webpack-plugin: 5.3.10(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack@5.94.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) + terser-webpack-plugin: 5.3.10(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack@5.94.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4)) watchpack: 2.4.2 webpack-sources: 3.2.3 optionalDependencies: @@ -39580,7 +39373,7 @@ snapshots: - esbuild - uglify-js - webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0)): + webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4): dependencies: '@types/eslint-scope': 3.7.7 '@types/estree': 1.0.6 @@ -39602,7 +39395,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 4.3.0 tapable: 2.2.1 - terser-webpack-plugin: 5.3.11(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack@5.98.0(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack-cli@5.1.4(webpack-dev-server@5.2.1)(webpack@5.98.0))) + terser-webpack-plugin: 5.3.11(@swc/core@1.5.7(@swc/helpers@0.5.11))(esbuild@0.25.0)(webpack@5.98.0) watchpack: 2.4.2 webpack-sources: 3.2.3 optionalDependencies: