@timo9378/flow2code 0.1.2 → 0.1.3

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 (47) hide show
  1. package/CHANGELOG.md +56 -56
  2. package/LICENSE +21 -21
  3. package/README.md +230 -223
  4. package/dist/cli.js +61 -2
  5. package/dist/server.d.ts +27 -27
  6. package/dist/server.js +876 -4
  7. package/out/404.html +1 -1
  8. package/out/__next.__PAGE__.txt +4 -4
  9. package/out/__next._full.txt +13 -13
  10. package/out/__next._head.txt +4 -4
  11. package/out/__next._index.txt +6 -6
  12. package/out/__next._tree.txt +2 -2
  13. package/out/_next/static/chunks/{41ec06cb3e44c96a.js → 5f1a9fec0e69c483.js} +2 -2
  14. package/out/_next/static/chunks/6b84376656bd9887.js +1 -0
  15. package/out/_next/static/chunks/993eabba22c07d39.js +54 -0
  16. package/out/_next/static/chunks/a6dad97d9634a72d.js.map +1 -1
  17. package/out/_next/static/chunks/ab8888d4b78b94be.js +5 -0
  18. package/out/_next/static/chunks/acf223168ac429f7.js +1 -0
  19. package/out/_next/static/chunks/{8486af13ea541d4d.js → b112c2f519e4b429.js} +1 -1
  20. package/out/_next/static/chunks/{4b43c69e91d19eee.js → b163b5d7cccbcf42.js} +1 -1
  21. package/out/_next/static/chunks/b6e8711267bccbbd.js +1 -0
  22. package/out/_next/static/chunks/c8a26302d935bf6e.css +1 -0
  23. package/out/_next/static/chunks/fbca595129527827.js +1 -0
  24. package/out/_next/static/chunks/{turbopack-b95472a0b02a2729.js → turbopack-576234c945ffdc44.js} +1 -1
  25. package/out/_not-found/__next._full.txt +11 -11
  26. package/out/_not-found/__next._head.txt +4 -4
  27. package/out/_not-found/__next._index.txt +6 -6
  28. package/out/_not-found/{__next._not-found.__PAGE__.txt → __next._not-found/__PAGE__.txt} +2 -2
  29. package/out/_not-found/__next._not-found.txt +3 -3
  30. package/out/_not-found/__next._tree.txt +2 -2
  31. package/out/_not-found.html +1 -1
  32. package/out/_not-found.txt +11 -11
  33. package/out/index.html +2 -2
  34. package/out/index.txt +13 -13
  35. package/package.json +123 -121
  36. package/scripts/postinstall.cjs +23 -0
  37. package/out/_next/static/chunks/324e22de375c241a.css +0 -1
  38. package/out/_next/static/chunks/3638bc7cb3d72bbe.js +0 -379
  39. package/out/_next/static/chunks/46e882944a7f3aed.js +0 -1
  40. package/out/_next/static/chunks/59b058cf94cc555e.js +0 -1
  41. package/out/_next/static/chunks/6e36daee8960e188.js +0 -5
  42. package/out/_next/static/chunks/80a5431d4e78c469.js +0 -1
  43. package/out/_next/static/chunks/8a38426c5ed81587.js +0 -1
  44. package/out/_next/static/chunks/edbd80df6004e249.js +0 -52
  45. /package/out/_next/static/{dSD-EjLFfv3PUm7xo33fg → gs3QpnA696kN6tOSlwt6o}/_buildManifest.js +0 -0
  46. /package/out/_next/static/{dSD-EjLFfv3PUm7xo33fg → gs3QpnA696kN6tOSlwt6o}/_clientMiddlewareManifest.json +0 -0
  47. /package/out/_next/static/{dSD-EjLFfv3PUm7xo33fg → gs3QpnA696kN6tOSlwt6o}/_ssgManifest.js +0 -0
package/dist/server.js CHANGED
@@ -2175,6 +2175,820 @@ 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
+ for (const [parentId, children] of ctx.controlFlowChildren) {
2713
+ for (const child of children) {
2714
+ addEdge(parentId, child.portId, child.nodeId, "input");
2715
+ connectedTargets.add(child.nodeId);
2716
+ }
2717
+ }
2718
+ for (const [nodeId, predId] of ctx.seqPredecessors) {
2719
+ if (connectedTargets.has(nodeId)) continue;
2720
+ const predNode = ctx.nodes.get(predId);
2721
+ if (!predNode) continue;
2722
+ const sourcePort = predNode.outputs[0]?.id ?? "output";
2723
+ addEdge(predId, sourcePort, nodeId, "input");
2724
+ }
2725
+ }
2726
+ function computeAuditHints(ctx) {
2727
+ const hints = [...ctx.auditData];
2728
+ for (const [nodeId, node] of ctx.nodes) {
2729
+ const line = ctx.nodeLines.get(nodeId);
2730
+ if (node.nodeType === "custom_code" /* CUSTOM_CODE */ || node.nodeType === "fetch_api" /* FETCH_API */) {
2731
+ const isInsideTryCatch = Array.from(ctx.controlFlowChildren.entries()).some(
2732
+ ([parentId, children]) => {
2733
+ const parent = ctx.nodes.get(parentId);
2734
+ return parent?.nodeType === "try_catch" /* TRY_CATCH */ && children.some((c) => c.nodeId === nodeId);
2735
+ }
2736
+ );
2737
+ let ancestorId = ctx.seqPredecessors.get(nodeId);
2738
+ let foundTryCatch = isInsideTryCatch;
2739
+ while (ancestorId && !foundTryCatch) {
2740
+ const ancestor = ctx.nodes.get(ancestorId);
2741
+ if (ancestor?.nodeType === "try_catch" /* TRY_CATCH */) foundTryCatch = true;
2742
+ for (const [parentId, children] of ctx.controlFlowChildren) {
2743
+ const parent = ctx.nodes.get(parentId);
2744
+ if (parent?.nodeType === "try_catch" /* TRY_CATCH */ && children.some((c) => c.nodeId === nodeId)) {
2745
+ foundTryCatch = true;
2746
+ }
2747
+ }
2748
+ ancestorId = ctx.seqPredecessors.get(ancestorId);
2749
+ }
2750
+ if (!foundTryCatch) {
2751
+ hints.push({
2752
+ nodeId,
2753
+ severity: "warning",
2754
+ message: `Async operation "${node.label}" has no error handling (missing try/catch)`,
2755
+ line
2756
+ });
2757
+ }
2758
+ }
2759
+ if (node.nodeType === "fetch_api" /* FETCH_API */) {
2760
+ hints.push({
2761
+ nodeId,
2762
+ severity: "info",
2763
+ message: "Consider checking response.ok or response.status after fetch",
2764
+ line
2765
+ });
2766
+ }
2767
+ }
2768
+ return hints;
2769
+ }
2770
+ function isFetchCall(node) {
2771
+ const text = node.getText();
2772
+ return text.includes("fetch(") && (text.includes("await") || node.getKind() === SyntaxKind.AwaitExpression);
2773
+ }
2774
+ function hasAwaitExpression(node) {
2775
+ if (node.getKind() === SyntaxKind.AwaitExpression) return true;
2776
+ return node.getText().startsWith("await ");
2777
+ }
2778
+ function extractAwaitedExpression(node) {
2779
+ const text = node.getText();
2780
+ if (text.startsWith("await ")) return text.slice(6);
2781
+ return text;
2782
+ }
2783
+ function isSimpleDeclaration(node) {
2784
+ const text = node.getText();
2785
+ 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);
2786
+ }
2787
+ function parseFetchNode(nodeId, text, varName) {
2788
+ const urlMatch = text.match(/fetch\(([^,)]+)/);
2789
+ let url = urlMatch?.[1]?.trim() ?? '""';
2790
+ url = url.replace(/^[`"']|[`"']$/g, "");
2791
+ const methodMatch = text.match(/method:\s*["'](\w+)["']/);
2792
+ const method = methodMatch?.[1]?.toUpperCase() ?? "GET";
2793
+ const headerMatch = text.match(/headers:\s*(\{[^}]+\})/);
2794
+ let headers;
2795
+ if (headerMatch) {
2796
+ headers = {};
2797
+ const headerPairs = headerMatch[1].matchAll(/"([^"]+)":\s*"([^"]+)"/g);
2798
+ for (const pair of headerPairs) {
2799
+ headers[pair[1]] = pair[2];
2800
+ }
2801
+ }
2802
+ return {
2803
+ id: nodeId,
2804
+ nodeType: "fetch_api" /* FETCH_API */,
2805
+ category: "action" /* ACTION */,
2806
+ label: `Fetch ${truncate(url, 30) || varName || "API"}`,
2807
+ params: {
2808
+ url,
2809
+ method,
2810
+ headers,
2811
+ parseJson: text.includes(".json()")
2812
+ },
2813
+ inputs: [{ id: "input", label: "Input", dataType: "any", required: false }],
2814
+ outputs: [
2815
+ { id: "response", label: "Response", dataType: "object" },
2816
+ { id: "data", label: "Data", dataType: "any" }
2817
+ ]
2818
+ };
2819
+ }
2820
+ function inferRoutePath(sourceFile) {
2821
+ const filePath = sourceFile.getFilePath();
2822
+ const appRouterMatch = filePath.match(/\/app\/api\/(.+?)\/route\.(ts|js)/);
2823
+ if (appRouterMatch) return `/api/${appRouterMatch[1]}`;
2824
+ const pagesMatch = filePath.match(/\/pages\/api\/(.+?)\.(ts|js)/);
2825
+ if (pagesMatch) return `/api/${pagesMatch[1]}`;
2826
+ const fullText = sourceFile.getFullText();
2827
+ const routeMatch = fullText.match(/\/api\/\S+/);
2828
+ if (routeMatch) return routeMatch[0];
2829
+ return "/api/handler";
2830
+ }
2831
+ function inferDataType(tsType) {
2832
+ if (tsType.includes("string")) return "string";
2833
+ if (tsType.includes("number")) return "number";
2834
+ if (tsType.includes("boolean")) return "boolean";
2835
+ if (tsType.includes("[]") || tsType.includes("Array")) return "array";
2836
+ if (tsType.includes("{") || tsType.includes("Record") || tsType.includes("object")) return "object";
2837
+ return "any";
2838
+ }
2839
+ function inferLabel(code, varName) {
2840
+ const callMatch = code.match(/^(\w+(?:\.\w+)*)\(/);
2841
+ if (callMatch) return callMatch[1];
2842
+ if (varName) return varName;
2843
+ return truncate(code, 30);
2844
+ }
2845
+ function trackVariableUses(nodeId, expression, ctx) {
2846
+ const identifiers = expression.match(/\b([a-zA-Z_]\w*)\b/g);
2847
+ if (!identifiers) return;
2848
+ const uses = [];
2849
+ const seen = /* @__PURE__ */ new Set();
2850
+ for (const ident of identifiers) {
2851
+ if (SKIP_IDENTIFIERS.has(ident)) continue;
2852
+ if (seen.has(ident)) continue;
2853
+ seen.add(ident);
2854
+ if (ctx.varDefs.has(ident)) {
2855
+ uses.push({ nodeId, portId: "input", varName: ident });
2856
+ }
2857
+ }
2858
+ if (uses.length > 0) {
2859
+ const existing = ctx.varUses.get(nodeId) ?? [];
2860
+ ctx.varUses.set(nodeId, [...existing, ...uses]);
2861
+ }
2862
+ }
2863
+ var SKIP_IDENTIFIERS = /* @__PURE__ */ new Set([
2864
+ // JS keywords
2865
+ "const",
2866
+ "let",
2867
+ "var",
2868
+ "function",
2869
+ "return",
2870
+ "if",
2871
+ "else",
2872
+ "for",
2873
+ "while",
2874
+ "do",
2875
+ "switch",
2876
+ "case",
2877
+ "break",
2878
+ "continue",
2879
+ "throw",
2880
+ "try",
2881
+ "catch",
2882
+ "finally",
2883
+ "new",
2884
+ "delete",
2885
+ "typeof",
2886
+ "void",
2887
+ "in",
2888
+ "of",
2889
+ "instanceof",
2890
+ "this",
2891
+ "super",
2892
+ "class",
2893
+ "extends",
2894
+ "import",
2895
+ "export",
2896
+ "default",
2897
+ "from",
2898
+ "as",
2899
+ "async",
2900
+ "await",
2901
+ "yield",
2902
+ "true",
2903
+ "false",
2904
+ "null",
2905
+ "undefined",
2906
+ // Common globals
2907
+ "console",
2908
+ "JSON",
2909
+ "Math",
2910
+ "Date",
2911
+ "Error",
2912
+ "Promise",
2913
+ "Array",
2914
+ "Object",
2915
+ "String",
2916
+ "Number",
2917
+ "Boolean",
2918
+ "Map",
2919
+ "Set",
2920
+ "RegExp",
2921
+ "Symbol",
2922
+ "Buffer",
2923
+ "process",
2924
+ "setTimeout",
2925
+ "setInterval",
2926
+ "clearTimeout",
2927
+ "clearInterval",
2928
+ "fetch",
2929
+ "Response",
2930
+ "Request",
2931
+ "Headers",
2932
+ "URL",
2933
+ "URLSearchParams",
2934
+ "NextResponse",
2935
+ "NextRequest",
2936
+ // Common methods
2937
+ "log",
2938
+ "error",
2939
+ "warn",
2940
+ "stringify",
2941
+ "parse",
2942
+ "json",
2943
+ "text",
2944
+ "toString",
2945
+ "map",
2946
+ "filter",
2947
+ "reduce",
2948
+ "forEach",
2949
+ "find",
2950
+ "some",
2951
+ "every",
2952
+ "includes",
2953
+ "push",
2954
+ "pop",
2955
+ "shift",
2956
+ "unshift",
2957
+ "slice",
2958
+ "splice",
2959
+ "concat",
2960
+ "join",
2961
+ "keys",
2962
+ "values",
2963
+ "entries",
2964
+ "assign",
2965
+ "freeze",
2966
+ "status",
2967
+ "ok",
2968
+ "headers",
2969
+ "body",
2970
+ "method",
2971
+ "url",
2972
+ "length",
2973
+ "trim",
2974
+ "split",
2975
+ "replace",
2976
+ "match",
2977
+ "test",
2978
+ "exec",
2979
+ "parseInt",
2980
+ "parseFloat",
2981
+ "isNaN",
2982
+ "isFinite",
2983
+ "then",
2984
+ "catch",
2985
+ "finally"
2986
+ ]);
2987
+ function truncate(str, maxLen) {
2988
+ if (str.length <= maxLen) return str;
2989
+ return str.slice(0, maxLen - 3) + "...";
2990
+ }
2991
+
2178
2992
  // src/lib/ir/security.ts
2179
2993
  var SECURITY_PATTERNS = [
2180
2994
  // ── Critical: Remote Code Execution / System Access ──
@@ -2208,7 +3022,7 @@ var SECURITY_PATTERNS = [
2208
3022
  { pattern: /\bwhile\s*\(\s*true\s*\)/, desc: "while(true) \u2014 possible infinite loop", severity: "info" },
2209
3023
  { pattern: /\bfor\s*\(\s*;\s*;\s*\)/, desc: "for(;;) \u2014 possible infinite loop", severity: "info" }
2210
3024
  ];
2211
- function truncate(str, maxLen) {
3025
+ function truncate2(str, maxLen) {
2212
3026
  return str.length > maxLen ? str.slice(0, maxLen) + "\u2026" : str;
2213
3027
  }
2214
3028
  function scanCode(nodeId, nodeLabel, code) {
@@ -2222,7 +3036,7 @@ function scanCode(nodeId, nodeLabel, code) {
2222
3036
  nodeId,
2223
3037
  nodeLabel,
2224
3038
  pattern: desc,
2225
- match: truncate(match[0], 80)
3039
+ match: truncate2(match[0], 80)
2226
3040
  });
2227
3041
  }
2228
3042
  }
@@ -2822,6 +3636,50 @@ function handleImportOpenAPI(body) {
2822
3636
  };
2823
3637
  }
2824
3638
  }
3639
+ function handleDecompile(body) {
3640
+ try {
3641
+ const { code, fileName, functionName } = body;
3642
+ if (!code || typeof code !== "string") {
3643
+ return { status: 400, body: { success: false, error: "Missing 'code' string in request body" } };
3644
+ }
3645
+ if (code.trim().length === 0) {
3646
+ return { status: 400, body: { success: false, error: "Code is empty" } };
3647
+ }
3648
+ const result = decompile(code, {
3649
+ fileName: fileName ?? "input.ts",
3650
+ functionName,
3651
+ audit: true
3652
+ });
3653
+ if (!result.success) {
3654
+ return {
3655
+ status: 422,
3656
+ body: {
3657
+ success: false,
3658
+ errors: result.errors ?? ["Decompile failed"],
3659
+ confidence: result.confidence
3660
+ }
3661
+ };
3662
+ }
3663
+ return {
3664
+ status: 200,
3665
+ body: {
3666
+ success: true,
3667
+ ir: result.ir,
3668
+ confidence: result.confidence,
3669
+ errors: result.errors ?? [],
3670
+ audit: result.audit ?? []
3671
+ }
3672
+ };
3673
+ } catch (err) {
3674
+ return {
3675
+ status: 500,
3676
+ body: {
3677
+ success: false,
3678
+ error: `Decompile error: ${err instanceof Error ? err.message : String(err)}`
3679
+ }
3680
+ };
3681
+ }
3682
+ }
2825
3683
 
2826
3684
  // src/server/index.ts
2827
3685
  var __filename = fileURLToPath(import.meta.url);
@@ -2986,6 +3844,11 @@ async function handleRequest(req, res, staticDir, projectRoot) {
2986
3844
  sendJson(res, result.status, result.body);
2987
3845
  return;
2988
3846
  }
3847
+ if (pathname === "/api/decompile") {
3848
+ const result = handleDecompile(body);
3849
+ sendJson(res, result.status, result.body);
3850
+ return;
3851
+ }
2989
3852
  sendJson(res, 404, { error: `Unknown API route: ${pathname}` });
2990
3853
  return;
2991
3854
  }
@@ -3013,6 +3876,7 @@ function startServer(options = {}) {
3013
3876
  res.end("Internal Server Error");
3014
3877
  });
3015
3878
  });
3879
+ const hasUI = existsSync2(join2(staticDir, "index.html"));
3016
3880
  server.listen(port, host, () => {
3017
3881
  const url = `http://localhost:${port}`;
3018
3882
  if (options.onReady) {
@@ -3023,14 +3887,22 @@ function startServer(options = {}) {
3023
3887
  console.log(` \u251C\u2500 Local: ${url}`);
3024
3888
  console.log(` \u251C\u2500 API: ${url}/api/compile`);
3025
3889
  console.log(` \u251C\u2500 Static: ${staticDir}`);
3026
- console.log(` \u2514\u2500 Project: ${projectRoot}
3027
- `);
3890
+ console.log(` \u2514\u2500 Project: ${projectRoot}`);
3891
+ if (!hasUI) {
3892
+ console.log();
3893
+ console.log(` \u26A0\uFE0F UI files not found (out/index.html missing).`);
3894
+ console.log(` The API endpoints still work, but the visual editor won't load.`);
3895
+ console.log(` To fix: run "pnpm build:ui" in the flow2code source directory,`);
3896
+ console.log(` or reinstall from npm: npm i @timo9378/flow2code@latest`);
3897
+ }
3898
+ console.log();
3028
3899
  }
3029
3900
  });
3030
3901
  return server;
3031
3902
  }
3032
3903
  export {
3033
3904
  handleCompile,
3905
+ handleDecompile,
3034
3906
  handleGenerate,
3035
3907
  handleImportOpenAPI,
3036
3908
  startServer