"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.LernaNode = void 0;
const lerna_graph_1 = require("./lerna-graph");
const fs_1 = require("fs");
const path_1 = require("path");
const _1 = require("./");
const compilation_status_1 = require("./enums/compilation.status");
const chalk_1 = __importDefault(require("chalk"));
const child_process_1 = require("child_process");
const rxjs_1 = require("rxjs");
const external_binaries_1 = require("../utils/external-binaries");
const typescript_1 = require("../utils/typescript");
const checksums_1 = require("../utils/checksums");
const ui_1 = require("../ui");
const chokidar_1 = require("chokidar");
var NodeStatus;
(function (NodeStatus) {
    NodeStatus[NodeStatus["DISABLED"] = 0] = "DISABLED";
    NodeStatus[NodeStatus["ENABLED"] = 1] = "ENABLED";
})(NodeStatus || (NodeStatus = {}));
class LernaNode {
    constructor(scheduler, graph, node, nodes, elements) {
        this._typeCheckLogs = [];
        this._watchers = [];
        graph.logger.log('node').debug('Building node', node.name);
        this.graph = graph;
        this.name = node.name;
        this.version = node.version;
        this.private = node.private;
        this.location = node.location;
        this.nodeStatus = NodeStatus.DISABLED;
        this.transpilingStatus = compilation_status_1.TranspilingStatus.NOT_TRANSPILED;
        this.typeCheckStatus = compilation_status_1.TypeCheckStatus.NOT_CHECKED;
        this._scheduler = scheduler;
        this.dependencies = node.dependencies.map((d) => {
            const dep = Array.from(nodes).find((n) => n.name === d);
            if (dep) {
                this.getGraph()
                    .logger.log('node')
                    .debug('Dependency is already built', d);
                return dep;
            }
            this.getGraph()
                .logger.log('node')
                .debug('Building dependency', d);
            const elt = elements.find((e) => e.name === d);
            this.getGraph()
                .logger.log('node')
                .debug('Is service', { name: d, result: lerna_graph_1.isService(elt.location) });
            return lerna_graph_1.isService(elt.location)
                ? new _1.Service(scheduler, graph, elt, nodes, elements)
                : new _1.Package(scheduler, graph, elt, nodes, elements);
        });
        this.getGraph()
            .logger.log('node')
            .debug('Node built', this.name);
        nodes.add(this);
    }
    get tscLogs() {
        return this._typeCheckLogs;
    }
    get lastTypeCheck() {
        return this._lastTypeCheck;
    }
    enable() {
        this.nodeStatus = NodeStatus.ENABLED;
    }
    disable() {
        this.nodeStatus = NodeStatus.DISABLED;
    }
    registerIPCServer(sockets) {
        this._ipc = sockets;
    }
    isEnabled() {
        return this.nodeStatus === NodeStatus.ENABLED;
    }
    isService() {
        this.getGraph()
            .logger.log('node')
            .debug('Is service', {
            node: this.getName(),
            location: path_1.join(this.location, 'serverless.yml'),
            result: fs_1.existsSync(path_1.join(this.location, 'serverless.yml')),
        });
        return fs_1.existsSync(path_1.join(this.location, 'serverless.yml')) || fs_1.existsSync(path_1.join(this.location, 'serverless.yaml'));
    }
    getTranspilingStatus() {
        return this.transpilingStatus;
    }
    getTypeCheckStatus() {
        return this.typeCheckStatus;
    }
    getChildren() {
        return this.dependencies;
    }
    getGraph() {
        return this.graph;
    }
    getVersion() {
        return this.version;
    }
    getChild(name) {
        return this.dependencies.find((d) => d.name === name);
    }
    setTranspilingStatus(status) {
        this.transpilingStatus = status;
        if (this._ipc) {
            this.getGraph()
                .logger.log('node')
                .debug('Notifying IPC server of graph update');
            this._ipc.graphUpdated();
        }
        if (this.getGraph().io) {
            this.getGraph().io.transpilingStatusUpdated(this, this.transpilingStatus);
        }
        ui_1.actions.updateCompilationStatus(this);
    }
    setTypeCheckingStatus(status) {
        this.typeCheckStatus = status;
        if (this.getGraph().io) {
            this.getGraph().io.typeCheckStatusUpdated(this, this.typeCheckStatus);
        }
    }
    isRoot() {
        return this.getDependent().length === 0;
    }
    getName() {
        return this.name;
    }
    getLocation() {
        return this.location;
    }
    getDependencies() {
        const deps = [];
        this._getDependencies(deps);
        return deps;
    }
    _getDependencies(deps) {
        for (const dep of this.dependencies) {
            deps.push(dep);
            dep._getDependencies(deps);
        }
    }
    getDependent() {
        const dependent = this.graph.getNodes().filter((n) => n
            .getDependencies()
            .map((n) => n.name)
            .includes(this.name));
        this.getGraph()
            .logger.log('node')
            .silly(`Nodes depending upon ${this.name}`, dependent.map((d) => d.name));
        return dependent;
    }
    getParents() {
        return this.graph.getNodes().filter((n) => n.dependencies.some((d) => d.name === this.name));
    }
    transpile() {
        return new rxjs_1.Observable((observer) => {
            switch (this.transpilingStatus) {
                case compilation_status_1.TranspilingStatus.TRANSPILED:
                case compilation_status_1.TranspilingStatus.ERROR_TRANSPILING:
                case compilation_status_1.TranspilingStatus.NOT_TRANSPILED:
                    this.transpilingPromise = this._startTranspiling();
                    break;
                case compilation_status_1.TranspilingStatus.TRANSPILING:
                    this.getGraph()
                        .logger.log('node')
                        .info('Package already transpiling', this.name);
                    break;
            }
            this.transpilingPromise
                .then(() => {
                this.getGraph()
                    .logger.log('node')
                    .info('Package transpiled', this.name);
                observer.next(this);
                this.setTranspilingStatus(compilation_status_1.TranspilingStatus.TRANSPILED);
                return observer.complete();
            })
                .catch((err) => {
                this.getGraph()
                    .logger.log('node')
                    .info(`Error transpiling ${this.getName()}`, err);
                this.setTranspilingStatus(compilation_status_1.TranspilingStatus.ERROR_TRANSPILING);
                return observer.error(err);
            });
        });
    }
    performTypeChecking(force = false) {
        return new rxjs_1.Observable((observer) => {
            switch (this.typeCheckStatus) {
                case compilation_status_1.TypeCheckStatus.SUCCESS:
                case compilation_status_1.TypeCheckStatus.ERROR:
                case compilation_status_1.TypeCheckStatus.NOT_CHECKED:
                    this._startTypeChecking(force).then((action) => {
                        if (action.recompile) {
                            this._watchTypeChecking().subscribe((next) => observer.next(next), (err) => observer.error(err), () => {
                                if (action.checksums != null) {
                                    checksums_1.checksums(this, this.getGraph().logger)
                                        .write(action.checksums)
                                        .then(() => {
                                        this.getGraph()
                                            .logger.log('node')
                                            .info('Checksum written', this.name);
                                        this._checksums = action.checksums;
                                        observer.complete();
                                    })
                                        .catch((e) => {
                                        this.getGraph()
                                            .logger.log('node')
                                            .debug(e);
                                        this.getGraph()
                                            .logger.log('node')
                                            .warn(`Error caching checksum for node ${this.name}. Next time node will be recompiled event if source does not change`);
                                        observer.complete();
                                    });
                                }
                                else {
                                    observer.complete();
                                }
                            });
                        }
                        else {
                            this.getGraph()
                                .logger.log('node')
                                .info(`Skipped type-checking of ${this.name}: sources did not change`);
                            this.setTypeCheckingStatus(compilation_status_1.TypeCheckStatus.SUCCESS);
                            this._typeCheckLogs = [
                                'Safe-compilation skipped, sources did not change since last type check. Checksums:',
                                JSON.stringify(this._checksums, null, 2),
                            ];
                            observer.next(this);
                            observer.complete();
                        }
                    });
                    break;
                case compilation_status_1.TypeCheckStatus.CHECKING:
                    this._watchTypeChecking().subscribe((next) => observer.next(next), (err) => observer.error(err), () => observer.complete());
                    break;
            }
        });
    }
    _startTranspiling() {
        return __awaiter(this, void 0, void 0, function* () {
            this.setTranspilingStatus(compilation_status_1.TranspilingStatus.TRANSPILING);
            this.getGraph()
                .logger.log('node')
                .info('Fast-compiling using transpile-only', this.name);
            return typescript_1.compileFiles(this.location, this.getGraph().logger);
        });
    }
    _startTypeChecking(force = false) {
        return __awaiter(this, void 0, void 0, function* () {
            this.setTypeCheckingStatus(compilation_status_1.TypeCheckStatus.CHECKING);
            let recompile = true;
            let currentChecksums = null;
            const checksumUtils = checksums_1.checksums(this, this.getGraph().logger);
            if (!force) {
                try {
                    const oldChecksums = yield checksumUtils.read();
                    currentChecksums = yield checksumUtils.calculate();
                    this._checksums = currentChecksums;
                    recompile = checksumUtils.compare(oldChecksums, currentChecksums);
                }
                catch (e) {
                    currentChecksums = yield checksumUtils.calculate().catch(() => {
                        return null;
                    });
                    this.getGraph()
                        .logger.log('node')
                        .warn('Error evaluating checksums for node', this.name);
                    this.getGraph()
                        .logger.log('node')
                        .debug(e);
                }
                this.getGraph()
                    .logger.log('node')
                    .info('Safe-compiling performing type-checks', this.name);
            }
            else {
                try {
                    currentChecksums = yield checksumUtils.calculate();
                }
                catch (e) {
                    this.getGraph()
                        .logger.log('node')
                        .warn('Error evaluating checksums for node', this.name);
                }
            }
            if (recompile) {
                this.typeCheckProcess = child_process_1.spawn(external_binaries_1.getBinary('tsc', this.graph.getProjectRoot(), this.getGraph().logger, this), {
                    cwd: this.location,
                    env: Object.assign(Object.assign({}, process.env), { FORCE_COLOR: '2' }),
                });
                this.typeCheckProcess.stderr.on('data', (data) => {
                    this.getGraph()
                        .logger.log('tsc')
                        .error(`${chalk_1.default.bold(this.name)}: ${data}`);
                    this._handleTscLogs(data);
                });
                this.typeCheckProcess.stdout.on('data', (data) => {
                    this.getGraph()
                        .logger.log('tsc')
                        .info(`${chalk_1.default.bold(this.name)}: ${data}`);
                    this._handleTscLogs(data);
                });
            }
            return { recompile, checksums: currentChecksums };
        });
    }
    _handleTscLogs(data) {
        this._typeCheckLogs.push(data.toString());
        if (this.getGraph().io) {
            this.getGraph().io.handleTscLogs(this.name, data.toString());
        }
    }
    _watchTypeChecking() {
        return new rxjs_1.Observable((observer) => {
            this.typeCheckProcess.on('close', (code) => {
                this.getGraph()
                    .logger.log('node')
                    .silly('npx tsc process closed');
                if (code === 0) {
                    this.setTypeCheckingStatus(compilation_status_1.TypeCheckStatus.SUCCESS);
                    this.getGraph()
                        .logger.log('node')
                        .info(`Package safe-compiled ${this.getName()}`);
                    observer.next(this);
                    this._lastTypeCheck = new Date().toISOString();
                    return observer.complete();
                }
                else {
                    this.setTypeCheckingStatus(compilation_status_1.TypeCheckStatus.ERROR);
                    this.getGraph()
                        .logger.log('node')
                        .info(`Error safe-compiling ${this.getName()}`);
                    return observer.error();
                }
            });
            this.typeCheckProcess.on('error', (err) => {
                this.getGraph()
                    .logger.log('node')
                    .silly('npx tsc process error');
                this.getGraph()
                    .logger.log('node')
                    .error(err);
                this.setTypeCheckingStatus(compilation_status_1.TypeCheckStatus.ERROR);
                this.getGraph()
                    .logger.log('node')
                    .info(`Error safe-compiling ${this.getName()}`, err);
                return observer.error(err);
            });
        });
    }
    watch() {
        this.getGraph()
            .logger.log('node')
            .info('Watching sources', `${this.location}/src/**/*.{ts,js,json}`);
        const watcher = chokidar_1.watch(`${this.location}/src/**/*.{ts,js,json}`);
        watcher.on('change', (path) => {
            this.getGraph()
                .logger.log('node')
                .info(`${chalk_1.default.bold(this.name)}: ${path} changed. Recompiling`);
            this._scheduler.fileChanged(this);
        });
        this._watchers.push(watcher);
    }
    unwatch() {
        this._watchers.forEach((w) => w.close());
    }
}
exports.LernaNode = LernaNode;
//# sourceMappingURL=lerna-node.js.map