Compare commits

...

39 Commits

Author SHA1 Message Date
Jonathan Netley 15498ddb9a
docs(corepack-version): reference node 24 as latest default (#723)
Co-authored-by: Steven <steven@ceriously.com>
Co-authored-by: Antoine du Hamel <duhamelantoine1995@gmail.com>
2025-06-27 08:00:56 +00:00
Antoine du Hamel 9a1794a59f
chore: fix permissions in the `publish` workflow (#721) 2025-06-14 22:26:33 +02:00
Mike McCready 783a42fbe3
chore!: remove Node.js 18.x and 23.x usage, add 24.x (#718)
BREAKING CHANGE: drop Node.js 18.x and 23.x support

In CI add Node.js 24.x tests with Windows exclusion
2025-06-06 20:21:48 +02:00
github-actions[bot] 98fd966176
chore(main): release 0.33.0 (#699)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-06-02 13:06:02 +00:00
Maël Nison 5fc3691354
feat: Adds guard to avoid stepping on Yarn's feet (#714)
* feat: Adds guard to avoid stepping on Yarn's feet

* Makes the path more specific

* Apply feedback

* Fixes test
2025-06-02 09:40:35 +02:00
github-actions[bot] b45b3a3244
feat: update package manager versions (#671)
* feat: update package manager versions

* feat: update package manager versions

* feat: update package manager versions

* feat: update package manager versions

* feat: update package manager versions

* feat: update package manager versions

* feat: update package manager versions

* feat: update package manager versions

* feat: update package manager versions

* feat: update package manager versions

* feat: update package manager versions

* feat: update package manager versions

* feat: update package manager versions

* Trigger actions?

* Fixes mocks

* nock again

---------

Co-authored-by: Create or Update Pull Request Action <create-or-update-pull-request@users.noreply.github.com>
Co-authored-by: Maël Nison <nison.mael@gmail.com>
2025-05-27 09:37:08 +02:00
Maël Nison 77fff3c1f3
Disables auto-pinning by default (#709)
* Disables auto-pinning by default

* Updates tests

* Updates the README
2025-05-16 19:14:38 +02:00
dependabot[bot] 273237a110
build(deps): bump vite from 6.2.6 to 6.3.4 (#705)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 6.2.6 to 6.3.4.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v6.3.4/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 6.3.4
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-01 00:37:14 +02:00
dependabot[bot] 2b43f26135
build(deps): bump vite from 6.2.3 to 6.2.6 (#702)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 6.2.3 to 6.2.6.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v6.2.6/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v6.2.6/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 6.2.6
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-30 00:19:14 +02:00
Antoine du Hamel aefde28a63
chore(publish): add `--provenance` (#701) 2025-04-09 17:48:07 +02:00
Mike McCready 0b94797f96
fix: debug text typo (#698) 2025-04-08 11:46:08 +02:00
dependabot[bot] 679bcefda5
build(deps): bump vite from 6.2.0 to 6.2.3 (#689)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 6.2.0 to 6.2.3.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v6.2.3/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v6.2.3/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-28 14:55:25 +01:00
Mike McCready 633764f7c4
chore(deps): update eslint to 9.22.0 (#676)
update @yarnpkg/eslint-config to 3.0.0

fix fixable eslint formatting errors

add @typescript-eslint/no-unused-vars caughtErrors:none

change VScode setting to eslint.useFlatConfig true
2025-03-26 20:26:06 +01:00
github-actions[bot] 19e3c6861a
chore(main): release 0.32.0 (#641) 2025-02-28 20:00:24 +00:00
github-actions[bot] b83bb5ec15
feat: update package manager versions (#617) 2025-02-28 19:46:59 +00:00
Antoine du Hamel 7deb39ead0
chore(deps): remove duplicate esbuild version, bump other deps (#667) 2025-02-28 19:34:05 +00:00
Antoine du Hamel 9b95b46f05
feat: add support for `.corepack.env` (#642) 2025-02-28 19:25:03 +00:00
Antoine du Hamel b456268851
feat: add limited support for `devEngines` (#643) 2025-02-28 20:06:33 +01:00
Antoine du Hamel 53b1fe75c4
feat: add more informative error when fetching latest stable fails (#644) 2025-02-28 20:05:59 +01:00
Antoine du Hamel 4be72f6941
fix(use): do not throw on invalid `packageManager` (#663) 2025-02-28 19:20:09 +01:00
Antoine du Hamel bb16184b7b
chore(tests): remove temporary directories in test teardown (#666) 2025-02-28 11:21:15 +00:00
Thomas Scholtes b0608d1a60
chore(tests): remove temporary directories in test teardown (#662) 2025-02-28 12:09:16 +01:00
Mike McCready 351d86c202
fix: replace explicit with specify as verb (#665) 2025-02-26 12:25:46 +01:00
Mike McCready 48ce89827c
chore(ci): enable node 23 ci tests for Windows (#660) 2025-02-15 09:25:51 +01:00
Thomas Scholtes da96687e69
chore(ci): reject linter warnings (#655) 2025-02-14 22:34:18 +00:00
Antoine du Hamel f8de7214e5
chore: fix lint warning (#657) 2025-02-14 23:20:20 +01:00
dependabot[bot] 7f7336b9bd
build(deps-dev): bump esbuild from 0.24.2 to 0.25.0 (#653)
Bumps [esbuild](https://github.com/evanw/esbuild) from 0.24.2 to 0.25.0.
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG-2024.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.24.2...v0.25.0)

---
updated-dependencies:
- dependency-name: esbuild
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-12 12:59:59 +01:00
Antoine du Hamel d26a552daa
chore(tests): split `invalid_signature` test in two (#651)
Ranges and tags are not the same thing, it can be useful to have two
different tests in case only one of them fail.
2025-02-12 10:14:34 +00:00
Thomas Scholtes 7b193d8498
chore(tests): fix string matching expectations (#648)
Vitest does not support matching a regex with a string, it would fail to report unmatched output.
2025-02-12 10:01:48 +00:00
Antoine du Hamel 91ea527475
chore(test): fix some custom registry tests (#649)
Co-authored-by: Thomas Scholtes <geigerzaehler@axiom.fm>
2025-02-11 18:56:39 +01:00
Thomas Scholtes c388c64805
chore(tests): don’t keep HTTP connections alive (#645)
This change almost halves the time it takes to run `tests/main.test.ts`.
2025-02-10 23:37:21 +01:00
Antoine du Hamel 12e77e5069
fix: do not resolve fallback descriptor when `packageManager` is defined (#632) 2025-02-08 18:55:54 +01:00
Mike McCready b0c46078f1
docs: add instructions to update corepack (#637) 2025-02-07 19:08:34 +01:00
Antoine du Hamel bae0839794
fix(doc): fix link to proxy library (#636) 2025-02-07 16:18:04 +01:00
Antoine du Hamel d5e938795b
chore(deps): bump all dependencies (#638) 2025-02-07 15:40:14 +01:00
Antoine du Hamel 9317593dda
chore(tests): await promise in `config.test.js` (#639) 2025-02-07 14:35:44 +00:00
Maël Nison 8163608e49
chore(build): bump TypeScript to 5.7 (#630)
* Updates TypeScript

* Updates the SDK
2025-02-03 17:07:57 +01:00
Mike McCready 721eeec8c3
chore: update yarn to 4.6.0 (#619) 2025-02-03 14:57:01 +01:00
Mike McCready a7cb9f603c
docs: add contributor windows os non-homedrive usage (#606) 2025-01-29 21:03:23 +01:00
39 changed files with 3141 additions and 1822 deletions

View File

@ -39,27 +39,26 @@ jobs:
run: corepack yarn typecheck
- name: "Check for linting errors"
run: corepack yarn lint
run: corepack yarn lint --max-warnings=0
build-and-test:
strategy:
fail-fast: false
matrix:
node:
- 18
- 20
- 22
- 23
- 24
platform:
- ubuntu-latest
- macos-latest
- windows-latest
# Temporarily skipping Node.js 23 under Windows due to issue
# https://github.com/nodejs/corepack/issues/597
# ci vitest fails "handle integrity checks" on Windows Node.js 23
# Temporarily skipping Node.js 24 under Windows due to issue
# https://github.com/nodejs/corepack/issues/715
# vitest fails "handle integrity checks" on Windows with Node.js 24.x
exclude:
- node: 23
- node: 24
platform: windows-latest
name: "${{matrix.platform}} w/ Node.js ${{matrix.node}}.x"

View File

@ -8,12 +8,14 @@ env:
YARN_ENABLE_GLOBAL_CACHE: false
permissions:
contents: write
pull-requests: write
contents: read
jobs:
release-please:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
outputs:
release_created: ${{ steps.release.outputs.release_created }}
release_tag: ${{ steps.release.outputs.tag_name }}
@ -29,6 +31,9 @@ jobs:
needs: release-please
if: ${{ needs.release-please.outputs.release_created }}
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write
steps:
- uses: actions/checkout@v4
@ -48,10 +53,10 @@ jobs:
restore-keys: |
${{runner.os}}-yarn-
- run: corepack yarn install --immutable
- name: Publish to the npm registry
run: |
corepack yarn install --immutable
corepack yarn npm publish
run: corepack yarn npm publish --provenance
env:
YARN_NPM_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}

View File

@ -4,7 +4,7 @@
"**/.yarn": true,
"**/.pnp.*": true
},
"eslint.experimental.useFlatConfig": true,
"eslint.useFlatConfig": true,
"eslint.nodePath": ".yarn/sdks",
"typescript.enablePromptUseWorkspaceTsdk": true,
"editor.codeActionsOnSave": {

View File

@ -1,20 +1,32 @@
#!/usr/bin/env node
const {existsSync} = require(`fs`);
const {createRequire} = require(`module`);
const {createRequire, register} = require(`module`);
const {resolve} = require(`path`);
const {pathToFileURL} = require(`url`);
const relPnpApiPath = "../../../../.pnp.cjs";
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`);
const absRequire = createRequire(absPnpApiPath);
const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`);
const isPnpLoaderEnabled = existsSync(absPnpLoaderPath);
if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require eslint/bin/eslint.js
require(absPnpApiPath).setup();
if (isPnpLoaderEnabled && register) {
register(pathToFileURL(absPnpLoaderPath));
}
}
}
const wrapWithUserWrapper = existsSync(absUserWrapperPath)
? exports => absRequire(absUserWrapperPath)(exports)
: exports => exports;
// Defer to the real eslint/bin/eslint.js your application uses
module.exports = absRequire(`eslint/bin/eslint.js`);
module.exports = wrapWithUserWrapper(absRequire(`eslint/bin/eslint.js`));

View File

@ -1,20 +1,32 @@
#!/usr/bin/env node
const {existsSync} = require(`fs`);
const {createRequire} = require(`module`);
const {createRequire, register} = require(`module`);
const {resolve} = require(`path`);
const {pathToFileURL} = require(`url`);
const relPnpApiPath = "../../../../.pnp.cjs";
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`);
const absRequire = createRequire(absPnpApiPath);
const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`);
const isPnpLoaderEnabled = existsSync(absPnpLoaderPath);
if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require eslint
require(absPnpApiPath).setup();
if (isPnpLoaderEnabled && register) {
register(pathToFileURL(absPnpLoaderPath));
}
}
}
const wrapWithUserWrapper = existsSync(absUserWrapperPath)
? exports => absRequire(absUserWrapperPath)(exports)
: exports => exports;
// Defer to the real eslint your application uses
module.exports = absRequire(`eslint`);
module.exports = wrapWithUserWrapper(absRequire(`eslint`));

View File

@ -1,20 +1,32 @@
#!/usr/bin/env node
const {existsSync} = require(`fs`);
const {createRequire} = require(`module`);
const {createRequire, register} = require(`module`);
const {resolve} = require(`path`);
const {pathToFileURL} = require(`url`);
const relPnpApiPath = "../../../../.pnp.cjs";
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`);
const absRequire = createRequire(absPnpApiPath);
const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`);
const isPnpLoaderEnabled = existsSync(absPnpLoaderPath);
if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require eslint/use-at-your-own-risk
require(absPnpApiPath).setup();
if (isPnpLoaderEnabled && register) {
register(pathToFileURL(absPnpLoaderPath));
}
}
}
const wrapWithUserWrapper = existsSync(absUserWrapperPath)
? exports => absRequire(absUserWrapperPath)(exports)
: exports => exports;
// Defer to the real eslint/use-at-your-own-risk your application uses
module.exports = absRequire(`eslint/use-at-your-own-risk`);
module.exports = wrapWithUserWrapper(absRequire(`eslint/use-at-your-own-risk`));

View File

@ -1,6 +1,6 @@
{
"name": "eslint",
"version": "8.56.0-sdk",
"version": "8.57.0-sdk",
"main": "./lib/api.js",
"type": "commonjs",
"bin": {

View File

@ -1,20 +1,32 @@
#!/usr/bin/env node
const {existsSync} = require(`fs`);
const {createRequire} = require(`module`);
const {createRequire, register} = require(`module`);
const {resolve} = require(`path`);
const {pathToFileURL} = require(`url`);
const relPnpApiPath = "../../../../.pnp.cjs";
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`);
const absRequire = createRequire(absPnpApiPath);
const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`);
const isPnpLoaderEnabled = existsSync(absPnpLoaderPath);
if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require typescript/bin/tsc
require(absPnpApiPath).setup();
if (isPnpLoaderEnabled && register) {
register(pathToFileURL(absPnpLoaderPath));
}
}
}
const wrapWithUserWrapper = existsSync(absUserWrapperPath)
? exports => absRequire(absUserWrapperPath)(exports)
: exports => exports;
// Defer to the real typescript/bin/tsc your application uses
module.exports = absRequire(`typescript/bin/tsc`);
module.exports = wrapWithUserWrapper(absRequire(`typescript/bin/tsc`));

View File

@ -1,20 +1,32 @@
#!/usr/bin/env node
const {existsSync} = require(`fs`);
const {createRequire} = require(`module`);
const {createRequire, register} = require(`module`);
const {resolve} = require(`path`);
const {pathToFileURL} = require(`url`);
const relPnpApiPath = "../../../../.pnp.cjs";
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`);
const absRequire = createRequire(absPnpApiPath);
const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`);
const isPnpLoaderEnabled = existsSync(absPnpLoaderPath);
if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require typescript/bin/tsserver
require(absPnpApiPath).setup();
if (isPnpLoaderEnabled && register) {
register(pathToFileURL(absPnpLoaderPath));
}
}
}
const wrapWithUserWrapper = existsSync(absUserWrapperPath)
? exports => absRequire(absUserWrapperPath)(exports)
: exports => exports;
// Defer to the real typescript/bin/tsserver your application uses
module.exports = absRequire(`typescript/bin/tsserver`);
module.exports = wrapWithUserWrapper(absRequire(`typescript/bin/tsserver`));

View File

@ -1,20 +1,32 @@
#!/usr/bin/env node
const {existsSync} = require(`fs`);
const {createRequire} = require(`module`);
const {createRequire, register} = require(`module`);
const {resolve} = require(`path`);
const {pathToFileURL} = require(`url`);
const relPnpApiPath = "../../../../.pnp.cjs";
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`);
const absRequire = createRequire(absPnpApiPath);
const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`);
const isPnpLoaderEnabled = existsSync(absPnpLoaderPath);
if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require typescript/lib/tsc.js
require(absPnpApiPath).setup();
if (isPnpLoaderEnabled && register) {
register(pathToFileURL(absPnpLoaderPath));
}
}
}
const wrapWithUserWrapper = existsSync(absUserWrapperPath)
? exports => absRequire(absUserWrapperPath)(exports)
: exports => exports;
// Defer to the real typescript/lib/tsc.js your application uses
module.exports = absRequire(`typescript/lib/tsc.js`);
module.exports = wrapWithUserWrapper(absRequire(`typescript/lib/tsc.js`));

View File

@ -1,15 +1,38 @@
#!/usr/bin/env node
const {existsSync} = require(`fs`);
const {createRequire} = require(`module`);
const {createRequire, register} = require(`module`);
const {resolve} = require(`path`);
const {pathToFileURL} = require(`url`);
const relPnpApiPath = "../../../../.pnp.cjs";
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`);
const absRequire = createRequire(absPnpApiPath);
const moduleWrapper = tsserver => {
const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`);
const isPnpLoaderEnabled = existsSync(absPnpLoaderPath);
if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require typescript/lib/tsserver.js
require(absPnpApiPath).setup();
if (isPnpLoaderEnabled && register) {
register(pathToFileURL(absPnpLoaderPath));
}
}
}
const wrapWithUserWrapper = existsSync(absUserWrapperPath)
? exports => absRequire(absUserWrapperPath)(exports)
: exports => exports;
const moduleWrapper = exports => {
return wrapWithUserWrapper(moduleWrapperFn(exports));
};
const moduleWrapperFn = tsserver => {
if (!process.versions.pnp) {
return tsserver;
}
@ -214,11 +237,11 @@ const moduleWrapper = tsserver => {
return tsserver;
};
if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require typescript/lib/tsserver.js
require(absPnpApiPath).setup();
}
const [major, minor] = absRequire(`typescript/package.json`).version.split(`.`, 2).map(value => parseInt(value, 10));
// In TypeScript@>=5.5 the tsserver uses the public TypeScript API so that needs to be patched as well.
// Ref https://github.com/microsoft/TypeScript/pull/55326
if (major > 5 || (major === 5 && minor >= 5)) {
moduleWrapper(absRequire(`typescript`));
}
// Defer to the real typescript/lib/tsserver.js your application uses

View File

@ -1,15 +1,38 @@
#!/usr/bin/env node
const {existsSync} = require(`fs`);
const {createRequire} = require(`module`);
const {createRequire, register} = require(`module`);
const {resolve} = require(`path`);
const {pathToFileURL} = require(`url`);
const relPnpApiPath = "../../../../.pnp.cjs";
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`);
const absRequire = createRequire(absPnpApiPath);
const moduleWrapper = tsserver => {
const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`);
const isPnpLoaderEnabled = existsSync(absPnpLoaderPath);
if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require typescript/lib/tsserverlibrary.js
require(absPnpApiPath).setup();
if (isPnpLoaderEnabled && register) {
register(pathToFileURL(absPnpLoaderPath));
}
}
}
const wrapWithUserWrapper = existsSync(absUserWrapperPath)
? exports => absRequire(absUserWrapperPath)(exports)
: exports => exports;
const moduleWrapper = exports => {
return wrapWithUserWrapper(moduleWrapperFn(exports));
};
const moduleWrapperFn = tsserver => {
if (!process.versions.pnp) {
return tsserver;
}
@ -214,11 +237,11 @@ const moduleWrapper = tsserver => {
return tsserver;
};
if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require typescript/lib/tsserverlibrary.js
require(absPnpApiPath).setup();
}
const [major, minor] = absRequire(`typescript/package.json`).version.split(`.`, 2).map(value => parseInt(value, 10));
// In TypeScript@>=5.5 the tsserver uses the public TypeScript API so that needs to be patched as well.
// Ref https://github.com/microsoft/TypeScript/pull/55326
if (major > 5 || (major === 5 && minor >= 5)) {
moduleWrapper(absRequire(`typescript`));
}
// Defer to the real typescript/lib/tsserverlibrary.js your application uses

View File

@ -1,20 +1,32 @@
#!/usr/bin/env node
const {existsSync} = require(`fs`);
const {createRequire} = require(`module`);
const {createRequire, register} = require(`module`);
const {resolve} = require(`path`);
const {pathToFileURL} = require(`url`);
const relPnpApiPath = "../../../../.pnp.cjs";
const absPnpApiPath = resolve(__dirname, relPnpApiPath);
const absUserWrapperPath = resolve(__dirname, `./sdk.user.cjs`);
const absRequire = createRequire(absPnpApiPath);
const absPnpLoaderPath = resolve(absPnpApiPath, `../.pnp.loader.mjs`);
const isPnpLoaderEnabled = existsSync(absPnpLoaderPath);
if (existsSync(absPnpApiPath)) {
if (!process.versions.pnp) {
// Setup the environment to be able to require typescript
require(absPnpApiPath).setup();
if (isPnpLoaderEnabled && register) {
register(pathToFileURL(absPnpLoaderPath));
}
}
}
const wrapWithUserWrapper = existsSync(absUserWrapperPath)
? exports => absRequire(absUserWrapperPath)(exports)
: exports => exports;
// Defer to the real typescript your application uses
module.exports = absRequire(`typescript`);
module.exports = wrapWithUserWrapper(absRequire(`typescript`));

View File

@ -1,6 +1,6 @@
{
"name": "typescript",
"version": "5.3.3-sdk",
"version": "5.7.3-sdk",
"main": "./lib/typescript.js",
"type": "commonjs",
"bin": {

View File

@ -1,5 +1,36 @@
# Changelog
## [0.33.0](https://github.com/nodejs/corepack/compare/v0.32.0...v0.33.0) (2025-06-02)
### Features
* Adds guard to avoid stepping on Yarn's feet ([#714](https://github.com/nodejs/corepack/issues/714)) ([5fc3691](https://github.com/nodejs/corepack/commit/5fc3691354eb5bdeca17a9495b234584353f0151))
* update package manager versions ([#671](https://github.com/nodejs/corepack/issues/671)) ([b45b3a3](https://github.com/nodejs/corepack/commit/b45b3a3244bacfbaf65188ae8c04209a1e98307d))
### Bug Fixes
* debug text typo ([#698](https://github.com/nodejs/corepack/issues/698)) ([0b94797](https://github.com/nodejs/corepack/commit/0b94797f96e30e46e466873fe7d437d0471cd92c))
## [0.32.0](https://github.com/nodejs/corepack/compare/v0.31.0...v0.32.0) (2025-02-28)
### Features
* add limited support for `devEngines` ([#643](https://github.com/nodejs/corepack/issues/643)) ([b456268](https://github.com/nodejs/corepack/commit/b4562688513f23e37e37b0d69a0daff33ca84c8d))
* add more informative error when fetching latest stable fails ([#644](https://github.com/nodejs/corepack/issues/644)) ([53b1fe7](https://github.com/nodejs/corepack/commit/53b1fe75c47c06bd72a8b8f8bb699a47c9ca32fb))
* add support for `.corepack.env` ([#642](https://github.com/nodejs/corepack/issues/642)) ([9b95b46](https://github.com/nodejs/corepack/commit/9b95b46f05e50fe1c60f05309c210ba8fe4e23c5))
* update package manager versions ([#617](https://github.com/nodejs/corepack/issues/617)) ([b83bb5e](https://github.com/nodejs/corepack/commit/b83bb5ec150980c998b9c7053dff307d912cb508))
### Bug Fixes
* do not resolve fallback descriptor when `packageManager` is defined ([#632](https://github.com/nodejs/corepack/issues/632)) ([12e77e5](https://github.com/nodejs/corepack/commit/12e77e506946d42a0de9ce8e68d75af8454d6776))
* **doc:** fix link to proxy library ([#636](https://github.com/nodejs/corepack/issues/636)) ([bae0839](https://github.com/nodejs/corepack/commit/bae08397943d4b99437389b4286546361091f4b3))
* replace explicit with specify as verb ([#665](https://github.com/nodejs/corepack/issues/665)) ([351d86c](https://github.com/nodejs/corepack/commit/351d86c20226a8c18bfe212be27401f2908b1595))
* **use:** do not throw on invalid `packageManager` ([#663](https://github.com/nodejs/corepack/issues/663)) ([4be72f6](https://github.com/nodejs/corepack/commit/4be72f6941afa0c9b2b7d26635016bb7b560df8a))
## [0.31.0](https://github.com/nodejs/corepack/compare/v0.30.0...v0.31.0) (2025-01-27)

View File

@ -2,7 +2,7 @@
If you want to build Corepack yourself, you can build the project like this:
1. Clone this repository.
1. Clone this repository. Some restrictions on the cloning location apply to Windows users, read [Contributing on Windows](#contributing-on-windows) for more information.
2. Run `yarn install` (or `corepack yarn install` if the global version of
`yarn` is not provided by Corepack).
3. Run `yarn build` (or `corepack yarn build`).
@ -11,6 +11,12 @@ The `dist/` directory now contains the corepack build and the shims.
Call `node ./dist/corepack --help` and behold.
You can also run the tests with `yarn test`.
## Contributing on Windows
If you are cloning the repo to a directory on a Microsoft Windows operating system, it is recommended to use the same drive as your Windows `HOMEDRIVE` to avoid build and other issues related to a current restriction with Yarn Plug'n'Play caching.
If you are unable to use your `HOMEDRIVE`, you may be able to work around the issue by setting the environment variable `YARN_ENABLE_GLOBAL_CACHE` to `false` before running `yarn install` (or `corepack yarn install`).
# Adding a new package manager
New package managers can be added by editing the following files:

View File

@ -11,7 +11,7 @@ and pnpm without having to install them**.
### Default Installs
Corepack is [distributed by default with all recent Node.js versions](https://nodejs.org/api/corepack.html).
Corepack is distributed with Node.js from version 14.19.0 up to (but not including) 25.0.0.
Run `corepack enable` to install the required Yarn and pnpm binaries on your path.
### Manual Installs
@ -41,6 +41,25 @@ is distributed along with Node.js itself.
</details>
<details><summary>Update Corepack using npm</summary>
To install the latest version of Corepack, use:
```shell
npm install -g corepack@latest
```
If Corepack was installed on your system using a Node.js Windows Installer
`.msi` package then you might need to remove it before attempting to install a
different version of Corepack using npm. You can select the Modify option of the
Node.js app settings to access the Windows Installer feature selection, and on
the "corepack manager" feature of the Node.js `.msi` package by selecting
"Entire feature will be unavailable". See
[Repair apps and programs in Windows](https://support.microsoft.com/en-us/windows/repair-apps-and-programs-in-windows-e90eefe4-d0a2-7c1b-dd59-949a9030f317)
for instructions on accessing the Windows apps page to modify settings.
</details>
<details><summary>Install Corepack from source</summary>
See [`CONTRIBUTING.md`](./CONTRIBUTING.md).
@ -94,6 +113,35 @@ use in the archive).
}
```
#### `devEngines.packageManager`
When a `devEngines.packageManager` field is defined, and is an object containing
a `"name"` field (can also optionally contain `version` and `onFail` fields),
Corepack will use it to validate you're using a compatible package manager.
Depending on the value of `devEngines.packageManager.onFail`:
- if set to `ignore`, Corepack won't print any warning or error.
- if unset or set to `error`, Corepack will throw an error in case of a mismatch.
- if set to `warn` or some other value, Corepack will print a warning in case
of mismatch.
If the top-level `packageManager` field is missing, Corepack will use the
package manager defined in `devEngines.packageManager` in which case you must
provide a specific version in `devEngines.packageManager.version`, ideally with
a hash, as explained in the previous section:
```json
{
"devEngines":{
"packageManager": {
"name": "yarn",
"version": "3.2.3+sha224.953c8233f7a92884eee2de69a1b92d1f2ec1655e66d08071ba9a02fa"
}
}
}
```
## Known Good Releases
When running Corepack within projects that don't list a supported package
@ -227,6 +275,7 @@ it.
Unlike `corepack use` this command doesn't take a package manager name nor a
version range, as it will always select the latest available version from the
range specified in `devEngines.packageManager.version`, or fallback to the
same major line. Should you need to upgrade to a new major, use an explicit
`corepack use {name}@latest` call (or simply `corepack use {name}`).
@ -237,8 +286,8 @@ same major line. Should you need to upgrade to a new major, use an explicit
package manager, and to not update the Last Known Good version when it
downloads a new version of the same major line.
- `COREPACK_ENABLE_AUTO_PIN` can be set to `0` to prevent Corepack from
updating the `packageManager` field when it detects that the local package
- `COREPACK_ENABLE_AUTO_PIN` can be set to `1` to instruct Corepack to
update the `packageManager` field when it detects that the local package
doesn't list it. In general we recommend to always list a `packageManager`
field (which you can easily set through `corepack use [name]@[version]`), as
it ensures that your project installs are always deterministic.
@ -248,6 +297,7 @@ same major line. Should you need to upgrade to a new major, use an explicit
set to `1` to have the URL shown. By default, when Corepack is called
explicitly (e.g. `corepack pnpm …`), it is set to `0`; when Corepack is called
implicitly (e.g. `pnpm …`), it is set to `1`.
The default value cannot be overridden in a `.corepack.env` file.
When standard input is a TTY and no CI environment is detected, Corepack will
ask for user input before starting the download.
@ -273,6 +323,14 @@ same major line. Should you need to upgrade to a new major, use an explicit
project. This means that it will always use the system-wide package manager
regardless of what is being specified in the project's `packageManager` field.
- `COREPACK_ENV_FILE` can be set to `0` to request Corepack to not attempt to
load `.corepack.env`; it can be set to a path to specify a different env file.
Only keys that start with `COREPACK_` and are not in the exception list
(`COREPACK_ENABLE_DOWNLOAD_PROMPT` and `COREPACK_ENV_FILE` are ignored)
will be taken into account.
For Node.js 18.x users, this setting has no effect as that version doesn't
support parsing of `.env` files.
- `COREPACK_HOME` can be set in order to define where Corepack should install
the package managers. By default it is set to `%LOCALAPPDATA%\node\corepack`
on Windows, and to `$HOME/.cache/node/corepack` everywhere else.
@ -294,7 +352,7 @@ same major line. Should you need to upgrade to a new major, use an explicit
empty password, explicitly set `COREPACK_NPM_PASSWORD` to an empty string.
- `HTTP_PROXY`, `HTTPS_PROXY`, and `NO_PROXY` are supported through
[`node-proxy-agent`](https://github.com/TooTallNate/node-proxy-agent).
[`proxy-from-env`](https://github.com/Rob--W/proxy-from-env).
- `COREPACK_INTEGRITY_KEYS` can be set to an empty string or `0` to
instruct Corepack to skip integrity checks, or to a JSON string containing

View File

@ -1,7 +1,7 @@
{
"definitions": {
"npm": {
"default": "11.0.0+sha1.7bba7c80740ef1f5b2c5d4cecc55e94912faa5e6",
"default": "11.4.1+sha1.80350af543069991de20657ebcd07d9624cfad06",
"fetchLatestFrom": {
"type": "npm",
"package": "npm"
@ -38,7 +38,7 @@
}
},
"pnpm": {
"default": "9.15.4+sha1.ffa0b5c573381e8035b354028ccff97c8e452047",
"default": "10.11.0+sha1.4048eeefd564ff1ab248fac3e2854d38245fe2f1",
"fetchLatestFrom": {
"type": "npm",
"package": "pnpm"
@ -102,7 +102,7 @@
"package": "yarn"
},
"transparent": {
"default": "4.6.0+sha224.acd0786f07ffc6c933940eb65fc1d627131ddf5455bddcc295dc90fd",
"default": "4.9.1+sha224.4285002185abb91fe2b781f27fd1e078086c37a7b095f6ea4ee25971",
"commands": [
[
"yarn",

View File

@ -13,11 +13,13 @@ export default [
...yarnpkg,
{
rules: {
// eslint-disable-next-line @typescript-eslint/naming-convention
'no-restricted-globals': [`error`, {
name: `fetch`,
message: `Use fetch from sources/httpUtils.ts`,
}],
'@typescript-eslint/no-unused-vars': [`error`, {
caughtErrors: `none`,
}],
},
},
];

View File

@ -1,6 +1,6 @@
{
"name": "corepack",
"version": "0.31.0",
"version": "0.33.0",
"homepage": "https://github.com/nodejs/corepack#readme",
"bugs": {
"url": "https://github.com/nodejs/corepack/issues"
@ -10,37 +10,37 @@
"url": "https://github.com/nodejs/corepack.git"
},
"engines": {
"node": "^18.17.1 || ^20.10.0 || >=22.11.0"
"node": "^20.10.0 || ^22.11.0 || >=24.0.0"
},
"exports": {
"./package.json": "./package.json"
},
"license": "MIT",
"packageManager": "yarn@4.3.1+sha224.934d21773e22af4b69a7032a2d3b4cb38c1f7c019624777cc9916b23",
"packageManager": "yarn@4.9.0+sha224.dce6c5df199861784bd9b0eecb2a228df97e3f18a02b1bb75ff98383",
"devDependencies": {
"@types/debug": "^4.1.5",
"@types/node": "^20.4.6",
"@types/proxy-from-env": "^1",
"@types/semver": "^7.1.0",
"@types/which": "^3.0.0",
"@yarnpkg/eslint-config": "^2.0.0",
"@yarnpkg/eslint-config": "^3.0.0",
"@yarnpkg/fslib": "^3.0.0-rc.48",
"@zkochan/cmd-shim": "^6.0.0",
"better-sqlite3": "^11.7.2",
"clipanion": "patch:clipanion@npm%3A3.2.1#~/.yarn/patches/clipanion-npm-3.2.1-fc9187f56c.patch",
"debug": "^4.1.1",
"esbuild": "^0.21.0",
"eslint": "^8.57.0",
"esbuild": "^0.25.0",
"eslint": "^9.22.0",
"proxy-from-env": "^1.1.0",
"semver": "^7.6.3",
"supports-color": "^9.0.0",
"supports-color": "^10.0.0",
"tar": "^7.4.0",
"tsx": "^4.16.2",
"typescript": "^5.3.3",
"typescript": "^5.7.3",
"undici": "^6.19.2",
"v8-compile-cache": "^2.3.0",
"vitest": "^2.0.3",
"which": "^4.0.0"
"vitest": "^3.0.5",
"which": "^5.0.0"
},
"resolutions": {
"undici-types": "6.x"

View File

@ -14,9 +14,9 @@ import * as folderUtils from './folderUtil
import type {NodeError} from './nodeUtils';
import * as semverUtils from './semverUtils';
import * as specUtils from './specUtils';
import {Config, Descriptor, Locator, PackageManagerSpec} from './types';
import {Config, Descriptor, LazyLocator, Locator} from './types';
import {SupportedPackageManagers, SupportedPackageManagerSet} from './types';
import {isSupportedPackageManager} from './types';
import {isSupportedPackageManager, PackageManagerSpec} from './types';
export type PreparedPackageManagerInfo = Awaited<ReturnType<Engine[`ensurePackageManager`]>>;
@ -237,19 +237,23 @@ export class Engine {
*
* If the project doesn't include a specification file, we just assume that
* whatever the user uses is exactly what they want to use. Since the version
* isn't explicited, we fallback on known good versions.
* isn't specified, we fallback on known good versions.
*
* Finally, if the project doesn't exist at all, we ask the user whether they
* want to create one in the current project. If they do, we initialize a new
* project using the default package managers, and configure it so that we
* don't need to ask again in the future.
*/
async findProjectSpec(initialCwd: string, locator: Locator, {transparent = false}: {transparent?: boolean} = {}): Promise<Descriptor> {
async findProjectSpec(initialCwd: string, locator: Locator | LazyLocator, {transparent = false}: {transparent?: boolean} = {}): Promise<Descriptor> {
// A locator is a valid descriptor (but not the other way around)
const fallbackDescriptor = {name: locator.name, range: `${locator.reference}`};
if (process.env.COREPACK_ENABLE_PROJECT_SPEC === `0`)
if (process.env.COREPACK_ENABLE_PROJECT_SPEC === `0`) {
if (typeof locator.reference === `function`)
fallbackDescriptor.range = await locator.reference();
return fallbackDescriptor;
}
if (process.env.COREPACK_ENABLE_STRICT === `0`)
transparent = true;
@ -258,12 +262,19 @@ export class Engine {
const result = await specUtils.loadSpec(initialCwd);
switch (result.type) {
case `NoProject`:
case `NoProject`: {
if (typeof locator.reference === `function`)
fallbackDescriptor.range = await locator.reference();
debugUtils.log(`Falling back to ${fallbackDescriptor.name}@${fallbackDescriptor.range} as no project manifest were found`);
return fallbackDescriptor;
}
case `NoSpec`: {
if (process.env.COREPACK_ENABLE_AUTO_PIN !== `0`) {
if (typeof locator.reference === `function`)
fallbackDescriptor.range = await locator.reference();
if (process.env.COREPACK_ENABLE_AUTO_PIN === `1`) {
const resolved = await this.resolveDescriptor(fallbackDescriptor, {allowTags: true});
if (resolved === null)
throw new UsageError(`Failed to successfully resolve '${fallbackDescriptor.range}' to a valid ${fallbackDescriptor.name} release`);
@ -277,21 +288,25 @@ export class Engine {
await specUtils.setLocalPackageManager(path.dirname(result.target), installSpec);
}
debugUtils.log(`Falling back to ${fallbackDescriptor.name}@${fallbackDescriptor.range} in the absence of "packageManage" field in ${result.target}`);
debugUtils.log(`Falling back to ${fallbackDescriptor.name}@${fallbackDescriptor.range} in the absence of "packageManager" field in ${result.target}`);
return fallbackDescriptor;
}
case `Found`: {
if (result.spec.name !== locator.name) {
const spec = result.getSpec();
if (spec.name !== locator.name) {
if (transparent) {
debugUtils.log(`Falling back to ${fallbackDescriptor.name}@${fallbackDescriptor.range} in a ${result.spec.name}@${result.spec.range} project`);
if (typeof locator.reference === `function`)
fallbackDescriptor.range = await locator.reference();
debugUtils.log(`Falling back to ${fallbackDescriptor.name}@${fallbackDescriptor.range} in a ${spec.name}@${spec.range} project`);
return fallbackDescriptor;
} else {
throw new UsageError(`This project is configured to use ${result.spec.name} because ${result.target} has a "packageManager" field`);
throw new UsageError(`This project is configured to use ${spec.name} because ${result.target} has a "packageManager" field`);
}
} else {
debugUtils.log(`Using ${result.spec.name}@${result.spec.range} as defined in project manifest ${result.target}`);
return result.spec;
debugUtils.log(`Using ${spec.name}@${spec.range} as defined in project manifest ${result.target}`);
return spec;
}
}
}
@ -299,14 +314,14 @@ export class Engine {
}
async executePackageManagerRequest({packageManager, binaryName, binaryVersion}: PackageManagerRequest, {cwd, args}: {cwd: string, args: Array<string>}): Promise<void> {
let fallbackLocator: Locator = {
let fallbackLocator: Locator | LazyLocator = {
name: binaryName as SupportedPackageManagers,
reference: undefined as any,
};
let isTransparentCommand = false;
if (packageManager != null) {
const defaultVersion = binaryVersion || await this.getDefaultVersion(packageManager);
const defaultVersion = binaryVersion || (() => this.getDefaultVersion(packageManager));
const definition = this.config.definitions[packageManager]!;
// If all leading segments match one of the patterns defined in the `transparent`
@ -325,7 +340,7 @@ export class Engine {
fallbackLocator = {
name: packageManager,
reference: fallbackReference,
reference: fallbackReference as string,
};
}

View File

@ -13,13 +13,13 @@ export abstract class BaseCommand extends Command<Context> {
const lookup = await specUtils.loadSpec(this.context.cwd);
switch (lookup.type) {
case `NoProject`:
throw new UsageError(`Couldn't find a project in the local directory - please explicit the package manager to pack, or run this command from a valid project`);
throw new UsageError(`Couldn't find a project in the local directory - please specify the package manager to pack, or run this command from a valid project`);
case `NoSpec`:
throw new UsageError(`The local project doesn't feature a 'packageManager' field - please explicit the package manager to pack, or update the manifest to reference it`);
throw new UsageError(`The local project doesn't feature a 'packageManager' field nor a 'devEngines.packageManager' field - please specify the package manager to pack, or update the manifest to reference it`);
default: {
return [lookup.spec];
return [lookup.range ?? lookup.getSpec()];
}
}
}

View File

@ -3,6 +3,7 @@ import fs from 'f
import path from 'path';
import which from 'which';
import * as corepackUtils from '../corepackUtils';
import {Context} from '../main';
import type {NodeError} from '../nodeUtils';
import {isSupportedPackageManager, SupportedPackageManagerSetWithoutNpm} from '../types';
@ -70,6 +71,11 @@ export class DisableCommand extends Command<Context> {
async removePosixLink(installDirectory: string, binName: string) {
const file = path.join(installDirectory, binName);
try {
if (binName.includes(`yarn`) && corepackUtils.isYarnSwitchPath(await fs.promises.realpath(file))) {
console.warn(`${binName} is already installed in ${file} and points to a Yarn Switch install - skipping`);
return;
}
await fs.promises.unlink(file);
} catch (err) {
if ((err as NodeError).code !== `ENOENT`) {

View File

@ -4,6 +4,7 @@ import fs from 'f
import path from 'path';
import which from 'which';
import * as corepackUtils from '../corepackUtils';
import {Context} from '../main';
import {isSupportedPackageManager, SupportedPackageManagerSetWithoutNpm} from '../types';
@ -83,6 +84,12 @@ export class EnableCommand extends Command<Context> {
if (fs.existsSync(file)) {
const currentSymlink = await fs.promises.readlink(file);
if (binName.includes(`yarn`) && corepackUtils.isYarnSwitchPath(await fs.promises.realpath(file))) {
console.warn(`${binName} is already installed in ${file} and points to a Yarn Switch install - skipping`);
return;
}
if (currentSymlink !== symlink) {
await fs.promises.unlink(file);
} else {

View File

@ -36,13 +36,13 @@ export class PrepareCommand extends Command<Context> {
const lookup = await specUtils.loadSpec(this.context.cwd);
switch (lookup.type) {
case `NoProject`:
throw new UsageError(`Couldn't find a project in the local directory - please explicit the package manager to pack, or run this command from a valid project`);
throw new UsageError(`Couldn't find a project in the local directory - please specify the package manager to pack, or run this command from a valid project`);
case `NoSpec`:
throw new UsageError(`The local project doesn't feature a 'packageManager' field - please explicit the package manager to pack, or update the manifest to reference it`);
throw new UsageError(`The local project doesn't feature a 'packageManager' field - please specify the package manager to pack, or update the manifest to reference it`);
default: {
specs.push(lookup.spec);
specs.push(lookup.getSpec());
}
}
}

View File

@ -19,6 +19,12 @@ import * as npmRegistryUtils from './npmRegist
import {RegistrySpec, Descriptor, Locator, PackageManagerSpec} from './types';
import {BinList, BinSpec, InstallSpec, DownloadSpec} from './types';
const YARN_SWITCH_REGEX = /[/\\]switch[/\\]bin[/\\]/;
export function isYarnSwitchPath(p: string) {
return YARN_SWITCH_REGEX.test(p);
}
export function getRegistryFromPackageManagerSpec(spec: PackageManagerSpec) {
return process.env.COREPACK_NPM_REGISTRY
? spec.npmRegistry ?? spec.registry

View File

@ -38,6 +38,10 @@ function getPackageManagerRequestFromCli(parameter: string | undefined, engine:
};
}
function isUsageError(error: any): error is UsageError {
return error?.name === `UsageError`;
}
export async function runMain(argv: Array<string>) {
const engine = new Engine();
@ -85,8 +89,8 @@ export async function runMain(argv: Array<string>) {
cwd: process.cwd(),
args: restArgs,
});
} catch (error: UsageError | any) {
if (error?.name === `UsageError`) {
} catch (error) {
if (isUsageError(error)) {
console.error(error.message);
process.exit(1);
}

View File

@ -38,19 +38,27 @@ export function verifySignature({signatures, integrity, packageName, version}: {
packageName: string;
version: string;
}) {
const {npm: keys} = process.env.COREPACK_INTEGRITY_KEYS ?
if (!Array.isArray(signatures) || !signatures.length) throw new Error(`No compatible signature found in package metadata`);
const {npm: trustedKeys} = process.env.COREPACK_INTEGRITY_KEYS ?
JSON.parse(process.env.COREPACK_INTEGRITY_KEYS) as typeof defaultConfig.keys :
defaultConfig.keys;
const key = keys.find(({keyid}) => signatures.some(s => s.keyid === keyid));
const signature = signatures.find(({keyid}) => keyid === key?.keyid);
if (key == null || signature == null) throw new Error(`Cannot find matching keyid: ${JSON.stringify({signatures, keys})}`);
let signature: typeof signatures[0] | undefined;
let key!: string;
for (const k of trustedKeys) {
signature = signatures.find(({keyid}) => keyid === k.keyid);
if (signature != null) {
key = k.key;
break;
}
}
if (signature?.sig == null) throw new UsageError(`The package was not signed by any trusted keys: ${JSON.stringify({signatures, trustedKeys}, undefined, 2)}`);
const verifier = createVerify(`SHA256`);
verifier.end(`${packageName}@${version}:${integrity}`);
const valid = verifier.verify(
`-----BEGIN PUBLIC KEY-----\n${key.key}\n-----END PUBLIC KEY-----`,
`-----BEGIN PUBLIC KEY-----\n${key}\n-----END PUBLIC KEY-----`,
signature.sig,
`base64`,
);
@ -65,10 +73,15 @@ export async function fetchLatestStableVersion(packageName: string) {
const {version, dist: {integrity, signatures, shasum}} = metadata;
if (!shouldSkipIntegrityCheck()) {
verifySignature({
packageName, version,
integrity, signatures,
});
try {
verifySignature({
packageName, version,
integrity, signatures,
});
} catch (cause) {
// TODO: consider switching to `UsageError` when https://github.com/arcanis/clipanion/issues/157 is fixed
throw new Error(`Corepack cannot download the latest stable version of ${packageName}; you can disable signature verification by setting COREPACK_INTEGRITY_CHECK to 0 in your env, or instruct Corepack to use the latest stable release known by this version of Corepack by setting COREPACK_USE_LATEST to 0`, {cause});
}
}
return `${version}+${

View File

@ -1,13 +1,17 @@
import {UsageError} from 'clipanion';
import fs from 'fs';
import path from 'path';
import semverSatisfies from 'semver/functions/satisfies';
import semverValid from 'semver/functions/valid';
import semverValidRange from 'semver/ranges/valid';
import {parseEnv} from 'util';
import {PreparedPackageManagerInfo} from './Engine';
import * as debugUtils from './debugUtils';
import {NodeError} from './nodeUtils';
import * as nodeUtils from './nodeUtils';
import {Descriptor, isSupportedPackageManager} from './types';
import type {LocalEnvFile} from './types';
const nodeModulesRegExp = /[\\/]node_modules[\\/](@[^\\/]*[\\/])?([^@\\/][^\\/]*)$/;
@ -52,16 +56,87 @@ export function parseSpec(raw: unknown, source: string, {enforceExactVersion = t
};
}
type CorepackPackageJSON = {
packageManager?: string;
devEngines?: {packageManager?: DevEngineDependency};
};
interface DevEngineDependency {
name: string;
version: string;
onFail?: `ignore` | `warn` | `error`;
}
function warnOrThrow(errorMessage: string, onFail?: DevEngineDependency[`onFail`]) {
switch (onFail) {
case `ignore`:
break;
case `error`:
case undefined:
throw new UsageError(errorMessage);
default:
console.warn(`! Corepack validation warning: ${errorMessage}`);
}
}
function parsePackageJSON(packageJSONContent: CorepackPackageJSON) {
const {packageManager: pm} = packageJSONContent;
if (packageJSONContent.devEngines?.packageManager != null) {
const {packageManager} = packageJSONContent.devEngines;
if (typeof packageManager !== `object`) {
console.warn(`! Corepack only supports objects as valid value for devEngines.packageManager. The current value (${JSON.stringify(packageManager)}) will be ignored.`);
return pm;
}
if (Array.isArray(packageManager)) {
console.warn(`! Corepack does not currently support array values for devEngines.packageManager`);
return pm;
}
const {name, version, onFail} = packageManager;
if (typeof name !== `string` || name.includes(`@`)) {
warnOrThrow(`The value of devEngines.packageManager.name ${JSON.stringify(name)} is not a supported string value`, onFail);
return pm;
}
if (version != null && (typeof version !== `string` || !semverValidRange(version))) {
warnOrThrow(`The value of devEngines.packageManager.version ${JSON.stringify(version)} is not a valid semver range`, onFail);
return pm;
}
debugUtils.log(`devEngines.packageManager defines that ${name}@${version} is the local package manager`);
if (pm) {
if (!pm.startsWith?.(`${name}@`))
warnOrThrow(`"packageManager" field is set to ${JSON.stringify(pm)} which does not match the "devEngines.packageManager" field set to ${JSON.stringify(name)}`, onFail);
else if (version != null && !semverSatisfies(pm.slice(packageManager.name.length + 1), version))
warnOrThrow(`"packageManager" field is set to ${JSON.stringify(pm)} which does not match the value defined in "devEngines.packageManager" for ${JSON.stringify(name)} of ${JSON.stringify(version)}`, onFail);
return pm;
}
return `${name}@${version ?? `*`}`;
}
return pm;
}
export async function setLocalPackageManager(cwd: string, info: PreparedPackageManagerInfo) {
const lookup = await loadSpec(cwd);
const range = `range` in lookup && lookup.range;
if (range) {
if (info.locator.name !== range.name || !semverSatisfies(info.locator.reference, range.range)) {
warnOrThrow(`The requested version of ${info.locator.name}@${info.locator.reference} does not match the devEngines specification (${range.name}@${range.range})`, range.onFail);
}
}
const content = lookup.type !== `NoProject`
? await fs.promises.readFile(lookup.target, `utf8`)
: ``;
const {data, indent} = nodeUtils.readPackageJson(content);
const previousPackageManager = data.packageManager ?? `unknown`;
const previousPackageManager = data.packageManager ?? (range ? `${range.name}@${range.range}` : `unknown`);
data.packageManager = `${info.locator.name}@${info.locator.reference}`;
const newContent = nodeUtils.normalizeLineEndings(content, `${JSON.stringify(data, null, indent)}\n`);
@ -72,10 +147,17 @@ export async function setLocalPackageManager(cwd: string, info: PreparedPackageM
};
}
interface FoundSpecResult {
type: `Found`;
target: string;
getSpec: () => Descriptor;
range?: Descriptor & {onFail?: DevEngineDependency[`onFail`]};
envFilePath?: string;
}
export type LoadSpecResult =
| {type: `NoProject`, target: string}
| {type: `NoSpec`, target: string}
| {type: `Found`, target: string, spec: Descriptor};
| FoundSpecResult;
export async function loadSpec(initialCwd: string): Promise<LoadSpecResult> {
let nextCwd = initialCwd;
@ -84,6 +166,8 @@ export async function loadSpec(initialCwd: string): Promise<LoadSpecResult> {
let selection: {
data: any;
manifestPath: string;
envFilePath?: string;
localEnv: LocalEnvFile;
} | null = null;
while (nextCwd !== currCwd && (!selection || !selection.data.packageManager)) {
@ -111,19 +195,60 @@ export async function loadSpec(initialCwd: string): Promise<LoadSpecResult> {
if (typeof data !== `object` || data === null)
throw new UsageError(`Invalid package.json in ${path.relative(initialCwd, manifestPath)}`);
selection = {data, manifestPath};
let localEnv: LocalEnvFile;
const envFilePath = path.resolve(currCwd, process.env.COREPACK_ENV_FILE ?? `.corepack.env`);
if (process.env.COREPACK_ENV_FILE == `0`) {
debugUtils.log(`Skipping env file as configured with COREPACK_ENV_FILE`);
localEnv = process.env;
} else if (typeof parseEnv !== `function`) {
// TODO: remove this block when support for Node.js 18.x is dropped.
debugUtils.log(`Skipping env file as it is not supported by the current version of Node.js`);
localEnv = process.env;
} else {
debugUtils.log(`Checking ${envFilePath}`);
try {
localEnv = {
...Object.fromEntries(Object.entries(parseEnv(await fs.promises.readFile(envFilePath, `utf8`))).filter(e => e[0].startsWith(`COREPACK_`))),
...process.env,
};
debugUtils.log(`Successfully loaded env file found at ${envFilePath}`);
} catch (err) {
if ((err as NodeError)?.code !== `ENOENT`)
throw err;
debugUtils.log(`No env file found at ${envFilePath}`);
localEnv = process.env;
}
}
selection = {data, manifestPath, localEnv, envFilePath};
}
if (selection === null)
return {type: `NoProject`, target: path.join(initialCwd, `package.json`)};
const rawPmSpec = selection.data.packageManager;
let envFilePath: string | undefined;
if (selection.localEnv !== process.env) {
envFilePath = selection.envFilePath;
process.env = selection.localEnv;
}
const rawPmSpec = parsePackageJSON(selection.data);
if (typeof rawPmSpec === `undefined`)
return {type: `NoSpec`, target: selection.manifestPath};
debugUtils.log(`${selection.manifestPath} defines ${rawPmSpec} as local package manager`);
return {
type: `Found`,
target: selection.manifestPath,
spec: parseSpec(rawPmSpec, path.relative(initialCwd, selection.manifestPath)),
envFilePath,
range: selection.data.devEngines?.packageManager?.version && {
name: selection.data.devEngines.packageManager.name,
range: selection.data.devEngines.packageManager.version,
onFail: selection.data.devEngines.packageManager.onFail,
},
// Lazy-loading it so we do not throw errors on commands that do not need valid spec.
getSpec: () => parseSpec(rawPmSpec, path.relative(initialCwd, selection.manifestPath)),
};
}

View File

@ -71,7 +71,7 @@ export interface DownloadSpec {
*/
export interface Config {
definitions: {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
[name in SupportedPackageManagers]?: {
/**
* Defines the version that needs to be used when running commands within
@ -145,3 +145,20 @@ export interface Locator {
*/
reference: string;
}
/**
*
*/
export interface LazyLocator {
/**
* The name of the package manager required.
*/
name: string;
/**
* The exact version required.
*/
reference: () => Promise<string>;
}
export type LocalEnvFile = Record<string, string | undefined>;

View File

@ -97,4 +97,23 @@ describe(`DisableCommand`, () => {
await expect(sortedEntries).resolves.toEqual([...binNames].sort());
});
});
it(`shouldn't remove Yarn binaries if they are in a /switch/ folder`, async () => {
await xfs.mktempPromise(async cwd => {
await xfs.mkdirPromise(ppath.join(cwd, `switch/bin`), {recursive: true});
await xfs.writeFilePromise(ppath.join(cwd, `switch/bin/yarn`), `hello`);
await xfs.linkPromise(
ppath.join(cwd, `switch/bin/yarn`),
ppath.join(cwd, `yarn`),
);
await expect(runCli(cwd, [`disable`])).resolves.toMatchObject({
exitCode: 0,
});
const file = await xfs.readFilePromise(ppath.join(cwd, `yarn`), `utf8`);
expect(file).toBe(`hello`);
});
});
});

View File

@ -1,7 +1,7 @@
import {Filename, ppath, xfs, npath} from '@yarnpkg/fslib';
import {delimiter} from 'node:path';
import process from 'node:process';
import {describe, beforeEach, it, expect} from 'vitest';
import {describe, beforeEach, it, expect, test} from 'vitest';
import {Engine} from '../sources/Engine';
import {SupportedPackageManagers, SupportedPackageManagerSetWithoutNpm} from '../sources/types';
@ -87,4 +87,42 @@ describe(`EnableCommand`, () => {
await expect(sortedEntries).resolves.toEqual(expectedEntries.sort());
});
});
test.skipIf(process.platform === `win32`)(`should overwrite existing files`, async () => {
await xfs.mktempPromise(async cwd => {
await xfs.writeFilePromise(ppath.join(cwd, `yarn`), `hello`);
process.env.PATH = `${npath.fromPortablePath(cwd)}${delimiter}${process.env.PATH}`;
await expect(runCli(cwd, [`enable`])).resolves.toMatchObject({
stdout: ``,
stderr: ``,
exitCode: 0,
});
const file = await xfs.readFilePromise(ppath.join(cwd, `yarn`), `utf8`);
expect(file).toBe(`hello`);
});
});
test.skipIf(process.platform === `win32`)(`shouldn't overwrite Yarn files if they are in a /switch/ folder`, async () => {
await xfs.mktempPromise(async cwd => {
await xfs.mkdirPromise(ppath.join(cwd, `switch/bin`), {recursive: true});
await xfs.writeFilePromise(ppath.join(cwd, `switch/bin/yarn`), `hello`);
await xfs.linkPromise(
ppath.join(cwd, `switch/bin/yarn`),
ppath.join(cwd, `yarn`),
);
process.env.PATH = `${npath.fromPortablePath(cwd)}${delimiter}${process.env.PATH}`;
await expect(runCli(cwd, [`enable`])).resolves.toMatchObject({
stdout: ``,
stderr: ``,
exitCode: 0,
});
const file = await xfs.readFilePromise(ppath.join(cwd, `yarn`), `utf8`);
expect(file).toBe(`hello`);
});
});
});

View File

@ -5,30 +5,131 @@ import {describe, beforeEach, it, expect} from 'vitest';
import {runCli} from './_runCli';
beforeEach(async () => {
const home = await xfs.mktempPromise();
// `process.env` is reset after each tests in setupTests.js.
process.env.COREPACK_HOME = npath.fromPortablePath(await xfs.mktempPromise());
process.env.COREPACK_HOME = npath.fromPortablePath(home);
process.env.COREPACK_DEFAULT_TO_LATEST = `0`;
return async () => {
await xfs.removePromise(home, {recursive: true});
};
});
describe(`UpCommand`, () => {
it(`should upgrade the package manager from the current project`, async () => {
await xfs.mktempPromise(async cwd => {
await xfs.writeJsonPromise(ppath.join(cwd, `package.json`), {
packageManager: `yarn@2.1.0`,
});
describe(`should update the "packageManager" field from the current project`, () => {
it(`to the same major if no devEngines range`, async () => {
await xfs.mktempPromise(async cwd => {
await xfs.writeJsonPromise(ppath.join(cwd, `package.json`), {
packageManager: `yarn@2.1.0`,
});
await expect(runCli(cwd, [`up`])).resolves.toMatchObject({
exitCode: 0,
stderr: ``,
});
await expect(runCli(cwd, [`up`])).resolves.toMatchObject({
exitCode: 0,
stderr: ``,
stdout: expect.stringMatching(/^Installing yarn@2\.4\.3 in the project\.\.\.\n\n/),
});
await expect(xfs.readJsonPromise(ppath.join(cwd, `package.json`))).resolves.toMatchObject({
packageManager: `yarn@2.4.3+sha512.8dd9fedc5451829619e526c56f42609ad88ae4776d9d3f9456d578ac085115c0c2f0fb02bb7d57fd2e1b6e1ac96efba35e80a20a056668f61c96934f67694fd0`,
});
await expect(xfs.readJsonPromise(ppath.join(cwd, `package.json`))).resolves.toMatchObject({
packageManager: `yarn@2.4.3+sha512.8dd9fedc5451829619e526c56f42609ad88ae4776d9d3f9456d578ac085115c0c2f0fb02bb7d57fd2e1b6e1ac96efba35e80a20a056668f61c96934f67694fd0`,
});
await expect(runCli(cwd, [`yarn`, `--version`])).resolves.toMatchObject({
exitCode: 0,
stdout: `2.4.3\n`,
await expect(runCli(cwd, [`yarn`, `--version`])).resolves.toMatchObject({
exitCode: 0,
stdout: `2.4.3\n`,
stderr: ``,
});
});
});
it(`to whichever range devEngines defines`, async () => {
await xfs.mktempPromise(async cwd => {
await xfs.writeJsonPromise(ppath.join(cwd, `package.json`), {
packageManager: `yarn@1.1.0`,
devEngines: {
packageManager: {
name: `yarn`,
version: `1.x || 2.x`,
},
},
});
await expect(runCli(cwd, [`up`])).resolves.toMatchObject({
exitCode: 0,
stderr: ``,
stdout: expect.stringMatching(/^Installing yarn@2\.4\.3 in the project\.\.\.\n\n/),
});
await expect(xfs.readJsonPromise(ppath.join(cwd, `package.json`))).resolves.toMatchObject({
packageManager: `yarn@2.4.3+sha512.8dd9fedc5451829619e526c56f42609ad88ae4776d9d3f9456d578ac085115c0c2f0fb02bb7d57fd2e1b6e1ac96efba35e80a20a056668f61c96934f67694fd0`,
});
await expect(runCli(cwd, [`yarn`, `--version`])).resolves.toMatchObject({
exitCode: 0,
stdout: `2.4.3\n`,
stderr: ``,
});
});
});
it(`to whichever range devEngines defines even if onFail is set to ignore`, async () => {
await xfs.mktempPromise(async cwd => {
await xfs.writeJsonPromise(ppath.join(cwd, `package.json`), {
packageManager: `pnpm@10.1.0`,
devEngines: {
packageManager: {
name: `yarn`,
version: `1.x || 2.x`,
onFail: `ignore`,
},
},
});
await expect(runCli(cwd, [`up`])).resolves.toMatchObject({
exitCode: 0,
stderr: ``,
stdout: expect.stringMatching(/^Installing yarn@2\.4\.3 in the project\.\.\.\n\n/),
});
await expect(xfs.readJsonPromise(ppath.join(cwd, `package.json`))).resolves.toMatchObject({
packageManager: `yarn@2.4.3+sha512.8dd9fedc5451829619e526c56f42609ad88ae4776d9d3f9456d578ac085115c0c2f0fb02bb7d57fd2e1b6e1ac96efba35e80a20a056668f61c96934f67694fd0`,
});
await expect(runCli(cwd, [`yarn`, `--version`])).resolves.toMatchObject({
exitCode: 0,
stdout: `2.4.3\n`,
stderr: ``,
});
});
});
it(`should succeed even if no 'packageManager' field`, async () => {
await xfs.mktempPromise(async cwd => {
process.env.NO_COLOR = `1`;
await xfs.writeJsonPromise(ppath.join(cwd, `package.json`), {
devEngines: {
packageManager: {
name: `yarn`,
version: `1.x || 2.x`,
},
},
});
await expect(runCli(cwd, [`up`])).resolves.toMatchObject({
exitCode: 0,
stderr: ``,
stdout: expect.stringMatching(/^Installing yarn@2\.4\.3 in the project\.\.\.\n\n/),
});
await expect(xfs.readJsonPromise(ppath.join(cwd, `package.json`))).resolves.toMatchObject({
packageManager: `yarn@2.4.3+sha512.8dd9fedc5451829619e526c56f42609ad88ae4776d9d3f9456d578ac085115c0c2f0fb02bb7d57fd2e1b6e1ac96efba35e80a20a056668f61c96934f67694fd0`,
});
await expect(runCli(cwd, [`yarn`, `--version`])).resolves.toMatchObject({
exitCode: 0,
stdout: `2.4.3\n`,
stderr: ``,
});
});
});
});

View File

@ -5,29 +5,112 @@ import {describe, beforeEach, it, expect} from 'vitest';
import {runCli} from './_runCli';
beforeEach(async () => {
const home = await xfs.mktempPromise();
// `process.env` is reset after each tests in setupTests.js.
process.env.COREPACK_HOME = npath.fromPortablePath(await xfs.mktempPromise());
process.env.COREPACK_HOME = npath.fromPortablePath(home);
process.env.COREPACK_DEFAULT_TO_LATEST = `0`;
return async () => {
await xfs.removePromise(home, {recursive: true});
};
});
describe(`UseCommand`, () => {
it(`should set the package manager in the current project`, async () => {
await xfs.mktempPromise(async cwd => {
await xfs.writeJsonPromise(ppath.join(cwd, `package.json`), {
packageManager: `yarn@1.0.0`,
});
describe(`should set the package manager in the current project`, () => {
it(`With an existing 'packageManager' field`, async () => {
await xfs.mktempPromise(async cwd => {
await xfs.writeJsonPromise(ppath.join(cwd, `package.json`), {
packageManager: `yarn@1.0.0`,
license: `MIT`,
});
await expect(runCli(cwd, [`use`, `yarn@1.22.4`])).resolves.toMatchObject({
exitCode: 0,
});
await expect(runCli(cwd, [`use`, `yarn@1.22.4`])).resolves.toMatchObject({
exitCode: 0,
stdout: expect.stringMatching(/^Installing yarn@1\.22\.4 in the project\.\.\.\n\n/),
stderr: ``,
});
await expect(xfs.readJsonPromise(ppath.join(cwd, `package.json`))).resolves.toMatchObject({
packageManager: `yarn@1.22.4+sha512.a1833b862fe52169bd6c2a033045a07df5bc6a23595c259e675fed1b2d035ab37abe6ce309720abb6636d68f03615054b6292dc0a70da31c8697fda228b50d18`,
});
await expect(xfs.readJsonPromise(ppath.join(cwd, `package.json`))).resolves.toMatchObject({
packageManager: `yarn@1.22.4+sha512.a1833b862fe52169bd6c2a033045a07df5bc6a23595c259e675fed1b2d035ab37abe6ce309720abb6636d68f03615054b6292dc0a70da31c8697fda228b50d18`,
});
await expect(runCli(cwd, [`yarn`, `--version`])).resolves.toMatchObject({
exitCode: 0,
stdout: `1.22.4\n`,
await expect(runCli(cwd, [`yarn`, `--version`])).resolves.toMatchObject({
exitCode: 0,
stdout: `1.22.4\n`,
stderr: ``,
});
});
});
it(`with 'devEngines.packageManager' field`, async () => {
await xfs.mktempPromise(async cwd => {
process.env.NO_COLOR = `1`;
const devEngines = {packageManager: {name: `yarn`, version: `2.x`}};
await xfs.writeJsonPromise(ppath.join(cwd, `package.json`), {
devEngines,
});
// Should refuse to install an incompatible version:
await expect(runCli(cwd, [`use`, `yarn@1.22.4`])).resolves.toMatchObject({
exitCode: 1,
stderr: ``,
stdout: `Installing yarn@1.22.4 in the project...\nUsage Error: The requested version of yarn@1.22.4+sha512.a1833b862fe52169bd6c2a033045a07df5bc6a23595c259e675fed1b2d035ab37abe6ce309720abb6636d68f03615054b6292dc0a70da31c8697fda228b50d18 does not match the devEngines specification (yarn@2.x)\n\n$ corepack use <pattern>\n`,
});
// Should accept setting to a compatible version:
await expect(runCli(cwd, [`use`, `yarn@2.4.3`])).resolves.toMatchObject({
exitCode: 0,
stderr: ``,
stdout: expect.stringMatching(/^Installing yarn@2\.4\.3 in the project\.\.\.\n\n/),
});
await expect(xfs.readJsonPromise(ppath.join(cwd, `package.json`))).resolves.toMatchObject({
devEngines,
packageManager: `yarn@2.4.3+sha512.8dd9fedc5451829619e526c56f42609ad88ae4776d9d3f9456d578ac085115c0c2f0fb02bb7d57fd2e1b6e1ac96efba35e80a20a056668f61c96934f67694fd0`,
});
await expect(runCli(cwd, [`yarn`, `--version`])).resolves.toMatchObject({
exitCode: 0,
stdout: `2.4.3\n`,
stderr: ``,
});
});
});
it(`with 'devEngines.packageManager' and 'packageManager' fields`, async () => {
await xfs.mktempPromise(async cwd => {
process.env.NO_COLOR = `1`;
const devEngines = {packageManager: {name: `yarn`, version: `1.x || 2.x`}};
await xfs.writeJsonPromise(ppath.join(cwd, `package.json`), {
devEngines,
packageManager: `yarn@1.1.0`,
license: `MIT`,
});
// Should refuse to install an incompatible version:
await expect(runCli(cwd, [`use`, `yarn@1.22.4`])).resolves.toMatchObject({
exitCode: 0,
stderr: ``,
stdout: expect.stringMatching(/^Installing yarn@1\.22\.4 in the project\.\.\.\n\n/),
});
// Should accept setting to a compatible version:
await expect(runCli(cwd, [`use`, `yarn@2.4.3`])).resolves.toMatchObject({
exitCode: 0,
stderr: ``,
stdout: expect.stringMatching(/^Installing yarn@2\.4\.3 in the project\.\.\.\n\n/),
});
await expect(xfs.readJsonPromise(ppath.join(cwd, `package.json`))).resolves.toMatchObject({
devEngines,
packageManager: `yarn@2.4.3+sha512.8dd9fedc5451829619e526c56f42609ad88ae4776d9d3f9456d578ac085115c0c2f0fb02bb7d57fd2e1b6e1ac96efba35e80a20a056668f61c96934f67694fd0`,
});
await expect(runCli(cwd, [`yarn`, `--version`])).resolves.toMatchObject({
exitCode: 0,
stdout: `2.4.3\n`,
stderr: ``,
});
});
});
});
@ -64,4 +147,50 @@ describe(`UseCommand`, () => {
});
});
});
describe(`should not care if packageManager is set to an invalid value`, () => {
for (const {description, packageManager} of [
{
description: `when a version range is given`,
packageManager: `yarn@1.x`,
},
{
description: `when only the pm name is given`,
packageManager: `yarn`,
},
{
description: `when the version is missing`,
packageManager: `yarn@`,
},
{
description: `when the field is not a string`,
packageManager: [],
},
]) {
it(description, async () => {
await xfs.mktempPromise(async cwd => {
await xfs.writeJsonPromise(ppath.join(cwd, `package.json`), {
packageManager,
license: `MIT`, // To avoid warning
});
await expect(runCli(cwd, [`use`, `yarn@1.22.4`])).resolves.toMatchObject({
exitCode: 0,
stderr: ``,
stdout: expect.stringMatching(/^Installing yarn@1\.22\.4 in the project\.\.\.\n\n/),
});
await expect(xfs.readJsonPromise(ppath.join(cwd, `package.json`))).resolves.toMatchObject({
packageManager: `yarn@1.22.4+sha512.a1833b862fe52169bd6c2a033045a07df5bc6a23595c259e675fed1b2d035ab37abe6ce309720abb6636d68f03615054b6292dc0a70da31c8697fda228b50d18`,
});
await expect(runCli(cwd, [`yarn`, `--version`])).resolves.toMatchObject({
exitCode: 0,
stdout: `1.22.4\n`,
stderr: ``,
});
});
});
}
});
});

View File

@ -79,7 +79,7 @@ const registry = {
__proto__: null,
yarn: [`1.9998.9999`],
pnpm: [`1.9998.9999`],
// eslint-disable-next-line @typescript-eslint/naming-convention
'@yarnpkg/cli-dist': [`5.9999.9999`],
customPkgManager: [`1.0.0`],
};
@ -111,6 +111,8 @@ function generateVersionMetadata(packageName, version) {
const TOKEN_MOCK = `SOME_DUMMY_VALUE`;
const server = createServer((req, res) => {
res.setHeader(`connection`, `close`);
const auth = req.headers.authorization;
if (
@ -129,7 +131,6 @@ const server = createServer((req, res) => {
const packageName = req.url.slice(1, slashPosition === -1 ? undefined : slashPosition);
if (packageName in registry) {
if (req.url === `/${packageName}`) {
// eslint-disable-next-line @typescript-eslint/naming-convention
res.end(JSON.stringify({"dist-tags": {
latest: registry[packageName].at(-1),
}, versions: Object.fromEntries(registry[packageName].map(version =>

View File

@ -9,6 +9,6 @@ describe(`key store should be up-to-date`, () => {
it(`should contain up-to-date npm keys`, async () => {
const r = await globalThis.fetch(new URL(`/-/npm/v1/keys`, DEFAULT_NPM_REGISTRY_URL));
expect(r.ok).toBe(true);
expect(r.json()).resolves.toMatchObject({keys: defaultConfig.keys.npm});
await expect(r.json()).resolves.toMatchObject({keys: defaultConfig.keys.npm});
});
});

View File

@ -1,31 +1,54 @@
import {Filename, ppath, xfs, npath, PortablePath} from '@yarnpkg/fslib';
import os from 'node:os';
import process from 'node:process';
import {beforeEach, describe, expect, it} from 'vitest';
import {Filename, ppath, xfs, npath, PortablePath} from '@yarnpkg/fslib';
import os from 'node:os';
import process from 'node:process';
import {afterEach, beforeEach, describe, expect, it} from 'vitest';
import config from '../config.json';
import * as folderUtils from '../sources/folderUtils';
import {SupportedPackageManagerSet} from '../sources/types';
import config from '../config.json';
import * as folderUtils from '../sources/folderUtils';
import {SupportedPackageManagerSet} from '../sources/types';
import {runCli} from './_runCli';
import {runCli} from './_runCli';
beforeEach(async () => {
const home = await xfs.mktempPromise();
// `process.env` is reset after each tests in setupTests.js.
process.env.COREPACK_HOME = npath.fromPortablePath(await xfs.mktempPromise());
process.env.COREPACK_HOME = npath.fromPortablePath(home);
process.env.COREPACK_DEFAULT_TO_LATEST = `0`;
return async () => {
await xfs.removePromise(home, {recursive: true});
};
});
it(`should refuse to download a package manager if the hash doesn't match`, async () => {
await xfs.mktempPromise(async cwd => {
await xfs.writeJsonPromise(ppath.join(cwd, `package.json` as Filename), {
packageManager: `yarn@1.22.4+sha1.deadbeef`,
});
describe(`should refuse to download a package manager if the hash doesn't match`, () => {
it(`the one defined in "devEngines.packageManager" field`, async () => {
await xfs.mktempPromise(async cwd => {
await xfs.writeJsonPromise(ppath.join(cwd, `package.json` as Filename), {
devEngines: {
packageManager: {name: `yarn`, version: `1.22.4+sha1.deadbeef`},
},
});
await expect(runCli(cwd, [`yarn`, `--version`])).resolves.toMatchObject({
exitCode: 1,
stderr: /Mismatch hashes/,
stdout: ``,
await expect(runCli(cwd, [`yarn`, `--version`])).resolves.toMatchObject({
exitCode: 1,
stderr: expect.stringContaining(`Mismatch hashes`),
stdout: ``,
});
});
});
it(`the one defined in "packageManager" field`, async () => {
await xfs.mktempPromise(async cwd => {
await xfs.writeJsonPromise(ppath.join(cwd, `package.json` as Filename), {
packageManager: `yarn@1.22.4+sha1.deadbeef`,
});
await expect(runCli(cwd, [`yarn`, `--version`])).resolves.toMatchObject({
exitCode: 1,
stderr: expect.stringContaining(`Mismatch hashes`),
stdout: ``,
});
});
});
});
@ -35,7 +58,7 @@ it(`should refuse to download a known package manager from a URL`, async () => {
// Package managers known by Corepack cannot be loaded from a URL.
await expect(runCli(cwd, [`yarn@https://registry.npmjs.com/yarn/-/yarn-1.22.21.tgz`, `--version`])).resolves.toMatchObject({
exitCode: 1,
stderr: /Illegal use of URL for known package manager/,
stderr: expect.stringContaining(`Illegal use of URL for known package manager`),
stdout: ``,
});
@ -57,7 +80,7 @@ it.fails(`should refuse to download a known package manager from a URL in packag
await expect(runCli(cwd, [`yarn`, `--version`])).resolves.toMatchObject({
exitCode: 1,
stderr: /Illegal use of URL for known package manager/,
stderr: expect.stringContaining(`Illegal use of URL for known package manager`),
stdout: ``,
});
@ -82,7 +105,7 @@ it(`should require a version to be specified`, async () => {
await expect(runCli(cwd, [`yarn`, `--version`])).resolves.toMatchObject({
exitCode: 1,
stderr: /expected a semver version/,
stderr: `No version specified for yarn in "packageManager" of package.json\n`,
stdout: ``,
});
@ -92,7 +115,7 @@ it(`should require a version to be specified`, async () => {
await expect(runCli(cwd, [`yarn`, `--version`])).resolves.toMatchObject({
exitCode: 1,
stderr: /expected a semver version/,
stderr: expect.stringContaining(`expected a semver version`),
stdout: ``,
});
@ -102,7 +125,7 @@ it(`should require a version to be specified`, async () => {
await expect(runCli(cwd, [`yarn`, `--version`])).resolves.toMatchObject({
exitCode: 1,
stderr: /expected a semver version/,
stderr: expect.stringContaining(`expected a semver version`),
stdout: ``,
});
});
@ -150,6 +173,20 @@ for (const [name, version, expectedVersion = version.split(`+`, 1)[0]] of tested
stderr: ``,
stdout: `${expectedVersion}\n`,
});
await xfs.writeJsonPromise(ppath.join(cwd, `package.json` as Filename), {
devEngines: {packageManager: {name, version}},
});
await expect(runCli(cwd, [name, `--version`])).resolves.toMatchObject(URL.canParse(version) ? {
exitCode: 1,
stderr: expect.stringMatching(/^The value of devEngines\.packageManager\.version ".+" is not a valid semver range\n$/),
stdout: ``,
} : {
exitCode: 0,
stderr: ``,
stdout: `${expectedVersion}\n`,
});
});
});
}
@ -231,6 +268,298 @@ it(`should ignore the packageManager field when found within a node_modules vend
});
});
describe(`should handle invalid devEngines values`, () => {
it(`throw on missing version`, async () => {
await xfs.mktempPromise(async cwd => {
await xfs.writeJsonPromise(ppath.join(cwd, `package.json` as PortablePath), {
devEngines: {
packageManager: {
name: `yarn`,
},
},
});
await expect(runCli(cwd, [`yarn`, `--version`])).resolves.toMatchObject({
exitCode: 1,
stderr: `Invalid package manager specification in package.json (yarn@*); expected a semver version\n`,
stdout: ``,
});
});
});
it(`throw on invalid version`, async () => {
await xfs.mktempPromise(async cwd => {
await xfs.writeJsonPromise(ppath.join(cwd, `package.json` as PortablePath), {
devEngines: {
packageManager: {
name: `yarn`,
version: `yarn@1.x`,
},
},
});
await expect(runCli(cwd, [`yarn`, `--version`])).resolves.toMatchObject({
exitCode: 1,
stderr: `The value of devEngines.packageManager.version "yarn@1.x" is not a valid semver range\n`,
stdout: ``,
});
});
});
it(`warn on array values`, async () => {
await xfs.mktempPromise(async cwd => {
await xfs.writeJsonPromise(ppath.join(cwd, `package.json` as PortablePath), {
packageManager: `yarn@1.22.4+sha1.01c1197ca5b27f21edc8bc472cd4c8ce0e5a470e`,
devEngines: {
packageManager: [{
name: `pnpm`,
version: `10.x`,
},
]},
});
await expect(runCli(cwd, [`yarn`, `--version`])).resolves.toMatchObject({
exitCode: 0,
stderr: `! Corepack does not currently support array values for devEngines.packageManager\n`,
stdout: `1.22.4\n`,
});
});
});
it(`warn on string values`, async () => {
await xfs.mktempPromise(async cwd => {
await xfs.writeJsonPromise(ppath.join(cwd, `package.json` as PortablePath), {
packageManager: `yarn@1.22.4+sha1.01c1197ca5b27f21edc8bc472cd4c8ce0e5a470e`,
devEngines: {
packageManager: `pnpm@10.x`,
},
});
await expect(runCli(cwd, [`yarn`, `--version`])).resolves.toMatchObject({
exitCode: 0,
stderr: `! Corepack only supports objects as valid value for devEngines.packageManager. The current value ("pnpm@10.x") will be ignored.\n`,
stdout: `1.22.4\n`,
});
});
});
it(`warn on number values`, async () => {
await xfs.mktempPromise(async cwd => {
await xfs.writeJsonPromise(ppath.join(cwd, `package.json` as PortablePath), {
packageManager: `yarn@1.22.4+sha1.01c1197ca5b27f21edc8bc472cd4c8ce0e5a470e`,
devEngines: {
packageManager: 10,
},
});
await expect(runCli(cwd, [`yarn`, `--version`])).resolves.toMatchObject({
exitCode: 0,
stderr: `! Corepack only supports objects as valid value for devEngines.packageManager. The current value (10) will be ignored.\n`,
stdout: `1.22.4\n`,
});
});
});
});
it(`should use hash from "packageManager" even when "devEngines" defines a different one`, async () => {
await xfs.mktempPromise(async cwd => {
await xfs.writeJsonPromise(ppath.join(cwd, `package.json` as PortablePath), {
packageManager: `yarn@3.0.0-rc.2+sha1.11111`,
devEngines: {
packageManager: {
name: `yarn`,
version: `3.0.0-rc.2+sha1.22222`,
},
},
});
await expect(runCli(cwd, [`yarn`, `--version`])).resolves.toMatchObject({
exitCode: 1,
stderr: expect.stringContaining(`Mismatch hashes. Expected 11111, got`),
stdout: ``,
});
});
});
describe(`should accept range in devEngines only if a specific version is provided`, () => {
it(`either in package.json#packageManager field`, async () => {
await xfs.mktempPromise(async cwd => {
await xfs.writeJsonPromise(ppath.join(cwd, `package.json` as PortablePath), {
devEngines: {
packageManager: {
name: `pnpm`,
version: `6.x`,
},
},
});
await expect(runCli(cwd, [`pnpm`, `--version`])).resolves.toMatchObject({
exitCode: 1,
stderr: `Invalid package manager specification in package.json (pnpm@6.x); expected a semver version\n`,
stdout: ``,
});
await xfs.writeJsonPromise(ppath.join(cwd, `package.json` as PortablePath), {
devEngines: {
packageManager: {
name: `pnpm`,
version: `6.x`,
},
},
packageManager: `pnpm@6.6.2+sha224.eb5c0acad3b0f40ecdaa2db9aa5a73134ad256e17e22d1419a2ab073`,
});
await expect(runCli(cwd, [`pnpm`, `--version`])).resolves.toMatchObject({
exitCode: 0,
stderr: ``,
stdout: `6.6.2\n`,
});
// No version should also work
await xfs.writeJsonPromise(ppath.join(cwd, `package.json` as PortablePath), {
devEngines: {
packageManager: {
name: `pnpm`,
},
},
packageManager: `pnpm@6.6.2+sha224.eb5c0acad3b0f40ecdaa2db9aa5a73134ad256e17e22d1419a2ab073`,
});
await expect(runCli(cwd, [`pnpm`, `--version`])).resolves.toMatchObject({
exitCode: 0,
stderr: ``,
stdout: `6.6.2\n`,
});
});
});
});
describe(`when devEngines.packageManager.name does not match packageManager`, () => {
it(`should ignore if devEngines.packageManager.onFail is set to "ignore"`, async () => {
await xfs.mktempPromise(async cwd => {
await xfs.writeJsonPromise(ppath.join(cwd, `package.json` as PortablePath), {
devEngines: {
packageManager: {
name: `yarn`,
onFail: `ignore`,
},
},
packageManager: `pnpm@6.6.2+sha1.7b4d6b176c1b93b5670ed94c24babb7d80c13854`,
});
await expect(runCli(cwd, [`pnpm`, `--version`])).resolves.toMatchObject({
exitCode: 0,
stderr: ``,
stdout: `6.6.2\n`,
});
});
});
it(`should warn if devEngines.packageManager.onFail is set to "warn"`, async () => {
await xfs.mktempPromise(async cwd => {
await xfs.writeJsonPromise(ppath.join(cwd, `package.json` as PortablePath), {
devEngines: {
packageManager: {
name: `yarn`,
onFail: `warn`,
},
},
packageManager: `pnpm@6.6.2+sha1.7b4d6b176c1b93b5670ed94c24babb7d80c13854`,
});
await expect(runCli(cwd, [`pnpm`, `--version`])).resolves.toMatchObject({
exitCode: 0,
stderr: `! Corepack validation warning: "packageManager" field is set to "pnpm@6.6.2+sha1.7b4d6b176c1b93b5670ed94c24babb7d80c13854" which does not match the "devEngines.packageManager" field set to "yarn"\n`,
stdout: `6.6.2\n`,
});
});
});
it(`should throw if devEngines.packageManager.onFail is set to "error"`, async () => {
await xfs.mktempPromise(async cwd => {
await xfs.writeJsonPromise(ppath.join(cwd, `package.json` as PortablePath), {
devEngines: {
packageManager: {
name: `yarn`,
onFail: `error`,
},
},
packageManager: `pnpm@6.6.2+sha1.7b4d6b176c1b93b5670ed94c24babb7d80c13854`,
});
await expect(runCli(cwd, [`pnpm`, `--version`])).resolves.toMatchObject({
exitCode: 1,
stderr: `"packageManager" field is set to "pnpm@6.6.2+sha1.7b4d6b176c1b93b5670ed94c24babb7d80c13854" which does not match the "devEngines.packageManager" field set to "yarn"\n`,
stdout: ``,
});
});
});
it(`should throw by default`, async () => {
await xfs.mktempPromise(async cwd => {
await xfs.writeJsonPromise(ppath.join(cwd, `package.json` as PortablePath), {
devEngines: {
packageManager: {
name: `yarn`,
},
},
packageManager: `pnpm@6.6.2+sha1.7b4d6b176c1b93b5670ed94c24babb7d80c13854`,
});
await expect(runCli(cwd, [`pnpm`, `--version`])).resolves.toMatchObject({
exitCode: 1,
stderr: `"packageManager" field is set to "pnpm@6.6.2+sha1.7b4d6b176c1b93b5670ed94c24babb7d80c13854" which does not match the "devEngines.packageManager" field set to "yarn"\n`,
stdout: ``,
});
});
});
});
describe(`should reject if range in devEngines does not match version provided`, () => {
it(`unless onFail is set to "ignore"`, async () => {
await xfs.mktempPromise(async cwd => {
await xfs.writeJsonPromise(ppath.join(cwd, `package.json` as PortablePath), {
devEngines: {
packageManager: {
name: `pnpm`,
version: `10.x`,
onFail: `ignore`,
},
},
packageManager: `pnpm@6.6.2+sha1.7b4d6b176c1b93b5670ed94c24babb7d80c13854`,
});
await expect(runCli(cwd, [`pnpm`, `--version`])).resolves.toMatchObject({
exitCode: 0,
stderr: ``,
stdout: `6.6.2\n`,
});
});
});
it(`unless onFail is set to "warn"`, async () => {
await xfs.mktempPromise(async cwd => {
await xfs.writeJsonPromise(ppath.join(cwd, `package.json` as PortablePath), {
devEngines: {
packageManager: {
name: `pnpm`,
version: `10.x`,
onFail: `warn`,
},
},
packageManager: `pnpm@6.6.2+sha1.7b4d6b176c1b93b5670ed94c24babb7d80c13854`,
});
await expect(runCli(cwd, [`pnpm`, `--version`])).resolves.toMatchObject({
exitCode: 0,
stderr: `! Corepack validation warning: "packageManager" field is set to "pnpm@6.6.2+sha1.7b4d6b176c1b93b5670ed94c24babb7d80c13854" which does not match the value defined in "devEngines.packageManager" for "pnpm" of "10.x"\n`,
stdout: `6.6.2\n`,
});
});
});
it(`in package.json#packageManager field`, async () => {
await xfs.mktempPromise(async cwd => {
await xfs.writeJsonPromise(ppath.join(cwd, `package.json` as PortablePath), {
devEngines: {
packageManager: {
name: `pnpm`,
version: `10.x`,
},
},
packageManager: `pnpm@6.6.2+sha1.7b4d6b176c1b93b5670ed94c24babb7d80c13854`,
});
await expect(runCli(cwd, [`pnpm`, `--version`])).resolves.toMatchObject({
exitCode: 1,
stderr: `"packageManager" field is set to "pnpm@6.6.2+sha1.7b4d6b176c1b93b5670ed94c24babb7d80c13854" which does not match the value defined in "devEngines.packageManager" for "pnpm" of "10.x"\n`,
stdout: ``,
});
});
});
});
it(`should use the closest matching packageManager field`, async () => {
await xfs.mktempPromise(async cwd => {
await xfs.mkdirPromise(ppath.join(cwd, `foo` as PortablePath), {recursive: true});
@ -272,7 +601,7 @@ it(`shouldn't allow using regular Yarn commands on npm-configured projects`, asy
await expect(runCli(cwd, [`yarn`, `--version`])).resolves.toMatchObject({
exitCode: 1,
stderr: /This project is configured to use npm/,
stderr: expect.stringContaining(`This project is configured to use npm`),
});
});
});
@ -318,18 +647,73 @@ for (const name of SupportedPackageManagerSet) {
});
}
it(`should configure the project when calling a package manager on it for the first time`, async () => {
await xfs.mktempPromise(async cwd => {
await xfs.writeJsonPromise(ppath.join(cwd, `package.json` as Filename), {
describe(`when called on a project without any defined packageManager`, () => {
it(`should not modify package.json by default`, async () => {
await xfs.mktempPromise(async cwd => {
await xfs.writeJsonPromise(ppath.join(cwd, `package.json` as Filename), {
// empty package.json file
});
await runCli(cwd, [`yarn`]);
const data = await xfs.readJsonPromise(ppath.join(cwd, `package.json` as Filename));
expect(Object.hasOwn(data, `packageManager`)).toBeFalsy();
});
});
await runCli(cwd, [`yarn`]);
it(`should modify package.json if enabled by env`, async () => {
process.env.COREPACK_ENABLE_AUTO_PIN = `1`;
const data = await xfs.readJsonPromise(ppath.join(cwd, `package.json` as Filename));
await xfs.mktempPromise(async cwd => {
await xfs.writeJsonPromise(ppath.join(cwd, `package.json` as Filename), {
// empty package.json file
});
expect(data).toMatchObject({
packageManager: `yarn@${config.definitions.yarn.default}`,
await runCli(cwd, [`yarn`]);
const data = await xfs.readJsonPromise(ppath.join(cwd, `package.json` as Filename));
expect(data).toMatchObject({
packageManager: expect.stringMatching(/^yarn@/),
});
});
});
it(`should modify package.json if enabled by .corepack.env`, async t => {
// Skip that test on Node.js 18.x as it lacks support for .env files.
if (process.version.startsWith(`v18.`)) t.skip();
await xfs.mktempPromise(async cwd => {
await xfs.writeJsonPromise(ppath.join(cwd, `package.json` as Filename), {
// empty package.json file
});
await xfs.writeFilePromise(ppath.join(cwd, `.corepack.env` as Filename), `COREPACK_ENABLE_AUTO_PIN=1\n`);
await runCli(cwd, [`yarn`]);
const data = await xfs.readJsonPromise(ppath.join(cwd, `package.json` as Filename));
expect(data).toMatchObject({
packageManager: expect.stringMatching(/^yarn@/),
});
});
});
it(`should not modify package.json if .corepack.env is disabled`, async () => {
process.env.COREPACK_ENV_FILE = `0`;
await xfs.mktempPromise(async cwd => {
await xfs.writeJsonPromise(ppath.join(cwd, `package.json` as Filename), {
// empty package.json file
});
await xfs.writeFilePromise(ppath.join(cwd, `.corepack.env` as Filename), `COREPACK_ENABLE_AUTO_PIN=1\n`);
await runCli(cwd, [`yarn`]);
const data = await xfs.readJsonPromise(ppath.join(cwd, `package.json` as Filename));
expect(Object.hasOwn(data, `packageManager`)).toBeFalsy();
});
});
});
@ -473,13 +857,19 @@ it(`should support disabling the network accesses from the environment`, async (
await expect(runCli(cwd, [`yarn`, `--version`])).resolves.toMatchObject({
stdout: ``,
stderr: /Network access disabled by the environment/,
stderr: expect.stringContaining(`Network access disabled by the environment`),
exitCode: 1,
});
});
});
describe(`read-only and offline environment`, () => {
afterEach(async () => {
const home = npath.toPortablePath(process.env.COREPACK_HOME!);
await xfs.chmodPromise(ppath.join(home, `lastKnownGood.json`), 0o644);
await xfs.chmodPromise(home, 0o755);
});
it(`should support running in project scope`, async () => {
await xfs.mktempPromise(async cwd => {
// Reset to default
@ -497,15 +887,10 @@ describe(`read-only and offline environment`, () => {
exitCode: 0,
});
// Let corepack discover the latest yarn version.
// BUG: This should not be necessary with a fully specified version in package.json plus populated corepack cache.
// Engine.executePackageManagerRequest needs to defer the fallback work. This requires a big refactoring.
await expect(runCli(cwd, [`yarn`, `--version`])).resolves.toMatchObject({
exitCode: 0,
});
// Make COREPACK_HOME ro
const home = npath.toPortablePath(folderUtils.getCorepackHomeFolder());
// Make a lastKnownGood.json file with not JSON-parsable content:
await xfs.writeFilePromise(ppath.join(home, `lastKnownGood.json`), `{`);
await xfs.chmodPromise(ppath.join(home, `lastKnownGood.json`), 0o444);
await xfs.chmodPromise(home, 0o555);
@ -811,16 +1196,35 @@ it(`should support package managers in ESM format`, async () => {
});
});
it(`should show a warning on stderr before downloading when enable`, async() => {
await xfs.mktempPromise(async cwd => {
process.env.COREPACK_ENABLE_DOWNLOAD_PROMPT = `1`;
await xfs.writeJsonPromise(ppath.join(cwd, `package.json` as Filename), {
packageManager: `yarn@3.0.0`,
describe(`should show a warning on stderr before downloading when enable`, () => {
it(`when enabled by the environment`, async () => {
await xfs.mktempPromise(async cwd => {
process.env.COREPACK_ENABLE_DOWNLOAD_PROMPT = `1`;
await xfs.writeJsonPromise(ppath.join(cwd, `package.json` as Filename), {
packageManager: `yarn@3.0.0`,
});
await expect(runCli(cwd, [`yarn`, `--version`])).resolves.toMatchObject({
exitCode: 0,
stdout: `3.0.0\n`,
stderr: `! Corepack is about to download https://repo.yarnpkg.com/3.0.0/packages/yarnpkg-cli/bin/yarn.js\n`,
});
});
await expect(runCli(cwd, [`yarn`, `--version`])).resolves.toMatchObject({
exitCode: 0,
stdout: `3.0.0\n`,
stderr: `! Corepack is about to download https://repo.yarnpkg.com/3.0.0/packages/yarnpkg-cli/bin/yarn.js\n`,
});
it(`should ignore setting in .corepack.env`, async () => {
await xfs.mktempPromise(async cwd => {
await xfs.writeFilePromise(
ppath.join(cwd, `.corepack.env` as Filename),
`COREPACK_ENABLE_DOWNLOAD_PROMPT=1\n`,
);
await xfs.writeJsonPromise(ppath.join(cwd, `package.json` as Filename), {
packageManager: `yarn@3.0.0`,
});
await expect(runCli(cwd, [`yarn`, `--version`])).resolves.toMatchObject({
exitCode: 0,
stdout: `3.0.0\n`,
stderr: ``,
});
});
});
});
@ -850,8 +1254,8 @@ it(`should download yarn classic from custom registry`, async () => {
process.env.COREPACK_ENABLE_DOWNLOAD_PROMPT = `1`;
await expect(runCli(cwd, [`yarn`, `--version`])).resolves.toMatchObject({
exitCode: 0,
stdout: /^1\.\d+\.\d+\r?\n$/,
stderr: /^! Corepack is about to download https:\/\/registry\.npmmirror\.com\/yarn\/-\/yarn-1\.\d+\.\d+\.tgz\r?\n$/,
stdout: expect.stringMatching(/^1\.\d+\.\d+\r?\n$/),
stderr: expect.stringMatching(/^! Corepack is about to download https:\/\/registry\.npmmirror\.com\/yarn\/-\/yarn-1\.\d+\.\d+\.tgz\r?\n$/),
});
// Should keep working with cache
@ -899,7 +1303,7 @@ it(`should download latest pnpm from custom registry`, async () => {
await expect(runCli(cwd, [`pnpm`, `--version`], true)).resolves.toMatchObject({
exitCode: 0,
stdout: `pnpm: Hello from custom registry\n`,
stderr: /^! The local project doesn't define a 'packageManager' field\. Corepack will now add one referencing pnpm@1\.9998\.9999@sha1\./,
stderr: ``,
});
// Should keep working with cache
@ -911,6 +1315,156 @@ it(`should download latest pnpm from custom registry`, async () => {
});
});
describe(`should pick up COREPACK_INTEGRITY_KEYS from env`, () => {
beforeEach(() => {
process.env.AUTH_TYPE = `COREPACK_NPM_TOKEN`; // See `_registryServer.mjs`
process.env.COREPACK_DEFAULT_TO_LATEST = `1`;
});
it(`from env variable`, async () => {
await xfs.mktempPromise(async cwd => {
await xfs.writeJsonPromise(ppath.join(cwd, `package.json` as Filename), {
});
await expect(runCli(cwd, [`pnpm`, `--version`], true)).resolves.toMatchObject({
exitCode: 1,
stdout: ``,
stderr: expect.stringContaining(`No compatible signature found in package metadata`),
});
process.env.COREPACK_INTEGRITY_KEYS = `0`;
await expect(runCli(cwd, [`pnpm`, `--version`], true)).resolves.toMatchObject({
exitCode: 0,
stdout: `pnpm: Hello from custom registry\n`,
stderr: ``,
});
});
});
it(`from .corepack.env file`, async t => {
// Skip that test on Node.js 18.x as it lacks support for .env files.
if (process.version.startsWith(`v18.`)) t.skip();
await xfs.mktempPromise(async cwd => {
await xfs.writeJsonPromise(ppath.join(cwd, `package.json` as Filename), {
});
await expect(runCli(cwd, [`pnpm`, `--version`], true)).resolves.toMatchObject({
exitCode: 1,
stdout: ``,
stderr: expect.stringContaining(`No compatible signature found in package metadata`),
});
await xfs.writeFilePromise(ppath.join(cwd, `.corepack.env` as Filename), `COREPACK_INTEGRITY_KEYS=0\n`);
await expect(runCli(cwd, [`pnpm`, `--version`], true)).resolves.toMatchObject({
exitCode: 0,
stdout: `pnpm: Hello from custom registry\n`,
stderr: ``,
});
});
});
it(`from env file defined by COREPACK_ENV_FILE`, async t => {
// Skip that test on Node.js 18.x as it lacks support for .env files.
if (process.version.startsWith(`v18.`)) t.skip();
await xfs.mktempPromise(async cwd => {
await xfs.writeJsonPromise(ppath.join(cwd, `package.json` as Filename), {
});
await xfs.writeFilePromise(ppath.join(cwd, `.corepack.env` as Filename), `COREPACK_INTEGRITY_KEYS={}\n`);
await xfs.writeFilePromise(ppath.join(cwd, `.other.env` as Filename), `COREPACK_INTEGRITY_KEYS=0\n`);
// By default, Corepack should be using .corepack.env and fail.
await expect(runCli(cwd, [`pnpm`, `--version`], true)).resolves.toMatchObject({
exitCode: 1,
stdout: ``,
stderr: expect.stringContaining(`No compatible signature found in package metadata`),
});
process.env.COREPACK_ENV_FILE = `.other.env`;
await expect(runCli(cwd, [`pnpm`, `--version`], true)).resolves.toMatchObject({
exitCode: 0,
stdout: `pnpm: Hello from custom registry\n`,
stderr: ``,
});
});
});
it(`from env even if there's a .corepack.env file`, async () => {
await xfs.mktempPromise(async cwd => {
await xfs.writeJsonPromise(ppath.join(cwd, `package.json` as Filename), {
});
await xfs.writeFilePromise(ppath.join(cwd, `.corepack.env` as Filename), `COREPACK_INTEGRITY_KEYS={}\n`);
// By default, Corepack should be using .corepack.env (or the built-in ones on Node.js 18.x) and fail.
await expect(runCli(cwd, [`pnpm`, `--version`], true)).resolves.toMatchObject({
exitCode: 1,
stdout: ``,
stderr: expect.stringContaining(`No compatible signature found in package metadata`),
});
process.env.COREPACK_INTEGRITY_KEYS = ``;
await expect(runCli(cwd, [`pnpm`, `--version`], true)).resolves.toMatchObject({
exitCode: 0,
stdout: `pnpm: Hello from custom registry\n`,
stderr: ``,
});
});
});
it(`should ignore .corepack.env file if COREPACK_ENV_FILE is set to 0`, async t => {
// Skip that test on Node.js 18.x as it lacks support for .env files.
if (process.version.startsWith(`v18.`)) t.skip();
await xfs.mktempPromise(async cwd => {
await xfs.writeJsonPromise(ppath.join(cwd, `package.json` as Filename), {
});
await xfs.writeFilePromise(ppath.join(cwd, `.corepack.env` as Filename), `COREPACK_INTEGRITY_KEYS=0\n`);
process.env.COREPACK_ENV_FILE = `0`;
await expect(runCli(cwd, [`pnpm`, `--version`], true)).resolves.toMatchObject({
exitCode: 1,
stdout: ``,
stderr: expect.stringContaining(`No compatible signature found in package metadata`),
});
delete process.env.COREPACK_ENV_FILE;
await expect(runCli(cwd, [`pnpm`, `--version`], true)).resolves.toMatchObject({
exitCode: 0,
stdout: `pnpm: Hello from custom registry\n`,
stderr: ``,
});
});
});
it(`from env file defined by COREPACK_ENV_FILE`, async t => {
// Skip that test on Node.js 18.x as it lacks support for .env files.
if (process.version.startsWith(`v18.`)) t.skip();
process.env.COREPACK_ENV_FILE = `.other.env`;
await xfs.mktempPromise(async cwd => {
await xfs.writeJsonPromise(ppath.join(cwd, `package.json` as Filename), {
});
await expect(runCli(cwd, [`pnpm`, `--version`], true)).resolves.toMatchObject({
exitCode: 1,
stdout: ``,
stderr: expect.stringContaining(`No compatible signature found in package metadata`),
});
await xfs.writeFilePromise(ppath.join(cwd, `.other.env` as Filename), `COREPACK_INTEGRITY_KEYS=0\n`);
await expect(runCli(cwd, [`pnpm`, `--version`], true)).resolves.toMatchObject({
exitCode: 0,
stdout: `pnpm: Hello from custom registry\n`,
stderr: ``,
});
});
});
});
for (const authType of [`COREPACK_NPM_REGISTRY`, `COREPACK_NPM_TOKEN`, `COREPACK_NPM_PASSWORD`, `PROXY`]) {
describe(`custom registry with auth ${authType}`, () => {
beforeEach(() => {
@ -967,122 +1521,139 @@ for (const authType of [`COREPACK_NPM_REGISTRY`, `COREPACK_NPM_TOKEN`, `COREPACK
describe(`handle integrity checks`, () => {
beforeEach(() => {
process.env.AUTH_TYPE = `COREPACK_NPM_TOKEN`; // See `_registryServer.mjs`
process.env.COREPACK_DEFAULT_TO_LATEST = `1`;
});
it(`should return no error when signature matches`, async () => {
process.env.TEST_INTEGRITY = `valid`; // See `_registryServer.mjs`
describe(`when signature matches`, () => {
beforeEach(() => {
process.env.TEST_INTEGRITY = `valid`; // See `_registryServer.mjs`
});
it(`should return no error when calling 'corepack use'`, async () => {
await xfs.mktempPromise(async cwd => {
// Skip rest of the test on Windows & Node.js 18.x as it inevitably times out otherwise.
if (process.version.startsWith(`v18.`) && os.platform() === `win32`) return;
// Removing home directory to force the "re-download"
await xfs.rmPromise(process.env.COREPACK_HOME as any, {recursive: true});
await Promise.all([
expect(runCli(cwd, [`use`, `pnpm`], true)).resolves.toMatchObject({
exitCode: 0,
stdout: `Installing pnpm@1.9998.9999 in the project...\n\npnpm: Hello from custom registry\n`,
stderr: ``,
}),
expect(runCli(cwd, [`use`, `yarn@1.x`], true)).resolves.toMatchObject({
exitCode: 0,
stdout: `Installing yarn@1.9998.9999 in the project...\n\nyarn: Hello from custom registry\n`,
stderr: ``,
}),
expect(runCli(cwd, [`use`, `yarn@latest`], true)).resolves.toMatchObject({
exitCode: 0,
stdout: `Installing yarn@5.9999.9999 in the project...\n\nyarn: Hello from custom registry\n`,
stderr: ``,
}),
]);
});
});
it(`should return no error when fetching latest version`, async () => {
process.env.COREPACK_DEFAULT_TO_LATEST = `1`;
await xfs.mktempPromise(async cwd => {
await Promise.all([
expect(runCli(cwd, [`pnpm`, `--version`], true)).resolves.toMatchObject({
exitCode: 0,
stdout: `pnpm: Hello from custom registry\n`,
stderr: ``,
}),
expect(runCli(cwd, [`yarn@1.x`, `--version`], true)).resolves.toMatchObject({
exitCode: 0,
stdout: `yarn: Hello from custom registry\n`,
stderr: ``,
}),
expect(runCli(cwd, [`yarn@5.x`, `--version`], true)).resolves.toMatchObject({
exitCode: 0,
stdout: `yarn: Hello from custom registry\n`,
stderr: ``,
}),
]);
});
});
});
it(`should return an error when signature does not match with a range`, async () => {
process.env.TEST_INTEGRITY = `invalid_signature`; // See `_registryServer.mjs`
await xfs.mktempPromise(async cwd => {
await Promise.all([
expect(runCli(cwd, [`pnpm`, `--version`], true)).resolves.toMatchObject({
exitCode: 0,
stdout: `pnpm: Hello from custom registry\n`,
stderr: ``,
}),
expect(runCli(cwd, [`yarn@1.x`, `--version`], true)).resolves.toMatchObject({
exitCode: 0,
stdout: `yarn: Hello from custom registry\n`,
stderr: ``,
}),
expect(runCli(cwd, [`yarn@5.x`, `--version`], true)).resolves.toMatchObject({
exitCode: 0,
stdout: `yarn: Hello from custom registry\n`,
stderr: ``,
}),
]);
// Skip rest of the test on Windows & Node.js 18.x as it inevitably times out otherwise.
if (process.version.startsWith(`v18.`) && os.platform() === `win32`) return;
// Removing home directory to force the "re-download"
await xfs.rmPromise(process.env.COREPACK_HOME as any, {recursive: true});
await Promise.all([
expect(runCli(cwd, [`use`, `pnpm`], true)).resolves.toMatchObject({
exitCode: 0,
stdout: `Installing pnpm@1.9998.9999 in the project...\n\npnpm: Hello from custom registry\n`,
stderr: ``,
}),
expect(runCli(cwd, [`use`, `yarn@1.x`], true)).resolves.toMatchObject({
exitCode: 0,
stdout: `Installing yarn@1.9998.9999 in the project...\n\nyarn: Hello from custom registry\n`,
stderr: ``,
}),
expect(runCli(cwd, [`use`, `yarn@latest`], true)).resolves.toMatchObject({
exitCode: 0,
stdout: `Installing yarn@5.9999.9999 in the project...\n\nyarn: Hello from custom registry\n`,
stderr: ``,
}),
]);
await expect(runCli(cwd, [`pnpm@1.x`, `--version`], true)).resolves.toMatchObject({
exitCode: 1,
stderr: expect.stringContaining(`Signature does not match`),
stdout: ``,
});
});
});
it(`should return an error when signature does not match with a tag`, async () => {
process.env.TEST_INTEGRITY = `invalid_signature`; // See `_registryServer.mjs`
await xfs.mktempPromise(async cwd => {
await expect(runCli(cwd, [`pnpm@1.x`, `--version`], true)).resolves.toMatchObject({
await expect(runCli(cwd, [`pnpm@latest`, `--version`], true)).resolves.toMatchObject({
exitCode: 1,
stderr: /Signature does not match/,
stdout: ``,
});
await expect(runCli(cwd, [`yarn@stable`, `--version`], true)).resolves.toMatchObject({
exitCode: 1,
stderr: /Signature does not match/,
stderr: expect.stringContaining(`Signature does not match`),
stdout: ``,
});
});
});
it(`should return an error when hash does not match without a tag`, async () => {
process.env.TEST_INTEGRITY = `invalid_integrity`; // See `_registryServer.mjs`
process.env.COREPACK_DEFAULT_TO_LATEST = `1`; // Necessary as the version defined in `config.json` does not exist on the custom registry.
await xfs.mktempPromise(async cwd => {
await expect(runCli(cwd, [`pnpm`, `--version`], true)).resolves.toMatchObject({
exitCode: 1,
stderr: /Mismatch hashes. Expected [a-f0-9]{128}, got [a-f0-9]{128}/,
stderr: expect.stringMatching(/Mismatch hashes. Expected [a-f0-9]{128}, got [a-f0-9]{128}/),
stdout: ``,
});
// A second time to validate the invalid version was not cached.
await expect(runCli(cwd, [`pnpm`, `--version`], true)).resolves.toMatchObject({
exitCode: 1,
stderr: /Mismatch hashes. Expected [a-f0-9]{128}, got [a-f0-9]{128}/,
stderr: expect.stringMatching(/Mismatch hashes. Expected [a-f0-9]{128}, got [a-f0-9]{128}/),
stdout: ``,
});
await expect(runCli(cwd, [`yarn`, `--version`], true)).resolves.toMatchObject({
exitCode: 1,
stderr: /Mismatch hashes. Expected [a-f0-9]{128}, got [a-f0-9]{128}/,
stderr: expect.stringMatching(/Mismatch hashes. Expected [a-f0-9]{128}, got [a-f0-9]{128}/),
stdout: ``,
});
await expect(runCli(cwd, [`use`, `pnpm`], true)).resolves.toMatchObject({
exitCode: 1,
stdout: /Mismatch hashes. Expected [a-f0-9]{128}, got [a-f0-9]{128}/,
stdout: expect.stringMatching(/Mismatch hashes. Expected [a-f0-9]{128}, got [a-f0-9]{128}/),
stderr: ``,
});
});
});
it(`should return an error when signature does not match without a tag`, async () => {
process.env.TEST_INTEGRITY = `invalid_signature`; // See `_registryServer.mjs`
process.env.COREPACK_DEFAULT_TO_LATEST = `1`; // Necessary as the version defined in `config.json` does not exist on the custom registry.
await xfs.mktempPromise(async cwd => {
await expect(runCli(cwd, [`pnpm`, `--version`], true)).resolves.toMatchObject({
exitCode: 1,
stderr: /Signature does not match/,
stderr: expect.stringContaining(`Signature does not match`),
stdout: ``,
});
// A second time to validate the invalid version was not cached.
await expect(runCli(cwd, [`pnpm`, `--version`], true)).resolves.toMatchObject({
exitCode: 1,
stderr: /Signature does not match/,
stderr: expect.stringContaining(`Signature does not match`),
stdout: ``,
});
await expect(runCli(cwd, [`yarn`, `--version`], true)).resolves.toMatchObject({
exitCode: 1,
stderr: /Signature does not match/,
stderr: expect.stringContaining(`Signature does not match`),
stdout: ``,
});
await expect(runCli(cwd, [`use`, `pnpm`], true)).resolves.toMatchObject({
exitCode: 1,
stdout: /Signature does not match/,
stdout: expect.stringContaining(`Signature does not match`),
stderr: ``,
});
});
@ -1093,12 +1664,12 @@ describe(`handle integrity checks`, () => {
await xfs.mktempPromise(async cwd => {
await expect(runCli(cwd, [`yarn@1.9998.9999`, `--version`], true)).resolves.toMatchObject({
exitCode: 1,
stderr: /Signature does not match/,
stderr: expect.stringContaining(`Signature does not match`),
stdout: ``,
});
await expect(runCli(cwd, [`use`, `yarn@1.9998.9999`], true)).resolves.toMatchObject({
exitCode: 1,
stdout: /Signature does not match/,
stdout: expect.stringContaining(`Signature does not match`),
stderr: ``,
});
});
@ -1109,12 +1680,12 @@ describe(`handle integrity checks`, () => {
await xfs.mktempPromise(async cwd => {
await expect(runCli(cwd, [`yarn@1.9998.9999`, `--version`], true)).resolves.toMatchObject({
exitCode: 1,
stderr: /Mismatch hashes. Expected [a-f0-9]{128}, got [a-f0-9]{128}/,
stderr: expect.stringMatching(/Mismatch hashes. Expected [a-f0-9]{128}, got [a-f0-9]{128}/),
stdout: ``,
});
await expect(runCli(cwd, [`use`, `yarn@1.9998.9999`], true)).resolves.toMatchObject({
exitCode: 1,
stdout: /Mismatch hashes. Expected [a-f0-9]{128}, got [a-f0-9]{128}/,
stdout: expect.stringMatching(/Mismatch hashes. Expected [a-f0-9]{128}, got [a-f0-9]{128}/),
stderr: ``,
});
});

Binary file not shown.

3178
yarn.lock

File diff suppressed because it is too large Load Diff