@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.
@@ -14,7 +14,7 @@ as Cloudflare Workers static assets (no server runtime, no database).
14
14
  portrait, landscape; SVG + PNG) for layout work before real imagery
15
15
  exists. Delete the directory once real assets land.
16
16
  - Prefer framework CSS classes (utilities, patterns, type roles like
17
- `font-heading`) over bespoke CSS. Run `npx vitops agents` to append the
17
+ `font-heading`) over bespoke CSS. Run `vp exec vitops agents` to append the
18
18
  full generated class-vocabulary reference to this file.
19
19
  - **Layout foundations — these are the answer, not a starting point.** Six
20
20
  substitutions carry the structure of nearly every page:
@@ -47,18 +47,72 @@ as Cloudflare Workers static assets (no server runtime, no database).
47
47
  PR with auto-merge; merging deploys prod. Only run it when the user
48
48
  explicitly asks to promote/ship to production.
49
49
 
50
+ ## Labels: badge and tag
51
+
52
+ Two label patterns, both enabled in `site.json`'s `patterns.items`: **`badge`** is a
53
+ static label (a status, count or category) and **`tag`** is a label that may be
54
+ static _or_ dynamic (editable, dismissable, in a `tag-list`). Don't invent a third
55
+ name — `chip` and `pill` are not part of this vocabulary. The button pattern's class
56
+ is `btn` (`<a class="btn">` is styled, not just a bare `<button>`).
57
+
58
+ ## Content Security Policy
59
+
60
+ `public/_headers` ships a **`Content-Security-Policy-Report-Only`** header for every
61
+ page (they are all static assets): violations are logged to the browser console and
62
+ nothing is blocked, so it can't break a page. Its `script-src` is `'self'` plus the
63
+ SHA-256 of each inline script `<Head />` emits (the colour-scheme bootstrap and the
64
+ consent-gate stub), with no `'unsafe-inline'`. `contentSecurityPolicy()` and
65
+ `inlineScriptHashes()` from `@getvitops/astro` produce the same strings.
66
+
67
+ Before enforcing it (rename the header to `Content-Security-Policy`), browse the pages
68
+ with the console open: add the origins of any third-party tag the consent gate
69
+ activates to `script-src` / `connect-src`, any embed to `frame-src`, and hash any
70
+ inline script you add yourself. Hashes rather than nonces because a static page has no
71
+ per-request response to carry one.
72
+
50
73
  ## Commands
51
74
 
52
- - `pnpm dev` — local dev
53
- - `pnpm typecheck` — `astro check`
54
- - `pnpm run build:css` — regenerate the framework CSS from `site.json`
55
- - `pnpm deploy` — build + `wrangler deploy` to prod
56
- - `pnpm run deploy:dev` — build + deploy the isolated dev worker
57
- - `pnpm run init:github` — one-time: create + push the GitHub repo (main +
58
- dev, dev default, branch protection + environment policy). Only run when
59
- the user asks to set up GitHub.
60
- - `pnpm run init:hosting` — one-time: read the Cloudflare account id and set
61
- it as a repo secret. Only run when the user asks for it.
62
- - `npx vitops validate site.json` — schema-check the site config
63
- - `npx vitops lint -i site.json -f css` — find framework classes in
64
- source that resolve to nothing under this format
75
+ This is a **Vite+** project: use `vp` (and its `vpr` shorthand), not `pnpm`/`npm`/`npx`.
76
+ The package manager underneath is pnpm, but `vp` picks it up for you.
77
+
78
+ | To… | Run |
79
+ | ---------------------------------------- | ---------------------------------------------------- |
80
+ | install dependencies | `vp install` |
81
+ | add / remove / update a package | `vp add <pkg>` · `vp remove <pkg>` · `vp update` |
82
+ | run a `package.json` script | `vpr <script>` (alias for `vp run <script>`) |
83
+ | run a locally installed binary | `vp exec <bin>` (e.g. `vp exec vitops docs classes`) |
84
+ | run a one-off package without installing | `vp dlx <pkg>` |
85
+ | format + lint + typecheck (files) | `vp check` |
86
+ | typecheck Astro (`.astro` files) | `vpr typecheck` |
87
+
88
+ **Scripts that wrap Astro must go through `vpr`.** `vp dev` / `vp build` run Vite
89
+ directly and skip Astro, so use `vpr dev`, `vpr build`, `vpr preview`.
90
+
91
+ **`vpr dev` returns immediately.** `astro dev` now starts the server in the
92
+ background. Check it with `vp exec astro dev status`, read its output with
93
+ `vp exec astro dev logs --follow`, and stop it with `vp exec astro dev stop`; the
94
+ site is at <http://localhost:4321>. Don't wait on it as if it were a foreground
95
+ process.
96
+
97
+ | Script | What it does |
98
+ | ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
99
+ | `vpr dev` | local dev |
100
+ | `vpr build:css` | regenerate the framework CSS from `site.json` |
101
+ | `vpr deploy` | build + `wrangler deploy` to prod |
102
+ | `vpr deploy:dev` | build + deploy the isolated dev worker |
103
+ | `vpr init:github` | one-time: create + push the GitHub repo (main + dev, dev default, branch protection + environment policy). Only when the user asks. |
104
+ | `vpr init:hosting` | one-time: read the Cloudflare account id and set it as a repo secret. Only when the user asks. |
105
+ | `vp exec vitops validate site.json` | schema-check the site config |
106
+ | `vp exec vitops lint -i site.json -f css` | find framework classes in source that resolve to nothing under this format |
107
+
108
+ **Release flow.** A site is not a published package, so there is no changeset or
109
+ version bump: ship by committing to `dev` (auto-deploys the dev worker) and then
110
+ running the **promote** workflow. `vp` has no `vp change` / `vp pm change` command
111
+ (checked against vp 1.5.0); for a _package_ it does have `vp pm version`,
112
+ `vp pm publish` and `vp pm stage`.
113
+
114
+ **Naming the project.** `vp create` takes the package name from the target
115
+ directory and invents a random slug when none is given, so pass one:
116
+ `vp create @getvitops:static --directory <client-slug>`. `wrangler.jsonc`'s `name`
117
+ is separate and still says what the template shipped — rename it to match before
118
+ the first deploy.
@@ -4,7 +4,7 @@ A static [Astro](https://astro.build) website on Cloudflare Workers (static
4
4
  assets — no server runtime), styled by the
5
5
  [Vitops design system](https://www.npmjs.com/org/getvitops).
6
6
 
7
- Scaffolded from `vp create @getvitops:static`.
7
+ Scaffolded from `vp create @getvitops:static --directory <client-slug>` (pass a directory: `vp create` derives the package name from it and invents a random slug otherwise).
8
8
 
9
9
  ## Stack
10
10
 
@@ -20,8 +20,8 @@ before real imagery exists — delete once real assets land.
20
20
  ## Local development
21
21
 
22
22
  ```bash
23
- pnpm install
24
- pnpm dev
23
+ vp install
24
+ vpr dev
25
25
  ```
26
26
 
27
27
  - Site: <http://localhost:4321>
@@ -29,8 +29,8 @@ pnpm dev
29
29
  ## Deploy
30
30
 
31
31
  ```bash
32
- npx wrangler login
33
- pnpm deploy
32
+ vp exec wrangler login
33
+ vpr deploy
34
34
  ```
35
35
 
36
36
  For a custom domain, uncomment the `routes` block in `wrangler.jsonc` and set
@@ -55,7 +55,7 @@ The repo ships a two-environment flow modeled on branch = environment:
55
55
  One-time setup — work through the checklist:
56
56
 
57
57
  ```bash
58
- pnpm run sop && open SETUP.html # or: xdg-open SETUP.html
58
+ vpr sop && open SETUP.html # or: xdg-open SETUP.html
59
59
  ```
60
60
 
61
61
  It covers host choice, GitHub + branch policy, hosting resources, going live
@@ -65,7 +65,7 @@ command per step. An AI agent can work the same list autonomously via
65
65
  `.agents/skills/setup/`; it stops at the steps that genuinely need a human
66
66
  (the Cloudflare API token, the domain cutover, publishing legal documents).
67
67
 
68
- `pnpm run deploy:dev` reproduces the dev deploy locally: it writes a
68
+ `vpr deploy:dev` reproduces the dev deploy locally: it writes a
69
69
  gitignored `.wrangler.dev.json` with the worker renamed and any custom-domain
70
70
  route prefixed `dev.` (`scripts/dev-wrangler-config.mjs`), so it never
71
71
  touches the prod worker.
@@ -7,7 +7,7 @@
7
7
  <!--
8
8
  Opened straight from disk (file://) before anything is deployed, which is
9
9
  when most of these steps happen — so it links a relative, pre-generated
10
- stylesheet rather than the site's own build output. Run `pnpm run sop`
10
+ stylesheet rather than the site's own build output. Run `vpr sop`
11
11
  first (it's a one-line `vitops generate -f css` into .vitops/sop/); the
12
12
  banner below explains this if the stylesheet 404s, and the markup stays
13
13
  legible either way — every item here is a real checkbox in a real
@@ -21,7 +21,7 @@
21
21
  -->
22
22
  <link id="sop-stylesheet" rel="stylesheet" href=".vitops/sop/styles.css" />
23
23
  <style>
24
- /* Minimal fallback so the page is still legible before `pnpm run sop`
24
+ /* Minimal fallback so the page is still legible before `vpr sop`
25
25
  has run once — intentionally not framework CSS, since it only ever
26
26
  applies in the narrow window before the real stylesheet exists. */
27
27
  body {
@@ -42,7 +42,7 @@
42
42
  <header class="region rhythm">
43
43
  <h1>Client onboarding checklist</h1>
44
44
  <p id="no-stylesheet-notice" class="banner" hidden>
45
- This page isn't styled yet — run <code>pnpm run sop</code> once (it generates
45
+ This page isn't styled yet — run <code>vpr sop</code> once (it generates
46
46
  <code>.vitops/sop/styles.css</code>), then reload.
47
47
  </p>
48
48
  <p>
@@ -90,7 +90,7 @@
90
90
  <span class="custom-check__box"
91
91
  ><span class="custom-check__indicator custom-check__indicator--check"></span
92
92
  ></span>
93
- <span> Preview it: <code>npx vitops setup repo --dry</code> </span>
93
+ <span> Preview it: <code>vp exec vitops setup repo --dry</code> </span>
94
94
  </label>
95
95
  <label class="custom-check">
96
96
  <input type="checkbox" data-sop="repo.run" />
@@ -98,7 +98,7 @@
98
98
  ><span class="custom-check__indicator custom-check__indicator--check"></span
99
99
  ></span>
100
100
  <span>
101
- Run it: <code>npx vitops setup repo</code> — creates the GitHub repo, pushes
101
+ Run it: <code>vp exec vitops setup repo</code> — creates the GitHub repo, pushes
102
102
  <code>main</code> + <code>dev</code>, makes <code>dev</code> default, and requests
103
103
  branch protection + the <code>production</code>/<code>dev</code>
104
104
  environments.
@@ -126,7 +126,7 @@
126
126
  <span class="custom-check__box"
127
127
  ><span class="custom-check__indicator custom-check__indicator--check"></span
128
128
  ></span>
129
- <span> Preview it: <code>npx vitops setup hosting --dry</code> </span>
129
+ <span> Preview it: <code>vp exec vitops setup hosting --dry</code> </span>
130
130
  </label>
131
131
  <label class="custom-check">
132
132
  <input type="checkbox" data-sop="hosting.run" />
@@ -134,10 +134,10 @@
134
134
  ><span class="custom-check__indicator custom-check__indicator--check"></span
135
135
  ></span>
136
136
  <span>
137
- Run it: <code>npx vitops setup hosting</code> — provisions the isolated dev D1
137
+ Run it: <code>vp exec vitops setup hosting</code> — provisions the isolated dev D1
138
138
  database + R2 bucket (EmDash template) and sets the <code>DEV_D1_ID</code> /
139
139
  <code>CLOUDFLARE_ACCOUNT_ID</code> repo secrets. Requires
140
- <code>npx wrangler login</code> first.
140
+ <code>vp exec wrangler login</code> first.
141
141
  </span>
142
142
  </label>
143
143
  <label class="custom-check">
@@ -276,7 +276,7 @@
276
276
  ><span class="custom-check__indicator custom-check__indicator--check"></span
277
277
  ></span>
278
278
  <span>
279
- Deploy once (<code>pnpm deploy</code>), note the printed
279
+ Deploy once (<code>vpr deploy</code>), note the printed
280
280
  <code>*.workers.dev</code> URL, and set
281
281
  <code>site.environments.production.url</code> in <code>site.json</code> to it.
282
282
  </span>
@@ -315,8 +315,8 @@
315
315
  ><span class="custom-check__indicator custom-check__indicator--check"></span
316
316
  ></span>
317
317
  <span>
318
- Preview it: <code>npx vitops setup domain --domain &lt;apex&gt; --dry</code> — this
319
- only plans and checks; it never applies the cutover itself.
318
+ Preview it: <code>vp exec vitops setup domain --domain &lt;apex&gt; --dry</code> —
319
+ this only plans and checks; it never applies the cutover itself.
320
320
  </span>
321
321
  </label>
322
322
  <label class="custom-check">
@@ -326,7 +326,7 @@
326
326
  ></span>
327
327
  <span>
328
328
  Uncomment the <code>routes</code> block in <code>wrangler.jsonc</code>, set your
329
- apex, and deploy: <code>pnpm deploy</code>.
329
+ apex, and deploy: <code>vpr deploy</code>.
330
330
  </span>
331
331
  </label>
332
332
  <label class="custom-check">
@@ -347,7 +347,8 @@
347
347
  ><span class="custom-check__indicator custom-check__indicator--check"></span
348
348
  ></span>
349
349
  <span>
350
- Re-run <code>npx vitops robots</code> and <code>npx vitops search notify</code>.
350
+ Re-run <code>vp exec vitops robots</code> and
351
+ <code>vp exec vitops search notify</code>.
351
352
  </span>
352
353
  </label>
353
354
  </div>
@@ -373,7 +374,7 @@
373
374
  <span class="custom-check__box"
374
375
  ><span class="custom-check__indicator custom-check__indicator--check"></span
375
376
  ></span>
376
- <span> Check it: <code>npx vitops validate site.json</code> </span>
377
+ <span> Check it: <code>vp exec vitops validate site.json</code> </span>
377
378
  </label>
378
379
  </div>
379
380
  </details>
@@ -398,8 +399,8 @@
398
399
  ><span class="custom-check__indicator custom-check__indicator--check"></span
399
400
  ></span>
400
401
  <span>
401
- Preview then run: <code>npx vitops setup search --dry</code>, then
402
- <code>npx vitops setup search</code>. This is a <strong>user OAuth</strong>
402
+ Preview then run: <code>vp exec vitops setup search --dry</code>, then
403
+ <code>vp exec vitops setup search</code>. This is a <strong>user OAuth</strong>
403
404
  action, deliberately — verifying a site makes the caller an owner, and that should
404
405
  be a person, not a deploy robot.
405
406
  </span>
@@ -410,8 +411,25 @@
410
411
  ><span class="custom-check__indicator custom-check__indicator--check"></span
411
412
  ></span>
412
413
  <span>
413
- Preview then run: <code>npx vitops setup ads --dry</code>, then
414
- <code>npx vitops setup ads</code>.
414
+ Preview then run: <code>vp exec vitops setup ads --dry</code>, then
415
+ <code>vp exec vitops setup ads</code>.
416
+ </span>
417
+ </label>
418
+ <label class="custom-check">
419
+ <input type="checkbox" data-sop="connections.turnstile" />
420
+ <span class="custom-check__box"
421
+ ><span class="custom-check__indicator custom-check__indicator--check"></span
422
+ ></span>
423
+ <span>
424
+ If the site has a contact form, create its spam check:
425
+ <code>vp exec vitops setup turnstile --dry</code>, then
426
+ <code>vp exec vitops setup turnstile</code>. It needs
427
+ <code>CLOUDFLARE_API_TOKEN</code> with
428
+ <strong>Account &gt; Turnstile &gt; Edit</strong> (plus
429
+ <code>CLOUDFLARE_ACCOUNT_ID</code>) and <code>wrangler login</code>. The public site
430
+ key goes into <code>site.security.turnstile.siteKey</code>; the secret is saved as
431
+ the Worker secret <code>VITOPS_TURNSTILE_SECRET</code> and never touches
432
+ <code>site.json</code>. It never edits or deletes an existing widget.
415
433
  </span>
416
434
  </label>
417
435
  <label class="custom-check">
@@ -453,10 +471,10 @@
453
471
  ></span>
454
472
  <span>
455
473
  Generate real documents from the filled-in config —
456
- <code>npx vitops legal --format portable-text</code> (EmDash; paste into the admin)
457
- or <code>npx vitops legal --format md</code> (static; commit the output). The seeded
458
- <code>/terms</code> and <code>/privacy</code> pages are deliberately short,
459
- obviously-unfinished placeholders.
474
+ <code>vp exec vitops legal --format portable-text</code> (EmDash; paste into the
475
+ admin) or <code>vp exec vitops legal --format md</code> (static; commit the output).
476
+ The seeded <code>/terms</code> and <code>/privacy</code> pages are deliberately
477
+ short, obviously-unfinished placeholders.
460
478
  </span>
461
479
  </label>
462
480
  <label class="custom-check">
@@ -484,7 +502,7 @@
484
502
  ></span>
485
503
  <span>
486
504
  Robots directive matches the live origin's intent (blocked while on workers.dev,
487
- open after cutover) — <code>npx vitops robots</code>.
505
+ open after cutover) — <code>vp exec vitops robots</code>.
488
506
  </span>
489
507
  </label>
490
508
  <label class="custom-check">
@@ -509,7 +527,7 @@
509
527
  ><span class="custom-check__indicator custom-check__indicator--check"></span
510
528
  ></span>
511
529
  <span>
512
- <code>npx vitops lint --src</code> and <code>pnpm typecheck</code> both clean.
530
+ <code>vp exec vitops lint --src</code> and <code>vpr typecheck</code> both clean.
513
531
  </span>
514
532
  </label>
515
533
  <label class="custom-check">
@@ -527,7 +545,7 @@
527
545
  <span class="custom-check__box"
528
546
  ><span class="custom-check__indicator custom-check__indicator--check"></span
529
547
  ></span>
530
- <span> <code>npx vitops search notify</code> run once live. </span>
548
+ <span> <code>vp exec vitops search notify</code> run once live. </span>
531
549
  </label>
532
550
  <label class="custom-check">
533
551
  <input type="checkbox" data-sop="launch.check" />
@@ -535,8 +553,8 @@
535
553
  ><span class="custom-check__indicator custom-check__indicator--check"></span
536
554
  ></span>
537
555
  <span>
538
- A day or two later: <code>npx vitops search notify --check</code> confirms Google
539
- actually indexed it.
556
+ A day or two later: <code>vp exec vitops search notify --check</code> confirms
557
+ Google actually indexed it.
540
558
  </span>
541
559
  </label>
542
560
  </div>
@@ -1,9 +1,21 @@
1
- # Cloudflare Workers static assets apply this file to every asset response.
1
+ # Cloudflare static-assets response headers for the whole site (every page here is
2
+ # a static asset). See AGENTS.md "Content Security Policy".
2
3
  #
3
- # A baseline that cannot break a page. No Content-Security-Policy on purpose: <Head /> emits
4
- # inline scripts (the consent stub, the colour-scheme bootstrap), so a strict policy needs
5
- # per-site nonces or hashes — add one deliberately once you know which third-party tags you load.
4
+ # REPORT-ONLY on purpose: violations are logged to the browser console and
5
+ # nothing is blocked, so this cannot break a page. Read the console on the
6
+ # pages you ship, add what is legitimately needed, then rename the header to
7
+ # `Content-Security-Policy` to enforce it.
8
+ #
9
+ # The two sha256 values are the inline scripts <Head /> emits (the colour-scheme
10
+ # bootstrap and the consent-gate stub). They change only when
11
+ # @getvitops/astro changes those scripts; regenerate with
12
+ # vp exec node -e "import('@getvitops/astro').then(m => console.log(m.contentSecurityPolicy()))"
13
+ # `templates.test.ts` in the toolchain repo guards that they match.
14
+ #
15
+ # Third-party tags the consent gate activates need their origins in
16
+ # script-src/connect-src before you enforce.
6
17
  /*
18
+ Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self' 'sha256-CEiVlTo192T9Rug/nAl+ihvkqOQkYdr6Rq3oc+gR9JU=' 'sha256-z/UqsITVvNGOJSaOSvhN1rdtyYdMIxpQ/lMXM1q0g90=' https://challenges.cloudflare.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self'; frame-src https://challenges.cloudflare.com; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'
7
19
  X-Content-Type-Options: nosniff
8
20
  Referrer-Policy: strict-origin-when-cross-origin
9
21
  Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=()
@@ -93,6 +93,13 @@
93
93
  "br": "0.5rem",
94
94
  "p": "1rem",
95
95
  "fs": "var(--fs-default)"
96
+ },
97
+ "label": {
98
+ "ds": "var(--ds-default)",
99
+ "b": "none",
100
+ "br": "0.25rem",
101
+ "p": "0.25em 0.5em",
102
+ "fs": "0.75rem"
96
103
  }
97
104
  },
98
105
  "z": {
@@ -126,7 +133,8 @@
126
133
  "ring": true
127
134
  }
128
135
  },
129
- "roles": []
136
+ "roles": ["success", "danger", "warning", "info"],
137
+ "class": "btn"
130
138
  },
131
139
  "card": {
132
140
  "group": "panel",
@@ -140,6 +148,51 @@
140
148
  },
141
149
  "states": {},
142
150
  "roles": []
151
+ },
152
+ "badge": {
153
+ "group": "label",
154
+ "class": "badge",
155
+ "default_role": "brand-primary",
156
+ "overrides": {
157
+ "p": "0.2em 0.6em",
158
+ "br": "999px",
159
+ "fs": "0.85em"
160
+ },
161
+ "base": {
162
+ "display": "inline-block",
163
+ "padding": "var(--p-badge-group)",
164
+ "border-radius": "var(--br-badge-group)",
165
+ "font-size": "var(--fs-badge-group)",
166
+ "font-weight": "600",
167
+ "color": "var(--color-text-on-brand-primary)"
168
+ },
169
+ "states": {},
170
+ "roles": ["success", "danger", "warning", "info"]
171
+ },
172
+ "tag": {
173
+ "group": "label",
174
+ "class": "tag",
175
+ "default_role": "neutral",
176
+ "overrides": {
177
+ "p": "0.25em 0.6em",
178
+ "b": "1px solid currentColor",
179
+ "fs": "0.85em"
180
+ },
181
+ "base": {
182
+ "display": "inline-flex",
183
+ "align-items": "center",
184
+ "gap": "0.25em",
185
+ "padding": "var(--p-tag-group)",
186
+ "border": "var(--b-tag-group)",
187
+ "border-radius": "var(--br-tag-group)",
188
+ "font-size": "var(--fs-tag-group)",
189
+ "font-weight": "500",
190
+ "line-height": "1.2",
191
+ "white-space": "nowrap",
192
+ "color": "var(--color-text-neutral-bold)"
193
+ },
194
+ "states": {},
195
+ "roles": ["success", "danger", "warning", "info"]
143
196
  }
144
197
  }
145
198
  },