@rsc-kit/core 0.30.33 → 0.30.34

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.
@@ -10,6 +10,12 @@ export interface CommandFile {
10
10
  file: string;
11
11
  /** A plain string literal exported as `description`, for `--help`. */
12
12
  description?: string;
13
+ /**
14
+ * `export const register = true`: runs from the server's bundle, which
15
+ * imports instrumentation.ts. Every other command runs from one that does
16
+ * not - see BARE_COMMANDS_MODULE_ID.
17
+ */
18
+ instrumented?: boolean;
13
19
  }
14
20
  export interface CommandFiles {
15
21
  commands: CommandFile[];
@@ -40,6 +46,26 @@ export declare const COMMANDS_MODULE_ID = "virtual:rsc-kit/commands";
40
46
  * build's mode or which Nitro preset it is for.
41
47
  */
42
48
  export declare function commandsModule(commands: readonly CommandFile[]): string;
49
+ /**
50
+ * The commands that did not ask for register(), as a bundle of their own.
51
+ *
52
+ * The server's entry imports instrumentation.ts first and statically - that is
53
+ * what stops a server with a bad environment before it serves - so anything
54
+ * loaded through it has validated the web's whole schema. A queue drain that
55
+ * imports only the worker's still needed the web's Stripe and session
56
+ * secrets. This module is the entry of a server environment of its own,
57
+ * bundled apart from the server's, so nothing in it can import back into the
58
+ * entry that loads instrumentation.ts. Each command's own imports still run:
59
+ * it validates what it imports, which is the point.
60
+ */
61
+ export declare const BARE_COMMANDS_MODULE_ID = "virtual:rsc-kit/commands-bare";
62
+ /**
63
+ * Its name as an input of the rsc environment, and so the file the server
64
+ * runs it from: `_ssr/rsc-commands.mjs`, beside the server's `_ssr/rsc.mjs`.
65
+ */
66
+ export declare const BARE_COMMANDS_ENVIRONMENT = "rsc-commands";
67
+ /** The bare table's module: runCommand over it, with no register() to give. */
68
+ export declare function bareCommandsModule(commands: readonly CommandFile[], runner: string): string;
43
69
  /**
44
70
  * What the generated server entry adds for an app with a `src/commands`:
45
71
  * `runCommand` over the table, with the entry's own `register()` and
@@ -21,6 +21,14 @@ function describe(source) {
21
21
  const match = /^\s*export\s+const\s+description\s*(?::\s*string\s*)?=\s*(["'`])([^"'`\\\n$]*)\1/m.exec(source);
22
22
  return match?.[2]?.trim() || undefined;
23
23
  }
24
+ /**
25
+ * `export const register = true`, read off the source as `description` is:
26
+ * which bundle a command runs from is decided before anything loads, and only
27
+ * one that asked for register() has a reason to load instrumentation.ts.
28
+ */
29
+ function registers(source) {
30
+ return /^\s*export\s+const\s+register\s*(?::\s*boolean\s*)?=\s*true\b/m.test(source);
31
+ }
24
32
  /**
25
33
  * The commands under `<sourceDir>/commands`.
26
34
  *
@@ -63,7 +71,10 @@ export function commandFiles(sourceDir) {
63
71
  continue;
64
72
  found.commands.splice(found.commands.indexOf(existing), 1);
65
73
  }
66
- const command = { name, file: path, description: describe(readFileSync(path, 'utf-8')) };
74
+ const source = readFileSync(path, 'utf-8');
75
+ const command = { name, file: path, description: describe(source) };
76
+ if (registers(source))
77
+ command.instrumented = true;
67
78
  taken.set(name, command);
68
79
  found.commands.push(command);
69
80
  }
@@ -84,6 +95,35 @@ export function commandsModule(commands) {
84
95
  const rows = commands.map(({ name, file, description }) => ` ${JSON.stringify(name)}: { load: () => import(${JSON.stringify(file)})${description ? `, description: ${JSON.stringify(description)}` : ''} },`);
85
96
  return `export const commands = {\n${rows.join('\n')}\n}\n`;
86
97
  }
98
+ /**
99
+ * The commands that did not ask for register(), as a bundle of their own.
100
+ *
101
+ * The server's entry imports instrumentation.ts first and statically - that is
102
+ * what stops a server with a bad environment before it serves - so anything
103
+ * loaded through it has validated the web's whole schema. A queue drain that
104
+ * imports only the worker's still needed the web's Stripe and session
105
+ * secrets. This module is the entry of a server environment of its own,
106
+ * bundled apart from the server's, so nothing in it can import back into the
107
+ * entry that loads instrumentation.ts. Each command's own imports still run:
108
+ * it validates what it imports, which is the point.
109
+ */
110
+ export const BARE_COMMANDS_MODULE_ID = 'virtual:rsc-kit/commands-bare';
111
+ /**
112
+ * Its name as an input of the rsc environment, and so the file the server
113
+ * runs it from: `_ssr/rsc-commands.mjs`, beside the server's `_ssr/rsc.mjs`.
114
+ */
115
+ export const BARE_COMMANDS_ENVIRONMENT = 'rsc-commands';
116
+ /** The bare table's module: runCommand over it, with no register() to give. */
117
+ export function bareCommandsModule(commands, runner) {
118
+ return (`import { runCommand as __runCommand } from ${JSON.stringify(runner)}\n` +
119
+ commandsModule(commands.filter((command) => !command.instrumented)) +
120
+ // Reached only by a register that is true at runtime but not as a literal
121
+ // the build could read, which put the command in the wrong bundle.
122
+ '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' +
123
+ 'export function runCommand(argv, program) {\n' +
124
+ ' return __runCommand(argv, { commands, program, ready: noRegister })\n' +
125
+ '}\n');
126
+ }
87
127
  /**
88
128
  * What the generated server entry adds for an app with a `src/commands`:
89
129
  * `runCommand` over the table, with the entry's own `register()` and
@@ -119,6 +159,8 @@ export function commandAnswerSource({ commands }) {
119
159
  const table = Object.fromEntries(commands.map(({ name, description }) => [name, { description }]));
120
160
  const help = commandHelp(table, '%PROGRAM%');
121
161
  return (`const COMMAND_NAMES = ${JSON.stringify(commands.map((c) => c.name))};\n` +
162
+ // Run from their own bundle, which does not import instrumentation.ts.
163
+ `const BARE_COMMANDS = ${JSON.stringify(commands.filter((c) => !c.instrumented).map((c) => c.name))};\n` +
122
164
  `const COMMAND_HELP = ${JSON.stringify(help)};\n` +
123
165
  '\n' +
124
166
  'const answer = (argv, program) => {\n' +
@@ -178,7 +220,8 @@ function earlySignalsSource() {
178
220
  */
179
221
  function loadAppSource(polyfills) {
180
222
  const loads = polyfills.map((name) => `await import(${JSON.stringify(name)});\n`).join('');
181
- return loads + 'const { runCommand } = await import("./_ssr/rsc.mjs");\n';
223
+ return (loads +
224
+ 'const { runCommand } = await (BARE_COMMANDS.includes(argv[0]) ? import("./_ssr/' + BARE_COMMANDS_ENVIRONMENT + '.mjs") : import("./_ssr/rsc.mjs"));\n');
182
225
  }
183
226
  /**
184
227
  * The entry a plain build runs a command from, written beside the server:
@@ -242,7 +285,9 @@ export function commandsEntrySource(options) {
242
285
  */
243
286
  export function compileEntryWithCommands(dirName, options) {
244
287
  // One after another, the polyfills before the entry that needs them.
245
- const chain = [...options.polyfills.map((name) => `import(${JSON.stringify(name)})`), 'import("./_ssr/rsc.mjs")']
288
+ // Both literal, so Bun embeds both; which one runs is the command's.
289
+ const app = `(BARE_COMMANDS.includes(argv[0]) ? import("./_ssr/${BARE_COMMANDS_ENVIRONMENT}.mjs") : import("./_ssr/rsc.mjs"))`;
290
+ const chain = [...options.polyfills.map((name) => `import(${JSON.stringify(name)})`), app]
246
291
  .map((load) => `.then(() => ${load})`)
247
292
  .join('\n ');
248
293
  return ('// The entry to compile into one binary:\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\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 * 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 command: CommandFile = { name, file: path, description: describe(readFileSync(path, 'utf-8')) }\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 * 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 `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 loads + 'const { runCommand } = await import(\"./_ssr/rsc.mjs\");\\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 const chain = [...options.polyfills.map((name) => `import(${JSON.stringify(name)})`), 'import(\"./_ssr/rsc.mjs\")']\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":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAC/D,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAC1D,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAuB7C,MAAM,MAAM,GAAG,qBAAqB,CAAA;AACpC,MAAM,IAAI,GAAG,+BAA+B,CAAA;AAE5C,4EAA4E;AAC5E,MAAM,eAAe,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,CAAA;AAE9E;;;;;GAKG;AACH,SAAS,QAAQ,CAAC,MAAc;IAC9B,MAAM,KAAK,GAAG,mFAAmF,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;IAE9G,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,SAAS,CAAA;AACxC,CAAC;AAED;;;;GAIG;AACH,SAAS,SAAS,CAAC,MAAc;IAC/B,OAAO,gEAAgE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;AACtF,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAAC,SAAiB;IAC5C,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,EAAE,UAAU,CAAC,CAAA;IACvC,MAAM,KAAK,GAAiB,EAAE,QAAQ,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,CAAA;IAEzD,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,KAAK,CAAA;IAElC,MAAM,KAAK,GAAG,IAAI,GAAG,EAAuB,CAAA;IAC5C,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAA;IAEtG,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,CAAA;QAElC,IAAI,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,SAAQ;QAEtE,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;YACxB,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,uEAAuE,EAAE,CAAC,CAAA;YAE7G,SAAQ;QACV,CAAC;QAED,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,gCAAgC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;YAAE,SAAQ;QAE/H,MAAM,SAAS,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAA;QACnE,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAA;QAEzD,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACrB,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,IAAI,wEAAwE,EAAE,CAAC,CAAA;YAEtH,SAAQ;QACV,CAAC;QAED,MAAM,QAAQ,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;QAEhC,IAAI,QAAQ,EAAE,CAAC;YACb,MAAM,IAAI,GAAG,eAAe,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,IAAI,eAAe,CAAC,OAAO,CAAC,SAAS,CAAC,CAAA;YAEhH,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC;gBACjB,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI;gBACjC,MAAM,EAAE,yBAAyB,IAAI,MAAM,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,iBAAiB;aACxF,CAAC,CAAA;YAEF,IAAI,IAAI;gBAAE,SAAQ;YAElB,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAA;QAC5D,CAAC;QAED,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;QAC1C,MAAM,OAAO,GAAgB,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAA;QAEhF,IAAI,SAAS,CAAC,MAAM,CAAC;YAAE,OAAO,CAAC,YAAY,GAAG,IAAI,CAAA;QAElD,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;QACxB,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;IAC9B,CAAC;IAED,OAAO,KAAK,CAAA;AACd,CAAC;AAED,8FAA8F;AAC9F,MAAM,CAAC,MAAM,kBAAkB,GAAG,0BAA0B,CAAA;AAE5D;;;;;;;;GAQG;AACH,MAAM,UAAU,cAAc,CAAC,QAAgC;IAC7D,MAAM,IAAI,GAAG,QAAQ,CAAC,GAAG,CACvB,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,EAAE,CAC9B,KAAK,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,0BAA0B,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,WAAW,CAAC,CAAC,CAAC,kBAAkB,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,CACrJ,CAAA;IAED,OAAO,8BAA8B,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAA;AAC7D,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,+BAA+B,CAAA;AAEtE;;;GAGG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,cAAc,CAAA;AAEvD,+EAA+E;AAC/E,MAAM,UAAU,kBAAkB,CAAC,QAAgC,EAAE,MAAc;IACjF,OAAO,CACL,8CAA8C,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI;QACxE,cAAc,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;QACnE,0EAA0E;QAC1E,mEAAmE;QACnE,+MAA+M;QAC/M,+CAA+C;QAC/C,yEAAyE;QACzE,KAAK,CACN,CAAA;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,MAAc;IAC1C,OAAO;yCACgC,IAAI,CAAC,SAAS,CAAC,kBAAkB,CAAC;6CAC9B,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC;;;;;;CAMlE,CAAA;AACD,CAAC;AASD;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,mBAAmB,CAAC,EAAE,QAAQ,EAAuB;IACnE,MAAM,KAAK,GAAG,MAAM,CAAC,WAAW,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,CAAA;IAClG,MAAM,IAAI,GAAG,WAAW,CAAC,KAAK,EAAE,WAAW,CAAC,CAAA;IAE5C,OAAO,CACL,yBAAyB,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK;QACzE,uEAAuE;QACvE,yBAAyB,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK;QACxG,wBAAwB,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK;QACjD,IAAI;QACJ,uCAAuC;QACvC,0BAA0B;QAC1B,iEAAiE;QACjE,IAAI;QACJ,6EAA6E;QAC7E,6DAA6D;QAC7D,8GAA8G;QAC9G,IAAI;QACJ,kBAAkB;QAClB,MAAM,CACP,CAAA;AACH,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,SAAS,kBAAkB;IACzB,OAAO,CACL,sEAAsE;QACtE,4BAA4B;QAC5B,kDAAkD;QAClD,IAAI;QACJ,uBAAuB;QACvB,IAAI;QACJ,2CAA2C;QAC3C,IAAI;QACJ,+GAA+G;QAC/G,MAAM;QACN,mCAAmC;QACnC,gCAAgC;QAChC,gCAAgC;QAChC,MAAM;QACN,oCAAoC;QACpC,mCAAmC;QACnC,yBAAyB,IAAI,CAAC,SAAS,CAAC,aAAa,CAAC,WAAW,CAAC,eAAe,CAClF,CAAA;AACH,CAAC;AAED;;;;;;;;;GASG;AACH,SAAS,aAAa,CAAC,SAA4B;IACjD,MAAM,KAAK,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,gBAAgB,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IAE1F,OAAO,CACL,KAAK;QACL,iFAAiF,GAAG,yBAAyB,GAAG,uCAAuC,CACxJ,CAAA;AACH,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,mBAAmB,CAAC,OAA4B;IAC9D,OAAO,CACL,iEAAiE;QACjE,0DAA0D;QAC1D,yDAAyD;QACzD,yCAAyC;QACzC,6CAA6C;QAC7C,IAAI;QACJ,mBAAmB,CAAC,OAAO,CAAC;QAC5B,IAAI;QACJ,uCAAuC;QACvC,4HAA4H;QAC5H,2CAA2C;QAC3C,IAAI;QACJ,mBAAmB;QACnB,mFAAmF;QACnF,uCAAuC;QACvC,YAAY;QACZ,uDAAuD;QACvD,IAAI;QACJ,MAAM,CAAC,kBAAkB,EAAE,CAAC;QAC5B,IAAI;QACJ,mBAAmB;QACnB,IAAI;QACJ,WAAW;QACX,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC;QAChD,+CAA+C;QAC/C,uBAAuB;QACvB,iFAAiF;QACjF,uCAAuC;QACvC,kEAAkE;QAClE,OAAO;QACP,IAAI;QACJ,yBAAyB;QACzB,KAAK,CACN,CAAA;AACH,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,wBAAwB,CAAC,OAAe,EAAE,OAA4B;IACpF,qEAAqE;IACrE,qEAAqE;IACrE,MAAM,GAAG,GAAG,qDAAqD,yBAAyB,oCAAoC,CAAA;IAC9H,MAAM,KAAK,GAAG,CAAC,GAAG,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,UAAU,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC;SACvF,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,eAAe,IAAI,GAAG,CAAC;SACrC,IAAI,CAAC,UAAU,CAAC,CAAA;IAEnB,OAAO,CACL,4CAA4C;QAC5C,sFAAsF;QACtF,MAAM;QACN,kFAAkF;QAClF,mFAAmF;QACnF,+EAA+E;QAC/E,uEAAuE;QACvE,MAAM;QACN,4EAA4E;QAC5E,MAAM;QACN,mFAAmF;QACnF,kFAAkF;QAClF,8EAA8E;QAC9E,6EAA6E;QAC7E,+EAA+E;QAC/E,iFAAiF;QACjF,yCAAyC;QACzC,aAAa,gBAAgB,CAAC,OAAO,CAAC,MAAM;QAC5C,IAAI;QACJ,mBAAmB,CAAC,OAAO,CAAC;QAC5B,IAAI;QACJ,yCAAyC;QACzC,iCAAiC;QACjC,sBAAsB;QACtB,MAAM;QACN,IAAI;QACJ,mCAAmC;QACnC,mFAAmF;QACnF,YAAY;QACZ,yCAAyC;QACzC,wDAAwD;QACxD,6CAA6C;QAC7C,IAAI;QACJ,qBAAqB;QACrB,qFAAqF;QACrF,yCAAyC;QACzC,cAAc;QACd,yDAAyD;QACzD,IAAI;QACJ,MAAM,CAAC,MAAM,CAAC,kBAAkB,EAAE,CAAC,CAAC;QACpC,IAAI;QACJ,yBAAyB;QACzB,SAAS,KAAK,IAAI;QAClB,8DAA8D;QAC9D,2FAA2F;QAC3F,OAAO;QACP,KAAK,CACN,CAAA;AACH,CAAC;AAED,SAAS,MAAM,CAAC,IAAY;IAC1B,OAAO,IAAI;SACR,KAAK,CAAC,IAAI,CAAC;SACX,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;SAC1C,IAAI,CAAC,IAAI,CAAC,CAAA;AACf,CAAC","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"]}
@@ -12,11 +12,12 @@
12
12
  * package and the logger the pages use are the ones it uses, in the same
13
13
  * bundle. It is loaded only when it is named: the server never evaluates one.
14
14
  *
15
- * `register()` from instrumentation.ts is skipped unless the command says
16
- * `export const register = true`. Importing instrumentation.ts is not
17
- * skipped - a bad environment fails there, at import, for a command as for a
18
- * server - but a command that only needs the schema should not pay for, or
19
- * fail on, a connection it never uses.
15
+ * instrumentation.ts is not imported unless the command says
16
+ * `export const register = true`: the build runs every other command from a
17
+ * bundle that leaves it out (see BARE_COMMANDS_MODULE_ID in commandFiles.ts).
18
+ * Its environment is the server's, and a queue drain that imports only the
19
+ * worker's should not fail on the web's secrets, nor pay for a connection it
20
+ * never uses.
20
21
  *
21
22
  * Nothing here imports `node:` anything: the runner is bundled into the
22
23
  * server's entry for every host, and a host without those modules must still
package/dist/commands.js CHANGED
@@ -12,11 +12,12 @@
12
12
  * package and the logger the pages use are the ones it uses, in the same
13
13
  * bundle. It is loaded only when it is named: the server never evaluates one.
14
14
  *
15
- * `register()` from instrumentation.ts is skipped unless the command says
16
- * `export const register = true`. Importing instrumentation.ts is not
17
- * skipped - a bad environment fails there, at import, for a command as for a
18
- * server - but a command that only needs the schema should not pay for, or
19
- * fail on, a connection it never uses.
15
+ * instrumentation.ts is not imported unless the command says
16
+ * `export const register = true`: the build runs every other command from a
17
+ * bundle that leaves it out (see BARE_COMMANDS_MODULE_ID in commandFiles.ts).
18
+ * Its environment is the server's, and a queue drain that imports only the
19
+ * worker's should not fail on the web's secrets, nor pay for a connection it
20
+ * never uses.
20
21
  *
21
22
  * Nothing here imports `node:` anything: the runner is bundled into the
22
23
  * server's entry for every host, and a host without those modules must still
@@ -1 +1 @@
1
- {"version":3,"file":"commands.js","sourceRoot":"","sources":["../src/commands.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * App commands: the files in `src/commands`, run as `./app <name>`.\n *\n * A compiled app is one binary that starts a server, and a release step or a\n * queue worker is the same code started differently - `./app migrate`,\n * `./app work`. The same entry also runs from a plain build, as\n * `node .output/server/commands.mjs <name>`, so nothing about a command\n * depends on Bun, on a platform, or on the binary.\n *\n * Each command is a file with a default export, `(args, context) => number\n * | void`, where `context` is `{ signal, deadline }`. It shares the build's code and dependencies, so the database\n * package and the logger the pages use are the ones it uses, in the same\n * bundle. It is loaded only when it is named: the server never evaluates one.\n *\n * `register()` from instrumentation.ts is skipped unless the command says\n * `export const register = true`. Importing instrumentation.ts is not\n * skipped - a bad environment fails there, at import, for a command as for a\n * server - but a command that only needs the schema should not pay for, or\n * fail on, a connection it never uses.\n *\n * Nothing here imports `node:` anything: the runner is bundled into the\n * server's entry for every host, and a host without those modules must still\n * load it. `process` is reached lazily, inside the functions that need it.\n */\n\n/**\n * Set on `globalThis` before the server's entry is imported, by whatever runs\n * a command. The entry would otherwise call register() the moment it loads, as\n * a server does at startup, which a command has not asked for.\n */\nexport const COMMAND_MODE = Symbol.for('rsc-kit.command')\n\n/** What a command receives beside its arguments. */\nexport interface CommandContext {\n /** Aborted by SIGTERM or SIGINT: a long-running command stops when it is. */\n signal: AbortSignal\n /**\n * When the runner stops waiting for the command, as a time in milliseconds\n * (`Date.now()`'s), once it has been told to stop; undefined until then.\n * A worker finishing its job reads `deadline - Date.now()` rather than\n * RSC_SHUTDOWN_TIMEOUT and its default.\n *\n * A getter, because what it answers changes: read `context.deadline` when\n * you need it. `({ deadline }) => ...` reads it once, as the command\n * starts, when it is always undefined, and keeps that.\n */\n readonly deadline: number | undefined\n}\n\n/** A command's default export. */\nexport type CommandFunction = (args: string[], context: CommandContext) => number | void | Promise<number | void>\n\n/**\n * Types a command's default export, so its arguments and context are known\n * without annotating them: `export default defineCommand(async (args, context) => {})`.\n * It returns what it is given.\n */\nexport function defineCommand(command: CommandFunction): CommandFunction {\n return command\n}\n\n/** Resolves after the event loop has taken a turn, which is when it hands out the signals that arrived while it was busy. */\nconst nextTurn = () => new Promise<void>((resolve) => (typeof setImmediate === 'function' ? setImmediate(resolve) : setTimeout(resolve, 0)))\n\n/**\n * The entry that loads the app owns the process's SIGTERM and SIGINT, and\n * leaves this on `globalThis` for the runner.\n *\n * Loading can take a while, and a signal before the runner exists finds only\n * the default action, which ends the process with 143. So the entry holds\n * them, and the runner is handed over to: the count of those that came first,\n * and every later one. The entry's listener is never taken off. A signal is\n * delivered on the event loop's next turn, not when it arrives, and a listener\n * removed in between has the signal taken from it without anyone getting it.\n */\nexport const EARLY_SIGNALS = Symbol.for('rsc-kit.command.signals')\n\ninterface EarlySignals {\n /** Stops that arrived before there was a runner to give them to. */\n count: number\n /** From now on, the entry gives each signal to this, and stops holding the process to its own timeout. */\n handoff: (listener: (() => void) | undefined) => void\n}\n\n/** What the build knows of one command file: how to load it, and a line to list it by. */\nexport interface CommandEntry {\n load: () => Promise<{ default?: unknown; register?: unknown }>\n description?: string\n}\n\nexport interface RunCommandOptions {\n commands: Record<string, CommandEntry>\n /** instrumentation.ts's register(), once. Awaited only for a command that exports `register = true`. */\n ready: () => Promise<unknown>\n /** instrumentation.ts's shutdown(), called after a command that was given `register()`. */\n shutdown?: () => unknown\n /** How the usage line names this program: `./app`, `node .output/server/commands.mjs`. */\n program?: string\n out?: (text: string) => void\n err?: (text: string) => void\n /** SIGTERM and SIGINT. The process, unless a test brings its own. */\n signals?: {\n on: (signal: 'SIGTERM' | 'SIGINT', listener: () => void) => unknown\n off: (signal: 'SIGTERM' | 'SIGINT', listener: () => void) => unknown\n }\n /** Milliseconds a command gets to stop once told to, and shutdown() after it. RSC_SHUTDOWN_TIMEOUT seconds (10) by default. */\n limit?: number\n}\n\n/** Whether this process was started to run a command rather than to serve. */\nexport function inCommandMode(): boolean {\n return Boolean((globalThis as Record<symbol, unknown>)[COMMAND_MODE])\n}\n\n/** The list `--help` prints, and what an unknown name is answered with. */\nexport function commandHelp(commands: Record<string, { description?: string }>, program = 'app'): string {\n const names = Object.keys(commands).sort()\n const width = Math.max(0, ...names.map((name) => name.length))\n const rows = names.map((name) => {\n const description = commands[name]?.description\n\n return description ? ` ${name.padEnd(width)} ${description}` : ` ${name}`\n })\n\n return (\n `Usage: ${program} <command> [args]\\n\\n` +\n (rows.length ? `Commands:\\n${rows.join('\\n')}\\n` : 'There are no commands: add a file to src/commands.\\n')\n )\n}\n\n/**\n * An exit code from what a command returned. Nothing is success; a number is\n * the code. Anything else is the command's mistake, and said so, because the\n * alternative is a process that exits 0 after returning a string.\n */\nfunction exitCodeOf(result: unknown, name: string): number {\n if (result === undefined) return 0\n\n if (typeof result === 'number' && Number.isInteger(result) && result >= 0 && result <= 255) return result\n\n throw new TypeError(\n `the command \"${name}\" returned ${JSON.stringify(result)}. A command returns an exit code from 0 to 255, or nothing.`,\n )\n}\n\nfunction unref(timer: unknown): void {\n const handle = timer as { unref?: () => void } | undefined\n\n if (handle && typeof handle.unref === 'function') handle.unref()\n}\n\n/** The seconds a command and its shutdown() get after a signal, in milliseconds. */\nfunction defaultLimit(): number {\n const seconds = Number(typeof process !== 'undefined' ? process.env.RSC_SHUTDOWN_TIMEOUT : undefined)\n\n return (Number.isFinite(seconds) && seconds > 0 ? seconds : 10) * 1000\n}\n\n/**\n * Run the command `argv` names and say how it ended.\n *\n * Resolves to the exit code and leaves exiting to the caller, so a test sees\n * the code and an entry decides when the process stops. 0 is success and 1 is\n * a command that threw or would not stop; 2 is a name that is not a command,\n * as it is for a tool that was misused.\n */\nexport async function runCommand(argv: readonly string[], options: RunCommandOptions): Promise<number> {\n const out = options.out ?? ((text: string) => void process.stdout.write(text))\n const err = options.err ?? ((text: string) => void process.stderr.write(text))\n const [name, ...args] = argv\n\n if (name === '--help' || name === '-h') {\n out(commandHelp(options.commands, options.program))\n\n return 0\n }\n\n if (name === undefined) {\n err(commandHelp(options.commands, options.program))\n\n return 2\n }\n\n const command = Object.hasOwn(options.commands, name) ? options.commands[name] : undefined\n\n if (!command) {\n err(`Not a command: ${name}\\n\\n${commandHelp(options.commands, options.program)}`)\n\n return 2\n }\n\n const limit = options.limit ?? defaultLimit()\n const signals = options.signals ?? (typeof process !== 'undefined' ? process : undefined)\n const controller = new AbortController()\n\n let registered = false\n let stop: ((code: number) => void) | undefined\n let stopTimer: unknown\n let deadline: number | undefined\n let signalled = 0\n const context: CommandContext = {\n signal: controller.signal,\n get deadline() {\n return deadline\n },\n }\n\n // The first signal asks the command to stop and gives it `limit` to do so,\n // as the server gives in-flight requests theirs. The second is a person who\n // meant it, and nothing waits.\n const onSignal = () => {\n signalled += 1\n\n if (signalled === 1) {\n deadline = Date.now() + limit\n controller.abort()\n\n stopTimer = setTimeout(() => {\n err(`[rsc-kit] the command \"${name}\" did not stop within ${limit / 1000} seconds.\\n`)\n stop?.(1)\n }, limit)\n\n unref(stopTimer)\n\n return\n }\n\n stop?.(1)\n }\n\n const stopped = new Promise<number>((resolve) => {\n stop = resolve\n })\n\n // Behind an entry that loaded the app, the process's signals are the\n // entry's: it hands each one over, and what came while the app was loading\n // is counted here. Told to stop while loading is told to stop.\n const early = (globalThis as Record<symbol, EarlySignals | undefined>)[EARLY_SIGNALS]\n\n if (early) {\n early.handoff(onSignal)\n\n for (let caught = 0; caught < early.count; caught++) onSignal()\n } else {\n signals?.on('SIGTERM', onSignal)\n signals?.on('SIGINT', onSignal)\n }\n\n // A command that is only waiting - for its signal, for a message that has\n // not come - holds nothing open, and Node answers an event loop with nothing\n // in it by exiting 13 with a warning about an unsettled await, where Bun\n // simply waits. A command that is running is running until it returns.\n const alive = setInterval(() => {}, 2 ** 30)\n\n const ran = (async (): Promise<number> => {\n try {\n const module = await command.load()\n\n if (typeof module.default !== 'function') {\n throw new TypeError(`src/commands/${name} has no default export. Export a function: export default async (args) => {}`)\n }\n\n // Opted in, and only then: a migration that needs the schema should not\n // fail because the queue it never touches is not up.\n if (module.register === true) {\n await options.ready()\n registered = true\n }\n\n // A stop that arrived while the loop was busy with a synchronous load has\n // not been delivered: a signal is delivered on the loop's next turn. One\n // turn here, so that the command is told before it starts rather than a\n // moment after - the same on Node, on Bun and in a binary.\n await nextTurn()\n\n return exitCodeOf(await (module.default as (...a: unknown[]) => unknown)(args, context), name)\n } catch (error) {\n err(`[rsc-kit] the command \"${name}\" failed: ${describeError(error, true)}\\n`)\n\n return 1\n }\n })()\n\n let code: number\n\n try {\n code = await Promise.race([ran, stopped])\n } finally {\n clearInterval(alive)\n clearTimeout(stopTimer as Parameters<typeof clearTimeout>[0])\n\n if (early) early.handoff(undefined)\n else {\n signals?.off('SIGTERM', onSignal)\n signals?.off('SIGINT', onSignal)\n }\n }\n\n // What register() opened is closed here, bounded like the server's: a\n // close that hangs must not hold a release step open.\n if (registered && options.shutdown) {\n let timer: unknown\n\n await Promise.race([\n Promise.resolve()\n .then(() => options.shutdown?.())\n .catch((error) => err(`[rsc-kit] instrumentation.ts shutdown() failed: ${describeError(error, false)}\\n`)),\n new Promise((resolve) => {\n timer = setTimeout(resolve, limit)\n unref(timer)\n }),\n ])\n\n clearTimeout(timer as Parameters<typeof clearTimeout>[0])\n }\n\n return code\n}\n\n/**\n * An error and every cause under it, one line of `[cause]:` each.\n *\n * A library that wraps an error keeps the reason in `cause`, and a stack does\n * not print it: Drizzle's DrizzleQueryError carries the SQL as its message and\n * the database's refusal - a timeout, a wrong password - as its cause, so a\n * failed migration said what it tried and never why. A cause that is not an\n * Error is printed as it is, and a chain that loops back stops there.\n */\nfunction describeError(error: unknown, stack: boolean): string {\n const seen = new Set<unknown>()\n const parts: string[] = []\n let current: unknown = error\n\n while (current !== undefined && current !== null && !seen.has(current)) {\n seen.add(current)\n parts.push(\n current instanceof Error ? (stack ? (current.stack ?? `${current.name}: ${current.message}`) : current.message) : String(current),\n )\n current = current instanceof Error ? current.cause : undefined\n }\n\n return parts.join('\\n[cause]: ')\n}\n"]}
1
+ {"version":3,"file":"commands.js","sourceRoot":"","sources":["../src/commands.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * App commands: the files in `src/commands`, run as `./app <name>`.\n *\n * A compiled app is one binary that starts a server, and a release step or a\n * queue worker is the same code started differently - `./app migrate`,\n * `./app work`. The same entry also runs from a plain build, as\n * `node .output/server/commands.mjs <name>`, so nothing about a command\n * depends on Bun, on a platform, or on the binary.\n *\n * Each command is a file with a default export, `(args, context) => number\n * | void`, where `context` is `{ signal, deadline }`. It shares the build's code and dependencies, so the database\n * package and the logger the pages use are the ones it uses, in the same\n * bundle. It is loaded only when it is named: the server never evaluates one.\n *\n * instrumentation.ts is not imported unless the command says\n * `export const register = true`: the build runs every other command from a\n * bundle that leaves it out (see BARE_COMMANDS_MODULE_ID in commandFiles.ts).\n * Its environment is the server's, and a queue drain that imports only the\n * worker's should not fail on the web's secrets, nor pay for a connection it\n * never uses.\n *\n * Nothing here imports `node:` anything: the runner is bundled into the\n * server's entry for every host, and a host without those modules must still\n * load it. `process` is reached lazily, inside the functions that need it.\n */\n\n/**\n * Set on `globalThis` before the server's entry is imported, by whatever runs\n * a command. The entry would otherwise call register() the moment it loads, as\n * a server does at startup, which a command has not asked for.\n */\nexport const COMMAND_MODE = Symbol.for('rsc-kit.command')\n\n/** What a command receives beside its arguments. */\nexport interface CommandContext {\n /** Aborted by SIGTERM or SIGINT: a long-running command stops when it is. */\n signal: AbortSignal\n /**\n * When the runner stops waiting for the command, as a time in milliseconds\n * (`Date.now()`'s), once it has been told to stop; undefined until then.\n * A worker finishing its job reads `deadline - Date.now()` rather than\n * RSC_SHUTDOWN_TIMEOUT and its default.\n *\n * A getter, because what it answers changes: read `context.deadline` when\n * you need it. `({ deadline }) => ...` reads it once, as the command\n * starts, when it is always undefined, and keeps that.\n */\n readonly deadline: number | undefined\n}\n\n/** A command's default export. */\nexport type CommandFunction = (args: string[], context: CommandContext) => number | void | Promise<number | void>\n\n/**\n * Types a command's default export, so its arguments and context are known\n * without annotating them: `export default defineCommand(async (args, context) => {})`.\n * It returns what it is given.\n */\nexport function defineCommand(command: CommandFunction): CommandFunction {\n return command\n}\n\n/** Resolves after the event loop has taken a turn, which is when it hands out the signals that arrived while it was busy. */\nconst nextTurn = () => new Promise<void>((resolve) => (typeof setImmediate === 'function' ? setImmediate(resolve) : setTimeout(resolve, 0)))\n\n/**\n * The entry that loads the app owns the process's SIGTERM and SIGINT, and\n * leaves this on `globalThis` for the runner.\n *\n * Loading can take a while, and a signal before the runner exists finds only\n * the default action, which ends the process with 143. So the entry holds\n * them, and the runner is handed over to: the count of those that came first,\n * and every later one. The entry's listener is never taken off. A signal is\n * delivered on the event loop's next turn, not when it arrives, and a listener\n * removed in between has the signal taken from it without anyone getting it.\n */\nexport const EARLY_SIGNALS = Symbol.for('rsc-kit.command.signals')\n\ninterface EarlySignals {\n /** Stops that arrived before there was a runner to give them to. */\n count: number\n /** From now on, the entry gives each signal to this, and stops holding the process to its own timeout. */\n handoff: (listener: (() => void) | undefined) => void\n}\n\n/** What the build knows of one command file: how to load it, and a line to list it by. */\nexport interface CommandEntry {\n load: () => Promise<{ default?: unknown; register?: unknown }>\n description?: string\n}\n\nexport interface RunCommandOptions {\n commands: Record<string, CommandEntry>\n /** instrumentation.ts's register(), once. Awaited only for a command that exports `register = true`. */\n ready: () => Promise<unknown>\n /** instrumentation.ts's shutdown(), called after a command that was given `register()`. */\n shutdown?: () => unknown\n /** How the usage line names this program: `./app`, `node .output/server/commands.mjs`. */\n program?: string\n out?: (text: string) => void\n err?: (text: string) => void\n /** SIGTERM and SIGINT. The process, unless a test brings its own. */\n signals?: {\n on: (signal: 'SIGTERM' | 'SIGINT', listener: () => void) => unknown\n off: (signal: 'SIGTERM' | 'SIGINT', listener: () => void) => unknown\n }\n /** Milliseconds a command gets to stop once told to, and shutdown() after it. RSC_SHUTDOWN_TIMEOUT seconds (10) by default. */\n limit?: number\n}\n\n/** Whether this process was started to run a command rather than to serve. */\nexport function inCommandMode(): boolean {\n return Boolean((globalThis as Record<symbol, unknown>)[COMMAND_MODE])\n}\n\n/** The list `--help` prints, and what an unknown name is answered with. */\nexport function commandHelp(commands: Record<string, { description?: string }>, program = 'app'): string {\n const names = Object.keys(commands).sort()\n const width = Math.max(0, ...names.map((name) => name.length))\n const rows = names.map((name) => {\n const description = commands[name]?.description\n\n return description ? ` ${name.padEnd(width)} ${description}` : ` ${name}`\n })\n\n return (\n `Usage: ${program} <command> [args]\\n\\n` +\n (rows.length ? `Commands:\\n${rows.join('\\n')}\\n` : 'There are no commands: add a file to src/commands.\\n')\n )\n}\n\n/**\n * An exit code from what a command returned. Nothing is success; a number is\n * the code. Anything else is the command's mistake, and said so, because the\n * alternative is a process that exits 0 after returning a string.\n */\nfunction exitCodeOf(result: unknown, name: string): number {\n if (result === undefined) return 0\n\n if (typeof result === 'number' && Number.isInteger(result) && result >= 0 && result <= 255) return result\n\n throw new TypeError(\n `the command \"${name}\" returned ${JSON.stringify(result)}. A command returns an exit code from 0 to 255, or nothing.`,\n )\n}\n\nfunction unref(timer: unknown): void {\n const handle = timer as { unref?: () => void } | undefined\n\n if (handle && typeof handle.unref === 'function') handle.unref()\n}\n\n/** The seconds a command and its shutdown() get after a signal, in milliseconds. */\nfunction defaultLimit(): number {\n const seconds = Number(typeof process !== 'undefined' ? process.env.RSC_SHUTDOWN_TIMEOUT : undefined)\n\n return (Number.isFinite(seconds) && seconds > 0 ? seconds : 10) * 1000\n}\n\n/**\n * Run the command `argv` names and say how it ended.\n *\n * Resolves to the exit code and leaves exiting to the caller, so a test sees\n * the code and an entry decides when the process stops. 0 is success and 1 is\n * a command that threw or would not stop; 2 is a name that is not a command,\n * as it is for a tool that was misused.\n */\nexport async function runCommand(argv: readonly string[], options: RunCommandOptions): Promise<number> {\n const out = options.out ?? ((text: string) => void process.stdout.write(text))\n const err = options.err ?? ((text: string) => void process.stderr.write(text))\n const [name, ...args] = argv\n\n if (name === '--help' || name === '-h') {\n out(commandHelp(options.commands, options.program))\n\n return 0\n }\n\n if (name === undefined) {\n err(commandHelp(options.commands, options.program))\n\n return 2\n }\n\n const command = Object.hasOwn(options.commands, name) ? options.commands[name] : undefined\n\n if (!command) {\n err(`Not a command: ${name}\\n\\n${commandHelp(options.commands, options.program)}`)\n\n return 2\n }\n\n const limit = options.limit ?? defaultLimit()\n const signals = options.signals ?? (typeof process !== 'undefined' ? process : undefined)\n const controller = new AbortController()\n\n let registered = false\n let stop: ((code: number) => void) | undefined\n let stopTimer: unknown\n let deadline: number | undefined\n let signalled = 0\n const context: CommandContext = {\n signal: controller.signal,\n get deadline() {\n return deadline\n },\n }\n\n // The first signal asks the command to stop and gives it `limit` to do so,\n // as the server gives in-flight requests theirs. The second is a person who\n // meant it, and nothing waits.\n const onSignal = () => {\n signalled += 1\n\n if (signalled === 1) {\n deadline = Date.now() + limit\n controller.abort()\n\n stopTimer = setTimeout(() => {\n err(`[rsc-kit] the command \"${name}\" did not stop within ${limit / 1000} seconds.\\n`)\n stop?.(1)\n }, limit)\n\n unref(stopTimer)\n\n return\n }\n\n stop?.(1)\n }\n\n const stopped = new Promise<number>((resolve) => {\n stop = resolve\n })\n\n // Behind an entry that loaded the app, the process's signals are the\n // entry's: it hands each one over, and what came while the app was loading\n // is counted here. Told to stop while loading is told to stop.\n const early = (globalThis as Record<symbol, EarlySignals | undefined>)[EARLY_SIGNALS]\n\n if (early) {\n early.handoff(onSignal)\n\n for (let caught = 0; caught < early.count; caught++) onSignal()\n } else {\n signals?.on('SIGTERM', onSignal)\n signals?.on('SIGINT', onSignal)\n }\n\n // A command that is only waiting - for its signal, for a message that has\n // not come - holds nothing open, and Node answers an event loop with nothing\n // in it by exiting 13 with a warning about an unsettled await, where Bun\n // simply waits. A command that is running is running until it returns.\n const alive = setInterval(() => {}, 2 ** 30)\n\n const ran = (async (): Promise<number> => {\n try {\n const module = await command.load()\n\n if (typeof module.default !== 'function') {\n throw new TypeError(`src/commands/${name} has no default export. Export a function: export default async (args) => {}`)\n }\n\n // Opted in, and only then: a migration that needs the schema should not\n // fail because the queue it never touches is not up.\n if (module.register === true) {\n await options.ready()\n registered = true\n }\n\n // A stop that arrived while the loop was busy with a synchronous load has\n // not been delivered: a signal is delivered on the loop's next turn. One\n // turn here, so that the command is told before it starts rather than a\n // moment after - the same on Node, on Bun and in a binary.\n await nextTurn()\n\n return exitCodeOf(await (module.default as (...a: unknown[]) => unknown)(args, context), name)\n } catch (error) {\n err(`[rsc-kit] the command \"${name}\" failed: ${describeError(error, true)}\\n`)\n\n return 1\n }\n })()\n\n let code: number\n\n try {\n code = await Promise.race([ran, stopped])\n } finally {\n clearInterval(alive)\n clearTimeout(stopTimer as Parameters<typeof clearTimeout>[0])\n\n if (early) early.handoff(undefined)\n else {\n signals?.off('SIGTERM', onSignal)\n signals?.off('SIGINT', onSignal)\n }\n }\n\n // What register() opened is closed here, bounded like the server's: a\n // close that hangs must not hold a release step open.\n if (registered && options.shutdown) {\n let timer: unknown\n\n await Promise.race([\n Promise.resolve()\n .then(() => options.shutdown?.())\n .catch((error) => err(`[rsc-kit] instrumentation.ts shutdown() failed: ${describeError(error, false)}\\n`)),\n new Promise((resolve) => {\n timer = setTimeout(resolve, limit)\n unref(timer)\n }),\n ])\n\n clearTimeout(timer as Parameters<typeof clearTimeout>[0])\n }\n\n return code\n}\n\n/**\n * An error and every cause under it, one line of `[cause]:` each.\n *\n * A library that wraps an error keeps the reason in `cause`, and a stack does\n * not print it: Drizzle's DrizzleQueryError carries the SQL as its message and\n * the database's refusal - a timeout, a wrong password - as its cause, so a\n * failed migration said what it tried and never why. A cause that is not an\n * Error is printed as it is, and a chain that loops back stops there.\n */\nfunction describeError(error: unknown, stack: boolean): string {\n const seen = new Set<unknown>()\n const parts: string[] = []\n let current: unknown = error\n\n while (current !== undefined && current !== null && !seen.has(current)) {\n seen.add(current)\n parts.push(\n current instanceof Error ? (stack ? (current.stack ?? `${current.name}: ${current.message}`) : current.message) : String(current),\n )\n current = current instanceof Error ? current.cause : undefined\n }\n\n return parts.join('\\n[cause]: ')\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
3
  /**
4
4
  * The point in the tree a navigation can replace on its own.
5
5
  *
@@ -43,6 +43,7 @@ export const NAVIGATION_TRANSITION_TYPE = "rsc-navigation";
43
43
  const addTransitionType = React.addTransitionType ??
44
44
  React
45
45
  .unstable_addTransitionType;
46
+ import { SegmentDepthContext } from "./segmentDepth";
46
47
  import { RedirectBoundary } from "./RedirectBoundary";
47
48
  import { retentionKey } from "../routing";
48
49
  import { navigationCommitted } from "./perf";
@@ -131,9 +132,6 @@ export function SegmentBoundary({ depth, pageKey, children, }) {
131
132
  // found /agent/tools/3 freezing the tab on every document load. The
132
133
  // markup of an Activity does not depend on its key, so the shell and the
133
134
  // client agree whenever both render one.
134
- if (!state) {
135
- return (_jsx(Activity, { mode: "visible", children: _jsx(RedirectBoundary, { children: children }) }, key));
136
- }
137
- return (_jsx(_Fragment, { children: state.entries.map((entry) => (_jsx(Activity, { mode: entry.key === state.activeKey ? "visible" : "hidden", children: _jsx(RedirectBoundary, { active: entry.key === state.activeKey, children: entry.tree }) }, entry.identity ?? entry.key))) }));
135
+ return (_jsx(SegmentDepthContext, { value: depth, children: !state ? (_jsx(Activity, { mode: "visible", children: _jsx(RedirectBoundary, { children: children }) }, key)) : state.entries.map((entry) => (_jsx(Activity, { mode: entry.key === state.activeKey ? "visible" : "hidden", children: _jsx(RedirectBoundary, { active: entry.key === state.activeKey, children: entry.tree }) }, entry.identity ?? entry.key))) }));
138
136
  }
139
137
  //# sourceMappingURL=SegmentBoundary.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"SegmentBoundary.js","sourceRoot":"","sources":["../../src/js/SegmentBoundary.tsx"],"names":[],"mappings":"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","sourcesContent":["\"use client\";\n\n/**\n * The point in the tree a navigation can replace on its own.\n *\n * Sits between a layout and its children, so showing a different segment\n * re-renders from here down and leaves the layouts above it mounted. Server\n * components cannot be re-rendered on the client, which is why the seam has to\n * be a client component reading from a store rather than the layout itself.\n *\n * Pages it has already shown stay mounted behind <Activity mode=\"hidden\">.\n * Hidden is not unmounted: effects are torn down but state survives, so\n * returning to a page brings back the form you were filling in. Rendering only\n * the current one would throw that away, which is what replacing the root did.\n */\n\nimport * as React from \"react\";\nimport { Activity, startTransition, useEffect, useState } from \"react\";\n\n/**\n * The transition type a navigation carries.\n *\n * The engine does not animate a navigation itself; React's own\n * <ViewTransition> does, wherever an app puts one. What the app cannot do\n * from outside is tell a navigation from the seed after hydration - the\n * boundary taking over its own server-rendered children, which animated\n * would be the page fading into itself on every load. So a navigation's\n * update carries this type, and the seed's does not: a boundary keyed on it,\n * `default={{ \"rsc-navigation\": \"page\", default: \"none\" }}`, animates\n * navigations and nothing else.\n *\n * An earlier shape wrapped the segment in a boundary of the engine's own,\n * behind a flag. React names every top-level element under a boundary and\n * morphs each from where it was to where it is, so the stylesheet pulled\n * them back into one root fade - and fought React over the root, which it\n * cancels when nothing outside a boundary changed: a captured root with a\n * hidden group, a dark viewport on the first navigation. One wrapper element\n * under the app's own boundary is one snapshot pair, which is the dissolve\n * wanted, with nothing to fight.\n */\nexport const NAVIGATION_TRANSITION_TYPE = \"rsc-navigation\";\n\n// Exported by React 19.3; `unstable_` before it. Read at call time from the\n// namespace rather than imported by name, so a React that lacks it still\n// loads this module and simply does not mark the type.\nconst addTransitionType: ((type: string) => void) | undefined =\n (React as { addTransitionType?: (type: string) => void }).addTransitionType ??\n (React as { unstable_addTransitionType?: (type: string) => void })\n .unstable_addTransitionType;\nimport type { ReactNode } from \"react\";\nimport { RedirectBoundary } from \"./RedirectBoundary\";\nimport { retentionKey } from \"../routing\";\nimport { navigationCommitted } from \"./perf\";\nimport {\n getSegmentState,\n replaceActive,\n navigatedOnce,\n seedSegment,\n subscribeToSegment,\n} from \"./segmentStore\";\n\n/**\n * The key the page this document arrived with is filed under: the url's,\n * query included, when the url is that page - which is always, in an app,\n * because the server keys a page by the document's own pathname. Anything\n * else keeps the server's key rather than guess.\n */\nfunction seedKey(pageKey: string): string {\n if (typeof window === \"undefined\") return pageKey;\n\n return retentionKey(window.location.pathname, null) === retentionKey(pageKey, null)\n ? retentionKey(window.location.pathname + window.location.search, null)\n : pageKey;\n}\n\nexport function SegmentBoundary({\n depth,\n pageKey,\n children,\n}: {\n depth: number;\n /** The page these server-rendered children belong to. */\n pageKey?: string;\n children: ReactNode;\n}) {\n // The store still addresses the boundary; the render reads React state, so\n // the update can be a transition. Initialised from the store on the client\n // and null on the server — exactly what getServerSnapshot did.\n const [state, setState] = useState<ReturnType<typeof getSegmentState>>(() =>\n typeof window === \"undefined\" ? null : getSegmentState(depth),\n );\n\n // A pattern shell is rendered for every url its route matches, so its tree\n // carries no key - and neither does the payload its resume streams in. The\n // page is still this url's: filed under it, read once, at the document's\n // own boot.\n const [fallback] = useState(() =>\n typeof window !== \"undefined\" && !navigatedOnce() ? window.location.pathname : \"\",\n );\n const key = pageKey || fallback;\n\n useEffect(\n () =>\n subscribeToSegment(depth, () => {\n startTransition(() => {\n // A navigation, not the seed: the seed is the page taking over\n // its own server-rendered children, and fading that is the page\n // fading into itself.\n if (navigatedOnce()) addTransitionType?.(NAVIGATION_TRANSITION_TYPE);\n\n setState(getSegmentState(depth));\n });\n }),\n [depth],\n );\n\n // The commit that put a navigated segment on screen: the end of the\n // navigation's measure, see perf.ts. Not on the first render, which is the\n // seed.\n useEffect(() => {\n if (state !== null) navigationCommitted();\n }, [state]);\n\n // Record the page we arrived on, so a later navigation away and back can\n // return to it. Without this the first page is the one page you cannot keep.\n // New children while the store already holds this depth is the document\n // rendered again in place - revalidate(\"all\") - and the page on screen\n // takes them under the key it has; see replaceActive.\n useEffect(() => {\n if (getSegmentState(depth)) replaceActive(depth, children);\n // Filed under the url's key, path and query, which is what every\n // navigation and refresh looks it up by; mounted under the server's, the\n // pathname the shell was frozen with. Seeded under the server's alone,\n // the first refresh of a page loaded at ?c=25 found nothing, mounted a\n // second copy, and showed every fallback in it.\n else if (key) seedSegment(depth, seedKey(key), children, key);\n }, [depth, key, children]);\n\n // Wrapped here rather than around the whole app because this is the closest\n // client component above a page: a redirect thrown inside the page's own\n // Suspense boundary surfaces at the nearest error boundary, and catching it\n // here leaves the layouts above mounted while the navigation runs. One\n // boundary per page, inside its Activity, not one around them all: a\n // boundary that catches renders its fallback in place of everything it\n // wraps, and one around them all unmounted every page being kept alive\n // behind the one redirecting - the destination among them, when it was\n // held - and then kept showing the fallback over the destination once it\n // arrived. The port's report was a sidebar and a breadcrumb over an empty\n // main.\n // The same shape before and after the seed. The server's render and the\n // first client render already place the children inside the retention\n // wrapper, keyed by the page, so when the store takes over after hydration\n // the tree changes state but not shape - and React keeps the DOM instead of\n // remounting the page, which was blank, then content, on every load.\n //\n // The Activity is there whether or not there is a page key to hang it on.\n // A parameterised route's PPR shell is rendered for every url it matches\n // and so carries no key - and the boundary used to leave the Activity out\n // then, so the shell had no <!--&--> markers where the client, hydrating\n // from the payload for the real url, rendered one. React 19.2 does not\n // recover from a hydration mismatch at an Activity: the boundary retries\n // hydrating, mismatches again, and the main thread never returns. A port\n // found /agent/tools/3 freezing the tab on every document load. The\n // markup of an Activity does not depend on its key, so the shell and the\n // client agree whenever both render one.\n if (!state) {\n return (\n <Activity key={key} mode=\"visible\">\n <RedirectBoundary>{children}</RedirectBoundary>\n </Activity>\n );\n }\n\n return (\n <>\n {state.entries.map((entry) => (\n <Activity\n key={entry.identity ?? entry.key}\n mode={entry.key === state.activeKey ? \"visible\" : \"hidden\"}\n >\n <RedirectBoundary active={entry.key === state.activeKey}>\n {entry.tree as ReactNode}\n </RedirectBoundary>\n </Activity>\n ))}\n </>\n );\n}\n"]}
1
+ {"version":3,"file":"SegmentBoundary.js","sourceRoot":"","sources":["../../src/js/SegmentBoundary.tsx"],"names":[],"mappings":"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","sourcesContent":["\"use client\";\n\n/**\n * The point in the tree a navigation can replace on its own.\n *\n * Sits between a layout and its children, so showing a different segment\n * re-renders from here down and leaves the layouts above it mounted. Server\n * components cannot be re-rendered on the client, which is why the seam has to\n * be a client component reading from a store rather than the layout itself.\n *\n * Pages it has already shown stay mounted behind <Activity mode=\"hidden\">.\n * Hidden is not unmounted: effects are torn down but state survives, so\n * returning to a page brings back the form you were filling in. Rendering only\n * the current one would throw that away, which is what replacing the root did.\n */\n\nimport * as React from \"react\";\nimport { Activity, startTransition, useEffect, useState } from \"react\";\n\n/**\n * The transition type a navigation carries.\n *\n * The engine does not animate a navigation itself; React's own\n * <ViewTransition> does, wherever an app puts one. What the app cannot do\n * from outside is tell a navigation from the seed after hydration - the\n * boundary taking over its own server-rendered children, which animated\n * would be the page fading into itself on every load. So a navigation's\n * update carries this type, and the seed's does not: a boundary keyed on it,\n * `default={{ \"rsc-navigation\": \"page\", default: \"none\" }}`, animates\n * navigations and nothing else.\n *\n * An earlier shape wrapped the segment in a boundary of the engine's own,\n * behind a flag. React names every top-level element under a boundary and\n * morphs each from where it was to where it is, so the stylesheet pulled\n * them back into one root fade - and fought React over the root, which it\n * cancels when nothing outside a boundary changed: a captured root with a\n * hidden group, a dark viewport on the first navigation. One wrapper element\n * under the app's own boundary is one snapshot pair, which is the dissolve\n * wanted, with nothing to fight.\n */\nexport const NAVIGATION_TRANSITION_TYPE = \"rsc-navigation\";\n\n// Exported by React 19.3; `unstable_` before it. Read at call time from the\n// namespace rather than imported by name, so a React that lacks it still\n// loads this module and simply does not mark the type.\nconst addTransitionType: ((type: string) => void) | undefined =\n (React as { addTransitionType?: (type: string) => void }).addTransitionType ??\n (React as { unstable_addTransitionType?: (type: string) => void })\n .unstable_addTransitionType;\nimport type { ReactNode } from \"react\";\nimport { SegmentDepthContext } from \"./segmentDepth\";\nimport { RedirectBoundary } from \"./RedirectBoundary\";\nimport { retentionKey } from \"../routing\";\nimport { navigationCommitted } from \"./perf\";\nimport {\n getSegmentState,\n replaceActive,\n navigatedOnce,\n seedSegment,\n subscribeToSegment,\n} from \"./segmentStore\";\n\n/**\n * The key the page this document arrived with is filed under: the url's,\n * query included, when the url is that page - which is always, in an app,\n * because the server keys a page by the document's own pathname. Anything\n * else keeps the server's key rather than guess.\n */\nfunction seedKey(pageKey: string): string {\n if (typeof window === \"undefined\") return pageKey;\n\n return retentionKey(window.location.pathname, null) === retentionKey(pageKey, null)\n ? retentionKey(window.location.pathname + window.location.search, null)\n : pageKey;\n}\n\nexport function SegmentBoundary({\n depth,\n pageKey,\n children,\n}: {\n depth: number;\n /** The page these server-rendered children belong to. */\n pageKey?: string;\n children: ReactNode;\n}) {\n // The store still addresses the boundary; the render reads React state, so\n // the update can be a transition. Initialised from the store on the client\n // and null on the server — exactly what getServerSnapshot did.\n const [state, setState] = useState<ReturnType<typeof getSegmentState>>(() =>\n typeof window === \"undefined\" ? null : getSegmentState(depth),\n );\n\n // A pattern shell is rendered for every url its route matches, so its tree\n // carries no key - and neither does the payload its resume streams in. The\n // page is still this url's: filed under it, read once, at the document's\n // own boot.\n const [fallback] = useState(() =>\n typeof window !== \"undefined\" && !navigatedOnce() ? window.location.pathname : \"\",\n );\n const key = pageKey || fallback;\n\n useEffect(\n () =>\n subscribeToSegment(depth, () => {\n startTransition(() => {\n // A navigation, not the seed: the seed is the page taking over\n // its own server-rendered children, and fading that is the page\n // fading into itself.\n if (navigatedOnce()) addTransitionType?.(NAVIGATION_TRANSITION_TYPE);\n\n setState(getSegmentState(depth));\n });\n }),\n [depth],\n );\n\n // The commit that put a navigated segment on screen: the end of the\n // navigation's measure, see perf.ts. Not on the first render, which is the\n // seed.\n useEffect(() => {\n if (state !== null) navigationCommitted();\n }, [state]);\n\n // Record the page we arrived on, so a later navigation away and back can\n // return to it. Without this the first page is the one page you cannot keep.\n // New children while the store already holds this depth is the document\n // rendered again in place - revalidate(\"all\") - and the page on screen\n // takes them under the key it has; see replaceActive.\n useEffect(() => {\n if (getSegmentState(depth)) replaceActive(depth, children);\n // Filed under the url's key, path and query, which is what every\n // navigation and refresh looks it up by; mounted under the server's, the\n // pathname the shell was frozen with. Seeded under the server's alone,\n // the first refresh of a page loaded at ?c=25 found nothing, mounted a\n // second copy, and showed every fallback in it.\n else if (key) seedSegment(depth, seedKey(key), children, key);\n }, [depth, key, children]);\n\n // Wrapped here rather than around the whole app because this is the closest\n // client component above a page: a redirect thrown inside the page's own\n // Suspense boundary surfaces at the nearest error boundary, and catching it\n // here leaves the layouts above mounted while the navigation runs. One\n // boundary per page, inside its Activity, not one around them all: a\n // boundary that catches renders its fallback in place of everything it\n // wraps, and one around them all unmounted every page being kept alive\n // behind the one redirecting - the destination among them, when it was\n // held - and then kept showing the fallback over the destination once it\n // arrived. The port's report was a sidebar and a breadcrumb over an empty\n // main.\n // The same shape before and after the seed. The server's render and the\n // first client render already place the children inside the retention\n // wrapper, keyed by the page, so when the store takes over after hydration\n // the tree changes state but not shape - and React keeps the DOM instead of\n // remounting the page, which was blank, then content, on every load.\n //\n // The Activity is there whether or not there is a page key to hang it on.\n // A parameterised route's PPR shell is rendered for every url it matches\n // and so carries no key - and the boundary used to leave the Activity out\n // then, so the shell had no <!--&--> markers where the client, hydrating\n // from the payload for the real url, rendered one. React 19.2 does not\n // recover from a hydration mismatch at an Activity: the boundary retries\n // hydrating, mismatches again, and the main thread never returns. A port\n // found /agent/tools/3 freezing the tab on every document load. The\n // markup of an Activity does not depend on its key, so the shell and the\n // client agree whenever both render one.\n return (\n <SegmentDepthContext value={depth}>\n {!state ? (\n <Activity key={key} mode=\"visible\">\n <RedirectBoundary>{children}</RedirectBoundary>\n </Activity>\n ) : state.entries.map((entry) => (\n <Activity\n key={entry.identity ?? entry.key}\n mode={entry.key === state.activeKey ? \"visible\" : \"hidden\"}\n >\n <RedirectBoundary active={entry.key === state.activeKey}>\n {entry.tree as ReactNode}\n </RedirectBoundary>\n </Activity>\n ))}\n </SegmentDepthContext>\n );\n}\n"]}
@@ -1,8 +1,9 @@
1
1
  "use client";
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
- import { useSyncExternalStore } from "react";
3
+ import { useContext, useSyncExternalStore } from "react";
4
4
  import { getSlotState, subscribeToSlot } from "./slotStore";
5
5
  import { RedirectBoundary } from "./RedirectBoundary";
6
+ import { SegmentDepthContext } from "./segmentDepth";
6
7
  /**
7
8
  * The seam a re-rendered slot is swapped in at.
8
9
  *
@@ -19,7 +20,9 @@ import { RedirectBoundary } from "./RedirectBoundary";
19
20
  * the document.
20
21
  */
21
22
  export function SlotBoundary({ name, children }) {
22
- const state = useSyncExternalStore((listener) => subscribeToSlot(name, listener), () => getSlotState(name), () => getSlotState(name));
23
+ // Which navigation replaces this region - see clearSlots.
24
+ const depth = useContext(SegmentDepthContext);
25
+ const state = useSyncExternalStore((listener) => subscribeToSlot(name, listener, depth), () => getSlotState(name), () => getSlotState(name));
23
26
  return (_jsx(RedirectBoundary, { children: (state.tree === undefined ? children : state.tree) }));
24
27
  }
25
28
  //# 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,oBAAoB,EAAE,MAAM,OAAO,CAAC;AAE7C,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEtD;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,YAAY,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAyC;IACpF,MAAM,KAAK,GAAG,oBAAoB,CAChC,CAAC,QAAQ,EAAE,EAAE,CAAC,eAAe,CAAC,IAAI,EAAE,QAAQ,CAAC,EAC7C,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 { useSyncExternalStore } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { getSlotState, subscribeToSlot } from \"./slotStore\";\nimport { RedirectBoundary } from \"./RedirectBoundary\";\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 const state = useSyncExternalStore(\n (listener) => subscribeToSlot(name, listener),\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,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"]}
@@ -877,8 +877,11 @@ export async function navigate(url, opts) {
877
877
  return;
878
878
  }
879
879
  // A slot rendered for the page being left has no claim on the one being
880
- // arrived at.
881
- clearSlots();
880
+ // arrived at - but only the slots the answer replaces: a section of a
881
+ // layout that stays mounted keeps what an action last rendered into it.
882
+ // Leaving an interception empties them all, as it always did: the
883
+ // interceptor sits in a slot of a layout that stays.
884
+ clearSlots(interceptedAtDepth !== null || interceptedOver !== null ? 0 : segmentDepth);
882
885
  interceptedOver = null;
883
886
  interceptedAtDepth = interceptSlot ? segmentDepth : null;
884
887
  interceptedFrom = interceptSlot ? (interceptedFrom ?? previousUrl) : null;