nukejs 0.0.5 → 0.0.7
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/README.md +96 -8
- package/dist/Link.js +16 -0
- package/dist/Link.js.map +7 -0
- package/dist/build-common.d.ts +57 -80
- package/dist/build-common.js +156 -168
- package/dist/build-common.js.map +2 -2
- package/dist/build-node.d.ts +14 -0
- package/dist/build-node.js +50 -51
- package/dist/build-node.js.map +2 -2
- package/dist/build-vercel.d.ts +18 -0
- package/dist/build-vercel.js +76 -63
- package/dist/build-vercel.js.map +2 -2
- package/dist/builder.d.ts +10 -0
- package/dist/builder.js +31 -62
- package/dist/builder.js.map +3 -3
- package/dist/bundle.js +69 -6
- package/dist/bundle.js.map +2 -2
- package/dist/component-analyzer.d.ts +13 -10
- package/dist/component-analyzer.js +26 -17
- package/dist/component-analyzer.js.map +2 -2
- package/dist/hmr-bundle.js +17 -4
- package/dist/hmr-bundle.js.map +2 -2
- package/dist/html-store.d.ts +7 -0
- package/dist/html-store.js.map +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/renderer.js +2 -7
- package/dist/renderer.js.map +2 -2
- package/dist/router.js +16 -4
- package/dist/router.js.map +2 -2
- package/dist/ssr.js +21 -4
- package/dist/ssr.js.map +2 -2
- package/dist/use-html.js +5 -1
- package/dist/use-html.js.map +2 -2
- package/dist/use-router.js +28 -0
- package/dist/use-router.js.map +7 -0
- package/package.json +1 -1
- package/dist/as-is/Link.tsx +0 -20
- package/dist/as-is/useRouter.ts +0 -33
- /package/dist/{as-is/Link.d.ts → Link.d.ts} +0 -0
- /package/dist/{as-is/useRouter.d.ts → use-router.d.ts} +0 -0
package/dist/build-vercel.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import path from "path";
|
|
3
|
-
import
|
|
3
|
+
import { randomBytes } from "node:crypto";
|
|
4
4
|
import { build } from "esbuild";
|
|
5
5
|
import { loadConfig } from "./config.js";
|
|
6
6
|
import {
|
|
@@ -18,14 +18,49 @@ import {
|
|
|
18
18
|
const OUTPUT_DIR = path.resolve(".vercel/output");
|
|
19
19
|
const FUNCTIONS_DIR = path.join(OUTPUT_DIR, "functions");
|
|
20
20
|
const STATIC_DIR = path.join(OUTPUT_DIR, "static");
|
|
21
|
-
|
|
22
|
-
fs.mkdirSync(
|
|
21
|
+
for (const dir of [FUNCTIONS_DIR, STATIC_DIR])
|
|
22
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
23
23
|
const config = await loadConfig();
|
|
24
24
|
const SERVER_DIR = path.resolve(config.serverDir);
|
|
25
25
|
const PAGES_DIR = path.resolve("./app/pages");
|
|
26
26
|
const PUBLIC_DIR = path.resolve("./app/public");
|
|
27
|
+
const NODE_BUILTINS = [
|
|
28
|
+
"node:*",
|
|
29
|
+
"http",
|
|
30
|
+
"https",
|
|
31
|
+
"fs",
|
|
32
|
+
"path",
|
|
33
|
+
"url",
|
|
34
|
+
"crypto",
|
|
35
|
+
"stream",
|
|
36
|
+
"buffer",
|
|
37
|
+
"events",
|
|
38
|
+
"util",
|
|
39
|
+
"os",
|
|
40
|
+
"net",
|
|
41
|
+
"tls",
|
|
42
|
+
"child_process",
|
|
43
|
+
"worker_threads",
|
|
44
|
+
"cluster",
|
|
45
|
+
"dgram",
|
|
46
|
+
"dns",
|
|
47
|
+
"readline",
|
|
48
|
+
"zlib",
|
|
49
|
+
"assert",
|
|
50
|
+
"module",
|
|
51
|
+
"perf_hooks",
|
|
52
|
+
"string_decoder",
|
|
53
|
+
"timers",
|
|
54
|
+
"async_hooks",
|
|
55
|
+
"v8",
|
|
56
|
+
"vm"
|
|
57
|
+
];
|
|
58
|
+
const CJS_COMPAT_BANNER = {
|
|
59
|
+
js: `import { createRequire } from 'module';
|
|
60
|
+
const require = createRequire(import.meta.url);`
|
|
61
|
+
};
|
|
27
62
|
function emitVercelFunction(name, bundleText) {
|
|
28
|
-
const funcDir = path.join(FUNCTIONS_DIR, name
|
|
63
|
+
const funcDir = path.join(FUNCTIONS_DIR, `${name}.func`);
|
|
29
64
|
fs.mkdirSync(funcDir, { recursive: true });
|
|
30
65
|
fs.writeFileSync(path.join(funcDir, "index.mjs"), bundleText);
|
|
31
66
|
fs.writeFileSync(
|
|
@@ -107,15 +142,16 @@ export default async function handler(req: IncomingMessage, res: ServerResponse)
|
|
|
107
142
|
function makePagesDispatcherSource(routes) {
|
|
108
143
|
const imports = routes.map((r, i) => `import __page_${i}__ from ${JSON.stringify(r.adapterPath)};`).join("\n");
|
|
109
144
|
const routeEntries = routes.map(
|
|
110
|
-
(r, i) => ` { regex: ${JSON.stringify(r.srcRegex)}, params: ${JSON.stringify(r.paramNames)}, handler: __page_${i}__ },`
|
|
145
|
+
(r, i) => ` { regex: ${JSON.stringify(r.srcRegex)}, params: ${JSON.stringify(r.paramNames)}, catchAll: ${JSON.stringify(r.catchAllNames)}, handler: __page_${i}__ },`
|
|
111
146
|
).join("\n");
|
|
112
147
|
return `import type { IncomingMessage, ServerResponse } from 'http';
|
|
113
148
|
${imports}
|
|
114
149
|
|
|
115
150
|
const ROUTES: Array<{
|
|
116
|
-
regex:
|
|
117
|
-
params:
|
|
118
|
-
|
|
151
|
+
regex: string;
|
|
152
|
+
params: string[];
|
|
153
|
+
catchAll: string[];
|
|
154
|
+
handler: (req: IncomingMessage, res: ServerResponse) => Promise<void>;
|
|
119
155
|
}> = [
|
|
120
156
|
${routeEntries}
|
|
121
157
|
];
|
|
@@ -128,9 +164,16 @@ export default async function handler(req: IncomingMessage, res: ServerResponse)
|
|
|
128
164
|
const m = pathname.match(new RegExp(route.regex));
|
|
129
165
|
if (!m) continue;
|
|
130
166
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
167
|
+
const catchAllSet = new Set(route.catchAll);
|
|
168
|
+
route.params.forEach((name, i) => {
|
|
169
|
+
const raw = m[i + 1] ?? '';
|
|
170
|
+
if (catchAllSet.has(name)) {
|
|
171
|
+
// Encode catch-all as repeated keys so the handler can getAll() \u2192 string[]
|
|
172
|
+
raw.split('/').filter(Boolean).forEach(seg => url.searchParams.append(name, seg));
|
|
173
|
+
} else {
|
|
174
|
+
url.searchParams.set(name, raw);
|
|
175
|
+
}
|
|
176
|
+
});
|
|
134
177
|
req.url = pathname + (url.search || '');
|
|
135
178
|
|
|
136
179
|
return route.handler(req, res);
|
|
@@ -147,9 +190,8 @@ const apiFiles = walkFiles(SERVER_DIR);
|
|
|
147
190
|
if (apiFiles.length === 0) console.warn(`\u26A0 No server files found in ${SERVER_DIR}`);
|
|
148
191
|
const apiRoutes = apiFiles.map((relPath) => ({ ...analyzeFile(relPath, "api"), absPath: path.join(SERVER_DIR, relPath) })).sort((a, b) => b.specificity - a.specificity);
|
|
149
192
|
if (apiRoutes.length > 0) {
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
fs.writeFileSync(dispatcherPath, dispatcherSource);
|
|
193
|
+
const dispatcherPath = path.join(SERVER_DIR, `_api_dispatcher_${randomBytes(4).toString("hex")}.ts`);
|
|
194
|
+
fs.writeFileSync(dispatcherPath, makeApiDispatcherSource(apiRoutes));
|
|
153
195
|
try {
|
|
154
196
|
const result = await build({
|
|
155
197
|
entryPoints: [dispatcherPath],
|
|
@@ -157,7 +199,8 @@ if (apiRoutes.length > 0) {
|
|
|
157
199
|
format: "esm",
|
|
158
200
|
platform: "node",
|
|
159
201
|
target: "node20",
|
|
160
|
-
|
|
202
|
+
banner: CJS_COMPAT_BANNER,
|
|
203
|
+
external: NODE_BUILTINS,
|
|
161
204
|
write: false
|
|
162
205
|
});
|
|
163
206
|
emitVercelFunction("api", result.outputFiles[0].text);
|
|
@@ -165,22 +208,20 @@ if (apiRoutes.length > 0) {
|
|
|
165
208
|
} finally {
|
|
166
209
|
fs.unlinkSync(dispatcherPath);
|
|
167
210
|
}
|
|
168
|
-
for (const { srcRegex } of apiRoutes)
|
|
211
|
+
for (const { srcRegex } of apiRoutes)
|
|
169
212
|
vercelRoutes.push({ src: srcRegex, dest: "/api" });
|
|
170
|
-
}
|
|
171
213
|
}
|
|
172
214
|
const serverPages = collectServerPages(PAGES_DIR);
|
|
173
215
|
if (serverPages.length > 0) {
|
|
174
|
-
const
|
|
175
|
-
const prerenderedHtml = await bundleClientComponents(
|
|
176
|
-
const
|
|
216
|
+
const globalRegistry = collectGlobalClientRegistry(serverPages, PAGES_DIR);
|
|
217
|
+
const prerenderedHtml = await bundleClientComponents(globalRegistry, PAGES_DIR, STATIC_DIR);
|
|
218
|
+
const prerenderedRecord = Object.fromEntries(prerenderedHtml);
|
|
177
219
|
const tempAdapterPaths = [];
|
|
178
220
|
for (const page of serverPages) {
|
|
179
|
-
const
|
|
180
|
-
const
|
|
181
|
-
const
|
|
182
|
-
const
|
|
183
|
-
const { registry, clientComponentNames } = buildPerPageRegistry(absPath, layoutPaths, PAGES_DIR);
|
|
221
|
+
const adapterDir = path.dirname(page.absPath);
|
|
222
|
+
const adapterPath = path.join(adapterDir, `_page_adapter_${randomBytes(4).toString("hex")}.ts`);
|
|
223
|
+
const layoutPaths = findPageLayouts(page.absPath, PAGES_DIR);
|
|
224
|
+
const { registry, clientComponentNames } = buildPerPageRegistry(page.absPath, layoutPaths, PAGES_DIR);
|
|
184
225
|
const layoutImports = layoutPaths.map((lp, i) => {
|
|
185
226
|
const rel = path.relative(adapterDir, lp).replace(/\\/g, "/");
|
|
186
227
|
return `import __layout_${i}__ from ${JSON.stringify(rel.startsWith(".") ? rel : "./" + rel)};`;
|
|
@@ -188,23 +229,25 @@ if (serverPages.length > 0) {
|
|
|
188
229
|
fs.writeFileSync(
|
|
189
230
|
adapterPath,
|
|
190
231
|
makePageAdapterSource({
|
|
191
|
-
pageImport: JSON.stringify("./" + path.basename(absPath)),
|
|
232
|
+
pageImport: JSON.stringify("./" + path.basename(page.absPath)),
|
|
192
233
|
layoutImports,
|
|
193
234
|
clientComponentNames,
|
|
194
235
|
allClientIds: [...registry.keys()],
|
|
195
236
|
layoutArrayItems: layoutPaths.map((_, i) => `__layout_${i}__`).join(", "),
|
|
196
|
-
prerenderedHtml:
|
|
237
|
+
prerenderedHtml: prerenderedRecord,
|
|
238
|
+
catchAllNames: page.catchAllNames
|
|
197
239
|
})
|
|
198
240
|
);
|
|
199
241
|
tempAdapterPaths.push(adapterPath);
|
|
200
|
-
console.log(` prepared ${path.relative(PAGES_DIR, absPath)} \u2192 ${page.funcPath} [page]`);
|
|
242
|
+
console.log(` prepared ${path.relative(PAGES_DIR, page.absPath)} \u2192 ${page.funcPath} [page]`);
|
|
201
243
|
}
|
|
202
244
|
const dispatcherRoutes = serverPages.map((page, i) => ({
|
|
203
245
|
adapterPath: tempAdapterPaths[i],
|
|
204
246
|
srcRegex: page.srcRegex,
|
|
205
|
-
paramNames: page.paramNames
|
|
247
|
+
paramNames: page.paramNames,
|
|
248
|
+
catchAllNames: page.catchAllNames
|
|
206
249
|
}));
|
|
207
|
-
const dispatcherPath = path.join(PAGES_DIR, `_pages_dispatcher_${
|
|
250
|
+
const dispatcherPath = path.join(PAGES_DIR, `_pages_dispatcher_${randomBytes(4).toString("hex")}.ts`);
|
|
208
251
|
fs.writeFileSync(dispatcherPath, makePagesDispatcherSource(dispatcherRoutes));
|
|
209
252
|
try {
|
|
210
253
|
const result = await build({
|
|
@@ -214,37 +257,8 @@ if (serverPages.length > 0) {
|
|
|
214
257
|
platform: "node",
|
|
215
258
|
target: "node20",
|
|
216
259
|
jsx: "automatic",
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
"http",
|
|
220
|
-
"https",
|
|
221
|
-
"fs",
|
|
222
|
-
"path",
|
|
223
|
-
"url",
|
|
224
|
-
"crypto",
|
|
225
|
-
"stream",
|
|
226
|
-
"buffer",
|
|
227
|
-
"events",
|
|
228
|
-
"util",
|
|
229
|
-
"os",
|
|
230
|
-
"net",
|
|
231
|
-
"tls",
|
|
232
|
-
"child_process",
|
|
233
|
-
"worker_threads",
|
|
234
|
-
"cluster",
|
|
235
|
-
"dgram",
|
|
236
|
-
"dns",
|
|
237
|
-
"readline",
|
|
238
|
-
"zlib",
|
|
239
|
-
"assert",
|
|
240
|
-
"module",
|
|
241
|
-
"perf_hooks",
|
|
242
|
-
"string_decoder",
|
|
243
|
-
"timers",
|
|
244
|
-
"async_hooks",
|
|
245
|
-
"v8",
|
|
246
|
-
"vm"
|
|
247
|
-
],
|
|
260
|
+
banner: CJS_COMPAT_BANNER,
|
|
261
|
+
external: NODE_BUILTINS,
|
|
248
262
|
define: { "process.env.NODE_ENV": '"production"' },
|
|
249
263
|
write: false
|
|
250
264
|
});
|
|
@@ -254,9 +268,8 @@ if (serverPages.length > 0) {
|
|
|
254
268
|
fs.unlinkSync(dispatcherPath);
|
|
255
269
|
for (const p of tempAdapterPaths) if (fs.existsSync(p)) fs.unlinkSync(p);
|
|
256
270
|
}
|
|
257
|
-
for (const { srcRegex } of serverPages)
|
|
271
|
+
for (const { srcRegex } of serverPages)
|
|
258
272
|
vercelRoutes.push({ src: srcRegex, dest: "/pages" });
|
|
259
|
-
}
|
|
260
273
|
}
|
|
261
274
|
fs.writeFileSync(
|
|
262
275
|
path.join(OUTPUT_DIR, "config.json"),
|
package/dist/build-vercel.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/build-vercel.ts"],
|
|
4
|
-
"sourcesContent": ["import fs from 'fs';\r\nimport path from 'path';\r\nimport crypto from 'crypto';\r\nimport { build } from 'esbuild';\r\n\r\nimport { loadConfig } from './config';\r\nimport {\r\n walkFiles,\r\n analyzeFile,\r\n collectServerPages,\r\n collectGlobalClientRegistry,\r\n bundleClientComponents,\r\n findPageLayouts,\r\n buildPerPageRegistry,\r\n makePageAdapterSource,\r\n buildCombinedBundle,\r\n copyPublicFiles,\r\n} from './build-common';\r\n\r\n// \u2500\u2500\u2500 Output directories \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\nconst OUTPUT_DIR = path.resolve('.vercel/output');\r\nconst FUNCTIONS_DIR = path.join(OUTPUT_DIR, 'functions');\r\nconst STATIC_DIR = path.join(OUTPUT_DIR, 'static');\r\n\r\nfs.mkdirSync(FUNCTIONS_DIR, { recursive: true });\r\nfs.mkdirSync(STATIC_DIR, { recursive: true });\r\n\r\n// \u2500\u2500\u2500 Config \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\nconst config = await loadConfig();\r\nconst SERVER_DIR = path.resolve(config.serverDir);\r\nconst PAGES_DIR = path.resolve('./app/pages');\r\nconst PUBLIC_DIR = path.resolve('./app/public');\r\n\r\n// \u2500\u2500\u2500 Helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\ntype VercelRoute = { src: string; dest: string };\r\n\r\n/** Writes a bundled dispatcher into a Vercel .func directory. */\r\nfunction emitVercelFunction(name: string, bundleText: string): void {\r\n const funcDir = path.join(FUNCTIONS_DIR, name + '.func');\r\n fs.mkdirSync(funcDir, { recursive: true });\r\n fs.writeFileSync(path.join(funcDir, 'index.mjs'), bundleText);\r\n fs.writeFileSync(\r\n path.join(funcDir, '.vc-config.json'),\r\n JSON.stringify({ runtime: 'nodejs20.x', handler: 'index.mjs', launcherType: 'Nodejs' }, null, 2),\r\n );\r\n}\r\n\r\n// \u2500\u2500\u2500 API dispatcher source \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\n/**\r\n * Generates a single dispatcher that imports every API route module directly,\r\n * matches the incoming URL against each route's regex, injects captured params,\r\n * and calls the right HTTP-method export (GET, POST, \u2026) or default export.\r\n *\r\n * enhance / parseBody helpers are included once rather than once per route.\r\n */\r\nfunction makeApiDispatcherSource(\r\n routes: Array<{ absPath: string; srcRegex: string; paramNames: string[] }>,\r\n): string {\r\n const imports = routes\r\n .map((r, i) => `import * as __api_${i}__ from ${JSON.stringify(r.absPath)};`)\r\n .join('\\n');\r\n\r\n const routeEntries = routes\r\n .map((r, i) =>\r\n ` { regex: ${JSON.stringify(r.srcRegex)}, params: ${JSON.stringify(r.paramNames)}, mod: __api_${i}__ },`,\r\n )\r\n .join('\\n');\r\n\r\n return `\\\r\nimport type { IncomingMessage, ServerResponse } from 'http';\r\n${imports}\r\n\r\nfunction enhance(res: ServerResponse) {\r\n (res as any).json = function(data: any, status = 200) {\r\n this.statusCode = status;\r\n this.setHeader('Content-Type', 'application/json');\r\n this.end(JSON.stringify(data));\r\n };\r\n (res as any).status = function(code: number) { this.statusCode = code; return this; };\r\n return res;\r\n}\r\n\r\nasync function parseBody(req: IncomingMessage): Promise<any> {\r\n return new Promise((resolve, reject) => {\r\n let body = '';\r\n req.on('data', (chunk: any) => { body += chunk.toString(); });\r\n req.on('end', () => {\r\n try {\r\n resolve(\r\n body && req.headers['content-type']?.includes('application/json')\r\n ? JSON.parse(body)\r\n : body,\r\n );\r\n } catch (e) { reject(e); }\r\n });\r\n req.on('error', reject);\r\n });\r\n}\r\n\r\nconst ROUTES = [\r\n${routeEntries}\r\n];\r\n\r\nexport default async function handler(req: IncomingMessage, res: ServerResponse) {\r\n const url = new URL(req.url || '/', 'http://localhost');\r\n const pathname = url.pathname;\r\n\r\n for (const route of ROUTES) {\r\n const m = pathname.match(new RegExp(route.regex));\r\n if (!m) continue;\r\n\r\n const method = (req.method || 'GET').toUpperCase();\r\n const apiRes = enhance(res);\r\n const apiReq = req as any;\r\n\r\n apiReq.body = await parseBody(req);\r\n apiReq.query = Object.fromEntries(url.searchParams);\r\n apiReq.params = {};\r\n route.params.forEach((name: string, i: number) => { apiReq.params[name] = m[i + 1]; });\r\n\r\n const fn = (route.mod as any)[method] ?? (route.mod as any)['default'];\r\n if (typeof fn !== 'function') {\r\n (apiRes as any).json({ error: \\`Method \\${method} not allowed\\` }, 405);\r\n return;\r\n }\r\n await fn(apiReq, apiRes);\r\n return;\r\n }\r\n\r\n res.statusCode = 404;\r\n res.setHeader('Content-Type', 'application/json');\r\n res.end(JSON.stringify({ error: 'Not Found' }));\r\n}\r\n`;\r\n}\r\n\r\n// \u2500\u2500\u2500 Pages dispatcher source \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\n/**\r\n * Generates a dispatcher that imports each page's pre-generated adapter by its\r\n * temp file path, matches the incoming URL, injects captured dynamic params as\r\n * query-string values (page handlers read params from req.url searchParams),\r\n * then delegates to the matching handler.\r\n */\r\nfunction makePagesDispatcherSource(\r\n routes: Array<{ adapterPath: string; srcRegex: string; paramNames: string[] }>,\r\n): string {\r\n const imports = routes\r\n .map((r, i) => `import __page_${i}__ from ${JSON.stringify(r.adapterPath)};`)\r\n .join('\\n');\r\n\r\n const routeEntries = routes\r\n .map((r, i) =>\r\n ` { regex: ${JSON.stringify(r.srcRegex)}, params: ${JSON.stringify(r.paramNames)}, handler: __page_${i}__ },`,\r\n )\r\n .join('\\n');\r\n\r\n return `\\\r\nimport type { IncomingMessage, ServerResponse } from 'http';\r\n${imports}\r\n\r\nconst ROUTES: Array<{\r\n regex: string;\r\n params: string[];\r\n handler: (req: IncomingMessage, res: ServerResponse) => Promise<void>;\r\n}> = [\r\n${routeEntries}\r\n];\r\n\r\nexport default async function handler(req: IncomingMessage, res: ServerResponse) {\r\n const url = new URL(req.url || '/', 'http://localhost');\r\n const pathname = url.pathname;\r\n\r\n for (const route of ROUTES) {\r\n const m = pathname.match(new RegExp(route.regex));\r\n if (!m) continue;\r\n\r\n // Inject dynamic params as query-string values so page handlers can read\r\n // them via new URL(req.url).searchParams \u2014 the same way they always have.\r\n route.params.forEach((name, i) => url.searchParams.set(name, m[i + 1]));\r\n req.url = pathname + (url.search || '');\r\n\r\n return route.handler(req, res);\r\n }\r\n\r\n res.statusCode = 404;\r\n res.setHeader('Content-Type', 'text/plain; charset=utf-8');\r\n res.end('Not Found');\r\n}\r\n`;\r\n}\r\n\r\n// \u2500\u2500\u2500 Build API function \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\nconst vercelRoutes: VercelRoute[] = [];\r\n\r\nconst apiFiles = walkFiles(SERVER_DIR);\r\nif (apiFiles.length === 0) console.warn(`\u26A0 No server files found in ${SERVER_DIR}`);\r\n\r\nconst apiRoutes = apiFiles\r\n .map(relPath => ({ ...analyzeFile(relPath, 'api'), absPath: path.join(SERVER_DIR, relPath) }))\r\n .sort((a, b) => b.specificity - a.specificity);\r\n\r\nif (apiRoutes.length > 0) {\r\n const dispatcherSource = makeApiDispatcherSource(apiRoutes);\r\n const dispatcherPath = path.join(SERVER_DIR, `_api_dispatcher_${crypto.randomBytes(4).toString('hex')}.ts`);\r\n fs.writeFileSync(dispatcherPath, dispatcherSource);\r\n\r\n try {\r\n const result = await build({\r\n entryPoints: [dispatcherPath],\r\n bundle: true,\r\n format: 'esm',\r\n platform: 'node',\r\n target: 'node20',\r\n packages: 'external',\r\n write: false,\r\n });\r\n emitVercelFunction('api', result.outputFiles[0].text);\r\n console.log(` built API dispatcher \u2192 api.func (${apiRoutes.length} route(s))`);\r\n } finally {\r\n fs.unlinkSync(dispatcherPath);\r\n }\r\n\r\n // API routes are listed first \u2014 they win on any URL collision with pages.\r\n for (const { srcRegex } of apiRoutes) {\r\n vercelRoutes.push({ src: srcRegex, dest: '/api' });\r\n }\r\n}\r\n\r\n// \u2500\u2500\u2500 Build Pages function \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\nconst serverPages = collectServerPages(PAGES_DIR);\r\n\r\nif (serverPages.length > 0) {\r\n // Pass 1 \u2014 bundle all client components to static files.\r\n const globalClientRegistry = collectGlobalClientRegistry(serverPages, PAGES_DIR);\r\n const prerenderedHtml = await bundleClientComponents(globalClientRegistry, PAGES_DIR, STATIC_DIR);\r\n const prerenderedHtmlRecord = Object.fromEntries(prerenderedHtml);\r\n\r\n // Pass 2 \u2014 write one temp adapter per page next to its source file (so\r\n // relative imports inside the component resolve correctly), then\r\n // bundle everything in one esbuild pass via the dispatcher.\r\n const tempAdapterPaths: string[] = [];\r\n\r\n for (const page of serverPages) {\r\n const { absPath } = page;\r\n const adapterDir = path.dirname(absPath);\r\n const adapterPath = path.join(adapterDir, `_page_adapter_${crypto.randomBytes(4).toString('hex')}.ts`);\r\n\r\n const layoutPaths = findPageLayouts(absPath, PAGES_DIR);\r\n const { registry, clientComponentNames } = buildPerPageRegistry(absPath, layoutPaths, PAGES_DIR);\r\n\r\n const layoutImports = layoutPaths\r\n .map((lp, i) => {\r\n const rel = path.relative(adapterDir, lp).replace(/\\\\/g, '/');\r\n return `import __layout_${i}__ from ${JSON.stringify(rel.startsWith('.') ? rel : './' + rel)};`;\r\n })\r\n .join('\\n');\r\n\r\n fs.writeFileSync(\r\n adapterPath,\r\n makePageAdapterSource({\r\n pageImport: JSON.stringify('./' + path.basename(absPath)),\r\n layoutImports,\r\n clientComponentNames,\r\n allClientIds: [...registry.keys()],\r\n layoutArrayItems: layoutPaths.map((_, i) => `__layout_${i}__`).join(', '),\r\n prerenderedHtml: prerenderedHtmlRecord,\r\n }),\r\n );\r\n\r\n tempAdapterPaths.push(adapterPath);\r\n console.log(` prepared ${path.relative(PAGES_DIR, absPath)} \u2192 ${page.funcPath} [page]`);\r\n }\r\n\r\n // Write the dispatcher and let esbuild bundle all adapters in one pass.\r\n const dispatcherRoutes = serverPages.map((page, i) => ({\r\n adapterPath: tempAdapterPaths[i],\r\n srcRegex: page.srcRegex,\r\n paramNames: page.paramNames,\r\n }));\r\n\r\n const dispatcherPath = path.join(PAGES_DIR, `_pages_dispatcher_${crypto.randomBytes(4).toString('hex')}.ts`);\r\n fs.writeFileSync(dispatcherPath, makePagesDispatcherSource(dispatcherRoutes));\r\n\r\n try {\r\n const result = await build({\r\n entryPoints: [dispatcherPath],\r\n bundle: true,\r\n format: 'esm',\r\n platform: 'node',\r\n target: 'node20',\r\n jsx: 'automatic',\r\n external: [\r\n 'node:*',\r\n 'http', 'https', 'fs', 'path', 'url', 'crypto', 'stream', 'buffer',\r\n 'events', 'util', 'os', 'net', 'tls', 'child_process', 'worker_threads',\r\n 'cluster', 'dgram', 'dns', 'readline', 'zlib', 'assert', 'module',\r\n 'perf_hooks', 'string_decoder', 'timers', 'async_hooks', 'v8', 'vm',\r\n ],\r\n define: { 'process.env.NODE_ENV': '\"production\"' },\r\n write: false,\r\n });\r\n emitVercelFunction('pages', result.outputFiles[0].text);\r\n console.log(` built Pages dispatcher \u2192 pages.func (${serverPages.length} page(s))`);\r\n } finally {\r\n fs.unlinkSync(dispatcherPath);\r\n for (const p of tempAdapterPaths) if (fs.existsSync(p)) fs.unlinkSync(p);\r\n }\r\n\r\n for (const { srcRegex } of serverPages) {\r\n vercelRoutes.push({ src: srcRegex, dest: '/pages' });\r\n }\r\n}\r\n\r\n// \u2500\u2500\u2500 Vercel config \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\nfs.writeFileSync(\r\n path.join(OUTPUT_DIR, 'config.json'),\r\n JSON.stringify({ version: 3, routes: vercelRoutes }, null, 2),\r\n);\r\n\r\nfs.writeFileSync(\r\n path.resolve('vercel.json'),\r\n JSON.stringify({ runtime: 'nodejs20.x' }, null, 2),\r\n);\r\n\r\n// \u2500\u2500\u2500 Static assets \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\nawait buildCombinedBundle(STATIC_DIR);\r\ncopyPublicFiles(PUBLIC_DIR, STATIC_DIR);\r\n\r\nconst fnCount = (apiRoutes.length > 0 ? 1 : 0) + (serverPages.length > 0 ? 1 : 0);\r\nconsole.log(`\\n\u2713 Vercel build complete \u2014 ${fnCount} function(s) \u2192 .vercel/output`);"],
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["/**\r\n * build-vercel.ts \u2014 Vercel Production Build\r\n *\r\n * Produces a .vercel/output/ directory conforming to the Vercel Build Output\r\n * API v3. Two serverless functions are emitted:\r\n *\r\n * api.func/ \u2190 single dispatcher bundling all API route handlers\r\n * pages.func/ \u2190 single dispatcher bundling all SSR page handlers\r\n *\r\n * Static assets (React runtime, client components, public files) go to\r\n * .vercel/output/static/ and are served by Vercel's CDN directly.\r\n *\r\n * Notes on bundling strategy:\r\n * - npm packages are FULLY BUNDLED (no node_modules at Vercel runtime).\r\n * - Node built-ins are kept external (available in the nodejs20.x runtime).\r\n * - A createRequire banner lets CJS packages (mongoose, etc.) resolve Node\r\n * built-ins correctly inside the ESM output bundle.\r\n */\r\n\r\nimport fs from 'fs';\r\nimport path from 'path';\r\nimport { randomBytes } from 'node:crypto';\r\nimport { build } from 'esbuild';\r\n\r\nimport { loadConfig } from './config';\r\nimport {\r\n walkFiles,\r\n analyzeFile,\r\n collectServerPages,\r\n collectGlobalClientRegistry,\r\n bundleClientComponents,\r\n findPageLayouts,\r\n buildPerPageRegistry,\r\n makePageAdapterSource,\r\n buildCombinedBundle,\r\n copyPublicFiles,\r\n} from './build-common';\r\n\r\n// \u2500\u2500\u2500 Output directories \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\nconst OUTPUT_DIR = path.resolve('.vercel/output');\r\nconst FUNCTIONS_DIR = path.join(OUTPUT_DIR, 'functions');\r\nconst STATIC_DIR = path.join(OUTPUT_DIR, 'static');\r\n\r\nfor (const dir of [FUNCTIONS_DIR, STATIC_DIR])\r\n fs.mkdirSync(dir, { recursive: true });\r\n\r\n// \u2500\u2500\u2500 Config \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\nconst config = await loadConfig();\r\nconst SERVER_DIR = path.resolve(config.serverDir);\r\nconst PAGES_DIR = path.resolve('./app/pages');\r\nconst PUBLIC_DIR = path.resolve('./app/public');\r\n\r\n// \u2500\u2500\u2500 Shared esbuild config \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\n/**\r\n * Node built-ins that should never be bundled.\r\n * npm packages are intentionally absent \u2014 they must be bundled because\r\n * Vercel serverless functions have no node_modules at runtime.\r\n */\r\nconst NODE_BUILTINS = [\r\n 'node:*',\r\n 'http', 'https', 'fs', 'path', 'url', 'crypto', 'stream', 'buffer',\r\n 'events', 'util', 'os', 'net', 'tls', 'child_process', 'worker_threads',\r\n 'cluster', 'dgram', 'dns', 'readline', 'zlib', 'assert', 'module',\r\n 'perf_hooks', 'string_decoder', 'timers', 'async_hooks', 'v8', 'vm',\r\n];\r\n\r\n/**\r\n * Banner injected at the top of every Vercel function bundle.\r\n *\r\n * Why it's needed: esbuild bundles CJS packages (mongoose, etc.) into ESM\r\n * output and replaces their require() calls with a __require2 shim. That\r\n * shim cannot resolve Node built-ins on its own inside an ESM module scope.\r\n * Injecting a real require (backed by createRequire) fixes the shim so that\r\n * dynamic require('crypto'), require('stream'), etc. work correctly.\r\n */\r\nconst CJS_COMPAT_BANNER = {\r\n js: `import { createRequire } from 'module';\\nconst require = createRequire(import.meta.url);`,\r\n};\r\n\r\n// \u2500\u2500\u2500 Helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\ntype VercelRoute = { src: string; dest: string };\r\n\r\n/** Writes a bundled dispatcher into a Vercel .func directory. */\r\nfunction emitVercelFunction(name: string, bundleText: string): void {\r\n const funcDir = path.join(FUNCTIONS_DIR, `${name}.func`);\r\n fs.mkdirSync(funcDir, { recursive: true });\r\n fs.writeFileSync(path.join(funcDir, 'index.mjs'), bundleText);\r\n fs.writeFileSync(\r\n path.join(funcDir, '.vc-config.json'),\r\n JSON.stringify({ runtime: 'nodejs20.x', handler: 'index.mjs', launcherType: 'Nodejs' }, null, 2),\r\n );\r\n}\r\n\r\n// \u2500\u2500\u2500 API dispatcher source \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\n/**\r\n * Generates a single TypeScript dispatcher that imports every API route module,\r\n * matches the incoming URL against each route's regex, injects captured params,\r\n * and calls the right HTTP-method export (GET, POST, \u2026) or default export.\r\n */\r\nfunction makeApiDispatcherSource(\r\n routes: Array<{ absPath: string; srcRegex: string; paramNames: string[] }>,\r\n): string {\r\n const imports = routes\r\n .map((r, i) => `import * as __api_${i}__ from ${JSON.stringify(r.absPath)};`)\r\n .join('\\n');\r\n\r\n const routeEntries = routes\r\n .map((r, i) =>\r\n ` { regex: ${JSON.stringify(r.srcRegex)}, params: ${JSON.stringify(r.paramNames)}, mod: __api_${i}__ },`,\r\n )\r\n .join('\\n');\r\n\r\n return `\\\r\nimport type { IncomingMessage, ServerResponse } from 'http';\r\n${imports}\r\n\r\nfunction enhance(res: ServerResponse) {\r\n (res as any).json = function(data: any, status = 200) {\r\n this.statusCode = status;\r\n this.setHeader('Content-Type', 'application/json');\r\n this.end(JSON.stringify(data));\r\n };\r\n (res as any).status = function(code: number) { this.statusCode = code; return this; };\r\n return res;\r\n}\r\n\r\nasync function parseBody(req: IncomingMessage): Promise<any> {\r\n return new Promise((resolve, reject) => {\r\n let body = '';\r\n req.on('data', (chunk: any) => { body += chunk.toString(); });\r\n req.on('end', () => {\r\n try {\r\n resolve(\r\n body && req.headers['content-type']?.includes('application/json')\r\n ? JSON.parse(body)\r\n : body,\r\n );\r\n } catch (e) { reject(e); }\r\n });\r\n req.on('error', reject);\r\n });\r\n}\r\n\r\nconst ROUTES = [\r\n${routeEntries}\r\n];\r\n\r\nexport default async function handler(req: IncomingMessage, res: ServerResponse) {\r\n const url = new URL(req.url || '/', 'http://localhost');\r\n const pathname = url.pathname;\r\n\r\n for (const route of ROUTES) {\r\n const m = pathname.match(new RegExp(route.regex));\r\n if (!m) continue;\r\n\r\n const method = (req.method || 'GET').toUpperCase();\r\n const apiRes = enhance(res);\r\n const apiReq = req as any;\r\n\r\n apiReq.body = await parseBody(req);\r\n apiReq.query = Object.fromEntries(url.searchParams);\r\n apiReq.params = {};\r\n route.params.forEach((name: string, i: number) => { apiReq.params[name] = m[i + 1]; });\r\n\r\n const fn = (route.mod as any)[method] ?? (route.mod as any)['default'];\r\n if (typeof fn !== 'function') {\r\n (apiRes as any).json({ error: \\`Method \\${method} not allowed\\` }, 405);\r\n return;\r\n }\r\n await fn(apiReq, apiRes);\r\n return;\r\n }\r\n\r\n res.statusCode = 404;\r\n res.setHeader('Content-Type', 'application/json');\r\n res.end(JSON.stringify({ error: 'Not Found' }));\r\n}\r\n`;\r\n}\r\n\r\n// \u2500\u2500\u2500 Pages dispatcher source \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\n/**\r\n * Generates a TypeScript dispatcher that imports each page's pre-generated\r\n * adapter, matches the incoming URL, encodes captured dynamic params as\r\n * query-string values (catch-all params use repeated keys), then delegates\r\n * to the matching handler.\r\n */\r\nfunction makePagesDispatcherSource(\r\n routes: Array<{\r\n adapterPath: string;\r\n srcRegex: string;\r\n paramNames: string[];\r\n catchAllNames: string[];\r\n }>,\r\n): string {\r\n const imports = routes\r\n .map((r, i) => `import __page_${i}__ from ${JSON.stringify(r.adapterPath)};`)\r\n .join('\\n');\r\n\r\n const routeEntries = routes\r\n .map((r, i) =>\r\n ` { regex: ${JSON.stringify(r.srcRegex)}, params: ${JSON.stringify(r.paramNames)}, catchAll: ${JSON.stringify(r.catchAllNames)}, handler: __page_${i}__ },`,\r\n )\r\n .join('\\n');\r\n\r\n return `\\\r\nimport type { IncomingMessage, ServerResponse } from 'http';\r\n${imports}\r\n\r\nconst ROUTES: Array<{\r\n regex: string;\r\n params: string[];\r\n catchAll: string[];\r\n handler: (req: IncomingMessage, res: ServerResponse) => Promise<void>;\r\n}> = [\r\n${routeEntries}\r\n];\r\n\r\nexport default async function handler(req: IncomingMessage, res: ServerResponse) {\r\n const url = new URL(req.url || '/', 'http://localhost');\r\n const pathname = url.pathname;\r\n\r\n for (const route of ROUTES) {\r\n const m = pathname.match(new RegExp(route.regex));\r\n if (!m) continue;\r\n\r\n const catchAllSet = new Set(route.catchAll);\r\n route.params.forEach((name, i) => {\r\n const raw = m[i + 1] ?? '';\r\n if (catchAllSet.has(name)) {\r\n // Encode catch-all as repeated keys so the handler can getAll() \u2192 string[]\r\n raw.split('/').filter(Boolean).forEach(seg => url.searchParams.append(name, seg));\r\n } else {\r\n url.searchParams.set(name, raw);\r\n }\r\n });\r\n req.url = pathname + (url.search || '');\r\n\r\n return route.handler(req, res);\r\n }\r\n\r\n res.statusCode = 404;\r\n res.setHeader('Content-Type', 'text/plain; charset=utf-8');\r\n res.end('Not Found');\r\n}\r\n`;\r\n}\r\n\r\n// \u2500\u2500\u2500 Build API function \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\nconst vercelRoutes: VercelRoute[] = [];\r\n\r\nconst apiFiles = walkFiles(SERVER_DIR);\r\nif (apiFiles.length === 0) console.warn(`\u26A0 No server files found in ${SERVER_DIR}`);\r\n\r\nconst apiRoutes = apiFiles\r\n .map(relPath => ({ ...analyzeFile(relPath, 'api'), absPath: path.join(SERVER_DIR, relPath) }))\r\n .sort((a, b) => b.specificity - a.specificity);\r\n\r\nif (apiRoutes.length > 0) {\r\n const dispatcherPath = path.join(SERVER_DIR, `_api_dispatcher_${randomBytes(4).toString('hex')}.ts`);\r\n fs.writeFileSync(dispatcherPath, makeApiDispatcherSource(apiRoutes));\r\n\r\n try {\r\n const result = await build({\r\n entryPoints: [dispatcherPath],\r\n bundle: true,\r\n format: 'esm',\r\n platform: 'node',\r\n target: 'node20',\r\n banner: CJS_COMPAT_BANNER,\r\n external: NODE_BUILTINS,\r\n write: false,\r\n });\r\n emitVercelFunction('api', result.outputFiles[0].text);\r\n console.log(` built API dispatcher \u2192 api.func (${apiRoutes.length} route(s))`);\r\n } finally {\r\n fs.unlinkSync(dispatcherPath);\r\n }\r\n\r\n // API routes are listed first \u2014 they win on any URL collision with pages.\r\n for (const { srcRegex } of apiRoutes)\r\n vercelRoutes.push({ src: srcRegex, dest: '/api' });\r\n}\r\n\r\n// \u2500\u2500\u2500 Build Pages function \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\nconst serverPages = collectServerPages(PAGES_DIR);\r\n\r\nif (serverPages.length > 0) {\r\n // Pass 1 \u2014 bundle all client components to static files.\r\n const globalRegistry = collectGlobalClientRegistry(serverPages, PAGES_DIR);\r\n const prerenderedHtml = await bundleClientComponents(globalRegistry, PAGES_DIR, STATIC_DIR);\r\n const prerenderedRecord = Object.fromEntries(prerenderedHtml);\r\n\r\n // Pass 2 \u2014 write one temp adapter per page next to its source file (so\r\n // relative imports resolve correctly), then bundle everything in\r\n // one esbuild pass via the dispatcher.\r\n const tempAdapterPaths: string[] = [];\r\n\r\n for (const page of serverPages) {\r\n const adapterDir = path.dirname(page.absPath);\r\n const adapterPath = path.join(adapterDir, `_page_adapter_${randomBytes(4).toString('hex')}.ts`);\r\n\r\n const layoutPaths = findPageLayouts(page.absPath, PAGES_DIR);\r\n const { registry, clientComponentNames } = buildPerPageRegistry(page.absPath, layoutPaths, PAGES_DIR);\r\n\r\n const layoutImports = layoutPaths\r\n .map((lp, i) => {\r\n const rel = path.relative(adapterDir, lp).replace(/\\\\/g, '/');\r\n return `import __layout_${i}__ from ${JSON.stringify(rel.startsWith('.') ? rel : './' + rel)};`;\r\n })\r\n .join('\\n');\r\n\r\n fs.writeFileSync(\r\n adapterPath,\r\n makePageAdapterSource({\r\n pageImport: JSON.stringify('./' + path.basename(page.absPath)),\r\n layoutImports,\r\n clientComponentNames,\r\n allClientIds: [...registry.keys()],\r\n layoutArrayItems: layoutPaths.map((_, i) => `__layout_${i}__`).join(', '),\r\n prerenderedHtml: prerenderedRecord,\r\n catchAllNames: page.catchAllNames,\r\n }),\r\n );\r\n\r\n tempAdapterPaths.push(adapterPath);\r\n console.log(` prepared ${path.relative(PAGES_DIR, page.absPath)} \u2192 ${page.funcPath} [page]`);\r\n }\r\n\r\n const dispatcherRoutes = serverPages.map((page, i) => ({\r\n adapterPath: tempAdapterPaths[i],\r\n srcRegex: page.srcRegex,\r\n paramNames: page.paramNames,\r\n catchAllNames: page.catchAllNames,\r\n }));\r\n\r\n const dispatcherPath = path.join(PAGES_DIR, `_pages_dispatcher_${randomBytes(4).toString('hex')}.ts`);\r\n fs.writeFileSync(dispatcherPath, makePagesDispatcherSource(dispatcherRoutes));\r\n\r\n try {\r\n const result = await build({\r\n entryPoints: [dispatcherPath],\r\n bundle: true,\r\n format: 'esm',\r\n platform: 'node',\r\n target: 'node20',\r\n jsx: 'automatic',\r\n banner: CJS_COMPAT_BANNER,\r\n external: NODE_BUILTINS,\r\n define: { 'process.env.NODE_ENV': '\"production\"' },\r\n write: false,\r\n });\r\n emitVercelFunction('pages', result.outputFiles[0].text);\r\n console.log(` built Pages dispatcher \u2192 pages.func (${serverPages.length} page(s))`);\r\n } finally {\r\n fs.unlinkSync(dispatcherPath);\r\n for (const p of tempAdapterPaths) if (fs.existsSync(p)) fs.unlinkSync(p);\r\n }\r\n\r\n for (const { srcRegex } of serverPages)\r\n vercelRoutes.push({ src: srcRegex, dest: '/pages' });\r\n}\r\n\r\n// \u2500\u2500\u2500 Vercel config \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\nfs.writeFileSync(\r\n path.join(OUTPUT_DIR, 'config.json'),\r\n JSON.stringify({ version: 3, routes: vercelRoutes }, null, 2),\r\n);\r\nfs.writeFileSync(\r\n path.resolve('vercel.json'),\r\n JSON.stringify({ runtime: 'nodejs20.x' }, null, 2),\r\n);\r\n\r\n// \u2500\u2500\u2500 Static assets \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\nawait buildCombinedBundle(STATIC_DIR);\r\ncopyPublicFiles(PUBLIC_DIR, STATIC_DIR);\r\n\r\nconst fnCount = (apiRoutes.length > 0 ? 1 : 0) + (serverPages.length > 0 ? 1 : 0);\r\nconsole.log(`\\n\u2713 Vercel build complete \u2014 ${fnCount} function(s) \u2192 .vercel/output`);\r\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/builder.d.ts
CHANGED
|
@@ -1 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* builder.ts — NukeJS Package Build Script
|
|
3
|
+
*
|
|
4
|
+
* Compiles the NukeJS source into dist/ via a single esbuild pass targeting
|
|
5
|
+
* Node ESM, followed by processDist() which rewrites bare relative imports
|
|
6
|
+
* (e.g. `from './utils'`) to include .js extensions as required by Node's
|
|
7
|
+
* strict ESM resolver.
|
|
8
|
+
*
|
|
9
|
+
* Finally, `tsc --emitDeclarationOnly` generates .d.ts files for consumers.
|
|
10
|
+
*/
|
|
1
11
|
export {};
|
package/dist/builder.js
CHANGED
|
@@ -3,38 +3,48 @@ import fs from "fs";
|
|
|
3
3
|
import { execSync } from "child_process";
|
|
4
4
|
import path from "path";
|
|
5
5
|
import { fileURLToPath } from "url";
|
|
6
|
-
const
|
|
7
|
-
const __dirname = path.dirname(__filename);
|
|
6
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
8
7
|
const srcDir = path.resolve(__dirname, "");
|
|
9
8
|
const outDir = path.resolve(__dirname, "../dist");
|
|
10
|
-
const excludeFolder = "as-is";
|
|
11
9
|
function cleanDist(dir) {
|
|
12
10
|
if (!fs.existsSync(dir)) return;
|
|
13
11
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
14
|
-
console.log(`\u{1F5D1}\uFE0F
|
|
12
|
+
console.log(`\u{1F5D1}\uFE0F Cleared ${dir}`);
|
|
15
13
|
}
|
|
16
|
-
function collectFiles(dir, exclude) {
|
|
17
|
-
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
14
|
+
function collectFiles(dir, exclude = []) {
|
|
18
15
|
const files = [];
|
|
19
|
-
for (const entry of
|
|
20
|
-
const
|
|
16
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
17
|
+
const full = path.join(dir, entry.name);
|
|
21
18
|
if (entry.isDirectory()) {
|
|
22
|
-
if (!exclude.includes(entry.name))
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
} else if (entry.isFile() && fullPath.match(/\.[tj]s$/)) {
|
|
26
|
-
files.push(fullPath);
|
|
19
|
+
if (!exclude.includes(entry.name)) files.push(...collectFiles(full, exclude));
|
|
20
|
+
} else if (/\.[tj]sx?$/.test(entry.name)) {
|
|
21
|
+
files.push(full);
|
|
27
22
|
}
|
|
28
23
|
}
|
|
29
24
|
return files;
|
|
30
25
|
}
|
|
31
|
-
|
|
26
|
+
function processDist(dir) {
|
|
27
|
+
(function walk(currentDir) {
|
|
28
|
+
fs.readdirSync(currentDir, { withFileTypes: true }).forEach((d) => {
|
|
29
|
+
const fullPath = path.join(currentDir, d.name);
|
|
30
|
+
if (d.isDirectory()) {
|
|
31
|
+
walk(fullPath);
|
|
32
|
+
} else if (fullPath.endsWith(".js")) {
|
|
33
|
+
let content = fs.readFileSync(fullPath, "utf-8");
|
|
34
|
+
content = content.replace(/from\s+['"](\.\/.*?)['"]/g, 'from "$1.js"');
|
|
35
|
+
content = content.replace(/import\(['"](\.\/.*?)['"]\)/g, 'import("$1.js")');
|
|
36
|
+
fs.writeFileSync(fullPath, content, "utf-8");
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
})(dir);
|
|
40
|
+
console.log("\u{1F527} Post-processing done: relative imports \u2192 .js extensions.");
|
|
41
|
+
}
|
|
32
42
|
async function runBuild() {
|
|
33
43
|
try {
|
|
34
44
|
cleanDist(outDir);
|
|
35
|
-
console.log("\u{1F680}
|
|
45
|
+
console.log("\u{1F680} Building sources\u2026");
|
|
36
46
|
await build({
|
|
37
|
-
entryPoints,
|
|
47
|
+
entryPoints: collectFiles(srcDir),
|
|
38
48
|
outdir: outDir,
|
|
39
49
|
platform: "node",
|
|
40
50
|
format: "esm",
|
|
@@ -42,56 +52,15 @@ async function runBuild() {
|
|
|
42
52
|
packages: "external",
|
|
43
53
|
sourcemap: true
|
|
44
54
|
});
|
|
45
|
-
console.log("\u2705
|
|
46
|
-
copyFolder(path.join(srcDir, excludeFolder), path.join(outDir, excludeFolder));
|
|
55
|
+
console.log("\u2705 Build done.");
|
|
47
56
|
processDist(outDir);
|
|
48
|
-
console.log("\u{1F4C4}
|
|
49
|
-
execSync("tsc --emitDeclarationOnly --declaration --outDir dist", {
|
|
50
|
-
|
|
51
|
-
});
|
|
52
|
-
console.log("\u{1F389} Build complete. dist folder is ready!");
|
|
57
|
+
console.log("\u{1F4C4} Generating TypeScript declarations\u2026");
|
|
58
|
+
execSync("tsc --emitDeclarationOnly --declaration --outDir dist", { stdio: "inherit" });
|
|
59
|
+
console.log("\n\u{1F389} Build complete \u2192 dist/");
|
|
53
60
|
} catch (err) {
|
|
54
|
-
console.error("\u274C
|
|
61
|
+
console.error("\u274C Build failed:", err);
|
|
55
62
|
process.exit(1);
|
|
56
63
|
}
|
|
57
64
|
}
|
|
58
|
-
function copyFolder(src, dest) {
|
|
59
|
-
if (!fs.existsSync(src)) return;
|
|
60
|
-
if (!fs.existsSync(dest)) fs.mkdirSync(dest, { recursive: true });
|
|
61
|
-
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
62
|
-
for (const entry of entries) {
|
|
63
|
-
const srcPath = path.join(src, entry.name);
|
|
64
|
-
const destPath = path.join(dest, entry.name);
|
|
65
|
-
if (entry.isDirectory()) {
|
|
66
|
-
copyFolder(srcPath, destPath);
|
|
67
|
-
} else {
|
|
68
|
-
fs.copyFileSync(srcPath, destPath);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
console.log(`\u{1F4C1} Copied as-is folder to ${dest}`);
|
|
72
|
-
}
|
|
73
|
-
function processDist(dir) {
|
|
74
|
-
const excludeFolder2 = "as-is";
|
|
75
|
-
(function walk(currentDir) {
|
|
76
|
-
fs.readdirSync(currentDir, { withFileTypes: true }).forEach((d) => {
|
|
77
|
-
const fullPath = path.join(currentDir, d.name);
|
|
78
|
-
if (d.isDirectory()) {
|
|
79
|
-
if (d.name !== excludeFolder2) walk(fullPath);
|
|
80
|
-
} else if (fullPath.endsWith(".js")) {
|
|
81
|
-
let content = fs.readFileSync(fullPath, "utf-8");
|
|
82
|
-
content = content.replace(
|
|
83
|
-
/from\s+['"](\.\/(?!as-is\/).*?)['"]/g,
|
|
84
|
-
'from "$1.js"'
|
|
85
|
-
);
|
|
86
|
-
content = content.replace(
|
|
87
|
-
/import\(['"](\.\/(?!as-is\/).*?)['"]\)/g,
|
|
88
|
-
'import("$1.js")'
|
|
89
|
-
);
|
|
90
|
-
fs.writeFileSync(fullPath, content, "utf-8");
|
|
91
|
-
}
|
|
92
|
-
});
|
|
93
|
-
})(dir);
|
|
94
|
-
console.log("\u{1F527} Post-processing done: .ts imports \u2192 .js (excluding as-is folder).");
|
|
95
|
-
}
|
|
96
65
|
runBuild();
|
|
97
66
|
//# sourceMappingURL=builder.js.map
|
package/dist/builder.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/builder.ts"],
|
|
4
|
-
"sourcesContent": ["
|
|
5
|
-
"mappings": "
|
|
6
|
-
"names": [
|
|
4
|
+
"sourcesContent": ["/**\r\n * builder.ts \u2014 NukeJS Package Build Script\r\n *\r\n * Compiles the NukeJS source into dist/ via a single esbuild pass targeting\r\n * Node ESM, followed by processDist() which rewrites bare relative imports\r\n * (e.g. `from './utils'`) to include .js extensions as required by Node's\r\n * strict ESM resolver.\r\n *\r\n * Finally, `tsc --emitDeclarationOnly` generates .d.ts files for consumers.\r\n */\r\n\r\nimport { build } from 'esbuild';\r\nimport fs from 'fs';\r\nimport { execSync } from 'child_process';\r\nimport path from 'path';\r\nimport { fileURLToPath } from 'url';\r\n\r\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\r\nconst srcDir = path.resolve(__dirname, '');\r\nconst outDir = path.resolve(__dirname, '../dist');\r\n\r\n// \u2500\u2500\u2500 Helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\nfunction cleanDist(dir: string): void {\r\n if (!fs.existsSync(dir)) return;\r\n fs.rmSync(dir, { recursive: true, force: true });\r\n console.log(`\uD83D\uDDD1\uFE0F Cleared ${dir}`);\r\n}\r\n\r\n/** Collects all .ts/.tsx/.js/.jsx files under `dir`, skipping `exclude` dirs. */\r\nfunction collectFiles(dir: string, exclude: string[] = []): string[] {\r\n const files: string[] = [];\r\n for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {\r\n const full = path.join(dir, entry.name);\r\n if (entry.isDirectory()) {\r\n if (!exclude.includes(entry.name)) files.push(...collectFiles(full, exclude));\r\n } else if (/\\.[tj]sx?$/.test(entry.name)) {\r\n files.push(full);\r\n }\r\n }\r\n return files;\r\n}\r\n\r\n// \u2500\u2500\u2500 Post-process .js files \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\n/** Rewrites bare relative imports to include .js extensions for Node ESM. */\r\nfunction processDist(dir: string) {\r\n (function walk(currentDir: string) {\r\n fs.readdirSync(currentDir, { withFileTypes: true }).forEach((d) => {\r\n const fullPath = path.join(currentDir, d.name);\r\n if (d.isDirectory()) {\r\n walk(fullPath);\r\n } else if (fullPath.endsWith('.js')) {\r\n let content = fs.readFileSync(fullPath, 'utf-8');\r\n content = content.replace(/from\\s+['\"](\\.\\/.*?)['\"]/g, 'from \"$1.js\"');\r\n content = content.replace(/import\\(['\"](\\.\\/.*?)['\"]\\)/g, 'import(\"$1.js\")');\r\n fs.writeFileSync(fullPath, content, 'utf-8');\r\n }\r\n });\r\n })(dir);\r\n\r\n console.log('\uD83D\uDD27 Post-processing done: relative imports \u2192 .js extensions.');\r\n}\r\n\r\n// \u2500\u2500\u2500 Build \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n\r\nasync function runBuild(): Promise<void> {\r\n try {\r\n cleanDist(outDir);\r\n\r\n console.log('\uD83D\uDE80 Building sources\u2026');\r\n await build({\r\n entryPoints: collectFiles(srcDir),\r\n outdir: outDir,\r\n platform: 'node',\r\n format: 'esm',\r\n target: ['node20'],\r\n packages: 'external',\r\n sourcemap: true,\r\n });\r\n console.log('\u2705 Build done.');\r\n\r\n processDist(outDir);\r\n\r\n console.log('\uD83D\uDCC4 Generating TypeScript declarations\u2026');\r\n execSync('tsc --emitDeclarationOnly --declaration --outDir dist', { stdio: 'inherit' });\r\n\r\n console.log('\\n\uD83C\uDF89 Build complete \u2192 dist/');\r\n } catch (err) {\r\n console.error('\u274C Build failed:', err);\r\n process.exit(1);\r\n }\r\n}\r\n\r\nrunBuild();"],
|
|
5
|
+
"mappings": "AAWA,SAAS,aAAa;AACtB,OAAO,QAAQ;AACf,SAAS,gBAAgB;AACzB,OAAO,UAAU;AACjB,SAAS,qBAAqB;AAE9B,MAAM,YAAY,KAAK,QAAQ,cAAc,YAAY,GAAG,CAAC;AAC7D,MAAM,SAAS,KAAK,QAAQ,WAAW,EAAE;AACzC,MAAM,SAAS,KAAK,QAAQ,WAAW,SAAS;AAIhD,SAAS,UAAU,KAAmB;AACpC,MAAI,CAAC,GAAG,WAAW,GAAG,EAAG;AACzB,KAAG,OAAO,KAAK,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAC/C,UAAQ,IAAI,4BAAgB,GAAG,EAAE;AACnC;AAGA,SAAS,aAAa,KAAa,UAAoB,CAAC,GAAa;AACnE,QAAM,QAAkB,CAAC;AACzB,aAAW,SAAS,GAAG,YAAY,KAAK,EAAE,eAAe,KAAK,CAAC,GAAG;AAChE,UAAM,OAAO,KAAK,KAAK,KAAK,MAAM,IAAI;AACtC,QAAI,MAAM,YAAY,GAAG;AACvB,UAAI,CAAC,QAAQ,SAAS,MAAM,IAAI,EAAG,OAAM,KAAK,GAAG,aAAa,MAAM,OAAO,CAAC;AAAA,IAC9E,WAAW,aAAa,KAAK,MAAM,IAAI,GAAG;AACxC,YAAM,KAAK,IAAI;AAAA,IACjB;AAAA,EACF;AACA,SAAO;AACT;AAKA,SAAS,YAAY,KAAa;AAChC,GAAC,SAAS,KAAK,YAAoB;AACjC,OAAG,YAAY,YAAY,EAAE,eAAe,KAAK,CAAC,EAAE,QAAQ,CAAC,MAAM;AACjE,YAAM,WAAW,KAAK,KAAK,YAAY,EAAE,IAAI;AAC7C,UAAI,EAAE,YAAY,GAAG;AACnB,aAAK,QAAQ;AAAA,MACf,WAAW,SAAS,SAAS,KAAK,GAAG;AACnC,YAAI,UAAU,GAAG,aAAa,UAAU,OAAO;AAC/C,kBAAU,QAAQ,QAAQ,6BAA6B,cAAc;AACrE,kBAAU,QAAQ,QAAQ,gCAAgC,iBAAiB;AAC3E,WAAG,cAAc,UAAU,SAAS,OAAO;AAAA,MAC7C;AAAA,IACF,CAAC;AAAA,EACH,GAAG,GAAG;AAEN,UAAQ,IAAI,yEAA6D;AAC3E;AAIA,eAAe,WAA0B;AACvC,MAAI;AACF,cAAU,MAAM;AAEhB,YAAQ,IAAI,mCAAuB;AACnC,UAAM,MAAM;AAAA,MACV,aAAa,aAAa,MAAM;AAAA,MAChC,QAAQ;AAAA,MACR,UAAU;AAAA,MACV,QAAQ;AAAA,MACR,QAAQ,CAAC,QAAQ;AAAA,MACjB,UAAU;AAAA,MACV,WAAW;AAAA,IACb,CAAC;AACD,YAAQ,IAAI,qBAAgB;AAE5B,gBAAY,MAAM;AAElB,YAAQ,IAAI,qDAAyC;AACrD,aAAS,yDAAyD,EAAE,OAAO,UAAU,CAAC;AAEtF,YAAQ,IAAI,0CAA8B;AAAA,EAC5C,SAAS,KAAK;AACZ,YAAQ,MAAM,yBAAoB,GAAG;AACrC,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF;AAEA,SAAS;",
|
|
6
|
+
"names": []
|
|
7
7
|
}
|