diff --git a/.yarn/versions/56c1f837.yml b/.yarn/versions/56c1f837.yml new file mode 100644 index 000000000000..30ad3dd6aa0f --- /dev/null +++ b/.yarn/versions/56c1f837.yml @@ -0,0 +1,36 @@ +releases: + "@yarnpkg/core": patch + "@yarnpkg/plugin-npm": patch + +declined: + - "@yarnpkg/plugin-catalog" + - "@yarnpkg/plugin-compat" + - "@yarnpkg/plugin-constraints" + - "@yarnpkg/plugin-dlx" + - "@yarnpkg/plugin-essentials" + - "@yarnpkg/plugin-exec" + - "@yarnpkg/plugin-file" + - "@yarnpkg/plugin-git" + - "@yarnpkg/plugin-github" + - "@yarnpkg/plugin-http" + - "@yarnpkg/plugin-init" + - "@yarnpkg/plugin-interactive-tools" + - "@yarnpkg/plugin-jsr" + - "@yarnpkg/plugin-link" + - "@yarnpkg/plugin-nm" + - "@yarnpkg/plugin-npm-cli" + - "@yarnpkg/plugin-pack" + - "@yarnpkg/plugin-patch" + - "@yarnpkg/plugin-pnp" + - "@yarnpkg/plugin-pnpm" + - "@yarnpkg/plugin-stage" + - "@yarnpkg/plugin-typescript" + - "@yarnpkg/plugin-version" + - "@yarnpkg/plugin-workspace-tools" + - "@yarnpkg/builder" + - "@yarnpkg/cli" + - "@yarnpkg/doctor" + - "@yarnpkg/extensions" + - "@yarnpkg/nm" + - "@yarnpkg/pnpify" + - "@yarnpkg/sdks" diff --git a/packages/acceptance-tests/pkg-tests-specs/sources/features/npmMinimalAgeGate.test.ts b/packages/acceptance-tests/pkg-tests-specs/sources/features/npmMinimalAgeGate.test.ts index 4222f8b2e710..2f9b6ed7be1f 100644 --- a/packages/acceptance-tests/pkg-tests-specs/sources/features/npmMinimalAgeGate.test.ts +++ b/packages/acceptance-tests/pkg-tests-specs/sources/features/npmMinimalAgeGate.test.ts @@ -472,6 +472,28 @@ describe(`Features`, () => { }), ); + test( + `unset scope gate inherits from the disabled global gate`, + makeTemporaryEnv({}, async ({path, run, source}) => { + const registryUrl = await startPackageServer(); + await xfs.writeJsonPromise(`${path}/.yarnrc.yml` as PortablePath, { + npmMinimalAgeGate: 0, + npmScopes: { + scoped: { + npmRegistryServer: registryUrl, + }, + }, + }); + + await run(`add`, `@scoped/release-date@1.1.1`); + + await expect(source(`require('@scoped/release-date/package.json')`)).resolves.toMatchObject({ + name: `@scoped/release-date`, + version: `1.1.1`, + }); + }), + ); + test( `--no-time-gate bypasses scope override`, makeTemporaryEnv({}, async ({path, run, source}) => { diff --git a/packages/plugin-npm/sources/index.ts b/packages/plugin-npm/sources/index.ts index ff2e33635538..f50c359acda3 100644 --- a/packages/plugin-npm/sources/index.ts +++ b/packages/plugin-npm/sources/index.ts @@ -77,6 +77,14 @@ const scopablePackageGateSettings = { }, } satisfies Record; +const scopedPackageGateSettings = { + npmMinimalAgeGate: { + ...scopablePackageGateSettings.npmMinimalAgeGate, + fallback: `npmMinimalAgeGate`, + default: undefined, + }, +} satisfies Record; + const globalOnlyPackageGateSettings = { npmPreapprovedPackages: { description: `Array of package descriptors or package name glob patterns to exclude from the minimum release age check`, @@ -107,7 +115,7 @@ declare module '@yarnpkg/core' { npmPublishRegistry: string | null; npmRegistryServer: string; - npmMinimalAgeGate: number; + npmMinimalAgeGate: number | undefined; }>>; npmRegistries: Map = { description: string; type: T; + fallback?: string; } & ({isArray?: false} | {isArray: true, concatenateValues?: boolean}); export enum DurationUnit { @@ -168,7 +169,7 @@ export enum DurationUnit { WEEKS = `w`, } export type DurationSettingsDefinition = BaseSettingsDefinition & { - default: string; + default: string | undefined; unit: DurationUnit; isNullable?: boolean; }; @@ -953,6 +954,9 @@ function getDefaultValue(configuration: Configuration, definition: SettingsDefin } } case SettingsType.DURATION: { + if (typeof definition.default === `undefined`) + return undefined; + return miscUtils.parseDuration(definition.default, definition.unit); } default: { @@ -966,7 +970,10 @@ type SettingTransforms = { getNativePaths: boolean; }; -function transformConfiguration(rawValue: unknown, definition: SettingsDefinitionNoDefault, transforms: SettingTransforms) { +function transformConfiguration(configuration: Configuration, rawValue: unknown, definition: SettingsDefinitionNoDefault, transforms: SettingTransforms) { + if (typeof rawValue === `undefined` && typeof definition.fallback !== `undefined`) + return configuration.get(definition.fallback); + if (definition.type === SettingsType.SECRET && typeof rawValue === `string` && transforms.hideSecrets) return SECRET; if (definition.type === SettingsType.ABSOLUTE_PATH && typeof rawValue === `string` && transforms.getNativePaths) @@ -976,7 +983,7 @@ function transformConfiguration(rawValue: unknown, definition: SettingsDefinitio const newValue: Array = []; for (const value of rawValue) - newValue.push(transformConfiguration(value, definition, transforms)); + newValue.push(transformConfiguration(configuration, value, definition, transforms)); return newValue; } @@ -988,7 +995,7 @@ function transformConfiguration(rawValue: unknown, definition: SettingsDefinitio const newValue: Map = new Map(); for (const [key, value] of rawValue.entries()) { - const transformedValue = transformConfiguration(value, definition.valueDefinition, transforms); + const transformedValue = transformConfiguration(configuration, value, definition.valueDefinition, transforms); if (typeof transformedValue !== `undefined`) { newValue.set(key, transformedValue); } @@ -1006,7 +1013,7 @@ function transformConfiguration(rawValue: unknown, definition: SettingsDefinitio for (const [key, value] of rawValue.entries()) { const propertyDefinition = definition.properties[key]; - const transformedValue = transformConfiguration(value, propertyDefinition, transforms); + const transformedValue = transformConfiguration(configuration, value, propertyDefinition, transforms); if (typeof transformedValue !== `undefined`) { newValue.set(key, transformedValue); } @@ -1733,7 +1740,7 @@ export class Configuration { if (typeof definition === `undefined`) throw new UsageError(`Couldn't find a configuration settings named "${key}"`); - return transformConfiguration(rawValue, definition, { + return transformConfiguration(this, rawValue, definition, { hideSecrets, getNativePaths, }) as T; diff --git a/packages/yarnpkg-core/tests/Configuration.test.ts b/packages/yarnpkg-core/tests/Configuration.test.ts index 285f9e1cb203..6bebb049f737 100644 --- a/packages/yarnpkg-core/tests/Configuration.test.ts +++ b/packages/yarnpkg-core/tests/Configuration.test.ts @@ -323,6 +323,24 @@ describe(`Configuration`, () => { }); describe(`Configuration merging`, () => { + it(`should resolve scoped npmMinimalAgeGate from the global value when unset`, async () => { + await initializeConfiguration({ + npmMinimalAgeGate: 0, + npmScopes: { + scopeName: { + npmRegistryServer: `https://example.com/npm/registry`, + }, + }, + }, async dir => { + const configuration = await Configuration.find(dir, { + modules: new Map([[`@yarnpkg/plugin-npm`, NpmPlugin]]), + plugins: new Set([`@yarnpkg/plugin-npm`]), + }); + + expect(configuration.getSpecial(`npmScopes`, {hideSecrets: false}).get(`scopeName`).get(`npmMinimalAgeGate`)).toBe(0); + }); + }); + it(`should merge map properties`, async () => { await initializeConfiguration({ npmRegistryServer: `https://foo.server`,