@getvitops/create 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +65 -0
- package/package.json +22 -0
- package/templates/emdash/.agents/skills/promote/SKILL.md +70 -0
- package/templates/emdash/.claude/hooks/session-start.sh +40 -0
- package/templates/emdash/.claude/settings.json +14 -0
- package/templates/emdash/.github/workflows/deploy-dev.yml +41 -0
- package/templates/emdash/.github/workflows/deploy-prod.yml +36 -0
- package/templates/emdash/.github/workflows/promote.yml +49 -0
- package/templates/emdash/.mcp.json +12 -0
- package/templates/emdash/AGENTS.md +50 -0
- package/templates/emdash/README.md +136 -0
- package/templates/emdash/_gitignore +25 -0
- package/templates/emdash/astro.config.mjs +41 -0
- package/templates/emdash/design-system.json +148 -0
- package/templates/emdash/package.json +46 -0
- package/templates/emdash/pnpm-workspace.yaml +34 -0
- package/templates/emdash/scripts/build-dev-wrangler.mjs +75 -0
- package/templates/emdash/seed/seed.json +343 -0
- package/templates/emdash/src/assets/logo.svg +6 -0
- package/templates/emdash/src/layouts/Layout.astro +133 -0
- package/templates/emdash/src/live.config.ts +12 -0
- package/templates/emdash/src/pages/404.astro +11 -0
- package/templates/emdash/src/pages/[...slug].astro +20 -0
- package/templates/emdash/src/pages/index.astro +27 -0
- package/templates/emdash/src/worker.ts +4 -0
- package/templates/emdash/tsconfig.json +5 -0
- package/templates/emdash/vite.config.ts +17 -0
- package/templates/emdash/wrangler.jsonc +34 -0
package/README.md
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# @getvitops/create
|
|
2
|
+
|
|
3
|
+
Project templates for the Vitops design system, consumed by
|
|
4
|
+
[Vite+](https://viteplus.dev)'s `vp create` via the `createConfig.templates`
|
|
5
|
+
manifest in this package's `package.json`.
|
|
6
|
+
|
|
7
|
+
## Usage
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# Interactive picker over this package's templates
|
|
11
|
+
vp create @getvitops
|
|
12
|
+
|
|
13
|
+
# Scaffold a specific template directly
|
|
14
|
+
vp create @getvitops:emdash
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
`vp create` copies the template, initializes git, installs dependencies, and
|
|
18
|
+
(optionally) writes agent/editor config — see `vp create --help`.
|
|
19
|
+
|
|
20
|
+
## Templates
|
|
21
|
+
|
|
22
|
+
### `emdash`
|
|
23
|
+
|
|
24
|
+
An [EmDash CMS](https://docs.emdashcms.com) website on Cloudflare Workers,
|
|
25
|
+
with every relevant `@getvitops/*` package pre-wired:
|
|
26
|
+
|
|
27
|
+
- **`@getvitops/astro`** — the `getvitops()` integration: design-system CSS
|
|
28
|
+
generated from `design-system.json` (Tailwind v4 format, layout-scoped via
|
|
29
|
+
`css.inject: false`), favicons/PWA from `src/assets/logo.svg`, and the
|
|
30
|
+
web-component runtime copied into `public/vitops/`.
|
|
31
|
+
- **`@getvitops/emdash`** — the EmDash native plugin: Vitops Portable Text
|
|
32
|
+
blocks (image compare, copy snippet, banner, disclosure, carousel) editors
|
|
33
|
+
insert from the admin slash menu, rendered with the design-system web
|
|
34
|
+
components (accessible no-JS fallbacks included).
|
|
35
|
+
- **`@getvitops/cli`** — `vitops` on the scaffolded project's PATH for
|
|
36
|
+
`vitops validate`, `vitops agents`, and manual `vitops generate` runs.
|
|
37
|
+
- **EmDash on Cloudflare** — D1 database + R2 media via
|
|
38
|
+
`@emdash-cms/cloudflare`, a Worker entry with the cron trigger for
|
|
39
|
+
scheduled publishing, and a seed (`seed/seed.json`): the `pages`
|
|
40
|
+
collection, `primary`/`footer` menus, and settings apply on first boot; the
|
|
41
|
+
starter pages (home, about, terms, privacy — plus a 404 route) publish when
|
|
42
|
+
the setup wizard's "include sample content" option is chosen.
|
|
43
|
+
- **dev → main promotion flow** — GitHub Actions deploying `dev` to an
|
|
44
|
+
isolated `-dev` worker (`dev.<domain>` with a custom domain; own D1/R2 via
|
|
45
|
+
`scripts/build-dev-wrangler.mjs`, which hard-fails without `DEV_D1_ID`) and
|
|
46
|
+
`main` to prod, plus a manual `promote.yml` workflow and a matching
|
|
47
|
+
`promote` agent skill (`.agents/skills/promote/`) that opens the
|
|
48
|
+
`dev → main` PR with auto-merge.
|
|
49
|
+
- **AI tooling baked in** — `.mcp.json` with the Astro + EmDash docs MCP
|
|
50
|
+
servers, and a Claude Code web SessionStart hook that installs Node 24
|
|
51
|
+
(matching `devEngines`) and the pinned pnpm in the remote container.
|
|
52
|
+
|
|
53
|
+
The scaffolded README walks through local dev (`pnpm dev` — no Cloudflare
|
|
54
|
+
account needed) and deploy (`wrangler login && pnpm deploy` — wrangler
|
|
55
|
+
provisions the D1/R2 resources on first deploy).
|
|
56
|
+
|
|
57
|
+
## Adding a template
|
|
58
|
+
|
|
59
|
+
1. Add a directory under `templates/<name>/` (copied as-is; ship `_gitignore`
|
|
60
|
+
instead of `.gitignore` — `vp create` renames it).
|
|
61
|
+
2. Add a `{ name, description, template: './templates/<name>' }` entry to
|
|
62
|
+
`createConfig.templates` in `package.json`.
|
|
63
|
+
|
|
64
|
+
Template dependencies must use concrete npm ranges (no `workspace:*` /
|
|
65
|
+
`catalog:` — the scaffolded project lives outside this monorepo).
|
package/package.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@getvitops/create",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Project templates for the Vitops design system — scaffold wired-up sites with `vp create @getvitops`.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"files": [
|
|
7
|
+
"templates"
|
|
8
|
+
],
|
|
9
|
+
"type": "module",
|
|
10
|
+
"publishConfig": {
|
|
11
|
+
"access": "public"
|
|
12
|
+
},
|
|
13
|
+
"createConfig": {
|
|
14
|
+
"templates": [
|
|
15
|
+
{
|
|
16
|
+
"name": "emdash",
|
|
17
|
+
"description": "EmDash CMS website on Cloudflare Workers (D1 + R2) with the Vitops design system pre-wired",
|
|
18
|
+
"template": "./templates/emdash"
|
|
19
|
+
}
|
|
20
|
+
]
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: promote
|
|
3
|
+
description: Promote dev → main to deploy the site to production. Use ONLY when the user explicitly asks to promote, ship, or release dev to prod (e.g. "/promote", "promote to prod", "ship dev to production"). This is a deliberate production deploy — never trigger it automatically from generic "deploy" talk or as a side effect of other work.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Promote (dev → main → production)
|
|
7
|
+
|
|
8
|
+
Promotion is the **manual** step that ships reviewed `dev` code to production.
|
|
9
|
+
It dispatches the **"Promote dev → main"** GitHub Action
|
|
10
|
+
(`.github/workflows/promote.yml`), which opens a PR from `dev` into the
|
|
11
|
+
protected `main` branch and enables auto-merge. Merging `main` triggers
|
|
12
|
+
**Deploy prod** (`deploy-prod.yml`), which ships the production worker.
|
|
13
|
+
|
|
14
|
+
Deploys that are **already automatic** (do not use this skill for them):
|
|
15
|
+
|
|
16
|
+
- **dev** auto-deploys on every push/merge to `dev` (`deploy-dev.yml` → the
|
|
17
|
+
`-dev` worker / `dev.<domain>`).
|
|
18
|
+
- **prod** auto-deploys on every push to `main` (`deploy-prod.yml`).
|
|
19
|
+
|
|
20
|
+
So this skill's only job is to kick off the `dev → main` promotion. It does
|
|
21
|
+
**not** promote schema/content — see below.
|
|
22
|
+
|
|
23
|
+
## Steps
|
|
24
|
+
|
|
25
|
+
1. **Confirm intent.** This ships to production. Briefly state what will happen
|
|
26
|
+
("This opens a PR from `dev` into `main`; merging deploys production.") and
|
|
27
|
+
proceed — the user invoking this skill is the confirmation. Only pause to
|
|
28
|
+
ask if they haven't clearly asked to go to prod.
|
|
29
|
+
|
|
30
|
+
2. **Dispatch the workflow** with the `gh` CLI (works from any checkout of this
|
|
31
|
+
repo; `--ref main` picks which copy of the workflow file runs — the workflow
|
|
32
|
+
itself operates on `dev` and `main` regardless):
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
gh workflow run promote.yml --ref main
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
3. **Find the run and report back.** Dispatch prints no run ID, so list the
|
|
39
|
+
runs to get the URL:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
gh run list --workflow=promote.yml --limit 1 --json databaseId,url,status,conclusion
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Give the user the run's `url`. If they want, watch it:
|
|
46
|
+
`gh run watch <databaseId>`, then report the conclusion and the promotion
|
|
47
|
+
PR: `gh pr view dev --base main --json url,state`.
|
|
48
|
+
|
|
49
|
+
## What to tell the user after dispatch
|
|
50
|
+
|
|
51
|
+
- The workflow opens (or reuses) a PR **dev → main** and enables auto-merge.
|
|
52
|
+
- **`main` should be branch-protected.** Whether the merge completes hands-off
|
|
53
|
+
depends on the repo setup:
|
|
54
|
+
- With **required reviews** (default), the PR waits for a human to approve —
|
|
55
|
+
the default `GITHUB_TOKEN` can't approve its own PR. Point the user to the
|
|
56
|
+
PR to approve it; only then does prod deploy.
|
|
57
|
+
- If a `PROMOTE_TOKEN` secret (a PAT / GitHub App token allowed to bypass
|
|
58
|
+
protection) is set, auto-merge completes on its own and prod deploys with
|
|
59
|
+
no further action.
|
|
60
|
+
- Once `main` advances, **Deploy prod** runs automatically.
|
|
61
|
+
|
|
62
|
+
## Schema / content changes
|
|
63
|
+
|
|
64
|
+
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.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
#
|
|
3
|
+
# SessionStart hook for Claude Code on the web.
|
|
4
|
+
#
|
|
5
|
+
# The web container ships Node 20/21/22, but this project pins Node 24 via
|
|
6
|
+
# `devEngines` in package.json. This hook installs Node 24 (via the image's
|
|
7
|
+
# nvm), makes it win over the baked-in Node on PATH for the whole session,
|
|
8
|
+
# activates the pinned pnpm, and installs dependencies so `astro check`,
|
|
9
|
+
# builds, and the EmDash CLI all run against the version the project expects.
|
|
10
|
+
set -euo pipefail
|
|
11
|
+
|
|
12
|
+
# Only relevant in the remote web container; local sessions manage their own Node.
|
|
13
|
+
if [ "${CLAUDE_CODE_REMOTE:-}" != "true" ]; then
|
|
14
|
+
exit 0
|
|
15
|
+
fi
|
|
16
|
+
|
|
17
|
+
# --- Node 24 (matches package.json > devEngines.runtime) ---------------------
|
|
18
|
+
export NVM_DIR="${NVM_DIR:-/opt/nvm}"
|
|
19
|
+
# shellcheck disable=SC1091
|
|
20
|
+
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
|
|
21
|
+
|
|
22
|
+
nvm install 24
|
|
23
|
+
nvm alias default 24
|
|
24
|
+
|
|
25
|
+
# The base image prepends its own node bin dir to PATH, ahead of nvm's shims,
|
|
26
|
+
# so a plain `node` resolves to the baked-in version. Put Node 24's bin dir
|
|
27
|
+
# first for this session and persist it to every subsequent tool call via
|
|
28
|
+
# $CLAUDE_ENV_FILE.
|
|
29
|
+
NODE24_BIN="$(dirname "$(nvm which 24)")"
|
|
30
|
+
export PATH="$NODE24_BIN:$PATH"
|
|
31
|
+
echo "export PATH=\"$NODE24_BIN:\$PATH\"" >> "$CLAUDE_ENV_FILE"
|
|
32
|
+
|
|
33
|
+
# --- Dependencies (pnpm, pinned via package.json > packageManager) -----------
|
|
34
|
+
cd "$CLAUDE_PROJECT_DIR"
|
|
35
|
+
export COREPACK_ENABLE_DOWNLOAD_PROMPT=0
|
|
36
|
+
corepack enable
|
|
37
|
+
# `install` (not `--frozen-lockfile`) so the warmed container cache is reusable.
|
|
38
|
+
pnpm install
|
|
39
|
+
|
|
40
|
+
echo "Ready: Node $(node --version), pnpm $(pnpm --version)"
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Deploys the `dev` branch to the isolated dev worker (<name>-dev, and
|
|
2
|
+
# dev.<domain> if a custom domain is configured in wrangler.jsonc).
|
|
3
|
+
# `dev` is the integration branch — every push here refreshes the review site.
|
|
4
|
+
# Promote to production with the "Promote dev → main" workflow (promote.yml).
|
|
5
|
+
#
|
|
6
|
+
# Required repo secrets:
|
|
7
|
+
# CLOUDFLARE_API_TOKEN — token with Workers + D1 + R2 edit on this account
|
|
8
|
+
# CLOUDFLARE_ACCOUNT_ID — the Cloudflare account id
|
|
9
|
+
# DEV_D1_ID — id from `wrangler d1 create <name>-dev`
|
|
10
|
+
# (without it the build script hard-fails, on purpose)
|
|
11
|
+
name: Deploy dev
|
|
12
|
+
|
|
13
|
+
on:
|
|
14
|
+
push:
|
|
15
|
+
branches: [dev]
|
|
16
|
+
|
|
17
|
+
permissions:
|
|
18
|
+
contents: read
|
|
19
|
+
|
|
20
|
+
concurrency:
|
|
21
|
+
group: deploy-dev
|
|
22
|
+
cancel-in-progress: true
|
|
23
|
+
|
|
24
|
+
jobs:
|
|
25
|
+
deploy:
|
|
26
|
+
runs-on: ubuntu-latest
|
|
27
|
+
environment: dev
|
|
28
|
+
steps:
|
|
29
|
+
- uses: actions/checkout@v4
|
|
30
|
+
- uses: pnpm/action-setup@v4
|
|
31
|
+
- uses: actions/setup-node@v4
|
|
32
|
+
with:
|
|
33
|
+
node-version: '24'
|
|
34
|
+
cache: pnpm
|
|
35
|
+
- run: pnpm install --frozen-lockfile
|
|
36
|
+
- name: Build + deploy dev worker
|
|
37
|
+
run: pnpm run deploy:dev
|
|
38
|
+
env:
|
|
39
|
+
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
|
40
|
+
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
|
41
|
+
DEV_D1_ID: ${{ secrets.DEV_D1_ID }}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Deploys `main` to production.
|
|
2
|
+
# `main` should be branch-protected — it then only advances via the Promote
|
|
3
|
+
# workflow (promote.yml), so a push here always represents promoted code.
|
|
4
|
+
#
|
|
5
|
+
# Required repo secrets:
|
|
6
|
+
# CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID
|
|
7
|
+
name: Deploy prod
|
|
8
|
+
|
|
9
|
+
on:
|
|
10
|
+
push:
|
|
11
|
+
branches: [main]
|
|
12
|
+
|
|
13
|
+
permissions:
|
|
14
|
+
contents: read
|
|
15
|
+
|
|
16
|
+
concurrency:
|
|
17
|
+
group: deploy-prod
|
|
18
|
+
cancel-in-progress: false
|
|
19
|
+
|
|
20
|
+
jobs:
|
|
21
|
+
deploy:
|
|
22
|
+
runs-on: ubuntu-latest
|
|
23
|
+
environment: production
|
|
24
|
+
steps:
|
|
25
|
+
- uses: actions/checkout@v4
|
|
26
|
+
- uses: pnpm/action-setup@v4
|
|
27
|
+
- uses: actions/setup-node@v4
|
|
28
|
+
with:
|
|
29
|
+
node-version: '24'
|
|
30
|
+
cache: pnpm
|
|
31
|
+
- run: pnpm install --frozen-lockfile
|
|
32
|
+
- name: Build + deploy prod worker
|
|
33
|
+
run: pnpm run deploy
|
|
34
|
+
env:
|
|
35
|
+
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
|
36
|
+
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Promote dev → main (code). Run manually after reviewing the dev site.
|
|
2
|
+
# Opens a PR from dev into the protected `main` branch and enables auto-merge;
|
|
3
|
+
# the merge triggers Deploy prod.
|
|
4
|
+
#
|
|
5
|
+
# Branch protection on `main` means the merge only completes when its rules are
|
|
6
|
+
# satisfied. Choose ONE:
|
|
7
|
+
# (a) let a human approve the PR (required reviews) — safest, full audit trail;
|
|
8
|
+
# (b) set repo secret PROMOTE_TOKEN to a PAT / GitHub App token allowed to
|
|
9
|
+
# bypass protection, for hands-off promotion.
|
|
10
|
+
# The default GITHUB_TOKEN cannot approve its own PR, so with required reviews
|
|
11
|
+
# the PR waits for a human — which is usually what you want for a prod promotion.
|
|
12
|
+
#
|
|
13
|
+
# Schema/content promotion is deliberately NOT here — the content model lives in
|
|
14
|
+
# the EmDash database, not git. Apply schema changes to prod via the admin or
|
|
15
|
+
# the site's MCP server, additive changes before the code deploy that uses
|
|
16
|
+
# them, removals after the code that stops using them.
|
|
17
|
+
name: Promote dev → main
|
|
18
|
+
|
|
19
|
+
on:
|
|
20
|
+
workflow_dispatch:
|
|
21
|
+
|
|
22
|
+
permissions:
|
|
23
|
+
contents: write
|
|
24
|
+
pull-requests: write
|
|
25
|
+
|
|
26
|
+
jobs:
|
|
27
|
+
promote:
|
|
28
|
+
runs-on: ubuntu-latest
|
|
29
|
+
steps:
|
|
30
|
+
- uses: actions/checkout@v4
|
|
31
|
+
with:
|
|
32
|
+
fetch-depth: 0
|
|
33
|
+
- name: Open promotion PR and enable auto-merge
|
|
34
|
+
env:
|
|
35
|
+
GH_TOKEN: ${{ secrets.PROMOTE_TOKEN || github.token }}
|
|
36
|
+
run: |
|
|
37
|
+
set -euo pipefail
|
|
38
|
+
if gh pr view dev --base main --json number >/dev/null 2>&1; then
|
|
39
|
+
echo "Promotion PR already open; ensuring auto-merge is enabled."
|
|
40
|
+
else
|
|
41
|
+
# printf, not a multi-line string: an unindented continuation line
|
|
42
|
+
# would end this YAML block and break the whole workflow file.
|
|
43
|
+
gh pr create --base main --head dev \
|
|
44
|
+
--title "Promote dev → main" \
|
|
45
|
+
--body "$(printf '%s\n\n%s' \
|
|
46
|
+
"Automated code promotion from \`dev\`. Merging deploys to production." \
|
|
47
|
+
"Remember: schema/content changes live in the EmDash database, not git — promote them separately if this release changes the content model.")"
|
|
48
|
+
fi
|
|
49
|
+
gh pr merge dev --auto --merge
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
This is an EmDash CMS website (Astro SSR on Cloudflare Workers, D1 + R2)
|
|
2
|
+
styled by the Vitops design system.
|
|
3
|
+
|
|
4
|
+
## Ground rules
|
|
5
|
+
|
|
6
|
+
- `design-system.json` is the single source of truth for the design system
|
|
7
|
+
(colors, type, spacing, patterns, animations). Never edit
|
|
8
|
+
`src/styles/tailwind.css` — it is generated from the JSON by the
|
|
9
|
+
`getvitops()` integration on every dev/build and is gitignored.
|
|
10
|
+
- Content lives in the EmDash database, not in the repo. Query it with
|
|
11
|
+
`getEmDashCollection` / `getEmDashEntry` from `emdash`; render Portable Text
|
|
12
|
+
with `<PortableText />` from `emdash/ui` (the Vitops blocks from
|
|
13
|
+
`@getvitops/emdash` are merged in automatically).
|
|
14
|
+
- `seed/seed.json` only applies against an empty database: structure
|
|
15
|
+
(collections, menus, settings) on first boot; the `content` entries only
|
|
16
|
+
when the setup wizard's "include sample content" option is chosen. Changing
|
|
17
|
+
the seed does not migrate an existing site — use the admin
|
|
18
|
+
(`/_emdash/admin`) or the site's MCP server (`/_emdash/api/mcp`) to change
|
|
19
|
+
the content model of a running site.
|
|
20
|
+
- Prefer framework CSS classes (utilities, patterns, type roles like
|
|
21
|
+
`font-heading`) over bespoke CSS. Run `npx vitops agents` to append the
|
|
22
|
+
full generated class-vocabulary reference to this file.
|
|
23
|
+
- The Worker cron trigger in `wrangler.jsonc` drives scheduled publishing —
|
|
24
|
+
don't remove it.
|
|
25
|
+
|
|
26
|
+
## Branch model
|
|
27
|
+
|
|
28
|
+
- Work on **`dev`** (the integration branch). Every push auto-deploys the
|
|
29
|
+
isolated `-dev` worker (own D1/R2; `dev.<domain>` with a custom domain).
|
|
30
|
+
- **`main`** is production and branch-protected — never push or merge to it
|
|
31
|
+
directly. Production ships only via the manual **promote** flow: the
|
|
32
|
+
`promote` skill (`.agents/skills/promote/SKILL.md`) dispatches
|
|
33
|
+
`.github/workflows/promote.yml`, which opens a `dev → main` PR with
|
|
34
|
+
auto-merge; merging deploys prod. Only run it when the user explicitly asks
|
|
35
|
+
to promote/ship to production.
|
|
36
|
+
- Sample legal pages (`/terms`, `/privacy`) ship as seeded placeholder content
|
|
37
|
+
— remind the user to replace them before launch.
|
|
38
|
+
|
|
39
|
+
## Docs MCP servers
|
|
40
|
+
|
|
41
|
+
`.mcp.json` preconfigures the Astro docs and EmDash docs MCP servers — prefer
|
|
42
|
+
searching them over pre-trained knowledge for Astro/EmDash questions.
|
|
43
|
+
|
|
44
|
+
## Commands
|
|
45
|
+
|
|
46
|
+
- `pnpm dev` — local dev with local D1/R2 emulation (no Cloudflare account)
|
|
47
|
+
- `pnpm typecheck` — `astro check`
|
|
48
|
+
- `pnpm deploy` — build + `wrangler deploy` prod (provisions D1/R2 on first run)
|
|
49
|
+
- `pnpm deploy:dev` — build + deploy the isolated dev worker (needs `DEV_D1_ID`)
|
|
50
|
+
- `npx vitops validate design-system.json` — schema-check the design config
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# My EmDash Site
|
|
2
|
+
|
|
3
|
+
An [EmDash CMS](https://docs.emdashcms.com) website on Cloudflare Workers,
|
|
4
|
+
styled by the [Vitops design system](https://www.npmjs.com/org/getvitops).
|
|
5
|
+
|
|
6
|
+
Scaffolded from `vp create @getvitops:emdash`.
|
|
7
|
+
|
|
8
|
+
## Stack
|
|
9
|
+
|
|
10
|
+
- **Astro** (SSR) on **Cloudflare Workers** — D1 database, R2 media storage
|
|
11
|
+
- **EmDash** — the admin lives at `/_emdash/admin`; content is queried with
|
|
12
|
+
`getEmDashCollection` / `getEmDashEntry` (Astro live content collections)
|
|
13
|
+
- **`@getvitops/astro`** — generates the design-system CSS from
|
|
14
|
+
`design-system.json` (Tailwind v4 format), generates favicons/PWA assets
|
|
15
|
+
from `src/assets/logo.svg`, and serves the web-component runtime
|
|
16
|
+
- **`@getvitops/emdash`** — design-system Portable Text blocks in the editor
|
|
17
|
+
slash menu: image compare, copy snippet, banner, disclosure, carousel
|
|
18
|
+
|
|
19
|
+
## Local development
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pnpm install
|
|
23
|
+
pnpm dev
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
No Cloudflare account needed: `astro dev` runs the Worker locally with local
|
|
27
|
+
D1/R2 emulation (state in `.wrangler/`). On first boot EmDash runs its
|
|
28
|
+
migrations and applies the structure from `seed/seed.json` — a `pages`
|
|
29
|
+
collection, a `primary` menu, and the site settings.
|
|
30
|
+
|
|
31
|
+
- Site: <http://localhost:4321>
|
|
32
|
+
- Admin: <http://localhost:4321/_emdash/admin>
|
|
33
|
+
|
|
34
|
+
Open the admin and complete the setup wizard — it creates your first account,
|
|
35
|
+
and choosing to **include sample content** publishes the starter pages from
|
|
36
|
+
the seed: home, about, terms (`/terms`), and privacy policy (`/privacy`).
|
|
37
|
+
Until then the home route shows a friendly empty state. Unknown paths render
|
|
38
|
+
`src/pages/404.astro`.
|
|
39
|
+
|
|
40
|
+
## Deploy
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npx wrangler login
|
|
44
|
+
pnpm deploy
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Wrangler provisions the D1 database and R2 bucket named in `wrangler.jsonc`
|
|
48
|
+
on the first deploy; EmDash migrates + applies the seed structure on first
|
|
49
|
+
boot (sample content again comes from the setup wizard). Later deploys leave
|
|
50
|
+
existing content alone. The cron trigger in `wrangler.jsonc` drives
|
|
51
|
+
scheduled publishing — don't remove it. For a custom domain, uncomment the
|
|
52
|
+
`routes` block in `wrangler.jsonc` and set your apex.
|
|
53
|
+
|
|
54
|
+
## Environments: dev → main promotion
|
|
55
|
+
|
|
56
|
+
The repo ships a two-environment flow modeled on branch = environment:
|
|
57
|
+
|
|
58
|
+
- **`dev`** is the integration branch. Every push auto-deploys the isolated
|
|
59
|
+
`-dev` worker (`.github/workflows/deploy-dev.yml`) — its own D1 database and
|
|
60
|
+
R2 bucket, served at `dev.<domain>` when a custom domain is configured (or
|
|
61
|
+
the worker's `workers.dev` URL otherwise).
|
|
62
|
+
- **`main`** is production. Every push auto-deploys the prod worker
|
|
63
|
+
(`.github/workflows/deploy-prod.yml`).
|
|
64
|
+
- **Promotion is manual**: run the **"Promote dev → main"** workflow
|
|
65
|
+
(`.github/workflows/promote.yml`) — or ask your AI agent to run the
|
|
66
|
+
`promote` skill (`.agents/skills/promote/`) — and it opens a PR from `dev`
|
|
67
|
+
into `main` with auto-merge enabled. With required reviews on `main`, a
|
|
68
|
+
human approves the PR; merging deploys prod.
|
|
69
|
+
|
|
70
|
+
One-time setup:
|
|
71
|
+
|
|
72
|
+
1. Create the branches: `git branch dev && git push -u origin main dev`, and
|
|
73
|
+
make `dev` the default working branch. Branch-protect `main` (require the
|
|
74
|
+
promotion PR + review).
|
|
75
|
+
2. Create the isolated dev resources:
|
|
76
|
+
`wrangler d1 create my-emdash-site-dev` and
|
|
77
|
+
`wrangler r2 bucket create my-emdash-site-media-dev`.
|
|
78
|
+
3. Add repo secrets: `CLOUDFLARE_API_TOKEN`, `CLOUDFLARE_ACCOUNT_ID`, and
|
|
79
|
+
`DEV_D1_ID` (the id printed by `d1 create`). Optionally `PROMOTE_TOKEN`
|
|
80
|
+
(a PAT allowed to bypass protection) for hands-off promotion.
|
|
81
|
+
|
|
82
|
+
`pnpm deploy:dev` reproduces the dev deploy locally: it rewrites the
|
|
83
|
+
adapter-generated wrangler config (`scripts/build-dev-wrangler.mjs`) so the
|
|
84
|
+
dev worker never touches prod data — it hard-fails without `DEV_D1_ID`,
|
|
85
|
+
on purpose. Schema/content changes live in the EmDash database, not git, so
|
|
86
|
+
promote them separately (via the prod admin or its MCP server).
|
|
87
|
+
|
|
88
|
+
## AI tooling
|
|
89
|
+
|
|
90
|
+
- **MCP servers** (`.mcp.json`): the Astro docs (`mcp.docs.astro.build`) and
|
|
91
|
+
EmDash docs (`docs.emdashcms.com/mcp`) servers are preconfigured, so coding
|
|
92
|
+
agents answer from current documentation. Your running site also exposes its
|
|
93
|
+
own content MCP server at `/_emdash/api/mcp` (authenticated).
|
|
94
|
+
- **Skills** (`.agents/skills/`): `promote` dispatches the dev → main
|
|
95
|
+
promotion workflow. Run `npx vitops agents` to also write the design-system
|
|
96
|
+
class reference into `AGENTS.md`.
|
|
97
|
+
- **Claude Code on the web**: the SessionStart hook
|
|
98
|
+
(`.claude/hooks/session-start.sh`) installs Node 24 (matching
|
|
99
|
+
`devEngines`), activates the pinned pnpm, and installs dependencies in the
|
|
100
|
+
remote container automatically. Point the remote environment at the `dev`
|
|
101
|
+
branch so pushes land on the auto-deployed review site.
|
|
102
|
+
|
|
103
|
+
## The design system
|
|
104
|
+
|
|
105
|
+
`design-system.json` is the single source of truth for colors, type, spacing,
|
|
106
|
+
shadows, patterns, and animations. The `getvitops()` integration regenerates
|
|
107
|
+
`src/styles/tailwind.css` from it on dev/build (the file is gitignored — edit
|
|
108
|
+
the JSON, not the CSS). `Layout.astro` imports the stylesheet so the admin
|
|
109
|
+
routes stay unstyled by it.
|
|
110
|
+
|
|
111
|
+
Useful commands (via `@getvitops/cli`):
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
npx vitops validate design-system.json # check the config against the schema
|
|
115
|
+
npx vitops agents # write the design-system reference into AGENTS.md
|
|
116
|
+
pnpm build:css # regenerate the CSS manually
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Project map
|
|
120
|
+
|
|
121
|
+
| Path | Purpose |
|
|
122
|
+
| -------------------------------- | ----------------------------------------------------------------- |
|
|
123
|
+
| `design-system.json` | Design tokens + patterns → generated CSS |
|
|
124
|
+
| `seed/seed.json` | First-boot content model + starter content |
|
|
125
|
+
| `src/worker.ts` | Worker entry (fetch + EmDash scheduled handler) |
|
|
126
|
+
| `src/live.config.ts` | Astro live content collections wiring for EmDash |
|
|
127
|
+
| `src/layouts/Layout.astro` | Site chrome: EmDash + Vitops head tags, nav from the CMS menu |
|
|
128
|
+
| `src/pages/index.astro` | Home — renders the `home` page entry |
|
|
129
|
+
| `src/pages/[...slug].astro` | Catch-all — renders any `pages` entry at its slug |
|
|
130
|
+
| `src/pages/404.astro` | Not-found page (the catch-all rewrites here) |
|
|
131
|
+
| `wrangler.jsonc` | Cloudflare bindings (D1/R2) + cron trigger |
|
|
132
|
+
| `scripts/build-dev-wrangler.mjs` | Rewrites the built wrangler config for the isolated dev worker |
|
|
133
|
+
| `.github/workflows/` | deploy-dev (push to `dev`), deploy-prod (push to `main`), promote |
|
|
134
|
+
| `.agents/skills/promote/` | Agent skill: dispatch the dev → main promotion |
|
|
135
|
+
| `.mcp.json` | Astro + EmDash docs MCP servers for coding agents |
|
|
136
|
+
| `.claude/hooks/` | Claude Code web: Node 24 + pnpm setup on session start |
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# build output
|
|
2
|
+
dist/
|
|
3
|
+
|
|
4
|
+
# dependencies
|
|
5
|
+
node_modules/
|
|
6
|
+
|
|
7
|
+
# generated types
|
|
8
|
+
.astro/
|
|
9
|
+
emdash-env.d.ts
|
|
10
|
+
worker-configuration.d.ts
|
|
11
|
+
|
|
12
|
+
# wrangler local state (local D1/R2 data lives here during `astro dev`)
|
|
13
|
+
.wrangler/
|
|
14
|
+
|
|
15
|
+
# generated by the getvitops() integration on dev/build
|
|
16
|
+
src/styles/
|
|
17
|
+
public/vitops/
|
|
18
|
+
|
|
19
|
+
# environment
|
|
20
|
+
.env
|
|
21
|
+
.env.*
|
|
22
|
+
!.env.example
|
|
23
|
+
|
|
24
|
+
# os / editor
|
|
25
|
+
.DS_Store
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import cloudflare from '@astrojs/cloudflare';
|
|
2
|
+
import react from '@astrojs/react';
|
|
3
|
+
import { d1, r2 } from '@emdash-cms/cloudflare';
|
|
4
|
+
import getvitops from '@getvitops/astro';
|
|
5
|
+
import { vitopsEmdash } from '@getvitops/emdash';
|
|
6
|
+
import { defineConfig } from 'astro/config';
|
|
7
|
+
import emdash from 'emdash/astro';
|
|
8
|
+
|
|
9
|
+
export default defineConfig({
|
|
10
|
+
output: 'server',
|
|
11
|
+
adapter: cloudflare(),
|
|
12
|
+
image: {
|
|
13
|
+
layout: 'constrained',
|
|
14
|
+
responsiveStyles: true,
|
|
15
|
+
},
|
|
16
|
+
integrations: [
|
|
17
|
+
// The EmDash admin UI (/_emdash/admin) is a React app.
|
|
18
|
+
react(),
|
|
19
|
+
emdash({
|
|
20
|
+
database: d1({ binding: 'DB', session: 'auto' }),
|
|
21
|
+
storage: r2({ binding: 'MEDIA' }),
|
|
22
|
+
// Vitops design-system blocks (image compare, copy snippet, banner,
|
|
23
|
+
// disclosure, carousel) in the editor slash menu. Default 'integration'
|
|
24
|
+
// script delivery: Layout.astro renders <Head /> from @getvitops/astro,
|
|
25
|
+
// which emits the web-component runtime tags.
|
|
26
|
+
plugins: [vitopsEmdash()],
|
|
27
|
+
}),
|
|
28
|
+
getvitops({
|
|
29
|
+
favicon: {
|
|
30
|
+
source: 'src/assets/logo.svg',
|
|
31
|
+
name: 'My EmDash Site',
|
|
32
|
+
themeColor: '#2e9b73',
|
|
33
|
+
backgroundColor: '#ffffff',
|
|
34
|
+
},
|
|
35
|
+
// inject: false — Layout.astro imports the generated stylesheet, so
|
|
36
|
+
// EmDash's /_emdash/admin routes don't inherit the design system while
|
|
37
|
+
// your pages (and EmDash previews rendered through the layout) do.
|
|
38
|
+
css: { format: 'tailwind', inject: false },
|
|
39
|
+
}),
|
|
40
|
+
],
|
|
41
|
+
});
|