feat(core): make older builders support new config formats

This commit is contained in:
victor savkin 2020-11-30 20:30:47 -05:00 committed by Victor Savkin
parent 6c1335ac83
commit 8afc5c5bce
18 changed files with 433 additions and 43086 deletions

View 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;
}

View File

@ -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': {

View File

@ -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()}`);

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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",

View File

@ -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'];

View File

@ -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": []

View File

@ -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.",

View File

@ -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));

View File

@ -33,19 +33,7 @@
"type": "string",
"description": "Project source path."
}
},
"oneOf": [
{
"anyOf": [
{
"required": ["configFolder"]
},
{
"required": ["pluginPath", "configPath", "srcRoot"]
}
]
}
]
},
"docsMode": {
"type": "boolean",

View File

@ -64,19 +64,7 @@
"type": "string",
"description": "Project source path."
}
},
"oneOf": [
{
"anyOf": [
{
"required": ["configFolder"]
},
{
"required": ["pluginPath", "configPath", "srcRoot"]
}
]
}
]
},
"docsMode": {
"type": "boolean",

View File

@ -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,
});
}
});
}

View File

@ -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;
}

View File

@ -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,
});
}
});
}

View File

@ -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'."
);
});
});
});

View File

@ -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';
}

View File

@ -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;
}