forest-cli
Version:
The Lumberjacks' toolbelt is the Forest CLI which makes easy to manage your back office application directly from the terminal.
311 lines • 28 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 crypto_1 = require("crypto");
const promises_1 = require("fs/promises");
const abstract_authenticated_command_1 = __importDefault(require("../../abstract-authenticated-command"));
const errors_1 = require("../../services/layout/errors");
const layout_manager_1 = __importDefault(require("../../services/layout/layout-manager"));
const plan_format_1 = require("../../services/layout/plan-format");
const resolve_command_scope_1 = require("../../services/layout/resolve-command-scope");
const sync_1 = require("../../services/layout/sync");
const workflow_apply_1 = require("../../services/layout/workflow-apply");
const workflow_bpmn_1 = require("../../services/layout/workflow-bpmn");
function readStdin() {
return new Promise((resolve, reject) => {
let data = '';
process.stdin.setEncoding('utf8');
process.stdin.on('data', chunk => {
data += chunk;
});
process.stdin.on('end', () => resolve(data));
process.stdin.on('error', reject);
});
}
/** Read the spec: from the file when given, else from the (non-TTY) stdin pipe. */
async function readInput(filePath) {
if (!filePath)
return readStdin();
try {
return await (0, promises_1.readFile)(filePath, 'utf8');
}
catch (error) {
const reason = error.code === 'ENOENT'
? 'no such file'
: error.message;
throw new workflow_bpmn_1.WorkflowSpecError(`Cannot read spec file "${filePath}": ${reason}.`);
}
}
/** Add a new (BPMN-less) workflow shell to the layout. */
function addWorkflowShell(manager, scope, shell) {
return manager.patchDomain('workflows', [
{
op: 'add',
path: '/workflows/-',
value: {
collectionId: shell.collection,
id: shell.id,
isVisible: true,
name: shell.name,
position: shell.position,
segmentIds: shell.segments,
},
},
], scope);
}
/**
* `forest workflow apply` — create-or-update ONE workflow from a JSON `steps`
* spec, without touching the rest of the layout. The spec is the source of
* truth: editing a workflow (e.g. adding a step) means editing the JSON and
* re-applying — the whole BPMN is recompiled and re-uploaded when it differs
* (it is atomic; there is no per-step patch), and the shell metadata (name,
* segments, position when pinned) is converged with `replace` patches. Upsert
* is matched by explicit `id` (which must exist — a stale id is an error, not
* a create), else by name + collection.
*
* Sequence: compile steps -> BPMN; show the plan and confirm; PATCH the shell
* (add if new, metadata replaces otherwise); upload the BPMN to the env's S3
* (presigned) when it changed; PATCH the returned `bpmnAwsS3Identifier`.
*/
class WorkflowApplyCommand 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(WorkflowApplyCommand);
const fromStdin = !args.file || args.file === '-';
if (!this.checkStdinUsage(fromStdin, flags)) {
this.exit(2);
return;
}
let spec;
let bpmn;
try {
spec = (0, workflow_apply_1.parseWorkflowSpec)(await readInput(fromStdin ? undefined : args.file));
bpmn = (0, workflow_bpmn_1.compileWorkflowToBpmn)(spec);
}
catch (error) {
if (this.reportSpecError(error))
return;
throw error;
}
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();
try {
await this.planAndExecute(manager, scope, spec, bpmn, flags, fromStdin);
}
catch (error) {
// Planning-time spec/environment mismatches (e.g. an `id` matching no
// remote workflow) get the same clean-error treatment as parse errors.
if (this.reportSpecError(error))
return;
if (error instanceof errors_1.LayoutApiError && error.status !== 401) {
this.logger.error((0, plan_format_1.explainApiError)(error, []));
this.exit(2);
}
throw error;
}
}
async planAndExecute(manager, scope, spec, bpmn, flags, fromStdin) {
const plan = await this.planApply(manager, scope, spec, bpmn);
if (plan.match && !plan.bpmnChanged && plan.shellChanges.length === 0) {
this.logger.success('Nothing to apply: the workflow already matches the spec.');
return;
}
this.printPlan(plan, spec, scope, flags['dry-run']);
if (flags['dry-run']) {
this.log('\n(dry-run: nothing sent)');
return;
}
if (!flags.force && !(await this.confirmOrBail(fromStdin, scope)))
return;
await this.executePlan(manager, scope, spec, plan);
this.printOutcome(plan, spec, scope);
}
reportSpecError(error) {
if (error instanceof workflow_bpmn_1.WorkflowSpecError) {
this.logger.error(error.message);
this.exit(2);
return true;
}
return false;
}
/** M2 guards: no silent hang on a TTY, no interactive prompt when stdin carries the spec. */
checkStdinUsage(fromStdin, flags) {
if (!fromStdin)
return true;
if (process.stdin.isTTY) {
this.logger.error('Missing spec: pass a file path, or pipe JSON via stdin (e.g. `forest workflow apply spec.json`).');
return false;
}
if (flags.projectId === undefined || !flags.env || !flags.team) {
this.logger.error('Reading the spec from stdin disables interactive prompts: pass -p, -e and -t explicitly.');
return false;
}
return true;
}
/** Resolve everything the apply needs (match, metadata diff, BPMN diff) — read-only. */
async planApply(manager, scope, spec, bpmn) {
const existing = (await manager.getLayoutDomain('workflows', scope));
const matches = (0, workflow_apply_1.findWorkflowMatches)(existing, spec);
// An explicit `id` means "update that workflow" — silently creating a new
// one (reusing a stale/mistyped id) would bypass the name+collection upsert.
if (spec.id !== undefined && matches.length === 0) {
throw new workflow_bpmn_1.WorkflowSpecError(`No workflow with id "${spec.id}" found in this environment. ` +
'Remove `id` from the spec to create a new workflow, or fix it to update an existing one.');
}
if (matches.length > 1) {
this.logger.warn(`${matches.length} workflows named "${spec.name}" exist on collection ${spec.collection} — updating the first (id ${matches[0].id}).`);
}
const match = matches[0];
if (match && match.collectionId !== spec.collection) {
throw new Error(`Workflow "${match.id}" is on collection "${match.collectionId}", not "${spec.collection}". ` +
'A workflow cannot change collection — remove the `id`/rename to create a new one.');
}
const id = match?.id ?? (0, crypto_1.randomUUID)();
const renderingId = await manager.getRenderingId(scope);
// Reuses the layout-apply idempotency brick: byte-compare the compiled BPMN
// against the stored version, so an unchanged spec uploads nothing (and a
// re-apply is a true no-op).
const [bpmnPlan] = match
? await (0, sync_1.planWorkflowBpmn)(manager, scope, [
{
collectionId: spec.collection,
id,
name: spec.name,
segmentIds: spec.segments,
start: spec.start,
steps: spec.steps,
},
], existing, renderingId)
: [{ bpmn, changed: true }];
return {
bpmn: bpmnPlan.bpmn,
bpmnChanged: bpmnPlan.changed,
existingCount: existing.length,
id,
match,
renderingId,
shellChanges: match ? (0, workflow_apply_1.planShellUpdate)(match, spec) : [],
};
}
/** Say what is about to happen (or would, on --dry-run) before doing anything. */
printPlan(plan, spec, scope, dryRun) {
const verb = dryRun ? 'Would' : 'Will';
const target = `${scope.environmentName} / ${scope.teamName}`;
const stepCount = (spec.steps ?? []).length;
if (!plan.match) {
this.log(`${verb} CREATE workflow "${spec.name}" on collection ${spec.collection} (${stepCount} steps) on ${target}.`);
return;
}
this.log(`${verb} UPDATE workflow "${plan.match.name}" (id ${plan.match.id}) on ${target}:`);
plan.shellChanges.forEach(change => this.log(` ~ ${change.label}`));
this.log(plan.bpmnChanged
? ` ~ BPMN: recompiled and replaced (${stepCount} steps) — the current BPMN and its layout edits will be overwritten`
: ' = BPMN unchanged, upload will be skipped');
}
/** Interactive confirmation — impossible when stdin carries the spec (require --force). */
async confirmOrBail(fromStdin, scope) {
if (fromStdin) {
this.logger.error('Cannot ask for confirmation when the spec is read from stdin: pass --force to apply.');
this.exit(2);
return false;
}
const { confirm } = await this.inquirer.prompt([
{
message: `Apply this workflow to ${scope.environmentName} / ${scope.teamName}?`,
name: 'confirm',
type: 'confirm',
},
]);
if (!confirm)
this.logger.info('Aborted: nothing was applied.');
return confirm;
}
async executePlan(manager, scope, spec, plan) {
if (!plan.match) {
await addWorkflowShell(manager, scope, {
collection: spec.collection,
id: plan.id,
name: spec.name,
position: spec.position ?? plan.existingCount,
segments: spec.segments ?? [],
});
}
else if (plan.shellChanges.length > 0) {
await manager.patchDomain('workflows', plan.shellChanges.map(change => change.op), scope);
}
if (plan.bpmnChanged)
await this.uploadAndLinkBpmn(manager, scope, spec, plan);
}
/**
* Upload the compiled BPMN and link it to the workflow. If this fails right
* after a new shell was created, roll the shell back so no unusable,
* BPMN-less workflow is left behind (an existing workflow is left as is —
* its previous BPMN stays linked and re-applying converges). A failed
* rollback is warned about, never swallowed.
*/
async uploadAndLinkBpmn(manager, scope, spec, plan) {
try {
const version = await manager.uploadWorkflowBpmn(scope, plan.id, spec.collection, plan.renderingId, plan.bpmn);
await manager.patchDomain('workflows', [{ op: 'replace', path: `/workflows/${plan.id}/bpmnAwsS3Identifier`, value: version }], scope);
}
catch (uploadError) {
if (!plan.match) {
await manager
.patchDomain('workflows', [{ op: 'remove', path: `/workflows/${plan.id}` }], scope)
.catch(() => {
this.logger.warn(`Failed to roll back workflow shell ${plan.id}: a workflow without BPMN may remain — delete it from the UI.`);
});
}
throw uploadError;
}
}
/** Success message reflecting what actually changed. */
printOutcome(plan, spec, scope) {
const name = this.chalk.bold(spec.name);
const target = `${this.chalk.bold(scope.environmentName)} / ${this.chalk.bold(scope.teamName)}`;
const stepCount = (spec.steps ?? []).length;
if (!plan.match) {
this.logger.success(`Created workflow ${name} (${stepCount} steps) on ${target}.`);
return;
}
const details = [
...plan.shellChanges.map(change => change.field),
plan.bpmnChanged ? `BPMN uploaded (${stepCount} steps)` : 'BPMN unchanged, skipped',
];
this.logger.success(`Updated workflow ${name} on ${target} (${details.join(', ')}).`);
}
}
exports.default = WorkflowApplyCommand;
WorkflowApplyCommand.args = {
file: core_1.Args.string({
description: 'JSON workflow spec ({ name, collection, steps, id?, segments?, position?, start?, version? }). "-" or omit to read stdin.',
required: false,
}),
};
WorkflowApplyCommand.description = 'Create or update a workflow from a JSON steps spec (compiles to BPMN and uploads it). ' +
'Reading the spec from stdin disables prompts: pass -p/-e/-t and --force explicitly.';
WorkflowApplyCommand.flags = {
'dry-run': core_1.Flags.boolean({
description: 'Resolve the scope, match the workflow and print the plan; send nothing.',
}),
env: core_1.Flags.string({ char: 'e', description: 'Environment name or id.' }),
force: core_1.Flags.boolean({
char: 'f',
description: 'Skip the confirmation prompt (required when the spec is read from stdin).',
}),
projectId: core_1.Flags.integer({ char: 'p', description: 'Project id.' }),
team: core_1.Flags.string({ char: 't', description: 'Team name or id.' }),
};
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXBwbHkuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvY29tbWFuZHMvd29ya2Zsb3cvYXBwbHkudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7QUFRQSxzQ0FBMEM7QUFDMUMsbUNBQW9DO0FBQ3BDLDBDQUF1QztBQUV2QywwR0FBZ0Y7QUFDaEYseURBQThEO0FBQzlELDBGQUFpRTtBQUNqRSxtRUFBb0U7QUFDcEUsdUZBQWtGO0FBQ2xGLHFEQUE4RDtBQUM5RCx5RUFJOEM7QUFDOUMsdUVBQStGO0FBRS9GLFNBQVMsU0FBUztJQUNoQixPQUFPLElBQUksT0FBTyxDQUFDLENBQUMsT0FBTyxFQUFFLE1BQU0sRUFBRSxFQUFFO1FBQ3JDLElBQUksSUFBSSxHQUFHLEVBQUUsQ0FBQztRQUNkLE9BQU8sQ0FBQyxLQUFLLENBQUMsV0FBVyxDQUFDLE1BQU0sQ0FBQyxDQUFDO1FBQ2xDLE9BQU8sQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDLE1BQU0sRUFBRSxLQUFLLENBQUMsRUFBRTtZQUMvQixJQUFJLElBQUksS0FBSyxDQUFDO1FBQ2hCLENBQUMsQ0FBQyxDQUFDO1FBQ0gsT0FBTyxDQUFDLEtBQUssQ0FBQyxFQUFFLENBQUMsS0FBSyxFQUFFLEdBQUcsRUFBRSxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDO1FBQzdDLE9BQU8sQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDLE9BQU8sRUFBRSxNQUFNLENBQUMsQ0FBQztJQUNwQyxDQUFDLENBQUMsQ0FBQztBQUNMLENBQUM7QUFFRCxtRkFBbUY7QUFDbkYsS0FBSyxVQUFVLFNBQVMsQ0FBQyxRQUE0QjtJQUNuRCxJQUFJLENBQUMsUUFBUTtRQUFFLE9BQU8sU0FBUyxFQUFFLENBQUM7SUFFbEMsSUFBSTtRQUNGLE9BQU8sTUFBTSxJQUFBLG1CQUFRLEVBQUMsUUFBUSxFQUFFLE1BQU0sQ0FBQyxDQUFDO0tBQ3pDO0lBQUMsT0FBTyxLQUFLLEVBQUU7UUFD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