@timo9378/flow2code 0.1.3 → 0.1.5
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 +22 -0
- package/dist/cli.js +210 -92
- package/dist/compiler.cjs +46 -24
- package/dist/compiler.js +46 -24
- package/dist/server.js +129 -41
- package/out/404.html +1 -1
- package/out/__next.__PAGE__.txt +2 -2
- package/out/__next._full.txt +3 -3
- package/out/__next._head.txt +1 -1
- package/out/__next._index.txt +2 -2
- package/out/__next._tree.txt +2 -2
- package/out/_next/static/chunks/{993eabba22c07d39.js → 2be31674b47c1089.js} +8 -13
- package/out/_next/static/chunks/83ab8820627f8bfe.css +1 -0
- package/out/_not-found/__next._full.txt +2 -2
- package/out/_not-found/__next._head.txt +1 -1
- package/out/_not-found/__next._index.txt +2 -2
- 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 +2 -2
- package/out/_not-found.html +1 -1
- package/out/_not-found.txt +2 -2
- package/out/index.html +2 -2
- package/out/index.txt +3 -3
- package/package.json +2 -1
- package/scripts/publish-all.sh +56 -0
- package/out/_next/static/chunks/c8a26302d935bf6e.css +0 -1
- /package/out/_next/static/{gs3QpnA696kN6tOSlwt6o → qqgjMH_XFks-bLx9gn_yB}/_buildManifest.js +0 -0
- /package/out/_next/static/{gs3QpnA696kN6tOSlwt6o → qqgjMH_XFks-bLx9gn_yB}/_clientMiddlewareManifest.json +0 -0
- /package/out/_next/static/{gs3QpnA696kN6tOSlwt6o → qqgjMH_XFks-bLx9gn_yB}/_ssgManifest.js +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,28 @@ 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.5] — 2026-03-05
|
|
9
|
+
|
|
10
|
+
### Security
|
|
11
|
+
- **Path traversal fix** — `serveStatic` now uses `resolve()` + `startsWith()` guard with `decodeURIComponent` to prevent `../../etc/passwd` and `%2e%2e%2f` attacks
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
- **DAG swallowed errors** — `.catch(() => {})` replaced with `.catch((err) => { console.error(...) })` so concurrent promise errors are logged instead of silently discarded
|
|
15
|
+
- **CLI watch sync I/O** — Watch mode now uses async `readFile`/`writeFile`/`mkdir` + 150ms debounce to prevent event loop blocking
|
|
16
|
+
- **Source map brittle regex** — Replaced full-line regex with robust `indexOf`-based scanner for `[nodeId] ---` suffix tokens; survives Prettier/ESLint reformatting
|
|
17
|
+
- **Compiler state mutation** — Centralized child block registration via `applyChildBlockRegistration()` helper; plugins no longer scatter-write to context
|
|
18
|
+
- **env-check false positives** — `env-check` command now includes `Object.keys(process.env)` in declared vars whitelist (CI/CD injected vars)
|
|
19
|
+
- **Plugin error guard** — `plugin.generate()` wrapped in try/catch with descriptive error message identifying plugin, node label, and node ID; preserves `{ cause }` chain
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
- **Logger system** — `src/lib/logger.ts` with picocolors, log levels (debug/info/warn/error/silent), structured output (`kv`, `kvLast`, `blank`, `raw`), `--silent` CLI flag
|
|
23
|
+
- **picocolors** added as direct dependency (zero-dep, 3.8x faster than chalk)
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
- Dev script switched back to Turbopack (`next dev --turbopack`), removed `--webpack` workaround
|
|
27
|
+
- Server and CLI now use structured logger instead of raw `console.log`/`console.error`
|
|
28
|
+
- Test count: 405 tests / 33 test files
|
|
29
|
+
|
|
8
30
|
## [0.1.0] — 2026-02-27
|
|
9
31
|
|
|
10
32
|
### Added
|
package/dist/cli.js
CHANGED
|
@@ -2079,7 +2079,7 @@ function generateNodeChainDAG(writer, triggerId, context) {
|
|
|
2079
2079
|
generateNodeBody(writer, node, context);
|
|
2080
2080
|
});
|
|
2081
2081
|
writer.writeLine(`)();`);
|
|
2082
|
-
writer.writeLine(`${promiseVar}.catch(() => {});`);
|
|
2082
|
+
writer.writeLine(`${promiseVar}.catch((err) => { console.error("[Flow2Code DAG Error]", err); });`);
|
|
2083
2083
|
writer.blankLine();
|
|
2084
2084
|
}
|
|
2085
2085
|
if (dagNodeIds.length > 0) {
|
|
@@ -2152,7 +2152,18 @@ function generateNodeBody(writer, node, context) {
|
|
|
2152
2152
|
const plugin = context.pluginRegistry.get(node.nodeType);
|
|
2153
2153
|
if (plugin) {
|
|
2154
2154
|
const pluginCtx = createPluginContext(context);
|
|
2155
|
-
|
|
2155
|
+
try {
|
|
2156
|
+
plugin.generate(node, writer, pluginCtx);
|
|
2157
|
+
} catch (err) {
|
|
2158
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
2159
|
+
const stack = err instanceof Error ? err.stack : void 0;
|
|
2160
|
+
throw new Error(
|
|
2161
|
+
`[flow2code] Plugin "${node.nodeType}" threw an error while generating node "${node.label}" (${node.id}):
|
|
2162
|
+
${errMsg}` + (stack ? `
|
|
2163
|
+
Stack: ${stack}` : ""),
|
|
2164
|
+
{ cause: err }
|
|
2165
|
+
);
|
|
2166
|
+
}
|
|
2156
2167
|
} else {
|
|
2157
2168
|
throw new Error(
|
|
2158
2169
|
`[flow2code] Unsupported node type: "${node.nodeType}". Register a plugin via pluginRegistry.register() or use a built-in node type.`
|
|
@@ -2160,6 +2171,13 @@ function generateNodeBody(writer, node, context) {
|
|
|
2160
2171
|
}
|
|
2161
2172
|
}
|
|
2162
2173
|
function createPluginContext(context) {
|
|
2174
|
+
const pendingChildBlockIds = [];
|
|
2175
|
+
const applyChildBlockRegistration = (nodeId) => {
|
|
2176
|
+
pendingChildBlockIds.push(nodeId);
|
|
2177
|
+
context.childBlockNodeIds.add(nodeId);
|
|
2178
|
+
context.symbolTableExclusions.add(nodeId);
|
|
2179
|
+
context.generatedBlockNodeIds.add(nodeId);
|
|
2180
|
+
};
|
|
2163
2181
|
return {
|
|
2164
2182
|
ir: context.ir,
|
|
2165
2183
|
nodeMap: context.nodeMap,
|
|
@@ -2185,9 +2203,7 @@ function createPluginContext(context) {
|
|
|
2185
2203
|
return resolveEnvVars(url, context);
|
|
2186
2204
|
},
|
|
2187
2205
|
generateChildNode(writer, node) {
|
|
2188
|
-
|
|
2189
|
-
context.symbolTableExclusions.add(node.id);
|
|
2190
|
-
context.generatedBlockNodeIds.add(node.id);
|
|
2206
|
+
applyChildBlockRegistration(node.id);
|
|
2191
2207
|
writer.writeLine(`// --- ${node.label} (${node.nodeType}) [${node.id}] ---`);
|
|
2192
2208
|
generateNodeBody(writer, node, context);
|
|
2193
2209
|
generateBlockContinuation(writer, node.id, context);
|
|
@@ -2231,27 +2247,27 @@ function collectRequiredPackages(ir, context) {
|
|
|
2231
2247
|
function buildSourceMap(code, ir, filePath) {
|
|
2232
2248
|
const lines = code.split("\n");
|
|
2233
2249
|
const mappings = {};
|
|
2234
|
-
const
|
|
2250
|
+
const validNodeIds = new Set(ir.nodes.map((n) => n.id));
|
|
2235
2251
|
let currentNodeId = null;
|
|
2236
2252
|
let currentStartLine = 0;
|
|
2237
2253
|
for (let i = 0; i < lines.length; i++) {
|
|
2238
2254
|
const lineNum = i + 1;
|
|
2239
|
-
const
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
}
|
|
2252
|
-
currentNodeId = null;
|
|
2253
|
-
}
|
|
2255
|
+
const line = lines[i];
|
|
2256
|
+
const bracketOpen = line.indexOf("[");
|
|
2257
|
+
const bracketClose = line.indexOf("] ---", bracketOpen);
|
|
2258
|
+
if (bracketOpen === -1 || bracketClose === -1) continue;
|
|
2259
|
+
const trimmed = line.trimStart();
|
|
2260
|
+
if (!trimmed.startsWith("//")) continue;
|
|
2261
|
+
const candidateId = line.slice(bracketOpen + 1, bracketClose);
|
|
2262
|
+
if (!validNodeIds.has(candidateId)) continue;
|
|
2263
|
+
if (currentNodeId) {
|
|
2264
|
+
mappings[currentNodeId] = {
|
|
2265
|
+
startLine: currentStartLine,
|
|
2266
|
+
endLine: lineNum - 1
|
|
2267
|
+
};
|
|
2254
2268
|
}
|
|
2269
|
+
currentNodeId = candidateId;
|
|
2270
|
+
currentStartLine = lineNum;
|
|
2255
2271
|
}
|
|
2256
2272
|
if (currentNodeId) {
|
|
2257
2273
|
mappings[currentNodeId] = {
|
|
@@ -2302,6 +2318,72 @@ var init_compiler = __esm({
|
|
|
2302
2318
|
}
|
|
2303
2319
|
});
|
|
2304
2320
|
|
|
2321
|
+
// src/lib/logger.ts
|
|
2322
|
+
import pc from "picocolors";
|
|
2323
|
+
var LEVEL_ORDER, Logger, logger;
|
|
2324
|
+
var init_logger = __esm({
|
|
2325
|
+
"src/lib/logger.ts"() {
|
|
2326
|
+
"use strict";
|
|
2327
|
+
LEVEL_ORDER = {
|
|
2328
|
+
debug: 0,
|
|
2329
|
+
info: 1,
|
|
2330
|
+
warn: 2,
|
|
2331
|
+
error: 3,
|
|
2332
|
+
silent: 4
|
|
2333
|
+
};
|
|
2334
|
+
Logger = class {
|
|
2335
|
+
/** Minimum log level (default: "info"). Set to "silent" to suppress all output. */
|
|
2336
|
+
level = "info";
|
|
2337
|
+
/** Prefix for all log lines (default: "[flow2code]") */
|
|
2338
|
+
prefix = "[flow2code]";
|
|
2339
|
+
shouldLog(level) {
|
|
2340
|
+
return LEVEL_ORDER[level] >= LEVEL_ORDER[this.level];
|
|
2341
|
+
}
|
|
2342
|
+
debug(...args) {
|
|
2343
|
+
if (!this.shouldLog("debug")) return;
|
|
2344
|
+
console.log(pc.gray(`${this.prefix} ${pc.dim("DEBUG")}`), ...args);
|
|
2345
|
+
}
|
|
2346
|
+
info(...args) {
|
|
2347
|
+
if (!this.shouldLog("info")) return;
|
|
2348
|
+
console.log(pc.blue(`${this.prefix}`), ...args);
|
|
2349
|
+
}
|
|
2350
|
+
success(...args) {
|
|
2351
|
+
if (!this.shouldLog("info")) return;
|
|
2352
|
+
console.log(pc.green(`${this.prefix} \u2705`), ...args);
|
|
2353
|
+
}
|
|
2354
|
+
warn(...args) {
|
|
2355
|
+
if (!this.shouldLog("warn")) return;
|
|
2356
|
+
console.warn(pc.yellow(`${this.prefix} \u26A0\uFE0F`), ...args);
|
|
2357
|
+
}
|
|
2358
|
+
error(...args) {
|
|
2359
|
+
if (!this.shouldLog("error")) return;
|
|
2360
|
+
console.error(pc.red(`${this.prefix} \u274C`), ...args);
|
|
2361
|
+
}
|
|
2362
|
+
/** Print a blank line (respects silent mode) */
|
|
2363
|
+
blank() {
|
|
2364
|
+
if (!this.shouldLog("info")) return;
|
|
2365
|
+
console.log();
|
|
2366
|
+
}
|
|
2367
|
+
/** Print raw text without prefix (respects silent mode) */
|
|
2368
|
+
raw(...args) {
|
|
2369
|
+
if (!this.shouldLog("info")) return;
|
|
2370
|
+
console.log(...args);
|
|
2371
|
+
}
|
|
2372
|
+
/** Formatted key-value line for startup banners */
|
|
2373
|
+
kv(key, value) {
|
|
2374
|
+
if (!this.shouldLog("info")) return;
|
|
2375
|
+
console.log(` ${pc.dim("\u251C\u2500")} ${pc.bold(key)} ${value}`);
|
|
2376
|
+
}
|
|
2377
|
+
/** Last key-value line (uses └─) */
|
|
2378
|
+
kvLast(key, value) {
|
|
2379
|
+
if (!this.shouldLog("info")) return;
|
|
2380
|
+
console.log(` ${pc.dim("\u2514\u2500")} ${pc.bold(key)} ${value}`);
|
|
2381
|
+
}
|
|
2382
|
+
};
|
|
2383
|
+
logger = new Logger();
|
|
2384
|
+
}
|
|
2385
|
+
});
|
|
2386
|
+
|
|
2305
2387
|
// src/lib/compiler/decompiler.ts
|
|
2306
2388
|
var decompiler_exports = {};
|
|
2307
2389
|
__export(decompiler_exports, {
|
|
@@ -2840,9 +2922,13 @@ function buildEdges(ctx) {
|
|
|
2840
2922
|
}
|
|
2841
2923
|
}
|
|
2842
2924
|
}
|
|
2925
|
+
const resolveTargetPort = (targetNodeId, fallback) => {
|
|
2926
|
+
const tgt = ctx.nodes.get(targetNodeId);
|
|
2927
|
+
return tgt?.inputs?.[0]?.id ?? fallback;
|
|
2928
|
+
};
|
|
2843
2929
|
for (const [parentId, children] of ctx.controlFlowChildren) {
|
|
2844
2930
|
for (const child of children) {
|
|
2845
|
-
addEdge(parentId, child.portId, child.nodeId, "input");
|
|
2931
|
+
addEdge(parentId, child.portId, child.nodeId, resolveTargetPort(child.nodeId, "input"));
|
|
2846
2932
|
connectedTargets.add(child.nodeId);
|
|
2847
2933
|
}
|
|
2848
2934
|
}
|
|
@@ -2851,7 +2937,7 @@ function buildEdges(ctx) {
|
|
|
2851
2937
|
const predNode = ctx.nodes.get(predId);
|
|
2852
2938
|
if (!predNode) continue;
|
|
2853
2939
|
const sourcePort = predNode.outputs[0]?.id ?? "output";
|
|
2854
|
-
addEdge(predId, sourcePort, nodeId, "input");
|
|
2940
|
+
addEdge(predId, sourcePort, nodeId, resolveTargetPort(nodeId, "input"));
|
|
2855
2941
|
}
|
|
2856
2942
|
}
|
|
2857
2943
|
function computeAuditHints(ctx) {
|
|
@@ -2983,7 +3069,9 @@ function trackVariableUses(nodeId, expression, ctx) {
|
|
|
2983
3069
|
if (seen.has(ident)) continue;
|
|
2984
3070
|
seen.add(ident);
|
|
2985
3071
|
if (ctx.varDefs.has(ident)) {
|
|
2986
|
-
|
|
3072
|
+
const targetNode = ctx.nodes.get(nodeId);
|
|
3073
|
+
const portId = targetNode?.inputs?.[0]?.id ?? "input";
|
|
3074
|
+
uses.push({ nodeId, portId, varName: ident });
|
|
2987
3075
|
}
|
|
2988
3076
|
}
|
|
2989
3077
|
if (uses.length > 0) {
|
|
@@ -3859,7 +3947,7 @@ __export(server_exports, {
|
|
|
3859
3947
|
});
|
|
3860
3948
|
import { createServer } from "http";
|
|
3861
3949
|
import { readFile, stat } from "fs/promises";
|
|
3862
|
-
import { join as join3, extname as extname2, dirname as dirname3 } from "path";
|
|
3950
|
+
import { join as join3, extname as extname2, dirname as dirname3, resolve as resolve2 } from "path";
|
|
3863
3951
|
import { fileURLToPath } from "url";
|
|
3864
3952
|
import { existsSync as existsSync3 } from "fs";
|
|
3865
3953
|
function resolveStaticDir() {
|
|
@@ -3920,7 +4008,7 @@ function sendJson(res, status, body) {
|
|
|
3920
4008
|
res.end(JSON.stringify(body));
|
|
3921
4009
|
}
|
|
3922
4010
|
async function readBody(req) {
|
|
3923
|
-
return new Promise((
|
|
4011
|
+
return new Promise((resolve4, reject) => {
|
|
3924
4012
|
const chunks = [];
|
|
3925
4013
|
let totalSize = 0;
|
|
3926
4014
|
req.on("data", (chunk) => {
|
|
@@ -3932,7 +4020,7 @@ async function readBody(req) {
|
|
|
3932
4020
|
}
|
|
3933
4021
|
chunks.push(chunk);
|
|
3934
4022
|
});
|
|
3935
|
-
req.on("end", () =>
|
|
4023
|
+
req.on("end", () => resolve4(Buffer.concat(chunks).toString("utf-8")));
|
|
3936
4024
|
req.on("error", reject);
|
|
3937
4025
|
});
|
|
3938
4026
|
}
|
|
@@ -3941,7 +4029,13 @@ async function parseJsonBody(req) {
|
|
|
3941
4029
|
return JSON.parse(raw);
|
|
3942
4030
|
}
|
|
3943
4031
|
async function serveStatic(staticDir, pathname, res) {
|
|
3944
|
-
|
|
4032
|
+
const decodedPath = decodeURIComponent(pathname);
|
|
4033
|
+
let filePath = join3(staticDir, decodedPath === "/" ? "index.html" : decodedPath);
|
|
4034
|
+
const resolvedPath = resolve2(filePath);
|
|
4035
|
+
const resolvedStaticDir = resolve2(staticDir);
|
|
4036
|
+
if (!resolvedPath.startsWith(resolvedStaticDir + (resolvedStaticDir.endsWith("/") || resolvedStaticDir.endsWith("\\") ? "" : process.platform === "win32" ? "\\" : "/"))) {
|
|
4037
|
+
return false;
|
|
4038
|
+
}
|
|
3945
4039
|
if (!extname2(filePath)) {
|
|
3946
4040
|
filePath += ".html";
|
|
3947
4041
|
}
|
|
@@ -4029,7 +4123,7 @@ function startServer(options = {}) {
|
|
|
4029
4123
|
const projectRoot = options.projectRoot ?? process.cwd();
|
|
4030
4124
|
const server = createServer((req, res) => {
|
|
4031
4125
|
handleRequest(req, res, staticDir, projectRoot).catch((err) => {
|
|
4032
|
-
|
|
4126
|
+
logger.error("Internal error:", err);
|
|
4033
4127
|
res.writeHead(500, { "Content-Type": "text/plain" });
|
|
4034
4128
|
res.end("Internal Server Error");
|
|
4035
4129
|
});
|
|
@@ -4040,20 +4134,20 @@ function startServer(options = {}) {
|
|
|
4040
4134
|
if (options.onReady) {
|
|
4041
4135
|
options.onReady(url);
|
|
4042
4136
|
} else {
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4137
|
+
logger.blank();
|
|
4138
|
+
logger.info("Flow2Code Dev Server");
|
|
4139
|
+
logger.kv("Local:", url);
|
|
4140
|
+
logger.kv("API:", `${url}/api/compile`);
|
|
4141
|
+
logger.kv("Static:", staticDir);
|
|
4142
|
+
logger.kvLast("Project:", projectRoot);
|
|
4049
4143
|
if (!hasUI) {
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4144
|
+
logger.blank();
|
|
4145
|
+
logger.warn("UI files not found (out/index.html missing).");
|
|
4146
|
+
logger.raw(" The API endpoints still work, but the visual editor won't load.");
|
|
4147
|
+
logger.raw(' To fix: run "pnpm build:ui" in the flow2code source directory,');
|
|
4148
|
+
logger.raw(" or reinstall from npm: npm i @timo9378/flow2code@latest");
|
|
4055
4149
|
}
|
|
4056
|
-
|
|
4150
|
+
logger.blank();
|
|
4057
4151
|
}
|
|
4058
4152
|
});
|
|
4059
4153
|
return server;
|
|
@@ -4063,6 +4157,7 @@ var init_server = __esm({
|
|
|
4063
4157
|
"src/server/index.ts"() {
|
|
4064
4158
|
"use strict";
|
|
4065
4159
|
init_handlers();
|
|
4160
|
+
init_logger();
|
|
4066
4161
|
init_handlers();
|
|
4067
4162
|
__filename = fileURLToPath(import.meta.url);
|
|
4068
4163
|
__dirname = dirname3(__filename);
|
|
@@ -4091,10 +4186,12 @@ var init_server = __esm({
|
|
|
4091
4186
|
|
|
4092
4187
|
// src/cli/index.ts
|
|
4093
4188
|
init_compiler();
|
|
4189
|
+
init_logger();
|
|
4094
4190
|
init_validator();
|
|
4095
4191
|
import { Command } from "commander";
|
|
4096
4192
|
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync as existsSync4, readdirSync as readdirSync2, rmSync as rmSync2 } from "fs";
|
|
4097
|
-
import {
|
|
4193
|
+
import { readFile as readFile2, writeFile, mkdir } from "fs/promises";
|
|
4194
|
+
import { join as join4, dirname as dirname4, resolve as resolve3, basename as basename2 } from "path";
|
|
4098
4195
|
import { watch } from "chokidar";
|
|
4099
4196
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4100
4197
|
|
|
@@ -4720,7 +4817,7 @@ var pkgJson = JSON.parse(readFileSync3(join4(__cliDirname, "..", "package.json")
|
|
|
4720
4817
|
var program = new Command();
|
|
4721
4818
|
program.name("flow2code").description("Visual AST Compiler: Compile .flow.json into native TypeScript").version(pkgJson.version);
|
|
4722
4819
|
program.command("compile <file>").description("Compile .flow.json or YAML directory to TypeScript (auto-detects format)").option("-o, --output <path>", "Specify output path (overrides auto-detection)").option("--platform <name>", "Target platform: nextjs | express | cloudflare", "nextjs").option("--dry-run", "Display generated code without writing to file").option("--source-map", "Generate Source Map mapping file (.flow.map.json)").action((file, options) => {
|
|
4723
|
-
const filePath =
|
|
4820
|
+
const filePath = resolve3(file);
|
|
4724
4821
|
if (!existsSync4(filePath)) {
|
|
4725
4822
|
console.error(`\u274C File/directory not found: ${filePath}`);
|
|
4726
4823
|
process.exit(1);
|
|
@@ -4758,7 +4855,7 @@ program.command("compile <file>").description("Compile .flow.json or YAML direct
|
|
|
4758
4855
|
process.exit(1);
|
|
4759
4856
|
}
|
|
4760
4857
|
const outputPath = options.output ?? result.filePath;
|
|
4761
|
-
const fullOutputPath =
|
|
4858
|
+
const fullOutputPath = resolve3(outputPath);
|
|
4762
4859
|
const outputDir = dirname4(fullOutputPath);
|
|
4763
4860
|
if (!existsSync4(outputDir)) {
|
|
4764
4861
|
mkdirSync3(outputDir, { recursive: true });
|
|
@@ -4771,7 +4868,7 @@ program.command("compile <file>").description("Compile .flow.json or YAML direct
|
|
|
4771
4868
|
console.log(`\u{1F5FA}\uFE0F Source Map: ${mapPath}`);
|
|
4772
4869
|
}
|
|
4773
4870
|
if (result.dependencies && result.dependencies.all.length > 0) {
|
|
4774
|
-
const projectPkgPath =
|
|
4871
|
+
const projectPkgPath = resolve3("package.json");
|
|
4775
4872
|
if (existsSync4(projectPkgPath)) {
|
|
4776
4873
|
try {
|
|
4777
4874
|
const pkgJson2 = JSON.parse(readFileSync3(projectPkgPath, "utf-8"));
|
|
@@ -4794,7 +4891,7 @@ program.command("compile <file>").description("Compile .flow.json or YAML direct
|
|
|
4794
4891
|
});
|
|
4795
4892
|
program.command("audit <file>").description("Decompile any TypeScript file into a visual FlowIR for code auditing").option("-o, --output <path>", "Write IR JSON to file instead of stdout").option("--format <fmt>", "Output format: json | mermaid | summary", "summary").option("--function <name>", "Target function name to decompile").option("--no-audit-hints", "Disable audit hints").action(async (file, options) => {
|
|
4796
4893
|
const { decompile: decompile2 } = await Promise.resolve().then(() => (init_decompiler(), decompiler_exports));
|
|
4797
|
-
const filePath =
|
|
4894
|
+
const filePath = resolve3(file);
|
|
4798
4895
|
if (!existsSync4(filePath)) {
|
|
4799
4896
|
console.error(`\u274C File not found: ${filePath}`);
|
|
4800
4897
|
process.exit(1);
|
|
@@ -4814,7 +4911,7 @@ program.command("audit <file>").description("Decompile any TypeScript file into
|
|
|
4814
4911
|
if (fmt === "json") {
|
|
4815
4912
|
const output = JSON.stringify(result.ir, null, 2);
|
|
4816
4913
|
if (options.output) {
|
|
4817
|
-
writeFileSync3(
|
|
4914
|
+
writeFileSync3(resolve3(options.output), output, "utf-8");
|
|
4818
4915
|
console.log(`\u2705 IR written to: ${options.output}`);
|
|
4819
4916
|
} else {
|
|
4820
4917
|
console.log(output);
|
|
@@ -4822,7 +4919,7 @@ program.command("audit <file>").description("Decompile any TypeScript file into
|
|
|
4822
4919
|
} else if (fmt === "mermaid") {
|
|
4823
4920
|
const mermaidOutput = irToMermaid(result.ir);
|
|
4824
4921
|
if (options.output) {
|
|
4825
|
-
writeFileSync3(
|
|
4922
|
+
writeFileSync3(resolve3(options.output), mermaidOutput, "utf-8");
|
|
4826
4923
|
console.log(`\u2705 Mermaid diagram written to: ${options.output}`);
|
|
4827
4924
|
} else {
|
|
4828
4925
|
console.log(mermaidOutput);
|
|
@@ -4849,7 +4946,7 @@ program.command("audit <file>").description("Decompile any TypeScript file into
|
|
|
4849
4946
|
console.log("");
|
|
4850
4947
|
if (options.output) {
|
|
4851
4948
|
const irJson = JSON.stringify(result.ir, null, 2);
|
|
4852
|
-
writeFileSync3(
|
|
4949
|
+
writeFileSync3(resolve3(options.output), irJson, "utf-8");
|
|
4853
4950
|
console.log(`\u2705 IR written to: ${options.output}`);
|
|
4854
4951
|
}
|
|
4855
4952
|
}
|
|
@@ -4883,8 +4980,8 @@ function irToMermaid(ir) {
|
|
|
4883
4980
|
return lines.join("\n");
|
|
4884
4981
|
}
|
|
4885
4982
|
program.command("watch [dir]").description("Watch directory, auto-compile .flow.json and YAML directory changes").option("-p, --project <path>", "Next.js project root directory", ".").action((dir = ".", options) => {
|
|
4886
|
-
const watchDir =
|
|
4887
|
-
const projectRoot =
|
|
4983
|
+
const watchDir = resolve3(dir);
|
|
4984
|
+
const projectRoot = resolve3(options.project ?? ".");
|
|
4888
4985
|
console.log(`\u{1F440} Watching: ${watchDir}/**/*.flow.json + **/*.yaml`);
|
|
4889
4986
|
console.log(`\u{1F4C1} Output to: ${projectRoot}`);
|
|
4890
4987
|
console.log("Press Ctrl+C to stop\n");
|
|
@@ -4895,17 +4992,28 @@ program.command("watch [dir]").description("Watch directory, auto-compile .flow.
|
|
|
4895
4992
|
ignoreInitial: false
|
|
4896
4993
|
}
|
|
4897
4994
|
);
|
|
4995
|
+
const pendingTimers = /* @__PURE__ */ new Map();
|
|
4996
|
+
const DEBOUNCE_MS = 150;
|
|
4898
4997
|
const handleChange = (filePath) => {
|
|
4899
|
-
|
|
4900
|
-
|
|
4901
|
-
} else if (filePath.endsWith(".yaml")) {
|
|
4998
|
+
let compileKey = filePath;
|
|
4999
|
+
if (filePath.endsWith(".yaml")) {
|
|
4902
5000
|
let dir2 = dirname4(filePath);
|
|
4903
5001
|
if (basename2(dir2) === "nodes") dir2 = dirname4(dir2);
|
|
4904
|
-
|
|
4905
|
-
|
|
4906
|
-
|
|
5002
|
+
compileKey = dir2;
|
|
5003
|
+
}
|
|
5004
|
+
const existing = pendingTimers.get(compileKey);
|
|
5005
|
+
if (existing) clearTimeout(existing);
|
|
5006
|
+
pendingTimers.set(compileKey, setTimeout(() => {
|
|
5007
|
+
pendingTimers.delete(compileKey);
|
|
5008
|
+
if (filePath.endsWith(".flow.json")) {
|
|
5009
|
+
compileFileAsync(filePath, projectRoot);
|
|
5010
|
+
} else if (filePath.endsWith(".yaml")) {
|
|
5011
|
+
const metaPath = join4(compileKey, "meta.yaml");
|
|
5012
|
+
if (existsSync4(metaPath)) {
|
|
5013
|
+
compileFlowDirAsync(compileKey, projectRoot);
|
|
5014
|
+
}
|
|
4907
5015
|
}
|
|
4908
|
-
}
|
|
5016
|
+
}, DEBOUNCE_MS));
|
|
4909
5017
|
};
|
|
4910
5018
|
watcher.on("change", handleChange);
|
|
4911
5019
|
watcher.on("add", handleChange);
|
|
@@ -4914,7 +5022,7 @@ program.command("watch [dir]").description("Watch directory, auto-compile .flow.
|
|
|
4914
5022
|
});
|
|
4915
5023
|
});
|
|
4916
5024
|
program.command("init").description("Initialize Flow2Code in current project (Zero Pollution mode)").action(() => {
|
|
4917
|
-
const flow2codeDir =
|
|
5025
|
+
const flow2codeDir = resolve3(".flow2code");
|
|
4918
5026
|
const flowsDir = join4(flow2codeDir, "flows");
|
|
4919
5027
|
if (!existsSync4(flow2codeDir)) {
|
|
4920
5028
|
mkdirSync3(flow2codeDir, { recursive: true });
|
|
@@ -4984,7 +5092,7 @@ program.command("init").description("Initialize Flow2Code in current project (Ze
|
|
|
4984
5092
|
writeFileSync3(examplePath, JSON.stringify(exampleFlow, null, 2), "utf-8");
|
|
4985
5093
|
console.log(`\u{1F4C4} Created example: ${examplePath}`);
|
|
4986
5094
|
}
|
|
4987
|
-
const gitignorePath =
|
|
5095
|
+
const gitignorePath = resolve3(".gitignore");
|
|
4988
5096
|
if (existsSync4(gitignorePath)) {
|
|
4989
5097
|
const content = readFileSync3(gitignorePath, "utf-8");
|
|
4990
5098
|
if (!content.includes(".flow2code/")) {
|
|
@@ -5003,7 +5111,7 @@ program.command("init").description("Initialize Flow2Code in current project (Ze
|
|
|
5003
5111
|
console.log(` npx ${pkgJson.name} watch .flow2code/flows/`);
|
|
5004
5112
|
});
|
|
5005
5113
|
program.command("split <file>").description("Split .flow.json into a Git-friendly YAML directory structure").option("-o, --output <dir>", "Specify output directory (default: same name as file)").action((file, options) => {
|
|
5006
|
-
const filePath =
|
|
5114
|
+
const filePath = resolve3(file);
|
|
5007
5115
|
if (!existsSync4(filePath)) {
|
|
5008
5116
|
console.error(`\u274C File not found: ${filePath}`);
|
|
5009
5117
|
process.exit(1);
|
|
@@ -5019,7 +5127,7 @@ program.command("split <file>").description("Split .flow.json into a Git-friendl
|
|
|
5019
5127
|
const outputDir = options.output ?? filePath.replace(/\.flow\.json$|\.json$/, "");
|
|
5020
5128
|
const written = splitToFileSystem(
|
|
5021
5129
|
ir,
|
|
5022
|
-
|
|
5130
|
+
resolve3(outputDir),
|
|
5023
5131
|
{ mkdirSync: mkdirSync3, writeFileSync: (p, c) => writeFileSync3(p, c, "utf-8") },
|
|
5024
5132
|
{ join: join4 }
|
|
5025
5133
|
);
|
|
@@ -5027,7 +5135,7 @@ program.command("split <file>").description("Split .flow.json into a Git-friendl
|
|
|
5027
5135
|
written.forEach((f) => console.log(` \u{1F4C4} ${f}`));
|
|
5028
5136
|
});
|
|
5029
5137
|
program.command("merge <dir>").description("Merge YAML directory structure into a .flow.json").option("-o, --output <file>", "Specify output file path").action((dir, options) => {
|
|
5030
|
-
const dirPath =
|
|
5138
|
+
const dirPath = resolve3(dir);
|
|
5031
5139
|
if (!existsSync4(dirPath)) {
|
|
5032
5140
|
console.error(`\u274C Directory not found: ${dirPath}`);
|
|
5033
5141
|
process.exit(1);
|
|
@@ -5039,7 +5147,7 @@ program.command("merge <dir>").description("Merge YAML directory structure into
|
|
|
5039
5147
|
{ join: join4 }
|
|
5040
5148
|
);
|
|
5041
5149
|
const outputFile = options.output ?? `${dirPath}.flow.json`;
|
|
5042
|
-
writeFileSync3(
|
|
5150
|
+
writeFileSync3(resolve3(outputFile), JSON.stringify(ir, null, 2), "utf-8");
|
|
5043
5151
|
console.log(`\u2705 Merged to: ${outputFile}`);
|
|
5044
5152
|
console.log(` ${ir.nodes.length} nodes, ${ir.edges.length} edges`);
|
|
5045
5153
|
} catch (err) {
|
|
@@ -5048,7 +5156,7 @@ program.command("merge <dir>").description("Merge YAML directory structure into
|
|
|
5048
5156
|
}
|
|
5049
5157
|
});
|
|
5050
5158
|
program.command("migrate <file>").description("Migrate .flow.json to Git-friendly YAML directory (preserves original file)").option("--delete-json", "Delete original .flow.json after successful migration").action((file, options) => {
|
|
5051
|
-
const filePath =
|
|
5159
|
+
const filePath = resolve3(file);
|
|
5052
5160
|
if (!existsSync4(filePath)) {
|
|
5053
5161
|
console.error(`\u274C File not found: ${filePath}`);
|
|
5054
5162
|
process.exit(1);
|
|
@@ -5069,7 +5177,7 @@ program.command("migrate <file>").description("Migrate .flow.json to Git-friendl
|
|
|
5069
5177
|
}
|
|
5070
5178
|
});
|
|
5071
5179
|
program.command("trace <file> <line>").description("Trace a generated code line number back to its canvas node (Source Map)").action((file, lineStr) => {
|
|
5072
|
-
const filePath =
|
|
5180
|
+
const filePath = resolve3(file);
|
|
5073
5181
|
const lineNum = parseInt(lineStr, 10);
|
|
5074
5182
|
if (isNaN(lineNum) || lineNum < 1) {
|
|
5075
5183
|
console.error("\u274C Line number must be a positive integer");
|
|
@@ -5099,8 +5207,8 @@ program.command("trace <file> <line>").description("Trace a generated code line
|
|
|
5099
5207
|
}
|
|
5100
5208
|
});
|
|
5101
5209
|
program.command("diff <before> <after>").description("Compare semantic differences between two .flow.json files").action((beforeFile, afterFile) => {
|
|
5102
|
-
const beforePath =
|
|
5103
|
-
const afterPath =
|
|
5210
|
+
const beforePath = resolve3(beforeFile);
|
|
5211
|
+
const afterPath = resolve3(afterFile);
|
|
5104
5212
|
if (!existsSync4(beforePath)) {
|
|
5105
5213
|
console.error(`\u274C File not found: ${beforePath}`);
|
|
5106
5214
|
process.exit(1);
|
|
@@ -5122,8 +5230,8 @@ program.command("diff <before> <after>").description("Compare semantic differenc
|
|
|
5122
5230
|
console.log(formatDiff(summary));
|
|
5123
5231
|
});
|
|
5124
5232
|
program.command("env-check <file>").description("Validate that environment variables referenced in .flow.json are declared").option("-e, --env <envFile>", "Specify .env file path", ".env").action((file, options) => {
|
|
5125
|
-
const filePath =
|
|
5126
|
-
const envPath =
|
|
5233
|
+
const filePath = resolve3(file);
|
|
5234
|
+
const envPath = resolve3(options.env);
|
|
5127
5235
|
if (!existsSync4(filePath)) {
|
|
5128
5236
|
console.error(`\u274C File not found: ${filePath}`);
|
|
5129
5237
|
process.exit(1);
|
|
@@ -5140,7 +5248,7 @@ program.command("env-check <file>").description("Validate that environment varia
|
|
|
5140
5248
|
const envContent = readFileSync3(envPath, "utf-8");
|
|
5141
5249
|
declaredVars = parseEnvFile(envContent);
|
|
5142
5250
|
} else {
|
|
5143
|
-
const examplePath =
|
|
5251
|
+
const examplePath = resolve3(".env.example");
|
|
5144
5252
|
if (existsSync4(examplePath)) {
|
|
5145
5253
|
const envContent = readFileSync3(examplePath, "utf-8");
|
|
5146
5254
|
declaredVars = parseEnvFile(envContent);
|
|
@@ -5151,7 +5259,11 @@ program.command("env-check <file>").description("Validate that environment varia
|
|
|
5151
5259
|
`);
|
|
5152
5260
|
}
|
|
5153
5261
|
}
|
|
5154
|
-
const
|
|
5262
|
+
const systemEnvKeys = Object.keys(process.env).filter(
|
|
5263
|
+
(k) => /^[A-Za-z_][A-Za-z0-9_]*$/.test(k)
|
|
5264
|
+
);
|
|
5265
|
+
const allDeclaredVars = [.../* @__PURE__ */ new Set([...declaredVars, ...systemEnvKeys])];
|
|
5266
|
+
const result = validateEnvVars(ir, allDeclaredVars);
|
|
5155
5267
|
console.log(formatEnvValidationReport(result));
|
|
5156
5268
|
if (!result.valid) {
|
|
5157
5269
|
process.exit(1);
|
|
@@ -5176,12 +5288,29 @@ async function startDevServer(options) {
|
|
|
5176
5288
|
}
|
|
5177
5289
|
});
|
|
5178
5290
|
}
|
|
5179
|
-
program.command("dev").description("Start Flow2Code visual editor (standalone dev server)").option("-p, --port <port>", "Server port", "3100").option("--no-open", "Do not auto-open browser").action(
|
|
5180
|
-
|
|
5181
|
-
|
|
5291
|
+
program.command("dev").description("Start Flow2Code visual editor (standalone dev server)").option("-p, --port <port>", "Server port", "3100").option("--no-open", "Do not auto-open browser").option("--silent", "Suppress non-error output (for CI/CD)").action((opts) => {
|
|
5292
|
+
if (opts.silent) logger.level = "silent";
|
|
5293
|
+
startDevServer(opts);
|
|
5294
|
+
});
|
|
5295
|
+
program.command("ui").description("Start Flow2Code visual editor (alias for dev)").option("-p, --port <port>", "Server port", "3100").option("--no-open", "Do not auto-open browser").option("--silent", "Suppress non-error output (for CI/CD)").action((opts) => {
|
|
5296
|
+
if (opts.silent) logger.level = "silent";
|
|
5297
|
+
startDevServer(opts);
|
|
5298
|
+
});
|
|
5299
|
+
function generateEnvExample() {
|
|
5300
|
+
const envExamplePath = resolve3(".env.example");
|
|
5301
|
+
if (!existsSync4(envExamplePath)) {
|
|
5302
|
+
writeFileSync3(
|
|
5303
|
+
envExamplePath,
|
|
5304
|
+
"# Flow2Code environment variables\n# Define API keys and sensitive information here\n",
|
|
5305
|
+
"utf-8"
|
|
5306
|
+
);
|
|
5307
|
+
console.log("\u{1F4DD} Generated .env.example");
|
|
5308
|
+
}
|
|
5309
|
+
}
|
|
5310
|
+
async function compileFileAsync(filePath, projectRoot) {
|
|
5182
5311
|
const startTime = Date.now();
|
|
5183
5312
|
try {
|
|
5184
|
-
const raw =
|
|
5313
|
+
const raw = await readFile2(filePath, "utf-8");
|
|
5185
5314
|
const ir = JSON.parse(raw);
|
|
5186
5315
|
const validation = validateFlowIR(ir);
|
|
5187
5316
|
if (!validation.valid) {
|
|
@@ -5202,9 +5331,9 @@ function compileFile(filePath, projectRoot) {
|
|
|
5202
5331
|
const outputPath = join4(projectRoot, result.filePath);
|
|
5203
5332
|
const outputDir = dirname4(outputPath);
|
|
5204
5333
|
if (!existsSync4(outputDir)) {
|
|
5205
|
-
|
|
5334
|
+
await mkdir(outputDir, { recursive: true });
|
|
5206
5335
|
}
|
|
5207
|
-
|
|
5336
|
+
await writeFile(outputPath, result.code, "utf-8");
|
|
5208
5337
|
const elapsed = Date.now() - startTime;
|
|
5209
5338
|
console.log(`\u2705 [${elapsed}ms] ${filePath} \u2192 ${outputPath}`);
|
|
5210
5339
|
} catch (err) {
|
|
@@ -5213,7 +5342,7 @@ function compileFile(filePath, projectRoot) {
|
|
|
5213
5342
|
);
|
|
5214
5343
|
}
|
|
5215
5344
|
}
|
|
5216
|
-
function
|
|
5345
|
+
async function compileFlowDirAsync(dirPath, projectRoot) {
|
|
5217
5346
|
const startTime = Date.now();
|
|
5218
5347
|
try {
|
|
5219
5348
|
const project = loadFlowProject(dirPath);
|
|
@@ -5237,9 +5366,9 @@ function compileFlowDir(dirPath, projectRoot) {
|
|
|
5237
5366
|
const outputPath = join4(projectRoot, result.filePath);
|
|
5238
5367
|
const outputDir = dirname4(outputPath);
|
|
5239
5368
|
if (!existsSync4(outputDir)) {
|
|
5240
|
-
|
|
5369
|
+
await mkdir(outputDir, { recursive: true });
|
|
5241
5370
|
}
|
|
5242
|
-
|
|
5371
|
+
await writeFile(outputPath, result.code, "utf-8");
|
|
5243
5372
|
const elapsed = Date.now() - startTime;
|
|
5244
5373
|
console.log(`\u2705 [${elapsed}ms] ${dirPath}/ \u2192 ${outputPath}`);
|
|
5245
5374
|
} catch (err) {
|
|
@@ -5248,17 +5377,6 @@ function compileFlowDir(dirPath, projectRoot) {
|
|
|
5248
5377
|
);
|
|
5249
5378
|
}
|
|
5250
5379
|
}
|
|
5251
|
-
function generateEnvExample() {
|
|
5252
|
-
const envExamplePath = resolve2(".env.example");
|
|
5253
|
-
if (!existsSync4(envExamplePath)) {
|
|
5254
|
-
writeFileSync3(
|
|
5255
|
-
envExamplePath,
|
|
5256
|
-
"# Flow2Code environment variables\n# Define API keys and sensitive information here\n",
|
|
5257
|
-
"utf-8"
|
|
5258
|
-
);
|
|
5259
|
-
console.log("\u{1F4DD} Generated .env.example");
|
|
5260
|
-
}
|
|
5261
|
-
}
|
|
5262
5380
|
if (process.argv.length <= 2) {
|
|
5263
5381
|
program.help();
|
|
5264
5382
|
}
|