@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
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,87 @@
|
|
|
1
1
|
# @getvitops/create
|
|
2
2
|
|
|
3
|
+
## 0.9.0-alpha.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 161c7f3: A contact form that works end to end: `vitops setup turnstile`, `<wc-turnstile>`, the `vitops.form` EmDash block, and the route the emdash template ships to receive it.
|
|
8
|
+
|
|
9
|
+
**CSS, all formats** (no existing class changes): `forms.css` gains `.form-notice--target:not(:target)` (a message revealed only when the URL fragment names it, which is how a prerendered page shows a no-JS form's result) and `:where(wc-turnstile) { display: block }`.
|
|
10
|
+
|
|
11
|
+
**`@getvitops/core`:** new `<wc-turnstile sitekey="…">` in `elements.js`. It loads Cloudflare Turnstile on the first interaction with its form (no request to `challenges.cloudflare.com` for a visitor who only reads the page), holds an early submit until the token arrives, and leaves its slotted no-JS explanation in place until JS takes over. Turnstile is category `necessary`, so it is not behind the consent banner.
|
|
12
|
+
|
|
13
|
+
**`@getvitops/utils` / `@getvitops/cli`:** new `@getvitops/utils/turnstile` and `vitops setup turnstile [--dry] [--check] [--no-write] [--no-secret]`. It plans from the site's hostnames, creates a managed widget unless one already covers them (an existing widget is adopted, never edited, rotated or deleted), writes the public key to `site.security.turnstile.siteKey`, and pipes the one-time secret to `wrangler secret put VITOPS_TURNSTILE_SECRET` without writing it to any file. Needs `CLOUDFLARE_API_TOKEN` with **Account > Turnstile > Edit** and `CLOUDFLARE_ACCOUNT_ID`.
|
|
14
|
+
|
|
15
|
+
**`@getvitops/astro`:** `virtual:getvitops/head` gains `turnstileSiteKey` (read from `site.security.turnstile.siteKey`) so blocks can render the widget. No other change.
|
|
16
|
+
|
|
17
|
+
**`@getvitops/generator`:** `TIERS` gains a `turnstile` entry (wc-turnstile, claimed by `vitops.form`); `wc-multi-field`'s `emdashExempt` reason is reworded now the form block exists.
|
|
18
|
+
|
|
19
|
+
**`@getvitops/emdash`:** new `vitops.form` block: a field repeater (text, email, phone, long text, choose one, tick box), required flags, help text, success/error messages, optional Turnstile. It renders a semantic `<form method="post">` that works with no JavaScript, with a honeypot named per `createConversionRoute`'s defaults. A field named `phone` submits as `tel`, because the route reserves `phone` and `event`.
|
|
20
|
+
|
|
21
|
+
**`@getvitops/create`:** the emdash template ships `src/pages/api/track.ts` (`prerender = false`, `createConversionRoute()`), a seeded contact form, and `challenges.cloudflare.com` in both templates' Report-Only CSP (`script-src`, `frame-src`). The route refuses a post with nowhere to deliver and fails closed when a site key exists without `VITOPS_TURNSTILE_SECRET`. SETUP.html gains the Turnstile step. Existing sites: copy the route and the CSP change, then run `vitops setup turnstile`.
|
|
22
|
+
|
|
23
|
+
- fec1b4b: The EmDash template's two build scripts are now CLI commands, so fixes reach existing sites with `vp update` instead of a re-copy.
|
|
24
|
+
|
|
25
|
+
- `vitops emdash prepare-db` migrates and seeds the local D1 the build prerenders from (was `scripts/prepare-local-db.mjs`). The start and stop calls time out after 5 minutes and it prints progress.
|
|
26
|
+
- `vitops emdash check-prerender [dir]` fails the build if a prerendered page is not a real page (was `scripts/check-prerender.mjs`).
|
|
27
|
+
|
|
28
|
+
**Migration for a site scaffolded earlier:** delete `scripts/prepare-local-db.mjs` and `scripts/check-prerender.mjs`, then in `package.json` set `"prepare-local-db": "vitops emdash prepare-db"`, `"build": "astro build && vitops emdash check-prerender"`, and use the same two commands in place of `node scripts/…` inside `deploy` and `deploy:dev`. A site that never had the scripts needs only the `package.json` entries.
|
|
29
|
+
|
|
30
|
+
- 224a731: **`@getvitops/astro` (no CSS change; all formats unaffected)**
|
|
31
|
+
|
|
32
|
+
- **`site.fonts` is no longer silently ignored.** `vitops()` now turns webfonts on by default when the site config (`site` option, or a `css.input` that is a site config) declares `site.fonts`, and logs that it did. Before, no `@font-face`, preload or warning was emitted unless you also passed `fonts: true`. Pass `fonts: false` to opt out. Migration: none — sites that already set `fonts` behave the same; sites that declared `site.fonts` without it will start loading those families.
|
|
33
|
+
- **The icon sprite is served when the site config asks for it.** `site.icons.sprite: true` made the generator write `icons.svg`, but only the `icons` integration option copied it to `public/vitops/`, so `/vitops/icons.svg` was never served. It is now mirrored whenever the site config sets it.
|
|
34
|
+
- **Strict CSP is achievable.** New build-time exports `inlineScriptHashes()`, `cspScriptHash()` and `contentSecurityPolicy()` give the SHA-256 hashes of the two inline scripts `<Head />` emits (colour-scheme bootstrap, consent-gate stub) and a starting policy built on them, with no `'unsafe-inline'` in `script-src`. The scripts moved from `Head.astro`'s frontmatter to `src/inline-scripts.ts` (shipped in the package) so the hash and the emitted bytes are the same constant; the rendered output is byte-identical.
|
|
35
|
+
|
|
36
|
+
**`@getvitops/create` (templates; existing scaffolded sites are not changed)**
|
|
37
|
+
|
|
38
|
+
- **emdash: every public page is prerendered.** `index.astro`, `[...slug].astro` (via `getStaticPaths` over the published `pages`) and `404.astro` set `prerender = true`. Unknown URLs now return the prerendered 404 with a real 404 status — the old `Astro.rewrite('/404')` from an on-demand route to a prerendered one was refused by Astro, so every unknown URL was a 500. `scripts/pull-content.mjs` (first step of `deploy`/`deploy:dev`) loads production's D1 into the local build database, because the Cloudflare adapter prerenders against a local D1 that is empty on CI; it fails closed. The deploy-prod workflow also runs on a `repository_dispatch` of type `emdash-publish`. Migration for an existing site: copy those pages, the script and the two `package.json` scripts, and add the trigger; previews of unpublished drafts no longer work on the public routes (see AGENTS.md).
|
|
39
|
+
- **emdash: seed menus are `primary` (Contact), `footer-sitemap` (Home, Contact) and `footer-legal` (Privacy Policy, Terms & Conditions, Cookie Notice)**, replacing `primary` (Home, About) and `footer` (Terms, Privacy), with placeholder `contact` and `cookies` pages so every seeded link resolves. The template Layout still reads the old menu names until it is updated; the seed only applies to an empty database, so existing sites keep their menus.
|
|
40
|
+
- **Both templates: `badge` and `tag` patterns (group `label`) are enabled, `button` carries `class: "btn"` so `<a class="btn">` is styled, and `neutral`/`surface` are declared as surface roles** (they were chromatic, which emitted no `.bg-neutral`/`text-neutral-muted`). css/bricks/tailwind output of a scaffolded site changes accordingly.
|
|
41
|
+
- **Both templates ship `public/_headers` with a `Content-Security-Policy-Report-Only` default** naming `<Head />`'s inline scripts by hash. It blocks nothing. `_headers` applies to static assets only, not Worker-rendered responses; see AGENTS.md for the EmDash admin and HTML-block trade-off.
|
|
42
|
+
- **Guidance is Vite+-first**: `AGENTS.md`, README, the `setup` skill, `SETUP.html` and the session hook use `vp`/`vpr`/`vp exec` instead of `pnpm`/`npx`, with one command table; `vpr dev` is documented as returning immediately. There is no `vp change`/`vp pm change` (checked against vp 1.5.0); the release flow is `dev` → promote.
|
|
43
|
+
- emdash ships `.agents/skills/custom-blocks/SKILL.md` (compose, extend, site-local, upstream; naming and lifecycle rules) and the favicon `themeColor`/`name` now derive from `site.json`.
|
|
44
|
+
- Label vocabulary: `badge` is a static label, `tag` is static or dynamic; `chip`/`pill` are not used.
|
|
45
|
+
|
|
46
|
+
**`@getvitops/cli`**: the bundled `vitops-design-system` skill points at `vp exec vitops docs <topic>`.
|
|
47
|
+
|
|
48
|
+
No consumer-facing change in `@getvitops/core`, `@getvitops/generator`, `@getvitops/utils` or `@getvitops/vite`.
|
|
49
|
+
|
|
50
|
+
- 55061e2: **Added: `<SiteNav>` and `<SiteFooter>` for Astro/EmDash sites.** Both are tier-1 markup over the existing `sitenav` pattern (no runtime JS: `<details>` accordions + Popover API drawer). Non-breaking.
|
|
51
|
+
|
|
52
|
+
- `<SiteNav {...await loadNavMenus(getMenu)} />` renders EmDash menu `primary` (the default: depth 3, dropdowns from the `md` container width, drawer below). If a `primary-mobile` menu exists it replaces the nav below `md` (depth 1); if `primary-mega` exists it replaces it from `xl` (mega panels). Adding the menu in the admin is the whole change. Only one tier is in the accessibility tree at a time. An item's `titleAttr` is its description (EmDash has no description field yet).
|
|
53
|
+
- `<SiteFooter sitemap legal name>` renders `footer-sitemap` + `footer-legal` + a copyright line.
|
|
54
|
+
- `@getvitops/astro/components/sitenav.ts` exports the pure helpers (`loadNavMenus`, `selectTiers`, ...).
|
|
55
|
+
- CSS (`patterns/sitenav.css`, **all formats**): tier visibility classes (`.sitenav-tier--below-*`/`--from-*`), `.sitenav--mega`, `.sitenav__desc`, `.sitefooter*`, `aria-current` styling, reduced-motion and forced-colors handling. Existing `.sitenav` rules are unchanged, so the Bricks element is unaffected.
|
|
56
|
+
- `@getvitops/create` (emdash template): the layout now renders header and footer through these components, with the colour-scheme toggle in the header. It reads menus `primary`, `footer-sitemap` and `footer-legal`; a template whose seed still has the old `footer` menu renders no footer links until the seed is updated.
|
|
57
|
+
- `@getvitops/generator`: `TIERS.sitenav` now lists the two Astro components.
|
|
58
|
+
|
|
59
|
+
- 7a4241e: EmDash template: make the database a build prerenders from valid before it builds, and fail the build when it isn't. The template runs `migrations: { runtime: 'check' }`, so a build against a local D1 with pending migrations prerendered EmDash's one-line "Database migrations are required" response into every page while still succeeding. A fresh CI runner, a first deploy, a `SKIP_PULL_CONTENT=1` build and an upgrade deploy all hit it.
|
|
60
|
+
|
|
61
|
+
- **New `scripts/prepare-local-db.mjs`** (`vpr prepare-local-db`): starts `astro dev` once, calls EmDash's `dev-bypass` setup (which migrates and applies the seed's sample content), checks the home page renders, and stops it. Writes only the disposable `.wrangler/state`. Run after `pull-content` by `vpr deploy`, `vpr deploy:dev` and the reusable deploy workflow. `SKIP_PREPARE_LOCAL_DB=1` skips it.
|
|
62
|
+
- **New `scripts/check-prerender.mjs`**, part of `vpr build` and both deploy scripts: fails when any prerendered page is not an HTML document with a title, or carries the pending-migrations message.
|
|
63
|
+
- **Existing sites:** add the two scripts, the `prepare-local-db` script entry, and `&& node scripts/check-prerender.mjs` after `astro build`; add a "Prepare local database" step before each Build step in a copied deploy workflow. Run `vpr prepare-local-db` once before a first local `vpr build`.
|
|
64
|
+
|
|
65
|
+
No consumer-facing change in the other toolchain packages.
|
|
66
|
+
|
|
67
|
+
### Patch Changes
|
|
68
|
+
|
|
69
|
+
- 06d6112: Fix the EmDash `/design-system` admin page erroring on a tailwind site. It fetches `/vitops/design-manifest.json`, which the `tailwind` format never emitted and the integration only mirrored under `editor: true` (which also loads `editor.js` on every public page).
|
|
70
|
+
|
|
71
|
+
- **generator** (`tailwind` format only): `design-manifest.json` is now emitted alongside `tailwind.css`. No change to the CSS.
|
|
72
|
+
- **astro**: new `vitops({ editorManifest: true })` writes everything an outside host needs into `public/vitops/` (`design-manifest.json`, `editor.js`, a standalone `editor.css`, and `design-system-editor.html`) without loading the editor on public pages. `editor: true` still loads it from `<Head />`.
|
|
73
|
+
- **vite**: `editorManifestDir` also writes `vitops/editor.css`, from a throwaway `css`-format pass on tailwind sites.
|
|
74
|
+
- **emdash**: the `/design-system` admin page was unstyled and overflowed the admin, because the editor borrows the framework CSS and the admin never loads it. It now embeds `/vitops/design-system-editor.html` in an iframe, so the design system can't leak into the admin's styles. **Requires `editorManifest: true`** in `vitops({ … })`.
|
|
75
|
+
- **create**: the EmDash template sets `editorManifest: true`. Existing sites: add it to `vitops({ … })` in `astro.config.mjs`.
|
|
76
|
+
|
|
77
|
+
No consumer-facing change in the other toolchain packages.
|
|
78
|
+
|
|
79
|
+
## 0.8.2
|
|
80
|
+
|
|
81
|
+
### Patch Changes
|
|
82
|
+
|
|
83
|
+
- 29fc659: EmDash template: the per-project EmDash skills install command is now `npx skills add emdash-cms/skills --skill '*' -y`. Without `-a`, the CLI writes the skills to `.agents/skills/` and symlinks them into `.claude/skills/`, so Claude Code and every other agent see the same files. The 0.8.0 notes named `-a claude-code`, which copies into `.claude/skills/` only.
|
|
84
|
+
|
|
3
85
|
## 0.8.1
|
|
4
86
|
|
|
5
87
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getvitops/create",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0-alpha.0",
|
|
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.
|
|
22
|
+
"@getvitops/generator": "8.3.0-alpha.0"
|
|
23
23
|
},
|
|
24
24
|
"engines": {
|
|
25
25
|
"node": ">=24"
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: custom-blocks
|
|
3
|
+
description: Decide whether a page section needs a new EmDash block type, and if so how to name, build, version and retire it. Use before creating, renaming or deleting any block type, any `src/components/blocks/*` renderer, or when asked to "add a block", "make an editable section" or "add a component the editors can place".
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Custom blocks
|
|
7
|
+
|
|
8
|
+
Pages are composed from **blocks** so non-technical editors can rearrange and edit
|
|
9
|
+
them in the CMS. A new block type is the most expensive way to get a section onto a
|
|
10
|
+
page: it adds a content-model entry, a renderer, a field schema and a migration
|
|
11
|
+
burden, and **a block type cannot be renamed or deleted** once pages use it (see
|
|
12
|
+
"Lifecycle"). So work down this list and stop at the first rung that fits.
|
|
13
|
+
|
|
14
|
+
## 1. Decision rule
|
|
15
|
+
|
|
16
|
+
1. **Compose what exists.** The `vitops.*` blocks from `@getvitops/emdash` (cards,
|
|
17
|
+
carousel, gallery, accordion, details, banner, hero, marquee, image-compare,
|
|
18
|
+
copy button, action link, …) resolve inside any block type's `portableText` field,
|
|
19
|
+
so "a section with a heading, intro and then a row of cards" is a section block
|
|
20
|
+
whose body holds `vitops.cards` — not a new "cards section" type. Check what is
|
|
21
|
+
installed first: `vp exec vitops docs components` lists every block the toolchain
|
|
22
|
+
ships and which tier provides it.
|
|
23
|
+
2. **Extend an existing block** if one is close. An extra option (a `variant`
|
|
24
|
+
select, an optional slot) on a block that already exists is a version bump on that
|
|
25
|
+
block, not a new type. Prefer this when the new thing is a variation of the same
|
|
26
|
+
content shape.
|
|
27
|
+
3. **Site-local block type** only when the content shape is specific to this site
|
|
28
|
+
(a bespoke dashboard mock, an A-versus-B argument with hard-coded figures). Keep
|
|
29
|
+
the renderer in `src/components/blocks/` and the type name prefixed for the site
|
|
30
|
+
(see "Naming"). Code-owned widgets expose only a caption field; the numbers live in
|
|
31
|
+
code, not in the CMS.
|
|
32
|
+
4. **Upstream it** when a second site would plausibly want it. Don't build a
|
|
33
|
+
generic block here and quietly fork it: raise it against the toolchain
|
|
34
|
+
(`getvitops/design-system`) with the shape you used, so every site gets it. Until
|
|
35
|
+
it ships, keep the local version and mark it `local (watch)` in a comment, so it
|
|
36
|
+
is easy to retire when the real one lands.
|
|
37
|
+
|
|
38
|
+
A form, a section container, a stats row, a logo grid, a CTA band and a steps list
|
|
39
|
+
are all _generic_ — expect to find them upstream or to upstream them, not to keep
|
|
40
|
+
them local.
|
|
41
|
+
|
|
42
|
+
## 2. Naming: check the framework's vocabulary first
|
|
43
|
+
|
|
44
|
+
Block types are permanent, so the name has to be right the first time. Before you
|
|
45
|
+
name one:
|
|
46
|
+
|
|
47
|
+
- Run `vp exec vitops docs components` and `vp exec vitops docs classes`. If the
|
|
48
|
+
framework already has a pattern by that name, **use its meaning**: `badge` is a
|
|
49
|
+
_static_ label; `tag` is a label that may be static or dynamic (editable,
|
|
50
|
+
dismissable). Never `chip` or `pill`. A block called `chips` that renders badges
|
|
51
|
+
cost one site a new type, a content migration and a permanently retired type.
|
|
52
|
+
- Prefix site-local types with a short site code (`acme_section`), never `vitops.`
|
|
53
|
+
— that namespace belongs to the toolchain.
|
|
54
|
+
- Name the thing, not its look: `section`, `steps`, `logos`; not `green-box`.
|
|
55
|
+
|
|
56
|
+
## 3. Field and renderer conventions
|
|
57
|
+
|
|
58
|
+
- **Colour: `tone` plus optional `color`.** `tone` takes a **role** name from
|
|
59
|
+
`site.json`'s `colors.roles` (preferred), falling back to a semantic name
|
|
60
|
+
(`info` `success` `warning` `danger`); `color` is the escape hatch for a palette
|
|
61
|
+
hue/step and should be rare — a site that sets it on every item is telling you a
|
|
62
|
+
role is missing. A select field can only list static options, so either list the
|
|
63
|
+
roles you actually use or take a validated string. Resolve it in one shared helper
|
|
64
|
+
(`src/components/blocks/tone.ts`) rather than per block.
|
|
65
|
+
- **Class names cannot be built from field values.** Tailwind only emits classes it
|
|
66
|
+
can read in source, so `` `bg-${tone}` `` produces nothing. Resolve a tone to CSS
|
|
67
|
+
custom properties (or a lookup table of whole class names) instead.
|
|
68
|
+
- **Links are `url` fields**, not `string`: EmDash's `url` type accepts site-relative
|
|
69
|
+
paths. (`string` is a mistake that needs a breaking version to undo.)
|
|
70
|
+
- **Repeater sub-fields are scalar-only** (no `portableText`, no links inside text),
|
|
71
|
+
so a checklist is one newline-separated `text` field. Don't fight it; rich item
|
|
72
|
+
bodies need EmDash to lift that limit.
|
|
73
|
+
- **Blocks don't nest.** A "section containing stats" is the section's own body
|
|
74
|
+
holding `vitops.*` blocks, or a flat sequence of sibling blocks; per-section
|
|
75
|
+
backdrops don't carry across siblings.
|
|
76
|
+
- **Reach for framework classes** (`centered`, `region`, `rhythm`, `subgrid`,
|
|
77
|
+
`cluster`, `font-*`, `card`, `badge`, `tag`) in the renderer; run
|
|
78
|
+
`vpr lint:design` — hand-written layout CSS in a block is the signal that the
|
|
79
|
+
framework is missing a pattern worth upstreaming.
|
|
80
|
+
- A card title is a heading: render a real `<h2>`–`<h4>` with a level prop, not a
|
|
81
|
+
styled `<p>`.
|
|
82
|
+
|
|
83
|
+
## 4. Lifecycle
|
|
84
|
+
|
|
85
|
+
- **Create** with the schema tools (the `schema_*` actions on the site's MCP server,
|
|
86
|
+
`/_emdash/api/mcp`) or the admin; commit the matching renderer and the
|
|
87
|
+
`emdash export-seed` output so a fresh environment gets the same model.
|
|
88
|
+
- **A breaking change is a new version**, not an edit: add the version, migrate the
|
|
89
|
+
entries that use the old one, then activate it. Two things the tooling does not say:
|
|
90
|
+
`schema_activate_block_type_version`'s `expectedFingerprint` is the fingerprint of
|
|
91
|
+
the **currently active** version, not the one you are activating (a mismatch is a
|
|
92
|
+
`CONFLICT`), and `schema_update_block_type` **replaces the whole `fields` list**, so
|
|
93
|
+
send every field, not just the changed one.
|
|
94
|
+
- **Migrating content:** `emdash content update` has no `--migrate-blocks` flag; the
|
|
95
|
+
REST route `PUT /_emdash/api/content/<collection>/<id>` with
|
|
96
|
+
`{ data, _rev, migrateBlocks: true }` does (it takes Portable Text arrays; the CLI
|
|
97
|
+
and MCP take Markdown). Updating a published entry stages a **draft** — a separate
|
|
98
|
+
`content publish` is needed before the live page changes, and (because pages are
|
|
99
|
+
prerendered) a rebuild after that.
|
|
100
|
+
- **Retiring is not deleting.** A type that was renamed or superseded stays in the
|
|
101
|
+
database and the seed permanently; keep its renderer mapping old values so existing
|
|
102
|
+
content still renders, mark it retired, and stop offering it to editors.
|
|
103
|
+
- **Typed fields:** `emdash types` does not type `blocks` fields (they come out as
|
|
104
|
+
`unknown[]`); hand-write a value type per renderer until EmDash generates it.
|
|
@@ -7,14 +7,14 @@ description: Work the client-onboarding checklist (SETUP.html) autonomously —
|
|
|
7
7
|
|
|
8
8
|
`SETUP.html` is the canonical checklist — this skill adds only sequencing,
|
|
9
9
|
gates, and where to stop. It does not restate the steps; read `SETUP.html`
|
|
10
|
-
(or `
|
|
10
|
+
(or `vp exec vitops setup --help`) for what each stage actually does and why.
|
|
11
11
|
|
|
12
12
|
## Steps
|
|
13
13
|
|
|
14
14
|
1. **Preview the whole sequence first.**
|
|
15
15
|
|
|
16
16
|
```bash
|
|
17
|
-
|
|
17
|
+
vp exec vitops setup all --dry
|
|
18
18
|
```
|
|
19
19
|
|
|
20
20
|
Read the plan before doing anything. It names every step it would take and
|
|
@@ -24,10 +24,10 @@ gates, and where to stop. It does not restate the steps; read `SETUP.html`
|
|
|
24
24
|
2. **Run it.**
|
|
25
25
|
|
|
26
26
|
```bash
|
|
27
|
-
|
|
27
|
+
vp exec vitops setup all
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
-
Re-run `
|
|
30
|
+
Re-run `vp exec vitops setup --check` after each leg rather than retrying on
|
|
31
31
|
your own judgment — a step already done resolves to a no-op skip, so a
|
|
32
32
|
second run is safe, and `--check` tells you exactly what still needs doing
|
|
33
33
|
instead of you guessing from output.
|
|
@@ -31,10 +31,13 @@ export PATH="$NODE24_BIN:$PATH"
|
|
|
31
31
|
echo "export PATH=\"$NODE24_BIN:\$PATH\"" >> "$CLAUDE_ENV_FILE"
|
|
32
32
|
|
|
33
33
|
# --- Dependencies (pnpm, pinned via package.json > packageManager) -----------
|
|
34
|
+
# pnpm is the bootstrap here: `vp` (Vite+) is a devDependency, so it only exists
|
|
35
|
+
# once this install has run. After that, use `vp install` / `vpr <script>` /
|
|
36
|
+
# `vp exec <bin>` — see AGENTS.md "Commands".
|
|
34
37
|
cd "$CLAUDE_PROJECT_DIR"
|
|
35
38
|
export COREPACK_ENABLE_DOWNLOAD_PROMPT=0
|
|
36
39
|
corepack enable
|
|
37
40
|
# `install` (not `--frozen-lockfile`) so the warmed container cache is reusable.
|
|
38
41
|
pnpm install
|
|
39
42
|
|
|
40
|
-
echo "Ready: Node $(node --version), pnpm $(pnpm --version)"
|
|
43
|
+
echo "Ready: Node $(node --version), pnpm $(pnpm --version), vp $(./node_modules/.bin/vp --version 2>/dev/null | head -1 || echo 'not installed')"
|
|
@@ -8,12 +8,23 @@
|
|
|
8
8
|
# Deploys via the vitops toolchain's reusable EmDash deploy workflow — see
|
|
9
9
|
# https://github.com/getvitops/design-system/blob/main/.github/workflows/site-deploy-emdash.yml
|
|
10
10
|
#
|
|
11
|
+
# Also runs on a `repository_dispatch` of type `emdash-publish`: pages are
|
|
12
|
+
# prerendered, so a publish in the EmDash admin only goes live after a rebuild,
|
|
13
|
+
# and an EmDash `content:afterPublish` hook POSTs that event (AGENTS.md,
|
|
14
|
+
# "Publish-triggered rebuilds"). A dispatch runs this file from the DEFAULT branch
|
|
15
|
+
# (`dev`), so `ref` pins the build to `main` — production's code, not dev's. It
|
|
16
|
+
# still goes through the `production` environment, so it still waits for the
|
|
17
|
+
# reviewers' approval; relax that rule if editors' publishes should go live
|
|
18
|
+
# unattended.
|
|
19
|
+
#
|
|
11
20
|
# Required repo secrets: CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID
|
|
12
21
|
name: Deploy prod
|
|
13
22
|
|
|
14
23
|
on:
|
|
15
24
|
push:
|
|
16
25
|
branches: [main]
|
|
26
|
+
repository_dispatch:
|
|
27
|
+
types: [emdash-publish]
|
|
17
28
|
|
|
18
29
|
concurrency:
|
|
19
30
|
group: deploy-prod
|
|
@@ -25,6 +36,7 @@ jobs:
|
|
|
25
36
|
with:
|
|
26
37
|
environment: production
|
|
27
38
|
is-dev: false
|
|
39
|
+
ref: ${{ github.event_name == 'repository_dispatch' && 'main' || '' }}
|
|
28
40
|
# Set this to your production URL once known.
|
|
29
41
|
health-check-url: ''
|
|
30
42
|
secrets:
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
This is an EmDash CMS website (Astro
|
|
2
|
-
styled by the Vitops design system.
|
|
1
|
+
This is an EmDash CMS website (Astro on Cloudflare Workers, D1 + R2 — public pages
|
|
2
|
+
prerendered, the Worker serving the admin and API) styled by the Vitops design system.
|
|
3
3
|
|
|
4
4
|
## Ground rules
|
|
5
5
|
|
|
@@ -21,11 +21,11 @@ styled by the Vitops design system.
|
|
|
21
21
|
`@getvitops/emdash` are merged in automatically).
|
|
22
22
|
- **Building pages from patterns:** the editor's insert menu offers the `vitops.*` blocks —
|
|
23
23
|
link/button, image compare, copy snippet, banner, disclosure, accordion, hero, carousel, gallery,
|
|
24
|
-
cards, marquee (`
|
|
24
|
+
cards, marquee (`vp exec vitops docs blocks` lists them with every field). Repeating content (cards, gallery images, accordion items, carousel slides) is
|
|
25
25
|
a repeater field. A block cannot contain other blocks, and a `vitops.*` block inside EmDash's
|
|
26
26
|
built-in `columns` does not render — for layout (a section, a split, a hero) use a `blocks`
|
|
27
27
|
field, or hand-write the page template with `@getvitops/astro` components
|
|
28
|
-
(`Subgrid`, `Cards`, `Carousel`, …). `
|
|
28
|
+
(`Subgrid`, `Cards`, `Carousel`, …). `vp exec vitops docs components` lists every pattern, which
|
|
29
29
|
tiers provide it, and which block renders it. Override a block's rendering per site with
|
|
30
30
|
`<PortableText components={{ type: { 'vitops.cards': MyCards } }} />`; hide one with
|
|
31
31
|
`vitopsEmdash({ blocks: { exclude: [...] } })` in `astro.config.mjs`.
|
|
@@ -36,7 +36,7 @@ styled by the Vitops design system.
|
|
|
36
36
|
(`/_emdash/admin`) or the site's MCP server (`/_emdash/api/mcp`) to change
|
|
37
37
|
the content model of a running site.
|
|
38
38
|
- Prefer framework CSS classes (utilities, patterns, type roles like
|
|
39
|
-
`font-heading`) over bespoke CSS. Run `
|
|
39
|
+
`font-heading`) over bespoke CSS. Run `vp exec vitops agents` to append the
|
|
40
40
|
full generated class-vocabulary reference to this file.
|
|
41
41
|
- **Layout foundations — these are the answer, not a starting point.** Six
|
|
42
42
|
substitutions carry the structure of nearly every page:
|
|
@@ -70,18 +70,81 @@ styled by the Vitops design system.
|
|
|
70
70
|
`li` is the grid item, so the anchor's tranches never align and it does not fill
|
|
71
71
|
the cell; it renders fine, which is why it keeps getting written.
|
|
72
72
|
|
|
73
|
-
`
|
|
73
|
+
`vpr lint:design` reports all of these, and the pre-commit hook fails on them
|
|
74
74
|
— the fix is the class, not a justification for the CSS.
|
|
75
75
|
|
|
76
|
-
- **
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
76
|
+
- **Rendering and freshness: everything is prerendered.** `output: 'server'` is
|
|
77
|
+
still required (the admin, media/API routes and scheduled publishing need a
|
|
78
|
+
Worker), but every public page — `index.astro`, `[...slug].astro`, `404.astro` —
|
|
79
|
+
has `export const prerender = true`, and `[...slug].astro` lists the published
|
|
80
|
+
`pages` entries in `getStaticPaths()`. The HTML is written at build time and
|
|
81
|
+
served as a static asset; the Worker only handles `/_emdash/*` and anything
|
|
82
|
+
you deliberately leave on-demand. A route with no `prerender` export is
|
|
83
|
+
re-rendered on every request, so don't leave it off by accident.
|
|
84
|
+
- **Where build-time content comes from.** The Cloudflare adapter prerenders
|
|
85
|
+
against a _local_ D1 (`.wrangler/state`), never the deployed one — on a CI
|
|
86
|
+
runner that is an empty database, which would bake "No home page yet" into
|
|
87
|
+
every page. `scripts/pull-content.mjs` (the first step of `vpr deploy` and
|
|
88
|
+
`vpr deploy:dev`) exports production's D1 and loads it locally first. It
|
|
89
|
+
fails closed: a failed export aborts the deploy rather than publishing an
|
|
90
|
+
empty site (the one exception is a database that doesn't exist yet, the first
|
|
91
|
+
deploy, which builds from the seed). `SKIP_PULL_CONTENT=1` skips it.
|
|
92
|
+
- **The local database must be migrated before the build.** The template runs
|
|
93
|
+
`migrations: { runtime: 'check' }`, so a build against a local database with
|
|
94
|
+
pending migrations prerenders EmDash's one-line "Database migrations are
|
|
95
|
+
required" response into every page — and still succeeds. That is what a fresh
|
|
96
|
+
runner, a first deploy, a `SKIP_PULL_CONTENT=1` build and an **upgrade** deploy
|
|
97
|
+
(the pull restores production's older schema, and `emdash migrate` only runs
|
|
98
|
+
after the build) would all publish. `vitops emdash prepare-db` (`vpr prepare-local-db`, run after the
|
|
99
|
+
pull by `vpr deploy`, `vpr deploy:dev` and the reusable workflow) starts `astro
|
|
100
|
+
dev` once, calls EmDash's `dev-bypass` setup (which migrates and applies the
|
|
101
|
+
seed's sample content) and stops it; `vitops emdash check-prerender` (part of
|
|
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.
|
|
85
148
|
- The Worker cron trigger in `wrangler.jsonc` drives scheduled publishing —
|
|
86
149
|
don't remove it.
|
|
87
150
|
- Hosting (adapter + EmDash database/storage) comes from `vitopsHosting()`
|
|
@@ -110,7 +173,7 @@ styled by the Vitops design system.
|
|
|
110
173
|
a finished policy, because the risk here is a plausible-looking document
|
|
111
174
|
getting shipped unread.
|
|
112
175
|
To replace them properly, fill in `site.json`'s `organization`/`site.legal`
|
|
113
|
-
fields and run `
|
|
176
|
+
fields and run `vp exec vitops legal --format portable-text --out ./legal`, then
|
|
114
177
|
paste each document's blocks into the page in the admin. That renders a real
|
|
115
178
|
policy from what the site actually does — the analytics provider it uses,
|
|
116
179
|
the fields its forms collect, where it deploys — rather than from guesses.
|
|
@@ -129,7 +192,7 @@ styled by the Vitops design system.
|
|
|
129
192
|
- **Submit `/sitemap.xml`** to search engines, not a per-collection sitemap;
|
|
130
193
|
collection sitemaps now split into pages of 2,000 entries.
|
|
131
194
|
- **A stuck D1 migration lock** is cleared with
|
|
132
|
-
`
|
|
195
|
+
`vp exec emdash migrate --take-over-lock <id>` (confirms the target, or
|
|
133
196
|
needs `--expected-target-fingerprint` non-interactively).
|
|
134
197
|
|
|
135
198
|
## Docs MCP servers
|
|
@@ -137,6 +200,92 @@ styled by the Vitops design system.
|
|
|
137
200
|
`.mcp.json` preconfigures the Astro docs and EmDash docs MCP servers — prefer
|
|
138
201
|
searching them over pre-trained knowledge for Astro/EmDash questions.
|
|
139
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
|
+
|
|
140
289
|
## EmDash agent skills
|
|
141
290
|
|
|
142
291
|
The EmDash skills (`building-emdash-site`, `creating-plugins`, `emdash-cli`,
|
|
@@ -146,20 +295,55 @@ project, not globally, so they match the `emdash` version this repo pins. If
|
|
|
146
295
|
and `SETUP.html` have the full step):
|
|
147
296
|
|
|
148
297
|
```bash
|
|
149
|
-
|
|
298
|
+
vp dlx skills add emdash-cms/skills --skill '*' -y
|
|
150
299
|
```
|
|
151
300
|
|
|
152
|
-
Refresh them with `
|
|
301
|
+
Refresh them with `vp dlx skills update` after bumping `emdash` — the
|
|
153
302
|
`upgrading-emdash` skill covers the rest of the upgrade.
|
|
154
303
|
|
|
155
304
|
## Commands
|
|
156
305
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
306
|
+
This is a **Vite+** project: use `vp` (and its `vpr` shorthand), not `pnpm`/`npm`/`npx`.
|
|
307
|
+
The package manager underneath is pnpm, but `vp` picks it up for you.
|
|
308
|
+
|
|
309
|
+
| To… | Run |
|
|
310
|
+
| ---------------------------------------- | ------------------------------------------------------- |
|
|
311
|
+
| install dependencies | `vp install` |
|
|
312
|
+
| add / remove / update a package | `vp add <pkg>` · `vp remove <pkg>` · `vp update` |
|
|
313
|
+
| run a `package.json` script | `vpr <script>` (alias for `vp run <script>`) |
|
|
314
|
+
| run a locally installed binary | `vp exec <bin>` (e.g. `vp exec vitops docs components`) |
|
|
315
|
+
| run a one-off package without installing | `vp dlx <pkg>` |
|
|
316
|
+
| format + lint + typecheck (files) | `vp check` |
|
|
317
|
+
| 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.
|
|
327
|
+
|
|
328
|
+
| Script | What it does |
|
|
329
|
+
| ----------------------------------- | ------------------------------------------------------------------------------------------- |
|
|
330
|
+
| `vpr dev` | local dev with local D1/R2 emulation (no Cloudflare account) |
|
|
331
|
+
| `vpr deploy` | pull production content, build, `wrangler deploy` prod (provisions D1/R2 on first run) |
|
|
332
|
+
| `vpr deploy:dev` | same, for the isolated dev worker (needs `DEV_D1_ID`) |
|
|
333
|
+
| `vpr pull-content` | just the content pull (see "Rendering and freshness") |
|
|
334
|
+
| `vpr prepare-local-db` | migrate and seed the local D1 the build prerenders from |
|
|
335
|
+
| `vpr init:github` | one-time: create + push the GitHub repo (main + dev, dev default). Only when the user asks. |
|
|
336
|
+
| `vpr init:hosting` | one-time: provision hosting (dev D1/R2 + repo secrets). Only when the user asks. |
|
|
337
|
+
| `vp exec vitops validate site.json` | schema-check the site config |
|
|
338
|
+
|
|
339
|
+
**Release flow.** A site is not a published package, so there is no changeset or
|
|
340
|
+
version bump: ship by committing to `dev` (auto-deploys the dev worker) and then
|
|
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`.
|
|
344
|
+
|
|
345
|
+
**Naming the project.** `vp create` takes the package name from the target
|
|
346
|
+
directory and invents a random slug when none is given, so pass one:
|
|
347
|
+
`vp create @getvitops:emdash --directory <client-slug>`. `wrangler.jsonc`'s
|
|
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.
|