@getvitops/create 0.9.0-alpha.0 → 0.9.0-alpha.2
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 +65 -0
- package/package.json +2 -2
- package/templates/emdash/.github/workflows/deploy-prod.yml +1 -1
- package/templates/emdash/AGENTS.md +149 -317
- package/templates/emdash/README.md +2 -2
- package/templates/emdash/_gitignore +13 -2
- package/templates/emdash/astro.config.mjs +2 -0
- package/templates/emdash/node_modules/.vitest-cache/CACHEDIR.TAG +3 -0
- package/templates/emdash/node_modules/.vitest-cache/_metadata.json +3 -0
- package/templates/emdash/package.json +6 -4
- package/templates/emdash/public/_headers +1 -1
- package/templates/emdash/scripts/pull-content.mjs +1 -1
- package/templates/emdash/seed/seed.json +79 -15
- package/templates/emdash/site.json +59 -2
- package/templates/emdash/src/layouts/Layout.astro +5 -0
- package/templates/emdash/src/pages/[...slug].astro +1 -1
- package/templates/emdash/src/pages/api/track.ts +2 -2
- package/templates/emdash/src/pages/index.astro +2 -2
- package/templates/static/AGENTS.md +7 -0
- package/templates/static/_gitignore +10 -0
- package/templates/static/package.json +3 -1
- package/templates/emdash/.agents/skills/custom-blocks/SKILL.md +0 -104
|
@@ -3,347 +3,179 @@ prerendered, the Worker serving the admin and API) styled by the Vitops design s
|
|
|
3
3
|
|
|
4
4
|
## Ground rules
|
|
5
5
|
|
|
6
|
-
- `site.json` is the single source of truth
|
|
7
|
-
|
|
8
|
-
`
|
|
9
|
-
|
|
10
|
-
and
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
`
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
`vpr build`) then fails the build if any prerendered page is not a real HTML page
|
|
103
|
-
with a title. Run `vpr prepare-local-db` by hand before a first local `vpr build`.
|
|
104
|
-
It only writes the disposable `.wrangler/state`; the dev administrator it creates
|
|
105
|
-
is never deployed.
|
|
106
|
-
- **Freshness is a rebuild.** Production is the truth for content, and a page
|
|
107
|
-
reflects it as of the last build. Publishing in the admin therefore has to
|
|
108
|
-
trigger one: the deploy workflows listen for a `repository_dispatch` event of
|
|
109
|
-
type `emdash-publish` (see "Publish-triggered rebuilds" below).
|
|
110
|
-
- **Drafts and previews.** A prerendered page cannot show an unpublished
|
|
111
|
-
draft, so an admin "Preview" link shows the last published build. If previews
|
|
112
|
-
matter, add a dedicated on-demand route for them rather than removing
|
|
113
|
-
`prerender` from the public ones.
|
|
114
|
-
- **Dynamic parts are server islands.** Anything that must be fresh per request
|
|
115
|
-
goes in its own component rendered with `server:defer` inside the prerendered
|
|
116
|
-
page; the surrounding HTML stays static. Keep islands rare — each is a Worker
|
|
117
|
-
request after load.
|
|
118
|
-
- **Unknown URLs.** There is no `Astro.rewrite('/404')`: a slug not listed by
|
|
119
|
-
`getStaticPaths()` has no route, so Astro serves the prerendered `404.html`
|
|
120
|
-
with a real 404 status. Rewriting an on-demand route to a prerendered one
|
|
121
|
-
fails (500), so don't reintroduce it.
|
|
122
|
-
- **The contact form is the one on-demand route.** The `vitops.form` block posts a plain
|
|
123
|
-
`<form method="post">` to `src/pages/api/track.ts` (`prerender = false`, built with
|
|
124
|
-
`createConversionRoute()`), which also answers the conversion beacons. The block works with
|
|
125
|
-
no JavaScript: the route answers a form post with a 303 back to the page plus `?sent=1#form-sent`
|
|
126
|
-
or `?error=1#form-error`, and the block's two notices are revealed by that fragment. Keep one
|
|
127
|
-
form per page. Where submissions go: `site.notifications.email` (or the primary location's
|
|
128
|
-
`email`) and `site.crm` when set. **With neither, the route refuses the post** rather than
|
|
129
|
-
accept a message nobody receives, so set `site.notifications.email` before launch and run
|
|
130
|
-
`vitops email setup` for its sender domain. A failed CRM write never fails the request; it is
|
|
131
|
-
stated in the notification email, which is the durability floor.
|
|
132
|
-
- **Spam protection**: a honeypot field, the route's origin / size / per-IP rate checks, and
|
|
133
|
-
Cloudflare Turnstile once `vitops setup turnstile` has put the public key in
|
|
134
|
-
`site.security.turnstile.siteKey` and saved the Worker secret `VITOPS_TURNSTILE_SECRET`
|
|
135
|
-
(never in `site.json`). With a key set the route fails closed if the secret is missing.
|
|
136
|
-
`TURNSTILE_REQUIRED` in the route (default `true`) makes a token-less post a refusal, which
|
|
137
|
-
means the form needs JavaScript; set it `false` to keep a no-JS submit working and accept
|
|
138
|
-
that a bot can then skip the token. Turnstile is category `necessary` (it protects the form
|
|
139
|
-
being sent), loads only when a visitor first uses the form, and its origin is allowed in the
|
|
140
|
-
Report-Only CSP in `public/_headers`.
|
|
141
|
-
- **Privacy policy inventory.** `vitops legal` derives "what personal information we collect"
|
|
142
|
-
from `site.templates` entries of type `form`, not from the editor's block, so a form added in
|
|
143
|
-
the admin is invisible to it. When you add or change form fields, mirror them in a
|
|
144
|
-
`site.templates` form entry in `site.json`, or the generated policy under-reports.
|
|
145
|
-
- A field named `phone` submits as `tel` (the route reserves `phone` and `event`).
|
|
146
|
-
- The per-IP rate limit is per Worker isolate; add a KV-backed `RateLimitStore` if you need a
|
|
147
|
-
fleet-wide one.
|
|
148
|
-
- The Worker cron trigger in `wrangler.jsonc` drives scheduled publishing —
|
|
149
|
-
don't remove it.
|
|
150
|
-
- Hosting (adapter + EmDash database/storage) comes from `vitopsHosting()`
|
|
151
|
-
(`@getvitops/emdash`) in astro.config.mjs — never hard-code an adapter or
|
|
152
|
-
d1/r2/sqlite there. Switch targets via `vitopsHosting({ target })` or the
|
|
153
|
-
`HOSTING` env var (see README "Hosting"); the `node` target needs
|
|
154
|
-
`@astrojs/node` + `better-sqlite3` installed first.
|
|
155
|
-
- **`SETUP.html` is the client-onboarding checklist.** When working it via the
|
|
156
|
-
`.agents/skills/setup/` skill, tick a box only for a step you actually
|
|
157
|
-
completed — never on the operator's behalf for a step marked human-only
|
|
158
|
-
(the Cloudflare API token, the domain cutover, publishing legal documents).
|
|
6
|
+
- `site.json` is the single source of truth: organization + site facts, and the design system
|
|
7
|
+
under `designSystem.themes.default`. Never edit the generated stylesheet
|
|
8
|
+
(`src/styles/tailwind.css` with the default `tailwind` format; `css.format` in
|
|
9
|
+
`astro.config.mjs` picks it). It is rebuilt from `site.json` on every dev/build and gitignored.
|
|
10
|
+
Only `src/styles/tailwind.css` and `tokens.json` are ignored: put your own CSS in a separate file
|
|
11
|
+
(for example `src/site.css`, imported from the layout) and commit it.
|
|
12
|
+
- Content lives in the EmDash database, not the repo. Read it with `getEmDashCollection` /
|
|
13
|
+
`getEmDashEntry` from `emdash`; render Portable Text with `<PortableText />` from `emdash/ui`
|
|
14
|
+
(the `vitops_*` blocks from `@getvitops/emdash` are merged in).
|
|
15
|
+
- **Where truth lives:** content (entries, media, menus) is **production's**, edited there; code,
|
|
16
|
+
`site.json` and schema are **git's** (`dev` → `main`). The dev database is a disposable copy
|
|
17
|
+
refreshed from prod, never promoted back. See `.agents/skills/content-and-deploys/SKILL.md`.
|
|
18
|
+
- `seed/seed.json` applies only to an empty database. To change a running site's content model, use
|
|
19
|
+
the admin (`/_emdash/admin`) or its MCP server (`/_emdash/api/mcp`).
|
|
20
|
+
- Hosting comes from `vitopsHosting()` in `astro.config.mjs` — never hard-code an adapter or
|
|
21
|
+
d1/r2/sqlite. Switch with `vitopsHosting({ target })` or the `HOSTING` env var.
|
|
22
|
+
- Keep the Worker cron trigger in `wrangler.jsonc`: it publishes content scheduled in the admin.
|
|
23
|
+
- `src/assets/placeholder/` is wireframe stand-in imagery; delete it once real assets land.
|
|
24
|
+
- Prefer framework classes (utilities, patterns, type roles like `font-heading`) over bespoke CSS.
|
|
25
|
+
`vp exec vitops agents` appends the full class reference to this file.
|
|
26
|
+
- `SETUP.html` is the onboarding checklist (`.agents/skills/setup/`). Tick a box only for a step you
|
|
27
|
+
completed, never for a human-only one (the Cloudflare API token, domain cutover, legal documents).
|
|
28
|
+
|
|
29
|
+
## Building pages
|
|
30
|
+
|
|
31
|
+
- The editor's insert menu offers the `vitops_*` blocks. `vp exec vitops docs blocks` lists them
|
|
32
|
+
with every field; `vp exec vitops docs components` lists every pattern, which tiers provide it
|
|
33
|
+
and which block renders it.
|
|
34
|
+
- **Nesting.** A block type with a `portableText` field, such as `vitops_section`, holds other
|
|
35
|
+
blocks. A `vitops_*` block holds only scalar fields and repeaters (cards, gallery images,
|
|
36
|
+
accordion items, slides), so it can't. EmDash's built-in `columns` doesn't render `vitops_*`
|
|
37
|
+
children. For layout, nest in a section, use a `blocks` field, or hand-write the template with
|
|
38
|
+
`@getvitops/astro` components (`Subgrid`, `Cards`, `Carousel`, …).
|
|
39
|
+
- **Section, block or widget area?** Copy-and-edit content is an EmDash Section. The same content
|
|
40
|
+
everywhere (announcement bar, pre-footer call to action, sidebar, footer columns) is a **widget
|
|
41
|
+
area**: define it under Widgets in the admin and render it with `<VitopsWidgetArea name="…" />`
|
|
42
|
+
from `@getvitops/emdash/astro` in the layout, or in page content with the `vitops_widgetArea`
|
|
43
|
+
block. Page-specific content is blocks. An area can't hold another area, and prerendered pages
|
|
44
|
+
show an edit after the next deploy (like menus).
|
|
45
|
+
- **Escape hatches on every block:** `theme` (a backdrop from the site's colour roles, `surface` or
|
|
46
|
+
`brand-primary:solid`, with the readable text for it picked by the design system, so contrast is
|
|
47
|
+
not the editor's judgement), `anchor` (the `id`, for `#links`), `className` (extra classes; under
|
|
48
|
+
the tailwind format only ones the site already uses) and `style` (CSS custom properties only,
|
|
49
|
+
e.g. `--p-card: 2rem`; anything else is dropped). The same backdrop is available on a widget area
|
|
50
|
+
(`<VitopsWidgetArea theme="…">`) and on your own markup (`themeAttrs('surface')` from
|
|
51
|
+
`@getvitops/emdash/astro`, spread on the layout's `<body>`). How a role looks (flat, raised,
|
|
52
|
+
glass) is the site's own role styling in `site.json`. Set `--header-offset` to the sticky
|
|
53
|
+
header's height so anchors clear it.
|
|
54
|
+
- Override a block per site with `<PortableText components={{ type: { 'vitops_cards': MyCards } }} />`;
|
|
55
|
+
hide one with `vitopsEmdash({ blocks: { exclude: [...] } })`.
|
|
56
|
+
- **One `<h1>` per page.** The template's page routes render the title as an `<h1>`, and a hero's
|
|
57
|
+
heading level defaults to `h1`, so set a hero's level to `2` on those pages (or let the hero own
|
|
58
|
+
the `h1`). Use `p` for a display heading that is not part of the outline. `vitops emdash build`
|
|
59
|
+
warns on any page without exactly one `<h1>`.
|
|
60
|
+
- Labels: `badge` is static (status, count, category); `tag` may be editable or dismissable. Don't
|
|
61
|
+
invent `chip` or `pill`. The button class is `btn`.
|
|
62
|
+
- **Before adding a block type** or a `src/components/blocks/*` renderer, read
|
|
63
|
+
`.agents/skills/custom-blocks/SKILL.md`. Types can't be renamed or deleted once pages use them.
|
|
64
|
+
|
|
65
|
+
## Layout foundations — the answer, not a starting point
|
|
66
|
+
|
|
67
|
+
| About to write… | Write instead |
|
|
68
|
+
| ------------------------------------------------------- | --------------- |
|
|
69
|
+
| a `wrap`/`wrapper`/`container`/`inner` class | **`centered`** |
|
|
70
|
+
| `max-width` + `margin-inline: auto` | **`centered`** |
|
|
71
|
+
| `padding-block` on a section | **`region`** |
|
|
72
|
+
| margins between headings/paragraphs/lists | **`rhythm`** |
|
|
73
|
+
| `display: grid` + `grid-template-columns: repeat(n, …)` | **`subgrid`** |
|
|
74
|
+
| `repeat(auto-fit, minmax(…))` | **`grid-auto`** |
|
|
75
|
+
| `display: flex` + `gap` for a row of controls | **`cluster`** |
|
|
76
|
+
|
|
77
|
+
`centered` is a grid of _named tracks_: a child widens itself with `breakout`/`spotlight`/
|
|
78
|
+
`fullbleed` without the parent knowing, which a bespoke `.wrap` can't do.
|
|
79
|
+
|
|
80
|
+
- **More than one card means `subgrid`**, so headings and CTAs align across the set:
|
|
81
|
+
`<ul class="subgrid subgrid-cols-3" role="list">` with `<li class="card subgrid-card">`, or
|
|
82
|
+
`<Subgrid />` (renders the slot verbatim; author the `<li>`s).
|
|
83
|
+
- **A card that is a link:** `stretched-link` inside it is zero-JS but kills text selection;
|
|
84
|
+
`<Cards>` keeps selection and falls back to the same link with no JS. Never both. Never
|
|
85
|
+
`<li><a class="card">`: the `li` is the grid item, so the tranches don't align.
|
|
86
|
+
- `vpr lint:design` reports all of these and the pre-commit hook fails on them. Fix the class, not
|
|
87
|
+
the CSS.
|
|
88
|
+
|
|
89
|
+
## Rendering and the contact form
|
|
90
|
+
|
|
91
|
+
- Every public page is **prerendered**; the Worker handles `/_emdash/*` and `src/pages/api/track.ts`.
|
|
92
|
+
An admin publish goes live after a rebuild. Run `vpr prepare-local-db` before a first local
|
|
93
|
+
`vpr build`. Details, previews and server islands: `.agents/skills/content-and-deploys/SKILL.md`.
|
|
94
|
+
- The contact form posts to `/api/track`. Set `site.notifications.email` before launch or the route
|
|
95
|
+
refuses posts, and mirror form fields in `site.templates`. Details:
|
|
96
|
+
`.agents/skills/contact-form/SKILL.md`.
|
|
97
|
+
- `public/_headers` ships a Report-Only CSP. Read `.agents/skills/csp/SKILL.md` before enforcing it
|
|
98
|
+
or adding embeds.
|
|
99
|
+
- `vitops audit` needs optional packages and a browser, checked before it starts a server:
|
|
100
|
+
`vp add -D playwright @axe-core/playwright lighthouse && vp exec playwright install chromium`.
|
|
101
|
+
Its page list is the built sitemap, then the seed's menus.
|
|
159
102
|
|
|
160
103
|
## Branch model
|
|
161
104
|
|
|
162
|
-
- Work on **`dev
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
-
|
|
193
|
-
|
|
194
|
-
- **
|
|
195
|
-
`
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
`.
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
## Labels: badge and tag
|
|
204
|
-
|
|
205
|
-
Two label patterns, both enabled in `site.json`'s `patterns.items`: **`badge`** is a
|
|
206
|
-
static label (a status, count or category) and **`tag`** is a label that may be
|
|
207
|
-
static _or_ dynamic (editable, dismissable, in a `tag-list`). Don't invent a third
|
|
208
|
-
name — `chip` and `pill` are not part of this vocabulary. The button pattern's class
|
|
209
|
-
is `btn` (`<a class="btn">` is styled, not just a bare `<button>`).
|
|
210
|
-
|
|
211
|
-
## Custom blocks
|
|
212
|
-
|
|
213
|
-
Before adding a block type, a `src/components/blocks/*` renderer, or anything an
|
|
214
|
-
editor places on a page, read `.agents/skills/custom-blocks/SKILL.md`: compose the
|
|
215
|
-
existing `vitops.*` blocks first, extend one second, go site-local third, upstream
|
|
216
|
-
last. Block types can't be renamed or deleted once pages use them, so check the
|
|
217
|
-
framework's pattern names before choosing one.
|
|
218
|
-
|
|
219
|
-
## Publish-triggered rebuilds
|
|
220
|
-
|
|
221
|
-
Pages are static, so a publish in the admin goes live only after a rebuild.
|
|
222
|
-
`.github/workflows/deploy-prod.yml` also runs on a `repository_dispatch` event of
|
|
223
|
-
type `emdash-publish` (it pins `ref` to `main`, because a dispatch runs from the
|
|
224
|
-
default branch, `dev`). Wire an EmDash plugin hook to send it:
|
|
225
|
-
|
|
226
|
-
```jsonc
|
|
227
|
-
// emdash-plugin.jsonc
|
|
228
|
-
"capabilities": ["content:read", "network:request"],
|
|
229
|
-
"allowedHosts": ["api.github.com"]
|
|
230
|
-
```
|
|
231
|
-
|
|
232
|
-
```ts
|
|
233
|
-
// src/plugin.ts
|
|
234
|
-
hooks: {
|
|
235
|
-
'content:afterPublish': async (_event, ctx) => {
|
|
236
|
-
const token = await ctx.settings.get<string>('githubToken'); // an encrypted plugin setting
|
|
237
|
-
if (!token || !ctx.http) return;
|
|
238
|
-
await ctx.http.fetch(`https://api.github.com/repos/${OWNER}/${REPO}/dispatches`, {
|
|
239
|
-
method: 'POST',
|
|
240
|
-
headers: { authorization: `Bearer ${token}`, accept: 'application/vnd.github+json' },
|
|
241
|
-
body: JSON.stringify({ event_type: 'emdash-publish' }),
|
|
242
|
-
});
|
|
243
|
-
},
|
|
244
|
-
}
|
|
245
|
-
```
|
|
246
|
-
|
|
247
|
-
The token is a fine-grained GitHub token with `Contents: write` on this repo only,
|
|
248
|
-
stored as the plugin's encrypted setting (a sandboxed plugin can't read Worker
|
|
249
|
-
environment variables), never in the repo. The hook name, capabilities and
|
|
250
|
-
`ctx.http` / `ctx.settings` calls follow EmDash's plugin reference but this exact
|
|
251
|
-
plugin has not been run against a live site, so treat it as a starting point. Two
|
|
252
|
-
costs to know: the run goes through the `production` environment, so it **waits for
|
|
253
|
-
the reviewers' approval** like any prod deploy (relax that rule if editors' publishes
|
|
254
|
-
should go live unattended), and bursts of publishes queue behind the
|
|
255
|
-
`deploy-prod` concurrency group rather than cancelling each other. Without the hook,
|
|
256
|
-
a manual run of **Deploy prod** (or a push to `main`) does the same job.
|
|
257
|
-
|
|
258
|
-
## Content Security Policy
|
|
259
|
-
|
|
260
|
-
`public/_headers` ships a **`Content-Security-Policy-Report-Only`** header: it logs
|
|
261
|
-
violations to the browser console and blocks nothing, so it can't break a page. Its
|
|
262
|
-
`script-src` is `'self'` plus the SHA-256 of each inline script `<Head />` emits (the
|
|
263
|
-
colour-scheme bootstrap and the consent-gate stub) — no `'unsafe-inline'`. Hashes,
|
|
264
|
-
not nonces, because prerendered pages have no per-request response to put a nonce in.
|
|
265
|
-
`contentSecurityPolicy()` and `inlineScriptHashes()` from `@getvitops/astro` produce
|
|
266
|
-
the same strings (`templates.test.ts` in the toolchain keeps the file in step).
|
|
267
|
-
|
|
268
|
-
Know what it does not cover before you flip it to enforcing (rename the header to
|
|
269
|
-
`Content-Security-Policy`):
|
|
270
|
-
|
|
271
|
-
- **`_headers` applies to static assets only.** Worker-rendered responses — the EmDash
|
|
272
|
-
admin (`/_emdash/*`), its API, anything left on-demand — don't get it. Setting a
|
|
273
|
-
header there means doing it in code (e.g. Astro middleware calling
|
|
274
|
-
`contentSecurityPolicy()`), and the admin's own inline scripts and editor HTML
|
|
275
|
-
would need allowing; leave the admin out of an enforced policy.
|
|
276
|
-
- **Editor-authored HTML blocks are the hard limit.** Any block that lets an editor
|
|
277
|
-
paste raw HTML or a `<script>` (an embed, a tracking snippet) puts arbitrary inline
|
|
278
|
-
script on the page, which a hash policy cannot name ahead of time. Enforcing a strict
|
|
279
|
-
policy means removing such blocks or moving embeds to a consent-gated
|
|
280
|
-
`data-consent` tag whose origin you allow explicitly.
|
|
281
|
-
- **Third-party tags** the consent gate activates need their origins in `script-src`
|
|
282
|
-
/ `connect-src`; anything with `frame` needs `frame-src`.
|
|
283
|
-
- **A server island** (`server:defer`) adds an inline loader script and a request to
|
|
284
|
-
the Worker; expect a report for the loader until you hash it.
|
|
285
|
-
|
|
286
|
-
Run the site, browse the pages you ship with the console open, fix or allow what is
|
|
287
|
-
reported, and only then enforce.
|
|
288
|
-
|
|
289
|
-
## EmDash agent skills
|
|
290
|
-
|
|
291
|
-
The EmDash skills (`building-emdash-site`, `creating-plugins`, `emdash-cli`,
|
|
292
|
-
`upgrading-emdash` and the two WordPress porting skills) are installed per
|
|
293
|
-
project, not globally, so they match the `emdash` version this repo pins. If
|
|
294
|
-
`.agents/skills/building-emdash-site/` is missing, install them (the setup guide
|
|
295
|
-
and `SETUP.html` have the full step):
|
|
296
|
-
|
|
297
|
-
```bash
|
|
298
|
-
vp dlx skills add emdash-cms/skills --skill '*' -y
|
|
299
|
-
```
|
|
300
|
-
|
|
301
|
-
Refresh them with `vp dlx skills update` after bumping `emdash` — the
|
|
302
|
-
`upgrading-emdash` skill covers the rest of the upgrade.
|
|
105
|
+
- Work on **`dev`**; every push auto-deploys the isolated `-dev` worker.
|
|
106
|
+
- **`main`** is production and protected: never push or merge to it. It ships only through the
|
|
107
|
+
`promote` skill (`.agents/skills/promote/`), and only when the user explicitly asks.
|
|
108
|
+
- Sample legal pages (`/terms`, `/privacy`) are deliberately short, unfinished placeholders; don't
|
|
109
|
+
"improve" them into something that reads as a finished policy. To replace them, fill in
|
|
110
|
+
`site.json`'s `organization`/`site.legal`, run
|
|
111
|
+
`vp exec vitops legal --format portable-text --out ./legal`, and paste each document into the page
|
|
112
|
+
in the admin. It still needs review before launch.
|
|
113
|
+
|
|
114
|
+
## EmDash 1.x behaviours
|
|
115
|
+
|
|
116
|
+
- Passkeys are bound to the hostname they were created on. After a domain cutover use **Settings →
|
|
117
|
+
General → Change domain**, then **Continue on**.
|
|
118
|
+
- Link entries by `entry.data.slug`, never `entry.id` (multilingual sites prefix `id` with a locale).
|
|
119
|
+
- Submit `/sitemap.xml` to search engines, not a per-collection sitemap.
|
|
120
|
+
- Clear a stuck D1 migration lock with `vp exec emdash migrate --take-over-lock <id>` (needs
|
|
121
|
+
`--expected-target-fingerprint` non-interactively).
|
|
122
|
+
|
|
123
|
+
## Improving the toolkit
|
|
124
|
+
|
|
125
|
+
You are also a source of feedback for `@getvitops/*`. When you struggle with a framework built-in,
|
|
126
|
+
write a workaround, add bespoke CSS or a site-local block, or find a doc wrong or missing, and the
|
|
127
|
+
need is **not** unique to this site, record it in `UPSTREAM.md` at the repo root (create it if
|
|
128
|
+
absent). One entry each, with: what you tried, what failed or was missing, the workaround (file
|
|
129
|
+
path), the change you'd propose, and the package (`@getvitops/emdash`, `astro`, `core`, `cli`,
|
|
130
|
+
`create`, or docs). Mention new entries in your end-of-task summary. Don't log choices that are
|
|
131
|
+
specific to this site, and never patch `node_modules`.
|
|
132
|
+
|
|
133
|
+
## Docs and skills
|
|
134
|
+
|
|
135
|
+
- `.mcp.json` preconfigures the Astro and EmDash docs MCP servers; search them before relying on
|
|
136
|
+
pre-trained knowledge.
|
|
137
|
+
- **Skills.** `@getvitops/skills` ships `custom-blocks`, `content-and-deploys`, `contact-form` and
|
|
138
|
+
`csp`. `skills experimental_sync` (run by `prepare`, so by `vp install`) links them into
|
|
139
|
+
`.agents/skills/` and `.claude/skills/`; they are gitignored and update with `vp update`. A skill
|
|
140
|
+
you write yourself with the same name is never replaced. `promote` and `setup` are this site's own.
|
|
141
|
+
- The EmDash skills (`building-emdash-site`, `creating-plugins`, `emdash-cli`, `upgrading-emdash`,
|
|
142
|
+
two WordPress porting skills) are installed per project to match the pinned `emdash`. If
|
|
143
|
+
`.agents/skills/building-emdash-site/` is missing: `vp dlx skills add emdash-cms/skills --skill '*' -y`.
|
|
144
|
+
Refresh with `vp dlx skills update` after bumping `emdash`.
|
|
303
145
|
|
|
304
146
|
## Commands
|
|
305
147
|
|
|
306
|
-
This is a **Vite+** project: use `vp` (and
|
|
307
|
-
|
|
148
|
+
This is a **Vite+** project: use `vp` (and `vpr`), not `pnpm`/`npm`/`npx`. Scripts that wrap Astro
|
|
149
|
+
go through `vpr` (`vpr dev`, `vpr build`, `vpr preview`), because `vp dev`/`vp build` skip Astro.
|
|
150
|
+
`vpr dev` returns immediately (the server runs in the background): check it with
|
|
151
|
+
`vp exec astro dev status`, read `vp exec astro dev logs --follow`, stop with
|
|
152
|
+
`vp exec astro dev stop`; the site is at <http://localhost:4321>.
|
|
308
153
|
|
|
309
154
|
| To… | Run |
|
|
310
155
|
| ---------------------------------------- | ------------------------------------------------------- |
|
|
311
156
|
| install dependencies | `vp install` |
|
|
312
157
|
| add / remove / update a package | `vp add <pkg>` · `vp remove <pkg>` · `vp update` |
|
|
313
|
-
| run a `package.json` script | `vpr <script>`
|
|
158
|
+
| run a `package.json` script | `vpr <script>` |
|
|
314
159
|
| run a locally installed binary | `vp exec <bin>` (e.g. `vp exec vitops docs components`) |
|
|
315
160
|
| run a one-off package without installing | `vp dlx <pkg>` |
|
|
316
161
|
| format + lint + typecheck (files) | `vp check` |
|
|
317
162
|
| typecheck Astro (`.astro` files) | `vpr typecheck` |
|
|
318
|
-
|
|
319
|
-
**Scripts that wrap Astro must go through `vpr`.** `vp dev` / `vp build` run Vite
|
|
320
|
-
directly and skip Astro, so use `vpr dev`, `vpr build`, `vpr preview`.
|
|
321
|
-
|
|
322
|
-
**`vpr dev` returns immediately.** `astro dev` now starts the server in the
|
|
323
|
-
background. Check it with `vp exec astro dev status`, read its output with
|
|
324
|
-
`vp exec astro dev logs --follow`, and stop it with `vp exec astro dev stop`; the
|
|
325
|
-
site is at <http://localhost:4321>. Don't wait on it as if it were a foreground
|
|
326
|
-
process.
|
|
163
|
+
| design-lint the markup | `vpr lint:design` |
|
|
327
164
|
|
|
328
165
|
| Script | What it does |
|
|
329
166
|
| ----------------------------------- | ------------------------------------------------------------------------------------------- |
|
|
330
167
|
| `vpr dev` | local dev with local D1/R2 emulation (no Cloudflare account) |
|
|
331
168
|
| `vpr deploy` | pull production content, build, `wrangler deploy` prod (provisions D1/R2 on first run) |
|
|
332
169
|
| `vpr deploy:dev` | same, for the isolated dev worker (needs `DEV_D1_ID`) |
|
|
333
|
-
| `vpr pull-content` | just the content pull
|
|
170
|
+
| `vpr pull-content` | just the content pull |
|
|
334
171
|
| `vpr prepare-local-db` | migrate and seed the local D1 the build prerenders from |
|
|
335
172
|
| `vpr init:github` | one-time: create + push the GitHub repo (main + dev, dev default). Only when the user asks. |
|
|
336
173
|
| `vpr init:hosting` | one-time: provision hosting (dev D1/R2 + repo secrets). Only when the user asks. |
|
|
337
174
|
| `vp exec vitops validate site.json` | schema-check the site config |
|
|
338
175
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
running the **promote** workflow. `vp` has no `vp change` / `vp pm change` command
|
|
342
|
-
(checked against vp 1.5.0); for a _package_ it does have `vp pm version`,
|
|
343
|
-
`vp pm publish` and `vp pm stage`.
|
|
176
|
+
Shipping is not a package release: no changeset or version bump. Commit to `dev`, then promote.
|
|
177
|
+
(`vp` has no `vp change` command.)
|
|
344
178
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
`
|
|
348
|
-
`name`, D1 `database_name` and R2 `bucket_name` are separate and still say
|
|
349
|
-
`my-emdash-site` — rename them to match before the first deploy.
|
|
179
|
+
`vp create` names the package after the target directory (pass `--directory <client-slug>`).
|
|
180
|
+
`wrangler.jsonc`'s `name`, D1 `database_name` and R2 `bucket_name` are separate and still say
|
|
181
|
+
`my-emdash-site`; rename them to match before the first deploy.
|
|
@@ -51,7 +51,7 @@ get the prerendered `src/pages/404.astro` with a real 404 status.
|
|
|
51
51
|
|
|
52
52
|
**Pages are prerendered.** `vpr build` writes static HTML for every published
|
|
53
53
|
`pages` entry, so an edit in the admin goes live on the next rebuild rather than
|
|
54
|
-
per request — see
|
|
54
|
+
per request — see `.agents/skills/content-and-deploys` for how production content
|
|
55
55
|
reaches the build (`scripts/pull-content.mjs`) and for the publish-triggered rebuild.
|
|
56
56
|
In `vpr dev` the pages read your local database, so edits appear straight away.
|
|
57
57
|
|
|
@@ -189,7 +189,7 @@ vpr build:css # regenerate the CSS manually
|
|
|
189
189
|
| `src/pages/index.astro` | Home — prerendered from the `home` page entry |
|
|
190
190
|
| `src/pages/[...slug].astro` | Prerendered once per published `pages` entry (`getStaticPaths`) |
|
|
191
191
|
| `src/pages/404.astro` | Prerendered not-found page, served with a 404 status for any unmatched URL |
|
|
192
|
-
| `public/_headers` | Report-Only CSP for the static pages (see
|
|
192
|
+
| `public/_headers` | Report-Only CSP for the static pages (see `.agents/skills/csp`) |
|
|
193
193
|
| `scripts/pull-content.mjs` | Loads production's D1 into the local build DB before the prerender (`deploy`/`deploy:dev`) |
|
|
194
194
|
| `wrangler.jsonc` | Cloudflare bindings (D1/R2) + cron trigger |
|
|
195
195
|
| `scripts/build-dev-wrangler.mjs` | Rewrites the built wrangler config for the isolated dev worker |
|
|
@@ -18,8 +18,9 @@ worker-configuration.d.ts
|
|
|
18
18
|
# node hosting target: SQLite db + local uploads
|
|
19
19
|
data/
|
|
20
20
|
|
|
21
|
-
# generated by the vitops() integration on dev/build
|
|
22
|
-
src/styles/
|
|
21
|
+
# generated by the vitops() integration on dev/build (site CSS goes in src/site.css, not here)
|
|
22
|
+
src/styles/tailwind.css
|
|
23
|
+
src/styles/tokens.json
|
|
23
24
|
public/vitops/
|
|
24
25
|
|
|
25
26
|
# generated by `pnpm run sop` — styles SETUP.html for a file:// open
|
|
@@ -32,3 +33,13 @@ public/vitops/
|
|
|
32
33
|
|
|
33
34
|
# os / editor
|
|
34
35
|
.DS_Store
|
|
36
|
+
|
|
37
|
+
# agent skills linked from @getvitops/skills by `skills experimental_sync` (see skills-lock.json)
|
|
38
|
+
.agents/skills/custom-blocks
|
|
39
|
+
.agents/skills/content-and-deploys
|
|
40
|
+
.agents/skills/contact-form
|
|
41
|
+
.agents/skills/csp
|
|
42
|
+
.claude/skills/custom-blocks
|
|
43
|
+
.claude/skills/content-and-deploys
|
|
44
|
+
.claude/skills/contact-form
|
|
45
|
+
.claude/skills/csp
|
|
@@ -35,6 +35,8 @@ const brandColor = colors.palette?.[roleHue('brand-primary')]?.seed ?? '#2e9b73'
|
|
|
35
35
|
export default defineConfig({
|
|
36
36
|
output: 'server',
|
|
37
37
|
adapter,
|
|
38
|
+
// The seed's `pages` collection serves entries at `/{slug}` (urlPattern), and `home` lives at `/`.
|
|
39
|
+
redirects: { '/home': '/' },
|
|
38
40
|
image: {
|
|
39
41
|
layout: 'constrained',
|
|
40
42
|
responsiveStyles: true,
|