fix(js): avoid nested paths in workspaces because they can lead to future issues (#29553)

For the new TS setup, we currently use nested glob patterns: `apps/**`,
`libs/**`, and `packages/**`.

Nested paths can result into too many projects being matched. For
example, if `libs/mylib/src/__fixtures__/package.json` is there for
testing, it will be matched as a project and likely result in an error.

Other tools like turborepo also caution against this:
https://turbo.build/repo/docs/crafting-your-repository/structuring-a-repository#declaring-directories-for-packages

If users want to, they could change to nested `**` paths, but we should
not use it by default.

Note: For CNW, we only use `apps/*` by default since that is where the
project lives. When users do `nx g lib packages/foo` then `packages/*`
will be added automatically.

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
<!-- This is the behavior we have today -->
Use nested `**` paths.

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
Don't use nested `**` paths.

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #
This commit is contained in:
Jack Hsu 2025-01-08 18:08:39 -05:00 committed by GitHub
parent e99cf07ffd
commit fb318005f2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 37 additions and 23 deletions

View File

@ -33,13 +33,13 @@ describe('Node Applications', () => {
'pnpm-workspace.yaml', 'pnpm-workspace.yaml',
` `
packages: packages:
- 'apps/**' - 'apps/*'
- 'packages/**' - 'packages/*'
` `
); );
} else { } else {
updateJson('package.json', (json) => { updateJson('package.json', (json) => {
json.workspaces = ['apps/**', 'packages/**']; json.workspaces = ['apps/*', 'packages/*'];
return json; return json;
}); });
} }

View File

@ -23,13 +23,13 @@ describe('Vue Plugin', () => {
'pnpm-workspace.yaml', 'pnpm-workspace.yaml',
` `
packages: packages:
- 'apps/**' - 'apps/*'
- 'packages/**' - 'packages/*'
` `
); );
} else { } else {
updateJson('package.json', (json) => { updateJson('package.json', (json) => {
json.workspaces = ['apps/**', 'packages/**']; json.workspaces = ['apps/*', 'packages/*'];
return json; return json;
}); });
} }

View File

@ -115,8 +115,8 @@ function getWorkspaceGlobsFromPreset(preset: string): string[] {
case Preset.RemixMonorepo: case Preset.RemixMonorepo:
case Preset.VueMonorepo: case Preset.VueMonorepo:
case Preset.WebComponents: case Preset.WebComponents:
return ['apps/**', 'libs/**', 'packages/**']; return ['apps/*'];
default: default:
return ['libs/**', 'packages/**']; return ['packages/*'];
} }
} }

View File

@ -198,10 +198,11 @@ export function addProjectToTsSolutionWorkspace(
tree: Tree, tree: Tree,
projectDir: string projectDir: string
) { ) {
// If dir is "libs/foo" then use "libs/**" so we don't need so many entries in the workspace file. // If dir is "libs/foo" then use "libs/*" so we don't need so many entries in the workspace file.
// If dir is nested like "libs/shared/foo" then we add "libs/shared/*".
// If the dir is just "foo" then we have to add it as is. // If the dir is just "foo" then we have to add it as is.
const baseDir = dirname(projectDir); const baseDir = dirname(projectDir);
const pattern = baseDir === '.' ? projectDir : `${baseDir}/**`; const pattern = baseDir === '.' ? projectDir : `${baseDir}/*`;
if (tree.exists('pnpm-workspace.yaml')) { if (tree.exists('pnpm-workspace.yaml')) {
const { load, dump } = require('@zkochan/js-yaml'); const { load, dump } = require('@zkochan/js-yaml');
const workspaceFile = tree.read('pnpm-workspace.yaml', 'utf-8'); const workspaceFile = tree.read('pnpm-workspace.yaml', 'utf-8');

View File

@ -1279,7 +1279,7 @@ describe('app', () => {
beforeEach(() => { beforeEach(() => {
appTree = createTreeWithEmptyWorkspace(); appTree = createTreeWithEmptyWorkspace();
updateJson(appTree, 'package.json', (json) => { updateJson(appTree, 'package.json', (json) => {
json.workspaces = ['packages/**', 'apps/**']; json.workspaces = ['packages/*', 'apps/*'];
return json; return json;
}); });
writeJson(appTree, 'tsconfig.base.json', { writeJson(appTree, 'tsconfig.base.json', {
@ -1482,10 +1482,10 @@ describe('app', () => {
const packageJson = readJson(appTree, 'package.json'); const packageJson = readJson(appTree, 'package.json');
expect(packageJson.workspaces).toEqual([ expect(packageJson.workspaces).toEqual([
'packages/**', 'packages/*',
'apps/**', 'apps/*',
'myapp', 'myapp',
'libs/**', 'libs/*',
]); ]);
}); });
@ -1519,11 +1519,24 @@ describe('app', () => {
unitTestRunner: 'none', unitTestRunner: 'none',
e2eTestRunner: 'none', e2eTestRunner: 'none',
}); });
await applicationGenerator(appTree, {
directory: 'packages/shared/util',
addPlugin: true,
linter: Linter.EsLint,
style: 'none',
bundler: 'vite',
unitTestRunner: 'none',
e2eTestRunner: 'none',
});
const pnpmContent = appTree.read('pnpm-workspace.yaml', 'utf-8'); const pnpmContent = appTree.read('pnpm-workspace.yaml', 'utf-8');
const pnpmWorkspaceFile = load(pnpmContent); const pnpmWorkspaceFile = load(pnpmContent);
expect(pnpmWorkspaceFile.packages).toEqual(['myapp', 'apps/**']); expect(pnpmWorkspaceFile.packages).toEqual([
'myapp',
'apps/*',
'packages/shared/*',
]);
}); });
}); });

View File

@ -230,7 +230,7 @@ describe('@nx/workspace:generateWorkspaceFiles', () => {
"scripts": {}, "scripts": {},
"version": "0.0.0", "version": "0.0.0",
"workspaces": [ "workspaces": [
"packages/**", "packages/*",
], ],
} }
`); `);
@ -294,7 +294,7 @@ describe('@nx/workspace:generateWorkspaceFiles', () => {
defaultBase: 'main', defaultBase: 'main',
packageManager: 'npm', packageManager: 'npm',
isCustomPreset: false, isCustomPreset: false,
workspaceGlobs: ['apps/**', 'packages/**'], workspaceGlobs: ['apps/*', 'packages/*'],
}); });
const packageJson = readJson(tree, '/proj/package.json'); const packageJson = readJson(tree, '/proj/package.json');
@ -310,8 +310,8 @@ describe('@nx/workspace:generateWorkspaceFiles', () => {
"scripts": {}, "scripts": {},
"version": "0.0.0", "version": "0.0.0",
"workspaces": [ "workspaces": [
"apps/**", "apps/*",
"packages/**", "packages/*",
], ],
} }
`); `);
@ -326,14 +326,14 @@ describe('@nx/workspace:generateWorkspaceFiles', () => {
defaultBase: 'main', defaultBase: 'main',
packageManager: 'pnpm', packageManager: 'pnpm',
isCustomPreset: false, isCustomPreset: false,
workspaceGlobs: ['apps/**', 'packages/**'], workspaceGlobs: ['apps/*', 'packages/*'],
}); });
const packageJson = tree.read('/proj/pnpm-workspace.yaml', 'utf-8'); const packageJson = tree.read('/proj/pnpm-workspace.yaml', 'utf-8');
expect(packageJson).toMatchInlineSnapshot(` expect(packageJson).toMatchInlineSnapshot(`
"packages: "packages:
- "apps/**" - "apps/*"
- "packages/**" - "packages/*"
" "
`); `);
}); });

View File

@ -428,7 +428,7 @@ function setUpWorkspacesInPackageJson(tree: Tree, options: NormalizedSchema) {
options.preset === Preset.Express) && options.preset === Preset.Express) &&
options.workspaces) options.workspaces)
) { ) {
const workspaces = options.workspaceGlobs ?? ['packages/**']; const workspaces = options.workspaceGlobs ?? ['packages/*'];
if (options.packageManager === 'pnpm') { if (options.packageManager === 'pnpm') {
tree.write( tree.write(
join(options.directory, 'pnpm-workspace.yaml'), join(options.directory, 'pnpm-workspace.yaml'),