@synergenius/flow-weaver 0.10.3 → 0.10.5
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/dist/cli/flow-weaver.mjs +217 -21
- package/dist/diagram/html-viewer.d.ts +8 -0
- package/dist/diagram/html-viewer.js +170 -7
- package/dist/diagram/index.js +48 -12
- package/dist/friendly-errors.js +10 -10
- package/package.json +1 -1
package/dist/cli/flow-weaver.mjs
CHANGED
|
@@ -53974,7 +53974,7 @@ function buildCoerceSuggestion(quoted, targetType) {
|
|
|
53974
53974
|
if (portRefs.length < 2) return null;
|
|
53975
53975
|
const coerceType = COERCE_TARGET_TYPES[targetType.toUpperCase()] || targetType.toLowerCase();
|
|
53976
53976
|
if (!["string", "number", "boolean", "json", "object"].includes(coerceType)) return null;
|
|
53977
|
-
return `@
|
|
53977
|
+
return `@connect ${portRefs[0]} -> ${portRefs[1]} as ${coerceType}`;
|
|
53978
53978
|
}
|
|
53979
53979
|
function extractCyclePath(message) {
|
|
53980
53980
|
const match2 = message.match(/:\s*(.+ -> .+)/);
|
|
@@ -54083,7 +54083,7 @@ var errorMappers = {
|
|
|
54083
54083
|
return {
|
|
54084
54084
|
title: "Type Mismatch",
|
|
54085
54085
|
explanation: `Type mismatch: you're connecting a ${source} to a ${target}. The value will be automatically converted, but this might cause unexpected behavior.`,
|
|
54086
|
-
fix: coerceSuggestion ? `
|
|
54086
|
+
fix: coerceSuggestion ? `Use \`${coerceSuggestion}\` for explicit coercion, change one of the port types, or use @strictTypes to turn this into an error.` : `Add \`as <type>\` to the @connect annotation (e.g. \`as string\`), change one of the port types, or use @strictTypes to turn this into an error.`,
|
|
54087
54087
|
code: error2.code
|
|
54088
54088
|
};
|
|
54089
54089
|
},
|
|
@@ -54182,7 +54182,7 @@ var errorMappers = {
|
|
|
54182
54182
|
return {
|
|
54183
54183
|
title: "Type Incompatible",
|
|
54184
54184
|
explanation: `Type mismatch: ${source} to ${target}. With @strictTypes enabled, this is an error instead of a warning.`,
|
|
54185
|
-
fix: coerceSuggestion ? `
|
|
54185
|
+
fix: coerceSuggestion ? `Use \`${coerceSuggestion}\` for explicit coercion, change one of the port types, or remove @strictTypes to allow implicit coercions.` : `Add \`as <type>\` to the @connect annotation (e.g. \`as number\`), change one of the port types, or remove @strictTypes to allow implicit coercions.`,
|
|
54186
54186
|
code: error2.code
|
|
54187
54187
|
};
|
|
54188
54188
|
},
|
|
@@ -54195,7 +54195,7 @@ var errorMappers = {
|
|
|
54195
54195
|
return {
|
|
54196
54196
|
title: "Unusual Type Coercion",
|
|
54197
54197
|
explanation: `Converting ${source} to ${target} is technically valid but semantically unusual and may produce unexpected behavior.`,
|
|
54198
|
-
fix: coerceSuggestion ? `If intentional,
|
|
54198
|
+
fix: coerceSuggestion ? `If intentional, use \`${coerceSuggestion}\` for explicit coercion, or use @strictTypes to enforce type safety.` : `If intentional, add \`as <type>\` to the @connect annotation, or use @strictTypes to enforce type safety.`,
|
|
54199
54199
|
code: error2.code
|
|
54200
54200
|
};
|
|
54201
54201
|
},
|
|
@@ -54464,7 +54464,7 @@ var errorMappers = {
|
|
|
54464
54464
|
return {
|
|
54465
54465
|
title: "Lossy Type Conversion",
|
|
54466
54466
|
explanation: `Converting ${source} to ${target} may lose data or produce unexpected results (e.g., NaN, truncation).`,
|
|
54467
|
-
fix: coerceSuggestion ? `
|
|
54467
|
+
fix: coerceSuggestion ? `Use \`${coerceSuggestion}\` for explicit coercion, or use @strictTypes to enforce type safety.` : `Add \`as <type>\` to the @connect annotation for explicit conversion, or use @strictTypes to enforce type safety.`,
|
|
54468
54468
|
code: error2.code
|
|
54469
54469
|
};
|
|
54470
54470
|
},
|
|
@@ -61588,24 +61588,46 @@ path[data-source].port-hover { opacity: 1; }
|
|
|
61588
61588
|
/* Info panel */
|
|
61589
61589
|
#info-panel {
|
|
61590
61590
|
position: fixed; bottom: 52px; left: 16px;
|
|
61591
|
-
max-width:
|
|
61591
|
+
max-width: 480px; min-width: 260px;
|
|
61592
61592
|
background: ${surfaceMain}; border: 1px solid ${borderSubtle};
|
|
61593
61593
|
border-radius: 8px; padding: 12px 16px;
|
|
61594
61594
|
font-size: 13px; line-height: 1.5;
|
|
61595
61595
|
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
|
|
61596
61596
|
z-index: 10; display: none;
|
|
61597
|
+
max-height: calc(100vh - 120px); overflow-y: auto;
|
|
61597
61598
|
}
|
|
61598
61599
|
#info-panel.visible { display: block; }
|
|
61599
61600
|
#info-panel h3 {
|
|
61600
61601
|
font-size: 14px; font-weight: 700; margin-bottom: 6px;
|
|
61601
61602
|
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
61602
61603
|
}
|
|
61604
|
+
#info-panel .node-desc { color: ${textMed}; font-size: 12px; margin-bottom: 8px; font-style: italic; }
|
|
61603
61605
|
#info-panel .info-section { margin-bottom: 6px; }
|
|
61604
61606
|
#info-panel .info-label { font-size: 11px; font-weight: 600; color: ${textLow}; text-transform: uppercase; letter-spacing: 0.5px; }
|
|
61605
61607
|
#info-panel .info-value { color: ${textMed}; }
|
|
61606
61608
|
#info-panel .port-list { list-style: none; padding: 0; }
|
|
61607
61609
|
#info-panel .port-list li { padding: 1px 0; }
|
|
61608
61610
|
#info-panel .port-list li::before { content: '\\2022'; margin-right: 6px; color: ${textLow}; }
|
|
61611
|
+
#info-panel .port-type { color: ${textLow}; font-size: 11px; margin-left: 4px; font-family: 'SF Mono', 'Fira Code', 'Consolas', monospace; }
|
|
61612
|
+
#info-panel pre {
|
|
61613
|
+
background: ${isDark ? "#161625" : "#f0f1fa"}; border: 1px solid ${borderSubtle};
|
|
61614
|
+
border-radius: 6px; padding: 10px; overflow-x: auto;
|
|
61615
|
+
font-family: 'SF Mono', 'Fira Code', 'Consolas', monospace;
|
|
61616
|
+
font-size: 12px; line-height: 1.6; white-space: pre;
|
|
61617
|
+
max-height: 300px; overflow-y: auto; margin: 6px 0 0;
|
|
61618
|
+
color: ${isDark ? "#e6edf3" : "#1a2340"};
|
|
61619
|
+
}
|
|
61620
|
+
.hl-kw { color: ${isDark ? "#8e9eff" : "#4040bf"}; }
|
|
61621
|
+
.hl-str { color: ${isDark ? "#ff7b72" : "#c4432b"}; }
|
|
61622
|
+
.hl-num { color: ${isDark ? "#f0a050" : "#b35e14"}; }
|
|
61623
|
+
.hl-cm { color: #6a737d; font-style: italic; }
|
|
61624
|
+
.hl-fn { color: ${isDark ? "#d2a8ff" : "#7c3aed"}; }
|
|
61625
|
+
.hl-ty { color: ${isDark ? "#d2a8ff" : "#7c3aed"}; }
|
|
61626
|
+
.hl-pn { color: ${isDark ? "#b8bdd0" : "#4a5578"}; }
|
|
61627
|
+
.hl-ann { color: ${isDark ? "#8e9eff" : "#4040bf"}; font-weight: 600; font-style: normal; }
|
|
61628
|
+
.hl-arr { color: ${isDark ? "#79c0ff" : "#0969da"}; font-weight: 600; font-style: normal; }
|
|
61629
|
+
.hl-id { color: ${isDark ? "#e6edf3" : "#1a2340"}; font-style: normal; }
|
|
61630
|
+
.hl-sc { color: ${isDark ? "#d2a8ff" : "#7c3aed"}; font-style: italic; }
|
|
61609
61631
|
|
|
61610
61632
|
/* Branding badge */
|
|
61611
61633
|
#branding {
|
|
@@ -61660,8 +61682,8 @@ path[data-source].port-hover { opacity: 1; }
|
|
|
61660
61682
|
<circle cx="10" cy="10" r="1.5" fill="${dotColor}" opacity="0.6"/>
|
|
61661
61683
|
</pattern>
|
|
61662
61684
|
</defs>
|
|
61663
|
-
<rect x="-100000" y="-100000" width="200000" height="200000" fill="${bg}"/>
|
|
61664
|
-
<rect x="-100000" y="-100000" width="200000" height="200000" fill="url(#viewer-dots)"/>
|
|
61685
|
+
<rect x="-100000" y="-100000" width="200000" height="200000" fill="${bg}" pointer-events="none"/>
|
|
61686
|
+
<rect x="-100000" y="-100000" width="200000" height="200000" fill="url(#viewer-dots)" pointer-events="none"/>
|
|
61665
61687
|
<g id="diagram">${inner}</g>
|
|
61666
61688
|
</svg>
|
|
61667
61689
|
<div id="controls">
|
|
@@ -61691,6 +61713,7 @@ path[data-source].port-hover { opacity: 1; }
|
|
|
61691
61713
|
</a>
|
|
61692
61714
|
<div id="scroll-hint">Use <kbd id="mod-key">Ctrl</kbd> + scroll to zoom</div>
|
|
61693
61715
|
<div id="studio-hint">Like rearranging? <a href="https://flowweaver.ai" target="_blank" rel="noopener">Flow Weaver Studio</a> saves your layouts.</div>
|
|
61716
|
+
<script>var nodeSources = ${JSON.stringify(options.nodeSources ?? {})};</script>
|
|
61694
61717
|
<script>
|
|
61695
61718
|
(function() {
|
|
61696
61719
|
'use strict';
|
|
@@ -61775,6 +61798,7 @@ path[data-source].port-hover { opacity: 1; }
|
|
|
61775
61798
|
|
|
61776
61799
|
// ---- Pan (drag) + Node drag ----
|
|
61777
61800
|
var draggedNodeId = null, dragNodeStart = null, didDragNode = false;
|
|
61801
|
+
var clickTarget = null; // stash the real target before setPointerCapture steals it
|
|
61778
61802
|
var dragCount = 0, nudgeIndex = 0, nudgeTimer = null;
|
|
61779
61803
|
var nudgeMessages = [
|
|
61780
61804
|
'Like rearranging? <a href="https://flowweaver.ai" target="_blank" rel="noopener">Flow Weaver Studio</a> saves your layouts.',
|
|
@@ -61789,6 +61813,8 @@ path[data-source].port-hover { opacity: 1; }
|
|
|
61789
61813
|
|
|
61790
61814
|
canvas.addEventListener('pointerdown', function(e) {
|
|
61791
61815
|
if (e.button !== 0) return;
|
|
61816
|
+
clickTarget = e.target; // stash before setPointerCapture redirects events
|
|
61817
|
+
didDrag = false;
|
|
61792
61818
|
// Check if clicking on a node body (walk up to detect data-node-id)
|
|
61793
61819
|
var t = e.target;
|
|
61794
61820
|
while (t && t !== canvas) {
|
|
@@ -61805,7 +61831,6 @@ path[data-source].port-hover { opacity: 1; }
|
|
|
61805
61831
|
}
|
|
61806
61832
|
// Canvas pan
|
|
61807
61833
|
pointerDown = true;
|
|
61808
|
-
didDrag = false;
|
|
61809
61834
|
dragLast = { x: e.clientX, y: e.clientY };
|
|
61810
61835
|
canvas.setPointerCapture(e.pointerId);
|
|
61811
61836
|
});
|
|
@@ -62258,14 +62283,28 @@ path[data-source].port-hover { opacity: 1; }
|
|
|
62258
62283
|
// Build info panel
|
|
62259
62284
|
infoTitle.textContent = labelText;
|
|
62260
62285
|
var html = '';
|
|
62286
|
+
var src = nodeSources[nodeId];
|
|
62287
|
+
if (src && src.description) {
|
|
62288
|
+
html += '<div class="node-desc">' + escapeH(src.description) + '</div>';
|
|
62289
|
+
}
|
|
62290
|
+
var portInfo = (src && src.ports) ? src.ports : {};
|
|
62291
|
+
function portLabel(name) {
|
|
62292
|
+
var p = portInfo[name];
|
|
62293
|
+
var label = escapeH(name);
|
|
62294
|
+
if (p) {
|
|
62295
|
+
var typeStr = p.tsType || p.type;
|
|
62296
|
+
if (typeStr) label += ' <span class="port-type">' + escapeH(typeStr) + '</span>';
|
|
62297
|
+
}
|
|
62298
|
+
return label;
|
|
62299
|
+
}
|
|
62261
62300
|
if (inputs.length) {
|
|
62262
62301
|
html += '<div class="info-section"><div class="info-label">Inputs</div><ul class="port-list">';
|
|
62263
|
-
inputs.forEach(function(n) { html += '<li>' +
|
|
62302
|
+
inputs.forEach(function(n) { html += '<li>' + portLabel(n) + '</li>'; });
|
|
62264
62303
|
html += '</ul></div>';
|
|
62265
62304
|
}
|
|
62266
62305
|
if (outputs.length) {
|
|
62267
62306
|
html += '<div class="info-section"><div class="info-label">Outputs</div><ul class="port-list">';
|
|
62268
|
-
outputs.forEach(function(n) { html += '<li>' +
|
|
62307
|
+
outputs.forEach(function(n) { html += '<li>' + portLabel(n) + '</li>'; });
|
|
62269
62308
|
html += '</ul></div>';
|
|
62270
62309
|
}
|
|
62271
62310
|
if (connectedNodes.size) {
|
|
@@ -62273,6 +62312,10 @@ path[data-source].port-hover { opacity: 1; }
|
|
|
62273
62312
|
html += Array.from(connectedNodes).map(escapeH).join(', ');
|
|
62274
62313
|
html += '</div></div>';
|
|
62275
62314
|
}
|
|
62315
|
+
if (src && src.source) {
|
|
62316
|
+
html += '<div class="info-section"><div class="info-label">Source</div>';
|
|
62317
|
+
html += '<pre><code>' + highlightTS(src.source) + '</code></pre></div>';
|
|
62318
|
+
}
|
|
62276
62319
|
infoBody.innerHTML = html;
|
|
62277
62320
|
infoPanel.classList.add('visible');
|
|
62278
62321
|
}
|
|
@@ -62281,10 +62324,130 @@ path[data-source].port-hover { opacity: 1; }
|
|
|
62281
62324
|
return s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
|
|
62282
62325
|
}
|
|
62283
62326
|
|
|
62327
|
+
var fwAnnotations = 'flowWeaver,input,output,step,node,connect,param,returns,fwImport,label,scope,position,color,icon,tag,map,path,name,description,expression,executeWhen,pullExecution,strictTypes,autoConnect,port,trigger,cancelOn,retries,timeout,throttle';
|
|
62328
|
+
|
|
62329
|
+
function highlightJSDoc(block) {
|
|
62330
|
+
var annSet = fwAnnotations.split(',');
|
|
62331
|
+
var out = '';
|
|
62332
|
+
var re = /(@[a-zA-Z]+)|(->)|(\\.[a-zA-Z_][a-zA-Z0-9_]*)|("[^"]*")|('\\''[^'\\'']*'\\'')|(-?[0-9]+(?:\\.[0-9]+)?)|([a-zA-Z_][a-zA-Z0-9_]*)|([^@a-zA-Z0-9"'\\-.]+)/g;
|
|
62333
|
+
var m;
|
|
62334
|
+
while ((m = re.exec(block)) !== null) {
|
|
62335
|
+
if (m[1]) {
|
|
62336
|
+
var tag = m[1].slice(1);
|
|
62337
|
+
if (annSet.indexOf(tag) >= 0) {
|
|
62338
|
+
out += '<span class="hl-ann">' + m[0] + '</span>';
|
|
62339
|
+
} else {
|
|
62340
|
+
out += '<span class="hl-ann">' + m[0] + '</span>';
|
|
62341
|
+
}
|
|
62342
|
+
} else if (m[2]) {
|
|
62343
|
+
out += '<span class="hl-arr">' + m[2] + '</span>';
|
|
62344
|
+
} else if (m[3]) {
|
|
62345
|
+
// .portName scope reference
|
|
62346
|
+
out += '<span class="hl-sc">' + m[3] + '</span>';
|
|
62347
|
+
} else if (m[4] || m[5]) {
|
|
62348
|
+
out += '<span class="hl-str">' + (m[4] || m[5]) + '</span>';
|
|
62349
|
+
} else if (m[6]) {
|
|
62350
|
+
out += '<span class="hl-num">' + m[6] + '</span>';
|
|
62351
|
+
} else if (m[7]) {
|
|
62352
|
+
var tys = 'string,number,boolean,any,void,never,unknown,STEP,STRING,NUMBER,BOOLEAN,ARRAY,OBJECT,FUNCTION,ANY';
|
|
62353
|
+
if (tys.split(',').indexOf(m[7]) >= 0) {
|
|
62354
|
+
out += '<span class="hl-ty">' + m[7] + '</span>';
|
|
62355
|
+
} else {
|
|
62356
|
+
out += '<span class="hl-id">' + m[7] + '</span>';
|
|
62357
|
+
}
|
|
62358
|
+
} else {
|
|
62359
|
+
out += m[0];
|
|
62360
|
+
}
|
|
62361
|
+
}
|
|
62362
|
+
return out;
|
|
62363
|
+
}
|
|
62364
|
+
|
|
62365
|
+
function highlightTS(code) {
|
|
62366
|
+
var tokens = [];
|
|
62367
|
+
var i = 0;
|
|
62368
|
+
while (i < code.length) {
|
|
62369
|
+
// Line comments
|
|
62370
|
+
if (code[i] === '/' && code[i+1] === '/') {
|
|
62371
|
+
var end = code.indexOf('\\n', i);
|
|
62372
|
+
if (end === -1) end = code.length;
|
|
62373
|
+
tokens.push({ t: 'cm', v: code.slice(i, end) });
|
|
62374
|
+
i = end;
|
|
62375
|
+
continue;
|
|
62376
|
+
}
|
|
62377
|
+
// Block comments (detect JSDoc for annotation highlighting)
|
|
62378
|
+
if (code[i] === '/' && code[i+1] === '*') {
|
|
62379
|
+
var end = code.indexOf('*/', i + 2);
|
|
62380
|
+
if (end === -1) end = code.length; else end += 2;
|
|
62381
|
+
var block = code.slice(i, end);
|
|
62382
|
+
var hasFW = /@(flowWeaver|input|output|step|node|connect|param|returns)/.test(block);
|
|
62383
|
+
if (hasFW) {
|
|
62384
|
+
tokens.push({ t: 'jsdoc', v: block });
|
|
62385
|
+
} else {
|
|
62386
|
+
tokens.push({ t: 'cm', v: block });
|
|
62387
|
+
}
|
|
62388
|
+
i = end;
|
|
62389
|
+
continue;
|
|
62390
|
+
}
|
|
62391
|
+
// Strings
|
|
62392
|
+
if (code[i] === "'" || code[i] === '"' || code[i] === '\`') {
|
|
62393
|
+
var q = code[i], j = i + 1;
|
|
62394
|
+
while (j < code.length && code[j] !== q) { if (code[j] === '\\\\') j++; j++; }
|
|
62395
|
+
tokens.push({ t: 'str', v: code.slice(i, j + 1) });
|
|
62396
|
+
i = j + 1;
|
|
62397
|
+
continue;
|
|
62398
|
+
}
|
|
62399
|
+
// Numbers
|
|
62400
|
+
if (/[0-9]/.test(code[i]) && (i === 0 || /[^a-zA-Z_$]/.test(code[i-1]))) {
|
|
62401
|
+
var j = i;
|
|
62402
|
+
while (j < code.length && /[0-9a-fA-FxX._]/.test(code[j])) j++;
|
|
62403
|
+
tokens.push({ t: 'num', v: code.slice(i, j) });
|
|
62404
|
+
i = j;
|
|
62405
|
+
continue;
|
|
62406
|
+
}
|
|
62407
|
+
// Words
|
|
62408
|
+
if (/[a-zA-Z_$]/.test(code[i])) {
|
|
62409
|
+
var j = i;
|
|
62410
|
+
while (j < code.length && /[a-zA-Z0-9_$]/.test(code[j])) j++;
|
|
62411
|
+
var w = code.slice(i, j);
|
|
62412
|
+
var kws = 'async,await,break,case,catch,class,const,continue,default,delete,do,else,export,extends,finally,for,from,function,if,import,in,instanceof,let,new,of,return,switch,throw,try,typeof,var,void,while,yield';
|
|
62413
|
+
var tys = 'string,number,boolean,any,void,never,unknown,null,undefined,true,false,Promise,Record,Map,Set,Array,Partial,Required,Omit,Pick';
|
|
62414
|
+
if (kws.split(',').indexOf(w) >= 0) {
|
|
62415
|
+
tokens.push({ t: 'kw', v: w });
|
|
62416
|
+
} else if (tys.split(',').indexOf(w) >= 0) {
|
|
62417
|
+
tokens.push({ t: 'ty', v: w });
|
|
62418
|
+
} else if (j < code.length && code[j] === '(') {
|
|
62419
|
+
tokens.push({ t: 'fn', v: w });
|
|
62420
|
+
} else {
|
|
62421
|
+
tokens.push({ t: '', v: w });
|
|
62422
|
+
}
|
|
62423
|
+
i = j;
|
|
62424
|
+
continue;
|
|
62425
|
+
}
|
|
62426
|
+
// Punctuation
|
|
62427
|
+
if (/[{}()\\[\\];:.,<>=!&|?+\\-*/%^~@]/.test(code[i])) {
|
|
62428
|
+
tokens.push({ t: 'pn', v: code[i] });
|
|
62429
|
+
i++;
|
|
62430
|
+
continue;
|
|
62431
|
+
}
|
|
62432
|
+
// Whitespace and other
|
|
62433
|
+
tokens.push({ t: '', v: code[i] });
|
|
62434
|
+
i++;
|
|
62435
|
+
}
|
|
62436
|
+
return tokens.map(function(tk) {
|
|
62437
|
+
if (tk.t === 'jsdoc') {
|
|
62438
|
+
return '<span class="hl-cm">' + highlightJSDoc(escapeH(tk.v)) + '</span>';
|
|
62439
|
+
}
|
|
62440
|
+
var v = escapeH(tk.v);
|
|
62441
|
+
return tk.t ? '<span class="hl-' + tk.t + '">' + v + '</span>' : v;
|
|
62442
|
+
}).join('');
|
|
62443
|
+
}
|
|
62444
|
+
|
|
62284
62445
|
// Delegate click: port click > node click > background
|
|
62446
|
+
// Use clickTarget (stashed from pointerdown) because setPointerCapture redirects click to canvas
|
|
62285
62447
|
canvas.addEventListener('click', function(e) {
|
|
62286
62448
|
if (didDrag || didDragNode) { didDragNode = false; return; }
|
|
62287
|
-
var target = e.target;
|
|
62449
|
+
var target = clickTarget || e.target;
|
|
62450
|
+
clickTarget = null;
|
|
62288
62451
|
while (target && target !== canvas) {
|
|
62289
62452
|
if (target.hasAttribute && target.hasAttribute('data-port-id')) {
|
|
62290
62453
|
e.stopPropagation();
|
|
@@ -62325,24 +62488,57 @@ function fileToSVG(filePath, options = {}) {
|
|
|
62325
62488
|
return pickAndRender(result.workflows, options);
|
|
62326
62489
|
}
|
|
62327
62490
|
function fileToHTML(filePath, options = {}) {
|
|
62328
|
-
const
|
|
62329
|
-
|
|
62491
|
+
const result = parser.parse(filePath);
|
|
62492
|
+
const ast = pickWorkflow(result.workflows, options);
|
|
62493
|
+
const svg = workflowToSVG(ast, options);
|
|
62494
|
+
return wrapSVGInHTML(svg, { title: options.workflowName ?? ast.name, theme: options.theme, nodeSources: buildNodeSourceMap(ast) });
|
|
62330
62495
|
}
|
|
62331
|
-
function
|
|
62496
|
+
function buildNodeSourceMap(ast) {
|
|
62497
|
+
const typeMap = new Map(ast.nodeTypes.map((nt) => [nt.functionName, nt]));
|
|
62498
|
+
const map3 = {};
|
|
62499
|
+
for (const inst of ast.instances) {
|
|
62500
|
+
const nt = typeMap.get(inst.nodeType);
|
|
62501
|
+
if (!nt) continue;
|
|
62502
|
+
const ports = {};
|
|
62503
|
+
for (const [name, def] of Object.entries(nt.inputs ?? {})) {
|
|
62504
|
+
ports[name] = { type: def.dataType, tsType: def.tsType };
|
|
62505
|
+
}
|
|
62506
|
+
for (const [name, def] of Object.entries(nt.outputs ?? {})) {
|
|
62507
|
+
ports[name] = { type: def.dataType, tsType: def.tsType };
|
|
62508
|
+
}
|
|
62509
|
+
map3[inst.id] = { description: nt.description, source: nt.functionText, ports };
|
|
62510
|
+
}
|
|
62511
|
+
const startPorts = {};
|
|
62512
|
+
for (const [name, def] of Object.entries(ast.startPorts ?? {})) {
|
|
62513
|
+
startPorts[name] = { type: def.dataType, tsType: def.tsType };
|
|
62514
|
+
}
|
|
62515
|
+
if (Object.keys(startPorts).length) {
|
|
62516
|
+
map3["Start"] = { description: ast.description, ports: startPorts };
|
|
62517
|
+
}
|
|
62518
|
+
const exitPorts = {};
|
|
62519
|
+
for (const [name, def] of Object.entries(ast.exitPorts ?? {})) {
|
|
62520
|
+
exitPorts[name] = { type: def.dataType, tsType: def.tsType };
|
|
62521
|
+
}
|
|
62522
|
+
if (Object.keys(exitPorts).length) {
|
|
62523
|
+
map3["Exit"] = { ports: exitPorts };
|
|
62524
|
+
}
|
|
62525
|
+
return map3;
|
|
62526
|
+
}
|
|
62527
|
+
function pickWorkflow(workflows, options) {
|
|
62332
62528
|
if (workflows.length === 0) {
|
|
62333
62529
|
throw new Error("No workflows found in source code");
|
|
62334
62530
|
}
|
|
62335
|
-
let workflow;
|
|
62336
62531
|
if (options.workflowName) {
|
|
62337
62532
|
const found = workflows.find((w) => w.name === options.workflowName);
|
|
62338
62533
|
if (!found) {
|
|
62339
62534
|
throw new Error(`Workflow "${options.workflowName}" not found. Available: ${workflows.map((w) => w.name).join(", ")}`);
|
|
62340
62535
|
}
|
|
62341
|
-
|
|
62342
|
-
} else {
|
|
62343
|
-
workflow = workflows[0];
|
|
62536
|
+
return found;
|
|
62344
62537
|
}
|
|
62345
|
-
return
|
|
62538
|
+
return workflows[0];
|
|
62539
|
+
}
|
|
62540
|
+
function pickAndRender(workflows, options) {
|
|
62541
|
+
return workflowToSVG(pickWorkflow(workflows, options), options);
|
|
62346
62542
|
}
|
|
62347
62543
|
|
|
62348
62544
|
// src/cli/commands/diagram.ts
|
|
@@ -96900,7 +97096,7 @@ function displayInstalledPackage(pkg) {
|
|
|
96900
97096
|
}
|
|
96901
97097
|
|
|
96902
97098
|
// src/cli/index.ts
|
|
96903
|
-
var version2 = true ? "0.10.
|
|
97099
|
+
var version2 = true ? "0.10.5" : "0.0.0-dev";
|
|
96904
97100
|
var program2 = new Command();
|
|
96905
97101
|
program2.name("flow-weaver").description("Flow Weaver Annotations - Compile and validate workflow files").version(version2, "-v, --version", "Output the current version");
|
|
96906
97102
|
program2.configureOutput({
|
|
@@ -8,6 +8,14 @@
|
|
|
8
8
|
export interface HtmlViewerOptions {
|
|
9
9
|
title?: string;
|
|
10
10
|
theme?: 'dark' | 'light';
|
|
11
|
+
nodeSources?: Record<string, {
|
|
12
|
+
description?: string;
|
|
13
|
+
source?: string;
|
|
14
|
+
ports?: Record<string, {
|
|
15
|
+
type: string;
|
|
16
|
+
tsType?: string;
|
|
17
|
+
}>;
|
|
18
|
+
}>;
|
|
11
19
|
}
|
|
12
20
|
export declare function wrapSVGInHTML(svgContent: string, options?: HtmlViewerOptions): string;
|
|
13
21
|
//# sourceMappingURL=html-viewer.d.ts.map
|
|
@@ -122,24 +122,46 @@ path[data-source].port-hover { opacity: 1; }
|
|
|
122
122
|
/* Info panel */
|
|
123
123
|
#info-panel {
|
|
124
124
|
position: fixed; bottom: 52px; left: 16px;
|
|
125
|
-
max-width:
|
|
125
|
+
max-width: 480px; min-width: 260px;
|
|
126
126
|
background: ${surfaceMain}; border: 1px solid ${borderSubtle};
|
|
127
127
|
border-radius: 8px; padding: 12px 16px;
|
|
128
128
|
font-size: 13px; line-height: 1.5;
|
|
129
129
|
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
|
|
130
130
|
z-index: 10; display: none;
|
|
131
|
+
max-height: calc(100vh - 120px); overflow-y: auto;
|
|
131
132
|
}
|
|
132
133
|
#info-panel.visible { display: block; }
|
|
133
134
|
#info-panel h3 {
|
|
134
135
|
font-size: 14px; font-weight: 700; margin-bottom: 6px;
|
|
135
136
|
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
136
137
|
}
|
|
138
|
+
#info-panel .node-desc { color: ${textMed}; font-size: 12px; margin-bottom: 8px; font-style: italic; }
|
|
137
139
|
#info-panel .info-section { margin-bottom: 6px; }
|
|
138
140
|
#info-panel .info-label { font-size: 11px; font-weight: 600; color: ${textLow}; text-transform: uppercase; letter-spacing: 0.5px; }
|
|
139
141
|
#info-panel .info-value { color: ${textMed}; }
|
|
140
142
|
#info-panel .port-list { list-style: none; padding: 0; }
|
|
141
143
|
#info-panel .port-list li { padding: 1px 0; }
|
|
142
144
|
#info-panel .port-list li::before { content: '\\2022'; margin-right: 6px; color: ${textLow}; }
|
|
145
|
+
#info-panel .port-type { color: ${textLow}; font-size: 11px; margin-left: 4px; font-family: 'SF Mono', 'Fira Code', 'Consolas', monospace; }
|
|
146
|
+
#info-panel pre {
|
|
147
|
+
background: ${isDark ? '#161625' : '#f0f1fa'}; border: 1px solid ${borderSubtle};
|
|
148
|
+
border-radius: 6px; padding: 10px; overflow-x: auto;
|
|
149
|
+
font-family: 'SF Mono', 'Fira Code', 'Consolas', monospace;
|
|
150
|
+
font-size: 12px; line-height: 1.6; white-space: pre;
|
|
151
|
+
max-height: 300px; overflow-y: auto; margin: 6px 0 0;
|
|
152
|
+
color: ${isDark ? '#e6edf3' : '#1a2340'};
|
|
153
|
+
}
|
|
154
|
+
.hl-kw { color: ${isDark ? '#8e9eff' : '#4040bf'}; }
|
|
155
|
+
.hl-str { color: ${isDark ? '#ff7b72' : '#c4432b'}; }
|
|
156
|
+
.hl-num { color: ${isDark ? '#f0a050' : '#b35e14'}; }
|
|
157
|
+
.hl-cm { color: #6a737d; font-style: italic; }
|
|
158
|
+
.hl-fn { color: ${isDark ? '#d2a8ff' : '#7c3aed'}; }
|
|
159
|
+
.hl-ty { color: ${isDark ? '#d2a8ff' : '#7c3aed'}; }
|
|
160
|
+
.hl-pn { color: ${isDark ? '#b8bdd0' : '#4a5578'}; }
|
|
161
|
+
.hl-ann { color: ${isDark ? '#8e9eff' : '#4040bf'}; font-weight: 600; font-style: normal; }
|
|
162
|
+
.hl-arr { color: ${isDark ? '#79c0ff' : '#0969da'}; font-weight: 600; font-style: normal; }
|
|
163
|
+
.hl-id { color: ${isDark ? '#e6edf3' : '#1a2340'}; font-style: normal; }
|
|
164
|
+
.hl-sc { color: ${isDark ? '#d2a8ff' : '#7c3aed'}; font-style: italic; }
|
|
143
165
|
|
|
144
166
|
/* Branding badge */
|
|
145
167
|
#branding {
|
|
@@ -194,8 +216,8 @@ path[data-source].port-hover { opacity: 1; }
|
|
|
194
216
|
<circle cx="10" cy="10" r="1.5" fill="${dotColor}" opacity="0.6"/>
|
|
195
217
|
</pattern>
|
|
196
218
|
</defs>
|
|
197
|
-
<rect x="-100000" y="-100000" width="200000" height="200000" fill="${bg}"/>
|
|
198
|
-
<rect x="-100000" y="-100000" width="200000" height="200000" fill="url(#viewer-dots)"/>
|
|
219
|
+
<rect x="-100000" y="-100000" width="200000" height="200000" fill="${bg}" pointer-events="none"/>
|
|
220
|
+
<rect x="-100000" y="-100000" width="200000" height="200000" fill="url(#viewer-dots)" pointer-events="none"/>
|
|
199
221
|
<g id="diagram">${inner}</g>
|
|
200
222
|
</svg>
|
|
201
223
|
<div id="controls">
|
|
@@ -225,6 +247,7 @@ path[data-source].port-hover { opacity: 1; }
|
|
|
225
247
|
</a>
|
|
226
248
|
<div id="scroll-hint">Use <kbd id="mod-key">Ctrl</kbd> + scroll to zoom</div>
|
|
227
249
|
<div id="studio-hint">Like rearranging? <a href="https://flowweaver.ai" target="_blank" rel="noopener">Flow Weaver Studio</a> saves your layouts.</div>
|
|
250
|
+
<script>var nodeSources = ${JSON.stringify(options.nodeSources ?? {})};</script>
|
|
228
251
|
<script>
|
|
229
252
|
(function() {
|
|
230
253
|
'use strict';
|
|
@@ -309,6 +332,7 @@ path[data-source].port-hover { opacity: 1; }
|
|
|
309
332
|
|
|
310
333
|
// ---- Pan (drag) + Node drag ----
|
|
311
334
|
var draggedNodeId = null, dragNodeStart = null, didDragNode = false;
|
|
335
|
+
var clickTarget = null; // stash the real target before setPointerCapture steals it
|
|
312
336
|
var dragCount = 0, nudgeIndex = 0, nudgeTimer = null;
|
|
313
337
|
var nudgeMessages = [
|
|
314
338
|
'Like rearranging? <a href="https://flowweaver.ai" target="_blank" rel="noopener">Flow Weaver Studio</a> saves your layouts.',
|
|
@@ -323,6 +347,8 @@ path[data-source].port-hover { opacity: 1; }
|
|
|
323
347
|
|
|
324
348
|
canvas.addEventListener('pointerdown', function(e) {
|
|
325
349
|
if (e.button !== 0) return;
|
|
350
|
+
clickTarget = e.target; // stash before setPointerCapture redirects events
|
|
351
|
+
didDrag = false;
|
|
326
352
|
// Check if clicking on a node body (walk up to detect data-node-id)
|
|
327
353
|
var t = e.target;
|
|
328
354
|
while (t && t !== canvas) {
|
|
@@ -339,7 +365,6 @@ path[data-source].port-hover { opacity: 1; }
|
|
|
339
365
|
}
|
|
340
366
|
// Canvas pan
|
|
341
367
|
pointerDown = true;
|
|
342
|
-
didDrag = false;
|
|
343
368
|
dragLast = { x: e.clientX, y: e.clientY };
|
|
344
369
|
canvas.setPointerCapture(e.pointerId);
|
|
345
370
|
});
|
|
@@ -792,14 +817,28 @@ path[data-source].port-hover { opacity: 1; }
|
|
|
792
817
|
// Build info panel
|
|
793
818
|
infoTitle.textContent = labelText;
|
|
794
819
|
var html = '';
|
|
820
|
+
var src = nodeSources[nodeId];
|
|
821
|
+
if (src && src.description) {
|
|
822
|
+
html += '<div class="node-desc">' + escapeH(src.description) + '</div>';
|
|
823
|
+
}
|
|
824
|
+
var portInfo = (src && src.ports) ? src.ports : {};
|
|
825
|
+
function portLabel(name) {
|
|
826
|
+
var p = portInfo[name];
|
|
827
|
+
var label = escapeH(name);
|
|
828
|
+
if (p) {
|
|
829
|
+
var typeStr = p.tsType || p.type;
|
|
830
|
+
if (typeStr) label += ' <span class="port-type">' + escapeH(typeStr) + '</span>';
|
|
831
|
+
}
|
|
832
|
+
return label;
|
|
833
|
+
}
|
|
795
834
|
if (inputs.length) {
|
|
796
835
|
html += '<div class="info-section"><div class="info-label">Inputs</div><ul class="port-list">';
|
|
797
|
-
inputs.forEach(function(n) { html += '<li>' +
|
|
836
|
+
inputs.forEach(function(n) { html += '<li>' + portLabel(n) + '</li>'; });
|
|
798
837
|
html += '</ul></div>';
|
|
799
838
|
}
|
|
800
839
|
if (outputs.length) {
|
|
801
840
|
html += '<div class="info-section"><div class="info-label">Outputs</div><ul class="port-list">';
|
|
802
|
-
outputs.forEach(function(n) { html += '<li>' +
|
|
841
|
+
outputs.forEach(function(n) { html += '<li>' + portLabel(n) + '</li>'; });
|
|
803
842
|
html += '</ul></div>';
|
|
804
843
|
}
|
|
805
844
|
if (connectedNodes.size) {
|
|
@@ -807,6 +846,10 @@ path[data-source].port-hover { opacity: 1; }
|
|
|
807
846
|
html += Array.from(connectedNodes).map(escapeH).join(', ');
|
|
808
847
|
html += '</div></div>';
|
|
809
848
|
}
|
|
849
|
+
if (src && src.source) {
|
|
850
|
+
html += '<div class="info-section"><div class="info-label">Source</div>';
|
|
851
|
+
html += '<pre><code>' + highlightTS(src.source) + '</code></pre></div>';
|
|
852
|
+
}
|
|
810
853
|
infoBody.innerHTML = html;
|
|
811
854
|
infoPanel.classList.add('visible');
|
|
812
855
|
}
|
|
@@ -815,10 +858,130 @@ path[data-source].port-hover { opacity: 1; }
|
|
|
815
858
|
return s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
|
|
816
859
|
}
|
|
817
860
|
|
|
861
|
+
var fwAnnotations = 'flowWeaver,input,output,step,node,connect,param,returns,fwImport,label,scope,position,color,icon,tag,map,path,name,description,expression,executeWhen,pullExecution,strictTypes,autoConnect,port,trigger,cancelOn,retries,timeout,throttle';
|
|
862
|
+
|
|
863
|
+
function highlightJSDoc(block) {
|
|
864
|
+
var annSet = fwAnnotations.split(',');
|
|
865
|
+
var out = '';
|
|
866
|
+
var re = /(@[a-zA-Z]+)|(->)|(\\.[a-zA-Z_][a-zA-Z0-9_]*)|("[^"]*")|('\\''[^'\\'']*'\\'')|(-?[0-9]+(?:\\.[0-9]+)?)|([a-zA-Z_][a-zA-Z0-9_]*)|([^@a-zA-Z0-9"'\\-.]+)/g;
|
|
867
|
+
var m;
|
|
868
|
+
while ((m = re.exec(block)) !== null) {
|
|
869
|
+
if (m[1]) {
|
|
870
|
+
var tag = m[1].slice(1);
|
|
871
|
+
if (annSet.indexOf(tag) >= 0) {
|
|
872
|
+
out += '<span class="hl-ann">' + m[0] + '</span>';
|
|
873
|
+
} else {
|
|
874
|
+
out += '<span class="hl-ann">' + m[0] + '</span>';
|
|
875
|
+
}
|
|
876
|
+
} else if (m[2]) {
|
|
877
|
+
out += '<span class="hl-arr">' + m[2] + '</span>';
|
|
878
|
+
} else if (m[3]) {
|
|
879
|
+
// .portName scope reference
|
|
880
|
+
out += '<span class="hl-sc">' + m[3] + '</span>';
|
|
881
|
+
} else if (m[4] || m[5]) {
|
|
882
|
+
out += '<span class="hl-str">' + (m[4] || m[5]) + '</span>';
|
|
883
|
+
} else if (m[6]) {
|
|
884
|
+
out += '<span class="hl-num">' + m[6] + '</span>';
|
|
885
|
+
} else if (m[7]) {
|
|
886
|
+
var tys = 'string,number,boolean,any,void,never,unknown,STEP,STRING,NUMBER,BOOLEAN,ARRAY,OBJECT,FUNCTION,ANY';
|
|
887
|
+
if (tys.split(',').indexOf(m[7]) >= 0) {
|
|
888
|
+
out += '<span class="hl-ty">' + m[7] + '</span>';
|
|
889
|
+
} else {
|
|
890
|
+
out += '<span class="hl-id">' + m[7] + '</span>';
|
|
891
|
+
}
|
|
892
|
+
} else {
|
|
893
|
+
out += m[0];
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
return out;
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
function highlightTS(code) {
|
|
900
|
+
var tokens = [];
|
|
901
|
+
var i = 0;
|
|
902
|
+
while (i < code.length) {
|
|
903
|
+
// Line comments
|
|
904
|
+
if (code[i] === '/' && code[i+1] === '/') {
|
|
905
|
+
var end = code.indexOf('\\n', i);
|
|
906
|
+
if (end === -1) end = code.length;
|
|
907
|
+
tokens.push({ t: 'cm', v: code.slice(i, end) });
|
|
908
|
+
i = end;
|
|
909
|
+
continue;
|
|
910
|
+
}
|
|
911
|
+
// Block comments (detect JSDoc for annotation highlighting)
|
|
912
|
+
if (code[i] === '/' && code[i+1] === '*') {
|
|
913
|
+
var end = code.indexOf('*/', i + 2);
|
|
914
|
+
if (end === -1) end = code.length; else end += 2;
|
|
915
|
+
var block = code.slice(i, end);
|
|
916
|
+
var hasFW = /@(flowWeaver|input|output|step|node|connect|param|returns)/.test(block);
|
|
917
|
+
if (hasFW) {
|
|
918
|
+
tokens.push({ t: 'jsdoc', v: block });
|
|
919
|
+
} else {
|
|
920
|
+
tokens.push({ t: 'cm', v: block });
|
|
921
|
+
}
|
|
922
|
+
i = end;
|
|
923
|
+
continue;
|
|
924
|
+
}
|
|
925
|
+
// Strings
|
|
926
|
+
if (code[i] === "'" || code[i] === '"' || code[i] === '\`') {
|
|
927
|
+
var q = code[i], j = i + 1;
|
|
928
|
+
while (j < code.length && code[j] !== q) { if (code[j] === '\\\\') j++; j++; }
|
|
929
|
+
tokens.push({ t: 'str', v: code.slice(i, j + 1) });
|
|
930
|
+
i = j + 1;
|
|
931
|
+
continue;
|
|
932
|
+
}
|
|
933
|
+
// Numbers
|
|
934
|
+
if (/[0-9]/.test(code[i]) && (i === 0 || /[^a-zA-Z_$]/.test(code[i-1]))) {
|
|
935
|
+
var j = i;
|
|
936
|
+
while (j < code.length && /[0-9a-fA-FxX._]/.test(code[j])) j++;
|
|
937
|
+
tokens.push({ t: 'num', v: code.slice(i, j) });
|
|
938
|
+
i = j;
|
|
939
|
+
continue;
|
|
940
|
+
}
|
|
941
|
+
// Words
|
|
942
|
+
if (/[a-zA-Z_$]/.test(code[i])) {
|
|
943
|
+
var j = i;
|
|
944
|
+
while (j < code.length && /[a-zA-Z0-9_$]/.test(code[j])) j++;
|
|
945
|
+
var w = code.slice(i, j);
|
|
946
|
+
var kws = 'async,await,break,case,catch,class,const,continue,default,delete,do,else,export,extends,finally,for,from,function,if,import,in,instanceof,let,new,of,return,switch,throw,try,typeof,var,void,while,yield';
|
|
947
|
+
var tys = 'string,number,boolean,any,void,never,unknown,null,undefined,true,false,Promise,Record,Map,Set,Array,Partial,Required,Omit,Pick';
|
|
948
|
+
if (kws.split(',').indexOf(w) >= 0) {
|
|
949
|
+
tokens.push({ t: 'kw', v: w });
|
|
950
|
+
} else if (tys.split(',').indexOf(w) >= 0) {
|
|
951
|
+
tokens.push({ t: 'ty', v: w });
|
|
952
|
+
} else if (j < code.length && code[j] === '(') {
|
|
953
|
+
tokens.push({ t: 'fn', v: w });
|
|
954
|
+
} else {
|
|
955
|
+
tokens.push({ t: '', v: w });
|
|
956
|
+
}
|
|
957
|
+
i = j;
|
|
958
|
+
continue;
|
|
959
|
+
}
|
|
960
|
+
// Punctuation
|
|
961
|
+
if (/[{}()\\[\\];:.,<>=!&|?+\\-*/%^~@]/.test(code[i])) {
|
|
962
|
+
tokens.push({ t: 'pn', v: code[i] });
|
|
963
|
+
i++;
|
|
964
|
+
continue;
|
|
965
|
+
}
|
|
966
|
+
// Whitespace and other
|
|
967
|
+
tokens.push({ t: '', v: code[i] });
|
|
968
|
+
i++;
|
|
969
|
+
}
|
|
970
|
+
return tokens.map(function(tk) {
|
|
971
|
+
if (tk.t === 'jsdoc') {
|
|
972
|
+
return '<span class="hl-cm">' + highlightJSDoc(escapeH(tk.v)) + '</span>';
|
|
973
|
+
}
|
|
974
|
+
var v = escapeH(tk.v);
|
|
975
|
+
return tk.t ? '<span class="hl-' + tk.t + '">' + v + '</span>' : v;
|
|
976
|
+
}).join('');
|
|
977
|
+
}
|
|
978
|
+
|
|
818
979
|
// Delegate click: port click > node click > background
|
|
980
|
+
// Use clickTarget (stashed from pointerdown) because setPointerCapture redirects click to canvas
|
|
819
981
|
canvas.addEventListener('click', function(e) {
|
|
820
982
|
if (didDrag || didDragNode) { didDragNode = false; return; }
|
|
821
|
-
var target = e.target;
|
|
983
|
+
var target = clickTarget || e.target;
|
|
984
|
+
clickTarget = null;
|
|
822
985
|
while (target && target !== canvas) {
|
|
823
986
|
if (target.hasAttribute && target.hasAttribute('data-port-id')) {
|
|
824
987
|
e.stopPropagation();
|
package/dist/diagram/index.js
CHANGED
|
@@ -28,37 +28,73 @@ export function fileToSVG(filePath, options = {}) {
|
|
|
28
28
|
*/
|
|
29
29
|
export function workflowToHTML(ast, options = {}) {
|
|
30
30
|
const svg = workflowToSVG(ast, options);
|
|
31
|
-
return wrapSVGInHTML(svg, { title: options.workflowName ?? ast.name, theme: options.theme });
|
|
31
|
+
return wrapSVGInHTML(svg, { title: options.workflowName ?? ast.name, theme: options.theme, nodeSources: buildNodeSourceMap(ast) });
|
|
32
32
|
}
|
|
33
33
|
/**
|
|
34
34
|
* Parse TypeScript source code and render the first (or named) workflow to interactive HTML.
|
|
35
35
|
*/
|
|
36
36
|
export function sourceToHTML(code, options = {}) {
|
|
37
|
-
const
|
|
38
|
-
|
|
37
|
+
const result = parser.parseFromString(code);
|
|
38
|
+
const ast = pickWorkflow(result.workflows, options);
|
|
39
|
+
const svg = workflowToSVG(ast, options);
|
|
40
|
+
return wrapSVGInHTML(svg, { title: options.workflowName ?? ast.name, theme: options.theme, nodeSources: buildNodeSourceMap(ast) });
|
|
39
41
|
}
|
|
40
42
|
/**
|
|
41
43
|
* Parse a workflow file and render the first (or named) workflow to interactive HTML.
|
|
42
44
|
*/
|
|
43
45
|
export function fileToHTML(filePath, options = {}) {
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
+
const result = parser.parse(filePath);
|
|
47
|
+
const ast = pickWorkflow(result.workflows, options);
|
|
48
|
+
const svg = workflowToSVG(ast, options);
|
|
49
|
+
return wrapSVGInHTML(svg, { title: options.workflowName ?? ast.name, theme: options.theme, nodeSources: buildNodeSourceMap(ast) });
|
|
46
50
|
}
|
|
47
|
-
function
|
|
51
|
+
function buildNodeSourceMap(ast) {
|
|
52
|
+
const typeMap = new Map(ast.nodeTypes.map(nt => [nt.functionName, nt]));
|
|
53
|
+
const map = {};
|
|
54
|
+
for (const inst of ast.instances) {
|
|
55
|
+
const nt = typeMap.get(inst.nodeType);
|
|
56
|
+
if (!nt)
|
|
57
|
+
continue;
|
|
58
|
+
const ports = {};
|
|
59
|
+
for (const [name, def] of Object.entries(nt.inputs ?? {})) {
|
|
60
|
+
ports[name] = { type: def.dataType, tsType: def.tsType };
|
|
61
|
+
}
|
|
62
|
+
for (const [name, def] of Object.entries(nt.outputs ?? {})) {
|
|
63
|
+
ports[name] = { type: def.dataType, tsType: def.tsType };
|
|
64
|
+
}
|
|
65
|
+
map[inst.id] = { description: nt.description, source: nt.functionText, ports };
|
|
66
|
+
}
|
|
67
|
+
// Virtual Start/Exit nodes get their port types from the workflow definition
|
|
68
|
+
const startPorts = {};
|
|
69
|
+
for (const [name, def] of Object.entries(ast.startPorts ?? {})) {
|
|
70
|
+
startPorts[name] = { type: def.dataType, tsType: def.tsType };
|
|
71
|
+
}
|
|
72
|
+
if (Object.keys(startPorts).length) {
|
|
73
|
+
map['Start'] = { description: ast.description, ports: startPorts };
|
|
74
|
+
}
|
|
75
|
+
const exitPorts = {};
|
|
76
|
+
for (const [name, def] of Object.entries(ast.exitPorts ?? {})) {
|
|
77
|
+
exitPorts[name] = { type: def.dataType, tsType: def.tsType };
|
|
78
|
+
}
|
|
79
|
+
if (Object.keys(exitPorts).length) {
|
|
80
|
+
map['Exit'] = { ports: exitPorts };
|
|
81
|
+
}
|
|
82
|
+
return map;
|
|
83
|
+
}
|
|
84
|
+
function pickWorkflow(workflows, options) {
|
|
48
85
|
if (workflows.length === 0) {
|
|
49
86
|
throw new Error('No workflows found in source code');
|
|
50
87
|
}
|
|
51
|
-
let workflow;
|
|
52
88
|
if (options.workflowName) {
|
|
53
89
|
const found = workflows.find(w => w.name === options.workflowName);
|
|
54
90
|
if (!found) {
|
|
55
91
|
throw new Error(`Workflow "${options.workflowName}" not found. Available: ${workflows.map(w => w.name).join(', ')}`);
|
|
56
92
|
}
|
|
57
|
-
|
|
58
|
-
}
|
|
59
|
-
else {
|
|
60
|
-
workflow = workflows[0];
|
|
93
|
+
return found;
|
|
61
94
|
}
|
|
62
|
-
return
|
|
95
|
+
return workflows[0];
|
|
96
|
+
}
|
|
97
|
+
function pickAndRender(workflows, options) {
|
|
98
|
+
return workflowToSVG(pickWorkflow(workflows, options), options);
|
|
63
99
|
}
|
|
64
100
|
//# sourceMappingURL=index.js.map
|
package/dist/friendly-errors.js
CHANGED
|
@@ -35,7 +35,7 @@ const COERCE_TARGET_TYPES = {
|
|
|
35
35
|
OBJECT: 'object',
|
|
36
36
|
};
|
|
37
37
|
/**
|
|
38
|
-
* Build a concrete
|
|
38
|
+
* Build a concrete `@connect ... as <type>` coercion suggestion from error context.
|
|
39
39
|
* Returns null if not enough info is available.
|
|
40
40
|
*/
|
|
41
41
|
function buildCoerceSuggestion(quoted, targetType) {
|
|
@@ -51,7 +51,7 @@ function buildCoerceSuggestion(quoted, targetType) {
|
|
|
51
51
|
const coerceType = COERCE_TARGET_TYPES[targetType.toUpperCase()] || targetType.toLowerCase();
|
|
52
52
|
if (!['string', 'number', 'boolean', 'json', 'object'].includes(coerceType))
|
|
53
53
|
return null;
|
|
54
|
-
return `@
|
|
54
|
+
return `@connect ${portRefs[0]} -> ${portRefs[1]} as ${coerceType}`;
|
|
55
55
|
}
|
|
56
56
|
function extractCyclePath(message) {
|
|
57
57
|
const match = message.match(/:\s*(.+ -> .+)/);
|
|
@@ -163,8 +163,8 @@ const errorMappers = {
|
|
|
163
163
|
title: 'Type Mismatch',
|
|
164
164
|
explanation: `Type mismatch: you're connecting a ${source} to a ${target}. The value will be automatically converted, but this might cause unexpected behavior.`,
|
|
165
165
|
fix: coerceSuggestion
|
|
166
|
-
? `
|
|
167
|
-
: `Add
|
|
166
|
+
? `Use \`${coerceSuggestion}\` for explicit coercion, change one of the port types, or use @strictTypes to turn this into an error.`
|
|
167
|
+
: `Add \`as <type>\` to the @connect annotation (e.g. \`as string\`), change one of the port types, or use @strictTypes to turn this into an error.`,
|
|
168
168
|
code: error.code,
|
|
169
169
|
};
|
|
170
170
|
},
|
|
@@ -264,8 +264,8 @@ const errorMappers = {
|
|
|
264
264
|
title: 'Type Incompatible',
|
|
265
265
|
explanation: `Type mismatch: ${source} to ${target}. With @strictTypes enabled, this is an error instead of a warning.`,
|
|
266
266
|
fix: coerceSuggestion
|
|
267
|
-
? `
|
|
268
|
-
: `Add
|
|
267
|
+
? `Use \`${coerceSuggestion}\` for explicit coercion, change one of the port types, or remove @strictTypes to allow implicit coercions.`
|
|
268
|
+
: `Add \`as <type>\` to the @connect annotation (e.g. \`as number\`), change one of the port types, or remove @strictTypes to allow implicit coercions.`,
|
|
269
269
|
code: error.code,
|
|
270
270
|
};
|
|
271
271
|
},
|
|
@@ -279,8 +279,8 @@ const errorMappers = {
|
|
|
279
279
|
title: 'Unusual Type Coercion',
|
|
280
280
|
explanation: `Converting ${source} to ${target} is technically valid but semantically unusual and may produce unexpected behavior.`,
|
|
281
281
|
fix: coerceSuggestion
|
|
282
|
-
? `If intentional,
|
|
283
|
-
: `If intentional, add
|
|
282
|
+
? `If intentional, use \`${coerceSuggestion}\` for explicit coercion, or use @strictTypes to enforce type safety.`
|
|
283
|
+
: `If intentional, add \`as <type>\` to the @connect annotation, or use @strictTypes to enforce type safety.`,
|
|
284
284
|
code: error.code,
|
|
285
285
|
};
|
|
286
286
|
},
|
|
@@ -550,8 +550,8 @@ const errorMappers = {
|
|
|
550
550
|
title: 'Lossy Type Conversion',
|
|
551
551
|
explanation: `Converting ${source} to ${target} may lose data or produce unexpected results (e.g., NaN, truncation).`,
|
|
552
552
|
fix: coerceSuggestion
|
|
553
|
-
? `
|
|
554
|
-
: `Add
|
|
553
|
+
? `Use \`${coerceSuggestion}\` for explicit coercion, or use @strictTypes to enforce type safety.`
|
|
554
|
+
: `Add \`as <type>\` to the @connect annotation for explicit conversion, or use @strictTypes to enforce type safety.`,
|
|
555
555
|
code: error.code,
|
|
556
556
|
};
|
|
557
557
|
},
|
package/package.json
CHANGED