@synergenius/flow-weaver 0.17.9 → 0.17.11

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.
@@ -786,7 +786,7 @@ export type TNodeTagAST = {
786
786
  };
787
787
  /** Visual customization for node types */
788
788
  export type TNodeVisualsAST = {
789
- /** Theme color: blue, purple, teal, orange, pink, green */
789
+ /** Theme color: blue, purple, cyan, orange, pink, green, red, yellow (teal is an alias for cyan) */
790
790
  color?: string;
791
791
  /** Icon preset name */
792
792
  icon?: string;
@@ -15,6 +15,7 @@ export interface PortParseResult {
15
15
  mergeStrategy?: string;
16
16
  hidden?: boolean;
17
17
  description?: string;
18
+ customMetadata?: Record<string, string | boolean | number>;
18
19
  }
19
20
  /**
20
21
  * Parse a port line (@input/@output/@step) and return structured result.
@@ -98,6 +98,7 @@ class PortParser extends CstParser {
98
98
  { ALT: () => this.SUBRULE(this.typeAttr) },
99
99
  { ALT: () => this.SUBRULE(this.mergeStrategyAttr) },
100
100
  { ALT: () => this.SUBRULE(this.hiddenAttr) },
101
+ { ALT: () => this.SUBRULE(this.customAttr) },
101
102
  ]);
102
103
  },
103
104
  });
@@ -131,6 +132,18 @@ class PortParser extends CstParser {
131
132
  hiddenAttr = this.RULE('hiddenAttr', () => {
132
133
  this.CONSUME(HiddenKeyword);
133
134
  });
135
+ // customAttr: key:value pairs for arbitrary metadata (e.g., artifactPath:"dist/")
136
+ customAttr = this.RULE('customAttr', () => {
137
+ this.CONSUME(Identifier, { LABEL: 'customKey' });
138
+ this.CONSUME(Colon);
139
+ this.OR([
140
+ { ALT: () => this.CONSUME(StringLiteral, { LABEL: 'customStrVal' }) },
141
+ { ALT: () => this.CONSUME(TrueKeyword, { LABEL: 'customTrue' }) },
142
+ { ALT: () => this.CONSUME(FalseKeyword, { LABEL: 'customFalse' }) },
143
+ { ALT: () => this.CONSUME2(Identifier, { LABEL: 'customIdVal' }) },
144
+ { ALT: () => this.CONSUME(Integer, { LABEL: 'customIntVal' }) },
145
+ ]);
146
+ });
134
147
  // - description text
135
148
  // Note: We just consume the dash here. The actual description text is extracted
136
149
  // from the raw input in parsePortLine() to handle special characters like &, :, etc.
@@ -232,6 +245,7 @@ class PortVisitor extends BaseVisitor {
232
245
  scope = this.visit(ctx.scopeClause);
233
246
  }
234
247
  let hidden;
248
+ let customMetadata;
235
249
  if (ctx.metadataBracket) {
236
250
  // Handle multiple metadata brackets (e.g., [order:1] [placement:TOP])
237
251
  for (const bracket of ctx.metadataBracket) {
@@ -246,6 +260,9 @@ class PortVisitor extends BaseVisitor {
246
260
  mergeStrategy = metadata.mergeStrategy;
247
261
  if (metadata.hidden)
248
262
  hidden = true;
263
+ if (metadata.customMetadata) {
264
+ customMetadata = { ...customMetadata, ...metadata.customMetadata };
265
+ }
249
266
  }
250
267
  }
251
268
  if (ctx.descriptionClause) {
@@ -263,6 +280,7 @@ class PortVisitor extends BaseVisitor {
263
280
  ...(mergeStrategy && { mergeStrategy }),
264
281
  ...(hidden && { hidden }),
265
282
  ...(description && { description }),
283
+ ...(customMetadata && { customMetadata }),
266
284
  };
267
285
  }
268
286
  outputPort(ctx) {
@@ -276,6 +294,7 @@ class PortVisitor extends BaseVisitor {
276
294
  scope = this.visit(ctx.scopeClause);
277
295
  }
278
296
  let hidden;
297
+ let customMetadata;
279
298
  if (ctx.metadataBracket) {
280
299
  // Handle multiple metadata brackets (e.g., [order:1] [placement:TOP])
281
300
  for (const bracket of ctx.metadataBracket) {
@@ -288,6 +307,9 @@ class PortVisitor extends BaseVisitor {
288
307
  dataType = metadata.dataType;
289
308
  if (metadata.hidden)
290
309
  hidden = true;
310
+ if (metadata.customMetadata) {
311
+ customMetadata = { ...customMetadata, ...metadata.customMetadata };
312
+ }
291
313
  }
292
314
  }
293
315
  if (ctx.descriptionClause) {
@@ -302,6 +324,7 @@ class PortVisitor extends BaseVisitor {
302
324
  ...(dataType && { dataType }),
303
325
  ...(hidden && { hidden }),
304
326
  ...(description && { description }),
327
+ ...(customMetadata && { customMetadata }),
305
328
  };
306
329
  }
307
330
  stepPort(ctx) {
@@ -325,6 +348,7 @@ class PortVisitor extends BaseVisitor {
325
348
  let dataType;
326
349
  let mergeStrategy;
327
350
  let hidden;
351
+ let customMetadata;
328
352
  if (ctx.orderAttr) {
329
353
  for (const attr of ctx.orderAttr) {
330
354
  order = this.visit(attr);
@@ -348,7 +372,14 @@ class PortVisitor extends BaseVisitor {
348
372
  if (ctx.hiddenAttr) {
349
373
  hidden = true;
350
374
  }
351
- return { order, placement, dataType, mergeStrategy, hidden };
375
+ if (ctx.customAttr) {
376
+ customMetadata = customMetadata || {};
377
+ for (const attr of ctx.customAttr) {
378
+ const { key, value } = this.visit(attr);
379
+ customMetadata[key] = value;
380
+ }
381
+ }
382
+ return { order, placement, dataType, mergeStrategy, hidden, customMetadata };
352
383
  }
353
384
  orderAttr(ctx) {
354
385
  return parseInt(ctx.orderValue[0].image, 10);
@@ -365,6 +396,31 @@ class PortVisitor extends BaseVisitor {
365
396
  mergeStrategyAttr(ctx) {
366
397
  return ctx.mergeStrategyValue[0].image;
367
398
  }
399
+ customAttr(ctx) {
400
+ const key = ctx.customKey[0].image;
401
+ let value;
402
+ if (ctx.customStrVal) {
403
+ // Remove quotes from string literal
404
+ const raw = ctx.customStrVal[0].image;
405
+ value = raw.slice(1, -1);
406
+ }
407
+ else if (ctx.customTrue) {
408
+ value = true;
409
+ }
410
+ else if (ctx.customFalse) {
411
+ value = false;
412
+ }
413
+ else if (ctx.customIntVal) {
414
+ value = parseInt(ctx.customIntVal[0].image, 10);
415
+ }
416
+ else if (ctx.customIdVal) {
417
+ value = ctx.customIdVal[0].image;
418
+ }
419
+ else {
420
+ value = '';
421
+ }
422
+ return { key, value };
423
+ }
368
424
  descriptionClause(ctx) {
369
425
  if (ctx.Identifier) {
370
426
  return ctx.Identifier.map((token) => token.image).join(' ');
@@ -9671,7 +9671,7 @@ var VERSION;
9671
9671
  var init_generated_version = __esm({
9672
9672
  "src/generated-version.ts"() {
9673
9673
  "use strict";
9674
- VERSION = "0.17.9";
9674
+ VERSION = "0.17.11";
9675
9675
  }
9676
9676
  });
9677
9677
 
@@ -31902,7 +31902,8 @@ var init_port_parser = __esm({
31902
31902
  { ALT: () => this.SUBRULE(this.placementAttr) },
31903
31903
  { ALT: () => this.SUBRULE(this.typeAttr) },
31904
31904
  { ALT: () => this.SUBRULE(this.mergeStrategyAttr) },
31905
- { ALT: () => this.SUBRULE(this.hiddenAttr) }
31905
+ { ALT: () => this.SUBRULE(this.hiddenAttr) },
31906
+ { ALT: () => this.SUBRULE(this.customAttr) }
31906
31907
  ]);
31907
31908
  }
31908
31909
  });
@@ -31936,6 +31937,18 @@ var init_port_parser = __esm({
31936
31937
  hiddenAttr = this.RULE("hiddenAttr", () => {
31937
31938
  this.CONSUME(HiddenKeyword);
31938
31939
  });
31940
+ // customAttr: key:value pairs for arbitrary metadata (e.g., artifactPath:"dist/")
31941
+ customAttr = this.RULE("customAttr", () => {
31942
+ this.CONSUME(Identifier, { LABEL: "customKey" });
31943
+ this.CONSUME(Colon);
31944
+ this.OR([
31945
+ { ALT: () => this.CONSUME(StringLiteral, { LABEL: "customStrVal" }) },
31946
+ { ALT: () => this.CONSUME(TrueKeyword, { LABEL: "customTrue" }) },
31947
+ { ALT: () => this.CONSUME(FalseKeyword, { LABEL: "customFalse" }) },
31948
+ { ALT: () => this.CONSUME2(Identifier, { LABEL: "customIdVal" }) },
31949
+ { ALT: () => this.CONSUME(Integer, { LABEL: "customIntVal" }) }
31950
+ ]);
31951
+ });
31939
31952
  // - description text
31940
31953
  // Note: We just consume the dash here. The actual description text is extracted
31941
31954
  // from the raw input in parsePortLine() to handle special characters like &, :, etc.
@@ -32022,6 +32035,7 @@ var init_port_parser = __esm({
32022
32035
  scope = this.visit(ctx.scopeClause);
32023
32036
  }
32024
32037
  let hidden;
32038
+ let customMetadata;
32025
32039
  if (ctx.metadataBracket) {
32026
32040
  for (const bracket of ctx.metadataBracket) {
32027
32041
  const metadata = this.visit(bracket);
@@ -32030,6 +32044,9 @@ var init_port_parser = __esm({
32030
32044
  if (metadata.dataType !== void 0) dataType = metadata.dataType;
32031
32045
  if (metadata.mergeStrategy !== void 0) mergeStrategy = metadata.mergeStrategy;
32032
32046
  if (metadata.hidden) hidden = true;
32047
+ if (metadata.customMetadata) {
32048
+ customMetadata = { ...customMetadata, ...metadata.customMetadata };
32049
+ }
32033
32050
  }
32034
32051
  }
32035
32052
  if (ctx.descriptionClause) {
@@ -32046,7 +32063,8 @@ var init_port_parser = __esm({
32046
32063
  ...dataType && { dataType },
32047
32064
  ...mergeStrategy && { mergeStrategy },
32048
32065
  ...hidden && { hidden },
32049
- ...description && { description }
32066
+ ...description && { description },
32067
+ ...customMetadata && { customMetadata }
32050
32068
  };
32051
32069
  }
32052
32070
  outputPort(ctx) {
@@ -32060,6 +32078,7 @@ var init_port_parser = __esm({
32060
32078
  scope = this.visit(ctx.scopeClause);
32061
32079
  }
32062
32080
  let hidden;
32081
+ let customMetadata;
32063
32082
  if (ctx.metadataBracket) {
32064
32083
  for (const bracket of ctx.metadataBracket) {
32065
32084
  const metadata = this.visit(bracket);
@@ -32067,6 +32086,9 @@ var init_port_parser = __esm({
32067
32086
  if (metadata.placement !== void 0) placement = metadata.placement;
32068
32087
  if (metadata.dataType !== void 0) dataType = metadata.dataType;
32069
32088
  if (metadata.hidden) hidden = true;
32089
+ if (metadata.customMetadata) {
32090
+ customMetadata = { ...customMetadata, ...metadata.customMetadata };
32091
+ }
32070
32092
  }
32071
32093
  }
32072
32094
  if (ctx.descriptionClause) {
@@ -32080,7 +32102,8 @@ var init_port_parser = __esm({
32080
32102
  ...placement && { placement },
32081
32103
  ...dataType && { dataType },
32082
32104
  ...hidden && { hidden },
32083
- ...description && { description }
32105
+ ...description && { description },
32106
+ ...customMetadata && { customMetadata }
32084
32107
  };
32085
32108
  }
32086
32109
  stepPort(ctx) {
@@ -32104,6 +32127,7 @@ var init_port_parser = __esm({
32104
32127
  let dataType;
32105
32128
  let mergeStrategy;
32106
32129
  let hidden;
32130
+ let customMetadata;
32107
32131
  if (ctx.orderAttr) {
32108
32132
  for (const attr of ctx.orderAttr) {
32109
32133
  order = this.visit(attr);
@@ -32127,7 +32151,14 @@ var init_port_parser = __esm({
32127
32151
  if (ctx.hiddenAttr) {
32128
32152
  hidden = true;
32129
32153
  }
32130
- return { order, placement, dataType, mergeStrategy, hidden };
32154
+ if (ctx.customAttr) {
32155
+ customMetadata = customMetadata || {};
32156
+ for (const attr of ctx.customAttr) {
32157
+ const { key, value: value2 } = this.visit(attr);
32158
+ customMetadata[key] = value2;
32159
+ }
32160
+ }
32161
+ return { order, placement, dataType, mergeStrategy, hidden, customMetadata };
32131
32162
  }
32132
32163
  orderAttr(ctx) {
32133
32164
  return parseInt(ctx.orderValue[0].image, 10);
@@ -32144,6 +32175,25 @@ var init_port_parser = __esm({
32144
32175
  mergeStrategyAttr(ctx) {
32145
32176
  return ctx.mergeStrategyValue[0].image;
32146
32177
  }
32178
+ customAttr(ctx) {
32179
+ const key = ctx.customKey[0].image;
32180
+ let value2;
32181
+ if (ctx.customStrVal) {
32182
+ const raw = ctx.customStrVal[0].image;
32183
+ value2 = raw.slice(1, -1);
32184
+ } else if (ctx.customTrue) {
32185
+ value2 = true;
32186
+ } else if (ctx.customFalse) {
32187
+ value2 = false;
32188
+ } else if (ctx.customIntVal) {
32189
+ value2 = parseInt(ctx.customIntVal[0].image, 10);
32190
+ } else if (ctx.customIdVal) {
32191
+ value2 = ctx.customIdVal[0].image;
32192
+ } else {
32193
+ value2 = "";
32194
+ }
32195
+ return { key, value: value2 };
32196
+ }
32147
32197
  descriptionClause(ctx) {
32148
32198
  if (ctx.Identifier) {
32149
32199
  return ctx.Identifier.map((token) => token.image).join(" ");
@@ -34219,7 +34269,7 @@ var init_jsdoc_parser = __esm({
34219
34269
  if (!result) {
34220
34270
  return;
34221
34271
  }
34222
- const { name, defaultValue, isOptional, scope, order, mergeStrategy, hidden, description } = result;
34272
+ const { name, defaultValue, isOptional, scope, order, mergeStrategy, hidden, description, customMetadata } = result;
34223
34273
  let type2;
34224
34274
  let tsType;
34225
34275
  const isScopedStepInput = scope && isScopedMandatoryPort(name);
@@ -34280,7 +34330,9 @@ var init_jsdoc_parser = __esm({
34280
34330
  ...scope && { scope },
34281
34331
  ...mergeStrategy && { mergeStrategy },
34282
34332
  ...hidden && { hidden },
34283
- ...order !== void 0 && { metadata: { order } },
34333
+ ...(order !== void 0 || customMetadata) && {
34334
+ metadata: { ...order !== void 0 && { order }, ...customMetadata }
34335
+ },
34284
34336
  ...tsType && { tsType }
34285
34337
  };
34286
34338
  }
@@ -34294,7 +34346,7 @@ var init_jsdoc_parser = __esm({
34294
34346
  if (!result) {
34295
34347
  return;
34296
34348
  }
34297
- const { name, scope, order, hidden, description } = result;
34349
+ const { name, scope, order, hidden, description, customMetadata } = result;
34298
34350
  let type2;
34299
34351
  let tsType;
34300
34352
  const isScopedStepOutput = scope && isScopedMandatoryPort(name);
@@ -34348,7 +34400,9 @@ var init_jsdoc_parser = __esm({
34348
34400
  label: description?.trim(),
34349
34401
  ...scope && { scope },
34350
34402
  ...hidden && { hidden },
34351
- ...order !== void 0 && { metadata: { order } },
34403
+ ...(order !== void 0 || customMetadata) && {
34404
+ metadata: { ...order !== void 0 && { order }, ...customMetadata }
34405
+ },
34352
34406
  ...tsType && { tsType }
34353
34407
  };
34354
34408
  }
@@ -34386,7 +34440,7 @@ var init_jsdoc_parser = __esm({
34386
34440
  if (!result) {
34387
34441
  return;
34388
34442
  }
34389
- const { name, order, description } = result;
34443
+ const { name, order, description, customMetadata } = result;
34390
34444
  let type2 = "ANY";
34391
34445
  if (isSuccessPort(name) || isFailurePort(name)) {
34392
34446
  type2 = "STEP";
@@ -34407,7 +34461,9 @@ var init_jsdoc_parser = __esm({
34407
34461
  config2.returnPorts[name] = {
34408
34462
  dataType: type2,
34409
34463
  label: description?.trim(),
34410
- ...order !== void 0 && { metadata: { order } }
34464
+ ...(order !== void 0 || customMetadata) && {
34465
+ metadata: { ...order !== void 0 && { order }, ...customMetadata }
34466
+ }
34411
34467
  };
34412
34468
  }
34413
34469
  /**
@@ -37833,7 +37889,7 @@ function darkenHex(hex, amount) {
37833
37889
  const nb = Math.round(b * (1 - amount));
37834
37890
  return `#${nr.toString(16).padStart(2, "0")}${ng.toString(16).padStart(2, "0")}${nb.toString(16).padStart(2, "0")}`;
37835
37891
  }
37836
- var DARK_PORT_COLORS, LIGHT_PORT_COLORS, DARK_FAILURE_COLOR, LIGHT_FAILURE_COLOR, NODE_VARIANT_COLORS, DARK_PALETTE, LIGHT_PALETTE, TYPE_ABBREVIATIONS, NODE_ICON_PATHS, VALID_NODE_ICONS;
37892
+ var DARK_PORT_COLORS, LIGHT_PORT_COLORS, DARK_FAILURE_COLOR, LIGHT_FAILURE_COLOR, NODE_DEFAULT_COLOR, NODE_VARIANT_COLORS, DARK_PALETTE, LIGHT_PALETTE, TYPE_ABBREVIATIONS, NODE_ICON_PATHS, VALID_NODE_ICONS;
37837
37893
  var init_theme = __esm({
37838
37894
  "src/diagram/theme.ts"() {
37839
37895
  "use strict";
@@ -37875,6 +37931,7 @@ var init_theme = __esm({
37875
37931
  };
37876
37932
  DARK_FAILURE_COLOR = "#ff4f4f";
37877
37933
  LIGHT_FAILURE_COLOR = "#e34646";
37934
+ NODE_DEFAULT_COLOR = "#334155";
37878
37935
  NODE_VARIANT_COLORS = {
37879
37936
  blue: { border: "#548ce3", darkBorder: "#5e9eff" },
37880
37937
  // blue-shade-2 / blue-dark-shade-1
@@ -37882,6 +37939,8 @@ var init_theme = __esm({
37882
37939
  // purple-shade-2 / purple-dark-shade-1
37883
37940
  cyan: { border: "#63ccc4", darkBorder: "#6fe5dc" },
37884
37941
  // cyan-shade-2 / cyan-dark-shade-1
37942
+ teal: { border: "#63ccc4", darkBorder: "#6fe5dc" },
37943
+ // alias for cyan
37885
37944
  orange: { border: "#e3732d", darkBorder: "#ff8133" },
37886
37945
  // orange-shade-2 / orange-dark-shade-1
37887
37946
  pink: { border: "#e349c2", darkBorder: "#ff52da" },
@@ -37890,10 +37949,8 @@ var init_theme = __esm({
37890
37949
  // green-shade-2 / green-dark-shade-1
37891
37950
  red: { border: "#e34646", darkBorder: "#ff4f4f" },
37892
37951
  // red-shade-2 / red-dark-shade-1
37893
- yellow: { border: "#e3a82b", darkBorder: "#ffbd30" },
37952
+ yellow: { border: "#e3a82b", darkBorder: "#ffbd30" }
37894
37953
  // yellow-shade-2 / yellow-dark-shade-1
37895
- teal: { border: "#3db0a8", darkBorder: "#4dc7be" }
37896
- // teal-shade-2 / teal-dark-shade-1
37897
37954
  };
37898
37955
  DARK_PALETTE = {
37899
37956
  background: "#202139",
@@ -50796,7 +50853,7 @@ function buildDiagramGraph(ast, options = {}) {
50796
50853
  diagramNodes.set("Start", {
50797
50854
  id: "Start",
50798
50855
  label: "Start",
50799
- color: "#334155",
50856
+ color: NODE_DEFAULT_COLOR,
50800
50857
  icon: "startNode",
50801
50858
  isVirtual: true,
50802
50859
  inputs: [],
@@ -50819,7 +50876,7 @@ function buildDiagramGraph(ast, options = {}) {
50819
50876
  diagramNodes.set("Exit", {
50820
50877
  id: "Exit",
50821
50878
  label: "Exit",
50822
- color: "#334155",
50879
+ color: NODE_DEFAULT_COLOR,
50823
50880
  icon: "exitNode",
50824
50881
  isVirtual: true,
50825
50882
  inputs: exitInputs,
@@ -51125,7 +51182,7 @@ function filterNonScopedPorts(ports) {
51125
51182
  return result;
51126
51183
  }
51127
51184
  function resolveNodeColor(color, theme = "dark") {
51128
- if (!color) return "#334155";
51185
+ if (!color) return NODE_DEFAULT_COLOR;
51129
51186
  const variant = NODE_VARIANT_COLORS[color];
51130
51187
  if (variant) return theme === "dark" ? variant.darkBorder : variant.border;
51131
51188
  return color;
@@ -51834,12 +51891,12 @@ function renderScopeConnection(parts2, conn, allConnections, parentNodeId) {
51834
51891
  );
51835
51892
  }
51836
51893
  function renderNodeBody(parts2, node, theme, indent) {
51837
- const strokeColor = node.color !== "#334155" ? node.color : theme.nodeIconColor;
51894
+ const strokeColor = node.color !== NODE_DEFAULT_COLOR ? node.color : theme.nodeIconColor;
51838
51895
  parts2.push(
51839
51896
  `${indent}<rect x="${node.x}" y="${node.y}" width="${node.width}" height="${node.height}" rx="${BORDER_RADIUS}" fill="${theme.nodeFill}" stroke="${strokeColor}" stroke-width="2" filter="url(#node-shadow)"/>`
51840
51897
  );
51841
51898
  const iconPath = NODE_ICON_PATHS[node.icon] ?? NODE_ICON_PATHS.code;
51842
- const iconColor = node.color !== "#334155" ? node.color : theme.nodeIconColor;
51899
+ const iconColor = node.color !== NODE_DEFAULT_COLOR ? node.color : theme.nodeIconColor;
51843
51900
  const iconSize = 40;
51844
51901
  const iconX = node.x + (node.width - iconSize) / 2;
51845
51902
  const iconY = node.y + (node.height - iconSize) / 2;
@@ -51851,7 +51908,7 @@ function renderNode(node, theme, themeName, allConnections) {
51851
51908
  const parts2 = [];
51852
51909
  parts2.push(` <g data-node-id="${escapeXml(node.id)}">`);
51853
51910
  if (node.scopeChildren && node.scopeChildren.length > 0) {
51854
- const strokeColor = node.color !== "#334155" ? node.color : theme.nodeIconColor;
51911
+ const strokeColor = node.color !== NODE_DEFAULT_COLOR ? node.color : theme.nodeIconColor;
51855
51912
  parts2.push(
51856
51913
  ` <rect x="${node.x}" y="${node.y}" width="${node.width}" height="${node.height}" rx="${BORDER_RADIUS}" fill="${theme.nodeFill}" stroke="${strokeColor}" stroke-width="2" filter="url(#node-shadow)"/>`
51857
51914
  );
@@ -51898,7 +51955,7 @@ function renderNodeLabel(parts2, node, theme) {
51898
51955
  const labelAnchor = isScoped ? "start" : "middle";
51899
51956
  parts2.push(` <g data-label-for="${escapeXml(node.id)}">`);
51900
51957
  parts2.push(` <rect x="${labelBgX}" y="${labelBgY}" width="${labelBgWidth}" height="${labelBgHeight}" rx="6" fill="${theme.labelBadgeFill}" opacity="0.8"/>`);
51901
- parts2.push(` <text class="node-label" x="${labelTextX}" y="${labelBgY + labelBgHeight / 2 + 6}" text-anchor="${labelAnchor}" fill="${node.color !== "#334155" ? node.color : theme.labelColor}">${labelText}</text>`);
51958
+ parts2.push(` <text class="node-label" x="${labelTextX}" y="${labelBgY + labelBgHeight / 2 + 6}" text-anchor="${labelAnchor}" fill="${node.color !== NODE_DEFAULT_COLOR ? node.color : theme.labelColor}">${labelText}</text>`);
51902
51959
  parts2.push(` </g>`);
51903
51960
  }
51904
51961
  function renderPortLabelsForNode(parts2, node, theme, themeName, showPortLabels) {
@@ -105820,7 +105877,7 @@ function displayInstalledPackage(pkg) {
105820
105877
  // src/cli/index.ts
105821
105878
  init_logger();
105822
105879
  init_error_utils();
105823
- var version2 = true ? "0.17.9" : "0.0.0-dev";
105880
+ var version2 = true ? "0.17.11" : "0.0.0-dev";
105824
105881
  var program2 = new Command();
105825
105882
  program2.name("flow-weaver").description("Flow Weaver Annotations - Compile and validate workflow files").option("-v, --version", "Output the current version").option("--no-color", "Disable colors").option("--color", "Force colors").on("option:version", () => {
105826
105883
  logger.banner(version2);
@@ -1,6 +1,6 @@
1
1
  import { isExecutePort, isSuccessPort, isFailurePort, SCOPED_PORT_NAMES, isScopedStartPort, isScopedSuccessPort, isScopedFailurePort } from '../constants.js';
2
2
  import { assignImplicitPortOrders } from '../utils/port-ordering.js';
3
- import { getPortColor, NODE_VARIANT_COLORS, TYPE_ABBREVIATIONS } from './theme.js';
3
+ import { getPortColor, NODE_VARIANT_COLORS, NODE_DEFAULT_COLOR, TYPE_ABBREVIATIONS } from './theme.js';
4
4
  import { layoutWorkflow } from './layout.js';
5
5
  import { calculateOrthogonalPathSafe, TrackAllocator } from './orthogonal-router.js';
6
6
  // ---- Constants (matching React component-node) ----
@@ -707,7 +707,7 @@ export function buildDiagramGraph(ast, options = {}) {
707
707
  diagramNodes.set('Start', {
708
708
  id: 'Start',
709
709
  label: 'Start',
710
- color: '#334155',
710
+ color: NODE_DEFAULT_COLOR,
711
711
  icon: 'startNode',
712
712
  isVirtual: true,
713
713
  inputs: [],
@@ -731,7 +731,7 @@ export function buildDiagramGraph(ast, options = {}) {
731
731
  diagramNodes.set('Exit', {
732
732
  id: 'Exit',
733
733
  label: 'Exit',
734
- color: '#334155',
734
+ color: NODE_DEFAULT_COLOR,
735
735
  icon: 'exitNode',
736
736
  isVirtual: true,
737
737
  inputs: exitInputs,
@@ -1098,7 +1098,7 @@ function filterNonScopedPorts(ports) {
1098
1098
  }
1099
1099
  function resolveNodeColor(color, theme = 'dark') {
1100
1100
  if (!color)
1101
- return '#334155';
1101
+ return NODE_DEFAULT_COLOR;
1102
1102
  const variant = NODE_VARIANT_COLORS[color];
1103
1103
  if (variant)
1104
1104
  return theme === 'dark' ? variant.darkBorder : variant.border;
@@ -1,4 +1,4 @@
1
- import { getTheme, getPortColor, getPortRingColor, TYPE_ABBREVIATIONS, NODE_ICON_PATHS } from './theme.js';
1
+ import { getTheme, getPortColor, getPortRingColor, TYPE_ABBREVIATIONS, NODE_ICON_PATHS, NODE_DEFAULT_COLOR } from './theme.js';
2
2
  import { PORT_RADIUS, BORDER_RADIUS, LABEL_HEIGHT, LABEL_GAP, SCOPE_PORT_COLUMN, measureText } from './geometry.js';
3
3
  function escapeXml(str) {
4
4
  return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
@@ -113,10 +113,10 @@ function renderScopeConnection(parts, conn, allConnections, parentNodeId) {
113
113
  // ---- Node rendering ----
114
114
  /** Render node body rect + icon */
115
115
  function renderNodeBody(parts, node, theme, indent) {
116
- const strokeColor = node.color !== '#334155' ? node.color : theme.nodeIconColor;
116
+ const strokeColor = node.color !== NODE_DEFAULT_COLOR ? node.color : theme.nodeIconColor;
117
117
  parts.push(`${indent}<rect x="${node.x}" y="${node.y}" width="${node.width}" height="${node.height}" rx="${BORDER_RADIUS}" fill="${theme.nodeFill}" stroke="${strokeColor}" stroke-width="2" filter="url(#node-shadow)"/>`);
118
118
  const iconPath = NODE_ICON_PATHS[node.icon] ?? NODE_ICON_PATHS.code;
119
- const iconColor = node.color !== '#334155' ? node.color : theme.nodeIconColor;
119
+ const iconColor = node.color !== NODE_DEFAULT_COLOR ? node.color : theme.nodeIconColor;
120
120
  const iconSize = 40;
121
121
  const iconX = node.x + (node.width - iconSize) / 2;
122
122
  const iconY = node.y + (node.height - iconSize) / 2;
@@ -126,8 +126,8 @@ function renderNode(node, theme, themeName, allConnections) {
126
126
  const parts = [];
127
127
  parts.push(` <g data-node-id="${escapeXml(node.id)}">`);
128
128
  if (node.scopeChildren && node.scopeChildren.length > 0) {
129
- // Scoped node: body rect only (no icon), then scope internals
130
- const strokeColor = node.color !== '#334155' ? node.color : theme.nodeIconColor;
129
+ // Scoped node: body rect only (icon omitted children occupy the inner area)
130
+ const strokeColor = node.color !== NODE_DEFAULT_COLOR ? node.color : theme.nodeIconColor;
131
131
  parts.push(` <rect x="${node.x}" y="${node.y}" width="${node.width}" height="${node.height}" rx="${BORDER_RADIUS}" fill="${theme.nodeFill}" stroke="${strokeColor}" stroke-width="2" filter="url(#node-shadow)"/>`);
132
132
  renderScopedContent(parts, node, theme, themeName, allConnections);
133
133
  }
@@ -178,7 +178,7 @@ function renderNodeLabel(parts, node, theme) {
178
178
  const labelAnchor = isScoped ? 'start' : 'middle';
179
179
  parts.push(` <g data-label-for="${escapeXml(node.id)}">`);
180
180
  parts.push(` <rect x="${labelBgX}" y="${labelBgY}" width="${labelBgWidth}" height="${labelBgHeight}" rx="6" fill="${theme.labelBadgeFill}" opacity="0.8"/>`);
181
- parts.push(` <text class="node-label" x="${labelTextX}" y="${labelBgY + labelBgHeight / 2 + 6}" text-anchor="${labelAnchor}" fill="${node.color !== '#334155' ? node.color : theme.labelColor}">${labelText}</text>`);
181
+ parts.push(` <text class="node-label" x="${labelTextX}" y="${labelBgY + labelBgHeight / 2 + 6}" text-anchor="${labelAnchor}" fill="${node.color !== NODE_DEFAULT_COLOR ? node.color : theme.labelColor}">${labelText}</text>`);
182
182
  parts.push(` </g>`);
183
183
  }
184
184
  /** Render port labels for a node if showPortLabels is enabled */
@@ -1,5 +1,6 @@
1
1
  import type { TDataType } from '../ast/types.js';
2
2
  import type { ThemePalette } from './types.js';
3
+ export declare const NODE_DEFAULT_COLOR = "#334155";
3
4
  export declare const NODE_VARIANT_COLORS: Record<string, {
4
5
  border: string;
5
6
  darkBorder: string;
@@ -26,16 +26,17 @@ const LIGHT_FAILURE_COLOR = '#e34646'; // red-shade-2
26
26
  // ---- Node theme variant colors ----
27
27
  // Dark: border = X-dark-shade-1 (base), icon = X-dark-shade-3
28
28
  // Light: border = X-shade-2
29
+ export const NODE_DEFAULT_COLOR = '#334155';
29
30
  export const NODE_VARIANT_COLORS = {
30
31
  blue: { border: '#548ce3', darkBorder: '#5e9eff' }, // blue-shade-2 / blue-dark-shade-1
31
32
  purple: { border: '#9f5fe3', darkBorder: '#b36bff' }, // purple-shade-2 / purple-dark-shade-1
32
33
  cyan: { border: '#63ccc4', darkBorder: '#6fe5dc' }, // cyan-shade-2 / cyan-dark-shade-1
34
+ teal: { border: '#63ccc4', darkBorder: '#6fe5dc' }, // alias for cyan
33
35
  orange: { border: '#e3732d', darkBorder: '#ff8133' }, // orange-shade-2 / orange-dark-shade-1
34
36
  pink: { border: '#e349c2', darkBorder: '#ff52da' }, // pink-shade-2 / pink-dark-shade-1
35
37
  green: { border: '#0ec850', darkBorder: '#10e15a' }, // green-shade-2 / green-dark-shade-1
36
38
  red: { border: '#e34646', darkBorder: '#ff4f4f' }, // red-shade-2 / red-dark-shade-1
37
39
  yellow: { border: '#e3a82b', darkBorder: '#ffbd30' }, // yellow-shade-2 / yellow-dark-shade-1
38
- teal: { border: '#3db0a8', darkBorder: '#4dc7be' }, // teal-shade-2 / teal-dark-shade-1
39
40
  };
40
41
  // ---- Theme palettes (exact values from token system) ----
41
42
  const DARK_PALETTE = {
@@ -100,7 +100,7 @@ const ANNOTATION_VALUES = {
100
100
  },
101
101
  {
102
102
  label: 'teal',
103
- detail: 'Teal node color',
103
+ detail: 'Teal node color (alias for cyan)',
104
104
  insertText: 'teal',
105
105
  insertTextFormat: 'plain',
106
106
  kind: 'value',
@@ -1,2 +1,2 @@
1
- export declare const VERSION = "0.17.9";
1
+ export declare const VERSION = "0.17.11";
2
2
  //# sourceMappingURL=generated-version.d.ts.map
@@ -1,3 +1,3 @@
1
1
  // Auto-generated by scripts/generate-version.ts — do not edit manually
2
- export const VERSION = '0.17.9';
2
+ export const VERSION = '0.17.11';
3
3
  //# sourceMappingURL=generated-version.js.map
@@ -522,7 +522,7 @@ export class JSDocParser {
522
522
  if (!result) {
523
523
  return;
524
524
  }
525
- const { name, defaultValue, isOptional, scope, order, mergeStrategy, hidden, description } = result;
525
+ const { name, defaultValue, isOptional, scope, order, mergeStrategy, hidden, description, customMetadata } = result;
526
526
  // Infer type from signature or scope callback return type
527
527
  let type;
528
528
  let tsType;
@@ -599,7 +599,9 @@ export class JSDocParser {
599
599
  ...(scope && { scope }),
600
600
  ...(mergeStrategy && { mergeStrategy: mergeStrategy }),
601
601
  ...(hidden && { hidden }),
602
- ...(order !== undefined && { metadata: { order } }),
602
+ ...((order !== undefined || customMetadata) && {
603
+ metadata: { ...(order !== undefined && { order }), ...customMetadata },
604
+ }),
603
605
  ...(tsType && { tsType }),
604
606
  };
605
607
  }
@@ -613,7 +615,7 @@ export class JSDocParser {
613
615
  if (!result) {
614
616
  return;
615
617
  }
616
- const { name, scope, order, hidden, description } = result;
618
+ const { name, scope, order, hidden, description, customMetadata } = result;
617
619
  // Infer type from return type or scope callback parameter
618
620
  let type;
619
621
  let tsType;
@@ -683,7 +685,9 @@ export class JSDocParser {
683
685
  label: description?.trim(),
684
686
  ...(scope && { scope }),
685
687
  ...(hidden && { hidden }),
686
- ...(order !== undefined && { metadata: { order } }),
688
+ ...((order !== undefined || customMetadata) && {
689
+ metadata: { ...(order !== undefined && { order }), ...customMetadata },
690
+ }),
687
691
  ...(tsType && { tsType }),
688
692
  };
689
693
  }
@@ -727,7 +731,7 @@ export class JSDocParser {
727
731
  if (!result) {
728
732
  return;
729
733
  }
730
- const { name, order, description } = result;
734
+ const { name, order, description, customMetadata } = result;
731
735
  // Infer type from return type signature
732
736
  let type = 'ANY';
733
737
  if (isSuccessPort(name) || isFailurePort(name)) {
@@ -753,7 +757,9 @@ export class JSDocParser {
753
757
  config.returnPorts[name] = {
754
758
  dataType: type,
755
759
  label: description?.trim(),
756
- ...(order !== undefined && { metadata: { order } }),
760
+ ...((order !== undefined || customMetadata) && {
761
+ metadata: { ...(order !== undefined && { order }), ...customMetadata },
762
+ }),
757
763
  };
758
764
  }
759
765
  /**
@@ -481,7 +481,7 @@ These annotations go on `@flowWeaver nodeType` blocks:
481
481
  | `@name` | Override display name | `@name MyCustomName` |
482
482
  | `@label` | Human-readable label | `@label Fetch with Timeout` |
483
483
  | `@description` | Node description | `@description Validates expense data` |
484
- | `@color` | Custom color | `@color "#ff6b35"` |
484
+ | `@color` | Custom color | `@color purple` or `@color "#ff6b35"` |
485
485
  | `@icon` | Custom icon | `@icon "database"` |
486
486
  | `@tag` | Visual tag/badge | `@tag async` or `@tag beta "Experimental"` |
487
487
  | `@scope` | Provides a named scope | `@scope processItem` |
@@ -491,6 +491,42 @@ These annotations go on `@flowWeaver nodeType` blocks:
491
491
 
492
492
  ---
493
493
 
494
+ ## Available Colors
495
+
496
+ Named colors adapt to the diagram theme (dark/light). You can also pass any hex color directly (e.g. `@color "#ff6b35"`).
497
+
498
+ `blue` · `purple` · `cyan` · `orange` · `pink` · `green` · `red` · `yellow` · `teal` (alias for cyan)
499
+
500
+ ## Available Icons
501
+
502
+ Icons render inside the node body in SVG diagrams. Names correspond to Material Symbols (outlined, weight 500).
503
+
504
+ **AI & ML:** `psychology` · `smartToy` · `autoAwesome` · `modelTraining` · `science` · `biotech`
505
+
506
+ **Data & storage:** `database` · `dataObject` · `tableChart` · `token` · `storage` · `memory`
507
+
508
+ **Cloud & network:** `api` · `webhook` · `cloudSync` · `cloudUpload` · `cloudDownload` · `dns` · `router` · `http` · `link`
509
+
510
+ **Security & auth:** `key` · `shield` · `vpnKey` · `verified` · `security` · `policy` · `adminPanelSettings`
511
+
512
+ **Logic & flow:** `altRoute` · `callSplit` · `callMerge` · `rule` · `filterAlt` · `repeat` · `sort`
513
+
514
+ **Actions & status:** `bolt` · `build` · `rocketLaunch` · `send` · `sync` · `refresh`
515
+
516
+ **Communication:** `notifications` · `email` · `campaign`
517
+
518
+ **Scheduling:** `event` · `schedule` · `timer`
519
+
520
+ **General tools:** `terminal` · `settings` · `tune` · `search` · `save` · `upload` · `download` · `edit` · `delete`
521
+
522
+ **Status:** `checkCircle` · `error` · `warning` · `info` · `help` · `visibility`
523
+
524
+ **Files:** `folder` · `description` · `attachFile`
525
+
526
+ **Structural:** `code` (default) · `flow` (workflow nodes) · `startNode` · `exitNode`
527
+
528
+ ---
529
+
494
530
  ## Related Topics
495
531
 
496
532
  - [Concepts](concepts) — Core workflow fundamentals
@@ -137,6 +137,7 @@ Expression nodes are pure functions where:
137
137
  - Primitive/array return -> single output port
138
138
  - Object return `{ a, b }` -> one port per property
139
139
  - Best for: transformers, math, utilities, data mapping, async fetchers, API calls
140
+ - Optional `@color` and `@icon` annotations customize the node's appearance in SVG diagrams (see `advanced-annotations` for available values)
140
141
 
141
142
  > **Start with expression mode.** Only switch to normal mode when you need to return data alongside a failure (error-with-data patterns) or for void side-effect functions. Expression nodes handle success/failure branching automatically — throw to trigger the `onFailure` path.
142
143
 
@@ -244,7 +244,7 @@ Multiple attribute brackets are allowed (zero or more). Attributes can be split
244
244
  @node myAdd Add [minimized, label: "Compact"]
245
245
  @node myAdd Add [pullExecution: trigger]
246
246
  @node myAdd Add [size: 200 150]
247
- @node myAdd Add [color: "#ff0000", icon: "math-plus"]
247
+ @node myAdd Add [color: "red", icon: "database"]
248
248
  @node myAdd Add [tags: "math" "Math operation", "transform"]
249
249
  @node myAdd Add [position: 180 0]
250
250
  @node myAdd Add [label: "hi"] [color: "#f00"] [position: 360 0]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@synergenius/flow-weaver",
3
- "version": "0.17.9",
3
+ "version": "0.17.11",
4
4
  "description": "Deterministic workflow compiler for AI agents. Compiles to standalone TypeScript, no runtime dependencies.",
5
5
  "private": false,
6
6
  "type": "module",