@xnoxs/flux-lang 4.0.3 → 4.0.5
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/dist/flux-cli.js +4994 -8166
- package/dist/flux.cjs.js +1 -1
- package/index.js +9 -10
- package/package.json +4 -5
- package/src/self/bundler.flux +21 -16
- package/src/self/bundler.js +185 -1
- package/src/self/cli.flux +274 -220
- package/src/self/cli.js +153 -125
- package/src/self/config.flux +36 -28
- package/src/self/config.js +34 -31
- package/src/self/pkg.flux +39 -136
- package/src/self/pkg.js +37 -124
package/src/self/cli.flux
CHANGED
|
@@ -73,13 +73,14 @@ fn showHelp():
|
|
|
73
73
|
console.log(bold("COMPILER:"))
|
|
74
74
|
val compilerCmds = [
|
|
75
75
|
["compile <file.flux>", "Compile .flux → .js"],
|
|
76
|
-
["bundle <entry.flux>", "Bundle
|
|
77
|
-
["run <file.flux>", "Compile and run
|
|
76
|
+
["bundle <entry.flux>", "Bundle multi-file project into one .js"],
|
|
77
|
+
["run <file.flux>", "Compile and run a single-file script"],
|
|
78
78
|
["watch <file.flux>", "Watch for changes, auto-compile"],
|
|
79
79
|
["check <file.flux>", "Type-check and static analysis"],
|
|
80
80
|
]
|
|
81
|
-
|
|
82
|
-
console.log(" " + green(("flux " +
|
|
81
|
+
compilerCmds.forEach(pair ->
|
|
82
|
+
console.log(" " + green(("flux " + pair[0]).padEnd(36)) + " " + gray(pair[1]))
|
|
83
|
+
)
|
|
83
84
|
|
|
84
85
|
console.log()
|
|
85
86
|
console.log(bold("TOOLING:"))
|
|
@@ -91,23 +92,25 @@ fn showHelp():
|
|
|
91
92
|
["tokens <file.flux>", "Show lexer token list"],
|
|
92
93
|
["ast <file.flux>", "Show Abstract Syntax Tree (JSON)"],
|
|
93
94
|
]
|
|
94
|
-
|
|
95
|
-
console.log(" " + green(("flux " +
|
|
95
|
+
toolCmds.forEach(pair ->
|
|
96
|
+
console.log(" " + green(("flux " + pair[0]).padEnd(36)) + " " + gray(pair[1]))
|
|
97
|
+
)
|
|
96
98
|
|
|
97
99
|
console.log()
|
|
98
100
|
console.log(bold("PACKAGE MANAGER:"))
|
|
99
101
|
val pkgCmds = [
|
|
100
|
-
["init [name]",
|
|
101
|
-
["add <pkg[@version]>", "Add a dependency"],
|
|
102
|
-
["remove <pkg>", "Remove a dependency"],
|
|
103
|
-
["install", "Install all dependencies"],
|
|
102
|
+
["init [name] [--template]", "Scaffold a new Flux project"],
|
|
103
|
+
["add <pkg[@version]>", "Add a dependency (npm install --save)"],
|
|
104
|
+
["remove <pkg>", "Remove a dependency (npm uninstall)"],
|
|
105
|
+
["install", "Install all dependencies (npm install)"],
|
|
104
106
|
["list", "List installed packages"],
|
|
105
|
-
["search <query>", "Search the
|
|
107
|
+
["search <query>", "Search the npm registry"],
|
|
106
108
|
["info <pkg>", "Show package details"],
|
|
107
|
-
["publish", "Publish package to registry"],
|
|
109
|
+
["publish", "Publish package to npm registry"],
|
|
108
110
|
]
|
|
109
|
-
|
|
110
|
-
console.log(" " + cyan(("flux " +
|
|
111
|
+
pkgCmds.forEach(pair ->
|
|
112
|
+
console.log(" " + cyan(("flux " + pair[0]).padEnd(36)) + " " + gray(pair[1]))
|
|
113
|
+
)
|
|
111
114
|
|
|
112
115
|
console.log()
|
|
113
116
|
console.log(bold("SELF-HOSTED:"))
|
|
@@ -116,8 +119,9 @@ fn showHelp():
|
|
|
116
119
|
["self-hosted build", "Bootstrap: compile compiler with itself"],
|
|
117
120
|
["self-hosted verify", "Verify self-hosted output matches stage-0"],
|
|
118
121
|
]
|
|
119
|
-
|
|
120
|
-
console.log(" " + yellow(("flux " +
|
|
122
|
+
selfCmds.forEach(pair ->
|
|
123
|
+
console.log(" " + yellow(("flux " + pair[0]).padEnd(36)) + " " + gray(pair[1]))
|
|
124
|
+
)
|
|
121
125
|
|
|
122
126
|
console.log()
|
|
123
127
|
console.log(bold("OPTIONS:"))
|
|
@@ -128,6 +132,7 @@ fn showHelp():
|
|
|
128
132
|
console.log(" " + yellow("--typecheck ") + " Enable type checking")
|
|
129
133
|
console.log(" " + yellow("--stdout ") + " Print to terminal")
|
|
130
134
|
console.log(" " + yellow("--no-color ") + " Disable colors")
|
|
135
|
+
console.log(" " + yellow("--template ") + " Init template: script, server, webapp")
|
|
131
136
|
console.log()
|
|
132
137
|
|
|
133
138
|
// ── Parse CLI args ────────────────────────────────────────────
|
|
@@ -145,6 +150,7 @@ fn parseArgs(argv):
|
|
|
145
150
|
verbose: false,
|
|
146
151
|
jsx: false,
|
|
147
152
|
jsxTarget: "browser",
|
|
153
|
+
template: null,
|
|
148
154
|
}
|
|
149
155
|
val positional = []
|
|
150
156
|
var i = 0
|
|
@@ -174,6 +180,9 @@ fn parseArgs(argv):
|
|
|
174
180
|
else if a == "--jsx-target":
|
|
175
181
|
i = i + 1
|
|
176
182
|
opts.jsxTarget = args[i]
|
|
183
|
+
else if a == "--template" or a == "-t":
|
|
184
|
+
i = i + 1
|
|
185
|
+
opts.template = args[i]
|
|
177
186
|
else if not a.startsWith("--"):
|
|
178
187
|
positional.push(a)
|
|
179
188
|
i = i + 1
|
|
@@ -236,6 +245,22 @@ fn printErrors(errors, source, filePath):
|
|
|
236
245
|
console.error(cyan(" Hint: ") + gray(err.hint))
|
|
237
246
|
console.error()
|
|
238
247
|
|
|
248
|
+
// ── Detect inter-file .flux imports in source ─────────────────
|
|
249
|
+
fn findFluxImports(source):
|
|
250
|
+
val found = []
|
|
251
|
+
val lines = source.split("\n")
|
|
252
|
+
for line in lines:
|
|
253
|
+
val trimmed = line.trim()
|
|
254
|
+
// Match: import ... from "./something" or import ... from "../something"
|
|
255
|
+
// where the path is relative (starts with ./ or ../)
|
|
256
|
+
val m = trimmed.match(/^import\s+.+\s+from\s+["'](\.[^"']+)["']/)
|
|
257
|
+
if m:
|
|
258
|
+
val src = m[1]
|
|
259
|
+
// Relative import to a .flux file (explicit or implicit extension)
|
|
260
|
+
if not src.endsWith(".js") and not src.endsWith(".json") and not src.endsWith(".node"):
|
|
261
|
+
found.push(src)
|
|
262
|
+
return found
|
|
263
|
+
|
|
239
264
|
// ══════════════════════════════════════════════════════════════
|
|
240
265
|
// Commands
|
|
241
266
|
// ══════════════════════════════════════════════════════════════
|
|
@@ -290,6 +315,23 @@ fn cmdCompile(filePath, opts):
|
|
|
290
315
|
// ── flux run ──────────────────────────────────────────────────
|
|
291
316
|
fn cmdRun(filePath, opts):
|
|
292
317
|
val { source, abs } = readFluxFile(filePath)
|
|
318
|
+
|
|
319
|
+
// Detect inter-file .flux imports — flux run cannot handle them
|
|
320
|
+
val fluxImports = findFluxImports(source)
|
|
321
|
+
if fluxImports.length > 0:
|
|
322
|
+
val example = fluxImports[0]
|
|
323
|
+
val entryRel = Path.relative(process.cwd(), abs)
|
|
324
|
+
val outFile = Path.basename(abs, ".flux") + ".js"
|
|
325
|
+
console.error(red("\n✗ Cannot run a multi-file Flux project with `flux run`.\n"))
|
|
326
|
+
console.error(" Found: " + yellow("import ... from \"" + example + "\""))
|
|
327
|
+
console.error()
|
|
328
|
+
console.error(" Use " + cyan("flux bundle") + " to compile all files into one, then run it:\n")
|
|
329
|
+
console.error(" " + yellow("flux bundle " + entryRel + " -o dist/" + outFile))
|
|
330
|
+
console.error(" " + yellow("node dist/" + outFile))
|
|
331
|
+
console.error()
|
|
332
|
+
console.error(" Or use " + cyan("flux run") + " only for single-file scripts (no inter-file .flux imports).\n")
|
|
333
|
+
process.exit(1)
|
|
334
|
+
|
|
293
335
|
val result = transpile(source, {
|
|
294
336
|
jsx: opts.jsx ?? false,
|
|
295
337
|
jsxTarget: opts.jsxTarget ?? "browser",
|
|
@@ -446,6 +488,11 @@ fn cmdBundle(entryPath, opts):
|
|
|
446
488
|
console.log(result.code)
|
|
447
489
|
return
|
|
448
490
|
|
|
491
|
+
// Ensure output directory exists
|
|
492
|
+
val outDir = Path.dirname(Path.resolve(outFile))
|
|
493
|
+
if not Fs.existsSync(outDir):
|
|
494
|
+
Fs.mkdirSync(outDir, { recursive: true })
|
|
495
|
+
|
|
449
496
|
Fs.writeFileSync(outFile, result.code, "utf8")
|
|
450
497
|
val kb = (result.code.length / 1024).toFixed(1)
|
|
451
498
|
console.log(
|
|
@@ -578,7 +625,8 @@ fn cmdRepl(opts):
|
|
|
578
625
|
// ── flux init ────────────────────────────────────────────────
|
|
579
626
|
fn cmdInit(name, opts):
|
|
580
627
|
val projectName = name ?? "my-flux-app"
|
|
581
|
-
val
|
|
628
|
+
val template = opts.template ?? "script"
|
|
629
|
+
val dir = Path.resolve(projectName)
|
|
582
630
|
|
|
583
631
|
if Fs.existsSync(dir):
|
|
584
632
|
console.error(red("✗ Directory already exists: " + projectName))
|
|
@@ -588,246 +636,251 @@ fn cmdInit(name, opts):
|
|
|
588
636
|
Fs.mkdirSync(Path.join(dir, "src"), { recursive: true })
|
|
589
637
|
Fs.mkdirSync(Path.join(dir, "tests"), { recursive: true })
|
|
590
638
|
|
|
591
|
-
val
|
|
592
|
-
// Run: flux run src/main.flux
|
|
593
|
-
|
|
594
|
-
// ── Algebraic Data Types + Pattern Matching ───────────────────
|
|
595
|
-
type Shape = Circle(radius) | Rect(width, height) | Triangle(base, height)
|
|
596
|
-
|
|
597
|
-
fn area(shape):
|
|
598
|
-
match shape:
|
|
599
|
-
when Circle(r): return Math.PI * r * r
|
|
600
|
-
when Rect(w, h): return w * h
|
|
601
|
-
when Triangle(b, h): return 0.5 * b * h
|
|
602
|
-
|
|
603
|
-
fn describe(shape):
|
|
604
|
-
match shape:
|
|
605
|
-
when Circle(r): return "Circle(r={r:.2f})"
|
|
606
|
-
when Rect(w, h): return "Rect({w:.1f}x{h:.1f})"
|
|
607
|
-
when Triangle(b, h): return "Triangle(b={b:.1f}, h={h:.1f})"
|
|
608
|
-
|
|
609
|
-
// ── Result type ───────────────────────────────────────────────
|
|
610
|
-
type Result = Ok(value) | Err(message)
|
|
611
|
-
|
|
612
|
-
fn safeDivide(a, b):
|
|
613
|
-
if b == 0: return Err("division by zero")
|
|
614
|
-
return Ok(a / b)
|
|
615
|
-
|
|
616
|
-
// ── Utility functions ─────────────────────────────────────────
|
|
617
|
-
fn greet(name): return "Hello from Flux, {name}!"
|
|
618
|
-
|
|
619
|
-
fn formatList(items):
|
|
620
|
-
if items.length == 0: return "(empty)"
|
|
621
|
-
return "[" + items.join(", ") + "]"
|
|
622
|
-
|
|
623
|
-
fn clamp(value, lo, hi):
|
|
624
|
-
if value < lo: return lo
|
|
625
|
-
if value > hi: return hi
|
|
626
|
-
return value
|
|
627
|
-
|
|
628
|
-
// ── Pipe operator + stdlib ────────────────────────────────────
|
|
629
|
-
val numbers = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
|
|
630
|
-
val processed = numbers
|
|
631
|
-
|> filter(n -> n > 3)
|
|
632
|
-
|> map(n -> n * n)
|
|
633
|
-
|> sort
|
|
634
|
-
|
|
635
|
-
// ── Main ──────────────────────────────────────────────────────
|
|
636
|
-
print(greet("{projectName}"))
|
|
637
|
-
print("Squares > 3: {formatList(processed)}")
|
|
638
|
-
print("clamp(12, 0, 10) = {clamp(12, 0, 10)}")
|
|
639
|
-
|
|
640
|
-
val shapes = [Circle(5.0), Rect(8.0, 3.0), Triangle(6.0, 4.0)]
|
|
641
|
-
for shape in shapes:
|
|
642
|
-
val a = area(shape)
|
|
643
|
-
print("{describe(shape)} area={a:.2f}")
|
|
644
|
-
|
|
645
|
-
match safeDivide(10.0, 3.0):
|
|
646
|
-
when Ok(v): print("10 / 3 = {v:.4f}")
|
|
647
|
-
when Err(e): print("Error: {e}")
|
|
648
|
-
`
|
|
649
|
-
|
|
650
|
-
val utilsFlux = `// Utility functions for {projectName}
|
|
651
|
-
// Use with: flux bundle src/main.flux (bundles imports automatically)
|
|
652
|
-
|
|
653
|
-
export fn greet(name):
|
|
654
|
-
return "Hello from Flux, {name}!"
|
|
655
|
-
|
|
656
|
-
export fn formatList(items):
|
|
657
|
-
if items.length == 0: return "(empty)"
|
|
658
|
-
return "[" + items.join(", ") + "]"
|
|
659
|
-
|
|
660
|
-
export fn clamp(value, lo, hi):
|
|
661
|
-
if value < lo: return lo
|
|
662
|
-
if value > hi: return hi
|
|
663
|
-
return value
|
|
664
|
-
|
|
665
|
-
export fn sum(nums):
|
|
666
|
-
return nums.reduce((acc, x) -> acc + x, 0)
|
|
667
|
-
|
|
668
|
-
export fn average(nums):
|
|
669
|
-
if nums.length == 0: return 0
|
|
670
|
-
return sum(nums) / nums.length
|
|
671
|
-
`
|
|
639
|
+
val gitignore = "node_modules/\ndist/\n*.js.map\n.DS_Store\n"
|
|
672
640
|
|
|
673
641
|
val testFlux = `// Tests for {projectName}
|
|
674
642
|
// Run: flux test tests/
|
|
675
643
|
|
|
676
|
-
// ── Functions under test ──────────────────────────────────────
|
|
677
644
|
fn add(a, b):
|
|
678
645
|
return a + b
|
|
679
646
|
|
|
680
|
-
fn mul(a, b):
|
|
681
|
-
return a * b
|
|
682
|
-
|
|
683
647
|
fn clamp(value, lo, hi):
|
|
684
648
|
if value < lo: return lo
|
|
685
649
|
if value > hi: return hi
|
|
686
650
|
return value
|
|
687
651
|
|
|
688
|
-
fn average(nums):
|
|
689
|
-
if nums.length == 0: return 0
|
|
690
|
-
return nums.reduce((s, x) -> s + x, 0) / nums.length
|
|
691
|
-
|
|
692
|
-
type Result = Ok(value) | Err(message)
|
|
693
|
-
|
|
694
|
-
fn safeDivide(a, b):
|
|
695
|
-
if b == 0: return Err("div by zero")
|
|
696
|
-
return Ok(a / b)
|
|
697
|
-
|
|
698
|
-
// ── Test functions ────────────────────────────────────────────
|
|
699
652
|
fn test_add():
|
|
700
653
|
assert(add(1, 2) == 3, "1+2=3")
|
|
701
654
|
assert(add(0, 0) == 0, "0+0=0")
|
|
702
655
|
assert(add(-1, 1) == 0, "-1+1=0")
|
|
703
656
|
|
|
704
|
-
fn test_mul():
|
|
705
|
-
assert(mul(3, 4) == 12, "3*4=12")
|
|
706
|
-
assert(mul(0, 5) == 0, "0*5=0")
|
|
707
|
-
|
|
708
657
|
fn test_clamp():
|
|
709
658
|
assert(clamp(5, 0, 10) == 5, "within range")
|
|
710
659
|
assert(clamp(-1, 0, 10) == 0, "below min")
|
|
711
660
|
assert(clamp(15, 0, 10) == 10, "above max")
|
|
661
|
+
`
|
|
662
|
+
|
|
663
|
+
// ── Template: script (default) — single file, no inter-file imports ──
|
|
664
|
+
if template == "script":
|
|
665
|
+
val mainFlux = `// {projectName} — a Flux script
|
|
666
|
+
// Run: npm run dev (or: node_modules/.bin/flux run src/main.flux)
|
|
667
|
+
|
|
668
|
+
import Fs from "fs"
|
|
669
|
+
import Path from "path"
|
|
712
670
|
|
|
713
|
-
fn
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
val ok = safeDivide(10, 2)
|
|
719
|
-
match ok:
|
|
720
|
-
when Ok(v): assert(v == 5, "10/2=5")
|
|
721
|
-
when Err(e): assert(false, "unexpected error")
|
|
722
|
-
val err = safeDivide(10, 0)
|
|
723
|
-
match err:
|
|
724
|
-
when Ok(v): assert(false, "expected error")
|
|
725
|
-
when Err(e): assert(e == "div by zero", "error message")
|
|
726
|
-
|
|
727
|
-
fn test_pipe_operator():
|
|
728
|
-
val result = [1, 2, 3, 4, 5] |> filter(n -> n > 2) |> map(n -> n * 2)
|
|
729
|
-
assert(result.length == 3, "filter length")
|
|
730
|
-
assert(result[0] == 6, "first element")
|
|
731
|
-
assert(result[2] == 10, "last element")
|
|
671
|
+
fn main():
|
|
672
|
+
print("Hello from Flux!")
|
|
673
|
+
print("Node version: {process.version}")
|
|
674
|
+
|
|
675
|
+
main()
|
|
732
676
|
`
|
|
733
677
|
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
678
|
+
val pkgJson = {
|
|
679
|
+
name: projectName,
|
|
680
|
+
version: "1.0.0",
|
|
681
|
+
description: "A Flux project",
|
|
682
|
+
main: "dist/main.js",
|
|
683
|
+
scripts: {
|
|
684
|
+
dev: "flux run src/main.flux",
|
|
685
|
+
build: "flux bundle src/main.flux -o dist/main.js",
|
|
686
|
+
start: "node dist/main.js",
|
|
687
|
+
test: "flux test tests/",
|
|
688
|
+
},
|
|
689
|
+
flux: {
|
|
690
|
+
entry: "src/main.flux",
|
|
691
|
+
outDir: "dist",
|
|
692
|
+
mangle: false,
|
|
693
|
+
sourcemap: false,
|
|
694
|
+
},
|
|
695
|
+
dependencies: {
|
|
696
|
+
"@xnoxs/flux-lang": "^" + VERSION,
|
|
697
|
+
},
|
|
698
|
+
devDependencies: {},
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
Fs.writeFileSync(Path.join(dir, "src", "main.flux"), mainFlux, "utf8")
|
|
702
|
+
Fs.writeFileSync(Path.join(dir, "tests", "main.test.flux"), testFlux, "utf8")
|
|
703
|
+
Fs.writeFileSync(Path.join(dir, "package.json"), JSON.stringify(pkgJson, null, 2) + "\n", "utf8")
|
|
704
|
+
Fs.writeFileSync(Path.join(dir, ".gitignore"), gitignore, "utf8")
|
|
705
|
+
|
|
706
|
+
console.log()
|
|
707
|
+
console.log(green("✓ Project ") + bold(projectName + "/") + green(" created with template ") + cyan("script"))
|
|
708
|
+
console.log()
|
|
709
|
+
console.log(bold(" Next steps:"))
|
|
710
|
+
console.log(" " + yellow("cd " + projectName))
|
|
711
|
+
console.log(" " + yellow("npm install"))
|
|
712
|
+
console.log(" " + yellow("npm run dev"))
|
|
713
|
+
console.log()
|
|
714
|
+
console.log(gray(" Add packages:"))
|
|
715
|
+
console.log(" " + yellow("flux add <package>"))
|
|
716
|
+
console.log()
|
|
717
|
+
return
|
|
756
718
|
|
|
757
|
-
|
|
719
|
+
// ── Template: server — Express HTTP server, single file ──
|
|
720
|
+
if template == "server":
|
|
721
|
+
val serverFlux = `// {projectName} — Flux HTTP server
|
|
722
|
+
// Run: npm run dev
|
|
723
|
+
// Build: npm run build && npm start
|
|
758
724
|
|
|
759
|
-
|
|
725
|
+
import Express from "express"
|
|
760
726
|
|
|
761
|
-
|
|
727
|
+
val app = Express()
|
|
728
|
+
val PORT = process.env.PORT or 3000
|
|
762
729
|
|
|
763
|
-
|
|
730
|
+
app.use(Express.json())
|
|
764
731
|
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
732
|
+
app.get("/", (req, res) ->
|
|
733
|
+
res.json({ status: "ok", message: "Hello from Flux!", port: PORT })
|
|
734
|
+
)
|
|
768
735
|
|
|
769
|
-
|
|
736
|
+
app.listen(PORT, -> print("Server running at http://localhost:{PORT}"))
|
|
737
|
+
`
|
|
738
|
+
|
|
739
|
+
val pkgJson = {
|
|
740
|
+
name: projectName,
|
|
741
|
+
version: "1.0.0",
|
|
742
|
+
description: "A Flux HTTP server",
|
|
743
|
+
main: "dist/server.js",
|
|
744
|
+
scripts: {
|
|
745
|
+
dev: "flux run src/server.flux",
|
|
746
|
+
build: "flux bundle src/server.flux -o dist/server.js",
|
|
747
|
+
start: "node dist/server.js",
|
|
748
|
+
test: "flux test tests/",
|
|
749
|
+
},
|
|
750
|
+
flux: {
|
|
751
|
+
entry: "src/server.flux",
|
|
752
|
+
outDir: "dist",
|
|
753
|
+
mangle: false,
|
|
754
|
+
sourcemap: false,
|
|
755
|
+
},
|
|
756
|
+
dependencies: {
|
|
757
|
+
"@xnoxs/flux-lang": "^" + VERSION,
|
|
758
|
+
"express": "^4.18.0",
|
|
759
|
+
},
|
|
760
|
+
devDependencies: {},
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
Fs.writeFileSync(Path.join(dir, "src", "server.flux"), serverFlux, "utf8")
|
|
764
|
+
Fs.writeFileSync(Path.join(dir, "tests", "main.test.flux"), testFlux, "utf8")
|
|
765
|
+
Fs.writeFileSync(Path.join(dir, "package.json"), JSON.stringify(pkgJson, null, 2) + "\n", "utf8")
|
|
766
|
+
Fs.writeFileSync(Path.join(dir, ".gitignore"), gitignore, "utf8")
|
|
770
767
|
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
768
|
+
console.log()
|
|
769
|
+
console.log(green("✓ Project ") + bold(projectName + "/") + green(" created with template ") + cyan("server"))
|
|
770
|
+
console.log()
|
|
771
|
+
console.log(bold(" Next steps:"))
|
|
772
|
+
console.log(" " + yellow("cd " + projectName))
|
|
773
|
+
console.log(" " + yellow("npm install"))
|
|
774
|
+
console.log(" " + yellow("npm run dev"))
|
|
775
|
+
console.log()
|
|
776
|
+
console.log(gray(" Build for production:"))
|
|
777
|
+
console.log(" " + yellow("npm run build && npm start"))
|
|
778
|
+
console.log()
|
|
779
|
+
return
|
|
782
780
|
|
|
783
|
-
|
|
781
|
+
// ── Template: webapp — multi-file project, uses flux bundle ──
|
|
782
|
+
if template == "webapp":
|
|
783
|
+
val utilsFlux = `// Utility functions
|
|
784
|
+
export fn formatDate(date):
|
|
785
|
+
val d = new Date(date)
|
|
786
|
+
return d.toISOString().split("T")[0]
|
|
784
787
|
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
| \`flux bundle src/main.flux -o dist/bundle.js\` | Bundle to single file |
|
|
789
|
-
| \`flux watch src/main.flux\` | Watch mode |
|
|
790
|
-
| \`flux check src/main.flux\` | Type check + static analysis |
|
|
791
|
-
| \`flux test tests/\` | Run all tests |
|
|
792
|
-
| \`flux fmt src/\` | Format source code |
|
|
793
|
-
| \`flux lint src/\` | Lint for issues |
|
|
794
|
-
| \`flux add <package>\` | Add a dependency |
|
|
788
|
+
export fn paginate(items, page, perPage):
|
|
789
|
+
val start = (page - 1) * perPage
|
|
790
|
+
return items.slice(start, start + perPage)
|
|
795
791
|
`
|
|
796
792
|
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
793
|
+
val routesFlux = `// Route handlers
|
|
794
|
+
import { formatDate, paginate } from "./utils" // inter-file .flux import
|
|
795
|
+
|
|
796
|
+
val items = [
|
|
797
|
+
{ id: 1, name: "Item A", date: "2024-01-01" },
|
|
798
|
+
{ id: 2, name: "Item B", date: "2024-02-01" },
|
|
799
|
+
{ id: 3, name: "Item C", date: "2024-03-01" },
|
|
800
|
+
]
|
|
801
|
+
|
|
802
|
+
export fn setupRoutes(app):
|
|
803
|
+
app.get("/items", (req, res) ->
|
|
804
|
+
val page = parseInt(req.query.page or "1")
|
|
805
|
+
val perPage = parseInt(req.query.per_page or "10")
|
|
806
|
+
val data = paginate(items, page, perPage)
|
|
807
|
+
res.json({ data, page })
|
|
808
|
+
)
|
|
805
809
|
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
810
|
+
app.get("/items/:id", (req, res) ->
|
|
811
|
+
val item = items.find(i -> i.id == parseInt(req.params.id))
|
|
812
|
+
if item == null:
|
|
813
|
+
res.status(404).json({ error: "Not found" })
|
|
814
|
+
else:
|
|
815
|
+
res.json({ data: item, date: formatDate(item.date) })
|
|
816
|
+
)
|
|
817
|
+
`
|
|
812
818
|
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
819
|
+
val serverFlux = `// Entry point — MUST be compiled with: npm run build
|
|
820
|
+
// Inter-file .flux imports only work via flux bundle, NOT flux run
|
|
821
|
+
import Express from "express"
|
|
822
|
+
import { setupRoutes } from "./routes" // inter-file .flux import
|
|
823
|
+
|
|
824
|
+
val app = Express()
|
|
825
|
+
val PORT = process.env.PORT or 3000
|
|
826
|
+
|
|
827
|
+
app.use(Express.json())
|
|
828
|
+
setupRoutes(app)
|
|
829
|
+
|
|
830
|
+
app.listen(PORT, -> print("{projectName} running at http://localhost:{PORT}"))
|
|
831
|
+
`
|
|
832
|
+
|
|
833
|
+
val pkgJson = {
|
|
834
|
+
name: projectName,
|
|
835
|
+
version: "1.0.0",
|
|
836
|
+
description: "A Flux web app",
|
|
837
|
+
main: "dist/server.js",
|
|
838
|
+
scripts: {
|
|
839
|
+
build: "flux bundle src/server.flux -o dist/server.js",
|
|
840
|
+
start: "node dist/server.js",
|
|
841
|
+
dev: "flux bundle src/server.flux -o dist/server.js && node dist/server.js",
|
|
842
|
+
watch: "flux watch src/server.flux",
|
|
843
|
+
test: "flux test tests/",
|
|
844
|
+
},
|
|
845
|
+
flux: {
|
|
846
|
+
entry: "src/server.flux",
|
|
847
|
+
outDir: "dist",
|
|
848
|
+
mangle: false,
|
|
849
|
+
sourcemap: true,
|
|
850
|
+
},
|
|
851
|
+
dependencies: {
|
|
852
|
+
"@xnoxs/flux-lang": "^" + VERSION,
|
|
853
|
+
"express": "^4.18.0",
|
|
854
|
+
},
|
|
855
|
+
devDependencies: {},
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
Fs.writeFileSync(Path.join(dir, "src", "utils.flux"), utilsFlux, "utf8")
|
|
859
|
+
Fs.writeFileSync(Path.join(dir, "src", "routes.flux"), routesFlux, "utf8")
|
|
860
|
+
Fs.writeFileSync(Path.join(dir, "src", "server.flux"), serverFlux, "utf8")
|
|
861
|
+
Fs.writeFileSync(Path.join(dir, "tests", "main.test.flux"), testFlux, "utf8")
|
|
862
|
+
Fs.writeFileSync(Path.join(dir, "package.json"), JSON.stringify(pkgJson, null, 2) + "\n", "utf8")
|
|
863
|
+
Fs.writeFileSync(Path.join(dir, ".gitignore"), gitignore, "utf8")
|
|
864
|
+
|
|
865
|
+
console.log()
|
|
866
|
+
console.log(green("✓ Project ") + bold(projectName + "/") + green(" created with template ") + cyan("webapp"))
|
|
867
|
+
console.log()
|
|
868
|
+
console.log(bold(" Next steps:"))
|
|
869
|
+
console.log(" " + yellow("cd " + projectName))
|
|
870
|
+
console.log(" " + yellow("npm install") + gray(" # install dependencies"))
|
|
871
|
+
console.log(" " + yellow("npm run build") + gray(" # compile all .flux → dist/server.js"))
|
|
872
|
+
console.log(" " + yellow("npm start") + gray(" # run the server"))
|
|
873
|
+
console.log()
|
|
874
|
+
console.log(yellow(" Note: ") + gray("This project uses multiple .flux files."))
|
|
875
|
+
console.log(gray(" Always build with ") + yellow("npm run build") + gray(" (flux bundle) before running."))
|
|
876
|
+
console.log(gray(" Do NOT use ") + red("flux run") + gray(" on files that import other .flux files."))
|
|
877
|
+
console.log()
|
|
878
|
+
return
|
|
879
|
+
|
|
880
|
+
// Unknown template
|
|
881
|
+
console.error(red("✗ Unknown template: " + template))
|
|
882
|
+
console.error(gray(" Available templates: script, server, webapp"))
|
|
883
|
+
process.exit(1)
|
|
831
884
|
|
|
832
885
|
// ── flux self-hosted ──────────────────────────────────────────
|
|
833
886
|
fn cmdSelfHosted(sub, opts):
|
|
@@ -975,6 +1028,7 @@ fn main():
|
|
|
975
1028
|
when "tokens": cmdTokens(positional[1], opts)
|
|
976
1029
|
when "ast": cmdAst(positional[1], opts)
|
|
977
1030
|
when "repl": cmdRepl(opts)
|
|
1031
|
+
when "test": runTests(positional.slice(1), opts)
|
|
978
1032
|
when "init": cmdInit(positional[1], opts)
|
|
979
1033
|
when "add": cmdAdd(positional.slice(1), opts)
|
|
980
1034
|
when "remove": cmdRemove(positional.slice(1), opts)
|