"use strict";
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;
