@xnoxs/flux-lang 4.0.2 → 4.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js CHANGED
@@ -3,17 +3,17 @@
3
3
  /**
4
4
  * Flux Lang — Public API
5
5
  *
6
- * const { transpile } = require('flux-lang');
7
- * import { transpile, format, buildStdlib } from 'flux-lang';
6
+ * const { transpile } = require('@xnoxs/flux-lang');
7
+ * import { transpile, format, buildStdlib } from '@xnoxs/flux-lang';
8
8
  */
9
9
 
10
- const { transpile } = require('./src/transpiler');
11
- const { format } = require('./src/formatter');
12
- const { buildStdlib, detectUsedSymbols, STDLIB_SYMBOLS } = require('./src/stdlib');
13
- const { Lexer } = require('./src/lexer');
14
- const { Parser } = require('./src/parser');
15
- const { bundle } = require('./src/bundler');
16
- const { loadConfig, mergeConfig, defineConfig } = require('./src/config');
10
+ const { transpile } = require('./src/self/transpiler');
11
+ const { format } = require('./src/self/formatter');
12
+ const { buildStdlib, detectUsedSymbols, STDLIB_SYMBOLS } = require('./src/self/stdlib');
13
+ const { Lexer } = require('./src/self/lexer');
14
+ const { Parser } = require('./src/self/parser');
15
+ const { bundle } = require('./src/self/bundler');
16
+ const { loadConfig, mergeConfig } = require('./src/self/config');
17
17
 
18
18
  module.exports = {
19
19
  transpile,
@@ -26,5 +26,4 @@ module.exports = {
26
26
  bundle,
27
27
  loadConfig,
28
28
  mergeConfig,
29
- defineConfig,
30
29
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xnoxs/flux-lang",
3
- "version": "4.0.2",
3
+ "version": "4.0.4",
4
4
  "description": "Flux — A modern language that transpiles to JavaScript. Python-clean syntax, TypeScript-level safety, Rust-inspired pattern matching.",
5
5
  "main": "dist/flux.cjs.js",
6
6
  "module": "dist/flux.esm.js",
@@ -9,9 +9,8 @@
9
9
  "flux": "./bin/flux.js"
10
10
  },
11
11
  "scripts": {
12
- "build": "node scripts/build.js",
13
- "prepublishOnly": "npm test && npm run build",
14
- "test": "node src/cli.js test tests/",
12
+ "build": "node -e \"console.log(\"build skipped\")\"",
13
+ "test": "node src/self/cli.js test tests/",
15
14
  "test:file": "node src/cli.js test",
16
15
  "check": "node src/cli.js check tests/01_basics.test.flux",
17
16
  "bench": "node benchmarks/bench.js",
@@ -84,4 +83,4 @@
84
83
  "devDependencies": {
85
84
  "esbuild": "^0.28.1"
86
85
  }
87
- }
86
+ }
@@ -124,11 +124,10 @@ export class Bundler:
124
124
 
125
125
  for imp in mod.imports:
126
126
  val srcId = toModuleId(imp.absPath)
127
- if imp.names.length == 1:
128
- lines.push(' var ' + imp.names[0] + ' = ' + srcId + ';')
129
- else:
130
- for name in imp.names:
131
- lines.push(' var ' + name + ' = ' + srcId + '._exports ? ' + srcId + '._exports.' + name + ' : ' + srcId + '.' + name + ';')
127
+ for name in imp.names:
128
+ val localName = name.alias ?? name.name
129
+ val importedName = name.name
130
+ lines.push(' var ' + localName + ' = ' + srcId + '._exports ? ' + srcId + '._exports.' + importedName + ' : ' + srcId + '.' + importedName + ';')
132
131
 
133
132
  if mod.imports.length > 0: lines.push('')
134
133
 
@@ -1,3 +1,5 @@
1
+ /* compiled from src/self/bundler.flux by Flux Lang */
2
+ 'use strict';
1
3
  // ── Flux stdlib ──
2
4
 
3
5
  function findIndex(arr, fn) { return arr.findIndex(fn); }
@@ -29,17 +31,25 @@ function toModuleId(absPath) {
29
31
  }
30
32
  function extractModuleInfo(ast, fromFile) {
31
33
  const imports = [];
34
+ const npmImports = [];
32
35
  const exports = [];
33
36
  const body = [];
34
37
  const dir = Path.dirname(fromFile);
35
38
  for (const node of ast.body) {
36
39
  if ((node.type == "ImportDecl")) {
37
- let src = node.source;
38
- if (!src.endsWith(".flux")) {
39
- src = (src + ".flux");
40
+ const src = node.source;
41
+ if ((src.startsWith("./") || src.startsWith("../"))) {
42
+ let resolved = src;
43
+ if (!resolved.endsWith(".flux")) {
44
+ resolved = (resolved + ".flux");
45
+ }
46
+ const absPath = Path.resolve(dir, resolved);
47
+ imports.push({ names: node.names, source: node.source, absPath });
48
+ }
49
+ else {
50
+ npmImports.push({ names: node.names, source: src });
51
+ body.push(node);
40
52
  }
41
- const absPath = Path.resolve(dir, src);
42
- imports.push({ names: node.names, source: node.source, absPath });
43
53
  }
44
54
  else if ((node.type == "ExportDecl")) {
45
55
  const inner = node.decl;
@@ -58,7 +68,7 @@ function extractModuleInfo(ast, fromFile) {
58
68
  body.push(node);
59
69
  }
60
70
  }
61
- return { cleanAst: { type: "Program", body }, imports, exports };
71
+ return { cleanAst: { type: "Program", body }, imports, npmImports, exports };
62
72
  }
63
73
  function codegenModule(ast) {
64
74
  const cg = makeCodeGen({ indent: " " });
@@ -122,9 +132,6 @@ class Bundler {
122
132
  lines.push("\"use strict\";");
123
133
  lines.push("");
124
134
  }
125
- lines.push("// flux_modules resolver");
126
- lines.push(`(function()${var _p=require('path'),_fs=require('fs'),_M=require('module'),_d=_p.join(process.cwd(),'flux_modules','node_modules');if(_fs.existsSync(_d)){var _o=_M._resolveFilename.bind(_M);_M._resolveFilename=function(r,p,m,op){if(!r.startsWith('.')&&!r.startsWith('/')&&!r.startsWith('node:')){var _fp=_p.join(_d,r.split('/')[0]);if(_fs.existsSync(_fp)){try{return _o(_p.join(_d,r),p,m,op);}catch(_e){}}}return _o(r,p,m,op);};}})();`);
127
- lines.push("");
128
135
  lines.push("(function() {");
129
136
  lines.push("");
130
137
  for (const absPath of this.order) {
@@ -142,13 +149,10 @@ class Bundler {
142
149
  }
143
150
  for (const imp of mod.imports) {
144
151
  const srcId = toModuleId(imp.absPath);
145
- if ((imp.names.length == 1)) {
146
- lines.push(((((" var " + imp.names[0]) + " = ") + srcId) + ";"));
147
- }
148
- else {
149
- for (const name of imp.names) {
150
- lines.push(((((((((((((" var " + name) + " = ") + srcId) + "._exports ? ") + srcId) + "._exports.") + name) + " : ") + srcId) + ".") + name) + ";"));
151
- }
152
+ for (const name of imp.names) {
153
+ const localName = (name.alias ?? name.name);
154
+ const importedName = name.name;
155
+ lines.push(((((((((((((" var " + localName) + " = ") + srcId) + "._exports ? ") + srcId) + "._exports.") + importedName) + " : ") + srcId) + ".") + importedName) + ";"));
152
156
  }
153
157
  }
154
158
  if ((mod.imports.length > 0)) {
package/src/self/cli.js CHANGED
@@ -1,10 +1,8 @@
1
+ /* compiled from src/self/cli.flux by Flux Lang */
2
+ 'use strict';
1
3
  // ── Flux stdlib ──
2
4
 
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); }
5
+ function find(arr, fn) { return arr.find(fn); }
8
6
 
9
7
  function some(arr, fn) { return arr.some(fn); }
10
8
 
@@ -14,10 +12,6 @@ function clamp(val, min, max) {
14
12
  return Math.min(Math.max(val, min), max);
15
13
  }
16
14
 
17
- function sum(arr) {
18
- return arr.reduce(function(a, b) { return a + b; }, 0);
19
- }
20
-
21
15
  function max(arr) {
22
16
  if (arguments.length > 1) return Math.max.apply(null, arguments);
23
17
  return Math.max.apply(null, arr);
@@ -96,7 +90,7 @@ function showHelp() {
96
90
  console.log(bold("USAGE:"));
97
91
  console.log(" flux <command> [options]\n");
98
92
  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"]];
93
+ 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"]];
100
94
  for (const __item__ of compilerCmds) {
101
95
  const [cmd, desc] = __item__;
102
96
  console.log((((" " + green(("flux " + cmd).padEnd(36))) + " ") + gray(desc)));
@@ -110,7 +104,7 @@ function showHelp() {
110
104
  }
111
105
  console.log();
112
106
  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"]];
107
+ 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"]];
114
108
  for (const __item__ of pkgCmds) {
115
109
  const [cmd, desc] = __item__;
116
110
  console.log((((" " + cyan(("flux " + cmd).padEnd(36))) + " ") + gray(desc)));
@@ -131,11 +125,12 @@ function showHelp() {
131
125
  console.log(((" " + yellow("--typecheck ")) + " Enable type checking"));
132
126
  console.log(((" " + yellow("--stdout ")) + " Print to terminal"));
133
127
  console.log(((" " + yellow("--no-color ")) + " Disable colors"));
128
+ console.log(((" " + yellow("--template ")) + " Init template: script, server, webapp"));
134
129
  console.log();
135
130
  }
136
131
  function parseArgs(argv) {
137
132
  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" };
133
+ 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
134
  const positional = [];
140
135
  let i = 0;
141
136
  while ((i < args.length)) {
@@ -175,6 +170,10 @@ function parseArgs(argv) {
175
170
  i = (i + 1);
176
171
  opts.jsxTarget = args[i];
177
172
  }
173
+ else if (((a == "--template") || (a == "-t"))) {
174
+ i = (i + 1);
175
+ opts.template = args[i];
176
+ }
178
177
  else if (!a.startsWith("--")) {
179
178
  positional.push(a);
180
179
  }
@@ -243,6 +242,21 @@ function printErrors(errors, source, filePath) {
243
242
  }
244
243
  console.error();
245
244
  }
245
+ function findFluxImports(source) {
246
+ const found = [];
247
+ const lines = source.split("\n");
248
+ for (const line of lines) {
249
+ const trimmed = line.trim();
250
+ const m = trimmed.match(/^import\s+.+\s+from\s+["'](\.[^"']+)["']/);
251
+ if (m) {
252
+ const src = m[1];
253
+ if (((!src.endsWith(".js") && !src.endsWith(".json")) && !src.endsWith(".node"))) {
254
+ found.push(src);
255
+ }
256
+ }
257
+ }
258
+ return found;
259
+ }
246
260
  function cmdCompile(filePath, opts) {
247
261
  const { source, abs } = readFluxFile(filePath);
248
262
  const cfg = loadConfig(Path.dirname(abs));
@@ -276,6 +290,21 @@ function cmdCompile(filePath, opts) {
276
290
  }
277
291
  function cmdRun(filePath, opts) {
278
292
  const { source, abs } = readFluxFile(filePath);
293
+ const fluxImports = findFluxImports(source);
294
+ if ((fluxImports.length > 0)) {
295
+ const example = fluxImports[0];
296
+ const entryRel = Path.relative(process.cwd(), abs);
297
+ const outFile = (Path.basename(abs, ".flux") + ".js");
298
+ console.error(red("\n✗ Cannot run a multi-file Flux project with `flux run`.\n"));
299
+ console.error((" Found: " + yellow((("import ... from \"" + example) + "\""))));
300
+ console.error();
301
+ console.error(((" Use " + cyan("flux bundle")) + " to compile all files into one, then run it:\n"));
302
+ console.error((" " + yellow(((("flux bundle " + entryRel) + " -o dist/") + outFile))));
303
+ console.error((" " + yellow(("node dist/" + outFile))));
304
+ console.error();
305
+ console.error(((" Or use " + cyan("flux run")) + " only for single-file scripts (no inter-file .flux imports).\n"));
306
+ process.exit(1);
307
+ }
279
308
  const result = transpile(source, { jsx: (opts.jsx ?? false), jsxTarget: (opts.jsxTarget ?? "browser"), mangle: false });
280
309
  if (!result.success) {
281
310
  console.error(red("\n✗ Compile error"));
@@ -443,6 +472,10 @@ function cmdBundle(entryPath, opts) {
443
472
  console.log(result.code);
444
473
  return;
445
474
  }
475
+ const outDir = Path.dirname(Path.resolve(outFile));
476
+ if (!Fs.existsSync(outDir)) {
477
+ Fs.mkdirSync(outDir, { recursive: true });
478
+ }
446
479
  Fs.writeFileSync(outFile, result.code, "utf8");
447
480
  const kb = (result.code.length / 1024).toFixed(1);
448
481
  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 +606,7 @@ function cmdRepl(opts) {
573
606
  }
574
607
  function cmdInit(name, opts) {
575
608
  const projectName = (name ?? "my-flux-app");
609
+ const template = (opts.template ?? "script");
576
610
  const dir = Path.resolve(projectName);
577
611
  if (Fs.existsSync(dir)) {
578
612
  console.error(red(("✗ Directory already exists: " + projectName)));
@@ -581,38 +615,77 @@ function cmdInit(name, opts) {
581
615
  Fs.mkdirSync(dir, { recursive: true });
582
616
  Fs.mkdirSync(Path.join(dir, "src"), { recursive: true });
583
617
  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)));
618
+ const gitignore = "node_modules/\ndist/\n*.js.map\n.DS_Store\n";
619
+ 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\")";
620
+ if ((template == "script")) {
621
+ 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()";
622
+ 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: { } };
623
+ Fs.writeFileSync(Path.join(dir, "src", "main.flux"), mainFlux, "utf8");
624
+ Fs.writeFileSync(Path.join(dir, "tests", "main.test.flux"), testFlux, "utf8");
625
+ Fs.writeFileSync(Path.join(dir, "package.json"), (JSON.stringify(pkgJson, null, 2) + "\n"), "utf8");
626
+ Fs.writeFileSync(Path.join(dir, ".gitignore"), gitignore, "utf8");
627
+ console.log();
628
+ console.log((((green("✓ Project ") + bold((projectName + "/"))) + green(" created with template ")) + cyan("script")));
629
+ console.log();
630
+ console.log(bold(" Next steps:"));
631
+ console.log((" " + yellow(("cd " + projectName))));
632
+ console.log((" " + yellow("npm install")));
633
+ console.log((" " + yellow("npm run dev")));
634
+ console.log();
635
+ console.log(gray(" Add packages:"));
636
+ console.log((" " + yellow("flux add <package>")));
637
+ console.log();
638
+ return;
603
639
  }
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();
640
+ if ((template == "server")) {
641
+ 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}\"))";
642
+ 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: { } };
643
+ Fs.writeFileSync(Path.join(dir, "src", "server.flux"), serverFlux, "utf8");
644
+ Fs.writeFileSync(Path.join(dir, "tests", "main.test.flux"), testFlux, "utf8");
645
+ Fs.writeFileSync(Path.join(dir, "package.json"), (JSON.stringify(pkgJson, null, 2) + "\n"), "utf8");
646
+ Fs.writeFileSync(Path.join(dir, ".gitignore"), gitignore, "utf8");
647
+ console.log();
648
+ console.log((((green("✓ Project ") + bold((projectName + "/"))) + green(" created with template ")) + cyan("server")));
649
+ console.log();
650
+ console.log(bold(" Next steps:"));
651
+ console.log((" " + yellow(("cd " + projectName))));
652
+ console.log((" " + yellow("npm install")));
653
+ console.log((" " + yellow("npm run dev")));
654
+ console.log();
655
+ console.log(gray(" Build for production:"));
656
+ console.log((" " + yellow("npm run build && npm start")));
657
+ console.log();
658
+ return;
659
+ }
660
+ if ((template == "webapp")) {
661
+ 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)";
662
+ 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 )";
663
+ 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}\"))";
664
+ 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: { } };
665
+ Fs.writeFileSync(Path.join(dir, "src", "utils.flux"), utilsFlux, "utf8");
666
+ Fs.writeFileSync(Path.join(dir, "src", "routes.flux"), routesFlux, "utf8");
667
+ Fs.writeFileSync(Path.join(dir, "src", "server.flux"), serverFlux, "utf8");
668
+ Fs.writeFileSync(Path.join(dir, "tests", "main.test.flux"), testFlux, "utf8");
669
+ Fs.writeFileSync(Path.join(dir, "package.json"), (JSON.stringify(pkgJson, null, 2) + "\n"), "utf8");
670
+ Fs.writeFileSync(Path.join(dir, ".gitignore"), gitignore, "utf8");
671
+ console.log();
672
+ console.log((((green("✓ Project ") + bold((projectName + "/"))) + green(" created with template ")) + cyan("webapp")));
673
+ console.log();
674
+ console.log(bold(" Next steps:"));
675
+ console.log((" " + yellow(("cd " + projectName))));
676
+ console.log(((" " + yellow("npm install")) + gray(" # install dependencies")));
677
+ console.log(((" " + yellow("npm run build")) + gray(" # compile all .flux → dist/server.js")));
678
+ console.log(((" " + yellow("npm start")) + gray(" # run the server")));
679
+ console.log();
680
+ console.log((yellow(" Note: ") + gray("This project uses multiple .flux files.")));
681
+ console.log(((gray(" Always build with ") + yellow("npm run build")) + gray(" (flux bundle) before running.")));
682
+ console.log(((gray(" Do NOT use ") + red("flux run")) + gray(" on files that import other .flux files.")));
683
+ console.log();
684
+ return;
685
+ }
686
+ console.error(red(("✗ Unknown template: " + template)));
687
+ console.error(gray(" Available templates: script, server, webapp"));
688
+ process.exit(1);
616
689
  }
617
690
  function cmdSelfHosted(sub, opts) {
618
691
  const SELF = Path.join(__dirname, ".");
@@ -54,6 +54,7 @@ export class CodeGenerator:
54
54
  smBuilder: any
55
55
  _needsFmt: bool
56
56
  _loopDepth: int
57
+ _fnDepth: int
57
58
  _version: string
58
59
 
59
60
  fn i():
@@ -166,7 +167,9 @@ export class CodeGenerator:
166
167
  else:
167
168
  self.emit(prefix + asyncKw + 'function ' + name + '(' + params + ') {')
168
169
  self.indIn()
170
+ self._fnDepth = self._fnDepth + 1
169
171
  for s in node.body: self.genStmt(s)
172
+ self._fnDepth = self._fnDepth - 1
170
173
  self.indOut()
171
174
  self.emit('}')
172
175
 
@@ -200,7 +203,9 @@ export class CodeGenerator:
200
203
  else:
201
204
  self.emit(staticKw + asyncKw + m.name + '(' + params + ') {')
202
205
  self.indIn()
206
+ self._fnDepth = self._fnDepth + 1
203
207
  for s in m.body: self.genStmt(s)
208
+ self._fnDepth = self._fnDepth - 1
204
209
  self.indOut()
205
210
  self.emit('}')
206
211
  self.blank()
@@ -304,8 +309,10 @@ export class CodeGenerator:
304
309
  val exprSrc = self.genExpr(arm.body[0].expr)
305
310
  if self._loopDepth > 0:
306
311
  self.emit(exprSrc + ';')
307
- else:
312
+ else if self._fnDepth > 0:
308
313
  self.emit('return ' + exprSrc + ';')
314
+ else:
315
+ self.emit(exprSrc + ';')
309
316
  else:
310
317
  for s in arm.body: self.genStmt(s)
311
318
  self.indOut()
@@ -492,11 +499,13 @@ export class CodeGenerator:
492
499
  val savedLevel = self.level
493
500
  val savedLoop = self._loopDepth
494
501
  self._loopDepth = 0
502
+ self._fnDepth = self._fnDepth + 1
495
503
  self.emit('(() => {')
496
504
  self.indIn()
497
505
  self.genMatch(node)
498
506
  self.indOut()
499
507
  self.emit('})()')
508
+ self._fnDepth = self._fnDepth - 1
500
509
  self._loopDepth = savedLoop
501
510
  val block = self.lines.splice(saved).map(l -> l.trimStart()).join(' ')
502
511
  self.level = savedLevel