From 84b4becd553bbf2a12e3101c00caa2788e1d6dd2 Mon Sep 17 00:00:00 2001 From: tomastrajan Date: Sun, 3 Dec 2017 00:37:33 +1100 Subject: [PATCH] feat(@angular/cli): specify multiple default collections --- packages/@angular/cli/commands/generate.ts | 31 ++++++++++++------- packages/@angular/cli/commands/new.ts | 20 ++++++++---- packages/@angular/cli/lib/config/schema.json | 10 +++++- packages/@angular/cli/tasks/init.ts | 3 +- .../cli/tasks/schematic-get-options.ts | 6 +--- packages/@angular/cli/utilities/schematics.ts | 23 ++++++++++++++ tests/acceptance/new.spec.ts | 20 +++++++++++- .../@custom-other/application/collection.json | 11 +++++++ .../application/files/veryemptyapp | 0 .../@custom-other/application/index.js | 6 ++++ .../@custom-other/application/package.json | 4 +++ .../@custom-other/application/schema.json | 13 ++++++++ 12 files changed, 121 insertions(+), 26 deletions(-) create mode 100644 tests/collections/@custom-other/application/collection.json create mode 100644 tests/collections/@custom-other/application/files/veryemptyapp create mode 100644 tests/collections/@custom-other/application/index.js create mode 100644 tests/collections/@custom-other/application/package.json create mode 100644 tests/collections/@custom-other/application/schema.json diff --git a/packages/@angular/cli/commands/generate.ts b/packages/@angular/cli/commands/generate.ts index e9a4d79b3585..9e7cbef55045 100644 --- a/packages/@angular/cli/commands/generate.ts +++ b/packages/@angular/cli/commands/generate.ts @@ -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'; @@ -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; @@ -65,8 +67,10 @@ export default Command.extend({ '' ], - 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) { @@ -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, @@ -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]; @@ -188,10 +192,9 @@ export default Command.extend({ printDetailedHelp: function (_options: any, rawArgs: any): string | Promise { 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, @@ -204,16 +207,22 @@ export default Command.extend({ }) .then((output: string[]) => { return [ + yellow(collectionName), cyan(`ng generate ${schematicName} ${cyan('[name]')} ${cyan('')}`), ...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) => { + 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')); } diff --git a/packages/@angular/cli/commands/new.ts b/packages/@angular/cli/commands/new.ts index aea3b7e82ab9..d5536ac56b7b 100644 --- a/packages/@angular/cli/commands/new.ts +++ b/packages/@angular/cli/commands/new.ts @@ -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'); @@ -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) { @@ -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', @@ -103,7 +109,7 @@ const NewCommand = Command.extend({ return getOptionsTask.run({ schematicName, - collectionName: this.getCollectionName(rawArgs) + collectionName }) .then((availableOptions: SchematicAvailableOptions) => { this.registerOptions({ @@ -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; @@ -158,8 +165,8 @@ const NewCommand = Command.extend({ }, printDetailedHelp: function (): string | Promise { - 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, @@ -172,6 +179,7 @@ const NewCommand = Command.extend({ }) .then((output: string[]) => { const outputLines = [ + yellow(collectionName), cyan(`ng new ${cyan('[name]')} ${cyan('')}`), ...output ]; diff --git a/packages/@angular/cli/lib/config/schema.json b/packages/@angular/cli/lib/config/schema.json index ed76e185da45..58ffdcd75bc3 100644 --- a/packages/@angular/cli/lib/config/schema.json +++ b/packages/@angular/cli/lib/config/schema.json @@ -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", diff --git a/packages/@angular/cli/tasks/init.ts b/packages/@angular/cli/tasks/init.ts index edaf9b6b3e5b..a88ce82f9222 100644 --- a/packages/@angular/cli/tasks/init.ts +++ b/packages/@angular/cli/tasks/init.ts @@ -72,7 +72,6 @@ export default Task.extend({ }); const cwd = this.project.root; - const schematicName = CliConfig.fromGlobal().get('defaults.schematics.newApp'); commandOptions.version = packageJson.version; const runOptions = { @@ -80,7 +79,7 @@ export default Task.extend({ workingDir: cwd, emptyHost: true, collectionName: commandOptions.collectionName, - schematicName + schematicName: commandOptions.schematicName }; return schematicRunTask.run(runOptions) diff --git a/packages/@angular/cli/tasks/schematic-get-options.ts b/packages/@angular/cli/tasks/schematic-get-options.ts index 77c300150b11..c5a49512d9eb 100644 --- a/packages/@angular/cli/tasks/schematic-get-options.ts +++ b/packages/@angular/cli/tasks/schematic-get-options.ts @@ -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 { @@ -19,10 +18,7 @@ export interface SchematicAvailableOptions { export default Task.extend({ run: function (options: SchematicGetOptions): Promise { - const collectionName = options.collectionName || - CliConfig.getValue('defaults.schematics.collection'); - - const collection = getCollection(collectionName); + const collection = getCollection(options.collectionName); const schematic = getSchematic(collection, options.schematicName); diff --git a/packages/@angular/cli/utilities/schematics.ts b/packages/@angular/cli/utilities/schematics.ts index f94067a8e42c..5b3c9766fa9c 100644 --- a/packages/@angular/cli/utilities/schematics.ts +++ b/packages/@angular/cli/utilities/schematics.ts @@ -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(); @@ -61,3 +62,25 @@ export function getSchematic(collection: Collection, schematicName: string): Schematic { 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]; +} diff --git a/tests/acceptance/new.spec.ts b/tests/acceptance/new.spec.ts index 3c70b92a68d4..3c85373f1c41 100644 --- a/tests/acceptance/new.spec.ts +++ b/tests/acceptance/new.spec.ts @@ -12,12 +12,13 @@ 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')) @@ -25,7 +26,9 @@ describe('Acceptance: ng new', function () { }, 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()); }); @@ -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); + }); + }); diff --git a/tests/collections/@custom-other/application/collection.json b/tests/collections/@custom-other/application/collection.json new file mode 100644 index 000000000000..d4e81499f3bd --- /dev/null +++ b/tests/collections/@custom-other/application/collection.json @@ -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" + } + } +} diff --git a/tests/collections/@custom-other/application/files/veryemptyapp b/tests/collections/@custom-other/application/files/veryemptyapp new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/collections/@custom-other/application/index.js b/tests/collections/@custom-other/application/index.js new file mode 100644 index 000000000000..db354934efd0 --- /dev/null +++ b/tests/collections/@custom-other/application/index.js @@ -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)]))]); +}; diff --git a/tests/collections/@custom-other/application/package.json b/tests/collections/@custom-other/application/package.json new file mode 100644 index 000000000000..a4050fa6a80c --- /dev/null +++ b/tests/collections/@custom-other/application/package.json @@ -0,0 +1,4 @@ +{ + "name": "very-empty-app", + "schematics": "./collection.json" +} diff --git a/tests/collections/@custom-other/application/schema.json b/tests/collections/@custom-other/application/schema.json new file mode 100644 index 000000000000..c92ff4c8865e --- /dev/null +++ b/tests/collections/@custom-other/application/schema.json @@ -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": [ + ] +}