@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,191 @@
1
+ import { t as RoutesReadError } from "../routes-read-error-DQhKIE2F.js";
2
+ import { n as routesModuleCode } from "../routes-module-code-DYtP7uj1.js";
3
+ import { t as configInPageMessage } from "../config-in-page-message-DLd3f0Jd.js";
4
+ import { w as CONFIG_BASENAMES } from "../load-config-barNPfXN.js";
5
+ import { t as sharedEngine } from "../shared-engine-D0j7uXg8.js";
6
+ import { t as configFileOf } from "../config-file-of-Clf_dWUQ.js";
7
+ import { n as ROUTES_PATTERN, r as ROUTES_SPECIFIER } from "../routes-module-id-CnUjQjYF.js";
8
+ import { t as routesFile } from "../routes-file-DRoqUZ0S.js";
9
+ import { basename, extname } from "node:path";
10
+ // src/esbuild/config-in-page.ts
11
+ const RESOLVING = "@noverax/courier-build:resolving-config";
12
+ /**
13
+ * Fail a page's bundle where it imports the project's config: it would ship
14
+ * the server's settings and every API's definition to the browser. The
15
+ * import is caught as it resolves, not as the module loads: another plugin
16
+ * may load the file first, as Angular's compiler loads every TypeScript
17
+ * module. esbuild hands the plugin only imports that name a config's file,
18
+ * with or without its extension; each resolves as it would, and the one that
19
+ * reaches the config — found anew, as it may appear or move while a context
20
+ * watches — fails.
21
+ *
22
+ * @param build - The build the plugin is set up for
23
+ * @param root - The project's root, where the config is found from
24
+ * @param named - The config, from the root or absolute, when it is not the one found
25
+ * @example
26
+ * ```ts
27
+ * if (build.initialOptions.platform !== "node") refuseConfigInPage(build, root, options.config);
28
+ * ```
29
+ */
30
+ function refuseConfigInPage(build, root, named) {
31
+ const names = named === void 0 ? CONFIG_BASENAMES : [basename(named)];
32
+ const stems = [...new Set(names.map((name) => name.slice(0, -extname(name).length)))];
33
+ const filter = new RegExp(`(^|[\\\\/])(${stems.map(escaped).join("|")})(\\.[cm]?[jt]s)?$`);
34
+ build.onResolve({ filter }, async (args) => {
35
+ if (args.pluginData === RESOLVING) return void 0;
36
+ const resolved = await build.resolve(args.path, {
37
+ kind: args.kind,
38
+ importer: args.importer,
39
+ resolveDir: args.resolveDir,
40
+ pluginData: RESOLVING
41
+ });
42
+ const configFile = configFileOf(root, named);
43
+ if (resolved.errors.length > 0 || configFile === void 0) return void 0;
44
+ if (withSlashes(resolved.path) !== withSlashes(configFile)) return void 0;
45
+ return { errors: [{ text: configInPageMessage(configFile) }] };
46
+ });
47
+ }
48
+ function escaped(name) {
49
+ return name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
50
+ }
51
+ function withSlashes(path) {
52
+ return path.split("\\").join("/");
53
+ }
54
+ // src/esbuild/load-routes.ts
55
+ /**
56
+ * The routes module, as esbuild loads it: the routes read from the config,
57
+ * with every path they came from watched, so a context that watches builds
58
+ * them again when one changes — a file that is not there yet included, and
59
+ * each folder whose entries the reading listed.
60
+ *
61
+ * When they cannot be read, what the failed reading read is watched instead,
62
+ * so mending the config brings the routes back. A first build fails with
63
+ * why; a later one keeps the routes read last, and warns.
64
+ *
65
+ * @param engine - The engine that reads the project's routes
66
+ * @param again - Whether a build has run before in this context
67
+ * @returns What esbuild loads
68
+ * @example
69
+ * ```ts
70
+ * build.onLoad({ filter: ANY_PATH, namespace }, () => loadRoutes(engine, builds > 1));
71
+ * ```
72
+ */
73
+ async function loadRoutes(engine, again) {
74
+ try {
75
+ const snapshot = await engine.read();
76
+ return {
77
+ contents: routesModuleCode(snapshot),
78
+ loader: "js",
79
+ ...watched(snapshot)
80
+ };
81
+ } catch (error) {
82
+ const failure = error instanceof Error ? error : new Error(String(error));
83
+ const followed = (failure instanceof RoutesReadError ? failure.inputs : void 0) ?? engine.last;
84
+ const watch = followed === void 0 ? {} : watched(followed);
85
+ const why = [{ text: `[courier] ${failure.message}` }];
86
+ const last = engine.last;
87
+ if (!again || last === void 0) return {
88
+ errors: why,
89
+ ...watch
90
+ };
91
+ return {
92
+ contents: routesModuleCode(last),
93
+ loader: "js",
94
+ warnings: why,
95
+ ...watch
96
+ };
97
+ }
98
+ }
99
+ function watched(inputs) {
100
+ return {
101
+ watchFiles: [...inputs.files],
102
+ watchDirs: [...inputs.listed ?? []]
103
+ };
104
+ }
105
+ // src/esbuild/courier-routes.ts
106
+ const NAMESPACE = "courier-routes";
107
+ const ANY_PATH = /.*/;
108
+ /**
109
+ * The esbuild plugin that builds the routes a page's proxy client may call
110
+ * from `courier.config.ts`, in place of the module `@noverax/courier/routes`
111
+ * ships. No routes file is written, and the routes carry no address, path or
112
+ * middleware. A page's bundle — any `platform` but `node` — that takes the
113
+ * config itself fails.
114
+ *
115
+ * A build that cannot read the config fails with why. A context that builds
116
+ * again, under `watch()` or `serve()`, follows every file the routes came
117
+ * from; when they cannot be read on a later build, it keeps the routes it
118
+ * read last and warns why.
119
+ *
120
+ * @param options - Where the config is, when it is not found from `absWorkingDir`
121
+ * @returns The plugin
122
+ * @example
123
+ * ```ts
124
+ * import { build } from "esbuild";
125
+ * import { courierRoutes } from "@noverax/courier-build/esbuild";
126
+ *
127
+ * await build({ entryPoints: ["src/main.ts"], bundle: true, plugins: [courierRoutes()] });
128
+ * ```
129
+ */
130
+ function courierRoutes(options = {}) {
131
+ return {
132
+ name: "courier-routes",
133
+ setup(build) {
134
+ const root = build.initialOptions.absWorkingDir ?? process.cwd();
135
+ const engine = sharedEngine({
136
+ root,
137
+ config: options.config,
138
+ envFiles: []
139
+ });
140
+ let builds = 0;
141
+ build.onStart(() => {
142
+ builds += 1;
143
+ });
144
+ build.onResolve({ filter: ROUTES_PATTERN }, () => ({
145
+ path: ROUTES_SPECIFIER,
146
+ namespace: NAMESPACE
147
+ }));
148
+ build.onLoad({
149
+ filter: ANY_PATH,
150
+ namespace: NAMESPACE
151
+ }, () => loadRoutes(engine, builds > 1));
152
+ if (build.initialOptions.platform !== "node") refuseConfigInPage(build, root, options.config);
153
+ }
154
+ };
155
+ }
156
+ // src/esbuild/written-routes-plugin.ts
157
+ /**
158
+ * The esbuild plugin for a host that follows only the files its build read
159
+ * from disk, as Angular's builders do, not those a plugin names: the
160
+ * routes are written to a module there, which `@noverax/courier/routes`
161
+ * resolves to, and watchers of what they came from rewrite it on a change,
162
+ * so the host builds it again. A page's bundle — any `platform` but `node` —
163
+ * that takes the config itself fails.
164
+ *
165
+ * A build fails while the routes cannot be read, with why; a host that
166
+ * watches keeps the routes read last, and with none read yet the module
167
+ * fails where it is imported, saying why.
168
+ *
169
+ * @param options - Where the project, its config and the module are, and whether the host watches
170
+ * @returns The plugin, once the routes are written
171
+ * @throws RoutesReadError `ROUTES_NOT_WRITTEN` when the module cannot be written
172
+ * @example
173
+ * ```ts
174
+ * const plugin = await writtenRoutesPlugin({ root, file: join(dir, "routes.js"), watch: false, elsewhere: "TMPDIR" });
175
+ * ```
176
+ */
177
+ async function writtenRoutesPlugin(options) {
178
+ const routes = await routesFile(options.root, options.config, options.file, options.elsewhere);
179
+ return {
180
+ name: "courier-routes",
181
+ setup(build) {
182
+ build.onResolve({ filter: ROUTES_PATTERN }, () => {
183
+ const failure = routes.failure;
184
+ if (!options.watch && failure !== void 0) return { errors: [{ text: `[courier] ${failure.message}` }] };
185
+ return { path: routes.file };
186
+ });
187
+ if (build.initialOptions.platform !== "node") refuseConfigInPage(build, options.root, options.config);
188
+ }
189
+ };
190
+ }
191
+ export { courierRoutes, writtenRoutesPlugin };
@@ -0,0 +1,134 @@
1
+ import { t as CourierRoutesOptions } from "./courier-routes-options-BYF7Sfdf.js";
2
+ /** What the routes rules of a project are made from. */
3
+ interface RoutesProject {
4
+ /** The project's root, absolute. */
5
+ readonly root: string;
6
+ /** The config file the options name, absolute; none when it is the one found from the root. */
7
+ readonly config?: string | undefined;
8
+ /**
9
+ * The module `@noverax/courier/routes` resolves to from the root, by its
10
+ * real path, as a bundler names it: the module the routes are built in
11
+ * place of.
12
+ */
13
+ readonly stubFile: string;
14
+ /** The config's real path, when there is one to read: a page's bundle must not take it. */
15
+ readonly configFile?: string | undefined;
16
+ }
17
+ /**
18
+ * The project a bundler builds, as its routes rules need it.
19
+ *
20
+ * @param root - The project's root, where the bundler resolves its packages from
21
+ * @param options - The plugin's options
22
+ * @returns The project
23
+ * @throws RoutesReadError `CORE_NOT_FOUND` when `@noverax/courier` does not resolve from the root
24
+ * @example
25
+ * ```ts
26
+ * routesProject("/app"); // { root: "/app", stubFile: "/app/node_modules/@noverax/courier/lib/routes/index.js", configFile: "/app/courier.config.ts" }
27
+ * ```
28
+ */
29
+ declare function routesProject(root: string, options?: CourierRoutesOptions): RoutesProject;
30
+ /**
31
+ * What a rule hands the routes loader. Plain data alone, as Turbopack takes
32
+ * a loader's options only as JSON.
33
+ */
34
+ interface RoutesLoaderOptions {
35
+ /** The project's root, absolute: the config is looked for there and above it. */
36
+ readonly root: string;
37
+ /** The config file, absolute, when it is not the one found from the root. */
38
+ readonly config?: string | undefined;
39
+ /**
40
+ * `"config-in-page"` on the rule that matches the config itself in a
41
+ * page's bundle, which the loader refuses; none on the routes module's.
42
+ */
43
+ readonly refuse?: "config-in-page" | undefined;
44
+ }
45
+ /** A module rule of webpack and Rspack that runs the routes loader on one module. */
46
+ interface RoutesLoaderRule {
47
+ /** The module the rule matches, by its real path: webpack and Rspack match a path from its start. */
48
+ test: string;
49
+ /** Before every other loader, which would read the module the package ships. */
50
+ enforce?: "pre";
51
+ /** The routes loader, and what it is told. */
52
+ use: Array<{
53
+ loader: string;
54
+ options: RoutesLoaderOptions;
55
+ }>;
56
+ }
57
+ /**
58
+ * The rule that builds the routes in place of the module
59
+ * `@noverax/courier/routes` ships, for every bundle the bundler builds: a
60
+ * page's and a server's alike.
61
+ *
62
+ * @param project - The project, as `routesProject()` gives it
63
+ * @returns The rule, for the bundler's `module.rules`
64
+ * @example
65
+ * ```ts
66
+ * config.module.rules.push(routesRule(routesProject(config.context)));
67
+ * ```
68
+ */
69
+ declare function routesRule(project: RoutesProject): RoutesLoaderRule;
70
+ /**
71
+ * The rule that fails a page's bundle where it takes the project's config
72
+ * as a module: it would ship the server's settings and every API's
73
+ * definition to the browser. Only for the bundles a browser runs; a server's
74
+ * may import the config.
75
+ *
76
+ * @param project - The project, as `routesProject()` gives it
77
+ * @returns The rule, for the `module.rules` of a page's bundle; none when the project has no config yet
78
+ * @example
79
+ * ```ts
80
+ * const rule = configInPageRule(project);
81
+ * if (rule && !isServer) config.module.rules.push(rule);
82
+ * ```
83
+ */
84
+ declare function configInPageRule(project: RoutesProject): RoutesLoaderRule | undefined;
85
+ /** What of a webpack or Rspack compiler the plugin reads, and adds its rules to. */
86
+ interface RoutesCompiler {
87
+ /** The folder the compiler resolves from: the project's root. */
88
+ readonly context: string;
89
+ readonly options: {
90
+ readonly target?: unknown;
91
+ readonly module: {
92
+ rules?: unknown[];
93
+ };
94
+ };
95
+ }
96
+ /**
97
+ * The webpack and Rspack plugin that builds the routes a page's proxy
98
+ * client may call from `courier.config.ts`, in place of the module
99
+ * `@noverax/courier/routes` ships, for every bundle the compiler builds. No
100
+ * routes file is written, and the routes carry no address, path or
101
+ * middleware. A page's bundle that takes the config itself fails.
102
+ *
103
+ * @example
104
+ * ```ts
105
+ * // webpack.config.js
106
+ * import { CourierRoutesPlugin } from "@noverax/courier-build/webpack";
107
+ *
108
+ * export default { plugins: [new CourierRoutesPlugin()] };
109
+ * ```
110
+ */
111
+ declare class CourierRoutesPlugin {
112
+ #private;
113
+ /**
114
+ * @param options - Where the config is, when it is not found from the compiler's context
115
+ */
116
+ constructor(options?: CourierRoutesOptions);
117
+ /**
118
+ * @param compiler - The compiler the plugin is given
119
+ */
120
+ apply(compiler: RoutesCompiler): void;
121
+ }
122
+ /**
123
+ * The loader this package ships, as webpack, Rspack and Turbopack load it:
124
+ * a CommonJS module, by its absolute path.
125
+ *
126
+ * @returns The loader file
127
+ * @throws Error when the package's own folder cannot be found
128
+ * @example
129
+ * ```ts
130
+ * routesLoaderFile(); // "/app/node_modules/@noverax/courier-build/lib/webpack/loader.cjs"
131
+ * ```
132
+ */
133
+ declare function routesLoaderFile(): string;
134
+ export { RoutesLoaderRule as a, RoutesProject as c, configInPageRule as i, routesProject as l, CourierRoutesPlugin as n, routesRule as o, RoutesCompiler as r, RoutesLoaderOptions as s, routesLoaderFile as t };
package/lib/index.d.ts ADDED
@@ -0,0 +1,101 @@
1
+ import { a as RoutesInputs, i as createRoutesEngine, n as RoutesEngineOptions, o as RoutesRequest, r as RoutesEvaluator, s as RoutesSnapshot, t as RoutesEngine } from "./routes-engine-BfwrCuNL.js";
2
+ import { CourierError } from "@noverax/courier";
3
+ /**
4
+ * The module a page's bundle takes in place of `@noverax/courier/routes`:
5
+ * the routes of every source of the config, as data alone, written as an
6
+ * object literal that keeps every name its own — `__proto__` too, which a
7
+ * literal would otherwise take for the object's prototype.
8
+ *
9
+ * @param snapshot - The routes as the config gave them
10
+ * @returns The module's code
11
+ * @example
12
+ * ```ts
13
+ * routesModuleCode(snapshot); // 'export const routes = {"shop":{"source":"shop","endpoints":{…}}};\n'
14
+ * ```
15
+ */
16
+ export declare function routesModuleCode(snapshot: RoutesSnapshot): string;
17
+ /**
18
+ * The same module, as CommonJS, for a host that requires it and transforms
19
+ * nothing it takes: Jest, outside its ES module mode. It exports `routes`
20
+ * as an ES module would, so an import compiled to a `require` takes it by
21
+ * name.
22
+ *
23
+ * @param snapshot - The routes as the config gave them
24
+ * @returns The module's code
25
+ * @example
26
+ * ```ts
27
+ * routesCommonJsCode(snapshot); // '"use strict";\nObject.defineProperty(exports, "__esModule", …);\nexports.routes = {…};\n'
28
+ * ```
29
+ */
30
+ export declare function routesCommonJsCode(snapshot: RoutesSnapshot): string;
31
+ /**
32
+ * The module a page's bundle takes when the routes were never read: one
33
+ * that fails where it is imported, saying why, as a build fails with it.
34
+ * It still exports `routes`, so a module that imports it links, and the
35
+ * reason is the error the page shows.
36
+ *
37
+ * @param message - Why the routes were not read
38
+ * @returns The module's code
39
+ * @example
40
+ * ```ts
41
+ * unreadRoutesModuleCode("courier.config.ts did not load: …");
42
+ * ```
43
+ */
44
+ export declare function unreadRoutesModuleCode(message: string): string;
45
+ /** Why a project's routes were not read. */
46
+ type RoutesReadErrorCode = "CONFIG_NOT_FOUND" | "CONFIG_NOT_TRUSTED" | "CONFIG_NOT_LOADED" | "ROUTES_NOT_READ" | "CORE_NOT_FOUND" | "ROUTES_NOT_WRITTEN";
47
+ /** Where a reading of the routes failed. */
48
+ interface RoutesReadErrorContext extends Record<string, unknown> {
49
+ /** The project's root the config was looked for from. */
50
+ root: string;
51
+ /** The config file, once one was found. */
52
+ configFile?: string | undefined;
53
+ }
54
+ /** What a failed reading leaves besides its reason. */
55
+ interface RoutesReadErrorDetails {
56
+ /** The stack of the error the config's own code threw, when it threw one. */
57
+ stack?: string | undefined;
58
+ /**
59
+ * What the reading had read when it failed: a change to any of it may mend
60
+ * the config, and a watcher follows them. None when the reading never
61
+ * answered.
62
+ */
63
+ inputs?: RoutesInputs | undefined;
64
+ }
65
+ /**
66
+ * A project's routes could not be read from its config. A build fails with
67
+ * it; a dev server keeps the routes it read last and reports it.
68
+ *
69
+ * @example
70
+ * ```ts
71
+ * throw new RoutesReadError("CONFIG_NOT_FOUND", "No courier.config.* in /app or above it", { root: "/app" });
72
+ * ```
73
+ */
74
+ export declare class RoutesReadError extends CourierError<RoutesReadErrorCode> {
75
+ readonly context: RoutesReadErrorContext;
76
+ /** What the reading had read when it failed, when it answered at all. */
77
+ readonly inputs: RoutesInputs | undefined;
78
+ /**
79
+ * @param code - Why the routes were not read
80
+ * @param message - What went wrong, and what to do about it
81
+ * @param context - The project's root, and the config file once found
82
+ * @param details - The stack of what the config threw, and what the reading had read
83
+ */
84
+ constructor(code: RoutesReadErrorCode, message: string, context: RoutesReadErrorContext, { stack, inputs }?: RoutesReadErrorDetails);
85
+ }
86
+ /**
87
+ * Why the project's config cannot be a module of a page, and what to write
88
+ * instead. It holds the server's settings and the definition of every API —
89
+ * their addresses, their middleware, what reads their secrets — and imported
90
+ * as a value it ships all of them to the browser. A page needs its type
91
+ * alone, which an `import type` gives without a byte of it.
92
+ *
93
+ * @param configFile - The project's config
94
+ * @returns The message a build fails with
95
+ * @example
96
+ * ```ts
97
+ * configInPageMessage("/app/courier.config.ts"); // "[courier] courier.config.ts is imported into a page as a value, …"
98
+ * ```
99
+ */
100
+ export declare function configInPageMessage(configFile: string): string;
101
+ export { type RoutesEngine, type RoutesEngineOptions, type RoutesEvaluator, type RoutesInputs, type RoutesReadErrorCode, type RoutesReadErrorContext, type RoutesReadErrorDetails, type RoutesRequest, type RoutesSnapshot, createRoutesEngine };
package/lib/index.js ADDED
@@ -0,0 +1,4 @@
1
+ import { t as RoutesReadError } from "./routes-read-error-DQhKIE2F.js";
2
+ import { i as createRoutesEngine, n as routesModuleCode, r as unreadRoutesModuleCode, t as routesCommonJsCode } from "./routes-module-code-DYtP7uj1.js";
3
+ import { t as configInPageMessage } from "./config-in-page-message-DLd3f0Jd.js";
4
+ export { RoutesReadError, configInPageMessage, createRoutesEngine, routesCommonJsCode, routesModuleCode, unreadRoutesModuleCode };
@@ -0,0 +1 @@
1
+ export {}
@@ -0,0 +1,46 @@
1
+ import { n as routesModuleCode, t as routesCommonJsCode } from "../routes-module-code-DYtP7uj1.js";
2
+ import { n as isRecord } from "../errno-code-CNx_E80R.js";
3
+ import { t as sharedEngine } from "../shared-engine-D0j7uXg8.js";
4
+ import { parentPort } from "node:worker_threads";
5
+ // src/jest/routes-thread.ts
6
+ parentPort?.on("message", (message) => {
7
+ if (!isRecord(message) || !isRecord(message.request)) return;
8
+ const { port, signal } = message;
9
+ if (!(signal instanceof Int32Array) || !isPort(port)) return;
10
+ answer(requestOf(message.request)).then((reply) => {
11
+ port.postMessage(reply);
12
+ port.close();
13
+ Atomics.store(signal, 0, 1);
14
+ Atomics.notify(signal, 0);
15
+ });
16
+ });
17
+ async function answer(request) {
18
+ try {
19
+ const snapshot = await sharedEngine({
20
+ root: request.root,
21
+ config: request.config,
22
+ envFiles: []
23
+ }).read();
24
+ return {
25
+ ok: true,
26
+ module: routesModuleCode(snapshot),
27
+ commonJs: routesCommonJsCode(snapshot)
28
+ };
29
+ } catch (error) {
30
+ return {
31
+ ok: false,
32
+ message: error instanceof Error ? error.message : String(error)
33
+ };
34
+ }
35
+ }
36
+ function requestOf(request) {
37
+ const root = typeof request.root === "string" ? request.root : process.cwd();
38
+ return typeof request.config === "string" ? {
39
+ root,
40
+ config: request.config
41
+ } : { root };
42
+ }
43
+ function isPort(value) {
44
+ return isRecord(value) && typeof value.postMessage === "function";
45
+ }
46
+ export {};
@@ -0,0 +1,121 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let node_path = require("node:path");
3
+ let node_crypto = require("node:crypto");
4
+ let node_worker_threads = require("node:worker_threads");
5
+ // src/util/is-record.ts
6
+ /**
7
+ * Whether a value is an object read by its keys: not null, not an array.
8
+ *
9
+ * @param value - The value to check
10
+ * @returns Whether it can be read as a record of fields
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * isRecord({ sources: {} }); // true
15
+ * isRecord(["shop"]); // false
16
+ * ```
17
+ */
18
+ function isRecord(value) {
19
+ return typeof value === "object" && value !== null && !Array.isArray(value);
20
+ }
21
+ // src/jest/routes-thread-messages.ts
22
+ /**
23
+ * Whether a message is the thread's answer.
24
+ *
25
+ * @param message - What came over the channel
26
+ * @returns Whether it is an answer
27
+ * @example
28
+ * ```ts
29
+ * if (isRoutesThreadReply(message)) settle(message);
30
+ * ```
31
+ */
32
+ function isRoutesThreadReply(message) {
33
+ if (!isRecord(message)) return false;
34
+ if (message.ok === true) return typeof message.module === "string" && typeof message.commonJs === "string";
35
+ return message.ok === false && typeof message.message === "string";
36
+ }
37
+ // src/jest/read-routes-sync.ts
38
+ /** How long a reading may take: the engine's own limit, and time to start the thread. */
39
+ const WAIT_MS = 75e3;
40
+ let thread;
41
+ /**
42
+ * Read the routes of a project as Jest asks a transformer for them: at once.
43
+ * The engine runs on a thread of its own, which reads the config in a
44
+ * process of its own as every bundler's plugin does; this one waits for its
45
+ * answer. The thread keeps the routes it read, so a later call, made while
46
+ * nothing they came from has changed, is answered without reading again.
47
+ *
48
+ * @param request - Where the project is
49
+ * @returns The routes module, or why there is none
50
+ * @example
51
+ * ```ts
52
+ * const reply = readRoutesSync({ root: options.config.rootDir });
53
+ * ```
54
+ */
55
+ function readRoutesSync(request) {
56
+ const signal = new Int32Array(new SharedArrayBuffer(4));
57
+ const { port1, port2 } = new node_worker_threads.MessageChannel();
58
+ routesThread().postMessage({
59
+ request,
60
+ port: port2,
61
+ signal
62
+ }, [port2]);
63
+ const waited = Atomics.wait(signal, 0, 0, WAIT_MS);
64
+ const reply = (0, node_worker_threads.receiveMessageOnPort)(port1)?.message;
65
+ port1.close();
66
+ if (isRoutesThreadReply(reply)) return reply;
67
+ return {
68
+ ok: false,
69
+ message: `The routes were not read: the thread that reads them gave ${waited === "timed-out" ? `no answer in ${WAIT_MS / 1e3} s` : "no answer"}`
70
+ };
71
+ }
72
+ function routesThread() {
73
+ if (thread !== void 0) return thread;
74
+ const started = new node_worker_threads.Worker((0, node_path.join)(__dirname, "routes-thread.js"));
75
+ started.unref();
76
+ started.once("exit", () => {
77
+ if (thread === started) thread = void 0;
78
+ });
79
+ thread = started;
80
+ return started;
81
+ }
82
+ // src/jest/routes-transformer.ts
83
+ /**
84
+ * The Jest transformer that builds the routes a proxy client may call from
85
+ * `courier.config.ts`, in place of the module `@noverax/courier/routes`
86
+ * ships, as CommonJS or as an ES module, as the test runs. The config is
87
+ * read in a process of its own, as every bundler's plugin reads it, from
88
+ * Jest's `rootDir`, once in each worker while nothing it read changes; the
89
+ * cache key is the module itself, so a run after an edit, or under another
90
+ * value of a variable the config read, never takes the routes of the last.
91
+ *
92
+ * @param options - Where the config is, when it is not found from `rootDir`
93
+ * @returns The transformer
94
+ * @throws Error from each method when the routes cannot be read, with why
95
+ * @example
96
+ * ```js
97
+ * // jest.config.js
98
+ * export default {
99
+ * transform: { "/node_modules/@noverax/courier/lib/routes/index\\.js$": "@noverax/courier-build/jest" },
100
+ * transformIgnorePatterns: ["/node_modules/(?!@noverax/courier/)"],
101
+ * };
102
+ * ```
103
+ */
104
+ function createTransformer(options = {}) {
105
+ const moduleFor = (transformOptions) => {
106
+ return codeOf(readRoutesSync({
107
+ root: transformOptions.config.rootDir,
108
+ config: options.config
109
+ }), transformOptions.supportsStaticESM);
110
+ };
111
+ return {
112
+ canInstrument: false,
113
+ getCacheKey: (_source, _path, transformOptions) => (0, node_crypto.createHash)("sha256").update(moduleFor(transformOptions)).digest("hex"),
114
+ process: (_source, _path, transformOptions) => ({ code: moduleFor(transformOptions) })
115
+ };
116
+ }
117
+ function codeOf(reply, esm) {
118
+ if (!reply.ok) throw new Error(`[courier] ${reply.message}`);
119
+ return esm ? reply.module : reply.commonJs;
120
+ }
121
+ exports.createTransformer = createTransformer;
@@ -0,0 +1,39 @@
1
+ import { t as CourierRoutesOptions } from "../courier-routes-options-BYF7Sfdf.cjs";
2
+ /** What Jest hands a transformer of a module: the part the routes transformer reads. */
3
+ export interface RoutesTransformOptions {
4
+ readonly config: {
5
+ readonly rootDir: string;
6
+ };
7
+ /** Whether the test runs as an ES module, under `--experimental-vm-modules`. */
8
+ readonly supportsStaticESM: boolean;
9
+ }
10
+ /** The transformer Jest runs on the module `@noverax/courier/routes` ships. */
11
+ export interface RoutesTransformer {
12
+ readonly canInstrument: false;
13
+ getCacheKey(source: string, path: string, options: RoutesTransformOptions): string;
14
+ process(source: string, path: string, options: RoutesTransformOptions): {
15
+ code: string;
16
+ };
17
+ }
18
+ /**
19
+ * The Jest transformer that builds the routes a proxy client may call from
20
+ * `courier.config.ts`, in place of the module `@noverax/courier/routes`
21
+ * ships, as CommonJS or as an ES module, as the test runs. The config is
22
+ * read in a process of its own, as every bundler's plugin reads it, from
23
+ * Jest's `rootDir`, once in each worker while nothing it read changes; the
24
+ * cache key is the module itself, so a run after an edit, or under another
25
+ * value of a variable the config read, never takes the routes of the last.
26
+ *
27
+ * @param options - Where the config is, when it is not found from `rootDir`
28
+ * @returns The transformer
29
+ * @throws Error from each method when the routes cannot be read, with why
30
+ * @example
31
+ * ```js
32
+ * // jest.config.js
33
+ * export default {
34
+ * transform: { "/node_modules/@noverax/courier/lib/routes/index\\.js$": "@noverax/courier-build/jest" },
35
+ * transformIgnorePatterns: ["/node_modules/(?!@noverax/courier/)"],
36
+ * };
37
+ * ```
38
+ */
39
+ export declare function createTransformer(options?: CourierRoutesOptions): RoutesTransformer;