@pact-toolbox/utils
Version:
1,009 lines (986 loc) • 54.8 kB
JavaScript
//#region rolldown:runtime
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __commonJS = (cb, mod) => function() {
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
key = keys[i];
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
get: ((k) => from[k]).bind(null, key),
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
});
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
value: mod,
enumerable: true
}) : target, mod));
//#endregion
const consola = __toESM(require("consola"));
const dockerode = __toESM(require("dockerode"));
const fs = __toESM(require("fs"));
const path = __toESM(require("path"));
const tar_fs = __toESM(require("tar-fs"));
const node_fs = __toESM(require("node:fs"));
const consola_utils = __toESM(require("consola/utils"));
const node_fs_promises = __toESM(require("node:fs/promises"));
const pathe = __toESM(require("pathe"));
const node_child_process = __toESM(require("node:child_process"));
const node_util = __toESM(require("node:util"));
const detect_port = __toESM(require("detect-port"));
const get_port_please = __toESM(require("get-port-please"));
const child_process = __toESM(require("child_process"));
const node_crypto = __toESM(require("node:crypto"));
const __clack_prompts = __toESM(require("@clack/prompts"));
//#region src/logger.ts
const logger = (0, consola.createConsola)({
level: 4,
formatOptions: {
columns: 80,
colors: false,
compact: false,
date: false
}
});
//#endregion
//#region src/chainwebApi.ts
/**
* Custom error class for Chainweb-related errors.
*/
var ChainWebError = class extends Error {
constructor(message, cause) {
super(message);
this.cause = cause;
this.name = "ChainWebError";
if (cause) this.stack += "\nCaused by: " + cause.stack;
}
};
/**
* Checks if the Chainweb node is healthy.
* @param serviceUrl - The base URL of the Chainweb service.
* @param timeout - Optional timeout in milliseconds.
* @returns Promise<boolean> - True if the node is healthy, false otherwise.
*/
async function isChainWebNodeOk(serviceUrl, timeout = 5e3) {
try {
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), timeout);
const res = await fetch(`${serviceUrl}/health-check`, { signal: controller.signal });
clearTimeout(id);
if (!res.ok) return false;
const message = await res.text();
if (message.includes("Health check OK.")) return true;
else return false;
} catch {
return false;
}
}
/**
* Checks if the Chainweb node has reached the target block height.
* @param targetHeight - The target block height.
* @param serviceUrl - The base URL of the Chainweb service.
* @param timeout - Optional timeout in milliseconds.
* @returns Promise<boolean> - True if the node is at or above the target height, false otherwise.
*/
async function isChainWebAtHeight(targetHeight, serviceUrl, timeout = 5e3) {
try {
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), timeout);
const res = await fetch(`${serviceUrl}/chainweb/0.0/development/cut`, { signal: controller.signal });
clearTimeout(id);
if (!res.ok) {
logger.error(`Failed to get chainweb cut: ${res.status} ${res.statusText}`);
return false;
}
const data = await res.json();
if (typeof data.height !== "number") {
logger.error(`Invalid response: height is not a number`);
return false;
}
const height = data.height;
return height >= targetHeight;
} catch (e) {
if (e.name === "AbortError") logger.error("Chainweb cut request timed out");
else logger.error(`Failed to get chainweb cut: ${e.message}`);
return false;
}
}
/**
* Requests the Chainweb node to create blocks on specified chains.
* @param params - Parameters including count, chainIds, and onDemandUrl.
* @returns Promise<any> - The response data from the server.
*/
async function makeBlocks({ count = 1, chainIds = ["0"], onDemandUrl }) {
const body = JSON.stringify(chainIds.reduce((acc, chainId) => ({
...acc,
[chainId]: count
}), {}));
const res = await fetch(`${onDemandUrl}/make-blocks`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body
});
if (!res.ok) throw new Error(`Failed to make blocks ${res.status} ${res.statusText}`);
return res.json();
}
/**
* Checks if blocks were successfully created.
* @param params - Parameters including count, chainIds, and onDemandUrl.
* @returns Promise<boolean> - True if blocks were made successfully, false otherwise.
*/
async function didMakeBlocks(params) {
try {
await makeBlocks(params);
return true;
} catch {
return false;
}
}
//#endregion
//#region ../../node_modules/.pnpm/@oxc-project+runtime@0.72.2/node_modules/@oxc-project/runtime/src/helpers/typeof.js
var require_typeof = __commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.72.2/node_modules/@oxc-project/runtime/src/helpers/typeof.js"(exports, module) {
function _typeof$2(o) {
"@babel/helpers - typeof";
return module.exports = _typeof$2 = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(o$1) {
return typeof o$1;
} : function(o$1) {
return o$1 && "function" == typeof Symbol && o$1.constructor === Symbol && o$1 !== Symbol.prototype ? "symbol" : typeof o$1;
}, module.exports.__esModule = true, module.exports["default"] = module.exports, _typeof$2(o);
}
module.exports = _typeof$2, module.exports.__esModule = true, module.exports["default"] = module.exports;
} });
//#endregion
//#region ../../node_modules/.pnpm/@oxc-project+runtime@0.72.2/node_modules/@oxc-project/runtime/src/helpers/toPrimitive.js
var require_toPrimitive = __commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.72.2/node_modules/@oxc-project/runtime/src/helpers/toPrimitive.js"(exports, module) {
var _typeof$1 = require_typeof()["default"];
function toPrimitive$1(t, r) {
if ("object" != _typeof$1(t) || !t) return t;
var e = t[Symbol.toPrimitive];
if (void 0 !== e) {
var i = e.call(t, r || "default");
if ("object" != _typeof$1(i)) return i;
throw new TypeError("@@toPrimitive must return a primitive value.");
}
return ("string" === r ? String : Number)(t);
}
module.exports = toPrimitive$1, module.exports.__esModule = true, module.exports["default"] = module.exports;
} });
//#endregion
//#region ../../node_modules/.pnpm/@oxc-project+runtime@0.72.2/node_modules/@oxc-project/runtime/src/helpers/toPropertyKey.js
var require_toPropertyKey = __commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.72.2/node_modules/@oxc-project/runtime/src/helpers/toPropertyKey.js"(exports, module) {
var _typeof = require_typeof()["default"];
var toPrimitive = require_toPrimitive();
function toPropertyKey$1(t) {
var i = toPrimitive(t, "string");
return "symbol" == _typeof(i) ? i : i + "";
}
module.exports = toPropertyKey$1, module.exports.__esModule = true, module.exports["default"] = module.exports;
} });
//#endregion
//#region ../../node_modules/.pnpm/@oxc-project+runtime@0.72.2/node_modules/@oxc-project/runtime/src/helpers/defineProperty.js
var require_defineProperty = __commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.72.2/node_modules/@oxc-project/runtime/src/helpers/defineProperty.js"(exports, module) {
var toPropertyKey = require_toPropertyKey();
function _defineProperty$2(e, r, t) {
return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
value: t,
enumerable: !0,
configurable: !0,
writable: !0
}) : e[r] = t, e;
}
module.exports = _defineProperty$2, module.exports.__esModule = true, module.exports["default"] = module.exports;
} });
//#endregion
//#region src/cleanup.ts
var import_defineProperty$1 = __toESM(require_defineProperty(), 1);
var CleanupHandler = class {
constructor() {
(0, import_defineProperty$1.default)(this, "cleanupFunctions", /* @__PURE__ */ new Set());
(0, import_defineProperty$1.default)(this, "cleanupRegistered", false);
(0, import_defineProperty$1.default)(this, "isCleaningUp", false);
}
registerCleanupFunction(cleanupFn) {
this.cleanupFunctions.add(cleanupFn);
this.registerSignalHandlers();
}
registerSignalHandlers() {
if (this.cleanupRegistered) return;
this.cleanupRegistered = true;
const cleanup = async (signal) => {
if (this.isCleaningUp) return;
this.isCleaningUp = true;
logger.info(`Received ${signal}, running cleanup functions...`);
for (const cleanupFn of this.cleanupFunctions) try {
await cleanupFn();
} catch (err) {
logger.error("Error during cleanup:", err);
}
process.exit(signal === "uncaughtException" || signal === "unhandledRejection" ? 1 : 0);
};
const signals = [
"SIGINT",
"SIGTERM",
"SIGQUIT",
"SIGHUP",
"exit",
"uncaughtException",
"unhandledRejection"
];
signals.forEach((signal) => {
process.on(signal, async (reasonOrExitCode) => {
if (signal === "exit") await cleanup(signal);
else if (signal === "uncaughtException" || signal === "unhandledRejection") {
logger.error(`${signal}:`, reasonOrExitCode);
await cleanup(signal);
} else await cleanup(signal);
});
});
}
};
const cleanupHandler = new CleanupHandler();
function cleanupOnExit(cleanupFn) {
cleanupHandler.registerCleanupFunction(cleanupFn);
}
//#endregion
//#region src/date.ts
function formatDate(date) {
if (typeof date === "string") date = new Date(date);
const { locale, timeZone } = Intl.DateTimeFormat().resolvedOptions();
return date.toLocaleDateString(locale, {
month: "short",
day: "numeric",
year: "numeric",
hour: "numeric",
minute: "numeric",
second: "numeric",
hour12: false,
timeZone
});
}
//#endregion
//#region src/docker/utils.ts
const DOCKER_SOCKET = process.env.DOCKER_SOCKET || "/var/run/docker.sock";
function isDockerInstalled() {
const socket = DOCKER_SOCKET;
try {
const stats = (0, node_fs.statSync)(socket);
return stats.isSocket();
} catch (e) {
logger.error(`Docker is not installed or the socket is not accessible: ${e}`);
return false;
}
}
const CHALK_SERVICE_COLORS = [
consola_utils.colors.cyan,
consola_utils.colors.green,
consola_utils.colors.yellow,
consola_utils.colors.blue,
consola_utils.colors.magenta,
consola_utils.colors.red
];
let colorIndex = 0;
const serviceChalkColorMap = /* @__PURE__ */ new Map();
function getServiceColor(serviceName) {
if (!serviceChalkColorMap.has(serviceName)) {
const selectedChalkFunction = CHALK_SERVICE_COLORS[colorIndex % CHALK_SERVICE_COLORS.length];
serviceChalkColorMap.set(serviceName, selectedChalkFunction);
colorIndex++;
}
return serviceChalkColorMap.get(serviceName);
}
//#endregion
//#region ../../node_modules/.pnpm/@oxc-project+runtime@0.72.2/node_modules/@oxc-project/runtime/src/helpers/checkPrivateRedeclaration.js
var require_checkPrivateRedeclaration = __commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.72.2/node_modules/@oxc-project/runtime/src/helpers/checkPrivateRedeclaration.js"(exports, module) {
function _checkPrivateRedeclaration(e, t) {
if (t.has(e)) throw new TypeError("Cannot initialize the same private elements twice on an object");
}
module.exports = _checkPrivateRedeclaration, module.exports.__esModule = true, module.exports["default"] = module.exports;
} });
//#endregion
//#region ../../node_modules/.pnpm/@oxc-project+runtime@0.72.2/node_modules/@oxc-project/runtime/src/helpers/classPrivateMethodInitSpec.js
var require_classPrivateMethodInitSpec = __commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.72.2/node_modules/@oxc-project/runtime/src/helpers/classPrivateMethodInitSpec.js"(exports, module) {
var checkPrivateRedeclaration$1 = require_checkPrivateRedeclaration();
function _classPrivateMethodInitSpec$2(e, a) {
checkPrivateRedeclaration$1(e, a), a.add(e);
}
module.exports = _classPrivateMethodInitSpec$2, module.exports.__esModule = true, module.exports["default"] = module.exports;
} });
//#endregion
//#region ../../node_modules/.pnpm/@oxc-project+runtime@0.72.2/node_modules/@oxc-project/runtime/src/helpers/classPrivateFieldInitSpec.js
var require_classPrivateFieldInitSpec = __commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.72.2/node_modules/@oxc-project/runtime/src/helpers/classPrivateFieldInitSpec.js"(exports, module) {
var checkPrivateRedeclaration = require_checkPrivateRedeclaration();
function _classPrivateFieldInitSpec$2(e, t, a) {
checkPrivateRedeclaration(e, t), t.set(e, a);
}
module.exports = _classPrivateFieldInitSpec$2, module.exports.__esModule = true, module.exports["default"] = module.exports;
} });
//#endregion
//#region ../../node_modules/.pnpm/@oxc-project+runtime@0.72.2/node_modules/@oxc-project/runtime/src/helpers/assertClassBrand.js
var require_assertClassBrand = __commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.72.2/node_modules/@oxc-project/runtime/src/helpers/assertClassBrand.js"(exports, module) {
function _assertClassBrand$2(e, t, n) {
if ("function" == typeof e ? e === t : e.has(t)) return arguments.length < 3 ? t : n;
throw new TypeError("Private element is not present on this object");
}
module.exports = _assertClassBrand$2, module.exports.__esModule = true, module.exports["default"] = module.exports;
} });
//#endregion
//#region ../../node_modules/.pnpm/@oxc-project+runtime@0.72.2/node_modules/@oxc-project/runtime/src/helpers/classPrivateFieldSet2.js
var require_classPrivateFieldSet2 = __commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.72.2/node_modules/@oxc-project/runtime/src/helpers/classPrivateFieldSet2.js"(exports, module) {
var assertClassBrand$1 = require_assertClassBrand();
function _classPrivateFieldSet2(s, a, r) {
return s.set(assertClassBrand$1(s, a), r), r;
}
module.exports = _classPrivateFieldSet2, module.exports.__esModule = true, module.exports["default"] = module.exports;
} });
//#endregion
//#region ../../node_modules/.pnpm/@oxc-project+runtime@0.72.2/node_modules/@oxc-project/runtime/src/helpers/classPrivateFieldGet2.js
var require_classPrivateFieldGet2 = __commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.72.2/node_modules/@oxc-project/runtime/src/helpers/classPrivateFieldGet2.js"(exports, module) {
var assertClassBrand = require_assertClassBrand();
function _classPrivateFieldGet2(s, a) {
return s.get(assertClassBrand(s, a));
}
module.exports = _classPrivateFieldGet2, module.exports.__esModule = true, module.exports["default"] = module.exports;
} });
//#endregion
//#region src/docker/DockerService.ts
var import_classPrivateMethodInitSpec$1 = __toESM(require_classPrivateMethodInitSpec(), 1);
var import_defineProperty = __toESM(require_defineProperty(), 1);
var import_classPrivateFieldInitSpec$1 = __toESM(require_classPrivateFieldInitSpec(), 1);
var import_classPrivateFieldSet2$1 = __toESM(require_classPrivateFieldSet2(), 1);
var import_classPrivateFieldGet2$1 = __toESM(require_classPrivateFieldGet2(), 1);
var import_assertClassBrand$1 = __toESM(require_assertClassBrand(), 1);
var _docker$1 = /* @__PURE__ */ new WeakMap();
var _networkName$1 = /* @__PURE__ */ new WeakMap();
var _containerId = /* @__PURE__ */ new WeakMap();
var _logStream = /* @__PURE__ */ new WeakMap();
var _coloredPrefix = /* @__PURE__ */ new WeakMap();
var _logger$1 = /* @__PURE__ */ new WeakMap();
var _DockerService_brand = /* @__PURE__ */ new WeakSet();
var DockerService = class {
constructor(config, options) {
(0, import_classPrivateMethodInitSpec$1.default)(this, _DockerService_brand);
(0, import_defineProperty.default)(this, "serviceName", void 0);
(0, import_defineProperty.default)(this, "config", void 0);
(0, import_defineProperty.default)(this, "containerName", void 0);
(0, import_defineProperty.default)(this, "healthCheckFailed", false);
(0, import_classPrivateFieldInitSpec$1.default)(this, _docker$1, void 0);
(0, import_classPrivateFieldInitSpec$1.default)(this, _networkName$1, void 0);
(0, import_classPrivateFieldInitSpec$1.default)(this, _containerId, void 0);
(0, import_classPrivateFieldInitSpec$1.default)(this, _logStream, null);
(0, import_classPrivateFieldInitSpec$1.default)(this, _coloredPrefix, void 0);
(0, import_classPrivateFieldInitSpec$1.default)(this, _logger$1, void 0);
this.serviceName = options.serviceName || config.containerName;
this.config = config;
this.containerName = config.containerName;
(0, import_classPrivateFieldSet2$1.default)(_docker$1, this, options.docker);
(0, import_classPrivateFieldSet2$1.default)(_networkName$1, this, options.networkName);
const colorizer = process.stdout.isTTY ? getServiceColor(this.serviceName) : null;
(0, import_classPrivateFieldSet2$1.default)(_coloredPrefix, this, colorizer ? colorizer(this.serviceName) : this.serviceName);
(0, import_classPrivateFieldSet2$1.default)(_logger$1, this, options.logger.withTag((0, import_classPrivateFieldGet2$1.default)(_coloredPrefix, this)));
}
async prepareImage() {
if (this.config.build && this.config.image) await (0, import_assertClassBrand$1.default)(_DockerService_brand, this, _buildImage).call(this);
else if (this.config.image) await (0, import_assertClassBrand$1.default)(_DockerService_brand, this, _pullImage).call(this);
}
async start() {
(0, import_classPrivateFieldGet2$1.default)(_logger$1, this).start(`Starting service instance...`);
await this.prepareImage();
try {
const existingContainer = (0, import_classPrivateFieldGet2$1.default)(_docker$1, this).getContainer(this.containerName);
const inspectInfo = await existingContainer.inspect();
(0, import_classPrivateFieldGet2$1.default)(_logger$1, this).warn(`Container '${this.containerName}' already exists (State: ${inspectInfo.State.Status}). Attempting to remove it.`);
if (inspectInfo.State.Running) await existingContainer.stop({ t: this.config.stopGracePeriod || 10 }).catch((err) => (0, import_classPrivateFieldGet2$1.default)(_logger$1, this).warn(`Could not stop existing container: ${err.message}`));
await existingContainer.remove().catch((err) => (0, import_classPrivateFieldGet2$1.default)(_logger$1, this).warn(`Could not remove existing container: ${err.message}`));
(0, import_classPrivateFieldGet2$1.default)(_logger$1, this).log(`Existing container '${this.containerName}' removed.`);
} catch (error) {
if (error.statusCode !== 404) {
(0, import_classPrivateFieldGet2$1.default)(_logger$1, this).error(`Error checking for existing container:`, error.message || error);
throw error;
}
}
let restartPolicy = void 0;
const deployRestartPolicy = this.config.deploy?.restartPolicy;
const topLevelRestart = this.config.restart;
if (deployRestartPolicy) {
let dockerodeConditionName = "no";
const composeCondition = deployRestartPolicy.condition;
if (composeCondition === "none") dockerodeConditionName = "no";
else if (composeCondition === "on-failure" || composeCondition === "unless-stopped" || composeCondition === "always") dockerodeConditionName = composeCondition;
else if (composeCondition) (0, import_classPrivateFieldGet2$1.default)(_logger$1, this).warn(`Unsupported deploy.restart_policy.condition: '${composeCondition}'. Defaulting to 'no'.`);
restartPolicy = {
Name: dockerodeConditionName,
MaximumRetryCount: deployRestartPolicy.maxAttempts
};
} else if (topLevelRestart) if (topLevelRestart === "on-failure" || topLevelRestart === "unless-stopped" || topLevelRestart === "always" || topLevelRestart === "no") restartPolicy = { Name: topLevelRestart };
else {
(0, import_classPrivateFieldGet2$1.default)(_logger$1, this).warn(`Unsupported top-level restart value: '${topLevelRestart}'. Defaulting to 'no'.`);
restartPolicy = { Name: "no" };
}
const createOptions = {
name: this.containerName,
Image: this.config.image,
Cmd: this.config.command,
Entrypoint: typeof this.config.entrypoint === "string" ? [this.config.entrypoint] : this.config.entrypoint,
Env: Array.isArray(this.config.environment) ? this.config.environment : Object.entries(this.config.environment || {}).map(([k, v]) => `${k}=${v}`),
ExposedPorts: {},
Labels: this.config.labels,
HostConfig: {
RestartPolicy: restartPolicy,
PortBindings: (0, import_assertClassBrand$1.default)(_DockerService_brand, this, _parsePorts).call(this),
Binds: this.config.volumes,
NetworkMode: (0, import_classPrivateFieldGet2$1.default)(_networkName$1, this),
Ulimits: this.config.ulimits
},
NetworkingConfig: { EndpointsConfig: { [(0, import_classPrivateFieldGet2$1.default)(_networkName$1, this)]: {} } },
StopSignal: this.config.stopSignal,
StopTimeout: this.config.stopGracePeriod,
Healthcheck: this.config.healthCheck,
platform: this.config.platform
};
if (this.config.expose) this.config.expose.forEach((p) => {
createOptions.ExposedPorts[`${p}/tcp`] = {};
});
try {
(0, import_classPrivateFieldGet2$1.default)(_logger$1, this).start(`Creating container '${this.containerName}' with image '${this.config.image}'...`);
const container = await (0, import_classPrivateFieldGet2$1.default)(_docker$1, this).createContainer(createOptions);
(0, import_classPrivateFieldSet2$1.default)(_containerId, this, container.id);
(0, import_classPrivateFieldGet2$1.default)(_logger$1, this).log(`Container '${this.containerName}' (ID: ${(0, import_classPrivateFieldGet2$1.default)(_containerId, this)}) created. Starting...`);
await container.start();
(0, import_classPrivateFieldGet2$1.default)(_logger$1, this).log(`Service started (Container: ${this.containerName}).`);
} catch (error) {
(0, import_classPrivateFieldGet2$1.default)(_logger$1, this).error(`Error starting service:`, error.message || error, error.json ? JSON.stringify(error.json) : "");
throw error;
}
}
async stop() {
const containerRef = (0, import_classPrivateFieldGet2$1.default)(_containerId, this) || this.containerName;
if (!containerRef) {
(0, import_classPrivateFieldGet2$1.default)(_logger$1, this).warn(`No container ID or name to stop.`);
return;
}
try {
const container = (0, import_classPrivateFieldGet2$1.default)(_docker$1, this).getContainer(containerRef);
const inspectInfo = await container.inspect().catch(() => null);
if (!inspectInfo) {
(0, import_classPrivateFieldGet2$1.default)(_logger$1, this).log(`Container '${containerRef}' not found for stopping.`);
return;
}
(0, import_classPrivateFieldGet2$1.default)(_logger$1, this).log(`Stopping container '${this.containerName}' (ID: ${inspectInfo.Id})...`);
await container.stop({ t: this.config.stopGracePeriod || 10 });
(0, import_classPrivateFieldGet2$1.default)(_logger$1, this).log(`Container '${this.containerName}' stopped.`);
} catch (error) {
if (error.statusCode === 304) (0, import_classPrivateFieldGet2$1.default)(_logger$1, this).log(`Container '${this.containerName}' was already stopped.`);
else if (error.statusCode === 404) (0, import_classPrivateFieldGet2$1.default)(_logger$1, this).log(`Container '${containerRef}' not found during stop.`);
else (0, import_classPrivateFieldGet2$1.default)(_logger$1, this).warn(`Error stopping container '${this.containerName}':`, error.message || error);
}
}
async remove() {
const containerRef = (0, import_classPrivateFieldGet2$1.default)(_containerId, this) || this.containerName;
if (!containerRef) {
(0, import_classPrivateFieldGet2$1.default)(_logger$1, this).warn(`No container ID or name to remove.`);
return;
}
try {
const container = (0, import_classPrivateFieldGet2$1.default)(_docker$1, this).getContainer(containerRef);
await container.inspect().catch((err) => {
if (err.statusCode === 404) (0, import_classPrivateFieldGet2$1.default)(_logger$1, this).log(`Container '${containerRef}' not found before removal, attempting removal anyway.`);
else throw err;
});
(0, import_classPrivateFieldGet2$1.default)(_logger$1, this).log(`Removing container '${this.containerName}'...`);
await container.remove({ force: true });
(0, import_classPrivateFieldGet2$1.default)(_logger$1, this).log(`Container '${this.containerName}' removed.`);
} catch (error) {
if (error.statusCode === 404) (0, import_classPrivateFieldGet2$1.default)(_logger$1, this).log(`Container '${containerRef}' was already removed.`);
else (0, import_classPrivateFieldGet2$1.default)(_logger$1, this).warn(`Error removing container '${this.containerName}':`, error.message || error);
}
}
async isHealthy() {
const containerRef = (0, import_classPrivateFieldGet2$1.default)(_containerId, this) || this.containerName;
if (!containerRef) return false;
try {
const data = await (0, import_classPrivateFieldGet2$1.default)(_docker$1, this).getContainer(containerRef).inspect();
return data.State.Health?.Status === "healthy";
} catch (error) {
if (error.statusCode !== 404) (0, import_classPrivateFieldGet2$1.default)(_logger$1, this).error(`Error checking health for container '${containerRef}':`, error.message || error);
return false;
}
}
async waitForHealthy(timeoutMs = 12e4, intervalMs = 1e3) {
if (!this.config.healthCheck) {
(0, import_classPrivateFieldGet2$1.default)(_logger$1, this).log(`No health check defined. Assuming healthy.`);
return;
}
(0, import_classPrivateFieldGet2$1.default)(_logger$1, this).log(`Waiting for container '${this.containerName}' to become healthy (timeout: ${timeoutMs}ms, interval: ${intervalMs}ms)...`);
const startTime = Date.now();
while (Date.now() - startTime < timeoutMs) {
try {
if (await this.isHealthy()) {
(0, import_classPrivateFieldGet2$1.default)(_logger$1, this).log(`Container '${this.containerName}' is healthy.`);
this.healthCheckFailed = false;
return;
}
} catch (error) {
(0, import_classPrivateFieldGet2$1.default)(_logger$1, this).warn(`Health check attempt failed for '${this.containerName}': ${error.message}`);
}
await new Promise((resolve) => setTimeout(resolve, intervalMs));
}
this.healthCheckFailed = true;
throw new Error(`Timeout waiting for container '${this.containerName}' to become healthy.`);
}
async streamLogs() {
const containerRef = (0, import_classPrivateFieldGet2$1.default)(_containerId, this) || this.containerName;
if (!containerRef) {
(0, import_classPrivateFieldGet2$1.default)(_logger$1, this).warn(`No container ID or name to stream logs from.`);
return;
}
try {
const container = (0, import_classPrivateFieldGet2$1.default)(_docker$1, this).getContainer(containerRef);
const inspectInfo = await container.inspect().catch(() => null);
if (!inspectInfo || !inspectInfo.State.Running) {
(0, import_classPrivateFieldGet2$1.default)(_logger$1, this).warn(`Container '${containerRef}' is not running. Cannot stream logs.`);
return;
}
(0, import_classPrivateFieldGet2$1.default)(_logger$1, this).log(`Attaching to logs of container '${this.containerName}'...`);
const stream = await container.logs({
follow: true,
stdout: true,
stderr: true,
timestamps: true
});
(0, import_classPrivateFieldSet2$1.default)(_logStream, this, stream);
(0, import_classPrivateFieldGet2$1.default)(_logStream, this).on("data", (chunk) => {
let logLine = chunk.toString("utf8");
const potentiallyPrefixed = /^[^a-zA-Z0-9\s\p{P}]*(?=\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})/u;
logLine = logLine.replace(potentiallyPrefixed, "");
logLine = logLine.replace(/[^\x20-\x7E\n\r\t]/g, "");
const trimmedMessage = logLine.trimEnd();
if (trimmedMessage) trimmedMessage.split("\n").forEach((line) => {
if (line.trim()) console.log(`${(0, import_classPrivateFieldGet2$1.default)(_coloredPrefix, this)} ${line}`);
});
});
(0, import_classPrivateFieldGet2$1.default)(_logStream, this).on("end", () => {
(0, import_classPrivateFieldGet2$1.default)(_logger$1, this).log(`Log stream ended for container '${this.containerName}'.`);
(0, import_classPrivateFieldSet2$1.default)(_logStream, this, null);
});
(0, import_classPrivateFieldGet2$1.default)(_logStream, this).on("error", (err) => {
(0, import_classPrivateFieldGet2$1.default)(_logger$1, this).error(`Error in log stream for container '${this.containerName}':`, err);
(0, import_classPrivateFieldSet2$1.default)(_logStream, this, null);
});
} catch (error) {
(0, import_classPrivateFieldGet2$1.default)(_logger$1, this).error(`Error attaching to logs for container '${this.containerName}':`, error.message || error);
(0, import_classPrivateFieldSet2$1.default)(_logStream, this, null);
}
}
stopLogStream() {
if ((0, import_classPrivateFieldGet2$1.default)(_logStream, this)) {
(0, import_classPrivateFieldGet2$1.default)(_logger$1, this).log(`Detaching from logs of container '${this.containerName}'.`);
if (typeof (0, import_classPrivateFieldGet2$1.default)(_logStream, this).destroy === "function") (0, import_classPrivateFieldGet2$1.default)(_logStream, this).destroy();
else if (typeof (0, import_classPrivateFieldGet2$1.default)(_logStream, this).end === "function") (0, import_classPrivateFieldGet2$1.default)(_logStream, this).end();
(0, import_classPrivateFieldSet2$1.default)(_logStream, this, null);
}
}
};
async function _pullImage() {
if (!this.config.image) return;
try {
const image = (0, import_classPrivateFieldGet2$1.default)(_docker$1, this).getImage(this.config.image);
await image.inspect();
return;
} catch (error) {
if (error.statusCode !== 404) throw error;
}
(0, import_classPrivateFieldGet2$1.default)(_logger$1, this).start(`Pulling image '${this.config.image}'...`);
try {
const stream = await (0, import_classPrivateFieldGet2$1.default)(_docker$1, this).pull(this.config.image, {});
await new Promise((resolve, reject) => {
(0, import_classPrivateFieldGet2$1.default)(_docker$1, this).modem.followProgress(stream, (err) => err ? reject(err) : resolve());
});
(0, import_classPrivateFieldGet2$1.default)(_logger$1, this).success(`Image '${this.config.image}' pulled successfully.`);
} catch (error) {
(0, import_classPrivateFieldGet2$1.default)(_logger$1, this).error(`Error pulling image '${this.config.image}':`, error);
throw error;
}
}
async function _buildImage() {
if (!this.config.build || !this.config.image) return;
(0, import_classPrivateFieldGet2$1.default)(_logger$1, this).start(`Building image '${this.config.image}' from context '${this.config.build.context}'...`);
const tarStream = tar_fs.pack(this.config.build.context);
const dockerfilePath = path.join(this.config.build.context, this.config.build.dockerfile);
if (!fs.existsSync(dockerfilePath)) throw new Error(`Dockerfile not found at ${dockerfilePath}`);
try {
const buildStream = await (0, import_classPrivateFieldGet2$1.default)(_docker$1, this).buildImage(tarStream, {
t: this.config.image,
dockerfile: this.config.build.dockerfile,
q: false
});
await new Promise((resolve, reject) => {
(0, import_classPrivateFieldGet2$1.default)(_docker$1, this).modem.followProgress(buildStream, (err, res) => {
if (err) return reject(err);
if (res && res.length > 0) {
const lastMessage = res[res.length - 1];
if (lastMessage?.errorDetail) return reject(new Error(lastMessage.errorDetail.message));
}
resolve();
}, (event) => {
if (event.stream) process.stdout.write(event.stream);
});
});
(0, import_classPrivateFieldGet2$1.default)(_logger$1, this).success(`Image '${this.config.image}' built successfully.`);
} catch (error) {
(0, import_classPrivateFieldGet2$1.default)(_logger$1, this).error(`Error building image '${this.config.image}':`, error);
throw error;
}
}
function _parsePorts() {
if (!this.config.ports) return void 0;
const portBindings = {};
this.config.ports.forEach((p) => {
portBindings[`${p.target}/${p.protocol || "tcp"}`] = [{ HostPort: String(p.published) }];
});
return portBindings;
}
//#endregion
//#region src/docker/ContainerOrchestrator.ts
var import_classPrivateMethodInitSpec = __toESM(require_classPrivateMethodInitSpec(), 1);
var import_classPrivateFieldInitSpec = __toESM(require_classPrivateFieldInitSpec(), 1);
var import_classPrivateFieldSet2 = __toESM(require_classPrivateFieldSet2(), 1);
var import_classPrivateFieldGet2 = __toESM(require_classPrivateFieldGet2(), 1);
var import_assertClassBrand = __toESM(require_assertClassBrand(), 1);
var _docker = /* @__PURE__ */ new WeakMap();
var _networkName = /* @__PURE__ */ new WeakMap();
var _networkId = /* @__PURE__ */ new WeakMap();
var _runningServices = /* @__PURE__ */ new WeakMap();
var _logger = /* @__PURE__ */ new WeakMap();
var _spinner = /* @__PURE__ */ new WeakMap();
var _volumes = /* @__PURE__ */ new WeakMap();
var _ContainerOrchestrator_brand = /* @__PURE__ */ new WeakSet();
var ContainerOrchestrator = class {
constructor(options) {
(0, import_classPrivateMethodInitSpec.default)(this, _ContainerOrchestrator_brand);
(0, import_classPrivateFieldInitSpec.default)(this, _docker, new dockerode.default());
(0, import_classPrivateFieldInitSpec.default)(this, _networkName, void 0);
(0, import_classPrivateFieldInitSpec.default)(this, _networkId, void 0);
(0, import_classPrivateFieldInitSpec.default)(this, _runningServices, void 0);
(0, import_classPrivateFieldInitSpec.default)(this, _logger, void 0);
(0, import_classPrivateFieldInitSpec.default)(this, _spinner, void 0);
(0, import_classPrivateFieldInitSpec.default)(this, _volumes, void 0);
(0, import_classPrivateFieldSet2.default)(_networkName, this, options.networkName);
(0, import_classPrivateFieldSet2.default)(_runningServices, this, /* @__PURE__ */ new Map());
(0, import_classPrivateFieldSet2.default)(_logger, this, options.logger.withTag("ContainerOrchestrator"));
(0, import_classPrivateFieldSet2.default)(_spinner, this, options.spinner);
(0, import_classPrivateFieldSet2.default)(_volumes, this, options.volumes || []);
}
async startServices(serviceConfigs) {
(0, import_classPrivateFieldGet2.default)(_logger, this).start(`Starting services...`);
await (0, import_assertClassBrand.default)(_ContainerOrchestrator_brand, this, _getOrCreateNetwork).call(this);
await (0, import_assertClassBrand.default)(_ContainerOrchestrator_brand, this, _createVolumes).call(this);
const orderedServiceGroupNames = (0, import_assertClassBrand.default)(_ContainerOrchestrator_brand, this, _resolveServiceOrder).call(this, serviceConfigs);
(0, import_classPrivateFieldGet2.default)(_logger, this).info(`Service group startup order: ${orderedServiceGroupNames.join(", ")}`);
for (const serviceGroupName of orderedServiceGroupNames) {
const config = serviceConfigs.find((s) => s.containerName === serviceGroupName);
if (!config) {
(0, import_classPrivateFieldGet2.default)(_logger, this).warn(`Config for service group '${serviceGroupName}' not found. Skipping.`);
continue;
}
const replicaCount = config.deploy?.replicas || 1;
const serviceInstances = [];
(0, import_classPrivateFieldGet2.default)(_logger, this).info(`Preparing to start ${replicaCount} instance(s) of service group '${serviceGroupName}'...`);
for (let i = 0; i < replicaCount; i++) {
const instanceName = replicaCount > 1 ? `${serviceGroupName}-${i + 1}` : serviceGroupName;
const instanceConfig = {
...config,
containerName: instanceName
};
const service = new DockerService(instanceConfig, {
serviceName: instanceName,
docker: (0, import_classPrivateFieldGet2.default)(_docker, this),
networkName: (0, import_classPrivateFieldGet2.default)(_networkName, this),
logger: (0, import_classPrivateFieldGet2.default)(_logger, this),
spinner: (0, import_classPrivateFieldGet2.default)(_spinner, this)
});
if (config.dependsOn) for (const depGroupName of Object.keys(config.dependsOn)) {
const depServiceGroupInstances = (0, import_classPrivateFieldGet2.default)(_runningServices, this).get(depGroupName);
if (!depServiceGroupInstances || depServiceGroupInstances.length === 0) throw new Error(`Dependency group '${depGroupName}' for '${instanceName}' not started or has no instances.`);
if (config.dependsOn[depGroupName]?.condition === "service_healthy") {
(0, import_classPrivateFieldGet2.default)(_logger, this).start(`Instance '${instanceName}' waiting for all instances of '${depGroupName}' to be healthy...`);
try {
await Promise.all(depServiceGroupInstances.map((depInstance) => depInstance.waitForHealthy()));
(0, import_classPrivateFieldGet2.default)(_logger, this).success(`All instances of '${depGroupName}' are healthy for '${instanceName}'.`);
} catch (healthError) {
(0, import_classPrivateFieldGet2.default)(_logger, this).error(`Health check failed for at least one instance of dependency group '${depGroupName}' for '${instanceName}': ${healthError.message}`);
throw new Error(`Dependency group '${depGroupName}' for '${instanceName}' failed to become healthy.`);
}
}
}
try {
(0, import_classPrivateFieldGet2.default)(_logger, this).start(`Starting instance '${instanceName}' of service group '${serviceGroupName}'...`);
await service.start();
serviceInstances.push(service);
} catch (startError) {
(0, import_classPrivateFieldGet2.default)(_logger, this).error(`Failed to start instance '${instanceName}' of service group '${serviceGroupName}': ${startError.message}`);
throw startError;
}
}
(0, import_classPrivateFieldGet2.default)(_runningServices, this).set(serviceGroupName, serviceInstances);
(0, import_classPrivateFieldGet2.default)(_logger, this).success(`All ${replicaCount} instance(s) of service group '${serviceGroupName}' attempted to start.`);
}
(0, import_classPrivateFieldGet2.default)(_logger, this).success(`All provided service groups attempted to start.`);
}
async streamAllLogs() {
for (const serviceInstances of (0, import_classPrivateFieldGet2.default)(_runningServices, this).values()) for (const service of serviceInstances) service.streamLogs().catch((err) => {
(0, import_classPrivateFieldGet2.default)(_logger, this).error(`Error starting log stream for ${service.serviceName}: ${err.message}`);
});
}
stopAllLogStreams() {
for (const serviceInstances of (0, import_classPrivateFieldGet2.default)(_runningServices, this).values()) for (const service of serviceInstances) service.stopLogStream();
}
async stopAllServices() {
(0, import_classPrivateFieldGet2.default)(_logger, this).start(`Gracefully shutting down all services...`);
this.stopAllLogStreams();
const serviceGroupNamesToStop = Array.from((0, import_classPrivateFieldGet2.default)(_runningServices, this).keys()).reverse();
for (const serviceGroupName of serviceGroupNamesToStop) {
const serviceInstances = (0, import_classPrivateFieldGet2.default)(_runningServices, this).get(serviceGroupName);
if (serviceInstances) {
(0, import_classPrivateFieldGet2.default)(_logger, this).start(`Stopping ${serviceInstances.length} instance(s) of service group '${serviceGroupName}'...`);
await Promise.all(serviceInstances.map(async (service) => {
await service.stop();
await service.remove();
}));
(0, import_classPrivateFieldGet2.default)(_logger, this).success(`All instances of service group '${serviceGroupName}' stopped and removed.`);
}
}
(0, import_classPrivateFieldGet2.default)(_runningServices, this).clear();
if ((0, import_classPrivateFieldGet2.default)(_networkId, this)) {
try {
const network = (0, import_classPrivateFieldGet2.default)(_docker, this).getNetwork((0, import_classPrivateFieldGet2.default)(_networkId, this));
const netInfo = await network.inspect().catch(() => null);
if (netInfo && netInfo.Containers && Object.keys(netInfo.Containers).length > 0) (0, import_classPrivateFieldGet2.default)(_logger, this).warn(`Network '${(0, import_classPrivateFieldGet2.default)(_networkName, this)}' (ID: ${(0, import_classPrivateFieldGet2.default)(_networkId, this)}) still has containers: ${Object.keys(netInfo.Containers).join(", ")}. Manual cleanup may be required.`);
else if (netInfo) {
(0, import_classPrivateFieldGet2.default)(_logger, this).info(`Removing network '${(0, import_classPrivateFieldGet2.default)(_networkName, this)}' (ID: ${(0, import_classPrivateFieldGet2.default)(_networkId, this)})...`);
await network.remove();
(0, import_classPrivateFieldGet2.default)(_logger, this).success(`Network '${(0, import_classPrivateFieldGet2.default)(_networkName, this)}' removed.`);
} else (0, import_classPrivateFieldGet2.default)(_logger, this).info(`Network '${(0, import_classPrivateFieldGet2.default)(_networkName, this)}' (ID: ${(0, import_classPrivateFieldGet2.default)(_networkId, this)}) not found, likely already removed.`);
} catch (error) {
if (error.statusCode === 404) (0, import_classPrivateFieldGet2.default)(_logger, this).info(`Network '${(0, import_classPrivateFieldGet2.default)(_networkName, this)}' (ID: ${(0, import_classPrivateFieldGet2.default)(_networkId, this)}) was already removed.`);
else (0, import_classPrivateFieldGet2.default)(_logger, this).warn(`Error removing network '${(0, import_classPrivateFieldGet2.default)(_networkName, this)}':`, error.message || error);
}
(0, import_classPrivateFieldSet2.default)(_networkId, this, void 0);
}
(0, import_classPrivateFieldGet2.default)(_logger, this).success(`Service cleanup complete.`);
}
};
async function _getOrCreateNetwork() {
try {
const network = (0, import_classPrivateFieldGet2.default)(_docker, this).getNetwork((0, import_classPrivateFieldGet2.default)(_networkName, this));
const inspectInfo = await network.inspect();
(0, import_classPrivateFieldSet2.default)(_networkId, this, inspectInfo.Id);
} catch (error) {
if (error.statusCode === 404) {
(0, import_classPrivateFieldGet2.default)(_logger, this).start(`Creating network '${(0, import_classPrivateFieldGet2.default)(_networkName, this)}'...`);
const createdNetwork = await (0, import_classPrivateFieldGet2.default)(_docker, this).createNetwork({
Name: (0, import_classPrivateFieldGet2.default)(_networkName, this),
Driver: "bridge"
});
(0, import_classPrivateFieldSet2.default)(_networkId, this, createdNetwork.id);
(0, import_classPrivateFieldGet2.default)(_logger, this).success(`Network '${(0, import_classPrivateFieldGet2.default)(_networkName, this)}' created (ID: ${(0, import_classPrivateFieldGet2.default)(_networkId, this)}).`);
} else {
(0, import_classPrivateFieldGet2.default)(_logger, this).error(`Error inspecting/creating network ${(0, import_classPrivateFieldGet2.default)(_networkName, this)}:`, error.message || error);
throw error;
}
}
if (!(0, import_classPrivateFieldGet2.default)(_networkId, this)) throw new Error(`Failed to obtain network ID for '${(0, import_classPrivateFieldGet2.default)(_networkName, this)}'`);
}
async function _createVolumes() {
for (const volume of (0, import_classPrivateFieldGet2.default)(_volumes, this)) try {
(0, import_classPrivateFieldGet2.default)(_docker, this).getVolume(volume);
} catch (error) {
if (error.statusCode === 404) await (0, import_classPrivateFieldGet2.default)(_docker, this).createVolume({ Name: volume });
}
}
function _resolveServiceOrder(services) {
const serviceMap = new Map(services.map((s) => [s.containerName, s]));
const dependencies = /* @__PURE__ */ new Map();
for (const service of services) {
const serviceGroupName = service.containerName;
if (!dependencies.has(serviceGroupName)) dependencies.set(serviceGroupName, /* @__PURE__ */ new Set());
if (service.dependsOn) {
const deps = dependencies.get(serviceGroupName);
Object.keys(service.dependsOn).forEach((depGroupName) => {
if (serviceMap.has(depGroupName)) deps.add(depGroupName);
else (0, import_classPrivateFieldGet2.default)(_logger, this).warn(`Dependency '${depGroupName}' for service '${serviceGroupName}' not found in defined services. It will be ignored.`);
});
}
}
const sorted = [];
const visited = /* @__PURE__ */ new Set();
const visiting = /* @__PURE__ */ new Set();
const visit = (serviceGroupName) => {
if (visited.has(serviceGroupName)) return;
if (visiting.has(serviceGroupName)) throw new Error(`Circular dependency detected: ${serviceGroupName}`);
visiting.add(serviceGroupName);
const serviceDeps = dependencies.get(serviceGroupName);
if (serviceDeps) for (const dep of serviceDeps) visit(dep);
visiting.delete(serviceGroupName);
visited.add(serviceGroupName);
sorted.push(serviceGroupName);
};
for (const service of services) {
const serviceGroupName = service.containerName;
if (!visited.has(serviceGroupName)) visit(serviceGroupName);
}
return sorted;
}
//#endregion
//#region src/fs.ts
async function ensureDir(dirPath) {
if (!await (0, node_fs_promises.access)(dirPath).catch(() => false)) await (0, node_fs_promises.mkdir)(dirPath, { recursive: true });
}
async function writeFile(filePath, content) {
await ensureDir((0, pathe.dirname)(filePath));
await (0, node_fs_promises.writeFile)(filePath, content.trim());
}
//#endregion
//#region src/helpers.ts
var TimeoutError = class extends Error {
constructor(message = "Operation timed out") {
super(message);
this.name = "TimeoutError";
}
};
var AbortError = class extends Error {
constructor(message = "Operation aborted") {
super(message);
this.name = "AbortError";
}
};
function delay(ms, signal) {
return new Promise((resolve, reject) => {
const timer = setTimeout(() => {
signal?.removeEventListener("abort", onAbort);
resolve();
}, ms);
const onAbort = () => {
clearTimeout(timer);
reject(new AbortError());
};
if (signal) if (signal.aborted) {
clearTimeout(timer);
reject(new AbortError());
} else signal.addEventListener("abort", onAbort);
});
}
/**
* Polls a function until it returns true or the timeout is reached
*/
async function pollFn(fn, options) {
const { timeout = 3e3, interval = 100, signal, stopOnError = false } = options;
const start = performance.now();
while (performance.now() - start < timeout) {
if (signal?.aborted) throw new AbortError();
try {
const result = await fn();
if (result) return;
} catch (err) {
if (stopOnError) throw err;
}
await delay(interval, signal);
}
throw new TimeoutError();
}
const execAsync = (0, node_util.promisify)(node_child_process.exec);
async function executeCommand(command, options) {
return execAsync(command, options);
}
//#endregion
//#region src/pact.ts
const PACT_VERSION_REGEX = /(\d+)\.(\d+)(?:\.(\d+))?(-[A-Za-z0-9]+)?/;
async function isAnyPactInstalled(match) {
const version = await getCurrentPactVersion();
return match ? version?.includes(match) ?? false : !!version;
}
async function getCurrentPactVersion() {
try {
const { stdout } = await execAsync("pact --version");
const match = stdout.match(PACT_VERSION_REGEX);
if (match) return match[0];
} catch {
return void 0;
}
}
async function installPact(version, nightly) {
if (nightly) return execAsync("npx pactup install --nightly");
if (version) return execAsync(`npx pactup install ${version}`);
return execAsync("npx pactup install --latest");
}
//#endregion
//#region src/port.ts
/**
* Gets a series of random network ports with gaps between each.
*
* @param host - The host for which to get the ports. Defaults to '127.0.0.1'.
* @param startGap - The minimum gap between successive ports. Defaults to 10.
* @param endGap - The maximum gap between successive ports. Defaults to 100.
* @returns An object containing the random ports assigned for public, service, on-demand, stratum, and p2p services.
* @throws {Error} If it fails to find a suitable port for any of the services.
*/
async function getRandomNetworkPorts(host = "127.0.0.1", startGap = 10, endGap = 100) {
if (startGap <= 0 || endGap <= 0 || startGap > endGap || endGap > 65535) throw new Error("Invalid port gap values provided.");
try {
const publicPort = await (0, get_port_please.getPort)({
host,
random: true,
name: "public"
});
const service = await (0, get_port_please.getPort)({
port: publicPort + startGap,
host,
portRange: [publicPort + startGap, publicPort + endGap],
name: "service"
});
const onDemand = await (0, get_port_please.getPort)({
port: service + startGap,
host,
portRange: [service + startGap, service + endGap],
name: "onDemand"
});
const stratum = await (0, get_port_please.getPort)({
port: onDemand + startGap,
host,
portRange: [onDemand + startGap, onDemand + endGap],
name: "stratum"
});
const p2p = await (0, get_port_please.getPort)({
port: stratum + startGap,
host,
portRange: [stratum + startGap, stratum + endGap],
name: "p2p"
});
return {
public: publicPort,
service,
onDemand,
stratum,
p2p
};
} catch (error) {
throw new Error(`Failed to get network ports: ${error.message}`);
}
}
async function isPortTaken(port) {
let p = await (0, detect_port.detectPort)(port);
if (p == port) return false;
return true;
}
//#endregion
//#region src/process.ts
function runBin(bin, args, options = {}) {
const { cwd = process.cwd(), silent = false, env = process.env, resolveOnStart = true, resolveIf } = options;
return new Promise((resolve, reject) => {
const child