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();
|
newProject();
|
||||||
const appName = uniq('app');
|
const appName = uniq('app');
|
||||||
|
|
||||||
runCLI(`generate @nrwl/next:app ${appName} --no-interactive`);
|
runCLI(`generate @nrwl/next:app ${appName}`);
|
||||||
|
|
||||||
const proxyConf = {
|
const proxyConf = {
|
||||||
'/external-api': {
|
'/external-api': {
|
||||||
|
|||||||
@ -126,7 +126,9 @@ export function newProject(): void {
|
|||||||
(f) => f !== '@nrwl/nx-plugin' && f !== `@nrwl/eslint-plugin-nx`
|
(f) => f !== '@nrwl/nx-plugin' && f !== `@nrwl/eslint-plugin-nx`
|
||||||
)
|
)
|
||||||
.forEach((p) => {
|
.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()}`);
|
execSync(`mv ./tmp/${currentCli()}/proj ${tmpBackupProjPath()}`);
|
||||||
|
|||||||
@ -9,5 +9,5 @@ module.exports = {
|
|||||||
resolver: '../../scripts/patched-jest-resolver.js',
|
resolver: '../../scripts/patched-jest-resolver.js',
|
||||||
moduleFileExtensions: ['ts', 'js', 'html'],
|
moduleFileExtensions: ['ts', 'js', 'html'],
|
||||||
coverageReporters: ['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",
|
"type": "string",
|
||||||
"enum": ["karma", "jest", "none"],
|
"enum": ["karma", "jest", "none"],
|
||||||
"description": "Test runner to use for unit tests",
|
"description": "Test runner to use for unit tests",
|
||||||
"default": "jest",
|
"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 ]"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"e2eTestRunner": {
|
"e2eTestRunner": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": ["protractor", "cypress", "none"],
|
"enum": ["protractor", "cypress", "none"],
|
||||||
"description": "Test runner to use for end to end (e2e) tests",
|
"description": "Test runner to use for end to end (e2e) tests",
|
||||||
"default": "cypress",
|
"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 ]"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"skipInstall": {
|
"skipInstall": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
|
|||||||
@ -57,7 +57,10 @@ export function parseRunOneOptions(
|
|||||||
// we need both to be able to run a target, no tasks runner
|
// we need both to be able to run a target, no tasks runner
|
||||||
const p =
|
const p =
|
||||||
workspaceConfigJson.projects && workspaceConfigJson.projects[project];
|
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 };
|
const res = { project, target, configuration, parsedArgs };
|
||||||
delete parsedArgs['configuration'];
|
delete parsedArgs['configuration'];
|
||||||
|
|||||||
@ -29,18 +29,15 @@
|
|||||||
},
|
},
|
||||||
"customServerPath": {
|
"customServerPath": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Use a custom server script",
|
"description": "Use a custom server script"
|
||||||
"default": null
|
|
||||||
},
|
},
|
||||||
"hostname": {
|
"hostname": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Hostname on which the application is served.",
|
"description": "Hostname on which the application is served."
|
||||||
"default": null
|
|
||||||
},
|
},
|
||||||
"proxyConfig": {
|
"proxyConfig": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Path to the proxy configuration file.",
|
"description": "Path to the proxy configuration file."
|
||||||
"default": null
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": []
|
"required": []
|
||||||
|
|||||||
@ -64,8 +64,7 @@
|
|||||||
},
|
},
|
||||||
"server": {
|
"server": {
|
||||||
"description": "The server script path to be used with next.",
|
"description": "The server script path to be used with next.",
|
||||||
"type": "string",
|
"type": "string"
|
||||||
"default": null
|
|
||||||
},
|
},
|
||||||
"linter": {
|
"linter": {
|
||||||
"description": "The tool to use for running lint checks.",
|
"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) {
|
export default function update(host: Tree) {
|
||||||
const p = readJson(host, 'package.json');
|
const p = readJson(host, 'package.json');
|
||||||
if (p['ng-update']) {
|
if (p['ng-update']) {
|
||||||
p['ng-migrate'] = p['ng-update'];
|
p['nx-migrations'] = p['ng-update'];
|
||||||
delete p['ng-update'];
|
delete p['ng-update'];
|
||||||
}
|
}
|
||||||
host.write('package.json', JSON.stringify(p, null, 2));
|
host.write('package.json', JSON.stringify(p, null, 2));
|
||||||
|
|||||||
@ -33,19 +33,7 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Project source path."
|
"description": "Project source path."
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"oneOf": [
|
|
||||||
{
|
|
||||||
"anyOf": [
|
|
||||||
{
|
|
||||||
"required": ["configFolder"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"required": ["pluginPath", "configPath", "srcRoot"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"docsMode": {
|
"docsMode": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
|
|||||||
@ -64,19 +64,7 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Project source path."
|
"description": "Project source path."
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"oneOf": [
|
|
||||||
{
|
|
||||||
"anyOf": [
|
|
||||||
{
|
|
||||||
"required": ["configFolder"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"required": ["pluginPath", "configPath", "srcRoot"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"docsMode": {
|
"docsMode": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
|
|||||||
@ -72,7 +72,7 @@ function parseGenerateOpts(
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
collectionName = generatorOptions.collection as string;
|
collectionName = generatorOptions.collection as string;
|
||||||
generatorName = '';
|
generatorName = 'new';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!collectionName) {
|
if (!collectionName) {
|
||||||
@ -164,9 +164,27 @@ function printChanges(fileChanges: FileChange[]) {
|
|||||||
|
|
||||||
export async function taoNew(root: string, args: string[], isVerbose = false) {
|
export async function taoNew(root: string, args: string[], isVerbose = false) {
|
||||||
const logger = getLogger(isVerbose);
|
const logger = getLogger(isVerbose);
|
||||||
|
const ws = new Workspaces();
|
||||||
return handleErrors(logger, isVerbose, async () => {
|
return handleErrors(logger, isVerbose, async () => {
|
||||||
const opts = parseGenerateOpts(args, 'new', null);
|
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,25 +204,25 @@ export async function generate(
|
|||||||
readDefaultCollection(workspaceDefinition)
|
readDefaultCollection(workspaceDefinition)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { schema, implementation } = ws.readGenerator(
|
||||||
|
opts.collectionName,
|
||||||
|
opts.generatorName
|
||||||
|
);
|
||||||
|
|
||||||
|
if (opts.help) {
|
||||||
|
printGenHelp(opts, schema, logger as any);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
const combinedOpts = await combineOptionsForGenerator(
|
||||||
|
opts.generatorOptions,
|
||||||
|
opts.collectionName,
|
||||||
|
opts.generatorName,
|
||||||
|
workspaceDefinition,
|
||||||
|
schema,
|
||||||
|
opts.interactive
|
||||||
|
);
|
||||||
|
|
||||||
if (ws.isNxGenerator(opts.collectionName, opts.generatorName)) {
|
if (ws.isNxGenerator(opts.collectionName, opts.generatorName)) {
|
||||||
const { schema, implementation } = ws.readGenerator(
|
|
||||||
opts.collectionName,
|
|
||||||
opts.generatorName
|
|
||||||
);
|
|
||||||
|
|
||||||
if (opts.help) {
|
|
||||||
printGenHelp(opts, schema, logger as any);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const combinedOpts = await combineOptionsForGenerator(
|
|
||||||
opts.generatorOptions,
|
|
||||||
opts.collectionName,
|
|
||||||
opts.generatorName,
|
|
||||||
workspaceDefinition,
|
|
||||||
schema,
|
|
||||||
opts.interactive
|
|
||||||
);
|
|
||||||
const host = new FsTree(root, isVerbose, logger);
|
const host = new FsTree(root, isVerbose, logger);
|
||||||
const task = await implementation(host, combinedOpts);
|
const task = await implementation(host, combinedOpts);
|
||||||
const changes = host.listChanges();
|
const changes = host.listChanges();
|
||||||
@ -219,7 +237,10 @@ export async function generate(
|
|||||||
logger.warn(`\nNOTE: The "dryRun" flag means no changes were made.`);
|
logger.warn(`\nNOTE: The "dryRun" flag means no changes were made.`);
|
||||||
}
|
}
|
||||||
} else {
|
} 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 { WorkspaceNodeModulesArchitectHost } from '@angular-devkit/architect/node';
|
||||||
import {
|
import {
|
||||||
json,
|
json,
|
||||||
JsonObject,
|
|
||||||
logging,
|
logging,
|
||||||
normalize,
|
normalize,
|
||||||
|
Path,
|
||||||
schema,
|
schema,
|
||||||
tags,
|
tags,
|
||||||
virtualFs,
|
virtualFs,
|
||||||
@ -12,13 +12,7 @@ import {
|
|||||||
} from '@angular-devkit/core';
|
} from '@angular-devkit/core';
|
||||||
import * as chalk from 'chalk';
|
import * as chalk from 'chalk';
|
||||||
import { NodeJsSyncHost } from '@angular-devkit/core/node';
|
import { NodeJsSyncHost } from '@angular-devkit/core/node';
|
||||||
import {
|
import { RunOptions } from './run';
|
||||||
coerceTypesInOptions,
|
|
||||||
convertAliases,
|
|
||||||
Options,
|
|
||||||
Schema,
|
|
||||||
} from '../shared/params';
|
|
||||||
import { printRunHelp, RunOptions } from './run';
|
|
||||||
import {
|
import {
|
||||||
FileSystemCollectionDescription,
|
FileSystemCollectionDescription,
|
||||||
FileSystemSchematicDescription,
|
FileSystemSchematicDescription,
|
||||||
@ -33,24 +27,25 @@ import {
|
|||||||
TaskExecutor,
|
TaskExecutor,
|
||||||
} from '@angular-devkit/schematics';
|
} from '@angular-devkit/schematics';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as inquirer from 'inquirer';
|
import { readFileSync } from 'fs';
|
||||||
import { detectPackageManager } from '@nrwl/tao/src/shared/package-manager';
|
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 * 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 { BaseWorkflow } from '@angular-devkit/schematics/src/workflow';
|
||||||
import { NodePackageName } from '@angular-devkit/schematics/tasks/package-manager/options';
|
import { NodePackageName } from '@angular-devkit/schematics/tasks/package-manager/options';
|
||||||
import { BuiltinTaskExecutor } from '@angular-devkit/schematics/tasks/node';
|
import { BuiltinTaskExecutor } from '@angular-devkit/schematics/tasks/node';
|
||||||
import { dirname, extname, join, resolve } from 'path';
|
import { dirname, extname, join, resolve } from 'path';
|
||||||
import { readFileSync } from 'fs';
|
|
||||||
import * as stripJsonComments from 'strip-json-comments';
|
import * as stripJsonComments from 'strip-json-comments';
|
||||||
|
import { FileBuffer } from '@angular-devkit/core/src/virtual-fs/host/interface';
|
||||||
function normalizeOptions(opts: Options, schema: Schema): Options {
|
import { Observable } from 'rxjs';
|
||||||
return convertAliases(coerceTypesInOptions(opts, schema), schema, false);
|
import { map, switchMap } from 'rxjs/operators';
|
||||||
}
|
|
||||||
|
|
||||||
export async function run(logger: any, root: string, opts: RunOptions) {
|
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(
|
const { workspace } = await workspaces.readWorkspace(
|
||||||
workspaceConfigName(root),
|
workspaceConfigName(root),
|
||||||
workspaces.createWorkspaceHost(fsHost)
|
workspaces.createWorkspaceHost(fsHost)
|
||||||
@ -60,32 +55,13 @@ export async function run(logger: any, root: string, opts: RunOptions) {
|
|||||||
registry.addPostTransform(schema.transforms.addUndefinedDefaults);
|
registry.addPostTransform(schema.transforms.addUndefinedDefaults);
|
||||||
const architectHost = new WorkspaceNodeModulesArchitectHost(workspace, root);
|
const architectHost = new WorkspaceNodeModulesArchitectHost(workspace, root);
|
||||||
const architect = new Architect(architectHost, registry);
|
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(
|
const run = await architect.scheduleTarget(
|
||||||
{
|
{
|
||||||
project: opts.project,
|
project: opts.project,
|
||||||
target: opts.target,
|
target: opts.target,
|
||||||
configuration: opts.configuration,
|
configuration: opts.configuration,
|
||||||
},
|
},
|
||||||
runOptions as JsonObject,
|
opts.runOptions,
|
||||||
{ logger }
|
{ logger }
|
||||||
);
|
);
|
||||||
const result = await run.output.toPromise();
|
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;
|
return result.success ? 0 : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createWorkflow(
|
function createWorkflow(
|
||||||
fsHost: virtualFs.Host<fs.Stats>,
|
fsHost: virtualFs.Host<fs.Stats>,
|
||||||
root: string,
|
root: string,
|
||||||
opts: any
|
opts: any
|
||||||
@ -106,77 +82,10 @@ async function createWorkflow(
|
|||||||
registry: new schema.CoreSchemaRegistry(formats.standardFormats),
|
registry: new schema.CoreSchemaRegistry(formats.standardFormats),
|
||||||
resolvePaths: [process.cwd(), root],
|
resolvePaths: [process.cwd(), root],
|
||||||
});
|
});
|
||||||
const _params = opts.generatorOptions._;
|
workflow.registry.addPostTransform(schema.transforms.addUndefinedDefaults);
|
||||||
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(
|
workflow.engineHost.registerOptionsTransform(
|
||||||
validateOptionsWithSchema(workflow.registry)
|
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;
|
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(
|
async function runSchematic(
|
||||||
root: string,
|
root: string,
|
||||||
workflow: NodeWorkflow,
|
workflow: NodeWorkflow,
|
||||||
@ -267,72 +146,35 @@ async function runSchematic(
|
|||||||
FileSystemCollectionDescription,
|
FileSystemCollectionDescription,
|
||||||
FileSystemSchematicDescription
|
FileSystemSchematicDescription
|
||||||
>,
|
>,
|
||||||
allowAdditionalArgs = false,
|
|
||||||
printDryRunMessage = true,
|
printDryRunMessage = true,
|
||||||
recorder: any = null
|
recorder: any = null
|
||||||
): Promise<{ status: number; loggingQueue: string[] }> {
|
): 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 };
|
const record = { loggingQueue: [] as string[], error: false };
|
||||||
workflow.reporter.subscribe(recorder || createRecorder(record, logger));
|
workflow.reporter.subscribe(recorder || createRecorder(record, logger));
|
||||||
|
|
||||||
const schematicOptions = normalizeOptions(
|
try {
|
||||||
opts.generatorOptions,
|
await workflow
|
||||||
flattenedSchema
|
.execute({
|
||||||
);
|
collection: opts.collectionName,
|
||||||
|
schematic: opts.generatorName,
|
||||||
if (schematicOptions['--'] && !allowAdditionalArgs) {
|
options: opts.generatorOptions,
|
||||||
schematicOptions['--'].forEach((unmatched) => {
|
debug: opts.debug,
|
||||||
const message =
|
logger,
|
||||||
`Could not match option '${unmatched.name}' to the ${opts.collectionName}:${opts.generatorName} schema.` +
|
})
|
||||||
(unmatched.possible.length > 0
|
.toPromise();
|
||||||
? ` Possible matches : ${unmatched.possible.join()}`
|
} catch (e) {
|
||||||
: '');
|
console.log(e);
|
||||||
logger.fatal(message);
|
throw e;
|
||||||
});
|
|
||||||
|
|
||||||
return { status: 1, loggingQueue: [] };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await workflow
|
|
||||||
.execute({
|
|
||||||
collection: opts.collectionName,
|
|
||||||
schematic: opts.generatorName,
|
|
||||||
options: { ...defaults, ...schematicOptions },
|
|
||||||
debug: opts.debug,
|
|
||||||
logger,
|
|
||||||
})
|
|
||||||
.toPromise();
|
|
||||||
|
|
||||||
if (!record.error) {
|
if (!record.error) {
|
||||||
record.loggingQueue.forEach((log) => logger.info(log));
|
record.loggingQueue.forEach((log) => logger.info(log));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (opts.dryRun && printDryRunMessage) {
|
if (opts.dryRun && printDryRunMessage) {
|
||||||
logger.warn(`\nNOTE: The "dryRun" flag means no changes were made.`);
|
logger.warn(`\nNOTE: The "dryRun" flag means no changes were made.`);
|
||||||
}
|
}
|
||||||
return { status: 0, loggingQueue: record.loggingQueue };
|
return { status: 0, loggingQueue: record.loggingQueue };
|
||||||
}
|
}
|
||||||
|
|
||||||
function isTTY(): boolean {
|
|
||||||
return !!process.stdout.isTTY && process.env['CI'] !== 'true';
|
|
||||||
}
|
|
||||||
|
|
||||||
class MigrationEngineHost extends NodeModulesEngineHost {
|
class MigrationEngineHost extends NodeModulesEngineHost {
|
||||||
private nodeInstallLogPrinted = false;
|
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(
|
export async function generate(
|
||||||
logger: logging.Logger,
|
logger: logging.Logger,
|
||||||
root: string,
|
root: string,
|
||||||
opts: GenerateOptions
|
opts: GenerateOptions
|
||||||
) {
|
) {
|
||||||
const fsHost = new virtualFs.ScopedHost(
|
const fsHost = new NxScopedHost(normalize(root));
|
||||||
new NodeJsSyncHost(),
|
const workflow = createWorkflow(fsHost, root, opts);
|
||||||
normalize(root)
|
|
||||||
);
|
|
||||||
const workflow = await createWorkflow(fsHost, root, opts);
|
|
||||||
const collection = getCollection(workflow, opts.collectionName);
|
const collection = getCollection(workflow, opts.collectionName);
|
||||||
const schematic = collection.createSchematic(opts.generatorName, true);
|
const schematic = collection.createSchematic(opts.generatorName, true);
|
||||||
return (
|
return (
|
||||||
@ -440,7 +366,7 @@ export async function runMigration(
|
|||||||
collection: string,
|
collection: string,
|
||||||
schematic: string
|
schematic: string
|
||||||
) {
|
) {
|
||||||
const host = new virtualFs.ScopedHost(new NodeJsSyncHost(), normalize(root));
|
const host = new NxScopedHost(normalize(root));
|
||||||
const workflow = new MigrationsWorkflow(host, logger);
|
const workflow = new MigrationsWorkflow(host, logger);
|
||||||
return workflow
|
return workflow
|
||||||
.execute({
|
.execute({
|
||||||
@ -484,10 +410,7 @@ export function wrapAngularDevkitSchematic(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const fsHost = new virtualFs.ScopedHost(
|
const fsHost = new NxScopedHost(normalize(host.root));
|
||||||
new NodeJsSyncHost(),
|
|
||||||
normalize(host.root)
|
|
||||||
);
|
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
(host as taoTree.FsTree).listChanges().map(async (c) => {
|
(host as taoTree.FsTree).listChanges().map(async (c) => {
|
||||||
@ -510,7 +433,7 @@ export function wrapAngularDevkitSchematic(
|
|||||||
force: false,
|
force: false,
|
||||||
defaults: false,
|
defaults: false,
|
||||||
};
|
};
|
||||||
const workflow = await createWorkflow(fsHost, host.root, options);
|
const workflow = createWorkflow(fsHost, host.root, options);
|
||||||
const collection = getCollection(workflow, collectionName);
|
const collection = getCollection(workflow, collectionName);
|
||||||
const schematic = collection.createSchematic(generatorName, true);
|
const schematic = collection.createSchematic(generatorName, true);
|
||||||
const res = await runSchematic(
|
const res = await runSchematic(
|
||||||
@ -520,7 +443,6 @@ export function wrapAngularDevkitSchematic(
|
|||||||
options,
|
options,
|
||||||
schematic,
|
schematic,
|
||||||
false,
|
false,
|
||||||
false,
|
|
||||||
recorder
|
recorder
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -536,22 +458,17 @@ export async function invokeNew(
|
|||||||
root: string,
|
root: string,
|
||||||
opts: GenerateOptions
|
opts: GenerateOptions
|
||||||
) {
|
) {
|
||||||
const fsHost = new virtualFs.ScopedHost(
|
const fsHost = new NxScopedHost(normalize(root));
|
||||||
new NodeJsSyncHost(),
|
const workflow = createWorkflow(fsHost, root, opts);
|
||||||
normalize(root)
|
|
||||||
);
|
|
||||||
const workflow = await createWorkflow(fsHost, root, opts);
|
|
||||||
const collection = getCollection(workflow, opts.collectionName);
|
const collection = getCollection(workflow, opts.collectionName);
|
||||||
const schematic = collection.createSchematic('new', true);
|
const schematic = collection.createSchematic('new', true);
|
||||||
const allowAdditionalArgs = true; // we can't yet know the schema to validate against
|
|
||||||
return (
|
return (
|
||||||
await runSchematic(
|
await runSchematic(
|
||||||
root,
|
root,
|
||||||
workflow,
|
workflow,
|
||||||
logger,
|
logger,
|
||||||
{ ...opts, generatorName: schematic.description.name },
|
{ ...opts, generatorName: schematic.description.name },
|
||||||
schematic,
|
schematic
|
||||||
allowAdditionalArgs
|
|
||||||
)
|
)
|
||||||
).status;
|
).status;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -146,21 +146,25 @@ export async function run(root: string, args: string[], isVerbose: boolean) {
|
|||||||
|
|
||||||
const target = workspace.projects[opts.project].targets[opts.target];
|
const target = workspace.projects[opts.project].targets[opts.target];
|
||||||
const [nodeModule, executor] = target.executor.split(':');
|
const [nodeModule, executor] = target.executor.split(':');
|
||||||
|
const { schema, implementation } = ws.readExecutor(nodeModule, executor);
|
||||||
|
const combinedOptions = combineOptionsForExecutor(
|
||||||
|
opts.runOptions,
|
||||||
|
opts.configuration,
|
||||||
|
target,
|
||||||
|
schema
|
||||||
|
);
|
||||||
|
if (opts.help) {
|
||||||
|
printRunHelp(opts, schema, logger);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (ws.isNxExecutor(nodeModule, executor)) {
|
if (ws.isNxExecutor(nodeModule, executor)) {
|
||||||
const { schema, implementation } = ws.readExecutor(nodeModule, executor);
|
|
||||||
const combinedOptions = combineOptionsForExecutor(
|
|
||||||
opts.runOptions,
|
|
||||||
opts.configuration,
|
|
||||||
target,
|
|
||||||
schema
|
|
||||||
);
|
|
||||||
if (opts.help) {
|
|
||||||
printRunHelp(opts, schema, logger);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return await implementation(combinedOptions, { root, target, workspace });
|
return await implementation(combinedOptions, { root, target, workspace });
|
||||||
} else {
|
} 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' }] });
|
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', () => {
|
describe('convertPositionParamsIntoNamedParams', () => {
|
||||||
@ -377,5 +423,27 @@ describe('params', () => {
|
|||||||
"Property 'key' does not match the schema. 'string' should be a 'boolean'."
|
"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;
|
alias?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
default?: string | number | boolean | string[];
|
default?: string | number | boolean | string[];
|
||||||
|
$ref?: string;
|
||||||
$default?: { $source: 'argv'; index: number };
|
$default?: { $source: 'argv'; index: number };
|
||||||
'x-prompt'?: string | { message: string; type: string; items: any[] };
|
'x-prompt'?: string | { message: string; type: string; items: any[] };
|
||||||
};
|
};
|
||||||
@ -20,6 +21,7 @@ export type Schema = {
|
|||||||
properties: Properties;
|
properties: Properties;
|
||||||
required?: string[];
|
required?: string[];
|
||||||
description?: string;
|
description?: string;
|
||||||
|
definitions?: Properties;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Unmatched = {
|
export type Unmatched = {
|
||||||
@ -143,13 +145,19 @@ export function validateOptsAgainstSchema(
|
|||||||
opts: { [k: string]: any },
|
opts: { [k: string]: any },
|
||||||
schema: Schema
|
schema: Schema
|
||||||
) {
|
) {
|
||||||
validateObject(opts, schema.properties || {}, schema.required || []);
|
validateObject(
|
||||||
|
opts,
|
||||||
|
schema.properties || {},
|
||||||
|
schema.required || [],
|
||||||
|
schema.definitions || {}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function validateObject(
|
export function validateObject(
|
||||||
opts: { [k: string]: any },
|
opts: { [k: string]: any },
|
||||||
properties: Properties,
|
properties: Properties,
|
||||||
required: string[]
|
required: string[],
|
||||||
|
definitions: Properties
|
||||||
) {
|
) {
|
||||||
required.forEach((p) => {
|
required.forEach((p) => {
|
||||||
if (opts[p] === undefined) {
|
if (opts[p] === undefined) {
|
||||||
@ -158,13 +166,22 @@ export function validateObject(
|
|||||||
});
|
});
|
||||||
|
|
||||||
Object.keys(opts).forEach((p) => {
|
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) return;
|
||||||
|
|
||||||
|
if (schema.$ref) {
|
||||||
|
schema = resolveDefinition(schema.$ref, definitions);
|
||||||
|
}
|
||||||
|
|
||||||
if (schema.oneOf) {
|
if (schema.oneOf) {
|
||||||
if (!Array.isArray(schema.oneOf))
|
if (!Array.isArray(schema.oneOf))
|
||||||
throw new Error(`Invalid schema file. oneOf must be an array.`);
|
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;
|
let passes = false;
|
||||||
schema.oneOf.forEach((r) => {
|
schema.oneOf.forEach((r) => {
|
||||||
try {
|
try {
|
||||||
validateProperty(propName, value, r);
|
validateProperty(propName, value, r, definitions);
|
||||||
passes = true;
|
passes = true;
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
});
|
});
|
||||||
@ -190,11 +207,16 @@ function validateProperty(propName: string, value: any, schema: any) {
|
|||||||
} else if (Array.isArray(value)) {
|
} else if (Array.isArray(value)) {
|
||||||
if (schema.type !== 'array') throwInvalidSchema(propName, schema);
|
if (schema.type !== 'array') throwInvalidSchema(propName, schema);
|
||||||
value.forEach((valueInArray) =>
|
value.forEach((valueInArray) =>
|
||||||
validateProperty(propName, valueInArray, schema.items || {})
|
validateProperty(propName, valueInArray, schema.items || {}, definitions)
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
if (schema.type !== 'object') throwInvalidSchema(propName, schema);
|
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) {
|
export function setDefaults(opts: { [k: string]: any }, schema: Schema) {
|
||||||
setDefaultsInObject(opts, schema.properties);
|
setDefaultsInObject(opts, schema.properties || {}, schema.definitions || {});
|
||||||
return opts;
|
return opts;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setDefaultsInObject(
|
function setDefaultsInObject(
|
||||||
opts: { [k: string]: any },
|
opts: { [k: string]: any },
|
||||||
properties: Properties
|
properties: Properties,
|
||||||
|
definitions: Properties
|
||||||
) {
|
) {
|
||||||
Object.keys(properties).forEach((p) => {
|
Object.keys(properties).forEach((p) => {
|
||||||
setPropertyDefault(opts, p, properties[p]);
|
setPropertyDefault(opts, p, properties[p], definitions);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function setPropertyDefault(
|
function setPropertyDefault(
|
||||||
opts: { [k: string]: any },
|
opts: { [k: string]: any },
|
||||||
propName: string,
|
propName: string,
|
||||||
schema: any
|
schema: any,
|
||||||
|
definitions: Properties
|
||||||
) {
|
) {
|
||||||
|
if (schema.$ref) {
|
||||||
|
schema = resolveDefinition(schema.$ref, definitions);
|
||||||
|
}
|
||||||
if (schema.type !== 'object' && schema.type !== 'array') {
|
if (schema.type !== 'object' && schema.type !== 'array') {
|
||||||
if (opts[propName] === undefined && schema.default !== undefined) {
|
if (opts[propName] === undefined && schema.default !== undefined) {
|
||||||
opts[propName] = schema.default;
|
opts[propName] = schema.default;
|
||||||
@ -239,14 +266,30 @@ function setPropertyDefault(
|
|||||||
items.type === 'object'
|
items.type === 'object'
|
||||||
) {
|
) {
|
||||||
opts[propName].forEach((valueInArray) =>
|
opts[propName].forEach((valueInArray) =>
|
||||||
setDefaultsInObject(valueInArray, items.properties || {})
|
setDefaultsInObject(valueInArray, items.properties || {}, definitions)
|
||||||
);
|
);
|
||||||
|
} else if (!opts[propName] && schema.default) {
|
||||||
|
opts[propName] = schema.default;
|
||||||
}
|
}
|
||||||
} else {
|
} 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(
|
export function convertPositionParamsIntoNamedParams(
|
||||||
opts: { [k: string]: any },
|
opts: { [k: string]: any },
|
||||||
schema: Schema,
|
schema: Schema,
|
||||||
@ -291,16 +334,13 @@ export async function combineOptionsForGenerator(
|
|||||||
commandLineOpts: Options,
|
commandLineOpts: Options,
|
||||||
collectionName: string,
|
collectionName: string,
|
||||||
generatorName: string,
|
generatorName: string,
|
||||||
ws: WorkspaceConfiguration,
|
ws: WorkspaceConfiguration | null,
|
||||||
schema: Schema,
|
schema: Schema,
|
||||||
isInteractive: boolean
|
isInteractive: boolean
|
||||||
) {
|
) {
|
||||||
const generatorDefaults =
|
const generatorDefaults = ws
|
||||||
ws.generators &&
|
? getGeneratorDefaults(ws, collectionName, generatorName)
|
||||||
ws.generators[collectionName] &&
|
: {};
|
||||||
ws.generators[collectionName][generatorName]
|
|
||||||
? ws.generators[collectionName][generatorName]
|
|
||||||
: {};
|
|
||||||
let combined = convertAliases(
|
let combined = convertAliases(
|
||||||
coerceTypesInOptions({ ...generatorDefaults, ...commandLineOpts }, schema),
|
coerceTypesInOptions({ ...generatorDefaults, ...commandLineOpts }, schema),
|
||||||
schema,
|
schema,
|
||||||
@ -311,7 +351,7 @@ export async function combineOptionsForGenerator(
|
|||||||
schema,
|
schema,
|
||||||
(commandLineOpts['_'] as string[]) || []
|
(commandLineOpts['_'] as string[]) || []
|
||||||
);
|
);
|
||||||
if (isInteractive) {
|
if (isInteractive && isTTY()) {
|
||||||
combined = await promptForValues(combined, schema);
|
combined = await promptForValues(combined, schema);
|
||||||
}
|
}
|
||||||
setDefaults(combined, schema);
|
setDefaults(combined, schema);
|
||||||
@ -319,24 +359,44 @@ export async function combineOptionsForGenerator(
|
|||||||
return combined;
|
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) {
|
async function promptForValues(opts: Options, schema: Schema) {
|
||||||
const prompts = [];
|
const prompts = [];
|
||||||
Object.entries(schema.properties).forEach(([k, v]) => {
|
Object.entries(schema.properties).forEach(([k, v]) => {
|
||||||
if (v['x-prompt'] && opts[k] === undefined) {
|
if (v['x-prompt'] && opts[k] === undefined) {
|
||||||
const question = {
|
const question = {
|
||||||
name: k,
|
name: k,
|
||||||
message: v['x-prompt'],
|
|
||||||
default: v.default,
|
default: v.default,
|
||||||
} as any;
|
} as any;
|
||||||
|
|
||||||
if (typeof v['x-prompt'] === 'string') {
|
if (typeof v['x-prompt'] === 'string') {
|
||||||
|
question.message = v['x-prompt'];
|
||||||
question.type = v.type;
|
question.type = v.type;
|
||||||
} else if (
|
} else if (
|
||||||
v['x-prompt'].type == 'confirmation' ||
|
v['x-prompt'].type == 'confirmation' ||
|
||||||
v['x-prompt'].type == 'confirm'
|
v['x-prompt'].type == 'confirm'
|
||||||
) {
|
) {
|
||||||
|
question.message = v['x-prompt'].message;
|
||||||
question.type = 'confirm';
|
question.type = 'confirm';
|
||||||
} else {
|
} else {
|
||||||
|
question.message = v['x-prompt'].message;
|
||||||
question.type = 'list';
|
question.type = 'list';
|
||||||
question.choices =
|
question.choices =
|
||||||
v['x-prompt'].items &&
|
v['x-prompt'].items &&
|
||||||
@ -379,3 +439,7 @@ export function lookupUnmatched(opts: Options, schema: Schema): Options {
|
|||||||
}
|
}
|
||||||
return opts;
|
return opts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isTTY(): boolean {
|
||||||
|
return !!process.stdout.isTTY && process.env['CI'] !== 'true';
|
||||||
|
}
|
||||||
|
|||||||
@ -128,31 +128,60 @@ export class Workspaces {
|
|||||||
const w = JSON.parse(
|
const w = JSON.parse(
|
||||||
fs.readFileSync(path.join(root, workspaceConfigName(root))).toString()
|
fs.readFileSync(path.join(root, workspaceConfigName(root))).toString()
|
||||||
);
|
);
|
||||||
|
return this.fromOldToNewFormat(w);
|
||||||
|
}
|
||||||
|
|
||||||
|
fromOldToNewFormat(w: any) {
|
||||||
Object.values(w.projects || {}).forEach((project: any) => {
|
Object.values(w.projects || {}).forEach((project: any) => {
|
||||||
if (!project.targets && project.architect) {
|
if (project.architect) {
|
||||||
project.targets = project.architect;
|
project.targets = project.architect;
|
||||||
project.architect = undefined;
|
delete project.architect;
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.values(project.targets || {}).forEach((target: any) => {
|
Object.values(project.targets || {}).forEach((target: any) => {
|
||||||
if (!target.executor && target.builder) {
|
if (target.builder) {
|
||||||
target.executor = 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.generators = project.schematics;
|
||||||
project.schematics = undefined;
|
delete project.schematics;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!w.generators && w.schematics) {
|
if (w.schematics) {
|
||||||
w.generators = 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;
|
return w;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user