@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/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 multiple files into one .js"],
77
- ["run <file.flux>", "Compile and run immediately"],
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
- for [cmd, desc] in compilerCmds:
82
- console.log(" " + green(("flux " + cmd).padEnd(36)) + " " + gray(desc))
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
- for [cmd, desc] in toolCmds:
95
- console.log(" " + green(("flux " + cmd).padEnd(36)) + " " + gray(desc))
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]", "Scaffold a new Flux project"],
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 package registry"],
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
- for [cmd, desc] in pkgCmds:
110
- console.log(" " + cyan(("flux " + cmd).padEnd(36)) + " " + gray(desc))
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
- for [cmd, desc] in selfCmds:
120
- console.log(" " + yellow(("flux " + cmd).padEnd(36)) + " " + gray(desc))
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 dir = Path.resolve(projectName)
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 mainFlux = `// {projectName} — built with Flux Lang v{VERSION}
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 test_average():
714
- assert(average([2, 4, 6]) == 4, "average of 2,4,6")
715
- assert(average([]) == 0, "empty list")
716
-
717
- fn test_safe_divide():
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
- val fluxJson = {
735
- name: projectName,
736
- version: "1.0.0",
737
- description: "A Flux Lang v" + VERSION + " project",
738
- author: "",
739
- license: "MIT",
740
- entry: "src/main.flux",
741
- outDir: "dist",
742
- sourcemap: false,
743
- typecheck: true,
744
- scripts: {
745
- start: "flux run src/main.flux",
746
- build: "flux bundle src/main.flux -o dist/bundle.js",
747
- dev: "flux watch src/main.flux",
748
- check: "flux check src/main.flux",
749
- test: "flux test tests/",
750
- fmt: "flux fmt src/",
751
- lint: "flux lint src/",
752
- },
753
- dependencies: {},
754
- devDependencies: { "@xnoxs/flux-lang": "^" + VERSION },
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
- val gitignore = "node_modules/\ndist/\nflux_modules/\n*.js.map\n.DS_Store\n"
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
- val readme = `# {projectName}
725
+ import Express from "express"
760
726
 
761
- A project built with [Flux Lang](https://flux-lang.dev) v{VERSION} — the self-hosted compiler.
727
+ val app = Express()
728
+ val PORT = process.env.PORT or 3000
762
729
 
763
- ## Quick Start
730
+ app.use(Express.json())
764
731
 
765
- \`\`\`bash
766
- flux run src/main.flux
767
- \`\`\`
732
+ app.get("/", (req, res) ->
733
+ res.json({ status: "ok", message: "Hello from Flux!", port: PORT })
734
+ )
768
735
 
769
- ## Project Structure
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
- {projectName}/
773
- ├── src/
774
- │ ├── main.flux # Entry point
775
- │ └── utils.flux # Utility functions
776
- ├── tests/
777
- │ └── main.test.flux # Test suite
778
- ├── flux_modules/ # Packages installed by \`flux install\` (gitignored)
779
- ├── flux.json # Project config & dependencies
780
- └── README.md
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
- ## Commands
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
- | Command | Description |
786
- |---|---|
787
- | \`flux run src/main.flux\` | Run the project |
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
- val allFiles = [
798
- projectName + "/src/main.flux",
799
- projectName + "/src/utils.flux",
800
- projectName + "/tests/main.test.flux",
801
- projectName + "/flux.json",
802
- projectName + "/.gitignore",
803
- projectName + "/README.md",
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
- Fs.writeFileSync(Path.join(dir, "src", "main.flux"), mainFlux, "utf8")
807
- Fs.writeFileSync(Path.join(dir, "src", "utils.flux"), utilsFlux, "utf8")
808
- Fs.writeFileSync(Path.join(dir, "tests", "main.test.flux"), testFlux, "utf8")
809
- Fs.writeFileSync(Path.join(dir, "flux.json"), JSON.stringify(fluxJson, null, 2) + "\n", "utf8")
810
- Fs.writeFileSync(Path.join(dir, ".gitignore"), gitignore, "utf8")
811
- Fs.writeFileSync(Path.join(dir, "README.md"), readme, "utf8")
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
- console.log()
814
- console.log(green("✓ Created: ") + bold(projectName + "/"))
815
- console.log()
816
- console.log(gray(" Files:"))
817
- for f in allFiles:
818
- console.log(" " + cyan(f))
819
- console.log()
820
- console.log(bold(" Next steps:"))
821
- console.log(" " + yellow("cd " + projectName))
822
- console.log(" " + yellow("flux run src/main.flux"))
823
- console.log()
824
- console.log(gray(" Add packages (installed into flux_modules/):"))
825
- console.log(" " + yellow("flux add <package>"))
826
- console.log(" " + yellow("flux install"))
827
- console.log()
828
- console.log(gray(" Or run the test suite:"))
829
- console.log(" " + yellow("flux test tests/"))
830
- console.log()
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)