Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(nx-plugin): correct importPath and import updates for migration to local plugins #16437

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/generated/cli/workspace-generator.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down
4 changes: 2 additions & 2 deletions packages/nx/src/command-line/nx-commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion packages/nx/src/command-line/workspace-generators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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, ',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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}`;
Expand All @@ -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) {
Expand All @@ -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<string, GeneratorsJsonEntry> = {};
Expand All @@ -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) {
AgentEnder marked this conversation as resolved.
Show resolved Hide resolved
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/nx-plugin', nxVersion);
const { pluginGenerator } =
Expand All @@ -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,
Expand Down