@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.
- package/LICENSE +21 -0
- package/README.md +295 -2
- package/lib/config/index.d.ts +811 -0
- package/lib/config/index.js +75 -0
- package/lib/config-file-of-Clf_dWUQ.js +28 -0
- package/lib/config-in-page-message-DLd3f0Jd.js +20 -0
- package/lib/courier-routes-options-BYF7Sfdf.d.cts +13 -0
- package/lib/courier-routes-options-BYF7Sfdf.d.ts +13 -0
- package/lib/engine/worker.d.ts +1 -0
- package/lib/engine/worker.js +548 -0
- package/lib/errno-code-CNx_E80R.js +32 -0
- package/lib/esbuild/index.d.ts +58 -0
- package/lib/esbuild/index.js +191 -0
- package/lib/index-_VUU5N5v.d.ts +134 -0
- package/lib/index.d.ts +101 -0
- package/lib/index.js +4 -0
- package/lib/jest/routes-thread.d.ts +1 -0
- package/lib/jest/routes-thread.js +46 -0
- package/lib/jest/transformer.cjs +121 -0
- package/lib/jest/transformer.d.cts +39 -0
- package/lib/load-config-barNPfXN.js +2834 -0
- package/lib/metro/index.cjs +25 -0
- package/lib/metro/index.d.cts +55 -0
- package/lib/metro/index.d.ts +67 -0
- package/lib/metro/index.js +105 -0
- package/lib/package-file-CVAKY_Nu.js +39 -0
- package/lib/read-limits-DUuUaEQM.js +41 -0
- package/lib/rollup/index.d.ts +51 -0
- package/lib/rollup/index.js +95 -0
- package/lib/routes-engine-BfwrCuNL.d.ts +117 -0
- package/lib/routes-file-DRoqUZ0S.js +206 -0
- package/lib/routes-loader-context-40OKDrQp.d.ts +26 -0
- package/lib/routes-module-code-DYtP7uj1.js +405 -0
- package/lib/routes-module-id-CnUjQjYF.js +12 -0
- package/lib/routes-read-error-DQhKIE2F.js +30 -0
- package/lib/routes-rule-Pu1Tz8Xm.js +108 -0
- package/lib/rsbuild/index.d.ts +41 -0
- package/lib/rsbuild/index.js +37 -0
- package/lib/rspack/index.d.ts +3 -0
- package/lib/rspack/index.js +1 -0
- package/lib/shared-engine-D0j7uXg8.js +28 -0
- package/lib/vite/index.d.ts +33 -0
- package/lib/vite/index.js +357 -0
- package/lib/webpack/index.d.ts +4 -0
- package/lib/webpack/index.js +3 -0
- package/lib/webpack/loader.cjs +31 -0
- package/lib/webpack/loader.d.cts +40 -0
- package/lib/webpack/run-routes-loader.d.ts +22 -0
- package/lib/webpack/run-routes-loader.js +103 -0
- package/lib/webpack-BPxhBYGV.js +42 -0
- package/lib/worker-messages-BT2vvNQJ.js +35 -0
- 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 };
|