@stackbox/cms 0.0.5 → 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 (111) hide show
  1. package/README.md +119 -7
  2. package/dist/AGENTS.md +215 -0
  3. package/dist/blocks.d.ts +6 -13
  4. package/dist/blocks.d.ts.map +1 -1
  5. package/dist/blocks.js +2 -0
  6. package/dist/blocks.js.map +1 -1
  7. package/dist/build.d.ts +14 -0
  8. package/dist/build.d.ts.map +1 -0
  9. package/dist/build.js +145 -0
  10. package/dist/build.js.map +1 -0
  11. package/dist/cache.d.ts +14 -0
  12. package/dist/cache.d.ts.map +1 -0
  13. package/dist/cache.js +93 -0
  14. package/dist/cache.js.map +1 -0
  15. package/dist/cli.d.ts +3 -0
  16. package/dist/cli.d.ts.map +1 -0
  17. package/dist/cli.js +16 -0
  18. package/dist/cli.js.map +1 -0
  19. package/dist/index.d.ts +12 -8
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +3 -0
  22. package/dist/index.js.map +1 -1
  23. package/dist/link.d.ts +3 -7
  24. package/dist/link.d.ts.map +1 -1
  25. package/dist/link.js +12 -19
  26. package/dist/link.js.map +1 -1
  27. package/dist/pages.d.ts +15 -46
  28. package/dist/pages.d.ts.map +1 -1
  29. package/dist/pages.js +2 -0
  30. package/dist/pages.js.map +1 -1
  31. package/dist/plugin.d.ts +22 -0
  32. package/dist/plugin.d.ts.map +1 -0
  33. package/dist/plugin.js +155 -0
  34. package/dist/plugin.js.map +1 -0
  35. package/dist/plugins/blog/index.d.ts +6 -5
  36. package/dist/plugins/blog/index.d.ts.map +1 -1
  37. package/dist/plugins/blog/index.js +16 -0
  38. package/dist/plugins/blog/index.js.map +1 -1
  39. package/dist/plugins/random-quote/assets/quotes.json +230 -0
  40. package/dist/plugins/random-quote/index.d.ts +3 -1
  41. package/dist/plugins/random-quote/index.d.ts.map +1 -1
  42. package/dist/plugins/random-quote/index.js +15 -1
  43. package/dist/plugins/random-quote/index.js.map +1 -1
  44. package/dist/plugins/sitemap/index.d.ts +14 -0
  45. package/dist/plugins/sitemap/index.d.ts.map +1 -0
  46. package/dist/plugins/sitemap/index.js +119 -0
  47. package/dist/plugins/sitemap/index.js.map +1 -0
  48. package/dist/render-head.d.ts +2 -2
  49. package/dist/render-head.d.ts.map +1 -1
  50. package/dist/render-page.d.ts +2 -4
  51. package/dist/render-page.d.ts.map +1 -1
  52. package/dist/render-page.js +7 -3
  53. package/dist/render-page.js.map +1 -1
  54. package/dist/site.d.ts +10 -17
  55. package/dist/site.d.ts.map +1 -1
  56. package/dist/site.js +138 -7
  57. package/dist/site.js.map +1 -1
  58. package/dist/slot-content.d.ts +3 -19
  59. package/dist/slot-content.d.ts.map +1 -1
  60. package/dist/slot-content.js.map +1 -1
  61. package/dist/slot-handle.d.ts +7 -25
  62. package/dist/slot-handle.d.ts.map +1 -1
  63. package/dist/slot-handle.js +29 -14
  64. package/dist/slot-handle.js.map +1 -1
  65. package/dist/stackbox/context.d.ts +2 -51
  66. package/dist/stackbox/context.d.ts.map +1 -1
  67. package/dist/stackbox/context.js.map +1 -1
  68. package/dist/templates.d.ts +7 -57
  69. package/dist/templates.d.ts.map +1 -1
  70. package/dist/templates.js.map +1 -1
  71. package/dist/types.d.ts +277 -0
  72. package/dist/types.d.ts.map +1 -0
  73. package/dist/types.js +2 -0
  74. package/dist/types.js.map +1 -0
  75. package/package.json +13 -3
  76. package/src/plugins/blog/AGENTS.md +5 -1
  77. package/src/plugins/blog/index.ts +22 -5
  78. package/src/plugins/random-quote/AGENTS.md +30 -7
  79. package/src/plugins/random-quote/index.ts +17 -1
  80. package/src/plugins/random-quote/public_assets/widget.css +5 -0
  81. package/src/plugins/sitemap/AGENTS.md +82 -0
  82. package/src/plugins/sitemap/index.ts +156 -0
  83. package/AGENTS.md +0 -87
  84. package/dist/modules/blog/module.d.ts +0 -20
  85. package/dist/modules/blog/module.d.ts.map +0 -1
  86. package/dist/modules/blog/module.js +0 -52
  87. package/dist/modules/blog/module.js.map +0 -1
  88. package/dist/modules/blog/posts.d.ts +0 -15
  89. package/dist/modules/blog/posts.d.ts.map +0 -1
  90. package/dist/modules/blog/posts.js +0 -46
  91. package/dist/modules/blog/posts.js.map +0 -1
  92. package/dist/plugins/blog/module.d.ts +0 -20
  93. package/dist/plugins/blog/module.d.ts.map +0 -1
  94. package/dist/plugins/blog/module.js +0 -52
  95. package/dist/plugins/blog/module.js.map +0 -1
  96. package/dist/plugins/blog/plugin.d.ts +0 -20
  97. package/dist/plugins/blog/plugin.d.ts.map +0 -1
  98. package/dist/plugins/blog/plugin.js +0 -52
  99. package/dist/plugins/blog/plugin.js.map +0 -1
  100. package/dist/plugins/random-quote/module.d.ts +0 -9
  101. package/dist/plugins/random-quote/module.d.ts.map +0 -1
  102. package/dist/plugins/random-quote/module.js +0 -31
  103. package/dist/plugins/random-quote/module.js.map +0 -1
  104. package/dist/plugins/random-quote/plugin.d.ts +0 -8
  105. package/dist/plugins/random-quote/plugin.d.ts.map +0 -1
  106. package/dist/plugins/random-quote/plugin.js +0 -31
  107. package/dist/plugins/random-quote/plugin.js.map +0 -1
  108. package/dist/plugins/random-quote/quotes.json +0 -230
  109. package/dist/public/_sb/plugins/random-quote/quotes.json +0 -230
  110. package/src/plugins/random-quote/public_assets/quotes.json +0 -230
  111. /package/{dist/plugins/random-quote/public_assets → src/plugins/random-quote/assets}/quotes.json +0 -0
package/README.md CHANGED
@@ -12,7 +12,7 @@ export default {
12
12
  };
13
13
  ```
14
14
 
15
- Pages are rendered on each request, so content, templates, blocks, and plugins can be fully dynamic — driven by request data, environment bindings, and async data fetching.
15
+ GET/HEAD page renders are cached in memory by default (stale-while-revalidate, single-flight refresh). Set `cache: false` on the site config, a page, or any block on that page to render every request. Plugin routes and plugin assets are always uncached.
16
16
 
17
17
  ## Why this exists
18
18
 
@@ -33,15 +33,87 @@ npm install @stackbox/cms
33
33
  | Primitive | Factory | Purpose |
34
34
  | --- | --- | --- |
35
35
  | **Site config** | `createSiteConfig(config)` | Definition-time settings shared by templates and pages. |
36
- | **Site** | `createSite(siteConfig, { pages })` | Runtime router with `fetch(request, env)` — a fetch-handler-compatible server. |
36
+ | **Site** | `createSite(siteConfig, { pages, plugins? })` | Runtime router with `fetch(request, env)` — a fetch-handler-compatible server. |
37
37
  | **Template** | `createTemplate({ siteConfig, slots, render })` | A reusable page layout that declares named **slots**. |
38
38
  | **Page** | `createPage(template, { path, title, slots })` | A single URL, built by filling a template's slots with content. |
39
39
  | **Block** | `createBlock({ name, render })` | A self-contained content block placed into a slot at request time. |
40
+ | **Plugin** | `createPlugin({ name, description, version, keywords, root })` | A packaged feature. Register it on `createSite({ plugins })` when you use it. |
40
41
 
41
- **Plugins vs blocks:** **Plugins** package whole features (blog, newsletter) under `@stackbox/cms/plugins/<name>` — they may export factories, content objects, blocks, types, and helpers. **Blocks** are the core slot primitive via `createBlock()`; plugins can ship blocks alongside other exports.
42
+ **Plugins vs blocks:** **Plugins** package whole features (blog, newsletter) — bundled under `@stackbox/cms/plugins/<name>` or a third-party package with the same shape. Each plugin **must** export a `plugin` object from `createPlugin()` (name, description, version, keywords). **Blocks** are the core slot primitive via `createBlock()`; plugins can ship blocks alongside other exports.
43
+
44
+ Importing a plugin does not enable it. Pass `plugin` into `createSite({ plugins })`. Only registered plugins have `public_assets/` served or copied.
42
45
 
43
46
  **Slots** are named regions in a template. Page content — strings, HTML, or blocks — is dropped into slots, and the engine resolves and renders everything (including async blocks, concurrently) to a single HTML string.
44
47
 
48
+ ## Page cache
49
+
50
+ Optional `cache: { min?: number; max?: number } | false` on site config, pages, and blocks (milliseconds). Omit = no opinion; `false` = never cache that request.
51
+
52
+ ```ts
53
+ createSiteConfig({
54
+ name: "My Site",
55
+ url: "https://example.com",
56
+ cache: { min: 60_000, max: 7 * 24 * 60 * 60 * 1000 },
57
+ });
58
+
59
+ createPage(template, {
60
+ path: "/live",
61
+ title: "Live",
62
+ cache: false,
63
+ slots: { content: [...] },
64
+ });
65
+
66
+ createBlock({
67
+ name: "ticker",
68
+ cache: { max: 5 * 60 * 1000 },
69
+ render() { ... },
70
+ });
71
+ ```
72
+
73
+ TTL merges settings from the site config, the page, and on-page blocks: highest `min` floors the result, lowest `max` caps it (default 1 day, hard cap 30 days). Responses include `Cache-Control`. Expired entries are served immediately while one background refresh runs per key.
74
+
75
+ ## Site hooks
76
+
77
+ Extend rendering at runtime with an optional `hooks` object on `createSite`:
78
+
79
+ ```ts
80
+ export default createSite(siteConfig, {
81
+ pages: [homePage],
82
+ hooks: {
83
+ shouldCache(request) {
84
+ return true;
85
+ },
86
+ renderSlotItem(html, info) {
87
+ return html;
88
+ },
89
+ afterRender(html, info) {
90
+ return html;
91
+ },
92
+ beforeResponse(response, info) {
93
+ return response;
94
+ },
95
+ },
96
+ });
97
+ ```
98
+
99
+ - **`shouldCache(request)`** — return `false` to skip the page cache for that request (page/block TTL still applies when it returns `true`)
100
+ - **`renderSlotItem(html, info)`** — transform each slot item after it renders
101
+ - **`afterRender(html, info)`** — transform the full page HTML
102
+ - **`beforeResponse(response, info)`** — adjust page and 404 responses before they are returned
103
+
104
+ Pages and blocks may optionally set `source` (for example `import.meta.url`) so hooks can point editors or agents at the defining file.
105
+
106
+ ## Types
107
+
108
+ Core types are exported as a single namespace:
109
+
110
+ ```ts
111
+ import type { Stackbox } from "@stackbox/cms";
112
+
113
+ type Page = Stackbox.Page;
114
+ type SiteHooks = Stackbox.SiteHooks;
115
+ ```
116
+
45
117
  ## Project layout
46
118
 
47
119
  ```
@@ -109,6 +181,7 @@ import aboutPage from "./pages/about";
109
181
 
110
182
  export default createSite(siteConfig, {
111
183
  pages: [homePage, aboutPage],
184
+ // plugins: [blogPlugin, randomQuotePlugin], // only plugins this site uses
112
185
  });
113
186
  ```
114
187
 
@@ -157,10 +230,12 @@ export const blogPostPages = blog.posts.map((post) =>
157
230
 
158
231
  ```ts
159
232
  // server.ts
233
+ import blogPlugin from "@stackbox/cms/plugins/blog";
160
234
  import { blogListingPages, blogPostPages } from "./pages/blog";
161
235
 
162
236
  export default createSite(siteConfig, {
163
237
  pages: [homePage, ...blogListingPages, ...blogPostPages],
238
+ plugins: [blogPlugin],
164
239
  });
165
240
  ```
166
241
 
@@ -169,28 +244,65 @@ export default createSite(siteConfig, {
169
244
  **Blog** — content objects wired into pages:
170
245
 
171
246
  ```ts
172
- import { createBlog } from "@stackbox/cms/plugins/blog";
247
+ import blogPlugin, { createBlog } from "@stackbox/cms/plugins/blog";
173
248
  ```
174
249
 
175
250
  **Random quote** — block-only plugin (drop into any slot):
176
251
 
177
252
  ```ts
178
- import { randomQuoteBlock } from "@stackbox/cms/plugins/random-quote";
253
+ import randomQuotePlugin, {
254
+ randomQuoteBlock,
255
+ } from "@stackbox/cms/plugins/random-quote";
179
256
  import myQuotes from "../content/quotes.json" with { type: "json" };
180
257
 
181
258
  slots: { sidebar: [randomQuoteBlock()] } // bundled quotes
182
259
  slots: { sidebar: [randomQuoteBlock({ quotes: myQuotes })] } // your own
183
260
  ```
184
261
 
262
+ Register every plugin you use:
263
+
264
+ ```ts
265
+ export default createSite(siteConfig, {
266
+ pages: [homePage, ...blogListingPages, ...blogPostPages],
267
+ plugins: [blogPlugin, randomQuotePlugin],
268
+ });
269
+ ```
270
+
271
+ Private plugin files live in `assets/` (imported by JS). Files served over HTTP live in `public_assets/` and are copied or served only for registered plugins — including third-party packages that follow the same layout.
272
+
273
+ Plugins may also register **`routes`** (served by `fetch()`) and **`build`** hooks (run by `stackbox-cms build`). The **sitemap** plugin uses both to serve and write `/sitemap.xml`:
274
+
275
+ ```ts
276
+ import sitemapPlugin from "@stackbox/cms/plugins/sitemap";
277
+
278
+ export default createSite(siteConfig, {
279
+ pages: [homePage, aboutPage],
280
+ plugins: [sitemapPlugin],
281
+ });
282
+ ```
283
+
284
+ Run the package build script so registered plugin `public_assets/` land in the site public directory:
285
+
286
+ ```bash
287
+ npx stackbox-cms build
288
+ # or: npx stackbox-cms build --site server.ts --outDir dist --publicDir dist/public
289
+ ```
290
+
291
+ ```json
292
+ "scripts": {
293
+ "build": "stackbox-cms build"
294
+ }
295
+ ```
296
+
185
297
  ## AI agents
186
298
 
187
- Bundled plugins include agent playbooks. See [`AGENTS.md`](AGENTS.md) for site conventions and a plugin catalog. When a user asks for a feature (e.g. "add a blog"), read **only** the matching plugin's `AGENTS.md` — do not load every plugin file.
299
+ Bundled plugins include agent playbooks. See [`dist/AGENTS.md`](dist/AGENTS.md) (generated on `npm run build`) for site conventions and a plugin keyword catalog. When a user asks for a feature (e.g. "add a blog"), read **only** the matching plugin's `AGENTS.md` — do not load every plugin file.
188
300
 
189
301
  If you are building a site that uses this package, add this to your project's `AGENTS.md`:
190
302
 
191
303
  ```md
192
304
  This site uses @stackbox/cms. Before adding features, read
193
- `node_modules/@stackbox/cms/AGENTS.md` and follow its plugin catalog.
305
+ `node_modules/@stackbox/cms/dist/AGENTS.md` and follow its plugin catalog.
194
306
  Do not reimplement bundled plugins.
195
307
  ```
196
308
 
package/dist/AGENTS.md ADDED
@@ -0,0 +1,215 @@
1
+ # @stackbox/cms — agent instructions
2
+
3
+ Stackbox is a code-first CMS that assembles TypeScript into a **fetch-handler-compatible server**. Use these conventions when building or extending a site.
4
+
5
+ ## Site conventions
6
+
7
+ | Primitive | Factory | Purpose |
8
+ | --- | --- | --- |
9
+ | Site config | `createSiteConfig(config)` | Definition-time settings shared by templates and pages |
10
+ | Site | `createSite(siteConfig, { pages, plugins? })` | Runtime router with `fetch(request, env)` |
11
+ | Template | `createTemplate({ siteConfig, slots, render })` | Reusable layout with named slots |
12
+ | Page | `createPage(template, { path, title, slots })` | A single URL built from a template + slot content |
13
+ | Block | `createBlock({ name, render })` | A content block placed into a slot at request time |
14
+ | Plugin | `createPlugin({ name, description, version, keywords, root })` | Packaged feature; register on the site when used |
15
+
16
+ **Project layout:**
17
+
18
+ ```
19
+ site.config.ts # createSiteConfig
20
+ server.ts # createSite(...) — default export is the fetch handler
21
+ templates/ # shared createTemplate() layouts
22
+ pages/ # createPage() per route or feature
23
+ content/ # markdown or other bundle-time content (per feature)
24
+ package.json # "build": "stackbox-cms build"
25
+ ```
26
+
27
+ The default export from `server.ts` implements `fetch(request, env)` and returns a `Response`. It works on Cloudflare Workers, Bun, Deno, and any runtime that speaks the fetch-handler pattern.
28
+
29
+ ## Page request cache
30
+
31
+ GET/HEAD page renders are cached in memory by default. Keys are pathname + query string (`/about?preview=1` is separate from `/about`). Plugin routes and `/_sb/plugins/` assets are never cached.
32
+
33
+ Optional `cache` on site config, pages, and blocks — same shape everywhere:
34
+
35
+ ```ts
36
+ export type CacheConfig = { min?: number; max?: number } | false;
37
+ ```
38
+
39
+ Milliseconds. Omit = no opinion. `cache: false` = fully dynamic (do not cache that request).
40
+
41
+ ```ts
42
+ createSiteConfig({
43
+ name: "My Site",
44
+ url: "https://example.com",
45
+ cache: { min: 60_000, max: 7 * 24 * 60 * 60 * 1000 },
46
+ })
47
+
48
+ createPage(template, {
49
+ path: "/live",
50
+ title: "Live",
51
+ cache: false,
52
+ slots: { content: [...] },
53
+ })
54
+
55
+ createBlock({
56
+ name: "random-quote",
57
+ cache: { max: 5 * 60 * 1000 },
58
+ render() { ... },
59
+ })
60
+ ```
61
+
62
+ TTL is derived from the site config, the page, and every block on that page. If **any** of those is `false`, the request is not cached. Otherwise merge `{ min, max }` (unset fields ignored): highest `min` floors the result, lowest `max` caps it (default TTL 1 day, hard cap 30 days). Registering a plugin does not change cache times — only blocks (or pages) with `cache` on that page count.
63
+
64
+ Stale entries are served immediately (stale-while-revalidate); one background refresh runs per key. Concurrent misses share a single render (single-flight). Failed refreshes keep the last good HTML.
65
+
66
+ Disable caching for a whole site with `createSiteConfig({ cache: false })`. `createSite` accepts an optional `cacheAdapter` for tests or custom stores — do not overload `cache` on `createSite`.
67
+
68
+ ## Site hooks
69
+
70
+ Optional runtime hooks on `createSite(siteConfig, { pages, plugins?, hooks? })`. All hooks are optional; omitting them preserves default behavior. Page/block `cache` TTL is unchanged — hooks do not replace that policy.
71
+
72
+ ```ts
73
+ createSite(siteConfig, {
74
+ pages: [homePage],
75
+ hooks: {
76
+ shouldCache(request) {
77
+ return !new URL(request.url).searchParams.has("preview");
78
+ },
79
+ renderSlotItem(html, { item, slot, index, page, ctx }) {
80
+ return html;
81
+ },
82
+ afterRender(html, { page, ctx }) {
83
+ return html;
84
+ },
85
+ beforeResponse(response, { request, page, ctx }) {
86
+ return response;
87
+ },
88
+ },
89
+ });
90
+ ```
91
+
92
+ | Hook | When | Signature |
93
+ | --- | --- | --- |
94
+ | `shouldCache` | Before the page cache adapter runs | `(request: Request) => boolean` — `false` skips the adapter for that request; `true` uses page/block TTL |
95
+ | `renderSlotItem` | After each slot item (block or HTML string) renders | `(html, info) => string` |
96
+ | `afterRender` | After the full page HTML is assembled | `(html, info) => string` |
97
+ | `beforeResponse` | Before `fetch` returns a page or 404 response | `(response, info) => Response` |
98
+
99
+ Plugin routes and `/_sb/plugins/` assets do not run render hooks. `beforeResponse` does not wrap plugin route responses.
100
+
101
+ Optional `source?: string` on `createPage` and `createBlock` (e.g. `import.meta.url`) is metadata for hook consumers — the engine stores it as given.
102
+
103
+ ## Plugins vs blocks
104
+
105
+ - **Plugin** — a packaged use-case (bundled under `@stackbox/cms/plugins/<name>`, or a third-party package with the same shape). **Default-exports** a `createPlugin()` registration object, plus named factories, content objects, blocks, types, and helpers.
106
+ - **Block** — a core primitive via `createBlock()`. Renders HTML into a template slot. Usable inside or outside plugins.
107
+
108
+ Slot content is `Block | string`. Plugin content arrays can interleave blocks and HTML strings.
109
+
110
+ **Register plugins when you use them.** Importing a plugin module is not enough — pass its default export to `createSite({ plugins })`. Only registered plugins have their `public_assets/` copied or served. Third-party packages are registered the same way; they are not discovered from a `plugins/` folder.
111
+
112
+ ```ts
113
+ import blogPlugin from "@stackbox/cms/plugins/blog";
114
+ import randomQuotePlugin from "@stackbox/cms/plugins/random-quote";
115
+
116
+ export default createSite(siteConfig, {
117
+ pages: [homePage, ...blogListingPages, ...blogPostPages],
118
+ plugins: [blogPlugin, randomQuotePlugin],
119
+ });
120
+ ```
121
+
122
+ `createPlugin({ name, description, version, keywords, root })` is **required**. `createSite` throws if a registered value is missing that metadata. `keywords` tell agents when to reach for the plugin.
123
+
124
+ Optional hooks on `createPlugin()`:
125
+
126
+ - **`routes({ site })`** — site-level paths served by `fetch()` before the GET/HEAD gate, plugin assets, and pages (e.g. `/sitemap.xml`). The route handler decides which HTTP methods are allowed. Must return synchronously. Paths must not collide with pages or other plugin routes.
127
+ - **`build({ site, outDir, publicDir })`** — called by `stackbox-cms build` after copying registered `public_assets/`. Write generated files into `publicDir` (or elsewhere under `outDir`).
128
+
129
+ ## Plugin catalog
130
+
131
+ Before implementing a use-case (blog, newsletter, docs, …), check this table. If the user's request matches a row, **read only that plugin's AGENTS.md** and follow it. Do not invent a parallel implementation.
132
+
133
+ <!-- plugin-catalog:start -->
134
+ | Plugin | Import | Read when the user mentions | Instructions |
135
+ | --- | --- | --- | --- |
136
+ | blog | `@stackbox/cms/plugins/blog` | blog, posts, articles, journal, markdown posts, blog listing, blog page | `src/plugins/blog/AGENTS.md` |
137
+ | random-quote | `@stackbox/cms/plugins/random-quote` | random quote, quote of the day, inspirational quote, sidebar quote, quotation | `src/plugins/random-quote/AGENTS.md` |
138
+ | sitemap | `@stackbox/cms/plugins/sitemap` | sitemap, sitemap.xml, seo, search engines, xml sitemap | `src/plugins/sitemap/AGENTS.md` |
139
+ <!-- plugin-catalog:end -->
140
+
141
+ When adding a new plugin to this package, add a folder under `src/plugins/<name>/`, **default-export** the `createPlugin()` registration from `index.ts`, and create `src/plugins/<name>/AGENTS.md` using the same heading structure as the blog plugin. Run `npm run build` — the package scans `dist/plugins/` and writes the keyword catalog to `dist/AGENTS.md` from each plugin's `keywords`. Document all export kinds (factory, types, blocks, helpers).
142
+
143
+ ## Plugin layout and assets
144
+
145
+ Every plugin — bundled or third-party — has the same shape:
146
+
147
+ ```
148
+ index.ts # default export: createPlugin({ ... }); named exports: factories, types, blocks
149
+ AGENTS.md # agent playbook
150
+ assets/ # private files imported by JS (JSON, templates, …)
151
+ public_assets/ # files served over HTTP (images, CSS, fonts, …)
152
+ ```
153
+
154
+ `root` is the directory that contains `assets/` and `public_assets/`. Pass `import.meta.dirname` when those folders sit next to the plugin entry. A third-party package may pass its package root instead.
155
+
156
+ | Folder | Copied to | When |
157
+ | --- | --- | --- |
158
+ | `assets/` | `{plugin.root}/assets` (next to compiled JS) | Package build of that plugin (`copyPluginPrivateAssets`) |
159
+ | `public_assets/` | `{publicDir}/_sb/plugins/{name}/` | Site build, **only for plugins registered on the site** (`copyRegisteredPluginAssets`) |
160
+
161
+ `public_assets/` is no longer mirrored into the plugin's compiled folder. Import private data from `./assets/...`, not from `public_assets/`.
162
+
163
+ At request time, `createSite.fetch` order is: **plugin routes** → **plugin public assets** (`/_sb/plugins/...`) → **pages**.
164
+
165
+ For a static `public/` directory, run the package build script — it loads the site, reads `site.plugins`, copies registered `public_assets/`, then runs each plugin's `build` hook:
166
+
167
+ ```bash
168
+ npx stackbox-cms build
169
+ ```
170
+
171
+ ```json
172
+ "scripts": {
173
+ "build": "stackbox-cms build"
174
+ }
175
+ ```
176
+
177
+ ```ts
178
+ import { build } from "@stackbox/cms/build";
179
+ import site from "./server.ts";
180
+
181
+ await build({
182
+ site,
183
+ outDir: "dist",
184
+ publicDir: "dist/public",
185
+ });
186
+ ```
187
+
188
+ Defaults: `--site` looks for `server.ts` (then `server.js`, `src/server.ts`, `src/server.js`); `outDir` is `dist`; `publicDir` is `{outDir}/public`. Flags: `--site`, `--outDir`, `--publicDir`.
189
+
190
+ Use `pluginAssetPath()` from `@stackbox/cms/link` for `<img src>`, `<link href>`, etc. Do not put site files under `/_sb/` — that prefix is reserved for Stackbox plugin assets.
191
+
192
+ ```ts
193
+ import { pluginAssetPath } from "@stackbox/cms/link";
194
+
195
+ pluginAssetPath("sb-random-quote", "widget.css");
196
+ // → "/_sb/plugins/sb-random-quote/widget.css"
197
+ ```
198
+
199
+ ## For site agents
200
+
201
+ If you are working in a **consumer site repo** (not this package), read the generated agent instructions from the installed package:
202
+
203
+ ```
204
+ node_modules/@stackbox/cms/dist/AGENTS.md
205
+ ```
206
+
207
+ Recommended snippet for the site's own `AGENTS.md`:
208
+
209
+ ```md
210
+ This site uses @stackbox/cms. Before adding features, read
211
+ `node_modules/@stackbox/cms/dist/AGENTS.md` and follow its plugin catalog.
212
+ Do not reimplement bundled plugins.
213
+ ```
214
+
215
+ When a request matches a catalog row, open **only** that plugin's instructions file (e.g. `node_modules/@stackbox/cms/src/plugins/blog/AGENTS.md`).
package/dist/blocks.d.ts CHANGED
@@ -1,18 +1,11 @@
1
- import type { RenderContext } from "./pages.js";
2
- import type { HSHtml } from "@hyperspan/html";
3
- export type BlockRenderResult = HSHtml | Promise<HSHtml>;
4
- export type Block = {
5
- readonly __kind: "block";
6
- readonly name: string;
7
- render(ctx: RenderContext): BlockRenderResult;
8
- };
1
+ import type { Stackbox as SB } from "./types.js";
9
2
  type BlockDef<TOptions = undefined> = {
10
3
  name: string;
11
- render(options: TOptions, ctx?: RenderContext): BlockRenderResult;
4
+ cache?: SB.CacheConfig;
5
+ source?: string;
6
+ render(options: TOptions, ctx?: SB.RenderContext): SB.BlockRenderResult;
12
7
  };
13
- export type BlockOptionsOf<F> = F extends (options?: infer O) => unknown ? [O] extends [undefined] ? undefined : O : never;
14
- export type BlockFactory<TOptions = undefined> = (options?: TOptions) => Block;
15
- export declare function createBlock<TOptions = undefined>(def: BlockDef<TOptions>): BlockFactory<TOptions>;
16
- export declare function isBlock(value: unknown): value is Block;
8
+ export declare function createBlock<TOptions = undefined>(def: BlockDef<TOptions>): SB.BlockFactory<TOptions>;
9
+ export declare function isBlock(value: unknown): value is SB.Block;
17
10
  export {};
18
11
  //# sourceMappingURL=blocks.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"blocks.d.ts","sourceRoot":"","sources":["../src/blocks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAE9C,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;AAEzD,MAAM,MAAM,KAAK,GAAG;IAClB,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,GAAG,EAAE,aAAa,GAAG,iBAAiB,CAAC;CAC/C,CAAC;AAEF,KAAK,QAAQ,CAAC,QAAQ,GAAG,SAAS,IAAI;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,OAAO,EAAE,QAAQ,EAAE,GAAG,CAAC,EAAE,aAAa,GAAG,iBAAiB,CAAC;CACnE,CAAC;AAEF,MAAM,MAAM,cAAc,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,KAAK,OAAO,GACpE,CAAC,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC,GACrB,SAAS,GACT,CAAC,GACH,KAAK,CAAC;AAEV,MAAM,MAAM,YAAY,CAAC,QAAQ,GAAG,SAAS,IAAI,CAC/C,OAAO,CAAC,EAAE,QAAQ,KACf,KAAK,CAAC;AAQX,wBAAgB,WAAW,CAAC,QAAQ,GAAG,SAAS,EAC9C,GAAG,EAAE,QAAQ,CAAC,QAAQ,CAAC,GACtB,YAAY,CAAC,QAAQ,CAAC,CAYxB;AAED,wBAAgB,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,KAAK,CAStD"}
1
+ {"version":3,"file":"blocks.d.ts","sourceRoot":"","sources":["../src/blocks.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,YAAY,CAAC;AAEjD,KAAK,QAAQ,CAAC,QAAQ,GAAG,SAAS,IAAI;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,OAAO,EAAE,QAAQ,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,aAAa,GAAG,EAAE,CAAC,iBAAiB,CAAC;CACzE,CAAC;AAQF,wBAAgB,WAAW,CAAC,QAAQ,GAAG,SAAS,EAC9C,GAAG,EAAE,QAAQ,CAAC,QAAQ,CAAC,GACtB,EAAE,CAAC,YAAY,CAAC,QAAQ,CAAC,CAc3B;AAED,wBAAgB,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,EAAE,CAAC,KAAK,CASzD"}
package/dist/blocks.js CHANGED
@@ -11,6 +11,8 @@ export function createBlock(def) {
11
11
  return ((options) => ({
12
12
  __kind: "block",
13
13
  name: def.name,
14
+ ...(def.cache !== undefined ? { cache: def.cache } : {}),
15
+ ...(def.source !== undefined ? { source: def.source } : {}),
14
16
  render: (ctx) => def.render(options, ctx),
15
17
  }));
16
18
  }
@@ -1 +1 @@
1
- {"version":3,"file":"blocks.js","sourceRoot":"","sources":["../src/blocks.ts"],"names":[],"mappings":"AA0BA,SAAS,iBAAiB,CAAC,IAAY;IACrC,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC/B,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;IACxD,CAAC;AACH,CAAC;AAED,MAAM,UAAU,WAAW,CACzB,GAAuB;IAEvB,iBAAiB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAE5B,IAAI,OAAO,GAAG,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;QACrC,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;IAC1D,CAAC;IAED,OAAO,CAAC,CAAC,OAAkB,EAAE,EAAE,CAAC,CAAC;QAC/B,MAAM,EAAE,OAAgB;QACxB,IAAI,EAAE,GAAG,CAAC,IAAI;QACd,MAAM,EAAE,CAAC,GAAkB,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,OAAmB,EAAE,GAAG,CAAC;KACrE,CAAC,CAA2B,CAAC;AAChC,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,KAAc;IACpC,OAAO,CACL,OAAO,KAAK,KAAK,QAAQ;QACzB,KAAK,KAAK,IAAI;QACb,KAAe,CAAC,MAAM,KAAK,OAAO;QACnC,OAAQ,KAAe,CAAC,IAAI,KAAK,QAAQ;QACxC,KAAe,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC;QAChC,OAAQ,KAAe,CAAC,MAAM,KAAK,UAAU,CAC9C,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"blocks.js","sourceRoot":"","sources":["../src/blocks.ts"],"names":[],"mappings":"AAUA,SAAS,iBAAiB,CAAC,IAAY;IACrC,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC/B,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;IACxD,CAAC;AACH,CAAC;AAED,MAAM,UAAU,WAAW,CACzB,GAAuB;IAEvB,iBAAiB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAE5B,IAAI,OAAO,GAAG,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;QACrC,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;IAC1D,CAAC;IAED,OAAO,CAAC,CAAC,OAAkB,EAAE,EAAE,CAAC,CAAC;QAC/B,MAAM,EAAE,OAAgB;QACxB,IAAI,EAAE,GAAG,CAAC,IAAI;QACd,GAAG,CAAC,GAAG,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACxD,GAAG,CAAC,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3D,MAAM,EAAE,CAAC,GAAqB,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,OAAmB,EAAE,GAAG,CAAC;KACxE,CAAC,CAA8B,CAAC;AACnC,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,KAAc;IACpC,OAAO,CACL,OAAO,KAAK,KAAK,QAAQ;QACzB,KAAK,KAAK,IAAI;QACb,KAAkB,CAAC,MAAM,KAAK,OAAO;QACtC,OAAQ,KAAkB,CAAC,IAAI,KAAK,QAAQ;QAC3C,KAAkB,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC;QACnC,OAAQ,KAAkB,CAAC,MAAM,KAAK,UAAU,CACjD,CAAC;AACJ,CAAC"}
@@ -0,0 +1,14 @@
1
+ import type { Stackbox as SB } from "./types.js";
2
+ export declare class BuildError extends Error {
3
+ constructor(message: string);
4
+ }
5
+ export declare function resolveBuildSettings(options: Omit<SB.BuildOptions, "site">): SB.BuildSettings;
6
+ /**
7
+ * Run the site build. Copies each registered plugin's `public_assets/` to
8
+ * `{publicDir}/_sb/plugins/{name}/`, then runs plugin `build` hooks.
9
+ */
10
+ export declare function build(options: SB.BuildOptions): Promise<SB.BuildResult>;
11
+ export declare function parseBuildCliArgs(argv: string[]): SB.BuildCliArgs;
12
+ export declare function loadSiteFromEntry(entry: string, cwd?: string): Promise<SB.Site>;
13
+ export declare function runBuildCli(argv: string[], cwd?: string): Promise<SB.BuildResult>;
14
+ //# sourceMappingURL=build.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,YAAY,CAAC;AASjD,qBAAa,UAAW,SAAQ,KAAK;gBACvB,OAAO,EAAE,MAAM;CAI5B;AASD,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,IAAI,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,CAAC,GACrC,EAAE,CAAC,aAAa,CAOlB;AAED;;;GAGG;AACH,wBAAsB,KAAK,CAAC,OAAO,EAAE,EAAE,CAAC,YAAY,GAAG,OAAO,CAAC,EAAE,CAAC,WAAW,CAAC,CAmB7E;AAcD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,YAAY,CAmCjE;AAkCD,wBAAsB,iBAAiB,CACrC,KAAK,EAAE,MAAM,EACb,GAAG,SAAgB,GAClB,OAAO,CAAC,EAAE,CAAC,IAAI,CAAC,CAuBlB;AAED,wBAAsB,WAAW,CAC/B,IAAI,EAAE,MAAM,EAAE,EACd,GAAG,SAAgB,GAClB,OAAO,CAAC,EAAE,CAAC,WAAW,CAAC,CAWzB"}
package/dist/build.js ADDED
@@ -0,0 +1,145 @@
1
+ import { existsSync, mkdirSync } from "node:fs";
2
+ import { join, resolve } from "node:path";
3
+ import { pathToFileURL } from "node:url";
4
+ import { register } from "node:module";
5
+ import { copyRegisteredPluginAssets, runRegisteredPluginBuilds, } from "./plugin.js";
6
+ import { isSite } from "./site.js";
7
+ const DEFAULT_SITE_ENTRIES = [
8
+ "server.ts",
9
+ "server.js",
10
+ "src/server.ts",
11
+ "src/server.js",
12
+ ];
13
+ export class BuildError extends Error {
14
+ constructor(message) {
15
+ super(message);
16
+ this.name = "BuildError";
17
+ }
18
+ }
19
+ function requireDirOption(value, label) {
20
+ if (typeof value !== "string" || value.trim().length === 0) {
21
+ throw new BuildError(`build(): ${label} must be a non-empty string`);
22
+ }
23
+ return resolve(value.trim());
24
+ }
25
+ export function resolveBuildSettings(options) {
26
+ const outDir = requireDirOption(options.outDir ?? "dist", "outDir");
27
+ const publicDir = requireDirOption(options.publicDir ?? join(outDir, "public"), "publicDir");
28
+ return { outDir, publicDir };
29
+ }
30
+ /**
31
+ * Run the site build. Copies each registered plugin's `public_assets/` to
32
+ * `{publicDir}/_sb/plugins/{name}/`, then runs plugin `build` hooks.
33
+ */
34
+ export async function build(options) {
35
+ if (!options || typeof options !== "object" || Array.isArray(options)) {
36
+ throw new BuildError("build(): site is required");
37
+ }
38
+ if (!isSite(options.site)) {
39
+ throw new BuildError("build(): site must be from createSite()");
40
+ }
41
+ const settings = resolveBuildSettings(options);
42
+ mkdirSync(settings.outDir, { recursive: true });
43
+ mkdirSync(settings.publicDir, { recursive: true });
44
+ copyRegisteredPluginAssets(options.site.plugins, settings.publicDir);
45
+ await runRegisteredPluginBuilds(options.site.plugins, {
46
+ site: options.site,
47
+ outDir: settings.outDir,
48
+ publicDir: settings.publicDir,
49
+ });
50
+ return { settings, plugins: options.site.plugins };
51
+ }
52
+ function readFlag(args, index, name) {
53
+ const value = args[index + 1];
54
+ if (!value || value.startsWith("-")) {
55
+ throw new BuildError(`stackbox-cms build: ${name} requires a value`);
56
+ }
57
+ return { value, next: index + 2 };
58
+ }
59
+ export function parseBuildCliArgs(argv) {
60
+ const args = argv.slice(2);
61
+ let index = 0;
62
+ if (args[0] && !args[0].startsWith("-")) {
63
+ if (args[0] !== "build") {
64
+ throw new BuildError(`stackbox-cms: unknown command "${args[0]}" (expected "build")`);
65
+ }
66
+ index = 1;
67
+ }
68
+ let site;
69
+ let outDir;
70
+ let publicDir;
71
+ while (index < args.length) {
72
+ const arg = args[index];
73
+ if (arg === "--site") {
74
+ ({ value: site, next: index } = readFlag(args, index, "--site"));
75
+ continue;
76
+ }
77
+ if (arg === "--outDir") {
78
+ ({ value: outDir, next: index } = readFlag(args, index, "--outDir"));
79
+ continue;
80
+ }
81
+ if (arg === "--publicDir") {
82
+ ({ value: publicDir, next: index } = readFlag(args, index, "--publicDir"));
83
+ continue;
84
+ }
85
+ throw new BuildError(`stackbox-cms build: unknown option "${arg}"`);
86
+ }
87
+ return { command: "build", site, outDir, publicDir };
88
+ }
89
+ function defaultSiteEntry(cwd) {
90
+ for (const candidate of DEFAULT_SITE_ENTRIES) {
91
+ const absolute = resolve(cwd, candidate);
92
+ if (existsSync(absolute)) {
93
+ return absolute;
94
+ }
95
+ }
96
+ throw new BuildError(`stackbox-cms build: no site entry found (looked for ${DEFAULT_SITE_ENTRIES.join(", ")}). Pass --site`);
97
+ }
98
+ let tsxRegistered = false;
99
+ function tsxAlreadyActive() {
100
+ const flags = [...process.execArgv, process.env.NODE_OPTIONS ?? ""].join(" ");
101
+ return flags.includes("tsx");
102
+ }
103
+ function ensureTsxRegistered() {
104
+ if (tsxRegistered || tsxAlreadyActive()) {
105
+ tsxRegistered = true;
106
+ return;
107
+ }
108
+ try {
109
+ register("tsx", import.meta.url);
110
+ }
111
+ catch {
112
+ // Loader already registered in this process.
113
+ }
114
+ tsxRegistered = true;
115
+ }
116
+ export async function loadSiteFromEntry(entry, cwd = process.cwd()) {
117
+ const absolute = resolve(cwd, entry);
118
+ if (!existsSync(absolute)) {
119
+ throw new BuildError(`stackbox-cms build: site entry not found: ${absolute}`);
120
+ }
121
+ ensureTsxRegistered();
122
+ const mod = (await import(pathToFileURL(absolute).href));
123
+ const candidate = isSite(mod.default)
124
+ ? mod.default
125
+ : isSite(mod.site)
126
+ ? mod.site
127
+ : null;
128
+ if (!candidate) {
129
+ throw new BuildError(`stackbox-cms build: ${absolute} must default-export a site from createSite()`);
130
+ }
131
+ return candidate;
132
+ }
133
+ export async function runBuildCli(argv, cwd = process.cwd()) {
134
+ const args = parseBuildCliArgs(argv);
135
+ const entry = args.site
136
+ ? resolve(cwd, args.site)
137
+ : defaultSiteEntry(cwd);
138
+ const site = await loadSiteFromEntry(entry, cwd);
139
+ return build({
140
+ site,
141
+ outDir: args.outDir ? resolve(cwd, args.outDir) : resolve(cwd, "dist"),
142
+ publicDir: args.publicDir ? resolve(cwd, args.publicDir) : undefined,
143
+ });
144
+ }
145
+ //# sourceMappingURL=build.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build.js","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import type { Stackbox as SB } from "./types.js";
2
+ export declare const DEFAULT_CACHE_TTL_MS: number;
3
+ export declare const MAX_CACHE_TTL_MS: number;
4
+ export declare function createMemoryCache(): SB.CacheAdapter;
5
+ export declare function isCacheConfig(value: unknown): value is SB.CacheConfig;
6
+ export declare function collectPageCacheConfigs(page: SB.SitePage, siteConfig: SB.SiteConfig): (SB.CacheConfig | undefined)[];
7
+ export declare function isPageCacheDisabled(configs: readonly (SB.CacheConfig | undefined)[]): boolean;
8
+ export declare function resolvePageCacheTtlMs(configs: readonly (SB.CacheConfig | undefined)[]): number;
9
+ export declare function buildPageCacheKey(pathname: string, search: string): string;
10
+ export declare function cacheControlFresh(ttlMs: number): string;
11
+ export declare function cacheControlStale(ttlMs: number): string;
12
+ export declare function isCacheEntryFresh(entry: SB.CacheEntry, now?: number): boolean;
13
+ export declare function isSuccessfulRender(html: string): boolean;
14
+ //# sourceMappingURL=cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,YAAY,CAAC;AAEjD,eAAO,MAAM,oBAAoB,QAAsB,CAAC;AACxD,eAAO,MAAM,gBAAgB,QAA2B,CAAC;AAEzD,wBAAgB,iBAAiB,IAAI,EAAE,CAAC,YAAY,CAUnD;AAWD,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,EAAE,CAAC,WAAW,CAKrE;AAeD,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,EAAE,CAAC,QAAQ,EACjB,UAAU,EAAE,EAAE,CAAC,UAAU,GACxB,CAAC,EAAE,CAAC,WAAW,GAAG,SAAS,CAAC,EAAE,CAkBhC;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,SAAS,CAAC,EAAE,CAAC,WAAW,GAAG,SAAS,CAAC,EAAE,GAAG,OAAO,CAE7F;AAED,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,SAAS,CAAC,EAAE,CAAC,WAAW,GAAG,SAAS,CAAC,EAAE,GAC/C,MAAM,CAqBR;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAE1E;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAGvD;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAGvD;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,EAAE,CAAC,UAAU,EAAE,GAAG,SAAa,GAAG,OAAO,CAEjF;AAID,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAExD"}