@schematics/angular
Version:
Schematics specific to Angular
328 lines (327 loc) • 13.6 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
const core_1 = require("@angular-devkit/core");
const schematics_1 = require("@angular-devkit/schematics");
const tasks_1 = require("@angular-devkit/schematics/tasks");
const dependencies_1 = require("../utility/dependencies");
const json_utils_1 = require("../utility/json-utils");
const latest_versions_1 = require("../utility/latest-versions");
const lint_fix_1 = require("../utility/lint-fix");
const paths_1 = require("../utility/paths");
const validation_1 = require("../utility/validation");
const workspace_1 = require("../utility/workspace");
const workspace_models_1 = require("../utility/workspace-models");
const schema_1 = require("./schema");
function addDependenciesToPackageJson(options) {
return (host, context) => {
[
{
type: dependencies_1.NodeDependencyType.Dev,
name: '@angular/compiler-cli',
version: latest_versions_1.latestVersions.Angular,
},
{
type: dependencies_1.NodeDependencyType.Dev,
name: '@angular-devkit/build-angular',
version: latest_versions_1.latestVersions.DevkitBuildAngular,
},
{
type: dependencies_1.NodeDependencyType.Dev,
name: 'typescript',
version: latest_versions_1.latestVersions.TypeScript,
},
].forEach(dependency => dependencies_1.addPackageJsonDependency(host, dependency));
if (!options.skipInstall) {
context.addTask(new tasks_1.NodePackageInstallTask());
}
return host;
};
}
function readTsLintConfig(host, path) {
const buffer = host.read(path);
if (!buffer) {
throw new schematics_1.SchematicsException(`Could not read ${path}.`);
}
const config = core_1.parseJsonAst(buffer.toString(), core_1.JsonParseMode.Loose);
if (config.kind !== 'object') {
throw new schematics_1.SchematicsException(`Invalid ${path}. Was expecting an object.`);
}
return config;
}
/**
* Merges the application tslint.json with the workspace tslint.json
* when the application being created is a root application
*
* @param {Tree} parentHost The root host of the schematic
*/
function mergeWithRootTsLint(parentHost) {
return (host) => {
const tsLintPath = '/tslint.json';
if (!host.exists(tsLintPath)) {
return;
}
const rootTslintConfig = readTsLintConfig(parentHost, tsLintPath);
const appTslintConfig = readTsLintConfig(host, tsLintPath);
const recorder = host.beginUpdate(tsLintPath);
rootTslintConfig.properties.forEach(prop => {
if (json_utils_1.findPropertyInAstObject(appTslintConfig, prop.key.value)) {
// property already exists. Skip!
return;
}
json_utils_1.insertPropertyInAstObjectInOrder(recorder, appTslintConfig, prop.key.value, prop.value.value, 2);
});
const rootRules = json_utils_1.findPropertyInAstObject(rootTslintConfig, 'rules');
const appRules = json_utils_1.findPropertyInAstObject(appTslintConfig, 'rules');
if (!appRules || appRules.kind !== 'object' || !rootRules || rootRules.kind !== 'object') {
// rules are not valid. Skip!
return;
}
rootRules.properties.forEach(prop => {
json_utils_1.insertPropertyInAstObjectInOrder(recorder, appRules, prop.key.value, prop.value.value, 4);
});
host.commitUpdate(recorder);
// this shouldn't be needed but at the moment without this formatting is not correct.
const content = readTsLintConfig(host, tsLintPath);
host.overwrite(tsLintPath, JSON.stringify(content.value, undefined, 2));
};
}
function addAppToWorkspaceFile(options, appDir) {
let projectRoot = appDir;
if (projectRoot) {
projectRoot += '/';
}
const schematics = {};
if (options.inlineTemplate === true
|| options.inlineStyle === true
|| options.style !== schema_1.Style.Css) {
const componentSchematicsOptions = {};
if (options.inlineTemplate === true) {
componentSchematicsOptions.inlineTemplate = true;
}
if (options.inlineStyle === true) {
componentSchematicsOptions.inlineStyle = true;
}
if (options.style && options.style !== schema_1.Style.Css) {
componentSchematicsOptions.style = options.style;
}
schematics['@schematics/angular:component'] = componentSchematicsOptions;
}
if (options.skipTests === true) {
['class', 'component', 'directive', 'guard', 'module', 'pipe', 'service'].forEach((type) => {
if (!(`@schematics/angular:${type}` in schematics)) {
schematics[`@schematics/angular:${type}`] = {};
}
schematics[`@schematics/angular:${type}`].skipTests = true;
});
}
const sourceRoot = core_1.join(core_1.normalize(projectRoot), 'src');
const project = {
root: core_1.normalize(projectRoot),
sourceRoot,
projectType: workspace_models_1.ProjectType.Application,
prefix: options.prefix || 'app',
schematics,
targets: {
build: {
builder: workspace_models_1.Builders.Browser,
options: {
outputPath: `dist/${options.name}`,
index: `${sourceRoot}/index.html`,
main: `${sourceRoot}/main.ts`,
polyfills: `${sourceRoot}/polyfills.ts`,
tsConfig: `${projectRoot}tsconfig.app.json`,
aot: !!options.enableIvy,
assets: [
`${sourceRoot}/favicon.ico`,
`${sourceRoot}/assets`,
],
styles: [
`${sourceRoot}/styles.${options.style}`,
],
scripts: [],
},
configurations: {
production: {
fileReplacements: [{
replace: `${sourceRoot}/environments/environment.ts`,
with: `${sourceRoot}/environments/environment.prod.ts`,
}],
optimization: true,
outputHashing: 'all',
sourceMap: false,
extractCss: true,
namedChunks: false,
aot: true,
extractLicenses: true,
vendorChunk: false,
buildOptimizer: true,
budgets: [
{
type: 'initial',
maximumWarning: '2mb',
maximumError: '5mb',
},
{
type: 'anyComponentStyle',
maximumWarning: '6kb',
maximumError: '10kb',
}
],
},
},
},
serve: {
builder: workspace_models_1.Builders.DevServer,
options: {
browserTarget: `${options.name}:build`,
},
configurations: {
production: {
browserTarget: `${options.name}:build:production`,
},
},
},
'extract-i18n': {
builder: workspace_models_1.Builders.ExtractI18n,
options: {
browserTarget: `${options.name}:build`,
},
},
test: options.minimal ? undefined : {
builder: workspace_models_1.Builders.Karma,
options: {
main: `${sourceRoot}/test.ts`,
polyfills: `${sourceRoot}/polyfills.ts`,
tsConfig: `${projectRoot}tsconfig.spec.json`,
karmaConfig: `${projectRoot}karma.conf.js`,
assets: [
`${sourceRoot}/favicon.ico`,
`${sourceRoot}/assets`,
],
styles: [
`${sourceRoot}/styles.${options.style}`,
],
scripts: [],
},
},
lint: options.minimal ? undefined : {
builder: workspace_models_1.Builders.TsLint,
options: {
tsConfig: [
`${projectRoot}tsconfig.app.json`,
`${projectRoot}tsconfig.spec.json`,
],
exclude: [
'**/node_modules/**',
],
},
},
},
};
return workspace_1.updateWorkspace(workspace => {
if (workspace.projects.size === 0) {
workspace.extensions.defaultProject = options.name;
}
workspace.projects.add({
name: options.name,
...project,
});
});
}
function minimalPathFilter(path) {
const toRemoveList = /(test.ts|tsconfig.spec.json|karma.conf.js|tslint.json).template$/;
return !toRemoveList.test(path);
}
function default_1(options) {
return async (host, context) => {
if (!options.name) {
throw new schematics_1.SchematicsException(`Invalid options, "name" is required.`);
}
validation_1.validateProjectName(options.name);
const appRootSelector = `${options.prefix}-root`;
const componentOptions = !options.minimal ?
{
inlineStyle: options.inlineStyle,
inlineTemplate: options.inlineTemplate,
skipTests: options.skipTests,
style: options.style,
viewEncapsulation: options.viewEncapsulation,
} :
{
inlineStyle: true,
inlineTemplate: true,
skipTests: true,
style: options.style,
};
const workspace = await workspace_1.getWorkspace(host);
const newProjectRoot = workspace.extensions.newProjectRoot || '';
const isRootApp = options.projectRoot !== undefined;
const appDir = isRootApp
? options.projectRoot
: core_1.join(core_1.normalize(newProjectRoot), options.name);
const sourceDir = `${appDir}/src/app`;
const e2eOptions = {
relatedAppName: options.name,
rootSelector: appRootSelector,
};
return schematics_1.chain([
addAppToWorkspaceFile(options, appDir),
schematics_1.mergeWith(schematics_1.apply(schematics_1.url('./files'), [
options.minimal ? schematics_1.filter(minimalPathFilter) : schematics_1.noop(),
schematics_1.applyTemplates({
utils: core_1.strings,
...options,
relativePathToWorkspaceRoot: paths_1.relativePathToWorkspaceRoot(appDir),
appName: options.name,
isRootApp,
}),
isRootApp ? mergeWithRootTsLint(host) : schematics_1.noop(),
schematics_1.move(appDir),
]), schematics_1.MergeStrategy.Overwrite),
schematics_1.schematic('module', {
name: 'app',
commonModule: false,
flat: true,
routing: options.routing,
routingScope: 'Root',
path: sourceDir,
project: options.name,
}),
schematics_1.schematic('component', {
name: 'app',
selector: appRootSelector,
flat: true,
path: sourceDir,
skipImport: true,
project: options.name,
...componentOptions,
}),
schematics_1.mergeWith(schematics_1.apply(schematics_1.url('./other-files'), [
componentOptions.inlineTemplate
? schematics_1.filter(path => !path.endsWith('.html.template'))
: schematics_1.noop(),
componentOptions.skipTests
? schematics_1.filter(path => !path.endsWith('.spec.ts.template'))
: schematics_1.noop(),
schematics_1.applyTemplates({
utils: core_1.strings,
...options,
selector: appRootSelector,
...componentOptions,
}),
schematics_1.move(sourceDir),
]), schematics_1.MergeStrategy.Overwrite),
options.minimal ? schematics_1.noop() : schematics_1.schematic('e2e', e2eOptions),
options.skipPackageJson ? schematics_1.noop() : addDependenciesToPackageJson(options),
options.lintFix ? lint_fix_1.applyLintFix(appDir) : schematics_1.noop(),
]);
};
}
exports.default = default_1;
;