1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | exports.SchematicCommand = exports.UnknownCollectionError = void 0;
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 | const core_1 = require("@angular-devkit/core");
|
12 | const node_1 = require("@angular-devkit/core/node");
|
13 | const schematics_1 = require("@angular-devkit/schematics");
|
14 | const tools_1 = require("@angular-devkit/schematics/tools");
|
15 | const inquirer = require("inquirer");
|
16 | const systemPath = require("path");
|
17 | const color_1 = require("../utilities/color");
|
18 | const config_1 = require("../utilities/config");
|
19 | const json_schema_1 = require("../utilities/json-schema");
|
20 | const package_manager_1 = require("../utilities/package-manager");
|
21 | const tty_1 = require("../utilities/tty");
|
22 | const analytics_1 = require("./analytics");
|
23 | const command_1 = require("./command");
|
24 | const parser_1 = require("./parser");
|
25 | class UnknownCollectionError extends Error {
|
26 | constructor(collectionName) {
|
27 | super(`Invalid collection (${collectionName}).`);
|
28 | }
|
29 | }
|
30 | exports.UnknownCollectionError = UnknownCollectionError;
|
31 | class SchematicCommand extends command_1.Command {
|
32 | constructor(context, description, logger) {
|
33 | super(context, description, logger);
|
34 | this.allowPrivateSchematics = false;
|
35 | this._host = new node_1.NodeJsSyncHost();
|
36 | this.defaultCollectionName = '@schematics/angular';
|
37 | this.collectionName = this.defaultCollectionName;
|
38 | }
|
39 | async initialize(options) {
|
40 | await this._loadWorkspace();
|
41 | await this.createWorkflow(options);
|
42 | if (this.schematicName) {
|
43 |
|
44 | const collection = this.getCollection(this.collectionName);
|
45 | const schematic = this.getSchematic(collection, this.schematicName, true);
|
46 | const options = await json_schema_1.parseJsonSchemaToOptions(this._workflow.registry, schematic.description.schemaJson || {});
|
47 | this.description.options.push(...options.filter(x => !x.hidden));
|
48 |
|
49 | for (const o of this.description.options) {
|
50 | if (o.userAnalytics && !analytics_1.isPackageNameSafeForAnalytics(this.collectionName)) {
|
51 | o.userAnalytics = undefined;
|
52 | }
|
53 | }
|
54 | }
|
55 | }
|
56 | async printHelp() {
|
57 | await super.printHelp();
|
58 | this.logger.info('');
|
59 | const subCommandOption = this.description.options.filter(x => x.subcommands)[0];
|
60 | if (!subCommandOption || !subCommandOption.subcommands) {
|
61 | return 0;
|
62 | }
|
63 | const schematicNames = Object.keys(subCommandOption.subcommands);
|
64 | if (schematicNames.length > 1) {
|
65 | this.logger.info('Available Schematics:');
|
66 | const namesPerCollection = {};
|
67 | schematicNames.forEach(name => {
|
68 | let [collectionName, schematicName] = name.split(/:/, 2);
|
69 | if (!schematicName) {
|
70 | schematicName = collectionName;
|
71 | collectionName = this.collectionName;
|
72 | }
|
73 | if (!namesPerCollection[collectionName]) {
|
74 | namesPerCollection[collectionName] = [];
|
75 | }
|
76 | namesPerCollection[collectionName].push(schematicName);
|
77 | });
|
78 | const defaultCollection = await this.getDefaultSchematicCollection();
|
79 | Object.keys(namesPerCollection).forEach(collectionName => {
|
80 | const isDefault = defaultCollection == collectionName;
|
81 | this.logger.info(` Collection "${collectionName}"${isDefault ? ' (default)' : ''}:`);
|
82 | namesPerCollection[collectionName].forEach(schematicName => {
|
83 | this.logger.info(` ${schematicName}`);
|
84 | });
|
85 | });
|
86 | }
|
87 | else if (schematicNames.length == 1) {
|
88 | this.logger.info('Help for schematic ' + schematicNames[0]);
|
89 | await this.printHelpSubcommand(subCommandOption.subcommands[schematicNames[0]]);
|
90 | }
|
91 | return 0;
|
92 | }
|
93 | async printHelpUsage() {
|
94 | const subCommandOption = this.description.options.filter(x => x.subcommands)[0];
|
95 | if (!subCommandOption || !subCommandOption.subcommands) {
|
96 | return;
|
97 | }
|
98 | const schematicNames = Object.keys(subCommandOption.subcommands);
|
99 | if (schematicNames.length == 1) {
|
100 | this.logger.info(this.description.description);
|
101 | const opts = this.description.options.filter(x => x.positional === undefined);
|
102 | const [collectionName, schematicName] = schematicNames[0].split(/:/)[0];
|
103 |
|
104 |
|
105 | const displayName = collectionName == (await this.getDefaultSchematicCollection())
|
106 | ? schematicName
|
107 | : schematicNames[0];
|
108 | const schematicOptions = subCommandOption.subcommands[schematicNames[0]].options;
|
109 | const schematicArgs = schematicOptions.filter(x => x.positional !== undefined);
|
110 | const argDisplay = schematicArgs.length > 0
|
111 | ? ' ' + schematicArgs.map(a => `<${core_1.strings.dasherize(a.name)}>`).join(' ')
|
112 | : '';
|
113 | this.logger.info(core_1.tags.oneLine `
|
114 | usage: ng ${this.description.name} ${displayName}${argDisplay}
|
115 | ${opts.length > 0 ? `[options]` : ``}
|
116 | `);
|
117 | this.logger.info('');
|
118 | }
|
119 | else {
|
120 | await super.printHelpUsage();
|
121 | }
|
122 | }
|
123 | getEngine() {
|
124 | return this._workflow.engine;
|
125 | }
|
126 | getCollection(collectionName) {
|
127 | const engine = this.getEngine();
|
128 | const collection = engine.createCollection(collectionName);
|
129 | if (collection === null) {
|
130 | throw new UnknownCollectionError(collectionName);
|
131 | }
|
132 | return collection;
|
133 | }
|
134 | getSchematic(collection, schematicName, allowPrivate) {
|
135 | return collection.createSchematic(schematicName, allowPrivate);
|
136 | }
|
137 | setPathOptions(options, workingDir) {
|
138 | if (workingDir === '') {
|
139 | return {};
|
140 | }
|
141 | return options
|
142 | .filter(o => o.format === 'path')
|
143 | .map(o => o.name)
|
144 | .reduce((acc, curr) => {
|
145 | acc[curr] = workingDir;
|
146 | return acc;
|
147 | }, {});
|
148 | }
|
149 | |
150 |
|
151 |
|
152 | async createWorkflow(options) {
|
153 | if (this._workflow) {
|
154 | return this._workflow;
|
155 | }
|
156 | const { force, dryRun } = options;
|
157 | const fsHost = new core_1.virtualFs.ScopedHost(new node_1.NodeJsSyncHost(), core_1.normalize(this.workspace.root));
|
158 | const workflow = new tools_1.NodeWorkflow(fsHost, {
|
159 | force,
|
160 | dryRun,
|
161 | packageManager: await package_manager_1.getPackageManager(this.workspace.root),
|
162 | packageRegistry: options.packageRegistry,
|
163 | root: core_1.normalize(this.workspace.root),
|
164 | registry: new core_1.schema.CoreSchemaRegistry(schematics_1.formats.standardFormats),
|
165 | resolvePaths: !!this.workspace.configFile
|
166 |
|
167 | ? this.collectionName === this.defaultCollectionName
|
168 |
|
169 | ? [__dirname, process.cwd(), this.workspace.root]
|
170 | : [process.cwd(), this.workspace.root, __dirname]
|
171 |
|
172 | : [__dirname, process.cwd()],
|
173 | });
|
174 | workflow.engineHost.registerContextTransform(context => {
|
175 |
|
176 |
|
177 | const collectionName = context.schematic.collection.description.name;
|
178 | if (analytics_1.isPackageNameSafeForAnalytics(collectionName)) {
|
179 | return {
|
180 | ...context,
|
181 | analytics: this.analytics,
|
182 | };
|
183 | }
|
184 | else {
|
185 | return context;
|
186 | }
|
187 | });
|
188 | const getProjectName = () => {
|
189 | if (this._workspace) {
|
190 | const projectNames = getProjectsByPath(this._workspace, process.cwd(), this.workspace.root);
|
191 | if (projectNames.length === 1) {
|
192 | return projectNames[0];
|
193 | }
|
194 | else {
|
195 | if (projectNames.length > 1) {
|
196 | this.logger.warn(core_1.tags.oneLine `
|
197 | Two or more projects are using identical roots.
|
198 | Unable to determine project using current working directory.
|
199 | Using default workspace project instead.
|
200 | `);
|
201 | }
|
202 | const defaultProjectName = this._workspace.extensions['defaultProject'];
|
203 | if (typeof defaultProjectName === 'string' && defaultProjectName) {
|
204 | return defaultProjectName;
|
205 | }
|
206 | }
|
207 | }
|
208 | return undefined;
|
209 | };
|
210 | const defaultOptionTransform = async (schematic, current) => ({
|
211 | ...(await config_1.getSchematicDefaults(schematic.collection.name, schematic.name, getProjectName())),
|
212 | ...current,
|
213 | });
|
214 | workflow.engineHost.registerOptionsTransform(defaultOptionTransform);
|
215 | if (options.defaults) {
|
216 | workflow.registry.addPreTransform(core_1.schema.transforms.addUndefinedDefaults);
|
217 | }
|
218 | else {
|
219 | workflow.registry.addPostTransform(core_1.schema.transforms.addUndefinedDefaults);
|
220 | }
|
221 | workflow.engineHost.registerOptionsTransform(tools_1.validateOptionsWithSchema(workflow.registry));
|
222 | workflow.registry.addSmartDefaultProvider('projectName', getProjectName);
|
223 | workflow.registry.useXDeprecatedProvider(msg => this.logger.warn(msg));
|
224 | if (options.interactive !== false && tty_1.isTTY()) {
|
225 | workflow.registry.usePromptProvider((definitions) => {
|
226 | const questions = definitions.map(definition => {
|
227 | var _a;
|
228 | const question = {
|
229 | name: definition.id,
|
230 | message: definition.message,
|
231 | default: definition.default,
|
232 | };
|
233 | const validator = definition.validator;
|
234 | if (validator) {
|
235 | question.validate = input => validator(input);
|
236 | }
|
237 | switch (definition.type) {
|
238 | case 'confirmation':
|
239 | question.type = 'confirm';
|
240 | break;
|
241 | case 'list':
|
242 | question.type = definition.multiselect ? 'checkbox' : 'list';
|
243 | question.choices = (_a = definition.items) === null || _a === void 0 ? void 0 : _a.map(item => {
|
244 | return typeof item == 'string'
|
245 | ? item
|
246 | : {
|
247 | name: item.label,
|
248 | value: item.value,
|
249 | };
|
250 | });
|
251 | break;
|
252 | default:
|
253 | question.type = definition.type;
|
254 | break;
|
255 | }
|
256 | return question;
|
257 | });
|
258 | return inquirer.prompt(questions);
|
259 | });
|
260 | }
|
261 | return (this._workflow = workflow);
|
262 | }
|
263 | async getDefaultSchematicCollection() {
|
264 | let workspace = await config_1.getWorkspace('local');
|
265 | if (workspace) {
|
266 | const project = config_1.getProjectByCwd(workspace);
|
267 | if (project && workspace.getProjectCli(project)) {
|
268 | const value = workspace.getProjectCli(project)['defaultCollection'];
|
269 | if (typeof value == 'string') {
|
270 | return value;
|
271 | }
|
272 | }
|
273 | if (workspace.getCli()) {
|
274 | const value = workspace.getCli()['defaultCollection'];
|
275 | if (typeof value == 'string') {
|
276 | return value;
|
277 | }
|
278 | }
|
279 | }
|
280 | workspace = await config_1.getWorkspace('global');
|
281 | if (workspace && workspace.getCli()) {
|
282 | const value = workspace.getCli()['defaultCollection'];
|
283 | if (typeof value == 'string') {
|
284 | return value;
|
285 | }
|
286 | }
|
287 | return this.defaultCollectionName;
|
288 | }
|
289 | async runSchematic(options) {
|
290 | const { schematicOptions, debug, dryRun } = options;
|
291 | let { collectionName, schematicName } = options;
|
292 | let nothingDone = true;
|
293 | let loggingQueue = [];
|
294 | let error = false;
|
295 | const workflow = this._workflow;
|
296 | const workingDir = core_1.normalize(systemPath.relative(this.workspace.root, process.cwd()));
|
297 |
|
298 | const schematic = this.getSchematic(this.getCollection(collectionName), schematicName, this.allowPrivateSchematics);
|
299 |
|
300 |
|
301 | collectionName = schematic.collection.description.name;
|
302 | schematicName = schematic.description.name;
|
303 |
|
304 | if (collectionName !== this.defaultCollectionName) {
|
305 | const [ast, configPath] = config_1.getWorkspaceRaw('local');
|
306 | if (ast) {
|
307 | const projectsKeyValue = ast.properties.find(p => p.key.value === 'projects');
|
308 | if (!projectsKeyValue || projectsKeyValue.value.kind !== 'object') {
|
309 | return;
|
310 | }
|
311 | const positions = [];
|
312 | for (const projectKeyValue of projectsKeyValue.value.properties) {
|
313 | const projectNode = projectKeyValue.value;
|
314 | if (projectNode.kind !== 'object') {
|
315 | continue;
|
316 | }
|
317 | const targetsKeyValue = projectNode.properties.find(p => p.key.value === 'targets');
|
318 | if (targetsKeyValue) {
|
319 | positions.push(targetsKeyValue.start);
|
320 | }
|
321 | }
|
322 | if (positions.length > 0) {
|
323 | const warning = core_1.tags.oneLine `
|
324 | WARNING: This command may not execute successfully.
|
325 | The package/collection may not support the 'targets' field within '${configPath}'.
|
326 | This can be corrected by renaming the following 'targets' fields to 'architect':
|
327 | `;
|
328 | const locations = positions
|
329 | .map((p, i) => `${i + 1}) Line: ${p.line + 1}; Column: ${p.character + 1}`)
|
330 | .join('\n');
|
331 | this.logger.warn(warning + '\n' + locations + '\n');
|
332 | }
|
333 | }
|
334 | }
|
335 |
|
336 | let o = null;
|
337 | let args;
|
338 | if (!schematic.description.schemaJson) {
|
339 | args = await this.parseFreeFormArguments(schematicOptions || []);
|
340 | }
|
341 | else {
|
342 | o = await json_schema_1.parseJsonSchemaToOptions(workflow.registry, schematic.description.schemaJson);
|
343 | args = await this.parseArguments(schematicOptions || [], o);
|
344 | }
|
345 | const allowAdditionalProperties = typeof schematic.description.schemaJson === 'object' && schematic.description.schemaJson.additionalProperties;
|
346 | if (args['--'] && !allowAdditionalProperties) {
|
347 | args['--'].forEach(additional => {
|
348 | this.logger.fatal(`Unknown option: '${additional.split(/=/)[0]}'`);
|
349 | });
|
350 | return 1;
|
351 | }
|
352 | const pathOptions = o ? this.setPathOptions(o, workingDir) : {};
|
353 | let input = { ...pathOptions, ...args };
|
354 |
|
355 | const projectName = input.project !== undefined ? '' + input.project : null;
|
356 | const defaults = await config_1.getSchematicDefaults(collectionName, schematicName, projectName);
|
357 | input = {
|
358 | ...defaults,
|
359 | ...input,
|
360 | ...options.additionalOptions,
|
361 | };
|
362 | workflow.reporter.subscribe((event) => {
|
363 | nothingDone = false;
|
364 |
|
365 | const eventPath = event.path.startsWith('/') ? event.path.substr(1) : event.path;
|
366 | switch (event.kind) {
|
367 | case 'error':
|
368 | error = true;
|
369 | const desc = event.description == 'alreadyExist' ? 'already exists' : 'does not exist.';
|
370 | this.logger.warn(`ERROR! ${eventPath} ${desc}.`);
|
371 | break;
|
372 | case 'update':
|
373 | loggingQueue.push(core_1.tags.oneLine `
|
374 | ${color_1.colors.white('UPDATE')} ${eventPath} (${event.content.length} bytes)
|
375 | `);
|
376 | break;
|
377 | case 'create':
|
378 | loggingQueue.push(core_1.tags.oneLine `
|
379 | ${color_1.colors.green('CREATE')} ${eventPath} (${event.content.length} bytes)
|
380 | `);
|
381 | break;
|
382 | case 'delete':
|
383 | loggingQueue.push(`${color_1.colors.yellow('DELETE')} ${eventPath}`);
|
384 | break;
|
385 | case 'rename':
|
386 | const eventToPath = event.to.startsWith('/') ? event.to.substr(1) : event.to;
|
387 | loggingQueue.push(`${color_1.colors.blue('RENAME')} ${eventPath} => ${eventToPath}`);
|
388 | break;
|
389 | }
|
390 | });
|
391 | workflow.lifeCycle.subscribe(event => {
|
392 | if (event.kind == 'end' || event.kind == 'post-tasks-start') {
|
393 | if (!error) {
|
394 |
|
395 | loggingQueue.forEach(log => this.logger.info(log));
|
396 | }
|
397 | loggingQueue = [];
|
398 | error = false;
|
399 | }
|
400 | });
|
401 | return new Promise(resolve => {
|
402 | workflow
|
403 | .execute({
|
404 | collection: collectionName,
|
405 | schematic: schematicName,
|
406 | options: input,
|
407 | debug: debug,
|
408 | logger: this.logger,
|
409 | allowPrivate: this.allowPrivateSchematics,
|
410 | })
|
411 | .subscribe({
|
412 | error: (err) => {
|
413 |
|
414 | if (err instanceof schematics_1.UnsuccessfulWorkflowExecution) {
|
415 |
|
416 | this.logger.fatal('The Schematic workflow failed. See above.');
|
417 | }
|
418 | else if (debug) {
|
419 | this.logger.fatal(`An error occured:\n${err.message}\n${err.stack}`);
|
420 | }
|
421 | else {
|
422 | this.logger.fatal(err.message);
|
423 | }
|
424 | resolve(1);
|
425 | },
|
426 | complete: () => {
|
427 | const showNothingDone = !(options.showNothingDone === false);
|
428 | if (nothingDone && showNothingDone) {
|
429 | this.logger.info('Nothing to be done.');
|
430 | }
|
431 | if (dryRun) {
|
432 | this.logger.warn(`\nNOTE: The "dryRun" flag means no changes were made.`);
|
433 | }
|
434 | resolve();
|
435 | },
|
436 | });
|
437 | });
|
438 | }
|
439 | async parseFreeFormArguments(schematicOptions) {
|
440 | return parser_1.parseFreeFormArguments(schematicOptions);
|
441 | }
|
442 | async parseArguments(schematicOptions, options) {
|
443 | return parser_1.parseArguments(schematicOptions, options, this.logger);
|
444 | }
|
445 | async _loadWorkspace() {
|
446 | if (this._workspace) {
|
447 | return;
|
448 | }
|
449 | try {
|
450 | const { workspace } = await core_1.workspaces.readWorkspace(this.workspace.root, core_1.workspaces.createWorkspaceHost(this._host));
|
451 | this._workspace = workspace;
|
452 | }
|
453 | catch (err) {
|
454 | if (!this.allowMissingWorkspace) {
|
455 |
|
456 | throw err;
|
457 | }
|
458 | }
|
459 | }
|
460 | }
|
461 | exports.SchematicCommand = SchematicCommand;
|
462 | function getProjectsByPath(workspace, path, root) {
|
463 | if (workspace.projects.size === 1) {
|
464 | return Array.from(workspace.projects.keys());
|
465 | }
|
466 | const isInside = (base, potential) => {
|
467 | const absoluteBase = systemPath.resolve(root, base);
|
468 | const absolutePotential = systemPath.resolve(root, potential);
|
469 | const relativePotential = systemPath.relative(absoluteBase, absolutePotential);
|
470 | if (!relativePotential.startsWith('..') && !systemPath.isAbsolute(relativePotential)) {
|
471 | return true;
|
472 | }
|
473 | return false;
|
474 | };
|
475 | const projects = Array.from(workspace.projects.entries())
|
476 | .map(([name, project]) => [systemPath.resolve(root, project.root), name])
|
477 | .filter(tuple => isInside(tuple[0], path))
|
478 |
|
479 |
|
480 |
|
481 | .sort((a, b) => b[0].length - a[0].length);
|
482 | if (projects.length === 1) {
|
483 | return [projects[0][1]];
|
484 | }
|
485 | else if (projects.length > 1) {
|
486 | const firstPath = projects[0][0];
|
487 | return projects.filter(v => v[0] === firstPath).map(v => v[1]);
|
488 | }
|
489 | return [];
|
490 | }
|