"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getLogger = exports.wrapAngularDevkitSchematic = exports.mockSchematicsForTesting = exports.overrideCollectionResolutionForTesting = exports.runMigration = exports.generate = exports.NxScopeHostUsedForWrappedSchematics = exports.NxScopedHost = exports.scheduleTarget = void 0;
const tslib_1 = require("tslib");
/* eslint-disable no-restricted-imports */
const core_1 = require("@angular-devkit/core");
const chalk = require("chalk");
const node_1 = require("@angular-devkit/core/node");
const package_manager_1 = require("../utils/package-manager");
const path_1 = require("path");
const rxjs_1 = require("rxjs");
const operators_1 = require("rxjs/operators");
const logger_1 = require("../utils/logger");
const fileutils_1 = require("../utils/fileutils");
const json_1 = require("../utils/json");
const angular_json_1 = require("./angular-json");
const project_graph_1 = require("../project-graph/project-graph");
const configuration_1 = require("../config/configuration");
const project_configuration_1 = require("../generators/utils/project-configuration");
const json_2 = require("../generators/utils/json");
const package_json_1 = require("../utils/package-json");
function scheduleTarget(root, opts, verbose) {
    return tslib_1.__awaiter(this, void 0, void 0, function* () {
        const { Architect } = require('@angular-devkit/architect');
        const { WorkspaceNodeModulesArchitectHost, } = require('@angular-devkit/architect/node');
        const logger = (0, exports.getLogger)(verbose);
        const fsHost = new NxScopedHost(root);
        const { workspace } = yield core_1.workspaces.readWorkspace('angular.json', core_1.workspaces.createWorkspaceHost(fsHost));
        const registry = new core_1.schema.CoreSchemaRegistry();
        registry.addPostTransform(core_1.schema.transforms.addUndefinedDefaults);
        registry.addSmartDefaultProvider('unparsed', () => {
            // This happens when context.scheduleTarget is used to run a target using nx:run-commands
            return [];
        });
        const architectHost = new WorkspaceNodeModulesArchitectHost(workspace, root);
        const architect = new Architect(architectHost, registry);
        const run = yield architect.scheduleTarget({
            project: opts.project,
            target: opts.target,
            configuration: opts.configuration,
        }, opts.runOptions, { logger });
        let lastOutputError;
        return run.output.pipe((0, operators_1.tap)((output) => (lastOutputError = !output.success ? output.error : undefined), (error) => { }, // do nothing, this could be an intentional error
        () => {
            lastOutputError ? logger.error(lastOutputError) : 0;
        }));
    });
}
exports.scheduleTarget = scheduleTarget;
function createWorkflow(fsHost, root, opts) {
    const NodeWorkflow = require('@angular-devkit/schematics/tools').NodeWorkflow;
    const workflow = new NodeWorkflow(fsHost, {
        force: false,
        dryRun: opts.dryRun,
        packageManager: (0, package_manager_1.detectPackageManager)(),
        root: (0, core_1.normalize)(root),
        registry: new core_1.schema.CoreSchemaRegistry(require('@angular-devkit/schematics').formats.standardFormats),
        resolvePaths: [process.cwd(), root],
    });
    workflow.registry.addPostTransform(core_1.schema.transforms.addUndefinedDefaults);
    workflow.engineHost.registerOptionsTransform(require('@angular-devkit/schematics/tools').validateOptionsWithSchema(workflow.registry));
    if (opts.interactive) {
        workflow.registry.usePromptProvider(createPromptProvider());
    }
    return workflow;
}
function getCollection(workflow, name) {
    const collection = workflow.engine.createCollection(name);
    if (!collection)
        throw new Error(`Cannot find collection '${name}'`);
    return collection;
}
function createRecorder(host, record, logger) {
    return tslib_1.__awaiter(this, void 0, void 0, function* () {
        return (event) => {
            let eventPath = event.path.startsWith('/')
                ? event.path.slice(1)
                : event.path;
            if (event.kind === 'error') {
                record.error = true;
                logger.warn(`ERROR! ${eventPath} ${event.description == 'alreadyExist'
                    ? 'already exists'
                    : 'does not exist.'}.`);
            }
            else if (event.kind === 'update') {
                record.loggingQueue.push(core_1.tags.oneLine `${chalk.white('UPDATE')} ${eventPath}`);
            }
            else if (event.kind === 'create') {
                record.loggingQueue.push(core_1.tags.oneLine `${chalk.green('CREATE')} ${eventPath}`);
            }
            else if (event.kind === 'delete') {
                record.loggingQueue.push(`${chalk.yellow('DELETE')} ${eventPath}`);
            }
            else if (event.kind === 'rename') {
                record.loggingQueue.push(`${chalk.blue('RENAME')} ${eventPath} => ${event.to}`);
            }
        };
    });
}
function runSchematic(host, root, workflow, logger, opts, schematic, printDryRunMessage = true, recorder = null) {
    return tslib_1.__awaiter(this, void 0, void 0, function* () {
        const record = { loggingQueue: [], error: false };
        workflow.reporter.subscribe(recorder || (yield createRecorder(host, record, logger)));
        try {
            yield workflow
                .execute({
                collection: opts.collectionName,
                schematic: opts.generatorName,
                options: opts.generatorOptions,
                debug: false,
                logger,
            })
                .toPromise();
        }
        catch (e) {
            console.log(e);
            throw e;
        }
        if (!record.error) {
            record.loggingQueue.forEach((log) => logger.info(log));
        }
        if (opts.dryRun && printDryRunMessage) {
            logger.warn(`\nNOTE: The "dryRun" flag means no changes were made.`);
        }
        return { status: 0, loggingQueue: record.loggingQueue };
    });
}
class NxScopedHost extends core_1.virtualFs.ScopedHost {
    constructor(root) {
        super(new node_1.NodeJsSyncHost(), (0, core_1.normalize)(root));
        this.root = root;
    }
    read(path) {
        if (path === 'angular.json' || path === '/angular.json') {
            if (this.cachedConfigurationV1) {
                return (0, rxjs_1.of)(Buffer.from(JSON.stringify(this.cachedConfigurationV1)));
            }
            return (0, rxjs_1.from)((() => tslib_1.__awaiter(this, void 0, void 0, function* () {
                const projectGraph = yield (0, project_graph_1.createProjectGraphAsync)();
                const nxJson = (0, configuration_1.readNxJson)();
                // Construct old workspace.json format from project graph
                const w = Object.assign(Object.assign({}, nxJson), (0, project_graph_1.readProjectsConfigurationFromProjectGraph)(projectGraph));
                const workspaceConfiguration = (0, angular_json_1.toOldFormat)(w);
                this.cachedConfigurationV1 = workspaceConfiguration;
                return Buffer.from(JSON.stringify(workspaceConfiguration));
            }))());
        }
        else {
            return super.read(path);
        }
    }
    write(path, content) {
        if (path === 'angular.json' || path === '/angular.json') {
            this.cachedConfigurationV1 = (0, angular_json_1.toOldFormat)((0, json_1.parseJson)(content.toString()));
            const configV2 = (0, angular_json_1.toNewFormat)(this.cachedConfigurationV1);
            return this.readExistingAngularJson().pipe((0, operators_1.concatMap)((existingAngularJson) => {
                const projectsInAngularJson = existingAngularJson
                    ? Object.keys(existingAngularJson.projects)
                    : [];
                const newAngularJson = { version: 1, projects: {} };
                const projects = configV2.projects;
                const allObservables = [];
                Object.keys(projects).forEach((projectName) => {
                    if (projectsInAngularJson.includes(projectName)) {
                        newAngularJson.projects[projectName] = projects[projectName];
                    }
                    else {
                        (0, project_configuration_1.updateProjectConfiguration)({
                            exists: () => true,
                            write: (path, content) => {
                                allObservables.push(super.write(path, content));
                            },
                        }, projectName, projects[projectName]);
                    }
                });
                if (Object.keys(newAngularJson.projects).length > 0) {
                    allObservables.push(super.write('angular.json', JSON.stringify((0, angular_json_1.toOldFormat)(newAngularJson))));
                }
                return (0, rxjs_1.concat)(allObservables);
            }));
        }
        else {
            return super.write(path, content);
        }
    }
    isFile(path) {
        if (path === 'angular.json' || path === '/angular.json') {
            return (0, rxjs_1.of)(true);
        }
        else {
            return super.isFile(path);
        }
    }
    exists(path) {
        if (path === 'angular.json' || path === '/angular.json') {
            return (0, rxjs_1.of)(true);
        }
        else {
            return super.exists(path);
        }
    }
    readExistingAngularJson() {
        return super
            .exists('angular.json')
            .pipe((0, operators_1.concatMap)((r) => r
            ? super
                .read('angular.json')
                .pipe((0, operators_1.map)((r) => (0, angular_json_1.toOldFormat)((0, json_1.parseJson)(arrayBufferToString(r)))))
            : (0, rxjs_1.of)(null)));
    }
}
exports.NxScopedHost = NxScopedHost;
function arrayBufferToString(buffer) {
    return String.fromCharCode.apply(null, new Uint8Array(buffer));
}
class NxScopeHostUsedForWrappedSchematics extends NxScopedHost {
    constructor(root, host) {
        super(root);
        this.host = host;
    }
    read(path) {
        if (path == 'angular.json' || path == '/angular.json') {
            const projectJsonConfig = (0, angular_json_1.toOldFormat)({
                projects: Object.fromEntries((0, project_configuration_1.getProjects)(this.host)),
            });
            return super.readExistingAngularJson().pipe((0, operators_1.map)((angularJson) => {
                if (angularJson) {
                    return Buffer.from(JSON.stringify({
                        version: 1,
                        projects: Object.assign(Object.assign({}, projectJsonConfig.projects), angularJson.projects),
                    }));
                }
                else {
                    return Buffer.from(JSON.stringify(projectJsonConfig));
                }
            }));
        }
        else {
            const match = findMatchingFileChange(this.host, path);
            if (match) {
                return (0, rxjs_1.of)(Buffer.from(match.content));
            }
            else {
                return super.read(path);
            }
        }
    }
    exists(path) {
        if (this.host.exists(path)) {
            return (0, rxjs_1.of)(true);
        }
        else if (path === 'angular.json' || path === '/angular.json') {
            return (0, rxjs_1.of)(true);
        }
        else {
            return super.exists(path);
        }
    }
    isDirectory(path) {
        if (this.host.exists(path) && !this.host.isFile(path)) {
            return (0, rxjs_1.of)(true);
        }
        else if (path === 'angular.json' || path === '/angular.json') {
            return (0, rxjs_1.of)(false);
        }
        else {
            return super.isDirectory(path);
        }
    }
    isFile(path) {
        if (this.host.isFile(path)) {
            return (0, rxjs_1.of)(true);
        }
        else if (path === 'angular.json' || path === '/angular.json') {
            return (0, rxjs_1.of)(true);
        }
        else {
            return super.isFile(path);
        }
    }
    list(path) {
        const fragments = this.host.children(path).map((child) => (0, core_1.fragment)(child));
        return (0, rxjs_1.of)(fragments);
    }
}
exports.NxScopeHostUsedForWrappedSchematics = NxScopeHostUsedForWrappedSchematics;
function findMatchingFileChange(host, path) {
    const targetPath = (0, core_1.normalize)(path.startsWith('/') ? path.substring(1) : path.toString());
    return host
        .listChanges()
        .find((f) => f.type !== 'DELETE' && (0, core_1.normalize)(f.path) === targetPath);
}
function generate(root, opts, verbose) {
    return tslib_1.__awaiter(this, void 0, void 0, function* () {
        const logger = (0, exports.getLogger)(verbose);
        const fsHost = new NxScopedHost(root);
        const workflow = createWorkflow(fsHost, root, opts);
        const collection = getCollection(workflow, opts.collectionName);
        const schematic = collection.createSchematic(opts.generatorName, true);
        return (yield runSchematic(fsHost, root, workflow, logger, Object.assign(Object.assign({}, opts), { generatorName: schematic.description.name }), schematic)).status;
    });
}
exports.generate = generate;
function createPromptProvider() {
    return (definitions) => {
        const questions = definitions.map((definition) => {
            const question = {
                name: definition.id,
                message: definition.message,
            };
            if (definition.default) {
                question.initial = definition.default;
            }
            const validator = definition.validator;
            if (validator) {
                question.validate = (input) => validator(input);
            }
            switch (definition.type) {
                case 'string':
                case 'input':
                    return Object.assign(Object.assign({}, question), { type: 'input' });
                case 'boolean':
                case 'confirmation':
                case 'confirm':
                    return Object.assign(Object.assign({}, question), { type: 'confirm' });
                case 'number':
                case 'numeral':
                    return Object.assign(Object.assign({}, question), { type: 'numeral' });
                case 'list':
                    return Object.assign(Object.assign({}, question), { type: !!definition.multiselect ? 'multiselect' : 'select', choices: definition.items &&
                            definition.items.map((item) => {
                                if (typeof item == 'string') {
                                    return item;
                                }
                                else {
                                    return {
                                        message: item.label,
                                        name: item.value,
                                    };
                                }
                            }) });
                default:
                    return Object.assign(Object.assign({}, question), { type: definition.type });
            }
        });
        return require('enquirer').prompt(questions);
    };
}
function runMigration(root, packageName, migrationName, isVerbose) {
    return tslib_1.__awaiter(this, void 0, void 0, function* () {
        const logger = (0, exports.getLogger)(isVerbose);
        const fsHost = new NxScopedHost(root);
        const workflow = createWorkflow(fsHost, root, {});
        const collection = resolveMigrationsCollection(packageName);
        const record = { loggingQueue: [], error: false };
        workflow.reporter.subscribe(yield createRecorder(fsHost, record, logger));
        yield workflow
            .execute({
            collection,
            schematic: migrationName,
            options: {},
            debug: false,
            logger: logger,
        })
            .toPromise();
        return {
            loggingQueue: record.loggingQueue,
            madeChanges: record.loggingQueue.length > 0,
        };
    });
}
exports.runMigration = runMigration;
function resolveMigrationsCollection(name) {
    var _a;
    let collectionPath = undefined;
    if (name.startsWith('.') || name.startsWith('/')) {
        name = (0, path_1.resolve)(name);
    }
    if ((0, path_1.extname)(name)) {
        collectionPath = require.resolve(name);
    }
    else {
        const { path: packageJsonPath, packageJson } = (0, package_json_1.readModulePackageJson)(name, [
            process.cwd(),
        ]);
        let pkgJsonSchematics = (_a = packageJson['nx-migrations']) !== null && _a !== void 0 ? _a : packageJson['ng-update'];
        if (!pkgJsonSchematics) {
            throw new Error(`Could not find migrations in package: "${name}"`);
        }
        if (typeof pkgJsonSchematics != 'string') {
            pkgJsonSchematics = pkgJsonSchematics.migrations;
        }
        collectionPath = require.resolve(pkgJsonSchematics, {
            paths: [(0, path_1.dirname)(packageJsonPath)],
        });
    }
    try {
        if (collectionPath) {
            (0, fileutils_1.readJsonFile)(collectionPath);
            return collectionPath;
        }
    }
    catch (_b) {
        throw new Error(`Invalid migration file in package: "${name}"`);
    }
    throw new Error(`Collection cannot be resolved: "${name}"`);
}
let collectionResolutionOverrides = null;
let mockedSchematics = null;
/**
 * By default, Angular Devkit schematic collections will be resolved using the Node resolution.
 * This doesn't work if you are testing schematics that refer to other schematics in the
 * same repo.
 *
 * This function can can be used to override the resolution behaviour.
 *
 * Example:
 *
 * ```typescript
 *   overrideCollectionResolutionForTesting({
 *     '@nrwl/workspace': path.join(__dirname, '../../../../workspace/generators.json'),
 *     '@nrwl/angular': path.join(__dirname, '../../../../angular/generators.json'),
 *     '@nrwl/linter': path.join(__dirname, '../../../../linter/generators.json')
 *   });
 *
 * ```
 */
function overrideCollectionResolutionForTesting(collections) {
    collectionResolutionOverrides = collections;
}
exports.overrideCollectionResolutionForTesting = overrideCollectionResolutionForTesting;
/**
 * If you have an Nx Devkit generator invoking the wrapped Angular Devkit schematic,
 * and you don't want the Angular Devkit schematic to run, you can mock it up using this function.
 *
 * Unfortunately, there are some edge cases in the Nx-Angular devkit integration that
 * can be seen in the unit tests context. This function is useful for handling that as well.
 *
 * In this case, you can mock it up.
 *
 * Example:
 *
 * ```typescript
 *   mockSchematicsForTesting({
 *     'mycollection:myschematic': (tree, params) => {
 *        tree.write("README.md");
 *     }
 *   });
 *
 * ```
 */
function mockSchematicsForTesting(schematics) {
    mockedSchematics = schematics;
}
exports.mockSchematicsForTesting = mockSchematicsForTesting;
function wrapAngularDevkitSchematic(collectionName, generatorName) {
    // This is idempotent, if it happens to get called
    // multiple times its no big deal. It ensures that some
    // patches are applied to @angular-devkit code which
    // are necessary. For the most part, our wrapped host hits
    // the necessary areas, but for some things it wouldn't make
    // sense for the adapter to be 100% accurate.
    //
    // e.g. Angular warns about tags, but some angular CLI schematics
    // were written with Nx in mind, and may care about tags.
    require('./compat');
    return (host, generatorOptions) => tslib_1.__awaiter(this, void 0, void 0, function* () {
        if (mockedSchematics &&
            mockedSchematics[`${collectionName}:${generatorName}`]) {
            return yield mockedSchematics[`${collectionName}:${generatorName}`](host, generatorOptions);
        }
        const emptyLogger = {
            log: (e) => { },
            info: (e) => { },
            warn: (e) => { },
            debug: () => { },
            error: (e) => { },
            fatal: (e) => { },
        };
        emptyLogger.createChild = () => emptyLogger;
        const recorder = (event) => {
            let eventPath = event.path.startsWith('/')
                ? event.path.slice(1)
                : event.path;
            if (event.kind === 'error') {
            }
            else if (event.kind === 'update') {
                if (eventPath === 'angular.json') {
                    saveProjectsConfigurationsInWrappedSchematic(host, event.content.toString());
                }
                else {
                    host.write(eventPath, event.content);
                }
            }
            else if (event.kind === 'create') {
                host.write(eventPath, event.content);
            }
            else if (event.kind === 'delete') {
                host.delete(eventPath);
            }
            else if (event.kind === 'rename') {
                host.rename(eventPath, event.to);
            }
        };
        const fsHost = new NxScopeHostUsedForWrappedSchematics(host.root, host);
        const options = {
            generatorOptions,
            dryRun: true,
            interactive: false,
            help: false,
            debug: false,
            collectionName,
            generatorName,
            force: false,
            defaults: false,
        };
        const workflow = createWorkflow(fsHost, host.root, options);
        // used for testing
        if (collectionResolutionOverrides) {
            const r = workflow.engineHost.resolve;
            workflow.engineHost.resolve = (collection, b, c) => {
                if (collectionResolutionOverrides[collection]) {
                    return collectionResolutionOverrides[collection];
                }
                else {
                    return r.apply(workflow.engineHost, [collection, b, c]);
                }
            };
        }
        const collection = getCollection(workflow, collectionName);
        const schematic = collection.createSchematic(generatorName, true);
        const res = yield runSchematic(fsHost, host.root, workflow, emptyLogger, options, schematic, false, recorder);
        if (res.status !== 0) {
            throw new Error(res.loggingQueue.join('\n'));
        }
    });
}
exports.wrapAngularDevkitSchematic = wrapAngularDevkitSchematic;
let logger;
const getLogger = (isVerbose = false) => {
    if (!logger) {
        logger = (0, node_1.createConsoleLogger)(isVerbose, process.stdout, process.stderr, {
            warn: (s) => chalk.bold(chalk.yellow(s)),
            error: (s) => {
                if (s.startsWith('NX ')) {
                    return `\n${logger_1.NX_ERROR} ${chalk.bold(chalk.red(s.slice(3)))}\n`;
                }
                return chalk.bold(chalk.red(s));
            },
            info: (s) => {
                if (s.startsWith('NX ')) {
                    return `\n${logger_1.NX_PREFIX} ${chalk.bold(s.slice(3))}\n`;
                }
                return chalk.white(s);
            },
        });
    }
    return logger;
};
exports.getLogger = getLogger;
function saveProjectsConfigurationsInWrappedSchematic(host, content) {
    const projects = (0, angular_json_1.toNewFormat)((0, json_1.parseJson)(content)).projects;
    const existingProjects = (0, project_configuration_1.getProjects)(host);
    const existingAngularJson = host.exists('angular.json')
        ? (0, json_2.readJson)(host, 'angular.json')
        : null;
    const projectsInAngularJson = existingAngularJson
        ? Object.keys(existingAngularJson.projects)
        : [];
    const newAngularJson = { projects: {} };
    Object.keys(projects).forEach((projectName) => {
        if (projectsInAngularJson.includes(projectName)) {
            newAngularJson.projects[projectName] = projects[projectName];
        }
        else {
            if (existingProjects.has(projectName)) {
                (0, project_configuration_1.updateProjectConfiguration)(host, projectName, projects[projectName]);
            }
            else {
                (0, project_configuration_1.addProjectConfiguration)(host, projectName, projects[projectName]);
            }
        }
    });
    if (Object.keys(newAngularJson.projects).length > 0) {
        host.write('angular.json', JSON.stringify((0, angular_json_1.toOldFormat)(newAngularJson), null, 2));
    }
}
//# sourceMappingURL=ngcli-adapter.js.map