Skip to content

Commit

Permalink
feat(core): add generator to migrate workspace generators to a local …
Browse files Browse the repository at this point in the history
…plugin
  • Loading branch information
AgentEnder committed Oct 18, 2022
1 parent 34c9fa9 commit dc4e011
Show file tree
Hide file tree
Showing 14 changed files with 393 additions and 10 deletions.
9 changes: 9 additions & 0 deletions docs/generated/packages/workspace.json
Original file line number Diff line number Diff line change
Expand Up @@ -203,11 +203,20 @@
"description": "The name of the project to move.",
"x-dropdown": "projects"
},
"newProjectName": {
"type": "string",
"description": "The name for the project after moving. Overrides the new inferred name if provided."
},
"destination": {
"type": "string",
"description": "The folder to move the project into.",
"$default": { "$source": "argv", "index": 0 }
},
"destinationRelativeToRoot": {
"type": "boolean",
"description": "If true, the provided destination route is relative to the workspace root.",
"default": false
},
"importPath": {
"type": "string",
"description": "The new import path to use in the `tsconfig.base.json`."
Expand Down
4 changes: 4 additions & 0 deletions packages/devkit/testing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,7 @@ export {
createTreeWithEmptyV1Workspace,
} from 'nx/src/generators/testing-utils/create-tree-with-empty-workspace';
export { createTree } from 'nx/src/generators/testing-utils/create-tree';

export function uniq(prefix: string): string {
return `${prefix}${Math.floor(Math.random() * 10000000)}`;
}
3 changes: 1 addition & 2 deletions packages/nx-plugin/.eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@
"error",
"@angular-devkit/architect",
"@angular-devkit/core",
"@angular-devkit/schematics",
"@nrwl/workspace"
"@angular-devkit/schematics"
]
}
},
Expand Down
5 changes: 5 additions & 0 deletions packages/nx-plugin/generators.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@
"factory": "./src/generators/executor/executor#executorSchematic",
"schema": "./src/generators/executor/schema.json",
"description": "Create an executor for an Nx Plugin."
},
"local-plugin-from-tools": {
"factory": "./src/generators/local-plugin-from-tools/generator",
"schema": "./src/generators/local-plugin-from-tools/schema.json",
"description": "Migrate existing workspace-generators to a workspace-tools plugin"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "<%= importPath %>",
"generators": {
<% generators.forEach((generator, idx) => { -%>
"<%= generator.name %>": {
"implementation": "<%= generator.implementation %>",
"schema": "<%= generator.schema %>",
<% if( generator.description ) { -%>
"description": "<%= generator.description %>"
<%_ } -%>
<% if( generator.cli ) { -%>
"cli": "<%= generator.cli %>"
<%_ } -%>
}<%= (idx < generators.length - 1) ? "," : "" %><% }) -%>
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import { createTreeWithEmptyWorkspace, uniq } from '@nrwl/devkit/testing';
import {
Tree,
readProjectConfiguration,
readJson,
joinPathFragments,
GeneratorsJson,
ProjectConfiguration,
getProjects,
} from '@nrwl/devkit';

import generator from './generator';
import workspaceGeneratorGenerator from '@nrwl/workspace/src/generators/workspace-generator/workspace-generator';
import { LocalPluginFromToolsGeneratorSchema } from './schema';

describe('local-plugin-from-tools generator', () => {
let tree: Tree;

const createOptions = (
overrides?: Partial<LocalPluginFromToolsGeneratorSchema>
): Partial<LocalPluginFromToolsGeneratorSchema> => ({
...overrides,
});

beforeEach(() => {
tree = createTreeWithEmptyWorkspace();
});

it('should find single workspace generator successfully', async () => {
await workspaceGeneratorGenerator(tree, {
name: 'my-generator',
skipFormat: false,
});
await generator(tree, createOptions());

const config = readProjectConfiguration(tree, 'workspace-plugin');
expect(config.root).toEqual('tools/workspace-plugin');

assertValidGenerator(tree, config, 'my-generator');
});

it('should convert multiple workspace generators successfully', async () => {
const generators = [...new Array(10)].map((x) => uniq('generator'));
for (const name of generators) {
await workspaceGeneratorGenerator(tree, {
name,
skipFormat: false,
});
}

await generator(tree, createOptions());

const config = readProjectConfiguration(tree, 'workspace-plugin');
expect(config.root).toEqual('tools/workspace-plugin');

for (const generator of generators) {
assertValidGenerator(tree, config, generator);
}
});

describe('--plugin-name', () => {
it('should obey project name', async () => {
await workspaceGeneratorGenerator(tree, {
name: 'my-generator',
skipFormat: false,
});
await generator(
tree,
createOptions({
pluginName: 'workspace-tools',
})
);

const config = readProjectConfiguration(tree, 'workspace-tools');
expect(config.root).toEqual('tools/workspace-tools');

assertValidGenerator(tree, config, 'my-generator');
});
});

describe('--tools-project-root', () => {
it('should place plugin in specified root', async () => {
await workspaceGeneratorGenerator(tree, {
name: 'my-generator',
skipFormat: false,
});
await generator(
tree,
createOptions({
toolsProjectRoot: 'libs/workspace-plugin',
})
);
const config = readProjectConfiguration(tree, 'workspace-plugin');
expect(config.root).toEqual('libs/workspace-plugin');

assertValidGenerator(tree, config, 'my-generator');
});
});

describe('--import-path', () => {
it('should support basic import paths', async () => {
await workspaceGeneratorGenerator(tree, {
name: 'my-generator',
skipFormat: false,
});
await generator(
tree,
createOptions({
importPath: 'workspace-tools',
})
);

const config = readProjectConfiguration(tree, 'workspace-plugin');
expect(config.root).toEqual('tools/workspace-plugin');
expect(
readJson(tree, 'tsconfig.base.json').compilerOptions.paths[
'workspace-tools'
]
).toEqual(['tools/workspace-plugin/src/index.ts']);

assertValidGenerator(tree, config, 'my-generator');
});

it('should support scoped import paths', async () => {
await workspaceGeneratorGenerator(tree, {
name: 'my-generator',
skipFormat: false,
});
await generator(
tree,
createOptions({
importPath: '@workspace/plugin',
})
);

const config = readProjectConfiguration(tree, 'workspace-plugin');
expect(config.root).toEqual('tools/workspace-plugin');
expect(
readJson(tree, 'tsconfig.base.json').compilerOptions.paths[
'@workspace/plugin'
]
).toEqual(['tools/workspace-plugin/src/index.ts']);

assertValidGenerator(tree, config, 'my-generator');
});
});
});

function assertValidGenerator(
tree: Tree,
config: ProjectConfiguration,
generator: string
) {
const generatorsJson = readJson<GeneratorsJson>(
tree,
joinPathFragments(config.root, 'generators.json')
);
expect(generatorsJson.generators).toHaveProperty(generator);
const generatorImplPath = joinPathFragments(
config.root,
generatorsJson.generators[generator].implementation,
'index.ts'
);
expect(tree.exists(generatorImplPath)).toBeTruthy();
const generatorSchemaPath = joinPathFragments(
config.root,
generatorsJson.generators[generator].schema
);
expect(tree.exists(generatorSchemaPath)).toBeTruthy();
}
135 changes: 135 additions & 0 deletions packages/nx-plugin/src/generators/local-plugin-from-tools/generator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import {
formatFiles,
generateFiles,
getWorkspaceLayout,
joinPathFragments,
readJson,
readProjectConfiguration,
Tree,
} from '@nrwl/devkit';
import pluginGenerator from '../plugin/plugin';
import * as path from 'path';
import { LocalPluginFromToolsGeneratorSchema } from './schema';
import { Linter } from '@nrwl/linter';
import { GeneratorsJsonEntry } from 'nx/src/config/misc-interfaces';
import { moveGenerator } from '@nrwl/workspace';

function normalizeOptions(
tree: Tree,
schema: Partial<LocalPluginFromToolsGeneratorSchema>
): LocalPluginFromToolsGeneratorSchema {
const { npmScope } = getWorkspaceLayout(tree);
const pluginName = schema.pluginName ?? 'workspace-plugin';

return {
importPath: schema.importPath ?? `@${npmScope}/${pluginName}`,
pluginName,
toolsProjectRoot:
schema.toolsProjectRoot ?? joinPathFragments('tools', pluginName),
};
}

function addFiles(
tree: Tree,
options: LocalPluginFromToolsGeneratorSchema,
generators: (GeneratorsJsonEntry & { name: string })[]
) {
const templateOptions = {
...options,
generators,
tmpl: '',
};
generateFiles(
tree,
path.join(__dirname, 'files'),
options.toolsProjectRoot,
templateOptions
);
}

export default async function (
tree: Tree,
options: Partial<LocalPluginFromToolsGeneratorSchema>
) {
const normalizedOptions = normalizeOptions(tree, options);
await pluginGenerator(tree, {
minimal: true,
name: normalizedOptions.pluginName,
importPath: normalizedOptions.importPath,
skipTsConfig: false,
compiler: 'tsc',
linter: Linter.EsLint,
skipFormat: true,
skipLintChecks: false,
unitTestRunner: 'jest',
});
await moveGeneratedPlugin(tree, normalizedOptions);
addFiles(
tree,
normalizedOptions,
collectAndMoveGenerators(tree, normalizedOptions)
);
await formatFiles(tree);
}

// Inspired by packages/nx/src/command-line/workspace-generators.ts
function collectAndMoveGenerators(
tree: Tree,
options: LocalPluginFromToolsGeneratorSchema
) {
const generators: ({
name: string;
} & GeneratorsJsonEntry)[] = [];
const generatorsDir = 'tools/generators';
const destinationDir = joinPathFragments(
readProjectConfiguration(tree, options.pluginName).root,
'src',
'generators'
);
for (const c of tree.children('tools/generators')) {
const childDir = path.join(generatorsDir, c);
const schemaPath = joinPathFragments(childDir, 'schema.json');
if (tree.exists(schemaPath)) {
const schema = readJson(tree, schemaPath);
generators.push({
name: c,
implementation: `./src/generators/${c}`,
schema: `./src/generators/${joinPathFragments(c, 'schema.json')}`,
description: schema.description ?? `Generator ${c}`,
});
moveFolder(tree, childDir, joinPathFragments(destinationDir, c));
}
}
return generators;
}

function moveFolder(tree: Tree, source: string, destination: string) {
for (const child of tree.children(source)) {
const existingPath = joinPathFragments(source, child);
const newPath = joinPathFragments(destination, child);
if (tree.isFile(existingPath)) {
tree.write(newPath, tree.read(existingPath));
tree.delete(existingPath);
} else {
moveFolder(tree, existingPath, newPath);
}
}
tree.delete(source);
}

function moveGeneratedPlugin(
tree: Tree,
options: LocalPluginFromToolsGeneratorSchema
) {
const config = readProjectConfiguration(tree, options.pluginName);
if (config.root !== options.toolsProjectRoot) {
return moveGenerator(tree, {
destination: options.toolsProjectRoot,
projectName: options.pluginName,
newProjectName: options.pluginName,
updateImportPath: true,
destinationRelativeToRoot: true,
importPath: options.importPath,
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface LocalPluginFromToolsGeneratorSchema {
pluginName: string;
importPath: string;
toolsProjectRoot: string;
}
Loading

0 comments on commit dc4e011

Please sign in to comment.