@xnoxs/flux-lang 4.0.3 → 4.0.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/src/self/cli.js CHANGED
@@ -1,48 +1,6 @@
1
- // ── Flux stdlib ──
2
-
3
- function map(arr, fn) { return arr.map(fn); }
4
-
5
- function filter(arr, fn) { return arr.filter(fn); }
6
-
7
- function reduce(arr, fn, init) { return arguments.length >= 3 ? arr.reduce(fn, init) : arr.reduce(fn); }
8
-
9
- function some(arr, fn) { return arr.some(fn); }
10
-
11
- function join(arr, sep) { return arr.join(sep != null ? sep : ','); }
12
-
13
- function clamp(val, min, max) {
14
- return Math.min(Math.max(val, min), max);
15
- }
16
-
17
- function sum(arr) {
18
- return arr.reduce(function(a, b) { return a + b; }, 0);
19
- }
20
-
21
- function max(arr) {
22
- if (arguments.length > 1) return Math.max.apply(null, arguments);
23
- return Math.max.apply(null, arr);
24
- }
25
-
26
- function padStart(s, len, char) {
27
- return String(s).padStart(len, char || ' ');
28
- }
29
-
30
- function padEnd(s, len, char) {
31
- return String(s).padEnd(len, char || ' ');
32
- }
33
-
34
- function trim(s) { return String(s).trim(); }
35
-
36
- function trimEnd(s) { return String(s).trimEnd(); }
37
-
38
- function startsWith(s, prefix) { return String(s).startsWith(prefix); }
39
-
40
- function endsWith(s, suffix) { return String(s).endsWith(suffix); }
41
-
42
- function repeat(s, n) { return String(s).repeat(n); }
43
- // ── end stdlib ──
44
-
45
- // Generated by Flux Transpiler v3.2.0
1
+ /* compiled from src/self/cli.flux */
2
+ 'use strict';
3
+ // Generated by Flux Transpiler v3.5.3 (self-hosted)
46
4
  "use strict";
47
5
 
48
6
  const Fs = require("fs");
@@ -57,7 +15,7 @@ const { loadConfig } = require("./config");
57
15
  const { cmdAdd, cmdRemove, cmdInstall, cmdList, cmdSearch, cmdInfo, cmdPublish } = require("./pkg");
58
16
  const VERSION = (FLUX_VERSION ?? "3.0.0");
59
17
  const STAGE = (FLUX_STAGE ?? "self-hosted");
60
- const C = { reset: "\u001b[0m", bold: "\u001b[1m", dim: "\u001b[2m", red: "\u001b[31m", green: "\u001b[32m", yellow: "\u001b[33m", blue: "\u001b[34m", cyan: "\u001b[36m", white: "\u001b[37m", gray: "\u001b[90m", magenta: "\u001b[35m" };
18
+ const C = { reset: "\\u001b[0m", bold: "\\u001b[1m", dim: "\\u001b[2m", red: "\\u001b[31m", green: "\\u001b[32m", yellow: "\\u001b[33m", blue: "\\u001b[34m", cyan: "\\u001b[36m", white: "\\u001b[37m", gray: "\\u001b[90m", magenta: "\\u001b[35m" };
61
19
  const noColor = (process.env.NO_COLOR || !process.stdout.isTTY);
62
20
  function clr(c, s) {
63
21
  return (noColor ? s : ((c + s) + C.reset));
@@ -96,32 +54,20 @@ function showHelp() {
96
54
  console.log(bold("USAGE:"));
97
55
  console.log(" flux <command> [options]\n");
98
56
  console.log(bold("COMPILER:"));
99
- const compilerCmds = [["compile <file.flux>", "Compile .flux → .js"], ["bundle <entry.flux>", "Bundle multiple files into one .js"], ["run <file.flux>", "Compile and run immediately"], ["watch <file.flux>", "Watch for changes, auto-compile"], ["check <file.flux>", "Type-check and static analysis"]];
100
- for (const __item__ of compilerCmds) {
101
- const [cmd, desc] = __item__;
102
- console.log((((" " + green(("flux " + cmd).padEnd(36))) + " ") + gray(desc)));
103
- }
57
+ const compilerCmds = [["compile <file.flux>", "Compile .flux → .js"], ["bundle <entry.flux>", "Bundle multi-file project into one .js"], ["run <file.flux>", "Compile and run a single-file script"], ["watch <file.flux>", "Watch for changes, auto-compile"], ["check <file.flux>", "Type-check and static analysis"]];
58
+ compilerCmds.forEach((pair) => console.log((((" " + green(("flux " + pair[0]).padEnd(36))) + " ") + gray(pair[1]))));
104
59
  console.log();
105
60
  console.log(bold("TOOLING:"));
106
61
  const toolCmds = [["lint <file.flux>", "Full lint: types + style + immutability"], ["fmt <file.flux>", "Format source code in-place"], ["test [dir]", "Run *.test.flux files"], ["repl", "Interactive REPL mode"], ["tokens <file.flux>", "Show lexer token list"], ["ast <file.flux>", "Show Abstract Syntax Tree (JSON)"]];
107
- for (const __item__ of toolCmds) {
108
- const [cmd, desc] = __item__;
109
- console.log((((" " + green(("flux " + cmd).padEnd(36))) + " ") + gray(desc)));
110
- }
62
+ toolCmds.forEach((pair) => console.log((((" " + green(("flux " + pair[0]).padEnd(36))) + " ") + gray(pair[1]))));
111
63
  console.log();
112
64
  console.log(bold("PACKAGE MANAGER:"));
113
- const pkgCmds = [["init [name]", "Scaffold a new Flux project"], ["add <pkg[@version]>", "Add a dependency"], ["remove <pkg>", "Remove a dependency"], ["install", "Install all dependencies"], ["list", "List installed packages"], ["search <query>", "Search the package registry"], ["info <pkg>", "Show package details"], ["publish", "Publish package to registry"]];
114
- for (const __item__ of pkgCmds) {
115
- const [cmd, desc] = __item__;
116
- console.log((((" " + cyan(("flux " + cmd).padEnd(36))) + " ") + gray(desc)));
117
- }
65
+ const pkgCmds = [["init [name] [--template]", "Scaffold a new Flux project"], ["add <pkg[@version]>", "Add a dependency (npm install --save)"], ["remove <pkg>", "Remove a dependency (npm uninstall)"], ["install", "Install all dependencies (npm install)"], ["list", "List installed packages"], ["search <query>", "Search the npm registry"], ["info <pkg>", "Show package details"], ["publish", "Publish package to npm registry"]];
66
+ pkgCmds.forEach((pair) => console.log((((" " + cyan(("flux " + pair[0]).padEnd(36))) + " ") + gray(pair[1]))));
118
67
  console.log();
119
68
  console.log(bold("SELF-HOSTED:"));
120
69
  const selfCmds = [["self-hosted", "Show self-hosted compiler status"], ["self-hosted build", "Bootstrap: compile compiler with itself"], ["self-hosted verify", "Verify self-hosted output matches stage-0"]];
121
- for (const __item__ of selfCmds) {
122
- const [cmd, desc] = __item__;
123
- console.log((((" " + yellow(("flux " + cmd).padEnd(36))) + " ") + gray(desc)));
124
- }
70
+ selfCmds.forEach((pair) => console.log((((" " + yellow(("flux " + pair[0]).padEnd(36))) + " ") + gray(pair[1]))));
125
71
  console.log();
126
72
  console.log(bold("OPTIONS:"));
127
73
  console.log(((" " + yellow("--out, -o <file> ")) + " Output file"));
@@ -131,11 +77,12 @@ function showHelp() {
131
77
  console.log(((" " + yellow("--typecheck ")) + " Enable type checking"));
132
78
  console.log(((" " + yellow("--stdout ")) + " Print to terminal"));
133
79
  console.log(((" " + yellow("--no-color ")) + " Disable colors"));
80
+ console.log(((" " + yellow("--template ")) + " Init template: script, server, webapp"));
134
81
  console.log();
135
82
  }
136
83
  function parseArgs(argv) {
137
84
  const args = argv.slice(2);
138
- const opts = { out: null, sourcemap: false, mangle: false, typecheck: false, strict: false, stdout: false, watch: false, dev: false, verbose: false, jsx: false, jsxTarget: "browser" };
85
+ const opts = { out: null, sourcemap: false, mangle: false, typecheck: false, strict: false, stdout: false, watch: false, dev: false, verbose: false, jsx: false, jsxTarget: "browser", template: null };
139
86
  const positional = [];
140
87
  let i = 0;
141
88
  while ((i < args.length)) {
@@ -175,6 +122,10 @@ function parseArgs(argv) {
175
122
  i = (i + 1);
176
123
  opts.jsxTarget = args[i];
177
124
  }
125
+ else if (((a == "--template") || (a == "-t"))) {
126
+ i = (i + 1);
127
+ opts.template = args[i];
128
+ }
178
129
  else if (!a.startsWith("--")) {
179
130
  positional.push(a);
180
131
  }
@@ -243,6 +194,21 @@ function printErrors(errors, source, filePath) {
243
194
  }
244
195
  console.error();
245
196
  }
197
+ function findFluxImports(source) {
198
+ const found = [];
199
+ const lines = source.split("\n");
200
+ for (const line of lines) {
201
+ const trimmed = line.trim();
202
+ const m = trimmed.match(/^import\s+.+\s+from\s+["'](\.[^"']+)["']/);
203
+ if (m) {
204
+ const src = m[1];
205
+ if (((!src.endsWith(".js") && !src.endsWith(".json")) && !src.endsWith(".node"))) {
206
+ found.push(src);
207
+ }
208
+ }
209
+ }
210
+ return found;
211
+ }
246
212
  function cmdCompile(filePath, opts) {
247
213
  const { source, abs } = readFluxFile(filePath);
248
214
  const cfg = loadConfig(Path.dirname(abs));
@@ -276,6 +242,21 @@ function cmdCompile(filePath, opts) {
276
242
  }
277
243
  function cmdRun(filePath, opts) {
278
244
  const { source, abs } = readFluxFile(filePath);
245
+ const fluxImports = findFluxImports(source);
246
+ if ((fluxImports.length > 0)) {
247
+ const example = fluxImports[0];
248
+ const entryRel = Path.relative(process.cwd(), abs);
249
+ const outFile = (Path.basename(abs, ".flux") + ".js");
250
+ console.error(red("\n✗ Cannot run a multi-file Flux project with `flux run`.\n"));
251
+ console.error((" Found: " + yellow((("import ... from \"" + example) + "\""))));
252
+ console.error();
253
+ console.error(((" Use " + cyan("flux bundle")) + " to compile all files into one, then run it:\n"));
254
+ console.error((" " + yellow(((("flux bundle " + entryRel) + " -o dist/") + outFile))));
255
+ console.error((" " + yellow(("node dist/" + outFile))));
256
+ console.error();
257
+ console.error(((" Or use " + cyan("flux run")) + " only for single-file scripts (no inter-file .flux imports).\n"));
258
+ process.exit(1);
259
+ }
279
260
  const result = transpile(source, { jsx: (opts.jsx ?? false), jsxTarget: (opts.jsxTarget ?? "browser"), mangle: false });
280
261
  if (!result.success) {
281
262
  console.error(red("\n✗ Compile error"));
@@ -443,6 +424,10 @@ function cmdBundle(entryPath, opts) {
443
424
  console.log(result.code);
444
425
  return;
445
426
  }
427
+ const outDir = Path.dirname(Path.resolve(outFile));
428
+ if (!Fs.existsSync(outDir)) {
429
+ Fs.mkdirSync(outDir, { recursive: true });
430
+ }
446
431
  Fs.writeFileSync(outFile, result.code, "utf8");
447
432
  const kb = (result.code.length / 1024).toFixed(1);
448
433
  console.log((((((green("✓ Bundle done") + gray(((" (" + elapsed) + "ms) "))) + Path.basename(abs)) + gray(((" + " + (result.modules - 1)) + " module(s) → "))) + cyan(Path.relative(process.cwd(), outFile))) + gray(((" [" + kb) + " KB]"))));
@@ -573,6 +558,7 @@ function cmdRepl(opts) {
573
558
  }
574
559
  function cmdInit(name, opts) {
575
560
  const projectName = (name ?? "my-flux-app");
561
+ const template = (opts.template ?? "script");
576
562
  const dir = Path.resolve(projectName);
577
563
  if (Fs.existsSync(dir)) {
578
564
  console.error(red(("✗ Directory already exists: " + projectName)));
@@ -581,38 +567,77 @@ function cmdInit(name, opts) {
581
567
  Fs.mkdirSync(dir, { recursive: true });
582
568
  Fs.mkdirSync(Path.join(dir, "src"), { recursive: true });
583
569
  Fs.mkdirSync(Path.join(dir, "tests"), { recursive: true });
584
- const mainFlux = "// {projectName} — built with Flux Lang v{VERSION}\n// Run: flux run src/main.flux\n\n// ── Algebraic Data Types + Pattern Matching ───────────────────\ntype Shape = Circle(radius) | Rect(width, height) | Triangle(base, height)\n\nfn area(shape):\n match shape:\n when Circle(r): return Math.PI * r * r\n when Rect(w, h): return w * h\n when Triangle(b, h): return 0.5 * b * h\n\nfn describe(shape):\n match shape:\n when Circle(r): return \"Circle(r={r:.2f})\"\n when Rect(w, h): return \"Rect({w:.1f}x{h:.1f})\"\n when Triangle(b, h): return \"Triangle(b={b:.1f}, h={h:.1f})\"\n\n// ── Result type ───────────────────────────────────────────────\ntype Result = Ok(value) | Err(message)\n\nfn safeDivide(a, b):\n if b == 0: return Err(\"division by zero\")\n return Ok(a / b)\n\n// ── Utility functions ─────────────────────────────────────────\nfn greet(name): return \"Hello from Flux, {name}!\"\n\nfn formatList(items):\n if items.length == 0: return \"(empty)\"\n return \"[\" + items.join(\", \") + \"]\"\n\nfn clamp(value, lo, hi):\n if value < lo: return lo\n if value > hi: return hi\n return value\n\n// ── Pipe operator + stdlib ────────────────────────────────────\nval numbers = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]\nval processed = numbers\n |> filter(n -> n > 3)\n |> map(n -> n * n)\n |> sort\n\n// ── Main ──────────────────────────────────────────────────────\nprint(greet(\"{projectName}\"))\nprint(\"Squares > 3: {formatList(processed)}\")\nprint(\"clamp(12, 0, 10) = {clamp(12, 0, 10)}\")\n\nval shapes = [Circle(5.0), Rect(8.0, 3.0), Triangle(6.0, 4.0)]\nfor shape in shapes:\n val a = area(shape)\n print(\"{describe(shape)} area={a:.2f}\")\n\nmatch safeDivide(10.0, 3.0):\n when Ok(v): print(\"10 / 3 = {v:.4f}\")\n when Err(e): print(\"Error: {e}\")";
585
- const utilsFlux = "// Utility functions for {projectName}\n// Use with: flux bundle src/main.flux (bundles imports automatically)\n\nexport fn greet(name):\n return \"Hello from Flux, {name}!\"\n\nexport fn formatList(items):\n if items.length == 0: return \"(empty)\"\n return \"[\" + items.join(\", \") + \"]\"\n\nexport fn clamp(value, lo, hi):\n if value < lo: return lo\n if value > hi: return hi\n return value\n\nexport fn sum(nums):\n return nums.reduce((acc, x) -> acc + x, 0)\n\nexport fn average(nums):\n if nums.length == 0: return 0\n return sum(nums) / nums.length";
586
- const testFlux = "// Tests for {projectName}\n// Run: flux test tests/\n\n// ── Functions under test ──────────────────────────────────────\nfn add(a, b):\n return a + b\n\nfn mul(a, b):\n return a * b\n\nfn clamp(value, lo, hi):\n if value < lo: return lo\n if value > hi: return hi\n return value\n\nfn average(nums):\n if nums.length == 0: return 0\n return nums.reduce((s, x) -> s + x, 0) / nums.length\n\ntype Result = Ok(value) | Err(message)\n\nfn safeDivide(a, b):\n if b == 0: return Err(\"div by zero\")\n return Ok(a / b)\n\n// ── Test functions ────────────────────────────────────────────\nfn test_add():\n assert(add(1, 2) == 3, \"1+2=3\")\n assert(add(0, 0) == 0, \"0+0=0\")\n assert(add(-1, 1) == 0, \"-1+1=0\")\n\nfn test_mul():\n assert(mul(3, 4) == 12, \"3*4=12\")\n assert(mul(0, 5) == 0, \"0*5=0\")\n\nfn test_clamp():\n assert(clamp(5, 0, 10) == 5, \"within range\")\n assert(clamp(-1, 0, 10) == 0, \"below min\")\n assert(clamp(15, 0, 10) == 10, \"above max\")\n\nfn test_average():\n assert(average([2, 4, 6]) == 4, \"average of 2,4,6\")\n assert(average([]) == 0, \"empty list\")\n\nfn test_safe_divide():\n val ok = safeDivide(10, 2)\n match ok:\n when Ok(v): assert(v == 5, \"10/2=5\")\n when Err(e): assert(false, \"unexpected error\")\n val err = safeDivide(10, 0)\n match err:\n when Ok(v): assert(false, \"expected error\")\n when Err(e): assert(e == \"div by zero\", \"error message\")\n\nfn test_pipe_operator():\n val result = [1, 2, 3, 4, 5] |> filter(n -> n > 2) |> map(n -> n * 2)\n assert(result.length == 3, \"filter length\")\n assert(result[0] == 6, \"first element\")\n assert(result[2] == 10, \"last element\")";
587
- const fluxJson = { name: projectName, version: "1.0.0", description: (("A Flux Lang v" + VERSION) + " project"), author: "", license: "MIT", entry: "src/main.flux", outDir: "dist", sourcemap: false, typecheck: true, scripts: { start: "flux run src/main.flux", build: "flux bundle src/main.flux -o dist/bundle.js", dev: "flux watch src/main.flux", check: "flux check src/main.flux", test: "flux test tests/", fmt: "flux fmt src/", lint: "flux lint src/" }, dependencies: { }, devDependencies: { "@xnoxs/flux-lang": ("^" + VERSION) } };
588
- const gitignore = "node_modules/\ndist/\nflux_modules/\n*.js.map\n.DS_Store\n";
589
- const readme = "# {projectName}\n\nA project built with [Flux Lang](https://flux-lang.dev) v{VERSION} — the self-hosted compiler.\n\n## Quick Start\n\n```bash\nflux run src/main.flux\n```\n\n## Project Structure\n\n```\n{projectName}/\n├── src/\n│ ├── main.flux # Entry point\n│ └── utils.flux # Utility functions\n├── tests/\n│ └── main.test.flux # Test suite\n├── flux_modules/ # Packages installed by `flux install` (gitignored)\n├── flux.json # Project config & dependencies\n└── README.md\n```\n\n## Commands\n\n| Command | Description |\n|---|---|\n| `flux run src/main.flux` | Run the project |\n| `flux bundle src/main.flux -o dist/bundle.js` | Bundle to single file |\n| `flux watch src/main.flux` | Watch mode |\n| `flux check src/main.flux` | Type check + static analysis |\n| `flux test tests/` | Run all tests |\n| `flux fmt src/` | Format source code |\n| `flux lint src/` | Lint for issues |\n| `flux add <package>` | Add a dependency |";
590
- const allFiles = [(projectName + "/src/main.flux"), (projectName + "/src/utils.flux"), (projectName + "/tests/main.test.flux"), (projectName + "/flux.json"), (projectName + "/.gitignore"), (projectName + "/README.md")];
591
- Fs.writeFileSync(Path.join(dir, "src", "main.flux"), mainFlux, "utf8");
592
- Fs.writeFileSync(Path.join(dir, "src", "utils.flux"), utilsFlux, "utf8");
593
- Fs.writeFileSync(Path.join(dir, "tests", "main.test.flux"), testFlux, "utf8");
594
- Fs.writeFileSync(Path.join(dir, "flux.json"), (JSON.stringify(fluxJson, null, 2) + "\n"), "utf8");
595
- Fs.writeFileSync(Path.join(dir, ".gitignore"), gitignore, "utf8");
596
- Fs.writeFileSync(Path.join(dir, "README.md"), readme, "utf8");
597
- console.log();
598
- console.log((green("✓ Created: ") + bold((projectName + "/"))));
599
- console.log();
600
- console.log(gray(" Files:"));
601
- for (const f of allFiles) {
602
- console.log((" " + cyan(f)));
570
+ const gitignore = "node_modules/\ndist/\n*.js.map\n.DS_Store\n";
571
+ const testFlux = "// Tests for {projectName}\n// Run: flux test tests/\n\nfn add(a, b):\n return a + b\n\nfn clamp(value, lo, hi):\n if value < lo: return lo\n if value > hi: return hi\n return value\n\nfn test_add():\n assert(add(1, 2) == 3, \"1+2=3\")\n assert(add(0, 0) == 0, \"0+0=0\")\n assert(add(-1, 1) == 0, \"-1+1=0\")\n\nfn test_clamp():\n assert(clamp(5, 0, 10) == 5, \"within range\")\n assert(clamp(-1, 0, 10) == 0, \"below min\")\n assert(clamp(15, 0, 10) == 10, \"above max\")";
572
+ if ((template == "script")) {
573
+ const mainFlux = "// {projectName} a Flux script\n// Run: npm run dev (or: node_modules/.bin/flux run src/main.flux)\n\nimport Fs from \"fs\"\nimport Path from \"path\"\n\nfn main():\n print(\"Hello from Flux!\")\n print(\"Node version: {process.version}\")\n\nmain()";
574
+ const pkgJson = { name: projectName, version: "1.0.0", description: "A Flux project", main: "dist/main.js", scripts: { dev: "flux run src/main.flux", build: "flux bundle src/main.flux -o dist/main.js", start: "node dist/main.js", test: "flux test tests/" }, flux: { entry: "src/main.flux", outDir: "dist", mangle: false, sourcemap: false }, dependencies: { "@xnoxs/flux-lang": ("^" + VERSION) }, devDependencies: { } };
575
+ Fs.writeFileSync(Path.join(dir, "src", "main.flux"), mainFlux, "utf8");
576
+ Fs.writeFileSync(Path.join(dir, "tests", "main.test.flux"), testFlux, "utf8");
577
+ Fs.writeFileSync(Path.join(dir, "package.json"), (JSON.stringify(pkgJson, null, 2) + "\n"), "utf8");
578
+ Fs.writeFileSync(Path.join(dir, ".gitignore"), gitignore, "utf8");
579
+ console.log();
580
+ console.log((((green("✓ Project ") + bold((projectName + "/"))) + green(" created with template ")) + cyan("script")));
581
+ console.log();
582
+ console.log(bold(" Next steps:"));
583
+ console.log((" " + yellow(("cd " + projectName))));
584
+ console.log((" " + yellow("npm install")));
585
+ console.log((" " + yellow("npm run dev")));
586
+ console.log();
587
+ console.log(gray(" Add packages:"));
588
+ console.log((" " + yellow("flux add <package>")));
589
+ console.log();
590
+ return;
603
591
  }
604
- console.log();
605
- console.log(bold(" Next steps:"));
606
- console.log((" " + yellow(("cd " + projectName))));
607
- console.log((" " + yellow("flux run src/main.flux")));
608
- console.log();
609
- console.log(gray(" Add packages (installed into flux_modules/):"));
610
- console.log((" " + yellow("flux add <package>")));
611
- console.log((" " + yellow("flux install")));
612
- console.log();
613
- console.log(gray(" Or run the test suite:"));
614
- console.log((" " + yellow("flux test tests/")));
615
- console.log();
592
+ if ((template == "server")) {
593
+ const serverFlux = "// {projectName} — Flux HTTP server\n// Run: npm run dev\n// Build: npm run build && npm start\n\nimport Express from \"express\"\n\nval app = Express()\nval PORT = process.env.PORT or 3000\n\napp.use(Express.json())\n\napp.get(\"/\", (req, res) ->\n res.json({ status: \"ok\", message: \"Hello from Flux!\", port: PORT })\n)\n\napp.listen(PORT, -> print(\"Server running at http://localhost:{PORT}\"))";
594
+ const pkgJson = { name: projectName, version: "1.0.0", description: "A Flux HTTP server", main: "dist/server.js", scripts: { dev: "flux run src/server.flux", build: "flux bundle src/server.flux -o dist/server.js", start: "node dist/server.js", test: "flux test tests/" }, flux: { entry: "src/server.flux", outDir: "dist", mangle: false, sourcemap: false }, dependencies: { "@xnoxs/flux-lang": ("^" + VERSION), express: "^4.18.0" }, devDependencies: { } };
595
+ Fs.writeFileSync(Path.join(dir, "src", "server.flux"), serverFlux, "utf8");
596
+ Fs.writeFileSync(Path.join(dir, "tests", "main.test.flux"), testFlux, "utf8");
597
+ Fs.writeFileSync(Path.join(dir, "package.json"), (JSON.stringify(pkgJson, null, 2) + "\n"), "utf8");
598
+ Fs.writeFileSync(Path.join(dir, ".gitignore"), gitignore, "utf8");
599
+ console.log();
600
+ console.log((((green("✓ Project ") + bold((projectName + "/"))) + green(" created with template ")) + cyan("server")));
601
+ console.log();
602
+ console.log(bold(" Next steps:"));
603
+ console.log((" " + yellow(("cd " + projectName))));
604
+ console.log((" " + yellow("npm install")));
605
+ console.log((" " + yellow("npm run dev")));
606
+ console.log();
607
+ console.log(gray(" Build for production:"));
608
+ console.log((" " + yellow("npm run build && npm start")));
609
+ console.log();
610
+ return;
611
+ }
612
+ if ((template == "webapp")) {
613
+ const utilsFlux = "// Utility functions\nexport fn formatDate(date):\n val d = new Date(date)\n return d.toISOString().split(\"T\")[0]\n\nexport fn paginate(items, page, perPage):\n val start = (page - 1) * perPage\n return items.slice(start, start + perPage)";
614
+ const routesFlux = "// Route handlers\nimport { formatDate, paginate } from \"./utils\" // inter-file .flux import\n\nval items = [\n { id: 1, name: \"Item A\", date: \"2024-01-01\" },\n { id: 2, name: \"Item B\", date: \"2024-02-01\" },\n { id: 3, name: \"Item C\", date: \"2024-03-01\" },\n]\n\nexport fn setupRoutes(app):\n app.get(\"/items\", (req, res) ->\n val page = parseInt(req.query.page or \"1\")\n val perPage = parseInt(req.query.per_page or \"10\")\n val data = paginate(items, page, perPage)\n res.json({ data, page })\n )\n\n app.get(\"/items/:id\", (req, res) ->\n val item = items.find(i -> i.id == parseInt(req.params.id))\n if item == null:\n res.status(404).json({ error: \"Not found\" })\n else:\n res.json({ data: item, date: formatDate(item.date) })\n )";
615
+ const serverFlux = "// Entry point — MUST be compiled with: npm run build\n// Inter-file .flux imports only work via flux bundle, NOT flux run\nimport Express from \"express\"\nimport { setupRoutes } from \"./routes\" // inter-file .flux import\n\nval app = Express()\nval PORT = process.env.PORT or 3000\n\napp.use(Express.json())\nsetupRoutes(app)\n\napp.listen(PORT, -> print(\"{projectName} running at http://localhost:{PORT}\"))";
616
+ const pkgJson = { name: projectName, version: "1.0.0", description: "A Flux web app", main: "dist/server.js", scripts: { build: "flux bundle src/server.flux -o dist/server.js", start: "node dist/server.js", dev: "flux bundle src/server.flux -o dist/server.js && node dist/server.js", watch: "flux watch src/server.flux", test: "flux test tests/" }, flux: { entry: "src/server.flux", outDir: "dist", mangle: false, sourcemap: true }, dependencies: { "@xnoxs/flux-lang": ("^" + VERSION), express: "^4.18.0" }, devDependencies: { } };
617
+ Fs.writeFileSync(Path.join(dir, "src", "utils.flux"), utilsFlux, "utf8");
618
+ Fs.writeFileSync(Path.join(dir, "src", "routes.flux"), routesFlux, "utf8");
619
+ Fs.writeFileSync(Path.join(dir, "src", "server.flux"), serverFlux, "utf8");
620
+ Fs.writeFileSync(Path.join(dir, "tests", "main.test.flux"), testFlux, "utf8");
621
+ Fs.writeFileSync(Path.join(dir, "package.json"), (JSON.stringify(pkgJson, null, 2) + "\n"), "utf8");
622
+ Fs.writeFileSync(Path.join(dir, ".gitignore"), gitignore, "utf8");
623
+ console.log();
624
+ console.log((((green("✓ Project ") + bold((projectName + "/"))) + green(" created with template ")) + cyan("webapp")));
625
+ console.log();
626
+ console.log(bold(" Next steps:"));
627
+ console.log((" " + yellow(("cd " + projectName))));
628
+ console.log(((" " + yellow("npm install")) + gray(" # install dependencies")));
629
+ console.log(((" " + yellow("npm run build")) + gray(" # compile all .flux → dist/server.js")));
630
+ console.log(((" " + yellow("npm start")) + gray(" # run the server")));
631
+ console.log();
632
+ console.log((yellow(" Note: ") + gray("This project uses multiple .flux files.")));
633
+ console.log(((gray(" Always build with ") + yellow("npm run build")) + gray(" (flux bundle) before running.")));
634
+ console.log(((gray(" Do NOT use ") + red("flux run")) + gray(" on files that import other .flux files.")));
635
+ console.log();
636
+ return;
637
+ }
638
+ console.error(red(("✗ Unknown template: " + template)));
639
+ console.error(gray(" Available templates: script, server, webapp"));
640
+ process.exit(1);
616
641
  }
617
642
  function cmdSelfHosted(sub, opts) {
618
643
  const SELF = Path.join(__dirname, ".");
@@ -745,91 +770,94 @@ function main() {
745
770
  const { positional, opts } = parseArgs(process.argv);
746
771
  const cmd = (positional[0] ?? "help");
747
772
  if (cmd === "compile") {
748
- return cmdCompile(positional[1], opts);
773
+ cmdCompile(positional[1], opts);
749
774
  }
750
775
  else if (cmd === "run") {
751
- return cmdRun(positional[1], opts);
776
+ cmdRun(positional[1], opts);
752
777
  }
753
778
  else if (cmd === "check") {
754
- return cmdCheck(positional.slice(1), opts);
779
+ cmdCheck(positional.slice(1), opts);
755
780
  }
756
781
  else if (cmd === "fmt") {
757
- return cmdFmt(positional.slice(1), opts);
782
+ cmdFmt(positional.slice(1), opts);
758
783
  }
759
784
  else if (cmd === "format") {
760
- return cmdFmt(positional.slice(1), opts);
785
+ cmdFmt(positional.slice(1), opts);
761
786
  }
762
787
  else if (cmd === "lint") {
763
- return cmdLint(positional.slice(1), opts);
788
+ cmdLint(positional.slice(1), opts);
764
789
  }
765
790
  else if (cmd === "bundle") {
766
- return cmdBundle(positional[1], opts);
791
+ cmdBundle(positional[1], opts);
767
792
  }
768
793
  else if (cmd === "watch") {
769
- return cmdWatch(positional[1], opts);
794
+ cmdWatch(positional[1], opts);
770
795
  }
771
796
  else if (cmd === "tokens") {
772
- return cmdTokens(positional[1], opts);
797
+ cmdTokens(positional[1], opts);
773
798
  }
774
799
  else if (cmd === "ast") {
775
- return cmdAst(positional[1], opts);
800
+ cmdAst(positional[1], opts);
776
801
  }
777
802
  else if (cmd === "repl") {
778
- return cmdRepl(opts);
803
+ cmdRepl(opts);
804
+ }
805
+ else if (cmd === "test") {
806
+ runTests(positional.slice(1), opts);
779
807
  }
780
808
  else if (cmd === "init") {
781
- return cmdInit(positional[1], opts);
809
+ cmdInit(positional[1], opts);
782
810
  }
783
811
  else if (cmd === "add") {
784
- return cmdAdd(positional.slice(1), opts);
812
+ cmdAdd(positional.slice(1), opts);
785
813
  }
786
814
  else if (cmd === "remove") {
787
- return cmdRemove(positional.slice(1), opts);
815
+ cmdRemove(positional.slice(1), opts);
788
816
  }
789
817
  else if (cmd === "rm") {
790
- return cmdRemove(positional.slice(1), opts);
818
+ cmdRemove(positional.slice(1), opts);
791
819
  }
792
820
  else if (cmd === "install") {
793
- return cmdInstall(opts);
821
+ cmdInstall(opts);
794
822
  }
795
823
  else if (cmd === "i") {
796
- return cmdInstall(opts);
824
+ cmdInstall(opts);
797
825
  }
798
826
  else if (cmd === "list") {
799
- return cmdList(opts);
827
+ cmdList(opts);
800
828
  }
801
829
  else if (cmd === "ls") {
802
- return cmdList(opts);
830
+ cmdList(opts);
803
831
  }
804
832
  else if (cmd === "search") {
805
- return cmdSearch(positional[1], opts);
833
+ cmdSearch(positional[1], opts);
806
834
  }
807
835
  else if (cmd === "info") {
808
- return cmdInfo(positional[1], opts);
836
+ cmdInfo(positional[1], opts);
809
837
  }
810
838
  else if (cmd === "publish") {
811
- return cmdPublish(opts);
839
+ cmdPublish(opts);
812
840
  }
813
841
  else if (cmd === "self-hosted") {
814
- return cmdSelfHosted(positional[1], opts);
842
+ cmdSelfHosted(positional[1], opts);
815
843
  }
816
844
  else if (cmd === "version") {
817
- return cmdVersion(opts);
845
+ cmdVersion(opts);
818
846
  }
819
847
  else if (cmd === "-v") {
820
- return cmdVersion(opts);
848
+ cmdVersion(opts);
821
849
  }
822
850
  else if (cmd === "--version") {
823
- return cmdVersion(opts);
851
+ cmdVersion(opts);
824
852
  }
825
853
  else if (cmd === "help") {
826
- return showHelp();
854
+ showHelp();
827
855
  }
828
856
  else if (cmd === "--help") {
829
- return showHelp();
857
+ showHelp();
830
858
  }
831
859
  else if (cmd === "-h") {
832
- return showHelp();
860
+ showHelp();
833
861
  }
834
862
  else {
835
863
  console.error(red(("✗ Unknown command: " + cmd)));
@@ -2,10 +2,11 @@
2
2
  // Flux Self-Hosted Config Reader
3
3
  // src/self/config.flux — written in Flux, compiled by stage-0
4
4
  //
5
- // Reads flux.json project config and merges with CLI flags.
6
- // Config file locations (searched in order):
7
- // ./flux.json
8
- // ./flux.config.js
5
+ // Reads config from package.json ("flux" key) or flux.json fallback.
6
+ // Config file resolution order:
7
+ // 1. package.json → { "flux": { ... } } ← primary
8
+ // 2. flux.json ← legacy fallback
9
+ // 3. default config ← if none found
9
10
  // ============================================================
10
11
 
11
12
  import Fs from "fs"
@@ -24,7 +25,7 @@ export val DEFAULT_CONFIG = {
24
25
  watch: false,
25
26
  ignore: [],
26
27
  selfHosted: false,
27
- registry: "https://registry.flux-lang.dev",
28
+ registry: "https://registry.npmjs.org",
28
29
  pkg: {
29
30
  name: "",
30
31
  version: "1.0.0",
@@ -36,11 +37,22 @@ export val DEFAULT_CONFIG = {
36
37
  }
37
38
  }
38
39
 
39
- // ── Load flux.json ────────────────────────────────────────────
40
+ // ── Load config (package.json "flux" key first, flux.json fallback) ──
40
41
  export fn loadConfig(cwd_):
41
42
  val cwd = cwd_ ?? process.cwd()
42
43
 
43
- // Try flux.json first
44
+ // 1. Try package.json → "flux" key (primary)
45
+ val pkgJsonPath = Path.join(cwd, "package.json")
46
+ if Fs.existsSync(pkgJsonPath):
47
+ try:
48
+ val raw = Fs.readFileSync(pkgJsonPath, "utf8")
49
+ val parsed = JSON.parse(raw)
50
+ if parsed.flux and typeof parsed.flux == "object":
51
+ return mergeConfig(DEFAULT_CONFIG, parsed.flux)
52
+ catch(e):
53
+ throw new Error("Invalid package.json: " + e.message)
54
+
55
+ // 2. Try flux.json (legacy fallback)
44
56
  val jsonPath = Path.join(cwd, "flux.json")
45
57
  if Fs.existsSync(jsonPath):
46
58
  try:
@@ -50,15 +62,7 @@ export fn loadConfig(cwd_):
50
62
  catch(e):
51
63
  throw new Error("Invalid flux.json: " + e.message)
52
64
 
53
- // Try flux.config.js
54
- val jsPath = Path.join(cwd, "flux.config.js")
55
- if Fs.existsSync(jsPath):
56
- try:
57
- val loaded = require(jsPath)
58
- return mergeConfig(DEFAULT_CONFIG, loaded)
59
- catch(e2):
60
- throw new Error("Invalid flux.config.js: " + e2.message)
61
-
65
+ // 3. Default config
62
66
  return { ...DEFAULT_CONFIG }
63
67
 
64
68
  // ── Deep merge config objects ─────────────────────────────────
@@ -71,12 +75,16 @@ export fn mergeConfig(base, overrides):
71
75
  result[key] = val_
72
76
  return result
73
77
 
74
- // ── Write flux.json ───────────────────────────────────────────
78
+ // ── Write flux key into package.json ─────────────────────────
75
79
  export fn writeConfig(config, cwd_):
76
- val cwd = cwd_ ?? process.cwd()
77
- val jsonPath = Path.join(cwd, "flux.json")
78
- val content = JSON.stringify(config, null, 2) + "\n"
79
- Fs.writeFileSync(jsonPath, content, "utf8")
80
+ val cwd = cwd_ ?? process.cwd()
81
+ val pkgPath = Path.join(cwd, "package.json")
82
+ var pkg = {}
83
+ if Fs.existsSync(pkgPath):
84
+ try: pkg = JSON.parse(Fs.readFileSync(pkgPath, "utf8"))
85
+ catch(e): pkg = {}
86
+ pkg.flux = config
87
+ Fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8")
80
88
 
81
89
  // ── Validate config ───────────────────────────────────────────
82
90
  export fn validateConfig(config):
@@ -93,20 +101,20 @@ export fn validateConfig(config):
93
101
  errors: errors,
94
102
  }
95
103
 
96
- // ── Read flux.json package info ───────────────────────────────
104
+ // ── Read package info (package.json first, flux.json fallback) ────
97
105
  export fn readPackage(cwd_):
98
- val cwd = cwd_ ?? process.cwd()
106
+ val cwd = cwd_ ?? process.cwd()
107
+ val pkgJson = Path.join(cwd, "package.json")
99
108
  val fluxJson = Path.join(cwd, "flux.json")
100
- val pkgJson = Path.join(cwd, "package.json")
101
109
 
102
- if Fs.existsSync(fluxJson):
110
+ if Fs.existsSync(pkgJson):
103
111
  try:
104
- return JSON.parse(Fs.readFileSync(fluxJson, "utf8"))
112
+ return JSON.parse(Fs.readFileSync(pkgJson, "utf8"))
105
113
  catch(e): return null
106
114
 
107
- if Fs.existsSync(pkgJson):
115
+ if Fs.existsSync(fluxJson):
108
116
  try:
109
- return JSON.parse(Fs.readFileSync(pkgJson, "utf8"))
117
+ return JSON.parse(Fs.readFileSync(fluxJson, "utf8"))
110
118
  catch(e2): return null
111
119
 
112
120
  return null