@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.
- package/CHANGELOG.md +40 -0
- package/README.md +112 -12
- package/dist/flux-cli.js +2 -2
- package/dist/flux.cjs.js +1 -1
- package/dist/flux.esm.js +1 -1
- package/dist/flux.min.js +1 -1
- package/package.json +1 -1
- package/src/self/bundler.js +7 -1
- package/src/self/checker.js +1 -1
- package/src/self/cli.flux +877 -0
- package/src/self/cli.js +818 -0
- package/src/self/codegen.js +11 -1
- package/src/self/config.flux +112 -0
- package/src/self/config.js +99 -0
- package/src/self/css-preprocessor.js +7 -1
- package/src/self/formatter.js +20 -1
- package/src/self/jsx.js +6 -0
- package/src/self/lexer.js +5 -1
- package/src/self/linter.js +5 -1
- package/src/self/mangler.js +2 -0
- package/src/self/parser.js +1 -1
- package/src/self/pkg.flux +301 -0
- package/src/self/pkg.js +288 -0
- package/src/self/sourcemap.js +1 -1
- package/src/self/stdlib.js +51 -36
- package/src/self/test-runner.js +7 -1
- package/src/self/transpiler.js +1 -1
- package/src/self/type-checker.js +9 -1
package/src/self/codegen.js
CHANGED
|
@@ -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.
|
|
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.
|
|
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" };
|
package/src/self/formatter.js
CHANGED
|
@@ -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.
|
|
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.
|
|
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" };
|
package/src/self/linter.js
CHANGED
|
@@ -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.
|
|
10
|
+
// Generated by Flux Transpiler v3.2.0
|
|
7
11
|
"use strict";
|
|
8
12
|
|
|
9
13
|
const { lexerize } = require("./lexer");
|
package/src/self/mangler.js
CHANGED
package/src/self/parser.js
CHANGED
|
@@ -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.
|
|
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()
|