@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 +31 -0
- package/README.md +3 -1
- package/dist/flux-cli.js +219 -15
- package/dist/flux.cjs.js +86 -3
- package/dist/flux.esm.js +86 -3
- package/dist/flux.min.js +20 -20
- package/package.json +1 -1
- package/src/self/cli.js +1 -1
- package/src/self/codegen.flux +5 -2
- package/src/self/codegen.js +1 -794
- package/src/self/css-preprocessor.js +1 -1
- package/src/self/lexer.flux +1 -1
- package/src/self/lexer.js +1 -713
- package/src/self/lexer.stage2.js +700 -0
- package/src/self/parser.flux +41 -0
- package/src/self/parser.js +1 -1571
- package/src/self/pkg.flux +107 -10
- package/src/self/pkg.js +108 -2
- package/src/self/test-runner.js +1 -1
- package/src/self/transpiler.flux +14 -4
- package/src/self/transpiler.js +1 -83
- package/src/self/type-checker.flux +12 -0
- package/src/self/type-checker.js +1 -1114
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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: "
|
|
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
|
|
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
|
-
|
|
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,
|
|
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
|
|
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
|
-
|
|
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
|
|
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(
|
|
10170
|
+
dry(`${pkgFile}: "version": "${oldVersion}" \u2192 "${newVersion}"`);
|
|
9967
10171
|
}
|
|
9968
|
-
ok(
|
|
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(
|
|
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,
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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));
|