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