@kosmojs/dev 0.4.3 → 0.4.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/package.json +4 -3
- package/pkg/chassis.js +308 -1
- package/pkg/chassis.js.map +1 -1
- package/pkg/index.d.ts +2 -1
- package/pkg/index.js +1430 -721
- package/pkg/index.js.map +1 -1
- package/pkg/preview.d.ts +15 -0
- package/pkg/runner.d.ts +22 -0
- package/pkg/templates/run.d.ts +29 -0
- package/pkg/assets/pkg-dTglxmYm.js +0 -523
- package/pkg/assets/pkg-dTglxmYm.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@kosmojs/dev",
|
|
4
|
-
"version": "0.4.
|
|
4
|
+
"version": "0.4.4",
|
|
5
5
|
"cacheVersion": "001",
|
|
6
6
|
"author": "Slee Woo",
|
|
7
7
|
"license": "MIT",
|
|
@@ -25,12 +25,13 @@
|
|
|
25
25
|
"./package.json": "./package.json"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@kosmojs/core": "^0.4.
|
|
29
|
-
"@kosmojs/lib": "^0.4.
|
|
28
|
+
"@kosmojs/core": "^0.4.4",
|
|
29
|
+
"@kosmojs/lib": "^0.4.4",
|
|
30
30
|
"@mdx-js/rollup": "^3.1.1",
|
|
31
31
|
"@sveltejs/vite-plugin-svelte": "^7.3.0",
|
|
32
32
|
"@vitejs/plugin-react": "^6.1.0",
|
|
33
33
|
"@vitejs/plugin-vue": "^6.0.8",
|
|
34
|
+
"chokidar": "^5.0.0",
|
|
34
35
|
"crc": "^4.3.2",
|
|
35
36
|
"path-to-regexp": "^8.4.2",
|
|
36
37
|
"semver": "^7.8.5",
|
package/pkg/chassis.js
CHANGED
|
@@ -1,2 +1,309 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{createRequire as e}from"node:module";import{basename as t,dirname as n,join as r,relative as i,resolve as a,sep as o}from"node:path";import{styleText as s}from"node:util";import{defaults as c}from"@kosmojs/core";import{collectVirtualModules as l,mergeConfigs as u,pathExists as d,pathResolver as f,renderToFile as p,routesFactory as m,spinnerFactory as h,vitePlugins as g}from"@kosmojs/lib";import _ from"crc/crc32";import{build as v,createServer as y,transformWithOxc as b}from"vite";import{mkdir as x,readFile as S,writeFile as C}from"node:fs/promises";import w from"node:http";import T from"node:net";import{readFileSync as E}from"node:fs";import{spawn as D}from"node:child_process";import{watch as O}from"chokidar";var k=JSON.parse(E(e(import.meta.url).resolve(`@kosmojs/dev/package.json`),`utf-8`)),A=(e,t,r)=>{let i=f(t).createPath.libApi(n(e.file),`cache.json`),o=async n=>{if(!n?.hash||!n.typeDeclarations||!n.referencedFiles)return;let i=await j(e.fileFullpath,{...r});if(M(n.hash,i)){for(let[e,r]of Object.entries(n.referencedFiles))if(!M(r,await j(a(t.root,e))))return;return n}};return{async get(e){if(await d(i))try{let t=JSON.parse(await S(i,`utf8`));return e?.validate?o(t):t}catch{}},async set(n){let a=await j(e.fileFullpath,{...r}),o={};for(let e of n.referencedFiles)o[e.replace(`${t.root}/`,``)]=await j(e);let s={...n,hash:a,referencedFiles:o};return await p(i,JSON.stringify(s,void 0,2),{},{render:e=>e}),s}}},j=async(e,t)=>{let n;try{n=await S(e,`utf8`)}catch{return 0}return n?_(JSON.stringify({...t,[k.cacheVersion]:n})):0},M=(e,t)=>e===t,N=`#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Serves every built source folder from a single process.
|
|
5
|
+
*
|
|
6
|
+
* Written by \`kosmo build\` into the dist directory; \`kosmo preview\` restarts it after each rebuild.
|
|
7
|
+
*
|
|
8
|
+
* node dist/run.js -p 4556
|
|
9
|
+
* node dist/run.js -s /tmp/app.sock
|
|
10
|
+
*
|
|
11
|
+
* Folders are discovered at startup from \`dist/<folder>/kosmo.json\`,
|
|
12
|
+
* so a partial build (\`kosmo build admin\`) never leaves a stale folder table behind,
|
|
13
|
+
* and removing a folder's dist directory is enough to stop serving it.
|
|
14
|
+
*
|
|
15
|
+
* Per folder:
|
|
16
|
+
* - SSR folders mount \`ssr/server.js\` - one listener for pages, assets and the bundled API.
|
|
17
|
+
* - CSR folders mount \`api/listener.js\` for the API and serve \`client/\` statically from memory,
|
|
18
|
+
* with the SPA fallback to index.html for unmatched page URLs.
|
|
19
|
+
*
|
|
20
|
+
* Authored as TypeScript, deployed with types stripped;
|
|
21
|
+
* only node builtins are used here: \`node:http\` runs unchanged on Node, Bun and Deno.
|
|
22
|
+
* */
|
|
23
|
+
|
|
24
|
+
import { chmod, readdir, readFile, unlink } from "node:fs/promises";
|
|
25
|
+
import type { IncomingMessage, Server, ServerResponse } from "node:http";
|
|
26
|
+
import { createServer } from "node:http";
|
|
27
|
+
import { extname, join, posix } from "node:path";
|
|
28
|
+
import { pathToFileURL } from "node:url";
|
|
29
|
+
import { parseArgs } from "node:util";
|
|
30
|
+
|
|
31
|
+
import { MIME_TYPES } from "@kosmojs/core";
|
|
32
|
+
|
|
33
|
+
type NodeListener = (req: IncomingMessage, res: ServerResponse) => void;
|
|
34
|
+
|
|
35
|
+
// the shape \`kosmo build\` writes to dist/<folder>/kosmo.json
|
|
36
|
+
type FolderManifest = {
|
|
37
|
+
name: string;
|
|
38
|
+
base: string;
|
|
39
|
+
apiBase: string;
|
|
40
|
+
api: boolean;
|
|
41
|
+
client: boolean;
|
|
42
|
+
ssr: boolean;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
type Folder = FolderManifest & {
|
|
46
|
+
dir: string;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
type Handler = {
|
|
50
|
+
name: string;
|
|
51
|
+
segments: [base: string, api?: string];
|
|
52
|
+
listener: NodeListener;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
type StaticFile = {
|
|
56
|
+
buffer: Buffer;
|
|
57
|
+
headers: Record<string, string>;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const ROOT = import.meta.dirname;
|
|
61
|
+
|
|
62
|
+
const contentTypeFor = (file: string): string => {
|
|
63
|
+
return MIME_TYPES[extname(file).toLowerCase()] || "application/octet-stream";
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const escapeRegex = (text: string): string => {
|
|
67
|
+
return text.replace(/[.*+?^\${}()|[\\]\\\\]/g, "\\\\$&").replace(/\\/+$/, "");
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// matches \`prefix\` itself and anything nested under it, never a sibling sharing the text
|
|
71
|
+
const prefixMatcher = (prefix: string): ((pathname: string) => boolean) => {
|
|
72
|
+
const pattern = new RegExp(\`^\${escapeRegex(prefix)}(?=$|/)\`);
|
|
73
|
+
return (pathname) => pattern.test(pathname);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Sorting mirrors the dev server: more specific prefixes first, API prefixes ahead of page prefixes.
|
|
78
|
+
*
|
|
79
|
+
* /admin/api weight 6
|
|
80
|
+
* /api weight 5
|
|
81
|
+
* /admin weight 1
|
|
82
|
+
* / weight 0
|
|
83
|
+
* */
|
|
84
|
+
const weightOf = ([base, api]: Handler["segments"]): number => {
|
|
85
|
+
const weight = base.split("/").filter(Boolean).length;
|
|
86
|
+
return api ? weight + 5 : weight;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const readFolders = async (): Promise<Array<Folder>> => {
|
|
90
|
+
const folders: Array<Folder> = [];
|
|
91
|
+
|
|
92
|
+
for (const entry of await readdir(ROOT, { withFileTypes: true })) {
|
|
93
|
+
if (!entry.isDirectory()) {
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const dir = join(ROOT, entry.name);
|
|
98
|
+
|
|
99
|
+
const manifest = await readFile(join(dir, "kosmo.json"), "utf8").catch(
|
|
100
|
+
() => undefined,
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
if (manifest) {
|
|
104
|
+
folders.push({ dir, ...(JSON.parse(manifest) as FolderManifest) });
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return folders;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* In-memory static server for a CSR folder's \`client/\` output.
|
|
113
|
+
* Everything is read once at startup; Vite hashes the files under \`assets/\`,
|
|
114
|
+
* so those are served as immutable, the rest as revalidate-always.
|
|
115
|
+
* */
|
|
116
|
+
const createStaticListener = async (
|
|
117
|
+
dir: string,
|
|
118
|
+
base: string,
|
|
119
|
+
): Promise<NodeListener> => {
|
|
120
|
+
const files = new Map<string, StaticFile>();
|
|
121
|
+
|
|
122
|
+
const walk = async (path: string, prefix: string) => {
|
|
123
|
+
for (const entry of await readdir(path, { withFileTypes: true })) {
|
|
124
|
+
// Vite's build manifest is not a public file
|
|
125
|
+
if (entry.name === ".vite") {
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const file = join(path, entry.name);
|
|
130
|
+
const url = posix.join(prefix, entry.name);
|
|
131
|
+
|
|
132
|
+
if (entry.isDirectory()) {
|
|
133
|
+
await walk(file, url);
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const buffer = await readFile(file);
|
|
138
|
+
|
|
139
|
+
files.set(url, {
|
|
140
|
+
buffer,
|
|
141
|
+
headers: {
|
|
142
|
+
"Content-Type": contentTypeFor(entry.name),
|
|
143
|
+
"Content-Length": String(buffer.length),
|
|
144
|
+
"Cache-Control": url.startsWith(posix.join(base, "assets/"))
|
|
145
|
+
? "public, max-age=31536000, immutable"
|
|
146
|
+
: "no-cache",
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
await walk(dir, base);
|
|
153
|
+
|
|
154
|
+
const index = files.get(posix.join(base, "index.html"));
|
|
155
|
+
|
|
156
|
+
return (req, res) => {
|
|
157
|
+
const { pathname } = new URL(req.url ?? "/", "http://localhost");
|
|
158
|
+
|
|
159
|
+
if (!["GET", "HEAD"].includes(req.method ?? "")) {
|
|
160
|
+
res.writeHead(405, { Allow: "GET, HEAD" });
|
|
161
|
+
res.end();
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// a real file wins; anything else is a client route, resolved by the client router
|
|
166
|
+
const file = files.get(pathname) || index;
|
|
167
|
+
|
|
168
|
+
if (!file) {
|
|
169
|
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
170
|
+
res.end("404: Not Found");
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
res.writeHead(200, file.headers);
|
|
175
|
+
res.end(req.method === "HEAD" ? undefined : file.buffer);
|
|
176
|
+
};
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const mountFolders = async (folders: Array<Folder>) => {
|
|
180
|
+
const handlers: Array<Handler> = [];
|
|
181
|
+
|
|
182
|
+
for (const { dir, name, base, apiBase, api, client, ssr } of folders) {
|
|
183
|
+
const apiPrefix = posix.join(base, apiBase);
|
|
184
|
+
|
|
185
|
+
if (ssr) {
|
|
186
|
+
// ssr/server.js bundles the backend; its listener already splits API from pages
|
|
187
|
+
const { createListener } = (await import(
|
|
188
|
+
pathToFileURL(join(dir, "ssr", "server.js")).href
|
|
189
|
+
)) as { createListener: () => Promise<NodeListener> };
|
|
190
|
+
|
|
191
|
+
const listener = await createListener();
|
|
192
|
+
|
|
193
|
+
handlers.push({ name, segments: [base, apiPrefix], listener });
|
|
194
|
+
handlers.push({ name, segments: [base], listener });
|
|
195
|
+
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (api) {
|
|
200
|
+
const { default: listener } = (await import(
|
|
201
|
+
pathToFileURL(join(dir, "api", "listener.js")).href
|
|
202
|
+
)) as { default: NodeListener };
|
|
203
|
+
|
|
204
|
+
handlers.push({ name, segments: [base, apiPrefix], listener });
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (client) {
|
|
208
|
+
const listener = await createStaticListener(join(dir, "client"), base);
|
|
209
|
+
handlers.push({ name, segments: [base], listener });
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return handlers
|
|
214
|
+
.sort((a, b) => weightOf(b.segments) - weightOf(a.segments))
|
|
215
|
+
.map(({ name, segments, listener }) => {
|
|
216
|
+
const [base, api] = segments;
|
|
217
|
+
return {
|
|
218
|
+
name,
|
|
219
|
+
prefix: api || base,
|
|
220
|
+
match: prefixMatcher(api || base),
|
|
221
|
+
listener,
|
|
222
|
+
};
|
|
223
|
+
});
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
export const createListener = async (): Promise<NodeListener> => {
|
|
227
|
+
const folders = await readFolders();
|
|
228
|
+
|
|
229
|
+
if (!folders.length) {
|
|
230
|
+
throw new Error(\`No built source folders found in \${ROOT}\`);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const handlers = await mountFolders(folders);
|
|
234
|
+
|
|
235
|
+
for (const { name, prefix } of handlers) {
|
|
236
|
+
console.log(\` \${prefix.padEnd(24)} -> \${name}\`);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return (req, res) => {
|
|
240
|
+
const { pathname } = new URL(req.url ?? "/", "http://localhost");
|
|
241
|
+
|
|
242
|
+
for (const { match, listener } of handlers) {
|
|
243
|
+
if (match(pathname)) {
|
|
244
|
+
listener(req, res);
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
res.writeHead(404, { "Content-Type": "text/html" });
|
|
250
|
+
res.end("<h1>404: Not Found</h1>");
|
|
251
|
+
};
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
export const startServer = async ({
|
|
255
|
+
port,
|
|
256
|
+
sock,
|
|
257
|
+
}: {
|
|
258
|
+
port?: string | number | undefined;
|
|
259
|
+
sock?: string | undefined;
|
|
260
|
+
}): Promise<Server> => {
|
|
261
|
+
if (![port, sock].some(Boolean)) {
|
|
262
|
+
throw new Error("Please provide either -p/--port or -s/--sock");
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (sock) {
|
|
266
|
+
await unlink(sock).catch((error) => {
|
|
267
|
+
if ((error as NodeJS.ErrnoException).code !== "ENOENT") {
|
|
268
|
+
throw error;
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
console.log(
|
|
274
|
+
\`\\n -> Starting server [ \${sock ? \`sock: \${sock}\` : \`port: \${port}\`} ]\\n\`,
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
const server = createServer(await createListener());
|
|
278
|
+
|
|
279
|
+
server.listen(sock || Number(port), async () => {
|
|
280
|
+
if (sock) {
|
|
281
|
+
// let a reverse proxy running as another user connect
|
|
282
|
+
await chmod(sock, 0o777);
|
|
283
|
+
}
|
|
284
|
+
console.log("\\n -> Server Started\\n");
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
return server;
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
if (pathToFileURL(process.argv[1] || "").href === import.meta.url) {
|
|
291
|
+
const {
|
|
292
|
+
values: { port, sock },
|
|
293
|
+
} = parseArgs({
|
|
294
|
+
options: {
|
|
295
|
+
port: { type: "string", short: "p" },
|
|
296
|
+
sock: { type: "string", short: "s" },
|
|
297
|
+
},
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
try {
|
|
301
|
+
await startServer({ port, sock });
|
|
302
|
+
} catch (error) {
|
|
303
|
+
console.error("Failed starting server");
|
|
304
|
+
console.error(error);
|
|
305
|
+
process.exit(1);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
`,P=e=>{let{generators:t}=e.config,n=e=>t.some(t=>t.meta.slot===e),r=n(`frontend`);return{name:e.name,base:e.config.base,apiBase:e.config.apiBase,api:n(`backend`),client:r,ssr:r&&n(`ssr`)}},F=async e=>{let{createPath:t}=f(e),n=t.distDir();await x(n,{recursive:!0}),await C(a(n,`kosmo.json`),JSON.stringify(P(e),void 0,2),`utf8`)},I=({root:e,distDir:t})=>a(e,t,`run.js`),L,R=async()=>{if(!L){let{code:e}=await b(N,`run.ts`,{sourcemap:!1});L=e}return L},z=async e=>{let t=I(e);return await x(a(t,`..`),{recursive:!0}),await C(t,await R(),`utf8`),t},B=300,V=/(^\.|~$|\.swp$|\.tmp$)/,H=async(e,n)=>{let{root:r,previewPort:a,sourceFolders:c}=e,l=I(e),u=(...e)=>{console.log(s(`dim`,`[preview]`),...e)},d,p=()=>{let e=D(process.execPath,[`--enable-source-maps`,l,`--port`,String(a)],{cwd:r,stdio:`inherit`});e.on(`exit`,(t,n)=>{d===e&&(d=void 0,u(s(`red`,`runner exited (${n??t})`)))}),d=e},m=()=>new Promise(e=>{let t=d;if(!t||t.exitCode!==null){d=void 0,e();return}d=void 0,t.once(`exit`,()=>e()),t.kill(`SIGTERM`)}),h=async()=>{await m(),p()},g=new Set,_=!1,v,y=async()=>{if(!_){_=!0;try{for(;g.size;){let t=[...g];g.clear();let r=!1;for(let e of t){u(`rebuilding ${s(`blue`,e.name)}`);try{await n(e),await F(e)}catch(t){r=!0,console.error(s(`red`,`${e.name}: build failed`)),console.error(t)}}await z(e),r?u(s(`yellow`,`keeping previous build running`)):await h()}}finally{_=!1}}},b=(e,n)=>{process.env.DEBUG&&u(`event ${e.name}: ${n} (building: ${_})`),!V.test(t(n))&&(g.add(e),clearTimeout(v),v=setTimeout(y,B))},x=c.map(e=>{let{createPath:t}=f(e);return{dir:t.src(),sourceFolder:e}}),S=e=>x.find(({dir:t})=>e===t||e.startsWith(t+o))?.sourceFolder,C=O(x.map(({dir:e})=>e),{ignoreInitial:!0});C.on(`all`,(e,t)=>{let n=S(t);n&&b(n,t)}),C.on(`error`,e=>{console.error(s(`red`,`watcher failed`)),console.error(e)});for(let{dir:e}of x)u(`watching ${s(`blue`,i(r,e))}`);p();let w=async()=>{clearTimeout(v),await C.close(),await m()};for(let e of[`SIGINT`,`SIGTERM`])process.once(e,async()=>{await w(),process.exit(0)});return w},U=async e=>{let{devPort:t,command:n}=e;for(let t of e.sourceFolders)for(let e of t.config.generators){if(!e.meta?.name||typeof e.factory!=`function`)throw Error(`${t.name}: Unrecognized generator - must be created via defineGenerator()`);try{await e.factory(t).start?.()}catch(n){throw console.error(s(`red`,`${t.name}: ${e.meta.name} generator failed to initialize`)),n}}if(n===`build`||n===`preview`){for(let t of e.sourceFolders)await G(t),await F(t);return await z(e),n===`build`?async()=>{}:H(e,G)}let i=[],a=[],o={};for(let t of e.sourceFolders)o[t.name]=await K(t);let d=await Y(t);for(let t of e.sourceFolders){let{createPath:e}=f(t),{generators:p}=t.config,m=J(t),h=[g.tsconfigPaths(t),g.nodePrefix(),g.virtualModules(l(t,p),{kind:`csr`,command:n})],_=p.find(e=>e.meta.slot===`frontend`),v=p.find(e=>e.meta.slot===`backend`);if(_){let r=await y(u(t.config,...p.map(({factory:e})=>e(t).config?.({kind:`client`,command:n})),{root:e.src(),cacheDir:W(t,n,`client`),plugins:h,server:{port:d++,middlewareMode:!0,hmr:{port:d++}}}));for(let[e,n]of Object.entries(o[t.name]))r.watcher.on(e,n);i.push([[t.config.base],()=>m.base,()=>r.middlewares]),a.push(r.close)}if(v){let l=await y(u(v.options,v.factory(t).config?.({kind:`backend`,command:n}),{root:e.src(),appType:`custom`,cacheDir:W(t,n,`backend`),plugins:h,server:{port:d++,middlewareMode:!0,hmr:{port:d++}},resolve:{conditions:[`node`]},environments:{api:{resolve:{conditions:[`node`]}}}})),f=l.environments.api,p=async()=>(f.runner.clearCache(),f.runner.import(r(c.apiDir,`dev.ts`)).then(e=>e.default)),g=await p();for(let[e,n]of Object.entries(o[t.name]))l.watcher.on(e,async e=>{if(f.moduleGraph.getModulesByFile(e)?.size)try{await n(e),await g?.teardownHandler?.(),g=await p()}catch(e){console.error(s(`red`,`${t.name}: api reload failed - keeping previous api program`)),console.error(e)}});i.push([[t.config.base,t.config.apiBase],()=>g.requestMatcher||m.api,()=>g.requestHandler()]),a.push(l.close)}}let p=([e])=>{let[t,n]=e,r=t.split(`/`).filter(Boolean).length;return n?r+5:r},m=i.sort((e,t)=>p(t)-p(e)),h=w.createServer((e,t)=>{for(let[,n,r]of m){let[i,a]=[n(),r()];if(i(e)){a(e,t);return}}t.writeHead(404,{"Content-Type":`text/html`}),t.end(`<h1>404: Not Found</h1>`)});return h.on(`error`,e=>{console.error(s(`red`,`Failed to start dev server on port ${t}`)),console.error(e.message),process.exit(1)}),h.listen(t),a.push(async()=>{h.close()}),async()=>{for(let e of a)await e();await new Promise(e=>setTimeout(e,100))}},W=({root:e,name:t},n,r)=>a(e,`var/.vite/${t}/${n}/${r}`),G=async e=>{let t=`build`,{createPath:n}=f(e),{generators:r}=e.config,i=[];{let{resolvers:t}=await m(e,A),n=h(`${e.name}: resolving routes`);for(let{name:e,handler:r}of t.values())n.append(`[ ${i.length+1} of ${t.size} ] ${e}`),i.push(await r());n.succeed(`ready ✨`)}let a=[g.tsconfigPaths(e),g.nodePrefix(),g.virtualModules(l(e,r),{kind:`csr`,command:t})];for(let t of r)await t.factory(e).build?.(i);let o=r.find(e=>e.meta.slot===`frontend`),s=r.find(e=>e.meta.slot===`backend`);if(o&&await v(u(e.config,...r.map(({factory:n})=>n(e).config?.({kind:`client`,command:t})),{root:n.src(),cacheDir:W(e,t,`client`),plugins:a,build:{outDir:n.distDir(`client`),manifest:!0,emptyOutDir:!0}})),s){let r=n.distDir(`api`);await v(u(s.options,s.factory(e).config?.({kind:`backend`,command:t}),{base:`./`,root:n.src(),appType:`custom`,plugins:a,resolve:{conditions:[`node`]},build:{ssr:!0,target:`esnext`,sourcemap:!0,emptyOutDir:!0,rolldownOptions:{input:[n.api(`app.ts`),n.api(`server.ts`),n.lib(`@api/listener.ts`)],output:{dir:r,format:`esm`}}},cacheDir:W(e,t,`backend`)}))}for(let t of r)await t.factory(e).postBuild?.(i)},K=async e=>{let{resolvers:t,resolversFactory:n}=await m(e,A),{createPath:r}=f(e),i=[];for(let t of e.config.generators){let n=t.factory(e);i.push({name:t.meta.name,factory:n})}let a=new Map,o=async t=>{let n=Array.from(a.values());for(let{name:r,factory:a}of i)try{await a.watch?.(n,t)}catch(n){console.error(s(`red`,`${e.name}: ${r} generator failed`)),t&&console.error(t),console.error(n)}},c=async n=>{let i=t.get(n);if(i)try{let e=await i.handler(n);return a.set(e.entry.fileFullpath,e),e}catch(t){let i=n.replace(`${r.api()}/`,``);console.error(s(`red`,`${e.name}: ${i} route resolution failed`)),console.error(t);return}};{let n=h(`${e.name}: resolving routes`);for(let{name:e,handler:r}of t.values()){n.append(`[ ${a.size+1} of ${t.size} ] ${e}`);let i=await r();a.set(i.entry.fileFullpath,i)}n.succeed(`ready ✨`)}return await o(),{async add(e){let[r]=n([e]).values();r&&(t.set(e,r),await c(e)&&await o({kind:`create`,file:e}))},async change(e){if(a.has(e))await c(e);else{let t=a.values().flatMap(({kind:t,entry:n})=>t===`apiRoute`&&n.referencedFiles.includes(e)?[n]:[]);for(let e of t)await c(e.fileFullpath)}await o({kind:`update`,file:e})},async unlink(e){t.delete(e),a.delete(e),await o({kind:`delete`,file:e})}}},q=e=>e.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`).replace(/\/+$/,``),J=({config:e})=>{let t=RegExp(`^${q(e.base)}(?=$|/)`),n=RegExp(`^${q(r(e.base,e.apiBase))}(?=$|/)`);return{base(e){return!n.test(e.url)&&t.test(e.url)},api(e){return n.test(e.url)}}},Y=async e=>{let t=0,n=0;for(let r of[3,2,1,``])if(t=Number(`${r}${e}`)+100,n=t+100,n<65e3)break;if(n>=65e3)throw Error(`the devPort in package.json should be less than 64000`);let r=n-t+1,i=Math.floor(Math.random()*r),a=await Array.from({length:r},(e,n)=>t+(i+n)%r).reduce(async(e,t)=>await e||(await X(t)?t:void 0),Promise.resolve(void 0));if(!a)throw Error(`No free ports found in range ${t}-${n}`);return a},X=e=>new Promise(t=>{let n=T.createServer();n.once(`error`,()=>t(!1)),n.once(`listening`,()=>{n.close(),t(!0)}),n.listen(e,`127.0.0.1`)});export{U as default};
|
|
2
309
|
//# sourceMappingURL=chassis.js.map
|
package/pkg/chassis.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chassis.js","names":[],"sources":["../src/cache.ts","../src/chassis.ts"],"sourcesContent":["import { readFileSync } from \"node:fs\";\nimport { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { createRequire } from \"node:module\";\nimport { dirname, resolve } from \"node:path\";\n\nimport crc from \"crc/crc32\";\n\nimport type {\n RouteResolverCache,\n RouteResolverCacheFactory,\n} from \"@kosmojs/core\";\nimport { pathExists, pathResolver } from \"@kosmojs/lib\";\n\n/**\n * Read the installed package.json at runtime to get the actual version.\n * A static `import ... with { type: \"json\" }` would be inlined by the\n * bundler with the pre-bump version, defeating the point.\n * */\nconst self = JSON.parse(\n readFileSync(\n createRequire(import.meta.url).resolve(\"@kosmojs/dev/package.json\"),\n \"utf-8\",\n ),\n);\n\nexport const cacheFactory: RouteResolverCacheFactory = (\n route,\n sourceFolder,\n extraContext,\n) => {\n const cacheFile = pathResolver(sourceFolder).createPath.libApi(\n dirname(route.file),\n \"cache.json\",\n );\n\n const validateCache = async (cache: RouteResolverCache) => {\n if (!cache?.hash) {\n return;\n }\n\n if (!cache.typeDeclarations || !cache.referencedFiles) {\n // incomplete cache\n return;\n }\n\n const hash = await generateFileHash(route.fileFullpath, {\n ...extraContext,\n });\n\n if (!identicalHashSum(cache.hash, hash)) {\n // route itself updated\n return;\n }\n\n for (const [file, hash] of Object.entries(cache.referencedFiles)) {\n if (\n !identicalHashSum(\n hash,\n await generateFileHash(resolve(sourceFolder.root, file)),\n )\n ) {\n // some referenced file updated\n return;\n }\n }\n\n return cache;\n };\n\n return {\n async get(opt?: { validate?: boolean }) {\n if (await pathExists(cacheFile)) {\n try {\n const cache = JSON.parse(await readFile(cacheFile, \"utf8\"));\n return opt?.validate //\n ? validateCache(cache)\n : cache;\n } catch (_e) {}\n }\n return undefined;\n },\n\n async set(cache) {\n const hash = await generateFileHash(route.fileFullpath, {\n ...extraContext,\n });\n\n const referencedFiles: Record<string, number> = {};\n\n for (const file of cache.referencedFiles) {\n referencedFiles[\n // Strip project root to ensure cached paths are relative\n // and portable across environments (CI, local, etc.)\n file.replace(`${sourceFolder.root}/`, \"\")\n ] = await generateFileHash(file);\n }\n\n const value = { ...cache, hash, referencedFiles };\n\n await mkdir(dirname(cacheFile), { recursive: true });\n await writeFile(cacheFile, JSON.stringify(value, null, 2), \"utf8\");\n\n return value;\n },\n };\n};\n\nconst generateFileHash = async (\n file: string,\n extraContext?: object,\n): Promise<number> => {\n let fileContent: string | undefined;\n try {\n fileContent = await readFile(file, \"utf8\");\n } catch (_e) {\n // file could be deleted since last build\n return 0;\n }\n return fileContent\n ? crc(\n JSON.stringify({\n ...extraContext,\n [self.cacheVersion]: fileContent,\n }),\n )\n : 0;\n};\n\n// return true if sums are identical\nconst identicalHashSum = (a: number, b: number) => {\n return a === b;\n};\n","import http, { type IncomingMessage, type ServerResponse } from \"node:http\";\nimport net from \"node:net\";\nimport { join, resolve } from \"node:path\";\nimport { styleText } from \"node:util\";\n\nimport { build, createServer, type RunnableDevEnvironment } from \"vite\";\n\nimport {\n defaults,\n type GeneratorFactory,\n type GeneratorMeta,\n type GeneratorSignature,\n type ProjectSettings,\n type ResolvedEntry,\n type SourceFolder,\n type WatcherEvent,\n} from \"@kosmojs/core\";\nimport type { DevSetup } from \"@kosmojs/core/api\";\nimport {\n mergeConfigs,\n pathResolver,\n routesFactory,\n spinnerFactory,\n vitePlugins,\n} from \"@kosmojs/lib\";\n\nimport coreGenerator from \"@kosmojs/core-generator\";\n\nimport { cacheFactory } from \"./cache\";\n\nexport default async (\n projectSettings: ProjectSettings,\n): Promise<() => Promise<void>> => {\n const { devPort, command } = projectSettings;\n\n // NOTE: initialize generators before anything else, regardless command\n for (const sourceFolder of projectSettings.sourceFolders) {\n for (const generator of folderGenerators(sourceFolder)) {\n if (!generator.meta?.name || typeof generator.factory !== \"function\") {\n throw new Error(\n `${sourceFolder.name}: Unrecognized generator - must be created via defineGenerator()`,\n );\n }\n\n try {\n await generator.factory(sourceFolder).start?.();\n } catch (error) {\n console.error(\n styleText(\n \"red\",\n `${sourceFolder.name}: ${generator.meta.name} generator failed to initialize`,\n ),\n );\n throw error;\n }\n }\n }\n\n if (command === \"build\") {\n for (const sourceFolder of projectSettings.sourceFolders) {\n const { createPath } = pathResolver(sourceFolder);\n\n const resolvedRoutes = [];\n\n {\n const { resolvers } = await routesFactory(sourceFolder, cacheFactory);\n\n const spinner = spinnerFactory(\n `${sourceFolder.name}: resolving routes`,\n );\n\n for (const { name, handler } of resolvers.values()) {\n spinner.append(\n `[ ${resolvedRoutes.length + 1} of ${resolvers.size} ] ${name}`,\n );\n resolvedRoutes.push(await handler());\n }\n\n spinner.succeed(\"ready ✨\");\n }\n\n const plugins = [\n vitePlugins.tsconfigPaths(sourceFolder),\n vitePlugins.nodePrefix(),\n ];\n\n const generators = folderGenerators(sourceFolder);\n\n for (const generator of generators) {\n await generator.factory(sourceFolder).build?.(resolvedRoutes);\n }\n\n const frontendGenerator = generators.find(\n (e) => e.meta.slot === \"frontend\",\n );\n\n const backendGenerator = generators.find(\n (e) => e.meta.slot === \"backend\",\n );\n\n // INFO: === build client ===\n if (frontendGenerator) {\n await build(\n mergeConfigs(\n // user-provided config - lowest priority\n sourceFolder.config,\n // generators configs - higher priority\n ...generators.map(({ factory }) => {\n return factory(sourceFolder).config?.({\n kind: \"client\",\n command,\n });\n }),\n // main config - highest priority\n {\n // base provided by sourceFolder.config\n root: createPath.src(),\n cacheDir: cacheDir(sourceFolder, command, \"client\"),\n plugins,\n build: {\n outDir: createPath.distDir(\"client\"),\n manifest: true,\n emptyOutDir: true,\n },\n },\n ),\n );\n }\n\n // INFO: === build backend ===\n if (backendGenerator) {\n const dir = createPath.distDir(\"api\");\n\n // NOTE: sourceFolder.config is client-specific config - not using for backend!\n // To provide backend-specific config pass it as api generator options.\n await build(\n mergeConfigs(\n // user-provided config - lowest priority\n backendGenerator.options,\n // generator config - higher priority\n backendGenerator\n .factory(sourceFolder)\n .config?.({ kind: \"backend\", command }),\n // main config - highest priority\n {\n base: \"./\",\n root: createPath.src(),\n appType: \"custom\",\n plugins,\n resolve: {\n conditions: [\"node\"],\n },\n build: {\n ssr: true,\n target: \"esnext\",\n sourcemap: true,\n emptyOutDir: true,\n rolldownOptions: {\n input: [\n createPath.api(\"app.ts\"),\n createPath.api(\"server.ts\"),\n ],\n output: {\n dir,\n format: \"esm\",\n },\n },\n },\n cacheDir: cacheDir(sourceFolder, command, \"backend\"),\n },\n ),\n );\n }\n\n for (const generator of generators) {\n await generator.factory(sourceFolder).postBuild?.(resolvedRoutes);\n }\n }\n\n return async () => {};\n }\n\n type RequestMatcher = (req: IncomingMessage) => boolean;\n type RequestHandler = (req: IncomingMessage, res: ServerResponse) => void;\n\n const requestHandlers: Array<\n [\n segments: [base: string, api?: string],\n matcherFactory: () => RequestMatcher,\n handlerFactory: () => RequestHandler,\n ]\n > = [];\n\n const teardownHandlers: Array<() => Promise<unknown>> = [];\n\n const eventMap: Record<string, Awaited<ReturnType<typeof eventFactory>>> = {};\n\n // WARN: call this before starting any server!\n for (const sourceFolder of projectSettings.sourceFolders) {\n eventMap[sourceFolder.name] = await eventFactory(sourceFolder);\n }\n\n let port = await findFreePort(devPort);\n\n for (const sourceFolder of projectSettings.sourceFolders) {\n const { createPath } = pathResolver(sourceFolder);\n\n const requestMatchers = matchersFactory(sourceFolder);\n\n const generators = folderGenerators(sourceFolder);\n\n const plugins = [\n vitePlugins.tsconfigPaths(sourceFolder),\n vitePlugins.nodePrefix(),\n ];\n\n const frontendGenerator = generators.find(\n (e) => e.meta.slot === \"frontend\",\n );\n\n const backendGenerator = generators.find((e) => e.meta.slot === \"backend\");\n\n // INFO: === start client server ===\n if (frontendGenerator) {\n const viteServer = await createServer(\n mergeConfigs(\n // user-provided config - lowest priority\n sourceFolder.config,\n // generators configs - higher priority\n ...generators.map(({ factory }) => {\n return factory(sourceFolder).config?.({ kind: \"client\", command });\n }),\n // main config - highest priority\n {\n // base provided by sourceFolder.config\n root: createPath.src(),\n cacheDir: cacheDir(sourceFolder, command, \"client\"),\n plugins,\n server: {\n port: port++,\n middlewareMode: true,\n hmr: { port: port++ },\n },\n },\n ),\n );\n\n for (const [evt, handler] of Object.entries(\n eventMap[sourceFolder.name],\n )) {\n viteServer.watcher.on(evt, handler);\n }\n\n requestHandlers.push([\n [sourceFolder.config.base],\n () => requestMatchers.base,\n () => viteServer.middlewares,\n ]);\n\n teardownHandlers.push(viteServer.close);\n }\n\n // INFO: === start backend server ===\n if (backendGenerator) {\n // NOTE: sourceFolder.config is client-specific config - not using for backend!\n // To provide backend-specific config pass it as api generator options.\n const viteServer = await createServer(\n mergeConfigs(\n // user-provided config - lowest priority\n backendGenerator.options,\n // generator config - higher priority\n backendGenerator\n .factory(sourceFolder)\n .config?.({ kind: \"backend\", command }),\n // main config - highest priority\n {\n root: createPath.src(),\n appType: \"custom\",\n cacheDir: cacheDir(sourceFolder, command, \"backend\"),\n plugins,\n server: {\n port: port++,\n middlewareMode: true,\n hmr: { port: port++ },\n },\n resolve: {\n conditions: [\"node\"],\n },\n environments: {\n api: {\n resolve: {\n conditions: [\"node\"],\n },\n },\n },\n },\n ),\n );\n\n const env = viteServer.environments.api as RunnableDevEnvironment;\n\n const loadDevSetup = async () => {\n env.runner.clearCache();\n return env.runner\n .import<{ default: DevSetup }>(join(defaults.apiDir, \"dev.ts\"))\n .then((e) => e.default);\n };\n\n let devSetup = await loadDevSetup();\n\n for (const [evt, handler] of Object.entries(\n eventMap[sourceFolder.name],\n )) {\n viteServer.watcher.on(evt, async (file) => {\n const mods = env.moduleGraph.getModulesByFile(file);\n if (mods?.size) {\n try {\n await handler(file);\n await devSetup?.teardownHandler?.();\n devSetup = await loadDevSetup();\n } catch (error) {\n /**\n * A transiently-invalid module (half-saved file, atomic rewrite mid-flight, syntax error)\n * must not take the dev server down: an uncaught rejection here is fatal to the whole process.\n * Keep serving the previous api program; the next successful change reloads it.\n * */\n console.error(\n styleText(\n \"red\",\n `${sourceFolder.name}: api reload failed - keeping previous api program`,\n ),\n );\n console.error(error);\n }\n }\n });\n }\n\n requestHandlers.push([\n [sourceFolder.config.base, sourceFolder.config.apiBase],\n () => devSetup.requestMatcher || requestMatchers.api,\n () => devSetup.requestHandler(),\n ]);\n\n teardownHandlers.push(viteServer.close);\n }\n }\n\n /**\n * Sorting is essential to ensure more specific paths are matched before broader ones.\n *\n * Given source folders:\n * - main app: baseurl=/ apiurl=/api\n * - admin app: baseurl=/admin apiurl=/admin/api\n *\n * Correct sort order:\n * 1. /admin/api — most specific\n * 2. /api\n * 3. /admin\n * 4. /\n * */\n const requestHandlerWeight = ([\n segments,\n ]: (typeof requestHandlers)[number]) => {\n const [base, api] = segments;\n /**\n * set weight to number of non-empty segments:\n * \"/\" weight is 0\n * \"/admin\" weight is 1\n * */\n const weight = base.split(\"/\").filter(Boolean).length;\n return api ? weight + 5 : weight;\n };\n\n const handlers = requestHandlers.sort(\n (a, b) => requestHandlerWeight(b) - requestHandlerWeight(a),\n );\n\n const httpServer = http.createServer((req, res) => {\n for (const [, matcherFactory, handlerFactory] of handlers) {\n // should be called on every request\n const [matcher, handler] = [matcherFactory(), handlerFactory()];\n if (matcher(req)) {\n handler(req, res);\n return;\n }\n }\n res.writeHead(404, { \"Content-Type\": \"text/html\" });\n res.end(\"<h1>404: Not Found</h1>\");\n });\n\n httpServer.on(\"error\", (error) => {\n console.error(\n styleText(\"red\", `Failed to start dev server on port ${devPort}`),\n );\n console.error(error.message);\n process.exit(1);\n });\n\n httpServer.listen(devPort);\n\n teardownHandlers.push(async () => {\n httpServer.close();\n });\n\n return async () => {\n for (const handler of teardownHandlers) {\n await handler();\n }\n // Let chokidar fully release file handles\n await new Promise((resolve) => setTimeout(resolve, 100));\n };\n};\n\nconst cacheDir = (\n { root, name }: SourceFolder,\n command: ProjectSettings[\"command\"],\n mode: \"client\" | \"backend\",\n) => {\n return resolve(root, `var/.vite/${name}/${command}/${mode}`);\n};\n\nconst folderGenerators = (\n sourceFolder: SourceFolder,\n): Array<GeneratorSignature> => {\n const generators: Array<GeneratorSignature> = [];\n\n const generatorsBySlot: Partial<\n Record<NonNullable<GeneratorMeta[\"slot\"]>, GeneratorSignature>\n > = {};\n\n for (const generator of sourceFolder.config.generators || []) {\n if (generator.meta.slot) {\n generatorsBySlot[generator.meta.slot] = generator;\n } else {\n generators.push(generator);\n }\n }\n\n return [\n // core generator should run first\n coreGenerator(),\n // then backend generator\n ...(generatorsBySlot.backend ? [generatorsBySlot.backend] : []),\n // then fetch generator, only if backend generator also enabled\n ...(generatorsBySlot.fetch && generatorsBySlot.backend\n ? [generatorsBySlot.fetch]\n : []),\n // then frontend generator\n ...(generatorsBySlot.frontend ? [generatorsBySlot.frontend] : []),\n // then slotless generators in the order they were added\n ...generators,\n // ssr generator should run after user generators\n ...(generatorsBySlot.ssr ? [generatorsBySlot.ssr] : []),\n // ssg generator should run after ssr generator\n ...(generatorsBySlot.ssg ? [generatorsBySlot.ssg] : []),\n ];\n};\n\nconst eventFactory = async (\n sourceFolder: SourceFolder,\n): Promise<\n Record<\"add\" | \"change\" | \"unlink\", (f: string) => Promise<void>>\n> => {\n const { resolvers, resolversFactory } = await routesFactory(\n sourceFolder,\n cacheFactory,\n );\n\n const { createPath } = pathResolver(sourceFolder);\n\n const generators: Array<{\n name: string | undefined;\n factory: GeneratorFactory;\n }> = [];\n\n for (const generator of folderGenerators(sourceFolder)) {\n const factory = generator.factory(sourceFolder);\n generators.push({ name: generator.meta.name, factory });\n }\n\n const resolvedRoutes = new Map<\n string, // fileFullpath\n ResolvedEntry\n >();\n\n const runGenerators = async (event?: WatcherEvent) => {\n /**\n * Watch handlers receive the full list of entries\n * and should process only those whose source file or dependencies were updated.\n * */\n const entries = Array.from(resolvedRoutes.values());\n\n for (const { name, factory } of generators) {\n try {\n await factory.watch?.(entries, event);\n } catch (error) {\n console.error(\n styleText(\"red\", `${sourceFolder.name}: ${name} generator failed`),\n );\n if (event) {\n console.error(event);\n }\n console.error(error);\n }\n }\n };\n\n const updateResolvedEntry = async (file: string) => {\n const resolver = resolvers.get(file);\n\n if (!resolver) {\n return;\n }\n\n try {\n const resolvedEntry = await resolver.handler(file);\n resolvedRoutes.set(resolvedEntry.entry.fileFullpath, resolvedEntry);\n return resolvedEntry;\n } catch (error) {\n const route = file.replace(`${createPath.api()}/`, \"\");\n console.error(\n styleText(\n \"red\",\n `${sourceFolder.name}: ${route} route resolution failed`,\n ),\n );\n console.error(error);\n return;\n }\n };\n\n {\n const spinner = spinnerFactory(`${sourceFolder.name}: resolving routes`);\n for (const { name, handler } of resolvers.values()) {\n spinner.append(\n `[ ${resolvedRoutes.size + 1} of ${resolvers.size} ] ${name}`,\n );\n const route = await handler();\n resolvedRoutes.set(route.entry.fileFullpath, route);\n }\n spinner.succeed(\"ready ✨\");\n }\n\n // NOTE: call only after routes resolved\n await runGenerators();\n\n return {\n async add(file: string) {\n const [resolver] = resolversFactory([file]).values();\n\n if (!resolver) {\n return;\n }\n\n resolvers.set(file, resolver);\n\n // call only after `resolvers.set(file)`\n const resolvedEntry = await updateResolvedEntry(file);\n\n if (resolvedEntry) {\n await runGenerators({ kind: \"create\", file });\n }\n },\n\n async change(file: string) {\n if (resolvedRoutes.has(file)) {\n // route updated\n await updateResolvedEntry(file);\n } else {\n // updating entries that are referencing updated file\n const relatedRoutes = resolvedRoutes\n .values()\n .flatMap(({ kind, entry }) => {\n return kind === \"apiRoute\"\n ? entry.referencedFiles.includes(file)\n ? [entry]\n : []\n : [];\n });\n\n for (const route of relatedRoutes) {\n await updateResolvedEntry(route.fileFullpath);\n }\n }\n\n await runGenerators({ kind: \"update\", file });\n },\n\n async unlink(file) {\n // route deleted\n resolvers.delete(file);\n resolvedRoutes.delete(file);\n await runGenerators({ kind: \"delete\", file });\n },\n };\n};\n\nconst normalizeRegex = (s: string) => {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\").replace(/\\/+$/, \"\");\n};\n\nconst matchersFactory: (\n sourceFolder: SourceFolder,\n) => Record<\"base\" | \"api\", (req: IncomingMessage) => boolean> = ({\n config,\n}) => {\n const basePattern = new RegExp(`^${normalizeRegex(config.base)}(?=$|/)`);\n const apiPattern = new RegExp(\n `^${normalizeRegex(join(config.base, config.apiBase))}(?=$|/)`,\n );\n return {\n base(req) {\n return apiPattern.test(req.url as string)\n ? false\n : basePattern.test(req.url as string);\n },\n api(req) {\n return apiPattern.test(req.url as string);\n },\n };\n};\n\nconst findFreePort = async (devPort: number): Promise<number> => {\n let minPort = 0;\n let maxPort = 0;\n\n for (const n of [3, 2, 1, \"\"]) {\n minPort = Number(`${n}${devPort}`) + 100;\n maxPort = minPort + 100;\n if (maxPort < 65000) {\n break;\n }\n }\n\n if (maxPort >= 65000) {\n throw new Error(\"the devPort in package.json should be less than 64000\");\n }\n\n const range = maxPort - minPort + 1;\n const startOffset = Math.floor(Math.random() * range);\n\n const ports = Array.from({ length: range }, (_, i) => {\n return minPort + ((startOffset + i) % range);\n });\n\n const freePort = await ports.reduce(\n async (prevPromise, port) => {\n const freePort = await prevPromise;\n if (freePort) {\n return freePort;\n }\n const isFree = await isPortFree(port);\n return isFree ? port : undefined;\n },\n Promise.resolve(undefined as number | undefined),\n );\n\n if (!freePort) {\n throw new Error(`No free ports found in range ${minPort}-${maxPort}`);\n }\n\n return freePort;\n};\n\nconst isPortFree = (port: number): Promise<boolean> => {\n return new Promise((resolve) => {\n const server = net.createServer();\n\n server.once(\"error\", () => resolve(false));\n\n server.once(\"listening\", () => {\n server.close();\n resolve(true);\n });\n\n server.listen(port, \"127.0.0.1\");\n });\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"chassis.js","names":[],"sources":["../src/cache.ts","../src/runner.ts","../src/preview.ts","../src/chassis.ts"],"sourcesContent":["import { readFileSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { createRequire } from \"node:module\";\nimport { dirname, resolve } from \"node:path\";\n\nimport crc from \"crc/crc32\";\n\nimport type {\n RouteResolverCache,\n RouteResolverCacheFactory,\n} from \"@kosmojs/core\";\nimport { pathExists, pathResolver, renderToFile } from \"@kosmojs/lib\";\n\n/**\n * Read the installed package.json at runtime to get the actual version.\n * A static `import ... with { type: \"json\" }` would be inlined by the\n * bundler with the pre-bump version, defeating the point.\n * */\nconst self = JSON.parse(\n readFileSync(\n createRequire(import.meta.url).resolve(\"@kosmojs/dev/package.json\"),\n \"utf-8\",\n ),\n);\n\nexport const cacheFactory: RouteResolverCacheFactory = (\n route,\n sourceFolder,\n extraContext,\n) => {\n const cacheFile = pathResolver(sourceFolder).createPath.libApi(\n dirname(route.file),\n \"cache.json\",\n );\n\n const validateCache = async (cache: RouteResolverCache) => {\n if (!cache?.hash) {\n return;\n }\n\n if (!cache.typeDeclarations || !cache.referencedFiles) {\n // incomplete cache\n return;\n }\n\n const hash = await generateFileHash(route.fileFullpath, {\n ...extraContext,\n });\n\n if (!identicalHashSum(cache.hash, hash)) {\n // route itself updated\n return;\n }\n\n for (const [file, hash] of Object.entries(cache.referencedFiles)) {\n if (\n !identicalHashSum(\n hash,\n await generateFileHash(resolve(sourceFolder.root, file)),\n )\n ) {\n // some referenced file updated\n return;\n }\n }\n\n return cache;\n };\n\n return {\n async get(opt?: { validate?: boolean }) {\n if (await pathExists(cacheFile)) {\n try {\n const cache = JSON.parse(await readFile(cacheFile, \"utf8\"));\n return opt?.validate //\n ? validateCache(cache)\n : cache;\n } catch (_e) {}\n }\n return undefined;\n },\n\n async set(cache) {\n const hash = await generateFileHash(route.fileFullpath, {\n ...extraContext,\n });\n\n const referencedFiles: Record<string, number> = {};\n\n for (const file of cache.referencedFiles) {\n referencedFiles[\n // Strip project root to ensure cached paths are relative\n // and portable across environments (CI, local, etc.)\n file.replace(`${sourceFolder.root}/`, \"\")\n ] = await generateFileHash(file);\n }\n\n const value = { ...cache, hash, referencedFiles };\n\n /**\n * use renderToFile to skip the write when nothing changed.\n * Two kosmo processes on one project resolve the same routes to the same bytes,\n * so this keeps them from rewriting each other's cache files.\n * */\n await renderToFile(\n cacheFile,\n JSON.stringify(value, undefined, 2),\n {},\n { render: (content) => content },\n );\n\n return value;\n },\n };\n};\n\nconst generateFileHash = async (\n file: string,\n extraContext?: object,\n): Promise<number> => {\n let fileContent: string | undefined;\n try {\n fileContent = await readFile(file, \"utf8\");\n } catch (_e) {\n // file could be deleted since last build\n return 0;\n }\n return fileContent\n ? crc(\n JSON.stringify({\n ...extraContext,\n [self.cacheVersion]: fileContent,\n }),\n )\n : 0;\n};\n\n// return true if sums are identical\nconst identicalHashSum = (a: number, b: number) => {\n return a === b;\n};\n","import { mkdir, writeFile } from \"node:fs/promises\";\nimport { resolve } from \"node:path\";\n\nimport { transformWithOxc } from \"vite\";\n\nimport type { ProjectSettings, SourceFolder } from \"@kosmojs/core\";\nimport { pathResolver } from \"@kosmojs/lib\";\n\nimport runTemplate from \"#templates/run\";\n\n/**\n * What `dist/run.js` needs to know about a built folder.\n * Written next to the folder's build output so the runner discovers folders from disk\n * rather than from a table that a partial build could leave stale.\n * */\nexport type FolderManifest = {\n name: string;\n base: string;\n apiBase: string;\n // dist/<folder>/api/listener.js exists\n api: boolean;\n // dist/<folder>/client/ exists\n client: boolean;\n // dist/<folder>/ssr/server.js exists - it bundles the api, run.js mounts it alone\n ssr: boolean;\n};\n\nexport const folderManifestFactory = (\n sourceFolder: SourceFolder,\n): FolderManifest => {\n const { generators } = sourceFolder.config;\n\n const hasSlot = (slot: string) => {\n return generators.some((e) => e.meta.slot === slot);\n };\n\n const client = hasSlot(\"frontend\");\n\n return {\n name: sourceFolder.name,\n base: sourceFolder.config.base,\n apiBase: sourceFolder.config.apiBase,\n api: hasSlot(\"backend\"),\n client,\n // the ssr generator skips its build when there is no frontend\n ssr: client && hasSlot(\"ssr\"),\n };\n};\n\nexport const writeFolderManifest = async (sourceFolder: SourceFolder) => {\n const { createPath } = pathResolver(sourceFolder);\n const dir = createPath.distDir();\n await mkdir(dir, { recursive: true });\n await writeFile(\n resolve(dir, \"kosmo.json\"),\n JSON.stringify(folderManifestFactory(sourceFolder), undefined, 2),\n \"utf8\",\n );\n};\n\nexport const runnerPath = ({ root, distDir }: ProjectSettings) => {\n return resolve(root, distDir, \"run.js\");\n};\n\nlet runnerCode: string | undefined;\n\n/**\n * The template is authored as TypeScript so it typechecks with the package;\n * the deployed file is plain JS. Stripping runs through the oxc transform\n * that vite already ships, once per process - preview redeploys after every rebuild.\n * */\nconst runnerCodeFactory = async (): Promise<string> => {\n if (!runnerCode) {\n const { code } = await transformWithOxc(runTemplate, \"run.ts\", {\n sourcemap: false,\n });\n runnerCode = code;\n }\n return runnerCode;\n};\n\n/**\n * run.js is static - it holds no folder-specific data -\n * so rewriting it on every build, partial builds included, is idempotent.\n * */\nexport const deployRunner = async (projectSettings: ProjectSettings) => {\n const file = runnerPath(projectSettings);\n await mkdir(resolve(file, \"..\"), { recursive: true });\n await writeFile(file, await runnerCodeFactory(), \"utf8\");\n return file;\n};\n","import { type ChildProcess, spawn } from \"node:child_process\";\nimport { basename, relative, sep } from \"node:path\";\nimport { styleText } from \"node:util\";\n\nimport { watch } from \"chokidar\";\n\nimport type { ProjectSettings, SourceFolder } from \"@kosmojs/core\";\nimport { pathResolver } from \"@kosmojs/lib\";\n\nimport { deployRunner, runnerPath, writeFolderManifest } from \"./runner\";\n\n// coalesce the burst of events an editor save produces into one rebuild\nconst DEBOUNCE_MS = 300;\n\n// editor scratch files - a rebuild for these would only produce the same output\nconst IGNORED_FILE = /(^\\.|~$|\\.swp$|\\.tmp$)/;\n\ntype Build = (sourceFolder: SourceFolder) => Promise<void>;\n\n/**\n * Preview = production build + watcher + `dist/run.js`.\n *\n * The runner is a child process rather than an in-process import:\n * a fresh process is the only way to reload an ESM graph, and it is exactly\n * what production does - `node dist/run.js`.\n *\n * Watching is chokidar over the `src/<folder>` trees - no Vite server,\n * no module graph: the unit of work is \"rebuild this folder\", so folder-level\n * granularity is all the watcher needs to provide.\n * */\nexport const previewFactory = async (\n projectSettings: ProjectSettings,\n build: Build,\n): Promise<() => Promise<void>> => {\n const { root, previewPort, sourceFolders } = projectSettings;\n\n const runner = runnerPath(projectSettings);\n\n const log = (...args: Array<unknown>) => {\n // biome-ignore lint: console\n console.log(styleText(\"dim\", \"[preview]\"), ...args);\n };\n\n let child: ChildProcess | undefined;\n\n const startRunner = () => {\n const proc = spawn(\n process.execPath,\n [\"--enable-source-maps\", runner, \"--port\", String(previewPort)],\n { cwd: root, stdio: \"inherit\" },\n );\n\n proc.on(\"exit\", (code, signal) => {\n // a replaced runner exits on our SIGTERM - only report the unexpected\n if (child === proc) {\n child = undefined;\n log(styleText(\"red\", `runner exited (${signal ?? code})`));\n }\n });\n\n child = proc;\n };\n\n const stopRunner = () => {\n return new Promise<void>((resolve) => {\n const proc = child;\n\n if (!proc || proc.exitCode !== null) {\n child = undefined;\n resolve();\n return;\n }\n\n child = undefined;\n proc.once(\"exit\", () => resolve());\n proc.kill(\"SIGTERM\");\n });\n };\n\n const restartRunner = async () => {\n await stopRunner();\n startRunner();\n };\n\n const dirty = new Set<SourceFolder>();\n\n let building = false;\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n const rebuild = async () => {\n if (building) {\n // picked up by the loop below once the current pass finishes\n return;\n }\n\n building = true;\n\n try {\n // a save landing mid-build re-enters the loop instead of being lost.\n // This also covers the build seeding a just-created empty route file:\n // the second pass finds it non-empty, writes nothing, and the loop ends.\n while (dirty.size) {\n const folders = [...dirty];\n dirty.clear();\n\n let failed = false;\n\n for (const sourceFolder of folders) {\n log(`rebuilding ${styleText(\"blue\", sourceFolder.name)}`);\n try {\n await build(sourceFolder);\n await writeFolderManifest(sourceFolder);\n } catch (error) {\n failed = true;\n console.error(\n styleText(\"red\", `${sourceFolder.name}: build failed`),\n );\n console.error(error);\n }\n }\n\n await deployRunner(projectSettings);\n\n if (failed) {\n // the running process serves the previous build from memory; leave it be\n log(styleText(\"yellow\", \"keeping previous build running\"));\n } else {\n await restartRunner();\n }\n }\n } finally {\n building = false;\n }\n };\n\n const schedule = (sourceFolder: SourceFolder, file: string) => {\n if (process.env.DEBUG) {\n log(`event ${sourceFolder.name}: ${file} (building: ${building})`);\n }\n if (IGNORED_FILE.test(basename(file))) {\n return;\n }\n dirty.add(sourceFolder);\n clearTimeout(timer);\n timer = setTimeout(rebuild, DEBOUNCE_MS);\n };\n\n const watchedFolders = sourceFolders.map((sourceFolder) => {\n const { createPath } = pathResolver(sourceFolder);\n return { dir: createPath.src(), sourceFolder };\n });\n\n const folderFor = (file: string) => {\n return watchedFolders.find(({ dir }) => {\n return file === dir || file.startsWith(dir + sep);\n })?.sourceFolder;\n };\n\n const watcher = watch(\n watchedFolders.map(({ dir }) => dir),\n { ignoreInitial: true },\n );\n\n watcher.on(\"all\", (_event, file) => {\n const sourceFolder = folderFor(file);\n if (sourceFolder) {\n schedule(sourceFolder, file);\n }\n });\n\n watcher.on(\"error\", (error) => {\n console.error(styleText(\"red\", \"watcher failed\"));\n console.error(error);\n });\n\n for (const { dir } of watchedFolders) {\n log(`watching ${styleText(\"blue\", relative(root, dir))}`);\n }\n\n startRunner();\n\n const teardown = async () => {\n clearTimeout(timer);\n await watcher.close();\n await stopRunner();\n };\n\n // the runner is a child of this process: take it down with us on a plain kill,\n // not only on the Ctrl-C the terminal already forwards to the whole process group\n for (const signal of [\"SIGINT\", \"SIGTERM\"] as const) {\n process.once(signal, async () => {\n await teardown();\n process.exit(0);\n });\n }\n\n return teardown;\n};\n","import http, { type IncomingMessage, type ServerResponse } from \"node:http\";\nimport net from \"node:net\";\nimport { join, resolve } from \"node:path\";\nimport { styleText } from \"node:util\";\n\nimport { build, createServer, type RunnableDevEnvironment } from \"vite\";\n\nimport {\n defaults,\n type GeneratorFactory,\n type ProjectSettings,\n type ResolvedEntry,\n type SourceFolder,\n type WatcherEvent,\n} from \"@kosmojs/core\";\nimport type { DevSetup } from \"@kosmojs/core/api\";\nimport {\n collectVirtualModules,\n mergeConfigs,\n pathResolver,\n routesFactory,\n spinnerFactory,\n vitePlugins,\n} from \"@kosmojs/lib\";\n\nimport { cacheFactory } from \"./cache\";\nimport { previewFactory } from \"./preview\";\nimport { deployRunner, writeFolderManifest } from \"./runner\";\n\nexport default async (\n projectSettings: ProjectSettings,\n): Promise<() => Promise<void>> => {\n const { devPort, command } = projectSettings;\n\n // NOTE: initialize generators before anything else, regardless command\n for (const sourceFolder of projectSettings.sourceFolders) {\n for (const generator of sourceFolder.config.generators) {\n if (!generator.meta?.name || typeof generator.factory !== \"function\") {\n throw new Error(\n `${sourceFolder.name}: Unrecognized generator - must be created via defineGenerator()`,\n );\n }\n\n try {\n await generator.factory(sourceFolder).start?.();\n } catch (error) {\n console.error(\n styleText(\n \"red\",\n `${sourceFolder.name}: ${generator.meta.name} generator failed to initialize`,\n ),\n );\n throw error;\n }\n }\n }\n\n if (command === \"build\" || command === \"preview\") {\n for (const sourceFolder of projectSettings.sourceFolders) {\n await buildSourceFolder(sourceFolder);\n await writeFolderManifest(sourceFolder);\n }\n\n await deployRunner(projectSettings);\n\n if (command === \"build\") {\n return async () => {};\n }\n\n return previewFactory(projectSettings, buildSourceFolder);\n }\n\n type RequestMatcher = (req: IncomingMessage) => boolean;\n type RequestHandler = (req: IncomingMessage, res: ServerResponse) => void;\n\n const requestHandlers: Array<\n [\n segments: [base: string, api?: string],\n matcherFactory: () => RequestMatcher,\n handlerFactory: () => RequestHandler,\n ]\n > = [];\n\n const teardownHandlers: Array<() => Promise<unknown>> = [];\n\n const eventMap: Record<string, Awaited<ReturnType<typeof eventFactory>>> = {};\n\n // WARN: call this before starting any server!\n for (const sourceFolder of projectSettings.sourceFolders) {\n eventMap[sourceFolder.name] = await eventFactory(sourceFolder);\n }\n\n let port = await findFreePort(devPort);\n\n for (const sourceFolder of projectSettings.sourceFolders) {\n const { createPath } = pathResolver(sourceFolder);\n const { generators } = sourceFolder.config;\n\n const requestMatchers = matchersFactory(sourceFolder);\n\n const plugins = [\n vitePlugins.tsconfigPaths(sourceFolder),\n vitePlugins.nodePrefix(),\n vitePlugins.virtualModules(\n collectVirtualModules(sourceFolder, generators),\n {\n // The dev server is always a CSR graph - SSR runs in production builds only -\n // so env-sensitive modules resolve to their client variants here,\n // whatever a concurrent build is doing.\n kind: \"csr\",\n command,\n },\n ),\n ];\n\n const frontendGenerator = generators.find(\n (e) => e.meta.slot === \"frontend\",\n );\n\n const backendGenerator = generators.find((e) => e.meta.slot === \"backend\");\n\n // INFO: === start client server ===\n if (frontendGenerator) {\n const viteServer = await createServer(\n mergeConfigs(\n // user-provided config - lowest priority\n sourceFolder.config,\n // generators configs - higher priority\n ...generators.map(({ factory }) => {\n return factory(sourceFolder).config?.({ kind: \"client\", command });\n }),\n // main config - highest priority\n {\n // base provided by sourceFolder.config\n root: createPath.src(),\n cacheDir: cacheDir(sourceFolder, command, \"client\"),\n plugins,\n server: {\n port: port++,\n middlewareMode: true,\n hmr: { port: port++ },\n },\n },\n ),\n );\n\n for (const [evt, handler] of Object.entries(\n eventMap[sourceFolder.name],\n )) {\n viteServer.watcher.on(evt, handler);\n }\n\n requestHandlers.push([\n [sourceFolder.config.base],\n () => requestMatchers.base,\n () => viteServer.middlewares,\n ]);\n\n teardownHandlers.push(viteServer.close);\n }\n\n // INFO: === start backend server ===\n if (backendGenerator) {\n // NOTE: sourceFolder.config is client-specific config - not using for backend!\n // To provide backend-specific config pass it as api generator options.\n const viteServer = await createServer(\n mergeConfigs(\n // user-provided config - lowest priority\n backendGenerator.options,\n // generator config - higher priority\n backendGenerator\n .factory(sourceFolder)\n .config?.({ kind: \"backend\", command }),\n // main config - highest priority\n {\n root: createPath.src(),\n appType: \"custom\",\n cacheDir: cacheDir(sourceFolder, command, \"backend\"),\n plugins,\n server: {\n port: port++,\n middlewareMode: true,\n hmr: { port: port++ },\n },\n resolve: {\n conditions: [\"node\"],\n },\n environments: {\n api: {\n resolve: {\n conditions: [\"node\"],\n },\n },\n },\n },\n ),\n );\n\n const env = viteServer.environments.api as RunnableDevEnvironment;\n\n const loadDevSetup = async () => {\n env.runner.clearCache();\n return env.runner\n .import<{ default: DevSetup }>(join(defaults.apiDir, \"dev.ts\"))\n .then((e) => e.default);\n };\n\n let devSetup = await loadDevSetup();\n\n for (const [evt, handler] of Object.entries(\n eventMap[sourceFolder.name],\n )) {\n viteServer.watcher.on(evt, async (file) => {\n const mods = env.moduleGraph.getModulesByFile(file);\n if (mods?.size) {\n try {\n await handler(file);\n await devSetup?.teardownHandler?.();\n devSetup = await loadDevSetup();\n } catch (error) {\n /**\n * A transiently-invalid module (half-saved file, atomic rewrite mid-flight, syntax error)\n * must not take the dev server down: an uncaught rejection here is fatal to the whole process.\n * Keep serving the previous api program; the next successful change reloads it.\n * */\n console.error(\n styleText(\n \"red\",\n `${sourceFolder.name}: api reload failed - keeping previous api program`,\n ),\n );\n console.error(error);\n }\n }\n });\n }\n\n requestHandlers.push([\n [sourceFolder.config.base, sourceFolder.config.apiBase],\n () => devSetup.requestMatcher || requestMatchers.api,\n () => devSetup.requestHandler(),\n ]);\n\n teardownHandlers.push(viteServer.close);\n }\n }\n\n /**\n * Sorting is essential to ensure more specific paths are matched before broader ones.\n *\n * Given source folders:\n * - main app: baseurl=/ apiurl=/api\n * - admin app: baseurl=/admin apiurl=/admin/api\n *\n * Correct sort order:\n * 1. /admin/api — most specific\n * 2. /api\n * 3. /admin\n * 4. /\n * */\n const requestHandlerWeight = ([\n segments,\n ]: (typeof requestHandlers)[number]) => {\n const [base, api] = segments;\n /**\n * set weight to number of non-empty segments:\n * \"/\" weight is 0\n * \"/admin\" weight is 1\n * */\n const weight = base.split(\"/\").filter(Boolean).length;\n return api ? weight + 5 : weight;\n };\n\n const handlers = requestHandlers.sort(\n (a, b) => requestHandlerWeight(b) - requestHandlerWeight(a),\n );\n\n const httpServer = http.createServer((req, res) => {\n for (const [, matcherFactory, handlerFactory] of handlers) {\n // should be called on every request\n const [matcher, handler] = [matcherFactory(), handlerFactory()];\n if (matcher(req)) {\n handler(req, res);\n return;\n }\n }\n res.writeHead(404, { \"Content-Type\": \"text/html\" });\n res.end(\"<h1>404: Not Found</h1>\");\n });\n\n httpServer.on(\"error\", (error) => {\n console.error(\n styleText(\"red\", `Failed to start dev server on port ${devPort}`),\n );\n console.error(error.message);\n process.exit(1);\n });\n\n httpServer.listen(devPort);\n\n teardownHandlers.push(async () => {\n httpServer.close();\n });\n\n return async () => {\n for (const handler of teardownHandlers) {\n await handler();\n }\n // Let chokidar fully release file handles\n await new Promise((resolve) => setTimeout(resolve, 100));\n };\n};\n\nconst cacheDir = (\n { root, name }: SourceFolder,\n command: ProjectSettings[\"command\"],\n mode: \"client\" | \"backend\",\n) => {\n return resolve(root, `var/.vite/${name}/${command}/${mode}`);\n};\n\n/**\n * Production build of one source folder - generators, client bundle, api bundle,\n * then the generators' post-build steps (ssr, ssg).\n * Shared by `build` and `preview`: preview is a production build that reruns on change,\n * so generators are told `command: \"build\"` and the build cache is the same.\n * */\nconst buildSourceFolder = async (sourceFolder: SourceFolder) => {\n const command = \"build\";\n\n const { createPath } = pathResolver(sourceFolder);\n const { generators } = sourceFolder.config;\n\n const resolvedRoutes = [];\n\n {\n const { resolvers } = await routesFactory(sourceFolder, cacheFactory);\n\n const spinner = spinnerFactory(`${sourceFolder.name}: resolving routes`);\n\n for (const { name, handler } of resolvers.values()) {\n spinner.append(\n `[ ${resolvedRoutes.length + 1} of ${resolvers.size} ] ${name}`,\n );\n resolvedRoutes.push(await handler());\n }\n\n spinner.succeed(\"ready ✨\");\n }\n\n const plugins = [\n vitePlugins.tsconfigPaths(sourceFolder),\n vitePlugins.nodePrefix(),\n vitePlugins.virtualModules(\n collectVirtualModules(sourceFolder, generators),\n {\n // `kind: \"csr\"` everywhere except the SSR bundle,\n // which installs its own copy with `kind: \"ssr\"`\n kind: \"csr\",\n command,\n },\n ),\n ];\n\n for (const generator of generators) {\n await generator.factory(sourceFolder).build?.(resolvedRoutes);\n }\n\n const frontendGenerator = generators.find((e) => e.meta.slot === \"frontend\");\n\n const backendGenerator = generators.find((e) => e.meta.slot === \"backend\");\n\n // INFO: === build client ===\n if (frontendGenerator) {\n await build(\n mergeConfigs(\n // user-provided config - lowest priority\n sourceFolder.config,\n // generators configs - higher priority\n ...generators.map(({ factory }) => {\n return factory(sourceFolder).config?.({\n kind: \"client\",\n command,\n });\n }),\n // main config - highest priority\n {\n // base provided by sourceFolder.config\n root: createPath.src(),\n cacheDir: cacheDir(sourceFolder, command, \"client\"),\n plugins,\n build: {\n outDir: createPath.distDir(\"client\"),\n manifest: true,\n emptyOutDir: true,\n },\n },\n ),\n );\n }\n\n // INFO: === build backend ===\n if (backendGenerator) {\n const dir = createPath.distDir(\"api\");\n\n // NOTE: sourceFolder.config is client-specific config - not using for backend!\n // To provide backend-specific config pass it as api generator options.\n await build(\n mergeConfigs(\n // user-provided config - lowest priority\n backendGenerator.options,\n // generator config - higher priority\n backendGenerator\n .factory(sourceFolder)\n .config?.({ kind: \"backend\", command }),\n // main config - highest priority\n {\n base: \"./\",\n root: createPath.src(),\n appType: \"custom\",\n plugins,\n resolve: {\n conditions: [\"node\"],\n },\n build: {\n ssr: true,\n target: \"esnext\",\n sourcemap: true,\n emptyOutDir: true,\n rolldownOptions: {\n input: [\n createPath.api(\"app.ts\"),\n createPath.api(\"server.ts\"),\n // node listener for dist/run.js - the app without a port\n createPath.lib(\"@api/listener.ts\"),\n ],\n output: {\n dir,\n format: \"esm\",\n },\n },\n },\n cacheDir: cacheDir(sourceFolder, command, \"backend\"),\n },\n ),\n );\n }\n\n for (const generator of generators) {\n await generator.factory(sourceFolder).postBuild?.(resolvedRoutes);\n }\n};\n\nconst eventFactory = async (\n sourceFolder: SourceFolder,\n): Promise<\n Record<\"add\" | \"change\" | \"unlink\", (f: string) => Promise<void>>\n> => {\n const { resolvers, resolversFactory } = await routesFactory(\n sourceFolder,\n cacheFactory,\n );\n\n const { createPath } = pathResolver(sourceFolder);\n\n const generators: Array<{\n name: string | undefined;\n factory: GeneratorFactory;\n }> = [];\n\n for (const generator of sourceFolder.config.generators) {\n const factory = generator.factory(sourceFolder);\n generators.push({ name: generator.meta.name, factory });\n }\n\n const resolvedRoutes = new Map<\n string, // fileFullpath\n ResolvedEntry\n >();\n\n const runGenerators = async (event?: WatcherEvent) => {\n /**\n * Watch handlers receive the full list of entries\n * and should process only those whose source file or dependencies were updated.\n * */\n const entries = Array.from(resolvedRoutes.values());\n\n for (const { name, factory } of generators) {\n try {\n await factory.watch?.(entries, event);\n } catch (error) {\n console.error(\n styleText(\"red\", `${sourceFolder.name}: ${name} generator failed`),\n );\n if (event) {\n console.error(event);\n }\n console.error(error);\n }\n }\n };\n\n const updateResolvedEntry = async (file: string) => {\n const resolver = resolvers.get(file);\n\n if (!resolver) {\n return;\n }\n\n try {\n const resolvedEntry = await resolver.handler(file);\n resolvedRoutes.set(resolvedEntry.entry.fileFullpath, resolvedEntry);\n return resolvedEntry;\n } catch (error) {\n const route = file.replace(`${createPath.api()}/`, \"\");\n console.error(\n styleText(\n \"red\",\n `${sourceFolder.name}: ${route} route resolution failed`,\n ),\n );\n console.error(error);\n return;\n }\n };\n\n {\n const spinner = spinnerFactory(`${sourceFolder.name}: resolving routes`);\n for (const { name, handler } of resolvers.values()) {\n spinner.append(\n `[ ${resolvedRoutes.size + 1} of ${resolvers.size} ] ${name}`,\n );\n const route = await handler();\n resolvedRoutes.set(route.entry.fileFullpath, route);\n }\n spinner.succeed(\"ready ✨\");\n }\n\n // NOTE: call only after routes resolved\n await runGenerators();\n\n return {\n async add(file) {\n const [resolver] = resolversFactory([file]).values();\n\n if (!resolver) {\n return;\n }\n\n resolvers.set(file, resolver);\n\n // call only after `resolvers.set(file)`\n const resolvedEntry = await updateResolvedEntry(file);\n\n if (resolvedEntry) {\n await runGenerators({ kind: \"create\", file });\n }\n },\n\n async change(file) {\n if (resolvedRoutes.has(file)) {\n // route updated\n await updateResolvedEntry(file);\n } else {\n // updating entries that are referencing updated file\n const relatedRoutes = resolvedRoutes\n .values()\n .flatMap(({ kind, entry }) => {\n return kind === \"apiRoute\"\n ? entry.referencedFiles.includes(file)\n ? [entry]\n : []\n : [];\n });\n\n for (const route of relatedRoutes) {\n await updateResolvedEntry(route.fileFullpath);\n }\n }\n\n await runGenerators({ kind: \"update\", file });\n },\n\n async unlink(file) {\n // route deleted\n resolvers.delete(file);\n resolvedRoutes.delete(file);\n await runGenerators({ kind: \"delete\", file });\n },\n };\n};\n\nconst normalizeRegex = (s: string) => {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\").replace(/\\/+$/, \"\");\n};\n\nconst matchersFactory: (\n sourceFolder: SourceFolder,\n) => Record<\"base\" | \"api\", (req: IncomingMessage) => boolean> = ({\n config,\n}) => {\n const basePattern = new RegExp(`^${normalizeRegex(config.base)}(?=$|/)`);\n const apiPattern = new RegExp(\n `^${normalizeRegex(join(config.base, config.apiBase))}(?=$|/)`,\n );\n return {\n base(req) {\n return apiPattern.test(req.url as string)\n ? false\n : basePattern.test(req.url as string);\n },\n api(req) {\n return apiPattern.test(req.url as string);\n },\n };\n};\n\nconst findFreePort = async (devPort: number): Promise<number> => {\n let minPort = 0;\n let maxPort = 0;\n\n for (const n of [3, 2, 1, \"\"]) {\n minPort = Number(`${n}${devPort}`) + 100;\n maxPort = minPort + 100;\n if (maxPort < 65000) {\n break;\n }\n }\n\n if (maxPort >= 65000) {\n throw new Error(\"the devPort in package.json should be less than 64000\");\n }\n\n const range = maxPort - minPort + 1;\n const startOffset = Math.floor(Math.random() * range);\n\n const ports = Array.from({ length: range }, (_, i) => {\n return minPort + ((startOffset + i) % range);\n });\n\n const freePort = await ports.reduce(\n async (prevPromise, port) => {\n const freePort = await prevPromise;\n if (freePort) {\n return freePort;\n }\n const isFree = await isPortFree(port);\n return isFree ? port : undefined;\n },\n Promise.resolve(undefined as number | undefined),\n );\n\n if (!freePort) {\n throw new Error(`No free ports found in range ${minPort}-${maxPort}`);\n }\n\n return freePort;\n};\n\nconst isPortFree = (port: number): Promise<boolean> => {\n return new Promise((resolve) => {\n const server = net.createServer();\n\n server.once(\"error\", () => resolve(false));\n\n server.once(\"listening\", () => {\n server.close();\n resolve(true);\n });\n\n server.listen(port, \"127.0.0.1\");\n });\n};\n"],"mappings":"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"}
|
package/pkg/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type FolderConfig, type SourceFolder } from "@kosmojs/core";
|
|
2
|
-
|
|
2
|
+
import coreGenerator from "@kosmojs/core-generator";
|
|
3
3
|
export { default as fetchGenerator } from "@kosmojs/fetch-generator";
|
|
4
4
|
export { default as h3Generator } from "@kosmojs/h3-generator";
|
|
5
5
|
export { default as honoGenerator } from "@kosmojs/hono-generator";
|
|
@@ -13,4 +13,5 @@ export { default as ssrGenerator } from "@kosmojs/ssr-generator";
|
|
|
13
13
|
export { default as svelteGenerator } from "@kosmojs/svelte-generator";
|
|
14
14
|
export { default as typeboxGenerator } from "@kosmojs/typebox-generator";
|
|
15
15
|
export { default as vueGenerator } from "@kosmojs/vue-generator";
|
|
16
|
+
export { coreGenerator };
|
|
16
17
|
export declare const defineConfig: (config: FolderConfig) => SourceFolder["config"];
|