feat(core): make older builders support new config formats
This commit is contained in:
parent
6c1335ac83
commit
8afc5c5bce
43
e2e/angular/src/config-compat.test.ts
Normal file
43
e2e/angular/src/config-compat.test.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import {
|
||||
expectTestsPass,
|
||||
newProject,
|
||||
readJson,
|
||||
runCLI,
|
||||
runCLIAsync,
|
||||
uniq,
|
||||
updateFile,
|
||||
} from '@nrwl/e2e/utils';
|
||||
|
||||
describe('new config format', () => {
|
||||
it('should work', async () => {
|
||||
newProject();
|
||||
|
||||
const myapp = uniq('myapp');
|
||||
runCLI(`generate @nrwl/angular:app ${myapp} --no-interactive`);
|
||||
|
||||
// update the angular.json
|
||||
const workspaceJson = readJson(`angular.json`);
|
||||
workspaceJson.projects[myapp].targets = updateConfig(
|
||||
workspaceJson.projects[myapp].architect
|
||||
);
|
||||
workspaceJson.generators = workspaceJson.schematics;
|
||||
delete workspaceJson.schematics;
|
||||
updateFile('angular.json', JSON.stringify(workspaceJson, null, 2));
|
||||
|
||||
const myapp2 = uniq('myapp');
|
||||
runCLI(`generate @nrwl/angular:app ${myapp2} --no-interactive`);
|
||||
expectTestsPass(await runCLIAsync(`test ${myapp2} --no-watch`));
|
||||
}, 1000000);
|
||||
});
|
||||
|
||||
function updateConfig(targets: any) {
|
||||
const res = {};
|
||||
Object.entries(targets).forEach(([name, t]: any) => {
|
||||
t.executor = t.builder;
|
||||
t.generators = t.schematics;
|
||||
delete t.builder;
|
||||
delete t.schematics;
|
||||
res[name] = t;
|
||||
});
|
||||
return res;
|
||||
}
|
||||
@ -15,7 +15,7 @@ describe('Next.js Applications', () => {
|
||||
newProject();
|
||||
const appName = uniq('app');
|
||||
|
||||
runCLI(`generate @nrwl/next:app ${appName} --no-interactive`);
|
||||
runCLI(`generate @nrwl/next:app ${appName}`);
|
||||
|
||||
const proxyConf = {
|
||||
'/external-api': {
|
||||
|
||||
@ -126,7 +126,9 @@ export function newProject(): void {
|
||||
(f) => f !== '@nrwl/nx-plugin' && f !== `@nrwl/eslint-plugin-nx`
|
||||
)
|
||||
.forEach((p) => {
|
||||
runCLI(`g ${p}:init`, { cwd: `./tmp/${currentCli()}/proj` });
|
||||
runCLI(`g ${p}:init --no-interactive`, {
|
||||
cwd: `./tmp/${currentCli()}/proj`,
|
||||
});
|
||||
});
|
||||
|
||||
execSync(`mv ./tmp/${currentCli()}/proj ${tmpBackupProjPath()}`);
|
||||
|
||||
@ -9,5 +9,5 @@ module.exports = {
|
||||
resolver: '../../scripts/patched-jest-resolver.js',
|
||||
moduleFileExtensions: ['ts', 'js', 'html'],
|
||||
coverageReporters: ['html'],
|
||||
maxWorkers: 2,
|
||||
maxWorkers: 1,
|
||||
};
|
||||
|
||||
42748
package-lock.json
generated
42748
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -9,41 +9,13 @@
|
||||
"type": "string",
|
||||
"enum": ["karma", "jest", "none"],
|
||||
"description": "Test runner to use for unit tests",
|
||||
"default": "jest",
|
||||
"x-prompt": {
|
||||
"message": "Which Unit Test Runner would you like to use for the application?",
|
||||
"type": "list",
|
||||
"items": [
|
||||
{
|
||||
"value": "jest",
|
||||
"label": "Jest [ https://jestjs.io ]"
|
||||
},
|
||||
{
|
||||
"value": "karma",
|
||||
"label": "Karma [ https://karma-runner.github.io ]"
|
||||
}
|
||||
]
|
||||
}
|
||||
"default": "jest"
|
||||
},
|
||||
"e2eTestRunner": {
|
||||
"type": "string",
|
||||
"enum": ["protractor", "cypress", "none"],
|
||||
"description": "Test runner to use for end to end (e2e) tests",
|
||||
"default": "cypress",
|
||||
"x-prompt": {
|
||||
"message": "Which E2E Test Runner would you like to use?",
|
||||
"type": "list",
|
||||
"items": [
|
||||
{
|
||||
"value": "cypress",
|
||||
"label": "Cypress [ https://www.cypress.io ]"
|
||||
},
|
||||
{
|
||||
"value": "protractor",
|
||||
"label": "Protractor [ https://www.protractortest.org ]"
|
||||
}
|
||||
]
|
||||
}
|
||||
"default": "cypress"
|
||||
},
|
||||
"skipInstall": {
|
||||
"type": "boolean",
|
||||
|
||||
@ -57,7 +57,10 @@ export function parseRunOneOptions(
|
||||
// we need both to be able to run a target, no tasks runner
|
||||
const p =
|
||||
workspaceConfigJson.projects && workspaceConfigJson.projects[project];
|
||||
if (!p || !p.architect || !p.architect[target]) return false;
|
||||
if (!p) return false;
|
||||
|
||||
const targets = p.architect ? p.architect : p.targets;
|
||||
if (!targets || !targets[target]) return false;
|
||||
|
||||
const res = { project, target, configuration, parsedArgs };
|
||||
delete parsedArgs['configuration'];
|
||||
|
||||
@ -29,18 +29,15 @@
|
||||
},
|
||||
"customServerPath": {
|
||||
"type": "string",
|
||||
"description": "Use a custom server script",
|
||||
"default": null
|
||||
"description": "Use a custom server script"
|
||||
},
|
||||
"hostname": {
|
||||
"type": "string",
|
||||
"description": "Hostname on which the application is served.",
|
||||
"default": null
|
||||
"description": "Hostname on which the application is served."
|
||||
},
|
||||
"proxyConfig": {
|
||||
"type": "string",
|
||||
"description": "Path to the proxy configuration file.",
|
||||
"default": null
|
||||
"description": "Path to the proxy configuration file."
|
||||
}
|
||||
},
|
||||
"required": []
|
||||
|
||||
@ -64,8 +64,7 @@
|
||||
},
|
||||
"server": {
|
||||
"description": "The server script path to be used with next.",
|
||||
"type": "string",
|
||||
"default": null
|
||||
"type": "string"
|
||||
},
|
||||
"linter": {
|
||||
"description": "The tool to use for running lint checks.",
|
||||
|
||||
@ -3,7 +3,7 @@ import { Tree, readJson } from '@nrwl/devkit';
|
||||
export default function update(host: Tree) {
|
||||
const p = readJson(host, 'package.json');
|
||||
if (p['ng-update']) {
|
||||
p['ng-migrate'] = p['ng-update'];
|
||||
p['nx-migrations'] = p['ng-update'];
|
||||
delete p['ng-update'];
|
||||
}
|
||||
host.write('package.json', JSON.stringify(p, null, 2));
|
||||
|
||||
@ -33,19 +33,7 @@
|
||||
"type": "string",
|
||||
"description": "Project source path."
|
||||
}
|
||||
},
|
||||
"oneOf": [
|
||||
{
|
||||
"anyOf": [
|
||||
{
|
||||
"required": ["configFolder"]
|
||||
},
|
||||
{
|
||||
"required": ["pluginPath", "configPath", "srcRoot"]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"docsMode": {
|
||||
"type": "boolean",
|
||||
|
||||
@ -64,19 +64,7 @@
|
||||
"type": "string",
|
||||
"description": "Project source path."
|
||||
}
|
||||
},
|
||||
"oneOf": [
|
||||
{
|
||||
"anyOf": [
|
||||
{
|
||||
"required": ["configFolder"]
|
||||
},
|
||||
{
|
||||
"required": ["pluginPath", "configPath", "srcRoot"]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"docsMode": {
|
||||
"type": "boolean",
|
||||
|
||||
@ -72,7 +72,7 @@ function parseGenerateOpts(
|
||||
}
|
||||
} else {
|
||||
collectionName = generatorOptions.collection as string;
|
||||
generatorName = '';
|
||||
generatorName = 'new';
|
||||
}
|
||||
|
||||
if (!collectionName) {
|
||||
@ -164,9 +164,27 @@ function printChanges(fileChanges: FileChange[]) {
|
||||
|
||||
export async function taoNew(root: string, args: string[], isVerbose = false) {
|
||||
const logger = getLogger(isVerbose);
|
||||
const ws = new Workspaces();
|
||||
return handleErrors(logger, isVerbose, async () => {
|
||||
const opts = parseGenerateOpts(args, 'new', null);
|
||||
return (await import('./ngcli-adapter')).invokeNew(logger, root, opts);
|
||||
|
||||
const { schema, implementation } = ws.readGenerator(
|
||||
opts.collectionName,
|
||||
opts.generatorName
|
||||
);
|
||||
|
||||
const combinedOpts = await combineOptionsForGenerator(
|
||||
opts.generatorOptions,
|
||||
opts.collectionName,
|
||||
opts.generatorName,
|
||||
null,
|
||||
schema,
|
||||
opts.interactive
|
||||
);
|
||||
return (await import('./ngcli-adapter')).invokeNew(logger, root, {
|
||||
...opts,
|
||||
generatorOptions: combinedOpts,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -186,7 +204,6 @@ export async function generate(
|
||||
readDefaultCollection(workspaceDefinition)
|
||||
);
|
||||
|
||||
if (ws.isNxGenerator(opts.collectionName, opts.generatorName)) {
|
||||
const { schema, implementation } = ws.readGenerator(
|
||||
opts.collectionName,
|
||||
opts.generatorName
|
||||
@ -196,7 +213,6 @@ export async function generate(
|
||||
printGenHelp(opts, schema, logger as any);
|
||||
return 0;
|
||||
}
|
||||
|
||||
const combinedOpts = await combineOptionsForGenerator(
|
||||
opts.generatorOptions,
|
||||
opts.collectionName,
|
||||
@ -205,6 +221,8 @@ export async function generate(
|
||||
schema,
|
||||
opts.interactive
|
||||
);
|
||||
|
||||
if (ws.isNxGenerator(opts.collectionName, opts.generatorName)) {
|
||||
const host = new FsTree(root, isVerbose, logger);
|
||||
const task = await implementation(host, combinedOpts);
|
||||
const changes = host.listChanges();
|
||||
@ -219,7 +237,10 @@ export async function generate(
|
||||
logger.warn(`\nNOTE: The "dryRun" flag means no changes were made.`);
|
||||
}
|
||||
} else {
|
||||
return (await import('./ngcli-adapter')).generate(logger, root, opts);
|
||||
return (await import('./ngcli-adapter')).generate(logger, root, {
|
||||
...opts,
|
||||
generatorOptions: combinedOpts,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -2,9 +2,9 @@ import { Architect } from '@angular-devkit/architect';
|
||||
import { WorkspaceNodeModulesArchitectHost } from '@angular-devkit/architect/node';
|
||||
import {
|
||||
json,
|
||||
JsonObject,
|
||||
logging,
|
||||
normalize,
|
||||
Path,
|
||||
schema,
|
||||
tags,
|
||||
virtualFs,
|
||||
@ -12,13 +12,7 @@ import {
|
||||
} from '@angular-devkit/core';
|
||||
import * as chalk from 'chalk';
|
||||
import { NodeJsSyncHost } from '@angular-devkit/core/node';
|
||||
import {
|
||||
coerceTypesInOptions,
|
||||
convertAliases,
|
||||
Options,
|
||||
Schema,
|
||||
} from '../shared/params';
|
||||
import { printRunHelp, RunOptions } from './run';
|
||||
import { RunOptions } from './run';
|
||||
import {
|
||||
FileSystemCollectionDescription,
|
||||
FileSystemSchematicDescription,
|
||||
@ -33,24 +27,25 @@ import {
|
||||
TaskExecutor,
|
||||
} from '@angular-devkit/schematics';
|
||||
import * as fs from 'fs';
|
||||
import * as inquirer from 'inquirer';
|
||||
import { readFileSync } from 'fs';
|
||||
import { detectPackageManager } from '@nrwl/tao/src/shared/package-manager';
|
||||
import { GenerateOptions, printGenHelp } from './generate';
|
||||
import { GenerateOptions } from './generate';
|
||||
import * as taoTree from '../shared/tree';
|
||||
import { workspaceConfigName } from '@nrwl/tao/src/shared/workspace';
|
||||
import {
|
||||
workspaceConfigName,
|
||||
Workspaces,
|
||||
} from '@nrwl/tao/src/shared/workspace';
|
||||
import { BaseWorkflow } from '@angular-devkit/schematics/src/workflow';
|
||||
import { NodePackageName } from '@angular-devkit/schematics/tasks/package-manager/options';
|
||||
import { BuiltinTaskExecutor } from '@angular-devkit/schematics/tasks/node';
|
||||
import { dirname, extname, join, resolve } from 'path';
|
||||
import { readFileSync } from 'fs';
|
||||
import * as stripJsonComments from 'strip-json-comments';
|
||||
|
||||
function normalizeOptions(opts: Options, schema: Schema): Options {
|
||||
return convertAliases(coerceTypesInOptions(opts, schema), schema, false);
|
||||
}
|
||||
import { FileBuffer } from '@angular-devkit/core/src/virtual-fs/host/interface';
|
||||
import { Observable } from 'rxjs';
|
||||
import { map, switchMap } from 'rxjs/operators';
|
||||
|
||||
export async function run(logger: any, root: string, opts: RunOptions) {
|
||||
const fsHost = new NodeJsSyncHost();
|
||||
const fsHost = new NxScopedHost(normalize(root));
|
||||
const { workspace } = await workspaces.readWorkspace(
|
||||
workspaceConfigName(root),
|
||||
workspaces.createWorkspaceHost(fsHost)
|
||||
@ -60,32 +55,13 @@ export async function run(logger: any, root: string, opts: RunOptions) {
|
||||
registry.addPostTransform(schema.transforms.addUndefinedDefaults);
|
||||
const architectHost = new WorkspaceNodeModulesArchitectHost(workspace, root);
|
||||
const architect = new Architect(architectHost, registry);
|
||||
|
||||
const builderConf = await architectHost.getBuilderNameForTarget({
|
||||
project: opts.project,
|
||||
target: opts.target,
|
||||
});
|
||||
const builderDesc = await architectHost.resolveBuilder(builderConf);
|
||||
const flattenedSchema = await registry
|
||||
.flatten(builderDesc.optionSchema as json.JsonObject)
|
||||
.toPromise();
|
||||
|
||||
if (opts.help) {
|
||||
printRunHelp(opts, flattenedSchema as Schema, logger);
|
||||
return 0;
|
||||
}
|
||||
|
||||
const runOptions = normalizeOptions(
|
||||
opts.runOptions,
|
||||
flattenedSchema as Schema
|
||||
);
|
||||
const run = await architect.scheduleTarget(
|
||||
{
|
||||
project: opts.project,
|
||||
target: opts.target,
|
||||
configuration: opts.configuration,
|
||||
},
|
||||
runOptions as JsonObject,
|
||||
opts.runOptions,
|
||||
{ logger }
|
||||
);
|
||||
const result = await run.output.toPromise();
|
||||
@ -93,7 +69,7 @@ export async function run(logger: any, root: string, opts: RunOptions) {
|
||||
return result.success ? 0 : 1;
|
||||
}
|
||||
|
||||
async function createWorkflow(
|
||||
function createWorkflow(
|
||||
fsHost: virtualFs.Host<fs.Stats>,
|
||||
root: string,
|
||||
opts: any
|
||||
@ -106,77 +82,10 @@ async function createWorkflow(
|
||||
registry: new schema.CoreSchemaRegistry(formats.standardFormats),
|
||||
resolvePaths: [process.cwd(), root],
|
||||
});
|
||||
const _params = opts.generatorOptions._;
|
||||
delete opts.generatorOptions._;
|
||||
workflow.registry.addSmartDefaultProvider('argv', (schema: JsonObject) => {
|
||||
if ('index' in schema) {
|
||||
return _params[Number(schema['index'])];
|
||||
} else {
|
||||
return _params;
|
||||
}
|
||||
});
|
||||
|
||||
if (opts.defaults) {
|
||||
workflow.registry.addPreTransform(schema.transforms.addUndefinedDefaults);
|
||||
} else {
|
||||
workflow.registry.addPostTransform(schema.transforms.addUndefinedDefaults);
|
||||
}
|
||||
|
||||
workflow.engineHost.registerOptionsTransform(
|
||||
validateOptionsWithSchema(workflow.registry)
|
||||
);
|
||||
|
||||
if (opts.interactive !== false && isTTY()) {
|
||||
workflow.registry.usePromptProvider(
|
||||
(definitions: schema.PromptDefinition[]) => {
|
||||
const questions: inquirer.QuestionCollection = definitions.map(
|
||||
(definition) => {
|
||||
const question = {
|
||||
name: definition.id,
|
||||
message: definition.message,
|
||||
default: definition.default as
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| string[],
|
||||
} as inquirer.Question;
|
||||
|
||||
const validator = definition.validator;
|
||||
if (validator) {
|
||||
question.validate = (input) => validator(input);
|
||||
}
|
||||
|
||||
switch (definition.type) {
|
||||
case 'confirmation':
|
||||
question.type = 'confirm';
|
||||
break;
|
||||
case 'list':
|
||||
question.type = definition.multiselect ? 'checkbox' : 'list';
|
||||
question.choices =
|
||||
definition.items &&
|
||||
definition.items.map((item) => {
|
||||
if (typeof item == 'string') {
|
||||
return item;
|
||||
} else {
|
||||
return {
|
||||
name: item.label,
|
||||
value: item.value,
|
||||
};
|
||||
}
|
||||
});
|
||||
break;
|
||||
default:
|
||||
question.type = definition.type;
|
||||
break;
|
||||
}
|
||||
return question;
|
||||
}
|
||||
);
|
||||
|
||||
return inquirer.prompt(questions);
|
||||
}
|
||||
);
|
||||
}
|
||||
return workflow;
|
||||
}
|
||||
|
||||
@ -228,36 +137,6 @@ function createRecorder(
|
||||
};
|
||||
}
|
||||
|
||||
async function getSchematicDefaults(
|
||||
root: string,
|
||||
collection: string,
|
||||
schematic: string
|
||||
) {
|
||||
const workspace = (
|
||||
await workspaces.readWorkspace(
|
||||
workspaceConfigName(root),
|
||||
workspaces.createWorkspaceHost(new NodeJsSyncHost())
|
||||
)
|
||||
).workspace;
|
||||
|
||||
let result = {};
|
||||
if (workspace.extensions.schematics) {
|
||||
const schematicObject =
|
||||
workspace.extensions.schematics[`${collection}:${schematic}`];
|
||||
if (schematicObject) {
|
||||
result = { ...result, ...(schematicObject as {}) };
|
||||
}
|
||||
const collectionObject = workspace.extensions.schematics[collection];
|
||||
if (
|
||||
typeof collectionObject == 'object' &&
|
||||
!Array.isArray(collectionObject)
|
||||
) {
|
||||
result = { ...result, ...(collectionObject[schematic] as {}) };
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async function runSchematic(
|
||||
root: string,
|
||||
workflow: NodeWorkflow,
|
||||
@ -267,72 +146,35 @@ async function runSchematic(
|
||||
FileSystemCollectionDescription,
|
||||
FileSystemSchematicDescription
|
||||
>,
|
||||
allowAdditionalArgs = false,
|
||||
printDryRunMessage = true,
|
||||
recorder: any = null
|
||||
): Promise<{ status: number; loggingQueue: string[] }> {
|
||||
const flattenedSchema = (await workflow.registry
|
||||
.flatten(schematic.description.schemaJson)
|
||||
.toPromise()) as Schema;
|
||||
|
||||
if (opts.help) {
|
||||
printGenHelp(opts, flattenedSchema as Schema, logger as any);
|
||||
return { status: 0, loggingQueue: [] };
|
||||
}
|
||||
|
||||
const defaults =
|
||||
opts.generatorName === 'new'
|
||||
? {}
|
||||
: await getSchematicDefaults(
|
||||
root,
|
||||
opts.collectionName,
|
||||
opts.generatorName
|
||||
);
|
||||
const record = { loggingQueue: [] as string[], error: false };
|
||||
workflow.reporter.subscribe(recorder || createRecorder(record, logger));
|
||||
|
||||
const schematicOptions = normalizeOptions(
|
||||
opts.generatorOptions,
|
||||
flattenedSchema
|
||||
);
|
||||
|
||||
if (schematicOptions['--'] && !allowAdditionalArgs) {
|
||||
schematicOptions['--'].forEach((unmatched) => {
|
||||
const message =
|
||||
`Could not match option '${unmatched.name}' to the ${opts.collectionName}:${opts.generatorName} schema.` +
|
||||
(unmatched.possible.length > 0
|
||||
? ` Possible matches : ${unmatched.possible.join()}`
|
||||
: '');
|
||||
logger.fatal(message);
|
||||
});
|
||||
|
||||
return { status: 1, loggingQueue: [] };
|
||||
}
|
||||
|
||||
try {
|
||||
await workflow
|
||||
.execute({
|
||||
collection: opts.collectionName,
|
||||
schematic: opts.generatorName,
|
||||
options: { ...defaults, ...schematicOptions },
|
||||
options: opts.generatorOptions,
|
||||
debug: opts.debug,
|
||||
logger,
|
||||
})
|
||||
.toPromise();
|
||||
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
throw e;
|
||||
}
|
||||
if (!record.error) {
|
||||
record.loggingQueue.forEach((log) => logger.info(log));
|
||||
}
|
||||
|
||||
if (opts.dryRun && printDryRunMessage) {
|
||||
logger.warn(`\nNOTE: The "dryRun" flag means no changes were made.`);
|
||||
}
|
||||
return { status: 0, loggingQueue: record.loggingQueue };
|
||||
}
|
||||
|
||||
function isTTY(): boolean {
|
||||
return !!process.stdout.isTTY && process.env['CI'] !== 'true';
|
||||
}
|
||||
|
||||
class MigrationEngineHost extends NodeModulesEngineHost {
|
||||
private nodeInstallLogPrinted = false;
|
||||
|
||||
@ -411,16 +253,100 @@ class MigrationsWorkflow extends BaseWorkflow {
|
||||
}
|
||||
}
|
||||
|
||||
class NxScopedHost extends virtualFs.ScopedHost<any> {
|
||||
constructor(root: Path) {
|
||||
super(new NodeJsSyncHost(), root);
|
||||
}
|
||||
|
||||
read(path: Path): Observable<FileBuffer> {
|
||||
if (this.isWorkspaceConfig(path)) {
|
||||
return this.isNewFormat().pipe(
|
||||
switchMap((newFormat) => {
|
||||
if (newFormat) {
|
||||
return super.read(path).pipe(
|
||||
map((r) => {
|
||||
try {
|
||||
const w = JSON.parse(Buffer.from(r).toString());
|
||||
return Buffer.from(
|
||||
JSON.stringify(
|
||||
new Workspaces().fromNewToOldFormat(w),
|
||||
null,
|
||||
2
|
||||
)
|
||||
);
|
||||
} catch (e) {
|
||||
return r;
|
||||
}
|
||||
})
|
||||
);
|
||||
} else {
|
||||
return super.read(path);
|
||||
}
|
||||
})
|
||||
);
|
||||
} else {
|
||||
return super.read(path);
|
||||
}
|
||||
}
|
||||
|
||||
write(path: Path, content: FileBuffer): Observable<void> {
|
||||
if (this.isWorkspaceConfig(path)) {
|
||||
return this.isNewFormat().pipe(
|
||||
switchMap((newFormat) => {
|
||||
if (newFormat) {
|
||||
try {
|
||||
const w = JSON.parse(Buffer.from(content).toString());
|
||||
return super.write(
|
||||
path,
|
||||
Buffer.from(
|
||||
JSON.stringify(
|
||||
new Workspaces().fromOldToNewFormat(w),
|
||||
null,
|
||||
2
|
||||
)
|
||||
)
|
||||
);
|
||||
} catch (e) {
|
||||
return super.write(path, content);
|
||||
}
|
||||
} else {
|
||||
return super.write(path, content);
|
||||
}
|
||||
})
|
||||
);
|
||||
} else {
|
||||
return super.write(path, content);
|
||||
}
|
||||
}
|
||||
|
||||
private isWorkspaceConfig(path: Path) {
|
||||
const p = path.toString();
|
||||
return (
|
||||
p === 'angular.json' ||
|
||||
p === '/angular.json' ||
|
||||
p === 'workspace.json' ||
|
||||
p === '/workspace.json'
|
||||
);
|
||||
}
|
||||
|
||||
private isNewFormat() {
|
||||
return super.exists('/angular.json' as any).pipe(
|
||||
switchMap((isAngularJson) => {
|
||||
return super
|
||||
.read((isAngularJson ? '/angular.json' : '/workspace.json') as any)
|
||||
.pipe(map((r) => !!JSON.parse(Buffer.from(r).toString()).generators));
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function generate(
|
||||
logger: logging.Logger,
|
||||
root: string,
|
||||
opts: GenerateOptions
|
||||
) {
|
||||
const fsHost = new virtualFs.ScopedHost(
|
||||
new NodeJsSyncHost(),
|
||||
normalize(root)
|
||||
);
|
||||
const workflow = await createWorkflow(fsHost, root, opts);
|
||||
const fsHost = new NxScopedHost(normalize(root));
|
||||
const workflow = createWorkflow(fsHost, root, opts);
|
||||
const collection = getCollection(workflow, opts.collectionName);
|
||||
const schematic = collection.createSchematic(opts.generatorName, true);
|
||||
return (
|
||||
@ -440,7 +366,7 @@ export async function runMigration(
|
||||
collection: string,
|
||||
schematic: string
|
||||
) {
|
||||
const host = new virtualFs.ScopedHost(new NodeJsSyncHost(), normalize(root));
|
||||
const host = new NxScopedHost(normalize(root));
|
||||
const workflow = new MigrationsWorkflow(host, logger);
|
||||
return workflow
|
||||
.execute({
|
||||
@ -484,10 +410,7 @@ export function wrapAngularDevkitSchematic(
|
||||
}
|
||||
};
|
||||
|
||||
const fsHost = new virtualFs.ScopedHost(
|
||||
new NodeJsSyncHost(),
|
||||
normalize(host.root)
|
||||
);
|
||||
const fsHost = new NxScopedHost(normalize(host.root));
|
||||
|
||||
await Promise.all(
|
||||
(host as taoTree.FsTree).listChanges().map(async (c) => {
|
||||
@ -510,7 +433,7 @@ export function wrapAngularDevkitSchematic(
|
||||
force: false,
|
||||
defaults: false,
|
||||
};
|
||||
const workflow = await createWorkflow(fsHost, host.root, options);
|
||||
const workflow = createWorkflow(fsHost, host.root, options);
|
||||
const collection = getCollection(workflow, collectionName);
|
||||
const schematic = collection.createSchematic(generatorName, true);
|
||||
const res = await runSchematic(
|
||||
@ -520,7 +443,6 @@ export function wrapAngularDevkitSchematic(
|
||||
options,
|
||||
schematic,
|
||||
false,
|
||||
false,
|
||||
recorder
|
||||
);
|
||||
|
||||
@ -536,22 +458,17 @@ export async function invokeNew(
|
||||
root: string,
|
||||
opts: GenerateOptions
|
||||
) {
|
||||
const fsHost = new virtualFs.ScopedHost(
|
||||
new NodeJsSyncHost(),
|
||||
normalize(root)
|
||||
);
|
||||
const workflow = await createWorkflow(fsHost, root, opts);
|
||||
const fsHost = new NxScopedHost(normalize(root));
|
||||
const workflow = createWorkflow(fsHost, root, opts);
|
||||
const collection = getCollection(workflow, opts.collectionName);
|
||||
const schematic = collection.createSchematic('new', true);
|
||||
const allowAdditionalArgs = true; // we can't yet know the schema to validate against
|
||||
return (
|
||||
await runSchematic(
|
||||
root,
|
||||
workflow,
|
||||
logger,
|
||||
{ ...opts, generatorName: schematic.description.name },
|
||||
schematic,
|
||||
allowAdditionalArgs
|
||||
schematic
|
||||
)
|
||||
).status;
|
||||
}
|
||||
|
||||
@ -146,7 +146,6 @@ export async function run(root: string, args: string[], isVerbose: boolean) {
|
||||
|
||||
const target = workspace.projects[opts.project].targets[opts.target];
|
||||
const [nodeModule, executor] = target.executor.split(':');
|
||||
if (ws.isNxExecutor(nodeModule, executor)) {
|
||||
const { schema, implementation } = ws.readExecutor(nodeModule, executor);
|
||||
const combinedOptions = combineOptionsForExecutor(
|
||||
opts.runOptions,
|
||||
@ -158,9 +157,14 @@ export async function run(root: string, args: string[], isVerbose: boolean) {
|
||||
printRunHelp(opts, schema, logger);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (ws.isNxExecutor(nodeModule, executor)) {
|
||||
return await implementation(combinedOptions, { root, target, workspace });
|
||||
} else {
|
||||
return (await import('./ngcli-adapter')).run(logger, root, opts);
|
||||
return (await import('./ngcli-adapter')).run(logger, root, {
|
||||
...opts,
|
||||
runOptions: combinedOptions,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -255,6 +255,52 @@ describe('params', () => {
|
||||
|
||||
expect(opts).toEqual({ a: [{ key: 'inner' }, { key: 'inner' }] });
|
||||
});
|
||||
|
||||
it('should set the default array value', () => {
|
||||
const opts = setDefaults(
|
||||
{},
|
||||
{
|
||||
properties: {
|
||||
a: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
key: {
|
||||
type: 'string',
|
||||
default: 'inner',
|
||||
},
|
||||
},
|
||||
},
|
||||
default: [],
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
expect(opts).toEqual({ a: [] });
|
||||
});
|
||||
|
||||
it('should resolve types using refs', () => {
|
||||
const opts = setDefaults(
|
||||
{},
|
||||
{
|
||||
properties: {
|
||||
a: {
|
||||
$ref: '#/definitions/a',
|
||||
},
|
||||
},
|
||||
definitions: {
|
||||
a: {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
expect(opts).toEqual({ a: true });
|
||||
});
|
||||
});
|
||||
|
||||
describe('convertPositionParamsIntoNamedParams', () => {
|
||||
@ -377,5 +423,27 @@ describe('params', () => {
|
||||
"Property 'key' does not match the schema. 'string' should be a 'boolean'."
|
||||
);
|
||||
});
|
||||
|
||||
it('should resolve types using refs', () => {
|
||||
expect(() =>
|
||||
validateOptsAgainstSchema(
|
||||
{ key: 'string' },
|
||||
{
|
||||
properties: {
|
||||
key: {
|
||||
$ref: '#/definitions/key',
|
||||
},
|
||||
},
|
||||
definitions: {
|
||||
key: {
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
).toThrow(
|
||||
"Property 'key' does not match the schema. 'string' should be a 'boolean'."
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -12,6 +12,7 @@ type Properties = {
|
||||
alias?: string;
|
||||
description?: string;
|
||||
default?: string | number | boolean | string[];
|
||||
$ref?: string;
|
||||
$default?: { $source: 'argv'; index: number };
|
||||
'x-prompt'?: string | { message: string; type: string; items: any[] };
|
||||
};
|
||||
@ -20,6 +21,7 @@ export type Schema = {
|
||||
properties: Properties;
|
||||
required?: string[];
|
||||
description?: string;
|
||||
definitions?: Properties;
|
||||
};
|
||||
|
||||
export type Unmatched = {
|
||||
@ -143,13 +145,19 @@ export function validateOptsAgainstSchema(
|
||||
opts: { [k: string]: any },
|
||||
schema: Schema
|
||||
) {
|
||||
validateObject(opts, schema.properties || {}, schema.required || []);
|
||||
validateObject(
|
||||
opts,
|
||||
schema.properties || {},
|
||||
schema.required || [],
|
||||
schema.definitions || {}
|
||||
);
|
||||
}
|
||||
|
||||
export function validateObject(
|
||||
opts: { [k: string]: any },
|
||||
properties: Properties,
|
||||
required: string[]
|
||||
required: string[],
|
||||
definitions: Properties
|
||||
) {
|
||||
required.forEach((p) => {
|
||||
if (opts[p] === undefined) {
|
||||
@ -158,13 +166,22 @@ export function validateObject(
|
||||
});
|
||||
|
||||
Object.keys(opts).forEach((p) => {
|
||||
validateProperty(p, opts[p], properties[p]);
|
||||
validateProperty(p, opts[p], properties[p], definitions);
|
||||
});
|
||||
}
|
||||
|
||||
function validateProperty(propName: string, value: any, schema: any) {
|
||||
function validateProperty(
|
||||
propName: string,
|
||||
value: any,
|
||||
schema: any,
|
||||
definitions: Properties
|
||||
) {
|
||||
if (!schema) return;
|
||||
|
||||
if (schema.$ref) {
|
||||
schema = resolveDefinition(schema.$ref, definitions);
|
||||
}
|
||||
|
||||
if (schema.oneOf) {
|
||||
if (!Array.isArray(schema.oneOf))
|
||||
throw new Error(`Invalid schema file. oneOf must be an array.`);
|
||||
@ -172,7 +189,7 @@ function validateProperty(propName: string, value: any, schema: any) {
|
||||
let passes = false;
|
||||
schema.oneOf.forEach((r) => {
|
||||
try {
|
||||
validateProperty(propName, value, r);
|
||||
validateProperty(propName, value, r, definitions);
|
||||
passes = true;
|
||||
} catch (e) {}
|
||||
});
|
||||
@ -190,11 +207,16 @@ function validateProperty(propName: string, value: any, schema: any) {
|
||||
} else if (Array.isArray(value)) {
|
||||
if (schema.type !== 'array') throwInvalidSchema(propName, schema);
|
||||
value.forEach((valueInArray) =>
|
||||
validateProperty(propName, valueInArray, schema.items || {})
|
||||
validateProperty(propName, valueInArray, schema.items || {}, definitions)
|
||||
);
|
||||
} else {
|
||||
if (schema.type !== 'object') throwInvalidSchema(propName, schema);
|
||||
validateObject(value, schema.properties || {}, schema.required || []);
|
||||
validateObject(
|
||||
value,
|
||||
schema.properties || {},
|
||||
schema.required || [],
|
||||
definitions
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -209,24 +231,29 @@ function throwInvalidSchema(propName: string, schema: any) {
|
||||
}
|
||||
|
||||
export function setDefaults(opts: { [k: string]: any }, schema: Schema) {
|
||||
setDefaultsInObject(opts, schema.properties);
|
||||
setDefaultsInObject(opts, schema.properties || {}, schema.definitions || {});
|
||||
return opts;
|
||||
}
|
||||
|
||||
function setDefaultsInObject(
|
||||
opts: { [k: string]: any },
|
||||
properties: Properties
|
||||
properties: Properties,
|
||||
definitions: Properties
|
||||
) {
|
||||
Object.keys(properties).forEach((p) => {
|
||||
setPropertyDefault(opts, p, properties[p]);
|
||||
setPropertyDefault(opts, p, properties[p], definitions);
|
||||
});
|
||||
}
|
||||
|
||||
function setPropertyDefault(
|
||||
opts: { [k: string]: any },
|
||||
propName: string,
|
||||
schema: any
|
||||
schema: any,
|
||||
definitions: Properties
|
||||
) {
|
||||
if (schema.$ref) {
|
||||
schema = resolveDefinition(schema.$ref, definitions);
|
||||
}
|
||||
if (schema.type !== 'object' && schema.type !== 'array') {
|
||||
if (opts[propName] === undefined && schema.default !== undefined) {
|
||||
opts[propName] = schema.default;
|
||||
@ -239,12 +266,28 @@ function setPropertyDefault(
|
||||
items.type === 'object'
|
||||
) {
|
||||
opts[propName].forEach((valueInArray) =>
|
||||
setDefaultsInObject(valueInArray, items.properties || {})
|
||||
setDefaultsInObject(valueInArray, items.properties || {}, definitions)
|
||||
);
|
||||
} else if (!opts[propName] && schema.default) {
|
||||
opts[propName] = schema.default;
|
||||
}
|
||||
} else {
|
||||
setDefaultsInObject(opts[propName], schema.properties);
|
||||
if (!opts[propName]) {
|
||||
opts[propName] = {};
|
||||
}
|
||||
setDefaultsInObject(opts[propName], schema.properties || {}, definitions);
|
||||
}
|
||||
}
|
||||
|
||||
function resolveDefinition(ref: string, definitions: Properties) {
|
||||
if (!ref.startsWith('#/definitions/')) {
|
||||
throw new Error(`$ref should start with "#/definitions/"`);
|
||||
}
|
||||
const definition = ref.split('#/definitions/')[1];
|
||||
if (!definitions[definition]) {
|
||||
throw new Error(`Cannot resolve ${ref}`);
|
||||
}
|
||||
return definitions[definition];
|
||||
}
|
||||
|
||||
export function convertPositionParamsIntoNamedParams(
|
||||
@ -291,15 +334,12 @@ export async function combineOptionsForGenerator(
|
||||
commandLineOpts: Options,
|
||||
collectionName: string,
|
||||
generatorName: string,
|
||||
ws: WorkspaceConfiguration,
|
||||
ws: WorkspaceConfiguration | null,
|
||||
schema: Schema,
|
||||
isInteractive: boolean
|
||||
) {
|
||||
const generatorDefaults =
|
||||
ws.generators &&
|
||||
ws.generators[collectionName] &&
|
||||
ws.generators[collectionName][generatorName]
|
||||
? ws.generators[collectionName][generatorName]
|
||||
const generatorDefaults = ws
|
||||
? getGeneratorDefaults(ws, collectionName, generatorName)
|
||||
: {};
|
||||
let combined = convertAliases(
|
||||
coerceTypesInOptions({ ...generatorDefaults, ...commandLineOpts }, schema),
|
||||
@ -311,7 +351,7 @@ export async function combineOptionsForGenerator(
|
||||
schema,
|
||||
(commandLineOpts['_'] as string[]) || []
|
||||
);
|
||||
if (isInteractive) {
|
||||
if (isInteractive && isTTY()) {
|
||||
combined = await promptForValues(combined, schema);
|
||||
}
|
||||
setDefaults(combined, schema);
|
||||
@ -319,24 +359,44 @@ export async function combineOptionsForGenerator(
|
||||
return combined;
|
||||
}
|
||||
|
||||
function getGeneratorDefaults(
|
||||
ws: WorkspaceConfiguration,
|
||||
collectionName: string,
|
||||
generatorName: string
|
||||
) {
|
||||
if (!ws.generators) return {};
|
||||
if (
|
||||
ws.generators[collectionName] &&
|
||||
ws.generators[collectionName][generatorName]
|
||||
) {
|
||||
return ws.generators[collectionName][generatorName];
|
||||
} else if (ws.generators[`${collectionName}:${generatorName}`]) {
|
||||
return ws.generators[`${collectionName}:${generatorName}`];
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
async function promptForValues(opts: Options, schema: Schema) {
|
||||
const prompts = [];
|
||||
Object.entries(schema.properties).forEach(([k, v]) => {
|
||||
if (v['x-prompt'] && opts[k] === undefined) {
|
||||
const question = {
|
||||
name: k,
|
||||
message: v['x-prompt'],
|
||||
default: v.default,
|
||||
} as any;
|
||||
|
||||
if (typeof v['x-prompt'] === 'string') {
|
||||
question.message = v['x-prompt'];
|
||||
question.type = v.type;
|
||||
} else if (
|
||||
v['x-prompt'].type == 'confirmation' ||
|
||||
v['x-prompt'].type == 'confirm'
|
||||
) {
|
||||
question.message = v['x-prompt'].message;
|
||||
question.type = 'confirm';
|
||||
} else {
|
||||
question.message = v['x-prompt'].message;
|
||||
question.type = 'list';
|
||||
question.choices =
|
||||
v['x-prompt'].items &&
|
||||
@ -379,3 +439,7 @@ export function lookupUnmatched(opts: Options, schema: Schema): Options {
|
||||
}
|
||||
return opts;
|
||||
}
|
||||
|
||||
function isTTY(): boolean {
|
||||
return !!process.stdout.isTTY && process.env['CI'] !== 'true';
|
||||
}
|
||||
|
||||
@ -128,31 +128,60 @@ export class Workspaces {
|
||||
const w = JSON.parse(
|
||||
fs.readFileSync(path.join(root, workspaceConfigName(root))).toString()
|
||||
);
|
||||
return this.fromOldToNewFormat(w);
|
||||
}
|
||||
|
||||
fromOldToNewFormat(w: any) {
|
||||
Object.values(w.projects || {}).forEach((project: any) => {
|
||||
if (!project.targets && project.architect) {
|
||||
if (project.architect) {
|
||||
project.targets = project.architect;
|
||||
project.architect = undefined;
|
||||
delete project.architect;
|
||||
}
|
||||
|
||||
Object.values(project.targets || {}).forEach((target: any) => {
|
||||
if (!target.executor && target.builder) {
|
||||
if (target.builder) {
|
||||
target.executor = target.builder;
|
||||
target.builder = undefined;
|
||||
delete target.builder;
|
||||
}
|
||||
});
|
||||
|
||||
if (!project.generators && project.schematics) {
|
||||
if (project.schematics) {
|
||||
project.generators = project.schematics;
|
||||
project.schematics = undefined;
|
||||
delete project.schematics;
|
||||
}
|
||||
});
|
||||
|
||||
if (!w.generators && w.schematics) {
|
||||
if (w.schematics) {
|
||||
w.generators = w.schematics;
|
||||
w.schematics = undefined;
|
||||
delete w.schematics;
|
||||
}
|
||||
return w;
|
||||
}
|
||||
|
||||
fromNewToOldFormat(w: any) {
|
||||
Object.values(w.projects || {}).forEach((project: any) => {
|
||||
if (project.targets) {
|
||||
project.architect = project.targets;
|
||||
delete project.targets;
|
||||
}
|
||||
|
||||
Object.values(project.architect || {}).forEach((target: any) => {
|
||||
if (target.executor) {
|
||||
target.builder = target.executor;
|
||||
delete target.execuctor;
|
||||
}
|
||||
});
|
||||
|
||||
if (project.generators) {
|
||||
project.schematics = project.generators;
|
||||
delete project.generators;
|
||||
}
|
||||
});
|
||||
|
||||
if (w.generators) {
|
||||
w.schematics = w.generators;
|
||||
delete w.generators;
|
||||
}
|
||||
return w;
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user