diff --git a/.yarn/versions/8dc23b49.yml b/.yarn/versions/8dc23b49.yml new file mode 100644 index 000000000000..7ce24db10de5 --- /dev/null +++ b/.yarn/versions/8dc23b49.yml @@ -0,0 +1,20 @@ +releases: + "@yarnpkg/cli": prerelease + "@yarnpkg/plugin-node-modules": prerelease + +declined: + - "@yarnpkg/plugin-constraints" + - "@yarnpkg/plugin-dlx" + - "@yarnpkg/plugin-essentials" + - "@yarnpkg/plugin-init" + - "@yarnpkg/plugin-interactive-tools" + - "@yarnpkg/plugin-npm-cli" + - "@yarnpkg/plugin-pack" + - "@yarnpkg/plugin-patch" + - "@yarnpkg/plugin-pnp" + - "@yarnpkg/plugin-stage" + - "@yarnpkg/plugin-typescript" + - "@yarnpkg/plugin-version" + - "@yarnpkg/plugin-workspace-tools" + - "@yarnpkg/core" + - "@yarnpkg/doctor" diff --git a/packages/acceptance-tests/pkg-tests-specs/sources/node-modules.test.ts b/packages/acceptance-tests/pkg-tests-specs/sources/node-modules.test.ts index 9bab862b0776..9193cb346b29 100644 --- a/packages/acceptance-tests/pkg-tests-specs/sources/node-modules.test.ts +++ b/packages/acceptance-tests/pkg-tests-specs/sources/node-modules.test.ts @@ -117,6 +117,27 @@ describe('Node_Modules', () => { ), ); + test(`should support dependency via link: protocol to a missing folder`, + makeTemporaryEnv( + { + dependencies: { + abc: `link:../abc`, + }, + }, + async ({path, run, source}) => { + await writeFile(npath.toPortablePath(`${path}/../one-fixed-dep.local/abc.js`), ''); + + await writeFile(npath.toPortablePath(`${path}/.yarnrc.yml`), ` + nodeLinker: "node-modules" + `); + + await expect(run(`install`)).resolves.toBeTruthy(); + + await expect(xfs.lstatPromise(npath.toPortablePath(`${path}/node_modules/abc`))).resolves.toBeDefined(); + }, + ), + ); + test(`should support replacement of regular dependency with portal: protocol dependency`, makeTemporaryEnv( { diff --git a/packages/plugin-node-modules/sources/NodeModulesLinker.ts b/packages/plugin-node-modules/sources/NodeModulesLinker.ts index dc0a0056b48f..4c18229358f9 100644 --- a/packages/plugin-node-modules/sources/NodeModulesLinker.ts +++ b/packages/plugin-node-modules/sources/NodeModulesLinker.ts @@ -1,17 +1,18 @@ -import {BuildDirective, MessageName, Project} from '@yarnpkg/core'; -import {Linker, LinkOptions, MinimalLinkOptions, LinkType} from '@yarnpkg/core'; -import {Locator, Package, BuildType} from '@yarnpkg/core'; -import {structUtils, Report, Manifest, miscUtils, FinalizeInstallStatus, FetchResult, DependencyMeta} from '@yarnpkg/core'; -import {VirtualFS, ZipOpenFS} from '@yarnpkg/fslib'; -import {PortablePath, npath, ppath, toFilename, Filename, xfs, FakeFS} from '@yarnpkg/fslib'; -import {getLibzipPromise} from '@yarnpkg/libzip'; -import {parseSyml} from '@yarnpkg/parsers'; -import {AbstractPnpInstaller} from '@yarnpkg/plugin-pnp'; -import {NodeModulesLocatorMap, buildLocatorMap, buildNodeModulesTree} from '@yarnpkg/pnpify'; -import {PnpSettings, makeRuntimeApi} from '@yarnpkg/pnp'; -import cmdShim from '@zkochan/cmd-shim'; -import {UsageError} from 'clipanion'; -import fs from 'fs'; +import {BuildDirective, MessageName, Project, FetchResult} from '@yarnpkg/core'; +import {Linker, LinkOptions, MinimalLinkOptions, LinkType} from '@yarnpkg/core'; +import {Locator, Package, BuildType, FinalizeInstallStatus} from '@yarnpkg/core'; +import {structUtils, Report, Manifest, miscUtils, DependencyMeta} from '@yarnpkg/core'; +import {VirtualFS, ZipOpenFS, xfs, FakeFS} from '@yarnpkg/fslib'; +import {PortablePath, npath, ppath, toFilename, Filename} from '@yarnpkg/fslib'; +import {getLibzipPromise} from '@yarnpkg/libzip'; +import {parseSyml} from '@yarnpkg/parsers'; +import {AbstractPnpInstaller} from '@yarnpkg/plugin-pnp'; +import {NodeModulesLocatorMap, buildLocatorMap} from '@yarnpkg/pnpify'; +import {buildNodeModulesTree} from '@yarnpkg/pnpify'; +import {PnpSettings, makeRuntimeApi} from '@yarnpkg/pnp'; +import cmdShim from '@zkochan/cmd-shim'; +import {UsageError} from 'clipanion'; +import fs from 'fs'; const STATE_FILE_VERSION = 1; const NODE_MODULES = `node_modules` as Filename; @@ -109,8 +110,11 @@ class NodeModulesInstaller extends AbstractPnpInstaller { const installStatuses: Array = []; - for (const [locatorStr, installRecord] of locatorMap.entries()) { - const locator = structUtils.parseLocator(locatorStr); + for (const [locatorKey, installRecord] of locatorMap.entries()) { + if (isLinkLocator(locatorKey)) + continue; + + const locator = structUtils.parseLocator(locatorKey); const pnpLocator = {name: structUtils.stringifyIdent(locator), reference: locator.reference}; const pnpEntry = pnp.getPackageInformation(pnpLocator); @@ -510,16 +514,26 @@ function refineNodeModulesRoots(locationTree: LocationTree, binSymlinks: BinSyml return {locationTree: refinedLocationTree, binSymlinks: refinedBinSymlinks}; }; +function isLinkLocator(locatorKey: LocatorKey): boolean { + let descriptor = structUtils.parseDescriptor(locatorKey); + if (structUtils.isVirtualDescriptor(descriptor)) + descriptor = structUtils.devirtualizeDescriptor(descriptor); + + return descriptor.range.startsWith('link:'); +}; + async function createBinSymlinkMap(installState: NodeModulesLocatorMap, locationTree: LocationTree, projectRoot: PortablePath, {loadManifest}: {loadManifest: (sourceLocation: PortablePath) => Promise}) { const locatorScriptMap = new Map>(); for (const [locatorKey, {locations}] of installState) { - const manifest = await loadManifest(locations[0]); + let manifest = isLinkLocator(locatorKey) ? null : await loadManifest(locations[0]); const bin = new Map(); - for (const [name, value] of manifest.bin) { - const target = ppath.join(locations[0], value); - if (value !== '' && xfs.existsSync(target)) { - bin.set(name, value); + if (manifest) { + for (const [name, value] of manifest.bin) { + const target = ppath.join(locations[0], value); + if (value !== '' && xfs.existsSync(target)) { + bin.set(name, value); + } } }