@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,25 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let node_path = require("node:path");
3
+ let node_url = require("node:url");
4
+ // src/metro/require-entry.ts
5
+ /**
6
+ * `withCourier()` for a `metro.config.js` that `require()`s it, as most do:
7
+ * built as CommonJS, it hands the work to the package's ES module beside it,
8
+ * which every Node the package runs on imports, `require(esm)` or not.
9
+ *
10
+ * @param config - The Metro config, or a promise of it
11
+ * @param options - Where the config is, and where the module is written
12
+ * @returns The config, with the resolver that answers the routes
13
+ * @example
14
+ * ```js
15
+ * const { withCourier } = require("@noverax/courier-build/metro");
16
+ * module.exports = withCourier(getDefaultConfig(__dirname));
17
+ * ```
18
+ */
19
+ async function withCourier(config, options = {}) {
20
+ const loaded = await import((0, node_url.pathToFileURL)((0, node_path.join)(__dirname, "index.js")).href);
21
+ const run = typeof loaded === "object" && loaded !== null ? Reflect.get(loaded, "withCourier") : void 0;
22
+ if (typeof run !== "function") throw new Error("[courier] @noverax/courier-build is installed incompletely: install it again.");
23
+ return await Reflect.apply(run, void 0, [config, options]);
24
+ }
25
+ exports.withCourier = withCourier;
@@ -0,0 +1,55 @@
1
+ import { t as CourierRoutesOptions } from "../courier-routes-options-BYF7Sfdf.cjs";
2
+ /** Where Metro found a module: the part the routes resolver reads. */
3
+ interface MetroResolution {
4
+ readonly type: string;
5
+ readonly filePath?: string | undefined;
6
+ }
7
+ /** What Metro hands a resolver: the part the routes resolver reads. */
8
+ interface MetroResolutionContext {
9
+ /** Whether the bundle is a development one: a build's fails where the routes were not read. */
10
+ readonly dev: boolean;
11
+ /** The module that imports the one resolved. */
12
+ readonly originModulePath: string;
13
+ /** Expo names a server's bundle here: `node`, or `react-server`. */
14
+ readonly customResolverOptions?: {
15
+ readonly environment?: unknown;
16
+ } | undefined;
17
+ /** The resolver the chain goes on with: Metro's own, or the one Expo wraps it in. */
18
+ readonly resolveRequest: MetroResolver;
19
+ }
20
+ /** A Metro resolver, as `resolver.resolveRequest` takes one. */
21
+ type MetroResolver = (context: MetroResolutionContext, moduleName: string, platform: string | null) => MetroResolution;
22
+ /** What of a Metro config the plugin reads and extends: Expo's `getDefaultConfig()` gives one. */
23
+ interface MetroConfigShape {
24
+ readonly projectRoot?: string | undefined;
25
+ readonly watchFolders?: readonly string[] | undefined;
26
+ readonly resolver?: {
27
+ readonly resolveRequest?: MetroResolver | null | undefined;
28
+ } | undefined;
29
+ }
30
+ /** Options of the routes plugin of Metro and Expo. */
31
+ interface MetroRoutesOptions extends CourierRoutesOptions {
32
+ /**
33
+ * The folder the routes module Metro bundles is written to, from the
34
+ * project's root or absolute: `node_modules/.cache/@noverax/courier` by
35
+ * default. Name another where `node_modules` cannot be written; one outside
36
+ * the project and its watched folders is watched too.
37
+ */
38
+ outDir?: string | undefined;
39
+ }
40
+ /**
41
+ * `withCourier()` for a `metro.config.js` that `require()`s it, as most do:
42
+ * built as CommonJS, it hands the work to the package's ES module beside it,
43
+ * which every Node the package runs on imports, `require(esm)` or not.
44
+ *
45
+ * @param config - The Metro config, or a promise of it
46
+ * @param options - Where the config is, and where the module is written
47
+ * @returns The config, with the resolver that answers the routes
48
+ * @example
49
+ * ```js
50
+ * const { withCourier } = require("@noverax/courier-build/metro");
51
+ * module.exports = withCourier(getDefaultConfig(__dirname));
52
+ * ```
53
+ */
54
+ export declare function withCourier<TConfig extends MetroConfigShape>(config: TConfig | Promise<TConfig>, options?: MetroRoutesOptions): Promise<TConfig & MetroConfigShape>;
55
+ export {};
@@ -0,0 +1,67 @@
1
+ import { t as CourierRoutesOptions } from "../courier-routes-options-BYF7Sfdf.js";
2
+ /** Where Metro found a module: the part the routes resolver reads. */
3
+ interface MetroResolution {
4
+ readonly type: string;
5
+ readonly filePath?: string | undefined;
6
+ }
7
+ /** What Metro hands a resolver: the part the routes resolver reads. */
8
+ interface MetroResolutionContext {
9
+ /** Whether the bundle is a development one: a build's fails where the routes were not read. */
10
+ readonly dev: boolean;
11
+ /** The module that imports the one resolved. */
12
+ readonly originModulePath: string;
13
+ /** Expo names a server's bundle here: `node`, or `react-server`. */
14
+ readonly customResolverOptions?: {
15
+ readonly environment?: unknown;
16
+ } | undefined;
17
+ /** The resolver the chain goes on with: Metro's own, or the one Expo wraps it in. */
18
+ readonly resolveRequest: MetroResolver;
19
+ }
20
+ /** A Metro resolver, as `resolver.resolveRequest` takes one. */
21
+ type MetroResolver = (context: MetroResolutionContext, moduleName: string, platform: string | null) => MetroResolution;
22
+ /** What of a Metro config the plugin reads and extends: Expo's `getDefaultConfig()` gives one. */
23
+ interface MetroConfigShape {
24
+ readonly projectRoot?: string | undefined;
25
+ readonly watchFolders?: readonly string[] | undefined;
26
+ readonly resolver?: {
27
+ readonly resolveRequest?: MetroResolver | null | undefined;
28
+ } | undefined;
29
+ }
30
+ /** Options of the routes plugin of Metro and Expo. */
31
+ interface MetroRoutesOptions extends CourierRoutesOptions {
32
+ /**
33
+ * The folder the routes module Metro bundles is written to, from the
34
+ * project's root or absolute: `node_modules/.cache/@noverax/courier` by
35
+ * default. Name another where `node_modules` cannot be written; one outside
36
+ * the project and its watched folders is watched too.
37
+ */
38
+ outDir?: string | undefined;
39
+ }
40
+ /**
41
+ * Have Metro — under Expo or React Native's CLI — bundle the routes a
42
+ * proxy client may call from `courier.config.ts`, in place of the module
43
+ * `@noverax/courier/routes` ships. The routes are read before Metro starts
44
+ * and written to a module of the app's own, which a dev server keeps
45
+ * current: a change to anything they came from reads them again, and Metro
46
+ * bundles the module anew. The routes carry no address, path or
47
+ * middleware. A bundle of the app that takes the config itself fails.
48
+ *
49
+ * A build fails while the routes cannot be read, with why; a development
50
+ * bundle keeps the routes read last, and with none read yet, the module
51
+ * fails where it is imported, saying why.
52
+ *
53
+ * @param config - The Metro config, as `getDefaultConfig()` gives it, or a promise of it
54
+ * @param options - Where the config is, and where the module is written
55
+ * @returns The config, with the resolver that answers the routes
56
+ * @throws RoutesReadError `ROUTES_NOT_WRITTEN` when the module's folder cannot be written
57
+ * @example
58
+ * ```js
59
+ * // metro.config.js
60
+ * const { getDefaultConfig } = require("expo/metro-config");
61
+ * const { withCourier } = require("@noverax/courier-build/metro");
62
+ *
63
+ * module.exports = withCourier(getDefaultConfig(__dirname));
64
+ * ```
65
+ */
66
+ export declare function withCourier<TConfig extends MetroConfigShape>(config: TConfig | Promise<TConfig>, options?: MetroRoutesOptions): Promise<TConfig & MetroConfigShape>;
67
+ export type { MetroConfigShape, MetroResolution, MetroResolutionContext, MetroResolver, MetroRoutesOptions };
@@ -0,0 +1,105 @@
1
+ import "../routes-module-id-CnUjQjYF.js";
2
+ import { t as routesFile } from "../routes-file-DRoqUZ0S.js";
3
+ import { basename, isAbsolute, join, relative, resolve } from "node:path";
4
+ import { realpathSync } from "node:fs";
5
+ // src/metro/routes-resolver.ts
6
+ const SERVER_ENVIRONMENTS = /* @__PURE__ */ new Set(["node", "react-server"]);
7
+ /**
8
+ * The resolver Metro runs first: `@noverax/courier/routes` resolves to the
9
+ * module the routes are written in, and every other module as the app's own
10
+ * resolver, or Metro's, resolves it. A build fails there while the routes
11
+ * cannot be read, with why; a development bundle takes the module, which
12
+ * says why where it is imported. A module of the app's bundle that resolves
13
+ * to the config fails it, as the config would ship the server's settings
14
+ * and every API's definition in the app; a server's bundle may take it.
15
+ *
16
+ * @param routes - The project's routes
17
+ * @param own - The resolver the config had, if any
18
+ * @returns The resolver
19
+ * @example
20
+ * ```ts
21
+ * const resolver = { ...config.resolver, resolveRequest: routesResolver(routes, config.resolver?.resolveRequest) };
22
+ * ```
23
+ */
24
+ function routesResolver(routes, own) {
25
+ return (context, moduleName, platform) => {
26
+ if (moduleName === "@noverax/courier/routes") {
27
+ if (!context.dev && routes.failure !== void 0) throw routes.failure;
28
+ return {
29
+ type: "sourceFile",
30
+ filePath: routes.file
31
+ };
32
+ }
33
+ const resolution = (own ?? context.resolveRequest)(context, moduleName, platform);
34
+ refuseConfigInApp(routes, context, resolution);
35
+ return resolution;
36
+ };
37
+ }
38
+ function refuseConfigInApp(routes, context, resolution) {
39
+ const configFile = routes.configFile;
40
+ const filePath = resolution.filePath;
41
+ if (configFile === void 0 || filePath === void 0 || isServer(context)) return;
42
+ if (basename(filePath) !== basename(configFile) || realOf(filePath) !== configFile) return;
43
+ throw new Error(`[courier] ${basename(configFile)} is imported into the app's bundle as a value, by ${context.originModulePath}: it would ship the server's settings and every API's definition in the app. Import its type alone: write \`import type config from\` where the module writes \`import config from\`.`);
44
+ }
45
+ function isServer(context) {
46
+ const environment = context.customResolverOptions?.environment;
47
+ return typeof environment === "string" && SERVER_ENVIRONMENTS.has(environment);
48
+ }
49
+ function realOf(path) {
50
+ try {
51
+ return realpathSync(path);
52
+ } catch {
53
+ return path;
54
+ }
55
+ }
56
+ // src/metro/with-courier.ts
57
+ const OUT_DIR = join("node_modules", ".cache", "@noverax", "courier");
58
+ const ELSEWHERE = "withCourier(config, { outDir })";
59
+ /**
60
+ * Have Metro — under Expo or React Native's CLI — bundle the routes a
61
+ * proxy client may call from `courier.config.ts`, in place of the module
62
+ * `@noverax/courier/routes` ships. The routes are read before Metro starts
63
+ * and written to a module of the app's own, which a dev server keeps
64
+ * current: a change to anything they came from reads them again, and Metro
65
+ * bundles the module anew. The routes carry no address, path or
66
+ * middleware. A bundle of the app that takes the config itself fails.
67
+ *
68
+ * A build fails while the routes cannot be read, with why; a development
69
+ * bundle keeps the routes read last, and with none read yet, the module
70
+ * fails where it is imported, saying why.
71
+ *
72
+ * @param config - The Metro config, as `getDefaultConfig()` gives it, or a promise of it
73
+ * @param options - Where the config is, and where the module is written
74
+ * @returns The config, with the resolver that answers the routes
75
+ * @throws RoutesReadError `ROUTES_NOT_WRITTEN` when the module's folder cannot be written
76
+ * @example
77
+ * ```js
78
+ * // metro.config.js
79
+ * const { getDefaultConfig } = require("expo/metro-config");
80
+ * const { withCourier } = require("@noverax/courier-build/metro");
81
+ *
82
+ * module.exports = withCourier(getDefaultConfig(__dirname));
83
+ * ```
84
+ */
85
+ async function withCourier(config, options = {}) {
86
+ const resolved = await config;
87
+ const root = resolved.projectRoot ?? process.cwd();
88
+ const folder = resolve(root, options.outDir ?? OUT_DIR);
89
+ const routes = await routesFile(root, options.config, join(folder, "routes.js"), ELSEWHERE);
90
+ const watchFolders = resolved.watchFolders ?? [];
91
+ const watched = [root, ...watchFolders].some((parent) => isWithin(parent, folder));
92
+ return {
93
+ ...resolved,
94
+ watchFolders: watched ? watchFolders : [...watchFolders, folder],
95
+ resolver: {
96
+ ...resolved.resolver,
97
+ resolveRequest: routesResolver(routes, resolved.resolver?.resolveRequest)
98
+ }
99
+ };
100
+ }
101
+ function isWithin(parent, path) {
102
+ const from = relative(resolve(parent), path);
103
+ return from === "" || !from.startsWith("..") && !isAbsolute(from);
104
+ }
105
+ export { withCourier };
@@ -0,0 +1,39 @@
1
+ import { dirname, join } from "node:path";
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ import { fileURLToPath } from "node:url";
4
+ // src/util/package-file.ts
5
+ /** The package this module ships in, by the name its manifest gives. */
6
+ const PACKAGE_NAME = "@noverax/courier-build";
7
+ let root;
8
+ /**
9
+ * A file this package ships, by its path from the package's root: the
10
+ * folder above this module that holds the manifest with the package's
11
+ * name, wherever this module was bundled into.
12
+ *
13
+ * @param path - The file, from the package's root
14
+ * @returns The file, absolute; none when no folder above holds the manifest
15
+ * @example
16
+ * ```ts
17
+ * packageFile("lib/engine/worker.js"); // "/app/node_modules/@noverax/courier-build/lib/engine/worker.js"
18
+ * ```
19
+ */
20
+ function packageFile(path) {
21
+ root ??= packageRoot() ?? null;
22
+ return root === null ? void 0 : join(root, path);
23
+ }
24
+ function packageRoot() {
25
+ let dir = dirname(fileURLToPath(import.meta.url));
26
+ while (!isPackageRoot(dir)) {
27
+ const parent = dirname(dir);
28
+ if (parent === dir) return void 0;
29
+ dir = parent;
30
+ }
31
+ return dir;
32
+ }
33
+ function isPackageRoot(dir) {
34
+ const manifest = join(dir, "package.json");
35
+ if (!existsSync(manifest)) return false;
36
+ const parsed = JSON.parse(readFileSync(manifest, "utf8"));
37
+ return typeof parsed === "object" && parsed !== null && Reflect.get(parsed, "name") === PACKAGE_NAME;
38
+ }
39
+ export { packageFile as t };
@@ -0,0 +1,41 @@
1
+ // src/config/definitions-phase.ts
2
+ /**
3
+ * The signal a project's code reads to tell that Courier is reading its
4
+ * definitions rather than serving requests: `COURIER_PHASE` holds
5
+ * `definitions` in every process Courier starts to read them — the
6
+ * `courier-cli routes` command, `courier-mcp --list`, Studio, the process a
7
+ * bundler plugin reads the routes in — after the fashion of `NEXT_PHASE`.
8
+ * Code that checks the environment as it is imported can skip the check
9
+ * then, as `@t3-oss/env-core` does with `skipValidation`; Courier guesses
10
+ * nothing for it. Where the application runs — `courier-server`,
11
+ * `courier-mcp` serving an agent, `attachRuntime()`, the app itself — the
12
+ * variable is not set.
13
+ */
14
+ /** The variable a project's code reads the phase from. */
15
+ const COURIER_PHASE = "COURIER_PHASE";
16
+ /** Its value while Courier reads the project's definitions. */
17
+ const DEFINITIONS_PHASE = "definitions";
18
+ /**
19
+ * Mark the process as one that reads the project's definitions, before the
20
+ * project's code runs in it.
21
+ *
22
+ * @param env - The environment to mark (default: the process's)
23
+ * @example
24
+ * ```ts
25
+ * enterDefinitionsPhase();
26
+ * process.env.COURIER_PHASE; // "definitions"
27
+ * ```
28
+ */
29
+ function enterDefinitionsPhase(env = process.env) {
30
+ env[COURIER_PHASE] = DEFINITIONS_PHASE;
31
+ }
32
+ // src/config/read-limits.ts
33
+ /**
34
+ * How long one reading of a project's config may take before it is given
35
+ * up, in milliseconds: a config that waits on the network, or on a timer,
36
+ * never ends. The routes plugin's process and `courier-cli routes` hold to
37
+ * it; Studio, which keeps running what it loaded last, has its own,
38
+ * `COURIER_CONFIG_LOAD_TIMEOUT_MS`.
39
+ */
40
+ const CONFIG_READ_LIMIT_MS = 6e4;
41
+ export { enterDefinitionsPhase as i, COURIER_PHASE as n, DEFINITIONS_PHASE as r, CONFIG_READ_LIMIT_MS as t };
@@ -0,0 +1,51 @@
1
+ import { t as CourierRoutesOptions } from "../courier-routes-options-BYF7Sfdf.js";
2
+ import "../routes-engine-BfwrCuNL.js";
3
+ /** What of Rollup's and Rolldown's plugin context the routes module is loaded with. */
4
+ interface RoutesPluginContext {
5
+ readonly meta: {
6
+ readonly watchMode: boolean;
7
+ };
8
+ addWatchFile(id: string): void;
9
+ warn(message: string): void;
10
+ error(message: string): never;
11
+ }
12
+ /** Options of the routes plugin of Rollup and Rolldown. */
13
+ interface RollupRoutesOptions extends CourierRoutesOptions {
14
+ /**
15
+ * The bundle is a server's, which may take the config itself: none is
16
+ * refused. A page's by default, which must not.
17
+ */
18
+ server?: boolean | undefined;
19
+ }
20
+ /** The plugin, as Rollup and Rolldown both take it. */
21
+ interface RoutesRollupPlugin {
22
+ readonly name: string;
23
+ buildStart(): void;
24
+ resolveId(source: string): string | null;
25
+ load(this: RoutesPluginContext, id: string): Promise<string> | null;
26
+ transform(this: RoutesPluginContext, code: string, id: string): null;
27
+ }
28
+ /**
29
+ * The Rollup and Rolldown plugin that builds the routes a page's proxy
30
+ * client may call from `courier.config.ts`, in place of the module
31
+ * `@noverax/courier/routes` ships. No routes file is written, and the routes
32
+ * carry no address, path or middleware. The bundle is taken for a page's,
33
+ * which fails where it takes the config itself; a server's says so with
34
+ * `server: true`.
35
+ *
36
+ * A build that cannot read the config fails with why. Under `watch`, every
37
+ * file the routes came from is watched; when they cannot be read on a
38
+ * rebuild, the routes read last stay, with a warning.
39
+ *
40
+ * @param options - Where the config is, when it is not found from the folder the bundler runs in, and whether the bundle is a server's
41
+ * @returns The plugin
42
+ * @example
43
+ * ```ts
44
+ * // rollup.config.js
45
+ * import { courierRoutes } from "@noverax/courier-build/rollup";
46
+ *
47
+ * export default { input: "src/main.js", plugins: [courierRoutes()] };
48
+ * ```
49
+ */
50
+ export declare function courierRoutes(options?: RollupRoutesOptions): RoutesRollupPlugin;
51
+ export type { RollupRoutesOptions, RoutesPluginContext, RoutesRollupPlugin };
@@ -0,0 +1,95 @@
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 { t as sharedEngine } from "../shared-engine-D0j7uXg8.js";
5
+ import { t as configFileOf } from "../config-file-of-Clf_dWUQ.js";
6
+ import { t as ROUTES_MODULE_ID } from "../routes-module-id-CnUjQjYF.js";
7
+ // src/rollup/load-routes-module.ts
8
+ /**
9
+ * The routes module: the routes read from the config, with every path they
10
+ * came from watched — a file that is not there yet, and each folder whose
11
+ * entries the reading listed, too — so a watching build builds it again
12
+ * once one changes.
13
+ *
14
+ * When they cannot be read, what the failed reading read is watched instead,
15
+ * so mending the config brings the routes back. A build fails with why;
16
+ * under watch, the routes read last stay, with a warning, and with none
17
+ * read yet the build fails.
18
+ *
19
+ * @param context - The plugin's context
20
+ * @param engine - The engine that reads the project's routes
21
+ * @returns The module's code
22
+ * @example
23
+ * ```ts
24
+ * load(id) { return id === ROUTES_MODULE_ID ? loadRoutesModule(this, engine) : null; }
25
+ * ```
26
+ */
27
+ async function loadRoutesModule(context, engine) {
28
+ try {
29
+ const snapshot = await engine.read();
30
+ follow(context, snapshot);
31
+ return routesModuleCode(snapshot);
32
+ } catch (error) {
33
+ const failure = error instanceof Error ? error : new Error(String(error));
34
+ const followed = (failure instanceof RoutesReadError ? failure.inputs : void 0) ?? engine.last;
35
+ if (followed !== void 0) follow(context, followed);
36
+ const why = `[courier] ${failure.message}`;
37
+ const last = engine.last;
38
+ if (!context.meta.watchMode || last === void 0) return context.error(why);
39
+ context.warn(why);
40
+ return routesModuleCode(last);
41
+ }
42
+ }
43
+ function follow(context, inputs) {
44
+ for (const path of [...inputs.files, ...inputs.listed ?? []]) context.addWatchFile(path);
45
+ }
46
+ // src/rollup/courier-routes.ts
47
+ /**
48
+ * The Rollup and Rolldown plugin that builds the routes a page's proxy
49
+ * client may call from `courier.config.ts`, in place of the module
50
+ * `@noverax/courier/routes` ships. No routes file is written, and the routes
51
+ * carry no address, path or middleware. The bundle is taken for a page's,
52
+ * which fails where it takes the config itself; a server's says so with
53
+ * `server: true`.
54
+ *
55
+ * A build that cannot read the config fails with why. Under `watch`, every
56
+ * file the routes came from is watched; when they cannot be read on a
57
+ * rebuild, the routes read last stay, with a warning.
58
+ *
59
+ * @param options - Where the config is, when it is not found from the folder the bundler runs in, and whether the bundle is a server's
60
+ * @returns The plugin
61
+ * @example
62
+ * ```ts
63
+ * // rollup.config.js
64
+ * import { courierRoutes } from "@noverax/courier-build/rollup";
65
+ *
66
+ * export default { input: "src/main.js", plugins: [courierRoutes()] };
67
+ * ```
68
+ */
69
+ function courierRoutes(options = {}) {
70
+ const root = process.cwd();
71
+ const engine = sharedEngine({
72
+ root,
73
+ config: options.config,
74
+ envFiles: []
75
+ });
76
+ let configFile;
77
+ return {
78
+ name: "courier-routes",
79
+ buildStart() {
80
+ configFile = options.server === true ? void 0 : configFileOf(root, options.config);
81
+ },
82
+ resolveId: (source) => source === "@noverax/courier/routes" || source === "\0@noverax/courier/routes" ? ROUTES_MODULE_ID : null,
83
+ load(id) {
84
+ return id === "\0@noverax/courier/routes" ? loadRoutesModule(this, engine) : null;
85
+ },
86
+ transform(_code, id) {
87
+ if (configFile !== void 0 && withSlashes(id.split("?")[0] ?? id) === withSlashes(configFile)) this.error(configInPageMessage(configFile));
88
+ return null;
89
+ }
90
+ };
91
+ }
92
+ function withSlashes(path) {
93
+ return path.split("\\").join("/");
94
+ }
95
+ export { courierRoutes };
@@ -0,0 +1,117 @@
1
+ import { CourierRoutes } from "@noverax/courier/define";
2
+ /**
3
+ * What one reading of a project's routes depended on: a change to any of it
4
+ * may change what the next reading gives.
5
+ */
6
+ interface RoutesInputs {
7
+ /** The config file read, absolute, once one was found. */
8
+ readonly configFile?: string | undefined;
9
+ /**
10
+ * Every path the reading depended on, absolute and sorted: the config, the
11
+ * modules it reached, the env files that were or may be read, the files
12
+ * it read and the paths it looked at. A file counts by its contents; a
13
+ * path that is not there or is a folder, by whether it is there and what
14
+ * it is.
15
+ */
16
+ readonly files: readonly string[];
17
+ /**
18
+ * The folders whose entries the reading listed, absolute and sorted: an
19
+ * entry added or removed may change what it gives. None when omitted.
20
+ */
21
+ readonly listed?: readonly string[] | undefined;
22
+ /**
23
+ * The environment variables the reading looked at, sorted, or `"*"` when it
24
+ * went through all of them.
25
+ */
26
+ readonly env: readonly string[] | "*";
27
+ }
28
+ /**
29
+ * The routes of a project's config, as one reading of it gave them, and
30
+ * what that reading depended on.
31
+ */
32
+ interface RoutesSnapshot extends RoutesInputs {
33
+ /** The config file the routes were read from, absolute. */
34
+ readonly configFile: string;
35
+ /**
36
+ * Each source's routes, by its name in the config's `sources`: every
37
+ * source, one that opens nothing to a page with no endpoints.
38
+ */
39
+ readonly routes: Readonly<Record<string, CourierRoutes>>;
40
+ }
41
+ /** What reading a project's routes is told: where the project is, and how it reads its variables. */
42
+ interface RoutesRequest {
43
+ /** The project's root, absolute: the config is looked for there and in the folders above it. */
44
+ readonly root: string;
45
+ /** The config file, absolute, when it is not the one found from the root. */
46
+ readonly configFile?: string | undefined;
47
+ /**
48
+ * The env files read before the config, absolute, the first to set a
49
+ * variable winning; a variable the process was started with wins over all.
50
+ */
51
+ readonly envFiles: readonly string[];
52
+ /**
53
+ * The variables those files give, as the bundler reads them — with its own
54
+ * expansion of `${VAR}` — in place of reading them here. A variable the
55
+ * process was started with still wins.
56
+ */
57
+ readonly env?: Readonly<Record<string, string>> | undefined;
58
+ }
59
+ /** Where a project is, for the engine that reads its routes. */
60
+ interface RoutesEngineOptions {
61
+ /** The project's root: the config is looked for there and in the folders above it. */
62
+ root: string;
63
+ /** The config file, absolute or from the root, when it is not the one found from the root. */
64
+ config?: string | undefined;
65
+ /**
66
+ * The env files read before the config, absolute or from the root, the
67
+ * first to set a variable winning; a variable the process was started with
68
+ * wins over all. None by default.
69
+ */
70
+ envFiles?: readonly string[] | undefined;
71
+ /**
72
+ * The variables those files give, as the bundler reads them, asked anew
73
+ * for every reading: the config then sees the values the app does, `${VAR}`
74
+ * expanded as the bundler expands it. Without it the files are read as
75
+ * they are.
76
+ */
77
+ envValues?: (() => Readonly<Record<string, string>>) | undefined;
78
+ }
79
+ /** Reads a project's routes, again only when what they depend on changed. */
80
+ interface RoutesEngine {
81
+ /**
82
+ * The routes as the config gives them now. Read in a process of its own
83
+ * the first time, and again only once a path the last reading depended
84
+ * on, or a variable it looked at, has changed. Calls made while a reading
85
+ * runs share it, unless something it read changed meanwhile: a call made
86
+ * after it began then waits for the next reading, which begins after the
87
+ * change. A failure is not kept: the next call reads again.
88
+ *
89
+ * @throws RoutesReadError when the config cannot be found or read
90
+ */
91
+ read(): Promise<RoutesSnapshot>;
92
+ /** The routes the last successful reading gave, if one has. */
93
+ readonly last: RoutesSnapshot | undefined;
94
+ /**
95
+ * A digest of what the latest successful reading depended on, as it is
96
+ * now: equal digests mean the same routes. `undefined` before the first,
97
+ * and while that reading may have missed a change made as it ran, until
98
+ * `read()` reads again.
99
+ */
100
+ fingerprint(): string | undefined;
101
+ }
102
+ /** How a reading of the routes runs: in a process of its own, unless a test says otherwise. */
103
+ type RoutesEvaluator = (request: RoutesRequest) => Promise<RoutesSnapshot>;
104
+ /**
105
+ * Create the engine that reads a project's routes for a bundler plugin.
106
+ *
107
+ * @param options - Where the project and its config are
108
+ * @param evaluate - How one reading runs: in a process of its own
109
+ * @returns The engine
110
+ * @example
111
+ * ```ts
112
+ * const engine = createRoutesEngine({ root: process.cwd(), envFiles: [".env.local", ".env"] });
113
+ * const { routes } = await engine.read();
114
+ * ```
115
+ */
116
+ declare function createRoutesEngine(options: RoutesEngineOptions, evaluate?: RoutesEvaluator): RoutesEngine;
117
+ export { RoutesInputs as a, createRoutesEngine as i, RoutesEngineOptions as n, RoutesRequest as o, RoutesEvaluator as r, RoutesSnapshot as s, RoutesEngine as t };