@getvitops/create 0.4.0 → 0.6.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 +129 -5
- package/README.md +7 -0
- package/package.json +17 -2
- package/templates/emdash/.env +1 -0
- package/templates/emdash/.github/workflows/ci.yml +12 -0
- package/templates/emdash/.github/workflows/deploy-dev.yml +17 -19
- package/templates/emdash/.github/workflows/deploy-prod.yml +17 -21
- package/templates/emdash/.github/workflows/promote.yml +8 -36
- package/templates/emdash/.mcp.json +4 -0
- package/templates/emdash/.nvmrc +1 -0
- package/templates/emdash/AGENTS.md +47 -0
- package/templates/emdash/README.md +4 -0
- package/templates/emdash/design-system.json +2 -2
- package/templates/emdash/package.json +1 -0
- package/templates/emdash/scripts/init-github.mjs +94 -10
- package/templates/emdash/src/assets/placeholder/wireframe-400x400.png +0 -0
- package/templates/emdash/src/assets/placeholder/wireframe-400x400.svg +1 -0
- package/templates/emdash/src/assets/placeholder/wireframe-400x600.png +0 -0
- package/templates/emdash/src/assets/placeholder/wireframe-400x600.svg +1 -0
- package/templates/emdash/src/assets/placeholder/wireframe-600x400.png +0 -0
- package/templates/emdash/src/assets/placeholder/wireframe-600x400.svg +1 -0
- package/templates/emdash/vite.config.ts +6 -0
- package/templates/static/.env +1 -0
- package/templates/static/.github/workflows/ci.yml +12 -0
- package/templates/static/.github/workflows/deploy-dev.yml +33 -0
- package/templates/static/.github/workflows/deploy-prod.yml +29 -0
- package/templates/static/.github/workflows/promote.yml +16 -0
- package/templates/static/.nvmrc +1 -0
- package/templates/static/AGENTS.md +57 -0
- package/templates/static/README.md +85 -0
- package/templates/static/_gitignore +24 -0
- package/templates/static/astro.config.mjs +19 -0
- package/templates/static/design-system.json +148 -0
- package/templates/static/package.json +39 -0
- package/templates/static/scripts/dev-wrangler-config.mjs +28 -0
- package/templates/static/scripts/init-github.mjs +186 -0
- package/templates/static/scripts/init-hosting.mjs +62 -0
- package/templates/static/src/assets/placeholder/wireframe-400x400.png +0 -0
- package/templates/static/src/assets/placeholder/wireframe-400x400.svg +1 -0
- package/templates/static/src/assets/placeholder/wireframe-400x600.png +0 -0
- package/templates/static/src/assets/placeholder/wireframe-400x600.svg +1 -0
- package/templates/static/src/assets/placeholder/wireframe-600x400.png +0 -0
- package/templates/static/src/assets/placeholder/wireframe-600x400.svg +1 -0
- package/templates/static/src/layouts/Layout.astro +26 -0
- package/templates/static/src/pages/404.astro +11 -0
- package/templates/static/src/pages/index.astro +13 -0
- package/templates/static/tsconfig.json +5 -0
- package/templates/static/wrangler.jsonc +15 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,129 @@
|
|
|
1
1
|
# @getvitops/create
|
|
2
2
|
|
|
3
|
+
## 0.6.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Scaffolded `static` and `emdash` projects now ship neutral placeholder wireframe images under
|
|
8
|
+
`src/assets/placeholder/` (square, portrait, landscape; SVG + PNG) for layout work before real
|
|
9
|
+
imagery exists. Delete the directory once real assets land.
|
|
10
|
+
|
|
11
|
+
## 0.5.0
|
|
12
|
+
|
|
13
|
+
### Minor Changes
|
|
14
|
+
|
|
15
|
+
- **The framework now says out loud which patterns are foundational, and reports it when you
|
|
16
|
+
reach past them.** From repeated downstream reports: sites were inventing a `.wrap` class and
|
|
17
|
+
using it everywhere instead of `.centered`, and `.subgrid` — the class for any set of cards —
|
|
18
|
+
was going essentially unused. Neither failure is visible in a build: every class involved is
|
|
19
|
+
real, nothing errors, and the page renders.
|
|
20
|
+
|
|
21
|
+
**`.subgrid` was missing from the generated class reference entirely.** So was `.cluster`, and
|
|
22
|
+
so was `.region`. `vitops docs classes` is the doc an agent is told to fetch for "which class
|
|
23
|
+
do I apply", and the framework's answer for a set of cards was not in it — which is most of why
|
|
24
|
+
it never got used. All three are now documented, along with `grid-auto`, and the reference opens
|
|
25
|
+
with a **Foundations** section that states the six substitutions as
|
|
26
|
+
_temptation → what to write instead_ rather than as a vocabulary list. The same table is now in
|
|
27
|
+
the shipped agent skill and in the `@getvitops/create` template's `AGENTS.md`, because a pointer
|
|
28
|
+
to a doc only helps someone who already suspects there is something to look up. (`css`/`bricks`
|
|
29
|
+
and `tailwind` alike — this is documentation and the `TIERS` manifest, not emitted CSS.)
|
|
30
|
+
|
|
31
|
+
**`vitops lint` gained three reuse rules and a markup pass.** The existing `.centered` rule only
|
|
32
|
+
fired when the hand-written CSS already referenced `--width-measure`, which is precisely the
|
|
33
|
+
author who was never going to hand-roll a container. It now also catches a page-scale
|
|
34
|
+
`max-width` (≥ 48rem, or a `ch` reading measure) with auto margins, and a
|
|
35
|
+
container-shaped class name — `wrap`, `wrapper`, `container`, `inner`, `shell`, … — carrying any
|
|
36
|
+
width cap at all. Reading the cap out of `min()` / `clamp()` too. New rules report a
|
|
37
|
+
hand-written `repeat()` grid as `.subgrid` (or `.grid-auto`), and a new **markup** pass reports a
|
|
38
|
+
repeated card set laid out without either — a loop that renders a card, or three or more cards
|
|
39
|
+
written out. That last one is the only check that can see this drift at all, since a card list
|
|
40
|
+
built from utility classes contains no bad class and no hand-written CSS.
|
|
41
|
+
|
|
42
|
+
All of these are `suggestion` severity, so they do not fail your build unless you pass
|
|
43
|
+
`--strict`. They found five real instances in this repo's own docs site.
|
|
44
|
+
|
|
45
|
+
**`vitops lint [files...]`** now takes explicit paths instead of scanning `--src`, so it can be
|
|
46
|
+
wired into a pre-commit hook — `vp`'s `staged` key appends the staged files to whatever it runs,
|
|
47
|
+
and a command that refused positionals could not be put in the one place the feedback lands at
|
|
48
|
+
the moment the code is written. Unreadable and non-source paths are skipped rather than fatal. The
|
|
49
|
+
`@getvitops/create` emdash template wires it up with `--strict` and adds a `lint:design` script.
|
|
50
|
+
|
|
51
|
+
**Two answers for "the whole card is a link", because one cannot exist.** Reported downstream:
|
|
52
|
+
agents dislike the `<li>` wrapper when a card is a link and write `<li><a class="card">` instead.
|
|
53
|
+
That shape is **wrong in a way that renders fine**, which is why it survives review — the `<li>`
|
|
54
|
+
is the grid item, so the anchor is an ordinary block inside it and the tranches within the anchor
|
|
55
|
+
never reach the parent's shared row lines. The alignment `.subgrid` exists for silently does not
|
|
56
|
+
happen, and the anchor does not fill the cell either. Putting the anchor in the grid's place
|
|
57
|
+
(`<ul><a></ul>`) is invalid HTML, so there was genuinely no correct shape to reach for.
|
|
58
|
+
|
|
59
|
+
There are now two, and they trade against each other because **no CSS-only technique can make a
|
|
60
|
+
whole card clickable and leave its text selectable** — a transparent overlay necessarily receives
|
|
61
|
+
the pointer-drag:
|
|
62
|
+
|
|
63
|
+
| Want | Use |
|
|
64
|
+
| ---------------------------------------- | ------------------------------------------- |
|
|
65
|
+
| zero JS, whole card clickable | `.stretched-link` — **text not selectable** |
|
|
66
|
+
| selectable text **and** a clickable card | `<Cards>` / `<wc-cards>` — needs JS |
|
|
67
|
+
|
|
68
|
+
`.stretched-link` goes on a link inside the card; its `::after` covers the card.
|
|
69
|
+
**`<wc-cards>` is a new tier-2 element** that adds no overlay and instead tells a click apart from
|
|
70
|
+
the end of a drag, so text selection survives. Its fallback is the card's own link, fully usable
|
|
71
|
+
with no JS, and the pointer cursor is applied by the element — so the affordance never appears
|
|
72
|
+
without the behaviour. `<Cards>` emits it for you. They are alternatives, never layered: with an
|
|
73
|
+
overlay present the JS has nothing to do, and `vitops lint` reports the combination. (All formats
|
|
74
|
+
— pattern partials, deliberately not `utilities.css`, which the tailwind format skips on the
|
|
75
|
+
invariant that everything left in it is a name Tailwind ships itself.)
|
|
76
|
+
|
|
77
|
+
### Breaking
|
|
78
|
+
- **`<Subgrid />` renders its slot verbatim — author the `<li>` items yourself.**
|
|
79
|
+
|
|
80
|
+
```diff
|
|
81
|
+
<Subgrid>
|
|
82
|
+
- <article class="card">…</article>
|
|
83
|
+
+ <li class="card subgrid-card">…</li>
|
|
84
|
+
</Subgrid>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
It used to parse the slotted HTML and rebuild each child as an `<li>`, carrying over only
|
|
88
|
+
`class` and `style`. That existed solely because a `<ul>` may contain nothing else, and it cost
|
|
89
|
+
more than it bought: the child's tag was discarded, `id`/`data-*`/`aria-*` were silently
|
|
90
|
+
dropped, and `href` could not survive at all. Nothing is copied now, so nothing is lost. `as`
|
|
91
|
+
picks the container (`ul` by default, `ol`, `div`), and all other props are forwarded — they
|
|
92
|
+
were previously discarded, including `role`.
|
|
93
|
+
|
|
94
|
+
- **`<Subgrid />` and `<Tree />` now emit `role="list"`,** as does the Bricks `sitenav` element.
|
|
95
|
+
`list-style: none` stops Safari + VoiceOver announcing a `<ul>` as a list, so every marker-less
|
|
96
|
+
framework list was silently losing the semantics its `<ul>` was chosen for. If you hand-write
|
|
97
|
+
`.subgrid`, `.list`, `.facet-list`, `.nav-items`, `.collapse-menu` or `.tree` markup, add
|
|
98
|
+
`role="list"` yourself — the framework cannot add it to markup it does not render, and each
|
|
99
|
+
partial now says so at the `list-style` reset.
|
|
100
|
+
- **`<Cards />` no longer adds `class="card"` to slotted children,** because it no longer parses
|
|
101
|
+
them. Write the class on the item. It now emits `<wc-cards>` around a `<Subgrid>`, which is what
|
|
102
|
+
makes the whole card clickable — and like `<Tree />`, **it emits its own element, so do not wrap
|
|
103
|
+
it in `<wc-cards>` yourself.** It also previously passed a `role="list"` that `<Subgrid>` silently
|
|
104
|
+
discarded, so that never took effect.
|
|
105
|
+
- **`.subgrid-card` now sets `position: relative`**, so `stretched-link` inside it needs no extra
|
|
106
|
+
class. If you were absolutely positioning a descendant of a `.subgrid-card` against an ancestor
|
|
107
|
+
_outside_ that card, it will now resolve against the card. Move the positioning context
|
|
108
|
+
explicitly. (`css`/`bricks`/`tailwind` — the pattern partial is inlined into all three.) Not
|
|
109
|
+
reachable by `vitops lint --fix`: the fix depends on which ancestor you meant, which the
|
|
110
|
+
linter cannot know.
|
|
111
|
+
|
|
112
|
+
### Fixed
|
|
113
|
+
- **`packages/generator/src/docs.ts` contained a literal NUL byte** (`CODE_SLOT`, written as the
|
|
114
|
+
raw character rather than an escape). libmagic classified the file as `data` rather than text, so
|
|
115
|
+
grep and ripgrep treated the largest doc emitter in the repo as **binary and silently skipped
|
|
116
|
+
it** — every search for a string in it returned nothing, with no error. It is now written as the `\u0000` escape.
|
|
117
|
+
Behaviour is identical; the file is searchable.
|
|
118
|
+
- **`.grid-auto` is documented.** It was a real framework class for auto-fit card grids with no
|
|
119
|
+
entry in the class reference, so the honest alternative to `subgrid` was as invisible as
|
|
120
|
+
`subgrid` itself.
|
|
121
|
+
- **`.raised` replaces the advice to use `.relative` above a `stretched-link` overlay**, which
|
|
122
|
+
could not work: a positioned element at `z-index: auto` does not rise above an explicit
|
|
123
|
+
`z-index` regardless of DOM order, so a second link or button in the card stayed underneath and
|
|
124
|
+
unclickable. `.raised` sets both, against the same `--z-tier-raised` token the overlay now uses
|
|
125
|
+
instead of a hard-coded `1`.
|
|
126
|
+
|
|
3
127
|
## 0.4.0
|
|
4
128
|
|
|
5
129
|
### Minor Changes
|
|
@@ -111,15 +235,15 @@
|
|
|
111
235
|
which `@getvitops/astro` re-exports wholesale. So from `astro.config.mjs`:
|
|
112
236
|
|
|
113
237
|
```js
|
|
114
|
-
import { generateIconInclude } from
|
|
115
|
-
import icon from
|
|
238
|
+
import { generateIconInclude } from "@getvitops/astro";
|
|
239
|
+
import icon from "astro-icon";
|
|
116
240
|
|
|
117
241
|
integrations: [
|
|
118
242
|
icon({
|
|
119
243
|
include: generateIconInclude({
|
|
120
|
-
ui:
|
|
121
|
-
brand:
|
|
122
|
-
semantic: [
|
|
244
|
+
ui: "fa7-solid",
|
|
245
|
+
brand: "simple-icons",
|
|
246
|
+
semantic: ["menu", "close", "search", "github"],
|
|
123
247
|
}),
|
|
124
248
|
}),
|
|
125
249
|
];
|
package/README.md
CHANGED
|
@@ -73,6 +73,13 @@ provisions the D1/R2 resources on first deploy).
|
|
|
73
73
|
Template dependencies must use concrete npm ranges (no `workspace:*` /
|
|
74
74
|
`catalog:` — the scaffolded project lives outside this monorepo).
|
|
75
75
|
|
|
76
|
+
`assets/` (this package root, unpublished) is the source of truth for
|
|
77
|
+
placeholder imagery shared across templates — `vp create` copies only the
|
|
78
|
+
one directory a template names, so there's no step that could copy a
|
|
79
|
+
sibling `assets/` for you. Mirror any new file into each template's
|
|
80
|
+
`src/assets/placeholder/` by hand; `templates.test.ts` asserts every mirror
|
|
81
|
+
stays byte-identical to the source.
|
|
82
|
+
|
|
76
83
|
## Changelog
|
|
77
84
|
|
|
78
85
|
This package's history: [`CHANGELOG.md`](./CHANGELOG.md) (shipped in the npm tarball, so it
|
package/package.json
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getvitops/create",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Project templates for the Vitops design system — scaffold wired-up sites with `vp create @getvitops`.",
|
|
5
|
+
"homepage": "https://docs.vitops.ca",
|
|
6
|
+
"bugs": "https://github.com/getvitops/design-system/issues",
|
|
5
7
|
"license": "MIT",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/getvitops/design-system.git",
|
|
11
|
+
"directory": "packages/create"
|
|
12
|
+
},
|
|
6
13
|
"files": [
|
|
7
14
|
"templates",
|
|
8
15
|
"CHANGELOG.md"
|
|
@@ -12,10 +19,18 @@
|
|
|
12
19
|
"access": "public"
|
|
13
20
|
},
|
|
14
21
|
"devDependencies": {
|
|
15
|
-
"vitest": "
|
|
22
|
+
"vitest": "4.1.9"
|
|
23
|
+
},
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=24"
|
|
16
26
|
},
|
|
17
27
|
"createConfig": {
|
|
18
28
|
"templates": [
|
|
29
|
+
{
|
|
30
|
+
"name": "static",
|
|
31
|
+
"description": "Static Astro website on Cloudflare Workers static assets with the Vitops design system pre-wired",
|
|
32
|
+
"template": "./templates/static"
|
|
33
|
+
},
|
|
19
34
|
{
|
|
20
35
|
"name": "emdash",
|
|
21
36
|
"description": "EmDash CMS website on Cloudflare Workers (D1 + R2) with the Vitops design system pre-wired",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
CLOUDFLARE_API_TOKEN=your_cloudflare_api_token
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Runs on every PR: typecheck + design-lint + build. deploy-dev.yml /
|
|
2
|
+
# deploy-prod.yml call the same reusable `site-ci.yml` themselves before they
|
|
3
|
+
# deploy, so this is what gives a PR the identical signal before it's even
|
|
4
|
+
# merged to `dev` — a broken build never reaches the dev worker.
|
|
5
|
+
name: CI
|
|
6
|
+
|
|
7
|
+
on:
|
|
8
|
+
pull_request:
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
ci:
|
|
12
|
+
uses: getvitops/design-system/.github/workflows/site-ci.yml@v1
|
|
@@ -3,6 +3,11 @@
|
|
|
3
3
|
# `dev` is the integration branch — every push here refreshes the review site.
|
|
4
4
|
# Promote to production with the "Promote dev → main" workflow (promote.yml).
|
|
5
5
|
#
|
|
6
|
+
# Deploys via the vitops toolchain's reusable EmDash deploy workflow — see
|
|
7
|
+
# https://github.com/getvitops/design-system/blob/main/.github/workflows/site-deploy-emdash.yml
|
|
8
|
+
# for what it does. Pinned to @v1 so a fix there reaches this repo without a
|
|
9
|
+
# manual re-paste; bump the tag deliberately if you want to pick up a change.
|
|
10
|
+
#
|
|
6
11
|
# Required repo secrets:
|
|
7
12
|
# CLOUDFLARE_API_TOKEN — token with Workers + D1 + R2 edit on this account
|
|
8
13
|
# CLOUDFLARE_ACCOUNT_ID — the Cloudflare account id
|
|
@@ -14,28 +19,21 @@ on:
|
|
|
14
19
|
push:
|
|
15
20
|
branches: [dev]
|
|
16
21
|
|
|
17
|
-
permissions:
|
|
18
|
-
contents: read
|
|
19
|
-
|
|
20
22
|
concurrency:
|
|
21
23
|
group: deploy-dev
|
|
22
24
|
cancel-in-progress: true
|
|
23
25
|
|
|
24
26
|
jobs:
|
|
25
27
|
deploy:
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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 }}
|
|
28
|
+
uses: getvitops/design-system/.github/workflows/site-deploy-emdash.yml@v1
|
|
29
|
+
with:
|
|
30
|
+
environment: dev
|
|
31
|
+
is-dev: true
|
|
32
|
+
# Set this to your dev worker's URL (or dev.<domain>) once known, so a
|
|
33
|
+
# bad migration fails the deploy instead of surfacing to the first
|
|
34
|
+
# visitor — see the reusable workflow's docblock.
|
|
35
|
+
health-check-url: ''
|
|
36
|
+
secrets:
|
|
37
|
+
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
|
38
|
+
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
|
39
|
+
DEV_D1_ID: ${{ secrets.DEV_D1_ID }}
|
|
@@ -1,36 +1,32 @@
|
|
|
1
1
|
# Deploys `main` to production.
|
|
2
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.
|
|
3
|
+
# workflow (promote.yml), so a push here always represents promoted code. The
|
|
4
|
+
# `production` GitHub Environment (set up by `scripts/init-github.mjs`) has
|
|
5
|
+
# required reviewers restricted to admins, so this PAUSES for approval before
|
|
6
|
+
# deploying.
|
|
4
7
|
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
8
|
+
# Deploys via the vitops toolchain's reusable EmDash deploy workflow — see
|
|
9
|
+
# https://github.com/getvitops/design-system/blob/main/.github/workflows/site-deploy-emdash.yml
|
|
10
|
+
#
|
|
11
|
+
# Required repo secrets: CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID
|
|
7
12
|
name: Deploy prod
|
|
8
13
|
|
|
9
14
|
on:
|
|
10
15
|
push:
|
|
11
16
|
branches: [main]
|
|
12
17
|
|
|
13
|
-
permissions:
|
|
14
|
-
contents: read
|
|
15
|
-
|
|
16
18
|
concurrency:
|
|
17
19
|
group: deploy-prod
|
|
18
20
|
cancel-in-progress: false
|
|
19
21
|
|
|
20
22
|
jobs:
|
|
21
23
|
deploy:
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
-
|
|
26
|
-
|
|
27
|
-
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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 }}
|
|
24
|
+
uses: getvitops/design-system/.github/workflows/site-deploy-emdash.yml@v1
|
|
25
|
+
with:
|
|
26
|
+
environment: production
|
|
27
|
+
is-dev: false
|
|
28
|
+
# Set this to your production URL once known.
|
|
29
|
+
health-check-url: ''
|
|
30
|
+
secrets:
|
|
31
|
+
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
|
32
|
+
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
|
@@ -2,48 +2,20 @@
|
|
|
2
2
|
# Opens a PR from dev into the protected `main` branch and enables auto-merge;
|
|
3
3
|
# the merge triggers Deploy prod.
|
|
4
4
|
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
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.
|
|
5
|
+
# Deploys via the vitops toolchain's reusable promote workflow — see
|
|
6
|
+
# https://github.com/getvitops/design-system/blob/main/.github/workflows/site-promote.yml
|
|
7
|
+
# for the branch-protection / PROMOTE_TOKEN options.
|
|
12
8
|
#
|
|
13
|
-
# Schema/content promotion is deliberately NOT here — the content model lives
|
|
14
|
-
# the EmDash database, not git. Apply schema changes to prod via the admin
|
|
15
|
-
# the site's MCP server, additive changes before the code deploy that uses
|
|
9
|
+
# Schema/content promotion is deliberately NOT here — the content model lives
|
|
10
|
+
# in the EmDash database, not git. Apply schema changes to prod via the admin
|
|
11
|
+
# or the site's MCP server, additive changes before the code deploy that uses
|
|
16
12
|
# them, removals after the code that stops using them.
|
|
17
13
|
name: Promote dev → main
|
|
18
14
|
|
|
19
15
|
on:
|
|
20
16
|
workflow_dispatch:
|
|
21
17
|
|
|
22
|
-
permissions:
|
|
23
|
-
contents: write
|
|
24
|
-
pull-requests: write
|
|
25
|
-
|
|
26
18
|
jobs:
|
|
27
19
|
promote:
|
|
28
|
-
|
|
29
|
-
|
|
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
|
|
20
|
+
uses: getvitops/design-system/.github/workflows/site-promote.yml@v1
|
|
21
|
+
secrets: inherit
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
24
|
|
@@ -7,6 +7,10 @@ styled by the Vitops design system.
|
|
|
7
7
|
(colors, type, spacing, patterns, animations). Never edit
|
|
8
8
|
`src/styles/tailwind.css` — it is generated from the JSON by the
|
|
9
9
|
`vitops()` integration on every dev/build and is gitignored.
|
|
10
|
+
- `src/assets/placeholder/` holds neutral wireframe stand-ins (square,
|
|
11
|
+
portrait, landscape; SVG + PNG) for layout work before real imagery
|
|
12
|
+
exists. Delete the directory once real assets land — it's separate from
|
|
13
|
+
`src/assets/logo.svg`, the actual brand asset.
|
|
10
14
|
- Content lives in the EmDash database, not in the repo. Query it with
|
|
11
15
|
`getEmDashCollection` / `getEmDashEntry` from `emdash`; render Portable Text
|
|
12
16
|
with `<PortableText />` from `emdash/ui` (the Vitops blocks from
|
|
@@ -20,6 +24,49 @@ styled by the Vitops design system.
|
|
|
20
24
|
- Prefer framework CSS classes (utilities, patterns, type roles like
|
|
21
25
|
`font-heading`) over bespoke CSS. Run `npx vitops agents` to append the
|
|
22
26
|
full generated class-vocabulary reference to this file.
|
|
27
|
+
- **Layout foundations — these are the answer, not a starting point.** Six
|
|
28
|
+
substitutions carry the structure of nearly every page:
|
|
29
|
+
| About to write… | Write instead |
|
|
30
|
+
| --- | --- |
|
|
31
|
+
| a `wrap`/`wrapper`/`container`/`inner` class | **`centered`** |
|
|
32
|
+
| `max-width` + `margin-inline: auto` | **`centered`** |
|
|
33
|
+
| `padding-block` on a section | **`region`** |
|
|
34
|
+
| margins between headings/paragraphs/lists | **`rhythm`** |
|
|
35
|
+
| `display: grid` + `grid-template-columns: repeat(n, …)` | **`subgrid`** |
|
|
36
|
+
| `repeat(auto-fit, minmax(…))` | **`grid-auto`** |
|
|
37
|
+
| `display: flex` + `gap` for a row of controls | **`cluster`** |
|
|
38
|
+
|
|
39
|
+
`centered` is a grid of _named tracks_, not a max-width box — a child widens
|
|
40
|
+
itself with `breakout`/`spotlight`/`fullbleed` without the parent knowing, which
|
|
41
|
+
is the thing a bespoke `.wrap` can never do. **More than one card means
|
|
42
|
+
`subgrid`**: a plain grid aligns the outer boxes but not the tranches inside
|
|
43
|
+
them, so headings and CTAs land at different heights across the set. Write
|
|
44
|
+
`<ul class="subgrid subgrid-cols-3" role="list">` with
|
|
45
|
+
`<li class="card subgrid-card">` items, or `<Subgrid />` (which renders the slot
|
|
46
|
+
verbatim — author the `<li>`s yourself; it adds `role="list"` for you, and a
|
|
47
|
+
hand-written one needs it because a marker-less `<ul>` stops being announced as a
|
|
48
|
+
list in Safari).
|
|
49
|
+
|
|
50
|
+
**When the whole card is a link the card is still the item**, and pick by whether
|
|
51
|
+
the card's text must stay selectable: `stretched-link` on a link inside it is
|
|
52
|
+
zero-JS but kills text selection, while `<Cards>` (which wraps the list in
|
|
53
|
+
`<wc-cards>`) keeps selection and falls back to that same link with no JS. Never
|
|
54
|
+
both — the overlay wins and the JS never runs. Never `<li><a class="card">`: the
|
|
55
|
+
`li` is the grid item, so the anchor's tranches never align and it does not fill
|
|
56
|
+
the cell; it renders fine, which is why it keeps getting written.
|
|
57
|
+
|
|
58
|
+
`npm run lint:design` reports all of these, and the pre-commit hook fails on them
|
|
59
|
+
— the fix is the class, not a justification for the CSS.
|
|
60
|
+
|
|
61
|
+
- **Prerender by default.** `output: 'server'` is required (the admin, media/API
|
|
62
|
+
routes, preview and scheduled publishing need a server), so prerendering is
|
|
63
|
+
opt-in per route: put `export const prerender = true` on every page that
|
|
64
|
+
doesn't need per-request data, and give dynamic routes a `getStaticPaths()`
|
|
65
|
+
that queries the collection at build time. A route with no `prerender` export
|
|
66
|
+
is re-rendered for every visitor on every request. The cost is that a
|
|
67
|
+
prerendered page reflects the database as of the last build, so an admin
|
|
68
|
+
publish needs a redeploy — leave a route server-rendered only where that is
|
|
69
|
+
unacceptable (preview, personalised content, must-be-live-in-seconds).
|
|
23
70
|
- The Worker cron trigger in `wrangler.jsonc` drives scheduled publishing —
|
|
24
71
|
don't remove it.
|
|
25
72
|
- Hosting (adapter + EmDash database/storage) comes from `vitopsHosting()`
|
|
@@ -16,6 +16,10 @@ Scaffolded from `vp create @getvitops:emdash`.
|
|
|
16
16
|
- **`@getvitops/emdash`** — design-system Portable Text blocks in the editor
|
|
17
17
|
slash menu: image compare, copy snippet, banner, disclosure, carousel
|
|
18
18
|
|
|
19
|
+
`src/assets/placeholder/` ships neutral wireframe images for layout work
|
|
20
|
+
before real imagery exists — delete once real assets land (separate from
|
|
21
|
+
`src/assets/logo.svg`, the brand asset).
|
|
22
|
+
|
|
19
23
|
## Local development
|
|
20
24
|
|
|
21
25
|
```bash
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$schema": "
|
|
2
|
+
"$schema": "./node_modules/@getvitops/generator/schema.json",
|
|
3
3
|
"colors": {
|
|
4
4
|
"palette": {
|
|
5
5
|
"brand": { "seed": "#2e9b73" },
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"p": "1rem",
|
|
55
55
|
"fs": "1rem"
|
|
56
56
|
},
|
|
57
|
-
"radii": { "
|
|
57
|
+
"radii": { "circle": "50%" },
|
|
58
58
|
"groups": {
|
|
59
59
|
"control": {
|
|
60
60
|
"ds": "var(--ds-default)",
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"init:hosting": "node scripts/init-hosting.mjs",
|
|
14
14
|
"typecheck": "astro check",
|
|
15
15
|
"build:css": "vitops generate -i design-system.json -f tailwind -o src/styles",
|
|
16
|
+
"lint:design": "vitops lint -i design-system.json -f tailwind --strict",
|
|
16
17
|
"prepare": "vp config"
|
|
17
18
|
},
|
|
18
19
|
"dependencies": {
|
|
@@ -1,15 +1,29 @@
|
|
|
1
1
|
// One-time GitHub bootstrap: creates a GitHub repo named after this project
|
|
2
|
-
// (package.json name), pushes `main` + `dev`,
|
|
3
|
-
//
|
|
4
|
-
// deploy-
|
|
2
|
+
// (package.json name), pushes `main` + `dev`, makes `dev` the default branch,
|
|
3
|
+
// and provisions the branch-protection + environment policy the deploy
|
|
4
|
+
// workflows assume (deploy-dev on push to dev, deploy-prod on push to main,
|
|
5
|
+
// promote.yml between them):
|
|
6
|
+
//
|
|
7
|
+
// - `main` requires a PR + the `ci` status check — it only advances via
|
|
8
|
+
// promote.yml, never a direct push.
|
|
9
|
+
// - the `production` GitHub Environment requires review from an admin
|
|
10
|
+
// collaborator before deploy-prod.yml is allowed to run — this is the
|
|
11
|
+
// "promoting to prod needs admin sign-off" gate.
|
|
12
|
+
// - `dev` is left UNPROTECTED on purpose — that's what lets an agent (or
|
|
13
|
+
// anyone with push access) commit and merge there directly for
|
|
14
|
+
// integration; only the promotion to `main` is gated.
|
|
5
15
|
//
|
|
6
16
|
// pnpm run init:github # private repo (default)
|
|
7
17
|
// pnpm run init:github -- --public
|
|
8
18
|
// pnpm run init:github -- --dry-run
|
|
9
19
|
//
|
|
10
|
-
// Idempotent: safe to re-run — existing git repo, commits, dev branch,
|
|
11
|
-
//
|
|
12
|
-
// (`gh auth login`).
|
|
20
|
+
// Idempotent: safe to re-run — existing git repo, commits, dev branch, origin
|
|
21
|
+
// remote, branch protection and environments are all left alone or updated
|
|
22
|
+
// in place. Requires the GitHub CLI (`gh`), logged in (`gh auth login`).
|
|
23
|
+
//
|
|
24
|
+
// NOTE: branch protection and environment required-reviewers are GitHub
|
|
25
|
+
// Pro/Team features on a PRIVATE repo. Each `gh api` call below prints the
|
|
26
|
+
// manual equivalent and continues rather than aborting when it 403s.
|
|
13
27
|
|
|
14
28
|
import { execSync, spawnSync } from 'node:child_process';
|
|
15
29
|
import { readFileSync } from 'node:fs';
|
|
@@ -48,6 +62,26 @@ function run(cmd) {
|
|
|
48
62
|
}
|
|
49
63
|
}
|
|
50
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Like `run`, but for `gh api` calls that provision an org/plan-gated
|
|
67
|
+
* feature (branch protection, environment reviewers). A 403 there means the
|
|
68
|
+
* repo's plan doesn't support it — not that the bootstrap failed — so this
|
|
69
|
+
* prints the manual equivalent and keeps going instead of exiting.
|
|
70
|
+
*/
|
|
71
|
+
function runPolicy(cmd, manualHint) {
|
|
72
|
+
if (dryRun) {
|
|
73
|
+
console.log(`[dry-run] ${cmd}`);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
console.log(`$ ${cmd}`);
|
|
77
|
+
const result = spawnSync(cmd, { shell: true, encoding: 'utf8' });
|
|
78
|
+
if (result.status === 0) return;
|
|
79
|
+
console.warn(
|
|
80
|
+
`\nWARNING: \`${cmd}\` failed (likely needs GitHub Pro/Team on a private repo).\n` +
|
|
81
|
+
`Set it up manually instead:\n ${manualHint}\n`,
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
51
85
|
// --- Preflight: gh installed and authenticated -------------------------------
|
|
52
86
|
if (check(['gh', '--version']).status !== 0) {
|
|
53
87
|
console.error('ERROR: the GitHub CLI (`gh`) is required. Install it from https://cli.github.com');
|
|
@@ -64,7 +98,7 @@ if (quiet('git rev-parse --is-inside-work-tree') === null) {
|
|
|
64
98
|
}
|
|
65
99
|
if (quiet('git rev-parse HEAD') === null) {
|
|
66
100
|
run('git add -A');
|
|
67
|
-
run('git commit -m "Initial commit (scaffolded from @getvitops/create
|
|
101
|
+
run('git commit -m "Initial commit (scaffolded from @getvitops/create)"');
|
|
68
102
|
}
|
|
69
103
|
|
|
70
104
|
// The workflows key off `main`; normalize whatever the initial branch is.
|
|
@@ -86,9 +120,58 @@ run('git push -u origin main dev');
|
|
|
86
120
|
run('gh repo edit --default-branch dev');
|
|
87
121
|
run('git checkout dev');
|
|
88
122
|
|
|
123
|
+
// --- Policy: protect main, gate production behind admin review --------------
|
|
124
|
+
const nwo = quiet('gh repo view --json nameWithOwner -q .nameWithOwner');
|
|
125
|
+
if (nwo) {
|
|
126
|
+
runPolicy(
|
|
127
|
+
`gh api -X PUT repos/${nwo}/branches/main/protection --input - <<'JSON'
|
|
128
|
+
{
|
|
129
|
+
"required_status_checks": { "strict": true, "contexts": ["ci"] },
|
|
130
|
+
"enforce_admins": false,
|
|
131
|
+
"required_pull_request_reviews": { "required_approving_review_count": 1 },
|
|
132
|
+
"restrictions": null,
|
|
133
|
+
"allow_force_pushes": false,
|
|
134
|
+
"allow_deletions": false
|
|
135
|
+
}
|
|
136
|
+
JSON`,
|
|
137
|
+
`Settings → Branches → protect 'main' (require a PR, 1 approval, the 'ci' ` +
|
|
138
|
+
`status check, no force-push/deletion).`,
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
const admins = quiet(
|
|
142
|
+
`gh api repos/${nwo}/collaborators?permission=admin -q '[.[].login]' 2>/dev/null`,
|
|
143
|
+
);
|
|
144
|
+
const reviewers = admins
|
|
145
|
+
? JSON.parse(admins).map((login) => ({ type: 'User', id_or_login: login }))
|
|
146
|
+
: [];
|
|
147
|
+
|
|
148
|
+
runPolicy(
|
|
149
|
+
`gh api -X PUT repos/${nwo}/environments/production --input - <<'JSON'
|
|
150
|
+
{
|
|
151
|
+
"reviewers": ${JSON.stringify(reviewers)},
|
|
152
|
+
"deployment_branch_policy": { "protected_branches": true, "custom_branch_policies": false }
|
|
153
|
+
}
|
|
154
|
+
JSON`,
|
|
155
|
+
`Settings → Environments → 'production' → require reviewers = your admins, ` +
|
|
156
|
+
`restrict deployment branches to 'main'. This is the prod-needs-admin-approval gate.`,
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
// `dev` gets an environment too (for the deploy-dev workflow's `environment:
|
|
160
|
+
// dev` reference), but deliberately no reviewers and no branch restriction —
|
|
161
|
+
// that's what keeps it open for direct pushes/merges.
|
|
162
|
+
runPolicy(
|
|
163
|
+
`gh api -X PUT repos/${nwo}/environments/dev --input - <<'JSON'
|
|
164
|
+
{ "reviewers": [] }
|
|
165
|
+
JSON`,
|
|
166
|
+
`Settings → Environments → create 'dev' with no required reviewers.`,
|
|
167
|
+
);
|
|
168
|
+
} else {
|
|
169
|
+
console.warn('WARNING: could not resolve the repo name-with-owner; skipping policy setup.');
|
|
170
|
+
}
|
|
171
|
+
|
|
89
172
|
// --- What's left (manual, needs your Cloudflare account) ---------------------
|
|
90
173
|
console.log(`
|
|
91
|
-
Repo ready: main (production) + dev (integration, default branch).
|
|
174
|
+
Repo ready: main (protected, production) + dev (integration, default branch).
|
|
92
175
|
NOTE: the first workflow runs will fail until the secrets below exist.
|
|
93
176
|
|
|
94
177
|
Next steps:
|
|
@@ -97,6 +180,7 @@ Next steps:
|
|
|
97
180
|
2. Secrets: gh secret set CLOUDFLARE_API_TOKEN
|
|
98
181
|
gh secret set CLOUDFLARE_ACCOUNT_ID
|
|
99
182
|
gh secret set DEV_D1_ID
|
|
100
|
-
3.
|
|
101
|
-
|
|
183
|
+
3. Verify policy: Settings → Branches (main protected?) and
|
|
184
|
+
Settings → Environments → production (reviewers set?) —
|
|
185
|
+
printed above if this ran on a plan that supports them.
|
|
102
186
|
`);
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="400" height="400" viewBox="0 0 400 400"><rect width="100%" height="100%" fill="#DDDDDD"/><path fill="#999999" d="M87.3 204.72h11.66v-12.54q0-1.77.24-3.92zm17.36 0h4.82v3.79q0 .53-.35.91-.34.39-.98.39h-3.49v9.35h-5.7v-9.35H82.79q-.66 0-1.16-.42-.51-.41-.64-1.02l-.69-3.31 18.16-24.25h6.2zm35.59-4.72q0 5.01-1.08 8.71-1.07 3.69-2.97 6.1-1.89 2.42-4.47 3.59-2.59 1.18-5.58 1.18-3 0-5.55-1.18-2.56-1.17-4.44-3.59-1.88-2.41-2.94-6.1-1.06-3.7-1.06-8.71 0-5.03 1.06-8.72 1.06-3.68 2.94-6.09t4.44-3.59q2.55-1.18 5.55-1.18 2.99 0 5.58 1.18 2.58 1.18 4.47 3.59 1.9 2.41 2.97 6.09 1.08 3.69 1.08 8.72m-6.76 0q0-4.16-.61-6.89t-1.63-4.35q-1.02-1.61-2.35-2.26-1.32-.65-2.75-.65-1.41 0-2.72.65t-2.32 2.26q-1 1.62-1.6 4.35t-.6 6.89.6 6.89 1.6 4.35q1.01 1.61 2.32 2.26t2.72.65q1.43 0 2.75-.65 1.33-.65 2.35-2.26 1.02-1.62 1.63-4.35t.61-6.89m37.5 0q0 5.01-1.08 8.71-1.07 3.69-2.97 6.1-1.89 2.42-4.47 3.59-2.59 1.18-5.58 1.18-3 0-5.55-1.18-2.56-1.17-4.44-3.59-1.88-2.41-2.94-6.1-1.06-3.7-1.06-8.71 0-5.03 1.06-8.72 1.06-3.68 2.94-6.09t4.44-3.59q2.55-1.18 5.55-1.18 2.99 0 5.58 1.18 2.58 1.18 4.47 3.59 1.9 2.41 2.97 6.09 1.08 3.69 1.08 8.72m-6.76 0q0-4.16-.61-6.89t-1.63-4.35q-1.02-1.61-2.35-2.26-1.32-.65-2.75-.65-1.41 0-2.72.65t-2.32 2.26q-1 1.62-1.6 4.35t-.6 6.89.6 6.89 1.6 4.35q1.01 1.61 2.32 2.26t2.72.65q1.43 0 2.75-.65 1.33-.65 2.35-2.26 1.02-1.62 1.63-4.35t.61-6.89m48.84 10.39-3.47 3.42-8.99-8.99-9.06 9.04-3.47-3.42 9.06-9.09-8.64-8.64 3.45-3.44 8.64 8.64 8.58-8.59 3.5 3.45-8.61 8.61zm22.95-5.67h11.66v-12.54q0-1.77.24-3.92zm17.35 0h4.83v3.79q0 .53-.35.91-.34.39-.98.39h-3.5v9.35h-5.69v-9.35h-16.17q-.66 0-1.16-.42-.51-.41-.64-1.02l-.69-3.31 18.15-24.25h6.2zm35.59-4.72q0 5.01-1.07 8.71-1.07 3.69-2.97 6.1-1.89 2.42-4.48 3.59-2.58 1.18-5.57 1.18-3 0-5.56-1.18-2.55-1.17-4.43-3.59-1.89-2.41-2.95-6.1-1.06-3.7-1.06-8.71 0-5.03 1.06-8.72 1.06-3.68 2.95-6.09 1.88-2.41 4.43-3.59 2.56-1.18 5.56-1.18 2.99 0 5.57 1.18 2.59 1.18 4.48 3.59 1.9 2.41 2.97 6.09 1.07 3.69 1.07 8.72m-6.75 0q0-4.16-.61-6.89t-1.63-4.35q-1.02-1.61-2.35-2.26-1.32-.65-2.75-.65-1.41 0-2.72.65t-2.32 2.26q-1.01 1.62-1.6 4.35-.6 2.73-.6 6.89t.6 6.89q.59 2.73 1.6 4.35 1.01 1.61 2.32 2.26t2.72.65q1.43 0 2.75-.65 1.33-.65 2.35-2.26 1.02-1.62 1.63-4.35t.61-6.89m37.49 0q0 5.01-1.07 8.71-1.07 3.69-2.97 6.1-1.89 2.42-4.48 3.59-2.58 1.18-5.57 1.18-3 0-5.56-1.18-2.55-1.17-4.43-3.59-1.89-2.41-2.95-6.1-1.06-3.7-1.06-8.71 0-5.03 1.06-8.72 1.06-3.68 2.95-6.09 1.88-2.41 4.43-3.59 2.56-1.18 5.56-1.18 2.99 0 5.57 1.18 2.59 1.18 4.48 3.59 1.9 2.41 2.97 6.09 1.07 3.69 1.07 8.72m-6.75 0q0-4.16-.61-6.89t-1.63-4.35q-1.02-1.61-2.35-2.26-1.32-.65-2.75-.65-1.41 0-2.72.65t-2.32 2.26q-1.01 1.62-1.6 4.35-.6 2.73-.6 6.89t.6 6.89q.59 2.73 1.6 4.35 1.01 1.61 2.32 2.26t2.72.65q1.43 0 2.75-.65 1.33-.65 2.35-2.26 1.02-1.62 1.63-4.35t.61-6.89"/></svg>
|