@getvitops/create 0.8.1 → 0.9.0-alpha.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 +82 -0
- package/package.json +2 -2
- package/templates/emdash/.agents/skills/custom-blocks/SKILL.md +104 -0
- package/templates/emdash/.agents/skills/setup/SKILL.md +4 -4
- package/templates/emdash/.claude/hooks/session-start.sh +4 -1
- package/templates/emdash/.github/workflows/deploy-prod.yml +12 -0
- package/templates/emdash/AGENTS.md +212 -28
- package/templates/emdash/README.md +49 -38
- package/templates/emdash/SETUP.html +45 -27
- package/templates/emdash/_gitignore +3 -0
- package/templates/emdash/astro.config.mjs +14 -1
- package/templates/emdash/package.json +7 -5
- package/templates/emdash/public/_headers +19 -4
- package/templates/emdash/scripts/pull-content.mjs +83 -0
- package/templates/emdash/seed/seed.json +126 -6
- package/templates/emdash/site.json +54 -1
- package/templates/emdash/src/cloudflare-workers.d.ts +9 -0
- package/templates/emdash/src/layouts/Layout.astro +22 -45
- package/templates/emdash/src/pages/404.astro +4 -3
- package/templates/emdash/src/pages/[...slug].astro +22 -12
- package/templates/emdash/src/pages/api/track.ts +158 -0
- package/templates/emdash/src/pages/index.astro +10 -9
- package/templates/static/.agents/skills/setup/SKILL.md +4 -4
- package/templates/static/AGENTS.md +68 -14
- package/templates/static/README.md +7 -7
- package/templates/static/SETUP.html +45 -27
- package/templates/static/public/_headers +16 -4
- package/templates/static/site.json +54 -1
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
An [EmDash CMS](https://docs.emdashcms.com) website on Cloudflare Workers,
|
|
4
4
|
styled by the [Vitops design system](https://www.npmjs.com/org/getvitops).
|
|
5
5
|
|
|
6
|
-
Scaffolded from `vp create @getvitops:emdash
|
|
6
|
+
Scaffolded from `vp create @getvitops:emdash --directory <client-slug>` (pass a directory: `vp create` derives the package name from it and invents a random slug otherwise).
|
|
7
7
|
|
|
8
8
|
## Stack
|
|
9
9
|
|
|
@@ -15,7 +15,7 @@ Scaffolded from `vp create @getvitops:emdash`.
|
|
|
15
15
|
`src/assets/logo.svg`, and serves the web-component runtime
|
|
16
16
|
- **`@getvitops/emdash`** — design-system Portable Text blocks in the editor
|
|
17
17
|
slash menu (hero, cards, accordion, carousel, gallery, marquee, banner, …);
|
|
18
|
-
`
|
|
18
|
+
`vp exec vitops docs blocks` lists every block and field
|
|
19
19
|
|
|
20
20
|
`src/assets/placeholder/` ships neutral wireframe images for layout work
|
|
21
21
|
before real imagery exists — delete once real assets land (separate from
|
|
@@ -24,8 +24,8 @@ before real imagery exists — delete once real assets land (separate from
|
|
|
24
24
|
## Local development
|
|
25
25
|
|
|
26
26
|
```bash
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
vp install
|
|
28
|
+
vpr dev
|
|
29
29
|
```
|
|
30
30
|
|
|
31
31
|
Once this is its own repo (not sitting inside the `getvitops/design-system`
|
|
@@ -36,22 +36,30 @@ honours that option at a project's actual root, and errors on it otherwise.
|
|
|
36
36
|
No Cloudflare account needed: `astro dev` runs the Worker locally with local
|
|
37
37
|
D1/R2 emulation (state in `.wrangler/`). On first boot EmDash runs its
|
|
38
38
|
migrations and applies the structure from `seed/seed.json` — a `pages`
|
|
39
|
-
collection,
|
|
39
|
+
collection, three menus (`primary`, `footer-sitemap`, `footer-legal`), and the site
|
|
40
|
+
settings.
|
|
40
41
|
|
|
41
42
|
- Site: <http://localhost:4321>
|
|
42
43
|
- Admin: <http://localhost:4321/_emdash/admin>
|
|
43
44
|
|
|
44
45
|
Open the admin and complete the setup wizard — it creates your first account,
|
|
45
46
|
and choosing to **include sample content** publishes the starter pages from
|
|
46
|
-
the seed: home, about, terms (`/terms`),
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
the seed: home, about, contact (`/contact`), terms (`/terms`), privacy policy
|
|
48
|
+
(`/privacy`) and cookie notice (`/cookies`) — every link in the seeded menus
|
|
49
|
+
resolves. Until then the home route shows a friendly empty state. Unknown paths
|
|
50
|
+
get the prerendered `src/pages/404.astro` with a real 404 status.
|
|
51
|
+
|
|
52
|
+
**Pages are prerendered.** `vpr build` writes static HTML for every published
|
|
53
|
+
`pages` entry, so an edit in the admin goes live on the next rebuild rather than
|
|
54
|
+
per request — see AGENTS.md "Rendering and freshness" for how production content
|
|
55
|
+
reaches the build (`scripts/pull-content.mjs`) and for the publish-triggered rebuild.
|
|
56
|
+
In `vpr dev` the pages read your local database, so edits appear straight away.
|
|
49
57
|
|
|
50
58
|
The two legal pages are placeholders. To generate real ones from what your site
|
|
51
59
|
actually does, fill in `site.json`'s `organization`/`site.legal` fields and run:
|
|
52
60
|
|
|
53
61
|
```sh
|
|
54
|
-
|
|
62
|
+
vp exec vitops legal --format portable-text --out ./legal
|
|
55
63
|
```
|
|
56
64
|
|
|
57
65
|
Then paste each document's blocks into the matching page in the admin. The
|
|
@@ -61,8 +69,8 @@ before you publish.
|
|
|
61
69
|
## Deploy
|
|
62
70
|
|
|
63
71
|
```bash
|
|
64
|
-
|
|
65
|
-
|
|
72
|
+
vp exec wrangler login
|
|
73
|
+
vpr deploy
|
|
66
74
|
```
|
|
67
75
|
|
|
68
76
|
Wrangler provisions the D1 database and R2 bucket named in `wrangler.jsonc`
|
|
@@ -92,7 +100,7 @@ One-time setup — see [Start a new EmDash site](https://docs.vitops.ca/guides/n
|
|
|
92
100
|
procedure, then work through the checklist:
|
|
93
101
|
|
|
94
102
|
```bash
|
|
95
|
-
|
|
103
|
+
vpr sop && open SETUP.html # or: xdg-open SETUP.html
|
|
96
104
|
```
|
|
97
105
|
|
|
98
106
|
It covers host choice, GitHub + branch policy, hosting resources, going live
|
|
@@ -102,7 +110,7 @@ command per step. An AI agent can work the same list autonomously via
|
|
|
102
110
|
`.agents/skills/setup/`; it stops at the steps that genuinely need a human
|
|
103
111
|
(the Cloudflare API token, the domain cutover, publishing legal documents).
|
|
104
112
|
|
|
105
|
-
`
|
|
113
|
+
`vpr deploy:dev` reproduces the dev deploy locally: it rewrites the
|
|
106
114
|
adapter-generated wrangler config (`scripts/build-dev-wrangler.mjs`) so the
|
|
107
115
|
dev worker never touches prod data — it hard-fails without `DEV_D1_ID`,
|
|
108
116
|
on purpose. Schema/content changes live in the EmDash database, not git, so
|
|
@@ -119,7 +127,7 @@ is a configurable dimension of the project, not a rewrite:
|
|
|
119
127
|
belong to this target.
|
|
120
128
|
- **`node`** — `@astrojs/node` (standalone) + SQLite file + local uploads,
|
|
121
129
|
for a VPS / docker-compose / k8s host. Not installed by default; to switch:
|
|
122
|
-
`
|
|
130
|
+
`vp add @astrojs/node better-sqlite3`, then `vitopsHosting({ target:
|
|
123
131
|
'node' })` (or set `HOSTING=node` — the env var wins). Run the built site
|
|
124
132
|
with `node ./dist/server/entry.mjs`; data lives in `./data/` (gitignored) —
|
|
125
133
|
persist it as a volume. Scheduled publishing runs in-process on Node, so
|
|
@@ -139,12 +147,12 @@ and media in the storage backend (bucket/directory copy).
|
|
|
139
147
|
agents answer from current documentation. Your running site also exposes its
|
|
140
148
|
own content MCP server at `/_emdash/api/mcp` (authenticated).
|
|
141
149
|
- **Skills** (`.agents/skills/`): `promote` dispatches the dev → main
|
|
142
|
-
promotion workflow. Run `
|
|
150
|
+
promotion workflow. Run `vp exec vitops agents` to also write the design-system
|
|
143
151
|
class reference into `AGENTS.md`.
|
|
144
152
|
- **EmDash skills** are installed per project so they match the pinned `emdash`
|
|
145
|
-
version: `
|
|
146
|
-
then commit `.agents/skills/` and `skills-lock.json`. Update with
|
|
147
|
-
`
|
|
153
|
+
version: `vp dlx skills add emdash-cms/skills --skill '*' -y`,
|
|
154
|
+
then commit `.agents/skills/`, the `.claude/skills/` symlinks and `skills-lock.json`. Update with
|
|
155
|
+
`vp dlx skills update` after an `emdash` bump.
|
|
148
156
|
- **Claude Code on the web**: the SessionStart hook
|
|
149
157
|
(`.claude/hooks/session-start.sh`) installs Node 24 (matching
|
|
150
158
|
`devEngines`), activates the pinned pnpm, and installs dependencies in the
|
|
@@ -164,27 +172,30 @@ routes stay unstyled by it.
|
|
|
164
172
|
Useful commands (via `@getvitops/cli`):
|
|
165
173
|
|
|
166
174
|
```bash
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
175
|
+
vp exec vitops validate site.json # check the config against the schema
|
|
176
|
+
vp exec vitops agents # write the design-system reference into AGENTS.md
|
|
177
|
+
vpr build:css # regenerate the CSS manually
|
|
170
178
|
```
|
|
171
179
|
|
|
172
180
|
## Project map
|
|
173
181
|
|
|
174
|
-
| Path | Purpose
|
|
175
|
-
| -------------------------------- |
|
|
176
|
-
| `site.json` | Org + site config; design tokens/patterns at `designSystem.themes.default` → generated CSS
|
|
177
|
-
| `seed/seed.json` | First-boot content model + starter content
|
|
178
|
-
| `src/worker.ts` | Worker entry (fetch + EmDash scheduled handler)
|
|
179
|
-
| `src/live.config.ts` | Astro live content collections wiring for EmDash
|
|
180
|
-
| `src/layouts/Layout.astro` | Site chrome: EmDash + Vitops head tags, nav from the CMS menu
|
|
181
|
-
| `src/pages/index.astro` | Home —
|
|
182
|
-
| `src/pages/[...slug].astro` |
|
|
183
|
-
| `src/pages/404.astro` |
|
|
184
|
-
| `
|
|
185
|
-
| `scripts/
|
|
186
|
-
| `
|
|
187
|
-
|
|
|
188
|
-
|
|
|
189
|
-
| `.
|
|
190
|
-
| `.
|
|
182
|
+
| Path | Purpose |
|
|
183
|
+
| -------------------------------- | -------------------------------------------------------------------------------------------------- |
|
|
184
|
+
| `site.json` | Org + site config; design tokens/patterns at `designSystem.themes.default` → generated CSS |
|
|
185
|
+
| `seed/seed.json` | First-boot content model + starter content |
|
|
186
|
+
| `src/worker.ts` | Worker entry (fetch + EmDash scheduled handler) |
|
|
187
|
+
| `src/live.config.ts` | Astro live content collections wiring for EmDash |
|
|
188
|
+
| `src/layouts/Layout.astro` | Site chrome: EmDash + Vitops head tags, nav from the CMS menu |
|
|
189
|
+
| `src/pages/index.astro` | Home — prerendered from the `home` page entry |
|
|
190
|
+
| `src/pages/[...slug].astro` | Prerendered once per published `pages` entry (`getStaticPaths`) |
|
|
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 AGENTS.md "Content Security Policy") |
|
|
193
|
+
| `scripts/pull-content.mjs` | Loads production's D1 into the local build DB before the prerender (`deploy`/`deploy:dev`) |
|
|
194
|
+
| `wrangler.jsonc` | Cloudflare bindings (D1/R2) + cron trigger |
|
|
195
|
+
| `scripts/build-dev-wrangler.mjs` | Rewrites the built wrangler config for the isolated dev worker |
|
|
196
|
+
| `SETUP.html` | Client-onboarding checklist — run `vpr sop` first (see "One-time setup") |
|
|
197
|
+
| `.github/workflows/` | deploy-dev (push to `dev`), deploy-prod (push to `main`, or an `emdash-publish` dispatch), promote |
|
|
198
|
+
| `.agents/skills/promote/` | Agent skill: dispatch the dev → main promotion |
|
|
199
|
+
| `.agents/skills/custom-blocks/` | Agent skill: when (and how) to add a block type — compose, extend, local, upstream |
|
|
200
|
+
| `.mcp.json` | Astro + EmDash docs MCP servers for coding agents |
|
|
201
|
+
| `.claude/hooks/` | Claude Code web: Node 24 + pnpm setup on session start |
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
<!--
|
|
8
8
|
Opened straight from disk (file://) before anything is deployed, which is
|
|
9
9
|
when most of these steps happen — so it links a relative, pre-generated
|
|
10
|
-
stylesheet rather than the site's own build output. Run `
|
|
10
|
+
stylesheet rather than the site's own build output. Run `vpr sop`
|
|
11
11
|
first (it's a one-line `vitops generate -f css` into .vitops/sop/); the
|
|
12
12
|
banner below explains this if the stylesheet 404s, and the markup stays
|
|
13
13
|
legible either way — every item here is a real checkbox in a real
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
-->
|
|
22
22
|
<link id="sop-stylesheet" rel="stylesheet" href=".vitops/sop/styles.css" />
|
|
23
23
|
<style>
|
|
24
|
-
/* Minimal fallback so the page is still legible before `
|
|
24
|
+
/* Minimal fallback so the page is still legible before `vpr sop`
|
|
25
25
|
has run once — intentionally not framework CSS, since it only ever
|
|
26
26
|
applies in the narrow window before the real stylesheet exists. */
|
|
27
27
|
body {
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
<header class="region rhythm">
|
|
43
43
|
<h1>Client onboarding checklist</h1>
|
|
44
44
|
<p id="no-stylesheet-notice" class="banner" hidden>
|
|
45
|
-
This page isn't styled yet — run <code>
|
|
45
|
+
This page isn't styled yet — run <code>vpr sop</code> once (it generates
|
|
46
46
|
<code>.vitops/sop/styles.css</code>), then reload.
|
|
47
47
|
</p>
|
|
48
48
|
<p>
|
|
@@ -90,7 +90,7 @@
|
|
|
90
90
|
<span class="custom-check__box"
|
|
91
91
|
><span class="custom-check__indicator custom-check__indicator--check"></span
|
|
92
92
|
></span>
|
|
93
|
-
<span> Preview it: <code>
|
|
93
|
+
<span> Preview it: <code>vp exec vitops setup repo --dry</code> </span>
|
|
94
94
|
</label>
|
|
95
95
|
<label class="custom-check">
|
|
96
96
|
<input type="checkbox" data-sop="repo.run" />
|
|
@@ -98,7 +98,7 @@
|
|
|
98
98
|
><span class="custom-check__indicator custom-check__indicator--check"></span
|
|
99
99
|
></span>
|
|
100
100
|
<span>
|
|
101
|
-
Run it: <code>
|
|
101
|
+
Run it: <code>vp exec vitops setup repo</code> — creates the GitHub repo, pushes
|
|
102
102
|
<code>main</code> + <code>dev</code>, makes <code>dev</code> default, and requests
|
|
103
103
|
branch protection + the <code>production</code>/<code>dev</code>
|
|
104
104
|
environments.
|
|
@@ -126,7 +126,7 @@
|
|
|
126
126
|
<span class="custom-check__box"
|
|
127
127
|
><span class="custom-check__indicator custom-check__indicator--check"></span
|
|
128
128
|
></span>
|
|
129
|
-
<span> Preview it: <code>
|
|
129
|
+
<span> Preview it: <code>vp exec vitops setup hosting --dry</code> </span>
|
|
130
130
|
</label>
|
|
131
131
|
<label class="custom-check">
|
|
132
132
|
<input type="checkbox" data-sop="hosting.run" />
|
|
@@ -134,10 +134,10 @@
|
|
|
134
134
|
><span class="custom-check__indicator custom-check__indicator--check"></span
|
|
135
135
|
></span>
|
|
136
136
|
<span>
|
|
137
|
-
Run it: <code>
|
|
137
|
+
Run it: <code>vp exec vitops setup hosting</code> — provisions the isolated dev D1
|
|
138
138
|
database + R2 bucket (EmDash template) and sets the <code>DEV_D1_ID</code> /
|
|
139
139
|
<code>CLOUDFLARE_ACCOUNT_ID</code> repo secrets. Requires
|
|
140
|
-
<code>
|
|
140
|
+
<code>vp exec wrangler login</code> first.
|
|
141
141
|
</span>
|
|
142
142
|
</label>
|
|
143
143
|
<label class="custom-check">
|
|
@@ -276,7 +276,7 @@
|
|
|
276
276
|
><span class="custom-check__indicator custom-check__indicator--check"></span
|
|
277
277
|
></span>
|
|
278
278
|
<span>
|
|
279
|
-
Deploy once (<code>
|
|
279
|
+
Deploy once (<code>vpr deploy</code>), note the printed
|
|
280
280
|
<code>*.workers.dev</code> URL, and set
|
|
281
281
|
<code>site.environments.production.url</code> in <code>site.json</code> to it.
|
|
282
282
|
</span>
|
|
@@ -315,8 +315,8 @@
|
|
|
315
315
|
><span class="custom-check__indicator custom-check__indicator--check"></span
|
|
316
316
|
></span>
|
|
317
317
|
<span>
|
|
318
|
-
Preview it: <code>
|
|
319
|
-
only plans and checks; it never applies the cutover itself.
|
|
318
|
+
Preview it: <code>vp exec vitops setup domain --domain <apex> --dry</code> —
|
|
319
|
+
this only plans and checks; it never applies the cutover itself.
|
|
320
320
|
</span>
|
|
321
321
|
</label>
|
|
322
322
|
<label class="custom-check">
|
|
@@ -326,7 +326,7 @@
|
|
|
326
326
|
></span>
|
|
327
327
|
<span>
|
|
328
328
|
Uncomment the <code>routes</code> block in <code>wrangler.jsonc</code>, set your
|
|
329
|
-
apex, and deploy: <code>
|
|
329
|
+
apex, and deploy: <code>vpr deploy</code>.
|
|
330
330
|
</span>
|
|
331
331
|
</label>
|
|
332
332
|
<label class="custom-check">
|
|
@@ -347,7 +347,8 @@
|
|
|
347
347
|
><span class="custom-check__indicator custom-check__indicator--check"></span
|
|
348
348
|
></span>
|
|
349
349
|
<span>
|
|
350
|
-
Re-run <code>
|
|
350
|
+
Re-run <code>vp exec vitops robots</code> and
|
|
351
|
+
<code>vp exec vitops search notify</code>.
|
|
351
352
|
</span>
|
|
352
353
|
</label>
|
|
353
354
|
</div>
|
|
@@ -373,7 +374,7 @@
|
|
|
373
374
|
<span class="custom-check__box"
|
|
374
375
|
><span class="custom-check__indicator custom-check__indicator--check"></span
|
|
375
376
|
></span>
|
|
376
|
-
<span> Check it: <code>
|
|
377
|
+
<span> Check it: <code>vp exec vitops validate site.json</code> </span>
|
|
377
378
|
</label>
|
|
378
379
|
</div>
|
|
379
380
|
</details>
|
|
@@ -398,8 +399,8 @@
|
|
|
398
399
|
><span class="custom-check__indicator custom-check__indicator--check"></span
|
|
399
400
|
></span>
|
|
400
401
|
<span>
|
|
401
|
-
Preview then run: <code>
|
|
402
|
-
<code>
|
|
402
|
+
Preview then run: <code>vp exec vitops setup search --dry</code>, then
|
|
403
|
+
<code>vp exec vitops setup search</code>. This is a <strong>user OAuth</strong>
|
|
403
404
|
action, deliberately — verifying a site makes the caller an owner, and that should
|
|
404
405
|
be a person, not a deploy robot.
|
|
405
406
|
</span>
|
|
@@ -410,8 +411,25 @@
|
|
|
410
411
|
><span class="custom-check__indicator custom-check__indicator--check"></span
|
|
411
412
|
></span>
|
|
412
413
|
<span>
|
|
413
|
-
Preview then run: <code>
|
|
414
|
-
<code>
|
|
414
|
+
Preview then run: <code>vp exec vitops setup ads --dry</code>, then
|
|
415
|
+
<code>vp exec vitops setup ads</code>.
|
|
416
|
+
</span>
|
|
417
|
+
</label>
|
|
418
|
+
<label class="custom-check">
|
|
419
|
+
<input type="checkbox" data-sop="connections.turnstile" />
|
|
420
|
+
<span class="custom-check__box"
|
|
421
|
+
><span class="custom-check__indicator custom-check__indicator--check"></span
|
|
422
|
+
></span>
|
|
423
|
+
<span>
|
|
424
|
+
If the site has a contact form, create its spam check:
|
|
425
|
+
<code>vp exec vitops setup turnstile --dry</code>, then
|
|
426
|
+
<code>vp exec vitops setup turnstile</code>. It needs
|
|
427
|
+
<code>CLOUDFLARE_API_TOKEN</code> with
|
|
428
|
+
<strong>Account > Turnstile > Edit</strong> (plus
|
|
429
|
+
<code>CLOUDFLARE_ACCOUNT_ID</code>) and <code>wrangler login</code>. The public site
|
|
430
|
+
key goes into <code>site.security.turnstile.siteKey</code>; the secret is saved as
|
|
431
|
+
the Worker secret <code>VITOPS_TURNSTILE_SECRET</code> and never touches
|
|
432
|
+
<code>site.json</code>. It never edits or deletes an existing widget.
|
|
415
433
|
</span>
|
|
416
434
|
</label>
|
|
417
435
|
<label class="custom-check">
|
|
@@ -453,10 +471,10 @@
|
|
|
453
471
|
></span>
|
|
454
472
|
<span>
|
|
455
473
|
Generate real documents from the filled-in config —
|
|
456
|
-
<code>
|
|
457
|
-
or <code>
|
|
458
|
-
<code>/terms</code> and <code>/privacy</code> pages are deliberately
|
|
459
|
-
obviously-unfinished placeholders.
|
|
474
|
+
<code>vp exec vitops legal --format portable-text</code> (EmDash; paste into the
|
|
475
|
+
admin) or <code>vp exec vitops legal --format md</code> (static; commit the output).
|
|
476
|
+
The seeded <code>/terms</code> and <code>/privacy</code> pages are deliberately
|
|
477
|
+
short, obviously-unfinished placeholders.
|
|
460
478
|
</span>
|
|
461
479
|
</label>
|
|
462
480
|
<label class="custom-check">
|
|
@@ -484,7 +502,7 @@
|
|
|
484
502
|
></span>
|
|
485
503
|
<span>
|
|
486
504
|
Robots directive matches the live origin's intent (blocked while on workers.dev,
|
|
487
|
-
open after cutover) — <code>
|
|
505
|
+
open after cutover) — <code>vp exec vitops robots</code>.
|
|
488
506
|
</span>
|
|
489
507
|
</label>
|
|
490
508
|
<label class="custom-check">
|
|
@@ -509,7 +527,7 @@
|
|
|
509
527
|
><span class="custom-check__indicator custom-check__indicator--check"></span
|
|
510
528
|
></span>
|
|
511
529
|
<span>
|
|
512
|
-
<code>
|
|
530
|
+
<code>vp exec vitops lint --src</code> and <code>vpr typecheck</code> both clean.
|
|
513
531
|
</span>
|
|
514
532
|
</label>
|
|
515
533
|
<label class="custom-check">
|
|
@@ -527,7 +545,7 @@
|
|
|
527
545
|
<span class="custom-check__box"
|
|
528
546
|
><span class="custom-check__indicator custom-check__indicator--check"></span
|
|
529
547
|
></span>
|
|
530
|
-
<span> <code>
|
|
548
|
+
<span> <code>vp exec vitops search notify</code> run once live. </span>
|
|
531
549
|
</label>
|
|
532
550
|
<label class="custom-check">
|
|
533
551
|
<input type="checkbox" data-sop="launch.check" />
|
|
@@ -535,8 +553,8 @@
|
|
|
535
553
|
><span class="custom-check__indicator custom-check__indicator--check"></span
|
|
536
554
|
></span>
|
|
537
555
|
<span>
|
|
538
|
-
A day or two later: <code>
|
|
539
|
-
actually indexed it.
|
|
556
|
+
A day or two later: <code>vp exec vitops search notify --check</code> confirms
|
|
557
|
+
Google actually indexed it.
|
|
540
558
|
</span>
|
|
541
559
|
</label>
|
|
542
560
|
</div>
|
|
@@ -12,6 +12,9 @@ worker-configuration.d.ts
|
|
|
12
12
|
# wrangler local state (local D1/R2 data lives here during `astro dev`)
|
|
13
13
|
.wrangler/
|
|
14
14
|
|
|
15
|
+
# production content dump `scripts/pull-content.mjs` loads into the local build DB
|
|
16
|
+
.vitops/content.sql
|
|
17
|
+
|
|
15
18
|
# node hosting target: SQLite db + local uploads
|
|
16
19
|
data/
|
|
17
20
|
|
|
@@ -23,6 +23,15 @@ const organizationName = resolveOrganizationName(config) ?? 'My EmDash Site';
|
|
|
23
23
|
// data migration, not a rewrite — see README "Hosting".
|
|
24
24
|
const { adapter, database, storage } = await vitopsHosting();
|
|
25
25
|
|
|
26
|
+
// Favicon/PWA colours come from site.json rather than a second copy here that
|
|
27
|
+
// drifts when the palette changes: the browser-UI colour is the `brand-primary`
|
|
28
|
+
// role's hue seed. (The page background behind the maskable icon is a computed
|
|
29
|
+
// ramp step, not a config value, so `backgroundColor` below stays a literal.)
|
|
30
|
+
const colors = config.designSystem?.themes?.default?.colors ?? {};
|
|
31
|
+
const roleHue = (role) =>
|
|
32
|
+
typeof colors.roles?.[role] === 'string' ? colors.roles[role] : colors.roles?.[role]?.hue;
|
|
33
|
+
const brandColor = colors.palette?.[roleHue('brand-primary')]?.seed ?? '#2e9b73';
|
|
34
|
+
|
|
26
35
|
export default defineConfig({
|
|
27
36
|
output: 'server',
|
|
28
37
|
adapter,
|
|
@@ -79,7 +88,7 @@ export default defineConfig({
|
|
|
79
88
|
favicon: {
|
|
80
89
|
source: 'src/assets/logo.svg',
|
|
81
90
|
name: organizationName,
|
|
82
|
-
themeColor:
|
|
91
|
+
themeColor: brandColor,
|
|
83
92
|
backgroundColor: '#ffffff',
|
|
84
93
|
},
|
|
85
94
|
// inject: false — Layout.astro imports the generated stylesheet, so
|
|
@@ -91,6 +100,10 @@ export default defineConfig({
|
|
|
91
100
|
// there instead of a bare design-system.json. legal/fonts/search/ads/
|
|
92
101
|
// reviews all read the same file (see AGENTS.md's "Client onboarding").
|
|
93
102
|
css: { format: 'tailwind', inject: false, input: 'site.json' },
|
|
103
|
+
// The /design-system admin page embeds public/vitops/design-system-editor.html in an
|
|
104
|
+
// iframe; this writes it, with its manifest, editor.js and editor.css, without loading
|
|
105
|
+
// the editor on public pages.
|
|
106
|
+
editorManifest: true,
|
|
94
107
|
site: { input: 'site.json' },
|
|
95
108
|
}),
|
|
96
109
|
],
|
|
@@ -5,10 +5,12 @@
|
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"dev": "astro dev",
|
|
8
|
-
"build": "astro build",
|
|
8
|
+
"build": "astro build && vitops emdash check-prerender",
|
|
9
9
|
"preview": "astro preview",
|
|
10
|
-
"
|
|
11
|
-
"
|
|
10
|
+
"pull-content": "node scripts/pull-content.mjs",
|
|
11
|
+
"prepare-local-db": "vitops emdash prepare-db",
|
|
12
|
+
"deploy": "vitops email check && node scripts/pull-content.mjs && vitops emdash prepare-db && astro build && vitops emdash check-prerender && emdash migrate --wrangler-config wrangler.jsonc && wrangler deploy",
|
|
13
|
+
"deploy:dev": "vitops email check && node scripts/pull-content.mjs && vitops emdash prepare-db && astro build && vitops emdash check-prerender && node scripts/build-dev-wrangler.mjs && emdash migrate --wrangler-config dist/server/wrangler.dev.json && wrangler deploy --config dist/server/wrangler.dev.json",
|
|
12
14
|
"init:github": "vitops setup repo",
|
|
13
15
|
"init:hosting": "vitops setup hosting",
|
|
14
16
|
"setup": "vitops setup all",
|
|
@@ -21,13 +23,13 @@
|
|
|
21
23
|
"dependencies": {
|
|
22
24
|
"@astrojs/cloudflare": "^14.0.0",
|
|
23
25
|
"@astrojs/react": "^7.1.2",
|
|
24
|
-
"@emdash-cms/cloudflare": "
|
|
26
|
+
"@emdash-cms/cloudflare": "latest",
|
|
25
27
|
"@getvitops/astro": "latest",
|
|
26
28
|
"@getvitops/core": "latest",
|
|
27
29
|
"@getvitops/emdash": "latest",
|
|
28
30
|
"@tailwindcss/vite": "^4.3.3",
|
|
29
31
|
"astro": "^7.4.4",
|
|
30
|
-
"emdash": "
|
|
32
|
+
"emdash": "latest",
|
|
31
33
|
"react": "^19.3.1",
|
|
32
34
|
"react-dom": "^19.3.1",
|
|
33
35
|
"tailwindcss": "^4.3.3"
|
|
@@ -1,9 +1,24 @@
|
|
|
1
|
-
# Cloudflare
|
|
1
|
+
# Cloudflare static-assets response headers. They apply to files served from the
|
|
2
|
+
# static asset store — the prerendered pages — and NOT to responses the Worker
|
|
3
|
+
# renders itself (the EmDash admin at /_emdash/*, its API, anything left
|
|
4
|
+
# on-demand). See AGENTS.md "Content Security Policy".
|
|
2
5
|
#
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
+
# REPORT-ONLY on purpose: violations are logged to the browser console and
|
|
7
|
+
# nothing is blocked, so this cannot break a page. Read the console on the
|
|
8
|
+
# pages you ship, add what is legitimately needed, then rename the header to
|
|
9
|
+
# `Content-Security-Policy` to enforce it.
|
|
10
|
+
#
|
|
11
|
+
# The two sha256 values are the inline scripts <Head /> emits (the colour-scheme
|
|
12
|
+
# bootstrap and the consent-gate stub). They change only when
|
|
13
|
+
# @getvitops/astro changes those scripts; regenerate with
|
|
14
|
+
# vp exec node -e "import('@getvitops/astro').then(m => console.log(m.contentSecurityPolicy()))"
|
|
15
|
+
# `templates.test.ts` in the toolchain repo guards that they match.
|
|
16
|
+
#
|
|
17
|
+
# Expect reports for scripts this policy cannot name by hash: EmDash's own head
|
|
18
|
+
# tags, a server-island loader, an editor's HTML block, and third-party tags the
|
|
19
|
+
# consent gate activates (allow their origins in script-src/connect-src).
|
|
6
20
|
/*
|
|
21
|
+
Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self' 'sha256-CEiVlTo192T9Rug/nAl+ihvkqOQkYdr6Rq3oc+gR9JU=' 'sha256-z/UqsITVvNGOJSaOSvhN1rdtyYdMIxpQ/lMXM1q0g90=' https://challenges.cloudflare.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self'; frame-src https://challenges.cloudflare.com; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'
|
|
7
22
|
X-Content-Type-Options: nosniff
|
|
8
23
|
Referrer-Policy: strict-origin-when-cross-origin
|
|
9
24
|
Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=()
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// Pre-build step: load production's EmDash content into the LOCAL D1 the build
|
|
2
|
+
// prerenders from.
|
|
3
|
+
//
|
|
4
|
+
// Why this exists: `src/pages/index.astro` and `[...slug].astro` are prerendered
|
|
5
|
+
// (`prerender = true` + `getStaticPaths`), so their HTML is written during
|
|
6
|
+
// `astro build`. The Cloudflare adapter prerenders inside a local workerd with a
|
|
7
|
+
// LOCAL D1 (`.wrangler/state`), never the deployed database — so on a CI runner
|
|
8
|
+
// the build would see an empty, auto-seeded database and bake "No home page yet"
|
|
9
|
+
// into every page. Production is the truth for content (see AGENTS.md), so this
|
|
10
|
+
// copies production's D1 into the local one first.
|
|
11
|
+
//
|
|
12
|
+
// wrangler d1 export <db> --remote → .vitops/content.sql
|
|
13
|
+
// wrangler d1 execute <db> --local ← .vitops/content.sql (state cleared first)
|
|
14
|
+
//
|
|
15
|
+
// Read-only against production (an export). It only ever writes the local
|
|
16
|
+
// `.wrangler/state/v3/d1` directory, which is disposable build state.
|
|
17
|
+
//
|
|
18
|
+
// Fails CLOSED. A deploy that proceeds after a failed pull would publish an empty
|
|
19
|
+
// site over a good one, so any failure aborts the deploy — except one case that is
|
|
20
|
+
// not a failure: the remote database does not exist yet (the first deploy, where
|
|
21
|
+
// wrangler provisions it). Building from the seed is only right once the local database is
|
|
22
|
+
// migrated and seeded, which is `scripts/prepare-local-db.mjs`'s job, run straight after this.
|
|
23
|
+
//
|
|
24
|
+
// Skip with SKIP_PULL_CONTENT=1 (e.g. a deliberate seed-only build).
|
|
25
|
+
|
|
26
|
+
import { execFileSync, spawnSync } from 'node:child_process';
|
|
27
|
+
import { mkdirSync, readFileSync, rmSync } from 'node:fs';
|
|
28
|
+
|
|
29
|
+
if (process.env.SKIP_PULL_CONTENT) {
|
|
30
|
+
console.log('pull-content: SKIP_PULL_CONTENT set — prerendering from the local/seed database.');
|
|
31
|
+
process.exit(0);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** wrangler.jsonc allows comments and trailing commas; JSON.parse does not. */
|
|
35
|
+
function parseJsonc(text) {
|
|
36
|
+
const out = text
|
|
37
|
+
.replace(/("(?:[^"\\]|\\.)*")|\/\*[\s\S]*?\*\/|\/\/[^\n]*/g, (m, str) => str ?? '')
|
|
38
|
+
.replace(/,(\s*[}\]])/g, '$1');
|
|
39
|
+
return JSON.parse(out);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const config = parseJsonc(readFileSync('wrangler.jsonc', 'utf8'));
|
|
43
|
+
const db = config.d1_databases?.[0]?.database_name;
|
|
44
|
+
if (!db) {
|
|
45
|
+
console.error('pull-content: wrangler.jsonc has no d1_databases[0].database_name.');
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const dump = '.vitops/content.sql';
|
|
50
|
+
mkdirSync('.vitops', { recursive: true });
|
|
51
|
+
|
|
52
|
+
const wrangler = (args) =>
|
|
53
|
+
spawnSync('wrangler', args, { encoding: 'utf8', env: { ...process.env, CI: '1' } });
|
|
54
|
+
|
|
55
|
+
const exported = wrangler([
|
|
56
|
+
'd1',
|
|
57
|
+
'export',
|
|
58
|
+
db,
|
|
59
|
+
'--remote',
|
|
60
|
+
'--output',
|
|
61
|
+
dump,
|
|
62
|
+
'--skip-confirmation',
|
|
63
|
+
]);
|
|
64
|
+
if (exported.status !== 0) {
|
|
65
|
+
const text = `${exported.stdout}\n${exported.stderr}`;
|
|
66
|
+
if (/not found|does not exist|no such database|couldn'?t find|could not find|7404/i.test(text)) {
|
|
67
|
+
console.warn(
|
|
68
|
+
`pull-content: remote database "${db}" does not exist yet (first deploy?) — ` +
|
|
69
|
+
'prerendering from the seed.',
|
|
70
|
+
);
|
|
71
|
+
process.exit(0);
|
|
72
|
+
}
|
|
73
|
+
console.error(`pull-content: could not export "${db}" from production:\n${text}`);
|
|
74
|
+
console.error('Refusing to build — prerendering now would publish an empty site.');
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Replaying onto a populated local database collides on CREATE TABLE, so start clean.
|
|
79
|
+
rmSync('.wrangler/state/v3/d1', { recursive: true, force: true });
|
|
80
|
+
execFileSync('wrangler', ['d1', 'execute', db, '--local', '--file', dump, '--yes'], {
|
|
81
|
+
stdio: 'inherit',
|
|
82
|
+
});
|
|
83
|
+
console.log(`pull-content: loaded production "${db}" into the local build database.`);
|