diff --git a/docs/documentation/generate.md b/docs/documentation/generate.md
index f1c928ab9583..6dd875a1cee4 100644
--- a/docs/documentation/generate.md
+++ b/docs/documentation/generate.md
@@ -49,3 +49,13 @@
Adds more details to output logging.
+
+
+ collection
+
+ --collection
(aliases: -c
) default value: @schematics/angular
+
+
+ Schematics collection to use.
+
+
diff --git a/package-lock.json b/package-lock.json
index 55b2cd6829db..1d82e1dbb259 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "@angular/cli",
- "version": "1.3.0-rc.5",
+ "version": "1.4.0-beta.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -14,6 +14,22 @@
"typescript": "2.4.2"
}
},
+ "@angular-devkit/core": {
+ "version": "0.0.10",
+ "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-0.0.10.tgz",
+ "integrity": "sha512-B3oJ1/ALpTC/Lyp9xP0QXt3hwMjUvUFYAIdLAeGF54FVdIkj58IiG+m6s2vTn0FKIcR1jZbHvGTQhd+Oeowcag=="
+ },
+ "@angular-devkit/schematics": {
+ "version": "0.0.17",
+ "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-0.0.17.tgz",
+ "integrity": "sha512-maL79fRoorHfFhIp+1PTiHwyVsAfuhvH1WMuCYI9Y8CUaMpDoThssyJUvTnhryi0shCpARgKRVjXayBeOH4ePQ==",
+ "requires": {
+ "@angular-devkit/core": "0.0.10",
+ "@ngtools/json-schema": "1.1.0",
+ "minimist": "1.2.0",
+ "rxjs": "5.4.2"
+ }
+ },
"@angular/compiler": {
"version": "4.3.3",
"resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-4.3.3.tgz",
@@ -52,6 +68,16 @@
"tsickle": "0.21.6"
}
},
+ "@ngtools/json-schema": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@ngtools/json-schema/-/json-schema-1.1.0.tgz",
+ "integrity": "sha1-w6DFRNYjkqzCgTpCyKDcb1j4aSI="
+ },
+ "@schematics/angular": {
+ "version": "0.0.27",
+ "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-0.0.27.tgz",
+ "integrity": "sha512-vrGIEWBI/1b+m1I8aDEaTZ2fGPKKqdnvtrDlKx1x/EofLy3BngiTBR/9Hu9lWY55UE6ZJlF8af/agzeIbF+uSA=="
+ },
"@types/chalk": {
"version": "0.4.31",
"resolved": "https://registry.npmjs.org/@types/chalk/-/chalk-0.4.31.tgz",
diff --git a/package.json b/package.json
index a95b549a6aab..70fb5551b256 100644
--- a/package.json
+++ b/package.json
@@ -41,6 +41,8 @@
"homepage": "https://github.com/angular/angular-cli",
"dependencies": {
"@angular-devkit/build-optimizer": "0.0.13",
+ "@angular-devkit/schematics": "0.0.17",
+ "@schematics/angular": "0.0.27",
"autoprefixer": "^6.5.3",
"chalk": "^2.0.1",
"circular-dependency-plugin": "^3.0.0",
diff --git a/packages/@angular/cli/commands/generate.ts b/packages/@angular/cli/commands/generate.ts
index 1c8e95ab9966..9d44cedbbd70 100644
--- a/packages/@angular/cli/commands/generate.ts
+++ b/packages/@angular/cli/commands/generate.ts
@@ -1,28 +1,28 @@
-import * as chalk from 'chalk';
-import * as fs from 'fs';
-import * as os from 'os';
-import * as path from 'path';
+import { cyan, yellow } from 'chalk';
+const stringUtils = require('ember-cli-string-utils');
import { oneLine } from 'common-tags';
import { CliConfig } from '../models/config';
+import 'rxjs/add/observable/of';
+import 'rxjs/add/operator/ignoreElements';
+import {
+ getCollection,
+ getEngineHost
+} from '../utilities/schematics';
+import { DynamicPathOptions, dynamicPathParser } from '../utilities/dynamic-path-parser';
+import { getAppFromConfig } from '../utilities/app-utils';
+import * as path from 'path';
+import { SchematicAvailableOptions } from '../tasks/schematic-get-options';
+
const Command = require('../ember-cli/lib/models/command');
-const Blueprint = require('../ember-cli/lib/models/blueprint');
-const parseOptions = require('../ember-cli/lib/utilities/parse-options');
const SilentError = require('silent-error');
-function loadBlueprints(): Array {
- const blueprintList = fs.readdirSync(path.join(__dirname, '..', 'blueprints'));
- const blueprints = blueprintList
- .filter(bp => bp.indexOf('-test') === -1)
- .filter(bp => bp !== 'ng')
- .map(bp => Blueprint.load(path.join(__dirname, '..', 'blueprints', bp)));
+const separatorRegEx = /[\/\\]/g;
- return blueprints;
-}
export default Command.extend({
name: 'generate',
- description: 'Generates and/or modifies files based on a blueprint.',
+ description: 'Generates and/or modifies files based on a schematic.',
aliases: ['g'],
availableOptions: [
@@ -34,117 +34,145 @@ export default Command.extend({
description: 'Run through without making any changes.'
},
{
- name: 'lint-fix',
+ name: 'force',
type: Boolean,
- aliases: ['lf'],
- description: 'Use lint to fix files after generation.'
+ default: false,
+ aliases: ['f'],
+ description: 'Forces overwriting of files.'
},
{
- name: 'verbose',
+ name: 'app',
+ type: String,
+ aliases: ['a'],
+ description: 'Specifies app name to use.'
+ },
+ {
+ name: 'collection',
+ type: String,
+ aliases: ['c'],
+ description: 'Schematics collection to use.'
+ },
+ {
+ name: 'lint-fix',
type: Boolean,
- default: false,
- aliases: ['v'],
- description: 'Adds more details to output logging.'
+ aliases: ['lf'],
+ description: 'Use lint to fix files after generation.'
}
],
anonymousOptions: [
- ''
+ ''
],
- beforeRun: function (rawArgs: string[]) {
- if (!rawArgs.length) {
- return;
+ getCollectionName(rawArgs: string[]) {
+ let collectionName = CliConfig.getValue('defaults.schematics.collection');
+ if (rawArgs) {
+ const parsedArgs = this.parseArgs(rawArgs, false);
+ if (parsedArgs.options.collection) {
+ collectionName = parsedArgs.options.collection;
+ }
}
+ return collectionName;
+ },
+
+ beforeRun: function(rawArgs: string[]) {
const isHelp = ['--help', '-h'].includes(rawArgs[0]);
if (isHelp) {
return;
}
- this.blueprints = loadBlueprints();
-
- const name = rawArgs[0];
- const blueprint = this.blueprints.find((bp: any) => bp.name === name
- || (bp.aliases && bp.aliases.includes(name)));
-
- if (!blueprint) {
- SilentError.debugOrThrow('@angular/cli/commands/generate',
- `Invalid blueprint: ${name}`);
- }
-
- if (!rawArgs[1]) {
- SilentError.debugOrThrow('@angular/cli/commands/generate',
- `The \`ng generate ${name}\` command requires a name to be specified.`);
+ const schematicName = rawArgs[0];
+ if (!schematicName) {
+ return Promise.reject(new SilentError(oneLine`
+ The "ng generate" command requires a
+ schematic name to be specified.
+ For more details, use "ng help".
+ `));
}
if (/^\d/.test(rawArgs[1])) {
SilentError.debugOrThrow('@angular/cli/commands/generate',
- `The \`ng generate ${name} ${rawArgs[1]}\` file name cannot begin with a digit.`);
+ `The \`ng generate ${schematicName} ${rawArgs[1]}\` file name cannot begin with a digit.`);
}
- rawArgs[0] = blueprint.name;
- this.registerOptions(blueprint);
- },
+ const SchematicGetOptionsTask = require('../tasks/schematic-get-options').default;
- printDetailedHelp: function () {
- if (!this.blueprints) {
- this.blueprints = loadBlueprints();
- }
- this.ui.writeLine(chalk.cyan(' Available blueprints'));
- this.ui.writeLine(this.blueprints.map((bp: any) => bp.printBasicHelp(false)).join(os.EOL));
+ const getOptionsTask = new SchematicGetOptionsTask({
+ ui: this.ui,
+ project: this.project
+ });
+ const collectionName = this.getCollectionName(rawArgs);
+
+ return getOptionsTask.run({
+ schematicName,
+ collectionName
+ })
+ .then((availableOptions: SchematicAvailableOptions) => {
+ let anonymousOptions: string[] = [];
+ if (collectionName === '@schematics/angular' && schematicName === 'interface') {
+ anonymousOptions = [''];
+ }
+
+ this.registerOptions({
+ anonymousOptions: anonymousOptions,
+ availableOptions: availableOptions
+ });
+ });
},
run: function (commandOptions: any, rawArgs: string[]) {
- const name = rawArgs[0];
- if (!name) {
- return Promise.reject(new SilentError(oneLine`
- The "ng generate" command requires a
- blueprint name to be specified.
- For more details, use "ng help".
- `));
+ if (rawArgs[0] === 'module' && !rawArgs[1]) {
+ throw 'The `ng generate module` command requires a name to be specified.';
}
- const blueprint = this.blueprints.find((bp: any) => bp.name === name
- || (bp.aliases && bp.aliases.includes(name)));
-
- const projectName = CliConfig.getValue('project.name');
- const blueprintOptions = {
- target: this.project.root,
- entity: {
- name: rawArgs[1],
- options: parseOptions(rawArgs.slice(2))
- },
- projectName,
- ui: this.ui,
+ const entityName = rawArgs[1];
+ commandOptions.name = stringUtils.dasherize(entityName.split(separatorRegEx).pop());
+
+ const appConfig = getAppFromConfig(commandOptions.app);
+ const dynamicPathOptions: DynamicPathOptions = {
project: this.project,
- settings: this.settings,
- testing: this.testing,
- args: rawArgs,
- ...commandOptions
+ entityName: entityName,
+ appConfig: appConfig,
+ dryRun: commandOptions.dryRun
};
+ const parsedPath = dynamicPathParser(dynamicPathOptions);
+ commandOptions.sourceDir = appConfig.root;
+ commandOptions.path = parsedPath.dir
+ .replace(appConfig.root + path.sep, '')
+ .replace(separatorRegEx, '/');
- return blueprint.install(blueprintOptions)
- .then(() => {
- const lintFix = commandOptions.lintFix !== undefined ?
- commandOptions.lintFix : CliConfig.getValue('defaults.lintFix');
-
- if (lintFix && blueprint.modifiedFiles) {
- const LintTask = require('../tasks/lint').default;
- const lintTask = new LintTask({
- ui: this.ui,
- project: this.project
- });
-
- return lintTask.run({
- fix: true,
- force: true,
- silent: true,
- configs: [{
- files: blueprint.modifiedFiles.filter((file: string) => /.ts$/.test(file))
- }]
- });
- }
+ const cwd = this.project.root;
+ const schematicName = rawArgs[0];
+
+ const SchematicRunTask = require('../tasks/schematic-run').default;
+ const schematicRunTask = new SchematicRunTask({
+ ui: this.ui,
+ project: this.project
+ });
+ const collectionName = this.getCollectionName(rawArgs);
+
+ if (collectionName === '@schematics/angular' && schematicName === 'interface' && rawArgs[2]) {
+ commandOptions.type = rawArgs[2];
+ }
+
+ return schematicRunTask.run({
+ taskOptions: commandOptions,
+ workingDir: cwd,
+ collectionName,
+ schematicName
});
+ },
+
+ printDetailedHelp: function () {
+ const engineHost = getEngineHost();
+ const collectionName = this.getCollectionName();
+ const collection = getCollection(collectionName);
+ const schematicNames: string[] = engineHost.listSchematics(collection);
+ this.ui.writeLine(cyan('Available schematics:'));
+ schematicNames.forEach(schematicName => {
+ this.ui.writeLine(yellow(` ${schematicName}`));
+ });
+ this.ui.writeLine('');
}
});
diff --git a/packages/@angular/cli/commands/new.ts b/packages/@angular/cli/commands/new.ts
index e52c0dea1095..5a82368ea306 100644
--- a/packages/@angular/cli/commands/new.ts
+++ b/packages/@angular/cli/commands/new.ts
@@ -1,24 +1,17 @@
import * as fs from 'fs';
import * as path from 'path';
import * as chalk from 'chalk';
-import denodeify = require('denodeify');
import InitCommand from './init';
import { CliConfig } from '../models/config';
import { validateProjectName } from '../utilities/validate-project-name';
import { oneLine } from 'common-tags';
+import { SchematicAvailableOptions } from '../tasks/schematic-get-options';
const Command = require('../ember-cli/lib/models/command');
const Project = require('../ember-cli/lib/models/project');
const SilentError = require('silent-error');
-// There's some problem with the generic typings for fs.makedir.
-// Couldn't find matching types for the callbacks so leaving it as any for now.
-const mkdir = denodeify(fs.mkdir as any);
-
-const configFile = '.angular-cli.json';
-const changeLater = (path: string) => `You can later change the value in "${configFile}" (${path})`;
-
const NewCommand = Command.extend({
name: 'new',
aliases: ['n'],
@@ -65,13 +58,6 @@ const NewCommand = Command.extend({
aliases: ['sg'],
description: 'Skip initializing a git repository.'
},
- {
- name: 'skip-tests',
- type: Boolean,
- default: false,
- aliases: ['st'],
- description: 'Skip creating spec files.'
- },
{
name: 'skip-commit',
type: Boolean,
@@ -80,69 +66,59 @@ const NewCommand = Command.extend({
description: 'Skip committing the first commit to git.'
},
{
- name: 'directory',
- type: String,
- aliases: ['dir'],
- description: 'The directory name to create the app in.'
- },
- {
- name: 'source-dir',
- type: String,
- default: 'src',
- aliases: ['sd'],
- description: `The name of the source directory. ${changeLater('apps[0].root')}.`
- },
- {
- name: 'style',
- type: String,
- default: 'css',
- description: oneLine`The style file default extension.
- Possible values: css, scss, less, sass, styl(stylus).
- ${changeLater('defaults.styleExt')}.
- `
- },
- {
- name: 'prefix',
+ name: 'collection',
type: String,
- default: 'app',
- aliases: ['p'],
- description: oneLine`
- The prefix to use for all component selectors.
- ${changeLater('apps[0].prefix')}.
- `
- },
- {
- name: 'routing',
- type: Boolean,
- default: false,
- description: 'Generate a routing module.'
- },
- {
- name: 'inline-style',
- type: Boolean,
- default: false,
- aliases: ['is'],
- description: 'Should have an inline style.'
- },
- {
- name: 'inline-template',
- type: Boolean,
- default: false,
- aliases: ['it'],
- description: 'Should have an inline template.'
- },
- {
- name: 'minimal',
- type: Boolean,
- default: false,
- description: 'Should create a minimal app.'
- }
+ aliases: ['c'],
+ description: 'Schematics collection to use.'
+ }
],
isProject: function (projectPath: string) {
return CliConfig.fromProject(projectPath) !== null;
},
+ getCollectionName(rawArgs: string[]) {
+ let collectionName = CliConfig.fromGlobal().get('defaults.schematics.collection');
+ if (rawArgs) {
+ const parsedArgs = this.parseArgs(rawArgs, false);
+ if (parsedArgs.options.collection) {
+ collectionName = parsedArgs.options.collection;
+ }
+ }
+ return collectionName;
+ },
+
+ beforeRun: function (rawArgs: string[]) {
+ const isHelp = ['--help', '-h'].includes(rawArgs[0]);
+ if (isHelp) {
+ return;
+ }
+
+ const schematicName = CliConfig.getValue('defaults.schematics.newApp');
+
+ if (/^\d/.test(rawArgs[1])) {
+ SilentError.debugOrThrow('@angular/cli/commands/generate',
+ `The \`ng new ${rawArgs[0]}\` file name cannot begin with a digit.`);
+ }
+
+ const SchematicGetOptionsTask = require('../tasks/schematic-get-options').default;
+
+ const getOptionsTask = new SchematicGetOptionsTask({
+ ui: this.ui,
+ project: this.project
+ });
+
+ return getOptionsTask.run({
+ schematicName,
+ collectionName: this.getCollectionName(rawArgs)
+ })
+ .then((availableOptions: SchematicAvailableOptions) => {
+ this.registerOptions({
+ availableOptions: availableOptions
+ });
+ });
+ },
+
run: function (commandOptions: any, rawArgs: string[]) {
const packageName = rawArgs.shift();
@@ -159,8 +135,16 @@ const NewCommand = Command.extend({
commandOptions.skipGit = true;
}
- const directoryName = path.join(process.cwd(),
- commandOptions.directory ? commandOptions.directory : packageName);
+ commandOptions.directory = commandOptions.directory || packageName;
+ const directoryName = path.join(process.cwd(), commandOptions.directory);
+
+ if (fs.existsSync(directoryName) && this.isProject(directoryName)) {
+ throw new SilentError(oneLine`
+ Directory ${directoryName} exists and is already an Angular CLI project.
+ `);
+ }
+
+ commandOptions.collectionName = this.getCollectionName(rawArgs);
const initCommand = new InitCommand({
ui: this.ui,
@@ -168,34 +152,9 @@ const NewCommand = Command.extend({
project: Project.nullProject(this.ui, this.cli)
});
- let createDirectory;
- if (commandOptions.dryRun) {
- createDirectory = Promise.resolve()
- .then(() => {
- if (fs.existsSync(directoryName) && this.isProject(directoryName)) {
- throw new SilentError(oneLine`
- Directory ${directoryName} exists and is already an Angular CLI project.
- `);
- }
- });
- } else {
- createDirectory = mkdir(directoryName)
- .catch((err) => {
- if (err.code === 'EEXIST') {
- if (this.isProject(directoryName)) {
- throw new SilentError(oneLine`
- Directory ${directoryName} exists and is already an Angular CLI project.
- `);
- }
- } else {
- throw err;
- }
- })
- .then(() => process.chdir(directoryName));
- }
-
- return createDirectory
+ return Promise.resolve()
.then(initCommand.run.bind(initCommand, commandOptions, rawArgs));
+
}
});
diff --git a/packages/@angular/cli/ember-cli/lib/models/command.js b/packages/@angular/cli/ember-cli/lib/models/command.js
index eea7afb92e4a..5a62554e5b06 100644
--- a/packages/@angular/cli/ember-cli/lib/models/command.js
+++ b/packages/@angular/cli/ember-cli/lib/models/command.js
@@ -496,7 +496,7 @@ let Command = CoreObject.extend({
@param {Object} commandArgs
@return {Object|null}
*/
- parseArgs(commandArgs) {
+ parseArgs(commandArgs, showErrors = true) {
let knownOpts = {}; // Parse options
let commandOptions = {};
let parsedOptions;
@@ -507,7 +507,7 @@ let Command = CoreObject.extend({
let validateParsed = function(key) {
// ignore 'argv', 'h', and 'help'
- if (!commandOptions.hasOwnProperty(key) && key !== 'argv' && key !== 'h' && key !== 'help') {
+ if (!commandOptions.hasOwnProperty(key) && key !== 'argv' && key !== 'h' && key !== 'help' && showErrors) {
this.ui.writeLine(chalk.yellow(`The option '--${key}' is not registered with the ${this.name} command. ` +
`Run \`ng ${this.name} --help\` for a list of supported options.`));
}
diff --git a/packages/@angular/cli/lib/config/schema.json b/packages/@angular/cli/lib/config/schema.json
index 7a8b2c08e4c5..9a890308dbab 100644
--- a/packages/@angular/cli/lib/config/schema.json
+++ b/packages/@angular/cli/lib/config/schema.json
@@ -536,6 +536,23 @@
"type": "string"
}
}
+ },
+ "schematics": {
+ "description": "Properties about schematics.",
+ "type": "object",
+ "properties": {
+ "collection": {
+ "description": "The schematics collection to use.",
+ "type": "string",
+ "default": "@schematics/angular"
+ },
+ "newApp": {
+ "description": "The new app schematic.",
+ "type": "string",
+ "default": "application"
+ }
+ },
+ "additionalProperties": false
}
},
"additionalProperties": false
diff --git a/packages/@angular/cli/package.json b/packages/@angular/cli/package.json
index 5db8da720528..90a4e859e32e 100644
--- a/packages/@angular/cli/package.json
+++ b/packages/@angular/cli/package.json
@@ -28,8 +28,10 @@
"homepage": "https://github.com/angular/angular-cli",
"dependencies": {
"@angular-devkit/build-optimizer": "0.0.13",
+ "@angular-devkit/schematics": "0.0.17",
"@ngtools/json-schema": "1.1.0",
"@ngtools/webpack": "1.7.0-beta.0",
+ "@schematics/angular": "0.0.27",
"autoprefixer": "^6.5.3",
"chalk": "^2.0.1",
"circular-dependency-plugin": "^3.0.0",
diff --git a/packages/@angular/cli/tasks/init.ts b/packages/@angular/cli/tasks/init.ts
index e868d1040959..5eccb41c2e12 100644
--- a/packages/@angular/cli/tasks/init.ts
+++ b/packages/@angular/cli/tasks/init.ts
@@ -7,22 +7,16 @@ import {CliConfig} from '../models/config';
const Task = require('../ember-cli/lib/models/task');
const SilentError = require('silent-error');
-const normalizeBlueprint = require('../ember-cli/lib/utilities/normalize-blueprint-option');
const GitInit = require('../tasks/git-init');
-const InstallBlueprint = require('../ember-cli/lib/tasks/install-blueprint');
export default Task.extend({
+
run: function (commandOptions: any, rawArgs: string[]) {
if (commandOptions.dryRun) {
commandOptions.skipInstall = true;
}
- const installBlueprint = new InstallBlueprint({
- ui: this.ui,
- project: this.project
- });
-
// needs an explicit check in case it's just 'undefined'
// due to passing of options from 'new' and 'addon'
let gitInit: any;
@@ -64,46 +58,54 @@ export default Task.extend({
return Promise.reject(new SilentError(message));
}
- const blueprintOpts = {
- dryRun: commandOptions.dryRun,
- blueprint: 'ng',
- rawName: packageName,
- targetFiles: rawArgs || '',
- rawArgs: rawArgs.toString(),
- sourceDir: commandOptions.sourceDir,
- style: commandOptions.style,
- prefix: commandOptions.prefix.trim() || 'app',
- routing: commandOptions.routing,
- inlineStyle: commandOptions.inlineStyle,
- inlineTemplate: commandOptions.inlineTemplate,
- minimal: commandOptions.minimal,
- ignoredUpdateFiles: ['favicon.ico'],
- skipGit: commandOptions.skipGit,
- skipTests: commandOptions.skipTests
- };
-
validateProjectName(packageName);
- blueprintOpts.blueprint = normalizeBlueprint(blueprintOpts.blueprint);
+ const SchematicRunTask = require('../tasks/schematic-run').default;
+ const schematicRunTask = new SchematicRunTask({
+ ui: this.ui,
+ project: this.project
+ });
+
+ const cwd = this.project.root;
+ const schematicName = CliConfig.fromGlobal().get('defaults.schematics.newApp');
+
+ const runOptions = {
+ taskOptions: commandOptions,
+ workingDir: cwd,
+ collectionName: commandOptions.collectionName,
+ schematicName
+ };
- return installBlueprint.run(blueprintOpts)
+ return schematicRunTask.run(runOptions)
+ .then(function () {
+ if (!commandOptions.dryRun) {
+ process.chdir(commandOptions.directory);
+ }
+ })
.then(function () {
if (!commandOptions.skipInstall) {
return checkYarnOrCNPM().then(() => npmInstall.run());
}
})
.then(function () {
- if (commandOptions.skipGit === false) {
+ if (!commandOptions.dryRun && commandOptions.skipGit === false) {
return gitInit.run(commandOptions, rawArgs);
}
})
.then(function () {
- if (commandOptions.linkCli) {
+ if (!commandOptions.dryRun && commandOptions.skipInstall === false) {
+ return npmInstall.run();
+ }
+ })
+ .then(function () {
+ if (!commandOptions.dryRun && commandOptions.linkCli) {
return linkCli.run();
}
})
.then(() => {
- this.ui.writeLine(chalk.green(`Project '${packageName}' successfully created.`));
+ if (!commandOptions.dryRun) {
+ this.ui.writeLine(chalk.green(`Project '${packageName}' successfully created.`));
+ }
});
}
});
diff --git a/packages/@angular/cli/tasks/schematic-get-options.ts b/packages/@angular/cli/tasks/schematic-get-options.ts
new file mode 100644
index 000000000000..47ecf721d132
--- /dev/null
+++ b/packages/@angular/cli/tasks/schematic-get-options.ts
@@ -0,0 +1,59 @@
+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 {
+ collectionName: string;
+ schematicName: string;
+}
+
+export interface SchematicAvailableOptions {
+ name: string;
+ description: string;
+ aliases: string[];
+ type: any;
+}
+
+export default Task.extend({
+ run: function (options: SchematicGetOptions): Promise {
+ const collectionName = options.collectionName ||
+ CliConfig.getValue('defaults.schematics.collection');
+
+ const collection = getCollection(collectionName);
+
+ const schematic = getSchematic(collection, options.schematicName);
+
+ const properties = schematic.description.schemaJson.properties;
+ const keys = Object.keys(properties);
+ const availableOptions = keys
+ .map(key => ({...properties[key], ...{name: stringUtils.dasherize(key)}}))
+ .map(opt => {
+ let type;
+ switch (opt.type) {
+ case 'string':
+ type = String;
+ break;
+ case 'boolean':
+ type = Boolean;
+ break;
+ }
+ let aliases: string[] = [];
+ if (opt.alias) {
+ aliases = [...aliases, opt.alias];
+ }
+ if (opt.aliases) {
+ aliases = [...aliases, ...opt.aliases];
+ }
+
+ return {
+ ...opt,
+ aliases,
+ type,
+ default: undefined // do not carry over schematics defaults
+ };
+ });
+
+ return Promise.resolve(availableOptions);
+ }
+});
diff --git a/packages/@angular/cli/tasks/schematic-run.ts b/packages/@angular/cli/tasks/schematic-run.ts
new file mode 100644
index 000000000000..5c40c4de0154
--- /dev/null
+++ b/packages/@angular/cli/tasks/schematic-run.ts
@@ -0,0 +1,230 @@
+import {
+ DryRunEvent,
+ DryRunSink,
+ FileSystemSink,
+ FileSystemTree,
+ Schematic,
+ Tree
+} from '@angular-devkit/schematics';
+import { FileSystemHost } from '@angular-devkit/schematics/tools';
+import { Observable } from 'rxjs/Observable';
+import * as path from 'path';
+import { green, red, yellow } from 'chalk';
+import { CliConfig } from '../models/config';
+import 'rxjs/add/operator/concatMap';
+import 'rxjs/add/operator/map';
+import { getCollection, getSchematic } from '../utilities/schematics';
+import { getAppFromConfig } from '../utilities/app-utils';
+
+
+const Task = require('../ember-cli/lib/models/task');
+
+export interface SchematicRunOptions {
+ taskOptions: SchematicOptions;
+ workingDir: string;
+ collectionName: string;
+ schematicName: string;
+}
+
+export interface SchematicOptions {
+ dryRun: boolean;
+ force: boolean;
+ [key: string]: any;
+}
+
+export interface SchematicOutput {
+ modifiedFiles: string[];
+}
+
+interface OutputLogging {
+ color: (msg: string) => string;
+ keyword: string;
+ message: string;
+}
+
+export default Task.extend({
+ run: function (options: SchematicRunOptions): Promise {
+ const { taskOptions, workingDir, collectionName, schematicName } = options;
+
+ const ui = this.ui;
+
+ const collection = getCollection(collectionName);
+ const schematic = getSchematic(collection, schematicName);
+
+ let modifiedFiles: string[] = [];
+
+ let appConfig;
+ try {
+ appConfig = getAppFromConfig(taskOptions.app);
+ } catch (err) {}
+
+ const projectRoot = !!this.project ? this.project.root : workingDir;
+
+ const preppedOptions = prepOptions(schematic, taskOptions);
+ const opts = { ...taskOptions, ...preppedOptions };
+
+ const host = Observable.of(new FileSystemTree(new FileSystemHost(workingDir)));
+
+ const dryRunSink = new DryRunSink(workingDir, opts.force);
+ const fsSink = new FileSystemSink(workingDir, opts.force);
+
+ let error = false;
+ const loggingQueue: OutputLogging[] = [];
+
+ dryRunSink.reporter.subscribe((event: DryRunEvent) => {
+ const eventPath = event.path.startsWith('/') ? event.path.substr(1) : event.path;
+ switch (event.kind) {
+ case 'error':
+ const desc = event.description == 'alreadyExist' ? 'already exists' : 'does not exist.';
+ ui.writeLine(`error! ${eventPath} ${desc}.`);
+ error = true;
+ break;
+ case 'update':
+ loggingQueue.push({
+ color: yellow,
+ keyword: 'update',
+ message: `${eventPath} (${event.content.length} bytes)`
+ });
+ modifiedFiles = [...modifiedFiles, event.path];
+ break;
+ case 'create':
+ loggingQueue.push({
+ color: green,
+ keyword: 'create',
+ message: `${eventPath} (${event.content.length} bytes)`
+ });
+ modifiedFiles = [...modifiedFiles, event.path];
+ break;
+ case 'delete':
+ loggingQueue.push({
+ color: red,
+ keyword: 'remove',
+ message: `${eventPath}`
+ });
+ break;
+ case 'rename':
+ const eventToPath = event.to.startsWith('/') ? event.to.substr(1) : event.to;
+ loggingQueue.push({
+ color: yellow,
+ keyword: 'rename',
+ message: `${eventPath} => ${eventToPath}`
+ });
+ break;
+ }
+ });
+
+ return new Promise((resolve, reject) => {
+ schematic.call(opts, host)
+ .map((tree: Tree) => Tree.optimize(tree))
+ .concatMap((tree: Tree) => {
+ return dryRunSink.commit(tree).ignoreElements().concat(Observable.of(tree));
+ })
+ .concatMap((tree: Tree) => {
+ if (!error) {
+ // Output the logging queue.
+ loggingQueue.forEach(log => ui.writeLine(` ${log.color(log.keyword)} ${log.message}`));
+ }
+
+ if (opts.dryRun || error) {
+ return Observable.of(tree);
+ }
+ return fsSink.commit(tree).ignoreElements().concat(Observable.of(tree));
+ })
+ .subscribe({
+ error(err) {
+ ui.writeLine(red(`Error: ${err.message}`));
+ reject(err.message);
+ },
+ complete() {
+ if (opts.dryRun) {
+ ui.writeLine(yellow(`\nNOTE: Run with "dry run" no changes were made.`));
+ }
+ resolve({modifiedFiles});
+ }
+ });
+ })
+ .then((output: SchematicOutput) => {
+ const modifiedFiles = output.modifiedFiles;
+ const lintFix = taskOptions.lintFix !== undefined ?
+ taskOptions.lintFix : CliConfig.getValue('defaults.lintFix');
+
+ if (lintFix && modifiedFiles) {
+ const LintTask = require('./lint').default;
+ const lintTask = new LintTask({
+ ui: this.ui,
+ project: this.project
+ });
+
+ return lintTask.run({
+ fix: true,
+ force: true,
+ silent: true,
+ configs: [{
+ files: modifiedFiles
+ .filter((file: string) => /.ts$/.test(file))
+ .map((file: string) => path.join(projectRoot, file))
+ }]
+ });
+ }
+ });
+ }
+});
+
+function prepOptions(schematic: Schematic<{}, {}>, options: SchematicOptions): SchematicOptions {
+
+ const properties = (schematic.description).schemaJson.properties;
+ const keys = Object.keys(properties);
+
+ if (schematic.description.name === 'component') {
+ options.prefix = (options.prefix === 'false' || options.prefix === '')
+ ? '' : options.prefix;
+ }
+
+ let preppedOptions = {
+ ...options,
+ ...readDefaults(schematic.description.name, keys, options)
+ };
+ preppedOptions = {
+ ...preppedOptions,
+ ...normalizeOptions(schematic.description.name, keys, options)
+ };
+
+ return preppedOptions;
+}
+
+function readDefaults(schematicName: string, optionKeys: string[], options: any): any {
+ return optionKeys.reduce((acc: any, key) => {
+ acc[key] = options[key] !== undefined ? options[key] : readDefault(schematicName, key);
+ return acc;
+ }, {});
+}
+
+const viewEncapsulationMap: any = {
+ 'emulated': 'Emulated',
+ 'native': 'Native',
+ 'none': 'None'
+};
+
+const changeDetectionMap: any = {
+ 'default': 'Default',
+ 'onpush': 'OnPush'
+};
+
+function normalizeOptions(schematicName: string, optionKeys: string[], options: any): any {
+ return optionKeys.reduce((acc: any, key) => {
+
+ if (schematicName === 'application' || schematicName === 'component') {
+ if (key === 'viewEncapsulation' && options[key]) {
+ acc[key] = viewEncapsulationMap[options[key].toLowerCase()];
+ } else if (key === 'changeDetection' && options[key]) {
+ acc[key] = changeDetectionMap[options[key].toLowerCase()];
+ }
+ }
+ return acc;
+ }, {});
+}
+
+function readDefault(schematicName: String, key: string) {
+ const jsonPath = `defaults.${schematicName}.${key}`;
+ return CliConfig.getValue(jsonPath);
+}
diff --git a/packages/@angular/cli/utilities/schematics.ts b/packages/@angular/cli/utilities/schematics.ts
new file mode 100644
index 000000000000..32912b81c6cf
--- /dev/null
+++ b/packages/@angular/cli/utilities/schematics.ts
@@ -0,0 +1,53 @@
+/**
+ * Refer to the angular shematics library to let the dependency validator
+ * know it is used..
+ *
+ * require('@schematics/angular')
+ */
+
+import {
+ Collection,
+ Schematic,
+ SchematicEngine,
+} from '@angular-devkit/schematics';
+import {
+ FileSystemSchematicDesc,
+ NodeModulesEngineHost
+} from '@angular-devkit/schematics/tools';
+import { SchemaClassFactory } from '@ngtools/json-schema';
+import 'rxjs/add/operator/concatMap';
+import 'rxjs/add/operator/map';
+
+const SilentError = require('silent-error');
+
+export function getEngineHost() {
+ const engineHost = new NodeModulesEngineHost();
+ return engineHost;
+}
+
+export function getCollection(collectionName: string): Collection {
+ const engineHost = getEngineHost();
+ const engine = new SchematicEngine(engineHost);
+
+ // Add support for schemaJson.
+ engineHost.registerOptionsTransform((schematic: FileSystemSchematicDesc, options: any) => {
+ if (schematic.schema) {
+ const SchemaMetaClass = SchemaClassFactory(schematic.schemaJson!);
+ const schemaClass = new SchemaMetaClass(options);
+ return schemaClass.$$root();
+ }
+ return options;
+ });
+
+ const collection = engine.createCollection(collectionName);
+
+ if (collection === null) {
+ throw new SilentError(`Invalid collection (${collectionName}).`);
+ }
+ return collection;
+}
+
+export function getSchematic(collection: Collection,
+ schematicName: string): Schematic {
+ return collection.createSchematic(schematicName);
+}
diff --git a/tests/e2e/tests/generate/component/component-duplicate.ts b/tests/e2e/tests/generate/component/component-duplicate.ts
index 25de588cdeed..9099d487b084 100644
--- a/tests/e2e/tests/generate/component/component-duplicate.ts
+++ b/tests/e2e/tests/generate/component/component-duplicate.ts
@@ -1,4 +1,3 @@
-import * as path from 'path';
import { ng } from '../../../utils/process';
import { oneLine } from 'common-tags';
@@ -8,17 +7,17 @@ export default function () {
if (!output.stdout.match(/update src[\\|\/]app[\\|\/]app.module.ts/)) {
throw new Error(oneLine`
Expected to match
- "update src${path.sep}app${path.sep}app.module.ts"
- in ${output}.`);
+ "update src/app/app.module.ts"
+ in ${output.stdout}.`);
}
})
.then(() => ng('generate', 'component', 'test-component'))
.then((output) => {
- if (!output.stdout.match(/identical src[\\|\/]app[\\|\/]app.module.ts/)) {
+ if (!output.stdout.match(/error! src[\\|\/]app[\\|\/]test-component[\\|\/]test-component.component.ts already exists./)) {
throw new Error(oneLine`
Expected to match
- "identical src${path.sep}app${path.sep}app.module.ts"
- in ${output}.`);
+ "ERROR! src/app/test-component/test-component.ts"
+ in ${output.stdout}.`);
}
});
}
diff --git a/tests/e2e/tests/generate/module/module-import.ts b/tests/e2e/tests/generate/module/module-import.ts
index 2f2f2104fe9c..05ba3a8a1e3b 100644
--- a/tests/e2e/tests/generate/module/module-import.ts
+++ b/tests/e2e/tests/generate/module/module-import.ts
@@ -27,17 +27,19 @@ export default function () {
.then(() => expectFileToMatch(modulePath, /imports: \[(.|\s)*Test3Module(.|\s)*\]/m))
.then(() => ng('generate', 'module', 'test4', '--routing', '--module', 'app'))
- .then(() => expectFileToMatch(modulePath,
- /import { Test4RoutingModule } from '.\/test4\/test4-routing.module'/))
- .then(() => expectFileToMatch(modulePath, /imports: \[(.|\s)*Test4RoutingModule(.|\s)*\]/m))
+ .then(() => expectFileToMatch(modulePath, /imports: \[(.|\s)*Test4Module(.|\s)*\]/m))
+ .then(() => expectFileToMatch(join('src', 'app', 'test4', 'test4.module.ts'),
+ /import { Test4RoutingModule } from '.\/test4-routing.module'/))
+ .then(() => expectFileToMatch(join('src', 'app', 'test4', 'test4.module.ts'),
+ /imports: \[(.|\s)*Test4RoutingModule(.|\s)*\]/m))
.then(() => ng('generate', 'module', 'test5', '--module', 'sub'))
.then(() => expectFileToMatch(subModulePath,
- /import { Test5Module } from '.\/..\/test5\/test5.module'/))
+ /import { Test5Module } from '..\/test5\/test5.module'/))
.then(() => expectFileToMatch(subModulePath, /imports: \[(.|\s)*Test5Module(.|\s)*\]/m))
.then(() => ng('generate', 'module', 'test6', '--module', join('sub', 'deep'))
.then(() => expectFileToMatch(deepSubModulePath,
- /import { Test6Module } from '.\/..\/..\/test6\/test6.module'/))
+ /import { Test6Module } from '..\/..\/test6\/test6.module'/))
.then(() => expectFileToMatch(deepSubModulePath, /imports: \[(.|\s)*Test6Module(.|\s)*\]/m)));
}
diff --git a/yarn.lock b/yarn.lock
index 91dea9eb2c57..c248f212faa4 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2,15 +2,28 @@
# yarn lockfile v1
-"@angular-devkit/build-optimizer@0.0.3":
- version "0.0.3"
- resolved "https://registry.yarnpkg.com/@angular-devkit/build-optimizer/-/build-optimizer-0.0.3.tgz#092bdf732b79a779ce540f9bb99d6590dd971204"
+"@angular-devkit/build-optimizer@0.0.5":
+ version "0.0.5"
+ resolved "https://registry.yarnpkg.com/@angular-devkit/build-optimizer/-/build-optimizer-0.0.5.tgz#321b141126ce462843e4d13e9d5603877e044860"
dependencies:
loader-utils "^1.1.0"
magic-string "^0.19.1"
source-map "^0.5.6"
typescript "^2.3.3"
+"@angular-devkit/core@0.0.6", "@angular-devkit/core@^0.0.6":
+ version "0.0.6"
+ resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-0.0.6.tgz#caf25c0c7928196e244b5fe5124256fcef6bce7c"
+
+"@angular-devkit/schematics@0.0.9":
+ version "0.0.9"
+ resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-0.0.9.tgz#84668c0196648de7e88e1727b2e7defbd7962dfd"
+ dependencies:
+ "@angular-devkit/core" "0.0.6"
+ "@ngtools/json-schema" "^1.1.0"
+ minimist "^1.2.0"
+ rxjs "^5.4.2"
+
"@angular/compiler-cli@^4.0.0":
version "4.2.4"
resolved "https://registry.yarnpkg.com/@angular/compiler-cli/-/compiler-cli-4.2.4.tgz#cce941a28362fc1c042ab85890fcaab1e233dd57"
@@ -37,6 +50,16 @@
dependencies:
tsickle "^0.21.0"
+"@ngtools/json-schema@^1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@ngtools/json-schema/-/json-schema-1.1.0.tgz#c3a0c544d62392acc2813a42c8a0dc6f58f86922"
+
+"@schematics/angular@0.0.9":
+ version "0.0.9"
+ resolved "https://registry.yarnpkg.com/@schematics/angular/-/angular-0.0.9.tgz#c9ff31078af3079990e448ddd07b735ed3c1b4bd"
+ dependencies:
+ "@angular-devkit/schematics" "0.0.9"
+
"@types/chalk@^0.4.28":
version "0.4.31"
resolved "https://registry.yarnpkg.com/@types/chalk/-/chalk-0.4.31.tgz#a31d74241a6b1edbb973cf36d97a2896834a51f9"