fix(@angular/cli): populate path with working directory in nested schematics

With this change we change the how we handle `"format": "path"` schematic property option. We replace the formatter in favour of a `SmartDefaultProvider`, which ensures that nested schematics can access the `workingDirectory`.
This commit is contained in:
Alan Agius 2022-05-24 16:37:19 +00:00 committed by Douglas Parker
parent cd2250fe23
commit e751464ea3
15 changed files with 78 additions and 19 deletions

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import { analytics, logging, normalize, schema, strings } from '@angular-devkit/core';
import { analytics, logging, schema, strings } from '@angular-devkit/core';
import { readFileSync } from 'fs';
import * as path from 'path';
import {
@ -197,8 +197,6 @@ export abstract class CommandModule<T extends {} = {}> implements CommandModuleI
* **Note:** This method should be called from the command bundler method.
*/
protected addSchemaOptionsToCommand<T>(localYargs: Argv<T>, options: Option[]): Argv<T> {
const workingDir = normalize(path.relative(this.context.root, process.cwd()));
for (const option of options) {
const {
default: defaultVal,
@ -211,7 +209,6 @@ export abstract class CommandModule<T extends {} = {}> implements CommandModuleI
hidden,
name,
choices,
format,
} = option;
const sharedOptions: YargsOptions & PositionalOptions = {
@ -224,11 +221,6 @@ export abstract class CommandModule<T extends {} = {}> implements CommandModuleI
...(this.context.args.options.help ? { default: defaultVal } : {}),
};
// Special case for schematics
if (workingDir && format === 'path' && name === 'path' && hidden) {
sharedOptions.default = workingDir;
}
if (positional === undefined) {
localYargs = localYargs.option(strings.dasherize(name), {
type,

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import { schema, tags } from '@angular-devkit/core';
import { normalize as devkitNormalize, isJsonObject, schema, tags } from '@angular-devkit/core';
import { Collection, UnsuccessfulWorkflowExecution, formats } from '@angular-devkit/schematics';
import {
FileSystemCollectionDescription,
@ -14,7 +14,7 @@ import {
NodeWorkflow,
} from '@angular-devkit/schematics/tools';
import type { CheckboxQuestion, Question } from 'inquirer';
import { resolve } from 'path';
import { relative, resolve } from 'path';
import { Argv } from 'yargs';
import { getProjectByCwd, getSchematicDefaults } from '../utilities/config';
import { memoize } from '../utilities/memoize';
@ -133,10 +133,16 @@ export abstract class SchematicsCommandModule
});
workflow.registry.addPostTransform(schema.transforms.addUndefinedDefaults);
workflow.registry.addSmartDefaultProvider('projectName', () => this.getProjectName());
workflow.registry.useXDeprecatedProvider((msg) => logger.warn(msg));
workflow.registry.addSmartDefaultProvider('projectName', () => this.getProjectName());
const workingDir = devkitNormalize(relative(this.context.root, process.cwd()));
workflow.registry.addSmartDefaultProvider('workingDirectory', () =>
workingDir === '' ? undefined : workingDir,
);
let shouldReportAnalytics = true;
workflow.engineHost.registerOptionsTransform(async (schematic, options) => {
if (shouldReportAnalytics) {
shouldReportAnalytics = false;
@ -150,6 +156,35 @@ export abstract class SchematicsCommandModule
]);
}
// TODO: The below should be removed in version 15 when we change 1P schematics to use the `workingDirectory smart default`.
// Handle `"format": "path"` options.
const schema = schematic?.schemaJson;
if (!options || !schema || !isJsonObject(schema)) {
return options;
}
if (!('path' in options && (options as Record<string, unknown>)['path'] === undefined)) {
return options;
}
const properties = schema?.['properties'];
if (!properties || !isJsonObject(properties)) {
return options;
}
const property = properties['path'];
if (!property || !isJsonObject(property)) {
return options;
}
if (property['format'] === 'path' && !property['$default']) {
(options as Record<string, unknown>)['path'] = workingDir || undefined;
this.context.logger.warn(
`The 'path' option in '${schematic?.schema}' is using deprecated behaviour.` +
`'workingDirectory' smart default provider should be used instead.`,
);
}
return options;
});

View File

@ -27,19 +27,16 @@
},
"main": {
"type": "string",
"format": "path",
"description": "The name of the main entry-point file.",
"default": "main.server.ts"
},
"appDir": {
"type": "string",
"format": "path",
"description": "The name of the application directory.",
"default": "app"
},
"rootModuleFileName": {
"type": "string",
"format": "path",
"description": "The name of the root module file",
"default": "app.server.module.ts"
},

View File

@ -18,6 +18,9 @@
"path": {
"type": "string",
"format": "path",
"$default": {
"$source": "workingDirectory"
},
"description": "The path at which to create the class, relative to the workspace root.",
"visible": false
},

View File

@ -9,6 +9,9 @@
"path": {
"type": "string",
"format": "path",
"$default": {
"$source": "workingDirectory"
},
"description": "The path at which to create the component file, relative to the current workspace. Default is a folder with the same name as the component in the project root.",
"visible": false
},

View File

@ -18,6 +18,9 @@
"path": {
"type": "string",
"format": "path",
"$default": {
"$source": "workingDirectory"
},
"description": "The path at which to create the interface that defines the directive, relative to the workspace root.",
"visible": false
},

View File

@ -18,6 +18,9 @@
"path": {
"type": "string",
"format": "path",
"$default": {
"$source": "workingDirectory"
},
"description": "The path at which to create the enum definition, relative to the current workspace.",
"visible": false
},

View File

@ -29,6 +29,9 @@
"path": {
"type": "string",
"format": "path",
"$default": {
"$source": "workingDirectory"
},
"description": "The path at which to create the interface that defines the guard, relative to the current workspace.",
"visible": false
},

View File

@ -18,6 +18,9 @@
"path": {
"type": "string",
"format": "path",
"$default": {
"$source": "workingDirectory"
},
"description": "The path at which to create the interceptor, relative to the workspace root.",
"visible": false
},

View File

@ -18,6 +18,9 @@
"path": {
"type": "string",
"format": "path",
"$default": {
"$source": "workingDirectory"
},
"description": "The path at which to create the interface, relative to the workspace root.",
"visible": false
},

View File

@ -18,6 +18,9 @@
"path": {
"type": "string",
"format": "path",
"$default": {
"$source": "workingDirectory"
},
"description": "The path at which to create the NgModule, relative to the workspace root.",
"visible": false
},

View File

@ -18,6 +18,9 @@
"path": {
"type": "string",
"format": "path",
"$default": {
"$source": "workingDirectory"
},
"description": "The path at which to create the pipe, relative to the workspace root.",
"visible": false
},

View File

@ -29,6 +29,9 @@
"path": {
"type": "string",
"format": "path",
"$default": {
"$source": "workingDirectory"
},
"description": "The path at which to create the interface that defines the resolver, relative to the current workspace.",
"visible": false
},

View File

@ -17,7 +17,9 @@
},
"path": {
"type": "string",
"format": "path",
"$default": {
"$source": "workingDirectory"
},
"description": "The path at which to create the service, relative to the workspace root.",
"visible": false
},

View File

@ -9,6 +9,9 @@
"path": {
"type": "string",
"format": "path",
"$default": {
"$source": "workingDirectory"
},
"description": "The path at which to create the worker file, relative to the current workspace.",
"visible": false
},