@xnoxs/flux-lang 3.3.3 → 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 +46 -26
- package/dist/flux.cjs.js +45 -25
- package/dist/flux.esm.js +45 -25
- package/dist/flux.min.js +40 -40
- 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
|
@@ -0,0 +1,877 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// Flux Self-Hosted CLI — flux command line interface
|
|
3
|
+
// src/self/cli.flux — written in Flux, compiled by stage-0
|
|
4
|
+
//
|
|
5
|
+
// Self-hosted: this CLI is compiled by the Flux compiler itself.
|
|
6
|
+
// Run via: node bin/flux.js run src/self/cli.flux
|
|
7
|
+
// Or after bootstrap: FLUX_SELF_HOSTED=1 flux <command>
|
|
8
|
+
// ============================================================
|
|
9
|
+
|
|
10
|
+
import Fs from "fs"
|
|
11
|
+
import Path from "path"
|
|
12
|
+
import Os from "os"
|
|
13
|
+
import { transpile, FLUX_VERSION, FLUX_STAGE } from "./transpiler"
|
|
14
|
+
import { bundle } from "./bundler"
|
|
15
|
+
import { format } from "./formatter"
|
|
16
|
+
import { lint } from "./linter"
|
|
17
|
+
import { runTests } from "./test-runner"
|
|
18
|
+
import { loadConfig } from "./config"
|
|
19
|
+
import {
|
|
20
|
+
cmdAdd, cmdRemove, cmdInstall, cmdList,
|
|
21
|
+
cmdSearch, cmdInfo, cmdPublish
|
|
22
|
+
} from "./pkg"
|
|
23
|
+
|
|
24
|
+
// ── Version ───────────────────────────────────────────────────
|
|
25
|
+
val VERSION = FLUX_VERSION ?? "3.0.0"
|
|
26
|
+
val STAGE = FLUX_STAGE ?? "self-hosted"
|
|
27
|
+
|
|
28
|
+
// ── ANSI Colors ───────────────────────────────────────────────
|
|
29
|
+
val C = {
|
|
30
|
+
reset: "\x1b[0m",
|
|
31
|
+
bold: "\x1b[1m",
|
|
32
|
+
dim: "\x1b[2m",
|
|
33
|
+
red: "\x1b[31m",
|
|
34
|
+
green: "\x1b[32m",
|
|
35
|
+
yellow: "\x1b[33m",
|
|
36
|
+
blue: "\x1b[34m",
|
|
37
|
+
cyan: "\x1b[36m",
|
|
38
|
+
white: "\x1b[37m",
|
|
39
|
+
gray: "\x1b[90m",
|
|
40
|
+
magenta: "\x1b[35m",
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
val noColor = process.env.NO_COLOR or not process.stdout.isTTY
|
|
44
|
+
fn clr(c, s): return noColor ? s : c + s + C.reset
|
|
45
|
+
fn bold(s): return clr(C.bold, s)
|
|
46
|
+
fn gray(s): return clr(C.gray, s)
|
|
47
|
+
fn green(s): return clr(C.green, s)
|
|
48
|
+
fn red(s): return clr(C.red, s)
|
|
49
|
+
fn cyan(s): return clr(C.cyan, s)
|
|
50
|
+
fn yellow(s): return clr(C.yellow, s)
|
|
51
|
+
fn blue(s): return clr(C.blue, s)
|
|
52
|
+
|
|
53
|
+
// ── Banner ────────────────────────────────────────────────────
|
|
54
|
+
fn showBanner():
|
|
55
|
+
if noColor:
|
|
56
|
+
console.log("Flux Lang " + VERSION + " [" + STAGE + "]")
|
|
57
|
+
return
|
|
58
|
+
console.log(cyan(bold(`
|
|
59
|
+
███████╗██╗ ██╗ ██╗██╗ ██╗
|
|
60
|
+
██╔════╝██║ ██║ ██║╚██╗██╔╝
|
|
61
|
+
█████╗ ██║ ██║ ██║ ╚███╔╝
|
|
62
|
+
██╔══╝ ██║ ██║ ██║ ██╔██╗
|
|
63
|
+
██║ ███████╗╚██████╔╝██╔╝ ██╗
|
|
64
|
+
╚═╝ ╚══════╝ ╚═════╝ ╚═╝ ╚═╝`)))
|
|
65
|
+
console.log(gray(" Flux Lang v" + VERSION + " [" + STAGE + "] → JavaScript\n"))
|
|
66
|
+
|
|
67
|
+
// ── Help ──────────────────────────────────────────────────────
|
|
68
|
+
fn showHelp():
|
|
69
|
+
showBanner()
|
|
70
|
+
console.log(bold("USAGE:"))
|
|
71
|
+
console.log(" flux <command> [options]\n")
|
|
72
|
+
|
|
73
|
+
console.log(bold("COMPILER:"))
|
|
74
|
+
val compilerCmds = [
|
|
75
|
+
["compile <file.flux>", "Compile .flux → .js"],
|
|
76
|
+
["bundle <entry.flux>", "Bundle multiple files into one .js"],
|
|
77
|
+
["run <file.flux>", "Compile and run immediately"],
|
|
78
|
+
["watch <file.flux>", "Watch for changes, auto-compile"],
|
|
79
|
+
["check <file.flux>", "Type-check and static analysis"],
|
|
80
|
+
]
|
|
81
|
+
for [cmd, desc] in compilerCmds:
|
|
82
|
+
console.log(" " + green(("flux " + cmd).padEnd(36)) + " " + gray(desc))
|
|
83
|
+
|
|
84
|
+
console.log()
|
|
85
|
+
console.log(bold("TOOLING:"))
|
|
86
|
+
val toolCmds = [
|
|
87
|
+
["lint <file.flux>", "Full lint: types + style + immutability"],
|
|
88
|
+
["fmt <file.flux>", "Format source code in-place"],
|
|
89
|
+
["test [dir]", "Run *.test.flux files"],
|
|
90
|
+
["repl", "Interactive REPL mode"],
|
|
91
|
+
["tokens <file.flux>", "Show lexer token list"],
|
|
92
|
+
["ast <file.flux>", "Show Abstract Syntax Tree (JSON)"],
|
|
93
|
+
]
|
|
94
|
+
for [cmd, desc] in toolCmds:
|
|
95
|
+
console.log(" " + green(("flux " + cmd).padEnd(36)) + " " + gray(desc))
|
|
96
|
+
|
|
97
|
+
console.log()
|
|
98
|
+
console.log(bold("PACKAGE MANAGER:"))
|
|
99
|
+
val pkgCmds = [
|
|
100
|
+
["init [name]", "Scaffold a new Flux project"],
|
|
101
|
+
["add <pkg[@version]>", "Add a dependency"],
|
|
102
|
+
["remove <pkg>", "Remove a dependency"],
|
|
103
|
+
["install", "Install all dependencies"],
|
|
104
|
+
["list", "List installed packages"],
|
|
105
|
+
["search <query>", "Search the package registry"],
|
|
106
|
+
["info <pkg>", "Show package details"],
|
|
107
|
+
["publish", "Publish package to registry"],
|
|
108
|
+
]
|
|
109
|
+
for [cmd, desc] in pkgCmds:
|
|
110
|
+
console.log(" " + cyan(("flux " + cmd).padEnd(36)) + " " + gray(desc))
|
|
111
|
+
|
|
112
|
+
console.log()
|
|
113
|
+
console.log(bold("SELF-HOSTED:"))
|
|
114
|
+
val selfCmds = [
|
|
115
|
+
["self-hosted", "Show self-hosted compiler status"],
|
|
116
|
+
["self-hosted build", "Bootstrap: compile compiler with itself"],
|
|
117
|
+
["self-hosted verify", "Verify self-hosted output matches stage-0"],
|
|
118
|
+
]
|
|
119
|
+
for [cmd, desc] in selfCmds:
|
|
120
|
+
console.log(" " + yellow(("flux " + cmd).padEnd(36)) + " " + gray(desc))
|
|
121
|
+
|
|
122
|
+
console.log()
|
|
123
|
+
console.log(bold("OPTIONS:"))
|
|
124
|
+
console.log(" " + yellow("--out, -o <file> ") + " Output file")
|
|
125
|
+
console.log(" " + yellow("--sourcemap, -m ") + " Generate .js.map")
|
|
126
|
+
console.log(" " + yellow("--watch, -w ") + " Watch mode")
|
|
127
|
+
console.log(" " + yellow("--mangle ") + " Minify identifiers")
|
|
128
|
+
console.log(" " + yellow("--typecheck ") + " Enable type checking")
|
|
129
|
+
console.log(" " + yellow("--stdout ") + " Print to terminal")
|
|
130
|
+
console.log(" " + yellow("--no-color ") + " Disable colors")
|
|
131
|
+
console.log()
|
|
132
|
+
|
|
133
|
+
// ── Parse CLI args ────────────────────────────────────────────
|
|
134
|
+
fn parseArgs(argv):
|
|
135
|
+
val args = argv.slice(2)
|
|
136
|
+
val opts = {
|
|
137
|
+
out: null,
|
|
138
|
+
sourcemap: false,
|
|
139
|
+
mangle: false,
|
|
140
|
+
typecheck: false,
|
|
141
|
+
strict: false,
|
|
142
|
+
stdout: false,
|
|
143
|
+
watch: false,
|
|
144
|
+
dev: false,
|
|
145
|
+
verbose: false,
|
|
146
|
+
jsx: false,
|
|
147
|
+
jsxTarget: "browser",
|
|
148
|
+
}
|
|
149
|
+
val positional = []
|
|
150
|
+
var i = 0
|
|
151
|
+
while i < args.length:
|
|
152
|
+
val a = args[i]
|
|
153
|
+
if a == "--out" or a == "-o":
|
|
154
|
+
i = i + 1
|
|
155
|
+
opts.out = args[i]
|
|
156
|
+
else if a == "--sourcemap" or a == "-m":
|
|
157
|
+
opts.sourcemap = true
|
|
158
|
+
else if a == "--mangle":
|
|
159
|
+
opts.mangle = true
|
|
160
|
+
else if a == "--typecheck" or a == "-t":
|
|
161
|
+
opts.typecheck = true
|
|
162
|
+
else if a == "--strict":
|
|
163
|
+
opts.strict = true
|
|
164
|
+
else if a == "--stdout":
|
|
165
|
+
opts.stdout = true
|
|
166
|
+
else if a == "--watch" or a == "-w":
|
|
167
|
+
opts.watch = true
|
|
168
|
+
else if a == "--dev":
|
|
169
|
+
opts.dev = true
|
|
170
|
+
else if a == "--verbose" or a == "-v":
|
|
171
|
+
opts.verbose = true
|
|
172
|
+
else if a == "--jsx":
|
|
173
|
+
opts.jsx = true
|
|
174
|
+
else if a == "--jsx-target":
|
|
175
|
+
i = i + 1
|
|
176
|
+
opts.jsxTarget = args[i]
|
|
177
|
+
else if not a.startsWith("--"):
|
|
178
|
+
positional.push(a)
|
|
179
|
+
i = i + 1
|
|
180
|
+
return { positional, opts }
|
|
181
|
+
|
|
182
|
+
// ── Read file or exit ─────────────────────────────────────────
|
|
183
|
+
fn readFluxFile(filePath):
|
|
184
|
+
val abs = Path.resolve(filePath)
|
|
185
|
+
if not Fs.existsSync(abs):
|
|
186
|
+
console.error(red("✗ File not found: " + abs))
|
|
187
|
+
process.exit(1)
|
|
188
|
+
if not filePath.endsWith(".flux"):
|
|
189
|
+
console.warn(yellow("⚠ Not a .flux file: " + filePath))
|
|
190
|
+
return { source: Fs.readFileSync(abs, "utf8"), abs }
|
|
191
|
+
|
|
192
|
+
// ── Derive output path ────────────────────────────────────────
|
|
193
|
+
fn deriveOutPath(inputPath, outFlag):
|
|
194
|
+
if outFlag: return Path.resolve(outFlag)
|
|
195
|
+
val base = Path.basename(inputPath, ".flux")
|
|
196
|
+
return Path.join(Path.dirname(Path.resolve(inputPath)), base + ".js")
|
|
197
|
+
|
|
198
|
+
// ── Error renderer ────────────────────────────────────────────
|
|
199
|
+
val ERROR_KIND = {
|
|
200
|
+
ParseError: "Syntax error",
|
|
201
|
+
LexerError: "Syntax error",
|
|
202
|
+
CheckError: "Static error",
|
|
203
|
+
TypeCheckError: "Type error",
|
|
204
|
+
TypeError: "Type error",
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
fn printErrors(errors, source, filePath):
|
|
208
|
+
val lines = source.split("\n")
|
|
209
|
+
for err in errors:
|
|
210
|
+
val kind = ERROR_KIND[err.name] ?? "Error"
|
|
211
|
+
val stage = err.stage ? gray(" [" + err.stage + "]") : ""
|
|
212
|
+
console.error()
|
|
213
|
+
console.error(red(bold(kind)) + stage)
|
|
214
|
+
if err.line:
|
|
215
|
+
val fileLabel = filePath ? cyan(Path.relative(process.cwd(), filePath)) : ""
|
|
216
|
+
val locLabel = yellow(err.line + ":" + (err.col ?? 1))
|
|
217
|
+
if fileLabel: console.error(" " + fileLabel + ":" + locLabel)
|
|
218
|
+
else: console.error(" Line " + locLabel)
|
|
219
|
+
console.error(" " + err.message)
|
|
220
|
+
if err.line and err.line <= lines.length:
|
|
221
|
+
val errLineIdx = err.line - 1
|
|
222
|
+
val col = Math.max(0, (err.col ?? 1) - 1)
|
|
223
|
+
val tokLen = Math.max(1, err.len ?? 1)
|
|
224
|
+
if errLineIdx > 0 and lines[errLineIdx - 1].trim() != "":
|
|
225
|
+
val prev = String(err.line - 1).padStart(4)
|
|
226
|
+
console.error(gray(" " + prev + " │ " + lines[errLineIdx - 1]))
|
|
227
|
+
val lineNum = String(err.line).padStart(4)
|
|
228
|
+
console.error(gray(" " + lineNum + " │ ") + lines[errLineIdx])
|
|
229
|
+
val squiggle = "^" + "~".repeat(Math.max(0, tokLen - 1))
|
|
230
|
+
val pointer = " ".repeat(col) + red(squiggle)
|
|
231
|
+
console.error(gray(" │ ") + pointer)
|
|
232
|
+
if errLineIdx + 1 < lines.length and lines[errLineIdx + 1].trim() != "":
|
|
233
|
+
val next = String(err.line + 1).padStart(4)
|
|
234
|
+
console.error(gray(" " + next + " │ " + lines[errLineIdx + 1]))
|
|
235
|
+
if err.hint:
|
|
236
|
+
console.error(cyan(" Hint: ") + gray(err.hint))
|
|
237
|
+
console.error()
|
|
238
|
+
|
|
239
|
+
// ══════════════════════════════════════════════════════════════
|
|
240
|
+
// Commands
|
|
241
|
+
// ══════════════════════════════════════════════════════════════
|
|
242
|
+
|
|
243
|
+
// ── flux compile ──────────────────────────────────────────────
|
|
244
|
+
fn cmdCompile(filePath, opts):
|
|
245
|
+
val { source, abs } = readFluxFile(filePath)
|
|
246
|
+
val cfg = loadConfig(Path.dirname(abs))
|
|
247
|
+
val outPath = deriveOutPath(filePath, opts.out)
|
|
248
|
+
val mapPath = outPath + ".map"
|
|
249
|
+
val t0 = Date.now()
|
|
250
|
+
|
|
251
|
+
val result = transpile(source, {
|
|
252
|
+
sourcemap: opts.sourcemap ?? cfg.sourcemap,
|
|
253
|
+
mangle: opts.mangle ?? cfg.mangle,
|
|
254
|
+
typecheck: opts.typecheck ?? cfg.typecheck,
|
|
255
|
+
jsx: opts.jsx ?? cfg.jsx,
|
|
256
|
+
jsxTarget: opts.jsxTarget ?? cfg.jsxTarget,
|
|
257
|
+
sourceFile: Path.relative(Path.dirname(outPath), abs),
|
|
258
|
+
outputFile: Path.basename(outPath),
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
if not result.success:
|
|
262
|
+
console.error(red("\n✗ Compile failed — " + result.errors.length + " error(s)"))
|
|
263
|
+
printErrors(result.errors, source, abs)
|
|
264
|
+
process.exit(1)
|
|
265
|
+
|
|
266
|
+
val elapsed = Date.now() - t0
|
|
267
|
+
|
|
268
|
+
if opts.stdout:
|
|
269
|
+
console.log(result.output)
|
|
270
|
+
return
|
|
271
|
+
|
|
272
|
+
Fs.writeFileSync(outPath, result.output, "utf8")
|
|
273
|
+
|
|
274
|
+
var extra = ""
|
|
275
|
+
if opts.sourcemap and result.sourceMap:
|
|
276
|
+
Fs.writeFileSync(mapPath, result.sourceMap, "utf8")
|
|
277
|
+
extra = gray(" + " + Path.relative(process.cwd(), mapPath))
|
|
278
|
+
|
|
279
|
+
val rel = Path.relative(process.cwd(), abs)
|
|
280
|
+
val relO = Path.relative(process.cwd(), outPath)
|
|
281
|
+
console.log(
|
|
282
|
+
green("✓ ") + gray("(" + elapsed + "ms) ") +
|
|
283
|
+
blue(rel) + gray(" → ") + cyan(relO) + extra
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
if result.typeErrors and result.typeErrors.length > 0:
|
|
287
|
+
console.warn(yellow("\n⚠ " + result.typeErrors.length + " type warning(s)"))
|
|
288
|
+
printErrors(result.typeErrors, source, abs)
|
|
289
|
+
|
|
290
|
+
// ── flux run ──────────────────────────────────────────────────
|
|
291
|
+
fn cmdRun(filePath, opts):
|
|
292
|
+
val { source, abs } = readFluxFile(filePath)
|
|
293
|
+
val result = transpile(source, {
|
|
294
|
+
jsx: opts.jsx ?? false,
|
|
295
|
+
jsxTarget: opts.jsxTarget ?? "browser",
|
|
296
|
+
mangle: false,
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
if not result.success:
|
|
300
|
+
console.error(red("\n✗ Compile error"))
|
|
301
|
+
printErrors(result.errors, source, abs)
|
|
302
|
+
process.exit(1)
|
|
303
|
+
|
|
304
|
+
val tmpPath = Path.join(Os.tmpdir(), "_flux_run_" + Date.now() + ".js")
|
|
305
|
+
Fs.writeFileSync(tmpPath, result.output, "utf8")
|
|
306
|
+
console.log(gray("▶ Running " + Path.basename(abs) + " ...\n"))
|
|
307
|
+
try:
|
|
308
|
+
require(tmpPath)
|
|
309
|
+
catch(e):
|
|
310
|
+
console.error(red("\n[Runtime Error] " + e.message))
|
|
311
|
+
process.exitCode = 1
|
|
312
|
+
finally:
|
|
313
|
+
try: Fs.unlinkSync(tmpPath)
|
|
314
|
+
catch(e2): null
|
|
315
|
+
|
|
316
|
+
// ── flux check ───────────────────────────────────────────────
|
|
317
|
+
fn runCheck(abs):
|
|
318
|
+
val source = Fs.readFileSync(abs, "utf8")
|
|
319
|
+
val baseName = Path.basename(abs)
|
|
320
|
+
val result = transpile(source, { check: true, typecheck: true })
|
|
321
|
+
|
|
322
|
+
if not result.success:
|
|
323
|
+
val n = result.errors.length
|
|
324
|
+
val kind = result.errors.some(e -> e.name == "CheckError") ? "static" : "syntax"
|
|
325
|
+
console.error(red("\n✗ " + baseName + ": " + n + " " + kind + " error(s)"))
|
|
326
|
+
printErrors(result.errors, source, abs)
|
|
327
|
+
return { ok: false, typeErrors: 0, warnings: 0 }
|
|
328
|
+
|
|
329
|
+
var allOk = true
|
|
330
|
+
val typeErrors = result.typeErrors ?? []
|
|
331
|
+
if typeErrors.length > 0:
|
|
332
|
+
console.error(red("\n✗ " + baseName + ": " + typeErrors.length + " type error(s)"))
|
|
333
|
+
printErrors(typeErrors, source, abs)
|
|
334
|
+
allOk = false
|
|
335
|
+
|
|
336
|
+
val warnings = result.typeWarnings ?? []
|
|
337
|
+
for w in warnings:
|
|
338
|
+
val fileRef = cyan(baseName) + (w.line ? yellow(":" + w.line) : "")
|
|
339
|
+
console.warn(yellow(" ⚠ ") + fileRef + gray(" " + w.message))
|
|
340
|
+
if w.hint: console.warn(cyan(" Hint: ") + gray(w.hint))
|
|
341
|
+
|
|
342
|
+
val fnRe = /^(?:async )?function \w/gm
|
|
343
|
+
val clsRe = /^class \w/gm
|
|
344
|
+
val fns = (result.output.match(fnRe) ?? []).length
|
|
345
|
+
val cls = (result.output.match(clsRe) ?? []).length
|
|
346
|
+
|
|
347
|
+
if allOk:
|
|
348
|
+
console.log(
|
|
349
|
+
green("✓ ") + cyan(baseName) + gray(" — no errors") +
|
|
350
|
+
gray(" Functions: " + fns + " | Classes: " + cls + " | JS output: " + result.output.split("\n").length + " lines")
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
return { ok: allOk, typeErrors: typeErrors.length, warnings: warnings.length }
|
|
354
|
+
|
|
355
|
+
fn cmdCheck(filePaths, opts):
|
|
356
|
+
if filePaths.length == 0:
|
|
357
|
+
console.error(red("✗ No files specified"))
|
|
358
|
+
process.exit(1)
|
|
359
|
+
|
|
360
|
+
var totalErrors = 0
|
|
361
|
+
var totalWarnings = 0
|
|
362
|
+
|
|
363
|
+
for filePath in filePaths:
|
|
364
|
+
val abs = Path.resolve(filePath)
|
|
365
|
+
if not Fs.existsSync(abs):
|
|
366
|
+
console.error(red("✗ File not found: " + filePath))
|
|
367
|
+
totalErrors = totalErrors + 1
|
|
368
|
+
continue
|
|
369
|
+
val r = runCheck(abs)
|
|
370
|
+
if not r.ok: totalErrors = totalErrors + 1
|
|
371
|
+
totalWarnings = totalWarnings + r.warnings
|
|
372
|
+
|
|
373
|
+
console.log()
|
|
374
|
+
if totalErrors > 0:
|
|
375
|
+
console.error(red("✗ " + totalErrors + " file(s) with errors"))
|
|
376
|
+
process.exit(1)
|
|
377
|
+
else:
|
|
378
|
+
console.log(green("✓ All files OK") + (totalWarnings > 0 ? yellow(" (" + totalWarnings + " warning(s))") : ""))
|
|
379
|
+
|
|
380
|
+
// ── flux fmt ──────────────────────────────────────────────────
|
|
381
|
+
fn cmdFmt(filePaths, opts):
|
|
382
|
+
var changed = 0
|
|
383
|
+
for filePath in filePaths:
|
|
384
|
+
val abs = Path.resolve(filePath)
|
|
385
|
+
if not Fs.existsSync(abs):
|
|
386
|
+
console.error(red("✗ Not found: " + filePath))
|
|
387
|
+
continue
|
|
388
|
+
val source = Fs.readFileSync(abs, "utf8")
|
|
389
|
+
val formatted = format(source)
|
|
390
|
+
if formatted != source:
|
|
391
|
+
if not opts.stdout:
|
|
392
|
+
Fs.writeFileSync(abs, formatted, "utf8")
|
|
393
|
+
console.log(green("✓ ") + gray("Formatted ") + cyan(Path.relative(process.cwd(), abs)))
|
|
394
|
+
changed = changed + 1
|
|
395
|
+
else:
|
|
396
|
+
console.log(formatted)
|
|
397
|
+
else:
|
|
398
|
+
console.log(gray("○ ") + gray("No changes: ") + Path.relative(process.cwd(), abs))
|
|
399
|
+
|
|
400
|
+
if not opts.stdout and changed > 0:
|
|
401
|
+
console.log()
|
|
402
|
+
console.log(green("✓ " + changed + " file(s) formatted"))
|
|
403
|
+
|
|
404
|
+
// ── flux lint ─────────────────────────────────────────────────
|
|
405
|
+
fn cmdLint(filePaths, opts):
|
|
406
|
+
var hasErrors = false
|
|
407
|
+
for filePath in filePaths:
|
|
408
|
+
val abs = Path.resolve(filePath)
|
|
409
|
+
if not Fs.existsSync(abs):
|
|
410
|
+
console.error(red("✗ Not found: " + filePath))
|
|
411
|
+
continue
|
|
412
|
+
val source = Fs.readFileSync(abs, "utf8")
|
|
413
|
+
val result = lint(source)
|
|
414
|
+
val name = Path.relative(process.cwd(), abs)
|
|
415
|
+
|
|
416
|
+
if result.errors.length == 0 and result.warnings.length == 0:
|
|
417
|
+
console.log(green("✓ ") + cyan(name) + gray(" — clean"))
|
|
418
|
+
else:
|
|
419
|
+
for e in result.errors:
|
|
420
|
+
console.error(red(" error ") + cyan(name + ":" + (e.line ?? "?")) + " " + e.message)
|
|
421
|
+
hasErrors = true
|
|
422
|
+
for w in result.warnings:
|
|
423
|
+
console.warn(yellow(" warn ") + cyan(name + ":" + (w.line ?? "?")) + " " + w.message)
|
|
424
|
+
|
|
425
|
+
if hasErrors: process.exit(1)
|
|
426
|
+
|
|
427
|
+
// ── flux bundle ───────────────────────────────────────────────
|
|
428
|
+
fn cmdBundle(entryPath, opts):
|
|
429
|
+
val abs = Path.resolve(entryPath)
|
|
430
|
+
if not Fs.existsSync(abs):
|
|
431
|
+
console.error(red("✗ File not found: " + entryPath))
|
|
432
|
+
process.exit(1)
|
|
433
|
+
|
|
434
|
+
val outFile = opts.out ?? Path.join(Path.dirname(abs), Path.basename(abs, ".flux") + ".bundle.js")
|
|
435
|
+
val t0 = Date.now()
|
|
436
|
+
val result = bundle(abs)
|
|
437
|
+
|
|
438
|
+
if not result.success:
|
|
439
|
+
console.error(red("\n✗ Bundle failed:\n"))
|
|
440
|
+
for e in result.errors:
|
|
441
|
+
console.error(red(" " + e.message))
|
|
442
|
+
process.exit(1)
|
|
443
|
+
|
|
444
|
+
val elapsed = Date.now() - t0
|
|
445
|
+
if opts.stdout:
|
|
446
|
+
console.log(result.code)
|
|
447
|
+
return
|
|
448
|
+
|
|
449
|
+
Fs.writeFileSync(outFile, result.code, "utf8")
|
|
450
|
+
val kb = (result.code.length / 1024).toFixed(1)
|
|
451
|
+
console.log(
|
|
452
|
+
green("✓ Bundle done") + gray(" (" + elapsed + "ms) ") +
|
|
453
|
+
Path.basename(abs) + gray(" + " + (result.modules - 1) + " module(s) → ") +
|
|
454
|
+
cyan(Path.relative(process.cwd(), outFile)) + gray(" [" + kb + " KB]")
|
|
455
|
+
)
|
|
456
|
+
|
|
457
|
+
// ── flux watch ───────────────────────────────────────────────
|
|
458
|
+
fn cmdWatch(filePath, opts):
|
|
459
|
+
val abs = Path.resolve(filePath)
|
|
460
|
+
if not Fs.existsSync(abs):
|
|
461
|
+
console.error(red("✗ File not found: " + filePath))
|
|
462
|
+
process.exit(1)
|
|
463
|
+
|
|
464
|
+
console.log(cyan("◉ Watching ") + Path.relative(process.cwd(), abs) + gray(" (Ctrl+C to stop)\n"))
|
|
465
|
+
cmdCompile(filePath, opts)
|
|
466
|
+
|
|
467
|
+
var timer = null
|
|
468
|
+
fn onDebounce():
|
|
469
|
+
console.log(gray("\n" + new Date().toLocaleTimeString() + " — ") + blue("change detected"))
|
|
470
|
+
cmdCompile(filePath, opts)
|
|
471
|
+
fn onChange():
|
|
472
|
+
if timer: clearTimeout(timer)
|
|
473
|
+
timer = setTimeout(onDebounce, 80)
|
|
474
|
+
Fs.watch(abs, onChange)
|
|
475
|
+
|
|
476
|
+
// ── flux tokens ───────────────────────────────────────────────
|
|
477
|
+
fn cmdTokens(filePath, opts):
|
|
478
|
+
val { source } = readFluxFile(filePath)
|
|
479
|
+
val { Lexer } = require(Path.join(__dirname, "lexer.js"))
|
|
480
|
+
val lexer = new Lexer(source)
|
|
481
|
+
val tokens = lexer.tokenize()
|
|
482
|
+
console.log(gray("Tokens (" + tokens.length + "):\n"))
|
|
483
|
+
for tok in tokens:
|
|
484
|
+
val loc = tok.line ? gray(" " + tok.line + ":" + (tok.col ?? 1)) : ""
|
|
485
|
+
val val_ = tok.value != null ? cyan(" " + JSON.stringify(tok.value)) : ""
|
|
486
|
+
console.log(" " + yellow(tok.type.padEnd(16)) + val_ + loc)
|
|
487
|
+
|
|
488
|
+
// ── flux ast ──────────────────────────────────────────────────
|
|
489
|
+
fn cmdAst(filePath, opts):
|
|
490
|
+
val { source } = readFluxFile(filePath)
|
|
491
|
+
val result = transpile(source, {})
|
|
492
|
+
if not result.success:
|
|
493
|
+
printErrors(result.errors, source, filePath)
|
|
494
|
+
process.exit(1)
|
|
495
|
+
console.log(JSON.stringify(result.ast, null, 2))
|
|
496
|
+
|
|
497
|
+
// ── flux repl ─────────────────────────────────────────────────
|
|
498
|
+
fn cmdRepl(opts):
|
|
499
|
+
val readline = require("readline")
|
|
500
|
+
val rl = readline.createInterface({
|
|
501
|
+
input: process.stdin,
|
|
502
|
+
output: process.stdout,
|
|
503
|
+
terminal: true,
|
|
504
|
+
prompt: cyan("flux") + gray("> "),
|
|
505
|
+
})
|
|
506
|
+
|
|
507
|
+
console.log()
|
|
508
|
+
console.log(bold("Flux Lang Interactive REPL") + gray(" v" + VERSION + " [" + STAGE + "]"))
|
|
509
|
+
console.log(gray("Type Flux code to compile and run it. Ctrl+C or .exit to quit.\n"))
|
|
510
|
+
rl.prompt()
|
|
511
|
+
|
|
512
|
+
var multiLine = ""
|
|
513
|
+
var inBlock = false
|
|
514
|
+
|
|
515
|
+
fn onLine(rawLine):
|
|
516
|
+
val line = rawLine
|
|
517
|
+
|
|
518
|
+
if line.trim() == ".exit" or line.trim() == ".quit":
|
|
519
|
+
console.log(gray("\nBye!"))
|
|
520
|
+
process.exit(0)
|
|
521
|
+
|
|
522
|
+
if line.trim() == ".help":
|
|
523
|
+
console.log(gray(" .exit — quit"))
|
|
524
|
+
console.log(gray(" .clear — clear screen"))
|
|
525
|
+
console.log(gray(" .help — this message"))
|
|
526
|
+
rl.prompt()
|
|
527
|
+
return
|
|
528
|
+
|
|
529
|
+
if line.trim() == ".clear":
|
|
530
|
+
console.clear()
|
|
531
|
+
rl.prompt()
|
|
532
|
+
return
|
|
533
|
+
|
|
534
|
+
val needsContinue = line.trimEnd().endsWith(":") or inBlock
|
|
535
|
+
|
|
536
|
+
if needsContinue and line.trim() != "":
|
|
537
|
+
multiLine = multiLine + line + "\n"
|
|
538
|
+
inBlock = true
|
|
539
|
+
process.stdout.write(gray("... "))
|
|
540
|
+
return
|
|
541
|
+
|
|
542
|
+
val src = inBlock ? multiLine : line
|
|
543
|
+
multiLine = ""
|
|
544
|
+
inBlock = false
|
|
545
|
+
|
|
546
|
+
if not src.trim():
|
|
547
|
+
rl.prompt()
|
|
548
|
+
return
|
|
549
|
+
|
|
550
|
+
try:
|
|
551
|
+
val result = transpile(src, { check: false })
|
|
552
|
+
if result.success:
|
|
553
|
+
val tmpPath = Path.join(Os.tmpdir(), "_flux_repl_" + Date.now() + ".js")
|
|
554
|
+
Fs.writeFileSync(tmpPath, result.output, "utf8")
|
|
555
|
+
try:
|
|
556
|
+
val out = require(tmpPath)
|
|
557
|
+
if out != undefined: console.log(green("← ") + JSON.stringify(out))
|
|
558
|
+
catch(e):
|
|
559
|
+
console.error(red("Runtime: ") + e.message)
|
|
560
|
+
finally:
|
|
561
|
+
try: Fs.unlinkSync(tmpPath)
|
|
562
|
+
catch(e2): null
|
|
563
|
+
else:
|
|
564
|
+
for e in result.errors:
|
|
565
|
+
console.error(red("✗ ") + e.message)
|
|
566
|
+
catch(e3):
|
|
567
|
+
console.error(red("Error: ") + e3.message)
|
|
568
|
+
|
|
569
|
+
rl.prompt()
|
|
570
|
+
|
|
571
|
+
fn onClose():
|
|
572
|
+
console.log(gray("\nBye!"))
|
|
573
|
+
process.exit(0)
|
|
574
|
+
|
|
575
|
+
rl.on("line", onLine)
|
|
576
|
+
rl.on("close", onClose)
|
|
577
|
+
|
|
578
|
+
// ── flux init ────────────────────────────────────────────────
|
|
579
|
+
fn cmdInit(name, opts):
|
|
580
|
+
val projectName = name ?? "my-flux-app"
|
|
581
|
+
val dir = Path.resolve(projectName)
|
|
582
|
+
|
|
583
|
+
if Fs.existsSync(dir):
|
|
584
|
+
console.error(red("✗ Directory already exists: " + projectName))
|
|
585
|
+
process.exit(1)
|
|
586
|
+
|
|
587
|
+
Fs.mkdirSync(dir, { recursive: true })
|
|
588
|
+
Fs.mkdirSync(Path.join(dir, "src"), { recursive: true })
|
|
589
|
+
Fs.mkdirSync(Path.join(dir, "tests"), { recursive: true })
|
|
590
|
+
|
|
591
|
+
val mainFlux = `// {projectName} — written in Flux Lang v{VERSION}
|
|
592
|
+
|
|
593
|
+
type Shape =
|
|
594
|
+
| Circle(radius: Float)
|
|
595
|
+
| Rect(width: Float, height: Float)
|
|
596
|
+
| Triangle(base: Float, height: Float)
|
|
597
|
+
|
|
598
|
+
fn area(shape: Shape) -> Float:
|
|
599
|
+
match shape:
|
|
600
|
+
when Circle(r): return Math.PI * r * r
|
|
601
|
+
when Rect(w, h): return w * h
|
|
602
|
+
when Triangle(b, h): return 0.5 * b * h
|
|
603
|
+
|
|
604
|
+
fn greet(name: String) -> String:
|
|
605
|
+
return "Hello from Flux, {name}!"
|
|
606
|
+
|
|
607
|
+
val shapes = [
|
|
608
|
+
Circle(5.0),
|
|
609
|
+
Rect(10.0, 4.0),
|
|
610
|
+
Triangle(6.0, 8.0),
|
|
611
|
+
]
|
|
612
|
+
|
|
613
|
+
for shape in shapes:
|
|
614
|
+
val a = area(shape)
|
|
615
|
+
print("Area: {a:.2f}")
|
|
616
|
+
|
|
617
|
+
print(greet("{projectName}"))
|
|
618
|
+
`
|
|
619
|
+
|
|
620
|
+
val testFlux = `// {projectName} tests
|
|
621
|
+
|
|
622
|
+
fn add(a: Int, b: Int) -> Int: return a + b
|
|
623
|
+
fn mul(a: Int, b: Int) -> Int: return a * b
|
|
624
|
+
|
|
625
|
+
test "add works":
|
|
626
|
+
assert add(1, 2) == 3
|
|
627
|
+
assert add(0, 0) == 0
|
|
628
|
+
assert add(-1, 1) == 0
|
|
629
|
+
|
|
630
|
+
test "mul works":
|
|
631
|
+
assert mul(3, 4) == 12
|
|
632
|
+
assert mul(0, 5) == 0
|
|
633
|
+
`
|
|
634
|
+
|
|
635
|
+
val fluxJson = {
|
|
636
|
+
name: projectName,
|
|
637
|
+
version: "1.0.0",
|
|
638
|
+
description: "A Flux Lang project",
|
|
639
|
+
author: "",
|
|
640
|
+
license: "MIT",
|
|
641
|
+
entry: "src/main.flux",
|
|
642
|
+
outDir: "dist",
|
|
643
|
+
sourcemap: false,
|
|
644
|
+
typecheck: true,
|
|
645
|
+
scripts: {
|
|
646
|
+
start: "flux run src/main.flux",
|
|
647
|
+
build: "flux bundle src/main.flux -o dist/bundle.js",
|
|
648
|
+
dev: "flux watch src/main.flux",
|
|
649
|
+
check: "flux check src/main.flux",
|
|
650
|
+
test: "flux test tests/",
|
|
651
|
+
fmt: "flux fmt src/",
|
|
652
|
+
lint: "flux lint src/",
|
|
653
|
+
},
|
|
654
|
+
dependencies: {},
|
|
655
|
+
devDependencies: {},
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
val gitignore = `node_modules/
|
|
659
|
+
dist/
|
|
660
|
+
flux_modules/
|
|
661
|
+
*.js.map
|
|
662
|
+
.DS_Store
|
|
663
|
+
`
|
|
664
|
+
|
|
665
|
+
val readme = `# {projectName}
|
|
666
|
+
|
|
667
|
+
A project built with [Flux Lang](https://flux-lang.dev) v{VERSION}.
|
|
668
|
+
|
|
669
|
+
## Getting Started
|
|
670
|
+
|
|
671
|
+
\`\`\`bash
|
|
672
|
+
flux run src/main.flux
|
|
673
|
+
\`\`\`
|
|
674
|
+
|
|
675
|
+
## Commands
|
|
676
|
+
|
|
677
|
+
| Command | Description |
|
|
678
|
+
|---|---|
|
|
679
|
+
| \`flux run src/main.flux\` | Run main file |
|
|
680
|
+
| \`flux build\` | Bundle to dist/ |
|
|
681
|
+
| \`flux watch src/main.flux\` | Watch mode |
|
|
682
|
+
| \`flux check src/main.flux\` | Type check |
|
|
683
|
+
| \`flux test tests/\` | Run tests |
|
|
684
|
+
| \`flux fmt src/\` | Format code |
|
|
685
|
+
`
|
|
686
|
+
|
|
687
|
+
Fs.writeFileSync(Path.join(dir, "src", "main.flux"), mainFlux, "utf8")
|
|
688
|
+
Fs.writeFileSync(Path.join(dir, "tests", "main.test.flux"), testFlux, "utf8")
|
|
689
|
+
Fs.writeFileSync(Path.join(dir, "flux.json"), JSON.stringify(fluxJson, null, 2) + "\n", "utf8")
|
|
690
|
+
Fs.writeFileSync(Path.join(dir, ".gitignore"), gitignore, "utf8")
|
|
691
|
+
Fs.writeFileSync(Path.join(dir, "README.md"), readme, "utf8")
|
|
692
|
+
|
|
693
|
+
console.log()
|
|
694
|
+
console.log(green("✓ Created: ") + bold(projectName + "/"))
|
|
695
|
+
console.log()
|
|
696
|
+
console.log(gray(" Files:"))
|
|
697
|
+
console.log(" " + cyan(projectName + "/src/main.flux"))
|
|
698
|
+
console.log(" " + cyan(projectName + "/tests/main.test.flux"))
|
|
699
|
+
console.log(" " + cyan(projectName + "/flux.json"))
|
|
700
|
+
console.log(" " + cyan(projectName + "/.gitignore"))
|
|
701
|
+
console.log(" " + cyan(projectName + "/README.md"))
|
|
702
|
+
console.log()
|
|
703
|
+
console.log(bold(" Next steps:"))
|
|
704
|
+
console.log(" " + yellow("cd " + projectName))
|
|
705
|
+
console.log(" " + yellow("flux run src/main.flux"))
|
|
706
|
+
console.log()
|
|
707
|
+
|
|
708
|
+
// ── flux self-hosted ──────────────────────────────────────────
|
|
709
|
+
fn cmdSelfHosted(sub, opts):
|
|
710
|
+
val SELF = Path.join(__dirname, ".")
|
|
711
|
+
|
|
712
|
+
val coreModules = [
|
|
713
|
+
"css-preprocessor", "checker", "type-checker",
|
|
714
|
+
"jsx", "lexer", "parser", "codegen", "transpiler",
|
|
715
|
+
]
|
|
716
|
+
val extModules = [
|
|
717
|
+
"formatter", "sourcemap", "stdlib", "mangler",
|
|
718
|
+
"linter", "bundler", "test-runner",
|
|
719
|
+
]
|
|
720
|
+
val newModules = ["config", "pkg", "cli"]
|
|
721
|
+
val allModules = [...coreModules, ...extModules, ...newModules]
|
|
722
|
+
|
|
723
|
+
if sub == "build":
|
|
724
|
+
val { execSync } = require("child_process")
|
|
725
|
+
val BIN = Path.join(__dirname, "../../bin/flux.js")
|
|
726
|
+
console.log()
|
|
727
|
+
console.log(bold("⚡ Flux Bootstrap — Stage 0"))
|
|
728
|
+
console.log(gray(" Compiling self-hosted sources with stage-0 compiler...\n"))
|
|
729
|
+
|
|
730
|
+
var ok = 0
|
|
731
|
+
var failed = 0
|
|
732
|
+
for name in allModules:
|
|
733
|
+
val src = Path.join(SELF, name + ".flux")
|
|
734
|
+
val out = Path.join(SELF, name + ".js")
|
|
735
|
+
if not Fs.existsSync(src):
|
|
736
|
+
console.log(gray(" ○ " + name + ".flux (skipped — not found)"))
|
|
737
|
+
continue
|
|
738
|
+
try:
|
|
739
|
+
val cmd = `node "${BIN}" compile "${src}" -o "${out}" --no-mangle`
|
|
740
|
+
execSync(cmd, { cwd: Path.join(__dirname, "../.."), stdio: "pipe" })
|
|
741
|
+
console.log(green(" ✓ ") + name + ".flux → " + gray(name + ".js"))
|
|
742
|
+
ok = ok + 1
|
|
743
|
+
catch(e):
|
|
744
|
+
console.error(red(" ✗ ") + name + ".flux — " + e.message.split("\n")[0])
|
|
745
|
+
failed = failed + 1
|
|
746
|
+
|
|
747
|
+
console.log()
|
|
748
|
+
if failed == 0:
|
|
749
|
+
console.log(green("✓ Bootstrap complete! ") + gray(ok + " modules compiled"))
|
|
750
|
+
console.log()
|
|
751
|
+
console.log(" Activate self-hosted mode:")
|
|
752
|
+
console.log(" " + yellow("FLUX_SELF_HOSTED=1 flux <command>"))
|
|
753
|
+
else:
|
|
754
|
+
console.log(red("✗ " + failed + " module(s) failed, " + ok + " succeeded"))
|
|
755
|
+
console.log()
|
|
756
|
+
return
|
|
757
|
+
|
|
758
|
+
if sub == "verify":
|
|
759
|
+
console.log(cyan("\n Verifying self-hosted compiler output...\n"))
|
|
760
|
+
try:
|
|
761
|
+
val selfMod = require(Path.join(SELF, "transpiler.js"))
|
|
762
|
+
val stage0Mod = require(Path.join(__dirname, "../transpiler.js"))
|
|
763
|
+
val testSrc = `fn greet(name): return "Hello, {name}!"\nval msg = greet("Flux")`
|
|
764
|
+
val r0 = stage0Mod.transpile(testSrc, {})
|
|
765
|
+
val r1 = selfMod.transpile(testSrc, {})
|
|
766
|
+
fn norm(s): return s.replace(/\/\/.*/g, "").replace(/\s+/g, " ").trim()
|
|
767
|
+
if norm(r0.output) == norm(r1.output):
|
|
768
|
+
console.log(green("✓ Self-hosted output matches stage-0!"))
|
|
769
|
+
console.log(green("✓ Flux is fully self-hosting."))
|
|
770
|
+
else:
|
|
771
|
+
console.log(yellow("⚠ Outputs differ (minor differences are OK)"))
|
|
772
|
+
console.log(gray(" Stage-0: ") + r0.output.split("\n")[0])
|
|
773
|
+
console.log(gray(" Self-hosted:") + r1.output.split("\n")[0])
|
|
774
|
+
catch(e):
|
|
775
|
+
console.error(red("✗ Verify failed: " + e.message))
|
|
776
|
+
console.log()
|
|
777
|
+
return
|
|
778
|
+
|
|
779
|
+
// Default: show status
|
|
780
|
+
console.log()
|
|
781
|
+
console.log(bold(" Flux Self-Hosted Compiler Status\n"))
|
|
782
|
+
val selfActive = process.env.FLUX_SELF_HOSTED == "1"
|
|
783
|
+
if selfActive:
|
|
784
|
+
console.log(" Mode: " + green("● ACTIVE") + gray(" (using self-hosted compiler)"))
|
|
785
|
+
else:
|
|
786
|
+
console.log(" Mode: " + gray("○ INACTIVE") + " " + gray("(using stage-0 compiler)"))
|
|
787
|
+
console.log(" Toggle: " + yellow("FLUX_SELF_HOSTED=1 flux <command>"))
|
|
788
|
+
console.log(" Build: " + yellow("flux self-hosted build"))
|
|
789
|
+
|
|
790
|
+
console.log()
|
|
791
|
+
console.log(bold(" Core Pipeline (" + coreModules.length + " modules)"))
|
|
792
|
+
for name in coreModules:
|
|
793
|
+
val jsPath = Path.join(SELF, name + ".js")
|
|
794
|
+
val flxPath = Path.join(SELF, name + ".flux")
|
|
795
|
+
val hasJs = Fs.existsSync(jsPath)
|
|
796
|
+
val hasFlx = Fs.existsSync(flxPath)
|
|
797
|
+
val kb = hasJs ? (Fs.statSync(jsPath).size / 1024).toFixed(1) : "?"
|
|
798
|
+
val sym = hasJs ? green("✓") : red("✗")
|
|
799
|
+
val note = not hasFlx ? gray(" (no .flux source)") : ""
|
|
800
|
+
console.log(" " + sym + " " + (name + ".flux").padEnd(26) + gray(kb + " KB") + note)
|
|
801
|
+
|
|
802
|
+
console.log()
|
|
803
|
+
console.log(bold(" Extended Toolchain (" + extModules.length + " modules)"))
|
|
804
|
+
for name in extModules:
|
|
805
|
+
val jsPath = Path.join(SELF, name + ".js")
|
|
806
|
+
val flxPath = Path.join(SELF, name + ".flux")
|
|
807
|
+
val hasJs = Fs.existsSync(jsPath)
|
|
808
|
+
val hasFlx = Fs.existsSync(flxPath)
|
|
809
|
+
val kb = hasJs ? (Fs.statSync(jsPath).size / 1024).toFixed(1) : "?"
|
|
810
|
+
val sym = hasJs ? green("✓") : red("✗")
|
|
811
|
+
val note = not hasFlx ? gray(" (no .flux source)") : ""
|
|
812
|
+
console.log(" " + sym + " " + (name + ".flux").padEnd(26) + gray(kb + " KB") + note)
|
|
813
|
+
|
|
814
|
+
console.log()
|
|
815
|
+
console.log(bold(" Ecosystem (" + newModules.length + " modules)"))
|
|
816
|
+
for name in newModules:
|
|
817
|
+
val jsPath = Path.join(SELF, name + ".js")
|
|
818
|
+
val flxPath = Path.join(SELF, name + ".flux")
|
|
819
|
+
val hasJs = Fs.existsSync(jsPath)
|
|
820
|
+
val hasFlx = Fs.existsSync(flxPath)
|
|
821
|
+
val kb = hasJs ? (Fs.statSync(jsPath).size / 1024).toFixed(1) : "?"
|
|
822
|
+
val sym = hasJs ? green("✓") : yellow("○")
|
|
823
|
+
console.log(" " + sym + " " + (name + ".flux").padEnd(26) + (hasJs ? gray(kb + " KB") : yellow("not built")))
|
|
824
|
+
|
|
825
|
+
console.log()
|
|
826
|
+
|
|
827
|
+
// ── flux version ─────────────────────────────────────────────
|
|
828
|
+
fn cmdVersion(opts):
|
|
829
|
+
if noColor:
|
|
830
|
+
console.log("flux-lang v" + VERSION)
|
|
831
|
+
return
|
|
832
|
+
console.log(cyan(bold("⚡ Flux Lang")) + gray(" v" + VERSION + " [" + STAGE + "]"))
|
|
833
|
+
|
|
834
|
+
// ══════════════════════════════════════════════════════════════
|
|
835
|
+
// Main entry point
|
|
836
|
+
// ══════════════════════════════════════════════════════════════
|
|
837
|
+
|
|
838
|
+
fn main():
|
|
839
|
+
val { positional, opts } = parseArgs(process.argv)
|
|
840
|
+
val cmd = positional[0] ?? "help"
|
|
841
|
+
|
|
842
|
+
match cmd:
|
|
843
|
+
when "compile": cmdCompile(positional[1], opts)
|
|
844
|
+
when "run": cmdRun(positional[1], opts)
|
|
845
|
+
when "check": cmdCheck(positional.slice(1), opts)
|
|
846
|
+
when "fmt": cmdFmt(positional.slice(1), opts)
|
|
847
|
+
when "format": cmdFmt(positional.slice(1), opts)
|
|
848
|
+
when "lint": cmdLint(positional.slice(1), opts)
|
|
849
|
+
when "bundle": cmdBundle(positional[1], opts)
|
|
850
|
+
when "watch": cmdWatch(positional[1], opts)
|
|
851
|
+
when "tokens": cmdTokens(positional[1], opts)
|
|
852
|
+
when "ast": cmdAst(positional[1], opts)
|
|
853
|
+
when "repl": cmdRepl(opts)
|
|
854
|
+
when "init": cmdInit(positional[1], opts)
|
|
855
|
+
when "add": cmdAdd(positional.slice(1), opts)
|
|
856
|
+
when "remove": cmdRemove(positional.slice(1), opts)
|
|
857
|
+
when "rm": cmdRemove(positional.slice(1), opts)
|
|
858
|
+
when "install": cmdInstall(opts)
|
|
859
|
+
when "i": cmdInstall(opts)
|
|
860
|
+
when "list": cmdList(opts)
|
|
861
|
+
when "ls": cmdList(opts)
|
|
862
|
+
when "search": cmdSearch(positional[1], opts)
|
|
863
|
+
when "info": cmdInfo(positional[1], opts)
|
|
864
|
+
when "publish": cmdPublish(opts)
|
|
865
|
+
when "self-hosted": cmdSelfHosted(positional[1], opts)
|
|
866
|
+
when "version": cmdVersion(opts)
|
|
867
|
+
when "-v": cmdVersion(opts)
|
|
868
|
+
when "--version": cmdVersion(opts)
|
|
869
|
+
when "help": showHelp()
|
|
870
|
+
when "--help": showHelp()
|
|
871
|
+
when "-h": showHelp()
|
|
872
|
+
when _:
|
|
873
|
+
console.error(red("✗ Unknown command: " + cmd))
|
|
874
|
+
console.error(gray(" Run: flux help"))
|
|
875
|
+
process.exit(1)
|
|
876
|
+
|
|
877
|
+
main()
|