@timo9378/flow2code 0.1.2 → 0.1.4

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.
Files changed (49) hide show
  1. package/CHANGELOG.md +56 -56
  2. package/LICENSE +21 -21
  3. package/README.md +230 -223
  4. package/dist/cli.js +70 -5
  5. package/dist/compiler.cjs +9 -3
  6. package/dist/compiler.js +9 -3
  7. package/dist/server.d.ts +27 -27
  8. package/dist/server.js +882 -4
  9. package/out/404.html +1 -1
  10. package/out/__next.__PAGE__.txt +4 -4
  11. package/out/__next._full.txt +13 -13
  12. package/out/__next._head.txt +4 -4
  13. package/out/__next._index.txt +6 -6
  14. package/out/__next._tree.txt +2 -2
  15. package/out/_next/static/chunks/2be31674b47c1089.js +49 -0
  16. package/out/_next/static/chunks/{41ec06cb3e44c96a.js → 5f1a9fec0e69c483.js} +2 -2
  17. package/out/_next/static/chunks/6b84376656bd9887.js +1 -0
  18. package/out/_next/static/chunks/83ab8820627f8bfe.css +1 -0
  19. package/out/_next/static/chunks/a6dad97d9634a72d.js.map +1 -1
  20. package/out/_next/static/chunks/ab8888d4b78b94be.js +5 -0
  21. package/out/_next/static/chunks/acf223168ac429f7.js +1 -0
  22. package/out/_next/static/chunks/{8486af13ea541d4d.js → b112c2f519e4b429.js} +1 -1
  23. package/out/_next/static/chunks/{4b43c69e91d19eee.js → b163b5d7cccbcf42.js} +1 -1
  24. package/out/_next/static/chunks/b6e8711267bccbbd.js +1 -0
  25. package/out/_next/static/chunks/fbca595129527827.js +1 -0
  26. package/out/_next/static/chunks/{turbopack-b95472a0b02a2729.js → turbopack-576234c945ffdc44.js} +1 -1
  27. package/out/_not-found/__next._full.txt +11 -11
  28. package/out/_not-found/__next._head.txt +4 -4
  29. package/out/_not-found/__next._index.txt +6 -6
  30. package/out/_not-found/{__next._not-found.__PAGE__.txt → __next._not-found/__PAGE__.txt} +2 -2
  31. package/out/_not-found/__next._not-found.txt +3 -3
  32. package/out/_not-found/__next._tree.txt +2 -2
  33. package/out/_not-found.html +1 -1
  34. package/out/_not-found.txt +11 -11
  35. package/out/index.html +2 -2
  36. package/out/index.txt +13 -13
  37. package/package.json +124 -121
  38. package/scripts/postinstall.cjs +23 -0
  39. package/out/_next/static/chunks/324e22de375c241a.css +0 -1
  40. package/out/_next/static/chunks/3638bc7cb3d72bbe.js +0 -379
  41. package/out/_next/static/chunks/46e882944a7f3aed.js +0 -1
  42. package/out/_next/static/chunks/59b058cf94cc555e.js +0 -1
  43. package/out/_next/static/chunks/6e36daee8960e188.js +0 -5
  44. package/out/_next/static/chunks/80a5431d4e78c469.js +0 -1
  45. package/out/_next/static/chunks/8a38426c5ed81587.js +0 -1
  46. package/out/_next/static/chunks/edbd80df6004e249.js +0 -52
  47. /package/out/_next/static/{dSD-EjLFfv3PUm7xo33fg → OIo5LFjdea8u6u4evHHQo}/_buildManifest.js +0 -0
  48. /package/out/_next/static/{dSD-EjLFfv3PUm7xo33fg → OIo5LFjdea8u6u4evHHQo}/_clientMiddlewareManifest.json +0 -0
  49. /package/out/_next/static/{dSD-EjLFfv3PUm7xo33fg → OIo5LFjdea8u6u4evHHQo}/_ssgManifest.js +0 -0
package/dist/server.js CHANGED
@@ -2175,6 +2175,826 @@ function buildSourceMap(code, ir, filePath) {
2175
2175
  };
2176
2176
  }
2177
2177
 
2178
+ // src/lib/compiler/decompiler.ts
2179
+ import {
2180
+ Project as Project2,
2181
+ SyntaxKind
2182
+ } from "ts-morph";
2183
+ function decompile(code, options = {}) {
2184
+ const errors = [];
2185
+ const { fileName = "input.ts", audit: enableAudit = true } = options;
2186
+ try {
2187
+ const project = new Project2({ useInMemoryFileSystem: true });
2188
+ const sourceFile = project.createSourceFile(fileName, code);
2189
+ const targetFn = findTargetFunction(sourceFile, options.functionName);
2190
+ if (!targetFn) {
2191
+ errors.push("No exported function found to decompile");
2192
+ return { success: false, errors, confidence: 0 };
2193
+ }
2194
+ const ctx = createDecompileContext();
2195
+ const trigger = createTriggerFromFunction(targetFn, sourceFile, ctx);
2196
+ ctx.addNode(trigger);
2197
+ const body = targetFn.fn.getBody();
2198
+ if (body && body.getKind() === SyntaxKind.Block) {
2199
+ walkBlock(body, trigger.id, ctx);
2200
+ }
2201
+ buildEdges(ctx);
2202
+ const auditHints = enableAudit ? computeAuditHints(ctx) : [];
2203
+ const now = (/* @__PURE__ */ new Date()).toISOString();
2204
+ const ir = {
2205
+ version: CURRENT_IR_VERSION,
2206
+ meta: {
2207
+ name: targetFn.name ?? fileName.replace(/\.(ts|tsx|js|jsx)$/, ""),
2208
+ description: `Decompiled from ${fileName}`,
2209
+ createdAt: now,
2210
+ updatedAt: now
2211
+ },
2212
+ nodes: ctx.getNodes(),
2213
+ edges: ctx.getEdges()
2214
+ };
2215
+ const nodeCount = ir.nodes.length;
2216
+ const hasControlFlow = ir.nodes.some((n) => n.category === "logic" /* LOGIC */);
2217
+ const confidence = Math.min(
2218
+ 0.95,
2219
+ 0.3 + nodeCount * 0.08 + (hasControlFlow ? 0.15 : 0)
2220
+ );
2221
+ return {
2222
+ success: true,
2223
+ ir,
2224
+ errors: errors.length > 0 ? errors : void 0,
2225
+ confidence,
2226
+ audit: auditHints.length > 0 ? auditHints : void 0
2227
+ };
2228
+ } catch (err) {
2229
+ return {
2230
+ success: false,
2231
+ errors: [err instanceof Error ? err.message : String(err)],
2232
+ confidence: 0
2233
+ };
2234
+ }
2235
+ }
2236
+ function createDecompileContext() {
2237
+ const ctx = {
2238
+ nodes: /* @__PURE__ */ new Map(),
2239
+ edgeList: [],
2240
+ varDefs: /* @__PURE__ */ new Map(),
2241
+ varUses: /* @__PURE__ */ new Map(),
2242
+ seqPredecessors: /* @__PURE__ */ new Map(),
2243
+ controlFlowChildren: /* @__PURE__ */ new Map(),
2244
+ auditData: [],
2245
+ counters: {},
2246
+ nodeLines: /* @__PURE__ */ new Map(),
2247
+ addNode(node, line) {
2248
+ ctx.nodes.set(node.id, node);
2249
+ if (line !== void 0) ctx.nodeLines.set(node.id, line);
2250
+ },
2251
+ getNodes() {
2252
+ return Array.from(ctx.nodes.values());
2253
+ },
2254
+ getEdges() {
2255
+ return ctx.edgeList;
2256
+ },
2257
+ nextId(prefix) {
2258
+ ctx.counters[prefix] = (ctx.counters[prefix] ?? 0) + 1;
2259
+ return `${prefix}_${ctx.counters[prefix]}`;
2260
+ }
2261
+ };
2262
+ return ctx;
2263
+ }
2264
+ function findTargetFunction(sourceFile, targetName) {
2265
+ const httpMethods = ["GET", "POST", "PUT", "PATCH", "DELETE"];
2266
+ if (targetName) {
2267
+ const fn = sourceFile.getFunction(targetName);
2268
+ if (fn) {
2269
+ const name = fn.getName();
2270
+ const httpMethod = name && httpMethods.includes(name.toUpperCase()) ? name.toUpperCase() : void 0;
2271
+ return { name: targetName, fn, httpMethod, isExported: fn.isExported() };
2272
+ }
2273
+ }
2274
+ for (const fn of sourceFile.getFunctions()) {
2275
+ if (!fn.isExported()) continue;
2276
+ const name = fn.getName();
2277
+ if (name && httpMethods.includes(name.toUpperCase())) {
2278
+ return { name, fn, httpMethod: name.toUpperCase(), isExported: true };
2279
+ }
2280
+ }
2281
+ for (const fn of sourceFile.getFunctions()) {
2282
+ if (fn.isExported()) {
2283
+ return { name: fn.getName(), fn, isExported: true };
2284
+ }
2285
+ }
2286
+ const allFns = sourceFile.getFunctions();
2287
+ if (allFns.length > 0) {
2288
+ const fn = allFns[0];
2289
+ return { name: fn.getName(), fn, isExported: fn.isExported() };
2290
+ }
2291
+ for (const stmt of sourceFile.getVariableStatements()) {
2292
+ if (!stmt.isExported()) continue;
2293
+ for (const decl of stmt.getDeclarations()) {
2294
+ const init = decl.getInitializer();
2295
+ if (init && init.getKind() === SyntaxKind.ArrowFunction) {
2296
+ return {
2297
+ name: decl.getName(),
2298
+ fn: init,
2299
+ isExported: true
2300
+ };
2301
+ }
2302
+ }
2303
+ }
2304
+ return null;
2305
+ }
2306
+ function createTriggerFromFunction(target, sourceFile, ctx) {
2307
+ const id = ctx.nextId("trigger");
2308
+ if (target.httpMethod) {
2309
+ const routePath = inferRoutePath(sourceFile);
2310
+ const method = target.httpMethod;
2311
+ const parseBody = method !== "GET";
2312
+ return {
2313
+ id,
2314
+ nodeType: "http_webhook" /* HTTP_WEBHOOK */,
2315
+ category: "trigger" /* TRIGGER */,
2316
+ label: `${method} ${routePath}`,
2317
+ params: { method, routePath, parseBody },
2318
+ inputs: [],
2319
+ outputs: [
2320
+ { id: "request", label: "Request", dataType: "object" },
2321
+ { id: "body", label: "Body", dataType: "object" },
2322
+ { id: "query", label: "Query", dataType: "object" }
2323
+ ]
2324
+ };
2325
+ }
2326
+ return {
2327
+ id,
2328
+ nodeType: "manual" /* MANUAL */,
2329
+ category: "trigger" /* TRIGGER */,
2330
+ label: target.name ?? "Entry Point",
2331
+ params: {
2332
+ functionName: target.name ?? "handler",
2333
+ args: target.fn.getParameters?.() ? target.fn.getParameters().map((p) => ({
2334
+ name: p.getName(),
2335
+ type: inferDataType(p.getType().getText())
2336
+ })) : []
2337
+ },
2338
+ inputs: [],
2339
+ outputs: [{ id: "output", label: "Output", dataType: "any" }]
2340
+ };
2341
+ }
2342
+ function walkBlock(block, parentNodeId, ctx, controlFlowPort) {
2343
+ const statements = block.getStatements();
2344
+ let lastNodeId = parentNodeId;
2345
+ for (const stmt of statements) {
2346
+ const nodeId = processStatement(stmt, lastNodeId, ctx);
2347
+ if (nodeId) {
2348
+ if (lastNodeId !== parentNodeId || !controlFlowPort) {
2349
+ ctx.seqPredecessors.set(nodeId, lastNodeId);
2350
+ }
2351
+ if (lastNodeId === parentNodeId && controlFlowPort) {
2352
+ if (!ctx.controlFlowChildren.has(parentNodeId)) {
2353
+ ctx.controlFlowChildren.set(parentNodeId, []);
2354
+ }
2355
+ ctx.controlFlowChildren.get(parentNodeId).push({
2356
+ portId: controlFlowPort,
2357
+ nodeId
2358
+ });
2359
+ }
2360
+ lastNodeId = nodeId;
2361
+ }
2362
+ }
2363
+ }
2364
+ function processStatement(stmt, prevNodeId, ctx) {
2365
+ const kind = stmt.getKind();
2366
+ const line = stmt.getStartLineNumber();
2367
+ switch (kind) {
2368
+ case SyntaxKind.VariableStatement:
2369
+ return processVariableStatement(stmt, prevNodeId, ctx, line);
2370
+ case SyntaxKind.IfStatement:
2371
+ return processIfStatement(stmt, prevNodeId, ctx, line);
2372
+ case SyntaxKind.ForOfStatement:
2373
+ return processForOfStatement(stmt, prevNodeId, ctx, line);
2374
+ case SyntaxKind.ForInStatement:
2375
+ return processForInStatement(stmt, prevNodeId, ctx, line);
2376
+ case SyntaxKind.ForStatement:
2377
+ return processForStatement(stmt, prevNodeId, ctx, line);
2378
+ case SyntaxKind.TryStatement:
2379
+ return processTryStatement(stmt, prevNodeId, ctx, line);
2380
+ case SyntaxKind.ReturnStatement:
2381
+ return processReturnStatement(stmt, prevNodeId, ctx, line);
2382
+ case SyntaxKind.ExpressionStatement:
2383
+ return processExpressionStatement(stmt, prevNodeId, ctx, line);
2384
+ default:
2385
+ return null;
2386
+ }
2387
+ }
2388
+ function processVariableStatement(stmt, _prevNodeId, ctx, line) {
2389
+ const declarations = stmt.getDeclarations();
2390
+ if (declarations.length === 0) return null;
2391
+ const decl = declarations[0];
2392
+ return processVariableDeclaration(decl, ctx, line);
2393
+ }
2394
+ function processVariableDeclaration(decl, ctx, line) {
2395
+ const varName = decl.getName();
2396
+ const init = decl.getInitializer();
2397
+ if (!init) return null;
2398
+ const initText = init.getText();
2399
+ if (isFetchCall(init)) {
2400
+ const nodeId2 = ctx.nextId("fetch");
2401
+ const node = parseFetchNode(nodeId2, initText, varName);
2402
+ ctx.addNode(node, line);
2403
+ ctx.varDefs.set(varName, { nodeId: nodeId2, portId: "data", varName });
2404
+ return nodeId2;
2405
+ }
2406
+ if (hasAwaitExpression(init)) {
2407
+ const nodeId2 = ctx.nextId("async_op");
2408
+ const awaitedCode = extractAwaitedExpression(init);
2409
+ ctx.addNode({
2410
+ id: nodeId2,
2411
+ nodeType: "custom_code" /* CUSTOM_CODE */,
2412
+ category: "action" /* ACTION */,
2413
+ label: inferLabel(awaitedCode, varName),
2414
+ params: { code: awaitedCode, returnVariable: varName },
2415
+ inputs: [{ id: "input", label: "Input", dataType: "any", required: false }],
2416
+ outputs: [{ id: "result", label: "Result", dataType: "any" }]
2417
+ }, line);
2418
+ ctx.varDefs.set(varName, { nodeId: nodeId2, portId: "result", varName });
2419
+ trackVariableUses(nodeId2, initText, ctx);
2420
+ return nodeId2;
2421
+ }
2422
+ if (isSimpleDeclaration(init)) {
2423
+ const nodeId2 = ctx.nextId("var");
2424
+ const dataType = inferDataType(decl.getType().getText());
2425
+ const isConst = decl.getVariableStatement()?.getDeclarationKind()?.toString() === "const";
2426
+ ctx.addNode({
2427
+ id: nodeId2,
2428
+ nodeType: "declare" /* DECLARE */,
2429
+ category: "variable" /* VARIABLE */,
2430
+ label: varName,
2431
+ params: {
2432
+ name: varName,
2433
+ dataType,
2434
+ initialValue: initText,
2435
+ isConst: isConst ?? true
2436
+ },
2437
+ inputs: [],
2438
+ outputs: [{ id: "value", label: "Value", dataType }]
2439
+ }, line);
2440
+ ctx.varDefs.set(varName, { nodeId: nodeId2, portId: "value", varName });
2441
+ trackVariableUses(nodeId2, initText, ctx);
2442
+ return nodeId2;
2443
+ }
2444
+ const nodeId = ctx.nextId("transform");
2445
+ ctx.addNode({
2446
+ id: nodeId,
2447
+ nodeType: "transform" /* TRANSFORM */,
2448
+ category: "variable" /* VARIABLE */,
2449
+ label: varName,
2450
+ params: { expression: initText },
2451
+ inputs: [{ id: "input", label: "Input", dataType: "any", required: true }],
2452
+ outputs: [{ id: "output", label: "Output", dataType: "any" }]
2453
+ }, line);
2454
+ ctx.varDefs.set(varName, { nodeId, portId: "output", varName });
2455
+ trackVariableUses(nodeId, initText, ctx);
2456
+ return nodeId;
2457
+ }
2458
+ function processIfStatement(stmt, _prevNodeId, ctx, line) {
2459
+ const nodeId = ctx.nextId("if");
2460
+ const condition = stmt.getExpression().getText();
2461
+ ctx.addNode({
2462
+ id: nodeId,
2463
+ nodeType: "if_else" /* IF_ELSE */,
2464
+ category: "logic" /* LOGIC */,
2465
+ label: `if (${truncate(condition, 40)})`,
2466
+ params: { condition },
2467
+ inputs: [{ id: "input", label: "Input", dataType: "any", required: true }],
2468
+ outputs: [
2469
+ { id: "true", label: "True", dataType: "any" },
2470
+ { id: "false", label: "False", dataType: "any" }
2471
+ ]
2472
+ }, line);
2473
+ trackVariableUses(nodeId, condition, ctx);
2474
+ const thenBlock = stmt.getThenStatement();
2475
+ if (thenBlock.getKind() === SyntaxKind.Block) {
2476
+ walkBlock(thenBlock, nodeId, ctx, "true");
2477
+ }
2478
+ const elseStmt = stmt.getElseStatement();
2479
+ if (elseStmt) {
2480
+ if (elseStmt.getKind() === SyntaxKind.Block) {
2481
+ walkBlock(elseStmt, nodeId, ctx, "false");
2482
+ } else if (elseStmt.getKind() === SyntaxKind.IfStatement) {
2483
+ const nestedId = processIfStatement(elseStmt, nodeId, ctx, elseStmt.getStartLineNumber());
2484
+ if (!ctx.controlFlowChildren.has(nodeId)) {
2485
+ ctx.controlFlowChildren.set(nodeId, []);
2486
+ }
2487
+ ctx.controlFlowChildren.get(nodeId).push({ portId: "false", nodeId: nestedId });
2488
+ }
2489
+ }
2490
+ if (!elseStmt) {
2491
+ ctx.auditData.push({
2492
+ nodeId,
2493
+ severity: "info",
2494
+ message: "If statement has no else branch \u2014 consider handling the negative case",
2495
+ line
2496
+ });
2497
+ }
2498
+ return nodeId;
2499
+ }
2500
+ function processForOfStatement(stmt, _prevNodeId, ctx, line) {
2501
+ const nodeId = ctx.nextId("loop");
2502
+ const initText = stmt.getInitializer().getText();
2503
+ const itemVar = initText.replace(/^(const|let|var)\s+/, "");
2504
+ const iterableExpr = stmt.getExpression().getText();
2505
+ ctx.addNode({
2506
+ id: nodeId,
2507
+ nodeType: "for_loop" /* FOR_LOOP */,
2508
+ category: "logic" /* LOGIC */,
2509
+ label: `for (${itemVar} of ${truncate(iterableExpr, 30)})`,
2510
+ params: {
2511
+ iterableExpression: iterableExpr,
2512
+ itemVariable: itemVar
2513
+ },
2514
+ inputs: [{ id: "iterable", label: "Iterable", dataType: "array", required: true }],
2515
+ outputs: [
2516
+ { id: "item", label: "Item", dataType: "any" },
2517
+ { id: "result", label: "Result", dataType: "array" }
2518
+ ]
2519
+ }, line);
2520
+ trackVariableUses(nodeId, iterableExpr, ctx);
2521
+ const body = stmt.getStatement();
2522
+ if (body.getKind() === SyntaxKind.Block) {
2523
+ walkBlock(body, nodeId, ctx, "item");
2524
+ }
2525
+ return nodeId;
2526
+ }
2527
+ function processForInStatement(stmt, _prevNodeId, ctx, line) {
2528
+ const nodeId = ctx.nextId("loop");
2529
+ const initText = stmt.getInitializer().getText();
2530
+ const itemVar = initText.replace(/^(const|let|var)\s+/, "");
2531
+ const iterableExpr = stmt.getExpression().getText();
2532
+ ctx.addNode({
2533
+ id: nodeId,
2534
+ nodeType: "for_loop" /* FOR_LOOP */,
2535
+ category: "logic" /* LOGIC */,
2536
+ label: `for (${itemVar} in ${truncate(iterableExpr, 30)})`,
2537
+ params: {
2538
+ iterableExpression: `Object.keys(${iterableExpr})`,
2539
+ itemVariable: itemVar
2540
+ },
2541
+ inputs: [{ id: "iterable", label: "Iterable", dataType: "array", required: true }],
2542
+ outputs: [
2543
+ { id: "item", label: "Item", dataType: "any" },
2544
+ { id: "result", label: "Result", dataType: "array" }
2545
+ ]
2546
+ }, line);
2547
+ trackVariableUses(nodeId, iterableExpr, ctx);
2548
+ const body = stmt.getStatement();
2549
+ if (body.getKind() === SyntaxKind.Block) {
2550
+ walkBlock(body, nodeId, ctx, "item");
2551
+ }
2552
+ return nodeId;
2553
+ }
2554
+ function processForStatement(stmt, _prevNodeId, ctx, line) {
2555
+ const nodeId = ctx.nextId("loop");
2556
+ const fullText = stmt.getText().split("{")[0].trim();
2557
+ ctx.addNode({
2558
+ id: nodeId,
2559
+ nodeType: "for_loop" /* FOR_LOOP */,
2560
+ category: "logic" /* LOGIC */,
2561
+ label: truncate(fullText, 50),
2562
+ params: {
2563
+ iterableExpression: fullText,
2564
+ itemVariable: "i"
2565
+ },
2566
+ inputs: [{ id: "iterable", label: "Iterable", dataType: "array", required: true }],
2567
+ outputs: [
2568
+ { id: "item", label: "Item", dataType: "any" },
2569
+ { id: "result", label: "Result", dataType: "array" }
2570
+ ]
2571
+ }, line);
2572
+ const body = stmt.getStatement();
2573
+ if (body.getKind() === SyntaxKind.Block) {
2574
+ walkBlock(body, nodeId, ctx, "item");
2575
+ }
2576
+ return nodeId;
2577
+ }
2578
+ function processTryStatement(stmt, _prevNodeId, ctx, line) {
2579
+ const nodeId = ctx.nextId("trycatch");
2580
+ const catchClause = stmt.getCatchClause();
2581
+ const errorVar = catchClause?.getVariableDeclaration()?.getName() ?? "error";
2582
+ ctx.addNode({
2583
+ id: nodeId,
2584
+ nodeType: "try_catch" /* TRY_CATCH */,
2585
+ category: "logic" /* LOGIC */,
2586
+ label: "Try / Catch",
2587
+ params: { errorVariable: errorVar },
2588
+ inputs: [{ id: "input", label: "Input", dataType: "any", required: true }],
2589
+ outputs: [
2590
+ { id: "success", label: "Success", dataType: "any" },
2591
+ { id: "error", label: "Error", dataType: "object" }
2592
+ ]
2593
+ }, line);
2594
+ const tryBlock = stmt.getTryBlock();
2595
+ walkBlock(tryBlock, nodeId, ctx, "success");
2596
+ if (catchClause) {
2597
+ const catchBlock = catchClause.getBlock();
2598
+ walkBlock(catchBlock, nodeId, ctx, "error");
2599
+ }
2600
+ return nodeId;
2601
+ }
2602
+ function processReturnStatement(stmt, _prevNodeId, ctx, line) {
2603
+ const nodeId = ctx.nextId("response");
2604
+ const returnExpr = stmt.getExpression();
2605
+ const returnText = returnExpr?.getText() ?? "";
2606
+ const isHttpResponse = returnText.includes("NextResponse") || returnText.includes("Response(") || returnText.includes(".json(") || returnText.includes("res.status") || returnText.includes("res.json");
2607
+ if (isHttpResponse) {
2608
+ const statusMatch = returnText.match(/status[:\s(]+(\d{3})/);
2609
+ const bodyMatch = returnText.match(/\.json\((.+?)(?:,|\))/s);
2610
+ ctx.addNode({
2611
+ id: nodeId,
2612
+ nodeType: "return_response" /* RETURN_RESPONSE */,
2613
+ category: "output" /* OUTPUT */,
2614
+ label: `Response ${statusMatch?.[1] ?? "200"}`,
2615
+ params: {
2616
+ statusCode: statusMatch ? parseInt(statusMatch[1]) : 200,
2617
+ bodyExpression: bodyMatch?.[1]?.trim() ?? returnText
2618
+ },
2619
+ inputs: [{ id: "data", label: "Data", dataType: "any", required: true }],
2620
+ outputs: []
2621
+ }, line);
2622
+ } else {
2623
+ ctx.addNode({
2624
+ id: nodeId,
2625
+ nodeType: "return_response" /* RETURN_RESPONSE */,
2626
+ category: "output" /* OUTPUT */,
2627
+ label: "Return",
2628
+ params: {
2629
+ statusCode: 200,
2630
+ bodyExpression: returnText || "undefined"
2631
+ },
2632
+ inputs: [{ id: "data", label: "Data", dataType: "any", required: true }],
2633
+ outputs: []
2634
+ }, line);
2635
+ }
2636
+ trackVariableUses(nodeId, returnText, ctx);
2637
+ return nodeId;
2638
+ }
2639
+ function processExpressionStatement(stmt, _prevNodeId, ctx, line) {
2640
+ const expr = stmt.getExpression();
2641
+ const exprText = expr.getText();
2642
+ if (isFetchCall(expr)) {
2643
+ const nodeId = ctx.nextId("fetch");
2644
+ const node = parseFetchNode(nodeId, exprText, void 0);
2645
+ ctx.addNode(node, line);
2646
+ return nodeId;
2647
+ }
2648
+ if (hasAwaitExpression(expr)) {
2649
+ const nodeId = ctx.nextId("async_op");
2650
+ const awaitedCode = extractAwaitedExpression(expr);
2651
+ ctx.addNode({
2652
+ id: nodeId,
2653
+ nodeType: "custom_code" /* CUSTOM_CODE */,
2654
+ category: "action" /* ACTION */,
2655
+ label: inferLabel(awaitedCode, void 0),
2656
+ params: { code: awaitedCode },
2657
+ inputs: [{ id: "input", label: "Input", dataType: "any", required: false }],
2658
+ outputs: [{ id: "result", label: "Result", dataType: "any" }]
2659
+ }, line);
2660
+ trackVariableUses(nodeId, exprText, ctx);
2661
+ return nodeId;
2662
+ }
2663
+ const flowStateMatch = exprText.match(/flowState\['([^']+)'\]\s*=\s*(.+)/s);
2664
+ if (flowStateMatch) {
2665
+ return null;
2666
+ }
2667
+ if (exprText.includes("res.status") || exprText.includes("res.json")) {
2668
+ const nodeId = ctx.nextId("response");
2669
+ const statusMatch = exprText.match(/status\((\d+)\)/);
2670
+ ctx.addNode({
2671
+ id: nodeId,
2672
+ nodeType: "return_response" /* RETURN_RESPONSE */,
2673
+ category: "output" /* OUTPUT */,
2674
+ label: `Response ${statusMatch?.[1] ?? "200"}`,
2675
+ params: {
2676
+ statusCode: statusMatch ? parseInt(statusMatch[1]) : 200,
2677
+ bodyExpression: exprText
2678
+ },
2679
+ inputs: [{ id: "data", label: "Data", dataType: "any", required: true }],
2680
+ outputs: []
2681
+ }, line);
2682
+ trackVariableUses(nodeId, exprText, ctx);
2683
+ return nodeId;
2684
+ }
2685
+ return null;
2686
+ }
2687
+ function buildEdges(ctx) {
2688
+ let edgeCounter = 0;
2689
+ const addEdge = (source, sourcePort, target, targetPort) => {
2690
+ const exists = ctx.edgeList.some(
2691
+ (e) => e.sourceNodeId === source && e.targetNodeId === target && e.sourcePortId === sourcePort
2692
+ );
2693
+ if (exists) return;
2694
+ ctx.edgeList.push({
2695
+ id: `e${++edgeCounter}`,
2696
+ sourceNodeId: source,
2697
+ sourcePortId: sourcePort,
2698
+ targetNodeId: target,
2699
+ targetPortId: targetPort
2700
+ });
2701
+ };
2702
+ const connectedTargets = /* @__PURE__ */ new Set();
2703
+ for (const [nodeId, uses] of ctx.varUses) {
2704
+ for (const use of uses) {
2705
+ const def = ctx.varDefs.get(use.varName);
2706
+ if (def && def.nodeId !== nodeId) {
2707
+ addEdge(def.nodeId, def.portId, nodeId, use.portId);
2708
+ connectedTargets.add(nodeId);
2709
+ }
2710
+ }
2711
+ }
2712
+ const resolveTargetPort = (targetNodeId, fallback) => {
2713
+ const tgt = ctx.nodes.get(targetNodeId);
2714
+ return tgt?.inputs?.[0]?.id ?? fallback;
2715
+ };
2716
+ for (const [parentId, children] of ctx.controlFlowChildren) {
2717
+ for (const child of children) {
2718
+ addEdge(parentId, child.portId, child.nodeId, resolveTargetPort(child.nodeId, "input"));
2719
+ connectedTargets.add(child.nodeId);
2720
+ }
2721
+ }
2722
+ for (const [nodeId, predId] of ctx.seqPredecessors) {
2723
+ if (connectedTargets.has(nodeId)) continue;
2724
+ const predNode = ctx.nodes.get(predId);
2725
+ if (!predNode) continue;
2726
+ const sourcePort = predNode.outputs[0]?.id ?? "output";
2727
+ addEdge(predId, sourcePort, nodeId, resolveTargetPort(nodeId, "input"));
2728
+ }
2729
+ }
2730
+ function computeAuditHints(ctx) {
2731
+ const hints = [...ctx.auditData];
2732
+ for (const [nodeId, node] of ctx.nodes) {
2733
+ const line = ctx.nodeLines.get(nodeId);
2734
+ if (node.nodeType === "custom_code" /* CUSTOM_CODE */ || node.nodeType === "fetch_api" /* FETCH_API */) {
2735
+ const isInsideTryCatch = Array.from(ctx.controlFlowChildren.entries()).some(
2736
+ ([parentId, children]) => {
2737
+ const parent = ctx.nodes.get(parentId);
2738
+ return parent?.nodeType === "try_catch" /* TRY_CATCH */ && children.some((c) => c.nodeId === nodeId);
2739
+ }
2740
+ );
2741
+ let ancestorId = ctx.seqPredecessors.get(nodeId);
2742
+ let foundTryCatch = isInsideTryCatch;
2743
+ while (ancestorId && !foundTryCatch) {
2744
+ const ancestor = ctx.nodes.get(ancestorId);
2745
+ if (ancestor?.nodeType === "try_catch" /* TRY_CATCH */) foundTryCatch = true;
2746
+ for (const [parentId, children] of ctx.controlFlowChildren) {
2747
+ const parent = ctx.nodes.get(parentId);
2748
+ if (parent?.nodeType === "try_catch" /* TRY_CATCH */ && children.some((c) => c.nodeId === nodeId)) {
2749
+ foundTryCatch = true;
2750
+ }
2751
+ }
2752
+ ancestorId = ctx.seqPredecessors.get(ancestorId);
2753
+ }
2754
+ if (!foundTryCatch) {
2755
+ hints.push({
2756
+ nodeId,
2757
+ severity: "warning",
2758
+ message: `Async operation "${node.label}" has no error handling (missing try/catch)`,
2759
+ line
2760
+ });
2761
+ }
2762
+ }
2763
+ if (node.nodeType === "fetch_api" /* FETCH_API */) {
2764
+ hints.push({
2765
+ nodeId,
2766
+ severity: "info",
2767
+ message: "Consider checking response.ok or response.status after fetch",
2768
+ line
2769
+ });
2770
+ }
2771
+ }
2772
+ return hints;
2773
+ }
2774
+ function isFetchCall(node) {
2775
+ const text = node.getText();
2776
+ return text.includes("fetch(") && (text.includes("await") || node.getKind() === SyntaxKind.AwaitExpression);
2777
+ }
2778
+ function hasAwaitExpression(node) {
2779
+ if (node.getKind() === SyntaxKind.AwaitExpression) return true;
2780
+ return node.getText().startsWith("await ");
2781
+ }
2782
+ function extractAwaitedExpression(node) {
2783
+ const text = node.getText();
2784
+ if (text.startsWith("await ")) return text.slice(6);
2785
+ return text;
2786
+ }
2787
+ function isSimpleDeclaration(node) {
2788
+ const text = node.getText();
2789
+ return !text.includes("await") && !text.includes("fetch(") && (node.getKind() === SyntaxKind.StringLiteral || node.getKind() === SyntaxKind.NumericLiteral || node.getKind() === SyntaxKind.TrueKeyword || node.getKind() === SyntaxKind.FalseKeyword || node.getKind() === SyntaxKind.NullKeyword || node.getKind() === SyntaxKind.ArrayLiteralExpression || node.getKind() === SyntaxKind.ObjectLiteralExpression || node.getKind() === SyntaxKind.NewExpression);
2790
+ }
2791
+ function parseFetchNode(nodeId, text, varName) {
2792
+ const urlMatch = text.match(/fetch\(([^,)]+)/);
2793
+ let url = urlMatch?.[1]?.trim() ?? '""';
2794
+ url = url.replace(/^[`"']|[`"']$/g, "");
2795
+ const methodMatch = text.match(/method:\s*["'](\w+)["']/);
2796
+ const method = methodMatch?.[1]?.toUpperCase() ?? "GET";
2797
+ const headerMatch = text.match(/headers:\s*(\{[^}]+\})/);
2798
+ let headers;
2799
+ if (headerMatch) {
2800
+ headers = {};
2801
+ const headerPairs = headerMatch[1].matchAll(/"([^"]+)":\s*"([^"]+)"/g);
2802
+ for (const pair of headerPairs) {
2803
+ headers[pair[1]] = pair[2];
2804
+ }
2805
+ }
2806
+ return {
2807
+ id: nodeId,
2808
+ nodeType: "fetch_api" /* FETCH_API */,
2809
+ category: "action" /* ACTION */,
2810
+ label: `Fetch ${truncate(url, 30) || varName || "API"}`,
2811
+ params: {
2812
+ url,
2813
+ method,
2814
+ headers,
2815
+ parseJson: text.includes(".json()")
2816
+ },
2817
+ inputs: [{ id: "input", label: "Input", dataType: "any", required: false }],
2818
+ outputs: [
2819
+ { id: "response", label: "Response", dataType: "object" },
2820
+ { id: "data", label: "Data", dataType: "any" }
2821
+ ]
2822
+ };
2823
+ }
2824
+ function inferRoutePath(sourceFile) {
2825
+ const filePath = sourceFile.getFilePath();
2826
+ const appRouterMatch = filePath.match(/\/app\/api\/(.+?)\/route\.(ts|js)/);
2827
+ if (appRouterMatch) return `/api/${appRouterMatch[1]}`;
2828
+ const pagesMatch = filePath.match(/\/pages\/api\/(.+?)\.(ts|js)/);
2829
+ if (pagesMatch) return `/api/${pagesMatch[1]}`;
2830
+ const fullText = sourceFile.getFullText();
2831
+ const routeMatch = fullText.match(/\/api\/\S+/);
2832
+ if (routeMatch) return routeMatch[0];
2833
+ return "/api/handler";
2834
+ }
2835
+ function inferDataType(tsType) {
2836
+ if (tsType.includes("string")) return "string";
2837
+ if (tsType.includes("number")) return "number";
2838
+ if (tsType.includes("boolean")) return "boolean";
2839
+ if (tsType.includes("[]") || tsType.includes("Array")) return "array";
2840
+ if (tsType.includes("{") || tsType.includes("Record") || tsType.includes("object")) return "object";
2841
+ return "any";
2842
+ }
2843
+ function inferLabel(code, varName) {
2844
+ const callMatch = code.match(/^(\w+(?:\.\w+)*)\(/);
2845
+ if (callMatch) return callMatch[1];
2846
+ if (varName) return varName;
2847
+ return truncate(code, 30);
2848
+ }
2849
+ function trackVariableUses(nodeId, expression, ctx) {
2850
+ const identifiers = expression.match(/\b([a-zA-Z_]\w*)\b/g);
2851
+ if (!identifiers) return;
2852
+ const uses = [];
2853
+ const seen = /* @__PURE__ */ new Set();
2854
+ for (const ident of identifiers) {
2855
+ if (SKIP_IDENTIFIERS.has(ident)) continue;
2856
+ if (seen.has(ident)) continue;
2857
+ seen.add(ident);
2858
+ if (ctx.varDefs.has(ident)) {
2859
+ const targetNode = ctx.nodes.get(nodeId);
2860
+ const portId = targetNode?.inputs?.[0]?.id ?? "input";
2861
+ uses.push({ nodeId, portId, varName: ident });
2862
+ }
2863
+ }
2864
+ if (uses.length > 0) {
2865
+ const existing = ctx.varUses.get(nodeId) ?? [];
2866
+ ctx.varUses.set(nodeId, [...existing, ...uses]);
2867
+ }
2868
+ }
2869
+ var SKIP_IDENTIFIERS = /* @__PURE__ */ new Set([
2870
+ // JS keywords
2871
+ "const",
2872
+ "let",
2873
+ "var",
2874
+ "function",
2875
+ "return",
2876
+ "if",
2877
+ "else",
2878
+ "for",
2879
+ "while",
2880
+ "do",
2881
+ "switch",
2882
+ "case",
2883
+ "break",
2884
+ "continue",
2885
+ "throw",
2886
+ "try",
2887
+ "catch",
2888
+ "finally",
2889
+ "new",
2890
+ "delete",
2891
+ "typeof",
2892
+ "void",
2893
+ "in",
2894
+ "of",
2895
+ "instanceof",
2896
+ "this",
2897
+ "super",
2898
+ "class",
2899
+ "extends",
2900
+ "import",
2901
+ "export",
2902
+ "default",
2903
+ "from",
2904
+ "as",
2905
+ "async",
2906
+ "await",
2907
+ "yield",
2908
+ "true",
2909
+ "false",
2910
+ "null",
2911
+ "undefined",
2912
+ // Common globals
2913
+ "console",
2914
+ "JSON",
2915
+ "Math",
2916
+ "Date",
2917
+ "Error",
2918
+ "Promise",
2919
+ "Array",
2920
+ "Object",
2921
+ "String",
2922
+ "Number",
2923
+ "Boolean",
2924
+ "Map",
2925
+ "Set",
2926
+ "RegExp",
2927
+ "Symbol",
2928
+ "Buffer",
2929
+ "process",
2930
+ "setTimeout",
2931
+ "setInterval",
2932
+ "clearTimeout",
2933
+ "clearInterval",
2934
+ "fetch",
2935
+ "Response",
2936
+ "Request",
2937
+ "Headers",
2938
+ "URL",
2939
+ "URLSearchParams",
2940
+ "NextResponse",
2941
+ "NextRequest",
2942
+ // Common methods
2943
+ "log",
2944
+ "error",
2945
+ "warn",
2946
+ "stringify",
2947
+ "parse",
2948
+ "json",
2949
+ "text",
2950
+ "toString",
2951
+ "map",
2952
+ "filter",
2953
+ "reduce",
2954
+ "forEach",
2955
+ "find",
2956
+ "some",
2957
+ "every",
2958
+ "includes",
2959
+ "push",
2960
+ "pop",
2961
+ "shift",
2962
+ "unshift",
2963
+ "slice",
2964
+ "splice",
2965
+ "concat",
2966
+ "join",
2967
+ "keys",
2968
+ "values",
2969
+ "entries",
2970
+ "assign",
2971
+ "freeze",
2972
+ "status",
2973
+ "ok",
2974
+ "headers",
2975
+ "body",
2976
+ "method",
2977
+ "url",
2978
+ "length",
2979
+ "trim",
2980
+ "split",
2981
+ "replace",
2982
+ "match",
2983
+ "test",
2984
+ "exec",
2985
+ "parseInt",
2986
+ "parseFloat",
2987
+ "isNaN",
2988
+ "isFinite",
2989
+ "then",
2990
+ "catch",
2991
+ "finally"
2992
+ ]);
2993
+ function truncate(str, maxLen) {
2994
+ if (str.length <= maxLen) return str;
2995
+ return str.slice(0, maxLen - 3) + "...";
2996
+ }
2997
+
2178
2998
  // src/lib/ir/security.ts
2179
2999
  var SECURITY_PATTERNS = [
2180
3000
  // ── Critical: Remote Code Execution / System Access ──
@@ -2208,7 +3028,7 @@ var SECURITY_PATTERNS = [
2208
3028
  { pattern: /\bwhile\s*\(\s*true\s*\)/, desc: "while(true) \u2014 possible infinite loop", severity: "info" },
2209
3029
  { pattern: /\bfor\s*\(\s*;\s*;\s*\)/, desc: "for(;;) \u2014 possible infinite loop", severity: "info" }
2210
3030
  ];
2211
- function truncate(str, maxLen) {
3031
+ function truncate2(str, maxLen) {
2212
3032
  return str.length > maxLen ? str.slice(0, maxLen) + "\u2026" : str;
2213
3033
  }
2214
3034
  function scanCode(nodeId, nodeLabel, code) {
@@ -2222,7 +3042,7 @@ function scanCode(nodeId, nodeLabel, code) {
2222
3042
  nodeId,
2223
3043
  nodeLabel,
2224
3044
  pattern: desc,
2225
- match: truncate(match[0], 80)
3045
+ match: truncate2(match[0], 80)
2226
3046
  });
2227
3047
  }
2228
3048
  }
@@ -2822,6 +3642,50 @@ function handleImportOpenAPI(body) {
2822
3642
  };
2823
3643
  }
2824
3644
  }
3645
+ function handleDecompile(body) {
3646
+ try {
3647
+ const { code, fileName, functionName } = body;
3648
+ if (!code || typeof code !== "string") {
3649
+ return { status: 400, body: { success: false, error: "Missing 'code' string in request body" } };
3650
+ }
3651
+ if (code.trim().length === 0) {
3652
+ return { status: 400, body: { success: false, error: "Code is empty" } };
3653
+ }
3654
+ const result = decompile(code, {
3655
+ fileName: fileName ?? "input.ts",
3656
+ functionName,
3657
+ audit: true
3658
+ });
3659
+ if (!result.success) {
3660
+ return {
3661
+ status: 422,
3662
+ body: {
3663
+ success: false,
3664
+ errors: result.errors ?? ["Decompile failed"],
3665
+ confidence: result.confidence
3666
+ }
3667
+ };
3668
+ }
3669
+ return {
3670
+ status: 200,
3671
+ body: {
3672
+ success: true,
3673
+ ir: result.ir,
3674
+ confidence: result.confidence,
3675
+ errors: result.errors ?? [],
3676
+ audit: result.audit ?? []
3677
+ }
3678
+ };
3679
+ } catch (err) {
3680
+ return {
3681
+ status: 500,
3682
+ body: {
3683
+ success: false,
3684
+ error: `Decompile error: ${err instanceof Error ? err.message : String(err)}`
3685
+ }
3686
+ };
3687
+ }
3688
+ }
2825
3689
 
2826
3690
  // src/server/index.ts
2827
3691
  var __filename = fileURLToPath(import.meta.url);
@@ -2986,6 +3850,11 @@ async function handleRequest(req, res, staticDir, projectRoot) {
2986
3850
  sendJson(res, result.status, result.body);
2987
3851
  return;
2988
3852
  }
3853
+ if (pathname === "/api/decompile") {
3854
+ const result = handleDecompile(body);
3855
+ sendJson(res, result.status, result.body);
3856
+ return;
3857
+ }
2989
3858
  sendJson(res, 404, { error: `Unknown API route: ${pathname}` });
2990
3859
  return;
2991
3860
  }
@@ -3013,6 +3882,7 @@ function startServer(options = {}) {
3013
3882
  res.end("Internal Server Error");
3014
3883
  });
3015
3884
  });
3885
+ const hasUI = existsSync2(join2(staticDir, "index.html"));
3016
3886
  server.listen(port, host, () => {
3017
3887
  const url = `http://localhost:${port}`;
3018
3888
  if (options.onReady) {
@@ -3023,14 +3893,22 @@ function startServer(options = {}) {
3023
3893
  console.log(` \u251C\u2500 Local: ${url}`);
3024
3894
  console.log(` \u251C\u2500 API: ${url}/api/compile`);
3025
3895
  console.log(` \u251C\u2500 Static: ${staticDir}`);
3026
- console.log(` \u2514\u2500 Project: ${projectRoot}
3027
- `);
3896
+ console.log(` \u2514\u2500 Project: ${projectRoot}`);
3897
+ 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`);
3903
+ }
3904
+ console.log();
3028
3905
  }
3029
3906
  });
3030
3907
  return server;
3031
3908
  }
3032
3909
  export {
3033
3910
  handleCompile,
3911
+ handleDecompile,
3034
3912
  handleGenerate,
3035
3913
  handleImportOpenAPI,
3036
3914
  startServer