Skip to content

feat(@angular/cli): specify multiple default collections #8723

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 20 additions & 11 deletions packages/@angular/cli/commands/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import 'rxjs/add/observable/of';
import 'rxjs/add/operator/ignoreElements';
import {
getCollection,
getCollectionNameForSchematicName,
getCollectionNames,
getEngineHost
} from '../utilities/schematics';
import { DynamicPathOptions, dynamicPathParser } from '../utilities/dynamic-path-parser';
Expand All @@ -17,7 +19,7 @@ import { SchematicAvailableOptions } from '../tasks/schematic-get-options';
const Command = require('../ember-cli/lib/models/command');
const SilentError = require('silent-error');

const { cyan, yellow } = chalk;
const { cyan, yellow, white } = chalk;
const separatorRegEx = /[\/\\]/g;


Expand Down Expand Up @@ -65,8 +67,10 @@ export default Command.extend({
'<schematic>'
],

getCollectionName(rawArgs: string[]) {
let collectionName = CliConfig.getValue('defaults.schematics.collection');
getCollectionName(schematicName: string, rawArgs: string[]) {
let collectionName = getCollectionNameForSchematicName(
getCollectionNames(), schematicName);

if (rawArgs) {
const parsedArgs = this.parseArgs(rawArgs, false);
if (parsedArgs.options.collection) {
Expand Down Expand Up @@ -103,7 +107,7 @@ export default Command.extend({
ui: this.ui,
project: this.project
});
const collectionName = this.getCollectionName(rawArgs);
const collectionName = this.getCollectionName(schematicName, rawArgs);

return getOptionsTask.run({
schematicName,
Expand Down Expand Up @@ -172,7 +176,7 @@ export default Command.extend({
project: this.project
});
const collectionName = commandOptions.collection ||
CliConfig.getValue('defaults.schematics.collection');
this.getCollectionName(schematicName);

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

printDetailedHelp: function (_options: any, rawArgs: any): string | Promise<string> {
const engineHost = getEngineHost();
const collectionName = this.getCollectionName();
const collection = getCollection(collectionName);
const schematicName = rawArgs[1];
if (schematicName) {
const collectionName = this.getCollectionName(schematicName);
const SchematicGetHelpOutputTask = require('../tasks/schematic-get-help-output').default;
const getHelpOutputTask = new SchematicGetHelpOutputTask({
ui: this.ui,
Expand All @@ -204,16 +207,22 @@ export default Command.extend({
})
.then((output: string[]) => {
return [
yellow(collectionName),
cyan(`ng generate ${schematicName} ${cyan('[name]')} ${cyan('<options...>')}`),
...output
].join('\n');
});
} else {
const schematicNames: string[] = engineHost.listSchematics(collection);
const output: string[] = [];
output.push(cyan('Available schematics:'));
schematicNames.forEach(schematicName => {
output.push(yellow(` ${schematicName}`));
output.push(cyan('Available collections & schematics:'));
const collections = getCollectionNames()
.map((collectionName: string) => getCollection(collectionName));
collections.forEach((collection: any) => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

List schematics per collection

output.push(yellow(`\n${collection.name}`));
const schematicNames: string[] = engineHost.listSchematics(collection);
schematicNames.forEach(schematicName => {
output.push(white(` ${schematicName}`));
});
});
return Promise.resolve(output.join('\n'));
}
Expand Down
20 changes: 14 additions & 6 deletions packages/@angular/cli/commands/new.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,12 @@ import { CliConfig } from '../models/config';
import { validateProjectName } from '../utilities/validate-project-name';
import { oneLine } from 'common-tags';
import { SchematicAvailableOptions } from '../tasks/schematic-get-options';
import {
getCollectionNameForSchematicName,
getCollectionNames
} from '../utilities/schematics';

const { cyan } = chalk;
const { cyan, yellow } = chalk;

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

getCollectionName(rawArgs: string[]) {
let collectionName = CliConfig.fromGlobal().get('defaults.schematics.collection');
getCollectionName(schematicName: string, rawArgs: string[]) {
let collectionName = getCollectionNameForSchematicName(
getCollectionNames(), schematicName);
if (rawArgs) {
const parsedArgs = this.parseArgs(rawArgs, false);
if (parsedArgs.options.collection) {
Expand All @@ -88,6 +93,7 @@ const NewCommand = Command.extend({
}

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

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

return getOptionsTask.run({
schematicName,
collectionName: this.getCollectionName(rawArgs)
collectionName
})
.then((availableOptions: SchematicAvailableOptions) => {
this.registerOptions({
Expand Down Expand Up @@ -137,10 +143,11 @@ const NewCommand = Command.extend({
`);
}

commandOptions.schematicName = CliConfig.fromGlobal().get('defaults.schematics.newApp');
if (commandOptions.collection) {
commandOptions.collectionName = commandOptions.collection;
} else {
commandOptions.collectionName = this.getCollectionName(rawArgs);
commandOptions.collectionName = this.getCollectionName(commandOptions.schematicName, rawArgs);
}

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

printDetailedHelp: function (): string | Promise<string> {
const collectionName = this.getCollectionName();
const schematicName = CliConfig.getValue('defaults.schematics.newApp');
const collectionName = this.getCollectionName(schematicName);
const SchematicGetHelpOutputTask = require('../tasks/schematic-get-help-output').default;
const getHelpOutputTask = new SchematicGetHelpOutputTask({
ui: this.ui,
Expand All @@ -172,6 +179,7 @@ const NewCommand = Command.extend({
})
.then((output: string[]) => {
const outputLines = [
yellow(collectionName),
cyan(`ng new ${cyan('[name]')} ${cyan('<options...>')}`),
...output
];
Expand Down
10 changes: 9 additions & 1 deletion packages/@angular/cli/lib/config/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -565,10 +565,18 @@
"type": "object",
"properties": {
"collection": {
"description": "The schematics collection to use.",
"description": "The base schematics collection to use.",
"type": "string",
"default": "@schematics/angular"
},
"collections": {
"description": "The additional schematics collections to use.",
"type": "array",
"items": {
"type": "string"
},
"default": []
},
"newApp": {
"description": "The new app schematic.",
"type": "string",
Expand Down
3 changes: 1 addition & 2 deletions packages/@angular/cli/tasks/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,15 +72,14 @@ export default Task.extend({
});

const cwd = this.project.root;
const schematicName = CliConfig.fromGlobal().get('defaults.schematics.newApp');
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to retrieve config value from multiple places. Now it's always responsibility of the caller.

commandOptions.version = packageJson.version;

const runOptions = {
taskOptions: commandOptions,
workingDir: cwd,
emptyHost: true,
collectionName: commandOptions.collectionName,
schematicName
schematicName: commandOptions.schematicName
};

return schematicRunTask.run(runOptions)
Expand Down
6 changes: 1 addition & 5 deletions packages/@angular/cli/tasks/schematic-get-options.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
const Task = require('../ember-cli/lib/models/task');
const stringUtils = require('ember-cli-string-utils');
import { CliConfig } from '../models/config';
import { getCollection, getSchematic } from '../utilities/schematics';

export interface SchematicGetOptions {
Expand All @@ -19,10 +18,7 @@ export interface SchematicAvailableOptions {

export default Task.extend({
run: function (options: SchematicGetOptions): Promise<SchematicAvailableOptions[]> {
const collectionName = options.collectionName ||
CliConfig.getValue('defaults.schematics.collection');
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to retrieve config value from multiple places. Now it's always responsibility of the caller.


const collection = getCollection(collectionName);
const collection = getCollection(options.collectionName);

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

Expand Down
23 changes: 23 additions & 0 deletions packages/@angular/cli/utilities/schematics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { SchemaClassFactory } from '@ngtools/json-schema';
import 'rxjs/add/operator/concatMap';
import 'rxjs/add/operator/map';

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

const engineHost = new NodeModulesEngineHost();
Expand Down Expand Up @@ -61,3 +62,25 @@ export function getSchematic(collection: Collection<any, any>,
schematicName: string): Schematic<any, any> {
return collection.createSchematic(schematicName);
}

export function getCollectionNames() {
const collectionNames = [CliConfig.getValue('defaults.schematics.collection')];
const additionalCollections = CliConfig.getValue('defaults.schematics.collections');
if (additionalCollections && additionalCollections.length) {
collectionNames.unshift(...additionalCollections);
}
return collectionNames;
}

export function getCollectionNameForSchematicName(collectionNames: string[],
schematicName: string): string {
return collectionNames.filter((collectionName: string) => {
let schematic;
try {
schematic = getSchematic(getCollection(collectionName), schematicName);
} catch (e) {
// it's OK, schematic doesn't exists in collection
}
return !!schematic;
})[0];
}
20 changes: 19 additions & 1 deletion tests/acceptance/new.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,23 @@ describe('Acceptance: ng new', function () {
beforeEach((done) => {
// Increase timeout for these tests only.
originalTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL;
jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000;
jasmine.DEFAULT_TIMEOUT_INTERVAL = 15000;

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

tmp.setup('./tmp')
.then(() => process.chdir('./tmp'))
.then(() => done());
}, 10000);

afterEach((done) => {
ng(['set', 'defaults.schematics.collections=null', '--global']);
fs.unlinkSync(path.join(__dirname, '/../../node_modules/@custom'));
fs.unlinkSync(path.join(__dirname, '/../../node_modules/@custom-other'));
jasmine.DEFAULT_TIMEOUT_INTERVAL = originalTimeout;
tmp.teardown('./tmp').then(() => done());
});
Expand Down Expand Up @@ -184,4 +187,19 @@ describe('Acceptance: ng new', function () {
})
.then(done, done.fail);
});

it('should use schematic from default collections', (done) => {
return ng(['set', 'defaults.schematics.collections=["@custom/application"]', '--global'])
.then(() => ng(['new', 'foo', '--skip-install', '--skip-git']))
.then(() => expect(() => fs.readFileSync('emptyapp', 'utf8')).not.toThrow())
.then(done, done.fail);
});

it('should use schematic from first matching collection from default collections', (done) => {
return ng(['set', 'defaults.schematics.collections=["@custom-other/application","@custom/application"]', '--global'])
.then(() => ng(['new', 'foo', '--skip-install', '--skip-git']))
.then(() => expect(() => fs.readFileSync('veryemptyapp', 'utf8')).not.toThrow())
.then(done, done.fail);
});

});
11 changes: 11 additions & 0 deletions tests/collections/@custom-other/application/collection.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"name": "@custom-other/application",
"version": "0.1",
"schematics": {
"application": {
"factory": "./index.js",
"schema": "./schema.json",
"description": "Create an very empty application"
}
}
}
Empty file.
6 changes: 6 additions & 0 deletions tests/collections/@custom-other/application/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
const s = require('@angular-devkit/schematics');

exports.default = function(options) {
return s.chain([s.mergeWith(s.apply(
s.url('./files'), [s.template({}), s.move(options.name)]))]);
};
4 changes: 4 additions & 0 deletions tests/collections/@custom-other/application/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"name": "very-empty-app",
"schematics": "./collection.json"
}
13 changes: 13 additions & 0 deletions tests/collections/@custom-other/application/schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"$schema": "http://json-schema.org/schema",
"id": "VeryEmptyApp",
"title": "Angular Bazel Workspace Options Schema",
"type": "object",
"properties": {
"name": {
"type": "string"
}
},
"required": [
]
}