feat(core): respect packageGroup in nx migrate (#9667)
* cleanup(core): refactor migrate.ts * feat(core): respect packageGroup in nx migrate ISSUES CLOSED: #4575 * fix build * chore(repo): trigger CI * chore(core): fix review comments, without packageGroup extension * chore(core): revert `withTempNpmDirectory` changes * chore(repo): trigger ci * chore(core): fix unit tests * chore(core): remove view and pack from pmc * chore(repo): kick off CI * chore(core): add tests for recursive packageGroup * chore(core): add another test for cyclic packageGroup * chore(repo): kickoff CI Co-authored-by: Craigory Coppola <craigorycoppola@gmail.com>
This commit is contained in:
parent
0e68c616fe
commit
32b49b6f7c
@ -15,7 +15,7 @@ import { packagesWeCareAbout } from 'nx/src/command-line/report';
|
|||||||
describe('Cli', () => {
|
describe('Cli', () => {
|
||||||
beforeEach(() => newProject());
|
beforeEach(() => newProject());
|
||||||
|
|
||||||
it('vvvshould execute long running tasks', async () => {
|
it('should execute long running tasks', async () => {
|
||||||
const myapp = uniq('myapp');
|
const myapp = uniq('myapp');
|
||||||
runCLI(`generate @nrwl/web:app ${myapp}`);
|
runCLI(`generate @nrwl/web:app ${myapp}`);
|
||||||
updateProjectConfig(myapp, (c) => {
|
updateProjectConfig(myapp, (c) => {
|
||||||
@ -156,7 +156,7 @@ describe('list', () => {
|
|||||||
describe('migrate', () => {
|
describe('migrate', () => {
|
||||||
beforeEach(() => newProject());
|
beforeEach(() => newProject());
|
||||||
|
|
||||||
it('clear-cacheshould run migrations', () => {
|
it('should run migrations', () => {
|
||||||
updateFile(
|
updateFile(
|
||||||
`./node_modules/migrate-parent-package/package.json`,
|
`./node_modules/migrate-parent-package/package.json`,
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
|
|||||||
@ -32,13 +32,13 @@ export async function determineMigration(
|
|||||||
): Promise<MigrationDefinition> {
|
): Promise<MigrationDefinition> {
|
||||||
const angularVersion = getInstalledAngularVersion();
|
const angularVersion = getInstalledAngularVersion();
|
||||||
const majorAngularVersion = major(angularVersion);
|
const majorAngularVersion = major(angularVersion);
|
||||||
latestWorkspaceVersionWithMigration = resolvePackageVersion(
|
latestWorkspaceVersionWithMigration = await resolvePackageVersion(
|
||||||
'@nrwl/angular',
|
'@nrwl/angular',
|
||||||
latestWorkspaceRangeVersionWithMigration
|
latestWorkspaceRangeVersionWithMigration
|
||||||
);
|
);
|
||||||
|
|
||||||
if (version) {
|
if (version) {
|
||||||
const normalizedVersion = normalizeVersion(version);
|
const normalizedVersion = await normalizeVersion(version);
|
||||||
if (lte(normalizedVersion, latestWorkspaceVersionWithMigration)) {
|
if (lte(normalizedVersion, latestWorkspaceVersionWithMigration)) {
|
||||||
// specified version should use @nrwl/workspace:ng-add
|
// specified version should use @nrwl/workspace:ng-add
|
||||||
return { packageName: '@nrwl/workspace', version: normalizedVersion };
|
return { packageName: '@nrwl/workspace', version: normalizedVersion };
|
||||||
@ -66,7 +66,8 @@ export async function determineMigration(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const latestNxCompatibleVersion = getNxVersionBasedOnInstalledAngularVersion(
|
const latestNxCompatibleVersion =
|
||||||
|
await getNxVersionBasedOnInstalledAngularVersion(
|
||||||
angularVersion,
|
angularVersion,
|
||||||
majorAngularVersion
|
majorAngularVersion
|
||||||
);
|
);
|
||||||
@ -105,7 +106,8 @@ async function findAndSuggestVersionToUse(
|
|||||||
majorAngularVersion: number,
|
majorAngularVersion: number,
|
||||||
userSpecifiedVersion: string
|
userSpecifiedVersion: string
|
||||||
): Promise<MigrationDefinition> {
|
): Promise<MigrationDefinition> {
|
||||||
const latestNxCompatibleVersion = getNxVersionBasedOnInstalledAngularVersion(
|
const latestNxCompatibleVersion =
|
||||||
|
await getNxVersionBasedOnInstalledAngularVersion(
|
||||||
angularVersion,
|
angularVersion,
|
||||||
majorAngularVersion
|
majorAngularVersion
|
||||||
);
|
);
|
||||||
@ -134,10 +136,10 @@ async function findAndSuggestVersionToUse(
|
|||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getNxVersionBasedOnInstalledAngularVersion(
|
async function getNxVersionBasedOnInstalledAngularVersion(
|
||||||
angularVersion: string,
|
angularVersion: string,
|
||||||
majorAngularVersion: number
|
majorAngularVersion: number
|
||||||
): string {
|
): Promise<string> {
|
||||||
if (lt(angularVersion, '13.0.0')) {
|
if (lt(angularVersion, '13.0.0')) {
|
||||||
// the @nrwl/angular:ng-add generator is only available for versions supporting
|
// the @nrwl/angular:ng-add generator is only available for versions supporting
|
||||||
// Angular >= 13.0.0, fall back to @nrwl/workspace:ng-add
|
// Angular >= 13.0.0, fall back to @nrwl/workspace:ng-add
|
||||||
@ -154,7 +156,7 @@ function getNxVersionBasedOnInstalledAngularVersion(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// use latest, only the last version in the map should not contain a max
|
// use latest, only the last version in the map should not contain a max
|
||||||
return resolvePackageVersion('@nrwl/angular', 'latest');
|
return await resolvePackageVersion('@nrwl/angular', 'latest');
|
||||||
}
|
}
|
||||||
|
|
||||||
async function promptForVersion(version: string): Promise<boolean> {
|
async function promptForVersion(version: string): Promise<boolean> {
|
||||||
@ -177,13 +179,13 @@ function getInstalledAngularVersion(): string {
|
|||||||
return readJsonFile(packageJsonPath).version;
|
return readJsonFile(packageJsonPath).version;
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeVersion(version: string): string {
|
async function normalizeVersion(version: string): Promise<string> {
|
||||||
if (
|
if (
|
||||||
version.startsWith('^') ||
|
version.startsWith('^') ||
|
||||||
version.startsWith('~') ||
|
version.startsWith('~') ||
|
||||||
version.split('.').length < 3
|
version.split('.').length < 3
|
||||||
) {
|
) {
|
||||||
return resolvePackageVersion('@nrwl/angular', version);
|
return await resolvePackageVersion('@nrwl/angular', version);
|
||||||
}
|
}
|
||||||
|
|
||||||
return version;
|
return version;
|
||||||
|
|||||||
@ -55,13 +55,13 @@ export function installDependencies(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resolvePackageVersion(
|
export async function resolvePackageVersion(
|
||||||
packageName: string,
|
packageName: string,
|
||||||
version: string
|
version: string
|
||||||
): string {
|
): Promise<string> {
|
||||||
try {
|
try {
|
||||||
return resolvePackageVersionUsingRegistry(packageName, version);
|
return await resolvePackageVersionUsingRegistry(packageName, version);
|
||||||
} catch {
|
} catch {
|
||||||
return resolvePackageVersionUsingInstallation(packageName, version);
|
return await resolvePackageVersionUsingInstallation(packageName, version);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,32 +1,36 @@
|
|||||||
|
import { PackageJson } from '../utils/package-json';
|
||||||
import { Migrator, normalizeVersion, parseMigrationsOptions } from './migrate';
|
import { Migrator, normalizeVersion, parseMigrationsOptions } from './migrate';
|
||||||
|
|
||||||
|
const createPackageJson = (
|
||||||
|
overrides: Partial<PackageJson> = {}
|
||||||
|
): PackageJson => ({
|
||||||
|
name: 'some-workspace',
|
||||||
|
version: '0.0.0',
|
||||||
|
...overrides,
|
||||||
|
});
|
||||||
|
|
||||||
describe('Migration', () => {
|
describe('Migration', () => {
|
||||||
describe('packageJson patch', () => {
|
describe('packageJson patch', () => {
|
||||||
it('should throw an error when the target package is not available', async () => {
|
it('should throw an error when the target package is not available', async () => {
|
||||||
const migrator = new Migrator({
|
const migrator = new Migrator({
|
||||||
packageJson: {},
|
packageJson: createPackageJson(),
|
||||||
versions: () => '1.0',
|
versions: () => '1.0',
|
||||||
fetch: (_p, _v) => {
|
fetch: (_p, _v) => {
|
||||||
throw new Error('cannot fetch');
|
throw new Error('cannot fetch');
|
||||||
},
|
},
|
||||||
from: {},
|
|
||||||
to: {},
|
to: {},
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
await expect(
|
||||||
await migrator.updatePackageJson('mypackage', 'myversion');
|
migrator.updatePackageJson('mypackage', 'myversion')
|
||||||
throw new Error('fail');
|
).rejects.toThrowError(/cannot fetch/);
|
||||||
} catch (e) {
|
|
||||||
expect(e.message).toEqual(`cannot fetch`);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return a patch to the new version', async () => {
|
it('should return a patch to the new version', async () => {
|
||||||
const migrator = new Migrator({
|
const migrator = new Migrator({
|
||||||
packageJson: {},
|
packageJson: createPackageJson(),
|
||||||
versions: () => '1.0.0',
|
versions: () => '1.0.0',
|
||||||
fetch: (_p, _v) => Promise.resolve({ version: '2.0.0' }),
|
fetch: (_p, _v) => Promise.resolve({ version: '2.0.0' }),
|
||||||
from: {},
|
|
||||||
to: {},
|
to: {},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -40,7 +44,7 @@ describe('Migration', () => {
|
|||||||
|
|
||||||
it('should collect the information recursively from upserts', async () => {
|
it('should collect the information recursively from upserts', async () => {
|
||||||
const migrator = new Migrator({
|
const migrator = new Migrator({
|
||||||
packageJson: { dependencies: { child: '1.0.0' } },
|
packageJson: createPackageJson({ dependencies: { child: '1.0.0' } }),
|
||||||
versions: () => '1.0.0',
|
versions: () => '1.0.0',
|
||||||
fetch: (p, _v) => {
|
fetch: (p, _v) => {
|
||||||
if (p === 'parent') {
|
if (p === 'parent') {
|
||||||
@ -68,7 +72,6 @@ describe('Migration', () => {
|
|||||||
return Promise.resolve(null);
|
return Promise.resolve(null);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
from: {},
|
|
||||||
to: {},
|
to: {},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -84,7 +87,7 @@ describe('Migration', () => {
|
|||||||
|
|
||||||
it('should support the deprecated "alwaysAddToPackageJson" option', async () => {
|
it('should support the deprecated "alwaysAddToPackageJson" option', async () => {
|
||||||
const migrator = new Migrator({
|
const migrator = new Migrator({
|
||||||
packageJson: { dependencies: { child1: '1.0.0' } },
|
packageJson: createPackageJson({ dependencies: { child1: '1.0.0' } }),
|
||||||
versions: () => '1.0.0',
|
versions: () => '1.0.0',
|
||||||
fetch: (p, _v) => {
|
fetch: (p, _v) => {
|
||||||
if (p === 'mypackage') {
|
if (p === 'mypackage') {
|
||||||
@ -108,7 +111,6 @@ describe('Migration', () => {
|
|||||||
return Promise.resolve(null);
|
return Promise.resolve(null);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
from: {},
|
|
||||||
to: {},
|
to: {},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -124,7 +126,7 @@ describe('Migration', () => {
|
|||||||
|
|
||||||
it('should stop recursive calls when exact version', async () => {
|
it('should stop recursive calls when exact version', async () => {
|
||||||
const migrator = new Migrator({
|
const migrator = new Migrator({
|
||||||
packageJson: { dependencies: { child: '1.0.0' } },
|
packageJson: createPackageJson({ dependencies: { child: '1.0.0' } }),
|
||||||
versions: () => '1.0.0',
|
versions: () => '1.0.0',
|
||||||
fetch: (p, _v) => {
|
fetch: (p, _v) => {
|
||||||
if (p === 'parent') {
|
if (p === 'parent') {
|
||||||
@ -157,7 +159,6 @@ describe('Migration', () => {
|
|||||||
return Promise.resolve(null);
|
return Promise.resolve(null);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
from: {},
|
|
||||||
to: {},
|
to: {},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -172,13 +173,13 @@ describe('Migration', () => {
|
|||||||
|
|
||||||
it('should set the version of a dependency to the newest', async () => {
|
it('should set the version of a dependency to the newest', async () => {
|
||||||
const migrator = new Migrator({
|
const migrator = new Migrator({
|
||||||
packageJson: {
|
packageJson: createPackageJson({
|
||||||
dependencies: {
|
dependencies: {
|
||||||
child1: '1.0.0',
|
child1: '1.0.0',
|
||||||
child2: '1.0.0',
|
child2: '1.0.0',
|
||||||
grandchild: '1.0.0',
|
grandchild: '1.0.0',
|
||||||
},
|
},
|
||||||
},
|
}),
|
||||||
versions: () => '1.0.0',
|
versions: () => '1.0.0',
|
||||||
fetch: (p, _v) => {
|
fetch: (p, _v) => {
|
||||||
if (p === 'parent') {
|
if (p === 'parent') {
|
||||||
@ -225,7 +226,6 @@ describe('Migration', () => {
|
|||||||
return Promise.resolve({ version: '4.0.0' });
|
return Promise.resolve({ version: '4.0.0' });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
from: {},
|
|
||||||
to: {},
|
to: {},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -242,7 +242,9 @@ describe('Migration', () => {
|
|||||||
|
|
||||||
it('should skip the versions <= currently installed', async () => {
|
it('should skip the versions <= currently installed', async () => {
|
||||||
const migrator = new Migrator({
|
const migrator = new Migrator({
|
||||||
packageJson: { dependencies: { child: '1.0.0', grandchild: '2.0.0' } },
|
packageJson: createPackageJson({
|
||||||
|
dependencies: { child: '1.0.0', grandchild: '2.0.0' },
|
||||||
|
}),
|
||||||
versions: () => '1.0.0',
|
versions: () => '1.0.0',
|
||||||
fetch: (p, _v) => {
|
fetch: (p, _v) => {
|
||||||
if (p === 'parent') {
|
if (p === 'parent') {
|
||||||
@ -275,7 +277,6 @@ describe('Migration', () => {
|
|||||||
return Promise.resolve({ version: '2.0.0' });
|
return Promise.resolve({ version: '2.0.0' });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
from: {},
|
|
||||||
to: {},
|
to: {},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -290,7 +291,9 @@ describe('Migration', () => {
|
|||||||
|
|
||||||
it('should conditionally process packages if they are installed', async () => {
|
it('should conditionally process packages if they are installed', async () => {
|
||||||
const migrator = new Migrator({
|
const migrator = new Migrator({
|
||||||
packageJson: { dependencies: { child1: '1.0.0', child2: '1.0.0' } },
|
packageJson: createPackageJson({
|
||||||
|
dependencies: { child1: '1.0.0', child2: '1.0.0' },
|
||||||
|
}),
|
||||||
versions: (p) => (p !== 'not-installed' ? '1.0.0' : null),
|
versions: (p) => (p !== 'not-installed' ? '1.0.0' : null),
|
||||||
fetch: (p, _v) => {
|
fetch: (p, _v) => {
|
||||||
if (p === 'parent') {
|
if (p === 'parent') {
|
||||||
@ -318,7 +321,6 @@ describe('Migration', () => {
|
|||||||
return Promise.resolve(null);
|
return Promise.resolve(null);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
from: {},
|
|
||||||
to: {},
|
to: {},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -331,78 +333,155 @@ describe('Migration', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// this is temporary. if nx gets used by other projects,
|
it('should migrate related libraries using packageGroup', async () => {
|
||||||
// we will extract the special casing
|
|
||||||
it('should special case @nrwl/workspace', async () => {
|
|
||||||
const migrator = new Migrator({
|
const migrator = new Migrator({
|
||||||
packageJson: {
|
packageJson: {
|
||||||
|
name: 'some-workspace',
|
||||||
|
version: '0.0.0',
|
||||||
|
|
||||||
devDependencies: {
|
devDependencies: {
|
||||||
'@nrwl/workspace': '0.9.0',
|
'@my-company/nx-workspace': '0.9.0',
|
||||||
'@nrwl/cli': '0.9.0',
|
'@my-company/lib-1': '0.9.0',
|
||||||
'@nrwl/angular': '0.9.0',
|
'@my-company/lib-2': '0.9.0',
|
||||||
'@nrwl/cypress': '0.9.0',
|
'@my-company/lib-3': '0.9.0',
|
||||||
'@nrwl/devkit': '0.9.0',
|
'@my-company/lib-3-child': '0.9.0',
|
||||||
'@nrwl/eslint-plugin-nx': '0.9.0',
|
'@my-company/lib-4': '0.9.0',
|
||||||
'@nrwl/express': '0.9.0',
|
'@my-company/lib-5': '0.9.0',
|
||||||
'@nrwl/jest': '0.9.0',
|
'@my-company/lib-6': '0.9.0',
|
||||||
'@nrwl/js': '0.9.0',
|
|
||||||
'@nrwl/linter': '0.9.0',
|
|
||||||
'@nrwl/nest': '0.9.0',
|
|
||||||
'@nrwl/next': '0.9.0',
|
|
||||||
'@nrwl/node': '0.9.0',
|
|
||||||
'@nrwl/nx-cloud': '0.9.0',
|
|
||||||
'@nrwl/nx-plugin': '0.9.0',
|
|
||||||
'@nrwl/react': '0.9.0',
|
|
||||||
'@nrwl/storybook': '0.9.0',
|
|
||||||
'@nrwl/web': '0.9.0',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
versions: () => '1.0.0',
|
versions: () => '1.0.0',
|
||||||
fetch: (_p, _v) => Promise.resolve({ version: '2.0.0' }),
|
fetch: async (pkg, version) => {
|
||||||
from: {},
|
if (pkg === '@my-company/nx-workspace') {
|
||||||
|
return {
|
||||||
|
version: '2.0.0',
|
||||||
|
packageGroup: [
|
||||||
|
'@my-company/lib-1',
|
||||||
|
'@my-company/lib-2',
|
||||||
|
'@my-company/lib-3',
|
||||||
|
{ package: '@my-company/lib-4', version: 'latest' },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (pkg === '@my-company/lib-6') {
|
||||||
|
return {
|
||||||
|
version: '2.0.0',
|
||||||
|
packageGroup: ['@my-company/nx-workspace'],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (pkg === '@my-company/lib-3') {
|
||||||
|
return {
|
||||||
|
version: '2.0.0',
|
||||||
|
packageGroup: ['@my-company/lib-3-child'],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (version === 'latest') {
|
||||||
|
return { version: '2.0.1' };
|
||||||
|
}
|
||||||
|
return { version: '2.0.0' };
|
||||||
|
},
|
||||||
to: {},
|
to: {},
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
await migrator.updatePackageJson('@nrwl/workspace', '2.0.0')
|
await migrator.updatePackageJson('@my-company/nx-workspace', '2.0.0')
|
||||||
).toEqual({
|
).toStrictEqual({
|
||||||
migrations: [],
|
migrations: [],
|
||||||
packageJson: {
|
packageJson: {
|
||||||
'@nrwl/workspace': { version: '2.0.0', addToPackageJson: false },
|
'@my-company/nx-workspace': {
|
||||||
'@nrwl/angular': { version: '2.0.0', addToPackageJson: false },
|
|
||||||
'@nrwl/cypress': { version: '2.0.0', addToPackageJson: false },
|
|
||||||
'@nrwl/devkit': { addToPackageJson: false, version: '2.0.0' },
|
|
||||||
'@nrwl/eslint-plugin-nx': {
|
|
||||||
version: '2.0.0',
|
version: '2.0.0',
|
||||||
addToPackageJson: false,
|
addToPackageJson: false,
|
||||||
},
|
},
|
||||||
'@nrwl/express': { version: '2.0.0', addToPackageJson: false },
|
'@my-company/lib-1': { version: '2.0.0', addToPackageJson: false },
|
||||||
'@nrwl/jest': { version: '2.0.0', addToPackageJson: false },
|
'@my-company/lib-2': { version: '2.0.0', addToPackageJson: false },
|
||||||
'@nrwl/js': { version: '2.0.0', addToPackageJson: false },
|
'@my-company/lib-3': { version: '2.0.0', addToPackageJson: false },
|
||||||
'@nrwl/linter': { version: '2.0.0', addToPackageJson: false },
|
'@my-company/lib-3-child': {
|
||||||
'@nrwl/nest': { version: '2.0.0', addToPackageJson: false },
|
version: '2.0.0',
|
||||||
'@nrwl/next': { version: '2.0.0', addToPackageJson: false },
|
addToPackageJson: false,
|
||||||
'@nrwl/node': { version: '2.0.0', addToPackageJson: false },
|
},
|
||||||
'@nrwl/nx-cloud': { version: '2.0.0', addToPackageJson: false },
|
'@my-company/lib-4': { version: '2.0.1', addToPackageJson: false },
|
||||||
'@nrwl/nx-plugin': { version: '2.0.0', addToPackageJson: false },
|
},
|
||||||
'@nrwl/react': { version: '2.0.0', addToPackageJson: false },
|
});
|
||||||
'@nrwl/storybook': { version: '2.0.0', addToPackageJson: false },
|
});
|
||||||
'@nrwl/web': { version: '2.0.0', addToPackageJson: false },
|
|
||||||
'@nrwl/cli': { version: '2.0.0', addToPackageJson: false },
|
it('should properly handle cyclic dependency in nested packageGroup', async () => {
|
||||||
|
const migrator = new Migrator({
|
||||||
|
packageJson: {
|
||||||
|
name: 'some-workspace',
|
||||||
|
version: '0.0.0',
|
||||||
|
|
||||||
|
devDependencies: {
|
||||||
|
'@my-company/nx-workspace': '0.9.0',
|
||||||
|
'@my-company/lib-1': '0.9.0',
|
||||||
|
'@my-company/lib-2': '0.9.0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
versions: () => '1.0.0',
|
||||||
|
fetch: async (pkg, version) => {
|
||||||
|
if (pkg === '@my-company/nx-workspace' && version === '2.0.0') {
|
||||||
|
return {
|
||||||
|
version: '2.0.0',
|
||||||
|
packageGroup: [
|
||||||
|
{ package: '@my-company/lib-1', version: 'latest' },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (pkg === '@my-company/nx-workspace' && version === '3.0.0') {
|
||||||
|
return {
|
||||||
|
version: '3.0.0',
|
||||||
|
packageGroup: ['@my-company/lib-1', '@my-company/lib-2'],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (pkg === '@my-company/lib-1' && version === 'latest') {
|
||||||
|
return {
|
||||||
|
version: '3.0.0',
|
||||||
|
packageGroup: ['@my-company/nx-workspace'],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (pkg === '@my-company/lib-1' && version === '3.0.0') {
|
||||||
|
return {
|
||||||
|
version: '3.0.0',
|
||||||
|
packageGroup: ['@my-company/nx-workspace'],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (pkg === '@my-company/lib-2' && version === '3.0.0') {
|
||||||
|
return {
|
||||||
|
version: '3.0.0',
|
||||||
|
packageGroup: [
|
||||||
|
// this should be ignored because it's a smaller version
|
||||||
|
{ package: '@my-company/nx-workspace', version: '2.99.0' },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
throw new Error(`Should not call fetch for ${pkg}@${version}`);
|
||||||
|
},
|
||||||
|
to: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
await migrator.updatePackageJson('@my-company/nx-workspace', '2.0.0')
|
||||||
|
).toStrictEqual({
|
||||||
|
migrations: [],
|
||||||
|
packageJson: {
|
||||||
|
'@my-company/nx-workspace': {
|
||||||
|
version: '3.0.0',
|
||||||
|
addToPackageJson: false,
|
||||||
|
},
|
||||||
|
'@my-company/lib-1': { version: '3.0.0', addToPackageJson: false },
|
||||||
|
'@my-company/lib-2': { version: '3.0.0', addToPackageJson: false },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not throw when packages are missing', async () => {
|
it('should not throw when packages are missing', async () => {
|
||||||
const migrator = new Migrator({
|
const migrator = new Migrator({
|
||||||
packageJson: {},
|
packageJson: createPackageJson(),
|
||||||
versions: (p) => (p === '@nrwl/nest' ? null : '1.0.0'),
|
versions: (p) => (p === '@nrwl/nest' ? null : '1.0.0'),
|
||||||
fetch: (_p, _v) =>
|
fetch: (_p, _v) =>
|
||||||
Promise.resolve({
|
Promise.resolve({
|
||||||
version: '2.0.0',
|
version: '2.0.0',
|
||||||
packageJsonUpdates: { one: { version: '2.0.0', packages: {} } },
|
packageJsonUpdates: { one: { version: '2.0.0', packages: {} } },
|
||||||
}),
|
}),
|
||||||
from: {},
|
|
||||||
to: {},
|
to: {},
|
||||||
});
|
});
|
||||||
await migrator.updatePackageJson('@nrwl/workspace', '2.0.0');
|
await migrator.updatePackageJson('@nrwl/workspace', '2.0.0');
|
||||||
@ -410,7 +489,7 @@ describe('Migration', () => {
|
|||||||
|
|
||||||
it('should only fetch packages that are installed', async () => {
|
it('should only fetch packages that are installed', async () => {
|
||||||
const migrator = new Migrator({
|
const migrator = new Migrator({
|
||||||
packageJson: {},
|
packageJson: createPackageJson(),
|
||||||
versions: (p) => (p === '@nrwl/nest' ? null : '1.0.0'),
|
versions: (p) => (p === '@nrwl/nest' ? null : '1.0.0'),
|
||||||
fetch: (p, _v) => {
|
fetch: (p, _v) => {
|
||||||
if (p === '@nrwl/nest') {
|
if (p === '@nrwl/nest') {
|
||||||
@ -421,7 +500,6 @@ describe('Migration', () => {
|
|||||||
packageJsonUpdates: { one: { version: '2.0.0', packages: {} } },
|
packageJsonUpdates: { one: { version: '2.0.0', packages: {} } },
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
from: {},
|
|
||||||
to: {},
|
to: {},
|
||||||
});
|
});
|
||||||
await migrator.updatePackageJson('@nrwl/workspace', '2.0.0');
|
await migrator.updatePackageJson('@nrwl/workspace', '2.0.0');
|
||||||
@ -429,7 +507,9 @@ describe('Migration', () => {
|
|||||||
|
|
||||||
it('should only fetch packages that are top-level deps', async () => {
|
it('should only fetch packages that are top-level deps', async () => {
|
||||||
const migrator = new Migrator({
|
const migrator = new Migrator({
|
||||||
packageJson: { devDependencies: { parent: '1.0.0', child1: '1.0.0' } },
|
packageJson: createPackageJson({
|
||||||
|
devDependencies: { parent: '1.0.0', child1: '1.0.0' },
|
||||||
|
}),
|
||||||
versions: () => '1.0.0',
|
versions: () => '1.0.0',
|
||||||
fetch: (p, _v) => {
|
fetch: (p, _v) => {
|
||||||
if (p === 'parent') {
|
if (p === 'parent') {
|
||||||
@ -455,7 +535,6 @@ describe('Migration', () => {
|
|||||||
throw new Error('Boom');
|
throw new Error('Boom');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
from: {},
|
|
||||||
to: {},
|
to: {},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -466,7 +545,9 @@ describe('Migration', () => {
|
|||||||
describe('migrations', () => {
|
describe('migrations', () => {
|
||||||
it('should create a list of migrations to run', async () => {
|
it('should create a list of migrations to run', async () => {
|
||||||
const migrator = new Migrator({
|
const migrator = new Migrator({
|
||||||
packageJson: { dependencies: { child: '1.0.0', newChild: '1.0.0' } },
|
packageJson: createPackageJson({
|
||||||
|
dependencies: { child: '1.0.0', newChild: '1.0.0' },
|
||||||
|
}),
|
||||||
versions: (p) => {
|
versions: (p) => {
|
||||||
if (p === 'parent') return '1.0.0';
|
if (p === 'parent') return '1.0.0';
|
||||||
if (p === 'child') return '1.0.0';
|
if (p === 'child') return '1.0.0';
|
||||||
@ -518,7 +599,6 @@ describe('Migration', () => {
|
|||||||
return Promise.resolve(null);
|
return Promise.resolve(null);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
from: {},
|
|
||||||
to: {},
|
to: {},
|
||||||
});
|
});
|
||||||
expect(await migrator.updatePackageJson('parent', '2.0.0')).toEqual({
|
expect(await migrator.updatePackageJson('parent', '2.0.0')).toEqual({
|
||||||
@ -546,7 +626,7 @@ describe('Migration', () => {
|
|||||||
|
|
||||||
it('should not generate migrations for non top-level packages', async () => {
|
it('should not generate migrations for non top-level packages', async () => {
|
||||||
const migrator = new Migrator({
|
const migrator = new Migrator({
|
||||||
packageJson: { dependencies: { child: '1.0.0' } },
|
packageJson: createPackageJson({ dependencies: { child: '1.0.0' } }),
|
||||||
versions: (p) => {
|
versions: (p) => {
|
||||||
if (p === 'parent') return '1.0.0';
|
if (p === 'parent') return '1.0.0';
|
||||||
if (p === 'child') return '1.0.0';
|
if (p === 'child') return '1.0.0';
|
||||||
@ -599,7 +679,6 @@ describe('Migration', () => {
|
|||||||
return Promise.resolve(null);
|
return Promise.resolve(null);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
from: {},
|
|
||||||
to: {},
|
to: {},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import { execSync } from 'child_process';
|
import { exec, execSync } from 'child_process';
|
||||||
import { copyFileSync, removeSync } from 'fs-extra';
|
import { remove } from 'fs-extra';
|
||||||
import { dirname, join } from 'path';
|
import { dirname, join } from 'path';
|
||||||
import { gt, lte } from 'semver';
|
import { gt, lte } from 'semver';
|
||||||
import { dirSync } from 'tmp';
|
import { promisify } from 'util';
|
||||||
import { NxJsonConfiguration } from '../config/nx-json';
|
import { NxJsonConfiguration } from '../config/nx-json';
|
||||||
import { flushChanges, FsTree } from '../config/tree';
|
import { flushChanges, FsTree } from '../config/tree';
|
||||||
import {
|
import {
|
||||||
@ -12,92 +12,111 @@ import {
|
|||||||
writeJsonFile,
|
writeJsonFile,
|
||||||
} from '../utils/fileutils';
|
} from '../utils/fileutils';
|
||||||
import { logger } from '../utils/logger';
|
import { logger } from '../utils/logger';
|
||||||
|
import { NxMigrationsConfiguration, PackageJson } from '../utils/package-json';
|
||||||
import {
|
import {
|
||||||
checkForNPMRC,
|
createTempNpmDirectory,
|
||||||
detectPackageManager,
|
|
||||||
getPackageManagerCommand,
|
getPackageManagerCommand,
|
||||||
|
packageRegistryPack,
|
||||||
|
packageRegistryView,
|
||||||
resolvePackageVersionUsingRegistry,
|
resolvePackageVersionUsingRegistry,
|
||||||
} from '../utils/package-manager';
|
} from '../utils/package-manager';
|
||||||
import { handleErrors } from '../utils/params';
|
import { handleErrors } from '../utils/params';
|
||||||
|
|
||||||
type Dependencies = 'dependencies' | 'devDependencies';
|
export type Dependencies = 'dependencies' | 'devDependencies';
|
||||||
|
|
||||||
export type MigrationsJson = {
|
export interface PackageJsonUpdateForPackage {
|
||||||
version: string;
|
version: string;
|
||||||
collection?: string;
|
ifPackageInstalled?: string;
|
||||||
generators?: {
|
alwaysAddToPackageJson?: boolean | Dependencies;
|
||||||
[name: string]: { version: string; description?: string; cli?: string };
|
addToPackageJson?: boolean | Dependencies;
|
||||||
};
|
}
|
||||||
packageJsonUpdates?: {
|
|
||||||
|
export type PackageJsonUpdates = {
|
||||||
[name: string]: {
|
[name: string]: {
|
||||||
version: string;
|
version: string;
|
||||||
packages: {
|
packages: {
|
||||||
[p: string]: {
|
[packageName: string]: PackageJsonUpdateForPackage;
|
||||||
version: string;
|
|
||||||
ifPackageInstalled?: string;
|
|
||||||
alwaysAddToPackageJson?: boolean;
|
|
||||||
addToPackageJson?: Dependencies;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export function normalizeVersion(version: string) {
|
export interface GeneratorMigration {
|
||||||
const [v, t] = version.split('-');
|
version: string;
|
||||||
const [major, minor, patch] = v.split('.');
|
description?: string;
|
||||||
const newV = `${major || 0}.${minor || 0}.${patch || 0}`;
|
cli?: string;
|
||||||
const newVersion = t ? `${newV}-${t}` : newV;
|
implementation?: string;
|
||||||
|
factory?: string;
|
||||||
try {
|
|
||||||
gt(newVersion, '0.0.0');
|
|
||||||
return newVersion;
|
|
||||||
} catch (e) {
|
|
||||||
try {
|
|
||||||
gt(newV, '0.0.0');
|
|
||||||
return newV;
|
|
||||||
} catch (e) {
|
|
||||||
const withoutPatch = `${major || 0}.${minor || 0}.0`;
|
|
||||||
try {
|
|
||||||
if (gt(withoutPatch, '0.0.0')) {
|
|
||||||
return withoutPatch;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
const withoutPatchAndMinor = `${major || 0}.0.0`;
|
|
||||||
try {
|
|
||||||
if (gt(withoutPatchAndMinor, '0.0.0')) {
|
|
||||||
return withoutPatchAndMinor;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
return '0.0.0';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function slash(packageName) {
|
export interface MigrationsJson {
|
||||||
|
version: string;
|
||||||
|
collection?: string;
|
||||||
|
generators?: { [name: string]: GeneratorMigration };
|
||||||
|
schematics?: { [name: string]: GeneratorMigration };
|
||||||
|
packageJsonUpdates?: PackageJsonUpdates;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ResolvedMigrationConfiguration extends MigrationsJson {
|
||||||
|
packageGroup?: NxMigrationsConfiguration['packageGroup'];
|
||||||
|
}
|
||||||
|
|
||||||
|
const execAsync = promisify(exec);
|
||||||
|
|
||||||
|
export function normalizeVersion(version: string) {
|
||||||
|
const [semver, prereleaseTag] = version.split('-');
|
||||||
|
const [major, minor, patch] = semver.split('.');
|
||||||
|
|
||||||
|
const newSemver = `${major || 0}.${minor || 0}.${patch || 0}`;
|
||||||
|
|
||||||
|
const newVersion = prereleaseTag
|
||||||
|
? `${newSemver}-${prereleaseTag}`
|
||||||
|
: newSemver;
|
||||||
|
|
||||||
|
const withoutPatch = `${major || 0}.${minor || 0}.0`;
|
||||||
|
const withoutPatchAndMinor = `${major || 0}.0.0`;
|
||||||
|
|
||||||
|
const variationsToCheck = [
|
||||||
|
newVersion,
|
||||||
|
newSemver,
|
||||||
|
withoutPatch,
|
||||||
|
withoutPatchAndMinor,
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const variation of variationsToCheck) {
|
||||||
|
try {
|
||||||
|
if (gt(variation, '0.0.0')) {
|
||||||
|
return variation;
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
return '0.0.0';
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeSlashes(packageName: string): string {
|
||||||
return packageName.replace(/\\/g, '/');
|
return packageName.replace(/\\/g, '/');
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Migrator {
|
export interface MigratorOptions {
|
||||||
private readonly packageJson: any;
|
packageJson: PackageJson;
|
||||||
private readonly versions: (p: string) => string;
|
versions: (pkg: string) => string;
|
||||||
private readonly fetch: (p: string, v: string) => Promise<MigrationsJson>;
|
fetch: (
|
||||||
private readonly from: { [p: string]: string };
|
pkg: string,
|
||||||
private readonly to: { [p: string]: string };
|
version: string
|
||||||
|
) => Promise<ResolvedMigrationConfiguration>;
|
||||||
|
to: { [pkg: string]: string };
|
||||||
|
}
|
||||||
|
|
||||||
constructor(opts: {
|
export class Migrator {
|
||||||
packageJson: any;
|
private readonly packageJson: MigratorOptions['packageJson'];
|
||||||
versions: (p: string) => string;
|
private readonly versions: MigratorOptions['versions'];
|
||||||
fetch: (p: string, v: string) => Promise<MigrationsJson>;
|
private readonly fetch: MigratorOptions['fetch'];
|
||||||
from: { [p: string]: string };
|
private readonly to: MigratorOptions['to'];
|
||||||
to: { [p: string]: string };
|
|
||||||
}) {
|
constructor(opts: MigratorOptions) {
|
||||||
this.packageJson = opts.packageJson;
|
this.packageJson = opts.packageJson;
|
||||||
this.versions = opts.versions;
|
this.versions = opts.versions;
|
||||||
this.fetch = opts.fetch;
|
this.fetch = opts.fetch;
|
||||||
this.from = opts.from;
|
|
||||||
this.to = opts.to;
|
this.to = opts.to;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,47 +126,47 @@ export class Migrator {
|
|||||||
{ version: targetVersion, addToPackageJson: false },
|
{ version: targetVersion, addToPackageJson: false },
|
||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
|
|
||||||
const migrations = await this._createMigrateJson(packageJson);
|
const migrations = await this._createMigrateJson(packageJson);
|
||||||
return { packageJson, migrations };
|
return { packageJson, migrations };
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _createMigrateJson(versions: {
|
private async _createMigrateJson(
|
||||||
[k: string]: { version: string; addToPackageJson: Dependencies | false };
|
versions: Record<string, PackageJsonUpdateForPackage>
|
||||||
}) {
|
) {
|
||||||
const migrations = await Promise.all(
|
const migrations = await Promise.all(
|
||||||
Object.keys(versions).map(async (c) => {
|
Object.keys(versions).map(async (packageName) => {
|
||||||
const currentVersion = this.versions(c);
|
const currentVersion = this.versions(packageName);
|
||||||
if (currentVersion === null) return [];
|
if (currentVersion === null) return [];
|
||||||
|
|
||||||
const target = versions[c];
|
const { version } = versions[packageName];
|
||||||
const migrationsJson = await this.fetch(c, target.version);
|
const { generators } = await this.fetch(packageName, version);
|
||||||
const generators = migrationsJson.generators;
|
|
||||||
if (!generators) return [];
|
if (!generators) return [];
|
||||||
return Object.keys(generators)
|
|
||||||
|
return Object.entries(generators)
|
||||||
.filter(
|
.filter(
|
||||||
(r) =>
|
([, migration]) =>
|
||||||
generators[r].version &&
|
migration.version &&
|
||||||
this.gt(generators[r].version, currentVersion) &&
|
this.gt(migration.version, currentVersion) &&
|
||||||
this.lte(generators[r].version, target.version)
|
this.lte(migration.version, version)
|
||||||
)
|
)
|
||||||
.map((r) => ({
|
.map(([migrationName, migration]) => ({
|
||||||
...migrationsJson.generators[r],
|
...migration,
|
||||||
package: c,
|
package: packageName,
|
||||||
name: r,
|
name: migrationName,
|
||||||
}));
|
}));
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
return migrations.reduce((m, c) => [...m, ...c], []);
|
return migrations.flat();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _updatePackageJson(
|
private async _updatePackageJson(
|
||||||
targetPackage: string,
|
targetPackage: string,
|
||||||
target: { version: string; addToPackageJson: Dependencies | false },
|
target: PackageJsonUpdateForPackage,
|
||||||
collectedVersions: {
|
collectedVersions: Record<string, PackageJsonUpdateForPackage>
|
||||||
[k: string]: { version: string; addToPackageJson: Dependencies | false };
|
): Promise<Record<string, PackageJsonUpdateForPackage>> {
|
||||||
}
|
|
||||||
) {
|
|
||||||
let targetVersion = target.version;
|
let targetVersion = target.version;
|
||||||
if (this.to[targetPackage]) {
|
if (this.to[targetPackage]) {
|
||||||
targetVersion = this.to[targetPackage];
|
targetVersion = this.to[targetPackage];
|
||||||
@ -158,16 +177,16 @@ export class Migrator {
|
|||||||
[targetPackage]: {
|
[targetPackage]: {
|
||||||
version: target.version,
|
version: target.version,
|
||||||
addToPackageJson: target.addToPackageJson || false,
|
addToPackageJson: target.addToPackageJson || false,
|
||||||
},
|
} as PackageJsonUpdateForPackage,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let migrationsJson;
|
let migrationsJson: ResolvedMigrationConfiguration;
|
||||||
try {
|
try {
|
||||||
migrationsJson = await this.fetch(targetPackage, targetVersion);
|
migrationsJson = await this.fetch(targetPackage, targetVersion);
|
||||||
targetVersion = migrationsJson.version;
|
targetVersion = migrationsJson.version;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e.message.indexOf('No matching version') > -1) {
|
if (e?.message?.includes('No matching version')) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`${e.message}\nRun migrate with --to="package1@version1,package2@version2"`
|
`${e.message}\nRun migrate with --to="package1@version1,package2@version2"`
|
||||||
);
|
);
|
||||||
@ -175,129 +194,120 @@ export class Migrator {
|
|||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const packages = this.collapsePackages(
|
const packages = this.collapsePackages(
|
||||||
targetPackage,
|
targetPackage,
|
||||||
targetVersion,
|
targetVersion,
|
||||||
migrationsJson
|
migrationsJson
|
||||||
);
|
);
|
||||||
|
|
||||||
const childCalls = await Promise.all(
|
const childPackageMigrations = await Promise.all(
|
||||||
Object.keys(packages)
|
Object.keys(packages)
|
||||||
.filter((r) => {
|
.filter((packageName) => {
|
||||||
return (
|
return (
|
||||||
!collectedVersions[r] ||
|
!collectedVersions[packageName] ||
|
||||||
this.gt(packages[r].version, collectedVersions[r].version)
|
this.gt(
|
||||||
|
packages[packageName].version,
|
||||||
|
collectedVersions[packageName].version
|
||||||
|
)
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
.map((u) =>
|
.map((packageName) =>
|
||||||
this._updatePackageJson(u, packages[u], {
|
this._updatePackageJson(packageName, packages[packageName], {
|
||||||
...collectedVersions,
|
...collectedVersions,
|
||||||
[targetPackage]: target,
|
[targetPackage]: target,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
return childCalls.reduce(
|
|
||||||
(m, c) => {
|
return childPackageMigrations.reduce(
|
||||||
Object.keys(c).forEach((r) => {
|
(migrations, childMigrations) => {
|
||||||
if (!m[r] || this.gt(c[r].version, m[r].version)) {
|
for (const migrationName of Object.keys(childMigrations)) {
|
||||||
m[r] = c[r];
|
if (
|
||||||
|
!migrations[migrationName] ||
|
||||||
|
this.gt(
|
||||||
|
childMigrations[migrationName].version,
|
||||||
|
migrations[migrationName].version
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
migrations[migrationName] = childMigrations[migrationName];
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
return m;
|
return migrations;
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
[targetPackage]: {
|
[targetPackage]: {
|
||||||
version: migrationsJson.version,
|
version: migrationsJson.version,
|
||||||
addToPackageJson: target.addToPackageJson || false,
|
addToPackageJson: target.addToPackageJson || false,
|
||||||
},
|
},
|
||||||
}
|
} as Record<string, PackageJsonUpdateForPackage>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private collapsePackages(
|
private collapsePackages(
|
||||||
packageName: string,
|
packageName: string,
|
||||||
targetVersion: string,
|
targetVersion: string,
|
||||||
m: MigrationsJson | null
|
migration: ResolvedMigrationConfiguration
|
||||||
) {
|
): Record<string, PackageJsonUpdateForPackage> {
|
||||||
// this should be used to know what version to include
|
// this should be used to know what version to include
|
||||||
// we should use from everywhere we use versions
|
// we should use from everywhere we use versions
|
||||||
|
|
||||||
if (packageName === '@nrwl/workspace') {
|
if (migration.packageGroup) {
|
||||||
if (!m.packageJsonUpdates) m.packageJsonUpdates = {};
|
migration.packageJsonUpdates ??= {};
|
||||||
m.packageJsonUpdates[`${targetVersion}-defaultPackages`] = {
|
migration.packageJsonUpdates[`${targetVersion}-defaultPackages`] = {
|
||||||
version: targetVersion,
|
version: targetVersion,
|
||||||
packages: [
|
packages: migration.packageGroup.reduce((acc, packageConfig) => {
|
||||||
'nx',
|
const { package: pkg, version } =
|
||||||
'@nrwl/angular',
|
typeof packageConfig === 'string'
|
||||||
'@nrwl/cypress',
|
? { package: packageConfig, version: targetVersion }
|
||||||
'@nrwl/devkit',
|
: packageConfig;
|
||||||
'@nrwl/eslint-plugin-nx',
|
|
||||||
'@nrwl/express',
|
return {
|
||||||
'@nrwl/jest',
|
...acc,
|
||||||
'@nrwl/js',
|
[pkg]: {
|
||||||
'@nrwl/cli',
|
version,
|
||||||
'@nrwl/linter',
|
|
||||||
'@nrwl/nest',
|
|
||||||
'@nrwl/next',
|
|
||||||
'@nrwl/node',
|
|
||||||
'@nrwl/nx-cloud',
|
|
||||||
'@nrwl/nx-plugin',
|
|
||||||
'@nrwl/react',
|
|
||||||
'@nrwl/storybook',
|
|
||||||
'@nrwl/web',
|
|
||||||
'@nrwl/react-native',
|
|
||||||
'@nrwl/detox',
|
|
||||||
].reduce(
|
|
||||||
(m, c) => ({
|
|
||||||
...m,
|
|
||||||
[c]: {
|
|
||||||
version: c === '@nrwl/nx-cloud' ? 'latest' : targetVersion,
|
|
||||||
alwaysAddToPackageJson: false,
|
alwaysAddToPackageJson: false,
|
||||||
},
|
} as PackageJsonUpdateForPackage,
|
||||||
}),
|
};
|
||||||
{}
|
}, {}),
|
||||||
),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (!m.packageJsonUpdates || !this.versions(packageName)) return {};
|
|
||||||
|
|
||||||
return Object.keys(m.packageJsonUpdates)
|
if (!migration.packageJsonUpdates || !this.versions(packageName)) return {};
|
||||||
.filter((r) => {
|
|
||||||
|
return Object.values(migration.packageJsonUpdates)
|
||||||
|
.filter(({ version, packages }) => {
|
||||||
return (
|
return (
|
||||||
this.gt(
|
packages &&
|
||||||
m.packageJsonUpdates[r].version,
|
this.gt(version, this.versions(packageName)) &&
|
||||||
this.versions(packageName)
|
this.lte(version, targetVersion)
|
||||||
) && this.lte(m.packageJsonUpdates[r].version, targetVersion)
|
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
.map((r) => m.packageJsonUpdates[r].packages)
|
.map(({ packages }) => {
|
||||||
.map((packages) => {
|
|
||||||
if (!packages) return {};
|
|
||||||
|
|
||||||
return Object.keys(packages)
|
|
||||||
.filter((pkg) => {
|
|
||||||
const { dependencies, devDependencies } = this.packageJson;
|
const { dependencies, devDependencies } = this.packageJson;
|
||||||
|
|
||||||
|
return Object.entries(packages)
|
||||||
|
.filter(([packageName, packageUpdate]) => {
|
||||||
return (
|
return (
|
||||||
(!packages[pkg].ifPackageInstalled ||
|
(!packageUpdate.ifPackageInstalled ||
|
||||||
this.versions(packages[pkg].ifPackageInstalled)) &&
|
this.versions(packageUpdate.ifPackageInstalled)) &&
|
||||||
(packages[pkg].alwaysAddToPackageJson ||
|
(packageUpdate.alwaysAddToPackageJson ||
|
||||||
packages[pkg].addToPackageJson ||
|
packageUpdate.addToPackageJson ||
|
||||||
!!dependencies?.[pkg] ||
|
!!dependencies?.[packageName] ||
|
||||||
!!devDependencies?.[pkg])
|
!!devDependencies?.[packageName])
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
.reduce(
|
.reduce(
|
||||||
(m, c) => ({
|
(acc, [packageName, packageUpdate]) => ({
|
||||||
...m,
|
...acc,
|
||||||
[c]: {
|
[packageName]: {
|
||||||
version: packages[c].version,
|
version: packageUpdate.version,
|
||||||
addToPackageJson: packages[c].alwaysAddToPackageJson
|
addToPackageJson: packageUpdate.alwaysAddToPackageJson
|
||||||
? 'dependencies'
|
? 'dependencies'
|
||||||
: packages[c].addToPackageJson || false,
|
: packageUpdate.addToPackageJson || false,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
{}
|
{} as Record<string, PackageJsonUpdateForPackage>
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
.reduce((m, c) => ({ ...m, ...c }), {});
|
.reduce((m, c) => ({ ...m, ...c }), {});
|
||||||
@ -333,7 +343,8 @@ function versionOverrides(overrides: string, param: string) {
|
|||||||
`Incorrect '${param}' section. Use --${param}="package@version"`
|
`Incorrect '${param}' section. Use --${param}="package@version"`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
res[slash(selectedPackage)] = normalizeVersionWithTagCheck(selectedVersion);
|
res[normalizeSlashes(selectedPackage)] =
|
||||||
|
normalizeVersionWithTagCheck(selectedVersion);
|
||||||
});
|
});
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
@ -388,6 +399,7 @@ type GenerateMigrations = {
|
|||||||
from: { [k: string]: string };
|
from: { [k: string]: string };
|
||||||
to: { [k: string]: string };
|
to: { [k: string]: string };
|
||||||
};
|
};
|
||||||
|
|
||||||
type RunMigrations = { type: 'runMigrations'; runMigrations: string };
|
type RunMigrations = { type: 'runMigrations'; runMigrations: string };
|
||||||
|
|
||||||
export function parseMigrationsOptions(options: {
|
export function parseMigrationsOptions(options: {
|
||||||
@ -407,7 +419,7 @@ export function parseMigrationsOptions(options: {
|
|||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
type: 'generateMigrations',
|
type: 'generateMigrations',
|
||||||
targetPackage: slash(targetPackage),
|
targetPackage: normalizeSlashes(targetPackage),
|
||||||
targetVersion,
|
targetVersion,
|
||||||
from,
|
from,
|
||||||
to,
|
to,
|
||||||
@ -420,16 +432,23 @@ export function parseMigrationsOptions(options: {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function versions(root: string, from: { [p: string]: string }) {
|
function versions(root: string, from: Record<string, string>) {
|
||||||
|
const cache: Record<string, string> = {};
|
||||||
|
|
||||||
return (packageName: string) => {
|
return (packageName: string) => {
|
||||||
try {
|
try {
|
||||||
if (from[packageName]) {
|
if (from[packageName]) {
|
||||||
return from[packageName];
|
return from[packageName];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!cache[packageName]) {
|
||||||
const packageJsonPath = require.resolve(`${packageName}/package.json`, {
|
const packageJsonPath = require.resolve(`${packageName}/package.json`, {
|
||||||
paths: [root],
|
paths: [root],
|
||||||
});
|
});
|
||||||
return readJsonFile(packageJsonPath).version;
|
cache[packageName] = readJsonFile(packageJsonPath).version;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cache[packageName];
|
||||||
} catch {
|
} catch {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -438,20 +457,21 @@ function versions(root: string, from: { [p: string]: string }) {
|
|||||||
|
|
||||||
// testing-fetch-start
|
// testing-fetch-start
|
||||||
function createFetcher() {
|
function createFetcher() {
|
||||||
const cache = {};
|
const cache: Record<string, ResolvedMigrationConfiguration> = {};
|
||||||
return async function f(
|
|
||||||
|
return async function nxMigrateFetcher(
|
||||||
packageName: string,
|
packageName: string,
|
||||||
packageVersion: string
|
packageVersion: string
|
||||||
): Promise<MigrationsJson> {
|
): Promise<ResolvedMigrationConfiguration> {
|
||||||
if (cache[`${packageName}-${packageVersion}`]) {
|
if (cache[`${packageName}-${packageVersion}`]) {
|
||||||
return cache[`${packageName}-${packageVersion}`];
|
return cache[`${packageName}-${packageVersion}`];
|
||||||
}
|
}
|
||||||
|
|
||||||
let resolvedVersion: string;
|
let resolvedVersion: string = packageVersion;
|
||||||
let migrations: any;
|
let resolvedMigrationConfiguration: ResolvedMigrationConfiguration;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
resolvedVersion = resolvePackageVersionUsingRegistry(
|
resolvedVersion = await resolvePackageVersionUsingRegistry(
|
||||||
packageName,
|
packageName,
|
||||||
packageVersion
|
packageVersion
|
||||||
);
|
);
|
||||||
@ -461,210 +481,224 @@ function createFetcher() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
logger.info(`Fetching ${packageName}@${packageVersion}`);
|
logger.info(`Fetching ${packageName}@${packageVersion}`);
|
||||||
migrations = await getPackageMigrations(packageName, resolvedVersion);
|
|
||||||
|
resolvedMigrationConfiguration = await getPackageMigrationsUsingRegistry(
|
||||||
|
packageName,
|
||||||
|
resolvedVersion
|
||||||
|
);
|
||||||
} catch {
|
} catch {
|
||||||
logger.info(`Fetching ${packageName}@${packageVersion}`);
|
logger.info(`Fetching ${packageName}@${packageVersion}`);
|
||||||
const result = await installPackageAndGetVersionAngMigrations(
|
|
||||||
|
resolvedMigrationConfiguration = await getPackageMigrationsUsingInstall(
|
||||||
packageName,
|
packageName,
|
||||||
packageVersion
|
packageVersion
|
||||||
);
|
);
|
||||||
resolvedVersion = result.resolvedVersion;
|
|
||||||
migrations = result.migrations;
|
resolvedVersion = resolvedMigrationConfiguration.version;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (migrations) {
|
resolvedMigrationConfiguration = {
|
||||||
cache[`${packageName}-${packageVersion}`] = cache[
|
...resolvedMigrationConfiguration,
|
||||||
`${packageName}-${resolvedVersion}`
|
generators:
|
||||||
] = {
|
resolvedMigrationConfiguration.generators ??
|
||||||
version: resolvedVersion,
|
resolvedMigrationConfiguration.schematics,
|
||||||
generators: migrations.generators ?? migrations.schematics,
|
|
||||||
packageJsonUpdates: migrations.packageJsonUpdates,
|
|
||||||
};
|
};
|
||||||
} else {
|
|
||||||
cache[`${packageName}-${packageVersion}`] = cache[
|
|
||||||
`${packageName}-${resolvedVersion}`
|
|
||||||
] = {
|
|
||||||
version: resolvedVersion,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return cache[`${packageName}-${packageVersion}`];
|
cache[`${packageName}-${packageVersion}`] = cache[
|
||||||
|
`${packageName}-${resolvedVersion}`
|
||||||
|
] = resolvedMigrationConfiguration;
|
||||||
|
|
||||||
|
return resolvedMigrationConfiguration;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// testing-fetch-end
|
// testing-fetch-end
|
||||||
|
|
||||||
async function getPackageMigrations(
|
async function getPackageMigrationsUsingRegistry(
|
||||||
packageName: string,
|
packageName: string,
|
||||||
packageVersion: string
|
packageVersion: string
|
||||||
) {
|
): Promise<ResolvedMigrationConfiguration> {
|
||||||
try {
|
|
||||||
// check if there are migrations in the packages by looking at the
|
// check if there are migrations in the packages by looking at the
|
||||||
// registry directly
|
// registry directly
|
||||||
const migrationsPath = getPackageMigrationsPathFromRegistry(
|
const migrationsConfig = await getPackageMigrationsConfigFromRegistry(
|
||||||
packageName,
|
packageName,
|
||||||
packageVersion
|
packageVersion
|
||||||
);
|
);
|
||||||
if (!migrationsPath) {
|
|
||||||
return null;
|
if (!migrationsConfig.migrations) {
|
||||||
|
return {
|
||||||
|
version: packageVersion,
|
||||||
|
packageGroup: migrationsConfig.packageGroup,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// try to obtain the migrations from the registry directly
|
// try to obtain the migrations from the registry directly
|
||||||
return await getPackageMigrationsUsingRegistry(
|
return await downloadPackageMigrationsFromRegistry(
|
||||||
packageName,
|
packageName,
|
||||||
packageVersion,
|
packageVersion,
|
||||||
migrationsPath
|
migrationsConfig
|
||||||
);
|
);
|
||||||
} catch {
|
|
||||||
// fall back to installing the package
|
|
||||||
const { migrations } = await installPackageAndGetVersionAngMigrations(
|
|
||||||
packageName,
|
|
||||||
packageVersion
|
|
||||||
);
|
|
||||||
return migrations;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPackageMigrationsPathFromRegistry(
|
function resolveNxMigrationConfig(json: Partial<PackageJson>) {
|
||||||
|
const parseNxMigrationsConfig = (
|
||||||
|
fromJson: string | NxMigrationsConfiguration
|
||||||
|
): NxMigrationsConfiguration => {
|
||||||
|
if (typeof fromJson === 'string') {
|
||||||
|
return { migrations: fromJson, packageGroup: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...(fromJson.migrations ? { migrations: fromJson.migrations } : {}),
|
||||||
|
...(fromJson.packageGroup ? { packageGroup: fromJson.packageGroup } : {}),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const config: NxMigrationsConfiguration = {
|
||||||
|
...parseNxMigrationsConfig(json['ng-update']),
|
||||||
|
...parseNxMigrationsConfig(json['nx-migrations']),
|
||||||
|
// In case there's a `migrations` field in `package.json`
|
||||||
|
...parseNxMigrationsConfig(json as any),
|
||||||
|
};
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getPackageMigrationsConfigFromRegistry(
|
||||||
packageName: string,
|
packageName: string,
|
||||||
packageVersion: string
|
packageVersion: string
|
||||||
): string | null {
|
): Promise<NxMigrationsConfiguration> {
|
||||||
let pm = detectPackageManager();
|
const result = await packageRegistryView(
|
||||||
if (pm === 'yarn') {
|
packageName,
|
||||||
pm = 'npm';
|
packageVersion,
|
||||||
}
|
'nx-migrations ng-update --json'
|
||||||
const result = execSync(
|
);
|
||||||
`${pm} view ${packageName}@${packageVersion} nx-migrations ng-update --json`,
|
|
||||||
{
|
|
||||||
stdio: [],
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.toString()
|
|
||||||
.trim();
|
|
||||||
|
|
||||||
if (!result) {
|
if (!result) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const json = JSON.parse(result);
|
return resolveNxMigrationConfig(JSON.parse(result));
|
||||||
let migrationsFilePath = json['nx-migrations'] ?? json['ng-update'] ?? json;
|
|
||||||
if (typeof json === 'object') {
|
|
||||||
migrationsFilePath = migrationsFilePath.migrations;
|
|
||||||
}
|
|
||||||
|
|
||||||
return migrationsFilePath;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getPackageMigrationsUsingRegistry(
|
async function downloadPackageMigrationsFromRegistry(
|
||||||
packageName: string,
|
packageName: string,
|
||||||
packageVersion: string,
|
packageVersion: string,
|
||||||
migrationsFilePath: string
|
{ migrations: migrationsFilePath, packageGroup }: NxMigrationsConfiguration
|
||||||
) {
|
): Promise<ResolvedMigrationConfiguration> {
|
||||||
const dir = dirSync().name;
|
const dir = createTempNpmDirectory();
|
||||||
createNPMRC(dir);
|
|
||||||
|
|
||||||
let pm = detectPackageManager();
|
let result: ResolvedMigrationConfiguration;
|
||||||
if (pm === 'yarn') {
|
|
||||||
pm = 'npm';
|
|
||||||
}
|
|
||||||
|
|
||||||
const tarballPath = execSync(`${pm} pack ${packageName}@${packageVersion}`, {
|
|
||||||
cwd: dir,
|
|
||||||
stdio: [],
|
|
||||||
})
|
|
||||||
.toString()
|
|
||||||
.trim();
|
|
||||||
|
|
||||||
let migrations = null;
|
|
||||||
migrationsFilePath = join('package', migrationsFilePath);
|
|
||||||
const migrationDestinationPath = join(dir, migrationsFilePath);
|
|
||||||
try {
|
try {
|
||||||
await extractFileFromTarball(
|
const { tarballPath } = await packageRegistryPack(
|
||||||
join(dir, tarballPath),
|
dir,
|
||||||
migrationsFilePath,
|
packageName,
|
||||||
migrationDestinationPath
|
packageVersion
|
||||||
);
|
);
|
||||||
|
|
||||||
migrations = readJsonFile(migrationDestinationPath);
|
const migrations = await extractFileFromTarball(
|
||||||
|
join(dir, tarballPath),
|
||||||
|
join('package', migrationsFilePath),
|
||||||
|
join(dir, migrationsFilePath)
|
||||||
|
).then((path) => readJsonFile<MigrationsJson>(path));
|
||||||
|
|
||||||
|
result = { ...migrations, packageGroup, version: packageVersion };
|
||||||
} catch {
|
} catch {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Failed to find migrations file "${migrationsFilePath}" in package "${packageName}@${packageVersion}".`
|
`Failed to find migrations file "${migrationsFilePath}" in package "${packageName}@${packageVersion}".`
|
||||||
);
|
);
|
||||||
}
|
} finally {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
removeSync(dir);
|
await remove(dir);
|
||||||
} catch {
|
} catch {
|
||||||
// It's okay if this fails, the OS will clean it up eventually
|
// It's okay if this fails, the OS will clean it up eventually
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return migrations;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function installPackageAndGetVersionAngMigrations(
|
async function getPackageMigrationsUsingInstall(
|
||||||
packageName: string,
|
packageName: string,
|
||||||
packageVersion: string
|
packageVersion: string
|
||||||
) {
|
): Promise<ResolvedMigrationConfiguration> {
|
||||||
const dir = dirSync().name;
|
const dir = createTempNpmDirectory();
|
||||||
createNPMRC(dir);
|
|
||||||
|
|
||||||
|
let result: ResolvedMigrationConfiguration;
|
||||||
|
|
||||||
|
try {
|
||||||
const pmc = getPackageManagerCommand();
|
const pmc = getPackageManagerCommand();
|
||||||
execSync(`${pmc.add} ${packageName}@${packageVersion}`, {
|
|
||||||
stdio: [],
|
await execAsync(`${pmc.add} ${packageName}@${packageVersion}`, {
|
||||||
cwd: dir,
|
cwd: dir,
|
||||||
});
|
});
|
||||||
|
|
||||||
const packageJsonPath = require.resolve(`${packageName}/package.json`, {
|
const {
|
||||||
paths: [dir],
|
migrations: migrationsFilePath,
|
||||||
});
|
packageGroup,
|
||||||
const { version: resolvedVersion } = readJsonFile(packageJsonPath);
|
packageJson,
|
||||||
|
} = readPackageMigrationConfig(packageName, dir);
|
||||||
|
|
||||||
const migrationsFilePath = packageToMigrationsFilePath(packageName, dir);
|
let migrations: MigrationsJson = undefined;
|
||||||
let migrations = null;
|
|
||||||
if (migrationsFilePath) {
|
if (migrationsFilePath) {
|
||||||
migrations = readJsonFile(migrationsFilePath);
|
migrations = readJsonFile<MigrationsJson>(migrationsFilePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
result = { ...migrations, packageGroup, version: packageJson.version };
|
||||||
|
} finally {
|
||||||
try {
|
try {
|
||||||
removeSync(dir);
|
await remove(dir);
|
||||||
} catch {
|
} catch {
|
||||||
// It's okay if this fails, the OS will clean it up eventually
|
// It's okay if this fails, the OS will clean it up eventually
|
||||||
}
|
}
|
||||||
|
|
||||||
return { migrations, resolvedVersion };
|
|
||||||
}
|
|
||||||
|
|
||||||
function createNPMRC(dir: string): void {
|
|
||||||
// A package.json is needed for pnpm pack and for .npmrc to resolve
|
|
||||||
writeJsonFile(`${dir}/package.json`, {});
|
|
||||||
const npmrc = checkForNPMRC();
|
|
||||||
if (npmrc) {
|
|
||||||
// Copy npmrc if it exists, so that npm still follows it.
|
|
||||||
copyFileSync(npmrc, `${dir}/.npmrc`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
function packageToMigrationsFilePath(packageName: string, dir: string) {
|
interface PackageMigrationConfig extends NxMigrationsConfiguration {
|
||||||
|
packageJson: PackageJson;
|
||||||
|
}
|
||||||
|
|
||||||
|
function readPackageMigrationConfig(
|
||||||
|
packageName: string,
|
||||||
|
dir: string
|
||||||
|
): PackageMigrationConfig {
|
||||||
const packageJsonPath = require.resolve(`${packageName}/package.json`, {
|
const packageJsonPath = require.resolve(`${packageName}/package.json`, {
|
||||||
paths: [dir],
|
paths: [dir],
|
||||||
});
|
});
|
||||||
const json = readJsonFile(packageJsonPath);
|
|
||||||
let migrationsFile = json['nx-migrations'] || json['ng-update'];
|
|
||||||
|
|
||||||
// migrationsFile is an object
|
const json = readJsonFile<PackageJson>(packageJsonPath);
|
||||||
if (migrationsFile && migrationsFile.migrations) {
|
const migrationConfigOrFile = json['nx-migrations'] || json['ng-update'];
|
||||||
migrationsFile = migrationsFile.migrations;
|
|
||||||
|
if (!migrationConfigOrFile) {
|
||||||
|
return { packageJson: json, migrations: null, packageGroup: [] };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const migrationsConfig =
|
||||||
|
typeof migrationConfigOrFile === 'string'
|
||||||
|
? {
|
||||||
|
migrations: migrationConfigOrFile,
|
||||||
|
packageGroup: [],
|
||||||
|
}
|
||||||
|
: migrationConfigOrFile;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (migrationsFile && typeof migrationsFile === 'string') {
|
const migrationFile = require.resolve(migrationsConfig.migrations, {
|
||||||
return require.resolve(migrationsFile, {
|
|
||||||
paths: [dirname(packageJsonPath)],
|
paths: [dirname(packageJsonPath)],
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
return null;
|
return {
|
||||||
}
|
packageJson: json,
|
||||||
|
migrations: migrationFile,
|
||||||
|
packageGroup: migrationsConfig.packageGroup,
|
||||||
|
};
|
||||||
} catch {
|
} catch {
|
||||||
return null;
|
return {
|
||||||
|
packageJson: json,
|
||||||
|
migrations: null,
|
||||||
|
packageGroup: migrationsConfig.packageGroup,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -680,28 +714,30 @@ function createMigrationsFile(
|
|||||||
|
|
||||||
function updatePackageJson(
|
function updatePackageJson(
|
||||||
root: string,
|
root: string,
|
||||||
updatedPackages: {
|
updatedPackages: Record<string, PackageJsonUpdateForPackage>
|
||||||
[p: string]: { version: string; addToPackageJson: Dependencies | false };
|
|
||||||
}
|
|
||||||
) {
|
) {
|
||||||
const packageJsonPath = join(root, 'package.json');
|
const packageJsonPath = join(root, 'package.json');
|
||||||
const parseOptions: JsonReadOptions = {};
|
const parseOptions: JsonReadOptions = {};
|
||||||
const json = readJsonFile(packageJsonPath, parseOptions);
|
const json = readJsonFile(packageJsonPath, parseOptions);
|
||||||
|
|
||||||
Object.keys(updatedPackages).forEach((p) => {
|
Object.keys(updatedPackages).forEach((p) => {
|
||||||
if (json.devDependencies && json.devDependencies[p]) {
|
if (json.devDependencies?.[p]) {
|
||||||
json.devDependencies[p] = updatedPackages[p].version;
|
|
||||||
} else if (json.dependencies && json.dependencies[p]) {
|
|
||||||
json.dependencies[p] = updatedPackages[p].version;
|
|
||||||
} else if (updatedPackages[p].addToPackageJson) {
|
|
||||||
if (updatedPackages[p].addToPackageJson === 'dependencies') {
|
|
||||||
if (!json.dependencies) json.dependencies = {};
|
|
||||||
json.dependencies[p] = updatedPackages[p].version;
|
|
||||||
} else if (updatedPackages[p].addToPackageJson === 'devDependencies') {
|
|
||||||
if (!json.devDependencies) json.devDependencies = {};
|
|
||||||
json.devDependencies[p] = updatedPackages[p].version;
|
json.devDependencies[p] = updatedPackages[p].version;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (json.dependencies?.[p]) {
|
||||||
|
json.dependencies[p] = updatedPackages[p].version;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dependencyType = updatedPackages[p].addToPackageJson;
|
||||||
|
if (typeof dependencyType === 'string') {
|
||||||
|
json[dependencyType] ??= {};
|
||||||
|
json[dependencyType][p] = updatedPackages[p].version;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
writeJsonFile(packageJsonPath, json, {
|
writeJsonFile(packageJsonPath, json, {
|
||||||
appendNewLine: parseOptions.endsWithNewline,
|
appendNewLine: parseOptions.endsWithNewline,
|
||||||
});
|
});
|
||||||
@ -720,18 +756,21 @@ async function generateMigrationsJsonAndUpdatePackageJson(
|
|||||||
try {
|
try {
|
||||||
logger.info(`Fetching meta data about packages.`);
|
logger.info(`Fetching meta data about packages.`);
|
||||||
logger.info(`It may take a few minutes.`);
|
logger.info(`It may take a few minutes.`);
|
||||||
|
|
||||||
const originalPackageJson = readJsonFile(join(root, 'package.json'));
|
const originalPackageJson = readJsonFile(join(root, 'package.json'));
|
||||||
|
|
||||||
const migrator = new Migrator({
|
const migrator = new Migrator({
|
||||||
packageJson: originalPackageJson,
|
packageJson: originalPackageJson,
|
||||||
versions: versions(root, opts.from),
|
versions: versions(root, opts.from),
|
||||||
fetch: createFetcher(),
|
fetch: createFetcher(),
|
||||||
from: opts.from,
|
|
||||||
to: opts.to,
|
to: opts.to,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { migrations, packageJson } = await migrator.updatePackageJson(
|
const { migrations, packageJson } = await migrator.updatePackageJson(
|
||||||
opts.targetPackage,
|
opts.targetPackage,
|
||||||
opts.targetVersion
|
opts.targetVersion
|
||||||
);
|
);
|
||||||
|
|
||||||
updatePackageJson(root, packageJson);
|
updatePackageJson(root, packageJson);
|
||||||
|
|
||||||
if (migrations.length > 0) {
|
if (migrations.length > 0) {
|
||||||
@ -752,13 +791,14 @@ async function generateMigrationsJsonAndUpdatePackageJson(
|
|||||||
`- Make sure package.json changes make sense and then run '${pmc.install}'`
|
`- Make sure package.json changes make sense and then run '${pmc.install}'`
|
||||||
);
|
);
|
||||||
if (migrations.length > 0) {
|
if (migrations.length > 0) {
|
||||||
logger.info(`- Run 'nx migrate --run-migrations'`);
|
logger.info(`- Run '${pmc.run('nx', 'migrate --run-migrations')}'`);
|
||||||
}
|
}
|
||||||
logger.info(`- To learn more go to https://nx.dev/using-nx/updating-nx`);
|
logger.info(`- To learn more go to https://nx.dev/using-nx/updating-nx`);
|
||||||
|
|
||||||
if (showConnectToCloudMessage()) {
|
if (showConnectToCloudMessage()) {
|
||||||
|
const cmd = pmc.run('nx', 'connect-to-nx-cloud');
|
||||||
logger.info(
|
logger.info(
|
||||||
`- You may run "nx connect-to-nx-cloud" to get faster builds, GitHub integration, and more. Check out https://nx.app`
|
`- You may run '${cmd}' to get faster builds, GitHub integration, and more. Check out https://nx.app`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -807,7 +847,7 @@ async function runMigrations(
|
|||||||
cli?: 'nx' | 'angular';
|
cli?: 'nx' | 'angular';
|
||||||
}[] = readJsonFile(join(root, opts.runMigrations)).migrations;
|
}[] = readJsonFile(join(root, opts.runMigrations)).migrations;
|
||||||
|
|
||||||
for (let m of migrations) {
|
for (const m of migrations) {
|
||||||
logger.info(`Running migration ${m.name}`);
|
logger.info(`Running migration ${m.name}`);
|
||||||
if (m.cli === 'nx') {
|
if (m.cli === 'nx') {
|
||||||
await runNxMigration(root, m.package, m.name);
|
await runNxMigration(root, m.package, m.name);
|
||||||
@ -826,12 +866,16 @@ async function runMigrations(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function runNxMigration(root: string, packageName: string, name: string) {
|
async function runNxMigration(root: string, packageName: string, name: string) {
|
||||||
const collectionPath = packageToMigrationsFilePath(packageName, root);
|
const collectionPath = readPackageMigrationConfig(
|
||||||
const collection = readJsonFile(collectionPath);
|
packageName,
|
||||||
|
root
|
||||||
|
).migrations;
|
||||||
|
|
||||||
|
const collection = readJsonFile<MigrationsJson>(collectionPath);
|
||||||
const g = collection.generators || collection.schematics;
|
const g = collection.generators || collection.schematics;
|
||||||
const implRelativePath = g[name].implementation || g[name].factory;
|
const implRelativePath = g[name].implementation || g[name].factory;
|
||||||
|
|
||||||
let implPath;
|
let implPath: string;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
implPath = require.resolve(implRelativePath, {
|
implPath = require.resolve(implRelativePath, {
|
||||||
|
|||||||
@ -119,7 +119,7 @@ export async function extractFileFromTarball(
|
|||||||
file: string,
|
file: string,
|
||||||
destinationFilePath: string
|
destinationFilePath: string
|
||||||
) {
|
) {
|
||||||
return new Promise<void>((resolve, reject) => {
|
return new Promise<string>((resolve, reject) => {
|
||||||
ensureDirSync(dirname(destinationFilePath));
|
ensureDirSync(dirname(destinationFilePath));
|
||||||
var tarExtractStream = tar.extract();
|
var tarExtractStream = tar.extract();
|
||||||
const destinationFileStream = createWriteStream(destinationFilePath);
|
const destinationFileStream = createWriteStream(destinationFilePath);
|
||||||
@ -130,7 +130,7 @@ export async function extractFileFromTarball(
|
|||||||
stream.pipe(destinationFileStream);
|
stream.pipe(destinationFileStream);
|
||||||
stream.on('end', () => {
|
stream.on('end', () => {
|
||||||
isFileExtracted = true;
|
isFileExtracted = true;
|
||||||
resolve();
|
resolve(destinationFilePath);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -9,9 +9,15 @@ export interface NxProjectPackageJsonConfiguration {
|
|||||||
targets?: Record<string, PackageJsonTargetConfiguration>;
|
targets?: Record<string, PackageJsonTargetConfiguration>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface NxMigrationsConfiguration {
|
||||||
|
migrations?: string;
|
||||||
|
packageGroup?: (string | { package: string; version: string })[];
|
||||||
|
}
|
||||||
|
|
||||||
export interface PackageJson {
|
export interface PackageJson {
|
||||||
// Generic Package.Json Configuration
|
// Generic Package.Json Configuration
|
||||||
name: string;
|
name: string;
|
||||||
|
version: string;
|
||||||
scripts?: Record<string, string>;
|
scripts?: Record<string, string>;
|
||||||
dependencies?: Record<string, string>;
|
dependencies?: Record<string, string>;
|
||||||
devDependencies?: Record<string, string>;
|
devDependencies?: Record<string, string>;
|
||||||
@ -30,7 +36,8 @@ export interface PackageJson {
|
|||||||
schematics?: string;
|
schematics?: string;
|
||||||
builders?: string;
|
builders?: string;
|
||||||
executors?: string;
|
executors?: string;
|
||||||
'nx-migrations'?: string;
|
'nx-migrations'?: string | NxMigrationsConfiguration;
|
||||||
|
'ng-update'?: string | NxMigrationsConfiguration;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildTargetFromScript(
|
export function buildTargetFromScript(
|
||||||
|
|||||||
@ -1,8 +1,13 @@
|
|||||||
import { execSync } from 'child_process';
|
import { exec, execSync } from 'child_process';
|
||||||
import { copyFileSync, existsSync, unlinkSync } from 'fs';
|
import { copyFileSync, existsSync } from 'fs';
|
||||||
|
import { remove } from 'fs-extra';
|
||||||
import { dirname, join } from 'path';
|
import { dirname, join } from 'path';
|
||||||
import { dirSync } from 'tmp';
|
import { dirSync } from 'tmp';
|
||||||
|
import { promisify } from 'util';
|
||||||
import { readJsonFile, writeJsonFile } from './fileutils';
|
import { readJsonFile, writeJsonFile } from './fileutils';
|
||||||
|
import { PackageJson } from './package-json';
|
||||||
|
|
||||||
|
const execAsync = promisify(exec);
|
||||||
|
|
||||||
export type PackageManager = 'yarn' | 'pnpm' | 'npm';
|
export type PackageManager = 'yarn' | 'pnpm' | 'npm';
|
||||||
|
|
||||||
@ -114,25 +119,37 @@ export function checkForNPMRC(
|
|||||||
return existsSync(path) ? path : null;
|
return existsSync(path) ? path : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a temporary directory where you can run package manager commands safely.
|
||||||
|
*
|
||||||
|
* For cases where you'd want to install packages that require an `.npmrc` set up,
|
||||||
|
* this function looks up for the nearest `.npmrc` (if exists) and copies it over to the
|
||||||
|
* temp directory.
|
||||||
|
*/
|
||||||
|
export function createTempNpmDirectory(): string {
|
||||||
|
const dir = dirSync().name;
|
||||||
|
|
||||||
|
// A package.json is needed for pnpm pack and for .npmrc to resolve
|
||||||
|
writeJsonFile(`${dir}/package.json`, {});
|
||||||
|
const npmrc = checkForNPMRC();
|
||||||
|
if (npmrc) {
|
||||||
|
// Copy npmrc if it exists, so that npm still follows it.
|
||||||
|
copyFileSync(npmrc, `${dir}/.npmrc`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return dir;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the resolved version for a given package and version tag using the
|
* Returns the resolved version for a given package and version tag using the
|
||||||
* NPM registry (when using Yarn it will fall back to NPM to fetch the info).
|
* NPM registry (when using Yarn it will fall back to NPM to fetch the info).
|
||||||
*/
|
*/
|
||||||
export function resolvePackageVersionUsingRegistry(
|
export async function resolvePackageVersionUsingRegistry(
|
||||||
packageName: string,
|
packageName: string,
|
||||||
version: string
|
version: string
|
||||||
): string {
|
): Promise<string> {
|
||||||
let pm = detectPackageManager();
|
|
||||||
if (pm === 'yarn') {
|
|
||||||
pm = 'npm';
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = execSync(`${pm} view ${packageName}@${version} version`, {
|
const result = await packageRegistryView(packageName, version, 'version');
|
||||||
stdio: [],
|
|
||||||
})
|
|
||||||
.toString()
|
|
||||||
.trim();
|
|
||||||
|
|
||||||
if (!result) {
|
if (!result) {
|
||||||
throw new Error(`Unable to resolve version ${packageName}@${version}.`);
|
throw new Error(`Unable to resolve version ${packageName}@${version}.`);
|
||||||
@ -157,32 +174,69 @@ export function resolvePackageVersionUsingRegistry(
|
|||||||
* installing it in a temporary directory and fetching the version from the
|
* installing it in a temporary directory and fetching the version from the
|
||||||
* package.json.
|
* package.json.
|
||||||
*/
|
*/
|
||||||
export function resolvePackageVersionUsingInstallation(
|
export async function resolvePackageVersionUsingInstallation(
|
||||||
packageName: string,
|
packageName: string,
|
||||||
version: string
|
version: string
|
||||||
): string {
|
): Promise<string> {
|
||||||
const dir = dirSync().name;
|
const dir = createTempNpmDirectory();
|
||||||
const npmrc = checkForNPMRC();
|
|
||||||
|
|
||||||
writeJsonFile(`${dir}/package.json`, {});
|
|
||||||
if (npmrc) {
|
|
||||||
// Copy npmrc if it exists, so that npm still follows it.
|
|
||||||
copyFileSync(npmrc, `${dir}/.npmrc`);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
try {
|
||||||
const pmc = getPackageManagerCommand();
|
const pmc = getPackageManagerCommand();
|
||||||
execSync(`${pmc.add} ${packageName}@${version}`, { stdio: [], cwd: dir });
|
await execAsync(`${pmc.add} ${packageName}@${version}`, { cwd: dir });
|
||||||
|
|
||||||
const packageJsonPath = require.resolve(`${packageName}/package.json`, {
|
const packageJsonPath = require.resolve(`${packageName}/package.json`, {
|
||||||
paths: [dir],
|
paths: [dir],
|
||||||
});
|
});
|
||||||
const { version: resolvedVersion } = readJsonFile(packageJsonPath);
|
|
||||||
|
|
||||||
|
return readJsonFile<PackageJson>(packageJsonPath).version;
|
||||||
|
} finally {
|
||||||
try {
|
try {
|
||||||
unlinkSync(dir);
|
await remove(dir);
|
||||||
} catch {
|
} catch {
|
||||||
// It's okay if this fails, the OS will clean it up eventually
|
// It's okay if this fails, the OS will clean it up eventually
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return resolvedVersion;
|
}
|
||||||
|
|
||||||
|
export async function packageRegistryView(
|
||||||
|
pkg: string,
|
||||||
|
version: string,
|
||||||
|
args: string
|
||||||
|
): Promise<string> {
|
||||||
|
let pm = detectPackageManager();
|
||||||
|
if (pm === 'yarn') {
|
||||||
|
/**
|
||||||
|
* yarn has `yarn info` but it behaves differently than (p)npm,
|
||||||
|
* which makes it's usage unreliable
|
||||||
|
*
|
||||||
|
* @see https://github.com/nrwl/nx/pull/9667#discussion_r842553994
|
||||||
|
*/
|
||||||
|
pm = 'npm';
|
||||||
|
}
|
||||||
|
|
||||||
|
const { stdout } = await execAsync(`${pm} view ${pkg}@${version} ${args}`);
|
||||||
|
return stdout.toString().trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function packageRegistryPack(
|
||||||
|
cwd: string,
|
||||||
|
pkg: string,
|
||||||
|
version: string
|
||||||
|
): Promise<{ tarballPath: string }> {
|
||||||
|
let pm = detectPackageManager();
|
||||||
|
if (pm === 'yarn') {
|
||||||
|
/**
|
||||||
|
* `(p)npm pack` will download a tarball of the specified version,
|
||||||
|
* whereas `yarn` pack creates a tarball of the active workspace, so it
|
||||||
|
* does not work for getting the content of a library.
|
||||||
|
*
|
||||||
|
* @see https://github.com/nrwl/nx/pull/9667#discussion_r842553994
|
||||||
|
*/
|
||||||
|
pm = 'npm';
|
||||||
|
}
|
||||||
|
|
||||||
|
const { stdout } = await execAsync(`${pm} pack ${pkg}@${version}`, { cwd });
|
||||||
|
|
||||||
|
const tarballPath = stdout.trim();
|
||||||
|
return { tarballPath };
|
||||||
}
|
}
|
||||||
|
|||||||
@ -107,6 +107,7 @@ describe('project graph utils', () => {
|
|||||||
describe('mergeNpmScriptsWithTargets', () => {
|
describe('mergeNpmScriptsWithTargets', () => {
|
||||||
const packageJson: PackageJson = {
|
const packageJson: PackageJson = {
|
||||||
name: 'my-app',
|
name: 'my-app',
|
||||||
|
version: '0.0.0',
|
||||||
scripts: {
|
scripts: {
|
||||||
build: 'echo 1',
|
build: 'echo 1',
|
||||||
},
|
},
|
||||||
|
|||||||
@ -47,7 +47,12 @@
|
|||||||
"@nrwl/nx-plugin",
|
"@nrwl/nx-plugin",
|
||||||
"@nrwl/react",
|
"@nrwl/react",
|
||||||
"@nrwl/storybook",
|
"@nrwl/storybook",
|
||||||
"@nrwl/web"
|
"@nrwl/web",
|
||||||
|
"@nrwl/js",
|
||||||
|
"@nrwl/cli",
|
||||||
|
"@nrwl/nx-cloud",
|
||||||
|
"@nrwl/react-native",
|
||||||
|
"@nrwl/detox"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
@ -82,6 +87,32 @@
|
|||||||
"tslib": "^2.3.0"
|
"tslib": "^2.3.0"
|
||||||
},
|
},
|
||||||
"nx-migrations": {
|
"nx-migrations": {
|
||||||
"migrations": "./migrations.json"
|
"migrations": "./migrations.json",
|
||||||
|
"packageGroup": [
|
||||||
|
"@nrwl/workspace",
|
||||||
|
"@nrwl/angular",
|
||||||
|
"nx",
|
||||||
|
"@nrwl/cypress",
|
||||||
|
"@nrwl/devkit",
|
||||||
|
"@nrwl/eslint-plugin-nx",
|
||||||
|
"@nrwl/express",
|
||||||
|
"@nrwl/jest",
|
||||||
|
"@nrwl/linter",
|
||||||
|
"@nrwl/nest",
|
||||||
|
"@nrwl/next",
|
||||||
|
"@nrwl/node",
|
||||||
|
"@nrwl/nx-plugin",
|
||||||
|
"@nrwl/react",
|
||||||
|
"@nrwl/storybook",
|
||||||
|
"@nrwl/web",
|
||||||
|
"@nrwl/js",
|
||||||
|
"@nrwl/cli",
|
||||||
|
"@nrwl/react-native",
|
||||||
|
"@nrwl/detox",
|
||||||
|
{
|
||||||
|
"package": "@nrwl/nx-cloud",
|
||||||
|
"version": "latest"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user