@timo9378/flow2code 0.1.4 → 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 +201 -89
- package/dist/compiler.cjs +37 -21
- package/dist/compiler.js +37 -21
- package/dist/server.js +120 -38
- package/out/404.html +1 -1
- package/out/__next.__PAGE__.txt +1 -1
- package/out/__next._full.txt +1 -1
- package/out/__next._head.txt +1 -1
- package/out/__next._index.txt +1 -1
- package/out/__next._tree.txt +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 +1 -1
- package/package.json +3 -3
- package/scripts/publish-all.sh +56 -0
- /package/out/_next/static/{OIo5LFjdea8u6u4evHHQo → qqgjMH_XFks-bLx9gn_yB}/_buildManifest.js +0 -0
- /package/out/_next/static/{OIo5LFjdea8u6u4evHHQo → qqgjMH_XFks-bLx9gn_yB}/_clientMiddlewareManifest.json +0 -0
- /package/out/_next/static/{OIo5LFjdea8u6u4evHHQo → 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, {
|
|
@@ -3865,7 +3947,7 @@ __export(server_exports, {
|
|
|
3865
3947
|
});
|
|
3866
3948
|
import { createServer } from "http";
|
|
3867
3949
|
import { readFile, stat } from "fs/promises";
|
|
3868
|
-
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";
|
|
3869
3951
|
import { fileURLToPath } from "url";
|
|
3870
3952
|
import { existsSync as existsSync3 } from "fs";
|
|
3871
3953
|
function resolveStaticDir() {
|
|
@@ -3926,7 +4008,7 @@ function sendJson(res, status, body) {
|
|
|
3926
4008
|
res.end(JSON.stringify(body));
|
|
3927
4009
|
}
|
|
3928
4010
|
async function readBody(req) {
|
|
3929
|
-
return new Promise((
|
|
4011
|
+
return new Promise((resolve4, reject) => {
|
|
3930
4012
|
const chunks = [];
|
|
3931
4013
|
let totalSize = 0;
|
|
3932
4014
|
req.on("data", (chunk) => {
|
|
@@ -3938,7 +4020,7 @@ async function readBody(req) {
|
|
|
3938
4020
|
}
|
|
3939
4021
|
chunks.push(chunk);
|
|
3940
4022
|
});
|
|
3941
|
-
req.on("end", () =>
|
|
4023
|
+
req.on("end", () => resolve4(Buffer.concat(chunks).toString("utf-8")));
|
|
3942
4024
|
req.on("error", reject);
|
|
3943
4025
|
});
|
|
3944
4026
|
}
|
|
@@ -3947,7 +4029,13 @@ async function parseJsonBody(req) {
|
|
|
3947
4029
|
return JSON.parse(raw);
|
|
3948
4030
|
}
|
|
3949
4031
|
async function serveStatic(staticDir, pathname, res) {
|
|
3950
|
-
|
|
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
|
+
}
|
|
3951
4039
|
if (!extname2(filePath)) {
|
|
3952
4040
|
filePath += ".html";
|
|
3953
4041
|
}
|
|
@@ -4035,7 +4123,7 @@ function startServer(options = {}) {
|
|
|
4035
4123
|
const projectRoot = options.projectRoot ?? process.cwd();
|
|
4036
4124
|
const server = createServer((req, res) => {
|
|
4037
4125
|
handleRequest(req, res, staticDir, projectRoot).catch((err) => {
|
|
4038
|
-
|
|
4126
|
+
logger.error("Internal error:", err);
|
|
4039
4127
|
res.writeHead(500, { "Content-Type": "text/plain" });
|
|
4040
4128
|
res.end("Internal Server Error");
|
|
4041
4129
|
});
|
|
@@ -4046,20 +4134,20 @@ function startServer(options = {}) {
|
|
|
4046
4134
|
if (options.onReady) {
|
|
4047
4135
|
options.onReady(url);
|
|
4048
4136
|
} else {
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
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);
|
|
4055
4143
|
if (!hasUI) {
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
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");
|
|
4061
4149
|
}
|
|
4062
|
-
|
|
4150
|
+
logger.blank();
|
|
4063
4151
|
}
|
|
4064
4152
|
});
|
|
4065
4153
|
return server;
|
|
@@ -4069,6 +4157,7 @@ var init_server = __esm({
|
|
|
4069
4157
|
"src/server/index.ts"() {
|
|
4070
4158
|
"use strict";
|
|
4071
4159
|
init_handlers();
|
|
4160
|
+
init_logger();
|
|
4072
4161
|
init_handlers();
|
|
4073
4162
|
__filename = fileURLToPath(import.meta.url);
|
|
4074
4163
|
__dirname = dirname3(__filename);
|
|
@@ -4097,10 +4186,12 @@ var init_server = __esm({
|
|
|
4097
4186
|
|
|
4098
4187
|
// src/cli/index.ts
|
|
4099
4188
|
init_compiler();
|
|
4189
|
+
init_logger();
|
|
4100
4190
|
init_validator();
|
|
4101
4191
|
import { Command } from "commander";
|
|
4102
4192
|
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync as existsSync4, readdirSync as readdirSync2, rmSync as rmSync2 } from "fs";
|
|
4103
|
-
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";
|
|
4104
4195
|
import { watch } from "chokidar";
|
|
4105
4196
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4106
4197
|
|
|
@@ -4726,7 +4817,7 @@ var pkgJson = JSON.parse(readFileSync3(join4(__cliDirname, "..", "package.json")
|
|
|
4726
4817
|
var program = new Command();
|
|
4727
4818
|
program.name("flow2code").description("Visual AST Compiler: Compile .flow.json into native TypeScript").version(pkgJson.version);
|
|
4728
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) => {
|
|
4729
|
-
const filePath =
|
|
4820
|
+
const filePath = resolve3(file);
|
|
4730
4821
|
if (!existsSync4(filePath)) {
|
|
4731
4822
|
console.error(`\u274C File/directory not found: ${filePath}`);
|
|
4732
4823
|
process.exit(1);
|
|
@@ -4764,7 +4855,7 @@ program.command("compile <file>").description("Compile .flow.json or YAML direct
|
|
|
4764
4855
|
process.exit(1);
|
|
4765
4856
|
}
|
|
4766
4857
|
const outputPath = options.output ?? result.filePath;
|
|
4767
|
-
const fullOutputPath =
|
|
4858
|
+
const fullOutputPath = resolve3(outputPath);
|
|
4768
4859
|
const outputDir = dirname4(fullOutputPath);
|
|
4769
4860
|
if (!existsSync4(outputDir)) {
|
|
4770
4861
|
mkdirSync3(outputDir, { recursive: true });
|
|
@@ -4777,7 +4868,7 @@ program.command("compile <file>").description("Compile .flow.json or YAML direct
|
|
|
4777
4868
|
console.log(`\u{1F5FA}\uFE0F Source Map: ${mapPath}`);
|
|
4778
4869
|
}
|
|
4779
4870
|
if (result.dependencies && result.dependencies.all.length > 0) {
|
|
4780
|
-
const projectPkgPath =
|
|
4871
|
+
const projectPkgPath = resolve3("package.json");
|
|
4781
4872
|
if (existsSync4(projectPkgPath)) {
|
|
4782
4873
|
try {
|
|
4783
4874
|
const pkgJson2 = JSON.parse(readFileSync3(projectPkgPath, "utf-8"));
|
|
@@ -4800,7 +4891,7 @@ program.command("compile <file>").description("Compile .flow.json or YAML direct
|
|
|
4800
4891
|
});
|
|
4801
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) => {
|
|
4802
4893
|
const { decompile: decompile2 } = await Promise.resolve().then(() => (init_decompiler(), decompiler_exports));
|
|
4803
|
-
const filePath =
|
|
4894
|
+
const filePath = resolve3(file);
|
|
4804
4895
|
if (!existsSync4(filePath)) {
|
|
4805
4896
|
console.error(`\u274C File not found: ${filePath}`);
|
|
4806
4897
|
process.exit(1);
|
|
@@ -4820,7 +4911,7 @@ program.command("audit <file>").description("Decompile any TypeScript file into
|
|
|
4820
4911
|
if (fmt === "json") {
|
|
4821
4912
|
const output = JSON.stringify(result.ir, null, 2);
|
|
4822
4913
|
if (options.output) {
|
|
4823
|
-
writeFileSync3(
|
|
4914
|
+
writeFileSync3(resolve3(options.output), output, "utf-8");
|
|
4824
4915
|
console.log(`\u2705 IR written to: ${options.output}`);
|
|
4825
4916
|
} else {
|
|
4826
4917
|
console.log(output);
|
|
@@ -4828,7 +4919,7 @@ program.command("audit <file>").description("Decompile any TypeScript file into
|
|
|
4828
4919
|
} else if (fmt === "mermaid") {
|
|
4829
4920
|
const mermaidOutput = irToMermaid(result.ir);
|
|
4830
4921
|
if (options.output) {
|
|
4831
|
-
writeFileSync3(
|
|
4922
|
+
writeFileSync3(resolve3(options.output), mermaidOutput, "utf-8");
|
|
4832
4923
|
console.log(`\u2705 Mermaid diagram written to: ${options.output}`);
|
|
4833
4924
|
} else {
|
|
4834
4925
|
console.log(mermaidOutput);
|
|
@@ -4855,7 +4946,7 @@ program.command("audit <file>").description("Decompile any TypeScript file into
|
|
|
4855
4946
|
console.log("");
|
|
4856
4947
|
if (options.output) {
|
|
4857
4948
|
const irJson = JSON.stringify(result.ir, null, 2);
|
|
4858
|
-
writeFileSync3(
|
|
4949
|
+
writeFileSync3(resolve3(options.output), irJson, "utf-8");
|
|
4859
4950
|
console.log(`\u2705 IR written to: ${options.output}`);
|
|
4860
4951
|
}
|
|
4861
4952
|
}
|
|
@@ -4889,8 +4980,8 @@ function irToMermaid(ir) {
|
|
|
4889
4980
|
return lines.join("\n");
|
|
4890
4981
|
}
|
|
4891
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) => {
|
|
4892
|
-
const watchDir =
|
|
4893
|
-
const projectRoot =
|
|
4983
|
+
const watchDir = resolve3(dir);
|
|
4984
|
+
const projectRoot = resolve3(options.project ?? ".");
|
|
4894
4985
|
console.log(`\u{1F440} Watching: ${watchDir}/**/*.flow.json + **/*.yaml`);
|
|
4895
4986
|
console.log(`\u{1F4C1} Output to: ${projectRoot}`);
|
|
4896
4987
|
console.log("Press Ctrl+C to stop\n");
|
|
@@ -4901,17 +4992,28 @@ program.command("watch [dir]").description("Watch directory, auto-compile .flow.
|
|
|
4901
4992
|
ignoreInitial: false
|
|
4902
4993
|
}
|
|
4903
4994
|
);
|
|
4995
|
+
const pendingTimers = /* @__PURE__ */ new Map();
|
|
4996
|
+
const DEBOUNCE_MS = 150;
|
|
4904
4997
|
const handleChange = (filePath) => {
|
|
4905
|
-
|
|
4906
|
-
|
|
4907
|
-
} else if (filePath.endsWith(".yaml")) {
|
|
4998
|
+
let compileKey = filePath;
|
|
4999
|
+
if (filePath.endsWith(".yaml")) {
|
|
4908
5000
|
let dir2 = dirname4(filePath);
|
|
4909
5001
|
if (basename2(dir2) === "nodes") dir2 = dirname4(dir2);
|
|
4910
|
-
|
|
4911
|
-
|
|
4912
|
-
|
|
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
|
+
}
|
|
4913
5015
|
}
|
|
4914
|
-
}
|
|
5016
|
+
}, DEBOUNCE_MS));
|
|
4915
5017
|
};
|
|
4916
5018
|
watcher.on("change", handleChange);
|
|
4917
5019
|
watcher.on("add", handleChange);
|
|
@@ -4920,7 +5022,7 @@ program.command("watch [dir]").description("Watch directory, auto-compile .flow.
|
|
|
4920
5022
|
});
|
|
4921
5023
|
});
|
|
4922
5024
|
program.command("init").description("Initialize Flow2Code in current project (Zero Pollution mode)").action(() => {
|
|
4923
|
-
const flow2codeDir =
|
|
5025
|
+
const flow2codeDir = resolve3(".flow2code");
|
|
4924
5026
|
const flowsDir = join4(flow2codeDir, "flows");
|
|
4925
5027
|
if (!existsSync4(flow2codeDir)) {
|
|
4926
5028
|
mkdirSync3(flow2codeDir, { recursive: true });
|
|
@@ -4990,7 +5092,7 @@ program.command("init").description("Initialize Flow2Code in current project (Ze
|
|
|
4990
5092
|
writeFileSync3(examplePath, JSON.stringify(exampleFlow, null, 2), "utf-8");
|
|
4991
5093
|
console.log(`\u{1F4C4} Created example: ${examplePath}`);
|
|
4992
5094
|
}
|
|
4993
|
-
const gitignorePath =
|
|
5095
|
+
const gitignorePath = resolve3(".gitignore");
|
|
4994
5096
|
if (existsSync4(gitignorePath)) {
|
|
4995
5097
|
const content = readFileSync3(gitignorePath, "utf-8");
|
|
4996
5098
|
if (!content.includes(".flow2code/")) {
|
|
@@ -5009,7 +5111,7 @@ program.command("init").description("Initialize Flow2Code in current project (Ze
|
|
|
5009
5111
|
console.log(` npx ${pkgJson.name} watch .flow2code/flows/`);
|
|
5010
5112
|
});
|
|
5011
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) => {
|
|
5012
|
-
const filePath =
|
|
5114
|
+
const filePath = resolve3(file);
|
|
5013
5115
|
if (!existsSync4(filePath)) {
|
|
5014
5116
|
console.error(`\u274C File not found: ${filePath}`);
|
|
5015
5117
|
process.exit(1);
|
|
@@ -5025,7 +5127,7 @@ program.command("split <file>").description("Split .flow.json into a Git-friendl
|
|
|
5025
5127
|
const outputDir = options.output ?? filePath.replace(/\.flow\.json$|\.json$/, "");
|
|
5026
5128
|
const written = splitToFileSystem(
|
|
5027
5129
|
ir,
|
|
5028
|
-
|
|
5130
|
+
resolve3(outputDir),
|
|
5029
5131
|
{ mkdirSync: mkdirSync3, writeFileSync: (p, c) => writeFileSync3(p, c, "utf-8") },
|
|
5030
5132
|
{ join: join4 }
|
|
5031
5133
|
);
|
|
@@ -5033,7 +5135,7 @@ program.command("split <file>").description("Split .flow.json into a Git-friendl
|
|
|
5033
5135
|
written.forEach((f) => console.log(` \u{1F4C4} ${f}`));
|
|
5034
5136
|
});
|
|
5035
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) => {
|
|
5036
|
-
const dirPath =
|
|
5138
|
+
const dirPath = resolve3(dir);
|
|
5037
5139
|
if (!existsSync4(dirPath)) {
|
|
5038
5140
|
console.error(`\u274C Directory not found: ${dirPath}`);
|
|
5039
5141
|
process.exit(1);
|
|
@@ -5045,7 +5147,7 @@ program.command("merge <dir>").description("Merge YAML directory structure into
|
|
|
5045
5147
|
{ join: join4 }
|
|
5046
5148
|
);
|
|
5047
5149
|
const outputFile = options.output ?? `${dirPath}.flow.json`;
|
|
5048
|
-
writeFileSync3(
|
|
5150
|
+
writeFileSync3(resolve3(outputFile), JSON.stringify(ir, null, 2), "utf-8");
|
|
5049
5151
|
console.log(`\u2705 Merged to: ${outputFile}`);
|
|
5050
5152
|
console.log(` ${ir.nodes.length} nodes, ${ir.edges.length} edges`);
|
|
5051
5153
|
} catch (err) {
|
|
@@ -5054,7 +5156,7 @@ program.command("merge <dir>").description("Merge YAML directory structure into
|
|
|
5054
5156
|
}
|
|
5055
5157
|
});
|
|
5056
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) => {
|
|
5057
|
-
const filePath =
|
|
5159
|
+
const filePath = resolve3(file);
|
|
5058
5160
|
if (!existsSync4(filePath)) {
|
|
5059
5161
|
console.error(`\u274C File not found: ${filePath}`);
|
|
5060
5162
|
process.exit(1);
|
|
@@ -5075,7 +5177,7 @@ program.command("migrate <file>").description("Migrate .flow.json to Git-friendl
|
|
|
5075
5177
|
}
|
|
5076
5178
|
});
|
|
5077
5179
|
program.command("trace <file> <line>").description("Trace a generated code line number back to its canvas node (Source Map)").action((file, lineStr) => {
|
|
5078
|
-
const filePath =
|
|
5180
|
+
const filePath = resolve3(file);
|
|
5079
5181
|
const lineNum = parseInt(lineStr, 10);
|
|
5080
5182
|
if (isNaN(lineNum) || lineNum < 1) {
|
|
5081
5183
|
console.error("\u274C Line number must be a positive integer");
|
|
@@ -5105,8 +5207,8 @@ program.command("trace <file> <line>").description("Trace a generated code line
|
|
|
5105
5207
|
}
|
|
5106
5208
|
});
|
|
5107
5209
|
program.command("diff <before> <after>").description("Compare semantic differences between two .flow.json files").action((beforeFile, afterFile) => {
|
|
5108
|
-
const beforePath =
|
|
5109
|
-
const afterPath =
|
|
5210
|
+
const beforePath = resolve3(beforeFile);
|
|
5211
|
+
const afterPath = resolve3(afterFile);
|
|
5110
5212
|
if (!existsSync4(beforePath)) {
|
|
5111
5213
|
console.error(`\u274C File not found: ${beforePath}`);
|
|
5112
5214
|
process.exit(1);
|
|
@@ -5128,8 +5230,8 @@ program.command("diff <before> <after>").description("Compare semantic differenc
|
|
|
5128
5230
|
console.log(formatDiff(summary));
|
|
5129
5231
|
});
|
|
5130
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) => {
|
|
5131
|
-
const filePath =
|
|
5132
|
-
const envPath =
|
|
5233
|
+
const filePath = resolve3(file);
|
|
5234
|
+
const envPath = resolve3(options.env);
|
|
5133
5235
|
if (!existsSync4(filePath)) {
|
|
5134
5236
|
console.error(`\u274C File not found: ${filePath}`);
|
|
5135
5237
|
process.exit(1);
|
|
@@ -5146,7 +5248,7 @@ program.command("env-check <file>").description("Validate that environment varia
|
|
|
5146
5248
|
const envContent = readFileSync3(envPath, "utf-8");
|
|
5147
5249
|
declaredVars = parseEnvFile(envContent);
|
|
5148
5250
|
} else {
|
|
5149
|
-
const examplePath =
|
|
5251
|
+
const examplePath = resolve3(".env.example");
|
|
5150
5252
|
if (existsSync4(examplePath)) {
|
|
5151
5253
|
const envContent = readFileSync3(examplePath, "utf-8");
|
|
5152
5254
|
declaredVars = parseEnvFile(envContent);
|
|
@@ -5157,7 +5259,11 @@ program.command("env-check <file>").description("Validate that environment varia
|
|
|
5157
5259
|
`);
|
|
5158
5260
|
}
|
|
5159
5261
|
}
|
|
5160
|
-
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);
|
|
5161
5267
|
console.log(formatEnvValidationReport(result));
|
|
5162
5268
|
if (!result.valid) {
|
|
5163
5269
|
process.exit(1);
|
|
@@ -5182,12 +5288,29 @@ async function startDevServer(options) {
|
|
|
5182
5288
|
}
|
|
5183
5289
|
});
|
|
5184
5290
|
}
|
|
5185
|
-
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(
|
|
5186
|
-
|
|
5187
|
-
|
|
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) {
|
|
5188
5311
|
const startTime = Date.now();
|
|
5189
5312
|
try {
|
|
5190
|
-
const raw =
|
|
5313
|
+
const raw = await readFile2(filePath, "utf-8");
|
|
5191
5314
|
const ir = JSON.parse(raw);
|
|
5192
5315
|
const validation = validateFlowIR(ir);
|
|
5193
5316
|
if (!validation.valid) {
|
|
@@ -5208,9 +5331,9 @@ function compileFile(filePath, projectRoot) {
|
|
|
5208
5331
|
const outputPath = join4(projectRoot, result.filePath);
|
|
5209
5332
|
const outputDir = dirname4(outputPath);
|
|
5210
5333
|
if (!existsSync4(outputDir)) {
|
|
5211
|
-
|
|
5334
|
+
await mkdir(outputDir, { recursive: true });
|
|
5212
5335
|
}
|
|
5213
|
-
|
|
5336
|
+
await writeFile(outputPath, result.code, "utf-8");
|
|
5214
5337
|
const elapsed = Date.now() - startTime;
|
|
5215
5338
|
console.log(`\u2705 [${elapsed}ms] ${filePath} \u2192 ${outputPath}`);
|
|
5216
5339
|
} catch (err) {
|
|
@@ -5219,7 +5342,7 @@ function compileFile(filePath, projectRoot) {
|
|
|
5219
5342
|
);
|
|
5220
5343
|
}
|
|
5221
5344
|
}
|
|
5222
|
-
function
|
|
5345
|
+
async function compileFlowDirAsync(dirPath, projectRoot) {
|
|
5223
5346
|
const startTime = Date.now();
|
|
5224
5347
|
try {
|
|
5225
5348
|
const project = loadFlowProject(dirPath);
|
|
@@ -5243,9 +5366,9 @@ function compileFlowDir(dirPath, projectRoot) {
|
|
|
5243
5366
|
const outputPath = join4(projectRoot, result.filePath);
|
|
5244
5367
|
const outputDir = dirname4(outputPath);
|
|
5245
5368
|
if (!existsSync4(outputDir)) {
|
|
5246
|
-
|
|
5369
|
+
await mkdir(outputDir, { recursive: true });
|
|
5247
5370
|
}
|
|
5248
|
-
|
|
5371
|
+
await writeFile(outputPath, result.code, "utf-8");
|
|
5249
5372
|
const elapsed = Date.now() - startTime;
|
|
5250
5373
|
console.log(`\u2705 [${elapsed}ms] ${dirPath}/ \u2192 ${outputPath}`);
|
|
5251
5374
|
} catch (err) {
|
|
@@ -5254,17 +5377,6 @@ function compileFlowDir(dirPath, projectRoot) {
|
|
|
5254
5377
|
);
|
|
5255
5378
|
}
|
|
5256
5379
|
}
|
|
5257
|
-
function generateEnvExample() {
|
|
5258
|
-
const envExamplePath = resolve2(".env.example");
|
|
5259
|
-
if (!existsSync4(envExamplePath)) {
|
|
5260
|
-
writeFileSync3(
|
|
5261
|
-
envExamplePath,
|
|
5262
|
-
"# Flow2Code environment variables\n# Define API keys and sensitive information here\n",
|
|
5263
|
-
"utf-8"
|
|
5264
|
-
);
|
|
5265
|
-
console.log("\u{1F4DD} Generated .env.example");
|
|
5266
|
-
}
|
|
5267
|
-
}
|
|
5268
5380
|
if (process.argv.length <= 2) {
|
|
5269
5381
|
program.help();
|
|
5270
5382
|
}
|