From fd01e2f753df2c6d02210b6a33e2388dcb788bed Mon Sep 17 00:00:00 2001 From: Eric White Date: Wed, 21 Feb 2024 18:24:23 -0500 Subject: [PATCH] feat: handle workspace protocol with any semver range specifier. fixes #7578 --- .changeset/slimy-geese-knock.md | 5 ++ pkg-manifest/exportable-manifest/src/index.ts | 82 ++++++++++++++----- .../exportable-manifest/test/index.test.ts | 8 ++ 3 files changed, 75 insertions(+), 20 deletions(-) create mode 100644 .changeset/slimy-geese-knock.md diff --git a/.changeset/slimy-geese-knock.md b/.changeset/slimy-geese-knock.md new file mode 100644 index 00000000000..7e66bb51542 --- /dev/null +++ b/.changeset/slimy-geese-knock.md @@ -0,0 +1,5 @@ +--- +"@pnpm/exportable-manifest": minor +--- + +feat: handle workspace protocol with any semver range specifier. fixes #7578 diff --git a/pkg-manifest/exportable-manifest/src/index.ts b/pkg-manifest/exportable-manifest/src/index.ts index 2a926ae7f60..9f1e66709a0 100644 --- a/pkg-manifest/exportable-manifest/src/index.ts +++ b/pkg-manifest/exportable-manifest/src/index.ts @@ -29,13 +29,18 @@ export async function createExportableManifest ( if (originalManifest.scripts != null) { publishManifest.scripts = omit(PREPUBLISH_SCRIPTS, originalManifest.scripts) } - await Promise.all((['dependencies', 'devDependencies', 'optionalDependencies', 'peerDependencies'] as const).map(async (depsField) => { - const deps = await makePublishDependencies(dir, originalManifest[depsField], opts?.modulesDir) + await Promise.all((['dependencies', 'devDependencies', 'optionalDependencies'] as const).map(async (depsField) => { + const deps = await makePublishDependencies(dir, originalManifest[depsField], { modulesDir: opts?.modulesDir }) if (deps != null) { publishManifest[depsField] = deps } })) + const peerDependencies = originalManifest.peerDependencies + if (peerDependencies) { + publishManifest.peerDependencies = await makePublishDependencies(dir, peerDependencies, { modulesDir: opts?.modulesDir, convertDependencyForPublish: makePublishPeerDependency }) + } + overridePublishConfig(publishManifest) if (opts?.readmeFile) { @@ -48,16 +53,32 @@ export async function createExportableManifest ( async function makePublishDependencies ( dir: string, dependencies: Dependencies | undefined, - modulesDir?: string + { modulesDir, convertDependencyForPublish = makePublishDependency }: { + modulesDir?: string + convertDependencyForPublish?: (depName: string, depSpec: string, dir: string, modulesDir?: string) => Promise + } = {} ): Promise { if (dependencies == null) return dependencies const publishDependencies = await pMapValues( - (depSpec, depName) => makePublishDependency(depName, depSpec, dir, modulesDir), + (depSpec, depName) => convertDependencyForPublish(depName, depSpec, dir, modulesDir), dependencies ) return publishDependencies } +async function resolveManifest (depName: string, modulesDir: string): Promise { + const { manifest } = await tryReadProjectManifest(path.join(modulesDir, depName)) + if (!manifest?.name || !manifest?.version) { + throw new PnpmError( + 'CANNOT_RESOLVE_WORKSPACE_PROTOCOL', + `Cannot resolve workspace protocol of dependency "${depName}" ` + + 'because this dependency is not installed. Try running "pnpm install".' + ) + } + + return manifest +} + async function makePublishDependency (depName: string, depSpec: string, dir: string, modulesDir?: string) { if (!depSpec.startsWith('workspace:')) { return depSpec @@ -67,14 +88,7 @@ async function makePublishDependency (depName: string, depSpec: string, dir: str const versionAliasSpecParts = /^workspace:(.*?)@?([\^~*])$/.exec(depSpec) if (versionAliasSpecParts != null) { modulesDir = modulesDir ?? path.join(dir, 'node_modules') - const { manifest } = await tryReadProjectManifest(path.join(modulesDir, depName)) - if (!manifest?.version) { - throw new PnpmError( - 'CANNOT_RESOLVE_WORKSPACE_PROTOCOL', - `Cannot resolve workspace protocol of dependency "${depName}" ` + - 'because this dependency is not installed. Try running "pnpm install".' - ) - } + const manifest = await resolveManifest(depName, modulesDir) const semverRangeToken = versionAliasSpecParts[2] !== '*' ? versionAliasSpecParts[2] : '' if (depName !== manifest.name) { @@ -83,14 +97,8 @@ async function makePublishDependency (depName: string, depSpec: string, dir: str return `${semverRangeToken}${manifest.version}` } if (depSpec.startsWith('workspace:./') || depSpec.startsWith('workspace:../')) { - const { manifest } = await tryReadProjectManifest(path.join(dir, depSpec.slice(10))) - if (!manifest?.name || !manifest?.version) { - throw new PnpmError( - 'CANNOT_RESOLVE_WORKSPACE_PROTOCOL', - `Cannot resolve workspace protocol of dependency "${depName}" ` + - 'because this dependency is not installed. Try running "pnpm install".' - ) - } + const manifest = await resolveManifest(depName, path.join(dir, depSpec.slice(10))) + if (manifest.name === depName) return `${manifest.version}` return `npm:${manifest.name}@${manifest.version}` } @@ -100,3 +108,37 @@ async function makePublishDependency (depName: string, depSpec: string, dir: str } return depSpec } + +async function makePublishPeerDependency (depName: string, depSpec: string, dir: string, modulesDir?: string) { + if (!depSpec.includes('workspace:')) { + return depSpec + } + + // Dependencies with bare "*", "^", "~",">=",">","<=",< versions + const workspaceSemverRegex = /workspace:([\^~*]|>=|>|<=|<)/ + const versionAliasSpecParts = workspaceSemverRegex.exec(depSpec) + + if (versionAliasSpecParts != null) { + modulesDir = modulesDir ?? path.join(dir, 'node_modules') + const manifest = await resolveManifest(depName, modulesDir) + + const [,semverRangGroup] = versionAliasSpecParts + + const semverRangeToken = semverRangGroup !== '*' ? semverRangGroup : '' + + return depSpec.replace(workspaceSemverRegex, `${semverRangeToken}${manifest.version}`) + } + + if (depSpec.includes('workspace:./') || depSpec.includes('workspace:../')) { + const manifest = await resolveManifest(depName, path.join(dir, depSpec.replace('workspace:', ''))) + + if (manifest.name !== depName) { + throw new PnpmError('DO_NOT_SUPPORT_ALIAS_IN_PEER_DEPENDENCIES', `Workspace protocol for peerDependencies does not support aliasing of "${depName}" to "${manifest.name}"`) + } + return `${manifest.version}` + } + + depSpec = depSpec.replace('workspace:', '') + + return depSpec +} diff --git a/pkg-manifest/exportable-manifest/test/index.test.ts b/pkg-manifest/exportable-manifest/test/index.test.ts index 0f2f6f1a344..449ade6c371 100644 --- a/pkg-manifest/exportable-manifest/test/index.test.ts +++ b/pkg-manifest/exportable-manifest/test/index.test.ts @@ -91,6 +91,10 @@ test('workspace deps are replaced', async () => { baz: 'workspace:baz@^', foo: 'workspace:*', }, + peerDependencies: { + foo: 'workspace:>= || ^3.9.0', + baz: '^1.0.0 || workspace:>', + }, } preparePackages([ @@ -123,5 +127,9 @@ test('workspace deps are replaced', async () => { baz: '^1.2.3', foo: '4.5.6', }, + peerDependencies: { + baz: '^1.0.0 || >1.2.3', + foo: '>=4.5.6 || ^3.9.0', + }, }) })