78 lines
2.0 KiB
JavaScript
78 lines
2.0 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;
|
|
}
|
|
|
|
/**
|
|
* Drop `enabled` before hashing so enable/disable does not create revision points.
|
|
* @param {unknown} parsed
|
|
*/
|
|
function stripEnabledForHash(parsed) {
|
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return parsed;
|
|
const { enabled, ...rest } = parsed;
|
|
return rest;
|
|
}
|
|
|
|
/**
|
|
* SHA256 of normalized workflow content (YAML → object → canonical JSON).
|
|
* @param {string} content
|
|
*/
|
|
export function workflowContentSha(content) {
|
|
const parsed = stripEnabledForHash(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, "");
|
|
}
|
|
|
|
/** @param {string} file */
|
|
export function workflowFileStem(file) {
|
|
return workflowIdFromFile(file);
|
|
}
|
|
|
|
/**
|
|
* 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));
|
|
}
|