diff --git a/.github/workflows/integration-workflow.yml b/.github/workflows/integration-workflow.yml index 27ad75825cf1..a9f40fecc869 100644 --- a/.github/workflows/integration-workflow.yml +++ b/.github/workflows/integration-workflow.yml @@ -32,6 +32,11 @@ jobs: with: node-version: ${{ env.node-version }} + - name: Enable Corepack + run: | + npm install -g corepack + corepack enable + - name: 'Check that the Yarn files don''t change on new installs (fix w/ "yarn install")' run: | node ./scripts/run-yarn.js --immutable --immutable-cache @@ -254,6 +259,11 @@ jobs: with: node-version: ${{matrix.node}}.x + - name: Enable Corepack + run: | + npm install -g corepack + corepack enable + - uses: actions/download-artifact@v4 with: name: yarn-artifacts diff --git a/.yarn/versions/4d0fa20f.yml b/.yarn/versions/4d0fa20f.yml new file mode 100644 index 000000000000..2ead6ddb739c --- /dev/null +++ b/.yarn/versions/4d0fa20f.yml @@ -0,0 +1,35 @@ +releases: + "@yarnpkg/cli": minor + "@yarnpkg/core": minor + "@yarnpkg/plugin-git": minor + "@yarnpkg/plugin-github": minor + +declined: + - "@yarnpkg/plugin-compat" + - "@yarnpkg/plugin-constraints" + - "@yarnpkg/plugin-dlx" + - "@yarnpkg/plugin-essentials" + - "@yarnpkg/plugin-exec" + - "@yarnpkg/plugin-file" + - "@yarnpkg/plugin-http" + - "@yarnpkg/plugin-init" + - "@yarnpkg/plugin-interactive-tools" + - "@yarnpkg/plugin-jsr" + - "@yarnpkg/plugin-link" + - "@yarnpkg/plugin-nm" + - "@yarnpkg/plugin-npm" + - "@yarnpkg/plugin-npm-cli" + - "@yarnpkg/plugin-pack" + - "@yarnpkg/plugin-patch" + - "@yarnpkg/plugin-pnp" + - "@yarnpkg/plugin-pnpm" + - "@yarnpkg/plugin-stage" + - "@yarnpkg/plugin-typescript" + - "@yarnpkg/plugin-version" + - "@yarnpkg/plugin-workspace-tools" + - "@yarnpkg/builder" + - "@yarnpkg/doctor" + - "@yarnpkg/extensions" + - "@yarnpkg/nm" + - "@yarnpkg/pnpify" + - "@yarnpkg/sdks" diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e96b54ffe13..0d5a2967b33e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ Yarn now accepts sponsors! Please take a look at our [OpenCollective](https://op Features in `master` can be tried out by running `yarn set version from sources` in your project. ::: +- Yarn can now install git repositories that use pnpm (workspaces are supported too if pnpm@>=6.x is installed on the system). + - Fixes `preferInteractive` forcing interactive mode in non-TTY environments. - `node-modules` linker now honors user-defined symlinks for `/node_modules` directories - `node-modules` linker supports hoisting into inner workspaces that are parents of other workspaces diff --git a/packages/acceptance-tests/pkg-tests-fixtures/repositories/pnpm-project.git/HEAD b/packages/acceptance-tests/pkg-tests-fixtures/repositories/pnpm-project.git/HEAD new file mode 100644 index 000000000000..cb089cd89a7d --- /dev/null +++ b/packages/acceptance-tests/pkg-tests-fixtures/repositories/pnpm-project.git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/packages/acceptance-tests/pkg-tests-fixtures/repositories/pnpm-project.git/config b/packages/acceptance-tests/pkg-tests-fixtures/repositories/pnpm-project.git/config new file mode 100644 index 000000000000..6b92557a9132 --- /dev/null +++ b/packages/acceptance-tests/pkg-tests-fixtures/repositories/pnpm-project.git/config @@ -0,0 +1,4 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false diff --git a/packages/acceptance-tests/pkg-tests-fixtures/repositories/pnpm-project.git/description b/packages/acceptance-tests/pkg-tests-fixtures/repositories/pnpm-project.git/description new file mode 100644 index 000000000000..498b267a8c78 --- /dev/null +++ b/packages/acceptance-tests/pkg-tests-fixtures/repositories/pnpm-project.git/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/packages/acceptance-tests/pkg-tests-fixtures/repositories/pnpm-project.git/hooks/post-update b/packages/acceptance-tests/pkg-tests-fixtures/repositories/pnpm-project.git/hooks/post-update new file mode 100644 index 000000000000..ec17ec1939b7 --- /dev/null +++ b/packages/acceptance-tests/pkg-tests-fixtures/repositories/pnpm-project.git/hooks/post-update @@ -0,0 +1,8 @@ +#!/bin/sh +# +# An example hook script to prepare a packed repository for use over +# dumb transports. +# +# To enable this hook, rename this file to "post-update". + +exec git update-server-info diff --git a/packages/acceptance-tests/pkg-tests-fixtures/repositories/pnpm-project.git/info/exclude b/packages/acceptance-tests/pkg-tests-fixtures/repositories/pnpm-project.git/info/exclude new file mode 100644 index 000000000000..a5196d1be8fb --- /dev/null +++ b/packages/acceptance-tests/pkg-tests-fixtures/repositories/pnpm-project.git/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/packages/acceptance-tests/pkg-tests-fixtures/repositories/pnpm-project.git/info/refs b/packages/acceptance-tests/pkg-tests-fixtures/repositories/pnpm-project.git/info/refs new file mode 100644 index 000000000000..d229466afc8e --- /dev/null +++ b/packages/acceptance-tests/pkg-tests-fixtures/repositories/pnpm-project.git/info/refs @@ -0,0 +1,3 @@ +57d05601ca0e00e0047b2ea8a0f30b0dc760e40b refs/heads/master +57d05601ca0e00e0047b2ea8a0f30b0dc760e40b refs/remotes/origin/HEAD +57d05601ca0e00e0047b2ea8a0f30b0dc760e40b refs/remotes/origin/master diff --git a/packages/acceptance-tests/pkg-tests-fixtures/repositories/pnpm-project.git/objects/04/d06cc440ad490ca4744395bb2c431b70b57898 b/packages/acceptance-tests/pkg-tests-fixtures/repositories/pnpm-project.git/objects/04/d06cc440ad490ca4744395bb2c431b70b57898 new file mode 100644 index 000000000000..152e7cffd729 --- /dev/null +++ b/packages/acceptance-tests/pkg-tests-fixtures/repositories/pnpm-project.git/objects/04/d06cc440ad490ca4744395bb2c431b70b57898 @@ -0,0 +1 @@ +x51 agNA:+ۀv(4-b|/%hTd!L(6sd%3ؿ̑G_;,QGH'Yp؇~6l(2ÉS6:;7 sq{Yr |6-ױz](i7mGg={a6Ө5*|5F~{@*x*7 qF_.VtΑmhk/J w/{0Q] ﯝ7"J(+ ..yBPNG Y+r1UENoCޙ$i- "PgBd2t-c,i4C)?K ɷIJ1jtg+q;뙐&,ዾFc6Ju(;{WWt2Q\ Vzٰ^b1=6"ϵE c`/+M9_]òsm͝qD.>C^J5);2lU?0*.prwbD׋"N%3}TB $Χ̨<nPCth +~h~e7zu[s;%u`n/r1,}[!b-?ӗDeeydN˥ %g+uZ%^bG d7{);F+;#o*Hbƾ;#5~w=_O \ No newline at end of file diff --git a/packages/acceptance-tests/pkg-tests-fixtures/repositories/pnpm-workspaces.git/objects/dc/bdfa141295731efd57481923e80604520f0a35 b/packages/acceptance-tests/pkg-tests-fixtures/repositories/pnpm-workspaces.git/objects/dc/bdfa141295731efd57481923e80604520f0a35 new file mode 100644 index 000000000000..aed2a2813eeb Binary files /dev/null and b/packages/acceptance-tests/pkg-tests-fixtures/repositories/pnpm-workspaces.git/objects/dc/bdfa141295731efd57481923e80604520f0a35 differ diff --git a/packages/acceptance-tests/pkg-tests-fixtures/repositories/pnpm-workspaces.git/objects/ee/09b194db8e42695fa9dca484a35715916cd089 b/packages/acceptance-tests/pkg-tests-fixtures/repositories/pnpm-workspaces.git/objects/ee/09b194db8e42695fa9dca484a35715916cd089 new file mode 100644 index 000000000000..78b35f36233f Binary files /dev/null and b/packages/acceptance-tests/pkg-tests-fixtures/repositories/pnpm-workspaces.git/objects/ee/09b194db8e42695fa9dca484a35715916cd089 differ diff --git a/packages/acceptance-tests/pkg-tests-fixtures/repositories/pnpm-workspaces.git/objects/info/packs b/packages/acceptance-tests/pkg-tests-fixtures/repositories/pnpm-workspaces.git/objects/info/packs new file mode 100644 index 000000000000..8b137891791f --- /dev/null +++ b/packages/acceptance-tests/pkg-tests-fixtures/repositories/pnpm-workspaces.git/objects/info/packs @@ -0,0 +1 @@ + diff --git a/packages/acceptance-tests/pkg-tests-fixtures/repositories/pnpm-workspaces.git/packed-refs b/packages/acceptance-tests/pkg-tests-fixtures/repositories/pnpm-workspaces.git/packed-refs new file mode 100644 index 000000000000..e8f55fb755db --- /dev/null +++ b/packages/acceptance-tests/pkg-tests-fixtures/repositories/pnpm-workspaces.git/packed-refs @@ -0,0 +1,2 @@ +# pack-refs with: peeled fully-peeled sorted +d16bc461ce9bd42690625105cd50ae5936ebce34 refs/remotes/origin/master diff --git a/packages/acceptance-tests/pkg-tests-fixtures/repositories/pnpm-workspaces.git/refs/heads/master b/packages/acceptance-tests/pkg-tests-fixtures/repositories/pnpm-workspaces.git/refs/heads/master new file mode 100644 index 000000000000..bc6a25f9e341 --- /dev/null +++ b/packages/acceptance-tests/pkg-tests-fixtures/repositories/pnpm-workspaces.git/refs/heads/master @@ -0,0 +1 @@ +d16bc461ce9bd42690625105cd50ae5936ebce34 diff --git a/packages/acceptance-tests/pkg-tests-specs/sources/protocols/git.test.ts b/packages/acceptance-tests/pkg-tests-specs/sources/protocols/git.test.ts index 4a5529d151f3..eedaaae67354 100644 --- a/packages/acceptance-tests/pkg-tests-specs/sources/protocols/git.test.ts +++ b/packages/acceptance-tests/pkg-tests-specs/sources/protocols/git.test.ts @@ -196,6 +196,26 @@ describe(`Protocols`, () => { ), ); + test( + `it should use pnpm to setup pnpm repositories`, + makeTemporaryEnv( + { + dependencies: { + [`pnpm-project`]: tests.startPackageServer().then(url => `${url}/repositories/pnpm-project.git`), + }, + }, + async ({path, run, source}) => { + await expect(run(`install`, { + env: { + NODE_ENV: `production`, + }, + })).resolves.toBeTruthy(); + + await expect(source(`require('pnpm-project')`)).resolves.toMatch(/\bpnpm\/[0-9]+/); + }, + ), + ); + test( `it should guarantee that all dependencies will be installed when using npm to setup npm repositories`, makeTemporaryEnv( @@ -263,6 +283,31 @@ describe(`Protocols`, () => { ), ); + test( + `it should support installing specific workspaces from pnpm repositories`, + makeTemporaryEnv( + { + dependencies: { + [`pkg-a`]: tests.startPackageServer().then(url => `${url}/repositories/pnpm-workspaces.git#workspace=pkg-a`), + [`pkg-b`]: tests.startPackageServer().then(url => `${url}/repositories/pnpm-workspaces.git#workspace=pkg-b`), + }, + }, + async ({path, run, source}) => { + await run(`install`); + + await expect(source(`require('pkg-a/package.json')`)).resolves.toMatchObject({ + name: `pkg-a`, + version: `1.0.0`, + }); + + await expect(source(`require('pkg-b/package.json')`)).resolves.toMatchObject({ + name: `pkg-b`, + version: `1.0.0`, + }); + }, + ), + ); + test( `it should not use Corepack to fetch Yarn Classic`, makeTemporaryEnv( diff --git a/packages/yarnpkg-core/sources/scriptUtils.ts b/packages/yarnpkg-core/sources/scriptUtils.ts index 2dcd7e59e6db..2a2edbcf4952 100644 --- a/packages/yarnpkg-core/sources/scriptUtils.ts +++ b/packages/yarnpkg-core/sources/scriptUtils.ts @@ -398,11 +398,47 @@ export async function prepareExternalProject(cwd: PortablePath, outputPath: Port return 0; }], + + [PackageManager.Pnpm, async () => { + // Remove environment variables that limit the install to just production dependencies + delete env.NODE_ENV; + + const install = await execUtils.pipevp(`pnpm`, [`install`], {cwd, env, stdin, stdout, stderr, end: execUtils.EndStrategy.ErrorCode}); + if (install.code !== 0) + return install.code; + + const packStream = new PassThrough(); + const packPromise = miscUtils.bufferStream(packStream); + + packStream.pipe(stdout); + + // It seems that pnpm doesn't support specifying the pack output path, + // so we have to extract the stdout on top of forking it to the logs. + + // - `pnpm pack` doesn't support the `--filter` flag so we have to use `pnpm exec` + // - We have to use the `--pack-destination` flag because otherwise pnpm generates the tarball inside the workspace cwd + // - Only pnpm@>=6.x supports the `--pack-destination` flag (and previous versions throw an error) + const packArgs = workspace !== null + ? [`--filter`, workspace, `exec`, `pnpm`, `pack`, `--pack-destination`, npath.fromPortablePath(cwd)] + : [`pack`]; + + const pack = await execUtils.pipevp(`pnpm`, packArgs, {cwd, env, stdin, stdout: packStream, stderr}); + if (pack.code !== 0) + return pack.code; + + const packOutput = (await packPromise).toString().trim().replace(/^.*\n/s, ``); + const packTarget = ppath.resolve(cwd, npath.toPortablePath(packOutput)); + + // Only then can we move the pack to its rightful location + await xfs.renamePromise(packTarget, outputPath); + + return 0; + }], ]); const workflow = workflows.get(effectivePackageManager); if (typeof workflow === `undefined`) - throw new Error(`Assertion failed: Unsupported workflow`); + throw new Error(`Assertion failed: Unsupported workflow: "${effectivePackageManager}"`); const code = await workflow(); if (code === 0 || typeof code === `undefined`)