@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.
- package/CHANGELOG.md +56 -56
- package/LICENSE +21 -21
- package/README.md +230 -223
- package/dist/cli.js +70 -5
- package/dist/compiler.cjs +9 -3
- package/dist/compiler.js +9 -3
- package/dist/server.d.ts +27 -27
- package/dist/server.js +882 -4
- package/out/404.html +1 -1
- package/out/__next.__PAGE__.txt +4 -4
- package/out/__next._full.txt +13 -13
- package/out/__next._head.txt +4 -4
- package/out/__next._index.txt +6 -6
- package/out/__next._tree.txt +2 -2
- package/out/_next/static/chunks/2be31674b47c1089.js +49 -0
- package/out/_next/static/chunks/{41ec06cb3e44c96a.js → 5f1a9fec0e69c483.js} +2 -2
- package/out/_next/static/chunks/6b84376656bd9887.js +1 -0
- package/out/_next/static/chunks/83ab8820627f8bfe.css +1 -0
- package/out/_next/static/chunks/a6dad97d9634a72d.js.map +1 -1
- package/out/_next/static/chunks/ab8888d4b78b94be.js +5 -0
- package/out/_next/static/chunks/acf223168ac429f7.js +1 -0
- package/out/_next/static/chunks/{8486af13ea541d4d.js → b112c2f519e4b429.js} +1 -1
- package/out/_next/static/chunks/{4b43c69e91d19eee.js → b163b5d7cccbcf42.js} +1 -1
- package/out/_next/static/chunks/b6e8711267bccbbd.js +1 -0
- package/out/_next/static/chunks/fbca595129527827.js +1 -0
- package/out/_next/static/chunks/{turbopack-b95472a0b02a2729.js → turbopack-576234c945ffdc44.js} +1 -1
- package/out/_not-found/__next._full.txt +11 -11
- package/out/_not-found/__next._head.txt +4 -4
- package/out/_not-found/__next._index.txt +6 -6
- package/out/_not-found/{__next._not-found.__PAGE__.txt → __next._not-found/__PAGE__.txt} +2 -2
- package/out/_not-found/__next._not-found.txt +3 -3
- package/out/_not-found/__next._tree.txt +2 -2
- package/out/_not-found.html +1 -1
- package/out/_not-found.txt +11 -11
- package/out/index.html +2 -2
- package/out/index.txt +13 -13
- package/package.json +124 -121
- package/scripts/postinstall.cjs +23 -0
- package/out/_next/static/chunks/324e22de375c241a.css +0 -1
- package/out/_next/static/chunks/3638bc7cb3d72bbe.js +0 -379
- package/out/_next/static/chunks/46e882944a7f3aed.js +0 -1
- package/out/_next/static/chunks/59b058cf94cc555e.js +0 -1
- package/out/_next/static/chunks/6e36daee8960e188.js +0 -5
- package/out/_next/static/chunks/80a5431d4e78c469.js +0 -1
- package/out/_next/static/chunks/8a38426c5ed81587.js +0 -1
- package/out/_next/static/chunks/edbd80df6004e249.js +0 -52
- /package/out/_next/static/{dSD-EjLFfv3PUm7xo33fg → OIo5LFjdea8u6u4evHHQo}/_buildManifest.js +0 -0
- /package/out/_next/static/{dSD-EjLFfv3PUm7xo33fg → OIo5LFjdea8u6u4evHHQo}/_clientMiddlewareManifest.json +0 -0
- /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
|
|
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:
|
|
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
|