@rsc-kit/core 0.30.4 → 0.30.5
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/dist/testing.d.ts +0 -7
- package/dist/testing.js +23 -0
- package/dist/testing.js.map +1 -1
- package/package.json +1 -1
package/dist/testing.d.ts
CHANGED
|
@@ -84,11 +84,4 @@ export declare function staticFile(root: string, request: Request): Response | n
|
|
|
84
84
|
*/
|
|
85
85
|
/** @internal Exported for its test. */
|
|
86
86
|
export declare function buildCommand(root: string): [string, string[]];
|
|
87
|
-
/**
|
|
88
|
-
* Build the app if it needs it, load it, and hand back something to fetch from.
|
|
89
|
-
*
|
|
90
|
-
* Memoised per root: every test file in a run shares one build and one loaded
|
|
91
|
-
* module, which is both the fast path and the correct one — two copies of the
|
|
92
|
-
* server bundle in one process would be two client-reference registries.
|
|
93
|
-
*/
|
|
94
87
|
export declare function createTestApp(options?: TestAppOptions): Promise<TestApp>;
|
package/dist/testing.js
CHANGED
|
@@ -187,7 +187,30 @@ async function ensureBuilt(root, build) {
|
|
|
187
187
|
* module, which is both the fast path and the correct one — two copies of the
|
|
188
188
|
* server bundle in one process would be two client-reference registries.
|
|
189
189
|
*/
|
|
190
|
+
/**
|
|
191
|
+
* Whether Request is the runtime's, told apart from a browser's by behaviour.
|
|
192
|
+
*
|
|
193
|
+
* A browser forbids a script from setting Cookie, so happy-dom's Request drops
|
|
194
|
+
* it from an init - and every signed-in request a test makes arrives with no
|
|
195
|
+
* session, bounces to sign-in, and fails far from the cause. Behaviour rather
|
|
196
|
+
* than identity, because identity depends on which file loaded first.
|
|
197
|
+
*/
|
|
198
|
+
function runtimeRequest() {
|
|
199
|
+
try {
|
|
200
|
+
return new Request('http://probe.test/', { headers: { cookie: 'probe=1' } }).headers.has('cookie');
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
190
206
|
export async function createTestApp(options = {}) {
|
|
207
|
+
if (!runtimeRequest()) {
|
|
208
|
+
throw new Error("[rsc-kit] createTestApp is running against a browser's Request: a DOM is registered globally " +
|
|
209
|
+
'(happy-dom, in a preload or an earlier test file), and it replaces Request, Response, Headers and fetch. ' +
|
|
210
|
+
'A browser cannot set Cookie, so every signed-in request would arrive with no session. ' +
|
|
211
|
+
'Register the DOM only in the component test file and `GlobalRegistrator.unregister()` after it, ' +
|
|
212
|
+
'and run the suites isolated (`bun test --isolate`). See the testing guide, "Components".');
|
|
213
|
+
}
|
|
191
214
|
const root = resolve(options.root ?? process.cwd());
|
|
192
215
|
const origin = options.origin ?? 'https://app.test';
|
|
193
216
|
built.set(root, built.get(root) ?? ensureBuilt(root, options.build ?? true));
|
package/dist/testing.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testing.js","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"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","sourcesContent":["// The whole app as a function, for tests.\n//\n// import { createTestApp } from '@rsc-kit/core/testing'\n//\n// const app = await createTestApp()\n// const res = await app.fetch('/api/orders', { headers: { Cookie: 'user=ada' } })\n//\n// No port, no browser, no server process. The built entry already exports the\n// same Request → Response handler the dev server and the production server\n// both call, so a test can hand it a Request and read the Response — through\n// the real router, the real middleware, the real api routes, and the pages the\n// build stored.\n//\n// That is the tier between a unit test and a browser. An action or a route is\n// a function and can be imported and called; a browser test proves the page\n// works; this proves the app answers a url the way it is deployed, which is\n// where a route that renders fine and is served wrong shows up.\n\nimport { spawnSync } from 'node:child_process'\nimport { existsSync, readFileSync, readdirSync, statSync } from 'node:fs'\nimport { extname, join, resolve, sep } from 'node:path'\nimport { pathToFileURL } from 'node:url'\nimport { httpHostCalls } from './hostCalls.js'\nimport { configureChanged } from './changed.js'\nimport { testHostFetch, type TestHost } from './testHost.js'\n\nexport { hostReply, testChanges, HOST_MIDDLEWARE, type HostCallInput, type HostHandler, type TestHost } from './testHost.js'\n\nexport interface TestApp {\n /** A path, not a url. The origin is whatever the app was told it is. */\n fetch(path: string, init?: RequestInit): Promise<Response>\n /**\n * What each region of the page at `path` refreshes on, rendered for this\n * request: `{ page: ['ledger'], repos: ['team:1:repos'] }` - `page` for the\n * page's own `refreshOn`, a section's name for each section's. A region\n * whose refreshOn gave no names is there with `[]`; one with no refreshOn\n * is not there at all.\n *\n * expect(await app.watched('/teams/1')).toMatchObject({ repos: ['team:1:repos'] })\n *\n * The names a tab would be handed, without reading them out of the payload.\n */\n watched(path: string, init?: RequestInit): Promise<Record<string, string[]>>\n /**\n * The page at `path` as markup: the whole streamed document, with every\n * `<script>` taken out.\n *\n * The document carries the page's payload for the browser to hydrate from,\n * and the payload holds everything the page was given - a client\n * component's props included, shown or not. A test asserting that something\n * is NOT on the page reads this, not `fetch`:\n *\n * expect(await app.markup('/teams/1')).not.toContain('internal note')\n *\n * Regions that streamed in later are included where they arrived; a\n * Suspense fallback they replaced is still in the markup too, as the\n * server sent it.\n */\n markup(path: string, init?: RequestInit): Promise<string>\n /** Where the build was read from, for a test that wants to look. */\n bundle: string\n}\n\nexport interface TestAppOptions {\n /** The project. Defaults to the working directory. */\n root?: string\n /**\n * Whether to build first.\n *\n * `true` builds when the source is newer than the last build, which is what\n * a test run wants: the first run pays for it, the rest do not, and an edit\n * is picked up. `false` never builds and fails loudly if there is nothing to\n * read — for a ci step that already built.\n */\n build?: boolean\n /** The origin requests are made against. Nothing reads it; it is a url. */\n origin?: string\n /**\n * The backend, answered in the test: rpc() calls and host guards by name.\n * The app reaches it through the same client it uses against a real one,\n * so nothing listens and RSC_BACKEND is not called. See `hostReply`.\n */\n host?: TestHost\n /**\n * What answers a url this app does not own - a Go route, a Laravel page,\n * /login - in place of the backend the app forwards it to. Handed the\n * request as the backend would receive it. Without one, such a url is\n * forwarded to RSC_BACKEND, and in a test that is usually a 502.\n */\n backend?: (request: Request) => Response | Promise<Response>\n}\n\n/**\n * Where the build put the server bundle.\n *\n * Two layouts, because two ways of building. Under Nitro — every scaffolded\n * app — it is inside Nitro's own directory. On its own the plugin writes\n * <outDir>/dist/rsc. The first that exists wins.\n */\nfunction findBundle(root: string): string | null {\n const candidates = [\n join(root, 'node_modules/.nitro/vite/services/rsc/index.js'),\n // The default outDir since 0.29.8; .rsc before it.\n join(root, '.rsc-kit/dist/rsc/index.js'),\n join(root, '.rsc/dist/rsc/index.js'),\n join(root, 'build/dist/rsc/index.js'),\n ]\n\n return candidates.find((path) => existsSync(path)) ?? null\n}\n\n/**\n * When anything a build is made from last changed: the app's source, its\n * Vite config, its dependencies - package.json, the lockfile, and the\n * installed engine itself. Only src/ used to count, so an app that upgraded\n * @rsc-kit/core kept testing the build the old one made, and passed.\n */\nfunction newestInput(root: string): number {\n const files = [\n 'vite.config.ts', 'vite.config.mts', 'vite.config.js', 'vite.config.mjs',\n 'package.json', 'bun.lock', 'bun.lockb', 'package-lock.json', 'pnpm-lock.yaml', 'yarn.lock',\n ]\n let latest = newest(join(root, 'src'))\n\n for (const file of files) {\n const path = join(root, file)\n\n if (existsSync(path)) latest = Math.max(latest, statSync(path).mtimeMs)\n }\n\n // The engine as installed: a workspace link changes without the lockfile.\n const engine = join(root, 'node_modules/@rsc-kit/core/dist')\n\n return Math.max(latest, newest(engine))\n}\n\n/** The newest mtime under a directory, for deciding whether a build is stale. */\nfunction newest(dir: string): number {\n if (!existsSync(dir)) return 0\n\n let latest = 0\n\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n if (entry.name === 'node_modules' || entry.name.startsWith('.')) continue\n\n const path = join(dir, entry.name)\n const time = entry.isDirectory() ? newest(path) : statSync(path).mtimeMs\n\n if (time > latest) latest = time\n }\n\n return latest\n}\n\n/** One build per process per root, however many test files ask. */\nconst built = new Map<string, Promise<string>>()\n\n/** The content types a test is likely to assert on; anything else is octet-stream. */\nconst CONTENT_TYPES: Record<string, string> = {\n '.html': 'text/html; charset=utf-8',\n '.js': 'text/javascript; charset=utf-8',\n '.mjs': 'text/javascript; charset=utf-8',\n '.css': 'text/css; charset=utf-8',\n '.json': 'application/json; charset=utf-8',\n '.webmanifest': 'application/manifest+json',\n '.xml': 'application/xml',\n '.txt': 'text/plain; charset=utf-8',\n '.svg': 'image/svg+xml',\n '.png': 'image/png',\n '.jpg': 'image/jpeg',\n '.jpeg': 'image/jpeg',\n '.webp': 'image/webp',\n '.avif': 'image/avif',\n '.gif': 'image/gif',\n '.ico': 'image/x-icon',\n '.woff': 'font/woff',\n '.woff2': 'font/woff2',\n '.wasm': 'application/wasm',\n '.map': 'application/json',\n}\n\n/**\n * A file the build put in .output/public, answered the way Nitro's static\n * layer would - which the handler this harness loads does not include. The\n * handler is the rsc service: routing, pages, actions, api routes. Assets,\n * the service worker, the manifest and the icons are files Nitro serves in\n * production from .output/public, and a test that asked for /sw.js used to\n * get the router's 404 for a file the deployment serves fine - the one\n * shape this harness exists to catch, in the other direction.\n *\n * Files only, for GET and HEAD, within the directory: a path that escapes it\n * is a request for the handler, not a file.\n */\n/** @internal Exported for its test. */\nexport function staticFile(root: string, request: Request): Response | null {\n if (request.method !== 'GET' && request.method !== 'HEAD') return null\n\n const publicDir = join(root, '.output/public')\n const pathname = decodeURIComponent(new URL(request.url).pathname)\n const file = resolve(publicDir, '.' + pathname)\n\n if (!file.startsWith(publicDir + sep) || !existsSync(file) || !statSync(file).isFile()) return null\n\n const type = CONTENT_TYPES[extname(file).toLowerCase()] ?? 'application/octet-stream'\n const body = request.method === 'HEAD' ? null : readFileSync(file)\n\n return new Response(body, {\n status: 200,\n headers: { 'Content-Type': type, 'Cache-Control': 'no-store', 'X-RSC-Kit-Test': 'static' },\n })\n}\n\n/**\n * How this project builds: its own `build` script, run by the package manager\n * this test is running under. Without a script, Vite directly - on Bun's\n * runtime when the tests are, since a bin's node shebang would otherwise\n * start it under Node.\n */\n/** @internal Exported for its test. */\nexport function buildCommand(root: string): [string, string[]] {\n const onBun = typeof process.versions.bun === 'string'\n\n let scripts: Record<string, string> = {}\n\n try {\n scripts = JSON.parse(readFileSync(join(root, 'package.json'), 'utf-8')).scripts ?? {}\n } catch {\n // No package.json, or not JSON: there is no script to run.\n }\n\n if (typeof scripts.build === 'string') {\n return onBun ? ['bun', ['run', 'build']] : ['npm', ['run', 'build']]\n }\n\n return onBun ? ['bun', ['--bun', 'vite', 'build']] : ['npx', ['vite', 'build']]\n}\n\nasync function ensureBuilt(root: string, build: boolean): Promise<string> {\n const existing = findBundle(root)\n\n if (!build) {\n if (!existing) {\n throw new Error(\n `[rsc-kit] No build to test against under ${root}. Run the build first, or let createTestApp() do it by leaving \\`build\\` on.`,\n )\n }\n\n return existing\n }\n\n const fresh = existing && statSync(existing).mtimeMs > newestInput(root)\n\n if (fresh) return existing\n\n // The user's own build command, so what is tested is what ships. A test\n // that built some other way would pass against a bundle nobody deploys -\n // and used to: this ran `npx vite build` whatever package.json said, which\n // on a Bun project built under Node and failed on the first `import 'bun'`.\n const [command, args] = buildCommand(root)\n const run = spawnSync(command, args, {\n cwd: root,\n stdio: ['ignore', 'pipe', 'pipe'],\n env: { ...process.env, NODE_ENV: 'production' },\n })\n\n if (run.status !== 0) {\n throw new Error(`[rsc-kit] The build failed, so there is nothing to test:\\n${run.stderr}`)\n }\n\n const bundle = findBundle(root)\n\n if (!bundle) {\n throw new Error(\n `[rsc-kit] The build finished but no server bundle was found under ${root}. Is rscKit() in vite.config?`,\n )\n }\n\n return bundle\n}\n\n/**\n * Build the app if it needs it, load it, and hand back something to fetch from.\n *\n * Memoised per root: every test file in a run shares one build and one loaded\n * module, which is both the fast path and the correct one — two copies of the\n * server bundle in one process would be two client-reference registries.\n */\nexport async function createTestApp(options: TestAppOptions = {}): Promise<TestApp> {\n const root = resolve(options.root ?? process.cwd())\n const origin = options.origin ?? 'https://app.test'\n\n built.set(root, built.get(root) ?? ensureBuilt(root, options.build ?? true))\n\n // The app is built for production, which refuses to sign refreshOn names\n // without a key. One process, one key: a fixed one, unless the test set its own.\n if (!process.env.RSC_SIGNING_SECRET) {\n configureChanged({ secret: 'rsc-kit-test' })\n }\n\n const bundle = await built.get(root)!\n const entry = (await import(pathToFileURL(bundle).href)) as {\n default: (request: Request) => Promise<Response>\n installHostFn?: (fn: (name: string, ...args: unknown[]) => Promise<unknown>) => () => void\n installBackendForward?: (fn: ((request: Request) => Response | Promise<Response>) | null) => void\n }\n\n if (typeof entry.default !== 'function') {\n throw new Error(`[rsc-kit] ${bundle} does not export a request handler.`)\n }\n\n const host = options.host\n ? httpHostCalls({ endpoint: 'http://test-host/__rsc/host-call', secret: 'test', fetch: testHostFetch(options.host) })\n : null\n\n if (host && !entry.installHostFn) {\n throw new Error(`[rsc-kit] ${bundle} cannot take a host. Rebuild against the current @rsc-kit/core.`)\n }\n\n const fetch: TestApp['fetch'] = async (path, init) => {\n const request = new Request(new URL(path, origin), init)\n\n // Per request, not once: the loaded bundle is shared by every app a\n // run creates, and the host is this app's.\n if (host) entry.installHostFn!(host)\n if (options.backend && !entry.installBackendForward) {\n throw new Error(`[rsc-kit] ${bundle} cannot take a backend. Rebuild against the current @rsc-kit/core.`)\n }\n entry.installBackendForward?.(options.backend ?? null)\n\n return staticFile(root, request) ?? entry.default(request)\n }\n\n return {\n bundle,\n fetch,\n markup: async (path, init) => {\n const html = await (await fetch(path, init)).text()\n\n // A script's body never holds its own closing tag: the payload writes\n // \"<\" as \\u003c, and React's scripts are its own.\n return html.replace(/<script\\b[^>]*>[\\s\\S]*?<\\/script>/gi, '')\n },\n watched: async (path, init) => {\n // The render records what each region resolves to while this is set:\n // the app's bundle runs in this process, so it sees the same global.\n const recording: Record<string, string[]> = {}\n const into = globalThis as { [WATCHED]?: Record<string, string[]> }\n\n into[WATCHED] = recording\n\n try {\n // Read to the end: the names resolve under their own Suspense, so\n // they are in the stream's tail, not its head.\n await (await fetch(path, init)).text()\n } finally {\n delete into[WATCHED]\n }\n\n return recording\n },\n }\n}\n\n/** Where a render records what its regions refresh on, while watched() asks (js/refreshOn). */\nconst WATCHED = Symbol.for('rsc-kit.test-watched')\n"]}
|
|
1
|
+
{"version":3,"file":"testing.js","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"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","sourcesContent":["// The whole app as a function, for tests.\n//\n// import { createTestApp } from '@rsc-kit/core/testing'\n//\n// const app = await createTestApp()\n// const res = await app.fetch('/api/orders', { headers: { Cookie: 'user=ada' } })\n//\n// No port, no browser, no server process. The built entry already exports the\n// same Request → Response handler the dev server and the production server\n// both call, so a test can hand it a Request and read the Response — through\n// the real router, the real middleware, the real api routes, and the pages the\n// build stored.\n//\n// That is the tier between a unit test and a browser. An action or a route is\n// a function and can be imported and called; a browser test proves the page\n// works; this proves the app answers a url the way it is deployed, which is\n// where a route that renders fine and is served wrong shows up.\n\nimport { spawnSync } from 'node:child_process'\nimport { existsSync, readFileSync, readdirSync, statSync } from 'node:fs'\nimport { extname, join, resolve, sep } from 'node:path'\nimport { pathToFileURL } from 'node:url'\nimport { httpHostCalls } from './hostCalls.js'\nimport { configureChanged } from './changed.js'\nimport { testHostFetch, type TestHost } from './testHost.js'\n\nexport { hostReply, testChanges, HOST_MIDDLEWARE, type HostCallInput, type HostHandler, type TestHost } from './testHost.js'\n\nexport interface TestApp {\n /** A path, not a url. The origin is whatever the app was told it is. */\n fetch(path: string, init?: RequestInit): Promise<Response>\n /**\n * What each region of the page at `path` refreshes on, rendered for this\n * request: `{ page: ['ledger'], repos: ['team:1:repos'] }` - `page` for the\n * page's own `refreshOn`, a section's name for each section's. A region\n * whose refreshOn gave no names is there with `[]`; one with no refreshOn\n * is not there at all.\n *\n * expect(await app.watched('/teams/1')).toMatchObject({ repos: ['team:1:repos'] })\n *\n * The names a tab would be handed, without reading them out of the payload.\n */\n watched(path: string, init?: RequestInit): Promise<Record<string, string[]>>\n /**\n * The page at `path` as markup: the whole streamed document, with every\n * `<script>` taken out.\n *\n * The document carries the page's payload for the browser to hydrate from,\n * and the payload holds everything the page was given - a client\n * component's props included, shown or not. A test asserting that something\n * is NOT on the page reads this, not `fetch`:\n *\n * expect(await app.markup('/teams/1')).not.toContain('internal note')\n *\n * Regions that streamed in later are included where they arrived; a\n * Suspense fallback they replaced is still in the markup too, as the\n * server sent it.\n */\n markup(path: string, init?: RequestInit): Promise<string>\n /** Where the build was read from, for a test that wants to look. */\n bundle: string\n}\n\nexport interface TestAppOptions {\n /** The project. Defaults to the working directory. */\n root?: string\n /**\n * Whether to build first.\n *\n * `true` builds when the source is newer than the last build, which is what\n * a test run wants: the first run pays for it, the rest do not, and an edit\n * is picked up. `false` never builds and fails loudly if there is nothing to\n * read — for a ci step that already built.\n */\n build?: boolean\n /** The origin requests are made against. Nothing reads it; it is a url. */\n origin?: string\n /**\n * The backend, answered in the test: rpc() calls and host guards by name.\n * The app reaches it through the same client it uses against a real one,\n * so nothing listens and RSC_BACKEND is not called. See `hostReply`.\n */\n host?: TestHost\n /**\n * What answers a url this app does not own - a Go route, a Laravel page,\n * /login - in place of the backend the app forwards it to. Handed the\n * request as the backend would receive it. Without one, such a url is\n * forwarded to RSC_BACKEND, and in a test that is usually a 502.\n */\n backend?: (request: Request) => Response | Promise<Response>\n}\n\n/**\n * Where the build put the server bundle.\n *\n * Two layouts, because two ways of building. Under Nitro — every scaffolded\n * app — it is inside Nitro's own directory. On its own the plugin writes\n * <outDir>/dist/rsc. The first that exists wins.\n */\nfunction findBundle(root: string): string | null {\n const candidates = [\n join(root, 'node_modules/.nitro/vite/services/rsc/index.js'),\n // The default outDir since 0.29.8; .rsc before it.\n join(root, '.rsc-kit/dist/rsc/index.js'),\n join(root, '.rsc/dist/rsc/index.js'),\n join(root, 'build/dist/rsc/index.js'),\n ]\n\n return candidates.find((path) => existsSync(path)) ?? null\n}\n\n/**\n * When anything a build is made from last changed: the app's source, its\n * Vite config, its dependencies - package.json, the lockfile, and the\n * installed engine itself. Only src/ used to count, so an app that upgraded\n * @rsc-kit/core kept testing the build the old one made, and passed.\n */\nfunction newestInput(root: string): number {\n const files = [\n 'vite.config.ts', 'vite.config.mts', 'vite.config.js', 'vite.config.mjs',\n 'package.json', 'bun.lock', 'bun.lockb', 'package-lock.json', 'pnpm-lock.yaml', 'yarn.lock',\n ]\n let latest = newest(join(root, 'src'))\n\n for (const file of files) {\n const path = join(root, file)\n\n if (existsSync(path)) latest = Math.max(latest, statSync(path).mtimeMs)\n }\n\n // The engine as installed: a workspace link changes without the lockfile.\n const engine = join(root, 'node_modules/@rsc-kit/core/dist')\n\n return Math.max(latest, newest(engine))\n}\n\n/** The newest mtime under a directory, for deciding whether a build is stale. */\nfunction newest(dir: string): number {\n if (!existsSync(dir)) return 0\n\n let latest = 0\n\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n if (entry.name === 'node_modules' || entry.name.startsWith('.')) continue\n\n const path = join(dir, entry.name)\n const time = entry.isDirectory() ? newest(path) : statSync(path).mtimeMs\n\n if (time > latest) latest = time\n }\n\n return latest\n}\n\n/** One build per process per root, however many test files ask. */\nconst built = new Map<string, Promise<string>>()\n\n/** The content types a test is likely to assert on; anything else is octet-stream. */\nconst CONTENT_TYPES: Record<string, string> = {\n '.html': 'text/html; charset=utf-8',\n '.js': 'text/javascript; charset=utf-8',\n '.mjs': 'text/javascript; charset=utf-8',\n '.css': 'text/css; charset=utf-8',\n '.json': 'application/json; charset=utf-8',\n '.webmanifest': 'application/manifest+json',\n '.xml': 'application/xml',\n '.txt': 'text/plain; charset=utf-8',\n '.svg': 'image/svg+xml',\n '.png': 'image/png',\n '.jpg': 'image/jpeg',\n '.jpeg': 'image/jpeg',\n '.webp': 'image/webp',\n '.avif': 'image/avif',\n '.gif': 'image/gif',\n '.ico': 'image/x-icon',\n '.woff': 'font/woff',\n '.woff2': 'font/woff2',\n '.wasm': 'application/wasm',\n '.map': 'application/json',\n}\n\n/**\n * A file the build put in .output/public, answered the way Nitro's static\n * layer would - which the handler this harness loads does not include. The\n * handler is the rsc service: routing, pages, actions, api routes. Assets,\n * the service worker, the manifest and the icons are files Nitro serves in\n * production from .output/public, and a test that asked for /sw.js used to\n * get the router's 404 for a file the deployment serves fine - the one\n * shape this harness exists to catch, in the other direction.\n *\n * Files only, for GET and HEAD, within the directory: a path that escapes it\n * is a request for the handler, not a file.\n */\n/** @internal Exported for its test. */\nexport function staticFile(root: string, request: Request): Response | null {\n if (request.method !== 'GET' && request.method !== 'HEAD') return null\n\n const publicDir = join(root, '.output/public')\n const pathname = decodeURIComponent(new URL(request.url).pathname)\n const file = resolve(publicDir, '.' + pathname)\n\n if (!file.startsWith(publicDir + sep) || !existsSync(file) || !statSync(file).isFile()) return null\n\n const type = CONTENT_TYPES[extname(file).toLowerCase()] ?? 'application/octet-stream'\n const body = request.method === 'HEAD' ? null : readFileSync(file)\n\n return new Response(body, {\n status: 200,\n headers: { 'Content-Type': type, 'Cache-Control': 'no-store', 'X-RSC-Kit-Test': 'static' },\n })\n}\n\n/**\n * How this project builds: its own `build` script, run by the package manager\n * this test is running under. Without a script, Vite directly - on Bun's\n * runtime when the tests are, since a bin's node shebang would otherwise\n * start it under Node.\n */\n/** @internal Exported for its test. */\nexport function buildCommand(root: string): [string, string[]] {\n const onBun = typeof process.versions.bun === 'string'\n\n let scripts: Record<string, string> = {}\n\n try {\n scripts = JSON.parse(readFileSync(join(root, 'package.json'), 'utf-8')).scripts ?? {}\n } catch {\n // No package.json, or not JSON: there is no script to run.\n }\n\n if (typeof scripts.build === 'string') {\n return onBun ? ['bun', ['run', 'build']] : ['npm', ['run', 'build']]\n }\n\n return onBun ? ['bun', ['--bun', 'vite', 'build']] : ['npx', ['vite', 'build']]\n}\n\nasync function ensureBuilt(root: string, build: boolean): Promise<string> {\n const existing = findBundle(root)\n\n if (!build) {\n if (!existing) {\n throw new Error(\n `[rsc-kit] No build to test against under ${root}. Run the build first, or let createTestApp() do it by leaving \\`build\\` on.`,\n )\n }\n\n return existing\n }\n\n const fresh = existing && statSync(existing).mtimeMs > newestInput(root)\n\n if (fresh) return existing\n\n // The user's own build command, so what is tested is what ships. A test\n // that built some other way would pass against a bundle nobody deploys -\n // and used to: this ran `npx vite build` whatever package.json said, which\n // on a Bun project built under Node and failed on the first `import 'bun'`.\n const [command, args] = buildCommand(root)\n const run = spawnSync(command, args, {\n cwd: root,\n stdio: ['ignore', 'pipe', 'pipe'],\n env: { ...process.env, NODE_ENV: 'production' },\n })\n\n if (run.status !== 0) {\n throw new Error(`[rsc-kit] The build failed, so there is nothing to test:\\n${run.stderr}`)\n }\n\n const bundle = findBundle(root)\n\n if (!bundle) {\n throw new Error(\n `[rsc-kit] The build finished but no server bundle was found under ${root}. Is rscKit() in vite.config?`,\n )\n }\n\n return bundle\n}\n\n/**\n * Build the app if it needs it, load it, and hand back something to fetch from.\n *\n * Memoised per root: every test file in a run shares one build and one loaded\n * module, which is both the fast path and the correct one — two copies of the\n * server bundle in one process would be two client-reference registries.\n */\n/**\n * Whether Request is the runtime's, told apart from a browser's by behaviour.\n *\n * A browser forbids a script from setting Cookie, so happy-dom's Request drops\n * it from an init - and every signed-in request a test makes arrives with no\n * session, bounces to sign-in, and fails far from the cause. Behaviour rather\n * than identity, because identity depends on which file loaded first.\n */\nfunction runtimeRequest(): boolean {\n try {\n return new Request('http://probe.test/', { headers: { cookie: 'probe=1' } }).headers.has('cookie')\n } catch {\n return false\n }\n}\n\nexport async function createTestApp(options: TestAppOptions = {}): Promise<TestApp> {\n if (!runtimeRequest()) {\n throw new Error(\n \"[rsc-kit] createTestApp is running against a browser's Request: a DOM is registered globally \" +\n '(happy-dom, in a preload or an earlier test file), and it replaces Request, Response, Headers and fetch. ' +\n 'A browser cannot set Cookie, so every signed-in request would arrive with no session. ' +\n 'Register the DOM only in the component test file and `GlobalRegistrator.unregister()` after it, ' +\n 'and run the suites isolated (`bun test --isolate`). See the testing guide, \"Components\".',\n )\n }\n\n const root = resolve(options.root ?? process.cwd())\n const origin = options.origin ?? 'https://app.test'\n\n built.set(root, built.get(root) ?? ensureBuilt(root, options.build ?? true))\n\n // The app is built for production, which refuses to sign refreshOn names\n // without a key. One process, one key: a fixed one, unless the test set its own.\n if (!process.env.RSC_SIGNING_SECRET) {\n configureChanged({ secret: 'rsc-kit-test' })\n }\n\n const bundle = await built.get(root)!\n const entry = (await import(pathToFileURL(bundle).href)) as {\n default: (request: Request) => Promise<Response>\n installHostFn?: (fn: (name: string, ...args: unknown[]) => Promise<unknown>) => () => void\n installBackendForward?: (fn: ((request: Request) => Response | Promise<Response>) | null) => void\n }\n\n if (typeof entry.default !== 'function') {\n throw new Error(`[rsc-kit] ${bundle} does not export a request handler.`)\n }\n\n const host = options.host\n ? httpHostCalls({ endpoint: 'http://test-host/__rsc/host-call', secret: 'test', fetch: testHostFetch(options.host) })\n : null\n\n if (host && !entry.installHostFn) {\n throw new Error(`[rsc-kit] ${bundle} cannot take a host. Rebuild against the current @rsc-kit/core.`)\n }\n\n const fetch: TestApp['fetch'] = async (path, init) => {\n const request = new Request(new URL(path, origin), init)\n\n // Per request, not once: the loaded bundle is shared by every app a\n // run creates, and the host is this app's.\n if (host) entry.installHostFn!(host)\n if (options.backend && !entry.installBackendForward) {\n throw new Error(`[rsc-kit] ${bundle} cannot take a backend. Rebuild against the current @rsc-kit/core.`)\n }\n entry.installBackendForward?.(options.backend ?? null)\n\n return staticFile(root, request) ?? entry.default(request)\n }\n\n return {\n bundle,\n fetch,\n markup: async (path, init) => {\n const html = await (await fetch(path, init)).text()\n\n // A script's body never holds its own closing tag: the payload writes\n // \"<\" as \\u003c, and React's scripts are its own.\n return html.replace(/<script\\b[^>]*>[\\s\\S]*?<\\/script>/gi, '')\n },\n watched: async (path, init) => {\n // The render records what each region resolves to while this is set:\n // the app's bundle runs in this process, so it sees the same global.\n const recording: Record<string, string[]> = {}\n const into = globalThis as { [WATCHED]?: Record<string, string[]> }\n\n into[WATCHED] = recording\n\n try {\n // Read to the end: the names resolve under their own Suspense, so\n // they are in the stream's tail, not its head.\n await (await fetch(path, init)).text()\n } finally {\n delete into[WATCHED]\n }\n\n return recording\n },\n }\n}\n\n/** Where a render records what its regions refresh on, while watched() asks (js/refreshOn). */\nconst WATCHED = Symbol.for('rsc-kit.test-watched')\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rsc-kit/core",
|
|
3
|
-
"version": "0.30.
|
|
3
|
+
"version": "0.30.5",
|
|
4
4
|
"description": "React Server Components as a Vite plugin. Routing, streaming, server actions, typed urls and build-time prerendering — deploy to Bun, Node, Workers, Vercel or Deno.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|