@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.
- package/CHANGELOG.md +23 -0
- package/LICENSE +21 -0
- package/README.md +768 -0
- package/dist/agent-skill.d.ts +1 -0
- package/dist/agent-skill.js +140 -0
- package/dist/assets.d.ts +125 -0
- package/dist/assets.js +341 -0
- package/dist/cli.d.ts +236 -0
- package/dist/cli.js +3077 -0
- package/dist/core.d.ts +473 -0
- package/dist/core.js +2897 -0
- package/dist/data.d.ts +121 -0
- package/dist/data.js +358 -0
- package/dist/deploy.d.ts +34 -0
- package/dist/deploy.js +203 -0
- package/dist/dev.d.ts +36 -0
- package/dist/dev.js +313 -0
- package/dist/example.d.ts +134 -0
- package/dist/example.js +1272 -0
- package/dist/fragment/client.d.ts +13 -0
- package/dist/fragment/client.js +150 -0
- package/dist/fragment.d.ts +15 -0
- package/dist/fragment.js +27 -0
- package/dist/html.d.ts +57 -0
- package/dist/html.js +208 -0
- package/dist/images.d.ts +95 -0
- package/dist/images.js +292 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/migration.d.ts +157 -0
- package/dist/migration.js +983 -0
- package/dist/optimize.d.ts +15 -0
- package/dist/optimize.js +215 -0
- package/dist/pagination.d.ts +26 -0
- package/dist/pagination.js +62 -0
- package/dist/paths.d.ts +1 -0
- package/dist/paths.js +10 -0
- package/dist/search.d.ts +32 -0
- package/dist/search.js +55 -0
- package/dist/testing.d.ts +66 -0
- package/dist/testing.js +97 -0
- package/dist/validate.d.ts +2 -0
- package/dist/validate.js +1 -0
- package/jsx-loader.mjs +52 -0
- package/jsx-register.mjs +7 -0
- package/package.json +135 -0
- package/tsconfig.base.json +16 -0
package/README.md
ADDED
|
@@ -0,0 +1,768 @@
|
|
|
1
|
+
# StoneAge
|
|
2
|
+
|
|
3
|
+
StoneAge is a data-site static site generator for publishing large, structured
|
|
4
|
+
datasets as fast, plain HTML.
|
|
5
|
+
|
|
6
|
+
It is not intended to be a general-purpose web application framework. The core
|
|
7
|
+
use case is a site with tens of thousands of generated pages, normalized data
|
|
8
|
+
exports, stable URLs, rich metadata, and a build pipeline that can regenerate
|
|
9
|
+
only the pages affected by changed inputs.
|
|
10
|
+
|
|
11
|
+
## Start Here
|
|
12
|
+
|
|
13
|
+
- Build a new site: start with [Getting Started](docs/getting-started.md), then
|
|
14
|
+
use [Site Build Guide](docs/site-build.md) for route families, artifacts,
|
|
15
|
+
metadata, assets, validation, and publishing.
|
|
16
|
+
- Define reusable templates: read [Components](docs/components.md) for TSX,
|
|
17
|
+
`html()`, `renderToString()`, static components, and opt-in islands.
|
|
18
|
+
- Migrate an existing SvelteKit static site: run the audit command and follow
|
|
19
|
+
[Migration Guide](docs/migration.md). StoneAge migration is not an adapter;
|
|
20
|
+
it converts routes, data, endpoints, and layouts into explicit static build
|
|
21
|
+
inputs.
|
|
22
|
+
- Understand the data pipeline: read [Data Flow](docs/data-flow.md) for
|
|
23
|
+
normalized data, public artifacts, view models, and dependency tracking.
|
|
24
|
+
|
|
25
|
+
## Goals
|
|
26
|
+
|
|
27
|
+
- Generate plain HTML as the default output.
|
|
28
|
+
- Avoid required runtime hydration payloads for page rendering.
|
|
29
|
+
- Keep data normalization separate from HTML rendering.
|
|
30
|
+
- Generate JSON, XML, CSV, and other non-HTML artifacts through explicit
|
|
31
|
+
artifact routes that share dependency caching without becoming page payloads.
|
|
32
|
+
- Regenerate pages incrementally from input hashes and dependency metadata.
|
|
33
|
+
- Remove stale page and artifact outputs from previous full builds when they
|
|
34
|
+
disappear from the current manifest.
|
|
35
|
+
- Provide typed route definitions for large page families.
|
|
36
|
+
- Reject duplicate page paths, duplicate artifact paths, and page/artifact
|
|
37
|
+
output file collisions before rendering starts.
|
|
38
|
+
- Split large listing pages with pagination helpers and stable first-page URLs.
|
|
39
|
+
- Configure trailing slash behavior for canonical URLs, sitemap entries, link
|
|
40
|
+
validation, and output file paths.
|
|
41
|
+
- Split large sitemaps into a sitemap index plus `sitemap-N.xml` URL set files
|
|
42
|
+
when a site wants bounded sitemap file sizes.
|
|
43
|
+
- Add page-level prefetch links for likely next navigations without requiring a
|
|
44
|
+
Service Worker. Sites can also opt into a tiny Service Worker prefetch layer
|
|
45
|
+
that only warms URLs declared by the current page, and can disable duplicate
|
|
46
|
+
native prefetch links when the Service Worker is the intended strategy.
|
|
47
|
+
- Generate metadata such as titles, descriptions, canonical URLs, language,
|
|
48
|
+
favicon links, Open Graph tags, Twitter card tags, noindex robots tags,
|
|
49
|
+
structured custom head tags, and sitemaps during HTML generation. Open Graph
|
|
50
|
+
output can be disabled at the site level and re-enabled for high-value pages.
|
|
51
|
+
- Write publishing manifests such as `robots.txt` and generic
|
|
52
|
+
`redirects.json` files from the same build configuration.
|
|
53
|
+
- Keep assets small and explicit: shared CSS, page-type CSS when needed, and
|
|
54
|
+
hand-authored JavaScript only for interactive islands.
|
|
55
|
+
- Optimize local raster images into responsive variants when optional `sharp`
|
|
56
|
+
support is available, while keeping builds functional when it is not.
|
|
57
|
+
- Allow component-style HTML composition, including optional TSX components,
|
|
58
|
+
without adding a client hydration runtime.
|
|
59
|
+
- Ship validation commands for generated page counts, missing links, exports,
|
|
60
|
+
public JSON/CSV syntax, sitemap integrity, missing asset references, and large
|
|
61
|
+
HTML outputs.
|
|
62
|
+
|
|
63
|
+
## Non-Goals
|
|
64
|
+
|
|
65
|
+
- StoneAge is not a React, Vue, or Svelte application framework.
|
|
66
|
+
- StoneAge does not require client-side hydration to display generated pages.
|
|
67
|
+
- StoneAge does not mix public data exports with HTML-only view models.
|
|
68
|
+
- StoneAge does not bundle source or normalized data into the published site by
|
|
69
|
+
default.
|
|
70
|
+
- StoneAge does not assume every generated page should be rebuilt on every run.
|
|
71
|
+
|
|
72
|
+
## Architecture
|
|
73
|
+
|
|
74
|
+
StoneAge is a TypeScript framework with Vite integration where it is useful for
|
|
75
|
+
asset bundling and development ergonomics.
|
|
76
|
+
|
|
77
|
+
The build pipeline is organized around three separate stages:
|
|
78
|
+
|
|
79
|
+
1. Normalize source data into internal JSON, CSV, or API snapshots.
|
|
80
|
+
2. Derive public data artifacts and HTML view models from those normalized
|
|
81
|
+
snapshots.
|
|
82
|
+
3. Render HTML pages, metadata, sitemaps, assets, and validation reports from
|
|
83
|
+
the view models.
|
|
84
|
+
|
|
85
|
+
The incremental build cache records source hashes, page dependencies, output
|
|
86
|
+
paths, generated links, and metadata. A changed member record, session record, or
|
|
87
|
+
conversation record only invalidates pages that depend on that input.
|
|
88
|
+
View models with record-level dependencies avoid inheriting a broad public data
|
|
89
|
+
artifact hash, while view models without record dependencies fall back to the
|
|
90
|
+
artifact hash for correctness.
|
|
91
|
+
StoneAge also fingerprints route family metadata defaults, page metadata,
|
|
92
|
+
canonical URLs, site-level head settings, title templates, structured custom
|
|
93
|
+
head tags, stylesheet references, and page-local asset/island declarations so
|
|
94
|
+
metadata-only changes do not leave stale HTML head output behind.
|
|
95
|
+
For very large sites that already compute a complete input hash, pass
|
|
96
|
+
`buildFingerprint` to skip route enumeration entirely when the hash matches the
|
|
97
|
+
previous build report. Pass `renderFingerprint` for source templates, CSS
|
|
98
|
+
inputs, and artifact renderers; when it changes, StoneAge invalidates cached
|
|
99
|
+
page and artifact outputs even if their data dependencies are unchanged.
|
|
100
|
+
Full builds remove manifest-managed page and artifact outputs that no longer
|
|
101
|
+
exist in the current route plan. Partial builds preserve omitted outputs because
|
|
102
|
+
they intentionally describe only a subset of the site.
|
|
103
|
+
|
|
104
|
+
## Example Route Families
|
|
105
|
+
|
|
106
|
+
StoneAge supports route families such as:
|
|
107
|
+
|
|
108
|
+
```text
|
|
109
|
+
/members/:id/:year/
|
|
110
|
+
/conversations/:id/
|
|
111
|
+
/sessions/:id/
|
|
112
|
+
/bills/:id/
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Route implementations should be typed generation functions, not ad hoc string
|
|
116
|
+
concatenation spread through project code.
|
|
117
|
+
|
|
118
|
+
Use typed route helpers to bind a pattern and its required params in one place:
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
import {
|
|
122
|
+
defineRouteFamily,
|
|
123
|
+
pathForRedirect,
|
|
124
|
+
patternParamMatcher,
|
|
125
|
+
} from "@t09tanaka/stoneage";
|
|
126
|
+
|
|
127
|
+
const members = defineRouteFamily({
|
|
128
|
+
name: "members",
|
|
129
|
+
pattern: "/members/:id/:year/",
|
|
130
|
+
paramMatchers: {
|
|
131
|
+
year: patternParamMatcher(/^\d{4}$/),
|
|
132
|
+
},
|
|
133
|
+
metadata: {
|
|
134
|
+
description: "A generated member page",
|
|
135
|
+
ogType: "profile",
|
|
136
|
+
},
|
|
137
|
+
entries: () => [
|
|
138
|
+
{
|
|
139
|
+
params: { id: "m1", year: 2026 },
|
|
140
|
+
dependencies: ["member:member-001"],
|
|
141
|
+
metadata: {
|
|
142
|
+
title: "Member profile",
|
|
143
|
+
},
|
|
144
|
+
render: () =>
|
|
145
|
+
`<main><a href="${members.path({ id: "m1", year: 2026 })}">Profile</a></main>`,
|
|
146
|
+
},
|
|
147
|
+
],
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
const legacyMemberUrl = pathForRedirect("/members/:id/", { id: "m1" });
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Project Status
|
|
154
|
+
|
|
155
|
+
This repository contains the release-candidate implementation of the data-site SSG core:
|
|
156
|
+
typed route and artifact families, incremental dependency caching, metadata and
|
|
157
|
+
sitemap generation, client asset and image helpers, validation commands,
|
|
158
|
+
deployment manifest generation, a large benchmark example, and package audit
|
|
159
|
+
checks. The migration audit also covers SvelteKit route entries, layout chains,
|
|
160
|
+
page actions, static endpoint methods, and reset route files so a large static
|
|
161
|
+
site can be moved without introducing an adapter layer.
|
|
162
|
+
|
|
163
|
+
## Example Site And Performance Work
|
|
164
|
+
|
|
165
|
+
The example site is part of the product, not a throwaway demo. It contains
|
|
166
|
+
enough generated data to expose real scaling problems: many records, many route
|
|
167
|
+
families, large index pages, metadata-heavy detail pages, exports, and sitemap
|
|
168
|
+
generation.
|
|
169
|
+
|
|
170
|
+
The framework measures the example site quantitatively and optimizes from
|
|
171
|
+
those measurements. Useful metrics include build time, incremental build time,
|
|
172
|
+
generated page count, total output size, largest HTML files, CSS and JavaScript
|
|
173
|
+
payloads, image output size, image optimization results, route-family write
|
|
174
|
+
counts, link-check time, sitemap size, and Lighthouse results for selected
|
|
175
|
+
pages.
|
|
176
|
+
|
|
177
|
+
The large example benchmark disables Open Graph tags by default to keep repeated
|
|
178
|
+
detail pages small while preserving title, description, canonical, and sitemap
|
|
179
|
+
metadata.
|
|
180
|
+
|
|
181
|
+
The example keeps internal record IDs separate from public URL slugs. Dependency
|
|
182
|
+
tracking and optional exports keep stable source IDs, while generated HTML,
|
|
183
|
+
links, and sitemap entries use shorter stable route slugs to reduce repeated
|
|
184
|
+
payload.
|
|
185
|
+
|
|
186
|
+
Tailwind CSS may be used as the default styling pipeline, but generated CSS must
|
|
187
|
+
remain small and predictable. StoneAge can build Tailwind output from explicit
|
|
188
|
+
route/component class candidates, which avoids scanning generated HTML at large
|
|
189
|
+
page counts. Existing Vite, PostCSS, or Tailwind pipelines can also stay outside
|
|
190
|
+
the HTML generator: build CSS as a normal asset, read the Vite manifest when
|
|
191
|
+
needed, and attach the resulting stylesheet paths through page metadata or the
|
|
192
|
+
client asset registry.
|
|
193
|
+
|
|
194
|
+
Client JavaScript is opt-in per page. Declare small assets or islands once, then
|
|
195
|
+
attach their ids from page metadata:
|
|
196
|
+
|
|
197
|
+
```ts
|
|
198
|
+
import { defineClientAssets, island, stylesheet } from "@t09tanaka/stoneage/assets";
|
|
199
|
+
import { html, islandMount, renderToString } from "@t09tanaka/stoneage/html";
|
|
200
|
+
|
|
201
|
+
const client = defineClientAssets({
|
|
202
|
+
search: [
|
|
203
|
+
stylesheet("/assets/search.css", { source: "asset-build/search.css" }),
|
|
204
|
+
island("/assets/search.js", {
|
|
205
|
+
source: "asset-build/search.js",
|
|
206
|
+
attributes: { "data-island": "search" },
|
|
207
|
+
}),
|
|
208
|
+
],
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
await buildSite({
|
|
212
|
+
outDir: "dist",
|
|
213
|
+
site,
|
|
214
|
+
publishing: { headers: true },
|
|
215
|
+
assets: {
|
|
216
|
+
client,
|
|
217
|
+
},
|
|
218
|
+
routes: [
|
|
219
|
+
{
|
|
220
|
+
name: "search",
|
|
221
|
+
pattern: "/search/",
|
|
222
|
+
entries: async () => [
|
|
223
|
+
{
|
|
224
|
+
params: {},
|
|
225
|
+
metadata: {
|
|
226
|
+
title: "Search",
|
|
227
|
+
description: "Search the archive",
|
|
228
|
+
islands: ["search"],
|
|
229
|
+
},
|
|
230
|
+
render: () =>
|
|
231
|
+
renderToString(
|
|
232
|
+
html(
|
|
233
|
+
"main",
|
|
234
|
+
null,
|
|
235
|
+
islandMount("search", {
|
|
236
|
+
key: "site-search",
|
|
237
|
+
props: { index: "/search.json" },
|
|
238
|
+
}),
|
|
239
|
+
),
|
|
240
|
+
),
|
|
241
|
+
},
|
|
242
|
+
],
|
|
243
|
+
},
|
|
244
|
+
],
|
|
245
|
+
});
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Assets declared with a `source` file are copied with a content hash in the
|
|
249
|
+
filename, their HTML references are rewritten, and `publishing.headers` adds an
|
|
250
|
+
immutable cache-control entry. Plain string paths and `assets.public` entries
|
|
251
|
+
continue to use fixed filenames.
|
|
252
|
+
|
|
253
|
+
For Google Fonts CSS2 subsets, use `googleFontsStylesheet("/assets/fonts.css",
|
|
254
|
+
{ families: ["Zen Maru Gothic:wght@700;900", "Outfit:wght@700;900"], text:
|
|
255
|
+
subsetText })`. StoneAge fetches the CSS and WOFF2 files at build time, rewrites
|
|
256
|
+
`@font-face` URLs to local content-hashed assets, and emits immutable cache
|
|
257
|
+
headers for the generated CSS and font files.
|
|
258
|
+
|
|
259
|
+
For Vite builds, `loadViteBuildAssetsFromManifest("dist/.vite/manifest.json", {
|
|
260
|
+
sourceDir: "dist", base: "/", entriesOnly: true })` reads a Vite manifest and
|
|
261
|
+
returns both `assets.client` and `assets.public` inputs. That lets a separate
|
|
262
|
+
Vite, PostCSS, or Tailwind pipeline produce hashed CSS, JS, and imported static
|
|
263
|
+
assets while StoneAge handles page injection and output-directory sync. Imported
|
|
264
|
+
chunk CSS is emitted before entry CSS, duplicate stylesheet paths are removed,
|
|
265
|
+
and `{ entriesOnly: true }` can keep non-entry chunks out of the client asset
|
|
266
|
+
registry while still copying emitted public files. Use `assetsFromViteManifest()`
|
|
267
|
+
or `publicAssetsFromViteManifest()` when a build script already has the manifest
|
|
268
|
+
object in memory and only needs one side of the mapping.
|
|
269
|
+
|
|
270
|
+
Use `copyPublicAssets()` when those external pipelines write assets outside the
|
|
271
|
+
StoneAge output directory. It copies files into stable public paths, rejects
|
|
272
|
+
paths that would escape the output directory or collide with another configured
|
|
273
|
+
asset, and skips unchanged files so asset sync does not force unrelated page
|
|
274
|
+
rewrites. Full builds remove managed public assets that were listed in the
|
|
275
|
+
previous report but are no longer configured; partial builds preserve them.
|
|
276
|
+
Pages still render as plain HTML and no hydration payload is generated by
|
|
277
|
+
default. `islandMount()` emits a root `data-island` element and only writes an
|
|
278
|
+
adjacent JSON props script when a page explicitly passes props for that island.
|
|
279
|
+
|
|
280
|
+
Deferred fragments can move supplemental HTML out of the initial page while
|
|
281
|
+
keeping the output static. Use `defineFragmentFamily()` for fragment outputs and
|
|
282
|
+
`deferredFragment()` from `@t09tanaka/stoneage/fragment` for the placeholder.
|
|
283
|
+
Fragments are emitted under `/_fragments/` as `.html` artifacts with
|
|
284
|
+
`text/html; charset=utf-8` and `x-robots-tag: noindex`; they are not pages, do
|
|
285
|
+
not receive metadata or canonical URLs, and are not added to sitemaps. Keep a
|
|
286
|
+
custom fallback link when you want the no-JavaScript path to use a canonical
|
|
287
|
+
page instead of the default fragment self-link. Pass `fallback: null` only when
|
|
288
|
+
the deferred content can be omitted without JavaScript. When fragments are
|
|
289
|
+
configured, StoneAge emits and wires the built-in client controller
|
|
290
|
+
automatically; pass `fragments: { families, client: false }` to keep manual
|
|
291
|
+
wiring. The controller supports `visible`, `details-open`, `idle`, and `manual`
|
|
292
|
+
triggers.
|
|
293
|
+
|
|
294
|
+
The package entrypoints are explicit. Import core build APIs from
|
|
295
|
+
`@t09tanaka/stoneage`, client asset helpers from `@t09tanaka/stoneage/assets`,
|
|
296
|
+
data-flow helpers from `@t09tanaka/stoneage/data`, deploy artifact helpers from
|
|
297
|
+
`@t09tanaka/stoneage/deploy`, TSX helpers from `@t09tanaka/stoneage/html`,
|
|
298
|
+
deferred fragment helpers from `@t09tanaka/stoneage/fragment`,
|
|
299
|
+
image helpers from `@t09tanaka/stoneage/images`, SvelteKit migration audit
|
|
300
|
+
helpers from `@t09tanaka/stoneage/migration`, optimization helpers from
|
|
301
|
+
`@t09tanaka/stoneage/optimize`, pagination helpers from
|
|
302
|
+
`@t09tanaka/stoneage/pagination`, and optional search-index helpers from
|
|
303
|
+
`@t09tanaka/stoneage/search`.
|
|
304
|
+
|
|
305
|
+
Data is injected at build time to prebuild the static site. Public JSON or CSV
|
|
306
|
+
exports are optional publishing features, not required page payloads.
|
|
307
|
+
Normalized snapshots can live outside `outDir` or under an internal
|
|
308
|
+
`.stoneage/normalized` path; they are not published unless you expose them
|
|
309
|
+
through artifact routes.
|
|
310
|
+
|
|
311
|
+
Large data producers can pass a complete `contentFingerprint` plus
|
|
312
|
+
`cacheManifestPath` to `writeNormalizedJsonData()` or
|
|
313
|
+
`writeNormalizedCsvData()`. When the fingerprint and manifest entry match an
|
|
314
|
+
existing snapshot, StoneAge reuses the snapshot metadata without rendering the
|
|
315
|
+
JSON/CSV string or reading the existing snapshot body:
|
|
316
|
+
|
|
317
|
+
```ts
|
|
318
|
+
await writeNormalizedJsonData({
|
|
319
|
+
key: "public:members",
|
|
320
|
+
path: ".stoneage/normalized/members.json",
|
|
321
|
+
data: members,
|
|
322
|
+
contentFingerprint: { hash: membersContentHash },
|
|
323
|
+
cacheManifestPath: ".stoneage/normalized-manifest.json",
|
|
324
|
+
});
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
The fingerprint must cover the full rendered snapshot content. This fast path
|
|
328
|
+
is for pipelines that already hash normalized records or shards before HTML
|
|
329
|
+
rendering.
|
|
330
|
+
|
|
331
|
+
Non-HTML outputs use artifact routes. They are cached and reported alongside
|
|
332
|
+
HTML pages, but they do not receive HTML metadata and are not added to the
|
|
333
|
+
sitemap:
|
|
334
|
+
|
|
335
|
+
```ts
|
|
336
|
+
import {
|
|
337
|
+
artifactEntriesFromPublicData,
|
|
338
|
+
definePublicDataJsonArtifacts,
|
|
339
|
+
renderCsvArtifact,
|
|
340
|
+
writeNormalizedJsonData,
|
|
341
|
+
} from "@t09tanaka/stoneage/data";
|
|
342
|
+
|
|
343
|
+
const publicMembers = await writeNormalizedJsonData({
|
|
344
|
+
key: "public:members",
|
|
345
|
+
path: "data/normalized/members.json",
|
|
346
|
+
data: members,
|
|
347
|
+
sourceDependencies: [{ key: "source:members", file: "data/source/members.csv" }],
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
await buildSite({
|
|
351
|
+
outDir: "dist",
|
|
352
|
+
site,
|
|
353
|
+
routes,
|
|
354
|
+
artifacts: [
|
|
355
|
+
{
|
|
356
|
+
name: "csvExports",
|
|
357
|
+
pattern: "/exports/:name.csv",
|
|
358
|
+
entries: () =>
|
|
359
|
+
artifactEntriesFromPublicData([publicMembers], {
|
|
360
|
+
params: () => ({ name: "members" }),
|
|
361
|
+
render: (artifact) =>
|
|
362
|
+
renderCsvArtifact(artifact.data, [
|
|
363
|
+
{ header: "id", value: (member) => member.id },
|
|
364
|
+
{ header: "name", value: (member) => member.name },
|
|
365
|
+
]),
|
|
366
|
+
}),
|
|
367
|
+
},
|
|
368
|
+
definePublicDataJsonArtifacts([publicMembers], {
|
|
369
|
+
path: (artifact) => `/data/${artifact.key.replace("public:", "")}.json`,
|
|
370
|
+
}),
|
|
371
|
+
],
|
|
372
|
+
});
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
`artifactEntriesFromPublicData()` can turn normalized public data artifacts into
|
|
376
|
+
artifact route entries while preserving their source dependencies. This is the
|
|
377
|
+
intended migration path for endpoint-style outputs such as `site-data.json`,
|
|
378
|
+
`search-index.json`, public CSV exports, and other SvelteKit `+server.ts`
|
|
379
|
+
equivalents. `renderJsonArtifact()` and `renderCsvArtifact()` provide small,
|
|
380
|
+
framework-independent renderers for those public exports. When a migrated
|
|
381
|
+
SvelteKit `+server.ts` endpoint needs response-style metadata, return
|
|
382
|
+
`jsonArtifact()`, `csvArtifact()`, `xmlArtifact()`, `textArtifact()`, or
|
|
383
|
+
`artifactResponse()` from `@t09tanaka/stoneage`; StoneAge writes the static body and records
|
|
384
|
+
`contentType`, `status`, and `headers` in the generated manifest.
|
|
385
|
+
When `publishing` is enabled, artifact `contentType` and headers are also
|
|
386
|
+
collected into a host-neutral `headers.json` manifest unless
|
|
387
|
+
`publishing.headers` is set to `false`. Deployment tooling can translate that
|
|
388
|
+
manifest to provider-specific header rules without mixing endpoint metadata into
|
|
389
|
+
page renderers.
|
|
390
|
+
`definePublicDataJsonArtifacts()` can publish split JSON data files plus a
|
|
391
|
+
small `site-data.json` manifest that lists their public paths. The manifest
|
|
392
|
+
depends on the list of exported files, while each data file keeps its own source
|
|
393
|
+
and public-data dependencies.
|
|
394
|
+
|
|
395
|
+
Optimized image variants can be consumed as structured attributes or rendered
|
|
396
|
+
directly as compact HTML:
|
|
397
|
+
|
|
398
|
+
```ts
|
|
399
|
+
import { optimizeImages, pictureHtmlFor } from "@t09tanaka/stoneage/images";
|
|
400
|
+
|
|
401
|
+
const result = await optimizeImages({
|
|
402
|
+
outDir: "dist",
|
|
403
|
+
images: [{ source: "assets/member.jpg", publicPath: "/images/member.jpg" }],
|
|
404
|
+
widths: [320, 640, 960],
|
|
405
|
+
formats: ["avif", "webp", "jpeg"],
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
const picture = pictureHtmlFor(result.generated, {
|
|
409
|
+
src: "/images/member.jpg",
|
|
410
|
+
width: 960,
|
|
411
|
+
height: 640,
|
|
412
|
+
alt: "Member portrait",
|
|
413
|
+
sizes: "(min-width: 48rem) 33vw, 100vw",
|
|
414
|
+
loading: "lazy",
|
|
415
|
+
decoding: "async",
|
|
416
|
+
fallbackFormat: "jpeg",
|
|
417
|
+
});
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
Paginated index routes can keep navigation data outside templates:
|
|
421
|
+
|
|
422
|
+
```ts
|
|
423
|
+
import { paginationLinks } from "@t09tanaka/stoneage/pagination";
|
|
424
|
+
|
|
425
|
+
const nav = paginationLinks(page.page, conversations.length, {
|
|
426
|
+
pageSize: 50,
|
|
427
|
+
pathForPage: (pageNumber) =>
|
|
428
|
+
pageNumber === 1 ? "/conversations/" : `/conversations/page/${pageNumber}/`,
|
|
429
|
+
});
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
The helper returns `previous`, `next`, and a stable page list. It does not render
|
|
433
|
+
HTML, so components can decide whether to expose links, metadata head tags, or
|
|
434
|
+
prefetch hints.
|
|
435
|
+
|
|
436
|
+
Search indexes are just optional artifacts. `defineSearchIndexArtifact()` can
|
|
437
|
+
publish a stable `search-index.json` for client-side search without making that
|
|
438
|
+
JSON required for ordinary page rendering:
|
|
439
|
+
|
|
440
|
+
```ts
|
|
441
|
+
import { defineSearchIndexArtifact } from "@t09tanaka/stoneage/search";
|
|
442
|
+
|
|
443
|
+
await buildSite({
|
|
444
|
+
outDir: "dist",
|
|
445
|
+
site,
|
|
446
|
+
routes,
|
|
447
|
+
artifacts: [
|
|
448
|
+
defineSearchIndexArtifact({
|
|
449
|
+
records: members,
|
|
450
|
+
dependencies: members.map((member) => ({
|
|
451
|
+
key: `member:${member.id}`,
|
|
452
|
+
value: member,
|
|
453
|
+
})),
|
|
454
|
+
document: (member) => ({
|
|
455
|
+
id: member.id,
|
|
456
|
+
title: member.name,
|
|
457
|
+
url: `/members/${member.slug}/${member.year}/`,
|
|
458
|
+
tags: ["member", member.district],
|
|
459
|
+
}),
|
|
460
|
+
}),
|
|
461
|
+
],
|
|
462
|
+
});
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
Route URLs use trailing slashes by default and write directory-style output such
|
|
466
|
+
as `members/m1/2026/index.html`. Sites that need extension-style output can set
|
|
467
|
+
`trailingSlash: "never"`:
|
|
468
|
+
|
|
469
|
+
```ts
|
|
470
|
+
await buildSite({
|
|
471
|
+
outDir: "dist",
|
|
472
|
+
trailingSlash: "never",
|
|
473
|
+
site,
|
|
474
|
+
routes,
|
|
475
|
+
});
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
This normalizes generated paths, canonical URLs, sitemap entries, internal link
|
|
479
|
+
validation, and page-local prefetch hints. Route renderers still own their HTML
|
|
480
|
+
body links, so projects should emit body hrefs that match their chosen URL
|
|
481
|
+
style.
|
|
482
|
+
|
|
483
|
+
For large route families, entries may declare their internal body links with
|
|
484
|
+
`links`. When present, StoneAge records those normalized links directly instead
|
|
485
|
+
of scanning the rendered HTML for anchors:
|
|
486
|
+
|
|
487
|
+
```ts
|
|
488
|
+
{
|
|
489
|
+
params: { id: member.slug },
|
|
490
|
+
links: conversations.map((conversation) => `/conversations/${conversation.slug}/`),
|
|
491
|
+
render: () => renderMember(member, conversations),
|
|
492
|
+
}
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
Large sites can split sitemap output while keeping `sitemap.xml` as the public
|
|
496
|
+
entry point:
|
|
497
|
+
|
|
498
|
+
```ts
|
|
499
|
+
await buildSite({
|
|
500
|
+
outDir: "dist",
|
|
501
|
+
site,
|
|
502
|
+
sitemap: {
|
|
503
|
+
maxUrlsPerFile: 10_000,
|
|
504
|
+
},
|
|
505
|
+
routes,
|
|
506
|
+
});
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
When the indexable page count exceeds the limit, StoneAge writes `sitemap.xml`
|
|
510
|
+
as a sitemap index and writes child URL sets such as `sitemap-1.xml` and
|
|
511
|
+
`sitemap-2.xml`. `validate` follows the index and compares the combined child
|
|
512
|
+
URLs against the generated manifest. It also reports sitemap index entries that
|
|
513
|
+
point at missing child sitemap files.
|
|
514
|
+
|
|
515
|
+
Useful commands:
|
|
516
|
+
|
|
517
|
+
```sh
|
|
518
|
+
npm run benchmark
|
|
519
|
+
npm run benchmark -- --trailing-slash never
|
|
520
|
+
npm run benchmark -- --public-data-exports
|
|
521
|
+
npm run benchmark:large
|
|
522
|
+
npm run benchmark:large:compare
|
|
523
|
+
npm run check:publish
|
|
524
|
+
npm run optimize
|
|
525
|
+
npm run deploy -- --provider netlify
|
|
526
|
+
npm run dev -- --out-dir example/dist --watch src --watch docs --build-command "npm run benchmark"
|
|
527
|
+
npm run audit:sveltekit -- --routes-dir ../example-sveltekit/src/routes --params-dir ../example-sveltekit/src/params --json --report migration/sveltekit-audit.json
|
|
528
|
+
npm run audit:sveltekit -- --routes-dir ../example-sveltekit/src/routes --params-dir ../example-sveltekit/src/params --migration-plan --json --report migration/sveltekit-plan.json
|
|
529
|
+
npm run audit:sveltekit -- --routes-dir ../example-sveltekit/src/routes --params-dir ../example-sveltekit/src/params --emit-stubs migration/stubs
|
|
530
|
+
npm run inspect -- --dependency member:member-001
|
|
531
|
+
npm run --silent inspect -- --dependency member:member-001 --json
|
|
532
|
+
npm run validate
|
|
533
|
+
npm run validate -- --max-html-bytes 25000 --max-css-bytes 50000 --max-js-bytes 10000 --max-public-file-bytes 2000000 --max-total-public-bytes 50000000 --max-image-bytes 10000000 --max-dependency-fanout 500
|
|
534
|
+
npm run validate -- --validation-config validation.json
|
|
535
|
+
npm run validate -- --write-validation-config validation.json
|
|
536
|
+
npm run --silent validate -- --json --report example/dist/.stoneage/validation-summary.json
|
|
537
|
+
npm run check:package
|
|
538
|
+
npx @t09tanaka/stoneage --help
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
`inspect --dependency <key>` reads the generated manifest and lists the pages and
|
|
542
|
+
artifacts that currently depend on that key. Use `--json` for scripts that plan
|
|
543
|
+
partial rebuilds or audit the dependency graph.
|
|
544
|
+
|
|
545
|
+
`dev` serves the generated output directory and injects a small Server-Sent
|
|
546
|
+
Events reload client into HTML responses. Use `--build-command <command>` with
|
|
547
|
+
one or more `--watch <path>` options to rebuild on source changes; browsers
|
|
548
|
+
reload only after the build command succeeds. StoneAge ignores the output
|
|
549
|
+
directory, `.git`, and `node_modules` while watching to avoid rebuild loops.
|
|
550
|
+
|
|
551
|
+
`benchmark --compare-to <file>` compares the current `.stoneage/benchmark.json`
|
|
552
|
+
metrics with a previous benchmark report and fails the command when size metrics
|
|
553
|
+
grow beyond `--max-size-regression-percent` (default `0`). The comparison is
|
|
554
|
+
limited to deterministic payload metrics such as total/public output bytes,
|
|
555
|
+
HTML/CSS/JS/image/data bytes, HTML averages, and p95 HTML size. Build times are
|
|
556
|
+
reported but not used as pass/fail gates because they are environment-dependent.
|
|
557
|
+
Use `--comparison-report <file>` when CI
|
|
558
|
+
should persist the pass/fail result, compared metric count, baseline path,
|
|
559
|
+
current report path, and individual regressions as JSON.
|
|
560
|
+
Use `npm run benchmark:large:compare` for CI baseline comparisons against the
|
|
561
|
+
committed `baselines/benchmark-large.json` report. Use `npm run check:publish`
|
|
562
|
+
when a publish gate should run that benchmark comparison and validate the output
|
|
563
|
+
with `validation.json`. Runtime compression is expected to be handled by the
|
|
564
|
+
serving layer, such as CloudFront, so routine benchmark and publish checks do not
|
|
565
|
+
precompress or compare Brotli/Gzip transfer estimates.
|
|
566
|
+
|
|
567
|
+
`validate` reads the generated `.stoneage/report.json`, split data-flow reports
|
|
568
|
+
(`.stoneage/data-flow-summary.json` and
|
|
569
|
+
`.stoneage/data-flow-dependencies.json`), and sitemap output, then reports page counts,
|
|
570
|
+
artifact counts, managed public asset counts and copy/skip counts, missing links, required metadata issues, duplicate or missing
|
|
571
|
+
canonical URLs, sitemap drift, dependency fanout, route/artifact data-flow
|
|
572
|
+
summaries, public JSON/CSV exports, sitemap URL count, sitemap file count,
|
|
573
|
+
duplicate sitemap URLs, invalid sitemap URL values, missing child sitemap files,
|
|
574
|
+
JSON parse failures, basic CSV structural failures,
|
|
575
|
+
missing title/description/canonical tags in generated HTML, generated canonical
|
|
576
|
+
URL duplicates, canonical paths that do not match the manifest,
|
|
577
|
+
framework hydration payload markers or runtime `__data.json` page data
|
|
578
|
+
references,
|
|
579
|
+
missing CSS/JS/image/icon/OGP/Twitter image/video poster references from generated HTML, missing or
|
|
580
|
+
size-mismatched manifest outputs, duplicate manifest output paths, stale or
|
|
581
|
+
mismatched `headers.json` entries for artifact response metadata, invalid or
|
|
582
|
+
stale `redirects.json` entries, invalid or missing sitemap references in
|
|
583
|
+
`robots.txt`, stale Netlify `_redirects` or `_headers` deploy artifacts, public
|
|
584
|
+
HTML files that are not listed in the manifest, missing local prefetch targets,
|
|
585
|
+
budget failures, public output file bytes by type, precompressed transfer
|
|
586
|
+
sidecar coverage when explicitly requested, referenced stylesheet asset bytes, public CSS and JavaScript
|
|
587
|
+
asset bytes, image asset bytes, the largest HTML files, the largest generated
|
|
588
|
+
artifacts, the largest public output files, the largest referenced stylesheets,
|
|
589
|
+
the largest public CSS and JavaScript files, and the largest public image files.
|
|
590
|
+
The optional HTML, CSS, JavaScript, public-file, total-public, image, Brotli,
|
|
591
|
+
and Gzip budgets are byte limits for generated output. Use public-file budgets
|
|
592
|
+
to catch a single oversized sitemap, image, or export, total-public budgets to
|
|
593
|
+
catch whole-site payload growth, image budgets to catch image optimization
|
|
594
|
+
regressions, and route family page budgets and artifact family output budgets
|
|
595
|
+
catch accidental output growth inside high-cardinality route groups or export
|
|
596
|
+
families before the whole site budget is exceeded. Brotli/Gzip sidecars remain
|
|
597
|
+
available through `optimize` for hosts that need precompressed files, but the
|
|
598
|
+
default publish gate leaves runtime compression to the serving layer.
|
|
599
|
+
Page, artifact, route family, artifact family, HTML, and dependency fanout
|
|
600
|
+
budgets are also available in `buildSite({ validation })`, so the generated
|
|
601
|
+
`.stoneage/report.json` records publish-gate failures even before the standalone
|
|
602
|
+
`validate` command runs.
|
|
603
|
+
The optional dependency fanout budget limits
|
|
604
|
+
how many generated pages and artifacts a single data dependency can invalidate;
|
|
605
|
+
it is available both in build validation and in the `validate` command.
|
|
606
|
+
Use `--validation-config <file>` to load stable CI validation settings from a
|
|
607
|
+
JSON file. CLI budget flags override values from the file, and
|
|
608
|
+
`--require-precompressed` is available only for deployments that intentionally
|
|
609
|
+
ship precompressed sidecars:
|
|
610
|
+
|
|
611
|
+
```json
|
|
612
|
+
{
|
|
613
|
+
"budgets": {
|
|
614
|
+
"maxPages": 22172,
|
|
615
|
+
"maxRouteFamilyPages": {
|
|
616
|
+
"categoryIndexes": 1,
|
|
617
|
+
"yearIndexes": 1,
|
|
618
|
+
"members": 250,
|
|
619
|
+
"sessions": 120,
|
|
620
|
+
"conversationIndexes": 1000,
|
|
621
|
+
"conversations": 20000,
|
|
622
|
+
"bills": 800
|
|
623
|
+
},
|
|
624
|
+
"maxHtmlBytes": 1810,
|
|
625
|
+
"maxPublicFileBytes": 738738,
|
|
626
|
+
"maxCssBytes": 376,
|
|
627
|
+
"maxJsBytes": 518,
|
|
628
|
+
"maxTotalPublicBytes": 16780313,
|
|
629
|
+
"maxImageBytes": 17000,
|
|
630
|
+
"maxDependencyFanout": 2
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
```
|
|
634
|
+
Unknown top-level or budget keys are rejected so configuration typos do not
|
|
635
|
+
silently weaken publish checks. JSON validation summaries and report files
|
|
636
|
+
include the effective validation options, config path, and config SHA-256 hash
|
|
637
|
+
when one was used, so CI artifacts can show which publish gate was applied.
|
|
638
|
+
Build scripts can share the same file for build-time validation:
|
|
639
|
+
|
|
640
|
+
```ts
|
|
641
|
+
import { buildSite, loadValidationConfig } from "@t09tanaka/stoneage";
|
|
642
|
+
|
|
643
|
+
await buildSite({
|
|
644
|
+
outDir: "dist",
|
|
645
|
+
site,
|
|
646
|
+
validation: await loadValidationConfig("validation.json"),
|
|
647
|
+
routes,
|
|
648
|
+
});
|
|
649
|
+
```
|
|
650
|
+
|
|
651
|
+
`loadValidationConfig()` returns the build-time `requiredMetadata` and `budgets`
|
|
652
|
+
settings, plus the source config path and SHA-256 hash that are copied into the
|
|
653
|
+
generated build validation report. The CLI-only `requirePrecompressed` flag is
|
|
654
|
+
still validated so one shared file can be used by both build scripts and
|
|
655
|
+
`stoneage validate`.
|
|
656
|
+
Use `--write-validation-config <file>` after measuring a representative output
|
|
657
|
+
to write an exact baseline config from the current page count, artifact count,
|
|
658
|
+
largest HTML file, largest public file, largest public CSS and JavaScript files,
|
|
659
|
+
total public bytes, image bytes, and largest
|
|
660
|
+
dependency fanout. The generated file is intentionally strict; edit it when a
|
|
661
|
+
site wants explicit growth headroom.
|
|
662
|
+
Public JSON/CSV export validation covers both `data/` exports and generated
|
|
663
|
+
artifact routes such as `/exports/*.csv` or `/search-index.json`.
|
|
664
|
+
|
|
665
|
+
Use `--json` when CI or another tool should consume the validation summary from
|
|
666
|
+
stdout. Use `--report <path>` to write the same summary to a JSON file.
|
|
667
|
+
`check:package` builds the package, audits the packed file list and public
|
|
668
|
+
subpath imports, installs the tarball into a temporary project, and runs the
|
|
669
|
+
installed `stoneage --help` bin.
|
|
670
|
+
|
|
671
|
+
`audit-sveltekit` statically scans a SvelteKit route tree and reports candidate
|
|
672
|
+
StoneAge page routes, artifact routes, dynamic params, param matchers, endpoint
|
|
673
|
+
content kinds, universal `+page.ts` / `+page.js` and `+layout.ts` /
|
|
674
|
+
`+layout.js` load modules, and migration warnings. Warnings include
|
|
675
|
+
optional/rest params, missing matcher files, sibling dynamic routes, route-group
|
|
676
|
+
page duplicates, page/artifact same-path outputs, and static paths shadowed by
|
|
677
|
+
bare dynamic routes. It is not an adapter and does not execute SvelteKit code.
|
|
678
|
+
Use it to build a route migration checklist before replacing `+page.server.ts`,
|
|
679
|
+
universal load modules, and `+server.ts` files with normalized data producers,
|
|
680
|
+
typed route families, and artifact routes. Add `--migration-plan` to include
|
|
681
|
+
deterministic route family and artifact family names, StoneAge patterns, matcher
|
|
682
|
+
wiring, renderer hints for endpoint artifacts, universal load flags, and
|
|
683
|
+
collision-test candidates in the JSON report. Add
|
|
684
|
+
`--emit-stubs <dir>` to also generate migration scaffolding from that plan; it
|
|
685
|
+
implies `--migration-plan` and writes `data.ts`, `routes.ts`, `artifacts.ts`,
|
|
686
|
+
`param-matchers.ts`, `build.ts`, and `collision-tests.md` under the target
|
|
687
|
+
directory. The stubs are intentionally non-adapter code: they import StoneAge
|
|
688
|
+
helpers, include TODOs for normalized data entries, render bodies, and build
|
|
689
|
+
wiring, and do not import SvelteKit modules. Stub emission is no-overwrite by
|
|
690
|
+
default so existing migration files are not silently replaced; the programmatic
|
|
691
|
+
`emitSvelteKitMigrationStubs(plan, dir, { overwrite: true })` API is available
|
|
692
|
+
when regeneration is intentional.
|
|
693
|
+
|
|
694
|
+
Publishing manifests are opt-in:
|
|
695
|
+
|
|
696
|
+
```ts
|
|
697
|
+
import { redirectToRoute, redirectsFromRecords } from "@t09tanaka/stoneage";
|
|
698
|
+
|
|
699
|
+
await buildSite({
|
|
700
|
+
outDir: "dist",
|
|
701
|
+
site,
|
|
702
|
+
publishing: {
|
|
703
|
+
robots: {
|
|
704
|
+
rules: [{ userAgent: "*", allow: ["/"], disallow: ["/drafts/"] }],
|
|
705
|
+
},
|
|
706
|
+
redirects: [
|
|
707
|
+
redirectToRoute({
|
|
708
|
+
from: "/old-members/:id/",
|
|
709
|
+
fromParams: { id: "m1" },
|
|
710
|
+
to: "/members/:id/:year/",
|
|
711
|
+
toParams: { id: "m1", year: 2026 },
|
|
712
|
+
status: 301,
|
|
713
|
+
}),
|
|
714
|
+
...redirectsFromRecords(memberRecords, {
|
|
715
|
+
from: "/members/:id/",
|
|
716
|
+
fromParams: (member) => ({ id: member.slug }),
|
|
717
|
+
to: "/members/:id/:year/",
|
|
718
|
+
toParams: (member) => ({ id: member.slug, year: member.canonicalYear }),
|
|
719
|
+
}),
|
|
720
|
+
],
|
|
721
|
+
},
|
|
722
|
+
routes,
|
|
723
|
+
});
|
|
724
|
+
```
|
|
725
|
+
|
|
726
|
+
StoneAge writes `robots.txt` with a default `Sitemap` line and writes
|
|
727
|
+
`redirects.json` and `headers.json` as host-neutral manifests that deployment
|
|
728
|
+
tooling can translate to provider-specific formats. Redirects are not pages and
|
|
729
|
+
are not added to the sitemap; the canonical HTML page should own metadata and
|
|
730
|
+
sitemap membership. `headers.json` is derived from artifact response metadata
|
|
731
|
+
plus managed immutable public assets such as content-hashed CSS, JS, and fonts.
|
|
732
|
+
Run `deploy --provider netlify` after validation to translate those host-neutral
|
|
733
|
+
manifests into Netlify `_redirects` and `_headers` files without adding provider
|
|
734
|
+
syntax to route or artifact definitions. Sites that publish `buildSite` output
|
|
735
|
+
directly to Netlify or Cloudflare Pages can instead set
|
|
736
|
+
`publishing.nativeRedirects: "netlify"` to write `_redirects` during the build;
|
|
737
|
+
when publishing headers are enabled, the same build also writes `_headers` while
|
|
738
|
+
keeping `headers.json` available. Set `publishing.redirectHtml: true` to
|
|
739
|
+
additionally emit a static `<meta http-equiv="refresh">` HTML page at each
|
|
740
|
+
internal redirect's `from` path, so redirects also work on plain static hosts
|
|
741
|
+
that do not interpret `redirects.json` or `_redirects`. A `from` that collides
|
|
742
|
+
with a real page, artifact, or public asset is skipped (the existing file is
|
|
743
|
+
never overwritten), and non-internal sources are left to the manifest. Like
|
|
744
|
+
`redirects.json` itself, these stubs are not swept on incremental builds, so
|
|
745
|
+
clear `dist/` for a clean rebuild after removing redirect rules.
|
|
746
|
+
|
|
747
|
+
`benchmark` measures generated output sizes without precompressing public files;
|
|
748
|
+
runtime Brotli/Gzip compression is expected to be handled by the serving layer.
|
|
749
|
+
The benchmark command accepts `--trailing-slash always` or
|
|
750
|
+
`--trailing-slash never` so the example site can be measured with either URL
|
|
751
|
+
style. `optimize` writes `.br` and `.gz` sidecars for public
|
|
752
|
+
HTML, CSS, JavaScript, XML, JSON, CSV, text, and SVG files while leaving the
|
|
753
|
+
internal `.stoneage` cache untouched. `validate` reports how many compressible
|
|
754
|
+
public files have Brotli and Gzip sidecars and lists missing sidecars in the
|
|
755
|
+
JSON summary when precompressed sidecar validation is explicitly requested. Pass
|
|
756
|
+
`--require-precompressed` only for deployment targets that require missing
|
|
757
|
+
sidecars to become validation issues.
|
|
758
|
+
|
|
759
|
+
## Documentation
|
|
760
|
+
|
|
761
|
+
- [Getting Started](docs/getting-started.md)
|
|
762
|
+
- [Site Build Guide](docs/site-build.md)
|
|
763
|
+
- [Components](docs/components.md)
|
|
764
|
+
- [Testing Components](docs/testing.md)
|
|
765
|
+
- [Migration Guide](docs/migration.md)
|
|
766
|
+
- [Architecture](docs/architecture.md)
|
|
767
|
+
- [Data Flow](docs/data-flow.md)
|
|
768
|
+
- [Performance](docs/performance.md)
|