@rsc-kit/core 0.30.17 → 0.30.19

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.
@@ -246,7 +246,12 @@ export function compileEntryWithCommands(dirName, options) {
246
246
  .map((load) => `.then(() => ${load})`)
247
247
  .join('\n ');
248
248
  return ('// The entry to compile into one binary:\n' +
249
- '// bun build --compile .output/server/compile.mjs --outfile dist/app\n' +
249
+ '// bun build --compile --splitting .output/server/compile.mjs --outfile dist/app\n' +
250
+ '//\n' +
251
+ '// --splitting is what lets a worker hold a stop. Bun parses every module in a\n' +
252
+ '// binary before it runs the first line of any, and a stop that arrives in that\n' +
253
+ '// time is the default action (exit 143): splitting makes this file its own\n' +
254
+ '// chunk, which holds the stop for the command from its first line.\n' +
250
255
  '//\n' +
251
256
  '// ./app starts the server. ./app <command> runs src/commands/<command>.\n' +
252
257
  '//\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 .output/server/compile.mjs --outfile dist/app\\n' +\n '//\\n' +\n '// ./app starts the server. ./app <command> runs src/commands/<command>.\\n' +\n '//\\n' +\n '// A binary carries what is imported by name. The server reads its frozen pages\\n' +\n '// through a computed import, which a compile cannot see - imported here, they\\n' +\n '// travel inside the binary and are handed to the server before it starts.\\n' +\n '// The server itself is imported only when no command was named: a static\\n' +\n '// import would start it, and bind its port, before argv could be read. Not\\n' +\n '// awaited, since a top-level await is what stopped --bytecode from applying.\\n' +\n 'import { basename } from \"node:path\";\\n' +\n `import \"./${inlineModuleName(dirName)}\";\\n` +\n '\\n' +\n commandAnswerSource(options) +\n '\\n' +\n 'const failed = (what) => (error) => {\\n' +\n ' console.error(what, error);\\n' +\n ' process.exit(1);\\n' +\n '};\\n' +\n '\\n' +\n 'if (process.argv.length <= 2) {\\n' +\n ' import(\"./index.mjs\").catch(failed(\"[rsc-kit] the server failed to start:\"));\\n' +\n '} else {\\n' +\n ' const argv = process.argv.slice(2);\\n' +\n ' const program = \"./\" + basename(process.execPath);\\n' +\n ' const answered = answer(argv, program);\\n' +\n '\\n' +\n ' if (answered) {\\n' +\n ' (answered.code === 0 ? process.stdout : process.stderr).write(answered.text);\\n' +\n ' process.exitCode = answered.code;\\n' +\n ' } else {\\n' +\n ' globalThis[Symbol.for(\"rsc-kit.command\")] = true;\\n' +\n '\\n' +\n indent(indent(earlySignalsSource())) +\n '\\n' +\n ' Promise.resolve()\\n' +\n ` ${chain}\\n` +\n ' .then(({ runCommand }) => runCommand(argv, program))\\n' +\n ' .then((code) => process.exit(code), failed(\"[rsc-kit] the app failed to load:\"));\\n' +\n ' }\\n' +\n '}\\n'\n )\n}\n\nfunction indent(text: string): string {\n return text\n .split('\\n')\n .map((line) => (line ? ' ' + line : line))\n .join('\\n')\n}\n"]}
1
+ {"version":3,"file":"commandFiles.js","sourceRoot":"","sources":["../src/commandFiles.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The build's half of app commands: which files in `src/commands` are\n * commands, what the generated entry says about them, and the small entries\n * that run one. The runner they call is `commands.ts`.\n */\n\nimport { existsSync, readdirSync, readFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { commandHelp, EARLY_SIGNALS } from './commands.js'\nimport { inlineModuleName } from './files.js'\n\nexport interface CommandFile {\n /** What is typed after the binary: the file's name without its extension. */\n name: string\n /** Absolute path, imported lazily by the entry. */\n file: string\n /** A plain string literal exported as `description`, for `--help`. */\n description?: string\n}\n\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"]}
@@ -7,8 +7,8 @@
7
7
  * `node .output/server/commands.mjs <name>`, so nothing about a command
8
8
  * depends on Bun, on a platform, or on the binary.
9
9
  *
10
- * Each command is a file with a default export, `(args, { signal }) => number
11
- * | void`. It shares the build's code and dependencies, so the database
10
+ * Each command is a file with a default export, `(args, context) => number
11
+ * | void`, where `context` is `{ signal, deadline }`. It shares the build's code and dependencies, so the database
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
  *
@@ -37,9 +37,21 @@ export interface CommandContext {
37
37
  * (`Date.now()`'s), once it has been told to stop; undefined until then.
38
38
  * A worker finishing its job reads `deadline - Date.now()` rather than
39
39
  * RSC_SHUTDOWN_TIMEOUT and its default.
40
+ *
41
+ * A getter, because what it answers changes: read `context.deadline` when
42
+ * you need it. `({ deadline }) => ...` reads it once, as the command
43
+ * starts, when it is always undefined, and keeps that.
40
44
  */
41
45
  readonly deadline: number | undefined;
42
46
  }
47
+ /** A command's default export. */
48
+ export type CommandFunction = (args: string[], context: CommandContext) => number | void | Promise<number | void>;
49
+ /**
50
+ * Types a command's default export, so its arguments and context are known
51
+ * without annotating them: `export default defineCommand(async (args, context) => {})`.
52
+ * It returns what it is given.
53
+ */
54
+ export declare function defineCommand(command: CommandFunction): CommandFunction;
43
55
  /**
44
56
  * The entry that loads the app owns the process's SIGTERM and SIGINT, and
45
57
  * leaves this on `globalThis` for the runner.
package/dist/commands.js CHANGED
@@ -7,8 +7,8 @@
7
7
  * `node .output/server/commands.mjs <name>`, so nothing about a command
8
8
  * depends on Bun, on a platform, or on the binary.
9
9
  *
10
- * Each command is a file with a default export, `(args, { signal }) => number
11
- * | void`. It shares the build's code and dependencies, so the database
10
+ * Each command is a file with a default export, `(args, context) => number
11
+ * | void`, where `context` is `{ signal, deadline }`. It shares the build's code and dependencies, so the database
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
  *
@@ -28,6 +28,16 @@
28
28
  * a server does at startup, which a command has not asked for.
29
29
  */
30
30
  export const COMMAND_MODE = Symbol.for('rsc-kit.command');
31
+ /**
32
+ * Types a command's default export, so its arguments and context are known
33
+ * without annotating them: `export default defineCommand(async (args, context) => {})`.
34
+ * It returns what it is given.
35
+ */
36
+ export function defineCommand(command) {
37
+ return command;
38
+ }
39
+ /** Resolves after the event loop has taken a turn, which is when it hands out the signals that arrived while it was busy. */
40
+ const nextTurn = () => new Promise((resolve) => (typeof setImmediate === 'function' ? setImmediate(resolve) : setTimeout(resolve, 0)));
31
41
  /**
32
42
  * The entry that loads the app owns the process's SIGTERM and SIGINT, and
33
43
  * leaves this on `globalThis` for the runner.
@@ -166,6 +176,11 @@ export async function runCommand(argv, options) {
166
176
  await options.ready();
167
177
  registered = true;
168
178
  }
179
+ // A stop that arrived while the loop was busy with a synchronous load has
180
+ // not been delivered: a signal is delivered on the loop's next turn. One
181
+ // turn here, so that the command is told before it starts rather than a
182
+ // moment after - the same on Node, on Bun and in a binary.
183
+ await nextTurn();
169
184
  return exitCodeOf(await module.default(args, context), name);
170
185
  }
171
186
  catch (error) {
@@ -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, { signal }) => number\n * | void`. 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 readonly deadline: number | undefined\n}\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 return exitCodeOf(await (module.default as (...a: unknown[]) => unknown)(args, context), name)\n } catch (error) {\n err(`[rsc-kit] the command \"${name}\" failed: ${error instanceof Error ? (error.stack ?? error.message) : String(error)}\\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: ${error instanceof Error ? error.message : String(error)}\\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"]}
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: ${error instanceof Error ? (error.stack ?? error.message) : String(error)}\\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: ${error instanceof Error ? error.message : String(error)}\\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"]}
package/dist/vite.js CHANGED
@@ -6969,6 +6969,7 @@ export const SERVER_ONLY = {
6969
6969
  "js/refreshOn": "section",
6970
6970
  host: "host",
6971
6971
  hostCalls: "host-calls",
6972
+ commands: "commands",
6972
6973
  prerender: "prerender",
6973
6974
  export: "export",
6974
6975
  files: "files",