create-website-build-kit 0.1.14 → 0.1.16

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/index.mjs CHANGED
@@ -108,9 +108,41 @@ if (!existsSync(join(dest, '.gitignore'))) {
108
108
  const pkgPath = join(dest, 'package.json');
109
109
  const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
110
110
  pkg.name = name.replace(/[^a-z0-9-]+/gi, '-').replace(/^-+|-+$/g, '').toLowerCase() || 'site';
111
- writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`);
112
111
 
113
- console.log(` ${GREEN}✓${RESET} template → ${dir}/`);
112
+ /*
113
+ * ── WHICH KIT THIS SITE CAME FROM ──────────────────────────────────────────
114
+ * ⚠ THE TEMPLATE IS COPIED, NOT LINKED. Nothing the kit fixes afterwards ever
115
+ * reaches a site already built — not a trap, not a gate, not a pipeline
116
+ * change. A shipped site sat 19% behind on every image for weeks after AVIF
117
+ * landed, and it surfaced only because somebody happened to read both trees
118
+ * for an unrelated reason.
119
+ *
120
+ * Without a stamp, "is this site current?" is archaeology: compare files by
121
+ * eye against a repo whose history you have to guess at. With one it is
122
+ * reading a line.
123
+ *
124
+ * The VERSION, not the commit: every release is tagged, so this resolves to a
125
+ * commit in one lookup, and embedding a commit would mean the packer's git
126
+ * state deciding what ships. It goes in package.json because that is the file
127
+ * a developer opens first and nobody deletes.
128
+ */
129
+ const kitPkg = JSON.parse(
130
+ readFileSync(join(dirname(fileURLToPath(import.meta.url)), 'package.json'), 'utf8'),
131
+ );
132
+ const stamp = { version: kitPkg.version, scaffolded: new Date().toISOString().slice(0, 10) };
133
+
134
+ /* Placed straight after `version` rather than appended, so it is visible
135
+ without scrolling past the dependency list. */
136
+ const ordered = {};
137
+ for (const [key, value] of Object.entries(pkg)) {
138
+ ordered[key] = value;
139
+ if (key === 'version') ordered.websiteBuildKit = stamp;
140
+ }
141
+ if (!ordered.websiteBuildKit) ordered.websiteBuildKit = stamp;
142
+
143
+ writeFileSync(pkgPath, `${JSON.stringify(ordered, null, 2)}\n`);
144
+
145
+ console.log(` ${GREEN}✓${RESET} template → ${dir}/${DIM} (kit ${stamp.version})${RESET}`);
114
146
 
115
147
  const run = (cmd, args, label) => {
116
148
  const r = spawnSync(cmd, args, { cwd: dest, stdio: 'ignore', shell: process.platform === 'win32' });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-website-build-kit",
3
- "version": "0.1.14",
3
+ "version": "0.1.16",
4
4
  "description": "Scaffold a production marketing site \u2014 Astro on Cloudflare Workers, with the gates, the migration playbook and the accessibility work already wired.",
5
5
  "keywords": [
6
6
  "astro",
@@ -95,6 +95,35 @@ The rules whose failure looks like success — double-counted pageviews, a conve
95
95
  two pipes, a trigger on `sent=1` that catches almost nothing — are in `docs/analytics.md`.
96
96
  Read it before adding any tag.
97
97
 
98
+ **`package.json` records which kit this site came from.** `websiteBuildKit.version` is stamped at
99
+ scaffold time and is never updated afterwards, because ⚠ **the template is copied, not linked** —
100
+ nothing the kit fixes later reaches this site. A trap closed upstream, a gate added, a pipeline
101
+ improved: none of it arrives. One site sat 19% behind on every image for weeks after AVIF landed.
102
+ Compare that version against the kit's releases when something here looks older than it should.
103
+
104
+ **`npm run check:drift` says what this site is behind on.** The kit is copied, not linked, so
105
+ nothing fixed upstream arrives here. It reports and changes nothing.
106
+
107
+ **Text over a photograph is measured, not forbidden.** Declare the region in
108
+ `src/data/contrast.json` — image, box, scrim strength, text colour — and `npm run check:contrast`
109
+ composites it and fails production below 4.5:1. ⚠ **The danger is never the photograph, it is a
110
+ weakened scrim.** On a real site two of three regions could not fail at any photograph; the one
111
+ exposure was a scrim lightened from 92% to 62% so a client's photography could show its colour.
112
+ This check is what makes weakening one safe.
113
+
114
+ ⚠ **A CMS DELETES EVERY KEY ITS SCHEMA FORGOT.** It rewrites the whole file from the schema, so
115
+ anything undeclared is absent from what it writes back — the client changes one field, saves, and
116
+ the rest is gone, looking like an ordinary content commit. `npm run check:cms` refuses a
117
+ `.pages.yml` that does not declare every key in the files it edits. **Declare keys the client will
118
+ never touch**, or move them out of a CMS-managed file.
119
+
120
+ ⚠ **UPLOADS GO TO `media/source/`, NEVER `public/img/`.** The direction is the whole bug:
121
+ `optimize-media.mjs` **reads** `media/source/` and **writes** `public/img/`. A CMS media source
122
+ pointed at the output produces files with no variants, no width/height and no manifest entry, so
123
+ `<Img>` throws and the client's own edit turns the build red. `<Img>` accepts a picker path like
124
+ `/img/photos/hero-1200.webp` and normalises it back to the key, so an image field can be a real
125
+ picker instead of asking the client to type a manifest key from memory.
126
+
98
127
  ⚠ **THE HONEYPOT IS CALLED `company`.** `api/contact.ts` discards any submission that fills it in,
99
128
  silently and with a 200, so a bot learns nothing. Add a real "Company" field to that form — an
100
129
  ordinary client request — and every enquiry from a company that types its name is thrown away, with
@@ -131,6 +131,36 @@ how.
131
131
  once — the header, the footer, every button, the notification emails, and what Google reads.
132
132
  Ask for them to be changed in that one file rather than page by page, or they will drift.
133
133
 
134
+ ### Editing it yourself
135
+
136
+ ⚠ *Delete this whole subsection if the site has no content editor.*
137
+
138
+ You sign in at ⚠ **editor URL** with your ⚠ **GitHub / provider** account. Ask ⚠ **name** to add
139
+ anyone else who needs access.
140
+
141
+ | Section | What it changes |
142
+ | --- | --- |
143
+ | ⚠ Site settings | ⚠ business name, contact details, the main button |
144
+ | ⚠ Pages | ⚠ the words on Home, About, Contact |
145
+ | ⚠ Services | ⚠ each service, its photograph and description |
146
+ | ⚠ Blog | ⚠ posts |
147
+
148
+ **A change is live a few minutes after you save.** ⚠ *Confirm the real timing before sending
149
+ this.* To check it worked, open the page in a private window — an ordinary window may show you
150
+ the version you had before.
151
+
152
+ **Photographs.** Upload them in the ⚠ **Photographs** area, then choose one in the field on the
153
+ page you are editing. Always fill in the short description beside the image: it is what a
154
+ blind visitor hears and what Google reads. Describe what is in the picture, not the file name.
155
+
156
+ **You cannot break the website from here.** If a change is wrong, the version before it stays
157
+ live until the new one is ready — so a mistake means your change does not appear, not that the
158
+ site goes down. Tell ⚠ **name** what you changed and it can be put back.
159
+
160
+ **Things to leave alone.** ⚠ *List anything the editor can reach that it should not — otherwise
161
+ delete this line.* If a screen asks for something that looks technical rather than something a
162
+ customer would read, stop and ask.
163
+
134
164
  ---
135
165
 
136
166
  ## 7. Accessibility
@@ -21,7 +21,8 @@
21
21
  "sharp": "^0.35.3",
22
22
  "turndown": "^7.2.4",
23
23
  "typescript": "^6.0.3",
24
- "wrangler": "^4.118.0"
24
+ "wrangler": "^4.118.0",
25
+ "yaml": "^2.9.0"
25
26
  },
26
27
  "engines": {
27
28
  "node": ">=22.12.0"
@@ -1337,7 +1338,6 @@
1337
1338
  "cpu": [
1338
1339
  "arm64"
1339
1340
  ],
1340
- "dev": true,
1341
1341
  "license": "Apache-2.0",
1342
1342
  "optional": true,
1343
1343
  "os": [
@@ -1360,7 +1360,6 @@
1360
1360
  "cpu": [
1361
1361
  "x64"
1362
1362
  ],
1363
- "dev": true,
1364
1363
  "license": "Apache-2.0",
1365
1364
  "optional": true,
1366
1365
  "os": [
@@ -1380,7 +1379,6 @@
1380
1379
  "version": "0.35.3",
1381
1380
  "resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.3.tgz",
1382
1381
  "integrity": "sha512-lUxcqWIj2wMQ9BrwNjngcr1gWUr5xgaGThBRqPPalIC2n67Cqj1uPh8NnA/ZhAg8hUbKl+kVHKwgUIwe6ZYPrg==",
1383
- "dev": true,
1384
1382
  "license": "Apache-2.0",
1385
1383
  "optional": true,
1386
1384
  "os": [
@@ -1403,7 +1401,6 @@
1403
1401
  "cpu": [
1404
1402
  "arm64"
1405
1403
  ],
1406
- "dev": true,
1407
1404
  "license": "LGPL-3.0-or-later",
1408
1405
  "optional": true,
1409
1406
  "os": [
@@ -1420,7 +1417,6 @@
1420
1417
  "cpu": [
1421
1418
  "x64"
1422
1419
  ],
1423
- "dev": true,
1424
1420
  "license": "LGPL-3.0-or-later",
1425
1421
  "optional": true,
1426
1422
  "os": [
@@ -1437,7 +1433,6 @@
1437
1433
  "cpu": [
1438
1434
  "arm"
1439
1435
  ],
1440
- "dev": true,
1441
1436
  "license": "LGPL-3.0-or-later",
1442
1437
  "optional": true,
1443
1438
  "os": [
@@ -1454,7 +1449,6 @@
1454
1449
  "cpu": [
1455
1450
  "arm64"
1456
1451
  ],
1457
- "dev": true,
1458
1452
  "license": "LGPL-3.0-or-later",
1459
1453
  "optional": true,
1460
1454
  "os": [
@@ -1471,7 +1465,6 @@
1471
1465
  "cpu": [
1472
1466
  "ppc64"
1473
1467
  ],
1474
- "dev": true,
1475
1468
  "license": "LGPL-3.0-or-later",
1476
1469
  "optional": true,
1477
1470
  "os": [
@@ -1488,7 +1481,6 @@
1488
1481
  "cpu": [
1489
1482
  "riscv64"
1490
1483
  ],
1491
- "dev": true,
1492
1484
  "license": "LGPL-3.0-or-later",
1493
1485
  "optional": true,
1494
1486
  "os": [
@@ -1505,7 +1497,6 @@
1505
1497
  "cpu": [
1506
1498
  "s390x"
1507
1499
  ],
1508
- "dev": true,
1509
1500
  "license": "LGPL-3.0-or-later",
1510
1501
  "optional": true,
1511
1502
  "os": [
@@ -1522,7 +1513,6 @@
1522
1513
  "cpu": [
1523
1514
  "x64"
1524
1515
  ],
1525
- "dev": true,
1526
1516
  "license": "LGPL-3.0-or-later",
1527
1517
  "optional": true,
1528
1518
  "os": [
@@ -1539,7 +1529,6 @@
1539
1529
  "cpu": [
1540
1530
  "arm64"
1541
1531
  ],
1542
- "dev": true,
1543
1532
  "license": "LGPL-3.0-or-later",
1544
1533
  "optional": true,
1545
1534
  "os": [
@@ -1556,7 +1545,6 @@
1556
1545
  "cpu": [
1557
1546
  "x64"
1558
1547
  ],
1559
- "dev": true,
1560
1548
  "license": "LGPL-3.0-or-later",
1561
1549
  "optional": true,
1562
1550
  "os": [
@@ -1573,7 +1561,6 @@
1573
1561
  "cpu": [
1574
1562
  "arm"
1575
1563
  ],
1576
- "dev": true,
1577
1564
  "license": "Apache-2.0",
1578
1565
  "optional": true,
1579
1566
  "os": [
@@ -1596,7 +1583,6 @@
1596
1583
  "cpu": [
1597
1584
  "arm64"
1598
1585
  ],
1599
- "dev": true,
1600
1586
  "license": "Apache-2.0",
1601
1587
  "optional": true,
1602
1588
  "os": [
@@ -1619,7 +1605,6 @@
1619
1605
  "cpu": [
1620
1606
  "ppc64"
1621
1607
  ],
1622
- "dev": true,
1623
1608
  "license": "Apache-2.0",
1624
1609
  "optional": true,
1625
1610
  "os": [
@@ -1642,7 +1627,6 @@
1642
1627
  "cpu": [
1643
1628
  "riscv64"
1644
1629
  ],
1645
- "dev": true,
1646
1630
  "license": "Apache-2.0",
1647
1631
  "optional": true,
1648
1632
  "os": [
@@ -1665,7 +1649,6 @@
1665
1649
  "cpu": [
1666
1650
  "s390x"
1667
1651
  ],
1668
- "dev": true,
1669
1652
  "license": "Apache-2.0",
1670
1653
  "optional": true,
1671
1654
  "os": [
@@ -1688,7 +1671,6 @@
1688
1671
  "cpu": [
1689
1672
  "x64"
1690
1673
  ],
1691
- "dev": true,
1692
1674
  "license": "Apache-2.0",
1693
1675
  "optional": true,
1694
1676
  "os": [
@@ -1711,7 +1693,6 @@
1711
1693
  "cpu": [
1712
1694
  "arm64"
1713
1695
  ],
1714
- "dev": true,
1715
1696
  "license": "Apache-2.0",
1716
1697
  "optional": true,
1717
1698
  "os": [
@@ -1734,7 +1715,6 @@
1734
1715
  "cpu": [
1735
1716
  "x64"
1736
1717
  ],
1737
- "dev": true,
1738
1718
  "license": "Apache-2.0",
1739
1719
  "optional": true,
1740
1720
  "os": [
@@ -1754,7 +1734,6 @@
1754
1734
  "version": "0.35.3",
1755
1735
  "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.3.tgz",
1756
1736
  "integrity": "sha512-cZ0XkcYGpHZkqW6iCkqTcmUC0CD9DhD5d/qeZlZkfRBn6GnHniZXLUo5+9xw8Iv76YE6LQFN9YNBlKREcCG76w==",
1757
- "dev": true,
1758
1737
  "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
1759
1738
  "optional": true,
1760
1739
  "dependencies": {
@@ -1774,7 +1753,6 @@
1774
1753
  "cpu": [
1775
1754
  "wasm32"
1776
1755
  ],
1777
- "dev": true,
1778
1756
  "license": "Apache-2.0",
1779
1757
  "optional": true,
1780
1758
  "dependencies": {
@@ -1794,7 +1772,6 @@
1794
1772
  "cpu": [
1795
1773
  "arm64"
1796
1774
  ],
1797
- "dev": true,
1798
1775
  "license": "Apache-2.0 AND LGPL-3.0-or-later",
1799
1776
  "optional": true,
1800
1777
  "os": [
@@ -1814,7 +1791,6 @@
1814
1791
  "cpu": [
1815
1792
  "ia32"
1816
1793
  ],
1817
- "dev": true,
1818
1794
  "license": "Apache-2.0 AND LGPL-3.0-or-later",
1819
1795
  "optional": true,
1820
1796
  "os": [
@@ -1834,7 +1810,6 @@
1834
1810
  "cpu": [
1835
1811
  "x64"
1836
1812
  ],
1837
- "dev": true,
1838
1813
  "license": "Apache-2.0 AND LGPL-3.0-or-later",
1839
1814
  "optional": true,
1840
1815
  "os": [
@@ -8,35 +8,38 @@
8
8
  "node": ">=22.12.0"
9
9
  },
10
10
  "scripts": {
11
- "dev": "astro dev",
12
- "check": "astro check",
13
11
  "a11y": "node scripts/check-a11y.mjs",
14
12
  "a11y:evidence": "node scripts/a11y-evidence.mjs",
15
- "reflow": "node scripts/check-reflow.mjs",
16
- "console": "node scripts/check-console.mjs",
17
- "shots": "node scripts/shots.mjs",
18
- "tells": "node scripts/tells.mjs",
19
- "media": "node scripts/optimize-media.mjs",
13
+ "build": "astro build",
14
+ "build:production": "node scripts/build.mjs production",
15
+ "build:staging": "node scripts/build.mjs staging",
20
16
  "cards": "node scripts/og-cards.mjs",
21
- "lastmod": "node scripts/lastmod.mjs",
22
- "indexnow": "node scripts/indexnow.mjs",
23
- "verify": "node scripts/verify.mjs",
17
+ "check": "astro check",
18
+ "check:cms": "node scripts/check-cms.mjs",
19
+ "check:contrast": "node scripts/check-contrast.mjs",
20
+ "check:copy": "node scripts/check-copy.mjs",
21
+ "check:drift": "node scripts/check-drift.mjs",
22
+ "check:form": "node scripts/check-form.mjs",
23
+ "check:secrets": "node scripts/check-secrets.mjs",
24
24
  "check:sitemap": "node scripts/check-sitemap.mjs",
25
- "recon": "node scripts/recon.mjs",
26
- "extract": "node scripts/extract.mjs",
25
+ "console": "node scripts/check-console.mjs",
26
+ "deploy:production": "npm run build:production && wrangler deploy && node scripts/check-secrets.mjs",
27
+ "deploy:staging": "npm run build:staging && wrangler deploy && node scripts/check-secrets.mjs",
28
+ "dev": "astro dev",
27
29
  "dns": "node scripts/dns-snapshot.mjs",
28
- "seo": "npx --yes @nurkamol/seo-audit@1",
29
- "redirects": "node scripts/redirects.mjs",
30
+ "extract": "node scripts/extract.mjs",
30
31
  "handover": "node scripts/md-to-pdf.mjs docs/handover.md docs/handover.pdf",
31
- "build": "astro build",
32
- "build:staging": "node scripts/build.mjs staging",
33
- "build:production": "node scripts/build.mjs production",
32
+ "indexnow": "node scripts/indexnow.mjs",
33
+ "lastmod": "node scripts/lastmod.mjs",
34
+ "media": "node scripts/optimize-media.mjs",
34
35
  "preview": "wrangler dev",
35
- "deploy:staging": "npm run build:staging && wrangler deploy && node scripts/check-secrets.mjs",
36
- "deploy:production": "npm run build:production && wrangler deploy && node scripts/check-secrets.mjs",
37
- "check:secrets": "node scripts/check-secrets.mjs",
38
- "check:copy": "node scripts/check-copy.mjs",
39
- "check:form": "node scripts/check-form.mjs"
36
+ "recon": "node scripts/recon.mjs",
37
+ "redirects": "node scripts/redirects.mjs",
38
+ "reflow": "node scripts/check-reflow.mjs",
39
+ "seo": "npx --yes @nurkamol/seo-audit@1",
40
+ "shots": "node scripts/shots.mjs",
41
+ "tells": "node scripts/tells.mjs",
42
+ "verify": "node scripts/verify.mjs"
40
43
  },
41
44
  "dependencies": {
42
45
  "@astrojs/cloudflare": "^14.1.7",
@@ -51,6 +54,7 @@
51
54
  "sharp": "^0.35.3",
52
55
  "turndown": "^7.2.4",
53
56
  "typescript": "^6.0.3",
54
- "wrangler": "^4.118.0"
57
+ "wrangler": "^4.118.0",
58
+ "yaml": "^2.9.0"
55
59
  }
56
60
  }
@@ -101,6 +101,12 @@ if (env === 'production') {
101
101
  environments — nobody ever meant two controls to share a name. */
102
102
  step(process.execPath, ['scripts/check-form.mjs']);
103
103
 
104
+ /* Same reasoning, and the same timing: a CMS config that does not declare every
105
+ key in the file it edits will delete content the first time the client saves.
106
+ That is a source bug, it is invisible in a build, and it costs nothing to
107
+ check. A no-op on projects with no `.pages.yml`. */
108
+ step(process.execPath, ['scripts/check-cms.mjs']);
109
+
104
110
  run(['build']);
105
111
 
106
112
  if (env === 'staging') {
@@ -116,6 +122,10 @@ step(process.execPath, ['scripts/check-env.mjs']);
116
122
 
117
123
  if (env === 'production') {
118
124
  step(process.execPath, ['scripts/check-sitemap.mjs']);
125
+ /* Production only: it measures the GENERATED images, and a staging build is
126
+ often run before `npm run media` has caught up. A no-op until a project
127
+ declares regions — the template has no design and therefore none. */
128
+ step(process.execPath, ['scripts/check-contrast.mjs']);
119
129
  }
120
130
 
121
131
  /* A sanity line, so the log says which environment actually ran rather than