"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 delay = require("delay");
const fs = require("mz/fs");
const path = require("path");
const createStore_1 = require("./createStore");
const pnpmPkgJson_1 = require("./pnpmPkgJson");
const runServerInBackground_1 = require("./runServerInBackground");
const serverConnectionInfoDir_1 = require("./serverConnectionInfoDir");
function default_1(opts) {
    return __awaiter(this, void 0, void 0, function* () {
        const store = yield store_path_1.default(opts.prefix, opts.store);
        const connectionInfoDir = serverConnectionInfoDir_1.default(store);
        const serverJsonPath = path.join(connectionInfoDir, 'server.json');
        let serverJson = yield tryLoadServerJson({ serverJsonPath, shouldRetryOnNoent: false });
        if (serverJson !== null) {
            if (serverJson.pnpmVersion !== pnpmPkgJson_1.default.version) {
                logger_1.default.warn(`The store server runs on pnpm v${serverJson.pnpmVersion}. It is recommended to connect with the same version (current is v${pnpmPkgJson_1.default.version})`);
            }
            logger_1.default.info('A store server is running. All store manipulations are delegated to it.');
            return {
                ctrl: yield server_1.connectStoreController(serverJson.connectionOptions),
                path: store,
            };
        }
        if (opts.useRunningStoreServer) {
            const err = new Error('No store server is running.');
            err['code'] = 'ERR_PNPM_NO_STORE_SERVER'; // tslint:disable-line:no-string-literal
            throw err;
        }
        if (opts.useStoreServer) {
            runServerInBackground_1.default(store);
            serverJson = yield tryLoadServerJson({ serverJsonPath, shouldRetryOnNoent: true });
            logger_1.default.info('A store server has been started. To stop it, use \`pnpm server stop\`');
            return {
                ctrl: yield server_1.connectStoreController(serverJson.connectionOptions),
                path: store,
            };
        }
        return yield createStore_1.default(Object.assign(opts, {
            store,
        }));
    });
}
exports.default = default_1;
function tryLoadServerJson(options) {
    return __awaiter(this, void 0, void 0, function* () {
        let beforeFirstAttempt = true;
        const startHRTime = process.hrtime();
        while (true) {
            if (!beforeFirstAttempt) {
                const elapsedHRTime = process.hrtime(startHRTime);
                // Time out after 10 seconds of waiting for the server to start, assuming something went wrong.
                // E.g. server got a SIGTERM or was otherwise abruptly terminated, server has a bug or a third
                // party is interfering.
                if (elapsedHRTime[0] >= 10) {
                    // Delete the file in an attempt to recover from this bad state.
                    try {
                        yield fs.unlink(options.serverJsonPath);
                    }
                    catch (error) {
                        if (error.code !== 'ENOENT') {
                            throw error;
                        }
                        // Either the server.json was manually removed or another process already removed it.
                    }
                    return null;
                }
                // Poll for server startup every 200 milliseconds.
                yield delay(200);
            }
            beforeFirstAttempt = false;
            let serverJsonStr;
            try {
                serverJsonStr = yield fs.readFile(options.serverJsonPath, 'utf8');
            }
            catch (error) {
                if (error.code !== 'ENOENT') {
                    throw error;
                }
                if (!options.shouldRetryOnNoent) {
                    return null;
                }
                continue;
            }
            let serverJson;
            try {
                serverJson = JSON.parse(serverJsonStr);
            }
            catch (error) {
                // Server is starting or server.json was modified by a third party.
                // We assume the best case and retry.
                continue;
            }
            if (serverJson === null) {
                // Our server should never write null to server.json, even though it is valid json.
                throw new Error('server.json was modified by a third party');
            }
            return serverJson;
        }
    });
}
exports.tryLoadServerJson = tryLoadServerJson;
//# sourceMappingURL=createStoreController.js.map