Skip to content

Commit

Permalink
fix(core): correctly handle negative patterns in workspaces/packages …
Browse files Browse the repository at this point in the history
…config (#26453)
  • Loading branch information
JamesHenry authored Jun 7, 2024
1 parent 356479b commit 28b3d80
Show file tree
Hide file tree
Showing 2 changed files with 285 additions and 5 deletions.
279 changes: 275 additions & 4 deletions packages/nx/src/plugins/package-json-workspaces/create-nodes.spec.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import * as memfs from 'memfs';

import '../../internal-testing-utils/mock-fs';
import { createNodeFromPackageJson } from './create-nodes';

import { vol } from 'memfs';
import { createNodeFromPackageJson, createNodes } from './create-nodes';

describe('nx package.json workspaces plugin', () => {
afterEach(() => {
vol.reset();
});

it('should build projects from package.json files', () => {
memfs.vol.fromJSON(
vol.fromJSON(
{
'package.json': JSON.stringify({
name: 'root',
Expand Down Expand Up @@ -175,4 +179,271 @@ describe('nx package.json workspaces plugin', () => {
}
`);
});

describe('negative patterns', () => {
it('should work based on negative patterns defined in package.json workspaces', () => {
vol.fromJSON(
{
'package.json': JSON.stringify({
name: 'root',
workspaces: [
'packages/*',
// Multiple negative entries
'!packages/fs',
'!packages/orm-browser-example',
'!packages/framework-examples',
],
}),
'packages/vite/package.json': JSON.stringify({ name: 'vite' }),
'packages/fs/package.json': JSON.stringify({ name: 'fs' }),
'packages/orm-browser-example/package.json': JSON.stringify({
name: 'orm-browser-example',
}),
'packages/framework-examples/package.json': JSON.stringify({
name: 'framework-examples',
}),
},
'/root'
);

const context = {
workspaceRoot: '/root',
configFiles: [],
nxJsonConfiguration: {},
};

// No matching project based on the package.json "workspace" config
expect(
createNodes[1]('package.json', undefined, context)
).toMatchInlineSnapshot(`{}`);

// Matching project based on the package.json "workspace" config
expect(createNodes[1]('packages/vite/package.json', undefined, context))
.toMatchInlineSnapshot(`
{
"projects": {
"packages/vite": {
"metadata": {
"targetGroups": {
"NPM Scripts": [],
},
},
"name": "vite",
"projectType": "library",
"root": "packages/vite",
"sourceRoot": "packages/vite",
"targets": {
"nx-release-publish": {
"dependsOn": [
"^nx-release-publish",
],
"executor": "@nx/js:release-publish",
"options": {},
},
},
},
},
}
`);

// No matching project based on the package.json "workspace" config
expect(
createNodes[1]('packages/fs/package.json', undefined, context)
).toMatchInlineSnapshot(`{}`);

// No matching project based on the package.json "workspace" config
expect(
createNodes[1](
'packages/orm-browser-example/package.json',
undefined,
context
)
).toMatchInlineSnapshot(`{}`);

// No matching project based on the package.json "workspace" config
expect(
createNodes[1](
'packages/framework-examples/package.json',
undefined,
context
)
).toMatchInlineSnapshot(`{}`);
});

it('should work based on negative patterns defined in pnpm-workspace.yaml', () => {
vol.fromJSON(
{
'package.json': JSON.stringify({ name: 'root' }),
// Multiple negative entries
'pnpm-workspace.yaml': `packages:
- 'packages/*'
- '!packages/fs'
- '!packages/orm-browser-example'
- '!packages/framework-examples'
`,
'packages/vite/package.json': JSON.stringify({ name: 'vite' }),
'packages/fs/package.json': JSON.stringify({ name: 'fs' }),
'packages/orm-browser-example/package.json': JSON.stringify({
name: 'orm-browser-example',
}),
'packages/framework-examples/package.json': JSON.stringify({
name: 'framework-examples',
}),
},
'/root'
);

const context = {
workspaceRoot: '/root',
configFiles: [],
nxJsonConfiguration: {},
};

// No matching project based on the pnpm-workspace.yaml "packages" config
expect(
createNodes[1]('package.json', undefined, context)
).toMatchInlineSnapshot(`{}`);

// Matching project based on the pnpm-workspace.yaml "packages" config
expect(createNodes[1]('packages/vite/package.json', undefined, context))
.toMatchInlineSnapshot(`
{
"projects": {
"packages/vite": {
"metadata": {
"targetGroups": {
"NPM Scripts": [],
},
},
"name": "vite",
"projectType": "library",
"root": "packages/vite",
"sourceRoot": "packages/vite",
"targets": {
"nx-release-publish": {
"dependsOn": [
"^nx-release-publish",
],
"executor": "@nx/js:release-publish",
"options": {},
},
},
},
},
}
`);

// No matching project based on the pnpm-workspace.yaml "packages" config
expect(
createNodes[1]('packages/fs/package.json', undefined, context)
).toMatchInlineSnapshot(`{}`);

// No matching project based on the pnpm-workspace.yaml "packages" config
expect(
createNodes[1](
'packages/orm-browser-example/package.json',
undefined,
context
)
).toMatchInlineSnapshot(`{}`);

// No matching project based on the pnpm-workspace.yaml "packages" config
expect(
createNodes[1](
'packages/framework-examples/package.json',
undefined,
context
)
).toMatchInlineSnapshot(`{}`);
});

it('should work based on negative patterns defined in lerna.json', () => {
vol.fromJSON(
{
'package.json': JSON.stringify({ name: 'root' }),
'lerna.json': JSON.stringify({
packages: [
'packages/*',
// Multiple negative entries
'!packages/fs',
'!packages/orm-browser-example',
'!packages/framework-examples',
],
}),
'packages/vite/package.json': JSON.stringify({ name: 'vite' }),
'packages/fs/package.json': JSON.stringify({ name: 'fs' }),
'packages/orm-browser-example/package.json': JSON.stringify({
name: 'orm-browser-example',
}),
'packages/framework-examples/package.json': JSON.stringify({
name: 'framework-examples',
}),
},
'/root'
);

const context = {
workspaceRoot: '/root',
configFiles: [],
nxJsonConfiguration: {},
};

// No matching project based on the lerna.json "packages" config
expect(
createNodes[1]('package.json', undefined, context)
).toMatchInlineSnapshot(`{}`);

// Matching project based on the lerna.json "packages" config
expect(createNodes[1]('packages/vite/package.json', undefined, context))
.toMatchInlineSnapshot(`
{
"projects": {
"packages/vite": {
"metadata": {
"targetGroups": {
"NPM Scripts": [],
},
},
"name": "vite",
"projectType": "library",
"root": "packages/vite",
"sourceRoot": "packages/vite",
"targets": {
"nx-release-publish": {
"dependsOn": [
"^nx-release-publish",
],
"executor": "@nx/js:release-publish",
"options": {},
},
},
},
},
}
`);

// No matching project based on the lerna.json "packages" config
expect(
createNodes[1]('packages/fs/package.json', undefined, context)
).toMatchInlineSnapshot(`{}`);

// No matching project based on the lerna.json "packages" config
expect(
createNodes[1](
'packages/orm-browser-example/package.json',
undefined,
context
)
).toMatchInlineSnapshot(`{}`);

// No matching project based on the lerna.json "packages" config
expect(
createNodes[1](
'packages/framework-examples/package.json',
undefined,
context
)
).toMatchInlineSnapshot(`{}`);
});
});
});
11 changes: 10 additions & 1 deletion packages/nx/src/plugins/package-json-workspaces/create-nodes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,16 @@ export function buildPackageJsonWorkspacesMatcher(

return (p: string) =>
positivePatterns.some((positive) => minimatch(p, positive)) &&
!negativePatterns.some((negative) => minimatch(p, negative));
/**
* minimatch will return true if the given p is NOT excluded by the negative pattern.
*
* For example if the negative pattern is "!packages/vite", then the given p "packages/vite" will return false,
* the given p "packages/something-else/package.json" will return true.
*
* Therefore, we need to ensure that every negative pattern returns true to validate that the given p is not
* excluded by any of the negative patterns.
*/
negativePatterns.every((negative) => minimatch(p, negative));
}

export function createNodeFromPackageJson(pkgJsonPath: string, root: string) {
Expand Down

0 comments on commit 28b3d80

Please sign in to comment.