"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    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) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
const logger_1 = require("@pnpm/logger");
const server_1 = require("@pnpm/server");
const store_path_1 = require("@pnpm/store-path");
const Diable = require("diable");
const getPort = require("get-port");
const isWindows = require("is-windows");
const mkdirp = require("mkdirp-promise");
const fs = require("mz/fs");
const path = require("path");
const onExit = require("signal-exit");
const createStore_1 = require("../../createStore");
const pnpmPkgJson_1 = require("../../pnpmPkgJson");
const serverConnectionInfoDir_1 = require("../../serverConnectionInfoDir");
exports.default = (opts) => __awaiter(this, void 0, void 0, function* () {
    if (opts.protocol === 'ipc' && opts.port) {
        throw new Error('Port cannot be selected when server communicates via IPC');
    }
    if (opts.background && !Diable.isDaemon()) {
        Diable();
    }
    const pathOfStore = yield store_path_1.default(opts.prefix, opts.store);
    const connectionInfoDir = serverConnectionInfoDir_1.default(pathOfStore);
    const serverJsonPath = path.join(connectionInfoDir, 'server.json');
    yield mkdirp(connectionInfoDir);
    // Open server.json with exclusive write access to ensure only one process can successfully
    // start the server. Note: NFS does not support exclusive writing, but do we really care?
    // Source: https://github.com/moxystudio/node-proper-lockfile#user-content-comparison
    let fd;
    try {
        fd = yield fs.open(serverJsonPath, 'wx');
    }
    catch (error) {
        if (error.code !== 'EEXIST') {
            throw error;
        }
        throw new Error(`Canceling startup of server (pid ${process.pid}) because another process got exclusive access to server.json`);
    }
    let server = null;
    onExit(() => {
        if (server !== null) {
            // Note that server.close returns a Promise, but we cannot wait for it because we may be
            // inside the 'exit' even of process.
            server.close();
        }
        if (fd !== null) {
            try {
                fs.closeSync(fd);
            }
            catch (error) {
                logger_1.default.error(error, `Got error while closing file descriptor of server.json, but the process is already exiting`);
            }
        }
        try {
            fs.unlinkSync(serverJsonPath);
        }
        catch (error) {
            if (error.code !== 'ENOENT') {
                logger_1.default.error(error, `Got error unlinking server.json, but the process is already exiting`);
            }
        }
    });
    const store = yield createStore_1.default(Object.assign(opts, {
        store: pathOfStore,
    }));
    const protocol = opts.protocol || opts.port && 'tcp' || 'auto';
    const serverOptions = yield getServerOptions(connectionInfoDir, { protocol, port: opts.port });
    const connectionOptions = {
        remotePrefix: serverOptions.path
            ? `http://unix:${serverOptions.path}:`
            : `http://${serverOptions.hostname}:${serverOptions.port}`,
    };
    server = server_1.createServer(store.ctrl, Object.assign({}, serverOptions, { ignoreStopRequests: opts.ignoreStopRequests, ignoreUploadRequests: opts.ignoreUploadRequests }));
    // Make sure to populate server.json after the server has started, so clients know that the server is
    // listening if a server.json with valid JSON content exists.
    const serverJson = {
        connectionOptions,
        pid: process.pid,
        pnpmVersion: pnpmPkgJson_1.default.version,
    };
    const serverJsonStr = JSON.stringify(serverJson, undefined, 2); // undefined and 2 are for formatting.
    const serverJsonBuffer = Buffer.from(serverJsonStr, 'utf8');
    // fs.write on NodeJS 4 requires the parameters offset and length to be set:
    // https://nodejs.org/docs/latest-v4.x/api/fs.html#fs_fs_write_fd_buffer_offset_length_position_callback
    yield fs.write(fd, serverJsonBuffer, 0, serverJsonBuffer.byteLength);
    const fdForClose = fd;
    // Set fd to null so we only attempt to close it once.
    fd = null;
    yield fs.close(fdForClose);
});
function getServerOptions(connectionInfoDir, opts) {
    return __awaiter(this, void 0, void 0, function* () {
        switch (opts.protocol) {
            case 'tcp':
                return yield getTcpOptions();
            case 'ipc':
                if (isWindows()) {
                    throw new Error('IPC protocol is not supported on Windows currently');
                }
                return getIpcOptions();
            case 'auto':
                if (isWindows()) {
                    return yield getTcpOptions();
                }
                return getIpcOptions();
            default:
                throw new Error(`Protocol ${opts.protocol} is not supported`);
        }
        function getTcpOptions() {
            return __awaiter(this, void 0, void 0, function* () {
                return {
                    hostname: 'localhost',
                    port: opts.port || (yield getPort({ port: 5813 })),
                };
            });
        }
        function getIpcOptions() {
            return {
                path: path.join(connectionInfoDir, 'socket'),
            };
        }
    });
}
//# sourceMappingURL=start.js.map