@rsc-kit/core 0.30.15 → 0.30.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/dist/commandFiles.d.ts +97 -0
- package/dist/commandFiles.js +297 -0
- package/dist/commandFiles.js.map +1 -0
- package/dist/commands.d.ts +95 -0
- package/dist/commands.js +207 -0
- package/dist/commands.js.map +1 -0
- package/dist/vite.js +101 -6
- package/dist/vite.js.map +1 -1
- package/package.json +1 -1
package/dist/commands.js
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* App commands: the files in `src/commands`, run as `./app <name>`.
|
|
3
|
+
*
|
|
4
|
+
* A compiled app is one binary that starts a server, and a release step or a
|
|
5
|
+
* queue worker is the same code started differently - `./app migrate`,
|
|
6
|
+
* `./app work`. The same entry also runs from a plain build, as
|
|
7
|
+
* `node .output/server/commands.mjs <name>`, so nothing about a command
|
|
8
|
+
* depends on Bun, on a platform, or on the binary.
|
|
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
|
|
12
|
+
* package and the logger the pages use are the ones it uses, in the same
|
|
13
|
+
* bundle. It is loaded only when it is named: the server never evaluates one.
|
|
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.
|
|
20
|
+
*
|
|
21
|
+
* Nothing here imports `node:` anything: the runner is bundled into the
|
|
22
|
+
* server's entry for every host, and a host without those modules must still
|
|
23
|
+
* load it. `process` is reached lazily, inside the functions that need it.
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* Set on `globalThis` before the server's entry is imported, by whatever runs
|
|
27
|
+
* a command. The entry would otherwise call register() the moment it loads, as
|
|
28
|
+
* a server does at startup, which a command has not asked for.
|
|
29
|
+
*/
|
|
30
|
+
export const COMMAND_MODE = Symbol.for('rsc-kit.command');
|
|
31
|
+
/**
|
|
32
|
+
* The entry that loads the app owns the process's SIGTERM and SIGINT, and
|
|
33
|
+
* leaves this on `globalThis` for the runner.
|
|
34
|
+
*
|
|
35
|
+
* Loading can take a while, and a signal before the runner exists finds only
|
|
36
|
+
* the default action, which ends the process with 143. So the entry holds
|
|
37
|
+
* them, and the runner is handed over to: the count of those that came first,
|
|
38
|
+
* and every later one. The entry's listener is never taken off. A signal is
|
|
39
|
+
* delivered on the event loop's next turn, not when it arrives, and a listener
|
|
40
|
+
* removed in between has the signal taken from it without anyone getting it.
|
|
41
|
+
*/
|
|
42
|
+
export const EARLY_SIGNALS = Symbol.for('rsc-kit.command.signals');
|
|
43
|
+
/** Whether this process was started to run a command rather than to serve. */
|
|
44
|
+
export function inCommandMode() {
|
|
45
|
+
return Boolean(globalThis[COMMAND_MODE]);
|
|
46
|
+
}
|
|
47
|
+
/** The list `--help` prints, and what an unknown name is answered with. */
|
|
48
|
+
export function commandHelp(commands, program = 'app') {
|
|
49
|
+
const names = Object.keys(commands).sort();
|
|
50
|
+
const width = Math.max(0, ...names.map((name) => name.length));
|
|
51
|
+
const rows = names.map((name) => {
|
|
52
|
+
const description = commands[name]?.description;
|
|
53
|
+
return description ? ` ${name.padEnd(width)} ${description}` : ` ${name}`;
|
|
54
|
+
});
|
|
55
|
+
return (`Usage: ${program} <command> [args]\n\n` +
|
|
56
|
+
(rows.length ? `Commands:\n${rows.join('\n')}\n` : 'There are no commands: add a file to src/commands.\n'));
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* An exit code from what a command returned. Nothing is success; a number is
|
|
60
|
+
* the code. Anything else is the command's mistake, and said so, because the
|
|
61
|
+
* alternative is a process that exits 0 after returning a string.
|
|
62
|
+
*/
|
|
63
|
+
function exitCodeOf(result, name) {
|
|
64
|
+
if (result === undefined)
|
|
65
|
+
return 0;
|
|
66
|
+
if (typeof result === 'number' && Number.isInteger(result) && result >= 0 && result <= 255)
|
|
67
|
+
return result;
|
|
68
|
+
throw new TypeError(`the command "${name}" returned ${JSON.stringify(result)}. A command returns an exit code from 0 to 255, or nothing.`);
|
|
69
|
+
}
|
|
70
|
+
function unref(timer) {
|
|
71
|
+
const handle = timer;
|
|
72
|
+
if (handle && typeof handle.unref === 'function')
|
|
73
|
+
handle.unref();
|
|
74
|
+
}
|
|
75
|
+
/** The seconds a command and its shutdown() get after a signal, in milliseconds. */
|
|
76
|
+
function defaultLimit() {
|
|
77
|
+
const seconds = Number(typeof process !== 'undefined' ? process.env.RSC_SHUTDOWN_TIMEOUT : undefined);
|
|
78
|
+
return (Number.isFinite(seconds) && seconds > 0 ? seconds : 10) * 1000;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Run the command `argv` names and say how it ended.
|
|
82
|
+
*
|
|
83
|
+
* Resolves to the exit code and leaves exiting to the caller, so a test sees
|
|
84
|
+
* the code and an entry decides when the process stops. 0 is success and 1 is
|
|
85
|
+
* a command that threw or would not stop; 2 is a name that is not a command,
|
|
86
|
+
* as it is for a tool that was misused.
|
|
87
|
+
*/
|
|
88
|
+
export async function runCommand(argv, options) {
|
|
89
|
+
const out = options.out ?? ((text) => void process.stdout.write(text));
|
|
90
|
+
const err = options.err ?? ((text) => void process.stderr.write(text));
|
|
91
|
+
const [name, ...args] = argv;
|
|
92
|
+
if (name === '--help' || name === '-h') {
|
|
93
|
+
out(commandHelp(options.commands, options.program));
|
|
94
|
+
return 0;
|
|
95
|
+
}
|
|
96
|
+
if (name === undefined) {
|
|
97
|
+
err(commandHelp(options.commands, options.program));
|
|
98
|
+
return 2;
|
|
99
|
+
}
|
|
100
|
+
const command = Object.hasOwn(options.commands, name) ? options.commands[name] : undefined;
|
|
101
|
+
if (!command) {
|
|
102
|
+
err(`Not a command: ${name}\n\n${commandHelp(options.commands, options.program)}`);
|
|
103
|
+
return 2;
|
|
104
|
+
}
|
|
105
|
+
const limit = options.limit ?? defaultLimit();
|
|
106
|
+
const signals = options.signals ?? (typeof process !== 'undefined' ? process : undefined);
|
|
107
|
+
const controller = new AbortController();
|
|
108
|
+
let registered = false;
|
|
109
|
+
let stop;
|
|
110
|
+
let stopTimer;
|
|
111
|
+
let deadline;
|
|
112
|
+
let signalled = 0;
|
|
113
|
+
const context = {
|
|
114
|
+
signal: controller.signal,
|
|
115
|
+
get deadline() {
|
|
116
|
+
return deadline;
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
// The first signal asks the command to stop and gives it `limit` to do so,
|
|
120
|
+
// as the server gives in-flight requests theirs. The second is a person who
|
|
121
|
+
// meant it, and nothing waits.
|
|
122
|
+
const onSignal = () => {
|
|
123
|
+
signalled += 1;
|
|
124
|
+
if (signalled === 1) {
|
|
125
|
+
deadline = Date.now() + limit;
|
|
126
|
+
controller.abort();
|
|
127
|
+
stopTimer = setTimeout(() => {
|
|
128
|
+
err(`[rsc-kit] the command "${name}" did not stop within ${limit / 1000} seconds.\n`);
|
|
129
|
+
stop?.(1);
|
|
130
|
+
}, limit);
|
|
131
|
+
unref(stopTimer);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
stop?.(1);
|
|
135
|
+
};
|
|
136
|
+
const stopped = new Promise((resolve) => {
|
|
137
|
+
stop = resolve;
|
|
138
|
+
});
|
|
139
|
+
// Behind an entry that loaded the app, the process's signals are the
|
|
140
|
+
// entry's: it hands each one over, and what came while the app was loading
|
|
141
|
+
// is counted here. Told to stop while loading is told to stop.
|
|
142
|
+
const early = globalThis[EARLY_SIGNALS];
|
|
143
|
+
if (early) {
|
|
144
|
+
early.handoff(onSignal);
|
|
145
|
+
for (let caught = 0; caught < early.count; caught++)
|
|
146
|
+
onSignal();
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
signals?.on('SIGTERM', onSignal);
|
|
150
|
+
signals?.on('SIGINT', onSignal);
|
|
151
|
+
}
|
|
152
|
+
// A command that is only waiting - for its signal, for a message that has
|
|
153
|
+
// not come - holds nothing open, and Node answers an event loop with nothing
|
|
154
|
+
// in it by exiting 13 with a warning about an unsettled await, where Bun
|
|
155
|
+
// simply waits. A command that is running is running until it returns.
|
|
156
|
+
const alive = setInterval(() => { }, 2 ** 30);
|
|
157
|
+
const ran = (async () => {
|
|
158
|
+
try {
|
|
159
|
+
const module = await command.load();
|
|
160
|
+
if (typeof module.default !== 'function') {
|
|
161
|
+
throw new TypeError(`src/commands/${name} has no default export. Export a function: export default async (args) => {}`);
|
|
162
|
+
}
|
|
163
|
+
// Opted in, and only then: a migration that needs the schema should not
|
|
164
|
+
// fail because the queue it never touches is not up.
|
|
165
|
+
if (module.register === true) {
|
|
166
|
+
await options.ready();
|
|
167
|
+
registered = true;
|
|
168
|
+
}
|
|
169
|
+
return exitCodeOf(await module.default(args, context), name);
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
err(`[rsc-kit] the command "${name}" failed: ${error instanceof Error ? (error.stack ?? error.message) : String(error)}\n`);
|
|
173
|
+
return 1;
|
|
174
|
+
}
|
|
175
|
+
})();
|
|
176
|
+
let code;
|
|
177
|
+
try {
|
|
178
|
+
code = await Promise.race([ran, stopped]);
|
|
179
|
+
}
|
|
180
|
+
finally {
|
|
181
|
+
clearInterval(alive);
|
|
182
|
+
clearTimeout(stopTimer);
|
|
183
|
+
if (early)
|
|
184
|
+
early.handoff(undefined);
|
|
185
|
+
else {
|
|
186
|
+
signals?.off('SIGTERM', onSignal);
|
|
187
|
+
signals?.off('SIGINT', onSignal);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
// What register() opened is closed here, bounded like the server's: a
|
|
191
|
+
// close that hangs must not hold a release step open.
|
|
192
|
+
if (registered && options.shutdown) {
|
|
193
|
+
let timer;
|
|
194
|
+
await Promise.race([
|
|
195
|
+
Promise.resolve()
|
|
196
|
+
.then(() => options.shutdown?.())
|
|
197
|
+
.catch((error) => err(`[rsc-kit] instrumentation.ts shutdown() failed: ${error instanceof Error ? error.message : String(error)}\n`)),
|
|
198
|
+
new Promise((resolve) => {
|
|
199
|
+
timer = setTimeout(resolve, limit);
|
|
200
|
+
unref(timer);
|
|
201
|
+
}),
|
|
202
|
+
]);
|
|
203
|
+
clearTimeout(timer);
|
|
204
|
+
}
|
|
205
|
+
return code;
|
|
206
|
+
}
|
|
207
|
+
//# sourceMappingURL=commands.js.map
|
|
@@ -0,0 +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"]}
|
package/dist/vite.js
CHANGED
|
@@ -33,6 +33,7 @@ import { ownHosts } from "./hostRouting.js";
|
|
|
33
33
|
import { unrollBarrelImports } from "./barrelImports.js";
|
|
34
34
|
import { hasUseSsr, serverRendererMessage, SERVER_RENDERER, ssrProxyModule, UseSsrError, withoutSsrDirective } from "./useSsr.js";
|
|
35
35
|
import { httpHostCalls } from "./hostCalls.js";
|
|
36
|
+
import { COMMANDS_MODULE_ID, commandFiles, commandsEntry, commandsEntrySource, commandsModule, compileEntryWithCommands } from "./commandFiles.js";
|
|
36
37
|
import { clientPackages, importsServerRenderer, packageDir as installedPackageDir, packageEntryInGraph, polyfillsToTrace, traceableByName } from "./clientPackages.js";
|
|
37
38
|
// Resolved once per rscKit() call. One build runs in one process, so these are
|
|
38
39
|
// module state rather than threaded through every helper.
|
|
@@ -47,6 +48,57 @@ let clientLibraryImports = [];
|
|
|
47
48
|
* decisions buildApp makes about where stored pages can live.
|
|
48
49
|
*/
|
|
49
50
|
let nitroPreset = null;
|
|
51
|
+
/** `src/commands`, read once per build; null until asked. See buildCommands(). */
|
|
52
|
+
let commandsFound = null;
|
|
53
|
+
/**
|
|
54
|
+
* What the entries that run a command need from the build: the commands, and
|
|
55
|
+
* the polyfills the server loads in a Nitro plugin that a command does not go
|
|
56
|
+
* through. Only the ones the app's graph has, as the plugin does.
|
|
57
|
+
*/
|
|
58
|
+
function commandEntryOptions() {
|
|
59
|
+
return {
|
|
60
|
+
commands: buildCommands(),
|
|
61
|
+
polyfills: FIRST_POLYFILLS.filter((name) => packageEntryInGraph(projectRoot, name) !== null),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* The presets whose output is a process that Node or Bun starts: where a
|
|
66
|
+
* command runs. Every other preset is a platform that starts the app itself -
|
|
67
|
+
* a function, a Worker - and a release step there is not the app's to run.
|
|
68
|
+
*/
|
|
69
|
+
const COMMAND_PRESETS = /^(?:bun|node|node[-_]server|node[-_]cluster)$/;
|
|
70
|
+
/**
|
|
71
|
+
* The app's commands, for a build that has a process to run them in.
|
|
72
|
+
*
|
|
73
|
+
* Decided once, the first time the bundle asks, and every later step reads the
|
|
74
|
+
* same answer: the table in the bundle, `commands.mjs`, the compile entry and
|
|
75
|
+
* `.output/rsc-kit.json` cannot disagree about what is there.
|
|
76
|
+
*
|
|
77
|
+
* Not the dev server, which has no output to run one from - a command there is
|
|
78
|
+
* `bun src/commands/<name>.ts`. Not a Worker: its bundle targets workerd, where
|
|
79
|
+
* the packages a release step wants (a database driver, `node:child_process`)
|
|
80
|
+
* either resolve to a different build or not at all, so a command that ran there
|
|
81
|
+
* would run something other than what was written. And not a preset this has not
|
|
82
|
+
* been run on: it says so, rather than writing an entry for a layout it has not
|
|
83
|
+
* seen.
|
|
84
|
+
*/
|
|
85
|
+
function buildCommands() {
|
|
86
|
+
if (resolvedConfig?.command !== "build")
|
|
87
|
+
return [];
|
|
88
|
+
if (commandsFound)
|
|
89
|
+
return commandsFound;
|
|
90
|
+
const { commands, ignored } = commandFiles(sourceDir);
|
|
91
|
+
for (const { path, reason } of ignored)
|
|
92
|
+
log(`${relative(projectRoot, path)} is not a command: ${reason}.`);
|
|
93
|
+
if (commands.length && !(nitroPreset && COMMAND_PRESETS.test(nitroPreset))) {
|
|
94
|
+
const names = commands.map((c) => c.name).join(", ");
|
|
95
|
+
log(nitroPreset?.startsWith("cloudflare")
|
|
96
|
+
? `src/commands is not built for a Worker (${names}): the bundle runs on workerd, which is not where a release step runs. Run the file itself, or wrangler.`
|
|
97
|
+
: `src/commands is built for the bun and node presets, and this build is ${nitroPreset ? `for ${nitroPreset}` : "not using a Nitro preset"} (${names}). Run the file itself from CI.`);
|
|
98
|
+
return (commandsFound = []);
|
|
99
|
+
}
|
|
100
|
+
return (commandsFound = commands);
|
|
101
|
+
}
|
|
50
102
|
/**
|
|
51
103
|
* Modules a runtime provides and no bundle should try to carry.
|
|
52
104
|
*
|
|
@@ -2490,6 +2542,20 @@ function registerOpenApiRoutes() {
|
|
|
2490
2542
|
}
|
|
2491
2543
|
apiRoutes.set(name, { name, absPath: OPENAPI_ROUTE_ID, methods: ["GET"], generated: { kind: "openapi", file: "" } });
|
|
2492
2544
|
}
|
|
2545
|
+
/** The table behind `runCommand` in the server's entry: src/commands, or nothing where they are not built. */
|
|
2546
|
+
function commandsPlugin() {
|
|
2547
|
+
return {
|
|
2548
|
+
name: "rsc-kit:commands",
|
|
2549
|
+
resolveId(id) {
|
|
2550
|
+
if (id === COMMANDS_MODULE_ID)
|
|
2551
|
+
return "\0" + id;
|
|
2552
|
+
},
|
|
2553
|
+
load(id) {
|
|
2554
|
+
if (id === "\0" + COMMANDS_MODULE_ID)
|
|
2555
|
+
return commandsModule(buildCommands());
|
|
2556
|
+
},
|
|
2557
|
+
};
|
|
2558
|
+
}
|
|
2493
2559
|
/** The module behind the synthesised /openapi.json route. */
|
|
2494
2560
|
function openApiPlugin() {
|
|
2495
2561
|
return {
|
|
@@ -3070,7 +3136,7 @@ export function instrumentationImport(instrumentation) {
|
|
|
3070
3136
|
return (`import { ${hooks.map((hook) => `${hook} as __instrumentation_${hook}`).join(", ")} } from ${from}\n` +
|
|
3071
3137
|
`const __instrumentation: ${type} = { ${hooks.map((hook) => `${hook}: __instrumentation_${hook}`).join(", ")} }`);
|
|
3072
3138
|
}
|
|
3073
|
-
function generateEntryRsc(fallbackOrigin = "") {
|
|
3139
|
+
function generateEntryRsc(fallbackOrigin = "", withCommands = false) {
|
|
3074
3140
|
const instrumentation = instrumentationFile();
|
|
3075
3141
|
// Every 404 page the app has, and the layouts each renders inside. Computed
|
|
3076
3142
|
// here rather than looked up at runtime: not-found is not a route, so the
|
|
@@ -3603,7 +3669,9 @@ if (!isolateRuntime) {
|
|
|
3603
3669
|
;(globalThis as { __rscKitShutdown?: () => unknown }).__rscKitShutdown = () => __instrumentation.shutdown?.()
|
|
3604
3670
|
}
|
|
3605
3671
|
|
|
3606
|
-
|
|
3672
|
+
// Started to run a command (src/commands), not to serve: register() waits for
|
|
3673
|
+
// the command that asks for it, rather than running at import.
|
|
3674
|
+
if (__instrumentation.register && !isolateRuntime && !(globalThis as Record<symbol, unknown>)[Symbol.for('rsc-kit.command')]) {
|
|
3607
3675
|
instrumented().catch((error) => {
|
|
3608
3676
|
console.error('[rsc-kit] instrumentation.ts register() failed:', error)
|
|
3609
3677
|
|
|
@@ -5990,7 +6058,11 @@ async function renderNotFound(request: Request): Promise<Response> {
|
|
|
5990
6058
|
: ""}
|
|
5991
6059
|
return new Response('Not found', { status: 404 })
|
|
5992
6060
|
}
|
|
5993
|
-
|
|
6061
|
+
${
|
|
6062
|
+
// After everything the entry is for: running a command reads its
|
|
6063
|
+
// register() and shutdown(). Only a build for an app that has commands; what
|
|
6064
|
+
// is in the table is decided later, when the bundle is built.
|
|
6065
|
+
withCommands ? commandsEntry(join(packageDir, "commands")) : ""}`;
|
|
5994
6066
|
}
|
|
5995
6067
|
function generateEntrySsr() {
|
|
5996
6068
|
const devUrls = join(packageDir, "devUrls");
|
|
@@ -7219,6 +7291,20 @@ export function rscKit(options = {}) {
|
|
|
7219
7291
|
if (nitro.options.dev)
|
|
7220
7292
|
return;
|
|
7221
7293
|
nitroPreset = nitro.options.preset ?? null;
|
|
7294
|
+
// The entry a command runs from, beside the server it does not start.
|
|
7295
|
+
// Written once Nitro has assembled the output, because it names a
|
|
7296
|
+
// file Nitro places: if that moves, the build says so rather than a
|
|
7297
|
+
// release step finding out.
|
|
7298
|
+
nitro.hooks.hook("compiled", async () => {
|
|
7299
|
+
if (buildCommands().length === 0)
|
|
7300
|
+
return;
|
|
7301
|
+
const serverDir = nitro.options.output.serverDir;
|
|
7302
|
+
if (!existsSync(join(serverDir, "_ssr", "rsc.mjs"))) {
|
|
7303
|
+
throw new Error(`[rsc-kit] src/commands runs from ${join(serverDir, "_ssr", "rsc.mjs")}, and Nitro did not write it there. ` +
|
|
7304
|
+
"Nitro's output layout changed; the commands entry needs to follow it.");
|
|
7305
|
+
}
|
|
7306
|
+
writeFileSync(join(serverDir, "commands.mjs"), commandsEntrySource(commandEntryOptions()));
|
|
7307
|
+
});
|
|
7222
7308
|
// On Workers the stored pages ride the static assets, not the bundle.
|
|
7223
7309
|
// Copied once Nitro has listed its public files, so the copies are
|
|
7224
7310
|
// not in that list: a path in it is served as an asset before the
|
|
@@ -7331,6 +7417,7 @@ export function rscKit(options = {}) {
|
|
|
7331
7417
|
// Both maps, not one: the config hook runs again when the config
|
|
7332
7418
|
// changes under a dev server, and a route.ts or a synthesised
|
|
7333
7419
|
// robots.txt left over from the previous run read as a duplicate.
|
|
7420
|
+
commandsFound = null;
|
|
7334
7421
|
components.clear();
|
|
7335
7422
|
apiRoutes.clear();
|
|
7336
7423
|
discover(appDir);
|
|
@@ -7413,7 +7500,7 @@ export function rscKit(options = {}) {
|
|
|
7413
7500
|
: env.command === "build"
|
|
7414
7501
|
? (declaresBackend ? "" : false)
|
|
7415
7502
|
: (detected || (declaresBackend ? "" : false));
|
|
7416
|
-
writeGenerated(join(genDir, "entry.rsc.tsx"), generateEntryRsc(fallbackOrigin));
|
|
7503
|
+
writeGenerated(join(genDir, "entry.rsc.tsx"), generateEntryRsc(fallbackOrigin, env.command === "build" && existsSync(join(sourceDir, "commands"))));
|
|
7417
7504
|
writeGenerated(join(genDir, "entry.ssr.tsx"), generateEntrySsr());
|
|
7418
7505
|
writeGenerated(join(genDir, "entry.browser.tsx"), generateEntryBrowser());
|
|
7419
7506
|
// Written beside the entries, for a host to read instead of walking the
|
|
@@ -7893,7 +7980,9 @@ export function rscKit(options = {}) {
|
|
|
7893
7980
|
// And the entry a binary is compiled from, so the pages above end up
|
|
7894
7981
|
// inside it rather than rendered live: bun build --compile
|
|
7895
7982
|
// .output/server/compile.mjs. The scaffold's script names it.
|
|
7896
|
-
writeFileSync(join(dirname(staticDir), "compile.mjs"),
|
|
7983
|
+
writeFileSync(join(dirname(staticDir), "compile.mjs"), buildCommands().length > 0
|
|
7984
|
+
? compileEntryWithCommands(NITRO_STATIC_DIR, commandEntryOptions())
|
|
7985
|
+
: compileEntrySource(NITRO_STATIC_DIR));
|
|
7897
7986
|
}
|
|
7898
7987
|
// Manifest first. The service worker precaches whatever it finds in this
|
|
7899
7988
|
// directory, so writing it afterwards leaves it out of the list — and an
|
|
@@ -7917,7 +8006,12 @@ export function rscKit(options = {}) {
|
|
|
7917
8006
|
if (clientOut) {
|
|
7918
8007
|
writeFileSync(join(dirname(clientOut), "rsc-kit.json"), JSON.stringify(output === "export"
|
|
7919
8008
|
? { version: PACKAGE_VERSION, output: "export", dir: relative(projectRoot, exportPath) }
|
|
7920
|
-
: {
|
|
8009
|
+
: {
|
|
8010
|
+
version: PACKAGE_VERSION,
|
|
8011
|
+
output: "server",
|
|
8012
|
+
...compileStep(projectRoot),
|
|
8013
|
+
...(buildCommands().length ? { commands: buildCommands().map((c) => c.name) } : {}),
|
|
8014
|
+
}, null, 2) + "\n");
|
|
7921
8015
|
}
|
|
7922
8016
|
},
|
|
7923
8017
|
configResolved(config) {
|
|
@@ -8000,6 +8094,7 @@ export function rscKit(options = {}) {
|
|
|
8000
8094
|
serverRendererInRsc(),
|
|
8001
8095
|
metadataRoutesPlugin(),
|
|
8002
8096
|
openApiPlugin(),
|
|
8097
|
+
commandsPlugin(),
|
|
8003
8098
|
extendableClientReferences(),
|
|
8004
8099
|
stableComponentNames(),
|
|
8005
8100
|
typecheckPlugin(),
|