gofront 0.0.1

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/index.js ADDED
@@ -0,0 +1,338 @@
1
+ #!/usr/bin/env node
2
+ // GoFront compiler CLI
3
+ //
4
+ // Usage:
5
+ // gofront <file.go> — compile single file, print JS to stdout
6
+ // gofront <dir> — compile all *.go in directory as one package
7
+ // gofront . — compile current directory
8
+ // gofront <file.go> -o out.js — write to file
9
+ // gofront <dir> -o out.js — write bundle to file
10
+ // gofront <file.go> --check — type-check only (no output)
11
+ // gofront <file.go> --watch — watch and recompile on change
12
+ // gofront <file.go> --ast — dump AST of first file (debug)
13
+ // gofront <file.go> --tokens — dump tokens of first file (debug)
14
+ // gofront init [dir] — scaffold a new GoFront project
15
+
16
+ import {
17
+ existsSync,
18
+ mkdirSync,
19
+ readFileSync,
20
+ statSync,
21
+ watch,
22
+ writeFileSync,
23
+ } from "node:fs";
24
+ import { createRequire } from "node:module";
25
+
26
+ const _require = createRequire(import.meta.url);
27
+ const { version } = _require("../package.json");
28
+
29
+ import { basename, dirname, join, resolve } from "node:path";
30
+ import { minify } from "terser";
31
+ import { CodeGen } from "./codegen.js";
32
+ import { compileDir } from "./compiler.js";
33
+ import { parseDts } from "./dts-parser.js";
34
+ import { Lexer } from "./lexer.js";
35
+ import { Parser } from "./parser.js";
36
+ import { isLocalPath, resolveAll, resolveGwDir } from "./resolver.js";
37
+ import { TypeChecker } from "./typechecker.js";
38
+
39
+ // ── Parse CLI args ───────────────────────────────────────────
40
+
41
+ const args = process.argv.slice(2);
42
+ if (args[0] === "--version" || args[0] === "-v") {
43
+ console.log(`gofront ${version}`);
44
+ process.exit(0);
45
+ }
46
+
47
+ if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
48
+ console.log(
49
+ `
50
+ GoFront — a Go-inspired language that compiles to JavaScript
51
+
52
+ Usage:
53
+ gofront <file.go> Compile single file and print to stdout
54
+ gofront <dir> (or gofront .) Compile all *.go in directory as one bundle
55
+ gofront <input> -o out.js Compile and write to file
56
+ gofront <input> --check Type-check only
57
+ gofront <input> --watch Watch for changes and recompile
58
+ gofront <input> --source-map Append inline source map to output
59
+ gofront <input> --minify Minify output with terser
60
+ gofront <file.go> --ast Dump AST (debug)
61
+ gofront <file.go> --tokens Dump tokens (debug)
62
+ gofront init [dir] Scaffold a new GoFront project
63
+ gofront --version Print version
64
+ `.trim(),
65
+ );
66
+ process.exit(0);
67
+ }
68
+
69
+ // ── init subcommand ──────────────────────────────────────────
70
+
71
+ if (args[0] === "init") {
72
+ const targetArg = args[1] ?? ".";
73
+ const targetDir = resolve(targetArg);
74
+
75
+ if (targetArg !== ".") {
76
+ try {
77
+ mkdirSync(targetDir, { recursive: true });
78
+ } catch (e) {
79
+ console.error(`gofront: cannot create '${targetArg}': ${e.message}`);
80
+ process.exit(1);
81
+ }
82
+ }
83
+
84
+ const mainPath = join(targetDir, "main.go");
85
+ if (existsSync(mainPath)) {
86
+ console.error(`gofront: ${mainPath} already exists — nothing written`);
87
+ process.exit(1);
88
+ }
89
+
90
+ const pkgName =
91
+ targetArg === "." ? basename(resolve(".")) : basename(targetDir);
92
+ // package names must be valid identifiers
93
+ const safePkg = pkgName.replace(/[^a-zA-Z0-9_]/g, "_").replace(/^[0-9]/, "_");
94
+
95
+ const template = `package ${safePkg}
96
+
97
+ func main() {
98
+ \tconsole.log("Hello from GoFront!")
99
+ }
100
+ `;
101
+ try {
102
+ writeFileSync(mainPath, template);
103
+ } catch (e) {
104
+ console.error(`gofront: cannot write '${mainPath}': ${e.message}`);
105
+ process.exit(1);
106
+ }
107
+
108
+ console.error(`gofront: created ${mainPath}`);
109
+ console.error(
110
+ `gofront: run gofront ${targetArg === "." ? "main.go" : `${targetArg}/main.go`} to compile`,
111
+ );
112
+ process.exit(0);
113
+ }
114
+
115
+ const inputArg = args[0];
116
+ const outputFlag = args.indexOf("-o");
117
+ const outputFile = outputFlag !== -1 ? args[outputFlag + 1] : null;
118
+ const checkOnly = args.includes("--check");
119
+ const dumpAst = args.includes("--ast");
120
+ const dumpTokens = args.includes("--tokens");
121
+ const sourceMap = args.includes("--source-map");
122
+ const watchMode = args.includes("--watch");
123
+ const minifyOutput = args.includes("--minify");
124
+
125
+ // ── Determine input mode ─────────────────────────────────────
126
+
127
+ const inputPath = resolve(inputArg);
128
+ let isDir = false;
129
+ try {
130
+ isDir = statSync(inputPath).isDirectory();
131
+ } catch (e) {
132
+ console.error(`gofront: cannot access '${inputArg}': ${e.message}`);
133
+ process.exit(1);
134
+ }
135
+
136
+ // ── Compile function (used for initial build and re-builds) ──
137
+
138
+ function runCompile() {
139
+ if (isDir) {
140
+ return compileDir(inputPath);
141
+ }
142
+
143
+ // Single-file mode
144
+ let source;
145
+ try {
146
+ source = readFileSync(inputPath, "utf8");
147
+ } catch (e) {
148
+ throw new Error(`cannot read '${inputArg}': ${e.message}`);
149
+ }
150
+
151
+ let tokens;
152
+ tokens = new Lexer(source, basename(inputPath)).tokenize();
153
+
154
+ if (dumpTokens) {
155
+ for (const tok of tokens) console.log(tok.toString());
156
+ process.exit(0);
157
+ }
158
+
159
+ let ast;
160
+ ast = new Parser(tokens, basename(inputPath), source).parse();
161
+ ast._source = source;
162
+
163
+ if (dumpAst) {
164
+ console.log(JSON.stringify(ast, null, 2));
165
+ process.exit(0);
166
+ }
167
+
168
+ const checker = new TypeChecker();
169
+ const fromDir = dirname(inputPath);
170
+ const jsImports = new Map();
171
+
172
+ for (const imp of ast.imports) {
173
+ for (const path of imp.paths) {
174
+ if (!path.startsWith("js:")) continue;
175
+ const dtsPath = join(fromDir, path.slice(3));
176
+ try {
177
+ const { types, values } = parseDts(readFileSync(dtsPath, "utf8"));
178
+ checker.addDefinitions(types, values);
179
+ } catch (e) {
180
+ throw new Error(`cannot read '${dtsPath}': ${e.message}`);
181
+ }
182
+ }
183
+ }
184
+
185
+ const resolved = resolveAll(ast.imports, inputPath, parseDts);
186
+ for (const [path, info] of resolved) {
187
+ if (!info) continue;
188
+ checker.addDefinitions(info.types, info.values);
189
+ jsImports.set(path, [...info.values.keys()]);
190
+ }
191
+
192
+ const bundledPackages = new Set();
193
+ const preambles = [];
194
+
195
+ for (const imp of ast.imports) {
196
+ for (const path of imp.paths) {
197
+ if (!isLocalPath(path)) continue;
198
+ const depDir = resolveGwDir(path, inputPath);
199
+ if (!depDir) {
200
+ console.error(
201
+ `gofront: warning: cannot find local package '${path}' relative to ${fromDir}`,
202
+ );
203
+ continue;
204
+ }
205
+ const dep = compileDir(depDir);
206
+ preambles.push(dep.js);
207
+ bundledPackages.add(dep.pkgName);
208
+ checker.addPackageNamespace(
209
+ dep.pkgName,
210
+ dep.exportedSymbols,
211
+ dep.exportedTypes,
212
+ );
213
+ }
214
+ }
215
+
216
+ const errors = checker.check(ast);
217
+ if (errors.length > 0) {
218
+ throw new Error(errors.map((e) => e.message).join("\n"));
219
+ }
220
+
221
+ const cg = new CodeGen(checker, jsImports, bundledPackages);
222
+ let js = cg.generate(ast);
223
+
224
+ if (sourceMap) {
225
+ const map = cg.getSourceMap(inputArg);
226
+ const b64 = Buffer.from(map).toString("base64");
227
+ js += `\n//# sourceMappingURL=data:application/json;base64,${b64}`;
228
+ }
229
+
230
+ const output = preambles.length > 0 ? `${preambles.join("\n")}\n${js}` : js;
231
+ return { js: output, _cg: cg };
232
+ }
233
+
234
+ function _writeOutput(js) {
235
+ if (outputFile) {
236
+ writeFileSync(outputFile, `${js}\n`);
237
+ } else {
238
+ console.log(js);
239
+ }
240
+ }
241
+
242
+ // ── Minify helper ────────────────────────────────────────────
243
+
244
+ async function maybeMinify(js) {
245
+ if (!minifyOutput) return js;
246
+ const result = await minify(js, {
247
+ module: true,
248
+ compress: true,
249
+ mangle: true,
250
+ });
251
+ return result.code;
252
+ }
253
+
254
+ // ── Single-shot mode ─────────────────────────────────────────
255
+
256
+ if (!watchMode) {
257
+ let result;
258
+ const startMs = performance.now();
259
+ try {
260
+ result = runCompile();
261
+ } catch (e) {
262
+ console.error(`gofront: ${e.message}`);
263
+ process.exit(1);
264
+ }
265
+
266
+ if (checkOnly) {
267
+ const elapsedMs = (performance.now() - startMs).toFixed(0);
268
+ console.error(`gofront: ${inputArg} — OK (${elapsedMs}ms)`);
269
+ process.exit(0);
270
+ }
271
+
272
+ let js;
273
+ try {
274
+ js = await maybeMinify(result.js);
275
+ } catch (e) {
276
+ console.error(`gofront: minify failed: ${e.message}`);
277
+ process.exit(1);
278
+ }
279
+
280
+ const elapsedMs = (performance.now() - startMs).toFixed(0);
281
+
282
+ if (outputFile) {
283
+ try {
284
+ writeFileSync(outputFile, `${js}\n`);
285
+ console.error(`gofront: wrote ${outputFile} (${elapsedMs}ms)`);
286
+ } catch (e) {
287
+ console.error(`gofront: cannot write '${outputFile}': ${e.message}`);
288
+ process.exit(1);
289
+ }
290
+ } else {
291
+ console.log(js);
292
+ }
293
+ process.exit(0);
294
+ }
295
+
296
+ // ── Watch mode ───────────────────────────────────────────────
297
+
298
+ function timestamp() {
299
+ return new Date().toLocaleTimeString();
300
+ }
301
+
302
+ async function buildOnce(_label) {
303
+ try {
304
+ const startMs = performance.now();
305
+ const result = runCompile();
306
+ const js = await maybeMinify(result.js);
307
+ const elapsedMs = (performance.now() - startMs).toFixed(0);
308
+ if (outputFile) {
309
+ writeFileSync(outputFile, `${js}\n`);
310
+ console.error(
311
+ `[${timestamp()}] gofront: OK — wrote ${outputFile} (${elapsedMs}ms)`,
312
+ );
313
+ } else {
314
+ // Clear screen then print
315
+ process.stdout.write("\x1Bc");
316
+ console.log(js);
317
+ console.error(`[${timestamp()}] gofront: OK (${elapsedMs}ms)`);
318
+ }
319
+ } catch (e) {
320
+ console.error(`[${timestamp()}] gofront: ERROR`);
321
+ for (const line of e.message.split("\n")) console.error(` ${line}`);
322
+ }
323
+ }
324
+
325
+ // Initial build
326
+ buildOnce("initial");
327
+
328
+ // Determine what to watch
329
+ const watchTarget = isDir ? inputPath : dirname(inputPath);
330
+
331
+ let debounce = null;
332
+ watch(watchTarget, { recursive: true }, (_event, filename) => {
333
+ if (filename && !filename.endsWith(".go")) return;
334
+ clearTimeout(debounce);
335
+ debounce = setTimeout(() => buildOnce(filename ?? "change"), 80);
336
+ });
337
+
338
+ console.error(`[${timestamp()}] gofront: watching ${inputArg} ...`);