@synergenius/flow-weaver 0.9.0 → 0.9.2
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 +77 -67
- package/dist/cli/flow-weaver.mjs +87 -49
- package/dist/diagram/html-viewer.js +54 -18
- package/dist/diagram/renderer.js +5 -4
- package/dist/utils/port-ordering.d.ts +5 -5
- package/dist/utils/port-ordering.js +34 -41
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -4,36 +4,30 @@
|
|
|
4
4
|
[](./LICENSE)
|
|
5
5
|
[](https://nodejs.org)
|
|
6
6
|
|
|
7
|
-
**
|
|
7
|
+
**Build AI agent workflows visually. Ship them as your own code.**
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Design agent workflows in the Studio, in TypeScript, or let AI build them for you. Everything compiles to standalone functions you deploy anywhere, no runtime dependency on Flow Weaver.
|
|
10
10
|
|
|
11
|
-
##
|
|
11
|
+
## Three Ways to Build
|
|
12
12
|
|
|
13
|
-
**
|
|
13
|
+
**Studio.** Drag, drop, connect. The visual editor renders your workflow as an interactive graph with bidirectional sync: canvas changes write code, code changes update the canvas. 80+ plugins handle rendering, state, minimap, undo/redo, and more.
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
**TypeScript.** Annotate plain functions with JSDoc tags. The compiler turns them into executable workflow graphs with full type safety, IDE autocomplete, and compile-time validation. No YAML, no JSON configs.
|
|
16
16
|
|
|
17
|
-
The development loop (steps 1-4 are fully autonomous):
|
|
17
|
+
**AI Agents.** Connect Claude Code, Cursor, or OpenClaw and they scaffold, validate, and ship workflows using 30+ MCP tools. The agent reads validation errors, fixes issues, and re-validates until the workflow compiles clean. The development loop (steps 1-4 are fully autonomous):
|
|
18
18
|
|
|
19
|
-
1. **Agent creates**: scaffolds from templates
|
|
19
|
+
1. **Agent creates**: scaffolds from templates, builds from a model, or writes from scratch
|
|
20
20
|
2. **Compiler validates**: 15+ validation passes catch missing connections, type mismatches, unreachable paths
|
|
21
|
-
3. **Agent iterates**: validation errors include fix suggestions, the agent corrects and re-validates
|
|
21
|
+
3. **Agent iterates**: validation errors include fix suggestions, the agent corrects and re-validates
|
|
22
22
|
4. **Agent tests**: deterministic mock providers for reproducible testing without real API calls
|
|
23
|
-
5. **Human reviews**: visual editor
|
|
24
|
-
|
|
25
|
-
The compiled code is yours. No runtime lock-in, no framework dependency.
|
|
23
|
+
5. **Human reviews**: visual editor or SVG diagram for final approval
|
|
26
24
|
|
|
27
25
|
## Quick Start
|
|
28
26
|
|
|
29
|
-
### Install
|
|
30
|
-
|
|
31
27
|
```bash
|
|
32
28
|
npm install @synergenius/flow-weaver
|
|
33
29
|
```
|
|
34
30
|
|
|
35
|
-
### Define a workflow
|
|
36
|
-
|
|
37
31
|
Workflows are plain TypeScript. Annotations declare the graph structure:
|
|
38
32
|
|
|
39
33
|
```typescript
|
|
@@ -66,8 +60,6 @@ export async function dataPipeline(
|
|
|
66
60
|
}
|
|
67
61
|
```
|
|
68
62
|
|
|
69
|
-
### Compile and run
|
|
70
|
-
|
|
71
63
|
```bash
|
|
72
64
|
npx flow-weaver compile data-pipeline.ts # generates executable code in-place
|
|
73
65
|
npx flow-weaver run data-pipeline.ts --params '{"rawData": "Hello World"}'
|
|
@@ -77,26 +69,36 @@ The compiler fills in the function body while preserving your code outside the g
|
|
|
77
69
|
|
|
78
70
|
## AI-Native Development with MCP
|
|
79
71
|
|
|
80
|
-
Flow Weaver includes an MCP server
|
|
72
|
+
Flow Weaver includes an MCP server for Claude Code, Cursor, OpenClaw, or any MCP-compatible agent:
|
|
81
73
|
|
|
82
74
|
```bash
|
|
83
75
|
npx flow-weaver mcp-server # auto-registers with Claude Code
|
|
84
76
|
```
|
|
85
77
|
|
|
86
|
-
What an AI agent can do:
|
|
87
|
-
|
|
88
78
|
| Capability | MCP Tools |
|
|
89
79
|
|-----------|-----------|
|
|
90
80
|
| **Build** | `fw_scaffold`, `fw_modify`, `fw_modify_batch`, `fw_add_node`, `fw_connect` |
|
|
81
|
+
| **Model** | `fw_create_model`, `fw_workflow_status`, `fw_implement_node` |
|
|
91
82
|
| **Validate** | `fw_validate` (with friendly error hints), `fw_doctor` |
|
|
92
83
|
| **Understand** | `fw_describe` (json/text/mermaid), `fw_query` (10 query types), `fw_diff` |
|
|
93
84
|
| **Test** | `fw_execute_workflow` (with trace), `fw_compile` |
|
|
94
|
-
| **Visualize** | `fw_diagram` (SVG), `fw_get_state`, `fw_focus_node` |
|
|
85
|
+
| **Visualize** | `fw_diagram` (SVG/HTML), `fw_get_state`, `fw_focus_node` |
|
|
95
86
|
| **Deploy** | `fw_export` (Lambda, Vercel, Cloudflare, Inngest), `fw_compile --target inngest` |
|
|
96
87
|
| **Reuse** | `fw_list_patterns`, `fw_apply_pattern`, `fw_extract_pattern` |
|
|
97
88
|
| **Extend** | `fw_market_search`, `fw_market_install` |
|
|
98
89
|
|
|
99
|
-
|
|
90
|
+
## Model-Driven Workflows
|
|
91
|
+
|
|
92
|
+
Design first, implement later. Describe the workflow shape (nodes, ports, execution path) and the compiler generates a valid skeleton with `declare function` stubs:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
# Via MCP: fw_create_model with nodes, inputs/outputs, and execution path
|
|
96
|
+
# Via CLI:
|
|
97
|
+
npx flow-weaver status my-workflow.ts # shows stub vs implemented progress
|
|
98
|
+
npx flow-weaver implement my-workflow.ts processData # scaffolds a node body
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
The graph is valid before any node has a real implementation. Fill in node bodies incrementally, check `status` to track progress. Architecture and implementation stay separate: the architect defines the shape, developers fill in the logic.
|
|
100
102
|
|
|
101
103
|
## Agent Workflow Templates
|
|
102
104
|
|
|
@@ -140,14 +142,14 @@ npx flow-weaver create workflow ai-agent-durable durable-agent.ts
|
|
|
140
142
|
The validator understands AI agent patterns and enforces safety rules:
|
|
141
143
|
|
|
142
144
|
```
|
|
143
|
-
AGENT_LLM_MISSING_ERROR_HANDLER LLM node's onFailure is unconnected
|
|
144
|
-
AGENT_UNGUARDED_TOOL_EXECUTOR Tool executor has no human-approval upstream
|
|
145
|
-
AGENT_MISSING_MEMORY_IN_LOOP Agent loop has LLM but no memory
|
|
146
|
-
AGENT_LLM_NO_FALLBACK LLM failure goes directly to Exit
|
|
147
|
-
AGENT_TOOL_NO_OUTPUT_HANDLING Tool executor outputs are all unconnected
|
|
145
|
+
AGENT_LLM_MISSING_ERROR_HANDLER LLM node's onFailure is unconnected, add error handling
|
|
146
|
+
AGENT_UNGUARDED_TOOL_EXECUTOR Tool executor has no human-approval upstream, add a gate
|
|
147
|
+
AGENT_MISSING_MEMORY_IN_LOOP Agent loop has LLM but no memory, conversations will be stateless
|
|
148
|
+
AGENT_LLM_NO_FALLBACK LLM failure goes directly to Exit, add retry or fallback logic
|
|
149
|
+
AGENT_TOOL_NO_OUTPUT_HANDLING Tool executor outputs are all unconnected, results are discarded
|
|
148
150
|
```
|
|
149
151
|
|
|
150
|
-
|
|
152
|
+
These aren't generic lint rules. The validator identifies LLM, tool-executor, human-approval, and memory nodes by port signatures, annotations, and naming patterns, then applies agent-specific checks.
|
|
151
153
|
|
|
152
154
|
## Deterministic Agent Testing
|
|
153
155
|
|
|
@@ -190,13 +192,30 @@ Most workflow engines either ban loops (DAG-only) or allow arbitrary cycles (har
|
|
|
190
192
|
*/
|
|
191
193
|
```
|
|
192
194
|
|
|
193
|
-
The scope's output ports become callback parameters, and input ports become return values.
|
|
194
|
-
- Agent reasoning loops (LLM -> tools -> memory -> LLM)
|
|
195
|
-
- ForEach over collections
|
|
196
|
-
- Map/reduce patterns
|
|
197
|
-
- Nested sub-workflows
|
|
195
|
+
The scope's output ports become callback parameters, and input ports become return values. Agent reasoning loops, ForEach over collections, map/reduce patterns, nested sub-workflows: all work while keeping the graph acyclic and statically analyzable.
|
|
198
196
|
|
|
199
|
-
|
|
197
|
+
## Diagram Generation
|
|
198
|
+
|
|
199
|
+
Generate SVG or interactive HTML diagrams from any workflow:
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
flow-weaver diagram workflow.ts -o workflow.svg --theme dark
|
|
203
|
+
flow-weaver diagram workflow.ts -o workflow.html --format html
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Customize node appearance with annotations:
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
/**
|
|
210
|
+
* @flowWeaver nodeType
|
|
211
|
+
* @color blue
|
|
212
|
+
* @icon database
|
|
213
|
+
*/
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Named colors: `blue`, `purple`, `green`, `cyan`, `orange`, `pink`, `red`, `yellow`. Icons include `api`, `database`, `shield`, `brain`, `cloud`, `search`, `code`, and 60+ more from Material Design 3.
|
|
217
|
+
|
|
218
|
+
The interactive HTML viewer supports zoom/pan, click-to-inspect nodes, port-level hover with connection tracing, and works standalone or embedded in an iframe.
|
|
200
219
|
|
|
201
220
|
## Multi-Target Compilation
|
|
202
221
|
|
|
@@ -220,23 +239,6 @@ flow-weaver serve ./workflows --port 3000 --swagger
|
|
|
220
239
|
|
|
221
240
|
Both the default TypeScript target and Inngest target parallelize independent nodes with `Promise.all()`. Inngest additionally wraps each node in `step.run()` for individual durability and generates typed Zod event schemas.
|
|
222
241
|
|
|
223
|
-
## Visual Human-in-the-Loop
|
|
224
|
-
|
|
225
|
-
Workflows compile from code, but humans review them visually:
|
|
226
|
-
|
|
227
|
-
```bash
|
|
228
|
-
# Generate SVG diagram
|
|
229
|
-
flow-weaver diagram workflow.ts -o workflow.svg --theme dark
|
|
230
|
-
|
|
231
|
-
# Describe structure for quick review
|
|
232
|
-
flow-weaver describe workflow.ts --format text
|
|
233
|
-
|
|
234
|
-
# Semantic diff between versions
|
|
235
|
-
flow-weaver diff workflow-v1.ts workflow-v2.ts
|
|
236
|
-
```
|
|
237
|
-
|
|
238
|
-
Flow Weaver Studio is a visual editor with bidirectional sync: code changes update the canvas, canvas changes update the code. 80+ plugins handle rendering, state, minimap, undo/redo, and more.
|
|
239
|
-
|
|
240
242
|
## API
|
|
241
243
|
|
|
242
244
|
```typescript
|
|
@@ -263,8 +265,16 @@ const code = generateCode(ast);
|
|
|
263
265
|
|-------------|---------|
|
|
264
266
|
| `@synergenius/flow-weaver` | Parse, validate, compile, generate, AST types, builders, diff, patterns |
|
|
265
267
|
| `@synergenius/flow-weaver/runtime` | Execution context, errors, function registry for generated code |
|
|
266
|
-
| `@synergenius/flow-weaver/
|
|
267
|
-
| `@synergenius/flow-weaver/
|
|
268
|
+
| `@synergenius/flow-weaver/testing` | Mock LLM/approval providers, recording/replay, assertions, token tracking |
|
|
269
|
+
| `@synergenius/flow-weaver/built-in-nodes` | delay, waitForEvent, waitForAgent, invokeWorkflow |
|
|
270
|
+
| `@synergenius/flow-weaver/diagram` | SVG/HTML diagram layout and rendering |
|
|
271
|
+
| `@synergenius/flow-weaver/ast` | AST types and utilities |
|
|
272
|
+
| `@synergenius/flow-weaver/api` | Programmatic workflow manipulation API |
|
|
273
|
+
| `@synergenius/flow-weaver/diff` | Semantic workflow diffing |
|
|
274
|
+
| `@synergenius/flow-weaver/deployment` | Multi-target deployment generators |
|
|
275
|
+
| `@synergenius/flow-weaver/marketplace` | Marketplace package utilities |
|
|
276
|
+
| `@synergenius/flow-weaver/editor` | Editor completions and suggestions |
|
|
277
|
+
| `@synergenius/flow-weaver/browser` | JSDoc port sync for browser environments |
|
|
268
278
|
| `@synergenius/flow-weaver/describe` | Programmatic workflow description |
|
|
269
279
|
| `@synergenius/flow-weaver/doc-metadata` | Documentation metadata extractors |
|
|
270
280
|
|
|
@@ -276,11 +286,16 @@ flow-weaver compile <file> # Compile to TypeScript or Inngest
|
|
|
276
286
|
flow-weaver validate <file> # Validate without compiling
|
|
277
287
|
flow-weaver run <file> # Execute a workflow
|
|
278
288
|
flow-weaver dev <file> # Watch + compile + run
|
|
289
|
+
flow-weaver strip <file> # Remove generated code sections
|
|
279
290
|
flow-weaver describe <file> # Structure output (json/text/mermaid)
|
|
280
|
-
flow-weaver diagram <file> # Generate SVG diagram
|
|
291
|
+
flow-weaver diagram <file> # Generate SVG or interactive HTML diagram
|
|
281
292
|
flow-weaver diff <f1> <f2> # Semantic workflow comparison
|
|
282
293
|
|
|
283
|
-
#
|
|
294
|
+
# Model-driven
|
|
295
|
+
flow-weaver status <file> # Show stub vs implemented progress
|
|
296
|
+
flow-weaver implement <file> <node> # Scaffold a node body from its stub
|
|
297
|
+
|
|
298
|
+
# Scaffolding
|
|
284
299
|
flow-weaver init [directory] # Create new project
|
|
285
300
|
flow-weaver create workflow <t> <f> # Scaffold from template
|
|
286
301
|
flow-weaver create node <name> <f> # Scaffold node type
|
|
@@ -307,10 +322,16 @@ flow-weaver docs search <query> # Search documentation
|
|
|
307
322
|
flow-weaver market search [query] # Search npm for packages
|
|
308
323
|
flow-weaver market install <pkg> # Install a package
|
|
309
324
|
flow-weaver market list # List installed packages
|
|
325
|
+
flow-weaver market init <name> # Scaffold a marketplace package
|
|
326
|
+
flow-weaver market pack # Validate and generate manifest
|
|
327
|
+
flow-weaver market publish # Publish to npm
|
|
310
328
|
|
|
311
|
-
# IDE
|
|
329
|
+
# Editor / IDE
|
|
312
330
|
flow-weaver mcp-server # Start MCP server for Claude Code
|
|
313
331
|
flow-weaver listen # Stream editor events
|
|
332
|
+
flow-weaver changelog # Generate changelog from git history
|
|
333
|
+
flow-weaver migrate <glob> # Run migration transforms on workflow files
|
|
334
|
+
flow-weaver plugin init <name> # Scaffold an external plugin
|
|
314
335
|
```
|
|
315
336
|
|
|
316
337
|
## Built-in Nodes
|
|
@@ -339,17 +360,6 @@ function nodeName(
|
|
|
339
360
|
|
|
340
361
|
Expression nodes (`@expression`) skip the control flow boilerplate. Inputs and outputs map directly to the TypeScript signature.
|
|
341
362
|
|
|
342
|
-
## Marketplace
|
|
343
|
-
|
|
344
|
-
Distribute node types, workflows, and patterns as npm packages:
|
|
345
|
-
|
|
346
|
-
```bash
|
|
347
|
-
flow-weaver market init my-nodes # Scaffold a package
|
|
348
|
-
flow-weaver market pack # Validate and generate manifest
|
|
349
|
-
flow-weaver market publish # Publish to npm
|
|
350
|
-
flow-weaver market install flowweaver-pack-openai # Install
|
|
351
|
-
```
|
|
352
|
-
|
|
353
363
|
## Testing
|
|
354
364
|
|
|
355
365
|
```bash
|
package/dist/cli/flow-weaver.mjs
CHANGED
|
@@ -44825,38 +44825,39 @@ function assignImplicitPortOrders(ports) {
|
|
|
44825
44825
|
scopeGroups.get(scopeKey).push([portName, portDef]);
|
|
44826
44826
|
}
|
|
44827
44827
|
for (const [scope, portsInScope] of scopeGroups.entries()) {
|
|
44828
|
+
let nextSlot2 = function(from) {
|
|
44829
|
+
while (occupied.has(from)) from++;
|
|
44830
|
+
occupied.add(from);
|
|
44831
|
+
return from;
|
|
44832
|
+
};
|
|
44833
|
+
var nextSlot = nextSlot2;
|
|
44828
44834
|
const isScoped = scope !== void 0;
|
|
44829
|
-
const
|
|
44830
|
-
const
|
|
44831
|
-
let minRegularExplicitOrder = Infinity;
|
|
44832
|
-
for (const [, portDef] of regularPorts) {
|
|
44835
|
+
const occupied = /* @__PURE__ */ new Set();
|
|
44836
|
+
for (const [, portDef] of portsInScope) {
|
|
44833
44837
|
const order = portDef.metadata?.order;
|
|
44834
44838
|
if (typeof order === "number") {
|
|
44835
|
-
|
|
44839
|
+
occupied.add(order);
|
|
44836
44840
|
}
|
|
44837
44841
|
}
|
|
44838
|
-
|
|
44839
|
-
|
|
44840
|
-
|
|
44841
|
-
|
|
44842
|
-
|
|
44843
|
-
|
|
44844
|
-
|
|
44845
|
-
|
|
44846
|
-
|
|
44847
|
-
|
|
44848
|
-
|
|
44849
|
-
|
|
44850
|
-
}
|
|
44842
|
+
const mandatoryNeedOrder = portsInScope.filter(
|
|
44843
|
+
([name, def]) => isMandatoryPort(name, isScoped) && def.metadata?.order === void 0
|
|
44844
|
+
);
|
|
44845
|
+
const regularNeedOrder = portsInScope.filter(
|
|
44846
|
+
([name, def]) => !isMandatoryPort(name, isScoped) && def.metadata?.order === void 0
|
|
44847
|
+
);
|
|
44848
|
+
let slot = -mandatoryNeedOrder.length;
|
|
44849
|
+
for (const [, portDef] of mandatoryNeedOrder) {
|
|
44850
|
+
if (!portDef.metadata) portDef.metadata = {};
|
|
44851
|
+
slot = nextSlot2(slot);
|
|
44852
|
+
portDef.metadata.order = slot;
|
|
44853
|
+
slot++;
|
|
44851
44854
|
}
|
|
44852
|
-
|
|
44853
|
-
for (const [, portDef] of
|
|
44854
|
-
if (portDef.metadata
|
|
44855
|
-
|
|
44856
|
-
|
|
44857
|
-
|
|
44858
|
-
portDef.metadata.order = currentRegularOrder++;
|
|
44859
|
-
}
|
|
44855
|
+
slot = Math.max(slot, 0);
|
|
44856
|
+
for (const [, portDef] of regularNeedOrder) {
|
|
44857
|
+
if (!portDef.metadata) portDef.metadata = {};
|
|
44858
|
+
slot = nextSlot2(slot);
|
|
44859
|
+
portDef.metadata.order = slot;
|
|
44860
|
+
slot++;
|
|
44860
44861
|
}
|
|
44861
44862
|
}
|
|
44862
44863
|
}
|
|
@@ -60592,7 +60593,7 @@ function renderSVG(graph, options = {}) {
|
|
|
60592
60593
|
function renderConnection(parts2, conn, gradIndex) {
|
|
60593
60594
|
const dashAttr = conn.isStepConnection ? "" : ' stroke-dasharray="8 4"';
|
|
60594
60595
|
parts2.push(
|
|
60595
|
-
` <path d="${conn.path}" fill="none" stroke="url(#conn-grad-${gradIndex})" stroke-width="3"${dashAttr} stroke-linecap="round" data-source="${escapeXml(conn.fromNode)}.${escapeXml(conn.fromPort)}" data-target="${escapeXml(conn.toNode)}.${escapeXml(conn.toPort)}"/>`
|
|
60596
|
+
` <path d="${conn.path}" fill="none" stroke="url(#conn-grad-${gradIndex})" stroke-width="3"${dashAttr} stroke-linecap="round" data-source="${escapeXml(conn.fromNode)}.${escapeXml(conn.fromPort)}:output" data-target="${escapeXml(conn.toNode)}.${escapeXml(conn.toPort)}:input"/>`
|
|
60596
60597
|
);
|
|
60597
60598
|
}
|
|
60598
60599
|
function renderScopeConnection(parts2, conn, allConnections) {
|
|
@@ -60600,7 +60601,7 @@ function renderScopeConnection(parts2, conn, allConnections) {
|
|
|
60600
60601
|
if (gradIndex < 0) return;
|
|
60601
60602
|
const dashAttr = conn.isStepConnection ? "" : ' stroke-dasharray="8 4"';
|
|
60602
60603
|
parts2.push(
|
|
60603
|
-
` <path d="${conn.path}" fill="none" stroke="url(#conn-grad-${gradIndex})" stroke-width="2.5"${dashAttr} stroke-linecap="round" data-source="${escapeXml(conn.fromNode)}.${escapeXml(conn.fromPort)}" data-target="${escapeXml(conn.toNode)}.${escapeXml(conn.toPort)}"/>`
|
|
60604
|
+
` <path d="${conn.path}" fill="none" stroke="url(#conn-grad-${gradIndex})" stroke-width="2.5"${dashAttr} stroke-linecap="round" data-source="${escapeXml(conn.fromNode)}.${escapeXml(conn.fromPort)}:output" data-target="${escapeXml(conn.toNode)}.${escapeXml(conn.toPort)}:input"/>`
|
|
60604
60605
|
);
|
|
60605
60606
|
}
|
|
60606
60607
|
function renderNodeBody(parts2, node, theme, indent) {
|
|
@@ -60681,7 +60682,7 @@ function renderPortDots(parts2, nodeId, inputs, outputs, themeName) {
|
|
|
60681
60682
|
const color = getPortColor(port.dataType, port.isFailure, themeName);
|
|
60682
60683
|
const ringColor = getPortRingColor(port.dataType, port.isFailure, themeName);
|
|
60683
60684
|
const dir = port.direction === "INPUT" ? "input" : "output";
|
|
60684
|
-
parts2.push(` <circle cx="${port.cx}" cy="${port.cy}" r="${PORT_RADIUS}" fill="${color}" stroke="${ringColor}" stroke-width="2" data-port-id="${escapeXml(nodeId)}.${escapeXml(port.name)}" data-direction="${dir}"/>`);
|
|
60685
|
+
parts2.push(` <circle cx="${port.cx}" cy="${port.cy}" r="${PORT_RADIUS}" fill="${color}" stroke="${ringColor}" stroke-width="2" data-port-id="${escapeXml(nodeId)}.${escapeXml(port.name)}:${dir}" data-direction="${dir}"/>`);
|
|
60685
60686
|
}
|
|
60686
60687
|
}
|
|
60687
60688
|
function renderPortLabels(parts2, nodeId, inputs, outputs, theme, themeName) {
|
|
@@ -60689,7 +60690,8 @@ function renderPortLabels(parts2, nodeId, inputs, outputs, theme, themeName) {
|
|
|
60689
60690
|
const color = getPortColor(port.dataType, port.isFailure, themeName);
|
|
60690
60691
|
const isInput = port.direction === "INPUT";
|
|
60691
60692
|
const abbrev = TYPE_ABBREVIATIONS[port.dataType] ?? port.dataType;
|
|
60692
|
-
const
|
|
60693
|
+
const dir = isInput ? "input" : "output";
|
|
60694
|
+
const portId = `${escapeXml(nodeId)}.${escapeXml(port.name)}:${dir}`;
|
|
60693
60695
|
const portLabel = port.label;
|
|
60694
60696
|
const typeWidth = measureText(abbrev);
|
|
60695
60697
|
const labelWidth = measureText(portLabel);
|
|
@@ -60983,43 +60985,79 @@ body.node-active .connections path.dimmed { opacity: 0.15; }
|
|
|
60983
60985
|
else if (e.key === 'Escape') deselectNode();
|
|
60984
60986
|
});
|
|
60985
60987
|
|
|
60986
|
-
// ---- Port label visibility
|
|
60987
|
-
var
|
|
60988
|
-
|
|
60988
|
+
// ---- Port label visibility ----
|
|
60989
|
+
var labelMap = {};
|
|
60990
|
+
content.querySelectorAll('.labels g[data-port-label]').forEach(function(lbl) {
|
|
60991
|
+
labelMap[lbl.getAttribute('data-port-label')] = lbl;
|
|
60992
|
+
});
|
|
60989
60993
|
|
|
60990
|
-
|
|
60991
|
-
|
|
60992
|
-
|
|
60993
|
-
|
|
60994
|
-
|
|
60995
|
-
|
|
60996
|
-
|
|
60994
|
+
// Build adjacency: portId \u2192 array of connected portIds
|
|
60995
|
+
var portConnections = {};
|
|
60996
|
+
content.querySelectorAll('.connections path').forEach(function(p) {
|
|
60997
|
+
var src = p.getAttribute('data-source');
|
|
60998
|
+
var tgt = p.getAttribute('data-target');
|
|
60999
|
+
if (!src || !tgt) return;
|
|
61000
|
+
if (!portConnections[src]) portConnections[src] = [];
|
|
61001
|
+
if (!portConnections[tgt]) portConnections[tgt] = [];
|
|
61002
|
+
portConnections[src].push(tgt);
|
|
61003
|
+
portConnections[tgt].push(src);
|
|
61004
|
+
});
|
|
61005
|
+
|
|
61006
|
+
var allLabelIds = Object.keys(labelMap);
|
|
61007
|
+
var hoveredPort = null;
|
|
61008
|
+
|
|
61009
|
+
function showLabel(id) { var l = labelMap[id]; if (l) { l.style.opacity = '1'; l.style.pointerEvents = 'auto'; } }
|
|
61010
|
+
function hideLabel(id) { var l = labelMap[id]; if (l) { l.style.opacity = '0'; l.style.pointerEvents = 'none'; } }
|
|
61011
|
+
|
|
61012
|
+
function showLabelsFor(nodeId) {
|
|
61013
|
+
allLabelIds.forEach(function(id) {
|
|
61014
|
+
if (id.indexOf(nodeId + '.') === 0) showLabel(id);
|
|
60997
61015
|
});
|
|
60998
61016
|
}
|
|
60999
|
-
function hideLabelsFor(
|
|
61000
|
-
|
|
61001
|
-
|
|
61002
|
-
if (portId.indexOf(id + '.') === 0) {
|
|
61003
|
-
lbl.style.opacity = '0';
|
|
61004
|
-
lbl.style.pointerEvents = 'none';
|
|
61005
|
-
}
|
|
61017
|
+
function hideLabelsFor(nodeId) {
|
|
61018
|
+
allLabelIds.forEach(function(id) {
|
|
61019
|
+
if (id.indexOf(nodeId + '.') === 0) hideLabel(id);
|
|
61006
61020
|
});
|
|
61007
61021
|
}
|
|
61008
61022
|
|
|
61023
|
+
// Node hover: show all port labels for the hovered node
|
|
61024
|
+
var nodeEls = content.querySelectorAll('.nodes g[data-node-id]');
|
|
61009
61025
|
nodeEls.forEach(function(nodeG) {
|
|
61010
61026
|
var nodeId = nodeG.getAttribute('data-node-id');
|
|
61011
61027
|
var parentNodeG = nodeG.parentElement ? nodeG.parentElement.closest('g[data-node-id]') : null;
|
|
61012
61028
|
var parentId = parentNodeG ? parentNodeG.getAttribute('data-node-id') : null;
|
|
61013
61029
|
nodeG.addEventListener('mouseenter', function() {
|
|
61030
|
+
if (hoveredPort) return; // port hover takes priority
|
|
61014
61031
|
if (parentId) hideLabelsFor(parentId);
|
|
61015
61032
|
showLabelsFor(nodeId);
|
|
61016
61033
|
});
|
|
61017
61034
|
nodeG.addEventListener('mouseleave', function() {
|
|
61035
|
+
if (hoveredPort) return;
|
|
61018
61036
|
hideLabelsFor(nodeId);
|
|
61019
61037
|
if (parentId) showLabelsFor(parentId);
|
|
61020
61038
|
});
|
|
61021
61039
|
});
|
|
61022
61040
|
|
|
61041
|
+
// Port hover: show this port's label + all connected port labels
|
|
61042
|
+
content.querySelectorAll('[data-port-id]').forEach(function(portEl) {
|
|
61043
|
+
var portId = portEl.getAttribute('data-port-id');
|
|
61044
|
+
var nodeId = portId.split('.')[0];
|
|
61045
|
+
var peers = (portConnections[portId] || []).concat(portId);
|
|
61046
|
+
|
|
61047
|
+
portEl.addEventListener('mouseenter', function() {
|
|
61048
|
+
hoveredPort = portId;
|
|
61049
|
+
// Hide all labels for this node first, then show only the relevant ones
|
|
61050
|
+
hideLabelsFor(nodeId);
|
|
61051
|
+
peers.forEach(showLabel);
|
|
61052
|
+
});
|
|
61053
|
+
portEl.addEventListener('mouseleave', function() {
|
|
61054
|
+
hoveredPort = null;
|
|
61055
|
+
peers.forEach(hideLabel);
|
|
61056
|
+
// Restore all labels for the node since we're still inside it
|
|
61057
|
+
showLabelsFor(nodeId);
|
|
61058
|
+
});
|
|
61059
|
+
});
|
|
61060
|
+
|
|
61023
61061
|
// ---- Click to inspect node ----
|
|
61024
61062
|
function deselectNode() {
|
|
61025
61063
|
selectedNodeId = null;
|
|
@@ -61046,7 +61084,7 @@ body.node-active .connections path.dimmed { opacity: 0.15; }
|
|
|
61046
61084
|
ports.forEach(function(p) {
|
|
61047
61085
|
var id = p.getAttribute('data-port-id');
|
|
61048
61086
|
var dir = p.getAttribute('data-direction');
|
|
61049
|
-
var name = id.split('.').slice(1).join('.');
|
|
61087
|
+
var name = id.split('.').slice(1).join('.').replace(/:(?:input|output)$/, '');
|
|
61050
61088
|
if (dir === 'input') inputs.push(name);
|
|
61051
61089
|
else outputs.push(name);
|
|
61052
61090
|
});
|
|
@@ -95710,7 +95748,7 @@ function displayInstalledPackage(pkg) {
|
|
|
95710
95748
|
}
|
|
95711
95749
|
|
|
95712
95750
|
// src/cli/index.ts
|
|
95713
|
-
var version2 = true ? "0.9.
|
|
95751
|
+
var version2 = true ? "0.9.2" : "0.0.0-dev";
|
|
95714
95752
|
var program2 = new Command();
|
|
95715
95753
|
program2.name("flow-weaver").description("Flow Weaver Annotations - Compile and validate workflow files").version(version2, "-v, --version", "Output the current version");
|
|
95716
95754
|
program2.configureOutput({
|
|
@@ -266,43 +266,79 @@ body.node-active .connections path.dimmed { opacity: 0.15; }
|
|
|
266
266
|
else if (e.key === 'Escape') deselectNode();
|
|
267
267
|
});
|
|
268
268
|
|
|
269
|
-
// ---- Port label visibility
|
|
270
|
-
var
|
|
271
|
-
|
|
269
|
+
// ---- Port label visibility ----
|
|
270
|
+
var labelMap = {};
|
|
271
|
+
content.querySelectorAll('.labels g[data-port-label]').forEach(function(lbl) {
|
|
272
|
+
labelMap[lbl.getAttribute('data-port-label')] = lbl;
|
|
273
|
+
});
|
|
272
274
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
275
|
+
// Build adjacency: portId → array of connected portIds
|
|
276
|
+
var portConnections = {};
|
|
277
|
+
content.querySelectorAll('.connections path').forEach(function(p) {
|
|
278
|
+
var src = p.getAttribute('data-source');
|
|
279
|
+
var tgt = p.getAttribute('data-target');
|
|
280
|
+
if (!src || !tgt) return;
|
|
281
|
+
if (!portConnections[src]) portConnections[src] = [];
|
|
282
|
+
if (!portConnections[tgt]) portConnections[tgt] = [];
|
|
283
|
+
portConnections[src].push(tgt);
|
|
284
|
+
portConnections[tgt].push(src);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
var allLabelIds = Object.keys(labelMap);
|
|
288
|
+
var hoveredPort = null;
|
|
289
|
+
|
|
290
|
+
function showLabel(id) { var l = labelMap[id]; if (l) { l.style.opacity = '1'; l.style.pointerEvents = 'auto'; } }
|
|
291
|
+
function hideLabel(id) { var l = labelMap[id]; if (l) { l.style.opacity = '0'; l.style.pointerEvents = 'none'; } }
|
|
292
|
+
|
|
293
|
+
function showLabelsFor(nodeId) {
|
|
294
|
+
allLabelIds.forEach(function(id) {
|
|
295
|
+
if (id.indexOf(nodeId + '.') === 0) showLabel(id);
|
|
280
296
|
});
|
|
281
297
|
}
|
|
282
|
-
function hideLabelsFor(
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
if (portId.indexOf(id + '.') === 0) {
|
|
286
|
-
lbl.style.opacity = '0';
|
|
287
|
-
lbl.style.pointerEvents = 'none';
|
|
288
|
-
}
|
|
298
|
+
function hideLabelsFor(nodeId) {
|
|
299
|
+
allLabelIds.forEach(function(id) {
|
|
300
|
+
if (id.indexOf(nodeId + '.') === 0) hideLabel(id);
|
|
289
301
|
});
|
|
290
302
|
}
|
|
291
303
|
|
|
304
|
+
// Node hover: show all port labels for the hovered node
|
|
305
|
+
var nodeEls = content.querySelectorAll('.nodes g[data-node-id]');
|
|
292
306
|
nodeEls.forEach(function(nodeG) {
|
|
293
307
|
var nodeId = nodeG.getAttribute('data-node-id');
|
|
294
308
|
var parentNodeG = nodeG.parentElement ? nodeG.parentElement.closest('g[data-node-id]') : null;
|
|
295
309
|
var parentId = parentNodeG ? parentNodeG.getAttribute('data-node-id') : null;
|
|
296
310
|
nodeG.addEventListener('mouseenter', function() {
|
|
311
|
+
if (hoveredPort) return; // port hover takes priority
|
|
297
312
|
if (parentId) hideLabelsFor(parentId);
|
|
298
313
|
showLabelsFor(nodeId);
|
|
299
314
|
});
|
|
300
315
|
nodeG.addEventListener('mouseleave', function() {
|
|
316
|
+
if (hoveredPort) return;
|
|
301
317
|
hideLabelsFor(nodeId);
|
|
302
318
|
if (parentId) showLabelsFor(parentId);
|
|
303
319
|
});
|
|
304
320
|
});
|
|
305
321
|
|
|
322
|
+
// Port hover: show this port's label + all connected port labels
|
|
323
|
+
content.querySelectorAll('[data-port-id]').forEach(function(portEl) {
|
|
324
|
+
var portId = portEl.getAttribute('data-port-id');
|
|
325
|
+
var nodeId = portId.split('.')[0];
|
|
326
|
+
var peers = (portConnections[portId] || []).concat(portId);
|
|
327
|
+
|
|
328
|
+
portEl.addEventListener('mouseenter', function() {
|
|
329
|
+
hoveredPort = portId;
|
|
330
|
+
// Hide all labels for this node first, then show only the relevant ones
|
|
331
|
+
hideLabelsFor(nodeId);
|
|
332
|
+
peers.forEach(showLabel);
|
|
333
|
+
});
|
|
334
|
+
portEl.addEventListener('mouseleave', function() {
|
|
335
|
+
hoveredPort = null;
|
|
336
|
+
peers.forEach(hideLabel);
|
|
337
|
+
// Restore all labels for the node since we're still inside it
|
|
338
|
+
showLabelsFor(nodeId);
|
|
339
|
+
});
|
|
340
|
+
});
|
|
341
|
+
|
|
306
342
|
// ---- Click to inspect node ----
|
|
307
343
|
function deselectNode() {
|
|
308
344
|
selectedNodeId = null;
|
|
@@ -329,7 +365,7 @@ body.node-active .connections path.dimmed { opacity: 0.15; }
|
|
|
329
365
|
ports.forEach(function(p) {
|
|
330
366
|
var id = p.getAttribute('data-port-id');
|
|
331
367
|
var dir = p.getAttribute('data-direction');
|
|
332
|
-
var name = id.split('.').slice(1).join('.');
|
|
368
|
+
var name = id.split('.').slice(1).join('.').replace(/:(?:input|output)$/, '');
|
|
333
369
|
if (dir === 'input') inputs.push(name);
|
|
334
370
|
else outputs.push(name);
|
|
335
371
|
});
|
package/dist/diagram/renderer.js
CHANGED
|
@@ -90,14 +90,14 @@ export function renderSVG(graph, options = {}) {
|
|
|
90
90
|
// ---- Connection rendering ----
|
|
91
91
|
function renderConnection(parts, conn, gradIndex) {
|
|
92
92
|
const dashAttr = conn.isStepConnection ? '' : ' stroke-dasharray="8 4"';
|
|
93
|
-
parts.push(` <path d="${conn.path}" fill="none" stroke="url(#conn-grad-${gradIndex})" stroke-width="3"${dashAttr} stroke-linecap="round" data-source="${escapeXml(conn.fromNode)}.${escapeXml(conn.fromPort)}" data-target="${escapeXml(conn.toNode)}.${escapeXml(conn.toPort)}"/>`);
|
|
93
|
+
parts.push(` <path d="${conn.path}" fill="none" stroke="url(#conn-grad-${gradIndex})" stroke-width="3"${dashAttr} stroke-linecap="round" data-source="${escapeXml(conn.fromNode)}.${escapeXml(conn.fromPort)}:output" data-target="${escapeXml(conn.toNode)}.${escapeXml(conn.toPort)}:input"/>`);
|
|
94
94
|
}
|
|
95
95
|
function renderScopeConnection(parts, conn, allConnections) {
|
|
96
96
|
const gradIndex = allConnections.indexOf(conn);
|
|
97
97
|
if (gradIndex < 0)
|
|
98
98
|
return;
|
|
99
99
|
const dashAttr = conn.isStepConnection ? '' : ' stroke-dasharray="8 4"';
|
|
100
|
-
parts.push(` <path d="${conn.path}" fill="none" stroke="url(#conn-grad-${gradIndex})" stroke-width="2.5"${dashAttr} stroke-linecap="round" data-source="${escapeXml(conn.fromNode)}.${escapeXml(conn.fromPort)}" data-target="${escapeXml(conn.toNode)}.${escapeXml(conn.toPort)}"/>`);
|
|
100
|
+
parts.push(` <path d="${conn.path}" fill="none" stroke="url(#conn-grad-${gradIndex})" stroke-width="2.5"${dashAttr} stroke-linecap="round" data-source="${escapeXml(conn.fromNode)}.${escapeXml(conn.fromPort)}:output" data-target="${escapeXml(conn.toNode)}.${escapeXml(conn.toPort)}:input"/>`);
|
|
101
101
|
}
|
|
102
102
|
// ---- Node rendering ----
|
|
103
103
|
/** Render node body rect + icon */
|
|
@@ -183,7 +183,7 @@ function renderPortDots(parts, nodeId, inputs, outputs, themeName) {
|
|
|
183
183
|
const color = getPortColor(port.dataType, port.isFailure, themeName);
|
|
184
184
|
const ringColor = getPortRingColor(port.dataType, port.isFailure, themeName);
|
|
185
185
|
const dir = port.direction === 'INPUT' ? 'input' : 'output';
|
|
186
|
-
parts.push(` <circle cx="${port.cx}" cy="${port.cy}" r="${PORT_RADIUS}" fill="${color}" stroke="${ringColor}" stroke-width="2" data-port-id="${escapeXml(nodeId)}.${escapeXml(port.name)}" data-direction="${dir}"/>`);
|
|
186
|
+
parts.push(` <circle cx="${port.cx}" cy="${port.cy}" r="${PORT_RADIUS}" fill="${color}" stroke="${ringColor}" stroke-width="2" data-port-id="${escapeXml(nodeId)}.${escapeXml(port.name)}:${dir}" data-direction="${dir}"/>`);
|
|
187
187
|
}
|
|
188
188
|
}
|
|
189
189
|
/** Render only port label badges (no dots) */
|
|
@@ -192,7 +192,8 @@ function renderPortLabels(parts, nodeId, inputs, outputs, theme, themeName) {
|
|
|
192
192
|
const color = getPortColor(port.dataType, port.isFailure, themeName);
|
|
193
193
|
const isInput = port.direction === 'INPUT';
|
|
194
194
|
const abbrev = TYPE_ABBREVIATIONS[port.dataType] ?? port.dataType;
|
|
195
|
-
const
|
|
195
|
+
const dir = isInput ? 'input' : 'output';
|
|
196
|
+
const portId = `${escapeXml(nodeId)}.${escapeXml(port.name)}:${dir}`;
|
|
196
197
|
const portLabel = port.label;
|
|
197
198
|
const typeWidth = measureText(abbrev);
|
|
198
199
|
const labelWidth = measureText(portLabel);
|
|
@@ -14,11 +14,11 @@ export declare function isMandatoryPort(portName: string, isScoped: boolean): bo
|
|
|
14
14
|
*
|
|
15
15
|
* Rules:
|
|
16
16
|
* 1. Ports are grouped by scope (undefined = external, string = scoped)
|
|
17
|
-
* 2.
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
17
|
+
* 2. Explicit order metadata is always preserved
|
|
18
|
+
* 3. Mandatory ports without explicit orders get negative slots (-N, ..., -1)
|
|
19
|
+
* so they always sort before any user-specified [order:0] data port
|
|
20
|
+
* 4. Regular ports without explicit orders fill non-negative slots (0+),
|
|
21
|
+
* skipping any slots already occupied by explicit orders
|
|
22
22
|
*
|
|
23
23
|
* @param ports - Record of port definitions to process (mutated in place)
|
|
24
24
|
*/
|
|
@@ -19,11 +19,11 @@ export function isMandatoryPort(portName, isScoped) {
|
|
|
19
19
|
*
|
|
20
20
|
* Rules:
|
|
21
21
|
* 1. Ports are grouped by scope (undefined = external, string = scoped)
|
|
22
|
-
* 2.
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
22
|
+
* 2. Explicit order metadata is always preserved
|
|
23
|
+
* 3. Mandatory ports without explicit orders get negative slots (-N, ..., -1)
|
|
24
|
+
* so they always sort before any user-specified [order:0] data port
|
|
25
|
+
* 4. Regular ports without explicit orders fill non-negative slots (0+),
|
|
26
|
+
* skipping any slots already occupied by explicit orders
|
|
27
27
|
*
|
|
28
28
|
* @param ports - Record of port definitions to process (mutated in place)
|
|
29
29
|
*/
|
|
@@ -40,48 +40,41 @@ export function assignImplicitPortOrders(ports) {
|
|
|
40
40
|
// Process each scope group independently
|
|
41
41
|
for (const [scope, portsInScope] of scopeGroups.entries()) {
|
|
42
42
|
const isScoped = scope !== undefined;
|
|
43
|
-
//
|
|
44
|
-
const
|
|
45
|
-
const
|
|
46
|
-
// Find minimum explicit order among regular ports (if any)
|
|
47
|
-
let minRegularExplicitOrder = Infinity;
|
|
48
|
-
for (const [, portDef] of regularPorts) {
|
|
43
|
+
// Collect all explicitly occupied order slots
|
|
44
|
+
const occupied = new Set();
|
|
45
|
+
for (const [, portDef] of portsInScope) {
|
|
49
46
|
const order = portDef.metadata?.order;
|
|
50
47
|
if (typeof order === "number") {
|
|
51
|
-
|
|
48
|
+
occupied.add(order);
|
|
52
49
|
}
|
|
53
50
|
}
|
|
54
|
-
//
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const regularPortsWithOrder0 = regularPorts.filter(([, p]) => p.metadata?.order === 0);
|
|
61
|
-
mandatoryStartOrder = regularPortsWithOrder0.length;
|
|
51
|
+
// Helper: find next available slot starting from `from`
|
|
52
|
+
function nextSlot(from) {
|
|
53
|
+
while (occupied.has(from))
|
|
54
|
+
from++;
|
|
55
|
+
occupied.add(from);
|
|
56
|
+
return from;
|
|
62
57
|
}
|
|
63
|
-
//
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
|
|
58
|
+
// Separate mandatory from regular ports (only those needing implicit orders)
|
|
59
|
+
const mandatoryNeedOrder = portsInScope.filter(([name, def]) => isMandatoryPort(name, isScoped) && def.metadata?.order === undefined);
|
|
60
|
+
const regularNeedOrder = portsInScope.filter(([name, def]) => !isMandatoryPort(name, isScoped) && def.metadata?.order === undefined);
|
|
61
|
+
// Mandatory ports fill negative slots so they always sort before [order:0] data ports
|
|
62
|
+
let slot = -mandatoryNeedOrder.length;
|
|
63
|
+
for (const [, portDef] of mandatoryNeedOrder) {
|
|
64
|
+
if (!portDef.metadata)
|
|
65
|
+
portDef.metadata = {};
|
|
66
|
+
slot = nextSlot(slot);
|
|
67
|
+
portDef.metadata.order = slot;
|
|
68
|
+
slot++;
|
|
73
69
|
}
|
|
74
|
-
//
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
83
|
-
portDef.metadata.order = currentRegularOrder++;
|
|
84
|
-
}
|
|
70
|
+
// Regular ports fill non-negative slots, skipping occupied ones
|
|
71
|
+
slot = Math.max(slot, 0);
|
|
72
|
+
for (const [, portDef] of regularNeedOrder) {
|
|
73
|
+
if (!portDef.metadata)
|
|
74
|
+
portDef.metadata = {};
|
|
75
|
+
slot = nextSlot(slot);
|
|
76
|
+
portDef.metadata.order = slot;
|
|
77
|
+
slot++;
|
|
85
78
|
}
|
|
86
79
|
}
|
|
87
80
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@synergenius/flow-weaver",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.2",
|
|
4
4
|
"description": "Deterministic workflow compiler for AI agents. Compiles to standalone TypeScript, no runtime dependencies.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
@@ -66,6 +66,10 @@
|
|
|
66
66
|
"./marketplace": {
|
|
67
67
|
"types": "./dist/marketplace/index.d.ts",
|
|
68
68
|
"default": "./dist/marketplace/index.js"
|
|
69
|
+
},
|
|
70
|
+
"./testing": {
|
|
71
|
+
"types": "./dist/testing/index.d.ts",
|
|
72
|
+
"default": "./dist/testing/index.js"
|
|
69
73
|
}
|
|
70
74
|
},
|
|
71
75
|
"bin": {
|