@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/README.md
CHANGED
|
@@ -28,7 +28,9 @@ is underneath:
|
|
|
28
28
|
`await rpc('Orders.recent')`, typed from the backend's own signatures. See
|
|
29
29
|
[Backend-Answered Pages](https://docs.rsc-kit.dev/hosts/backend-answered-pages).
|
|
30
30
|
- **It compiles to a single binary** with `bun build --compile`, assets and
|
|
31
|
-
frozen pages inside it.
|
|
31
|
+
frozen pages inside it. A file in `src/commands` is a release step or a
|
|
32
|
+
worker in the same binary: `./app migrate`
|
|
33
|
+
([guide](https://docs.rsc-kit.dev/guides/commands)).
|
|
32
34
|
|
|
33
35
|
## Setup
|
|
34
36
|
|
package/dist/appAssets.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ShareImages, ShareKind } from "./shareImages.js";
|
|
1
2
|
/** One found file, and where it will be served from. */
|
|
2
3
|
export interface AppAsset {
|
|
3
4
|
/** The file's name in the app directory. */
|
|
@@ -15,8 +16,11 @@ export interface AppAssets {
|
|
|
15
16
|
appleSplash: (AppAsset & {
|
|
16
17
|
media: string | null;
|
|
17
18
|
})[];
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Share images by the folder they sit in, "" being app/ itself. The route
|
|
21
|
+
* that shows one is found by walking up from its own folder.
|
|
22
|
+
*/
|
|
23
|
+
shareImages: Record<string, Partial<Record<ShareKind, AppAsset>>>;
|
|
20
24
|
}
|
|
21
25
|
/**
|
|
22
26
|
* Where these are served from.
|
|
@@ -27,13 +31,30 @@ export interface AppAssets {
|
|
|
27
31
|
* for at `/favicon.ico` whatever any markup says.
|
|
28
32
|
*/
|
|
29
33
|
export declare const ASSET_BASE = "/_app";
|
|
34
|
+
/**
|
|
35
|
+
* A file's path as a url path: each segment encoded, the slashes kept.
|
|
36
|
+
*
|
|
37
|
+
* A folder under app/ may be `[team]`, and a bracket is not legal in a url's
|
|
38
|
+
* path - a strict scraper refuses the whole address, and a card with no image
|
|
39
|
+
* is all it says. Everything a plain file name is made of passes through
|
|
40
|
+
* untouched, so `icon-192.png` is `icon-192.png`.
|
|
41
|
+
*/
|
|
42
|
+
export declare function assetHref(file: string): string;
|
|
43
|
+
/** The file path a url path names, which is what the build writes and the dev server reads. */
|
|
44
|
+
export declare function assetFile(href: string): string;
|
|
30
45
|
/** What the app declared by putting a file where it could be found. */
|
|
31
46
|
export declare function appAssets(appDir: string): AppAssets;
|
|
47
|
+
/** The urls only, which is all a page being rendered needs of them. */
|
|
48
|
+
export declare function shareImageUrls(assets: AppAssets): ShareImages;
|
|
32
49
|
export declare function typeOf(file: string): string;
|
|
33
|
-
/** The head tags these produce, as plain data the generated entry turns into elements. */
|
|
34
50
|
/** Every asset found, favicon first: what the build copies and the dev server answers. */
|
|
35
51
|
export declare function allAppAssets(assets: AppAssets): AppAsset[];
|
|
36
|
-
|
|
52
|
+
/**
|
|
53
|
+
* The head tags these produce, as plain data the generated entry turns into
|
|
54
|
+
* elements - the same on every page. The share images are not here: which one
|
|
55
|
+
* a page shows depends on where the page is, and that is decided as it renders.
|
|
56
|
+
*/
|
|
57
|
+
export declare function headTags(assets: AppAssets): {
|
|
37
58
|
tag: "link" | "meta";
|
|
38
59
|
props: Record<string, string>;
|
|
39
60
|
}[];
|
package/dist/appAssets.js
CHANGED
|
@@ -13,6 +13,10 @@
|
|
|
13
13
|
// opengraph-image.png <meta property="og:image">
|
|
14
14
|
// twitter-image.png <meta name="twitter:image">
|
|
15
15
|
//
|
|
16
|
+
// The two share images may also sit in any folder beneath app/, and are then
|
|
17
|
+
// the card for the routes under that folder - the nearest one wins. See
|
|
18
|
+
// shareImages.ts for the rule and appAssets() for the scan.
|
|
19
|
+
//
|
|
16
20
|
// The same bargain as every other file in this directory: put it where it
|
|
17
21
|
// belongs and it works, with nothing to register. Next established these names
|
|
18
22
|
// and there is nothing to gain by inventing different ones — an app moving
|
|
@@ -22,6 +26,7 @@
|
|
|
22
26
|
// this verbatim"; these are read as well as served — an icon's size decides
|
|
23
27
|
// what goes in the manifest, and its presence decides what goes in the head.
|
|
24
28
|
import { existsSync, readdirSync } from "node:fs";
|
|
29
|
+
import { join } from "node:path";
|
|
25
30
|
import { isAppleSplash, splashMedia } from "./appleSplash.js";
|
|
26
31
|
const IMAGE = /\.(png|svg|jpg|jpeg|webp|gif|avif)$/i;
|
|
27
32
|
/**
|
|
@@ -33,11 +38,32 @@ const IMAGE = /\.(png|svg|jpg|jpeg|webp|gif|avif)$/i;
|
|
|
33
38
|
* for at `/favicon.ico` whatever any markup says.
|
|
34
39
|
*/
|
|
35
40
|
export const ASSET_BASE = "/_app";
|
|
41
|
+
/**
|
|
42
|
+
* A file's path as a url path: each segment encoded, the slashes kept.
|
|
43
|
+
*
|
|
44
|
+
* A folder under app/ may be `[team]`, and a bracket is not legal in a url's
|
|
45
|
+
* path - a strict scraper refuses the whole address, and a card with no image
|
|
46
|
+
* is all it says. Everything a plain file name is made of passes through
|
|
47
|
+
* untouched, so `icon-192.png` is `icon-192.png`.
|
|
48
|
+
*/
|
|
49
|
+
export function assetHref(file) {
|
|
50
|
+
return `${ASSET_BASE}/${file.split("/").map(encodeURIComponent).join("/")}`;
|
|
51
|
+
}
|
|
52
|
+
/** The file path a url path names, which is what the build writes and the dev server reads. */
|
|
53
|
+
export function assetFile(href) {
|
|
54
|
+
try {
|
|
55
|
+
return decodeURIComponent(href);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// A stray % is not an encoding. Left as it came, it matches nothing.
|
|
59
|
+
return href;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
36
62
|
function assetsIn(dir, match) {
|
|
37
63
|
return readdirSync(dir)
|
|
38
64
|
.filter(match)
|
|
39
65
|
.sort()
|
|
40
|
-
.map((file) => ({ file, href:
|
|
66
|
+
.map((file) => ({ file, href: assetHref(file) }));
|
|
41
67
|
}
|
|
42
68
|
/** What the app declared by putting a file where it could be found. */
|
|
43
69
|
export function appAssets(appDir) {
|
|
@@ -48,8 +74,7 @@ export function appAssets(appDir) {
|
|
|
48
74
|
appleIcon: null,
|
|
49
75
|
appleSplash: [],
|
|
50
76
|
screenshots: [],
|
|
51
|
-
|
|
52
|
-
twitter: null,
|
|
77
|
+
shareImages: {},
|
|
53
78
|
};
|
|
54
79
|
}
|
|
55
80
|
const names = readdirSync(appDir);
|
|
@@ -68,10 +93,59 @@ export function appAssets(appDir) {
|
|
|
68
93
|
...asset,
|
|
69
94
|
media: splashMedia(asset.file),
|
|
70
95
|
})),
|
|
71
|
-
|
|
72
|
-
twitter: assetsIn(appDir, (name) => /^twitter-image[-\w]*/.test(name) && IMAGE.test(name))[0] ?? null,
|
|
96
|
+
shareImages: shareImagesIn(appDir),
|
|
73
97
|
};
|
|
74
98
|
}
|
|
99
|
+
const SHARE_FILES = {
|
|
100
|
+
openGraph: /^opengraph-image[-\w]*/,
|
|
101
|
+
twitter: /^twitter-image[-\w]*/,
|
|
102
|
+
};
|
|
103
|
+
/**
|
|
104
|
+
* Folders that are not part of the url tree, so a file in one answers no route:
|
|
105
|
+
* private (`_x`), a parallel slot (`@x`), an interception (`(.)x`), dependencies
|
|
106
|
+
* and anything hidden.
|
|
107
|
+
*/
|
|
108
|
+
function isRouteFolder(name) {
|
|
109
|
+
return !/^(?:[._@]|\(\.{1,3}\))/.test(name) && name !== "node_modules";
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Every share image under app/, by the folder it is in.
|
|
113
|
+
*
|
|
114
|
+
* A file in a folder is served from a path that keeps the folder, because two
|
|
115
|
+
* folders may each have an opengraph-image.png and one flat /_app/ name would
|
|
116
|
+
* make the second silently overwrite the first.
|
|
117
|
+
*/
|
|
118
|
+
function shareImagesIn(appDir) {
|
|
119
|
+
const found = {};
|
|
120
|
+
const walk = (folder) => {
|
|
121
|
+
const dir = join(appDir, folder);
|
|
122
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
123
|
+
for (const kind of Object.keys(SHARE_FILES)) {
|
|
124
|
+
const name = entries
|
|
125
|
+
.filter((e) => e.isFile() && SHARE_FILES[kind].test(e.name) && IMAGE.test(e.name))
|
|
126
|
+
.map((e) => e.name)
|
|
127
|
+
.sort()[0];
|
|
128
|
+
if (name) {
|
|
129
|
+
const file = folder === "" ? name : `${folder}/${name}`;
|
|
130
|
+
(found[folder] ??= {})[kind] = { file, href: assetHref(file) };
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
for (const entry of entries) {
|
|
134
|
+
if (entry.isDirectory() && isRouteFolder(entry.name)) {
|
|
135
|
+
walk(folder === "" ? entry.name : `${folder}/${entry.name}`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
walk("");
|
|
140
|
+
return found;
|
|
141
|
+
}
|
|
142
|
+
/** The urls only, which is all a page being rendered needs of them. */
|
|
143
|
+
export function shareImageUrls(assets) {
|
|
144
|
+
return Object.fromEntries(Object.entries(assets.shareImages).map(([folder, kinds]) => [
|
|
145
|
+
folder,
|
|
146
|
+
Object.fromEntries(Object.entries(kinds).map(([kind, asset]) => [kind, asset.href])),
|
|
147
|
+
]));
|
|
148
|
+
}
|
|
75
149
|
export function typeOf(file) {
|
|
76
150
|
const extension = file.slice(file.lastIndexOf(".") + 1).toLowerCase();
|
|
77
151
|
return extension === "svg"
|
|
@@ -82,7 +156,6 @@ export function typeOf(file) {
|
|
|
82
156
|
? "image/x-icon"
|
|
83
157
|
: `image/${extension}`;
|
|
84
158
|
}
|
|
85
|
-
/** The head tags these produce, as plain data the generated entry turns into elements. */
|
|
86
159
|
/** Every asset found, favicon first: what the build copies and the dev server answers. */
|
|
87
160
|
export function allAppAssets(assets) {
|
|
88
161
|
return [
|
|
@@ -91,11 +164,15 @@ export function allAppAssets(assets) {
|
|
|
91
164
|
...(assets.appleIcon ? [assets.appleIcon] : []),
|
|
92
165
|
...assets.appleSplash,
|
|
93
166
|
...assets.screenshots,
|
|
94
|
-
...(assets.
|
|
95
|
-
...(assets.twitter ? [assets.twitter] : []),
|
|
167
|
+
...Object.values(assets.shareImages).flatMap((kinds) => Object.values(kinds)),
|
|
96
168
|
];
|
|
97
169
|
}
|
|
98
|
-
|
|
170
|
+
/**
|
|
171
|
+
* The head tags these produce, as plain data the generated entry turns into
|
|
172
|
+
* elements - the same on every page. The share images are not here: which one
|
|
173
|
+
* a page shows depends on where the page is, and that is decided as it renders.
|
|
174
|
+
*/
|
|
175
|
+
export function headTags(assets) {
|
|
99
176
|
const tags = [];
|
|
100
177
|
if (assets.favicon) {
|
|
101
178
|
tags.push({
|
|
@@ -134,27 +211,6 @@ export function headTags(assets, origin) {
|
|
|
134
211
|
tags.push({ tag: "meta", props: { name: "mobile-web-app-capable", content: "yes" } });
|
|
135
212
|
tags.push({ tag: "meta", props: { name: "apple-mobile-web-app-capable", content: "yes" } });
|
|
136
213
|
}
|
|
137
|
-
// Absolute when the app said where it lives. The og spec asks for an absolute
|
|
138
|
-
// url and several crawlers still mean it — a relative one is read by some and
|
|
139
|
-
// ignored by others, which is the worst of both. Relative is what is left
|
|
140
|
-
// when nothing said, and is better than omitting the tag.
|
|
141
|
-
const absolute = (href) => origin ? new URL(href, origin).href : href;
|
|
142
|
-
if (assets.openGraph) {
|
|
143
|
-
tags.push({
|
|
144
|
-
tag: "meta",
|
|
145
|
-
props: { property: "og:image", content: absolute(assets.openGraph.href) },
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
if (assets.twitter) {
|
|
149
|
-
tags.push({
|
|
150
|
-
tag: "meta",
|
|
151
|
-
props: { name: "twitter:image", content: absolute(assets.twitter.href) },
|
|
152
|
-
});
|
|
153
|
-
tags.push({
|
|
154
|
-
tag: "meta",
|
|
155
|
-
props: { name: "twitter:card", content: "summary_large_image" },
|
|
156
|
-
});
|
|
157
|
-
}
|
|
158
214
|
return tags;
|
|
159
215
|
}
|
|
160
216
|
//# sourceMappingURL=appAssets.js.map
|
package/dist/appAssets.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"appAssets.js","sourceRoot":"","sources":["../src/appAssets.ts"],"names":[],"mappings":"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","sourcesContent":["// Files that describe the app, found by name rather than configured.\n//\n// src/app/\n// favicon.ico <link rel=\"icon\">\n// icon.png <link rel=\"icon\">, and the manifest's icons\n// icon-192.png …as many as you like; sizes read from the name\n// apple-icon.png <link rel=\"apple-touch-icon\">\n// screenshot-wide-1280x720.png the manifest's screenshots, for Chrome's\n// screenshot-narrow-750x1334.png richer install sheet; size and form\n// factor read from the name\n// apple-splash-1179x2556.png <link rel=\"apple-touch-startup-image\" media=…>\n// …one per device and orientation; see appleSplash.ts\n// opengraph-image.png <meta property=\"og:image\">\n// twitter-image.png <meta name=\"twitter:image\">\n//\n// The same bargain as every other file in this directory: put it where it\n// belongs and it works, with nothing to register. Next established these names\n// and there is nothing to gain by inventing different ones — an app moving\n// between the two should not have to rename its favicon.\n//\n// They live in `app/` rather than `public/` on purpose. `public/` is \"serve\n// this verbatim\"; these are read as well as served — an icon's size decides\n// what goes in the manifest, and its presence decides what goes in the head.\n\nimport { existsSync, readdirSync } from \"node:fs\";\nimport { isAppleSplash, splashMedia } from \"./appleSplash.js\";\n\n/** One found file, and where it will be served from. */\nexport interface AppAsset {\n /** The file's name in the app directory. */\n file: string;\n /** The url it is served at. */\n href: string;\n}\n\nexport interface AppAssets {\n favicon: AppAsset | null;\n icons: AppAsset[];\n appleIcon: AppAsset | null;\n /** For the manifest's screenshots: wide for desktop, narrow for a phone. */\n screenshots: AppAsset[];\n /** iOS launch screens, each with the media query its size maps to. */\n appleSplash: (AppAsset & { media: string | null })[];\n openGraph: AppAsset | null;\n twitter: AppAsset | null;\n}\n\nconst IMAGE = /\\.(png|svg|jpg|jpeg|webp|gif|avif)$/i;\n\n/**\n * Where these are served from.\n *\n * Their own directory rather than the output root, so an app that happens to\n * have a `public/icon.png` as well is not silently overwritten by one of\n * these, in either direction. The exception is the favicon, which browsers ask\n * for at `/favicon.ico` whatever any markup says.\n */\nexport const ASSET_BASE = \"/_app\";\n\nfunction assetsIn(dir: string, match: (name: string) => boolean): AppAsset[] {\n return readdirSync(dir)\n .filter(match)\n .sort()\n .map((file) => ({ file, href: `${ASSET_BASE}/${file}` }));\n}\n\n/** What the app declared by putting a file where it could be found. */\nexport function appAssets(appDir: string): AppAssets {\n if (!existsSync(appDir)) {\n return {\n favicon: null,\n icons: [],\n appleIcon: null,\n appleSplash: [],\n screenshots: [],\n openGraph: null,\n twitter: null,\n };\n }\n\n const names = readdirSync(appDir);\n const has = (name: string) => names.includes(name);\n\n return {\n // Served where a browser looks for it regardless of markup: a request for\n // /favicon.ico goes out whether or not a link tag exists, and answering it\n // from somewhere else means answering a 404 to the request that matters.\n favicon: has(\"favicon.ico\")\n ? { file: \"favicon.ico\", href: \"/favicon.ico\" }\n : null,\n icons: assetsIn(\n appDir,\n (name) => /^icon[-\\w]*/.test(name) && IMAGE.test(name),\n ),\n appleIcon:\n assetsIn(\n appDir,\n (name) => /^apple-icon[-\\w]*/.test(name) && IMAGE.test(name),\n )[0] ?? null,\n screenshots: assetsIn(appDir, (name) =>\n /^screenshot-(wide|narrow)[-\\w]*\\.(png|jpe?g|webp)$/i.test(name),\n ),\n appleSplash: assetsIn(appDir, isAppleSplash).map((asset) => ({\n ...asset,\n media: splashMedia(asset.file),\n })),\n openGraph:\n assetsIn(\n appDir,\n (name) => /^opengraph-image[-\\w]*/.test(name) && IMAGE.test(name),\n )[0] ?? null,\n twitter:\n assetsIn(\n appDir,\n (name) => /^twitter-image[-\\w]*/.test(name) && IMAGE.test(name),\n )[0] ?? null,\n };\n}\n\nexport function typeOf(file: string): string {\n const extension = file.slice(file.lastIndexOf(\".\") + 1).toLowerCase();\n\n return extension === \"svg\"\n ? \"image/svg+xml\"\n : extension === \"jpg\" || extension === \"jpeg\"\n ? \"image/jpeg\"\n : extension === \"ico\"\n ? \"image/x-icon\"\n : `image/${extension}`;\n}\n\n/** The head tags these produce, as plain data the generated entry turns into elements. */\n/** Every asset found, favicon first: what the build copies and the dev server answers. */\nexport function allAppAssets(assets: AppAssets): AppAsset[] {\n return [\n ...(assets.favicon ? [assets.favicon] : []),\n ...assets.icons,\n ...(assets.appleIcon ? [assets.appleIcon] : []),\n ...assets.appleSplash,\n ...assets.screenshots,\n ...(assets.openGraph ? [assets.openGraph] : []),\n ...(assets.twitter ? [assets.twitter] : []),\n ];\n}\n\nexport function headTags(\n assets: AppAssets,\n origin?: string,\n): { tag: \"link\" | \"meta\"; props: Record<string, string> }[] {\n const tags: { tag: \"link\" | \"meta\"; props: Record<string, string> }[] = [];\n\n if (assets.favicon) {\n tags.push({\n tag: \"link\",\n props: { rel: \"icon\", href: assets.favicon.href, type: \"image/x-icon\" },\n });\n }\n\n for (const icon of assets.icons) {\n // A maskable icon is for a launcher, padded for its mask; as a tab's icon\n // it would be the small one. It goes in the manifest and nowhere else.\n if (/(^|-)maskable\\b/i.test(icon.file)) continue;\n\n tags.push({\n tag: \"link\",\n props: { rel: \"icon\", href: icon.href, type: typeOf(icon.file) },\n });\n }\n\n if (assets.appleIcon) {\n tags.push({\n tag: \"link\",\n props: { rel: \"apple-touch-icon\", href: assets.appleIcon.href },\n });\n }\n\n // Only the sizes a device has: a query that matches nothing is bytes in\n // every page for no one, and the build names those files instead.\n const splashes = assets.appleSplash.filter((splash) => splash.media !== null);\n\n for (const splash of splashes) {\n tags.push({\n tag: \"link\",\n props: { rel: \"apple-touch-startup-image\", href: splash.href, media: splash.media! },\n });\n }\n\n // Safari shows a launch screen only to an app launched from the home\n // screen as an app. Whoever put these in app/ meant that.\n if (splashes.length > 0) {\n tags.push({ tag: \"meta\", props: { name: \"mobile-web-app-capable\", content: \"yes\" } });\n tags.push({ tag: \"meta\", props: { name: \"apple-mobile-web-app-capable\", content: \"yes\" } });\n }\n\n // Absolute when the app said where it lives. The og spec asks for an absolute\n // url and several crawlers still mean it — a relative one is read by some and\n // ignored by others, which is the worst of both. Relative is what is left\n // when nothing said, and is better than omitting the tag.\n const absolute = (href: string) =>\n origin ? new URL(href, origin).href : href;\n\n if (assets.openGraph) {\n tags.push({\n tag: \"meta\",\n props: { property: \"og:image\", content: absolute(assets.openGraph.href) },\n });\n }\n\n if (assets.twitter) {\n tags.push({\n tag: \"meta\",\n props: { name: \"twitter:image\", content: absolute(assets.twitter.href) },\n });\n tags.push({\n tag: \"meta\",\n props: { name: \"twitter:card\", content: \"summary_large_image\" },\n });\n }\n\n return tags;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"appAssets.js","sourceRoot":"","sources":["../src/appAssets.ts"],"names":[],"mappings":"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","sourcesContent":["// Files that describe the app, found by name rather than configured.\n//\n// src/app/\n// favicon.ico <link rel=\"icon\">\n// icon.png <link rel=\"icon\">, and the manifest's icons\n// icon-192.png …as many as you like; sizes read from the name\n// apple-icon.png <link rel=\"apple-touch-icon\">\n// screenshot-wide-1280x720.png the manifest's screenshots, for Chrome's\n// screenshot-narrow-750x1334.png richer install sheet; size and form\n// factor read from the name\n// apple-splash-1179x2556.png <link rel=\"apple-touch-startup-image\" media=…>\n// …one per device and orientation; see appleSplash.ts\n// opengraph-image.png <meta property=\"og:image\">\n// twitter-image.png <meta name=\"twitter:image\">\n//\n// The two share images may also sit in any folder beneath app/, and are then\n// the card for the routes under that folder - the nearest one wins. See\n// shareImages.ts for the rule and appAssets() for the scan.\n//\n// The same bargain as every other file in this directory: put it where it\n// belongs and it works, with nothing to register. Next established these names\n// and there is nothing to gain by inventing different ones — an app moving\n// between the two should not have to rename its favicon.\n//\n// They live in `app/` rather than `public/` on purpose. `public/` is \"serve\n// this verbatim\"; these are read as well as served — an icon's size decides\n// what goes in the manifest, and its presence decides what goes in the head.\n\nimport { existsSync, readdirSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { isAppleSplash, splashMedia } from \"./appleSplash.js\";\nimport type { ShareImages, ShareKind } from \"./shareImages.js\";\n\n/** One found file, and where it will be served from. */\nexport interface AppAsset {\n /** The file's name in the app directory. */\n file: string;\n /** The url it is served at. */\n href: string;\n}\n\nexport interface AppAssets {\n favicon: AppAsset | null;\n icons: AppAsset[];\n appleIcon: AppAsset | null;\n /** For the manifest's screenshots: wide for desktop, narrow for a phone. */\n screenshots: AppAsset[];\n /** iOS launch screens, each with the media query its size maps to. */\n appleSplash: (AppAsset & { media: string | null })[];\n /**\n * Share images by the folder they sit in, \"\" being app/ itself. The route\n * that shows one is found by walking up from its own folder.\n */\n shareImages: Record<string, Partial<Record<ShareKind, AppAsset>>>;\n}\n\nconst IMAGE = /\\.(png|svg|jpg|jpeg|webp|gif|avif)$/i;\n\n/**\n * Where these are served from.\n *\n * Their own directory rather than the output root, so an app that happens to\n * have a `public/icon.png` as well is not silently overwritten by one of\n * these, in either direction. The exception is the favicon, which browsers ask\n * for at `/favicon.ico` whatever any markup says.\n */\nexport const ASSET_BASE = \"/_app\";\n\n/**\n * A file's path as a url path: each segment encoded, the slashes kept.\n *\n * A folder under app/ may be `[team]`, and a bracket is not legal in a url's\n * path - a strict scraper refuses the whole address, and a card with no image\n * is all it says. Everything a plain file name is made of passes through\n * untouched, so `icon-192.png` is `icon-192.png`.\n */\nexport function assetHref(file: string): string {\n return `${ASSET_BASE}/${file.split(\"/\").map(encodeURIComponent).join(\"/\")}`;\n}\n\n/** The file path a url path names, which is what the build writes and the dev server reads. */\nexport function assetFile(href: string): string {\n try {\n return decodeURIComponent(href);\n } catch {\n // A stray % is not an encoding. Left as it came, it matches nothing.\n return href;\n }\n}\n\nfunction assetsIn(dir: string, match: (name: string) => boolean): AppAsset[] {\n return readdirSync(dir)\n .filter(match)\n .sort()\n .map((file) => ({ file, href: assetHref(file) }));\n}\n\n/** What the app declared by putting a file where it could be found. */\nexport function appAssets(appDir: string): AppAssets {\n if (!existsSync(appDir)) {\n return {\n favicon: null,\n icons: [],\n appleIcon: null,\n appleSplash: [],\n screenshots: [],\n shareImages: {},\n };\n }\n\n const names = readdirSync(appDir);\n const has = (name: string) => names.includes(name);\n\n return {\n // Served where a browser looks for it regardless of markup: a request for\n // /favicon.ico goes out whether or not a link tag exists, and answering it\n // from somewhere else means answering a 404 to the request that matters.\n favicon: has(\"favicon.ico\")\n ? { file: \"favicon.ico\", href: \"/favicon.ico\" }\n : null,\n icons: assetsIn(\n appDir,\n (name) => /^icon[-\\w]*/.test(name) && IMAGE.test(name),\n ),\n appleIcon:\n assetsIn(\n appDir,\n (name) => /^apple-icon[-\\w]*/.test(name) && IMAGE.test(name),\n )[0] ?? null,\n screenshots: assetsIn(appDir, (name) =>\n /^screenshot-(wide|narrow)[-\\w]*\\.(png|jpe?g|webp)$/i.test(name),\n ),\n appleSplash: assetsIn(appDir, isAppleSplash).map((asset) => ({\n ...asset,\n media: splashMedia(asset.file),\n })),\n shareImages: shareImagesIn(appDir),\n };\n}\n\nconst SHARE_FILES: Record<ShareKind, RegExp> = {\n openGraph: /^opengraph-image[-\\w]*/,\n twitter: /^twitter-image[-\\w]*/,\n};\n\n/**\n * Folders that are not part of the url tree, so a file in one answers no route:\n * private (`_x`), a parallel slot (`@x`), an interception (`(.)x`), dependencies\n * and anything hidden.\n */\nfunction isRouteFolder(name: string): boolean {\n return !/^(?:[._@]|\\(\\.{1,3}\\))/.test(name) && name !== \"node_modules\";\n}\n\n/**\n * Every share image under app/, by the folder it is in.\n *\n * A file in a folder is served from a path that keeps the folder, because two\n * folders may each have an opengraph-image.png and one flat /_app/ name would\n * make the second silently overwrite the first.\n */\nfunction shareImagesIn(appDir: string): AppAssets[\"shareImages\"] {\n const found: AppAssets[\"shareImages\"] = {};\n\n const walk = (folder: string): void => {\n const dir = join(appDir, folder);\n const entries = readdirSync(dir, { withFileTypes: true });\n\n for (const kind of Object.keys(SHARE_FILES) as ShareKind[]) {\n const name = entries\n .filter((e) => e.isFile() && SHARE_FILES[kind].test(e.name) && IMAGE.test(e.name))\n .map((e) => e.name)\n .sort()[0];\n\n if (name) {\n const file = folder === \"\" ? name : `${folder}/${name}`;\n\n (found[folder] ??= {})[kind] = { file, href: assetHref(file) };\n }\n }\n\n for (const entry of entries) {\n if (entry.isDirectory() && isRouteFolder(entry.name)) {\n walk(folder === \"\" ? entry.name : `${folder}/${entry.name}`);\n }\n }\n };\n\n walk(\"\");\n\n return found;\n}\n\n/** The urls only, which is all a page being rendered needs of them. */\nexport function shareImageUrls(assets: AppAssets): ShareImages {\n return Object.fromEntries(\n Object.entries(assets.shareImages).map(([folder, kinds]) => [\n folder,\n Object.fromEntries(\n Object.entries(kinds).map(([kind, asset]) => [kind, asset.href]),\n ),\n ]),\n );\n}\n\nexport function typeOf(file: string): string {\n const extension = file.slice(file.lastIndexOf(\".\") + 1).toLowerCase();\n\n return extension === \"svg\"\n ? \"image/svg+xml\"\n : extension === \"jpg\" || extension === \"jpeg\"\n ? \"image/jpeg\"\n : extension === \"ico\"\n ? \"image/x-icon\"\n : `image/${extension}`;\n}\n\n/** Every asset found, favicon first: what the build copies and the dev server answers. */\nexport function allAppAssets(assets: AppAssets): AppAsset[] {\n return [\n ...(assets.favicon ? [assets.favicon] : []),\n ...assets.icons,\n ...(assets.appleIcon ? [assets.appleIcon] : []),\n ...assets.appleSplash,\n ...assets.screenshots,\n ...Object.values(assets.shareImages).flatMap((kinds) => Object.values(kinds)),\n ];\n}\n\n/**\n * The head tags these produce, as plain data the generated entry turns into\n * elements - the same on every page. The share images are not here: which one\n * a page shows depends on where the page is, and that is decided as it renders.\n */\nexport function headTags(\n assets: AppAssets,\n): { tag: \"link\" | \"meta\"; props: Record<string, string> }[] {\n const tags: { tag: \"link\" | \"meta\"; props: Record<string, string> }[] = [];\n\n if (assets.favicon) {\n tags.push({\n tag: \"link\",\n props: { rel: \"icon\", href: assets.favicon.href, type: \"image/x-icon\" },\n });\n }\n\n for (const icon of assets.icons) {\n // A maskable icon is for a launcher, padded for its mask; as a tab's icon\n // it would be the small one. It goes in the manifest and nowhere else.\n if (/(^|-)maskable\\b/i.test(icon.file)) continue;\n\n tags.push({\n tag: \"link\",\n props: { rel: \"icon\", href: icon.href, type: typeOf(icon.file) },\n });\n }\n\n if (assets.appleIcon) {\n tags.push({\n tag: \"link\",\n props: { rel: \"apple-touch-icon\", href: assets.appleIcon.href },\n });\n }\n\n // Only the sizes a device has: a query that matches nothing is bytes in\n // every page for no one, and the build names those files instead.\n const splashes = assets.appleSplash.filter((splash) => splash.media !== null);\n\n for (const splash of splashes) {\n tags.push({\n tag: \"link\",\n props: { rel: \"apple-touch-startup-image\", href: splash.href, media: splash.media! },\n });\n }\n\n // Safari shows a launch screen only to an app launched from the home\n // screen as an app. Whoever put these in app/ meant that.\n if (splashes.length > 0) {\n tags.push({ tag: \"meta\", props: { name: \"mobile-web-app-capable\", content: \"yes\" } });\n tags.push({ tag: \"meta\", props: { name: \"apple-mobile-web-app-capable\", content: \"yes\" } });\n }\n\n return tags;\n}\n"]}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The build's half of app commands: which files in `src/commands` are
|
|
3
|
+
* commands, what the generated entry says about them, and the small entries
|
|
4
|
+
* that run one. The runner they call is `commands.ts`.
|
|
5
|
+
*/
|
|
6
|
+
export interface CommandFile {
|
|
7
|
+
/** What is typed after the binary: the file's name without its extension. */
|
|
8
|
+
name: string;
|
|
9
|
+
/** Absolute path, imported lazily by the entry. */
|
|
10
|
+
file: string;
|
|
11
|
+
/** A plain string literal exported as `description`, for `--help`. */
|
|
12
|
+
description?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface CommandFiles {
|
|
15
|
+
commands: CommandFile[];
|
|
16
|
+
/** Paths under src/commands that are not commands, and why, for the build to say. */
|
|
17
|
+
ignored: {
|
|
18
|
+
path: string;
|
|
19
|
+
reason: string;
|
|
20
|
+
}[];
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The commands under `<sourceDir>/commands`.
|
|
24
|
+
*
|
|
25
|
+
* Flat on purpose: a name is what a person types, and `db/seed.ts` raises
|
|
26
|
+
* whether that is `db:seed`, `db/seed` or `seed`. Declaration files, tests and
|
|
27
|
+
* anything starting with `_` or `.` are not commands - the last is how a
|
|
28
|
+
* helper lives beside the commands that share it.
|
|
29
|
+
*/
|
|
30
|
+
export declare function commandFiles(sourceDir: string): CommandFiles;
|
|
31
|
+
/** The module the generated entry reads the table from, resolved when the bundle is built. */
|
|
32
|
+
export declare const COMMANDS_MODULE_ID = "virtual:rsc-kit/commands";
|
|
33
|
+
/**
|
|
34
|
+
* The table: every command as a dynamic import, so each is a chunk of its own
|
|
35
|
+
* and the server never evaluates one. An empty object when there are none -
|
|
36
|
+
* a Worker's build, say, which does not carry them.
|
|
37
|
+
*
|
|
38
|
+
* A module of its own rather than text in the entry because the entry is
|
|
39
|
+
* written while the config is still being read, before anything knows the
|
|
40
|
+
* build's mode or which Nitro preset it is for.
|
|
41
|
+
*/
|
|
42
|
+
export declare function commandsModule(commands: readonly CommandFile[]): string;
|
|
43
|
+
/**
|
|
44
|
+
* What the generated server entry adds for an app with a `src/commands`:
|
|
45
|
+
* `runCommand` over the table, with the entry's own `register()` and
|
|
46
|
+
* `shutdown()`. `runner` is the engine's `commands` module, named without an
|
|
47
|
+
* extension like every other engine module the entry imports.
|
|
48
|
+
*/
|
|
49
|
+
export declare function commandsEntry(runner: string): string;
|
|
50
|
+
/**
|
|
51
|
+
* The entry a plain build runs a command from, written beside the server:
|
|
52
|
+
*
|
|
53
|
+
* node .output/server/commands.mjs migrate
|
|
54
|
+
* bun .output/server/commands.mjs migrate
|
|
55
|
+
*
|
|
56
|
+
* The server is not started - nothing here imports it - and the flag is set
|
|
57
|
+
* before the entry is imported, since importing it is what would otherwise
|
|
58
|
+
* call register().
|
|
59
|
+
*/
|
|
60
|
+
export declare function commandsEntrySource(): string;
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The build's half of app commands: which files in `src/commands` are
|
|
3
|
+
* commands, what the generated entry says about them, and the small entries
|
|
4
|
+
* that run one. The runner they call is `commands.ts`.
|
|
5
|
+
*/
|
|
6
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
7
|
+
import { join } from 'node:path';
|
|
8
|
+
const SOURCE = /\.(?:[cm]?[jt]sx?)$/;
|
|
9
|
+
const NAME = /^[A-Za-z0-9][A-Za-z0-9_.:-]*$/;
|
|
10
|
+
/** Earlier wins when two files share a name: migrate.ts over migrate.js. */
|
|
11
|
+
const EXTENSION_ORDER = ['ts', 'mts', 'cts', 'tsx', 'js', 'mjs', 'cjs', 'jsx'];
|
|
12
|
+
/**
|
|
13
|
+
* `export const description = 'Apply the database migrations'`, read off the
|
|
14
|
+
* source so `--help` can list a command without loading it - a command's
|
|
15
|
+
* imports are the database and the logger, and listing should not open either.
|
|
16
|
+
* A plain string literal only; anything computed is simply not listed.
|
|
17
|
+
*/
|
|
18
|
+
function describe(source) {
|
|
19
|
+
const match = /^\s*export\s+const\s+description\s*(?::\s*string\s*)?=\s*(["'`])([^"'`\\\n$]*)\1/m.exec(source);
|
|
20
|
+
return match?.[2]?.trim() || undefined;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The commands under `<sourceDir>/commands`.
|
|
24
|
+
*
|
|
25
|
+
* Flat on purpose: a name is what a person types, and `db/seed.ts` raises
|
|
26
|
+
* whether that is `db:seed`, `db/seed` or `seed`. Declaration files, tests and
|
|
27
|
+
* anything starting with `_` or `.` are not commands - the last is how a
|
|
28
|
+
* helper lives beside the commands that share it.
|
|
29
|
+
*/
|
|
30
|
+
export function commandFiles(sourceDir) {
|
|
31
|
+
const dir = join(sourceDir, 'commands');
|
|
32
|
+
const found = { commands: [], ignored: [] };
|
|
33
|
+
if (!existsSync(dir))
|
|
34
|
+
return found;
|
|
35
|
+
const taken = new Map();
|
|
36
|
+
const entries = readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
|
|
37
|
+
for (const entry of entries) {
|
|
38
|
+
const path = join(dir, entry.name);
|
|
39
|
+
if (entry.name.startsWith('_') || entry.name.startsWith('.'))
|
|
40
|
+
continue;
|
|
41
|
+
if (entry.isDirectory()) {
|
|
42
|
+
found.ignored.push({ path, reason: 'commands are the files directly in src/commands, not a folder of them' });
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (!SOURCE.test(entry.name) || /\.d\.[cm]?ts$/.test(entry.name) || /\.(?:test|spec)\.[cm]?[jt]sx?$/.test(entry.name))
|
|
46
|
+
continue;
|
|
47
|
+
const extension = entry.name.slice(entry.name.lastIndexOf('.') + 1);
|
|
48
|
+
const name = entry.name.slice(0, -(extension.length + 1));
|
|
49
|
+
if (!NAME.test(name)) {
|
|
50
|
+
found.ignored.push({ path, reason: `"${name}" cannot be typed as a command: letters, digits, "-", "_", "." and ":"` });
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
const existing = taken.get(name);
|
|
54
|
+
if (existing) {
|
|
55
|
+
const keep = EXTENSION_ORDER.indexOf(existing.file.split('.').pop() ?? '') <= EXTENSION_ORDER.indexOf(extension);
|
|
56
|
+
found.ignored.push({
|
|
57
|
+
path: keep ? path : existing.file,
|
|
58
|
+
reason: `two files are called "${name}"; ${keep ? existing.file : path} is the command`,
|
|
59
|
+
});
|
|
60
|
+
if (keep)
|
|
61
|
+
continue;
|
|
62
|
+
found.commands.splice(found.commands.indexOf(existing), 1);
|
|
63
|
+
}
|
|
64
|
+
const command = { name, file: path, description: describe(readFileSync(path, 'utf-8')) };
|
|
65
|
+
taken.set(name, command);
|
|
66
|
+
found.commands.push(command);
|
|
67
|
+
}
|
|
68
|
+
return found;
|
|
69
|
+
}
|
|
70
|
+
/** The module the generated entry reads the table from, resolved when the bundle is built. */
|
|
71
|
+
export const COMMANDS_MODULE_ID = 'virtual:rsc-kit/commands';
|
|
72
|
+
/**
|
|
73
|
+
* The table: every command as a dynamic import, so each is a chunk of its own
|
|
74
|
+
* and the server never evaluates one. An empty object when there are none -
|
|
75
|
+
* a Worker's build, say, which does not carry them.
|
|
76
|
+
*
|
|
77
|
+
* A module of its own rather than text in the entry because the entry is
|
|
78
|
+
* written while the config is still being read, before anything knows the
|
|
79
|
+
* build's mode or which Nitro preset it is for.
|
|
80
|
+
*/
|
|
81
|
+
export function commandsModule(commands) {
|
|
82
|
+
const rows = commands.map(({ name, file, description }) => ` ${JSON.stringify(name)}: { load: () => import(${JSON.stringify(file)})${description ? `, description: ${JSON.stringify(description)}` : ''} },`);
|
|
83
|
+
return `export const commands = {\n${rows.join('\n')}\n}\n`;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* What the generated server entry adds for an app with a `src/commands`:
|
|
87
|
+
* `runCommand` over the table, with the entry's own `register()` and
|
|
88
|
+
* `shutdown()`. `runner` is the engine's `commands` module, named without an
|
|
89
|
+
* extension like every other engine module the entry imports.
|
|
90
|
+
*/
|
|
91
|
+
export function commandsEntry(runner) {
|
|
92
|
+
return `
|
|
93
|
+
import { commands as __commands } from ${JSON.stringify(COMMANDS_MODULE_ID)}
|
|
94
|
+
import { runCommand as __runCommand } from ${JSON.stringify(runner)}
|
|
95
|
+
|
|
96
|
+
// src/commands, each loaded only when it is named.
|
|
97
|
+
export function runCommand(argv: string[], program?: string) {
|
|
98
|
+
return __runCommand(argv, { commands: __commands, program, ready: instrumented, shutdown: __instrumentation.shutdown })
|
|
99
|
+
}
|
|
100
|
+
`;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* The entry a plain build runs a command from, written beside the server:
|
|
104
|
+
*
|
|
105
|
+
* node .output/server/commands.mjs migrate
|
|
106
|
+
* bun .output/server/commands.mjs migrate
|
|
107
|
+
*
|
|
108
|
+
* The server is not started - nothing here imports it - and the flag is set
|
|
109
|
+
* before the entry is imported, since importing it is what would otherwise
|
|
110
|
+
* call register().
|
|
111
|
+
*/
|
|
112
|
+
export function commandsEntrySource() {
|
|
113
|
+
return ('// Run one of the app\'s commands without starting the server:\n' +
|
|
114
|
+
'// node .output/server/commands.mjs <command> [args]\n' +
|
|
115
|
+
'// bun .output/server/commands.mjs <command> [args]\n' +
|
|
116
|
+
'import { relative } from "node:path";\n' +
|
|
117
|
+
'import { fileURLToPath } from "node:url";\n' +
|
|
118
|
+
'\n' +
|
|
119
|
+
'globalThis[Symbol.for("rsc-kit.command")] = true;\n' +
|
|
120
|
+
'\n' +
|
|
121
|
+
'let code = 1;\n' +
|
|
122
|
+
'\n' +
|
|
123
|
+
'try {\n' +
|
|
124
|
+
' const { runCommand } = await import("./_ssr/rsc.mjs");\n' +
|
|
125
|
+
' const runtime = process.versions.bun ? "bun" : "node";\n' +
|
|
126
|
+
'\n' +
|
|
127
|
+
' code = await runCommand(process.argv.slice(2), runtime + " " + relative(process.cwd(), fileURLToPath(import.meta.url)));\n' +
|
|
128
|
+
'} catch (error) {\n' +
|
|
129
|
+
' // The app failed to load: instrumentation.ts refusing a bad environment,\n' +
|
|
130
|
+
' // as it would stop the server.\n' +
|
|
131
|
+
' console.error("[rsc-kit] the app failed to load:", error);\n' +
|
|
132
|
+
'}\n' +
|
|
133
|
+
'\n' +
|
|
134
|
+
'process.exit(code);\n');
|
|
135
|
+
}
|
|
136
|
+
//# sourceMappingURL=commandFiles.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commandFiles.js","sourceRoot":"","sources":["../src/commandFiles.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAC/D,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAiBhC,MAAM,MAAM,GAAG,qBAAqB,CAAA;AACpC,MAAM,IAAI,GAAG,+BAA+B,CAAA;AAE5C,4EAA4E;AAC5E,MAAM,eAAe,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,CAAA;AAE9E;;;;;GAKG;AACH,SAAS,QAAQ,CAAC,MAAc;IAC9B,MAAM,KAAK,GAAG,mFAAmF,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;IAE9G,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,SAAS,CAAA;AACxC,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAAC,SAAiB;IAC5C,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,EAAE,UAAU,CAAC,CAAA;IACvC,MAAM,KAAK,GAAiB,EAAE,QAAQ,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,CAAA;IAEzD,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,KAAK,CAAA;IAElC,MAAM,KAAK,GAAG,IAAI,GAAG,EAAuB,CAAA;IAC5C,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAA;IAEtG,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,CAAA;QAElC,IAAI,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,SAAQ;QAEtE,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;YACxB,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,uEAAuE,EAAE,CAAC,CAAA;YAE7G,SAAQ;QACV,CAAC;QAED,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,gCAAgC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;YAAE,SAAQ;QAE/H,MAAM,SAAS,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAA;QACnE,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAA;QAEzD,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACrB,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,IAAI,wEAAwE,EAAE,CAAC,CAAA;YAEtH,SAAQ;QACV,CAAC;QAED,MAAM,QAAQ,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;QAEhC,IAAI,QAAQ,EAAE,CAAC;YACb,MAAM,IAAI,GAAG,eAAe,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,IAAI,eAAe,CAAC,OAAO,CAAC,SAAS,CAAC,CAAA;YAEhH,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC;gBACjB,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI;gBACjC,MAAM,EAAE,yBAAyB,IAAI,MAAM,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,iBAAiB;aACxF,CAAC,CAAA;YAEF,IAAI,IAAI;gBAAE,SAAQ;YAElB,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAA;QAC5D,CAAC;QAED,MAAM,OAAO,GAAgB,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,QAAQ,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,EAAE,CAAA;QAErG,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;QACxB,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;IAC9B,CAAC;IAED,OAAO,KAAK,CAAA;AACd,CAAC;AAED,8FAA8F;AAC9F,MAAM,CAAC,MAAM,kBAAkB,GAAG,0BAA0B,CAAA;AAE5D;;;;;;;;GAQG;AACH,MAAM,UAAU,cAAc,CAAC,QAAgC;IAC7D,MAAM,IAAI,GAAG,QAAQ,CAAC,GAAG,CACvB,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,EAAE,CAC9B,KAAK,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,0BAA0B,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,WAAW,CAAC,CAAC,CAAC,kBAAkB,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,CACrJ,CAAA;IAED,OAAO,8BAA8B,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAA;AAC7D,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,MAAc;IAC1C,OAAO;yCACgC,IAAI,CAAC,SAAS,CAAC,kBAAkB,CAAC;6CAC9B,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC;;;;;;CAMlE,CAAA;AACD,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,mBAAmB;IACjC,OAAO,CACL,kEAAkE;QAClE,0DAA0D;QAC1D,yDAAyD;QACzD,yCAAyC;QACzC,6CAA6C;QAC7C,IAAI;QACJ,qDAAqD;QACrD,IAAI;QACJ,iBAAiB;QACjB,IAAI;QACJ,SAAS;QACT,4DAA4D;QAC5D,4DAA4D;QAC5D,IAAI;QACJ,8HAA8H;QAC9H,qBAAqB;QACrB,+EAA+E;QAC/E,qCAAqC;QACrC,gEAAgE;QAChE,KAAK;QACL,IAAI;QACJ,uBAAuB,CACxB,CAAA;AACH,CAAC","sourcesContent":["/**\n * The build's half of app commands: which files in `src/commands` are\n * commands, what the generated entry says about them, and the small entries\n * that run one. The runner they call is `commands.ts`.\n */\n\nimport { existsSync, readdirSync, readFileSync } from 'node:fs'\nimport { join } from 'node:path'\n\nexport interface CommandFile {\n /** What is typed after the binary: the file's name without its extension. */\n name: string\n /** Absolute path, imported lazily by the entry. */\n file: string\n /** A plain string literal exported as `description`, for `--help`. */\n description?: string\n}\n\nexport interface CommandFiles {\n commands: CommandFile[]\n /** Paths under src/commands that are not commands, and why, for the build to say. */\n ignored: { path: string; reason: string }[]\n}\n\nconst SOURCE = /\\.(?:[cm]?[jt]sx?)$/\nconst NAME = /^[A-Za-z0-9][A-Za-z0-9_.:-]*$/\n\n/** Earlier wins when two files share a name: migrate.ts over migrate.js. */\nconst EXTENSION_ORDER = ['ts', 'mts', 'cts', 'tsx', 'js', 'mjs', 'cjs', 'jsx']\n\n/**\n * `export const description = 'Apply the database migrations'`, read off the\n * source so `--help` can list a command without loading it - a command's\n * imports are the database and the logger, and listing should not open either.\n * A plain string literal only; anything computed is simply not listed.\n */\nfunction describe(source: string): string | undefined {\n const match = /^\\s*export\\s+const\\s+description\\s*(?::\\s*string\\s*)?=\\s*([\"'`])([^\"'`\\\\\\n$]*)\\1/m.exec(source)\n\n return match?.[2]?.trim() || undefined\n}\n\n/**\n * The commands under `<sourceDir>/commands`.\n *\n * Flat on purpose: a name is what a person types, and `db/seed.ts` raises\n * whether that is `db:seed`, `db/seed` or `seed`. Declaration files, tests and\n * anything starting with `_` or `.` are not commands - the last is how a\n * helper lives beside the commands that share it.\n */\nexport function commandFiles(sourceDir: string): CommandFiles {\n const dir = join(sourceDir, 'commands')\n const found: CommandFiles = { commands: [], ignored: [] }\n\n if (!existsSync(dir)) return found\n\n const taken = new Map<string, CommandFile>()\n const entries = readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))\n\n for (const entry of entries) {\n const path = join(dir, entry.name)\n\n if (entry.name.startsWith('_') || entry.name.startsWith('.')) continue\n\n if (entry.isDirectory()) {\n found.ignored.push({ path, reason: 'commands are the files directly in src/commands, not a folder of them' })\n\n continue\n }\n\n if (!SOURCE.test(entry.name) || /\\.d\\.[cm]?ts$/.test(entry.name) || /\\.(?:test|spec)\\.[cm]?[jt]sx?$/.test(entry.name)) continue\n\n const extension = entry.name.slice(entry.name.lastIndexOf('.') + 1)\n const name = entry.name.slice(0, -(extension.length + 1))\n\n if (!NAME.test(name)) {\n found.ignored.push({ path, reason: `\"${name}\" cannot be typed as a command: letters, digits, \"-\", \"_\", \".\" and \":\"` })\n\n continue\n }\n\n const existing = taken.get(name)\n\n if (existing) {\n const keep = EXTENSION_ORDER.indexOf(existing.file.split('.').pop() ?? '') <= EXTENSION_ORDER.indexOf(extension)\n\n found.ignored.push({\n path: keep ? path : existing.file,\n reason: `two files are called \"${name}\"; ${keep ? existing.file : path} is the command`,\n })\n\n if (keep) continue\n\n found.commands.splice(found.commands.indexOf(existing), 1)\n }\n\n const command: CommandFile = { name, file: path, description: describe(readFileSync(path, 'utf-8')) }\n\n taken.set(name, command)\n found.commands.push(command)\n }\n\n return found\n}\n\n/** The module the generated entry reads the table from, resolved when the bundle is built. */\nexport const COMMANDS_MODULE_ID = 'virtual:rsc-kit/commands'\n\n/**\n * The table: every command as a dynamic import, so each is a chunk of its own\n * and the server never evaluates one. An empty object when there are none -\n * a Worker's build, say, which does not carry them.\n *\n * A module of its own rather than text in the entry because the entry is\n * written while the config is still being read, before anything knows the\n * build's mode or which Nitro preset it is for.\n */\nexport function commandsModule(commands: readonly CommandFile[]): string {\n const rows = commands.map(\n ({ name, file, description }) =>\n ` ${JSON.stringify(name)}: { load: () => import(${JSON.stringify(file)})${description ? `, description: ${JSON.stringify(description)}` : ''} },`,\n )\n\n return `export const commands = {\\n${rows.join('\\n')}\\n}\\n`\n}\n\n/**\n * What the generated server entry adds for an app with a `src/commands`:\n * `runCommand` over the table, with the entry's own `register()` and\n * `shutdown()`. `runner` is the engine's `commands` module, named without an\n * extension like every other engine module the entry imports.\n */\nexport function commandsEntry(runner: string): string {\n return `\nimport { commands as __commands } from ${JSON.stringify(COMMANDS_MODULE_ID)}\nimport { runCommand as __runCommand } from ${JSON.stringify(runner)}\n\n// src/commands, each loaded only when it is named.\nexport function runCommand(argv: string[], program?: string) {\n return __runCommand(argv, { commands: __commands, program, ready: instrumented, shutdown: __instrumentation.shutdown })\n}\n`\n}\n\n/**\n * The entry a plain build runs a command from, written beside the server:\n *\n * node .output/server/commands.mjs migrate\n * bun .output/server/commands.mjs migrate\n *\n * The server is not started - nothing here imports it - and the flag is set\n * before the entry is imported, since importing it is what would otherwise\n * call register().\n */\nexport function commandsEntrySource(): string {\n return (\n '// Run one of the app\\'s commands without starting the server:\\n' +\n '// node .output/server/commands.mjs <command> [args]\\n' +\n '// bun .output/server/commands.mjs <command> [args]\\n' +\n 'import { relative } from \"node:path\";\\n' +\n 'import { fileURLToPath } from \"node:url\";\\n' +\n '\\n' +\n 'globalThis[Symbol.for(\"rsc-kit.command\")] = true;\\n' +\n '\\n' +\n 'let code = 1;\\n' +\n '\\n' +\n 'try {\\n' +\n ' const { runCommand } = await import(\"./_ssr/rsc.mjs\");\\n' +\n ' const runtime = process.versions.bun ? \"bun\" : \"node\";\\n' +\n '\\n' +\n ' code = await runCommand(process.argv.slice(2), runtime + \" \" + relative(process.cwd(), fileURLToPath(import.meta.url)));\\n' +\n '} catch (error) {\\n' +\n ' // The app failed to load: instrumentation.ts refusing a bad environment,\\n' +\n ' // as it would stop the server.\\n' +\n ' console.error(\"[rsc-kit] the app failed to load:\", error);\\n' +\n '}\\n' +\n '\\n' +\n 'process.exit(code);\\n'\n )\n}\n"]}
|
|
@@ -0,0 +1,74 @@
|
|
|
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 declare const COMMAND_MODE: unique symbol;
|
|
31
|
+
/** What a command receives beside its arguments. */
|
|
32
|
+
export interface CommandContext {
|
|
33
|
+
/** Aborted by SIGTERM or SIGINT: a long-running command stops when it is. */
|
|
34
|
+
signal: AbortSignal;
|
|
35
|
+
}
|
|
36
|
+
/** What the build knows of one command file: how to load it, and a line to list it by. */
|
|
37
|
+
export interface CommandEntry {
|
|
38
|
+
load: () => Promise<{
|
|
39
|
+
default?: unknown;
|
|
40
|
+
register?: unknown;
|
|
41
|
+
}>;
|
|
42
|
+
description?: string;
|
|
43
|
+
}
|
|
44
|
+
export interface RunCommandOptions {
|
|
45
|
+
commands: Record<string, CommandEntry>;
|
|
46
|
+
/** instrumentation.ts's register(), once. Awaited only for a command that exports `register = true`. */
|
|
47
|
+
ready: () => Promise<unknown>;
|
|
48
|
+
/** instrumentation.ts's shutdown(), called after a command that was given `register()`. */
|
|
49
|
+
shutdown?: () => unknown;
|
|
50
|
+
/** How the usage line names this program: `./app`, `node .output/server/commands.mjs`. */
|
|
51
|
+
program?: string;
|
|
52
|
+
out?: (text: string) => void;
|
|
53
|
+
err?: (text: string) => void;
|
|
54
|
+
/** SIGTERM and SIGINT. The process, unless a test brings its own. */
|
|
55
|
+
signals?: {
|
|
56
|
+
on: (signal: 'SIGTERM' | 'SIGINT', listener: () => void) => unknown;
|
|
57
|
+
off: (signal: 'SIGTERM' | 'SIGINT', listener: () => void) => unknown;
|
|
58
|
+
};
|
|
59
|
+
/** Milliseconds a command gets to stop once told to, and shutdown() after it. RSC_SHUTDOWN_TIMEOUT seconds (10) by default. */
|
|
60
|
+
limit?: number;
|
|
61
|
+
}
|
|
62
|
+
/** Whether this process was started to run a command rather than to serve. */
|
|
63
|
+
export declare function inCommandMode(): boolean;
|
|
64
|
+
/** The list `--help` prints, and what an unknown name is answered with. */
|
|
65
|
+
export declare function commandHelp(commands: Record<string, CommandEntry>, program?: string): string;
|
|
66
|
+
/**
|
|
67
|
+
* Run the command `argv` names and say how it ended.
|
|
68
|
+
*
|
|
69
|
+
* Resolves to the exit code and leaves exiting to the caller, so a test sees
|
|
70
|
+
* the code and an entry decides when the process stops. 0 is success and 1 is
|
|
71
|
+
* a command that threw or would not stop; 2 is a name that is not a command,
|
|
72
|
+
* as it is for a tool that was misused.
|
|
73
|
+
*/
|
|
74
|
+
export declare function runCommand(argv: readonly string[], options: RunCommandOptions): Promise<number>;
|