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
JavaScript
;
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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