@xnoxs/flux-lang 4.0.3 → 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.3",
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
+ }
@@ -1 +1,202 @@
1
- function findIndex(arr, fn) { return arr.findIndex(fn); } function join(arr, sep) { return arr.join(sep != null ? sep : ','); } function includes(arr, val) { return arr.includes(val); } function trim(s) { return String(s).trim(); } function startsWith(s, prefix) { return String(s).startsWith(prefix); } function endsWith(s, suffix) { return String(s).endsWith(suffix); } function repeat(s, n) { return String(s).repeat(n); } "use strict"; const Fs = require("fs"); const Path = require("path"); const { lexerize } = require("./lexer"); const { makeParser } = require("./parser"); const { makeCodeGen } = require("./codegen"); function _a(_d) { const base = Path.basename(_d, ".flux"); return ("_flux_" + base.replace(/[^a-zA-Z0-9]/g, "_")); } function _b(_e, _f) { const imports = []; const exports = []; const body = []; const dir = Path.dirname(_f); for (const _g of _e.body) { if ((_g.type == "ImportDecl")) { let src = _g.source; if (!src.endsWith(".flux")) { src = (src + ".flux"); } const _d = Path.resolve(dir, src); imports.push({ names: _g.names, source: _g.source, absPath: _d }); } else if ((_g.type == "ExportDecl")) { const inner = _g.decl; if ((inner.type == "FnDecl")) { exports.push(inner.name); } if ((inner.type == "ClassDecl")) { exports.push(inner.name); } if ((inner.type == "VarDecl")) { exports.push(inner.name); } body.push(inner); } else { body.push(_g); } } return { cleanAst: { type: "Program", body }, imports, exports }; } function _c(_e) { const cg = makeCodeGen({ indent: " " }); const result = cg.generate(_e); const lines = result.code.split("\n"); const start = lines.findIndex((_h) => (((_h.trim() != "") && !_h.includes("Generated by Flux")) && !_h.includes("\"use strict\""))); return lines.slice(start).join("\n"); } class Bundler { constructor(entryFile, options, modules, order, visited, inStack) { this.entryFile = entryFile; this.options = options; this.modules = modules; this.order = order; this.visited = visited; this.inStack = inStack; } bundle() { this.collect(this.entryFile); return this.link(); } collect(_d) { if (this.visited.has(_d)) { return; } if (this.inStack.has(_d)) { throw new Error(((("[" + Path.basename(_d)) + "] Circular dependency detected: ") + _d)); } if (!Fs.existsSync(_d)) { throw new Error(((("[" + Path.basename(_d)) + "] File not found: ") + _d)); } this.inStack.add(_d); const source = Fs.readFileSync(_d, "utf8"); let _e = null; try { const tokens = lexerize(source).tokenize(); _e = makeParser(tokens).parse(); } catch (_i) { throw new Error(((("[" + Path.basename(_d)) + "] Parse error: ") + _i.message)); } const info = _b(_e, _d); this.modules.set(_d, { cleanAst: info.cleanAst, imports: info.imports, exports: info.exports, source, absPath: _d }); for (const _j of info.imports) { this.collect(_j.absPath); } this.inStack.delete(_d); this.visited.add(_d); this.order.push(_d); } link() { const lines = []; const banner = (this.options.banner != false); if (banner) { lines.push("// Generated by Flux Bundler (self-hosted)"); lines.push(("// Entry: " + Path.basename(this.entryFile))); lines.push(("// Modules: " + this.order.length)); lines.push("\"use strict\";"); lines.push(""); } lines.push("// flux_modules resolver"); 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('_g:')){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);};}})();`); lines.push(""); lines.push("(function() {"); lines.push(""); for (const _d of this.order) { const mod = this.modules.get(_d); const isEntry = (_d == this.entryFile); const modId = _a(_d); const relName = Path.relative(process.cwd(), _d); lines.push((" // " + "─".repeat(60))); lines.push((" // Module: " + relName)); lines.push((" // " + "─".repeat(60))); if (!isEntry) { lines.push(((" var " + modId) + " = (function() {")); lines.push(" var _exports = {};"); lines.push(""); } for (const _j of mod.imports) { const srcId = _a(_j.absPath); for (const _k of _j.names) { const localName = (_k.alias ?? _k.name); const importedName = _k.name; lines.push(((((((((((((" var " + localName) + " = ") + srcId) + "._exports ? ") + srcId) + "._exports.") + importedName) + " : ") + srcId) + ".") + importedName) + ";")); } } if ((mod.imports.length > 0)) { lines.push(""); } const moduleCode = _c(mod.cleanAst); lines.push(moduleCode); if (!isEntry) { if ((mod.exports.length > 0)) { lines.push(""); for (const _l of mod.exports) { lines.push(((((" _exports." + _l) + " = ") + _l) + ";")); } } lines.push(""); lines.push(" return _exports;"); lines.push(" })()"); lines.push(((((" " + modId) + "._exports = ") + modId) + ";")); } lines.push(""); } lines.push("})();"); return { code: lines.join("\n"), modules: this.order.length }; } } module.exports.Bundler = Bundler; function makeBundler(_m, _n) { return new Bundler(Path.resolve(_m), (_n ?? { }), new Map(), [], new Set(), new Set()); } module.exports.makeBundler = makeBundler; function bundle(_m, _n) { const result = { success: false, code: "", modules: 0, errors: [] }; try { const b = makeBundler(_m, _n); const out = b.bundle(); result.code = out.code; result.modules = out.modules; result.success = true; } catch (_i) { result.errors.push({ message: _i.message, name: _i.name, file: null }); } return result; } module.exports.bundle = bundle;
1
+ /* compiled from src/self/bundler.flux by Flux Lang */
2
+ 'use strict';
3
+ // ── Flux stdlib ──
4
+
5
+ function findIndex(arr, fn) { return arr.findIndex(fn); }
6
+
7
+ function join(arr, sep) { return arr.join(sep != null ? sep : ','); }
8
+
9
+ function includes(arr, val) { return arr.includes(val); }
10
+
11
+ function trim(s) { return String(s).trim(); }
12
+
13
+ function startsWith(s, prefix) { return String(s).startsWith(prefix); }
14
+
15
+ function endsWith(s, suffix) { return String(s).endsWith(suffix); }
16
+
17
+ function repeat(s, n) { return String(s).repeat(n); }
18
+ // ── end stdlib ──
19
+
20
+ // Generated by Flux Transpiler v3.2.0
21
+ "use strict";
22
+
23
+ const Fs = require("fs");
24
+ const Path = require("path");
25
+ const { lexerize } = require("./lexer");
26
+ const { makeParser } = require("./parser");
27
+ const { makeCodeGen } = require("./codegen");
28
+ function toModuleId(absPath) {
29
+ const base = Path.basename(absPath, ".flux");
30
+ return ("_flux_" + base.replace(/[^a-zA-Z0-9]/g, "_"));
31
+ }
32
+ function extractModuleInfo(ast, fromFile) {
33
+ const imports = [];
34
+ const npmImports = [];
35
+ const exports = [];
36
+ const body = [];
37
+ const dir = Path.dirname(fromFile);
38
+ for (const node of ast.body) {
39
+ if ((node.type == "ImportDecl")) {
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);
52
+ }
53
+ }
54
+ else if ((node.type == "ExportDecl")) {
55
+ const inner = node.decl;
56
+ if ((inner.type == "FnDecl")) {
57
+ exports.push(inner.name);
58
+ }
59
+ if ((inner.type == "ClassDecl")) {
60
+ exports.push(inner.name);
61
+ }
62
+ if ((inner.type == "VarDecl")) {
63
+ exports.push(inner.name);
64
+ }
65
+ body.push(inner);
66
+ }
67
+ else {
68
+ body.push(node);
69
+ }
70
+ }
71
+ return { cleanAst: { type: "Program", body }, imports, npmImports, exports };
72
+ }
73
+ function codegenModule(ast) {
74
+ const cg = makeCodeGen({ indent: " " });
75
+ const result = cg.generate(ast);
76
+ const lines = result.code.split("\n");
77
+ const start = lines.findIndex((l) => (((l.trim() != "") && !l.includes("Generated by Flux")) && !l.includes("\"use strict\"")));
78
+ return lines.slice(start).join("\n");
79
+ }
80
+ class Bundler {
81
+ constructor(entryFile, options, modules, order, visited, inStack) {
82
+ this.entryFile = entryFile;
83
+ this.options = options;
84
+ this.modules = modules;
85
+ this.order = order;
86
+ this.visited = visited;
87
+ this.inStack = inStack;
88
+ }
89
+
90
+ bundle() {
91
+ this.collect(this.entryFile);
92
+ return this.link();
93
+ }
94
+
95
+ collect(absPath) {
96
+ if (this.visited.has(absPath)) {
97
+ return;
98
+ }
99
+ if (this.inStack.has(absPath)) {
100
+ throw new Error(((("[" + Path.basename(absPath)) + "] Circular dependency detected: ") + absPath));
101
+ }
102
+ if (!Fs.existsSync(absPath)) {
103
+ throw new Error(((("[" + Path.basename(absPath)) + "] File not found: ") + absPath));
104
+ }
105
+ this.inStack.add(absPath);
106
+ const source = Fs.readFileSync(absPath, "utf8");
107
+ let ast = null;
108
+ try {
109
+ const tokens = lexerize(source).tokenize();
110
+ ast = makeParser(tokens).parse();
111
+ }
112
+ catch (e) {
113
+ throw new Error(((("[" + Path.basename(absPath)) + "] Parse error: ") + e.message));
114
+ }
115
+ const info = extractModuleInfo(ast, absPath);
116
+ this.modules.set(absPath, { cleanAst: info.cleanAst, imports: info.imports, exports: info.exports, source, absPath });
117
+ for (const imp of info.imports) {
118
+ this.collect(imp.absPath);
119
+ }
120
+ this.inStack.delete(absPath);
121
+ this.visited.add(absPath);
122
+ this.order.push(absPath);
123
+ }
124
+
125
+ link() {
126
+ const lines = [];
127
+ const banner = (this.options.banner != false);
128
+ if (banner) {
129
+ lines.push("// Generated by Flux Bundler (self-hosted)");
130
+ lines.push(("// Entry: " + Path.basename(this.entryFile)));
131
+ lines.push(("// Modules: " + this.order.length));
132
+ lines.push("\"use strict\";");
133
+ lines.push("");
134
+ }
135
+ lines.push("(function() {");
136
+ lines.push("");
137
+ for (const absPath of this.order) {
138
+ const mod = this.modules.get(absPath);
139
+ const isEntry = (absPath == this.entryFile);
140
+ const modId = toModuleId(absPath);
141
+ const relName = Path.relative(process.cwd(), absPath);
142
+ lines.push((" // " + "─".repeat(60)));
143
+ lines.push((" // Module: " + relName));
144
+ lines.push((" // " + "─".repeat(60)));
145
+ if (!isEntry) {
146
+ lines.push(((" var " + modId) + " = (function() {"));
147
+ lines.push(" var _exports = {};");
148
+ lines.push("");
149
+ }
150
+ for (const imp of mod.imports) {
151
+ const srcId = toModuleId(imp.absPath);
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) + ";"));
156
+ }
157
+ }
158
+ if ((mod.imports.length > 0)) {
159
+ lines.push("");
160
+ }
161
+ const moduleCode = codegenModule(mod.cleanAst);
162
+ lines.push(moduleCode);
163
+ if (!isEntry) {
164
+ if ((mod.exports.length > 0)) {
165
+ lines.push("");
166
+ for (const expName of mod.exports) {
167
+ lines.push(((((" _exports." + expName) + " = ") + expName) + ";"));
168
+ }
169
+ }
170
+ lines.push("");
171
+ lines.push(" return _exports;");
172
+ lines.push(" })()");
173
+ lines.push(((((" " + modId) + "._exports = ") + modId) + ";"));
174
+ }
175
+ lines.push("");
176
+ }
177
+ lines.push("})();");
178
+ return { code: lines.join("\n"), modules: this.order.length };
179
+ }
180
+
181
+ }
182
+
183
+ module.exports.Bundler = Bundler;
184
+ function makeBundler(entryFile, options) {
185
+ return new Bundler(Path.resolve(entryFile), (options ?? { }), new Map(), [], new Set(), new Set());
186
+ }
187
+ module.exports.makeBundler = makeBundler;
188
+ function bundle(entryFile, options) {
189
+ const result = { success: false, code: "", modules: 0, errors: [] };
190
+ try {
191
+ const b = makeBundler(entryFile, options);
192
+ const out = b.bundle();
193
+ result.code = out.code;
194
+ result.modules = out.modules;
195
+ result.success = true;
196
+ }
197
+ catch (e) {
198
+ result.errors.push({ message: e.message, name: e.name, file: null });
199
+ }
200
+ return result;
201
+ }
202
+ module.exports.bundle = bundle;
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, ".");
@@ -1,3 +1,5 @@
1
+ /* compiled from src/self/config.flux by Flux Lang */
2
+ 'use strict';
1
3
  // ── Flux stdlib ──
2
4
 
3
5
  function join(arr, sep) { return arr.join(sep != null ? sep : ','); }
@@ -14,10 +16,23 @@ function endsWith(s, suffix) { return String(s).endsWith(suffix); }
14
16
 
15
17
  const Fs = require("fs");
16
18
  const Path = require("path");
17
- const DEFAULT_CONFIG = { entry: "src/main.flux", outDir: "dist", sourcemap: false, mangle: false, jsx: false, jsxTarget: "browser", typecheck: true, strict: false, watch: false, ignore: [], selfHosted: false, registry: "https://registry.flux-lang.dev", pkg: { name: "", version: "1.0.0", description: "", author: "", license: "MIT", deps: { }, devDeps: { } } };
19
+ const DEFAULT_CONFIG = { entry: "src/main.flux", outDir: "dist", sourcemap: false, mangle: false, jsx: false, jsxTarget: "browser", typecheck: true, strict: false, watch: false, ignore: [], selfHosted: false, registry: "https://registry.npmjs.org", pkg: { name: "", version: "1.0.0", description: "", author: "", license: "MIT", deps: { }, devDeps: { } } };
18
20
  module.exports.DEFAULT_CONFIG = DEFAULT_CONFIG;
19
21
  function loadConfig(cwd_) {
20
22
  const cwd = (cwd_ ?? process.cwd());
23
+ const pkgJsonPath = Path.join(cwd, "package.json");
24
+ if (Fs.existsSync(pkgJsonPath)) {
25
+ try {
26
+ const raw = Fs.readFileSync(pkgJsonPath, "utf8");
27
+ const parsed = JSON.parse(raw);
28
+ if ((parsed.flux && (typeof parsed.flux == "object"))) {
29
+ return mergeConfig(DEFAULT_CONFIG, parsed.flux);
30
+ }
31
+ }
32
+ catch (e) {
33
+ throw new Error(("Invalid package.json: " + e.message));
34
+ }
35
+ }
21
36
  const jsonPath = Path.join(cwd, "flux.json");
22
37
  if (Fs.existsSync(jsonPath)) {
23
38
  try {
@@ -29,16 +44,6 @@ function loadConfig(cwd_) {
29
44
  throw new Error(("Invalid flux.json: " + e.message));
30
45
  }
31
46
  }
32
- const jsPath = Path.join(cwd, "flux.config.js");
33
- if (Fs.existsSync(jsPath)) {
34
- try {
35
- const loaded = require(jsPath);
36
- return mergeConfig(DEFAULT_CONFIG, loaded);
37
- }
38
- catch (e2) {
39
- throw new Error(("Invalid flux.config.js: " + e2.message));
40
- }
41
- }
42
47
  return { ...DEFAULT_CONFIG };
43
48
  }
44
49
  module.exports.loadConfig = loadConfig;
@@ -58,9 +63,18 @@ function mergeConfig(base, overrides) {
58
63
  module.exports.mergeConfig = mergeConfig;
59
64
  function writeConfig(config, cwd_) {
60
65
  const cwd = (cwd_ ?? process.cwd());
61
- const jsonPath = Path.join(cwd, "flux.json");
62
- const content = (JSON.stringify(config, null, 2) + "\n");
63
- Fs.writeFileSync(jsonPath, content, "utf8");
66
+ const pkgPath = Path.join(cwd, "package.json");
67
+ let pkg = { };
68
+ if (Fs.existsSync(pkgPath)) {
69
+ try {
70
+ pkg = JSON.parse(Fs.readFileSync(pkgPath, "utf8"));
71
+ }
72
+ catch (e) {
73
+ pkg = { };
74
+ }
75
+ }
76
+ pkg.flux = config;
77
+ Fs.writeFileSync(pkgPath, (JSON.stringify(pkg, null, 2) + "\n"), "utf8");
64
78
  }
65
79
  module.exports.writeConfig = writeConfig;
66
80
  function validateConfig(config) {
@@ -76,19 +90,19 @@ function validateConfig(config) {
76
90
  module.exports.validateConfig = validateConfig;
77
91
  function readPackage(cwd_) {
78
92
  const cwd = (cwd_ ?? process.cwd());
79
- const fluxJson = Path.join(cwd, "flux.json");
80
93
  const pkgJson = Path.join(cwd, "package.json");
81
- if (Fs.existsSync(fluxJson)) {
94
+ const fluxJson = Path.join(cwd, "flux.json");
95
+ if (Fs.existsSync(pkgJson)) {
82
96
  try {
83
- return JSON.parse(Fs.readFileSync(fluxJson, "utf8"));
97
+ return JSON.parse(Fs.readFileSync(pkgJson, "utf8"));
84
98
  }
85
99
  catch (e) {
86
100
  return null;
87
101
  }
88
102
  }
89
- if (Fs.existsSync(pkgJson)) {
103
+ if (Fs.existsSync(fluxJson)) {
90
104
  try {
91
- return JSON.parse(Fs.readFileSync(pkgJson, "utf8"));
105
+ return JSON.parse(Fs.readFileSync(fluxJson, "utf8"));
92
106
  }
93
107
  catch (e2) {
94
108
  return null;