Skip to content

Commit d7b1aeb

Browse files
committed
feat(export): add PlantUML and contract exporters
1 parent aae55c7 commit d7b1aeb

File tree

8 files changed

+505
-1
lines changed

8 files changed

+505
-1
lines changed

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,9 @@ OrgScript is intentionally artifact-first. A single `.orgs` file can produce mul
103103
6. BPMN skeleton exports
104104
7. LittleHorse workflow skeletons
105105
8. Graph JSON exports
106-
9. AI-ready structured JSON exports
106+
9. PlantUML skeleton exports
107+
10. OpenAPI-style contract metadata
108+
11. AI-ready structured JSON exports
107109

108110
Generated examples live under:
109111

@@ -204,6 +206,8 @@ orgscript export html <file>
204206
orgscript export bpmn <file>
205207
orgscript export littlehorse <file>
206208
orgscript export graph <file>
209+
orgscript export plantuml <file>
210+
orgscript export contract <file>
207211
orgscript export context <file>
208212
orgscript analyze <file> [--json]
209213
```

docs/OrgScript-Handbuch-DE.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ orgscript export context ./examples/lead-qualification.orgs
7171
orgscript export bpmn ./examples/lead-qualification.orgs
7272
orgscript export littlehorse ./examples/lead-qualification.orgs
7373
orgscript export graph ./examples/lead-qualification.orgs
74+
orgscript export plantuml ./examples/lead-qualification.orgs
75+
orgscript export contract ./examples/lead-qualification.orgs
7476
orgscript export graph ./examples/lead-qualification.orgs
7577
```
7678

@@ -83,6 +85,8 @@ Was sie tun:
8385
- `export bpmn` erzeugt ein BPMN-XML-Skelett fuer Prozessbloecke
8486
- `export littlehorse` erzeugt ein LittleHorse-Workflow-Skelett (Pseudo-Code)
8587
- `export graph` erzeugt ein kompaktes Graph-JSON (Nodes + Edges)
88+
- `export plantuml` erzeugt PlantUML-Skelette fuer Prozesse und Stateflows
89+
- `export contract` erzeugt ein OpenAPI-aehnliches Prozess-Contract-JSON
8690
- `export graph` erzeugt ein kompaktes Knoten-und-Kanten-JSON
8791

8892
## Kommentare und Annotationen
@@ -169,6 +173,7 @@ Standardverhalten der Exporter:
169173
- Annotationen erscheinen in Markdown und HTML nur mit `--with-annotations`
170174
- BPMN- und LittleHorse-Exporter sind Skelette und brauchen manuelle Nacharbeit
171175
- Graph-JSON-Export ist ein kompaktes Integrationsartefakt fuer Tooling und Visualisierung
176+
- PlantUML- und Contract-Exporter sind leichte Skelette fuer Kommunikation und Tooling
172177
- Graph-Export ist eine kompakte Strukturansicht, kein semantischer Ersatz fuer das kanonische Modell
173178

174179
So bleibt Geschaeftslogik explizit und Kommentare werden keine versteckte zweite Sprache.

docs/OrgScript-Manual-EN.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ orgscript export context ./examples/lead-qualification.orgs
7171
orgscript export bpmn ./examples/lead-qualification.orgs
7272
orgscript export littlehorse ./examples/lead-qualification.orgs
7373
orgscript export graph ./examples/lead-qualification.orgs
74+
orgscript export plantuml ./examples/lead-qualification.orgs
75+
orgscript export contract ./examples/lead-qualification.orgs
7476
orgscript export graph ./examples/lead-qualification.orgs
7577
```
7678

@@ -83,6 +85,8 @@ What they do:
8385
- `export bpmn` creates a BPMN XML skeleton for process blocks
8486
- `export littlehorse` creates a LittleHorse workflow skeleton (pseudo-code scaffold)
8587
- `export graph` creates a minimal graph JSON (nodes + edges)
88+
- `export plantuml` creates PlantUML skeletons for processes and stateflows
89+
- `export contract` creates an OpenAPI-style process contract JSON
8690
- `export graph` creates a compact nodes-and-edges JSON graph
8791

8892
## Comments and annotations
@@ -169,6 +173,7 @@ Default exporter policy:
169173
- annotations appear in Markdown and HTML only when you pass `--with-annotations`
170174
- BPMN and LittleHorse exporters are skeletons and require manual review before use
171175
- Graph JSON export is a compact integration artifact for tooling and visualization
176+
- PlantUML and contract exporters are lightweight scaffolds for communication and tooling
172177
- Graph export is a compact structural view, not a semantic replacement for the canonical model
173178

174179
This keeps business meaning explicit and prevents comments from becoming a hidden second language.

src/command-line.js

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ const { toMermaidMarkdown } = require("./export-mermaid");
1818
const { toHtmlDocumentation } = require("./export-html");
1919
const { toBpmnXml } = require("./export-bpmn");
2020
const { toGraphJson } = require("./export-graph");
21+
const { toPlantuml } = require("./export-plantuml");
2122
const { toLittleHorseSkeleton } = require("./export-littlehorse");
23+
const { toContractJson } = require("./export-contract");
2224
const { formatDocument } = require("./formatter");
2325
const { lintDocument, summarizeFindings } = require("./linter");
2426
const { buildModel, validateFile } = require("./validate");
@@ -158,7 +160,9 @@ Targets:
158160
html Self-contained documentation page
159161
bpmn BPMN XML skeleton
160162
graph Graph JSON (nodes + edges)
163+
plantuml PlantUML skeletons (process + stateflow)
161164
littlehorse LittleHorse workflow skeleton (pseudo-code)
165+
contract OpenAPI-style process contract (metadata)
162166
163167
Options:
164168
--with-annotations Include annotations and document metadata in supported Markdown and HTML exports
@@ -247,6 +251,16 @@ ${docs}`);
247251
Usage:
248252
orgscript export graph <file>
249253
254+
${docs}`);
255+
return;
256+
}
257+
258+
if (target === "plantuml") {
259+
console.log(`orgscript export plantuml
260+
261+
Usage:
262+
orgscript export plantuml <file>
263+
250264
${docs}`);
251265
return;
252266
}
@@ -257,6 +271,16 @@ ${docs}`);
257271
Usage:
258272
orgscript export littlehorse <file>
259273
274+
${docs}`);
275+
return;
276+
}
277+
278+
if (target === "contract") {
279+
console.log(`orgscript export contract
280+
281+
Usage:
282+
orgscript export contract <file>
283+
260284
${docs}`);
261285
return;
262286
}
@@ -499,6 +523,27 @@ function run(args) {
499523
}
500524
}
501525

526+
if (command === "export" && maybeSubcommand === "plantuml") {
527+
const absolutePath = resolveFile("export", maybeFile);
528+
const result = buildModel(absolutePath);
529+
530+
if (!result.ok) {
531+
printDiagnostics(
532+
`EXPORT ${toDisplayPath(absolutePath)}`,
533+
createValidateReport(absolutePath, result).diagnostics
534+
);
535+
process.exit(1);
536+
}
537+
538+
try {
539+
process.stdout.write(toPlantuml(toCanonicalModel(result.ast)));
540+
process.exit(0);
541+
} catch (error) {
542+
console.error(`Cannot export PlantUML from ${absolutePath}: ${error.message}`);
543+
process.exit(1);
544+
}
545+
}
546+
502547
if (command === "export" && maybeSubcommand === "littlehorse") {
503548
const absolutePath = resolveFile("export", maybeFile);
504549
const result = buildModel(absolutePath);
@@ -520,6 +565,27 @@ function run(args) {
520565
}
521566
}
522567

568+
if (command === "export" && maybeSubcommand === "contract") {
569+
const absolutePath = resolveFile("export", maybeFile);
570+
const result = buildModel(absolutePath);
571+
572+
if (!result.ok) {
573+
printDiagnostics(
574+
`EXPORT ${toDisplayPath(absolutePath)}`,
575+
createValidateReport(absolutePath, result).diagnostics
576+
);
577+
process.exit(1);
578+
}
579+
580+
try {
581+
process.stdout.write(toContractJson(toCanonicalModel(result.ast)));
582+
process.exit(0);
583+
} catch (error) {
584+
console.error(`Cannot export contract from ${absolutePath}: ${error.message}`);
585+
process.exit(1);
586+
}
587+
}
588+
523589
if (command === "export" && maybeSubcommand === "html") {
524590
const absolutePath = resolveFile("export", maybeFile);
525591
const result = buildModel(absolutePath);

src/export-contract.js

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
function toContractJson(model) {
2+
const processes = (model.body || []).filter((node) => node.type === "process");
3+
const stateflows = (model.body || []).filter((node) => node.type === "stateflow");
4+
5+
if (processes.length === 0 && stateflows.length === 0) {
6+
throw new Error(
7+
"No contract-exportable blocks found. Supported block types: process, stateflow."
8+
);
9+
}
10+
11+
const contract = {
12+
version: "0.1",
13+
type: "contract",
14+
processes: processes.map(toProcessContract),
15+
stateflows: stateflows.map(toStateflowContract),
16+
};
17+
18+
return `${JSON.stringify(contract, null, 2)}\n`;
19+
}
20+
21+
function toProcessContract(processNode) {
22+
const triggers = (processNode.body || [])
23+
.filter((statement) => statement.type === "when")
24+
.map((statement) => statement.trigger || "unknown");
25+
26+
const steps = flattenStatements(processNode.body || []);
27+
28+
return {
29+
name: processNode.name,
30+
triggers,
31+
steps: steps.map((step) => ({
32+
kind: step.kind,
33+
label: step.label,
34+
condition: step.condition || null,
35+
})),
36+
};
37+
}
38+
39+
function toStateflowContract(stateflow) {
40+
return {
41+
name: stateflow.name,
42+
states: stateflow.states || [],
43+
transitions: (stateflow.transitions || []).map((edge) => ({
44+
from: edge.from,
45+
to: edge.to,
46+
})),
47+
};
48+
}
49+
50+
function flattenStatements(statements) {
51+
const steps = [];
52+
53+
for (const statement of statements) {
54+
if (statement.type === "when") {
55+
steps.push({
56+
kind: "when",
57+
label: `when ${statement.trigger || "unknown"}`,
58+
});
59+
continue;
60+
}
61+
62+
if (statement.type === "if") {
63+
const condition = formatCondition(statement.condition);
64+
steps.push({
65+
kind: "if",
66+
label: `if ${condition}`,
67+
condition,
68+
});
69+
steps.push(...flattenStatements(statement.then || []));
70+
for (const branch of statement.elseIf || []) {
71+
const branchCondition = formatCondition(branch.condition);
72+
steps.push({
73+
kind: "else-if",
74+
label: `else if ${branchCondition}`,
75+
condition: branchCondition,
76+
});
77+
steps.push(...flattenStatements(branch.then || []));
78+
}
79+
if (statement.else && (statement.else.body || []).length > 0) {
80+
steps.push({ kind: "else", label: "else", condition: null });
81+
steps.push(...flattenStatements(statement.else.body || []));
82+
}
83+
continue;
84+
}
85+
86+
steps.push({
87+
kind: statement.type,
88+
label: formatAction(statement),
89+
});
90+
}
91+
92+
return steps;
93+
}
94+
95+
function formatAction(statement) {
96+
if (statement.type === "assign") {
97+
return `assign ${statement.target || "?"} = ${formatExpression(statement.value)}`;
98+
}
99+
100+
if (statement.type === "transition") {
101+
return `transition ${statement.target || "?"} to ${formatExpression(statement.value)}`;
102+
}
103+
104+
if (statement.type === "notify") {
105+
return `notify ${statement.target} "${statement.message}"`;
106+
}
107+
108+
if (statement.type === "create") {
109+
return `create ${statement.entity}`;
110+
}
111+
112+
if (statement.type === "update") {
113+
return `update ${statement.target || "?"} = ${formatExpression(statement.value)}`;
114+
}
115+
116+
if (statement.type === "require") {
117+
return `require ${statement.requirement}`;
118+
}
119+
120+
if (statement.type === "stop") {
121+
return "stop";
122+
}
123+
124+
return statement.type;
125+
}
126+
127+
function formatCondition(condition) {
128+
if (!condition) {
129+
return "unknown condition";
130+
}
131+
132+
if (condition.type === "logical") {
133+
return condition.conditions.map(formatCondition).join(` ${condition.operator} `);
134+
}
135+
136+
return `${formatExpression(condition.left)} ${condition.operator} ${formatExpression(condition.right)}`;
137+
}
138+
139+
function formatExpression(expression) {
140+
if (!expression) {
141+
return "?";
142+
}
143+
144+
if (expression.type === "field") {
145+
return expression.path;
146+
}
147+
148+
if (expression.type === "identifier") {
149+
return expression.value;
150+
}
151+
152+
if (expression.type === "string") {
153+
return `"${expression.value}"`;
154+
}
155+
156+
if (expression.type === "boolean") {
157+
return expression.value ? "true" : "false";
158+
}
159+
160+
return String(expression.value);
161+
}
162+
163+
module.exports = {
164+
toContractJson,
165+
};

0 commit comments

Comments
 (0)