@rsc-kit/core 0.30.34 → 0.30.36

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.
@@ -78,6 +78,8 @@ export interface CommandEntryOptions {
78
78
  commands: readonly CommandFile[];
79
79
  /** Polyfills the app's graph has (`reflect-metadata`), by package name. */
80
80
  polyfills: readonly string[];
81
+ /** The mode the build baked in. See nodeEnvSource. */
82
+ nodeEnv?: 'development' | 'production';
81
83
  }
82
84
  /**
83
85
  * What the entries can answer without loading the app: `--help`, no command,
@@ -141,6 +141,14 @@ export function runCommand(argv: string[], program?: string) {
141
141
  }
142
142
  `;
143
143
  }
144
+ /**
145
+ * A command is not started through Nitro, so the plugin that gives the server
146
+ * process its NODE_ENV does not run for it (see nodeEnvPlugin in vite.ts).
147
+ * Before the app is imported, and never over a value someone set.
148
+ */
149
+ function nodeEnvSource(mode = 'production') {
150
+ return `if (!process.env["NODE_ENV"]) process.env["NODE_ENV"] = ${JSON.stringify(mode)};\n`;
151
+ }
144
152
  /**
145
153
  * What the entries can answer without loading the app: `--help`, no command,
146
154
  * a name that is not one.
@@ -240,6 +248,8 @@ export function commandsEntrySource(options) {
240
248
  'import { relative } from "node:path";\n' +
241
249
  'import { fileURLToPath } from "node:url";\n' +
242
250
  '\n' +
251
+ nodeEnvSource(options.nodeEnv) +
252
+ '\n' +
243
253
  commandAnswerSource(options) +
244
254
  '\n' +
245
255
  'const argv = process.argv.slice(2);\n' +
@@ -309,6 +319,8 @@ export function compileEntryWithCommands(dirName, options) {
309
319
  'import { basename } from "node:path";\n' +
310
320
  `import "./${inlineModuleName(dirName)}";\n` +
311
321
  '\n' +
322
+ nodeEnvSource(options.nodeEnv) +
323
+ '\n' +
312
324
  commandAnswerSource(options) +
313
325
  '\n' +
314
326
  'const failed = (what) => (error) => {\n' +
@@ -1 +1 @@
1
- {"version":3,"file":"commandFiles.js","sourceRoot":"","sources":["../src/commandFiles.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The build's half of app commands: which files in `src/commands` are\n * commands, what the generated entry says about them, and the small entries\n * that run one. The runner they call is `commands.ts`.\n */\n\nimport { existsSync, readdirSync, readFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { commandHelp, EARLY_SIGNALS } from './commands.js'\nimport { inlineModuleName } from './files.js'\n\nexport interface CommandFile {\n /** What is typed after the binary: the file's name without its extension. */\n name: string\n /** Absolute path, imported lazily by the entry. */\n file: string\n /** A plain string literal exported as `description`, for `--help`. */\n description?: string\n /**\n * `export const register = true`: runs from the server's bundle, which\n * imports instrumentation.ts. Every other command runs from one that does\n * not - see BARE_COMMANDS_MODULE_ID.\n */\n instrumented?: boolean\n}\n\nexport interface CommandFiles {\n commands: CommandFile[]\n /** Paths under src/commands that are not commands, and why, for the build to say. */\n ignored: { path: string; reason: string }[]\n}\n\nconst SOURCE = /\\.(?:[cm]?[jt]sx?)$/\nconst NAME = /^[A-Za-z0-9][A-Za-z0-9_.:-]*$/\n\n/** Earlier wins when two files share a name: migrate.ts over migrate.js. */\nconst EXTENSION_ORDER = ['ts', 'mts', 'cts', 'tsx', 'js', 'mjs', 'cjs', 'jsx']\n\n/**\n * `export const description = 'Apply the database migrations'`, read off the\n * source so `--help` can list a command without loading it - a command's\n * imports are the database and the logger, and listing should not open either.\n * A plain string literal only; anything computed is simply not listed.\n */\nfunction describe(source: string): string | undefined {\n const match = /^\\s*export\\s+const\\s+description\\s*(?::\\s*string\\s*)?=\\s*([\"'`])([^\"'`\\\\\\n$]*)\\1/m.exec(source)\n\n return match?.[2]?.trim() || undefined\n}\n\n/**\n * `export const register = true`, read off the source as `description` is:\n * which bundle a command runs from is decided before anything loads, and only\n * one that asked for register() has a reason to load instrumentation.ts.\n */\nfunction registers(source: string): boolean {\n return /^\\s*export\\s+const\\s+register\\s*(?::\\s*boolean\\s*)?=\\s*true\\b/m.test(source)\n}\n\n/**\n * The commands under `<sourceDir>/commands`.\n *\n * Flat on purpose: a name is what a person types, and `db/seed.ts` raises\n * whether that is `db:seed`, `db/seed` or `seed`. Declaration files, tests and\n * anything starting with `_` or `.` are not commands - the last is how a\n * helper lives beside the commands that share it.\n */\nexport function commandFiles(sourceDir: string): CommandFiles {\n const dir = join(sourceDir, 'commands')\n const found: CommandFiles = { commands: [], ignored: [] }\n\n if (!existsSync(dir)) return found\n\n const taken = new Map<string, CommandFile>()\n const entries = readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))\n\n for (const entry of entries) {\n const path = join(dir, entry.name)\n\n if (entry.name.startsWith('_') || entry.name.startsWith('.')) continue\n\n if (entry.isDirectory()) {\n found.ignored.push({ path, reason: 'commands are the files directly in src/commands, not a folder of them' })\n\n continue\n }\n\n if (!SOURCE.test(entry.name) || /\\.d\\.[cm]?ts$/.test(entry.name) || /\\.(?:test|spec)\\.[cm]?[jt]sx?$/.test(entry.name)) continue\n\n const extension = entry.name.slice(entry.name.lastIndexOf('.') + 1)\n const name = entry.name.slice(0, -(extension.length + 1))\n\n if (!NAME.test(name)) {\n found.ignored.push({ path, reason: `\"${name}\" cannot be typed as a command: letters, digits, \"-\", \"_\", \".\" and \":\"` })\n\n continue\n }\n\n const existing = taken.get(name)\n\n if (existing) {\n const keep = EXTENSION_ORDER.indexOf(existing.file.split('.').pop() ?? '') <= EXTENSION_ORDER.indexOf(extension)\n\n found.ignored.push({\n path: keep ? path : existing.file,\n reason: `two files are called \"${name}\"; ${keep ? existing.file : path} is the command`,\n })\n\n if (keep) continue\n\n found.commands.splice(found.commands.indexOf(existing), 1)\n }\n\n const source = readFileSync(path, 'utf-8')\n const command: CommandFile = { name, file: path, description: describe(source) }\n\n if (registers(source)) command.instrumented = true\n\n taken.set(name, command)\n found.commands.push(command)\n }\n\n return found\n}\n\n/** The module the generated entry reads the table from, resolved when the bundle is built. */\nexport const COMMANDS_MODULE_ID = 'virtual:rsc-kit/commands'\n\n/**\n * The table: every command as a dynamic import, so each is a chunk of its own\n * and the server never evaluates one. An empty object when there are none -\n * a Worker's build, say, which does not carry them.\n *\n * A module of its own rather than text in the entry because the entry is\n * written while the config is still being read, before anything knows the\n * build's mode or which Nitro preset it is for.\n */\nexport function commandsModule(commands: readonly CommandFile[]): string {\n const rows = commands.map(\n ({ name, file, description }) =>\n ` ${JSON.stringify(name)}: { load: () => import(${JSON.stringify(file)})${description ? `, description: ${JSON.stringify(description)}` : ''} },`,\n )\n\n return `export const commands = {\\n${rows.join('\\n')}\\n}\\n`\n}\n\n/**\n * The commands that did not ask for register(), as a bundle of their own.\n *\n * The server's entry imports instrumentation.ts first and statically - that is\n * what stops a server with a bad environment before it serves - so anything\n * loaded through it has validated the web's whole schema. A queue drain that\n * imports only the worker's still needed the web's Stripe and session\n * secrets. This module is the entry of a server environment of its own,\n * bundled apart from the server's, so nothing in it can import back into the\n * entry that loads instrumentation.ts. Each command's own imports still run:\n * it validates what it imports, which is the point.\n */\nexport const BARE_COMMANDS_MODULE_ID = 'virtual:rsc-kit/commands-bare'\n\n/**\n * Its name as an input of the rsc environment, and so the file the server\n * runs it from: `_ssr/rsc-commands.mjs`, beside the server's `_ssr/rsc.mjs`.\n */\nexport const BARE_COMMANDS_ENVIRONMENT = 'rsc-commands'\n\n/** The bare table's module: runCommand over it, with no register() to give. */\nexport function bareCommandsModule(commands: readonly CommandFile[], runner: string): string {\n return (\n `import { runCommand as __runCommand } from ${JSON.stringify(runner)}\\n` +\n commandsModule(commands.filter((command) => !command.instrumented)) +\n // Reached only by a register that is true at runtime but not as a literal\n // the build could read, which put the command in the wrong bundle.\n 'const noRegister = () => Promise.reject(new Error(\"register() runs for a command that says export const register = true, as a literal: this one computes it, so it was built without instrumentation.ts.\"))\\n' +\n 'export function runCommand(argv, program) {\\n' +\n ' return __runCommand(argv, { commands, program, ready: noRegister })\\n' +\n '}\\n'\n )\n}\n\n/**\n * What the generated server entry adds for an app with a `src/commands`:\n * `runCommand` over the table, with the entry's own `register()` and\n * `shutdown()`. `runner` is the engine's `commands` module, named without an\n * extension like every other engine module the entry imports.\n */\nexport function commandsEntry(runner: string): string {\n return `\nimport { commands as __commands } from ${JSON.stringify(COMMANDS_MODULE_ID)}\nimport { runCommand as __runCommand } from ${JSON.stringify(runner)}\n\n// src/commands, each loaded only when it is named.\nexport function runCommand(argv: string[], program?: string) {\n return __runCommand(argv, { commands: __commands, program, ready: instrumented, shutdown: __instrumentation.shutdown })\n}\n`\n}\n\n/** What the entries need to know that only the build does. */\nexport interface CommandEntryOptions {\n commands: readonly CommandFile[]\n /** Polyfills the app's graph has (`reflect-metadata`), by package name. */\n polyfills: readonly string[]\n}\n\n/**\n * What the entries can answer without loading the app: `--help`, no command,\n * a name that is not one.\n *\n * Loading the app imports instrumentation.ts, and so validates the\n * environment - and `./app --help` on a build machine, or in a CI step that\n * has no database url, is not a reason to refuse. The names and descriptions\n * are in the entry itself because the binary carries nothing else: a\n * Dockerfile copies `dist/app` and `.output/rsc-kit.json` is not in it.\n *\n * The help text is written by the same function the runner uses, with the\n * program's name left to fill in, so the two cannot word it differently.\n * `answer` is the whole of the logic; the runner repeats it as a backstop.\n */\nexport function commandAnswerSource({ commands }: CommandEntryOptions): string {\n const table = Object.fromEntries(commands.map(({ name, description }) => [name, { description }]))\n const help = commandHelp(table, '%PROGRAM%')\n\n return (\n `const COMMAND_NAMES = ${JSON.stringify(commands.map((c) => c.name))};\\n` +\n // Run from their own bundle, which does not import instrumentation.ts.\n `const BARE_COMMANDS = ${JSON.stringify(commands.filter((c) => !c.instrumented).map((c) => c.name))};\\n` +\n `const COMMAND_HELP = ${JSON.stringify(help)};\\n` +\n '\\n' +\n 'const answer = (argv, program) => {\\n' +\n ' const [name] = argv;\\n' +\n ' const help = COMMAND_HELP.split(\"%PROGRAM%\").join(program);\\n' +\n '\\n' +\n ' if (name === \"--help\" || name === \"-h\") return { code: 0, text: help };\\n' +\n ' if (name === undefined) return { code: 2, text: help };\\n' +\n ' if (!COMMAND_NAMES.includes(name)) return { code: 2, text: \"Not a command: \" + name + \"\\\\n\\\\n\" + help };\\n' +\n '\\n' +\n ' return null;\\n' +\n '};\\n'\n )\n}\n\n/**\n * The process's SIGTERM and SIGINT, held from before the app loads.\n *\n * The runner takes over once the app has loaded, and the app can take a while\n * - a signal before that finds only the default action, which ends the\n * process with 143 and neither stops a worker the way it asks nor lets it\n * say it stopped. These listeners count what comes first and the runner is\n * handed it (see EARLY_SIGNALS in commands.ts), and from then on each signal\n * is passed to the runner. They are never taken off: a signal is delivered on\n * the event loop's next turn, and one that arrived during a synchronous load\n * is lost to a listener removed before that turn.\n *\n * If the load never finishes the process still ends: RSC_SHUTDOWN_TIMEOUT\n * after the first, or at once on the second.\n */\nfunction earlySignalsSource(): string {\n return (\n 'const early = { count: 0, timer: undefined, listener: undefined };\\n' +\n 'const onSignal = () => {\\n' +\n ' if (early.listener) return early.listener();\\n' +\n '\\n' +\n ' early.count += 1;\\n' +\n '\\n' +\n ' if (early.count > 1) process.exit(1);\\n' +\n '\\n' +\n ' early.timer = setTimeout(() => process.exit(1), (Number(process.env.RSC_SHUTDOWN_TIMEOUT) || 10) * 1000);\\n' +\n '};\\n' +\n 'early.handoff = (listener) => {\\n' +\n ' clearTimeout(early.timer);\\n' +\n ' early.listener = listener;\\n' +\n '};\\n' +\n 'process.on(\"SIGTERM\", onSignal);\\n' +\n 'process.on(\"SIGINT\", onSignal);\\n' +\n `globalThis[Symbol.for(${JSON.stringify(EARLY_SIGNALS.description)})] = early;\\n`\n )\n}\n\n/**\n * Loading the app to run a command: the polyfills first, then the entry.\n *\n * A polyfill a dependency checks for at import (`reflect-metadata`, which\n * tsyringe wants and an auth library brings) is loaded for the server by a\n * Nitro plugin, and a command does not go through Nitro. The bundler decides\n * where an external import lands among the chunks, so the app cannot be left\n * to import it in time - this does, by name, from the copy the build traced\n * beside the server. Only in the command path: the server has its own.\n */\nfunction loadAppSource(polyfills: readonly string[]): string {\n const loads = polyfills.map((name) => `await import(${JSON.stringify(name)});\\n`).join('')\n\n return (\n loads +\n 'const { runCommand } = await (BARE_COMMANDS.includes(argv[0]) ? import(\"./_ssr/' + BARE_COMMANDS_ENVIRONMENT + '.mjs\") : import(\"./_ssr/rsc.mjs\"));\\n'\n )\n}\n\n/**\n * The entry a plain build runs a command from, written beside the server:\n *\n * node .output/server/commands.mjs migrate\n * bun .output/server/commands.mjs migrate\n *\n * The server is not started - nothing here imports it - and the flag is set\n * before the entry is imported, since importing it is what would otherwise\n * call register().\n */\nexport function commandsEntrySource(options: CommandEntryOptions): string {\n return (\n \"// Run one of the app's commands without starting the server:\\n\" +\n '// node .output/server/commands.mjs <command> [args]\\n' +\n '// bun .output/server/commands.mjs <command> [args]\\n' +\n 'import { relative } from \"node:path\";\\n' +\n 'import { fileURLToPath } from \"node:url\";\\n' +\n '\\n' +\n commandAnswerSource(options) +\n '\\n' +\n 'const argv = process.argv.slice(2);\\n' +\n 'const program = (process.versions.bun ? \"bun\" : \"node\") + \" \" + relative(process.cwd(), fileURLToPath(import.meta.url));\\n' +\n 'const answered = answer(argv, program);\\n' +\n '\\n' +\n 'if (answered) {\\n' +\n ' (answered.code === 0 ? process.stdout : process.stderr).write(answered.text);\\n' +\n ' process.exitCode = answered.code;\\n' +\n '} else {\\n' +\n ' globalThis[Symbol.for(\"rsc-kit.command\")] = true;\\n' +\n '\\n' +\n indent(earlySignalsSource()) +\n '\\n' +\n ' let code = 1;\\n' +\n '\\n' +\n ' try {\\n' +\n indent(indent(loadAppSource(options.polyfills))) +\n ' code = await runCommand(argv, program);\\n' +\n ' } catch (error) {\\n' +\n ' // The app failed to load: instrumentation.ts refusing a bad environment,\\n' +\n ' // as it would stop the server.\\n' +\n ' console.error(\"[rsc-kit] the app failed to load:\", error);\\n' +\n ' }\\n' +\n '\\n' +\n ' process.exit(code);\\n' +\n '}\\n'\n )\n}\n\n/**\n * The entry a binary is compiled from, for an app that has `src/commands`:\n * the server with no arguments, and a command with one.\n *\n * The server's import is dynamic here, and only here. A static import starts\n * the server as it evaluates, before any line of this file could look at\n * `argv`, and a release step run as `./app migrate` would bind the port the\n * running version holds. It is not awaited - a top-level await is what stopped\n * `--bytecode` from applying to this entry - and a literal specifier is one\n * Bun embeds, as it does the static one. The command path chains its imports\n * with `then` for the same reason.\n *\n * Any argument is a command, and an unknown one is refused (exit 2) rather\n * than ignored by a server that starts.\n */\nexport function compileEntryWithCommands(dirName: string, options: CommandEntryOptions): string {\n // One after another, the polyfills before the entry that needs them.\n // Both literal, so Bun embeds both; which one runs is the command's.\n const app = `(BARE_COMMANDS.includes(argv[0]) ? import(\"./_ssr/${BARE_COMMANDS_ENVIRONMENT}.mjs\") : import(\"./_ssr/rsc.mjs\"))`\n const chain = [...options.polyfills.map((name) => `import(${JSON.stringify(name)})`), app]\n .map((load) => `.then(() => ${load})`)\n .join('\\n ')\n\n return (\n '// The entry to compile into one binary:\\n' +\n '// bun build --compile --splitting .output/server/compile.mjs --outfile dist/app\\n' +\n '//\\n' +\n '// --splitting is what lets a worker hold a stop. Bun parses every module in a\\n' +\n '// binary before it runs the first line of any, and a stop that arrives in that\\n' +\n '// time is the default action (exit 143): splitting makes this file its own\\n' +\n '// chunk, which holds the stop for the command from its first line.\\n' +\n '//\\n' +\n '// ./app starts the server. ./app <command> runs src/commands/<command>.\\n' +\n '//\\n' +\n '// A binary carries what is imported by name. The server reads its frozen pages\\n' +\n '// through a computed import, which a compile cannot see - imported here, they\\n' +\n '// travel inside the binary and are handed to the server before it starts.\\n' +\n '// The server itself is imported only when no command was named: a static\\n' +\n '// import would start it, and bind its port, before argv could be read. Not\\n' +\n '// awaited, since a top-level await is what stopped --bytecode from applying.\\n' +\n 'import { basename } from \"node:path\";\\n' +\n `import \"./${inlineModuleName(dirName)}\";\\n` +\n '\\n' +\n commandAnswerSource(options) +\n '\\n' +\n 'const failed = (what) => (error) => {\\n' +\n ' console.error(what, error);\\n' +\n ' process.exit(1);\\n' +\n '};\\n' +\n '\\n' +\n 'if (process.argv.length <= 2) {\\n' +\n ' import(\"./index.mjs\").catch(failed(\"[rsc-kit] the server failed to start:\"));\\n' +\n '} else {\\n' +\n ' const argv = process.argv.slice(2);\\n' +\n ' const program = \"./\" + basename(process.execPath);\\n' +\n ' const answered = answer(argv, program);\\n' +\n '\\n' +\n ' if (answered) {\\n' +\n ' (answered.code === 0 ? process.stdout : process.stderr).write(answered.text);\\n' +\n ' process.exitCode = answered.code;\\n' +\n ' } else {\\n' +\n ' globalThis[Symbol.for(\"rsc-kit.command\")] = true;\\n' +\n '\\n' +\n indent(indent(earlySignalsSource())) +\n '\\n' +\n ' Promise.resolve()\\n' +\n ` ${chain}\\n` +\n ' .then(({ runCommand }) => runCommand(argv, program))\\n' +\n ' .then((code) => process.exit(code), failed(\"[rsc-kit] the app failed to load:\"));\\n' +\n ' }\\n' +\n '}\\n'\n )\n}\n\nfunction indent(text: string): string {\n return text\n .split('\\n')\n .map((line) => (line ? ' ' + line : line))\n .join('\\n')\n}\n"]}
1
+ {"version":3,"file":"commandFiles.js","sourceRoot":"","sources":["../src/commandFiles.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The build's half of app commands: which files in `src/commands` are\n * commands, what the generated entry says about them, and the small entries\n * that run one. The runner they call is `commands.ts`.\n */\n\nimport { existsSync, readdirSync, readFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { commandHelp, EARLY_SIGNALS } from './commands.js'\nimport { inlineModuleName } from './files.js'\n\nexport interface CommandFile {\n /** What is typed after the binary: the file's name without its extension. */\n name: string\n /** Absolute path, imported lazily by the entry. */\n file: string\n /** A plain string literal exported as `description`, for `--help`. */\n description?: string\n /**\n * `export const register = true`: runs from the server's bundle, which\n * imports instrumentation.ts. Every other command runs from one that does\n * not - see BARE_COMMANDS_MODULE_ID.\n */\n instrumented?: boolean\n}\n\nexport interface CommandFiles {\n commands: CommandFile[]\n /** Paths under src/commands that are not commands, and why, for the build to say. */\n ignored: { path: string; reason: string }[]\n}\n\nconst SOURCE = /\\.(?:[cm]?[jt]sx?)$/\nconst NAME = /^[A-Za-z0-9][A-Za-z0-9_.:-]*$/\n\n/** Earlier wins when two files share a name: migrate.ts over migrate.js. */\nconst EXTENSION_ORDER = ['ts', 'mts', 'cts', 'tsx', 'js', 'mjs', 'cjs', 'jsx']\n\n/**\n * `export const description = 'Apply the database migrations'`, read off the\n * source so `--help` can list a command without loading it - a command's\n * imports are the database and the logger, and listing should not open either.\n * A plain string literal only; anything computed is simply not listed.\n */\nfunction describe(source: string): string | undefined {\n const match = /^\\s*export\\s+const\\s+description\\s*(?::\\s*string\\s*)?=\\s*([\"'`])([^\"'`\\\\\\n$]*)\\1/m.exec(source)\n\n return match?.[2]?.trim() || undefined\n}\n\n/**\n * `export const register = true`, read off the source as `description` is:\n * which bundle a command runs from is decided before anything loads, and only\n * one that asked for register() has a reason to load instrumentation.ts.\n */\nfunction registers(source: string): boolean {\n return /^\\s*export\\s+const\\s+register\\s*(?::\\s*boolean\\s*)?=\\s*true\\b/m.test(source)\n}\n\n/**\n * The commands under `<sourceDir>/commands`.\n *\n * Flat on purpose: a name is what a person types, and `db/seed.ts` raises\n * whether that is `db:seed`, `db/seed` or `seed`. Declaration files, tests and\n * anything starting with `_` or `.` are not commands - the last is how a\n * helper lives beside the commands that share it.\n */\nexport function commandFiles(sourceDir: string): CommandFiles {\n const dir = join(sourceDir, 'commands')\n const found: CommandFiles = { commands: [], ignored: [] }\n\n if (!existsSync(dir)) return found\n\n const taken = new Map<string, CommandFile>()\n const entries = readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))\n\n for (const entry of entries) {\n const path = join(dir, entry.name)\n\n if (entry.name.startsWith('_') || entry.name.startsWith('.')) continue\n\n if (entry.isDirectory()) {\n found.ignored.push({ path, reason: 'commands are the files directly in src/commands, not a folder of them' })\n\n continue\n }\n\n if (!SOURCE.test(entry.name) || /\\.d\\.[cm]?ts$/.test(entry.name) || /\\.(?:test|spec)\\.[cm]?[jt]sx?$/.test(entry.name)) continue\n\n const extension = entry.name.slice(entry.name.lastIndexOf('.') + 1)\n const name = entry.name.slice(0, -(extension.length + 1))\n\n if (!NAME.test(name)) {\n found.ignored.push({ path, reason: `\"${name}\" cannot be typed as a command: letters, digits, \"-\", \"_\", \".\" and \":\"` })\n\n continue\n }\n\n const existing = taken.get(name)\n\n if (existing) {\n const keep = EXTENSION_ORDER.indexOf(existing.file.split('.').pop() ?? '') <= EXTENSION_ORDER.indexOf(extension)\n\n found.ignored.push({\n path: keep ? path : existing.file,\n reason: `two files are called \"${name}\"; ${keep ? existing.file : path} is the command`,\n })\n\n if (keep) continue\n\n found.commands.splice(found.commands.indexOf(existing), 1)\n }\n\n const source = readFileSync(path, 'utf-8')\n const command: CommandFile = { name, file: path, description: describe(source) }\n\n if (registers(source)) command.instrumented = true\n\n taken.set(name, command)\n found.commands.push(command)\n }\n\n return found\n}\n\n/** The module the generated entry reads the table from, resolved when the bundle is built. */\nexport const COMMANDS_MODULE_ID = 'virtual:rsc-kit/commands'\n\n/**\n * The table: every command as a dynamic import, so each is a chunk of its own\n * and the server never evaluates one. An empty object when there are none -\n * a Worker's build, say, which does not carry them.\n *\n * A module of its own rather than text in the entry because the entry is\n * written while the config is still being read, before anything knows the\n * build's mode or which Nitro preset it is for.\n */\nexport function commandsModule(commands: readonly CommandFile[]): string {\n const rows = commands.map(\n ({ name, file, description }) =>\n ` ${JSON.stringify(name)}: { load: () => import(${JSON.stringify(file)})${description ? `, description: ${JSON.stringify(description)}` : ''} },`,\n )\n\n return `export const commands = {\\n${rows.join('\\n')}\\n}\\n`\n}\n\n/**\n * The commands that did not ask for register(), as a bundle of their own.\n *\n * The server's entry imports instrumentation.ts first and statically - that is\n * what stops a server with a bad environment before it serves - so anything\n * loaded through it has validated the web's whole schema. A queue drain that\n * imports only the worker's still needed the web's Stripe and session\n * secrets. This module is the entry of a server environment of its own,\n * bundled apart from the server's, so nothing in it can import back into the\n * entry that loads instrumentation.ts. Each command's own imports still run:\n * it validates what it imports, which is the point.\n */\nexport const BARE_COMMANDS_MODULE_ID = 'virtual:rsc-kit/commands-bare'\n\n/**\n * Its name as an input of the rsc environment, and so the file the server\n * runs it from: `_ssr/rsc-commands.mjs`, beside the server's `_ssr/rsc.mjs`.\n */\nexport const BARE_COMMANDS_ENVIRONMENT = 'rsc-commands'\n\n/** The bare table's module: runCommand over it, with no register() to give. */\nexport function bareCommandsModule(commands: readonly CommandFile[], runner: string): string {\n return (\n `import { runCommand as __runCommand } from ${JSON.stringify(runner)}\\n` +\n commandsModule(commands.filter((command) => !command.instrumented)) +\n // Reached only by a register that is true at runtime but not as a literal\n // the build could read, which put the command in the wrong bundle.\n 'const noRegister = () => Promise.reject(new Error(\"register() runs for a command that says export const register = true, as a literal: this one computes it, so it was built without instrumentation.ts.\"))\\n' +\n 'export function runCommand(argv, program) {\\n' +\n ' return __runCommand(argv, { commands, program, ready: noRegister })\\n' +\n '}\\n'\n )\n}\n\n/**\n * What the generated server entry adds for an app with a `src/commands`:\n * `runCommand` over the table, with the entry's own `register()` and\n * `shutdown()`. `runner` is the engine's `commands` module, named without an\n * extension like every other engine module the entry imports.\n */\nexport function commandsEntry(runner: string): string {\n return `\nimport { commands as __commands } from ${JSON.stringify(COMMANDS_MODULE_ID)}\nimport { runCommand as __runCommand } from ${JSON.stringify(runner)}\n\n// src/commands, each loaded only when it is named.\nexport function runCommand(argv: string[], program?: string) {\n return __runCommand(argv, { commands: __commands, program, ready: instrumented, shutdown: __instrumentation.shutdown })\n}\n`\n}\n\n/** What the entries need to know that only the build does. */\nexport interface CommandEntryOptions {\n commands: readonly CommandFile[]\n /** Polyfills the app's graph has (`reflect-metadata`), by package name. */\n polyfills: readonly string[]\n /** The mode the build baked in. See nodeEnvSource. */\n nodeEnv?: 'development' | 'production'\n}\n\n/**\n * A command is not started through Nitro, so the plugin that gives the server\n * process its NODE_ENV does not run for it (see nodeEnvPlugin in vite.ts).\n * Before the app is imported, and never over a value someone set.\n */\nfunction nodeEnvSource(mode: 'development' | 'production' = 'production'): string {\n return `if (!process.env[\"NODE_ENV\"]) process.env[\"NODE_ENV\"] = ${JSON.stringify(mode)};\\n`\n}\n\n/**\n * What the entries can answer without loading the app: `--help`, no command,\n * a name that is not one.\n *\n * Loading the app imports instrumentation.ts, and so validates the\n * environment - and `./app --help` on a build machine, or in a CI step that\n * has no database url, is not a reason to refuse. The names and descriptions\n * are in the entry itself because the binary carries nothing else: a\n * Dockerfile copies `dist/app` and `.output/rsc-kit.json` is not in it.\n *\n * The help text is written by the same function the runner uses, with the\n * program's name left to fill in, so the two cannot word it differently.\n * `answer` is the whole of the logic; the runner repeats it as a backstop.\n */\nexport function commandAnswerSource({ commands }: CommandEntryOptions): string {\n const table = Object.fromEntries(commands.map(({ name, description }) => [name, { description }]))\n const help = commandHelp(table, '%PROGRAM%')\n\n return (\n `const COMMAND_NAMES = ${JSON.stringify(commands.map((c) => c.name))};\\n` +\n // Run from their own bundle, which does not import instrumentation.ts.\n `const BARE_COMMANDS = ${JSON.stringify(commands.filter((c) => !c.instrumented).map((c) => c.name))};\\n` +\n `const COMMAND_HELP = ${JSON.stringify(help)};\\n` +\n '\\n' +\n 'const answer = (argv, program) => {\\n' +\n ' const [name] = argv;\\n' +\n ' const help = COMMAND_HELP.split(\"%PROGRAM%\").join(program);\\n' +\n '\\n' +\n ' if (name === \"--help\" || name === \"-h\") return { code: 0, text: help };\\n' +\n ' if (name === undefined) return { code: 2, text: help };\\n' +\n ' if (!COMMAND_NAMES.includes(name)) return { code: 2, text: \"Not a command: \" + name + \"\\\\n\\\\n\" + help };\\n' +\n '\\n' +\n ' return null;\\n' +\n '};\\n'\n )\n}\n\n/**\n * The process's SIGTERM and SIGINT, held from before the app loads.\n *\n * The runner takes over once the app has loaded, and the app can take a while\n * - a signal before that finds only the default action, which ends the\n * process with 143 and neither stops a worker the way it asks nor lets it\n * say it stopped. These listeners count what comes first and the runner is\n * handed it (see EARLY_SIGNALS in commands.ts), and from then on each signal\n * is passed to the runner. They are never taken off: a signal is delivered on\n * the event loop's next turn, and one that arrived during a synchronous load\n * is lost to a listener removed before that turn.\n *\n * If the load never finishes the process still ends: RSC_SHUTDOWN_TIMEOUT\n * after the first, or at once on the second.\n */\nfunction earlySignalsSource(): string {\n return (\n 'const early = { count: 0, timer: undefined, listener: undefined };\\n' +\n 'const onSignal = () => {\\n' +\n ' if (early.listener) return early.listener();\\n' +\n '\\n' +\n ' early.count += 1;\\n' +\n '\\n' +\n ' if (early.count > 1) process.exit(1);\\n' +\n '\\n' +\n ' early.timer = setTimeout(() => process.exit(1), (Number(process.env.RSC_SHUTDOWN_TIMEOUT) || 10) * 1000);\\n' +\n '};\\n' +\n 'early.handoff = (listener) => {\\n' +\n ' clearTimeout(early.timer);\\n' +\n ' early.listener = listener;\\n' +\n '};\\n' +\n 'process.on(\"SIGTERM\", onSignal);\\n' +\n 'process.on(\"SIGINT\", onSignal);\\n' +\n `globalThis[Symbol.for(${JSON.stringify(EARLY_SIGNALS.description)})] = early;\\n`\n )\n}\n\n/**\n * Loading the app to run a command: the polyfills first, then the entry.\n *\n * A polyfill a dependency checks for at import (`reflect-metadata`, which\n * tsyringe wants and an auth library brings) is loaded for the server by a\n * Nitro plugin, and a command does not go through Nitro. The bundler decides\n * where an external import lands among the chunks, so the app cannot be left\n * to import it in time - this does, by name, from the copy the build traced\n * beside the server. Only in the command path: the server has its own.\n */\nfunction loadAppSource(polyfills: readonly string[]): string {\n const loads = polyfills.map((name) => `await import(${JSON.stringify(name)});\\n`).join('')\n\n return (\n loads +\n 'const { runCommand } = await (BARE_COMMANDS.includes(argv[0]) ? import(\"./_ssr/' + BARE_COMMANDS_ENVIRONMENT + '.mjs\") : import(\"./_ssr/rsc.mjs\"));\\n'\n )\n}\n\n/**\n * The entry a plain build runs a command from, written beside the server:\n *\n * node .output/server/commands.mjs migrate\n * bun .output/server/commands.mjs migrate\n *\n * The server is not started - nothing here imports it - and the flag is set\n * before the entry is imported, since importing it is what would otherwise\n * call register().\n */\nexport function commandsEntrySource(options: CommandEntryOptions): string {\n return (\n \"// Run one of the app's commands without starting the server:\\n\" +\n '// node .output/server/commands.mjs <command> [args]\\n' +\n '// bun .output/server/commands.mjs <command> [args]\\n' +\n 'import { relative } from \"node:path\";\\n' +\n 'import { fileURLToPath } from \"node:url\";\\n' +\n '\\n' +\n nodeEnvSource(options.nodeEnv) +\n '\\n' +\n commandAnswerSource(options) +\n '\\n' +\n 'const argv = process.argv.slice(2);\\n' +\n 'const program = (process.versions.bun ? \"bun\" : \"node\") + \" \" + relative(process.cwd(), fileURLToPath(import.meta.url));\\n' +\n 'const answered = answer(argv, program);\\n' +\n '\\n' +\n 'if (answered) {\\n' +\n ' (answered.code === 0 ? process.stdout : process.stderr).write(answered.text);\\n' +\n ' process.exitCode = answered.code;\\n' +\n '} else {\\n' +\n ' globalThis[Symbol.for(\"rsc-kit.command\")] = true;\\n' +\n '\\n' +\n indent(earlySignalsSource()) +\n '\\n' +\n ' let code = 1;\\n' +\n '\\n' +\n ' try {\\n' +\n indent(indent(loadAppSource(options.polyfills))) +\n ' code = await runCommand(argv, program);\\n' +\n ' } catch (error) {\\n' +\n ' // The app failed to load: instrumentation.ts refusing a bad environment,\\n' +\n ' // as it would stop the server.\\n' +\n ' console.error(\"[rsc-kit] the app failed to load:\", error);\\n' +\n ' }\\n' +\n '\\n' +\n ' process.exit(code);\\n' +\n '}\\n'\n )\n}\n\n/**\n * The entry a binary is compiled from, for an app that has `src/commands`:\n * the server with no arguments, and a command with one.\n *\n * The server's import is dynamic here, and only here. A static import starts\n * the server as it evaluates, before any line of this file could look at\n * `argv`, and a release step run as `./app migrate` would bind the port the\n * running version holds. It is not awaited - a top-level await is what stopped\n * `--bytecode` from applying to this entry - and a literal specifier is one\n * Bun embeds, as it does the static one. The command path chains its imports\n * with `then` for the same reason.\n *\n * Any argument is a command, and an unknown one is refused (exit 2) rather\n * than ignored by a server that starts.\n */\nexport function compileEntryWithCommands(dirName: string, options: CommandEntryOptions): string {\n // One after another, the polyfills before the entry that needs them.\n // Both literal, so Bun embeds both; which one runs is the command's.\n const app = `(BARE_COMMANDS.includes(argv[0]) ? import(\"./_ssr/${BARE_COMMANDS_ENVIRONMENT}.mjs\") : import(\"./_ssr/rsc.mjs\"))`\n const chain = [...options.polyfills.map((name) => `import(${JSON.stringify(name)})`), app]\n .map((load) => `.then(() => ${load})`)\n .join('\\n ')\n\n return (\n '// The entry to compile into one binary:\\n' +\n '// bun build --compile --splitting .output/server/compile.mjs --outfile dist/app\\n' +\n '//\\n' +\n '// --splitting is what lets a worker hold a stop. Bun parses every module in a\\n' +\n '// binary before it runs the first line of any, and a stop that arrives in that\\n' +\n '// time is the default action (exit 143): splitting makes this file its own\\n' +\n '// chunk, which holds the stop for the command from its first line.\\n' +\n '//\\n' +\n '// ./app starts the server. ./app <command> runs src/commands/<command>.\\n' +\n '//\\n' +\n '// A binary carries what is imported by name. The server reads its frozen pages\\n' +\n '// through a computed import, which a compile cannot see - imported here, they\\n' +\n '// travel inside the binary and are handed to the server before it starts.\\n' +\n '// The server itself is imported only when no command was named: a static\\n' +\n '// import would start it, and bind its port, before argv could be read. Not\\n' +\n '// awaited, since a top-level await is what stopped --bytecode from applying.\\n' +\n 'import { basename } from \"node:path\";\\n' +\n `import \"./${inlineModuleName(dirName)}\";\\n` +\n '\\n' +\n nodeEnvSource(options.nodeEnv) +\n '\\n' +\n commandAnswerSource(options) +\n '\\n' +\n 'const failed = (what) => (error) => {\\n' +\n ' console.error(what, error);\\n' +\n ' process.exit(1);\\n' +\n '};\\n' +\n '\\n' +\n 'if (process.argv.length <= 2) {\\n' +\n ' import(\"./index.mjs\").catch(failed(\"[rsc-kit] the server failed to start:\"));\\n' +\n '} else {\\n' +\n ' const argv = process.argv.slice(2);\\n' +\n ' const program = \"./\" + basename(process.execPath);\\n' +\n ' const answered = answer(argv, program);\\n' +\n '\\n' +\n ' if (answered) {\\n' +\n ' (answered.code === 0 ? process.stdout : process.stderr).write(answered.text);\\n' +\n ' process.exitCode = answered.code;\\n' +\n ' } else {\\n' +\n ' globalThis[Symbol.for(\"rsc-kit.command\")] = true;\\n' +\n '\\n' +\n indent(indent(earlySignalsSource())) +\n '\\n' +\n ' Promise.resolve()\\n' +\n ` ${chain}\\n` +\n ' .then(({ runCommand }) => runCommand(argv, program))\\n' +\n ' .then((code) => process.exit(code), failed(\"[rsc-kit] the app failed to load:\"));\\n' +\n ' }\\n' +\n '}\\n'\n )\n}\n\nfunction indent(text: string): string {\n return text\n .split('\\n')\n .map((line) => (line ? ' ' + line : line))\n .join('\\n')\n}\n"]}
@@ -13,6 +13,13 @@ import type { ReactNode } from "react";
13
13
  * under the SegmentBoundary above it, which carries one; a slot of the root
14
14
  * layout sits under nothing, and without this its redirect would unmount
15
15
  * the document.
16
+ *
17
+ * A refresh is swapped in as a transition. Read through useSyncExternalStore it
18
+ * was applied at once - React never treats that store's update as one it can
19
+ * hold back - so content still arriving suspended to the nearest fallback, and
20
+ * a team picker and its breadcrumb blinked to their skeleton on every save.
21
+ * Held in this boundary's state instead, React keeps what is on screen until
22
+ * the new content is ready, and one that is ready shows at once.
16
23
  */
17
24
  export declare function SlotBoundary({ name, children }: {
18
25
  name: string;
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
- import { useContext, useSyncExternalStore } from "react";
3
+ import { startTransition, useContext, useEffect, useState } from "react";
4
4
  import { getSlotState, subscribeToSlot } from "./slotStore";
5
5
  import { RedirectBoundary } from "./RedirectBoundary";
6
6
  import { SegmentDepthContext } from "./segmentDepth";
@@ -18,11 +18,26 @@ import { SegmentDepthContext } from "./segmentDepth";
18
18
  * under the SegmentBoundary above it, which carries one; a slot of the root
19
19
  * layout sits under nothing, and without this its redirect would unmount
20
20
  * the document.
21
+ *
22
+ * A refresh is swapped in as a transition. Read through useSyncExternalStore it
23
+ * was applied at once - React never treats that store's update as one it can
24
+ * hold back - so content still arriving suspended to the nearest fallback, and
25
+ * a team picker and its breadcrumb blinked to their skeleton on every save.
26
+ * Held in this boundary's state instead, React keeps what is on screen until
27
+ * the new content is ready, and one that is ready shows at once.
21
28
  */
22
29
  export function SlotBoundary({ name, children }) {
23
30
  // Which navigation replaces this region - see clearSlots.
24
31
  const depth = useContext(SegmentDepthContext);
25
- const state = useSyncExternalStore((listener) => subscribeToSlot(name, listener, depth), () => getSlotState(name), () => getSlotState(name));
32
+ const [state, setState] = useState(() => getSlotState(name));
33
+ useEffect(() => {
34
+ const sync = () => startTransition(() => setState(getSlotState(name)));
35
+ const unsubscribe = subscribeToSlot(name, sync, depth);
36
+ // A write between the render and this subscription would otherwise wait
37
+ // for the next one.
38
+ sync();
39
+ return unsubscribe;
40
+ }, [name, depth]);
26
41
  return (_jsx(RedirectBoundary, { children: (state.tree === undefined ? children : state.tree) }));
27
42
  }
28
43
  //# sourceMappingURL=SlotBoundary.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"SlotBoundary.js","sourceRoot":"","sources":["../../src/js/SlotBoundary.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,EAAE,UAAU,EAAE,oBAAoB,EAAE,MAAM,OAAO,CAAC;AAEzD,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAErD;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,YAAY,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAyC;IACpF,0DAA0D;IAC1D,MAAM,KAAK,GAAG,UAAU,CAAC,mBAAmB,CAAC,CAAC;IAC9C,MAAM,KAAK,GAAG,oBAAoB,CAChC,CAAC,QAAQ,EAAE,EAAE,CAAC,eAAe,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,CAAC,EACpD,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,EACxB,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,CACzB,CAAC;IAEF,OAAO,CACL,KAAC,gBAAgB,cACd,CAAC,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAc,GAC/C,CACpB,CAAC;AACJ,CAAC","sourcesContent":["\"use client\";\n\nimport { useContext, useSyncExternalStore } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { getSlotState, subscribeToSlot } from \"./slotStore\";\nimport { RedirectBoundary } from \"./RedirectBoundary\";\nimport { SegmentDepthContext } from \"./segmentDepth\";\n\n/**\n * The seam a re-rendered slot is swapped in at.\n *\n * Server components cannot be re-rendered in the browser, so the swap point\n * has to be a client component reading from a store. With nothing stored it\n * renders what the server sent with the page — which is the behaviour that\n * existed before slots could be revalidated, and is what makes this safe to\n * wrap every slot in.\n *\n * Inside a RedirectBoundary, because a slot is a page too and may decide a\n * redirect inside its own Suspense boundary. A slot of a nested layout sits\n * under the SegmentBoundary above it, which carries one; a slot of the root\n * layout sits under nothing, and without this its redirect would unmount\n * the document.\n */\nexport function SlotBoundary({ name, children }: { name: string; children: ReactNode }) {\n // Which navigation replaces this region - see clearSlots.\n const depth = useContext(SegmentDepthContext);\n const state = useSyncExternalStore(\n (listener) => subscribeToSlot(name, listener, depth),\n () => getSlotState(name),\n () => getSlotState(name),\n );\n\n return (\n <RedirectBoundary>\n {(state.tree === undefined ? children : state.tree) as ReactNode}\n </RedirectBoundary>\n );\n}\n"]}
1
+ {"version":3,"file":"SlotBoundary.js","sourceRoot":"","sources":["../../src/js/SlotBoundary.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAEzE,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAErD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,UAAU,YAAY,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAyC;IACpF,0DAA0D;IAC1D,MAAM,KAAK,GAAG,UAAU,CAAC,mBAAmB,CAAC,CAAC;IAC9C,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC;IAE7D,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,IAAI,GAAG,GAAG,EAAE,CAAC,eAAe,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACvE,MAAM,WAAW,GAAG,eAAe,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;QAEvD,wEAAwE;QACxE,oBAAoB;QACpB,IAAI,EAAE,CAAC;QAEP,OAAO,WAAW,CAAC;IACrB,CAAC,EAAE,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;IAElB,OAAO,CACL,KAAC,gBAAgB,cACd,CAAC,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAc,GAC/C,CACpB,CAAC;AACJ,CAAC","sourcesContent":["\"use client\";\n\nimport { startTransition, useContext, useEffect, useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { getSlotState, subscribeToSlot } from \"./slotStore\";\nimport { RedirectBoundary } from \"./RedirectBoundary\";\nimport { SegmentDepthContext } from \"./segmentDepth\";\n\n/**\n * The seam a re-rendered slot is swapped in at.\n *\n * Server components cannot be re-rendered in the browser, so the swap point\n * has to be a client component reading from a store. With nothing stored it\n * renders what the server sent with the page — which is the behaviour that\n * existed before slots could be revalidated, and is what makes this safe to\n * wrap every slot in.\n *\n * Inside a RedirectBoundary, because a slot is a page too and may decide a\n * redirect inside its own Suspense boundary. A slot of a nested layout sits\n * under the SegmentBoundary above it, which carries one; a slot of the root\n * layout sits under nothing, and without this its redirect would unmount\n * the document.\n *\n * A refresh is swapped in as a transition. Read through useSyncExternalStore it\n * was applied at once - React never treats that store's update as one it can\n * hold back - so content still arriving suspended to the nearest fallback, and\n * a team picker and its breadcrumb blinked to their skeleton on every save.\n * Held in this boundary's state instead, React keeps what is on screen until\n * the new content is ready, and one that is ready shows at once.\n */\nexport function SlotBoundary({ name, children }: { name: string; children: ReactNode }) {\n // Which navigation replaces this region - see clearSlots.\n const depth = useContext(SegmentDepthContext);\n const [state, setState] = useState(() => getSlotState(name));\n\n useEffect(() => {\n const sync = () => startTransition(() => setState(getSlotState(name)));\n const unsubscribe = subscribeToSlot(name, sync, depth);\n\n // A write between the render and this subscription would otherwise wait\n // for the next one.\n sync();\n\n return unsubscribe;\n }, [name, depth]);\n\n return (\n <RedirectBoundary>\n {(state.tree === undefined ? children : state.tree) as ReactNode}\n </RedirectBoundary>\n );\n}\n"]}
package/dist/vite.d.ts CHANGED
@@ -609,6 +609,22 @@ export declare function serverOnlyEntry(id: string, root: string): string | null
609
609
  * process to signal, and not the dev server or the build, where Ctrl+C should
610
610
  * stop at once.
611
611
  */
612
+ /**
613
+ * The process's NODE_ENV, when nobody set one: the mode the build baked in.
614
+ *
615
+ * The build replaces the expression `process.env.NODE_ENV` in the bundle, and
616
+ * that is all it did - the running process had none. Code that read the
617
+ * variable any other way read nothing: a typed env built from a spread of
618
+ * `process.env` fell to its schema's `.default('development')`, and a Stripe
619
+ * webhook that skipped live events outside production answered every live
620
+ * event 200 "skipped" for five weeks. Next sets it in the process; so does
621
+ * this, so the bundle and the process cannot disagree.
622
+ *
623
+ * Never over a value someone set. Not written as \`process.env.NODE_ENV\`:
624
+ * Nitro replaces that expression too, and an assignment to a string literal
625
+ * is a syntax error.
626
+ */
627
+ export declare function nodeEnvPlugin(mode: "development" | "production"): string;
612
628
  export declare const SHUTDOWN_PLUGIN = "export default function rscKitShutdown() {\n if (typeof navigator !== 'undefined' && navigator.userAgent === 'Cloudflare-Workers') return\n if (typeof process === 'undefined' || typeof process.once !== 'function') return\n\n const unref = (timer) => (timer && typeof timer.unref === 'function' ? timer.unref() : timer)\n let stopping = false\n\n const stop = () => {\n if (stopping) return\n stopping = true\n\n const drain = (Number(process.env.SERVER_SHUTDOWN_TIMEOUT) || 5) * 1000\n const limit = (Number(process.env.RSC_SHUTDOWN_TIMEOUT) || 10) * 1000\n\n unref(setTimeout(() => {\n const hook = globalThis.__rscKitShutdown\n\n Promise.race([\n Promise.resolve().then(() => (typeof hook === 'function' ? hook() : undefined)),\n new Promise((resolve) => unref(setTimeout(resolve, limit))),\n ])\n .catch((error) => console.error('[rsc-kit] instrumentation.ts shutdown() failed:', error))\n .finally(() => process.exit(0))\n }, drain + 250))\n }\n\n process.once('SIGTERM', stop)\n process.once('SIGINT', stop)\n}\n";
613
629
  /**
614
630
  * The PUBLIC_ variables an app still sets, and where each one is set.
package/dist/vite.js CHANGED
@@ -52,6 +52,8 @@ let clientLibraryImports = [];
52
52
  let nitroPreset = null;
53
53
  /** Set by Nitro's setup of a built server, which asks for the commands before Vite has resolved. */
54
54
  let nitroBuilding = false;
55
+ /** The mode this build bakes in, for the process that runs it. See nodeEnvPlugin. */
56
+ let builtNodeEnv = "production";
55
57
  /** `src/commands`, read once per build; null until asked. See buildCommands(). */
56
58
  let commandsFound = null;
57
59
  /**
@@ -62,6 +64,7 @@ let commandsFound = null;
62
64
  function commandEntryOptions() {
63
65
  return {
64
66
  commands: buildCommands(),
67
+ nodeEnv: builtNodeEnv,
65
68
  polyfills: FIRST_POLYFILLS.filter((name) => packageEntryInGraph(projectRoot, name) !== null),
66
69
  };
67
70
  }
@@ -7347,6 +7350,27 @@ export default function rscKitStartup() {
7347
7350
  * process to signal, and not the dev server or the build, where Ctrl+C should
7348
7351
  * stop at once.
7349
7352
  */
7353
+ /**
7354
+ * The process's NODE_ENV, when nobody set one: the mode the build baked in.
7355
+ *
7356
+ * The build replaces the expression `process.env.NODE_ENV` in the bundle, and
7357
+ * that is all it did - the running process had none. Code that read the
7358
+ * variable any other way read nothing: a typed env built from a spread of
7359
+ * `process.env` fell to its schema's `.default('development')`, and a Stripe
7360
+ * webhook that skipped live events outside production answered every live
7361
+ * event 200 "skipped" for five weeks. Next sets it in the process; so does
7362
+ * this, so the bundle and the process cannot disagree.
7363
+ *
7364
+ * Never over a value someone set. Not written as \`process.env.NODE_ENV\`:
7365
+ * Nitro replaces that expression too, and an assignment to a string literal
7366
+ * is a syntax error.
7367
+ */
7368
+ export function nodeEnvPlugin(mode) {
7369
+ return `const env = typeof process === "undefined" ? undefined : process.env;
7370
+ if (env && !env.NODE_ENV) env.NODE_ENV = ${JSON.stringify(mode)};
7371
+ export default () => {};
7372
+ `;
7373
+ }
7350
7374
  export const SHUTDOWN_PLUGIN = `export default function rscKitShutdown() {
7351
7375
  if (typeof navigator !== 'undefined' && navigator.userAgent === 'Cloudflare-Workers') return
7352
7376
  if (typeof process === 'undefined' || typeof process.once !== 'function') return
@@ -7680,6 +7704,11 @@ export function rscKit(options = {}) {
7680
7704
  "\nexport default () => {};\n";
7681
7705
  nitro.options.plugins = ["#rsc-kit/polyfills", ...(nitro.options.plugins ?? [])];
7682
7706
  }
7707
+ // Before every other plugin, the polyfills too: any of them may read
7708
+ // it. Its source is asked for when Nitro bundles, after Vite has
7709
+ // resolved the mode.
7710
+ nitro.options.virtual["#rsc-kit/node-env"] = () => nodeEnvPlugin(builtNodeEnv);
7711
+ nitro.options.plugins = ["#rsc-kit/node-env", ...(nitro.options.plugins ?? [])];
7683
7712
  // Every built server, instrumentation.ts or not: the hang it prevents
7684
7713
  // is any open connection, wherever the app opened it.
7685
7714
  if (!nitro.options.dev) {
@@ -7810,7 +7839,7 @@ export function rscKit(options = {}) {
7810
7839
  * answer travels with the bundle instead of with whoever starts it.
7811
7840
  */
7812
7841
  define: {
7813
- "process.env.NODE_ENV": JSON.stringify(env.mode === "development" ? "development" : "production"),
7842
+ "process.env.NODE_ENV": JSON.stringify((builtNodeEnv = env.mode === "development" ? "development" : "production")),
7814
7843
  __RSC_OFFLINE__: JSON.stringify(offline),
7815
7844
  },
7816
7845
  /*