@iryu54/stack-monitor
Version:
Monitor processes as a stack
7,013 lines • 257 kB
JavaScript
#!/usr/bin/env node
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __typeError = (msg) => {
throw TypeError(msg);
};
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __commonJS = (cb, mod) => function __require() {
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
};
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
// helpers/args.js
var require_args = __commonJS({
"helpers/args.js"(exports2, module2) {
"use strict";
var path = require("path");
var yargs = require("yargs/yargs");
var { hideBin } = require("yargs/helpers");
var yarg = yargs(hideBin(process.argv)).usage("Usage: <path-to-your-stack> [options]").alias("pe", "pull-env").describe("pe", "Pull env from a service (need --environment and --service)").default("pe", false).alias("e", "environment").describe("e", "Choose your environment").default("e", void 0).alias("s", "service").describe("s", "Service").default("s", void 0).alias("ss", "services").describe("ss", "Services").default("ss", []).boolean(["pe"]).string(["e", "s"]).array(["ss"]).help("h").alias("h", "help").parse();
var args2 = Object.assign({
rootPath: path.resolve(yarg["_"]?.[0] || "."),
initialCwd: ""
}, yarg);
if (args2.rootPath) {
args2.initialCwd = process.cwd();
process.chdir(args2.rootPath);
}
if (args2.pullEnv && !args2.service) {
console.error("Error: --service needed");
process.exit(1);
}
if (args2.pullEnv && !args2.environment) {
console.error("Error: --environment needed");
process.exit(1);
}
module2.exports = args2;
}
});
// ../../common/socket-server/src/CustomObservable.js
var require_CustomObservable = __commonJS({
"../../common/socket-server/src/CustomObservable.js"(exports2, module2) {
"use strict";
function CustomObservable() {
this.funcs = [];
}
CustomObservable.prototype.subscribe = function(fun) {
this.funcs.push(fun);
};
CustomObservable.prototype.next = function(...value) {
this.funcs.forEach((f) => f(...value));
};
CustomObservable.prototype.off = function(fun) {
this.funcs = this.funcs.filter((f) => f !== fun);
};
CustomObservable.prototype.destroy = function() {
this.funcs = [];
};
module2.exports = CustomObservable;
}
});
// ../../common/socket-server/src/sockets.js
var require_sockets = __commonJS({
"../../common/socket-server/src/sockets.js"(exports2, module2) {
"use strict";
var WebSocket = require("ws");
var CustomObservable = require_CustomObservable();
var events = {};
module2.exports = {
/** @type {WebSocket.Server | null} */
io: null,
emit(channel, ...data) {
events[channel]?.next(...data);
this.io?.clients.forEach((ws) => {
ws.send(JSON.stringify({ channel, data }));
});
},
on: (event, cb) => {
if (!events[event]) events[event] = new CustomObservable();
events[event].subscribe(cb);
},
off(event, cb) {
events[event]?.off(cb);
},
/**
*
* @param {*} server
*/
connect(server) {
this.io = new WebSocket.Server({ noServer: true, path: "/socket" });
this.io.on("connection", function message(ws) {
ws.on("message", function message2(event) {
const { channel, data } = JSON.parse(event?.toString());
events[channel]?.next(...data);
});
});
const self = this;
server.on("upgrade", function upgrade(request, socket, head) {
if (request.url === "/socket") {
self.io?.handleUpgrade(request, socket, head, function done(ws) {
self.io?.emit("connection", ws, request);
});
}
});
}
};
}
});
// ../../common/socket-server/src/index.js
var require_src = __commonJS({
"../../common/socket-server/src/index.js"(exports2, module2) {
"use strict";
module2.exports = {
sockets: require_sockets()
};
}
});
// ../../modules/bugs/backend/routes.js
var require_routes = __commonJS({
"../../modules/bugs/backend/routes.js"(exports2, module2) {
"use strict";
var express = require("express");
var router = express.Router();
var pathfs = require("path");
var { fork } = require("child_process");
module2.exports = (stackMonitor) => {
router.get("/bugs/:service", async (req, res) => {
const service = stackMonitor.findService(req.params.service);
if (!service) return res.status(404).send("SERVICE_NOT_FOUND");
const ts = fork(pathfs.resolve(__dirname, "checkJsFork"));
ts.on("message", (results) => {
res.json(results);
ts.kill("SIGKILL");
});
ts.send(req.query.cwd || service.getRootPath() || __dirname);
return null;
});
return router;
};
}
});
// ../../modules/bugs/backend/index.js
var require_backend = __commonJS({
"../../modules/bugs/backend/index.js"(exports2, module2) {
"use strict";
var { existsSync } = require("fs");
var pathfs = require("path");
var plugin = {
enabled: true,
name: "Bugs",
displayName: "Bugs",
description: "Find bugs across a whole javascript project",
icon: "fas fa-bug",
placements: ["service"],
order: 4,
export: null,
hidden: async (service) => {
if (!service) return true;
const npm = new service.Stack.npm(service);
const serviceIsNpm = !!npm.getNpmPaths(service);
return !serviceIsNpm;
},
routes: require_routes()
};
module2.exports = plugin;
}
});
// ../../modules/base64/backend/Base64.js
var require_Base64 = __commonJS({
"../../modules/base64/backend/Base64.js"(exports2, module2) {
"use strict";
var Base64 = (stackMonitor) => ({
/**
* Encode une chaîne en Base64
* @param {string} value - La chaîne à encoder
* @returns {string} - La chaîne encodée
*/
encode: (value = "") => {
try {
const buffer = Buffer.from(value, "utf-8");
return buffer.toString("base64");
} catch (error) {
console.error("Error encoding to base64:", error);
return "";
}
},
/**
* Décode une chaîne Base64
* @param {string} value - La chaîne Base64 à décoder
* @returns {string} - La chaîne décodée
*/
decode: (value = "") => {
try {
const buffer = Buffer.from(value, "base64");
return buffer.toString("utf-8");
} catch (error) {
console.error("Error decoding from base64:", error);
return "";
}
}
});
module2.exports = Base64;
}
});
// ../../modules/base64/backend/routes.js
var require_routes2 = __commonJS({
"../../modules/base64/backend/routes.js"(exports2, module2) {
"use strict";
var express = require("express");
var router = express.Router();
var Base64 = require_Base64();
module2.exports = (stackMonitor) => {
const base64 = Base64(stackMonitor);
router.post("/base64/encode", (req, res) => {
try {
const { value } = req.body;
const result = base64.encode(value);
res.json({ result });
} catch (error) {
res.status(500).json({
error: true,
message: error instanceof Error ? error.message : "Unknown error"
});
}
});
router.post("/base64/decode", (req, res) => {
try {
const { value } = req.body;
const result = base64.decode(value);
res.json({ result });
} catch (error) {
res.status(500).json({
error: true,
message: error instanceof Error ? error.message : "Unknown error"
});
}
});
return router;
};
}
});
// ../../modules/base64/backend/index.js
var require_backend2 = __commonJS({
"../../modules/base64/backend/index.js"(exports2, module2) {
"use strict";
var plugin = {
enabled: true,
name: "Base64",
displayName: "Base64",
description: "Encode and decode Base64 strings",
icon: "fas fa-exchange-alt",
placements: [
{
position: "toolbox",
label: "Base64",
icon: "fas fa-exchange-alt",
goTo: { path: "/Base64" },
active: "Base64"
}
],
export: require_Base64(),
order: 8,
routes: require_routes2()
};
module2.exports = plugin;
}
});
// ../../modules/configuration/backend/index.js
var require_backend3 = __commonJS({
"../../modules/configuration/backend/index.js"(exports2, module2) {
"use strict";
var plugin = {
enabled: true,
name: "Configuration",
displayName: "Configuration",
description: "Show all configurations used to launch the given service",
icon: "fas fa-cog",
export: null,
placements: ["service"],
order: 5
};
module2.exports = plugin;
}
});
// ../../modules/dev-ops/backend/index.js
var require_backend4 = __commonJS({
"../../modules/dev-ops/backend/index.js"(exports2, module2) {
"use strict";
var plugin = {
enabled: false,
name: "DevOps",
displayName: "Dev ops",
description: "Choose a tool used for dev ops purposes",
icon: "fas fa-hard-hat",
order: 7,
export: null,
placements: [{
position: "sidebar",
label: "DevOps",
icon: "fas fa-hard-hat",
goTo: { path: "/DevOps" },
active: "DevOps"
}]
};
module2.exports = plugin;
}
});
// ../../modules/diff/backend/routes.js
var require_routes3 = __commonJS({
"../../modules/diff/backend/routes.js"(exports2, module2) {
"use strict";
var express = require("express");
var router = express.Router();
var { v4 } = require("uuid");
module2.exports = () => {
router.get("/diff", async (req, res) => {
res.json(v4());
});
return router;
};
}
});
// ../../modules/diff/backend/index.js
var require_backend5 = __commonJS({
"../../modules/diff/backend/index.js"(exports2, module2) {
"use strict";
var plugin = {
enabled: true,
name: "Diff",
displayName: "Differences",
description: "Find differences between two strings",
icon: "fas fa-columns",
export: null,
placements: [
{
position: "toolbox",
label: "Diff",
icon: "fas fa-columns",
goTo: { path: "/Diff" },
active: "Diff"
}
],
order: 6,
routes: require_routes3()
};
module2.exports = plugin;
}
});
// helpers/conflictStorage.js
var require_conflictStorage = __commonJS({
"helpers/conflictStorage.js"(exports2, module2) {
"use strict";
var pendingConflicts = [];
function storeConflict(conflict) {
const conflictId = Date.now().toString();
pendingConflicts.push({
id: conflictId,
timestamp: Date.now(),
...conflict
});
if (pendingConflicts.length > 20) {
pendingConflicts.shift();
}
return conflictId;
}
function getPendingConflicts() {
return pendingConflicts;
}
function removeConflict(conflictId) {
const index = pendingConflicts.findIndex((c) => c.id === conflictId);
if (index !== -1) {
pendingConflicts.splice(index, 1);
}
}
module2.exports = {
storeConflict,
getPendingConflicts,
removeConflict
};
}
});
// helpers/reencrypt-nodered.js
var require_reencrypt_nodered = __commonJS({
"helpers/reencrypt-nodered.js"(exports2, module2) {
"use strict";
var crypto = require("crypto");
var { readFile, writeFile } = require("fs/promises");
var encryptionAlgorithm = "aes-256-ctr";
module2.exports = async function decryptCreds(oldSecret, newScret, path) {
const oldKey = crypto.createHash("sha256").update(oldSecret).digest();
const newKey = crypto.createHash("sha256").update(newScret).digest();
const cipher = JSON.parse(await readFile(path, "utf-8"));
let flows = cipher["$"];
const vector = Buffer.from(flows.substring(0, 32), "hex");
flows = flows.substring(32);
const decipher = crypto.createDecipheriv(encryptionAlgorithm, oldKey, vector);
const decrypted = decipher.update(flows, "base64", "utf8") + decipher.final("utf8");
const newVector = crypto.randomBytes(16);
const newCipher = crypto.createCipheriv(encryptionAlgorithm, newKey, newVector);
const encrypted = newCipher.update(decrypted, "utf8", "base64") + newCipher.final("base64");
await writeFile(path, JSON.stringify({
"$": newVector.toString("hex") + encrypted
}, null, 2), "utf-8");
};
}
});
// models/EncryptionKey.js
var require_EncryptionKey = __commonJS({
"models/EncryptionKey.js"(exports2, module2) {
"use strict";
var { existsSync } = require("fs");
var path = require("path");
var { writeFile, readFile, appendFile } = require("fs/promises");
var { randomUUID: randomUUID2 } = require("crypto");
var dbs2 = require_dbs();
var { generateKey, encrypt, decrypt } = require_crypto();
var reencryptNodered = require_reencrypt_nodered();
var pathfs = require("path");
var args2 = require_args();
var _EncryptionKey_instances, getDb_fn;
var EncryptionKey = class {
constructor() {
__privateAdd(this, _EncryptionKey_instances);
this.encryptionKey = "";
}
async init() {
this.encryptionKey = (await __privateMethod(this, _EncryptionKey_instances, getDb_fn).call(this).read()).encryptionKey;
const dirname = path.dirname(await dbs2.getDb("encryption-key", { encrypted: false }).getPath());
const gitignorePath = path.resolve(dirname, ".gitignore");
if (!existsSync(gitignorePath)) {
writeFile(gitignorePath, "encryption-key.json");
} else {
const gitignoreFile = (await readFile(gitignorePath, "utf-8")).split("\n");
const gitignoreHasKey = (key) => gitignoreFile.some((line) => line.trim() === key);
if (!gitignoreHasKey("encryption-key.json")) await appendFile(gitignorePath, "\nencryption-key.json");
if (!gitignoreHasKey("overrides")) await appendFile(gitignorePath, "\noverrides");
}
}
async update() {
console.log(JSON.stringify(["stack-monitor", "update"], (_, v) => typeof v === "function" ? `[func]` : v));
return __privateMethod(this, _EncryptionKey_instances, getDb_fn).call(this).write(this.toStorage());
}
toStorage() {
return {
encryptionKey: this.encryptionKey
};
}
async generateKey() {
return generateKey();
}
async testKey(encryptionKey) {
try {
const variable = randomUUID2();
const result = await encrypt(variable, { encryptionKey });
const decrypted = await decrypt(result, { encryptionKey });
if (decrypted === variable) return true;
return false;
} catch (error) {
console.error(error);
return false;
}
}
async saveKey(key, { noReload } = { noReload: false }) {
if (!await this.testKey(key)) throw new Error("Key not valid");
try {
const envSample = (await dbs2.getDbs("envs"))[0];
if (envSample) await dbs2.getDb(`envs/${envSample}`).read();
if (this.encryptionKey) {
await reencryptNodered(this.encryptionKey, key, pathfs.resolve(require_stack().getRootPath(), "nodered/flow_cred.json"));
await dbs2.reencrypt(this.encryptionKey, key);
}
;
} catch (error) {
console.error(error);
}
const shouldRestart = this.encryptionKey !== key;
this.encryptionKey = key;
await this.update();
if (!noReload) {
await require_stack().selectConf();
}
if (shouldRestart) {
console.log("Restart...");
require("child_process").spawn(process.argv[0], process.argv.slice(1), {
cwd: args2.initialCwd,
detached: true,
stdio: "inherit"
}).unref();
process.exit(0);
}
return key;
}
};
_EncryptionKey_instances = new WeakSet();
getDb_fn = function() {
return dbs2.getDb("encryption-key", { encrypted: false });
};
module2.exports = new EncryptionKey();
}
});
// helpers/crypto.js
var require_crypto = __commonJS({
"helpers/crypto.js"(exports2, module2) {
"use strict";
var _sodium = require("libsodium-wrappers");
var crypto = require("crypto");
var { sockets } = require_src();
var conflictStorage = require_conflictStorage();
var path = require("path");
module2.exports.generateKey = async () => {
await _sodium.ready;
const sodium = _sodium;
const key = sodium.crypto_aead_aegis256_keygen();
return sodium.to_base64(key);
};
module2.exports.encrypt = async (data, { additionnalNonce = "", encryptionKey = "" } = {}) => {
await _sodium.ready;
const sodium = _sodium;
if (!encryptionKey) encryptionKey = require_EncryptionKey().encryptionKey;
const key = sodium.from_base64(encryptionKey);
if (!key || key.length !== sodium.crypto_secretbox_KEYBYTES) {
throw new Error("Invalid encryption key length");
}
let nonce;
if (additionnalNonce) {
const combinedHash = crypto.createHash("blake2b512").update(data + additionnalNonce).digest();
nonce = combinedHash.slice(0, sodium.crypto_secretbox_NONCEBYTES);
} else {
nonce = sodium.randombytes_buf(sodium.crypto_secretbox_NONCEBYTES);
}
const dataStr = typeof data === "string" ? data : String(data);
const dataArray = new TextEncoder().encode(dataStr);
const ciphertext = sodium.crypto_secretbox_easy(dataArray, nonce, key);
return Buffer.concat([
Buffer.from(nonce.buffer, nonce.byteOffset, nonce.byteLength),
Buffer.from(ciphertext.buffer, ciphertext.byteOffset, ciphertext.byteLength)
]).toString("base64");
};
module2.exports.decryptFile = async (encryptedData, options = {}, filePath) => {
if (typeof encryptedData === "string" && (encryptedData.includes("<<<<<<< HEAD") || encryptedData.includes("=======") || encryptedData.includes(">>>>>>>"))) {
return await handleGitConflict(encryptedData, options, filePath);
}
return await module2.exports.decrypt(encryptedData, options);
};
module2.exports.decrypt = async (encryptedData, { additionnalNonce = "", encryptionKey = "" } = {}) => {
if (typeof encryptedData === "string" && (encryptedData.includes("<<<<<<< HEAD") || encryptedData.includes("=======") || encryptedData.includes(">>>>>>>"))) {
return await handleGitConflict(encryptedData, { additionnalNonce, encryptionKey });
}
await _sodium.ready;
const sodium = _sodium;
if (!encryptionKey) encryptionKey = require_EncryptionKey().encryptionKey;
const key = sodium.from_base64(encryptionKey);
if (!key || key.length !== sodium.crypto_secretbox_KEYBYTES) {
throw new Error("Invalid decryption key length");
}
const encryptedBuffer = Buffer.from(encryptedData, "base64");
const nonce = encryptedBuffer.slice(0, sodium.crypto_secretbox_NONCEBYTES);
const ciphertext = encryptedBuffer.slice(sodium.crypto_secretbox_NONCEBYTES);
let decrypted;
try {
const ciphertextArray = new Uint8Array(ciphertext);
const nonceArray = new Uint8Array(nonce);
decrypted = sodium.crypto_secretbox_open_easy(ciphertextArray, nonceArray, key);
} catch (error) {
throw new Error("Decryption failed");
}
if (!decrypted) {
throw new Error("Decryption failed");
}
return Buffer.from(decrypted).toString("utf-8");
};
async function handleGitConflict(conflictedData, options, filePath) {
const headMatch = conflictedData.match(/<<<<<<< HEAD\r?\n([\s\S]*?)\r?\n=======\r?\n([\s\S]*?)\r?\n>>>>>>>.*/);
if (!headMatch) {
throw new Error("Git conflict detected but could not be properly parsed");
}
try {
const ourVersion = headMatch[1];
const theirVersion = headMatch[2];
let ourDecrypted = "";
let theirDecrypted = "";
try {
ourDecrypted = await module2.exports.decrypt(ourVersion, options);
} catch (err) {
const errorMessage = err instanceof Error ? err.message : String(err);
ourDecrypted = `[ERROR DECRYPTING OUR VERSION: ${errorMessage}]`;
}
try {
theirDecrypted = await module2.exports.decrypt(theirVersion, options);
} catch (err) {
const errorMessage = err instanceof Error ? err.message : String(err);
theirDecrypted = `[ERROR DECRYPTING THEIR VERSION: ${errorMessage}]`;
}
const conflictData = {
original: conflictedData,
ourVersion: ourDecrypted,
theirVersion: theirDecrypted,
filePath: filePath || null,
filename: filePath ? path.basename(filePath) : null
};
const conflictId = conflictStorage.storeConflict(conflictData);
conflictData.id = conflictId;
sockets.emit("crypto:conflict", conflictData);
return `${JSON.stringify({
ourVersion: ourDecrypted,
theirVersion: theirDecrypted
})}`;
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
console.error("Error handling git conflict:", errorMessage);
throw new Error(`Git conflict detected, but failed to process: ${errorMessage}`);
}
}
}
});
// helpers/dbs.js
var require_dbs = __commonJS({
"helpers/dbs.js"(exports2, module2) {
"use strict";
var {
existsSync,
mkdirSync,
writeFileSync,
readFileSync,
unlinkSync
} = require("fs");
var pathfs = require("path");
var {
readdir,
mkdir,
writeFile,
readFile,
unlink
} = require("fs/promises");
var { fdir } = require("fdir");
var PromiseB2 = require("bluebird");
var { sockets } = require_src();
var args2 = require_args();
var { encrypt, decrypt, decryptFile } = require_crypto();
var alasql = require("alasql");
module2.exports = new class {
constructor() {
__publicField(this, "cache", {});
}
getRootPath() {
const rootPath = pathfs.resolve(args2.rootPath, ".stackmonitor/dbs");
if (!existsSync(rootPath)) mkdirSync(rootPath, { recursive: true });
return rootPath;
}
async getDbs(namespace = "") {
const pathToDbs = pathfs.resolve(this.getRootPath(), namespace);
if (!existsSync(pathToDbs)) await mkdir(pathToDbs, { recursive: true });
return (await readdir(pathToDbs)).map((id) => id.replace(pathfs.extname(id), "").replace(".encrypted", ""));
}
async reencrypt(oldKey, newKey) {
const api = new fdir().withFullPaths().filter((path) => path.endsWith("encrypted.json")).crawl(this.getRootPath());
await PromiseB2.map(api.withPromise(), async (file) => {
const fileEncrypted = await readFile(file, "utf-8");
const additionnalNonce = file.split(".stackmonitor").pop();
const fileDecrypted = await decrypt(fileEncrypted, { additionnalNonce, encryptionKey: oldKey });
const fileReEncrypted = await encrypt(fileDecrypted, { additionnalNonce, encryptionKey: newKey });
await writeFile(file, fileReEncrypted, "utf-8");
});
}
getDb(id, { encrypted, defaultData } = { encrypted: true, defaultData: {} }) {
const getPath = async () => {
const persistencePath = pathfs.resolve(`${this.getRootPath()}/${id}${encrypted ? ".encrypted" : ""}.json`);
if (!existsSync(pathfs.dirname(persistencePath))) await mkdirSync(pathfs.dirname(persistencePath), { recursive: true });
if (!existsSync(persistencePath)) {
let defaultDB = JSON.stringify(defaultData || [], null, 2);
if (encrypted) defaultDB = await encrypt(defaultDB, { additionnalNonce: persistencePath.split(".stackmonitor").pop() });
await writeFile(persistencePath, defaultDB, "utf-8");
this.cache[id] = defaultDB;
}
return persistencePath;
};
const table = id.replace(/[^a-z0-9]|\s+|\r?\n|\r/gmi, "_");
const read = async () => {
if (this.cache[id]) return this.cache[id];
const path = await getPath();
const additionnalNonce = path.split(".stackmonitor").pop();
let db = readFileSync(path, "utf-8");
if (encrypted) {
try {
if (typeof db === "string" && (db.includes("<<<<<<< HEAD") || db.includes("=======") || db.includes(">>>>>>>"))) {
db = await decryptFile(db, { additionnalNonce }, path);
} else {
db = await decrypt(db, { additionnalNonce }).catch((err) => {
console.error(path, err);
sockets.emit("system:wrongKey");
throw err;
});
}
} catch (err) {
console.error(path, err);
sockets.emit("system:wrongKey");
throw err;
}
}
this.cache[id] = JSON.parse(db);
if (!alasql.tables[table]) {
await alasql(`CREATE TABLE ${table}`);
}
alasql.tables[table].data = this.cache[id];
return this.cache[id];
};
const write = async (data) => {
let db = JSON.stringify(data, null, 2);
const path = await getPath();
const additionnalNonce = path.split(".stackmonitor").pop();
if (encrypted) db = await encrypt(db, { additionnalNonce });
writeFileSync(path, db, "utf-8");
this.cache[id] = data;
if (alasql.tables[table]) {
alasql.tables[table].data = data;
}
};
const escapeQuote = (data) => {
if (typeof data === "string") {
return data.replace(/'/g, "''");
}
return data;
};
const setValue = (item) => {
if (item == null) {
return "NULL";
}
if (item instanceof Date && item.toISOString) {
return `'${item.toISOString()}'`;
}
if (typeof item === "string") {
return `'${escapeQuote(item)}'`;
}
return `${item}`;
};
return {
getPath,
alasql: {
table,
buildUpdateQuery(data) {
const set = [];
Object.keys(data).forEach((key) => {
set.push(`${key} = ${setValue(data[key])}`);
});
return set.join(", ");
},
read: async (sql) => {
await read();
return alasql.promise(sql);
},
write: async (sql, value) => {
await read();
await alasql.promise(sql, value);
await write(await alasql(`select * from ${table}`));
}
},
write,
read,
delete: async () => {
delete this.cache[id];
return unlink(await getPath());
}
};
}
}();
}
});
// ../../modules/documentation/backend/Documentation.js
var require_Documentation = __commonJS({
"../../modules/documentation/backend/Documentation.js"(exports2, module2) {
"use strict";
var PromiseB2 = require("bluebird");
var { randomUUID: randomUUID2 } = require("crypto");
var dbs2 = require_dbs();
var db = dbs2.getDb(`documentations`);
var dbDocumentationTree = dbs2.getDb(`documentations-tree`, { encrypted: true, defaultData: [] }).alasql;
var Documentation = class {
/**
* @param {import('@clabroche/common-typings').NonFunctionProperties<Documentation>} documentation
*/
constructor(documentation) {
this.id = documentation.id || "";
this.text = documentation.text || "";
}
static async load(id) {
}
static async all() {
return db.alasql.select("Select * from ?");
}
static async find(envId) {
const documentations = await this.all();
return documentations.find((env) => env.id === envId);
}
async save() {
const obj = this.toStorage();
await dbs2.getDb(`documentations/${this.id}`).write(obj);
return this;
}
async update(env) {
this.transform = env.transform;
this.label = env.label;
await dbs2.getDb(`documentations/${this.id}`).write(this.toStorage());
}
async delete() {
await dbs2.getDb(`documentations/${this.id}`).delete();
}
toStorage() {
return {
id: this.id,
label: this.label,
transform: this.transform
};
}
};
module2.exports = Documentation;
}
});
// ../../modules/documentation/backend/Leaf.js
var require_Leaf = __commonJS({
"../../modules/documentation/backend/Leaf.js"(exports2, module2) {
"use strict";
var PromiseB2 = require("bluebird");
var { randomUUID: randomUUID2 } = require("crypto");
var dbs2 = require_dbs();
var { v4 } = require("uuid");
var dbLeafTree = dbs2.getDb(`leafs-leafs`, { encrypted: true, defaultData: [] }).alasql;
var Leaf = class _Leaf {
/**
* @param {import('@clabroche/common-typings').NonFunctionProperties<Leaf>} leaf
*/
constructor(leaf) {
this.id = leaf.id || v4();
this.docId = leaf.docId || "";
this.serviceId = leaf.serviceId || "";
this.label = leaf.label || "";
this.position = leaf.position || -1;
this.text = leaf.text || "";
this.parentId = leaf.parentId || "";
}
static async getTree(serviceLabel) {
const leafs = await dbLeafTree.read(`Select * from ${dbLeafTree.table} ${serviceLabel ? `where serviceId = '${serviceLabel}'` : `where serviceId=''`}`);
return leafs;
}
toStorage() {
return {
docId: this.docId,
position: this.position,
serviceId: this.serviceId,
label: this.label,
text: this.text,
parentId: this.parentId
};
}
async remove() {
return dbLeafTree.write(`DELETE from ${dbLeafTree.table} where id = '${this.id}'`);
}
static async find({ id }) {
const [leaf] = await dbLeafTree.read(`Select * from ${dbLeafTree.table} where id='${id}'`);
return leaf ? new _Leaf(leaf) : null;
}
async save() {
const leafExists = await _Leaf.find({ id: this.id });
const storage = this.toStorage();
const set = dbLeafTree.buildUpdateQuery(storage);
await leafExists ? dbLeafTree.write(`update ${dbLeafTree.table} set ${set} where id='${this.id}'`) : dbLeafTree.write(`insert into ${dbLeafTree.table} (id, docId, position, serviceId, label, text, parentId) values ('${this.id}', '${this.docId}', ${this.position}, '${this.serviceId}', '${this.label.replace(/'/g, "''")}', '${this.text.replace(/'/g, "''")}', '${this.parentId}')`);
return this;
}
};
module2.exports = Leaf;
}
});
// ../../modules/documentation/backend/routes.js
var require_routes4 = __commonJS({
"../../modules/documentation/backend/routes.js"(exports2, module2) {
"use strict";
var express = require("express");
var Leaf = require_Leaf();
var PromiseB2 = require("bluebird");
var router = express.Router();
module2.exports = (stackMonitor) => {
const { findService } = stackMonitor;
router.get("/documentation/tree", async (req, res) => {
const service = findService(req.query.serviceId?.toString() || "");
const result = await Leaf.getTree(service?.label);
return res.send(result);
});
router.post("/documentation/tree/sort", async (req, res) => {
const leafs = await PromiseB2.mapSeries(req.body, async (_leaf, index) => {
const leaf = await Leaf.find({ id: _leaf.id });
if (!leaf) return;
leaf.position = index;
await leaf.save();
return leaf;
});
return res.send(leafs);
});
router.post("/documentation/tree", async (req, res) => {
const result = await new Leaf({
...req.body
}).save();
return res.send(result);
});
router.post("/documentation/tree/:key", async (req, res) => {
const result = await new Leaf({
...req.body,
id: req.params.key
}).save();
return res.send(result);
});
router.delete("/documentation/tree/:key", async (req, res) => {
const result = await Leaf.find({
id: req.params.key
});
if (result) return res.send(await result.remove());
return res.send();
});
return router;
};
}
});
// ../../modules/documentation/backend/index.js
var require_backend6 = __commonJS({
"../../modules/documentation/backend/index.js"(exports2, module2) {
"use strict";
var { readFile } = require("fs/promises");
var PromiseB2 = require("bluebird");
var pathfs = require("path");
var Documentation = require_Documentation();
var plugin = {
enabled: true,
name: "Documentation",
displayName: "Documentation",
description: "Read documentation for a given service",
icon: "fas fa-book",
// export: Documentation,
placements: ["service"],
order: 6,
routes: require_routes4(),
finder: async (search, stackMonitor) => {
}
};
module2.exports = plugin;
}
});
// ../../modules/finder/backend/routes.js
var require_routes5 = __commonJS({
"../../modules/finder/backend/routes.js"(exports2, module2) {
"use strict";
var express = require("express");
var router = express.Router();
var PromiseB2 = require("bluebird");
function pluginToUrl(plugin) {
for (let i = 0; i < plugin.placements.length; i += 1) {
const placement = plugin.placements[i];
if (typeof placement !== "string") {
if (placement.position === "toolbox") {
return `/toolbox${placement.goTo?.path || placement.goTo}`;
}
if (placement.position === "sidebar") {
return `${placement.goTo?.path || placement.goTo}`;
}
}
}
return "";
}
var routes = (stackMonitor) => {
const {
getServices,
helpers: { searchString }
} = stackMonitor;
router.get("/finder/search", async (req, res) => {
const search = req.query.q?.toString()?.toUpperCase() || "";
const services = getServices().filter((service) => searchString(service?.label, search)).map((service) => ({
title: service.label,
description: service.description,
group: "Service",
url: `/stack-single/${service.label}`
}));
const { plugins } = stackMonitor;
const _plugins = (await PromiseB2.map(Object.keys(plugins), (key) => plugins[key]).map(async (plugin) => [
...await plugin?.finder?.(search, stackMonitor)?.catch?.(() => []) || [],
...searchString(plugin.name, search) ? [{
title: plugin.displayName || plugin.name,
description: plugin.description,
group: "Plugin",
icon: plugin.icon,
url: pluginToUrl(plugin) || ""
}] : []
])).flat().filter((a) => a?.url);
const result = [
...services,
..._plugins
].filter((a) => a);
res.send(result);
});
return router;
};
module2.exports = routes;
}
});
// ../../modules/finder/backend/index.js
var require_backend7 = __commonJS({
"../../modules/finder/backend/index.js"(exports2, module2) {
"use strict";
var commandExists = require("command-exists");
var plugin = {
enabled: true,
name: "Finder",
displayName: "Finder",
description: "Find all you want inside this app",
icon: "fab fa-git-alt",
export: null,
order: -1,
placements: ["global", {
position: "sidebar",
label: "Finder",
icon: "fas fa-search",
goTo: { path: "/Finder" },
active: "Finder"
}],
hidden: () => commandExists("git").then(() => false).catch(() => true),
routes: require_routes5()
};
module2.exports = plugin;
}
});
// helpers/exec.js
var require_exec = __commonJS({
"helpers/exec.js"(exports2, module2) {
"use strict";
var { exec } = require("child_process");
module2.exports = {
/**
* @param {string} cmd
* @param {import('child_process').ExecOptions} options
* @returns {Promise<string>}
*/
execAsync(cmd, options) {
return new Promise((res, rej) => {
exec(cmd, options, (err, stdout, stderr) => {
if (err) return rej(stderr || err);
return res(stdout);
});
});
},
/**
* @param {string} cmd
* @param {import('child_process').ExecOptions} options
* @returns {Promise<string>}
*/
execAsyncWithoutErr(cmd, options) {
return new Promise((res) => {
exec(cmd, options, (err, stdout) => {
res(stdout);
});
});
},
/**
* @param {string} cmd
* @param {import('child_process').ExecOptions} options
* @returns {Promise<string>}
*/
execAsyncGetError(cmd, options) {
return new Promise((res) => {
exec(cmd, options, (err, stdout, stderr) => {
res(stderr);
});
});
}
};
}
});
// ../../common/express-http-error/src/HTTPError.js
var require_HTTPError = __commonJS({
"../../common/express-http-error/src/HTTPError.js"(exports2, module2) {
"use strict";
var dayjs = require("dayjs");
var { v4: uuid } = require("uuid");
var statusMessage = {
403: "Not allowed",
404: "Resource not found",
500: "We cannot respond to your request for moment. Contact support for more information"
};
module2.exports.statusMessage = statusMessage;
var HTTPError = class extends Error {
/**
*
* @param {string} message
* @param {number} code
* @param {string} errorId
* @param {string} date
* @param {string} stack
*/
constructor(message, code = 500, errorId = uuid(), date = dayjs().format("YYYY-MM-DD HH:mm:ss"), stack = "") {
super(message);
this.code = code || 500;
this.errorId = errorId;
this.date = date;
this.message = process.env.NODE_ENV === "production" ? statusMessage[this.code] || message?.toString() || message : message?.toString() || message || statusMessage[this.code];
this.originalMessage = message;
this.originalStack = stack || new Error().stack;
}
};
module2.exports = HTTPError;
}
});
// ../../common/express-http-error/src/index.js
var require_src2 = __commonJS({
"../../common/express-http-error/src/index.js"(exports2, module2) {
"use strict";
var HTTPError = require_HTTPError();
module2.exports = HTTPError;
}
});
// ../../modules/git/backend/Git.js
var require_Git = __commonJS({
"../../modules/git/backend/Git.js"(exports2, module2) {
"use strict";
var { existsSync } = require("fs");
var pathfs = require("path");
var { execAsync, execAsyncWithoutErr } = require_exec();
var HTTPError = require_src2();
var Git = (stackMonitor) => {
const { findService } = stackMonitor;
const searchGit = (path) => {
if (existsSync(pathfs.resolve(path, ".git"))) return path;
const parentPath = pathfs.resolve(path, "..");
if (parentPath === pathfs.resolve("/")) return null;
return searchGit(parentPath);
};
const getGitRootPath = (service) => searchGit(service.getRootPath());
async function requirements(service) {
if (!service) throw new Error("Git Error: Service not found");
if (!service.git) throw new Error(`Git Error - ${service?.label}: Git option not set`);
const path = getGitRootPath(service);
if (!path) return false;
if (!existsSync(path)) return false;
return true;
}
return {
/**
* @param {string} serviceName
* @param {{graphOnAll?: boolean}} param1
*/
async getGraph(serviceName, { graphOnAll } = {}) {
const service = findService(serviceName);
if (!await requirements(service)) return [];
const cmd = `git -c color.ui=always log --decorate=full --oneline --graph ${graphOnAll ? "--all" : ""} -500`;
const result = await execAsync(cmd, { cwd: getGitRootPath(service), env: process.env });
return result.split("\n");
},
/** @param {string} serviceName */
async getBranches(serviceName, fetch = false) {
const service = findService(serviceName);
if (!await requirements(service)) return [];
if (fetch) await this.fetch(serviceName);
const origin = await this.getOrigin(serviceName);
const unmergeableBranches = ["dev", "develop", "main", "master"];
const currentBranch = await this.getCurrentBranch(service.label);
const mergedBranches = await execAsyncWithoutErr("git branch --no-color --merged develop", { cwd: getGitRootPath(service) }).then((branches) => branches.trim().split("\n").map((a) => a.replace("*", "").trim()));
const localBranches = await execAsyncWithoutErr("git branch --no-color", { cwd: getGitRootPath(service) }).then((res) => res.toString().trim().split("\n").map((branch) => {
const name = branch.replace("*", "").trim();
return {
name,
isRemote: false,
isCurrentBranch: currentBranch === name,
canDelete: !unmergeableBranches.includes(name) && currentBranch !== name,
merged: mergedBranches.includes(name) && !unmergeableBranches.includes(name) && currentBranch !== name
};
}));
const remoteBranches = await execAsyncWithoutErr("git branch --no-color -r", { cwd: getGitRootPath(service) }).then((res) => res.toString().trim().split("\n").map((branch) => {
const name = branch.replace("*", "").trim();
return {
name: name.replace(`${origin}/`, ""),
isRemote: true,
canDelete: false,
merged: false
};
}).filter((remoteBranch) => !localBranches.find((localBranch) => remoteBranch.name === localBranch.name)));
return [
...localBranches,
...remoteBranches
];
},
/** @param {string} serviceName */
async getCurrentBranch(serviceName) {
const service = findService(serviceName);
if (!await requirements(service)) return "";
return (await execAsync("git rev-parse --abbrev-ref HEAD", { cwd: getGitRootPath(service) }).catch((err) => {
}))?.trim();
},
/**
*
* @param {string} serviceName
* @param {string} branchName
* @param {boolean} shouldPush
* @returns
*/
async addBranch(serviceName, branchName, shouldPush = false) {
if (!branchName) throw new Error("Branch name is empty");
const service = findService(serviceName);
if (!await requirements(service)) return "";
const res = (await execAsync(`git checkout -b ${branchName}`, { cwd: getGitRootPath(service) }))?.trim();
if (shouldPush) await this.push(serviceName);
return res;
},
/** @param {string} serviceName */
async getStatus(serviceName) {
const service = findService(serviceName);
if (!await requirements(service)) return [];
return execAsyncWithoutErr("git -c color.status=no status -s", { cwd: getGitRootPath(service) }).then((res) => res.toString().trim().split("\n")?.filter((a) => a));
},
/** @param {string} serviceName */
async getDiff(serviceName) {
const service = findService(serviceName);
if (!await requirements(service)) return "";
return execAsync("git diff --minimal", { cwd: getGitRootPath(service) });
},
/**
* @param {string} serviceName
* @param {string} branchName
*/
async changeBranch(serviceName, branchName) {
const service = findService(serviceName);
if (!await requirements(service)) return "ko";
await execAsync(`git checkout ${branchName}`, { cwd: getGitRootPath(service) });
return "ok";
},
/**
* @param {string} serviceName
* @param {string} branchName
*/
async deleteBranch(serviceName, branchName) {
const service = findService(serviceName);
if (!await requirements(service)) return "ko";
await execAsync(`git branch --delete ${branchName}`, { cwd: getGitRootPath(service) });
return "ok";
},
/**
* @param {string} serviceName
* @param {string} branchName
*/
async remoteDelta(serviceName, branchName) {
const service = findService(serviceName);
if (!await requirements(service)) return 0;
const upstream = await execAsync("git rev-parse --abbrev-ref --symbolic-full-name @{u}", { cwd: getGitRootPath(service) }).catch(() => {
});
if (!upstream) {
throw new HTTPError("BRANCH_NOT_PUSHED", 500.12578);
}
await execAsync(`git fetch origin ${branchName}`, { cwd: getGitRootPath(service) });
const localCommit = await execAsync(`git log --oneline ${branchName}`, { cwd: getGitRootPath(service) });
const remoteCommit = await execAsync(`git log --oneline origin/${branchName}`, { cwd: getGitRootPath(service) });
return localCommit.trim().split("\n").length - remoteCommit.trim().split("\n").length;
},
/** @param {string} serviceName */
async fetch(serviceName) {
const service = findService(serviceName);
if (!await requirements(service)) return "ko";
await execAsync("git fetch", { cwd: getGitRootPath(service) });
return "ok";
},
/** @param {string} serviceName */
async reset(serviceName) {
const service = findService(serviceName);
if (!await requirements(service)) return "ko";
await execAsync("git reset --hard", { cwd: getGitRootPath(service) });
return "ok";
},
/** @param {string} serviceName */
async pull(serviceName) {
const service = findService(serviceName);
if (!await requirements(service)) return "ko";
const origin = await this.getOrigin(serviceName);
const currentBranch = await this.getCurrentBranch(service.label);
await execAsync(`git pull ${origin} ${currentBranch}`, { cwd: getGitRootPath(service) });
return "ok";
},
/** @param {string} serviceName */
async getOrigin(serviceName) {
const service = findService(serviceName);
if (!await requirements(service)) return "";
return (await execAsync("git remote -v", { cwd: getGitRootPath(service) })).trim().split("\n").find((a) => a?.includes("fetch"))?.split(" ")[0]?.trim() || "";
},
/** @param {string} serviceName */
async push(serviceName) {
const service = findService(serviceName);
if (!await requirements(service)) return "ko";
const origin = await this.getOrigin(serviceName);
const currentBranch = await this.getCurrentBranch(service.label);
await execAsync(`git push ${origin} ${currentBranch}`, { cwd: getGitRootPath(service) });
return "ok";
},
/** @param {string} serviceName */
async stash(serviceName) {
const service = findService(serviceName);
if (!await requirements(service)) return "ko";
await execAsync("git add .", { cwd: getGitRootPath(service) });
await execAsync("git stash", { cwd: getGitRootPath(service) });
return "ok";
},
/** @param {string} serviceName */
async stashPop(serviceName) {
const service = findService(serviceName);
if (!await requirements(service)) return "ko";
await execAsync("git stash pop", { cwd: getGitRootPath(service) });
await execAsync("git reset HEAD", { cwd: getGitRootPath(service) });
return "ok";
},
/** @param {string} serviceName */
async stashList(serviceName) {
const service = findService(serviceName);
if (!await requirements(service)) return "";
const list = await execAsync("git stash show", { cwd: getGitRootPath(service) }).catch(() => "");
return list;
},
/**
* @param {string} serviceName
* @param {string} filePath
*/
async checkoutFile(serviceName, filePath) {
const service = findService(serviceName);
if (!await requirements(service)) return "ko";
await execAsync(`git checkout ${filePath}`, { cwd: getGitRootPath(service) });
return "ok";
}
};
};
module2.exports = Git;
}
});
// ../../modules/git/backend/routes.js
var require_routes6 = __commonJS({
"../../modules/git/backend/routes.js"(exports2, module2) {
"use strict";
var express = require("express");
var router = express.Router();
module2.exports = (stackMonitor) => {
const { git } = stackMonitor;
router.get("/git/:service/graph", async (req, res) => {
const graph = await git.getGraph(req.params.service, { graphOnAll: req.query.graphOnAll === "true" });
res.json(graph);
});
router.get("/git/:service/branches", async (req, res) => {
const branches = await git.getBranches(req.params.service);
res.json(branches);
});
router.get("/git/:service/status", async (req, res) => {
const status = await git.getStatus(req.params.service);
res.json(status);
});
router.get("/git/:service/diff", async (req, res) => {
const diff = await git.getDiff(req.params.service);
res.json(diff);
});
router.post("/git/:service/branch/:branchName/change", async (req, res) => {
await git.changeBranch(req.params.service, req.params.branchName).then((result) => res.json(result));
});
router.delete("/git/:service/branch/:branchName", async (req, res) => {
await git.deleteBranch(req.params.service, req.params.branchName).then((result) => res.json(result));
});
router.get("/git/:service/branch/:branchName/remote-delta", async (req, res) => {
await git.remoteDelta(req.params.service, req.params.branchName).then((result) => res.json(result));
});
router.post("/git/:service/fetch", async (req, res) => {
await git.fetch(req.params.service).then((result) => res.json(result));
});
router.delete("/git/:service/reset", async (req, res) => {
await git.reset(req.params.service).then((result) => res.json(result));
});
router.get("/git/:service/current-branch", async (req, res) => {
const currentBranch = await git.getCurrentBranch(req.params.service);
res.json(currentBranch);
});
router.post("/git/:service/add-branch", async (req, res) => {
const currentBranch = await git.addBranch(req.params.service, req.body.name, !!req.body.shouldPush);
res.json(currentBranch);
});
router.post("/git/:service/pull", async (req, res) => {
await git.pull(req.params.service).then((result) => res.json(result));
});
router.post("/git/:service/stash", async (req, res) => {
await git.stash(req.params.service).then((result) => res.json(result));
});
router.post("/git/:service/stash-pop", async (req, res) => {
await git.stashPop(req.params.service).then((result) => res.json(result));
});
router.post("/git/:service/stash-list", async (req, res) => {
await git.stashList(req.params.service).then((result) => res.json(result));
});
router.delete("/git/:service/checkout/:file", async (req, res) => {
await git.checkoutFile(req.params.service, req.params.file.trim()).then((result) => res.json(result));
});
return router;
};
}
});
// ../../modules/git/backend/index.js
var require_backend8 = __commonJS({
"../../modules/git/backend/index.js"(exports2, module2) {
"use strict";
var commandExists = require("command-exists");
var plugin = {
enabled: true,
name: "Git",
displayName: "Git",
description: "View and manage git across whole projects",
icon: "fab fa-git-alt",
export: require_Git(),
placements: ["service", {
position: "sidebar",
label: "GIT",
icon: "fab fa-git-alt",
goTo: { path: "/Git-NotUpToDate" },
active: "Git-NotUpToDate"
}],
order: 2,
hidden: (service, stack, placement) => {
if (placement === "sidebar") return commandExists("git").then(() => false).catch(() => true);
if (placement === "service" && service?.git?.remote) return false;
return true;
},
routes: require_routes6()
};
module2.exports = plugin;
}
});
// ../../modules/github/backend/routes.js
var require_routes7 = __commonJS({
"../../modules/github/backend/routes.js"(exports2, module2) {
"use strict";
var express = require("express");
var { Octokit } = require("fix-esm").require("@octokit/core");
var { restEndpointMethods } = require("fix-esm").require("@octokit/plugin-rest-endpoint-methods");
var router = express.Router();
var MyOctokit = Octokit.plugin(restEndpointMethods);
var octokit = process.env.STACK_MONITOR_GH_APIKEY ? new MyOctokit({ auth: process.env.STACK_MONITOR_GH_APIKEY }) : null;
module2.exports = (stackMonitor) => {
const { findService } = stackMonitor;
router.get("/github/service/:label/ready", async (req, res) => {
requirements();
if (!octokit) return null;
const { data: { login } } = await octokit.rest.users.getAuthenticated();
return res.send(login);
});
router.post("/github/service/:label/apikey", async (req, res) => {
const { apikey } = req.body;
if (!apikey) return res.status(400).send("apikey not found in body");
const _octokit = new Octokit.Octokit({ auth: apikey });
const { data: { login } } = await _octokit.rest.users.getAuthenticated();
octokit = _octokit;
return res.send(login);
});
router.get("/github/service/:label/whoami", async (req, res) => {
requirements();
if (!octokit) return;
const { data: { login } } = await octokit.rest.users.getAuthenticated();
res.json({ loggedAs: login });
});
router.get("/github/service/:label/pull-requests", async (req, res) => {
const service = findService(req.params.label);
if (!service) return res.status(404).send("Service not found");
const [owner, repo] = `${new URL(service.git.home).pathname.replace(".git", "")}`.split("/").slice(-2);
requirements();
if (!octokit) return null;
const { data } = await octokit.rest.pulls.list({ owner, repo });
return res.json(data);
});
return router;
};
function requirements() {
if (!octokit && !process.env.STACK_MONITOR_GH_APIKEY) {
throw new Error("github api is not initialized");
} else if (!octokit) {
octokit = new MyOctokit({ auth: process.env.STACK_MONITOR_GH_APIKEY });
}
}
}
});
// ../../modules/github/backend/index.js
var require_backend9 = __commonJS({
"../../modules/github/backend/index.js"(exports2, module2) {
"use strict";
var plugin = {
enabled: true,
name: "Github",
displayName: "Github",
description: "View pull requests for a given service",
icon: "fab fa-github",
export: null,
placements: ["service"],
order: 3,
/** @param {import('../../../servers/server/models/Service')} service */
hidden: async (service) => {
if (!service) return true;
return !service.git?.remote?.includes("github.com");
},
routes: require_routes7()
};
module2.exports = plugin;
}
});
// ../../modules/global-scripts/backend/GlobalScripts.js
var require_GlobalScripts = __commonJS({
"../../modules/global-scripts/backend/GlobalScripts.js"(exports2, module2) {
"use strict";
var { v4 } = require("uuid");
var globalScripts = [];
var currentScriptsByCommunicationId = {};
var GlobalScripts = (stackMonitor) => {
const { Socket } = stackMonitor;
return {
/** @param {GlobalScript} script */
addScript(script) {
const index = globalScripts.findIndex((s) => s.label === script.label);
if (index >= 0) {
globalScripts.splice(index, 1);
}
globalScripts.push(script);
stackMonitor.Socket.emit("reloadScripts");
},
getScripts() {
return globalScripts;
},
/** @param {string} label */
getScript(label) {
const scripts = this.getScripts();
const script = scripts.find((s) => s.label === label);
return script;
},
/**
* @param {string} scriptId
* @param {string} stepLabel
*/
getStep(scriptId, stepLabel) {
const script = this.getScript(scriptId);
if (!script) return null;
const step = script.pipeline.find((st) => st.id === stepLabel);
if (!step) return null;
return step;
},
/** @param {string} id */
launchScript(id) {
const script = this.getScript(id);
if (!script) throw new Error(`Script ${id} not found`);
const communicationId = v4();
const track = {
scriptId: script.label,
currentStep: script.pipeline[0].id,
loadingStep: script.pipeline[0].id,
steps: {},
output: {},
prompts: {}
};
currentScriptsByCommunicationId[communicationId] = track;
function getStepIndex(step) {
return (script?.pipeline || []).indexOf(step);
}
Socket.on(communicationId, async (socket, event, data) => {
let step = null;
while (step = this.getStep(script.label, track.currentStep)) {
const index = getStepIndex(step);
const nextStep = index >= 0 ? script.pipeline[index + 1]?.id : "";
track.loadingStep = step.id;
track.steps[track.currentStep] = {
...track.steps[track.currentStep],
isValidated: false,
error: void 0,
printData: void 0
};
socket.emit(communicationId, "track", track);
try {
if (step.skip) {
const shouldSkip = await step.skip(track.output, track.prompts);
if (shouldSkip) {
track.steps[track.currentStep].skipped = true;
track.loadingStep = "";
track.currentStep = nextStep;
socket.emit(communicationId, "track", track);
continue;
}
}
} catch (error) {
console.error("Script error");
console.error(error);
track.steps[track.currentStep].error = error.message || error;
break;
}
const shouldBreak = await prompt(step, track, event, data);
if (shouldBreak) break;
if (event === "validate-prompt") event = "";
if (step.script) {
try {
track.output[track.currentStep] = await step.script(track.output, track.prompts);
} catch (error) {
console.error("Script error");
console.error(error);
track.steps[track.currentStep].error = error.message || error;
break;
}
}
if (step.print) {
try {
track.steps[track.currentStep].printData = await step.print(track.output, track.prompts);
await new Promise((resolve) => setTimeout(() => {
resolve(null);
}, 0));
} catch (error) {
console.error("Print error");
console.error(error);
track.steps[track.currentStep].error = error.message || error;
break;
}
}
track.steps[track.currentStep].isValidated = true;
track.currentStep = nextStep;
}
track.loadingStep = "";
socket.emit(communicationId, "track", track);
});
return communicationId;
}
};
};
module2.exports = GlobalScripts;
async function prompt(step, track, event, data) {
if (!step.prompt) return false;
if (event === "validate-prompt") {
if (step.prompt?.validation) {
try {
const msgError = await step.prompt.validation(data);
if (msgError) {
track.steps[track.currentStep].error = msgError;
return true;
}
track.prompts[track.currentStep] = data;
} catch (error) {
console.error("Script error");
console.error(error);
track.steps[track.currentStep].error = error.message || error;
return true;
}
} else {
track.prompts[track.currentStep] = data;
}
return false;
}
if (step.prompt.defaultValue) {
try {
const defaultValue = await step.prompt.defaultValue(track.output);
track.steps[track.currentStep].promptValue = defaultValue;
} catch (error) {
console.error("Script error");
console.error(error);
track.steps[track.currentStep].error = error.message || error;
return true;
}
}
if (step.prompt.options) {
try {
const options = await step.prompt.options(track.output);
track.steps[track.currentStep].promptOptions = options;
} catch (error) {
console.error("Script error");
console.error(error);
track.steps[track.currentStep].error = error.message || error;
return true;
}
}
return true;
}
}
});
// ../../modules/global-scripts/backend/routes.js
var require_routes8 = __commonJS({
"../../modules/global-scripts/backend/routes.js"(exports2, module2) {
"use strict";
var express = require("express");
var router = express.Router();
module2.exports = (Stack) => {
const { globalScripts } = Stack;
router.get("/global-scripts/", async (req, res) => {
res.json(globalScripts.getScripts());
});
router.get("/global-scripts/:id", async (req, res) => {
const script = await globalScripts.getScript(req.params.id);
res.json(script);
});
router.post("/global-scripts/:id", async (req, res) => {
const communicationId = await globalScripts.launchScript(req.params.id);
res.json(communicationId);
});
return router;
};
}
});
// ../../modules/global-scripts/backend/index.js
var require_backend10 = __commonJS({
"../../modules/global-scripts/backend/index.js"(exports2, module2) {
"use strict";
var GlobalScripts = require_GlobalScripts();
var plugin = {
enabled: true,
name: "Global scripts",
displayName: "Global scripts",
description: "Launch scripts and rule the world",
icon: "fas fa-columns",
export: GlobalScripts,
placements: [
{
position: "dev-ops",
label: "Global scripts",
iconText: "{}",
goTo: { path: "/GlobalScripts" },
active: "GlobalScripts"
}
],
finder: (search, stackMonitor) => {
const scripts = stackMonitor.globalScripts.getScripts().filter((script) => script.label.toUpperCase()?.includes(search?.toUpperCase()));
return [
...scripts.map((script) => ({
icon: "fas fa-cog",
title: script.label,
group: "Global scripts",
description: "",
secondaryTitle: "",
url: { path: "/DevOps/GlobalScripts", query: { script: script.label } }
}))
];
},
routes: require_routes8()
};
module2.exports = plugin;
}
});
// ../../modules/http-client/backend/HttpClient.js
var require_HttpClient = __commonJS({
"../../modules/http-client/backend/HttpClient.js"(exports2, module2) {
"use strict";
var axios = require("axios").default;
var HttpClient = (stackMonitor) => ({
/**
* Send HTTP request
* @param {Object} options - Request options
* @param {string} options.method - HTTP method (GET, POST, PUT, DELETE, etc.)
* @param {string} options.url - Request URL
* @param {Object} [options.headers] - Request headers
* @param {Object|string} [options.body] - Request body
* @param {Object} [options.params] - Query parameters
* @returns {Promise<Object>} Response data
*/
sendRequest: async ({ method, url, headers = {}, body = null, params = {} }) => {
try {
const config = {
method,
url,
headers,
params
};
if (body) {
config.data = body;
}
const startTime = Date.now();
const response = await axios(config);
const endTime = Date.now();
return {
status: response.status,
statusText: response.statusText,
headers: response.headers,
data: response.data,
responseTime: endTime - startTime
};
} catch (error) {
if (error && typeof error === "object" && "response" in error) {
const axiosError = (
/** @type {import('axios').AxiosError} */
error
);
return {
status: axiosError.response?.status,
statusText: axiosError.response?.statusText,
headers: axiosError.response?.headers,
data: axiosError.response?.data,
error: true
};
}
throw error;
}
}
});
module2.exports = HttpClient;
}
});
// ../../modules/http-client/backend/routes.js
var require_routes9 = __commonJS({
"../../modules/http-client/backend/routes.js"(exports2, module2) {
"use strict";
var express = require("express");
var router = express.Router();
var HttpClient = require_HttpClient();
module2.exports = (stackMonitor) => {
const httpClient = HttpClient(stackMonitor);
router.post("/http-client/request", async (req, res) => {
try {
const { method, url, headers, body, params } = req.body;
const response = await httpClient.sendRequest({
method,
url,
headers,
body,
params
});
res.json(response);
} catch (error) {
res.status(500).json({
error: true,
message: error instanceof Error ? error.message : "Unknown error",
stack: error instanceof Error ? error.stack : void 0
});
}
});
router.get("/http-client/history", (req, res) => {
res.json([]);
});
router.post("/http-client/history", (req, res) => {
res.json({ success: true });
});
return router;
};
}
});
// ../../modules/http-client/backend/index.js
var require_backend11 = __commonJS({
"../../modules/http-client/backend/index.js"(exports2, module2) {
"use strict";
var plugin = {
enabled: true,
name: "HttpClient",
displayName: "HTTP Client",
description: "Test HTTP endpoints like Postman",
icon: "fas fa-exchange-alt",
placements: [
{
position: "toolbox",
label: "HTTP Client",
icon: "fas fa-exchange-alt",
goTo: { path: "/HttpClient" },
active: "HttpClient"
}
],
export: require_HttpClient(),
order: 7,
routes: require_routes9()
};
module2.exports = plugin;
}
});
// ../../modules/json-formatter/backend/JSONFormatter.js
var require_JSONFormatter = __commonJS({
"../../modules/json-formatter/backend/JSONFormatter.js"(exports2, module2) {
"use strict";
var fs = require("fs");
var path = require("path");
var yaml = require("js-yaml");
var xmljs = require("xml-js");
var Ajv = require("ajv").default;
var JSONFormatter = class {
/**
* Validate JSON against a schema
* @param {Object} json - The JSON object to validate
* @param {Object} schema - The JSON schema
* @returns {Object} Validation result
*/
validateSchema(json, schema) {
const ajv = new Ajv();
const validate = ajv.compile(schema);
const valid = validate(json);
return {
valid,
errors: validate.errors || []
};
}
/**
* Convert JSON to YAML
* @param {Object} json - The JSON object to convert
* @returns {string} YAML string
*/
jsonToYaml(json) {
return yaml.dump(json);
}
/**
* Convert YAML to JSON
* @param {string} yamlStr - The YAML string to convert
* @returns {Object} JSON object
*/
yamlToJson(yamlStr) {
return yaml.load(yamlStr);
}
/**
* Convert JSON to XML
* @param {Object} json - The JSON object to convert
* @returns {string} XML string
*/
jsonToXml(json) {
return xmljs.js2xml(json, { compact: true, spaces: 2 });
}
/**
* Convert XML to JSON
* @param {string} xmlStr - The XML string to convert
* @returns {Object} JSON object
*/
xmlToJson(xmlStr) {
return xmljs.xml2js(xmlStr, { compact: true });
}
/**
* Compress JSON (remove whitespace)
* @param {Object|string} json - The JSON object or string to compress
* @returns {string} Compressed JSON string
*/
compressJson(json) {
const jsonObj = typeof json === "string" ? JSON.parse(json) : json;
return JSON.stringify(jsonObj);
}
/**
* Pretty print JSON (add indentation)
* @param {Object|string} json - The JSON object or string to format
* @param {number} indentation - Number of spaces for indentation
* @returns {string} Formatted JSON string
*/
prettyPrintJson(json, indentation = 2) {
const jsonObj = typeof json === "string" ? JSON.parse(json) : json;
return JSON.stringify(jsonObj, null, indentation);
}
/**
* Calculate statistics about a JSON object
* @param {Object} json - The JSON object
* @returns {Object} Statistics object
*/
getJsonStats(json) {
const stats = {
size: 0,
stringifiedSize: 0,
depth: 0,
keys: 0,
arrays: 0,
objects: 0,
primitives: 0
};
stats.stringifiedSize = JSON.stringify(json).length;
stats.size = Buffer.from(JSON.stringify(json)).length;
const traverse = (obj, depth = 0) => {
if (depth > stats.depth) stats.depth = depth;
if (Array.isArray(obj)) {
stats.arrays++;
for (const item of obj) {
if (typeof item === "object" && item !== null) {
traverse(item, depth + 1);
} else {
stats.primitives++;
}
}
} else if (typeof obj === "object" && obj !== null) {
stats.objects++;
const objKeys = Object.keys(obj);
stats.keys += objKeys.length;
for (const key of objKeys) {
if (typeof obj[key] === "object" && obj[key] !== null) {
traverse(obj[key], depth + 1);
} else {
stats.primitives++;
}
}
}
};
traverse(json);
return stats;
}
/**
* Query JSON using JSONPath
* @param {Object} json - The JSON object
* @param {string} query - JSONPath query
* @returns {Array} Query results
*/
queryJson(json, query) {
if (query === "$") return json;
return [{ message: "JSONPath implementation would return results here" }];
}
};
module2.exports = new JSONFormatter();
}
});
// ../../modules/json-formatter/backend/routes.js
var require_routes10 = __commonJS({
"../../modules/json-formatter/backend/routes.js"(exports2, module2) {
"use strict";
var express = require("express");
var router = express.Router();
var JSONFormatter = require_JSONFormatter();
module2.exports = function(stackMonitor) {
router.post("/JSONFormatterEnhanced/validate", (req, res) => {
try {
const { json, schema } = req.body;
const result = JSONFormatter.validateSchema(json, schema);
res.json(result);
} catch (error) {
res.status(400).json({ error: error instanceof Error ? error.message : String(error) });
}
});
router.post("/JSONFormatterEnhanced/to-yaml", (req, res) => {
try {
const { json } = req.body;
const yaml = JSONFormatter.jsonToYaml(json);
res.json({ yaml });
} catch (error) {
res.status(400).json({ error: error instanceof Error ? error.message : String(error) });
}
});
router.post("/JSONFormatterEnhanced/from-yaml", (req, res) => {
try {
const { yaml } = req.body;
const json = JSONFormatter.yamlToJson(yaml);
res.json({ json });
} catch (error) {
res.status(400).json({ error: error instanceof Error ? error.message : String(error) });
}
});
router.post("/JSONFormatterEnhanced/to-xml", (req, res) => {
try {
const { json } = req.body;
const xml = JSONFormatter.jsonToXml(json);
res.json({ xml });
} catch (error) {
res.status(400).json({ error: error instanceof Error ? error.message : String(error) });
}
});
router.post("/JSONFormatterEnhanced/from-xml", (req, res) => {
try {
const { xml } = req.body;
const json = JSONFormatter.xmlToJson(xml);
res.json({ json });
} catch (error) {
res.status(400).json({ error: error instanceof Error ? error.message : String(error) });
}
});
router.post("/JSONFormatterEnhanced/stats", (req, res) => {
try {
const { json } = req.body;
const stats = JSONFormatter.getJsonStats(json);
res.json(stats);
} catch (error) {
res.status(400).json({ error: error instanceof Error ? error.message : String(error) });
}
});
router.post("/JSONFormatterEnhanced/query", (req, res) => {
try {
const { json, query } = req.body;
const results = JSONFormatter.queryJson(json, query);
res.json(results);
} catch (error) {
res.status(400).json({ error: error instanceof Error ? error.message : String(error) });
}
});
return router;
};
}
});
// ../../modules/json-formatter/backend/index.js
var require_backend12 = __commonJS({
"../../modules/json-formatter/backend/index.js"(exports2, module2) {
"use strict";
var plugin = {
enabled: true,
name: "JSONFormatter",
displayName: "JSON",
description: "View, edit, validate, transform and explore JSON data",
icon: "fas fa-brackets-curly",
export: require_JSONFormatter(),
placements: [
{
position: "toolbox",
label: "JSON",
iconText: "{}",
goTo: { path: "/JSONFormatter" },
active: "JSONFormatter"
}
],
routes: require_routes10(),
order: 6
};
module2.exports = plugin;
}
});
// ../../modules/jwt/backend/JWT.js
var require_JWT = __commonJS({
"../../modules/jwt/backend/JWT.js"(exports2, module2) {
"use strict";
var jsonwebtoken = require("jsonwebtoken");
var JWT = class {
/**
* Decode a JWT without verifying signature
* @param {string} token JWT token string
* @returns {object} Decoded JWT payload
*/
decode(token) {
return jsonwebtoken.decode(token, { complete: true });
}
/**
* Verify and decode a JWT token
* @param {string} token JWT token string
* @param {string} secret Secret key for verification
* @param {object} options JWT verify options
* @returns {object} Decoded JWT if valid
*/
verify(token, secret, options = {}) {
try {
return jsonwebtoken.verify(token, secret, options);
} catch (err) {
const error = err instanceof Error ? err.message : String(err);
return { error };
}
}
/**
* Generate a new JWT token
* @param {object} payload Data to encode in the token
* @param {string} secret Secret key for signing
* @param {object} options JWT sign options
* @returns {string} Signed JWT token
*/
generate(payload, secret, options = {}) {
return jsonwebtoken.sign(payload, secret, options);
}
/**
* Analyze token structure and details
* @param {string} token JWT token string
* @returns {object} Analysis results
*/
analyze(token) {
try {
const decoded = this.decode(token);
if (!decoded) return { error: "Invalid token format" };
const parts = token.split(".");
const analysis = {
structure: {
header: decoded.header,
payload: decoded.payload,
signature: parts[2] ? true : false
},
validation: {
hasExpiry: decoded.payload.exp ? true : false,
hasIssuedAt: decoded.payload.iat ? true : false,
hasNotBefore: decoded.payload.nbf ? true : false
}
};
if (analysis.validation.hasExpiry) {
const expiry = new Date(decoded.payload.exp * 1e3);
const now = /* @__PURE__ */ new Date();
analysis.validation.isExpired = now > expiry;
analysis.validation.expiresIn = analysis.validation.isExpired ? "Expired" : this.formatTimeRemaining(expiry.getTime() - now.getTime());
}
if (decoded.header.alg) {
analysis.security = {
algorithm: decoded.header.alg,
isSecure: decoded.header.alg !== "none" && !decoded.header.alg.startsWith("HS")
};
}
return analysis;
} catch (err) {
const error = err instanceof Error ? err.message : String(err);
return { error };
}
}
/**
* Format milliseconds to human readable time
* @param {number} ms Milliseconds
* @returns {string} Formatted time string
*/
formatTimeRemaining(ms) {
if (ms <= 0) return "Expired";
const seconds = Math.floor(ms / 1e3);
const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60);
const days = Math.floor(hours / 24);
if (days > 0) return `${days} day(s)`;
if (hours > 0) return `${hours} hour(s)`;
if (minutes > 0) return `${minutes} minute(s)`;
return `${seconds} second(s)`;
}
};
module2.exports = new JWT();
}
});
// ../../modules/jwt/backend/routes.js
var require_routes11 = __commonJS({
"../../modules/jwt/backend/routes.js"(exports2, module2) {
"use strict";
var express = require("express");
var JWT = require_JWT();
var router = express.Router();
module2.exports = () => {
router.post("/JWT/", async (req, res) => {
const { jwt } = req.body;
try {
res.json(JWT.decode(jwt));
} catch (err) {
res.status(400).json({ error: err instanceof Error ? err.message : String(err) });
}
});
router.post("/JWT/analyze", async (req, res) => {
const { jwt } = req.body;
try {
res.json(JWT.analyze(jwt));
} catch (err) {
res.status(400).json({ error: err instanceof Error ? err.message : String(err) });
}
});
router.post("/JWT/verify", async (req, res) => {
const { jwt, secret, options } = req.body;
try {
res.json(JWT.verify(jwt, secret, options));
} catch (err) {
res.status(400).json({ error: err instanceof Error ? err.message : String(err) });
}
});
router.post("/JWT/generate", async (req, res) => {
const { payload, secret, options } = req.body;
try {
const token = JWT.generate(payload, secret, options);
res.json({ token });
} catch (err) {
res.status(400).json({ error: err instanceof Error ? err.message : String(err) });
}
});
return router;
};
}
});
// ../../modules/jwt/backend/index.js
var require_backend13 = __commonJS({
"../../modules/jwt/backend/index.js"(exports2, module2) {
"use strict";
var plugin = {
enabled: true,
name: "JWT",
displayName: "JWT",
description: "Decode, verify and create JSON Web Tokens",
icon: "fas fa-key",
export: require_JWT(),
placements: [
{
position: "toolbox",
label: "JWT",
icon: "fas fa-key",
goTo: { path: "/JWT" },
active: "JWT"
}
],
order: 6,
routes: require_routes11()
};
module2.exports = plugin;
}
});
// ../../modules/kanban/backend/save.js
var require_save = __commonJS({
"../../modules/kanban/backend/save.js"(exports2, module2) {
"use strict";
var kanban = {
/** @param {import('@clabroche/common-typings').StackMonitor} stackMonitor */
getSave(stackMonitor) {
return stackMonitor.getSave("kanban.json", {
/** @type {Partial<import('@clabroche/common-typings').NonFunctionProperties<import('./Kanban').BoardType>['prototype']>[]} */
boards: [],
/** @type {Partial<import('@clabroche/common-typings').NonFunctionProperties<import('./Kanban').ColumnType>['prototype']>[]} */
columns: [],
/** @type {Partial<import('@clabroche/common-typings').NonFunctionProperties<import('./Kanban').CardType>['prototype']>[]} */
cards: []
}, {
afterGet(data) {
if (!data.boards) data.boards = [];
if (!data.columns) data.columns = [];
if (!data.cards) data.cards = [];
},
beforeSave(data) {
if (!data.boards) data.boards = [];
if (!data.columns) data.columns = [];
if (!data.cards) data.cards = [];
}
});
}
};
module2.exports = kanban;
}
});
// ../../modules/kanban/backend/Kanban.js
var require_Kanban = __commonJS({
"../../modules/kanban/backend/Kanban.js"(exports2, module2) {
"use strict";
var { v4 } = require("uuid");
var { getSave: _getSave } = require_save();
var stackMonitor = null;
function getSave() {
if (!stackMonitor) throw new Error("Call init before");
const { data, save } = _getSave(stackMonitor);
return { data, save };
}
var Board = class _Board {
/** @param {Partial<import('@clabroche/common-typings').NonFunctionProperties<Board>>} board */
constructor(board = {}) {
this.id = board.id || v4();
this.name = board.name;
this.columnIds = board.columnIds || [];
}
/** @returns {Board[]} */
static all() {
const { data } = getSave();
return data.boards.map((b) => b ? new _Board(b) : null).filter((a) => a);
}
/** @param {string} id */
static get(id) {
const { data } = getSave();
const board = data.boards.find((d) => d?.id === id);
return board ? new _Board(board) : null;
}
/**
*
* @returns {Column[]}
*/
getColumns() {
return this.columnIds.map((columnId) => Column.get(columnId)).filter((a) => a);
}
delete() {
const { data, save } = getSave();
this.getColumns().forEach((c) => c.delete());
data.boards = data.boards.filter((b) => b?.id !== this.id);
save();
}
/** @param {string} id */
getColumn(id) {
return this.getColumns().find((c) => c?.id === id);
}
/** @param {Partial<import('@clabroche/common-typings').NonFunctionProperties<Column>>} column */
addColumn(column) {
const savedColumn = new Column({ ...column, boardId: this.id }).save();
this.columnIds = [.../* @__PURE__ */ new Set([...this.columnIds, savedColumn.id])];
this.save();
return savedColumn;
}
save() {
const { data, save } = getSave();
const existing = data.boards.find((b) => b?.id === this.id);
if (existing) {
Object.assign(existing, { ...this });
} else {
data.boards.push(this);
}
save();
return this;
}
};
var Column = class _Column {
/** @param {Partial<import('@clabroche/common-typings').NonFunctionProperties<Column>>} column */
constructor(column = {}) {
this.id = column.id || v4();
this.name = column.name;
this.color = column.color;
this.boardId = column.boardId;
this.cardIds = column.cardIds || [];
}
/** @param {string} id */
static get(id) {
const { data } = getSave();
const column = data.columns.find((d) => d?.id === id);
return column ? new _Column(column) : null;
}
/** @returns {Card[]} */
getCards() {
return this.cardIds.map((columnId) => Card.get(columnId)).filter((a) => a);
}
/** @param {string} id */
getCard(id) {
return this.getCards().find((c) => c?.id === id);
}
/** @param {Partial<import('@clabroche/common-typings').NonFunctionProperties<Card>>} card */
addCard(card) {
const savedColumn = new Card({ ...card, boardId: this.boardId, columnId: this.id }).save();
this.cardIds = [.../* @__PURE__ */ new Set([...this.cardIds, savedColumn.id])];
this.save();
return savedColumn;
}
delete() {
const { data, save } = getSave();
this.getCards().forEach((c) => c.delete());
data.columns = data.columns.filter((b) => b?.id !== this.id);
save();
}
save() {
const { data, save } = getSave();
const existing = data.columns.find((b) => b?.id === this.id);
if (existing) {
Object.assign(existing, { ...this });
} else {
data.columns.push(this);
}
save();
return this;
}
};
var Card = class _Card {
/** @param {Partial<import('@clabroche/common-typings').NonFunctionProperties<Card>>} card */
constructor(card = {}) {
this.id = card.id || v4();
this.name = card.name;
this.description = card.description;
this.boardId = card.boardId;
this.columnId = card.columnId;
}
/** @param {string} id */
static get(id) {
const { data } = getSave();
const cards = data.cards.find((d) => d?.id === id);
return cards ? new _Card(cards) : null;
}
delete() {
const { data, save } = getSave();
data.cards = data.cards.filter((b) => b?.id !== this.id);
save();
}
save() {
const { data, save } = getSave();
const existing = data.cards.find((b) => b?.id === this.id);
if (existing) {
Object.assign(existing, { ...this });
} else {
data.cards.push(this);
}
save();
return this;
}
};
var Kanban = (_stackMonitor) => {
stackMonitor = _stackMonitor;
return {
Card,
Column,
Board
};
};
module2.exports = Kanban;
}
});
// ../../modules/kanban/backend/routes.js
var require_routes12 = __commonJS({
"../../modules/kanban/backend/routes.js"(exports2, module2) {
"use strict";
var express = require("express");
var router = express.Router();
module2.exports = (stackMonitor) => {
const { Board } = require_Kanban()(stackMonitor);
router.get("/kanban/boards", async (req, res) => {
res.json(Board.all());
});
router.post("/kanban/boards", async (req, res) => {
const board = new Board(req.body).save();
res.json(board);
});
router.delete("/kanban/boards/:boardId", async (req, res) => {
const board = Board.get(req.params.boardId);
board?.delete();
res.json(board);
});
router.get("/kanban/boards/:boardId/columns", async (req, res) => {
res.json(Board.get(req.params.boardId)?.getColumns());
});
router.get("/kanban/boards/:boardId/columns/:columnId", async (req, res) => {
res.json(Board.get(req.params.boardId)?.getColumn(req.params.columnId));
});
router.delete("/kanban/boards/:boardId/columns/:columnId", async (req, res) => {
const column = Board.get(req.params.boardId)?.getColumn(req.params.columnId);
column?.delete();
res.json(column);
});
router.post("/kanban/boards/:boardId/columns", async (req, res) => {
const board = Board.get(req.params.boardId)?.addColumn(req.body);
res.json(board);
});
router.get("/kanban/boards/:boardId/columns/:columnId/cards", async (req, res) => {
res.json(Board.get(req.params.boardId)?.getColumn(req.params.columnId)?.getCards());
});
router.delete("/kanban/boards/:boardId/columns/:columnId/cards/:cardId", async (req, res) => {
const card = Board.get(req.params.boardId)?.getColumn(req.params.columnId)?.getCard(req.params.cardId);
card?.delete();
res.json(card);
});
router.post("/kanban/boards/:boardId/columns/:columnId/cards", async (req, res) => {
res.json(Board.get(req.params.boardId)?.getColumn(req.params.columnId)?.addCard(req.body));
});
return router;
};
}
});
// ../../modules/kanban/backend/index.js
var require_backend14 = __commonJS({
"../../modules/kanban/backend/index.js"(exports2, module2) {
"use strict";
var Kanban = require_Kanban();
var plugin = {
enabled: true,
name: "kanban",
displayName: "Kanban",
description: "Manage your project with cards",
icon: "fas fa-columns",
placements: [
{
position: "toolbox",
label: "Kanban",
icon: "fas fa-columns",
goTo: { path: "/kanban" },
active: "kanban"
}
],
finder: (search, stackMonitor) => {
const boards = Kanban(stackMonitor).Board.all().filter((board) => stackMonitor.helpers.searchString(board?.name || "", search));
const cards = Kanban(stackMonitor).Board.all().flatMap((b) => b?.getColumns())?.flatMap((c) => c?.getCards())?.filter((board) => stackMonitor.helpers.searchString(board?.name || "", search));
return [
...boards.map((board) => ({
icon: "fas fa-columns",
title: board?.name || "",
group: "Kanban",
description: "Manage your project with cards",
secondaryTitle: "Board",
url: { path: "/toolbox/kanban", query: { boardId: board?.id } }
})),
...cards.map((card) => ({
icon: "fas fa-columns",
title: card?.name || "",
group: "Kanban",
description: "Manage your project with cards",
secondaryTitle: "Card",
url: { path: "/toolbox/kanban", query: { boardId: card?.boardId, cardId: card?.id } }
}))
];
},
export: Kanban,
order: 6,
routes: require_routes12()
};
module2.exports = plugin;
}
});
// ../../modules/logs/backend/routes.js
var require_routes13 = __commonJS({
"../../modules/logs/backend/routes.js"(exports2, module2) {
"use strict";
var express = require("express");
var { v4 } = require("uuid");
var router = express.Router();
module2.exports = (stackMonitor) => {
const history = stackMonitor.getSave("history.json", {
/** @type {History[]} */
history: []
});
const { findService, Socket } = stackMonitor;
router.get("/logs/:service/logs", (req, res) => {
const service = findService(req.params.service);
res.send(service ? service.store : []);
});
router.get("/logs/:service/autocomplete", (req, res) => {
const msg = req.query.message;
if (!msg && !req.query.force) return res.json([]);
const historyToSend = groupBy(history.data.history, "raw").sort((a, b) => a.timestamp - b.timestamp).filter((a) => a?.raw?.startsWith(msg) || a.cmd?.startsWith(msg)).slice(-10);
return res.json(historyToSend);
});
router.post("/logs/:service/prompt", async (req, res) => {
const service = findService(req.params.service);
let command = req.body.command || {};
const pid = req.body.pid ? +req.body.pid : void 0;
if (!command.spawnCmd) command.spawnCmd = "\n";
let result = {
id: v4(),
pid,
cmd: command.spawnCmd,
args: [],
raw: command.spawnCmd,
timestamp: Date.now(),
service: service.label || ""
};
if (pid) service.respondToProcess(pid, command.spawnCmd);
else if (command) {
const { spawnProcess, launchMessage } = await service.launchProcess(
{ spawnCmd: command.spawnCmd, spawnArgs: command.spawnArgs || [], spawnOptions: command.spawnOptions || {} },
false
);
result = {
...result,
pid: spawnProcess?.pid,
raw: launchMessage.raw,
timestamp: launchMessage.timestamp
};
history.data.history.push(result);
history.save();
}
res.json(result);
});
router.post("/logs/:service/terminate", (req, res) => {
const service = findService(req.params.service);
const {
/** @type {number | undefined} */
pid,
/** @type {boolean | undefined} */
forceKill
} = req.body;
if (!pid) throw new Error("Pid is required");
if (pid) service.terminate(pid, !!forceKill);
Socket.emit("logs:update", []);
res.send("ok");
});
router.delete("/logs/:service/logs", (req, res) => {
const service = findService(req.params.service);
service.store = [];
Socket.emit("logs:clear", { label: service.label });
res.send(service.store);
});
return router;
};
var groupBy = (xs, key) => {
const group = xs.reduce((rv, x) => {
if (!rv[x[key]]) rv[x[key]] = { ...x, number: 0, timestamps: [] };
rv[x[key]].timestamps.push(x.timestamp);
rv[x[key]].timestamp = x.timestamp;
return rv;
}, {});
return Object.keys(group).map((key2) => group[key2]);
};
}
});
// ../../modules/logs/backend/index.js
var require_backend15 = __commonJS({
"../../modules/logs/backend/index.js"(exports2, module2) {
"use strict";
var dayjs = require("dayjs");
var plugin = {
enabled: true,
name: "Logs",
displayName: "Logs",
description: "Show, parse and communicate with logs produced by yours commands",
icon: "fas fa-terminal",
export: null,
placements: ["service"],
order: 1,
/**
* @param {*} search
* @param {import('@clabroche/common-typings').StackMonitor} stackMonitor
*/
finder: (search, stackMonitor) => {
const services = stackMonitor.getServices()?.flatMap((s) => s.store?.slice(-300).reverse().map((line) => ({ log: line, service: s })))?.filter(({ log }) => stackMonitor.helpers.searchString(log.raw, search));
return [
...services.map((service) => ({
icon: "fas fa-terminal",
title: service.log.raw,
group: "Logs",
description: `Log from ${service.service.label}`,
secondaryTitle: service.service.label,
secondaryDescription: dayjs(service.log.timestamp).format("YYYY-DD-MM HH:mm:ss"),
url: { path: `/stack-single/${encodeURIComponent(service.service.label)}`, query: { tab: "Logs" } }
}))
];
},
routes: require_routes13()
};
module2.exports = plugin;
}
});
// ../../modules/mongo/backend/Mongo.js
var require_Mongo = __commonJS({
"../../modules/mongo/backend/Mongo.js"(exports2, module2) {
"use strict";
var { ObjectId } = require("mongodb");
var { MongoClient } = require("mongodb");
var MongoDB = class {
/**
* Generate a new ObjectId
* @returns {ObjectId} A new MongoDB ObjectId
*/
generateObjectId() {
return new ObjectId();
}
/**
* Check if a string is a valid MongoDB ObjectId
* @param {string} id - The string to check
* @returns {boolean} True if valid, false otherwise
*/
isValidObjectId(id) {
if (!id) return false;
try {
new ObjectId(id);
return true;
} catch (error) {
return false;
}
}
/**
* Decode an ObjectId to get its components
* @param {string} objectId - The ObjectId to decode
* @returns {Object} The decoded information
*/
decodeObjectId(objectId) {
if (!this.isValidObjectId(objectId)) {
return {
isValid: false,
error: "Invalid ObjectId format"
};
}
try {
const timestamp = parseInt(objectId.substring(0, 8), 16);
const date = new Date(timestamp * 1e3);
const buffer = Buffer.from(objectId, "hex");
return {
isValid: true,
timestamp,
date,
machineId: objectId.substring(8, 14),
processId: objectId.substring(14, 18),
counter: objectId.substring(18, 24),
format: {
hex: objectId,
binary: buffer.toString("binary"),
base64: buffer.toString("base64")
}
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
isValid: false,
error: errorMessage
};
}
}
/**
* Create an ObjectId from a timestamp
* @param {Date|string} date - The date to use
* @returns {string} The generated ObjectId
*/
objectIdFromDate(date) {
const timestamp = Math.floor(new Date(date).getTime() / 1e3);
return `${timestamp.toString(16)}0000000000000000`;
}
/**
* Compare two ObjectIds
* @param {string} objectId1 - First ObjectId
* @param {string} objectId2 - Second ObjectId
* @returns {Object} Comparison results
*/
compareObjectIds(objectId1, objectId2) {
const obj1 = this.decodeObjectId(objectId1);
const obj2 = this.decodeObjectId(objectId2);
if (!obj1.isValid || !obj2.isValid) {
return {
isValid: false,
error: "One or both ObjectIds are invalid"
};
}
const timeDifference = obj1.timestamp - obj2.timestamp;
const millisecondsDiff = timeDifference * 1e3;
return {
isValid: true,
timeDifference: {
seconds: timeDifference,
milliseconds: millisecondsDiff,
humanReadable: this.formatTimeDifference(millisecondsDiff)
},
sameServer: obj1.machineId === obj2.machineId,
sameProcess: obj1.processId === obj2.processId,
counterDifference: parseInt(obj1.counter, 16) - parseInt(obj2.counter, 16)
};
}
/**
* Format time difference in a human readable format
* @param {number} milliseconds - Time difference in milliseconds
* @returns {string} Formatted time difference
*/
formatTimeDifference(milliseconds) {
const absMs = Math.abs(milliseconds);
const prefix = milliseconds >= 0 ? "newer by " : "older by ";
if (absMs < 1e3) return `${prefix}${absMs} milliseconds`;
if (absMs < 6e4) return `${prefix}${Math.floor(absMs / 1e3)} seconds`;
if (absMs < 36e5) return `${prefix}${Math.floor(absMs / 6e4)} minutes`;
if (absMs < 864e5) return `${prefix}${Math.floor(absMs / 36e5)} hours`;
return `${prefix}${Math.floor(absMs / 864e5)} days`;
}
/**
* Test MongoDB connection
* @param {string} connectionString - MongoDB connection string
* @returns {Promise<Object>} Connection test results
*/
async testConnection(connectionString) {
let client;
try {
client = new MongoClient(connectionString, {
serverSelectionTimeoutMS: 5e3,
connectTimeoutMS: 5e3
});
await client.connect();
const admin = client.db().admin();
const serverInfo = await admin.serverInfo();
const dbList = await client.db().admin().listDatabases();
return {
success: true,
version: serverInfo.version,
databases: dbList.databases.map((db) => ({
name: db.name,
sizeOnDisk: db.sizeOnDisk,
empty: db.empty
}))
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
success: false,
error: errorMessage
};
} finally {
if (client) await client.close();
}
}
};
module2.exports = new MongoDB();
}
});
// ../../modules/mongo/backend/routes.js
var require_routes14 = __commonJS({
"../../modules/mongo/backend/routes.js"(exports2, module2) {
"use strict";
var express = require("express");
var MongoDB = require_Mongo();
var router = express.Router();
router.get("/mongo/generate", async (req, res) => {
res.json(MongoDB.generateObjectId());
});
router.post("/mongo/validate", async (req, res) => {
const { objectId } = req.body;
res.json({
isValid: MongoDB.isValidObjectId(objectId)
});
});
router.post("/mongo/decode", async (req, res) => {
const { objectId } = req.body;
res.json(MongoDB.decodeObjectId(objectId));
});
router.post("/mongo/from-date", async (req, res) => {
const { date } = req.body;
res.json({
objectId: MongoDB.objectIdFromDate(date)
});
});
router.post("/mongo/compare", async (req, res) => {
const { objectId1, objectId2 } = req.body;
res.json(MongoDB.compareObjectIds(objectId1, objectId2));
});
router.post("/mongo/test-connection", async (req, res) => {
const { connectionString } = req.body;
try {
const result = await MongoDB.testConnection(connectionString);
res.json(result);
} catch (error) {
res.status(500).json({
success: false,
error: error instanceof Error ? error.message : String(error)
});
}
});
module2.exports = () => router;
}
});
// ../../modules/mongo/backend/index.js
var require_backend16 = __commonJS({
"../../modules/mongo/backend/index.js"(exports2, module2) {
"use strict";
var plugin = {
enabled: true,
name: "Mongo",
displayName: "MongoDB",
description: "MongoDB toolkit with ObjectID operations, validation, comparison and connection testing",
icon: "fab fa-envira",
export: null,
placements: [
{
position: "toolbox",
label: "MongoDB",
icon: "fab fa-envira",
goTo: { path: "/Mongo" },
active: "Mongo"
}
],
order: 6,
routes: require_routes14()
};
module2.exports = plugin;
}
});
// ../../modules/node-repl/backend/routes.js
var require_routes15 = __commonJS({
"../../modules/node-repl/backend/routes.js"(exports2, module2) {
"use strict";
var express = require("express");
var { v4 } = require("uuid");
var { mkdir, writeFile } = require("fs/promises");
var { writeFileSync, readFileSync, unlinkSync } = require("fs");
var pathfs = require("path");
var { mkdirSync, existsSync } = require("fs");
var { spawn } = require("child_process");
var { promisify } = require("util");
var setTimeoutAsync = promisify(setTimeout);
var prettier = require("prettier");
var { exec } = require("child_process");
var execAsync = promisify(exec);
var path = require("path");
var fs = require("fs").promises;
var router = express.Router();
var homedir = require("os").homedir();
var MAX_SCRIPT_SIZE = 1024 * 1024;
var MAX_EXECUTION_TIME = 3e4;
var MAX_MEMORY = 512 * 1024 * 1024;
var confDir = pathfs.resolve(homedir, ".stack-monitor");
if (!existsSync(confDir)) mkdirSync(confDir);
var confPath = pathfs.resolve(confDir, "node-repl");
if (!existsSync(confPath)) writeFileSync(confPath, "{}", "utf-8");
var conf = JSON.parse(readFileSync(confPath, "utf-8"));
var packagesDir = pathfs.resolve(confDir, "node-repl-packages");
if (!existsSync(packagesDir)) mkdirSync(packagesDir);
function getRoomPackagesDir(room) {
return pathfs.resolve(packagesDir, room);
}
async function initRoomPackageJson(room) {
const roomDir = getRoomPackagesDir(room);
if (!existsSync(roomDir)) {
mkdirSync(roomDir, { recursive: true });
}
const packageJsonPath = pathfs.resolve(roomDir, "package.json");
if (!existsSync(packageJsonPath)) {
await fs.writeFile(packageJsonPath, JSON.stringify({ dependencies: {} }, null, 2), "utf-8");
}
return packageJsonPath;
}
async function cleanupRoomPackages(room) {
const roomDir = getRoomPackagesDir(room);
if (existsSync(roomDir)) {
try {
await fs.rm(roomDir, { recursive: true, force: true });
} catch (err) {
console.error(`Error cleaning up packages for room ${room}:`, err);
}
}
}
async function cleanupTempFiles() {
const sandboxDir = pathfs.resolve(__dirname, "sandbox-node-repl");
if (existsSync(sandboxDir)) {
const files = await promisify(require("fs").readdir)(sandboxDir);
for (const file of files) {
try {
await promisify(require("fs").unlink)(pathfs.join(sandboxDir, file));
} catch (err) {
console.error(`Error cleaning up file ${file}:`, err);
}
}
}
}
function validateScript(script) {
if (script.length > MAX_SCRIPT_SIZE) {
throw new Error(`Script too large. Maximum size is ${MAX_SCRIPT_SIZE / 1024 / 1024}MB`);
}
const dangerousImports = script.match(/require\(['"](?!${ALLOWED_MODULES.join('|')})['"]\)/g);
if (dangerousImports) {
throw new Error("Script contains unauthorized module imports");
}
return true;
}
async function checkNpmAvailable() {
try {
await execAsync("npm --version");
return true;
} catch (error) {
return false;
}
}
module2.exports = (stackMonitor) => {
const { Socket } = stackMonitor;
cleanupTempFiles().catch(console.error);
router.post("/node-repl/chat/:room", async (req, res) => {
try {
const { room } = req.params;
const { script } = req.body;
validateScript(script);
if (!conf.chat?.[room]) {
if (!conf.chat) conf.chat = {};
if (!conf.chat?.[room]) conf.chat[room] = {};
}
conf.chat[room].script = script;
await initRoomPackageJson(room);
const sessionId = v4();
const sessionDir = path.join(__dirname, "sandbox-node-repl", sessionId);
await fs.mkdir(sessionDir, { recursive: true });
const scriptPath = path.join(sessionDir, "script.js");
await fs.writeFile(scriptPath, script);
let result = "";
let error = null;
let timeoutId;
const customEnv = conf.chat[room]?.env || {};
const env = {
...process.env,
...customEnv,
NODE_OPTIONS: `--max-old-space-size=${MAX_MEMORY / 1024 / 1024}`,
NODE_PATH: path.join(getRoomPackagesDir(room), "node_modules")
};
const spawnCmd = spawn("node", [scriptPath], {
env,
cwd: sessionDir
});
timeoutId = setTimeout(() => {
spawnCmd.kill();
error = new Error("Script execution timed out");
}, MAX_EXECUTION_TIME);
Socket.emit("node-repl:update", { clear: true });
spawnCmd.stdout.on("data", (data) => {
const output = data.toString("utf-8");
Socket.emit("node-repl:update", { msg: output, type: "stdout" });
result += output;
});
spawnCmd.stderr.on("data", (data) => {
const output = data.toString("utf-8");
Socket.emit("node-repl:update", { msg: output, type: "stderr" });
result += output;
});
spawnCmd.on("error", (err) => {
error = err;
Socket.emit("node-repl:update", { msg: `Error: ${err.message}`, type: "error" });
});
spawnCmd.on("close", (code) => {
clearTimeout(timeoutId);
Socket.emit("node-repl:update", { close: true });
try {
conf.chat[room].result = result;
conf.chat[room].lastExecution = (/* @__PURE__ */ new Date()).toISOString();
conf.chat[room].error = error ? error.message : null;
save();
} catch (err) {
console.error("Error saving result:", err);
}
try {
require("fs").rmSync(sessionDir, { recursive: true, force: true });
} catch (err) {
console.error("Error cleaning up temporary directory:", err);
}
});
save();
res.json(sessionId);
} catch (err) {
Socket.emit("node-repl:update", {
msg: `Error: ${err.message}`,
type: "error",
clear: true
});
res.status(400).json({ error: err.message });
}
});
router.get("/node-repl/rooms", async (req, res) => {
res.json(Object.keys(conf?.chat || {}));
});
router.post("/node-repl/rooms", async (req, res) => {
const { room } = req.body;
if (!conf?.chat?.[room]) {
if (!conf?.chat) conf.chat = {};
if (!conf?.chat?.[room]) conf.chat[room] = {};
}
save();
res.json(Object.keys(conf?.chat || {}));
});
router.delete("/node-repl/rooms/:room", async (req, res) => {
const { room } = req.params;
if (conf?.chat?.[room]) {
delete conf.chat[room];
await cleanupRoomPackages(room);
}
save();
res.json(Object.keys(conf?.chat || {}));
});
router.get("/node-repl/chat/:room", async (req, res) => {
res.json(conf?.chat?.[req.params.room]);
});
router.post("/node-repl/format", async (req, res) => {
try {
const { code } = req.body;
if (!code) {
throw new Error("No code provided");
}
const formatted = await prettier.format(code, {
parser: "babel",
semi: true,
singleQuote: true,
trailingComma: "es5",
printWidth: 80,
tabWidth: 2,
useTabs: false,
bracketSpacing: true,
arrowParens: "avoid"
});
res.json({ formatted });
} catch (err) {
res.status(400).json({ error: err.message });
}
});
router.post("/node-repl/env", async (req, res) => {
try {
const { room, env } = req.body;
if (!room || !env) {
throw new Error("Room and environment variables are required");
}
if (!conf.chat?.[room]) {
if (!conf.chat) conf.chat = {};
if (!conf.chat?.[room]) conf.chat[room] = {};
}
conf.chat[room].env = env;
save();
res.json({ success: true });
} catch (err) {
res.status(400).json({ error: err.message });
}
});
router.get("/node-repl/env/:room", async (req, res) => {
try {
const { room } = req.params;
res.json(conf?.chat?.[room]?.env || {});
} catch (err) {
res.status(400).json({ error: err.message });
}
});
router.post("/node-repl/install", async (req, res) => {
try {
const { package: packageName, room } = req.body;
if (!packageName || !room) {
return res.status(400).json({ error: "Package name and room are required" });
}
const npmAvailable = await checkNpmAvailable();
if (!npmAvailable) {
return res.status(500).json({
error: "npm is not available in the system PATH. Please install Node.js and npm first."
});
}
const packageJsonPath = await initRoomPackageJson(room);
const roomDir = getRoomPackagesDir(room);
await execAsync(`npm install ${packageName}`, { cwd: roomDir });
const installedPackageJson = JSON.parse(await fs.readFile(packageJsonPath, "utf8"));
res.json(installedPackageJson.dependencies || {});
} catch (error) {
console.error("Error installing package:", error);
res.status(500).json({ error: error.message });
}
});
router.get("/node-repl/packages/:room", async (req, res) => {
try {
const { room } = req.params;
const packageJsonPath = pathfs.resolve(getRoomPackagesDir(room), "package.json");
try {
const packageJson = JSON.parse(await fs.readFile(packageJsonPath, "utf8"));
res.json(packageJson.dependencies || {});
} catch (err) {
res.json({});
}
} catch (error) {
console.error("Error listing packages:", error);
res.status(500).json({ error: error.message });
}
});
router.delete("/node-repl/packages/:room/:name", async (req, res) => {
try {
const { room, name } = req.params;
if (!name || !room) {
return res.status(400).json({ error: "Package name and room are required" });
}
const npmAvailable = await checkNpmAvailable();
if (!npmAvailable) {
return res.status(500).json({
error: "npm is not available in the system PATH. Please install Node.js and npm first."
});
}
const roomDir = getRoomPackagesDir(room);
const packageJsonPath = pathfs.resolve(roomDir, "package.json");
await execAsync(`npm uninstall ${name}`, { cwd: roomDir });
const updatedPackageJson = JSON.parse(await fs.readFile(packageJsonPath, "utf8"));
res.json(updatedPackageJson.dependencies || {});
} catch (error) {
console.error("Error uninstalling package:", error);
res.status(500).json({ error: error.message });
}
});
function save() {
try {
writeFileSync(confPath, JSON.stringify(conf, null, 2), "utf-8");
} catch (err) {
console.error("Error saving configuration:", err);
}
}
return router;
};
}
});
// ../../modules/node-repl/backend/index.js
var require_backend17 = __commonJS({
"../../modules/node-repl/backend/index.js"(exports2, module2) {
"use strict";
var plugin = {
enabled: true,
name: "NodeREPL",
displayName: "Node REPL",
description: "Create nodejs scripts for test purposes",
icon: "fab fa-node",
export: null,
placements: [
{
position: "toolbox",
label: "Node Sandbox",
icon: "fab fa-node",
goTo: { path: "/NodeREPL" },
active: "NodeREPL"
}
],
order: 6,
routes: require_routes15()
};
module2.exports = plugin;
}
});
// helpers/specialCaracters.helper.js
var require_specialCaracters_helper = __commonJS({
"helpers/specialCaracters.helper.js"(exports2, module2) {
"use strict";
module2.exports = {};
module2.exports.specialCaracters = [
["\xE0", "à", "agrave"],
["\xC0", "À", "agrave"],
["\xE1", "á", "aacute"],
["\xC1", "Á", "aacute"],
["\xE2", "â", "acirc"],
["\xC2", "Â", "acirc"],
["\xE3", "ã", "atilde"],
["\xC3", "Ã", "atilde"],
["\xE4", "ä", "auml"],
["\xC4", "Ä", "auml"],
["\xE5", "å", "aring"],
["\xC5", "Å", "aring"],
["\xE6", "æ", "aelig"],
["\xC6", "Æ", "aelig"],
["\xE8", "è", "egrave"],
["\xC8", "È", "egrave"],
["\xE9", "é", "eacute"],
["\xC9", "É", "eacute"],
["\xEA", "ê", "ecirc"],
["\xCA", "Ê", "ecirc"],
["\xEB", "ë", "euml"],
["\xCB", "Ë", "euml"],
["\xEC", "ì", "igrave"],
["\xCC", "Ì", "igrave"],
["\xED", "í", "iacute"],
["\xCD", "Í", "iacute"],
["\xEE", "î", "icirc"],
["\xCE", "Î", "icirc"],
["\xEF", "ï", "iuml"],
["\xCF", "Ï", "iuml"],
["\xF2", "ò", "ograve"],
["\xD2", "Ò", "ograve"],
["\xF3", "ó", "oacute"],
["\xD3", "Ó", "oacute"],
["\xF4", "ô", "ocirc"],
["\xD4", "Ô", "ocirc"],
["\xF5", "õ", "otilde"],
["\xD5", "Õ", "otilde"],
["\xF6", "ö", "ouml"],
["\xD6", "Ö", "ouml"],
["\xF8", "ø", "oslash"],
["\xD8", "Ø", "oslash"],
["\xF9", "ù", "ugrave"],
["\xD9", "Ù", "ugrave"],
["\xFA", "ú", "uacute"],
["\xDA", "Ú", "uacute"],
["\xFB", "û", "ucirc"],
["\xDB", "Û", "ucirc"],
["\xFC", "ü", "uuml"],
["\xDC", "Ü", "uuml"],
["\xF1", "ñ", "ntilde"],
["\xD1", "Ñ", "ntilde"],
["\xE7", "ç", "ccedil"],
["\xC7", "Ç", "ccedil"],
["\xFD", "ý", "yacute"],
["\xDD", "Ý", "yacute"],
["\xDF", "ß", "szlig"],
["\xAB", "«"],
["\xBB", "»"],
["&", "&"],
["<", "<"],
[">", ">"],
['"', """],
["\xA7", "¶"],
["\xA9", "©"]
];
}
});
// helpers/stringTransformer.helper.js
var require_stringTransformer_helper = __commonJS({
"helpers/stringTransformer.helper.js"(exports2, module2) {
"use strict";
var slugify = require("slugify").default;
var { specialCaracters } = require_specialCaracters_helper();
var alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
function numberToLetters(number, result = "") {
let charIndex = number % alphabet.length;
let quotient = number / alphabet.length;
if (charIndex - 1 === -1) {
charIndex = alphabet.length;
quotient -= 1;
}
result = alphabet.charAt(charIndex - 1) + result;
return quotient >= 1 ? numberToLetters(+quotient, result) : result;
}
function transformBeginingNumber(str) {
let transformedString = str;
const beginningNumber = Number.parseInt(transformedString, 10);
const isBeginningWithNumber = Number.isInteger(beginningNumber);
if (isBeginningWithNumber) {
const hasAlreadySeparator = transformedString.split(beginningNumber.toString())?.[1]?.charAt(0) === "_";
transformedString = transformedString.replace(beginningNumber.toString(), `${numberToLetters(beginningNumber)}${hasAlreadySeparator ? "" : "_"}`);
}
return transformedString;
}
module2.exports.humanStringToKey = (str, separator = "_") => {
let transformedString = str.trim().toLowerCase();
specialCaracters.forEach((sc) => {
const [char, eacute, slug] = sc;
if (eacute) transformedString = transformedString.replaceAll(eacute.toLowerCase(), char);
if (slug) transformedString = transformedString.replaceAll(slug.toLowerCase(), char);
});
transformedString = transformBeginingNumber(transformedString);
slugify.extend({
"'": separator,
"-": "_",
"(": separator,
")": separator
});
return slugify(transformedString, separator);
};
module2.exports.replaceEnvs = (str) => {
Object.keys(process.env).forEach((env) => {
if (!env) return;
str = str.replaceAll(`$${env}`, process.env[env]);
});
return str;
};
}
});
// ../../modules/npm/backend/Npm.js
var require_Npm = __commonJS({
"../../modules/npm/backend/Npm.js"(exports2, module2) {
"use strict";
var pathfs = require("path");
var { execAsync } = require_exec();
var { readFile } = require("fs/promises");
var { replaceEnvs } = require_stringTransformer_helper();
var { existsSync } = require("fs");
var Npm = class {
/** @param {import('@clabroche/servers-server/models/Service')} service */
constructor(service) {
this.service = service;
}
async isNpm(path) {
if (path) {
return existsSync(pathfs.resolve(path?.toString(), "package.json"));
}
return null;
}
getNpmPaths() {
return [
...this.service.commands.filter((cmd) => cmd.spawnOptions.cwd?.toString()?.trim() && cmd.spawnOptions.cwd !== ".").map((cmd) => replaceEnvs(cmd.spawnOptions.cwd)),
this.service.getRootPath()
].filter((cwd) => this.isNpm(cwd));
}
async packageJSON(path) {
if (path) {
return JSON.parse(await readFile(pathfs.resolve(path?.toString(), "package.json"), "utf-8"));
}
return {};
}
async packageLock(path) {
if (path) {
return JSON.parse(await readFile(pathfs.resolve(path?.toString(), "package-lock.json"), "utf-8")).catch((err) => {
console.error(err);
return {};
});
}
return {};
}
/**
* @returns {Promise<import('./index').Outdated>}
*/
async outdated(path) {
const result = await execAsync("npm outdated --json || true", { cwd: path });
return JSON.parse(result);
}
};
module2.exports = Npm;
}
});
// ../../modules/npm/backend/routes.js
var require_routes16 = __commonJS({
"../../modules/npm/backend/routes.js"(exports2, module2) {
"use strict";
var express = require("express");
var router = express.Router();
var Npm = require_Npm();
module2.exports = (stackMonitor) => {
const { findService } = stackMonitor;
router.get("/npm/:service", async (req, res) => {
const service = findService(req.params.service);
const npm = new Npm(service);
const isNpm = await npm.isNpm(req.query.cwd);
res.json(isNpm);
});
router.get("/npm/:service/paths", async (req, res) => {
const service = findService(req.params.service);
const npm = new Npm(service);
const paths = await npm.getNpmPaths();
res.json(paths);
});
router.get("/npm/:service/packagejson", async (req, res) => {
const service = findService(req.params.service);
const npm = new Npm(service);
const packagejson = await npm.packageJSON(req.query.cwd);
res.json(packagejson);
});
router.get("/npm/:service/outdated", async (req, res) => {
const service = findService(req.params.service);
const npm = new Npm(service);
const packagejson = await npm.outdated(req.query.cwd);
res.json(packagejson);
});
return router;
};
}
});
// ../../modules/npm/backend/index.js
var require_backend18 = __commonJS({
"../../modules/npm/backend/index.js"(exports2, module2) {
"use strict";
var Npm = require_Npm();
var plugin = {
enabled: true,
name: "Npm",
displayName: "Npm",
description: "View your dependencies and execute your scripts on a service",
icon: "fab fa-npm",
export: Npm,
placements: ["service"],
order: 4,
/**
*
* @param {import('../../../servers/server/models/Service')} service
* @returns
*/
hidden: async (service) => {
if (!service) return false;
const project = new Npm(service);
const serviceIsNpm = !!project.getNpmPaths().length;
return !serviceIsNpm;
},
routes: require_routes16()
};
module2.exports = plugin;
}
});
// models/ports.js
var require_ports = __commonJS({
"models/ports.js"(exports2, module2) {
"use strict";
module2.exports = {
http: process.env.STACK_MONITOR_HTTP_PORT || 0,
/** @param {number} port */
setHttpPort(port) {
if (!this.http) this.http = port;
}
};
}
});
// ../../modules/openai/backend/routes.js
var require_routes17 = __commonJS({
"../../modules/openai/backend/routes.js"(exports2, module2) {
"use strict";
var express = require("express");
var pathfs = require("path");
var OpenAIApi = require("openai").OpenAI;
var {
existsSync,
mkdirSync,
createReadStream,
writeFileSync,
readFileSync
} = require("fs");
var { encode } = require("gpt-3-encoder");
var { v4 } = require("uuid");
var { writeFile, unlink } = require("fs/promises");
var homedir = require("os").homedir();
var axios = require("axios").default;
var PromiseB2 = require("bluebird");
var ports = require_ports();
var router = express.Router();
var confDir = pathfs.resolve(homedir, ".stack-monitor");
if (!existsSync(confDir)) mkdirSync(confDir);
var openaiConfPath = pathfs.resolve(confDir, "openaiconf.json");
if (!existsSync(openaiConfPath)) writeFileSync(openaiConfPath, "{}", "utf-8");
var openaiStoragePath = pathfs.resolve(confDir, "storage");
if (!existsSync(openaiStoragePath)) mkdirSync(openaiStoragePath);
var openaiconf = JSON.parse(readFileSync(openaiConfPath, "utf-8"));
var openai = process.env.STACK_MONITOR_OPENAI_APIKEY ? new OpenAIApi({
apiKey: process.env.STACK_MONITOR_OPENAI_APIKEY
}) : null;
module2.exports = () => {
router.get("/openai", async (req, res) => {
res.json("hello");
});
router.get("/openai/image/:id", async (req, res) => {
res.setHeader("content-type", "image/png");
res.setHeader("content-disposition", "filename=image.png");
const path = pathfs.resolve(openaiStoragePath, `${req.params.id}.png`);
if (existsSync(path)) createReadStream(path).pipe(res);
else res.status(404).send("file not found");
});
router.get("/openai/models", async (req, res) => {
if (!openai) return res.status(400).send("Openai not initialized");
const models = await openai.models.list({});
return res.json(models.data);
});
router.post("/openai/tokenize", async (req, res) => {
const encoded = encode(req.body.data);
res.json({
nbTokens: encoded.length,
price: encoded.length * 2e-3 / 1e3
});
});
router.post("/openai/review", async (req, res) => {
if (!openai) return res.status(400).send("Openai not initialized");
const result = await openai.chat.completions.create({
model: "gpt-3.5-turbo",
temperature: 0,
n: 1,
max_tokens: Infinity,
messages: [
{ role: "system", content: 'You are a developer for 10 years. You are expert in git and you should write a commit. I gave you a git diff after. Write me a resume of this diff and write me a commit for this diff in this format: "<fix|feat|major>: <your message not exceeding 60 characters>"' },
{ role: "assistant", content: req.body.data }
]
}).catch((err) => {
console.error(err.response.data);
return Promise.reject(err);
});
return res.json(result.choices[0]?.message?.content || "Cannot respond");
});
router.post("/openai/error", async (req, res) => {
if (!openai) return res.status(400).send("Openai not initialized");
const result = await openai.chat.completions.create({
model: "gpt-3.5-turbo",
temperature: 0,
n: 1,
max_tokens: Infinity,
messages: [
{ role: "system", content: "You are a developer for 20 years. You are expert in it and you should find a solution to this following error. Resume the following error and try to fix it" },
{ role: "assistant", content: req.body.data }
]
}).catch((err) => {
console.error(err.response.data);
return Promise.reject(err);
});
return res.json(result.choices[0]?.message?.content || "Cannot respond");
});
router.post("/openai/chat/:room/image", async (req, res) => {
if (!openai) return res.status(400).send("Openai not initialized");
const { message, quality, resolution } = req.body;
if (!openaiconf?.chat) openaiconf.chat = {};
if (!openaiconf?.chat?.[req.params.room]) openaiconf.chat[req.params.room] = {};
if (!openaiconf?.chat?.[req.params.room]?.messages) {
openaiconf.chat[req.params.room].messages = [
{ role: "system", content: "Tu es un assistant utile." }
];
}
const messages = openaiconf?.chat[req.params.room]?.messages || [];
messages.push({
_id: v4(),
// @ts-ignore
role: "user",
content: message,
created_at: (/* @__PURE__ */ new Date()).toISOString()
});
const response = await openai.images.generate({
quality,
model: "dall-e-3",
prompt: message,
n: 1,
size: resolution
});
if (response?.data?.[0]?.url) {
const { url, revised_prompt } = response.data[0];
const uuid = v4();
const { data: fileBuffer } = await axios.get(url, {
responseType: "arraybuffer",
maxContentLength: Infinity,
maxBodyLength: Infinity
});
await writeFile(pathfs.resolve(openaiStoragePath, `${uuid}.png`), fileBuffer);
messages.push({
url: `http://localhost:${ports.http}/openai/image/${uuid}`,
contentId: uuid,
revised_prompt,
_id: v4(),
created_at: (/* @__PURE__ */ new Date()).toISOString()
});
save();
}
return res.json(response.data[0].url);
});
router.post("/openai/chat/:room", async (req, res) => {
if (!openai) return res.status(400).send("Openai not initialized");
const { message, model, temperature } = req.body;
if (!openaiconf?.chat) openaiconf.chat = {};
if (!openaiconf?.chat?.[req.params.room]) openaiconf.chat[req.params.room] = {};
if (!openaiconf?.chat?.[req.params.room]?.messages) {
openaiconf.chat[req.params.room].messages = [
{ role: "system", content: "Tu es un assistant utile." }
];
}
const messages = openaiconf?.chat[req.params.room]?.messages || [];
messages.push({
_id: v4(),
// @ts-ignore
role: "user",
content: message,
created_at: (/* @__PURE__ */ new Date()).toISOString()
});
const result = await openai.chat.completions.create({
model: model || "gpt-3.5-turbo",
temperature: Number.isNaN(+temperature) ? 0 : +temperature,
n: 1,
max_tokens: Infinity,
// @ts-ignore
messages: messages.filter((f) => f?.content).map((a) => ({ role: a.role || "user", content: a.content || "" })).slice(-15)
}).catch((err) => Promise.reject(err));
if (result?.choices?.[0]?.message) {
const { usage } = result;
const { message: message2 } = result.choices[0];
messages.push({
...message2,
...usage,
_id: v4(),
created_at: (/* @__PURE__ */ new Date()).toISOString()
});
}
save();
return res.json(messages);
});
router.get("/openai/rooms", async (req, res) => {
res.json(Object.keys(openaiconf?.chat || {}));
});
router.post("/openai/rooms", async (req, res) => {
const { room } = req.body;
if (!openaiconf?.chat?.[room]) {
if (!openaiconf?.chat) openaiconf.chat = {};
if (!openaiconf?.chat?.[room]) openaiconf.chat[room] = {};
}
save();
res.json(Object.keys(openaiconf?.chat || {}));
});
router.delete("/openai/rooms/:room", async (req, res) => {
const { room } = req.params;
if (openaiconf?.chat?.[room]) {
await PromiseB2.map(openaiconf?.chat?.[room].messages, async (message) => {
if (message.contentId && existsSync(pathfs.resolve(openaiStoragePath, `${message.contentId}.png`))) {
await unlink(pathfs.resolve(openaiStoragePath, `${message.contentId}.png`));
}
}, { concurrency: 4 });
delete openaiconf.chat[room];
}
save();
res.json(Object.keys(openaiconf?.chat || {}));
});
router.get("/openai/chat/:room", async (req, res) => {
res.json(openaiconf?.chat?.[req.params.room]?.messages);
});
router.post("/openai/apikey", async (req, res) => {
const { apikey } = req.body;
process.env.STACK_MONITOR_OPENAI_APIKEY = apikey;
save();
openai = process.env.STACK_MONITOR_OPENAI_APIKEY ? new OpenAIApi({
apiKey: process.env.STACK_MONITOR_OPENAI_APIKEY
}) : null;
res.json(true);
});
router.get("/openai/ready", async (req, res) => {
if (!openai) return res.json(false);
console.log(process.env.STACK_MONITOR_OPENAI_APIKEY, openaiconf?.apikey);
try {
const { data } = await openai.models.list({});
res.json(!!data?.length);
} catch (error) {
res.json(false);
}
return void 0;
});
function save() {
writeFileSync(openaiConfPath, JSON.stringify(openaiconf), "utf-8");
}
return router;
};
}
});
// ../../modules/openai/backend/index.js
var require_backend19 = __commonJS({
"../../modules/openai/backend/index.js"(exports2, module2) {
"use strict";
var plugin = {
enabled: true,
name: "OpenAI",
displayName: "Open A.I.",
description: "Just chat with Chat GPT",
icon: "fas fa-brain",
export: null,
placements: [
{
position: "toolbox",
label: "OpenAi",
icon: "fas fa-brain",
goTo: { path: "/OpenAI" },
active: "OpenAI"
}
],
order: 6,
routes: require_routes17()
};
module2.exports = plugin;
}
});
// ../../modules/regex/backend/index.js
var require_backend20 = __commonJS({
"../../modules/regex/backend/index.js"(exports2, module2) {
"use strict";
var plugin = {
enabled: true,
name: "Regex",
displayName: "Regex",
description: "Test your regular expression",
icon: "fas fa-key",
export: null,
placements: [
{
position: "toolbox",
label: "Regex",
icon: "fas fa-key",
goTo: { path: "/Regex" },
active: "Regex"
}
],
order: 6
};
module2.exports = plugin;
}
});
// ../../modules/sql-beautifier/backend/SQLBeautifier.js
var require_SQLBeautifier = __commonJS({
"../../modules/sql-beautifier/backend/SQLBeautifier.js"(exports2, module2) {
"use strict";
var sqlFormatter = require("sql-formatter");
var SQLBeautifier = class {
/**
* Beautify SQL query
* @param {string} sql - The SQL query to beautify
* @returns {BeautifyResult} The beautified SQL query
* @throws {Error} If the SQL query is invalid or formatting fails
*/
beautify(sql) {
if (!sql || typeof sql !== "string") {
throw new Error("SQL query must be a non-empty string");
}
try {
const formattedSQL = sqlFormatter.format(sql);
return {
result: formattedSQL
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : "Unknown error";
throw new Error(`Failed to beautify SQL: ${errorMessage}`);
}
}
};
module2.exports = new SQLBeautifier();
}
});
// ../../modules/sql-beautifier/backend/routes.js
var require_routes18 = __commonJS({
"../../modules/sql-beautifier/backend/routes.js"(exports2, module2) {
"use strict";
var express = require("express");
var beautifier = require_SQLBeautifier();
function routes(stackMonitor) {
const router = express.Router();
router.post("/sqlbeautifier/beautify", (req, res) => {
const { sql } = req.body;
if (!sql || typeof sql !== "string") {
return res.status(400).json({
error: "SQL query must be a non-empty string"
});
}
try {
const result = beautifier.beautify(sql);
res.json(result);
} catch (error) {
const errorMessage = error instanceof Error ? error.message : "An unknown error occurred while beautifying SQL";
res.status(500).json({ error: errorMessage });
}
});
return router;
}
module2.exports = routes;
}
});
// ../../modules/sql-beautifier/backend/index.js
var require_backend21 = __commonJS({
"../../modules/sql-beautifier/backend/index.js"(exports2, module2) {
"use strict";
var plugin = {
enabled: true,
name: "sqlbeautifier",
displayName: "SQL Beautifier",
description: "Format and beautify SQL queries with syntax highlighting and real-time updates",
icon: "fas fa-database",
order: 8,
export: require_SQLBeautifier(),
routes: require_routes18(),
placements: [
{
position: "toolbox",
label: "SQL Beautifier",
icon: "fas fa-database",
goTo: { path: "/SQLBeautifier" },
active: "SQLBeautifier"
}
]
};
module2.exports = plugin;
}
});
// ../../modules/toolbox/backend/index.js
var require_backend22 = __commonJS({
"../../modules/toolbox/backend/index.js"(exports2, module2) {
"use strict";
var plugin = {
enabled: true,
name: "Toolbox",
displayName: "Toolbox",
description: "Choose from a bunch of tool",
icon: "fas fa-plus",
export: null,
placements: [{
position: "sidebar",
label: "Toolbox",
icon: "fas fa-toolbox",
goTo: "/toolbox",
active: "toolbox"
}],
order: 6
};
module2.exports = plugin;
}
});
// ../../modules/uuid/backend/UUID.js
var require_UUID = __commonJS({
"../../modules/uuid/backend/UUID.js"(exports2, module2) {
"use strict";
var { v4 } = require("uuid");
var UUID = (stackMonitor) => ({
generate: ({ count = 1, noDash = false, uppercase = false } = {}) => {
const uuids = Array(count).fill().map(() => {
let uuid = v4();
if (noDash) {
uuid = uuid.replace(/-/g, "");
}
if (uppercase) {
uuid = uuid.toUpperCase();
}
return uuid;
});
return count === 1 ? uuids[0] : uuids;
}
});
module2.exports = UUID;
}
});
// ../../modules/uuid/backend/routes.js
var require_routes19 = __commonJS({
"../../modules/uuid/backend/routes.js"(exports2, module2) {
"use strict";
var express = require("express");
var router = express.Router();
var UUID = require_UUID();
module2.exports = (stackMonitor) => {
const uuid = UUID(stackMonitor);
router.get("/uuid/", async (req, res) => {
const { count, noDash, uppercase } = req.query;
res.json(uuid.generate({
count: count ? parseInt(count, 10) : void 0,
noDash: noDash === "true",
uppercase: uppercase === "true"
}));
});
return router;
};
}
});
// ../../modules/uuid/backend/index.js
var require_backend23 = __commonJS({
"../../modules/uuid/backend/index.js"(exports2, module2) {
"use strict";
var plugin = {
enabled: true,
name: "UUID",
displayName: "UUID",
description: "Generate an UUID",
icon: "fas fa-random",
placements: [
{
position: "toolbox",
label: "UUID",
icon: "fas fa-random",
goTo: { path: "/UUID" },
active: "UUID"
}
],
export: require_UUID(),
order: 6,
routes: require_routes19()
};
module2.exports = plugin;
}
});
// ../../modules/help/backend/index.js
var require_backend24 = __commonJS({
"../../modules/help/backend/index.js"(exports2, module2) {
"use strict";
var plugin = {
enabled: true,
name: "Help",
displayName: "Help",
description: "Show help",
icon: "fa-question-circle",
export: null,
placements: [{
position: "sidebar",
label: "Help",
icon: "fas fa-question-circle",
goTo: { path: "/Help" },
active: "Help"
}],
order: Infinity
};
module2.exports = plugin;
}
});
// ../../modules/openapi/backend/routes.js
var require_routes20 = __commonJS({
"../../modules/openapi/backend/routes.js"(exports2, module2) {
"use strict";
var express = require("express");
var axios = require("axios").default;
var router = express.Router();
module2.exports = () => {
router.post("/openapi/swagger.json", async (req, res) => {
const { data } = await axios.get(req.body.url);
res.json(data);
});
return router;
};
}
});
// ../../modules/openapi/backend/index.js
var require_backend25 = __commonJS({
"../../modules/openapi/backend/index.js"(exports2, module2) {
"use strict";
var plugin = {
enabled: true,
name: "OpenApi",
displayName: "OpenApi",
export: null,
description: "Open an open api json into swagger",
icon: "fas fa-network-wired",
placements: ["service"],
order: 4,
hidden: (service) => {
if (!service) return false;
return !service?.openapiURL;
},
routes: require_routes20()
};
module2.exports = plugin;
}
});
// ../../modules/vscode/backend/routes.js
var require_routes21 = __commonJS({
"../../modules/vscode/backend/routes.js"(exports2, module2) {
"use strict";
var express = require("express");
var open = require("open");
var pathfs = require("path");
var ws = require("workspace-tools");
var { readFile } = require("fs/promises");
var { readFileSync, existsSync } = require("fs");
var { execAsync } = require_exec();
var router = express.Router();
var routes = (stackMonitor) => {
router.post("/vscode/install", async (req, res) => {
await execAsync("code --install-extension ./stack-monitor.vsix", { cwd: __dirname });
res.send("ok");
});
router.delete("/vscode/uninstall", async (req, res) => {
await execAsync("code --uninstall-extension clabroche.stack-monitor", { cwd: __dirname });
res.send("ok");
});
router.get("/vscode/open-url", async (req, res) => {
const url = req.query.url?.toString();
if (url) open(url);
res.send("ok");
});
router.get("/vscode/get-services-from-file", async (req, res) => {
try {
if (!req.query.file) return res.status(400).send("file params is required");
const packageRoot = ws.findPackageRoot(req.query.file?.toString());
let projectRoot;
try {
projectRoot = ws.findProjectRoot(req.query.file?.toString());
} catch (error) {
projectRoot = packageRoot;
}
if (!packageRoot) return res.status(400).send("no package found");
if (!projectRoot) return res.status(400).send("no package found");
const monorepo = packageRoot !== projectRoot;
if (monorepo) {
const packageInfos = await ws.getPackageInfosAsync(projectRoot);
const { dependents: dependentsMap } = ws.createDependencyMap(packageInfos);
const packageJSON = JSON.parse(await readFile(pathfs.resolve(packageRoot, "./package.json"), "utf-8"));
const dependents = [
...dependentsMap.get(packageJSON.name) || []
];
const services = stackMonitor.getServices().filter((service) => {
const servicePackageNames = [
service.getRootPath(),
...service.commands.map((cmd) => cmd.spawnOptions?.env)
].flat(100).filter((a, i, arr) => a && !arr.slice(0, i).includes(a) && a.toString() !== projectRoot && existsSync(pathfs.resolve(a.toString(), "./package.json"))).map((path) => JSON.parse(readFileSync(pathfs.resolve(path?.toString() || "", "./package.json"), "utf-8")).name);
return dependents.some((packageName) => servicePackageNames.includes(packageName)) || servicePackageNames.includes(packageJSON.name);
});
res.json(services);
} else {
const packageRoot2 = ws.findPackageRoot(req.query.file?.toString());
let projectRoot2;
try {
projectRoot2 = ws.findProjectRoot(req.query.file?.toString());
} catch (error) {
projectRoot2 = packageRoot2;
}
if (!packageRoot2) return res.status(400).send("no package found");
if (!projectRoot2) return res.status(400).send("no package found");
const services = stackMonitor.getServices().filter((service) => {
const servicePaths = [
service.getRootPath(),
...service.commands.map((cmd) => cmd.spawnOptions?.env)
].flat(100).filter((a, i, arr) => a && !arr.slice(0, i).includes(a) && existsSync(pathfs.resolve(a.toString(), "./package.json")));
return servicePaths.includes(projectRoot2);
});
res.json(services);
}
} catch (error) {
console.error(error);
res.json([]);
}
return null;
});
router.get("/vscode/download", (req, res) => {
const vsixPath = pathfs.resolve(__dirname, "./stack-monitor.vsix");
res.download(vsixPath, "stack-monitor.vsix");
});
return router;
};
module2.exports = routes;
}
});
// ../../modules/vscode/backend/index.js
var require_backend26 = __commonJS({
"../../modules/vscode/backend/index.js"(exports2, module2) {
"use strict";
var commandExists = require("command-exists");
var plugin = {
enabled: true,
name: "Vscode",
displayName: "Vscode",
description: "interact with vscode extension",
icon: "fab fa-git-alt",
export: null,
order: Infinity,
placements: [{
position: "sidebar",
label: "VScode extension",
img: "https://upload.wikimedia.org/wikipedia/commons/thumb/9/9a/Visual_Studio_Code_1.35_icon.svg/512px-Visual_Studio_Code_1.35_icon.svg.png",
goTo: { path: "/Vscode" },
active: "Vscode"
}],
hidden: () => commandExists("code").then(() => false).catch(() => true),
routes: require_routes21()
};
module2.exports = plugin;
}
});
// ../../modules/docker/backend/routes.js
var require_routes22 = __commonJS({
"../../modules/docker/backend/routes.js"(exports2, module2) {
"use strict";
var express = require("express");
var { execAsync } = require_exec();
var router = express.Router();
module2.exports = (stackMonitor) => {
router.get("/docker/:service", async (req, res) => {
const service = stackMonitor.findService(req.params.service);
if (!service) return res.status(404).send("Service not found");
const isAlive = await execAsync(`docker inspect --format {{.State.Pid}} ${service.container.name}`, {}).catch(() => null);
const dockerInfos = await execAsync(`docker inspect ${service.container.name}`, {}).then((data) => {
try {
return JSON.parse(data)[0];
} catch (error) {
console.error(error);
return "";
}
}).catch(() => null);
let dockerImage = null;
if (dockerInfos) {
dockerImage = await execAsync(`docker history --no-trunc --human --format json ${dockerInfos.Image}`, {}).then((data) => {
try {
return data.trim().split("\n").map((a) => JSON.parse(a)).sort((a, b) => {
if (a && b) return a.CreatedAt.localeCompare(b.CreatedAt);
return 0;
}).map((a) => `${a.Size} ${a.CreatedBy} `);
} catch (error) {
console.error(error);
return "";
}
}).catch(() => null);
}
res.json([
{ label: "containerName", value: service.container.name },
{ label: "isAlive", value: !!isAlive },
{ label: "pid", value: isAlive },
{ label: "dockerInfos", value: dockerInfos, type: "json" },
{ label: "image", value: dockerImage, type: "json" }
]);
return null;
});
return router;
};
}
});
// ../../modules/docker/backend/index.js
var require_backend27 = __commonJS({
"../../modules/docker/backend/index.js"(exports2, module2) {
"use strict";
var plugin = {
enabled: true,
name: "Docker",
displayName: "Docker",
description: "Show docker configs",
icon: "fab fa-docker",
export: null,
placements: ["service"],
order: 4,
/** @param {import('../../../servers/server/models/Service') | null} service */
hidden: async (service) => {
if (!service?.container) return true;
return !service.container.enabled;
},
routes: require_routes22()
};
module2.exports = plugin;
}
});
// ../../modules/workflows/backend/Workflows.js
var require_Workflows = __commonJS({
"../../modules/workflows/backend/Workflows.js"(exports2, module2) {
"use strict";
var NodeRed = (stackMonitor) => {
};
module2.exports = NodeRed;
}
});
// ../../common/express-health-check/src/index.js
var require_src3 = __commonJS({
"../../common/express-health-check/src/index.js"(exports2, module2) {
"use strict";
module2.exports = (express, { name = "", version = "" } = {}) => express.Router().use("/health", (req, res) => {
res.json({
name,
version,
health: true
});
});
}
});
// ../../common/express-error-handler/src/errorCodes.js
var require_errorCodes = __commonJS({
"../../common/express-error-handler/src/errorCodes.js"(exports2, module2) {
"use strict";
module2.exports = [
{ code: 100, label: "Continue" },
{ code: 101, label: "Switching Protocols" },
{ code: 102, label: "Processing (WebDAV; RFC 2518)" },
{ code: 103, label: "Early Hints (RFC 8297)" },
{ code: 200, label: "OK" },
{ code: 201, label: "Created" },
{ code: 202, label: "Accepted" },
{ code: 203, label: "Non-Authoritative Information (since HTTP/1.1)" },
{ code: 204, label: "No Content" },
{ code: 205, label: "Reset Content" },
{ code: 206, label: "Partial Content" },
{ code: 207, label: "Multi-Status (WebDAV; RFC 4918)" },
{ code: 208, label: "Already Reported (WebDAV; RFC 5842)" },
{ code: 226, label: "IM Used (RFC 3229)" },
{ code: 300, label: "Multiple Choices" },
{ code: 301, label: "Moved Permanently" },
{ code: 302, label: 'Found (Previously "Moved temporarily")' },
{ code: 303, label: "See Other (since HTTP/1.1)" },
{ code: 304, label: "Not Modified" },
{ code: 305, label: "Use Proxy (since HTTP/1.1)" },
{ code: 306, label: "Switch Proxy" },
{ code: 307, label: "Temporary Redirect (since HTTP/1.1)" },
{ code: 308, label: "Permanent Redirect" },
{ code: 400, label: "Bad Request" },
{ code: 401, label: "Unauthorized" },
{ code: 402, label: "Payment Required" },
{ code: 403, label: "Forbidden" },
{ code: 404, label: "Not Found" },
{ code: 405, label: "Method Not Allowed" },
{ code: 406, label: "Not Acceptable" },
{ code: 407, label: "Proxy Authentication Required" },
{ code: 408, label: "Request Timeout" },
{ code: 409, label: "Conflict" },
{ code: 410, label: "Gone" },
{ code: 411, label: "Length Required" },
{ code: 412, label: "Precondition Failed" },
{ code: 413, label: "Payload Too Large" },
{ code: 414, label: "URI Too Long" },
{ code: 415, label: "Unsupported Media Type" },
{ code: 416, label: "Range Not Satisfiable" },
{ code: 417, label: "Expectation Failed" },
{ code: 418, label: "I'm a teapot (RFC 2324, RFC 7168)" },
{ code: 421, label: "Misdirected Request" },
{ code: 422, label: "Unprocessable Entity" },
{ code: 423, label: "Locked (WebDAV; RFC 4918)" },
{ code: 424, label: "Failed Dependency (WebDAV; RFC 4918)" },
{ code: 425, label: "Too Early (RFC 8470)" },
{ code: 426, label: "Upgrade Required" },
{ code: 428, label: "Precondition Required (RFC 6585)" },
{ code: 429, label: "Too Many Requests (RFC 6585)" },
{ code: 431, label: "Request Header Fields Too Large (RFC 6585)" },
{ code: 451, label: "Unavailable For Legal Reasons (RFC 7725)" },
{ code: 500, label: "Internal Server Error" },
{ code: 501, label: "Not Implemented" },
{ code: 502, label: "Bad Gateway" },
{ code: 503, label: "Service Unavailable" },
{ code: 504, label: "Gateway Timeout" },
{ code: 505, label: "HTTP Version Not Supported" },
{ code: 506, label: "Variant Also Negotiates (RFC 2295)" },
{ code: 507, label: "Insufficient Storage (WebDAV; RFC 4918)" },
{ code: 508, label: "Loop Detected (WebDAV; RFC 5842)" },
{ code: 510, label: "Not Extended (RFC 2774)" },
{ code: 511, label: "Network Authentication Required (RFC 6585)" },
{ code: 419, label: "Page Expired (Laravel Framework)" },
{ code: 420, label: "Method Failure (Spring Framework)" },
{ code: 420, label: "Enhance Your Calm (Twitter)" },
{ code: 430, label: "Request Header Fields Too Large (Shopify)" },
{ code: 450, label: "Blocked by Windows Parental Controls (Microsoft)" },
{ code: 498, label: "Invalid Token (Esri)" },
{ code: 499, label: "Token Required (Esri)" },
{ code: 509, label: "Bandwidth Limit Exceeded (Apache Web Server/cPanel)" },
{ code: 529, label: "Site is overloaded" },
{ code: 530, label: "Site is frozen" },
{ code: 598, label: "(Informal convention) Network read timeout error" },
{ code: 599, label: "Network Connect Timeout Error" },
{ code: 440, label: "Login Time-out" },
{ code: 449, label: "Retry With" },
{ code: 451, label: "Redirect" },
{ code: 444, label: "No Response" },
{ code: 494, label: "Request header too large" },
{ code: 495, label: "SSL Certificate Error" },
{ code: 496, label: "SSL Certificate Required" },
{ code: 497, label: "HTTP Request Sent to HTTPS Port" },
{ code: 499, label: "Client Closed Request" },
{ code: 520, label: "Web Server Returned an Unknown Error" },
{ code: 521, label: "Web Server Is Down" },
{ code: 522, label: "Connection Timed Out" },
{ code: 523, label: "Origin Is Unreachable" },
{ code: 524, label: "A Timeout Occurred" },
{ code: 525, label: "SSL Handshake Failed" },
{ code: 526, label: "Invalid SSL Certificate" },
{ code: 527, label: "Railgun Error" },
{ code: 460, label: "" },
{ code: 463, label: "" },
{ code: 464, label: "" },
{ code: 561, label: "Unauthorized" },
{ code: 110, label: "Response is Stale" },
{ code: 111, label: "Revalidation Failed" },
{ code: 112, label: "Disconnected Operation" },
{ code: 113, label: "Heuristic Expiration" },
{ code: 199, label: "Miscellaneous Warning" },
{ code: 214, label: "Transformation Applied" },
{ code: 299, label: "Miscellaneous Persistent Warning" }
];
}
});
// ../../common/express-error-handler/src/index.js
var require_src4 = __commonJS({
"../../common/express-error-handler/src/index.js"(exports2, module2) {
"use strict";
var HTTPError = require_src2();
var errorCodes = require_errorCodes();
module2.exports = () => (err, req, res, next) => {
const isValidCode = (!Number.isNaN(Number(err.code)) || typeof err.code !== "number") && errorCodes.map((a) => a.code).includes(Math.floor(+err.code));
let httpErr;
if (err instanceof HTTPError && isValidCode) {
httpErr = err;
} else {
httpErr = !err.code || !isValidCode ? new HTTPError(err, 500, err?.errorId, err?.date, err?.stack) : new HTTPError(err.message, err.code, err?.errorId, err?.date, err?.stack);
}
console.error(httpErr);
res.status(Math.floor(httpErr.code)).json({
errorId: httpErr.errorId,
date: httpErr.date,
code: httpErr.code,
message: httpErr.message,
details: httpErr.details || err?.details,
...err?.customCode ? { customCode: err.customCode } : {}
});
next(err);
};
}
});
// ../../common/express-404/src/index.js
var require_src5 = __commonJS({
"../../common/express-404/src/index.js"(exports2, module2) {
"use strict";
module2.exports = (req, res) => {
res.status(404).send(`${req.method} ${req.url} not found`);
};
}
});
// ../../common/express/src/index.js
var require_src6 = __commonJS({
"../../common/express/src/index.js"(exports2, module2) {
"use strict";
require("express-async-errors");
var express = require("express");
var cors = require("cors");
var pathfs = require("path");
var fs = require("fs");
var healthCheck = require_src3();
var helmet = require("helmet").default;
var cookieParser = require("cookie-parser");
var compression = require("compression");
var http = require("http");
var Socket = require_src();
var CustomObservable = require_CustomObservable();
var onServerLaunch = new CustomObservable();
var _server;
require("express-async-errors");
module2.exports = {
onServerLaunch,
getServer: () => {
return new Promise((res) => {
if (_server) return res(_server);
onServerLaunch.subscribe(() => {
res(_server);
});
});
},
/**
* @param {{
* port: number | string,
* baseUrl?: string,
* helmetConf?: import('helmet').HelmetOptions | null,
* corsConf?: import('cors').CorsOptions,
* beforeAll?: ({app, server}) => any,
* afterAll?: () => any,
* socket?: boolean,
* onListening?: (server: {server: http.Server<typeof http.IncomingMessage, typeof http.ServerResponse>, app: import('express').Express}) => any,
* beforeStatic?: (server: {server: http.Server<typeof http.IncomingMessage, typeof http.ServerResponse>, app: import('express').Express}) => any,
* afterControllers?: (server: {server: http.Server<typeof http.IncomingMessage, typeof http.ServerResponse>, app: import('express').Express}) => any,
* controllers?: () => import('express').RequestHandler,
* staticController?: string,
* noGreetings?: boolean,
* apiPrefix?: string
* bodyLimit?: string
* healthPath?: string
* }} param0
* @returns
*/
async launch({
port = process.env.PORT || 4002,
baseUrl = __dirname,
helmetConf = {},
corsConf = {},
beforeAll = () => {
},
afterAll = () => {
},
onListening = () => {
},
beforeStatic = () => {
},
afterControllers = () => {
},
controllers = () => () => {
},
staticController = void 0,
noGreetings = false,
socket = false,
apiPrefix = "/api",
bodyLimit = "50mb",
healthPath = apiPrefix
}) {
const pkgJSONPath = pathfs.resolve(baseUrl, "package.json");
const isPkgJSONExists = fs.existsSync(pkgJSONPath);
const pkgJSON = isPkgJSONExists ? require(pkgJSONPath) : { name: "unknown", version: "unknown" };
const appVersion = pkgJSON.version;
const appName = pkgJSON.name;
console.log(`<h2 style="background: linear-gradient(90deg, rgba(52,70,91,1) 0%, rgba(28,92,227,1) 100%);color:white;border-radius:10px;padding:10px;width: max-content">${appName.replace("@clabroche/", "")}</h2>`);
console.log(`v${appVersion} started, listening on port ${port}.`);
const app = express();
const server = http.createServer(app);
if (helmetConf) {
app.use((req, res, next) => {
if (req.url.startsWith("/red")) return next();
helmet(
helmetConf
)(req, res, next);
});
}
console.log("Enable cors...");
app.use(cors(corsConf));
app.options("*", (req, res, next) => {
res.setHeader("Access-Control-Allow-Credentials", "true");
next();
});
console.log("Enable JSON body...");
app.use(express.json({ limit: bodyLimit }));
app.use(express.urlencoded({ extended: true }));
console.log("Enable Cookie parser...");
app.use(cookieParser());
console.log("Enable Compression...");
app.use(compression());
console.log("Enable health check...");
app.use(healthPath, healthCheck(express, { name: appName, version: appVersion }));
beforeStatic?.({ server, app });
console.log("Apply additional routes...");
if (staticController) {
app.use("/", express.static(staticController));
}
if (socket) {
console.log("Enable socket...");
Socket.sockets.connect(server);
}
app.use(apiPrefix, controllers());
if (!noGreetings) {
app.get("/", (req, res) => res.json({ appName, appVersion }));
}
afterControllers?.({ server, app });
if (process.env.NODE_ENV === "HFBXdZMJxLyJoua28asEaxRixJ6LriR7FnRzX6pwA7pFjZ") {
const createProxyMiddleware = require("http-proxy-middleware").createProxyMiddleware;
app.use("/", createProxyMiddleware({
target: "http://127.0.0.1:5173",
changeOrigin: false,
ws: true,
logger: console
}));
server.on("upgrade", (req, res) => {
if (req.url === "/") {
proxy.ws(req, res, {
target: "ws://127.0.0.1:5173"
});
}
});
}
console.log("Enable error handling...");
app.use(require_src4()());
console.log("Enable 404 handling...");
app.use(require_src5());
console.log("Apply additional tasks before launch...");
await beforeAll({ app, server });
console.log("Launch...");
server.on("listening", () => onListening({ server, app }));
server.listen(port, async () => {
_server = server;
onServerLaunch.next({ server, app });
console.log("<h1>\u2713 Launched</h1>");
});
server.on("close", async () => {
await afterAll();
});
return server;
},
express
};
}
});
// ../../modules/workflows/backend/routes.js
var require_routes23 = __commonJS({
"../../modules/workflows/backend/routes.js"(exports2, module2) {
"use strict";
var { express, getServer } = require_src6();
var { sockets } = require_src();
var RED = require("node-red");
var compressing = require("compressing");
var router = express.Router();
var pathfs = require("path");
var { existsSync } = require("fs");
var { rm, readFile, writeFile, mkdir, readdir } = require("fs/promises");
var ports = require_ports();
var { execAsync } = require_exec();
var EncryptionKey = require_EncryptionKey();
var PromiseB2 = require("bluebird");
var { v4 } = require("uuid");
module2.exports = (Stack) => {
getServer().then(async (server) => {
const userDir = pathfs.resolve(Stack.getRootPath(), "nodered");
if (!existsSync(userDir)) await mkdir(userDir, { recursive: true });
const settings = {
httpAdminRoot: "/red",
httpNodeRoot: "/node-red",
userDir,
credentialSecret: EncryptionKey.encryptionKey,
functionGlobalContext: {},
flowFile: "flow.json",
paletteCategories: ["Stack Monitor"]
};
settings.functionGlobalContext.stackmonitor = {
sockets,
url: `http://localhost:${ports.http}`,
stack: require_stack()
};
RED.init(server, {
...settings
});
router.use(settings.httpAdminRoot, RED.httpAdmin);
router.use(settings.httpNodeRoot, RED.httpNode);
const moduleName = "node-red-contrib-stack-monitor";
const modulePath = pathfs.resolve(userDir, "node_modules", moduleName);
const localNodeModuleTarPath = pathfs.resolve(__dirname, `${moduleName}.tar`);
const packageJSONPath = pathfs.resolve(userDir, "package.json");
if (existsSync(modulePath)) {
console.log(moduleName, "found, delete it before start nodered");
await rm(modulePath, { recursive: true, force: true });
await execAsync("npm uninstall node-red-contrib-stack-monitor", { cwd: userDir });
}
await PromiseB2.map(await readdir(userDir), async (file) => {
if (file.startsWith(`node-red-contrib-stack-monitor-`) && file.endsWith(".tgz")) {
await rm(pathfs.resolve(userDir, file));
}
});
let buffer;
if (existsSync(localNodeModuleTarPath)) {
console.log(`Read ${moduleName} from local tar`);
buffer = await readFile(localNodeModuleTarPath);
} else {
let stream2buffer2 = function(stream2) {
return new Promise((resolve, reject) => {
const _buf = [];
stream2.on("data", (chunk) => _buf.push(chunk));
stream2.on("end", () => resolve(Buffer.concat(_buf)));
stream2.on("error", (err) => reject(err));
});
};
var stream2buffer = stream2buffer2;
console.log(`Build ${moduleName} from dir`);
const stream = new compressing.tar.Stream();
stream.addEntry(pathfs.resolve(__dirname, "./nodes"), { ignoreBase: true, relativePath: "package" });
buffer = await stream2buffer2(stream);
}
let packageJSON = {
"name": "node-red-project",
"description": "A Node-RED Project",
"version": "0.0.1",
"private": true,
"dependencies": {}
};
if (existsSync(packageJSONPath)) {
Object.assign(packageJSON, JSON.parse(await readFile(packageJSONPath, "utf-8")));
}
const tgzFileName = `${moduleName}.tgz`;
packageJSON.dependencies[moduleName] = `file:${tgzFileName}`;
const pathToTgz = pathfs.resolve(userDir, `${tgzFileName}`);
await writeFile(pathToTgz, buffer);
await writeFile(packageJSONPath, JSON.stringify(packageJSON, null, 2), "utf-8");
await execAsync("npm i", { cwd: userDir });
await writeFile(pathfs.resolve(userDir, ".gitignore"), `*
!flow.json
!flow_cred.json
!package.json
`);
await RED.start();
sockets.emit("scenarios:start");
});
return router;
};
}
});
// ../../modules/workflows/backend/index.js
var require_backend28 = __commonJS({
"../../modules/workflows/backend/index.js"(exports2, module2) {
"use strict";
var Workflows = require_Workflows();
var plugin = {
enabled: true,
name: "Workflows",
displayName: "Workflows",
description: "Make workflows based on events or manual triggers",
icon: "fas fa-sitemap",
export: Workflows,
order: 10,
placements: [
{
position: "sidebar-top",
label: "Workflows",
icon: "fas fa-sitemap",
goTo: { path: "/Workflows" },
active: "Workflows"
}
],
finder: (search, stackMonitor) => {
return [];
},
routes: require_routes23()
};
module2.exports = plugin;
}
});
// ../../modules/plugins-loader/backend/plugins.js
var require_plugins = __commonJS({
"../../modules/plugins-loader/backend/plugins.js"(exports2, module2) {
"use strict";
var plugins = {
bugs: require_backend(),
base64: require_backend2(),
configuration: require_backend3(),
devOps: require_backend4(),
diff: require_backend5(),
documentation: require_backend6(),
finder: require_backend7(),
git: require_backend8(),
github: require_backend9(),
globalScripts: require_backend10(),
httpClient: require_backend11(),
jsonFormatter: require_backend12(),
jwt: require_backend13(),
kanban: require_backend14(),
logs: require_backend15(),
mongo: require_backend16(),
nodeRepl: require_backend17(),
npm: require_backend18(),
openai: require_backend19(),
regex: require_backend20(),
sqlBeautifier: require_backend21(),
toolbox: require_backend22(),
uuid: require_backend23(),
help: require_backend24(),
openapi: require_backend25(),
vscode: require_backend26(),
docker: require_backend27(),
nodered: require_backend28()
};
module2.exports = Object.keys(plugins).reduce((acc, key) => {
if (plugins[key].enabled) acc[key] = plugins[key];
return acc;
}, {});
}
});
// helpers/readline.js
var require_readline = __commonJS({
"helpers/readline.js"(exports2, module2) {
"use strict";
var { EventEmitter } = require("events");
var _instances, emit_fn, _a;
module2.exports = (_a = class extends EventEmitter {
/**
*
* @param {{input?: import('stream').Readable, emitAfterNoDataMs?: number}} options
*/
constructor(options = {}) {
super();
__privateAdd(this, _instances);
this.options = options;
if (options.input) this.linkToInput(options.input);
this.all = [];
this.timeout = void 0;
}
/**
*
* @param {import('stream').Readable} readStream
*/
linkToInput(readStream) {
readStream.on("data", (data) => {
clearTimeout(this.timeout);
data = data.toString("utf-8");
const dataLen = data.length;
for (let i = 0; i < dataLen; i += 1) {
const char = data[i];
if (char === "\n" || char === "\r\n") {
__privateMethod(this, _instances, emit_fn).call(this);
} else {
this.all.push(char);
}
}
if (this.options.emitAfterNoDataMs) {
this.timeout = setTimeout(() => {
__privateMethod(this, _instances, emit_fn).call(this);
}, this.options.emitAfterNoDataMs);
}
});
}
}, _instances = new WeakSet(), emit_fn = function() {
if (!this.all.length) return;
const line = this.all.splice(0, this.all.length).join("");
this.emit("line", line);
}, _a);
}
});
// helpers/ansiconvert.js
var require_ansiconvert = __commonJS({
"helpers/ansiconvert.js"(exports2, module2) {
"use strict";
var ansiconvert = new (require("ansi-to-html"))({
newline: true,
escapeXML: false,
bg: "#FFFFFFFF",
fg: "#4c4c4c"
});
function stripAnsi(msg) {
return msg.replaceAll(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/gm, "");
}
function unescapeAnsi(msg) {
return msg.replaceAll(/\\u001b\[/gm, "\x1B[").replaceAll(/\\u009b\[/gm, "\x9B[");
}
module2.exports = { ansiconvert, stripAnsi, unescapeAnsi };
}
});
// helpers/isWindows.js
var require_isWindows = __commonJS({
"helpers/isWindows.js"(exports2, module2) {
"use strict";
module2.exports = process.platform === "win32";
}
});
// models/Environment.js
var require_Environment = __commonJS({
"models/Environment.js"(exports2, module2) {
"use strict";
var PromiseB2 = require("bluebird");
var { cloneDeep, merge, over } = require("lodash");
var dbs2 = require_dbs();
var { existsSync } = require("fs");
var Environment = class _Environment {
/**
* @param {import('@clabroche/common-typings').NonFunctionProperties<Environment>} environment
*/
constructor(environment) {
this.label = environment.label || "";
this.default = environment.default || false;
this.color = environment.color || "";
this.bgColor = environment.bgColor || "";
this.envs = environment.envs || {};
this.extends = environment.extends || [];
this.overrideEnvs = environment.overrideEnvs || {};
}
static async load(label, Stack) {
const environmentDB = await dbs2.getDb(`envs/${label}`).read();
const overridesDB = dbs2.getDb(`overrides/${label}-environment`);
if (!existsSync(await overridesDB.getPath())) await new _Environment(environmentDB).save();
const overrides = await dbs2.getDb(`overrides/${label}-environment`).read();
merge(environmentDB?.envs || {}, overrides?.envs || {});
return new _Environment(environmentDB, Stack);
}
static async all() {
const allDbds = await dbs2.getDbs("envs");
const environments = await PromiseB2.map(allDbds, (id) => _Environment.load(id));
if (!environments.length) {
const localEnv = new _Environment({
bgColor: "#FFFFFF",
color: "#000000",
default: true,
label: "Local",
envs: {}
});
await localEnv.save();
environments.push(localEnv);
}
return environments;
}
static async find(envLabel) {
const envs = await this.all();
return envs.find((env) => env.label === envLabel);
}
/** @returns {Promise<Environment[]>} */
async getExtendedEnvironments() {
return [
this,
...await PromiseB2.mapSeries(this.extends || [], (environmentLabel) => _Environment.find(environmentLabel)).filter((a) => !!a)
];
}
async getBank() {
const bank = {};
const environments = await this.getExtendedEnvironments();
await PromiseB2.map(environments.reverse(), async (environment) => {
Object.assign(bank, environment?.envs);
});
Object.assign(bank, this.envs);
return bank;
}
async save() {
const dbToWrite = this.toStorage();
const overrideDbToWrite = { envs: {} };
Object.keys(dbToWrite.envs).forEach((key) => {
if (key.endsWith("_STACK_MONITOR_OVERRIDE")) {
overrideDbToWrite.envs[key] = dbToWrite.envs[key];
delete dbToWrite.envs[key];
}
});
await dbs2.getDb(`overrides/${this.label}-environment`).write(overrideDbToWrite);
await dbs2.getDb(`envs/${this.label}`).write(dbToWrite);
}
async update(env) {
this.default = env.default;
this.color = env.color;
this.bgColor = env.bgColor;
this.envs = env.envs;
this.extends = env.extends;
return this.save();
}
async delete() {
await dbs2.getDb(`envs/${this.label}`).delete();
}
toStorage() {
return cloneDeep({
label: this.label,
default: this.default,
color: this.color,
bgColor: this.bgColor,
extends: this.extends,
envs: this.envs
});
}
};
module2.exports = Environment;
}
});
// parser/json.js
var require_json = __commonJS({
"parser/json.js"(exports2, module2) {
"use strict";
var parser2 = {
id: "stack-monitor-parser-jsons",
label: "Parse jsons",
readonly: true,
transform: (line) => {
if (!line.raw) return line;
const firstChar = line.raw.trim().charAt(0);
if (firstChar === "[" || firstChar === "{") {
try {
line.json = JSON.parse(line.raw);
} catch (error) {
}
}
return line;
}
};
module2.exports = parser2;
}
});
// parser/debug.js
var require_debug = __commonJS({
"parser/debug.js"(exports2, module2) {
"use strict";
var jsonParser = require_json();
var parser2 = {
id: "stack-monitor-parser-debug",
label: "Stackmonitor debug",
readonly: true,
transform: (line, ...rest) => {
if (!line.json) line = jsonParser.transform(line, ...rest);
if (!line.json) return line;
if (
/** @type {Array<any>} */
line?.json?.[0] === "stack-monitor"
) {
line.debug = line.json.length === 2 ? (
/** @type {Array<any>} */
line?.json?.[1]
) : line.json.slice(1);
}
return line;
}
};
module2.exports = parser2;
}
});
// parser/link.js
var require_link = __commonJS({
"parser/link.js"(exports2, module2) {
"use strict";
var urlRegex = /(http|ftp|https):\/\/([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])/gi;
var parser2 = {
id: "stack-monitor-parser-links",
label: "Parse links",
readonly: true,
transform: (line) => {
line.msg = line.msg.replaceAll(urlRegex, (url) => `<a href="${url}" target="_blank">${url}</a>`);
return line;
}
};
module2.exports = parser2;
}
});
// models/Parser.js
var require_Parser = __commonJS({
"models/Parser.js"(exports, module) {
"use strict";
var PromiseB = require("bluebird");
var { randomUUID } = require("crypto");
var dbs = require_dbs();
var nativeParsers = {
[require_debug().id]: require_debug(),
[require_json().id]: require_json(),
[require_link().id]: require_link()
};
var Parser = class _Parser {
/**
* @param {import('@clabroche/common-typings').NonFunctionProperties<Parser>} parser
*/
constructor(parser) {
this.label = parser.label || "";
this.id = parser.id || randomUUID();
this.transform = parser.transform || "";
this.readonly = parser.readonly || false;
try {
this.transformFunction = typeof parser.transform === "string" ? eval(parser.transform) : parser.transform;
} catch (error) {
console.error("[PARSER]:", this.label, "Cannot interpret your parser", error);
}
}
static async load(id) {
if (nativeParsers[id]) return new _Parser(nativeParsers[id]);
return new _Parser(await dbs.getDb(`parsers/${id}`).read());
}
static async all() {
const allDbds = [
...await dbs.getDbs("parsers"),
...Object.keys(nativeParsers)
];
return PromiseB.map(allDbds, (id) => this.load(id));
}
static async find(envId) {
const parsers = await this.all();
return parsers.find((env) => env.id === envId);
}
async save() {
const obj = this.toStorage();
await dbs.getDb(`parsers/${this.id}`).write(obj);
return this;
}
async update(env) {
this.transform = env.transform;
this.label = env.label;
await dbs.getDb(`parsers/${this.id}`).write(this.toStorage());
}
async delete() {
await dbs.getDb(`parsers/${this.id}`).delete();
}
toStorage() {
return {
id: this.id,
label: this.label,
transform: this.transform
};
}
};
module.exports = Parser;
}
});
// models/Service.js
var require_Service = __commonJS({
"models/Service.js"(exports2, module2) {
"use strict";
var os = require("os");
var { spawn } = require("child_process");
var killport = require("kill-port");
var URL2 = require("url");
var path = require("path");
var PromiseB2 = require("bluebird");
var dayjs = require("dayjs");
var { v4 } = require("uuid");
var { existsSync, readFileSync } = require("fs");
var kill = require("tree-kill");
var axios = require("axios").default;
var pathfs = require("path");
var net = require("net");
var { sockets } = require_src();
var { mkdir, writeFile } = require("fs/promises");
var { cloneDeep, over } = require("lodash");
var CreateInterface = require_readline();
var { stripAnsi, ansiconvert, unescapeAnsi } = require_ansiconvert();
var isWindows = require_isWindows();
var { execAsync } = require_exec();
var { humanStringToKey, replaceEnvs } = require_stringTransformer_helper();
var dbs2 = require_dbs();
var Environment = require_Environment();
var ParserModel = require_Parser();
var userInfo = os.userInfo();
var { gid, uid, username } = userInfo;
var alias = {
ls: { cmd: "ls", args: ["--color=force"] },
gco: { cmd: "git", args: ["checkout"] }
};
function Service(service, Stack, { isUpdate } = { isUpdate: false }) {
return (async () => {
this.Stack = Stack;
this.queue = service.queue || [];
this.stopQueue = false;
if (!isUpdate) {
this.processQueue();
}
this.label = service.label || "";
this.description = service.description || "";
this.git = {
home: service.git?.home || "",
remote: service.git?.remote || ""
};
this.documentation = service.documentation || "";
this.openapiURL = service.openapiURL || "";
this.url = service.url || "";
this.rootPath = service.rootPath || ".";
this.urls = service.urls || [];
this.groups = service.groups || [];
this.parsers = service.parsers || [
"stack-monitor-parser-debug",
"stack-monitor-parser-jsons",
"stack-monitor-parser-links"
];
this.enabled = service.enabled || false;
this.crashed = service.crashed || false;
this.container = service.container || {};
if (this.container) {
this.container.name = humanStringToKey(this.label);
if (!this.container.volumes?.length) this.container.volumes = [];
if (!this.container.build) this.container.build = "";
this.container.ports = this.container.ports || [];
this.container.sharedVolume = this.container.sharedVolume || "~/.stack-monitor";
this.container.ignoreVolumes = this.container.ignoreVolumes?.length ? this.container.ignoreVolumes.filter((f) => !f.startsWith(this.container?.sharedVolume || "")) : [];
if (!this.container.bootstrap) this.container.bootstrap = { commands: [] };
if (!this.container.bootstrap.commands) this.container.bootstrap.commands = [];
}
this.exited = service.exited || false;
this.envs = service.envs || {};
this.commands = service.commands || [];
this.shortcuts = service.shortcuts || [];
this.store = service.store || [];
this.pids = service.pids || [];
this.lastDatePrinted = service.lastDatePrinted || null;
this.health = service.health || {
enabled: false,
url: "",
interval: "",
method: "GET",
returnCode: 200,
responseText: "",
timeout: 0,
startAfter: 0
};
const environments = await Environment.all();
const overrides = await dbs2.getDb(`overrides/${this.label}-envs`).read();
if (!isUpdate) {
environments.forEach((environment) => {
const envs = this.envs[environment.label] || {};
Object.keys(envs).forEach((key) => {
const env = envs[key];
const tag = extractTag(env.value);
if (tag) envs[key].override = `{{${tag}_STACK_MONITOR_OVERRIDE}}`;
const override = overrides[environment.label]?.[key];
if (!env || !override) return;
envs[key].override = override;
});
});
}
this.meta = service.meta || {};
await PromiseB2.map(this.commands || [], async (command) => {
command.effectiveParsers = await PromiseB2.map([
...this.parsers || [],
...command.parsers || []
], (parserId) => ParserModel.find(parserId)).filter((a) => !!a);
});
return this;
})();
}
Service.prototype.getRootPath = function() {
return replaceEnvs(this.rootPath);
};
Service.prototype.processQueue = function() {
if (this.queue.length) {
const messages = this.queue.splice(0, this.queue.length);
sockets.emit("logs:update", messages);
}
if (this.stopQueue) return;
setTimeout(() => {
this.processQueue();
}, 100);
};
Service.prototype.save = async function() {
const obj = this.toStorage();
const overrides = {};
await PromiseB2.map(Object.keys(obj.envs), (environmentLabel) => {
if (!overrides[environmentLabel]) overrides[environmentLabel] = {};
Object.keys(obj.envs[environmentLabel]).forEach((key) => {
const env = obj.envs[environmentLabel][key];
overrides[environmentLabel][key] = env.override;
delete env.systemOverride;
delete env.override;
});
});
await dbs2.getDb(`overrides/${this.label}-envs`).write(overrides);
await dbs2.getDb(`services/${this.label}`).write(obj);
};
Service.prototype.delete = async function() {
await dbs2.getDb(`overrides/${this.label}-envs`).delete();
await dbs2.getDb(`services/${this.label}`).delete();
};
Service.load = async function(label, Stack) {
return new Service(await dbs2.getDb(`services/${label}`).read(), Stack);
};
Service.prototype.loadCustomEnv = function(path2) {
const dotEnvPath = pathfs.resolve(path2, ".env");
if (existsSync(dotEnvPath) && readFileSync(dotEnvPath, { encoding: "utf-8" }).trim()) {
console.log(`! A .env will override your ${this.label} service !`);
return require("dotenv").parse(readFileSync(dotEnvPath, "utf-8"));
}
return null;
};
Service.prototype.exportInApi = function() {
const res = { ...this };
delete res.pids;
delete res.store;
delete res.Stack;
return res;
};
Service.prototype.toStorage = function() {
const service = cloneDeep({
label: this.label,
commands: this.commands,
description: this.description,
git: this.git,
groups: this.groups,
url: this.url,
rootPath: this.rootPath,
openapiURL: this.openapiURL,
health: this.health,
urls: this.urls,
parsers: this.parsers,
container: this.container,
meta: this.meta,
envs: this.envs,
shortcuts: this.shortcuts
});
return service;
};
Service.prototype.restart = async function() {
await this.kill();
await this.launch();
};
Service.prototype.sendHasBeenModified = function() {
sockets.emit("conf:update", [this.label]);
};
Service.prototype.kill = async function(keepEnabled = false) {
if (this.container?.enabled && this.container?.name) {
await execAsync(`docker stop ${this.container.name}`, {}).catch(console.error);
await execAsync(`docker rm ${this.container.name}`, {}).catch(console.error);
} else {
await PromiseB2.map(this.pids, async (spawnedProcess) => {
if (!spawnedProcess.pid) return;
await killAsync(spawnedProcess.pid);
spawnedProcess.kill("SIGKILL");
});
const urls = [...this.urls || [], this.url].filter((a) => a);
if (urls.length) {
await PromiseB2.mapSeries(urls, async (url) => {
const { port } = URL2.parse(url);
if (port && !Number.isNaN(+port)) {
let free = false;
for (let i = 0; i < 16; i += 1) {
await wait(100);
free = await checkport(+port);
if (free) break;
}
if (!free) {
await killport(+port).catch((err) => console.error("Error: (Kill port):", err?.message || err));
}
}
});
}
}
this.pids = [];
sockets.emit("logs:clear", { label: this.label });
this.store = [];
await wait(100);
this.enabled = keepEnabled;
this.crashed = false;
this.sendHasBeenModified();
};
Service.prototype.launch = async function() {
this.store = [];
await this.kill(true).catch(console.error);
if (this.container?.enabled) {
await this.buildDocker({ isMainProcess: true });
}
if (this.commands?.length) {
await PromiseB2.map(this.commands, async (command) => {
if (command?.spawnCmd) {
await this.launchProcess(command);
}
});
}
this.enabled = true;
this.sendHasBeenModified();
};
Service.prototype.add = async function(data, logMessageOverride, {
pid,
isMainProcess,
command
}) {
const timestamp = Date.now();
const ansiMsg = unescapeAnsi(data.toString());
const stripMsg = stripAnsi(ansiMsg);
const htmlMessage = ansiMsg ? ansiconvert.toHtml(ansiMsg) : "<br/>";
let line = {
pid,
msg: htmlMessage,
raw: stripMsg,
timestamp,
label: this.label,
json: null,
debug: null,
...logMessageOverride,
id: v4()
};
line = await PromiseB2.reduce(command.effectiveParsers || [], async (line2, parser2) => {
try {
const result = await parser2.transformFunction(line2, this);
if (!result?.id) {
console.error(`It seems your parser "${parser2.label}" not return correct value. Please verify or disable it..`);
return line2;
}
return result;
} catch (err) {
console.error("[PARSER]:", parser2.label, ":", err);
return line2;
}
}, line);
if (line.hide) return;
if (line.source === "stderr" && isMainProcess) {
sockets.emit("alert", { label: this.label, message: line.raw.toString(), type: "error", commandId: command?.id });
}
if (timestamp > (this.lastDatePrinted || Date.now()) + 2e3) {
const date = `\u{1F551} ${dayjs().format("YYYY-MM-DD HH:mm:ss")}`;
const line2 = {
id: v4(),
raw: date,
label: this.label,
msg: date,
timestamp,
isSeparator: true
};
this.store.push(line2);
this.queue.push(line2);
}
this.lastDatePrinted = Date.now();
if (line.msg.length > 1e5 && !line.msg.startsWith('["stack-monitor"')) line.msg = line.msg.slice(0, 1e4);
this.store.push(line);
this.queue.push(line);
};
Service.prototype.launchProcess = async function(command, isMainProcess = true) {
try {
this.enabled = true;
this.sendHasBeenModified();
this.crashed = false;
this.exited = false;
let { cmd, args: args2, options } = this.container.enabled ? await this.parseIncomingCommandDocker(command) : await this.parseIncomingCommand(command);
if (!existsSync(options.cwd)) {
this.crashed = true;
this.exited = true;
const launchMessage2 = {
id: v4(),
timestamp: Date.now(),
label: this.label,
pid: null,
msg: `Path does not exists (${options.cwd})`,
raw: `Path does not exists (${options.cwd})`,
cmd: {
cmd,
args: args2,
options,
status: "exited"
}
};
console.error(launchMessage2.msg);
this.add(launchMessage2.msg, { source: "stderr" }, { pid: null, command, isMainProcess });
return { spawnProcess: null, launchMessage: launchMessage2 };
}
const spawnProcess = spawn(cmd, args2, { ...options, detached: !isWindows });
let pid = 0;
pid = spawnProcess.pid;
if (this.container?.customPid) {
this.container.customPid({ pid: spawnProcess.pid, cmd, args: args2 }).then((_pid) => {
pid = _pid;
});
}
if (!this.pids) this.pids = [];
this.pids.push(spawnProcess);
spawnProcess.title = this.label;
this.lastDatePrinted = Date.now();
this.queue = [];
new CreateInterface({
input: spawnProcess.stdout,
emitAfterNoDataMs: 100
}).on("line", (message) => {
this.add(message, { source: "stdout" }, { isMainProcess, pid, command });
});
new CreateInterface({
input: spawnProcess.stderr,
emitAfterNoDataMs: 100
}).on("line", (message) => {
this.add(message, { source: "stderr" }, { isMainProcess, pid, command });
});
const launchMessage = {
id: v4(),
timestamp: Date.now(),
label: this.label,
pid,
msg: `${cmd} ${args2.join(" ")}`,
raw: `${cmd} ${args2.join(" ")}`,
cmd: {
cmd,
args: args2,
options,
status: "running"
}
};
spawnProcess.on("exit", (code, signal) => {
if (code) {
if (launchMessage.cmd) launchMessage.cmd.status = "error";
if (isMainProcess) {
sockets.emit("service:crash", {
label: this.label,
code,
signal,
pid
});
this.crashed = true;
}
} else if (launchMessage.cmd) {
launchMessage.cmd.status = "exited";
if (isMainProcess) {
this.exited = true;
}
}
sockets.emit("service:exit", {
label: this.label,
code,
signal,
pid
});
sockets.emit("logs:update:lines", [launchMessage]);
});
const date = `\u{1F551} ${dayjs().format("YYYY-MM-DD HH:mm:ss")}`;
const line = {
id: v4(),
raw: date,
label: this.label,
msg: date,
timestamp: this.lastDatePrinted,
isSeparator: true
};
this.store.push(line, launchMessage);
sockets.emit("logs:update", [line, launchMessage]);
if (isMainProcess) {
setTimeout(() => {
this.launchHealthChecker(spawnProcess);
}, this.health.startAfter || 0);
sockets.emit("service:start", {
label: this.label,
pid
});
}
return {
launchMessage,
spawnProcess
};
} catch (error) {
const launchMessage = {
id: v4(),
timestamp: Date.now(),
label: this.label,
pid: null,
msg: `ERROR: ${error?.message || error}`,
raw: `ERROR: ${error?.message || error}`,
cmd: {
cmd: command.spawnCmd,
args: command.spawnArgs,
options: command.spawnOptions,
status: "exited"
}
};
const date = `\u{1F551} ${dayjs().format("YYYY-MM-DD HH:mm:ss")}`;
const line = {
id: v4(),
raw: date,
label: this.label,
msg: date,
timestamp: this.lastDatePrinted,
isSeparator: true
};
sockets.emit("logs:update", [line, launchMessage]);
throw error;
}
};
Service.prototype.launchHealthChecker = async function(spawnProcess) {
if (!spawnProcess.pid && !this.container?.name || !this.health?.enabled) return;
const healthy = await axios({
method: this.health.method || "GET",
url: this.health.url || this.url,
headers: { Accept: "*/*" },
timeout: +(this.health.timeout || 0),
validateStatus: (status) => status === +(this.health.returnCode || 200)
}).then(({ data: response }) => {
if (this.health.responseText && this.health.responseText !== JSON.stringify(response)) {
return false;
}
return true;
}).catch(() => false);
if (!healthy && !this.crashed) {
this.crashed = true;
sockets.emit("service:healthcheck:down", { label: this.label, pid: spawnProcess.pid });
} else if (healthy && this.crashed) {
this.crashed = false;
sockets.emit("service:healthcheck:up", { label: this.label, pid: spawnProcess.pid });
}
await wait(+(this.health.interval || 1e3));
this.launchHealthChecker(spawnProcess);
};
Service.prototype.respondToProcess = function(pid, message) {
const process2 = this.pids.find((process3) => process3.pid === pid);
if (!process2) return console.error(`Pid (${pid}) not found`);
process2.stdin?.write(`${message.trim()}
`);
const line = {
id: v4(),
raw: `${message.trim()}
`,
label: this.label,
msg: `${message.trim()}
`,
timestamp: Date.now(),
prompt: true,
pid
};
this.store.push(line);
sockets.emit("logs:update", [line]);
return null;
};
Service.prototype.terminate = function(pid, forceKill = false) {
const processFound = this.pids.find((process2) => process2.pid === pid);
if (!processFound) return console.error(`Pid (${pid}) not found`);
if (processFound.pid) {
if (isWindows) process.kill(processFound.pid, "SIGKILL");
else process.kill(-processFound.pid, forceKill ? "SIGKILL" : "SIGTERM");
}
return null;
};
Service.prototype.enable = function() {
this.enabled = true;
this.sendHasBeenModified();
};
Service.prototype.disable = function() {
this.enabled = false;
this.sendHasBeenModified();
};
Service.prototype.getGlobalEnvs = async function(environmentLabel) {
const globalEnvironment = await Environment.find(environmentLabel);
return (label) => {
const tag = this.extractTag(label);
if (tag) {
return globalEnvironment?.envs[tag] ?? "";
}
return label;
};
};
Service.prototype.buildEnvs = async function(environmentLabel) {
const globalEnvironment = await Environment.find(environmentLabel);
if (!globalEnvironment) return {};
const extendedEnvironments = await globalEnvironment.getExtendedEnvironments();
const environmentsKeys = /* @__PURE__ */ new Map();
const searchEnv = (label, env) => {
const tag = this.extractTag(label);
if (tag) {
return env[tag] ?? "";
}
return label;
};
extendedEnvironments.map((extendedEnvironment) => {
Object.keys(this.envs[extendedEnvironment.label] || {}).forEach((key) => {
const env = this.envs[extendedEnvironment.label][key];
if (!environmentsKeys.has(key)) {
for (let i = 0; i < extendedEnvironments.length; i++) {
let occurrence = searchEnv(env.systemOverride, extendedEnvironments[i].envs) || searchEnv(env.override, extendedEnvironments[i].envs) || searchEnv(env.value, extendedEnvironments[i].envs);
if (occurrence) occurrence = `${env.prefix || ""}${occurrence || ""}${env.suffix || ""}`;
const value = occurrence;
if (value) {
environmentsKeys.set(key, value);
return;
}
}
}
});
});
return [...environmentsKeys].reduce((agg, [key, value]) => {
agg[key] = value;
return agg;
}, {});
};
Service.prototype.extractTag = function(field) {
const extractedTag = /{{(.*)}}/gi.exec(field)?.[1]?.trim();
return extractedTag;
};
Service.prototype.parseIncomingCommandDocker = async function(command) {
let { spawnCmd, spawnArgs = [], spawnOptions = { envs: [] } } = command;
const isAlive = await execAsync(`docker inspect --format {{.State.Pid}} ${this.container.name}`, {}).then((pid) => pid.trim() !== "0").catch(() => false);
const cmd = "docker";
const args2 = isAlive ? ["exec", this.container.name, spawnCmd, ...spawnArgs] : [
"run",
"--rm",
...await this.getDockerEnvsArgs(),
...await this.getDockerVolumesArgs(),
...await this.getDockerPorts(),
"--network",
"host",
"--name",
this.container.name,
this.container.name,
"sh",
`-c '${spawnCmd} ${spawnArgs.join(" ")}'`
];
const cwd = pathfs.resolve(replaceEnvs(spawnOptions.cwd || this.getRootPath() || "."));
const options = {
cwd,
shell: isWindows ? process.env.ComSpec : "/bin/sh"
};
return { cmd, args: args2, options };
};
Service.prototype.parseIncomingCommand = async function(command) {
const { spawnCmd, spawnArgs = [], spawnOptions = { envs: [] } } = command;
let cmd = spawnCmd?.split(" ")?.[0];
const argFromCmd = spawnCmd?.split(" ")?.slice(1).join(" ");
let args2 = [argFromCmd, ...spawnArgs].filter((a) => a);
const currentAlias = alias[cmd];
if (currentAlias) {
cmd = currentAlias?.cmd || cmd;
args2 = [...currentAlias?.args || [], ...args2];
}
const cwd = pathfs.resolve(replaceEnvs(spawnOptions.cwd || this.getRootPath() || "."));
const options = {
...spawnOptions,
cwd,
shell: isWindows ? process.env.ComSpec : "/bin/sh",
env: {
...process.env,
...await this.buildEnvs(this.Stack.getCurrentEnvironment()?.label)
}
};
if (cmd.match(/[/\\]/g)) {
cmd = path.resolve(options.cwd.toString(), cmd);
}
options.env = { ...process.env, ...options.env };
return { cmd, args: args2, options };
};
function replaceHome(str) {
return str.startsWith("~") ? pathfs.resolve(os.homedir(), str.replace("~/", "")) : pathfs.resolve(str);
}
Service.prototype.getDockerVolumesArgs = async function() {
const internalVolumeRootPath = replaceHome(this.container.sharedVolume);
const volumesCmd = this.container.volumes.map((v) => {
let [external, internal] = v.split(":");
if (external) external = pathfs.resolve(replaceHome(replaceEnvs(external)));
if (internal) internal = pathfs.resolve(replaceHome(replaceEnvs(internal)));
return ["-v", `"${external}:${internal || external}"`];
});
volumesCmd.push(...await PromiseB2.map(this.container.ignoreVolumes, async (ignoredVolume) => {
const volumePath = pathfs.join(internalVolumeRootPath, `ignored-volume-${humanStringToKey(this.label)}`, ignoredVolume);
if (!existsSync(volumePath)) await mkdir(volumePath, { recursive: true });
return ["-v", `"${volumePath}:${ignoredVolume}"`];
}).filter((f) => !!f?.length));
return volumesCmd.flat(1);
};
Service.prototype.getDockerPorts = async function() {
const portsCmd = [];
this.container.ports.forEach((port) => {
portsCmd.push("-p", port);
});
return portsCmd.flat(1);
};
Service.prototype.getDockerEnvsArgs = async function() {
const envs = await this.buildEnvs(this.Stack.getCurrentEnvironment()?.label);
const envCmd = [];
Object.keys(envs).forEach((key) => {
envCmd.push("-e", `"${key}=${envs[key]}"`);
});
return envCmd;
};
Service.prototype.launchDockerBuild = async function({ isMainProcess }) {
const internalVolumeRootPath = replaceHome(this.container.sharedVolume);
const dockerFilePath = pathfs.resolve(internalVolumeRootPath, `Dockerfile.${this.container.name}`);
const dockerContextPath = pathfs.resolve(internalVolumeRootPath, ".empty-context");
const command = {
spwanCmd: "docker",
spawnArgs: ["build", "-f", dockerFilePath, "-t", this.container?.name || "", dockerContextPath],
spawnOptions: { cwd: internalVolumeRootPath, shell: isWindows ? true : "/bin/sh", env: process.env }
};
this.add('<i class="fab fa-docker" title="docker"></i> <i class="fas fa-hard-hat" title="build"></i> Building docker image...', { source: "stdout" }, { pid: null, isMainProcess, command });
this.add(`<i class="fab fa-docker" title="docker"></i> <i class="fas fa-hard-hat" title="build"></i> ${command.spwanCmd} ${command.spawnArgs?.join(" ")}}`, { source: "stdout" }, { pid: null, isMainProcess, command });
await new Promise((resolve, reject) => {
const buildProcess = spawn(command.spwanCmd, command.spawnArgs, command.spawnOptions);
new CreateInterface({
input: buildProcess.stdout
}).on("line", (message) => {
this.add(`<i class="fab fa-docker" title="docker"></i> <i class="fas fa-hard-hat" title="build"></i> ${message}`, { source: "stdout" }, { isMainProcess, pid: buildProcess.pid, command });
});
new CreateInterface({
input: buildProcess.stderr
}).on("line", (message) => {
this.add(`<i class="fab fa-docker" title="docker"></i> <i class="fas fa-hard-hat" title="build"></i> ${message}`, { source: "stdout" }, { isMainProcess, pid: buildProcess.pid, command });
});
buildProcess.on("exit", (code) => {
if (code) {
this.exited = true;
this.crashed = true;
return reject(code);
}
return resolve(null);
});
});
};
Service.prototype.launchDockerBootstrap = async function({ isMainProcess }) {
const volumesCmd = await this.getDockerVolumesArgs();
const baseArgs = ["run", "--name", this.container.name, "--init", "--rm", , ...isWindows ? [] : ["--user", this.container.user || `${uid}:${gid}`], "--network", "host", ...volumesCmd];
if (this.container.bootstrap) {
const envCmd = await this.getDockerEnvsArgs();
await PromiseB2.mapSeries(this.container.bootstrap.commands || [], async (command) => {
await new Promise((resolve, reject) => {
const entrypoint = command.entrypoint ? ["--entrypoint", `${command.entrypoint}`] : [];
const userEntrypoint = command.user && !isWindows ? ["--user", `${command.user || this.container.user || `${uid}:${gid}`}`] : [];
const bootstrapArgs = (
/**@type {String[]}*/
[
...baseArgs,
...entrypoint.flat(1),
...userEntrypoint.flat(1),
...envCmd.flat(1),
this.container.name,
command.cmd
].filter((a) => !!a)
);
const commandBootstrap = {
spawnCmd: "docker",
spawnArgs: bootstrapArgs,
spawnOptions: { shell: isWindows ? process.env.ComSpec : "/bin/sh" }
};
this.add(`<i class="fab fa-docker" title="docker"></i> <i class="fas fa-hourglass-start" title="bootstrap"></i> ${commandBootstrap.spawnCmd} ${commandBootstrap.spawnArgs?.join(" ")}`, { source: "stdout" }, { isMainProcess, pid: null, command: commandBootstrap });
const bootstrapProcess = spawn(
commandBootstrap.spawnCmd,
commandBootstrap.spawnArgs,
commandBootstrap.spawnOptions
);
new CreateInterface({
input: bootstrapProcess.stdout
}).on("line", (message) => {
this.add(`<i class="fab fa-docker" title="docker"></i> <i class="fas fa-hourglass-start" title="bootstrap"></i> ${message}`, { source: "stdout" }, { isMainProcess, pid: bootstrapProcess.pid, command: commandBootstrap });
});
new CreateInterface({
input: bootstrapProcess.stderr
}).on("line", (message) => {
this.add(`<i class="fab fa-docker" title="docker"></i> <i class="fas fa-hourglass-start" title="bootstrap"></i> ${message}`, { source: "stderr" }, { isMainProcess, pid: bootstrapProcess.pid, command: commandBootstrap });
});
bootstrapProcess.on("exit", (code) => {
if (code) return reject(code);
return resolve(null);
});
});
});
}
};
Service.prototype.buildDocker = async function({
isMainProcess
}) {
const internalVolumeRootPath = replaceHome(this.container.sharedVolume);
const dockerFilePath = pathfs.resolve(internalVolumeRootPath, `Dockerfile.${this.container.name}`);
const dockerIgnoreFilePath = pathfs.resolve(internalVolumeRootPath, ".dockerignore");
const dockerContextPath = pathfs.resolve(internalVolumeRootPath, ".empty-context");
if (!existsSync(internalVolumeRootPath)) await mkdir(internalVolumeRootPath, { recursive: true });
if (!existsSync(dockerContextPath)) await mkdir(dockerContextPath, { recursive: true });
await writeFile(dockerFilePath, `${this.container.build || ""}`);
await writeFile(dockerIgnoreFilePath, "Dockerfile.*".trim(), "utf-8");
await this.launchDockerBuild({ isMainProcess });
await this.launchDockerBootstrap({ isMainProcess });
this.container.customPid = async () => {
const getPid = () => execAsync(`docker inspect --format {{.State.Pid}} ${this.container?.name}`, {}).then((a) => a.trim()).catch(() => null);
let pid = await getPid();
if (!pid) {
await wait(1e3);
pid = await getPid();
}
return pid && !Number.isNaN(+pid) ? +pid : null;
};
};
var wait = (ms) => new Promise((resolve) => {
setTimeout(resolve, ms);
});
function killAsync(pid) {
return new Promise((resolve, reject) => {
kill(pid, (err, children) => {
if (err) return reject(err);
return resolve(children);
});
});
}
function checkport(_port) {
return new Promise((resolve) => {
const s = net.createServer();
s.once("error", () => {
s.close();
resolve(false);
});
s.once("listening", () => {
resolve(true);
s.close();
});
s.listen(_port);
});
}
function extractTag(field) {
const extractedTag = /{{(.*)}}/gi.exec(field)?.[1]?.trim();
return extractedTag || "";
}
module2.exports = Service;
}
});
// helpers/exportedHelpers.js
var require_exportedHelpers = __commonJS({
"helpers/exportedHelpers.js"(exports2, module2) {
"use strict";
module2.exports = {
/**
* @param {string} str
* @param {string} search
*/
searchString(str, search) {
return str?.toUpperCase()?.includes(search);
}
};
}
});
// helpers/version.js
var require_version = __commonJS({
"helpers/version.js"(exports2, module2) {
"use strict";
var { existsSync, readFileSync } = require("fs");
var path = require("path");
var file = {
version: "0.0.0"
};
if (existsSync(path.resolve(__dirname, "./package.json"))) {
file.version = JSON.parse(readFileSync(path.resolve(__dirname, "./package.json"), { encoding: "utf-8" })).version;
} else if (existsSync(path.resolve(__dirname, "../package.json"))) {
file.version = JSON.parse(readFileSync(path.resolve(__dirname, "../package.json"), { encoding: "utf-8" })).version;
} else if (existsSync(path.resolve(__dirname, "../../../lerna.json"))) {
file.version = JSON.parse(readFileSync(path.resolve(__dirname, "../../../lerna.json"), { encoding: "utf-8" })).version;
}
module2.exports = {
version: file.version
};
}
});
// models/saves.js
var require_saves = __commonJS({
"models/saves.js"(exports2, module2) {
"use strict";
var pathfs = require("path");
var homedir = require("os").homedir();
var {
existsSync,
mkdirSync,
writeFileSync,
readFileSync
} = require("fs");
var confDir = pathfs.resolve(homedir, ".stack-monitor");
function getSave(file, initialData, options = {}) {
if (!existsSync(confDir)) mkdirSync(confDir);
const dataConfPath = pathfs.resolve(confDir, file);
if (!existsSync(dataConfPath)) writeFileSync(dataConfPath, JSON.stringify(initialData), "utf-8");
const data = JSON.parse(readFileSync(dataConfPath, "utf-8"));
options?.afterGet?.(data);
return {
/** @type {T} */
data,
save() {
options?.beforeSave?.(data);
writeFileSync(dataConfPath, JSON.stringify(data), "utf-8");
}
};
}
module2.exports = getSave;
}
});
// models/stack.js
var require_stack = __commonJS({
"models/stack.js"(exports2, module2) {
"use strict";
var { sockets } = require_src();
var plugins = require_plugins();
var PromiseB2 = require("bluebird");
var Service = require_Service();
var ports = require_ports();
var dbs2 = require_dbs();
var EnvironmentModel = require_Environment();
var EncryptionKey = require_EncryptionKey();
var CustomObservable = require_CustomObservable();
var args2 = require_args();
var { existsSync, mkdirSync } = require("fs");
var pathfs = require("path");
var { default: axios } = require("axios");
function Stack(stack) {
this.onServerLauch = new CustomObservable();
return (async () => {
this.watchFiles = stack.watchFiles || [];
this.logParsers = stack.logParsers || [];
this.monorepo = stack.monorepo || false;
this.themes = stack.themes || {};
this.environments = stack.environments ? stack.environments.map((env) => new EnvironmentModel(env)) : [];
this.documentation = stack.documentation;
this.services = await PromiseB2.map(stack.services || [], (service) => new Service(
service,
/** @type {StackWithPlugins} */
Stack
));
this.helpers = require_exportedHelpers();
return this;
})();
}
Stack.currentStack = null;
Stack.currentWatches = [];
Stack.currentEnvironment = null;
Stack.Socket = sockets;
Stack.plugins = plugins;
Stack.version = require_version().version || "";
Stack.url = `http://localhost:${ports.http}`;
Stack.port = +ports.http;
Stack.parsers = {
links: require_link(),
jsons: require_json(),
debug: require_debug()
};
Stack.helpers = require_exportedHelpers();
Stack.getSave = require_saves();
Stack.prototype.enable = async function(servicesLabelSelected) {
const services = this.getServices();
await PromiseB2.map(servicesLabelSelected, (serviceConf) => {
const service = services.find((service2) => serviceConf.label === service2.label);
if (!service) return null;
const hasChanged = serviceConf.enabled !== service.enabled;
if (hasChanged) {
if (serviceConf.enabled) {
service.enable();
return service.launch();
}
service.disable();
return service.kill();
}
return null;
});
};
Stack.restart = async function() {
await PromiseB2.map(this.getEnabledServices(), (service) => service.restart());
};
Stack.kill = async function() {
await PromiseB2.map(this.getEnabledServices(), (service) => service.kill());
};
Stack.getCurrentEnvironment = function() {
return Stack.currentEnvironment;
};
Stack.prototype.toStorage = function() {
return {
watchFiles: this.watchFiles,
themes: this.themes,
services: this.services.map((s) => s.toStorage())
};
};
Stack.getRootPath = () => {
const rootPath = pathfs.resolve(args2.rootPath, ".stackmonitor");
if (!existsSync(rootPath)) mkdirSync(rootPath, { recursive: true });
return rootPath;
};
Stack.parse = async function() {
const dbsRootPath = await dbs2.getDbs("services");
let services = [];
let environments = [];
try {
services = await PromiseB2.map(dbsRootPath, (id) => Service.load(id, Stack));
environments = await EnvironmentModel.all();
} catch (error) {
console.error(error);
this.Socket.emit("system:wrongKey");
}
return new Stack({
environments,
services
});
};
Stack.prototype.launch = async function() {
await PromiseB2.map(this.getServices(), (microservice) => {
if (microservice.enabled) {
return microservice.launch();
}
return microservice.kill();
});
};
Stack.getStack = function() {
return Stack.currentStack;
};
Stack.getServices = function() {
return Stack.currentStack?.services || [];
};
Stack.deleteService = async function(label) {
if (!Stack.currentStack) return;
const service = this.findService(label);
Stack.currentStack.services = Stack.currentStack.services.filter((a) => a.label !== label);
await service.delete();
};
Stack.prototype.getServices = function() {
return Stack.getServices();
};
Stack.getAxios = function() {
return axios.create({ baseURL: this.url });
};
Stack.getEnabledServices = function() {
return Stack.getServices().filter((s) => s.enabled);
};
Stack.prototype.getEnabledServices = function() {
return Stack.getEnabledServices();
};
Stack.prototype.exportInApi = function() {
const res = { ...this };
res.services = res.services?.map((s) => s.exportInApi());
return res;
};
Stack.findService = function(serviceLabel) {
return Stack.getServices().filter((s) => s.label === serviceLabel)[0];
};
Stack.prototype.findService = function(serviceLabel) {
return Stack.findService(serviceLabel);
};
Stack.selectConf = async function() {
await EncryptionKey.init();
if (!EncryptionKey.encryptionKey) {
await EncryptionKey.saveKey(await EncryptionKey.generateKey());
}
Stack.currentStack = await this.parse();
Stack.currentEnvironment = args2.e || process.env.STACK_MONITOR_DEFAULT_ENVIRONMENT ? Stack.currentStack.environments.find((env) => env.label === args2.e?.toString() || env.label === process.env.STACK_MONITOR_DEFAULT_ENVIRONMENT) || null : Stack.currentStack.environments.find((env) => env.default) || null;
if (process.env.STACK_MONITOR_SERVICES) {
process.env.STACK_MONITOR_SERVICES.split(",").forEach((serviceLabel) => {
const service = Stack.findService(serviceLabel);
if (service) service.enable();
});
}
return sockets.emit("stack:selectConf");
};
Stack.prototype.changeEnvironment = async function(envLabel) {
const environment = await EnvironmentModel.find(envLabel);
if (environment) {
Stack.currentEnvironment = environment;
Stack.getServices().forEach((service) => {
if (!service.envs[envLabel]) {
service.envs[envLabel] = {};
}
});
const enabledServices = Stack.getEnabledServices();
await Stack.kill();
enabledServices.forEach((s) => {
s.enabled = true;
s.store = [];
});
await Stack.getStack()?.launch();
} else {
throw new Error("Environment not found");
}
};
Stack.stopWatchers = function() {
Stack.currentWatches.forEach((currentWatch) => currentWatch.close());
};
var pluginsToLoad = (
/** @type {(keyof typeof plugins)[]} */
Object.keys(plugins).reduce(
(p, key) => {
const plugin = plugins[key];
if (plugin.export) {
p[key] = typeof plugin.export === "function" && !/^\s*class\s+/.test(plugin.export.toString()) ? plugin.export(
/** @type {StackWithPlugins} */
Stack
) : plugin.export;
}
return p;
},
/** @type {OmitNever<typeof plugins>} */
{}
)
);
module2.exports = /** @type {StackWithPlugins} */
Object.assign(Stack, pluginsToLoad);
}
});
// helpers/console.table.js
var require_console_table = __commonJS({
"helpers/console.table.js"(exports2, module2) {
"use strict";
var { Transform } = require("stream");
var { Console } = require("console");
function table(input) {
const ts = new Transform({ transform(chunk, enc, cb) {
cb(null, chunk);
} });
const logger = new Console({ stdout: ts });
logger.table(input);
const table2 = (ts.read() || "").toString();
let result = "";
for (const row of table2.split(/[\r\n]+/)) {
let r = row.replace(/[^┬]*┬/, "\u250C");
r = r.replace(/^├─*┼/, "\u251C");
r = r.replace(/│[^│]*/, "");
r = r.replace(/^└─*┴/, "\u2514");
r = r.replace(/'/g, " ");
result += `${r}
`;
}
console.log(result);
}
module2.exports = table;
}
});
// helpers/plugins.js
var require_plugins2 = __commonJS({
"helpers/plugins.js"(exports2, module2) {
"use strict";
var plugins = require_plugins();
var Stack = require_stack();
var routes = [];
var forService = [];
var allPlugins = {};
Object.keys(plugins).map((key) => plugins[
/** @type {keyof (typeof plugins)} */
key
]).forEach((plugin) => {
if (!plugin) return null;
if (plugin.placements?.includes("service")) forService.push(plugin);
if (plugin.routes) {
routes.push(plugin.routes(Stack));
delete plugin.routes;
}
(plugin.placements || []).forEach((p) => {
const position = typeof p === "string" ? p : p.position;
if (!position) return null;
if (!allPlugins[position]) allPlugins[position] = [];
if (!allPlugins[position].includes(plugin)) {
allPlugins[position].push({
...plugin,
placements: (plugin.placements || []).filter((_p) => typeof _p !== "string" && _p.position === position)
});
}
return null;
});
return plugin;
});
module2.exports = {
forService,
...allPlugins,
routes
};
}
});
// routes/plugins.js
var require_plugins3 = __commonJS({
"routes/plugins.js"(exports2, module2) {
"use strict";
var { express } = require_src6();
var router = express.Router();
var PromiseB2 = require("bluebird");
var { findService } = require_stack();
var Stack = require_stack();
var plugins = require_plugins2();
router.get("/services/:service", async (req, res) => {
const service = findService(req.params.service);
const services = await PromiseB2.filter(plugins.forService, async (plugin) => plugin.hidden ? !await plugin.hidden(service, Stack, "service") : true);
services.sort((a, b) => (b?.order || Number.MAX_SAFE_INTEGER) - (a?.order || Number.MAX_SAFE_INTEGER));
res.send(services);
});
router.get("/services", async (req, res) => {
const services = await PromiseB2.filter(plugins.forService, async (plugin) => plugin.hidden ? !await plugin.hidden(null, Stack, "global") : true);
res.send(services);
});
router.get("/:type", async (req, res) => {
const services = await PromiseB2.filter(plugins[req.params.type], async (plugin) => plugin.hidden ? !await plugin.hidden(null, Stack, req.params.type) : true);
services.sort((a, b) => (a.order || 1e3) - (b.order || 1e3));
res.send(services);
});
module2.exports = router;
}
});
// routes/system.js
var require_system = __commonJS({
"routes/system.js"(exports2, module2) {
"use strict";
var { express } = require_src6();
var axios = require("axios");
var router = express.Router();
var pidusageTree = require("pidusage-tree");
var os = require("os");
var Stack = require_stack();
var { execAsync } = require_exec();
var args2 = require_args();
router.get("/:service/infos", async (req, res) => {
try {
const service = Stack.findService(req.params.service);
if (service.container?.name) {
const data = await execAsync(`docker container stats --format '{ "cpuPerc": "{{.CPUPerc}}", "memPerc": "{{.MemPerc}}" }' --no-stream --no-trunc user_api_monorepo`, {}).then((_data) => {
const data2 = JSON.parse(_data);
const cpuPerc = data2.cpuPerc.trim().replaceAll("%", "");
const memPerc = data2.memPerc.trim().replaceAll("%", "");
return {
cpu: Number.isNaN(+cpuPerc) ? 0 : +cpuPerc,
mem: Number.isNaN(+memPerc) ? 0 : +memPerc
};
});
res.json(data);
} else {
const pid = service.container?.customPid ? await service.container.customPid({ cmd: null, args: null, pid: null }) : service.pids[0]?.pid;
res.json(pid ? await getCPU(pid) : {
cpu: null,
ram: null
});
}
} catch (e) {
res.json({ cpu: null, mem: null });
}
});
router.get("/disconnect", async () => {
process.exit(0);
});
router.get("/restart", async (req, res) => {
res.json({ success: true, message: "Restart in progress..." });
setTimeout(() => {
console.log("Restarting application...");
require("child_process").spawn(process.argv[0], process.argv.slice(1), {
cwd: args2.initialCwd || process.cwd(),
detached: true,
stdio: "inherit"
}).unref();
process.exit(0);
}, 500);
});
router.get("/proxy-img", async (req, res) => {
axios({
method: "get",
url: req.query.url,
responseType: "stream"
}).then((response) => {
for (const key in response.headers) {
if (response.headers.hasOwnProperty(key)) {
const element = response.headers[key];
res.header(key, element);
}
}
res.status(response.status);
response.data.pipe(res);
}).catch(({ response }) => {
for (const key in response.headers) {
if (response.headers.hasOwnProperty(key)) {
const element = response.headers[key];
res.header(key, element);
}
}
res.status(response.status);
response.data.pipe(res);
});
});
async function getCPU(pid) {
const tree = await pidusageTree(pid).catch(() => null);
const cpus = [];
let mem = 0;
let cpuPerc = 0;
let totalMem = 0;
if (tree) {
Object.keys(tree).forEach((key) => {
if (tree[key]?.cpu) cpus.push(tree[key].cpu);
if (tree[key]?.memory) mem += tree[key].memory;
});
cpuPerc = cpus.reduce((prev, curr) => prev + curr, 0) / cpus.length;
totalMem = os.totalmem();
}
return {
cpu: Number.isNaN(cpuPerc) ? 0 : cpuPerc,
mem: mem / totalMem
};
}
module2.exports = router;
}
});
// models/myConfs.js
var require_myConfs = __commonJS({
"models/myConfs.js"(exports2, module2) {
"use strict";
var pathfs = require("path");
var os = require("os");
var {
existsSync,
mkdirSync,
writeFileSync,
readFileSync
} = require("fs");
var persistencePath = pathfs.resolve(os.homedir(), ".stack-monitor");
if (!existsSync(persistencePath)) mkdirSync(persistencePath, { recursive: true });
var confsPath = pathfs.resolve(persistencePath, "confs");
if (!existsSync(confsPath)) writeFileSync(confsPath, JSON.stringify([]), "utf-8");
var store = JSON.parse(readFileSync(confsPath, "utf-8"));
module2.exports = {
/** @type {string[]} */
confs: store,
/** @param {string} conf */
async add(conf) {
if (!store.includes(conf)) {
store.push(conf);
await writeFileSync(confsPath, JSON.stringify(store), "utf-8");
}
},
/** @param {string} conf */
async remove(conf) {
store.splice(store.indexOf(conf), 1);
await writeFileSync(confsPath, JSON.stringify(store), "utf-8");
}
};
}
});
// routes/stack.js
var require_stack2 = __commonJS({
"routes/stack.js"(exports2, module2) {
"use strict";
var { express } = require_src6();
var { exec } = require("child_process");
var open = require("open");
var commandExists = require("command-exists").sync;
var { Octokit } = require("fix-esm").require("@octokit/core");
var { restEndpointMethods } = require("fix-esm").require("@octokit/plugin-rest-endpoint-methods");
var { sockets } = require_src();
var Stack = require_stack();
var myConfs = require_myConfs();
var router = express.Router();
var { findService } = Stack;
var Service = require_Service();
var Environment = require_Environment();
var { replaceEnvs } = require_stringTransformer_helper();
var MyOctokit = Octokit.plugin(restEndpointMethods);
router.post("/services", async (req, res) => {
const existingService = Stack.findService(req.body.label);
if (existingService) {
await existingService.constructor(req.body, Stack, { isUpdate: true });
await existingService.save();
sockets.emit("conf:update", [existingService.label]);
res.send(existingService);
} else {
const service = await new Service(req.body, Stack);
await service.save();
Stack.getStack()?.services.push(service);
sockets.emit("conf:update", [service.label]);
res.send(service);
}
});
router.put("/:service/duplicate", async (req, res) => {
const existingService = findService(req.params.service).toStorage();
const service = await new Service(existingService, Stack);
service.label = req.body.label;
await service.save();
Stack.getStack()?.services.push(service);
sockets.emit("conf:update");
res.send(service);
});
router.patch("/:service", async (req, res) => {
const service = findService(req.params.service);
await service.save();
Stack.getStack()?.services.push(service);
sockets.emit("conf:update");
res.send(service);
});
router.get("/export-env", async (req, res) => {
const service = findService(req.query.service?.toString() || "");
if (!service) return res.status(404).send("Service not found");
const command = service.commands?.[+(req.query.commandIndex?.toString() || "0")];
if (!command) return res.status(404).send("Command not found");
const envs = await service.buildEnvs(req.query.environment);
res.send(envs);
});
router.delete("/:service", async (req, res) => {
await Stack.deleteService(req.params.service);
sockets.emit("conf:update");
res.send("ok");
});
router.get("/has-update", async (req, res) => {
try {
const localVersion = `v${require_version().version}`;
const octokit = new MyOctokit({ auth: process.env.STACK_MONITOR_GH_APIKEY });
const { data: tags } = await octokit.rest.repos.listTags({ owner: "clabroche", repo: "stack-monitor" });
const remoteVersion = tags[0]?.name;
return res.json({
local: localVersion,
remote: remoteVersion,
hasUpdate: localVersion !== remoteVersion
});
} catch (error) {
console.error(error);
return res.json(null);
}
});
router.get("/configuration", (req, res) => {
res.json(Stack.getStack()?.exportInApi());
});
router.get("/environment", (req, res) => {
res.json(Stack.getCurrentEnvironment());
});
router.patch("/environments/:environmentLabel", async (req, res) => {
const env = await Stack.getStack()?.environments.find((env2) => env2.label === req.params.environmentLabel);
if (!env) return res.status(404).send("Environment not found");
await env.update(req.body);
await sockets.emit("reloadEnvironments");
res.json(Stack.getCurrentEnvironment());
});
router.delete("/environments/:environmentLabel", async (req, res) => {
const env = await Stack.getStack()?.environments.find((env2) => env2.label === req.params.environmentLabel);
if (!env) return res.status(404).send("Environment not found");
await env.delete();
await sockets.emit("reloadEnvironments");
res.json("ok");
});
router.get("/additional-themes", (req, res) => {
const additionalThemes = Stack.getStack()?.themes || {};
res.json(additionalThemes);
});
router.post("/environment", async (req, res) => {
const { environment } = req.body;
if (!environment) return res.status(400).send("Provide an environment field in body");
await Stack.getStack()?.changeEnvironment(environment);
return res.json(Stack.getCurrentEnvironment());
});
router.post("/environment/create", async (req, res) => {
const { environment } = req.body;
if (!environment) return res.status(400).send("Provide an environment field in body");
const newEnvironment = new Environment(environment);
await newEnvironment.save();
await sockets.emit("reloadEnvironments");
return res.json(Stack.getCurrentEnvironment());
});
router.get("/environments", async (req, res) => {
res.json(await Environment.all());
});
router.get("/all-confs-path", (req, res) => {
res.json(myConfs.confs);
});
router.post("/select-conf", async (req, res) => {
const { path } = req.body;
await Stack.selectConf();
res.json(path);
});
router.post("/delete-conf", async (req, res) => {
const { path } = req.body;
await myConfs.remove(path);
res.json(path);
});
router.post("/choose", async (req, res) => {
const servicesLabelSelected = req.body;
if (!Array.isArray(servicesLabelSelected)) return res.status(400).send("you should provide an array as body with all service label you want to launch");
const stack = Stack.getStack();
if (!stack) return res.status(500).send("Stack not configured");
await stack.enable(servicesLabelSelected);
return res.json(stack.getEnabledServices().map((s) => s.exportInApi()));
});
router.get("/", (req, res) => {
const stack = Stack.getStack();
res.json(stack ? stack.exportInApi() : null);
});
router.get("/services", (req, res) => {
res.json(Stack.getServices().map((s) => s.exportInApi()));
});
router.get("/restart-all", async (req, res) => {
try {
await Stack.restart();
res.send({ success: true });
} catch (error) {
console.error("Error during restart all:", error);
const errorMessage = error instanceof Error ? error.message : String(error);
res.status(500).send({ success: false, error: errorMessage });
}
});
router.get("/:service", (req, res) => {
const service = findService(req.params.service);
res.send(service.exportInApi());
});
router.get("/:service/open-folder", (req, res) => {
open(replaceEnvs(req.query.path?.toString() || "."));
res.send();
});
router.get("/:service/restart", async (req, res) => {
const service = findService(req.params.service);
await service.restart();
res.send();
});
router.get("/:service/start", async (req, res) => {
const service = findService(req.params.service);
await service.launch();
res.send();
});
router.get("/:service/stop", async (req, res) => {
const service = findService(req.params.service);
await service.kill();
res.send();
});
module2.exports = router;
}
});
// routes/fs.js
var require_fs = __commonJS({
"routes/fs.js"(exports2, module2) {
"use strict";
var { express } = require_src6();
var router = express.Router();
var pathfs = require("path");
var PromiseB2 = require("bluebird");
var os = require("os");
var { sort } = require("fast-sort");
var { readdir, readFile, stat } = require("fs/promises");
router.get("/home-dir", async (req, res) => {
res.send(os.homedir());
});
router.get("/ls", async (req, res) => {
const path = req.query.path?.toString() || __dirname;
const dir = await readdir(path);
const parentDirectory = {
absolutePath: pathfs.resolve(path, ".."),
name: "..",
isDirectory: true
};
let dirs = [parentDirectory];
await PromiseB2.map(dir, async (entry) => {
try {
if (entry.charAt(0) === ".") return null;
const absolutePath = pathfs.resolve(path, entry);
const entryStat = await stat(absolutePath);
const entryInfos = {
absolutePath,
name: entry,
isDirectory: entryStat.isDirectory(),
npmInfos: null,
isStack: false
};
if (entryInfos.isDirectory) {
entryInfos.npmInfos = await getNpmInfos(entryInfos.absolutePath);
} else if (pathfs.extname(absolutePath) === ".js") {
try {
const stack = require(absolutePath);
if (Array.isArray(stack) && stack.length && stack[0].label && stack[0].spawnCmd) {
entryInfos.isStack = true;
}
} catch (error) {
console.error(error);
}
}
dirs.push(entryInfos);
return null;
} catch (error) {
console.error(error);
return null;
}
});
dirs = sort(dirs).asc((d) => d.name.toUpperCase());
res.json(dirs);
});
async function getNpmInfos(path) {
const dir = await readdir(path);
if (dir.includes("package.json")) {
const packageJSON = JSON.parse(await readFile(pathfs.resolve(path, "package.json"), "utf-8"));
return {
path,
packageJSON,
version: packageJSON.version,
name: packageJSON.name,
author: packageJSON.author
};
}
return null;
}
module2.exports = router;
}
});
// routes/crypto.js
var require_crypto2 = __commonJS({
"routes/crypto.js"(exports2, module2) {
"use strict";
var { express } = require_src6();
var config = require_EncryptionKey();
var dbs2 = require_dbs();
var { encrypt } = require_crypto();
var fs = require("fs").promises;
var path = require("path");
var HTTPError = require_src2();
var { sockets } = require_src();
var conflictStorage = require_conflictStorage();
var router = express.Router();
router.get("/generate-key", async (req, res) => {
res.send(await config.generateKey());
});
router.post("/encryption-key", async (req, res) => {
const { key } = req.body;
await config.saveKey(key);
return res.send(key);
});
router.get("/should-setup", async (req, res) => {
if (!config.encryptionKey) return res.json(true);
const envSample = (await dbs2.getDbs("envs"))[0];
try {
if (envSample) await dbs2.getDb(`envs/${envSample}`).read();
return res.json(false);
} catch (error) {
console.error(error);
return res.json(true);
}
});
router.get("/encryption-key", async (req, res) => {
res.send(config.encryptionKey);
});
router.post("/test-encryption-key", async (req, res) => {
const result = await config.testKey(req.body?.key || config.encryptionKey);
if (result) return res.send("ok");
return res.status(400).send(config.encryptionKey);
});
router.get("/pending-conflicts", async (req, res) => {
res.json(conflictStorage.getPendingConflicts());
});
router.post("/resolve-conflict", async (req, res) => {
try {
const { original, resolution, conflictId } = req.body;
if (!original || !resolution) {
throw new HTTPError("Missing original or resolution data", 400);
}
const headerMatch = original.match(/<<<<<<< HEAD\r?\n([\s\S]*?)\r?\n=======\r?\n([\s\S]*?)\r?\n>>>>>>>\s?(.*)/);
if (!headerMatch) {
throw new HTTPError("Invalid conflict format", 400);
}
const branchIdentifier = headerMatch[3];
const conflict = conflictStorage.getPendingConflicts().find((c) => c.id === conflictId);
const filePath = conflict?.filePath;
if (!filePath) {
console.warn(`No file path found for conflict ID: ${conflictId}`);
}
const encryptedResolution = await encrypt(resolution, { additionnalNonce: branchIdentifier });
let fileWritten = false;
if (filePath) {
try {
const fileExists = await fs.access(filePath).then(() => true).catch(() => false);
if (fileExists) {
await fs.writeFile(filePath, encryptedResolution, "utf8");
console.log(`Conflict resolved and saved to file: ${filePath}`);
fileWritten = true;
} else {
console.error(`Could not resolve conflict: file does not exist at path: ${filePath}`);
}
} catch (writeError) {
const errorMessage = writeError instanceof Error ? writeError.message : String(writeError);
console.error("Error writing resolved file:", writeError);
throw new HTTPError(`Failed to write to file: ${errorMessage}`, 500);
}
}
if (conflictId) {
conflictStorage.removeConflict(conflictId);
}
sockets.emit("crypto:conflict-resolved", { conflictId });
return res.json({
success: true,
encryptedResolution,
filePath,
fileWritten
});
} catch (error) {
console.error("Error resolving conflict:", error);
let statusCode = 500;
let message = "Failed to resolve conflict";
if (error instanceof Error) {
message = error.message;
if (error instanceof HTTPError && typeof error.code === "number") {
statusCode = error.code;
}
}
return res.status(statusCode).json({
success: false,
message
});
}
});
module2.exports = router;
}
});
// routes/parsers.js
var require_parsers = __commonJS({
"routes/parsers.js"(exports2, module2) {
"use strict";
var { express } = require_src6();
var Parser2 = require_Parser();
var router = express.Router();
router.post("/", async (req, res) => {
delete req.body.id;
const parser2 = new Parser2(req.body);
await parser2.save();
res.json(parser2.toStorage());
});
router.put("/:id", async (req, res) => {
const parser2 = await Parser2.find(req.params.id);
if (!parser2) return res.status(404).send("Parser not found");
await parser2.update(req.body);
res.json(parser2.toStorage());
});
router.delete("/:id", async (req, res) => {
const parser2 = await Parser2.find(req.params.id);
if (!parser2) return res.status(404).send("Parser not found");
await parser2?.delete();
res.json("ok");
});
router.get("/", async (req, res) => {
const parsers = await Parser2.all();
res.json(parsers);
});
module2.exports = router;
}
});
// models/editors.js
var require_editors = __commonJS({
"models/editors.js"(exports2, module2) {
"use strict";
var commandExists = require("command-exists").sync;
var codeArgsBuilder = ({ link, command, cwd, line, column }) => `${command} ${link ? `--goto ${link}${line ? `:${line}${column ? `:${column}` : ""}` : ""}` : ""} ${cwd}`;
var intellijArgsBuilder = ({ link, command, cwd, line, column }) => {
return `${command} ${cwd} ${column ? `--column ${column}` : ""} ${line ? `--line ${line}` : ""} ${link}`;
};
var editors = {
code: {
commands: ["code", "code-insiders"],
args: codeArgsBuilder
},
cursor: {
commands: ["cursor"],
args: codeArgsBuilder
},
intellij: {
commands: ["idea"],
args: intellijArgsBuilder
},
androidstudio: {
commands: ["android-studio"],
args: intellijArgsBuilder
},
sublime: {
commands: ["subl"],
args: ({ link, command, cwd, line, column }) => link ? `${command} ${link ? `${link}${line ? `:${line}${column ? `:${column}` : ""}` : ""}` : ""}` : `${command} ${cwd}`
}
};
module2.exports.allEditors = Object.keys(editors);
module2.exports.availableEditors = module2.exports.allEditors.filter((key) => editors[key].commands.find((command) => commandExists(command)));
module2.exports.getCommandLine = (editorLabel, { cwd = ".", link = "", line = 0, column = 0 } = {}) => {
const editor = editors[editorLabel];
if (!editor) return null;
const command = editor.commands.find((command2) => commandExists(command2));
if (command) {
return editor.args({ cwd, command, line, column, link });
}
return;
};
}
});
// routes/editors.js
var require_editors2 = __commonJS({
"routes/editors.js"(exports2, module2) {
"use strict";
var { express } = require_src6();
var { availableEditors, getCommandLine } = require_editors();
var { replaceEnvs } = require_stringTransformer_helper();
var Stack = require_stack();
var { exec } = require("child_process");
var { findService } = Stack;
var router = express.Router();
router.get("/available-editors", (req, res) => {
res.send(availableEditors);
});
router.get("/:service/open-in-vs-code", (req, res) => {
const service = findService(req.params.service);
const commandLine = getCommandLine(req.query.editor);
if (!commandLine) return res.status(400).send("Editor not found");
exec(commandLine, { cwd: replaceEnvs(service.rootPath) || replaceEnvs(req.query.path?.toString() || "."), env: process.env });
res.send("ok");
});
router.get("/:service/open-link-in-vs-code", (req, res) => {
const service = findService(req.params.service);
const commandLine = getCommandLine(req.query.editor, { link: req.query.link?.toString() || "" });
if (!commandLine) return res.status(400).send("Editor not found");
const cwd = replaceEnvs(service.rootPath) || replaceEnvs(req.query.path?.toString() || ".");
exec(commandLine, { cwd, env: process.env });
res.send({ commandLine, cwd });
});
module2.exports = router;
}
});
// routes/index.js
var require_routes24 = __commonJS({
"routes/index.js"(exports2, module2) {
"use strict";
var { express } = require_src6();
var router = express.Router();
var plugins = require_plugins2();
plugins.routes.forEach((route) => router.use(route));
router.use("/plugins", require_plugins3());
router.use("/system", require_system());
router.use("/stack", require_stack2());
router.use("/fs", require_fs());
router.use("/crypto", require_crypto2());
router.use("/parsers", require_parsers());
router.use("/editors", require_editors2());
router.get("/version", async (req, res) => {
res.send(require_version().version);
});
module2.exports = router;
}
});
// helpers/cpu.js
var require_cpu = __commonJS({
"helpers/cpu.js"(exports2, module2) {
"use strict";
var os = require("os");
var { fork } = require("child_process");
var path = require("path");
var { sockets } = require_src();
var nbCpus = os.cpus().length;
var controller = null;
var child = null;
controller = new AbortController();
var { signal } = controller;
child = fork(path.resolve(__dirname, "./cpuFork.js"), [], { signal });
child.on("message", (message) => {
const { ram, cpu } = JSON.parse(message.toString());
sockets.emit("infos:global", {
nbCpus,
memPercentage: ram.memPercentage,
totalmem: ram.totalmem,
freemem: ram.freemem,
cpu
});
});
module2.exports = {
stopCpu: async () => {
child?.kill("SIGKILL");
}
};
}
});
// app.js
var require_app = __commonJS({
"app.js"(exports2, module2) {
"use strict";
require("express-async-errors");
var { express } = require_src6();
var indexRouter = require_routes24();
var { stopCpu } = require_cpu();
var { stopWatchers } = require_stack();
var app = express();
app.use("/", indexRouter);
app.stopWorkers = async () => {
await stopCpu();
stopWatchers();
};
module2.exports = app;
}
});
// bin/server.js
var require_server = __commonJS({
"bin/server.js"(exports2, module2) {
"use strict";
var { launch } = require_src6();
var pathfs = require("path");
var ports = require_ports();
var table = require_console_table();
var args2 = require_args();
module2.exports = {
async launch() {
await launch({
port: process.env.STACK_MONITOR_HTTP_PORT || 0,
controllers: () => require_app(),
socket: true,
apiPrefix: "/",
bodyLimit: "100mb",
noGreetings: true,
staticController: process.env.NODE_ENV !== "HFBXdZMJxLyJoua28asEaxRixJ6LriR7FnRzX6pwA7pFjZ" ? pathfs.resolve(__dirname, "public") : void 0,
helmetConf: process.env.NODE_ENV !== "HFBXdZMJxLyJoua28asEaxRixJ6LriR7FnRzX6pwA7pFjZ" ? {
crossOriginEmbedderPolicy: false,
crossOriginResourcePolicy: false,
contentSecurityPolicy: {
directives: {
upgradeInsecureRequests: null,
"frame-src": ["'self'", "clabroche.github.io", "jsoncrack.com"]
},
useDefaults: true
}
} : null,
onListening({ server }) {
const addr = server.address();
const port = typeof addr === "string" ? addr : addr?.port;
ports.setHttpPort(+(port || 0));
if (process.env.NODE_ENV !== "HFBXdZMJxLyJoua28asEaxRixJ6LriR7FnRzX6pwA7pFjZ" && !process.versions.electron) {
require("open")(`http://localhost:${port}`);
}
(() => {
table([
{ "": "Version", Value: require_version().version, "Overrided By": "-" },
{ "": "Port", Value: ports.http, "Overrided By": "STACK_MONITOR_HTTP_PORT" },
{ "": "Url", Value: `http://localhost:${ports.http}`, "Overrided By": "-" },
...args2.ss.length ? [{ "": "Services", Value: args2.ss.join(", "), "Overrided By": "-" }] : []
]);
})();
}
});
await require_stack().selectConf();
}
};
}
});
// bin/www
process.title = "stack-monitor";
var args = require_args();
require("dotenv").config();
(async () => {
if (args["pull-env"]) {
await require_stack().selectConf();
const service = require_stack().findService(args.s);
if (!service) {
console.error("Service", args.s, "not found");
process.exit(1);
}
const envs = await service.buildEnvs(args["e"]);
console.log(Object.keys(envs).reduce((agg, key) => agg += `
${key}=${envs[key]}`, "").trim());
process.exit(0);
} else {
console.log("Root path:", args.rootPath);
require_server().launch().catch((err) => {
console.error(err);
return err;
});
}
})();
//# sourceMappingURL=www.js.map