@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,811 @@
1
+ import { CourierError } from "@noverax/courier";
2
+ import { Jiti, ModuleCache, TransformOptions, TransformResult } from "jiti";
3
+ import { CourierConfig } from "@noverax/courier/config";
4
+ /** The file names a Courier config may have, in the order they are looked for */
5
+ export declare const CONFIG_BASENAMES: ReadonlyArray<string>;
6
+ /**
7
+ * Every path a config is looked for at from a folder, in the order it is
8
+ * looked for: each of {@link CONFIG_BASENAMES} in the folder, then in each
9
+ * folder above it, up to the top of the project — the first folder that
10
+ * holds its repository (`.git`, `.hg`, `.jj`) or is the root of its
11
+ * workspace (`pnpm-workspace.yaml`, or a `package.json` that declares
12
+ * `workspaces`) — or, with no such folder, the root of the file system.
13
+ *
14
+ * @param startDir - The folder to look in first
15
+ * @returns The paths, absolute, whether there or not
16
+ * @example
17
+ * ```ts
18
+ * configCandidates("/repo/apps/web"); // → ["/repo/apps/web/courier.config.ts", …, "/repo/courier.config.cjs"]
19
+ * ```
20
+ */
21
+ export declare function configCandidates(startDir: string): string[];
22
+ /** The frameworks Courier tells apart. */
23
+ type FrameworkId = "next" | "nuxt" | "sveltekit" | "astro" | "react-router" | "remix" | "solid-start" | "expo" | "nestjs" | "fastify" | "hono" | "express" | "koa" | "hapi" | "elysia" | "h3" | "nitro" | "react" | "vue";
24
+ /** A framework, and the packages a project that uses it lists. */
25
+ interface FrameworkMarker {
26
+ readonly id: FrameworkId;
27
+ /** The framework as its users name it. */
28
+ readonly label: string;
29
+ /** The package whose presence tells the framework, and whose version is the framework's. */
30
+ readonly marker: string;
31
+ /** Another package the project must list too. */
32
+ readonly also?: string;
33
+ /**
34
+ * Whether the marker tells the framework first as a dependency the app
35
+ * runs with: Courier's setup installs Hono for Studio's mock as a dev
36
+ * dependency, in an app built with another framework. Listed only among
37
+ * the dev dependencies, it still tells the framework where no other is
38
+ * listed, as for a worker a bundler builds.
39
+ */
40
+ readonly runsWith?: boolean;
41
+ /**
42
+ * Whether it runs a server alone, which a package may list beside the
43
+ * framework its page is built with: such a package is told by its
44
+ * server, which comes before the page's in the list, and is not a
45
+ * page's alone. The same servers as the upgrade's, in create-courier.
46
+ */
47
+ readonly server?: boolean;
48
+ }
49
+ /** The frameworks, the ones built on others first: a Next.js app lists React too; a server before a page Vite builds. */
50
+ export declare const FRAMEWORK_MARKERS: readonly FrameworkMarker[];
51
+ /** The packages a package.json lists, by the field it lists them in. */
52
+ interface ListedPackages {
53
+ readonly dependencies: Readonly<Record<string, unknown>>;
54
+ readonly devDependencies: Readonly<Record<string, unknown>>;
55
+ readonly peerDependencies: Readonly<Record<string, unknown>>;
56
+ readonly optionalDependencies: Readonly<Record<string, unknown>>;
57
+ }
58
+ /** The framework a project is built with, and the package.json that says so. */
59
+ interface ProjectFramework {
60
+ /** The framework, or `undefined` when the package.json lists none Courier tells. */
61
+ readonly framework: FrameworkMarker | undefined;
62
+ /** The package.json read, absolute; `undefined` when there is none up to the root of the disk. */
63
+ readonly manifest: string | undefined;
64
+ }
65
+ /**
66
+ * The framework a project is built with, told by the packages it lists: a
67
+ * framework told first by what the app runs with counts among the dev
68
+ * dependencies only where no other framework is listed.
69
+ *
70
+ * @param listed - The packages the project's package.json lists
71
+ * @returns The framework, or `undefined` when it lists none Courier tells
72
+ * @example
73
+ * ```ts
74
+ * detectFramework({ dependencies: { next: "^16.0.0", react: "^19.0.0" }, … })?.label; // "Next.js"
75
+ * ```
76
+ */
77
+ export declare function detectFramework(listed: ListedPackages): FrameworkMarker | undefined;
78
+ /**
79
+ * The framework of the project a folder belongs to, told by the nearest
80
+ * package.json at or above it. A package.json that does not parse lists
81
+ * nothing: the project is then read as plain Node, as it would be with none.
82
+ *
83
+ * @param folder - A folder of the project, absolute: the config's own
84
+ * @returns The framework, and the package.json that told it
85
+ * @example
86
+ * ```ts
87
+ * projectFramework("/app").framework?.id; // "sveltekit", where /app/package.json lists @sveltejs/kit
88
+ * ```
89
+ */
90
+ export declare function projectFramework(folder: string): ProjectFramework;
91
+ /** What a module of the project gets for an import its framework's server answers itself. */
92
+ type ModuleRule = {
93
+ readonly kind: "empty";
94
+ } | {
95
+ readonly kind: "provided";
96
+ readonly exports: () => object;
97
+ } | {
98
+ readonly kind: "refused";
99
+ readonly reason: string;
100
+ readonly remedy: string;
101
+ };
102
+ /** What a framework says of an import or a name of its own that the load could not find. */
103
+ interface Diagnosis {
104
+ /** What the import or the name is, worded to follow it: `is a module of SvelteKit's runtime, …`. */
105
+ readonly reason: string;
106
+ /** What to do about it, worded as an instruction. */
107
+ readonly remedy: string;
108
+ }
109
+ /** What a framework's server answers outside its build, and what it says of what it does not. */
110
+ interface FrameworkRules {
111
+ /** The imports the framework's server answers itself, by specifier. */
112
+ readonly modules: ReadonlyMap<string, ModuleRule>;
113
+ /** What the framework says of an import of its own that does not resolve outside its build. */
114
+ readonly unresolved?: ((specifier: string) => Diagnosis | undefined) | undefined;
115
+ /** What the framework says of a name its build provides where the code imports nothing. */
116
+ readonly undefinedName?: ((name: string) => Diagnosis | undefined) | undefined;
117
+ }
118
+ /** How a framework's server answers imports outside its build. */
119
+ interface ResolutionProfile extends FrameworkRules {
120
+ /** The framework; `undefined` for a project read as plain Node. */
121
+ readonly framework: FrameworkMarker | undefined;
122
+ }
123
+ /**
124
+ * A store of jiti transforms: TypeScript and ESM sources compiled to the
125
+ * CommonJS jiti evaluates, reused while the bytes and options that produced
126
+ * them are unchanged.
127
+ */
128
+ interface TransformCache {
129
+ /**
130
+ * What jiti's transform yields for `options`: the kept code when the store
131
+ * holds a successful transform of the same file from the same bytes and
132
+ * options, and otherwise whatever `compute` returns.
133
+ *
134
+ * @param options - What jiti hands its transform
135
+ * @param compute - jiti's own transform, run on a miss
136
+ * @returns The result of `compute`, or its kept code, byte for byte
137
+ */
138
+ transform(options: TransformOptions, compute: (options: TransformOptions) => TransformResult): TransformResult;
139
+ }
140
+ /** Where a {@link TransformCache} keeps transforms, and how much in memory. */
141
+ interface TransformCacheOptions {
142
+ /**
143
+ * A directory to keep transforms in across processes, as
144
+ * `findTransformCacheDirectory` finds it. Without one, or with `null`,
145
+ * transforms are kept in memory only.
146
+ */
147
+ directory?: string | null | undefined;
148
+ /**
149
+ * Characters of transformed code kept in memory across all files; the file
150
+ * used least recently is dropped first once they are exceeded. `0` keeps
151
+ * none, for a process that loads each module once. Defaults to 128 Mi
152
+ * characters.
153
+ */
154
+ maxMemoryChars?: number | undefined;
155
+ /**
156
+ * Whether the `directory` is only read: nothing in it is created, written,
157
+ * pruned or marked as used, so a transform computed on a miss stays in
158
+ * this process. For a process that loads, once, what another process has
159
+ * just kept there, as a mock worker loads the modules Studio loaded: its
160
+ * own entries would only duplicate that process's. Defaults to `false`.
161
+ */
162
+ readOnly?: boolean | undefined;
163
+ }
164
+ /**
165
+ * Create a {@link TransformCache} that keeps transforms in memory for the
166
+ * life of the process and, given a `directory`, on disk for the next one.
167
+ *
168
+ * Each file holds one entry per module system — required and imported are
169
+ * transformed differently, and a module reached both ways keeps both. An
170
+ * entry is reused only when the digest of jiti's version, the options and
171
+ * the source matches, so an edited file is transformed again the first time
172
+ * it is loaded.
173
+ *
174
+ * A transform is looked for in memory, then in the directory, where a hit
175
+ * is also kept in memory; only when both miss is it computed, and a success
176
+ * is then kept in both. The directory is shared with every process that
177
+ * names it, and a directory that cannot be read or written only costs
178
+ * transforms; see `createTransformCacheDisk` for what an entry must be to be
179
+ * used. A mock worker, which loads once the modules Studio has just loaded,
180
+ * opens Studio's directory `readOnly` with `maxMemoryChars: 0`: it reads
181
+ * what Studio kept, adds nothing to the directory, and holds nothing in
182
+ * memory past its one load.
183
+ *
184
+ * A failed transform, one whose result carries an `error`, is never kept:
185
+ * babel can fail on bytes it transforms without complaint a moment later,
186
+ * such as a deeply nested module that overflows the stack while babel is
187
+ * still cold. Such a file is transformed again on every load, and every
188
+ * failure reaches jiti with its error, as it does without a cache. A file
189
+ * named by a relative path, or not named, is transformed every time.
190
+ *
191
+ * @param options - The directory (default: none, memory only), the memory
192
+ * bound (default: 128 Mi characters), and whether the directory is only
193
+ * read (default: no)
194
+ * @returns A cache to pass to `createConfigJiti` or `loadCourierConfig`
195
+ * @example
196
+ * ```ts
197
+ * const transformCache = createTransformCache({
198
+ * directory: findTransformCacheDirectory(projectRoot),
199
+ * });
200
+ * const first = await loadCourierConfig({ cwd: projectRoot, transformCache });
201
+ * // Unchanged modules are not transformed again, here or in the next process:
202
+ * const second = await loadCourierConfig({ cwd: projectRoot, transformCache });
203
+ *
204
+ * // A process that loads once what another has just kept:
205
+ * const reader = createTransformCache({ directory, readOnly: true, maxMemoryChars: 0 });
206
+ * ```
207
+ */
208
+ export declare function createTransformCache({ directory, maxMemoryChars, readOnly }?: TransformCacheOptions): TransformCache;
209
+ /** How a project-file loader resolves imports and transforms modules. */
210
+ interface ConfigJitiOptions {
211
+ /**
212
+ * Whether imports resolve through the project's tsconfig path aliases, as
213
+ * the project's own files expect. Off to load an installed package as it is:
214
+ * an alias of its scope would send its imports to project sources.
215
+ */
216
+ aliases?: boolean | undefined;
217
+ /**
218
+ * Where to reuse each module's transform while its bytes are unchanged.
219
+ * Without one, every module is transformed on every load. Aliases take no
220
+ * part in it: they change where an import resolves, never the code a
221
+ * module's own bytes transform to.
222
+ */
223
+ transformCache?: TransformCache | undefined;
224
+ /**
225
+ * Told the file of each module the load transforms — the config and every
226
+ * TypeScript or ES module of the project it reaches — so whoever follows
227
+ * the project knows which files its config depends on.
228
+ */
229
+ onModule?: ((filename: string) => void) | undefined;
230
+ /**
231
+ * Runs each transform the load makes, the first of which loads the
232
+ * transformer itself. A caller noting what the config's code reads runs it
233
+ * unnoted: what the transformer reads is its own, not the config's.
234
+ */
235
+ runTransform?: (<TResult>(transform: () => TResult) => TResult) | undefined;
236
+ /**
237
+ * How the project's framework answers the imports its server answers
238
+ * itself — Next's `server-only`, SvelteKit's `$env/static/private`; see
239
+ * `ResolutionProfile`. Left out, the framework the nearest package.json to
240
+ * the file lists.
241
+ */
242
+ profile?: ResolutionProfile | undefined;
243
+ /**
244
+ * Told of each module whose transform failed — its syntax — with what the
245
+ * transformer said, so whoever reports the load's failure can say which
246
+ * module would not compile.
247
+ */
248
+ onTransformError?: ((filename: string, error: unknown) => void) | undefined;
249
+ }
250
+ /**
251
+ * The module loader for Courier's project files — `courier.config.*` and
252
+ * `courier.mocks.*` — with the options both need: the project's tsconfig path
253
+ * aliases, the imports its framework's server answers itself, default-export
254
+ * interop, no module cache, and no native import tried before the loader's
255
+ * own.
256
+ *
257
+ * A framework resolves some imports where it builds the server, never
258
+ * through Node: Next makes `server-only` an empty module on its server
259
+ * layers, SvelteKit and Astro write their env modules from the environment.
260
+ * The loader answers them as that server does, by the framework the
261
+ * project's package.json lists (see `profileOf`), in every module it
262
+ * transforms; one the project's own path aliases name is theirs. A package
263
+ * the loader hands to Node — an installed library — resolves its own imports
264
+ * as Node does while that works; where it fails — a library that imports
265
+ * `server-only` itself — the loader runs the package itself, and answers
266
+ * them there too. A package Node loads without a failure keeps Node's
267
+ * answers: one that imports an installed `client-only` is not refused.
268
+ *
269
+ * The module cache is off because this is also the reload path: the dev
270
+ * server loads the files again on every edit, and a cached module would hand
271
+ * back the file as it was before the user changed it. Every load therefore
272
+ * reads and evaluates every file again; a `transformCache` only spares the
273
+ * transform of a file whose bytes are unchanged.
274
+ *
275
+ * jiti's native attempt, which imports every module natively before falling
276
+ * back to its own loader, stays off too, whatever `JITI_TRY_NATIVE` says and
277
+ * under Bun, where it defaults to on: a module imported natively is the
278
+ * runtime's first evaluation of it for the life of the process, so an edit to
279
+ * it would never be seen, and the attempt comes before the module cache
280
+ * `evalModule` is handed, so two files evaluated over one cache would each
281
+ * evaluate a module they both import.
282
+ *
283
+ * jiti's own file cache stays off, with or without a `transformCache`:
284
+ * - it writes each entry in place, so concurrent writers can leave an entry
285
+ * torn between two transforms that its check still accepts;
286
+ * - an entry it cannot write makes the whole load throw `EACCES`;
287
+ * - unless the file's own directory has a `node_modules`, it falls back to
288
+ * `os.tmpdir()/jiti`, where another user of the machine can place an entry
289
+ * that is then run;
290
+ * - its key ignores `interopDefault` and the babel options, so a transform
291
+ * made under other options can be handed back;
292
+ * - nothing ever prunes it.
293
+ *
294
+ * jiti is loaded here rather than at the top of a module so a bundler that
295
+ * merely reaches this code does not pull a TypeScript transpiler into its
296
+ * graph; only a process that actually reads such a file pays for it.
297
+ *
298
+ * @param file - Absolute, symlink-resolved path of the file to load; modules
299
+ * and aliases resolve from its directory
300
+ * @param options - Whether to resolve through the project's path aliases
301
+ * (default: yes), where to reuse transforms (default: nowhere), and how the
302
+ * framework answers its own imports (default: as package.json tells it)
303
+ * @returns A jiti instance for that file
304
+ * @example
305
+ * ```ts
306
+ * const transformCache = createTransformCache();
307
+ * const jiti = await createConfigJiti(configFile, { transformCache });
308
+ * const config = await jiti.import(configFile, { default: true });
309
+ * ```
310
+ */
311
+ export declare function createConfigJiti(file: string, { aliases, transformCache, onModule, runTransform, profile, onTransformError }?: ConfigJitiOptions): Promise<Jiti>;
312
+ /**
313
+ * The signal a project's code reads to tell that Courier is reading its
314
+ * definitions rather than serving requests: `COURIER_PHASE` holds
315
+ * `definitions` in every process Courier starts to read them — the
316
+ * `courier-cli routes` command, `courier-mcp --list`, Studio, the process a
317
+ * bundler plugin reads the routes in — after the fashion of `NEXT_PHASE`.
318
+ * Code that checks the environment as it is imported can skip the check
319
+ * then, as `@t3-oss/env-core` does with `skipValidation`; Courier guesses
320
+ * nothing for it. Where the application runs — `courier-server`,
321
+ * `courier-mcp` serving an agent, `attachRuntime()`, the app itself — the
322
+ * variable is not set.
323
+ */
324
+ /** The variable a project's code reads the phase from. */
325
+ export declare const COURIER_PHASE = "COURIER_PHASE";
326
+ /** Its value while Courier reads the project's definitions. */
327
+ export declare const DEFINITIONS_PHASE = "definitions";
328
+ /**
329
+ * Mark the process as one that reads the project's definitions, before the
330
+ * project's code runs in it.
331
+ *
332
+ * @param env - The environment to mark (default: the process's)
333
+ * @example
334
+ * ```ts
335
+ * enterDefinitionsPhase();
336
+ * process.env.COURIER_PHASE; // "definitions"
337
+ * ```
338
+ */
339
+ export declare function enterDefinitionsPhase(env?: NodeJS.ProcessEnv): void;
340
+ /** Why a project's config — or its mocks file — did not load. */
341
+ type ConfigLoadErrorCode = "MODULE_NOT_FOUND" | "MODULE_REFUSED" | "TRANSFORM_FAILED" | "EVALUATION_FAILED" | "LOAD_TIMED_OUT";
342
+ /** Where a load failed. */
343
+ interface ConfigLoadErrorContext extends Record<string, unknown> {
344
+ /** The file loaded, absolute: `courier.config.*` or `courier.mocks.*`. */
345
+ readonly entry: string;
346
+ /** The import that did not resolve or was refused, as written. */
347
+ readonly specifier: string | undefined;
348
+ /**
349
+ * The modules from the entry to the one that failed, the entry first: each
350
+ * of the project's by its absolute path, as the system names it — with `\`
351
+ * on Windows — and an installed package by its name.
352
+ */
353
+ readonly importChain: readonly string[];
354
+ }
355
+ /**
356
+ * A project's config, or its mocks file, did not load. The message says
357
+ * what failed and in which module, with the chain of imports that led there
358
+ * from the entry — `src/api.ts imports "server-only", which does not resolve
359
+ * (courier.config.ts → src/api.ts).` — in one line; `hint` says what to do,
360
+ * in another. Neither holds a secret: the project's own message is quoted
361
+ * with the environment's values masked and every credential `redact()`
362
+ * finds cut, each as `[redacted]`. What the project threw is the `cause`,
363
+ * for a log at debug level, cut the same way in its message and its stack.
364
+ *
365
+ * @example
366
+ * ```ts
367
+ * if (error instanceof ConfigLoadError) log.error(`${error.message}\n${error.hint}`);
368
+ * ```
369
+ */
370
+ export declare class ConfigLoadError extends CourierError<ConfigLoadErrorCode> {
371
+ readonly context: ConfigLoadErrorContext;
372
+ /** What to do about it, in a sentence or two. */
373
+ readonly hint: string;
374
+ /**
375
+ * @param code - Why the load failed
376
+ * @param message - What failed and where, in one line
377
+ * @param details - Where it failed, what to do, and what the project threw
378
+ */
379
+ constructor(code: ConfigLoadErrorCode, message: string, details: {
380
+ context: ConfigLoadErrorContext;
381
+ hint: string;
382
+ cause?: unknown;
383
+ });
384
+ }
385
+ /** What a loader knows of a load that failed. */
386
+ interface FailedLoad {
387
+ /** The file loaded, absolute: `courier.config.*` or `courier.mocks.*`. */
388
+ readonly entry: string;
389
+ /** The load's module cache, which tells the chain of imports; none, and the chain is the entry. */
390
+ readonly cache?: ModuleCache | undefined;
391
+ /** The entry's key in the cache, when the loader registered it under another than its path. */
392
+ readonly entryKey?: string | undefined;
393
+ /** The framework Courier read the project as; left out, the one the entry's package.json lists. */
394
+ readonly profile?: ResolutionProfile | undefined;
395
+ /** The package.json that told the framework, absolute. */
396
+ readonly manifest?: string | undefined;
397
+ /** Each module whose transform failed in the load, by file, with what the transformer said. */
398
+ readonly transformFailures?: ReadonlyMap<string, unknown> | undefined;
399
+ }
400
+ /**
401
+ * The load error that says why a load failed, what to do, and where — the
402
+ * chain of imports from the entry to the module that failed.
403
+ *
404
+ * @param error - What the load threw
405
+ * @param load - What the loader knows of the load
406
+ * @returns The error, typed: an error that already is one is handed back
407
+ * @example
408
+ * ```ts
409
+ * try { await jiti.evalModule(source, { filename: file, cache }); }
410
+ * catch (error) { throw configLoadError(error, { entry: file, cache }); }
411
+ * ```
412
+ */
413
+ export declare function configLoadError(error: unknown, load: FailedLoad): ConfigLoadError;
414
+ /**
415
+ * The load error for a load given up after its time: the evaluation goes on
416
+ * — nothing stops a module's top-level code — and the process that gave up
417
+ * on it ends rather than wait.
418
+ *
419
+ * @param load - What the loader knows of the load
420
+ * @param limitMs - How long it was given
421
+ * @returns The error
422
+ * @example
423
+ * ```ts
424
+ * reject(loadTimedOut({ entry: configFile }, 60_000));
425
+ * ```
426
+ */
427
+ export declare function loadTimedOut(load: FailedLoad, limitMs: number): ConfigLoadError;
428
+ /** How one of the project's files is run, what runs just before its code does, and how long it may take. */
429
+ interface ImportProjectFileOptions extends ConfigJitiOptions {
430
+ /**
431
+ * Called once the loader is made, just before the file's code runs. A
432
+ * caller noting what that code reads starts here, so the loader's own
433
+ * start-up is not taken for the file's.
434
+ */
435
+ beforeRun?: (() => void) | undefined;
436
+ /**
437
+ * How long the file may take to run before it is given up with
438
+ * `ConfigLoadError` `LOAD_TIMED_OUT`, in milliseconds. Its code runs on —
439
+ * nothing stops a module's top-level code — so a process that gives up
440
+ * should end. Left out, it is waited for however long it takes.
441
+ */
442
+ timeoutMs?: number | undefined;
443
+ /**
444
+ * Hand back the file's default export rather than its namespace, as the
445
+ * runtime's loader does for `import(file, { default: true })`: see
446
+ * {@link settledDefault}. A promise it exports is waited for within the
447
+ * load's time.
448
+ */
449
+ default?: boolean | undefined;
450
+ }
451
+ /**
452
+ * Run one of the project's files — `courier.config.*`, `courier.mocks.*` —
453
+ * with the project's loader, and hand back its module namespace. The
454
+ * modules it runs go into a cache of this load's own, so a failure names the
455
+ * chain of imports that led to it: the file runs as the loader's `import`
456
+ * runs it, the loader choosing by its format whether to transform it or hand
457
+ * it to Node.
458
+ *
459
+ * @param file - The file, absolute and symlink-resolved
460
+ * @param options - How the loader resolves and transforms, what runs before the file, how long it may take, and whether its default is wanted
461
+ * @returns The file's module namespace, or its default export, settled
462
+ * @throws ConfigLoadError saying why the file did not load, where, and what to do
463
+ * @example
464
+ * ```ts
465
+ * const namespace = await importProjectFile("/app/courier.mocks.ts");
466
+ * ```
467
+ */
468
+ export declare function importProjectFile(file: string, { beforeRun, timeoutMs, default: wantsDefault, ...options }?: ImportProjectFileOptions): Promise<unknown>;
469
+ /**
470
+ * A project file's default export, as the runtime's loader hands it back
471
+ * for `import(file, { default: true })`: the namespace's default, or the
472
+ * namespace itself where that is nullish — a promise among them settled.
473
+ * A promise that rejects fails the load as the file's code throwing does,
474
+ * with `ConfigLoadError`, whose text and cause hold no value of the
475
+ * environment. Every loader of a project's file settles its default here.
476
+ *
477
+ * @param namespace - The file's module namespace, as its loader evaluated it
478
+ * @param load - What the loader knows of the load
479
+ * @returns The default export, settled
480
+ * @throws ConfigLoadError when the promise rejects
481
+ * @example
482
+ * ```ts
483
+ * const config = await settledDefault(await loadCourierEntry(file, cache, transformCache), { entry: file });
484
+ * ```
485
+ */
486
+ export declare function settledDefault(namespace: unknown, load: FailedLoad): Promise<unknown>;
487
+ /**
488
+ * Result of a successful `loadCourierConfig` call.
489
+ *
490
+ * `configFile` is the absolute, symlink-resolved path of the file that
491
+ * was loaded — the dev-server side uses it for the `fs.watch` dep tracker;
492
+ * the runtime side uses it only for log/debug output.
493
+ */
494
+ interface LoadedCourierConfig {
495
+ config: CourierConfig;
496
+ configFile: string;
497
+ }
498
+ /**
499
+ * Options for `loadCourierConfig`.
500
+ */
501
+ interface LoadCourierConfigOptions {
502
+ /**
503
+ * Starting directory for the config search. Defaults to `process.cwd()`.
504
+ * The dev-server passes the explicit project root; the runtime path
505
+ * passes `process.cwd()` so the walk-up finds configs located at the
506
+ * workspace root from a nested package cwd (common in monorepos).
507
+ */
508
+ cwd?: string | undefined;
509
+ /**
510
+ * When true, skip the walk-up and require the config file to exist at
511
+ * exactly `cwd`. The dev-server uses this because it was told the
512
+ * project root explicitly and wants to distinguish "no config in root"
513
+ * from "config found higher up" (which would usually mean the user
514
+ * pointed at the wrong directory). The runtime path leaves this false
515
+ * so a nested `cwd` still discovers a root-level config.
516
+ *
517
+ * Defaults to false (walk up).
518
+ */
519
+ requireAtCwd?: boolean | undefined;
520
+ /**
521
+ * Where to reuse each module's transform while its bytes are unchanged. A
522
+ * caller that keeps one across loads transforms only the modules whose
523
+ * bytes changed; without one, every module is transformed on every load.
524
+ */
525
+ transformCache?: TransformCache | undefined;
526
+ /** Told the file of each module the load transforms; see `createConfigJiti`. */
527
+ onModule?: ((filename: string) => void) | undefined;
528
+ /**
529
+ * Reuse the transforms kept in the project's `node_modules/.cache`, the
530
+ * ones Studio keeps, and keep there the ones this load makes: a process
531
+ * that loads the config beside Studio — an app that attaches, the MCP
532
+ * server, `courier-server` — transforms only the modules no process has
533
+ * transformed yet. On a project of a thousand endpoints that is hundreds
534
+ * of modules, most of a second. Passed over when `transformCache` is given.
535
+ */
536
+ projectTransforms?: boolean | undefined;
537
+ /**
538
+ * How long the load may take before it is given up with `ConfigLoadError`
539
+ * `LOAD_TIMED_OUT`, in milliseconds. The config's code runs on — nothing
540
+ * stops a module's top-level code — so a process that gives up should
541
+ * end. Left out, the load is waited for however long it takes.
542
+ */
543
+ timeoutMs?: number | undefined;
544
+ }
545
+ /**
546
+ * Walk up from `startDir` looking for a `courier.config.*` file, up to the
547
+ * top of the project: the folder that holds its repository or is the root
548
+ * of its workspace (see {@link configCandidates}). A config found in a folder
549
+ * above `startDir` is taken only when it belongs to this user or to the
550
+ * owner of `startDir`, and no user outside its group may write it: a folder
551
+ * many users share, such as `/tmp`, would otherwise run anyone's config.
552
+ *
553
+ * @param startDir - The directory to look in first
554
+ * @returns The absolute path of the first match, or `null` when no directory
555
+ * up to the top of the project holds one
556
+ * @throws UntrustedConfigError when the nearest config lies above `startDir`
557
+ * and someone else could have put it there
558
+ * @example
559
+ * ```ts
560
+ * findConfigFile("/app/src"); // → "/app/courier.config.ts", or null
561
+ * ```
562
+ */
563
+ export declare function findConfigFile(startDir: string): string | null;
564
+ /**
565
+ * Find a config file in `cwd` itself, without walking up.
566
+ *
567
+ * @param cwd - The directory to look in
568
+ * @returns The first of {@link CONFIG_BASENAMES} present there, or `null`
569
+ * @example
570
+ * ```ts
571
+ * findConfigFileAt(process.cwd()); // → "/app/courier.config.ts", or null
572
+ * ```
573
+ */
574
+ export declare function findConfigFileAt(cwd: string): string | null;
575
+ /**
576
+ * Find and load a `courier.config.*` file.
577
+ *
578
+ * Returns `null` when no config file exists, so a caller can fall back.
579
+ * Throws `ConfigLoadError` when a file was found and loading it failed,
580
+ * saying why, where — the chain of imports — and what to do.
581
+ *
582
+ * @param options - Where to look, where to reuse transforms, and how long to wait
583
+ * @returns The config and its canonical path, or `null` when there is no config
584
+ * @example
585
+ * ```ts
586
+ * const loaded = await loadCourierConfig({ cwd: projectRoot, requireAtCwd: true });
587
+ * ```
588
+ */
589
+ export declare function loadCourierConfig(options?: LoadCourierConfigOptions): Promise<LoadedCourierConfig | null>;
590
+ /**
591
+ * The transforms kept in a project's `node_modules/.cache` — the ones Studio
592
+ * keeps — for a process that loads the project's modules once: it reuses
593
+ * them, keeps there the ones it makes, and holds none in memory.
594
+ *
595
+ * @param projectRoot - The project's root
596
+ * @returns The cache, or `undefined` when the project has no directory to keep them in
597
+ * @example
598
+ * ```ts
599
+ * await loadCourierConfigFile(file, { transformCache: projectTransformCache(dirname(file)) });
600
+ * ```
601
+ */
602
+ export declare function projectTransformCache(projectRoot: string): TransformCache | undefined;
603
+ /**
604
+ * How one config file is loaded: the loader's options, what runs just before
605
+ * the config's code does, and how long it may take; see `importProjectFile`.
606
+ */
607
+ interface LoadConfigFileOptions extends Omit<ImportProjectFileOptions, "default"> {}
608
+ /**
609
+ * Load one config file, found by whatever means the caller chose — the
610
+ * standalone server takes it from `--config`.
611
+ *
612
+ * @param file - Absolute path of an existing config file
613
+ * @param options - How the loader resolves imports and where it reuses
614
+ * transforms, see {@link ConfigJitiOptions}, what runs before the config,
615
+ * and how long it may take
616
+ * @returns The config and the canonical path it was loaded from
617
+ * @throws ConfigLoadError when the file will not load: an import that does
618
+ * not resolve or that the framework refuses, a module that does not
619
+ * compile, or code that throws
620
+ * @example
621
+ * ```ts
622
+ * const { config } = await loadCourierConfigFile("/app/courier.config.ts", {
623
+ * transformCache: createTransformCache(),
624
+ * });
625
+ * ```
626
+ */
627
+ export declare function loadCourierConfigFile(file: string, options?: LoadConfigFileOptions): Promise<LoadedCourierConfig>;
628
+ /** An environment, as a process holds it. */
629
+ type Environment = Readonly<Record<string, string | undefined>>;
630
+ /**
631
+ * The text with no secret left to quote, by the rules above.
632
+ *
633
+ * @param text - The message, as the project's code wrote it
634
+ * @param env - The environment whose values are masked
635
+ * @returns The message, with the one sign in place of each secret
636
+ * @example
637
+ * ```ts
638
+ * withoutSecrets("cannot reach postgres://app:pw@db/shop", { PGUSER: "postgres" });
639
+ * // → "cannot reach [redacted]://db/shop"
640
+ * ```
641
+ */
642
+ export declare function withoutSecrets(text: string, env?: Environment): string;
643
+ /**
644
+ * What the project's code threw, as an error whose message and stack hold
645
+ * no secret, by the rules above: the error a load error carries as its
646
+ * `cause`, so no log that prints it — at debug level, by a bundler — holds
647
+ * one either. It keeps the thrown error's name and `code`; a value thrown
648
+ * that is no error becomes one, its text cut the same way.
649
+ *
650
+ * @param thrown - What the code threw
651
+ * @param env - The environment whose values are masked
652
+ * @returns The error, masked
653
+ * @example
654
+ * ```ts
655
+ * maskedError(new TypeError(`bad ${process.env.API_KEY}`)).message; // "bad [redacted]"
656
+ * ```
657
+ */
658
+ export declare function maskedError(thrown: unknown, env?: Environment): Error;
659
+ /** Path aliases a project declares, ready for both consumers. */
660
+ interface PathAliases {
661
+ /**
662
+ * Prefix → absolute target, in the shape jiti's `alias` option takes.
663
+ * Only the first target of a mapping appears: jiti resolves one path per
664
+ * alias, while `candidatesFor` offers every declared target.
665
+ */
666
+ readonly loader: Readonly<Record<string, string>>;
667
+ /** Absolute paths `specifier` may denote, in declaration order. */
668
+ candidatesFor(specifier: string): string[];
669
+ }
670
+ /**
671
+ * Read the path aliases that apply to `projectRoot`.
672
+ *
673
+ * @param projectRoot - Directory holding the project's `tsconfig.json`
674
+ * @returns The aliases, empty when the project declares none
675
+ *
676
+ * @example
677
+ * ```ts
678
+ * const aliases = readPathAliases("/app");
679
+ * aliases.candidatesFor("@/lib/sources"); // → ["/app/src/lib/sources"]
680
+ * ```
681
+ */
682
+ export declare function readPathAliases(projectRoot: string): PathAliases;
683
+ /**
684
+ * How a framework's server answers the imports it answers itself. A
685
+ * framework that answers none, and a project that lists no framework, are
686
+ * read as plain Node.
687
+ *
688
+ * @param framework - The project's framework, as its package.json tells it
689
+ * @returns The profile every loader of the project's files applies
690
+ * @example
691
+ * ```ts
692
+ * profileOf(projectFramework(projectRoot).framework).modules.has("server-only"); // true for Next.js
693
+ * ```
694
+ */
695
+ export declare function profileOf(framework: FrameworkMarker | undefined): ResolutionProfile;
696
+ /** The import a profile refused, and the framework whose rule refused it. */
697
+ interface RefusedModuleContext extends Record<string, unknown> {
698
+ readonly specifier: string;
699
+ /** The framework, as its users name it. */
700
+ readonly framework: string | undefined;
701
+ }
702
+ /**
703
+ * A module of the project imported one its framework refuses where
704
+ * definitions run — Next's `client-only` — so the config did not load.
705
+ *
706
+ * @example
707
+ * ```ts
708
+ * throw new RefusedModuleError("client-only", rule, nextMarker);
709
+ * ```
710
+ */
711
+ export declare class RefusedModuleError extends CourierError<"MODULE_REFUSED"> {
712
+ readonly context: RefusedModuleContext;
713
+ /** Why the framework refuses it, worded to follow the import: `is the marker of …`. */
714
+ readonly reason: string;
715
+ /** What to do about it, worded as an instruction. */
716
+ readonly remedy: string;
717
+ /**
718
+ * @param specifier - The import, as written
719
+ * @param rule - Why the framework refuses it, and what to do
720
+ * @param framework - The framework whose rule it is
721
+ */
722
+ constructor(specifier: string, rule: {
723
+ readonly reason: string;
724
+ readonly remedy: string;
725
+ }, framework: FrameworkMarker | undefined);
726
+ }
727
+ /**
728
+ * How long one reading of a project's config may take before it is given
729
+ * up, in milliseconds: a config that waits on the network, or on a timer,
730
+ * never ends. The routes plugin's process and `courier-cli routes` hold to
731
+ * it; Studio, which keeps running what it loaded last, has its own,
732
+ * `COURIER_CONFIG_LOAD_TIMEOUT_MS`.
733
+ */
734
+ export declare const CONFIG_READ_LIMIT_MS = 6e4;
735
+ /**
736
+ * Where a project's transforms are kept across processes:
737
+ * `.cache/courier/transform` in the nearest `node_modules` directory at or
738
+ * above the project root.
739
+ *
740
+ * The nearest one is taken, not the one module resolution would reach: under
741
+ * pnpm that lies inside `.pnpm`, and what keeps the directory safe is the
742
+ * rule below, not where it is. Every file in it holds code that Studio, mock
743
+ * workers and build plugins run, so on POSIX the directory is refused when
744
+ * `node_modules`, `.cache`, `courier` or `transform` — those that exist — is
745
+ * a symbolic link or belongs to another user, and when `courier` or
746
+ * `transform`, which only Studio creates, is writable by its group or
747
+ * others; any of these lets someone else replace or remove entries, or put
748
+ * the directory somewhere they control. `node_modules` and `.cache` may be
749
+ * group-writable, as package managers leave them under a umask of 002:
750
+ * whoever can write `node_modules` can replace any package in it already. On
751
+ * Windows only links and other kinds of file are refused.
752
+ *
753
+ * There is none when no `node_modules` exists, as with Yarn Plug'n'Play, or
754
+ * when `COURIER_TRANSFORM_CACHE` is `0`, `false` or `off` in any case. The
755
+ * system temporary directory, which every user of the machine may write, is
756
+ * never used instead. Why there is none is logged at debug level, once for
757
+ * each root until the reason changes, however often the root is looked up.
758
+ *
759
+ * @param projectRoot - The directory holding `courier.config.*`
760
+ * @param env - The environment to read `COURIER_TRANSFORM_CACHE` from
761
+ * (default: `process.env`)
762
+ * @returns The directory, which may not exist yet, or `null` for none
763
+ * @example
764
+ * ```ts
765
+ * const transformCache = createTransformCache({
766
+ * directory: findTransformCacheDirectory(projectRoot),
767
+ * });
768
+ * ```
769
+ */
770
+ export declare function findTransformCacheDirectory(projectRoot: string, env?: Readonly<Record<string, string | undefined>>): string | null;
771
+ /**
772
+ * The keys of a courier.config that Courier does not read, at the top and
773
+ * in its `studio`, `server`, `runtime` and `agents` blocks — a misspelt `studo:` say
774
+ * — so a load can name them rather than ignore them in silence.
775
+ *
776
+ * @param config - The config, as its module exported it
777
+ * @returns Each such key, dotted under its block
778
+ * @example
779
+ * ```ts
780
+ * unknownConfigKeys({ sources: {}, studo: {}, server: { prot: 3001 } }); // → ["studo", "server.prot"]
781
+ * ```
782
+ */
783
+ export declare function unknownConfigKeys(config: unknown): string[];
784
+ /** Where a config that was not run lies, and the folder it was looked for from. */
785
+ interface UntrustedConfigContext extends Record<string, unknown> {
786
+ /** The config, absolute. */
787
+ configFile: string;
788
+ /** The folder the config was looked for from, below it. */
789
+ folder: string;
790
+ }
791
+ /**
792
+ * The config nearest a folder lies in a folder above it, and someone else
793
+ * could have put it there: it belongs to another user, or any user may
794
+ * write it. It was not run, as it would run with the rights of whoever
795
+ * builds or serves the project.
796
+ *
797
+ * @example
798
+ * ```ts
799
+ * throw new UntrustedConfigError("/tmp/courier.config.ts", "/tmp/app", "it belongs to uid 1001");
800
+ * ```
801
+ */
802
+ export declare class UntrustedConfigError extends CourierError<"CONFIG_NOT_TRUSTED"> {
803
+ readonly context: UntrustedConfigContext;
804
+ /**
805
+ * @param configFile - The config that was not run, absolute
806
+ * @param folder - The folder it was looked for from
807
+ * @param reason - Why it is not trusted, worded to follow "and"
808
+ */
809
+ constructor(configFile: string, folder: string, reason: string);
810
+ }
811
+ export type { ConfigJitiOptions, ConfigLoadErrorCode, ConfigLoadErrorContext, FailedLoad, FrameworkId, FrameworkMarker, ImportProjectFileOptions, ListedPackages, LoadConfigFileOptions, LoadCourierConfigOptions, LoadedCourierConfig, ModuleRule, PathAliases, ProjectFramework, RefusedModuleContext, ResolutionProfile, TransformCache, TransformCacheOptions, UntrustedConfigContext };