- Store up to 50 revisions per workflow in SQLite with normalized SHA dedup - Soft-delete workflows to trash (7-day retention) with restore and permanent purge - Assign UUID filenames for new and duplicated workflows; show name + file in UI - Warn on invalid YAML, unknown scripts, and plaintext secrets with save-anyway option - Add workflow backup zip (workflows + plugins) and merge/replace restore - Trash page, history panel on editor, and smoke test Co-authored-by: Nasyarobby Putra <nasyarobby@gmail.com>
63 lines
1.6 KiB
JavaScript
63 lines
1.6 KiB
JavaScript
import { createHash, randomUUID } from "node:crypto";
|
|
import yaml from "yaml";
|
|
|
|
/**
|
|
* Stable key order for canonical JSON (dedup ignores YAML formatting).
|
|
* @param {unknown} value
|
|
*/
|
|
export function canonicalize(value) {
|
|
if (value == null || typeof value !== "object") return value;
|
|
if (Array.isArray(value)) return value.map(canonicalize);
|
|
const out = {};
|
|
for (const key of Object.keys(value).sort()) {
|
|
out[key] = canonicalize(value[key]);
|
|
}
|
|
return out;
|
|
}
|
|
|
|
/**
|
|
* Parse YAML to a JS object (null when empty/invalid for callers that handle errors).
|
|
* @param {string} content
|
|
*/
|
|
export function parseWorkflowObject(content) {
|
|
if (typeof content !== "string" || !content.trim()) return null;
|
|
return yaml.parse(content) ?? null;
|
|
}
|
|
|
|
/**
|
|
* SHA256 of normalized workflow content (YAML → object → canonical JSON).
|
|
* @param {string} content
|
|
*/
|
|
export function workflowContentSha(content) {
|
|
const parsed = parseWorkflowObject(content);
|
|
const canonical = canonicalize(parsed);
|
|
const json = JSON.stringify(canonical);
|
|
return createHash("sha256").update(json, "utf8").digest("hex");
|
|
}
|
|
|
|
/**
|
|
* @param {string} file
|
|
*/
|
|
export function workflowIdFromFile(file) {
|
|
return String(file).replace(/\.ya?ml$/i, "");
|
|
}
|
|
|
|
/**
|
|
* New on-disk workflow filename: `{uuid}.yaml`.
|
|
* @param {string} [uuid]
|
|
*/
|
|
export function newWorkflowFilename(uuid) {
|
|
const id = uuid ?? randomUUID();
|
|
return `${id}.yaml`;
|
|
}
|
|
|
|
const UUID_FILE_RE =
|
|
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.ya?ml$/i;
|
|
|
|
/**
|
|
* @param {string} file
|
|
*/
|
|
export function isUuidWorkflowFile(file) {
|
|
return UUID_FILE_RE.test(String(file));
|
|
}
|