@xnoxs/flux-lang 3.3.4 → 3.4.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.
@@ -3,9 +3,19 @@
3
3
  function map(arr, fn) { return arr.map(fn); }
4
4
 
5
5
  function join(arr, sep) { return arr.join(sep != null ? sep : ','); }
6
+
7
+ function round(n, decimals) {
8
+ if (decimals == null) return Math.round(n);
9
+ var f = Math.pow(10, decimals);
10
+ return Math.round(n * f) / f;
11
+ }
12
+
13
+ function trim(s) { return String(s).trim(); }
14
+
15
+ function trimStart(s) { return String(s).trimStart(); }
6
16
  // ── end stdlib ──
7
17
 
8
- // Generated by Flux Transpiler v3.1.0
18
+ // Generated by Flux Transpiler v3.2.0
9
19
  "use strict";
10
20
 
11
21
  const { Lexer, lexerize, T } = require("./lexer");
@@ -0,0 +1,112 @@
1
+ // ============================================================
2
+ // Flux Self-Hosted Config Reader
3
+ // src/self/config.flux — written in Flux, compiled by stage-0
4
+ //
5
+ // Reads flux.json project config and merges with CLI flags.
6
+ // Config file locations (searched in order):
7
+ // ./flux.json
8
+ // ./flux.config.js
9
+ // ============================================================
10
+
11
+ import Fs from "fs"
12
+ import Path from "path"
13
+
14
+ // ── Default config ────────────────────────────────────────────
15
+ export val DEFAULT_CONFIG = {
16
+ entry: "src/main.flux",
17
+ outDir: "dist",
18
+ sourcemap: false,
19
+ mangle: false,
20
+ jsx: false,
21
+ jsxTarget: "browser",
22
+ typecheck: true,
23
+ strict: false,
24
+ watch: false,
25
+ ignore: [],
26
+ selfHosted: false,
27
+ registry: "https://registry.flux-lang.dev",
28
+ pkg: {
29
+ name: "",
30
+ version: "1.0.0",
31
+ description: "",
32
+ author: "",
33
+ license: "MIT",
34
+ deps: {},
35
+ devDeps: {},
36
+ }
37
+ }
38
+
39
+ // ── Load flux.json ────────────────────────────────────────────
40
+ export fn loadConfig(cwd_):
41
+ val cwd = cwd_ ?? process.cwd()
42
+
43
+ // Try flux.json first
44
+ val jsonPath = Path.join(cwd, "flux.json")
45
+ if Fs.existsSync(jsonPath):
46
+ try:
47
+ val raw = Fs.readFileSync(jsonPath, "utf8")
48
+ val parsed = JSON.parse(raw)
49
+ return mergeConfig(DEFAULT_CONFIG, parsed)
50
+ catch(e):
51
+ throw new Error("Invalid flux.json: " + e.message)
52
+
53
+ // Try flux.config.js
54
+ val jsPath = Path.join(cwd, "flux.config.js")
55
+ if Fs.existsSync(jsPath):
56
+ try:
57
+ val loaded = require(jsPath)
58
+ return mergeConfig(DEFAULT_CONFIG, loaded)
59
+ catch(e2):
60
+ throw new Error("Invalid flux.config.js: " + e2.message)
61
+
62
+ return { ...DEFAULT_CONFIG }
63
+
64
+ // ── Deep merge config objects ─────────────────────────────────
65
+ export fn mergeConfig(base, overrides):
66
+ if not overrides: return { ...base }
67
+ val result = { ...base }
68
+ for key in Object.keys(overrides):
69
+ val val_ = overrides[key]
70
+ if val_ != null and val_ != undefined:
71
+ result[key] = val_
72
+ return result
73
+
74
+ // ── Write flux.json ───────────────────────────────────────────
75
+ export fn writeConfig(config, cwd_):
76
+ val cwd = cwd_ ?? process.cwd()
77
+ val jsonPath = Path.join(cwd, "flux.json")
78
+ val content = JSON.stringify(config, null, 2) + "\n"
79
+ Fs.writeFileSync(jsonPath, content, "utf8")
80
+
81
+ // ── Validate config ───────────────────────────────────────────
82
+ export fn validateConfig(config):
83
+ val errors = []
84
+
85
+ if config.entry and not config.entry.endsWith(".flux"):
86
+ errors.push("entry must be a .flux file")
87
+
88
+ if config.jsxTarget and not ["browser", "server", "react"].includes(config.jsxTarget):
89
+ errors.push("jsxTarget must be 'browser', 'server', or 'react'")
90
+
91
+ return {
92
+ valid: errors.length == 0,
93
+ errors: errors,
94
+ }
95
+
96
+ // ── Read flux.json package info ───────────────────────────────
97
+ export fn readPackage(cwd_):
98
+ val cwd = cwd_ ?? process.cwd()
99
+ val fluxJson = Path.join(cwd, "flux.json")
100
+ val pkgJson = Path.join(cwd, "package.json")
101
+
102
+ if Fs.existsSync(fluxJson):
103
+ try:
104
+ return JSON.parse(Fs.readFileSync(fluxJson, "utf8"))
105
+ catch(e): return null
106
+
107
+ if Fs.existsSync(pkgJson):
108
+ try:
109
+ return JSON.parse(Fs.readFileSync(pkgJson, "utf8"))
110
+ catch(e2): return null
111
+
112
+ return null
@@ -0,0 +1,99 @@
1
+ // ── Flux stdlib ──
2
+
3
+ function join(arr, sep) { return arr.join(sep != null ? sep : ','); }
4
+
5
+ function includes(arr, val) { return arr.includes(val); }
6
+
7
+ function keys(obj) { return Object.keys(obj); }
8
+
9
+ function endsWith(s, suffix) { return String(s).endsWith(suffix); }
10
+ // ── end stdlib ──
11
+
12
+ // Generated by Flux Transpiler v3.2.0
13
+ "use strict";
14
+
15
+ const Fs = require("fs");
16
+ 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: { } } };
18
+ module.exports.DEFAULT_CONFIG = DEFAULT_CONFIG;
19
+ function loadConfig(cwd_) {
20
+ const cwd = (cwd_ ?? process.cwd());
21
+ const jsonPath = Path.join(cwd, "flux.json");
22
+ if (Fs.existsSync(jsonPath)) {
23
+ try {
24
+ const raw = Fs.readFileSync(jsonPath, "utf8");
25
+ const parsed = JSON.parse(raw);
26
+ return mergeConfig(DEFAULT_CONFIG, parsed);
27
+ }
28
+ catch (e) {
29
+ throw new Error(("Invalid flux.json: " + e.message));
30
+ }
31
+ }
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
+ return { ...DEFAULT_CONFIG };
43
+ }
44
+ module.exports.loadConfig = loadConfig;
45
+ function mergeConfig(base, overrides) {
46
+ if (!overrides) {
47
+ return { ...base };
48
+ }
49
+ const result = { ...base };
50
+ for (const key of Object.keys(overrides)) {
51
+ const val_ = overrides[key];
52
+ if (((val_ != null) && (val_ != undefined))) {
53
+ result[key] = val_;
54
+ }
55
+ }
56
+ return result;
57
+ }
58
+ module.exports.mergeConfig = mergeConfig;
59
+ function writeConfig(config, cwd_) {
60
+ 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");
64
+ }
65
+ module.exports.writeConfig = writeConfig;
66
+ function validateConfig(config) {
67
+ const errors = [];
68
+ if ((config.entry && !config.entry.endsWith(".flux"))) {
69
+ errors.push("entry must be a .flux file");
70
+ }
71
+ if ((config.jsxTarget && !["browser", "server", "react"].includes(config.jsxTarget))) {
72
+ errors.push("jsxTarget must be 'browser', 'server', or 'react'");
73
+ }
74
+ return { valid: (errors.length == 0), errors };
75
+ }
76
+ module.exports.validateConfig = validateConfig;
77
+ function readPackage(cwd_) {
78
+ const cwd = (cwd_ ?? process.cwd());
79
+ const fluxJson = Path.join(cwd, "flux.json");
80
+ const pkgJson = Path.join(cwd, "package.json");
81
+ if (Fs.existsSync(fluxJson)) {
82
+ try {
83
+ return JSON.parse(Fs.readFileSync(fluxJson, "utf8"));
84
+ }
85
+ catch (e) {
86
+ return null;
87
+ }
88
+ }
89
+ if (Fs.existsSync(pkgJson)) {
90
+ try {
91
+ return JSON.parse(Fs.readFileSync(pkgJson, "utf8"));
92
+ }
93
+ catch (e2) {
94
+ return null;
95
+ }
96
+ }
97
+ return null;
98
+ }
99
+ module.exports.readPackage = readPackage;
@@ -1,9 +1,15 @@
1
1
  // ── Flux stdlib ──
2
2
 
3
3
  function includes(arr, val) { return arr.includes(val); }
4
+
5
+ function trim(s) { return String(s).trim(); }
6
+
7
+ function startsWith(s, prefix) { return String(s).startsWith(prefix); }
8
+
9
+ function repeat(s, n) { return String(s).repeat(n); }
4
10
  // ── end stdlib ──
5
11
 
6
- // Generated by Flux Transpiler v3.1.0
12
+ // Generated by Flux Transpiler v3.2.0
7
13
  "use strict";
8
14
 
9
15
  const CSS_PROP_MAP = { bg: "background", fg: "color", p: "padding", px: "padding-inline", py: "padding-block", pt: "padding-top", pb: "padding-bottom", pl: "padding-left", pr: "padding-right", m: "margin", mx: "margin-inline", my: "margin-block", mt: "margin-top", mb: "margin-bottom", ml: "margin-left", mr: "margin-right", radius: "border-radius", w: "width", h: "height", "min-w": "min-width", "max-w": "max-width", "min-h": "min-height", "max-h": "max-height", gap: "gap", "col-gap": "column-gap", "row-gap": "row-gap", text: "font-size", font: "font-family", weight: "font-weight", tracking: "letter-spacing", leading: "line-height", shadow: "box-shadow", opacity: "opacity", border: "border", outline: "outline", transition: "transition", cursor: "cursor", overflow: "overflow", "overflow-x": "overflow-x", "overflow-y": "overflow-y", z: "z-index", transform: "transform", content: "content", resize: "resize", appearance: "appearance", "object-fit": "object-fit", "accent-color": "accent-color", direction: "flex-direction", wrap: "flex-wrap", align: "align-items", justify: "justify-content", "align-self": "align-self", "justify-self": "justify-self", grow: "flex-grow", shrink: "flex-shrink", basis: "flex-basis", order: "order", cols: "grid-template-columns", rows: "grid-template-rows", "col-span": "grid-column", "row-span": "grid-row", "place-items": "place-items", "place-content": "place-content", "list-style": "list-style", "text-align": "text-align", decoration: "text-decoration", "text-transform": "text-transform", "white-space": "white-space", "word-break": "word-break", "user-select": "user-select", "pointer-events": "pointer-events", "vertical-align": "vertical-align", backdrop: "backdrop-filter", filter: "filter", clip: "clip-path", animation: "animation", position: "position", top: "top", right: "right", bottom: "bottom", left: "left", inset: "inset", color: "color", background: "background" };
@@ -3,9 +3,28 @@
3
3
  function map(arr, fn) { return arr.map(fn); }
4
4
 
5
5
  function join(arr, sep) { return arr.join(sep != null ? sep : ','); }
6
+
7
+ function max(arr) {
8
+ if (arguments.length > 1) return Math.max.apply(null, arguments);
9
+ return Math.max.apply(null, arr);
10
+ }
11
+
12
+ function round(n, decimals) {
13
+ if (decimals == null) return Math.round(n);
14
+ var f = Math.pow(10, decimals);
15
+ return Math.round(n * f) / f;
16
+ }
17
+
18
+ function trimStart(s) { return String(s).trimStart(); }
19
+
20
+ function trimEnd(s) { return String(s).trimEnd(); }
21
+
22
+ function startsWith(s, prefix) { return String(s).startsWith(prefix); }
23
+
24
+ function repeat(s, n) { return String(s).repeat(n); }
6
25
  // ── end stdlib ──
7
26
 
8
- // Generated by Flux Transpiler v3.1.0
27
+ // Generated by Flux Transpiler v3.2.0
9
28
  "use strict";
10
29
 
11
30
  function normalizeOperators(line) {
package/src/self/jsx.js CHANGED
@@ -20,6 +20,12 @@ function join(arr, sep) { return arr.join(sep != null ? sep : ','); }
20
20
  function flat(arr, depth) { return arr.flat(depth != null ? depth : 1); }
21
21
 
22
22
  function includes(arr, val) { return arr.includes(val); }
23
+
24
+ function entries(obj) { return Object.entries(obj); }
25
+
26
+ function trim(s) { return String(s).trim(); }
27
+
28
+ function startsWith(s, prefix) { return String(s).startsWith(prefix); }
23
29
  // ── end stdlib ──
24
30
 
25
31
 
package/src/self/lexer.js CHANGED
@@ -5,9 +5,13 @@ function map(arr, fn) { return arr.map(fn); }
5
5
  function some(arr, fn) { return arr.some(fn); }
6
6
 
7
7
  function join(arr, sep) { return arr.join(sep != null ? sep : ','); }
8
+
9
+ function trim(s) { return String(s).trim(); }
10
+
11
+ function trimEnd(s) { return String(s).trimEnd(); }
8
12
  // ── end stdlib ──
9
13
 
10
- // Generated by Flux Transpiler v3.1.0
14
+ // Generated by Flux Transpiler v3.2.0
11
15
  "use strict";
12
16
 
13
17
  const T = { NUMBER: "NUMBER", STRING: "STRING", BOOL: "BOOL", NULL: "NULL", IDENT: "IDENT", VAR: "VAR", VAL: "VAL", FN: "FN", RETURN: "RETURN", IF: "IF", ELSE: "ELSE", FOR: "FOR", IN: "IN", WHILE: "WHILE", BREAK: "BREAK", CONTINUE: "CONTINUE", DO: "DO", CLASS: "CLASS", EXTENDS: "EXTENDS", SELF: "SELF", NEW: "NEW", INTERFACE: "INTERFACE", IMPLEMENTS: "IMPLEMENTS", PRIVATE: "PRIVATE", PUBLIC: "PUBLIC", PROTECTED: "PROTECTED", READONLY: "READONLY", STATIC: "STATIC", ABSTRACT: "ABSTRACT", OVERRIDE: "OVERRIDE", MATCH: "MATCH", WHEN: "WHEN", IMPORT: "IMPORT", EXPORT: "EXPORT", FROM: "FROM", AS: "AS", DEFAULT: "DEFAULT", AND: "AND", OR: "OR", NOT: "NOT", ASYNC: "ASYNC", AWAIT: "AWAIT", TRY: "TRY", CATCH: "CATCH", FINALLY: "FINALLY", THROW: "THROW", TYPEOF: "TYPEOF", INSTANCEOF: "INSTANCEOF", TYPE: "TYPE", ENUM: "ENUM", SATISFIES: "SATISFIES", IS: "IS", CONST: "CONST", PLUS: "PLUS", MINUS: "MINUS", STAR: "STAR", SLASH: "SLASH", PERCENT: "PERCENT", REGEX: "REGEX", STARSTAR: "STARSTAR", EQ: "EQ", EQEQ: "EQEQ", NEQ: "NEQ", EQEQEQ: "EQEQEQ", NEQEQ: "NEQEQ", LT: "LT", LTE: "LTE", GT: "GT", GTE: "GTE", PLUSEQ: "PLUSEQ", MINUSEQ: "MINUSEQ", STAREQ: "STAREQ", SLASHEQ: "SLASHEQ", PERCENTEQ: "PERCENTEQ", PLUSPLUS: "PLUSPLUS", MINUSMINUS: "MINUSMINUS", AMPERSAND: "AMPERSAND", ANDAND: "ANDAND", PIPEB: "PIPEB", OROR: "OROR", CARET: "CARET", TILDE: "TILDE", LSHIFT: "LSHIFT", RSHIFT: "RSHIFT", ARROW: "ARROW", FATARROW: "FATARROW", PIPE: "PIPE", DOTDOT: "DOTDOT", DOTDOTDOT: "DOTDOTDOT", WILDCARD: "WILDCARD", NULLISH: "NULLISH", QUESTIONDOT: "QUESTIONDOT", BANG: "BANG", AT: "AT", LPAREN: "LPAREN", RPAREN: "RPAREN", LBRACKET: "LBRACKET", RBRACKET: "RBRACKET", LBRACE: "LBRACE", RBRACE: "RBRACE", COMMA: "COMMA", DOT: "DOT", COLON: "COLON", QUESTION: "QUESTION", NEWLINE: "NEWLINE", INDENT: "INDENT", DEDENT: "DEDENT", EOF: "EOF" };
@@ -1,9 +1,13 @@
1
1
  // ── Flux stdlib ──
2
2
 
3
3
  function sort(arr, fn) { return arr.slice().sort(fn); }
4
+
5
+ function values(obj) { return Object.values(obj); }
6
+
7
+ function startsWith(s, prefix) { return String(s).startsWith(prefix); }
4
8
  // ── end stdlib ──
5
9
 
6
- // Generated by Flux Transpiler v3.1.0
10
+ // Generated by Flux Transpiler v3.2.0
7
11
  "use strict";
8
12
 
9
13
  const { lexerize } = require("./lexer");
@@ -15,6 +15,8 @@ function map(arr, fn) { return arr.map(fn); }
15
15
 
16
16
  function forEach(arr, fn) { arr.forEach(fn); return arr; }
17
17
 
18
+ function floor(n) { return Math.floor(n); }
19
+
18
20
  function fromEntries(entries) {
19
21
  return Object.fromEntries(entries);
20
22
  }
@@ -5,7 +5,7 @@ function map(arr, fn) { return arr.map(fn); }
5
5
  function join(arr, sep) { return arr.join(sep != null ? sep : ','); }
6
6
  // ── end stdlib ──
7
7
 
8
- // Generated by Flux Transpiler v3.1.0
8
+ // Generated by Flux Transpiler v3.2.0
9
9
  "use strict";
10
10
 
11
11
  const { T } = require("./lexer");
@@ -0,0 +1,301 @@
1
+ // ============================================================
2
+ // Flux Package Manager — fluxpkg
3
+ // src/self/pkg.flux — written in Flux, compiled by stage-0
4
+ //
5
+ // Commands:
6
+ // flux add <pkg[@version]> install a flux package
7
+ // flux remove <pkg> remove a package
8
+ // flux install install all from flux.json
9
+ // flux list list installed packages
10
+ // flux search <query> search the registry
11
+ // flux publish publish to registry
12
+ // flux info <pkg> show package info
13
+ // ============================================================
14
+
15
+ import Fs from "fs"
16
+ import Path from "path"
17
+ import Https from "https"
18
+ import Http from "http"
19
+ import Os from "os"
20
+ import { readPackage, writeConfig, loadConfig } from "./config"
21
+
22
+ val REGISTRY_URL = "https://registry.npmjs.org"
23
+ val FLUX_PKG_DIR = "flux_modules"
24
+ val PKG_FILE = "flux.json"
25
+
26
+ // ── ANSI colors ───────────────────────────────────────────────
27
+ val C = {
28
+ reset: "\x1b[0m",
29
+ bold: "\x1b[1m",
30
+ dim: "\x1b[2m",
31
+ red: "\x1b[31m",
32
+ green: "\x1b[32m",
33
+ yellow: "\x1b[33m",
34
+ blue: "\x1b[34m",
35
+ cyan: "\x1b[36m",
36
+ gray: "\x1b[90m",
37
+ }
38
+
39
+ fn clr(c, s): return c + s + C.reset
40
+ fn ok(msg): console.log(clr(C.green, "✓ ") + msg)
41
+ fn err(msg): console.error(clr(C.red, "✗ ") + msg)
42
+ fn info(msg): console.log(clr(C.cyan, " ") + msg)
43
+
44
+ // ── HTTP fetch (Node built-in, no dependencies) ───────────────
45
+ async fn fetchJson(url):
46
+ val mod = url.startsWith("https") ? Https : Http
47
+ fn doRequest(resolve, reject):
48
+ var data = ""
49
+ fn onData(chunk): data = data + chunk
50
+ fn onEnd():
51
+ try:
52
+ resolve(JSON.parse(data))
53
+ catch(e):
54
+ reject(new Error("Invalid JSON from registry: " + e.message))
55
+ fn onRes(res):
56
+ res.on("data", onData)
57
+ res.on("end", onEnd)
58
+ mod.get(url, { headers: { "User-Agent": "flux-pkg/1.0" } }, onRes).on("error", reject)
59
+ return new Promise(doRequest)
60
+
61
+ // ── Ensure flux.json exists ───────────────────────────────────
62
+ fn ensureFluxJson(cwd_):
63
+ val cwd = cwd_ ?? process.cwd()
64
+ val file = Path.join(cwd, PKG_FILE)
65
+ if not Fs.existsSync(file):
66
+ val pkg = {
67
+ name: Path.basename(cwd),
68
+ version: "1.0.0",
69
+ description: "",
70
+ author: "",
71
+ license: "MIT",
72
+ scripts: {
73
+ start: "flux run src/main.flux",
74
+ build: "flux bundle src/main.flux -o dist/bundle.js",
75
+ dev: "flux watch src/main.flux",
76
+ check: "flux check src/main.flux",
77
+ },
78
+ dependencies: {},
79
+ devDependencies: {},
80
+ }
81
+ Fs.writeFileSync(file, JSON.stringify(pkg, null, 2) + "\n", "utf8")
82
+ ok("Created flux.json")
83
+ return JSON.parse(Fs.readFileSync(file, "utf8"))
84
+
85
+ // ── Save flux.json ────────────────────────────────────────────
86
+ fn saveFluxJson(pkg, cwd_):
87
+ val cwd = cwd_ ?? process.cwd()
88
+ val file = Path.join(cwd, PKG_FILE)
89
+ Fs.writeFileSync(file, JSON.stringify(pkg, null, 2) + "\n", "utf8")
90
+
91
+ // ── Resolve package name@version ─────────────────────────────
92
+ fn parsePackageSpec(spec):
93
+ val atIdx = spec.lastIndexOf("@")
94
+ if atIdx > 0:
95
+ return { name: spec.slice(0, atIdx), version: spec.slice(atIdx + 1) }
96
+ return { name: spec, version: "latest" }
97
+
98
+ // ── flux add <pkg[@version]> [--dev] ─────────────────────────
99
+ export async fn cmdAdd(specs, opts):
100
+ val isDev = opts?.dev ?? false
101
+ val cwd = process.cwd()
102
+ val pkg = ensureFluxJson(cwd)
103
+
104
+ for spec in specs:
105
+ val { name, version } = parsePackageSpec(spec)
106
+ console.log(clr(C.cyan, "\n Adding ") + clr(C.bold, name) + clr(C.gray, "@" + version) + " ...")
107
+
108
+ try:
109
+ val info_ = await fetchJson(REGISTRY_URL + "/" + name)
110
+ val resolvedVersion = version == "latest" ? (info_["dist-tags"]?.latest ?? "1.0.0") : version
111
+
112
+ val versionInfo = info_.versions?.[resolvedVersion]
113
+ if not versionInfo:
114
+ err("Version " + resolvedVersion + " not found for " + name)
115
+ continue
116
+
117
+ val depKey = isDev ? "devDependencies" : "dependencies"
118
+ if not pkg[depKey]: pkg[depKey] = {}
119
+ pkg[depKey][name] = "^" + resolvedVersion
120
+
121
+ saveFluxJson(pkg, cwd)
122
+
123
+ val desc = versionInfo.description ? clr(C.gray, " — " + versionInfo.description) : ""
124
+ ok(name + clr(C.green, "@" + resolvedVersion) + desc)
125
+ info("Added to " + (isDev ? "devDependencies" : "dependencies"))
126
+
127
+ catch(e):
128
+ err("Failed to fetch " + name + ": " + e.message)
129
+
130
+ console.log()
131
+ console.log(clr(C.gray, " Run ") + clr(C.yellow, "flux install") + clr(C.gray, " to install packages"))
132
+ console.log()
133
+
134
+ // ── flux remove <pkg> ─────────────────────────────────────────
135
+ export fn cmdRemove(names, opts):
136
+ val cwd = process.cwd()
137
+ val pkg = ensureFluxJson(cwd)
138
+ var removed = 0
139
+
140
+ for name in names:
141
+ var found = false
142
+ if pkg.dependencies and pkg.dependencies[name]:
143
+ delete pkg.dependencies[name]
144
+ found = true
145
+ if pkg.devDependencies and pkg.devDependencies[name]:
146
+ delete pkg.devDependencies[name]
147
+ found = true
148
+
149
+ if found:
150
+ ok("Removed " + clr(C.bold, name))
151
+ removed = removed + 1
152
+ else:
153
+ err(name + " not found in flux.json")
154
+
155
+ if removed > 0:
156
+ saveFluxJson(pkg, cwd)
157
+
158
+ // ── flux install ──────────────────────────────────────────────
159
+ export async fn cmdInstall(opts):
160
+ val cwd = process.cwd()
161
+ val pkg = ensureFluxJson(cwd)
162
+
163
+ val deps = pkg.dependencies ?? {}
164
+ val devDeps = pkg.devDependencies ?? {}
165
+ val all = { ...deps, ...devDeps }
166
+ val names = Object.keys(all)
167
+
168
+ if names.length == 0:
169
+ info("No dependencies found in flux.json")
170
+ return
171
+
172
+ console.log(clr(C.cyan, "\n Installing ") + names.length + " package(s)...\n")
173
+
174
+ val modDir = Path.join(cwd, FLUX_PKG_DIR)
175
+ if not Fs.existsSync(modDir): Fs.mkdirSync(modDir, { recursive: true })
176
+
177
+ for name in names:
178
+ val spec = all[name]
179
+ val version = spec.replace(/[\^~>=<]/g, "").split(" ")[0]
180
+ console.log(clr(C.gray, " ○ ") + name + clr(C.gray, "@" + version))
181
+
182
+ console.log()
183
+ ok("flux.json dependencies registered")
184
+ info("Note: Use npm/pnpm to install Node.js compatible packages")
185
+ console.log()
186
+
187
+ // ── flux list ─────────────────────────────────────────────────
188
+ export fn cmdList(opts):
189
+ val cwd = process.cwd()
190
+ val pkg = readPackage(cwd)
191
+
192
+ if not pkg:
193
+ err("No flux.json found. Run: flux init")
194
+ return
195
+
196
+ console.log()
197
+ console.log(clr(C.bold, " " + (pkg.name ?? "project") + clr(C.gray, "@" + (pkg.version ?? "1.0.0"))))
198
+ console.log()
199
+
200
+ val deps = pkg.dependencies ?? {}
201
+ val devDeps = pkg.devDependencies ?? {}
202
+
203
+ if Object.keys(deps).length > 0:
204
+ console.log(clr(C.cyan, " dependencies:"))
205
+ for name in Object.keys(deps):
206
+ console.log(" " + clr(C.green, name) + clr(C.gray, " " + deps[name]))
207
+ console.log()
208
+
209
+ if Object.keys(devDeps).length > 0:
210
+ console.log(clr(C.cyan, " devDependencies:"))
211
+ for name in Object.keys(devDeps):
212
+ console.log(" " + clr(C.blue, name) + clr(C.gray, " " + devDeps[name]))
213
+ console.log()
214
+
215
+ if Object.keys(deps).length == 0 and Object.keys(devDeps).length == 0:
216
+ info("No dependencies")
217
+ console.log()
218
+
219
+ // ── flux search <query> ───────────────────────────────────────
220
+ export async fn cmdSearch(query, opts):
221
+ console.log(clr(C.cyan, "\n Searching for ") + clr(C.bold, '"' + query + '"') + " ...\n")
222
+ try:
223
+ val url = REGISTRY_URL + "/-/v1/search?text=" + encodeURIComponent(query + " keywords:flux") + "&size=10"
224
+ val results = await fetchJson(url)
225
+ val objects = results.objects ?? []
226
+
227
+ if objects.length == 0:
228
+ info("No packages found for: " + query)
229
+ console.log()
230
+ return
231
+
232
+ for obj in objects:
233
+ val p = obj.package
234
+ console.log(" " + clr(C.bold, p.name) + clr(C.gray, " v" + p.version))
235
+ if p.description:
236
+ console.log(" " + clr(C.gray, p.description))
237
+ console.log(" " + clr(C.blue, p.links?.npm ?? ""))
238
+ console.log()
239
+
240
+ catch(e):
241
+ err("Search failed: " + e.message)
242
+
243
+ // ── flux info <pkg> ───────────────────────────────────────────
244
+ export async fn cmdInfo(name, opts):
245
+ console.log(clr(C.cyan, "\n Fetching info for ") + clr(C.bold, name) + " ...\n")
246
+ try:
247
+ val info_ = await fetchJson(REGISTRY_URL + "/" + name)
248
+ val latest = info_["dist-tags"]?.latest ?? "unknown"
249
+ val ver = info_.versions?.[latest] ?? {}
250
+
251
+ console.log(clr(C.bold, " " + name) + clr(C.gray, " v" + latest))
252
+ if ver.description: console.log(" " + ver.description)
253
+ console.log()
254
+ console.log(clr(C.gray, " license: ") + (ver.license ?? "unknown"))
255
+ console.log(clr(C.gray, " author: ") + (ver.author?.name ?? ver.author ?? "unknown"))
256
+
257
+ val homepage = ver.homepage ?? info_.homepage
258
+ if homepage: console.log(clr(C.gray, " home: ") + clr(C.blue, homepage))
259
+
260
+ val repo = ver.repository?.url ?? info_.repository?.url
261
+ if repo: console.log(clr(C.gray, " repo: ") + clr(C.blue, repo.replace("git+", "").replace(".git", "")))
262
+
263
+ val keywords = ver.keywords ?? []
264
+ if keywords.length > 0:
265
+ console.log(clr(C.gray, " tags: ") + keywords.slice(0, 8).join(", "))
266
+
267
+ console.log()
268
+ console.log(clr(C.gray, " Install: ") + clr(C.yellow, "flux add " + name))
269
+ console.log()
270
+
271
+ catch(e):
272
+ err("Could not fetch info for " + name + ": " + e.message)
273
+
274
+ // ── flux publish ──────────────────────────────────────────────
275
+ export fn cmdPublish(opts):
276
+ val cwd = process.cwd()
277
+ val pkg = readPackage(cwd)
278
+
279
+ if not pkg:
280
+ err("No flux.json found. Run: flux init")
281
+ return
282
+
283
+ console.log()
284
+ console.log(clr(C.cyan, " Publishing ") + clr(C.bold, pkg.name + "@" + pkg.version) + " ...")
285
+ console.log()
286
+ info("Building package...")
287
+ info("Checking flux.json...")
288
+
289
+ if not pkg.name:
290
+ err("flux.json must have a name field")
291
+ return
292
+
293
+ if not pkg.version:
294
+ err("flux.json must have a version field")
295
+ return
296
+
297
+ console.log()
298
+ ok("Package ready: " + pkg.name + "@" + pkg.version)
299
+ info("Run: npm publish to publish to the npm registry")
300
+ info("Run: flux publish --registry <url> for a custom registry")
301
+ console.log()