From c0d1ecd72816e13e2d41d1883b6c0fab08449158 Mon Sep 17 00:00:00 2001 From: ychampion Date: Thu, 9 Jul 2026 14:31:53 +0000 Subject: [PATCH] fix(npm): inherit global minimal age gate in scopes --- .yarn/versions/8b92376b.yml | 36 +++++++++++++++++++ .../features/npmMinimalAgeGate.test.ts | 25 +++++++++++++ packages/plugin-npm/sources/index.ts | 14 ++++++-- .../yarnpkg-core/sources/Configuration.ts | 8 ++++- 4 files changed, 80 insertions(+), 3 deletions(-) create mode 100644 .yarn/versions/8b92376b.yml diff --git a/.yarn/versions/8b92376b.yml b/.yarn/versions/8b92376b.yml new file mode 100644 index 000000000000..30ad3dd6aa0f --- /dev/null +++ b/.yarn/versions/8b92376b.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..74275062f919 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,31 @@ describe(`Features`, () => { }), ); + test( + `scope inherits disabled global gate when unset`, + makeTemporaryEnv({}, async ({path, run, source}) => { + const registryUrl = await startPackageServer(); + await xfs.writeJsonPromise(`${path}/.yarnrc.yml` as PortablePath, { + npmMinimalAgeGate: 0, + npmScopes: { + scoped: { + npmRegistryServer: registryUrl, + }, + }, + }); + + const {stdout} = await run(`config`, `get`, `npmScopes`, `--json`); + expect(JSON.parse(stdout).scoped.npmMinimalAgeGate).toBeNull(); + + await run(`add`, `@scoped/release-date@^1.0.0`); + + await expect(source(`require('@scoped/release-date/package.json')`)).resolves.toMatchObject({ + name: `@scoped/release-date`, + version: `1.1.2`, + }); + }), + ); + 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..66c599fb82aa 100644 --- a/packages/plugin-npm/sources/index.ts +++ b/packages/plugin-npm/sources/index.ts @@ -77,6 +77,16 @@ const scopablePackageGateSettings = { }, } satisfies Record; +const scopedPackageGateSettings = { + npmMinimalAgeGate: { + description: `Minimum age of a package version according to the publish date on the npm registry to be considered for installation`, + type: SettingsType.DURATION, + unit: DurationUnit.MINUTES, + isNullable: true, + default: null, + }, +} 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 +117,7 @@ declare module '@yarnpkg/core' { npmPublishRegistry: string | null; npmRegistryServer: string; - npmMinimalAgeGate: number; + npmMinimalAgeGate: number | null; }>>; npmRegistries: Map & { - default: string; + default: string | null; unit: DurationUnit; isNullable?: boolean; }; @@ -810,6 +810,9 @@ function parseSingleValue(configuration: Configuration, path: string, valueBase: if (value === null && !definition.isNullable && definition.default !== null) throw new Error(`Non-nullable configuration settings "${path}" cannot be set to null`); + if (value === null) + return null; + if (`values` in definition && definition.values?.includes(value)) return value; @@ -953,6 +956,9 @@ function getDefaultValue(configuration: Configuration, definition: SettingsDefin } } case SettingsType.DURATION: { + if (definition.default === null) + return null; + return miscUtils.parseDuration(definition.default, definition.unit); } default: {