@noverax/courier-build 0.0.0-stage → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +289 -2
  3. package/lib/config/index.d.ts +420 -0
  4. package/lib/config/index.js +66 -0
  5. package/lib/config-file-of-Zkwyi9zf.js +28 -0
  6. package/lib/config-in-page-message-DLd3f0Jd.js +20 -0
  7. package/lib/courier-routes-options-BYF7Sfdf.d.cts +13 -0
  8. package/lib/courier-routes-options-BYF7Sfdf.d.ts +13 -0
  9. package/lib/engine/worker.d.ts +1 -0
  10. package/lib/engine/worker.js +551 -0
  11. package/lib/errno-code-CNx_E80R.js +32 -0
  12. package/lib/esbuild/index.d.ts +58 -0
  13. package/lib/esbuild/index.js +191 -0
  14. package/lib/index-_VUU5N5v.d.ts +134 -0
  15. package/lib/index.d.ts +101 -0
  16. package/lib/index.js +4 -0
  17. package/lib/jest/routes-thread.d.ts +1 -0
  18. package/lib/jest/routes-thread.js +46 -0
  19. package/lib/jest/transformer.cjs +121 -0
  20. package/lib/jest/transformer.d.cts +39 -0
  21. package/lib/load-config-BeedbpkN.js +1419 -0
  22. package/lib/metro/index.cjs +25 -0
  23. package/lib/metro/index.d.cts +55 -0
  24. package/lib/metro/index.d.ts +67 -0
  25. package/lib/metro/index.js +105 -0
  26. package/lib/package-file-CVAKY_Nu.js +39 -0
  27. package/lib/rollup/index.d.ts +51 -0
  28. package/lib/rollup/index.js +95 -0
  29. package/lib/routes-engine-BfwrCuNL.d.ts +117 -0
  30. package/lib/routes-file-DcpgxYb1.js +206 -0
  31. package/lib/routes-loader-context-40OKDrQp.d.ts +26 -0
  32. package/lib/routes-module-code-DO3aII6v.js +402 -0
  33. package/lib/routes-module-id-CnUjQjYF.js +12 -0
  34. package/lib/routes-read-error-DQhKIE2F.js +30 -0
  35. package/lib/routes-rule-CjzX6NS5.js +108 -0
  36. package/lib/rsbuild/index.d.ts +41 -0
  37. package/lib/rsbuild/index.js +37 -0
  38. package/lib/rspack/index.d.ts +3 -0
  39. package/lib/rspack/index.js +1 -0
  40. package/lib/shared-engine-CCu7vP_0.js +28 -0
  41. package/lib/vite/index.d.ts +33 -0
  42. package/lib/vite/index.js +346 -0
  43. package/lib/webpack/index.d.ts +4 -0
  44. package/lib/webpack/index.js +3 -0
  45. package/lib/webpack/loader.cjs +31 -0
  46. package/lib/webpack/loader.d.cts +40 -0
  47. package/lib/webpack/run-routes-loader.d.ts +22 -0
  48. package/lib/webpack/run-routes-loader.js +103 -0
  49. package/lib/webpack-B0v9i8lk.js +42 -0
  50. package/lib/worker-messages-BT2vvNQJ.js +35 -0
  51. package/package.json +209 -4
@@ -0,0 +1,551 @@
1
+ import { t as RoutesReadError } from "../routes-read-error-DQhKIE2F.js";
2
+ import { n as isRecord } from "../errno-code-CNx_E80R.js";
3
+ import { t as workerMessages } from "../worker-messages-BT2vvNQJ.js";
4
+ import { a as projectTransformCache, c as UntrustedConfigError, f as configCandidates, i as loadCourierConfigFile, t as findConfigFile } from "../load-config-BeedbpkN.js";
5
+ import * as nodeModule from "node:module";
6
+ import { createRequire, syncBuiltinESMExports } from "node:module";
7
+ import { basename, dirname, isAbsolute, resolve, sep } from "node:path";
8
+ import { existsSync, readFileSync } from "node:fs";
9
+ import { fileURLToPath, pathToFileURL } from "node:url";
10
+ import { parseEnv } from "node:util";
11
+ import { MessageChannel } from "node:worker_threads";
12
+ // src/util/define-own.ts
13
+ /**
14
+ * Give a record a key of its own, whatever the key. Assigning `record[key]`
15
+ * with the key `__proto__` sets the record's prototype instead, and the key
16
+ * is lost: a source of that name went missing from the routes built of the
17
+ * config's sources.
18
+ *
19
+ * @param record - The record: an object literal, which no key but `__proto__` sets through a setter
20
+ * @param key - The key, as the config spells it
21
+ * @param value - Its value
22
+ * @example
23
+ * ```ts
24
+ * const routes: Record<string, CourierRoutes> = {};
25
+ * defineOwn(routes, "__proto__", legacyRoutes);
26
+ * Object.keys(routes); // → ["__proto__"]
27
+ * ```
28
+ */
29
+ function defineOwn(record, key, value) {
30
+ Object.defineProperty(record, key, {
31
+ value,
32
+ enumerable: true,
33
+ writable: true,
34
+ configurable: true
35
+ });
36
+ }
37
+ // src/engine/project-config.ts
38
+ /**
39
+ * The config a reading of the routes reads: the one the request names, or
40
+ * the nearest found from its root, up to the top of the project.
41
+ *
42
+ * @param request - Where the project is, and the config it names
43
+ * @returns The config, and where one was looked for first
44
+ * @throws RoutesReadError `CONFIG_NOT_FOUND` with no config up to the top of
45
+ * the project, and `CONFIG_NOT_TRUSTED` for one above the root that someone
46
+ * else could have put there; either names every path looked at
47
+ * @example
48
+ * ```ts
49
+ * projectConfigFile({ root: "/app/web", envFiles: [] });
50
+ * // → { configFile: "/app/courier.config.ts", lookedFor: ["/app/web/courier.config.ts", …] }
51
+ * ```
52
+ */
53
+ function projectConfigFile(request) {
54
+ if (request.configFile !== void 0) return {
55
+ configFile: request.configFile,
56
+ lookedFor: []
57
+ };
58
+ const candidates = configCandidates(request.root);
59
+ const unread = (code, message, configFile) => new RoutesReadError(code, message, {
60
+ root: request.root,
61
+ configFile
62
+ }, { inputs: {
63
+ files: [...candidates].sort(),
64
+ env: []
65
+ } });
66
+ let found;
67
+ try {
68
+ found = findConfigFile(request.root);
69
+ } catch (error) {
70
+ if (!(error instanceof UntrustedConfigError)) throw error;
71
+ throw unread("CONFIG_NOT_TRUSTED", error.message, error.context.configFile);
72
+ }
73
+ if (found === null) throw unread("CONFIG_NOT_FOUND", `No courier.config.* in ${request.root} or a folder above it, up to the top of the project: the routes a page takes are read from it. Name another file with the plugin's \`config\` option.`);
74
+ return {
75
+ configFile: found,
76
+ lookedFor: candidates.slice(0, candidates.indexOf(found))
77
+ };
78
+ }
79
+ /**
80
+ * Load the project's config with the project's transforms, kept in its
81
+ * `node_modules/.cache` for the next reading.
82
+ *
83
+ * @param configFile - The config, absolute
84
+ * @param request - Where the project is
85
+ * @param options - What runs around the config's code
86
+ * @returns The config, and its real path
87
+ * @throws RoutesReadError `CONFIG_NOT_LOADED` with what the loader or the config threw
88
+ * @example
89
+ * ```ts
90
+ * const loaded = await loadProjectConfig("/app/courier.config.ts", request, { onModule });
91
+ * ```
92
+ */
93
+ async function loadProjectConfig(configFile, request, options) {
94
+ try {
95
+ const transformCache = projectTransformCache(dirname(configFile));
96
+ return await loadCourierConfigFile(configFile, {
97
+ ...options,
98
+ transformCache
99
+ });
100
+ } catch (error) {
101
+ throw new RoutesReadError("CONFIG_NOT_LOADED", `${basename(configFile)} did not load, so the routes a page takes were not read: ${error instanceof Error ? error.message : String(error)}`, {
102
+ root: request.root,
103
+ configFile
104
+ }, { stack: error instanceof Error ? error.stack : void 0 });
105
+ }
106
+ }
107
+ // src/engine/read-env-files.ts
108
+ /**
109
+ * Put the variables a project's env files give into `process.env` before its
110
+ * config is read, as the framework's own server does: the first file to set
111
+ * a variable wins, and one the process was started with keeps its value.
112
+ * The bundler's own reading of them, when given, stands in for this one, so
113
+ * a value built of others with `${VAR}` is the one the app sees.
114
+ *
115
+ * @param files - The env files, absolute, the first winning; one that is not there is passed over
116
+ * @param given - The variables the bundler read from them, in place of reading them here
117
+ * @returns The files that were there
118
+ * @example
119
+ * ```ts
120
+ * readEnvFiles(["/app/.env.production.local", "/app/.env.production", "/app/.env.local", "/app/.env"]);
121
+ * ```
122
+ */
123
+ function readEnvFiles(files, given) {
124
+ const fromProcess = new Set(Object.keys(process.env));
125
+ const present = files.filter((file) => existsSync(file));
126
+ const values = given === void 0 ? Object.entries(parsedFrom(present)) : Object.entries(given);
127
+ for (const [key, value] of values) if (!fromProcess.has(key)) process.env[key] = value;
128
+ return present;
129
+ }
130
+ function parsedFrom(files) {
131
+ const parsed = {};
132
+ for (const file of files) for (const [key, value] of Object.entries(parseEnv(readFileSync(file, "utf8")))) if (value !== void 0 && !(key in parsed)) parsed[key] = value;
133
+ return parsed;
134
+ }
135
+ // src/util/local-path.ts
136
+ const NAMESPACE = "\\\\?\\";
137
+ const SHARE_NAMESPACE = "\\\\?\\UNC\\";
138
+ /**
139
+ * A path as this system names it: absolute, written with its own
140
+ * separator, `.` and `..` resolved, and on Windows out of the namespace
141
+ * `path.toNamespacedPath` writes it in (`\\?\D:\app` is `D:\app`). The code
142
+ * a reading runs may name one file in any of these ways; a watcher reports
143
+ * it in this one.
144
+ *
145
+ * @param path - The path, absolute or from the working folder
146
+ * @returns The path
147
+ * @example
148
+ * ```ts
149
+ * localPath("/app/src/../flags.json"); // "/app/flags.json"
150
+ * ```
151
+ */
152
+ function localPath(path) {
153
+ if (sep === "\\" && path.startsWith(SHARE_NAMESPACE)) return resolve(`\\\\${path.slice(8)}`);
154
+ if (sep === "\\" && path.startsWith(NAMESPACE)) return resolve(path.slice(4));
155
+ return resolve(path);
156
+ }
157
+ // src/engine/track-env-reads.ts
158
+ /**
159
+ * Note every environment variable the code that runs next looks at, by
160
+ * putting a watcher in front of `process.env`: a name read, tested for or
161
+ * described is noted, and listing them all notes them all.
162
+ *
163
+ * @param options - Whether to begin paused
164
+ * @returns What was looked at, asked once the code has run
165
+ * @example
166
+ * ```ts
167
+ * const reads = trackEnvReads();
168
+ * process.env.API_URL;
169
+ * reads.names(); // ["API_URL"]
170
+ * ```
171
+ */
172
+ function trackEnvReads({ paused: startPaused = false } = {}) {
173
+ const read = /* @__PURE__ */ new Set();
174
+ let all = false;
175
+ let waiting = startPaused;
176
+ let paused = waiting ? 1 : 0;
177
+ process.env = new Proxy(process.env, watcherOf({
178
+ read: (key) => {
179
+ if (paused === 0 && typeof key === "string") read.add(key);
180
+ },
181
+ listed: () => {
182
+ if (paused === 0) all = true;
183
+ }
184
+ }));
185
+ return {
186
+ names: () => all ? "*" : [...read].sort(),
187
+ untracked(run) {
188
+ paused += 1;
189
+ try {
190
+ return run();
191
+ } finally {
192
+ paused -= 1;
193
+ }
194
+ },
195
+ resume() {
196
+ if (waiting) paused -= 1;
197
+ waiting = false;
198
+ }
199
+ };
200
+ }
201
+ function watcherOf(looks) {
202
+ return {
203
+ get(target, key) {
204
+ looks.read(key);
205
+ return Reflect.get(target, key);
206
+ },
207
+ has(target, key) {
208
+ looks.read(key);
209
+ return Reflect.has(target, key);
210
+ },
211
+ getOwnPropertyDescriptor(target, key) {
212
+ looks.read(key);
213
+ return Reflect.getOwnPropertyDescriptor(target, key);
214
+ },
215
+ ownKeys(target) {
216
+ looks.listed();
217
+ return Reflect.ownKeys(target);
218
+ },
219
+ set: (target, key, value) => Reflect.set(target, key, value),
220
+ defineProperty: (target, key, descriptor) => Reflect.defineProperty(target, key, descriptor)
221
+ };
222
+ }
223
+ // src/engine/track-file-reads.ts
224
+ const require$1 = createRequire(import.meta.url);
225
+ const READS = [
226
+ "readFileSync",
227
+ "readFile",
228
+ "createReadStream",
229
+ "existsSync",
230
+ "statSync",
231
+ "lstatSync",
232
+ "stat",
233
+ "lstat",
234
+ "accessSync",
235
+ "access"
236
+ ];
237
+ const LISTS = [
238
+ "readdirSync",
239
+ "readdir",
240
+ "opendirSync",
241
+ "opendir"
242
+ ];
243
+ const PROMISED_READS = [
244
+ "readFile",
245
+ "stat",
246
+ "lstat",
247
+ "access"
248
+ ];
249
+ const PROMISED_LISTS = ["readdir", "opendir"];
250
+ /**
251
+ * Note every path the code that runs next reads or looks at through `fs`,
252
+ * and every folder it lists: a config that reads an OpenAPI document, or
253
+ * builds its sources from a folder's entries, depends on them as on the
254
+ * modules it imports. Each path is noted as the system names it, however
255
+ * the code wrote it. Installed packages are left out, as the install
256
+ * changes them, and so is anything `fs` is handed that names no path.
257
+ *
258
+ * @returns What was read, asked once the code has run
259
+ * @example
260
+ * ```ts
261
+ * const reads = trackFileReads();
262
+ * readFileSync("./openapi.json");
263
+ * reads.paths(); // ["/app/openapi.json"]
264
+ * ```
265
+ */
266
+ function trackFileReads() {
267
+ const paths = /* @__PURE__ */ new Set();
268
+ const folders = /* @__PURE__ */ new Set();
269
+ const fs = require$1("node:fs");
270
+ const promises = require$1("node:fs/promises");
271
+ if (isRecord(fs)) {
272
+ noteCalls(fs, READS, paths);
273
+ noteCalls(fs, LISTS, folders);
274
+ }
275
+ if (isRecord(promises)) {
276
+ noteCalls(promises, PROMISED_READS, paths);
277
+ noteCalls(promises, PROMISED_LISTS, folders);
278
+ }
279
+ syncBuiltinESMExports();
280
+ return {
281
+ paths: () => [...paths],
282
+ folders: () => [...folders]
283
+ };
284
+ }
285
+ function noteCalls(target, names, noted) {
286
+ for (const name of names) {
287
+ const original = target[name];
288
+ if (typeof original !== "function") continue;
289
+ target[name] = Object.assign(function(...args) {
290
+ const path = pathOf(args[0]);
291
+ if (path !== void 0 && !path.split(sep).includes("node_modules")) noted.add(path);
292
+ return Reflect.apply(original, this, args);
293
+ }, original);
294
+ }
295
+ }
296
+ function pathOf(value) {
297
+ if (typeof value === "string") return localPath(value);
298
+ if (value instanceof URL) return value.protocol === "file:" ? fileURLToPath(value) : void 0;
299
+ if (Buffer.isBuffer(value)) return pathOf(value.toString());
300
+ }
301
+ // src/engine/note-resolved-files.ts
302
+ const ASYNC_HOOKS = `let port;
303
+ export function initialize(data) {
304
+ port = data.port;
305
+ port.on("message", (message) => { if (message === "flush") port.postMessage("flushed"); });
306
+ port.unref();
307
+ }
308
+ export async function resolve(specifier, context, nextResolve) {
309
+ const resolved = await nextResolve(specifier, context);
310
+ if (resolved.url.startsWith("file:")) port.postMessage(resolved.url);
311
+ return resolved;
312
+ }
313
+ `;
314
+ /**
315
+ * Note the file of every ES module Node resolves from now on, into a set:
316
+ * with the synchronous hooks Node has from 22.15, as it resolves each one;
317
+ * before, with hooks on the loader's own thread, whose notes arrive once
318
+ * `settled()` resolves.
319
+ *
320
+ * @param noted - Where each file goes
321
+ * @param hooks - Which hooks: the synchronous ones wherever Node has them
322
+ * @returns How to wait for every note
323
+ * @example
324
+ * ```ts
325
+ * const resolved = noteResolvedFiles(files);
326
+ * await import(configUrl);
327
+ * await resolved.settled(); // files now holds every module the import resolved
328
+ * ```
329
+ */
330
+ function noteResolvedFiles(noted, hooks = typeof nodeModule.registerHooks === "function" ? "sync" : "async") {
331
+ if (hooks === "sync" && typeof nodeModule.registerHooks === "function") {
332
+ nodeModule.registerHooks({ resolve(specifier, context, nextResolve) {
333
+ const resolved = nextResolve(specifier, context);
334
+ if (resolved.url.startsWith("file:")) noted.add(fileURLToPath(resolved.url));
335
+ return resolved;
336
+ } });
337
+ return { settled: () => Promise.resolve() };
338
+ }
339
+ return noteOffThread(noted);
340
+ }
341
+ function noteOffThread(noted) {
342
+ const { port1, port2 } = new MessageChannel();
343
+ const flushed = [];
344
+ port1.on("message", (message) => {
345
+ if (message === "flushed") flushed.shift()?.();
346
+ else if (typeof message === "string") noted.add(fileURLToPath(message));
347
+ if (flushed.length === 0) port1.unref();
348
+ });
349
+ port1.unref();
350
+ nodeModule.register(`data:text/javascript,${encodeURIComponent(ASYNC_HOOKS)}`, {
351
+ data: { port: port2 },
352
+ transferList: [port2]
353
+ });
354
+ return { settled: () => new Promise((resolve) => {
355
+ flushed.push(resolve);
356
+ port1.ref();
357
+ port1.postMessage("flush");
358
+ }) };
359
+ }
360
+ // src/engine/track-module-files.ts
361
+ const require = createRequire(import.meta.url);
362
+ /**
363
+ * Note the file of every module the code that runs next loads: the ones
364
+ * the config's loader transforms are told by `noteFile`; the ones Node
365
+ * loads itself are read from its CommonJS cache and from each ES module it
366
+ * resolves.
367
+ *
368
+ * @param hooks - How the ES modules Node resolves are noted: synchronously wherever Node can
369
+ * @returns The files noted, and how to note one
370
+ * @example
371
+ * ```ts
372
+ * const modules = trackModuleFiles();
373
+ * await loadCourierConfigFile(file, { onModule: modules.noteFile });
374
+ * await modules.files(); // ["/app/courier.config.ts", "/app/src/api.ts", …]
375
+ * ```
376
+ */
377
+ function trackModuleFiles(hooks) {
378
+ const noted = /* @__PURE__ */ new Set();
379
+ const resolved = noteResolvedFiles(noted, hooks);
380
+ return {
381
+ noteFile: (file) => noted.add(file),
382
+ files: async () => {
383
+ await resolved.settled();
384
+ return [...noted, ...Object.keys(require.cache).filter((key) => isAbsolute(key))];
385
+ }
386
+ };
387
+ }
388
+ // src/engine/track-reading.ts
389
+ /**
390
+ * Note what one reading of the routes reads, from now on: the modules Node
391
+ * and the config's loader load, the files read and folders listed once the
392
+ * config is found, and the environment variables the config's code looks at
393
+ * once it runs, besides the config and the env files.
394
+ *
395
+ * @param request - The reading's request, whose env files are read before it
396
+ * @returns The tracker
397
+ * @example
398
+ * ```ts
399
+ * const reading = trackReading(request);
400
+ * await loadProjectConfig(configFile, request, reading.loadOptions);
401
+ * await reading.inputs(); // { configFile, files: [...], env: ["API_URL"] }
402
+ * ```
403
+ */
404
+ function trackReading(request) {
405
+ const modules = trackModuleFiles();
406
+ const env = trackEnvReads({ paused: true });
407
+ let configFile;
408
+ let reads;
409
+ const looked = /* @__PURE__ */ new Set();
410
+ return {
411
+ loadOptions: {
412
+ onModule: modules.noteFile,
413
+ beforeRun: env.resume,
414
+ runTransform: env.untracked
415
+ },
416
+ noteConfig(file, lookedFor) {
417
+ configFile = file;
418
+ reads ??= trackFileReads();
419
+ looked.add(file);
420
+ for (const path of lookedFor) looked.add(path);
421
+ },
422
+ inputs: async () => {
423
+ const files = [
424
+ ...looked,
425
+ ...request.envFiles,
426
+ ...await modules.files()
427
+ ];
428
+ return {
429
+ configFile,
430
+ files: [...new Set([...files, ...reads?.paths() ?? []].map(localPath))].sort(),
431
+ listed: [...reads?.folders() ?? []].sort(),
432
+ env: env.names()
433
+ };
434
+ }
435
+ };
436
+ }
437
+ // src/engine/read-project-routes.ts
438
+ /**
439
+ * Read a project's routes from its config: its env files first, then the
440
+ * config, every module it reaches transformed once and kept for the next
441
+ * process, then each source's routes by the rules the proxy applies. Run in
442
+ * a process of its own, which it leaves changed: `process.env` is watched,
443
+ * and Node's resolution hooked.
444
+ *
445
+ * @param request - Where the project is, and which env files it reads
446
+ * @returns The routes, and every file and variable they depended on
447
+ * @throws RoutesReadError `CONFIG_NOT_FOUND` with no config to read,
448
+ * `CONFIG_NOT_TRUSTED` for one someone else could have put above the root,
449
+ * and `CONFIG_NOT_LOADED` for one that would not load or holds no
450
+ * `sources`; each names what the reading had read, which may mend it
451
+ * @example
452
+ * ```ts
453
+ * const { routes } = await readProjectRoutes({ root: "/app", envFiles: ["/app/.env"] });
454
+ * ```
455
+ */
456
+ async function readProjectRoutes(request) {
457
+ readEnvFiles(request.envFiles, request.env);
458
+ const reading = trackReading(request);
459
+ try {
460
+ const { configFile, lookedFor } = projectConfigFile(request);
461
+ reading.noteConfig(configFile, lookedFor);
462
+ const loaded = await loadProjectConfig(configFile, request, reading.loadOptions);
463
+ reading.noteConfig(loaded.configFile, lookedFor);
464
+ const routes = await routesOfSources(loaded, request.root);
465
+ return {
466
+ ...await reading.inputs(),
467
+ configFile: loaded.configFile,
468
+ routes
469
+ };
470
+ } catch (error) {
471
+ throw withInputs(error, await reading.inputs(), request);
472
+ }
473
+ }
474
+ async function routesOfSources(loaded, root) {
475
+ const sources = sourcesOf(loaded, root);
476
+ const read = await projectRoutesOf(loaded.configFile, root);
477
+ const routes = {};
478
+ for (const name of Object.keys(sources).sort(byCodeUnits)) defineOwn(routes, name, read(sources[name], name));
479
+ return routes;
480
+ }
481
+ function sourcesOf(loaded, root) {
482
+ const sources = isRecord(loaded.config) ? loaded.config.sources : void 0;
483
+ if (isRecord(sources)) return sources;
484
+ throw new RoutesReadError("CONFIG_NOT_LOADED", `${basename(loaded.configFile)} exports no \`sources\`: the routes a page takes are read from the APIs it names there.`, {
485
+ root,
486
+ configFile: loaded.configFile
487
+ });
488
+ }
489
+ async function projectRoutesOf(configFile, root) {
490
+ const path = createRequire(configFile).resolve("@noverax/courier/internal");
491
+ const core = await import(pathToFileURL(path).href);
492
+ if (typeof core.routesOf === "function") return core.routesOf;
493
+ throw new RoutesReadError("CONFIG_NOT_LOADED", `The @noverax/courier at ${path} reads no routes: update it to the version of @noverax/courier-build.`, {
494
+ root,
495
+ configFile
496
+ });
497
+ }
498
+ function withInputs(error, inputs, request) {
499
+ const stack = error instanceof Error ? error.stack : void 0;
500
+ if (!(error instanceof RoutesReadError)) return new RoutesReadError("CONFIG_NOT_LOADED", `The routes a page takes were not read: ${error instanceof Error ? error.message : String(error)}`, {
501
+ root: request.root,
502
+ configFile: inputs.configFile
503
+ }, {
504
+ stack,
505
+ inputs
506
+ });
507
+ if (error.inputs !== void 0) return error;
508
+ return new RoutesReadError(error.code, error.message, error.context, {
509
+ stack,
510
+ inputs
511
+ });
512
+ }
513
+ function byCodeUnits(a, b) {
514
+ return a < b ? -1 : a > b ? 1 : 0;
515
+ }
516
+ // src/engine/worker.ts
517
+ process.on("message", take);
518
+ process.once("disconnect", () => process.exit(1));
519
+ function take(message) {
520
+ if (!workerMessages.isRequest(message)) return;
521
+ process.off("message", take);
522
+ answer(message.request);
523
+ }
524
+ async function answer(request) {
525
+ const reply = await readProjectRoutes(request).then((snapshot) => ({
526
+ ok: true,
527
+ snapshot
528
+ }), (error) => failure(error, request));
529
+ process.send?.(workerMessages.reply(reply), () => process.exit(0));
530
+ }
531
+ function failure(error, request) {
532
+ if (error instanceof RoutesReadError) return {
533
+ ok: false,
534
+ code: error.code,
535
+ message: error.message,
536
+ context: error.context,
537
+ stack: error.stack,
538
+ inputs: error.inputs
539
+ };
540
+ return {
541
+ ok: false,
542
+ code: "CONFIG_NOT_LOADED",
543
+ message: `The routes a page takes were not read: ${error instanceof Error ? error.message : String(error)}`,
544
+ context: {
545
+ root: request.root,
546
+ configFile: request.configFile
547
+ },
548
+ stack: error instanceof Error ? error.stack : void 0
549
+ };
550
+ }
551
+ export {};
@@ -0,0 +1,32 @@
1
+ // src/util/is-record.ts
2
+ /**
3
+ * Whether a value is an object read by its keys: not null, not an array.
4
+ *
5
+ * @param value - The value to check
6
+ * @returns Whether it can be read as a record of fields
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * isRecord({ sources: {} }); // true
11
+ * isRecord(["shop"]); // false
12
+ * ```
13
+ */
14
+ function isRecord(value) {
15
+ return typeof value === "object" && value !== null && !Array.isArray(value);
16
+ }
17
+ // src/config/errno-code.ts
18
+ /**
19
+ * The `code` a Node system error carries, such as `ENOENT` or `EACCES`.
20
+ *
21
+ * @param error - Anything thrown
22
+ * @returns The code, or `undefined` when the value carries none
23
+ * @example
24
+ * ```ts
25
+ * if (errnoCode(err) === "ENOENT") return null;
26
+ * ```
27
+ */
28
+ function errnoCode(error) {
29
+ if (typeof error !== "object" || error === null || !("code" in error)) return void 0;
30
+ return typeof error.code === "string" ? error.code : void 0;
31
+ }
32
+ export { isRecord as n, errnoCode as t };
@@ -0,0 +1,58 @@
1
+ import { t as CourierRoutesOptions } from "../courier-routes-options-BYF7Sfdf.js";
2
+ import { Plugin } from "esbuild";
3
+ /**
4
+ * The esbuild plugin that builds the routes a page's proxy client may call
5
+ * from `courier.config.ts`, in place of the module `@noverax/courier/routes`
6
+ * ships. No routes file is written, and the routes carry no address, path or
7
+ * middleware. A page's bundle — any `platform` but `node` — that takes the
8
+ * config itself fails.
9
+ *
10
+ * A build that cannot read the config fails with why. A context that builds
11
+ * again, under `watch()` or `serve()`, follows every file the routes came
12
+ * from; when they cannot be read on a later build, it keeps the routes it
13
+ * read last and warns why.
14
+ *
15
+ * @param options - Where the config is, when it is not found from `absWorkingDir`
16
+ * @returns The plugin
17
+ * @example
18
+ * ```ts
19
+ * import { build } from "esbuild";
20
+ * import { courierRoutes } from "@noverax/courier-build/esbuild";
21
+ *
22
+ * await build({ entryPoints: ["src/main.ts"], bundle: true, plugins: [courierRoutes()] });
23
+ * ```
24
+ */
25
+ export declare function courierRoutes(options?: CourierRoutesOptions): Plugin;
26
+ /** Options of the esbuild plugin that takes the routes from a module written to disk. */
27
+ interface WrittenRoutesOptions extends CourierRoutesOptions {
28
+ /** The project's root, absolute: the config is found from it. */
29
+ readonly root: string;
30
+ /** The module the routes are written to, absolute: the host must follow it as a file it reads. */
31
+ readonly file: string;
32
+ /** Whether the host builds again on a change: then the routes read last stay while the config is broken. */
33
+ readonly watch: boolean;
34
+ /** How the app names another place for the module, should that one not take it: said in the error. */
35
+ readonly elsewhere: string;
36
+ }
37
+ /**
38
+ * The esbuild plugin for a host that follows only the files its build read
39
+ * from disk, as Angular's builders do, not those a plugin names: the
40
+ * routes are written to a module there, which `@noverax/courier/routes`
41
+ * resolves to, and watchers of what they came from rewrite it on a change,
42
+ * so the host builds it again. A page's bundle — any `platform` but `node` —
43
+ * that takes the config itself fails.
44
+ *
45
+ * A build fails while the routes cannot be read, with why; a host that
46
+ * watches keeps the routes read last, and with none read yet the module
47
+ * fails where it is imported, saying why.
48
+ *
49
+ * @param options - Where the project, its config and the module are, and whether the host watches
50
+ * @returns The plugin, once the routes are written
51
+ * @throws RoutesReadError `ROUTES_NOT_WRITTEN` when the module cannot be written
52
+ * @example
53
+ * ```ts
54
+ * const plugin = await writtenRoutesPlugin({ root, file: join(dir, "routes.js"), watch: false, elsewhere: "TMPDIR" });
55
+ * ```
56
+ */
57
+ export declare function writtenRoutesPlugin(options: WrittenRoutesOptions): Promise<Plugin>;
58
+ export type { CourierRoutesOptions, WrittenRoutesOptions };