Skip to content

Commit

Permalink
fix(core): nx migrate should accept tags other than latest and next f…
Browse files Browse the repository at this point in the history
…or community packages (#15673)
  • Loading branch information
AgentEnder committed Mar 16, 2023
1 parent 041d3b6 commit 9791f31
Show file tree
Hide file tree
Showing 3 changed files with 127 additions and 60 deletions.
116 changes: 77 additions & 39 deletions packages/nx/src/command-line/migrate.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import * as enquirer from 'enquirer';
import { PackageJson } from '../utils/package-json';
import * as packageMgrUtils from '../utils/package-manager';

import {
Migrator,
normalizeVersion,
Expand Down Expand Up @@ -1477,6 +1479,9 @@ describe('Migration', () => {
it('should return version when it meets semver requirements', () => {
expect(normalizeVersion('1.2.3')).toEqual('1.2.3');
expect(normalizeVersion('1.2.3-beta.1')).toEqual('1.2.3-beta.1');
expect(normalizeVersion('1.2.3-beta-next.1')).toEqual(
'1.2.3-beta-next.1'
);
});

it('should handle versions missing a patch or a minor', () => {
Expand All @@ -1486,15 +1491,17 @@ describe('Migration', () => {
});

it('should handle incorrect versions', () => {
expect(normalizeVersion('1-invalid-version')).toEqual('1.0.0-invalid');
expect(normalizeVersion('1-invalid-version')).toEqual(
'1.0.0-invalid-version'
);
expect(normalizeVersion('1.invalid-version')).toEqual('1.0.0');
expect(normalizeVersion('invalid-version')).toEqual('0.0.0');
});
});

describe('parseMigrationsOptions', () => {
it('should work for generating migrations', () => {
const r = parseMigrationsOptions({
it('should work for generating migrations', async () => {
const r = await parseMigrationsOptions({
packageAndVersion: '8.12.0',
from: '@myscope/a@12.3,@myscope/b@1.1.1',
to: '@myscope/c@12.3.1',
Expand All @@ -1513,8 +1520,8 @@ describe('Migration', () => {
});
});

it('should work for running migrations', () => {
const r = parseMigrationsOptions({
it('should work for running migrations', async () => {
const r = await parseMigrationsOptions({
runMigrations: '',
ifExists: true,
});
Expand All @@ -1525,117 +1532,148 @@ describe('Migration', () => {
});
});

it('should handle different variations of the target package', () => {
it('should handle different variations of the target package', async () => {
jest
.spyOn(packageMgrUtils, 'packageRegistryView')
.mockImplementation((pkg, version) => {
return Promise.resolve(version);
});
expect(
parseMigrationsOptions({ packageAndVersion: '@angular/core' })
await parseMigrationsOptions({ packageAndVersion: '@angular/core' })
).toMatchObject({
targetPackage: '@angular/core',
targetVersion: 'latest',
});
expect(
parseMigrationsOptions({ packageAndVersion: '8.12' })
await parseMigrationsOptions({ packageAndVersion: '8.12' })
).toMatchObject({
targetPackage: '@nrwl/workspace',
targetVersion: '8.12.0',
});
expect(parseMigrationsOptions({ packageAndVersion: '8' })).toMatchObject({
expect(
await parseMigrationsOptions({ packageAndVersion: '8' })
).toMatchObject({
targetPackage: '@nrwl/workspace',
targetVersion: '8.0.0',
});
expect(parseMigrationsOptions({ packageAndVersion: '12' })).toMatchObject(
{
targetPackage: '@nrwl/workspace',
targetVersion: '12.0.0',
}
);
expect(
parseMigrationsOptions({ packageAndVersion: '8.12.0-beta.0' })
await parseMigrationsOptions({ packageAndVersion: '12' })
).toMatchObject({
targetPackage: '@nrwl/workspace',
targetVersion: '12.0.0',
});
expect(
await parseMigrationsOptions({ packageAndVersion: '8.12.0-beta.0' })
).toMatchObject({
targetPackage: '@nrwl/workspace',
targetVersion: '8.12.0-beta.0',
});
expect(
parseMigrationsOptions({ packageAndVersion: 'next' })
await parseMigrationsOptions({ packageAndVersion: 'next' })
).toMatchObject({
targetPackage: 'nx',
targetVersion: 'next',
});
expect(
parseMigrationsOptions({ packageAndVersion: '13.10.0' })
await parseMigrationsOptions({ packageAndVersion: '13.10.0' })
).toMatchObject({
targetPackage: '@nrwl/workspace',
targetVersion: '13.10.0',
});
expect(
parseMigrationsOptions({ packageAndVersion: '@nrwl/workspace@8.12' })
await parseMigrationsOptions({
packageAndVersion: '@nrwl/workspace@8.12',
})
).toMatchObject({
targetPackage: '@nrwl/workspace',
targetVersion: '8.12.0',
});
expect(
parseMigrationsOptions({ packageAndVersion: 'mypackage@8.12' })
await parseMigrationsOptions({ packageAndVersion: 'mypackage@8.12' })
).toMatchObject({
targetPackage: 'mypackage',
targetVersion: '8.12.0',
});
expect(
parseMigrationsOptions({ packageAndVersion: 'mypackage' })
await parseMigrationsOptions({ packageAndVersion: 'mypackage' })
).toMatchObject({
targetPackage: 'mypackage',
targetVersion: 'latest',
});
expect(
parseMigrationsOptions({ packageAndVersion: 'mypackage2' })
await parseMigrationsOptions({ packageAndVersion: 'mypackage2' })
).toMatchObject({
targetPackage: 'mypackage2',
targetVersion: 'latest',
});
expect(
parseMigrationsOptions({ packageAndVersion: '@nrwl/workspace@latest' })
await parseMigrationsOptions({
packageAndVersion: '@nrwl/workspace@latest',
})
).toMatchObject({
targetPackage: '@nrwl/workspace',
targetVersion: 'latest',
});
expect(
await parseMigrationsOptions({
packageAndVersion: '@nrwl/workspace@alpha',
})
).toMatchObject({
targetPackage: '@nrwl/workspace',
targetVersion: 'alpha',
});
});

it('should handle incorrect from', () => {
expect(() =>
it('should handle incorrect from', async () => {
await expect(() =>
parseMigrationsOptions({
packageAndVersion: '8.12.0',
from: '@myscope/a@',
})
).toThrowError(`Incorrect 'from' section. Use --from="package@version"`);
expect(() =>
).rejects.toThrowError(
`Incorrect 'from' section. Use --from="package@version"`
);
await expect(() =>
parseMigrationsOptions({
packageAndVersion: '8.12.0',
from: '@myscope/a',
})
).toThrowError(`Incorrect 'from' section. Use --from="package@version"`);
expect(() =>
).rejects.toThrowError(
`Incorrect 'from' section. Use --from="package@version"`
);
await expect(() =>
parseMigrationsOptions({ packageAndVersion: '8.12.0', from: 'myscope' })
).toThrowError(`Incorrect 'from' section. Use --from="package@version"`);
).rejects.toThrowError(
`Incorrect 'from' section. Use --from="package@version"`
);
});

it('should handle incorrect to', () => {
expect(() =>
it('should handle incorrect to', async () => {
await expect(() =>
parseMigrationsOptions({
packageAndVersion: '8.12.0',
to: '@myscope/a@',
})
).toThrowError(`Incorrect 'to' section. Use --to="package@version"`);
expect(() =>
).rejects.toThrowError(
`Incorrect 'to' section. Use --to="package@version"`
);
await expect(() =>
parseMigrationsOptions({
packageAndVersion: '8.12.0',
to: '@myscope/a',
})
).toThrowError(`Incorrect 'to' section. Use --to="package@version"`);
expect(() =>
).rejects.toThrowError(
`Incorrect 'to' section. Use --to="package@version"`
);
await expect(() =>
parseMigrationsOptions({ packageAndVersion: '8.12.0', to: 'myscope' })
).toThrowError(`Incorrect 'to' section. Use --to="package@version"`);
).rejects.toThrowError(
`Incorrect 'to' section. Use --to="package@version"`
);
});

it('should handle backslashes in package names', () => {
const r = parseMigrationsOptions({
it('should handle backslashes in package names', async () => {
const r = await parseMigrationsOptions({
packageAndVersion: '@nrwl\\workspace@8.12.0',
from: '@myscope\\a@12.3,@myscope\\b@1.1.1',
to: '@myscope\\c@12.3.1',
Expand Down
67 changes: 48 additions & 19 deletions packages/nx/src/command-line/migrate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,10 @@ export interface ResolvedMigrationConfiguration extends MigrationsJson {
const execAsync = promisify(exec);

export function normalizeVersion(version: string) {
const [semver, prereleaseTag] = version.split('-');
const [semver, ...prereleaseTagParts] = version.split('-');
// Handle versions like 1.0.0-beta-next.2
const prereleaseTag = prereleaseTagParts.join('-');

const [major, minor, patch] = semver.split('.');

const newSemver = `${major || 0}.${minor || 0}.${patch || 0}`;
Expand Down Expand Up @@ -614,14 +617,24 @@ const LEGACY_NRWL_PACKAGE_GROUP: ArrayPackageGroup = [
{ package: '@nrwl/tao', version: '*' },
];

function normalizeVersionWithTagCheck(version: string) {
if (version === 'latest' || version === 'next') return version;
async function normalizeVersionWithTagCheck(
pkg: string,
version: string
): Promise<string> {
// This doesn't seem like a valid version, lets check if its a tag on the registry.
if (version && !coerce(version)) {
try {
return packageRegistryView(pkg, version, 'version');
} catch {
// fall through to old logic
}
}
return normalizeVersion(version);
}

function versionOverrides(overrides: string, param: string) {
const res = {};
overrides.split(',').forEach((p) => {
async function versionOverrides(overrides: string, param: string) {
const res: Record<string, string> = {};
const promises = overrides.split(',').map((p) => {
const split = p.lastIndexOf('@');
if (split === -1 || split === 0) {
throw new Error(
Expand All @@ -635,13 +648,19 @@ function versionOverrides(overrides: string, param: string) {
`Incorrect '${param}' section. Use --${param}="package@version"`
);
}
res[normalizeSlashes(selectedPackage)] =
normalizeVersionWithTagCheck(selectedVersion);
return normalizeVersionWithTagCheck(selectedPackage, selectedVersion).then(
(version) => {
res[normalizeSlashes(selectedPackage)] = version;
}
);
});
await Promise.all(promises);
return res;
}

function parseTargetPackageAndVersion(args: string) {
async function parseTargetPackageAndVersion(
args: string
): Promise<{ targetPackage: string; targetVersion: string }> {
if (!args) {
throw new Error(
`Provide the correct package name and version. E.g., my-package@9.0.0.`
Expand All @@ -662,7 +681,10 @@ function parseTargetPackageAndVersion(args: string) {
`Provide the correct package name and version. E.g., my-package@9.0.0.`
);
}
const targetVersion = normalizeVersionWithTagCheck(maybeVersion);
const targetVersion = await normalizeVersionWithTagCheck(
targetPackage,
maybeVersion
);
return { targetPackage, targetVersion };
}
} else {
Expand All @@ -672,7 +694,10 @@ function parseTargetPackageAndVersion(args: string) {
valid(args) ||
args.match(/^\d+(?:\.\d+)?(?:\.\d+)?$/)
) {
const targetVersion = normalizeVersionWithTagCheck(args);
// Passing `nx` here may seem wrong, but nx and @nrwl/workspace are synced in version.
// We could duplicate the ternary below, but its not necessary since they are equivalent
// on the registry
const targetVersion = await normalizeVersionWithTagCheck('nx', args);
const targetPackage =
!['latest', 'next'].includes(args) && lt(targetVersion, '14.0.0-beta.0')
? '@nrwl/workspace'
Expand Down Expand Up @@ -707,19 +732,23 @@ type RunMigrations = {
ifExists: boolean;
};

export function parseMigrationsOptions(options: {
export async function parseMigrationsOptions(options: {
[k: string]: any;
}): GenerateMigrations | RunMigrations {
}): Promise<GenerateMigrations | RunMigrations> {
if (options.runMigrations === '') {
options.runMigrations = 'migrations.json';
}

if (!options.runMigrations) {
const from = options.from
? versionOverrides(options.from as string, 'from')
: {};
const to = options.to ? versionOverrides(options.to as string, 'to') : {};
const { targetPackage, targetVersion } = parseTargetPackageAndVersion(
const [from, to] = await Promise.all([
options.from
? versionOverrides(options.from as string, 'from')
: Promise.resolve({} as Record<string, string>),
options.to
? await versionOverrides(options.to as string, 'to')
: Promise.resolve({} as Record<string, string>),
]);
const { targetPackage, targetVersion } = await parseTargetPackageAndVersion(
options['packageAndVersion']
);
return {
Expand Down Expand Up @@ -1525,7 +1554,7 @@ export async function migrate(
}

return handleErrors(process.env.NX_VERBOSE_LOGGING === 'true', async () => {
const opts = parseMigrationsOptions(args);
const opts = await parseMigrationsOptions(args);
if (opts.type === 'generateMigrations') {
await generateMigrationsJsonAndUpdatePackageJson(root, opts);
} else {
Expand Down
4 changes: 2 additions & 2 deletions scripts/depcheck/discrepancies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { satisfies } from 'semver';

// Ignore packages that are defined here per package
const IGNORE_MATCHES = {
'*': [],
'*': [] as string[],
angular: ['webpack-merge', '@phenomnomnominal/tsquery'],
cypress: ['webpack', '@babel/core', 'babel-loader'],
};
Expand All @@ -13,7 +13,7 @@ export default function getDiscrepancies(
projectDependencies: JSON,
devDependencies: JSON
) {
return Object.keys(projectDependencies)
return Object.keys(projectDependencies ?? ({} as Record<string, string>))
.filter((p) => !p.startsWith('@nrwl/') && p !== 'nx')
.filter((p) =>
!IGNORE_MATCHES['*'].includes(p) && IGNORE_MATCHES[name]
Expand Down

1 comment on commit 9791f31

@vercel
Copy link

@vercel vercel bot commented on 9791f31 Mar 16, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

nx-dev – ./

nx-dev-nrwl.vercel.app
nx-five.vercel.app
nx.dev
nx-dev-git-master-nrwl.vercel.app

Please sign in to comment.