@getvitops/create 0.8.2 → 0.9.0-alpha.1
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 +103 -0
- package/package.json +2 -2
- package/templates/emdash/.agents/skills/custom-blocks/SKILL.md +104 -0
- package/templates/emdash/.agents/skills/setup/SKILL.md +4 -4
- package/templates/emdash/.claude/hooks/session-start.sh +4 -1
- package/templates/emdash/.github/workflows/deploy-prod.yml +12 -0
- package/templates/emdash/AGENTS.md +228 -28
- package/templates/emdash/README.md +48 -37
- package/templates/emdash/SETUP.html +45 -27
- package/templates/emdash/_gitignore +3 -0
- package/templates/emdash/astro.config.mjs +14 -1
- package/templates/emdash/package.json +7 -5
- package/templates/emdash/public/_headers +19 -4
- package/templates/emdash/scripts/pull-content.mjs +83 -0
- package/templates/emdash/seed/seed.json +188 -6
- package/templates/emdash/site.json +102 -3
- package/templates/emdash/src/cloudflare-workers.d.ts +9 -0
- package/templates/emdash/src/layouts/Layout.astro +27 -45
- package/templates/emdash/src/pages/404.astro +4 -3
- package/templates/emdash/src/pages/[...slug].astro +22 -12
- package/templates/emdash/src/pages/api/track.ts +158 -0
- package/templates/emdash/src/pages/index.astro +10 -9
- package/templates/static/.agents/skills/setup/SKILL.md +4 -4
- package/templates/static/AGENTS.md +68 -14
- package/templates/static/README.md +7 -7
- package/templates/static/SETUP.html +45 -27
- package/templates/static/public/_headers +16 -4
- package/templates/static/site.json +54 -1
|
@@ -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 `
|
|
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 `
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
319
|
-
only plans and checks; it never applies the cutover itself.
|
|
318
|
+
Preview it: <code>vp exec vitops setup domain --domain <apex> --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>
|
|
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>
|
|
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>
|
|
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>
|
|
402
|
-
<code>
|
|
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>
|
|
414
|
-
<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 > Turnstile > 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>
|
|
457
|
-
or <code>
|
|
458
|
-
<code>/terms</code> and <code>/privacy</code> pages are deliberately
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
@@ -12,6 +12,9 @@ worker-configuration.d.ts
|
|
|
12
12
|
# wrangler local state (local D1/R2 data lives here during `astro dev`)
|
|
13
13
|
.wrangler/
|
|
14
14
|
|
|
15
|
+
# production content dump `scripts/pull-content.mjs` loads into the local build DB
|
|
16
|
+
.vitops/content.sql
|
|
17
|
+
|
|
15
18
|
# node hosting target: SQLite db + local uploads
|
|
16
19
|
data/
|
|
17
20
|
|
|
@@ -23,6 +23,15 @@ const organizationName = resolveOrganizationName(config) ?? 'My EmDash Site';
|
|
|
23
23
|
// data migration, not a rewrite — see README "Hosting".
|
|
24
24
|
const { adapter, database, storage } = await vitopsHosting();
|
|
25
25
|
|
|
26
|
+
// Favicon/PWA colours come from site.json rather than a second copy here that
|
|
27
|
+
// drifts when the palette changes: the browser-UI colour is the `brand-primary`
|
|
28
|
+
// role's hue seed. (The page background behind the maskable icon is a computed
|
|
29
|
+
// ramp step, not a config value, so `backgroundColor` below stays a literal.)
|
|
30
|
+
const colors = config.designSystem?.themes?.default?.colors ?? {};
|
|
31
|
+
const roleHue = (role) =>
|
|
32
|
+
typeof colors.roles?.[role] === 'string' ? colors.roles[role] : colors.roles?.[role]?.hue;
|
|
33
|
+
const brandColor = colors.palette?.[roleHue('brand-primary')]?.seed ?? '#2e9b73';
|
|
34
|
+
|
|
26
35
|
export default defineConfig({
|
|
27
36
|
output: 'server',
|
|
28
37
|
adapter,
|
|
@@ -79,7 +88,7 @@ export default defineConfig({
|
|
|
79
88
|
favicon: {
|
|
80
89
|
source: 'src/assets/logo.svg',
|
|
81
90
|
name: organizationName,
|
|
82
|
-
themeColor:
|
|
91
|
+
themeColor: brandColor,
|
|
83
92
|
backgroundColor: '#ffffff',
|
|
84
93
|
},
|
|
85
94
|
// inject: false — Layout.astro imports the generated stylesheet, so
|
|
@@ -91,6 +100,10 @@ export default defineConfig({
|
|
|
91
100
|
// there instead of a bare design-system.json. legal/fonts/search/ads/
|
|
92
101
|
// reviews all read the same file (see AGENTS.md's "Client onboarding").
|
|
93
102
|
css: { format: 'tailwind', inject: false, input: 'site.json' },
|
|
103
|
+
// The /design-system admin page embeds public/vitops/design-system-editor.html in an
|
|
104
|
+
// iframe; this writes it, with its manifest, editor.js and editor.css, without loading
|
|
105
|
+
// the editor on public pages.
|
|
106
|
+
editorManifest: true,
|
|
94
107
|
site: { input: 'site.json' },
|
|
95
108
|
}),
|
|
96
109
|
],
|
|
@@ -5,10 +5,12 @@
|
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"dev": "astro dev",
|
|
8
|
-
"build": "astro build",
|
|
8
|
+
"build": "astro build && vitops emdash check-prerender",
|
|
9
9
|
"preview": "astro preview",
|
|
10
|
-
"
|
|
11
|
-
"
|
|
10
|
+
"pull-content": "node scripts/pull-content.mjs",
|
|
11
|
+
"prepare-local-db": "vitops emdash prepare-db",
|
|
12
|
+
"deploy": "vitops email check && node scripts/pull-content.mjs && vitops emdash prepare-db && astro build && vitops emdash check-prerender && emdash migrate --wrangler-config wrangler.jsonc && wrangler deploy",
|
|
13
|
+
"deploy:dev": "vitops email check && node scripts/pull-content.mjs && vitops emdash prepare-db && astro build && vitops emdash check-prerender && node scripts/build-dev-wrangler.mjs && emdash migrate --wrangler-config dist/server/wrangler.dev.json && wrangler deploy --config dist/server/wrangler.dev.json",
|
|
12
14
|
"init:github": "vitops setup repo",
|
|
13
15
|
"init:hosting": "vitops setup hosting",
|
|
14
16
|
"setup": "vitops setup all",
|
|
@@ -21,13 +23,13 @@
|
|
|
21
23
|
"dependencies": {
|
|
22
24
|
"@astrojs/cloudflare": "^14.0.0",
|
|
23
25
|
"@astrojs/react": "^7.1.2",
|
|
24
|
-
"@emdash-cms/cloudflare": "
|
|
26
|
+
"@emdash-cms/cloudflare": "latest",
|
|
25
27
|
"@getvitops/astro": "latest",
|
|
26
28
|
"@getvitops/core": "latest",
|
|
27
29
|
"@getvitops/emdash": "latest",
|
|
28
30
|
"@tailwindcss/vite": "^4.3.3",
|
|
29
31
|
"astro": "^7.4.4",
|
|
30
|
-
"emdash": "
|
|
32
|
+
"emdash": "latest",
|
|
31
33
|
"react": "^19.3.1",
|
|
32
34
|
"react-dom": "^19.3.1",
|
|
33
35
|
"tailwindcss": "^4.3.3"
|
|
@@ -1,9 +1,24 @@
|
|
|
1
|
-
# Cloudflare
|
|
1
|
+
# Cloudflare static-assets response headers. They apply to files served from the
|
|
2
|
+
# static asset store — the prerendered pages — and NOT to responses the Worker
|
|
3
|
+
# renders itself (the EmDash admin at /_emdash/*, its API, anything left
|
|
4
|
+
# on-demand). See AGENTS.md "Content Security Policy".
|
|
2
5
|
#
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
+
# REPORT-ONLY on purpose: violations are logged to the browser console and
|
|
7
|
+
# nothing is blocked, so this cannot break a page. Read the console on the
|
|
8
|
+
# pages you ship, add what is legitimately needed, then rename the header to
|
|
9
|
+
# `Content-Security-Policy` to enforce it.
|
|
10
|
+
#
|
|
11
|
+
# The two sha256 values are the inline scripts <Head /> emits (the colour-scheme
|
|
12
|
+
# bootstrap and the consent-gate stub). They change only when
|
|
13
|
+
# @getvitops/astro changes those scripts; regenerate with
|
|
14
|
+
# vp exec node -e "import('@getvitops/astro').then(m => console.log(m.contentSecurityPolicy()))"
|
|
15
|
+
# `templates.test.ts` in the toolchain repo guards that they match.
|
|
16
|
+
#
|
|
17
|
+
# Expect reports for scripts this policy cannot name by hash: EmDash's own head
|
|
18
|
+
# tags, a server-island loader, an editor's HTML block, and third-party tags the
|
|
19
|
+
# consent gate activates (allow their origins in script-src/connect-src).
|
|
6
20
|
/*
|
|
21
|
+
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
22
|
X-Content-Type-Options: nosniff
|
|
8
23
|
Referrer-Policy: strict-origin-when-cross-origin
|
|
9
24
|
Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=()
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// Pre-build step: load production's EmDash content into the LOCAL D1 the build
|
|
2
|
+
// prerenders from.
|
|
3
|
+
//
|
|
4
|
+
// Why this exists: `src/pages/index.astro` and `[...slug].astro` are prerendered
|
|
5
|
+
// (`prerender = true` + `getStaticPaths`), so their HTML is written during
|
|
6
|
+
// `astro build`. The Cloudflare adapter prerenders inside a local workerd with a
|
|
7
|
+
// LOCAL D1 (`.wrangler/state`), never the deployed database — so on a CI runner
|
|
8
|
+
// the build would see an empty, auto-seeded database and bake "No home page yet"
|
|
9
|
+
// into every page. Production is the truth for content (see AGENTS.md), so this
|
|
10
|
+
// copies production's D1 into the local one first.
|
|
11
|
+
//
|
|
12
|
+
// wrangler d1 export <db> --remote → .vitops/content.sql
|
|
13
|
+
// wrangler d1 execute <db> --local ← .vitops/content.sql (state cleared first)
|
|
14
|
+
//
|
|
15
|
+
// Read-only against production (an export). It only ever writes the local
|
|
16
|
+
// `.wrangler/state/v3/d1` directory, which is disposable build state.
|
|
17
|
+
//
|
|
18
|
+
// Fails CLOSED. A deploy that proceeds after a failed pull would publish an empty
|
|
19
|
+
// site over a good one, so any failure aborts the deploy — except one case that is
|
|
20
|
+
// not a failure: the remote database does not exist yet (the first deploy, where
|
|
21
|
+
// wrangler provisions it). Building from the seed is only right once the local database is
|
|
22
|
+
// migrated and seeded, which is `scripts/prepare-local-db.mjs`'s job, run straight after this.
|
|
23
|
+
//
|
|
24
|
+
// Skip with SKIP_PULL_CONTENT=1 (e.g. a deliberate seed-only build).
|
|
25
|
+
|
|
26
|
+
import { execFileSync, spawnSync } from 'node:child_process';
|
|
27
|
+
import { mkdirSync, readFileSync, rmSync } from 'node:fs';
|
|
28
|
+
|
|
29
|
+
if (process.env.SKIP_PULL_CONTENT) {
|
|
30
|
+
console.log('pull-content: SKIP_PULL_CONTENT set — prerendering from the local/seed database.');
|
|
31
|
+
process.exit(0);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** wrangler.jsonc allows comments and trailing commas; JSON.parse does not. */
|
|
35
|
+
function parseJsonc(text) {
|
|
36
|
+
const out = text
|
|
37
|
+
.replace(/("(?:[^"\\]|\\.)*")|\/\*[\s\S]*?\*\/|\/\/[^\n]*/g, (m, str) => str ?? '')
|
|
38
|
+
.replace(/,(\s*[}\]])/g, '$1');
|
|
39
|
+
return JSON.parse(out);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const config = parseJsonc(readFileSync('wrangler.jsonc', 'utf8'));
|
|
43
|
+
const db = config.d1_databases?.[0]?.database_name;
|
|
44
|
+
if (!db) {
|
|
45
|
+
console.error('pull-content: wrangler.jsonc has no d1_databases[0].database_name.');
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const dump = '.vitops/content.sql';
|
|
50
|
+
mkdirSync('.vitops', { recursive: true });
|
|
51
|
+
|
|
52
|
+
const wrangler = (args) =>
|
|
53
|
+
spawnSync('wrangler', args, { encoding: 'utf8', env: { ...process.env, CI: '1' } });
|
|
54
|
+
|
|
55
|
+
const exported = wrangler([
|
|
56
|
+
'd1',
|
|
57
|
+
'export',
|
|
58
|
+
db,
|
|
59
|
+
'--remote',
|
|
60
|
+
'--output',
|
|
61
|
+
dump,
|
|
62
|
+
'--skip-confirmation',
|
|
63
|
+
]);
|
|
64
|
+
if (exported.status !== 0) {
|
|
65
|
+
const text = `${exported.stdout}\n${exported.stderr}`;
|
|
66
|
+
if (/not found|does not exist|no such database|couldn'?t find|could not find|7404/i.test(text)) {
|
|
67
|
+
console.warn(
|
|
68
|
+
`pull-content: remote database "${db}" does not exist yet (first deploy?) — ` +
|
|
69
|
+
'prerendering from the seed.',
|
|
70
|
+
);
|
|
71
|
+
process.exit(0);
|
|
72
|
+
}
|
|
73
|
+
console.error(`pull-content: could not export "${db}" from production:\n${text}`);
|
|
74
|
+
console.error('Refusing to build — prerendering now would publish an empty site.');
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Replaying onto a populated local database collides on CREATE TABLE, so start clean.
|
|
79
|
+
rmSync('.wrangler/state/v3/d1', { recursive: true, force: true });
|
|
80
|
+
execFileSync('wrangler', ['d1', 'execute', db, '--local', '--file', dump, '--yes'], {
|
|
81
|
+
stdio: 'inherit',
|
|
82
|
+
});
|
|
83
|
+
console.log(`pull-content: loaded production "${db}" into the local build database.`);
|
|
@@ -35,6 +35,17 @@
|
|
|
35
35
|
{
|
|
36
36
|
"name": "primary",
|
|
37
37
|
"label": "Primary Navigation",
|
|
38
|
+
"items": [
|
|
39
|
+
{
|
|
40
|
+
"type": "custom",
|
|
41
|
+
"label": "Contact",
|
|
42
|
+
"url": "/contact"
|
|
43
|
+
}
|
|
44
|
+
]
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"name": "footer-sitemap",
|
|
48
|
+
"label": "Footer: Site Map",
|
|
38
49
|
"items": [
|
|
39
50
|
{
|
|
40
51
|
"type": "custom",
|
|
@@ -43,15 +54,20 @@
|
|
|
43
54
|
},
|
|
44
55
|
{
|
|
45
56
|
"type": "custom",
|
|
46
|
-
"label": "
|
|
47
|
-
"url": "/
|
|
57
|
+
"label": "Contact",
|
|
58
|
+
"url": "/contact"
|
|
48
59
|
}
|
|
49
60
|
]
|
|
50
61
|
},
|
|
51
62
|
{
|
|
52
|
-
"name": "footer",
|
|
53
|
-
"label": "Footer",
|
|
63
|
+
"name": "footer-legal",
|
|
64
|
+
"label": "Footer: Legal",
|
|
54
65
|
"items": [
|
|
66
|
+
{
|
|
67
|
+
"type": "custom",
|
|
68
|
+
"label": "Privacy Policy",
|
|
69
|
+
"url": "/privacy"
|
|
70
|
+
},
|
|
55
71
|
{
|
|
56
72
|
"type": "custom",
|
|
57
73
|
"label": "Terms & Conditions",
|
|
@@ -59,8 +75,50 @@
|
|
|
59
75
|
},
|
|
60
76
|
{
|
|
61
77
|
"type": "custom",
|
|
62
|
-
"label": "
|
|
63
|
-
"url": "/
|
|
78
|
+
"label": "Cookie Notice",
|
|
79
|
+
"url": "/cookies"
|
|
80
|
+
}
|
|
81
|
+
]
|
|
82
|
+
}
|
|
83
|
+
],
|
|
84
|
+
"widgetAreas": [
|
|
85
|
+
{
|
|
86
|
+
"name": "announcement",
|
|
87
|
+
"label": "Announcement bar",
|
|
88
|
+
"description": "Shown above the header on every page. Empty until you add a widget.",
|
|
89
|
+
"widgets": []
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"name": "pre-footer",
|
|
93
|
+
"label": "Above the footer",
|
|
94
|
+
"description": "A call to action shown above the footer on every page (Widgets in the admin).",
|
|
95
|
+
"widgets": [
|
|
96
|
+
{
|
|
97
|
+
"type": "content",
|
|
98
|
+
"title": "Ready to talk?",
|
|
99
|
+
"content": [
|
|
100
|
+
{
|
|
101
|
+
"_type": "block",
|
|
102
|
+
"_key": "pf-text",
|
|
103
|
+
"style": "normal",
|
|
104
|
+
"markDefs": [],
|
|
105
|
+
"children": [
|
|
106
|
+
{
|
|
107
|
+
"_type": "span",
|
|
108
|
+
"_key": "pf-text-s",
|
|
109
|
+
"text": "Tell us what you need and we will reply within one business day.",
|
|
110
|
+
"marks": []
|
|
111
|
+
}
|
|
112
|
+
]
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"_type": "vitops.actionLink",
|
|
116
|
+
"_key": "pf-cta",
|
|
117
|
+
"label": "Contact us",
|
|
118
|
+
"href": "/contact",
|
|
119
|
+
"variant": "cta"
|
|
120
|
+
}
|
|
121
|
+
]
|
|
64
122
|
}
|
|
65
123
|
]
|
|
66
124
|
}
|
|
@@ -436,6 +494,130 @@
|
|
|
436
494
|
}
|
|
437
495
|
]
|
|
438
496
|
}
|
|
497
|
+
},
|
|
498
|
+
{
|
|
499
|
+
"id": "contact",
|
|
500
|
+
"slug": "contact",
|
|
501
|
+
"status": "published",
|
|
502
|
+
"data": {
|
|
503
|
+
"title": "Contact",
|
|
504
|
+
"content": [
|
|
505
|
+
{
|
|
506
|
+
"_type": "vitops.banner",
|
|
507
|
+
"_key": "contact-placeholder",
|
|
508
|
+
"message": "Placeholder text — replace with your contact details before launch.",
|
|
509
|
+
"tone": "warning",
|
|
510
|
+
"dismissible": false
|
|
511
|
+
},
|
|
512
|
+
{
|
|
513
|
+
"_type": "block",
|
|
514
|
+
"_key": "contact-body",
|
|
515
|
+
"style": "normal",
|
|
516
|
+
"markDefs": [],
|
|
517
|
+
"children": [
|
|
518
|
+
{
|
|
519
|
+
"_type": "span",
|
|
520
|
+
"_key": "contact-body-1",
|
|
521
|
+
"text": "Tell visitors how to reach you: an email address, a phone number, a street address or a form. This page exists so the Contact link in the header and footer resolves; edit it in the EmDash admin.",
|
|
522
|
+
"marks": []
|
|
523
|
+
}
|
|
524
|
+
]
|
|
525
|
+
},
|
|
526
|
+
{
|
|
527
|
+
"_type": "vitops.form",
|
|
528
|
+
"_key": "contact-form",
|
|
529
|
+
"heading": "Send us a message",
|
|
530
|
+
"level": "2",
|
|
531
|
+
"fields": [
|
|
532
|
+
{
|
|
533
|
+
"label": "Your name",
|
|
534
|
+
"name": "name",
|
|
535
|
+
"type": "text",
|
|
536
|
+
"required": true
|
|
537
|
+
},
|
|
538
|
+
{
|
|
539
|
+
"label": "Email",
|
|
540
|
+
"name": "email",
|
|
541
|
+
"type": "email",
|
|
542
|
+
"required": true
|
|
543
|
+
},
|
|
544
|
+
{
|
|
545
|
+
"label": "Phone",
|
|
546
|
+
"name": "phone",
|
|
547
|
+
"type": "tel",
|
|
548
|
+
"required": false
|
|
549
|
+
},
|
|
550
|
+
{
|
|
551
|
+
"label": "Message",
|
|
552
|
+
"name": "message",
|
|
553
|
+
"type": "textarea",
|
|
554
|
+
"required": true
|
|
555
|
+
}
|
|
556
|
+
],
|
|
557
|
+
"submitLabel": "Send message",
|
|
558
|
+
"successMessage": "Thanks. Your message has been sent and we will reply soon."
|
|
559
|
+
}
|
|
560
|
+
]
|
|
561
|
+
}
|
|
562
|
+
},
|
|
563
|
+
{
|
|
564
|
+
"id": "cookies",
|
|
565
|
+
"slug": "cookies",
|
|
566
|
+
"status": "published",
|
|
567
|
+
"data": {
|
|
568
|
+
"title": "Cookie Notice",
|
|
569
|
+
"content": [
|
|
570
|
+
{
|
|
571
|
+
"_type": "vitops.banner",
|
|
572
|
+
"_key": "cookies-placeholder",
|
|
573
|
+
"message": "Placeholder text — replace with your own cookie notice before launch. This is not legal advice. `vitops legal` can generate a draft from site.json.",
|
|
574
|
+
"tone": "warning",
|
|
575
|
+
"dismissible": false
|
|
576
|
+
},
|
|
577
|
+
{
|
|
578
|
+
"_type": "block",
|
|
579
|
+
"_key": "cookies-intro",
|
|
580
|
+
"style": "normal",
|
|
581
|
+
"markDefs": [],
|
|
582
|
+
"children": [
|
|
583
|
+
{
|
|
584
|
+
"_type": "span",
|
|
585
|
+
"_key": "cookies-intro-1",
|
|
586
|
+
"text": "This site uses a small number of cookies. Essential cookies keep the site working; others are only set if you choose to allow them.",
|
|
587
|
+
"marks": []
|
|
588
|
+
}
|
|
589
|
+
]
|
|
590
|
+
},
|
|
591
|
+
{
|
|
592
|
+
"_type": "block",
|
|
593
|
+
"_key": "cookies-h",
|
|
594
|
+
"style": "h2",
|
|
595
|
+
"markDefs": [],
|
|
596
|
+
"children": [
|
|
597
|
+
{
|
|
598
|
+
"_type": "span",
|
|
599
|
+
"_key": "cookies-h-1",
|
|
600
|
+
"text": "What cookies we use",
|
|
601
|
+
"marks": []
|
|
602
|
+
}
|
|
603
|
+
]
|
|
604
|
+
},
|
|
605
|
+
{
|
|
606
|
+
"_type": "block",
|
|
607
|
+
"_key": "cookies-body",
|
|
608
|
+
"style": "normal",
|
|
609
|
+
"markDefs": [],
|
|
610
|
+
"children": [
|
|
611
|
+
{
|
|
612
|
+
"_type": "span",
|
|
613
|
+
"_key": "cookies-body-1",
|
|
614
|
+
"text": "List each cookie, who sets it and why. Replace this paragraph with the real list before launch.",
|
|
615
|
+
"marks": []
|
|
616
|
+
}
|
|
617
|
+
]
|
|
618
|
+
}
|
|
619
|
+
]
|
|
620
|
+
}
|
|
439
621
|
}
|
|
440
622
|
]
|
|
441
623
|
}
|