fix(core): handle package manager workspaces configuration in move generator (#30268)
## Current Behavior Moving a project included in the package manager workspaces setup to a new destination that's not matched by that configuration results in the project not included in the package manager workspaces setup. ## Expected Behavior Moving a project included in the package manager workspaces setup to a new destination that's not matched by that configuration should result in the new destination included in the workspaces setup. ## Related Issue(s) Fixes #
This commit is contained in:
parent
d1a7ac96ce
commit
432a645d21
@ -60,7 +60,7 @@ export async function detoxApplicationGeneratorInternal(
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (options.isUsingTsSolutionConfig) {
|
if (options.isUsingTsSolutionConfig) {
|
||||||
addProjectToTsSolutionWorkspace(host, options.e2eProjectRoot);
|
await addProjectToTsSolutionWorkspace(host, options.e2eProjectRoot);
|
||||||
}
|
}
|
||||||
|
|
||||||
sortPackageJsonFields(host, options.e2eProjectRoot);
|
sortPackageJsonFields(host, options.e2eProjectRoot);
|
||||||
|
|||||||
@ -65,7 +65,7 @@ export async function expoApplicationGeneratorInternal(
|
|||||||
// If we are using the new TS solution
|
// If we are using the new TS solution
|
||||||
// We need to update the workspace file (package.json or pnpm-workspaces.yaml) to include the new project
|
// We need to update the workspace file (package.json or pnpm-workspaces.yaml) to include the new project
|
||||||
if (options.isTsSolutionSetup) {
|
if (options.isTsSolutionSetup) {
|
||||||
addProjectToTsSolutionWorkspace(host, options.appProjectRoot);
|
await addProjectToTsSolutionWorkspace(host, options.appProjectRoot);
|
||||||
}
|
}
|
||||||
|
|
||||||
const lintTask = await addLinting(host, {
|
const lintTask = await addLinting(host, {
|
||||||
|
|||||||
@ -70,7 +70,7 @@ export async function expoLibraryGeneratorInternal(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (options.isUsingTsSolutionConfig) {
|
if (options.isUsingTsSolutionConfig) {
|
||||||
addProjectToTsSolutionWorkspace(host, options.projectRoot);
|
await addProjectToTsSolutionWorkspace(host, options.projectRoot);
|
||||||
}
|
}
|
||||||
|
|
||||||
const initTask = await init(host, { ...options, skipFormat: true });
|
const initTask = await init(host, { ...options, skipFormat: true });
|
||||||
|
|||||||
@ -2303,6 +2303,28 @@ describe('lib', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should add the project root to the package manager workspaces config when a more generic pattern would match other projects that were not previously included', async () => {
|
||||||
|
tree.write(
|
||||||
|
'not-included-dir/some-other-project-not-included/package.json',
|
||||||
|
'{}'
|
||||||
|
);
|
||||||
|
|
||||||
|
await libraryGenerator(tree, {
|
||||||
|
...defaultOptions,
|
||||||
|
directory: 'not-included-dir/my-lib',
|
||||||
|
bundler: 'tsc',
|
||||||
|
unitTestRunner: 'none',
|
||||||
|
linter: 'none',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(readJson(tree, 'package.json').workspaces).toContain(
|
||||||
|
'not-included-dir/my-lib'
|
||||||
|
);
|
||||||
|
expect(readJson(tree, 'package.json').workspaces).not.toContain(
|
||||||
|
'not-included-dir/*'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it('should not add a pattern for a project that already matches an existing pattern', async () => {
|
it('should not add a pattern for a project that already matches an existing pattern', async () => {
|
||||||
updateJson(tree, 'package.json', (json) => {
|
updateJson(tree, 'package.json', (json) => {
|
||||||
json.workspaces = ['packages/**'];
|
json.workspaces = ['packages/**'];
|
||||||
|
|||||||
@ -100,12 +100,6 @@ export async function libraryGeneratorInternal(
|
|||||||
);
|
);
|
||||||
const options = await normalizeOptions(tree, schema);
|
const options = await normalizeOptions(tree, schema);
|
||||||
|
|
||||||
// If we are using the new TS solution
|
|
||||||
// We need to update the workspace file (package.json or pnpm-workspaces.yaml) to include the new project
|
|
||||||
if (options.isUsingTsSolutionConfig) {
|
|
||||||
addProjectToTsSolutionWorkspace(tree, options.projectRoot);
|
|
||||||
}
|
|
||||||
|
|
||||||
createFiles(tree, options);
|
createFiles(tree, options);
|
||||||
|
|
||||||
await configureProject(tree, options);
|
await configureProject(tree, options);
|
||||||
@ -114,6 +108,12 @@ export async function libraryGeneratorInternal(
|
|||||||
tasks.push(addProjectDependencies(tree, options));
|
tasks.push(addProjectDependencies(tree, options));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we are using the new TS solution
|
||||||
|
// We need to update the workspace file (package.json or pnpm-workspaces.yaml) to include the new project
|
||||||
|
if (options.isUsingTsSolutionConfig) {
|
||||||
|
await addProjectToTsSolutionWorkspace(tree, options.projectRoot);
|
||||||
|
}
|
||||||
|
|
||||||
if (options.bundler === 'rollup') {
|
if (options.bundler === 'rollup') {
|
||||||
const { configurationGenerator } = ensurePackage('@nx/rollup', nxVersion);
|
const { configurationGenerator } = ensurePackage('@nx/rollup', nxVersion);
|
||||||
await configurationGenerator(tree, {
|
await configurationGenerator(tree, {
|
||||||
|
|||||||
@ -25,7 +25,16 @@ export function getProjectPackageManagerWorkspaceState(
|
|||||||
return 'no-workspaces';
|
return 'no-workspaces';
|
||||||
}
|
}
|
||||||
|
|
||||||
const patterns = getGlobPatternsFromPackageManagerWorkspaces(
|
const patterns = getPackageManagerWorkspacesPatterns(tree);
|
||||||
|
const isIncluded = patterns.some((p) =>
|
||||||
|
picomatch(p)(join(projectRoot, 'package.json'))
|
||||||
|
);
|
||||||
|
|
||||||
|
return isIncluded ? 'included' : 'excluded';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPackageManagerWorkspacesPatterns(tree: Tree): string[] {
|
||||||
|
return getGlobPatternsFromPackageManagerWorkspaces(
|
||||||
tree.root,
|
tree.root,
|
||||||
(path) => readJson(tree, path, { expectComments: true }),
|
(path) => readJson(tree, path, { expectComments: true }),
|
||||||
(path) => {
|
(path) => {
|
||||||
@ -35,11 +44,6 @@ export function getProjectPackageManagerWorkspaceState(
|
|||||||
},
|
},
|
||||||
(path) => tree.exists(path)
|
(path) => tree.exists(path)
|
||||||
);
|
);
|
||||||
const isIncluded = patterns.some((p) =>
|
|
||||||
picomatch(p)(join(projectRoot, 'package.json'))
|
|
||||||
);
|
|
||||||
|
|
||||||
return isIncluded ? 'included' : 'excluded';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isUsingPackageManagerWorkspaces(tree: Tree): boolean {
|
export function isUsingPackageManagerWorkspaces(tree: Tree): boolean {
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
|
globAsync,
|
||||||
joinPathFragments,
|
joinPathFragments,
|
||||||
offsetFromRoot,
|
offsetFromRoot,
|
||||||
output,
|
output,
|
||||||
@ -11,6 +12,7 @@ import {
|
|||||||
import { basename, dirname, join } from 'node:path/posix';
|
import { basename, dirname, join } from 'node:path/posix';
|
||||||
import { FsTree } from 'nx/src/generators/tree';
|
import { FsTree } from 'nx/src/generators/tree';
|
||||||
import {
|
import {
|
||||||
|
getPackageManagerWorkspacesPatterns,
|
||||||
getProjectPackageManagerWorkspaceState,
|
getProjectPackageManagerWorkspaceState,
|
||||||
isUsingPackageManagerWorkspaces,
|
isUsingPackageManagerWorkspaces,
|
||||||
} from '../package-manager-workspaces';
|
} from '../package-manager-workspaces';
|
||||||
@ -211,7 +213,7 @@ export function updateTsconfigFiles(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addProjectToTsSolutionWorkspace(
|
export async function addProjectToTsSolutionWorkspace(
|
||||||
tree: Tree,
|
tree: Tree,
|
||||||
projectDir: string
|
projectDir: string
|
||||||
) {
|
) {
|
||||||
@ -220,11 +222,26 @@ export function addProjectToTsSolutionWorkspace(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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", we try to use "libs/*" but we only do it if it's
|
||||||
// If dir is nested like "libs/shared/foo" then we add "libs/shared/*".
|
// safe to do so. So, we first check if adding that pattern doesn't result
|
||||||
// If the dir is just "foo" then we have to add it as is.
|
// in extra projects being matched. If extra projects are matched, or the
|
||||||
|
// dir is just "foo" then we add it as is.
|
||||||
const baseDir = dirname(projectDir);
|
const baseDir = dirname(projectDir);
|
||||||
const pattern = baseDir === '.' ? projectDir : `${baseDir}/*`;
|
let pattern = projectDir;
|
||||||
|
if (baseDir !== '.') {
|
||||||
|
const patterns = getPackageManagerWorkspacesPatterns(tree);
|
||||||
|
const projectsBefore = await globAsync(tree, patterns);
|
||||||
|
patterns.push(`${baseDir}/*/package.json`);
|
||||||
|
const projectsAfter = await globAsync(tree, patterns);
|
||||||
|
|
||||||
|
if (projectsBefore.length + 1 === projectsAfter.length) {
|
||||||
|
// Adding the pattern to the parent directory only results in one extra
|
||||||
|
// project being matched, which is the project we're adding. It's safe
|
||||||
|
// to add the pattern to the parent directory.
|
||||||
|
pattern = `${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');
|
||||||
|
|||||||
@ -73,7 +73,7 @@ export async function applicationGeneratorInternal(host: Tree, schema: Schema) {
|
|||||||
// If we are using the new TS solution
|
// If we are using the new TS solution
|
||||||
// We need to update the workspace file (package.json or pnpm-workspaces.yaml) to include the new project
|
// We need to update the workspace file (package.json or pnpm-workspaces.yaml) to include the new project
|
||||||
if (options.isTsSolutionSetup) {
|
if (options.isTsSolutionSetup) {
|
||||||
addProjectToTsSolutionWorkspace(host, options.appProjectRoot);
|
await addProjectToTsSolutionWorkspace(host, options.appProjectRoot);
|
||||||
}
|
}
|
||||||
|
|
||||||
const e2eTask = await addE2e(host, options);
|
const e2eTask = await addE2e(host, options);
|
||||||
|
|||||||
@ -168,7 +168,7 @@ export async function libraryGeneratorInternal(host: Tree, rawOptions: Schema) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (options.isUsingTsSolutionConfig) {
|
if (options.isUsingTsSolutionConfig) {
|
||||||
addProjectToTsSolutionWorkspace(host, options.projectRoot);
|
await addProjectToTsSolutionWorkspace(host, options.projectRoot);
|
||||||
}
|
}
|
||||||
|
|
||||||
sortPackageJsonFields(host, options.projectRoot);
|
sortPackageJsonFields(host, options.projectRoot);
|
||||||
|
|||||||
@ -516,7 +516,7 @@ export async function applicationGeneratorInternal(tree: Tree, schema: Schema) {
|
|||||||
// If we are using the new TS solution
|
// If we are using the new TS solution
|
||||||
// We need to update the workspace file (package.json or pnpm-workspaces.yaml) to include the new project
|
// We need to update the workspace file (package.json or pnpm-workspaces.yaml) to include the new project
|
||||||
if (options.isUsingTsSolutionConfig) {
|
if (options.isUsingTsSolutionConfig) {
|
||||||
addProjectToTsSolutionWorkspace(tree, options.appProjectRoot);
|
await addProjectToTsSolutionWorkspace(tree, options.appProjectRoot);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateTsConfigOptions(tree, options);
|
updateTsConfigOptions(tree, options);
|
||||||
|
|||||||
@ -259,7 +259,7 @@ export async function e2eProjectGeneratorInternal(
|
|||||||
// If we are using the new TS solution
|
// If we are using the new TS solution
|
||||||
// We need to update the workspace file (package.json or pnpm-workspaces.yaml) to include the new project
|
// We need to update the workspace file (package.json or pnpm-workspaces.yaml) to include the new project
|
||||||
if (isUsingTsSolutionConfig) {
|
if (isUsingTsSolutionConfig) {
|
||||||
addProjectToTsSolutionWorkspace(host, options.e2eProjectRoot);
|
await addProjectToTsSolutionWorkspace(host, options.e2eProjectRoot);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!options.skipFormat) {
|
if (!options.skipFormat) {
|
||||||
|
|||||||
@ -57,7 +57,7 @@ export async function libraryGeneratorInternal(tree: Tree, schema: Schema) {
|
|||||||
// If we are using the new TS solution
|
// If we are using the new TS solution
|
||||||
// We need to update the workspace file (package.json or pnpm-workspaces.yaml) to include the new project
|
// We need to update the workspace file (package.json or pnpm-workspaces.yaml) to include the new project
|
||||||
if (options.isUsingTsSolutionConfig) {
|
if (options.isUsingTsSolutionConfig) {
|
||||||
addProjectToTsSolutionWorkspace(tree, options.projectRoot);
|
await addProjectToTsSolutionWorkspace(tree, options.projectRoot);
|
||||||
}
|
}
|
||||||
|
|
||||||
const tasks: GeneratorCallback[] = [];
|
const tasks: GeneratorCallback[] = [];
|
||||||
|
|||||||
@ -151,7 +151,7 @@ export async function applicationGenerator(tree: Tree, schema: Schema) {
|
|||||||
// If we are using the new TS solution
|
// If we are using the new TS solution
|
||||||
// We need to update the workspace file (package.json or pnpm-workspaces.yaml) to include the new project
|
// We need to update the workspace file (package.json or pnpm-workspaces.yaml) to include the new project
|
||||||
if (options.isUsingTsSolutionConfig) {
|
if (options.isUsingTsSolutionConfig) {
|
||||||
addProjectToTsSolutionWorkspace(tree, options.appProjectRoot);
|
await addProjectToTsSolutionWorkspace(tree, options.appProjectRoot);
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.push(
|
tasks.push(
|
||||||
|
|||||||
@ -258,7 +258,7 @@ export async function e2eProjectGeneratorInternal(host: Tree, schema: Schema) {
|
|||||||
// If we are using the new TS solution
|
// If we are using the new TS solution
|
||||||
// We need to update the workspace file (package.json or pnpm-workspaces.yaml) to include the new project
|
// We need to update the workspace file (package.json or pnpm-workspaces.yaml) to include the new project
|
||||||
if (options.isTsSolutionSetup) {
|
if (options.isTsSolutionSetup) {
|
||||||
addProjectToTsSolutionWorkspace(host, options.projectRoot);
|
await addProjectToTsSolutionWorkspace(host, options.projectRoot);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!options.skipFormat) {
|
if (!options.skipFormat) {
|
||||||
|
|||||||
@ -69,7 +69,7 @@ export async function reactNativeApplicationGeneratorInternal(
|
|||||||
// If we are using the new TS solution
|
// If we are using the new TS solution
|
||||||
// We need to update the workspace file (package.json or pnpm-workspaces.yaml) to include the new project
|
// We need to update the workspace file (package.json or pnpm-workspaces.yaml) to include the new project
|
||||||
if (options.isTsSolutionSetup) {
|
if (options.isTsSolutionSetup) {
|
||||||
addProjectToTsSolutionWorkspace(host, options.appProjectRoot);
|
await addProjectToTsSolutionWorkspace(host, options.appProjectRoot);
|
||||||
}
|
}
|
||||||
|
|
||||||
const lintTask = await addLinting(host, {
|
const lintTask = await addLinting(host, {
|
||||||
|
|||||||
@ -88,7 +88,7 @@ export async function reactNativeLibraryGeneratorInternal(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (options.isUsingTsSolutionConfig) {
|
if (options.isUsingTsSolutionConfig) {
|
||||||
addProjectToTsSolutionWorkspace(host, options.projectRoot);
|
await addProjectToTsSolutionWorkspace(host, options.projectRoot);
|
||||||
}
|
}
|
||||||
|
|
||||||
const lintTask = await addLinting(host, {
|
const lintTask = await addLinting(host, {
|
||||||
|
|||||||
@ -72,12 +72,6 @@ export async function applicationGeneratorInternal(
|
|||||||
|
|
||||||
const options = await normalizeOptions(tree, schema);
|
const options = await normalizeOptions(tree, schema);
|
||||||
|
|
||||||
// If we are using the new TS solution
|
|
||||||
// We need to update the workspace file (package.json or pnpm-workspaces.yaml) to include the new project
|
|
||||||
if (options.isUsingTsSolutionConfig) {
|
|
||||||
addProjectToTsSolutionWorkspace(tree, options.appProjectRoot);
|
|
||||||
}
|
|
||||||
|
|
||||||
showPossibleWarnings(tree, options);
|
showPossibleWarnings(tree, options);
|
||||||
|
|
||||||
const initTask = await reactInitGenerator(tree, {
|
const initTask = await reactInitGenerator(tree, {
|
||||||
@ -115,6 +109,12 @@ export async function applicationGeneratorInternal(
|
|||||||
await createApplicationFiles(tree, options);
|
await createApplicationFiles(tree, options);
|
||||||
addProject(tree, options);
|
addProject(tree, options);
|
||||||
|
|
||||||
|
// If we are using the new TS solution
|
||||||
|
// We need to update the workspace file (package.json or pnpm-workspaces.yaml) to include the new project
|
||||||
|
if (options.isUsingTsSolutionConfig) {
|
||||||
|
await addProjectToTsSolutionWorkspace(tree, options.appProjectRoot);
|
||||||
|
}
|
||||||
|
|
||||||
if (options.style === 'tailwind') {
|
if (options.style === 'tailwind') {
|
||||||
const twTask = await setupTailwindGenerator(tree, {
|
const twTask = await setupTailwindGenerator(tree, {
|
||||||
project: options.projectName,
|
project: options.projectName,
|
||||||
|
|||||||
@ -62,7 +62,7 @@ export async function libraryGeneratorInternal(host: Tree, schema: Schema) {
|
|||||||
const options = await normalizeOptions(host, schema);
|
const options = await normalizeOptions(host, schema);
|
||||||
|
|
||||||
if (options.isUsingTsSolutionConfig) {
|
if (options.isUsingTsSolutionConfig) {
|
||||||
addProjectToTsSolutionWorkspace(host, options.projectRoot);
|
await addProjectToTsSolutionWorkspace(host, options.projectRoot);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.publishable === true && !schema.importPath) {
|
if (options.publishable === true && !schema.importPath) {
|
||||||
|
|||||||
@ -85,7 +85,7 @@ export async function remixApplicationGeneratorInternal(
|
|||||||
// If we are using the new TS solution
|
// If we are using the new TS solution
|
||||||
// We need to update the workspace file (package.json or pnpm-workspaces.yaml) to include the new project
|
// We need to update the workspace file (package.json or pnpm-workspaces.yaml) to include the new project
|
||||||
if (options.isUsingTsSolutionConfig) {
|
if (options.isUsingTsSolutionConfig) {
|
||||||
addProjectToTsSolutionWorkspace(tree, options.projectRoot);
|
await addProjectToTsSolutionWorkspace(tree, options.projectRoot);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!options.isUsingTsSolutionConfig) {
|
if (!options.isUsingTsSolutionConfig) {
|
||||||
|
|||||||
@ -30,7 +30,7 @@ export async function remixLibraryGeneratorInternal(
|
|||||||
const options = await normalizeOptions(tree, schema);
|
const options = await normalizeOptions(tree, schema);
|
||||||
|
|
||||||
if (options.isUsingTsSolutionConfig) {
|
if (options.isUsingTsSolutionConfig) {
|
||||||
addProjectToTsSolutionWorkspace(tree, options.projectRoot);
|
await addProjectToTsSolutionWorkspace(tree, options.projectRoot);
|
||||||
}
|
}
|
||||||
|
|
||||||
const jsInitTask = await jsInitGenerator(tree, {
|
const jsInitTask = await jsInitGenerator(tree, {
|
||||||
|
|||||||
@ -56,7 +56,7 @@ export async function applicationGeneratorInternal(
|
|||||||
// If we are using the new TS solution
|
// If we are using the new TS solution
|
||||||
// We need to update the workspace file (package.json or pnpm-workspaces.yaml) to include the new project
|
// We need to update the workspace file (package.json or pnpm-workspaces.yaml) to include the new project
|
||||||
if (options.isUsingTsSolutionConfig) {
|
if (options.isUsingTsSolutionConfig) {
|
||||||
addProjectToTsSolutionWorkspace(tree, options.appProjectRoot);
|
await addProjectToTsSolutionWorkspace(tree, options.appProjectRoot);
|
||||||
}
|
}
|
||||||
|
|
||||||
const nxJson = readNxJson(tree);
|
const nxJson = readNxJson(tree);
|
||||||
|
|||||||
@ -56,7 +56,7 @@ export async function libraryGeneratorInternal(tree: Tree, schema: Schema) {
|
|||||||
// If we are using the new TS solution
|
// If we are using the new TS solution
|
||||||
// We need to update the workspace file (package.json or pnpm-workspaces.yaml) to include the new project
|
// We need to update the workspace file (package.json or pnpm-workspaces.yaml) to include the new project
|
||||||
if (options.isUsingTsSolutionConfig) {
|
if (options.isUsingTsSolutionConfig) {
|
||||||
addProjectToTsSolutionWorkspace(tree, options.projectRoot);
|
await addProjectToTsSolutionWorkspace(tree, options.projectRoot);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.isUsingTsSolutionConfig) {
|
if (options.isUsingTsSolutionConfig) {
|
||||||
|
|||||||
@ -301,7 +301,7 @@ export async function applicationGeneratorInternal(host: Tree, schema: Schema) {
|
|||||||
const options = await normalizeOptions(host, schema);
|
const options = await normalizeOptions(host, schema);
|
||||||
|
|
||||||
if (options.isUsingTsSolutionConfig) {
|
if (options.isUsingTsSolutionConfig) {
|
||||||
addProjectToTsSolutionWorkspace(host, options.appProjectRoot);
|
await addProjectToTsSolutionWorkspace(host, options.appProjectRoot);
|
||||||
}
|
}
|
||||||
|
|
||||||
const tasks: GeneratorCallback[] = [];
|
const tasks: GeneratorCallback[] = [];
|
||||||
|
|||||||
@ -39,8 +39,10 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nx/devkit": "file:../devkit",
|
"@nx/devkit": "file:../devkit",
|
||||||
|
"@zkochan/js-yaml": "0.0.7",
|
||||||
"chalk": "^4.1.0",
|
"chalk": "^4.1.0",
|
||||||
"enquirer": "~2.3.6",
|
"enquirer": "~2.3.6",
|
||||||
|
"picomatch": "4.0.2",
|
||||||
"tslib": "^2.3.0",
|
"tslib": "^2.3.0",
|
||||||
"yargs-parser": "21.1.1"
|
"yargs-parser": "21.1.1"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,14 +1,23 @@
|
|||||||
import 'nx/src/internal-testing-utils/mock-project-graph';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
detectPackageManager,
|
||||||
readJson,
|
readJson,
|
||||||
readProjectConfiguration,
|
readProjectConfiguration,
|
||||||
Tree,
|
Tree,
|
||||||
|
updateJson,
|
||||||
updateProjectConfiguration,
|
updateProjectConfiguration,
|
||||||
} from '@nx/devkit';
|
} from '@nx/devkit';
|
||||||
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||||
import { moveGenerator } from './move';
|
import { moveGenerator } from './move';
|
||||||
|
|
||||||
|
jest.mock('@nx/devkit', () => ({
|
||||||
|
...jest.requireActual('@nx/devkit'),
|
||||||
|
createProjectGraphAsync: jest.fn().mockImplementation(async () => ({
|
||||||
|
nodes: {},
|
||||||
|
dependencies: {},
|
||||||
|
})),
|
||||||
|
detectPackageManager: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
// nx-ignore-next-line
|
// nx-ignore-next-line
|
||||||
const { libraryGenerator } = require('@nx/js');
|
const { libraryGenerator } = require('@nx/js');
|
||||||
|
|
||||||
@ -16,6 +25,9 @@ describe('move', () => {
|
|||||||
let tree: Tree;
|
let tree: Tree;
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
||||||
|
(detectPackageManager as jest.Mock).mockImplementation((...args) =>
|
||||||
|
jest.requireActual('@nx/devkit').detectPackageManager(...args)
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should update jest config when moving down directories', async () => {
|
it('should update jest config when moving down directories', async () => {
|
||||||
@ -195,4 +207,93 @@ describe('move', () => {
|
|||||||
// check that the project.json file is not present
|
// check that the project.json file is not present
|
||||||
expect(tree.exists('packages/lib1/project.json')).toBeFalsy();
|
expect(tree.exists('packages/lib1/project.json')).toBeFalsy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should add new destination to the package manager workspaces config when it does not match any existing pattern and it was previously included', async () => {
|
||||||
|
updateJson(tree, 'package.json', (json) => {
|
||||||
|
json.workspaces = ['libs/*'];
|
||||||
|
return json;
|
||||||
|
});
|
||||||
|
await libraryGenerator(tree, { directory: 'libs/lib1' });
|
||||||
|
|
||||||
|
await moveGenerator(tree, {
|
||||||
|
projectName: 'lib1',
|
||||||
|
destination: 'packages/lib1',
|
||||||
|
updateImportPath: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const packageJson = readJson(tree, 'package.json');
|
||||||
|
expect(packageJson.workspaces).toStrictEqual(['libs/*', 'packages/*']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add new destination to the pnpm workspaces config when it does not match any existing pattern and it was previously included', async () => {
|
||||||
|
(detectPackageManager as jest.Mock).mockReturnValue('pnpm');
|
||||||
|
tree.write(
|
||||||
|
'pnpm-workspace.yaml',
|
||||||
|
`packages:
|
||||||
|
- 'libs/*'`
|
||||||
|
);
|
||||||
|
await libraryGenerator(tree, { directory: 'libs/lib1' });
|
||||||
|
|
||||||
|
await moveGenerator(tree, {
|
||||||
|
projectName: 'lib1',
|
||||||
|
destination: 'packages/lib1',
|
||||||
|
updateImportPath: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(tree.read('pnpm-workspace.yaml', 'utf-8')).toMatchInlineSnapshot(`
|
||||||
|
"packages:
|
||||||
|
- 'libs/*'
|
||||||
|
- 'packages/*'
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add the project root to the package manager workspaces config when a more generic pattern would match other projects that were not previously included', async () => {
|
||||||
|
updateJson(tree, 'package.json', (json) => {
|
||||||
|
json.workspaces = ['libs/*'];
|
||||||
|
return json;
|
||||||
|
});
|
||||||
|
await libraryGenerator(tree, { directory: 'libs/lib1' });
|
||||||
|
// extra project that's not part of the package manager workspaces
|
||||||
|
await libraryGenerator(tree, { directory: 'packages/some-package' });
|
||||||
|
|
||||||
|
await moveGenerator(tree, {
|
||||||
|
projectName: 'lib1',
|
||||||
|
destination: 'packages/lib1',
|
||||||
|
updateImportPath: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const packageJson = readJson(tree, 'package.json');
|
||||||
|
expect(packageJson.workspaces).toStrictEqual(['libs/*', 'packages/lib1']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not add new destination to the package manager workspaces config when it was not previously included', async () => {
|
||||||
|
updateJson(tree, 'package.json', (json) => {
|
||||||
|
json.workspaces = ['apps/*'];
|
||||||
|
return json;
|
||||||
|
});
|
||||||
|
await libraryGenerator(tree, { directory: 'libs/lib1' });
|
||||||
|
|
||||||
|
await moveGenerator(tree, {
|
||||||
|
projectName: 'lib1',
|
||||||
|
destination: 'packages/lib1',
|
||||||
|
updateImportPath: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const packageJson = readJson(tree, 'package.json');
|
||||||
|
expect(packageJson.workspaces).toStrictEqual(['apps/*']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not configure package manager workspaces if it was not previously configured', async () => {
|
||||||
|
await libraryGenerator(tree, { directory: 'libs/lib1' });
|
||||||
|
|
||||||
|
await moveGenerator(tree, {
|
||||||
|
projectName: 'lib1',
|
||||||
|
destination: 'packages/lib1',
|
||||||
|
updateImportPath: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const packageJson = readJson(tree, 'package.json');
|
||||||
|
expect(packageJson.workspaces).toBeUndefined();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,11 +1,20 @@
|
|||||||
import {
|
import {
|
||||||
formatFiles,
|
formatFiles,
|
||||||
|
installPackagesTask,
|
||||||
readProjectConfiguration,
|
readProjectConfiguration,
|
||||||
removeProjectConfiguration,
|
removeProjectConfiguration,
|
||||||
Tree,
|
type GeneratorCallback,
|
||||||
|
type Tree,
|
||||||
} from '@nx/devkit';
|
} from '@nx/devkit';
|
||||||
|
import { isProjectIncludedInPackageManagerWorkspaces } from '../../utilities/package-manager-workspaces';
|
||||||
|
import { addProjectToTsSolutionWorkspace } from '../../utilities/typescript/ts-solution-setup';
|
||||||
import { checkDestination } from './lib/check-destination';
|
import { checkDestination } from './lib/check-destination';
|
||||||
import { createProjectConfigurationInNewDestination } from './lib/create-project-configuration-in-new-destination';
|
import { createProjectConfigurationInNewDestination } from './lib/create-project-configuration-in-new-destination';
|
||||||
|
import {
|
||||||
|
maybeExtractJestConfigBase,
|
||||||
|
maybeExtractTsConfigBase,
|
||||||
|
maybeMigrateEslintConfigIfRootProject,
|
||||||
|
} from './lib/extract-base-configs';
|
||||||
import { moveProjectFiles } from './lib/move-project-files';
|
import { moveProjectFiles } from './lib/move-project-files';
|
||||||
import { normalizeSchema } from './lib/normalize-schema';
|
import { normalizeSchema } from './lib/normalize-schema';
|
||||||
import { runAngularPlugin } from './lib/run-angular-plugin';
|
import { runAngularPlugin } from './lib/run-angular-plugin';
|
||||||
@ -20,15 +29,14 @@ import { updatePackageJson } from './lib/update-package-json';
|
|||||||
import { updateProjectRootFiles } from './lib/update-project-root-files';
|
import { updateProjectRootFiles } from './lib/update-project-root-files';
|
||||||
import { updateReadme } from './lib/update-readme';
|
import { updateReadme } from './lib/update-readme';
|
||||||
import { updateStorybookConfig } from './lib/update-storybook-config';
|
import { updateStorybookConfig } from './lib/update-storybook-config';
|
||||||
import {
|
|
||||||
maybeMigrateEslintConfigIfRootProject,
|
|
||||||
maybeExtractJestConfigBase,
|
|
||||||
maybeExtractTsConfigBase,
|
|
||||||
} from './lib/extract-base-configs';
|
|
||||||
import { Schema } from './schema';
|
import { Schema } from './schema';
|
||||||
|
|
||||||
export async function moveGenerator(tree: Tree, rawSchema: Schema) {
|
export async function moveGenerator(tree: Tree, rawSchema: Schema) {
|
||||||
let projectConfig = readProjectConfiguration(tree, rawSchema.projectName);
|
let projectConfig = readProjectConfiguration(tree, rawSchema.projectName);
|
||||||
|
const wasIncludedInWorkspaces = isProjectIncludedInPackageManagerWorkspaces(
|
||||||
|
tree,
|
||||||
|
projectConfig.root
|
||||||
|
);
|
||||||
const schema = await normalizeSchema(tree, rawSchema, projectConfig);
|
const schema = await normalizeSchema(tree, rawSchema, projectConfig);
|
||||||
checkDestination(tree, schema, rawSchema.destination);
|
checkDestination(tree, schema, rawSchema.destination);
|
||||||
|
|
||||||
@ -61,9 +69,29 @@ export async function moveGenerator(tree: Tree, rawSchema: Schema) {
|
|||||||
|
|
||||||
await runAngularPlugin(tree, schema);
|
await runAngularPlugin(tree, schema);
|
||||||
|
|
||||||
|
let task: GeneratorCallback;
|
||||||
|
if (wasIncludedInWorkspaces) {
|
||||||
|
// check if the new destination is included in the package manager workspaces
|
||||||
|
const isIncludedInWorkspaces = isProjectIncludedInPackageManagerWorkspaces(
|
||||||
|
tree,
|
||||||
|
schema.destination
|
||||||
|
);
|
||||||
|
if (!isIncludedInWorkspaces) {
|
||||||
|
// the new destination is not included in the package manager workspaces
|
||||||
|
// so we need to add it and run a package install to ensure the symlink
|
||||||
|
// is created
|
||||||
|
await addProjectToTsSolutionWorkspace(tree, schema.destination);
|
||||||
|
task = () => installPackagesTask(tree, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!schema.skipFormat) {
|
if (!schema.skipFormat) {
|
||||||
await formatFiles(tree);
|
await formatFiles(tree);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (task) {
|
||||||
|
return task;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default moveGenerator;
|
export default moveGenerator;
|
||||||
|
|||||||
@ -0,0 +1,49 @@
|
|||||||
|
import { detectPackageManager, readJson, type Tree } from '@nx/devkit';
|
||||||
|
import { join } from 'node:path/posix';
|
||||||
|
import { getGlobPatternsFromPackageManagerWorkspaces } from 'nx/src/plugins/package-json';
|
||||||
|
import { PackageJson } from 'nx/src/utils/package-json';
|
||||||
|
import picomatch = require('picomatch');
|
||||||
|
|
||||||
|
export function isProjectIncludedInPackageManagerWorkspaces(
|
||||||
|
tree: Tree,
|
||||||
|
projectRoot: string
|
||||||
|
): boolean {
|
||||||
|
if (!isUsingPackageManagerWorkspaces(tree)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const patterns = getPackageManagerWorkspacesPatterns(tree);
|
||||||
|
|
||||||
|
return patterns.some((p) => picomatch(p)(join(projectRoot, 'package.json')));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPackageManagerWorkspacesPatterns(tree: Tree): string[] {
|
||||||
|
return getGlobPatternsFromPackageManagerWorkspaces(
|
||||||
|
tree.root,
|
||||||
|
(path) => readJson(tree, path, { expectComments: true }),
|
||||||
|
(path) => {
|
||||||
|
const content = tree.read(path, 'utf-8');
|
||||||
|
const { load } = require('@zkochan/js-yaml');
|
||||||
|
return load(content, { filename: path });
|
||||||
|
},
|
||||||
|
(path) => tree.exists(path)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isUsingPackageManagerWorkspaces(tree: Tree): boolean {
|
||||||
|
return isWorkspacesEnabled(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isWorkspacesEnabled(tree: Tree): boolean {
|
||||||
|
const packageManager = detectPackageManager(tree.root);
|
||||||
|
if (packageManager === 'pnpm') {
|
||||||
|
return tree.exists('pnpm-workspace.yaml');
|
||||||
|
}
|
||||||
|
|
||||||
|
// yarn and npm both use the same 'workspaces' property in package.json
|
||||||
|
if (tree.exists('package.json')) {
|
||||||
|
const packageJson = readJson<PackageJson>(tree, 'package.json');
|
||||||
|
return !!packageJson?.workspaces;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
@ -1,11 +1,17 @@
|
|||||||
import {
|
import {
|
||||||
detectPackageManager,
|
detectPackageManager,
|
||||||
|
globAsync,
|
||||||
readJson,
|
readJson,
|
||||||
type Tree,
|
type Tree,
|
||||||
workspaceRoot,
|
workspaceRoot,
|
||||||
} from '@nx/devkit';
|
} from '@nx/devkit';
|
||||||
|
import { dirname } from 'node:path/posix';
|
||||||
import { FsTree } from 'nx/src/generators/tree';
|
import { FsTree } from 'nx/src/generators/tree';
|
||||||
import { type PackageJson } from 'nx/src/utils/package-json';
|
import { type PackageJson } from 'nx/src/utils/package-json';
|
||||||
|
import {
|
||||||
|
getPackageManagerWorkspacesPatterns,
|
||||||
|
isProjectIncludedInPackageManagerWorkspaces,
|
||||||
|
} from '../package-manager-workspaces';
|
||||||
|
|
||||||
function isUsingPackageManagerWorkspaces(tree: Tree): boolean {
|
function isUsingPackageManagerWorkspaces(tree: Tree): boolean {
|
||||||
return isWorkspacesEnabled(tree);
|
return isWorkspacesEnabled(tree);
|
||||||
@ -75,3 +81,62 @@ export function isUsingTsSolutionSetup(tree?: Tree): boolean {
|
|||||||
isWorkspaceSetupWithTsSolution(tree)
|
isWorkspaceSetupWithTsSolution(tree)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function addProjectToTsSolutionWorkspace(
|
||||||
|
tree: Tree,
|
||||||
|
projectDir: string
|
||||||
|
) {
|
||||||
|
const isIncluded = isProjectIncludedInPackageManagerWorkspaces(
|
||||||
|
tree,
|
||||||
|
projectDir
|
||||||
|
);
|
||||||
|
if (isIncluded) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If dir is "libs/foo", we try to use "libs/*" but we only do it if it's
|
||||||
|
// safe to do so. So, we first check if adding that pattern doesn't result
|
||||||
|
// in extra projects being matched. If extra projects are matched, or the
|
||||||
|
// dir is just "foo" then we add it as is.
|
||||||
|
const baseDir = dirname(projectDir);
|
||||||
|
let pattern = projectDir;
|
||||||
|
if (baseDir !== '.') {
|
||||||
|
const patterns = getPackageManagerWorkspacesPatterns(tree);
|
||||||
|
const projectsBefore = await globAsync(tree, patterns);
|
||||||
|
patterns.push(`${baseDir}/*/package.json`);
|
||||||
|
const projectsAfter = await globAsync(tree, patterns);
|
||||||
|
|
||||||
|
if (projectsBefore.length + 1 === projectsAfter.length) {
|
||||||
|
// Adding the pattern to the parent directory only results in one extra
|
||||||
|
// project being matched, which is the project we're adding. It's safe
|
||||||
|
// to add the pattern to the parent directory.
|
||||||
|
pattern = `${baseDir}/*`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tree.exists('pnpm-workspace.yaml')) {
|
||||||
|
const { load, dump } = require('@zkochan/js-yaml');
|
||||||
|
const workspaceFile = tree.read('pnpm-workspace.yaml', 'utf-8');
|
||||||
|
const yamlData = load(workspaceFile) ?? {};
|
||||||
|
yamlData.packages ??= [];
|
||||||
|
|
||||||
|
if (!yamlData.packages.includes(pattern)) {
|
||||||
|
yamlData.packages.push(pattern);
|
||||||
|
tree.write(
|
||||||
|
'pnpm-workspace.yaml',
|
||||||
|
dump(yamlData, { indent: 2, quotingType: '"', forceQuotes: true })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Update package.json
|
||||||
|
const packageJson = readJson(tree, 'package.json');
|
||||||
|
if (!packageJson.workspaces) {
|
||||||
|
packageJson.workspaces = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!packageJson.workspaces.includes(pattern)) {
|
||||||
|
packageJson.workspaces.push(pattern);
|
||||||
|
tree.write('package.json', JSON.stringify(packageJson, null, 2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user