@xnoxs/flux-lang 4.0.3 → 4.0.4
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 +4996 -8159
- package/dist/flux.cjs.js +3763 -5620
- package/dist/flux.esm.js +3767 -5619
- package/dist/flux.min.js +95 -361
- package/index.js +9 -10
- package/package.json +4 -5
- package/src/self/bundler.js +202 -1
- package/src/self/cli.js +116 -43
- package/src/self/config.js +33 -19
- package/src/self/pkg.js +33 -111
package/index.js
CHANGED
|
@@ -3,17 +3,17 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* Flux Lang — Public API
|
|
5
5
|
*
|
|
6
|
-
* const { transpile } = require('flux-lang');
|
|
7
|
-
* import { transpile, format, buildStdlib } from 'flux-lang';
|
|
6
|
+
* const { transpile } = require('@xnoxs/flux-lang');
|
|
7
|
+
* import { transpile, format, buildStdlib } from '@xnoxs/flux-lang';
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
const { transpile } = require('./src/transpiler');
|
|
11
|
-
const { format } = require('./src/formatter');
|
|
12
|
-
const { buildStdlib, detectUsedSymbols, STDLIB_SYMBOLS } = require('./src/stdlib');
|
|
13
|
-
const { Lexer } = require('./src/lexer');
|
|
14
|
-
const { Parser } = require('./src/parser');
|
|
15
|
-
const { bundle } = require('./src/bundler');
|
|
16
|
-
const { loadConfig, mergeConfig
|
|
10
|
+
const { transpile } = require('./src/self/transpiler');
|
|
11
|
+
const { format } = require('./src/self/formatter');
|
|
12
|
+
const { buildStdlib, detectUsedSymbols, STDLIB_SYMBOLS } = require('./src/self/stdlib');
|
|
13
|
+
const { Lexer } = require('./src/self/lexer');
|
|
14
|
+
const { Parser } = require('./src/self/parser');
|
|
15
|
+
const { bundle } = require('./src/self/bundler');
|
|
16
|
+
const { loadConfig, mergeConfig } = require('./src/self/config');
|
|
17
17
|
|
|
18
18
|
module.exports = {
|
|
19
19
|
transpile,
|
|
@@ -26,5 +26,4 @@ module.exports = {
|
|
|
26
26
|
bundle,
|
|
27
27
|
loadConfig,
|
|
28
28
|
mergeConfig,
|
|
29
|
-
defineConfig,
|
|
30
29
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xnoxs/flux-lang",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.4",
|
|
4
4
|
"description": "Flux — A modern language that transpiles to JavaScript. Python-clean syntax, TypeScript-level safety, Rust-inspired pattern matching.",
|
|
5
5
|
"main": "dist/flux.cjs.js",
|
|
6
6
|
"module": "dist/flux.esm.js",
|
|
@@ -9,9 +9,8 @@
|
|
|
9
9
|
"flux": "./bin/flux.js"
|
|
10
10
|
},
|
|
11
11
|
"scripts": {
|
|
12
|
-
"build": "node
|
|
13
|
-
"
|
|
14
|
-
"test": "node src/cli.js test tests/",
|
|
12
|
+
"build": "node -e \"console.log(\"build skipped\")\"",
|
|
13
|
+
"test": "node src/self/cli.js test tests/",
|
|
15
14
|
"test:file": "node src/cli.js test",
|
|
16
15
|
"check": "node src/cli.js check tests/01_basics.test.flux",
|
|
17
16
|
"bench": "node benchmarks/bench.js",
|
|
@@ -84,4 +83,4 @@
|
|
|
84
83
|
"devDependencies": {
|
|
85
84
|
"esbuild": "^0.28.1"
|
|
86
85
|
}
|
|
87
|
-
}
|
|
86
|
+
}
|
package/src/self/bundler.js
CHANGED
|
@@ -1 +1,202 @@
|
|
|
1
|
-
|
|
1
|
+
/* compiled from src/self/bundler.flux by Flux Lang */
|
|
2
|
+
'use strict';
|
|
3
|
+
// ── Flux stdlib ──
|
|
4
|
+
|
|
5
|
+
function findIndex(arr, fn) { return arr.findIndex(fn); }
|
|
6
|
+
|
|
7
|
+
function join(arr, sep) { return arr.join(sep != null ? sep : ','); }
|
|
8
|
+
|
|
9
|
+
function includes(arr, val) { return arr.includes(val); }
|
|
10
|
+
|
|
11
|
+
function trim(s) { return String(s).trim(); }
|
|
12
|
+
|
|
13
|
+
function startsWith(s, prefix) { return String(s).startsWith(prefix); }
|
|
14
|
+
|
|
15
|
+
function endsWith(s, suffix) { return String(s).endsWith(suffix); }
|
|
16
|
+
|
|
17
|
+
function repeat(s, n) { return String(s).repeat(n); }
|
|
18
|
+
// ── end stdlib ──
|
|
19
|
+
|
|
20
|
+
// Generated by Flux Transpiler v3.2.0
|
|
21
|
+
"use strict";
|
|
22
|
+
|
|
23
|
+
const Fs = require("fs");
|
|
24
|
+
const Path = require("path");
|
|
25
|
+
const { lexerize } = require("./lexer");
|
|
26
|
+
const { makeParser } = require("./parser");
|
|
27
|
+
const { makeCodeGen } = require("./codegen");
|
|
28
|
+
function toModuleId(absPath) {
|
|
29
|
+
const base = Path.basename(absPath, ".flux");
|
|
30
|
+
return ("_flux_" + base.replace(/[^a-zA-Z0-9]/g, "_"));
|
|
31
|
+
}
|
|
32
|
+
function extractModuleInfo(ast, fromFile) {
|
|
33
|
+
const imports = [];
|
|
34
|
+
const npmImports = [];
|
|
35
|
+
const exports = [];
|
|
36
|
+
const body = [];
|
|
37
|
+
const dir = Path.dirname(fromFile);
|
|
38
|
+
for (const node of ast.body) {
|
|
39
|
+
if ((node.type == "ImportDecl")) {
|
|
40
|
+
const src = node.source;
|
|
41
|
+
if ((src.startsWith("./") || src.startsWith("../"))) {
|
|
42
|
+
let resolved = src;
|
|
43
|
+
if (!resolved.endsWith(".flux")) {
|
|
44
|
+
resolved = (resolved + ".flux");
|
|
45
|
+
}
|
|
46
|
+
const absPath = Path.resolve(dir, resolved);
|
|
47
|
+
imports.push({ names: node.names, source: node.source, absPath });
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
npmImports.push({ names: node.names, source: src });
|
|
51
|
+
body.push(node);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
else if ((node.type == "ExportDecl")) {
|
|
55
|
+
const inner = node.decl;
|
|
56
|
+
if ((inner.type == "FnDecl")) {
|
|
57
|
+
exports.push(inner.name);
|
|
58
|
+
}
|
|
59
|
+
if ((inner.type == "ClassDecl")) {
|
|
60
|
+
exports.push(inner.name);
|
|
61
|
+
}
|
|
62
|
+
if ((inner.type == "VarDecl")) {
|
|
63
|
+
exports.push(inner.name);
|
|
64
|
+
}
|
|
65
|
+
body.push(inner);
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
body.push(node);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return { cleanAst: { type: "Program", body }, imports, npmImports, exports };
|
|
72
|
+
}
|
|
73
|
+
function codegenModule(ast) {
|
|
74
|
+
const cg = makeCodeGen({ indent: " " });
|
|
75
|
+
const result = cg.generate(ast);
|
|
76
|
+
const lines = result.code.split("\n");
|
|
77
|
+
const start = lines.findIndex((l) => (((l.trim() != "") && !l.includes("Generated by Flux")) && !l.includes("\"use strict\"")));
|
|
78
|
+
return lines.slice(start).join("\n");
|
|
79
|
+
}
|
|
80
|
+
class Bundler {
|
|
81
|
+
constructor(entryFile, options, modules, order, visited, inStack) {
|
|
82
|
+
this.entryFile = entryFile;
|
|
83
|
+
this.options = options;
|
|
84
|
+
this.modules = modules;
|
|
85
|
+
this.order = order;
|
|
86
|
+
this.visited = visited;
|
|
87
|
+
this.inStack = inStack;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
bundle() {
|
|
91
|
+
this.collect(this.entryFile);
|
|
92
|
+
return this.link();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
collect(absPath) {
|
|
96
|
+
if (this.visited.has(absPath)) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (this.inStack.has(absPath)) {
|
|
100
|
+
throw new Error(((("[" + Path.basename(absPath)) + "] Circular dependency detected: ") + absPath));
|
|
101
|
+
}
|
|
102
|
+
if (!Fs.existsSync(absPath)) {
|
|
103
|
+
throw new Error(((("[" + Path.basename(absPath)) + "] File not found: ") + absPath));
|
|
104
|
+
}
|
|
105
|
+
this.inStack.add(absPath);
|
|
106
|
+
const source = Fs.readFileSync(absPath, "utf8");
|
|
107
|
+
let ast = null;
|
|
108
|
+
try {
|
|
109
|
+
const tokens = lexerize(source).tokenize();
|
|
110
|
+
ast = makeParser(tokens).parse();
|
|
111
|
+
}
|
|
112
|
+
catch (e) {
|
|
113
|
+
throw new Error(((("[" + Path.basename(absPath)) + "] Parse error: ") + e.message));
|
|
114
|
+
}
|
|
115
|
+
const info = extractModuleInfo(ast, absPath);
|
|
116
|
+
this.modules.set(absPath, { cleanAst: info.cleanAst, imports: info.imports, exports: info.exports, source, absPath });
|
|
117
|
+
for (const imp of info.imports) {
|
|
118
|
+
this.collect(imp.absPath);
|
|
119
|
+
}
|
|
120
|
+
this.inStack.delete(absPath);
|
|
121
|
+
this.visited.add(absPath);
|
|
122
|
+
this.order.push(absPath);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
link() {
|
|
126
|
+
const lines = [];
|
|
127
|
+
const banner = (this.options.banner != false);
|
|
128
|
+
if (banner) {
|
|
129
|
+
lines.push("// Generated by Flux Bundler (self-hosted)");
|
|
130
|
+
lines.push(("// Entry: " + Path.basename(this.entryFile)));
|
|
131
|
+
lines.push(("// Modules: " + this.order.length));
|
|
132
|
+
lines.push("\"use strict\";");
|
|
133
|
+
lines.push("");
|
|
134
|
+
}
|
|
135
|
+
lines.push("(function() {");
|
|
136
|
+
lines.push("");
|
|
137
|
+
for (const absPath of this.order) {
|
|
138
|
+
const mod = this.modules.get(absPath);
|
|
139
|
+
const isEntry = (absPath == this.entryFile);
|
|
140
|
+
const modId = toModuleId(absPath);
|
|
141
|
+
const relName = Path.relative(process.cwd(), absPath);
|
|
142
|
+
lines.push((" // " + "─".repeat(60)));
|
|
143
|
+
lines.push((" // Module: " + relName));
|
|
144
|
+
lines.push((" // " + "─".repeat(60)));
|
|
145
|
+
if (!isEntry) {
|
|
146
|
+
lines.push(((" var " + modId) + " = (function() {"));
|
|
147
|
+
lines.push(" var _exports = {};");
|
|
148
|
+
lines.push("");
|
|
149
|
+
}
|
|
150
|
+
for (const imp of mod.imports) {
|
|
151
|
+
const srcId = toModuleId(imp.absPath);
|
|
152
|
+
for (const name of imp.names) {
|
|
153
|
+
const localName = (name.alias ?? name.name);
|
|
154
|
+
const importedName = name.name;
|
|
155
|
+
lines.push(((((((((((((" var " + localName) + " = ") + srcId) + "._exports ? ") + srcId) + "._exports.") + importedName) + " : ") + srcId) + ".") + importedName) + ";"));
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if ((mod.imports.length > 0)) {
|
|
159
|
+
lines.push("");
|
|
160
|
+
}
|
|
161
|
+
const moduleCode = codegenModule(mod.cleanAst);
|
|
162
|
+
lines.push(moduleCode);
|
|
163
|
+
if (!isEntry) {
|
|
164
|
+
if ((mod.exports.length > 0)) {
|
|
165
|
+
lines.push("");
|
|
166
|
+
for (const expName of mod.exports) {
|
|
167
|
+
lines.push(((((" _exports." + expName) + " = ") + expName) + ";"));
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
lines.push("");
|
|
171
|
+
lines.push(" return _exports;");
|
|
172
|
+
lines.push(" })()");
|
|
173
|
+
lines.push(((((" " + modId) + "._exports = ") + modId) + ";"));
|
|
174
|
+
}
|
|
175
|
+
lines.push("");
|
|
176
|
+
}
|
|
177
|
+
lines.push("})();");
|
|
178
|
+
return { code: lines.join("\n"), modules: this.order.length };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
module.exports.Bundler = Bundler;
|
|
184
|
+
function makeBundler(entryFile, options) {
|
|
185
|
+
return new Bundler(Path.resolve(entryFile), (options ?? { }), new Map(), [], new Set(), new Set());
|
|
186
|
+
}
|
|
187
|
+
module.exports.makeBundler = makeBundler;
|
|
188
|
+
function bundle(entryFile, options) {
|
|
189
|
+
const result = { success: false, code: "", modules: 0, errors: [] };
|
|
190
|
+
try {
|
|
191
|
+
const b = makeBundler(entryFile, options);
|
|
192
|
+
const out = b.bundle();
|
|
193
|
+
result.code = out.code;
|
|
194
|
+
result.modules = out.modules;
|
|
195
|
+
result.success = true;
|
|
196
|
+
}
|
|
197
|
+
catch (e) {
|
|
198
|
+
result.errors.push({ message: e.message, name: e.name, file: null });
|
|
199
|
+
}
|
|
200
|
+
return result;
|
|
201
|
+
}
|
|
202
|
+
module.exports.bundle = bundle;
|
package/src/self/cli.js
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
|
+
/* compiled from src/self/cli.flux by Flux Lang */
|
|
2
|
+
'use strict';
|
|
1
3
|
// ── Flux stdlib ──
|
|
2
4
|
|
|
3
|
-
function
|
|
4
|
-
|
|
5
|
-
function filter(arr, fn) { return arr.filter(fn); }
|
|
6
|
-
|
|
7
|
-
function reduce(arr, fn, init) { return arguments.length >= 3 ? arr.reduce(fn, init) : arr.reduce(fn); }
|
|
5
|
+
function find(arr, fn) { return arr.find(fn); }
|
|
8
6
|
|
|
9
7
|
function some(arr, fn) { return arr.some(fn); }
|
|
10
8
|
|
|
@@ -14,10 +12,6 @@ function clamp(val, min, max) {
|
|
|
14
12
|
return Math.min(Math.max(val, min), max);
|
|
15
13
|
}
|
|
16
14
|
|
|
17
|
-
function sum(arr) {
|
|
18
|
-
return arr.reduce(function(a, b) { return a + b; }, 0);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
15
|
function max(arr) {
|
|
22
16
|
if (arguments.length > 1) return Math.max.apply(null, arguments);
|
|
23
17
|
return Math.max.apply(null, arr);
|
|
@@ -96,7 +90,7 @@ function showHelp() {
|
|
|
96
90
|
console.log(bold("USAGE:"));
|
|
97
91
|
console.log(" flux <command> [options]\n");
|
|
98
92
|
console.log(bold("COMPILER:"));
|
|
99
|
-
const compilerCmds = [["compile <file.flux>", "Compile .flux → .js"], ["bundle <entry.flux>", "Bundle
|
|
93
|
+
const compilerCmds = [["compile <file.flux>", "Compile .flux → .js"], ["bundle <entry.flux>", "Bundle multi-file project into one .js"], ["run <file.flux>", "Compile and run a single-file script"], ["watch <file.flux>", "Watch for changes, auto-compile"], ["check <file.flux>", "Type-check and static analysis"]];
|
|
100
94
|
for (const __item__ of compilerCmds) {
|
|
101
95
|
const [cmd, desc] = __item__;
|
|
102
96
|
console.log((((" " + green(("flux " + cmd).padEnd(36))) + " ") + gray(desc)));
|
|
@@ -110,7 +104,7 @@ function showHelp() {
|
|
|
110
104
|
}
|
|
111
105
|
console.log();
|
|
112
106
|
console.log(bold("PACKAGE MANAGER:"));
|
|
113
|
-
const pkgCmds = [["init [name]", "Scaffold a new Flux project"], ["add <pkg[@version]>", "Add a dependency"], ["remove <pkg>", "Remove a dependency"], ["install", "Install all dependencies"], ["list", "List installed packages"], ["search <query>", "Search the
|
|
107
|
+
const pkgCmds = [["init [name] [--template]", "Scaffold a new Flux project"], ["add <pkg[@version]>", "Add a dependency (npm install --save)"], ["remove <pkg>", "Remove a dependency (npm uninstall)"], ["install", "Install all dependencies (npm install)"], ["list", "List installed packages"], ["search <query>", "Search the npm registry"], ["info <pkg>", "Show package details"], ["publish", "Publish package to npm registry"]];
|
|
114
108
|
for (const __item__ of pkgCmds) {
|
|
115
109
|
const [cmd, desc] = __item__;
|
|
116
110
|
console.log((((" " + cyan(("flux " + cmd).padEnd(36))) + " ") + gray(desc)));
|
|
@@ -131,11 +125,12 @@ function showHelp() {
|
|
|
131
125
|
console.log(((" " + yellow("--typecheck ")) + " Enable type checking"));
|
|
132
126
|
console.log(((" " + yellow("--stdout ")) + " Print to terminal"));
|
|
133
127
|
console.log(((" " + yellow("--no-color ")) + " Disable colors"));
|
|
128
|
+
console.log(((" " + yellow("--template ")) + " Init template: script, server, webapp"));
|
|
134
129
|
console.log();
|
|
135
130
|
}
|
|
136
131
|
function parseArgs(argv) {
|
|
137
132
|
const args = argv.slice(2);
|
|
138
|
-
const opts = { out: null, sourcemap: false, mangle: false, typecheck: false, strict: false, stdout: false, watch: false, dev: false, verbose: false, jsx: false, jsxTarget: "browser" };
|
|
133
|
+
const opts = { out: null, sourcemap: false, mangle: false, typecheck: false, strict: false, stdout: false, watch: false, dev: false, verbose: false, jsx: false, jsxTarget: "browser", template: null };
|
|
139
134
|
const positional = [];
|
|
140
135
|
let i = 0;
|
|
141
136
|
while ((i < args.length)) {
|
|
@@ -175,6 +170,10 @@ function parseArgs(argv) {
|
|
|
175
170
|
i = (i + 1);
|
|
176
171
|
opts.jsxTarget = args[i];
|
|
177
172
|
}
|
|
173
|
+
else if (((a == "--template") || (a == "-t"))) {
|
|
174
|
+
i = (i + 1);
|
|
175
|
+
opts.template = args[i];
|
|
176
|
+
}
|
|
178
177
|
else if (!a.startsWith("--")) {
|
|
179
178
|
positional.push(a);
|
|
180
179
|
}
|
|
@@ -243,6 +242,21 @@ function printErrors(errors, source, filePath) {
|
|
|
243
242
|
}
|
|
244
243
|
console.error();
|
|
245
244
|
}
|
|
245
|
+
function findFluxImports(source) {
|
|
246
|
+
const found = [];
|
|
247
|
+
const lines = source.split("\n");
|
|
248
|
+
for (const line of lines) {
|
|
249
|
+
const trimmed = line.trim();
|
|
250
|
+
const m = trimmed.match(/^import\s+.+\s+from\s+["'](\.[^"']+)["']/);
|
|
251
|
+
if (m) {
|
|
252
|
+
const src = m[1];
|
|
253
|
+
if (((!src.endsWith(".js") && !src.endsWith(".json")) && !src.endsWith(".node"))) {
|
|
254
|
+
found.push(src);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return found;
|
|
259
|
+
}
|
|
246
260
|
function cmdCompile(filePath, opts) {
|
|
247
261
|
const { source, abs } = readFluxFile(filePath);
|
|
248
262
|
const cfg = loadConfig(Path.dirname(abs));
|
|
@@ -276,6 +290,21 @@ function cmdCompile(filePath, opts) {
|
|
|
276
290
|
}
|
|
277
291
|
function cmdRun(filePath, opts) {
|
|
278
292
|
const { source, abs } = readFluxFile(filePath);
|
|
293
|
+
const fluxImports = findFluxImports(source);
|
|
294
|
+
if ((fluxImports.length > 0)) {
|
|
295
|
+
const example = fluxImports[0];
|
|
296
|
+
const entryRel = Path.relative(process.cwd(), abs);
|
|
297
|
+
const outFile = (Path.basename(abs, ".flux") + ".js");
|
|
298
|
+
console.error(red("\n✗ Cannot run a multi-file Flux project with `flux run`.\n"));
|
|
299
|
+
console.error((" Found: " + yellow((("import ... from \"" + example) + "\""))));
|
|
300
|
+
console.error();
|
|
301
|
+
console.error(((" Use " + cyan("flux bundle")) + " to compile all files into one, then run it:\n"));
|
|
302
|
+
console.error((" " + yellow(((("flux bundle " + entryRel) + " -o dist/") + outFile))));
|
|
303
|
+
console.error((" " + yellow(("node dist/" + outFile))));
|
|
304
|
+
console.error();
|
|
305
|
+
console.error(((" Or use " + cyan("flux run")) + " only for single-file scripts (no inter-file .flux imports).\n"));
|
|
306
|
+
process.exit(1);
|
|
307
|
+
}
|
|
279
308
|
const result = transpile(source, { jsx: (opts.jsx ?? false), jsxTarget: (opts.jsxTarget ?? "browser"), mangle: false });
|
|
280
309
|
if (!result.success) {
|
|
281
310
|
console.error(red("\n✗ Compile error"));
|
|
@@ -443,6 +472,10 @@ function cmdBundle(entryPath, opts) {
|
|
|
443
472
|
console.log(result.code);
|
|
444
473
|
return;
|
|
445
474
|
}
|
|
475
|
+
const outDir = Path.dirname(Path.resolve(outFile));
|
|
476
|
+
if (!Fs.existsSync(outDir)) {
|
|
477
|
+
Fs.mkdirSync(outDir, { recursive: true });
|
|
478
|
+
}
|
|
446
479
|
Fs.writeFileSync(outFile, result.code, "utf8");
|
|
447
480
|
const kb = (result.code.length / 1024).toFixed(1);
|
|
448
481
|
console.log((((((green("✓ Bundle done") + gray(((" (" + elapsed) + "ms) "))) + Path.basename(abs)) + gray(((" + " + (result.modules - 1)) + " module(s) → "))) + cyan(Path.relative(process.cwd(), outFile))) + gray(((" [" + kb) + " KB]"))));
|
|
@@ -573,6 +606,7 @@ function cmdRepl(opts) {
|
|
|
573
606
|
}
|
|
574
607
|
function cmdInit(name, opts) {
|
|
575
608
|
const projectName = (name ?? "my-flux-app");
|
|
609
|
+
const template = (opts.template ?? "script");
|
|
576
610
|
const dir = Path.resolve(projectName);
|
|
577
611
|
if (Fs.existsSync(dir)) {
|
|
578
612
|
console.error(red(("✗ Directory already exists: " + projectName)));
|
|
@@ -581,38 +615,77 @@ function cmdInit(name, opts) {
|
|
|
581
615
|
Fs.mkdirSync(dir, { recursive: true });
|
|
582
616
|
Fs.mkdirSync(Path.join(dir, "src"), { recursive: true });
|
|
583
617
|
Fs.mkdirSync(Path.join(dir, "tests"), { recursive: true });
|
|
584
|
-
const
|
|
585
|
-
const
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
console.log(("
|
|
618
|
+
const gitignore = "node_modules/\ndist/\n*.js.map\n.DS_Store\n";
|
|
619
|
+
const testFlux = "// Tests for {projectName}\n// Run: flux test tests/\n\nfn add(a, b):\n return a + b\n\nfn clamp(value, lo, hi):\n if value < lo: return lo\n if value > hi: return hi\n return value\n\nfn test_add():\n assert(add(1, 2) == 3, \"1+2=3\")\n assert(add(0, 0) == 0, \"0+0=0\")\n assert(add(-1, 1) == 0, \"-1+1=0\")\n\nfn test_clamp():\n assert(clamp(5, 0, 10) == 5, \"within range\")\n assert(clamp(-1, 0, 10) == 0, \"below min\")\n assert(clamp(15, 0, 10) == 10, \"above max\")";
|
|
620
|
+
if ((template == "script")) {
|
|
621
|
+
const mainFlux = "// {projectName} — a Flux script\n// Run: npm run dev (or: node_modules/.bin/flux run src/main.flux)\n\nimport Fs from \"fs\"\nimport Path from \"path\"\n\nfn main():\n print(\"Hello from Flux!\")\n print(\"Node version: {process.version}\")\n\nmain()";
|
|
622
|
+
const pkgJson = { name: projectName, version: "1.0.0", description: "A Flux project", main: "dist/main.js", scripts: { dev: "flux run src/main.flux", build: "flux bundle src/main.flux -o dist/main.js", start: "node dist/main.js", test: "flux test tests/" }, flux: { entry: "src/main.flux", outDir: "dist", mangle: false, sourcemap: false }, dependencies: { "@xnoxs/flux-lang": ("^" + VERSION) }, devDependencies: { } };
|
|
623
|
+
Fs.writeFileSync(Path.join(dir, "src", "main.flux"), mainFlux, "utf8");
|
|
624
|
+
Fs.writeFileSync(Path.join(dir, "tests", "main.test.flux"), testFlux, "utf8");
|
|
625
|
+
Fs.writeFileSync(Path.join(dir, "package.json"), (JSON.stringify(pkgJson, null, 2) + "\n"), "utf8");
|
|
626
|
+
Fs.writeFileSync(Path.join(dir, ".gitignore"), gitignore, "utf8");
|
|
627
|
+
console.log();
|
|
628
|
+
console.log((((green("✓ Project ") + bold((projectName + "/"))) + green(" created with template ")) + cyan("script")));
|
|
629
|
+
console.log();
|
|
630
|
+
console.log(bold(" Next steps:"));
|
|
631
|
+
console.log((" " + yellow(("cd " + projectName))));
|
|
632
|
+
console.log((" " + yellow("npm install")));
|
|
633
|
+
console.log((" " + yellow("npm run dev")));
|
|
634
|
+
console.log();
|
|
635
|
+
console.log(gray(" Add packages:"));
|
|
636
|
+
console.log((" " + yellow("flux add <package>")));
|
|
637
|
+
console.log();
|
|
638
|
+
return;
|
|
603
639
|
}
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
640
|
+
if ((template == "server")) {
|
|
641
|
+
const serverFlux = "// {projectName} — Flux HTTP server\n// Run: npm run dev\n// Build: npm run build && npm start\n\nimport Express from \"express\"\n\nval app = Express()\nval PORT = process.env.PORT or 3000\n\napp.use(Express.json())\n\napp.get(\"/\", (req, res) ->\n res.json({ status: \"ok\", message: \"Hello from Flux!\", port: PORT })\n)\n\napp.listen(PORT, -> print(\"Server running at http://localhost:{PORT}\"))";
|
|
642
|
+
const pkgJson = { name: projectName, version: "1.0.0", description: "A Flux HTTP server", main: "dist/server.js", scripts: { dev: "flux run src/server.flux", build: "flux bundle src/server.flux -o dist/server.js", start: "node dist/server.js", test: "flux test tests/" }, flux: { entry: "src/server.flux", outDir: "dist", mangle: false, sourcemap: false }, dependencies: { "@xnoxs/flux-lang": ("^" + VERSION), express: "^4.18.0" }, devDependencies: { } };
|
|
643
|
+
Fs.writeFileSync(Path.join(dir, "src", "server.flux"), serverFlux, "utf8");
|
|
644
|
+
Fs.writeFileSync(Path.join(dir, "tests", "main.test.flux"), testFlux, "utf8");
|
|
645
|
+
Fs.writeFileSync(Path.join(dir, "package.json"), (JSON.stringify(pkgJson, null, 2) + "\n"), "utf8");
|
|
646
|
+
Fs.writeFileSync(Path.join(dir, ".gitignore"), gitignore, "utf8");
|
|
647
|
+
console.log();
|
|
648
|
+
console.log((((green("✓ Project ") + bold((projectName + "/"))) + green(" created with template ")) + cyan("server")));
|
|
649
|
+
console.log();
|
|
650
|
+
console.log(bold(" Next steps:"));
|
|
651
|
+
console.log((" " + yellow(("cd " + projectName))));
|
|
652
|
+
console.log((" " + yellow("npm install")));
|
|
653
|
+
console.log((" " + yellow("npm run dev")));
|
|
654
|
+
console.log();
|
|
655
|
+
console.log(gray(" Build for production:"));
|
|
656
|
+
console.log((" " + yellow("npm run build && npm start")));
|
|
657
|
+
console.log();
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
if ((template == "webapp")) {
|
|
661
|
+
const utilsFlux = "// Utility functions\nexport fn formatDate(date):\n val d = new Date(date)\n return d.toISOString().split(\"T\")[0]\n\nexport fn paginate(items, page, perPage):\n val start = (page - 1) * perPage\n return items.slice(start, start + perPage)";
|
|
662
|
+
const routesFlux = "// Route handlers\nimport { formatDate, paginate } from \"./utils\" // inter-file .flux import\n\nval items = [\n { id: 1, name: \"Item A\", date: \"2024-01-01\" },\n { id: 2, name: \"Item B\", date: \"2024-02-01\" },\n { id: 3, name: \"Item C\", date: \"2024-03-01\" },\n]\n\nexport fn setupRoutes(app):\n app.get(\"/items\", (req, res) ->\n val page = parseInt(req.query.page or \"1\")\n val perPage = parseInt(req.query.per_page or \"10\")\n val data = paginate(items, page, perPage)\n res.json({ data, page })\n )\n\n app.get(\"/items/:id\", (req, res) ->\n val item = items.find(i -> i.id == parseInt(req.params.id))\n if item == null:\n res.status(404).json({ error: \"Not found\" })\n else:\n res.json({ data: item, date: formatDate(item.date) })\n )";
|
|
663
|
+
const serverFlux = "// Entry point — MUST be compiled with: npm run build\n// Inter-file .flux imports only work via flux bundle, NOT flux run\nimport Express from \"express\"\nimport { setupRoutes } from \"./routes\" // inter-file .flux import\n\nval app = Express()\nval PORT = process.env.PORT or 3000\n\napp.use(Express.json())\nsetupRoutes(app)\n\napp.listen(PORT, -> print(\"{projectName} running at http://localhost:{PORT}\"))";
|
|
664
|
+
const pkgJson = { name: projectName, version: "1.0.0", description: "A Flux web app", main: "dist/server.js", scripts: { build: "flux bundle src/server.flux -o dist/server.js", start: "node dist/server.js", dev: "flux bundle src/server.flux -o dist/server.js && node dist/server.js", watch: "flux watch src/server.flux", test: "flux test tests/" }, flux: { entry: "src/server.flux", outDir: "dist", mangle: false, sourcemap: true }, dependencies: { "@xnoxs/flux-lang": ("^" + VERSION), express: "^4.18.0" }, devDependencies: { } };
|
|
665
|
+
Fs.writeFileSync(Path.join(dir, "src", "utils.flux"), utilsFlux, "utf8");
|
|
666
|
+
Fs.writeFileSync(Path.join(dir, "src", "routes.flux"), routesFlux, "utf8");
|
|
667
|
+
Fs.writeFileSync(Path.join(dir, "src", "server.flux"), serverFlux, "utf8");
|
|
668
|
+
Fs.writeFileSync(Path.join(dir, "tests", "main.test.flux"), testFlux, "utf8");
|
|
669
|
+
Fs.writeFileSync(Path.join(dir, "package.json"), (JSON.stringify(pkgJson, null, 2) + "\n"), "utf8");
|
|
670
|
+
Fs.writeFileSync(Path.join(dir, ".gitignore"), gitignore, "utf8");
|
|
671
|
+
console.log();
|
|
672
|
+
console.log((((green("✓ Project ") + bold((projectName + "/"))) + green(" created with template ")) + cyan("webapp")));
|
|
673
|
+
console.log();
|
|
674
|
+
console.log(bold(" Next steps:"));
|
|
675
|
+
console.log((" " + yellow(("cd " + projectName))));
|
|
676
|
+
console.log(((" " + yellow("npm install")) + gray(" # install dependencies")));
|
|
677
|
+
console.log(((" " + yellow("npm run build")) + gray(" # compile all .flux → dist/server.js")));
|
|
678
|
+
console.log(((" " + yellow("npm start")) + gray(" # run the server")));
|
|
679
|
+
console.log();
|
|
680
|
+
console.log((yellow(" Note: ") + gray("This project uses multiple .flux files.")));
|
|
681
|
+
console.log(((gray(" Always build with ") + yellow("npm run build")) + gray(" (flux bundle) before running.")));
|
|
682
|
+
console.log(((gray(" Do NOT use ") + red("flux run")) + gray(" on files that import other .flux files.")));
|
|
683
|
+
console.log();
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
686
|
+
console.error(red(("✗ Unknown template: " + template)));
|
|
687
|
+
console.error(gray(" Available templates: script, server, webapp"));
|
|
688
|
+
process.exit(1);
|
|
616
689
|
}
|
|
617
690
|
function cmdSelfHosted(sub, opts) {
|
|
618
691
|
const SELF = Path.join(__dirname, ".");
|
package/src/self/config.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/* compiled from src/self/config.flux by Flux Lang */
|
|
2
|
+
'use strict';
|
|
1
3
|
// ── Flux stdlib ──
|
|
2
4
|
|
|
3
5
|
function join(arr, sep) { return arr.join(sep != null ? sep : ','); }
|
|
@@ -14,10 +16,23 @@ function endsWith(s, suffix) { return String(s).endsWith(suffix); }
|
|
|
14
16
|
|
|
15
17
|
const Fs = require("fs");
|
|
16
18
|
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.
|
|
19
|
+
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.npmjs.org", pkg: { name: "", version: "1.0.0", description: "", author: "", license: "MIT", deps: { }, devDeps: { } } };
|
|
18
20
|
module.exports.DEFAULT_CONFIG = DEFAULT_CONFIG;
|
|
19
21
|
function loadConfig(cwd_) {
|
|
20
22
|
const cwd = (cwd_ ?? process.cwd());
|
|
23
|
+
const pkgJsonPath = Path.join(cwd, "package.json");
|
|
24
|
+
if (Fs.existsSync(pkgJsonPath)) {
|
|
25
|
+
try {
|
|
26
|
+
const raw = Fs.readFileSync(pkgJsonPath, "utf8");
|
|
27
|
+
const parsed = JSON.parse(raw);
|
|
28
|
+
if ((parsed.flux && (typeof parsed.flux == "object"))) {
|
|
29
|
+
return mergeConfig(DEFAULT_CONFIG, parsed.flux);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
catch (e) {
|
|
33
|
+
throw new Error(("Invalid package.json: " + e.message));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
21
36
|
const jsonPath = Path.join(cwd, "flux.json");
|
|
22
37
|
if (Fs.existsSync(jsonPath)) {
|
|
23
38
|
try {
|
|
@@ -29,16 +44,6 @@ function loadConfig(cwd_) {
|
|
|
29
44
|
throw new Error(("Invalid flux.json: " + e.message));
|
|
30
45
|
}
|
|
31
46
|
}
|
|
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
47
|
return { ...DEFAULT_CONFIG };
|
|
43
48
|
}
|
|
44
49
|
module.exports.loadConfig = loadConfig;
|
|
@@ -58,9 +63,18 @@ function mergeConfig(base, overrides) {
|
|
|
58
63
|
module.exports.mergeConfig = mergeConfig;
|
|
59
64
|
function writeConfig(config, cwd_) {
|
|
60
65
|
const cwd = (cwd_ ?? process.cwd());
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
Fs.
|
|
66
|
+
const pkgPath = Path.join(cwd, "package.json");
|
|
67
|
+
let pkg = { };
|
|
68
|
+
if (Fs.existsSync(pkgPath)) {
|
|
69
|
+
try {
|
|
70
|
+
pkg = JSON.parse(Fs.readFileSync(pkgPath, "utf8"));
|
|
71
|
+
}
|
|
72
|
+
catch (e) {
|
|
73
|
+
pkg = { };
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
pkg.flux = config;
|
|
77
|
+
Fs.writeFileSync(pkgPath, (JSON.stringify(pkg, null, 2) + "\n"), "utf8");
|
|
64
78
|
}
|
|
65
79
|
module.exports.writeConfig = writeConfig;
|
|
66
80
|
function validateConfig(config) {
|
|
@@ -76,19 +90,19 @@ function validateConfig(config) {
|
|
|
76
90
|
module.exports.validateConfig = validateConfig;
|
|
77
91
|
function readPackage(cwd_) {
|
|
78
92
|
const cwd = (cwd_ ?? process.cwd());
|
|
79
|
-
const fluxJson = Path.join(cwd, "flux.json");
|
|
80
93
|
const pkgJson = Path.join(cwd, "package.json");
|
|
81
|
-
|
|
94
|
+
const fluxJson = Path.join(cwd, "flux.json");
|
|
95
|
+
if (Fs.existsSync(pkgJson)) {
|
|
82
96
|
try {
|
|
83
|
-
return JSON.parse(Fs.readFileSync(
|
|
97
|
+
return JSON.parse(Fs.readFileSync(pkgJson, "utf8"));
|
|
84
98
|
}
|
|
85
99
|
catch (e) {
|
|
86
100
|
return null;
|
|
87
101
|
}
|
|
88
102
|
}
|
|
89
|
-
if (Fs.existsSync(
|
|
103
|
+
if (Fs.existsSync(fluxJson)) {
|
|
90
104
|
try {
|
|
91
|
-
return JSON.parse(Fs.readFileSync(
|
|
105
|
+
return JSON.parse(Fs.readFileSync(fluxJson, "utf8"));
|
|
92
106
|
}
|
|
93
107
|
catch (e2) {
|
|
94
108
|
return null;
|