@kasperpeulen/vitest-plugin-nextjs 0.0.0-stage → 0.1.0-alpha.1
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/README.md +249 -2
- package/dist/_virtual/_rolldown/runtime.js +14 -0
- package/dist/action-ids.d.ts +18 -0
- package/dist/action-ids.js +44 -0
- package/dist/adapter.d.ts +8 -0
- package/dist/adapter.js +12 -0
- package/dist/app-requests.d.ts +63 -0
- package/dist/app-requests.js +253 -0
- package/dist/browser-context.d.ts +24 -0
- package/dist/browser-context.js +157 -0
- package/dist/build-output.d.ts +68 -0
- package/dist/build-output.js +216 -0
- package/dist/build.d.ts +26 -0
- package/dist/build.js +39 -0
- package/dist/built-files.d.ts +9 -0
- package/dist/built-files.js +22 -0
- package/dist/cache.d.ts +44 -0
- package/dist/cache.js +99 -0
- package/dist/client-components.d.ts +3 -0
- package/dist/client-components.js +4 -0
- package/dist/client-file-key.d.ts +5 -0
- package/dist/client-file-key.js +5 -0
- package/dist/client-files.d.ts +83 -0
- package/dist/client-files.js +186 -0
- package/dist/client-graph.d.ts +94 -0
- package/dist/client-graph.js +266 -0
- package/dist/client-modules.d.ts +10 -0
- package/dist/client-modules.js +43 -0
- package/dist/client.d.ts +42 -0
- package/dist/client.js +263 -0
- package/dist/commonjs.d.ts +44 -0
- package/dist/commonjs.js +111 -0
- package/dist/compat/browser/client.d.ts +19 -0
- package/dist/compat/browser/client.js +13 -0
- package/dist/compat/build.d.ts +145 -0
- package/dist/compat/build.js +149 -0
- package/dist/compat/checks.d.ts +16 -0
- package/dist/compat/checks.js +148 -0
- package/dist/compat/patches.d.ts +13 -0
- package/dist/compat/patches.js +30 -0
- package/dist/compat/paths.d.ts +74 -0
- package/dist/compat/paths.js +160 -0
- package/dist/compat/rsc/handlers.d.ts +2 -0
- package/dist/compat/rsc/handlers.js +2 -0
- package/dist/compat/rsc/prerender.d.ts +10 -0
- package/dist/compat/rsc/prerender.js +9 -0
- package/dist/compat/server/headers.d.ts +3 -0
- package/dist/compat/server/headers.js +3 -0
- package/dist/compat/ssr/build-output.d.ts +4 -0
- package/dist/compat/ssr/build-output.js +3 -0
- package/dist/compat/ssr/cache.d.ts +6 -0
- package/dist/compat/ssr/cache.js +6 -0
- package/dist/compat/ssr/handler.d.ts +8 -0
- package/dist/compat/ssr/handler.js +8 -0
- package/dist/compat/ssr/node-server.d.ts +5 -0
- package/dist/compat/ssr/node-server.js +4 -0
- package/dist/compat/ssr/render-errors.d.ts +4 -0
- package/dist/compat/ssr/render-errors.js +4 -0
- package/dist/compat/ssr/senders.d.ts +6 -0
- package/dist/compat/ssr/senders.js +6 -0
- package/dist/compat/types.d.ts +8 -0
- package/dist/compat/types.js +1 -0
- package/dist/compile.d.ts +39 -0
- package/dist/compile.js +175 -0
- package/dist/disk.d.ts +16 -0
- package/dist/disk.js +13 -0
- package/dist/document-loads.d.ts +15 -0
- package/dist/document-loads.js +53 -0
- package/dist/document.d.ts +79 -0
- package/dist/document.js +330 -0
- package/dist/downloads.d.ts +15 -0
- package/dist/downloads.js +71 -0
- package/dist/esm-require.d.ts +11 -0
- package/dist/esm-require.js +32 -0
- package/dist/fast-crypto/index.js +1093 -0
- package/dist/fast-crypto.d.ts +20 -0
- package/dist/fast-crypto.js +151 -0
- package/dist/flight.d.ts +19 -0
- package/dist/flight.js +42 -0
- package/dist/form-submission.d.ts +15 -0
- package/dist/form-submission.js +27 -0
- package/dist/from-page.d.ts +5 -0
- package/dist/from-page.js +44 -0
- package/dist/globals.d.ts +1 -0
- package/dist/globals.js +197 -0
- package/dist/host-document.d.ts +26 -0
- package/dist/host-document.js +34 -0
- package/dist/host-return-shared.d.ts +5 -0
- package/dist/host-return-shared.js +5 -0
- package/dist/host-return.d.ts +5 -0
- package/dist/host-return.js +16 -0
- package/dist/index.d.ts +280 -0
- package/dist/index.js +281 -0
- package/dist/internal.d.ts +2 -0
- package/dist/internal.js +3 -0
- package/dist/leftovers.d.ts +11 -0
- package/dist/leftovers.js +55 -0
- package/dist/mode.d.ts +169 -0
- package/dist/mode.js +31 -0
- package/dist/node/LICENSES.md +3540 -0
- package/dist/node/assert/strict.js +29 -0
- package/dist/node/assert.js +3044 -0
- package/dist/node/buffer.js +1499 -0
- package/dist/node/child_process.js +45 -0
- package/dist/node/cluster.js +85 -0
- package/dist/node/console.js +96 -0
- package/dist/node/constants.js +495 -0
- package/dist/node/crypto.js +1388 -0
- package/dist/node/dgram.js +78 -0
- package/dist/node/diagnostics_channel.js +122 -0
- package/dist/node/dns/promises.js +129 -0
- package/dist/node/dns.js +137 -0
- package/dist/node/domain.js +43 -0
- package/dist/node/events.js +1155 -0
- package/dist/node/fs/promises.js +40 -0
- package/dist/node/fs.js +10375 -0
- package/dist/node/http.js +326 -0
- package/dist/node/http2.js +305 -0
- package/dist/node/https.js +57 -0
- package/dist/node/inspector/promises.js +66 -0
- package/dist/node/inspector.js +76 -0
- package/dist/node/module.js +205 -0
- package/dist/node/net.js +392 -0
- package/dist/node/os.js +259 -0
- package/dist/node/path/posix.js +25 -0
- package/dist/node/path/win32.js +25 -0
- package/dist/node/path.js +384 -0
- package/dist/node/perf_hooks.js +313 -0
- package/dist/node/process.js +452 -0
- package/dist/node/punycode.js +178 -0
- package/dist/node/querystring.js +934 -0
- package/dist/node/readline/promises.js +71 -0
- package/dist/node/readline.js +56 -0
- package/dist/node/repl.js +44 -0
- package/dist/node/sqlite.js +22 -0
- package/dist/node/stream/consumers.js +47 -0
- package/dist/node/stream/promises.js +6 -0
- package/dist/node/stream/web.js +52 -0
- package/dist/node/stream.js +4323 -0
- package/dist/node/string_decoder.js +276 -0
- package/dist/node/sys.js +6 -0
- package/dist/node/timers/promises.js +71 -0
- package/dist/node/timers.js +133 -0
- package/dist/node/tls.js +122 -0
- package/dist/node/trace_events.js +23 -0
- package/dist/node/tty.js +69 -0
- package/dist/node/url.js +1426 -0
- package/dist/node/util/types.js +1456 -0
- package/dist/node/util.js +4807 -0
- package/dist/node/v8.js +160 -0
- package/dist/node/vm.js +88 -0
- package/dist/node/wasi.js +16 -0
- package/dist/node/worker_threads.js +123 -0
- package/dist/node/zlib.js +1396 -0
- package/dist/node-builtins.d.ts +40 -0
- package/dist/node-builtins.js +74 -0
- package/dist/node-on-page.d.ts +46 -0
- package/dist/node-on-page.js +101 -0
- package/dist/node-platform.d.ts +36 -0
- package/dist/node-platform.js +152 -0
- package/dist/node-rendered.d.ts +21 -0
- package/dist/node-rendered.js +26 -0
- package/dist/node-server.d.ts +46 -0
- package/dist/node-server.js +193 -0
- package/dist/not-found-page.d.ts +19 -0
- package/dist/not-found-page.js +74 -0
- package/dist/optimizer-cache-key.d.ts +26 -0
- package/dist/optimizer-cache-key.js +73 -0
- package/dist/own-package.d.ts +1 -0
- package/dist/own-package.js +6 -0
- package/dist/page-store.d.ts +33 -0
- package/dist/page-store.js +80 -0
- package/dist/page-timers.d.ts +22 -0
- package/dist/page-timers.js +47 -0
- package/dist/page.d.ts +112 -0
- package/dist/page.js +549 -0
- package/dist/paths.d.ts +13 -0
- package/dist/paths.js +47 -0
- package/dist/plugin.d.ts +23 -0
- package/dist/plugin.js +1006 -0
- package/dist/prerender-verdict.d.ts +8 -0
- package/dist/prerender-verdict.js +67 -0
- package/dist/project/context.d.ts +546 -0
- package/dist/project/context.js +111 -0
- package/dist/project/entries.d.ts +42 -0
- package/dist/project/entries.js +189 -0
- package/dist/project/env-worker.d.ts +1 -0
- package/dist/project/env-worker.js +9 -0
- package/dist/project/env.d.ts +36 -0
- package/dist/project/env.js +82 -0
- package/dist/project/layers.d.ts +31 -0
- package/dist/project/layers.js +150 -0
- package/dist/project/loaders.d.ts +71 -0
- package/dist/project/loaders.js +300 -0
- package/dist/project/metadata.d.ts +8 -0
- package/dist/project/metadata.js +34 -0
- package/dist/project/module-exports.d.ts +29 -0
- package/dist/project/module-exports.js +77 -0
- package/dist/project/routes.d.ts +19 -0
- package/dist/project/routes.js +92 -0
- package/dist/project/routing.d.ts +46 -0
- package/dist/project/routing.js +280 -0
- package/dist/project/transform.d.ts +8 -0
- package/dist/project/transform.js +110 -0
- package/dist/project/webpack.d.ts +65 -0
- package/dist/project/webpack.js +191 -0
- package/dist/project.d.ts +269 -0
- package/dist/project.js +58 -0
- package/dist/registry-global.d.ts +5 -0
- package/dist/registry-global.js +5 -0
- package/dist/registry.d.ts +241 -0
- package/dist/registry.js +30 -0
- package/dist/route-chunks.d.ts +13 -0
- package/dist/route-chunks.js +32 -0
- package/dist/route-lookup.d.ts +34 -0
- package/dist/route-lookup.js +207 -0
- package/dist/routes-ahead-shared.d.ts +17 -0
- package/dist/routes-ahead-shared.js +9 -0
- package/dist/routes-ahead.d.ts +10 -0
- package/dist/routes-ahead.js +54 -0
- package/dist/rsc.d.ts +19 -0
- package/dist/rsc.js +171 -0
- package/dist/server-action.d.ts +9 -0
- package/dist/server-action.js +15 -0
- package/dist/server-code.d.ts +176 -0
- package/dist/server-code.js +357 -0
- package/dist/server-in-front.d.ts +37 -0
- package/dist/server-in-front.js +90 -0
- package/dist/server-layers.d.ts +22 -0
- package/dist/server-layers.js +33 -0
- package/dist/setup-browser.d.ts +1 -0
- package/dist/setup-browser.js +8 -0
- package/dist/setup-node.d.ts +1 -0
- package/dist/setup-node.js +6 -0
- package/dist/setup.d.ts +8 -0
- package/dist/setup.js +62 -0
- package/dist/speed-features.d.ts +13 -0
- package/dist/speed-features.js +95 -0
- package/dist/ssr.d.ts +70 -0
- package/dist/ssr.js +480 -0
- package/dist/static-build.d.ts +36 -0
- package/dist/static-build.js +92 -0
- package/dist/storage.d.ts +31 -0
- package/dist/storage.js +94 -0
- package/dist/styles-shared.d.ts +137 -0
- package/dist/styles-shared.js +124 -0
- package/dist/styles.d.ts +102 -0
- package/dist/styles.js +730 -0
- package/dist/stylesheet-versions.d.ts +17 -0
- package/dist/stylesheet-versions.js +43 -0
- package/dist/tester.html +10 -0
- package/dist/use-cache.d.ts +15 -0
- package/dist/use-cache.js +93 -0
- package/dist/vitest-version.d.ts +6 -0
- package/dist/vitest-version.js +12 -0
- package/package.json +125 -3
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { NextProject, NextRoute } from "./project.js";
|
|
2
|
+
import { Plugin } from "vite";
|
|
3
|
+
|
|
4
|
+
//#region src/route-lookup.d.ts
|
|
5
|
+
/** A request the code makes: its URL, a placeholder for each expression of a template. */
|
|
6
|
+
type RouteRequest = {
|
|
7
|
+
url: string; /** Whether it renders the route's files, which the node of a test does not. */
|
|
8
|
+
route: boolean; /** Whether the middleware gets it. */
|
|
9
|
+
proxy: boolean;
|
|
10
|
+
};
|
|
11
|
+
/** What a placeholder of a template literal stands for in `RouteRequest.url`. */
|
|
12
|
+
declare const placeholder = "\0";
|
|
13
|
+
/**
|
|
14
|
+
* The requests that the calls of `renderServer()` and `handleRequest()` of
|
|
15
|
+
* `specifier` make in `code`, a module of JavaScript. `"every"` where a call
|
|
16
|
+
* or its URL is not read off the code. Nothing for code that calls neither.
|
|
17
|
+
*/
|
|
18
|
+
declare function requestsOf(code: string, specifier: string): Promise<RouteRequest[] | "every">;
|
|
19
|
+
/** The routes a request can get, by its URL: every route where that is not known. */
|
|
20
|
+
declare function routesOf(project: Pick<NextProject, "routes" | "routing" | "pageOf" | "middlewareFile">, {
|
|
21
|
+
url,
|
|
22
|
+
proxy
|
|
23
|
+
}: Pick<RouteRequest, "url" | "proxy">): Promise<NextRoute[]>;
|
|
24
|
+
type RouteLookupOptions = {
|
|
25
|
+
getProject: () => NextProject; /** What a test imports `renderServer()` and `handleRequest()` from. */
|
|
26
|
+
specifier: string;
|
|
27
|
+
};
|
|
28
|
+
/** The plugin that adds the routes a module opens to the map of Vitest's lookup. */
|
|
29
|
+
declare function routeLookupPlugin({
|
|
30
|
+
getProject,
|
|
31
|
+
specifier
|
|
32
|
+
}: RouteLookupOptions): Plugin;
|
|
33
|
+
//#endregion
|
|
34
|
+
export { RouteLookupOptions, RouteRequest, placeholder, requestsOf, routeLookupPlugin, routesOf };
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { walk } from "./commonjs.js";
|
|
2
|
+
import { normalizePath, parseAstAsync } from "vite";
|
|
3
|
+
import { lookupEnvironment, lookupServers } from "@kasperpeulen/vitest-plugin-rsc/internal/related-lookup";
|
|
4
|
+
//#region src/route-lookup.ts
|
|
5
|
+
/** What a placeholder of a template literal stands for in `RouteRequest.url`. */
|
|
6
|
+
const placeholder = "\0";
|
|
7
|
+
const opening = ["renderServer", "handleRequest"];
|
|
8
|
+
/**
|
|
9
|
+
* The requests that the calls of `renderServer()` and `handleRequest()` of
|
|
10
|
+
* `specifier` make in `code`, a module of JavaScript. `"every"` where a call
|
|
11
|
+
* or its URL is not read off the code. Nothing for code that calls neither.
|
|
12
|
+
*/
|
|
13
|
+
async function requestsOf(code, specifier) {
|
|
14
|
+
const ast = await parseAstAsync(code);
|
|
15
|
+
const functions = /* @__PURE__ */ new Map();
|
|
16
|
+
const namespaces = /* @__PURE__ */ new Set();
|
|
17
|
+
for (const statement of ast.body) {
|
|
18
|
+
if (statement.source?.value !== specifier) continue;
|
|
19
|
+
if (statement.type === "ExportNamedDeclaration" || statement.type === "ExportAllDeclaration") return "every";
|
|
20
|
+
if (statement.type !== "ImportDeclaration") continue;
|
|
21
|
+
for (const imported of statement.specifiers) {
|
|
22
|
+
const local = imported.local.name;
|
|
23
|
+
if (imported.type === "ImportNamespaceSpecifier") namespaces.add(local);
|
|
24
|
+
if (imported.type !== "ImportSpecifier") continue;
|
|
25
|
+
const { name, value } = imported.imported;
|
|
26
|
+
const importedName = name ?? value;
|
|
27
|
+
if (importedName && opening.includes(importedName)) functions.set(local, importedName);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
for (const [node] of walk(ast)) {
|
|
31
|
+
const source = node.type === "ImportExpression" && node.source;
|
|
32
|
+
if (source && source.type === "Literal" && source.value === specifier) return "every";
|
|
33
|
+
}
|
|
34
|
+
if (functions.size === 0 && namespaces.size === 0) return [];
|
|
35
|
+
const requests = [];
|
|
36
|
+
for (const [node, parent] of walk(ast)) {
|
|
37
|
+
if (node.type !== "Identifier" || !parent) continue;
|
|
38
|
+
const name = node.name;
|
|
39
|
+
if (parent.type === "MemberExpression" && parent.property === node && !parent.computed || parent.type === "Property" && parent.key === node && !parent.computed && !parent.shorthand || parent.type === "ImportSpecifier" || parent.type === "ImportNamespaceSpecifier") continue;
|
|
40
|
+
let callee = node;
|
|
41
|
+
let opened = functions.get(name);
|
|
42
|
+
if (namespaces.has(name)) {
|
|
43
|
+
if (parent.type !== "MemberExpression" || parent.object !== node || parent.computed) return "every";
|
|
44
|
+
opened = parent.property.name;
|
|
45
|
+
if (!opening.includes(opened)) continue;
|
|
46
|
+
callee = parent;
|
|
47
|
+
}
|
|
48
|
+
if (!opened) continue;
|
|
49
|
+
const call = callee === node ? parent : parentOf(ast, callee);
|
|
50
|
+
if (call?.type !== "CallExpression" || call.callee !== callee) return "every";
|
|
51
|
+
const request = requestOf(opened, call.arguments);
|
|
52
|
+
if (!request) return "every";
|
|
53
|
+
requests.push(...request);
|
|
54
|
+
}
|
|
55
|
+
return requests;
|
|
56
|
+
}
|
|
57
|
+
function requestOf(name, args) {
|
|
58
|
+
const [first, second] = args;
|
|
59
|
+
if (name === "handleRequest") {
|
|
60
|
+
const url = first && urlOf(first);
|
|
61
|
+
return url === void 0 ? void 0 : [{
|
|
62
|
+
url,
|
|
63
|
+
route: true,
|
|
64
|
+
proxy: true
|
|
65
|
+
}];
|
|
66
|
+
}
|
|
67
|
+
if (first === void 0 || first.type === "ObjectExpression") {
|
|
68
|
+
const options = first && optionsOf(first);
|
|
69
|
+
if (first && !options) return;
|
|
70
|
+
const url = options?.url ? urlOf(options.url) : "/";
|
|
71
|
+
if (url === void 0) return;
|
|
72
|
+
return [{
|
|
73
|
+
url,
|
|
74
|
+
route: true,
|
|
75
|
+
proxy: options?.proxy === void 0 || isOn(options.proxy)
|
|
76
|
+
}];
|
|
77
|
+
}
|
|
78
|
+
if (!isElement(first)) return;
|
|
79
|
+
if (second === void 0) return [];
|
|
80
|
+
const options = optionsOf(second);
|
|
81
|
+
if (!options) return;
|
|
82
|
+
const url = options.url ? urlOf(options.url) : "/";
|
|
83
|
+
if (url === void 0) return;
|
|
84
|
+
const route = options.layouts !== void 0 && isOn(options.layouts);
|
|
85
|
+
const proxy = options.proxy !== void 0 && isOn(options.proxy);
|
|
86
|
+
return route || proxy ? [{
|
|
87
|
+
url,
|
|
88
|
+
route,
|
|
89
|
+
proxy
|
|
90
|
+
}] : [];
|
|
91
|
+
}
|
|
92
|
+
function optionsOf(node) {
|
|
93
|
+
if (node.type !== "ObjectExpression") return;
|
|
94
|
+
const options = {};
|
|
95
|
+
for (const property of node.properties) {
|
|
96
|
+
if (property.type !== "Property" || property.computed) return;
|
|
97
|
+
const key = property.key;
|
|
98
|
+
const name = key.name ?? key.value;
|
|
99
|
+
if (typeof name !== "string") return;
|
|
100
|
+
options[name] = property.value;
|
|
101
|
+
}
|
|
102
|
+
return options;
|
|
103
|
+
}
|
|
104
|
+
function isElement(node) {
|
|
105
|
+
if (node.type !== "CallExpression") return false;
|
|
106
|
+
const callee = node.callee;
|
|
107
|
+
const name = callee.type === "Identifier" ? callee.name : callee.type === "MemberExpression" && !callee.computed ? callee.property.name : void 0;
|
|
108
|
+
return name !== void 0 && /^(?:_?jsxs?|_?jsxDEV|createElement)$/.test(name);
|
|
109
|
+
}
|
|
110
|
+
const isOn = (node) => !(node.type === "Literal" && node.value === false) && !(node.type === "Identifier" && node.name === "undefined");
|
|
111
|
+
function urlOf(node) {
|
|
112
|
+
if (node.type === "Literal" && typeof node.value === "string") return node.value;
|
|
113
|
+
if (node.type !== "TemplateLiteral") return;
|
|
114
|
+
const quasis = node.quasis;
|
|
115
|
+
if (quasis.some(({ value }) => value.cooked === null)) return;
|
|
116
|
+
return quasis.map(({ value }) => value.cooked).join("\0");
|
|
117
|
+
}
|
|
118
|
+
function parentOf(ast, child) {
|
|
119
|
+
for (const [node, parent] of walk(ast)) if (node === child) return parent;
|
|
120
|
+
}
|
|
121
|
+
/** The routes a request can get, by its URL: every route where that is not known. */
|
|
122
|
+
async function routesOf(project, { url, proxy }) {
|
|
123
|
+
const { routes, routing } = project;
|
|
124
|
+
const pagesOf = async (target) => new Set(await Promise.all([project.pageOf(target, { proxy: false }), ...proxy ? [project.pageOf(target, { proxy: true })] : []]));
|
|
125
|
+
const orNotFound = (found) => found.length > 0 ? found : proxy && project.middlewareFile ? routes : routes.filter((route) => route.page === "/_not-found/page");
|
|
126
|
+
if (!url.includes("\0")) {
|
|
127
|
+
const pages = await pagesOf(new URL(url, "http://localhost"));
|
|
128
|
+
return orNotFound(routes.filter((route) => pages.has(route.page)));
|
|
129
|
+
}
|
|
130
|
+
let pathname = url.split(/[?#]/)[0].replace(/^[a-z][a-z\d+.-]*:\/\/[^/]*/i, "");
|
|
131
|
+
if (pathname.startsWith("\0")) {
|
|
132
|
+
const slash = pathname.indexOf("/");
|
|
133
|
+
if (slash === -1) return routes;
|
|
134
|
+
pathname = pathname.slice(slash);
|
|
135
|
+
}
|
|
136
|
+
if (!pathname.startsWith("/")) pathname = `/${pathname}`;
|
|
137
|
+
const sample = await pagesOf(new URL(pathname.replaceAll("\0", "-x-"), "http://localhost"));
|
|
138
|
+
if (routing.basePath && pathname.startsWith(routing.basePath)) pathname = pathname.slice(routing.basePath.length);
|
|
139
|
+
const parts = segmentsOf(pathname);
|
|
140
|
+
const found = routes.filter((route) => sample.has(route.page) || matches(parts, segmentsOf(route.pathname)));
|
|
141
|
+
return found.length > 0 ? found : routes;
|
|
142
|
+
}
|
|
143
|
+
const segmentsOf = (pathname) => pathname.split("/").filter(Boolean);
|
|
144
|
+
const isCatchAll = (segment) => /^\[\[?\.\.\./.test(segment);
|
|
145
|
+
const isOptional = (segment) => segment.startsWith("[[...");
|
|
146
|
+
const isDynamic = (segment) => segment.startsWith("[");
|
|
147
|
+
function matches(url, route) {
|
|
148
|
+
const [part, ...restOfUrl] = url;
|
|
149
|
+
const [segment, ...restOfRoute] = route;
|
|
150
|
+
if (part === void 0) return route.every(isOptional);
|
|
151
|
+
if (segment === void 0) return false;
|
|
152
|
+
if (restOfUrl.length === 0 && part !== "\0" && part.endsWith("\0")) {
|
|
153
|
+
if (isDynamic(segment)) return true;
|
|
154
|
+
return patternOf(part).test(segment);
|
|
155
|
+
}
|
|
156
|
+
if (isCatchAll(segment)) return matches(restOfUrl, route) || matches(restOfUrl, restOfRoute) || isOptional(segment) && matches(url, restOfRoute);
|
|
157
|
+
if (isDynamic(segment)) return matches(restOfUrl, restOfRoute);
|
|
158
|
+
return patternOf(part).test(segment) && matches(restOfUrl, restOfRoute);
|
|
159
|
+
}
|
|
160
|
+
const patternOf = (part) => new RegExp(`^${part.split("\0").map((text) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join(".*")}$`);
|
|
161
|
+
/** The plugin that adds the routes a module opens to the map of Vitest's lookup. */
|
|
162
|
+
function routeLookupPlugin({ getProject, specifier }) {
|
|
163
|
+
const servers = lookupServers();
|
|
164
|
+
const filesOf = /* @__PURE__ */ new WeakMap();
|
|
165
|
+
const filesOfRoute = (project, route) => {
|
|
166
|
+
if (!filesOf.has(project)) filesOf.set(project, /* @__PURE__ */ new Map());
|
|
167
|
+
const files = filesOf.get(project);
|
|
168
|
+
if (!files.has(route)) files.set(route, project.loadRouteEntry(route).then(({ watchFiles }) => watchFiles.map(normalizePath).filter((file) => !file.includes("/node_modules/"))));
|
|
169
|
+
return files.get(route);
|
|
170
|
+
};
|
|
171
|
+
const routesOfRequests = async (project, requests) => {
|
|
172
|
+
if (requests === "every") return project.routes;
|
|
173
|
+
try {
|
|
174
|
+
const opened = requests.filter(({ route }) => route);
|
|
175
|
+
return (await Promise.all(opened.map((request) => routesOf(project, request)))).flat();
|
|
176
|
+
} catch {
|
|
177
|
+
return project.routes;
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
return {
|
|
181
|
+
name: "vitest-plugin-rsc:next-route-lookup",
|
|
182
|
+
enforce: "post",
|
|
183
|
+
configureVitest({ project, defineCacheKeyGenerator }) {
|
|
184
|
+
if (!servers.add(project)) return;
|
|
185
|
+
defineCacheKeyGenerator(({ environment, sourceCode }) => environment.name === lookupEnvironment && sourceCode.includes(specifier) ? false : void 0);
|
|
186
|
+
},
|
|
187
|
+
async transform(code, id) {
|
|
188
|
+
if (!servers.inLookup(this)) return;
|
|
189
|
+
if (!code.includes(specifier) || id.includes("/node_modules/")) return;
|
|
190
|
+
const requests = await requestsOf(code, specifier);
|
|
191
|
+
if (requests !== "every" && requests.length === 0) return;
|
|
192
|
+
const project = getProject();
|
|
193
|
+
const routes = await routesOfRequests(project, requests);
|
|
194
|
+
const files = new Set((await Promise.all(routes.map((route) => filesOfRoute(project, route)))).flat());
|
|
195
|
+
const proxy = requests === "every" || requests.some((request) => request.proxy);
|
|
196
|
+
if (project.middlewareFile && proxy) files.add(normalizePath(project.middlewareFile));
|
|
197
|
+
files.delete(normalizePath(id.split("?")[0]));
|
|
198
|
+
if (files.size === 0) return;
|
|
199
|
+
return {
|
|
200
|
+
code: `${code}\nvoid (() => [${[...files].sort().map((file) => `import(${JSON.stringify(file)})`).join(", ")}]);\n`,
|
|
201
|
+
map: null
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
//#endregion
|
|
207
|
+
export { placeholder, requestsOf, routeLookupPlugin, routesOf };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
//#region src/routes-ahead-shared.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Where the page tells the dev server of a test file that starts in it, and
|
|
4
|
+
* of a route it opened, over the websocket of the page (`askDevServer()` in
|
|
5
|
+
* ../utils.ts), with a `RoutesAheadNews`.
|
|
6
|
+
*/
|
|
7
|
+
declare const routesAheadEvent = "vitest-plugin-rsc:next-routes-ahead";
|
|
8
|
+
/**
|
|
9
|
+
* A test file that starts in a tab, or a route that one opened, by the name
|
|
10
|
+
* of its modules. The file is absolute, as Vitest names it.
|
|
11
|
+
*/
|
|
12
|
+
type RoutesAheadNews = {
|
|
13
|
+
testFile: string;
|
|
14
|
+
opened?: string;
|
|
15
|
+
};
|
|
16
|
+
//#endregion
|
|
17
|
+
export { RoutesAheadNews, routesAheadEvent };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
//#region src/routes-ahead-shared.ts
|
|
2
|
+
/**
|
|
3
|
+
* Where the page tells the dev server of a test file that starts in it, and
|
|
4
|
+
* of a route it opened, over the websocket of the page (`askDevServer()` in
|
|
5
|
+
* ../utils.ts), with a `RoutesAheadNews`.
|
|
6
|
+
*/
|
|
7
|
+
const routesAheadEvent = "vitest-plugin-rsc:next-routes-ahead";
|
|
8
|
+
//#endregion
|
|
9
|
+
export { routesAheadEvent };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Plugin, ViteDevServer } from "vite";
|
|
2
|
+
|
|
3
|
+
//#region src/routes-ahead.d.ts
|
|
4
|
+
type RoutesAheadOptions = {
|
|
5
|
+
/** The root of the project, under whose `node_modules/.cache/` the routes are kept. */root(): string; /** Has the dev server compile a route, by the name of its modules: see `compileRoute()` in styles.ts. */
|
|
6
|
+
compile(vite: ViteDevServer, entry: string): Promise<unknown>;
|
|
7
|
+
};
|
|
8
|
+
declare function routesAheadPlugin(options: RoutesAheadOptions): Plugin;
|
|
9
|
+
//#endregion
|
|
10
|
+
export { RoutesAheadOptions, routesAheadPlugin };
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { routesAheadEvent } from "./routes-ahead-shared.js";
|
|
2
|
+
import { normalizePath } from "vite";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import { answerPage } from "@kasperpeulen/vitest-plugin-rsc/internal/page-bridge";
|
|
6
|
+
//#region src/routes-ahead.ts
|
|
7
|
+
const cacheFile = "node_modules/.cache/vitest-plugin-rsc/routes-ahead-1.json";
|
|
8
|
+
function routesAheadPlugin(options) {
|
|
9
|
+
let kept;
|
|
10
|
+
const opened = /* @__PURE__ */ new Map();
|
|
11
|
+
function read() {
|
|
12
|
+
if (kept) return kept;
|
|
13
|
+
try {
|
|
14
|
+
const { files } = JSON.parse(fs.readFileSync(path.join(options.root(), cacheFile), "utf8"));
|
|
15
|
+
kept = new Map(Object.entries(files));
|
|
16
|
+
} catch {
|
|
17
|
+
kept = /* @__PURE__ */ new Map();
|
|
18
|
+
}
|
|
19
|
+
return kept;
|
|
20
|
+
}
|
|
21
|
+
function write(routes) {
|
|
22
|
+
const file = path.join(options.root(), cacheFile);
|
|
23
|
+
try {
|
|
24
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
25
|
+
const written = `${file}.${process.pid}`;
|
|
26
|
+
fs.writeFileSync(written, JSON.stringify({ files: Object.fromEntries(routes) }));
|
|
27
|
+
fs.renameSync(written, file);
|
|
28
|
+
} catch {}
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
name: "vitest-plugin-rsc:next-routes-ahead",
|
|
32
|
+
configureServer(vite) {
|
|
33
|
+
answerPage(vite, routesAheadEvent, async ({ testFile, opened: entry }) => {
|
|
34
|
+
const file = normalizePath(path.relative(options.root(), testFile));
|
|
35
|
+
const routes = read();
|
|
36
|
+
if (entry === void 0) {
|
|
37
|
+
for (const ahead of routes.get(file) ?? []) await options.compile(vite, ahead).catch(() => {});
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
const now = opened.get(file) ?? [];
|
|
41
|
+
opened.set(file, now);
|
|
42
|
+
if (now.includes(entry)) return null;
|
|
43
|
+
now.push(entry);
|
|
44
|
+
const before = routes.get(file);
|
|
45
|
+
if (before?.length === now.length && before.every((route, i) => route === now[i])) return null;
|
|
46
|
+
routes.set(file, [...now]);
|
|
47
|
+
write(routes);
|
|
48
|
+
return null;
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
//#endregion
|
|
54
|
+
export { routesAheadPlugin };
|
package/dist/rsc.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
//#region src/rsc.d.ts
|
|
2
|
+
/** The page module of the route of a node: see `loadNodeEntry()` in project/entries.ts. */
|
|
3
|
+
declare function loadComponent(): Promise<{
|
|
4
|
+
default: () => unknown;
|
|
5
|
+
}>;
|
|
6
|
+
declare global {
|
|
7
|
+
var __vite_rsc_require__: (id: string) => Promise<unknown>;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Next's `__next_app__.require`: the module loader of the server bundle, which
|
|
11
|
+
* holds the modules of both server layers. React's Flight codec calls it for a
|
|
12
|
+
* reference in a payload, and Next calls it for the module of a Server Action.
|
|
13
|
+
*
|
|
14
|
+
* Like webpack's, it returns the same promise for the same id: React keeps the
|
|
15
|
+
* state of a module on that promise.
|
|
16
|
+
*/
|
|
17
|
+
declare function requireModule(id: string): Promise<unknown>;
|
|
18
|
+
//#endregion
|
|
19
|
+
export { loadComponent, requireModule };
|
package/dist/rsc.js
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { actionModulePrefix, registry } from "./registry.js";
|
|
2
|
+
import { builtStylesheetsOf, stylesheetsEvent } from "./styles-shared.js";
|
|
3
|
+
import { buildDirectory, builtFiles } from "./built-files.js";
|
|
4
|
+
import { preloadPage } from "./document.js";
|
|
5
|
+
import { NoFallbackError } from "./compat/rsc/handlers.js";
|
|
6
|
+
import { nextConfig, speedFeatures } from "virtual:vitest-plugin-rsc/next-manifest";
|
|
7
|
+
import { askDevServer, versionsOfAnswers } from "@kasperpeulen/vitest-plugin-rsc/internal/utils";
|
|
8
|
+
import { createElement } from "react";
|
|
9
|
+
import { NodeRendered } from "@kasperpeulen/vitest-plugin-nextjs/node-rendered";
|
|
10
|
+
import { createServerManifest } from "@vitejs/plugin-rsc/core/rsc";
|
|
11
|
+
import * as ReactServer from "@vitejs/plugin-rsc/react/rsc";
|
|
12
|
+
import { prerender } from "@vitejs/plugin-rsc/react/rsc/static";
|
|
13
|
+
import * as FlightServer from "@vitejs/plugin-rsc/vendor/react-server-dom/server.edge";
|
|
14
|
+
import appPages from "virtual:vitest-plugin-rsc/next-app-pages";
|
|
15
|
+
import loadMiddleware from "virtual:vitest-plugin-rsc/next-middleware";
|
|
16
|
+
import routeHandlers from "virtual:vitest-plugin-rsc/next-route-handlers";
|
|
17
|
+
import serverReferences from "virtual:vitest-plugin-rsc/server-references";
|
|
18
|
+
//#region src/rsc.ts
|
|
19
|
+
ReactServer.setRequireModule({ load: (id) => {
|
|
20
|
+
if (!serverReferences) return __vite_rsc_raw_import__(id);
|
|
21
|
+
const load = serverReferences[id];
|
|
22
|
+
if (!load) return Promise.reject(/* @__PURE__ */ new Error(`vitest-plugin-rsc: the build has no Server Action module "${id}"`));
|
|
23
|
+
return load();
|
|
24
|
+
} });
|
|
25
|
+
registry.speedFeatures = speedFeatures;
|
|
26
|
+
registry.flightServer = {
|
|
27
|
+
renderToReadableStream: (model, _clientModules, options) => ReactServer.renderToReadableStream(model, options),
|
|
28
|
+
decodeReply: (body, _serverModules, options) => ReactServer.decodeReply(body, options),
|
|
29
|
+
decodeReplyFromAsyncIterable: (body, _serverModules, options) => FlightServer.decodeReplyFromAsyncIterable(body, createServerManifest(), options),
|
|
30
|
+
decodeAction: (body) => ReactServer.decodeAction(body),
|
|
31
|
+
decodeFormState: (result, body) => ReactServer.decodeFormState(result, body),
|
|
32
|
+
createTemporaryReferenceSet: ReactServer.createTemporaryReferenceSet,
|
|
33
|
+
registerServerReference: ReactServer.registerServerReference,
|
|
34
|
+
registerClientReference: ReactServer.registerClientReference,
|
|
35
|
+
createClientModuleProxy: FlightServer.createClientModuleProxy
|
|
36
|
+
};
|
|
37
|
+
registry.flightServer.decodeReplyFromBusboy = (busboy, _serverModules, options) => new Promise((resolve, reject) => {
|
|
38
|
+
const form = new FormData();
|
|
39
|
+
busboy.on("field", (name, value) => form.append(name, value));
|
|
40
|
+
busboy.on("file", (name, file, info) => {
|
|
41
|
+
const chunks = [];
|
|
42
|
+
file.on("data", (chunk) => chunks.push(new Uint8Array(chunk)));
|
|
43
|
+
file.on("end", () => form.append(name, new File(chunks, info.filename, { type: info.mimeType })));
|
|
44
|
+
});
|
|
45
|
+
busboy.on("error", reject);
|
|
46
|
+
busboy.on("finish", () => resolve(ReactServer.decodeReply(form, options)));
|
|
47
|
+
});
|
|
48
|
+
registry.flightStatic = { prerender: (model, _clientModules, options) => prerender(model, options) };
|
|
49
|
+
registry.flightClient = {
|
|
50
|
+
createFromReadableStream: (stream, { serverConsumerManifest: _, ...options } = {}) => ReactServer.createFromReadableStream(stream, options),
|
|
51
|
+
encodeReply: ReactServer.encodeReply,
|
|
52
|
+
createTemporaryReferenceSet: ReactServer.createClientTemporaryReferenceSet
|
|
53
|
+
};
|
|
54
|
+
function withServerCache(entry) {
|
|
55
|
+
const { routeModule } = entry;
|
|
56
|
+
if (routeModule) routeModule.getIncrementalCache = async () => globalThis.__incrementalCache;
|
|
57
|
+
return entry;
|
|
58
|
+
}
|
|
59
|
+
registry.loadAppPage = async (page) => {
|
|
60
|
+
const load = appPages[page];
|
|
61
|
+
if (!load) throw new Error(`vitest-plugin-rsc: unknown Next.js app page ${page}`);
|
|
62
|
+
return registry.appPages[page] ??= withServerCache(await load());
|
|
63
|
+
};
|
|
64
|
+
const directory = buildDirectory;
|
|
65
|
+
if (directory) {
|
|
66
|
+
builtFiles().catch(() => {});
|
|
67
|
+
const warned = /* @__PURE__ */ new Set();
|
|
68
|
+
const missing = (file) => {
|
|
69
|
+
if (warned.has(file)) return;
|
|
70
|
+
warned.add(file);
|
|
71
|
+
console.warn(`vitest-plugin-rsc: the files that render a node, \`hostFiles\` of renderServer(), name ${file}, which the static build has no stylesheets for: it is no file of the host that the rsc layer built. In a build the files of the host are what \`host.files\` of vitestPluginNext() matches, named from the root of the project. The stylesheets it imports are not linked.`);
|
|
72
|
+
};
|
|
73
|
+
registry.loadStylesheets = async (entry, _inline, files) => builtStylesheetsOf(await builtFiles(), entry, directory, files, missing);
|
|
74
|
+
const linksAtPath = process.env.NODE_ENV === "production" && !nextConfig.deploymentId;
|
|
75
|
+
const encoded = (path) => path.split("/").map(encodeURIComponent).join("/");
|
|
76
|
+
registry.preloadRoute = async (entry, files, signal) => {
|
|
77
|
+
const built = await builtFiles().catch(() => void 0);
|
|
78
|
+
if (!built || signal?.aborted) return;
|
|
79
|
+
const linked = Object.values(builtStylesheetsOf(built, entry, directory, files)).flat();
|
|
80
|
+
preloadPage({
|
|
81
|
+
stylesheets: linksAtPath ? linked.map(({ path }) => new URL(`${nextConfig.assetPrefix}/_next/${encoded(path)}`, location.href).href) : [],
|
|
82
|
+
crossOrigin: nextConfig.crossOrigin || void 0,
|
|
83
|
+
modules: (built.modules[entry] ?? []).map((index) => new URL(built.chunks[index], directory).href)
|
|
84
|
+
});
|
|
85
|
+
};
|
|
86
|
+
} else {
|
|
87
|
+
const stylesheets = /* @__PURE__ */ new Map();
|
|
88
|
+
let stylesheetsOf;
|
|
89
|
+
registry.loadStylesheets = async (entry, inline, files) => {
|
|
90
|
+
registry.routeOpened?.(entry);
|
|
91
|
+
const versions = await versionsOfAnswers();
|
|
92
|
+
if (versions !== stylesheetsOf) {
|
|
93
|
+
stylesheets.clear();
|
|
94
|
+
stylesheetsOf = versions;
|
|
95
|
+
}
|
|
96
|
+
const key = JSON.stringify([
|
|
97
|
+
entry,
|
|
98
|
+
inline,
|
|
99
|
+
files
|
|
100
|
+
]);
|
|
101
|
+
let answer = stylesheets.get(key);
|
|
102
|
+
if (!answer) {
|
|
103
|
+
const asked = askDevServer(stylesheetsEvent, {
|
|
104
|
+
entry,
|
|
105
|
+
inline,
|
|
106
|
+
files
|
|
107
|
+
});
|
|
108
|
+
stylesheets.set(key, answer = asked);
|
|
109
|
+
asked.catch(() => stylesheets.get(key) === asked && stylesheets.delete(key));
|
|
110
|
+
}
|
|
111
|
+
return answer;
|
|
112
|
+
};
|
|
113
|
+
registry.preloadRoute = async () => {};
|
|
114
|
+
}
|
|
115
|
+
/** The page module of the route of a node: see `loadNodeEntry()` in project/entries.ts. */
|
|
116
|
+
async function loadComponent() {
|
|
117
|
+
return { default: function Component() {
|
|
118
|
+
const node = (registry.openedByRequest.getStore() ?? registry.opened)?.node;
|
|
119
|
+
if (!node) throw new Error("vitest-plugin-rsc: the node of the test is gone");
|
|
120
|
+
const { ui, version } = node;
|
|
121
|
+
return version === void 0 ? ui : createElement(NodeRendered, { version }, ui);
|
|
122
|
+
} };
|
|
123
|
+
}
|
|
124
|
+
const loadRouteHandler = async (page) => {
|
|
125
|
+
const load = routeHandlers[page];
|
|
126
|
+
if (!load) throw new Error(`vitest-plugin-rsc: unknown Next.js route handler ${page}`);
|
|
127
|
+
return withServerCache(await load());
|
|
128
|
+
};
|
|
129
|
+
registry.loadRouteHandler = async (page) => (await loadRouteHandler(page)).handler;
|
|
130
|
+
registry.prerenderVerdict = async ({ kind, page, pathname }) => {
|
|
131
|
+
const { prerenderVerdictOf } = await import("virtual:vitest-plugin-rsc/next-prerender");
|
|
132
|
+
return prerenderVerdictOf(kind === "route" ? await loadRouteHandler(page) : await registry.loadAppPage(page), pathname, kind);
|
|
133
|
+
};
|
|
134
|
+
registry.isNoFallbackError = (error) => error instanceof NoFallbackError;
|
|
135
|
+
registry.loadMiddleware = async () => {
|
|
136
|
+
if (!loadMiddleware) throw new Error("vitest-plugin-rsc: the app has no middleware");
|
|
137
|
+
return (await loadMiddleware()).handler;
|
|
138
|
+
};
|
|
139
|
+
const modules = /* @__PURE__ */ new Map();
|
|
140
|
+
/**
|
|
141
|
+
* Next's `__next_app__.require`: the module loader of the server bundle, which
|
|
142
|
+
* holds the modules of both server layers. React's Flight codec calls it for a
|
|
143
|
+
* reference in a payload, and Next calls it for the module of a Server Action.
|
|
144
|
+
*
|
|
145
|
+
* Like webpack's, it returns the same promise for the same id: React keeps the
|
|
146
|
+
* state of a module on that promise.
|
|
147
|
+
*/
|
|
148
|
+
function requireModule(id) {
|
|
149
|
+
let loading = modules.get(id);
|
|
150
|
+
if (!loading) modules.set(id, loading = loadModule(id));
|
|
151
|
+
return loading;
|
|
152
|
+
}
|
|
153
|
+
registry.ownServerAction = import("./server-action.js").then(({ runInServerActionOfTest }) => Reflect.get(runInServerActionOfTest, "$$id"));
|
|
154
|
+
registry.hasServerAction = async (id) => {
|
|
155
|
+
if (!id.includes("#")) return false;
|
|
156
|
+
try {
|
|
157
|
+
return (await requireModule(actionModulePrefix + id))[id]?.$$typeof === Symbol.for("react.server.reference");
|
|
158
|
+
} catch {
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
async function loadModule(id) {
|
|
163
|
+
if (id.startsWith("action:")) {
|
|
164
|
+
const actionId = id.slice(actionModulePrefix.length);
|
|
165
|
+
return { [actionId]: await ReactServer.loadServerAction(actionId) };
|
|
166
|
+
}
|
|
167
|
+
if (id.startsWith("$$server:")) return globalThis.__vite_rsc_require__(id);
|
|
168
|
+
return registry.loadSsrModule(id);
|
|
169
|
+
}
|
|
170
|
+
//#endregion
|
|
171
|
+
export { loadComponent, requireModule };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
//#region src/server-action.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* The Server Action that `runInServerAction()` calls: it runs the function the
|
|
4
|
+
* test handed over. The test and the server share their modules, so that is
|
|
5
|
+
* the test's own function, inside the request of the action.
|
|
6
|
+
*/
|
|
7
|
+
declare function runInServerActionOfTest(key: number): Promise<void>;
|
|
8
|
+
//#endregion
|
|
9
|
+
export { runInServerActionOfTest };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use server";
|
|
2
|
+
import { registry } from "./registry.js";
|
|
3
|
+
//#region src/server-action.ts
|
|
4
|
+
/**
|
|
5
|
+
* The Server Action that `runInServerAction()` calls: it runs the function the
|
|
6
|
+
* test handed over. The test and the server share their modules, so that is
|
|
7
|
+
* the test's own function, inside the request of the action.
|
|
8
|
+
*/
|
|
9
|
+
async function runInServerActionOfTest(key) {
|
|
10
|
+
const action = registry.serverActions.get(key);
|
|
11
|
+
if (!action) throw new Error("vitest-plugin-rsc: no function to run as a Server Action");
|
|
12
|
+
await action();
|
|
13
|
+
}
|
|
14
|
+
//#endregion
|
|
15
|
+
export { runInServerActionOfTest };
|