@noverax/courier-build 0.0.0-stage → 0.5.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 (52) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +295 -2
  3. package/lib/config/index.d.ts +811 -0
  4. package/lib/config/index.js +75 -0
  5. package/lib/config-file-of-Clf_dWUQ.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 +548 -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-barNPfXN.js +2834 -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/read-limits-DUuUaEQM.js +41 -0
  28. package/lib/rollup/index.d.ts +51 -0
  29. package/lib/rollup/index.js +95 -0
  30. package/lib/routes-engine-BfwrCuNL.d.ts +117 -0
  31. package/lib/routes-file-DRoqUZ0S.js +206 -0
  32. package/lib/routes-loader-context-40OKDrQp.d.ts +26 -0
  33. package/lib/routes-module-code-DYtP7uj1.js +405 -0
  34. package/lib/routes-module-id-CnUjQjYF.js +12 -0
  35. package/lib/routes-read-error-DQhKIE2F.js +30 -0
  36. package/lib/routes-rule-Pu1Tz8Xm.js +108 -0
  37. package/lib/rsbuild/index.d.ts +41 -0
  38. package/lib/rsbuild/index.js +37 -0
  39. package/lib/rspack/index.d.ts +3 -0
  40. package/lib/rspack/index.js +1 -0
  41. package/lib/shared-engine-D0j7uXg8.js +28 -0
  42. package/lib/vite/index.d.ts +33 -0
  43. package/lib/vite/index.js +357 -0
  44. package/lib/webpack/index.d.ts +4 -0
  45. package/lib/webpack/index.js +3 -0
  46. package/lib/webpack/loader.cjs +31 -0
  47. package/lib/webpack/loader.d.cts +40 -0
  48. package/lib/webpack/run-routes-loader.d.ts +22 -0
  49. package/lib/webpack/run-routes-loader.js +103 -0
  50. package/lib/webpack-BPxhBYGV.js +42 -0
  51. package/lib/worker-messages-BT2vvNQJ.js +35 -0
  52. package/package.json +209 -4
@@ -0,0 +1,548 @@
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 { T as configCandidates, a as projectTransformCache, c as UntrustedConfigError, d as configLoadError, h as localPath, i as loadCourierConfigFile, p as maskedError, t as findConfigFile } from "../load-config-barNPfXN.js";
5
+ import * as nodeModule from "node:module";
6
+ import { createRequire, syncBuiltinESMExports } from "node:module";
7
+ import { basename, dirname, isAbsolute, 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
+ const failure = configLoadError(error, { entry: configFile });
102
+ const message = `${basename(configFile)} did not load, so the routes a page takes were not read: ${failure.message} ${failure.hint}`;
103
+ throw new RoutesReadError("CONFIG_NOT_LOADED", message, {
104
+ root: request.root,
105
+ configFile
106
+ }, { stack: failure.cause instanceof Error ? failure.cause.stack : void 0 });
107
+ }
108
+ }
109
+ // src/engine/read-env-files.ts
110
+ /**
111
+ * Put the variables a project's env files give into `process.env` before its
112
+ * config is read, as the framework's own server does: the first file to set
113
+ * a variable wins, and one the process was started with keeps its value.
114
+ * The bundler's own reading of them, when given, stands in for this one, so
115
+ * a value built of others with `${VAR}` is the one the app sees.
116
+ *
117
+ * @param files - The env files, absolute, the first winning; one that is not there is passed over
118
+ * @param given - The variables the bundler read from them, in place of reading them here
119
+ * @returns The files that were there
120
+ * @example
121
+ * ```ts
122
+ * readEnvFiles(["/app/.env.production.local", "/app/.env.production", "/app/.env.local", "/app/.env"]);
123
+ * ```
124
+ */
125
+ function readEnvFiles(files, given) {
126
+ const fromProcess = new Set(Object.keys(process.env));
127
+ const present = files.filter((file) => existsSync(file));
128
+ const values = given === void 0 ? Object.entries(parsedFrom(present)) : Object.entries(given);
129
+ for (const [key, value] of values) if (!fromProcess.has(key)) process.env[key] = value;
130
+ return present;
131
+ }
132
+ function parsedFrom(files) {
133
+ const parsed = {};
134
+ 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;
135
+ return parsed;
136
+ }
137
+ // src/engine/reading-failure.ts
138
+ /**
139
+ * Why a reading of the routes failed, as a `RoutesReadError` whatever it
140
+ * threw. The engine's own failure is kept as it is, its text the engine's
141
+ * words and what the loader quoted of the project, masked already. Anything
142
+ * else came from the project's code, a getter of its config or a source the
143
+ * core read, and is `CONFIG_NOT_LOADED`, its text and stack with no secret
144
+ * left in them: a bundler prints them, shows them on the page, and a CI
145
+ * log, which may be public, keeps them.
146
+ *
147
+ * @param thrown - What the reading threw
148
+ * @param context - The project's root, and the config file once found
149
+ * @param inputs - What the reading had read, for a failure that names none yet
150
+ * @returns The failure
151
+ * @example
152
+ * ```ts
153
+ * readingFailure(new Error(`bad ${process.env.API_KEY}`), { root: "/app" }).message;
154
+ * // → "The routes a page takes were not read: bad [redacted]"
155
+ * ```
156
+ */
157
+ function readingFailure(thrown, context, inputs) {
158
+ if (thrown instanceof RoutesReadError) {
159
+ if (thrown.inputs !== void 0 || inputs === void 0) return thrown;
160
+ return new RoutesReadError(thrown.code, thrown.message, thrown.context, {
161
+ stack: thrown.stack,
162
+ inputs
163
+ });
164
+ }
165
+ const masked = maskedError(thrown);
166
+ return new RoutesReadError("CONFIG_NOT_LOADED", `The routes a page takes were not read: ${masked.message}`, context, {
167
+ stack: thrown instanceof Error ? masked.stack : void 0,
168
+ inputs
169
+ });
170
+ }
171
+ // src/engine/track-env-reads.ts
172
+ /**
173
+ * Note every environment variable the code that runs next looks at, by
174
+ * putting a watcher in front of `process.env`: a name read, tested for or
175
+ * described is noted, and listing them all notes them all.
176
+ *
177
+ * @param options - Whether to begin paused
178
+ * @returns What was looked at, asked once the code has run
179
+ * @example
180
+ * ```ts
181
+ * const reads = trackEnvReads();
182
+ * process.env.API_URL;
183
+ * reads.names(); // ["API_URL"]
184
+ * ```
185
+ */
186
+ function trackEnvReads({ paused: startPaused = false } = {}) {
187
+ const read = /* @__PURE__ */ new Set();
188
+ let all = false;
189
+ let waiting = startPaused;
190
+ let paused = waiting ? 1 : 0;
191
+ process.env = new Proxy(process.env, watcherOf({
192
+ read: (key) => {
193
+ if (paused === 0 && typeof key === "string") read.add(key);
194
+ },
195
+ listed: () => {
196
+ if (paused === 0) all = true;
197
+ }
198
+ }));
199
+ return {
200
+ names: () => all ? "*" : [...read].sort(),
201
+ untracked(run) {
202
+ paused += 1;
203
+ try {
204
+ return run();
205
+ } finally {
206
+ paused -= 1;
207
+ }
208
+ },
209
+ resume() {
210
+ if (waiting) paused -= 1;
211
+ waiting = false;
212
+ }
213
+ };
214
+ }
215
+ function watcherOf(looks) {
216
+ return {
217
+ get(target, key) {
218
+ looks.read(key);
219
+ return Reflect.get(target, key);
220
+ },
221
+ has(target, key) {
222
+ looks.read(key);
223
+ return Reflect.has(target, key);
224
+ },
225
+ getOwnPropertyDescriptor(target, key) {
226
+ looks.read(key);
227
+ return Reflect.getOwnPropertyDescriptor(target, key);
228
+ },
229
+ ownKeys(target) {
230
+ looks.listed();
231
+ return Reflect.ownKeys(target);
232
+ },
233
+ set: (target, key, value) => Reflect.set(target, key, value),
234
+ defineProperty: (target, key, descriptor) => Reflect.defineProperty(target, key, descriptor)
235
+ };
236
+ }
237
+ // src/engine/track-file-reads.ts
238
+ const require$1 = createRequire(import.meta.url);
239
+ const READS = [
240
+ "readFileSync",
241
+ "readFile",
242
+ "createReadStream",
243
+ "existsSync",
244
+ "statSync",
245
+ "lstatSync",
246
+ "stat",
247
+ "lstat",
248
+ "accessSync",
249
+ "access"
250
+ ];
251
+ const LISTS = [
252
+ "readdirSync",
253
+ "readdir",
254
+ "opendirSync",
255
+ "opendir"
256
+ ];
257
+ const PROMISED_READS = [
258
+ "readFile",
259
+ "stat",
260
+ "lstat",
261
+ "access"
262
+ ];
263
+ const PROMISED_LISTS = ["readdir", "opendir"];
264
+ /**
265
+ * Note every path the code that runs next reads or looks at through `fs`,
266
+ * and every folder it lists: a config that reads an OpenAPI document, or
267
+ * builds its sources from a folder's entries, depends on them as on the
268
+ * modules it imports. Each path is noted as the system names it, however
269
+ * the code wrote it. Installed packages are left out, as the install
270
+ * changes them, and so is anything `fs` is handed that names no path.
271
+ *
272
+ * @returns What was read, asked once the code has run
273
+ * @example
274
+ * ```ts
275
+ * const reads = trackFileReads();
276
+ * readFileSync("./openapi.json");
277
+ * reads.paths(); // ["/app/openapi.json"]
278
+ * ```
279
+ */
280
+ function trackFileReads() {
281
+ const paths = /* @__PURE__ */ new Set();
282
+ const folders = /* @__PURE__ */ new Set();
283
+ const fs = require$1("node:fs");
284
+ const promises = require$1("node:fs/promises");
285
+ if (isRecord(fs)) {
286
+ noteCalls(fs, READS, paths);
287
+ noteCalls(fs, LISTS, folders);
288
+ }
289
+ if (isRecord(promises)) {
290
+ noteCalls(promises, PROMISED_READS, paths);
291
+ noteCalls(promises, PROMISED_LISTS, folders);
292
+ }
293
+ syncBuiltinESMExports();
294
+ return {
295
+ paths: () => [...paths],
296
+ folders: () => [...folders]
297
+ };
298
+ }
299
+ function noteCalls(target, names, noted) {
300
+ for (const name of names) {
301
+ const original = target[name];
302
+ if (typeof original !== "function") continue;
303
+ target[name] = Object.assign(function(...args) {
304
+ const path = pathOf(args[0]);
305
+ if (path !== void 0 && !path.split(sep).includes("node_modules")) noted.add(path);
306
+ return Reflect.apply(original, this, args);
307
+ }, original);
308
+ }
309
+ }
310
+ function pathOf(value) {
311
+ if (typeof value === "string") return localPath(value);
312
+ if (value instanceof URL) return value.protocol === "file:" ? fileURLToPath(value) : void 0;
313
+ if (Buffer.isBuffer(value)) return pathOf(value.toString());
314
+ }
315
+ // src/engine/note-resolved-files.ts
316
+ const ASYNC_HOOKS = `let port;
317
+ export function initialize(data) {
318
+ port = data.port;
319
+ port.on("message", (message) => { if (message === "flush") port.postMessage("flushed"); });
320
+ port.unref();
321
+ }
322
+ export async function resolve(specifier, context, nextResolve) {
323
+ const resolved = await nextResolve(specifier, context);
324
+ if (resolved.url.startsWith("file:")) port.postMessage(resolved.url);
325
+ return resolved;
326
+ }
327
+ `;
328
+ /**
329
+ * Note the file of every ES module Node resolves from now on, into a set:
330
+ * with the synchronous hooks Node has from 22.15, as it resolves each one;
331
+ * before, with hooks on the loader's own thread, whose notes arrive once
332
+ * `settled()` resolves.
333
+ *
334
+ * @param noted - Where each file goes
335
+ * @param hooks - Which hooks: the synchronous ones wherever Node has them
336
+ * @returns How to wait for every note
337
+ * @example
338
+ * ```ts
339
+ * const resolved = noteResolvedFiles(files);
340
+ * await import(configUrl);
341
+ * await resolved.settled(); // files now holds every module the import resolved
342
+ * ```
343
+ */
344
+ function noteResolvedFiles(noted, hooks = typeof nodeModule.registerHooks === "function" ? "sync" : "async") {
345
+ if (hooks === "sync" && typeof nodeModule.registerHooks === "function") {
346
+ nodeModule.registerHooks({ resolve(specifier, context, nextResolve) {
347
+ const resolved = nextResolve(specifier, context);
348
+ if (resolved.url.startsWith("file:")) noted.add(fileURLToPath(resolved.url));
349
+ return resolved;
350
+ } });
351
+ return { settled: () => Promise.resolve() };
352
+ }
353
+ return noteOffThread(noted);
354
+ }
355
+ function noteOffThread(noted) {
356
+ const { port1, port2 } = new MessageChannel();
357
+ const flushed = [];
358
+ port1.on("message", (message) => {
359
+ if (message === "flushed") flushed.shift()?.();
360
+ else if (typeof message === "string") noted.add(fileURLToPath(message));
361
+ if (flushed.length === 0) port1.unref();
362
+ });
363
+ port1.unref();
364
+ nodeModule.register(`data:text/javascript,${encodeURIComponent(ASYNC_HOOKS)}`, {
365
+ data: { port: port2 },
366
+ transferList: [port2]
367
+ });
368
+ return { settled: () => new Promise((resolve) => {
369
+ flushed.push(resolve);
370
+ port1.ref();
371
+ port1.postMessage("flush");
372
+ }) };
373
+ }
374
+ // src/engine/track-module-files.ts
375
+ const require = createRequire(import.meta.url);
376
+ /**
377
+ * Note the file of every module the code that runs next loads: the ones
378
+ * the config's loader transforms are told by `noteFile`; the ones Node
379
+ * loads itself are read from its CommonJS cache and from each ES module it
380
+ * resolves.
381
+ *
382
+ * @param hooks - How the ES modules Node resolves are noted: synchronously wherever Node can
383
+ * @returns The files noted, and how to note one
384
+ * @example
385
+ * ```ts
386
+ * const modules = trackModuleFiles();
387
+ * await loadCourierConfigFile(file, { onModule: modules.noteFile });
388
+ * await modules.files(); // ["/app/courier.config.ts", "/app/src/api.ts", …]
389
+ * ```
390
+ */
391
+ function trackModuleFiles(hooks) {
392
+ const noted = /* @__PURE__ */ new Set();
393
+ const resolved = noteResolvedFiles(noted, hooks);
394
+ return {
395
+ noteFile: (file) => noted.add(file),
396
+ files: async () => {
397
+ await resolved.settled();
398
+ return [...noted, ...Object.keys(require.cache).filter((key) => isAbsolute(key))];
399
+ }
400
+ };
401
+ }
402
+ // src/engine/track-reading.ts
403
+ /**
404
+ * Note what one reading of the routes reads, from now on: the modules Node
405
+ * and the config's loader load, the files read and folders listed once the
406
+ * config is found, and the environment variables the config's code looks at
407
+ * once it runs, besides the config and the env files.
408
+ *
409
+ * @param request - The reading's request, whose env files are read before it
410
+ * @returns The tracker
411
+ * @example
412
+ * ```ts
413
+ * const reading = trackReading(request);
414
+ * await loadProjectConfig(configFile, request, reading.loadOptions);
415
+ * await reading.inputs(); // { configFile, files: [...], env: ["API_URL"] }
416
+ * ```
417
+ */
418
+ function trackReading(request) {
419
+ const modules = trackModuleFiles();
420
+ const env = trackEnvReads({ paused: true });
421
+ let configFile;
422
+ let reads;
423
+ const looked = /* @__PURE__ */ new Set();
424
+ return {
425
+ loadOptions: {
426
+ onModule: modules.noteFile,
427
+ beforeRun: env.resume,
428
+ runTransform: env.untracked
429
+ },
430
+ noteConfig(file, lookedFor) {
431
+ configFile = file;
432
+ reads ??= trackFileReads();
433
+ looked.add(file);
434
+ for (const path of lookedFor) looked.add(path);
435
+ },
436
+ inputs: async () => {
437
+ const files = [
438
+ ...looked,
439
+ ...request.envFiles,
440
+ ...await modules.files()
441
+ ];
442
+ return {
443
+ configFile,
444
+ files: [...new Set([...files, ...reads?.paths() ?? []].map(localPath))].sort(),
445
+ listed: [...reads?.folders() ?? []].sort(),
446
+ env: env.names()
447
+ };
448
+ }
449
+ };
450
+ }
451
+ // src/engine/read-project-routes.ts
452
+ /**
453
+ * Read a project's routes from its config: its env files first, then the
454
+ * config, every module it reaches transformed once and kept for the next
455
+ * process, then each source's routes by the rules the proxy applies. Run in
456
+ * a process of its own, which it leaves changed: `process.env` is watched,
457
+ * and Node's resolution hooked.
458
+ *
459
+ * @param request - Where the project is, and which env files it reads
460
+ * @returns The routes, and every file and variable they depended on
461
+ * @throws RoutesReadError `CONFIG_NOT_FOUND` with no config to read,
462
+ * `CONFIG_NOT_TRUSTED` for one someone else could have put above the root,
463
+ * and `CONFIG_NOT_LOADED` for one that would not load or holds no
464
+ * `sources`; each names what the reading had read, which may mend it
465
+ * @example
466
+ * ```ts
467
+ * const { routes } = await readProjectRoutes({ root: "/app", envFiles: ["/app/.env"] });
468
+ * ```
469
+ */
470
+ async function readProjectRoutes(request) {
471
+ readEnvFiles(request.envFiles, request.env);
472
+ const reading = trackReading(request);
473
+ try {
474
+ const { configFile, lookedFor } = projectConfigFile(request);
475
+ reading.noteConfig(configFile, lookedFor);
476
+ const loaded = await loadProjectConfig(configFile, request, reading.loadOptions);
477
+ reading.noteConfig(loaded.configFile, lookedFor);
478
+ const routes = await routesOfSources(loaded, request.root);
479
+ return {
480
+ ...await reading.inputs(),
481
+ configFile: loaded.configFile,
482
+ routes
483
+ };
484
+ } catch (error) {
485
+ const inputs = await reading.inputs();
486
+ throw readingFailure(error, {
487
+ root: request.root,
488
+ configFile: inputs.configFile
489
+ }, inputs);
490
+ }
491
+ }
492
+ async function routesOfSources(loaded, root) {
493
+ const sources = sourcesOf(loaded, root);
494
+ const read = await projectRoutesOf(loaded.configFile, root);
495
+ const routes = {};
496
+ for (const name of Object.keys(sources).sort(byCodeUnits)) defineOwn(routes, name, read(sources[name], name));
497
+ return routes;
498
+ }
499
+ function sourcesOf(loaded, root) {
500
+ const sources = isRecord(loaded.config) ? loaded.config.sources : void 0;
501
+ if (isRecord(sources)) return sources;
502
+ 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.`, {
503
+ root,
504
+ configFile: loaded.configFile
505
+ });
506
+ }
507
+ async function projectRoutesOf(configFile, root) {
508
+ const path = createRequire(configFile).resolve("@noverax/courier/internal");
509
+ const core = await import(pathToFileURL(path).href);
510
+ if (typeof core.routesOf === "function") return core.routesOf;
511
+ throw new RoutesReadError("CONFIG_NOT_LOADED", `The @noverax/courier at ${path} reads no routes: update it to the version of @noverax/courier-build.`, {
512
+ root,
513
+ configFile
514
+ });
515
+ }
516
+ function byCodeUnits(a, b) {
517
+ return a < b ? -1 : a > b ? 1 : 0;
518
+ }
519
+ // src/engine/worker.ts
520
+ process.on("message", take);
521
+ process.once("disconnect", () => process.exit(1));
522
+ function take(message) {
523
+ if (!workerMessages.isRequest(message)) return;
524
+ process.off("message", take);
525
+ answer(message.request);
526
+ }
527
+ async function answer(request) {
528
+ const reply = await readProjectRoutes(request).then((snapshot) => ({
529
+ ok: true,
530
+ snapshot
531
+ }), (error) => failure(error, request));
532
+ process.send?.(workerMessages.reply(reply), () => process.exit(0));
533
+ }
534
+ function failure(error, request) {
535
+ const failed = readingFailure(error, {
536
+ root: request.root,
537
+ configFile: request.configFile
538
+ });
539
+ return {
540
+ ok: false,
541
+ code: failed.code,
542
+ message: failed.message,
543
+ context: failed.context,
544
+ stack: failed.stack,
545
+ inputs: failed.inputs
546
+ };
547
+ }
548
+ 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 };