Skip to content

Commit 59e9e8f

Browse files
authored
feat(commands): lazy load commands (#3805)
This should help minimize startup time for `ng` commands.
1 parent 2c203f5 commit 59e9e8f

File tree

10 files changed

+450
-427
lines changed

10 files changed

+450
-427
lines changed
+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { Version } from '../upgrade/version';
2+
import WebpackBuild from '../tasks/build-webpack';
3+
import WebpackBuildWatch from '../tasks/build-webpack-watch';
4+
import { BuildOptions } from './build';
5+
6+
export default function buildRun(commandOptions: BuildOptions) {
7+
if (commandOptions.environment === '') {
8+
if (commandOptions.target === 'development') {
9+
commandOptions.environment = 'dev';
10+
}
11+
if (commandOptions.target === 'production') {
12+
commandOptions.environment = 'prod';
13+
}
14+
}
15+
16+
const project = this.project;
17+
18+
// Check angular version.
19+
Version.assertAngularVersionIs2_3_1OrHigher(project.root);
20+
21+
const ui = this.ui;
22+
const buildTask = commandOptions.watch ?
23+
new WebpackBuildWatch({
24+
cliProject: project,
25+
ui: ui,
26+
outputPath: commandOptions.outputPath,
27+
target: commandOptions.target,
28+
environment: commandOptions.environment
29+
}) :
30+
new WebpackBuild({
31+
cliProject: project,
32+
ui: ui,
33+
outputPath: commandOptions.outputPath,
34+
target: commandOptions.target,
35+
environment: commandOptions.environment,
36+
});
37+
38+
return buildTask.run(commandOptions);
39+
}

packages/angular-cli/commands/build.ts

+1-35
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
1-
import {Version} from '../upgrade/version';
21
const Command = require('../ember-cli/lib/models/command');
3-
import WebpackBuild from '../tasks/build-webpack';
4-
import WebpackBuildWatch from '../tasks/build-webpack-watch';
52

63
export interface BuildOptions {
74
target?: string;
@@ -52,38 +49,7 @@ const BuildCommand = Command.extend({
5249
],
5350

5451
run: function (commandOptions: BuildOptions) {
55-
if (commandOptions.environment === '') {
56-
if (commandOptions.target === 'development') {
57-
commandOptions.environment = 'dev';
58-
}
59-
if (commandOptions.target === 'production') {
60-
commandOptions.environment = 'prod';
61-
}
62-
}
63-
64-
const project = this.project;
65-
66-
// Check angular version.
67-
Version.assertAngularVersionIs2_3_1OrHigher(project.root);
68-
69-
const ui = this.ui;
70-
const buildTask = commandOptions.watch ?
71-
new WebpackBuildWatch({
72-
cliProject: project,
73-
ui: ui,
74-
outputPath: commandOptions.outputPath,
75-
target: commandOptions.target,
76-
environment: commandOptions.environment
77-
}) :
78-
new WebpackBuild({
79-
cliProject: project,
80-
ui: ui,
81-
outputPath: commandOptions.outputPath,
82-
target: commandOptions.target,
83-
environment: commandOptions.environment,
84-
});
85-
86-
return buildTask.run(commandOptions);
52+
return require('./build.run').default.call(this, commandOptions);
8753
}
8854
});
8955

packages/angular-cli/commands/e2e.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
const Command = require('../ember-cli/lib/models/command');
2-
import {E2eTask} from '../tasks/e2e';
3-
import {CliConfig} from '../models/config';
2+
import { CliConfig } from '../models/config';
43

54
const E2eCommand = Command.extend({
65
name: 'e2e',
76
description: 'Run e2e tests in existing project',
87
works: 'insideProject',
98
run: function () {
9+
const E2eTask = require('../tasks/e2e').E2eTask;
1010
this.project.ngConfig = this.project.ngConfig || CliConfig.fromProject();
1111

1212
const e2eTask = new E2eTask({
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
const SilentError = require('silent-error');
2+
import denodeify = require('denodeify');
3+
4+
import { exec } from 'child_process';
5+
import * as chalk from 'chalk';
6+
import * as fs from 'fs';
7+
import * as fse from 'fs-extra';
8+
import * as path from 'path';
9+
import WebpackBuild from '../tasks/build-webpack';
10+
import CreateGithubRepo from '../tasks/create-github-repo';
11+
import { CliConfig } from '../models/config';
12+
import { GithubPagesDeployOptions } from './github-pages-deploy';
13+
14+
const fsReadDir = <any>denodeify(fs.readdir);
15+
const fsCopy = <any>denodeify(fse.copy);
16+
17+
export default function githubPagesDeployRun(options: GithubPagesDeployOptions, rawArgs: string[]) {
18+
const ui = this.ui;
19+
const root = this.project.root;
20+
const execOptions = {
21+
cwd: root
22+
};
23+
24+
if (options.environment === '') {
25+
if (options.target === 'development') {
26+
options.environment = 'dev';
27+
}
28+
if (options.target === 'production') {
29+
options.environment = 'prod';
30+
}
31+
}
32+
33+
const projectName = this.project.pkg.name;
34+
35+
const outDir = CliConfig.fromProject().config.apps[0].outDir;
36+
const indexFilename = CliConfig.fromProject().config.apps[0].index;
37+
38+
let ghPagesBranch = 'gh-pages';
39+
let destinationBranch = options.userPage ? 'master' : ghPagesBranch;
40+
let initialBranch: string;
41+
let branchErrMsg = ' You might also need to return to the initial branch manually.';
42+
43+
// declared here so that tests can stub exec
44+
const execPromise = <(cmd: string, options?: any) => Promise<string>>denodeify(exec);
45+
46+
const buildTask = new WebpackBuild({
47+
ui: this.ui,
48+
cliProject: this.project,
49+
target: options.target,
50+
environment: options.environment,
51+
outputPath: outDir
52+
});
53+
54+
/**
55+
* BaseHref tag setting logic:
56+
* First, use --base-href flag value if provided.
57+
* Else if --user-page is true, then keep baseHref default as declared in index.html.
58+
* Otherwise auto-replace with `/${projectName}/`.
59+
*/
60+
const baseHref = options.baseHref || (options.userPage ? null : `/${projectName}/`);
61+
62+
const buildOptions = {
63+
target: options.target,
64+
environment: options.environment,
65+
outputPath: outDir,
66+
baseHref: baseHref,
67+
};
68+
69+
const createGithubRepoTask = new CreateGithubRepo({
70+
ui: this.ui,
71+
project: this.project
72+
});
73+
74+
const createGithubRepoOptions = {
75+
projectName,
76+
ghUsername: options.ghUsername,
77+
ghToken: options.ghToken
78+
};
79+
80+
return checkForPendingChanges()
81+
.then(build)
82+
.then(saveStartingBranchName)
83+
.then(createGitHubRepoIfNeeded)
84+
.then(checkoutGhPages)
85+
.then(cleanGhPagesBranch)
86+
.then(copyFiles)
87+
.then(createNotFoundPage)
88+
.then(addAndCommit)
89+
.then(returnStartingBranch)
90+
.then(pushToGitRepo)
91+
.then(printProjectUrl)
92+
.catch(failGracefully);
93+
94+
function checkForPendingChanges() {
95+
return execPromise('git status --porcelain')
96+
.then((stdout: string) => {
97+
if (/\w+/m.test(stdout)) {
98+
let msg = 'Uncommitted file changes found! Please commit all changes before deploying.';
99+
return Promise.reject(new SilentError(msg));
100+
}
101+
});
102+
}
103+
104+
function build() {
105+
if (options.skipBuild) { return Promise.resolve(); }
106+
return buildTask.run(buildOptions);
107+
}
108+
109+
function saveStartingBranchName() {
110+
return execPromise('git rev-parse --abbrev-ref HEAD')
111+
.then((stdout: string) => initialBranch = stdout.replace(/\s/g, ''));
112+
}
113+
114+
function createGitHubRepoIfNeeded() {
115+
return execPromise('git remote -v')
116+
.then(function (stdout) {
117+
if (!/origin\s+(https:\/\/|git@)github\.com/m.test(stdout)) {
118+
return createGithubRepoTask.run(createGithubRepoOptions)
119+
.then(() => {
120+
// only push starting branch if it's not the destinationBranch
121+
// this happens commonly when using github user pages, since
122+
// they require the destination branch to be 'master'
123+
if (destinationBranch !== initialBranch) {
124+
execPromise(`git push -u origin ${initialBranch}`);
125+
}
126+
});
127+
}
128+
});
129+
}
130+
131+
function checkoutGhPages() {
132+
return execPromise(`git checkout ${ghPagesBranch}`)
133+
.catch(createGhPagesBranch);
134+
}
135+
136+
function createGhPagesBranch() {
137+
return execPromise(`git checkout --orphan ${ghPagesBranch}`)
138+
.then(() => execPromise('git rm --cached -r .', execOptions))
139+
.then(() => execPromise('git add .gitignore', execOptions))
140+
.then(() => execPromise('git clean -f -d', execOptions))
141+
.then(() => execPromise(`git commit -m \"initial ${ghPagesBranch} commit\"`));
142+
}
143+
144+
function cleanGhPagesBranch() {
145+
return execPromise('git ls-files')
146+
.then(function (stdout) {
147+
let files = '';
148+
stdout.split(/\n/).forEach(function (f) {
149+
// skip .gitignore & 404.html
150+
if ((f != '') && (f != '.gitignore') && (f != '404.html')) {
151+
files = files.concat(`"${f}" `);
152+
}
153+
});
154+
return execPromise(`git rm -r ${files}`)
155+
.catch(() => {
156+
// Ignoring errors when trying to erase files.
157+
});
158+
});
159+
}
160+
161+
function copyFiles() {
162+
return fsReadDir(outDir)
163+
.then((files: string[]) => Promise.all(files.map((file) => {
164+
if (file === '.gitignore') {
165+
// don't overwrite the .gitignore file
166+
return Promise.resolve();
167+
}
168+
return fsCopy(path.join(outDir, file), path.join('.', file));
169+
})));
170+
}
171+
172+
function createNotFoundPage() {
173+
const indexHtml = path.join(root, indexFilename);
174+
const notFoundPage = path.join(root, '404.html');
175+
return fsCopy(indexHtml, notFoundPage);
176+
}
177+
178+
function addAndCommit() {
179+
return execPromise('git add .', execOptions)
180+
.then(() => execPromise(`git commit -m "${options.message}"`))
181+
.catch(() => {
182+
let msg = 'No changes found. Deployment skipped.';
183+
return returnStartingBranch()
184+
.then(() => Promise.reject(new SilentError(msg)))
185+
.catch(() => Promise.reject(new SilentError(msg.concat(branchErrMsg))));
186+
});
187+
}
188+
189+
function returnStartingBranch() {
190+
return execPromise(`git checkout ${initialBranch}`);
191+
}
192+
193+
function pushToGitRepo() {
194+
return execPromise(`git push origin ${ghPagesBranch}:${destinationBranch}`)
195+
.catch((err) => returnStartingBranch()
196+
.catch(() => Promise.reject(err)));
197+
}
198+
199+
function printProjectUrl() {
200+
return execPromise('git remote -v')
201+
.then((stdout) => {
202+
let match = stdout.match(/origin\s+(?:https:\/\/|git@)github\.com(?:\:|\/)([^\/]+)/m);
203+
let userName = match[1].toLowerCase();
204+
let url = `https://${userName}.github.io/${options.userPage ? '' : (baseHref + '/')}`;
205+
ui.writeLine(chalk.green(`Deployed! Visit ${url}`));
206+
ui.writeLine('Github pages might take a few minutes to show the deployed site.');
207+
});
208+
}
209+
210+
function failGracefully(error: Error) {
211+
if (error && (/git clean/.test(error.message) || /Permission denied/.test(error.message))) {
212+
ui.writeLine(error.message);
213+
let msg = 'There was a permissions error during git file operations, ' +
214+
'please close any open project files/folders and try again.';
215+
return Promise.reject(new SilentError(msg.concat(branchErrMsg)));
216+
} else {
217+
return Promise.reject(error);
218+
}
219+
}
220+
}

0 commit comments

Comments
 (0)