@kosmojs/dev 0.1.0 → 0.1.2

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/pkg/chassis.js CHANGED
@@ -1,540 +1,483 @@
1
- // src/chassis.ts
1
+ import { dirname, join, resolve } from "node:path";
2
+ import { defaults } from "@kosmojs/core";
2
3
  import http from "node:http";
3
4
  import net from "node:net";
4
- import { join, resolve as resolve2 } from "node:path";
5
5
  import { styleText } from "node:util";
6
6
  import { build, createServer } from "vite";
7
- import {
8
- defaults
9
- } from "@kosmojs/core";
10
- import { pathResolver as pathResolver2, routesFactory, spinnerFactory } from "@kosmojs/lib";
7
+ import { pathExists, pathResolver, routesFactory, spinnerFactory } from "@kosmojs/lib";
11
8
  import coreGenerator from "@kosmojs/core-generator";
12
-
13
- // src/cache.ts
14
9
  import { mkdir, readFile, writeFile } from "node:fs/promises";
15
- import { dirname, resolve } from "node:path";
16
10
  import crc from "crc/crc32";
17
- import self from "@kosmojs/dev/package.json" with { type: "json" };
18
- import { pathExists, pathResolver } from "@kosmojs/lib";
11
+ var package_default = {
12
+ type: "module",
13
+ name: "@kosmojs/dev",
14
+ version: "0.1.1",
15
+ cacheVersion: "001",
16
+ author: "Slee Woo",
17
+ license: "MIT",
18
+ files: ["pkg/*"],
19
+ exports: {
20
+ ".": {
21
+ "types": "./pkg/index.d.ts",
22
+ "default": "./pkg/index.js"
23
+ },
24
+ "./chassis": {
25
+ "types": "./pkg/chassis.d.ts",
26
+ "default": "./pkg/chassis.js"
27
+ },
28
+ "./package.json": "./package.json"
29
+ },
30
+ scripts: { "build": "wsbuild src/index.ts src/chassis.ts" },
31
+ dependencies: { "crc": "^4.3.2" },
32
+ devDependencies: {
33
+ "@kosmojs/core": "workspace:^",
34
+ "@kosmojs/core-generator": "workspace:^",
35
+ "@kosmojs/fetch-generator": "workspace:^",
36
+ "@kosmojs/hono-generator": "workspace:^",
37
+ "@kosmojs/koa-generator": "workspace:^",
38
+ "@kosmojs/lib": "workspace:^",
39
+ "@kosmojs/mdx-generator": "workspace:^",
40
+ "@kosmojs/openapi-generator": "workspace:^",
41
+ "@kosmojs/react-generator": "workspace:^",
42
+ "@kosmojs/solid-generator": "workspace:^",
43
+ "@kosmojs/ssg-generator": "workspace:^",
44
+ "@kosmojs/ssr-generator": "workspace:^",
45
+ "@kosmojs/typebox-generator": "workspace:^",
46
+ "@kosmojs/vue-generator": "workspace:^"
47
+ }
48
+ };
49
+ //#endregion
50
+ //#region src/cache.ts
51
+ /**
52
+ * Import from published package to ensure correct version at runtime.
53
+ * Local import would be bundled with pre-bump version; this external
54
+ * import resolves to the actual published package.json.
55
+ * */
19
56
  var cacheFactory = (route, sourceFolder, extraContext) => {
20
- const cacheFile = pathResolver(sourceFolder).createPath.libApi(
21
- dirname(route.file),
22
- "cache.json"
23
- );
24
- const validateCache = async (cache) => {
25
- if (!cache?.hash) {
26
- return;
27
- }
28
- if (!cache.typeDeclarations || !cache.referencedFiles) {
29
- return;
30
- }
31
- const hash = await generateFileHash(route.fileFullpath, {
32
- ...extraContext
33
- });
34
- if (!identicalHashSum(cache.hash, hash)) {
35
- return;
36
- }
37
- for (const [file, hash2] of Object.entries(cache.referencedFiles)) {
38
- if (!identicalHashSum(
39
- hash2,
40
- await generateFileHash(resolve(sourceFolder.root, file))
41
- )) {
42
- return;
43
- }
44
- }
45
- return cache;
46
- };
47
- return {
48
- async get(opt) {
49
- if (await pathExists(cacheFile)) {
50
- try {
51
- const cache = JSON.parse(await readFile(cacheFile, "utf8"));
52
- return opt?.validate ? validateCache(cache) : cache;
53
- } catch (_e) {
54
- }
55
- }
56
- return void 0;
57
- },
58
- async set(cache) {
59
- const hash = await generateFileHash(route.fileFullpath, {
60
- ...extraContext
61
- });
62
- const referencedFiles = {};
63
- for (const file of cache.referencedFiles) {
64
- referencedFiles[
65
- // Strip project root to ensure cached paths are relative
66
- // and portable across environments (CI, local, etc.)
67
- file.replace(`${sourceFolder.root}/`, "")
68
- ] = await generateFileHash(file);
69
- }
70
- const value = { ...cache, hash, referencedFiles };
71
- await mkdir(dirname(cacheFile), { recursive: true });
72
- await writeFile(cacheFile, JSON.stringify(value, null, 2), "utf8");
73
- return value;
74
- }
75
- };
57
+ const cacheFile = pathResolver(sourceFolder).createPath.libApi(dirname(route.file), "cache.json");
58
+ const validateCache = async (cache) => {
59
+ if (!cache?.hash) return;
60
+ if (!cache.typeDeclarations || !cache.referencedFiles) return;
61
+ const hash = await generateFileHash(route.fileFullpath, { ...extraContext });
62
+ if (!identicalHashSum(cache.hash, hash)) return;
63
+ for (const [file, hash] of Object.entries(cache.referencedFiles)) if (!identicalHashSum(hash, await generateFileHash(resolve(sourceFolder.root, file)))) return;
64
+ return cache;
65
+ };
66
+ return {
67
+ async get(opt) {
68
+ if (await pathExists(cacheFile)) try {
69
+ const cache = JSON.parse(await readFile(cacheFile, "utf8"));
70
+ return opt?.validate ? validateCache(cache) : cache;
71
+ } catch (_e) {}
72
+ },
73
+ async set(cache) {
74
+ const hash = await generateFileHash(route.fileFullpath, { ...extraContext });
75
+ const referencedFiles = {};
76
+ for (const file of cache.referencedFiles) referencedFiles[file.replace(`${sourceFolder.root}/`, "")] = await generateFileHash(file);
77
+ const value = {
78
+ ...cache,
79
+ hash,
80
+ referencedFiles
81
+ };
82
+ await mkdir(dirname(cacheFile), { recursive: true });
83
+ await writeFile(cacheFile, JSON.stringify(value, null, 2), "utf8");
84
+ return value;
85
+ }
86
+ };
76
87
  };
77
88
  var generateFileHash = async (file, extraContext) => {
78
- let fileContent;
79
- try {
80
- fileContent = await readFile(file, "utf8");
81
- } catch (_e) {
82
- return 0;
83
- }
84
- return fileContent ? crc(
85
- JSON.stringify({
86
- ...extraContext,
87
- [self.cacheVersion]: fileContent
88
- })
89
- ) : 0;
89
+ let fileContent;
90
+ try {
91
+ fileContent = await readFile(file, "utf8");
92
+ } catch (_e) {
93
+ return 0;
94
+ }
95
+ return fileContent ? crc(JSON.stringify({
96
+ ...extraContext,
97
+ [package_default.cacheVersion]: fileContent
98
+ })) : 0;
90
99
  };
91
100
  var identicalHashSum = (a, b) => {
92
- return a === b;
101
+ return a === b;
93
102
  };
94
-
95
- // src/chassis.ts
103
+ //#endregion
104
+ //#region src/chassis.ts
96
105
  var chassis_default = async (projectSettings) => {
97
- const { devPort, command } = projectSettings;
98
- for (const sourceFolder of projectSettings.sourceFolders) {
99
- for (const base of folderGenerators(sourceFolder)) {
100
- if (!base.meta?.name || typeof base.factory !== "function") {
101
- throw new Error(
102
- `${sourceFolder.name}: Unrecognized generator - must be created via defineGenerator()`
103
- );
104
- }
105
- const factory = base.factory(sourceFolder);
106
- if (!factory.meta?.name) {
107
- throw new Error(
108
- `${sourceFolder.name}: ${base.meta.name} generator is missing meta property`
109
- );
110
- }
111
- try {
112
- await factory.start?.();
113
- } catch (error) {
114
- console.error(
115
- styleText(
116
- "red",
117
- `${sourceFolder.name}: ${base.meta.name} generator failed to initialize`
118
- )
119
- );
120
- throw error;
121
- }
122
- }
123
- }
124
- if (command === "build") {
125
- for (const sourceFolder of projectSettings.sourceFolders) {
126
- const { config, baseurl } = sourceFolder;
127
- const { createPath } = pathResolver2(sourceFolder);
128
- const resolvedRoutes = [];
129
- {
130
- const { resolvers } = await routesFactory(sourceFolder, cacheFactory);
131
- const spinner = spinnerFactory(
132
- `${sourceFolder.name}: resolving routes`
133
- );
134
- for (const { name, handler } of resolvers.values()) {
135
- spinner.append(
136
- `[ ${resolvedRoutes.length + 1} of ${resolvers.size} ] ${name}`
137
- );
138
- resolvedRoutes.push(await handler());
139
- }
140
- spinner.succeed("ready \u2728");
141
- }
142
- const generators = folderGenerators(sourceFolder);
143
- const plugins = [...config.plugins || []];
144
- for (const base of generators) {
145
- await base.factory(sourceFolder).build?.(resolvedRoutes);
146
- plugins.push(...base.plugins?.(sourceFolder, command) || []);
147
- }
148
- {
149
- const outDir = createPath.distDir("client");
150
- await build({
151
- ...config,
152
- configFile: false,
153
- root: createPath.src(),
154
- base: join(baseurl, "/"),
155
- plugins,
156
- resolve: {
157
- ...config.resolve,
158
- tsconfigPaths: true
159
- },
160
- build: {
161
- ...config?.build,
162
- outDir,
163
- manifest: true,
164
- emptyOutDir: true
165
- },
166
- cacheDir: cacheDir(sourceFolder, command, "client")
167
- });
168
- }
169
- const apiGenerator = generators.find((e) => e.meta.slot === "api");
170
- if (apiGenerator) {
171
- const dir = createPath.distDir("api");
172
- const noExternal = Array.isArray(apiGenerator.options?.noExternal) ? apiGenerator.options.noExternal : generators.flatMap(({ meta }) => {
173
- return Object.keys({
174
- ...meta.dependencies,
175
- ...meta.devDependencies
176
- });
177
- });
178
- await build({
179
- configFile: false,
180
- root: createPath.src(),
181
- appType: "custom",
182
- plugins: [...apiGenerator.plugins?.(sourceFolder, command) || []],
183
- define: {
184
- ...config.define,
185
- KOSMO_PRODUCTION_BUILD: "true"
186
- },
187
- ssr: { noExternal },
188
- resolve: {
189
- ...config.resolve,
190
- tsconfigPaths: true,
191
- conditions: ["node"]
192
- },
193
- build: {
194
- ssr: true,
195
- target: "esnext",
196
- sourcemap: true,
197
- emptyOutDir: true,
198
- rolldownOptions: {
199
- input: [createPath.api("app.ts"), createPath.api("server.ts")],
200
- output: {
201
- dir,
202
- format: "esm"
203
- }
204
- }
205
- },
206
- cacheDir: cacheDir(sourceFolder, command, "backend")
207
- });
208
- }
209
- for (const base of generators) {
210
- await base.factory(sourceFolder).postBuild?.(resolvedRoutes);
211
- }
212
- }
213
- return async () => {
214
- };
215
- }
216
- const requestHandlers = [];
217
- const teardownHandlers = [];
218
- const eventMap = {};
219
- for (const sourceFolder of projectSettings.sourceFolders) {
220
- eventMap[sourceFolder.name] = await eventFactory(sourceFolder);
221
- }
222
- let port = await findFreePort(devPort);
223
- for (const sourceFolder of projectSettings.sourceFolders) {
224
- const { config, baseurl } = sourceFolder;
225
- const { createPath } = pathResolver2(sourceFolder);
226
- const requestMatchers = matchersFactory(sourceFolder);
227
- const generators = folderGenerators(sourceFolder);
228
- const plugins = [...config.plugins || []];
229
- for (const base of generators) {
230
- plugins.push(...base.plugins?.(sourceFolder, command) || []);
231
- }
232
- const viteServer = await createServer({
233
- ...config,
234
- configFile: false,
235
- root: createPath.src(),
236
- base: join(baseurl, "/"),
237
- plugins,
238
- server: {
239
- ...config.server,
240
- port: port++,
241
- middlewareMode: true,
242
- hmr: { port: port++ }
243
- },
244
- resolve: {
245
- ...config.resolve,
246
- tsconfigPaths: true
247
- },
248
- define: {
249
- ...config.define
250
- },
251
- cacheDir: cacheDir(sourceFolder, command, "client")
252
- });
253
- for (const [evt, handler] of Object.entries(eventMap[sourceFolder.name])) {
254
- viteServer.watcher.on(evt, handler);
255
- }
256
- requestHandlers.push([
257
- [sourceFolder.baseurl],
258
- () => requestMatchers.base,
259
- () => viteServer.middlewares
260
- ]);
261
- teardownHandlers.push(viteServer.close);
262
- }
263
- for (const sourceFolder of projectSettings.sourceFolders) {
264
- const { config } = sourceFolder;
265
- if (!folderGenerators(sourceFolder).find((e) => e.meta.slot === "api")) {
266
- continue;
267
- }
268
- const { createPath } = pathResolver2(sourceFolder);
269
- const requestMatchers = matchersFactory(sourceFolder);
270
- const viteServer = await createServer({
271
- configFile: false,
272
- root: createPath.src(),
273
- appType: "custom",
274
- server: {
275
- port: port++,
276
- middlewareMode: true,
277
- hmr: { port: port++ }
278
- },
279
- resolve: { tsconfigPaths: true },
280
- define: {
281
- ...config?.define,
282
- KOSMO_PRODUCTION_BUILD: "false"
283
- },
284
- environments: {
285
- api: {
286
- resolve: {
287
- conditions: ["node"]
288
- }
289
- }
290
- },
291
- cacheDir: cacheDir(sourceFolder, command, "backend")
292
- });
293
- const env = viteServer.environments.api;
294
- const loadDevSetup = async () => {
295
- env.runner.clearCache();
296
- return env.runner.import(join(defaults.apiDir, "dev.ts")).then((e) => e.default);
297
- };
298
- let devSetup = await loadDevSetup();
299
- for (const [evt, handler] of Object.entries(eventMap[sourceFolder.name])) {
300
- viteServer.watcher.on(evt, async (file) => {
301
- const mods = env.moduleGraph.getModulesByFile(file);
302
- if (mods?.size) {
303
- await handler(file);
304
- await devSetup?.teardownHandler?.();
305
- devSetup = await loadDevSetup();
306
- }
307
- });
308
- }
309
- requestHandlers.push([
310
- [sourceFolder.baseurl, sourceFolder.apiurl],
311
- () => devSetup.requestMatcher || requestMatchers.api,
312
- () => devSetup.requestHandler()
313
- ]);
314
- teardownHandlers.push(viteServer.close);
315
- }
316
- const requestHandlerWeight = ([
317
- segments
318
- ]) => {
319
- const [base, api] = segments;
320
- const weight = base.split("/").filter(Boolean).length;
321
- return api ? weight + 5 : weight;
322
- };
323
- const handlers = requestHandlers.sort(
324
- (a, b) => requestHandlerWeight(b) - requestHandlerWeight(a)
325
- );
326
- const httpServer = http.createServer((req, res) => {
327
- for (const [, matcherFactory, handlerFactory] of handlers) {
328
- const [matcher, handler] = [matcherFactory(), handlerFactory()];
329
- if (matcher(req)) {
330
- handler(req, res);
331
- return;
332
- }
333
- }
334
- res.writeHead(404, { "Content-Type": "text/html" });
335
- res.end("<h1>404: Not Found</h1>");
336
- });
337
- httpServer.on("error", (error) => {
338
- console.error(
339
- styleText("red", `Failed to start dev server on port ${devPort}`)
340
- );
341
- console.error(error.message);
342
- process.exit(1);
343
- });
344
- httpServer.listen(devPort);
345
- teardownHandlers.push(async () => {
346
- httpServer.close();
347
- });
348
- return async () => {
349
- for (const handler of teardownHandlers) {
350
- await handler().catch(console.error);
351
- }
352
- await new Promise((resolve3) => setTimeout(resolve3, 100));
353
- };
106
+ const { devPort, command } = projectSettings;
107
+ for (const sourceFolder of projectSettings.sourceFolders) for (const base of folderGenerators(sourceFolder)) {
108
+ if (!base.meta?.name || typeof base.factory !== "function") throw new Error(`${sourceFolder.name}: Unrecognized generator - must be created via defineGenerator()`);
109
+ const factory = base.factory(sourceFolder);
110
+ if (!factory.meta?.name) throw new Error(`${sourceFolder.name}: ${base.meta.name} generator is missing meta property`);
111
+ try {
112
+ await factory.start?.();
113
+ } catch (error) {
114
+ console.error(styleText("red", `${sourceFolder.name}: ${base.meta.name} generator failed to initialize`));
115
+ throw error;
116
+ }
117
+ }
118
+ if (command === "build") {
119
+ for (const sourceFolder of projectSettings.sourceFolders) {
120
+ const { config } = sourceFolder;
121
+ const { createPath } = pathResolver(sourceFolder);
122
+ const resolvedRoutes = [];
123
+ {
124
+ const { resolvers } = await routesFactory(sourceFolder, cacheFactory);
125
+ const spinner = spinnerFactory(`${sourceFolder.name}: resolving routes`);
126
+ for (const { name, handler } of resolvers.values()) {
127
+ spinner.append(`[ ${resolvedRoutes.length + 1} of ${resolvers.size} ] ${name}`);
128
+ resolvedRoutes.push(await handler());
129
+ }
130
+ spinner.succeed("ready ✨");
131
+ }
132
+ const generators = folderGenerators(sourceFolder);
133
+ const plugins = [...config.plugins || []];
134
+ for (const base of generators) {
135
+ await base.factory(sourceFolder).build?.(resolvedRoutes);
136
+ plugins.push(...base.plugins?.(sourceFolder, command) || []);
137
+ }
138
+ {
139
+ const outDir = createPath.distDir("client");
140
+ await build({
141
+ ...config,
142
+ configFile: false,
143
+ root: createPath.src(),
144
+ base: join(config.base, "/"),
145
+ plugins,
146
+ resolve: {
147
+ ...config.resolve,
148
+ tsconfigPaths: true
149
+ },
150
+ build: {
151
+ ...config?.build,
152
+ outDir,
153
+ manifest: true,
154
+ emptyOutDir: true
155
+ },
156
+ cacheDir: cacheDir(sourceFolder, command, "client")
157
+ });
158
+ }
159
+ const apiGenerator = generators.find((e) => e.meta.slot === "api");
160
+ if (apiGenerator) {
161
+ const dir = createPath.distDir("api");
162
+ const externalizeOptions = apiGenerator.options ? Object.entries(apiGenerator.options).flatMap(([k, v]) => {
163
+ return k === "external" || k === "noExternal" ? [[k, v]] : [];
164
+ }) : [];
165
+ await build({
166
+ configFile: false,
167
+ root: createPath.src(),
168
+ appType: "custom",
169
+ plugins: [...apiGenerator.plugins?.(sourceFolder, command) || []],
170
+ define: {
171
+ ...config.define,
172
+ KOSMO_PRODUCTION_BUILD: "true"
173
+ },
174
+ ssr: externalizeOptions.length ? Object.fromEntries(externalizeOptions) : { external: true },
175
+ resolve: {
176
+ ...config.resolve,
177
+ tsconfigPaths: true,
178
+ conditions: ["node"]
179
+ },
180
+ build: {
181
+ ssr: true,
182
+ target: "esnext",
183
+ sourcemap: true,
184
+ emptyOutDir: true,
185
+ rolldownOptions: {
186
+ input: [createPath.api("app.ts"), createPath.api("server.ts")],
187
+ output: {
188
+ dir,
189
+ format: "esm"
190
+ }
191
+ }
192
+ },
193
+ cacheDir: cacheDir(sourceFolder, command, "backend")
194
+ });
195
+ }
196
+ for (const base of generators) await base.factory(sourceFolder).postBuild?.(resolvedRoutes);
197
+ }
198
+ return async () => {};
199
+ }
200
+ const requestHandlers = [];
201
+ const teardownHandlers = [];
202
+ const eventMap = {};
203
+ for (const sourceFolder of projectSettings.sourceFolders) eventMap[sourceFolder.name] = await eventFactory(sourceFolder);
204
+ let port = await findFreePort(devPort);
205
+ for (const sourceFolder of projectSettings.sourceFolders) {
206
+ const { config } = sourceFolder;
207
+ const { createPath } = pathResolver(sourceFolder);
208
+ const requestMatchers = matchersFactory(sourceFolder);
209
+ const generators = folderGenerators(sourceFolder);
210
+ const plugins = [...config.plugins || []];
211
+ for (const base of generators) plugins.push(...base.plugins?.(sourceFolder, command) || []);
212
+ const viteServer = await createServer({
213
+ ...config,
214
+ configFile: false,
215
+ root: createPath.src(),
216
+ base: join(config.base, "/"),
217
+ plugins,
218
+ server: {
219
+ ...config.server,
220
+ port: port++,
221
+ middlewareMode: true,
222
+ hmr: { port: port++ }
223
+ },
224
+ resolve: {
225
+ ...config.resolve,
226
+ tsconfigPaths: true
227
+ },
228
+ define: { ...config.define },
229
+ cacheDir: cacheDir(sourceFolder, command, "client")
230
+ });
231
+ for (const [evt, handler] of Object.entries(eventMap[sourceFolder.name])) viteServer.watcher.on(evt, handler);
232
+ requestHandlers.push([
233
+ [sourceFolder.config.base],
234
+ () => requestMatchers.base,
235
+ () => viteServer.middlewares
236
+ ]);
237
+ teardownHandlers.push(viteServer.close);
238
+ }
239
+ for (const sourceFolder of projectSettings.sourceFolders) {
240
+ const { config } = sourceFolder;
241
+ if (!folderGenerators(sourceFolder).find((e) => e.meta.slot === "api")) continue;
242
+ const { createPath } = pathResolver(sourceFolder);
243
+ const requestMatchers = matchersFactory(sourceFolder);
244
+ const viteServer = await createServer({
245
+ configFile: false,
246
+ root: createPath.src(),
247
+ appType: "custom",
248
+ server: {
249
+ port: port++,
250
+ middlewareMode: true,
251
+ hmr: { port: port++ }
252
+ },
253
+ resolve: { tsconfigPaths: true },
254
+ define: {
255
+ ...config?.define,
256
+ KOSMO_PRODUCTION_BUILD: "false"
257
+ },
258
+ environments: { api: { resolve: { conditions: ["node"] } } },
259
+ cacheDir: cacheDir(sourceFolder, command, "backend")
260
+ });
261
+ const env = viteServer.environments.api;
262
+ const loadDevSetup = async () => {
263
+ env.runner.clearCache();
264
+ return env.runner.import(join(defaults.apiDir, "dev.ts")).then((e) => e.default);
265
+ };
266
+ let devSetup = await loadDevSetup();
267
+ for (const [evt, handler] of Object.entries(eventMap[sourceFolder.name])) viteServer.watcher.on(evt, async (file) => {
268
+ if (env.moduleGraph.getModulesByFile(file)?.size) {
269
+ await handler(file);
270
+ await devSetup?.teardownHandler?.();
271
+ devSetup = await loadDevSetup();
272
+ }
273
+ });
274
+ requestHandlers.push([
275
+ [sourceFolder.config.base, sourceFolder.config.apiBase],
276
+ () => devSetup.requestMatcher || requestMatchers.api,
277
+ () => devSetup.requestHandler()
278
+ ]);
279
+ teardownHandlers.push(viteServer.close);
280
+ }
281
+ /**
282
+ * Sorting is essential to ensure more specific paths are matched before broader ones.
283
+ *
284
+ * Given source folders:
285
+ * - main app: baseurl=/ apiurl=/api
286
+ * - admin app: baseurl=/admin apiurl=/admin/api
287
+ *
288
+ * Correct sort order:
289
+ * 1. /admin/api — most specific
290
+ * 2. /api
291
+ * 3. /admin
292
+ * 4. /
293
+ * */
294
+ const requestHandlerWeight = ([segments]) => {
295
+ const [base, api] = segments;
296
+ /**
297
+ * set weight to number of non-empty segments:
298
+ * "/" weight is 0
299
+ * "/admin" weight is 1
300
+ * */
301
+ const weight = base.split("/").filter(Boolean).length;
302
+ return api ? weight + 5 : weight;
303
+ };
304
+ const handlers = requestHandlers.sort((a, b) => requestHandlerWeight(b) - requestHandlerWeight(a));
305
+ const httpServer = http.createServer((req, res) => {
306
+ for (const [, matcherFactory, handlerFactory] of handlers) {
307
+ const [matcher, handler] = [matcherFactory(), handlerFactory()];
308
+ if (matcher(req)) {
309
+ handler(req, res);
310
+ return;
311
+ }
312
+ }
313
+ res.writeHead(404, { "Content-Type": "text/html" });
314
+ res.end("<h1>404: Not Found</h1>");
315
+ });
316
+ httpServer.on("error", (error) => {
317
+ console.error(styleText("red", `Failed to start dev server on port ${devPort}`));
318
+ console.error(error.message);
319
+ process.exit(1);
320
+ });
321
+ httpServer.listen(devPort);
322
+ teardownHandlers.push(async () => {
323
+ httpServer.close();
324
+ });
325
+ return async () => {
326
+ for (const handler of teardownHandlers) await handler();
327
+ await new Promise((resolve) => setTimeout(resolve, 100));
328
+ };
354
329
  };
355
330
  var cacheDir = ({ root, name }, command, mode) => {
356
- return resolve2(root, `var/.vite/${name}/${command}/${mode}`);
331
+ return resolve(root, `var/.vite/${name}/${command}/${mode}`);
357
332
  };
358
333
  var folderGenerators = (sourceFolder) => {
359
- const { generators = [] } = sourceFolder.config;
360
- const coreGenerators = {};
361
- const userGenerators = [];
362
- for (const base of generators) {
363
- if (base.meta.slot) {
364
- coreGenerators[base.meta.slot] = base;
365
- } else {
366
- userGenerators.push(base);
367
- }
368
- }
369
- return [
370
- // core generator should run first
371
- coreGenerator(),
372
- // then api generator
373
- ...coreGenerators.api ? [coreGenerators.api] : [],
374
- // then fetch generator, only if api generator also enabled
375
- ...coreGenerators.fetch && coreGenerators.api ? [coreGenerators.fetch] : [],
376
- // then user generators in the order they were added
377
- ...userGenerators,
378
- // then ssr generator should run after user generators
379
- ...coreGenerators.ssr ? [coreGenerators.ssr] : [],
380
- // and ssg generator should run last
381
- ...coreGenerators.ssg ? [coreGenerators.ssg] : []
382
- ];
334
+ const { generators = [] } = sourceFolder.config;
335
+ const coreGenerators = {};
336
+ const userGenerators = [];
337
+ for (const base of generators) if (base.meta.slot) coreGenerators[base.meta.slot] = base;
338
+ else userGenerators.push(base);
339
+ return [
340
+ coreGenerator(),
341
+ ...coreGenerators.api ? [coreGenerators.api] : [],
342
+ ...coreGenerators.fetch && coreGenerators.api ? [coreGenerators.fetch] : [],
343
+ ...userGenerators,
344
+ ...coreGenerators.ssr ? [coreGenerators.ssr] : [],
345
+ ...coreGenerators.ssg ? [coreGenerators.ssg] : []
346
+ ];
383
347
  };
384
348
  var eventFactory = async (sourceFolder) => {
385
- const { resolvers, resolversFactory } = await routesFactory(
386
- sourceFolder,
387
- cacheFactory
388
- );
389
- const { createPath } = pathResolver2(sourceFolder);
390
- const generators = [];
391
- for (const base of folderGenerators(sourceFolder)) {
392
- const factory = base.factory(sourceFolder);
393
- generators.push({ name: base.meta.name, factory });
394
- }
395
- const resolvedRoutes = /* @__PURE__ */ new Map();
396
- const runGenerators = async (event) => {
397
- const entries = Array.from(resolvedRoutes.values());
398
- for (const { name, factory } of generators) {
399
- try {
400
- await factory.watch?.(entries, event);
401
- } catch (error) {
402
- console.error(
403
- styleText("red", `${sourceFolder.name}: ${name} generator failed`)
404
- );
405
- if (event) {
406
- console.error(event);
407
- }
408
- console.error(error);
409
- }
410
- }
411
- };
412
- const updateResolvedEntry = async (file) => {
413
- const resolver = resolvers.get(file);
414
- if (!resolver) {
415
- return;
416
- }
417
- try {
418
- const resolvedEntry = await resolver.handler(file);
419
- resolvedRoutes.set(resolvedEntry.entry.fileFullpath, resolvedEntry);
420
- return resolvedEntry;
421
- } catch (error) {
422
- const route = file.replace(`${createPath.api()}/`, "");
423
- console.error(
424
- styleText(
425
- "red",
426
- `${sourceFolder.name}: ${route} route resolution failed`
427
- )
428
- );
429
- console.error(error);
430
- return;
431
- }
432
- };
433
- {
434
- const spinner = spinnerFactory(`${sourceFolder.name}: resolving routes`);
435
- for (const { name, handler } of resolvers.values()) {
436
- spinner.append(
437
- `[ ${resolvedRoutes.size + 1} of ${resolvers.size} ] ${name}`
438
- );
439
- const route = await handler();
440
- resolvedRoutes.set(route.entry.fileFullpath, route);
441
- }
442
- spinner.succeed("ready \u2728");
443
- }
444
- await runGenerators();
445
- return {
446
- async add(file) {
447
- const [resolver] = resolversFactory([file]).values();
448
- if (!resolver) {
449
- return;
450
- }
451
- resolvers.set(file, resolver);
452
- const resolvedEntry = await updateResolvedEntry(file);
453
- if (resolvedEntry) {
454
- await runGenerators({ kind: "create", file });
455
- }
456
- },
457
- async change(file) {
458
- if (resolvedRoutes.has(file)) {
459
- await updateResolvedEntry(file);
460
- } else {
461
- const relatedRoutes = resolvedRoutes.values().flatMap(({ kind, entry }) => {
462
- return kind === "apiRoute" ? entry.referencedFiles.includes(file) ? [entry] : [] : [];
463
- });
464
- for (const route of relatedRoutes) {
465
- await updateResolvedEntry(route.fileFullpath);
466
- }
467
- }
468
- await runGenerators({ kind: "update", file });
469
- },
470
- async unlink(file) {
471
- resolvers.delete(file);
472
- resolvedRoutes.delete(file);
473
- await runGenerators({ kind: "delete", file });
474
- }
475
- };
349
+ const { resolvers, resolversFactory } = await routesFactory(sourceFolder, cacheFactory);
350
+ const { createPath } = pathResolver(sourceFolder);
351
+ const generators = [];
352
+ for (const base of folderGenerators(sourceFolder)) {
353
+ const factory = base.factory(sourceFolder);
354
+ generators.push({
355
+ name: base.meta.name,
356
+ factory
357
+ });
358
+ }
359
+ const resolvedRoutes = /* @__PURE__ */ new Map();
360
+ const runGenerators = async (event) => {
361
+ /**
362
+ * Watch handlers receive the full list of entries
363
+ * and should process only those whose source file or dependencies were updated.
364
+ * */
365
+ const entries = Array.from(resolvedRoutes.values());
366
+ for (const { name, factory } of generators) try {
367
+ await factory.watch?.(entries, event);
368
+ } catch (error) {
369
+ console.error(styleText("red", `${sourceFolder.name}: ${name} generator failed`));
370
+ if (event) console.error(event);
371
+ console.error(error);
372
+ }
373
+ };
374
+ const updateResolvedEntry = async (file) => {
375
+ const resolver = resolvers.get(file);
376
+ if (!resolver) return;
377
+ try {
378
+ const resolvedEntry = await resolver.handler(file);
379
+ resolvedRoutes.set(resolvedEntry.entry.fileFullpath, resolvedEntry);
380
+ return resolvedEntry;
381
+ } catch (error) {
382
+ const route = file.replace(`${createPath.api()}/`, "");
383
+ console.error(styleText("red", `${sourceFolder.name}: ${route} route resolution failed`));
384
+ console.error(error);
385
+ return;
386
+ }
387
+ };
388
+ {
389
+ const spinner = spinnerFactory(`${sourceFolder.name}: resolving routes`);
390
+ for (const { name, handler } of resolvers.values()) {
391
+ spinner.append(`[ ${resolvedRoutes.size + 1} of ${resolvers.size} ] ${name}`);
392
+ const route = await handler();
393
+ resolvedRoutes.set(route.entry.fileFullpath, route);
394
+ }
395
+ spinner.succeed("ready ✨");
396
+ }
397
+ await runGenerators();
398
+ return {
399
+ async add(file) {
400
+ const [resolver] = resolversFactory([file]).values();
401
+ if (!resolver) return;
402
+ resolvers.set(file, resolver);
403
+ if (await updateResolvedEntry(file)) await runGenerators({
404
+ kind: "create",
405
+ file
406
+ });
407
+ },
408
+ async change(file) {
409
+ if (resolvedRoutes.has(file)) await updateResolvedEntry(file);
410
+ else {
411
+ const relatedRoutes = resolvedRoutes.values().flatMap(({ kind, entry }) => {
412
+ return kind === "apiRoute" ? entry.referencedFiles.includes(file) ? [entry] : [] : [];
413
+ });
414
+ for (const route of relatedRoutes) await updateResolvedEntry(route.fileFullpath);
415
+ }
416
+ await runGenerators({
417
+ kind: "update",
418
+ file
419
+ });
420
+ },
421
+ async unlink(file) {
422
+ resolvers.delete(file);
423
+ resolvedRoutes.delete(file);
424
+ await runGenerators({
425
+ kind: "delete",
426
+ file
427
+ });
428
+ }
429
+ };
476
430
  };
477
- var matchersFactory = ({
478
- baseurl,
479
- apiurl
480
- }) => {
481
- const basePattern = new RegExp(`^${baseurl}($|/*)`);
482
- const apiPattern = new RegExp(`^${join(baseurl, apiurl)}($|/*)`);
483
- return {
484
- base(req) {
485
- return apiPattern.test(req.url) ? false : basePattern.test(req.url);
486
- },
487
- api(req) {
488
- return apiPattern.test(req.url);
489
- }
490
- };
431
+ var matchersFactory = ({ config }) => {
432
+ const basePattern = new RegExp(`^${config.base}($|/*)`);
433
+ const apiPattern = new RegExp(`^${join(config.base, config.apiBase)}($|/*)`);
434
+ return {
435
+ base(req) {
436
+ return apiPattern.test(req.url) ? false : basePattern.test(req.url);
437
+ },
438
+ api(req) {
439
+ return apiPattern.test(req.url);
440
+ }
441
+ };
491
442
  };
492
443
  var findFreePort = async (devPort) => {
493
- let minPort = 0;
494
- let maxPort = 0;
495
- for (const n of [3, 2, 1, ""]) {
496
- minPort = Number(`${n}${devPort}`) + 100;
497
- maxPort = minPort + 100;
498
- if (maxPort < 65e3) {
499
- break;
500
- }
501
- }
502
- if (maxPort >= 65e3) {
503
- throw new Error("the devPort in package.json should be less than 64000");
504
- }
505
- const range = maxPort - minPort + 1;
506
- const startOffset = Math.floor(Math.random() * range);
507
- const ports = Array.from({ length: range }, (_, i) => {
508
- return minPort + (startOffset + i) % range;
509
- });
510
- const freePort = await ports.reduce(
511
- async (prevPromise, port) => {
512
- const freePort2 = await prevPromise;
513
- if (freePort2) {
514
- return freePort2;
515
- }
516
- const isFree = await isPortFree(port);
517
- return isFree ? port : void 0;
518
- },
519
- Promise.resolve(void 0)
520
- );
521
- if (!freePort) {
522
- throw new Error(`No free ports found in range ${minPort}-${maxPort}`);
523
- }
524
- return freePort;
444
+ let minPort = 0;
445
+ let maxPort = 0;
446
+ for (const n of [
447
+ 3,
448
+ 2,
449
+ 1,
450
+ ""
451
+ ]) {
452
+ minPort = Number(`${n}${devPort}`) + 100;
453
+ maxPort = minPort + 100;
454
+ if (maxPort < 65e3) break;
455
+ }
456
+ if (maxPort >= 65e3) throw new Error("the devPort in package.json should be less than 64000");
457
+ const range = maxPort - minPort + 1;
458
+ const startOffset = Math.floor(Math.random() * range);
459
+ const freePort = await Array.from({ length: range }, (_, i) => {
460
+ return minPort + (startOffset + i) % range;
461
+ }).reduce(async (prevPromise, port) => {
462
+ const freePort = await prevPromise;
463
+ if (freePort) return freePort;
464
+ return await isPortFree(port) ? port : void 0;
465
+ }, Promise.resolve(void 0));
466
+ if (!freePort) throw new Error(`No free ports found in range ${minPort}-${maxPort}`);
467
+ return freePort;
525
468
  };
526
469
  var isPortFree = (port) => {
527
- return new Promise((resolve3) => {
528
- const server = net.createServer();
529
- server.once("error", () => resolve3(false));
530
- server.once("listening", () => {
531
- server.close();
532
- resolve3(true);
533
- });
534
- server.listen(port, "127.0.0.1");
535
- });
470
+ return new Promise((resolve) => {
471
+ const server = net.createServer();
472
+ server.once("error", () => resolve(false));
473
+ server.once("listening", () => {
474
+ server.close();
475
+ resolve(true);
476
+ });
477
+ server.listen(port, "127.0.0.1");
478
+ });
536
479
  };
537
- export {
538
- chassis_default as default
539
- };
540
- //# sourceMappingURL=chassis.js.map
480
+ //#endregion
481
+ export { chassis_default as default };
482
+
483
+ //# sourceMappingURL=chassis.js.map