feat(core): add migration to update workspace generators to a local plugin (#12700)
This commit is contained in:
parent
f04f316271
commit
1743ff10ed
@ -5,7 +5,9 @@ description: 'Runs a workspace generator from the tools/generators directory'
|
|||||||
|
|
||||||
# workspace-generator
|
# workspace-generator
|
||||||
|
|
||||||
Runs a workspace generator from the tools/generators directory
|
**Deprecated:** Use a local plugin instead. See: https://nx.dev/deprecated/workspace-generators
|
||||||
|
|
||||||
|
Runs a workspace generator from the tools/generators directory
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
@ -17,23 +19,45 @@ Install `nx` globally to invoke the command directly using `nx`, or use `npx nx`
|
|||||||
|
|
||||||
## Options
|
## Options
|
||||||
|
|
||||||
|
### dryRun
|
||||||
|
|
||||||
|
Type: `boolean`
|
||||||
|
|
||||||
|
Default: `false`
|
||||||
|
|
||||||
|
Preview the changes without updating files
|
||||||
|
|
||||||
|
### generator
|
||||||
|
|
||||||
|
Type: `string`
|
||||||
|
|
||||||
|
Name of the generator (e.g., @nrwl/js:library, library)
|
||||||
|
|
||||||
### help
|
### help
|
||||||
|
|
||||||
Type: `boolean`
|
Type: `boolean`
|
||||||
|
|
||||||
Show help
|
Show help
|
||||||
|
|
||||||
### list-generators
|
### interactive
|
||||||
|
|
||||||
Type: `boolean`
|
Type: `boolean`
|
||||||
|
|
||||||
List the available workspace-generators
|
Default: `true`
|
||||||
|
|
||||||
### name
|
When false disables interactive input prompts for options
|
||||||
|
|
||||||
Type: `string`
|
### quiet
|
||||||
|
|
||||||
The name of your generator
|
Type: `boolean`
|
||||||
|
|
||||||
|
Hides logs from tree operations (e.g. `CREATE package.json`)
|
||||||
|
|
||||||
|
### verbose
|
||||||
|
|
||||||
|
Type: `boolean`
|
||||||
|
|
||||||
|
Prints additional information about the commands (e.g., stack traces)
|
||||||
|
|
||||||
### version
|
### version
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,9 @@ description: 'Runs a workspace generator from the tools/generators directory'
|
|||||||
|
|
||||||
# workspace-generator
|
# workspace-generator
|
||||||
|
|
||||||
Runs a workspace generator from the tools/generators directory
|
**Deprecated:** Use a local plugin instead. See: https://nx.dev/deprecated/workspace-generators
|
||||||
|
|
||||||
|
Runs a workspace generator from the tools/generators directory
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
@ -17,23 +19,45 @@ Install `nx` globally to invoke the command directly using `nx`, or use `npx nx`
|
|||||||
|
|
||||||
## Options
|
## Options
|
||||||
|
|
||||||
|
### dryRun
|
||||||
|
|
||||||
|
Type: `boolean`
|
||||||
|
|
||||||
|
Default: `false`
|
||||||
|
|
||||||
|
Preview the changes without updating files
|
||||||
|
|
||||||
|
### generator
|
||||||
|
|
||||||
|
Type: `string`
|
||||||
|
|
||||||
|
Name of the generator (e.g., @nrwl/js:library, library)
|
||||||
|
|
||||||
### help
|
### help
|
||||||
|
|
||||||
Type: `boolean`
|
Type: `boolean`
|
||||||
|
|
||||||
Show help
|
Show help
|
||||||
|
|
||||||
### list-generators
|
### interactive
|
||||||
|
|
||||||
Type: `boolean`
|
Type: `boolean`
|
||||||
|
|
||||||
List the available workspace-generators
|
Default: `true`
|
||||||
|
|
||||||
### name
|
When false disables interactive input prompts for options
|
||||||
|
|
||||||
Type: `string`
|
### quiet
|
||||||
|
|
||||||
The name of your generator
|
Type: `boolean`
|
||||||
|
|
||||||
|
Hides logs from tree operations (e.g. `CREATE package.json`)
|
||||||
|
|
||||||
|
### verbose
|
||||||
|
|
||||||
|
Type: `boolean`
|
||||||
|
|
||||||
|
Prints additional information about the commands (e.g., stack traces)
|
||||||
|
|
||||||
### version
|
### version
|
||||||
|
|
||||||
|
|||||||
@ -7,20 +7,7 @@
|
|||||||
"title": "Create a custom generator",
|
"title": "Create a custom generator",
|
||||||
"description": "Create a custom generator.",
|
"description": "Create a custom generator.",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {},
|
||||||
"name": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Generator name.",
|
|
||||||
"$default": { "$source": "argv", "index": 0 },
|
|
||||||
"x-prompt": "What name would you like to use for the workspace generator?"
|
|
||||||
},
|
|
||||||
"skipFormat": {
|
|
||||||
"description": "Skip formatting files.",
|
|
||||||
"type": "boolean",
|
|
||||||
"default": false,
|
|
||||||
"x-priority": "internal"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": ["name"],
|
"required": ["name"],
|
||||||
"presets": []
|
"presets": []
|
||||||
},
|
},
|
||||||
|
|||||||
@ -8,6 +8,10 @@ Check the [nx-plugin guide](/packages/nx-plugin) for information on creating a n
|
|||||||
|
|
||||||
## Converting workspace generators to local generators
|
## Converting workspace generators to local generators
|
||||||
|
|
||||||
|
{% callout type=\"info\" %}
|
||||||
|
When migrating to Nx 16, a new workspace plugin is automatically generated in the tools folder if you already have workspace-generators.
|
||||||
|
{% /callout %}
|
||||||
|
|
||||||
- If you don't already have a local plugin, use Nx to generate one:
|
- If you don't already have a local plugin, use Nx to generate one:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
|
|||||||
@ -86,7 +86,6 @@ describe('Nx Commands', () => {
|
|||||||
// check for schematics
|
// check for schematics
|
||||||
expect(listOutput).toContain('workspace');
|
expect(listOutput).toContain('workspace');
|
||||||
expect(listOutput).toContain('library');
|
expect(listOutput).toContain('library');
|
||||||
expect(listOutput).toContain('workspace-generator');
|
|
||||||
|
|
||||||
// check for builders
|
// check for builders
|
||||||
expect(listOutput).toContain('run-commands');
|
expect(listOutput).toContain('run-commands');
|
||||||
|
|||||||
@ -756,180 +756,3 @@ describe('Workspace Tests', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('workspace-generator', () => {
|
|
||||||
const packageManager = getSelectedPackageManager() || 'pnpm';
|
|
||||||
const proj = uniq('workspace');
|
|
||||||
|
|
||||||
beforeAll(() => {
|
|
||||||
runCreateWorkspace(proj, {
|
|
||||||
preset: 'ts',
|
|
||||||
packageManager,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
afterAll(() => cleanupProject());
|
|
||||||
|
|
||||||
let custom: string;
|
|
||||||
let failing: string;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
custom = uniq('custom');
|
|
||||||
failing = uniq('custom-failing');
|
|
||||||
runCLI(`g @nrwl/workspace:workspace-generator ${custom} --no-interactive`);
|
|
||||||
runCLI(`g @nrwl/workspace:workspace-generator ${failing} --no-interactive`);
|
|
||||||
|
|
||||||
checkFilesExist(
|
|
||||||
`tools/generators/${custom}/index.ts`,
|
|
||||||
`tools/generators/${custom}/schema.json`
|
|
||||||
);
|
|
||||||
checkFilesExist(
|
|
||||||
`tools/generators/${failing}/index.ts`,
|
|
||||||
`tools/generators/${failing}/schema.json`
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should compile only generator files with dependencies', () => {
|
|
||||||
const workspace = uniq('workspace');
|
|
||||||
|
|
||||||
updateFile(
|
|
||||||
'tools/utils/command-line-utils.ts',
|
|
||||||
`
|
|
||||||
export const noop = () => {}
|
|
||||||
`
|
|
||||||
);
|
|
||||||
updateFile(
|
|
||||||
'tools/utils/logger.ts',
|
|
||||||
`
|
|
||||||
export const log = (...args: any[]) => console.log(...args)
|
|
||||||
`
|
|
||||||
);
|
|
||||||
updateFile(
|
|
||||||
`tools/generators/utils.ts`,
|
|
||||||
`
|
|
||||||
export const noop = ()=>{}
|
|
||||||
`
|
|
||||||
);
|
|
||||||
updateFile(`tools/generators/${custom}/index.ts`, (content) => {
|
|
||||||
return `
|
|
||||||
import { log } from '../../utils/logger'; \n
|
|
||||||
${content}
|
|
||||||
`;
|
|
||||||
});
|
|
||||||
|
|
||||||
runCLI(`workspace-generator ${custom} ${workspace} --no-interactive -d`);
|
|
||||||
|
|
||||||
expect(() =>
|
|
||||||
checkFilesExist(
|
|
||||||
`dist/out-tsc/tools/generators/${custom}/index.js`,
|
|
||||||
`dist/out-tsc/tools/generators/utils.js`,
|
|
||||||
`dist/out-tsc/tools/utils/logger.js`
|
|
||||||
)
|
|
||||||
).not.toThrow();
|
|
||||||
expect(() =>
|
|
||||||
checkFilesExist(`dist/out-tsc/tools/utils/utils.js`)
|
|
||||||
).toThrow();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should support workspace-specific generators', async () => {
|
|
||||||
const json = readJson(`tools/generators/${custom}/schema.json`);
|
|
||||||
json.properties['directory'] = {
|
|
||||||
type: 'string',
|
|
||||||
description: 'lib directory',
|
|
||||||
};
|
|
||||||
json.properties['skipTsConfig'] = {
|
|
||||||
type: 'boolean',
|
|
||||||
description: 'skip changes to tsconfig',
|
|
||||||
};
|
|
||||||
json.properties['inlineprop'] = json.properties['name'];
|
|
||||||
json.required = ['inlineprop'];
|
|
||||||
delete json.properties['name'];
|
|
||||||
|
|
||||||
updateFile(`tools/generators/${custom}/schema.json`, JSON.stringify(json));
|
|
||||||
|
|
||||||
const indexFile = readFile(`tools/generators/${custom}/index.ts`);
|
|
||||||
updateFile(
|
|
||||||
`tools/generators/${custom}/index.ts`,
|
|
||||||
indexFile.replace(
|
|
||||||
'name: schema.name',
|
|
||||||
'name: schema.inlineprop, directory: schema.directory, skipTsConfig: schema.skipTsConfig'
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
const helpOutput = runCLI(`workspace-generator ${custom} --help`);
|
|
||||||
expect(helpOutput).toContain(
|
|
||||||
`workspace-generator ${custom} [inlineprop] (options)`
|
|
||||||
);
|
|
||||||
expect(helpOutput).toContain(`--directory`);
|
|
||||||
expect(helpOutput).toContain(`--skipTsConfig`);
|
|
||||||
|
|
||||||
const workspace = uniq('workspace');
|
|
||||||
const dryRunOutput = runCLI(
|
|
||||||
`workspace-generator ${custom} ${workspace} --no-interactive --directory=dir --skipTsConfig=true -d`
|
|
||||||
);
|
|
||||||
expect(exists(`packages/dir/${workspace}/src/index.ts`)).toEqual(false);
|
|
||||||
expect(dryRunOutput).toContain(
|
|
||||||
`CREATE packages/dir/${workspace}/src/index.ts`
|
|
||||||
);
|
|
||||||
|
|
||||||
runCLI(
|
|
||||||
`workspace-generator ${custom} ${workspace} --no-interactive --directory=dir`
|
|
||||||
);
|
|
||||||
checkFilesExist(`packages/dir/${workspace}/src/index.ts`);
|
|
||||||
|
|
||||||
const jsonFailing = readJson(`tools/generators/${failing}/schema.json`);
|
|
||||||
jsonFailing.properties = {};
|
|
||||||
jsonFailing.required = [];
|
|
||||||
updateFile(
|
|
||||||
`tools/generators/${failing}/schema.json`,
|
|
||||||
JSON.stringify(jsonFailing)
|
|
||||||
);
|
|
||||||
|
|
||||||
updateFile(
|
|
||||||
`tools/generators/${failing}/index.ts`,
|
|
||||||
`
|
|
||||||
export default function() {
|
|
||||||
throw new Error();
|
|
||||||
}
|
|
||||||
`
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
|
||||||
await runCLI(`workspace-generator ${failing} --no-interactive`);
|
|
||||||
fail(`Should exit 1 for a workspace-generator that throws an error`);
|
|
||||||
} catch (e) {}
|
|
||||||
|
|
||||||
const listOutput = runCLI('workspace-generator --list-generators');
|
|
||||||
expect(listOutput).toContain(custom);
|
|
||||||
expect(listOutput).toContain(failing);
|
|
||||||
}, 1000000);
|
|
||||||
|
|
||||||
it('should support angular devkit schematics', () => {
|
|
||||||
const angularDevkitSchematic = uniq('angular-devkit-schematic');
|
|
||||||
runCLI(
|
|
||||||
`g @nrwl/workspace:workspace-generator ${angularDevkitSchematic} --no-interactive`
|
|
||||||
);
|
|
||||||
|
|
||||||
const json = readJson(
|
|
||||||
`tools/generators/${angularDevkitSchematic}/schema.json`
|
|
||||||
);
|
|
||||||
json.properties = {};
|
|
||||||
json.required = [];
|
|
||||||
delete json.cli;
|
|
||||||
updateFile(
|
|
||||||
`tools/generators/${angularDevkitSchematic}/schema.json`,
|
|
||||||
JSON.stringify(json)
|
|
||||||
);
|
|
||||||
|
|
||||||
updateFile(
|
|
||||||
`tools/generators/${angularDevkitSchematic}/index.ts`,
|
|
||||||
`
|
|
||||||
export default function() {
|
|
||||||
return (tree) => tree;
|
|
||||||
}
|
|
||||||
`
|
|
||||||
);
|
|
||||||
|
|
||||||
runCLI(`workspace-generator ${angularDevkitSchematic} --no-interactive`);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|||||||
@ -259,9 +259,6 @@ describe('Nx Plugin', () => {
|
|||||||
expect(results).not.toContain(goodMigration);
|
expect(results).not.toContain(goodMigration);
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* @todo(@AgentEnder): reenable after figuring out @swc-node
|
|
||||||
*/
|
|
||||||
describe('local plugins', () => {
|
describe('local plugins', () => {
|
||||||
let plugin: string;
|
let plugin: string;
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@ -368,6 +365,28 @@ describe('Nx Plugin', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('workspace-generator', () => {
|
||||||
|
let custom: string;
|
||||||
|
|
||||||
|
it('should work with generate wrapper', () => {
|
||||||
|
custom = uniq('custom');
|
||||||
|
const project = uniq('generated-project');
|
||||||
|
runCLI(`g @nrwl/nx-plugin:plugin workspace-plugin --no-interactive`);
|
||||||
|
runCLI(
|
||||||
|
`g @nrwl/nx-plugin:generator ${custom} --project workspace-plugin --no-interactive`
|
||||||
|
);
|
||||||
|
runCLI(
|
||||||
|
`workspace-generator ${custom} --name ${project} --no-interactive`
|
||||||
|
);
|
||||||
|
expect(() => {
|
||||||
|
checkFilesExist(
|
||||||
|
`libs/${project}/src/index.ts`,
|
||||||
|
`libs/${project}/project.json`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('--directory', () => {
|
describe('--directory', () => {
|
||||||
it('should create a plugin in the specified directory', () => {
|
it('should create a plugin in the specified directory', () => {
|
||||||
const plugin = uniq('plugin');
|
const plugin = uniq('plugin');
|
||||||
|
|||||||
@ -23,8 +23,7 @@
|
|||||||
"error",
|
"error",
|
||||||
"@angular-devkit/architect",
|
"@angular-devkit/architect",
|
||||||
"@angular-devkit/core",
|
"@angular-devkit/core",
|
||||||
"@angular-devkit/schematics",
|
"@angular-devkit/schematics"
|
||||||
"@nx/workspace"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -22,6 +22,7 @@ import { addTsLibDependencies } from '@nx/js/src/utils/typescript/add-tslib-depe
|
|||||||
import { addSwcRegisterDependencies } from '@nx/js/src/utils/swc/add-swc-dependencies';
|
import { addSwcRegisterDependencies } from '@nx/js/src/utils/swc/add-swc-dependencies';
|
||||||
|
|
||||||
import type { Schema } from './schema';
|
import type { Schema } from './schema';
|
||||||
|
import { tsLibVersion } from '@nx/js/src/utils/versions';
|
||||||
|
|
||||||
const nxVersion = require('../../../package.json').version;
|
const nxVersion = require('../../../package.json').version;
|
||||||
|
|
||||||
@ -87,6 +88,7 @@ export async function pluginGenerator(host: Tree, schema: Schema) {
|
|||||||
addDependenciesToPackageJson(
|
addDependenciesToPackageJson(
|
||||||
host,
|
host,
|
||||||
{
|
{
|
||||||
|
tslib: tsLibVersion,
|
||||||
'@nx/devkit': nxVersion,
|
'@nx/devkit': nxVersion,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@ -266,16 +266,17 @@ export const commandsObject = yargs
|
|||||||
process.exit(0);
|
process.exit(0);
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
/**
|
||||||
|
* @deprecated(v17): Remove `workspace-generator in v17. Use local plugins.
|
||||||
|
*/
|
||||||
.command({
|
.command({
|
||||||
command: 'workspace-generator [name]',
|
command: 'workspace-generator [name]',
|
||||||
describe: 'Runs a workspace generator from the tools/generators directory',
|
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 [name]'],
|
||||||
builder: async (yargs) =>
|
builder: async (yargs) =>
|
||||||
linkToNxDevAndExamples(
|
linkToNxDevAndExamples(withGenerateOptions(yargs), 'workspace-generator'),
|
||||||
await withWorkspaceGeneratorOptions(yargs, process.argv.slice(3)),
|
|
||||||
'workspace-generator'
|
|
||||||
),
|
|
||||||
handler: workspaceGeneratorHandler,
|
handler: workspaceGeneratorHandler,
|
||||||
})
|
})
|
||||||
.command({
|
.command({
|
||||||
@ -824,142 +825,11 @@ function withRunOneOptions(yargs: yargs.Argv) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type OptionArgumentDefinition = {
|
/**
|
||||||
type: yargs.Options['type'];
|
* @deprecated(v17): Remove `workspace-generator in v17. Use local plugins.
|
||||||
describe?: string;
|
*/
|
||||||
default?: any;
|
async function workspaceGeneratorHandler(args: yargs.Arguments) {
|
||||||
choices?: yargs.Options['type'][];
|
await (await import('./workspace-generators')).workspaceGenerators(args);
|
||||||
demandOption?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
type WorkspaceGeneratorProperties = {
|
|
||||||
[name: string]:
|
|
||||||
| {
|
|
||||||
type: yargs.Options['type'];
|
|
||||||
description?: string;
|
|
||||||
default?: any;
|
|
||||||
enum?: yargs.Options['type'][];
|
|
||||||
demandOption?: boolean;
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: yargs.PositionalOptionsType;
|
|
||||||
description?: string;
|
|
||||||
default?: any;
|
|
||||||
enum?: yargs.PositionalOptionsType[];
|
|
||||||
$default: {
|
|
||||||
$source: 'argv';
|
|
||||||
index: number;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
function isPositionalProperty(
|
|
||||||
property: WorkspaceGeneratorProperties[keyof WorkspaceGeneratorProperties]
|
|
||||||
): property is { type: yargs.PositionalOptionsType } {
|
|
||||||
return property['$default']?.['$source'] === 'argv';
|
|
||||||
}
|
|
||||||
|
|
||||||
async function withWorkspaceGeneratorOptions(
|
|
||||||
yargs: yargs.Argv,
|
|
||||||
args: string[]
|
|
||||||
) {
|
|
||||||
// filter out only positional arguments
|
|
||||||
args = args.filter((a) => !a.startsWith('-'));
|
|
||||||
if (args.length) {
|
|
||||||
// this is an actual workspace generator
|
|
||||||
return withCustomGeneratorOptions(yargs, args[0]);
|
|
||||||
} else {
|
|
||||||
yargs
|
|
||||||
.option('list-generators', {
|
|
||||||
describe: 'List the available workspace-generators',
|
|
||||||
type: 'boolean',
|
|
||||||
})
|
|
||||||
.positional('name', {
|
|
||||||
type: 'string',
|
|
||||||
describe: 'The name of your generator',
|
|
||||||
});
|
|
||||||
/**
|
|
||||||
* Don't require `name` if only listing available
|
|
||||||
* schematics
|
|
||||||
*/
|
|
||||||
if ((await yargs.argv).listGenerators !== true) {
|
|
||||||
yargs.demandOption('name');
|
|
||||||
}
|
|
||||||
return yargs;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function withCustomGeneratorOptions(
|
|
||||||
yargs: yargs.Argv,
|
|
||||||
generatorName: string
|
|
||||||
) {
|
|
||||||
const schema = (
|
|
||||||
await import('./workspace-generators')
|
|
||||||
).workspaceGeneratorSchema(generatorName);
|
|
||||||
const options = [];
|
|
||||||
const positionals = [];
|
|
||||||
|
|
||||||
Object.entries(
|
|
||||||
(schema.properties ?? {}) as WorkspaceGeneratorProperties
|
|
||||||
).forEach(([name, prop]) => {
|
|
||||||
const option: { name: string; definition: OptionArgumentDefinition } = {
|
|
||||||
name,
|
|
||||||
definition: {
|
|
||||||
describe: prop.description,
|
|
||||||
type: prop.type,
|
|
||||||
default: prop.default,
|
|
||||||
choices: prop.enum,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
if (schema.required && schema.required.includes(name)) {
|
|
||||||
option.definition.demandOption = true;
|
|
||||||
}
|
|
||||||
options.push(option);
|
|
||||||
if (isPositionalProperty(prop)) {
|
|
||||||
positionals.push({
|
|
||||||
name,
|
|
||||||
definition: {
|
|
||||||
describe: prop.description,
|
|
||||||
type: prop.type,
|
|
||||||
choices: prop.enum,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let command = generatorName;
|
|
||||||
positionals.forEach(({ name }) => {
|
|
||||||
command += ` [${name}]`;
|
|
||||||
});
|
|
||||||
if (options.length) {
|
|
||||||
command += ' (options)';
|
|
||||||
}
|
|
||||||
|
|
||||||
yargs
|
|
||||||
.command({
|
|
||||||
// this is the default and only command
|
|
||||||
command,
|
|
||||||
describe: schema.description || '',
|
|
||||||
builder: (y) => {
|
|
||||||
options.forEach(({ name, definition }) => {
|
|
||||||
y.option(name, definition);
|
|
||||||
});
|
|
||||||
positionals.forEach(({ name, definition }) => {
|
|
||||||
y.positional(name, definition);
|
|
||||||
});
|
|
||||||
return linkToNxDevAndExamples(y, 'workspace-generator');
|
|
||||||
},
|
|
||||||
handler: workspaceGeneratorHandler,
|
|
||||||
})
|
|
||||||
.fail(() => void 0); // no action is needed on failure as Nx will handle it based on schema validation
|
|
||||||
|
|
||||||
return yargs;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function workspaceGeneratorHandler() {
|
|
||||||
await (
|
|
||||||
await import('./workspace-generators')
|
|
||||||
).workspaceGenerators(process.argv.slice(3));
|
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,213 +1,35 @@
|
|||||||
import * as chalk from 'chalk';
|
import yargs = require('yargs');
|
||||||
import { execSync } from 'child_process';
|
import { readNxJson } from '../config/configuration';
|
||||||
import { readdirSync, existsSync } from 'fs';
|
import { NxJsonConfiguration } from '../devkit-exports';
|
||||||
import { copySync, removeSync } from 'fs-extra';
|
import { NX_PREFIX } from '../utils/logger';
|
||||||
import * as path from 'path';
|
|
||||||
import * as yargsParser from 'yargs-parser';
|
|
||||||
import { workspaceRoot } from '../utils/workspace-root';
|
|
||||||
import { fileExists } from '../utils/fileutils';
|
|
||||||
import { output } from '../utils/output';
|
import { output } from '../utils/output';
|
||||||
import type { CompilerOptions } from 'typescript';
|
|
||||||
import { generate } from './generate';
|
|
||||||
import { readJsonFile, writeJsonFile } from '../utils/fileutils';
|
|
||||||
import { logger } from '../utils/logger';
|
|
||||||
import { getPackageManagerCommand } from '../utils/package-manager';
|
|
||||||
import { normalizePath } from '../utils/path';
|
|
||||||
import { parserConfiguration } from './nx-commands';
|
|
||||||
|
|
||||||
const rootDirectory = workspaceRoot;
|
/**
|
||||||
const toolsDir = path.join(rootDirectory, 'tools');
|
* Wraps `workspace-generator` to invoke `generate`.
|
||||||
const generatorsDir = path.join(toolsDir, 'generators');
|
*
|
||||||
const toolsTsConfigPath = path.join(toolsDir, 'tsconfig.tools.json');
|
* @deprecated(v17): Remove `workspace-generator in v17. Use local plugins.
|
||||||
|
*/
|
||||||
|
export async function workspaceGenerators(args: yargs.Arguments) {
|
||||||
|
const generator = process.argv.slice(3);
|
||||||
|
|
||||||
type TsConfig = {
|
output.warn({
|
||||||
extends: string;
|
title: `${NX_PREFIX} Workspace Generators are no longer supported`,
|
||||||
compilerOptions: CompilerOptions;
|
bodyLines: [
|
||||||
files?: string[];
|
'Instead, Nx now supports executing generators or executors from ',
|
||||||
include?: string[];
|
'local plugins. To run a generator from a local plugin, ',
|
||||||
exclude?: string[];
|
'use `nx generate` like you would with any other generator.',
|
||||||
references?: Array<{ path: string }>;
|
'',
|
||||||
};
|
'For more information, see: https://nx.dev/deprecated/workspace-generators',
|
||||||
|
],
|
||||||
export async function workspaceGenerators(args: string[]) {
|
|
||||||
const outDir = compileTools();
|
|
||||||
const collectionFile = path.join(outDir, 'workspace-generators.json');
|
|
||||||
const parsedArgs = parseOptions(args, outDir, collectionFile);
|
|
||||||
if (parsedArgs.listGenerators) {
|
|
||||||
return listGenerators(collectionFile);
|
|
||||||
} else {
|
|
||||||
process.exitCode = await generate(process.cwd(), parsedArgs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function workspaceGeneratorSchema(name: string) {
|
|
||||||
const schemaFile = path.join(generatorsDir, name, 'schema.json');
|
|
||||||
|
|
||||||
if (fileExists(schemaFile)) {
|
|
||||||
return readJsonFile(schemaFile);
|
|
||||||
} else {
|
|
||||||
logger.error(`Cannot find schema for ${name}. Does the generator exist?`);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// compile tools
|
|
||||||
function compileTools() {
|
|
||||||
const toolsOutDir = getToolsOutDir();
|
|
||||||
removeSync(toolsOutDir);
|
|
||||||
compileToolsDir(toolsOutDir);
|
|
||||||
|
|
||||||
const generatorsOutDir = path.join(toolsOutDir, 'generators');
|
|
||||||
const collectionData = constructCollection();
|
|
||||||
writeJsonFile(
|
|
||||||
path.join(generatorsOutDir, 'workspace-generators.json'),
|
|
||||||
collectionData
|
|
||||||
);
|
|
||||||
return generatorsOutDir;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getToolsOutDir() {
|
|
||||||
const outDir = toolsTsConfig().compilerOptions.outDir;
|
|
||||||
|
|
||||||
if (!outDir) {
|
|
||||||
logger.error(`${toolsTsConfigPath} must specify an outDir`);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return path.resolve(toolsDir, outDir);
|
|
||||||
}
|
|
||||||
|
|
||||||
function compileToolsDir(outDir: string) {
|
|
||||||
copySync(generatorsDir, path.join(outDir, 'generators'));
|
|
||||||
|
|
||||||
const tmpTsConfigPath = createTmpTsConfig(toolsTsConfigPath, {
|
|
||||||
include: [path.join(generatorsDir, '**/*.ts')],
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const pmc = getPackageManagerCommand();
|
const nxJson: NxJsonConfiguration = readNxJson();
|
||||||
const tsc = `${pmc.exec} tsc`;
|
const collection = nxJson.npmScope
|
||||||
try {
|
? `@${nxJson.npmScope}/workspace-plugin`
|
||||||
execSync(`${tsc} -p ${tmpTsConfigPath}`, {
|
: 'workspace-plugin';
|
||||||
stdio: 'inherit',
|
|
||||||
cwd: rootDirectory,
|
args._ = args._.slice(1);
|
||||||
});
|
args.generator = `${collection}:${generator}`;
|
||||||
} catch {
|
|
||||||
process.exit(1);
|
return (await import('./generate')).generate(process.cwd(), args);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function constructCollection() {
|
|
||||||
const generators = {};
|
|
||||||
const schematics = {};
|
|
||||||
readdirSync(generatorsDir).forEach((c) => {
|
|
||||||
const childDir = path.join(generatorsDir, c);
|
|
||||||
if (existsSync(path.join(childDir, 'schema.json'))) {
|
|
||||||
const generatorOrSchematic = {
|
|
||||||
factory: `./${c}`,
|
|
||||||
schema: `./${normalizePath(path.join(c, 'schema.json'))}`,
|
|
||||||
description: `Schematic ${c}`,
|
|
||||||
};
|
|
||||||
|
|
||||||
const { isSchematic } = readJsonFile(path.join(childDir, 'schema.json'));
|
|
||||||
if (isSchematic) {
|
|
||||||
schematics[c] = generatorOrSchematic;
|
|
||||||
} else {
|
|
||||||
generators[c] = generatorOrSchematic;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
name: 'workspace-generators',
|
|
||||||
version: '1.0',
|
|
||||||
generators,
|
|
||||||
schematics,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function toolsTsConfig(): TsConfig {
|
|
||||||
return readJsonFile<TsConfig>(toolsTsConfigPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
function listGenerators(collectionFile: string) {
|
|
||||||
try {
|
|
||||||
const bodyLines: string[] = [];
|
|
||||||
|
|
||||||
const collection = readJsonFile(collectionFile);
|
|
||||||
|
|
||||||
bodyLines.push(chalk.bold(chalk.green('WORKSPACE GENERATORS')));
|
|
||||||
bodyLines.push('');
|
|
||||||
bodyLines.push(
|
|
||||||
...Object.entries(collection.generators).map(
|
|
||||||
([schematicName, schematicMeta]: [string, any]) => {
|
|
||||||
return `${chalk.bold(schematicName)} : ${schematicMeta.description}`;
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
bodyLines.push('');
|
|
||||||
|
|
||||||
output.log({
|
|
||||||
title: '',
|
|
||||||
bodyLines,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
logger.fatal(error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseOptions(
|
|
||||||
args: string[],
|
|
||||||
outDir: string,
|
|
||||||
collectionFile: string
|
|
||||||
): { [k: string]: any } {
|
|
||||||
const schemaPath = path.join(outDir, args[0], 'schema.json');
|
|
||||||
let booleanProps = [];
|
|
||||||
if (fileExists(schemaPath)) {
|
|
||||||
const { properties } = readJsonFile(
|
|
||||||
path.join(outDir, args[0], 'schema.json')
|
|
||||||
);
|
|
||||||
if (properties) {
|
|
||||||
booleanProps = Object.keys(properties).filter(
|
|
||||||
(key) => properties[key].type === 'boolean'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const parsed = yargsParser(args, {
|
|
||||||
boolean: ['dryRun', 'listGenerators', 'interactive', ...booleanProps],
|
|
||||||
alias: {
|
|
||||||
dryRun: ['d'],
|
|
||||||
listSchematics: ['l'],
|
|
||||||
},
|
|
||||||
default: {
|
|
||||||
interactive: true,
|
|
||||||
},
|
|
||||||
configuration: parserConfiguration,
|
|
||||||
});
|
|
||||||
parsed['generator'] = `${collectionFile}:${parsed['_'][0]}`;
|
|
||||||
parsed['_'] = parsed['_'].slice(1);
|
|
||||||
return parsed;
|
|
||||||
}
|
|
||||||
|
|
||||||
function createTmpTsConfig(
|
|
||||||
tsconfigPath: string,
|
|
||||||
updateConfig: Partial<TsConfig>
|
|
||||||
) {
|
|
||||||
const tmpTsConfigPath = path.join(
|
|
||||||
path.dirname(tsconfigPath),
|
|
||||||
'tsconfig.generated.json'
|
|
||||||
);
|
|
||||||
const originalTSConfig = readJsonFile<TsConfig>(tsconfigPath);
|
|
||||||
const generatedTSConfig: TsConfig = {
|
|
||||||
...originalTSConfig,
|
|
||||||
...updateConfig,
|
|
||||||
};
|
|
||||||
process.on('exit', () => cleanupTmpTsConfigFile(tmpTsConfigPath));
|
|
||||||
writeJsonFile(tmpTsConfigPath, generatedTSConfig);
|
|
||||||
|
|
||||||
return tmpTsConfigPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
function cleanupTmpTsConfigFile(tmpTsConfigPath: string) {
|
|
||||||
if (tmpTsConfigPath) {
|
|
||||||
removeSync(tmpTsConfigPath);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -89,6 +89,12 @@
|
|||||||
"version": "16.0.0-beta.1",
|
"version": "16.0.0-beta.1",
|
||||||
"description": "Replace @nrwl/workspace with @nx/workspace",
|
"description": "Replace @nrwl/workspace with @nx/workspace",
|
||||||
"implementation": "./src/migrations/update-16-0-0-add-nx-packages/update-16-0-0-add-nx-packages"
|
"implementation": "./src/migrations/update-16-0-0-add-nx-packages/update-16-0-0-add-nx-packages"
|
||||||
|
},
|
||||||
|
"16-0-0-move-workspace-generators-into-local-plugin": {
|
||||||
|
"version": "16.0.0-beta.3",
|
||||||
|
"description": "Generates a plugin called 'workspace-plugin' containing your workspace generators.",
|
||||||
|
"cli": "nx",
|
||||||
|
"implementation": "./src/migrations/update-16-0-0/move-workspace-generators-to-local-plugin"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packageJsonUpdates": {
|
"packageJsonUpdates": {
|
||||||
|
|||||||
@ -10,17 +10,21 @@ export function createProjectConfigurationInNewDestination(
|
|||||||
schema: NormalizedSchema,
|
schema: NormalizedSchema,
|
||||||
projectConfig: ProjectConfiguration
|
projectConfig: ProjectConfiguration
|
||||||
) {
|
) {
|
||||||
if (projectConfig.name) {
|
projectConfig.name = schema.newProjectName;
|
||||||
projectConfig.name = schema.newProjectName;
|
|
||||||
}
|
// Subtle bug if project name === path, where the updated name was being overrideen.
|
||||||
|
const { name, ...rest } = projectConfig;
|
||||||
|
|
||||||
// replace old root path with new one
|
// replace old root path with new one
|
||||||
const projectString = JSON.stringify(projectConfig);
|
const projectString = JSON.stringify(rest);
|
||||||
const newProjectString = projectString.replace(
|
const newProjectString = projectString.replace(
|
||||||
new RegExp(projectConfig.root, 'g'),
|
new RegExp(projectConfig.root, 'g'),
|
||||||
schema.relativeToRootDestination
|
schema.relativeToRootDestination
|
||||||
);
|
);
|
||||||
const newProject: ProjectConfiguration = JSON.parse(newProjectString);
|
const newProject: ProjectConfiguration = {
|
||||||
|
name,
|
||||||
|
...JSON.parse(newProjectString),
|
||||||
|
};
|
||||||
|
|
||||||
// Create a new project with the root replaced
|
// Create a new project with the root replaced
|
||||||
addProjectConfiguration(tree, schema.newProjectName, newProject);
|
addProjectConfiguration(tree, schema.newProjectName, newProject);
|
||||||
|
|||||||
@ -11,7 +11,8 @@ export function normalizeSchema(
|
|||||||
const destination = schema.destination.startsWith('/')
|
const destination = schema.destination.startsWith('/')
|
||||||
? normalizeSlashes(schema.destination.slice(1))
|
? normalizeSlashes(schema.destination.slice(1))
|
||||||
: schema.destination;
|
: schema.destination;
|
||||||
const newProjectName = getNewProjectName(destination);
|
const newProjectName =
|
||||||
|
schema.newProjectName ?? getNewProjectName(destination);
|
||||||
const { npmScope } = getWorkspaceLayout(tree);
|
const { npmScope } = getWorkspaceLayout(tree);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -110,7 +110,9 @@ export function updateImports(
|
|||||||
|
|
||||||
if (schema.updateImportPath) {
|
if (schema.updateImportPath) {
|
||||||
tsConfig.compilerOptions.paths[projectRef.to] = updatedPath;
|
tsConfig.compilerOptions.paths[projectRef.to] = updatedPath;
|
||||||
delete tsConfig.compilerOptions.paths[projectRef.from];
|
if (projectRef.from !== projectRef.to) {
|
||||||
|
delete tsConfig.compilerOptions.paths[projectRef.from];
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
tsConfig.compilerOptions.paths[projectRef.from] = updatedPath;
|
tsConfig.compilerOptions.paths[projectRef.from] = updatedPath;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,6 +19,10 @@ export function getDestination(
|
|||||||
schema: Schema,
|
schema: Schema,
|
||||||
project: ProjectConfiguration
|
project: ProjectConfiguration
|
||||||
): string {
|
): string {
|
||||||
|
if (schema.destinationRelativeToRoot) {
|
||||||
|
return schema.destination;
|
||||||
|
}
|
||||||
|
|
||||||
const projectType = project.projectType;
|
const projectType = project.projectType;
|
||||||
|
|
||||||
const workspaceLayout = getWorkspaceLayout(host);
|
const workspaceLayout = getWorkspaceLayout(host);
|
||||||
|
|||||||
@ -4,10 +4,11 @@ export interface Schema {
|
|||||||
importPath?: string;
|
importPath?: string;
|
||||||
updateImportPath: boolean;
|
updateImportPath: boolean;
|
||||||
skipFormat?: boolean;
|
skipFormat?: boolean;
|
||||||
|
destinationRelativeToRoot?: boolean;
|
||||||
|
newProjectName?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface NormalizedSchema extends Schema {
|
export interface NormalizedSchema extends Schema {
|
||||||
importPath: string;
|
importPath: string;
|
||||||
newProjectName: string;
|
|
||||||
relativeToRootDestination: string;
|
relativeToRootDestination: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +0,0 @@
|
|||||||
import { Tree, formatFiles, installPackagesTask } from '@nx/devkit';
|
|
||||||
import { libraryGenerator } from '@nx/js';
|
|
||||||
|
|
||||||
export default async function(tree: Tree, schema: any) {
|
|
||||||
await libraryGenerator(tree, {name: schema.name});
|
|
||||||
await formatFiles(tree);
|
|
||||||
return () => {
|
|
||||||
installPackagesTask(tree)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "http://json-schema.org/schema",
|
|
||||||
"cli": "nx",
|
|
||||||
"$id": "<%= name %>",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"name": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Library name",
|
|
||||||
"$default": {
|
|
||||||
"$source": "argv",
|
|
||||||
"index": 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": ["name"]
|
|
||||||
}
|
|
||||||
@ -1,4 +1 @@
|
|||||||
export interface Schema {
|
export interface Schema {}
|
||||||
name: string;
|
|
||||||
skipFormat: boolean;
|
|
||||||
}
|
|
||||||
|
|||||||
@ -4,22 +4,6 @@
|
|||||||
"title": "Create a custom generator",
|
"title": "Create a custom generator",
|
||||||
"description": "Create a custom generator.",
|
"description": "Create a custom generator.",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {},
|
||||||
"name": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Generator name.",
|
|
||||||
"$default": {
|
|
||||||
"$source": "argv",
|
|
||||||
"index": 0
|
|
||||||
},
|
|
||||||
"x-prompt": "What name would you like to use for the workspace generator?"
|
|
||||||
},
|
|
||||||
"skipFormat": {
|
|
||||||
"description": "Skip formatting files.",
|
|
||||||
"type": "boolean",
|
|
||||||
"default": false,
|
|
||||||
"x-priority": "internal"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": ["name"]
|
"required": ["name"]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,17 +0,0 @@
|
|||||||
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
|
||||||
import workspaceGenerator from './workspace-generator';
|
|
||||||
|
|
||||||
describe('workspace-generator', () => {
|
|
||||||
it('should generate a target', async () => {
|
|
||||||
const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
|
||||||
const opts = {
|
|
||||||
name: 'custom',
|
|
||||||
skipFormat: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
await workspaceGenerator(tree, opts);
|
|
||||||
|
|
||||||
expect(tree.exists('tools/generators/custom/index.ts')).toBeTruthy();
|
|
||||||
expect(tree.exists('tools/generators/custom/schema.json')).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -1,42 +1,15 @@
|
|||||||
import { Schema } from './schema';
|
import { Schema } from './schema';
|
||||||
import {
|
import { Tree, stripIndents } from '@nx/devkit';
|
||||||
Tree,
|
|
||||||
formatFiles,
|
|
||||||
generateFiles,
|
|
||||||
names,
|
|
||||||
joinPathFragments,
|
|
||||||
addDependenciesToPackageJson,
|
|
||||||
} from '@nx/devkit';
|
|
||||||
import { nxVersion } from '../../utils/versions';
|
|
||||||
|
|
||||||
export default async function (host: Tree, schema: Schema) {
|
export default async function (host: Tree, schema: Schema) {
|
||||||
const options = normalizeOptions(schema);
|
const message = stripIndents`Workspace Generators are no longer supported. Instead,
|
||||||
|
Nx now supports executing generators or executors from local plugins. To get
|
||||||
|
started, install @nx/nx-plugin and run \`nx g plugin\`.
|
||||||
|
|
||||||
generateFiles(
|
Afterwards, or if you already have an Nx plugin, you can run
|
||||||
host,
|
\`nx g generator --project {my-plugin}\` to add a new generator.
|
||||||
joinPathFragments(__dirname, 'files'),
|
|
||||||
joinPathFragments('tools/generators', schema.name),
|
For more information, see: https://nx.dev/deprecated/workspace-generators`;
|
||||||
options
|
|
||||||
);
|
|
||||||
|
|
||||||
const installTask = addDependenciesToPackageJson(
|
throw new Error(message);
|
||||||
host,
|
|
||||||
{},
|
|
||||||
{
|
|
||||||
'@nx/devkit': nxVersion,
|
|
||||||
// types/node is neccessary for pnpm since it's used in tsconfig and transitive
|
|
||||||
// dependencies are not resolved correctly
|
|
||||||
'@types/node': 'latest',
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!schema.skipFormat) {
|
|
||||||
await formatFiles(host);
|
|
||||||
}
|
|
||||||
return installTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
function normalizeOptions(options: Schema): any {
|
|
||||||
const name = names(options.name).fileName;
|
|
||||||
return { ...options, name, tmpl: '' };
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,174 @@
|
|||||||
|
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||||
|
import {
|
||||||
|
Tree,
|
||||||
|
readProjectConfiguration,
|
||||||
|
readJson,
|
||||||
|
joinPathFragments,
|
||||||
|
GeneratorsJson,
|
||||||
|
ProjectConfiguration,
|
||||||
|
stripIndents,
|
||||||
|
getProjects,
|
||||||
|
} from '@nx/devkit';
|
||||||
|
|
||||||
|
import generator from './move-workspace-generators-to-local-plugin';
|
||||||
|
|
||||||
|
describe('move-workspace-generators-to-local-plugin', () => {
|
||||||
|
let tree: Tree;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
tree = createTreeWithEmptyWorkspace();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should find single workspace generator successfully', async () => {
|
||||||
|
await workspaceGeneratorGenerator(tree, {
|
||||||
|
name: 'my-generator',
|
||||||
|
});
|
||||||
|
await generator(tree);
|
||||||
|
console.log(getProjects(tree).keys());
|
||||||
|
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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await generator(tree);
|
||||||
|
|
||||||
|
const config = readProjectConfiguration(tree, 'workspace-plugin');
|
||||||
|
expect(config.root).toEqual('tools/workspace-plugin');
|
||||||
|
|
||||||
|
for (const generator of generators) {
|
||||||
|
assertValidGenerator(tree, config, generator);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be idempotent', async () => {
|
||||||
|
const generators = [...new Array(10)].map((x) => uniq('generator'));
|
||||||
|
for (const name of generators) {
|
||||||
|
await workspaceGeneratorGenerator(tree, {
|
||||||
|
name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await generator(tree);
|
||||||
|
|
||||||
|
const generatorsJson = readJson(
|
||||||
|
tree,
|
||||||
|
'tools/workspace-plugin/generators.json'
|
||||||
|
);
|
||||||
|
|
||||||
|
await generator(tree);
|
||||||
|
expect(readJson(tree, 'tools/workspace-plugin/generators.json')).toEqual(
|
||||||
|
generatorsJson
|
||||||
|
);
|
||||||
|
|
||||||
|
const config = readProjectConfiguration(tree, 'workspace-plugin');
|
||||||
|
|
||||||
|
for (const generator of generators) {
|
||||||
|
assertValidGenerator(tree, config, generator);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should merge new generators into existing plugin', async () => {
|
||||||
|
const generators = [...new Array(10)].map((x) => uniq('generator'));
|
||||||
|
for (const name of generators) {
|
||||||
|
await workspaceGeneratorGenerator(tree, {
|
||||||
|
name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await generator(tree);
|
||||||
|
|
||||||
|
const moreGenerators = [...new Array(5)].map((x) => uniq('generator'));
|
||||||
|
for (const name of moreGenerators) {
|
||||||
|
await workspaceGeneratorGenerator(tree, {
|
||||||
|
name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await generator(tree);
|
||||||
|
|
||||||
|
const config = readProjectConfiguration(tree, 'workspace-plugin');
|
||||||
|
|
||||||
|
for (const generator of generators.concat(moreGenerators)) {
|
||||||
|
assertValidGenerator(tree, config, 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
function uniq(prefix: string) {
|
||||||
|
return `${prefix}${Math.floor(Math.random() * 10000000)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function workspaceGeneratorGenerator(
|
||||||
|
host: Tree,
|
||||||
|
schema: { name: string }
|
||||||
|
) {
|
||||||
|
const outputDirectory = joinPathFragments('tools/generators', schema.name);
|
||||||
|
|
||||||
|
host.write(
|
||||||
|
joinPathFragments(outputDirectory, 'index.ts'),
|
||||||
|
stripIndents`import { Tree, formatFiles, installPackagesTask } from '@nx/devkit';
|
||||||
|
import { libraryGenerator } from '@nx/workspace/generators';
|
||||||
|
|
||||||
|
export default async function(tree: Tree, schema: any) {
|
||||||
|
await libraryGenerator(tree, {name: schema.name});
|
||||||
|
await formatFiles(tree);
|
||||||
|
return () => {
|
||||||
|
installPackagesTask(tree)
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
);
|
||||||
|
|
||||||
|
host.write(
|
||||||
|
joinPathFragments(outputDirectory, 'schema.json'),
|
||||||
|
stripIndents`{
|
||||||
|
"$schema": "http://json-schema.org/schema",
|
||||||
|
"cli": "nx",
|
||||||
|
"$id": "<%= name %>",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Library name",
|
||||||
|
"$default": {
|
||||||
|
"$source": "argv",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["name"]
|
||||||
|
}
|
||||||
|
`
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,188 @@
|
|||||||
|
import {
|
||||||
|
addDependenciesToPackageJson,
|
||||||
|
ensurePackage,
|
||||||
|
formatFiles,
|
||||||
|
getProjects,
|
||||||
|
getWorkspaceLayout,
|
||||||
|
joinPathFragments,
|
||||||
|
output,
|
||||||
|
ProjectConfiguration,
|
||||||
|
readJson,
|
||||||
|
readProjectConfiguration,
|
||||||
|
Tree,
|
||||||
|
updateJson,
|
||||||
|
writeJson,
|
||||||
|
} from '@nx/devkit';
|
||||||
|
// nx-ignore-next-line
|
||||||
|
import * as path from 'path';
|
||||||
|
import {
|
||||||
|
GeneratorsJson,
|
||||||
|
GeneratorsJsonEntry,
|
||||||
|
} from 'nx/src/config/misc-interfaces';
|
||||||
|
import { moveGenerator } from '../../generators/move/move';
|
||||||
|
import { nxVersion } from '../../utils/versions';
|
||||||
|
import { PackageJson } from 'nx/src/utils/package-json';
|
||||||
|
|
||||||
|
const PROJECT_NAME = 'workspace-plugin';
|
||||||
|
const DESTINATION = `tools/${PROJECT_NAME}`;
|
||||||
|
|
||||||
|
export default async function (tree: Tree) {
|
||||||
|
const tasks = [];
|
||||||
|
if (!tree.children('tools/generators').length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let project = getProjects(tree).get(PROJECT_NAME);
|
||||||
|
if (!project) {
|
||||||
|
await createNewPlugin(tree);
|
||||||
|
tasks.push(
|
||||||
|
addDependenciesToPackageJson(
|
||||||
|
tree,
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
'@nx/nx-plugin': nxVersion,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
project = readProjectConfiguration(tree, PROJECT_NAME);
|
||||||
|
}
|
||||||
|
await updateExistingPlugin(tree, project);
|
||||||
|
await formatFiles(tree);
|
||||||
|
return () => {
|
||||||
|
for (const task of tasks) {
|
||||||
|
task();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inspired by packages/nx/src/command-line/workspace-generators.ts
|
||||||
|
function collectAndMoveGenerators(tree: Tree, destinationProjectRoot: string) {
|
||||||
|
const generators: Record<string, GeneratorsJsonEntry> = {};
|
||||||
|
const generatorsDir = 'tools/generators';
|
||||||
|
const destinationDir = joinPathFragments(
|
||||||
|
destinationProjectRoot,
|
||||||
|
'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[c] = {
|
||||||
|
implementation: `./src/generators/${c}`,
|
||||||
|
schema: `./src/generators/${joinPathFragments(c, 'schema.json')}`,
|
||||||
|
description: schema.description ?? `Generator ${c}`,
|
||||||
|
};
|
||||||
|
tree.rename(childDir, joinPathFragments(destinationDir, c));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return generators;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createNewPlugin(tree: Tree) {
|
||||||
|
ensurePackage('@nx/nx-plugin', nxVersion);
|
||||||
|
const { pluginGenerator } =
|
||||||
|
// nx-ignore-next-line
|
||||||
|
require('@nx/nx-plugin/src/generators/plugin/plugin');
|
||||||
|
|
||||||
|
// nx-ignore-next-line
|
||||||
|
const { Linter } = ensurePackage('@nx/linter', nxVersion);
|
||||||
|
|
||||||
|
const { npmScope } = getWorkspaceLayout(tree);
|
||||||
|
const importPath = npmScope ? `${npmScope}/${PROJECT_NAME}` : PROJECT_NAME;
|
||||||
|
|
||||||
|
await pluginGenerator(tree, {
|
||||||
|
minimal: true,
|
||||||
|
name: PROJECT_NAME,
|
||||||
|
importPath: importPath,
|
||||||
|
skipTsConfig: false,
|
||||||
|
compiler: 'tsc',
|
||||||
|
linter: Linter.EsLint,
|
||||||
|
skipFormat: true,
|
||||||
|
skipLintChecks: false,
|
||||||
|
unitTestRunner: 'jest',
|
||||||
|
e2eTestRunner: 'none',
|
||||||
|
});
|
||||||
|
getCreateGeneratorsJson()(
|
||||||
|
tree,
|
||||||
|
readProjectConfiguration(tree, PROJECT_NAME).root,
|
||||||
|
PROJECT_NAME
|
||||||
|
);
|
||||||
|
await moveGeneratedPlugin(tree, DESTINATION, importPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveGeneratedPlugin(
|
||||||
|
tree: Tree,
|
||||||
|
destination: string,
|
||||||
|
importPath: string
|
||||||
|
) {
|
||||||
|
const config = readProjectConfiguration(tree, PROJECT_NAME);
|
||||||
|
if (config.root !== DESTINATION) {
|
||||||
|
return moveGenerator(tree, {
|
||||||
|
destination,
|
||||||
|
projectName: PROJECT_NAME,
|
||||||
|
newProjectName: PROJECT_NAME,
|
||||||
|
updateImportPath: true,
|
||||||
|
destinationRelativeToRoot: true,
|
||||||
|
importPath: importPath,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateExistingPlugin(tree: Tree, project: ProjectConfiguration) {
|
||||||
|
const packageJson = readJson<PackageJson>(
|
||||||
|
tree,
|
||||||
|
joinPathFragments(project.root, 'package.json')
|
||||||
|
);
|
||||||
|
const defaultGeneratorsPath = joinPathFragments(
|
||||||
|
project.root,
|
||||||
|
'generators.json'
|
||||||
|
);
|
||||||
|
let generatorsJsonPath =
|
||||||
|
packageJson.generators ||
|
||||||
|
packageJson.schematics ||
|
||||||
|
tree.exists(defaultGeneratorsPath)
|
||||||
|
? defaultGeneratorsPath
|
||||||
|
: null;
|
||||||
|
if (!generatorsJsonPath) {
|
||||||
|
getCreateGeneratorsJson()(
|
||||||
|
tree,
|
||||||
|
readProjectConfiguration(tree, PROJECT_NAME).root,
|
||||||
|
PROJECT_NAME
|
||||||
|
);
|
||||||
|
generatorsJsonPath = defaultGeneratorsPath;
|
||||||
|
}
|
||||||
|
updateJson<GeneratorsJson>(tree, generatorsJsonPath, (json) => {
|
||||||
|
const generators = collectAndMoveGenerators(tree, project.root);
|
||||||
|
json.generators ??= {};
|
||||||
|
for (const generator in generators) {
|
||||||
|
if (json.generators[generator]) {
|
||||||
|
output.warn({
|
||||||
|
title: `Generator ${generator} already exists in ${project.name}`,
|
||||||
|
bodyLines: [
|
||||||
|
'Since you have a generator with the same name in your plugin, the generator from workspace-generators has been discarded.',
|
||||||
|
],
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
json.generators[generator] = generators[generator];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return json;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCreateGeneratorsJson(): (
|
||||||
|
host: Tree,
|
||||||
|
projectRoot: string,
|
||||||
|
projectName: string,
|
||||||
|
skipLintChecks?: boolean,
|
||||||
|
skipFormat?: boolean
|
||||||
|
) => Promise<void> {
|
||||||
|
// We cant use `as typeof import('@nx/nx-plugin/src/generators/generator/generator');` here
|
||||||
|
// because it will cause a typescript error at build time.
|
||||||
|
const { createGeneratorsJson } =
|
||||||
|
// nx-ignore-next-line
|
||||||
|
require('@nx/nx-plugin/src/generators/generator/generator');
|
||||||
|
return createGeneratorsJson;
|
||||||
|
}
|
||||||
@ -192,6 +192,16 @@ const IGNORE_MATCHES_BY_FILE: Record<string, string[]> = {
|
|||||||
'../../packages/angular/src/migrations/update-12-3-0/update-storybook.ts'
|
'../../packages/angular/src/migrations/update-12-3-0/update-storybook.ts'
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
'@nx/nx-plugin': [
|
||||||
|
join(
|
||||||
|
__dirname,
|
||||||
|
'../../packages/workspace/src/migrations/update-16-0-0/move-workspace-generators-to-local-plugin.spec.ts'
|
||||||
|
),
|
||||||
|
join(
|
||||||
|
__dirname,
|
||||||
|
'../../packages/workspace/src/migrations/update-16-0-0/move-workspace-generators-to-local-plugin.ts'
|
||||||
|
),
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
export default async function getMissingDependencies(
|
export default async function getMissingDependencies(
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user