@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,108 @@
1
+ import { t as packageFile } from "./package-file-CVAKY_Nu.js";
2
+ import { t as RoutesReadError } from "./routes-read-error-DQhKIE2F.js";
3
+ import { t as configFileOf } from "./config-file-of-Clf_dWUQ.js";
4
+ import { createRequire } from "node:module";
5
+ import { join, resolve } from "node:path";
6
+ import { realpathSync } from "node:fs";
7
+ // src/webpack/routes-loader-file.ts
8
+ /**
9
+ * The loader this package ships, as webpack, Rspack and Turbopack load it:
10
+ * a CommonJS module, by its absolute path.
11
+ *
12
+ * @returns The loader file
13
+ * @throws Error when the package's own folder cannot be found
14
+ * @example
15
+ * ```ts
16
+ * routesLoaderFile(); // "/app/node_modules/@noverax/courier-build/lib/webpack/loader.cjs"
17
+ * ```
18
+ */
19
+ function routesLoaderFile() {
20
+ const file = packageFile("lib/webpack/loader.cjs");
21
+ if (file === void 0) throw new Error("[courier] @noverax/courier-build cannot find the folder it is installed in: install it again.");
22
+ return file;
23
+ }
24
+ // src/webpack/config-in-page-rule.ts
25
+ /**
26
+ * The rule that fails a page's bundle where it takes the project's config
27
+ * as a module: it would ship the server's settings and every API's
28
+ * definition to the browser. Only for the bundles a browser runs; a server's
29
+ * may import the config.
30
+ *
31
+ * @param project - The project, as `routesProject()` gives it
32
+ * @returns The rule, for the `module.rules` of a page's bundle; none when the project has no config yet
33
+ * @example
34
+ * ```ts
35
+ * const rule = configInPageRule(project);
36
+ * if (rule && !isServer) config.module.rules.push(rule);
37
+ * ```
38
+ */
39
+ function configInPageRule(project) {
40
+ if (project.configFile === void 0) return void 0;
41
+ return {
42
+ test: project.configFile,
43
+ use: [{
44
+ loader: routesLoaderFile(),
45
+ options: {
46
+ root: project.root,
47
+ config: project.config,
48
+ refuse: "config-in-page"
49
+ }
50
+ }]
51
+ };
52
+ }
53
+ // src/webpack/routes-project.ts
54
+ /**
55
+ * The project a bundler builds, as its routes rules need it.
56
+ *
57
+ * @param root - The project's root, where the bundler resolves its packages from
58
+ * @param options - The plugin's options
59
+ * @returns The project
60
+ * @throws RoutesReadError `CORE_NOT_FOUND` when `@noverax/courier` does not resolve from the root
61
+ * @example
62
+ * ```ts
63
+ * routesProject("/app"); // { root: "/app", stubFile: "/app/node_modules/@noverax/courier/lib/routes/index.js", configFile: "/app/courier.config.ts" }
64
+ * ```
65
+ */
66
+ function routesProject(root, options = {}) {
67
+ const config = options.config === void 0 ? void 0 : resolve(root, options.config);
68
+ return {
69
+ root,
70
+ config,
71
+ stubFile: stubFileOf(root),
72
+ configFile: configFileOf(root, config)
73
+ };
74
+ }
75
+ function stubFileOf(root) {
76
+ try {
77
+ return realpathSync(createRequire(join(root, "package.json")).resolve("@noverax/courier/routes"));
78
+ } catch (error) {
79
+ throw new RoutesReadError("CORE_NOT_FOUND", `@noverax/courier does not resolve from ${root}, so there is no routes module to build the routes in place of. Install it in the project, beside @noverax/courier-build.`, { root }, { stack: error instanceof Error ? error.stack : void 0 });
80
+ }
81
+ }
82
+ // src/webpack/routes-rule.ts
83
+ /**
84
+ * The rule that builds the routes in place of the module
85
+ * `@noverax/courier/routes` ships, for every bundle the bundler builds: a
86
+ * page's and a server's alike.
87
+ *
88
+ * @param project - The project, as `routesProject()` gives it
89
+ * @returns The rule, for the bundler's `module.rules`
90
+ * @example
91
+ * ```ts
92
+ * config.module.rules.push(routesRule(routesProject(config.context)));
93
+ * ```
94
+ */
95
+ function routesRule(project) {
96
+ return {
97
+ test: project.stubFile,
98
+ enforce: "pre",
99
+ use: [{
100
+ loader: routesLoaderFile(),
101
+ options: {
102
+ root: project.root,
103
+ config: project.config
104
+ }
105
+ }]
106
+ };
107
+ }
108
+ export { routesLoaderFile as i, routesProject as n, configInPageRule as r, routesRule as t };
@@ -0,0 +1,41 @@
1
+ import { t as CourierRoutesOptions } from "../courier-routes-options-BYF7Sfdf.js";
2
+ /** What of an Rspack config the plugin adds its rules to. */
3
+ interface RulesConfig {
4
+ module?: {
5
+ rules?: unknown[];
6
+ } | undefined;
7
+ }
8
+ /** What of Rsbuild's plugin API the plugin uses. */
9
+ interface RoutesRsbuildApi {
10
+ readonly context: {
11
+ readonly rootPath: string;
12
+ };
13
+ modifyRspackConfig(modify: (config: RulesConfig, utils: {
14
+ readonly isServer: boolean;
15
+ }) => void): void;
16
+ }
17
+ /** An Rsbuild plugin, as Rsbuild takes it in `plugins`. */
18
+ interface RoutesRsbuildPlugin {
19
+ readonly name: string;
20
+ setup(api: RoutesRsbuildApi): void;
21
+ }
22
+ /**
23
+ * The Rsbuild plugin that builds the routes a page's proxy client may call
24
+ * from `courier.config.ts`, in place of the module `@noverax/courier/routes`
25
+ * ships, for every environment Rsbuild builds: a page's and a server's
26
+ * alike. No routes file is written. A page's bundle that takes the config
27
+ * itself fails.
28
+ *
29
+ * @param options - Where the config is, when it is not found from the project's root
30
+ * @returns The plugin
31
+ * @example
32
+ * ```ts
33
+ * // rsbuild.config.ts
34
+ * import { courierRoutes } from "@noverax/courier-build/rsbuild";
35
+ * import { defineConfig } from "@rsbuild/core";
36
+ *
37
+ * export default defineConfig({ plugins: [courierRoutes()] });
38
+ * ```
39
+ */
40
+ export declare function courierRoutes(options?: CourierRoutesOptions): RoutesRsbuildPlugin;
41
+ export type { CourierRoutesOptions, RoutesRsbuildApi, RoutesRsbuildPlugin };
@@ -0,0 +1,37 @@
1
+ import { n as routesProject, r as configInPageRule, t as routesRule } from "../routes-rule-Pu1Tz8Xm.js";
2
+ // src/rsbuild/courier-routes.ts
3
+ /**
4
+ * The Rsbuild 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, for every environment Rsbuild builds: a page's and a server's
7
+ * alike. No routes file is written. A page's bundle that takes the config
8
+ * itself fails.
9
+ *
10
+ * @param options - Where the config is, when it is not found from the project's root
11
+ * @returns The plugin
12
+ * @example
13
+ * ```ts
14
+ * // rsbuild.config.ts
15
+ * import { courierRoutes } from "@noverax/courier-build/rsbuild";
16
+ * import { defineConfig } from "@rsbuild/core";
17
+ *
18
+ * export default defineConfig({ plugins: [courierRoutes()] });
19
+ * ```
20
+ */
21
+ function courierRoutes(options = {}) {
22
+ return {
23
+ name: "courier-routes",
24
+ setup(api) {
25
+ const project = routesProject(api.context.rootPath, options);
26
+ api.modifyRspackConfig((config, { isServer }) => {
27
+ config.module ??= {};
28
+ config.module.rules ??= [];
29
+ const rules = config.module.rules;
30
+ rules.push(routesRule(project));
31
+ const guard = configInPageRule(project);
32
+ if (guard !== void 0 && !isServer) rules.push(guard);
33
+ });
34
+ }
35
+ };
36
+ }
37
+ export { courierRoutes };
@@ -0,0 +1,3 @@
1
+ import { t as CourierRoutesOptions } from "../courier-routes-options-BYF7Sfdf.js";
2
+ import { n as CourierRoutesPlugin, r as RoutesCompiler } from "../index-_VUU5N5v.js";
3
+ export { type CourierRoutesOptions, CourierRoutesPlugin, type RoutesCompiler };
@@ -0,0 +1 @@
1
+ export { t as CourierRoutesPlugin } from "../webpack-BPxhBYGV.js";
@@ -0,0 +1,28 @@
1
+ import { i as createRoutesEngine } from "./routes-module-code-DYtP7uj1.js";
2
+ // src/engine/shared-engine.ts
3
+ const engines = /* @__PURE__ */ new Map();
4
+ /**
5
+ * The engine for a project, made once per process for the same root, config
6
+ * and env files, and shared by every plugin made for them.
7
+ *
8
+ * @param options - Where the project and its config are, and its env files
9
+ * @returns The engine those options name
10
+ * @example
11
+ * ```ts
12
+ * sharedEngine({ root: "/app", envFiles: ["/app/.env"] }) === sharedEngine({ root: "/app", envFiles: ["/app/.env"] }); // true
13
+ * ```
14
+ */
15
+ function sharedEngine(options) {
16
+ const key = JSON.stringify([
17
+ options.root,
18
+ options.config ?? null,
19
+ options.envFiles ?? []
20
+ ]);
21
+ let engine = engines.get(key);
22
+ if (engine === void 0) {
23
+ engine = createRoutesEngine(options);
24
+ engines.set(key, engine);
25
+ }
26
+ return engine;
27
+ }
28
+ export { sharedEngine as t };
@@ -0,0 +1,33 @@
1
+ import { Plugin } from "vite";
2
+ /** Options of the routes plugin. */
3
+ interface CourierRoutesOptions {
4
+ /**
5
+ * The config file, from Vite's root or absolute, when it is not the
6
+ * `courier.config.*` in the root or the nearest folder above it.
7
+ */
8
+ config?: string | undefined;
9
+ }
10
+ /**
11
+ * The Vite plugin that hands every page, and every server rendering one,
12
+ * the routes its proxy client may call: read from `courier.config.ts` by
13
+ * the rules the proxy applies, in place of the module
14
+ * `@noverax/courier/routes` ships. No routes file is written, and the
15
+ * routes carry no address, path or middleware; a page that imports the
16
+ * config itself fails the build.
17
+ *
18
+ * A build that cannot read the config fails with why; the dev server reads
19
+ * it again whenever something it came from changes, and Vitest, which runs
20
+ * tests through Vite, reruns the tests that import the routes.
21
+ *
22
+ * @param options - Where the config is, when it is not found from the root
23
+ * @returns The plugin
24
+ * @example
25
+ * ```ts
26
+ * // vite.config.ts
27
+ * import { courierRoutes } from "@noverax/courier-build/vite";
28
+ *
29
+ * export default defineConfig({ plugins: [courierRoutes()] });
30
+ * ```
31
+ */
32
+ export declare function courierRoutes(options?: CourierRoutesOptions): Plugin;
33
+ export type { CourierRoutesOptions };
@@ -0,0 +1,357 @@
1
+ import { t as RoutesReadError } from "../routes-read-error-DQhKIE2F.js";
2
+ import { n as routesModuleCode, r as unreadRoutesModuleCode } from "../routes-module-code-DYtP7uj1.js";
3
+ import { n as isRecord } from "../errno-code-CNx_E80R.js";
4
+ import { t as configInPageMessage } from "../config-in-page-message-DLd3f0Jd.js";
5
+ import { T as configCandidates, h as localPath } from "../load-config-barNPfXN.js";
6
+ import { t as sharedEngine } from "../shared-engine-D0j7uXg8.js";
7
+ import { t as configFileOf } from "../config-file-of-Clf_dWUQ.js";
8
+ import { n as ROUTES_PATTERN, r as ROUTES_SPECIFIER, t as ROUTES_MODULE_ID } from "../routes-module-id-CnUjQjYF.js";
9
+ import { basename, dirname, isAbsolute, join, resolve, sep } from "node:path";
10
+ import { existsSync, statSync } from "node:fs";
11
+ import { loadEnv } from "vite";
12
+ // src/vite/config-in-page.ts
13
+ /**
14
+ * Refuse the project's config as a module of a page. It holds the server's
15
+ * settings and the definition of every API — their addresses, their
16
+ * middleware, what reads their secrets — and imported as a value it ships
17
+ * all of them to the browser. A page needs its type alone, which an
18
+ * `import type` gives without a byte of it.
19
+ *
20
+ * @param context - Vite's plugin context
21
+ * @param id - The module Vite is transforming
22
+ * @param options - What Vite before 6 says of the module: `ssr` for a server's
23
+ * @param configFile - The project's config, absolute
24
+ * @example
25
+ * ```ts
26
+ * transform(code, id, options) { refuseConfigInPage(this, id, options, configFile); }
27
+ * ```
28
+ */
29
+ function refuseConfigInPage(context, id, options, configFile) {
30
+ if (configFile === void 0 || !isPageModule(context, options)) return;
31
+ if (withSlashes(id.split("?")[0] ?? id) !== withSlashes(configFile)) return;
32
+ context.error(configInPageMessage(configFile));
33
+ }
34
+ function isPageModule(context, options) {
35
+ const consumer = context.environment?.config.consumer;
36
+ return consumer === void 0 ? options?.ssr !== true : consumer === "client";
37
+ }
38
+ function withSlashes(path) {
39
+ return path.split("\\").join("/");
40
+ }
41
+ // src/vite/watch-once.ts
42
+ const given = /* @__PURE__ */ new WeakMap();
43
+ /**
44
+ * Hand a dev server's watcher each path it does not follow yet. The routes
45
+ * module is loaded again on every edit, and follows the same paths each
46
+ * time: each is given to each server once. A path the watcher follows
47
+ * already, as it follows Vite's root, is not given at all: chokidar reads a
48
+ * folder it is handed as one just added, and an entry made since the routes
49
+ * were read would pass for one that was there, with no notice of it. A
50
+ * watcher that stops at a depth lists the folders below it unread: it is
51
+ * handed every path.
52
+ *
53
+ * @param server - The dev server
54
+ * @param paths - The paths to watch, there or not
55
+ * @example
56
+ * ```ts
57
+ * watchOnce(server, ["/app/courier.config.ts", "/app/.env.local"]);
58
+ * ```
59
+ */
60
+ function watchOnce(server, paths) {
61
+ let seen = given.get(server);
62
+ if (seen === void 0) {
63
+ seen = /* @__PURE__ */ new Set();
64
+ given.set(server, seen);
65
+ }
66
+ const fresh = [...paths].filter((path) => !seen.has(path));
67
+ for (const path of fresh) seen.add(path);
68
+ if (fresh.length === 0) return;
69
+ const watched = server.watcher.options.depth === void 0 ? server.watcher.getWatched() : {};
70
+ const unfollowed = fresh.filter((path) => {
71
+ const local = localPath(path);
72
+ return !watched[dirname(local)]?.includes(basename(local));
73
+ });
74
+ if (unfollowed.length > 0) server.watcher.add(unfollowed);
75
+ }
76
+ // src/vite/follow-routes-inputs.ts
77
+ /**
78
+ * Follow what a reading of the routes read, so that a change to it loads
79
+ * the routes module again. Each file is watched for the module, and Vite
80
+ * loads it again once one changes; every path, there or not, is followed by
81
+ * each dev server's watcher, which loads it again once one appears or goes,
82
+ * and so is every entry of a folder the reading listed. Installed packages
83
+ * are left to the install.
84
+ *
85
+ * @param context - Vite's plugin context, loading the routes module
86
+ * @param state - What the plugin knows, whose followed paths this replaces
87
+ * @param inputs - What the reading read, whether it gave routes or failed
88
+ * @example
89
+ * ```ts
90
+ * followRoutesInputs(this, state, snapshot);
91
+ * ```
92
+ */
93
+ function followRoutesInputs(context, state, inputs) {
94
+ const followed = /* @__PURE__ */ new Set();
95
+ for (const path of inputs.files) {
96
+ if (isInstalled(path)) continue;
97
+ followed.add(path);
98
+ if (isFile(path)) context.addWatchFile(path);
99
+ }
100
+ const listed = new Set((inputs.listed ?? []).filter((folder) => !isInstalled(folder)));
101
+ state.followed = followed;
102
+ state.listed = listed;
103
+ for (const server of state.servers) watchOnce(server, [...followed, ...listed]);
104
+ }
105
+ function isInstalled(path) {
106
+ return path.split(sep).includes("node_modules");
107
+ }
108
+ function isFile(path) {
109
+ try {
110
+ return statSync(path, { throwIfNoEntry: false })?.isFile() === true;
111
+ } catch {
112
+ return false;
113
+ }
114
+ }
115
+ // src/vite/load-routes-module.ts
116
+ /**
117
+ * The routes module for a page: the routes read from the config, with every
118
+ * project path they came from followed, so a change loads the module again.
119
+ *
120
+ * When they cannot be read, what the failed reading read is followed
121
+ * instead, so mending the config or a module it imports brings the routes
122
+ * back, in a dev server and in a build that watches alike. A build then
123
+ * fails with why. A dev server keeps the routes it read last, and reports
124
+ * why in its terminal and on the page; with none read yet, the module fails
125
+ * where it is imported, with the reason.
126
+ *
127
+ * @param context - Vite's plugin context, which watches files and fails builds
128
+ * @param state - What the plugin knows of the Vite it runs in
129
+ * @returns The module's code
130
+ * @example
131
+ * ```ts
132
+ * load(id) { return id === ROUTES_MODULE_ID ? loadRoutesModule(this, state) : undefined; }
133
+ * ```
134
+ */
135
+ async function loadRoutesModule(context, state) {
136
+ const engine = state.engine;
137
+ if (engine === void 0) throw new RoutesReadError("ROUTES_NOT_READ", "The routes were asked for before Vite resolved its config", { root: process.cwd() });
138
+ try {
139
+ const snapshot = await engine.read();
140
+ followRoutesInputs(context, state, snapshot);
141
+ return routesModuleCode(snapshot);
142
+ } catch (error) {
143
+ const failure = asError(error);
144
+ const followed = (failure instanceof RoutesReadError ? failure.inputs : void 0) ?? engine.last;
145
+ if (followed !== void 0) followRoutesInputs(context, state, followed);
146
+ if (state.config?.command === "build") context.error(failure);
147
+ report(state, failure);
148
+ const last = engine.last;
149
+ return last === void 0 ? unreadRoutesModuleCode(failure.message) : routesModuleCode(last);
150
+ }
151
+ }
152
+ function report(state, error) {
153
+ const message = `[courier] ${error.message}`;
154
+ state.config?.logger.error(message, {
155
+ timestamp: true,
156
+ error
157
+ });
158
+ for (const server of state.servers) server.ws.send({
159
+ type: "error",
160
+ err: {
161
+ message,
162
+ stack: error.stack ?? ""
163
+ }
164
+ });
165
+ }
166
+ function asError(error) {
167
+ return error instanceof Error ? error : new Error(String(error));
168
+ }
169
+ // src/vite/vite-env-files.ts
170
+ /**
171
+ * The env files Vite reads for a mode, in the order the first to set a
172
+ * variable wins: the mode's local file, the mode's, the local one, then
173
+ * `.env` — the variables the app's own `import.meta.env` is given.
174
+ *
175
+ * @param settings - Vite's root, mode and env folder
176
+ * @returns The files, absolute; none when the env folder is turned off
177
+ * @example
178
+ * ```ts
179
+ * viteEnvFiles({ root: "/app", mode: "production" });
180
+ * // → ["/app/.env.production.local", "/app/.env.production", "/app/.env.local", "/app/.env"]
181
+ * ```
182
+ */
183
+ function viteEnvFiles({ root, mode, envDir }) {
184
+ if (envDir === false) return [];
185
+ const dir = envDir ?? root;
186
+ return [
187
+ `.env.${mode}.local`,
188
+ `.env.${mode}`,
189
+ ".env.local",
190
+ ".env"
191
+ ].map((name) => join(dir, name));
192
+ }
193
+ // src/vite/vite-env-values.ts
194
+ /**
195
+ * The variables Vite's env files give for a mode, with the values Vite gives
196
+ * them — `${VAR}` and `${VAR:-default}` filled in as it fills them — for
197
+ * each the process was not started with: what a reading of the routes puts
198
+ * into `process.env` before the config runs, so it sees what the app sees.
199
+ *
200
+ * @param settings - Vite's root, mode and env folder
201
+ * @returns The variables; none when the env folder is turned off
202
+ * @example
203
+ * ```ts
204
+ * viteEnvValues({ root: "/app", mode: "production" }); // { API_URL: "https://api.example.com/v1" }
205
+ * ```
206
+ */
207
+ function viteEnvValues({ root, mode, envDir }) {
208
+ if (envDir === false) return {};
209
+ const loaded = loadEnv(mode, envDir ?? root, "");
210
+ return Object.fromEntries(Object.entries(loaded).filter(([key]) => !(key in process.env)));
211
+ }
212
+ // src/vite/watch-routes-inputs.ts
213
+ const PATH_EVENTS = [
214
+ "add",
215
+ "unlink",
216
+ "addDir",
217
+ "unlinkDir"
218
+ ];
219
+ /**
220
+ * Load the routes module again when a path the routes depend on appears or
221
+ * goes, which no change of a watched file reports: a config saved where one
222
+ * is looked for, inside Vite's root or above it, or the config removed; an
223
+ * env file or a module made where the latest reading looked for one; an
224
+ * entry added to or removed from a folder it listed.
225
+ *
226
+ * Vite watches with chokidar 3, which reads a folder it was handed missing
227
+ * files in no more than once a second, and then for the first of them
228
+ * alone: a config saved there under another of its names was never
229
+ * reported. Each raw event of a folder a config may be in looks at every
230
+ * place there a config is looked for, and a config that appears or goes is
231
+ * told once, however many events report it.
232
+ *
233
+ * @param server - A dev server the plugin serves
234
+ * @param state - What the plugin knows: the paths the latest reading followed
235
+ * @param config - Where the config may be, and what to do when it moves
236
+ * @example
237
+ * ```ts
238
+ * configureServer(server) {
239
+ * watchRoutesInputs(server, state, { candidates: configCandidates(root), onChange });
240
+ * }
241
+ * ```
242
+ */
243
+ function watchRoutesInputs(server, state, config) {
244
+ const candidates = new Set(config.candidates);
245
+ watchOnce(server, candidates);
246
+ const there = new Map([...candidates].map((path) => [path, existsSync(path)]));
247
+ const onPath = (path) => {
248
+ const isConfig = candidates.has(path);
249
+ if (!isConfig && !state.followed.has(path) && !state.listed.has(dirname(path))) return;
250
+ if (isConfig) {
251
+ const now = existsSync(path);
252
+ if (there.get(path) === now) return;
253
+ there.set(path, now);
254
+ config.onChange();
255
+ }
256
+ reloadRoutes(server);
257
+ };
258
+ for (const event of PATH_EVENTS) server.watcher.on(event, onPath);
259
+ const folders = new Set([...candidates].map((path) => dirname(path)));
260
+ server.watcher.on("raw", (_event, path, details) => {
261
+ const folder = eventFolder(path, details);
262
+ if (folder === void 0 || !folders.has(folder)) return;
263
+ for (const candidate of candidates) if (dirname(candidate) === folder) onPath(candidate);
264
+ });
265
+ }
266
+ function eventFolder(path, details) {
267
+ const watched = isRecord(details) ? details.watchedPath : void 0;
268
+ const folder = typeof watched === "string" ? watched : path;
269
+ return isAbsolute(folder) ? resolve(folder) : void 0;
270
+ }
271
+ function reloadRoutes(server) {
272
+ const environments = server.environments;
273
+ const graphs = environments === void 0 ? [] : Object.values(environments).map((each) => each.moduleGraph);
274
+ for (const graph of graphs) invalidateRoutes(graph);
275
+ if (environments === void 0) invalidateRoutes(server.moduleGraph);
276
+ server.ws.send({ type: "full-reload" });
277
+ }
278
+ function invalidateRoutes(graph) {
279
+ const module = graph.getModuleById(ROUTES_MODULE_ID);
280
+ if (module !== void 0) graph.invalidateModule(module);
281
+ }
282
+ // src/vite/courier-routes.ts
283
+ /**
284
+ * The Vite plugin that hands every page, and every server rendering one,
285
+ * the routes its proxy client may call: read from `courier.config.ts` by
286
+ * the rules the proxy applies, in place of the module
287
+ * `@noverax/courier/routes` ships. No routes file is written, and the
288
+ * routes carry no address, path or middleware; a page that imports the
289
+ * config itself fails the build.
290
+ *
291
+ * A build that cannot read the config fails with why; the dev server reads
292
+ * it again whenever something it came from changes, and Vitest, which runs
293
+ * tests through Vite, reruns the tests that import the routes.
294
+ *
295
+ * @param options - Where the config is, when it is not found from the root
296
+ * @returns The plugin
297
+ * @example
298
+ * ```ts
299
+ * // vite.config.ts
300
+ * import { courierRoutes } from "@noverax/courier-build/vite";
301
+ *
302
+ * export default defineConfig({ plugins: [courierRoutes()] });
303
+ * ```
304
+ */
305
+ function courierRoutes(options = {}) {
306
+ const state = {
307
+ servers: /* @__PURE__ */ new Set(),
308
+ followed: /* @__PURE__ */ new Set(),
309
+ listed: /* @__PURE__ */ new Set(),
310
+ underVitest: false
311
+ };
312
+ return {
313
+ name: "courier-routes",
314
+ enforce: "pre",
315
+ config: () => ({
316
+ resolve: { alias: [{
317
+ find: ROUTES_PATTERN,
318
+ replacement: ROUTES_MODULE_ID
319
+ }] },
320
+ optimizeDeps: { exclude: [ROUTES_SPECIFIER] }
321
+ }),
322
+ configResolved: (config) => resolveRoutes(state, config, options.config),
323
+ configureServer: (server) => serveRoutes(state, server, options.config),
324
+ resolveId: (id) => id === "@noverax/courier/routes" || id === "\0@noverax/courier/routes" ? ROUTES_MODULE_ID : void 0,
325
+ load(id) {
326
+ return id === "\0@noverax/courier/routes" ? loadRoutesModule(this, state) : void 0;
327
+ },
328
+ transform(_code, id, transformOptions) {
329
+ if (!state.underVitest) refuseConfigInPage(this, id, transformOptions, state.configFile);
330
+ },
331
+ configureVitest(context) {
332
+ state.underVitest = true;
333
+ (context.defineCacheKeyGenerator ?? context.experimental_defineCacheKeyGenerator)?.(({ id }) => id === "\0@noverax/courier/routes" ? false : void 0);
334
+ }
335
+ };
336
+ }
337
+ function resolveRoutes(state, config, named) {
338
+ state.config = config;
339
+ state.engine = sharedEngine({
340
+ root: config.root,
341
+ config: named,
342
+ envFiles: viteEnvFiles(config),
343
+ envValues: () => viteEnvValues(config)
344
+ });
345
+ state.configFile = configFileOf(config.root, named);
346
+ }
347
+ function serveRoutes(state, server, named) {
348
+ state.servers.add(server);
349
+ const root = server.config.root;
350
+ watchRoutesInputs(server, state, {
351
+ candidates: named === void 0 ? configCandidates(root) : [resolve(root, named)],
352
+ onChange: () => {
353
+ state.configFile = configFileOf(server.config.root, named);
354
+ }
355
+ });
356
+ }
357
+ export { courierRoutes };
@@ -0,0 +1,4 @@
1
+ import { t as CourierRoutesOptions } from "../courier-routes-options-BYF7Sfdf.js";
2
+ import { a as RoutesLoaderRule, c as RoutesProject, i as configInPageRule, l as routesProject, n as CourierRoutesPlugin, o as routesRule, r as RoutesCompiler, s as RoutesLoaderOptions, t as routesLoaderFile } from "../index-_VUU5N5v.js";
3
+ import { t as RoutesLoaderContext } from "../routes-loader-context-40OKDrQp.js";
4
+ export { type CourierRoutesOptions, CourierRoutesPlugin, type RoutesCompiler, type RoutesLoaderContext, type RoutesLoaderOptions, type RoutesLoaderRule, type RoutesProject, configInPageRule, routesLoaderFile, routesProject, routesRule };
@@ -0,0 +1,3 @@
1
+ import { i as routesLoaderFile, n as routesProject, r as configInPageRule, t as routesRule } from "../routes-rule-Pu1Tz8Xm.js";
2
+ import { t as CourierRoutesPlugin } from "../webpack-BPxhBYGV.js";
3
+ export { CourierRoutesPlugin, configInPageRule, routesLoaderFile, routesProject, routesRule };
@@ -0,0 +1,31 @@
1
+ let node_path = require("node:path");
2
+ let node_url = require("node:url");
3
+ // src/webpack/loader.ts
4
+ /**
5
+ * The loader webpack, Rspack and Turbopack run on the module
6
+ * `@noverax/courier/routes` ships, which they load as CommonJS: it hands the
7
+ * work to the package's ES modules. Its result is never kept for the same
8
+ * inputs, as the routes may follow a variable the config reads, which no
9
+ * bundler sees; the engine reads them again only when what they came from
10
+ * has changed.
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * config.module.rules.push({ test: stubFile, use: [{ loader: routesLoaderFile(), options: { root } }] });
15
+ * ```
16
+ */
17
+ function courierRoutesLoader() {
18
+ this.cacheable(false);
19
+ const done = this.async();
20
+ import((0, node_url.pathToFileURL)((0, node_path.join)(__dirname, "run-routes-loader.js")).href).then((module) => runnerOf(module)(this)).then((code) => done(null, code), (error) => done(error instanceof Error ? error : new Error(String(error))));
21
+ }
22
+ function runnerOf(module) {
23
+ const run = typeof module === "object" && module !== null ? Reflect.get(module, "runRoutesLoader") : void 0;
24
+ if (typeof run !== "function") throw new Error("[courier] @noverax/courier-build is installed incompletely: install it again.");
25
+ return async (context) => {
26
+ const code = await Reflect.apply(run, void 0, [context]);
27
+ if (typeof code !== "string") throw new Error("[courier] The routes loader built no module.");
28
+ return code;
29
+ };
30
+ }
31
+ module.exports = courierRoutesLoader;