mirror of
https://github.com/pnpm/pnpm.git
synced 2026-03-31 13:32:18 -04:00
* feat: workspace:^ and workspace:~ aliases (#3116) * fix: apply suggestions and add changeset * fix(npm-resolver): workspace aliases * refactor: implement suggestions * refactor: use only one regex
This commit is contained in:
committed by
GitHub
parent
25cea85567
commit
85fb21a837
8
.changeset/giant-carrots-heal.md
Normal file
8
.changeset/giant-carrots-heal.md
Normal file
@@ -0,0 +1,8 @@
|
||||
---
|
||||
"@pnpm/exportable-manifest": minor
|
||||
"@pnpm/npm-resolver": minor
|
||||
"@pnpm/plugin-commands-publishing": minor
|
||||
"@pnpm/resolve-workspace-range": minor
|
||||
---
|
||||
|
||||
Add support for workspace:^ and workspace:~ aliases
|
||||
@@ -72,7 +72,10 @@ async function makePublishDependency (depName: string, depSpec: string, dir: str
|
||||
if (!depSpec.startsWith('workspace:')) {
|
||||
return depSpec
|
||||
}
|
||||
if (depSpec === 'workspace:*' || depSpec.endsWith('@*')) {
|
||||
|
||||
// Dependencies with bare "*", "^" and "~" versions
|
||||
const versionAliasSpecParts = /^workspace:([^@]+@)?([\^~*])$/.exec(depSpec)
|
||||
if (versionAliasSpecParts != null) {
|
||||
const { manifest } = await tryReadProjectManifest(path.join(dir, 'node_modules', depName))
|
||||
if ((manifest == null) || !manifest.version) {
|
||||
throw new PnpmError(
|
||||
@@ -81,10 +84,12 @@ async function makePublishDependency (depName: string, depSpec: string, dir: str
|
||||
'because this dependency is not installed. Try running "pnpm install".'
|
||||
)
|
||||
}
|
||||
|
||||
const semverRangeToken = versionAliasSpecParts[2] !== '*' ? versionAliasSpecParts[2] : ''
|
||||
if (depName !== manifest.name) {
|
||||
return `npm:${manifest.name!}@${manifest.version}`
|
||||
return `npm:${manifest.name!}@${semverRangeToken}${manifest.version}`
|
||||
}
|
||||
return manifest.version
|
||||
return `${semverRangeToken}${manifest.version}`
|
||||
}
|
||||
if (depSpec.startsWith('workspace:./') || depSpec.startsWith('workspace:../')) {
|
||||
const { manifest } = await tryReadProjectManifest(path.join(dir, depSpec.substr(10)))
|
||||
|
||||
@@ -191,6 +191,23 @@ async function resolveNpm (
|
||||
}
|
||||
}
|
||||
|
||||
function workspacePrefToNpm (workspacePref: string): string {
|
||||
const prefParts = /^workspace:([^@]+@)?(.*)$/.exec(workspacePref)
|
||||
if (prefParts == null) {
|
||||
throw new Error(`Invalid workspace spec: ${workspacePref}`)
|
||||
}
|
||||
const [workspacePkgAlias, workspaceVersion] = prefParts.slice(1)
|
||||
|
||||
const pkgAliasPart = workspacePkgAlias != null && workspacePkgAlias
|
||||
? `npm:${workspacePkgAlias}`
|
||||
: ''
|
||||
const versionPart = workspaceVersion === '^' || workspaceVersion === '~'
|
||||
? '*'
|
||||
: workspaceVersion
|
||||
|
||||
return `${pkgAliasPart}${versionPart}`
|
||||
}
|
||||
|
||||
function tryResolveFromWorkspace (
|
||||
wantedDependency: WantedDependency,
|
||||
opts: {
|
||||
@@ -203,10 +220,8 @@ function tryResolveFromWorkspace (
|
||||
if (!wantedDependency.pref?.startsWith('workspace:')) {
|
||||
return null
|
||||
}
|
||||
let pref = wantedDependency.pref.substr(10)
|
||||
if (pref.includes('@', 1)) {
|
||||
pref = `npm:${pref}`
|
||||
}
|
||||
const pref = workspacePrefToNpm(wantedDependency.pref)
|
||||
|
||||
const spec = parsePref(pref, wantedDependency.alias, opts.defaultTag, opts.registry)
|
||||
if (spec == null) throw new Error(`Invalid workspace: spec (${wantedDependency.pref})`)
|
||||
if (opts.workspacePackages == null) {
|
||||
|
||||
@@ -1547,3 +1547,67 @@ test('request to a package with malformed metadata', async () => {
|
||||
new PnpmError('MALFORMED_METADATA', 'Received malformed metadata for "code-snippet"')
|
||||
)
|
||||
})
|
||||
|
||||
test('resolve workspace:^', async () => {
|
||||
const storeDir = tempy.directory()
|
||||
const resolve = createResolveFromNpm({
|
||||
storeDir,
|
||||
})
|
||||
const resolveResult = await resolve({ alias: 'is-positive', pref: 'workspace:^' }, {
|
||||
projectDir: '/home/istvan/src',
|
||||
registry,
|
||||
workspacePackages: {
|
||||
'is-positive': {
|
||||
'1.0.0': {
|
||||
dir: '/home/istvan/src/is-positive',
|
||||
manifest: {
|
||||
name: 'is-positive',
|
||||
version: '1.0.0',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(resolveResult!.resolvedVia).toBe('local-filesystem')
|
||||
expect(resolveResult!.id).toBe('link:is-positive')
|
||||
expect(resolveResult!.resolution).toStrictEqual({
|
||||
directory: '/home/istvan/src/is-positive',
|
||||
type: 'directory',
|
||||
})
|
||||
expect(resolveResult!.manifest).toBeTruthy()
|
||||
expect(resolveResult!.manifest!.name).toBe('is-positive')
|
||||
expect(resolveResult!.manifest!.version).toBe('1.0.0')
|
||||
})
|
||||
|
||||
test('resolve workspace:~', async () => {
|
||||
const storeDir = tempy.directory()
|
||||
const resolve = createResolveFromNpm({
|
||||
storeDir,
|
||||
})
|
||||
const resolveResult = await resolve({ alias: 'is-positive', pref: 'workspace:~' }, {
|
||||
projectDir: '/home/istvan/src',
|
||||
registry,
|
||||
workspacePackages: {
|
||||
'is-positive': {
|
||||
'1.0.0': {
|
||||
dir: '/home/istvan/src/is-positive',
|
||||
manifest: {
|
||||
name: 'is-positive',
|
||||
version: '1.0.0',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(resolveResult!.resolvedVia).toBe('local-filesystem')
|
||||
expect(resolveResult!.id).toBe('link:is-positive')
|
||||
expect(resolveResult!.resolution).toStrictEqual({
|
||||
directory: '/home/istvan/src/is-positive',
|
||||
type: 'directory',
|
||||
})
|
||||
expect(resolveResult!.manifest).toBeTruthy()
|
||||
expect(resolveResult!.manifest!.name).toBe('is-positive')
|
||||
expect(resolveResult!.manifest!.version).toBe('1.0.0')
|
||||
})
|
||||
|
||||
@@ -351,12 +351,16 @@ test('convert specs with workspace protocols to regular version ranges', async (
|
||||
'is-positive': '1.0.0',
|
||||
'lodash.delay': '~4.1.0',
|
||||
odd: 'workspace:is-odd@*',
|
||||
rd: 'workspace:ramda@^',
|
||||
'word-wrap': 'workspace:~',
|
||||
},
|
||||
devDependencies: {
|
||||
'random-package': 'workspace:^1.2.3',
|
||||
through: 'workspace:^',
|
||||
},
|
||||
optionalDependencies: {
|
||||
'lodash.deburr': 'workspace:^4.1.0',
|
||||
ww: 'workspace:wordwrap@~',
|
||||
},
|
||||
peerDependencies: {
|
||||
'random-package': 'workspace:*',
|
||||
@@ -394,6 +398,22 @@ test('convert specs with workspace protocols to regular version ranges', async (
|
||||
name: 'target',
|
||||
version: '1.0.0',
|
||||
},
|
||||
{
|
||||
name: 'ramda',
|
||||
version: '0.1.0',
|
||||
},
|
||||
{
|
||||
name: 'word-wrap',
|
||||
version: '0.1.0',
|
||||
},
|
||||
{
|
||||
name: 'through',
|
||||
version: '0.0.1',
|
||||
},
|
||||
{
|
||||
name: 'wordwrap',
|
||||
version: '0.0.1',
|
||||
},
|
||||
])
|
||||
|
||||
await writeYamlFile('pnpm-workspace.yaml', { packages: ['**', '!store/**'] })
|
||||
@@ -417,6 +437,7 @@ test('convert specs with workspace protocols to regular version ranges', async (
|
||||
crossSpawn.sync(pnpmBin, ['multi', 'install', '--store-dir=store', `--registry=http://localhost:${REGISTRY_MOCK_PORT}`])
|
||||
|
||||
process.chdir('workspace-protocol-package')
|
||||
|
||||
await publish.handler({
|
||||
...DEFAULT_OPTS,
|
||||
argv: { original: ['publish', ...CREDENTIALS] },
|
||||
@@ -435,12 +456,16 @@ test('convert specs with workspace protocols to regular version ranges', async (
|
||||
'lodash.delay': '~4.1.0',
|
||||
even: 'npm:is-even@^1.0.0',
|
||||
odd: 'npm:is-odd@1.0.0',
|
||||
rd: 'npm:ramda@^0.1.0',
|
||||
'word-wrap': '~0.1.0',
|
||||
})
|
||||
expect(publishedManifest.devDependencies).toStrictEqual({
|
||||
'random-package': '^1.2.3',
|
||||
through: '^0.0.1',
|
||||
})
|
||||
expect(publishedManifest.optionalDependencies).toStrictEqual({
|
||||
'lodash.deburr': '^4.1.0',
|
||||
ww: 'npm:wordwrap@~0.0.1',
|
||||
})
|
||||
expect(publishedManifest.peerDependencies).toStrictEqual({
|
||||
'random-package': '1.2.3',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import semver from 'semver'
|
||||
|
||||
export default function (range: string, versions: string[]) {
|
||||
if (range === '*') {
|
||||
if (range === '*' || range === '^' || range === '~') {
|
||||
return semver.maxSatisfying(versions, '*', {
|
||||
includePrerelease: true,
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user