UNPKG

forest-cli

Version:

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

301 lines 29.6 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const core_1 = require("@oclif/core"); const fs_1 = require("fs"); const path_1 = __importDefault(require("path")); const abstract_authenticated_command_1 = __importDefault(require("../../abstract-authenticated-command")); const errors_1 = require("../../services/layout/errors"); const layout_file_1 = require("../../services/layout/layout-file"); const layout_manager_1 = __importDefault(require("../../services/layout/layout-manager")); const plan_format_1 = require("../../services/layout/plan-format"); const read_1 = require("../../services/layout/read"); const resolve_command_scope_1 = require("../../services/layout/resolve-command-scope"); const sync_1 = require("../../services/layout/sync"); const types_1 = require("../../services/layout/types"); const workflow_sidecar_1 = require("../../services/layout/workflow-sidecar"); /** Upload each changed step-graph's BPMN to S3 and link the returned versions (one patch). */ async function uploadWorkflowBpmns(manager, scope, plans, renderingId) { if (plans.length === 0) return; const bpmnOps = []; // eslint-disable-next-line no-restricted-syntax -- sequential: one S3 upload at a time for (const plan of plans) { // eslint-disable-next-line no-await-in-loop -- intentional sequential uploads const version = await manager.uploadWorkflowBpmn(scope, plan.workflow.id, plan.workflow.collectionId, renderingId, plan.bpmn); bpmnOps.push({ op: 'replace', path: `/workflows/${plan.workflow.id}/bpmnAwsS3Identifier`, value: version, }); } await manager.patchDomain('workflows', bpmnOps, scope); } /** * Read `workflows/<id>.bpmn` sidecars for file workflows that carry no `steps` * (steps take precedence). Only workflows that declare BPMN in the layout * (`bpmnAwsS3Identifier`) are considered: * - declared BPMN + sidecar on disk → an upload plan; * - declared BPMN, no sidecar → `missing`: its BPMN cannot be transported, the * caller warns and never patches the (source) ref; * - sidecar on disk but NO declared BPMN → `orphaned`: a leftover file must not * silently attach BPMN to a workflow that never had one — warn and skip. */ function readWorkflowSidecars(docs, filePath, stepWorkflowsList) { const stepIds = new Set(stepWorkflowsList.map(workflow => workflow.id)); const dir = path_1.default.join(path_1.default.dirname(filePath), 'workflows'); const plans = []; const missing = []; const orphaned = []; (docs.workflows ?? []) .filter(workflow => !stepIds.has(workflow.id)) .forEach(workflow => { // `sidecarPath` returns null for a traversal-unsafe id, so a crafted layout // file can never make apply read a file outside the workflows/ directory. const file = (0, workflow_sidecar_1.sidecarPath)(dir, workflow.id); const hasSidecar = file !== null && (0, fs_1.existsSync)(file); if (!workflow.bpmnAwsS3Identifier) { if (hasSidecar) orphaned.push(workflow.id); } else if (hasSidecar) { plans.push({ bpmn: (0, fs_1.readFileSync)(file, 'utf8'), workflow: { collectionId: workflow.collectionId, id: workflow.id, name: workflow.name ?? workflow.id, }, }); } else { // The workflow had BPMN in the source, but its bytes weren't checked in. missing.push({ id: workflow.id, name: workflow.name ?? workflow.id }); } }); return { missing, orphaned, plans }; } /** * `forest layout apply` — compute the JSON-Patch plan between forest-layout.json * and the live rendering, then PATCH it to the environment. Idempotent: an * unchanged file produces no operation and sends nothing. */ class LayoutApplyCommand extends abstract_authenticated_command_1.default { constructor(argv, config, plan) { super(argv, config, plan); const { assertPresent, env, inquirer } = this.context; assertPresent({ env, inquirer }); this.env = env; this.inquirer = inquirer; } async runAuthenticated() { const { args, flags } = await this.parse(LayoutApplyCommand); const filePath = path_1.default.resolve(process.cwd(), args.file); const { docs, scope: fileScope } = (0, layout_file_1.parseLayoutFile)((0, fs_1.readFileSync)(filePath, 'utf8')); // The file header is provenance only: the target env/team is chosen here // (flags or interactive prompt), never defaulted to where the file was pulled // from — otherwise re-applying onto its own environment would always be a no-op. const scope = await (0, resolve_command_scope_1.resolveCommandScope)({ baseEnv: this.env, flags: { env: flags.env, projectId: flags.projectId, team: flags.team }, }); const manager = new layout_manager_1.default(); // Resolve ids for workflows that carry an authored `steps` graph (mutates docs) // BEFORE diffing, so the shell `add` and the BPMN link target the same id. const bpmnWorkflows = (0, sync_1.stepWorkflows)(docs); // Round-trip: workflows with a `workflows/<id>.bpmn` sidecar and no `steps` are // uploaded verbatim to the target env (faithful transport, incl. UI-authored ones). const { missing: sidecarMissing, orphaned: sidecarOrphaned, plans: sidecarAll, } = flags['with-workflows'] ? readWorkflowSidecars(docs, filePath, bpmnWorkflows) : { missing: [], orphaned: [], plans: [] }; const remote = await (0, read_1.fetchRemoteDocs)(manager, scope, (0, sync_1.domainsInFile)(docs)); const diff = (0, sync_1.diffAllDomains)(remote, docs); const { warnings } = diff; // With --with-workflows the sidecar upload owns each workflow's BPMN pointer, // so the non-portable `bpmnAwsS3Identifier` must never be patched from the file. const ops = flags['with-workflows'] ? (0, workflow_sidecar_1.stripWorkflowBpmnOps)(diff.ops) : diff.ops; this.warnOnCrossEnvBpmnRefs(flags['with-workflows'], ops, fileScope.environmentId, scope); // Compile each step-graph and skip the ones whose BPMN already matches what // is stored — so re-applying a file with `steps` (or a sidecar) stays idempotent. const renderingId = bpmnWorkflows.length > 0 || sidecarAll.length > 0 ? await manager.getRenderingId(scope) : 0; const remoteWorkflows = (remote.workflows ?? []); const bpmnPlans = await (0, sync_1.planWorkflowBpmn)(manager, scope, bpmnWorkflows, remoteWorkflows, renderingId); const bpmnToUpload = bpmnPlans.filter(plan => plan.changed); const sidecarPlans = await (0, workflow_sidecar_1.planSidecarUploads)(manager, scope, sidecarAll, remoteWorkflows, renderingId); // A missing sidecar is only harmless when the target workflow already exists // (it keeps its own BPMN) — a workflow being ADDED by this apply would be // created with no BPMN at all, which deserves its own explicit warning. const sidecarMissingSplit = (0, workflow_sidecar_1.partitionMissingSidecars)(sidecarMissing, remoteWorkflows); this.logPlan({ bpmnToUpload, ops, sidecarMissing: sidecarMissingSplit, sidecarOrphaned, sidecarPlans, warnings, }); if (ops.length === 0 && bpmnToUpload.length === 0 && sidecarPlans.length === 0) { this.logger.success('Nothing to apply: the environment already matches the file.'); return; } if (!flags.force && !(await this.confirm(scope.environmentName, scope.teamName))) { this.logger.info('Aborted: nothing was applied.'); return; } const applied = await this.pushChanges(manager, scope, { bpmnToUpload, ops, renderingId, sidecarPlans, }); if (!applied) return; const bpmnCount = bpmnToUpload.length + sidecarPlans.length; const bpmnNote = bpmnCount > 0 ? ` + ${bpmnCount} workflow BPMN` : ''; this.logger.success(`Applied ${ops.length} change${ops.length === 1 ? '' : 's'}${bpmnNote} to ${this.chalk.bold(scope.environmentName)} / ${this.chalk.bold(scope.teamName)}.`); } /** * Applying a file pulled from ANOTHER environment without `--with-workflows` * would patch its `bpmnAwsS3Identifier` pointers into the target, where the * S3 objects do not exist (the pointer is env-local). The ops are kept — a * same-env restore legitimately rewrites its own pointers, and the header may * be wrong or hand-edited — but the user is warned loudly beforehand. */ warnOnCrossEnvBpmnRefs(withWorkflows, ops, fileEnvironmentId, scope) { if (withWorkflows || !(0, workflow_sidecar_1.hasWorkflowBpmnOps)(ops)) return; if (fileEnvironmentId === undefined || fileEnvironmentId === scope.environmentId) return; this.logger.warn('This file was pulled from a different environment and the plan patches workflow BPMN ' + 'references (`bpmnAwsS3Identifier`), which are NOT portable across environments — the ' + 'target would point at S3 objects that do not exist there. Use `pull --with-workflows` ' + 'then `apply --with-workflows` to transport the BPMN itself.'); } /** * Push the plan: one atomic PATCH per domain (stable order), then the step and * sidecar BPMN uploads. Returns false (and exits 2) on a recoverable API error; * re-running converges since each domain patch is atomic. */ async pushChanges(manager, scope, plan) { try { const domainsToPatch = types_1.LAYOUT_DOMAINS.filter(domain => plan.ops.some(op => op.domain === domain)); // eslint-disable-next-line no-restricted-syntax -- sequential awaits: domains patched one at a time for (const domain of domainsToPatch) { // eslint-disable-next-line no-await-in-loop -- intentional: atomic, ordered per-domain patches await manager.patchDomain(domain, plan.ops.filter(op => op.domain === domain), scope); } // Shells now exist, so upload each changed step-graph, then the sidecars — // each sidecar to its TARGET-env workflow id (resolved after the patches // for workflows this apply just added). await uploadWorkflowBpmns(manager, scope, plan.bpmnToUpload, plan.renderingId); const sidecarUploads = await this.resolveSidecarTargets(manager, scope, plan.sidecarPlans); await uploadWorkflowBpmns(manager, scope, sidecarUploads.map(upload => ({ bpmn: upload.bpmn, workflow: upload.target })), plan.renderingId); return true; } catch (error) { if (error instanceof errors_1.LayoutApiError && error.status !== 401) { this.logger.error((0, plan_format_1.explainApiError)(error, plan.ops)); this.logger.warn('`forest layout apply` is idempotent — some changes may already be applied; re-run it to converge.'); this.exit(2); return false; } throw error; } } /** * Resolve the sidecars whose target workflow did not exist before the domain * patches (they were `add`ed by this very apply): re-fetch the workflows * document once and match id-then-name. Still-unmatched sidecars are skipped * with a warning — never uploaded under a nonexistent id. */ async resolveSidecarTargets(manager, scope, plans) { const unresolved = plans.some(plan => plan.target === null); const refreshed = unresolved ? (await manager.getLayoutDomain('workflows', scope)) : []; return plans.flatMap(plan => { if (plan.target) return [{ ...plan, target: plan.target }]; const remote = (0, workflow_sidecar_1.resolveRemoteWorkflow)(refreshed, plan.workflow); if (!remote) { this.logger.warn(`Workflow « ${plan.workflow.name} » was not found in the target environment after ` + 'the patch — its BPMN sidecar was not uploaded.'); return []; } return [ { ...plan, target: { collectionId: String(remote.collectionId ?? plan.workflow.collectionId), id: String(remote.id), }, }, ]; }); } /** Print the human-readable plan preview (diff + workflow uploads + sidecar warnings). */ logPlan(preview) { const { createdWithoutBpmn, targetKeepsOwn } = preview.sidecarMissing; if (targetKeepsOwn.length > 0) { this.logger.warn(`${targetKeepsOwn.length} workflow(s) have BPMN in the file but no ` + `workflows/<id>.bpmn sidecar — their BPMN was not transported (the target keeps its ` + `own): ${targetKeepsOwn.map(workflow => workflow.name).join(', ')}.`); } if (createdWithoutBpmn.length > 0) { this.logger.warn(`${createdWithoutBpmn.length} workflow(s) will be created WITHOUT BPMN: they declare ` + `BPMN in the layout file but have no workflows/<id>.bpmn sidecar and do not exist in ` + `the target — run \`layout pull --with-workflows\` on the source first: ` + `${createdWithoutBpmn.map(workflow => workflow.name).join(', ')}.`); } if (preview.sidecarOrphaned.length > 0) { this.logger.warn(`${preview.sidecarOrphaned.length} sidecar file(s) match a workflow that declares no ` + `BPMN in the layout file — skipped (not uploaded): ${preview.sidecarOrphaned.join(', ')}.`); } this.log((0, plan_format_1.formatPlan)(preview.ops, preview.warnings)); preview.bpmnToUpload.forEach(plan => this.log(` ⚙ workflow « ${plan.workflow.name} »: compile + upload BPMN (${plan.workflow.steps.length} steps)`)); preview.sidecarPlans.forEach(plan => this.log(` ↑ workflow « ${plan.workflow.name} »: upload BPMN sidecar`)); } async confirm(environmentName, teamName) { const { confirm } = await this.inquirer.prompt([ { message: `Apply these changes to ${environmentName} / ${teamName}?`, name: 'confirm', type: 'confirm', }, ]); return confirm; } } exports.default = LayoutApplyCommand; LayoutApplyCommand.args = { file: core_1.Args.string({ description: 'Layout file to apply (e.g. forest-layout.json).', name: 'file', required: true, }), }; LayoutApplyCommand.description = 'Apply a forest-layout.json to an environment (pushes JSON-Patch changes).'; LayoutApplyCommand.flags = { env: core_1.Flags.string({ char: 'e', description: 'Environment to apply to (name or id). Prompted if omitted.', }), team: core_1.Flags.string({ char: 't', description: 'Team to apply to (name or id). Prompted if omitted.', }), projectId: core_1.Flags.integer({ char: 'p', description: 'Id of the project (resolved/prompted when omitted).', }), force: core_1.Flags.boolean({ char: 'f', description: 'Skip the confirmation prompt.', }), 'with-workflows': core_1.Flags.boolean({ description: 'Also upload workflow BPMN sidecars (workflows/<id>.bpmn) to the target env — the round-trip counterpart of `pull --with-workflows`.', }), }; //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXBwbHkuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvY29tbWFuZHMvbGF5b3V0L2FwcGx5LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7O0FBUUEsc0NBQTBDO0FBQzFDLDJCQUE4QztBQUM5QyxnREFBd0I7QUFFeEIsMEdBQWdGO0FBQ2hGLHlEQUE4RDtBQUM5RCxtRUFBb0U7QUFDcEUsMEZBQWlFO0FBQ2pFLG1FQUFnRjtBQUNoRixxREFBNkQ7QUFDN0QsdUZBQWtGO0FBQ2xGLHFEQUtvQztBQUNwQyx1REFBNkQ7QUFDN0QsNkVBT2dEO0FBRWhELDhGQUE4RjtBQUM5RixLQUFLLFVBQVUsbUJBQW1CLENBQ2hDLE9BQXNCLEVBQ3RCLEtBQWtCLEVBQ2xCLEtBQThFLEVBQzlFLFdBQW1CO0lBRW5CLElBQUksS0FBSyxDQUFDLE1BQU0sS0FBSyxDQUFDO1FBQUUsT0FBTztJQUUvQixNQUFNLE9BQU8sR0FBRyxFQUFFLENBQUM7SUFDbkIsdUZBQXVGO0lBQ3ZGLEtBQUssTUFBTSxJQUFJLElBQUksS0FBSyxFQUFFO1FBQ3hCLDhFQUE4RTtRQUM5RSxNQUFNLE9BQU8sR0FBRyxNQUFNLE9BQU8sQ0FBQyxrQkFBa0IsQ0FDOUMsS0FBSyxFQUNMLElBQUksQ0FBQyxRQUFRLENBQUMsRUFBRSxFQUNoQixJQUFJLENBQUMsUUFBUSxDQUFDLFlBQVksRUFDMUIsV0FBVyxFQUNYLElBQUksQ0FBQyxJQUFJLENBQ1YsQ0FBQztRQUNGLE9BQU8sQ0FBQyxJQUFJLENBQUM7WUFDWCxFQUFFLEVBQUUsU0FBa0I7WUFDdEIsSUFBSSxFQUFFLGNBQWMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxFQUFFLHNCQUFzQjtZQUMxRCxLQUFLLEVBQUUsT0FBTztTQUNmLENBQUMsQ0FBQztLQUNKO0lBRUQsTUFBTSxPQUFPLENBQUMsV0FBVyxDQUFDLFdBQVcsRUFBRSxPQUFPLEVBQUUsS0FBSyxDQUFDLENBQUM7QUFDekQsQ0FBQztBQVNEOzs7Ozs7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