Skip to content

Commit aa93830

Browse files
committed
feat(@angular/cli): specify multiple default collections
1 parent d5a5fe2 commit aa93830

File tree

7 files changed

+87
-26
lines changed

7 files changed

+87
-26
lines changed

packages/@angular/cli/commands/generate.ts

+20-11
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import 'rxjs/add/observable/of';
77
import 'rxjs/add/operator/ignoreElements';
88
import {
99
getCollection,
10+
getCollectionNameForSchematicName,
11+
getCollectionNames,
1012
getEngineHost
1113
} from '../utilities/schematics';
1214
import { DynamicPathOptions, dynamicPathParser } from '../utilities/dynamic-path-parser';
@@ -17,7 +19,7 @@ import { SchematicAvailableOptions } from '../tasks/schematic-get-options';
1719
const Command = require('../ember-cli/lib/models/command');
1820
const SilentError = require('silent-error');
1921

20-
const { cyan, yellow } = chalk;
22+
const { cyan, yellow, white } = chalk;
2123
const separatorRegEx = /[\/\\]/g;
2224

2325

@@ -65,8 +67,10 @@ export default Command.extend({
6567
'<schematic>'
6668
],
6769

68-
getCollectionName(rawArgs: string[]) {
69-
let collectionName = CliConfig.getValue('defaults.schematics.collection');
70+
getCollectionName(schematicName: string, rawArgs: string[]) {
71+
let collectionName = getCollectionNameForSchematicName(
72+
getCollectionNames(), schematicName);
73+
7074
if (rawArgs) {
7175
const parsedArgs = this.parseArgs(rawArgs, false);
7276
if (parsedArgs.options.collection) {
@@ -103,7 +107,7 @@ export default Command.extend({
103107
ui: this.ui,
104108
project: this.project
105109
});
106-
const collectionName = this.getCollectionName(rawArgs);
110+
const collectionName = this.getCollectionName(schematicName, rawArgs);
107111

108112
return getOptionsTask.run({
109113
schematicName,
@@ -172,7 +176,7 @@ export default Command.extend({
172176
project: this.project
173177
});
174178
const collectionName = commandOptions.collection ||
175-
CliConfig.getValue('defaults.schematics.collection');
179+
this.getCollectionName(schematicName);
176180

177181
if (collectionName === '@schematics/angular' && schematicName === 'interface' && rawArgs[2]) {
178182
commandOptions.type = rawArgs[2];
@@ -188,10 +192,9 @@ export default Command.extend({
188192

189193
printDetailedHelp: function (_options: any, rawArgs: any): string | Promise<string> {
190194
const engineHost = getEngineHost();
191-
const collectionName = this.getCollectionName();
192-
const collection = getCollection(collectionName);
193195
const schematicName = rawArgs[1];
194196
if (schematicName) {
197+
const collectionName = this.getCollectionName(schematicName);
195198
const SchematicGetHelpOutputTask = require('../tasks/schematic-get-help-output').default;
196199
const getHelpOutputTask = new SchematicGetHelpOutputTask({
197200
ui: this.ui,
@@ -204,16 +207,22 @@ export default Command.extend({
204207
})
205208
.then((output: string[]) => {
206209
return [
210+
yellow(collectionName),
207211
cyan(`ng generate ${schematicName} ${cyan('[name]')} ${cyan('<options...>')}`),
208212
...output
209213
].join('\n');
210214
});
211215
} else {
212-
const schematicNames: string[] = engineHost.listSchematics(collection);
213216
const output: string[] = [];
214-
output.push(cyan('Available schematics:'));
215-
schematicNames.forEach(schematicName => {
216-
output.push(yellow(` ${schematicName}`));
217+
output.push(cyan('Available collections & schematics:'));
218+
const collections = getCollectionNames()
219+
.map((collectionName: string) => getCollection(collectionName));
220+
collections.forEach((collection: any) => {
221+
output.push(yellow(`\n${collection.name}`));
222+
const schematicNames: string[] = engineHost.listSchematics(collection);
223+
schematicNames.forEach(schematicName => {
224+
output.push(white(` ${schematicName}`));
225+
});
217226
});
218227
return Promise.resolve(output.join('\n'));
219228
}

packages/@angular/cli/commands/new.ts

+14-6
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,12 @@ import { CliConfig } from '../models/config';
66
import { validateProjectName } from '../utilities/validate-project-name';
77
import { oneLine } from 'common-tags';
88
import { SchematicAvailableOptions } from '../tasks/schematic-get-options';
9+
import {
10+
getCollectionNameForSchematicName,
11+
getCollectionNames
12+
} from '../utilities/schematics';
913

10-
const { cyan } = chalk;
14+
const { cyan, yellow } = chalk;
1115

1216
const Command = require('../ember-cli/lib/models/command');
1317
const SilentError = require('silent-error');
@@ -70,8 +74,9 @@ const NewCommand = Command.extend({
7074
return CliConfig.fromProject(projectPath) !== null;
7175
},
7276

73-
getCollectionName(rawArgs: string[]) {
74-
let collectionName = CliConfig.fromGlobal().get('defaults.schematics.collection');
77+
getCollectionName(schematicName: string, rawArgs: string[]) {
78+
let collectionName = getCollectionNameForSchematicName(
79+
getCollectionNames(), schematicName);
7580
if (rawArgs) {
7681
const parsedArgs = this.parseArgs(rawArgs, false);
7782
if (parsedArgs.options.collection) {
@@ -88,6 +93,7 @@ const NewCommand = Command.extend({
8893
}
8994

9095
const schematicName = CliConfig.getValue('defaults.schematics.newApp');
96+
const collectionName = this.getCollectionName(schematicName, rawArgs);
9197

9298
if (/^\d/.test(rawArgs[1])) {
9399
SilentError.debugOrThrow('@angular/cli/commands/generate',
@@ -103,7 +109,7 @@ const NewCommand = Command.extend({
103109

104110
return getOptionsTask.run({
105111
schematicName,
106-
collectionName: this.getCollectionName(rawArgs)
112+
collectionName
107113
})
108114
.then((availableOptions: SchematicAvailableOptions) => {
109115
this.registerOptions({
@@ -137,10 +143,11 @@ const NewCommand = Command.extend({
137143
`);
138144
}
139145

146+
commandOptions.schematicName = CliConfig.fromGlobal().get('defaults.schematics.newApp');
140147
if (commandOptions.collection) {
141148
commandOptions.collectionName = commandOptions.collection;
142149
} else {
143-
commandOptions.collectionName = this.getCollectionName(rawArgs);
150+
commandOptions.collectionName = this.getCollectionName(commandOptions.schematicName, rawArgs);
144151
}
145152

146153
const InitTask = require('../tasks/init').default;
@@ -158,8 +165,8 @@ const NewCommand = Command.extend({
158165
},
159166

160167
printDetailedHelp: function (): string | Promise<string> {
161-
const collectionName = this.getCollectionName();
162168
const schematicName = CliConfig.getValue('defaults.schematics.newApp');
169+
const collectionName = this.getCollectionName(schematicName);
163170
const SchematicGetHelpOutputTask = require('../tasks/schematic-get-help-output').default;
164171
const getHelpOutputTask = new SchematicGetHelpOutputTask({
165172
ui: this.ui,
@@ -172,6 +179,7 @@ const NewCommand = Command.extend({
172179
})
173180
.then((output: string[]) => {
174181
const outputLines = [
182+
yellow(collectionName),
175183
cyan(`ng new ${cyan('[name]')} ${cyan('<options...>')}`),
176184
...output
177185
];

packages/@angular/cli/lib/config/schema.json

+9-1
Original file line numberDiff line numberDiff line change
@@ -565,10 +565,18 @@
565565
"type": "object",
566566
"properties": {
567567
"collection": {
568-
"description": "The schematics collection to use.",
568+
"description": "The base schematics collection to use.",
569569
"type": "string",
570570
"default": "@schematics/angular"
571571
},
572+
"collections": {
573+
"description": "The additional schematics collections to use.",
574+
"type": "array",
575+
"items": {
576+
"type": "string"
577+
},
578+
"default": []
579+
},
572580
"newApp": {
573581
"description": "The new app schematic.",
574582
"type": "string",

packages/@angular/cli/tasks/init.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -72,15 +72,14 @@ export default Task.extend({
7272
});
7373

7474
const cwd = this.project.root;
75-
const schematicName = CliConfig.fromGlobal().get('defaults.schematics.newApp');
7675
commandOptions.version = packageJson.version;
7776

7877
const runOptions = {
7978
taskOptions: commandOptions,
8079
workingDir: cwd,
8180
emptyHost: true,
8281
collectionName: commandOptions.collectionName,
83-
schematicName
82+
schematicName: commandOptions.schematicName
8483
};
8584

8685
return schematicRunTask.run(runOptions)

packages/@angular/cli/tasks/schematic-get-options.ts

+1-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
const Task = require('../ember-cli/lib/models/task');
22
const stringUtils = require('ember-cli-string-utils');
3-
import { CliConfig } from '../models/config';
43
import { getCollection, getSchematic } from '../utilities/schematics';
54

65
export interface SchematicGetOptions {
@@ -19,10 +18,7 @@ export interface SchematicAvailableOptions {
1918

2019
export default Task.extend({
2120
run: function (options: SchematicGetOptions): Promise<SchematicAvailableOptions[]> {
22-
const collectionName = options.collectionName ||
23-
CliConfig.getValue('defaults.schematics.collection');
24-
25-
const collection = getCollection(collectionName);
21+
const collection = getCollection(options.collectionName);
2622

2723
const schematic = getSchematic(collection, options.schematicName);
2824

packages/@angular/cli/utilities/schematics.ts

+23
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { SchemaClassFactory } from '@ngtools/json-schema';
2020
import 'rxjs/add/operator/concatMap';
2121
import 'rxjs/add/operator/map';
2222

23+
import { CliConfig } from '../models/config';
2324
const SilentError = require('silent-error');
2425

2526
const engineHost = new NodeModulesEngineHost();
@@ -61,3 +62,25 @@ export function getSchematic(collection: Collection<any, any>,
6162
schematicName: string): Schematic<any, any> {
6263
return collection.createSchematic(schematicName);
6364
}
65+
66+
export function getCollectionNames() {
67+
const collectionNames = [CliConfig.getValue('defaults.schematics.collection')];
68+
const additionalCollections = CliConfig.getValue('defaults.schematics.collections');
69+
if (additionalCollections && additionalCollections.length) {
70+
collectionNames.unshift(...additionalCollections);
71+
}
72+
return collectionNames;
73+
}
74+
75+
export function getCollectionNameForSchematicName(collectionNames: string[],
76+
schematicName: string): string {
77+
return collectionNames.filter((collectionName: string) => {
78+
let schematic;
79+
try {
80+
schematic = getSchematic(getCollection(collectionName), schematicName);
81+
} catch (e) {
82+
// it's OK, schematic doesn't exists in collection
83+
}
84+
return !!schematic;
85+
})[0];
86+
}

tests/acceptance/new.spec.ts

+19-1
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,23 @@ describe('Acceptance: ng new', function () {
1212
beforeEach((done) => {
1313
// Increase timeout for these tests only.
1414
originalTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL;
15-
jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000;
15+
jasmine.DEFAULT_TIMEOUT_INTERVAL = 15000;
1616

1717
spyOn(console, 'error');
1818
// symlink custom collections to node_modules, so we can use with ng new
1919
// it is a bit dirty, but bootstrap-local tricks won't work here
2020
fs.symlinkSync(`${process.cwd()}/tests/collections/@custom`, `./node_modules/@custom`, 'dir');
21+
fs.symlinkSync(`${process.cwd()}/tests/collections/@custom-other`, `./node_modules/@custom-other`, 'dir');
2122

2223
tmp.setup('./tmp')
2324
.then(() => process.chdir('./tmp'))
2425
.then(() => done());
2526
}, 10000);
2627

2728
afterEach((done) => {
29+
ng(['set', 'defaults.schematics.collections=null', '--global']);
2830
fs.unlinkSync(path.join(__dirname, '/../../node_modules/@custom'));
31+
fs.unlinkSync(path.join(__dirname, '/../../node_modules/@custom-other'));
2932
jasmine.DEFAULT_TIMEOUT_INTERVAL = originalTimeout;
3033
tmp.teardown('./tmp').then(() => done());
3134
});
@@ -184,4 +187,19 @@ describe('Acceptance: ng new', function () {
184187
})
185188
.then(done, done.fail);
186189
});
190+
191+
it('should use schematic from default collections', (done) => {
192+
return ng(['set', 'defaults.schematics.collections=["@custom/application"]', '--global'])
193+
.then(() => ng(['new', 'foo', '--skip-install', '--skip-git']))
194+
.then(() => expect(() => fs.readFileSync('emptyapp', 'utf8')).not.toThrow())
195+
.then(done, done.fail);
196+
});
197+
198+
it('should use schematic from first matching collection from default collections', (done) => {
199+
return ng(['set', 'defaults.schematics.collections=["@custom-other/application","@custom/application"]', '--global'])
200+
.then(() => ng(['new', 'foo', '--skip-install', '--skip-git']))
201+
.then(() => expect(() => fs.readFileSync('veryemptyapp', 'utf8')).not.toThrow())
202+
.then(done, done.fail);
203+
});
204+
187205
});

0 commit comments

Comments
 (0)