@xnoxs/flux-lang 3.5.2 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,37 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ---
9
9
 
10
+ ## [4.0.0] — 2026-06-27
11
+
12
+ ### Added
13
+ - **`declare` keyword** — ambient type declarations, zero JS output
14
+ - `declare fn name(params): RetType` — declare external function signature
15
+ - `declare async fn name(params): RetType` — declare async external function
16
+ - `declare fn name(params) -> RetType` — alternative arrow return type syntax
17
+ - `declare val name: Type` — declare typed global constant
18
+ - `declare var name: Type` — declare typed mutable global
19
+ - `declare class Name` — declare external class
20
+ - Type checker registers all declared names in scope with their types
21
+ - Codegen emits **zero JavaScript** for all `declare` nodes (ambient only)
22
+ - Self-hosted compiler (lexer, parser, codegen, type-checker) fully updated
23
+ - 11 new tests in `tests/14_declare.test.flux`
24
+
25
+ ### Fixed
26
+ - **`flux fmt <directory>`** — previously crashed with `EISDIR`; now formats all `.flux` files in the given directory recursively
27
+ - **`flux publish`** — previously crashed with "package.json not found" in Flux projects using `flux.json`; now falls back correctly to `flux.json`
28
+
29
+ ### Changed
30
+ - Version bumped from `3.5.3` → `4.0.0`
31
+ - All version references in webapp (`app/ecosystem.flux`) updated to `v4.0.0`
32
+ - README updated with v4.0.0 release notes
33
+ - Docs: new **Ambient Declarations** section (`/docs#declare`) with full reference table, two code examples, callouts
34
+ - Docs sidebar: `Ambient Declarations` added under Type System and v3 Features
35
+ - Examples page: new **📣 declare** card
36
+ - Playground: new **declare** entry in Load Example dropdown
37
+ - Total tests: **113 passed** (102 language + 11 declare)
38
+
39
+ ---
40
+
10
41
  ## [3.4.4] — 2026-06-26
11
42
 
12
43
  ### Added
package/README.md CHANGED
@@ -1,7 +1,9 @@
1
- # ⚡ Flux Lang v3.4.7
1
+ # ⚡ Flux Lang v4.0.0
2
2
 
3
3
  **Flux** is a modern programming language that transpiles to clean JavaScript — combining Python-style indentation, TypeScript-grade type safety, Rust-inspired pattern matching, and a rich standard library — all with **zero runtime overhead**.
4
4
 
5
+ > **v4.0.0 — `declare` keyword + full ambient declarations:** Flux now supports TypeScript-style ambient declarations. Declare external functions, variables, and classes without implementation — the type checker registers them in scope, codegen emits zero JS. Self-hosted compiler fully updated.
6
+
5
7
  > **v3.4.0 — Fully Self-Hosted:** Every component of the Flux compiler — lexer, parser, type checker, code generator, formatter, linter, bundler, package manager, and CLI — is now written entirely in Flux and compiled by itself. `node scripts/bootstrap.js` → "Bootstrap complete — Flux is self-hosting."
6
8
 
7
9
  ```
package/dist/flux-cli.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  /*!
3
- * flux-lang v3.5.2
3
+ * flux-lang v4.0.0
4
4
  * Flux — A modern language that transpiles to JavaScript. Python-clean syntax, TypeScript-level safety, Rust-inspired pattern matching.
5
5
  * (c) 2026 Flux Lang Contributors
6
6
  * Released under the MIT License
@@ -101,6 +101,7 @@ var require_lexer = __commonJS({
101
101
  VAL: "VAL",
102
102
  FN: "FN",
103
103
  RETURN: "RETURN",
104
+ DECLARE: "DECLARE",
104
105
  // Keywords — control flow
105
106
  IF: "IF",
106
107
  ELSE: "ELSE",
@@ -237,6 +238,7 @@ var require_lexer = __commonJS({
237
238
  val: T.VAL,
238
239
  fn: T.FN,
239
240
  return: T.RETURN,
241
+ declare: T.DECLARE,
240
242
  if: T.IF,
241
243
  else: T.ELSE,
242
244
  for: T.FOR,
@@ -374,7 +376,17 @@ var require_lexer = __commonJS({
374
376
  if (this.ch() === "\\") {
375
377
  this.adv();
376
378
  const e = this.adv();
377
- text += { n: "\n", t: " ", '"': '"', "'": "'", "\\": "\\", "{": "{", "}": "}" }[e] || "\\" + e;
379
+ if (e === "u") {
380
+ const hex = this.src.slice(this.pos, this.pos + 4);
381
+ this.pos += 4;
382
+ text += String.fromCharCode(parseInt(hex, 16));
383
+ } else if (e === "x") {
384
+ const hex = this.src.slice(this.pos, this.pos + 2);
385
+ this.pos += 2;
386
+ text += String.fromCharCode(parseInt(hex, 16));
387
+ } else {
388
+ text += { n: "\n", t: " ", r: "\r", '"': '"', "'": "'", "\\": "\\", "{": "{", "}": "}" }[e] || "\\" + e;
389
+ }
378
390
  } else if (this.ch() === "{") {
379
391
  parts.push({ type: "text", value: text });
380
392
  text = "";
@@ -569,7 +581,17 @@ var require_lexer = __commonJS({
569
581
  if (this.ch() === "\\") {
570
582
  this.adv();
571
583
  const e = this.adv();
572
- s += { n: "\n", t: " ", r: "\r", "'": "'", "\\": "\\" }[e] || "\\" + e;
584
+ if (e === "u") {
585
+ const hex = this.src.slice(this.pos, this.pos + 4);
586
+ this.pos += 4;
587
+ s += String.fromCharCode(parseInt(hex, 16));
588
+ } else if (e === "x") {
589
+ const hex = this.src.slice(this.pos, this.pos + 2);
590
+ this.pos += 2;
591
+ s += String.fromCharCode(parseInt(hex, 16));
592
+ } else {
593
+ s += { n: "\n", t: " ", r: "\r", "'": "'", "\\": "\\" }[e] || "\\" + e;
594
+ }
573
595
  } else {
574
596
  s += this.adv();
575
597
  }
@@ -1092,6 +1114,8 @@ var require_parser = __commonJS({
1092
1114
  if (decorators.length) n.decorators = decorators;
1093
1115
  return n;
1094
1116
  }
1117
+ case T.DECLARE:
1118
+ return this.parseDeclareDecl();
1095
1119
  case T.IF:
1096
1120
  return this.parseIf();
1097
1121
  case T.FOR:
@@ -1562,6 +1586,48 @@ var require_parser = __commonJS({
1562
1586
  }
1563
1587
  this.err("Expected -> or : after function signature");
1564
1588
  }
1589
+ // ── declare fn/val/var/class ───────────────────────────────────
1590
+ parseDeclareDecl() {
1591
+ const loc = this.eat(T.DECLARE);
1592
+ const tok = this.peek();
1593
+ if (tok.type === T.FN || tok.type === T.ASYNC) {
1594
+ const isAsync = tok.type === T.ASYNC;
1595
+ this.skip();
1596
+ if (isAsync) this.eat(T.FN);
1597
+ const KEYWORD_AS_NAME = /* @__PURE__ */ new Set([T.NEW, T.DELETE, T.FROM, T.AS, T.DEFAULT, T.IS, T.IN, T.TYPE]);
1598
+ const name = this.check(T.IDENT) || KEYWORD_AS_NAME.has(this.peek().type) ? this.skip().value : null;
1599
+ const params = this.parseParamList();
1600
+ let retType = null;
1601
+ if (this.check(T.ARROW)) {
1602
+ this.pos++;
1603
+ retType = this.parseTypeAnn();
1604
+ } else if (this.check(T.COLON)) {
1605
+ this.pos++;
1606
+ retType = this.parseTypeAnn();
1607
+ }
1608
+ this.skipNewlines();
1609
+ const decl = { type: "FnDecl", name, params, retType, body: [], inline: false, async: isAsync, loc: tok };
1610
+ return { type: "DeclareDecl", decl, loc };
1611
+ }
1612
+ if (tok.type === T.VAL || tok.type === T.VAR) {
1613
+ const kind = tok.type === T.VAR ? "var" : "val";
1614
+ this.skip();
1615
+ const name = this.eat(T.IDENT).value;
1616
+ let typeAnn = null;
1617
+ if (this.maybe(T.COLON)) typeAnn = this.parseTypeAnn();
1618
+ this.skipNewlines();
1619
+ const decl = { type: "VarDecl", kind, name, typeAnn, init: null, loc: tok };
1620
+ return { type: "DeclareDecl", decl, loc };
1621
+ }
1622
+ if (tok.type === T.CLASS) {
1623
+ this.skip();
1624
+ const name = this.eat(T.IDENT).value;
1625
+ this.skipNewlines();
1626
+ const decl = { type: "ClassDecl", name, fields: [], methods: [], loc: tok };
1627
+ return { type: "DeclareDecl", decl, loc };
1628
+ }
1629
+ this.err("Expected fn, async fn, val, var, or class after declare");
1630
+ }
1565
1631
  // ── async fn ──────────────────────────────────────────────────
1566
1632
  parseAsyncFn() {
1567
1633
  this.eat(T.ASYNC);
@@ -2673,6 +2739,9 @@ function _fmt(v, s) {
2673
2739
  return this.genInterfaceDecl(node);
2674
2740
  case "EnumDecl":
2675
2741
  return this.genEnumDecl(node);
2742
+ case "DeclareDecl":
2743
+ return;
2744
+ // ambient declaration — no JS output
2676
2745
  case "ExprStmt":
2677
2746
  return this.emit(this.genExpr(node.expr) + ";");
2678
2747
  default:
@@ -5675,6 +5744,20 @@ var require_type_checker = __commonJS({
5675
5744
  }
5676
5745
  break;
5677
5746
  }
5747
+ case "DeclareDecl": {
5748
+ const d = node.decl;
5749
+ if (d.type === "FnDecl") {
5750
+ const retType = d.retType ? parseAnnotation(d.retType) : null;
5751
+ const paramTypes = d.params.map((p) => p.typeAnn ? parseAnnotation(p.typeAnn) : T_UNKNOWN);
5752
+ if (d.name) env.set(d.name, T_FN(paramTypes, retType || T_UNKNOWN));
5753
+ } else if (d.type === "VarDecl") {
5754
+ const t = d.typeAnn ? parseAnnotation(d.typeAnn) : T_UNKNOWN;
5755
+ env.set(d.name, t);
5756
+ } else if (d.type === "ClassDecl") {
5757
+ env.set(d.name, T_NAMED(d.name));
5758
+ }
5759
+ break;
5760
+ }
5678
5761
  case "TypeDecl":
5679
5762
  for (const v of node.variants) {
5680
5763
  if (v.fields.length === 0) env.set(v.name, T_NAMED(node.name));
@@ -7223,7 +7306,7 @@ var require_package = __commonJS({
7223
7306
  "package.json"(exports2, module2) {
7224
7307
  module2.exports = {
7225
7308
  name: "@xnoxs/flux-lang",
7226
- version: "3.5.2",
7309
+ version: "4.0.0",
7227
7310
  description: "Flux \u2014 A modern language that transpiles to JavaScript. Python-clean syntax, TypeScript-level safety, Rust-inspired pattern matching.",
7228
7311
  main: "dist/flux.cjs.js",
7229
7312
  module: "dist/flux.esm.js",
@@ -8579,7 +8662,8 @@ var require_pkg = __commonJS({
8579
8662
  var { readPackage, writeConfig, loadConfig: loadConfig2 } = require_config2();
8580
8663
  var REGISTRY_URL = "https://registry.npmjs.org";
8581
8664
  var PKG_FILE = "flux.json";
8582
- var C2 = { reset: "\\u001b[0m", bold: "\\u001b[1m", dim: "\\u001b[2m", red: "\\u001b[31m", green: "\\u001b[32m", yellow: "\\u001b[33m", blue: "\\u001b[34m", cyan: "\\u001b[36m", gray: "\\u001b[90m" };
8665
+ var ESC = "\x1B";
8666
+ var C2 = { reset: ESC + "[0m", bold: ESC + "[1m", dim: ESC + "[2m", red: ESC + "[31m", green: ESC + "[32m", yellow: ESC + "[33m", blue: ESC + "[34m", cyan: ESC + "[36m", gray: ESC + "[90m" };
8583
8667
  function clr2(c, s) {
8584
8668
  return c + s + C2.reset;
8585
8669
  }
@@ -8592,6 +8676,27 @@ var require_pkg = __commonJS({
8592
8676
  function info(msg) {
8593
8677
  console.log(clr2(C2.cyan, " ") + msg);
8594
8678
  }
8679
+ var SPIN_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
8680
+ var stdout = process.stdout;
8681
+ function startSpinner(label) {
8682
+ if (!stdout.isTTY) {
8683
+ return null;
8684
+ }
8685
+ let frame = 0;
8686
+ function tick() {
8687
+ stdout.write("\r" + clr2(C2.cyan, SPIN_FRAMES[frame % SPIN_FRAMES.length]) + " " + label + " ");
8688
+ frame = frame + 1;
8689
+ }
8690
+ const timer = setInterval(tick, 80);
8691
+ return timer;
8692
+ }
8693
+ function stopSpinner(timer) {
8694
+ if (!timer) {
8695
+ return;
8696
+ }
8697
+ clearInterval(timer);
8698
+ stdout.write("\r" + ESC + "[2K");
8699
+ }
8595
8700
  async function fetchJson(url) {
8596
8701
  const mod = url.startsWith("https") ? Https : Http;
8597
8702
  function doRequest(resolve, reject) {
@@ -8643,11 +8748,12 @@ var require_pkg = __commonJS({
8643
8748
  const pkg = ensureFluxJson(cwd);
8644
8749
  for (const spec of specs) {
8645
8750
  const { name, version } = parsePackageSpec(spec);
8646
- console.log(clr2(C2.cyan, "\n Adding ") + clr2(C2.bold, name) + clr2(C2.gray, "@" + version) + " ...");
8751
+ const spinner = startSpinner("Fetching " + clr2(C2.bold, name) + clr2(C2.gray, "@" + version) + " ...");
8647
8752
  try {
8648
8753
  const info_ = await fetchJson(REGISTRY_URL + "/" + name);
8649
8754
  const resolvedVersion = version == "latest" ? ((_a = info_["dist-tags"]) == null ? void 0 : _a.latest) ?? "1.0.0" : version;
8650
8755
  const versionInfo = (_b = info_.versions) == null ? void 0 : _b[resolvedVersion];
8756
+ stopSpinner(spinner);
8651
8757
  if (!versionInfo) {
8652
8758
  err("Version " + resolvedVersion + " not found for " + name);
8653
8759
  continue;
@@ -8662,6 +8768,7 @@ var require_pkg = __commonJS({
8662
8768
  ok(name + clr2(C2.green, "@" + resolvedVersion) + desc);
8663
8769
  info("Added to " + (isDev ? "devDependencies" : "dependencies"));
8664
8770
  } catch (e) {
8771
+ stopSpinner(spinner);
8665
8772
  err("Failed to fetch " + name + ": " + e.message);
8666
8773
  }
8667
8774
  }
@@ -8841,6 +8948,82 @@ var require_pkg = __commonJS({
8841
8948
  }
8842
8949
  }
8843
8950
  module2.exports.cmdInfo = cmdInfo;
8951
+ async function cmdUpgrade(opts) {
8952
+ var _a;
8953
+ const isCheck = (opts == null ? void 0 : opts.check) ?? false;
8954
+ const cwd = process.cwd();
8955
+ const pkg = readPackage(cwd);
8956
+ if (!pkg) {
8957
+ err("No flux.json found. Run: flux init");
8958
+ return;
8959
+ }
8960
+ const deps = pkg.dependencies ?? {};
8961
+ const devDeps = pkg.devDependencies ?? {};
8962
+ const all = { ...deps, ...devDeps };
8963
+ const names = Object.keys(all);
8964
+ if (names.length == 0) {
8965
+ info("No dependencies to upgrade");
8966
+ console.log();
8967
+ return;
8968
+ }
8969
+ if (isCheck) {
8970
+ console.log(clr2(C2.cyan, "\n Checking " + names.length + " package(s) for updates...\n"));
8971
+ } else {
8972
+ console.log(clr2(C2.cyan, "\n Upgrading " + names.length + " package(s) to latest...\n"));
8973
+ }
8974
+ let outdated = 0;
8975
+ let updated = 0;
8976
+ for (const name of names) {
8977
+ const current = all[name];
8978
+ const spinner = startSpinner("Checking " + clr2(C2.bold, name) + " ...");
8979
+ try {
8980
+ const info_ = await fetchJson(REGISTRY_URL + "/" + name);
8981
+ const latest = ((_a = info_["dist-tags"]) == null ? void 0 : _a.latest) ?? null;
8982
+ stopSpinner(spinner);
8983
+ if (!latest) {
8984
+ console.log(" " + clr2(C2.gray, "? ") + clr2(C2.bold, name) + clr2(C2.gray, " \u2014 could not resolve latest"));
8985
+ continue;
8986
+ }
8987
+ const currentClean = current.replace("^", "").replace("~", "");
8988
+ if (currentClean == latest) {
8989
+ console.log(" " + clr2(C2.green, "\u2713 ") + clr2(C2.bold, name) + clr2(C2.gray, " " + current + " (up to date)"));
8990
+ } else {
8991
+ outdated = outdated + 1;
8992
+ if (isCheck) {
8993
+ console.log(" " + clr2(C2.yellow, "\u2191 ") + clr2(C2.bold, name) + clr2(C2.gray, " " + current + " \u2192 ") + clr2(C2.green, "^" + latest));
8994
+ } else {
8995
+ const isDev = devDeps[name] != null;
8996
+ const depKey = isDev ? "devDependencies" : "dependencies";
8997
+ pkg[depKey][name] = "^" + latest;
8998
+ updated = updated + 1;
8999
+ console.log(" " + clr2(C2.green, "\u2713 ") + clr2(C2.bold, name) + clr2(C2.gray, " " + current + " \u2192 ") + clr2(C2.green, "^" + latest));
9000
+ }
9001
+ }
9002
+ } catch (e) {
9003
+ stopSpinner(spinner);
9004
+ console.log(" " + clr2(C2.red, "\u2717 ") + clr2(C2.bold, name) + clr2(C2.gray, " \u2014 " + e.message));
9005
+ }
9006
+ }
9007
+ console.log();
9008
+ if (isCheck) {
9009
+ if (outdated == 0) {
9010
+ ok("All packages are up to date");
9011
+ } else {
9012
+ console.log(clr2(C2.yellow, " " + outdated + " package(s) can be upgraded"));
9013
+ console.log(clr2(C2.gray, " Run ") + clr2(C2.yellow, "flux upgrade") + clr2(C2.gray, " to apply updates"));
9014
+ }
9015
+ } else {
9016
+ if (updated == 0) {
9017
+ ok("All packages are already up to date");
9018
+ } else {
9019
+ saveFluxJson(pkg, cwd);
9020
+ ok(updated + " package(s) updated in flux.json");
9021
+ console.log(clr2(C2.gray, " Run ") + clr2(C2.yellow, "flux install") + clr2(C2.gray, " to install updated versions"));
9022
+ }
9023
+ }
9024
+ console.log();
9025
+ }
9026
+ module2.exports.cmdUpgrade = cmdUpgrade;
8844
9027
  function cmdPublish2(opts) {
8845
9028
  const cwd = process.cwd();
8846
9029
  const pkg = readPackage(cwd);
@@ -8980,7 +9163,7 @@ function showHelp() {
8980
9163
  console.log(clr(C.gray, " flux watch app.flux"));
8981
9164
  console.log(clr(C.gray, " flux repl"));
8982
9165
  console.log();
8983
- console.log(clr(C.bold, "WHAT'S NEW in v3.0.0:"));
9166
+ console.log(clr(C.bold, `WHAT'S NEW in v${VERSION}:`));
8984
9167
  const news = [
8985
9168
  "Return type annotations \u2014 fn greet(name: String) -> String:",
8986
9169
  "Union types \u2014 val x: Int | String | Null",
@@ -9627,7 +9810,18 @@ function cmdLint(filePath) {
9627
9810
  if (exitCode !== 0) process.exit(exitCode);
9628
9811
  }
9629
9812
  function cmdFmt(filePath, opts) {
9630
- const { source, abs } = readFluxFile(filePath);
9813
+ const abs = path.resolve(filePath);
9814
+ if (fs.existsSync(abs) && fs.statSync(abs).isDirectory()) {
9815
+ const entries = fs.readdirSync(abs).filter((f) => f.endsWith(".flux"));
9816
+ if (entries.length === 0) {
9817
+ console.log(clr(C.gray, ` No .flux files found in ${filePath}`));
9818
+ return;
9819
+ }
9820
+ let changed = 0;
9821
+ for (const entry of entries) cmdFmt(path.join(filePath, entry), opts);
9822
+ return;
9823
+ }
9824
+ const { source } = readFluxFile(filePath);
9631
9825
  const { format } = require_formatter();
9632
9826
  const output = format(source);
9633
9827
  if (output === source) {
@@ -9894,9 +10088,19 @@ ${e.message}`);
9894
10088
  banner();
9895
10089
  console.log(clr(C.bold, ` Release Workflow${isDry ? clr(C.yellow, " [DRY RUN]") : ""}
9896
10090
  `));
9897
- const pkgPath = path.resolve("package.json");
9898
10091
  const changelogPath = path.resolve("CHANGELOG.md");
9899
- if (!fs.existsSync(pkgPath)) fail("package.json not found");
10092
+ const npmPkgPath = path.resolve("package.json");
10093
+ const fluxPkgPath = path.resolve("flux.json");
10094
+ let pkgPath, pkgFile;
10095
+ if (fs.existsSync(npmPkgPath)) {
10096
+ pkgPath = npmPkgPath;
10097
+ pkgFile = "package.json";
10098
+ } else if (fs.existsSync(fluxPkgPath)) {
10099
+ pkgPath = fluxPkgPath;
10100
+ pkgFile = "flux.json";
10101
+ } else {
10102
+ fail("No package.json or flux.json found \u2014 run: flux init");
10103
+ }
9900
10104
  const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
9901
10105
  const oldVersion = pkg.version;
9902
10106
  const newVersion = bumpVersion(oldVersion, bumpType);
@@ -9958,19 +10162,19 @@ ${e.message}`);
9958
10162
  step("Confirm release");
9959
10163
  const go = await confirm(`Release ${clr(C.bold, pkg.name + "@" + newVersion)} (${tagName})?`);
9960
10164
  if (!go) fail("Release cancelled");
9961
- step(`Bumping package.json: ${oldVersion} \u2192 ${newVersion}`);
10165
+ step(`Bumping ${pkgFile}: ${oldVersion} \u2192 ${newVersion}`);
9962
10166
  if (!isDry) {
9963
10167
  pkg.version = newVersion;
9964
10168
  fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
9965
10169
  } else {
9966
- dry(`package.json: "version": "${oldVersion}" \u2192 "${newVersion}"`);
10170
+ dry(`${pkgFile}: "version": "${oldVersion}" \u2192 "${newVersion}"`);
9967
10171
  }
9968
- ok(`package.json updated`);
10172
+ ok(`${pkgFile} updated`);
9969
10173
  step("Updating CHANGELOG.md");
9970
10174
  const clUpdated = updateChangelog(newVersion, changelogPath);
9971
10175
  if (clUpdated) ok(`CHANGELOG.md updated \u2014 [${newVersion}] section created`);
9972
10176
  step("Creating git commit");
9973
- run("git add package.json CHANGELOG.md");
10177
+ run(`git add ${pkgFile} CHANGELOG.md`);
9974
10178
  run(`git commit -m "chore: release ${tagName}"`);
9975
10179
  ok(`Committed: chore: release ${tagName}`);
9976
10180
  step(`Creating git tag: ${tagName}`);
@@ -10002,7 +10206,7 @@ ${e.message}`);
10002
10206
  console.log(clr(C.green, C.bold + ` \u2713 Released: ${pkg.name}@${newVersion}` + C.reset));
10003
10207
  console.log();
10004
10208
  console.log(clr(C.gray, " Summary:"));
10005
- console.log(` ${clr(C.cyan, `package.json`)} version \u2192 ${newVersion}`);
10209
+ console.log(` ${clr(C.cyan, pkgFile)} version \u2192 ${newVersion}`);
10006
10210
  if (clUpdated)
10007
10211
  console.log(` ${clr(C.cyan, `CHANGELOG.md`)} [${newVersion}] \u2014 ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`);
10008
10212
  console.log(` ${clr(C.cyan, `git tag`)} ${tagName}`);
package/dist/flux.cjs.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * flux-lang v3.5.2
2
+ * flux-lang v4.0.0
3
3
  * Flux — A modern language that transpiles to JavaScript. Python-clean syntax, TypeScript-level safety, Rust-inspired pattern matching.
4
4
  * (c) 2026 Flux Lang Contributors
5
5
  * Released under the MIT License
@@ -30,6 +30,7 @@ var require_lexer = __commonJS({
30
30
  VAL: "VAL",
31
31
  FN: "FN",
32
32
  RETURN: "RETURN",
33
+ DECLARE: "DECLARE",
33
34
  // Keywords — control flow
34
35
  IF: "IF",
35
36
  ELSE: "ELSE",
@@ -166,6 +167,7 @@ var require_lexer = __commonJS({
166
167
  val: T.VAL,
167
168
  fn: T.FN,
168
169
  return: T.RETURN,
170
+ declare: T.DECLARE,
169
171
  if: T.IF,
170
172
  else: T.ELSE,
171
173
  for: T.FOR,
@@ -303,7 +305,17 @@ var require_lexer = __commonJS({
303
305
  if (this.ch() === "\\") {
304
306
  this.adv();
305
307
  const e = this.adv();
306
- text += { n: "\n", t: " ", '"': '"', "'": "'", "\\": "\\", "{": "{", "}": "}" }[e] || "\\" + e;
308
+ if (e === "u") {
309
+ const hex = this.src.slice(this.pos, this.pos + 4);
310
+ this.pos += 4;
311
+ text += String.fromCharCode(parseInt(hex, 16));
312
+ } else if (e === "x") {
313
+ const hex = this.src.slice(this.pos, this.pos + 2);
314
+ this.pos += 2;
315
+ text += String.fromCharCode(parseInt(hex, 16));
316
+ } else {
317
+ text += { n: "\n", t: " ", r: "\r", '"': '"', "'": "'", "\\": "\\", "{": "{", "}": "}" }[e] || "\\" + e;
318
+ }
307
319
  } else if (this.ch() === "{") {
308
320
  parts.push({ type: "text", value: text });
309
321
  text = "";
@@ -498,7 +510,17 @@ var require_lexer = __commonJS({
498
510
  if (this.ch() === "\\") {
499
511
  this.adv();
500
512
  const e = this.adv();
501
- s += { n: "\n", t: " ", r: "\r", "'": "'", "\\": "\\" }[e] || "\\" + e;
513
+ if (e === "u") {
514
+ const hex = this.src.slice(this.pos, this.pos + 4);
515
+ this.pos += 4;
516
+ s += String.fromCharCode(parseInt(hex, 16));
517
+ } else if (e === "x") {
518
+ const hex = this.src.slice(this.pos, this.pos + 2);
519
+ this.pos += 2;
520
+ s += String.fromCharCode(parseInt(hex, 16));
521
+ } else {
522
+ s += { n: "\n", t: " ", r: "\r", "'": "'", "\\": "\\" }[e] || "\\" + e;
523
+ }
502
524
  } else {
503
525
  s += this.adv();
504
526
  }
@@ -1021,6 +1043,8 @@ var require_parser = __commonJS({
1021
1043
  if (decorators.length) n.decorators = decorators;
1022
1044
  return n;
1023
1045
  }
1046
+ case T.DECLARE:
1047
+ return this.parseDeclareDecl();
1024
1048
  case T.IF:
1025
1049
  return this.parseIf();
1026
1050
  case T.FOR:
@@ -1491,6 +1515,48 @@ var require_parser = __commonJS({
1491
1515
  }
1492
1516
  this.err("Expected -> or : after function signature");
1493
1517
  }
1518
+ // ── declare fn/val/var/class ───────────────────────────────────
1519
+ parseDeclareDecl() {
1520
+ const loc = this.eat(T.DECLARE);
1521
+ const tok = this.peek();
1522
+ if (tok.type === T.FN || tok.type === T.ASYNC) {
1523
+ const isAsync = tok.type === T.ASYNC;
1524
+ this.skip();
1525
+ if (isAsync) this.eat(T.FN);
1526
+ const KEYWORD_AS_NAME = /* @__PURE__ */ new Set([T.NEW, T.DELETE, T.FROM, T.AS, T.DEFAULT, T.IS, T.IN, T.TYPE]);
1527
+ const name = this.check(T.IDENT) || KEYWORD_AS_NAME.has(this.peek().type) ? this.skip().value : null;
1528
+ const params = this.parseParamList();
1529
+ let retType = null;
1530
+ if (this.check(T.ARROW)) {
1531
+ this.pos++;
1532
+ retType = this.parseTypeAnn();
1533
+ } else if (this.check(T.COLON)) {
1534
+ this.pos++;
1535
+ retType = this.parseTypeAnn();
1536
+ }
1537
+ this.skipNewlines();
1538
+ const decl = { type: "FnDecl", name, params, retType, body: [], inline: false, async: isAsync, loc: tok };
1539
+ return { type: "DeclareDecl", decl, loc };
1540
+ }
1541
+ if (tok.type === T.VAL || tok.type === T.VAR) {
1542
+ const kind = tok.type === T.VAR ? "var" : "val";
1543
+ this.skip();
1544
+ const name = this.eat(T.IDENT).value;
1545
+ let typeAnn = null;
1546
+ if (this.maybe(T.COLON)) typeAnn = this.parseTypeAnn();
1547
+ this.skipNewlines();
1548
+ const decl = { type: "VarDecl", kind, name, typeAnn, init: null, loc: tok };
1549
+ return { type: "DeclareDecl", decl, loc };
1550
+ }
1551
+ if (tok.type === T.CLASS) {
1552
+ this.skip();
1553
+ const name = this.eat(T.IDENT).value;
1554
+ this.skipNewlines();
1555
+ const decl = { type: "ClassDecl", name, fields: [], methods: [], loc: tok };
1556
+ return { type: "DeclareDecl", decl, loc };
1557
+ }
1558
+ this.err("Expected fn, async fn, val, var, or class after declare");
1559
+ }
1494
1560
  // ── async fn ──────────────────────────────────────────────────
1495
1561
  parseAsyncFn() {
1496
1562
  this.eat(T.ASYNC);
@@ -2602,6 +2668,9 @@ function _fmt(v, s) {
2602
2668
  return this.genInterfaceDecl(node);
2603
2669
  case "EnumDecl":
2604
2670
  return this.genEnumDecl(node);
2671
+ case "DeclareDecl":
2672
+ return;
2673
+ // ambient declaration — no JS output
2605
2674
  case "ExprStmt":
2606
2675
  return this.emit(this.genExpr(node.expr) + ";");
2607
2676
  default:
@@ -5604,6 +5673,20 @@ var require_type_checker = __commonJS({
5604
5673
  }
5605
5674
  break;
5606
5675
  }
5676
+ case "DeclareDecl": {
5677
+ const d = node.decl;
5678
+ if (d.type === "FnDecl") {
5679
+ const retType = d.retType ? parseAnnotation(d.retType) : null;
5680
+ const paramTypes = d.params.map((p) => p.typeAnn ? parseAnnotation(p.typeAnn) : T_UNKNOWN);
5681
+ if (d.name) env.set(d.name, T_FN(paramTypes, retType || T_UNKNOWN));
5682
+ } else if (d.type === "VarDecl") {
5683
+ const t = d.typeAnn ? parseAnnotation(d.typeAnn) : T_UNKNOWN;
5684
+ env.set(d.name, t);
5685
+ } else if (d.type === "ClassDecl") {
5686
+ env.set(d.name, T_NAMED(d.name));
5687
+ }
5688
+ break;
5689
+ }
5607
5690
  case "TypeDecl":
5608
5691
  for (const v of node.variants) {
5609
5692
  if (v.fields.length === 0) env.set(v.name, T_NAMED(node.name));