"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.deployToCloudRun = exports.deployToFunction = void 0;
const architect_1 = require("@angular-devkit/architect");
const fs_1 = require("fs");
const fs_extra_1 = require("fs-extra");
const path_1 = require("path");
const child_process_1 = require("child_process");
const functions_templates_1 = require("./functions-templates");
const semver_1 = require("semver");
const open_1 = __importDefault(require("open"));
const schematics_1 = require("@angular-devkit/schematics");
const versions_json_1 = require("../versions.json");
const winston = __importStar(require("winston"));
const triple_beam_1 = __importDefault(require("triple-beam"));
const inquirer = __importStar(require("inquirer"));
const DEFAULT_EMULATOR_PORT = 5000;
const DEFAULT_EMULATOR_HOST = 'localhost';
const DEFAULT_CLOUD_RUN_OPTIONS = {
    memory: '1Gi',
    timeout: 60,
    maxInstances: 'default',
    maxConcurrency: 'default',
    minInstances: 'default',
    cpus: 1,
};
const spawnAsync = (command, options) => __awaiter(void 0, void 0, void 0, function* () {
    return new Promise((resolve, reject) => {
        const [spawnCommand, ...args] = command.split(/\s+/);
        const spawnProcess = child_process_1.spawn(spawnCommand, args, options);
        const chunks = [];
        const errorChunks = [];
        spawnProcess.stdout.on('data', (data) => {
            process.stdout.write(data.toString());
            chunks.push(data);
        });
        spawnProcess.stderr.on('data', (data) => {
            process.stderr.write(data.toString());
            errorChunks.push(data);
        });
        spawnProcess.on('error', (error) => {
            reject(error);
        });
        spawnProcess.on('close', (code) => {
            if (code === 1) {
                reject(Buffer.concat(errorChunks).toString());
                return;
            }
            resolve(Buffer.concat(chunks));
        });
    });
});
const escapeRegExp = (str) => str.replace(/[\-\[\]\/{}()*+?.\\^$|]/g, '\\$&');
const moveSync = (src, dest) => {
    fs_extra_1.copySync(src, dest);
    fs_extra_1.removeSync(src);
};
const deployToHosting = (firebaseTools, context, workspaceRoot, options, firebaseToken) => __awaiter(void 0, void 0, void 0, function* () {
    var _a;
    const siteTarget = (_a = options.target) !== null && _a !== void 0 ? _a : context.target.project;
    if (options.preview) {
        yield firebaseTools.serve({
            port: DEFAULT_EMULATOR_PORT,
            host: DEFAULT_EMULATOR_HOST,
            only: `hosting:${siteTarget}`,
            nonInteractive: true,
            projectRoot: workspaceRoot,
        });
        const { deployProject } = yield inquirer.prompt({
            type: 'confirm',
            name: 'deployProject',
            message: 'Would you like to deploy your application to Firebase Hosting?'
        });
        if (!deployProject) {
            return;
        }
        process.env.FIREBASE_FRAMEWORKS_SKIP_BUILD = 'true';
    }
    return yield firebaseTools.deploy({
        only: `hosting:${siteTarget}`,
        cwd: workspaceRoot,
        token: firebaseToken,
        nonInteractive: true,
        projectRoot: workspaceRoot,
    });
});
const defaultFsHost = {
    moveSync,
    writeFileSync: fs_1.writeFileSync,
    renameSync: fs_1.renameSync,
    copySync: fs_extra_1.copySync,
    removeSync: fs_extra_1.removeSync,
    existsSync: fs_1.existsSync,
};
const findPackageVersion = (packageManager, name) => {
    const match = child_process_1.execSync(`${packageManager} list ${name}`).toString().match(`[^|\s]${escapeRegExp(name)}[@| ][^\s]+(\s.+)?$`);
    return match ? match[0].split(new RegExp(`${escapeRegExp(name)}[@| ]`))[1].split(/\s/)[0] : null;
};
const getPackageJson = (context, workspaceRoot, options, main) => {
    var _a, _b, _c, _d, _e;
    const dependencies = {};
    const devDependencies = {};
    if (options.ssr !== 'cloud-run') {
        Object.keys(versions_json_1.firebaseFunctionsDependencies).forEach(name => {
            const { version, dev } = versions_json_1.firebaseFunctionsDependencies[name];
            (dev ? devDependencies : dependencies)[name] = version;
        });
    }
    if (fs_1.existsSync(path_1.join(workspaceRoot, 'angular.json'))) {
        const angularJson = JSON.parse(fs_1.readFileSync(path_1.join(workspaceRoot, 'angular.json')).toString());
        const packageManager = (_b = (_a = angularJson.cli) === null || _a === void 0 ? void 0 : _a.packageManager) !== null && _b !== void 0 ? _b : 'npm';
        const server = angularJson.projects[context.target.project].architect.server;
        const externalDependencies = ((_c = server === null || server === void 0 ? void 0 : server.options) === null || _c === void 0 ? void 0 : _c.externalDependencies) || [];
        const bundleDependencies = (_e = (_d = server === null || server === void 0 ? void 0 : server.options) === null || _d === void 0 ? void 0 : _d.bundleDependencies) !== null && _e !== void 0 ? _e : true;
        if (bundleDependencies) {
            externalDependencies.forEach(externalDependency => {
                const packageVersion = findPackageVersion(packageManager, externalDependency);
                if (packageVersion) {
                    dependencies[externalDependency] = packageVersion;
                }
            });
        }
        else {
            if (fs_1.existsSync(path_1.join(workspaceRoot, 'package.json'))) {
                const packageJson = JSON.parse(fs_1.readFileSync(path_1.join(workspaceRoot, 'package.json')).toString());
                Object.keys(packageJson.dependencies).forEach((dependency) => {
                    dependencies[dependency] = packageJson.dependencies[dependency];
                });
            }
        }
    }
    return functions_templates_1.defaultPackage(dependencies, devDependencies, options, main);
};
const deployToFunction = (firebaseTools, context, workspaceRoot, staticBuildTarget, serverBuildTarget, options, firebaseToken, fsHost = defaultFsHost) => __awaiter(void 0, void 0, void 0, function* () {
    var _b;
    const staticBuildOptions = yield context.getTargetOptions(architect_1.targetFromTargetString(staticBuildTarget.name));
    if (!staticBuildOptions.outputPath || typeof staticBuildOptions.outputPath !== 'string') {
        throw new Error(`Cannot read the output path option of the Angular project '${staticBuildTarget.name}' in angular.json`);
    }
    const serverBuildOptions = yield context.getTargetOptions(architect_1.targetFromTargetString(serverBuildTarget.name));
    if (!serverBuildOptions.outputPath || typeof serverBuildOptions.outputPath !== 'string') {
        throw new Error(`Cannot read the output path option of the Angular project '${serverBuildTarget.name}' in angular.json`);
    }
    const staticOut = path_1.join(workspaceRoot, staticBuildOptions.outputPath);
    const serverOut = path_1.join(workspaceRoot, serverBuildOptions.outputPath);
    const functionsOut = options.outputPath ? path_1.join(workspaceRoot, options.outputPath) : path_1.dirname(serverOut);
    const functionName = options.functionName || functions_templates_1.DEFAULT_FUNCTION_NAME;
    const newStaticOut = path_1.join(functionsOut, staticBuildOptions.outputPath);
    const newServerOut = path_1.join(functionsOut, serverBuildOptions.outputPath);
    if (options.outputPath) {
        fsHost.removeSync(functionsOut);
        fsHost.copySync(staticOut, newStaticOut);
        fsHost.copySync(serverOut, newServerOut);
    }
    else {
        fsHost.moveSync(staticOut, newStaticOut);
        fsHost.moveSync(serverOut, newServerOut);
    }
    const packageJson = getPackageJson(context, workspaceRoot, options);
    const nodeVersion = packageJson.engines.node;
    if (!semver_1.satisfies(process.versions.node, nodeVersion.toString())) {
        context.logger.warn(`⚠️ Your Node.js version (${process.versions.node}) does not match the Firebase Functions runtime (${nodeVersion}).`);
    }
    const functionsPackageJsonPath = path_1.join(functionsOut, 'package.json');
    fsHost.writeFileSync(functionsPackageJsonPath, JSON.stringify(packageJson, null, 2));
    if (options.CF3v2) {
        fsHost.writeFileSync(path_1.join(functionsOut, 'index.js'), functions_templates_1.functionGen2(serverBuildOptions.outputPath, options, functionName));
    }
    else {
        fsHost.writeFileSync(path_1.join(functionsOut, 'index.js'), functions_templates_1.defaultFunction(serverBuildOptions.outputPath, options, functionName));
    }
    if (!options.prerender) {
        try {
            fsHost.renameSync(path_1.join(newStaticOut, 'index.html'), path_1.join(newStaticOut, 'index.original.html'));
        }
        catch (e) { }
    }
    const siteTarget = (_b = options.target) !== null && _b !== void 0 ? _b : context.target.project;
    if (fsHost.existsSync(functionsPackageJsonPath)) {
        child_process_1.execSync(`npm --prefix ${functionsOut} install`);
    }
    else {
        console.error(`No package.json exists at ${functionsOut}`);
    }
    if (options.preview) {
        yield firebaseTools.serve({
            port: DEFAULT_EMULATOR_PORT,
            host: DEFAULT_EMULATOR_HOST,
            targets: [`hosting:${siteTarget}`, `functions:${functionName}`],
            nonInteractive: true,
            projectRoot: workspaceRoot,
        });
        const { deployProject } = yield inquirer.prompt({
            type: 'confirm',
            name: 'deployProject',
            message: 'Would you like to deploy your application to Firebase Hosting & Cloud Functions?'
        });
        if (!deployProject) {
            return;
        }
    }
    return yield firebaseTools.deploy({
        only: `hosting:${siteTarget},functions:${functionName}`,
        cwd: workspaceRoot,
        token: firebaseToken,
        nonInteractive: true,
        projectRoot: workspaceRoot,
    });
});
exports.deployToFunction = deployToFunction;
const deployToCloudRun = (firebaseTools, context, workspaceRoot, staticBuildTarget, serverBuildTarget, options, firebaseToken, fsHost = defaultFsHost) => __awaiter(void 0, void 0, void 0, function* () {
    var _c;
    const staticBuildOptions = yield context.getTargetOptions(architect_1.targetFromTargetString(staticBuildTarget.name));
    if (!staticBuildOptions.outputPath || typeof staticBuildOptions.outputPath !== 'string') {
        throw new Error(`Cannot read the output path option of the Angular project '${staticBuildTarget.name}' in angular.json`);
    }
    const serverBuildOptions = yield context.getTargetOptions(architect_1.targetFromTargetString(serverBuildTarget.name));
    if (!serverBuildOptions.outputPath || typeof serverBuildOptions.outputPath !== 'string') {
        throw new Error(`Cannot read the output path option of the Angular project '${serverBuildTarget.name}' in angular.json`);
    }
    const staticOut = path_1.join(workspaceRoot, staticBuildOptions.outputPath);
    const serverOut = path_1.join(workspaceRoot, serverBuildOptions.outputPath);
    const cloudRunOut = options.outputPath ? path_1.join(workspaceRoot, options.outputPath) : path_1.join(path_1.dirname(serverOut), 'run');
    const serviceId = options.functionName || functions_templates_1.DEFAULT_FUNCTION_NAME;
    const newStaticOut = path_1.join(cloudRunOut, staticBuildOptions.outputPath);
    const newServerOut = path_1.join(cloudRunOut, serverBuildOptions.outputPath);
    fsHost.removeSync(cloudRunOut);
    fsHost.copySync(staticOut, newStaticOut);
    fsHost.copySync(serverOut, newServerOut);
    const packageJson = getPackageJson(context, workspaceRoot, options, path_1.join(serverBuildOptions.outputPath, 'main.js'));
    const nodeVersion = packageJson.engines.node;
    if (!semver_1.satisfies(process.versions.node, nodeVersion.toString())) {
        context.logger.warn(`⚠️ Your Node.js version (${process.versions.node}) does not match the Cloud Run runtime (${nodeVersion}).`);
    }
    fsHost.writeFileSync(path_1.join(cloudRunOut, 'package.json'), JSON.stringify(packageJson, null, 2));
    fsHost.writeFileSync(path_1.join(cloudRunOut, 'Dockerfile'), functions_templates_1.dockerfile(options));
    if (!options.prerender) {
        try {
            fsHost.renameSync(path_1.join(newStaticOut, 'index.html'), path_1.join(newStaticOut, 'index.original.html'));
        }
        catch (e) { }
    }
    if (options.preview) {
        throw new schematics_1.SchematicsException('Cloud Run preview not supported.');
    }
    const deployArguments = [];
    const cloudRunOptions = options.cloudRunOptions || {};
    Object.entries(DEFAULT_CLOUD_RUN_OPTIONS).forEach(([k, v]) => {
        cloudRunOptions[k] || (cloudRunOptions[k] = v);
    });
    if (cloudRunOptions.cpus) {
        deployArguments.push('--cpu', cloudRunOptions.cpus);
    }
    if (cloudRunOptions.maxConcurrency) {
        deployArguments.push('--concurrency', cloudRunOptions.maxConcurrency);
    }
    if (cloudRunOptions.maxInstances) {
        deployArguments.push('--max-instances', cloudRunOptions.maxInstances);
    }
    if (cloudRunOptions.memory) {
        deployArguments.push('--memory', cloudRunOptions.memory);
    }
    if (cloudRunOptions.minInstances) {
        deployArguments.push('--min-instances', cloudRunOptions.minInstances);
    }
    if (cloudRunOptions.timeout) {
        deployArguments.push('--timeout', cloudRunOptions.timeout);
    }
    if (cloudRunOptions.vpcConnector) {
        deployArguments.push('--vpc-connector', cloudRunOptions.vpcConnector);
    }
    context.logger.info(`📦 Deploying to Cloud Run`);
    yield spawnAsync(`gcloud builds submit ${cloudRunOut} --tag gcr.io/${options.firebaseProject}/${serviceId} --project ${options.firebaseProject} --quiet`);
    yield spawnAsync(`gcloud run deploy ${serviceId} --image gcr.io/${options.firebaseProject}/${serviceId} --project ${options.firebaseProject} ${deployArguments.join(' ')} --platform managed --allow-unauthenticated --region=${options.region} --quiet`);
    const siteTarget = (_c = options.target) !== null && _c !== void 0 ? _c : context.target.project;
    return yield firebaseTools.deploy({
        only: `hosting:${siteTarget}`,
        cwd: workspaceRoot,
        token: firebaseToken,
        nonInteractive: true,
        projectRoot: workspaceRoot,
    });
});
exports.deployToCloudRun = deployToCloudRun;
function deploy(firebaseTools, context, staticBuildTarget, serverBuildTarget, prerenderBuildTarget, firebaseProject, options, firebaseToken) {
    return __awaiter(this, void 0, void 0, function* () {
        if (!firebaseToken) {
            yield firebaseTools.login();
            const user = yield firebaseTools.login({ projectRoot: context.workspaceRoot });
            console.log(`Logged into Firebase as ${user.email}.`);
        }
        if (options.version && options.version >= 2) {
            if (semver_1.lt(firebaseTools.cli.version(), '12.2')) {
                throw new schematics_1.SchematicsException('firebase-tools version 12.2+ is required.');
            }
            process.env.FIREBASE_FRAMEWORK_BUILD_TARGET = (prerenderBuildTarget || serverBuildTarget || staticBuildTarget).name;
        }
        else {
            if (prerenderBuildTarget) {
                const run = yield context.scheduleTarget(architect_1.targetFromTargetString(prerenderBuildTarget.name), prerenderBuildTarget.options);
                yield run.result;
            }
            else {
                if (!context.target) {
                    throw new Error('Cannot execute the build target');
                }
                context.logger.info(`📦 Building "${context.target.project}"`);
                const builders = [
                    context.scheduleTarget(architect_1.targetFromTargetString(staticBuildTarget.name), staticBuildTarget.options).then(run => run.result)
                ];
                if (serverBuildTarget) {
                    builders.push(context.scheduleTarget(architect_1.targetFromTargetString(serverBuildTarget.name), serverBuildTarget.options).then(run => run.result));
                }
                yield Promise.all(builders);
            }
        }
        try {
            yield firebaseTools.use(firebaseProject, {
                project: firebaseProject,
                projectRoot: context.workspaceRoot,
            });
        }
        catch (e) {
            throw new Error(`Cannot select firebase project '${firebaseProject}'`);
        }
        options.firebaseProject = firebaseProject;
        const logger = new winston.transports.Console({
            level: 'info',
            format: winston.format.printf((info) => {
                var _a, _b, _c, _d;
                const emulator = (_c = (_b = (_a = info[triple_beam_1.default.SPLAT]) === null || _a === void 0 ? void 0 : _a[1]) === null || _b === void 0 ? void 0 : _b.metadata) === null || _c === void 0 ? void 0 : _c.emulator;
                const text = (_d = info[triple_beam_1.default.SPLAT]) === null || _d === void 0 ? void 0 : _d[0];
                if (text === null || text === void 0 ? void 0 : text.replace) {
                    const plainText = text.replace(/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[mGK]/g, '');
                    if ((emulator === null || emulator === void 0 ? void 0 : emulator.name) === 'hosting' && plainText.startsWith('Local server: ')) {
                        open_1.default(plainText.split(': ')[1]);
                    }
                }
                return [info.message, ...(info[triple_beam_1.default.SPLAT] || [])]
                    .filter((chunk) => typeof chunk === 'string')
                    .join(' ');
            })
        });
        firebaseTools.logger.logger.add(logger);
        if ((!options.version || options.version < 2) && serverBuildTarget) {
            if (options.ssr === 'cloud-run') {
                yield exports.deployToCloudRun(firebaseTools, context, context.workspaceRoot, staticBuildTarget, serverBuildTarget, options, firebaseToken);
            }
            else {
                yield exports.deployToFunction(firebaseTools, context, context.workspaceRoot, staticBuildTarget, serverBuildTarget, options, firebaseToken);
            }
        }
        else {
            yield deployToHosting(firebaseTools, context, context.workspaceRoot, options, firebaseToken);
        }
    });
}
exports.default = deploy;
