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.
@@ -0,0 +1,156 @@
1
+ // Multi-file GoFront package compiler.
2
+ //
3
+ // compileDir(dir, options) — compile all *.go in a directory as one package
4
+ // compileFiles(files, options) — compile an explicit list of .go files
5
+ //
6
+ // Both return:
7
+ // {
8
+ // pkgName: string,
9
+ // js: string, // generated JS bundle (may include dep preamble)
10
+ // exportedSymbols: Map<name, type>, // for importers to build a namespace
11
+ // exportedTypes: Map<name, type>,
12
+ // }
13
+ //
14
+ // Local imports (`import "./subpkg"`) are compiled recursively and bundled inline.
15
+ // Cross-package access uses the qualified form: `pkg.Foo`. The codegen
16
+ // de-qualifies it because the dependency is inlined.
17
+
18
+ import { readdirSync, readFileSync } from "node:fs";
19
+ import { basename, dirname, join, resolve } from "node:path";
20
+ import { CodeGen } from "./codegen.js";
21
+ import { parseDts } from "./dts-parser.js";
22
+ import { Lexer } from "./lexer.js";
23
+ import { Parser } from "./parser.js";
24
+ import { isLocalPath, resolveAll, resolveGwDir } from "./resolver.js";
25
+ import { TypeChecker } from "./typechecker.js";
26
+
27
+ // ── Helpers ──────────────────────────────────────────────────
28
+
29
+ function parseFile(filePath) {
30
+ const source = readFileSync(filePath, "utf8");
31
+ const filename = basename(filePath);
32
+ const tokens = new Lexer(source, filename).tokenize();
33
+ const ast = new Parser(tokens, filename, source).parse();
34
+ ast._source = source;
35
+ return ast;
36
+ }
37
+
38
+ function gwFilesIn(dir) {
39
+ return readdirSync(dir)
40
+ .filter((f) => f.endsWith(".go"))
41
+ .sort() // deterministic order
42
+ .map((f) => join(dir, f));
43
+ }
44
+
45
+ // ── Main entry points ─────────────────────────────────────────
46
+
47
+ export function compileDir(dir, options = {}) {
48
+ const files = gwFilesIn(dir);
49
+ if (files.length === 0) throw new Error(`No .go files found in ${dir}`);
50
+ return compileFiles(files, { ...options, fromDir: dir });
51
+ }
52
+
53
+ export function compileFiles(files, options = {}) {
54
+ const fromDir = options.fromDir ?? dirname(resolve(files[0]));
55
+
56
+ // ── 1. Parse ─────────────────────────────────────────────────
57
+ const parseErrors = [];
58
+ const programs = [];
59
+ for (const f of files) {
60
+ try {
61
+ programs.push(parseFile(f));
62
+ } catch (e) {
63
+ parseErrors.push(e.message);
64
+ }
65
+ }
66
+ if (parseErrors.length > 0) {
67
+ throw new Error(parseErrors.join("\n"));
68
+ }
69
+
70
+ // Validate consistent package name
71
+ const pkgNames = [...new Set(programs.map((p) => p.pkg.name))];
72
+ if (pkgNames.length > 1)
73
+ throw new Error(
74
+ `Mixed package names in ${fromDir}: ${pkgNames.join(", ")}`,
75
+ );
76
+ const pkgName = pkgNames[0];
77
+
78
+ // ── 2. Resolve imports ────────────────────────────────────────
79
+ const checker = new TypeChecker();
80
+ const jsImports = new Map(); // npm imports → exported names (for ESM emit)
81
+ const bundledPackages = new Set(); // package names whose code is inlined
82
+ const preambles = []; // JS code from compiled sub-packages
83
+
84
+ const allImports = programs.flatMap((p) => p.imports);
85
+ const dummyFromFile = join(fromDir, "_dummy.go");
86
+
87
+ // js: prefix — local .d.ts files
88
+ for (const imp of allImports) {
89
+ for (const path of imp.paths) {
90
+ if (!path.startsWith("js:")) continue;
91
+ const dtsPath = join(fromDir, path.slice(3));
92
+ try {
93
+ const { types, values } = parseDts(readFileSync(dtsPath, "utf8"));
94
+ checker.addDefinitions(types, values);
95
+ } catch (e) {
96
+ throw new Error(`Cannot read '${dtsPath}': ${e.message}`);
97
+ }
98
+ }
99
+ }
100
+
101
+ // npm packages
102
+ const resolved = resolveAll(allImports, dummyFromFile, parseDts);
103
+ for (const [path, info] of resolved) {
104
+ if (!info) continue;
105
+ checker.addDefinitions(info.types, info.values);
106
+ jsImports.set(path, [...info.values.keys()]);
107
+ }
108
+
109
+ // local GoFront packages (./subdir)
110
+ const seenLocalPaths = new Set();
111
+ for (const imp of allImports) {
112
+ for (const path of imp.paths) {
113
+ if (!isLocalPath(path) || seenLocalPaths.has(path)) continue;
114
+ seenLocalPaths.add(path);
115
+
116
+ const depDir = resolveGwDir(path, dummyFromFile);
117
+ if (!depDir) {
118
+ console.error(
119
+ `gofront: warning: cannot find local package '${path}' relative to ${fromDir}`,
120
+ );
121
+ continue;
122
+ }
123
+
124
+ // Recursively compile dependency
125
+ const dep = compileDir(depDir);
126
+ preambles.push(dep.js);
127
+ bundledPackages.add(dep.pkgName);
128
+ checker.addPackageNamespace(
129
+ dep.pkgName,
130
+ dep.exportedSymbols,
131
+ dep.exportedTypes,
132
+ );
133
+ }
134
+ }
135
+
136
+ // ── 3. Type-check ─────────────────────────────────────────────
137
+ const errors = checker.checkAll(programs);
138
+ if (errors.length > 0) {
139
+ const msgs = errors.map((e) => e.message).join("\n");
140
+ throw new Error(msgs);
141
+ }
142
+
143
+ // ── 4. Code generation ────────────────────────────────────────
144
+ const codegen = new CodeGen(checker, jsImports, bundledPackages);
145
+ const mainJs = codegen.generateAll(programs);
146
+
147
+ const js =
148
+ preambles.length > 0 ? `${preambles.join("\n")}\n${mainJs}` : mainJs;
149
+
150
+ return {
151
+ pkgName,
152
+ js,
153
+ exportedSymbols: checker.getExportedSymbols(),
154
+ exportedTypes: checker.getExportedTypes(),
155
+ };
156
+ }