@getvitops/create 0.5.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 +13 -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 +4 -0
- package/templates/emdash/README.md +4 -0
- package/templates/emdash/design-system.json +1 -1
- 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/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
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
This is a static Astro website styled by the Vitops design system, deployed
|
|
2
|
+
as Cloudflare Workers static assets (no server runtime, no database).
|
|
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 `src/styles/` by
|
|
8
|
+
hand — it is generated from the JSON by the `vitops()` integration on every
|
|
9
|
+
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.
|
|
13
|
+
- Prefer framework CSS classes (utilities, patterns, type roles like
|
|
14
|
+
`font-heading`) over bespoke CSS. Run `npx vitops agents` to append the
|
|
15
|
+
full generated class-vocabulary reference to this file.
|
|
16
|
+
- **Layout foundations — these are the answer, not a starting point.** Six
|
|
17
|
+
substitutions carry the structure of nearly every page:
|
|
18
|
+
| About to write… | Write instead |
|
|
19
|
+
| --- | --- |
|
|
20
|
+
| a `wrap`/`wrapper`/`container`/`inner` class | **`centered`** |
|
|
21
|
+
| `max-width` + `margin-inline: auto` | **`centered`** |
|
|
22
|
+
| `padding-block` on a section | **`region`** |
|
|
23
|
+
| margins between headings/paragraphs/lists | **`rhythm`** |
|
|
24
|
+
| `display: grid` + `grid-template-columns: repeat(n, …)` | **`subgrid`** |
|
|
25
|
+
| `repeat(auto-fit, minmax(…))` | **`grid-auto`** |
|
|
26
|
+
| `display: flex` + `gap` for a row of controls | **`cluster`** |
|
|
27
|
+
- Every page is prerendered by default (`output: 'static'`) — there is no
|
|
28
|
+
server to fall back to, so nothing here can read request-time data
|
|
29
|
+
(cookies, headers, query params) at render time. If a page genuinely needs
|
|
30
|
+
that, it needs an adapter and a different template — this one stays pure
|
|
31
|
+
static on purpose.
|
|
32
|
+
|
|
33
|
+
## Branch model
|
|
34
|
+
|
|
35
|
+
- Work on **`dev`** (the integration branch). Every push auto-deploys the
|
|
36
|
+
isolated `-dev` worker (`dev.<domain>` with a custom domain).
|
|
37
|
+
- **`main`** is production and branch-protected — never push or merge to it
|
|
38
|
+
directly. Production ships only via the manual **promote** flow:
|
|
39
|
+
`.github/workflows/promote.yml`, dispatched manually, opens a `dev → main`
|
|
40
|
+
PR with auto-merge; merging deploys prod. Only run it when the user
|
|
41
|
+
explicitly asks to promote/ship to production.
|
|
42
|
+
|
|
43
|
+
## Commands
|
|
44
|
+
|
|
45
|
+
- `pnpm dev` — local dev
|
|
46
|
+
- `pnpm typecheck` — `astro check`
|
|
47
|
+
- `pnpm run build:css` — regenerate the framework CSS from `design-system.json`
|
|
48
|
+
- `pnpm deploy` — build + `wrangler deploy` to prod
|
|
49
|
+
- `pnpm run deploy:dev` — build + deploy the isolated dev worker
|
|
50
|
+
- `pnpm run init:github` — one-time: create + push the GitHub repo (main +
|
|
51
|
+
dev, dev default, branch protection + environment policy). Only run when
|
|
52
|
+
the user asks to set up GitHub.
|
|
53
|
+
- `pnpm run init:hosting` — one-time: read the Cloudflare account id and set
|
|
54
|
+
it as a repo secret. Only run when the user asks for it.
|
|
55
|
+
- `npx vitops validate design-system.json` — schema-check the design config
|
|
56
|
+
- `npx vitops lint -i design-system.json -f css` — find framework classes in
|
|
57
|
+
source that resolve to nothing under this format
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# My Static Site
|
|
2
|
+
|
|
3
|
+
A static [Astro](https://astro.build) website on Cloudflare Workers (static
|
|
4
|
+
assets — no server runtime), styled by the
|
|
5
|
+
[Vitops design system](https://www.npmjs.com/org/getvitops).
|
|
6
|
+
|
|
7
|
+
Scaffolded from `vp create @getvitops:static`.
|
|
8
|
+
|
|
9
|
+
## Stack
|
|
10
|
+
|
|
11
|
+
- **Astro** (`output: 'static'`) on **Cloudflare Workers Static Assets** — no
|
|
12
|
+
adapter, no database, no server runtime
|
|
13
|
+
- **`@getvitops/astro`** — generates the design-system CSS from
|
|
14
|
+
`design-system.json` and serves the web-component runtime
|
|
15
|
+
|
|
16
|
+
`src/assets/placeholder/` ships neutral wireframe images for layout work
|
|
17
|
+
before real imagery exists — delete once real assets land.
|
|
18
|
+
|
|
19
|
+
## Local development
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pnpm install
|
|
23
|
+
pnpm dev
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
- Site: <http://localhost:4321>
|
|
27
|
+
|
|
28
|
+
## Deploy
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npx wrangler login
|
|
32
|
+
pnpm deploy
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
For a custom domain, uncomment the `routes` block in `wrangler.jsonc` and set
|
|
36
|
+
your apex — Cloudflare provisions the DNS record + TLS cert on deploy.
|
|
37
|
+
|
|
38
|
+
## Environments: dev → main promotion
|
|
39
|
+
|
|
40
|
+
The repo ships a two-environment flow modeled on branch = environment:
|
|
41
|
+
|
|
42
|
+
- **`dev`** is the integration branch. Every push auto-deploys the isolated
|
|
43
|
+
`-dev` worker (`.github/workflows/deploy-dev.yml`), served at
|
|
44
|
+
`dev.<domain>` when a custom domain is configured (or the worker's
|
|
45
|
+
`workers.dev` URL otherwise).
|
|
46
|
+
- **`main`** is production. Every push auto-deploys the prod worker
|
|
47
|
+
(`.github/workflows/deploy-prod.yml`) — gated behind a `production`
|
|
48
|
+
GitHub Environment that requires an admin's approval before it runs.
|
|
49
|
+
- **Promotion is manual**: run the **"Promote dev → main"** workflow
|
|
50
|
+
(`.github/workflows/promote.yml`) and it opens a PR from `dev` into `main`
|
|
51
|
+
with auto-merge enabled. With required reviews on `main`, a human approves
|
|
52
|
+
the PR; merging deploys prod (after the admin approval above).
|
|
53
|
+
|
|
54
|
+
One-time setup:
|
|
55
|
+
|
|
56
|
+
1. `pnpm run init:github` — creates a GitHub repo named after this project
|
|
57
|
+
(via the `gh` CLI; `-- --public` for a public repo), commits if needed,
|
|
58
|
+
pushes `main` + `dev`, makes `dev` the default branch, protects `main`
|
|
59
|
+
(PR + review + the `ci` check required), and creates a `production`
|
|
60
|
+
environment gated on your admin collaborators. Idempotent; `-- --dry-run`
|
|
61
|
+
previews. (Branch protection and environment reviewers need GitHub
|
|
62
|
+
Pro/Team on a private repo — the script prints the manual equivalent if
|
|
63
|
+
your plan doesn't support them.)
|
|
64
|
+
2. `pnpm run init:hosting` — reads your Cloudflare account id (after
|
|
65
|
+
`npx wrangler login`) and sets the `CLOUDFLARE_ACCOUNT_ID` repo secret.
|
|
66
|
+
Idempotent; `-- --dry-run` previews.
|
|
67
|
+
3. Create a Cloudflare API token (Workers edit —
|
|
68
|
+
<https://dash.cloudflare.com/profile/api-tokens>; tokens can't be created
|
|
69
|
+
via CLI) and `gh secret set CLOUDFLARE_API_TOKEN`. Optionally
|
|
70
|
+
`PROMOTE_TOKEN` (a PAT allowed to bypass protection) for hands-off
|
|
71
|
+
promotion. The first workflow runs fail until the secrets exist.
|
|
72
|
+
|
|
73
|
+
`pnpm run deploy:dev` reproduces the dev deploy locally: it writes a
|
|
74
|
+
gitignored `.wrangler.dev.json` with the worker renamed and any custom-domain
|
|
75
|
+
route prefixed `dev.` (`scripts/dev-wrangler-config.mjs`), so it never
|
|
76
|
+
touches the prod worker.
|
|
77
|
+
|
|
78
|
+
## CI/deploy workflows
|
|
79
|
+
|
|
80
|
+
`.github/workflows/{ci,deploy-dev,deploy-prod,promote}.yml` are thin callers
|
|
81
|
+
of reusable workflows published by
|
|
82
|
+
[`getvitops/design-system`](https://github.com/getvitops/design-system),
|
|
83
|
+
pinned to `@v1`. A fix to the shared deploy logic there reaches this repo the
|
|
84
|
+
next time those workflows run — no re-pasting needed. Bump the pin
|
|
85
|
+
deliberately if you want to hold back a change.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# build output
|
|
2
|
+
dist/
|
|
3
|
+
|
|
4
|
+
# dependencies
|
|
5
|
+
node_modules/
|
|
6
|
+
|
|
7
|
+
# generated types
|
|
8
|
+
.astro/
|
|
9
|
+
|
|
10
|
+
# wrangler local state
|
|
11
|
+
.wrangler/
|
|
12
|
+
.wrangler.dev.json
|
|
13
|
+
|
|
14
|
+
# generated by the vitops() integration on dev/build
|
|
15
|
+
src/styles/
|
|
16
|
+
public/vitops/
|
|
17
|
+
|
|
18
|
+
# environment
|
|
19
|
+
.env
|
|
20
|
+
.env.*
|
|
21
|
+
!.env.example
|
|
22
|
+
|
|
23
|
+
# os / editor
|
|
24
|
+
.DS_Store
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import vitops from '@getvitops/astro';
|
|
3
|
+
import { defineConfig } from 'astro/config';
|
|
4
|
+
|
|
5
|
+
export default defineConfig({
|
|
6
|
+
// Pure static output — deploys as Workers Static Assets (see
|
|
7
|
+
// wrangler.jsonc), no adapter and no server runtime.
|
|
8
|
+
output: 'static',
|
|
9
|
+
integrations: [
|
|
10
|
+
vitops({
|
|
11
|
+
css: {
|
|
12
|
+
input: 'design-system.json',
|
|
13
|
+
format: 'css',
|
|
14
|
+
out: 'src/styles',
|
|
15
|
+
},
|
|
16
|
+
webComponents: true,
|
|
17
|
+
}),
|
|
18
|
+
],
|
|
19
|
+
});
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "./node_modules/@getvitops/generator/schema.json",
|
|
3
|
+
"colors": {
|
|
4
|
+
"palette": {
|
|
5
|
+
"brand": { "seed": "#2e9b73" },
|
|
6
|
+
"ink": { "seed": "#3f4a5c" },
|
|
7
|
+
"sky": { "seed": "#2f7bc0" },
|
|
8
|
+
"sun": { "seed": "#d08a1f" },
|
|
9
|
+
"rose": { "seed": "#c74a42" }
|
|
10
|
+
},
|
|
11
|
+
"roles": {
|
|
12
|
+
"neutral": "ink",
|
|
13
|
+
"surface": "ink",
|
|
14
|
+
"ui-primary": "brand",
|
|
15
|
+
"brand-primary": "brand",
|
|
16
|
+
"info": "sky",
|
|
17
|
+
"success": "brand",
|
|
18
|
+
"warning": "sun",
|
|
19
|
+
"danger": "rose"
|
|
20
|
+
},
|
|
21
|
+
"utilities": ["bg", "text", "icon", "border"]
|
|
22
|
+
},
|
|
23
|
+
"shadows": {
|
|
24
|
+
"sm": "0 1px 2px rgb(0 0 0 / 0.08)",
|
|
25
|
+
"md": "0 4px 6px rgb(0 0 0 / 0.12)",
|
|
26
|
+
"lg": "0 10px 15px rgb(0 0 0 / 0.18)"
|
|
27
|
+
},
|
|
28
|
+
"fonts": {
|
|
29
|
+
"display": "system-ui, -apple-system, \"Segoe UI\", sans-serif",
|
|
30
|
+
"sans": "system-ui, -apple-system, \"Segoe UI\", Roboto, sans-serif",
|
|
31
|
+
"mono": "ui-monospace, \"SF Mono\", Menlo, monospace"
|
|
32
|
+
},
|
|
33
|
+
"typeScale": {
|
|
34
|
+
"base": "1rem",
|
|
35
|
+
"names": ["2xs", "xs", "s", "m", "l", "xl", "2xl", "3xl"],
|
|
36
|
+
"baseStep": 4,
|
|
37
|
+
"baseline": "m",
|
|
38
|
+
"ratio": 1.2,
|
|
39
|
+
"fluid": { "minVw": "22.5rem", "maxVw": "80rem", "minRatio": 1.12 }
|
|
40
|
+
},
|
|
41
|
+
"spaceScale": {
|
|
42
|
+
"base": "1rem",
|
|
43
|
+
"names": ["2xs", "xs", "s", "m", "l", "xl", "2xl", "3xl"],
|
|
44
|
+
"baseStep": 4,
|
|
45
|
+
"baseline": "m",
|
|
46
|
+
"ratio": 1.25,
|
|
47
|
+
"fluid": { "minVw": "22.5rem", "maxVw": "80rem", "minRatio": 1.2 }
|
|
48
|
+
},
|
|
49
|
+
"patterns": {
|
|
50
|
+
"defaults": {
|
|
51
|
+
"ds": "none",
|
|
52
|
+
"b": "1px solid var(--color-bg-surface-x-muted)",
|
|
53
|
+
"br": "0.375rem",
|
|
54
|
+
"p": "1rem",
|
|
55
|
+
"fs": "1rem"
|
|
56
|
+
},
|
|
57
|
+
"radii": { "circle": "50%" },
|
|
58
|
+
"groups": {
|
|
59
|
+
"control": {
|
|
60
|
+
"ds": "var(--ds-default)",
|
|
61
|
+
"b": "var(--b-default)",
|
|
62
|
+
"br": "var(--br-default)",
|
|
63
|
+
"p": "0.5em 1em",
|
|
64
|
+
"fs": "var(--fs-default)"
|
|
65
|
+
},
|
|
66
|
+
"panel": {
|
|
67
|
+
"ds": "var(--shadow-md)",
|
|
68
|
+
"b": "var(--b-default)",
|
|
69
|
+
"br": "0.5rem",
|
|
70
|
+
"p": "1rem",
|
|
71
|
+
"fs": "var(--fs-default)"
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
"z": { "raised": 1, "sticky": 100, "overlay": 200, "top": 1000 },
|
|
75
|
+
"items": {
|
|
76
|
+
"button": {
|
|
77
|
+
"group": "control",
|
|
78
|
+
"element": "button",
|
|
79
|
+
"default_role": "brand-primary",
|
|
80
|
+
"base": {
|
|
81
|
+
"padding": "0.6em 1.2em",
|
|
82
|
+
"border-radius": "var(--br-control, 0.25rem)",
|
|
83
|
+
"border": "none",
|
|
84
|
+
"font-weight": "600",
|
|
85
|
+
"color": "#fff",
|
|
86
|
+
"cursor": "pointer",
|
|
87
|
+
"box-shadow": "var(--shadow-sm)"
|
|
88
|
+
},
|
|
89
|
+
"states": {
|
|
90
|
+
"hover": { "step": 1 },
|
|
91
|
+
"active": { "scale": 0.97 },
|
|
92
|
+
"focus-visible": { "ring": true }
|
|
93
|
+
},
|
|
94
|
+
"roles": []
|
|
95
|
+
},
|
|
96
|
+
"card": {
|
|
97
|
+
"group": "panel",
|
|
98
|
+
"class": "card",
|
|
99
|
+
"base": {
|
|
100
|
+
"border-radius": "var(--br-card-group)",
|
|
101
|
+
"padding": "1.5rem",
|
|
102
|
+
"background": "var(--color-bg-surface-muted)",
|
|
103
|
+
"border": "var(--b-card-group)",
|
|
104
|
+
"box-shadow": "var(--ds-card-group)"
|
|
105
|
+
},
|
|
106
|
+
"states": {},
|
|
107
|
+
"roles": []
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
"typography": {
|
|
112
|
+
"families": {
|
|
113
|
+
"display": "var(--font-display)",
|
|
114
|
+
"sans": "var(--font-sans)",
|
|
115
|
+
"code": "var(--font-mono, ui-monospace, monospace)"
|
|
116
|
+
},
|
|
117
|
+
"roles": {
|
|
118
|
+
"display": {
|
|
119
|
+
"family": "display",
|
|
120
|
+
"size": "var(--text-3xl)",
|
|
121
|
+
"weight": 700,
|
|
122
|
+
"line-height": "1.05",
|
|
123
|
+
"tracking": "-0.03em"
|
|
124
|
+
},
|
|
125
|
+
"heading": {
|
|
126
|
+
"family": "display",
|
|
127
|
+
"size": "var(--text-xl)",
|
|
128
|
+
"weight": 600,
|
|
129
|
+
"line-height": "1.3"
|
|
130
|
+
},
|
|
131
|
+
"body": { "family": "sans", "size": "var(--text-m)", "weight": 400, "line-height": "1.55" }
|
|
132
|
+
},
|
|
133
|
+
"headings": { "h1": "display", "h2": "heading", "h3": "heading" }
|
|
134
|
+
},
|
|
135
|
+
"animations": {
|
|
136
|
+
"effects": {
|
|
137
|
+
"fade-in": { "kf": "composite", "vars": { "opacity-from": 0 } },
|
|
138
|
+
"slide-up": {
|
|
139
|
+
"kf": "composite",
|
|
140
|
+
"vars": { "translate-y-from": "var(--slide-distance, 2rem)" }
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
"journeys": {
|
|
144
|
+
"base": { "fade": { "opacity-from": 0 }, "slide": {} },
|
|
145
|
+
"compose": [["fade"], ["slide"], ["fade", "slide"]]
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "my-static-site",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "astro dev",
|
|
8
|
+
"build": "astro build",
|
|
9
|
+
"preview": "astro preview",
|
|
10
|
+
"deploy": "astro build && wrangler deploy",
|
|
11
|
+
"deploy:dev": "astro build && node scripts/dev-wrangler-config.mjs && wrangler deploy --config .wrangler.dev.json",
|
|
12
|
+
"init:github": "node scripts/init-github.mjs",
|
|
13
|
+
"init:hosting": "node scripts/init-hosting.mjs",
|
|
14
|
+
"typecheck": "astro check",
|
|
15
|
+
"build:css": "vitops generate -i design-system.json -f css -o src/styles",
|
|
16
|
+
"lint:design": "vitops lint -i design-system.json -f css --strict",
|
|
17
|
+
"prepare": "vp config"
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@getvitops/astro": "latest",
|
|
21
|
+
"astro": "^7.1.2"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@astrojs/check": "^0.9.7",
|
|
25
|
+
"@getvitops/cli": "latest",
|
|
26
|
+
"typescript": "^6.0.3",
|
|
27
|
+
"vite": "latest",
|
|
28
|
+
"vite-plus": "latest",
|
|
29
|
+
"wrangler": "^4.111.0"
|
|
30
|
+
},
|
|
31
|
+
"devEngines": {
|
|
32
|
+
"runtime": {
|
|
33
|
+
"name": "node",
|
|
34
|
+
"version": "24.18.0",
|
|
35
|
+
"onFail": "download"
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"packageManager": "pnpm@11.17.0"
|
|
39
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Local counterpart to the rewrite the CI dev-deploy workflow does: no adapter
|
|
2
|
+
// regenerates this site's `wrangler.jsonc` (it's a pure static build — Workers
|
|
3
|
+
// Static Assets, no server config to merge), so the checked-in file already
|
|
4
|
+
// IS the deploy config. This just suffixes the worker name and any
|
|
5
|
+
// custom-domain route so a local `pnpm run deploy:dev` never touches the prod
|
|
6
|
+
// worker, writing the result to a gitignored file rather than editing
|
|
7
|
+
// wrangler.jsonc in place.
|
|
8
|
+
//
|
|
9
|
+
// pnpm run deploy:dev → astro build && node scripts/dev-wrangler-config.mjs
|
|
10
|
+
// && wrangler deploy --config .wrangler.dev.json
|
|
11
|
+
|
|
12
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
13
|
+
|
|
14
|
+
const SRC = 'wrangler.jsonc';
|
|
15
|
+
const OUT = '.wrangler.dev.json';
|
|
16
|
+
|
|
17
|
+
const raw = readFileSync(SRC, 'utf8')
|
|
18
|
+
.replace(/\/\/.*$/gm, '')
|
|
19
|
+
.replace(/,(\s*[}\]])/g, '$1');
|
|
20
|
+
const cfg = JSON.parse(raw);
|
|
21
|
+
|
|
22
|
+
cfg.name = `${cfg.name}-dev`;
|
|
23
|
+
cfg.routes = (cfg.routes ?? []).map((r) =>
|
|
24
|
+
r?.pattern ? { ...r, pattern: `dev.${r.pattern}` } : r,
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
writeFileSync(OUT, `${JSON.stringify(cfg, null, 2)}\n`);
|
|
28
|
+
console.log(`Wrote ${OUT}\n name: ${cfg.name}\n routes: ${JSON.stringify(cfg.routes)}`);
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
// One-time GitHub bootstrap: creates a GitHub repo named after this project
|
|
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.
|
|
15
|
+
//
|
|
16
|
+
// pnpm run init:github # private repo (default)
|
|
17
|
+
// pnpm run init:github -- --public
|
|
18
|
+
// pnpm run init:github -- --dry-run
|
|
19
|
+
//
|
|
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.
|
|
27
|
+
|
|
28
|
+
import { execSync, spawnSync } from 'node:child_process';
|
|
29
|
+
import { readFileSync } from 'node:fs';
|
|
30
|
+
|
|
31
|
+
const args = new Set(process.argv.slice(2));
|
|
32
|
+
const dryRun = args.has('--dry-run');
|
|
33
|
+
const visibility = args.has('--public') ? '--public' : '--private';
|
|
34
|
+
|
|
35
|
+
const repoName = JSON.parse(readFileSync('package.json', 'utf8')).name.replace(/^@[^/]+\//, '');
|
|
36
|
+
|
|
37
|
+
function check(cmd) {
|
|
38
|
+
return spawnSync(cmd[0], cmd.slice(1), { encoding: 'utf8' });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function quiet(cmd) {
|
|
42
|
+
try {
|
|
43
|
+
return execSync(cmd, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
|
|
44
|
+
} catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function run(cmd) {
|
|
50
|
+
if (dryRun) {
|
|
51
|
+
console.log(`[dry-run] ${cmd}`);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
console.log(`$ ${cmd}`);
|
|
55
|
+
try {
|
|
56
|
+
execSync(cmd, { stdio: 'inherit' });
|
|
57
|
+
} catch {
|
|
58
|
+
console.error(
|
|
59
|
+
`\nERROR: \`${cmd}\` failed (see output above). Fix and re-run — the script is idempotent.`,
|
|
60
|
+
);
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
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
|
+
|
|
85
|
+
// --- Preflight: gh installed and authenticated -------------------------------
|
|
86
|
+
if (check(['gh', '--version']).status !== 0) {
|
|
87
|
+
console.error('ERROR: the GitHub CLI (`gh`) is required. Install it from https://cli.github.com');
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
if (check(['gh', 'auth', 'status']).status !== 0) {
|
|
91
|
+
console.error('ERROR: `gh` is not logged in. Run `gh auth login` first.');
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// --- Local git: repo, initial commit, main + dev branches --------------------
|
|
96
|
+
if (quiet('git rev-parse --is-inside-work-tree') === null) {
|
|
97
|
+
run('git init -b main');
|
|
98
|
+
}
|
|
99
|
+
if (quiet('git rev-parse HEAD') === null) {
|
|
100
|
+
run('git add -A');
|
|
101
|
+
run('git commit -m "Initial commit (scaffolded from @getvitops/create)"');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// The workflows key off `main`; normalize whatever the initial branch is.
|
|
105
|
+
const branch = quiet('git branch --show-current');
|
|
106
|
+
if (branch && branch !== 'main' && branch !== 'dev') {
|
|
107
|
+
run(`git branch -m ${branch} main`);
|
|
108
|
+
}
|
|
109
|
+
if (quiet('git show-ref --verify refs/heads/dev') === null) {
|
|
110
|
+
run('git branch dev main');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// --- GitHub: create repo (same name), push, default to dev -------------------
|
|
114
|
+
if (quiet('git remote get-url origin') === null) {
|
|
115
|
+
run(`gh repo create ${repoName} ${visibility} --source=. --remote=origin`);
|
|
116
|
+
} else {
|
|
117
|
+
console.log('origin remote already set — skipping `gh repo create`.');
|
|
118
|
+
}
|
|
119
|
+
run('git push -u origin main dev');
|
|
120
|
+
run('gh repo edit --default-branch dev');
|
|
121
|
+
run('git checkout dev');
|
|
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
|
+
|
|
172
|
+
// --- What's left (manual, needs your Cloudflare account) ---------------------
|
|
173
|
+
console.log(`
|
|
174
|
+
Repo ready: main (protected, production) + dev (integration, default branch).
|
|
175
|
+
NOTE: the first workflow runs will fail until the secrets below exist.
|
|
176
|
+
|
|
177
|
+
Next steps:
|
|
178
|
+
1. Dev resources: wrangler d1 create ${repoName}-dev (note the printed id)
|
|
179
|
+
wrangler r2 bucket create ${repoName}-media-dev
|
|
180
|
+
2. Secrets: gh secret set CLOUDFLARE_API_TOKEN
|
|
181
|
+
gh secret set CLOUDFLARE_ACCOUNT_ID
|
|
182
|
+
gh secret set DEV_D1_ID
|
|
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.
|
|
186
|
+
`);
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// One-time hosting-platform setup for this static site.
|
|
2
|
+
//
|
|
3
|
+
// pnpm run init:hosting
|
|
4
|
+
// pnpm run init:hosting -- --dry-run
|
|
5
|
+
//
|
|
6
|
+
// There is no D1/R2 to provision here — a pure static build has no data
|
|
7
|
+
// layer — so this reduces to reading the Cloudflare account id from
|
|
8
|
+
// `wrangler whoami` and, when a GitHub repo is wired up (see
|
|
9
|
+
// init-github.mjs), setting it as a repo secret. The API token itself still
|
|
10
|
+
// has to be created by hand (the CLI can't mint one).
|
|
11
|
+
//
|
|
12
|
+
// Idempotent: safe to re-run. Requires wrangler auth (`npx wrangler login`).
|
|
13
|
+
|
|
14
|
+
import { execSync, spawnSync } from 'node:child_process';
|
|
15
|
+
|
|
16
|
+
const dryRun = process.argv.includes('--dry-run');
|
|
17
|
+
|
|
18
|
+
function quiet(cmd) {
|
|
19
|
+
try {
|
|
20
|
+
return execSync(cmd, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
|
|
21
|
+
} catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function run(cmd) {
|
|
27
|
+
if (dryRun) {
|
|
28
|
+
console.log(`[dry-run] ${cmd}`);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
console.log(`$ ${cmd}`);
|
|
32
|
+
execSync(cmd, { stdio: 'inherit' });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const whoami = spawnSync('npx', ['wrangler', 'whoami'], { encoding: 'utf8' });
|
|
36
|
+
if (whoami.status !== 0) {
|
|
37
|
+
console.error('ERROR: wrangler is not logged in. Run `npx wrangler login` first.');
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
// "Account Name | <32-hex account id>" table row in `wrangler whoami` output.
|
|
41
|
+
const accountId = (whoami.stdout.match(/\b([0-9a-f]{32})\b/) ?? [])[1] ?? null;
|
|
42
|
+
|
|
43
|
+
const hasGh = spawnSync('gh', ['auth', 'status'], { encoding: 'utf8' }).status === 0;
|
|
44
|
+
const hasOrigin = quiet('git remote get-url origin') !== null;
|
|
45
|
+
if (hasGh && hasOrigin && accountId) {
|
|
46
|
+
run(`gh secret set CLOUDFLARE_ACCOUNT_ID --body ${accountId}`);
|
|
47
|
+
} else {
|
|
48
|
+
console.log(
|
|
49
|
+
'\nSkipping GitHub secret (no gh auth, no origin remote, or no account id — run init:github first).',
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
console.log(`
|
|
54
|
+
Cloudflare account: ${accountId ?? '(not found — check `wrangler whoami`)'}
|
|
55
|
+
|
|
56
|
+
Still manual (CLI can't create API tokens):
|
|
57
|
+
1. Create a token with Workers edit permission:
|
|
58
|
+
https://dash.cloudflare.com/profile/api-tokens
|
|
59
|
+
2. gh secret set CLOUDFLARE_API_TOKEN
|
|
60
|
+
|
|
61
|
+
Prod/dev D1/R2 don't apply here — this is a pure static site.
|
|
62
|
+
`);
|
|
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>
|
|
Binary file
|