"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 (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 __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.isServerPortInUse = exports.checkDbConnection = exports.getDependencies = exports.installPackages = exports.checkThatNpmCanReadCwd = exports.bunIsAvailable = exports.yarnIsAvailable = exports.checkNodeVersion = exports.scaffoldAlreadyExists = exports.isSafeToCreateProjectIn = void 0;
/* eslint-disable no-console */
const child_process_1 = require("child_process");
const cross_spawn_1 = __importDefault(require("cross-spawn"));
const fs_extra_1 = __importDefault(require("fs-extra"));
const path_1 = __importDefault(require("path"));
const picocolors_1 = __importDefault(require("picocolors"));
const semver_1 = __importDefault(require("semver"));
const constants_1 = require("./constants");
/**
 * If project only contains files generated by GH, it’s safe.
 * Also, if project contains remnant error logs from a previous
 * installation, lets remove them now.
 * We also special case IJ-based products .idea because it integrates with CRA:
 * https://github.com/facebook/create-react-app/pull/368#issuecomment-243446094
 */
function isSafeToCreateProjectIn(root, name) {
    // These files should be allowed to remain on a failed install,
    // but then silently removed during the next create.
    const errorLogFilePatterns = ['npm-debug.log', 'yarn-error.log', 'yarn-debug.log'];
    const validFiles = [
        '.DS_Store',
        'Thumbs.db',
        '.git',
        '.gitignore',
        '.idea',
        'README.md',
        'LICENSE',
        '.hg',
        '.hgignore',
        '.hgcheck',
        '.npmignore',
        'mkdocs.yml',
        'docs',
        '.travis.yml',
        '.gitlab-ci.yml',
        '.gitattributes',
        'migration.ts',
        'node_modules',
        'package.json',
        'package-lock.json',
        'src',
        'static',
        'tsconfig.json',
        'yarn.lock',
    ];
    console.log();
    const conflicts = fs_extra_1.default
        .readdirSync(root)
        .filter(file => !validFiles.includes(file))
        // IntelliJ IDEA creates module files before CRA is launched
        .filter(file => !/\.iml$/.test(file))
        // Don't treat log files from previous installation as conflicts
        .filter(file => !errorLogFilePatterns.some(pattern => file.indexOf(pattern) === 0));
    if (conflicts.length > 0) {
        console.log(`The directory ${picocolors_1.default.green(name)} contains files that could conflict:`);
        console.log();
        for (const file of conflicts) {
            console.log(`  ${file}`);
        }
        console.log();
        console.log('Either try using a new directory name, or remove the files listed above.');
        return false;
    }
    // Remove any remnant files from a previous installation
    const currentFiles = fs_extra_1.default.readdirSync(path_1.default.join(root));
    currentFiles.forEach(file => {
        errorLogFilePatterns.forEach(errorLogFilePattern => {
            // This will catch `(npm-debug|yarn-error|yarn-debug).log*` files
            if (file.indexOf(errorLogFilePattern) === 0) {
                fs_extra_1.default.removeSync(path_1.default.join(root, file));
            }
        });
    });
    return true;
}
exports.isSafeToCreateProjectIn = isSafeToCreateProjectIn;
function scaffoldAlreadyExists(root, name) {
    const scaffoldFiles = ['migration.ts', 'package.json', 'tsconfig.json', 'README.md'];
    const files = fs_extra_1.default.readdirSync(root);
    return scaffoldFiles.every(scaffoldFile => files.includes(scaffoldFile));
}
exports.scaffoldAlreadyExists = scaffoldAlreadyExists;
function checkNodeVersion(requiredVersion) {
    if (!semver_1.default.satisfies(process.version, requiredVersion)) {
        console.error(picocolors_1.default.red('You are running Node %s.\n' +
            'Vendure requires Node %s or higher. \n' +
            'Please update your version of Node.'), process.version, requiredVersion);
        process.exit(1);
    }
}
exports.checkNodeVersion = checkNodeVersion;
function yarnIsAvailable() {
    try {
        (0, child_process_1.execSync)('yarnpkg --version', { stdio: 'ignore' });
        return true;
    }
    catch (e) {
        return false;
    }
}
exports.yarnIsAvailable = yarnIsAvailable;
// Bun support should not be exposed yet, see
// https://github.com/oven-sh/bun/issues/4947
// https://github.com/lovell/sharp/issues/3511
function bunIsAvailable() {
    try {
        (0, child_process_1.execSync)('bun --version', { stdio: 'ignore' });
        return true;
    }
    catch (e) {
        return false;
    }
}
exports.bunIsAvailable = bunIsAvailable;
function checkThatNpmCanReadCwd() {
    const cwd = process.cwd();
    let childOutput = null;
    try {
        // Note: intentionally using spawn over exec since
        // the problem doesn't reproduce otherwise.
        // `npm config list` is the only reliable way I could find
        // to reproduce the wrong path. Just printing process.cwd()
        // in a Node process was not enough.
        childOutput = cross_spawn_1.default.sync('npm', ['config', 'list']).output.join('');
    }
    catch (err) {
        // Something went wrong spawning node.
        // Not great, but it means we can't do this check.
        // We might fail later on, but let's continue.
        return true;
    }
    if (typeof childOutput !== 'string') {
        return true;
    }
    const lines = childOutput.split('\n');
    // `npm config list` output includes the following line:
    // "; cwd = C:\path\to\current\dir" (unquoted)
    // I couldn't find an easier way to get it.
    const prefix = '; cwd = ';
    const line = lines.find(l => l.indexOf(prefix) === 0);
    if (typeof line !== 'string') {
        // Fail gracefully. They could remove it.
        return true;
    }
    const npmCWD = line.substring(prefix.length);
    if (npmCWD === cwd) {
        return true;
    }
    console.error(picocolors_1.default.red('Could not start an npm process in the right directory.\n\n' +
        `The current directory is: ${picocolors_1.default.bold(cwd)}\n` +
        `However, a newly started npm process runs in: ${picocolors_1.default.bold(npmCWD)}\n\n` +
        'This is probably caused by a misconfigured system terminal shell.'));
    if (process.platform === 'win32') {
        console.error(picocolors_1.default.red('On Windows, this can usually be fixed by running:\n\n') +
            `  ${picocolors_1.default.cyan('reg')} delete "HKCU\\Software\\Microsoft\\Command Processor" /v AutoRun /f\n` +
            `  ${picocolors_1.default.cyan('reg')} delete "HKLM\\Software\\Microsoft\\Command Processor" /v AutoRun /f\n\n` +
            picocolors_1.default.red('Try to run the above two lines in the terminal.\n') +
            picocolors_1.default.red('To learn more about this problem, read: https://blogs.msdn.microsoft.com/oldnewthing/20071121-00/?p=24433/'));
    }
    return false;
}
exports.checkThatNpmCanReadCwd = checkThatNpmCanReadCwd;
/**
 * Install packages via npm or yarn.
 * Based on the install function from https://github.com/facebook/create-react-app
 */
function installPackages(root, useYarn, dependencies, isDev, logLevel, isCi = false) {
    return new Promise((resolve, reject) => {
        let command;
        let args;
        if (useYarn) {
            command = 'yarnpkg';
            args = ['add', '--exact', '--ignore-engines'];
            if (isDev) {
                args.push('--dev');
            }
            if (isCi) {
                // In CI, publish to Verdaccio
                // See https://github.com/yarnpkg/yarn/issues/6029
                args.push('--registry http://localhost:4873/');
                // Increase network timeout
                // See https://github.com/yarnpkg/yarn/issues/4890#issuecomment-358179301
                args.push('--network-timeout 300000');
            }
            args = args.concat(dependencies);
            // Explicitly set cwd() to work around issues like
            // https://github.com/facebook/create-react-app/issues/3326.
            // Unfortunately we can only do this for Yarn because npm support for
            // equivalent --prefix flag doesn't help with this issue.
            // This is why for npm, we run checkThatNpmCanReadCwd() early instead.
            args.push('--cwd');
            args.push(root);
        }
        else {
            command = 'npm';
            args = ['install', '--save', '--save-exact', '--loglevel', 'error'].concat(dependencies);
            if (isDev) {
                args.push('--save-dev');
            }
        }
        if (logLevel === 'verbose') {
            args.push('--verbose');
        }
        const child = (0, cross_spawn_1.default)(command, args, { stdio: logLevel === 'silent' ? 'ignore' : 'inherit' });
        child.on('close', code => {
            if (code !== 0) {
                let message = 'An error occurred when installing dependencies.';
                if (logLevel === 'silent') {
                    message += ' Try running with `--log-level info` or `--log-level verbose` to diagnose.';
                }
                reject({
                    message,
                    command: `${command} ${args.join(' ')}`,
                });
                return;
            }
            resolve();
        });
    });
}
exports.installPackages = installPackages;
function getDependencies(dbType, vendurePkgVersion = '') {
    const dependencies = [
        `@vendure.ecommerce/core${vendurePkgVersion}`,
        `@vendure.ecommerce/email-plugin${vendurePkgVersion}`,
        `@vendure.ecommerce/asset-server-plugin${vendurePkgVersion}`,
        `@vendure.ecommerce/admin-ui-plugin${vendurePkgVersion}`,
        'dotenv',
        dbDriverPackage(dbType),
        `typescript@${constants_1.TYPESCRIPT_VERSION}`,
    ];
    const devDependencies = ['concurrently', 'ts-node'];
    return { dependencies, devDependencies };
}
exports.getDependencies = getDependencies;
/**
 * Returns the name of the npm driver package for the
 * selected database.
 */
function dbDriverPackage(dbType) {
    switch (dbType) {
        case 'mysql':
        case 'mariadb':
            return 'mysql';
        case 'postgres':
            return 'pg';
        case 'sqlite':
            return 'better-sqlite3';
        case 'sqljs':
            return 'sql.js';
        case 'mssql':
            return 'mssql';
        case 'oracle':
            return 'oracledb';
        default:
            const n = dbType;
            console.error(picocolors_1.default.red(`No driver package configured for type "${dbType}"`));
            return '';
    }
}
/**
 * Checks that the specified DB connection options are working (i.e. a connection can be
 * established) and that the named database exists.
 */
function checkDbConnection(options, root) {
    switch (options.type) {
        case 'mysql':
            return checkMysqlDbExists(options, root);
        case 'postgres':
            return checkPostgresDbExists(options, root);
        default:
            return Promise.resolve(true);
    }
}
exports.checkDbConnection = checkDbConnection;
async function checkMysqlDbExists(options, root) {
    var _a;
    const mysql = await (_a = path_1.default.join(root, 'node_modules/mysql'), Promise.resolve().then(() => __importStar(require(_a))));
    const connectionOptions = {
        host: options.host,
        user: options.username,
        password: options.password,
        port: options.port,
        database: options.database,
    };
    const connection = mysql.createConnection(connectionOptions);
    return new Promise((resolve, reject) => {
        connection.connect((err) => {
            if (err) {
                if (err.code === 'ER_BAD_DB_ERROR') {
                    throwDatabaseDoesNotExist(options.database);
                }
                throwConnectionError(err);
            }
            resolve(true);
        });
    }).then(() => {
        return new Promise((resolve, reject) => {
            connection.end((err) => {
                resolve(true);
            });
        });
    });
}
async function checkPostgresDbExists(options, root) {
    var _a;
    const { Client } = await (_a = path_1.default.join(root, 'node_modules/pg'), Promise.resolve().then(() => __importStar(require(_a))));
    const connectionOptions = {
        host: options.host,
        user: options.username,
        password: options.password,
        port: options.port,
        database: options.database,
        schema: options.schema,
    };
    const client = new Client(connectionOptions);
    try {
        await client.connect();
        const schema = await client.query(`SELECT schema_name FROM information_schema.schemata WHERE schema_name = '${options.schema}'`);
        if (schema.rows.length === 0) {
            throw new Error('NO_SCHEMA');
        }
    }
    catch (e) {
        if (e.code === '3D000') {
            throwDatabaseDoesNotExist(options.database);
        }
        else if (e.message === 'NO_SCHEMA') {
            throwDatabaseSchemaDoesNotExist(options.database, options.schema);
        }
        throwConnectionError(e);
        await client.end();
        throw e;
    }
    await client.end();
    return true;
}
function throwConnectionError(err) {
    throw new Error('Could not connect to the database. ' +
        `Please check the connection settings in your Vendure config.\n[${(err.message || err.toString())}]`);
}
function throwDatabaseDoesNotExist(name) {
    throw new Error(`Database "${name}" does not exist. Please create the database and then try again.`);
}
function throwDatabaseSchemaDoesNotExist(dbName, schemaName) {
    throw new Error(`Schema "${dbName}.${schemaName}" does not exist. Please create the schema "${schemaName}" and then try again.`);
}
function isServerPortInUse() {
    // eslint-disable-next-line @typescript-eslint/no-var-requires
    const tcpPortUsed = require('tcp-port-used');
    try {
        return tcpPortUsed.check(constants_1.SERVER_PORT);
    }
    catch (e) {
        console.log(picocolors_1.default.yellow(`Warning: could not determine whether port ${constants_1.SERVER_PORT} is available`));
        return Promise.resolve(false);
    }
}
exports.isServerPortInUse = isServerPortInUse;
//# sourceMappingURL=helpers.js.map