"use strict";
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.runCommander = exports.templateFile = exports.templateDir = exports.templatePath = exports.createFromTemplate = exports.componentCreator = exports.moduleCreator = exports.createModule = exports.projectCreator = exports.createProject = exports.monorepoCreator = exports.createMonorepo = exports.apiCreator = exports.serverSdkCreator = exports.clientSdkCreator = exports.withSpinner = void 0;
const path_1 = __importDefault(require("path"));
const fs_extra_1 = __importDefault(require("fs-extra"));
const ora_1 = __importDefault(require("ora"));
const chalk_1 = __importDefault(require("chalk"));
const ansi_escapes_1 = require("ansi-escapes");
const commander_1 = __importDefault(require("commander"));
const tmp_1 = __importDefault(require("tmp"));
const hatch_util_1 = require("@launchtray/hatch-util");
const replace_in_file_1 = __importDefault(require("replace-in-file"));
const child_process_1 = require("child_process");
const rush_lib_1 = require("@microsoft/rush-lib");
const comment_json_1 = require("comment-json");
const yaml_1 = __importDefault(require("yaml"));
const types_1 = require("yaml/types");
const dotenv_1 = __importDefault(require("dotenv"));
const withSpinner = (message, task) => __awaiter(void 0, void 0, void 0, function* () {
    const spinner = (0, ora_1.default)(message).start();
    try {
        yield task();
    }
    finally {
        spinner.stop();
        process.stdout.write(ansi_escapes_1.eraseLine);
    }
});
exports.withSpinner = withSpinner;
const log = (message) => {
    // eslint-disable-next-line no-console -- intentional log to console
    console.log(message);
};
const logError = (message) => {
    // eslint-disable-next-line no-console -- intentional log to console
    console.error(chalk_1.default.red(message));
};
const determinePackageName = ({ overriddenName, apiPackageName, desiredSuffix, isServer }) => {
    let projectName;
    if (apiPackageName != null && overriddenName == null) {
        // First remove 'api' suffix, so we don't have redundant service-api-sdk
        projectName = apiPackageName
            .replace(/([-./])api$/, `$1${isServer ? 'server' : 'client'}$1${desiredSuffix}`);
        if (!projectName.endsWith(desiredSuffix)) {
            projectName = `${projectName}-${isServer ? 'server' : 'client'}-${desiredSuffix}`;
        }
    }
    else {
        projectName = overriddenName;
    }
    return projectName;
};
const createSdk = (parentDirectory, projectFolder, sdkOptions) => {
    if (sdkOptions.dependency == null && sdkOptions.spec == null) {
        throw new Error('Dependency or input spec must be specified');
    }
    if (sdkOptions.spec != null && sdkOptions.name == null) {
        throw new Error('Name must be specified when generating an SDK from an input spec');
    }
    const projectName = determinePackageName({
        overriddenName: sdkOptions.name,
        apiPackageName: sdkOptions.dependency,
        desiredSuffix: 'sdk',
        isServer: sdkOptions.isServer,
    });
    return (0, exports.createProject)(parentDirectory, projectName, projectFolder, { sdkOptions });
};
const sdkCreator = (parentDirectory, projectFolder, isServer) => {
    return (sdkOptions) => {
        return createSdk(parentDirectory, projectFolder, Object.assign(Object.assign({}, sdkOptions), { isServer }));
    };
};
const clientSdkCreator = (parentDirectory, projectFolder) => {
    return sdkCreator(parentDirectory, projectFolder, false);
};
exports.clientSdkCreator = clientSdkCreator;
const serverSdkCreator = (parentDirectory, projectFolder) => {
    return sdkCreator(parentDirectory, projectFolder, true);
};
exports.serverSdkCreator = serverSdkCreator;
const apiCreator = (parentDirectory, projectFolder) => {
    return (projectName, apiOptions) => {
        return (0, exports.createProject)(parentDirectory, projectName, projectFolder, { apiOptions });
    };
};
exports.apiCreator = apiCreator;
const createMonorepo = (parentDirectory, monorepoName) => __awaiter(void 0, void 0, void 0, function* () {
    if (monorepoName == null) {
        throw new Error('Monorepo name must be specified');
    }
    const monorepoPath = `${process.cwd()}/${monorepoName}`;
    yield (0, exports.createFromTemplate)({
        srcPath: (0, exports.templateDir)(parentDirectory),
        dstPath: monorepoPath,
        name: monorepoName,
        templateType: 'monorepo',
    });
    log(chalk_1.default.green(`Created '${monorepoPath}' monorepo`));
});
exports.createMonorepo = createMonorepo;
const monorepoCreator = (parentDirectory) => {
    return (monorepoName) => {
        return (0, exports.createMonorepo)(parentDirectory, monorepoName);
    };
};
exports.monorepoCreator = monorepoCreator;
const createProject = (parentDirectory, projectName, projectFolder, options) => __awaiter(void 0, void 0, void 0, function* () {
    if (projectName == null) {
        throw new Error('Package name must be specified');
    }
    const projectPath = `${process.cwd()}/${projectName}`;
    const { dstPath, inMonorepo } = yield (0, exports.createFromTemplate)(Object.assign({ srcPath: (0, exports.templateDir)(parentDirectory), dstPath: projectPath, name: projectName, templateType: 'project', projectFolder }, options));
    log(chalk_1.default.green(`Created '${dstPath}'`));
    if (inMonorepo) {
        log('Now might be a good time run to `rush update`');
    }
    else {
        log('Now might be a good time to cd into the project and install dependencies (e.g. via npm, yarn)');
    }
});
exports.createProject = createProject;
const projectCreator = (parentDirectory, projectFolder) => {
    return (projectName) => {
        return (0, exports.createProject)(parentDirectory, projectName, projectFolder);
    };
};
exports.projectCreator = projectCreator;
const createModule = (parentDirectory, moduleName, extension = 'ts') => __awaiter(void 0, void 0, void 0, function* () {
    if (moduleName == null) {
        throw new Error('Module name must be specified');
    }
    const modulePath = `${process.cwd()}/${moduleName}.${extension}`;
    yield (0, exports.createFromTemplate)({
        srcPath: (0, exports.templateFile)(parentDirectory, extension),
        dstPath: modulePath,
        name: moduleName,
    });
    log(chalk_1.default.green(`Created '${modulePath}'`));
});
exports.createModule = createModule;
const moduleCreator = (parentDirectory, extension = 'ts') => {
    return (moduleName) => {
        return (0, exports.createModule)(parentDirectory, moduleName, extension);
    };
};
exports.moduleCreator = moduleCreator;
const componentCreator = (parentDirectory) => {
    return (moduleName) => __awaiter(void 0, void 0, void 0, function* () {
        yield (0, exports.createModule)(parentDirectory, moduleName, 'tsx');
        yield (0, exports.createModule)(path_1.default.resolve(parentDirectory, '../story/'), moduleName, 'stories.tsx');
    });
};
exports.componentCreator = componentCreator;
const toShortName = (name) => {
    const components = name.split('/');
    return components[components.length - 1];
};
const toEnvName = (shortName) => {
    return shortName.replace(/[^a-zA-Z0-9]/g, '_').toUpperCase();
};
const toDockerServiceName = (shortName, isStaticServer) => {
    return (isStaticServer ? '_static_' : '') + shortName;
};
const toPortName = (shortName, isStaticServer) => {
    const envName = toEnvName(shortName);
    return `${(isStaticServer ? '_STATIC_' : '') + envName}_PORT`;
};
const createDockerService = (doc, shortName, isStaticServer) => {
    const serviceName = toDockerServiceName(shortName, isStaticServer);
    const service = {
        key: serviceName,
        value: {
            build: {
                context: '.',
                target: (isStaticServer ? 'static-server' : 'production-app'),
                // eslint-disable-next-line @typescript-eslint/naming-convention -- following typical ARG naming convention
                args: { APP_NAME: shortName },
            },
            ports: [`\${${toPortName(shortName, isStaticServer)}}:80`],
        },
    };
    if (!isStaticServer) {
        service.value.env_file = [
            './containers.env',
        ];
        service.value.environment = {
            // eslint-disable-next-line @typescript-eslint/naming-convention -- following typical ENV_VAR naming convention
            STATIC_ASSETS_BASE_URL: `http://localhost:\${${toPortName(shortName, true)}}`,
        };
    }
    const pair = new types_1.Pair(service.key, service.value);
    if (!doc.has('services')) {
        const services = new types_1.Pair('services', new types_1.YAMLMap());
        services.spaceBefore = true;
        doc.addIn([], services);
    }
    else {
        const services = doc.get('services');
        if (typeof services !== 'object'
            || typeof (services.toJSON()) !== 'object'
            || Array.isArray(services.toJSON())) {
            throw new Error('Existing docker-compose.yaml file has an invalid `services` definition');
        }
        pair.spaceBefore = true;
    }
    doc.addIn(['services'], pair);
};
const MIN_PORT = 3002; // Start after default dev server ports
const MAX_PORT = 65535;
// Finds ports which:
// - are not already used in the .env file
// - are back to back
// This allows for the ports to be used for both prod and
// dev, where (port + 1) is used for the static file server
// and the webpack server, respectively.
const findAvailablePortPair = (dotEnv) => {
    const dotEnvPorts = {};
    const foundPorts = [];
    for (const key of Object.keys(dotEnv)) {
        if (key.endsWith('_PORT')) {
            const port = parseInt(dotEnv[key], 10);
            if (!Number.isNaN(port) && port >= MIN_PORT && port <= MAX_PORT) {
                dotEnvPorts[port] = true;
            }
        }
    }
    for (let port = MIN_PORT; port <= MAX_PORT; port++) {
        if (dotEnvPorts[port] == null && dotEnvPorts[port + 1] == null) {
            foundPorts.push(port);
            port += 1;
            foundPorts.push(port);
            break;
        }
    }
    return foundPorts;
};
const parseDotEnv = (dotEnvPath) => {
    if (!fs_extra_1.default.existsSync(dotEnvPath)) {
        fs_extra_1.default.createFileSync(dotEnvPath);
    }
    return dotenv_1.default.parse(fs_extra_1.default.readFileSync(dotEnvPath));
};
const appendToFile = (filePath, lines) => {
    if (!fs_extra_1.default.existsSync(filePath)) {
        fs_extra_1.default.createFileSync(filePath);
    }
    const stream = fs_extra_1.default.createWriteStream(filePath, { flags: 'a' });
    try {
        for (const line of lines) {
            stream.write(`${line}\n`);
        }
    }
    finally {
        stream.end();
    }
};
const HATCH_SERVER_TEMPLATE_NAMES = ['webapp', 'microservice'];
const updateDockerComposition = (templateName, monorepoRootDir, shortName) => __awaiter(void 0, void 0, void 0, function* () {
    if (HATCH_SERVER_TEMPLATE_NAMES.includes(templateName)) {
        const dockerComposePath = path_1.default.resolve(monorepoRootDir, 'docker-compose.yaml');
        let dockerComposeDocument;
        if (fs_extra_1.default.existsSync(dockerComposePath)) {
            const dockerComposeFile = fs_extra_1.default.readFileSync(dockerComposePath, 'utf8');
            dockerComposeDocument = yaml_1.default.parseDocument(dockerComposeFile);
            const asJSON = dockerComposeDocument.toJSON();
            const asString = dockerComposeDocument.toString();
            if (asJSON == null
                || asString == null
                || asString.trim() === ''
                || typeof asJSON !== 'object'
                || Array.isArray(asJSON)) {
                dockerComposeDocument = new yaml_1.default.Document(new types_1.YAMLMap());
            }
        }
        else {
            dockerComposeDocument = new yaml_1.default.Document(new types_1.YAMLMap());
        }
        if (!dockerComposeDocument.has('version')) {
            const version = new types_1.Pair('version', '3.8');
            dockerComposeDocument.addIn([], version);
        }
        createDockerService(dockerComposeDocument, shortName, false);
        createDockerService(dockerComposeDocument, shortName, true);
        fs_extra_1.default.writeFileSync(dockerComposePath, dockerComposeDocument.toString());
        const dotEnvPath = path_1.default.resolve(monorepoRootDir, '.env');
        const dotEnv = parseDotEnv(dotEnvPath);
        const ports = findAvailablePortPair(dotEnv);
        appendToFile(dotEnvPath, [
            `${toPortName(shortName, false)}=${ports[0]}`,
            `${toPortName(shortName, true)}=${ports[1]}`,
        ]);
        const prodEnvPath = path_1.default.resolve(monorepoRootDir, 'containers.env');
        appendToFile(prodEnvPath, [
            `${toEnvName(shortName)}_BASE_URL=http://${toDockerServiceName(shortName, false)}`,
        ]);
        const devEnvPath = path_1.default.resolve(monorepoRootDir, 'dev.env');
        appendToFile(devEnvPath, [
            `${toEnvName(shortName)}_BASE_URL=http://localhost:$${toPortName(shortName, false)}`,
        ]);
    }
});
const updateVersionPolicies = (monorepoPath) => {
    const versionPoliciesPath = path_1.default.resolve(monorepoPath, 'common', 'config', 'rush', 'version-policies.json');
    const versionPoliciesRaw = fs_extra_1.default.readFileSync(versionPoliciesPath).toString();
    const versionPoliciesParsed = (0, comment_json_1.parse)(versionPoliciesRaw);
    versionPoliciesParsed.push({
        definitionName: 'individualVersion',
        policyName: 'libraries',
    });
    versionPoliciesParsed.push({
        definitionName: 'individualVersion',
        policyName: 'tools',
    });
    const versionPoliciesRawUpdated = (0, comment_json_1.stringify)(versionPoliciesParsed, null, 2);
    fs_extra_1.default.writeFileSync(versionPoliciesPath, versionPoliciesRawUpdated);
};
const updatePnpmConfig = (monorepoPath) => {
    const pnpmConfigPath = path_1.default.resolve(monorepoPath, 'common', 'config', 'rush', 'pnpm-config.json');
    const pnpmConfigRaw = fs_extra_1.default.readFileSync(pnpmConfigPath).toString();
    const pnpmConfigParsed = (0, comment_json_1.parse)(pnpmConfigRaw);
    pnpmConfigParsed.strictPeerDependencies = true;
    pnpmConfigParsed.useWorkspaces = true;
    pnpmConfigParsed.globalOverrides = Object.assign(Object.assign({}, pnpmConfigParsed.globalOverrides), { 'fork-ts-checker-webpack-plugin': '6.4.0', 'postcss@^8.2': '8.2.12' });
    const pnpmConfigRawUpdated = (0, comment_json_1.stringify)(pnpmConfigParsed, null, 2);
    fs_extra_1.default.writeFileSync(pnpmConfigPath, pnpmConfigRawUpdated);
};
const updateCustomCommands = (monorepoPath) => {
    const commandLinePath = path_1.default.resolve(monorepoPath, 'common', 'config', 'rush', 'command-line.json');
    const commandLineRaw = fs_extra_1.default.readFileSync(commandLinePath).toString();
    const commandLineParsed = (0, comment_json_1.parse)(commandLineRaw);
    commandLineParsed.commands.push({
        commandKind: 'global',
        name: 'start',
        summary: 'Runs all apps locally in dev mode',
        description: 'This command will run all hatch servers locally in development mode',
        safeForSimultaneousRushProcesses: true,
        shellCommand: 'bash ./run-apps-in-dev-mode',
    });
    commandLineParsed.commands.push({
        commandKind: 'global',
        name: 'start:containers',
        summary: 'Runs all apps locally via Docker',
        description: 'This command will run all hatch servers locally as production builds in Docker',
        safeForSimultaneousRushProcesses: true,
        shellCommand: 'bash ./run-apps-in-containers',
    });
    commandLineParsed.commands.push({
        commandKind: 'bulk',
        name: 'test',
        summary: 'Runs all project\'s package.json test script hook commands if defined',
        description: 'This command will run all project\'s package.json test script hook commands if defined',
        enableParallelism: true,
        ignoreMissingScript: true,
    });
    commandLineParsed.commands.push({
        commandKind: 'bulk',
        name: 'lint:fix',
        summary: 'Fixes all lint issues',
        description: 'Fixes lint issues for all projects via lint:fix npm script',
        enableParallelism: true,
        safeForSimultaneousRushProcesses: false,
        ignoreDependencyOrder: true,
        ignoreMissingScript: true,
        allowWarningsInSuccessfulBuild: true,
    });
    commandLineParsed.commands.push({
        commandKind: 'bulk',
        name: 'lint',
        summary: 'Lints all projects',
        description: 'Finds lint issues for all projects via lint npm script',
        enableParallelism: true,
        safeForSimultaneousRushProcesses: false,
        ignoreDependencyOrder: true,
        ignoreMissingScript: true,
        allowWarningsInSuccessfulBuild: true,
    });
    commandLineParsed.commands.push({
        name: 'build:watch',
        commandKind: 'bulk',
        summary: 'Build projects and watch for changes',
        description: 'For details, see the article "Using watch mode" on the Rush website: https://rushjs.io/',
        incremental: true,
        enableParallelism: true,
        ignoreMissingScript: true,
        watchForChanges: true,
    });
    const commandLineRawUpdated = (0, comment_json_1.stringify)(commandLineParsed, null, 2);
    fs_extra_1.default.writeFileSync(commandLinePath, commandLineRawUpdated);
};
const patchPackageJson = (tempFilePath, 
// eslint-disable-next-line @typescript-eslint/no-explicit-any
packageModifier) => {
    var _a;
    const packagePath = path_1.default.resolve(tempFilePath, 'package.json');
    const packageString = fs_extra_1.default.readFileSync(packagePath).toString();
    const packageParsed = (0, comment_json_1.parse)(packageString);
    const returnedPackage = (_a = packageModifier(packageParsed)) !== null && _a !== void 0 ? _a : packageParsed;
    const packageUpdated = (0, comment_json_1.stringify)(returnedPackage, null, 2);
    fs_extra_1.default.writeFileSync(packagePath, packageUpdated);
};
const generateSdk = (sdkOptions, tempFilePath, rushConfigParsed, monorepoRootDir) => {
    patchPackageJson(tempFilePath, (packageParsed) => {
        var _a;
        const sdkPackageParsed = packageParsed;
        let dependencyVersion = sdkOptions.ver;
        if (sdkOptions.dependency != null && dependencyVersion == null) {
            const dependencyProject = (_a = rushConfigParsed === null || rushConfigParsed === void 0 ? void 0 : rushConfigParsed.projects) === null || _a === void 0 ? void 0 : _a.find((project) => {
                return project.packageName === sdkOptions.dependency;
            });
            const dependencyProjectFolder = dependencyProject === null || dependencyProject === void 0 ? void 0 : dependencyProject.projectFolder;
            if (dependencyProjectFolder != null && monorepoRootDir != null) {
                const dependencyPackagePath = path_1.default.resolve(monorepoRootDir, dependencyProjectFolder, 'package.json');
                const dependencyPackage = fs_extra_1.default.readFileSync(dependencyPackagePath).toString();
                const dependencyPackageParsed = (0, comment_json_1.parse)(dependencyPackage);
                dependencyVersion = dependencyPackageParsed === null || dependencyPackageParsed === void 0 ? void 0 : dependencyPackageParsed.version;
            }
        }
        if (dependencyVersion == null) {
            dependencyVersion = 'latest';
        }
        const cliName = sdkOptions.isServer ? 'hatch-server-sdk' : 'hatch-client-sdk';
        if (sdkOptions.dependency != null) {
            sdkPackageParsed.devDependencies[sdkOptions.dependency] = dependencyVersion;
            sdkPackageParsed.scripts.build = `rimraf dist && ${cliName} --dependency ${sdkOptions.dependency} && tsc`;
            sdkPackageParsed.scripts['build:watch'] = `${cliName} --dependency ${sdkOptions.dependency} && tsc`;
        }
        else if (sdkOptions.spec != null) {
            sdkPackageParsed.scripts.build = `rimraf dist && ${cliName} --spec ${sdkOptions.spec} && tsc`;
            sdkPackageParsed.scripts['build:watch'] = `${cliName} --spec ${sdkOptions.spec} && tsc`;
        }
    });
};
const patchApiPackage = (apiOptions, tempFilePath) => {
    patchPackageJson(tempFilePath, (packageParsed) => {
        const apiPackage = packageParsed;
        const { specType } = apiOptions;
        if (specType != null && specType !== 'spot') {
            apiPackage.scripts.build = apiPackage.scripts.build.replace('--spec src/api-overrides.yaml --spot src/api.ts', `--spec src/api.${apiOptions.specType}`);
            apiPackage.scripts['build:watch'] = apiPackage.scripts['build:watch'].replace('--spec src/api-overrides.yaml --spot src/api.ts', `--spec src/api.${apiOptions.specType}`);
            fs_extra_1.default.removeSync(path_1.default.resolve(tempFilePath, 'src', 'api.ts'));
            fs_extra_1.default.removeSync(path_1.default.resolve(tempFilePath, 'src', 'api-overrides.yaml'));
            if (specType === 'json') {
                fs_extra_1.default.removeSync(path_1.default.resolve(tempFilePath, 'src', 'api.yaml'));
            }
            else {
                fs_extra_1.default.removeSync(path_1.default.resolve(tempFilePath, 'src', 'api.json'));
            }
        }
        else {
            fs_extra_1.default.removeSync(path_1.default.resolve(tempFilePath, 'src', 'api.yaml'));
            fs_extra_1.default.removeSync(path_1.default.resolve(tempFilePath, 'src', 'api.json'));
        }
    });
};
const createFromTemplate = ({ srcPath, dstPath, name, templateType, projectFolder, sdkOptions, apiOptions }) => __awaiter(void 0, void 0, void 0, function* () {
    let adjustedDstPath = dstPath;
    let inMonorepo = false;
    const templateName = path_1.default.basename(path_1.default.dirname(path_1.default.resolve(srcPath)));
    let rushConfigPath;
    let monorepoRootDir;
    if (templateType === 'project' && projectFolder != null) {
        rushConfigPath = rush_lib_1.RushConfiguration.tryFindRushJsonLocation({ startingFolder: dstPath });
        if (rushConfigPath != null) {
            inMonorepo = true;
            monorepoRootDir = path_1.default.dirname(rushConfigPath);
            adjustedDstPath = path_1.default.resolve(monorepoRootDir, projectFolder, toShortName(name));
        }
    }
    if (fs_extra_1.default.existsSync(adjustedDstPath)) {
        throw new Error(`Failed to create ${adjustedDstPath} as it already exits!`);
    }
    // eslint-disable-next-line complexity -- refactor on future addition / cleanup
    yield (0, exports.withSpinner)(`Creating '${name}'`, () => __awaiter(void 0, void 0, void 0, function* () {
        const tempFileFuture = new hatch_util_1.CompletableFuture();
        if (templateType === 'monorepo' || templateType === 'project') {
            tmp_1.default.dir({ unsafeCleanup: true }, (err, tmpPath, cleanUp) => {
                if (err != null) {
                    cleanUp();
                    tempFileFuture.completeExceptionally(err);
                }
                tempFileFuture.complete([tmpPath, cleanUp]);
            });
        }
        else {
            tmp_1.default.file((err, tmpPath, fd, cleanUp) => {
                if (err != null) {
                    cleanUp();
                    tempFileFuture.completeExceptionally(err);
                }
                tempFileFuture.complete([tmpPath, cleanUp]);
            });
        }
        const [tempFilePath, cleanUp] = yield tempFileFuture.get();
        try {
            if (templateType === 'monorepo') {
                const rushExecutable = path_1.default.resolve(__dirname, '../node_modules/.bin/rush');
                const rushInitCmd = (0, child_process_1.spawnSync)(rushExecutable, ['init'], { encoding: 'utf8', cwd: tempFilePath });
                if (rushInitCmd.error != null) {
                    throw new Error(`Error initializing monorepo: ${rushInitCmd.error.message}`);
                }
                // remove files that will be replaced by template
                const rushGitIgnorePath = path_1.default.resolve(tempFilePath, '.gitignore');
                if (fs_extra_1.default.existsSync(rushGitIgnorePath)) {
                    yield fs_extra_1.default.remove(rushGitIgnorePath);
                }
                const templateRushConfigPath = path_1.default.resolve(tempFilePath, 'rush.json');
                if (fs_extra_1.default.existsSync(templateRushConfigPath)) {
                    yield fs_extra_1.default.remove(templateRushConfigPath);
                }
                updateVersionPolicies(tempFilePath);
                updateCustomCommands(tempFilePath);
                updatePnpmConfig(tempFilePath);
            }
            yield fs_extra_1.default.copy(srcPath, tempFilePath);
            if (templateType === 'monorepo') {
                // Replace template names with generated project name
                yield (0, replace_in_file_1.default)({
                    files: `${tempFilePath}/**/*`,
                    from: /HATCH_CLI_TEMPLATE_VAR_monorepoName/g,
                    to: name,
                });
                // Rename hidden / project files
                const imlPath = path_1.default.resolve(tempFilePath, 'dot-idea', 'HATCH_CLI_TEMPLATE_VAR_monorepoName.iml');
                if (fs_extra_1.default.existsSync(imlPath)) {
                    yield fs_extra_1.default.move(imlPath, path_1.default.resolve(tempFilePath, 'dot-idea', `${name}.iml`));
                }
                const dotIdeaPath = path_1.default.resolve(tempFilePath, 'dot-idea');
                if (fs_extra_1.default.existsSync(dotIdeaPath)) {
                    yield fs_extra_1.default.move(dotIdeaPath, path_1.default.resolve(tempFilePath, '.idea'));
                }
                const dotGitIgnorePath = path_1.default.resolve(tempFilePath, 'dot-gitignore');
                if (fs_extra_1.default.existsSync(dotGitIgnorePath)) {
                    yield fs_extra_1.default.move(dotGitIgnorePath, path_1.default.resolve(tempFilePath, '.gitignore'));
                }
                const dotNpmIgnorePath = path_1.default.resolve(tempFilePath, 'dot-npmignore');
                if (fs_extra_1.default.existsSync(dotNpmIgnorePath)) {
                    yield fs_extra_1.default.move(dotNpmIgnorePath, path_1.default.resolve(tempFilePath, '.npmignore'));
                }
                const dotDockerIgnorePath = path_1.default.resolve(tempFilePath, 'dot-dockerignore');
                if (fs_extra_1.default.existsSync(dotDockerIgnorePath)) {
                    yield fs_extra_1.default.move(dotDockerIgnorePath, path_1.default.resolve(tempFilePath, '.dockerignore'));
                }
                const dotEslintRcPath = path_1.default.resolve(tempFilePath, 'dot-eslintrc.js');
                if (fs_extra_1.default.existsSync(dotEslintRcPath)) {
                    yield fs_extra_1.default.move(dotEslintRcPath, path_1.default.resolve(tempFilePath, '.eslintrc.js'));
                }
            }
            else if (templateType === 'project') {
                // Delete files that might be copied over if this is a local dev install
                const nodeModulesPath = path_1.default.resolve(tempFilePath, 'node_modules');
                if (fs_extra_1.default.existsSync(nodeModulesPath)) {
                    yield fs_extra_1.default.remove(nodeModulesPath);
                }
                const distPath = path_1.default.resolve(tempFilePath, 'dist');
                if (fs_extra_1.default.existsSync(distPath)) {
                    yield fs_extra_1.default.remove(distPath);
                }
                const rushPath = path_1.default.resolve(tempFilePath, '.rush');
                if (fs_extra_1.default.existsSync(rushPath)) {
                    yield fs_extra_1.default.remove(rushPath);
                }
                // Replace template names with generated project name
                yield (0, replace_in_file_1.default)({
                    files: `${tempFilePath}/**/*`,
                    from: /HATCH_CLI_TEMPLATE_VAR_projectName/g,
                    to: name,
                });
                yield (0, replace_in_file_1.default)({
                    files: `${tempFilePath}/**/*`,
                    from: /HATCH_CLI_TEMPLATE_VAR_projectShortName/g,
                    to: toShortName(name),
                });
                yield (0, replace_in_file_1.default)({
                    files: `${tempFilePath}/package.json`,
                    from: `@launchtray/hatch-template-${templateName}`,
                    to: name,
                });
                yield (0, replace_in_file_1.default)({
                    files: `${tempFilePath}/package.json`,
                    from: /workspace:\*/g,
                    to: `${require('../package.json').version}`,
                });
                // Handle hidden / project files
                const tsconfigExtendsPath = path_1.default.resolve(tempFilePath, 'tsconfig-extends');
                if (fs_extra_1.default.existsSync(tsconfigExtendsPath)) {
                    if (inMonorepo) {
                        yield fs_extra_1.default.move(tsconfigExtendsPath, path_1.default.resolve(tempFilePath, 'tsconfig.json'), { overwrite: true });
                    }
                    else {
                        yield fs_extra_1.default.remove(tsconfigExtendsPath);
                    }
                }
                const imlPath = path_1.default.resolve(tempFilePath, 'dot-idea', 'HATCH_CLI_TEMPLATE_VAR_projectShortName.iml');
                if (fs_extra_1.default.existsSync(imlPath)) {
                    if (inMonorepo) {
                        yield fs_extra_1.default.remove(imlPath);
                    }
                    else {
                        yield fs_extra_1.default.move(imlPath, path_1.default.resolve(tempFilePath, 'dot-idea', `${toShortName(name)}.iml`));
                    }
                }
                const dotIdeaPath = path_1.default.resolve(tempFilePath, 'dot-idea');
                if (fs_extra_1.default.existsSync(dotIdeaPath)) {
                    if (inMonorepo) {
                        yield fs_extra_1.default.remove(dotIdeaPath);
                    }
                    else {
                        yield fs_extra_1.default.move(dotIdeaPath, path_1.default.resolve(tempFilePath, '.idea'));
                    }
                }
                const dotStorybookPath = path_1.default.resolve(tempFilePath, 'dot-storybook');
                if (fs_extra_1.default.existsSync(dotStorybookPath)) {
                    yield fs_extra_1.default.move(dotStorybookPath, path_1.default.resolve(tempFilePath, '.storybook'));
                }
                const dotGitIgnorePath = path_1.default.resolve(tempFilePath, 'dot-gitignore');
                if (fs_extra_1.default.existsSync(dotGitIgnorePath)) {
                    yield fs_extra_1.default.move(dotGitIgnorePath, path_1.default.resolve(tempFilePath, '.gitignore'));
                }
                const dotNpmIgnorePath = path_1.default.resolve(tempFilePath, 'dot-npmignore');
                if (fs_extra_1.default.existsSync(dotNpmIgnorePath)) {
                    yield fs_extra_1.default.move(dotNpmIgnorePath, path_1.default.resolve(tempFilePath, '.npmignore'));
                }
                // .eslintrc.js will be copied over, but in some cases we might want the template directory within hatch
                // to have different rules than the hatched project. In these cases, dot-eslintrc.js can be defined.
                const dotEslintRcPath = path_1.default.resolve(tempFilePath, 'dot-eslintrc.js');
                if (fs_extra_1.default.existsSync(dotEslintRcPath)) {
                    const destEslintPath = path_1.default.resolve(tempFilePath, '.eslintrc.js');
                    if (fs_extra_1.default.existsSync(destEslintPath)) {
                        yield fs_extra_1.default.remove(destEslintPath);
                    }
                    yield fs_extra_1.default.move(dotEslintRcPath, destEslintPath);
                }
                const dotDockerIgnorePath = path_1.default.resolve(tempFilePath, 'dot-dockerignore');
                if (fs_extra_1.default.existsSync(dotDockerIgnorePath)) {
                    if (inMonorepo) {
                        yield fs_extra_1.default.remove(dotDockerIgnorePath);
                    }
                    else {
                        yield fs_extra_1.default.move(dotDockerIgnorePath, path_1.default.resolve(tempFilePath, '.dockerignore'));
                    }
                }
                const dockerfilePath = path_1.default.resolve(tempFilePath, 'Dockerfile');
                if (fs_extra_1.default.existsSync(dockerfilePath) && inMonorepo) {
                    yield fs_extra_1.default.remove(dockerfilePath);
                }
                const testPath = path_1.default.resolve(tempFilePath, 'src', '__test__', 'HATCH_CLI_TEMPLATE_VAR_projectShortName.test.ts');
                if (fs_extra_1.default.existsSync(testPath)) {
                    yield fs_extra_1.default.move(testPath, path_1.default.resolve(tempFilePath, 'src', '__test__', `${toShortName(name)}.test.ts`));
                }
                if (rushConfigPath != null && monorepoRootDir != null && projectFolder != null) {
                    patchPackageJson(tempFilePath, (packageParsed) => {
                        var _a;
                        const updatedPackage = packageParsed;
                        if (((_a = packageParsed.scripts) === null || _a === void 0 ? void 0 : _a.start) != null) {
                            updatedPackage.scripts['start:no-watch'] = packageParsed.scripts.start;
                            updatedPackage.scripts.start = `rush build:watch --to-except ${name} & rushx start:no-watch`;
                        }
                        return updatedPackage;
                    });
                    const rushConfigRaw = fs_extra_1.default.readFileSync(rushConfigPath).toString();
                    const rushConfigParsed = (0, comment_json_1.parse)(rushConfigRaw);
                    if (sdkOptions != null) {
                        generateSdk(sdkOptions, tempFilePath, rushConfigParsed, monorepoRootDir);
                    }
                    const projectRelativePath = path_1.default.join(projectFolder, toShortName(name));
                    const project = {
                        packageName: name,
                        projectFolder: projectRelativePath,
                    };
                    if (projectFolder === 'libraries') {
                        project.versionPolicyName = 'libraries';
                    }
                    else if (projectFolder === 'tools') {
                        project.versionPolicyName = 'tools';
                    }
                    else {
                        project.shouldPublish = false;
                    }
                    rushConfigParsed.projects.push(project);
                    const rushConfigRawUpdated = (0, comment_json_1.stringify)(rushConfigParsed, null, 2);
                    fs_extra_1.default.writeFileSync(rushConfigPath, rushConfigRawUpdated);
                    yield updateDockerComposition(templateName, monorepoRootDir, toShortName(name));
                }
                else if (projectFolder != null && sdkOptions != null) {
                    generateSdk(sdkOptions, tempFilePath);
                }
                if (apiOptions != null) {
                    patchApiPackage(apiOptions, tempFilePath);
                }
            }
            else {
                yield (0, replace_in_file_1.default)({
                    files: tempFilePath,
                    from: /HATCH_CLI_TEMPLATE_VAR_moduleName/g,
                    to: name,
                });
            }
            yield fs_extra_1.default.copy(tempFilePath, adjustedDstPath);
        }
        finally {
            cleanUp();
        }
    }));
    return { dstPath: adjustedDstPath, inMonorepo };
});
exports.createFromTemplate = createFromTemplate;
const templatePath = (parentDirectory, templateFile) => {
    return path_1.default.resolve(parentDirectory, '../../../src/templates/', path_1.default.basename(parentDirectory), templateFile);
};
exports.templatePath = templatePath;
const templateDir = (parentDirectory, dirName = 'template') => {
    return (0, exports.templatePath)(parentDirectory, dirName);
};
exports.templateDir = templateDir;
const templateFile = (parentDirectory, extension = 'ts', moduleName = 'template') => {
    return (0, exports.templatePath)(parentDirectory, `${moduleName}.${extension}`);
};
exports.templateFile = templateFile;
const runCommander = () => {
    commander_1.default.parseAsync(process.argv)
        .catch((err) => {
        logError(err.message);
        commander_1.default.help();
    });
};
exports.runCommander = runCommander;
//# sourceMappingURL=util.js.map