@h3ravel/shared
Version:
Shared Utilities.
538 lines (528 loc) • 16.7 kB
JavaScript
//#region rolldown:runtime
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
key = keys[i];
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
get: ((k) => from[k]).bind(null, key),
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
});
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
value: mod,
enumerable: true
}) : target, mod));
//#endregion
let fs_promises = require("fs/promises");
let escalade_sync = require("escalade/sync");
escalade_sync = __toESM(escalade_sync);
let path = require("path");
path = __toESM(path);
let chalk = require("chalk");
chalk = __toESM(chalk);
let inquirer_autocomplete_standalone = require("inquirer-autocomplete-standalone");
inquirer_autocomplete_standalone = __toESM(inquirer_autocomplete_standalone);
let __inquirer_prompts = require("@inquirer/prompts");
let crypto = require("crypto");
crypto = __toESM(crypto);
let preferred_pm = require("preferred-pm");
preferred_pm = __toESM(preferred_pm);
//#region src/Utils/EnvParser.ts
var EnvParser = class {
static parse(initial) {
const parsed = { ...initial };
for (const key in parsed) {
const value = parsed[key];
parsed[key] = this.parseValue(value);
}
return parsed;
}
static parseValue(value) {
/**
* Null/undefined stay untouched
*/
if (value === null || value === void 0) return value;
/**
* Convert string "true"/"false" to boolean
*/
if (value === "true") return true;
if (value === "false") return false;
/**
* Convert string numbers to number
*/
if (!isNaN(value) && value.trim() !== "") return Number(value);
/**
* Convert string "null" and "undefined"
*/
if (value === "null") return null;
if (value === "undefined") return void 0;
/**
* Otherwise return as-is (string)
*/
return value;
}
};
//#endregion
//#region src/Utils/FileSystem.ts
var FileSystem = class {
static findModulePkg(moduleId, cwd) {
const parts = moduleId.replace(/\\/g, "/").split("/");
let packageName = "";
if (parts.length > 0 && parts[0][0] === "@") packageName += parts.shift() + "/";
packageName += parts.shift();
const packageJson = path.default.join(cwd ?? process.cwd(), "node_modules", packageName);
const resolved = this.resolveFileUp("package", ["json"], packageJson);
if (!resolved) return;
return path.default.join(path.default.dirname(resolved), parts.join("/"));
}
/**
* Check if file exists
*
* @param path
* @returns
*/
static async fileExists(path$2) {
try {
await (0, fs_promises.access)(path$2);
return true;
} catch {
return false;
}
}
/**
* Recursively find files starting from given cwd
*
* @param name
* @param extensions
* @param cwd
*
* @returns
*/
static resolveFileUp(name, extensions, cwd) {
cwd ??= process.cwd();
return (0, escalade_sync.default)(cwd, (dir, filesNames) => {
if (typeof extensions === "function") return extensions(dir, filesNames);
const candidates = new Set(extensions.map((ext) => `${name}.${ext}`));
for (const filename of filesNames) if (candidates.has(filename)) return filename;
return false;
}) ?? void 0;
}
};
//#endregion
//#region src/Utils/Logger.ts
var Logger = class Logger {
/**
* Global verbosity configuration
*/
static verbosity = 0;
static isQuiet = false;
static isSilent = false;
/**
* Configure global verbosity levels
*/
static configure(options = {}) {
this.verbosity = options.verbosity ?? 0;
this.isQuiet = options.quiet ?? false;
this.isSilent = options.silent ?? false;
}
/**
* Check if output should be suppressed
*/
static shouldSuppressOutput(level) {
if (this.isSilent) return true;
if (this.isQuiet && (level === "info" || level === "success")) return true;
if (level === "debug" && this.verbosity < 3) return true;
return false;
}
static twoColumnDetail(name, value, log = true, spacer = ".") {
const regex = /\x1b\[\d+m/g;
const width = Math.max(process.stdout.columns, 100);
const dots = Math.max(width - name.replace(regex, "").length - value.replace(regex, "").length - 10, 0);
if (log) return console.log(name, chalk.default.gray(spacer.repeat(dots)), value);
else return [
name,
chalk.default.gray(spacer.repeat(dots)),
value
];
}
static describe(name, desc, width = 50, log = true) {
width = Math.max(width, 30);
const dots = Math.max(width - name.replace(/\x1b\[\d+m/g, "").length, 0);
if (log) return console.log(name, " ".repeat(dots), desc);
else return [
name,
" ".repeat(dots),
desc
];
}
/**
* Logs the message in two columns but allways passing status
*
* @param name
* @param value
* @param status
* @param exit
* @param preserveCol
*/
static split(name, value, status, exit = false, preserveCol = false) {
status ??= "info";
const color = {
success: chalk.default.bgGreen,
info: chalk.default.bgBlue,
error: chalk.default.bgRed
};
const [_name, dots, val] = this.twoColumnDetail(name, value, false);
console.log(this.textFormat(_name, color[status], preserveCol), dots, val);
if (exit) process.exit(0);
}
/**
* Wraps text with chalk
*
* @param txt
* @param color
* @param preserveCol
* @returns
*/
static textFormat(txt, color, preserveCol = false) {
const str = String(txt);
if (preserveCol) return str;
const [first, ...rest] = str.split(":");
if (rest.length === 0) return str;
return color(` ${first} `) + rest.join(":");
}
/**
* Logs a success message
*
* @param msg
* @param exit
* @param preserveCol
*/
static success(msg, exit = false, preserveCol = false) {
if (!this.shouldSuppressOutput("success")) console.log(chalk.default.green("✓"), this.textFormat(msg, chalk.default.bgGreen, preserveCol));
if (exit) process.exit(0);
}
/**
* Logs an informational message
*
* @param msg
* @param exit
* @param preserveCol
*/
static info(msg, exit = false, preserveCol = false) {
if (!this.shouldSuppressOutput("info")) console.log(chalk.default.blue("ℹ"), this.textFormat(msg, chalk.default.bgBlue, preserveCol));
if (exit) process.exit(0);
}
/**
* Logs an error message
*
* @param msg
* @param exit
* @param preserveCol
*/
static error(msg, exit = true, preserveCol = false) {
if (!this.shouldSuppressOutput("error")) if (msg instanceof Error) {
if (msg.message) console.error(chalk.default.red("✖"), this.textFormat("ERROR:" + msg.message, chalk.default.bgRed, preserveCol));
console.error(chalk.default.red(`${msg.detail ? `${msg.detail}\n` : ""}${msg.stack}`));
} else console.error(chalk.default.red("✖"), this.textFormat(msg, chalk.default.bgRed, preserveCol));
if (exit) process.exit(1);
}
/**
* Logs a warning message
*
* @param msg
* @param exit
* @param preserveCol
*/
static warn(msg, exit = false, preserveCol = false) {
if (!this.shouldSuppressOutput("warn")) console.warn(chalk.default.yellow("⚠"), this.textFormat(msg, chalk.default.bgYellow, preserveCol));
if (exit) process.exit(0);
}
/**
* Logs a debug message (only shown with verbosity >= 3)
*
* @param msg
* @param exit
* @param preserveCol
*/
static debug(msg, exit = false, preserveCol = false) {
if (!this.shouldSuppressOutput("debug")) if (Array.isArray(msg)) for (let i = 0; i < msg.length; i++) console.log(chalk.default.gray("🐛"), chalk.default.bgGray(i + 1), this.textFormat(msg[i], chalk.default.bgGray, preserveCol));
else console.log(chalk.default.gray("🐛"), this.textFormat(msg, chalk.default.bgGray, preserveCol));
if (exit) process.exit(0);
}
/**
* Terminates the process
*/
static quiet() {
process.exit(0);
}
static chalker(styles) {
return (input$1) => styles.reduce((acc, style) => {
if (style in chalk.default) return (typeof style === "function" ? style : chalk.default[style])(acc);
return acc;
}, input$1);
}
static parse(config, joiner = " ", log = true, sc) {
const string = config.map(([str, opt]) => {
if (Array.isArray(opt)) opt = Logger.chalker(opt);
const output = typeof opt === "string" && typeof chalk.default[opt] === "function" ? chalk.default[opt](str) : typeof opt === "function" ? opt(str) : str;
if (!sc) return output;
return this.textFormat(output, Logger.chalker(Array.isArray(sc) ? sc : [sc]));
}).join(joiner);
if (log && !this.shouldSuppressOutput("line")) console.log(string);
else return string;
}
/**
* Ouput formater object or format the output
*
* @returns
*/
static log = ((config, joiner, log = true, sc) => {
if (typeof config === "string") {
const conf = [[config, joiner]];
return this.parse(conf, "", log, sc);
} else if (config) return this.parse(config, String(joiner), log, sc);
return this;
});
};
//#endregion
//#region src/Utils/PathLoader.ts
var PathLoader = class {
paths = {
base: "",
views: "/src/resources/views",
assets: "/public/assets",
routes: "/src/routes",
config: "/src/config",
public: "/public",
storage: "/storage",
database: "/src/database"
};
/**
* Dynamically retrieves a path property from the class.
* Any property ending with "Path" is accessible automatically.
*
* @param name - The base name of the path property
* @param prefix - The base path to prefix to the path
* @returns
*/
getPath(name, prefix) {
let path$2;
if (prefix && name !== "base") path$2 = path.default.join(prefix, this.paths[name]);
else path$2 = this.paths[name];
if (name === "public") path$2 = path$2.replace("/public", path.default.join("/", process.env.DIST_DIR ?? ".h3ravel/serve"));
else path$2 = path$2.replace("/src/", `/${process.env.DIST_DIR ?? ".h3ravel/serve"}/`.replace(/([^:]\/)\/+/g, "$1"));
return path.default.normalize(path$2);
}
/**
* Programatically set the paths.
*
* @param name - The base name of the path property
* @param path - The new path
* @param base - The base path to include to the path
*/
setPath(name, path$2, base) {
if (base && name !== "base") this.paths[name] = path.default.join(base, path$2);
this.paths[name] = path$2;
}
};
//#endregion
//#region src/Utils/Prompts.ts
var Prompts = class extends Logger {
/**
* Allows users to pick from a predefined set of choices when asked a question.
*/
static async choice(message, choices, defaultIndex) {
return (0, __inquirer_prompts.select)({
message,
choices,
default: defaultIndex ? choices.at(defaultIndex) : void 0
});
}
/**
* Ask the user for a simple "yes or no" confirmation.
* By default, this method returns `false`. However, if the user enters y or yes
* in response to the prompt, the method would return `true`.
*/
static async confirm(message, def) {
return (0, __inquirer_prompts.confirm)({
message,
default: def
});
}
/**
* Prompt the user with the given question, accept their input,
* and then return the user's input back to your command.
*/
static async ask(message, def) {
return (0, __inquirer_prompts.input)({
message,
default: def
});
}
/**
* Prompt the user with the given question, accept their input which
* will not be visible to them as they type in the console,
* and then return the user's input back to your command.
*/
static async secret(message, mask) {
return (0, __inquirer_prompts.password)({
message,
mask
});
}
/**
* Provide auto-completion for possible choices.
* The user can still provide any answer, regardless of the auto-completion hints.
*/
static async anticipate(message, source, def) {
return (0, inquirer_autocomplete_standalone.default)({
message,
source: Array.isArray(source) ? async (term) => {
return (term ? source.filter((e) => e.includes(term)) : source).map((e) => ({ value: e }));
} : source,
suggestOnly: true,
default: def
});
}
};
//#endregion
//#region src/Utils/Resolver.ts
var Resolver = class {
static async getPakageInstallCommand(pkg) {
const pm = (await (0, preferred_pm.default)(process.cwd()))?.name ?? "pnpm";
let cmd = "install ";
if (!pkg) if (pm === "npm" || pm === "pnpm" || pm === "bun") cmd = "install";
else cmd = "";
else if (pm === "yarn" || pm === "pnpm" || pm === "bun") cmd = "add ";
return `${pm} ${cmd}${pkg ?? ""}`;
}
static async getInstallCommand(pkg) {
const pm = (await (0, preferred_pm.default)(process.cwd()))?.name ?? "pnpm";
let cmd = "install";
if (pm === "yarn" || pm === "pnpm") cmd = "add";
else if (pm === "bun") cmd = "create";
return `${pm} ${cmd} ${pkg}`;
}
/**
* Create a hash for a function or an object
*
* @param provider
* @returns
*/
static hashObjectOrFunction(provider) {
return crypto.default.createHash("sha1").update(provider.toString()).digest("hex");
}
/**
* Checks if a function is asyncronous
*
* @param func
* @returns
*/
static isAsyncFunction(func) {
if (typeof func !== "function") return false;
return Object.prototype.toString.call(func) === "[object AsyncFunction]";
}
};
//#endregion
//#region src/Utils/scripts.ts
const mainTsconfig = {
extends: "@h3ravel/shared/tsconfig.json",
compilerOptions: {
baseUrl: ".",
outDir: "dist",
paths: {
"src/*": ["./../src/*"],
"App/*": ["./../src/app/*"],
"root/*": ["./../*"],
"routes/*": ["./../src/routes/*"],
"config/*": ["./../src/config/*"],
"resources/*": ["./../src/resources/*"]
},
target: "es2022",
module: "es2022",
moduleResolution: "Node",
esModuleInterop: true,
strict: true,
allowJs: true,
skipLibCheck: true,
resolveJsonModule: true,
noEmit: true,
experimentalDecorators: true,
emitDecoratorMetadata: true
},
include: ["./**/*.d.ts", "./../**/*"],
exclude: [
".",
"./../**/console/bin",
"./../dist",
"./../**/dist",
"./../**/node_modules",
"./../.node_modules",
"./../**/node_modules/*",
"./../**/public",
"./../public",
"./../**/storage",
"./../storage",
"./../**coverage**",
"./../eslint.config.js",
"./../jest.config.ts",
"./../arquebus.config.js"
]
};
const baseTsconfig = { extends: "./.h3ravel/tsconfig.json" };
const packageJsonScript = {
build: "NODE_ENV=production tsdown --config-loader unconfig -c tsdown.default.config.ts",
dev: "NODE_ENV=development pnpm tsdown --config-loader unconfig -c tsdown.default.config.ts",
start: "DIST_DIR=dist node -r source-map-support/register dist/server.js",
lint: "eslint . --ext .ts",
test: "NODE_NO_WARNINGS=1 NODE_ENV=testing jest --passWithNoTests",
postinstall: "pnpm prepare"
};
//#endregion
//#region src/Utils/TaskManager.ts
var TaskManager = class {
static async taskRunner(description, task) {
const startTime = process.hrtime();
let result = false;
try {
result = await Promise.all([(task || (() => true))()].flat());
} finally {
const endTime = process.hrtime(startTime);
const duration = (endTime[0] * 1e9 + endTime[1]) / 1e6;
Logger.twoColumnDetail(Logger.parse([[description, "green"]], "", false), [Logger.parse([[`${Math.floor(duration)}ms`, "gray"]], "", false), Logger.parse([[result !== false ? "✔" : "✘", result !== false ? "green" : "red"]], "", false)].join(" "));
}
}
static async advancedTaskRunner(info, task) {
const startTime = process.hrtime();
const [startInfo, stopInfo] = info;
if (stopInfo) Logger.twoColumnDetail(startInfo[0], Logger.log(startInfo[1], ["yellow", "bold"], false));
try {
return await Promise.race([task()]);
} catch (e) {
Logger.error("ERROR: " + e.message);
} finally {
const endTime = process.hrtime(startTime);
const duration = (endTime[0] * 1e9 + endTime[1]) / 1e6;
Logger.twoColumnDetail(stopInfo?.[0] ?? startInfo[0], [Logger.parse([[`${Math.floor(duration)}ms`, "gray"]], "", false), Logger.parse([[`✔ ${stopInfo?.[1] ?? startInfo[1]}`, ["green", "bold"]]], "", false)].join(" "));
}
}
};
//#endregion
exports.EnvParser = EnvParser;
exports.FileSystem = FileSystem;
exports.Logger = Logger;
exports.PathLoader = PathLoader;
exports.Prompts = Prompts;
exports.Resolver = Resolver;
exports.TaskManager = TaskManager;
exports.baseTsconfig = baseTsconfig;
exports.mainTsconfig = mainTsconfig;
exports.packageJsonScript = packageJsonScript;
//# sourceMappingURL=index.cjs.map