diff --git a/.yarn/versions/dab519eb.yml b/.yarn/versions/dab519eb.yml new file mode 100644 index 000000000000..74fddec29365 --- /dev/null +++ b/.yarn/versions/dab519eb.yml @@ -0,0 +1,36 @@ +releases: + "@yarnpkg/cli": minor + "@yarnpkg/core": minor + "@yarnpkg/plugin-essentials": minor + "@yarnpkg/plugin-npm": minor + +declined: + - "@yarnpkg/builder" + - "@yarnpkg/doctor" + - "@yarnpkg/extensions" + - "@yarnpkg/nm" + - "@yarnpkg/pnpify" + - "@yarnpkg/sdks" + - "@yarnpkg/plugin-catalog" + - "@yarnpkg/plugin-compat" + - "@yarnpkg/plugin-constraints" + - "@yarnpkg/plugin-dlx" + - "@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" diff --git a/.yarnrc.yml b/.yarnrc.yml index 56fe4b1c1f3d..1b80bc09359e 100644 --- a/.yarnrc.yml +++ b/.yarnrc.yml @@ -1,3 +1,6 @@ +catalog: + typescript: ^5.9.2 + changesetIgnorePatterns: - .github/** - .yarn/cache/** @@ -15,34 +18,32 @@ immutablePatterns: initScope: yarnpkg -pnpZipBackend: js +npmMinimalAgeGate: 0 npmPublishAccess: public npmPublishProvenance: true -catalog: - typescript: "^5.9.2" - packageExtensions: - markdown-it@*: - dependencies: - punycode: "*" - "@docusaurus/preset-classic@3.7.0": peerDependencies: - "@docusaurus/plugin-content-docs": "3.7.0" + "@docusaurus/plugin-content-docs": 3.7.0 docusaurus-plugin-typedoc-api@^4: dependencies: - "@docusaurus/mdx-loader": "^3" - "@docusaurus/theme-common": "^3" + "@docusaurus/mdx-loader": ^3 + "@docusaurus/theme-common": ^3 peerDependencies: + "@docusaurus/plugin-content-docs": "*" react-dom: "*" typedoc: "*" - "@docusaurus/plugin-content-docs": "*" + markdown-it@*: + dependencies: + punycode: "*" pnpEnableEsmLoader: true +pnpZipBackend: js + preferInteractive: true supportedArchitectures: diff --git a/packages/acceptance-tests/pkg-tests-core/sources/utils/tests.ts b/packages/acceptance-tests/pkg-tests-core/sources/utils/tests.ts index 582521f82a63..1093d9fa7c63 100644 --- a/packages/acceptance-tests/pkg-tests-core/sources/utils/tests.ts +++ b/packages/acceptance-tests/pkg-tests-core/sources/utils/tests.ts @@ -179,6 +179,11 @@ export const ADVISORIES = new Map>([ }]], ]); +// Packages without an explicit publish date pretend to have been released long +// ago, so that the `npmMinimalAgeGate` setting (defaulting to `1d`) doesn't +// quarantine them when running tests. +const OLD_RELEASE_DATE = new Date(0).toISOString(); + const RELEASE_DATE_PACKAGES: Record> = { "release-date": { "1.0.0": new Date(new Date().getTime() - /* 10 days */ 1000 * 60 * 60 * 24 * 10).toISOString(), @@ -444,7 +449,9 @@ export const startPackageServer = ({type}: {type: keyof typeof packageServerUrls }), )), ), - time: name in RELEASE_DATE_PACKAGES ? RELEASE_DATE_PACKAGES[name] : undefined, + time: name in RELEASE_DATE_PACKAGES + ? RELEASE_DATE_PACKAGES[name] + : Object.fromEntries(versions.map(version => [version, OLD_RELEASE_DATE])), [`dist-tags`]: { latest: semver.maxSatisfying(versions, `*`), ...distTags, diff --git a/packages/acceptance-tests/pkg-tests-specs/sources/commands/install.test.ts b/packages/acceptance-tests/pkg-tests-specs/sources/commands/install.test.ts index e8a9fbe0454e..fda0b6ff68c1 100644 --- a/packages/acceptance-tests/pkg-tests-specs/sources/commands/install.test.ts +++ b/packages/acceptance-tests/pkg-tests-specs/sources/commands/install.test.ts @@ -77,7 +77,7 @@ describe(`Commands`, () => { expect(match).not.toBeNull(); const currentVersion = Number(match![1]); - const previousVersion = Math.max(0, currentVersion - 1); + const previousVersion = Math.max(0, currentVersion - 2); const downgraded = lockfile.replace( /^(__metadata:\r?\n {2}version: )\d+$/m, @@ -96,6 +96,41 @@ describe(`Commands`, () => { }), ); + test( + `it should migrate old lockfiles by disabling npmMinimalAgeGate when unset`, + makeTemporaryEnv({ + dependencies: { + [`no-deps`]: `1.0.0`, + }, + }, async ({path, run}) => { + const lockfilePath = ppath.join(path, Filename.lockfile); + const rcPath = ppath.join(path, Filename.rc); + + await run(`install`); + + const lockfile = await xfs.readFilePromise(lockfilePath, `utf8`); + const match = lockfile.match(/^__metadata:\r?\n {2}version: (\d+)$/m); + + expect(match).not.toBeNull(); + const currentVersion = Number(match![1]); + const previousVersion = Math.max(0, currentVersion - 1); + + const downgraded = lockfile.replace( + /^(__metadata:\r?\n {2}version: )\d+$/m, + `$1${previousVersion}`, + ); + + expect(downgraded).not.toEqual(lockfile); + await xfs.writeFilePromise(lockfilePath, downgraded); + + await expect(xfs.existsPromise(rcPath)).resolves.toBeFalsy(); + + await run(`install`); + + await expect(xfs.readFilePromise(rcPath, `utf8`)).resolves.toMatch(/npmMinimalAgeGate:\s+['"]?0['"]?/); + }), + ); + test( `it should print the logs to the standard output when using --inline-builds`, makeTemporaryEnv({ 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 f78a387e4d94..e05c65fa35ca 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 @@ -369,5 +369,83 @@ describe(`Features`, () => { }), ); }); + describe(`--no-time-gate`, () => { + test( + `yarn add should ignore the minimum release age when --no-time-gate is set`, + makeTemporaryEnv({}, { + npmMinimalAgeGate: `1d`, + }, async ({run, source}) => { + await run(`add`, `--no-time-gate`, `release-date@^1.0.0`); + + await expect(source(`require('release-date/package.json')`)).resolves.toMatchObject({ + name: `release-date`, + version: `1.1.1`, + }); + }), + ); + + test( + `yarn add should ignore the minimum release age for exact versions when --no-time-gate is set`, + makeTemporaryEnv({}, { + npmMinimalAgeGate: `1d`, + }, async ({run, source}) => { + await run(`add`, `--no-time-gate`, `release-date@1.1.1`); + + await expect(source(`require('release-date/package.json')`)).resolves.toMatchObject({ + name: `release-date`, + version: `1.1.1`, + }); + }), + ); + + test( + `yarn up should ignore the minimum release age when --no-time-gate is set`, + makeTemporaryEnv({ + dependencies: {[`release-date`]: `^1.0.0`}, + }, { + npmMinimalAgeGate: `1d`, + }, async ({run, source}) => { + await run(`install`); + await run(`set`, `resolution`, `release-date@npm:^1.0.0`, `npm:1.0.0`); + + const preUpVersion = (await source(`require('release-date/package.json')`)).version; + if (preUpVersion !== `1.0.0`) + throw new Error(`Pre-up version is not 1.0.0`); + + await run(`up`, `--no-time-gate`, `release-date`); + + await expect(source(`require('release-date/package.json')`)).resolves.toMatchObject({ + name: `release-date`, + version: `1.1.1`, + }); + }), + ); + + test( + `yarn up -R should ignore the minimum release age when --no-time-gate is set`, + makeTemporaryEnv({ + dependencies: {[`release-date`]: `^1.0.0`}, + }, { + npmMinimalAgeGate: `1d`, + pnpFallbackMode: `all`, + pnpMode: `loose`, + }, async ({run, source}) => { + await run(`install`); + await run(`set`, `resolution`, `release-date@npm:^1.0.0`, `npm:1.0.0`); + await run(`set`, `resolution`, `release-date-transitive@npm:^1.0.0`, `npm:1.0.0`); + + await run(`up`, `--no-time-gate`, `-R`, `*`); + + await expect(source(`require('release-date/package.json')`)).resolves.toMatchObject({ + name: `release-date`, + version: `1.1.1`, + }); + await expect(source(`require('release-date-transitive/package.json')`)).resolves.toMatchObject({ + name: `release-date-transitive`, + version: `1.1.1`, + }); + }), + ); + }); }); }); diff --git a/packages/docusaurus/docs/features/security.mdx b/packages/docusaurus/docs/features/security.mdx index 6ba786450d2b..3b62f3981416 100644 --- a/packages/docusaurus/docs/features/security.mdx +++ b/packages/docusaurus/docs/features/security.mdx @@ -23,7 +23,7 @@ Yarn doesn't run postinstalls by default ever since 4.14. You must either enable ## Age gate -Yarn 4.12 introduced `npmMinimalAgeGate` to restrict packages installed on your machine to only packages that got published at least N days prior. The `npmPreapprovedPackages` setting also lets you bypass this check for specific packages. +Yarn 4.12 introduced `npmMinimalAgeGate` to restrict packages installed on your machine to only packages that got published at least N days prior. The setting defaults to `1d`, so a brand new release won't be installed until it has had a chance to be reviewed by the community. The `npmPreapprovedPackages` setting also lets you bypass this check for specific packages, and the `--no-time-gate` flag on `yarn add` and `yarn up` lets you bypass it for a single command. ## Hardened mode diff --git a/packages/docusaurus/static/configuration/yarnrc.json b/packages/docusaurus/static/configuration/yarnrc.json index 65ea5b77898e..f67d1535b9c6 100644 --- a/packages/docusaurus/static/configuration/yarnrc.json +++ b/packages/docusaurus/static/configuration/yarnrc.json @@ -503,7 +503,7 @@ { "type": "number" }, { "type": "string", "pattern": "^(\\d*\\.?\\d+)(ms|s|m|h|d|w)?$" } ], - "default": "3d" + "default": "1d" }, "npmPreapprovedPackages": { "_package": "@yarnpkg/core", diff --git a/packages/plugin-essentials/sources/commands/add.ts b/packages/plugin-essentials/sources/commands/add.ts index b50def73e0ad..39c3188fa737 100644 --- a/packages/plugin-essentials/sources/commands/add.ts +++ b/packages/plugin-essentials/sources/commands/add.ts @@ -113,6 +113,10 @@ export default class AddCommand extends BaseCommand { description: `Reuse the highest version already used somewhere within the project`, }); + noTimeGate = Option.Boolean(`--no-time-gate`, false, { + description: `Disable the minimum release age check for this command`, + }); + mode = Option.String(`--mode`, { description: `Change what artifacts installs generate`, validator: t.isEnum(InstallMode), @@ -124,6 +128,10 @@ export default class AddCommand extends BaseCommand { async execute() { const configuration = await Configuration.find(this.context.cwd, this.context.plugins); + + if (this.noTimeGate) + configuration.useWithSource(``, {npmMinimalAgeGate: `0`}, configuration.startingCwd, {overwrite: true}); + const {project, workspace} = await Project.find(configuration, this.context.cwd); const cache = await Cache.find(configuration); diff --git a/packages/plugin-essentials/sources/commands/install.ts b/packages/plugin-essentials/sources/commands/install.ts index 0a0bca3ac818..7382ce038121 100644 --- a/packages/plugin-essentials/sources/commands/install.ts +++ b/packages/plugin-essentials/sources/commands/install.ts @@ -31,6 +31,10 @@ const LOCKFILE_MIGRATION_RULES: Array<{ selector: v => v < 9, name: `enableScripts`, value: true, +}, { + selector: v => v < 10, + name: `npmMinimalAgeGate` as keyof ConfigurationValueMap, + value: `0`, }]; // eslint-disable-next-line arca/no-default-export diff --git a/packages/plugin-essentials/sources/commands/up.ts b/packages/plugin-essentials/sources/commands/up.ts index adc95c12406f..ae66bd91453c 100644 --- a/packages/plugin-essentials/sources/commands/up.ts +++ b/packages/plugin-essentials/sources/commands/up.ts @@ -84,6 +84,10 @@ export default class UpCommand extends BaseCommand { description: `Resolve again ALL resolutions for those packages`, }); + noTimeGate = Option.Boolean(`--no-time-gate`, false, { + description: `Disable the minimum release age check for this command`, + }); + mode = Option.String(`--mode`, { description: `Change what artifacts installs generate`, validator: t.isEnum(InstallMode), @@ -105,6 +109,10 @@ export default class UpCommand extends BaseCommand { async executeUpRecursive() { const configuration = await Configuration.find(this.context.cwd, this.context.plugins); + + if (this.noTimeGate) + configuration.useWithSource(``, {npmMinimalAgeGate: `0`}, configuration.startingCwd, {overwrite: true}); + const {project, workspace} = await Project.find(configuration, this.context.cwd); const cache = await Cache.find(configuration); @@ -151,6 +159,10 @@ export default class UpCommand extends BaseCommand { async executeUpClassic() { const configuration = await Configuration.find(this.context.cwd, this.context.plugins); + + if (this.noTimeGate) + configuration.useWithSource(``, {npmMinimalAgeGate: `0`}, configuration.startingCwd, {overwrite: true}); + const {project, workspace} = await Project.find(configuration, this.context.cwd); const cache = await Cache.find(configuration); diff --git a/packages/plugin-npm/sources/index.ts b/packages/plugin-npm/sources/index.ts index a783cdd74fec..0ce095946364 100644 --- a/packages/plugin-npm/sources/index.ts +++ b/packages/plugin-npm/sources/index.ts @@ -73,7 +73,7 @@ const packageGateSettings = { 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, - default: `0m`, + default: `1d`, }, npmPreapprovedPackages: { description: `Array of package descriptors or package name glob patterns to exclude from the minimum release age check`, diff --git a/packages/yarnpkg-core/sources/Project.ts b/packages/yarnpkg-core/sources/Project.ts index fb1eb11d4ec5..8e42251dc68c 100644 --- a/packages/yarnpkg-core/sources/Project.ts +++ b/packages/yarnpkg-core/sources/Project.ts @@ -46,7 +46,7 @@ import {IdentHash, DescriptorHash, LocatorHash, PackageExtensionStatus} from './ // the Package type; no more no less. export const LOCKFILE_VERSION = miscUtils.parseInt( process.env.YARN_LOCKFILE_VERSION_OVERRIDE ?? - 9, + 10, ); // Same thing but must be bumped when the members of the Project class changes (we diff --git a/yarn.lock b/yarn.lock index c4dccc41d967..8bb86f1d185c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,7 +2,7 @@ # Manual changes might be lost - proceed with caution! __metadata: - version: 9 + version: 10 cacheKey: 10 "@aashutoshrathi/word-wrap@npm:^1.2.3":