fix(core): restore older nx core migrations for repair (#31254)

<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
<!-- This is the behavior we have today -->

These migrations should not have been removed as part of the Nx 21
update

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->

The migrations are restored

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #
This commit is contained in:
Jason Jean 2025-05-16 13:01:56 -04:00 committed by GitHub
parent e724eedc5e
commit a45ec7e0bd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 1165 additions and 0 deletions

View File

@ -3503,6 +3503,66 @@
"originalFilePath": "/packages/nx",
"path": "/nx-api/nx/migrations/19-2-0-move-graph-cache-directory",
"type": "migration"
},
"/nx-api/nx/migrations/move-default-base-to-nx-json-root": {
"description": "Moves affected.defaultBase to defaultBase in `nx.json`",
"file": "generated/packages/nx/migrations/move-default-base-to-nx-json-root.json",
"hidden": false,
"name": "move-default-base-to-nx-json-root",
"version": "18.1.0-beta.3",
"originalFilePath": "/packages/nx",
"path": "/nx-api/nx/migrations/move-default-base-to-nx-json-root",
"type": "migration"
},
"/nx-api/nx/migrations/18.0.0-disable-adding-plugins-for-existing-workspaces": {
"description": "Updates nx.json to disabled adding plugins when generating projects in an existing Nx workspace",
"file": "generated/packages/nx/migrations/18.0.0-disable-adding-plugins-for-existing-workspaces.json",
"hidden": false,
"name": "18.0.0-disable-adding-plugins-for-existing-workspaces",
"version": "18.0.0-beta.2",
"originalFilePath": "/packages/nx",
"path": "/nx-api/nx/migrations/18.0.0-disable-adding-plugins-for-existing-workspaces",
"type": "migration"
},
"/nx-api/nx/migrations/17.3.0-update-nx-wrapper": {
"description": "Updates the nx wrapper.",
"file": "generated/packages/nx/migrations/17.3.0-update-nx-wrapper.json",
"hidden": false,
"name": "17.3.0-update-nx-wrapper",
"version": "17.3.0-beta.6",
"originalFilePath": "/packages/nx",
"path": "/nx-api/nx/migrations/17.3.0-update-nx-wrapper",
"type": "migration"
},
"/nx-api/nx/migrations/rm-default-collection-npm-scope": {
"description": "Migration for v17.0.0-rc.1",
"file": "generated/packages/nx/migrations/rm-default-collection-npm-scope.json",
"hidden": false,
"name": "rm-default-collection-npm-scope",
"version": "17.0.0-rc.1",
"originalFilePath": "/packages/nx",
"path": "/nx-api/nx/migrations/rm-default-collection-npm-scope",
"type": "migration"
},
"/nx-api/nx/migrations/17.0.0-use-minimal-config-for-tasks-runner-options": {
"description": "Use minimal config for tasksRunnerOptions",
"file": "generated/packages/nx/migrations/17.0.0-use-minimal-config-for-tasks-runner-options.json",
"hidden": false,
"name": "17.0.0-use-minimal-config-for-tasks-runner-options",
"version": "17.0.0-beta.3",
"originalFilePath": "/packages/nx",
"path": "/nx-api/nx/migrations/17.0.0-use-minimal-config-for-tasks-runner-options",
"type": "migration"
},
"/nx-api/nx/migrations/17.0.0-move-cache-directory": {
"description": "Updates the default cache directory to .nx/cache",
"file": "generated/packages/nx/migrations/17.0.0-move-cache-directory.json",
"hidden": false,
"name": "17.0.0-move-cache-directory",
"version": "17.0.0-beta.1",
"originalFilePath": "/packages/nx",
"path": "/nx-api/nx/migrations/17.0.0-move-cache-directory",
"type": "migration"
}
},
"path": "/nx-api/nx"

View File

@ -3479,6 +3479,66 @@
"originalFilePath": "/packages/nx",
"path": "nx/migrations/19-2-0-move-graph-cache-directory",
"type": "migration"
},
{
"description": "Moves affected.defaultBase to defaultBase in `nx.json`",
"file": "generated/packages/nx/migrations/move-default-base-to-nx-json-root.json",
"hidden": false,
"name": "move-default-base-to-nx-json-root",
"version": "18.1.0-beta.3",
"originalFilePath": "/packages/nx",
"path": "nx/migrations/move-default-base-to-nx-json-root",
"type": "migration"
},
{
"description": "Updates nx.json to disabled adding plugins when generating projects in an existing Nx workspace",
"file": "generated/packages/nx/migrations/18.0.0-disable-adding-plugins-for-existing-workspaces.json",
"hidden": false,
"name": "18.0.0-disable-adding-plugins-for-existing-workspaces",
"version": "18.0.0-beta.2",
"originalFilePath": "/packages/nx",
"path": "nx/migrations/18.0.0-disable-adding-plugins-for-existing-workspaces",
"type": "migration"
},
{
"description": "Updates the nx wrapper.",
"file": "generated/packages/nx/migrations/17.3.0-update-nx-wrapper.json",
"hidden": false,
"name": "17.3.0-update-nx-wrapper",
"version": "17.3.0-beta.6",
"originalFilePath": "/packages/nx",
"path": "nx/migrations/17.3.0-update-nx-wrapper",
"type": "migration"
},
{
"description": "Migration for v17.0.0-rc.1",
"file": "generated/packages/nx/migrations/rm-default-collection-npm-scope.json",
"hidden": false,
"name": "rm-default-collection-npm-scope",
"version": "17.0.0-rc.1",
"originalFilePath": "/packages/nx",
"path": "nx/migrations/rm-default-collection-npm-scope",
"type": "migration"
},
{
"description": "Use minimal config for tasksRunnerOptions",
"file": "generated/packages/nx/migrations/17.0.0-use-minimal-config-for-tasks-runner-options.json",
"hidden": false,
"name": "17.0.0-use-minimal-config-for-tasks-runner-options",
"version": "17.0.0-beta.3",
"originalFilePath": "/packages/nx",
"path": "nx/migrations/17.0.0-use-minimal-config-for-tasks-runner-options",
"type": "migration"
},
{
"description": "Updates the default cache directory to .nx/cache",
"file": "generated/packages/nx/migrations/17.0.0-move-cache-directory.json",
"hidden": false,
"name": "17.0.0-move-cache-directory",
"version": "17.0.0-beta.1",
"originalFilePath": "/packages/nx",
"path": "nx/migrations/17.0.0-move-cache-directory",
"type": "migration"
}
],
"githubRoot": "https://github.com/nrwl/nx/blob/master",

View File

@ -0,0 +1,13 @@
{
"name": "17.0.0-move-cache-directory",
"cli": "nx",
"version": "17.0.0-beta.1",
"description": "Updates the default cache directory to .nx/cache",
"implementation": "/packages/nx/src/migrations/update-17-0-0/move-cache-directory.ts",
"aliases": [],
"hidden": false,
"path": "/packages/nx",
"schema": null,
"type": "migration",
"examplesFile": "#### Sample Code Changes\n\nAdd `.nx/cache` to the `.gitignore` file.\n\n{% tabs %}\n{% tab label=\"Before\" %}\n\n```{% fileName=\".gitignore\" %}\nnode_modules\n```\n\n{% /tab %}\n{% tab label=\"After\" %}\n\n```{% highlightLines=[2] fileName=\".gitignore\" %}\nnode_modules\n.nx/cache\n```\n\n{% /tab %}\n{% /tabs %}\n\nAdd `.nx/cache` to the `.prettierignore` file.\n\n{% tabs %}\n{% tab label=\"Before\" %}\n\n```ts {% fileName=\".prettierignore\" %}\n/dist\n```\n\n{% /tab %}\n{% tab label=\"After\" %}\n\n```ts {% highlightLines=[2] fileName=\".prettierignore\" %}\n/dist\n.nx/cache\n```\n\n{% /tab %}\n{% /tabs %}\n"
}

View File

@ -0,0 +1,13 @@
{
"name": "17.0.0-use-minimal-config-for-tasks-runner-options",
"cli": "nx",
"version": "17.0.0-beta.3",
"description": "Use minimal config for tasksRunnerOptions",
"implementation": "/packages/nx/src/migrations/update-17-0-0/use-minimal-config-for-tasks-runner-options.ts",
"aliases": [],
"hidden": false,
"path": "/packages/nx",
"schema": null,
"type": "migration",
"examplesFile": ""
}

View File

@ -0,0 +1,13 @@
{
"name": "17.3.0-update-nx-wrapper",
"cli": "nx",
"version": "17.3.0-beta.6",
"description": "Updates the nx wrapper.",
"implementation": "/packages/nx/src/migrations/update-17-3-0/update-nxw.ts",
"aliases": [],
"hidden": false,
"path": "/packages/nx",
"schema": null,
"type": "migration",
"examplesFile": ""
}

View File

@ -0,0 +1,14 @@
{
"name": "18.0.0-disable-adding-plugins-for-existing-workspaces",
"cli": "nx",
"version": "18.0.0-beta.2",
"description": "Updates nx.json to disabled adding plugins when generating projects in an existing Nx workspace",
"implementation": "/packages/nx/src/migrations/update-18-0-0/disable-crystal-for-existing-workspaces.ts",
"x-repair-skip": true,
"aliases": [],
"hidden": false,
"path": "/packages/nx",
"schema": null,
"type": "migration",
"examplesFile": ""
}

View File

@ -0,0 +1,12 @@
{
"name": "move-default-base-to-nx-json-root",
"version": "18.1.0-beta.3",
"description": "Moves affected.defaultBase to defaultBase in `nx.json`",
"implementation": "/packages/nx/src/migrations/update-17-2-0/move-default-base.ts",
"aliases": [],
"hidden": false,
"path": "/packages/nx",
"schema": null,
"type": "migration",
"examplesFile": ""
}

View File

@ -0,0 +1,12 @@
{
"name": "rm-default-collection-npm-scope",
"version": "17.0.0-rc.1",
"description": "Migration for v17.0.0-rc.1",
"implementation": "/packages/nx/src/migrations/update-17-0-0/rm-default-collection-npm-scope.ts",
"aliases": [],
"hidden": false,
"path": "/packages/nx",
"schema": null,
"type": "migration",
"examplesFile": ""
}

View File

@ -1,5 +1,40 @@
{
"generators": {
"17.0.0-move-cache-directory": {
"cli": "nx",
"version": "17.0.0-beta.1",
"description": "Updates the default cache directory to .nx/cache",
"implementation": "./src/migrations/update-17-0-0/move-cache-directory"
},
"17.0.0-use-minimal-config-for-tasks-runner-options": {
"cli": "nx",
"version": "17.0.0-beta.3",
"description": "Use minimal config for tasksRunnerOptions",
"implementation": "./src/migrations/update-17-0-0/use-minimal-config-for-tasks-runner-options"
},
"rm-default-collection-npm-scope": {
"version": "17.0.0-rc.1",
"description": "Migration for v17.0.0-rc.1",
"implementation": "./src/migrations/update-17-0-0/rm-default-collection-npm-scope"
},
"17.3.0-update-nx-wrapper": {
"cli": "nx",
"version": "17.3.0-beta.6",
"description": "Updates the nx wrapper.",
"implementation": "./src/migrations/update-17-3-0/update-nxw"
},
"18.0.0-disable-adding-plugins-for-existing-workspaces": {
"cli": "nx",
"version": "18.0.0-beta.2",
"description": "Updates nx.json to disabled adding plugins when generating projects in an existing Nx workspace",
"implementation": "./src/migrations/update-18-0-0/disable-crystal-for-existing-workspaces",
"x-repair-skip": true
},
"move-default-base-to-nx-json-root": {
"version": "18.1.0-beta.3",
"description": "Moves affected.defaultBase to defaultBase in `nx.json`",
"implementation": "./src/migrations/update-17-2-0/move-default-base"
},
"19-2-0-move-graph-cache-directory": {
"cli": "nx",
"version": "19.2.0-beta.2",

View File

@ -0,0 +1,41 @@
#### Sample Code Changes
Add `.nx/cache` to the `.gitignore` file.
{% tabs %}
{% tab label="Before" %}
```{% fileName=".gitignore" %}
node_modules
```
{% /tab %}
{% tab label="After" %}
```{% highlightLines=[2] fileName=".gitignore" %}
node_modules
.nx/cache
```
{% /tab %}
{% /tabs %}
Add `.nx/cache` to the `.prettierignore` file.
{% tabs %}
{% tab label="Before" %}
```ts {% fileName=".prettierignore" %}
/dist
```
{% /tab %}
{% tab label="After" %}
```ts {% highlightLines=[2] fileName=".prettierignore" %}
/dist
.nx/cache
```
{% /tab %}
{% /tabs %}

View File

@ -0,0 +1,71 @@
import { createTree } from '../../generators/testing-utils/create-tree';
import { createTreeWithEmptyWorkspace } from '../../generators/testing-utils/create-tree-with-empty-workspace';
import migrate from './move-cache-directory';
describe('move-cache-directory', () => {
it('should add .nx/cache to the gitignore', () => {
const tree = createTreeWithEmptyWorkspace();
tree.write('.gitignore', 'node_modules');
migrate(tree);
expect(tree.read('.gitignore', 'utf-8')).toMatchInlineSnapshot(`
"node_modules
.nx/cache"
`);
});
it('should work if .gitignore is not present', () => {
const tree = createTreeWithEmptyWorkspace();
tree.delete('.gitignore');
migrate(tree);
expect(tree.read('.gitignore', 'utf-8')).toMatchInlineSnapshot(
`".nx/cache"`
);
});
it('should not change gitignore if directly ignored', () => {
const tree = createTreeWithEmptyWorkspace();
tree.write('.gitignore', 'node_modules\n.nx/cache');
migrate(tree);
expect(tree.read('.gitignore', 'utf-8')).toMatchInlineSnapshot(`
"node_modules
.nx/cache"
`);
});
it('should not change gitignore if ignored by another pattern', () => {
const tree = createTreeWithEmptyWorkspace();
tree.write('.gitignore', 'node_modules\n.*/cache');
migrate(tree);
expect(tree.read('.gitignore', 'utf-8')).toMatchInlineSnapshot(`
"node_modules
.*/cache"
`);
});
it('should not update gitignore for lerna repos without nx.json', () => {
const tree = createTree();
tree.write('.gitignore', 'node_modules');
tree.write('lerna.json', '{}');
migrate(tree);
expect(tree.read('.gitignore', 'utf-8')).toMatchInlineSnapshot(
`"node_modules"`
);
});
it('should handle prettierignore', () => {
const tree = createTree();
tree.write('.prettierignore', '/dist');
migrate(tree);
expect(tree.read('.prettierignore', 'utf-8')).toMatchInlineSnapshot(`
"/dist
/.nx/cache"
`);
});
it('should handle missing prettierignore', () => {
const tree = createTree();
tree.delete('.prettierignore');
migrate(tree);
expect(tree.exists('.prettierignore')).toBeFalsy();
});
});

View File

@ -0,0 +1,37 @@
import { Tree } from '../../generators/tree';
import ignore from 'ignore';
export default function moveCacheDirectory(tree: Tree) {
// If nx.json doesn't exist the repo can't utilize
// caching, so .nx/cache is less relevant. Lerna users
// that don't want to fully opt in to Nx at this time
// may also be caught off guard by the appearance of
// a .nx directory, so we are going to special case
// this for the time being.
if (tree.exists('lerna.json') && !tree.exists('nx.json')) {
return;
}
updateGitIgnore(tree);
if (tree.exists('.prettierignore')) {
const ignored = tree.read('.prettierignore', 'utf-8');
if (!ignored.includes('.nx/cache')) {
tree.write('.prettierignore', [ignored, '/.nx/cache'].join('\n'));
}
}
}
function updateGitIgnore(tree: Tree) {
const gitignore = tree.exists('.gitignore')
? tree.read('.gitignore', 'utf-8')
: '';
const ig = ignore();
ig.add(gitignore);
if (!ig.ignores('.nx/cache')) {
const updatedLines = gitignore.length
? [gitignore, '.nx/cache']
: ['.nx/cache'];
tree.write('.gitignore', updatedLines.join('\n'));
}
}

View File

@ -0,0 +1,53 @@
import { createTree } from '../../generators/testing-utils/create-tree';
import update from './rm-default-collection-npm-scope';
import { readJson, updateJson, writeJson } from '../../generators/utils/json';
import { NxJsonConfiguration } from '../../config/nx-json';
import { Tree } from '../../generators/tree';
describe('rm-default-collection-npm-scope migration', () => {
let tree: Tree;
beforeEach(() => {
tree = createTree();
});
describe('with nx.json', () => {
beforeEach(() => {
writeJson(tree, 'nx.json', {
affected: {
defaultBase: 'master',
},
npmScope: 'scope',
cli: {
defaultCollection: 'collection',
},
} as NxJsonConfiguration & { npmScope: string; cli: { defaultCollection: string } });
});
it('should remove npmScope', async () => {
await update(tree);
expect(readJson(tree, 'nx.json').npmScope).not.toBeDefined();
});
it('should remove defaultCollection', async () => {
updateJson<NxJsonConfiguration>(tree, 'nx.json', (json) => {
json.cli.packageManager = 'npm';
return json;
});
await update(tree);
expect(readJson(tree, 'nx.json').cli).toEqual({
packageManager: 'npm',
});
});
it('should remove cli', async () => {
await update(tree);
expect(readJson(tree, 'nx.json').cli).not.toBeDefined();
});
});
describe('without nx.json', () => {
it('should run successfully', async () => {
await update(tree);
});
});
});

View File

@ -0,0 +1,96 @@
import { formatChangedFilesWithPrettierIfAvailable } from '../../generators/internal-utils/format-changed-files-with-prettier-if-available';
import { Tree } from '../../generators/tree';
import { readNxJson, updateNxJson } from '../../generators/utils/nx-json';
import { readJson } from '../../generators/utils/json';
import { output } from '../../utils/output';
import { NxJsonConfiguration } from '../../config/nx-json';
import { joinPathFragments } from '../../utils/path';
export default async function update(tree: Tree) {
if (!tree.exists('nx.json')) {
return;
}
const nxJson = readNxJson(tree);
delete nxJson.cli?.['defaultCollection'];
if (nxJson?.cli && Object.keys(nxJson.cli).length < 1) {
delete nxJson.cli;
}
warnNpmScopeHasChanged(tree, nxJson);
delete nxJson['npmScope'];
updateNxJson(tree, nxJson);
await formatChangedFilesWithPrettierIfAvailable(tree);
}
function warnNpmScopeHasChanged(
tree: Tree,
nxJson: NxJsonConfiguration
): boolean {
const originalScope = nxJson['npmScope'];
// There was no original scope
if (!originalScope) {
return false;
}
// package.json does not exist
if (!tree.exists('package.json')) {
return false;
}
const newScope = getNpmScopeFromPackageJson(tree);
// New and Original scope are the same.
if (originalScope === newScope) {
return false;
}
const packageJsonName = readJson(tree, 'package.json').name;
if (newScope) {
output.warn({
title: 'npmScope has been removed from nx.json',
bodyLines: [
'This will now be read from package.json',
`Old value which was in nx.json: ${originalScope}`,
`New value from package.json: ${newScope}`,
`Typescript path mappings for new libraries will now be generated as such: @${newScope}/new-lib instead of @${originalScope}/new-lib`,
`If you would like to change this back, change the name in package.json to ${packageJsonName.replace(
newScope,
originalScope
)}`,
],
});
} else {
// There is no scope in package.json
output.warn({
title: 'npmScope has been removed from nx.json',
bodyLines: [
'This will now be read from package.json',
`Old value which was in nx.json: ${originalScope}`,
`New value from package.json: null`,
`Typescript path mappings for new libraries will now be generated as such: new-lib instead of @${originalScope}/new-lib`,
`If you would like to change this back, change the name in package.json to ${joinPathFragments(
`@${originalScope}`,
packageJsonName
)}`,
],
});
}
}
function getNpmScopeFromPackageJson(tree: Tree) {
const { name } = tree.exists('package.json')
? readJson<{ name?: string }>(tree, 'package.json')
: { name: null };
if (name?.startsWith('@')) {
return name.split('/')[0].substring(1);
}
}

View File

@ -0,0 +1,228 @@
import { NxJsonConfiguration } from '../../config/nx-json';
import { createTreeWithEmptyWorkspace } from '../../generators/testing-utils/create-tree-with-empty-workspace';
import { readJson, writeJson } from '../../generators/utils/json';
import { Tree } from '../../generators/tree';
const verifyOrUpdateNxCloudClient = jest.fn();
jest.mock('../../nx-cloud/update-manager', () => ({
...jest.requireActual('../../nx-cloud/update-manager'),
verifyOrUpdateNxCloudClient,
}));
import migrate from './use-minimal-config-for-tasks-runner-options';
describe('use-minimal-config-for-tasks-runner-options migration', () => {
let tree: Tree;
beforeEach(() => {
tree = createTreeWithEmptyWorkspace();
});
it('should update nx.json with minimal config', async () => {
writeJson<NxJsonConfiguration>(tree, 'nx.json', {
tasksRunnerOptions: {
default: {
runner: 'nx/tasks-runners/default',
options: {
cacheableOperations: ['build', 'test'],
},
},
},
});
await migrate(tree);
const nxJson = readJson<NxJsonConfiguration>(tree, 'nx.json');
expect(nxJson.tasksRunnerOptions).toEqual(undefined);
expect(nxJson.targetDefaults).toMatchInlineSnapshot(`
{
"build": {
"cache": true,
},
"test": {
"cache": true,
},
}
`);
});
it('should not update nx.json if there are multiple tasks runners', async () => {
writeJson<NxJsonConfiguration>(tree, 'nx.json', {
tasksRunnerOptions: {
default: {
runner: 'nx/tasks-runners/default',
options: {},
},
custom: {
runner: 'custom',
options: {},
},
},
});
await migrate(tree);
const nxJson = readJson<NxJsonConfiguration>(tree, 'nx.json');
expect(nxJson.tasksRunnerOptions).toEqual({
default: {
runner: 'nx/tasks-runners/default',
options: {},
},
custom: {
runner: 'custom',
options: {},
},
});
});
it('should move nxCloudAccessToken and nxCloudUrl for nx-cloud', async () => {
writeJson<NxJsonConfiguration>(tree, 'nx.json', {
tasksRunnerOptions: {
default: {
runner: 'nx-cloud',
options: {
accessToken: 'abc123',
url: 'https://nx.app',
encryptionKey: 'secret',
},
},
},
});
writeJson(tree, 'package.json', {
devDependencies: {
'nx-cloud': 'latest',
nx: 'latest',
},
});
await migrate(tree);
const nxJson = readJson<NxJsonConfiguration>(tree, 'nx.json');
expect(nxJson.nxCloudAccessToken).toEqual('abc123');
expect(nxJson.nxCloudUrl).toEqual('https://nx.app');
expect(nxJson.nxCloudEncryptionKey).toEqual('secret');
expect(nxJson.tasksRunnerOptions).not.toBeDefined();
expect(readJson(tree, 'package.json').devDependencies).toEqual({
nx: 'latest',
});
});
it('should move nxCloudAccessToken and nxCloudUrl for @nrwl/nx-cloud', async () => {
writeJson<NxJsonConfiguration>(tree, 'nx.json', {
tasksRunnerOptions: {
default: {
runner: '@nrwl/nx-cloud',
options: {
accessToken: 'abc123',
url: 'https://nx.app',
maskedProperties: 'secret',
},
},
},
});
await migrate(tree);
const nxJson = readJson<NxJsonConfiguration>(tree, 'nx.json');
expect(nxJson.nxCloudAccessToken).toEqual('abc123');
expect(nxJson.nxCloudUrl).toEqual('https://nx.app');
expect(nxJson.tasksRunnerOptions.default.options).toMatchInlineSnapshot(`
{
"maskedProperties": "secret",
}
`);
expect(nxJson.tasksRunnerOptions.default.runner).not.toBeDefined();
});
it('should add useLightClient false for outdated enterprise customers', async () => {
writeJson<NxJsonConfiguration>(tree, 'nx.json', {
tasksRunnerOptions: {
default: {
runner: 'nx-cloud',
options: {
accessToken: 'abc123',
url: 'https://nx-cloud.example.com',
},
},
},
});
verifyOrUpdateNxCloudClient.mockImplementation(() => {
throw new Error();
});
await migrate(tree);
const nxJson = readJson<NxJsonConfiguration>(tree, 'nx.json');
expect(nxJson.nxCloudAccessToken).toEqual('abc123');
expect(nxJson.nxCloudUrl).toEqual('https://nx-cloud.example.com');
expect(nxJson.tasksRunnerOptions.default.options).toMatchInlineSnapshot(`
{
"useLightClient": false,
}
`);
expect(nxJson.tasksRunnerOptions.default.runner).not.toBeDefined();
});
it('should not update accessToken if runner is not nx-cloud', async () => {
writeJson<NxJsonConfiguration>(tree, 'nx.json', {
tasksRunnerOptions: {
default: {
runner: 'custom',
options: {
cacheDirectory: '.nx/cache',
useDaemonProcess: false,
accessToken: 'xxxx-xxx-xxxx',
},
},
},
});
await migrate(tree);
expect(readJson<NxJsonConfiguration>(tree, 'nx.json'))
.toMatchInlineSnapshot(`
{
"cacheDirectory": ".nx/cache",
"tasksRunnerOptions": {
"default": {
"options": {
"accessToken": "xxxx-xxx-xxxx",
},
"runner": "custom",
},
},
"useDaemonProcess": false,
}
`);
});
it('should work if nx.json does not exist', async () => {
tree.delete('nx.json');
await migrate(tree);
expect(tree.exists('nx.json')).toEqual(false);
});
it('should not throw is cacheableOperations is an unexpected type', async () => {
writeJson<NxJsonConfiguration>(tree, 'nx.json', {
tasksRunnerOptions: {
default: {
runner: 'nx/tasks-runners/default',
options: {
cacheableOperations: 'invalid',
},
},
},
});
await migrate(tree);
const nxJson = readJson<NxJsonConfiguration>(tree, 'nx.json');
expect(nxJson.tasksRunnerOptions).toMatchInlineSnapshot(`
{
"default": {
"options": {
"cacheableOperations": "invalid",
},
},
}
`);
});
});

View File

@ -0,0 +1,145 @@
import { updateJson } from '../../generators/utils/json';
import { Tree } from '../../generators/tree';
import { NxJsonConfiguration } from '../../config/nx-json';
import { PackageJson } from '../../utils/package-json';
import { formatChangedFilesWithPrettierIfAvailable } from '../../generators/internal-utils/format-changed-files-with-prettier-if-available';
import { readNxJson } from '../../generators/utils/nx-json';
import {
NxCloudEnterpriseOutdatedError,
verifyOrUpdateNxCloudClient,
} from '../../nx-cloud/update-manager';
import { getRunnerOptions } from '../../tasks-runner/run-command';
import { output } from '../../utils/output';
export default async function migrate(tree: Tree) {
if (!tree.exists('nx.json')) {
return;
}
const nxJson = readNxJson(tree);
// Already migrated
if (!nxJson.tasksRunnerOptions?.default) {
return;
}
const nxCloudClientSupported = await isNxCloudClientSupported(nxJson);
updateJson<NxJsonConfiguration>(tree, 'nx.json', (nxJson) => {
const { runner, options } = nxJson.tasksRunnerOptions.default;
// This property shouldn't ever be part of tasks runner options.
if (options.useDaemonProcess !== undefined) {
nxJson.useDaemonProcess = options.useDaemonProcess;
delete options.useDaemonProcess;
}
// Remaining keys may be specific to a given runner, so leave them alone if there are multiple runners.
if (Object.keys(nxJson.tasksRunnerOptions ?? {}).length > 1) {
return nxJson;
}
// These options can only be moved for nx-cloud.
if (runner === 'nx-cloud' || runner === '@nrwl/nx-cloud') {
nxJson.nxCloudAccessToken = options.accessToken;
delete options.accessToken;
if (options.url) {
nxJson.nxCloudUrl = options.url;
delete options.url;
}
if (nxCloudClientSupported) {
removeNxCloudDependency(tree);
} else {
options.useLightClient = false;
}
if (options.encryptionKey) {
nxJson.nxCloudEncryptionKey = options.encryptionKey;
delete options.encryptionKey;
}
}
// These options should be safe to move for all tasks runners:
if (options.parallel !== undefined) {
nxJson.parallel = options.parallel;
delete options.parallel;
}
if (options.cacheDirectory !== undefined) {
nxJson.cacheDirectory = options.cacheDirectory;
delete options.cacheDirectory;
}
if (Array.isArray(options.cacheableOperations)) {
nxJson.targetDefaults ??= {};
for (const target of options.cacheableOperations) {
nxJson.targetDefaults[target] ??= {};
nxJson.targetDefaults[target].cache ??= true;
}
delete options.cacheableOperations;
}
if (
['nx-cloud', '@nrwl/nx-cloud', 'nx/tasks-runners/default'].includes(
runner
)
) {
delete nxJson.tasksRunnerOptions.default.runner;
if (Object.values(options).length === 0) {
delete nxJson.tasksRunnerOptions;
}
}
return nxJson;
});
await formatChangedFilesWithPrettierIfAvailable(tree);
}
async function isNxCloudClientSupported(nxJson: NxJsonConfiguration) {
const nxCloudOptions = getRunnerOptions('default', nxJson, {}, true);
// Non enterprise workspaces support the Nx Cloud Client
if (!isNxCloudEnterpriseWorkspace(nxJson)) {
return true;
}
// If we can get the nx cloud client, it's supported
try {
await verifyOrUpdateNxCloudClient(nxCloudOptions);
return true;
} catch (e) {
if (e instanceof NxCloudEnterpriseOutdatedError) {
output.warn({
title: 'Nx Cloud Instance is outdated.',
bodyLines: [
'If you are an Nx Enterprise customer, please reach out to your assigned Developer Productivity Engineer.',
'If you are NOT an Nx Enterprise customer but are seeing this message, please reach out to cloud-support@nrwl.io.',
],
});
}
return false;
}
}
function isNxCloudEnterpriseWorkspace(nxJson: NxJsonConfiguration) {
const { runner, options } = nxJson.tasksRunnerOptions.default;
return (
(runner === 'nx-cloud' || runner === '@nrwl/nx-cloud') &&
options.url &&
![
'https://nx.app',
'https://cloud.nx.app',
'https://staging.nx.app',
'https://snapshot.nx.app',
].includes(options.url)
);
}
function removeNxCloudDependency(tree: Tree) {
if (tree.exists('package.json')) {
updateJson<PackageJson>(tree, 'package.json', (packageJson) => {
delete packageJson.dependencies?.['nx-cloud'];
delete packageJson.devDependencies?.['nx-cloud'];
delete packageJson.dependencies?.['@nrwl/nx-cloud'];
delete packageJson.devDependencies?.['@nrwl/nx-cloud'];
return packageJson;
});
}
}

View File

@ -0,0 +1,47 @@
import { createTreeWithEmptyWorkspace } from '../../generators/testing-utils/create-tree-with-empty-workspace';
import { Tree } from '../../generators/tree';
import { readNxJson, updateNxJson } from '../../generators/utils/nx-json';
import update from './move-default-base';
describe('update-17.1.0 migration', () => {
let tree: Tree;
beforeEach(() => {
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
});
it("shouldn't do anything if affected.defaultBase is not set", async () => {
tree.write('nx.json', JSON.stringify({}));
await update(tree);
expect(readNxJson(tree)).toEqual({});
});
it("shouldn't remove affected if other keys present", async () => {
updateNxJson(tree, {
affected: {
defaultBase: 'master',
otherKey: 'otherValue',
} as any,
});
await update(tree);
expect(readNxJson(tree)).toEqual({
affected: {
otherKey: 'otherValue',
},
defaultBase: 'master',
});
});
it('should remove affected if no other keys present', async () => {
updateNxJson(tree, {
affected: {
defaultBase: 'master',
} as any,
});
await update(tree);
expect(readNxJson(tree)).toEqual({
defaultBase: 'master',
});
});
});

View File

@ -0,0 +1,23 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { readNxJson, updateNxJson } from '../../generators/utils/nx-json';
import { Tree } from '../../generators/tree';
import { NxJsonConfiguration } from '../../config/nx-json';
import { formatChangedFilesWithPrettierIfAvailable } from '../../generators/internal-utils/format-changed-files-with-prettier-if-available';
/**
* Updates existing workspaces to move nx.json's affected.defaultBase to nx.json's base.
*/
export default async function update(host: Tree) {
const nxJson = readNxJson(host) as NxJsonConfiguration & {
affected: { defaultBase?: string };
};
if (nxJson?.affected?.defaultBase) {
nxJson.defaultBase = nxJson.affected.defaultBase;
delete nxJson.affected.defaultBase;
if (Object.keys(nxJson.affected).length === 0) {
delete nxJson.affected;
}
updateNxJson(host, nxJson);
}
await formatChangedFilesWithPrettierIfAvailable(host);
}

View File

@ -0,0 +1,104 @@
import { createTreeWithEmptyWorkspace } from '../../generators/testing-utils/create-tree-with-empty-workspace';
import { Tree } from '../../generators/tree';
import { readJson, writeJson } from '../../generators/utils/json';
import nxReleasePath from './nx-release-path';
describe('nxReleasePath', () => {
let tree: Tree;
beforeEach(() => {
tree = createTreeWithEmptyWorkspace();
});
it('should update changelog renderer references', () => {
writeJson(tree, 'nx.json', {
$schema: './node_modules/nx/schemas/nx-schema.json',
release: {
changelog: {
git: {
commit: true,
tag: true,
},
workspaceChangelog: {
createRelease: 'github',
renderer: 'nx/changelog-renderer',
},
projectChangelogs: {
renderer: 'nx/changelog-renderer',
},
},
version: {
generatorOptions: {
currentVersionResolver: 'git-tag',
specifierSource: 'conventional-commits',
},
},
},
});
tree.write(
'some-script.js',
`
import { releaseVersion } from 'nx/src/command-line/release';
const { releaseChangelog } = require("nx/src/command-line/release");
`
);
// these should not be updated, only the formalized programmatic API
tree.write(
'some-other-file.ts',
`
import { foo } from 'nx/src/command-line/release/nested/thing';
const { releaseChangelog } = require("nx/src/command-line/release/another/nested/thing");
`
);
nxReleasePath(tree);
// intentionally unchanged
expect(tree.read('some-other-file.ts').toString('utf-8'))
.toMatchInlineSnapshot(`
"
import { foo } from 'nx/src/command-line/release/nested/thing';
const { releaseChangelog } = require("nx/src/command-line/release/another/nested/thing");
"
`);
// programmatic API should be updated to nx/release
expect(tree.read('some-script.js').toString('utf-8'))
.toMatchInlineSnapshot(`
"
import { releaseVersion } from 'nx/release';
const { releaseChangelog } = require("nx/release");
"
`);
// nx/changelog-renderer references should be updated to nx/release/changelog-renderer
expect(readJson(tree, 'nx.json')).toMatchInlineSnapshot(`
{
"$schema": "./node_modules/nx/schemas/nx-schema.json",
"release": {
"changelog": {
"git": {
"commit": true,
"tag": true,
},
"projectChangelogs": {
"renderer": "nx/release/changelog-renderer",
},
"workspaceChangelog": {
"createRelease": "github",
"renderer": "nx/release/changelog-renderer",
},
},
"version": {
"generatorOptions": {
"currentVersionResolver": "git-tag",
"specifierSource": "conventional-commits",
},
},
},
}
`);
});
});

View File

@ -0,0 +1,51 @@
import { join, relative, sep } from 'node:path';
import { Tree } from '../../generators/tree';
import { getIgnoreObject } from '../../utils/ignore';
export default function nxReleasePath(tree: Tree) {
visitNotIgnoredFiles(tree, '', (file) => {
const contents = tree.read(file).toString('utf-8');
if (
// the deep import usage should be replaced by the new location
contents.includes('nx/src/command-line/release') ||
// changelog-renderer has moved into nx/release
contents.includes('nx/changelog-renderer')
) {
const finalContents = contents
// replace instances of old changelog renderer location
.replace(/nx\/changelog-renderer/g, 'nx/release/changelog-renderer')
// replace instances of deep import for programmatic API (only perform the replacement if an actual import by checking for trailing ' or ")
.replace(/nx\/src\/command-line\/release(['"])/g, 'nx/release$1');
tree.write(file, finalContents);
}
});
}
// Adapted from devkit
export function visitNotIgnoredFiles(
tree: Tree,
dirPath: string = tree.root,
visitor: (path: string) => void
): void {
const ig = getIgnoreObject();
dirPath = normalizePathRelativeToRoot(dirPath, tree.root);
if (dirPath !== '' && ig?.ignores(dirPath)) {
return;
}
for (const child of tree.children(dirPath)) {
const fullPath = join(dirPath, child);
if (ig?.ignores(fullPath)) {
continue;
}
if (tree.isFile(fullPath)) {
visitor(fullPath);
} else {
visitNotIgnoredFiles(tree, fullPath, visitor);
}
}
}
// Copied from devkit
function normalizePathRelativeToRoot(path: string, root: string): string {
return relative(root, join(root, path)).split(sep).join('/');
}

View File

@ -0,0 +1,26 @@
import { createTreeWithEmptyWorkspace } from '../../generators/testing-utils/create-tree-with-empty-workspace';
import migrate from './disable-crystal-for-existing-workspaces';
describe('disable crystal for existing workspaces', () => {
it('should add flag to nx.json', async () => {
const tree = createTreeWithEmptyWorkspace();
await migrate(tree);
expect(tree.read('nx.json', 'utf-8')).toMatchInlineSnapshot(`
"{
"affected": {
"defaultBase": "main"
},
"targetDefaults": {
"build": {
"cache": true
},
"lint": {
"cache": true
}
},
"useInferencePlugins": false
}
"
`);
});
});

View File

@ -0,0 +1,11 @@
import { readNxJson, updateNxJson } from '../../generators/utils/nx-json';
import { Tree } from '../../generators/tree';
import { formatChangedFilesWithPrettierIfAvailable } from '../../generators/internal-utils/format-changed-files-with-prettier-if-available';
export default async function migrate(tree: Tree) {
const nxJson = readNxJson(tree);
nxJson.useInferencePlugins = false;
updateNxJson(tree, nxJson);
await formatChangedFilesWithPrettierIfAvailable(tree);
}