gofront 1.0.1 → 1.2.0

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.
@@ -308,6 +308,64 @@ export function setupExtendedGlobals(globals, types) {
308
308
  },
309
309
  });
310
310
 
311
+ const TESTING_T = {
312
+ kind: "named",
313
+ name: "testing.T",
314
+ underlying: { kind: "struct", fields: new Map(), methods: new Map() },
315
+ };
316
+ const TESTING_PTR = { kind: "pointer", base: TESTING_T };
317
+
318
+ TESTING_T.underlying.methods = new Map([
319
+ ["Error", { kind: "func", params: [ANY], returns: [VOID], variadic: true }],
320
+ [
321
+ "Errorf",
322
+ { kind: "func", params: [STRING, ANY], returns: [VOID], variadic: true },
323
+ ],
324
+ ["Fatal", { kind: "func", params: [ANY], returns: [VOID], variadic: true }],
325
+ [
326
+ "Fatalf",
327
+ { kind: "func", params: [STRING, ANY], returns: [VOID], variadic: true },
328
+ ],
329
+ ["Fail", { kind: "func", params: [], returns: [VOID] }],
330
+ ["Failed", { kind: "func", params: [], returns: [BOOL] }],
331
+ ["FailNow", { kind: "func", params: [], returns: [VOID] }],
332
+ ["Log", { kind: "func", params: [ANY], returns: [VOID], variadic: true }],
333
+ [
334
+ "Logf",
335
+ { kind: "func", params: [STRING, ANY], returns: [VOID], variadic: true },
336
+ ],
337
+ ["Skip", { kind: "func", params: [ANY], returns: [VOID], variadic: true }],
338
+ [
339
+ "Skipf",
340
+ { kind: "func", params: [STRING, ANY], returns: [VOID], variadic: true },
341
+ ],
342
+ ["Skipped", { kind: "func", params: [], returns: [BOOL] }],
343
+ ["Helper", { kind: "func", params: [], returns: [VOID] }],
344
+ [
345
+ "Run",
346
+ {
347
+ kind: "func",
348
+ params: [
349
+ STRING,
350
+ { kind: "func", params: [TESTING_PTR], returns: [VOID] },
351
+ ],
352
+ returns: [BOOL],
353
+ },
354
+ ],
355
+ ["Name", { kind: "func", params: [], returns: [STRING] }],
356
+ ]);
357
+
358
+ globals.define("testing", {
359
+ kind: "namespace",
360
+ name: "testing",
361
+ members: {
362
+ T: TESTING_T,
363
+ Short: { kind: "func", params: [], returns: [BOOL] },
364
+ Verbose: { kind: "func", params: [], returns: [BOOL] },
365
+ },
366
+ });
367
+ types.set("testing.T", TESTING_T);
368
+
311
369
  for (const name of [
312
370
  "append",
313
371
  "len",
package/src/vendor.js ADDED
@@ -0,0 +1,280 @@
1
+ // Vendor dependency bundler for GoFront projects.
2
+ // Dynamically loads a bundler (rolldown or esbuild) from consumer devDependencies.
3
+
4
+ import {
5
+ copyFileSync,
6
+ existsSync,
7
+ mkdirSync,
8
+ readFileSync,
9
+ rmSync,
10
+ writeFileSync,
11
+ } from "node:fs";
12
+ import { createRequire } from "node:module";
13
+ import { dirname, isAbsolute, join, relative, resolve } from "node:path";
14
+ import { pathToFileURL } from "node:url";
15
+
16
+ export function getExportNames(pkgName) {
17
+ const base = pkgName.replace(/^@[^/]+\//, "");
18
+ const clean = base.replace(/[^a-zA-Z0-9_]/g, "_");
19
+ const names = new Set([pkgName, base, clean]);
20
+
21
+ if (pkgName === "@emailjs/browser") names.add("emailjs");
22
+ if (pkgName === "fuse.js") {
23
+ names.add("Fuse");
24
+ names.add("fuse");
25
+ }
26
+ if (pkgName === "prismjs") {
27
+ names.add("Prism");
28
+ names.add("prism");
29
+ }
30
+ if (pkgName === "marked") {
31
+ names.add("marked");
32
+ }
33
+
34
+ return [...names];
35
+ }
36
+
37
+ export function generateVendorEntry(packages) {
38
+ const imports = [];
39
+ const assignments = [];
40
+ const exports = [];
41
+
42
+ packages.forEach((pkg, idx) => {
43
+ const id = `_dep_${idx}`;
44
+ imports.push(`import * as ${id} from ${JSON.stringify(pkg)};`);
45
+
46
+ const names = getExportNames(pkg);
47
+ for (const name of names) {
48
+ assignments.push(
49
+ ` window[${JSON.stringify(name)}] = ${id}.default || ${id};`,
50
+ );
51
+ }
52
+
53
+ const cleanId = pkg
54
+ .replace(/^@[^/]+\//, "")
55
+ .replace(/[^a-zA-Z0-9_$]/g, "_");
56
+ exports.push(` ${id} as ${cleanId}`);
57
+ });
58
+
59
+ return `// Auto-generated GoFront vendor entry point
60
+ ${imports.join("\n")}
61
+
62
+ if (typeof window !== "undefined") {
63
+ ${assignments.join("\n")}
64
+ }
65
+
66
+ export {
67
+ ${exports.join(",\n")}
68
+ };
69
+ `;
70
+ }
71
+
72
+ export function findBundler(projectDir) {
73
+ const pkgPath = join(projectDir, "package.json");
74
+ let req;
75
+ if (existsSync(pkgPath)) {
76
+ req = createRequire(pkgPath);
77
+ } else {
78
+ req = createRequire(join(projectDir, "dummy.js"));
79
+ }
80
+
81
+ try {
82
+ const path = req.resolve("rolldown");
83
+ return { name: "rolldown", path };
84
+ } catch {}
85
+
86
+ try {
87
+ const path = req.resolve("esbuild");
88
+ return { name: "esbuild", path };
89
+ } catch {}
90
+
91
+ return null;
92
+ }
93
+
94
+ export function loadVendorConfig(projectDir) {
95
+ const pkgPath = join(projectDir, "package.json");
96
+ let pkg = {};
97
+ if (existsSync(pkgPath)) {
98
+ try {
99
+ pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
100
+ } catch {}
101
+ }
102
+
103
+ const gfjPath = join(projectDir, "gofront.json");
104
+ let gfj = {};
105
+ if (existsSync(gfjPath)) {
106
+ try {
107
+ gfj = JSON.parse(readFileSync(gfjPath, "utf8"));
108
+ } catch {}
109
+ }
110
+
111
+ const vendorConfig = gfj.vendor ?? pkg.vendor ?? null;
112
+ const dependencies = Object.keys(pkg.dependencies ?? {});
113
+
114
+ let dest = null;
115
+ let packages = dependencies;
116
+ let minify = false;
117
+
118
+ if (typeof vendorConfig === "string") {
119
+ dest = vendorConfig;
120
+ } else if (vendorConfig && typeof vendorConfig === "object") {
121
+ if (vendorConfig.dest) dest = vendorConfig.dest;
122
+ if (Array.isArray(vendorConfig.packages)) packages = vendorConfig.packages;
123
+ if (typeof vendorConfig.minify === "boolean") minify = vendorConfig.minify;
124
+ }
125
+
126
+ if (!dest) {
127
+ dest = existsSync(join(projectDir, "app")) ? "app/vendor.js" : "vendor.js";
128
+ }
129
+
130
+ return { dest, packages, minify };
131
+ }
132
+
133
+ export function resolveDestinationPaths(projectRoot, dest) {
134
+ const destList = Array.isArray(dest) ? dest : [dest];
135
+ if (destList.length === 0) {
136
+ throw new Error(
137
+ "gofront: at least one vendor destination must be specified",
138
+ );
139
+ }
140
+
141
+ return destList.map((d) => {
142
+ const fullPath = resolve(projectRoot, d);
143
+ const relDest = relative(projectRoot, fullPath);
144
+ if (relDest.startsWith("..") || isAbsolute(relDest)) {
145
+ throw new Error(
146
+ `gofront: vendor destination '${d}' is outside project directory`,
147
+ );
148
+ }
149
+ return fullPath;
150
+ });
151
+ }
152
+
153
+ async function runBundler(
154
+ bundlerInfo,
155
+ { projectRoot, entryFile, destPath, minify },
156
+ ) {
157
+ if (bundlerInfo.name === "rolldown") {
158
+ const rolldownModule =
159
+ bundlerInfo.instance ??
160
+ (await import(pathToFileURL(bundlerInfo.path).href));
161
+ const rolldownFn =
162
+ rolldownModule.rolldown ||
163
+ rolldownModule.default?.rolldown ||
164
+ rolldownModule.default;
165
+
166
+ const bundle = await rolldownFn({
167
+ input: entryFile,
168
+ cwd: projectRoot,
169
+ });
170
+ await bundle.write({
171
+ file: destPath,
172
+ format: "esm",
173
+ minify: Boolean(minify),
174
+ });
175
+ await bundle.close();
176
+ return;
177
+ }
178
+
179
+ if (bundlerInfo.name === "esbuild") {
180
+ const esbuildModule =
181
+ bundlerInfo.instance ??
182
+ (await import(pathToFileURL(bundlerInfo.path).href));
183
+ const buildFn =
184
+ esbuildModule.build || esbuildModule.default?.build || esbuildModule;
185
+
186
+ await buildFn({
187
+ entryPoints: [entryFile],
188
+ bundle: true,
189
+ format: "esm",
190
+ outfile: destPath,
191
+ absWorkingDir: projectRoot,
192
+ minify: Boolean(minify),
193
+ });
194
+ }
195
+ }
196
+
197
+ export async function bundleVendor(projectDir = ".", options = {}) {
198
+ const projectRoot = resolve(projectDir);
199
+ const config = loadVendorConfig(projectRoot);
200
+
201
+ const packages = options.packages ?? config.packages;
202
+ const dest = options.dest ?? config.dest;
203
+ const minify = options.minify ?? config.minify ?? false;
204
+
205
+ if (!packages || packages.length === 0) {
206
+ return {
207
+ bundled: [],
208
+ skipped: 0,
209
+ dest,
210
+ minify: Boolean(minify),
211
+ message: "no external dependencies found",
212
+ };
213
+ }
214
+
215
+ const destPaths = resolveDestinationPaths(projectRoot, dest);
216
+ const bundlerInfo = options.bundler ?? findBundler(projectRoot);
217
+ if (!bundlerInfo) {
218
+ console.warn(
219
+ "gofront: no bundler found (rolldown or esbuild). Run 'npm install --save-dev rolldown' to enable vendor bundling.",
220
+ );
221
+ return {
222
+ bundled: [],
223
+ skipped: packages.length,
224
+ dest,
225
+ bundler: null,
226
+ minify: Boolean(minify),
227
+ reason: "no bundler installed",
228
+ };
229
+ }
230
+
231
+ for (const p of destPaths) {
232
+ mkdirSync(dirname(p), { recursive: true });
233
+ }
234
+
235
+ if (typeof bundlerInfo.bundle === "function") {
236
+ await bundlerInfo.bundle({
237
+ projectRoot,
238
+ packages,
239
+ dest: destPaths.length === 1 ? destPaths[0] : destPaths,
240
+ minify: Boolean(minify),
241
+ });
242
+ return {
243
+ bundled: packages,
244
+ bundler: bundlerInfo.name ?? "custom",
245
+ dest,
246
+ minify: Boolean(minify),
247
+ };
248
+ }
249
+
250
+ const entryCode = generateVendorEntry(packages);
251
+ const entryFile = join(
252
+ projectRoot,
253
+ `.gofront-vendor-entry-${Date.now()}.mjs`,
254
+ );
255
+ writeFileSync(entryFile, entryCode);
256
+
257
+ try {
258
+ await runBundler(bundlerInfo, {
259
+ projectRoot,
260
+ entryFile,
261
+ destPath: destPaths[0],
262
+ minify,
263
+ });
264
+
265
+ for (let i = 1; i < destPaths.length; i++) {
266
+ copyFileSync(destPaths[0], destPaths[i]);
267
+ }
268
+ } finally {
269
+ if (existsSync(entryFile)) {
270
+ rmSync(entryFile, { force: true });
271
+ }
272
+ }
273
+
274
+ return {
275
+ bundled: packages,
276
+ bundler: bundlerInfo.name,
277
+ dest,
278
+ minify: Boolean(minify),
279
+ };
280
+ }