@timo9378/flow2code 0.1.4 → 0.1.6

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/compiler.cjs CHANGED
@@ -1831,21 +1831,22 @@ function compile(ir, options) {
1831
1831
  errors: validation.errors.map((e) => `[${e.code}] ${e.message}`)
1832
1832
  };
1833
1833
  }
1834
+ const workingIR = validation.migrated && validation.migratedIR ? validation.migratedIR : ir;
1834
1835
  let plan;
1835
1836
  try {
1836
- plan = topologicalSort(ir);
1837
+ plan = topologicalSort(workingIR);
1837
1838
  } catch (err) {
1838
1839
  return {
1839
1840
  success: false,
1840
1841
  errors: [err instanceof Error ? err.message : String(err)]
1841
1842
  };
1842
1843
  }
1843
- const nodeMap = new Map(ir.nodes.map((n) => [n.id, n]));
1844
+ const nodeMap = new Map(workingIR.nodes.map((n) => [n.id, n]));
1844
1845
  const platformName = options?.platform ?? "nextjs";
1845
1846
  const platform = getPlatform(platformName);
1846
- const symbolTable = buildSymbolTable(ir);
1847
+ const symbolTable = buildSymbolTable(workingIR);
1847
1848
  const context = {
1848
- ir,
1849
+ ir: workingIR,
1849
1850
  plan,
1850
1851
  nodeMap,
1851
1852
  envVars: /* @__PURE__ */ new Set(),
@@ -1862,8 +1863,8 @@ function compile(ir, options) {
1862
1863
  generatedBlockNodeIds: /* @__PURE__ */ new Set(),
1863
1864
  pluginRegistry
1864
1865
  };
1865
- const trigger = ir.nodes.find((n) => n.category === "trigger" /* TRIGGER */);
1866
- const preComputedBlockNodes = computeControlFlowDescendants(ir, trigger.id);
1866
+ const trigger = workingIR.nodes.find((n) => n.category === "trigger" /* TRIGGER */);
1867
+ const preComputedBlockNodes = computeControlFlowDescendants(workingIR, trigger.id);
1867
1868
  for (const nodeId of preComputedBlockNodes) {
1868
1869
  context.childBlockNodeIds.add(nodeId);
1869
1870
  context.symbolTableExclusions.add(nodeId);
@@ -1873,7 +1874,7 @@ function compile(ir, options) {
1873
1874
  );
1874
1875
  if (hasConcurrency) {
1875
1876
  context.dagMode = true;
1876
- for (const node of ir.nodes) {
1877
+ for (const node of workingIR.nodes) {
1877
1878
  if (node.category !== "trigger" /* TRIGGER */) {
1878
1879
  context.symbolTableExclusions.add(node.id);
1879
1880
  }
@@ -1888,8 +1889,8 @@ function compile(ir, options) {
1888
1889
  });
1889
1890
  const code = sourceFile.getFullText();
1890
1891
  const filePath = platform.getOutputFilePath(trigger);
1891
- collectRequiredPackages(ir, context);
1892
- const sourceMap = buildSourceMap(code, ir, filePath);
1892
+ collectRequiredPackages(workingIR, context);
1893
+ const sourceMap = buildSourceMap(code, workingIR, filePath);
1893
1894
  const dependencies = {
1894
1895
  all: [...context.requiredPackages].sort(),
1895
1896
  missing: [...context.requiredPackages].sort(),
@@ -2093,7 +2094,7 @@ function generateNodeChainDAG(writer, triggerId, context) {
2093
2094
  generateNodeBody(writer, node, context);
2094
2095
  });
2095
2096
  writer.writeLine(`)();`);
2096
- writer.writeLine(`${promiseVar}.catch(() => {});`);
2097
+ writer.writeLine(`${promiseVar}.catch((err) => { console.error("[Flow2Code DAG Error]", err); });`);
2097
2098
  writer.blankLine();
2098
2099
  }
2099
2100
  if (dagNodeIds.length > 0) {
@@ -2143,11 +2144,8 @@ function generateConcurrentNodes(writer, nodeIds, context) {
2143
2144
  writer.writeLine(";");
2144
2145
  }
2145
2146
  writer.writeLine(
2146
- `const [${taskNames.map((_, i) => `r${i}`).join(", ")}] = await Promise.all([${taskNames.map((t) => `${t}()`).join(", ")}]);`
2147
+ `await Promise.all([${taskNames.map((t) => `${t}()`).join(", ")}]);`
2147
2148
  );
2148
- activeNodeIds.forEach((nodeId, i) => {
2149
- writer.writeLine(`flowState['${nodeId}'] = r${i};`);
2150
- });
2151
2149
  activeNodeIds.forEach((nodeId) => {
2152
2150
  const varName = context.symbolTable.getVarName(nodeId);
2153
2151
  writer.writeLine(`const ${varName} = flowState['${nodeId}'];`);
@@ -2166,7 +2164,18 @@ function generateNodeBody(writer, node, context) {
2166
2164
  const plugin = context.pluginRegistry.get(node.nodeType);
2167
2165
  if (plugin) {
2168
2166
  const pluginCtx = createPluginContext(context);
2169
- plugin.generate(node, writer, pluginCtx);
2167
+ try {
2168
+ plugin.generate(node, writer, pluginCtx);
2169
+ } catch (err) {
2170
+ const errMsg = err instanceof Error ? err.message : String(err);
2171
+ const stack = err instanceof Error ? err.stack : void 0;
2172
+ throw new Error(
2173
+ `[flow2code] Plugin "${node.nodeType}" threw an error while generating node "${node.label}" (${node.id}):
2174
+ ${errMsg}` + (stack ? `
2175
+ Stack: ${stack}` : ""),
2176
+ { cause: err }
2177
+ );
2178
+ }
2170
2179
  } else {
2171
2180
  throw new Error(
2172
2181
  `[flow2code] Unsupported node type: "${node.nodeType}". Register a plugin via pluginRegistry.register() or use a built-in node type.`
@@ -2174,6 +2183,13 @@ function generateNodeBody(writer, node, context) {
2174
2183
  }
2175
2184
  }
2176
2185
  function createPluginContext(context) {
2186
+ const pendingChildBlockIds = [];
2187
+ const applyChildBlockRegistration = (nodeId) => {
2188
+ pendingChildBlockIds.push(nodeId);
2189
+ context.childBlockNodeIds.add(nodeId);
2190
+ context.symbolTableExclusions.add(nodeId);
2191
+ context.generatedBlockNodeIds.add(nodeId);
2192
+ };
2177
2193
  return {
2178
2194
  ir: context.ir,
2179
2195
  nodeMap: context.nodeMap,
@@ -2199,9 +2215,7 @@ function createPluginContext(context) {
2199
2215
  return resolveEnvVars(url, context);
2200
2216
  },
2201
2217
  generateChildNode(writer, node) {
2202
- context.childBlockNodeIds.add(node.id);
2203
- context.symbolTableExclusions.add(node.id);
2204
- context.generatedBlockNodeIds.add(node.id);
2218
+ applyChildBlockRegistration(node.id);
2205
2219
  writer.writeLine(`// --- ${node.label} (${node.nodeType}) [${node.id}] ---`);
2206
2220
  generateNodeBody(writer, node, context);
2207
2221
  generateBlockContinuation(writer, node.id, context);
@@ -2245,27 +2259,27 @@ function collectRequiredPackages(ir, context) {
2245
2259
  function buildSourceMap(code, ir, filePath) {
2246
2260
  const lines = code.split("\n");
2247
2261
  const mappings = {};
2248
- const nodeMarkerRegex = /^[\s]*\/\/ --- .+? \(.+?\) \[(.+?)\] ---$/;
2262
+ const validNodeIds = new Set(ir.nodes.map((n) => n.id));
2249
2263
  let currentNodeId = null;
2250
2264
  let currentStartLine = 0;
2251
2265
  for (let i = 0; i < lines.length; i++) {
2252
2266
  const lineNum = i + 1;
2253
- const match = lines[i].match(nodeMarkerRegex);
2254
- if (match) {
2255
- if (currentNodeId) {
2256
- mappings[currentNodeId] = {
2257
- startLine: currentStartLine,
2258
- endLine: lineNum - 1
2259
- };
2260
- }
2261
- const [, nodeId] = match;
2262
- if (ir.nodes.some((n) => n.id === nodeId)) {
2263
- currentNodeId = nodeId;
2264
- currentStartLine = lineNum;
2265
- } else {
2266
- currentNodeId = null;
2267
- }
2267
+ const line = lines[i];
2268
+ const bracketOpen = line.indexOf("[");
2269
+ const bracketClose = line.indexOf("] ---", bracketOpen);
2270
+ if (bracketOpen === -1 || bracketClose === -1) continue;
2271
+ const trimmed = line.trimStart();
2272
+ if (!trimmed.startsWith("//")) continue;
2273
+ const candidateId = line.slice(bracketOpen + 1, bracketClose);
2274
+ if (!validNodeIds.has(candidateId)) continue;
2275
+ if (currentNodeId) {
2276
+ mappings[currentNodeId] = {
2277
+ startLine: currentStartLine,
2278
+ endLine: lineNum - 1
2279
+ };
2268
2280
  }
2281
+ currentNodeId = candidateId;
2282
+ currentStartLine = lineNum;
2269
2283
  }
2270
2284
  if (currentNodeId) {
2271
2285
  mappings[currentNodeId] = {
package/dist/compiler.js CHANGED
@@ -1764,21 +1764,22 @@ function compile(ir, options) {
1764
1764
  errors: validation.errors.map((e) => `[${e.code}] ${e.message}`)
1765
1765
  };
1766
1766
  }
1767
+ const workingIR = validation.migrated && validation.migratedIR ? validation.migratedIR : ir;
1767
1768
  let plan;
1768
1769
  try {
1769
- plan = topologicalSort(ir);
1770
+ plan = topologicalSort(workingIR);
1770
1771
  } catch (err) {
1771
1772
  return {
1772
1773
  success: false,
1773
1774
  errors: [err instanceof Error ? err.message : String(err)]
1774
1775
  };
1775
1776
  }
1776
- const nodeMap = new Map(ir.nodes.map((n) => [n.id, n]));
1777
+ const nodeMap = new Map(workingIR.nodes.map((n) => [n.id, n]));
1777
1778
  const platformName = options?.platform ?? "nextjs";
1778
1779
  const platform = getPlatform(platformName);
1779
- const symbolTable = buildSymbolTable(ir);
1780
+ const symbolTable = buildSymbolTable(workingIR);
1780
1781
  const context = {
1781
- ir,
1782
+ ir: workingIR,
1782
1783
  plan,
1783
1784
  nodeMap,
1784
1785
  envVars: /* @__PURE__ */ new Set(),
@@ -1795,8 +1796,8 @@ function compile(ir, options) {
1795
1796
  generatedBlockNodeIds: /* @__PURE__ */ new Set(),
1796
1797
  pluginRegistry
1797
1798
  };
1798
- const trigger = ir.nodes.find((n) => n.category === "trigger" /* TRIGGER */);
1799
- const preComputedBlockNodes = computeControlFlowDescendants(ir, trigger.id);
1799
+ const trigger = workingIR.nodes.find((n) => n.category === "trigger" /* TRIGGER */);
1800
+ const preComputedBlockNodes = computeControlFlowDescendants(workingIR, trigger.id);
1800
1801
  for (const nodeId of preComputedBlockNodes) {
1801
1802
  context.childBlockNodeIds.add(nodeId);
1802
1803
  context.symbolTableExclusions.add(nodeId);
@@ -1806,7 +1807,7 @@ function compile(ir, options) {
1806
1807
  );
1807
1808
  if (hasConcurrency) {
1808
1809
  context.dagMode = true;
1809
- for (const node of ir.nodes) {
1810
+ for (const node of workingIR.nodes) {
1810
1811
  if (node.category !== "trigger" /* TRIGGER */) {
1811
1812
  context.symbolTableExclusions.add(node.id);
1812
1813
  }
@@ -1821,8 +1822,8 @@ function compile(ir, options) {
1821
1822
  });
1822
1823
  const code = sourceFile.getFullText();
1823
1824
  const filePath = platform.getOutputFilePath(trigger);
1824
- collectRequiredPackages(ir, context);
1825
- const sourceMap = buildSourceMap(code, ir, filePath);
1825
+ collectRequiredPackages(workingIR, context);
1826
+ const sourceMap = buildSourceMap(code, workingIR, filePath);
1826
1827
  const dependencies = {
1827
1828
  all: [...context.requiredPackages].sort(),
1828
1829
  missing: [...context.requiredPackages].sort(),
@@ -2026,7 +2027,7 @@ function generateNodeChainDAG(writer, triggerId, context) {
2026
2027
  generateNodeBody(writer, node, context);
2027
2028
  });
2028
2029
  writer.writeLine(`)();`);
2029
- writer.writeLine(`${promiseVar}.catch(() => {});`);
2030
+ writer.writeLine(`${promiseVar}.catch((err) => { console.error("[Flow2Code DAG Error]", err); });`);
2030
2031
  writer.blankLine();
2031
2032
  }
2032
2033
  if (dagNodeIds.length > 0) {
@@ -2076,11 +2077,8 @@ function generateConcurrentNodes(writer, nodeIds, context) {
2076
2077
  writer.writeLine(";");
2077
2078
  }
2078
2079
  writer.writeLine(
2079
- `const [${taskNames.map((_, i) => `r${i}`).join(", ")}] = await Promise.all([${taskNames.map((t) => `${t}()`).join(", ")}]);`
2080
+ `await Promise.all([${taskNames.map((t) => `${t}()`).join(", ")}]);`
2080
2081
  );
2081
- activeNodeIds.forEach((nodeId, i) => {
2082
- writer.writeLine(`flowState['${nodeId}'] = r${i};`);
2083
- });
2084
2082
  activeNodeIds.forEach((nodeId) => {
2085
2083
  const varName = context.symbolTable.getVarName(nodeId);
2086
2084
  writer.writeLine(`const ${varName} = flowState['${nodeId}'];`);
@@ -2099,7 +2097,18 @@ function generateNodeBody(writer, node, context) {
2099
2097
  const plugin = context.pluginRegistry.get(node.nodeType);
2100
2098
  if (plugin) {
2101
2099
  const pluginCtx = createPluginContext(context);
2102
- plugin.generate(node, writer, pluginCtx);
2100
+ try {
2101
+ plugin.generate(node, writer, pluginCtx);
2102
+ } catch (err) {
2103
+ const errMsg = err instanceof Error ? err.message : String(err);
2104
+ const stack = err instanceof Error ? err.stack : void 0;
2105
+ throw new Error(
2106
+ `[flow2code] Plugin "${node.nodeType}" threw an error while generating node "${node.label}" (${node.id}):
2107
+ ${errMsg}` + (stack ? `
2108
+ Stack: ${stack}` : ""),
2109
+ { cause: err }
2110
+ );
2111
+ }
2103
2112
  } else {
2104
2113
  throw new Error(
2105
2114
  `[flow2code] Unsupported node type: "${node.nodeType}". Register a plugin via pluginRegistry.register() or use a built-in node type.`
@@ -2107,6 +2116,13 @@ function generateNodeBody(writer, node, context) {
2107
2116
  }
2108
2117
  }
2109
2118
  function createPluginContext(context) {
2119
+ const pendingChildBlockIds = [];
2120
+ const applyChildBlockRegistration = (nodeId) => {
2121
+ pendingChildBlockIds.push(nodeId);
2122
+ context.childBlockNodeIds.add(nodeId);
2123
+ context.symbolTableExclusions.add(nodeId);
2124
+ context.generatedBlockNodeIds.add(nodeId);
2125
+ };
2110
2126
  return {
2111
2127
  ir: context.ir,
2112
2128
  nodeMap: context.nodeMap,
@@ -2132,9 +2148,7 @@ function createPluginContext(context) {
2132
2148
  return resolveEnvVars(url, context);
2133
2149
  },
2134
2150
  generateChildNode(writer, node) {
2135
- context.childBlockNodeIds.add(node.id);
2136
- context.symbolTableExclusions.add(node.id);
2137
- context.generatedBlockNodeIds.add(node.id);
2151
+ applyChildBlockRegistration(node.id);
2138
2152
  writer.writeLine(`// --- ${node.label} (${node.nodeType}) [${node.id}] ---`);
2139
2153
  generateNodeBody(writer, node, context);
2140
2154
  generateBlockContinuation(writer, node.id, context);
@@ -2178,27 +2192,27 @@ function collectRequiredPackages(ir, context) {
2178
2192
  function buildSourceMap(code, ir, filePath) {
2179
2193
  const lines = code.split("\n");
2180
2194
  const mappings = {};
2181
- const nodeMarkerRegex = /^[\s]*\/\/ --- .+? \(.+?\) \[(.+?)\] ---$/;
2195
+ const validNodeIds = new Set(ir.nodes.map((n) => n.id));
2182
2196
  let currentNodeId = null;
2183
2197
  let currentStartLine = 0;
2184
2198
  for (let i = 0; i < lines.length; i++) {
2185
2199
  const lineNum = i + 1;
2186
- const match = lines[i].match(nodeMarkerRegex);
2187
- if (match) {
2188
- if (currentNodeId) {
2189
- mappings[currentNodeId] = {
2190
- startLine: currentStartLine,
2191
- endLine: lineNum - 1
2192
- };
2193
- }
2194
- const [, nodeId] = match;
2195
- if (ir.nodes.some((n) => n.id === nodeId)) {
2196
- currentNodeId = nodeId;
2197
- currentStartLine = lineNum;
2198
- } else {
2199
- currentNodeId = null;
2200
- }
2200
+ const line = lines[i];
2201
+ const bracketOpen = line.indexOf("[");
2202
+ const bracketClose = line.indexOf("] ---", bracketOpen);
2203
+ if (bracketOpen === -1 || bracketClose === -1) continue;
2204
+ const trimmed = line.trimStart();
2205
+ if (!trimmed.startsWith("//")) continue;
2206
+ const candidateId = line.slice(bracketOpen + 1, bracketClose);
2207
+ if (!validNodeIds.has(candidateId)) continue;
2208
+ if (currentNodeId) {
2209
+ mappings[currentNodeId] = {
2210
+ startLine: currentStartLine,
2211
+ endLine: lineNum - 1
2212
+ };
2201
2213
  }
2214
+ currentNodeId = candidateId;
2215
+ currentStartLine = lineNum;
2202
2216
  }
2203
2217
  if (currentNodeId) {
2204
2218
  mappings[currentNodeId] = {
package/dist/server.js CHANGED
@@ -3,7 +3,7 @@
3
3
  // src/server/index.ts
4
4
  import { createServer } from "http";
5
5
  import { readFile, stat } from "fs/promises";
6
- import { join as join2, extname, dirname as dirname2 } from "path";
6
+ import { join as join2, extname, dirname as dirname2, resolve as resolve2 } from "path";
7
7
  import { fileURLToPath } from "url";
8
8
  import { existsSync as existsSync2 } from "fs";
9
9
 
@@ -1717,21 +1717,22 @@ function compile(ir, options) {
1717
1717
  errors: validation.errors.map((e) => `[${e.code}] ${e.message}`)
1718
1718
  };
1719
1719
  }
1720
+ const workingIR = validation.migrated && validation.migratedIR ? validation.migratedIR : ir;
1720
1721
  let plan;
1721
1722
  try {
1722
- plan = topologicalSort(ir);
1723
+ plan = topologicalSort(workingIR);
1723
1724
  } catch (err) {
1724
1725
  return {
1725
1726
  success: false,
1726
1727
  errors: [err instanceof Error ? err.message : String(err)]
1727
1728
  };
1728
1729
  }
1729
- const nodeMap = new Map(ir.nodes.map((n) => [n.id, n]));
1730
+ const nodeMap = new Map(workingIR.nodes.map((n) => [n.id, n]));
1730
1731
  const platformName = options?.platform ?? "nextjs";
1731
1732
  const platform = getPlatform(platformName);
1732
- const symbolTable = buildSymbolTable(ir);
1733
+ const symbolTable = buildSymbolTable(workingIR);
1733
1734
  const context = {
1734
- ir,
1735
+ ir: workingIR,
1735
1736
  plan,
1736
1737
  nodeMap,
1737
1738
  envVars: /* @__PURE__ */ new Set(),
@@ -1748,8 +1749,8 @@ function compile(ir, options) {
1748
1749
  generatedBlockNodeIds: /* @__PURE__ */ new Set(),
1749
1750
  pluginRegistry
1750
1751
  };
1751
- const trigger = ir.nodes.find((n) => n.category === "trigger" /* TRIGGER */);
1752
- const preComputedBlockNodes = computeControlFlowDescendants(ir, trigger.id);
1752
+ const trigger = workingIR.nodes.find((n) => n.category === "trigger" /* TRIGGER */);
1753
+ const preComputedBlockNodes = computeControlFlowDescendants(workingIR, trigger.id);
1753
1754
  for (const nodeId of preComputedBlockNodes) {
1754
1755
  context.childBlockNodeIds.add(nodeId);
1755
1756
  context.symbolTableExclusions.add(nodeId);
@@ -1759,7 +1760,7 @@ function compile(ir, options) {
1759
1760
  );
1760
1761
  if (hasConcurrency) {
1761
1762
  context.dagMode = true;
1762
- for (const node of ir.nodes) {
1763
+ for (const node of workingIR.nodes) {
1763
1764
  if (node.category !== "trigger" /* TRIGGER */) {
1764
1765
  context.symbolTableExclusions.add(node.id);
1765
1766
  }
@@ -1774,8 +1775,8 @@ function compile(ir, options) {
1774
1775
  });
1775
1776
  const code = sourceFile.getFullText();
1776
1777
  const filePath = platform.getOutputFilePath(trigger);
1777
- collectRequiredPackages(ir, context);
1778
- const sourceMap = buildSourceMap(code, ir, filePath);
1778
+ collectRequiredPackages(workingIR, context);
1779
+ const sourceMap = buildSourceMap(code, workingIR, filePath);
1779
1780
  const dependencies = {
1780
1781
  all: [...context.requiredPackages].sort(),
1781
1782
  missing: [...context.requiredPackages].sort(),
@@ -1979,7 +1980,7 @@ function generateNodeChainDAG(writer, triggerId, context) {
1979
1980
  generateNodeBody(writer, node, context);
1980
1981
  });
1981
1982
  writer.writeLine(`)();`);
1982
- writer.writeLine(`${promiseVar}.catch(() => {});`);
1983
+ writer.writeLine(`${promiseVar}.catch((err) => { console.error("[Flow2Code DAG Error]", err); });`);
1983
1984
  writer.blankLine();
1984
1985
  }
1985
1986
  if (dagNodeIds.length > 0) {
@@ -2029,11 +2030,8 @@ function generateConcurrentNodes(writer, nodeIds, context) {
2029
2030
  writer.writeLine(";");
2030
2031
  }
2031
2032
  writer.writeLine(
2032
- `const [${taskNames.map((_, i) => `r${i}`).join(", ")}] = await Promise.all([${taskNames.map((t) => `${t}()`).join(", ")}]);`
2033
+ `await Promise.all([${taskNames.map((t) => `${t}()`).join(", ")}]);`
2033
2034
  );
2034
- activeNodeIds.forEach((nodeId, i) => {
2035
- writer.writeLine(`flowState['${nodeId}'] = r${i};`);
2036
- });
2037
2035
  activeNodeIds.forEach((nodeId) => {
2038
2036
  const varName = context.symbolTable.getVarName(nodeId);
2039
2037
  writer.writeLine(`const ${varName} = flowState['${nodeId}'];`);
@@ -2052,7 +2050,18 @@ function generateNodeBody(writer, node, context) {
2052
2050
  const plugin = context.pluginRegistry.get(node.nodeType);
2053
2051
  if (plugin) {
2054
2052
  const pluginCtx = createPluginContext(context);
2055
- plugin.generate(node, writer, pluginCtx);
2053
+ try {
2054
+ plugin.generate(node, writer, pluginCtx);
2055
+ } catch (err) {
2056
+ const errMsg = err instanceof Error ? err.message : String(err);
2057
+ const stack = err instanceof Error ? err.stack : void 0;
2058
+ throw new Error(
2059
+ `[flow2code] Plugin "${node.nodeType}" threw an error while generating node "${node.label}" (${node.id}):
2060
+ ${errMsg}` + (stack ? `
2061
+ Stack: ${stack}` : ""),
2062
+ { cause: err }
2063
+ );
2064
+ }
2056
2065
  } else {
2057
2066
  throw new Error(
2058
2067
  `[flow2code] Unsupported node type: "${node.nodeType}". Register a plugin via pluginRegistry.register() or use a built-in node type.`
@@ -2060,6 +2069,13 @@ function generateNodeBody(writer, node, context) {
2060
2069
  }
2061
2070
  }
2062
2071
  function createPluginContext(context) {
2072
+ const pendingChildBlockIds = [];
2073
+ const applyChildBlockRegistration = (nodeId) => {
2074
+ pendingChildBlockIds.push(nodeId);
2075
+ context.childBlockNodeIds.add(nodeId);
2076
+ context.symbolTableExclusions.add(nodeId);
2077
+ context.generatedBlockNodeIds.add(nodeId);
2078
+ };
2063
2079
  return {
2064
2080
  ir: context.ir,
2065
2081
  nodeMap: context.nodeMap,
@@ -2085,9 +2101,7 @@ function createPluginContext(context) {
2085
2101
  return resolveEnvVars(url, context);
2086
2102
  },
2087
2103
  generateChildNode(writer, node) {
2088
- context.childBlockNodeIds.add(node.id);
2089
- context.symbolTableExclusions.add(node.id);
2090
- context.generatedBlockNodeIds.add(node.id);
2104
+ applyChildBlockRegistration(node.id);
2091
2105
  writer.writeLine(`// --- ${node.label} (${node.nodeType}) [${node.id}] ---`);
2092
2106
  generateNodeBody(writer, node, context);
2093
2107
  generateBlockContinuation(writer, node.id, context);
@@ -2131,27 +2145,27 @@ function collectRequiredPackages(ir, context) {
2131
2145
  function buildSourceMap(code, ir, filePath) {
2132
2146
  const lines = code.split("\n");
2133
2147
  const mappings = {};
2134
- const nodeMarkerRegex = /^[\s]*\/\/ --- .+? \(.+?\) \[(.+?)\] ---$/;
2148
+ const validNodeIds = new Set(ir.nodes.map((n) => n.id));
2135
2149
  let currentNodeId = null;
2136
2150
  let currentStartLine = 0;
2137
2151
  for (let i = 0; i < lines.length; i++) {
2138
2152
  const lineNum = i + 1;
2139
- const match = lines[i].match(nodeMarkerRegex);
2140
- if (match) {
2141
- if (currentNodeId) {
2142
- mappings[currentNodeId] = {
2143
- startLine: currentStartLine,
2144
- endLine: lineNum - 1
2145
- };
2146
- }
2147
- const [, nodeId] = match;
2148
- if (ir.nodes.some((n) => n.id === nodeId)) {
2149
- currentNodeId = nodeId;
2150
- currentStartLine = lineNum;
2151
- } else {
2152
- currentNodeId = null;
2153
- }
2153
+ const line = lines[i];
2154
+ const bracketOpen = line.indexOf("[");
2155
+ const bracketClose = line.indexOf("] ---", bracketOpen);
2156
+ if (bracketOpen === -1 || bracketClose === -1) continue;
2157
+ const trimmed = line.trimStart();
2158
+ if (!trimmed.startsWith("//")) continue;
2159
+ const candidateId = line.slice(bracketOpen + 1, bracketClose);
2160
+ if (!validNodeIds.has(candidateId)) continue;
2161
+ if (currentNodeId) {
2162
+ mappings[currentNodeId] = {
2163
+ startLine: currentStartLine,
2164
+ endLine: lineNum - 1
2165
+ };
2154
2166
  }
2167
+ currentNodeId = candidateId;
2168
+ currentStartLine = lineNum;
2155
2169
  }
2156
2170
  if (currentNodeId) {
2157
2171
  mappings[currentNodeId] = {
@@ -3469,7 +3483,9 @@ function handleCompile(body, projectRoot) {
3469
3483
  let writtenPath = null;
3470
3484
  if (shouldWrite && result.filePath && result.code) {
3471
3485
  const fullPath = resolve(join(projectRoot, result.filePath));
3472
- if (!fullPath.startsWith(resolve(projectRoot))) {
3486
+ const resolvedRoot = resolve(projectRoot);
3487
+ const sep = resolvedRoot.endsWith("/") || resolvedRoot.endsWith("\\") ? "" : process.platform === "win32" ? "\\" : "/";
3488
+ if (!fullPath.startsWith(resolvedRoot + sep)) {
3473
3489
  return {
3474
3490
  status: 400,
3475
3491
  body: { success: false, error: "Output path escapes project root" }
@@ -3687,6 +3703,66 @@ function handleDecompile(body) {
3687
3703
  }
3688
3704
  }
3689
3705
 
3706
+ // src/lib/logger.ts
3707
+ import pc from "picocolors";
3708
+ var LEVEL_ORDER = {
3709
+ debug: 0,
3710
+ info: 1,
3711
+ warn: 2,
3712
+ error: 3,
3713
+ silent: 4
3714
+ };
3715
+ var Logger = class {
3716
+ /** Minimum log level (default: "info"). Set to "silent" to suppress all output. */
3717
+ level = "info";
3718
+ /** Prefix for all log lines (default: "[flow2code]") */
3719
+ prefix = "[flow2code]";
3720
+ shouldLog(level) {
3721
+ return LEVEL_ORDER[level] >= LEVEL_ORDER[this.level];
3722
+ }
3723
+ debug(...args) {
3724
+ if (!this.shouldLog("debug")) return;
3725
+ console.log(pc.gray(`${this.prefix} ${pc.dim("DEBUG")}`), ...args);
3726
+ }
3727
+ info(...args) {
3728
+ if (!this.shouldLog("info")) return;
3729
+ console.log(pc.blue(`${this.prefix}`), ...args);
3730
+ }
3731
+ success(...args) {
3732
+ if (!this.shouldLog("info")) return;
3733
+ console.log(pc.green(`${this.prefix} \u2705`), ...args);
3734
+ }
3735
+ warn(...args) {
3736
+ if (!this.shouldLog("warn")) return;
3737
+ console.warn(pc.yellow(`${this.prefix} \u26A0\uFE0F`), ...args);
3738
+ }
3739
+ error(...args) {
3740
+ if (!this.shouldLog("error")) return;
3741
+ console.error(pc.red(`${this.prefix} \u274C`), ...args);
3742
+ }
3743
+ /** Print a blank line (respects silent mode) */
3744
+ blank() {
3745
+ if (!this.shouldLog("info")) return;
3746
+ console.log();
3747
+ }
3748
+ /** Print raw text without prefix (respects silent mode) */
3749
+ raw(...args) {
3750
+ if (!this.shouldLog("info")) return;
3751
+ console.log(...args);
3752
+ }
3753
+ /** Formatted key-value line for startup banners */
3754
+ kv(key, value) {
3755
+ if (!this.shouldLog("info")) return;
3756
+ console.log(` ${pc.dim("\u251C\u2500")} ${pc.bold(key)} ${value}`);
3757
+ }
3758
+ /** Last key-value line (uses └─) */
3759
+ kvLast(key, value) {
3760
+ if (!this.shouldLog("info")) return;
3761
+ console.log(` ${pc.dim("\u2514\u2500")} ${pc.bold(key)} ${value}`);
3762
+ }
3763
+ };
3764
+ var logger = new Logger();
3765
+
3690
3766
  // src/server/index.ts
3691
3767
  var __filename = fileURLToPath(import.meta.url);
3692
3768
  var __dirname = dirname2(__filename);
@@ -3768,7 +3844,7 @@ function sendJson(res, status, body) {
3768
3844
  }
3769
3845
  var MAX_BODY_SIZE = 2 * 1024 * 1024;
3770
3846
  async function readBody(req) {
3771
- return new Promise((resolve2, reject) => {
3847
+ return new Promise((resolve3, reject) => {
3772
3848
  const chunks = [];
3773
3849
  let totalSize = 0;
3774
3850
  req.on("data", (chunk) => {
@@ -3780,7 +3856,7 @@ async function readBody(req) {
3780
3856
  }
3781
3857
  chunks.push(chunk);
3782
3858
  });
3783
- req.on("end", () => resolve2(Buffer.concat(chunks).toString("utf-8")));
3859
+ req.on("end", () => resolve3(Buffer.concat(chunks).toString("utf-8")));
3784
3860
  req.on("error", reject);
3785
3861
  });
3786
3862
  }
@@ -3789,7 +3865,13 @@ async function parseJsonBody(req) {
3789
3865
  return JSON.parse(raw);
3790
3866
  }
3791
3867
  async function serveStatic(staticDir, pathname, res) {
3792
- let filePath = join2(staticDir, pathname === "/" ? "index.html" : pathname);
3868
+ const decodedPath = decodeURIComponent(pathname);
3869
+ let filePath = join2(staticDir, decodedPath === "/" ? "index.html" : decodedPath);
3870
+ const resolvedPath = resolve2(filePath);
3871
+ const resolvedStaticDir = resolve2(staticDir);
3872
+ if (!resolvedPath.startsWith(resolvedStaticDir + (resolvedStaticDir.endsWith("/") || resolvedStaticDir.endsWith("\\") ? "" : process.platform === "win32" ? "\\" : "/"))) {
3873
+ return false;
3874
+ }
3793
3875
  if (!extname(filePath)) {
3794
3876
  filePath += ".html";
3795
3877
  }
@@ -3877,7 +3959,7 @@ function startServer(options = {}) {
3877
3959
  const projectRoot = options.projectRoot ?? process.cwd();
3878
3960
  const server = createServer((req, res) => {
3879
3961
  handleRequest(req, res, staticDir, projectRoot).catch((err) => {
3880
- console.error("[flow2code] Internal error:", err);
3962
+ logger.error("Internal error:", err);
3881
3963
  res.writeHead(500, { "Content-Type": "text/plain" });
3882
3964
  res.end("Internal Server Error");
3883
3965
  });
@@ -3888,20 +3970,20 @@ function startServer(options = {}) {
3888
3970
  if (options.onReady) {
3889
3971
  options.onReady(url);
3890
3972
  } else {
3891
- console.log(`
3892
- \u{1F680} Flow2Code Dev Server`);
3893
- console.log(` \u251C\u2500 Local: ${url}`);
3894
- console.log(` \u251C\u2500 API: ${url}/api/compile`);
3895
- console.log(` \u251C\u2500 Static: ${staticDir}`);
3896
- console.log(` \u2514\u2500 Project: ${projectRoot}`);
3973
+ logger.blank();
3974
+ logger.info("Flow2Code Dev Server");
3975
+ logger.kv("Local:", url);
3976
+ logger.kv("API:", `${url}/api/compile`);
3977
+ logger.kv("Static:", staticDir);
3978
+ logger.kvLast("Project:", projectRoot);
3897
3979
  if (!hasUI) {
3898
- console.log();
3899
- console.log(` \u26A0\uFE0F UI files not found (out/index.html missing).`);
3900
- console.log(` The API endpoints still work, but the visual editor won't load.`);
3901
- console.log(` To fix: run "pnpm build:ui" in the flow2code source directory,`);
3902
- console.log(` or reinstall from npm: npm i @timo9378/flow2code@latest`);
3980
+ logger.blank();
3981
+ logger.warn("UI files not found (out/index.html missing).");
3982
+ logger.raw(" The API endpoints still work, but the visual editor won't load.");
3983
+ logger.raw(' To fix: run "pnpm build:ui" in the flow2code source directory,');
3984
+ logger.raw(" or reinstall from npm: npm i @timo9378/flow2code@latest");
3903
3985
  }
3904
- console.log();
3986
+ logger.blank();
3905
3987
  }
3906
3988
  });
3907
3989
  return server;