UNPKG

forest-cli

Version:

The Lumberjacks' toolbelt is the Forest CLI which makes easy to manage your back office application directly from the terminal.

160 lines 12.7 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.selectStaleSidecars = exports.planSidecarUploads = exports.hasWorkflowBpmnOps = exports.stripWorkflowBpmnOps = exports.partitionMissingSidecars = exports.resolveRemoteWorkflow = exports.sidecarPath = exports.isSafeWorkflowId = void 0; const path_1 = __importDefault(require("path")); const diff_1 = require("./diff"); const errors_1 = require("./errors"); const patch_rules_1 = require("./patch-rules"); /** * A workflow id becomes a sidecar filename (`workflows/<id>.bpmn`). Because that * id comes from a layout file, a crafted file could set it to `../../.ssh/id_rsa` * and make `apply --with-workflows` read an arbitrary file (or `pull` write * outside the workflows dir). Only accept ids that are a single, traversal-free * path segment — Forest ids are UUIDs, so this is deliberately strict. */ function isSafeWorkflowId(id) { return (typeof id === 'string' && id.length > 0 && !id.includes('/') && !id.includes('\\') && !id.includes('..') && path_1.default.basename(id) === id); } exports.isSafeWorkflowId = isSafeWorkflowId; /** The sidecar path for a workflow id, or null when the id is unsafe. */ function sidecarPath(dir, id) { return isSafeWorkflowId(id) ? path_1.default.join(dir, `${id}.bpmn`) : null; } exports.sidecarPath = sidecarPath; /** * Resolve a file workflow to its target-env counterpart by DELEGATING to the * diff engine's own resolver (`remoteResolver` in diff.ts), so the sidecar * upload targets the very workflow the domain patch addressed — including on * duplicate names, where the resolver's Map makes the LAST remote win. A * dev→prod promote typically matches by NAME (each env mints its own ids). */ function resolveRemoteWorkflow(remoteWorkflows, workflow) { return (0, diff_1.remoteResolver)(remoteWorkflows)(workflow); } exports.resolveRemoteWorkflow = resolveRemoteWorkflow; /** * Split the missing-sidecar workflows by what actually happens to their BPMN: * - `targetKeepsOwn`: the workflow exists in the target env (id-then-name), so * the strip of `bpmnAwsS3Identifier` leaves the target's own BPMN untouched; * - `createdWithoutBpmn`: the workflow matches nothing in the target — this * apply `add`s it, and with its declared pointer stripped and no sidecar to * upload, it is created with NO BPMN at all. The caller must warn explicitly: * "keeps its own" would be a lie here. */ function partitionMissingSidecars(missing, remoteWorkflows) { const targetKeepsOwn = []; const createdWithoutBpmn = []; missing.forEach(workflow => { if (resolveRemoteWorkflow(remoteWorkflows, workflow)) targetKeepsOwn.push(workflow); else createdWithoutBpmn.push(workflow); }); return { createdWithoutBpmn, targetKeepsOwn }; } exports.partitionMissingSidecars = partitionMissingSidecars; /** * When `--with-workflows`, BPMN transport is owned by the sidecar upload, not by * the JSON patch: `bpmnAwsS3Identifier` is a per-environment S3 pointer and is not * portable. Drop it from the diff so a pulled file never patches a target workflow * to the source env's (nonexistent) version — for workflows with a sidecar the * upload links the fresh id; for those without, the target keeps its own BPMN. */ function stripWorkflowBpmnOps(ops) { return ops .filter(op => !/\/bpmnAwsS3Identifier$/.test(op.path)) .map(op => { if (op.op !== 'add' || !op.value || typeof op.value !== 'object') return op; const value = { ...op.value }; delete value.bpmnAwsS3Identifier; return { ...op, value }; }); } exports.stripWorkflowBpmnOps = stripWorkflowBpmnOps; /** * True when the plan would write a `bpmnAwsS3Identifier` into the target — either * as a replace of the pointer itself or inside an added workflow. Used by `apply` * WITHOUT `--with-workflows` to warn on cross-environment applies: the pointer is * env-local, so patching it into another env plants a dead S3 reference. */ function hasWorkflowBpmnOps(ops) { return ops.some(op => { if (/\/bpmnAwsS3Identifier$/.test(op.path)) return true; if (op.op !== 'add' || !op.value || typeof op.value !== 'object') return false; const pointer = op.value.bpmnAwsS3Identifier; return pointer !== undefined && pointer !== null; }); } exports.hasWorkflowBpmnOps = hasWorkflowBpmnOps; /** * Decide which sidecars need uploading, resolving each to its TARGET-env * workflow first (id-then-name, like the diff) so that: * - a same-name workflow with a different id (dev→prod promote) is uploaded * under the TARGET id, never the source one; * - byte-identical BPMN is skipped (idempotent re-apply), including in that * name-matched case. */ async function planSidecarUploads(manager, scope, sidecars, remoteWorkflows, renderingId) { const decided = await Promise.all(sidecars.map(async (sidecar) => { const remote = resolveRemoteWorkflow(remoteWorkflows, sidecar.workflow); if (!remote) return { changed: true, upload: { ...sidecar, target: null } }; const target = { collectionId: String(remote.collectionId ?? sidecar.workflow.collectionId), id: String(remote.id), }; const upload = { ...sidecar, target }; const version = remote.bpmnAwsS3Identifier; if (typeof version !== 'string' || !version) return { changed: true, upload }; try { const stored = await manager.getWorkflowBpmn(scope, target.id, target.collectionId, version, renderingId); return { changed: stored !== sidecar.bpmn, upload }; } catch (error) { // Auth/permission failures must surface, not be reframed as "changed". if (error instanceof errors_1.LayoutApiError && (error.status === 401 || error.status === 403)) { throw error; } return { changed: true, upload }; } })); return decided.filter(entry => entry.changed).map(entry => entry.upload); } exports.planSidecarUploads = planSidecarUploads; /** * A managed sidecar filename: `<id>.bpmn` where the id has the shape of a REAL * server workflow id (numeric or UUID — the same `NUM_OR_UUID` class the patch * whitelist uses). Managed sidecars are the only files pull ever writes, so * they are the only files it may ever delete. */ const MANAGED_SIDECAR = new RegExp(`^${patch_rules_1.NUM_OR_UUID}\\.bpmn$`); /** * The sidecar filenames a pull should prune: files named after a server * workflow id (`<num-or-uuid>.bpmn`) that matches no workflow left in the * environment. Everything else is preserved: * - files whose basename is not a server-shaped id (a user's own BPMN files, * e.g. `review.bpmn` — pull never wrote them, so pull never deletes them); * - sidecars of workflows still present in the env, even when their download * was skipped (dead S3 ref) — that sidecar may be the last copy of the BPMN, * and a backup tool must never destroy the backup. */ function selectStaleSidecars(entries, keepIds) { return entries.filter(entry => { const match = MANAGED_SIDECAR.exec(entry); return match !== null && !keepIds.has(match[1]); }); } exports.selectStaleSidecars = selectStaleSidecars; //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoid29ya2Zsb3ctc2lkZWNhci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9zZXJ2aWNlcy9sYXlvdXQvd29ya2Zsb3ctc2lkZWNhci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7QUFhQSxnREFBd0I7QUFFeEIsaUNBQXdDO0FBQ3hDLHFDQUEwQztBQUMxQywrQ0FBNEM7QUFFNUM7Ozs7OztHQU1HO0FBQ0gsU0FBZ0IsZ0JBQWdCLENBQUMsRUFBVztJQUMxQyxPQUFPLENBQ0wsT0FBTyxFQUFFLEtBQUssUUFBUTtRQUN0QixFQUFFLENBQUMsTUFBTSxHQUFHLENBQUM7UUFDYixDQUFDLEVBQUUsQ0FBQyxRQUFRLENBQUMsR0FBRyxDQUFDO1FBQ2pCLENBQUMsRUFBRSxDQUFDLFFBQVEsQ0FBQyxJQUFJLENBQUM7UUFDbEIsQ0FBQyxFQUFFLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQztRQUNsQixjQUFJLENBQUMsUUFBUSxDQUFDLEVBQUUsQ0FBQyxLQUFLLEVBQUUsQ0FDekIsQ0FBQztBQUNKLENBQUM7QUFURCw0Q0FTQztBQUVELHlFQUF5RTtBQUN6RSxTQUFnQixXQUFXLENBQUMsR0FBVyxFQUFFLEVBQVc7SUFDbEQsT0FBTyxnQkFBZ0IsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUMsY0FBSSxDQUFDLElBQUksQ0FBQyxHQUFHLEVBQUUsR0FBRyxFQUFFLE9BQU8sQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUM7QUFDcEUsQ0FBQztBQUZELGtDQUVDO0FBRUQ7Ozs7OztHQU1HO0FBQ0gsU0FBZ0IscUJBQXFCLENBQ25DLGVBQStDLEVBQy9DLFFBQXVDO0lBRXZDLE9BQU8sSUFBQSxxQkFBYyxFQUFDLGVBQWUsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxDQUFDO0FBQ25ELENBQUM7QUFMRCxzREFLQztBQUtEOzs7Ozs7OztHQVFHO0FBQ0gsU0FBZ0Isd0JBQXdCLENBQ3RDLE9BQXlCLEVBQ3pCLGVBQStD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