@rsc-kit/core 0.29.5 → 0.29.7

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/typegen.d.ts CHANGED
@@ -9,3 +9,11 @@
9
9
  * build and cannot be stale - and every one is put back as it was.
10
10
  */
11
11
  export declare function typegen(root?: string, check?: boolean): Promise<string[]>;
12
+ /**
13
+ * What a build of the app in `root` will produce - `{ output: "server", ... }`
14
+ * or `{ output: "export", dir }` - read before building, from the app's own
15
+ * Vite config, evaluated the way a build evaluates it. A value computed in
16
+ * the config, or RSC_OUTPUT in the environment, resolves exactly as the build
17
+ * resolves it, because it is the same code deciding.
18
+ */
19
+ export declare function appInfo(root?: string): Promise<Record<string, unknown>>;
package/dist/typegen.js CHANGED
@@ -87,4 +87,38 @@ if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href || process.arg
87
87
  exit(1);
88
88
  });
89
89
  }
90
+ /**
91
+ * What a build of the app in `root` will produce - `{ output: "server", ... }`
92
+ * or `{ output: "export", dir }` - read before building, from the app's own
93
+ * Vite config, evaluated the way a build evaluates it. A value computed in
94
+ * the config, or RSC_OUTPUT in the environment, resolves exactly as the build
95
+ * resolves it, because it is the same code deciding.
96
+ */
97
+ export async function appInfo(root = cwd()) {
98
+ const local = createRequire(join(root, "package.json"));
99
+ let vitePath;
100
+ try {
101
+ vitePath = local.resolve("vite");
102
+ }
103
+ catch {
104
+ throw new Error("No vite installed in " + root + ". Run this from the app's directory.");
105
+ }
106
+ const vite = (await import(pathToFileURL(vitePath).href));
107
+ // From inside the app, as a build runs: rscKit() takes its project root
108
+ // from the working directory when the config creates it.
109
+ const from = cwd();
110
+ process.chdir(root);
111
+ try {
112
+ const loaded = await vite.loadConfigFromFile({ command: "build", mode: "production" }, undefined, root);
113
+ if (!loaded)
114
+ throw new Error("No vite.config found in " + root + ".");
115
+ const plugin = (await flatten(loaded.config.plugins ?? [])).find((p) => p.name === "rsc-kit");
116
+ if (!plugin?.api?.info)
117
+ throw new Error("The vite.config in " + root + " does not use rscKit().");
118
+ return plugin.api.info();
119
+ }
120
+ finally {
121
+ process.chdir(from);
122
+ }
123
+ }
90
124
  //# sourceMappingURL=typegen.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"typegen.js","sourceRoot":"","sources":["../src/typegen.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\n// rsc-kit-typegen: write the route types without starting Vite.\n//\n// The types - every url the app has, for <Link href> and redirect() - are\n// written when the dev server or a build starts, and again when a dev server\n// sees a route added. A typecheck run with neither, by an agent or in CI,\n// read the types from the last start: a route added since failed with\n// '\"/admin\"' is not assignable to type Route until something started Vite.\n//\n// The app's own vite.config is loaded, through the app's own Vite, so the\n// plugin's options - sourceDir, outDir, a host's route file - are the ones it\n// builds with. Only the plugin's config step runs, which is what writes them.\n\nimport { existsSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { join, relative } from \"node:path\";\nimport { cwd, exit, stderr, stdout } from \"node:process\";\nimport { pathToFileURL } from \"node:url\";\n\ninterface PluginLike {\n name?: string;\n config?: (config: object, env: { command: string; mode: string }) => unknown;\n api?: { generatedFiles?: () => string[] };\n}\n\nasync function flatten(option: unknown): Promise<PluginLike[]> {\n const value = await option;\n\n if (Array.isArray(value)) return (await Promise.all(value.map(flatten))).flat();\n\n return value && typeof value === \"object\" ? [value as PluginLike] : [];\n}\n\n/**\n * Write the types; or, with `check`, say which committed generated files are\n * not what the backend and the route tree produce now, and change nothing.\n *\n * For CI: `rsc-kit-typegen --check` fails when rsc-host.json or the types were\n * committed and the backend has moved on since. Only files that were already\n * there are compared - one that is not committed is written fresh by every\n * build and cannot be stale - and every one is put back as it was.\n */\nexport async function typegen(root = cwd(), check = false): Promise<string[]> {\n const local = createRequire(join(root, \"package.json\"));\n let vitePath: string;\n\n try {\n vitePath = local.resolve(\"vite\");\n } catch {\n throw new Error(\"No vite installed in \" + root + \". Run this from the app's directory.\");\n }\n\n const vite = (await import(pathToFileURL(vitePath).href)) as {\n loadConfigFromFile: (\n env: { command: string; mode: string },\n file?: string,\n root?: string,\n ) => Promise<{ config: { plugins?: unknown[] } } | null>;\n };\n\n // As a build would: a manifest command that fails is a failure, not a\n // warning, when the question is whether what is committed is current.\n const env = check ? { command: \"build\", mode: \"production\" } : { command: \"serve\", mode: \"development\" };\n const loaded = await vite.loadConfigFromFile(env, undefined, root);\n\n if (!loaded) throw new Error(\"No vite.config found in \" + root + \".\");\n\n const plugin = (await flatten(loaded.config.plugins ?? [])).find((p) => p.name === \"rsc-kit\");\n\n if (!plugin?.config) throw new Error(\"The vite.config in \" + root + \" does not use rscKit().\");\n\n if (!check) {\n await plugin.config({}, env);\n\n return [];\n }\n\n const files = plugin.api?.generatedFiles?.() ?? [];\n const before = new Map(files.filter((f) => existsSync(f)).map((f) => [f, readFileSync(f, \"utf-8\")]));\n\n try {\n await plugin.config({}, env);\n\n return [...before].filter(([f, text]) => !existsSync(f) || readFileSync(f, \"utf-8\") !== text).map(([f]) => f);\n } finally {\n for (const [f, text] of before) writeFileSync(f, text);\n }\n}\n\n// Run as a command, not when imported.\nif (import.meta.url === pathToFileURL(process.argv[1] ?? \"\").href || process.argv[1]?.endsWith(\"rsc-kit-typegen\")) {\n const check = process.argv.includes(\"--check\");\n\n typegen(cwd(), check)\n .then((stale) => {\n if (!check) {\n stdout.write(\"rsc-kit: route types written\\n\");\n\n return;\n }\n\n if (stale.length === 0) {\n stdout.write(\"rsc-kit: generated files are current\\n\");\n\n return;\n }\n\n stderr.write(\n \"rsc-kit-typegen --check: these are committed and no longer what the backend and the routes produce:\\n\" +\n stale.map((f) => \" \" + relative(cwd(), f)).join(\"\\n\") +\n \"\\nRun rsc-kit-typegen and commit the result.\\n\",\n );\n exit(1);\n })\n .catch((error: unknown) => {\n stderr.write(\"rsc-kit-typegen: \" + (error instanceof Error ? error.message : String(error)) + \"\\n\");\n exit(1);\n });\n}\n"]}
1
+ {"version":3,"file":"typegen.js","sourceRoot":"","sources":["../src/typegen.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\n// rsc-kit-typegen: write the route types without starting Vite.\n//\n// The types - every url the app has, for <Link href> and redirect() - are\n// written when the dev server or a build starts, and again when a dev server\n// sees a route added. A typecheck run with neither, by an agent or in CI,\n// read the types from the last start: a route added since failed with\n// '\"/admin\"' is not assignable to type Route until something started Vite.\n//\n// The app's own vite.config is loaded, through the app's own Vite, so the\n// plugin's options - sourceDir, outDir, a host's route file - are the ones it\n// builds with. Only the plugin's config step runs, which is what writes them.\n\nimport { existsSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { join, relative } from \"node:path\";\nimport { cwd, exit, stderr, stdout } from \"node:process\";\nimport { pathToFileURL } from \"node:url\";\n\ninterface PluginLike {\n name?: string;\n config?: (config: object, env: { command: string; mode: string }) => unknown;\n api?: { generatedFiles?: () => string[]; info?: () => Record<string, unknown> };\n}\n\nasync function flatten(option: unknown): Promise<PluginLike[]> {\n const value = await option;\n\n if (Array.isArray(value)) return (await Promise.all(value.map(flatten))).flat();\n\n return value && typeof value === \"object\" ? [value as PluginLike] : [];\n}\n\n/**\n * Write the types; or, with `check`, say which committed generated files are\n * not what the backend and the route tree produce now, and change nothing.\n *\n * For CI: `rsc-kit-typegen --check` fails when rsc-host.json or the types were\n * committed and the backend has moved on since. Only files that were already\n * there are compared - one that is not committed is written fresh by every\n * build and cannot be stale - and every one is put back as it was.\n */\nexport async function typegen(root = cwd(), check = false): Promise<string[]> {\n const local = createRequire(join(root, \"package.json\"));\n let vitePath: string;\n\n try {\n vitePath = local.resolve(\"vite\");\n } catch {\n throw new Error(\"No vite installed in \" + root + \". Run this from the app's directory.\");\n }\n\n const vite = (await import(pathToFileURL(vitePath).href)) as {\n loadConfigFromFile: (\n env: { command: string; mode: string },\n file?: string,\n root?: string,\n ) => Promise<{ config: { plugins?: unknown[] } } | null>;\n };\n\n // As a build would: a manifest command that fails is a failure, not a\n // warning, when the question is whether what is committed is current.\n const env = check ? { command: \"build\", mode: \"production\" } : { command: \"serve\", mode: \"development\" };\n const loaded = await vite.loadConfigFromFile(env, undefined, root);\n\n if (!loaded) throw new Error(\"No vite.config found in \" + root + \".\");\n\n const plugin = (await flatten(loaded.config.plugins ?? [])).find((p) => p.name === \"rsc-kit\");\n\n if (!plugin?.config) throw new Error(\"The vite.config in \" + root + \" does not use rscKit().\");\n\n if (!check) {\n await plugin.config({}, env);\n\n return [];\n }\n\n const files = plugin.api?.generatedFiles?.() ?? [];\n const before = new Map(files.filter((f) => existsSync(f)).map((f) => [f, readFileSync(f, \"utf-8\")]));\n\n try {\n await plugin.config({}, env);\n\n return [...before].filter(([f, text]) => !existsSync(f) || readFileSync(f, \"utf-8\") !== text).map(([f]) => f);\n } finally {\n for (const [f, text] of before) writeFileSync(f, text);\n }\n}\n\n// Run as a command, not when imported.\nif (import.meta.url === pathToFileURL(process.argv[1] ?? \"\").href || process.argv[1]?.endsWith(\"rsc-kit-typegen\")) {\n const check = process.argv.includes(\"--check\");\n\n typegen(cwd(), check)\n .then((stale) => {\n if (!check) {\n stdout.write(\"rsc-kit: route types written\\n\");\n\n return;\n }\n\n if (stale.length === 0) {\n stdout.write(\"rsc-kit: generated files are current\\n\");\n\n return;\n }\n\n stderr.write(\n \"rsc-kit-typegen --check: these are committed and no longer what the backend and the routes produce:\\n\" +\n stale.map((f) => \" \" + relative(cwd(), f)).join(\"\\n\") +\n \"\\nRun rsc-kit-typegen and commit the result.\\n\",\n );\n exit(1);\n })\n .catch((error: unknown) => {\n stderr.write(\"rsc-kit-typegen: \" + (error instanceof Error ? error.message : String(error)) + \"\\n\");\n exit(1);\n });\n}\n\n/**\n * What a build of the app in `root` will produce - `{ output: \"server\", ... }`\n * or `{ output: \"export\", dir }` - read before building, from the app's own\n * Vite config, evaluated the way a build evaluates it. A value computed in\n * the config, or RSC_OUTPUT in the environment, resolves exactly as the build\n * resolves it, because it is the same code deciding.\n */\nexport async function appInfo(root = cwd()): Promise<Record<string, unknown>> {\n const local = createRequire(join(root, \"package.json\"));\n let vitePath: string;\n\n try {\n vitePath = local.resolve(\"vite\");\n } catch {\n throw new Error(\"No vite installed in \" + root + \". Run this from the app's directory.\");\n }\n\n const vite = (await import(pathToFileURL(vitePath).href)) as {\n loadConfigFromFile: (\n env: { command: string; mode: string },\n file?: string,\n root?: string,\n ) => Promise<{ config: { plugins?: unknown[] } } | null>;\n };\n // From inside the app, as a build runs: rscKit() takes its project root\n // from the working directory when the config creates it.\n const from = cwd();\n\n process.chdir(root);\n\n try {\n const loaded = await vite.loadConfigFromFile({ command: \"build\", mode: \"production\" }, undefined, root);\n\n if (!loaded) throw new Error(\"No vite.config found in \" + root + \".\");\n\n const plugin = (await flatten(loaded.config.plugins ?? [])).find((p) => p.name === \"rsc-kit\");\n\n if (!plugin?.api?.info) throw new Error(\"The vite.config in \" + root + \" does not use rscKit().\");\n\n return plugin.api.info();\n } finally {\n process.chdir(from);\n }\n}\n\n"]}
package/dist/vite.d.ts CHANGED
@@ -405,6 +405,31 @@ export interface KnownAction {
405
405
  * its own - is its character code, so no two paths share an alias.
406
406
  */
407
407
  export declare function toAlias(name: string): string;
408
+ /**
409
+ * Which of the two metadata exports a module actually has.
410
+ *
411
+ * Both are read separately because the generated entry names each one it
412
+ * mentions, and naming an export that is not there is a bundler warning on
413
+ * every build — `Import 'generateMetadata' will always be undefined`. Most
414
+ * pages export only the static object, so referencing both meant that warning
415
+ * for almost every route in an app.
416
+ */
417
+ /**
418
+ * The root layout's metadataBase, read from the source. Wanted while the
419
+ * manifest is built, before there is a bundle to execute - the same reason
420
+ * generateStaticParams is detected by reading. A `new URL('…')` or a string
421
+ * literal; anything computed is not seen, and rscKit({ hosts }) says it.
422
+ */
423
+ /**
424
+ * The app's single-binary step, as its package.json declares it: the script
425
+ * named compile, and the file its --outfile writes. Read from the app's own
426
+ * script, so a deploy runs what the app runs and never assumes a path. None
427
+ * when the app has no such script - a server it starts with node or bun.
428
+ */
429
+ export declare function compileStep(root: string): {
430
+ compile?: string;
431
+ binary?: string;
432
+ };
408
433
  /**
409
434
  * How the generated entry imports instrumentation.ts, as `__instrumentation`.
410
435
  *
package/dist/vite.js CHANGED
@@ -2565,6 +2565,25 @@ function discover(dir) {
2565
2565
  * generateStaticParams is detected by reading. A `new URL('…')` or a string
2566
2566
  * literal; anything computed is not seen, and rscKit({ hosts }) says it.
2567
2567
  */
2568
+ /**
2569
+ * The app's single-binary step, as its package.json declares it: the script
2570
+ * named compile, and the file its --outfile writes. Read from the app's own
2571
+ * script, so a deploy runs what the app runs and never assumes a path. None
2572
+ * when the app has no such script - a server it starts with node or bun.
2573
+ */
2574
+ export function compileStep(root) {
2575
+ try {
2576
+ const pkg = JSON.parse(readFileSync(join(root, "package.json"), "utf-8"));
2577
+ const script = pkg.scripts?.compile;
2578
+ if (!script)
2579
+ return {};
2580
+ const outfile = /--outfile[= ]+("[^"]+"|'[^']+'|\S+)/.exec(script)?.[1]?.replace(/^["']|["']$/g, "");
2581
+ return outfile ? { compile: "compile", binary: outfile } : { compile: "compile" };
2582
+ }
2583
+ catch {
2584
+ return {};
2585
+ }
2586
+ }
2568
2587
  /** The root layout's metadataBase as the loaded app has it, whatever it was computed from. */
2569
2588
  async function loadedMetadataBase(engine, manifest) {
2570
2589
  const resolve = engine
@@ -2659,7 +2678,19 @@ function urlSchemaExports(absPath) {
2659
2678
  * generated is cheaper than something generated that returns early, and it
2660
2679
  * keeps every backend-shaped idea out of a runtime that has no backend.
2661
2680
  */
2662
- const FALLBACK_CONSTS = `const FALLBACK_ORIGIN = __ORIGIN__
2681
+ const FALLBACK_CONSTS = `const FALLBACK_DEFAULT = __ORIGIN__
2682
+
2683
+ // Where a url nothing here owns is handed on, read per request from the
2684
+ // environment this server runs in - RSC_BACKEND, or APP_URL beside a
2685
+ // host-call secret, exactly as host calls find their backend - so a build
2686
+ // carries no address and one binary runs against whichever backend its
2687
+ // deployment names. FALLBACK_DEFAULT is the development server's, or an
2688
+ // explicit rscKit({ devFallback }); a build never fills it from its own env.
2689
+ function fallbackOrigin(): string {
2690
+ const env = (typeof process === 'undefined' ? {} : process.env) as Record<string, string | undefined>
2691
+
2692
+ return env.RSC_BACKEND || (env.RSC_HOST_CALL_SECRET ? env.APP_URL : '') || FALLBACK_DEFAULT
2693
+ }
2663
2694
  const FALLBACK_MARKER = 'x-rsc-renderer-fallback'
2664
2695
  const PROXIED_MARKER = 'x-rsc-proxied-by-backend'
2665
2696
  `;
@@ -2672,7 +2703,8 @@ const NO_FALLBACK_BODY = ` const answer = await devHandler(request)
2672
2703
 
2673
2704
  return await notFound(request)
2674
2705
  `;
2675
- const FALLBACK_BODY = ` const answer = await devHandler(request)
2706
+ const FALLBACK_BODY = ` const FALLBACK_ORIGIN = fallbackOrigin()
2707
+ const answer = await devHandler(request)
2676
2708
 
2677
2709
  if (answer) return answer
2678
2710
 
@@ -2694,7 +2726,15 @@ const FALLBACK_BODY = ` const answer = await devHandler(request)
2694
2726
  // Came from the backend's own proxy, so it has already been through that
2695
2727
  // route table and the answer there was no. Sending it back asks the same
2696
2728
  // question a second time.
2697
- if (!FALLBACK_ORIGIN || request.headers.has(PROXIED_MARKER)) {
2729
+ // No backend named where this server runs: this app's own not-found, as
2730
+ // an app without a backend answers - or a test's stand-in backend.
2731
+ if (!FALLBACK_ORIGIN) {
2732
+ if (backendForward) return await backendForward(request)
2733
+
2734
+ return await notFound(request)
2735
+ }
2736
+
2737
+ if (request.headers.has(PROXIED_MARKER)) {
2698
2738
  return new Response('Not found', { status: 404 })
2699
2739
  }
2700
2740
 
@@ -3449,6 +3489,40 @@ export function installBackendForward(fn: ((request: Request) => Response | Prom
3449
3489
  backendForward = fn
3450
3490
  }
3451
3491
 
3492
+ /**
3493
+ * Whether the backend answers, for /_rsc/health: null when there is no
3494
+ * backend to ask, "ok" when it answered, and otherwise why not.
3495
+ *
3496
+ * Asks the route guards with none to run - every adapter passes an empty
3497
+ * list, so the call runs no app code and touches no data, yet it goes all
3498
+ * the way: the address, the network, the secret, the adapter. A port that is
3499
+ * open but answering with the wrong secret is not ready, and this says so.
3500
+ */
3501
+ export async function checkBackend(timeoutMs = 2000): Promise<string | null> {
3502
+ if (typeof installHostCallsOnce === 'function') installHostCallsOnce()
3503
+
3504
+ const host = currentHost
3505
+
3506
+ if (!host) return null
3507
+
3508
+ let timer: ReturnType<typeof setTimeout> | undefined
3509
+
3510
+ try {
3511
+ const answer = await Promise.race([
3512
+ host('__rsc.middleware', []),
3513
+ new Promise((_, reject) => {
3514
+ timer = setTimeout(() => reject(new Error('no answer within ' + timeoutMs + 'ms')), timeoutMs)
3515
+ }),
3516
+ ])
3517
+
3518
+ return answer === true ? 'ok' : 'answered ' + JSON.stringify(answer) + ' to an empty guard check'
3519
+ } catch (error) {
3520
+ return error instanceof Error ? error.message : String(error)
3521
+ } finally {
3522
+ clearTimeout(timer)
3523
+ }
3524
+ }
3525
+
3452
3526
  export function installHostFn(fn: HostFn) {
3453
3527
  currentHost = fn
3454
3528
  // The host keeps the versions a page refreshes on, read through it.
@@ -5445,7 +5519,7 @@ export async function handleRscPprShell(
5445
5519
  * server rendering live pages it had already frozen. The gate is the mode, not
5446
5520
  * the file.
5447
5521
  */
5448
- ${fallbackOrigin ? FALLBACK_CONSTS.replace("__ORIGIN__", JSON.stringify(fallbackOrigin)) : ""}${NITRO_HOST_CALLS}
5522
+ ${fallbackOrigin === false ? "" : FALLBACK_CONSTS.replace("__ORIGIN__", JSON.stringify(fallbackOrigin))}${NITRO_HOST_CALLS}
5449
5523
  let devHandler: ((request: Request) => Promise<Response | null>) | null = null
5450
5524
 
5451
5525
  export default async function handler(request: Request): Promise<Response> {
@@ -5479,6 +5553,7 @@ async function serve(request: Request): Promise<Response> {
5479
5553
  manifest,
5480
5554
  getStaticParams,
5481
5555
  installHostFn,
5556
+ checkBackend,
5482
5557
  handleRsc,
5483
5558
  handleRscStream,
5484
5559
  handleRscHtmlStream,
@@ -5498,7 +5573,7 @@ async function serve(request: Request): Promise<Response> {
5498
5573
  ${NITRO_HANDLER_OPTIONS}${NITRO_PRERENDERED} maxActionBody: ${maxActionBody === undefined ? "undefined" : String(maxActionBody)},
5499
5574
  })
5500
5575
 
5501
- ${fallbackOrigin ? FALLBACK_BODY : NO_FALLBACK_BODY}}
5576
+ ${fallbackOrigin === false ? NO_FALLBACK_BODY : FALLBACK_BODY}}
5502
5577
 
5503
5578
  /**
5504
5579
  * The page for a url nothing answers.
@@ -6656,6 +6731,17 @@ export function rscKit(options = {}) {
6656
6731
  // For rsc-kit-typegen --check: every file the config step writes that an
6657
6732
  // app might commit, so a stale one can be named.
6658
6733
  api: {
6734
+ /**
6735
+ * What a build of this app will produce, known before building: for a
6736
+ * deploy that has to plan - a binary or a folder, which script to run -
6737
+ * before anything exists. `rsc-kit info --json` prints it; the build
6738
+ * writes the same thing to .output/rsc-kit.json as it finishes.
6739
+ */
6740
+ info() {
6741
+ return output === "export"
6742
+ ? { version: PACKAGE_VERSION, output: "export", dir: relative(projectRoot, exportPath) }
6743
+ : { version: PACKAGE_VERSION, output: "server", ...compileStep(projectRoot) };
6744
+ },
6659
6745
  generatedFiles() {
6660
6746
  return [
6661
6747
  join(projectRoot, HOST_FILE),
@@ -6852,7 +6938,22 @@ export function rscKit(options = {}) {
6852
6938
  backendEnv.APP_URL ??
6853
6939
  "")
6854
6940
  : "";
6855
- const fallbackOrigin = options.devFallback === false ? "" : (options.devFallback ?? detected);
6941
+ // false turns forwarding off. A string from the config is fixed; what
6942
+ // the env says is only the dev server's - a build reads its backend at
6943
+ // runtime and bakes nothing in, so a binary is not tied to the machine
6944
+ // that built it.
6945
+ // Forwarding is for an app with a backend, and whether it has one is
6946
+ // in its own files - rsc-host.json, a hostManifest, a hostCall - not in
6947
+ // whatever environment it happened to be built in. A JavaScript-only
6948
+ // app gets no forwarding code at all; it is the backend.
6949
+ const declaresBackend = Boolean(options.hostManifest || options.hostCall || existsSync(join(projectRoot, HOST_FILE)));
6950
+ const fallbackOrigin = options.devFallback === false
6951
+ ? false
6952
+ : typeof options.devFallback === "string"
6953
+ ? options.devFallback
6954
+ : env.command === "build"
6955
+ ? (declaresBackend ? "" : false)
6956
+ : (detected || (declaresBackend ? "" : false));
6856
6957
  writeGenerated(join(genDir, "entry.rsc.tsx"), generateEntryRsc(fallbackOrigin));
6857
6958
  writeGenerated(join(genDir, "entry.ssr.tsx"), generateEntrySsr());
6858
6959
  writeGenerated(join(genDir, "entry.browser.tsx"), generateEntryBrowser());
@@ -7348,6 +7449,15 @@ export function rscKit(options = {}) {
7348
7449
  // after the prerender, and an export taken any earlier left them out.
7349
7450
  if (output === "export")
7350
7451
  await exportAfterPrerender(results, staticDir, clientOut ?? publicAssetsDir);
7452
+ // What this build produced, where a deploy can read it without parsing
7453
+ // vite.config.ts - which can turn export on through RSC_OUTPUT, or
7454
+ // compute it - or knowing where rsc-kit keeps things inside .output.
7455
+ // Written by the code that decided, so it cannot disagree with it.
7456
+ if (clientOut) {
7457
+ writeFileSync(join(dirname(clientOut), "rsc-kit.json"), JSON.stringify(output === "export"
7458
+ ? { version: PACKAGE_VERSION, output: "export", dir: relative(projectRoot, exportPath) }
7459
+ : { version: PACKAGE_VERSION, output: "server", ...compileStep(projectRoot) }, null, 2) + "\n");
7460
+ }
7351
7461
  },
7352
7462
  configResolved(config) {
7353
7463
  // A build that is not a production build cannot work here, and the