mirror of
https://github.com/pnpm/pnpm.git
synced 2025-12-23 23:29:17 -05:00
feat: add disallow-workspace-cycles option to error on cyclic dependencies (#7144)
* feat: add `no-cyclic` option to error on cyclic dependencies * refactor: rename no-cyclic to disallow-workspace-cycles
This commit is contained in:
committed by
GitHub
parent
b0df222db0
commit
832e288263
8
.changeset/old-snails-behave.md
Normal file
8
.changeset/old-snails-behave.md
Normal file
@@ -0,0 +1,8 @@
|
||||
---
|
||||
"@pnpm/config": minor
|
||||
"@pnpm/core": minor
|
||||
"@pnpm/plugin-commands-installation": minor
|
||||
"pnpm": minor
|
||||
---
|
||||
|
||||
Add `disallow-workspace-cycles` option to error instead of warn about cyclic dependencies
|
||||
@@ -160,6 +160,7 @@ export interface Config {
|
||||
dedupePeerDependents?: boolean
|
||||
patchesDir?: string
|
||||
ignoreWorkspaceCycles?: boolean
|
||||
disallowWorkspaceCycles?: boolean
|
||||
packGzipLevel?: number
|
||||
|
||||
registries: Registries
|
||||
|
||||
@@ -45,6 +45,7 @@ export const types = Object.assign({
|
||||
'dedupe-direct-deps': Boolean,
|
||||
dev: [null, true],
|
||||
dir: String,
|
||||
'disallow-workspace-cycles': Boolean,
|
||||
'enable-modules-dir': Boolean,
|
||||
'enable-pre-post-scripts': Boolean,
|
||||
'exclude-links-from-lockfile': Boolean,
|
||||
@@ -193,6 +194,7 @@ export async function getConfig (
|
||||
'deploy-all-files': false,
|
||||
'dedupe-peer-dependents': true,
|
||||
'dedupe-direct-deps': false,
|
||||
'disallow-workspace-cycles': false,
|
||||
'enable-modules-dir': true,
|
||||
'exclude-links-from-lockfile': false,
|
||||
'extend-node-path': true,
|
||||
|
||||
@@ -105,6 +105,7 @@ export interface StrictInstallOptions {
|
||||
resolutionMode: 'highest' | 'time-based' | 'lowest-direct'
|
||||
resolvePeersFromWorkspaceRoot: boolean
|
||||
ignoreWorkspaceCycles: boolean
|
||||
disallowWorkspaceCycles: boolean
|
||||
|
||||
publicHoistPattern: string[] | undefined
|
||||
hoistPattern: string[] | undefined
|
||||
@@ -223,6 +224,7 @@ const defaults = (opts: InstallOptions) => {
|
||||
resolvePeersFromWorkspaceRoot: true,
|
||||
extendNodePath: true,
|
||||
ignoreWorkspaceCycles: false,
|
||||
disallowWorkspaceCycles: false,
|
||||
excludeLinksFromLockfile: false,
|
||||
} as StrictInstallOptions
|
||||
}
|
||||
|
||||
@@ -98,6 +98,7 @@ export type ImportCommandOptions = Pick<Config,
|
||||
| 'selectedProjectsGraph'
|
||||
| 'workspaceDir'
|
||||
| 'ignoreWorkspaceCycles'
|
||||
| 'disallowWorkspaceCycles'
|
||||
| 'sharedWorkspaceLockfile'
|
||||
> & CreateStoreControllerOptions & Omit<InstallOptions, 'storeController' | 'lockfileOnly' | 'preferredVersions'>
|
||||
|
||||
@@ -139,6 +140,11 @@ export async function handler (
|
||||
const cyclicDependenciesInfo = sequencedGraph.cycles.length > 0
|
||||
? `: ${sequencedGraph.cycles.map(deps => deps.join(', ')).join('; ')}`
|
||||
: ''
|
||||
|
||||
if (opts.disallowWorkspaceCycles) {
|
||||
throw new PnpmError('DISALLOW_WORKSPACE_CYCLES', `There are cyclic workspace dependencies${cyclicDependenciesInfo}`)
|
||||
}
|
||||
|
||||
logger.warn({
|
||||
message: `There are cyclic workspace dependencies${cyclicDependenciesInfo}`,
|
||||
prefix: opts.workspaceDir,
|
||||
|
||||
@@ -293,6 +293,7 @@ export type InstallCommandOptions = Pick<Config,
|
||||
| 'extraEnv'
|
||||
| 'resolutionMode'
|
||||
| 'ignoreWorkspaceCycles'
|
||||
| 'disallowWorkspaceCycles'
|
||||
> & CreateStoreControllerOptions & {
|
||||
argv: {
|
||||
original: string[]
|
||||
|
||||
@@ -81,6 +81,7 @@ export type InstallDepsOptions = Pick<Config,
|
||||
| 'workspaceDir'
|
||||
| 'extraEnv'
|
||||
| 'ignoreWorkspaceCycles'
|
||||
| 'disallowWorkspaceCycles'
|
||||
> & CreateStoreControllerOptions & {
|
||||
argv: {
|
||||
original: string[]
|
||||
@@ -157,6 +158,11 @@ when running add/update with the --workspace option')
|
||||
const cyclicDependenciesInfo = sequencedGraph.cycles.length > 0
|
||||
? `: ${sequencedGraph.cycles.map(deps => deps.join(', ')).join('; ')}`
|
||||
: ''
|
||||
|
||||
if (opts.disallowWorkspaceCycles) {
|
||||
throw new PnpmError('DISALLOW_WORKSPACE_CYCLES', `There are cyclic workspace dependencies${cyclicDependenciesInfo}`)
|
||||
}
|
||||
|
||||
logger.warn({
|
||||
message: `There are cyclic workspace dependencies${cyclicDependenciesInfo}`,
|
||||
prefix: opts.workspaceDir,
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
import { install } from '@pnpm/plugin-commands-installation'
|
||||
import { readProjects } from '@pnpm/filter-workspace-packages'
|
||||
import { preparePackages } from '@pnpm/prepare'
|
||||
import { DEFAULT_OPTS } from './utils'
|
||||
import type { PnpmError } from '@pnpm/error'
|
||||
|
||||
test('should error if disallow-workspace-cycles is set', async () => {
|
||||
preparePackages([
|
||||
{
|
||||
name: 'project-1',
|
||||
version: '1.0.0',
|
||||
dependencies: { 'project-2': 'workspace:*' },
|
||||
},
|
||||
{
|
||||
name: 'project-2',
|
||||
version: '2.0.0',
|
||||
devDependencies: { 'project-1': 'workspace:*' },
|
||||
},
|
||||
])
|
||||
|
||||
const { allProjects, selectedProjectsGraph } = await readProjects(process.cwd(), [])
|
||||
|
||||
let err!: PnpmError
|
||||
try {
|
||||
await install.handler({
|
||||
...DEFAULT_OPTS,
|
||||
allProjects,
|
||||
dir: process.cwd(),
|
||||
recursive: true,
|
||||
selectedProjectsGraph,
|
||||
workspaceDir: process.cwd(),
|
||||
disallowWorkspaceCycles: true,
|
||||
})
|
||||
} catch (_err: any) { // eslint-disable-line
|
||||
err = _err
|
||||
}
|
||||
expect(err.code).toBe('ERR_PNPM_DISALLOW_WORKSPACE_CYCLES')
|
||||
})
|
||||
|
||||
test('should not error if disallow-workspace-cycles is not set', async () => {
|
||||
preparePackages([
|
||||
{
|
||||
name: 'project-1',
|
||||
version: '1.0.0',
|
||||
dependencies: { 'project-2': 'workspace:*' },
|
||||
},
|
||||
{
|
||||
name: 'project-2',
|
||||
version: '2.0.0',
|
||||
devDependencies: { 'project-1': 'workspace:*' },
|
||||
},
|
||||
])
|
||||
|
||||
const { allProjects, selectedProjectsGraph } = await readProjects(process.cwd(), [])
|
||||
|
||||
let err!: PnpmError
|
||||
try {
|
||||
await install.handler({
|
||||
...DEFAULT_OPTS,
|
||||
allProjects,
|
||||
dir: process.cwd(),
|
||||
recursive: true,
|
||||
selectedProjectsGraph,
|
||||
workspaceDir: process.cwd(),
|
||||
})
|
||||
} catch (_err: any) { // eslint-disable-line
|
||||
err = _err
|
||||
}
|
||||
expect(err).toBeUndefined()
|
||||
})
|
||||
|
||||
test('should not error if there are no cyclic dependencies', async () => {
|
||||
preparePackages([
|
||||
{
|
||||
name: 'project-1',
|
||||
version: '1.0.0',
|
||||
dependencies: { 'project-2': 'workspace:*' },
|
||||
},
|
||||
{
|
||||
name: 'project-2',
|
||||
version: '2.0.0',
|
||||
},
|
||||
])
|
||||
|
||||
const { allProjects, selectedProjectsGraph } = await readProjects(process.cwd(), [])
|
||||
|
||||
let err!: PnpmError
|
||||
try {
|
||||
await install.handler({
|
||||
...DEFAULT_OPTS,
|
||||
allProjects,
|
||||
dir: process.cwd(),
|
||||
recursive: true,
|
||||
selectedProjectsGraph,
|
||||
workspaceDir: process.cwd(),
|
||||
disallowWorkspaceCycles: true,
|
||||
})
|
||||
} catch (_err: any) { // eslint-disable-line
|
||||
err = _err
|
||||
}
|
||||
expect(err).toBeUndefined()
|
||||
})
|
||||
Reference in New Issue
Block a user