@t09tanaka/stoneage 0.1.0

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.
Files changed (47) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/LICENSE +21 -0
  3. package/README.md +768 -0
  4. package/dist/agent-skill.d.ts +1 -0
  5. package/dist/agent-skill.js +140 -0
  6. package/dist/assets.d.ts +125 -0
  7. package/dist/assets.js +341 -0
  8. package/dist/cli.d.ts +236 -0
  9. package/dist/cli.js +3077 -0
  10. package/dist/core.d.ts +473 -0
  11. package/dist/core.js +2897 -0
  12. package/dist/data.d.ts +121 -0
  13. package/dist/data.js +358 -0
  14. package/dist/deploy.d.ts +34 -0
  15. package/dist/deploy.js +203 -0
  16. package/dist/dev.d.ts +36 -0
  17. package/dist/dev.js +313 -0
  18. package/dist/example.d.ts +134 -0
  19. package/dist/example.js +1272 -0
  20. package/dist/fragment/client.d.ts +13 -0
  21. package/dist/fragment/client.js +150 -0
  22. package/dist/fragment.d.ts +15 -0
  23. package/dist/fragment.js +27 -0
  24. package/dist/html.d.ts +57 -0
  25. package/dist/html.js +208 -0
  26. package/dist/images.d.ts +95 -0
  27. package/dist/images.js +292 -0
  28. package/dist/index.d.ts +1 -0
  29. package/dist/index.js +1 -0
  30. package/dist/migration.d.ts +157 -0
  31. package/dist/migration.js +983 -0
  32. package/dist/optimize.d.ts +15 -0
  33. package/dist/optimize.js +215 -0
  34. package/dist/pagination.d.ts +26 -0
  35. package/dist/pagination.js +62 -0
  36. package/dist/paths.d.ts +1 -0
  37. package/dist/paths.js +10 -0
  38. package/dist/search.d.ts +32 -0
  39. package/dist/search.js +55 -0
  40. package/dist/testing.d.ts +66 -0
  41. package/dist/testing.js +97 -0
  42. package/dist/validate.d.ts +2 -0
  43. package/dist/validate.js +1 -0
  44. package/jsx-loader.mjs +52 -0
  45. package/jsx-register.mjs +7 -0
  46. package/package.json +135 -0
  47. package/tsconfig.base.json +16 -0
package/dist/deploy.js ADDED
@@ -0,0 +1,203 @@
1
+ import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
2
+ import { dirname, join } from "node:path";
3
+ export async function writeDeployArtifacts(options) {
4
+ if (options.provider === "github-pages") {
5
+ return writeGitHubPagesArtifacts(options);
6
+ }
7
+ if (options.provider !== "netlify") {
8
+ throw new Error(`Unsupported deploy provider: ${options.provider}`);
9
+ }
10
+ const redirects = await readRedirectsManifest(join(options.outDir, "redirects.json"));
11
+ const headers = await readHeadersManifest(join(options.outDir, "headers.json"));
12
+ const written = [];
13
+ if (redirects.redirects.length > 0) {
14
+ await writeText(join(options.outDir, "_redirects"), renderNetlifyRedirects(redirects.redirects));
15
+ written.push("_redirects");
16
+ }
17
+ else {
18
+ await rm(join(options.outDir, "_redirects"), { force: true });
19
+ }
20
+ if (headers.headers.length > 0) {
21
+ await writeText(join(options.outDir, "_headers"), renderNetlifyHeaders(headers.headers));
22
+ written.push("_headers");
23
+ }
24
+ else {
25
+ await rm(join(options.outDir, "_headers"), { force: true });
26
+ }
27
+ return {
28
+ provider: options.provider,
29
+ files: written,
30
+ redirects: redirects.redirects.length,
31
+ headers: headers.headers.length,
32
+ };
33
+ }
34
+ async function writeGitHubPagesArtifacts(options) {
35
+ const written = [];
36
+ // .nojekyll lets GitHub Pages serve paths that start with an underscore.
37
+ // It is always written as an empty, idempotent file.
38
+ await writeIfChanged(join(options.outDir, ".nojekyll"), "");
39
+ written.push(".nojekyll");
40
+ const cname = normalizeCname(options.cname);
41
+ if (cname !== undefined) {
42
+ await writeIfChanged(join(options.outDir, "CNAME"), `${cname}\n`);
43
+ written.push("CNAME");
44
+ }
45
+ return {
46
+ provider: options.provider,
47
+ files: written,
48
+ redirects: 0,
49
+ headers: 0,
50
+ };
51
+ }
52
+ export function normalizeCname(cname) {
53
+ if (cname === undefined) {
54
+ return undefined;
55
+ }
56
+ const trimmed = cname.trim();
57
+ if (!trimmed) {
58
+ return undefined;
59
+ }
60
+ if (/\s/.test(trimmed)) {
61
+ throw new Error("CNAME must be a single host without whitespace or multiple lines.");
62
+ }
63
+ // Accept full URLs by extracting the host only (no protocol, no path).
64
+ const withProtocol = /^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed)
65
+ ? trimmed
66
+ : `https://${trimmed}`;
67
+ let parsed;
68
+ try {
69
+ parsed = new URL(withProtocol);
70
+ }
71
+ catch {
72
+ throw new Error(`Invalid CNAME domain: ${cname}`);
73
+ }
74
+ // GitHub Pages CNAME accepts only a bare hostname, so drop any port,
75
+ // userinfo, or path that may have been supplied with a full URL.
76
+ const host = parsed.hostname;
77
+ if (!host || parsed.port || parsed.username || parsed.password) {
78
+ throw new Error(`Invalid CNAME domain: ${cname}`);
79
+ }
80
+ return host;
81
+ }
82
+ export function renderNetlifyRedirects(redirects) {
83
+ return redirects
84
+ .map((redirect) => `${redirect.from} ${redirect.to} ${redirect.status}`)
85
+ .join("\n")
86
+ .concat("\n");
87
+ }
88
+ export function renderNetlifyHeaders(headers) {
89
+ return headers
90
+ .map((entry) => {
91
+ const lines = [entry.path];
92
+ for (const [name, value] of Object.entries(entry.headers)) {
93
+ lines.push(` ${netlifyHeaderName(name)}: ${value}`);
94
+ }
95
+ return lines.join("\n");
96
+ })
97
+ .join("\n\n")
98
+ .concat("\n");
99
+ }
100
+ async function readRedirectsManifest(path) {
101
+ const raw = await readOptionalText(path);
102
+ if (!raw) {
103
+ return { redirects: [] };
104
+ }
105
+ const parsed = parseManifestJson(raw, "redirects.json");
106
+ if (!isRedirectsManifest(parsed)) {
107
+ throw new Error("redirects.json must contain a redirects array with from, to, and status entries.");
108
+ }
109
+ return parsed;
110
+ }
111
+ async function readHeadersManifest(path) {
112
+ const raw = await readOptionalText(path);
113
+ if (!raw) {
114
+ return { headers: [] };
115
+ }
116
+ const parsed = parseManifestJson(raw, "headers.json");
117
+ if (!isHeadersManifest(parsed)) {
118
+ throw new Error("headers.json must contain a headers array with path and headers entries.");
119
+ }
120
+ return parsed;
121
+ }
122
+ function parseManifestJson(raw, name) {
123
+ try {
124
+ return JSON.parse(raw);
125
+ }
126
+ catch (error) {
127
+ throw new Error(`${name} is not valid JSON: ${errorMessage(error)}.`);
128
+ }
129
+ }
130
+ function isRedirectsManifest(value) {
131
+ if (!value || typeof value !== "object" || !("redirects" in value)) {
132
+ return false;
133
+ }
134
+ const redirects = value.redirects;
135
+ return Array.isArray(redirects) && redirects.every(isRedirectManifestEntry);
136
+ }
137
+ function isRedirectManifestEntry(value) {
138
+ if (!value || typeof value !== "object") {
139
+ return false;
140
+ }
141
+ const entry = value;
142
+ return (typeof entry.from === "string" &&
143
+ Boolean(entry.from) &&
144
+ typeof entry.to === "string" &&
145
+ Boolean(entry.to) &&
146
+ typeof entry.status === "number");
147
+ }
148
+ function isHeadersManifest(value) {
149
+ if (!value || typeof value !== "object" || !("headers" in value)) {
150
+ return false;
151
+ }
152
+ const headers = value.headers;
153
+ return Array.isArray(headers) && headers.every(isHeadersManifestEntry);
154
+ }
155
+ function isHeadersManifestEntry(value) {
156
+ if (!value || typeof value !== "object") {
157
+ return false;
158
+ }
159
+ const entry = value;
160
+ return typeof entry.path === "string" && Boolean(entry.path) && isStringRecord(entry.headers);
161
+ }
162
+ function isStringRecord(value) {
163
+ return (value !== null &&
164
+ typeof value === "object" &&
165
+ !Array.isArray(value) &&
166
+ Object.values(value).every((item) => typeof item === "string"));
167
+ }
168
+ function netlifyHeaderName(name) {
169
+ return name
170
+ .split("-")
171
+ .map((part) => `${part.slice(0, 1).toUpperCase()}${part.slice(1).toLowerCase()}`)
172
+ .join("-");
173
+ }
174
+ async function readOptionalText(path) {
175
+ try {
176
+ return await readFile(path, "utf8");
177
+ }
178
+ catch (error) {
179
+ if (isMissingPath(error)) {
180
+ return undefined;
181
+ }
182
+ throw error;
183
+ }
184
+ }
185
+ async function writeText(path, contents) {
186
+ await mkdir(dirname(path), { recursive: true });
187
+ await writeFile(path, contents);
188
+ }
189
+ // Writes only when the file is absent or its contents differ, so reruns are
190
+ // idempotent.
191
+ async function writeIfChanged(path, contents) {
192
+ const existing = await readOptionalText(path);
193
+ if (existing === contents) {
194
+ return;
195
+ }
196
+ await writeText(path, contents);
197
+ }
198
+ function isMissingPath(error) {
199
+ return error instanceof Error && "code" in error && error.code === "ENOENT";
200
+ }
201
+ function errorMessage(error) {
202
+ return error instanceof Error ? error.message : String(error);
203
+ }
package/dist/dev.d.ts ADDED
@@ -0,0 +1,36 @@
1
+ export type DevServerOptions = {
2
+ outDir: string;
3
+ host?: string;
4
+ port?: number;
5
+ watchPaths?: string[];
6
+ buildCommand?: string;
7
+ debounceMs?: number;
8
+ cwd?: string;
9
+ logger?: Pick<Console, "error" | "log">;
10
+ };
11
+ export type DevServer = {
12
+ url: string;
13
+ close: () => Promise<void>;
14
+ reload: () => void;
15
+ };
16
+ export type DevServerFile = {
17
+ absolutePath: string;
18
+ contentType: string;
19
+ injectHotReload: boolean;
20
+ };
21
+ export type BuildScheduler = {
22
+ schedule: () => Promise<void>;
23
+ };
24
+ type BuildSchedulerOptions = {
25
+ debounceMs: number;
26
+ runBuild: () => Promise<void>;
27
+ onReload: () => void;
28
+ onError: (error: Error) => void;
29
+ onStart?: () => void;
30
+ };
31
+ export declare function injectHotReloadClient(html: string): string;
32
+ export declare function resolveDevServerFile(outDir: string, requestUrl: string): Promise<DevServerFile | undefined>;
33
+ export declare function createBuildScheduler(options: BuildSchedulerOptions): BuildScheduler;
34
+ export declare function startDevServer(options: DevServerOptions): Promise<DevServer>;
35
+ export declare function runBuildCommand(command: string, cwd?: string): Promise<void>;
36
+ export {};
package/dist/dev.js ADDED
@@ -0,0 +1,313 @@
1
+ import { spawn } from "node:child_process";
2
+ import { watch } from "node:fs";
3
+ import { mkdir, readFile, readdir, realpath, stat } from "node:fs/promises";
4
+ import { createServer } from "node:http";
5
+ import { basename, dirname, extname, join, relative, resolve, sep } from "node:path";
6
+ const reloadEventsPath = "/__stoneage/events";
7
+ const hotReloadClient = `<script type="module">const events = new EventSource("${reloadEventsPath}");
8
+ events.addEventListener("reload", () => location.reload());
9
+ </script>`;
10
+ export function injectHotReloadClient(html) {
11
+ const closingBody = /<\/body\s*>/i.exec(html);
12
+ if (!closingBody) {
13
+ return `${html}${hotReloadClient}`;
14
+ }
15
+ return `${html.slice(0, closingBody.index)}${hotReloadClient}${html.slice(closingBody.index)}`;
16
+ }
17
+ export async function resolveDevServerFile(outDir, requestUrl) {
18
+ const root = await realpath(outDir).catch(() => resolve(outDir));
19
+ let pathname;
20
+ try {
21
+ pathname = decodeURIComponent(new URL(requestUrl, "http://stoneage.local").pathname);
22
+ }
23
+ catch {
24
+ return undefined;
25
+ }
26
+ if (pathname.includes("\0") || pathname === reloadEventsPath) {
27
+ return undefined;
28
+ }
29
+ const relativePath = pathname.replace(/^\/+/, "");
30
+ const baseCandidate = resolve(root, relativePath || ".");
31
+ if (!isInsidePath(root, baseCandidate)) {
32
+ return undefined;
33
+ }
34
+ const candidates = pathname.endsWith("/")
35
+ ? [join(baseCandidate, "index.html")]
36
+ : [
37
+ baseCandidate,
38
+ join(baseCandidate, "index.html"),
39
+ `${baseCandidate}.html`,
40
+ ];
41
+ for (const candidate of candidates) {
42
+ if (!isInsidePath(root, candidate)) {
43
+ continue;
44
+ }
45
+ const candidateStat = await stat(candidate).catch(() => undefined);
46
+ if (!candidateStat?.isFile()) {
47
+ continue;
48
+ }
49
+ const realCandidate = await realpath(candidate).catch(() => candidate);
50
+ if (!isInsidePath(root, realCandidate)) {
51
+ continue;
52
+ }
53
+ const contentType = contentTypeForPath(candidate);
54
+ return {
55
+ absolutePath: candidate,
56
+ contentType,
57
+ injectHotReload: contentType.startsWith("text/html"),
58
+ };
59
+ }
60
+ return undefined;
61
+ }
62
+ async function readNotFoundPage(outDir) {
63
+ const root = await realpath(outDir).catch(() => resolve(outDir));
64
+ const candidate = join(root, "404.html");
65
+ const candidateStat = await stat(candidate).catch(() => undefined);
66
+ if (!candidateStat?.isFile()) {
67
+ return undefined;
68
+ }
69
+ return readFile(candidate, "utf8").catch(() => undefined);
70
+ }
71
+ export function createBuildScheduler(options) {
72
+ let timer;
73
+ let active = Promise.resolve();
74
+ let waiters = [];
75
+ const resolveWaiters = () => {
76
+ const current = waiters;
77
+ waiters = [];
78
+ for (const resolveWaiter of current) {
79
+ resolveWaiter();
80
+ }
81
+ };
82
+ const run = async () => {
83
+ try {
84
+ options.onStart?.();
85
+ await options.runBuild();
86
+ options.onReload();
87
+ }
88
+ catch (error) {
89
+ options.onError(error instanceof Error ? error : new Error(String(error)));
90
+ }
91
+ };
92
+ return {
93
+ schedule: () => new Promise((resolveSchedule) => {
94
+ waiters.push(resolveSchedule);
95
+ if (timer) {
96
+ clearTimeout(timer);
97
+ }
98
+ timer = setTimeout(() => {
99
+ timer = undefined;
100
+ active = active.then(run, run).finally(resolveWaiters);
101
+ }, options.debounceMs);
102
+ }),
103
+ };
104
+ }
105
+ export async function startDevServer(options) {
106
+ const host = options.host ?? "127.0.0.1";
107
+ const port = options.port ?? 4321;
108
+ const debounceMs = options.debounceMs ?? 100;
109
+ const logger = options.logger ?? console;
110
+ const clients = new Set();
111
+ const watchers = [];
112
+ const outDir = resolve(options.outDir);
113
+ const notifyReload = () => {
114
+ for (const client of clients) {
115
+ client.write("event: reload\ndata: {}\n\n");
116
+ }
117
+ };
118
+ const scheduler = createBuildScheduler({
119
+ debounceMs,
120
+ runBuild: options.buildCommand
121
+ ? () => runBuildCommand(options.buildCommand, options.cwd ?? process.cwd())
122
+ : async () => undefined,
123
+ onStart: () => {
124
+ if (options.buildCommand) {
125
+ logger.log(`StoneAge dev rebuild started: ${options.buildCommand}`);
126
+ }
127
+ },
128
+ onReload: () => {
129
+ logger.log("StoneAge dev reload");
130
+ notifyReload();
131
+ },
132
+ onError: (error) => logger.error(`StoneAge dev build failed: ${error.message}`),
133
+ });
134
+ if (options.buildCommand) {
135
+ logger.log(`StoneAge dev initial build: ${options.buildCommand}`);
136
+ await runBuildCommand(options.buildCommand, options.cwd ?? process.cwd()).catch((error) => {
137
+ logger.error(`StoneAge dev initial build failed: ${error instanceof Error ? error.message : String(error)}`);
138
+ });
139
+ }
140
+ const server = createServer(async (request, response) => {
141
+ const requestUrl = request.url ?? "/";
142
+ if (new URL(requestUrl, "http://stoneage.local").pathname === reloadEventsPath) {
143
+ response.writeHead(200, {
144
+ "Content-Type": "text/event-stream; charset=utf-8",
145
+ "Cache-Control": "no-cache, no-transform",
146
+ Connection: "keep-alive",
147
+ });
148
+ response.write(": connected\n\n");
149
+ clients.add(response);
150
+ request.on("close", () => clients.delete(response));
151
+ return;
152
+ }
153
+ const file = await resolveDevServerFile(outDir, requestUrl);
154
+ if (!file) {
155
+ const notFoundHtml = await readNotFoundPage(outDir);
156
+ if (notFoundHtml !== undefined) {
157
+ response.writeHead(404, {
158
+ "Content-Type": "text/html; charset=utf-8",
159
+ "Cache-Control": "no-cache",
160
+ });
161
+ response.end(injectHotReloadClient(notFoundHtml));
162
+ return;
163
+ }
164
+ response.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" });
165
+ response.end("Not found\n");
166
+ return;
167
+ }
168
+ const body = await readFile(file.absolutePath, file.injectHotReload ? "utf8" : undefined);
169
+ response.writeHead(200, {
170
+ "Content-Type": file.contentType,
171
+ "Cache-Control": "no-cache",
172
+ });
173
+ response.end(file.injectHotReload ? injectHotReloadClient(body) : body);
174
+ });
175
+ await listen(server, port, host);
176
+ for (const watchPath of options.watchPaths ?? []) {
177
+ watchers.push(...(await watchDirectories(resolve(options.cwd ?? process.cwd(), watchPath), outDir, () => {
178
+ void scheduler.schedule();
179
+ })));
180
+ }
181
+ const address = server.address();
182
+ const actualPort = typeof address === "object" && address ? address.port : port;
183
+ const url = `http://${host}:${actualPort}`;
184
+ return {
185
+ url,
186
+ reload: notifyReload,
187
+ close: async () => {
188
+ for (const watcher of watchers) {
189
+ watcher.close();
190
+ }
191
+ for (const client of clients) {
192
+ client.end();
193
+ }
194
+ await new Promise((resolveClose, rejectClose) => {
195
+ server.close((error) => (error ? rejectClose(error) : resolveClose()));
196
+ });
197
+ },
198
+ };
199
+ }
200
+ export function runBuildCommand(command, cwd = process.cwd()) {
201
+ return new Promise((resolveRun, rejectRun) => {
202
+ const child = spawn(command, {
203
+ cwd,
204
+ shell: true,
205
+ stdio: "inherit",
206
+ });
207
+ child.on("error", rejectRun);
208
+ child.on("exit", (code, signal) => {
209
+ if (code === 0) {
210
+ resolveRun();
211
+ return;
212
+ }
213
+ rejectRun(new Error(signal ? `${command} exited with ${signal}` : `${command} exited with ${code}`));
214
+ });
215
+ });
216
+ }
217
+ async function listen(server, port, host) {
218
+ await new Promise((resolveListen, rejectListen) => {
219
+ server.once("error", rejectListen);
220
+ server.listen(port, host, () => {
221
+ server.off("error", rejectListen);
222
+ resolveListen();
223
+ });
224
+ });
225
+ }
226
+ async function watchDirectories(root, outDir, onChange) {
227
+ const watchers = [];
228
+ const watchedDirs = new Set();
229
+ const rootStat = await stat(root).catch(() => undefined);
230
+ if (rootStat?.isFile()) {
231
+ const fileDir = dirname(root);
232
+ const fileName = basename(root);
233
+ watchers.push(watch(fileDir, (_eventType, filename) => {
234
+ if (!filename || String(filename) === fileName) {
235
+ onChange();
236
+ }
237
+ }));
238
+ return watchers;
239
+ }
240
+ if (!rootStat) {
241
+ await mkdir(root, { recursive: true });
242
+ }
243
+ const visit = async (dir) => {
244
+ if (shouldIgnoreWatchPath(dir, outDir)) {
245
+ return;
246
+ }
247
+ if (watchedDirs.has(dir)) {
248
+ return;
249
+ }
250
+ watchedDirs.add(dir);
251
+ watchers.push(watch(dir, (_eventType, filename) => {
252
+ if (filename) {
253
+ const changedPath = resolve(dir, String(filename));
254
+ if (shouldIgnoreWatchPath(changedPath, outDir)) {
255
+ return;
256
+ }
257
+ void stat(changedPath).then((changedStat) => {
258
+ if (changedStat.isDirectory()) {
259
+ void visit(changedPath);
260
+ }
261
+ }, () => undefined);
262
+ }
263
+ onChange();
264
+ }));
265
+ const entries = await readdir(dir, { withFileTypes: true }).catch(() => []);
266
+ for (const entry of entries) {
267
+ if (entry.isDirectory()) {
268
+ await visit(join(dir, entry.name));
269
+ }
270
+ }
271
+ };
272
+ await visit(root);
273
+ return watchers;
274
+ }
275
+ function shouldIgnoreWatchPath(path, outDir) {
276
+ const name = path.split(sep).at(-1);
277
+ return (name === ".git" ||
278
+ name === "node_modules" ||
279
+ isInsidePath(outDir, path));
280
+ }
281
+ function isInsidePath(root, path) {
282
+ const resolvedRoot = resolve(root);
283
+ const resolvedPath = resolve(path);
284
+ const pathRelativeToRoot = relative(resolvedRoot, resolvedPath);
285
+ return (pathRelativeToRoot === "" ||
286
+ (!pathRelativeToRoot.startsWith("..") && !pathRelativeToRoot.startsWith(sep)));
287
+ }
288
+ function contentTypeForPath(path) {
289
+ switch (extname(path).toLowerCase()) {
290
+ case ".html":
291
+ return "text/html; charset=utf-8";
292
+ case ".css":
293
+ return "text/css; charset=utf-8";
294
+ case ".js":
295
+ case ".mjs":
296
+ return "text/javascript; charset=utf-8";
297
+ case ".json":
298
+ return "application/json; charset=utf-8";
299
+ case ".xml":
300
+ return "application/xml; charset=utf-8";
301
+ case ".svg":
302
+ return "image/svg+xml";
303
+ case ".png":
304
+ return "image/png";
305
+ case ".jpg":
306
+ case ".jpeg":
307
+ return "image/jpeg";
308
+ case ".webp":
309
+ return "image/webp";
310
+ default:
311
+ return "application/octet-stream";
312
+ }
313
+ }
@@ -0,0 +1,134 @@
1
+ import { type BuildConfig, type BuildResult } from "./core.js";
2
+ import { type ImageConverter } from "./images.js";
3
+ import type { CompressionMetrics } from "./optimize.js";
4
+ import { type PaginatedPage } from "./pagination.js";
5
+ export type ExampleCounts = {
6
+ members: number;
7
+ sessions: number;
8
+ conversations: number;
9
+ bills: number;
10
+ };
11
+ export type ExampleBuildOptions = {
12
+ outDir: string;
13
+ counts?: Partial<ExampleCounts>;
14
+ trailingSlash?: BuildConfig["trailingSlash"];
15
+ recordHashCache?: ExampleRecordHashCache;
16
+ conversationIndexCache?: ExampleConversationIndexCache;
17
+ includePublicDataExports?: boolean;
18
+ sitemapMaxUrlsPerFile?: number;
19
+ mutateConversationId?: string;
20
+ partialConversationId?: string;
21
+ imageConverter?: ImageConverter;
22
+ loadImageConverter?: () => Promise<ImageConverter | undefined>;
23
+ };
24
+ export type ExampleBuildResult = BuildResult & {
25
+ imageOptimization: ImageOptimizationSummary;
26
+ dataNormalization: DataNormalizationSummary;
27
+ dataHashing: DataHashingSummary;
28
+ pagination: PaginationSummary;
29
+ };
30
+ export type ExampleBenchmark = {
31
+ pageCount: number;
32
+ fullBuildMs: number;
33
+ incrementalBuildMs: number;
34
+ fullBuildPagesWritten: number;
35
+ incrementalBuildPagesWritten: number;
36
+ changedBuildMs: number;
37
+ changedBuildPagesWritten: number;
38
+ normalizedDataSnapshots: number;
39
+ normalizedDataBytes: number;
40
+ fullBuildNormalizedDataWrites: number;
41
+ incrementalBuildNormalizedDataWrites: number;
42
+ changedBuildNormalizedDataWrites: number;
43
+ fullBuildRecordHashes: number;
44
+ incrementalBuildRecordHashes: number;
45
+ changedBuildRecordHashes: number;
46
+ fullBuildConversationIndexItemsMaterialized: number;
47
+ incrementalBuildConversationIndexItemsMaterialized: number;
48
+ changedBuildConversationIndexItemsMaterialized: number;
49
+ totalOutputBytes: number;
50
+ publicOutputBytes: number;
51
+ internalOutputBytes: number;
52
+ htmlOutputBytes: number;
53
+ cssOutputBytes: number;
54
+ jsOutputBytes: number;
55
+ imageOutputBytes: number;
56
+ dataOutputBytes: number;
57
+ averageHtmlBytes: number;
58
+ p95HtmlBytes: number;
59
+ compressedTransferBytes?: CompressionSummary;
60
+ imageOptimization: ImageOptimizationSummary;
61
+ largestHtml: Array<{
62
+ path: string;
63
+ bytes: number;
64
+ }>;
65
+ routeFamilies: Record<string, number>;
66
+ sitemapUrlCount: number;
67
+ sitemapFileCount: number;
68
+ sitemapBytes?: number;
69
+ largestSitemapBytes?: number;
70
+ missingLinkCount: number;
71
+ validationIssueCount: number;
72
+ largestDependencyFanout: number;
73
+ publicDataRecordCounts?: ExampleDataRecordCounts;
74
+ };
75
+ export type ExampleOutputSizes = {
76
+ totalOutputBytes: number;
77
+ publicOutputBytes: number;
78
+ internalOutputBytes: number;
79
+ htmlOutputBytes: number;
80
+ cssOutputBytes: number;
81
+ jsOutputBytes: number;
82
+ imageOutputBytes: number;
83
+ dataOutputBytes: number;
84
+ averageHtmlBytes: number;
85
+ p95HtmlBytes: number;
86
+ };
87
+ type CompressionSummary = Omit<CompressionMetrics, "files">;
88
+ type ImageOptimizationSummary = {
89
+ generated: number;
90
+ skipped: number;
91
+ outputBytes: number;
92
+ };
93
+ type DataNormalizationSummary = {
94
+ snapshots: number;
95
+ written: number;
96
+ bytes: number;
97
+ };
98
+ type DataHashingSummary = {
99
+ recordsHashed: number;
100
+ };
101
+ export type ExampleDataRecordCounts = {
102
+ members: number;
103
+ sessions: number;
104
+ conversations: number;
105
+ bills: number;
106
+ categories: number;
107
+ votes: number;
108
+ yearlyAggregates: number;
109
+ };
110
+ export type ExampleRecordHashCache = Map<string, RecordHashCacheEntry>;
111
+ type RecordHashCacheEntry = {
112
+ fingerprint: string;
113
+ hash: string;
114
+ };
115
+ type PaginationSummary = {
116
+ conversationIndexItemsMaterialized: number;
117
+ };
118
+ type ExampleConversationIndexCache = Map<string, PaginatedPage<Conversation>[]>;
119
+ type Conversation = {
120
+ id: string;
121
+ slug: string;
122
+ memberId: string;
123
+ sessionId: string;
124
+ categoryId: string;
125
+ title: string;
126
+ body: string;
127
+ };
128
+ export declare function buildExampleSite(options: ExampleBuildOptions): Promise<ExampleBuildResult>;
129
+ export declare function benchmarkExampleSite(options: ExampleBuildOptions): Promise<ExampleBenchmark>;
130
+ export declare function measureExampleOutputSizes(outDir: string, options?: {
131
+ includePublicDataExports?: boolean;
132
+ knownHtmlOutputBytes?: ReadonlyMap<string, number>;
133
+ }): Promise<ExampleOutputSizes>;
134
+ export {};