Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(2396): metadata checks should run on prerelease tags #2431

Merged
merged 1 commit into from
Nov 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 26 additions & 11 deletions packages/@o3r/components/builders/metadata-check/index.it.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,11 +191,12 @@ async function writeFileAsJSON(path: string, content: object) {
}

const initTest = async (
allowBreakingChanges: boolean,
newMetadata: ComponentConfigOutput[],
migrationData: MigrationFile<MigrationConfigData>,
packageNameSuffix: string
packageNameSuffix: string,
options?: { allowBreakingChanges?: boolean; prerelease?: string }
) => {
const { allowBreakingChanges = false, prerelease } = options || {};
const { workspacePath, appName, applicationPath, o3rVersion, isYarnTest } = o3rEnvironment.testEnvironment;
const execAppOptions = { ...getDefaultExecSyncOptions(), cwd: applicationPath };
const execAppOptionsWorkspace = { ...getDefaultExecSyncOptions(), cwd: workspacePath };
Expand Down Expand Up @@ -253,7 +254,8 @@ const initTest = async (
latestVersion = baseVersion;
}

const bumpedVersion = inc(latestVersion, 'patch');
const prereleaseSuffix = prerelease ? `-${prerelease}.0` : '';
const bumpedVersion = inc(latestVersion.replace(/-.*$/, ''), 'patch') + prereleaseSuffix;

const args = getPackageManager() === 'yarn' ? [] : ['--no-git-tag-version', '-f'];
packageManagerVersion(bumpedVersion, args, execAppOptions);
Expand All @@ -270,10 +272,23 @@ const initTest = async (
describe('check metadata migration', () => {
test('should not throw', async () => {
await initTest(
true,
newConfigurationMetadata,
defaultMigrationData,
'allow-breaking-changes'
'allow-breaking-changes',
{ allowBreakingChanges: true }
);
const { workspacePath, appName } = o3rEnvironment.testEnvironment;
const execAppOptionsWorkspace = { ...getDefaultExecSyncOptions(), cwd: workspacePath };

expect(() => packageManagerExec({ script: 'ng', args: ['run', `${appName}:check-metadata`] }, execAppOptionsWorkspace)).not.toThrow();
});

test('should not throw on prerelease', async () => {
await initTest(
newConfigurationMetadata,
defaultMigrationData,
'allow-breaking-changes-prerelease',
{ allowBreakingChanges: true, prerelease: 'rc' }
);
const { workspacePath, appName } = o3rEnvironment.testEnvironment;
const execAppOptionsWorkspace = { ...getDefaultExecSyncOptions(), cwd: workspacePath };
Expand All @@ -283,13 +298,13 @@ describe('check metadata migration', () => {

test('should throw because no migration data', async () => {
await initTest(
true,
newConfigurationMetadata,
{
...defaultMigrationData,
changes: []
},
'no-migration-data'
'no-migration-data',
{ allowBreakingChanges: true }
);
const { workspacePath, appName } = o3rEnvironment.testEnvironment;
const execAppOptionsWorkspace = { ...getDefaultExecSyncOptions(), cwd: workspacePath };
Expand All @@ -310,7 +325,6 @@ describe('check metadata migration', () => {

test('should throw because migration data invalid', async () => {
await initTest(
true,
[newConfigurationMetadata[0]],
{
...defaultMigrationData,
Expand All @@ -322,7 +336,8 @@ describe('check metadata migration', () => {
}
}))
},
'invalid-data'
'invalid-data',
{ allowBreakingChanges: true }
);
const { workspacePath, appName } = o3rEnvironment.testEnvironment;
const execAppOptionsWorkspace = { ...getDefaultExecSyncOptions(), cwd: workspacePath };
Expand All @@ -343,13 +358,13 @@ describe('check metadata migration', () => {

test('should throw because breaking changes are not allowed', async () => {
await initTest(
false,
newConfigurationMetadata,
{
...defaultMigrationData,
changes: []
},
'breaking-changes'
'breaking-changes',
{ allowBreakingChanges: false }
);
const { workspacePath, appName } = o3rEnvironment.testEnvironment;
const execAppOptionsWorkspace = { ...getDefaultExecSyncOptions(), cwd: workspacePath };
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { npmHttpUtils, NpmSemverFetcher, NpmSemverResolver } from '@yarnpkg/plugin-npm';
import { Descriptor, miscUtils, Package, ResolveOptions, structUtils } from '@yarnpkg/core';
import { Range, SemVer, valid } from 'semver';

/**
* Unexposed constant from yarn https://github.com/yarnpkg/berry/blob/master/packages/plugin-npm/sources/constants.ts
*/
const PROTOCOL = `npm:`;

/**
* Extends NpmSeverResolver to support prerelease tags
* Check original code from https://github.com/yarnpkg/berry/blob/master/packages/plugin-npm/sources/NpmSemverResolver.ts
*/
export class CustomNpmSemverResolver extends NpmSemverResolver {
/** @inheritDoc */
public override async getCandidates(descriptor: Descriptor, _dependencies: Record<string, Package>, opts: ResolveOptions) {
const range = new Range(descriptor.range.slice(PROTOCOL.length), {includePrerelease: true});
const registryData = await npmHttpUtils.getPackageMetadata(descriptor, {
cache: opts.fetchOptions?.cache,
project: opts.project,
version: valid(range.raw) ? range.raw : undefined
});

const candidates = miscUtils.mapAndFilter(Object.keys(registryData.versions), (version) => {
try {
const candidate = new SemVer(version, {includePrerelease: true});
if (range.test(candidate)) {
return candidate;
}
} catch { }

return miscUtils.mapAndFilter.skip;
});

const noDeprecatedCandidates = candidates.filter((version) => !registryData.versions[version.raw].deprecated);

// If there are versions that aren't deprecated, use them
const finalCandidates = noDeprecatedCandidates.length > 0
? noDeprecatedCandidates
: candidates;

finalCandidates.sort((a, b) => -a.compare(b));

return finalCandidates.map(version => {
const versionLocator = structUtils.makeLocator(descriptor, `${PROTOCOL}${version.raw}`);
const archiveUrl = registryData.versions[version.raw].dist.tarball;

if (NpmSemverFetcher.isConventionalTarballUrl(versionLocator, archiveUrl, {configuration: opts.project.configuration})) {
return versionLocator;
} else {
// eslint-disable-next-line @typescript-eslint/naming-convention
return structUtils.bindLocator(versionLocator, {__archiveUrl: archiveUrl});
}
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,22 @@ export async function getFilesFromRegistry(packageDescriptor: string, paths: str
const tempDirPath = join(tmpdir(), tempDirName);
let extractedFiles: { [key: string]: string } = {};
mkdirSync(tempDirPath);
const [,packageName,packageRange] = sanitizeInput(packageDescriptor).match(/^(.*?)(?:\b@(.+))?$/) || [];

try {
const npmViewCmd = runAndThrowOnError(
`npm view "${sanitizeInput(packageDescriptor)}" version --json`,
`npm view "${packageName}" versions --json`,
{ shell: true, encoding: 'utf8' }
);
const versions = JSON.parse(npmViewCmd.stdout.trim()) as string[] | string;
let versions = JSON.parse(npmViewCmd.stdout.trim()) as string[] | string;
if (typeof versions !== 'string') {
if (packageRange) {
const range = new semver.Range(packageRange, {includePrerelease: true});
versions = versions.filter((v) => range.test(v));
}
versions.sort((a, b) => semver.compare(b, a));
}
const latestVersion = typeof versions === 'string' ? versions : versions[0];
const packageName = packageDescriptor.replace(/\b@[^@]+$/, '');

const npmPackCmd = runAndThrowOnError(
`npm pack "${packageName}@${sanitizeInput(latestVersion)}" --pack-destination "${pathToPosix(tempDirPath)}"`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { npath } from '@yarnpkg/fslib';
import yarnNpmPlugin from '@yarnpkg/plugin-npm';
import { join } from 'node:path';
import { O3rCliError } from '@o3r/schematics';
import { CustomNpmSemverResolver } from './custom-npm-semver-resolver';

// Class copied from https://github.com/yarnpkg/berry/blob/master/packages/yarnpkg-core/sources/MultiResolver.ts
// because it is not exposed in @yarnpkg/core
Expand Down Expand Up @@ -146,7 +147,7 @@ async function fetchPackage(project: Project, descriptor: Descriptor): Promise<F
const report = new ThrowReport();
const multiResolver = new MultiResolver(
// eslint-disable-next-line new-cap
(yarnNpmPlugin.resolvers || []).map((resolver) => new resolver())
([CustomNpmSemverResolver, ...yarnNpmPlugin.resolvers || []]).map((resolver) => new resolver())
);
const multiFetcher = new MultiFetcher(
// eslint-disable-next-line new-cap
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,12 @@ async function writeFileAsJSON(path: string, content: object) {
}

const initTest = async (
allowBreakingChanges: boolean,
newMetadata: LocalizationMetadata,
migrationData: MigrationFile<MigrationLocalizationMetadata>,
packageNameSuffix: string
packageNameSuffix: string,
options?: { allowBreakingChanges?: boolean; prerelease?: string }
) => {
const { allowBreakingChanges = false, prerelease } = options || {};
const { workspacePath, appName, applicationPath, o3rVersion, isYarnTest } = o3rEnvironment.testEnvironment;
const execAppOptions = { ...getDefaultExecSyncOptions(), cwd: applicationPath };
const execAppOptionsWorkspace = { ...getDefaultExecSyncOptions(), cwd: workspacePath };
Expand Down Expand Up @@ -129,7 +130,8 @@ const initTest = async (
latestVersion = baseVersion;
}

const bumpedVersion = inc(latestVersion, 'patch');
const prereleaseSuffix = prerelease ? `-${prerelease}.0` : '';
const bumpedVersion = inc(latestVersion.replace(/-.*$/, ''), 'patch') + prereleaseSuffix;

const args = getPackageManager() === 'yarn' ? [] : ['--no-git-tag-version', '-f'];
packageManagerVersion(bumpedVersion, args, execAppOptions);
Expand All @@ -146,10 +148,23 @@ const initTest = async (
describe('check metadata migration', () => {
test('should not throw', async () => {
await initTest(
true,
newLocalizationMetadata,
defaultMigrationData,
'allow-breaking-changes'
'allow-breaking-changes',
{ allowBreakingChanges: true }
);
const { workspacePath, appName } = o3rEnvironment.testEnvironment;
const execAppOptionsWorkspace = { ...getDefaultExecSyncOptions(), cwd: workspacePath };

expect(() => packageManagerExec({ script: 'ng', args: ['run', `${appName}:check-metadata`] }, execAppOptionsWorkspace)).not.toThrow();
});

test('should not throw on prerelease', async () => {
await initTest(
newLocalizationMetadata,
defaultMigrationData,
'allow-breaking-changes-prerelease',
{ allowBreakingChanges: true, prerelease: 'rc' }
);
const { workspacePath, appName } = o3rEnvironment.testEnvironment;
const execAppOptionsWorkspace = { ...getDefaultExecSyncOptions(), cwd: workspacePath };
Expand All @@ -159,13 +174,13 @@ describe('check metadata migration', () => {

test('should throw because no migration data', async () => {
await initTest(
true,
newLocalizationMetadata,
{
...defaultMigrationData,
changes: []
},
'no-migration-data'
'no-migration-data',
{ allowBreakingChanges: true }
);
const { workspacePath, appName } = o3rEnvironment.testEnvironment;
const execAppOptionsWorkspace = { ...getDefaultExecSyncOptions(), cwd: workspacePath };
Expand All @@ -185,7 +200,6 @@ describe('check metadata migration', () => {

test('should throw because migration data invalid', async () => {
await initTest(
true,
[newLocalizationMetadata[0]],
{
...defaultMigrationData,
Expand All @@ -197,7 +211,8 @@ describe('check metadata migration', () => {
}
}))
},
'invalid-data'
'invalid-data',
{ allowBreakingChanges: true }
);
const { workspacePath, appName } = o3rEnvironment.testEnvironment;
const execAppOptionsWorkspace = { ...getDefaultExecSyncOptions(), cwd: workspacePath };
Expand All @@ -217,13 +232,13 @@ describe('check metadata migration', () => {

test('should throw because breaking changes are not allowed', async () => {
await initTest(
false,
newLocalizationMetadata,
{
...defaultMigrationData,
changes: []
},
'breaking-changes'
'breaking-changes',
{ allowBreakingChanges: false }
);
const { workspacePath, appName } = o3rEnvironment.testEnvironment;
const execAppOptionsWorkspace = { ...getDefaultExecSyncOptions(), cwd: workspacePath };
Expand Down
Loading
Loading