feat: specifying a path to workspace packages in version specs

close #2959
PR #2972
This commit is contained in:
Zoltan Kochan
2020-11-14 14:22:30 +02:00
committed by GitHub
parent 084614f559
commit 284e95c5e4
9 changed files with 149 additions and 11 deletions

View File

@@ -0,0 +1,5 @@
---
"@pnpm/local-resolver": minor
---
Support relative path to workspace directory.

View File

@@ -0,0 +1,5 @@
---
"@pnpm/exportable-manifest": minor
---
Convert relative workspace paths to version specs.

View File

@@ -0,0 +1,5 @@
---
"@pnpm/npm-resolver": minor
---
Skip workspace protocol specs that use relative path.

View File

@@ -76,6 +76,18 @@ async function makePublishDependency (depName: string, depSpec: string, dir: str
}
return manifest.version
}
if (depSpec.startsWith('workspace:./') || depSpec.startsWith('workspace:../')) {
const { manifest } = await tryReadProjectManifest(path.join(dir, depSpec.substr(10)))
if (!manifest || !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".'
)
}
if (manifest.name === depName) return `${manifest.version}`
return `npm:${manifest.name}@${manifest.version}`
}
depSpec = depSpec.substr(10)
if (depSpec.includes('@')) {
return `npm:${depSpec}`

View File

@@ -22,7 +22,7 @@ export default function parsePref (
projectDir: string,
lockfileDir: string
): LocalPackageSpec | null {
if (pref.startsWith('link:')) {
if (pref.startsWith('link:') || pref.startsWith('workspace:')) {
return fromLocal(pref, projectDir, lockfileDir, 'directory')
}
if (pref.endsWith('.tgz') ||
@@ -54,8 +54,8 @@ function fromLocal (
type: 'file' | 'directory'
): LocalPackageSpec {
const spec = pref.replace(/\\/g, '/')
.replace(/^(file|link):[/]*([A-Za-z]:)/, '$2') // drive name paths on windows
.replace(/^(file|link):(?:[/]*([~./]))?/, '$2')
.replace(/^(file|link|workspace):[/]*([A-Za-z]:)/, '$2') // drive name paths on windows
.replace(/^(file|link|workspace):(?:[/]*([~./]))?/, '$2')
const protocol = type === 'directory' ? 'link:' : 'file:'
let fetchSpec!: string

View File

@@ -11,6 +11,15 @@ test('resolve directory', async () => {
expect(resolveResult!.resolution['type']).toEqual('directory')
})
test('resolve workspace directory', async () => {
const resolveResult = await resolveFromLocal({ pref: 'workspace:..' }, { projectDir: __dirname })
expect(resolveResult!.id).toEqual('link:..')
expect(resolveResult!.normalizedPref).toEqual('link:..')
expect(resolveResult!['manifest']!.name).toEqual('@pnpm/local-resolver')
expect(resolveResult!.resolution['directory']).toEqual('..')
expect(resolveResult!.resolution['type']).toEqual('directory')
})
test('resolve directory specified using the file: protocol', async () => {
const resolveResult = await resolveFromLocal({ pref: 'file:..' }, { projectDir: __dirname })
expect(resolveResult!.id).toEqual('link:..')

View File

@@ -114,14 +114,17 @@ async function resolveNpm (
opts: ResolveFromNpmOptions
): Promise<ResolveResult | null> {
const defaultTag = opts.defaultTag ?? 'latest'
const resolvedFromWorkspace = tryResolveFromWorkspace(wantedDependency, {
defaultTag,
projectDir: opts.projectDir,
registry: opts.registry,
workspacePackages: opts.workspacePackages,
})
if (resolvedFromWorkspace) {
return resolvedFromWorkspace
if (wantedDependency.pref?.startsWith('workspace:')) {
if (wantedDependency.pref.startsWith('workspace:.')) return null
const resolvedFromWorkspace = tryResolveFromWorkspace(wantedDependency, {
defaultTag,
projectDir: opts.projectDir,
registry: opts.registry,
workspacePackages: opts.workspacePackages,
})
if (resolvedFromWorkspace) {
return resolvedFromWorkspace
}
}
const workspacePackages = opts.alwaysTryWorkspacePackages !== false ? opts.workspacePackages : undefined
const spec = wantedDependency.pref

View File

@@ -73,6 +73,19 @@ test('resolveFromNpm()', async () => {
expect(meta['dist-tags']).toBeTruthy()
})
test('relative workspace protocol is skipped', async () => {
const storeDir = tempy.directory()
const resolve = createResolveFromNpm({
storeDir,
})
const resolveResult = await resolve({ pref: 'workspace:../is-positive' }, {
projectDir: '/home/istvan/src',
registry,
})
expect(resolveResult).toBe(null)
})
test('dry run', async (done) => {
nock(registry)
.get('/is-positive')

View File

@@ -444,6 +444,92 @@ because this dependency is not installed. Try running "pnpm install".'
})
})
test('convert specs with relative workspace protocols to regular version ranges', async () => {
preparePackages(undefined, [
{
name: 'relative-workspace-protocol-package',
version: '1.0.0',
dependencies: {
'file-type': 'workspace:../file-type',
'is-neg': 'workspace:../is-negative',
'is-positive': '1.0.0',
'lodash.delay': '~4.1.0',
},
devDependencies: {
'random-package': 'workspace:../random-package',
},
optionalDependencies: {
'lodash.deburr': 'workspace:../lodash.deburr',
},
peerDependencies: {
'random-package': 'workspace:../random-package',
},
},
{
name: 'is-negative',
version: '1.0.0',
},
{
name: 'file-type',
version: '12.0.1',
},
{
name: 'lodash.deburr',
version: '4.1.0',
},
{
name: 'lodash.delay',
version: '4.1.0',
},
{
name: 'random-package',
version: '1.2.3',
},
{
name: 'target',
version: '1.0.0',
},
])
await writeYamlFile('pnpm-workspace.yaml', { packages: ['**', '!store/**'] })
process.chdir('relative-workspace-protocol-package')
await publish.handler({
...DEFAULT_OPTS,
argv: { original: ['publish', ...CREDENTIALS] },
dir: process.cwd(),
}, [])
process.chdir('../target')
crossSpawn.sync(pnpmBin, [
'add',
'--store-dir=../store',
'relative-workspace-protocol-package',
'--no-link-workspace-packages',
`--registry=http://localhost:${REGISTRY_MOCK_PORT}`,
])
const { default: publishedManifest } = await import(path.resolve('node_modules/relative-workspace-protocol-package/package.json'))
expect(publishedManifest.dependencies).toStrictEqual({
'file-type': '12.0.1',
'is-neg': 'npm:is-negative@1.0.0',
'is-positive': '1.0.0',
'lodash.delay': '~4.1.0',
})
expect(publishedManifest.devDependencies).toStrictEqual({
'random-package': '1.2.3',
})
expect(publishedManifest.optionalDependencies).toStrictEqual({
'lodash.deburr': '4.1.0',
})
expect(publishedManifest.peerDependencies).toStrictEqual({
'random-package': '1.2.3',
})
})
test('publish: runs all the lifecycle scripts', async () => {
prepare(undefined, {
name: 'test-publish-with-scripts',