@getvitops/create 0.8.0-alpha.0 → 0.8.1

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 CHANGED
@@ -1,12 +1,307 @@
1
1
  # @getvitops/create
2
2
 
3
+ ## 0.8.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Both templates pin Node by major only (`devEngines.runtime.version: "24"`), matching `.node-version`. The exact `24.18.0` pin disagreed with the floating `.node-version`, so `vp` warned in every fresh scaffold.
8
+
9
+ ## 0.8.0
10
+
11
+ ### Minor Changes
12
+
13
+ - 226b1aa: **Content now lives in production; dev is a refreshed copy.** The `dev → main` flow is for code,
14
+ pages and `site.json`. EmDash content is edited on the production site (drafts, "Publish changes"
15
+ and preview links are the staging step), and the dev database is disposable and refreshed _from_
16
+ prod — never promoted back. Both templates gain a hotfix path back into `dev`.
17
+
18
+ - **New `refresh-dev.yml` (emdash template) + `scripts/refresh-dev.mjs`** — copies prod's D1
19
+ database into `<name>-dev` (weekly and on demand), and prod's R2 bucket too when the optional
20
+ `R2_ACCESS_KEY_ID` / `R2_SECRET_ACCESS_KEY` repo secrets are set. It drops every table in its
21
+ target, so it refuses a database that isn't `*-dev`, doesn't match `DEV_D1_ID`, or is
22
+ production's. **Overwrites everything in the dev database on each run** — content edited on dev
23
+ is lost by design.
24
+ - **New `backmerge.yml` (both templates)** — on every push to `main`, opens a `main → dev` PR when
25
+ merging would change `dev`, so a prod hotfix doesn't leave `dev` behind. The PR is left for a
26
+ human to merge unless a `BACKMERGE_TOKEN` secret (PAT / GitHub App token) is set: a merge made
27
+ with the default token triggers no workflows, so Deploy dev would not run.
28
+ - **Magic-link sign-in in the emdash template**: it now registers `cloudflareEmail()` and a
29
+ `send_email` binding named `EMAIL`. A passkey only works at the origin it was registered on, so a
30
+ refreshed dev site would otherwise lock editors out; the cloned users already exist, so the same
31
+ email signs in on both origins. **Existing sites:** templates are copied at scaffold time, so
32
+ nothing changes until you add both yourself, and magic link answers "Email is not configured"
33
+ until you do. Onboard the sender first (`npx vitops email setup send.<your-domain>`) and set
34
+ `from` to an address on that domain — the template ships a placeholder you must replace.
35
+ - **The dev worker no longer has a cron trigger** (`build-dev-wrangler.mjs`), since a refreshed
36
+ database carries prod's scheduled publishes and plugin jobs. Scheduled publishing can't be
37
+ exercised on dev. Set a different `EMDASH_PREVIEW_SECRET` on the dev worker, or prod preview
38
+ links will open drafts there.
39
+ - `SETUP.html` and the `promote` skill describe the new model; `SETUP.html` gains four items under
40
+ "3. Hosting resources" (the OAuth apps and preview secret are manual).
41
+
42
+ No consumer-facing change in `@getvitops/core`, `@getvitops/generator`, `@getvitops/utils`,
43
+ `@getvitops/cli`, `@getvitops/vite`, `@getvitops/astro` or `@getvitops/emdash`. The reusable
44
+ workflows (`site-refresh-dev.yml`, `site-backmerge.yml`) reach existing client repos only once the
45
+ `v1` tag is moved by `release-workflows.yml`; a client needs the new caller files above to use them.
46
+
47
+ - 0a4f64b: **EmDash template:** documents installing the EmDash agent skills per project
48
+ (`npx skills add emdash-cms/skills --skill '*' -a claude-code -y`, so they match the pinned `emdash`) in
49
+ the README and `AGENTS.md`, and lists the current blocks (hero, cards, accordion, gallery, marquee, …)
50
+ with `npx vitops docs blocks` as the full reference. Raises the dependency floors (`@astrojs/react`
51
+ 7.1, `@astrojs/check` 0.9.12, React 19.3.1) and both templates now pin `pnpm@12.16.1`.
52
+ - c3c1bcb: **New: the design-system editor, mounted inside the EmDash admin.** `vitopsEmdash({
53
+ designSystemEditor: { config } })` (`config` is your `site.json`, e.g. `import config from
54
+ './site.json' with { type: 'json' }`) adds a `/design-system` admin page mounting
55
+ `<wc-theme-editor>` — the same live token editor `@getvitops/vite`'s dev server has always
56
+ offered, now usable by the client directly, no dev server required. An edit is visible
57
+ site-wide immediately (a runtime `:root` override, served via a new plugin API route and
58
+ `page:fragments`) and — once `site.deployment.repo`/`branch` and a `VITOPS_GITHUB_TOKEN`
59
+ secret are set (see the scaffold's `SETUP.html`) — auto-commits to your `dev` branch, so it
60
+ reaches the git-tracked `site.json` through your repo's normal review path rather than
61
+ becoming a second, unreviewed source of truth. Until that's configured, the live half still
62
+ works; only the auto-commit half is skipped (and says so in the admin page). The save route
63
+ runs the exact same accessibility (APCA contrast) gate `generate()` enforces at build time,
64
+ synchronously, before persisting — nothing non-compliant ever goes live even momentarily.
65
+
66
+ Requires `@astrojs/react` + `react` + `react-dom` (already scaffold dependencies, for
67
+ EmDash's own admin) and `@getvitops/core` (added as a new scaffold dependency this release).
68
+ Declares two new plugin capabilities when enabled: `network:request` (for the GitHub
69
+ auto-commit) and `hooks.page-fragments:register` (for the live override) — reviewed once at
70
+ plugin-install time.
71
+
72
+ **Breaking for `@getvitops/emdash`**: `vitopsEmdash()`'s `hooks.page-fragments:register`
73
+ capability is now also granted whenever `designSystemEditor` is set, independent of the
74
+ existing `scripts: 'fragments'` option (which governs something unrelated — web-component
75
+ script delivery). No change if you don't use `designSystemEditor`.
76
+
77
+ Also new, supporting the above:
78
+
79
+ - `@getvitops/generator`: `validateColorContract()` and `buildReverseIndex()` (both
80
+ `/tokens` subpath, Worker-safe) — the monotonicity/contrast gate and the
81
+ `design-manifest.json` reverse-index, each extracted from `generate()`'s `build()` into
82
+ standalone, non-throwing functions. `patchToOverrideCss()` turns a design-system patch
83
+ into the `:root` override `<style>` block. `designSystemPath`/`getAt`/`setAt`/
84
+ `mergePatch`/`formatConfig` moved from `@getvitops/vite` into `@getvitops/generator/config`
85
+ (re-exported from `@getvitops/vite` unchanged) — one "patch a config file's design-system
86
+ subtree" implementation shared by the dev endpoint and the new plugin route. New
87
+ `site.deployment.repo`/`branch` config fields.
88
+ - `@getvitops/utils`: new `/git` subpath — `commitFile()`, a Worker-safe (fetch-only)
89
+ wrapper over GitHub's Contents API.
90
+ - `@getvitops/core`: `<wc-theme-editor>` gains a `save-endpoint` attribute (default
91
+ unchanged — `/__vitops/design-system`) so the same element can target the new plugin
92
+ route instead of the dev-only Vite endpoint.
93
+
94
+ - 2ef2d41: **Tested against EmDash 1.4.** `emdash` and `@emdash-cms/cloudflare` are now `^1.4.0` in the
95
+ `emdash` template, and `@getvitops/emdash`'s peer ranges for both moved from `>=1.0.0 <2` to
96
+ `>=1.3.0 <2` — **breaking for a site still on EmDash 1.0–1.2**: upgrade `emdash` and
97
+ `@emdash-cms/cloudflare` together (EmDash warns at build when the two differ) before upgrading
98
+ this package. 1.4 fixes a stored XSS in the visual-editing toolbar (an author's published content
99
+ could run script for a signed-in editor), so an existing client site should take it promptly.
100
+
101
+ - **`vitopsHosting()` coalesces D1 reads by default** — new `cloudflare.coalesce` option
102
+ (default `true`, passed to `d1({ coalesce })`). With `session: 'auto'`, EmDash 1.3 stops
103
+ preloading settings/menus/widgets for logged-out visitors unless reads are coalesced, so this
104
+ restores one round trip instead of several. It is experimental upstream: a read and a write
105
+ started together may run write-first, so `await` a read before a write that must not affect it.
106
+ Set `coalesce: false` to opt out. No effect without a session.
107
+ - **Template:** `AGENTS.md` gains an "EmDash 1.x behaviours" section (passkeys are bound to a
108
+ hostname so use _Change domain_ at cutover; admin-authored HTML blocks run JS and conflict with
109
+ Astro CSP; link by `entry.data.slug`, not `entry.id`; submit `/sitemap.xml`; clearing a stuck D1
110
+ lock with `emdash migrate --take-over-lock`).
111
+ - **Upstream behaviour changes to know about (no code change here):** `cacheHint.lastModified` is
112
+ no longer auto-populated and pages lose the implicit `Cache-Control: no-cache`; collection
113
+ sitemaps paginate at 2,000 entries; `entry.id` is always locale-prefixed on multilingual sites.
114
+
115
+ No consumer-facing change in `@getvitops/core`, `@getvitops/generator`, `@getvitops/utils`,
116
+ `@getvitops/cli` or `@getvitops/vite`. `@getvitops/astro` has one — see the scaffold changeset.
117
+
118
+ - ab451ae: **Scaffolds now pin Astro `^7.4.4` and ship a `.node-version`.** Both templates (`emdash`, `static`)
119
+ move from `astro: ^7.1.2` to `^7.4.4` — the newest release at least three days old when this was
120
+ chosen (7.4.5 was under a day old), matching what this repo builds and tests against — and add a
121
+ `.node-version` containing `24`, so version managers that read it (fnm, mise, asdf, Volta) agree with
122
+ the `.nvmrc` the reusable CI workflows read and the `devEngines` pin pnpm reads. `templates.test.ts`
123
+ now fails if those three disagree, or if a template's Astro range drifts from the repo's catalog.
124
+
125
+ Existing projects are not changed: bump `astro` yourself, and add `.node-version` if you want it.
126
+
127
+ Formats reached: none — no CSS change. Other packages in the `fixed` group: No consumer-facing change
128
+ in this package.
129
+
130
+ - b70c7f0: **Scaffolded projects now hold new releases for 3 days before installing them.** Both templates set
131
+ `minimumReleaseAge: 4320` (the `emdash` template was 1440; the `static` template had no
132
+ `pnpm-workspace.yaml` and so used pnpm's one-day default). The `static` template now ships a
133
+ `pnpm-workspace.yaml` carrying the cooldown, the build approvals, and the same toolchain exemptions as
134
+ `emdash` — `@getvitops/*` and the Vite+ / Oxc tools are exempt, since they release together.
135
+
136
+ If a fresh install in a scaffolded project is rejected for a dependency published in the last three
137
+ days, wait for it to age or add its exact version to `minimumReleaseAgeExclude`. Existing projects are
138
+ not changed.
139
+
140
+ Formats reached: none — no CSS change. Other packages in the `fixed` group: No consumer-facing change
141
+ in this package.
142
+
143
+ ### Patch Changes
144
+
145
+ - 0574388: Add `vitops email setup|check` — connect `site.notifications.email.from` to whether its domain is
146
+ actually onboarded onto an outbound email provider.
147
+
148
+ Before this, nothing checked that. `EMAIL.send()` throws `E_SENDER_NOT_VERIFIED` at runtime with no
149
+ build or deploy-time warning — the failure mode is silent and production-only.
150
+
151
+ `vitops email setup [domain]` onboards a sending domain (currently Cloudflare Email Sending only)
152
+ and polls DNS until its SPF/DKIM/DMARC records resolve or a timeout is reached; `vitops email
153
+ check` is the non-blocking deploy guard both `@getvitops/create` templates' `deploy`/`deploy:dev`
154
+ scripts now run first (`--strict` makes it a real gate; opt out per-site with
155
+ `site.notifications.email.verifySender: false`, or globally with `--skip` /
156
+ `VITOPS_SKIP_EMAIL_CHECK`).
157
+
158
+ Two things worth knowing before running it:
159
+
160
+ - **The onboarded entity is an exact name, and it is usually a subdomain**
161
+ (`send.yourdomain.com`), never the apex your `from` address names. When the exact domain isn't
162
+ onboarded but a sibling in the same zone already is, the command reports that as the fix — send
163
+ from the onboarded subdomain — rather than silently trying to onboard the apex, which rewrites
164
+ `_dmarc.<domain>` and can tighten DMARC policy on every other mail your domain sends.
165
+ - Candidates come from `site.notifications.email.from` (or its derived `noreply@<domains
166
+ .canonical>` default) first; a best-effort scan of `--src` only supplements it, and never guesses
167
+ at a computed `from` value.
168
+
169
+ New `@getvitops/utils/email` subpath and `site.notifications.email.verifySender` config field.
170
+ Needs `CLOUDFLARE_API_TOKEN` with an Email Sending scope in addition to the `Zone:Read` other
171
+ commands already use.
172
+
173
+ - 1a8231d: **EmDash blocks are now a tier of the component manifest, and there are four more of them.**
174
+
175
+ `@getvitops/emdash` adds `vitops.gallery`, `vitops.cards`, `vitops.accordion` and `vitops.marquee`
176
+ (each a thin adapter over the matching `@getvitops/astro` component), and `vitops.carousel` now edits
177
+ its slides as a **Slides repeater** instead of two newline-separated fields. Blocks saved before this
178
+ keep rendering: with no `slides`, the carousel still reads `images`/`alts`, and both legacy fields stay
179
+ in the editor, labelled as legacy. The editor's field type now admits `repeater` and `media_picker`.
180
+
181
+ - **New `blocks` plugin option** — `vitopsEmdash({ blocks: { include, exclude, extra } })`. `include` is
182
+ an allowlist, `exclude` drops blocks, `extra` adds the site's own **editor** definitions (the site
183
+ supplies their renderers via `<PortableText components={{ type }}>`, the same route that overrides a
184
+ `vitops.*` renderer). An unknown name or a colliding `type` throws at plugin construction.
185
+ - Link and image URLs in blocks with a scheme other than `http`, `https`, `mailto` or `tel` are
186
+ dropped (see the security entry).
187
+
188
+ `@getvitops/generator`: `TierEntry` gains an optional `emdash: string[]` (block `_type`s) and `Tier`
189
+ gains `'emdash'`, so `tierPatterns('emdash')` projects it. **Breaking only for code that exhaustively
190
+ switches on `Tier` or builds a `Record<Tier, …>`** — add an `emdash` case. The agent doc
191
+ `vitops docs components` gains an "EmDash blocks" table and the docsite a `components/emdash` page. The
192
+ guard that every block and every `emdash` entry agree lives in `@getvitops/emdash`'s `blocks.test.ts`,
193
+ because the generator cannot import the plugin.
194
+
195
+ `@getvitops/create`: the EmDash template's agent guidance explains the blocks, their limits (a block
196
+ cannot contain blocks; `vitops.*` blocks do not render inside EmDash's built-in `columns`) and the
197
+ `blocks` option.
198
+
199
+ Formats reached: none — no CSS change. Other packages in the `fixed` group: No consumer-facing change
200
+ in this package.
201
+
202
+ - 779d22e: Add a `vitops.hero` EmDash block (eyebrow, heading, text, two links, optional image; centered or split layout) built on the `.centered`/`.split` layout primitives. The EmDash template's home page now seeds one of each of hero, cards, accordion and marquee. `@getvitops/generator`: the `split` pattern lists the new block in `TIERS`.
203
+ - 0574388: Bump `emdash`/`@emdash-cms/cloudflare` peers to `>=1.0.0 <2` and `vite-plus` to `^1.0.0`, following
204
+ both projects' GA releases.
205
+
206
+ **Breaking for `@getvitops/emdash` consumers on EmDash 0.x**: the peer range no longer admits
207
+ `emdash@0.31.x`/`@emdash-cms/cloudflare@0.31.x`. Upgrade `emdash` and `@emdash-cms/cloudflare` to
208
+ `^1.0.1` first — nothing in `@getvitops/emdash`'s own plugin surface (`admin.portableTextBlocks`,
209
+ the `hooks.page-fragments:register` capability, `definePlugin()`) changed against EmDash 1.0, so no
210
+ code here needed to change, only the declared range.
211
+
212
+ The `emdash` scaffold template (`vp create @getvitops:emdash`) picks up several fixes alongside
213
+ the version bump:
214
+
215
+ - `tailwindcss`/`@tailwindcss/vite` are now real dependencies — the template selected
216
+ `css.format: 'tailwind'` without installing either, which threw on build.
217
+ - `site.json` now sets `site.icons.sprite: true` plus the full `semantic` list the
218
+ `vitops.actionLink` block's icon picker offers — previously its sprite icons pointed at a file
219
+ the build never generated.
220
+ - `astro.config.mjs` sets `migrations: { runtime: 'check', dev: 'auto' }`, and
221
+ `site-deploy-emdash.yml` runs EmDash 1.0's documented `build → status → apply → deploy → check`
222
+ migration sequence explicitly, rather than relying solely on first-request auto-migration. This
223
+ needs a new GitHub Environment variable, `EMDASH_TARGET_FINGERPRINT` (one per environment),
224
+ which only a human can set — run `emdash migrate --status` against the real deployed database,
225
+ review the printed identity, then `gh variable set EMDASH_TARGET_FINGERPRINT --env <env> --body
226
+ <value>`. Deploys fail clearly on the migration-apply step until it's set.
227
+ - `lint.options.typeAware`/`typeCheck` in the template's own `vite.config.ts` are commented out,
228
+ not deleted: vite-plus 1.0 only honours that option in a project's actual root config, and hard
229
+ errors on it in a nested one (which this file is, sitting inside the `design-system` monorepo).
230
+ Uncomment it once scaffolded into its own repo.
231
+
232
+ No config schema changes. `@getvitops/generator` also switches from the `zod/mini` subpath import
233
+ to the standalone `@zod/mini` package (zod 4.5+; same API) and fixes `validateConfig()` silently
234
+ losing the offending key name on an `unrecognized_keys` error (e.g. a typo'd `site.ads` platform
235
+ now names the bad key instead of reporting a bare "Invalid input").
236
+
237
+ Also fixed: `vp check`'s `tsconfig-error` for `packages/create/templates/emdash/tsconfig.json`
238
+ (`extends: "astro/tsconfigs/strict"`, unresolvable from inside this monorepo since the template
239
+ has no `node_modules` of its own — a pre-existing, structural gap, newly surfaced as a hard error
240
+ by oxlint 1.85's deeper config discovery, and blocking on every commit since this repo's pre-commit
241
+ hook runs `vp check --fix`). Fixed with a new `scripts/link-template-deps.mjs`, wired as the root
242
+ `postinstall` script: symlinks `astro`/`emdash`/`@emdash-cms/cloudflare` into the template's own
243
+ `node_modules/` (resolved fresh from sibling packages that already depend on them for real, not a
244
+ hardcoded `.pnpm` hash) and writes a minimal `astro:content` ambient shim at `.astro/types.d.ts`
245
+ (the same path `astro sync` would generate for real). Both are gitignored and only ever exist
246
+ inside this monorepo's copy of the template — a real scaffold installs its own real copies instead.
247
+ Considered and rejected first: inlining astro's strict preset (drift risk against astro's own
248
+ evolving default) and making the template a real pnpm workspace member (would change how its
249
+ `@getvitops/*: "latest"` deps resolve locally, which `templates.test.ts` exists to protect).
250
+
251
+ **Known gap, not fixed here**: `packages/astro/src/components/container.test.ts` (the
252
+ `AstroContainer`-based render tests) fails to even load under vite-plus 1.0's Vite 8 — direct
253
+ `.astro` imports in a Vitest file need the Astro Vite plugin registered, which nothing in this
254
+ repo's test config actually does (Astro's own docs call for `getViteConfig()`, which doesn't
255
+ compose with vite-plus's own `defineConfig`). A type-level shim
256
+ (`packages/astro/src/components/astro-modules.d.ts`) fixes `vp check`'s type-aware pass, but the
257
+ runtime `vp test` failure for this one file is still open.
258
+
259
+ - 0f8c018: **New: `resolveOrganizationName(config, locale?)`**, exported from `@getvitops/astro/routes`
260
+ alongside `toNotifyContext`/`toCrmConfig` — the same `organization.name` → `legalName` →
261
+ `undefined` fallback chain `toNotifyContext` already used for its `organizationName` field,
262
+ now usable standalone anywhere a build-time-only display name is needed (favicon generation,
263
+ a scaffold's `astro.config.mjs`, a `Layout.astro` fallback). `toNotifyContext` is unchanged —
264
+ it now delegates to this helper internally.
265
+
266
+ The emdash scaffold uses it to remove a three-way duplicated `'My EmDash Site'` string:
267
+ `astro.config.mjs`'s favicon `name` and `Layout.astro`'s title fallback both previously
268
+ hardcoded it independently of `site.json`'s `organization.name`. EmDash's own
269
+ `getSiteSettings().title` (set in the admin) still wins at render time when it's set — it's a
270
+ runtime-only fact that can't be read at build time at all — this only replaces the two
271
+ hardcoded _fallback_ strings.
272
+
273
+ - 2ef2d41: **`resolveOrganizationName()` accepts a plain `import site from './site.json'`.** It was typed
274
+ against the full `Config`, which a JSON import cannot satisfy — TypeScript widens its literals
275
+ (`colors.utilities` becomes `string[]`, not the schema's union) — so every scaffolded site's
276
+ `Layout.astro` failed `astro check`. It now takes `OrganizationNameSource`, the slice it actually
277
+ reads (`organization.name` / `legalName`, `site.defaultLocale`); a resolved `Config` still
278
+ satisfies it, so nothing that compiled before stops compiling.
279
+
280
+ **Both templates now typecheck and lint clean on a fresh scaffold**, found by the new `scaffold`
281
+ CI job (it installs each template against this checkout's packed packages):
282
+
283
+ - `site.json` declares `neutral` and `surface` as `{ "hue": "ink", "kind": "surface" }`. As bare
284
+ hue strings they were chromatic roles, which emit no bare `bg-neutral` and no
285
+ `text-neutral-muted`, so the template's own footer class resolved to nothing and `vitops lint
286
+ --strict` (run by the template's pre-commit hook) failed. **Existing sites are unaffected** —
287
+ this changes only what a new scaffold starts with; if your own `site.json` has the same two
288
+ lines and uses `text-neutral-*` or `bg-surface*`, change them the same way.
289
+ - The `emdash` template's `index.astro` and `[...slug].astro` cast `page.data.title`, which is
290
+ `unknown` until `emdash types` has run against a live site, so `astro check` passed only after
291
+ the first `pnpm dev`.
292
+
293
+ No consumer-facing change in `@getvitops/core`, `@getvitops/generator`, `@getvitops/utils`,
294
+ `@getvitops/cli` or `@getvitops/vite`.
295
+
296
+ - 80bbef1: Both templates ship `public/_headers` with a no-break security baseline (`X-Content-Type-Options`, `Referrer-Policy`, `Permissions-Policy`, `X-Frame-Options: SAMEORIGIN`), applied by Workers static assets. No CSP: `<Head />` emits inline scripts, so a policy needs per-site nonces or hashes. Note `_headers` covers static assets only; for the EmDash template's Worker-rendered pages, add headers in the Worker.
297
+
3
298
  ## 0.8.0-alpha.0
4
299
 
5
300
  ### Minor Changes
6
301
 
7
302
  - c3c1bcb: **New: the design-system editor, mounted inside the EmDash admin.** `vitopsEmdash({
8
- designSystemEditor: { config } })` (`config` is your `site.json`, e.g. `import config from
9
- './site.json' with { type: 'json' }`) adds a `/design-system` admin page mounting
303
+ designSystemEditor: { config } })` (`config` is your `site.json`, e.g. `import config from
304
+ './site.json' with { type: 'json' }`) adds a `/design-system` admin page mounting
10
305
  `<wc-theme-editor>` — the same live token editor `@getvitops/vite`'s dev server has always
11
306
  offered, now usable by the client directly, no dev server required. An edit is visible
12
307
  site-wide immediately (a runtime `:root` override, served via a new plugin API route and
@@ -55,7 +350,7 @@ designSystemEditor: { config } })` (`config` is your `site.json`, e.g. `import c
55
350
 
56
351
  `vitops email setup [domain]` onboards a sending domain (currently Cloudflare Email Sending only)
57
352
  and polls DNS until its SPF/DKIM/DMARC records resolve or a timeout is reached; `vitops email
58
- check` is the non-blocking deploy guard both `@getvitops/create` templates' `deploy`/`deploy:dev`
353
+ check` is the non-blocking deploy guard both `@getvitops/create` templates' `deploy`/`deploy:dev`
59
354
  scripts now run first (`--strict` makes it a real gate; opt out per-site with
60
355
  `site.notifications.email.verifySender: false`, or globally with `--skip` /
61
356
  `VITOPS_SKIP_EMAIL_CHECK`).
@@ -67,7 +362,7 @@ check` is the non-blocking deploy guard both `@getvitops/create` templates' `dep
67
362
  from the onboarded subdomain — rather than silently trying to onboard the apex, which rewrites
68
363
  `_dmarc.<domain>` and can tighten DMARC policy on every other mail your domain sends.
69
364
  - Candidates come from `site.notifications.email.from` (or its derived `noreply@<domains
70
- .canonical>` default) first; a best-effort scan of `--src` only supplements it, and never guesses
365
+ .canonical>` default) first; a best-effort scan of `--src` only supplements it, and never guesses
71
366
  at a computed `from` value.
72
367
 
73
368
  New `@getvitops/utils/email` subpath and `site.notifications.email.verifySender` config field.
@@ -96,7 +391,7 @@ check` is the non-blocking deploy guard both `@getvitops/create` templates' `dep
96
391
  needs a new GitHub Environment variable, `EMDASH_TARGET_FINGERPRINT` (one per environment),
97
392
  which only a human can set — run `emdash migrate --status` against the real deployed database,
98
393
  review the printed identity, then `gh variable set EMDASH_TARGET_FINGERPRINT --env <env> --body
99
- <value>`. Deploys fail clearly on the migration-apply step until it's set.
394
+ <value>`. Deploys fail clearly on the migration-apply step until it's set.
100
395
  - `lint.options.typeAware`/`typeCheck` in the template's own `vite.config.ts` are commented out,
101
396
  not deleted: vite-plus 1.0 only honours that option in a project's actual root config, and hard
102
397
  errors on it in a nested one (which this file is, sitting inside the `design-system` monorepo).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getvitops/create",
3
- "version": "0.8.0-alpha.0",
3
+ "version": "0.8.1",
4
4
  "description": "Project templates for the Vitops design system — scaffold wired-up sites with `vp create @getvitops`.",
5
5
  "homepage": "https://docs.vitops.ca",
6
6
  "bugs": "https://github.com/getvitops/design-system/issues",
@@ -19,7 +19,7 @@
19
19
  "access": "public"
20
20
  },
21
21
  "devDependencies": {
22
- "@getvitops/generator": "8.2.0-alpha.0"
22
+ "@getvitops/generator": "8.2.0"
23
23
  },
24
24
  "engines": {
25
25
  "node": ">=24"
@@ -59,12 +59,29 @@ So this skill's only job is to kick off the `dev → main` promotion. It does
59
59
  no further action.
60
60
  - Once `main` advances, **Deploy prod** runs automatically.
61
61
 
62
- ## Schema / content changes
62
+ ## Content is not promoted — it lives in prod
63
+
64
+ Content (entries, media, menus, settings) is edited on the **production** site,
65
+ using drafts, "Publish changes" and preview links as the staging step. The dev
66
+ database is a disposable copy of prod: the **Refresh dev from prod** workflow
67
+ (`refresh-dev.yml`) overwrites it, one direction only. Never try to carry dev
68
+ content to prod — a refresh would erase it anyway.
69
+
70
+ ## Schema changes
63
71
 
64
72
  If this release changes the content model, code promotion is **not** enough —
65
- the schema lives in the EmDash database, not git. Apply the same schema change
66
- to the prod site separately (via its admin at `/_emdash/admin` or its MCP
67
- server at `/_emdash/api/mcp`). Ordering: additive changes (new collection /
68
- optional field) **before** the code deploy that uses them; removals **after**
69
- the code that stops using them. Mention this to the user only if the release
70
- plausibly touched the schema.
73
+ the schema lives in the EmDash database, not git. Rehearse it first: run
74
+ **Refresh dev from prod**, apply the change to dev, check the site. Then apply the
75
+ same change to prod (admin at `/_emdash/admin`, or the MCP server at
76
+ `/_emdash/api/mcp`). Ordering: additive changes (new collection / optional
77
+ field) **before** the code deploy that uses them; removals **after** the code
78
+ that stops using them. Mention this to the user only if the release plausibly
79
+ touched the schema.
80
+
81
+ ## Hotfixing prod
82
+
83
+ A fix that can't wait for dev: branch off `main`, PR into `main`, deploy. The
84
+ **Back-merge main → dev** workflow then opens a `main → dev` PR so dev doesn't
85
+ drift (merge it — that is what triggers a dev deploy — unless
86
+ `BACKMERGE_TOKEN` is set, in which case it merges itself when clean). Conflicts
87
+ are left on the PR for a human.
File without changes
@@ -0,0 +1,18 @@
1
+ # Carry changes made directly on `main` (a hotfix) back into `dev`, so the
2
+ # integration branch doesn't silently drift from what is live. Runs on every
3
+ # push to `main`, including each promotion, and opens a PR only when merging
4
+ # main into dev would actually change dev.
5
+ #
6
+ # Deploys via the vitops toolchain's reusable workflow — see
7
+ # https://github.com/getvitops/design-system/blob/main/.github/workflows/site-backmerge.yml
8
+ # for why the PR is left for a human unless BACKMERGE_TOKEN is set.
9
+ name: Back-merge main → dev
10
+
11
+ on:
12
+ push:
13
+ branches: [main]
14
+
15
+ jobs:
16
+ backmerge:
17
+ uses: getvitops/design-system/.github/workflows/site-backmerge.yml@v1
18
+ secrets: inherit
@@ -0,0 +1,25 @@
1
+ # Refresh the dev site's database and media from PRODUCTION.
2
+ #
3
+ # Content is edited in prod (drafts + preview links are the staging step); dev
4
+ # holds a disposable copy so code and schema changes can be rehearsed against
5
+ # real data. This only ever copies prod → dev, and refuses to run against a
6
+ # database whose id is production's. Run it before testing a schema change, or
7
+ # let the weekly schedule keep dev from drifting.
8
+ #
9
+ # Deploys via the vitops toolchain's reusable workflow — see
10
+ # https://github.com/getvitops/design-system/blob/main/.github/workflows/site-refresh-dev.yml
11
+ #
12
+ # Required: the same CLOUDFLARE_API_TOKEN / CLOUDFLARE_ACCOUNT_ID / DEV_D1_ID
13
+ # secrets Deploy dev uses. Optional, for copying media as well:
14
+ # R2_ACCESS_KEY_ID / R2_SECRET_ACCESS_KEY — an R2 API token (S3 credentials)
15
+ name: Refresh dev from prod
16
+
17
+ on:
18
+ workflow_dispatch:
19
+ schedule:
20
+ - cron: '0 6 * * 1'
21
+
22
+ jobs:
23
+ refresh:
24
+ uses: getvitops/design-system/.github/workflows/site-refresh-dev.yml@v1
25
+ secrets: inherit
@@ -0,0 +1 @@
1
+ 24
@@ -19,6 +19,16 @@ styled by the Vitops design system.
19
19
  `getEmDashCollection` / `getEmDashEntry` from `emdash`; render Portable Text
20
20
  with `<PortableText />` from `emdash/ui` (the Vitops blocks from
21
21
  `@getvitops/emdash` are merged in automatically).
22
+ - **Building pages from patterns:** the editor's insert menu offers the `vitops.*` blocks —
23
+ link/button, image compare, copy snippet, banner, disclosure, accordion, hero, carousel, gallery,
24
+ cards, marquee (`npx vitops docs blocks` lists them with every field). Repeating content (cards, gallery images, accordion items, carousel slides) is
25
+ a repeater field. A block cannot contain other blocks, and a `vitops.*` block inside EmDash's
26
+ built-in `columns` does not render — for layout (a section, a split, a hero) use a `blocks`
27
+ field, or hand-write the page template with `@getvitops/astro` components
28
+ (`Subgrid`, `Cards`, `Carousel`, …). `npx vitops docs components` lists every pattern, which
29
+ tiers provide it, and which block renders it. Override a block's rendering per site with
30
+ `<PortableText components={{ type: { 'vitops.cards': MyCards } }} />`; hide one with
31
+ `vitopsEmdash({ blocks: { exclude: [...] } })` in `astro.config.mjs`.
22
32
  - `seed/seed.json` only applies against an empty database: structure
23
33
  (collections, menus, settings) on first boot; the `content` entries only
24
34
  when the setup wizard's "include sample content" option is chosen. Changing
@@ -30,15 +40,16 @@ styled by the Vitops design system.
30
40
  full generated class-vocabulary reference to this file.
31
41
  - **Layout foundations — these are the answer, not a starting point.** Six
32
42
  substitutions carry the structure of nearly every page:
33
- | About to write… | Write instead |
34
- | --- | --- |
35
- | a `wrap`/`wrapper`/`container`/`inner` class | **`centered`** |
36
- | `max-width` + `margin-inline: auto` | **`centered`** |
37
- | `padding-block` on a section | **`region`** |
38
- | margins between headings/paragraphs/lists | **`rhythm`** |
39
- | `display: grid` + `grid-template-columns: repeat(n, …)` | **`subgrid`** |
40
- | `repeat(auto-fit, minmax(…))` | **`grid-auto`** |
41
- | `display: flex` + `gap` for a row of controls | **`cluster`** |
43
+
44
+ | About to write… | Write instead |
45
+ | ------------------------------------------------------- | --------------- |
46
+ | a `wrap`/`wrapper`/`container`/`inner` class | **`centered`** |
47
+ | `max-width` + `margin-inline: auto` | **`centered`** |
48
+ | `padding-block` on a section | **`region`** |
49
+ | margins between headings/paragraphs/lists | **`rhythm`** |
50
+ | `display: grid` + `grid-template-columns: repeat(n, …)` | **`subgrid`** |
51
+ | `repeat(auto-fit, minmax(…))` | **`grid-auto`** |
52
+ | `display: flex` + `gap` for a row of controls | **`cluster`** |
42
53
 
43
54
  `centered` is a grid of _named tracks_, not a max-width box — a child widens
44
55
  itself with `breakout`/`spotlight`/`fullbleed` without the parent knowing, which
@@ -105,11 +116,42 @@ styled by the Vitops design system.
105
116
  the fields its forms collect, where it deploys — rather than from guesses.
106
117
  It still needs review before launch.
107
118
 
119
+ ## EmDash 1.x behaviours worth knowing
120
+
121
+ - **Passkeys are bound to the hostname they were created on.** After the
122
+ workers.dev → custom-domain cutover, use **Settings → General → Change
123
+ domain**, then **Continue on** to add a passkey at the new address.
124
+ - **HTML blocks created in the admin run JavaScript** in a sandboxed iframe
125
+ (Contributor and up can add it). Astro's `security.csp` blocks their styles
126
+ and scripts, so don't turn CSP on without allowing for that.
127
+ - **Link entries by `entry.data.slug`, never `entry.id`** — on multilingual
128
+ sites `id` carries a locale prefix.
129
+ - **Submit `/sitemap.xml`** to search engines, not a per-collection sitemap;
130
+ collection sitemaps now split into pages of 2,000 entries.
131
+ - **A stuck D1 migration lock** is cleared with
132
+ `pnpm exec emdash migrate --take-over-lock <id>` (confirms the target, or
133
+ needs `--expected-target-fingerprint` non-interactively).
134
+
108
135
  ## Docs MCP servers
109
136
 
110
137
  `.mcp.json` preconfigures the Astro docs and EmDash docs MCP servers — prefer
111
138
  searching them over pre-trained knowledge for Astro/EmDash questions.
112
139
 
140
+ ## EmDash agent skills
141
+
142
+ The EmDash skills (`building-emdash-site`, `creating-plugins`, `emdash-cli`,
143
+ `upgrading-emdash` and the two WordPress porting skills) are installed per
144
+ project, not globally, so they match the `emdash` version this repo pins. If
145
+ `.agents/skills/building-emdash-site/` is missing, install them (the setup guide
146
+ and `SETUP.html` have the full step):
147
+
148
+ ```bash
149
+ npx skills add emdash-cms/skills --skill '*' -a claude-code -y
150
+ ```
151
+
152
+ Refresh them with `npx skills update` after bumping `emdash` — the
153
+ `upgrading-emdash` skill covers the rest of the upgrade.
154
+
113
155
  ## Commands
114
156
 
115
157
  - `pnpm dev` — local dev with local D1/R2 emulation (no Cloudflare account)
@@ -14,7 +14,8 @@ Scaffolded from `vp create @getvitops:emdash`.
14
14
  (Tailwind v4 format), generates favicons/PWA assets from
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
- slash menu: image compare, copy snippet, banner, disclosure, carousel
17
+ slash menu (hero, cards, accordion, carousel, gallery, marquee, banner, …);
18
+ `npx vitops docs blocks` lists every block and field
18
19
 
19
20
  `src/assets/placeholder/` ships neutral wireframe images for layout work
20
21
  before real imagery exists — delete once real assets land (separate from
@@ -87,7 +88,8 @@ The repo ships a two-environment flow modeled on branch = environment:
87
88
  into `main` with auto-merge enabled. With required reviews on `main`, a
88
89
  human approves the PR; merging deploys prod.
89
90
 
90
- One-time setup — work through the checklist:
91
+ One-time setup — see [Start a new EmDash site](https://docs.vitops.ca/guides/new-emdash-site) for the whole
92
+ procedure, then work through the checklist:
91
93
 
92
94
  ```bash
93
95
  pnpm run sop && open SETUP.html # or: xdg-open SETUP.html
@@ -118,12 +120,12 @@ is a configurable dimension of the project, not a rewrite:
118
120
  - **`node`** — `@astrojs/node` (standalone) + SQLite file + local uploads,
119
121
  for a VPS / docker-compose / k8s host. Not installed by default; to switch:
120
122
  `pnpm add @astrojs/node better-sqlite3`, then `vitopsHosting({ target:
121
- 'node' })` (or set `HOSTING=node` — the env var wins). Run the built site
123
+ 'node' })` (or set `HOSTING=node` — the env var wins). Run the built site
122
124
  with `node ./dist/server/entry.mjs`; data lives in `./data/` (gitignored) —
123
125
  persist it as a volume. Scheduled publishing runs in-process on Node, so
124
126
  the worker/cron pieces aren't needed. For production, move storage to
125
127
  S3-compatible and/or the database to Postgres via `vitopsHosting({ node:
126
- { database, storage } })`. CI deploy for Node hosts is TBD — the shipped
128
+ { database, storage } })`. CI deploy for Node hosts is TBD — the shipped
127
129
  workflows are Cloudflare-specific.
128
130
 
129
131
  Moving an existing site between targets (either direction) is a data
@@ -139,6 +141,10 @@ and media in the storage backend (bucket/directory copy).
139
141
  - **Skills** (`.agents/skills/`): `promote` dispatches the dev → main
140
142
  promotion workflow. Run `npx vitops agents` to also write the design-system
141
143
  class reference into `AGENTS.md`.
144
+ - **EmDash skills** are installed per project so they match the pinned `emdash`
145
+ version: `npx skills add emdash-cms/skills --skill '*' -a claude-code -y`,
146
+ then commit `.agents/skills/` and `skills-lock.json`. Update with
147
+ `npx skills update` after an `emdash` bump.
142
148
  - **Claude Code on the web**: the SessionStart hook
143
149
  (`.claude/hooks/session-start.sh`) installs Node 24 (matching
144
150
  `devEngines`), activates the pinned pnpm, and installs dependencies in the
@@ -194,6 +194,59 @@
194
194
  so.
195
195
  </span>
196
196
  </label>
197
+ <label class="custom-check">
198
+ <input type="checkbox" data-sop="hosting.content-in-prod" />
199
+ <span class="custom-check__box"
200
+ ><span class="custom-check__indicator custom-check__indicator--check"></span
201
+ ></span>
202
+ <span>
203
+ <strong>Know where content is edited (EmDash template).</strong> Editors work on the
204
+ <em>production</em> site — drafts, "Publish changes" and preview links are the
205
+ staging step. The dev site holds a disposable copy of production's database; it is
206
+ refreshed from prod and never promoted back. Code, pages and
207
+ <code>site.json</code> still go dev → main.
208
+ </span>
209
+ </label>
210
+ <label class="custom-check">
211
+ <input type="checkbox" data-sop="hosting.refresh-dev" />
212
+ <span class="custom-check__box"
213
+ ><span class="custom-check__indicator custom-check__indicator--check"></span
214
+ ></span>
215
+ <span>
216
+ EmDash template: run <strong>Refresh dev from prod</strong> (Actions tab) once the
217
+ prod site has content, and again before rehearsing a schema change. It runs weekly
218
+ on its own. To copy media as well, set <code>R2_ACCESS_KEY_ID</code> and
219
+ <code>R2_SECRET_ACCESS_KEY</code> (an R2 API token's S3 credentials) as repo
220
+ secrets; without them only the database is refreshed.
221
+ </span>
222
+ </label>
223
+ <label class="custom-check">
224
+ <input type="checkbox" data-sop="hosting.magic-link" />
225
+ <span class="custom-check__box"
226
+ ><span class="custom-check__indicator custom-check__indicator--check"></span
227
+ ></span>
228
+ <span>
229
+ EmDash template: onboard the sender domain for sign-in email —
230
+ <code>npx vitops email setup send.&lt;your-domain&gt;</code>, then put that address
231
+ in <code>cloudflareEmail({ from })</code> in <code>astro.config.mjs</code> (it ships
232
+ a placeholder). Editors sign in with a magic link ("Sign in with email" on the login
233
+ page) because a passkey only works at the origin it was registered on, so a
234
+ refreshed dev site has no other way in. Check <strong>Settings → Email</strong> and
235
+ send the test email on both prod and dev.
236
+ </span>
237
+ </label>
238
+ <label class="custom-check">
239
+ <input type="checkbox" data-sop="hosting.preview-secret" />
240
+ <span class="custom-check__box"
241
+ ><span class="custom-check__indicator custom-check__indicator--check"></span
242
+ ></span>
243
+ <span>
244
+ EmDash template: set a different <code>EMDASH_PREVIEW_SECRET</code> on the dev
245
+ worker (<code>wrangler secret put EMDASH_PREVIEW_SECRET</code>). The secret lives in
246
+ the database, so a refresh copies prod's — and prod preview links would then open
247
+ drafts on dev.
248
+ </span>
249
+ </label>
197
250
  </div>
198
251
  </details>
199
252