Skip to content

Commit 912be39

Browse files
committed
feat(@angular/cli): Update generate & new to use schematics
This feature is related to angular#6593
1 parent 2d7a6f2 commit 912be39

File tree

11 files changed

+411
-173
lines changed

11 files changed

+411
-173
lines changed

package.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,10 @@
4040
},
4141
"homepage": "https://github.com/angular/angular-cli",
4242
"dependencies": {
43-
"@angular-devkit/build-optimizer": "0.0.3",
43+
"@angular-devkit/build-optimizer": "0.0.5",
44+
"@angular-devkit/core": "^0.0.6",
45+
"@angular-devkit/schematics": "0.0.9",
46+
"@schematics/angular": "0.0.9",
4447
"autoprefixer": "^6.5.3",
4548
"chalk": "^2.0.1",
4649
"circular-dependency-plugin": "^3.0.0",
+118-100
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,54 @@
1-
import * as chalk from 'chalk';
2-
import * as fs from 'fs';
3-
import * as os from 'os';
4-
import * as path from 'path';
1+
import { cyan, yellow } from 'chalk';
2+
const stringUtils = require('ember-cli-string-utils');
53
import { oneLine } from 'common-tags';
64
import { CliConfig } from '../models/config';
75

6+
import 'rxjs/add/observable/of';
7+
import 'rxjs/add/operator/ignoreElements';
8+
import { getCollection, getEngineHost, getSchematic, runSchematic } from '../utilities/schematics';
9+
import { DynamicPathOptions, dynamicPathParser } from '../utilities/dynamic-path-parser';
10+
import { getAppFromConfig } from '../utilities/app-utils';
11+
import * as path from 'path';
12+
813
const Command = require('../ember-cli/lib/models/command');
9-
const Blueprint = require('../ember-cli/lib/models/blueprint');
10-
const parseOptions = require('../ember-cli/lib/utilities/parse-options');
1114
const SilentError = require('silent-error');
1215

13-
function loadBlueprints(): Array<any> {
14-
const blueprintList = fs.readdirSync(path.join(__dirname, '..', 'blueprints'));
15-
const blueprints = blueprintList
16-
.filter(bp => bp.indexOf('-test') === -1)
17-
.filter(bp => bp !== 'ng')
18-
.map(bp => Blueprint.load(path.join(__dirname, '..', 'blueprints', bp)));
19-
20-
return blueprints;
16+
function mapSchematicOptions(schematic: any): any[] {
17+
const properties = schematic.description.schemaJson.properties;
18+
const keys = Object.keys(properties);
19+
const options = keys
20+
.map(key => ({...properties[key], ...{name: stringUtils.dasherize(key)}}))
21+
.map(opt => {
22+
let type;
23+
switch (opt.type) {
24+
case 'string':
25+
type = String;
26+
break;
27+
case 'boolean':
28+
type = Boolean;
29+
break;
30+
}
31+
let aliases: string[] = [];
32+
if (opt.alias) {
33+
aliases = [...aliases, opt.alias];
34+
}
35+
if (opt.aliases) {
36+
aliases = [...aliases, ...opt.aliases];
37+
}
38+
39+
return {
40+
...opt,
41+
aliases,
42+
type
43+
};
44+
});
45+
46+
return options;
2147
}
2248

2349
export default Command.extend({
2450
name: 'generate',
25-
description: 'Generates and/or modifies files based on a blueprint.',
51+
description: 'Generates and/or modifies files based on a schematic.',
2652
aliases: ['g'],
2753

2854
availableOptions: [
@@ -34,117 +60,109 @@ export default Command.extend({
3460
description: 'Run through without making any changes.'
3561
},
3662
{
37-
name: 'lint-fix',
63+
name: 'force',
3864
type: Boolean,
39-
aliases: ['lf'],
40-
description: 'Use lint to fix files after generation.'
65+
default: false,
66+
aliases: ['f'],
67+
description: 'Forces overwriting of files.'
4168
},
4269
{
43-
name: 'verbose',
44-
type: Boolean,
45-
default: false,
46-
aliases: ['v'],
47-
description: 'Adds more details to output logging.'
70+
name: 'app',
71+
type: String,
72+
aliases: ['a'],
73+
description: 'Specifies app name to use.'
74+
},
75+
{
76+
name: 'collection',
77+
type: String,
78+
aliases: ['c'],
79+
description: 'Schematics collection to use.'
4880
}
4981
],
5082

5183
anonymousOptions: [
52-
'<blueprint>'
84+
'<schemtatic>'
5385
],
5486

55-
beforeRun: function (rawArgs: string[]) {
56-
if (!rawArgs.length) {
57-
return;
87+
getCollectionName(rawArgs: string[]) {
88+
let collectionName = CliConfig.getValue('defaults.schematics.collection');
89+
if (rawArgs) {
90+
const parsedArgs = this.parseArgs(rawArgs, false);
91+
if (parsedArgs.options.collection) {
92+
collectionName = parsedArgs.options.collection;
93+
}
5894
}
95+
return collectionName;
96+
},
97+
98+
beforeRun: function(rawArgs: string[]) {
99+
const collection = getCollection(this.getCollectionName(rawArgs));
59100

60101
const isHelp = ['--help', '-h'].includes(rawArgs[0]);
61102
if (isHelp) {
62103
return;
63104
}
64105

65-
this.blueprints = loadBlueprints();
66-
67-
const name = rawArgs[0];
68-
const blueprint = this.blueprints.find((bp: any) => bp.name === name
69-
|| (bp.aliases && bp.aliases.includes(name)));
70-
71-
if (!blueprint) {
72-
SilentError.debugOrThrow('@angular/cli/commands/generate',
73-
`Invalid blueprint: ${name}`);
74-
}
75-
76-
if (!rawArgs[1]) {
77-
SilentError.debugOrThrow('@angular/cli/commands/generate',
78-
`The \`ng generate ${name}\` command requires a name to be specified.`);
106+
const schematicName = rawArgs[0];
107+
if (!schematicName) {
108+
return Promise.reject(new SilentError(oneLine`
109+
The "ng generate" command requires a
110+
schematic name to be specified.
111+
For more details, use "ng help".
112+
`));
79113
}
80114

81-
if (/^\d/.test(rawArgs[1])) {
82-
SilentError.debugOrThrow('@angular/cli/commands/generate',
83-
`The \`ng generate ${name} ${rawArgs[1]}\` file name cannot begin with a digit.`);
84-
}
115+
this.schematic = getSchematic(collection, schematicName);
85116

86-
rawArgs[0] = blueprint.name;
87-
this.registerOptions(blueprint);
88-
},
117+
const schematicOptions = mapSchematicOptions(this.schematic);
89118

90-
printDetailedHelp: function () {
91-
if (!this.blueprints) {
92-
this.blueprints = loadBlueprints();
93-
}
94-
this.ui.writeLine(chalk.cyan(' Available blueprints'));
95-
this.ui.writeLine(this.blueprints.map((bp: any) => bp.printBasicHelp(false)).join(os.EOL));
119+
this.registerOptions({availableOptions: schematicOptions});
96120
},
97121

98122
run: function (commandOptions: any, rawArgs: string[]) {
99-
const name = rawArgs[0];
100-
if (!name) {
101-
return Promise.reject(new SilentError(oneLine`
102-
The "ng generate" command requires a
103-
blueprint name to be specified.
104-
For more details, use "ng help".
105-
`));
106-
}
123+
const entityName = rawArgs[1];
124+
commandOptions.name = stringUtils.dasherize(entityName.split(path.sep).pop());
107125

108-
const blueprint = this.blueprints.find((bp: any) => bp.name === name
109-
|| (bp.aliases && bp.aliases.includes(name)));
110-
111-
const projectName = CliConfig.getValue('project.name');
112-
const blueprintOptions = {
113-
target: this.project.root,
114-
entity: {
115-
name: rawArgs[1],
116-
options: parseOptions(rawArgs.slice(2))
117-
},
118-
projectName,
119-
ui: this.ui,
126+
const appConfig = getAppFromConfig(commandOptions.app);
127+
const dynamicPathOptions: DynamicPathOptions = {
120128
project: this.project,
121-
settings: this.settings,
122-
testing: this.testing,
123-
args: rawArgs,
124-
...commandOptions
129+
entityName: entityName,
130+
appConfig: appConfig,
131+
dryRun: commandOptions.dryRun
125132
};
133+
const parsedPath = dynamicPathParser(dynamicPathOptions);
134+
commandOptions.sourceDir = appConfig.root;
135+
commandOptions.path = parsedPath.dir.replace(appConfig.root + path.sep, '');
136+
137+
const cwd = process.cwd();
138+
let dir = cwd;
139+
const sourceRoot = path.join(this.project.root, appConfig.root);
140+
const appRoot = path.join(sourceRoot, 'app');
141+
if (cwd.indexOf(appRoot) === 0) {
142+
dir = cwd;
143+
} else if (cwd.indexOf(sourceRoot) === 0) {
144+
dir = path.join(sourceRoot, 'app');
145+
} else {
146+
dir = path.join(this.project.root, appConfig.root, 'app');
147+
}
126148

127-
return blueprint.install(blueprintOptions)
128-
.then(() => {
129-
const lintFix = commandOptions.lintFix !== undefined ?
130-
commandOptions.lintFix : CliConfig.getValue('defaults.lintFix');
131-
132-
if (lintFix && blueprint.modifiedFiles) {
133-
const LintTask = require('../tasks/lint').default;
134-
const lintTask = new LintTask({
135-
ui: this.ui,
136-
project: this.project
137-
});
138-
139-
return lintTask.run({
140-
fix: true,
141-
force: true,
142-
silent: true,
143-
configs: [{
144-
files: blueprint.modifiedFiles.filter((file: string) => /.ts$/.test(file))
145-
}]
146-
});
147-
}
148-
});
149+
const collectionName = this.getCollectionName(rawArgs);
150+
const collection = getCollection(collectionName);
151+
const schematicName = rawArgs[0];
152+
const schematic = getSchematic(collection, schematicName);
153+
return runSchematic(schematic, cwd, dir, commandOptions, this.project.root);
154+
155+
},
156+
157+
printDetailedHelp: function () {
158+
const engineHost = getEngineHost();
159+
const collectionName = this.getCollectionName();
160+
const collection = getCollection(collectionName);
161+
const schematicNames: string[] = engineHost.listSchematics(collection);
162+
this.ui.writeLine(cyan('Available schematics:'));
163+
schematicNames.forEach(schematicName => {
164+
this.ui.writeLine(yellow(` ${schematicName}`));
165+
});
166+
this.ui.writeLine('');
149167
}
150168
});

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

+8-33
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import * as fs from 'fs';
22
import * as path from 'path';
33
import * as chalk from 'chalk';
4-
import denodeify = require('denodeify');
54

65
import InitCommand from './init';
76
import { CliConfig } from '../models/config';
@@ -12,10 +11,6 @@ const Command = require('../ember-cli/lib/models/command');
1211
const Project = require('../ember-cli/lib/models/project');
1312
const SilentError = require('silent-error');
1413

15-
// There's some problem with the generic typings for fs.makedir.
16-
// Couldn't find matching types for the callbacks so leaving it as any for now.
17-
const mkdir = denodeify<string, void>(fs.mkdir as any);
18-
1914
const configFile = '.angular-cli.json';
2015
const changeLater = (path: string) => `You can later change the value in "${configFile}" (${path})`;
2116

@@ -159,42 +154,22 @@ const NewCommand = Command.extend({
159154
commandOptions.skipGit = true;
160155
}
161156

162-
const directoryName = path.join(process.cwd(),
163-
commandOptions.directory ? commandOptions.directory : packageName);
164-
157+
commandOptions.directory = commandOptions.directory || packageName;
158+
const directoryName = path.join(process.cwd(), commandOptions.directory);
165159
const initCommand = new InitCommand({
166160
ui: this.ui,
167161
tasks: this.tasks,
168162
project: Project.nullProject(this.ui, this.cli)
169163
});
170164

171-
let createDirectory;
172-
if (commandOptions.dryRun) {
173-
createDirectory = Promise.resolve()
174-
.then(() => {
175-
if (fs.existsSync(directoryName) && this.isProject(directoryName)) {
176-
throw new SilentError(oneLine`
177-
Directory ${directoryName} exists and is already an Angular CLI project.
178-
`);
179-
}
180-
});
181-
} else {
182-
createDirectory = mkdir(directoryName)
183-
.catch((err) => {
184-
if (err.code === 'EEXIST') {
185-
if (this.isProject(directoryName)) {
186-
throw new SilentError(oneLine`
187-
Directory ${directoryName} exists and is already an Angular CLI project.
188-
`);
189-
}
190-
} else {
191-
throw err;
192-
}
193-
})
194-
.then(() => process.chdir(directoryName));
165+
166+
if (fs.existsSync(directoryName) && this.isProject(directoryName)) {
167+
throw new SilentError(oneLine`
168+
Directory ${directoryName} exists and is already an Angular CLI project.
169+
`);
195170
}
196171

197-
return createDirectory
172+
return Promise.resolve()
198173
.then(initCommand.run.bind(initCommand, commandOptions, rawArgs));
199174
}
200175
});

packages/@angular/cli/ember-cli/lib/models/command.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -496,7 +496,7 @@ let Command = CoreObject.extend({
496496
@param {Object} commandArgs
497497
@return {Object|null}
498498
*/
499-
parseArgs(commandArgs) {
499+
parseArgs(commandArgs, showErrors = true) {
500500
let knownOpts = {}; // Parse options
501501
let commandOptions = {};
502502
let parsedOptions;
@@ -507,7 +507,7 @@ let Command = CoreObject.extend({
507507

508508
let validateParsed = function(key) {
509509
// ignore 'argv', 'h', and 'help'
510-
if (!commandOptions.hasOwnProperty(key) && key !== 'argv' && key !== 'h' && key !== 'help') {
510+
if (!commandOptions.hasOwnProperty(key) && key !== 'argv' && key !== 'h' && key !== 'help' && showErrors) {
511511
this.ui.writeLine(chalk.yellow(`The option '--${key}' is not registered with the ${this.name} command. ` +
512512
`Run \`ng ${this.name} --help\` for a list of supported options.`));
513513
}

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

+17
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,23 @@
536536
"type": "string"
537537
}
538538
}
539+
},
540+
"schematics": {
541+
"description": "Properties about schematics.",
542+
"type": "object",
543+
"properties": {
544+
"collection": {
545+
"description": "The schematics collection to use.",
546+
"type": "string",
547+
"default": "@schematics/angular"
548+
},
549+
"newApp": {
550+
"description": "The new app schematic.",
551+
"type": "string",
552+
"default": "angular-app"
553+
}
554+
},
555+
"additionalProperties": false
539556
}
540557
},
541558
"additionalProperties": false

packages/@angular/cli/models/config.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export class CliConfig extends CliConfigBase<ConfigInterface> {
3232
const projectConfig = CliConfig.fromProject();
3333
if (projectConfig) {
3434
value = projectConfig.get(jsonPath);
35-
} else if (CliConfig.globalConfigFilePath() !== CliConfig.configFilePath()) {
35+
} else {
3636
const globalConfig = CliConfig.fromGlobal();
3737
if (globalConfig) {
3838
value = globalConfig.get(jsonPath);

0 commit comments

Comments
 (0)