Skip to content

Commit

Permalink
fix(2396): metadata checks should run on prerelease tags (#2397)
Browse files Browse the repository at this point in the history
## Proposed change

- Use `npm view <package> versions` instead of `npm view <package@range>
version` to include the prerelease tags
- Extends the `NpmSemverResolver` used for yarn to include prerelease
tags

## Related issues

<!--
Please make sure to follow the [contribution
guidelines](https://github.com/amadeus-digital/Otter/blob/main/CONTRIBUTING.md)
-->

<!-- * 🐛 Fix #issue -->
* 🐛 Fix resolves #2396 
<!-- * 🚀 Feature #issue -->
<!-- * 🚀 Feature resolves #issue -->
<!-- * :octocat: Pull Request #issue -->
  • Loading branch information
fpaul-1A authored Nov 6, 2024
2 parents 57d295f + 8824a17 commit 78ac431
Show file tree
Hide file tree
Showing 6 changed files with 143 additions and 37 deletions.
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
37 changes: 26 additions & 11 deletions packages/@o3r/localization/builders/metadata-check/index.it.spec.ts
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

0 comments on commit 78ac431

Please sign in to comment.