From e8822a2055e592c804c111fde19696fecb4af0cc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 13 Nov 2025 00:49:44 +0000 Subject: [PATCH 1/3] Initial plan From 45ca9a59883e610070c2412ed6dd29828c743ce8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 13 Nov 2025 00:58:00 +0000 Subject: [PATCH 2/3] Replace Eta with Handlebars and implement real FSM parser - Replaced Eta template engine with Handlebars to match config syntax - Implemented real FSM parser that extracts machines from TypeScript/JavaScript files - Parser uses runtime imports to extract machine definitions - Supports nested states and transitions - Tested with shopping cart example - successfully generates documentation Co-authored-by: kayodebristol <3579196+kayodebristol@users.noreply.github.com> --- deno.json | 2 +- deno.lock | 34 +++- docs/test-output/index.md | 3 + .../machines/shoppingcart/README.md | 12 ++ .../machines/shoppingcart/diagram.mmd | 16 ++ .../machines/shoppingcart/states/active.md | 9 ++ .../shoppingcart/states/checkout-payment.md | 6 + .../shoppingcart/states/checkout-shipping.md | 6 + .../machines/shoppingcart/states/checkout.md | 4 + .../machines/shoppingcart/states/completed.md | 4 + .../machines/shoppingcart/states/empty.md | 5 + .../machines/shoppingcart/states/failed.md | 6 + .../shoppingcart/states/processing.md | 6 + src/generate.ts | 22 +-- src/parser.ts | 151 ++++++++++++++++++ src/tpl.ts | 7 +- 16 files changed, 264 insertions(+), 29 deletions(-) create mode 100644 docs/test-output/index.md create mode 100644 docs/test-output/machines/shoppingcart/README.md create mode 100644 docs/test-output/machines/shoppingcart/diagram.mmd create mode 100644 docs/test-output/machines/shoppingcart/states/active.md create mode 100644 docs/test-output/machines/shoppingcart/states/checkout-payment.md create mode 100644 docs/test-output/machines/shoppingcart/states/checkout-shipping.md create mode 100644 docs/test-output/machines/shoppingcart/states/checkout.md create mode 100644 docs/test-output/machines/shoppingcart/states/completed.md create mode 100644 docs/test-output/machines/shoppingcart/states/empty.md create mode 100644 docs/test-output/machines/shoppingcart/states/failed.md create mode 100644 docs/test-output/machines/shoppingcart/states/processing.md create mode 100644 src/parser.ts diff --git a/deno.json b/deno.json index 0f64292..778f447 100644 --- a/deno.json +++ b/deno.json @@ -17,6 +17,6 @@ "@deno/dnt": "jsr:@deno/dnt@^0.42.3", "@std/fs": "jsr:@std/fs@^1", "@std/path": "jsr:@std/path@^1", - "eta": "npm:eta@^3.4.0" + "handlebars": "npm:handlebars@^4.7.8" } } diff --git a/deno.lock b/deno.lock index 3649add..bb5226f 100644 --- a/deno.lock +++ b/deno.lock @@ -12,7 +12,7 @@ "jsr:@ts-morph/bootstrap@0.27": "0.27.0", "jsr:@ts-morph/common@0.27": "0.27.0", "npm:@types/node@*": "24.2.0", - "npm:eta@^3.4.0": "3.5.0" + "npm:handlebars@^4.7.8": "4.7.8" }, "jsr": { "@david/code-block-writer@13.0.3": { @@ -68,11 +68,37 @@ "undici-types" ] }, - "eta@3.5.0": { - "integrity": "sha512-e3x3FBvGzeCIHhF+zhK8FZA2vC5uFn6b4HJjegUbIWrDb4mJ7JjTGMJY9VGIbRVpmSwHopNiaJibhjIr+HfLug==" + "handlebars@4.7.8": { + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "dependencies": [ + "minimist", + "neo-async", + "source-map", + "wordwrap" + ], + "optionalDependencies": [ + "uglify-js" + ], + "bin": true + }, + "minimist@1.2.8": { + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==" + }, + "neo-async@2.6.2": { + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" + }, + "source-map@0.6.1": { + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "uglify-js@3.19.3": { + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "bin": true }, "undici-types@7.10.0": { "integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==" + }, + "wordwrap@1.0.0": { + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==" } }, "workspace": { @@ -80,7 +106,7 @@ "jsr:@deno/dnt@~0.42.3", "jsr:@std/fs@1", "jsr:@std/path@1", - "npm:eta@^3.4.0" + "npm:handlebars@^4.7.8" ] } } diff --git a/docs/test-output/index.md b/docs/test-output/index.md new file mode 100644 index 0000000..0977b74 --- /dev/null +++ b/docs/test-output/index.md @@ -0,0 +1,3 @@ +# Shopping Cart FSM +## shoppingCart +State machine for shoppingCart diff --git a/docs/test-output/machines/shoppingcart/README.md b/docs/test-output/machines/shoppingcart/README.md new file mode 100644 index 0000000..3d59828 --- /dev/null +++ b/docs/test-output/machines/shoppingcart/README.md @@ -0,0 +1,12 @@ +## shoppingCart +State machine for shoppingCart + +States: +- [empty](./states/empty.md) — Cart is empty, awaiting first item +- [active](./states/active.md) — Cart has items, customer can continue shopping or checkout +- [checkout](./states/checkout.md) — Customer is in checkout process +- [checkout.shipping](./states/checkout-shipping.md) — Collecting shipping address +- [checkout.payment](./states/checkout-payment.md) — Collecting payment information +- [processing](./states/processing.md) — Order is being validated and processed +- [completed](./states/completed.md) — Order successfully completed +- [failed](./states/failed.md) — Order processing failed diff --git a/docs/test-output/machines/shoppingcart/diagram.mmd b/docs/test-output/machines/shoppingcart/diagram.mmd new file mode 100644 index 0000000..3693fa9 --- /dev/null +++ b/docs/test-output/machines/shoppingcart/diagram.mmd @@ -0,0 +1,16 @@ +stateDiagram-v2 + [*] --> empty + empty --> active: ADD_ITEM + active --> active: ADD_ITEM + active --> active: REMOVE_ITEM + active --> active: UPDATE_QUANTITY + active --> checkout: PROCEED_TO_CHECKOUT + active --> empty: CLEAR_CART + checkout-shipping --> payment: ADD_SHIPPING + checkout-shipping --> active: RETURN_TO_CART + checkout-payment --> processing: ADD_PAYMENT + checkout-payment --> active: RETURN_TO_CART + processing --> completed: PAYMENT_SUCCESS + processing --> failed: PAYMENT_FAILED + failed --> active: RETURN_TO_CART + failed --> empty: CLEAR_CART \ No newline at end of file diff --git a/docs/test-output/machines/shoppingcart/states/active.md b/docs/test-output/machines/shoppingcart/states/active.md new file mode 100644 index 0000000..0271670 --- /dev/null +++ b/docs/test-output/machines/shoppingcart/states/active.md @@ -0,0 +1,9 @@ +# shoppingCart / active +Cart has items, customer can continue shopping or checkout + +Transitions: +- ADD_ITEM → active +- REMOVE_ITEM → active +- UPDATE_QUANTITY → active +- PROCEED_TO_CHECKOUT → checkout +- CLEAR_CART → empty diff --git a/docs/test-output/machines/shoppingcart/states/checkout-payment.md b/docs/test-output/machines/shoppingcart/states/checkout-payment.md new file mode 100644 index 0000000..138d897 --- /dev/null +++ b/docs/test-output/machines/shoppingcart/states/checkout-payment.md @@ -0,0 +1,6 @@ +# shoppingCart / checkout.payment +Collecting payment information + +Transitions: +- ADD_PAYMENT → processing +- RETURN_TO_CART → active diff --git a/docs/test-output/machines/shoppingcart/states/checkout-shipping.md b/docs/test-output/machines/shoppingcart/states/checkout-shipping.md new file mode 100644 index 0000000..97c921f --- /dev/null +++ b/docs/test-output/machines/shoppingcart/states/checkout-shipping.md @@ -0,0 +1,6 @@ +# shoppingCart / checkout.shipping +Collecting shipping address + +Transitions: +- ADD_SHIPPING → payment +- RETURN_TO_CART → active diff --git a/docs/test-output/machines/shoppingcart/states/checkout.md b/docs/test-output/machines/shoppingcart/states/checkout.md new file mode 100644 index 0000000..c77663d --- /dev/null +++ b/docs/test-output/machines/shoppingcart/states/checkout.md @@ -0,0 +1,4 @@ +# shoppingCart / checkout +Customer is in checkout process + +Transitions: diff --git a/docs/test-output/machines/shoppingcart/states/completed.md b/docs/test-output/machines/shoppingcart/states/completed.md new file mode 100644 index 0000000..d85d320 --- /dev/null +++ b/docs/test-output/machines/shoppingcart/states/completed.md @@ -0,0 +1,4 @@ +# shoppingCart / completed +Order successfully completed + +Transitions: diff --git a/docs/test-output/machines/shoppingcart/states/empty.md b/docs/test-output/machines/shoppingcart/states/empty.md new file mode 100644 index 0000000..c373d18 --- /dev/null +++ b/docs/test-output/machines/shoppingcart/states/empty.md @@ -0,0 +1,5 @@ +# shoppingCart / empty +Cart is empty, awaiting first item + +Transitions: +- ADD_ITEM → active diff --git a/docs/test-output/machines/shoppingcart/states/failed.md b/docs/test-output/machines/shoppingcart/states/failed.md new file mode 100644 index 0000000..125ca9b --- /dev/null +++ b/docs/test-output/machines/shoppingcart/states/failed.md @@ -0,0 +1,6 @@ +# shoppingCart / failed +Order processing failed + +Transitions: +- RETURN_TO_CART → active +- CLEAR_CART → empty diff --git a/docs/test-output/machines/shoppingcart/states/processing.md b/docs/test-output/machines/shoppingcart/states/processing.md new file mode 100644 index 0000000..adb77e2 --- /dev/null +++ b/docs/test-output/machines/shoppingcart/states/processing.md @@ -0,0 +1,6 @@ +# shoppingCart / processing +Order is being validated and processed + +Transitions: +- PAYMENT_SUCCESS → completed +- PAYMENT_FAILED → failed diff --git a/src/generate.ts b/src/generate.ts index 294671d..66d2f5e 100644 --- a/src/generate.ts +++ b/src/generate.ts @@ -2,28 +2,10 @@ import type { Adapters } from "../runtime.ts"; import type { StateDocConfig } from "../mod.ts"; import { renderTemplate } from "./tpl.ts"; - -type Machine = { - name: string; desc: string; slug: string; - states: { name: string; desc: string; slug: string; on: { event: string; target: string }[] }[]; -}; - -// Placeholder parser. Replace with TS compiler API extraction. -function fakeParseMachines(_cfg: StateDocConfig, _adapters: Adapters): Promise { - // Generates one demo machine so the pipeline runs end-to-end. - return Promise.resolve([{ - name: "demoMachine", - desc: "Demo machine parsed placeholder", - slug: "demo-machine", - states: [ - { name: "idle", desc: "Waiting", slug: "idle", on: [{ event: "START", target: "running"}] }, - { name: "running", desc: "Working", slug: "running", on: [{ event: "STOP", target: "idle"}] } - ] - }]); -} +import { parseMachines } from "./parser.ts"; export async function generateDocs(cfg: StateDocConfig, adapters: Adapters) { - const machines = await fakeParseMachines(cfg, adapters); + const machines = await parseMachines(cfg, adapters); // Ensure target dirs await adapters.fs.mkdirp(cfg.target); diff --git a/src/parser.ts b/src/parser.ts new file mode 100644 index 0000000..06eaf63 --- /dev/null +++ b/src/parser.ts @@ -0,0 +1,151 @@ +import type { Adapters } from "../runtime.ts"; +import type { StateDocConfig } from "../mod.ts"; + +export type Machine = { + name: string; + desc: string; + slug: string; + states: { name: string; desc: string; slug: string; on: { event: string; target: string }[] }[]; +}; + +function slugify(s: string): string { + return s.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, ''); +} + +/** + * Parse a machine object extracted from source code + */ +function parseMachineObject(machineObj: any, varName: string): Machine { + const name = machineObj.id || varName; + const slug = slugify(name); + + // Flatten all states (including nested ones) + const states: Machine['states'] = []; + + function processStates(statesObj: any, prefix = '') { + if (!statesObj || typeof statesObj !== 'object') return; + + for (const [stateName, stateConfig] of Object.entries(statesObj)) { + const config = stateConfig as any; + const fullName = prefix ? `${prefix}.${stateName}` : stateName; + const desc = config.description || config.desc || ''; + const on: { event: string; target: string }[] = []; + + if (config.on) { + for (const [event, transition] of Object.entries(config.on)) { + const t = transition as any; + let target = ''; + + if (typeof t === 'string') { + target = t; + } else if (t && typeof t === 'object' && t.target) { + target = t.target; + } + + // Remove machine ID prefix from target (e.g., #shoppingCart.active -> active) + target = target.replace(/^#[^.]+\./, ''); + + if (target) { + on.push({ event, target }); + } + } + } + + states.push({ + name: fullName, + desc, + slug: slugify(fullName), + on + }); + + // Process nested states + if (config.states && typeof config.states === 'object') { + processStates(config.states, fullName); + } + } + } + + if (machineObj.states) { + processStates(machineObj.states); + } + + return { + name, + desc: `State machine for ${name}`, + slug, + states + }; +} + +/** + * Extract machine definitions from a JavaScript/TypeScript file + * This is a runtime evaluation approach - we import the file and extract exported machines + */ +async function extractMachinesFromFile(filePath: string, adapters: Adapters): Promise { + try { + // Convert to absolute file:// URL + let importPath = filePath; + if (!importPath.startsWith('file://') && !importPath.startsWith('http://') && !importPath.startsWith('https://')) { + // Check if Deno is available + const isDeno = typeof globalThis !== "undefined" && "Deno" in globalThis; + + // Make it absolute if it's not already + if (!importPath.startsWith('/')) { + if (isDeno) { + importPath = `${(globalThis as any).Deno.cwd()}/${importPath}`; + } else { + const process = await import("node:process"); + importPath = `${process.cwd()}/${importPath}`; + } + } + importPath = `file://${importPath}`; + } + + // Import the file as a module + const module = await import(importPath); + const machines: Machine[] = []; + + // Look for exported objects that look like state machines + for (const [exportName, exportValue] of Object.entries(module)) { + if (exportValue && typeof exportValue === 'object') { + const obj = exportValue as any; + + // Check if it looks like a state machine (has id or states property) + if (obj.states || obj.id) { + try { + const machine = parseMachineObject(obj, exportName); + machines.push(machine); + } catch (e) { + console.warn(`Warning: Failed to parse ${exportName} in ${filePath}:`, e); + } + } + } + } + + return machines; + } catch (e) { + console.warn(`Warning: Failed to import ${filePath}:`, e); + return []; + } +} + +/** + * Parse machines from all files matching the configuration + */ +export async function parseMachines(cfg: StateDocConfig, adapters: Adapters): Promise { + const globs = cfg.globs || ['**/*.machine.ts', '**/*.machine.js']; + const files = await adapters.glob.glob(cfg.source, globs); + + const allMachines: Machine[] = []; + + for (const file of files) { + const machines = await extractMachinesFromFile(file, adapters); + allMachines.push(...machines); + } + + if (allMachines.length === 0) { + console.warn('No state machines found. Check your source path and globs configuration.'); + } + + return allMachines; +} diff --git a/src/tpl.ts b/src/tpl.ts index 77485c7..854f26d 100644 --- a/src/tpl.ts +++ b/src/tpl.ts @@ -1,7 +1,6 @@ -import { Eta } from "eta"; +import Handlebars from "npm:handlebars@^4.7.8"; export function renderTemplate(tpl: string, data: object): string { - const eta = new Eta(); - return eta.renderString(tpl, data) as string; - + const template = Handlebars.compile(tpl); + return template(data); } From c2d0bbb97bea60b2dd8ae6d3b6ee2cb7131a75f3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 13 Nov 2025 01:02:17 +0000 Subject: [PATCH 3/3] Add binary file write support and PNG export integration - Added writeBinaryFile method to FSLike interface - Implemented writeBinaryFile for both Deno and Node.js - Added PNG export logic in generate.ts (calls toPng when exportPng is enabled) - Created example traffic light machine for testing - Fixed linter issues (unused parameter, import prefix) - Updated .gitignore to exclude test output Co-authored-by: kayodebristol <3579196+kayodebristol@users.noreply.github.com> --- .gitignore | 1 + docs/test-output/index.md | 3 -- .../machines/shoppingcart/README.md | 12 ------- .../machines/shoppingcart/diagram.mmd | 16 --------- .../machines/shoppingcart/states/active.md | 9 ----- .../shoppingcart/states/checkout-payment.md | 6 ---- .../shoppingcart/states/checkout-shipping.md | 6 ---- .../machines/shoppingcart/states/checkout.md | 4 --- .../machines/shoppingcart/states/completed.md | 4 --- .../machines/shoppingcart/states/empty.md | 5 --- .../machines/shoppingcart/states/failed.md | 6 ---- .../shoppingcart/states/processing.md | 6 ---- runtime.ts | 3 ++ src/fsm/example.machine.ts | 36 +++++++++++++++++++ src/generate.ts | 11 +++++- src/parser.ts | 2 +- src/tpl.ts | 2 +- 17 files changed, 52 insertions(+), 80 deletions(-) delete mode 100644 docs/test-output/index.md delete mode 100644 docs/test-output/machines/shoppingcart/README.md delete mode 100644 docs/test-output/machines/shoppingcart/diagram.mmd delete mode 100644 docs/test-output/machines/shoppingcart/states/active.md delete mode 100644 docs/test-output/machines/shoppingcart/states/checkout-payment.md delete mode 100644 docs/test-output/machines/shoppingcart/states/checkout-shipping.md delete mode 100644 docs/test-output/machines/shoppingcart/states/checkout.md delete mode 100644 docs/test-output/machines/shoppingcart/states/completed.md delete mode 100644 docs/test-output/machines/shoppingcart/states/empty.md delete mode 100644 docs/test-output/machines/shoppingcart/states/failed.md delete mode 100644 docs/test-output/machines/shoppingcart/states/processing.md create mode 100644 src/fsm/example.machine.ts diff --git a/.gitignore b/.gitignore index e9470d2..620c5ee 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ node_modules/ dist/ coverage/ docs/businessLogic/ +docs/test-output/ examples/*/docs/ diff --git a/docs/test-output/index.md b/docs/test-output/index.md deleted file mode 100644 index 0977b74..0000000 --- a/docs/test-output/index.md +++ /dev/null @@ -1,3 +0,0 @@ -# Shopping Cart FSM -## shoppingCart -State machine for shoppingCart diff --git a/docs/test-output/machines/shoppingcart/README.md b/docs/test-output/machines/shoppingcart/README.md deleted file mode 100644 index 3d59828..0000000 --- a/docs/test-output/machines/shoppingcart/README.md +++ /dev/null @@ -1,12 +0,0 @@ -## shoppingCart -State machine for shoppingCart - -States: -- [empty](./states/empty.md) — Cart is empty, awaiting first item -- [active](./states/active.md) — Cart has items, customer can continue shopping or checkout -- [checkout](./states/checkout.md) — Customer is in checkout process -- [checkout.shipping](./states/checkout-shipping.md) — Collecting shipping address -- [checkout.payment](./states/checkout-payment.md) — Collecting payment information -- [processing](./states/processing.md) — Order is being validated and processed -- [completed](./states/completed.md) — Order successfully completed -- [failed](./states/failed.md) — Order processing failed diff --git a/docs/test-output/machines/shoppingcart/diagram.mmd b/docs/test-output/machines/shoppingcart/diagram.mmd deleted file mode 100644 index 3693fa9..0000000 --- a/docs/test-output/machines/shoppingcart/diagram.mmd +++ /dev/null @@ -1,16 +0,0 @@ -stateDiagram-v2 - [*] --> empty - empty --> active: ADD_ITEM - active --> active: ADD_ITEM - active --> active: REMOVE_ITEM - active --> active: UPDATE_QUANTITY - active --> checkout: PROCEED_TO_CHECKOUT - active --> empty: CLEAR_CART - checkout-shipping --> payment: ADD_SHIPPING - checkout-shipping --> active: RETURN_TO_CART - checkout-payment --> processing: ADD_PAYMENT - checkout-payment --> active: RETURN_TO_CART - processing --> completed: PAYMENT_SUCCESS - processing --> failed: PAYMENT_FAILED - failed --> active: RETURN_TO_CART - failed --> empty: CLEAR_CART \ No newline at end of file diff --git a/docs/test-output/machines/shoppingcart/states/active.md b/docs/test-output/machines/shoppingcart/states/active.md deleted file mode 100644 index 0271670..0000000 --- a/docs/test-output/machines/shoppingcart/states/active.md +++ /dev/null @@ -1,9 +0,0 @@ -# shoppingCart / active -Cart has items, customer can continue shopping or checkout - -Transitions: -- ADD_ITEM → active -- REMOVE_ITEM → active -- UPDATE_QUANTITY → active -- PROCEED_TO_CHECKOUT → checkout -- CLEAR_CART → empty diff --git a/docs/test-output/machines/shoppingcart/states/checkout-payment.md b/docs/test-output/machines/shoppingcart/states/checkout-payment.md deleted file mode 100644 index 138d897..0000000 --- a/docs/test-output/machines/shoppingcart/states/checkout-payment.md +++ /dev/null @@ -1,6 +0,0 @@ -# shoppingCart / checkout.payment -Collecting payment information - -Transitions: -- ADD_PAYMENT → processing -- RETURN_TO_CART → active diff --git a/docs/test-output/machines/shoppingcart/states/checkout-shipping.md b/docs/test-output/machines/shoppingcart/states/checkout-shipping.md deleted file mode 100644 index 97c921f..0000000 --- a/docs/test-output/machines/shoppingcart/states/checkout-shipping.md +++ /dev/null @@ -1,6 +0,0 @@ -# shoppingCart / checkout.shipping -Collecting shipping address - -Transitions: -- ADD_SHIPPING → payment -- RETURN_TO_CART → active diff --git a/docs/test-output/machines/shoppingcart/states/checkout.md b/docs/test-output/machines/shoppingcart/states/checkout.md deleted file mode 100644 index c77663d..0000000 --- a/docs/test-output/machines/shoppingcart/states/checkout.md +++ /dev/null @@ -1,4 +0,0 @@ -# shoppingCart / checkout -Customer is in checkout process - -Transitions: diff --git a/docs/test-output/machines/shoppingcart/states/completed.md b/docs/test-output/machines/shoppingcart/states/completed.md deleted file mode 100644 index d85d320..0000000 --- a/docs/test-output/machines/shoppingcart/states/completed.md +++ /dev/null @@ -1,4 +0,0 @@ -# shoppingCart / completed -Order successfully completed - -Transitions: diff --git a/docs/test-output/machines/shoppingcart/states/empty.md b/docs/test-output/machines/shoppingcart/states/empty.md deleted file mode 100644 index c373d18..0000000 --- a/docs/test-output/machines/shoppingcart/states/empty.md +++ /dev/null @@ -1,5 +0,0 @@ -# shoppingCart / empty -Cart is empty, awaiting first item - -Transitions: -- ADD_ITEM → active diff --git a/docs/test-output/machines/shoppingcart/states/failed.md b/docs/test-output/machines/shoppingcart/states/failed.md deleted file mode 100644 index 125ca9b..0000000 --- a/docs/test-output/machines/shoppingcart/states/failed.md +++ /dev/null @@ -1,6 +0,0 @@ -# shoppingCart / failed -Order processing failed - -Transitions: -- RETURN_TO_CART → active -- CLEAR_CART → empty diff --git a/docs/test-output/machines/shoppingcart/states/processing.md b/docs/test-output/machines/shoppingcart/states/processing.md deleted file mode 100644 index adb77e2..0000000 --- a/docs/test-output/machines/shoppingcart/states/processing.md +++ /dev/null @@ -1,6 +0,0 @@ -# shoppingCart / processing -Order is being validated and processed - -Transitions: -- PAYMENT_SUCCESS → completed -- PAYMENT_FAILED → failed diff --git a/runtime.ts b/runtime.ts index eaeebac..d12d874 100644 --- a/runtime.ts +++ b/runtime.ts @@ -6,6 +6,7 @@ export const isDeno = typeof globalThis !== "undefined" && "Deno" in globalThis; export interface FSLike { readFile(p: string): Promise; writeFile(p: string, data: string): Promise; + writeBinaryFile(p: string, data: Uint8Array): Promise; mkdirp(p: string): Promise; exists(p: string): Promise; } @@ -48,6 +49,7 @@ export async function loadAdapters(): Promise { const fs: FSLike = { readFile: p => Deno.readTextFile(p), writeFile: (p, d) => Deno.writeTextFile(p, d), + writeBinaryFile: (p, d) => Deno.writeFile(p, d), mkdirp: p => ensureDir(p), exists: p => Deno.stat(p).then(()=>true).catch(()=>false), }; @@ -71,6 +73,7 @@ export async function loadAdapters(): Promise { const fs: FSLike = { readFile: p => fsP.readFile(p, "utf8"), writeFile: (p, d) => fsP.writeFile(p, d, "utf8").then(()=>{}), + writeBinaryFile: (p, d) => fsP.writeFile(p, d).then(()=>{}), mkdirp: p => fsP.mkdir(p, { recursive: true }).then(() => {}), exists: p => fsP.stat(p).then(() => true).catch(() => false), }; diff --git a/src/fsm/example.machine.ts b/src/fsm/example.machine.ts new file mode 100644 index 0000000..9a85b16 --- /dev/null +++ b/src/fsm/example.machine.ts @@ -0,0 +1,36 @@ +/** + * Example state machine for testing + */ +export const trafficLightMachine = { + id: 'trafficLight', + initial: 'red', + states: { + red: { + description: 'Stop - vehicles must wait', + on: { + TIMER: { + target: 'green', + description: 'Light changes to green' + } + } + }, + yellow: { + description: 'Caution - prepare to stop', + on: { + TIMER: { + target: 'red', + description: 'Light changes to red' + } + } + }, + green: { + description: 'Go - vehicles may proceed', + on: { + TIMER: { + target: 'yellow', + description: 'Light changes to yellow' + } + } + } + } +}; diff --git a/src/generate.ts b/src/generate.ts index 66d2f5e..1af723c 100644 --- a/src/generate.ts +++ b/src/generate.ts @@ -41,6 +41,15 @@ export async function generateDocs(cfg: StateDocConfig, adapters: Adapters) { st.on.map((tr: { event: string; target: string }) => ` ${st.slug} --> ${tr.target}: ${tr.event}`) ) ]; - await adapters.fs.writeFile(adapters.join(mdir, "diagram.mmd"), lines.join("\n")); + const mermaidText = lines.join("\n"); + await adapters.fs.writeFile(adapters.join(mdir, "diagram.mmd"), mermaidText); + + // Export PNG if configured (feature not yet implemented, will be null) + if (cfg.visualization?.exportPng) { + const pngData = await adapters.mermaid.toPng(mermaidText); + if (pngData) { + await adapters.fs.writeBinaryFile(adapters.join(mdir, "diagram.png"), pngData); + } + } } } diff --git a/src/parser.ts b/src/parser.ts index 06eaf63..17f169e 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -81,7 +81,7 @@ function parseMachineObject(machineObj: any, varName: string): Machine { * Extract machine definitions from a JavaScript/TypeScript file * This is a runtime evaluation approach - we import the file and extract exported machines */ -async function extractMachinesFromFile(filePath: string, adapters: Adapters): Promise { +async function extractMachinesFromFile(filePath: string, _adapters: Adapters): Promise { try { // Convert to absolute file:// URL let importPath = filePath; diff --git a/src/tpl.ts b/src/tpl.ts index 854f26d..9ae5cc3 100644 --- a/src/tpl.ts +++ b/src/tpl.ts @@ -1,4 +1,4 @@ -import Handlebars from "npm:handlebars@^4.7.8"; +import Handlebars from "handlebars"; export function renderTemplate(tpl: string, data: object): string { const template = Handlebars.compile(tpl);