UNPKG

@runeya/runeya

Version:

Monitor processes as a stack

1,289 lines (1,261 loc) 200 kB
#!/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); // ../../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() }; } }); // 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 { existsSync } = require("fs"); 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 rootPath = path.resolve(yarg["_"]?.[0] || "."); var args2 = Object.assign({ rootPath, initialCwd: "", runeyaConfigPath: path.resolve(rootPath, ".runeya"), runeyaGlobalConfigPath: path.resolve(require("os").homedir(), ".runeya-global") }, yarg); if (!existsSync(args2.rootPath)) { console.error("Error: Runeya was launched with an invalid root path that does not exist. Please check your launch command. \nPath: ", args2.rootPath); process.exit(1); } 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; } }); // helpers/migrateStackMonitor.js var require_migrateStackMonitor = __commonJS({ "helpers/migrateStackMonitor.js"(exports2, module2) { "use strict"; var pathfs = require("path"); var { existsSync } = require("fs"); var { cp } = require("fs/promises"); var args2 = require_args(); module2.exports = async function migrateStackMonitor2() { const localLegacyPath = pathfs.resolve(args2.runeyaConfigPath, "../.stackmonitor"); const localNewPath = args2.runeyaConfigPath; if (existsSync(localLegacyPath) && !existsSync(localNewPath)) { console.log("Legacy path found, copy to new path"); await cp(localLegacyPath, localNewPath, { recursive: true, force: true }); } const globalLegacyPath = pathfs.resolve(args2.runeyaGlobalConfigPath, "../.stackmonitor"); const globalNewPath = args2.runeyaGlobalConfigPath; if (existsSync(globalLegacyPath) && !existsSync(globalNewPath)) { console.log("Legacy path found, copy to new path"); await cp(globalLegacyPath, globalNewPath, { recursive: true, force: true }); } if (existsSync(localLegacyPath) && !existsSync(pathfs.resolve(args2.runeyaConfigPath, "dbs/overrides"))) { console.log("Legacy path found, copy to new path"); await cp(pathfs.resolve(localLegacyPath, "dbs/overrides"), pathfs.resolve(args2.runeyaConfigPath, "dbs/overrides"), { recursive: true, force: true }); } if (existsSync(localLegacyPath) && !existsSync(pathfs.resolve(args2.runeyaConfigPath, "dbs/encryption-key.json"))) { console.log("Legacy path found, copy to new path"); await cp(pathfs.resolve(localLegacyPath, "dbs/encryption-key.json"), pathfs.resolve(args2.runeyaConfigPath, "dbs/encryption-key.json"), { recursive: true, force: true }); } }; } }); // 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 crypto2 = require("crypto"); var { readFile, writeFile } = require("fs/promises"); var encryptionAlgorithm = "aes-256-ctr"; module2.exports = async function decryptCreds(oldSecret, newScret, path) { const oldKey = crypto2.createHash("sha256").update(oldSecret).digest(); const newKey = crypto2.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 = crypto2.createDecipheriv(encryptionAlgorithm, oldKey, vector); const decrypted = decipher.update(flows, "base64", "utf8") + decipher.final("utf8"); const newVector = crypto2.randomBytes(16); const newCipher = crypto2.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"); }; } }); // ../../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 = (runeya) => { router.get("/bugs/:service", async (req, res) => { const service = runeya.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/configuration/backend/index.js var require_backend2 = __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/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('@runeya/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('@runeya/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_routes2 = __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 = (runeya) => { const { findService } = runeya; 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_backend3 = __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_routes2(), finder: async (search, runeya) => { } }; module2.exports = plugin; } }); // ../../modules/finder/backend/routes.js var require_routes3 = __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 = (runeya) => { const { getServices, helpers: { searchString } } = runeya; 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: plugins2 } = runeya; const _plugins = (await PromiseB2.map(Object.keys(plugins2), (key) => plugins2[key]).map(async (plugin) => [ ...await plugin?.finder?.(search, runeya)?.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_backend4 = __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_routes3() }; 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 | string} 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 = (runeya) => { const { findService } = runeya; 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_routes4 = __commonJS({ "../../modules/git/backend/routes.js"(exports2, module2) { "use strict"; var express = require("express"); var router = express.Router(); module2.exports = (runeya) => { const { git } = runeya; 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_backend5 = __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_routes4() }; module2.exports = plugin; } }); // ../../modules/github/backend/routes.js var require_routes5 = __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.RUNEYA_GH_APIKEY ? new MyOctokit({ auth: process.env.RUNEYA_GH_APIKEY }) : null; module2.exports = (runeya) => { const { findService } = runeya; 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.RUNEYA_GH_APIKEY) { throw new Error("github api is not initialized"); } else if (!octokit) { octokit = new MyOctokit({ auth: process.env.RUNEYA_GH_APIKEY }); } } } }); // ../../modules/github/backend/index.js var require_backend6 = __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_routes5() }; module2.exports = plugin; } }); // ../../modules/logs/backend/routes.js var require_routes6 = __commonJS({ "../../modules/logs/backend/routes.js"(exports2, module2) { "use strict"; var express = require("express"); var { v4 } = require("uuid"); var router = express.Router(); module2.exports = (runeya) => { const history = runeya.getSave("history.json", { /** @type {History[]} */ history: [] }); const { findService, Socket } = runeya; 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_backend7 = __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('@runeya/common-typings').Runeya} runeya */ finder: (search, runeya) => { const services = runeya.getServices()?.flatMap((s) => s.store?.slice(-300).reverse().map((line) => ({ log: line, service: s })))?.filter(({ log }) => runeya.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_routes6() }; 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;", "agrave"], ["\xC0", "&Agrave;", "agrave"], ["\xE1", "&aacute;", "aacute"], ["\xC1", "&Aacute;", "aacute"], ["\xE2", "&acirc;", "acirc"], ["\xC2", "&Acirc;", "acirc"], ["\xE3", "&atilde;", "atilde"], ["\xC3", "&Atilde;", "atilde"], ["\xE4", "&auml;", "auml"], ["\xC4", "&Auml;", "auml"], ["\xE5", "&aring;", "aring"], ["\xC5", "&Aring;", "aring"], ["\xE6", "&aelig;", "aelig"], ["\xC6", "&AElig;", "aelig"], ["\xE8", "&egrave;", "egrave"], ["\xC8", "&Egrave;", "egrave"], ["\xE9", "&eacute;", "eacute"], ["\xC9", "&Eacute;", "eacute"], ["\xEA", "&ecirc;", "ecirc"], ["\xCA", "&Ecirc;", "ecirc"], ["\xEB", "&euml;", "euml"], ["\xCB", "&Euml;", "euml"], ["\xEC", "&igrave;", "igrave"], ["\xCC", "&Igrave;", "igrave"], ["\xED", "&iacute;", "iacute"], ["\xCD", "&Iacute;", "iacute"], ["\xEE", "&icirc;", "icirc"], ["\xCE", "&Icirc;", "icirc"], ["\xEF", "&iuml;", "iuml"], ["\xCF", "&Iuml;", "iuml"], ["\xF2", "&ograve;", "ograve"], ["\xD2", "&Ograve;", "ograve"], ["\xF3", "&oacute;", "oacute"], ["\xD3", "&Oacute;", "oacute"], ["\xF4", "&ocirc;", "ocirc"], ["\xD4", "&Ocirc;", "ocirc"], ["\xF5", "&otilde;", "otilde"], ["\xD5", "&Otilde;", "otilde"], ["\xF6", "&ouml;", "ouml"], ["\xD6", "&Ouml;", "ouml"], ["\xF8", "&oslash;", "oslash"], ["\xD8", "&Oslash;", "oslash"], ["\xF9", "&ugrave;", "ugrave"], ["\xD9", "&Ugrave;", "ugrave"], ["\xFA", "&uacute;", "uacute"], ["\xDA", "&Uacute;", "uacute"], ["\xFB", "&ucirc;", "ucirc"], ["\xDB", "&Ucirc;", "ucirc"], ["\xFC", "&uuml;", "uuml"], ["\xDC", "&Uuml;", "uuml"], ["\xF1", "&ntilde;", "ntilde"], ["\xD1", "&Ntilde;", "ntilde"], ["\xE7", "&ccedil;", "ccedil"], ["\xC7", "&Ccedil;", "ccedil"], ["\xFD", "&yacute;", "yacute"], ["\xDD", "&Yacute;", "yacute"], ["\xDF", "&szlig;", "szlig"], ["\xAB", "&laquo;"], ["\xBB", "&raquo;"], ["&", "&amp;"], ["<", "&lt;"], [">", "&gt;"], ['"', "&quot;"], ["\xA7", "&para;"], ["\xA9", "&copy;"] ]; } }); // 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.