From b44dbf9c22e39a659561073a172cacb8c98a4397 Mon Sep 17 00:00:00 2001 From: Craigory Coppola Date: Thu, 20 Apr 2023 17:15:48 -0400 Subject: [PATCH] fix(nx-plugin): correct importPath and import updates for migration to local plugins (#16437) --- docs/generated/cli/workspace-generator.md | 2 +- .../nx/documents/workspace-generator.md | 2 +- packages/nx/src/command-line/nx-commands.ts | 4 +- .../src/command-line/workspace-generators.ts | 2 +- ...rkspace-generators-to-local-plugin.spec.ts | 34 +++++++++ ...ve-workspace-generators-to-local-plugin.ts | 75 ++++++++++++++++++- 6 files changed, 112 insertions(+), 7 deletions(-) diff --git a/docs/generated/cli/workspace-generator.md b/docs/generated/cli/workspace-generator.md index b5332ac1316c4..edb0b80d3f48a 100644 --- a/docs/generated/cli/workspace-generator.md +++ b/docs/generated/cli/workspace-generator.md @@ -12,7 +12,7 @@ description: 'Runs a workspace generator from the tools/generators directory' ## Usage ```shell -nx workspace-generator [name] +nx workspace-generator [generator] ``` Install `nx` globally to invoke the command directly using `nx`, or use `npx nx`, `yarn nx`, or `pnpm nx`. diff --git a/docs/generated/packages/nx/documents/workspace-generator.md b/docs/generated/packages/nx/documents/workspace-generator.md index b5332ac1316c4..edb0b80d3f48a 100644 --- a/docs/generated/packages/nx/documents/workspace-generator.md +++ b/docs/generated/packages/nx/documents/workspace-generator.md @@ -12,7 +12,7 @@ description: 'Runs a workspace generator from the tools/generators directory' ## Usage ```shell -nx workspace-generator [name] +nx workspace-generator [generator] ``` Install `nx` globally to invoke the command directly using `nx`, or use `npx nx`, `yarn nx`, or `pnpm nx`. diff --git a/packages/nx/src/command-line/nx-commands.ts b/packages/nx/src/command-line/nx-commands.ts index 78ce53be2305b..efa385c32a4ab 100644 --- a/packages/nx/src/command-line/nx-commands.ts +++ b/packages/nx/src/command-line/nx-commands.ts @@ -238,11 +238,11 @@ export const commandsObject = yargs * @deprecated(v17): Remove `workspace-generator in v17. Use local plugins. */ .command({ - command: 'workspace-generator [name]', + command: 'workspace-generator [generator]', describe: 'Runs a workspace generator from the tools/generators directory', deprecated: 'Use a local plugin instead. See: https://nx.dev/deprecated/workspace-generators', - aliases: ['workspace-schematic [name]'], + aliases: ['workspace-schematic [generator]'], builder: async (yargs) => linkToNxDevAndExamples(withGenerateOptions(yargs), 'workspace-generator'), handler: workspaceGeneratorHandler, diff --git a/packages/nx/src/command-line/workspace-generators.ts b/packages/nx/src/command-line/workspace-generators.ts index df8981abff628..be1041d790ffc 100644 --- a/packages/nx/src/command-line/workspace-generators.ts +++ b/packages/nx/src/command-line/workspace-generators.ts @@ -13,7 +13,7 @@ export async function workspaceGenerators(args: yargs.Arguments) { const generator = process.argv.slice(3); output.warn({ - title: `${NX_PREFIX} Workspace Generators are no longer supported`, + title: `Workspace Generators are no longer supported`, bodyLines: [ 'Instead, Nx now supports executing generators or executors from ', 'local plugins. To run a generator from a local plugin, ', diff --git a/packages/workspace/src/migrations/update-16-0-0/move-workspace-generators-to-local-plugin.spec.ts b/packages/workspace/src/migrations/update-16-0-0/move-workspace-generators-to-local-plugin.spec.ts index ac9fa31229cd2..f9bb7c51b91ff 100644 --- a/packages/workspace/src/migrations/update-16-0-0/move-workspace-generators-to-local-plugin.spec.ts +++ b/packages/workspace/src/migrations/update-16-0-0/move-workspace-generators-to-local-plugin.spec.ts @@ -101,6 +101,40 @@ describe('move-workspace-generators-to-local-plugin', () => { assertValidGenerator(tree, config, generator); } }); + + it('should update imports that point to a parent directory', async () => { + await workspaceGeneratorGenerator(tree, { + name: 'my-generator', + }); + tree.write('tools/utils/index.ts', 'export default function someUtil() {}'); + tree.write( + 'tools/generators/my-generator/index.ts', + 'import { someUtil } from "../../utils";' + ); + tree.write( + 'tools/generators/my-generator/lib/index.ts', + 'import { someUtil } from "../../../utils";' + ); + await generator(tree); + + const config = readProjectConfiguration(tree, 'workspace-plugin'); + const generatorImplPath = joinPathFragments( + config.root, + 'src/generators/my-generator/index.ts' + ); + const generatorImpl = tree.read(generatorImplPath).toString('utf-8'); + expect(generatorImpl).toContain( + `import { someUtil } from '../../../../utils';` + ); + const generatorLibImplPath = joinPathFragments( + config.root, + 'src/generators/my-generator/lib/index.ts' + ); + const generatorLibImpl = tree.read(generatorLibImplPath).toString('utf-8'); + expect(generatorLibImpl).toContain( + `import { someUtil } from '../../../../../utils';` + ); + }); }); function assertValidGenerator( diff --git a/packages/workspace/src/migrations/update-16-0-0/move-workspace-generators-to-local-plugin.ts b/packages/workspace/src/migrations/update-16-0-0/move-workspace-generators-to-local-plugin.ts index 1a200b2d12de3..1c097e59b9223 100644 --- a/packages/workspace/src/migrations/update-16-0-0/move-workspace-generators-to-local-plugin.ts +++ b/packages/workspace/src/migrations/update-16-0-0/move-workspace-generators-to-local-plugin.ts @@ -22,6 +22,7 @@ import { import { moveGenerator } from '../../generators/move/move'; import { nxVersion } from '../../utils/versions'; import { PackageJson } from 'nx/src/utils/package-json'; +import { posix } from 'path'; const PROJECT_NAME = 'workspace-plugin'; const DESTINATION = `tools/${PROJECT_NAME}`; @@ -47,6 +48,7 @@ export default async function (tree: Tree) { project = readProjectConfiguration(tree, PROJECT_NAME); } await updateExistingPlugin(tree, project); + removeToolsGeneratorsIfEmpty(tree); await formatFiles(tree); return () => { for (const task of tasks) { @@ -55,6 +57,16 @@ export default async function (tree: Tree) { }; } +function removeToolsGeneratorsIfEmpty(tree: Tree) { + const children = tree.children('tools/generators'); + if ( + children.length === 0 || + (children.length === 1 && children[0] === '.gitkeep') + ) { + tree.delete('tools/generators'); + } +} + // Inspired by packages/nx/src/command-line/workspace-generators.ts function collectAndMoveGenerators(tree: Tree, destinationProjectRoot: string) { const generators: Record = {}; @@ -74,12 +86,71 @@ function collectAndMoveGenerators(tree: Tree, destinationProjectRoot: string) { schema: `./src/generators/${joinPathFragments(c, 'schema.json')}`, description: schema.description ?? `Generator ${c}`, }; - tree.rename(childDir, joinPathFragments(destinationDir, c)); + moveFilesInDirectory( + tree, + childDir, + joinPathFragments(destinationDir, c) + ); } } return generators; } +function moveFilesInDirectory(tree: Tree, source: string, destination: string) { + const relative = posix.relative(source, posix.dirname(destination)); + if (!relative.startsWith('../')) { + // If the destination is in the same directory or a subdirectory of the source + // we can just move the files. If it is not, we need to update the relative imports. + return; + } + let offsetLevel = 0; + const pathParts = relative.split('/'); + for (const part of pathParts) { + if (part === '..') { + offsetLevel++; + } else { + break; + } + } + for (const c of tree.children(source)) { + if (!tree.isFile(c)) { + moveFilesInDirectory( + tree, + joinPathFragments(source, c), + joinPathFragments(destination, c) + ); + } + tree.rename( + joinPathFragments(source, c), + joinPathFragments(destination, c) + ); + // If its a TS file we can update relative imports with find + replace + // This could be done with AST, but since we are only looking at relative + // imports its easy to do via string replace. We replace any strings starting + // with a relative path outside of their own directory. + if (c.endsWith('.ts')) { + let content = tree.read(joinPathFragments(destination, c)).toString(); + // +2 is a bit of a magic number here - represents extra directory levels in a normal + // plugin structure compared to the workspace-generator layout + const extraDirectoriesInPluginStructure = 2; + content = content.replace( + new RegExp(`'` + `\.\.\/`.repeat(offsetLevel), 'g'), + "'" + '../'.repeat(offsetLevel + extraDirectoriesInPluginStructure) + ); + content = content.replace( + new RegExp(`"` + `\.\.\/`.repeat(offsetLevel), 'g'), + '"' + '../'.repeat(offsetLevel + extraDirectoriesInPluginStructure) + ); + // We write it back in the same spot, since it is moved as if it was a regular file after this + tree.write(joinPathFragments(source, c), content); + } + tree.rename( + joinPathFragments(source, c), + joinPathFragments(destination, c) + ); + } +} + async function createNewPlugin(tree: Tree) { ensurePackage('@nx/plugin', nxVersion); const { pluginGenerator } = @@ -90,7 +161,7 @@ async function createNewPlugin(tree: Tree) { const { Linter } = ensurePackage('@nx/linter', nxVersion); const { npmScope } = getWorkspaceLayout(tree); - const importPath = npmScope ? `${npmScope}/${PROJECT_NAME}` : PROJECT_NAME; + const importPath = npmScope ? `@${npmScope}/${PROJECT_NAME}` : PROJECT_NAME; await pluginGenerator(tree, { minimal: true,