"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 });
const path_1 = __importDefault(require("path"));
const child_process_1 = require("child_process");
const chalk_1 = __importDefault(require("chalk"));
const shelljs_1 = require("shelljs");
const core_1 = require("@restlessness/core");
const isWin = process.platform === 'win32';
const printRestlessnessData = (d, newlineAtStart = false) => {
    const data = d.toString();
    const nl = data.endsWith('\n') ? '' : '\n';
    const nlStart = newlineAtStart ? '\n' : '';
    process.stdout.write(`${nlStart}${chalk_1.default.blue('RESTLESSNESS')}: ${data}${nl}`);
};
const printRestlessnessError = e => {
    process.stderr.write(chalk_1.default.red(`RESTLESSNESS error:\n${e.toString()}`));
};
function getProjectName() {
    try {
        return require(path_1.default.join(process.cwd(), 'package.json')).name;
    }
    catch (_a) {
        throw 'Cannot find package.json. Are you on the root folder?';
    }
}
function checkPeerDependencies() {
    const deps = ['serve', 'serverless'];
    for (const dep of deps) {
        if (!shelljs_1.which(dep)) {
            throw `Cannot find ${dep}. Maybe you forgot to install it with: npm i ${dep} -g`;
        }
    }
}
/*
spawnBackend, spawnFrontend, spawnProject functions spawns the homonym processes.
The ChildProcess object is returned through a promise, with some callbacks already defined:
- stdout/err data event: check the output and resolve the promise if the server starts successfully
- exit event: reject the promise if the process exit with an error code. This event can then be overridden
- error event: reject the promise if the process cannot be spawned
*/
function spawnBackend() {
    return new Promise((resolve, reject) => {
        const proc = child_process_1.spawn('serverless', ['offline', '--config', 'serverless-services/offline.json', '--httpPort', '4123', '--lambdaPort', '3003'], {
            cwd: path_1.default.join(__dirname, '..', '..', '..', 'lib', 'assets', 'backend'),
            env: Object.assign(Object.assign({}, process.env), { RLN_PROJECT_PATH: process.cwd() }),
            /*
            First 3 value corresponds to stdin/stdout/stderr.
            'pipe' value creates a pipe between the child process and the parent process.
            The parent can then access the pipe through a property on the child_process object (subprocess.stdio[fd]).
            Pipes created for fds 0, 1, and 2 are also available as subprocess.stdin, subprocess.stdout and
            subprocess.stderr, respectively.
            'ipc' value creates an Inter Process Communication channel for passing messages between parent and child.
            Setting this option enable the send() method (process.send() for the child, child_process.send()
            for the parent) as well as the 'message' event.
            see https://nodejs.org/api/child_process.html#child_process_options_stdio for a detailed description.
            */
            stdio: isWin ? undefined : ['pipe', 'pipe', 'pipe', 'ipc'],
            shell: true,
        });
        proc.stdout.on('data', d => {
            const data = d.toString();
            if (data.indexOf('[HTTP] server ready:') !== -1) {
                resolve(proc);
            }
            const trimmed = data.trim();
            const isShowingTable = trimmed.startsWith('┌─') && trimmed.endsWith('─┘');
            if (trimmed && !trimmed.startsWith('offline:') && !isShowingTable) {
                printRestlessnessData(data);
            }
        });
        proc.stderr.on('data', printRestlessnessError);
        proc.on('error', reject);
        proc.on('exit', code => {
            reject(`Restlessness backend process exited with status code ${code}`);
        });
    });
}
function spawnFrontend() {
    return new Promise((resolve, reject) => {
        const proc = child_process_1.spawn('serve', [], {
            cwd: path_1.default.join(__dirname, '..', '..', '..', 'lib', 'assets', 'frontend', 'build'),
            shell: true,
        });
        proc.stdout.on('data', d => {
            if (d.toString().indexOf('Accepting connections at') !== -1) {
                resolve(proc);
            }
        });
        proc.stderr.on('data', printRestlessnessError);
        proc.on('error', reject);
        proc.on('exit', code => {
            reject(`Restlessness frontend process exited with status code ${code}`);
        });
    });
}
function spawnProject(name, env) {
    return new Promise((resolve, reject) => {
        // @TODO: 'serverless-services/offline.json' should be changed with `${PathResolver.getOfflineServerlessJsonPath}.json`
        const proc = child_process_1.spawn('serverless', ['--config', 'serverless-services/offline.json', 'offline', '--httpPort', '4000', '--lambdaPort', '3002'], {
            env: Object.assign(Object.assign(Object.assign({}, process.env), env), { RLN_ENVIRONMENT_LOADED: 'true' }),
            shell: true,
        });
        proc.stdout.on('data', d => {
            if (d.toString().indexOf('[HTTP] server ready:') !== -1) {
                resolve(proc);
            }
            process.stdout.write(`${chalk_1.default.green(name)}: ${d.toString()}`);
        });
        proc.stderr.on('data', d => {
            process.stderr.write(chalk_1.default.red(`${name}: ${d.toString()}`));
        });
        proc.on('error', reject);
        proc.on('exit', code => {
            reject(`${name} process exited with status code ${code}`);
        });
    });
}
exports.default = (argv) => __awaiter(void 0, void 0, void 0, function* () {
    if (argv._.length > 2) {
        throw 'Unexpected number of arguments';
    }
    else if (argv._.length < 2) {
        throw 'Expected env name';
    }
    checkPeerDependencies();
    const projectName = getProjectName();
    const envFile = new core_1.EnvFile(argv._[1]);
    const projectEnv = yield envFile.expand();
    let projectProc;
    let frontendProc;
    let backendProc;
    const terminateChildren = () => {
        var _a, _b, _c;
        (_a = projectProc) === null || _a === void 0 ? void 0 : _a.kill();
        (_b = frontendProc) === null || _b === void 0 ? void 0 : _b.kill();
        (_c = backendProc) === null || _c === void 0 ? void 0 : _c.kill();
    };
    /*
    If any of the processes exit prematurely with an error code all other
    processes are killed, since the 3 processes are dependents
    */
    const terminateOnExit = returnCode => {
        if (returnCode !== 0) {
            terminateChildren();
        }
    };
    process.on('SIGINT', () => {
        printRestlessnessData('Shutting down...', true);
        terminateChildren();
    });
    try {
        backendProc = yield spawnBackend();
        backendProc.on('message', (message) => __awaiter(void 0, void 0, void 0, function* () {
            var _a;
            if (message === 'RESTART_PROJECT') {
                (_a = projectProc) === null || _a === void 0 ? void 0 : _a.kill();
                printRestlessnessData(`Restarting project ${projectName}...`);
                projectProc = yield spawnProject(projectName, projectEnv);
            }
        }));
        backendProc.on('exit', terminateOnExit);
    }
    catch (e) {
        terminateChildren();
        throw e;
    }
    try {
        frontendProc = yield spawnFrontend();
        frontendProc.on('exit', terminateOnExit);
        printRestlessnessData('Running on http://localhost:5000\n');
    }
    catch (e) {
        terminateChildren();
        throw e;
    }
    try {
        projectProc = yield spawnProject(projectName, projectEnv);
        projectProc.on('exit', terminateOnExit);
    }
    catch (e) {
        terminateChildren();
        throw e;
    }
});
//# sourceMappingURL=index.js.map