mirror of
https://github.com/angular/angular-cli.git
synced 2025-05-14 17:43:52 +08:00
feat(@angular/cli): add support for multiple schematics collections
The `schematicCollections` can be placed under the `cli` option in the global `.angular.json` configuration, at the root or at project level in `angular.json` . ```jsonc { "$schema": "./node_modules/@angular/cli/lib/config/schema.json", "version": 1, "cli": { "schematicCollections": ["@schematics/angular", "@angular/material"] } // ... } ``` **Rationale** When this option is not configured and a user would like to run a schematic which is not part of `@schematics/angular`, the collection name needs to be provided to `ng generate` command in the form of `[collection-name:schematic-name]`. This make the `ng generate` command too verbose for repeated usages. This is where `schematicCollections` comes handle. When adding `@angular/material` to the list of `schematicCollections`, the generate command will try to locate the schematic in the specified collections. ``` ng generate navigation ``` is equivalent to: ``` ng generate @angular/material:navigation ``` **Conflicting schematic names** When multiple collections have a schematic with the same name. Both `ng generate` and `ng new` will run the first schematic matched based on the ordering (as specified) of `schematicCollections`. DEPRECATED: The `defaultCollection` workspace option has been deprecated in favor of `schematicCollections`. Before ```json "defaultCollection": "@angular/material" ``` After ```json "schematicCollections": ["@angular/material"] ``` Closes #12157
This commit is contained in:
parent
c9c781c7d5
commit
366cabc66c
35
docs/specifications/schematic-collections-config.md
Normal file
35
docs/specifications/schematic-collections-config.md
Normal file
@ -0,0 +1,35 @@
|
||||
# Schematics Collections (`schematicCollections`)
|
||||
|
||||
The `schematicCollections` can be placed under the `cli` option in the global `.angular.json` configuration, at the root or at project level in `angular.json` .
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||
"version": 1,
|
||||
"cli": {
|
||||
"schematicCollections": ["@schematics/angular", "@angular/material"]
|
||||
}
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
## Rationale
|
||||
|
||||
When this option is not configured and a user would like to run a schematic which is not part of `@schematics/angular`,
|
||||
the collection name needs to be provided to `ng generate` command in the form of `[collection-name:schematic-name]`. This make the `ng generate` command too verbose for repeated usages.
|
||||
|
||||
This is where the `schematicCollections` option can be useful. When adding `@angular/material` to the list of `schematicCollections`, the generate command will try to locate the schematic in the specified collections.
|
||||
|
||||
```
|
||||
ng generate navigation
|
||||
```
|
||||
|
||||
is equivalent to:
|
||||
|
||||
```
|
||||
ng generate @angular/material:navigation
|
||||
```
|
||||
|
||||
## Conflicting schematic names
|
||||
|
||||
When multiple collections have a schematic with the same name. Both `ng generate` and `ng new` will run the first schematic matched based on the ordering (as specified) of `schematicCollections`.
|
@ -43,7 +43,16 @@
|
||||
"properties": {
|
||||
"defaultCollection": {
|
||||
"description": "The default schematics collection to use.",
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"x-deprecated": "Use 'schematicCollections' instead."
|
||||
},
|
||||
"schematicCollections": {
|
||||
"type": "array",
|
||||
"description": "The list of schematic collections to use.",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"uniqueItems": true
|
||||
}
|
||||
},
|
||||
"packageManager": {
|
||||
"description": "Specify which package manager tool to use.",
|
||||
@ -162,7 +171,16 @@
|
||||
"cli": {
|
||||
"defaultCollection": {
|
||||
"description": "The default schematics collection to use.",
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"x-deprecated": "Use 'schematicCollections' instead."
|
||||
},
|
||||
"schematicCollections": {
|
||||
"type": "array",
|
||||
"description": "The list of schematic collections to use.",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"uniqueItems": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"schematics": {
|
||||
|
@ -33,7 +33,7 @@ import { Option, parseJsonSchemaToOptions } from './utilities/json-schema';
|
||||
import { SchematicEngineHost } from './utilities/schematic-engine-host';
|
||||
import { subscribeToWorkflow } from './utilities/schematic-workflow';
|
||||
|
||||
const DEFAULT_SCHEMATICS_COLLECTION = '@schematics/angular';
|
||||
export const DEFAULT_SCHEMATICS_COLLECTION = '@schematics/angular';
|
||||
|
||||
export interface SchematicsCommandArgs {
|
||||
interactive: boolean;
|
||||
@ -95,16 +95,21 @@ export abstract class SchematicsCommandModule
|
||||
return parseJsonSchemaToOptions(workflow.registry, schemaJson);
|
||||
}
|
||||
|
||||
private _workflowForBuilder: NodeWorkflow | undefined;
|
||||
private _workflowForBuilder = new Map<string, NodeWorkflow>();
|
||||
protected getOrCreateWorkflowForBuilder(collectionName: string): NodeWorkflow {
|
||||
if (this._workflowForBuilder) {
|
||||
return this._workflowForBuilder;
|
||||
const cached = this._workflowForBuilder.get(collectionName);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
return (this._workflowForBuilder = new NodeWorkflow(this.context.root, {
|
||||
const workflow = new NodeWorkflow(this.context.root, {
|
||||
resolvePaths: this.getResolvePaths(collectionName),
|
||||
engineHostCreator: (options) => new SchematicEngineHost(options.resolvePaths),
|
||||
}));
|
||||
});
|
||||
|
||||
this._workflowForBuilder.set(collectionName, workflow);
|
||||
|
||||
return workflow;
|
||||
}
|
||||
|
||||
private _workflowForExecution: NodeWorkflow | undefined;
|
||||
@ -238,36 +243,55 @@ export abstract class SchematicsCommandModule
|
||||
return (this._workflowForExecution = workflow);
|
||||
}
|
||||
|
||||
private _defaultSchematicCollection: string | undefined;
|
||||
protected async getDefaultSchematicCollection(): Promise<string> {
|
||||
if (this._defaultSchematicCollection) {
|
||||
return this._defaultSchematicCollection;
|
||||
private _schematicCollections: Set<string> | undefined;
|
||||
protected async getSchematicCollections(): Promise<Set<string>> {
|
||||
if (this._schematicCollections) {
|
||||
return this._schematicCollections;
|
||||
}
|
||||
|
||||
let workspace = await getWorkspace('local');
|
||||
const getSchematicCollections = (
|
||||
configSection: Record<string, unknown> | undefined,
|
||||
): Set<string> | undefined => {
|
||||
if (!configSection) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (workspace) {
|
||||
const project = getProjectByCwd(workspace);
|
||||
const { schematicCollections, defaultCollection } = configSection;
|
||||
if (Array.isArray(schematicCollections)) {
|
||||
return new Set(schematicCollections);
|
||||
} else if (typeof defaultCollection === 'string') {
|
||||
return new Set([defaultCollection]);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const localWorkspace = await getWorkspace('local');
|
||||
if (localWorkspace) {
|
||||
const project = getProjectByCwd(localWorkspace);
|
||||
if (project) {
|
||||
const value = workspace.getProjectCli(project)['defaultCollection'];
|
||||
if (typeof value == 'string') {
|
||||
return (this._defaultSchematicCollection = value);
|
||||
const value = getSchematicCollections(localWorkspace.getProjectCli(project));
|
||||
if (value) {
|
||||
this._schematicCollections = value;
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
const value = workspace.getCli()['defaultCollection'];
|
||||
if (typeof value === 'string') {
|
||||
return (this._defaultSchematicCollection = value);
|
||||
}
|
||||
}
|
||||
|
||||
workspace = await getWorkspace('global');
|
||||
const value = workspace?.getCli()['defaultCollection'];
|
||||
if (typeof value === 'string') {
|
||||
return (this._defaultSchematicCollection = value);
|
||||
const globalWorkspace = await getWorkspace('global');
|
||||
const value =
|
||||
getSchematicCollections(localWorkspace?.getCli()) ??
|
||||
getSchematicCollections(globalWorkspace?.getCli());
|
||||
if (value) {
|
||||
this._schematicCollections = value;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
return (this._defaultSchematicCollection = DEFAULT_SCHEMATICS_COLLECTION);
|
||||
this._schematicCollections = new Set([DEFAULT_SCHEMATICS_COLLECTION]);
|
||||
|
||||
return this._schematicCollections;
|
||||
}
|
||||
|
||||
protected parseSchematicInfo(
|
||||
|
@ -103,6 +103,7 @@ export class ConfigCommandModule
|
||||
>([
|
||||
['cli.warnings.versionMismatch', undefined],
|
||||
['cli.defaultCollection', undefined],
|
||||
['cli.schematicCollections', undefined],
|
||||
['cli.packageManager', undefined],
|
||||
['cli.analytics', undefined],
|
||||
|
||||
|
@ -9,6 +9,7 @@
|
||||
import { strings } from '@angular-devkit/core';
|
||||
import { Argv } from 'yargs';
|
||||
import {
|
||||
CommandModuleError,
|
||||
CommandModuleImplementation,
|
||||
Options,
|
||||
OtherOptions,
|
||||
@ -48,28 +49,9 @@ export class GenerateCommandModule
|
||||
handler: (options) => this.handler(options),
|
||||
});
|
||||
|
||||
const collectionName = await this.getCollectionName();
|
||||
const workflow = this.getOrCreateWorkflowForBuilder(collectionName);
|
||||
const collection = workflow.engine.createCollection(collectionName);
|
||||
const schematicsInCollection = collection.description.schematics;
|
||||
|
||||
// We cannot use `collection.listSchematicNames()` as this doesn't return hidden schematics.
|
||||
const schematicNames = new Set(Object.keys(schematicsInCollection).sort());
|
||||
const [, schematicNameFromArgs] = this.parseSchematicInfo(
|
||||
// positional = [generate, component] or [generate]
|
||||
this.context.args.positional[1],
|
||||
);
|
||||
|
||||
if (schematicNameFromArgs && schematicNames.has(schematicNameFromArgs)) {
|
||||
// No need to process all schematics since we know which one the user invoked.
|
||||
schematicNames.clear();
|
||||
schematicNames.add(schematicNameFromArgs);
|
||||
}
|
||||
|
||||
for (const schematicName of schematicNames) {
|
||||
if (schematicsInCollection[schematicName].private) {
|
||||
continue;
|
||||
}
|
||||
for (const [schematicName, collectionName] of await this.getSchematicsToRegister()) {
|
||||
const workflow = this.getOrCreateWorkflowForBuilder(collectionName);
|
||||
const collection = workflow.engine.createCollection(collectionName);
|
||||
|
||||
const {
|
||||
description: {
|
||||
@ -110,8 +92,11 @@ export class GenerateCommandModule
|
||||
async run(options: Options<GenerateCommandArgs> & OtherOptions): Promise<number | void> {
|
||||
const { dryRun, schematic, defaults, force, interactive, ...schematicOptions } = options;
|
||||
|
||||
const [collectionName = await this.getCollectionName(), schematicName = ''] =
|
||||
this.parseSchematicInfo(schematic);
|
||||
const [collectionName, schematicName] = this.parseSchematicInfo(schematic);
|
||||
|
||||
if (!collectionName || !schematicName) {
|
||||
throw new CommandModuleError('A collection and schematic is required during execution.');
|
||||
}
|
||||
|
||||
return this.runSchematic({
|
||||
collectionName,
|
||||
@ -126,13 +111,13 @@ export class GenerateCommandModule
|
||||
});
|
||||
}
|
||||
|
||||
private async getCollectionName(): Promise<string> {
|
||||
const [collectionName = await this.getDefaultSchematicCollection()] = this.parseSchematicInfo(
|
||||
private async getCollectionNames(): Promise<string[]> {
|
||||
const [collectionName] = this.parseSchematicInfo(
|
||||
// positional = [generate, component] or [generate]
|
||||
this.context.args.positional[1],
|
||||
);
|
||||
|
||||
return collectionName;
|
||||
return collectionName ? [collectionName] : [...(await this.getSchematicCollections())];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -151,12 +136,15 @@ export class GenerateCommandModule
|
||||
);
|
||||
|
||||
const dasherizedSchematicName = strings.dasherize(schematicName);
|
||||
const schematicCollectionsFromConfig = await this.getSchematicCollections();
|
||||
const collectionNames = await this.getCollectionNames();
|
||||
|
||||
// Only add the collection name as part of the command when it's not the default collection or when it has been provided via the CLI.
|
||||
// Only add the collection name as part of the command when it's not a known
|
||||
// schematics collection or when it has been provided via the CLI.
|
||||
// Ex:`ng generate @schematics/angular:component`
|
||||
const commandName =
|
||||
!!collectionNameFromArgs ||
|
||||
(await this.getDefaultSchematicCollection()) !== (await this.getCollectionName())
|
||||
!collectionNames.some((c) => schematicCollectionsFromConfig.has(c))
|
||||
? collectionName + ':' + dasherizedSchematicName
|
||||
: dasherizedSchematicName;
|
||||
|
||||
@ -171,4 +159,54 @@ export class GenerateCommandModule
|
||||
|
||||
return `${commandName}${positionalArgs ? ' ' + positionalArgs : ''}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get schematics that can to be registered as subcommands.
|
||||
*/
|
||||
private async *getSchematics(): AsyncGenerator<{
|
||||
schematicName: string;
|
||||
collectionName: string;
|
||||
}> {
|
||||
const seenNames = new Set<string>();
|
||||
for (const collectionName of await this.getCollectionNames()) {
|
||||
const workflow = this.getOrCreateWorkflowForBuilder(collectionName);
|
||||
const collection = workflow.engine.createCollection(collectionName);
|
||||
|
||||
for (const schematicName of collection.listSchematicNames(true /** includeHidden */)) {
|
||||
// If a schematic with this same name is already registered skip.
|
||||
if (!seenNames.has(schematicName)) {
|
||||
seenNames.add(schematicName);
|
||||
yield { schematicName, collectionName };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get schematics that should to be registered as subcommands.
|
||||
*
|
||||
* @returns a sorted list of schematic that needs to be registered as subcommands.
|
||||
*/
|
||||
private async getSchematicsToRegister(): Promise<
|
||||
[schematicName: string, collectionName: string][]
|
||||
> {
|
||||
const schematicsToRegister: [schematicName: string, collectionName: string][] = [];
|
||||
const [, schematicNameFromArgs] = this.parseSchematicInfo(
|
||||
// positional = [generate, component] or [generate]
|
||||
this.context.args.positional[1],
|
||||
);
|
||||
|
||||
for await (const { schematicName, collectionName } of this.getSchematics()) {
|
||||
if (schematicName === schematicNameFromArgs) {
|
||||
return [[schematicName, collectionName]];
|
||||
}
|
||||
|
||||
schematicsToRegister.push([schematicName, collectionName]);
|
||||
}
|
||||
|
||||
// Didn't find the schematic or no schematic name was provided Ex: `ng generate --help`.
|
||||
return schematicsToRegister.sort(([nameA], [nameB]) =>
|
||||
nameA.localeCompare(nameB, undefined, { sensitivity: 'accent' }),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ import {
|
||||
OtherOptions,
|
||||
} from '../../command-builder/command-module';
|
||||
import {
|
||||
DEFAULT_SCHEMATICS_COLLECTION,
|
||||
SchematicsCommandArgs,
|
||||
SchematicsCommandModule,
|
||||
} from '../../command-builder/schematics-command-module';
|
||||
@ -51,7 +52,7 @@ export class NewCommandModule
|
||||
const collectionName =
|
||||
typeof collectionNameFromArgs === 'string'
|
||||
? collectionNameFromArgs
|
||||
: await this.getDefaultSchematicCollection();
|
||||
: await this.getCollectionFromConfig();
|
||||
|
||||
const workflow = await this.getOrCreateWorkflowForBuilder(collectionName);
|
||||
const collection = workflow.engine.createCollection(collectionName);
|
||||
@ -62,7 +63,7 @@ export class NewCommandModule
|
||||
|
||||
async run(options: Options<NewCommandArgs> & OtherOptions): Promise<number | void> {
|
||||
// Register the version of the CLI in the registry.
|
||||
const collectionName = options.collection ?? (await this.getDefaultSchematicCollection());
|
||||
const collectionName = options.collection ?? (await this.getCollectionFromConfig());
|
||||
const workflow = await this.getOrCreateWorkflowForExecution(collectionName, options);
|
||||
workflow.registry.addSmartDefaultProvider('ng-cli-version', () => VERSION.full);
|
||||
|
||||
@ -89,4 +90,19 @@ export class NewCommandModule
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/** Find a collection from config that has an `ng-new` schematic. */
|
||||
private async getCollectionFromConfig(): Promise<string> {
|
||||
for (const collectionName of await this.getSchematicCollections()) {
|
||||
const workflow = this.getOrCreateWorkflowForBuilder(collectionName);
|
||||
const collection = workflow.engine.createCollection(collectionName);
|
||||
const schematicsInCollection = collection.description.schematics;
|
||||
|
||||
if (Object.keys(schematicsInCollection).includes(this.schematicName)) {
|
||||
return collectionName;
|
||||
}
|
||||
}
|
||||
|
||||
return DEFAULT_SCHEMATICS_COLLECTION;
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,11 @@
|
||||
"version": "14.0.0",
|
||||
"factory": "./update-14/remove-default-project-option",
|
||||
"description": "Remove 'defaultProject' option from workspace configuration. The project to use will be determined from the current working directory."
|
||||
},
|
||||
"replace-default-collection-option": {
|
||||
"version": "14.0.0",
|
||||
"factory": "./update-14/replace-default-collection-option",
|
||||
"description": "Replace 'defaultCollection' option in workspace configuration with 'schematicCollections'."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,35 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import { JsonValue, isJsonObject } from '@angular-devkit/core';
|
||||
import { Rule } from '@angular-devkit/schematics';
|
||||
import { updateWorkspace } from '../../utility/workspace';
|
||||
|
||||
/** Migration to replace 'defaultCollection' option in angular.json. */
|
||||
export default function (): Rule {
|
||||
return updateWorkspace((workspace) => {
|
||||
// workspace level
|
||||
replaceDefaultCollection(workspace.extensions['cli']);
|
||||
|
||||
// Project level
|
||||
for (const project of workspace.projects.values()) {
|
||||
replaceDefaultCollection(project.extensions['cli']);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function replaceDefaultCollection(cliExtension: JsonValue | undefined): void {
|
||||
if (cliExtension && isJsonObject(cliExtension) && cliExtension['defaultCollection']) {
|
||||
// If `schematicsCollection` defined `defaultCollection` is ignored hence no need to warn.
|
||||
if (!cliExtension['schematicCollections']) {
|
||||
cliExtension['schematicCollections'] = [cliExtension['defaultCollection']];
|
||||
}
|
||||
|
||||
delete cliExtension['defaultCollection'];
|
||||
}
|
||||
}
|
@ -0,0 +1,101 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import { EmptyTree } from '@angular-devkit/schematics';
|
||||
import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing';
|
||||
import { ProjectType, WorkspaceSchema } from '../../utility/workspace-models';
|
||||
|
||||
describe(`Migration to replace 'defaultCollection' option.`, () => {
|
||||
const schematicName = 'replace-default-collection-option';
|
||||
const schematicRunner = new SchematicTestRunner(
|
||||
'migrations',
|
||||
require.resolve('../migration-collection.json'),
|
||||
);
|
||||
|
||||
let tree: UnitTestTree;
|
||||
beforeEach(() => {
|
||||
tree = new UnitTestTree(new EmptyTree());
|
||||
});
|
||||
|
||||
it(`should replace 'defaultCollection' with 'schematicCollections' at the root level`, async () => {
|
||||
const angularConfig: WorkspaceSchema = {
|
||||
version: 1,
|
||||
projects: {},
|
||||
cli: {
|
||||
defaultCollection: 'foo',
|
||||
},
|
||||
};
|
||||
|
||||
tree.create('/angular.json', JSON.stringify(angularConfig, undefined, 2));
|
||||
const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise();
|
||||
const { cli } = JSON.parse(newTree.readContent('/angular.json'));
|
||||
|
||||
expect(cli.defaultCollection).toBeUndefined();
|
||||
expect(cli.schematicCollections).toEqual(['foo']);
|
||||
});
|
||||
|
||||
it(`should not error when 'cli' is not defined`, async () => {
|
||||
const angularConfig: WorkspaceSchema = {
|
||||
version: 1,
|
||||
projects: {},
|
||||
};
|
||||
|
||||
tree.create('/angular.json', JSON.stringify(angularConfig, undefined, 2));
|
||||
const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise();
|
||||
const { cli } = JSON.parse(newTree.readContent('/angular.json'));
|
||||
|
||||
expect(cli).toBeUndefined();
|
||||
});
|
||||
|
||||
it(`should replace 'defaultCollection' with 'schematicCollections' at the project level`, async () => {
|
||||
const angularConfig: WorkspaceSchema = {
|
||||
version: 1,
|
||||
cli: {
|
||||
defaultCollection: 'foo',
|
||||
},
|
||||
projects: {
|
||||
test: {
|
||||
sourceRoot: '',
|
||||
root: '',
|
||||
prefix: '',
|
||||
projectType: ProjectType.Application,
|
||||
cli: {
|
||||
defaultCollection: 'bar',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
tree.create('/angular.json', JSON.stringify(angularConfig, undefined, 2));
|
||||
const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise();
|
||||
const {
|
||||
projects: { test },
|
||||
} = JSON.parse(newTree.readContent('/angular.json'));
|
||||
|
||||
expect(test.cli.defaultCollection).toBeUndefined();
|
||||
expect(test.cli.schematicCollections).toEqual(['bar']);
|
||||
});
|
||||
|
||||
it(`should not replace 'defaultCollection' with 'schematicCollections', when it is already defined`, async () => {
|
||||
const angularConfig: WorkspaceSchema = {
|
||||
version: 1,
|
||||
projects: {},
|
||||
cli: {
|
||||
defaultCollection: 'foo',
|
||||
schematicCollections: ['bar'],
|
||||
},
|
||||
};
|
||||
|
||||
tree.create('/angular.json', JSON.stringify(angularConfig, undefined, 2));
|
||||
const newTree = await schematicRunner.runSchematicAsync(schematicName, {}, tree).toPromise();
|
||||
const { cli } = JSON.parse(newTree.readContent('/angular.json'));
|
||||
|
||||
expect(cli.defaultCollection).toBeUndefined();
|
||||
expect(cli.schematicCollections).toEqual(['bar']);
|
||||
});
|
||||
});
|
@ -129,10 +129,15 @@ export type ServeBuilderTarget = BuilderTarget<Builders.DevServer, ServeBuilderO
|
||||
export type ExtractI18nBuilderTarget = BuilderTarget<Builders.ExtractI18n, ExtractI18nOptions>;
|
||||
export type E2EBuilderTarget = BuilderTarget<Builders.Protractor, E2EOptions>;
|
||||
|
||||
interface WorkspaceCLISchema {
|
||||
warnings?: Record<string, boolean>;
|
||||
schematicCollections?: string[];
|
||||
defaultCollection?: string;
|
||||
}
|
||||
export interface WorkspaceSchema {
|
||||
version: 1;
|
||||
defaultProject?: string;
|
||||
cli?: { warnings?: Record<string, boolean> };
|
||||
cli?: WorkspaceCLISchema;
|
||||
projects: {
|
||||
[key: string]: WorkspaceProject<ProjectType.Application | ProjectType.Library>;
|
||||
};
|
||||
@ -148,7 +153,7 @@ export interface WorkspaceProject<TProjectType extends ProjectType = ProjectType
|
||||
sourceRoot: string;
|
||||
prefix: string;
|
||||
|
||||
cli?: { warnings?: Record<string, boolean> };
|
||||
cli?: WorkspaceCLISchema;
|
||||
|
||||
/**
|
||||
* Tool options.
|
||||
|
@ -86,7 +86,7 @@ export default function () {
|
||||
.then(() =>
|
||||
updateJsonFile('angular.json', (json) => {
|
||||
json.cli = json.cli || ({} as any);
|
||||
json.cli.defaultCollection = 'fake-schematics';
|
||||
json.cli.schematicCollections = ['fake-schematics'];
|
||||
}),
|
||||
)
|
||||
.then(() => ng('generate', 'fake', '--help'))
|
||||
|
@ -0,0 +1,95 @@
|
||||
import { join } from 'path';
|
||||
import { ng } from '../../utils/process';
|
||||
import { writeMultipleFiles, createDir, expectFileToExist } from '../../utils/fs';
|
||||
import { updateJsonFile } from '../../utils/project';
|
||||
|
||||
export default async function () {
|
||||
// setup temp collection
|
||||
const genRoot = join('node_modules/fake-schematics/');
|
||||
const fakeComponentSchematicDesc = 'Fake component schematic';
|
||||
|
||||
await createDir(genRoot);
|
||||
await writeMultipleFiles({
|
||||
[join(genRoot, 'package.json')]: JSON.stringify({
|
||||
'schematics': './collection.json',
|
||||
}),
|
||||
[join(genRoot, 'collection.json')]: JSON.stringify({
|
||||
'schematics': {
|
||||
'fake': {
|
||||
'description': 'Fake schematic',
|
||||
'schema': './fake-schema.json',
|
||||
'factory': './fake',
|
||||
},
|
||||
'component': {
|
||||
'description': fakeComponentSchematicDesc,
|
||||
'schema': './fake-schema.json',
|
||||
'factory': './fake-component',
|
||||
},
|
||||
},
|
||||
}),
|
||||
[join(genRoot, 'fake-schema.json')]: JSON.stringify({
|
||||
'$id': 'FakeSchema',
|
||||
'title': 'Fake Schema',
|
||||
'type': 'object',
|
||||
}),
|
||||
[join(genRoot, 'fake.js')]: `
|
||||
exports.default = function (options) {
|
||||
return (host, context) => {
|
||||
console.log('fake schematic run.');
|
||||
};
|
||||
}
|
||||
`,
|
||||
[join(genRoot, 'fake-component.js')]: `
|
||||
exports.default = function (options) {
|
||||
return (host, context) => {
|
||||
console.log('fake component schematic run.');
|
||||
};
|
||||
}
|
||||
`,
|
||||
});
|
||||
|
||||
await updateJsonFile('angular.json', (json) => {
|
||||
json.cli ??= {};
|
||||
json.cli.schematicCollections = ['fake-schematics', '@schematics/angular'];
|
||||
});
|
||||
|
||||
// should display schematics for all schematics
|
||||
const { stdout: stdout1 } = await ng('generate', '--help');
|
||||
if (!stdout1.includes('ng generate component')) {
|
||||
throw new Error(`Didn't show schematics of '@schematics/angular'.`);
|
||||
}
|
||||
|
||||
if (!stdout1.includes('ng generate fake')) {
|
||||
throw new Error(`Didn't show schematics of 'fake-schematics'.`);
|
||||
}
|
||||
|
||||
// check registration order. Both schematics contain a component schematic verify that the first one wins.
|
||||
if (!stdout1.includes(fakeComponentSchematicDesc)) {
|
||||
throw new Error(`Didn't show fake component description.`);
|
||||
}
|
||||
|
||||
// Verify execution based on ordering
|
||||
const { stdout: stdout2 } = await ng('generate', 'component');
|
||||
if (!stdout2.includes('fake component schematic run')) {
|
||||
throw new Error(`stdout didn't contain 'fake component schematic run'.`);
|
||||
}
|
||||
|
||||
await updateJsonFile('angular.json', (json) => {
|
||||
json.cli ??= {};
|
||||
json.cli.schematicCollections = ['@schematics/angular', 'fake-schematics'];
|
||||
});
|
||||
|
||||
const { stdout: stdout3 } = await ng('generate', '--help');
|
||||
if (!stdout3.includes('ng generate component [name]')) {
|
||||
throw new Error(`Didn't show component description from @schematics/angular.`);
|
||||
}
|
||||
if (stdout3.includes(fakeComponentSchematicDesc)) {
|
||||
throw new Error(`Shown fake component description, when it shouldn't.`);
|
||||
}
|
||||
|
||||
// Verify execution based on ordering
|
||||
const projectDir = join('src', 'app');
|
||||
const componentDir = join(projectDir, 'test-component');
|
||||
await ng('generate', 'component', 'test-component');
|
||||
await expectFileToExist(componentDir);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user