feat(workflows): add revision history, trash, UUID naming, and backup
- 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>
This commit is contained in:
@@ -1,4 +1,10 @@
|
||||
import yaml from "yaml";
|
||||
import {
|
||||
newWorkflowFilename,
|
||||
workflowFileStem,
|
||||
} from "./workflow-normalize.js";
|
||||
|
||||
export { workflowFileStem };
|
||||
|
||||
export function ensureWorkflowFilename(file) {
|
||||
const trimmed = String(file ?? "").trim();
|
||||
@@ -6,13 +12,8 @@ export function ensureWorkflowFilename(file) {
|
||||
return /\.ya?ml$/i.test(trimmed) ? trimmed : `${trimmed}.yaml`;
|
||||
}
|
||||
|
||||
export function workflowFileStem(file) {
|
||||
return String(file).replace(/\.ya?ml$/i, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Next unused copy filename: `track.yaml` → `track-copy.yaml`,
|
||||
* `track-copy.yaml` → `track-copy-2.yaml`.
|
||||
* Legacy human-readable copy name (kept for UI hints).
|
||||
* @param {string} file
|
||||
* @param {string[]} existingFiles
|
||||
*/
|
||||
@@ -33,6 +34,19 @@ export function suggestCopyFilename(file, existingFiles = []) {
|
||||
return candidate(n);
|
||||
}
|
||||
|
||||
/**
|
||||
* UUID-based duplicate filename (default for new duplicates).
|
||||
* @param {string[]} existingFiles
|
||||
*/
|
||||
export function suggestDuplicateFilename(existingFiles = []) {
|
||||
const existing = new Set(existingFiles);
|
||||
let file = newWorkflowFilename();
|
||||
while (existing.has(file)) {
|
||||
file = newWorkflowFilename();
|
||||
}
|
||||
return file;
|
||||
}
|
||||
|
||||
export function nextCopyName(name) {
|
||||
const trimmed = String(name ?? "").trim();
|
||||
if (!trimmed) return "copy";
|
||||
@@ -55,7 +69,10 @@ export function httpPathCopySuffix(sourceFile, destFile) {
|
||||
function suffixHttpPath(path, suffix) {
|
||||
const trimmed = String(path).replace(/\/+$/, "");
|
||||
const withSlash = trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
|
||||
const safe = String(suffix).replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "copy";
|
||||
const safe =
|
||||
String(suffix)
|
||||
.replace(/[^A-Za-z0-9._-]+/g, "-")
|
||||
.replace(/^-+|-+$/g, "") || "copy";
|
||||
return `${withSlash}-${safe}`;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user