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