@bpmnkit/cli 0.0.37 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -9,7 +9,7 @@
9
9
  [![ai-assisted](https://img.shields.io/badge/AI--assisted-claude-8b5cf6?style=flat-square)](https://github.com/bpmnkit/monorepo)
10
10
  [![experimental](https://img.shields.io/badge/status-experimental-f59e0b?style=flat-square)](https://github.com/bpmnkit/monorepo)
11
11
 
12
- [Website](https://bpmnkit.com) · [Documentation](https://docs.bpmnkit.com) · [GitHub](https://github.com/bpmnkit/monorepo) · [Changelog](https://github.com/bpmnkit/monorepo/blob/main/apps/cli/CHANGELOG.md)
12
+ [Website](https://bpmnkit.com) · [Documentation](https://bpmnkit.com/docs) · [GitHub](https://github.com/bpmnkit/monorepo) · [Changelog](https://github.com/bpmnkit/monorepo/blob/main/apps/cli/CHANGELOG.md)
13
13
  </div>
14
14
 
15
15
  ---
@@ -1,5 +1,6 @@
1
1
  import { readFile, writeFile } from "node:fs/promises";
2
- import { Bpmn, compactify, expand } from "@bpmnkit/core";
2
+ import { resolve } from "node:path";
3
+ import { Bpmn, applyAutoLayout, applyBpmnOperations, compactify, expand } from "@bpmnkit/core";
3
4
  // ── JSON schema reference ─────────────────────────────────────────────────────
4
5
  const SCHEMA_HELP = `CompactDiagram JSON schema — for --definition and stdin input
5
6
  ==============================================================
@@ -455,6 +456,35 @@ async function readStdin() {
455
456
  }
456
457
  return Buffer.concat(chunks).toString("utf-8").trim();
457
458
  }
459
+ // ── Output path guard ─────────────────────────────────────────────────────────
460
+ /**
461
+ * Decide where `--input` mode writes, refusing to replace the input file unless
462
+ * the caller asked for it explicitly.
463
+ *
464
+ * The patch itself is applied to the full model, so nothing outside it is lost.
465
+ * Replacing the source is still destructive — the diagram is re-laid out, and a
466
+ * mistaken patch has nowhere to be compared against — so in-place replacement
467
+ * has to be asked for by name.
468
+ *
469
+ * @param inputFile - The `--input` path.
470
+ * @param outputFlag - The `--output` path, if any. `-` (stdout) is handled by the caller.
471
+ * @param force - Whether `--force` was passed.
472
+ * @returns The path to write to.
473
+ * @throws If the write would replace `inputFile` and `force` is false.
474
+ */
475
+ export function resolveModifyOutputPath({ inputFile, outputFlag, force, }) {
476
+ const output = typeof outputFlag === "string" && outputFlag.length > 0 ? outputFlag : undefined;
477
+ const inPlace = output === undefined || resolve(output) === resolve(inputFile);
478
+ if (inPlace && !force) {
479
+ throw new Error([
480
+ `Refusing to overwrite ${inputFile}.`,
481
+ "The patch applies to the full model, so nothing is dropped, but the diagram is " +
482
+ "re-laid out and the original is gone once it is replaced.",
483
+ `Write elsewhere with --output <file>, or pass --force to replace ${inputFile} anyway.`,
484
+ ].join("\n"));
485
+ }
486
+ return output ?? inputFile;
487
+ }
458
488
  // ── Command ───────────────────────────────────────────────────────────────────
459
489
  const generateBpmnCmd = {
460
490
  name: "bpmn",
@@ -500,7 +530,9 @@ const generateBpmnCmd = {
500
530
  {
501
531
  name: "input",
502
532
  short: "f",
503
- description: "Existing .bpmn file to load and modify",
533
+ description: "Existing .bpmn file to load and modify. The patch is applied to the full model, so " +
534
+ "pools, lanes, data wiring and Zeebe detail are preserved; the diagram is re-laid " +
535
+ "out. Requires --output, or --force to replace it in place.",
504
536
  type: "string",
505
537
  },
506
538
  {
@@ -513,6 +545,11 @@ const generateBpmnCmd = {
513
545
  description: "Print the CompactDiagram JSON of --input and exit (for AI inspection of existing files)",
514
546
  type: "boolean",
515
547
  },
548
+ {
549
+ name: "force",
550
+ description: "Allow --input to be replaced in place. Prefer --output <file>.",
551
+ type: "boolean",
552
+ },
516
553
  ],
517
554
  examples: [
518
555
  {
@@ -553,11 +590,11 @@ const generateBpmnCmd = {
553
590
  },
554
591
  {
555
592
  description: "Add a new gateway path to an existing file",
556
- command: 'casen generate bpmn --input order.bpmn --patch \'{"elements":[{"id":"notify","type":"serviceTask","name":"Notify","jobType":"notify-worker"},{"id":"end2","type":"endEvent","name":"Notified"}],"flows":[{"id":"fn1","from":"gw","to":"notify","condition":"= urgent"},{"id":"fn2","from":"notify","to":"end2"}]}\'',
593
+ command: 'casen generate bpmn --input order.bpmn --output order.patched.bpmn --patch \'{"elements":[{"id":"notify","type":"serviceTask","name":"Notify","jobType":"notify-worker"},{"id":"end2","type":"endEvent","name":"Notified"}],"flows":[{"id":"fn1","from":"gw","to":"notify","condition":"= urgent"},{"id":"fn2","from":"notify","to":"end2"}]}\'',
557
594
  },
558
595
  {
559
596
  description: "Pipe a patch from AI output",
560
- command: 'echo \'{"elements":[...],"flows":[...]}\' | casen generate bpmn --input order.bpmn',
597
+ command: 'echo \'{"elements":[...],"flows":[...]}\' | casen generate bpmn --input order.bpmn --output order.patched.bpmn',
561
598
  },
562
599
  {
563
600
  description: "Re-apply auto-layout to an existing file",
@@ -580,12 +617,20 @@ const generateBpmnCmd = {
580
617
  if (inputFile) {
581
618
  const xml = await readFile(inputFile, "utf-8");
582
619
  const defs = Bpmn.parse(xml);
583
- const compact = compactify(defs);
584
620
  // --dump-compact: print JSON for AI inspection and exit
585
621
  if (ctx.flags["dump-compact"]) {
586
- process.stdout.write(`${JSON.stringify(compact, null, 2)}\n`);
622
+ process.stdout.write(`${JSON.stringify(compactify(defs), null, 2)}\n`);
587
623
  return;
588
624
  }
625
+ // Settle the destination before reading stdin or building the patch, so an
626
+ // unwritable target fails immediately instead of after the work is done.
627
+ const outputPath = outputFlag === "-"
628
+ ? null
629
+ : resolveModifyOutputPath({
630
+ inputFile,
631
+ outputFlag,
632
+ force: ctx.flags.force === true,
633
+ });
589
634
  // Resolve patch from --patch flag or stdin
590
635
  let patch = null;
591
636
  if (patchFlag) {
@@ -607,22 +652,40 @@ const generateBpmnCmd = {
607
652
  }
608
653
  }
609
654
  }
610
- // Apply patch to first process (covers all single-process cases)
655
+ // Apply the patch to the full model, not to the compact view of it: an
656
+ // element added this way leaves the document's pools, lanes, data
657
+ // wiring and Zeebe detail exactly where they were.
658
+ let edited = defs;
611
659
  if (patch) {
612
- const proc = compact.processes[0];
613
- if (!proc)
660
+ const process = defs.processes[0];
661
+ if (!process)
614
662
  throw new Error("Input BPMN has no processes");
615
- if (patch.elements?.length)
616
- proc.elements.push(...patch.elements);
617
- if (patch.flows?.length)
618
- proc.flows.push(...patch.flows);
663
+ const operations = [
664
+ ...(patch.elements ?? []).map((element) => ({ op: "insert", element, parent: process.id })),
665
+ ...(patch.flows ?? []).map((flow) => ({
666
+ op: "add_flow",
667
+ id: flow.id,
668
+ parent: process.id,
669
+ from: flow.from,
670
+ to: flow.to,
671
+ name: flow.name,
672
+ condition: flow.condition,
673
+ })),
674
+ ];
675
+ try {
676
+ edited = applyBpmnOperations(defs, operations).definitions;
677
+ }
678
+ catch (error) {
679
+ // Unresolved ids used to be skipped in silence, so a patch naming a
680
+ // misspelled element reported success and changed nothing.
681
+ throw new Error(`Patch could not be applied: ${error instanceof Error ? error.message : String(error)}`);
682
+ }
619
683
  }
620
- const patched = Bpmn.export(expand(compact));
621
- if (outputFlag === "-") {
684
+ const patched = Bpmn.export(applyAutoLayout(edited));
685
+ if (outputPath === null) {
622
686
  process.stdout.write(patched);
623
687
  return;
624
688
  }
625
- const outputPath = typeof outputFlag === "string" && outputFlag.length > 0 ? outputFlag : inputFile;
626
689
  await writeFile(outputPath, patched, "utf-8");
627
690
  ctx.output.ok(patch ? `Patched and written to ${outputPath}` : `Re-laid-out and written to ${outputPath}`);
628
691
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bpmnkit/cli",
3
- "version": "0.0.37",
3
+ "version": "0.1.0",
4
4
  "description": "Command-line interface for Camunda 8 — deploy, manage, and monitor processes from the terminal",
5
5
  "type": "module",
6
6
  "bin": {
@@ -17,14 +17,14 @@
17
17
  },
18
18
  "dependencies": {
19
19
  "@bpmnkit/api": "0.0.20",
20
- "@bpmnkit/ascii": "0.0.30",
20
+ "@bpmnkit/ascii": "0.0.31",
21
21
  "@bpmnkit/connector-gen": "0.0.15",
22
- "@bpmnkit/connectors": "0.0.2",
23
- "@bpmnkit/core": "0.1.2",
24
- "@bpmnkit/engine": "0.1.30",
22
+ "@bpmnkit/connectors": "0.0.3",
23
+ "@bpmnkit/core": "0.2.0",
24
+ "@bpmnkit/engine": "0.1.31",
25
25
  "@bpmnkit/patterns": "0.0.5",
26
26
  "@bpmnkit/profiles": "0.0.18",
27
- "@bpmnkit/proxy": "0.0.33"
27
+ "@bpmnkit/proxy": "0.1.0"
28
28
  },
29
29
  "publishConfig": {
30
30
  "access": "public"