mirror of
https://github.com/pnpm/pnpm.git
synced 2025-12-24 15:48:06 -05:00
fix: install failure on pnpm deploy when using catalogs (#8298)
* fix: install failure on pnpm deploy when using catalogs * test: add test for pnpm deploy when using catalogs * chore: changeset
This commit is contained in:
7
.changeset/calm-files-hide.md
Normal file
7
.changeset/calm-files-hide.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@pnpm/plugin-commands-installation": patch
|
||||
"@pnpm/plugin-commands-deploy": patch
|
||||
pnpm: patch
|
||||
---
|
||||
|
||||
The `pnpm deploy` command now supports the [`catalog:` protocol](https://pnpm.io/catalogs).
|
||||
@@ -252,6 +252,7 @@ export type InstallCommandOptions = Pick<Config,
|
||||
| 'autoInstallPeers'
|
||||
| 'bail'
|
||||
| 'bin'
|
||||
| 'catalogs'
|
||||
| 'cliOptions'
|
||||
| 'dedupeDirectDeps'
|
||||
| 'dedupePeerDependents'
|
||||
|
||||
6
pnpm-lock.yaml
generated
6
pnpm-lock.yaml
generated
@@ -5617,6 +5617,12 @@ importers:
|
||||
|
||||
releasing/plugin-commands-deploy:
|
||||
dependencies:
|
||||
'@pnpm/catalogs.resolver':
|
||||
specifier: workspace:*
|
||||
version: link:../../catalogs/resolver
|
||||
'@pnpm/catalogs.types':
|
||||
specifier: workspace:*
|
||||
version: link:../../catalogs/types
|
||||
'@pnpm/cli-utils':
|
||||
specifier: workspace:*
|
||||
version: link:../../cli/cli-utils
|
||||
|
||||
@@ -42,6 +42,8 @@
|
||||
"@pnpm/workspace.filter-packages-from-dir": "workspace:*"
|
||||
},
|
||||
"dependencies": {
|
||||
"@pnpm/catalogs.resolver": "workspace:*",
|
||||
"@pnpm/catalogs.types": "workspace:*",
|
||||
"@pnpm/cli-utils": "workspace:*",
|
||||
"@pnpm/common-cli-options-help": "workspace:*",
|
||||
"@pnpm/directory-fetcher": "workspace:*",
|
||||
|
||||
@@ -11,6 +11,7 @@ import rimraf from '@zkochan/rimraf'
|
||||
import renderHelp from 'render-help'
|
||||
import { deployHook } from './deployHook'
|
||||
import { logger } from '@pnpm/logger'
|
||||
import { deployCatalogHook } from './deployCatalogHook'
|
||||
|
||||
export const shorthands = install.shorthands
|
||||
|
||||
@@ -101,6 +102,7 @@ export async function handler (
|
||||
readPackage: [
|
||||
...(opts.hooks?.readPackage ?? []),
|
||||
deployHook,
|
||||
deployCatalogHook.bind(null, opts.catalogs ?? {}),
|
||||
],
|
||||
},
|
||||
frozenLockfile: false,
|
||||
|
||||
51
releasing/plugin-commands-deploy/src/deployCatalogHook.ts
Normal file
51
releasing/plugin-commands-deploy/src/deployCatalogHook.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { matchCatalogResolveResult, resolveFromCatalog } from '@pnpm/catalogs.resolver'
|
||||
import { type Catalogs } from '@pnpm/catalogs.types'
|
||||
import { type ProjectManifest, DEPENDENCIES_FIELDS } from '@pnpm/types'
|
||||
|
||||
/**
|
||||
* Teach the `pnpm deploy` command how to interpret the catalog: protocol.
|
||||
*
|
||||
* This is a hack to work around a design problem between pnpm deploy and
|
||||
* catalogs.
|
||||
*
|
||||
* - The catalog protocol is intentionally only allowed to be used by
|
||||
* importers. External dependencies cannot use the catalog: protocol by
|
||||
* design.
|
||||
* - When using pnpm deploy, dependency workspace packages aren't considered
|
||||
* "importers".
|
||||
*
|
||||
* To work around the conflict of designs above, this readPackage hook exists to
|
||||
* make catalogs usable by non-importers specifically on pnpm deploy.
|
||||
*
|
||||
* Unfortunately this introduces a correctness issue where the catalog: protocol
|
||||
* is replaced for all packages (even external dependencies), not just packages
|
||||
* within the pnpm workspace. This caveat is somewhat mitigated by the fact that
|
||||
* a regular pnpm install would still fail before users could pnpm deploy a
|
||||
* project.
|
||||
*/
|
||||
export function deployCatalogHook (catalogs: Catalogs, pkg: ProjectManifest): ProjectManifest {
|
||||
for (const depField of DEPENDENCIES_FIELDS) {
|
||||
const depsBlock = pkg[depField]
|
||||
if (depsBlock == null) {
|
||||
continue
|
||||
}
|
||||
|
||||
for (const [alias, pref] of Object.entries(depsBlock)) {
|
||||
const resolveResult = resolveFromCatalog(catalogs, { alias, pref })
|
||||
|
||||
matchCatalogResolveResult(resolveResult, {
|
||||
unused: () => {},
|
||||
|
||||
misconfiguration: (result) => {
|
||||
throw result.error
|
||||
},
|
||||
|
||||
found: (result) => {
|
||||
depsBlock[alias] = result.resolution.specifier
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return pkg
|
||||
}
|
||||
@@ -213,3 +213,53 @@ test('deploy with dedupePeerDependents=true ignores the value of dedupePeerDepen
|
||||
project.has('is-positive')
|
||||
expect(fs.existsSync('sub-dir/deploy')).toBe(false)
|
||||
})
|
||||
|
||||
// Regression test for https://github.com/pnpm/pnpm/issues/8297 (pnpm deploy doesn't replace catalog: protocol)
|
||||
test('deploy works when workspace packages use catalog protocol', async () => {
|
||||
preparePackages([
|
||||
{
|
||||
name: 'project-1',
|
||||
dependencies: {
|
||||
'project-2': 'workspace:*',
|
||||
'is-positive': 'catalog:',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'project-2',
|
||||
dependencies: {
|
||||
'project-3': 'workspace:*',
|
||||
'is-positive': 'catalog:',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'project-3',
|
||||
dependencies: {
|
||||
'project-3': 'workspace:*',
|
||||
'is-positive': 'catalog:',
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
const { allProjects, selectedProjectsGraph } = await filterPackagesFromDir(process.cwd(), [{ namePattern: 'project-1' }])
|
||||
|
||||
await deploy.handler({
|
||||
...DEFAULT_OPTS,
|
||||
allProjects,
|
||||
catalogs: {
|
||||
default: {
|
||||
'is-positive': '1.0.0',
|
||||
},
|
||||
},
|
||||
dir: process.cwd(),
|
||||
dev: false,
|
||||
production: true,
|
||||
recursive: true,
|
||||
selectedProjectsGraph,
|
||||
sharedWorkspaceLockfile: true,
|
||||
lockfileDir: process.cwd(),
|
||||
workspaceDir: process.cwd(),
|
||||
}, ['deploy'])
|
||||
|
||||
// Make sure the is-positive cataloged dependency was actually installed.
|
||||
expect(fs.existsSync('deploy/node_modules/.pnpm/project-3@file+project-3/node_modules/is-positive')).toBeTruthy()
|
||||
})
|
||||
|
||||
37
releasing/plugin-commands-deploy/test/deployCatalogHook.ts
Normal file
37
releasing/plugin-commands-deploy/test/deployCatalogHook.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { deployCatalogHook } from '../src/deployCatalogHook'
|
||||
|
||||
test('deployCatalogHook()', () => {
|
||||
const catalogs = {
|
||||
default: {
|
||||
a: '^1.0.0',
|
||||
},
|
||||
foo: {
|
||||
b: '^2.0.0',
|
||||
},
|
||||
bar: {
|
||||
c: '^3.0.0',
|
||||
},
|
||||
}
|
||||
|
||||
expect(deployCatalogHook(catalogs, {
|
||||
dependencies: {
|
||||
a: 'catalog:',
|
||||
},
|
||||
devDependencies: {
|
||||
b: 'catalog:foo',
|
||||
},
|
||||
optionalDependencies: {
|
||||
c: 'catalog:bar',
|
||||
},
|
||||
})).toStrictEqual({
|
||||
dependencies: {
|
||||
a: '^1.0.0',
|
||||
},
|
||||
devDependencies: {
|
||||
b: '^2.0.0',
|
||||
},
|
||||
optionalDependencies: {
|
||||
c: '^3.0.0',
|
||||
},
|
||||
})
|
||||
})
|
||||
@@ -15,6 +15,12 @@
|
||||
{
|
||||
"path": "../../__utils__/prepare"
|
||||
},
|
||||
{
|
||||
"path": "../../catalogs/resolver"
|
||||
},
|
||||
{
|
||||
"path": "../../catalogs/types"
|
||||
},
|
||||
{
|
||||
"path": "../../cli/cli-utils"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user