Introduce plugin/<id> installs (zip, HTTPS git, example, fork), jerapah-plugin.json manifests with jerapah semver ranges, isolated plugin dirs under data/plugins, and app version 0.1.0. Core scripts are non-editable; get-current-time moves to examples/plugins. Co-authored-by: Nasyarobby Putra <nasyarobby@gmail.com>
80 lines
2.1 KiB
JavaScript
80 lines
2.1 KiB
JavaScript
import fs from "fs";
|
|
import path from "path";
|
|
import { fileURLToPath } from "url";
|
|
import { SERVER_ROOT } from "./paths.js";
|
|
|
|
const ROOT_PKG = path.resolve(SERVER_ROOT, "../../package.json");
|
|
|
|
/**
|
|
* JerapahFlow app version from the monorepo root package.json.
|
|
* @returns {string}
|
|
*/
|
|
export function getAppVersion() {
|
|
try {
|
|
const raw = JSON.parse(fs.readFileSync(ROOT_PKG, "utf8"));
|
|
if (typeof raw.version === "string" && raw.version.trim()) {
|
|
return raw.version.trim();
|
|
}
|
|
} catch {
|
|
// fall through
|
|
}
|
|
return "0.1.0";
|
|
}
|
|
|
|
/**
|
|
* @param {string} version
|
|
* @returns {[number, number, number]}
|
|
*/
|
|
function parseSemver(version) {
|
|
const cleaned = String(version).trim().replace(/^v/i, "");
|
|
const core = cleaned.split("-")[0].split("+")[0];
|
|
const parts = core.split(".").map((p) => Number(p));
|
|
return [parts[0] || 0, parts[1] || 0, parts[2] || 0];
|
|
}
|
|
|
|
/**
|
|
* @param {[number, number, number]} a
|
|
* @param {[number, number, number]} b
|
|
*/
|
|
function cmp(a, b) {
|
|
for (let i = 0; i < 3; i++) {
|
|
if (a[i] !== b[i]) return a[i] < b[i] ? -1 : 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Minimal semver range check for patterns used in manifests:
|
|
* `1.2.3`, `>=0.1.0`, `<1.0.0`, `>=0.1.0 <1.0.0`
|
|
*
|
|
* @param {string} version
|
|
* @param {string} range
|
|
* @returns {boolean}
|
|
*/
|
|
export function satisfiesRange(version, range) {
|
|
if (typeof range !== "string" || !range.trim()) return false;
|
|
const ver = parseSemver(version);
|
|
const tokens = range.trim().split(/\s+/);
|
|
for (const token of tokens) {
|
|
if (/^\d+\.\d+\.\d+/.test(token) && !token.startsWith(">") && !token.startsWith("<")) {
|
|
if (cmp(ver, parseSemver(token)) !== 0) return false;
|
|
continue;
|
|
}
|
|
const m = /^(>=|<=|>|<|=)?\s*v?(\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)$/.exec(
|
|
token,
|
|
);
|
|
if (!m) return false;
|
|
const op = m[1] || "=";
|
|
const bound = parseSemver(m[2]);
|
|
const c = cmp(ver, bound);
|
|
if (op === ">=" && c < 0) return false;
|
|
if (op === "<=" && c > 0) return false;
|
|
if (op === ">" && c <= 0) return false;
|
|
if (op === "<" && c >= 0) return false;
|
|
if (op === "=" && c !== 0) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void fileURLToPath;
|