@rsc-kit/core 0.30.14 → 0.30.16
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/appAssets.d.ts +25 -4
- package/dist/appAssets.js +86 -30
- package/dist/appAssets.js.map +1 -1
- package/dist/commandFiles.d.ts +60 -0
- package/dist/commandFiles.js +136 -0
- package/dist/commandFiles.js.map +1 -0
- package/dist/commands.d.ts +74 -0
- package/dist/commands.js +172 -0
- package/dist/commands.js.map +1 -0
- package/dist/files.d.ts +3 -1
- package/dist/files.js +47 -1
- package/dist/files.js.map +1 -1
- package/dist/metadata.d.ts +4 -0
- package/dist/metadata.js.map +1 -1
- package/dist/shareImages.d.ts +30 -0
- package/dist/shareImages.js +50 -0
- package/dist/shareImages.js.map +1 -0
- package/dist/vite.js +157 -22
- package/dist/vite.js.map +1 -1
- package/package.json +1 -1
package/dist/commands.js
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
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
|
+
/** Whether this process was started to run a command rather than to serve. */
|
|
32
|
+
export function inCommandMode() {
|
|
33
|
+
return Boolean(globalThis[COMMAND_MODE]);
|
|
34
|
+
}
|
|
35
|
+
/** The list `--help` prints, and what an unknown name is answered with. */
|
|
36
|
+
export function commandHelp(commands, program = 'app') {
|
|
37
|
+
const names = Object.keys(commands).sort();
|
|
38
|
+
const width = Math.max(0, ...names.map((name) => name.length));
|
|
39
|
+
const rows = names.map((name) => {
|
|
40
|
+
const description = commands[name]?.description;
|
|
41
|
+
return description ? ` ${name.padEnd(width)} ${description}` : ` ${name}`;
|
|
42
|
+
});
|
|
43
|
+
return (`Usage: ${program} <command> [args]\n\n` +
|
|
44
|
+
(rows.length ? `Commands:\n${rows.join('\n')}\n` : 'There are no commands: add a file to src/commands.\n'));
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* An exit code from what a command returned. Nothing is success; a number is
|
|
48
|
+
* the code. Anything else is the command's mistake, and said so, because the
|
|
49
|
+
* alternative is a process that exits 0 after returning a string.
|
|
50
|
+
*/
|
|
51
|
+
function exitCodeOf(result, name) {
|
|
52
|
+
if (result === undefined)
|
|
53
|
+
return 0;
|
|
54
|
+
if (typeof result === 'number' && Number.isInteger(result) && result >= 0 && result <= 255)
|
|
55
|
+
return result;
|
|
56
|
+
throw new TypeError(`the command "${name}" returned ${JSON.stringify(result)}. A command returns an exit code from 0 to 255, or nothing.`);
|
|
57
|
+
}
|
|
58
|
+
function unref(timer) {
|
|
59
|
+
const handle = timer;
|
|
60
|
+
if (handle && typeof handle.unref === 'function')
|
|
61
|
+
handle.unref();
|
|
62
|
+
}
|
|
63
|
+
/** The seconds a command and its shutdown() get after a signal, in milliseconds. */
|
|
64
|
+
function defaultLimit() {
|
|
65
|
+
const seconds = Number(typeof process !== 'undefined' ? process.env.RSC_SHUTDOWN_TIMEOUT : undefined);
|
|
66
|
+
return (Number.isFinite(seconds) && seconds > 0 ? seconds : 10) * 1000;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Run the command `argv` names and say how it ended.
|
|
70
|
+
*
|
|
71
|
+
* Resolves to the exit code and leaves exiting to the caller, so a test sees
|
|
72
|
+
* the code and an entry decides when the process stops. 0 is success and 1 is
|
|
73
|
+
* a command that threw or would not stop; 2 is a name that is not a command,
|
|
74
|
+
* as it is for a tool that was misused.
|
|
75
|
+
*/
|
|
76
|
+
export async function runCommand(argv, options) {
|
|
77
|
+
const out = options.out ?? ((text) => void process.stdout.write(text));
|
|
78
|
+
const err = options.err ?? ((text) => void process.stderr.write(text));
|
|
79
|
+
const [name, ...args] = argv;
|
|
80
|
+
if (name === '--help' || name === '-h') {
|
|
81
|
+
out(commandHelp(options.commands, options.program));
|
|
82
|
+
return 0;
|
|
83
|
+
}
|
|
84
|
+
if (name === undefined) {
|
|
85
|
+
err(commandHelp(options.commands, options.program));
|
|
86
|
+
return 2;
|
|
87
|
+
}
|
|
88
|
+
const command = Object.hasOwn(options.commands, name) ? options.commands[name] : undefined;
|
|
89
|
+
if (!command) {
|
|
90
|
+
err(`Not a command: ${name}\n\n${commandHelp(options.commands, options.program)}`);
|
|
91
|
+
return 2;
|
|
92
|
+
}
|
|
93
|
+
const limit = options.limit ?? defaultLimit();
|
|
94
|
+
const signals = options.signals ?? (typeof process !== 'undefined' ? process : undefined);
|
|
95
|
+
const controller = new AbortController();
|
|
96
|
+
let registered = false;
|
|
97
|
+
let stop;
|
|
98
|
+
let stopTimer;
|
|
99
|
+
let signalled = 0;
|
|
100
|
+
// The first signal asks the command to stop and gives it `limit` to do so,
|
|
101
|
+
// as the server gives in-flight requests theirs. The second is a person who
|
|
102
|
+
// meant it, and nothing waits.
|
|
103
|
+
const onSignal = () => {
|
|
104
|
+
signalled += 1;
|
|
105
|
+
if (signalled === 1) {
|
|
106
|
+
controller.abort();
|
|
107
|
+
stopTimer = setTimeout(() => {
|
|
108
|
+
err(`[rsc-kit] the command "${name}" did not stop within ${limit / 1000} seconds.\n`);
|
|
109
|
+
stop?.(1);
|
|
110
|
+
}, limit);
|
|
111
|
+
unref(stopTimer);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
stop?.(1);
|
|
115
|
+
};
|
|
116
|
+
const stopped = new Promise((resolve) => {
|
|
117
|
+
stop = resolve;
|
|
118
|
+
});
|
|
119
|
+
signals?.on('SIGTERM', onSignal);
|
|
120
|
+
signals?.on('SIGINT', onSignal);
|
|
121
|
+
// A command that is only waiting - for its signal, for a message that has
|
|
122
|
+
// not come - holds nothing open, and Node answers an event loop with nothing
|
|
123
|
+
// in it by exiting 13 with a warning about an unsettled await, where Bun
|
|
124
|
+
// simply waits. A command that is running is running until it returns.
|
|
125
|
+
const alive = setInterval(() => { }, 2 ** 30);
|
|
126
|
+
const ran = (async () => {
|
|
127
|
+
try {
|
|
128
|
+
const module = await command.load();
|
|
129
|
+
if (typeof module.default !== 'function') {
|
|
130
|
+
throw new TypeError(`src/commands/${name} has no default export. Export a function: export default async (args) => {}`);
|
|
131
|
+
}
|
|
132
|
+
// Opted in, and only then: a migration that needs the schema should not
|
|
133
|
+
// fail because the queue it never touches is not up.
|
|
134
|
+
if (module.register === true) {
|
|
135
|
+
await options.ready();
|
|
136
|
+
registered = true;
|
|
137
|
+
}
|
|
138
|
+
return exitCodeOf(await module.default(args, { signal: controller.signal }), name);
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
err(`[rsc-kit] the command "${name}" failed: ${error instanceof Error ? (error.stack ?? error.message) : String(error)}\n`);
|
|
142
|
+
return 1;
|
|
143
|
+
}
|
|
144
|
+
})();
|
|
145
|
+
let code;
|
|
146
|
+
try {
|
|
147
|
+
code = await Promise.race([ran, stopped]);
|
|
148
|
+
}
|
|
149
|
+
finally {
|
|
150
|
+
clearInterval(alive);
|
|
151
|
+
clearTimeout(stopTimer);
|
|
152
|
+
signals?.off('SIGTERM', onSignal);
|
|
153
|
+
signals?.off('SIGINT', onSignal);
|
|
154
|
+
}
|
|
155
|
+
// What register() opened is closed here, bounded like the server's: a
|
|
156
|
+
// close that hangs must not hold a release step open.
|
|
157
|
+
if (registered && options.shutdown) {
|
|
158
|
+
let timer;
|
|
159
|
+
await Promise.race([
|
|
160
|
+
Promise.resolve()
|
|
161
|
+
.then(() => options.shutdown?.())
|
|
162
|
+
.catch((error) => err(`[rsc-kit] instrumentation.ts shutdown() failed: ${error instanceof Error ? error.message : String(error)}\n`)),
|
|
163
|
+
new Promise((resolve) => {
|
|
164
|
+
timer = setTimeout(resolve, limit);
|
|
165
|
+
unref(timer);
|
|
166
|
+
}),
|
|
167
|
+
]);
|
|
168
|
+
clearTimeout(timer);
|
|
169
|
+
}
|
|
170
|
+
return code;
|
|
171
|
+
}
|
|
172
|
+
//# 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\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, CommandEntry>, 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 signalled = 0\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 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 signals?.on('SIGTERM', onSignal)\n signals?.on('SIGINT', onSignal)\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, { signal: controller.signal }), 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 signals?.off('SIGTERM', onSignal)\n signals?.off('SIGINT', onSignal)\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/files.d.ts
CHANGED
|
@@ -67,7 +67,9 @@ export declare function assetsReader(assets: AssetsBinding, prefix: string): (na
|
|
|
67
67
|
* whose other Nitro app compiled with it asked why this one could not.
|
|
68
68
|
* `bun build --compile --bytecode .output/server/compile.mjs` works now.
|
|
69
69
|
*/
|
|
70
|
-
export declare function compileEntrySource(dirName: string
|
|
70
|
+
export declare function compileEntrySource(dirName: string, options?: {
|
|
71
|
+
commands?: boolean;
|
|
72
|
+
}): string;
|
|
71
73
|
/** The name the build writes the inline module under, beside the stored pages' directory. */
|
|
72
74
|
export declare function inlineModuleName(dirName: string): string;
|
|
73
75
|
/**
|
package/dist/files.js
CHANGED
|
@@ -264,7 +264,9 @@ function safeUrl(relative, base) {
|
|
|
264
264
|
* whose other Nitro app compiled with it asked why this one could not.
|
|
265
265
|
* `bun build --compile --bytecode .output/server/compile.mjs` works now.
|
|
266
266
|
*/
|
|
267
|
-
export function compileEntrySource(dirName) {
|
|
267
|
+
export function compileEntrySource(dirName, options = {}) {
|
|
268
|
+
if (options.commands)
|
|
269
|
+
return compileEntryWithCommands(dirName);
|
|
268
270
|
return ('// The entry to compile into one binary:\n' +
|
|
269
271
|
'// bun build --compile .output/server/compile.mjs --outfile dist/app\n' +
|
|
270
272
|
'//\n' +
|
|
@@ -277,6 +279,50 @@ export function compileEntrySource(dirName) {
|
|
|
277
279
|
`import "./${inlineModuleName(dirName)}";\n` +
|
|
278
280
|
'import "./index.mjs";\n');
|
|
279
281
|
}
|
|
282
|
+
/**
|
|
283
|
+
* The same entry for an app that has `src/commands`: the binary is the server
|
|
284
|
+
* with no arguments, and a command with one.
|
|
285
|
+
*
|
|
286
|
+
* The server's import is dynamic here, and only here. A static import starts
|
|
287
|
+
* the server as it evaluates, before any line of this file could look at
|
|
288
|
+
* `argv`, and a release step run as `./app migrate` would bind the port the
|
|
289
|
+
* running version holds. It is not awaited - a top-level await is what stopped
|
|
290
|
+
* `--bytecode` from applying to this entry - and a literal specifier is one
|
|
291
|
+
* Bun embeds, as it does the static one.
|
|
292
|
+
*
|
|
293
|
+
* Any argument is a command, and an unknown one is the runner's to refuse
|
|
294
|
+
* (exit 2) rather than a server that ignores it and starts.
|
|
295
|
+
*/
|
|
296
|
+
function compileEntryWithCommands(dirName) {
|
|
297
|
+
return ('// The entry to compile into one binary:\n' +
|
|
298
|
+
'// bun build --compile .output/server/compile.mjs --outfile dist/app\n' +
|
|
299
|
+
'//\n' +
|
|
300
|
+
'// ./app starts the server. ./app <command> runs src/commands/<command>.\n' +
|
|
301
|
+
'//\n' +
|
|
302
|
+
'// A binary carries what is imported by name. The server reads its frozen pages\n' +
|
|
303
|
+
'// through a computed import, which a compile cannot see - imported here, they\n' +
|
|
304
|
+
'// travel inside the binary and are handed to the server before it starts.\n' +
|
|
305
|
+
'// The server itself is imported only when no command was named: a static\n' +
|
|
306
|
+
'// import would start it, and bind its port, before argv could be read. Not\n' +
|
|
307
|
+
'// awaited, since a top-level await is what stopped --bytecode from applying.\n' +
|
|
308
|
+
'import { basename } from "node:path";\n' +
|
|
309
|
+
`import "./${inlineModuleName(dirName)}";\n` +
|
|
310
|
+
'\n' +
|
|
311
|
+
'const failed = (what) => (error) => {\n' +
|
|
312
|
+
' console.error(what, error);\n' +
|
|
313
|
+
' process.exit(1);\n' +
|
|
314
|
+
'};\n' +
|
|
315
|
+
'\n' +
|
|
316
|
+
'if (process.argv.length <= 2) {\n' +
|
|
317
|
+
' import("./index.mjs").catch(failed("[rsc-kit] the server failed to start:"));\n' +
|
|
318
|
+
'} else {\n' +
|
|
319
|
+
' globalThis[Symbol.for("rsc-kit.command")] = true;\n' +
|
|
320
|
+
'\n' +
|
|
321
|
+
' import("./_ssr/rsc.mjs")\n' +
|
|
322
|
+
' .then(({ runCommand }) => runCommand(process.argv.slice(2), "./" + basename(process.execPath)))\n' +
|
|
323
|
+
' .then((code) => process.exit(code), failed("[rsc-kit] the app failed to load:"));\n' +
|
|
324
|
+
'}\n');
|
|
325
|
+
}
|
|
280
326
|
/** The name the build writes the inline module under, beside the stored pages' directory. */
|
|
281
327
|
export function inlineModuleName(dirName) {
|
|
282
328
|
return `${dirName}-inline.mjs`;
|
package/dist/files.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"files.js","sourceRoot":"","sources":["../src/files.ts"],"names":[],"mappings":"AAAA,mCAAmC;AACnC,EAAE;AACF,6EAA6E;AAC7E,6EAA6E;AAC7E,8EAA8E;AAC9E,sEAAsE;AACtE,EAAE;AACF,2EAA2E;AAC3E,8EAA8E;AAC9E,4CAA4C;AAE5C,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAC1E,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAEzC;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAAC,GAAW;IACzC;;;;;;;;;;;;;;;;;OAiBG;IACH,IAAI,OAAO,GAAgC,IAAI,CAAA;IAE/C,MAAM,iBAAiB,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAA;IAC1C,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAkB,CAAA;IAC1C,IAAI,IAAI,GAAG,CAAC,CAAA;IAEZ,MAAM,QAAQ,GAAG,CAAC,IAAY,EAAE,IAAY,EAAQ,EAAE;QACpD,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,CAAA;QAE5B,IAAI,IAAI,GAAG,iBAAiB,GAAG,CAAC;YAAE,OAAM;QAExC,OAAO,IAAI,GAAG,IAAI,GAAG,iBAAiB,IAAI,QAAQ,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;YAC5D,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAe,CAAA;YACrD,IAAI,IAAI,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAE,CAAC,MAAM,GAAG,CAAC,CAAA;YACxC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;QACzB,CAAC;QAED,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;QACxB,IAAI,IAAI,IAAI,CAAA;IACd,CAAC,CAAA;IAED,MAAM,OAAO,GAAG,KAAK,IAA0B,EAAE;QAC/C,OAAO,KAAK,OAAO,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;aAC1C,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;YAC1E,sEAAsE;YACtE,yDAAyD;aACxD,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,GAAG,EAAU,CAAC,CAAA;QAEjC,OAAO,MAAM,OAAO,CAAA;IACtB,CAAC,CAAA;IAED,OAAO,KAAK,EAAE,IAAY,EAA0B,EAAE;QACpD,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;YAAE,OAAO,IAAI,CAAA;QAEpC,IAAI,CAAC,CAAC,MAAM,OAAO,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,OAAO,IAAI,CAAA;QAE7C,MAAM,MAAM,GAAG,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;QAEjC,IAAI,MAAM,KAAK,SAAS;YAAE,OAAO,MAAM,CAAA;QAEvC,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,EAAE,OAAO,CAAC,CAAA;YAErD,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;YAEpB,OAAO,IAAI,CAAA;QACb,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAA;QACb,CAAC;IACH,CAAC,CAAA;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,MAAM,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAA;AAElE,SAAS,aAAa;IACpB,MAAM,KAAK,GAAI,UAAsC,CAAC,cAAc,CAAC,CAAA;IAErE,OAAO,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAE,KAAgC,CAAC,CAAC,CAAC,IAAI,CAAA;AACtF,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,SAAiB,EAAE,OAAe,EAAE,MAAM,GAAG,CAAC;IAC9E,IAAI,MAAM,GAA6D,IAAI,CAAA;IAE3E,MAAM,UAAU,GAAG,KAAK,IAAuD,EAAE;QAC/E,MAAM,QAAQ,GAAG,aAAa,EAAE,CAAA;QAEhC,IAAI,QAAQ;YAAE,OAAO,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,CAAA;QAE3D,IAAI,GAAG,GAAkB,IAAI,CAAA;QAE7B,IAAI,CAAC;YACH,MAAM,EAAE,aAAa,EAAE,GAAG,MAAM,MAAM,CAAC,UAAU,CAAC,CAAA;YAElD,GAAG,GAAG,OAAO,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC,CAAA;QACzC,CAAC;QAAC,MAAM,CAAC;YACP,sEAAsE;YACtE,sCAAsC;QACxC,CAAC;QAED,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;YACjB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;gBACjC,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,CAAA;gBAE5D,IAAI,CAAC;oBACH,MAAM,OAAO,CAAC,SAAS,CAAC,CAAA;oBAExB,OAAO,eAAe,CAAC,SAAS,CAAC,CAAA;gBACnC,CAAC;gBAAC,MAAM,CAAC;oBACP,mCAAmC;gBACrC,CAAC;YACH,CAAC;QACH,CAAC;QAED,qEAAqE;QACrE,mEAAmE;QACnE,MAAM,MAAM,GAAG,MAAM,aAAa,EAAE,CAAA;QAEpC,IAAI,MAAM;YAAE,OAAO,YAAY,CAAC,MAAM,EAAE,IAAI,aAAa,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;QAErE,MAAM,MAAM,GAAG,MAAM,iBAAiB,CAAC,SAAS,EAAE,GAAG,OAAO,aAAa,EAAE,MAAM,CAAC,CAAA;QAElF,OAAO,MAAM,CAAC,CAAC,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC,IAAI,CAAA;IACzE,CAAC,CAAA;IAED,OAAO,KAAK,EAAE,IAAY,EAA0B,EAAE;QACpD,MAAM,KAAK,UAAU,EAAE,CAAA;QAEvB,OAAO,MAAM,CAAC,MAAM,MAAM,CAAC,CAAC,IAAI,CAAC,CAAA;IACnC,CAAC,CAAA;AACH,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,OAAe,EAAU,EAAE,CAAC,IAAI,OAAO,EAAE,CAAA;AAEvE,8EAA8E;AAC9E,KAAK,UAAU,aAAa;IAC1B,IAAI,OAAO,SAAS,KAAK,WAAW,IAAI,SAAS,CAAC,SAAS,KAAK,oBAAoB;QAAE,OAAO,IAAI,CAAA;IAEjG,IAAI,CAAC;QACH,uEAAuE;QACvE,uBAAuB;QACvB,MAAM,SAAS,GAAG,oBAAoB,CAAA;QACtC,MAAM,EAAE,GAAG,EAAE,GAAG,CAAC,MAAM,MAAM,CAAC,kBAAkB,CAAC,SAAS,CAAC,CAAwC,CAAA;QAEnG,OAAO,GAAG,CAAC,MAAM,IAAI,OAAO,GAAG,CAAC,MAAM,CAAC,KAAK,KAAK,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAA;IACjF,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAA;IACb,CAAC;AACH,CAAC;AAMD;;;;;;;GAOG;AACH,MAAM,WAAW,GAAG,IAAI,CAAA;AAExB,MAAM,UAAU,YAAY,CAAC,MAAqB,EAAE,MAAc;IAChE,MAAM,KAAK,GAAG,IAAI,GAAG,EAAkC,CAAA;IAEvD,OAAO,CAAC,IAAI,EAAE,EAAE;QACd,IAAI,OAAO,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;QAE7B,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,mEAAmE;YACnE,wEAAwE;YACxE,gEAAgE;YAChE,IAAI,KAAK,CAAC,IAAI,IAAI,WAAW;gBAAE,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAM,CAAC,CAAA;YAEvE,MAAM,IAAI,GAAG,GAAG,MAAM,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAA;YAE7E,OAAO,GAAG,MAAM;iBACb,KAAK,CAAC,IAAI,OAAO,CAAC,yBAAyB,IAAI,EAAE,CAAC,CAAC;iBACnD,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;iBAC1D,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAA;YAEpB,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;QAC1B,CAAC;QAED,OAAO,OAAO,CAAA;IAChB,CAAC,CAAA;AACH,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,KAAK,UAAU,iBAAiB,CAC9B,SAAiB,EACjB,QAAgB,EAChB,MAAc;IAEd,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACjC,MAAM,QAAQ,GAAG,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAA;QAElD,KAAK,MAAM,SAAS,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC,EAAE,CAAC;YACjE,IAAI,CAAC,SAAS;gBAAE,SAAQ;YAExB,IAAI,CAAC;gBACH,MAAM,MAAM,GAAG,CAAC,MAAM,MAAM,CAAC,kBAAkB,CAAC,SAAS,CAAC,CAAyC,CAAA;gBAEnG,IAAI,MAAM,CAAC,OAAO,IAAI,OAAO,MAAM,CAAC,OAAO,KAAK,QAAQ;oBAAE,OAAO,MAAM,CAAC,OAAO,CAAA;YACjF,CAAC;YAAC,MAAM,CAAC;gBACP,mCAAmC;YACrC,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAA;AACb,CAAC;AAED,SAAS,OAAO,CAAC,QAAgB,EAAE,IAAY;IAC7C,IAAI,CAAC;QACH,OAAO,IAAI,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,IAAI,CAAA;IACrC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAA;IACb,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,kBAAkB,CAAC,OAAe;IAChD,OAAO,CACL,4CAA4C;QAC5C,0EAA0E;QAC1E,MAAM;QACN,MAAM;QACN,mFAAmF;QACnF,kFAAkF;QAClF,8EAA8E;QAC9E,iFAAiF;QACjF,4EAA4E;QAC5E,aAAa,gBAAgB,CAAC,OAAO,CAAC,MAAM;QAC5C,yBAAyB,CAC1B,CAAA;AACH,CAAC;AAED,6FAA6F;AAC7F,MAAM,UAAU,gBAAgB,CAAC,OAAe;IAC9C,OAAO,GAAG,OAAO,aAAa,CAAA;AAChC,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,GAAW;IAClD,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,GAAG,MAAM,MAAM,CAAC,kBAAkB,CAAC,CAAA;IAC1E,MAAM,OAAO,GAA2B,EAAE,CAAA;IAE1C,MAAM,IAAI,GAAG,KAAK,EAAE,GAAW,EAAiB,EAAE;QAChD,KAAK,MAAM,KAAK,IAAI,MAAM,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;YACxE,MAAM,GAAG,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAA;YAErD,IAAI,KAAK,CAAC,WAAW,EAAE;gBAAE,MAAM,IAAI,CAAC,GAAG,CAAC,CAAA;;gBACnC,OAAO,CAAC,GAAG,CAAC,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,OAAO,CAAC,CAAA;QACzD,CAAC;IACH,CAAC,CAAA;IAED,MAAM,IAAI,CAAC,EAAE,CAAC,CAAA;IAEd,sEAAsE;IACtE,yEAAyE;IACzE,2EAA2E;IAC3E,OAAO,CACL,uFAAuF;QACvF,gBAAgB;QAChB,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC;QACvB,KAAK;QACL,6DAA6D;QAC7D,yBAAyB,CAC1B,CAAA;AACH,CAAC;AAED,SAAS,aAAa,CAAC,QAAgB;IACrC,IAAI,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,gCAAgC,CAAA;IACrE,IAAI,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC;QAAE,OAAO,yBAAyB,CAAA;IAC/D,IAAI,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC;QAAE,OAAO,iCAAiC,CAAA;IACvE,IAAI,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC;QAAE,OAAO,eAAe,CAAA;IACrD,IAAI,QAAQ,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAAE,OAAO,YAAY,CAAA;IAEpD,OAAO,0BAA0B,CAAA;AACnC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,OAAO,CAAC,GAAW;IACjC,MAAM,KAAK,GAAG,KAAK,EAAE,IAAY,EAAE,QAAgB,EAAiB,EAAE;QACpE,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;QAE5B,MAAM,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;QAC/C,MAAM,SAAS,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAA;IACjC,CAAC,CAAA;IAED,wEAAwE;IACxE,6EAA6E;IAC7E,wCAAwC;IACxC,OAAO,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,EAAE,GAAG,EAAE,CAAC,CAAA;AACtC,CAAC;AAED;;;;;;GAMG;AACH;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CAAC,IAAY,EAAE,EAAU;IACjD,OAAO,KAAK,IAAmB,EAAE;QAC/B,MAAM,KAAK,CAAC,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;QACpC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IACzC,CAAC,CAAA;AACH,CAAC;AAUD;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,SAAiB,EACjB,EAAU;IAEV,MAAM,OAAO,GAAa,EAAE,CAAA;IAC5B,MAAM,OAAO,GAAmC,EAAE,CAAA;IAClD,IAAI,OAAiB,CAAA;IAErB,IAAI,CAAC;QACH,OAAO,GAAG,CAAC,MAAM,OAAO,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAa,CAAA;IACvE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAA;IAC7B,CAAC;IAED,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;QACnC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,WAAW,CAAC;YAAE,SAAQ;QAE1C,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;QACzE,MAAM,GAAG,GAAG,GAAG,GAAG,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAA;QAChD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,EAAE,OAAO,CAAC,CAAiB,CAAA;QAE1F,IAAI,MAAM,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YAC1B,OAAO,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,WAAW,MAAM,CAAC,MAAM,EAAE,EAAE,CAAC,CAAA;YACtD,SAAQ;QACV,CAAC;QAED,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;YAClB,OAAO,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,mBAAmB,EAAE,CAAC,CAAA;YAC/C,SAAQ;QACV,CAAC;QAED,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAChC,OAAO,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,qDAAqD,EAAE,CAAC,CAAA;YACjF,SAAQ;QACV,CAAC;QAED,MAAM,MAAM,GAAG,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,CAAA;QAE7B,MAAM,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;QACjD,MAAM,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,CAAA;QACpC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IACnB,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAA;AAC7B,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,IAAY,EAAE,EAAU,EAAE,GAAG,GAAG,UAAU;IACnE,OAAO,KAAK,IAAmB,EAAE;QAC/B,MAAM,EAAE,GAAG,GAAG,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,CAAA;QAExC,IAAI,EAAE,KAAK,EAAE;YAAE,OAAM;QAErB,MAAM,MAAM,GAAG,IAAI,CAAC,EAAE,EAAE,EAAE,CAAC,CAAA;QAE3B,MAAM,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;QACjD,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IAC7C,CAAC,CAAA;AACH,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,SAAiB,EAAE,SAAiB,EAAE,YAAoB;IAClG,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,MAAM,MAAM,CAAC,kBAAkB,CAAC,CAAA;IACtG,MAAM,MAAM,GAAG,aAAa,CAAC,SAAS,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,IAAI,YAAY,CAAC,CAAA;IAE5E,IAAI,CAAC;QACH,MAAM,IAAI,CAAC,SAAS,CAAC,CAAA;IACvB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,CAAC,CAAA;IACV,CAAC;IAED,IAAI,MAAM,GAAG,CAAC,CAAA;IACd,MAAM,KAAK,GAAG,KAAK,EAAE,GAAW,EAAiB,EAAE;QACjD,KAAK,MAAM,KAAK,IAAI,MAAM,IAAI,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;YAC7D,IAAI,KAAK,CAAC,WAAW,EAAE;gBAAE,MAAM,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAA;;gBACtD,MAAM,EAAE,CAAA;QACf,CAAC;IACH,CAAC,CAAA;IAED,MAAM,KAAK,CAAC,SAAS,CAAC,CAAA;IACtB,MAAM,EAAE,CAAC,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,MAAM,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IAEjE,qEAAqE;IACrE,mEAAmE;IACnE,sEAAsE;IACtE,+CAA+C;IAC/C,MAAM,EAAE,EAAE,EAAE,GAAG,MAAM,MAAM,CAAC,kBAAkB,CAAC,CAAA;IAE/C,MAAM,EAAE,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;IAErD,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,IAAI,CAAC,YAAY,EAAE,OAAO,CAAC,CAE1D,CAAA;QAED,IAAI,MAAM,CAAC,MAAM,IAAI,MAAM,CAAC,MAAM,CAAC,gBAAgB,KAAK,IAAI,EAAE,CAAC;YAC7D,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC,CAAC,EAAE,CAAA;YACjG,MAAM,OAAO,GAAG,IAAI,MAAM,IAAI,CAAA;YAE9B,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC;gBAAE,MAAM,CAAC,MAAM,CAAC,gBAAgB,GAAG,CAAC,GAAG,KAAK,EAAE,OAAO,CAAC,CAAA;YAElF,MAAM,KAAK,CAAC,YAAY,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAA;QAC5D,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,0EAA0E;QAC1E,uDAAuD;IACzD,CAAC;IAED,OAAO,MAAM,CAAA;AACf,CAAC","sourcesContent":["// Reading built output off a disk.\n//\n// Kept apart from the host on purpose: it is the only part of serving an RSC\n// app that assumes a filesystem, and plenty of places to run one do not have\n// one. Nothing in `@rsc-kit/core/host` imports this module, so a bundle for a\n// host without a filesystem carries no reference to `node:fs` at all.\n//\n// Assets are not here any more — Nitro publishes and serves those. What is\n// left is the prerendered output: written by the build, read by the server it\n// generates, and copied by a static export.\n\nimport { cp, mkdir, readdir, readFile, writeFile } from 'node:fs/promises'\nimport { dirname, join } from 'node:path'\n\n/**\n * Read what the prerenderer wrote, from a directory.\n *\n * Missing is an answer, not an error: a partial prerender is a valid state,\n * and anything not found here is rendered on demand instead.\n */\nexport function prerenderedFrom(dir: string) {\n /**\n * What is on disk, listed once.\n *\n * A miss used to be a thrown ENOENT, and misses are the common case: the\n * host asks for `{url}.html`, then `{url}.ppr.html`, then the route's\n * pattern — so every request to a page served by a pattern shell threw and\n * caught two exceptions before finding anything. Measured at 3,000 req/s\n * against 28,000 for the same page read from memory.\n *\n * Contents are cached for the life of the process. A build's output does\n * not change under a running server - a deploy restarts it, and a server\n * that outlived a rebuild would be serving a stale engine against new\n * pages anyway - so the first read is the only one. Under load the same\n * page used to be opened, read and closed thousands of times a second,\n * which a profile showed as the one cost on a stored page that was ours\n * rather than React's. Capped by bytes, so a site with more pages than\n * memory holds the hot ones and re-reads the rest.\n */\n let present: Promise<Set<string>> | null = null\n\n const CONTENT_CAP_BYTES = 64 * 1024 * 1024\n const contents = new Map<string, string>()\n let held = 0\n\n const remember = (name: string, text: string): void => {\n const size = text.length * 2\n\n if (size > CONTENT_CAP_BYTES / 4) return\n\n while (held + size > CONTENT_CAP_BYTES && contents.size > 0) {\n const oldest = contents.keys().next().value as string\n held -= contents.get(oldest)!.length * 2\n contents.delete(oldest)\n }\n\n contents.set(name, text)\n held += size\n }\n\n const listing = async (): Promise<Set<string>> => {\n present ??= readdir(dir, { recursive: true })\n .then((names) => new Set(names.map((n) => String(n).replace(/\\\\/g, '/'))))\n // An absent directory is an empty one: a partial prerender is a valid\n // state, and everything falls through to being rendered.\n .catch(() => new Set<string>())\n\n return await present\n }\n\n return async (name: string): Promise<string | null> => {\n if (name.includes('..')) return null\n\n if (!(await listing()).has(name)) return null\n\n const cached = contents.get(name)\n\n if (cached !== undefined) return cached\n\n try {\n const text = await readFile(join(dir, name), 'utf-8')\n\n remember(name, text)\n\n return text\n } catch {\n return null\n }\n }\n}\n\n/**\n * The same reader, for a directory whose absolute path is not known until the\n * server runs.\n *\n * Nitro bundles the generated entry to a different depth depending on the\n * preset — `.output/server/index.mjs` for one, `.output/server/_ssr/rsc.mjs`\n * for another — so neither a fixed relative path nor a path baked in at build\n * time survives: the first is wrong for half the presets, and the second stops\n * being true the moment `.output/` is copied to the machine that serves it.\n *\n * So the caller passes its own `import.meta.url` and the directory is found by\n * walking up from it. A few levels, bounded, and resolved once.\n *\n * prerendered: prerenderedBeside(import.meta.url, 'rsc-static')\n *\n * Not finding it is a valid state and not an error: nothing was frozen, or\n * this host has no filesystem to have frozen it on. Every page renders live,\n * which is what happens today everywhere.\n */\n/**\n * Where a compiled binary puts the frozen pages.\n *\n * `bun build --compile` embeds what is imported statically and nothing a\n * computed import names, so the walk below finds no directory and no\n * module inside a binary. The build writes `compile.mjs` beside the\n * server: it imports the inline module by name - which embeds it - and\n * hands the pages over here before importing the server. Shared as a\n * global under a registered symbol, because the wrapper and this module\n * are bundled apart.\n */\nexport const EMBEDDED_PAGES = Symbol.for('rsc-kit.embedded-pages')\n\nfunction embeddedPages(): Record<string, string> | null {\n const pages = (globalThis as Record<symbol, unknown>)[EMBEDDED_PAGES]\n\n return pages && typeof pages === 'object' ? (pages as Record<string, string>) : null\n}\n\nexport function prerenderedBeside(moduleUrl: string, dirName: string, levels = 4) {\n let reader: Promise<(name: string) => Promise<string | null>> | null = null\n\n const resolveDir = async (): Promise<(name: string) => Promise<string | null>> => {\n const embedded = embeddedPages()\n\n if (embedded) return async (name) => embedded[name] ?? null\n\n let dir: string | null = null\n\n try {\n const { fileURLToPath } = await import('node:url')\n\n dir = dirname(fileURLToPath(moduleUrl))\n } catch {\n // No filesystem, or no file: url to speak of. The inline module below\n // is the only place the pages can be.\n }\n\n if (dir !== null) {\n for (let i = 0; i <= levels; i++) {\n const candidate = join(dir, ...Array(i).fill('..'), dirName)\n\n try {\n await readdir(candidate)\n\n return prerenderedFrom(candidate)\n } catch {\n // Not at this level. Keep walking.\n }\n }\n }\n\n // A Worker: the pages were uploaded as static assets, under a prefix\n // the Worker answers first, and are read back through the binding.\n const assets = await workersAssets()\n\n if (assets) return assetsReader(assets, `/${ASSETS_PREFIX(dirName)}`)\n\n const inline = await inlinePrerendered(moduleUrl, `${dirName}-inline.mjs`, levels)\n\n return inline ? async (name) => inline[name] ?? null : async () => null\n }\n\n return async (name: string): Promise<string | null> => {\n reader ??= resolveDir()\n\n return await (await reader)(name)\n }\n}\n\n/**\n * Where the stored pages live among a Worker's static assets.\n *\n * Under a prefix, beside the app's own public files, because that is the\n * one store a Worker has that costs nothing in script size: an inline module\n * counts against the bundle's limit, and a port's 571 stored category pages\n * came to 69 MB of module against a limit of 10. The prefix is one the\n * Worker answers first - `run_worker_first` in wrangler.json, written by\n * the build - so a browser asking for `/_rsc-static/x.ppr.html` reaches the\n * app, which has no such route, rather than the asset; a stored page of a\n * guarded route is served only by the host that runs the guard.\n */\nexport const ASSETS_PREFIX = (dirName: string): string => `_${dirName}`\n\n/** The Workers assets binding, where this runs on Workers and it is bound. */\nasync function workersAssets(): Promise<AssetsBinding | null> {\n if (typeof navigator === 'undefined' || navigator.userAgent !== 'Cloudflare-Workers') return null\n\n try {\n // Computed, so neither the bundler nor tsc looks for a module only the\n // Workers runtime has.\n const specifier = 'cloudflare:workers'\n const { env } = (await import(/* @vite-ignore */ specifier)) as { env: { ASSETS?: AssetsBinding } }\n\n return env.ASSETS && typeof env.ASSETS.fetch === 'function' ? env.ASSETS : null\n } catch {\n return null\n }\n}\n\ninterface AssetsBinding {\n fetch(input: Request | string): Promise<Response>\n}\n\n/**\n * Read stored pages through the assets binding, each name once per isolate.\n *\n * The host asks for `{url}.html`, then `{url}.ppr.html`, then the pattern's,\n * on every request: three asks per page, most of them misses. The binding\n * is a fetch, not a memory read, so the answers - misses included - are kept\n * for the life of the isolate, the way the disk reader keeps its listing.\n */\nconst KNOWN_LIMIT = 4096\n\nexport function assetsReader(assets: AssetsBinding, prefix: string): (name: string) => Promise<string | null> {\n const known = new Map<string, Promise<string | null>>()\n\n return (name) => {\n let pending = known.get(name)\n\n if (!pending) {\n // Bounded. Misses are kept too, and every made-up path is three of\n // them: kept for the life of the isolate, a crawler of random urls grew\n // this without end. Dropping the oldest costs one fetch, later.\n if (known.size >= KNOWN_LIMIT) known.delete(known.keys().next().value!)\n\n const path = `${prefix}/${name.split('/').map(encodeURIComponent).join('/')}`\n\n pending = assets\n .fetch(new Request(`https://assets.invalid${path}`))\n .then((response) => (response.ok ? response.text() : null))\n .catch(() => null)\n\n known.set(name, pending)\n }\n\n return pending\n }\n}\n\n/**\n * The frozen pages as one module, for a runtime with no filesystem.\n *\n * A Worker cannot readdir. Its modules are what wrangler uploaded, and a\n * sibling .mjs beside the bundle is one of them - so the build writes every\n * stored file into `rsc-static-inline.mjs` as strings, and this walks up from\n * the caller the way the disk reader does, importing rather than reading.\n * The specifier is computed, and marked for the bundler to leave alone: at\n * bundle time the file does not exist yet, because it is written by the\n * prerender that runs the bundle.\n *\n * Two forms of the specifier, because runtimes disagree. workerd resolves a\n * relative string against the importing module's name and refuses a url;\n * Node and Bun want the url, since a relative string would resolve against\n * this file rather than the caller's.\n */\nasync function inlinePrerendered(\n moduleUrl: string,\n fileName: string,\n levels: number,\n): Promise<Record<string, string> | null> {\n for (let i = 0; i <= levels; i++) {\n const relative = './' + '../'.repeat(i) + fileName\n\n for (const specifier of [relative, safeUrl(relative, moduleUrl)]) {\n if (!specifier) continue\n\n try {\n const loaded = (await import(/* @vite-ignore */ specifier)) as { default?: Record<string, string> }\n\n if (loaded.default && typeof loaded.default === 'object') return loaded.default\n } catch {\n // Not there, or not at this level.\n }\n }\n }\n\n return null\n}\n\nfunction safeUrl(relative: string, base: string): string | null {\n try {\n return new URL(relative, base).href\n } catch {\n return null\n }\n}\n\n/**\n * The entry a binary is compiled from, written beside the server.\n *\n * Two static imports, in this order: the inline module first - evaluating\n * it hands the pages over - and the server second. A module's imports are\n * evaluated in order, before its own body, so the pages are in place before\n * the server's first line runs. Bun embeds both.\n *\n * Static rather than `await import()`: a top-level await is what made Bun\n * skip `--bytecode` for this entry, silently, byte for byte - and a port\n * whose other Nitro app compiled with it asked why this one could not.\n * `bun build --compile --bytecode .output/server/compile.mjs` works now.\n */\nexport function compileEntrySource(dirName: string): string {\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 '//\\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 '// Two static imports in this order, and no top-level await: imports evaluate\\n' +\n '// in order, and an await here is what stopped --bytecode from applying.\\n' +\n `import \"./${inlineModuleName(dirName)}\";\\n` +\n 'import \"./index.mjs\";\\n'\n )\n}\n\n/** The name the build writes the inline module under, beside the stored pages' directory. */\nexport function inlineModuleName(dirName: string): string {\n return `${dirName}-inline.mjs`\n}\n\n/**\n * Every stored file under `dir`, as the source of the inline module.\n * Strings only: a frozen page, its flight payload, its meta, an api answer.\n */\nexport async function inlineModuleSource(dir: string): Promise<string> {\n const { readdir: list, readFile: read } = await import('node:fs/promises')\n const entries: Record<string, string> = {}\n\n const walk = async (sub: string): Promise<void> => {\n for (const entry of await list(join(dir, sub), { withFileTypes: true })) {\n const rel = sub ? `${sub}/${entry.name}` : entry.name\n\n if (entry.isDirectory()) await walk(rel)\n else entries[rel] = await read(join(dir, rel), 'utf-8')\n }\n }\n\n await walk('')\n\n // Exported for the Worker, which imports it when the directory is not\n // there, and handed over as a global for the binary, whose compile entry\n // imports it first for exactly this side effect. The same data either way.\n return (\n '// @generated by the RSC build: the stored pages, for a runtime with no filesystem.\\n' +\n 'const pages = ' +\n JSON.stringify(entries) +\n ';\\n' +\n 'globalThis[Symbol.for(\"rsc-kit.embedded-pages\")] = pages;\\n' +\n 'export default pages;\\n'\n )\n}\n\nfunction contentTypeOf(pathname: string): string {\n if (pathname.endsWith('.js')) return 'text/javascript; charset=utf-8'\n if (pathname.endsWith('.css')) return 'text/css; charset=utf-8'\n if (pathname.endsWith('.map')) return 'application/json; charset=utf-8'\n if (pathname.endsWith('.svg')) return 'image/svg+xml'\n if (pathname.endsWith('.woff2')) return 'font/woff2'\n\n return 'application/octet-stream'\n}\n\n/**\n * Write build output into a directory.\n *\n * The sink `prerender` and `exportSite` take. Names are relative and may\n * contain directories — `docs/index.html` — so each one's parent is created\n * as it goes.\n */\nexport function writeTo(dir: string): ((name: string, contents: string) => Promise<void>) & { dir: string } {\n const write = async (name: string, contents: string): Promise<void> => {\n const path = join(dir, name)\n\n await mkdir(dirname(path), { recursive: true })\n await writeFile(path, contents)\n }\n\n // Where it wrote, for a caller that has to read it back. The embeddable\n // module has to list these files, and being told the directory a second time\n // is a second place for it to be wrong.\n return Object.assign(write, { dir })\n}\n\n/**\n * Copy the built browser bundle into an exported site.\n *\n * Separate from the export itself because copying a tree of files is a\n * filesystem operation by nature — a deploy that uploads them to a bucket\n * has its own way to do that, and passes its own callback instead.\n */\n/**\n * The whole client output into an exported site: every file a server would\n * have answered as a file - the bundle, public/, icons, the web manifest,\n * the service worker. Precompressed copies (.br, .gz, .zst) come too; a host\n * that does not serve them ignores them.\n */\nexport function copyPublic(from: string, to: string) {\n return async (): Promise<void> => {\n await mkdir(to, { recursive: true })\n await cp(from, to, { recursive: true })\n }\n}\n\n/** A stored route.ts answer, as prerender writes it. */\ninterface StoredAnswer {\n status: number\n headers: [string, string][]\n body: string\n varies: boolean\n}\n\n/**\n * Stored route.ts answers written out as the files they are: the body at the\n * url, `robots.txt` at robots.txt. Only answers a static host can give as\n * they were - a 200 the same for everyone, at a url with an extension, since\n * a file is typed by its extension and the stored content type would be lost.\n */\nexport async function exportStoredAnswers(\n staticDir: string,\n to: string,\n): Promise<{ written: string[]; skipped: { url: string; why: string }[] }> {\n const written: string[] = []\n const skipped: { url: string; why: string }[] = []\n let entries: string[]\n\n try {\n entries = (await readdir(staticDir, { recursive: true })) as string[]\n } catch {\n return { written, skipped }\n }\n\n for (const entry of entries.sort()) {\n if (!entry.endsWith('.api.json')) continue\n\n const path = entry.slice(0, -'.api.json'.length).split(/[\\\\/]/).join('/')\n const url = '/' + (path === 'index' ? '' : path)\n const answer = JSON.parse(await readFile(join(staticDir, entry), 'utf-8')) as StoredAnswer\n\n if (answer.status !== 200) {\n skipped.push({ url, why: `answers ${answer.status}` })\n continue\n }\n\n if (answer.varies) {\n skipped.push({ url, why: 'varies by request' })\n continue\n }\n\n if (!/\\.[a-z0-9]+$/i.test(path)) {\n skipped.push({ url, why: 'no extension to give a static host its content type' })\n continue\n }\n\n const target = join(to, path)\n\n await mkdir(dirname(target), { recursive: true })\n await writeFile(target, answer.body)\n written.push(url)\n }\n\n return { written, skipped }\n}\n\nexport function copyAssets(from: string, to: string, url = '/assets/') {\n return async (): Promise<void> => {\n const at = url.replace(/^\\/+|\\/+$/g, '')\n\n if (at === '') return\n\n const target = join(to, at)\n\n await mkdir(dirname(target), { recursive: true })\n await cp(from, target, { recursive: true })\n }\n}\n\n/**\n * Put the stored pages among a Worker's static assets, and have the Worker\n * answer their prefix first.\n *\n * Run after Nitro has written its public-asset list and wrangler.json, so\n * the copies are in neither: not served as assets ahead of the Worker, and\n * not needing a wrangler key Nitro would override. `run_worker_first` is\n * added to the written config for the one prefix. The server-side directory\n * is removed once copied, so nothing in it is uploaded twice. Returns how\n * many files were moved; none when nothing was stored.\n */\nexport async function storedPagesToAssets(staticDir: string, publicDir: string, wranglerPath: string): Promise<number> {\n const { cp, readFile: read, writeFile: write, stat, readdir: list } = await import('node:fs/promises')\n const prefix = ASSETS_PREFIX(staticDir.split(/[\\\\/]/).pop() || 'rsc-static')\n\n try {\n await stat(staticDir)\n } catch {\n return 0\n }\n\n let copied = 0\n const count = async (dir: string): Promise<void> => {\n for (const entry of await list(dir, { withFileTypes: true })) {\n if (entry.isDirectory()) await count(join(dir, entry.name))\n else copied++\n }\n }\n\n await count(staticDir)\n await cp(staticDir, join(publicDir, prefix), { recursive: true })\n\n // Moved, not copied: wrangler's default rules upload every .html and\n // .txt under the server directory as a text module, and 573 stored\n // shells left beside the bundle were 55 MB of modules against a 10 MB\n // limit - a deploy that happened to fit, once.\n const { rm } = await import('node:fs/promises')\n\n await rm(staticDir, { recursive: true, force: true })\n\n try {\n const config = JSON.parse(await read(wranglerPath, 'utf-8')) as {\n assets?: { run_worker_first?: boolean | string[] } & Record<string, unknown>\n }\n\n if (config.assets && config.assets.run_worker_first !== true) {\n const first = Array.isArray(config.assets.run_worker_first) ? config.assets.run_worker_first : []\n const pattern = `/${prefix}/*`\n\n if (!first.includes(pattern)) config.assets.run_worker_first = [...first, pattern]\n\n await write(wranglerPath, JSON.stringify(config, null, 2))\n }\n } catch {\n // No wrangler.json to amend: a deploy config the app writes itself, which\n // then carries the prefix in its own run_worker_first.\n }\n\n return copied\n}\n"]}
|
|
1
|
+
{"version":3,"file":"files.js","sourceRoot":"","sources":["../src/files.ts"],"names":[],"mappings":"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","sourcesContent":["// Reading built output off a disk.\n//\n// Kept apart from the host on purpose: it is the only part of serving an RSC\n// app that assumes a filesystem, and plenty of places to run one do not have\n// one. Nothing in `@rsc-kit/core/host` imports this module, so a bundle for a\n// host without a filesystem carries no reference to `node:fs` at all.\n//\n// Assets are not here any more — Nitro publishes and serves those. What is\n// left is the prerendered output: written by the build, read by the server it\n// generates, and copied by a static export.\n\nimport { cp, mkdir, readdir, readFile, writeFile } from 'node:fs/promises'\nimport { dirname, join } from 'node:path'\n\n/**\n * Read what the prerenderer wrote, from a directory.\n *\n * Missing is an answer, not an error: a partial prerender is a valid state,\n * and anything not found here is rendered on demand instead.\n */\nexport function prerenderedFrom(dir: string) {\n /**\n * What is on disk, listed once.\n *\n * A miss used to be a thrown ENOENT, and misses are the common case: the\n * host asks for `{url}.html`, then `{url}.ppr.html`, then the route's\n * pattern — so every request to a page served by a pattern shell threw and\n * caught two exceptions before finding anything. Measured at 3,000 req/s\n * against 28,000 for the same page read from memory.\n *\n * Contents are cached for the life of the process. A build's output does\n * not change under a running server - a deploy restarts it, and a server\n * that outlived a rebuild would be serving a stale engine against new\n * pages anyway - so the first read is the only one. Under load the same\n * page used to be opened, read and closed thousands of times a second,\n * which a profile showed as the one cost on a stored page that was ours\n * rather than React's. Capped by bytes, so a site with more pages than\n * memory holds the hot ones and re-reads the rest.\n */\n let present: Promise<Set<string>> | null = null\n\n const CONTENT_CAP_BYTES = 64 * 1024 * 1024\n const contents = new Map<string, string>()\n let held = 0\n\n const remember = (name: string, text: string): void => {\n const size = text.length * 2\n\n if (size > CONTENT_CAP_BYTES / 4) return\n\n while (held + size > CONTENT_CAP_BYTES && contents.size > 0) {\n const oldest = contents.keys().next().value as string\n held -= contents.get(oldest)!.length * 2\n contents.delete(oldest)\n }\n\n contents.set(name, text)\n held += size\n }\n\n const listing = async (): Promise<Set<string>> => {\n present ??= readdir(dir, { recursive: true })\n .then((names) => new Set(names.map((n) => String(n).replace(/\\\\/g, '/'))))\n // An absent directory is an empty one: a partial prerender is a valid\n // state, and everything falls through to being rendered.\n .catch(() => new Set<string>())\n\n return await present\n }\n\n return async (name: string): Promise<string | null> => {\n if (name.includes('..')) return null\n\n if (!(await listing()).has(name)) return null\n\n const cached = contents.get(name)\n\n if (cached !== undefined) return cached\n\n try {\n const text = await readFile(join(dir, name), 'utf-8')\n\n remember(name, text)\n\n return text\n } catch {\n return null\n }\n }\n}\n\n/**\n * The same reader, for a directory whose absolute path is not known until the\n * server runs.\n *\n * Nitro bundles the generated entry to a different depth depending on the\n * preset — `.output/server/index.mjs` for one, `.output/server/_ssr/rsc.mjs`\n * for another — so neither a fixed relative path nor a path baked in at build\n * time survives: the first is wrong for half the presets, and the second stops\n * being true the moment `.output/` is copied to the machine that serves it.\n *\n * So the caller passes its own `import.meta.url` and the directory is found by\n * walking up from it. A few levels, bounded, and resolved once.\n *\n * prerendered: prerenderedBeside(import.meta.url, 'rsc-static')\n *\n * Not finding it is a valid state and not an error: nothing was frozen, or\n * this host has no filesystem to have frozen it on. Every page renders live,\n * which is what happens today everywhere.\n */\n/**\n * Where a compiled binary puts the frozen pages.\n *\n * `bun build --compile` embeds what is imported statically and nothing a\n * computed import names, so the walk below finds no directory and no\n * module inside a binary. The build writes `compile.mjs` beside the\n * server: it imports the inline module by name - which embeds it - and\n * hands the pages over here before importing the server. Shared as a\n * global under a registered symbol, because the wrapper and this module\n * are bundled apart.\n */\nexport const EMBEDDED_PAGES = Symbol.for('rsc-kit.embedded-pages')\n\nfunction embeddedPages(): Record<string, string> | null {\n const pages = (globalThis as Record<symbol, unknown>)[EMBEDDED_PAGES]\n\n return pages && typeof pages === 'object' ? (pages as Record<string, string>) : null\n}\n\nexport function prerenderedBeside(moduleUrl: string, dirName: string, levels = 4) {\n let reader: Promise<(name: string) => Promise<string | null>> | null = null\n\n const resolveDir = async (): Promise<(name: string) => Promise<string | null>> => {\n const embedded = embeddedPages()\n\n if (embedded) return async (name) => embedded[name] ?? null\n\n let dir: string | null = null\n\n try {\n const { fileURLToPath } = await import('node:url')\n\n dir = dirname(fileURLToPath(moduleUrl))\n } catch {\n // No filesystem, or no file: url to speak of. The inline module below\n // is the only place the pages can be.\n }\n\n if (dir !== null) {\n for (let i = 0; i <= levels; i++) {\n const candidate = join(dir, ...Array(i).fill('..'), dirName)\n\n try {\n await readdir(candidate)\n\n return prerenderedFrom(candidate)\n } catch {\n // Not at this level. Keep walking.\n }\n }\n }\n\n // A Worker: the pages were uploaded as static assets, under a prefix\n // the Worker answers first, and are read back through the binding.\n const assets = await workersAssets()\n\n if (assets) return assetsReader(assets, `/${ASSETS_PREFIX(dirName)}`)\n\n const inline = await inlinePrerendered(moduleUrl, `${dirName}-inline.mjs`, levels)\n\n return inline ? async (name) => inline[name] ?? null : async () => null\n }\n\n return async (name: string): Promise<string | null> => {\n reader ??= resolveDir()\n\n return await (await reader)(name)\n }\n}\n\n/**\n * Where the stored pages live among a Worker's static assets.\n *\n * Under a prefix, beside the app's own public files, because that is the\n * one store a Worker has that costs nothing in script size: an inline module\n * counts against the bundle's limit, and a port's 571 stored category pages\n * came to 69 MB of module against a limit of 10. The prefix is one the\n * Worker answers first - `run_worker_first` in wrangler.json, written by\n * the build - so a browser asking for `/_rsc-static/x.ppr.html` reaches the\n * app, which has no such route, rather than the asset; a stored page of a\n * guarded route is served only by the host that runs the guard.\n */\nexport const ASSETS_PREFIX = (dirName: string): string => `_${dirName}`\n\n/** The Workers assets binding, where this runs on Workers and it is bound. */\nasync function workersAssets(): Promise<AssetsBinding | null> {\n if (typeof navigator === 'undefined' || navigator.userAgent !== 'Cloudflare-Workers') return null\n\n try {\n // Computed, so neither the bundler nor tsc looks for a module only the\n // Workers runtime has.\n const specifier = 'cloudflare:workers'\n const { env } = (await import(/* @vite-ignore */ specifier)) as { env: { ASSETS?: AssetsBinding } }\n\n return env.ASSETS && typeof env.ASSETS.fetch === 'function' ? env.ASSETS : null\n } catch {\n return null\n }\n}\n\ninterface AssetsBinding {\n fetch(input: Request | string): Promise<Response>\n}\n\n/**\n * Read stored pages through the assets binding, each name once per isolate.\n *\n * The host asks for `{url}.html`, then `{url}.ppr.html`, then the pattern's,\n * on every request: three asks per page, most of them misses. The binding\n * is a fetch, not a memory read, so the answers - misses included - are kept\n * for the life of the isolate, the way the disk reader keeps its listing.\n */\nconst KNOWN_LIMIT = 4096\n\nexport function assetsReader(assets: AssetsBinding, prefix: string): (name: string) => Promise<string | null> {\n const known = new Map<string, Promise<string | null>>()\n\n return (name) => {\n let pending = known.get(name)\n\n if (!pending) {\n // Bounded. Misses are kept too, and every made-up path is three of\n // them: kept for the life of the isolate, a crawler of random urls grew\n // this without end. Dropping the oldest costs one fetch, later.\n if (known.size >= KNOWN_LIMIT) known.delete(known.keys().next().value!)\n\n const path = `${prefix}/${name.split('/').map(encodeURIComponent).join('/')}`\n\n pending = assets\n .fetch(new Request(`https://assets.invalid${path}`))\n .then((response) => (response.ok ? response.text() : null))\n .catch(() => null)\n\n known.set(name, pending)\n }\n\n return pending\n }\n}\n\n/**\n * The frozen pages as one module, for a runtime with no filesystem.\n *\n * A Worker cannot readdir. Its modules are what wrangler uploaded, and a\n * sibling .mjs beside the bundle is one of them - so the build writes every\n * stored file into `rsc-static-inline.mjs` as strings, and this walks up from\n * the caller the way the disk reader does, importing rather than reading.\n * The specifier is computed, and marked for the bundler to leave alone: at\n * bundle time the file does not exist yet, because it is written by the\n * prerender that runs the bundle.\n *\n * Two forms of the specifier, because runtimes disagree. workerd resolves a\n * relative string against the importing module's name and refuses a url;\n * Node and Bun want the url, since a relative string would resolve against\n * this file rather than the caller's.\n */\nasync function inlinePrerendered(\n moduleUrl: string,\n fileName: string,\n levels: number,\n): Promise<Record<string, string> | null> {\n for (let i = 0; i <= levels; i++) {\n const relative = './' + '../'.repeat(i) + fileName\n\n for (const specifier of [relative, safeUrl(relative, moduleUrl)]) {\n if (!specifier) continue\n\n try {\n const loaded = (await import(/* @vite-ignore */ specifier)) as { default?: Record<string, string> }\n\n if (loaded.default && typeof loaded.default === 'object') return loaded.default\n } catch {\n // Not there, or not at this level.\n }\n }\n }\n\n return null\n}\n\nfunction safeUrl(relative: string, base: string): string | null {\n try {\n return new URL(relative, base).href\n } catch {\n return null\n }\n}\n\n/**\n * The entry a binary is compiled from, written beside the server.\n *\n * Two static imports, in this order: the inline module first - evaluating\n * it hands the pages over - and the server second. A module's imports are\n * evaluated in order, before its own body, so the pages are in place before\n * the server's first line runs. Bun embeds both.\n *\n * Static rather than `await import()`: a top-level await is what made Bun\n * skip `--bytecode` for this entry, silently, byte for byte - and a port\n * whose other Nitro app compiled with it asked why this one could not.\n * `bun build --compile --bytecode .output/server/compile.mjs` works now.\n */\nexport function compileEntrySource(dirName: string, options: { commands?: boolean } = {}): string {\n if (options.commands) return compileEntryWithCommands(dirName)\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 '//\\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 '// Two static imports in this order, and no top-level await: imports evaluate\\n' +\n '// in order, and an await here is what stopped --bytecode from applying.\\n' +\n `import \"./${inlineModuleName(dirName)}\";\\n` +\n 'import \"./index.mjs\";\\n'\n )\n}\n\n/**\n * The same entry for an app that has `src/commands`: the binary is the server\n * 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.\n *\n * Any argument is a command, and an unknown one is the runner's to refuse\n * (exit 2) rather than a server that ignores it and starts.\n */\nfunction compileEntryWithCommands(dirName: string): string {\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 '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 ' globalThis[Symbol.for(\"rsc-kit.command\")] = true;\\n' +\n '\\n' +\n ' import(\"./_ssr/rsc.mjs\")\\n' +\n ' .then(({ runCommand }) => runCommand(process.argv.slice(2), \"./\" + basename(process.execPath)))\\n' +\n ' .then((code) => process.exit(code), failed(\"[rsc-kit] the app failed to load:\"));\\n' +\n '}\\n'\n )\n}\n\n/** The name the build writes the inline module under, beside the stored pages' directory. */\nexport function inlineModuleName(dirName: string): string {\n return `${dirName}-inline.mjs`\n}\n\n/**\n * Every stored file under `dir`, as the source of the inline module.\n * Strings only: a frozen page, its flight payload, its meta, an api answer.\n */\nexport async function inlineModuleSource(dir: string): Promise<string> {\n const { readdir: list, readFile: read } = await import('node:fs/promises')\n const entries: Record<string, string> = {}\n\n const walk = async (sub: string): Promise<void> => {\n for (const entry of await list(join(dir, sub), { withFileTypes: true })) {\n const rel = sub ? `${sub}/${entry.name}` : entry.name\n\n if (entry.isDirectory()) await walk(rel)\n else entries[rel] = await read(join(dir, rel), 'utf-8')\n }\n }\n\n await walk('')\n\n // Exported for the Worker, which imports it when the directory is not\n // there, and handed over as a global for the binary, whose compile entry\n // imports it first for exactly this side effect. The same data either way.\n return (\n '// @generated by the RSC build: the stored pages, for a runtime with no filesystem.\\n' +\n 'const pages = ' +\n JSON.stringify(entries) +\n ';\\n' +\n 'globalThis[Symbol.for(\"rsc-kit.embedded-pages\")] = pages;\\n' +\n 'export default pages;\\n'\n )\n}\n\nfunction contentTypeOf(pathname: string): string {\n if (pathname.endsWith('.js')) return 'text/javascript; charset=utf-8'\n if (pathname.endsWith('.css')) return 'text/css; charset=utf-8'\n if (pathname.endsWith('.map')) return 'application/json; charset=utf-8'\n if (pathname.endsWith('.svg')) return 'image/svg+xml'\n if (pathname.endsWith('.woff2')) return 'font/woff2'\n\n return 'application/octet-stream'\n}\n\n/**\n * Write build output into a directory.\n *\n * The sink `prerender` and `exportSite` take. Names are relative and may\n * contain directories — `docs/index.html` — so each one's parent is created\n * as it goes.\n */\nexport function writeTo(dir: string): ((name: string, contents: string) => Promise<void>) & { dir: string } {\n const write = async (name: string, contents: string): Promise<void> => {\n const path = join(dir, name)\n\n await mkdir(dirname(path), { recursive: true })\n await writeFile(path, contents)\n }\n\n // Where it wrote, for a caller that has to read it back. The embeddable\n // module has to list these files, and being told the directory a second time\n // is a second place for it to be wrong.\n return Object.assign(write, { dir })\n}\n\n/**\n * Copy the built browser bundle into an exported site.\n *\n * Separate from the export itself because copying a tree of files is a\n * filesystem operation by nature — a deploy that uploads them to a bucket\n * has its own way to do that, and passes its own callback instead.\n */\n/**\n * The whole client output into an exported site: every file a server would\n * have answered as a file - the bundle, public/, icons, the web manifest,\n * the service worker. Precompressed copies (.br, .gz, .zst) come too; a host\n * that does not serve them ignores them.\n */\nexport function copyPublic(from: string, to: string) {\n return async (): Promise<void> => {\n await mkdir(to, { recursive: true })\n await cp(from, to, { recursive: true })\n }\n}\n\n/** A stored route.ts answer, as prerender writes it. */\ninterface StoredAnswer {\n status: number\n headers: [string, string][]\n body: string\n varies: boolean\n}\n\n/**\n * Stored route.ts answers written out as the files they are: the body at the\n * url, `robots.txt` at robots.txt. Only answers a static host can give as\n * they were - a 200 the same for everyone, at a url with an extension, since\n * a file is typed by its extension and the stored content type would be lost.\n */\nexport async function exportStoredAnswers(\n staticDir: string,\n to: string,\n): Promise<{ written: string[]; skipped: { url: string; why: string }[] }> {\n const written: string[] = []\n const skipped: { url: string; why: string }[] = []\n let entries: string[]\n\n try {\n entries = (await readdir(staticDir, { recursive: true })) as string[]\n } catch {\n return { written, skipped }\n }\n\n for (const entry of entries.sort()) {\n if (!entry.endsWith('.api.json')) continue\n\n const path = entry.slice(0, -'.api.json'.length).split(/[\\\\/]/).join('/')\n const url = '/' + (path === 'index' ? '' : path)\n const answer = JSON.parse(await readFile(join(staticDir, entry), 'utf-8')) as StoredAnswer\n\n if (answer.status !== 200) {\n skipped.push({ url, why: `answers ${answer.status}` })\n continue\n }\n\n if (answer.varies) {\n skipped.push({ url, why: 'varies by request' })\n continue\n }\n\n if (!/\\.[a-z0-9]+$/i.test(path)) {\n skipped.push({ url, why: 'no extension to give a static host its content type' })\n continue\n }\n\n const target = join(to, path)\n\n await mkdir(dirname(target), { recursive: true })\n await writeFile(target, answer.body)\n written.push(url)\n }\n\n return { written, skipped }\n}\n\nexport function copyAssets(from: string, to: string, url = '/assets/') {\n return async (): Promise<void> => {\n const at = url.replace(/^\\/+|\\/+$/g, '')\n\n if (at === '') return\n\n const target = join(to, at)\n\n await mkdir(dirname(target), { recursive: true })\n await cp(from, target, { recursive: true })\n }\n}\n\n/**\n * Put the stored pages among a Worker's static assets, and have the Worker\n * answer their prefix first.\n *\n * Run after Nitro has written its public-asset list and wrangler.json, so\n * the copies are in neither: not served as assets ahead of the Worker, and\n * not needing a wrangler key Nitro would override. `run_worker_first` is\n * added to the written config for the one prefix. The server-side directory\n * is removed once copied, so nothing in it is uploaded twice. Returns how\n * many files were moved; none when nothing was stored.\n */\nexport async function storedPagesToAssets(staticDir: string, publicDir: string, wranglerPath: string): Promise<number> {\n const { cp, readFile: read, writeFile: write, stat, readdir: list } = await import('node:fs/promises')\n const prefix = ASSETS_PREFIX(staticDir.split(/[\\\\/]/).pop() || 'rsc-static')\n\n try {\n await stat(staticDir)\n } catch {\n return 0\n }\n\n let copied = 0\n const count = async (dir: string): Promise<void> => {\n for (const entry of await list(dir, { withFileTypes: true })) {\n if (entry.isDirectory()) await count(join(dir, entry.name))\n else copied++\n }\n }\n\n await count(staticDir)\n await cp(staticDir, join(publicDir, prefix), { recursive: true })\n\n // Moved, not copied: wrangler's default rules upload every .html and\n // .txt under the server directory as a text module, and 573 stored\n // shells left beside the bundle were 55 MB of modules against a 10 MB\n // limit - a deploy that happened to fit, once.\n const { rm } = await import('node:fs/promises')\n\n await rm(staticDir, { recursive: true, force: true })\n\n try {\n const config = JSON.parse(await read(wranglerPath, 'utf-8')) as {\n assets?: { run_worker_first?: boolean | string[] } & Record<string, unknown>\n }\n\n if (config.assets && config.assets.run_worker_first !== true) {\n const first = Array.isArray(config.assets.run_worker_first) ? config.assets.run_worker_first : []\n const pattern = `/${prefix}/*`\n\n if (!first.includes(pattern)) config.assets.run_worker_first = [...first, pattern]\n\n await write(wranglerPath, JSON.stringify(config, null, 2))\n }\n } catch {\n // No wrangler.json to amend: a deploy config the app writes itself, which\n // then carries the prefix in its own run_worker_first.\n }\n\n return copied\n}\n"]}
|
package/dist/metadata.d.ts
CHANGED
|
@@ -42,6 +42,10 @@ export interface OpenGraph {
|
|
|
42
42
|
siteName?: string;
|
|
43
43
|
type?: "website" | "article" | "profile" | "book" | (string & {});
|
|
44
44
|
locale?: string;
|
|
45
|
+
/**
|
|
46
|
+
* Replaces the `opengraph-image.png` of any folder above this page, rather
|
|
47
|
+
* than following it. A file in the page's own folder wins over this.
|
|
48
|
+
*/
|
|
45
49
|
images?: string | URL | OpenGraphImage | (string | URL | OpenGraphImage)[];
|
|
46
50
|
}
|
|
47
51
|
/** The same card for X, which reads `name=` rather than `property=`. */
|
package/dist/metadata.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"metadata.js","sourceRoot":"","sources":["../src/metadata.ts"],"names":[],"mappings":"AAAA,sDAAsD;AACtD,EAAE;AACF,6DAA6D;AAC7D,EAAE;AACF,4DAA4D;AAC5D,EAAE;AACF,4EAA4E;AAC5E,+EAA+E;AAC/E,+EAA+E;AAC/E,4EAA4E;AAC5E,2EAA2E;AAC3E,EAAE;AACF,4EAA4E;AAC5E,+EAA+E;AAC/E,MAAM","sourcesContent":["// What a page says about itself, as importable types.\n//\n// import type { Metadata } from '@rsc-kit/core/metadata'\n//\n// export const metadata: Metadata = { title: 'Orders' }\n//\n// Imported rather than ambient, and that is the whole point of the move. An\n// ambient declaration has to be COPIED into the project, which means it is not\n// there until the build has run once — so a freshly cloned app reports \"Cannot\n// find name 'Metadata'\" on every page until someone runs the dev server. An\n// import resolves from node_modules the moment dependencies are installed.\n//\n// The ambient names still work. `.rsc-kit/rsc-types.d.ts` now aliases these\n// rather than restating them, so there is one definition and two ways to reach\n// it.\n\nexport interface IconDescriptor {\n url: string | URL;\n type?: string;\n sizes?: string;\n color?: string;\n rel?: string;\n media?: string;\n fetchPriority?: \"high\" | \"low\" | \"auto\";\n}\n\nexport type IconURL = string | URL;\n\nexport interface Icons {\n icon?: IconURL | IconDescriptor | (IconURL | IconDescriptor)[];\n apple?: IconURL | IconDescriptor | (IconURL | IconDescriptor)[];\n shortcut?: IconURL | IconDescriptor | (IconURL | IconDescriptor)[];\n other?: IconDescriptor | IconDescriptor[];\n}\n\n/** A layout's title, wrapping the titles of the pages beneath it. */\nexport interface TitleTemplate {\n /** `%s` stands in for the page's own title. */\n template?: string;\n /** Used by a page that exports no title of its own. */\n default?: string;\n}\n\n/** One image a share card may show. A string is its url. */\nexport interface OpenGraphImage {\n url: string | URL;\n width?: number;\n height?: number;\n alt?: string;\n type?: string;\n}\n\n/**\n * The card a link to this page unfurls into on Facebook, Slack, LinkedIn and\n * most of the rest. Rendered with `property=`, which is what those scrapers\n * read — a `name=` attribute is ignored by every one of them.\n */\nexport interface OpenGraph {\n title?: string;\n description?: string;\n /** Absolute, or relative to `metadataBase`. */\n url?: string | URL;\n siteName?: string;\n type?: \"website\" | \"article\" | \"profile\" | \"book\" | (string & {});\n locale?: string;\n images?: string | URL | OpenGraphImage | (string | URL | OpenGraphImage)[];\n}\n\n/** The same card for X, which reads `name=` rather than `property=`. */\nexport interface Twitter {\n card?: \"summary\" | \"summary_large_image\" | \"app\" | \"player\";\n title?: string;\n description?: string;\n /** The site's account, `@handle`. */\n site?: string;\n /** The author's account, `@handle`. */\n creator?: string;\n images?: string | URL | OpenGraphImage | (string | URL | OpenGraphImage)[];\n}\n\nexport interface Robots {\n index?: boolean;\n follow?: boolean;\n noarchive?: boolean;\n nosnippet?: boolean;\n noimageindex?: boolean;\n nocache?: boolean;\n notranslate?: boolean;\n indexifembedded?: boolean;\n nositelinkssearchbox?: boolean;\n unavailable_after?: string;\n \"max-video-preview\"?: number | string;\n \"max-image-preview\"?: \"none\" | \"standard\" | \"large\";\n \"max-snippet\"?: number;\n googleBot?: string | Omit<Robots, \"googleBot\">;\n}\n\n/** An iOS launch screen: an image, and the media query naming the device it is for. */\nexport interface AppleStartupImage {\n url: string | URL;\n media?: string;\n}\n\nexport interface AppleWebApp {\n /** Opens as an app from the home screen - no browser chrome. */\n capable?: boolean;\n /** The name under the home screen icon, when it should differ from the page title. */\n title?: string;\n /** The status bar over the app: `default`, `black`, or `black-translucent` to draw under it. */\n statusBarStyle?: \"default\" | \"black\" | \"black-translucent\";\n /** Launch screens. A string or url is one image for every device. */\n startupImage?: string | URL | (string | URL | AppleStartupImage)[];\n}\n\nexport interface Metadata {\n /** A string on a page; a template on a layout, applied to the pages below it. */\n title?: string | TitleTemplate;\n description?: string;\n keywords?: string | string[];\n author?: string;\n /**\n * A string, or the object Next takes: `{ index: false, follow: false }`\n * becomes `<meta name=\"robots\" content=\"noindex, nofollow\">`, the flags\n * by name, the limits as `name:value`; `googleBot` is the same shape for\n * `<meta name=\"googlebot\">`.\n */\n robots?: string | Robots;\n /**\n * Where the site lives, so a relative image or url can be made absolute.\n *\n * metadataBase: new URL('https://example.com')\n *\n * On the root layout, once. A share-card scraper needs an absolute url and\n * several refuse a relative one; without this, `og:image` for an image in\n * `app/` is emitted relative and works in some places and not others. The\n * same name as Next, so a port carries it across unchanged.\n */\n metadataBase?: string | URL;\n icons?: IconURL | (IconURL | IconDescriptor)[] | Icons | null;\n openGraph?: OpenGraph;\n twitter?: Twitter;\n /**\n * How the app behaves added to an iPhone's home screen. Next's shape, so a\n * port carries it across unchanged.\n *\n * appleWebApp: {\n * capable: true,\n * title: 'Orders',\n * statusBarStyle: 'black-translucent',\n * startupImage: [{ url: '/splash-1179x2556.png', media: '...' }],\n * }\n *\n * `true` is `{ capable: true }`. Launch screens are simpler as files: an\n * `apple-splash-1179x2556.png` in `app/` is linked with the right media\n * query for the device its size names - see the PWA guide.\n */\n appleWebApp?: boolean | AppleWebApp;\n /** @deprecated Use `openGraph.title`. Still rendered, correctly, as `property=`. */\n \"og:title\"?: string;\n /** @deprecated Use `openGraph.description`. */\n \"og:description\"?: string;\n /** @deprecated Use `openGraph.images`. */\n \"og:image\"?: string;\n /** @deprecated Use `openGraph.url`. */\n \"og:url\"?: string;\n /** @deprecated Use `openGraph.type`. */\n \"og:type\"?: string;\n /** @deprecated Use `openGraph.siteName`. */\n \"og:site_name\"?: string;\n /** @deprecated Use `twitter.card`. */\n \"twitter:card\"?: string;\n /** @deprecated Use `twitter.title`. */\n \"twitter:title\"?: string;\n /** @deprecated Use `twitter.description`. */\n \"twitter:description\"?: string;\n /** @deprecated Use `twitter.images`. */\n \"twitter:image\"?: string;\n /** @deprecated Use `twitter.site`. */\n \"twitter:site\"?: string;\n\n /**\n * Any other meta tag, by name.\n *\n * other: { 'fb:app_id': '123', 'theme-color': '#000' }\n *\n * Here rather than alongside the named keys, and that is what makes the rest\n * of this interface worth annotating. An index signature on the interface\n * itself made every key legal — so `titel` was accepted in silence, and an\n * editor offered no completions at all, because with any identifier valid\n * TypeScript reads an unfinished key as a shorthand property and goes looking\n * for a variable by that name.\n */\n other?: Record<string, string | string[] | null | undefined>;\n}\n\n/**\n * Metadata that depends on the request.\n *\n * Receives the same awaitables a page does, so one shape is learned rather\n * than two:\n *\n * export const generateMetadata: GenerateMetadata<{ slug: string }> =\n * async ({ params }) => ({ title: (await params).slug })\n */\nexport type GenerateMetadata<P = Record<string, string>> = (args: {\n params: Promise<P>;\n searchParams: Promise<URLSearchParams>;\n}) => Metadata | Promise<Metadata>;\n\n/** One rule block of a robots.txt: which agents, what they may and may not fetch. */\nexport interface RobotsRule {\n userAgent?: string | string[];\n allow?: string | string[];\n disallow?: string | string[];\n crawlDelay?: number;\n}\n\n/** One url of a sitemap. `url` may be relative when the root layout has a metadataBase. */\nexport interface SitemapEntry {\n url: string;\n lastModified?: string | Date;\n changeFrequency?:\n \"always\" | \"hourly\" | \"daily\" | \"weekly\" | \"monthly\" | \"yearly\" | \"never\";\n priority?: number;\n /** Image urls on this page, for image search. */\n images?: string[];\n /** Translations of this page: language tag to url. */\n alternates?: { languages?: Record<string, string> };\n}\n\n/** A link in an llms.txt section. */\nexport interface LlmsLink {\n title: string;\n url: string;\n description?: string;\n}\n\nexport interface LlmsSection {\n title: string;\n links: LlmsLink[];\n}\n\n/**\n * The files a site describes itself with, each from a file beside the root\n * layout and written the way Next writes them:\n *\n * app/robots.ts -> /robots.txt default export returns MetadataRoute.Robots\n * app/sitemap.ts -> /sitemap.xml default export returns MetadataRoute.Sitemap\n * app/llms.ts -> /llms.txt default export returns MetadataRoute.Llms\n *\n * Each may return a string instead, served as written. A relative url in any\n * of them is made absolute with the root layout's metadataBase.\n */\nexport namespace MetadataRoute {\n export type Robots = {\n rules: RobotsRule | RobotsRule[];\n sitemap?: string | string[];\n host?: string;\n };\n export type Sitemap = SitemapEntry[];\n /** The llms.txt shape at llmstxt.org: a title, a summary, then sections of links. */\n export type Llms = {\n title: string;\n summary?: string;\n /** Paragraphs after the summary, before the sections. */\n details?: string | string[];\n sections?: LlmsSection[];\n };\n}\n\n/**\n * `export const viewport`, in Next's shape, on a layout or a page: layouts\n * outer to inner, then the page, merged per key. What is not set is\n * `width=device-width, initial-scale=1`, written into every document the way\n * Next wrote it — a layout ported from Next never wrote the tag, and a page\n * without one is the desktop layout on a phone. A layout that renders\n * `<meta name=\"viewport\">` itself is left alone.\n */\nexport interface Viewport {\n width?: string | number;\n height?: string | number;\n initialScale?: number;\n minimumScale?: number;\n maximumScale?: number;\n userScalable?: boolean;\n viewportFit?: \"auto\" | \"cover\" | \"contain\";\n interactiveWidget?: \"resizes-visual\" | \"resizes-content\" | \"overlays-content\";\n /** One colour, or one per media query. Wins over the web manifest's. */\n themeColor?: string | { media?: string; color: string }[];\n colorScheme?: \"normal\" | \"light\" | \"dark\" | \"light dark\" | \"dark light\" | \"only light\";\n}\n"]}
|
|
1
|
+
{"version":3,"file":"metadata.js","sourceRoot":"","sources":["../src/metadata.ts"],"names":[],"mappings":"AAAA,sDAAsD;AACtD,EAAE;AACF,6DAA6D;AAC7D,EAAE;AACF,4DAA4D;AAC5D,EAAE;AACF,4EAA4E;AAC5E,+EAA+E;AAC/E,+EAA+E;AAC/E,4EAA4E;AAC5E,2EAA2E;AAC3E,EAAE;AACF,4EAA4E;AAC5E,+EAA+E;AAC/E,MAAM","sourcesContent":["// What a page says about itself, as importable types.\n//\n// import type { Metadata } from '@rsc-kit/core/metadata'\n//\n// export const metadata: Metadata = { title: 'Orders' }\n//\n// Imported rather than ambient, and that is the whole point of the move. An\n// ambient declaration has to be COPIED into the project, which means it is not\n// there until the build has run once — so a freshly cloned app reports \"Cannot\n// find name 'Metadata'\" on every page until someone runs the dev server. An\n// import resolves from node_modules the moment dependencies are installed.\n//\n// The ambient names still work. `.rsc-kit/rsc-types.d.ts` now aliases these\n// rather than restating them, so there is one definition and two ways to reach\n// it.\n\nexport interface IconDescriptor {\n url: string | URL;\n type?: string;\n sizes?: string;\n color?: string;\n rel?: string;\n media?: string;\n fetchPriority?: \"high\" | \"low\" | \"auto\";\n}\n\nexport type IconURL = string | URL;\n\nexport interface Icons {\n icon?: IconURL | IconDescriptor | (IconURL | IconDescriptor)[];\n apple?: IconURL | IconDescriptor | (IconURL | IconDescriptor)[];\n shortcut?: IconURL | IconDescriptor | (IconURL | IconDescriptor)[];\n other?: IconDescriptor | IconDescriptor[];\n}\n\n/** A layout's title, wrapping the titles of the pages beneath it. */\nexport interface TitleTemplate {\n /** `%s` stands in for the page's own title. */\n template?: string;\n /** Used by a page that exports no title of its own. */\n default?: string;\n}\n\n/** One image a share card may show. A string is its url. */\nexport interface OpenGraphImage {\n url: string | URL;\n width?: number;\n height?: number;\n alt?: string;\n type?: string;\n}\n\n/**\n * The card a link to this page unfurls into on Facebook, Slack, LinkedIn and\n * most of the rest. Rendered with `property=`, which is what those scrapers\n * read — a `name=` attribute is ignored by every one of them.\n */\nexport interface OpenGraph {\n title?: string;\n description?: string;\n /** Absolute, or relative to `metadataBase`. */\n url?: string | URL;\n siteName?: string;\n type?: \"website\" | \"article\" | \"profile\" | \"book\" | (string & {});\n locale?: string;\n /**\n * Replaces the `opengraph-image.png` of any folder above this page, rather\n * than following it. A file in the page's own folder wins over this.\n */\n images?: string | URL | OpenGraphImage | (string | URL | OpenGraphImage)[];\n}\n\n/** The same card for X, which reads `name=` rather than `property=`. */\nexport interface Twitter {\n card?: \"summary\" | \"summary_large_image\" | \"app\" | \"player\";\n title?: string;\n description?: string;\n /** The site's account, `@handle`. */\n site?: string;\n /** The author's account, `@handle`. */\n creator?: string;\n images?: string | URL | OpenGraphImage | (string | URL | OpenGraphImage)[];\n}\n\nexport interface Robots {\n index?: boolean;\n follow?: boolean;\n noarchive?: boolean;\n nosnippet?: boolean;\n noimageindex?: boolean;\n nocache?: boolean;\n notranslate?: boolean;\n indexifembedded?: boolean;\n nositelinkssearchbox?: boolean;\n unavailable_after?: string;\n \"max-video-preview\"?: number | string;\n \"max-image-preview\"?: \"none\" | \"standard\" | \"large\";\n \"max-snippet\"?: number;\n googleBot?: string | Omit<Robots, \"googleBot\">;\n}\n\n/** An iOS launch screen: an image, and the media query naming the device it is for. */\nexport interface AppleStartupImage {\n url: string | URL;\n media?: string;\n}\n\nexport interface AppleWebApp {\n /** Opens as an app from the home screen - no browser chrome. */\n capable?: boolean;\n /** The name under the home screen icon, when it should differ from the page title. */\n title?: string;\n /** The status bar over the app: `default`, `black`, or `black-translucent` to draw under it. */\n statusBarStyle?: \"default\" | \"black\" | \"black-translucent\";\n /** Launch screens. A string or url is one image for every device. */\n startupImage?: string | URL | (string | URL | AppleStartupImage)[];\n}\n\nexport interface Metadata {\n /** A string on a page; a template on a layout, applied to the pages below it. */\n title?: string | TitleTemplate;\n description?: string;\n keywords?: string | string[];\n author?: string;\n /**\n * A string, or the object Next takes: `{ index: false, follow: false }`\n * becomes `<meta name=\"robots\" content=\"noindex, nofollow\">`, the flags\n * by name, the limits as `name:value`; `googleBot` is the same shape for\n * `<meta name=\"googlebot\">`.\n */\n robots?: string | Robots;\n /**\n * Where the site lives, so a relative image or url can be made absolute.\n *\n * metadataBase: new URL('https://example.com')\n *\n * On the root layout, once. A share-card scraper needs an absolute url and\n * several refuse a relative one; without this, `og:image` for an image in\n * `app/` is emitted relative and works in some places and not others. The\n * same name as Next, so a port carries it across unchanged.\n */\n metadataBase?: string | URL;\n icons?: IconURL | (IconURL | IconDescriptor)[] | Icons | null;\n openGraph?: OpenGraph;\n twitter?: Twitter;\n /**\n * How the app behaves added to an iPhone's home screen. Next's shape, so a\n * port carries it across unchanged.\n *\n * appleWebApp: {\n * capable: true,\n * title: 'Orders',\n * statusBarStyle: 'black-translucent',\n * startupImage: [{ url: '/splash-1179x2556.png', media: '...' }],\n * }\n *\n * `true` is `{ capable: true }`. Launch screens are simpler as files: an\n * `apple-splash-1179x2556.png` in `app/` is linked with the right media\n * query for the device its size names - see the PWA guide.\n */\n appleWebApp?: boolean | AppleWebApp;\n /** @deprecated Use `openGraph.title`. Still rendered, correctly, as `property=`. */\n \"og:title\"?: string;\n /** @deprecated Use `openGraph.description`. */\n \"og:description\"?: string;\n /** @deprecated Use `openGraph.images`. */\n \"og:image\"?: string;\n /** @deprecated Use `openGraph.url`. */\n \"og:url\"?: string;\n /** @deprecated Use `openGraph.type`. */\n \"og:type\"?: string;\n /** @deprecated Use `openGraph.siteName`. */\n \"og:site_name\"?: string;\n /** @deprecated Use `twitter.card`. */\n \"twitter:card\"?: string;\n /** @deprecated Use `twitter.title`. */\n \"twitter:title\"?: string;\n /** @deprecated Use `twitter.description`. */\n \"twitter:description\"?: string;\n /** @deprecated Use `twitter.images`. */\n \"twitter:image\"?: string;\n /** @deprecated Use `twitter.site`. */\n \"twitter:site\"?: string;\n\n /**\n * Any other meta tag, by name.\n *\n * other: { 'fb:app_id': '123', 'theme-color': '#000' }\n *\n * Here rather than alongside the named keys, and that is what makes the rest\n * of this interface worth annotating. An index signature on the interface\n * itself made every key legal — so `titel` was accepted in silence, and an\n * editor offered no completions at all, because with any identifier valid\n * TypeScript reads an unfinished key as a shorthand property and goes looking\n * for a variable by that name.\n */\n other?: Record<string, string | string[] | null | undefined>;\n}\n\n/**\n * Metadata that depends on the request.\n *\n * Receives the same awaitables a page does, so one shape is learned rather\n * than two:\n *\n * export const generateMetadata: GenerateMetadata<{ slug: string }> =\n * async ({ params }) => ({ title: (await params).slug })\n */\nexport type GenerateMetadata<P = Record<string, string>> = (args: {\n params: Promise<P>;\n searchParams: Promise<URLSearchParams>;\n}) => Metadata | Promise<Metadata>;\n\n/** One rule block of a robots.txt: which agents, what they may and may not fetch. */\nexport interface RobotsRule {\n userAgent?: string | string[];\n allow?: string | string[];\n disallow?: string | string[];\n crawlDelay?: number;\n}\n\n/** One url of a sitemap. `url` may be relative when the root layout has a metadataBase. */\nexport interface SitemapEntry {\n url: string;\n lastModified?: string | Date;\n changeFrequency?:\n \"always\" | \"hourly\" | \"daily\" | \"weekly\" | \"monthly\" | \"yearly\" | \"never\";\n priority?: number;\n /** Image urls on this page, for image search. */\n images?: string[];\n /** Translations of this page: language tag to url. */\n alternates?: { languages?: Record<string, string> };\n}\n\n/** A link in an llms.txt section. */\nexport interface LlmsLink {\n title: string;\n url: string;\n description?: string;\n}\n\nexport interface LlmsSection {\n title: string;\n links: LlmsLink[];\n}\n\n/**\n * The files a site describes itself with, each from a file beside the root\n * layout and written the way Next writes them:\n *\n * app/robots.ts -> /robots.txt default export returns MetadataRoute.Robots\n * app/sitemap.ts -> /sitemap.xml default export returns MetadataRoute.Sitemap\n * app/llms.ts -> /llms.txt default export returns MetadataRoute.Llms\n *\n * Each may return a string instead, served as written. A relative url in any\n * of them is made absolute with the root layout's metadataBase.\n */\nexport namespace MetadataRoute {\n export type Robots = {\n rules: RobotsRule | RobotsRule[];\n sitemap?: string | string[];\n host?: string;\n };\n export type Sitemap = SitemapEntry[];\n /** The llms.txt shape at llmstxt.org: a title, a summary, then sections of links. */\n export type Llms = {\n title: string;\n summary?: string;\n /** Paragraphs after the summary, before the sections. */\n details?: string | string[];\n sections?: LlmsSection[];\n };\n}\n\n/**\n * `export const viewport`, in Next's shape, on a layout or a page: layouts\n * outer to inner, then the page, merged per key. What is not set is\n * `width=device-width, initial-scale=1`, written into every document the way\n * Next wrote it — a layout ported from Next never wrote the tag, and a page\n * without one is the desktop layout on a phone. A layout that renders\n * `<meta name=\"viewport\">` itself is left alone.\n */\nexport interface Viewport {\n width?: string | number;\n height?: string | number;\n initialScale?: number;\n minimumScale?: number;\n maximumScale?: number;\n userScalable?: boolean;\n viewportFit?: \"auto\" | \"cover\" | \"contain\";\n interactiveWidget?: \"resizes-visual\" | \"resizes-content\" | \"overlays-content\";\n /** One colour, or one per media query. Wins over the web manifest's. */\n themeColor?: string | { media?: string; color: string }[];\n colorScheme?: \"normal\" | \"light\" | \"dark\" | \"light dark\" | \"dark light\" | \"only light\";\n}\n"]}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export type ShareKind = "openGraph" | "twitter";
|
|
2
|
+
/** The urls found, by the folder they sit in: "" is app/ itself. */
|
|
3
|
+
export type ShareImages = Record<string, Partial<Record<ShareKind, string>>>;
|
|
4
|
+
/** How far below app/ a folder is: "" is 0, "shop/cart" is 2. */
|
|
5
|
+
export declare function depthOf(folder: string): number;
|
|
6
|
+
/**
|
|
7
|
+
* The folder a route file lives in, below app/.
|
|
8
|
+
*
|
|
9
|
+
* Names are the build's: `app/(shop)/cart/page` is "(shop)/cart", and
|
|
10
|
+
* `app/layout` is "". Route groups stay, because they are folders on disk and
|
|
11
|
+
* a file in one is found by the folder it is in, not by the url it never shows.
|
|
12
|
+
*/
|
|
13
|
+
export declare function folderOf(component: string): string;
|
|
14
|
+
/** The image in the nearest folder at or above `folder`, and how deep that folder is. */
|
|
15
|
+
export declare function nearestShareImage(images: ShareImages, folder: string, kind: ShareKind): {
|
|
16
|
+
href: string;
|
|
17
|
+
depth: number;
|
|
18
|
+
} | null;
|
|
19
|
+
/**
|
|
20
|
+
* Whether the file is what the page shows, given the images its metadata
|
|
21
|
+
* exports.
|
|
22
|
+
*
|
|
23
|
+
* The nearer declaration wins. Equal distance goes to the file: it is the more
|
|
24
|
+
* specific thing to have done - a file beside the page is that page's card.
|
|
25
|
+
*/
|
|
26
|
+
export declare function fileWins(file: {
|
|
27
|
+
depth: number;
|
|
28
|
+
} | null, exported: {
|
|
29
|
+
depth: number;
|
|
30
|
+
} | null): boolean;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// Which share image a route shows.
|
|
2
|
+
//
|
|
3
|
+
// An opengraph-image.png or twitter-image.png can sit in app/ itself or in any
|
|
4
|
+
// folder beneath it, and the nearest one to a route is the route's. A page
|
|
5
|
+
// that exports its own openGraph.images is a declaration too, and the nearer
|
|
6
|
+
// of the two wins - at the same distance the file does, which is Next's rule
|
|
7
|
+
// and the reason this one is not different.
|
|
8
|
+
//
|
|
9
|
+
// Kept apart from appAssets.ts, which reads the disk: this runs while a page
|
|
10
|
+
// renders, inside the bundle, and a module that imports node:fs does not
|
|
11
|
+
// belong there.
|
|
12
|
+
/** How far below app/ a folder is: "" is 0, "shop/cart" is 2. */
|
|
13
|
+
export function depthOf(folder) {
|
|
14
|
+
return folder === "" ? 0 : folder.split("/").length;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The folder a route file lives in, below app/.
|
|
18
|
+
*
|
|
19
|
+
* Names are the build's: `app/(shop)/cart/page` is "(shop)/cart", and
|
|
20
|
+
* `app/layout` is "". Route groups stay, because they are folders on disk and
|
|
21
|
+
* a file in one is found by the folder it is in, not by the url it never shows.
|
|
22
|
+
*/
|
|
23
|
+
export function folderOf(component) {
|
|
24
|
+
return component.split("/").slice(1, -1).join("/");
|
|
25
|
+
}
|
|
26
|
+
/** The image in the nearest folder at or above `folder`, and how deep that folder is. */
|
|
27
|
+
export function nearestShareImage(images, folder, kind) {
|
|
28
|
+
const parts = folder === "" ? [] : folder.split("/");
|
|
29
|
+
for (let n = parts.length; n >= 0; n--) {
|
|
30
|
+
const href = images[parts.slice(0, n).join("/")]?.[kind];
|
|
31
|
+
if (href)
|
|
32
|
+
return { href, depth: n };
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Whether the file is what the page shows, given the images its metadata
|
|
38
|
+
* exports.
|
|
39
|
+
*
|
|
40
|
+
* The nearer declaration wins. Equal distance goes to the file: it is the more
|
|
41
|
+
* specific thing to have done - a file beside the page is that page's card.
|
|
42
|
+
*/
|
|
43
|
+
export function fileWins(file, exported) {
|
|
44
|
+
if (!file)
|
|
45
|
+
return false;
|
|
46
|
+
if (!exported)
|
|
47
|
+
return true;
|
|
48
|
+
return file.depth >= exported.depth;
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=shareImages.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shareImages.js","sourceRoot":"","sources":["../src/shareImages.ts"],"names":[],"mappings":"AAAA,mCAAmC;AACnC,EAAE;AACF,+EAA+E;AAC/E,2EAA2E;AAC3E,6EAA6E;AAC7E,6EAA6E;AAC7E,4CAA4C;AAC5C,EAAE;AACF,6EAA6E;AAC7E,yEAAyE;AACzE,gBAAgB;AAOhB,iEAAiE;AACjE,MAAM,UAAU,OAAO,CAAC,MAAc;IACpC,OAAO,MAAM,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC;AACtD,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,QAAQ,CAAC,SAAiB;IACxC,OAAO,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACrD,CAAC;AAED,yFAAyF;AACzF,MAAM,UAAU,iBAAiB,CAC/B,MAAmB,EACnB,MAAc,EACd,IAAe;IAEf,MAAM,KAAK,GAAG,MAAM,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAErD,KAAK,IAAI,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QACvC,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC;QAEzD,IAAI,IAAI;YAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;IACtC,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,QAAQ,CACtB,IAA8B,EAC9B,QAAkC;IAElC,IAAI,CAAC,IAAI;QAAE,OAAO,KAAK,CAAC;IACxB,IAAI,CAAC,QAAQ;QAAE,OAAO,IAAI,CAAC;IAE3B,OAAO,IAAI,CAAC,KAAK,IAAI,QAAQ,CAAC,KAAK,CAAC;AACtC,CAAC","sourcesContent":["// Which share image a route shows.\n//\n// An opengraph-image.png or twitter-image.png can sit in app/ itself or in any\n// folder beneath it, and the nearest one to a route is the route's. A page\n// that exports its own openGraph.images is a declaration too, and the nearer\n// of the two wins - at the same distance the file does, which is Next's rule\n// and the reason this one is not different.\n//\n// Kept apart from appAssets.ts, which reads the disk: this runs while a page\n// renders, inside the bundle, and a module that imports node:fs does not\n// belong there.\n\nexport type ShareKind = \"openGraph\" | \"twitter\";\n\n/** The urls found, by the folder they sit in: \"\" is app/ itself. */\nexport type ShareImages = Record<string, Partial<Record<ShareKind, string>>>;\n\n/** How far below app/ a folder is: \"\" is 0, \"shop/cart\" is 2. */\nexport function depthOf(folder: string): number {\n return folder === \"\" ? 0 : folder.split(\"/\").length;\n}\n\n/**\n * The folder a route file lives in, below app/.\n *\n * Names are the build's: `app/(shop)/cart/page` is \"(shop)/cart\", and\n * `app/layout` is \"\". Route groups stay, because they are folders on disk and\n * a file in one is found by the folder it is in, not by the url it never shows.\n */\nexport function folderOf(component: string): string {\n return component.split(\"/\").slice(1, -1).join(\"/\");\n}\n\n/** The image in the nearest folder at or above `folder`, and how deep that folder is. */\nexport function nearestShareImage(\n images: ShareImages,\n folder: string,\n kind: ShareKind,\n): { href: string; depth: number } | null {\n const parts = folder === \"\" ? [] : folder.split(\"/\");\n\n for (let n = parts.length; n >= 0; n--) {\n const href = images[parts.slice(0, n).join(\"/\")]?.[kind];\n\n if (href) return { href, depth: n };\n }\n\n return null;\n}\n\n/**\n * Whether the file is what the page shows, given the images its metadata\n * exports.\n *\n * The nearer declaration wins. Equal distance goes to the file: it is the more\n * specific thing to have done - a file beside the page is that page's card.\n */\nexport function fileWins(\n file: { depth: number } | null,\n exported: { depth: number } | null,\n): boolean {\n if (!file) return false;\n if (!exported) return true;\n\n return file.depth >= exported.depth;\n}\n"]}
|