@timo9378/flow2code 0.1.5 → 0.1.7
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 +28 -0
- package/dist/cli.js +67 -31
- package/dist/compiler.cjs +27 -24
- package/dist/compiler.js +27 -24
- package/dist/server.js +29 -25
- package/out/404.html +1 -1
- package/out/__next.__PAGE__.txt +2 -2
- package/out/__next._full.txt +2 -2
- package/out/__next._head.txt +1 -1
- package/out/__next._index.txt +1 -1
- package/out/__next._tree.txt +1 -1
- package/out/_next/static/chunks/{2be31674b47c1089.js → 2fd98ca28cbab9a6.js} +2 -2
- package/out/_next/static/chunks/4ce13068a7e61854.js +25 -0
- package/out/_next/static/chunks/{5f1a9fec0e69c483.js → fd3c7cf5ea219c74.js} +1 -1
- package/out/_not-found/__next._full.txt +1 -1
- package/out/_not-found/__next._head.txt +1 -1
- package/out/_not-found/__next._index.txt +1 -1
- package/out/_not-found/__next._not-found/__PAGE__.txt +1 -1
- package/out/_not-found/__next._not-found.txt +1 -1
- package/out/_not-found/__next._tree.txt +1 -1
- package/out/_not-found.html +1 -1
- package/out/_not-found.txt +1 -1
- package/out/index.html +2 -2
- package/out/index.txt +2 -2
- package/package.json +1 -1
- /package/out/_next/static/{qqgjMH_XFks-bLx9gn_yB → 4eCLtLp-IPL1tppPGnHZE}/_buildManifest.js +0 -0
- /package/out/_next/static/{qqgjMH_XFks-bLx9gn_yB → 4eCLtLp-IPL1tppPGnHZE}/_clientMiddlewareManifest.json +0 -0
- /package/out/_next/static/{qqgjMH_XFks-bLx9gn_yB → 4eCLtLp-IPL1tppPGnHZE}/_ssgManifest.js +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,34 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
7
7
|
|
|
8
|
+
## [0.1.7] — 2026-03-05
|
|
9
|
+
|
|
10
|
+
### Performance
|
|
11
|
+
- **Precompute `edgeSuccessors` map** — Eliminated O(N×E) per-call rebuild in `generateBlockContinuation`; successor lookup is now O(1) via pre-built map in `CompilerContext`
|
|
12
|
+
- **Reuse `nodeMap` in control-flow analysis** — `computeControlFlowDescendants` now receives the existing `nodeMap` instead of rebuilding a redundant `new Map()` each call
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
- **Decompiler `processForStatement` AST correctness** — Replaced fragile `.split("{")` string hack with proper AST methods (`getInitializer()`, `getCondition()`, `getIncrementor()`); fixes incorrect parsing when loop body contains object literals
|
|
16
|
+
- **OpenAPI YAML import** — `handleImportOpenAPI` now supports `.yaml`/`.yml` files via dynamic `import("yaml")` instead of silently failing with `JSON.parse`
|
|
17
|
+
- **`revokeObjectURL` download race** — Deferred `URL.revokeObjectURL` by 10 seconds after `click()` to prevent Safari/slow-browser download failures
|
|
18
|
+
- **CLI watch mode async I/O** — New `loadFlowProjectAsync` reads flow projects with `fs/promises` (`readFile`/`readdir`); `compileFlowDirAsync` no longer blocks the event loop with sync file I/O
|
|
19
|
+
|
|
20
|
+
### Tests
|
|
21
|
+
- Added `loadFlowProjectAsync` parity tests (split + JSON + error case)
|
|
22
|
+
- Test count: 410 tests / 33 test files
|
|
23
|
+
|
|
24
|
+
## [0.1.6] — 2026-03-05
|
|
25
|
+
|
|
26
|
+
### Fixed (Critical)
|
|
27
|
+
- **Compiler uses migratedIR** — `compile()` now uses validator's auto-migrated IR instead of the stale original, preventing silent data corruption on older IR versions
|
|
28
|
+
- **`generateConcurrentNodes` flowState overwrite** — Removed `flowState[nodeId] = rN` overwrite that replaced correct values with `undefined` (task functions already populate flowState internally)
|
|
29
|
+
- **`handleCompile` path traversal** — Added separator suffix to `startsWith()` check, preventing directory-prefix attacks (e.g. `/home/user` → `/home/user2/`)
|
|
30
|
+
- **Undo/Redo snapshot deep clone** — `createSnapshot()` now uses `structuredClone(n.data)` instead of shallow spread, preventing nested mutation from corrupting all snapshots sharing a reference
|
|
31
|
+
|
|
32
|
+
### Tests
|
|
33
|
+
- Added path traversal regression test, IR migration integration test
|
|
34
|
+
- Test count: 407 tests / 33 test files
|
|
35
|
+
|
|
8
36
|
## [0.1.5] — 2026-03-05
|
|
9
37
|
|
|
10
38
|
### Security
|
package/dist/cli.js
CHANGED
|
@@ -1822,21 +1822,22 @@ function compile(ir, options) {
|
|
|
1822
1822
|
errors: validation.errors.map((e) => `[${e.code}] ${e.message}`)
|
|
1823
1823
|
};
|
|
1824
1824
|
}
|
|
1825
|
+
const workingIR = validation.migrated && validation.migratedIR ? validation.migratedIR : ir;
|
|
1825
1826
|
let plan;
|
|
1826
1827
|
try {
|
|
1827
|
-
plan = topologicalSort(
|
|
1828
|
+
plan = topologicalSort(workingIR);
|
|
1828
1829
|
} catch (err) {
|
|
1829
1830
|
return {
|
|
1830
1831
|
success: false,
|
|
1831
1832
|
errors: [err instanceof Error ? err.message : String(err)]
|
|
1832
1833
|
};
|
|
1833
1834
|
}
|
|
1834
|
-
const nodeMap = new Map(
|
|
1835
|
+
const nodeMap = new Map(workingIR.nodes.map((n) => [n.id, n]));
|
|
1835
1836
|
const platformName = options?.platform ?? "nextjs";
|
|
1836
1837
|
const platform = getPlatform(platformName);
|
|
1837
|
-
const symbolTable = buildSymbolTable(
|
|
1838
|
+
const symbolTable = buildSymbolTable(workingIR);
|
|
1838
1839
|
const context = {
|
|
1839
|
-
ir,
|
|
1840
|
+
ir: workingIR,
|
|
1840
1841
|
plan,
|
|
1841
1842
|
nodeMap,
|
|
1842
1843
|
envVars: /* @__PURE__ */ new Set(),
|
|
@@ -1851,10 +1852,18 @@ function compile(ir, options) {
|
|
|
1851
1852
|
dagMode: false,
|
|
1852
1853
|
symbolTableExclusions: /* @__PURE__ */ new Set(),
|
|
1853
1854
|
generatedBlockNodeIds: /* @__PURE__ */ new Set(),
|
|
1854
|
-
pluginRegistry
|
|
1855
|
+
pluginRegistry,
|
|
1856
|
+
edgeSuccessors: (() => {
|
|
1857
|
+
const map = /* @__PURE__ */ new Map();
|
|
1858
|
+
for (const edge of workingIR.edges) {
|
|
1859
|
+
if (!map.has(edge.sourceNodeId)) map.set(edge.sourceNodeId, []);
|
|
1860
|
+
map.get(edge.sourceNodeId).push(edge.targetNodeId);
|
|
1861
|
+
}
|
|
1862
|
+
return map;
|
|
1863
|
+
})()
|
|
1855
1864
|
};
|
|
1856
|
-
const trigger =
|
|
1857
|
-
const preComputedBlockNodes = computeControlFlowDescendants(
|
|
1865
|
+
const trigger = workingIR.nodes.find((n) => n.category === "trigger" /* TRIGGER */);
|
|
1866
|
+
const preComputedBlockNodes = computeControlFlowDescendants(workingIR, trigger.id, nodeMap);
|
|
1858
1867
|
for (const nodeId of preComputedBlockNodes) {
|
|
1859
1868
|
context.childBlockNodeIds.add(nodeId);
|
|
1860
1869
|
context.symbolTableExclusions.add(nodeId);
|
|
@@ -1864,7 +1873,7 @@ function compile(ir, options) {
|
|
|
1864
1873
|
);
|
|
1865
1874
|
if (hasConcurrency) {
|
|
1866
1875
|
context.dagMode = true;
|
|
1867
|
-
for (const node of
|
|
1876
|
+
for (const node of workingIR.nodes) {
|
|
1868
1877
|
if (node.category !== "trigger" /* TRIGGER */) {
|
|
1869
1878
|
context.symbolTableExclusions.add(node.id);
|
|
1870
1879
|
}
|
|
@@ -1879,8 +1888,8 @@ function compile(ir, options) {
|
|
|
1879
1888
|
});
|
|
1880
1889
|
const code = sourceFile.getFullText();
|
|
1881
1890
|
const filePath = platform.getOutputFilePath(trigger);
|
|
1882
|
-
collectRequiredPackages(
|
|
1883
|
-
const sourceMap = buildSourceMap(code,
|
|
1891
|
+
collectRequiredPackages(workingIR, context);
|
|
1892
|
+
const sourceMap = buildSourceMap(code, workingIR, filePath);
|
|
1884
1893
|
const dependencies = {
|
|
1885
1894
|
all: [...context.requiredPackages].sort(),
|
|
1886
1895
|
missing: [...context.requiredPackages].sort(),
|
|
@@ -1934,8 +1943,7 @@ function isControlFlowEdge(edge, nodeMap) {
|
|
|
1934
1943
|
const controlPorts = CONTROL_FLOW_PORT_MAP[sourceNode.nodeType];
|
|
1935
1944
|
return controlPorts !== void 0 && controlPorts.has(edge.sourcePortId);
|
|
1936
1945
|
}
|
|
1937
|
-
function computeControlFlowDescendants(ir, triggerId) {
|
|
1938
|
-
const nodeMap = new Map(ir.nodes.map((n) => [n.id, n]));
|
|
1946
|
+
function computeControlFlowDescendants(ir, triggerId, nodeMap) {
|
|
1939
1947
|
const strippedSuccessors = /* @__PURE__ */ new Map();
|
|
1940
1948
|
for (const node of ir.nodes) {
|
|
1941
1949
|
strippedSuccessors.set(node.id, /* @__PURE__ */ new Set());
|
|
@@ -1968,13 +1976,7 @@ function computeControlFlowDescendants(ir, triggerId) {
|
|
|
1968
1976
|
function generateBlockContinuation(writer, fromNodeId, context) {
|
|
1969
1977
|
const reachable = /* @__PURE__ */ new Set();
|
|
1970
1978
|
const bfsQueue = [fromNodeId];
|
|
1971
|
-
const edgeSuccessors =
|
|
1972
|
-
for (const edge of context.ir.edges) {
|
|
1973
|
-
if (!edgeSuccessors.has(edge.sourceNodeId)) {
|
|
1974
|
-
edgeSuccessors.set(edge.sourceNodeId, []);
|
|
1975
|
-
}
|
|
1976
|
-
edgeSuccessors.get(edge.sourceNodeId).push(edge.targetNodeId);
|
|
1977
|
-
}
|
|
1979
|
+
const edgeSuccessors = context.edgeSuccessors;
|
|
1978
1980
|
while (bfsQueue.length > 0) {
|
|
1979
1981
|
const id = bfsQueue.shift();
|
|
1980
1982
|
if (reachable.has(id)) continue;
|
|
@@ -2129,11 +2131,8 @@ function generateConcurrentNodes(writer, nodeIds, context) {
|
|
|
2129
2131
|
writer.writeLine(";");
|
|
2130
2132
|
}
|
|
2131
2133
|
writer.writeLine(
|
|
2132
|
-
`
|
|
2134
|
+
`await Promise.all([${taskNames.map((t) => `${t}()`).join(", ")}]);`
|
|
2133
2135
|
);
|
|
2134
|
-
activeNodeIds.forEach((nodeId, i) => {
|
|
2135
|
-
writer.writeLine(`flowState['${nodeId}'] = r${i};`);
|
|
2136
|
-
});
|
|
2137
2136
|
activeNodeIds.forEach((nodeId) => {
|
|
2138
2137
|
const varName = context.symbolTable.getVarName(nodeId);
|
|
2139
2138
|
writer.writeLine(`const ${varName} = flowState['${nodeId}'];`);
|
|
@@ -2766,7 +2765,10 @@ function processForInStatement(stmt, _prevNodeId, ctx, line) {
|
|
|
2766
2765
|
}
|
|
2767
2766
|
function processForStatement(stmt, _prevNodeId, ctx, line) {
|
|
2768
2767
|
const nodeId = ctx.nextId("loop");
|
|
2769
|
-
const
|
|
2768
|
+
const initText = stmt.getInitializer()?.getText() ?? "";
|
|
2769
|
+
const condText = stmt.getCondition()?.getText() ?? "";
|
|
2770
|
+
const incrText = stmt.getIncrementor()?.getText() ?? "";
|
|
2771
|
+
const fullText = `for (${initText}; ${condText}; ${incrText})`;
|
|
2770
2772
|
ctx.addNode({
|
|
2771
2773
|
id: nodeId,
|
|
2772
2774
|
nodeType: "for_loop" /* FOR_LOOP */,
|
|
@@ -3707,7 +3709,9 @@ function handleCompile(body, projectRoot) {
|
|
|
3707
3709
|
let writtenPath = null;
|
|
3708
3710
|
if (shouldWrite && result.filePath && result.code) {
|
|
3709
3711
|
const fullPath = resolve(join2(projectRoot, result.filePath));
|
|
3710
|
-
|
|
3712
|
+
const resolvedRoot = resolve(projectRoot);
|
|
3713
|
+
const sep = resolvedRoot.endsWith("/") || resolvedRoot.endsWith("\\") ? "" : process.platform === "win32" ? "\\" : "/";
|
|
3714
|
+
if (!fullPath.startsWith(resolvedRoot + sep)) {
|
|
3711
3715
|
return {
|
|
3712
3716
|
status: 400,
|
|
3713
3717
|
body: { success: false, error: "Output path escapes project root" }
|
|
@@ -3946,7 +3950,7 @@ __export(server_exports, {
|
|
|
3946
3950
|
startServer: () => startServer
|
|
3947
3951
|
});
|
|
3948
3952
|
import { createServer } from "http";
|
|
3949
|
-
import { readFile, stat } from "fs/promises";
|
|
3953
|
+
import { readFile as readFile2, stat } from "fs/promises";
|
|
3950
3954
|
import { join as join3, extname as extname2, dirname as dirname3, resolve as resolve2 } from "path";
|
|
3951
3955
|
import { fileURLToPath } from "url";
|
|
3952
3956
|
import { existsSync as existsSync3 } from "fs";
|
|
@@ -4044,7 +4048,7 @@ async function serveStatic(staticDir, pathname, res) {
|
|
|
4044
4048
|
if (!s.isFile()) return false;
|
|
4045
4049
|
const ext = extname2(filePath).toLowerCase();
|
|
4046
4050
|
const contentType = MIME_TYPES[ext] || "application/octet-stream";
|
|
4047
|
-
const content = await
|
|
4051
|
+
const content = await readFile2(filePath);
|
|
4048
4052
|
res.writeHead(200, { "Content-Type": contentType });
|
|
4049
4053
|
res.end(content);
|
|
4050
4054
|
return true;
|
|
@@ -4108,7 +4112,7 @@ async function handleRequest(req, res, staticDir, projectRoot) {
|
|
|
4108
4112
|
if (served) return;
|
|
4109
4113
|
const indexPath = join3(staticDir, "index.html");
|
|
4110
4114
|
try {
|
|
4111
|
-
const content = await
|
|
4115
|
+
const content = await readFile2(indexPath, "utf-8");
|
|
4112
4116
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
4113
4117
|
res.end(content);
|
|
4114
4118
|
} catch {
|
|
@@ -4190,7 +4194,7 @@ init_logger();
|
|
|
4190
4194
|
init_validator();
|
|
4191
4195
|
import { Command } from "commander";
|
|
4192
4196
|
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync as existsSync4, readdirSync as readdirSync2, rmSync as rmSync2 } from "fs";
|
|
4193
|
-
import { readFile as
|
|
4197
|
+
import { readFile as readFile3, writeFile, mkdir } from "fs/promises";
|
|
4194
4198
|
import { join as join4, dirname as dirname4, resolve as resolve3, basename as basename2 } from "path";
|
|
4195
4199
|
import { watch } from "chokidar";
|
|
4196
4200
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
@@ -4329,6 +4333,7 @@ function sanitizeFilename(id) {
|
|
|
4329
4333
|
|
|
4330
4334
|
// src/lib/storage/flow-project.ts
|
|
4331
4335
|
import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync, rmSync, statSync } from "fs";
|
|
4336
|
+
import { readFile, readdir } from "fs/promises";
|
|
4332
4337
|
import { join, dirname } from "path";
|
|
4333
4338
|
function detectFormat(inputPath) {
|
|
4334
4339
|
if (inputPath.endsWith(".flow.json") && existsSync(inputPath)) {
|
|
@@ -4379,6 +4384,37 @@ function loadFlowProject(inputPath) {
|
|
|
4379
4384
|
const ir = mergeIR({ meta, edges, nodes });
|
|
4380
4385
|
return { path: resolvedPath, format, ir };
|
|
4381
4386
|
}
|
|
4387
|
+
async function loadFlowProjectAsync(inputPath) {
|
|
4388
|
+
const { resolvedPath, format } = detectFormat(inputPath);
|
|
4389
|
+
if (format === "json") {
|
|
4390
|
+
if (!existsSync(resolvedPath)) {
|
|
4391
|
+
throw new Error(`Flow file not found: ${resolvedPath}`);
|
|
4392
|
+
}
|
|
4393
|
+
const raw = await readFile(resolvedPath, "utf-8");
|
|
4394
|
+
const ir2 = JSON.parse(raw);
|
|
4395
|
+
return { path: resolvedPath, format, ir: ir2 };
|
|
4396
|
+
}
|
|
4397
|
+
if (!existsSync(resolvedPath)) {
|
|
4398
|
+
throw new Error(`Flow directory not found: ${resolvedPath}`);
|
|
4399
|
+
}
|
|
4400
|
+
const metaPath = join(resolvedPath, "meta.yaml");
|
|
4401
|
+
if (!existsSync(metaPath)) {
|
|
4402
|
+
throw new Error(`meta.yaml not found in ${resolvedPath} \u2014 not a valid Flow directory`);
|
|
4403
|
+
}
|
|
4404
|
+
const meta = await readFile(metaPath, "utf-8");
|
|
4405
|
+
const edgesPath = join(resolvedPath, "edges.yaml");
|
|
4406
|
+
const edges = existsSync(edgesPath) ? await readFile(edgesPath, "utf-8") : "";
|
|
4407
|
+
const nodesDir = join(resolvedPath, "nodes");
|
|
4408
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
4409
|
+
if (existsSync(nodesDir)) {
|
|
4410
|
+
const nodeFiles = (await readdir(nodesDir)).filter((f) => f.endsWith(".yaml"));
|
|
4411
|
+
for (const file of nodeFiles) {
|
|
4412
|
+
nodes.set(file, await readFile(join(nodesDir, file), "utf-8"));
|
|
4413
|
+
}
|
|
4414
|
+
}
|
|
4415
|
+
const ir = mergeIR({ meta, edges, nodes });
|
|
4416
|
+
return { path: resolvedPath, format, ir };
|
|
4417
|
+
}
|
|
4382
4418
|
function saveFlowProject(ir, outputPath, options = {}) {
|
|
4383
4419
|
const { format = "split", cleanOrphanNodes = true } = options;
|
|
4384
4420
|
if (format === "json") {
|
|
@@ -5310,7 +5346,7 @@ function generateEnvExample() {
|
|
|
5310
5346
|
async function compileFileAsync(filePath, projectRoot) {
|
|
5311
5347
|
const startTime = Date.now();
|
|
5312
5348
|
try {
|
|
5313
|
-
const raw = await
|
|
5349
|
+
const raw = await readFile3(filePath, "utf-8");
|
|
5314
5350
|
const ir = JSON.parse(raw);
|
|
5315
5351
|
const validation = validateFlowIR(ir);
|
|
5316
5352
|
if (!validation.valid) {
|
|
@@ -5345,7 +5381,7 @@ async function compileFileAsync(filePath, projectRoot) {
|
|
|
5345
5381
|
async function compileFlowDirAsync(dirPath, projectRoot) {
|
|
5346
5382
|
const startTime = Date.now();
|
|
5347
5383
|
try {
|
|
5348
|
-
const project =
|
|
5384
|
+
const project = await loadFlowProjectAsync(dirPath);
|
|
5349
5385
|
const ir = project.ir;
|
|
5350
5386
|
const validation = validateFlowIR(ir);
|
|
5351
5387
|
if (!validation.valid) {
|
package/dist/compiler.cjs
CHANGED
|
@@ -1831,21 +1831,22 @@ function compile(ir, options) {
|
|
|
1831
1831
|
errors: validation.errors.map((e) => `[${e.code}] ${e.message}`)
|
|
1832
1832
|
};
|
|
1833
1833
|
}
|
|
1834
|
+
const workingIR = validation.migrated && validation.migratedIR ? validation.migratedIR : ir;
|
|
1834
1835
|
let plan;
|
|
1835
1836
|
try {
|
|
1836
|
-
plan = topologicalSort(
|
|
1837
|
+
plan = topologicalSort(workingIR);
|
|
1837
1838
|
} catch (err) {
|
|
1838
1839
|
return {
|
|
1839
1840
|
success: false,
|
|
1840
1841
|
errors: [err instanceof Error ? err.message : String(err)]
|
|
1841
1842
|
};
|
|
1842
1843
|
}
|
|
1843
|
-
const nodeMap = new Map(
|
|
1844
|
+
const nodeMap = new Map(workingIR.nodes.map((n) => [n.id, n]));
|
|
1844
1845
|
const platformName = options?.platform ?? "nextjs";
|
|
1845
1846
|
const platform = getPlatform(platformName);
|
|
1846
|
-
const symbolTable = buildSymbolTable(
|
|
1847
|
+
const symbolTable = buildSymbolTable(workingIR);
|
|
1847
1848
|
const context = {
|
|
1848
|
-
ir,
|
|
1849
|
+
ir: workingIR,
|
|
1849
1850
|
plan,
|
|
1850
1851
|
nodeMap,
|
|
1851
1852
|
envVars: /* @__PURE__ */ new Set(),
|
|
@@ -1860,10 +1861,18 @@ function compile(ir, options) {
|
|
|
1860
1861
|
dagMode: false,
|
|
1861
1862
|
symbolTableExclusions: /* @__PURE__ */ new Set(),
|
|
1862
1863
|
generatedBlockNodeIds: /* @__PURE__ */ new Set(),
|
|
1863
|
-
pluginRegistry
|
|
1864
|
+
pluginRegistry,
|
|
1865
|
+
edgeSuccessors: (() => {
|
|
1866
|
+
const map = /* @__PURE__ */ new Map();
|
|
1867
|
+
for (const edge of workingIR.edges) {
|
|
1868
|
+
if (!map.has(edge.sourceNodeId)) map.set(edge.sourceNodeId, []);
|
|
1869
|
+
map.get(edge.sourceNodeId).push(edge.targetNodeId);
|
|
1870
|
+
}
|
|
1871
|
+
return map;
|
|
1872
|
+
})()
|
|
1864
1873
|
};
|
|
1865
|
-
const trigger =
|
|
1866
|
-
const preComputedBlockNodes = computeControlFlowDescendants(
|
|
1874
|
+
const trigger = workingIR.nodes.find((n) => n.category === "trigger" /* TRIGGER */);
|
|
1875
|
+
const preComputedBlockNodes = computeControlFlowDescendants(workingIR, trigger.id, nodeMap);
|
|
1867
1876
|
for (const nodeId of preComputedBlockNodes) {
|
|
1868
1877
|
context.childBlockNodeIds.add(nodeId);
|
|
1869
1878
|
context.symbolTableExclusions.add(nodeId);
|
|
@@ -1873,7 +1882,7 @@ function compile(ir, options) {
|
|
|
1873
1882
|
);
|
|
1874
1883
|
if (hasConcurrency) {
|
|
1875
1884
|
context.dagMode = true;
|
|
1876
|
-
for (const node of
|
|
1885
|
+
for (const node of workingIR.nodes) {
|
|
1877
1886
|
if (node.category !== "trigger" /* TRIGGER */) {
|
|
1878
1887
|
context.symbolTableExclusions.add(node.id);
|
|
1879
1888
|
}
|
|
@@ -1888,8 +1897,8 @@ function compile(ir, options) {
|
|
|
1888
1897
|
});
|
|
1889
1898
|
const code = sourceFile.getFullText();
|
|
1890
1899
|
const filePath = platform.getOutputFilePath(trigger);
|
|
1891
|
-
collectRequiredPackages(
|
|
1892
|
-
const sourceMap = buildSourceMap(code,
|
|
1900
|
+
collectRequiredPackages(workingIR, context);
|
|
1901
|
+
const sourceMap = buildSourceMap(code, workingIR, filePath);
|
|
1893
1902
|
const dependencies = {
|
|
1894
1903
|
all: [...context.requiredPackages].sort(),
|
|
1895
1904
|
missing: [...context.requiredPackages].sort(),
|
|
@@ -1948,8 +1957,7 @@ function isControlFlowEdge(edge, nodeMap) {
|
|
|
1948
1957
|
const controlPorts = CONTROL_FLOW_PORT_MAP[sourceNode.nodeType];
|
|
1949
1958
|
return controlPorts !== void 0 && controlPorts.has(edge.sourcePortId);
|
|
1950
1959
|
}
|
|
1951
|
-
function computeControlFlowDescendants(ir, triggerId) {
|
|
1952
|
-
const nodeMap = new Map(ir.nodes.map((n) => [n.id, n]));
|
|
1960
|
+
function computeControlFlowDescendants(ir, triggerId, nodeMap) {
|
|
1953
1961
|
const strippedSuccessors = /* @__PURE__ */ new Map();
|
|
1954
1962
|
for (const node of ir.nodes) {
|
|
1955
1963
|
strippedSuccessors.set(node.id, /* @__PURE__ */ new Set());
|
|
@@ -1982,13 +1990,7 @@ function computeControlFlowDescendants(ir, triggerId) {
|
|
|
1982
1990
|
function generateBlockContinuation(writer, fromNodeId, context) {
|
|
1983
1991
|
const reachable = /* @__PURE__ */ new Set();
|
|
1984
1992
|
const bfsQueue = [fromNodeId];
|
|
1985
|
-
const edgeSuccessors =
|
|
1986
|
-
for (const edge of context.ir.edges) {
|
|
1987
|
-
if (!edgeSuccessors.has(edge.sourceNodeId)) {
|
|
1988
|
-
edgeSuccessors.set(edge.sourceNodeId, []);
|
|
1989
|
-
}
|
|
1990
|
-
edgeSuccessors.get(edge.sourceNodeId).push(edge.targetNodeId);
|
|
1991
|
-
}
|
|
1993
|
+
const edgeSuccessors = context.edgeSuccessors;
|
|
1992
1994
|
while (bfsQueue.length > 0) {
|
|
1993
1995
|
const id = bfsQueue.shift();
|
|
1994
1996
|
if (reachable.has(id)) continue;
|
|
@@ -2143,11 +2145,8 @@ function generateConcurrentNodes(writer, nodeIds, context) {
|
|
|
2143
2145
|
writer.writeLine(";");
|
|
2144
2146
|
}
|
|
2145
2147
|
writer.writeLine(
|
|
2146
|
-
`
|
|
2148
|
+
`await Promise.all([${taskNames.map((t) => `${t}()`).join(", ")}]);`
|
|
2147
2149
|
);
|
|
2148
|
-
activeNodeIds.forEach((nodeId, i) => {
|
|
2149
|
-
writer.writeLine(`flowState['${nodeId}'] = r${i};`);
|
|
2150
|
-
});
|
|
2151
2150
|
activeNodeIds.forEach((nodeId) => {
|
|
2152
2151
|
const varName = context.symbolTable.getVarName(nodeId);
|
|
2153
2152
|
writer.writeLine(`const ${varName} = flowState['${nodeId}'];`);
|
|
@@ -2820,7 +2819,10 @@ function processForInStatement(stmt, _prevNodeId, ctx, line) {
|
|
|
2820
2819
|
}
|
|
2821
2820
|
function processForStatement(stmt, _prevNodeId, ctx, line) {
|
|
2822
2821
|
const nodeId = ctx.nextId("loop");
|
|
2823
|
-
const
|
|
2822
|
+
const initText = stmt.getInitializer()?.getText() ?? "";
|
|
2823
|
+
const condText = stmt.getCondition()?.getText() ?? "";
|
|
2824
|
+
const incrText = stmt.getIncrementor()?.getText() ?? "";
|
|
2825
|
+
const fullText = `for (${initText}; ${condText}; ${incrText})`;
|
|
2824
2826
|
ctx.addNode({
|
|
2825
2827
|
id: nodeId,
|
|
2826
2828
|
nodeType: "for_loop" /* FOR_LOOP */,
|
|
@@ -3769,6 +3771,7 @@ function sanitizeFilename(id) {
|
|
|
3769
3771
|
|
|
3770
3772
|
// src/lib/storage/flow-project.ts
|
|
3771
3773
|
var import_node_fs = require("fs");
|
|
3774
|
+
var import_promises = require("fs/promises");
|
|
3772
3775
|
var import_node_path = require("path");
|
|
3773
3776
|
function detectFormat(inputPath) {
|
|
3774
3777
|
if (inputPath.endsWith(".flow.json") && (0, import_node_fs.existsSync)(inputPath)) {
|
package/dist/compiler.js
CHANGED
|
@@ -1764,21 +1764,22 @@ function compile(ir, options) {
|
|
|
1764
1764
|
errors: validation.errors.map((e) => `[${e.code}] ${e.message}`)
|
|
1765
1765
|
};
|
|
1766
1766
|
}
|
|
1767
|
+
const workingIR = validation.migrated && validation.migratedIR ? validation.migratedIR : ir;
|
|
1767
1768
|
let plan;
|
|
1768
1769
|
try {
|
|
1769
|
-
plan = topologicalSort(
|
|
1770
|
+
plan = topologicalSort(workingIR);
|
|
1770
1771
|
} catch (err) {
|
|
1771
1772
|
return {
|
|
1772
1773
|
success: false,
|
|
1773
1774
|
errors: [err instanceof Error ? err.message : String(err)]
|
|
1774
1775
|
};
|
|
1775
1776
|
}
|
|
1776
|
-
const nodeMap = new Map(
|
|
1777
|
+
const nodeMap = new Map(workingIR.nodes.map((n) => [n.id, n]));
|
|
1777
1778
|
const platformName = options?.platform ?? "nextjs";
|
|
1778
1779
|
const platform = getPlatform(platformName);
|
|
1779
|
-
const symbolTable = buildSymbolTable(
|
|
1780
|
+
const symbolTable = buildSymbolTable(workingIR);
|
|
1780
1781
|
const context = {
|
|
1781
|
-
ir,
|
|
1782
|
+
ir: workingIR,
|
|
1782
1783
|
plan,
|
|
1783
1784
|
nodeMap,
|
|
1784
1785
|
envVars: /* @__PURE__ */ new Set(),
|
|
@@ -1793,10 +1794,18 @@ function compile(ir, options) {
|
|
|
1793
1794
|
dagMode: false,
|
|
1794
1795
|
symbolTableExclusions: /* @__PURE__ */ new Set(),
|
|
1795
1796
|
generatedBlockNodeIds: /* @__PURE__ */ new Set(),
|
|
1796
|
-
pluginRegistry
|
|
1797
|
+
pluginRegistry,
|
|
1798
|
+
edgeSuccessors: (() => {
|
|
1799
|
+
const map = /* @__PURE__ */ new Map();
|
|
1800
|
+
for (const edge of workingIR.edges) {
|
|
1801
|
+
if (!map.has(edge.sourceNodeId)) map.set(edge.sourceNodeId, []);
|
|
1802
|
+
map.get(edge.sourceNodeId).push(edge.targetNodeId);
|
|
1803
|
+
}
|
|
1804
|
+
return map;
|
|
1805
|
+
})()
|
|
1797
1806
|
};
|
|
1798
|
-
const trigger =
|
|
1799
|
-
const preComputedBlockNodes = computeControlFlowDescendants(
|
|
1807
|
+
const trigger = workingIR.nodes.find((n) => n.category === "trigger" /* TRIGGER */);
|
|
1808
|
+
const preComputedBlockNodes = computeControlFlowDescendants(workingIR, trigger.id, nodeMap);
|
|
1800
1809
|
for (const nodeId of preComputedBlockNodes) {
|
|
1801
1810
|
context.childBlockNodeIds.add(nodeId);
|
|
1802
1811
|
context.symbolTableExclusions.add(nodeId);
|
|
@@ -1806,7 +1815,7 @@ function compile(ir, options) {
|
|
|
1806
1815
|
);
|
|
1807
1816
|
if (hasConcurrency) {
|
|
1808
1817
|
context.dagMode = true;
|
|
1809
|
-
for (const node of
|
|
1818
|
+
for (const node of workingIR.nodes) {
|
|
1810
1819
|
if (node.category !== "trigger" /* TRIGGER */) {
|
|
1811
1820
|
context.symbolTableExclusions.add(node.id);
|
|
1812
1821
|
}
|
|
@@ -1821,8 +1830,8 @@ function compile(ir, options) {
|
|
|
1821
1830
|
});
|
|
1822
1831
|
const code = sourceFile.getFullText();
|
|
1823
1832
|
const filePath = platform.getOutputFilePath(trigger);
|
|
1824
|
-
collectRequiredPackages(
|
|
1825
|
-
const sourceMap = buildSourceMap(code,
|
|
1833
|
+
collectRequiredPackages(workingIR, context);
|
|
1834
|
+
const sourceMap = buildSourceMap(code, workingIR, filePath);
|
|
1826
1835
|
const dependencies = {
|
|
1827
1836
|
all: [...context.requiredPackages].sort(),
|
|
1828
1837
|
missing: [...context.requiredPackages].sort(),
|
|
@@ -1881,8 +1890,7 @@ function isControlFlowEdge(edge, nodeMap) {
|
|
|
1881
1890
|
const controlPorts = CONTROL_FLOW_PORT_MAP[sourceNode.nodeType];
|
|
1882
1891
|
return controlPorts !== void 0 && controlPorts.has(edge.sourcePortId);
|
|
1883
1892
|
}
|
|
1884
|
-
function computeControlFlowDescendants(ir, triggerId) {
|
|
1885
|
-
const nodeMap = new Map(ir.nodes.map((n) => [n.id, n]));
|
|
1893
|
+
function computeControlFlowDescendants(ir, triggerId, nodeMap) {
|
|
1886
1894
|
const strippedSuccessors = /* @__PURE__ */ new Map();
|
|
1887
1895
|
for (const node of ir.nodes) {
|
|
1888
1896
|
strippedSuccessors.set(node.id, /* @__PURE__ */ new Set());
|
|
@@ -1915,13 +1923,7 @@ function computeControlFlowDescendants(ir, triggerId) {
|
|
|
1915
1923
|
function generateBlockContinuation(writer, fromNodeId, context) {
|
|
1916
1924
|
const reachable = /* @__PURE__ */ new Set();
|
|
1917
1925
|
const bfsQueue = [fromNodeId];
|
|
1918
|
-
const edgeSuccessors =
|
|
1919
|
-
for (const edge of context.ir.edges) {
|
|
1920
|
-
if (!edgeSuccessors.has(edge.sourceNodeId)) {
|
|
1921
|
-
edgeSuccessors.set(edge.sourceNodeId, []);
|
|
1922
|
-
}
|
|
1923
|
-
edgeSuccessors.get(edge.sourceNodeId).push(edge.targetNodeId);
|
|
1924
|
-
}
|
|
1926
|
+
const edgeSuccessors = context.edgeSuccessors;
|
|
1925
1927
|
while (bfsQueue.length > 0) {
|
|
1926
1928
|
const id = bfsQueue.shift();
|
|
1927
1929
|
if (reachable.has(id)) continue;
|
|
@@ -2076,11 +2078,8 @@ function generateConcurrentNodes(writer, nodeIds, context) {
|
|
|
2076
2078
|
writer.writeLine(";");
|
|
2077
2079
|
}
|
|
2078
2080
|
writer.writeLine(
|
|
2079
|
-
`
|
|
2081
|
+
`await Promise.all([${taskNames.map((t) => `${t}()`).join(", ")}]);`
|
|
2080
2082
|
);
|
|
2081
|
-
activeNodeIds.forEach((nodeId, i) => {
|
|
2082
|
-
writer.writeLine(`flowState['${nodeId}'] = r${i};`);
|
|
2083
|
-
});
|
|
2084
2083
|
activeNodeIds.forEach((nodeId) => {
|
|
2085
2084
|
const varName = context.symbolTable.getVarName(nodeId);
|
|
2086
2085
|
writer.writeLine(`const ${varName} = flowState['${nodeId}'];`);
|
|
@@ -2756,7 +2755,10 @@ function processForInStatement(stmt, _prevNodeId, ctx, line) {
|
|
|
2756
2755
|
}
|
|
2757
2756
|
function processForStatement(stmt, _prevNodeId, ctx, line) {
|
|
2758
2757
|
const nodeId = ctx.nextId("loop");
|
|
2759
|
-
const
|
|
2758
|
+
const initText = stmt.getInitializer()?.getText() ?? "";
|
|
2759
|
+
const condText = stmt.getCondition()?.getText() ?? "";
|
|
2760
|
+
const incrText = stmt.getIncrementor()?.getText() ?? "";
|
|
2761
|
+
const fullText = `for (${initText}; ${condText}; ${incrText})`;
|
|
2760
2762
|
ctx.addNode({
|
|
2761
2763
|
id: nodeId,
|
|
2762
2764
|
nodeType: "for_loop" /* FOR_LOOP */,
|
|
@@ -3705,6 +3707,7 @@ function sanitizeFilename(id) {
|
|
|
3705
3707
|
|
|
3706
3708
|
// src/lib/storage/flow-project.ts
|
|
3707
3709
|
import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync, rmSync, statSync } from "fs";
|
|
3710
|
+
import { readFile, readdir } from "fs/promises";
|
|
3708
3711
|
import { join, dirname } from "path";
|
|
3709
3712
|
function detectFormat(inputPath) {
|
|
3710
3713
|
if (inputPath.endsWith(".flow.json") && existsSync(inputPath)) {
|
package/dist/server.js
CHANGED
|
@@ -1717,21 +1717,22 @@ function compile(ir, options) {
|
|
|
1717
1717
|
errors: validation.errors.map((e) => `[${e.code}] ${e.message}`)
|
|
1718
1718
|
};
|
|
1719
1719
|
}
|
|
1720
|
+
const workingIR = validation.migrated && validation.migratedIR ? validation.migratedIR : ir;
|
|
1720
1721
|
let plan;
|
|
1721
1722
|
try {
|
|
1722
|
-
plan = topologicalSort(
|
|
1723
|
+
plan = topologicalSort(workingIR);
|
|
1723
1724
|
} catch (err) {
|
|
1724
1725
|
return {
|
|
1725
1726
|
success: false,
|
|
1726
1727
|
errors: [err instanceof Error ? err.message : String(err)]
|
|
1727
1728
|
};
|
|
1728
1729
|
}
|
|
1729
|
-
const nodeMap = new Map(
|
|
1730
|
+
const nodeMap = new Map(workingIR.nodes.map((n) => [n.id, n]));
|
|
1730
1731
|
const platformName = options?.platform ?? "nextjs";
|
|
1731
1732
|
const platform = getPlatform(platformName);
|
|
1732
|
-
const symbolTable = buildSymbolTable(
|
|
1733
|
+
const symbolTable = buildSymbolTable(workingIR);
|
|
1733
1734
|
const context = {
|
|
1734
|
-
ir,
|
|
1735
|
+
ir: workingIR,
|
|
1735
1736
|
plan,
|
|
1736
1737
|
nodeMap,
|
|
1737
1738
|
envVars: /* @__PURE__ */ new Set(),
|
|
@@ -1746,10 +1747,18 @@ function compile(ir, options) {
|
|
|
1746
1747
|
dagMode: false,
|
|
1747
1748
|
symbolTableExclusions: /* @__PURE__ */ new Set(),
|
|
1748
1749
|
generatedBlockNodeIds: /* @__PURE__ */ new Set(),
|
|
1749
|
-
pluginRegistry
|
|
1750
|
+
pluginRegistry,
|
|
1751
|
+
edgeSuccessors: (() => {
|
|
1752
|
+
const map = /* @__PURE__ */ new Map();
|
|
1753
|
+
for (const edge of workingIR.edges) {
|
|
1754
|
+
if (!map.has(edge.sourceNodeId)) map.set(edge.sourceNodeId, []);
|
|
1755
|
+
map.get(edge.sourceNodeId).push(edge.targetNodeId);
|
|
1756
|
+
}
|
|
1757
|
+
return map;
|
|
1758
|
+
})()
|
|
1750
1759
|
};
|
|
1751
|
-
const trigger =
|
|
1752
|
-
const preComputedBlockNodes = computeControlFlowDescendants(
|
|
1760
|
+
const trigger = workingIR.nodes.find((n) => n.category === "trigger" /* TRIGGER */);
|
|
1761
|
+
const preComputedBlockNodes = computeControlFlowDescendants(workingIR, trigger.id, nodeMap);
|
|
1753
1762
|
for (const nodeId of preComputedBlockNodes) {
|
|
1754
1763
|
context.childBlockNodeIds.add(nodeId);
|
|
1755
1764
|
context.symbolTableExclusions.add(nodeId);
|
|
@@ -1759,7 +1768,7 @@ function compile(ir, options) {
|
|
|
1759
1768
|
);
|
|
1760
1769
|
if (hasConcurrency) {
|
|
1761
1770
|
context.dagMode = true;
|
|
1762
|
-
for (const node of
|
|
1771
|
+
for (const node of workingIR.nodes) {
|
|
1763
1772
|
if (node.category !== "trigger" /* TRIGGER */) {
|
|
1764
1773
|
context.symbolTableExclusions.add(node.id);
|
|
1765
1774
|
}
|
|
@@ -1774,8 +1783,8 @@ function compile(ir, options) {
|
|
|
1774
1783
|
});
|
|
1775
1784
|
const code = sourceFile.getFullText();
|
|
1776
1785
|
const filePath = platform.getOutputFilePath(trigger);
|
|
1777
|
-
collectRequiredPackages(
|
|
1778
|
-
const sourceMap = buildSourceMap(code,
|
|
1786
|
+
collectRequiredPackages(workingIR, context);
|
|
1787
|
+
const sourceMap = buildSourceMap(code, workingIR, filePath);
|
|
1779
1788
|
const dependencies = {
|
|
1780
1789
|
all: [...context.requiredPackages].sort(),
|
|
1781
1790
|
missing: [...context.requiredPackages].sort(),
|
|
@@ -1834,8 +1843,7 @@ function isControlFlowEdge(edge, nodeMap) {
|
|
|
1834
1843
|
const controlPorts = CONTROL_FLOW_PORT_MAP[sourceNode.nodeType];
|
|
1835
1844
|
return controlPorts !== void 0 && controlPorts.has(edge.sourcePortId);
|
|
1836
1845
|
}
|
|
1837
|
-
function computeControlFlowDescendants(ir, triggerId) {
|
|
1838
|
-
const nodeMap = new Map(ir.nodes.map((n) => [n.id, n]));
|
|
1846
|
+
function computeControlFlowDescendants(ir, triggerId, nodeMap) {
|
|
1839
1847
|
const strippedSuccessors = /* @__PURE__ */ new Map();
|
|
1840
1848
|
for (const node of ir.nodes) {
|
|
1841
1849
|
strippedSuccessors.set(node.id, /* @__PURE__ */ new Set());
|
|
@@ -1868,13 +1876,7 @@ function computeControlFlowDescendants(ir, triggerId) {
|
|
|
1868
1876
|
function generateBlockContinuation(writer, fromNodeId, context) {
|
|
1869
1877
|
const reachable = /* @__PURE__ */ new Set();
|
|
1870
1878
|
const bfsQueue = [fromNodeId];
|
|
1871
|
-
const edgeSuccessors =
|
|
1872
|
-
for (const edge of context.ir.edges) {
|
|
1873
|
-
if (!edgeSuccessors.has(edge.sourceNodeId)) {
|
|
1874
|
-
edgeSuccessors.set(edge.sourceNodeId, []);
|
|
1875
|
-
}
|
|
1876
|
-
edgeSuccessors.get(edge.sourceNodeId).push(edge.targetNodeId);
|
|
1877
|
-
}
|
|
1879
|
+
const edgeSuccessors = context.edgeSuccessors;
|
|
1878
1880
|
while (bfsQueue.length > 0) {
|
|
1879
1881
|
const id = bfsQueue.shift();
|
|
1880
1882
|
if (reachable.has(id)) continue;
|
|
@@ -2029,11 +2031,8 @@ function generateConcurrentNodes(writer, nodeIds, context) {
|
|
|
2029
2031
|
writer.writeLine(";");
|
|
2030
2032
|
}
|
|
2031
2033
|
writer.writeLine(
|
|
2032
|
-
`
|
|
2034
|
+
`await Promise.all([${taskNames.map((t) => `${t}()`).join(", ")}]);`
|
|
2033
2035
|
);
|
|
2034
|
-
activeNodeIds.forEach((nodeId, i) => {
|
|
2035
|
-
writer.writeLine(`flowState['${nodeId}'] = r${i};`);
|
|
2036
|
-
});
|
|
2037
2036
|
activeNodeIds.forEach((nodeId) => {
|
|
2038
2037
|
const varName = context.symbolTable.getVarName(nodeId);
|
|
2039
2038
|
writer.writeLine(`const ${varName} = flowState['${nodeId}'];`);
|
|
@@ -2569,7 +2568,10 @@ function processForInStatement(stmt, _prevNodeId, ctx, line) {
|
|
|
2569
2568
|
}
|
|
2570
2569
|
function processForStatement(stmt, _prevNodeId, ctx, line) {
|
|
2571
2570
|
const nodeId = ctx.nextId("loop");
|
|
2572
|
-
const
|
|
2571
|
+
const initText = stmt.getInitializer()?.getText() ?? "";
|
|
2572
|
+
const condText = stmt.getCondition()?.getText() ?? "";
|
|
2573
|
+
const incrText = stmt.getIncrementor()?.getText() ?? "";
|
|
2574
|
+
const fullText = `for (${initText}; ${condText}; ${incrText})`;
|
|
2573
2575
|
ctx.addNode({
|
|
2574
2576
|
id: nodeId,
|
|
2575
2577
|
nodeType: "for_loop" /* FOR_LOOP */,
|
|
@@ -3485,7 +3487,9 @@ function handleCompile(body, projectRoot) {
|
|
|
3485
3487
|
let writtenPath = null;
|
|
3486
3488
|
if (shouldWrite && result.filePath && result.code) {
|
|
3487
3489
|
const fullPath = resolve(join(projectRoot, result.filePath));
|
|
3488
|
-
|
|
3490
|
+
const resolvedRoot = resolve(projectRoot);
|
|
3491
|
+
const sep = resolvedRoot.endsWith("/") || resolvedRoot.endsWith("\\") ? "" : process.platform === "win32" ? "\\" : "/";
|
|
3492
|
+
if (!fullPath.startsWith(resolvedRoot + sep)) {
|
|
3489
3493
|
return {
|
|
3490
3494
|
status: 400,
|
|
3491
3495
|
body: { success: false, error: "Output path escapes project root" }
|