Files
pnpm/pnpm11/workspace/projects-reader/test/normalizePatterns.ts
T
zhsamaandZoltan Kochan 965a600785 fix(workspace): normalize package directory patterns (#14582)
Workspace discovery passed directory patterns to wax without the POSIX path
normalization that pnpm11 receives from tinyglobby. This could omit projects
from installs and recursive commands, or include projects a negation excluded.

Add a POSIX normalization entry point to pnpm-fs, sharing the existing dot and
parent-component reduction with native-path normalization. POSIX separators
are interpreted consistently on every platform, preserving glob backslashes.
Normalize each configured pattern before selecting the existing literal,
one-level wildcard, or generic glob discovery path. Preserve the original
pattern in invalid-glob diagnostics.

The TypeScript implementation already normalizes these paths through
tinyglobby. Add matching TypeScript regression coverage alongside Rust
filesystem/discovery tests and a CLI install/list/recursive-script test.

Fixes pnpm/pnpm#14571.

---------

Co-authored-by: Zoltan Kochan <z@kochan.io>
2026-09-06 20:32:26 +02:00

73 lines
2.9 KiB
TypeScript

import path from 'node:path'
import { expect, test } from '@jest/globals'
import { findPackages } from '@pnpm/workspace.projects-reader'
const root = path.join(import.meta.dirname, 'findPackages-fixtures/many-pkgs-2')
test.each([
'./components/component-1',
'././components/component-1/',
'./components/./component-1',
'components/./component-1',
'components/component-1/.',
'components//component-1',
'.//components/component-1',
'components/missing/../component-1',
])('normalizes literal pattern %s', async (pattern) => {
const projects = await findPackages(root, { patterns: [pattern], includeRoot: true })
expect(projects.map(({ manifest }) => manifest.name).sort()).toStrictEqual(['component-1', 'many-pkgs-2'])
})
test.each([
'./components/*',
'././components/*',
'./components/**',
'./components/{component-1,component-2}',
'components//./*',
'components/missing/../*',
])('normalizes glob pattern %s', async (pattern) => {
const projects = await findPackages(root, { patterns: [pattern], includeRoot: true })
expect(projects.map(({ manifest }) => manifest.name).sort()).toStrictEqual(['component-1', 'component-2', 'many-pkgs-2'])
})
test.each([
'!./components/component-1',
'!././components/component-1/',
'!components/./component-1',
'!./components//component-1',
'!components/missing/../component-1',
].flatMap((pattern) => ['components/*', './components/**'].map((include) => ({ include, pattern }))))('normalizes $pattern with $include', async ({ include, pattern }) => {
const projects = await findPackages(root, { patterns: [include, pattern], includeRoot: true })
expect(projects.map(({ manifest }) => manifest.name).sort()).toStrictEqual(['component-2', 'many-pkgs-2'])
})
test.each([
'./../many-pkgs-2/components/*',
'././../many-pkgs-2/components/*',
'../many-pkgs-2/components/missing/../*',
])('normalizes parent-relative pattern %s', async (pattern) => {
const projects = await findPackages(root, {
patterns: [pattern, '!./../many-pkgs-2/components/./component-1'],
includeRoot: true,
})
expect(projects.map(({ manifest }) => manifest.name).sort()).toStrictEqual(['component-2', 'many-pkgs-2'])
})
test('deduplicates normalized patterns and preserves the workspace root', async () => {
const projects = await findPackages(root, {
patterns: ['././', 'components/../', 'components/component-1', './components/./component-1'],
includeRoot: true,
})
expect(projects.map(({ manifest }) => manifest.name).sort()).toStrictEqual(['component-1', 'many-pkgs-2'])
})
test.each([
'!.',
'!./',
'!components/..',
])('drops negation %s that normalizes to the workspace root', async (pattern) => {
const projects = await findPackages(root, { patterns: ['components/*', pattern], includeRoot: true })
expect(projects.map(({ manifest }) => manifest.name).sort()).toStrictEqual(['component-1', 'component-2', 'many-pkgs-2'])
})