"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (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 () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createVendureApp = createVendureApp;
const prompts_1 = require("@clack/prompts");
const commander_1 = require("commander");
const fs_extra_1 = __importDefault(require("fs-extra"));
const node_child_process_1 = require("node:child_process");
const promises_1 = require("node:timers/promises");
const open_1 = __importDefault(require("open"));
const os_1 = __importDefault(require("os"));
const path_1 = __importDefault(require("path"));
const picocolors_1 = __importDefault(require("picocolors"));
const constants_1 = require("./constants");
const gather_user_responses_1 = require("./gather-user-responses");
const helpers_1 = require("./helpers");
const logger_1 = require("./logger");
// eslint-disable-next-line @typescript-eslint/no-var-requires
const packageJson = require('../package.json');
(0, helpers_1.checkNodeVersion)(constants_1.REQUIRED_NODE_VERSION);
let projectName;
// Set the environment variable which can then be used to
// conditionally modify behaviour of core or plugins.
const createEnvVar = 'CREATING_VENDURE_APP';
process.env[createEnvVar] = 'true';
commander_1.program
    .version(packageJson.version)
    .arguments('<project-directory>')
    .usage(`${picocolors_1.default.green('<project-directory>')} [options]`)
    .action(name => {
    projectName = name;
})
    .option('--log-level <logLevel>', "Log level, either 'silent', 'info', or 'verbose'", /^(silent|info|verbose)$/i, 'info')
    .option('--verbose', 'Alias for --log-level verbose', false)
    .option('--use-npm', 'Uses npm rather than as the default package manager. DEPRECATED: Npm is now the default')
    .option('--ci', 'Runs without prompts for use in CI scenarios', false)
    .parse(process.argv);
const options = commander_1.program.opts();
void createVendureApp(projectName, options.useNpm, options.verbose ? 'verbose' : options.logLevel || 'info', options.ci).catch(err => {
    (0, logger_1.log)(err);
    process.exit(1);
});
async function createVendureApp(name, useNpm, logLevel, isCi = false) {
    var _a, _b;
    (0, logger_1.setLogLevel)(logLevel);
    if (!runPreChecks(name, useNpm)) {
        return;
    }
    (0, prompts_1.intro)(`Let's create a ${picocolors_1.default.blue(picocolors_1.default.bold('Vendure App'))} ✨ ${picocolors_1.default.dim(`v${packageJson.version}`)}`);
    const mode = isCi
        ? 'ci'
        : (await (0, prompts_1.select)({
            message: 'How should we proceed?',
            options: [
                { label: 'Quick Start', value: 'quick', hint: 'Get up an running in a single step' },
                {
                    label: 'Manual Configuration',
                    value: 'manual',
                    hint: 'Customize your Vendure project with more advanced settings',
                },
            ],
            initialValue: 'quick',
        }));
    (0, helpers_1.checkCancel)(mode);
    const portSpinner = (0, prompts_1.spinner)();
    let port = constants_1.SERVER_PORT;
    const attemptedPortRange = 20;
    portSpinner.start(`Establishing port...`);
    while (await (0, helpers_1.isServerPortInUse)(port)) {
        const nextPort = port + 1;
        portSpinner.message(picocolors_1.default.yellow(`Port ${port} is in use. Attempting to use ${nextPort}`));
        port = nextPort;
        if (port > constants_1.SERVER_PORT + attemptedPortRange) {
            portSpinner.stop(picocolors_1.default.red('Could not find an available port'));
            (0, prompts_1.outro)(`Please ensure there is a port available between ${constants_1.SERVER_PORT} and ${constants_1.SERVER_PORT + attemptedPortRange}`);
            process.exit(1);
        }
    }
    portSpinner.stop(`Using port ${port}`);
    process.env.PORT = port.toString();
    const root = path_1.default.resolve(name);
    const appName = path_1.default.basename(root);
    const scaffoldExists = (0, helpers_1.scaffoldAlreadyExists)(root, name);
    const packageManager = 'npm';
    if (scaffoldExists) {
        (0, logger_1.log)(picocolors_1.default.yellow('It appears that a new Vendure project scaffold already exists. Re-using the existing files...'), { newline: 'after' });
    }
    const { dbType, configSource, envSource, envDtsSource, indexSource, indexWorkerSource, readmeSource, dockerfileSource, dockerComposeSource, populateProducts, } = mode === 'ci'
        ? await (0, gather_user_responses_1.getCiConfiguration)(root, packageManager)
        : mode === 'manual'
            ? await (0, gather_user_responses_1.getManualConfiguration)(root, packageManager)
            : await (0, gather_user_responses_1.getQuickStartConfiguration)(root, packageManager);
    process.chdir(root);
    if (packageManager !== 'npm' && !(0, helpers_1.checkThatNpmCanReadCwd)()) {
        process.exit(1);
    }
    const packageJsonContents = {
        name: appName,
        version: '0.1.0',
        private: true,
        scripts: {
            'dev:server': 'ts-node ./src/index.ts',
            'dev:worker': 'ts-node ./src/index-worker.ts',
            dev: 'concurrently npm:dev:*',
            build: 'tsc',
            'start:server': 'node ./dist/index.js',
            'start:worker': 'node ./dist/index-worker.js',
            start: 'concurrently npm:start:*',
        },
    };
    const setupSpinner = (0, prompts_1.spinner)();
    setupSpinner.start(`Setting up your new Vendure project in ${picocolors_1.default.green(root)}\nThis may take a few minutes...`);
    const srcPathScript = (fileName) => path_1.default.join(root, 'src', `${fileName}.ts`);
    fs_extra_1.default.writeFileSync(path_1.default.join(root, 'package.json'), JSON.stringify(packageJsonContents, null, 2) + os_1.default.EOL);
    const { dependencies, devDependencies } = (0, helpers_1.getDependencies)(dbType, `@${packageJson.version}`);
    setupSpinner.stop(`Created ${picocolors_1.default.green('package.json')}`);
    const installSpinner = (0, prompts_1.spinner)();
    installSpinner.start(`Installing ${dependencies[0]} + ${dependencies.length - 1} more dependencies`);
    try {
        await (0, helpers_1.installPackages)({ dependencies, logLevel });
    }
    catch (e) {
        (0, prompts_1.outro)(picocolors_1.default.red(`Failed to inst all dependencies. Please try again.`));
        process.exit(1);
    }
    installSpinner.stop(`Successfully installed ${dependencies.length} dependencies`);
    if (devDependencies.length) {
        const installDevSpinner = (0, prompts_1.spinner)();
        installDevSpinner.start(`Installing ${devDependencies[0]} + ${devDependencies.length - 1} more dev dependencies`);
        try {
            await (0, helpers_1.installPackages)({ dependencies: devDependencies, isDevDependencies: true, logLevel });
        }
        catch (e) {
            (0, prompts_1.outro)(picocolors_1.default.red(`Failed to install dev dependencies. Please try again.`));
            process.exit(1);
        }
        installDevSpinner.stop(`Successfully installed ${devDependencies.length} dev dependencies`);
    }
    const scaffoldSpinner = (0, prompts_1.spinner)();
    scaffoldSpinner.start(`Generating app scaffold`);
    // We add this pause so that the above output is displayed before the
    // potentially lengthy file operations begin, which can prevent that
    // from displaying and thus make the user think that the process has hung.
    await (0, promises_1.setTimeout)(500);
    fs_extra_1.default.ensureDirSync(path_1.default.join(root, 'src'));
    const assetPath = (fileName) => path_1.default.join(__dirname, '../assets', fileName);
    const configFile = srcPathScript('vendure-config');
    try {
        await fs_extra_1.default
            .writeFile(configFile, configSource)
            .then(() => fs_extra_1.default.writeFile(path_1.default.join(root, '.env'), envSource))
            .then(() => fs_extra_1.default.writeFile(srcPathScript('environment.d'), envDtsSource))
            .then(() => fs_extra_1.default.writeFile(srcPathScript('index'), indexSource))
            .then(() => fs_extra_1.default.writeFile(srcPathScript('index-worker'), indexWorkerSource))
            .then(() => fs_extra_1.default.writeFile(path_1.default.join(root, 'README.md'), readmeSource))
            .then(() => fs_extra_1.default.writeFile(path_1.default.join(root, 'Dockerfile'), dockerfileSource))
            .then(() => fs_extra_1.default.writeFile(path_1.default.join(root, 'docker-compose.yml'), dockerComposeSource))
            .then(() => fs_extra_1.default.ensureDir(path_1.default.join(root, 'src/plugins')))
            .then(() => fs_extra_1.default.copyFile(assetPath('gitignore.template'), path_1.default.join(root, '.gitignore')))
            .then(() => fs_extra_1.default.copyFile(assetPath('tsconfig.template.json'), path_1.default.join(root, 'tsconfig.json')))
            .then(() => fs_extra_1.default.copyFile(assetPath('tsconfig.dashboard.template.json'), path_1.default.join(root, 'tsconfig.dashboard.json')))
            .then(() => fs_extra_1.default.copyFile(assetPath('vite.config.template.mts'), path_1.default.join(root, 'vite.config.mts')))
            .then(() => createDirectoryStructure(root))
            .then(() => copyEmailTemplates(root));
    }
    catch (e) {
        (0, prompts_1.outro)(picocolors_1.default.red(`Failed to create app scaffold: ${e.message}`));
        process.exit(1);
    }
    scaffoldSpinner.stop(`Generated app scaffold`);
    if (mode === 'quick' && dbType === 'postgres') {
        (0, helpers_1.cleanUpDockerResources)(name);
        await (0, helpers_1.startPostgresDatabase)(root);
    }
    const populateSpinner = (0, prompts_1.spinner)();
    populateSpinner.start(`Initializing your new Vendure server`);
    // We want to display a set of tips and instructions to the user
    // as the initialization process is running because it can take
    // a few minutes to complete.
    const tips = [
        populateProducts
            ? 'We are populating sample data so that you can start testing right away'
            : 'We are setting up your Vendure server',
        '☕ This can take a minute or two, so grab a coffee',
        `✨ We'd love it if you drop us a star on GitHub: https://github.com/vendure-ecommerce/vendure`,
        `📖 Check out the Vendure documentation at https://docs.vendure.io`,
        `💬 Join our Discord community to chat with other Vendure developers: https://vendure.io/community`,
        '💡 In the mean time, here are some tips to get you started',
        `Vendure provides dedicated GraphQL APIs for both the Admin and Shop`,
        `Almost every aspect of Vendure is customizable via plugins`,
        `You can run 'vendure add' from the command line to add new plugins & features`,
        `Use the EventBus in your plugins to react to events in the system`,
        `Vendure supports multiple languages & currencies out of the box`,
        `☕ Did we mention this can take a while?`,
        `Our custom fields feature allows you to add any kind of data to your entities`,
        `Vendure is built with TypeScript, so you get full type safety`,
        `Combined with GraphQL's static schema, your type safety is end-to-end`,
        `☕ Almost there now... thanks for your patience!`,
        `Collections allow you to group products together`,
        `Our AssetServerPlugin allows you to dynamically resize & optimize images`,
        `Order flows are fully customizable to suit your business requirements`,
        `Role-based permissions allow you to control access to every part of the system`,
        `Customers can be grouped for targeted promotions & custom pricing`,
        `You can find integrations in the Vendure Hub: https://vendure.io/hub`,
    ];
    let tipIndex = 0;
    let timer;
    const tipInterval = 10000;
    function displayTip() {
        populateSpinner.message(tips[tipIndex]);
        tipIndex++;
        if (tipIndex >= tips.length) {
            // skip the intro tips if looping
            tipIndex = 3;
        }
        timer = setTimeout(displayTip, tipInterval);
    }
    timer = setTimeout(displayTip, tipInterval);
    // register ts-node so that the config file can be loaded
    // eslint-disable-next-line @typescript-eslint/no-var-requires
    require((0, helpers_1.resolvePackageRootDir)('ts-node', root)).register();
    let superAdminCredentials;
    try {
        const { populate } = await Promise.resolve(`${path_1.default.join((0, helpers_1.resolvePackageRootDir)('@vendure/core', root), 'cli', 'populate')}`).then(s => __importStar(require(s)));
        const { bootstrap, DefaultLogger, LogLevel, JobQueueService } = await Promise.resolve(`${path_1.default.join((0, helpers_1.resolvePackageRootDir)('@vendure/core', root), 'dist', 'index')}`).then(s => __importStar(require(s)));
        const { config } = await Promise.resolve(`${configFile}`).then(s => __importStar(require(s)));
        const assetsDir = path_1.default.join(__dirname, '../assets');
        superAdminCredentials = config.authOptions.superadminCredentials;
        const initialDataPath = path_1.default.join(assetsDir, 'initial-data.json');
        const vendureLogLevel = logLevel === 'info' || logLevel === 'silent'
            ? LogLevel.Error
            : logLevel === 'verbose'
                ? LogLevel.Verbose
                : LogLevel.Info;
        const bootstrapFn = async () => {
            var _a;
            await (0, helpers_1.checkDbConnection)(config.dbConnectionOptions, root);
            const _app = await bootstrap(Object.assign(Object.assign({}, config), { apiOptions: Object.assign(Object.assign({}, ((_a = config.apiOptions) !== null && _a !== void 0 ? _a : {})), { port }), dbConnectionOptions: Object.assign(Object.assign({}, config.dbConnectionOptions), { synchronize: true }), logger: new DefaultLogger({ level: vendureLogLevel }), importExportOptions: {
                    importAssetsDir: path_1.default.join(assetsDir, 'images'),
                } }));
            await _app.get(JobQueueService).start();
            return _app;
        };
        const app = await populate(bootstrapFn, initialDataPath, populateProducts ? path_1.default.join(assetsDir, 'products.csv') : undefined);
        // Pause to ensure the worker jobs have time to complete.
        if (isCi) {
            (0, logger_1.log)('[CI] Pausing before close...');
        }
        await (0, promises_1.setTimeout)(isCi ? 30000 : 2000);
        await app.close();
        if (isCi) {
            (0, logger_1.log)('[CI] Pausing after close...');
            await (0, promises_1.setTimeout)(10000);
        }
        populateSpinner.stop(`Server successfully initialized${populateProducts ? ' and populated' : ''}`);
        clearTimeout(timer);
        /**
         * This is currently disabled because I am running into issues actually getting the server
         * to quite properly in response to a SIGINT.
         * This means that the server runs, but cannot be ended, without forcefully
         * killing the process.
         *
         * Once this has been resolved, the following code can be re-enabled by
         * setting `autoRunServer` to `true`.
         */
        const autoRunServer = false;
        if (mode === 'quick' && autoRunServer) {
            // In quick-start mode, we want to now run the server and open up
            // a browser window to the Admin UI.
            try {
                const adminUiUrl = `http://localhost:${port}/admin`;
                const quickStartInstructions = [
                    'Use the following credentials to log in to the Admin UI:',
                    `Username: ${picocolors_1.default.green((_a = config.authOptions.superadminCredentials) === null || _a === void 0 ? void 0 : _a.identifier)}`,
                    `Password: ${picocolors_1.default.green((_b = config.authOptions.superadminCredentials) === null || _b === void 0 ? void 0 : _b.password)}`,
                    `Open your browser and navigate to: ${picocolors_1.default.green(adminUiUrl)}`,
                    '',
                ];
                (0, prompts_1.note)(quickStartInstructions.join('\n'));
                const npmCommand = os_1.default.platform() === 'win32' ? 'npm.cmd' : 'npm';
                let quickStartProcess;
                try {
                    quickStartProcess = (0, node_child_process_1.spawn)(npmCommand, ['run', 'dev'], {
                        cwd: root,
                        stdio: 'inherit',
                    });
                }
                catch (e) {
                    /* empty */
                }
                // process.stdin.resume();
                process.on('SIGINT', function () {
                    displayOutro(root, name, superAdminCredentials);
                    quickStartProcess === null || quickStartProcess === void 0 ? void 0 : quickStartProcess.kill('SIGINT');
                    process.exit(0);
                });
                // Give enough time for the server to get up and running
                // before opening the window.
                await (0, promises_1.setTimeout)(10000);
                try {
                    await (0, open_1.default)(adminUiUrl, {
                        newInstance: true,
                    });
                }
                catch (e) {
                    /* empty */
                }
            }
            catch (e) {
                (0, logger_1.log)(picocolors_1.default.red(`Failed to start the server: ${e.message}`), {
                    newline: 'after',
                    level: 'verbose',
                });
            }
        }
        else {
            clearTimeout(timer);
            displayOutro(root, name, superAdminCredentials);
            process.exit(0);
        }
    }
    catch (e) {
        (0, logger_1.log)(e.toString());
        (0, prompts_1.outro)(picocolors_1.default.red(`Failed to initialize server. Please try again.`));
        process.exit(1);
    }
}
function displayOutro(root, name, superAdminCredentials) {
    var _a, _b;
    const startCommand = 'npm run dev';
    const nextSteps = [
        `Your new Vendure server was created!`,
        picocolors_1.default.gray(root),
        `\n`,
        `Next, run:`,
        picocolors_1.default.gray('$ ') + picocolors_1.default.blue(picocolors_1.default.bold(`cd ${name}`)),
        picocolors_1.default.gray('$ ') + picocolors_1.default.blue(picocolors_1.default.bold(`${startCommand}`)),
        `\n`,
        `This will start the server in development mode.`,
        `To access the Admin UI, open your browser and navigate to:`,
        `\n`,
        picocolors_1.default.green(`http://localhost:3000/admin`),
        `\n`,
        `Use the following credentials to log in:`,
        `Username: ${picocolors_1.default.green((_a = superAdminCredentials === null || superAdminCredentials === void 0 ? void 0 : superAdminCredentials.identifier) !== null && _a !== void 0 ? _a : 'superadmin')}`,
        `Password: ${picocolors_1.default.green((_b = superAdminCredentials === null || superAdminCredentials === void 0 ? void 0 : superAdminCredentials.password) !== null && _b !== void 0 ? _b : 'superadmin')}`,
        '\n',
        '➡️ Docs: https://docs.vendure.io',
        '➡️ Discord community: https://vendure.io/community',
        '➡️ Star us on GitHub:',
        '   https://github.com/vendure-ecommerce/vendure',
    ];
    (0, prompts_1.note)(nextSteps.join('\n'), picocolors_1.default.green('Setup complete!'));
    (0, prompts_1.outro)(`Happy hacking!`);
}
/**
 * Run some initial checks to ensure that it is okay to proceed with creating
 * a new Vendure project in the given location.
 */
function runPreChecks(name, useNpm) {
    if (typeof name === 'undefined') {
        (0, logger_1.log)(picocolors_1.default.red(`Please specify the project directory:`));
        (0, logger_1.log)(`  ${picocolors_1.default.cyan(commander_1.program.name())} ${picocolors_1.default.green('<project-directory>')}`, { newline: 'after' });
        (0, logger_1.log)('For example:');
        (0, logger_1.log)(`  ${picocolors_1.default.cyan(commander_1.program.name())} ${picocolors_1.default.green('my-vendure-app')}`);
        process.exit(1);
        return false;
    }
    const root = path_1.default.resolve(name);
    try {
        fs_extra_1.default.ensureDirSync(name);
    }
    catch (e) {
        (0, logger_1.log)(picocolors_1.default.red(`Could not create project directory ${name}: ${e.message}`));
        return false;
    }
    if (!(0, helpers_1.isSafeToCreateProjectIn)(root, name)) {
        process.exit(1);
    }
    return true;
}
/**
 * Generate the default directory structure for a new Vendure project
 */
async function createDirectoryStructure(root) {
    await fs_extra_1.default.ensureDir(path_1.default.join(root, 'static', 'email', 'test-emails'));
    await fs_extra_1.default.ensureDir(path_1.default.join(root, 'static', 'assets'));
}
/**
 * Copy the email templates into the app
 */
async function copyEmailTemplates(root) {
    const emailPackageDirname = (0, helpers_1.resolvePackageRootDir)('@vendure/email-plugin', root);
    const templateDir = path_1.default.join(emailPackageDirname, 'templates');
    try {
        await fs_extra_1.default.copy(templateDir, path_1.default.join(root, 'static', 'email', 'templates'));
    }
    catch (err) {
        (0, logger_1.log)(picocolors_1.default.red('Failed to copy email templates.'));
        (0, logger_1.log)(err);
        process.exit(0);
    }
}
//# sourceMappingURL=create-vendure-app.js.map