feat(release): revamped nx release version implementation (#30418)
This commit is contained in:
parent
dbd86480c8
commit
7b85d912ba
@ -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-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-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` | 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-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` | 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. |
|
| `--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-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-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` | 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-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` | 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. |
|
| `--git-tag-args` | string | Additional arguments to pass to the `git tag` command invoked behind the scenes. |
|
||||||
|
|||||||
@ -99,8 +99,8 @@ Project specific configuration for `nx release`
|
|||||||
#### Type declaration
|
#### Type declaration
|
||||||
|
|
||||||
| Name | Type |
|
| Name | Type |
|
||||||
| :--------- | :------------------------------------------------------------------------------- |
|
| :--------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `version?` | `Pick`\<`NxReleaseVersionConfiguration`, `"generator"` \| `"generatorOptions"`\> |
|
| `version?` | `Pick`\<`NxReleaseVersionConfiguration`, `"generator"` \| `"generatorOptions"`\> \| `Pick`\<`NxReleaseVersionV2Configuration`, `"versionActions"` \| `"versionActionsOptions"` \| `"manifestRootsToUpdate"` \| `"currentVersionResolver"` \| `"currentVersionResolverMetadata"` \| `"fallbackCurrentVersionResolver"` \| `"versionPrefix"` \| `"preserveLocalDependencyProtocols"`\> |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@ -11,21 +11,40 @@
|
|||||||
"properties": {
|
"properties": {
|
||||||
"projects": {
|
"projects": {
|
||||||
"type": "array",
|
"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.",
|
"description": "The ProjectGraphProjectNodes being versioned in the current execution.",
|
||||||
"items": { "type": "object" }
|
"items": { "type": "object" }
|
||||||
},
|
},
|
||||||
"projectGraph": {
|
"projectGraph": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
"hidden": true,
|
||||||
|
"$comment": "This is not configured/passed by the user. It is provided by the version command.",
|
||||||
"description": "ProjectGraph instance"
|
"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": {
|
"specifier": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Exact version or semver keyword to apply to the selected release group. Overrides specifierSource."
|
"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": {
|
"specifierSource": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"default": "prompt",
|
"default": "prompt",
|
||||||
|
|||||||
@ -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-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-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` | 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-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` | 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. |
|
| `--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-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-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` | 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-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` | 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. |
|
| `--git-tag-args` | string | Additional arguments to pass to the `git tag` command invoked behind the scenes. |
|
||||||
|
|||||||
@ -104,10 +104,8 @@ describe('nx release circular dependencies', () => {
|
|||||||
nxJson.release = {
|
nxJson.release = {
|
||||||
projectsRelationship: 'fixed',
|
projectsRelationship: 'fixed',
|
||||||
version: {
|
version: {
|
||||||
generatorOptions: {
|
|
||||||
updateDependents: 'never',
|
updateDependents: 'never',
|
||||||
},
|
},
|
||||||
},
|
|
||||||
changelog: {
|
changelog: {
|
||||||
// Enable project level changelogs for all examples
|
// Enable project level changelogs for all examples
|
||||||
projectChangelogs: true,
|
projectChangelogs: true,
|
||||||
@ -124,19 +122,17 @@ describe('nx release circular dependencies', () => {
|
|||||||
|
|
||||||
NX Running release version for project: {project-name}
|
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 manifest: {project-name}/package.json
|
||||||
{project-name} 📄 Resolved the current version as 1.0.0 from {project-name}/package.json
|
{project-name} ❓ Applied semver relative bump "major", from the given specifier, to get new version 2.0.0
|
||||||
{project-name} 📄 Using the provided version specifier "major".
|
{project-name} ✍️ New version 2.0.0 written to manifest: {project-name}/package.json
|
||||||
{project-name} ✍️ New version 2.0.0 written to {project-name}/package.json
|
{project-name} ✍️ Updated 1 dependency in manifest: {project-name}/package.json
|
||||||
{project-name} ✍️ Applying new version 2.0.0 to 1 package which depends on {project-name}
|
|
||||||
|
|
||||||
NX Running release version for project: {project-name}
|
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 manifest: {project-name}/package.json
|
||||||
{project-name} 📄 Resolved the current version as 1.0.0 from {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} 📄 Using the provided version specifier "major".
|
{project-name} ✍️ New version 2.0.0 written to manifest: {project-name}/package.json
|
||||||
{project-name} ✍️ New version 2.0.0 written to {project-name}/package.json
|
{project-name} ✍️ Updated 1 dependency in manifest: {project-name}/package.json
|
||||||
{project-name} ✍️ Applying new version 2.0.0 to 1 package which depends on {project-name}
|
|
||||||
|
|
||||||
|
|
||||||
"name": "@proj/{project-name}",
|
"name": "@proj/{project-name}",
|
||||||
@ -289,10 +285,8 @@ describe('nx release circular dependencies', () => {
|
|||||||
nxJson.release = {
|
nxJson.release = {
|
||||||
projectsRelationship: 'fixed',
|
projectsRelationship: 'fixed',
|
||||||
version: {
|
version: {
|
||||||
generatorOptions: {
|
|
||||||
updateDependents: 'auto',
|
updateDependents: 'auto',
|
||||||
},
|
},
|
||||||
},
|
|
||||||
changelog: {
|
changelog: {
|
||||||
// Enable project level changelogs for all examples
|
// Enable project level changelogs for all examples
|
||||||
projectChangelogs: true,
|
projectChangelogs: true,
|
||||||
@ -305,23 +299,22 @@ describe('nx release circular dependencies', () => {
|
|||||||
`release major --verbose --first-release -y -d`
|
`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(`
|
expect(releaseOutput).toMatchInlineSnapshot(`
|
||||||
|
|
||||||
NX Running release version for project: {project-name}
|
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 manifest: {project-name}/package.json
|
||||||
{project-name} 📄 Resolved the current version as 1.0.0 from {project-name}/package.json
|
{project-name} ❓ Applied semver relative bump "major", from the given specifier, to get new version 2.0.0
|
||||||
{project-name} 📄 Using the provided version specifier "major".
|
{project-name} ✍️ New version 2.0.0 written to manifest: {project-name}/package.json
|
||||||
{project-name} ✍️ New version 2.0.0 written to {project-name}/package.json
|
{project-name} ✍️ Updated 1 dependency in manifest: {project-name}/package.json
|
||||||
{project-name} ✍️ Applying new version 2.0.0 to 1 package which depends on {project-name}
|
|
||||||
|
|
||||||
NX Running release version for project: {project-name}
|
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 manifest: {project-name}/package.json
|
||||||
{project-name} 📄 Resolved the current version as 1.0.0 from {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} 📄 Using the provided version specifier "major".
|
{project-name} ✍️ New version 2.0.0 written to manifest: {project-name}/package.json
|
||||||
{project-name} ✍️ New version 2.0.0 written to {project-name}/package.json
|
{project-name} ✍️ Updated 1 dependency in manifest: {project-name}/package.json
|
||||||
{project-name} ✍️ Applying new version 2.0.0 to 1 package which depends on {project-name}
|
|
||||||
|
|
||||||
|
|
||||||
"name": "@proj/{project-name}",
|
"name": "@proj/{project-name}",
|
||||||
@ -474,10 +467,8 @@ describe('nx release circular dependencies', () => {
|
|||||||
nxJson.release = {
|
nxJson.release = {
|
||||||
projectsRelationship: 'independent',
|
projectsRelationship: 'independent',
|
||||||
version: {
|
version: {
|
||||||
generatorOptions: {
|
|
||||||
updateDependents: 'never',
|
updateDependents: 'never',
|
||||||
},
|
},
|
||||||
},
|
|
||||||
changelog: {
|
changelog: {
|
||||||
// Enable project level changelogs for all examples
|
// Enable project level changelogs for all examples
|
||||||
projectChangelogs: true,
|
projectChangelogs: true,
|
||||||
@ -494,19 +485,19 @@ describe('nx release circular dependencies', () => {
|
|||||||
|
|
||||||
NX Running release version for project: {project-name}
|
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 manifest: {project-name}/package.json
|
||||||
{project-name} 📄 Resolved the current version as 1.0.0 from {project-name}/package.json
|
{project-name} ❓ Applied semver relative bump "major", from the given specifier, to get new version 2.0.0
|
||||||
{project-name} 📄 Using the provided version specifier "major".
|
{project-name} ✍️ New version 2.0.0 written to manifest: {project-name}/package.json
|
||||||
{project-name} ✍️ New version 2.0.0 written to {project-name}/package.json
|
{project-name} ⏩ Skipping dependent updates as "updateDependents" is not "auto"
|
||||||
{project-name} ✍️ Applying new version 2.0.0 to 1 package which depends on {project-name}
|
{project-name} ✍️ Updated 1 dependency in manifest: {project-name}/package.json
|
||||||
|
|
||||||
NX Running release version for project: {project-name}
|
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 manifest: {project-name}/package.json
|
||||||
{project-name} 📄 Resolved the current version as 1.0.0 from {project-name}/package.json
|
{project-name} ❓ Applied semver relative bump "major", from the given specifier, to get new version 2.0.0
|
||||||
{project-name} 📄 Using the provided version specifier "major".
|
{project-name} ✍️ New version 2.0.0 written to manifest: {project-name}/package.json
|
||||||
{project-name} ✍️ New version 2.0.0 written to {project-name}/package.json
|
{project-name} ⏩ Skipping dependent updates as "updateDependents" is not "auto"
|
||||||
{project-name} ✍️ Applying new version 2.0.0 to 1 package which depends on {project-name}
|
{project-name} ✍️ Updated 1 dependency in manifest: {project-name}/package.json
|
||||||
|
|
||||||
|
|
||||||
"name": "@proj/{project-name}",
|
"name": "@proj/{project-name}",
|
||||||
@ -656,10 +647,8 @@ describe('nx release circular dependencies', () => {
|
|||||||
nxJson.release = {
|
nxJson.release = {
|
||||||
projectsRelationship: 'independent',
|
projectsRelationship: 'independent',
|
||||||
version: {
|
version: {
|
||||||
generatorOptions: {
|
|
||||||
updateDependents: 'never',
|
updateDependents: 'never',
|
||||||
},
|
},
|
||||||
},
|
|
||||||
changelog: {
|
changelog: {
|
||||||
// Enable project level changelogs for all examples
|
// Enable project level changelogs for all examples
|
||||||
projectChangelogs: true,
|
projectChangelogs: true,
|
||||||
@ -682,13 +671,10 @@ describe('nx release circular dependencies', () => {
|
|||||||
|
|
||||||
NX Running release version for project: {project-name}
|
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 manifest: {project-name}/package.json
|
||||||
{project-name} 📄 Resolved the current version as 1.0.0 from {project-name}/package.json
|
{project-name} ❓ Applied semver relative bump "major", from the given specifier, to get new version 2.0.0
|
||||||
{project-name} 📄 Using the provided version specifier "major".
|
{project-name} ✍️ New version 2.0.0 written to manifest: {project-name}/package.json
|
||||||
{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} ⏩ Skipping dependent updates as "updateDependents" is not "auto"
|
||||||
- {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
|
|
||||||
|
|
||||||
|
|
||||||
"name": "@proj/{project-name}",
|
"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.
|
+ 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
|
NX Staging changed files with git
|
||||||
|
|
||||||
@ -780,10 +767,8 @@ describe('nx release circular dependencies', () => {
|
|||||||
nxJson.release = {
|
nxJson.release = {
|
||||||
projectsRelationship: 'independent',
|
projectsRelationship: 'independent',
|
||||||
version: {
|
version: {
|
||||||
generatorOptions: {
|
|
||||||
updateDependents: 'auto',
|
updateDependents: 'auto',
|
||||||
},
|
},
|
||||||
},
|
|
||||||
changelog: {
|
changelog: {
|
||||||
// Enable project level changelogs for all examples
|
// Enable project level changelogs for all examples
|
||||||
projectChangelogs: true,
|
projectChangelogs: true,
|
||||||
@ -800,19 +785,22 @@ describe('nx release circular dependencies', () => {
|
|||||||
|
|
||||||
NX Running release version for project: {project-name}
|
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 manifest: {project-name}/package.json
|
||||||
{project-name} 📄 Resolved the current version as 1.0.0 from {project-name}/package.json
|
{project-name} ❓ Applied semver relative bump "major", from the given specifier, to get new version 2.0.0
|
||||||
{project-name} 📄 Using the provided version specifier "major".
|
{project-name} ✍️ New version 2.0.0 written to manifest: {project-name}/package.json
|
||||||
{project-name} ✍️ New version 2.0.0 written to {project-name}/package.json
|
{project-name} ✍️ Updated 1 dependency in manifest: {project-name}/package.json
|
||||||
{project-name} ✍️ Applying new version 2.0.0 to 1 package which depends on {project-name}
|
{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}
|
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 manifest: {project-name}/package.json
|
||||||
{project-name} 📄 Resolved the current version as 1.0.0 from {project-name}/package.json
|
{project-name} ✍️ Updated 1 dependency in manifest: {project-name}/package.json
|
||||||
{project-name} 📄 Using the provided version specifier "major".
|
{project-name} ❓ Applied semver relative bump "patch", because a dependency was bumped, to get new version 1.0.1
|
||||||
{project-name} ✍️ New version 2.0.0 written to {project-name}/package.json
|
{project-name} ✍️ New version 1.0.1 written to manifest: {project-name}/package.json
|
||||||
{project-name} ✍️ Applying new version 2.0.0 to 1 package which depends on {project-name}
|
{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}",
|
"name": "@proj/{project-name}",
|
||||||
@ -962,10 +950,8 @@ describe('nx release circular dependencies', () => {
|
|||||||
nxJson.release = {
|
nxJson.release = {
|
||||||
projectsRelationship: 'independent',
|
projectsRelationship: 'independent',
|
||||||
version: {
|
version: {
|
||||||
generatorOptions: {
|
|
||||||
updateDependents: 'auto',
|
updateDependents: 'auto',
|
||||||
},
|
},
|
||||||
},
|
|
||||||
changelog: {
|
changelog: {
|
||||||
// Enable project level changelogs for all examples
|
// Enable project level changelogs for all examples
|
||||||
projectChangelogs: true,
|
projectChangelogs: true,
|
||||||
@ -988,11 +974,18 @@ describe('nx release circular dependencies', () => {
|
|||||||
|
|
||||||
NX Running release version for project: {project-name}
|
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 manifest: {project-name}/package.json
|
||||||
{project-name} 📄 Resolved the current version as 1.0.0 from {project-name}/package.json
|
{project-name} ❓ Applied semver relative bump "major", from the given specifier, to get new version 2.0.0
|
||||||
{project-name} 📄 Using the provided version specifier "major".
|
{project-name} ✍️ New version 2.0.0 written to manifest: {project-name}/package.json
|
||||||
{project-name} ✍️ New version 2.0.0 written to {project-name}/package.json
|
{project-name} ✍️ Updated 1 dependency in manifest: {project-name}/package.json
|
||||||
{project-name} ✍️ Applying new version 2.0.0 to 1 package which depends on {project-name}
|
{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}",
|
"name": "@proj/{project-name}",
|
||||||
|
|||||||
@ -165,22 +165,22 @@ describe('nx release conventional commits config', () => {
|
|||||||
const versionResultNoChanges = runCLI(`release version -d`);
|
const versionResultNoChanges = runCLI(`release version -d`);
|
||||||
|
|
||||||
expect(versionResultNoChanges).toContain(
|
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(
|
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(
|
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(
|
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(
|
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(
|
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
|
// 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`);
|
const versionResultInvalidConventionalCommit = runCLI(`release version -d`);
|
||||||
|
|
||||||
expect(versionResultInvalidConventionalCommit).toContain(
|
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(
|
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(
|
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(
|
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(
|
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(
|
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
|
// update my-pkg-3 with a fix commit
|
||||||
@ -223,22 +223,22 @@ describe('nx release conventional commits config', () => {
|
|||||||
const versionResultDocsChanges = runCLI(`release version -d`);
|
const versionResultDocsChanges = runCLI(`release version -d`);
|
||||||
|
|
||||||
expect(versionResultDocsChanges).toContain(
|
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(
|
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(
|
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(
|
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(
|
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(
|
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
|
// update my-pkg-2 with a fix commit
|
||||||
@ -253,22 +253,22 @@ describe('nx release conventional commits config', () => {
|
|||||||
|
|
||||||
const versionResultCustomTypeChanges = runCLI(`release version -d`);
|
const versionResultCustomTypeChanges = runCLI(`release version -d`);
|
||||||
expect(versionResultCustomTypeChanges).toContain(
|
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(
|
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(
|
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(
|
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(
|
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(
|
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) => ({
|
updateJson(`${pkg1}/package.json`, (json) => ({
|
||||||
@ -282,22 +282,22 @@ describe('nx release conventional commits config', () => {
|
|||||||
|
|
||||||
const versionResultCustomTypeBreakingChanges = runCLI(`release version -d`);
|
const versionResultCustomTypeBreakingChanges = runCLI(`release version -d`);
|
||||||
expect(versionResultCustomTypeBreakingChanges).toContain(
|
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(
|
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(
|
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(
|
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(
|
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(
|
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) => ({
|
updateJson(`${pkg4}/package.json`, (json) => ({
|
||||||
@ -309,22 +309,22 @@ describe('nx release conventional commits config', () => {
|
|||||||
|
|
||||||
const versionResultChoreChanges = runCLI(`release version -d`);
|
const versionResultChoreChanges = runCLI(`release version -d`);
|
||||||
expect(versionResultChoreChanges).toContain(
|
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(
|
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(
|
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(
|
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(
|
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(
|
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) => ({
|
updateJson(`${pkg5}/package.json`, (json) => ({
|
||||||
@ -338,22 +338,22 @@ describe('nx release conventional commits config', () => {
|
|||||||
|
|
||||||
const versionResultPerfChanges = runCLI(`release version -d`);
|
const versionResultPerfChanges = runCLI(`release version -d`);
|
||||||
expect(versionResultPerfChanges).toContain(
|
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(
|
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(
|
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(
|
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(
|
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(
|
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) => ({
|
updateJson(`${pkg6}/package.json`, (json) => ({
|
||||||
@ -365,22 +365,22 @@ describe('nx release conventional commits config', () => {
|
|||||||
|
|
||||||
const versionResultRefactorChanges = runCLI(`release version -d`);
|
const versionResultRefactorChanges = runCLI(`release version -d`);
|
||||||
expect(versionResultRefactorChanges).toContain(
|
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(
|
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(
|
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(
|
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(
|
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(
|
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
|
// 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`);
|
const versionResultInvalidConventionalCommit = runCLI(`release version -d`);
|
||||||
|
|
||||||
expect(versionResultInvalidConventionalCommit).toContain(
|
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(
|
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(
|
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(
|
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(
|
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(
|
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
|
// update my-pkg-1 with a feature commit
|
||||||
@ -522,22 +522,22 @@ describe('nx release conventional commits config', () => {
|
|||||||
const versionResultFixCommit = runCLI(`release version -d`);
|
const versionResultFixCommit = runCLI(`release version -d`);
|
||||||
|
|
||||||
expect(versionResultFixCommit).toContain(
|
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(
|
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(
|
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(
|
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(
|
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(
|
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
|
// Normally, users would use `nx release` or the programmatic api to ensure that
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import {
|
|||||||
updateJson,
|
updateJson,
|
||||||
} from '@nx/e2e/utils';
|
} from '@nx/e2e/utils';
|
||||||
import { execSync } from 'child_process';
|
import { execSync } from 'child_process';
|
||||||
|
import { NxReleaseVersionV2Configuration } from 'nx/src/config/nx-json';
|
||||||
import type { PackageJson } from 'nx/src/utils/package-json';
|
import type { PackageJson } from 'nx/src/utils/package-json';
|
||||||
|
|
||||||
describe('nx release - custom npm registries', () => {
|
describe('nx release - custom npm registries', () => {
|
||||||
@ -306,7 +307,7 @@ describe('nx release - custom npm registries', () => {
|
|||||||
expect(
|
expect(
|
||||||
versionResult.match(
|
versionResult.match(
|
||||||
new RegExp(
|
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'
|
'g'
|
||||||
)
|
)
|
||||||
).length
|
).length
|
||||||
@ -315,7 +316,7 @@ describe('nx release - custom npm registries', () => {
|
|||||||
expect(
|
expect(
|
||||||
versionResult.match(
|
versionResult.match(
|
||||||
new RegExp(
|
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'
|
'g'
|
||||||
)
|
)
|
||||||
).length
|
).length
|
||||||
@ -324,7 +325,7 @@ describe('nx release - custom npm registries', () => {
|
|||||||
expect(
|
expect(
|
||||||
versionResult.match(
|
versionResult.match(
|
||||||
new RegExp(
|
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'
|
'g'
|
||||||
)
|
)
|
||||||
).length
|
).length
|
||||||
@ -333,7 +334,7 @@ describe('nx release - custom npm registries', () => {
|
|||||||
expect(
|
expect(
|
||||||
versionResult.match(
|
versionResult.match(
|
||||||
new RegExp(
|
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'
|
'g'
|
||||||
)
|
)
|
||||||
).length
|
).length
|
||||||
@ -376,14 +377,14 @@ describe('nx release - custom npm registries', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
updateJson<ProjectConfiguration>(`${projectName}/project.json`, (json) => {
|
updateJson<ProjectConfiguration>(`${projectName}/project.json`, (json) => {
|
||||||
|
const releaseConfig = (json.release ?? {}) as {
|
||||||
|
version: NxReleaseVersionV2Configuration;
|
||||||
|
};
|
||||||
|
|
||||||
if (options.projectConfig) {
|
if (options.projectConfig) {
|
||||||
json.release = {
|
releaseConfig.version = {
|
||||||
version: {
|
|
||||||
generatorOptions: {
|
|
||||||
currentVersionResolver: 'registry',
|
currentVersionResolver: 'registry',
|
||||||
currentVersionResolverMetadata: {},
|
currentVersionResolverMetadata: {},
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
json.targets = {
|
json.targets = {
|
||||||
...json.targets,
|
...json.targets,
|
||||||
@ -396,16 +397,20 @@ describe('nx release - custom npm registries', () => {
|
|||||||
json.targets['nx-release-publish'].options.registry =
|
json.targets['nx-release-publish'].options.registry =
|
||||||
options.projectConfig.registry;
|
options.projectConfig.registry;
|
||||||
(
|
(
|
||||||
json.release.version.generatorOptions
|
releaseConfig.version.currentVersionResolverMetadata as Record<
|
||||||
.currentVersionResolverMetadata as Record<string, string>
|
string,
|
||||||
|
string
|
||||||
|
>
|
||||||
).registry = options.projectConfig.registry;
|
).registry = options.projectConfig.registry;
|
||||||
}
|
}
|
||||||
if (options.projectConfig?.tag) {
|
if (options.projectConfig?.tag) {
|
||||||
json.targets['nx-release-publish'].options.tag =
|
json.targets['nx-release-publish'].options.tag =
|
||||||
options.projectConfig.tag;
|
options.projectConfig.tag;
|
||||||
(
|
(
|
||||||
json.release.version.generatorOptions
|
releaseConfig.version.currentVersionResolverMetadata as Record<
|
||||||
.currentVersionResolverMetadata as Record<string, string>
|
string,
|
||||||
|
string
|
||||||
|
>
|
||||||
).tag = options.projectConfig.tag;
|
).tag = options.projectConfig.tag;
|
||||||
}
|
}
|
||||||
if (options.projectConfig?.publish?.registry) {
|
if (options.projectConfig?.publish?.registry) {
|
||||||
@ -418,16 +423,21 @@ describe('nx release - custom npm registries', () => {
|
|||||||
}
|
}
|
||||||
if (options.projectConfig?.version?.registry) {
|
if (options.projectConfig?.version?.registry) {
|
||||||
(
|
(
|
||||||
json.release.version.generatorOptions
|
releaseConfig.version.currentVersionResolverMetadata as Record<
|
||||||
.currentVersionResolverMetadata as Record<string, string>
|
string,
|
||||||
|
string
|
||||||
|
>
|
||||||
).registry = options.projectConfig.version.registry;
|
).registry = options.projectConfig.version.registry;
|
||||||
}
|
}
|
||||||
if (options.projectConfig?.version?.tag) {
|
if (options.projectConfig?.version?.tag) {
|
||||||
(
|
(
|
||||||
json.release.version.generatorOptions
|
releaseConfig.version.currentVersionResolverMetadata as Record<
|
||||||
.currentVersionResolverMetadata as Record<string, string>
|
string,
|
||||||
|
string
|
||||||
|
>
|
||||||
).tag = options.projectConfig.version.tag;
|
).tag = options.projectConfig.version.tag;
|
||||||
}
|
}
|
||||||
|
json.release = releaseConfig;
|
||||||
return json;
|
return json;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -242,10 +242,8 @@ describe('nx release first run', () => {
|
|||||||
nxJson.release = {
|
nxJson.release = {
|
||||||
projects: [pkg1, pkg2, pkg3],
|
projects: [pkg1, pkg2, pkg3],
|
||||||
version: {
|
version: {
|
||||||
generatorOptions: {
|
|
||||||
fallbackCurrentVersionResolver: 'disk',
|
fallbackCurrentVersionResolver: 'disk',
|
||||||
},
|
},
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return nxJson;
|
return nxJson;
|
||||||
@ -268,10 +266,8 @@ describe('nx release first run', () => {
|
|||||||
nxJson.release = {
|
nxJson.release = {
|
||||||
projects: [pkg1, pkg2, pkg3],
|
projects: [pkg1, pkg2, pkg3],
|
||||||
version: {
|
version: {
|
||||||
generatorOptions: {
|
|
||||||
fallbackCurrentVersionResolver: 'disk',
|
fallbackCurrentVersionResolver: 'disk',
|
||||||
},
|
},
|
||||||
},
|
|
||||||
changelog: {
|
changelog: {
|
||||||
automaticFromRef: true,
|
automaticFromRef: true,
|
||||||
},
|
},
|
||||||
@ -302,10 +298,8 @@ describe('nx release first run', () => {
|
|||||||
projects: [pkg1, pkg2, pkg3],
|
projects: [pkg1, pkg2, pkg3],
|
||||||
projectsRelationship: 'independent',
|
projectsRelationship: 'independent',
|
||||||
version: {
|
version: {
|
||||||
generatorOptions: {
|
|
||||||
fallbackCurrentVersionResolver: 'disk',
|
fallbackCurrentVersionResolver: 'disk',
|
||||||
},
|
},
|
||||||
},
|
|
||||||
changelog: {
|
changelog: {
|
||||||
projectChangelogs: {},
|
projectChangelogs: {},
|
||||||
},
|
},
|
||||||
@ -332,10 +326,8 @@ describe('nx release first run', () => {
|
|||||||
projects: [pkg1, pkg2, pkg3],
|
projects: [pkg1, pkg2, pkg3],
|
||||||
projectsRelationship: 'independent',
|
projectsRelationship: 'independent',
|
||||||
version: {
|
version: {
|
||||||
generatorOptions: {
|
|
||||||
fallbackCurrentVersionResolver: 'disk',
|
fallbackCurrentVersionResolver: 'disk',
|
||||||
},
|
},
|
||||||
},
|
|
||||||
changelog: {
|
changelog: {
|
||||||
automaticFromRef: true,
|
automaticFromRef: true,
|
||||||
projectChangelogs: {},
|
projectChangelogs: {},
|
||||||
|
|||||||
@ -126,10 +126,9 @@ describe('nx release - independent projects', () => {
|
|||||||
|
|
||||||
NX Running release version for project: {project-name}
|
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 manifest: {project-name}/package.json
|
||||||
{project-name} 📄 Resolved the current version as 0.0.0 from {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} 📄 Using the provided version specifier "999.9.9-package.1".
|
{project-name} ✍️ New version 999.9.9-package.1 written to manifest: {project-name}/package.json
|
||||||
{project-name} ✍️ New version 999.9.9-package.1 written to {project-name}/package.json
|
|
||||||
|
|
||||||
|
|
||||||
"name": "@proj/{project-name}",
|
"name": "@proj/{project-name}",
|
||||||
@ -155,10 +154,9 @@ describe('nx release - independent projects', () => {
|
|||||||
|
|
||||||
NX Running release version for project: {project-name}
|
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 manifest: {project-name}/package.json
|
||||||
{project-name} 📄 Resolved the current version as 0.0.0 from {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} 📄 Using the provided version specifier "999.9.9-package.2".
|
{project-name} ✍️ New version 999.9.9-package.2 written to manifest: {project-name}/package.json
|
||||||
{project-name} ✍️ New version 999.9.9-package.2 written to {project-name}/package.json
|
|
||||||
|
|
||||||
|
|
||||||
"name": "@proj/{project-name}",
|
"name": "@proj/{project-name}",
|
||||||
@ -187,11 +185,16 @@ describe('nx release - independent projects', () => {
|
|||||||
|
|
||||||
NX Running release version for project: {project-name}
|
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.2 from manifest: {project-name}/package.json
|
||||||
{project-name} 📄 Resolved the current version as 0.0.0 from {project-name}/package.json
|
{project-name} ✍️ Updated 1 dependency in manifest: {project-name}/package.json
|
||||||
{project-name} 📄 Using the provided version specifier "999.9.9-package.3".
|
{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-package.3 written to {project-name}/package.json
|
{project-name} ✍️ New version 999.9.9 written to manifest: {project-name}/package.json
|
||||||
{project-name} ✍️ Applying new version 999.9.9-package.3 to 1 package which depends on {project-name}
|
|
||||||
|
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}",
|
"name": "@proj/{project-name}",
|
||||||
@ -238,10 +241,9 @@ describe('nx release - independent projects', () => {
|
|||||||
|
|
||||||
NX Running release version for project: {project-name}
|
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 manifest: {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} ❓ 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} 📄 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 manifest: {project-name}/package.json
|
||||||
{project-name} ✍️ New version 999.9.9-version-git-operations-test.2 written to {project-name}/package.json
|
|
||||||
|
|
||||||
|
|
||||||
"name": "@proj/{project-name}",
|
"name": "@proj/{project-name}",
|
||||||
@ -317,28 +319,26 @@ describe('nx release - independent projects', () => {
|
|||||||
|
|
||||||
NX Running release version for project: {project-name}
|
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 manifest: {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} ❓ 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} 📄 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 manifest: {project-name}/package.json
|
||||||
{project-name} ✍️ New version 999.9.9-version-git-operations-test.3 written to {project-name}/package.json
|
|
||||||
|
|
||||||
NX Running release version for project: {project-name}
|
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 manifest: {project-name}/package.json
|
||||||
{project-name} 📄 Resolved the current version as 999.9.9 from {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} 📄 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 manifest: {project-name}/package.json
|
||||||
{project-name} ✍️ New version 999.9.9-version-git-operations-test.3 written to {project-name}/package.json
|
|
||||||
|
|
||||||
NX Running release version for project: {project-name}
|
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 manifest: {project-name}/package.json
|
||||||
{project-name} 📄 Resolved the current version as 999.9.9-package.3 from {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} 📄 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 manifest: {project-name}/package.json
|
||||||
{project-name} ✍️ New version 999.9.9-version-git-operations-test.3 written to {project-name}/package.json
|
{project-name} ✍️ Updated 1 dependency in manifest: {project-name}/package.json
|
||||||
|
|
||||||
|
|
||||||
"name": "@proj/{project-name}",
|
"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",
|
+ "version": "999.9.9-version-git-operations-test.3",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
||||||
@ -348,15 +348,18 @@ describe('nx release - independent projects', () => {
|
|||||||
+ "version": "999.9.9-version-git-operations-test.3",
|
+ "version": "999.9.9-version-git-operations-test.3",
|
||||||
"scripts": {
|
"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}",
|
"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",
|
+ "version": "999.9.9-version-git-operations-test.3",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
||||||
|
|
||||||
Skipped lock file update because {package-manager} workspaces are not enabled.
|
|
||||||
|
|
||||||
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
|
NX Committing changes with git
|
||||||
@ -877,10 +880,8 @@ describe('nx release - independent projects', () => {
|
|||||||
projectsRelationship: 'independent',
|
projectsRelationship: 'independent',
|
||||||
releaseTagPattern: '{projectName}@v{version}',
|
releaseTagPattern: '{projectName}@v{version}',
|
||||||
version: {
|
version: {
|
||||||
generatorOptions: {
|
|
||||||
currentVersionResolver: 'git-tag',
|
currentVersionResolver: 'git-tag',
|
||||||
},
|
},
|
||||||
},
|
|
||||||
changelog: {
|
changelog: {
|
||||||
projectChangelogs: true,
|
projectChangelogs: true,
|
||||||
},
|
},
|
||||||
@ -933,12 +934,9 @@ describe('nx release - independent projects', () => {
|
|||||||
projectsRelationship: 'independent',
|
projectsRelationship: 'independent',
|
||||||
releaseTagPattern: '{projectName}@v{version}',
|
releaseTagPattern: '{projectName}@v{version}',
|
||||||
version: {
|
version: {
|
||||||
generatorOptions: {
|
|
||||||
// added specifierSource to ensure conventional commits are used
|
|
||||||
specifierSource: 'conventional-commits',
|
specifierSource: 'conventional-commits',
|
||||||
currentVersionResolver: 'git-tag',
|
currentVersionResolver: 'git-tag',
|
||||||
},
|
},
|
||||||
},
|
|
||||||
changelog: {
|
changelog: {
|
||||||
projectChangelogs: true,
|
projectChangelogs: true,
|
||||||
},
|
},
|
||||||
@ -985,14 +983,17 @@ describe('nx release - independent projects', () => {
|
|||||||
expect(
|
expect(
|
||||||
releaseOutput.match(
|
releaseOutput.match(
|
||||||
new RegExp(
|
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'
|
'g'
|
||||||
)
|
)
|
||||||
).length
|
).length
|
||||||
).toEqual(1);
|
).toEqual(1);
|
||||||
expect(
|
expect(
|
||||||
releaseOutput.match(
|
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
|
).length
|
||||||
).toEqual(1);
|
).toEqual(1);
|
||||||
expect(
|
expect(
|
||||||
@ -1004,14 +1005,17 @@ describe('nx release - independent projects', () => {
|
|||||||
expect(
|
expect(
|
||||||
releaseOutput.match(
|
releaseOutput.match(
|
||||||
new RegExp(
|
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'
|
'g'
|
||||||
)
|
)
|
||||||
).length
|
).length
|
||||||
).toEqual(1);
|
).toEqual(1);
|
||||||
expect(
|
expect(
|
||||||
releaseOutput.match(
|
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
|
).length
|
||||||
).toEqual(1);
|
).toEqual(1);
|
||||||
expect(
|
expect(
|
||||||
@ -1021,7 +1025,7 @@ describe('nx release - independent projects', () => {
|
|||||||
|
|
||||||
expect(
|
expect(
|
||||||
releaseOutput.match(new RegExp(`Generating an entry in `, 'g')).length
|
releaseOutput.match(new RegExp(`Generating an entry in `, 'g')).length
|
||||||
).toEqual(2);
|
).toEqual(3);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
releaseOutput.match(
|
releaseOutput.match(
|
||||||
|
|||||||
@ -83,10 +83,8 @@ describe('nx release multiple release branches', () => {
|
|||||||
commit: true,
|
commit: true,
|
||||||
tag: true,
|
tag: true,
|
||||||
},
|
},
|
||||||
generatorOptions: {
|
|
||||||
currentVersionResolver: 'git-tag',
|
currentVersionResolver: 'git-tag',
|
||||||
},
|
},
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return json;
|
return json;
|
||||||
@ -125,24 +123,21 @@ describe('nx release multiple release branches', () => {
|
|||||||
|
|
||||||
NX Running release version for project: {project-name}
|
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 tags using pattern "v{version}". Falling back to the version 0.0.0 in manifest: {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} ❓ Applied explicit semver value "0.0.7", from the given specifier, to get new version 0.0.7
|
||||||
{project-name} 📄 Using the provided version specifier "0.0.7".
|
{project-name} ✍️ New version 0.0.7 written to manifest: {project-name}/package.json
|
||||||
{project-name} ✍️ New version 0.0.7 written to {project-name}/package.json
|
|
||||||
|
|
||||||
NX Running release version for project: {project-name}
|
NX Running release version for project: {project-name}
|
||||||
|
|
||||||
{project-name} 🔍 Reading data for package "@proj/{project-name}" from {project-name}/package.json
|
{project-name} 🔄 Reusing the current version 0.0.0 already resolved for {project-name} from the disk fallback
|
||||||
{project-name} 📄 Using the current version 0.0.0 already resolved from 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} 📄 Using the provided version specifier "0.0.7".
|
{project-name} ✍️ New version 0.0.7 written to manifest: {project-name}/package.json
|
||||||
{project-name} ✍️ New version 0.0.7 written to {project-name}/package.json
|
|
||||||
|
|
||||||
NX Running release version for project: {project-name}
|
NX Running release version for project: {project-name}
|
||||||
|
|
||||||
{project-name} 🔍 Reading data for package "@proj/{project-name}" from {project-name}/package.json
|
{project-name} 🔄 Reusing the current version 0.0.0 already resolved for {project-name} from the disk fallback
|
||||||
{project-name} 📄 Using the current version 0.0.0 already resolved from 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} 📄 Using the provided version specifier "0.0.7".
|
{project-name} ✍️ New version 0.0.7 written to manifest: {project-name}/package.json
|
||||||
{project-name} ✍️ New version 0.0.7 written to {project-name}/package.json
|
|
||||||
|
|
||||||
|
|
||||||
"name": "@proj/{project-name}",
|
"name": "@proj/{project-name}",
|
||||||
@ -174,24 +169,33 @@ describe('nx release multiple release branches', () => {
|
|||||||
|
|
||||||
NX Running release version for project: {project-name}
|
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", based on releaseTagPattern "v{version}"
|
||||||
{project-name} 📄 Resolved the current version as 0.0.7 from git tag "v0.0.7".
|
{project-name} ❓ Applied semver relative bump "minor", from the given specifier, to get new version 0.1.0
|
||||||
{project-name} 📄 Using the provided version specifier "minor".
|
{project-name} ✍️ New version 0.1.0 written to manifest: {project-name}/package.json
|
||||||
{project-name} ✍️ New version 0.1.0 written to {project-name}/package.json
|
|
||||||
|
|
||||||
NX Running release version for project: {project-name}
|
NX Running release version for project: {project-name}
|
||||||
|
|
||||||
{project-name} 🔍 Reading data for package "@proj/{project-name}" from {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} 📄 Using the current version 0.0.7 already resolved 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} 📄 Using the provided version specifier "minor".
|
{project-name} ✍️ New version 0.1.0 written to manifest: {project-name}/package.json
|
||||||
{project-name} ✍️ New version 0.1.0 written to {project-name}/package.json
|
|
||||||
|
|
||||||
NX Running release version for project: {project-name}
|
NX Running release version for project: {project-name}
|
||||||
|
|
||||||
{project-name} 🔍 Reading data for package "@proj/{project-name}" from {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} 📄 Using the current version 0.0.7 already resolved 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} 📄 Using the provided version specifier "minor".
|
{project-name} ✍️ New version 0.1.0 written to manifest: {project-name}/package.json
|
||||||
{project-name} ✍️ New version 0.1.0 written to {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}",
|
"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
|
NX Committing changes with git
|
||||||
|
|
||||||
|
|
||||||
@ -226,24 +218,21 @@ describe('nx release multiple release branches', () => {
|
|||||||
|
|
||||||
NX Running release version for project: {project-name}
|
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", based on releaseTagPattern "v{version}"
|
||||||
{project-name} 📄 Resolved the current version as 0.0.7 from git tag "v0.0.7".
|
{project-name} ❓ Applied semver relative bump "patch", from the given specifier, to get new version 0.0.8
|
||||||
{project-name} 📄 Using the provided version specifier "patch".
|
{project-name} ✍️ New version 0.0.8 written to manifest: {project-name}/package.json
|
||||||
{project-name} ✍️ New version 0.0.8 written to {project-name}/package.json
|
|
||||||
|
|
||||||
NX Running release version for project: {project-name}
|
NX Running release version for project: {project-name}
|
||||||
|
|
||||||
{project-name} 🔍 Reading data for package "@proj/{project-name}" from {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} 📄 Using the current version 0.0.7 already resolved 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} 📄 Using the provided version specifier "patch".
|
{project-name} ✍️ New version 0.0.8 written to manifest: {project-name}/package.json
|
||||||
{project-name} ✍️ New version 0.0.8 written to {project-name}/package.json
|
|
||||||
|
|
||||||
NX Running release version for project: {project-name}
|
NX Running release version for project: {project-name}
|
||||||
|
|
||||||
{project-name} 🔍 Reading data for package "@proj/{project-name}" from {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} 📄 Using the current version 0.0.7 already resolved 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} 📄 Using the provided version specifier "patch".
|
{project-name} ✍️ New version 0.0.8 written to manifest: {project-name}/package.json
|
||||||
{project-name} ✍️ New version 0.0.8 written to {project-name}/package.json
|
|
||||||
|
|
||||||
|
|
||||||
"name": "@proj/{project-name}",
|
"name": "@proj/{project-name}",
|
||||||
@ -281,10 +270,8 @@ describe('nx release multiple release branches', () => {
|
|||||||
commit: true,
|
commit: true,
|
||||||
tag: true,
|
tag: true,
|
||||||
},
|
},
|
||||||
generatorOptions: {
|
|
||||||
currentVersionResolver: 'git-tag',
|
currentVersionResolver: 'git-tag',
|
||||||
},
|
},
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return json;
|
return json;
|
||||||
@ -321,24 +308,33 @@ describe('nx release multiple release branches', () => {
|
|||||||
|
|
||||||
NX Running release version for project: {project-name}
|
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 tags using pattern "v{version}". Falling back to the version 0.0.0 in manifest: {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} ❓ Applied semver relative bump "minor", from the given specifier, to get new version 0.1.0
|
||||||
{project-name} 📄 Using the provided version specifier "minor".
|
{project-name} ✍️ New version 0.1.0 written to manifest: {project-name}/package.json
|
||||||
{project-name} ✍️ New version 0.1.0 written to {project-name}/package.json
|
|
||||||
|
|
||||||
NX Running release version for project: {project-name}
|
NX Running release version for project: {project-name}
|
||||||
|
|
||||||
{project-name} 🔍 Reading data for package "@proj/{project-name}" from {project-name}/package.json
|
{project-name} 🔄 Reusing the current version 0.0.0 already resolved for {project-name} from the disk fallback
|
||||||
{project-name} 📄 Using the current version 0.0.0 already resolved from 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} 📄 Using the provided version specifier "minor".
|
{project-name} ✍️ New version 0.1.0 written to manifest: {project-name}/package.json
|
||||||
{project-name} ✍️ New version 0.1.0 written to {project-name}/package.json
|
|
||||||
|
|
||||||
NX Running release version for project: {project-name}
|
NX Running release version for project: {project-name}
|
||||||
|
|
||||||
{project-name} 🔍 Reading data for package "@proj/{project-name}" from {project-name}/package.json
|
{project-name} 🔄 Reusing the current version 0.0.0 already resolved for {project-name} from the disk fallback
|
||||||
{project-name} 📄 Using the current version 0.0.0 already resolved from 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} 📄 Using the provided version specifier "minor".
|
{project-name} ✍️ New version 0.1.0 written to manifest: {project-name}/package.json
|
||||||
{project-name} ✍️ New version 0.1.0 written to {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}",
|
"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
|
NX Committing changes with git
|
||||||
|
|
||||||
|
|
||||||
@ -373,24 +357,21 @@ describe('nx release multiple release branches', () => {
|
|||||||
|
|
||||||
NX Running release version for project: {project-name}
|
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", based on releaseTagPattern "v{version}"
|
||||||
{project-name} 📄 Resolved the current version as 0.1.0 from git tag "v0.1.0".
|
{project-name} ❓ Applied semver relative bump "major", from the given specifier, to get new version 1.0.0
|
||||||
{project-name} 📄 Using the provided version specifier "major".
|
{project-name} ✍️ New version 1.0.0 written to manifest: {project-name}/package.json
|
||||||
{project-name} ✍️ New version 1.0.0 written to {project-name}/package.json
|
|
||||||
|
|
||||||
NX Running release version for project: {project-name}
|
NX Running release version for project: {project-name}
|
||||||
|
|
||||||
{project-name} 🔍 Reading data for package "@proj/{project-name}" from {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} 📄 Using the current version 0.1.0 already resolved 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} 📄 Using the provided version specifier "major".
|
{project-name} ✍️ New version 1.0.0 written to manifest: {project-name}/package.json
|
||||||
{project-name} ✍️ New version 1.0.0 written to {project-name}/package.json
|
|
||||||
|
|
||||||
NX Running release version for project: {project-name}
|
NX Running release version for project: {project-name}
|
||||||
|
|
||||||
{project-name} 🔍 Reading data for package "@proj/{project-name}" from {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} 📄 Using the current version 0.1.0 already resolved 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} 📄 Using the provided version specifier "major".
|
{project-name} ✍️ New version 1.0.0 written to manifest: {project-name}/package.json
|
||||||
{project-name} ✍️ New version 1.0.0 written to {project-name}/package.json
|
|
||||||
|
|
||||||
|
|
||||||
"name": "@proj/{project-name}",
|
"name": "@proj/{project-name}",
|
||||||
|
|||||||
@ -61,9 +61,9 @@ describe('nx release pre-version command', () => {
|
|||||||
silenceError: true,
|
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(
|
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) => {
|
updateJson(`nx.json`, (json) => {
|
||||||
|
|||||||
@ -120,12 +120,16 @@ describe('nx release preserve local dependency protocols', () => {
|
|||||||
return { workspacePath: tmpProjPath(), pkg1, pkg2 };
|
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
|
// The package manager currently does not matter for the versioning behavior, it's imperatively controlled by the user
|
||||||
const { workspacePath } = await initializeProject('pnpm');
|
const { workspacePath } = await initializeProject('pnpm');
|
||||||
|
|
||||||
updateJson<NxJsonConfiguration>('nx.json', (nxJson) => {
|
updateJson<NxJsonConfiguration>('nx.json', (nxJson) => {
|
||||||
nxJson.release = {};
|
nxJson.release = {
|
||||||
|
version: {
|
||||||
|
preserveLocalDependencyProtocols: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
return nxJson;
|
return nxJson;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -133,16 +137,14 @@ describe('nx release preserve local dependency protocols', () => {
|
|||||||
expect(runCLI(`release version minor -d --verbose`, { cwd: workspacePath }))
|
expect(runCLI(`release version minor -d --verbose`, { cwd: workspacePath }))
|
||||||
.toMatchInlineSnapshot(`
|
.toMatchInlineSnapshot(`
|
||||||
NX Running release version for project: {project-name}
|
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 manifest: {project-name}/package.json
|
||||||
{project-name} 📄 Resolved the current version as 0.0.0 from {project-name}/package.json
|
{project-name} ❓ Applied semver relative bump "minor", from the given specifier, to get new version 0.1.0
|
||||||
{project-name} 📄 Using the provided version specifier "minor".
|
{project-name} ✍️ New version 0.1.0 written to manifest: {project-name}/package.json
|
||||||
{project-name} ✍️ New version 0.1.0 written to {project-name}/package.json
|
{project-name} ✍️ Updated 1 dependency in manifest: {project-name}/package.json
|
||||||
NX Running release version for project: {project-name}
|
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 manifest: {project-name}/package.json
|
||||||
{project-name} 📄 Resolved the current version as 0.0.0 from {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} 📄 Using the provided version specifier "minor".
|
{project-name} ✍️ New version 0.1.0 written to manifest: {project-name}/package.json
|
||||||
{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}
|
|
||||||
"name": "@proj/{project-name}",
|
"name": "@proj/{project-name}",
|
||||||
- "version": "0.0.0",
|
- "version": "0.0.0",
|
||||||
+ "version": "0.1.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
|
// The package manager currently does not matter for the versioning behavior, it's imperatively controlled by the user
|
||||||
const { workspacePath } = await initializeProject('pnpm');
|
const { workspacePath } = await initializeProject('pnpm');
|
||||||
|
|
||||||
updateJson<NxJsonConfiguration>('nx.json', (nxJson) => {
|
updateJson<NxJsonConfiguration>('nx.json', (nxJson) => {
|
||||||
nxJson.release = {
|
nxJson.release = {
|
||||||
version: {
|
version: {},
|
||||||
generatorOptions: {
|
|
||||||
preserveLocalDependencyProtocols: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
return nxJson;
|
return nxJson;
|
||||||
});
|
});
|
||||||
@ -185,16 +183,13 @@ describe('nx release preserve local dependency protocols', () => {
|
|||||||
expect(runCLI(`release version minor -d --verbose`, { cwd: workspacePath }))
|
expect(runCLI(`release version minor -d --verbose`, { cwd: workspacePath }))
|
||||||
.toMatchInlineSnapshot(`
|
.toMatchInlineSnapshot(`
|
||||||
NX Running release version for project: {project-name}
|
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 manifest: {project-name}/package.json
|
||||||
{project-name} 📄 Resolved the current version as 0.0.0 from {project-name}/package.json
|
{project-name} ❓ Applied semver relative bump "minor", from the given specifier, to get new version 0.1.0
|
||||||
{project-name} 📄 Using the provided version specifier "minor".
|
{project-name} ✍️ New version 0.1.0 written to manifest: {project-name}/package.json
|
||||||
{project-name} ✍️ New version 0.1.0 written to {project-name}/package.json
|
|
||||||
NX Running release version for project: {project-name}
|
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 manifest: {project-name}/package.json
|
||||||
{project-name} 📄 Resolved the current version as 0.0.0 from {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} 📄 Using the provided version specifier "minor".
|
{project-name} ✍️ New version 0.1.0 written to manifest: {project-name}/package.json
|
||||||
{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}
|
|
||||||
"name": "@proj/{project-name}",
|
"name": "@proj/{project-name}",
|
||||||
- "version": "0.0.0",
|
- "version": "0.0.0",
|
||||||
+ "version": "0.1.0",
|
+ "version": "0.1.0",
|
||||||
|
|||||||
@ -222,8 +222,8 @@ describe('nx release - private JS packages', () => {
|
|||||||
).toEqual('999.9.9');
|
).toEqual('999.9.9');
|
||||||
|
|
||||||
// The private package should have never been published
|
// The private package should have never been published
|
||||||
expect(() => execSync(`npm view @proj/${privatePkg} version`)).toThrowError(
|
expect(() => execSync(`npm view @proj/${privatePkg} version`)).toThrow(
|
||||||
/npm ERR! code E404/
|
/npm (ERR!|error) code E404/
|
||||||
);
|
);
|
||||||
}, 500000);
|
}, 500000);
|
||||||
|
|
||||||
@ -310,8 +310,8 @@ describe('nx release - private JS packages', () => {
|
|||||||
).toEqual('999.9.10');
|
).toEqual('999.9.10');
|
||||||
|
|
||||||
// The private package should have never been published
|
// The private package should have never been published
|
||||||
expect(() => execSync(`npm view @proj/${privatePkg} version`)).toThrowError(
|
expect(() => execSync(`npm view @proj/${privatePkg} version`)).toThrow(
|
||||||
/npm ERR! code E404/
|
/npm (ERR!|error) code E404/
|
||||||
);
|
);
|
||||||
}, 500000);
|
}, 500000);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -89,10 +89,9 @@ describe('release publishable libraries in workspace with ts solution setup', ()
|
|||||||
expect(releaseOutput).toMatchInlineSnapshot(`
|
expect(releaseOutput).toMatchInlineSnapshot(`
|
||||||
NX Executing pre-version command
|
NX Executing pre-version command
|
||||||
NX Running release version for project: @proj/{project-name}
|
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 manifest: 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} ❓ Applied explicit semver value "0.0.2", from the given specifier, to get new version 0.0.2
|
||||||
@proj/{project-name} 📄 Using the provided version specifier "0.0.2".
|
@proj/{project-name} ✍️ New version 0.0.2 written to manifest: packages/{project-name}/package.json
|
||||||
@proj/{project-name} ✍️ New version 0.0.2 written to packages/{project-name}/package.json
|
|
||||||
"name": "@proj/{project-name}",
|
"name": "@proj/{project-name}",
|
||||||
- "version": "0.0.1",
|
- "version": "0.0.1",
|
||||||
+ "version": "0.0.2",
|
+ "version": "0.0.2",
|
||||||
@ -144,10 +143,9 @@ describe('release publishable libraries in workspace with ts solution setup', ()
|
|||||||
expect(releaseOutput).toMatchInlineSnapshot(`
|
expect(releaseOutput).toMatchInlineSnapshot(`
|
||||||
NX Executing pre-version command
|
NX Executing pre-version command
|
||||||
NX Running release version for project: @proj/{project-name}
|
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 manifest: 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} ❓ Applied explicit semver value "0.0.3", from the given specifier, to get new version 0.0.3
|
||||||
@proj/{project-name} 📄 Using the provided version specifier "0.0.3".
|
@proj/{project-name} ✍️ New version 0.0.3 written to manifest: packages/{project-name}/package.json
|
||||||
@proj/{project-name} ✍️ New version 0.0.3 written to packages/{project-name}/package.json
|
|
||||||
"name": "@proj/{project-name}",
|
"name": "@proj/{project-name}",
|
||||||
- "version": "0.0.1",
|
- "version": "0.0.1",
|
||||||
+ "version": "0.0.3",
|
+ "version": "0.0.3",
|
||||||
@ -204,10 +202,9 @@ describe('release publishable libraries in workspace with ts solution setup', ()
|
|||||||
expect(releaseOutput).toMatchInlineSnapshot(`
|
expect(releaseOutput).toMatchInlineSnapshot(`
|
||||||
NX Executing pre-version command
|
NX Executing pre-version command
|
||||||
NX Running release version for project: @proj/{project-name}
|
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 manifest: 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} ❓ Applied explicit semver value "0.0.4", from the given specifier, to get new version 0.0.4
|
||||||
@proj/{project-name} 📄 Using the provided version specifier "0.0.4".
|
@proj/{project-name} ✍️ New version 0.0.4 written to manifest: packages/{project-name}/package.json
|
||||||
@proj/{project-name} ✍️ New version 0.0.4 written to packages/{project-name}/package.json
|
|
||||||
"name": "@proj/{project-name}",
|
"name": "@proj/{project-name}",
|
||||||
- "version": "0.0.1",
|
- "version": "0.0.1",
|
||||||
+ "version": "0.0.4",
|
+ "version": "0.0.4",
|
||||||
|
|||||||
@ -88,10 +88,9 @@ describe('release publishable libraries', () => {
|
|||||||
expect(releaseOutput).toMatchInlineSnapshot(`
|
expect(releaseOutput).toMatchInlineSnapshot(`
|
||||||
NX Executing pre-version command
|
NX Executing pre-version command
|
||||||
NX Running release version for project: {project-name}
|
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", based on releaseTagPattern "v{version}"
|
||||||
{project-name} 📄 Resolved the current version as 0.0.0 from git tag "v0.0.0".
|
{project-name} ❓ Applied explicit semver value "0.0.2", from the given specifier, to get new version 0.0.2
|
||||||
{project-name} 📄 Using the provided version specifier "0.0.2".
|
{project-name} ✍️ New version 0.0.2 written to manifest: dist/packages/{project-name}/package.json
|
||||||
{project-name} ✍️ New version 0.0.2 written to dist/packages/{project-name}/package.json
|
|
||||||
"name": "@proj/{project-name}",
|
"name": "@proj/{project-name}",
|
||||||
- "version": "0.0.1",
|
- "version": "0.0.1",
|
||||||
+ "version": "0.0.2",
|
+ "version": "0.0.2",
|
||||||
@ -144,10 +143,9 @@ describe('release publishable libraries', () => {
|
|||||||
expect(releaseOutput).toMatchInlineSnapshot(`
|
expect(releaseOutput).toMatchInlineSnapshot(`
|
||||||
NX Executing pre-version command
|
NX Executing pre-version command
|
||||||
NX Running release version for project: {project-name}
|
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", based on releaseTagPattern "v{version}"
|
||||||
{project-name} 📄 Resolved the current version as 0.0.2 from git tag "v0.0.2".
|
{project-name} ❓ Applied explicit semver value "0.0.3", from the given specifier, to get new version 0.0.3
|
||||||
{project-name} 📄 Using the provided version specifier "0.0.3".
|
{project-name} ✍️ New version 0.0.3 written to manifest: dist/packages/{project-name}/package.json
|
||||||
{project-name} ✍️ New version 0.0.3 written to dist/packages/{project-name}/package.json
|
|
||||||
"name": "@proj/{project-name}",
|
"name": "@proj/{project-name}",
|
||||||
- "version": "0.0.1",
|
- "version": "0.0.1",
|
||||||
+ "version": "0.0.3",
|
+ "version": "0.0.3",
|
||||||
@ -202,10 +200,9 @@ describe('release publishable libraries', () => {
|
|||||||
expect(releaseOutput).toMatchInlineSnapshot(`
|
expect(releaseOutput).toMatchInlineSnapshot(`
|
||||||
NX Executing pre-version command
|
NX Executing pre-version command
|
||||||
NX Running release version for project: {project-name}
|
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", based on releaseTagPattern "v{version}"
|
||||||
{project-name} 📄 Resolved the current version as 0.0.3 from git tag "v0.0.3".
|
{project-name} ❓ Applied explicit semver value "0.0.4", from the given specifier, to get new version 0.0.4
|
||||||
{project-name} 📄 Using the provided version specifier "0.0.4".
|
{project-name} ✍️ New version 0.0.4 written to manifest: dist/packages/{project-name}/package.json
|
||||||
{project-name} ✍️ New version 0.0.4 written to dist/packages/{project-name}/package.json
|
|
||||||
"name": "@proj/{project-name}",
|
"name": "@proj/{project-name}",
|
||||||
- "version": "0.0.1",
|
- "version": "0.0.1",
|
||||||
+ "version": "0.0.4",
|
+ "version": "0.0.4",
|
||||||
@ -260,10 +257,9 @@ describe('release publishable libraries', () => {
|
|||||||
expect(releaseOutput).toMatchInlineSnapshot(`
|
expect(releaseOutput).toMatchInlineSnapshot(`
|
||||||
NX Executing pre-version command
|
NX Executing pre-version command
|
||||||
NX Running release version for project: {project-name}
|
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", based on releaseTagPattern "v{version}"
|
||||||
{project-name} 📄 Resolved the current version as 0.0.4 from git tag "v0.0.4".
|
{project-name} ❓ Applied explicit semver value "0.0.5", from the given specifier, to get new version 0.0.5
|
||||||
{project-name} 📄 Using the provided version specifier "0.0.5".
|
{project-name} ✍️ New version 0.0.5 written to manifest: dist/packages/{project-name}/package.json
|
||||||
{project-name} ✍️ New version 0.0.5 written to dist/packages/{project-name}/package.json
|
|
||||||
"name": "@proj/{project-name}",
|
"name": "@proj/{project-name}",
|
||||||
- "version": "0.0.1",
|
- "version": "0.0.1",
|
||||||
+ "version": "0.0.5",
|
+ "version": "0.0.5",
|
||||||
|
|||||||
@ -1,4 +1,8 @@
|
|||||||
import type { NxJsonConfiguration } from '@nx/devkit';
|
import {
|
||||||
|
type NxJsonConfiguration,
|
||||||
|
detectPackageManager,
|
||||||
|
getPackageManagerCommand,
|
||||||
|
} from '@nx/devkit';
|
||||||
import {
|
import {
|
||||||
cleanupProject,
|
cleanupProject,
|
||||||
createFile,
|
createFile,
|
||||||
@ -6,13 +10,16 @@ import {
|
|||||||
killProcessAndPorts,
|
killProcessAndPorts,
|
||||||
newProject,
|
newProject,
|
||||||
readFile,
|
readFile,
|
||||||
|
readJson,
|
||||||
runCLI,
|
runCLI,
|
||||||
runCommandAsync,
|
runCommandAsync,
|
||||||
runCommandUntil,
|
runCommandUntil,
|
||||||
|
tmpProjPath,
|
||||||
uniq,
|
uniq,
|
||||||
updateJson,
|
updateJson,
|
||||||
} from '@nx/e2e/utils';
|
} 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({
|
expect.addSnapshotSerializer({
|
||||||
serialize(str: string) {
|
serialize(str: string) {
|
||||||
@ -49,8 +56,13 @@ describe('nx release', () => {
|
|||||||
let pkg1: string;
|
let pkg1: string;
|
||||||
let pkg2: string;
|
let pkg2: string;
|
||||||
let pkg3: string;
|
let pkg3: string;
|
||||||
|
let previousPackageManager: string;
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
|
previousPackageManager = process.env.SELECTED_PM;
|
||||||
|
// Ensure consistent package manager usage in all environments for this file
|
||||||
|
process.env.SELECTED_PM = 'npm';
|
||||||
|
|
||||||
newProject({
|
newProject({
|
||||||
packages: ['@nx/js'],
|
packages: ['@nx/js'],
|
||||||
});
|
});
|
||||||
@ -71,7 +83,10 @@ describe('nx release', () => {
|
|||||||
return json;
|
return json;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
afterAll(() => cleanupProject());
|
afterAll(() => {
|
||||||
|
cleanupProject();
|
||||||
|
process.env.SELECTED_PM = previousPackageManager;
|
||||||
|
});
|
||||||
|
|
||||||
it('should version and publish multiple related npm packages with zero config', async () => {
|
it('should version and publish multiple related npm packages with zero config', async () => {
|
||||||
// Normalize git committer information so it is deterministic in snapshots
|
// 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`
|
`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`);
|
const versionOutput = runCLI(`release version 999.9.9`);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -104,17 +123,24 @@ describe('nx release', () => {
|
|||||||
).toEqual(3);
|
).toEqual(3);
|
||||||
expect(
|
expect(
|
||||||
versionOutput.match(
|
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
|
).length
|
||||||
).toEqual(3);
|
).toEqual(3);
|
||||||
|
// First project
|
||||||
expect(
|
expect(
|
||||||
versionOutput.match(
|
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
|
).length
|
||||||
).toEqual(3);
|
).toEqual(1);
|
||||||
|
// Other projects
|
||||||
expect(
|
expect(
|
||||||
versionOutput.match(
|
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
|
).length
|
||||||
).toEqual(3);
|
).toEqual(3);
|
||||||
|
|
||||||
@ -265,7 +291,7 @@ describe('nx release', () => {
|
|||||||
projects: ['*', '!@proj/source'],
|
projects: ['*', '!@proj/source'],
|
||||||
version: {
|
version: {
|
||||||
generator: '@nx/js:release-version',
|
generator: '@nx/js:release-version',
|
||||||
generatorOptions: {
|
|
||||||
// Resolve the latest version from the custom registry instance, therefore finding the previously published versions
|
// Resolve the latest version from the custom registry instance, therefore finding the previously published versions
|
||||||
currentVersionResolver: 'registry',
|
currentVersionResolver: 'registry',
|
||||||
currentVersionResolverMetadata: {
|
currentVersionResolverMetadata: {
|
||||||
@ -275,7 +301,6 @@ describe('nx release', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
};
|
};
|
||||||
return nxJson;
|
return nxJson;
|
||||||
});
|
});
|
||||||
@ -296,17 +321,12 @@ describe('nx release', () => {
|
|||||||
versionOutput2.match(/Running release version for project: my-pkg-\d*/g)
|
versionOutput2.match(/Running release version for project: my-pkg-\d*/g)
|
||||||
.length
|
.length
|
||||||
).toEqual(3);
|
).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(
|
expect(
|
||||||
versionOutput2.match(
|
versionOutput2.match(
|
||||||
new RegExp(
|
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'
|
'g'
|
||||||
)
|
)
|
||||||
).length
|
).length
|
||||||
@ -315,15 +335,28 @@ describe('nx release', () => {
|
|||||||
expect(
|
expect(
|
||||||
versionOutput2.match(
|
versionOutput2.match(
|
||||||
new RegExp(
|
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'
|
'g'
|
||||||
)
|
)
|
||||||
).length
|
).length
|
||||||
).toEqual(2);
|
).toEqual(2);
|
||||||
|
|
||||||
|
// First project
|
||||||
expect(
|
expect(
|
||||||
versionOutput2.match(
|
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
|
).length
|
||||||
).toEqual(3);
|
).toEqual(3);
|
||||||
|
|
||||||
@ -421,17 +454,17 @@ describe('nx release', () => {
|
|||||||
execSync(
|
execSync(
|
||||||
`npm view @proj/${pkg1}@next version --registry=${customRegistryUrl}`
|
`npm view @proj/${pkg1}@next version --registry=${customRegistryUrl}`
|
||||||
)
|
)
|
||||||
).toThrowError(/npm ERR! code E404/);
|
).toThrow(/npm (ERR!|error) code E404/);
|
||||||
expect(() =>
|
expect(() =>
|
||||||
execSync(
|
execSync(
|
||||||
`npm view @proj/${pkg2}@next version --registry=${customRegistryUrl}`
|
`npm view @proj/${pkg2}@next version --registry=${customRegistryUrl}`
|
||||||
)
|
)
|
||||||
).toThrowError(/npm ERR! code E404/);
|
).toThrow(/npm (ERR!|error) code E404/);
|
||||||
expect(() =>
|
expect(() =>
|
||||||
execSync(
|
execSync(
|
||||||
`npm view @proj/${pkg3}@next version --registry=${customRegistryUrl}`
|
`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"
|
// Actually publish to the custom registry (not e2e registry), and a custom dist tag of "next"
|
||||||
const publishOutput3 = runCLI(publishToNext);
|
const publishOutput3 = runCLI(publishToNext);
|
||||||
@ -739,13 +772,11 @@ describe('nx release', () => {
|
|||||||
releaseTagPattern: 'xx{version}',
|
releaseTagPattern: 'xx{version}',
|
||||||
version: {
|
version: {
|
||||||
generator: '@nx/js:release-version',
|
generator: '@nx/js:release-version',
|
||||||
generatorOptions: {
|
|
||||||
// Resolve the latest version from the git tag
|
// Resolve the latest version from the git tag
|
||||||
currentVersionResolver: 'git-tag',
|
currentVersionResolver: 'git-tag',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
};
|
};
|
||||||
return nxJson;
|
return nxJson;
|
||||||
});
|
});
|
||||||
@ -758,11 +789,6 @@ describe('nx release', () => {
|
|||||||
versionOutput3.match(/Running release version for project: my-pkg-\d*/g)
|
versionOutput3.match(/Running release version for project: my-pkg-\d*/g)
|
||||||
.length
|
.length
|
||||||
).toEqual(3);
|
).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...
|
// It should resolve the current version from the git tag once...
|
||||||
expect(
|
expect(
|
||||||
@ -777,15 +803,28 @@ describe('nx release', () => {
|
|||||||
expect(
|
expect(
|
||||||
versionOutput3.match(
|
versionOutput3.match(
|
||||||
new RegExp(
|
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'
|
'g'
|
||||||
)
|
)
|
||||||
).length
|
).length
|
||||||
).toEqual(2);
|
).toEqual(2);
|
||||||
|
|
||||||
|
// First project
|
||||||
expect(
|
expect(
|
||||||
versionOutput3.match(
|
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
|
).length
|
||||||
).toEqual(3);
|
).toEqual(3);
|
||||||
|
|
||||||
@ -803,14 +842,11 @@ describe('nx release', () => {
|
|||||||
projects: ['*', '!@proj/source'],
|
projects: ['*', '!@proj/source'],
|
||||||
releaseTagPattern: 'xx{version}',
|
releaseTagPattern: 'xx{version}',
|
||||||
version: {
|
version: {
|
||||||
generator: '@nx/js:release-version',
|
|
||||||
generatorOptions: {
|
|
||||||
specifierSource: 'conventional-commits',
|
specifierSource: 'conventional-commits',
|
||||||
currentVersionResolver: 'git-tag',
|
currentVersionResolver: 'git-tag',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
};
|
};
|
||||||
return nxJson;
|
return nxJson;
|
||||||
});
|
});
|
||||||
@ -821,11 +857,6 @@ describe('nx release', () => {
|
|||||||
versionOutput4.match(/Running release version for project: my-pkg-\d*/g)
|
versionOutput4.match(/Running release version for project: my-pkg-\d*/g)
|
||||||
.length
|
.length
|
||||||
).toEqual(3);
|
).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...
|
// It should resolve the current version from the git tag once...
|
||||||
expect(
|
expect(
|
||||||
@ -840,13 +871,24 @@ describe('nx release', () => {
|
|||||||
expect(
|
expect(
|
||||||
versionOutput4.match(
|
versionOutput4.match(
|
||||||
new RegExp(
|
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'
|
'g'
|
||||||
)
|
)
|
||||||
).length
|
).length
|
||||||
).toEqual(2);
|
).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(
|
await runCommandAsync(
|
||||||
`git add ${pkg1}/my-file.txt && git commit -m "feat!: add new file"`
|
`git add ${pkg1}/my-file.txt && git commit -m "feat!: add new file"`
|
||||||
@ -856,7 +898,7 @@ describe('nx release', () => {
|
|||||||
|
|
||||||
expect(
|
expect(
|
||||||
versionOutput5.match(
|
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
|
).length
|
||||||
).toEqual(3);
|
).toEqual(3);
|
||||||
|
|
||||||
@ -916,17 +958,22 @@ describe('nx release', () => {
|
|||||||
tag: true,
|
tag: true,
|
||||||
},
|
},
|
||||||
version: {
|
version: {
|
||||||
generatorOptions: {
|
|
||||||
specifierSource: 'conventional-commits',
|
specifierSource: 'conventional-commits',
|
||||||
currentVersionResolver: 'git-tag',
|
currentVersionResolver: 'git-tag',
|
||||||
},
|
},
|
||||||
},
|
|
||||||
changelog: {
|
changelog: {
|
||||||
projectChangelogs: {},
|
projectChangelogs: {},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
return nxJson;
|
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 zz-1300.0.0`);
|
||||||
await runCommandAsync(`git tag xx-1400.0.0`);
|
await runCommandAsync(`git tag xx-1400.0.0`);
|
||||||
@ -954,7 +1001,7 @@ describe('nx release', () => {
|
|||||||
expect(
|
expect(
|
||||||
releaseOutput2.match(
|
releaseOutput2.match(
|
||||||
new RegExp(
|
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'
|
'g'
|
||||||
)
|
)
|
||||||
).length
|
).length
|
||||||
@ -977,7 +1024,7 @@ describe('nx release', () => {
|
|||||||
expect(
|
expect(
|
||||||
releaseOutput3.match(
|
releaseOutput3.match(
|
||||||
new RegExp(
|
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'
|
'g'
|
||||||
)
|
)
|
||||||
).length
|
).length
|
||||||
@ -996,7 +1043,7 @@ describe('nx release', () => {
|
|||||||
expect(
|
expect(
|
||||||
releaseOutput3.match(
|
releaseOutput3.match(
|
||||||
new RegExp(
|
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'
|
'g'
|
||||||
)
|
)
|
||||||
).length
|
).length
|
||||||
@ -1031,13 +1078,21 @@ describe('nx release', () => {
|
|||||||
tag: false,
|
tag: false,
|
||||||
},
|
},
|
||||||
version: {
|
version: {
|
||||||
generatorOptions: {
|
|
||||||
currentVersionResolver: 'git-tag',
|
currentVersionResolver: 'git-tag',
|
||||||
},
|
},
|
||||||
},
|
|
||||||
};
|
};
|
||||||
return nxJson;
|
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`, {
|
const releaseOutput4a = runCLI(`release patch --skip-publish`, {
|
||||||
silenceError: true,
|
silenceError: true,
|
||||||
@ -1045,11 +1100,9 @@ describe('nx release', () => {
|
|||||||
|
|
||||||
expect(releaseOutput4a).toMatchInlineSnapshot(`
|
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
|
Pass --verbose to see the stacktrace.
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
|
|
||||||
`);
|
`);
|
||||||
@ -1062,44 +1115,48 @@ describe('nx release', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
expect(releaseOutput4b).toMatch(
|
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(
|
expect(
|
||||||
releaseOutput4b.match(
|
releaseOutput4b.match(
|
||||||
new RegExp(
|
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'
|
'g'
|
||||||
)
|
)
|
||||||
).length
|
).length
|
||||||
).toEqual(2);
|
).toEqual(2);
|
||||||
|
|
||||||
updateJson<NxJsonConfiguration>('nx.json', (nxJson) => {
|
updateJson<NxJsonConfiguration>('nx.json', (nxJson) => {
|
||||||
nxJson.release.version.generatorOptions.fallbackCurrentVersionResolver =
|
(
|
||||||
'disk';
|
nxJson.release.version as NxReleaseVersionV2Configuration
|
||||||
|
).fallbackCurrentVersionResolver = 'disk';
|
||||||
return nxJson;
|
return nxJson;
|
||||||
});
|
});
|
||||||
|
|
||||||
const releaseOutput5 = runCLI(`release patch --skip-publish`);
|
const releaseOutput5 = runCLI(`release patch --skip-publish`);
|
||||||
|
|
||||||
expect(releaseOutput5).toMatch(
|
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(
|
expect(
|
||||||
releaseOutput5.match(
|
releaseOutput5.match(
|
||||||
new RegExp(
|
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'
|
'g'
|
||||||
)
|
)
|
||||||
).length
|
).length
|
||||||
).toEqual(2);
|
).toEqual(2);
|
||||||
|
|
||||||
updateJson<NxJsonConfiguration>('nx.json', (nxJson) => {
|
updateJson<NxJsonConfiguration>('nx.json', (nxJson) => {
|
||||||
nxJson.release.version.generatorOptions.currentVersionResolver =
|
(
|
||||||
'registry';
|
nxJson.release.version as NxReleaseVersionV2Configuration
|
||||||
nxJson.release.version.generatorOptions.currentVersionResolverMetadata = {
|
).currentVersionResolver = 'registry';
|
||||||
|
(
|
||||||
|
nxJson.release.version as NxReleaseVersionV2Configuration
|
||||||
|
).currentVersionResolverMetadata = {
|
||||||
tag: 'other',
|
tag: 'other',
|
||||||
};
|
};
|
||||||
delete nxJson.release.version.generatorOptions
|
delete (nxJson.release.version as NxReleaseVersionV2Configuration)
|
||||||
.fallbackCurrentVersionResolver;
|
.fallbackCurrentVersionResolver;
|
||||||
return nxJson;
|
return nxJson;
|
||||||
});
|
});
|
||||||
@ -1111,7 +1168,7 @@ describe('nx release', () => {
|
|||||||
expect(
|
expect(
|
||||||
releaseOutput6a.match(
|
releaseOutput6a.match(
|
||||||
new RegExp(
|
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'
|
'g'
|
||||||
)
|
)
|
||||||
).length
|
).length
|
||||||
@ -1125,32 +1182,37 @@ describe('nx release', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
expect(releaseOutput6b).toMatch(
|
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(
|
expect(
|
||||||
releaseOutput6b.match(
|
releaseOutput6b.match(
|
||||||
new RegExp(
|
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'
|
'g'
|
||||||
)
|
)
|
||||||
).length
|
).length
|
||||||
).toEqual(2);
|
).toEqual(2);
|
||||||
|
|
||||||
updateJson<NxJsonConfiguration>('nx.json', (nxJson) => {
|
updateJson<NxJsonConfiguration>('nx.json', (nxJson) => {
|
||||||
nxJson.release.version.generatorOptions.fallbackCurrentVersionResolver =
|
(
|
||||||
'disk';
|
nxJson.release.version as NxReleaseVersionV2Configuration
|
||||||
|
).fallbackCurrentVersionResolver = 'disk';
|
||||||
return nxJson;
|
return nxJson;
|
||||||
});
|
});
|
||||||
|
|
||||||
const releaseOutput7 = runCLI(`release patch --skip-publish --verbose`);
|
const releaseOutput7 = runCLI(`release patch --skip-publish --verbose`);
|
||||||
|
|
||||||
expect(releaseOutput7).toMatch(
|
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(
|
expect(
|
||||||
releaseOutput7.match(
|
releaseOutput7.match(
|
||||||
new RegExp(
|
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'
|
'g'
|
||||||
)
|
)
|
||||||
).length
|
).length
|
||||||
|
|||||||
@ -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.
|
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
|
||||||
|
|
||||||
|
|
||||||
`);
|
`);
|
||||||
|
|
||||||
|
|||||||
@ -92,10 +92,8 @@ describe('nx release version plans only touched', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
version: {
|
version: {
|
||||||
generatorOptions: {
|
|
||||||
specifierSource: 'version-plans',
|
specifierSource: 'version-plans',
|
||||||
},
|
},
|
||||||
},
|
|
||||||
changelog: {
|
changelog: {
|
||||||
projectChangelogs: true,
|
projectChangelogs: true,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -101,10 +101,8 @@ describe('nx release version plans', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
version: {
|
version: {
|
||||||
generatorOptions: {
|
|
||||||
specifierSource: 'version-plans',
|
specifierSource: 'version-plans',
|
||||||
},
|
},
|
||||||
},
|
|
||||||
changelog: {
|
changelog: {
|
||||||
projectChangelogs: true,
|
projectChangelogs: true,
|
||||||
},
|
},
|
||||||
@ -145,21 +143,29 @@ Here is another line in the message.
|
|||||||
silenceError: true,
|
silenceError: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result).toContain(
|
expect(result).toMatch(
|
||||||
`${pkg1} 📄 Resolved the specifier as "minor" using version plans.`
|
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
|
// pkg2 uses the previously resolved specifier from pkg1
|
||||||
expect(result).toContain(
|
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(
|
expect(result).toMatch(
|
||||||
`${pkg3} 📄 Resolved the specifier as "patch" using version plans.`
|
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(
|
expect(result).toMatch(
|
||||||
`${pkg4} 📄 Resolved the specifier as "preminor" using version plans.`
|
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(
|
expect(result).toMatch(
|
||||||
`${pkg5} 📄 Resolved the specifier as "prerelease" using version plans.`
|
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
|
// 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,
|
silenceError: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result2).toContain(
|
expect(result2).toMatch(
|
||||||
`${pkg1} 📄 Resolved the specifier as "minor" using version plans.`
|
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
|
// pkg2 uses the previously resolved specifier from pkg1
|
||||||
expect(result2).toContain(
|
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(
|
expect(result2).toMatch(
|
||||||
`${pkg3} 📄 Resolved the specifier as "patch" using version plans.`
|
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(
|
expect(result2).toMatch(
|
||||||
`${pkg4} 📄 Resolved the specifier as "preminor" using version plans.`
|
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(
|
expect(result2).toMatch(
|
||||||
`${pkg5} 📄 Resolved the specifier as "patch" using version plans.`
|
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
|
// replace the date with a placeholder to make the snapshot deterministic
|
||||||
@ -407,10 +421,8 @@ Update packages in both groups with a mix #2
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
version: {
|
version: {
|
||||||
generatorOptions: {
|
|
||||||
specifierSource: 'version-plans',
|
specifierSource: 'version-plans',
|
||||||
},
|
},
|
||||||
},
|
|
||||||
changelog: {
|
changelog: {
|
||||||
projectChangelogs: true,
|
projectChangelogs: true,
|
||||||
},
|
},
|
||||||
@ -516,21 +528,29 @@ const yargs = require('yargs');
|
|||||||
failOnError: false,
|
failOnError: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result).toContain(
|
expect(result).toMatch(
|
||||||
`${pkg1} 📄 Resolved the specifier as "minor" using version plans.`
|
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
|
// pkg2 uses the previously resolved specifier from pkg1
|
||||||
expect(result).toContain(
|
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(
|
expect(result).toMatch(
|
||||||
`${pkg3} 📄 Resolved the specifier as "patch" using version plans.`
|
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(
|
expect(result).toMatch(
|
||||||
`${pkg4} 📄 Resolved the specifier as "preminor" using version plans.`
|
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(
|
expect(result).toMatch(
|
||||||
`${pkg5} 📄 Resolved the specifier as "prerelease" using version plans.`
|
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
|
// 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,
|
silenceError: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result2).toContain(
|
expect(result2).toMatch(
|
||||||
`${pkg1} 📄 Resolved the specifier as "minor" using version plans.`
|
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
|
// pkg2 uses the previously resolved specifier from pkg1
|
||||||
expect(result2).toContain(
|
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(
|
expect(result2).toMatch(
|
||||||
`${pkg3} 📄 Resolved the specifier as "patch" using version plans.`
|
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(
|
expect(result2).toMatch(
|
||||||
`${pkg4} 📄 Resolved the specifier as "preminor" using version plans.`
|
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(
|
expect(result2).toMatch(
|
||||||
`${pkg5} 📄 Resolved the specifier as "patch" using version plans.`
|
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
|
// 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,
|
silenceError: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(versionResult).toContain(
|
expect(versionResult).toMatch(
|
||||||
`${pkg1} 📄 Resolved the specifier as "minor" using version plans.`
|
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
|
// pkg2 uses the previously resolved specifier from pkg1
|
||||||
expect(versionResult).toContain(
|
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', {
|
const changelogResult = runCLI('release changelog 0.1.0 --verbose', {
|
||||||
@ -845,10 +875,8 @@ Update packages in both groups with a mix #2
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
version: {
|
version: {
|
||||||
generatorOptions: {
|
|
||||||
specifierSource: 'version-plans',
|
specifierSource: 'version-plans',
|
||||||
},
|
},
|
||||||
},
|
|
||||||
changelog: {
|
changelog: {
|
||||||
projectChangelogs: true,
|
projectChangelogs: true,
|
||||||
},
|
},
|
||||||
@ -895,24 +923,34 @@ Update packages in both groups with a mix #2
|
|||||||
expect(versionResult).toContain(
|
expect(versionResult).toContain(
|
||||||
'Skipping version plan discovery as a specifier was provided'
|
'Skipping version plan discovery as a specifier was provided'
|
||||||
);
|
);
|
||||||
expect(versionResult).toContain(
|
expect(versionResult).toMatch(
|
||||||
`${pkg1} 📄 Using the provided version specifier "major".`
|
new RegExp(
|
||||||
|
`${pkg1} ❓ Applied semver relative bump "major", from the given specifier, to get new version 1\\.0\\.0`
|
||||||
|
)
|
||||||
);
|
);
|
||||||
expect(versionResult).toContain(
|
expect(versionResult).toMatch(
|
||||||
`${pkg2} 📄 Using the provided version specifier "major".`
|
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(
|
expect(versionResult).toMatch(
|
||||||
`${pkg3} 📄 Using the provided version specifier "major".`
|
new RegExp(
|
||||||
|
`${pkg3} ❓ Applied semver relative bump "major", from the given specifier, to get new version 1\\.0\\.0`
|
||||||
|
)
|
||||||
);
|
);
|
||||||
expect(versionResult).toContain(
|
expect(versionResult).toMatch(
|
||||||
`${pkg4} 📄 Using the provided version specifier "major".`
|
new RegExp(
|
||||||
|
`${pkg4} ❓ Applied semver relative bump "major", from the given specifier, to get new version 1\\.0\\.0`
|
||||||
|
)
|
||||||
);
|
);
|
||||||
expect(versionResult).toContain(
|
expect(versionResult).toMatch(
|
||||||
`${pkg5} 📄 Using the provided version specifier "major".`
|
new RegExp(
|
||||||
|
`${pkg5} ❓ Applied semver relative bump "major", from the given specifier, to get new version 1\\.0\\.0`
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(versionResult).toContain(
|
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);
|
expect(readdirSync(versionPlansDir).length).toEqual(2);
|
||||||
|
|||||||
@ -70,6 +70,8 @@ export function runCommand(
|
|||||||
cwd: tmpProjPath(),
|
cwd: tmpProjPath(),
|
||||||
stdio: 'pipe',
|
stdio: 'pipe',
|
||||||
env: {
|
env: {
|
||||||
|
// Use new versioning by default in e2e tests
|
||||||
|
NX_INTERNAL_USE_LEGACY_VERSIONING: 'false',
|
||||||
...getStrippedEnvironmentVariables(),
|
...getStrippedEnvironmentVariables(),
|
||||||
...childProcessOptions?.env,
|
...childProcessOptions?.env,
|
||||||
FORCE_COLOR: 'false',
|
FORCE_COLOR: 'false',
|
||||||
@ -235,6 +237,8 @@ export function runCommandAsync(
|
|||||||
cwd: opts.cwd || tmpProjPath(),
|
cwd: opts.cwd || tmpProjPath(),
|
||||||
env: {
|
env: {
|
||||||
CI: 'true',
|
CI: 'true',
|
||||||
|
// Use new versioning by default in e2e tests
|
||||||
|
NX_INTERNAL_USE_LEGACY_VERSIONING: 'false',
|
||||||
...(opts.env || getStrippedEnvironmentVariables()),
|
...(opts.env || getStrippedEnvironmentVariables()),
|
||||||
FORCE_COLOR: 'false',
|
FORCE_COLOR: 'false',
|
||||||
},
|
},
|
||||||
@ -279,6 +283,8 @@ export function runCommandUntil(
|
|||||||
encoding: 'utf-8',
|
encoding: 'utf-8',
|
||||||
env: {
|
env: {
|
||||||
CI: 'true',
|
CI: 'true',
|
||||||
|
// Use new versioning by default in e2e tests
|
||||||
|
NX_INTERNAL_USE_LEGACY_VERSIONING: 'false',
|
||||||
...getStrippedEnvironmentVariables(),
|
...getStrippedEnvironmentVariables(),
|
||||||
...opts.env,
|
...opts.env,
|
||||||
FORCE_COLOR: 'false',
|
FORCE_COLOR: 'false',
|
||||||
@ -392,6 +398,8 @@ export function runCLI(
|
|||||||
cwd: opts.cwd || tmpProjPath(),
|
cwd: opts.cwd || tmpProjPath(),
|
||||||
env: {
|
env: {
|
||||||
CI: 'true',
|
CI: 'true',
|
||||||
|
// Use new versioning by default in e2e tests
|
||||||
|
NX_INTERNAL_USE_LEGACY_VERSIONING: 'false',
|
||||||
...getStrippedEnvironmentVariables(),
|
...getStrippedEnvironmentVariables(),
|
||||||
...opts.env,
|
...opts.env,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -53,6 +53,7 @@
|
|||||||
"@jest/reporters": "29.7.0",
|
"@jest/reporters": "29.7.0",
|
||||||
"@jest/test-result": "29.7.0",
|
"@jest/test-result": "29.7.0",
|
||||||
"@jest/types": "29.6.3",
|
"@jest/types": "29.6.3",
|
||||||
|
"@ltd/j-toml": "^1.38.0",
|
||||||
"@module-federation/enhanced": "^0.9.0",
|
"@module-federation/enhanced": "^0.9.0",
|
||||||
"@module-federation/sdk": "^0.9.0",
|
"@module-federation/sdk": "^0.9.0",
|
||||||
"@monodon/rust": "2.1.1",
|
"@monodon/rust": "2.1.1",
|
||||||
|
|||||||
@ -1,9 +1,14 @@
|
|||||||
import type { Tree } from '@nx/devkit';
|
import type { Tree } from '@nx/devkit';
|
||||||
import { addProjectConfiguration, joinPathFragments } from '@nx/devkit';
|
import {
|
||||||
import type { AngularProjectConfiguration } from '../../../utils/types';
|
addProjectConfiguration,
|
||||||
import type { NormalizedSchema } from './normalized-schema';
|
joinPathFragments,
|
||||||
|
readJson,
|
||||||
|
} from '@nx/devkit';
|
||||||
import { addBuildTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils';
|
import { addBuildTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils';
|
||||||
import { addReleaseConfigForNonTsSolution } from '@nx/js/src/generators/library/utils/add-release-config';
|
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(
|
export async function addProject(
|
||||||
tree: Tree,
|
tree: Tree,
|
||||||
@ -44,7 +49,9 @@ export async function addProject(
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (libraryOptions.publishable) {
|
if (libraryOptions.publishable) {
|
||||||
|
const nxJson = readJson(tree, 'nx.json');
|
||||||
await addReleaseConfigForNonTsSolution(
|
await addReleaseConfigForNonTsSolution(
|
||||||
|
shouldUseLegacyVersioning(nxJson.release),
|
||||||
tree,
|
tree,
|
||||||
libraryOptions.name,
|
libraryOptions.name,
|
||||||
project
|
project
|
||||||
|
|||||||
@ -28,10 +28,12 @@ import {
|
|||||||
import { promptWhenInteractive } from '@nx/devkit/src/generators/prompt';
|
import { promptWhenInteractive } from '@nx/devkit/src/generators/prompt';
|
||||||
import { addBuildTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils';
|
import { addBuildTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils';
|
||||||
import { logShowProjectCommand } from '@nx/devkit/src/utils/log-show-project-command';
|
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 { type PackageJson } from 'nx/src/utils/package-json';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
import type { CompilerOptions } from 'typescript';
|
import type { CompilerOptions } from 'typescript';
|
||||||
import { normalizeLinterOption } from '../../utils/generator-prompts';
|
import { normalizeLinterOption } from '../../utils/generator-prompts';
|
||||||
|
import { sortPackageJsonFields } from '../../utils/package-json/sort-fields';
|
||||||
import { getUpdatedPackageJsonContent } from '../../utils/package-json/update-package-json';
|
import { getUpdatedPackageJsonContent } from '../../utils/package-json/update-package-json';
|
||||||
import { addSwcConfig } from '../../utils/swc/add-swc-config';
|
import { addSwcConfig } from '../../utils/swc/add-swc-config';
|
||||||
import { getSwcDependencies } from '../../utils/swc/add-swc-dependencies';
|
import { getSwcDependencies } from '../../utils/swc/add-swc-dependencies';
|
||||||
@ -63,7 +65,6 @@ import type {
|
|||||||
LibraryGeneratorSchema,
|
LibraryGeneratorSchema,
|
||||||
NormalizedLibraryGeneratorOptions,
|
NormalizedLibraryGeneratorOptions,
|
||||||
} from './schema';
|
} from './schema';
|
||||||
import { sortPackageJsonFields } from '../../utils/package-json/sort-fields';
|
|
||||||
import {
|
import {
|
||||||
addReleaseConfigForNonTsSolution,
|
addReleaseConfigForNonTsSolution,
|
||||||
addReleaseConfigForTsSolution,
|
addReleaseConfigForTsSolution,
|
||||||
@ -264,8 +265,9 @@ async function configureProject(
|
|||||||
tree: Tree,
|
tree: Tree,
|
||||||
options: NormalizedLibraryGeneratorOptions
|
options: NormalizedLibraryGeneratorOptions
|
||||||
) {
|
) {
|
||||||
if (options.hasPlugin) {
|
|
||||||
const nxJson = readNxJson(tree);
|
const nxJson = readNxJson(tree);
|
||||||
|
|
||||||
|
if (options.hasPlugin) {
|
||||||
ensureProjectIsIncludedInPluginRegistrations(
|
ensureProjectIsIncludedInPluginRegistrations(
|
||||||
nxJson,
|
nxJson,
|
||||||
options.projectRoot,
|
options.projectRoot,
|
||||||
@ -343,6 +345,7 @@ async function configureProject(
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
await addReleaseConfigForNonTsSolution(
|
await addReleaseConfigForNonTsSolution(
|
||||||
|
shouldUseLegacyVersioning(nxJson.release),
|
||||||
tree,
|
tree,
|
||||||
options.name,
|
options.name,
|
||||||
projectConfiguration,
|
projectConfiguration,
|
||||||
|
|||||||
@ -12,6 +12,8 @@ import {
|
|||||||
addReleaseConfigForTsSolution,
|
addReleaseConfigForTsSolution,
|
||||||
} from './add-release-config';
|
} from './add-release-config';
|
||||||
|
|
||||||
|
const USE_LEGACY_VERSIONING = true;
|
||||||
|
|
||||||
describe('add release config', () => {
|
describe('add release config', () => {
|
||||||
let tree: Tree;
|
let tree: Tree;
|
||||||
|
|
||||||
@ -23,7 +25,12 @@ describe('add release config', () => {
|
|||||||
describe('addReleaseConfigForNonTsSolution', () => {
|
describe('addReleaseConfigForNonTsSolution', () => {
|
||||||
it('should update the nx-release-publish target to specify dist/{projectRoot} as the package root', async () => {
|
it('should update the nx-release-publish target to specify dist/{projectRoot} as the package root', async () => {
|
||||||
const projectConfig: ProjectConfiguration = { root: 'libs/my-lib' };
|
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({
|
expect(projectConfig.targets?.['nx-release-publish']).toEqual({
|
||||||
options: {
|
options: {
|
||||||
packageRoot: 'dist/{projectRoot}',
|
packageRoot: 'dist/{projectRoot}',
|
||||||
@ -42,7 +49,12 @@ describe('add release config', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const projectConfig = { root: 'libs/my-lib' };
|
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');
|
const nxJson = readJson(tree, 'nx.json');
|
||||||
expect(nxJson.release).toEqual({
|
expect(nxJson.release).toEqual({
|
||||||
@ -59,7 +71,12 @@ describe('add release config', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const projectConfig = { root: 'libs/my-lib' };
|
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');
|
const nxJson = readJson(tree, 'nx.json');
|
||||||
expect(nxJson.release).toEqual({
|
expect(nxJson.release).toEqual({
|
||||||
@ -86,7 +103,12 @@ describe('add release config', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const projectConfig = { root: 'libs/my-lib' };
|
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');
|
const nxJson = readJson(tree, 'nx.json');
|
||||||
expect(nxJson.release).toEqual({
|
expect(nxJson.release).toEqual({
|
||||||
@ -109,7 +131,12 @@ describe('add release config', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const projectConfig = { root: 'libs/my-lib' };
|
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');
|
const nxJson = readJson(tree, 'nx.json');
|
||||||
expect(nxJson.release).toEqual({
|
expect(nxJson.release).toEqual({
|
||||||
@ -131,7 +158,12 @@ describe('add release config', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const projectConfig = { root: 'libs/my-lib' };
|
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');
|
const nxJson = readJson(tree, 'nx.json');
|
||||||
expect(nxJson.release).toEqual({
|
expect(nxJson.release).toEqual({
|
||||||
@ -156,7 +188,12 @@ describe('add release config', () => {
|
|||||||
root: 'libs/my-lib',
|
root: 'libs/my-lib',
|
||||||
tags: ['one', 'two'],
|
tags: ['one', 'two'],
|
||||||
};
|
};
|
||||||
await addReleaseConfigForNonTsSolution(tree, 'my-lib', projectConfig);
|
await addReleaseConfigForNonTsSolution(
|
||||||
|
USE_LEGACY_VERSIONING,
|
||||||
|
tree,
|
||||||
|
'my-lib',
|
||||||
|
projectConfig
|
||||||
|
);
|
||||||
|
|
||||||
const nxJson = readJson(tree, 'nx.json');
|
const nxJson = readJson(tree, 'nx.json');
|
||||||
expect(nxJson.release).toEqual({
|
expect(nxJson.release).toEqual({
|
||||||
@ -178,7 +215,12 @@ describe('add release config', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const projectConfig = { root: 'packages/my-lib' };
|
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');
|
const nxJson = readJson(tree, 'nx.json');
|
||||||
expect(nxJson.release).toEqual({
|
expect(nxJson.release).toEqual({
|
||||||
@ -200,7 +242,12 @@ describe('add release config', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const projectConfig = { root: 'libs/my-lib' };
|
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');
|
const nxJson = readJson(tree, 'nx.json');
|
||||||
expect(nxJson.release).toEqual({
|
expect(nxJson.release).toEqual({
|
||||||
@ -222,7 +269,12 @@ describe('add release config', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const projectConfig = { root: 'libs/my-lib' };
|
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');
|
const nxJson = readJson(tree, 'nx.json');
|
||||||
expect(nxJson.release).toEqual({
|
expect(nxJson.release).toEqual({
|
||||||
@ -252,7 +304,12 @@ describe('add release config', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const projectConfig = { root: 'libs/my-lib' };
|
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');
|
const nxJson = readJson(tree, 'nx.json');
|
||||||
expect(nxJson.release).toEqual({
|
expect(nxJson.release).toEqual({
|
||||||
@ -288,7 +345,12 @@ describe('add release config', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const projectConfig = { root: 'libs/my-lib' };
|
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');
|
const nxJson = readJson(tree, 'nx.json');
|
||||||
expect(nxJson.release).toEqual({
|
expect(nxJson.release).toEqual({
|
||||||
|
|||||||
@ -114,6 +114,7 @@ export async function addReleaseConfigForTsSolution(
|
|||||||
* Add release option in project.json and add packageRoot to nx-release-publish target
|
* Add release option in project.json and add packageRoot to nx-release-publish target
|
||||||
*/
|
*/
|
||||||
export async function addReleaseConfigForNonTsSolution(
|
export async function addReleaseConfigForNonTsSolution(
|
||||||
|
useLegacyVersioning: boolean,
|
||||||
tree: Tree,
|
tree: Tree,
|
||||||
projectName: string,
|
projectName: string,
|
||||||
projectConfiguration: ProjectConfiguration,
|
projectConfiguration: ProjectConfiguration,
|
||||||
@ -131,6 +132,7 @@ export async function addReleaseConfigForNonTsSolution(
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (useLegacyVersioning) {
|
||||||
projectConfiguration.release = {
|
projectConfiguration.release = {
|
||||||
version: {
|
version: {
|
||||||
generatorOptions: {
|
generatorOptions: {
|
||||||
@ -142,6 +144,18 @@ export async function addReleaseConfigForNonTsSolution(
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
} 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);
|
await addReleaseConfigForTsSolution(tree, projectName, projectConfiguration);
|
||||||
|
|
||||||
|
|||||||
@ -102,8 +102,9 @@ describe('release-version-workspace-root-project', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO This will not pass until NXC-573 is resolved
|
// NOTE: The ported version of this test passes in "versioning v2"
|
||||||
// Until then, this test will error because the version generator is incorrectly
|
//
|
||||||
|
// 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.
|
// 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 () => {
|
it.skip('should not error when run with custom packageRoot containing {projectRoot}', async () => {
|
||||||
projectGraph = createWorkspaceWithPackageDependencies(tree, {
|
projectGraph = createWorkspaceWithPackageDependencies(tree, {
|
||||||
|
|||||||
@ -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(
|
function createReleaseGroup(
|
||||||
|
|||||||
@ -33,10 +33,11 @@ import {
|
|||||||
VersionData,
|
VersionData,
|
||||||
deriveNewSemverVersion,
|
deriveNewSemverVersion,
|
||||||
validReleaseVersionPrefixes,
|
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 { interpolate } from 'nx/src/tasks-runner/utils';
|
||||||
import * as ora from 'ora';
|
import * as ora from 'ora';
|
||||||
import { ReleaseType, gt, inc, prerelease } from 'semver';
|
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 { isLocallyLinkedPackageVersion } from '../../utils/is-locally-linked-package-version';
|
||||||
import { parseRegistryOptions } from '../../utils/npm-config';
|
import { parseRegistryOptions } from '../../utils/npm-config';
|
||||||
import { ReleaseVersionGeneratorSchema } from './schema';
|
import { ReleaseVersionGeneratorSchema } from './schema';
|
||||||
@ -45,7 +46,6 @@ import {
|
|||||||
resolveLocalPackageDependencies,
|
resolveLocalPackageDependencies,
|
||||||
} from './utils/resolve-local-package-dependencies';
|
} from './utils/resolve-local-package-dependencies';
|
||||||
import { sortProjectsTopologically } from './utils/sort-projects-topologically';
|
import { sortProjectsTopologically } from './utils/sort-projects-topologically';
|
||||||
import { updateLockFile } from './utils/update-lock-file';
|
|
||||||
|
|
||||||
function resolvePreIdSpecifier(currentSpecifier: string, preid?: string) {
|
function resolvePreIdSpecifier(currentSpecifier: string, preid?: string) {
|
||||||
if (!currentSpecifier.startsWith('pre') && preid) {
|
if (!currentSpecifier.startsWith('pre') && preid) {
|
||||||
@ -103,7 +103,6 @@ Valid values are: ${validReleaseVersionPrefixes
|
|||||||
) as ReleaseType;
|
) as ReleaseType;
|
||||||
|
|
||||||
// Sort the projects topologically if update dependents is enabled
|
// Sort the projects topologically if update dependents is enabled
|
||||||
// TODO: maybe move this sorting to the command level?
|
|
||||||
const projects =
|
const projects =
|
||||||
updateDependents === 'never' ||
|
updateDependents === 'never' ||
|
||||||
options.releaseGroup.projectsRelationship !== 'independent'
|
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;
|
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.
|
// 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.
|
// Users must manually graduate from a prerelease to a release by providing an explicit specifier.
|
||||||
if (prerelease(currentVersion ?? '')) {
|
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;
|
const cwd = tree.root;
|
||||||
changedFiles.push(...(await updateLockFile(cwd, opts)));
|
changedFiles.push(
|
||||||
|
...(await updateLockFile(cwd, {
|
||||||
|
...opts,
|
||||||
|
useLegacyVersioning: true,
|
||||||
|
}))
|
||||||
|
);
|
||||||
return { changedFiles, deletedFiles };
|
return { changedFiles, deletedFiles };
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -1044,7 +1046,8 @@ function createResolvePackageRoot(customPackageRoot?: string) {
|
|||||||
return projectNode.data.root;
|
return projectNode.data.root;
|
||||||
}
|
}
|
||||||
if (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 projectNode.data.root;
|
||||||
}
|
}
|
||||||
return interpolate(customPackageRoot, {
|
return interpolate(customPackageRoot, {
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
export { ReleaseVersionGeneratorSchema } from 'nx/src/command-line/release/version';
|
export { ReleaseVersionGeneratorSchema } from 'nx/src/command-line/release/version-legacy';
|
||||||
|
|||||||
@ -8,6 +8,8 @@
|
|||||||
"properties": {
|
"properties": {
|
||||||
"projects": {
|
"projects": {
|
||||||
"type": "array",
|
"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.",
|
"description": "The ProjectGraphProjectNodes being versioned in the current execution.",
|
||||||
"items": {
|
"items": {
|
||||||
"type": "object"
|
"type": "object"
|
||||||
@ -15,16 +17,33 @@
|
|||||||
},
|
},
|
||||||
"projectGraph": {
|
"projectGraph": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
"hidden": true,
|
||||||
|
"$comment": "This is not configured/passed by the user. It is provided by the version command.",
|
||||||
"description": "ProjectGraph instance"
|
"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": {
|
"specifier": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Exact version or semver keyword to apply to the selected release group. Overrides specifierSource."
|
"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": {
|
"specifierSource": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"default": "prompt",
|
"default": "prompt",
|
||||||
|
|||||||
@ -16,14 +16,20 @@ export async function updateLockFile(
|
|||||||
{
|
{
|
||||||
dryRun,
|
dryRun,
|
||||||
verbose,
|
verbose,
|
||||||
generatorOptions,
|
useLegacyVersioning,
|
||||||
|
options,
|
||||||
}: {
|
}: {
|
||||||
dryRun?: boolean;
|
dryRun?: boolean;
|
||||||
verbose?: boolean;
|
verbose?: boolean;
|
||||||
generatorOptions?: Record<string, unknown>;
|
useLegacyVersioning?: boolean;
|
||||||
|
options?: {
|
||||||
|
skipLockFileUpdate?: boolean;
|
||||||
|
installArgs?: string;
|
||||||
|
installIgnoreScripts?: boolean;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
if (generatorOptions?.skipLockFileUpdate) {
|
if (options?.skipLockFileUpdate) {
|
||||||
if (verbose) {
|
if (verbose) {
|
||||||
console.log(
|
console.log(
|
||||||
'\nSkipped lock file update because skipLockFileUpdate was set.'
|
'\nSkipped lock file update because skipLockFileUpdate was set.'
|
||||||
@ -65,13 +71,13 @@ export async function updateLockFile(
|
|||||||
|
|
||||||
const packageManagerCommands = getPackageManagerCommand(packageManager);
|
const packageManagerCommands = getPackageManagerCommand(packageManager);
|
||||||
|
|
||||||
let installArgs = generatorOptions?.installArgs || '';
|
let installArgs = options?.installArgs || '';
|
||||||
|
|
||||||
output.logSingleLine(`Updating ${packageManager} lock file`);
|
output.logSingleLine(`Updating ${packageManager} lock file`);
|
||||||
|
|
||||||
let env: object = {};
|
let env: object = {};
|
||||||
|
|
||||||
if (generatorOptions?.installIgnoreScripts) {
|
if (options?.installIgnoreScripts) {
|
||||||
if (packageManager === 'yarn') {
|
if (packageManager === 'yarn') {
|
||||||
env = { YARN_ENABLE_SCRIPTS: 'false' };
|
env = { YARN_ENABLE_SCRIPTS: 'false' };
|
||||||
} else {
|
} else {
|
||||||
@ -99,7 +105,7 @@ export async function updateLockFile(
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
execLockFileUpdate(command, cwd, env);
|
execLockFileUpdate(command, cwd, env, useLegacyVersioning);
|
||||||
|
|
||||||
if (isDaemonEnabled) {
|
if (isDaemonEnabled) {
|
||||||
try {
|
try {
|
||||||
@ -122,7 +128,8 @@ export async function updateLockFile(
|
|||||||
function execLockFileUpdate(
|
function execLockFileUpdate(
|
||||||
command: string,
|
command: string,
|
||||||
cwd: string,
|
cwd: string,
|
||||||
env: object = {}
|
env: object,
|
||||||
|
useLegacyVersioning: boolean
|
||||||
): void {
|
): void {
|
||||||
try {
|
try {
|
||||||
const LARGE_BUFFER = 1024 * 1000000;
|
const LARGE_BUFFER = 1024 * 1000000;
|
||||||
@ -136,13 +143,17 @@ function execLockFileUpdate(
|
|||||||
windowsHide: false,
|
windowsHide: false,
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
const configPathStart = useLegacyVersioning
|
||||||
|
? 'release.version.generatorOptions'
|
||||||
|
: 'release.version.versionActionsOptions';
|
||||||
|
|
||||||
output.error({
|
output.error({
|
||||||
title: `Error updating lock file with command '${command}'`,
|
title: `Error updating lock file with command '${command}'`,
|
||||||
bodyLines: [
|
bodyLines: [
|
||||||
`Verify that '${command}' succeeds when run from the workspace root.`,
|
`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 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 'release.version.generatorOptions.installIgnoreScripts' to true in nx.json.`,
|
`To ignore install lifecycle scripts, set '${configPathStart}.installIgnoreScripts' to true in nx.json.`,
|
||||||
`To disable this step entirely, set 'release.version.generatorOptions.skipLockFileUpdate' to true in nx.json.`,
|
`To disable this step entirely, set '${configPathStart}.skipLockFileUpdate' to true in nx.json.`,
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
throw e;
|
throw e;
|
||||||
273
packages/js/src/release/version-actions.ts
Normal file
273
packages/js/src/release/version-actions.ts
Normal file
@ -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<string, unknown>;
|
||||||
|
}
|
||||||
|
) => {
|
||||||
|
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<string>((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<boolean> {
|
||||||
|
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<string[]> {
|
||||||
|
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<string, string>
|
||||||
|
): Promise<string[]> {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -134,7 +134,9 @@
|
|||||||
"@nx/key",
|
"@nx/key",
|
||||||
// Powerpack plugin conditionally available dynamically at runtime
|
// Powerpack plugin conditionally available dynamically at runtime
|
||||||
"@nx/powerpack-conformance",
|
"@nx/powerpack-conformance",
|
||||||
"@nx/conformance"
|
"@nx/conformance",
|
||||||
|
// Only used in test-utils at the time of writing
|
||||||
|
"@ltd/j-toml"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@ -7,4 +7,6 @@ export {
|
|||||||
releaseChangelog,
|
releaseChangelog,
|
||||||
releasePublish,
|
releasePublish,
|
||||||
releaseVersion,
|
releaseVersion,
|
||||||
|
VersionActions,
|
||||||
|
AfterAllProjectsVersioned,
|
||||||
} from '../src/command-line/release';
|
} from '../src/command-line/release';
|
||||||
|
|||||||
@ -684,50 +684,150 @@
|
|||||||
},
|
},
|
||||||
"NxReleaseVersionConfiguration": {
|
"NxReleaseVersionConfiguration": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
"$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": {
|
"properties": {
|
||||||
|
"useLegacyVersioning": {
|
||||||
|
"const": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"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": {
|
"conventionalCommits": {
|
||||||
"type": "boolean",
|
"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.",
|
"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
|
"default": false
|
||||||
},
|
},
|
||||||
"generator": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"generatorOptions": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": true
|
|
||||||
},
|
|
||||||
"git": {
|
"git": {
|
||||||
"$ref": "#/definitions/NxReleaseGitConfiguration"
|
"$ref": "#/definitions/NxReleaseGitConfiguration"
|
||||||
},
|
},
|
||||||
"preVersionCommand": {
|
"preVersionCommand": {
|
||||||
"type": "string",
|
"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'."
|
"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
|
"additionalProperties": false
|
||||||
},
|
},
|
||||||
"NxReleaseGroupVersionConfiguration": {
|
"else": {
|
||||||
"type": "object",
|
"additionalProperties": false,
|
||||||
"properties": {
|
"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": {
|
"conventionalCommits": {
|
||||||
"type": "boolean",
|
"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.",
|
"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
|
"default": false
|
||||||
},
|
},
|
||||||
"generator": {
|
"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"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"generatorOptions": {
|
"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."
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": true
|
|
||||||
},
|
},
|
||||||
|
"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": {
|
||||||
"groupPreVersionCommand": {
|
"groupPreVersionCommand": {
|
||||||
"type": "string",
|
"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'."
|
"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": {
|
"NxReleaseChangelogConfiguration": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
|||||||
@ -4,11 +4,7 @@ import { readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|||||||
import { ReleaseType, valid } from 'semver';
|
import { ReleaseType, valid } from 'semver';
|
||||||
import { dirSync } from 'tmp';
|
import { dirSync } from 'tmp';
|
||||||
import type { DependencyBump } from '../../../release/changelog-renderer';
|
import type { DependencyBump } from '../../../release/changelog-renderer';
|
||||||
import {
|
import { NxReleaseConfiguration, readNxJson } from '../../config/nx-json';
|
||||||
NxReleaseChangelogConfiguration,
|
|
||||||
NxReleaseConfiguration,
|
|
||||||
readNxJson,
|
|
||||||
} from '../../config/nx-json';
|
|
||||||
import {
|
import {
|
||||||
FileData,
|
FileData,
|
||||||
ProjectFileMap,
|
ProjectFileMap,
|
||||||
@ -22,9 +18,9 @@ import {
|
|||||||
} from '../../project-graph/file-map-utils';
|
} from '../../project-graph/file-map-utils';
|
||||||
import { createProjectGraphAsync } from '../../project-graph/project-graph';
|
import { createProjectGraphAsync } from '../../project-graph/project-graph';
|
||||||
import { interpolate } from '../../tasks-runner/utils';
|
import { interpolate } from '../../tasks-runner/utils';
|
||||||
|
import { handleErrors } from '../../utils/handle-errors';
|
||||||
import { isCI } from '../../utils/is-ci';
|
import { isCI } from '../../utils/is-ci';
|
||||||
import { output } from '../../utils/output';
|
import { output } from '../../utils/output';
|
||||||
import { handleErrors } from '../../utils/handle-errors';
|
|
||||||
import { joinPathFragments } from '../../utils/path';
|
import { joinPathFragments } from '../../utils/path';
|
||||||
import { workspaceRoot } from '../../utils/workspace-root';
|
import { workspaceRoot } from '../../utils/workspace-root';
|
||||||
import { ChangelogOptions } from './command-object';
|
import { ChangelogOptions } from './command-object';
|
||||||
@ -39,6 +35,7 @@ import {
|
|||||||
ReleaseGroupWithName,
|
ReleaseGroupWithName,
|
||||||
filterReleaseGroups,
|
filterReleaseGroups,
|
||||||
} from './config/filter-release-groups';
|
} from './config/filter-release-groups';
|
||||||
|
import { shouldUseLegacyVersioning } from './config/use-legacy-versioning';
|
||||||
import {
|
import {
|
||||||
GroupVersionPlan,
|
GroupVersionPlan,
|
||||||
ProjectsVersionPlan,
|
ProjectsVersionPlan,
|
||||||
@ -129,7 +126,13 @@ export function createAPI(overrideReleaseConfig: NxReleaseConfiguration) {
|
|||||||
userProvidedReleaseConfig
|
userProvidedReleaseConfig
|
||||||
);
|
);
|
||||||
if (configError) {
|
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
|
// --print-config exits directly as it is not designed to be combined with any other programmatic operations
|
||||||
if (args.printConfig) {
|
if (args.printConfig) {
|
||||||
@ -275,7 +278,7 @@ export function createAPI(overrideReleaseConfig: NxReleaseConfiguration) {
|
|||||||
const postGitTasks: PostGitTask[] = [];
|
const postGitTasks: PostGitTask[] = [];
|
||||||
|
|
||||||
let workspaceChangelogChanges: ChangelogChange[] = [];
|
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[] = [];
|
let workspaceChangelogCommits: GitCommit[] = [];
|
||||||
|
|
||||||
// If there are multiple release groups, we'll just skip the workspace changelog anyway.
|
// If there are multiple release groups, we'll just skip the workspace changelog anyway.
|
||||||
@ -393,7 +396,7 @@ export function createAPI(overrideReleaseConfig: NxReleaseConfiguration) {
|
|||||||
nxReleaseConfig,
|
nxReleaseConfig,
|
||||||
workspaceChangelogVersion,
|
workspaceChangelogVersion,
|
||||||
changes: workspaceChangelogChanges,
|
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(
|
commits: filterHiddenCommits(
|
||||||
workspaceChangelogCommits,
|
workspaceChangelogCommits,
|
||||||
nxReleaseConfig.conventionalCommits
|
nxReleaseConfig.conventionalCommits
|
||||||
@ -449,7 +452,7 @@ export function createAPI(overrideReleaseConfig: NxReleaseConfiguration) {
|
|||||||
.map((dep) => {
|
.map((dep) => {
|
||||||
return {
|
return {
|
||||||
dependencyName: dep.source,
|
dependencyName: dep.source,
|
||||||
newVersion: projectsVersionData[dep.source].newVersion,
|
newVersion: projectsVersionData[dep.source]?.newVersion ?? null,
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.filter((b) => b.newVersion !== null);
|
.filter((b) => b.newVersion !== null);
|
||||||
@ -500,7 +503,7 @@ export function createAPI(overrideReleaseConfig: NxReleaseConfiguration) {
|
|||||||
if (releaseGroup.projectsRelationship === 'independent') {
|
if (releaseGroup.projectsRelationship === 'independent') {
|
||||||
for (const project of projectNodes) {
|
for (const project of projectNodes) {
|
||||||
let changes: ChangelogChange[] | null = null;
|
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[];
|
let commits: GitCommit[];
|
||||||
|
|
||||||
if (releaseGroup.resolvedVersionPlans) {
|
if (releaseGroup.resolvedVersionPlans) {
|
||||||
@ -646,7 +649,7 @@ export function createAPI(overrideReleaseConfig: NxReleaseConfiguration) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let changes: ChangelogChange[] = [];
|
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[] = [];
|
let commits: GitCommit[] = [];
|
||||||
if (releaseGroup.resolvedVersionPlans) {
|
if (releaseGroup.resolvedVersionPlans) {
|
||||||
changes = (releaseGroup.resolvedVersionPlans as GroupVersionPlan[])
|
changes = (releaseGroup.resolvedVersionPlans as GroupVersionPlan[])
|
||||||
@ -994,11 +997,15 @@ async function applyChangesAndExit(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (args.gitPush ?? nxReleaseConfig.changelog.git.push) {
|
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({
|
await gitPush({
|
||||||
gitRemote: args.gitRemote,
|
gitRemote: args.gitRemote,
|
||||||
dryRun: args.dryRun,
|
dryRun: args.dryRun,
|
||||||
verbose: args.verbose,
|
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(
|
function filterHiddenCommits(
|
||||||
commits: GitCommit[],
|
commits: GitCommit[],
|
||||||
conventionalCommitsConfig: NxReleaseConfig['conventionalCommits']
|
conventionalCommitsConfig: NxReleaseConfig['conventionalCommits']
|
||||||
@ -1414,7 +1421,9 @@ async function promptForGitHubRelease(): Promise<boolean> {
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
return result.confirmation;
|
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
|
// Handle the case where the user exits the prompt with ctrl+c
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -34,6 +34,7 @@ interface GitOptions {
|
|||||||
gitTagMessage?: string;
|
gitTagMessage?: string;
|
||||||
gitTagArgs?: string | string[];
|
gitTagArgs?: string | string[];
|
||||||
gitPush?: boolean;
|
gitPush?: boolean;
|
||||||
|
gitPushArgs?: string | string[];
|
||||||
gitRemote?: string;
|
gitRemote?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -459,6 +460,11 @@ function withGitOptions<T>(yargs: Argv<T>): Argv<T & GitOptions> {
|
|||||||
'Whether or not to automatically push the changes made by this command to the remote git repository.',
|
'Whether or not to automatically push the changes made by this command to the remote git repository.',
|
||||||
type: 'boolean',
|
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', {
|
.option('git-remote', {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
description:
|
description:
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -14,19 +14,24 @@
|
|||||||
import { join, relative } from 'node:path';
|
import { join, relative } from 'node:path';
|
||||||
import { URL } from 'node:url';
|
import { URL } from 'node:url';
|
||||||
import {
|
import {
|
||||||
|
LegacyNxReleaseVersionConfiguration,
|
||||||
NxJsonConfiguration,
|
NxJsonConfiguration,
|
||||||
NxReleaseChangelogConfiguration,
|
NxReleaseChangelogConfiguration,
|
||||||
|
NxReleaseConfiguration,
|
||||||
|
NxReleaseGitConfiguration,
|
||||||
|
NxReleaseVersionV2Configuration,
|
||||||
} from '../../../config/nx-json';
|
} from '../../../config/nx-json';
|
||||||
import { ProjectFileMap, ProjectGraph } from '../../../config/project-graph';
|
import { ProjectFileMap, ProjectGraph } from '../../../config/project-graph';
|
||||||
import { readJsonFile } from '../../../utils/fileutils';
|
import { readJsonFile } from '../../../utils/fileutils';
|
||||||
import { findMatchingProjects } from '../../../utils/find-matching-projects';
|
import { findMatchingProjects } from '../../../utils/find-matching-projects';
|
||||||
import { output } from '../../../utils/output';
|
import { output } from '../../../utils/output';
|
||||||
import { PackageJson } from '../../../utils/package-json';
|
import { PackageJson } from '../../../utils/package-json';
|
||||||
import { workspaceRoot } from '../../../utils/workspace-root';
|
|
||||||
import { normalizePath } from '../../../utils/path';
|
import { normalizePath } from '../../../utils/path';
|
||||||
|
import { workspaceRoot } from '../../../utils/workspace-root';
|
||||||
import { resolveChangelogRenderer } from '../utils/resolve-changelog-renderer';
|
import { resolveChangelogRenderer } from '../utils/resolve-changelog-renderer';
|
||||||
import { resolveNxJsonConfigErrorMessage } from '../utils/resolve-nx-json-error-message';
|
import { resolveNxJsonConfigErrorMessage } from '../utils/resolve-nx-json-error-message';
|
||||||
import { DEFAULT_CONVENTIONAL_COMMITS_CONFIG } from './conventional-commits';
|
import { DEFAULT_CONVENTIONAL_COMMITS_CONFIG } from './conventional-commits';
|
||||||
|
import { shouldUseLegacyVersioning } from './use-legacy-versioning';
|
||||||
|
|
||||||
type DeepRequired<T> = Required<{
|
type DeepRequired<T> = Required<{
|
||||||
[K in keyof T]: T[K] extends Required<T[K]> ? T[K] : DeepRequired<T[K]>;
|
[K in keyof T]: T[K] extends Required<T[K]> ? T[K] : DeepRequired<T[K]>;
|
||||||
@ -55,6 +60,9 @@ type RemoveBooleanFromPropertiesOnEach<T, K extends keyof T[keyof T]> = {
|
|||||||
|
|
||||||
export const IMPLICIT_DEFAULT_RELEASE_GROUP = '__default__';
|
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
|
* 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
|
* 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<
|
export type NxReleaseConfig = Omit<
|
||||||
DeepRequired<
|
DeepRequired<
|
||||||
NxJsonConfiguration['release'] & {
|
NxReleaseConfiguration & {
|
||||||
groups: DeepRequired<
|
groups: DeepRequired<
|
||||||
RemoveTrueFromPropertiesOnEach<
|
RemoveTrueFromPropertiesOnEach<
|
||||||
EnsureProjectsArray<NxJsonConfiguration['release']['groups']>,
|
EnsureProjectsArray<NxReleaseConfiguration['groups']>,
|
||||||
'changelog'
|
'changelog'
|
||||||
>
|
>
|
||||||
>;
|
>;
|
||||||
// Remove the true shorthand from the changelog config types, it will be normalized to a default object
|
// Remove the true shorthand from the changelog config types, it will be normalized to a default object
|
||||||
changelog: RemoveTrueFromProperties<
|
changelog: RemoveTrueFromProperties<
|
||||||
DeepRequired<NxJsonConfiguration['release']['changelog']>,
|
DeepRequired<NxReleaseConfiguration['changelog']>,
|
||||||
'workspaceChangelog' | 'projectChangelogs'
|
'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
|
// 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<
|
DeepRequired<
|
||||||
RemoveBooleanFromProperties<
|
RemoveBooleanFromProperties<
|
||||||
DeepRequired<
|
DeepRequired<
|
||||||
NxJsonConfiguration['release']['conventionalCommits']['types']
|
NxReleaseConfiguration['conventionalCommits']['types']
|
||||||
>,
|
>,
|
||||||
string
|
string
|
||||||
>
|
>
|
||||||
@ -105,7 +113,7 @@ export interface CreateNxReleaseConfigError {
|
|||||||
| 'RELEASE_GROUP_MATCHES_NO_PROJECTS'
|
| 'RELEASE_GROUP_MATCHES_NO_PROJECTS'
|
||||||
| 'RELEASE_GROUP_RELEASE_TAG_PATTERN_VERSION_PLACEHOLDER_MISSING_OR_EXCESSIVE'
|
| 'RELEASE_GROUP_RELEASE_TAG_PATTERN_VERSION_PLACEHOLDER_MISSING_OR_EXCESSIVE'
|
||||||
| 'PROJECT_MATCHES_MULTIPLE_GROUPS'
|
| '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'
|
| 'GLOBAL_GIT_CONFIG_MIXED_WITH_GRANULAR_GIT_CONFIG'
|
||||||
| 'CANNOT_RESOLVE_CHANGELOG_RENDERER'
|
| 'CANNOT_RESOLVE_CHANGELOG_RENDERER'
|
||||||
| 'INVALID_CHANGELOG_CREATE_RELEASE_PROVIDER'
|
| 'INVALID_CHANGELOG_CREATE_RELEASE_PROVIDER'
|
||||||
@ -147,13 +155,15 @@ export async function createNxReleaseConfig(
|
|||||||
if (hasInvalidConventionalCommitsConfig(userConfig)) {
|
if (hasInvalidConventionalCommitsConfig(userConfig)) {
|
||||||
return {
|
return {
|
||||||
error: {
|
error: {
|
||||||
code: 'CONVENTIONAL_COMMITS_SHORTHAND_MIXED_WITH_OVERLAPPING_GENERATOR_OPTIONS',
|
code: 'CONVENTIONAL_COMMITS_SHORTHAND_MIXED_WITH_OVERLAPPING_OPTIONS',
|
||||||
data: {},
|
data: {},
|
||||||
},
|
},
|
||||||
nxReleaseConfig: null,
|
nxReleaseConfig: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const USE_LEGACY_VERSIONING = shouldUseLegacyVersioning(userConfig);
|
||||||
|
|
||||||
const gitDefaults = {
|
const gitDefaults = {
|
||||||
commit: false,
|
commit: false,
|
||||||
commitMessage: 'chore(release): publish {version}',
|
commitMessage: 'chore(release): publish {version}',
|
||||||
@ -163,6 +173,7 @@ export async function createNxReleaseConfig(
|
|||||||
tagArgs: '',
|
tagArgs: '',
|
||||||
stageChanges: false,
|
stageChanges: false,
|
||||||
push: false,
|
push: false,
|
||||||
|
pushArgs: '',
|
||||||
};
|
};
|
||||||
const versionGitDefaults = {
|
const versionGitDefaults = {
|
||||||
...gitDefaults,
|
...gitDefaults,
|
||||||
@ -259,12 +270,41 @@ export async function createNxReleaseConfig(
|
|||||||
projectsRelationship: workspaceProjectsRelationship,
|
projectsRelationship: workspaceProjectsRelationship,
|
||||||
git: gitDefaults,
|
git: gitDefaults,
|
||||||
version: {
|
version: {
|
||||||
|
useLegacyVersioning: USE_LEGACY_VERSIONING,
|
||||||
git: versionGitDefaults,
|
git: versionGitDefaults,
|
||||||
conventionalCommits: userConfig.version?.conventionalCommits || false,
|
conventionalCommits: userConfig.version?.conventionalCommits || false,
|
||||||
|
preVersionCommand: userConfig.version?.preVersionCommand || '',
|
||||||
|
...(USE_LEGACY_VERSIONING
|
||||||
|
? {
|
||||||
generator: '@nx/js:release-version',
|
generator: '@nx/js:release-version',
|
||||||
generatorOptions: defaultGeneratorOptions,
|
generatorOptions: defaultGeneratorOptions,
|
||||||
preVersionCommand: userConfig.version?.preVersionCommand || '',
|
}
|
||||||
},
|
: {
|
||||||
|
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<NxReleaseConfiguration['version']>,
|
||||||
changelog: {
|
changelog: {
|
||||||
git: changelogGitDefaults,
|
git: changelogGitDefaults,
|
||||||
workspaceChangelog: disableWorkspaceChangelog
|
workspaceChangelog: disableWorkspaceChangelog
|
||||||
@ -318,12 +358,23 @@ export async function createNxReleaseConfig(
|
|||||||
|
|
||||||
const GROUP_DEFAULTS: Omit<NxReleaseConfig['groups'][string], 'projects'> = {
|
const GROUP_DEFAULTS: Omit<NxReleaseConfig['groups'][string], 'projects'> = {
|
||||||
projectsRelationship: groupProjectsRelationship,
|
projectsRelationship: groupProjectsRelationship,
|
||||||
version: {
|
version: USE_LEGACY_VERSIONING
|
||||||
|
? ({
|
||||||
conventionalCommits: false,
|
conventionalCommits: false,
|
||||||
generator: '@nx/js:release-version',
|
generator: '@nx/js:release-version',
|
||||||
generatorOptions: {},
|
generatorOptions: {},
|
||||||
groupPreVersionCommand: '',
|
groupPreVersionCommand: '',
|
||||||
},
|
} as DeepRequired<
|
||||||
|
NxReleaseConfiguration['groups']['string']['version']
|
||||||
|
>)
|
||||||
|
: ({
|
||||||
|
conventionalCommits: false,
|
||||||
|
versionActions: DEFAULT_VERSION_ACTIONS_PATH,
|
||||||
|
versionActionsOptions: {},
|
||||||
|
groupPreVersionCommand: '',
|
||||||
|
} as DeepRequired<
|
||||||
|
NxReleaseConfiguration['groups']['string']['version']
|
||||||
|
>),
|
||||||
changelog: {
|
changelog: {
|
||||||
createRelease: false,
|
createRelease: false,
|
||||||
entryWhenNoChanges:
|
entryWhenNoChanges:
|
||||||
@ -359,7 +410,9 @@ export async function createNxReleaseConfig(
|
|||||||
[
|
[
|
||||||
WORKSPACE_DEFAULTS.version,
|
WORKSPACE_DEFAULTS.version,
|
||||||
// Merge in the git defaults from the top level
|
// Merge in the git defaults from the top level
|
||||||
{ git: versionGitDefaults } as NxReleaseConfig['version'],
|
{
|
||||||
|
git: versionGitDefaults,
|
||||||
|
} as NxReleaseConfig['version'],
|
||||||
{
|
{
|
||||||
git: userConfig.git as Partial<NxReleaseConfig['git']>,
|
git: userConfig.git as Partial<NxReleaseConfig['git']>,
|
||||||
} as NxReleaseConfig['version'],
|
} 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
|
// these options are not supported at the group level, only the root/command level
|
||||||
const rootVersionWithoutGlobalOptions = {
|
let rootVersionWithoutGlobalOptions = {
|
||||||
...rootVersionConfig,
|
...rootVersionConfig,
|
||||||
};
|
} as DeepRequired<
|
||||||
|
{
|
||||||
|
useLegacyVersioning?: boolean;
|
||||||
|
git?: NxReleaseGitConfiguration;
|
||||||
|
preVersionCommand?: string;
|
||||||
|
} & LegacyNxReleaseVersionConfiguration
|
||||||
|
>;
|
||||||
delete rootVersionWithoutGlobalOptions.git;
|
delete rootVersionWithoutGlobalOptions.git;
|
||||||
delete rootVersionWithoutGlobalOptions.preVersionCommand;
|
delete rootVersionWithoutGlobalOptions.preVersionCommand;
|
||||||
|
|
||||||
// Apply conventionalCommits shorthand to the final group defaults if explicitly configured in the original user config
|
// Apply conventionalCommits shorthand to the final group defaults if explicitly configured in the original user config
|
||||||
if (userConfig.version?.conventionalCommits === true) {
|
if (userConfig.version?.conventionalCommits === true) {
|
||||||
|
if (USE_LEGACY_VERSIONING) {
|
||||||
rootVersionWithoutGlobalOptions.generatorOptions = {
|
rootVersionWithoutGlobalOptions.generatorOptions = {
|
||||||
...rootVersionWithoutGlobalOptions.generatorOptions,
|
...rootVersionWithoutGlobalOptions.generatorOptions,
|
||||||
currentVersionResolver: 'git-tag',
|
currentVersionResolver: 'git-tag',
|
||||||
specifierSource: 'conventional-commits',
|
specifierSource: 'conventional-commits',
|
||||||
};
|
};
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
rootVersionWithoutGlobalOptions as NxReleaseVersionV2Configuration
|
||||||
|
).currentVersionResolver = 'git-tag';
|
||||||
|
(
|
||||||
|
rootVersionWithoutGlobalOptions as NxReleaseVersionV2Configuration
|
||||||
|
).specifierSource = 'conventional-commits';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (userConfig.version?.conventionalCommits === false) {
|
if (userConfig.version?.conventionalCommits === false) {
|
||||||
delete rootVersionWithoutGlobalOptions.generatorOptions
|
delete rootVersionWithoutGlobalOptions.generatorOptions
|
||||||
.currentVersionResolver;
|
.currentVersionResolver;
|
||||||
delete rootVersionWithoutGlobalOptions.generatorOptions.specifierSource;
|
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
|
// Apply versionPlans shorthand to the final group defaults if explicitly configured in the original user config
|
||||||
if (userConfig.versionPlans) {
|
if (userConfig.versionPlans) {
|
||||||
|
if (USE_LEGACY_VERSIONING) {
|
||||||
rootVersionWithoutGlobalOptions.generatorOptions = {
|
rootVersionWithoutGlobalOptions.generatorOptions = {
|
||||||
...rootVersionWithoutGlobalOptions.generatorOptions,
|
...rootVersionWithoutGlobalOptions.generatorOptions,
|
||||||
specifierSource: 'version-plans',
|
specifierSource: 'version-plans',
|
||||||
};
|
};
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
rootVersionWithoutGlobalOptions as NxReleaseVersionV2Configuration
|
||||||
|
).specifierSource = 'version-plans';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (userConfig.versionPlans === false) {
|
if (userConfig.versionPlans === false) {
|
||||||
delete rootVersionWithoutGlobalOptions.generatorOptions.specifierSource;
|
delete rootVersionWithoutGlobalOptions.generatorOptions.specifierSource;
|
||||||
|
delete (rootVersionWithoutGlobalOptions as NxReleaseVersionV2Configuration)
|
||||||
|
.specifierSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
const groups: NxReleaseConfig['groups'] =
|
const groups: NxReleaseConfig['groups'] =
|
||||||
@ -462,9 +542,9 @@ export async function createNxReleaseConfig(
|
|||||||
* be the valid source of truth for that type of config.
|
* be the valid source of truth for that type of config.
|
||||||
*/
|
*/
|
||||||
version: deepMergeDefaults(
|
version: deepMergeDefaults(
|
||||||
[GROUP_DEFAULTS.version],
|
[GROUP_DEFAULTS.version] as any,
|
||||||
rootVersionWithoutGlobalOptions
|
rootVersionWithoutGlobalOptions
|
||||||
),
|
) as any,
|
||||||
// If the user has set something custom for releaseTagPattern at the top level, respect it for the implicit default group
|
// If the user has set something custom for releaseTagPattern at the top level, respect it for the implicit default group
|
||||||
releaseTagPattern:
|
releaseTagPattern:
|
||||||
userConfig.releaseTagPattern || GROUP_DEFAULTS.releaseTagPattern,
|
userConfig.releaseTagPattern || GROUP_DEFAULTS.releaseTagPattern,
|
||||||
@ -551,7 +631,7 @@ export async function createNxReleaseConfig(
|
|||||||
version: deepMergeDefaults(
|
version: deepMergeDefaults(
|
||||||
// First apply any group level defaults, then apply actual root level config, then group level config
|
// 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: '' },
|
{ ...rootVersionWithoutGlobalOptions, groupPreVersionCommand: '' },
|
||||||
],
|
],
|
||||||
releaseGroup.version
|
releaseGroup.version
|
||||||
@ -584,13 +664,29 @@ export async function createNxReleaseConfig(
|
|||||||
projects: matchingProjects,
|
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
|
// Apply conventionalCommits shorthand to the final group if explicitly configured in the original group
|
||||||
if (releaseGroup.version?.conventionalCommits === true) {
|
if (releaseGroup.version?.conventionalCommits === true) {
|
||||||
|
if (USE_LEGACY_VERSIONING) {
|
||||||
finalReleaseGroup.version.generatorOptions = {
|
finalReleaseGroup.version.generatorOptions = {
|
||||||
...finalReleaseGroup.version.generatorOptions,
|
...finalReleaseGroup.version.generatorOptions,
|
||||||
currentVersionResolver: 'git-tag',
|
currentVersionResolver: 'git-tag',
|
||||||
specifierSource: 'conventional-commits',
|
specifierSource: 'conventional-commits',
|
||||||
};
|
};
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
finalReleaseGroup.version as NxReleaseVersionV2Configuration
|
||||||
|
).currentVersionResolver = 'git-tag';
|
||||||
|
(
|
||||||
|
finalReleaseGroup.version as NxReleaseVersionV2Configuration
|
||||||
|
).specifierSource = 'conventional-commits';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
releaseGroup.version?.conventionalCommits === false &&
|
releaseGroup.version?.conventionalCommits === false &&
|
||||||
@ -598,10 +694,15 @@ export async function createNxReleaseConfig(
|
|||||||
) {
|
) {
|
||||||
delete finalReleaseGroup.version.generatorOptions.currentVersionResolver;
|
delete finalReleaseGroup.version.generatorOptions.currentVersionResolver;
|
||||||
delete finalReleaseGroup.version.generatorOptions.specifierSource;
|
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
|
// Apply versionPlans shorthand to the final group if explicitly configured in the original group
|
||||||
if (releaseGroup.versionPlans) {
|
if (releaseGroup.versionPlans) {
|
||||||
|
if (USE_LEGACY_VERSIONING) {
|
||||||
finalReleaseGroup.version = {
|
finalReleaseGroup.version = {
|
||||||
...finalReleaseGroup.version,
|
...finalReleaseGroup.version,
|
||||||
generatorOptions: {
|
generatorOptions: {
|
||||||
@ -609,14 +710,20 @@ export async function createNxReleaseConfig(
|
|||||||
specifierSource: 'version-plans',
|
specifierSource: 'version-plans',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
finalReleaseGroup.version as NxReleaseVersionV2Configuration
|
||||||
|
).specifierSource = 'version-plans';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
releaseGroup.versionPlans === false &&
|
releaseGroup.versionPlans === false &&
|
||||||
releaseGroupName !== IMPLICIT_DEFAULT_RELEASE_GROUP
|
releaseGroupName !== IMPLICIT_DEFAULT_RELEASE_GROUP
|
||||||
) {
|
) {
|
||||||
delete finalReleaseGroup.version.generatorOptions.specifierSource;
|
delete finalReleaseGroup.version.generatorOptions.specifierSource;
|
||||||
|
delete (finalReleaseGroup.version as NxReleaseVersionV2Configuration)
|
||||||
|
.specifierSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
releaseGroups[releaseGroupName] = finalReleaseGroup;
|
releaseGroups[releaseGroupName] = finalReleaseGroup;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -755,7 +862,8 @@ function fillUnspecifiedConventionalCommitsProperties(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function handleNxReleaseConfigError(
|
export async function handleNxReleaseConfigError(
|
||||||
error: CreateNxReleaseConfigError
|
error: CreateNxReleaseConfigError,
|
||||||
|
useLegacyVersioning: boolean
|
||||||
): Promise<never> {
|
): Promise<never> {
|
||||||
switch (error.code) {
|
switch (error.code) {
|
||||||
case 'PROJECTS_AND_GROUPS_DEFINED':
|
case 'PROJECTS_AND_GROUPS_DEFINED':
|
||||||
@ -808,13 +916,16 @@ export async function handleNxReleaseConfigError(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'CONVENTIONAL_COMMITS_SHORTHAND_MIXED_WITH_OVERLAPPING_GENERATOR_OPTIONS':
|
case 'CONVENTIONAL_COMMITS_SHORTHAND_MIXED_WITH_OVERLAPPING_OPTIONS':
|
||||||
{
|
{
|
||||||
const nxJsonMessage = await resolveNxJsonConfigErrorMessage([
|
const nxJsonMessage = await resolveNxJsonConfigErrorMessage([
|
||||||
'release',
|
'release',
|
||||||
]);
|
]);
|
||||||
|
const text = useLegacyVersioning
|
||||||
|
? '"version.generatorOptions"'
|
||||||
|
: 'configuration options';
|
||||||
output.error({
|
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],
|
bodyLines: [nxJsonMessage],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -990,7 +1101,7 @@ function deepMergeDefaults<T>(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* We want to prevent users from setting both the conventionalCommits shorthand and any of the related
|
* 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(
|
function hasInvalidConventionalCommitsConfig(
|
||||||
userConfig: NxJsonConfiguration['release']
|
userConfig: NxJsonConfiguration['release']
|
||||||
@ -998,8 +1109,16 @@ function hasInvalidConventionalCommitsConfig(
|
|||||||
// at the root
|
// at the root
|
||||||
if (
|
if (
|
||||||
userConfig.version?.conventionalCommits === true &&
|
userConfig.version?.conventionalCommits === true &&
|
||||||
(userConfig.version?.generatorOptions?.currentVersionResolver ||
|
// v2 config - directly on version config
|
||||||
userConfig.version?.generatorOptions?.specifierSource)
|
((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;
|
return true;
|
||||||
}
|
}
|
||||||
@ -1008,8 +1127,15 @@ function hasInvalidConventionalCommitsConfig(
|
|||||||
for (const group of Object.values(userConfig.groups)) {
|
for (const group of Object.values(userConfig.groups)) {
|
||||||
if (
|
if (
|
||||||
group.version?.conventionalCommits === true &&
|
group.version?.conventionalCommits === true &&
|
||||||
(group.version?.generatorOptions?.currentVersionResolver ||
|
// v2 config - directly on version config
|
||||||
group.version?.generatorOptions?.specifierSource)
|
((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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,12 +21,14 @@ describe('filterReleaseGroups()', () => {
|
|||||||
tagArgs: '',
|
tagArgs: '',
|
||||||
stageChanges: false,
|
stageChanges: false,
|
||||||
push: false,
|
push: false,
|
||||||
|
pushArgs: '',
|
||||||
},
|
},
|
||||||
workspaceChangelog: false,
|
workspaceChangelog: false,
|
||||||
projectChangelogs: false,
|
projectChangelogs: false,
|
||||||
automaticFromRef: false,
|
automaticFromRef: false,
|
||||||
},
|
},
|
||||||
version: {
|
version: {
|
||||||
|
useLegacyVersioning: true,
|
||||||
conventionalCommits: false,
|
conventionalCommits: false,
|
||||||
generator: '',
|
generator: '',
|
||||||
generatorOptions: {},
|
generatorOptions: {},
|
||||||
@ -39,6 +41,7 @@ describe('filterReleaseGroups()', () => {
|
|||||||
tagMessage: '',
|
tagMessage: '',
|
||||||
tagArgs: '',
|
tagArgs: '',
|
||||||
push: false,
|
push: false,
|
||||||
|
pushArgs: '',
|
||||||
},
|
},
|
||||||
preVersionCommand: '',
|
preVersionCommand: '',
|
||||||
},
|
},
|
||||||
@ -53,6 +56,7 @@ describe('filterReleaseGroups()', () => {
|
|||||||
tagArgs: '',
|
tagArgs: '',
|
||||||
stageChanges: false,
|
stageChanges: false,
|
||||||
push: false,
|
push: false,
|
||||||
|
pushArgs: '',
|
||||||
},
|
},
|
||||||
conventionalCommits: DEFAULT_CONVENTIONAL_COMMITS_CONFIG,
|
conventionalCommits: DEFAULT_CONVENTIONAL_COMMITS_CONFIG,
|
||||||
versionPlans: false,
|
versionPlans: false,
|
||||||
|
|||||||
@ -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;
|
||||||
|
}
|
||||||
@ -49,3 +49,11 @@ export const releaseVersion = defaultClient.releaseVersion.bind(
|
|||||||
export const release = defaultClient.release.bind(
|
export const release = defaultClient.release.bind(
|
||||||
defaultClient
|
defaultClient
|
||||||
) as typeof defaultClient.release;
|
) as typeof defaultClient.release;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export {
|
||||||
|
AfterAllProjectsVersioned,
|
||||||
|
VersionActions,
|
||||||
|
} from './version/version-actions';
|
||||||
|
|||||||
@ -6,8 +6,8 @@ import {
|
|||||||
parseFiles,
|
parseFiles,
|
||||||
splitArgsIntoNxArgsAndOverrides,
|
splitArgsIntoNxArgsAndOverrides,
|
||||||
} from '../../utils/command-line-utils';
|
} from '../../utils/command-line-utils';
|
||||||
import { output } from '../../utils/output';
|
|
||||||
import { handleErrors } from '../../utils/handle-errors';
|
import { handleErrors } from '../../utils/handle-errors';
|
||||||
|
import { output } from '../../utils/output';
|
||||||
import { PlanCheckOptions, PlanOptions } from './command-object';
|
import { PlanCheckOptions, PlanOptions } from './command-object';
|
||||||
import {
|
import {
|
||||||
createNxReleaseConfig,
|
createNxReleaseConfig,
|
||||||
@ -16,6 +16,7 @@ import {
|
|||||||
} from './config/config';
|
} from './config/config';
|
||||||
import { deepMergeJson } from './config/deep-merge-json';
|
import { deepMergeJson } from './config/deep-merge-json';
|
||||||
import { filterReleaseGroups } from './config/filter-release-groups';
|
import { filterReleaseGroups } from './config/filter-release-groups';
|
||||||
|
import { shouldUseLegacyVersioning } from './config/use-legacy-versioning';
|
||||||
import {
|
import {
|
||||||
readRawVersionPlans,
|
readRawVersionPlans,
|
||||||
setResolvedVersionPlansOnGroups,
|
setResolvedVersionPlansOnGroups,
|
||||||
@ -42,7 +43,13 @@ export function createAPI(overrideReleaseConfig: NxReleaseConfiguration) {
|
|||||||
userProvidedReleaseConfig
|
userProvidedReleaseConfig
|
||||||
);
|
);
|
||||||
if (configError) {
|
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
|
// --print-config exits directly as it is not designed to be combined with any other programmatic operations
|
||||||
if (args.printConfig) {
|
if (args.printConfig) {
|
||||||
@ -69,7 +76,8 @@ export function createAPI(overrideReleaseConfig: NxReleaseConfiguration) {
|
|||||||
title: 'Version plans are not enabled',
|
title: 'Version plans are not enabled',
|
||||||
bodyLines: [
|
bodyLines: [
|
||||||
'Please ensure at least one release group has version plans enabled in your Nx release configuration if you want to use this command.',
|
'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;
|
return 1;
|
||||||
|
|||||||
@ -12,8 +12,8 @@ import {
|
|||||||
parseFiles,
|
parseFiles,
|
||||||
splitArgsIntoNxArgsAndOverrides,
|
splitArgsIntoNxArgsAndOverrides,
|
||||||
} from '../../utils/command-line-utils';
|
} from '../../utils/command-line-utils';
|
||||||
import { output } from '../../utils/output';
|
|
||||||
import { handleErrors } from '../../utils/handle-errors';
|
import { handleErrors } from '../../utils/handle-errors';
|
||||||
|
import { output } from '../../utils/output';
|
||||||
import { PlanOptions } from './command-object';
|
import { PlanOptions } from './command-object';
|
||||||
import {
|
import {
|
||||||
createNxReleaseConfig,
|
createNxReleaseConfig,
|
||||||
@ -22,6 +22,7 @@ import {
|
|||||||
} from './config/config';
|
} from './config/config';
|
||||||
import { deepMergeJson } from './config/deep-merge-json';
|
import { deepMergeJson } from './config/deep-merge-json';
|
||||||
import { filterReleaseGroups } from './config/filter-release-groups';
|
import { filterReleaseGroups } from './config/filter-release-groups';
|
||||||
|
import { shouldUseLegacyVersioning } from './config/use-legacy-versioning';
|
||||||
import { getVersionPlansAbsolutePath } from './config/version-plans';
|
import { getVersionPlansAbsolutePath } from './config/version-plans';
|
||||||
import { generateVersionPlanContent } from './utils/generate-version-plan-content';
|
import { generateVersionPlanContent } from './utils/generate-version-plan-content';
|
||||||
import { createGetTouchedProjectsForGroup } from './utils/get-touched-projects-for-group';
|
import { createGetTouchedProjectsForGroup } from './utils/get-touched-projects-for-group';
|
||||||
@ -50,7 +51,13 @@ export function createAPI(overrideReleaseConfig: NxReleaseConfiguration) {
|
|||||||
userProvidedReleaseConfig
|
userProvidedReleaseConfig
|
||||||
);
|
);
|
||||||
if (configError) {
|
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
|
// --print-config exits directly as it is not designed to be combined with any other programmatic operations
|
||||||
if (args.printConfig) {
|
if (args.printConfig) {
|
||||||
@ -319,10 +326,12 @@ async function promptForVersion(message: string): Promise<string> {
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
return reply.version;
|
return reply.version;
|
||||||
} catch (e) {
|
} catch {
|
||||||
output.log({
|
output.log({
|
||||||
title: 'Cancelled version plan creation.',
|
title: 'Cancelled version plan creation.',
|
||||||
});
|
});
|
||||||
|
// Ensure the cursor is always restored before exiting
|
||||||
|
process.stdout.write('\u001b[?25h');
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,10 @@ import {
|
|||||||
ProjectGraphProjectNode,
|
ProjectGraphProjectNode,
|
||||||
} from '../../config/project-graph';
|
} from '../../config/project-graph';
|
||||||
import { createProjectFileMapUsingProjectGraph } from '../../project-graph/file-map-utils';
|
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 { createProjectGraphAsync } from '../../project-graph/project-graph';
|
||||||
import { runCommandForTasks } from '../../tasks-runner/run-command';
|
import { runCommandForTasks } from '../../tasks-runner/run-command';
|
||||||
import {
|
import {
|
||||||
@ -17,6 +21,7 @@ import {
|
|||||||
import { handleErrors } from '../../utils/handle-errors';
|
import { handleErrors } from '../../utils/handle-errors';
|
||||||
import { output } from '../../utils/output';
|
import { output } from '../../utils/output';
|
||||||
import { projectHasTarget } from '../../utils/project-graph-utils';
|
import { projectHasTarget } from '../../utils/project-graph-utils';
|
||||||
|
import { workspaceRoot } from '../../utils/workspace-root';
|
||||||
import { generateGraph } from '../graph/graph';
|
import { generateGraph } from '../graph/graph';
|
||||||
import { PublishOptions } from './command-object';
|
import { PublishOptions } from './command-object';
|
||||||
import {
|
import {
|
||||||
@ -25,12 +30,8 @@ import {
|
|||||||
} from './config/config';
|
} from './config/config';
|
||||||
import { deepMergeJson } from './config/deep-merge-json';
|
import { deepMergeJson } from './config/deep-merge-json';
|
||||||
import { filterReleaseGroups } from './config/filter-release-groups';
|
import { filterReleaseGroups } from './config/filter-release-groups';
|
||||||
|
import { shouldUseLegacyVersioning } from './config/use-legacy-versioning';
|
||||||
import { printConfigAndExit } from './utils/print-config';
|
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 {
|
export interface PublishProjectsResult {
|
||||||
[projectName: string]: {
|
[projectName: string]: {
|
||||||
@ -83,7 +84,13 @@ export function createAPI(overrideReleaseConfig: NxReleaseConfiguration) {
|
|||||||
userProvidedReleaseConfig
|
userProvidedReleaseConfig
|
||||||
);
|
);
|
||||||
if (configError) {
|
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
|
// --print-config exits directly as it is not designed to be combined with any other programmatic operations
|
||||||
if (args.printConfig) {
|
if (args.printConfig) {
|
||||||
|
|||||||
@ -3,8 +3,8 @@ import { rmSync } from 'node:fs';
|
|||||||
import { NxReleaseConfiguration, readNxJson } from '../../config/nx-json';
|
import { NxReleaseConfiguration, readNxJson } from '../../config/nx-json';
|
||||||
import { createProjectFileMapUsingProjectGraph } from '../../project-graph/file-map-utils';
|
import { createProjectFileMapUsingProjectGraph } from '../../project-graph/file-map-utils';
|
||||||
import { createProjectGraphAsync } from '../../project-graph/project-graph';
|
import { createProjectGraphAsync } from '../../project-graph/project-graph';
|
||||||
import { output } from '../../utils/output';
|
|
||||||
import { handleErrors } from '../../utils/handle-errors';
|
import { handleErrors } from '../../utils/handle-errors';
|
||||||
|
import { output } from '../../utils/output';
|
||||||
import {
|
import {
|
||||||
createAPI as createReleaseChangelogAPI,
|
createAPI as createReleaseChangelogAPI,
|
||||||
shouldCreateGitHubRelease,
|
shouldCreateGitHubRelease,
|
||||||
@ -17,6 +17,7 @@ import {
|
|||||||
} from './config/config';
|
} from './config/config';
|
||||||
import { deepMergeJson } from './config/deep-merge-json';
|
import { deepMergeJson } from './config/deep-merge-json';
|
||||||
import { filterReleaseGroups } from './config/filter-release-groups';
|
import { filterReleaseGroups } from './config/filter-release-groups';
|
||||||
|
import { shouldUseLegacyVersioning } from './config/use-legacy-versioning';
|
||||||
import {
|
import {
|
||||||
readRawVersionPlans,
|
readRawVersionPlans,
|
||||||
setResolvedVersionPlansOnGroups,
|
setResolvedVersionPlansOnGroups,
|
||||||
@ -79,7 +80,13 @@ export function createAPI(overrideReleaseConfig: NxReleaseConfiguration) {
|
|||||||
userProvidedReleaseConfig
|
userProvidedReleaseConfig
|
||||||
);
|
);
|
||||||
if (configError) {
|
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
|
// --print-config exits directly as it is not designed to be combined with any other programmatic operations
|
||||||
if (args.printConfig) {
|
if (args.printConfig) {
|
||||||
@ -254,6 +261,7 @@ export function createAPI(overrideReleaseConfig: NxReleaseConfiguration) {
|
|||||||
await gitPush({
|
await gitPush({
|
||||||
dryRun: args.dryRun,
|
dryRun: args.dryRun,
|
||||||
verbose: args.verbose,
|
verbose: args.verbose,
|
||||||
|
additionalArgs: nxReleaseConfig.git.pushArgs,
|
||||||
});
|
});
|
||||||
hasPushedChanges = true;
|
hasPushedChanges = true;
|
||||||
}
|
}
|
||||||
@ -361,7 +369,9 @@ async function promptForPublish(): Promise<boolean> {
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
return reply.confirmation;
|
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
|
// Handle the case where the user exits the prompt with ctrl+c
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { LegacyNxReleaseVersionConfiguration } from '../../../config/nx-json';
|
||||||
import { ProjectGraph } from '../../../config/project-graph';
|
import { ProjectGraph } from '../../../config/project-graph';
|
||||||
import { deepEquals } from '../../../utils/json-diff';
|
import { deepEquals } from '../../../utils/json-diff';
|
||||||
import { ReleaseGroupWithName } from '../config/filter-release-groups';
|
import { ReleaseGroupWithName } from '../config/filter-release-groups';
|
||||||
@ -15,11 +16,14 @@ export function batchProjectsByGeneratorConfig(
|
|||||||
for (const projectName of projectNamesToBatch) {
|
for (const projectName of projectNamesToBatch) {
|
||||||
const project = projectGraph.nodes[projectName];
|
const project = projectGraph.nodes[projectName];
|
||||||
const generator =
|
const generator =
|
||||||
project.data.release?.version?.generator ||
|
(project.data.release?.version as LegacyNxReleaseVersionConfiguration)
|
||||||
releaseGroup.version.generator;
|
?.generator ||
|
||||||
|
(releaseGroup.version as LegacyNxReleaseVersionConfiguration).generator;
|
||||||
const generatorOptions = {
|
const generatorOptions = {
|
||||||
...releaseGroup.version.generatorOptions,
|
...(releaseGroup.version as LegacyNxReleaseVersionConfiguration)
|
||||||
...project.data.release?.version?.generatorOptions,
|
.generatorOptions,
|
||||||
|
...(project.data.release?.version as LegacyNxReleaseVersionConfiguration)
|
||||||
|
?.generatorOptions,
|
||||||
};
|
};
|
||||||
|
|
||||||
let found = false;
|
let found = false;
|
||||||
|
|||||||
@ -416,10 +416,12 @@ export async function gitPush({
|
|||||||
gitRemote,
|
gitRemote,
|
||||||
dryRun,
|
dryRun,
|
||||||
verbose,
|
verbose,
|
||||||
|
additionalArgs,
|
||||||
}: {
|
}: {
|
||||||
gitRemote?: string;
|
gitRemote?: string;
|
||||||
dryRun?: boolean;
|
dryRun?: boolean;
|
||||||
verbose?: boolean;
|
verbose?: boolean;
|
||||||
|
additionalArgs?: string | string[];
|
||||||
}) {
|
}) {
|
||||||
const commandArgs = [
|
const commandArgs = [
|
||||||
'push',
|
'push',
|
||||||
@ -430,6 +432,13 @@ export async function gitPush({
|
|||||||
// Set custom git remote if provided
|
// Set custom git remote if provided
|
||||||
...(gitRemote ? [gitRemote] : []),
|
...(gitRemote ? [gitRemote] : []),
|
||||||
];
|
];
|
||||||
|
if (additionalArgs) {
|
||||||
|
if (Array.isArray(additionalArgs)) {
|
||||||
|
commandArgs.push(...additionalArgs);
|
||||||
|
} else {
|
||||||
|
commandArgs.push(...additionalArgs.split(' '));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (verbose) {
|
if (verbose) {
|
||||||
console.log(
|
console.log(
|
||||||
@ -534,7 +543,7 @@ export function parseGitCommit(
|
|||||||
commit.shortHash
|
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
|
// 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,
|
isBreaking: false,
|
||||||
authors: getAllAuthorsForCommit(commit),
|
authors: getAllAuthorsForCommit(commit),
|
||||||
// Not applicable to version plans
|
// Not applicable to version plans
|
||||||
|
|||||||
@ -296,7 +296,9 @@ async function promptForContinueInGitHub(): Promise<boolean> {
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
return reply.open === 'Yes';
|
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
|
// Handle the case where the user exits the prompt with ctrl+c
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { prompt } from 'enquirer';
|
|||||||
import { RELEASE_TYPES, valid } from 'semver';
|
import { RELEASE_TYPES, valid } from 'semver';
|
||||||
import { ProjectGraph } from '../../../config/project-graph';
|
import { ProjectGraph } from '../../../config/project-graph';
|
||||||
import { NxReleaseConfig } from '../config/config';
|
import { NxReleaseConfig } from '../config/config';
|
||||||
|
import { SemverBumpType } from '../version/version-actions';
|
||||||
import { getGitDiff, parseCommits } from './git';
|
import { getGitDiff, parseCommits } from './git';
|
||||||
import { determineSemverChange } from './semver';
|
import { determineSemverChange } from './semver';
|
||||||
import { getCommitsRelevantToProjects } from './shared';
|
import { getCommitsRelevantToProjects } from './shared';
|
||||||
@ -25,7 +26,7 @@ export async function resolveSemverSpecifierFromConventionalCommits(
|
|||||||
export async function resolveSemverSpecifierFromPrompt(
|
export async function resolveSemverSpecifierFromPrompt(
|
||||||
selectionMessage: string,
|
selectionMessage: string,
|
||||||
customVersionMessage: string
|
customVersionMessage: string
|
||||||
): Promise<string> {
|
): Promise<SemverBumpType | string> {
|
||||||
try {
|
try {
|
||||||
const reply = await prompt<{ specifier: string }>([
|
const reply = await prompt<{ specifier: string }>([
|
||||||
{
|
{
|
||||||
@ -42,7 +43,7 @@ export async function resolveSemverSpecifierFromPrompt(
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
if (reply.specifier !== 'custom') {
|
if (reply.specifier !== 'custom') {
|
||||||
return reply.specifier;
|
return reply.specifier as SemverBumpType;
|
||||||
} else {
|
} else {
|
||||||
const reply = await prompt<{ specifier: string }>([
|
const reply = await prompt<{ specifier: string }>([
|
||||||
{
|
{
|
||||||
@ -60,7 +61,8 @@ export async function resolveSemverSpecifierFromPrompt(
|
|||||||
return reply.specifier;
|
return reply.specifier;
|
||||||
}
|
}
|
||||||
} catch {
|
} 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
|
// We need to catch the error from enquirer prompt, otherwise yargs will print its help
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
// https://github.com/unjs/changelogen/blob/main/src/semver.ts
|
||||||
export function determineSemverChange(
|
export function determineSemverChange(
|
||||||
commits: GitCommit[],
|
commits: GitCommit[],
|
||||||
|
|||||||
36
packages/nx/src/command-line/release/utils/shared-legacy.ts
Normal file
36
packages/nx/src/command-line/release/utils/shared-legacy.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import { Tree } from '../../../generators/tree';
|
||||||
|
|
||||||
|
export type ReleaseVersionGeneratorResult = {
|
||||||
|
data: VersionData;
|
||||||
|
callback: (
|
||||||
|
tree: Tree,
|
||||||
|
opts: {
|
||||||
|
dryRun?: boolean;
|
||||||
|
verbose?: boolean;
|
||||||
|
generatorOptions?: Record<string, unknown>;
|
||||||
|
}
|
||||||
|
) => 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[];
|
||||||
|
}
|
||||||
|
>;
|
||||||
@ -199,6 +199,7 @@ describe('shared', () => {
|
|||||||
target: 'core',
|
target: 'core',
|
||||||
type: 'static',
|
type: 'static',
|
||||||
dependencyCollection: 'devDependencies',
|
dependencyCollection: 'devDependencies',
|
||||||
|
rawVersionSpec: '1.0.0-canary.1',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
newVersion: '1.0.0-canary.2',
|
newVersion: '1.0.0-canary.2',
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import * as chalk from 'chalk';
|
import * as chalk from 'chalk';
|
||||||
import { prerelease } from 'semver';
|
import { prerelease } from 'semver';
|
||||||
import { ProjectGraph } from '../../../config/project-graph';
|
import { ProjectGraph } from '../../../config/project-graph';
|
||||||
import { Tree } from '../../../generators/tree';
|
|
||||||
import { createFileMapUsingProjectGraph } from '../../../project-graph/file-map-utils';
|
import { createFileMapUsingProjectGraph } from '../../../project-graph/file-map-utils';
|
||||||
import { interpolate } from '../../../tasks-runner/utils';
|
import { interpolate } from '../../../tasks-runner/utils';
|
||||||
import { output } from '../../../utils/output';
|
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?`
|
`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 = {
|
// project name -> version data entry
|
||||||
data: VersionData;
|
export type VersionData = Record<string, VersionDataEntry>;
|
||||||
callback: (
|
|
||||||
tree: Tree,
|
|
||||||
opts: {
|
|
||||||
dryRun?: boolean;
|
|
||||||
verbose?: boolean;
|
|
||||||
generatorOptions?: Record<string, unknown>;
|
|
||||||
}
|
|
||||||
) => Promise<
|
|
||||||
| string[]
|
|
||||||
| {
|
|
||||||
changedFiles: string[];
|
|
||||||
deletedFiles: string[];
|
|
||||||
}
|
|
||||||
>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type VersionData = Record<
|
export interface VersionDataEntry {
|
||||||
string,
|
currentVersion: string;
|
||||||
{
|
|
||||||
/**
|
/**
|
||||||
* newVersion will be null in the case that no changes are detected for the project,
|
* newVersion will be null in the case that no changes are detected for the project,
|
||||||
* e.g. when using conventional commits
|
* e.g. when using conventional commits
|
||||||
*/
|
*/
|
||||||
newVersion: string | null;
|
newVersion: string | null;
|
||||||
currentVersion: string;
|
|
||||||
/**
|
/**
|
||||||
* The list of projects which depend upon the current project.
|
* The list of projects which depend upon the current project.
|
||||||
* TODO: investigate generic type for this once more ecosystems are explored
|
|
||||||
*/
|
*/
|
||||||
dependentProjects: any[];
|
dependentProjects: {
|
||||||
|
source: string;
|
||||||
|
target: string;
|
||||||
|
type: string;
|
||||||
|
dependencyCollection: string;
|
||||||
|
rawVersionSpec: string;
|
||||||
|
}[];
|
||||||
}
|
}
|
||||||
>;
|
|
||||||
|
|
||||||
function isPrerelease(version: string): boolean {
|
function isPrerelease(version: string): boolean {
|
||||||
// prerelease returns an array of matching prerelease "components", or null if the version is not a prerelease
|
// prerelease returns an array of matching prerelease "components", or null if the version is not a prerelease
|
||||||
|
|||||||
756
packages/nx/src/command-line/release/version-legacy.ts
Normal file
756
packages/nx/src/command-line/release/version-legacy.ts
Normal file
@ -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<string, unknown>;
|
||||||
|
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<NxReleaseVersionResult> {
|
||||||
|
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<void>)[] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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<string>();
|
||||||
|
const additionalDeletedFiles = new Set<string>();
|
||||||
|
|
||||||
|
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<string, unknown> | undefined,
|
||||||
|
projectNames: string[],
|
||||||
|
releaseGroup: ReleaseGroupWithName,
|
||||||
|
versionData: VersionData,
|
||||||
|
conventionalCommitsConfig: NxReleaseConfig['conventionalCommits']
|
||||||
|
): Promise<ReleaseVersionGeneratorResult['callback']> {
|
||||||
|
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<string, unknown>;
|
||||||
|
normalizedGeneratorName: string;
|
||||||
|
schema: any;
|
||||||
|
implementationFactory: () => Generator<unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
|
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<string, string> = {
|
||||||
|
...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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,32 +1,21 @@
|
|||||||
import * as chalk from 'chalk';
|
import * as chalk from 'chalk';
|
||||||
import { execSync } from 'node:child_process';
|
import { execSync } from 'node:child_process';
|
||||||
import { readFileSync } from 'node:fs';
|
import { readFileSync } from 'node:fs';
|
||||||
import { relative } from 'node:path';
|
|
||||||
import { Generator } from '../../config/misc-interfaces';
|
|
||||||
import {
|
import {
|
||||||
NxJsonConfiguration,
|
|
||||||
NxReleaseConfiguration,
|
NxReleaseConfiguration,
|
||||||
|
NxReleaseVersionV2Configuration,
|
||||||
readNxJson,
|
readNxJson,
|
||||||
} from '../../config/nx-json';
|
} from '../../config/nx-json';
|
||||||
import {
|
import { formatChangedFilesWithPrettierIfAvailable } from '../../generators/internal-utils/format-changed-files-with-prettier-if-available';
|
||||||
ProjectGraph,
|
|
||||||
ProjectGraphProjectNode,
|
|
||||||
} from '../../config/project-graph';
|
|
||||||
import { FsTree, Tree, flushChanges } from '../../generators/tree';
|
import { FsTree, Tree, flushChanges } from '../../generators/tree';
|
||||||
import { createProjectFileMapUsingProjectGraph } from '../../project-graph/file-map-utils';
|
import { createProjectFileMapUsingProjectGraph } from '../../project-graph/file-map-utils';
|
||||||
import {
|
import { createProjectGraphAsync } from '../../project-graph/project-graph';
|
||||||
createProjectGraphAsync,
|
import { handleErrors } from '../../utils/handle-errors';
|
||||||
readProjectsConfigurationFromProjectGraph,
|
|
||||||
} from '../../project-graph/project-graph';
|
|
||||||
import { output } from '../../utils/output';
|
import { output } from '../../utils/output';
|
||||||
import { combineOptionsForGenerator } from '../../utils/params';
|
|
||||||
import { joinPathFragments } from '../../utils/path';
|
import { joinPathFragments } from '../../utils/path';
|
||||||
import { workspaceRoot } from '../../utils/workspace-root';
|
import { workspaceRoot } from '../../utils/workspace-root';
|
||||||
import { parseGeneratorString } from '../generate/generate';
|
|
||||||
import { getGeneratorInformation } from '../generate/generator-utils';
|
|
||||||
import { VersionOptions } from './command-object';
|
import { VersionOptions } from './command-object';
|
||||||
import {
|
import {
|
||||||
NxReleaseConfig,
|
|
||||||
createNxReleaseConfig,
|
createNxReleaseConfig,
|
||||||
handleNxReleaseConfigError,
|
handleNxReleaseConfigError,
|
||||||
} from './config/config';
|
} from './config/config';
|
||||||
@ -39,70 +28,28 @@ import {
|
|||||||
readRawVersionPlans,
|
readRawVersionPlans,
|
||||||
setResolvedVersionPlansOnGroups,
|
setResolvedVersionPlansOnGroups,
|
||||||
} from './config/version-plans';
|
} from './config/version-plans';
|
||||||
import { batchProjectsByGeneratorConfig } from './utils/batch-projects-by-generator-config';
|
|
||||||
import { gitAdd, gitPush, gitTag } from './utils/git';
|
import { gitAdd, gitPush, gitTag } from './utils/git';
|
||||||
import { printDiff } from './utils/print-changes';
|
import { printDiff } from './utils/print-changes';
|
||||||
import { printConfigAndExit } from './utils/print-config';
|
import { printConfigAndExit } from './utils/print-config';
|
||||||
import { resolveNxJsonConfigErrorMessage } from './utils/resolve-nx-json-error-message';
|
import { resolveNxJsonConfigErrorMessage } from './utils/resolve-nx-json-error-message';
|
||||||
import {
|
import {
|
||||||
ReleaseVersionGeneratorResult,
|
|
||||||
VersionData,
|
VersionData,
|
||||||
commitChanges,
|
commitChanges,
|
||||||
createCommitMessageValues,
|
createCommitMessageValues,
|
||||||
createGitTagValues,
|
createGitTagValues,
|
||||||
handleDuplicateGitTags,
|
handleDuplicateGitTags,
|
||||||
} from './utils/shared';
|
} 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;
|
const LARGE_BUFFER = 1024 * 1000000;
|
||||||
|
|
||||||
// Reexport some utils for use in plugin release-version generator implementations
|
// 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 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<string, unknown>;
|
|
||||||
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 {
|
export interface NxReleaseVersionResult {
|
||||||
/**
|
/**
|
||||||
* In one specific (and very common) case, an overall workspace version is relevant, for example when there is
|
* 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
|
args: VersionOptions
|
||||||
): Promise<NxReleaseVersionResult> {
|
): Promise<NxReleaseVersionResult> {
|
||||||
const projectGraph = await createProjectGraphAsync({ exitOnError: true });
|
const projectGraph = await createProjectGraphAsync({ exitOnError: true });
|
||||||
const { projects } =
|
|
||||||
readProjectsConfigurationFromProjectGraph(projectGraph);
|
|
||||||
const nxJson = readNxJson();
|
const nxJson = readNxJson();
|
||||||
const userProvidedReleaseConfig = deepMergeJson(
|
const userProvidedReleaseConfig = deepMergeJson(
|
||||||
nxJson.release ?? {},
|
nxJson.release ?? {},
|
||||||
overrideReleaseConfig ?? {}
|
overrideReleaseConfig ?? {}
|
||||||
);
|
);
|
||||||
|
const USE_LEGACY_VERSIONING = shouldUseLegacyVersioning(
|
||||||
|
userProvidedReleaseConfig
|
||||||
|
);
|
||||||
|
|
||||||
// Apply default configuration to any optional user configuration
|
// Apply default configuration to any optional user configuration
|
||||||
const { error: configError, nxReleaseConfig } = await createNxReleaseConfig(
|
const { error: configError, nxReleaseConfig } = await createNxReleaseConfig(
|
||||||
@ -146,7 +94,10 @@ export function createAPI(overrideReleaseConfig: NxReleaseConfiguration) {
|
|||||||
userProvidedReleaseConfig
|
userProvidedReleaseConfig
|
||||||
);
|
);
|
||||||
if (configError) {
|
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
|
// --print-config exits directly as it is not designed to be combined with any other programmatic operations
|
||||||
if (args.printConfig) {
|
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
|
// 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 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,
|
// 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;
|
args.deleteVersionPlans = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run any configured top level pre-version command
|
||||||
|
*/
|
||||||
runPreVersionCommand(nxReleaseConfig.version.preVersionCommand, {
|
runPreVersionCommand(nxReleaseConfig.version.preVersionCommand, {
|
||||||
dryRun: args.dryRun,
|
dryRun: args.dryRun,
|
||||||
verbose: args.verbose,
|
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<void>)[] = [];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* additionalChangedFiles are files which need to be updated as a side-effect of versioning (such as package manager lock files),
|
* Run any configured pre-version command for the selected release groups
|
||||||
* and need to get staged and committed as part of the existing commit, if applicable.
|
|
||||||
*/
|
|
||||||
const additionalChangedFiles = new Set<string>();
|
|
||||||
const additionalDeletedFiles = new Set<string>();
|
|
||||||
|
|
||||||
if (args.projects?.length) {
|
|
||||||
/**
|
|
||||||
* Run versioning for all remaining release groups and filtered projects within them
|
|
||||||
*/
|
*/
|
||||||
for (const releaseGroup of releaseGroups) {
|
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
|
|
||||||
*/
|
|
||||||
for (const releaseGroup of releaseGroups) {
|
|
||||||
const releaseGroupName = releaseGroup.name;
|
|
||||||
|
|
||||||
runPreVersionCommand(
|
runPreVersionCommand(
|
||||||
releaseGroup.version.groupPreVersionCommand,
|
releaseGroup.version.groupPreVersionCommand,
|
||||||
{
|
{
|
||||||
@ -408,62 +203,61 @@ export function createAPI(overrideReleaseConfig: NxReleaseConfiguration) {
|
|||||||
},
|
},
|
||||||
releaseGroup
|
releaseGroup
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const projectBatches = batchProjectsByGeneratorConfig(
|
const tree = new FsTree(workspaceRoot, args.verbose);
|
||||||
projectGraph,
|
|
||||||
releaseGroup,
|
|
||||||
// Batch based on all projects within the release group
|
|
||||||
releaseGroup.projects
|
|
||||||
);
|
|
||||||
|
|
||||||
for (const [
|
const commitMessage: string | undefined =
|
||||||
generatorConfigString,
|
args.gitCommitMessage || nxReleaseConfig.version.git.commitMessage;
|
||||||
projectNames,
|
|
||||||
] of projectBatches.entries()) {
|
/**
|
||||||
const [generatorName, generatorOptions] = JSON.parse(
|
* additionalChangedFiles are files which need to be updated as a side-effect of versioning (such as package manager lock files),
|
||||||
generatorConfigString
|
* and need to get staged and committed as part of the existing commit, if applicable.
|
||||||
);
|
*/
|
||||||
// Resolve the generator for the batch and run versioning on the projects within the batch
|
const additionalChangedFiles = new Set<string>();
|
||||||
const generatorData = resolveGeneratorData({
|
const additionalDeletedFiles = new Set<string>();
|
||||||
...extractGeneratorCollectionAndName(
|
|
||||||
`batch "${JSON.stringify(
|
const processor = new ReleaseGroupProcessor(
|
||||||
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,
|
tree,
|
||||||
generatorData,
|
projectGraph,
|
||||||
args.generatorOptionsOverrides,
|
nxReleaseConfig,
|
||||||
projectNames,
|
releaseGroups,
|
||||||
releaseGroup,
|
releaseGroupToFilteredProjects,
|
||||||
versionData,
|
{
|
||||||
nxReleaseConfig.conventionalCommits
|
dryRun: args.dryRun,
|
||||||
);
|
verbose: args.verbose,
|
||||||
// Capture the callback so that we can run it after flushing the changes to disk
|
firstRelease: args.firstRelease,
|
||||||
generatorCallbacks.push(async () => {
|
preid: args.preid ?? '',
|
||||||
const result = await generatorCallback(tree, {
|
userGivenSpecifier: args.specifier as SemverBumpType | undefined,
|
||||||
dryRun: !!args.dryRun,
|
filters: {
|
||||||
verbose: !!args.verbose,
|
projects: args.projects,
|
||||||
generatorOptions: {
|
groups: args.groups,
|
||||||
...generatorOptions,
|
|
||||||
...args.generatorOptionsOverrides,
|
|
||||||
},
|
},
|
||||||
});
|
|
||||||
const { changedFiles, deletedFiles } =
|
|
||||||
parseGeneratorCallbackResult(result);
|
|
||||||
changedFiles.forEach((f) => additionalChangedFiles.add(f));
|
|
||||||
deletedFiles.forEach((f) => additionalDeletedFiles.add(f));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
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
|
// 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[] =
|
const gitTagValues: string[] =
|
||||||
@ -478,9 +272,13 @@ export function createAPI(overrideReleaseConfig: NxReleaseConfiguration) {
|
|||||||
|
|
||||||
printAndFlushChanges(tree, !!args.dryRun);
|
printAndFlushChanges(tree, !!args.dryRun);
|
||||||
|
|
||||||
for (const generatorCallback of generatorCallbacks) {
|
const { changedFiles: changed, deletedFiles: deleted } =
|
||||||
await generatorCallback();
|
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
|
// Only applicable when there is a single release group with a fixed relationship
|
||||||
let workspaceVersion: string | null | undefined = undefined;
|
let workspaceVersion: string | null | undefined = undefined;
|
||||||
@ -548,11 +346,15 @@ export function createAPI(overrideReleaseConfig: NxReleaseConfiguration) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (args.gitPush ?? nxReleaseConfig.version.git.push) {
|
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({
|
await gitPush({
|
||||||
gitRemote: args.gitRemote,
|
gitRemote: args.gitRemote,
|
||||||
dryRun: args.dryRun,
|
dryRun: args.dryRun,
|
||||||
verbose: args.verbose,
|
verbose: args.verbose,
|
||||||
|
additionalArgs:
|
||||||
|
args.gitPushArgs || nxReleaseConfig.version.git.pushArgs,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -563,88 +365,12 @@ 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<string, unknown> | undefined,
|
|
||||||
projectNames: string[],
|
|
||||||
releaseGroup: ReleaseGroupWithName,
|
|
||||||
versionData: VersionData,
|
|
||||||
conventionalCommitsConfig: NxReleaseConfig['conventionalCommits']
|
|
||||||
): Promise<ReleaseVersionGeneratorResult['callback']> {
|
|
||||||
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) {
|
function printAndFlushChanges(tree: Tree, isDryRun: boolean) {
|
||||||
const changes = tree.listChanges();
|
const changes = tree.listChanges();
|
||||||
|
|
||||||
console.log('');
|
console.log('');
|
||||||
|
|
||||||
|
if (changes.length > 0) {
|
||||||
// Print the changes
|
// Print the changes
|
||||||
changes.forEach((f) => {
|
changes.forEach((f) => {
|
||||||
if (f.type === 'CREATE') {
|
if (f.type === 'CREATE') {
|
||||||
@ -670,91 +396,18 @@ function printAndFlushChanges(tree: Tree, isDryRun: boolean) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
let text = isDryRun ? ' would be ' : ' ';
|
||||||
|
output.warn({
|
||||||
|
title: `No files${text}changed as a result of running versioning`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (!isDryRun) {
|
if (!isDryRun) {
|
||||||
flushChanges(workspaceRoot, changes);
|
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<unknown>;
|
|
||||||
}
|
|
||||||
|
|
||||||
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(
|
function runPreVersionCommand(
|
||||||
preVersionCommand: string,
|
preVersionCommand: string,
|
||||||
{ dryRun, verbose }: { dryRun: boolean; verbose: boolean },
|
{ dryRun, verbose }: { dryRun: boolean; verbose: boolean },
|
||||||
@ -801,16 +454,3 @@ function runPreVersionCommand(
|
|||||||
process.exit(1);
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -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<ReturnType<typeof getLatestGitTagForPattern>>
|
||||||
|
| undefined,
|
||||||
|
fallbackCurrentVersionResolver?: 'disk',
|
||||||
|
preid?: string
|
||||||
|
): Promise<SemverBumpType> {
|
||||||
|
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;
|
||||||
|
}
|
||||||
@ -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,
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -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"
|
||||||
|
}
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
2646
packages/nx/src/command-line/release/version/release-version.spec.ts
Normal file
2646
packages/nx/src/command-line/release/version/release-version.spec.ts
Normal file
File diff suppressed because it is too large
Load Diff
@ -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"`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -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<ReturnType<typeof getLatestGitTagForPattern>>
|
||||||
|
): Promise<string | null> {
|
||||||
|
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<string> {
|
||||||
|
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<string> {
|
||||||
|
/**
|
||||||
|
* 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<ReturnType<typeof getLatestGitTagForPattern>>
|
||||||
|
): Promise<string> {
|
||||||
|
/**
|
||||||
|
* 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<string> {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
157
packages/nx/src/command-line/release/version/test-utils.spec.ts
Normal file
157
packages/nx/src/command-line/release/version/test-utils.spec.ts
Normal file
@ -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",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
||||||
644
packages/nx/src/command-line/release/version/test-utils.ts
Normal file
644
packages/nx/src/command-line/release/version/test-utils.ts
Normal file
@ -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<NxJsonConfiguration['release'], 'groups'>,
|
||||||
|
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> | (() => 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<boolean> {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateProjectDependencies(
|
||||||
|
tree: Tree,
|
||||||
|
_projectGraph: ProjectGraph,
|
||||||
|
dependenciesToUpdate: Record<string, string>
|
||||||
|
): Promise<string[]> {
|
||||||
|
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<string, unknown>,
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -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');
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -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<T>(
|
||||||
|
nodes: T[],
|
||||||
|
getEdges: (node: T) => T[]
|
||||||
|
): T[] {
|
||||||
|
const result: T[] = [];
|
||||||
|
const visited = new Set<T>();
|
||||||
|
const temp = new Set<T>();
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
440
packages/nx/src/command-line/release/version/version-actions.ts
Normal file
440
packages/nx/src/command-line/release/version/version-actions.ts
Normal file
@ -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<string, unknown>;
|
||||||
|
}
|
||||||
|
) => 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<void> {
|
||||||
|
// 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<string, unknown>} 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<string, unknown>,
|
||||||
|
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<ProjectGraphDependency[]> {
|
||||||
|
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<boolean>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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<string[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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<string, string>
|
||||||
|
): Promise<string[]>;
|
||||||
|
}
|
||||||
@ -2,16 +2,16 @@ import { existsSync } from 'fs';
|
|||||||
import { dirname, join } from 'path';
|
import { dirname, join } from 'path';
|
||||||
|
|
||||||
import type { ChangelogRenderOptions } from '../../release/changelog-renderer';
|
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 type { PackageManager } from '../utils/package-manager';
|
||||||
|
import { workspaceRoot } from '../utils/workspace-root';
|
||||||
import type {
|
import type {
|
||||||
InputDefinition,
|
InputDefinition,
|
||||||
TargetConfiguration,
|
TargetConfiguration,
|
||||||
TargetDependencyConfig,
|
TargetDependencyConfig,
|
||||||
} from './workspace-json-project-json';
|
} from './workspace-json-project-json';
|
||||||
|
|
||||||
import { readJsonFile } from '../utils/fileutils';
|
|
||||||
import { workspaceRoot } from '../utils/workspace-root';
|
|
||||||
|
|
||||||
export type ImplicitDependencyEntry<T = '*' | string[]> = {
|
export type ImplicitDependencyEntry<T = '*' | string[]> = {
|
||||||
[key: string]: T | ImplicitJsonSubsetDependency<T>;
|
[key: string]: T | ImplicitJsonSubsetDependency<T>;
|
||||||
};
|
};
|
||||||
@ -56,6 +56,12 @@ interface NxInstallationConfiguration {
|
|||||||
plugins?: Record<string, string>;
|
plugins?: Record<string, string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 {
|
export interface NxReleaseVersionConfiguration {
|
||||||
generator?: string;
|
generator?: string;
|
||||||
generatorOptions?: Record<string, unknown>;
|
generatorOptions?: Record<string, unknown>;
|
||||||
@ -71,6 +77,102 @@ export interface NxReleaseVersionConfiguration {
|
|||||||
*/
|
*/
|
||||||
conventionalCommits?: boolean;
|
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<string, unknown>;
|
||||||
|
/**
|
||||||
|
* 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<string, unknown>;
|
||||||
|
/**
|
||||||
|
* 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 {
|
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.
|
* Whether or not to automatically push the changes made by this command to the remote git repository.
|
||||||
*/
|
*/
|
||||||
push?: boolean;
|
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 {
|
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
|
* 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.
|
* 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
|
* 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;
|
automaticFromRef?: boolean;
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* If no version config is provided, we will assume that @nx/js:release-version
|
* If no version configuration is provided, we will assume that TypeScript/JavaScript experience is what is desired,
|
||||||
* is the desired generator implementation, allowing for terser config for the common case.
|
* 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;
|
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;
|
preVersionCommand?: string;
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import type { PackageJson } from '../utils/package-json';
|
|||||||
import type {
|
import type {
|
||||||
NxJsonConfiguration,
|
NxJsonConfiguration,
|
||||||
NxReleaseVersionConfiguration,
|
NxReleaseVersionConfiguration,
|
||||||
|
NxReleaseVersionV2Configuration,
|
||||||
} from './nx-json';
|
} from './nx-json';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -107,9 +108,19 @@ export interface ProjectConfiguration {
|
|||||||
* Project specific configuration for `nx release`
|
* Project specific configuration for `nx release`
|
||||||
*/
|
*/
|
||||||
release?: {
|
release?: {
|
||||||
version?: Pick<
|
version?:
|
||||||
NxReleaseVersionConfiguration,
|
| Pick<NxReleaseVersionConfiguration, 'generator' | 'generatorOptions'>
|
||||||
'generator' | 'generatorOptions'
|
| Pick<
|
||||||
|
// Expose a subset of version config options at the project level
|
||||||
|
NxReleaseVersionV2Configuration,
|
||||||
|
| 'versionActions'
|
||||||
|
| 'versionActionsOptions'
|
||||||
|
| 'manifestRootsToUpdate'
|
||||||
|
| 'currentVersionResolver'
|
||||||
|
| 'currentVersionResolverMetadata'
|
||||||
|
| 'fallbackCurrentVersionResolver'
|
||||||
|
| 'versionPrefix'
|
||||||
|
| 'preserveLocalDependencyProtocols'
|
||||||
>;
|
>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import { relative } from 'path';
|
|
||||||
import {
|
import {
|
||||||
addProjectConfiguration,
|
addProjectConfiguration,
|
||||||
ensurePackage,
|
ensurePackage,
|
||||||
@ -6,6 +5,7 @@ import {
|
|||||||
GeneratorCallback,
|
GeneratorCallback,
|
||||||
installPackagesTask,
|
installPackagesTask,
|
||||||
joinPathFragments,
|
joinPathFragments,
|
||||||
|
readNxJson,
|
||||||
readProjectConfiguration,
|
readProjectConfiguration,
|
||||||
runTasksInSerial,
|
runTasksInSerial,
|
||||||
Tree,
|
Tree,
|
||||||
@ -15,33 +15,35 @@ import {
|
|||||||
import { getRelativeCwd } from '@nx/devkit/src/generators/artifact-name-and-directory-utils';
|
import { getRelativeCwd } from '@nx/devkit/src/generators/artifact-name-and-directory-utils';
|
||||||
import { logShowProjectCommand } from '@nx/devkit/src/utils/log-show-project-command';
|
import { logShowProjectCommand } from '@nx/devkit/src/utils/log-show-project-command';
|
||||||
import { addTsConfigPath, initGenerator as jsInitGenerator } from '@nx/js';
|
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 {
|
import {
|
||||||
addReleaseConfigForNonTsSolution,
|
addReleaseConfigForNonTsSolution,
|
||||||
addReleaseConfigForTsSolution,
|
addReleaseConfigForTsSolution,
|
||||||
releaseTasks,
|
releaseTasks,
|
||||||
} from '@nx/js/src/generators/library/utils/add-release-config';
|
} 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 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) {
|
export async function libraryGenerator(host: Tree, schema: Schema) {
|
||||||
return await libraryGeneratorInternal(host, {
|
return await libraryGeneratorInternal(host, {
|
||||||
@ -258,7 +260,9 @@ export async function libraryGeneratorInternal(host: Tree, schema: Schema) {
|
|||||||
projectConfiguration
|
projectConfiguration
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
const nxJson = readNxJson(host);
|
||||||
await addReleaseConfigForNonTsSolution(
|
await addReleaseConfigForNonTsSolution(
|
||||||
|
shouldUseLegacyVersioning(nxJson.release),
|
||||||
host,
|
host,
|
||||||
options.name,
|
options.name,
|
||||||
projectConfiguration
|
projectConfiguration
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import {
|
|||||||
GeneratorCallback,
|
GeneratorCallback,
|
||||||
installPackagesTask,
|
installPackagesTask,
|
||||||
joinPathFragments,
|
joinPathFragments,
|
||||||
|
readJson,
|
||||||
readProjectConfiguration,
|
readProjectConfiguration,
|
||||||
runTasksInSerial,
|
runTasksInSerial,
|
||||||
toJS,
|
toJS,
|
||||||
@ -11,31 +12,32 @@ import {
|
|||||||
updateProjectConfiguration,
|
updateProjectConfiguration,
|
||||||
writeJson,
|
writeJson,
|
||||||
} from '@nx/devkit';
|
} 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 { getRelativeCwd } from '@nx/devkit/src/generators/artifact-name-and-directory-utils';
|
||||||
import { relative } from 'path';
|
import { logShowProjectCommand } from '@nx/devkit/src/utils/log-show-project-command';
|
||||||
import {
|
import { addTsConfigPath, initGenerator as jsInitGenerator } from '@nx/js';
|
||||||
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 {
|
import {
|
||||||
addReleaseConfigForNonTsSolution,
|
addReleaseConfigForNonTsSolution,
|
||||||
addReleaseConfigForTsSolution,
|
addReleaseConfigForTsSolution,
|
||||||
releaseTasks,
|
releaseTasks,
|
||||||
} from '@nx/js/src/generators/library/utils/add-release-config';
|
} 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 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) {
|
export function libraryGenerator(tree: Tree, schema: Schema) {
|
||||||
return libraryGeneratorInternal(tree, {
|
return libraryGeneratorInternal(tree, {
|
||||||
@ -178,7 +180,9 @@ export async function libraryGeneratorInternal(tree: Tree, schema: Schema) {
|
|||||||
projectConfig
|
projectConfig
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
const nxJson = readJson(tree, 'nx.json');
|
||||||
await addReleaseConfigForNonTsSolution(
|
await addReleaseConfigForNonTsSolution(
|
||||||
|
shouldUseLegacyVersioning(nxJson.release),
|
||||||
tree,
|
tree,
|
||||||
options.projectName,
|
options.projectName,
|
||||||
projectConfig
|
projectConfig
|
||||||
|
|||||||
671
pnpm-lock.yaml
generated
671
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user