@beechcms/cms 0.8.0 → 0.9.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/README.md ADDED
@@ -0,0 +1 @@
1
+ .github/README.md
package/bin/cli.mjs CHANGED
@@ -5,7 +5,7 @@ import pc from 'picocolors'
5
5
 
6
6
  let [,, command, ...args] = process.argv
7
7
 
8
- if (command && args[0] && ['db', 'seed', 'schema', 'dev', 'generate', 'mailpit'].includes(command)) {
8
+ if (command && args[0] && !args[0].startsWith('-') && ['db', 'seed', 'schema', 'types', 'dev', 'generate', 'mailpit'].includes(command)) {
9
9
  command = `${command}:${args.shift()}`
10
10
  }
11
11
 
@@ -22,6 +22,13 @@ const COMMANDS = {
22
22
  'seed:load': cmdSeedLoad,
23
23
  'seed:create': cmdSeedCreate,
24
24
  'schema:diff': cmdSchemaDiff,
25
+ 'schema:audit': cmdSchemaAudit,
26
+ 'schema:migrate': cmdSchemaMigrate,
27
+ 'schema:export': cmdSchemaExport,
28
+ 'schema:plan': cmdSchemaPlan,
29
+ 'schema:apply': cmdSchemaApply,
30
+ 'types:generate': cmdGenerateTypes,
31
+ 'types:check': cmdTypesCheck,
25
32
  'init': cmdInit,
26
33
  'validate': cmdValidate,
27
34
  'deploy': cmdDeploy,
@@ -72,12 +79,39 @@ function help() {
72
79
  ${pc.cyan('db:reset')} Remove local Wrangler state and re-bootstrap database
73
80
 
74
81
  ${pc.bold('3. Database & Types Management')}
75
- ${pc.cyan('gen types typescript')} (alias: ${pc.cyan('gen-types')})
76
- Generate TypeScript interfaces from active D1 database
77
- --local Target local D1 SQLite state (default)
78
- --remote Target remote Cloudflare D1
82
+ ${pc.cyan('types generate')} (aliases: ${pc.cyan('gen-types')}, ${pc.cyan('gen types typescript')})
83
+ Generate TypeScript interfaces + schema fingerprint from live D1
84
+ --local / --remote Target local SQLite state (default) or remote D1
85
+ --db <name> Override D1 database name
86
+ -o, --output <file> Output path (default: beech.generated.ts; aliases print to stdout)
87
+ ${pc.cyan('types check')} Diff committed generated types against live D1 (writes nothing)
88
+ --local / --remote Target local SQLite state (default) or remote D1
89
+ --db <name> Override D1 database name
90
+ -o, --output <file> File to check (default: beech.generated.ts)
91
+ Exits 1 when the file is missing or stale.
92
+ ${pc.cyan('schema export')} Write beech.schema.ts from live D1 state
93
+ --out <file> Destination (default: beech.schema.ts)
94
+ --stdout Print instead of writing
95
+ --remote Target remote D1
96
+ ${pc.cyan('schema diff')} Compare beech.schema.ts against the deployed schema
97
+ --manifest <f> Manifest path (default: beech.schema.ts)
98
+ Exits 1 when drift is detected.
99
+ ${pc.cyan('schema audit')} Report relation storage and legacy tag definitions that need repair (read-only)
100
+ --remote Target remote D1 instead of local (default: local)
101
+ --db <name> Override D1 database name
102
+ ${pc.cyan('schema migrate')} Migrate pre-0.9 tag/choice seeds to the closed-vocabulary tags contract
103
+ --apply Write the changes (default: dry run, exits 1 while work is pending)
104
+ --remote Target remote D1 instead of local (default: local)
79
105
  --db <name> Override D1 database name
80
- -o, --output Output file path (default: standard output)
106
+ ${pc.cyan('schema plan')} Show what applying beech.schema.ts would change (writes nothing)
107
+ --manifest <f> Manifest path (default: beech.schema.ts)
108
+ --api-url <url> API origin (default: BEECH_API_URL or http://localhost:8789)
109
+ Exits 1 when a seed cannot be applied.
110
+ ${pc.cyan('schema apply')} Apply beech.schema.ts through the control plane
111
+ --manifest <f> Manifest path (default: beech.schema.ts)
112
+ --api-url <url> API origin
113
+ -y, --yes Skip the confirmation prompt
114
+ Additive only. Authorizes in the browser on first use.
81
115
  ${pc.cyan('validate')} Validate runtime schema status
82
116
 
83
117
  ${pc.bold('4. Forms & Frontend Generation')}
@@ -102,6 +136,8 @@ function help() {
102
136
  ${pc.cyan('test')} Run the test suite via Turborepo / Vitest
103
137
  --coverage Generate coverage reports
104
138
  --diff Run test coverage only for files modified on the branch
139
+ --tier <list> Run one or more tiers: unit, flow, integration, e2e (comma-separated)
140
+ e2e is Playwright-driven and is never selected by --diff
105
141
  ${pc.cyan('lint')} Run ESLint quality checks
106
142
  ${pc.cyan('setup:cloudflare')} (alias: ${pc.cyan('setup:cf')})
107
143
  Interactive 1-step Cloudflare provisioning (D1, R2, Presigned S3 secrets)
@@ -131,17 +167,17 @@ async function cmdInit(args) {
131
167
  await init({ initDb, local: !remote, db, nonInteractive: yes })
132
168
  }
133
169
 
134
- async function cmdSeedLoad(_args) {
170
+ async function cmdSeedLoad() {
135
171
  const { seedLoad } = await import('@beechcms/cli')
136
172
  await seedLoad({})
137
173
  }
138
174
 
139
- async function cmdValidate(_args) {
175
+ async function cmdValidate() {
140
176
  const { validate } = await import('@beechcms/cli')
141
177
  await validate({})
142
178
  }
143
179
 
144
- async function cmdSeedCreate(_args) {
180
+ async function cmdSeedCreate() {
145
181
  const { seedCreate } = await import('@beechcms/cli')
146
182
  await seedCreate({})
147
183
  }
@@ -153,7 +189,7 @@ async function cmdDeploy(args) {
153
189
  await deploy({ skipSeed, skipCheck })
154
190
  }
155
191
 
156
- async function cmdUpdate(_args) {
192
+ async function cmdUpdate() {
157
193
  const { update } = await import('@beechcms/cli')
158
194
  await update({})
159
195
  }
@@ -178,14 +214,64 @@ async function cmdReset(args) {
178
214
  }
179
215
 
180
216
  async function cmdSchemaDiff(args) {
181
- const remote = args.includes('--remote')
182
- const write = args.includes('--write')
183
- const nameIdx = args.indexOf('--name')
184
- const name = nameIdx !== -1 ? args[nameIdx + 1] : undefined
185
- const dbIdx = args.indexOf('--db')
186
- const db = dbIdx !== -1 ? args[dbIdx + 1] : undefined
217
+ const remote = args.includes('--remote')
218
+ const manifestIdx = args.indexOf('--manifest')
219
+ const manifest = manifestIdx !== -1 ? args[manifestIdx + 1] : undefined
220
+ const dbIdx = args.indexOf('--db')
221
+ const db = dbIdx !== -1 ? args[dbIdx + 1] : undefined
187
222
  const { schemaDiff } = await import('@beechcms/cli')
188
- await schemaDiff({ local: !remote, write, name, db })
223
+ await schemaDiff({ local: !remote, manifest, db })
224
+ }
225
+
226
+ async function cmdSchemaAudit(args) {
227
+ const remote = args.includes('--remote')
228
+ const dbIdx = args.indexOf('--db')
229
+ const db = dbIdx !== -1 ? args[dbIdx + 1] : undefined
230
+ const { schemaAudit } = await import('@beechcms/cli')
231
+ await schemaAudit({ local: !remote, db })
232
+ }
233
+
234
+ async function cmdSchemaMigrate(args) {
235
+ const remote = args.includes('--remote')
236
+ const apply = args.includes('--apply')
237
+ const dbIdx = args.indexOf('--db')
238
+ const db = dbIdx !== -1 ? args[dbIdx + 1] : undefined
239
+ const { schemaMigrate } = await import('@beechcms/cli')
240
+ await schemaMigrate({ apply, local: !remote, db })
241
+ }
242
+
243
+ async function cmdSchemaExport(args) {
244
+ const remote = args.includes('--remote')
245
+ const stdout = args.includes('--stdout')
246
+ const outIdx = args.indexOf('--out')
247
+ const oIdx = args.indexOf('-o')
248
+ const out = stdout ? null
249
+ : outIdx !== -1 ? args[outIdx + 1]
250
+ : oIdx !== -1 ? args[oIdx + 1]
251
+ : undefined
252
+ const dbIdx = args.indexOf('--db')
253
+ const db = dbIdx !== -1 ? args[dbIdx + 1] : undefined
254
+ const { schemaExport } = await import('@beechcms/cli')
255
+ await schemaExport({ out, local: !remote, db })
256
+ }
257
+
258
+ async function cmdSchemaPlan(args) {
259
+ const manifestIdx = args.indexOf('--manifest')
260
+ const manifest = manifestIdx !== -1 ? args[manifestIdx + 1] : undefined
261
+ const apiIdx = args.indexOf('--api-url')
262
+ const apiUrl = apiIdx !== -1 ? args[apiIdx + 1] : undefined
263
+ const { schemaPlan } = await import('@beechcms/cli')
264
+ await schemaPlan({ manifest, apiUrl })
265
+ }
266
+
267
+ async function cmdSchemaApply(args) {
268
+ const manifestIdx = args.indexOf('--manifest')
269
+ const manifest = manifestIdx !== -1 ? args[manifestIdx + 1] : undefined
270
+ const apiIdx = args.indexOf('--api-url')
271
+ const apiUrl = apiIdx !== -1 ? args[apiIdx + 1] : undefined
272
+ const yes = args.includes('--yes') || args.includes('-y')
273
+ const { schemaApply } = await import('@beechcms/cli')
274
+ await schemaApply({ manifest, apiUrl, yes })
189
275
  }
190
276
 
191
277
  async function cmdGenerateTypes(args) {
@@ -196,7 +282,7 @@ async function cmdGenerateTypes(args) {
196
282
  const outIdx = args.indexOf('--out')
197
283
  const outputIdx = args.indexOf('--output')
198
284
  const oIdx = args.indexOf('-o')
199
-
285
+
200
286
  if (outIdx !== -1 && args[outIdx + 1]) out = args[outIdx + 1]
201
287
  else if (outputIdx !== -1 && args[outputIdx + 1]) out = args[outputIdx + 1]
202
288
  else if (oIdx !== -1 && args[oIdx + 1]) out = args[oIdx + 1]
@@ -204,8 +290,33 @@ async function cmdGenerateTypes(args) {
204
290
  const dbIdx = args.indexOf('--db')
205
291
  const db = dbIdx !== -1 ? args[dbIdx + 1] : undefined
206
292
 
293
+ // `beech types generate` writes beech.generated.ts; the historical `gen-types` aliases keep
294
+ // their stdout default, so no existing script changes behaviour.
295
+ const isTypesGenerate = command === 'types:generate'
296
+ const destination = out ?? (isTypesGenerate ? undefined : null)
297
+
207
298
  const { generateTypes } = await import('@beechcms/cli')
208
- await generateTypes({ out, local, db })
299
+ await generateTypes({ out: destination, local, db })
300
+ }
301
+
302
+ async function cmdTypesCheck(args) {
303
+ const remote = args.includes('--remote')
304
+ const local = !remote
305
+
306
+ let out
307
+ const outIdx = args.indexOf('--out')
308
+ const outputIdx = args.indexOf('--output')
309
+ const oIdx = args.indexOf('-o')
310
+
311
+ if (outIdx !== -1 && args[outIdx + 1]) out = args[outIdx + 1]
312
+ else if (outputIdx !== -1 && args[outputIdx + 1]) out = args[outputIdx + 1]
313
+ else if (oIdx !== -1 && args[oIdx + 1]) out = args[oIdx + 1]
314
+
315
+ const dbIdx = args.indexOf('--db')
316
+ const db = dbIdx !== -1 ? args[dbIdx + 1] : undefined
317
+
318
+ const { typesCheck } = await import('@beechcms/cli')
319
+ await typesCheck({ out, local, db })
209
320
  }
210
321
 
211
322
  async function cmdForms(args) {
@@ -225,12 +336,12 @@ async function cmdForms(args) {
225
336
  }
226
337
 
227
338
  // New unified command wrappers:
228
- async function cmdDbMigrate(args) {
339
+ async function cmdDbMigrate() {
229
340
  const { dbMigrate } = await import('@beechcms/cli')
230
341
  await dbMigrate({})
231
342
  }
232
343
 
233
- async function cmdDbReset(args) {
344
+ async function cmdDbReset() {
234
345
  const { dbReset } = await import('@beechcms/cli')
235
346
  await dbReset({})
236
347
  }
@@ -241,22 +352,22 @@ async function cmdDev(args) {
241
352
  await dev({ plain })
242
353
  }
243
354
 
244
- async function cmdDevStop(args) {
355
+ async function cmdDevStop() {
245
356
  const { devStop } = await import('@beechcms/cli')
246
357
  await devStop()
247
358
  }
248
359
 
249
- async function cmdDevReset(args) {
360
+ async function cmdDevReset() {
250
361
  const { devReset } = await import('@beechcms/cli')
251
362
  await devReset()
252
363
  }
253
364
 
254
- async function cmdDevTunnel(args) {
365
+ async function cmdDevTunnel() {
255
366
  const { devTunnel } = await import('@beechcms/cli')
256
367
  await devTunnel()
257
368
  }
258
369
 
259
- async function cmdMailpitClear(args) {
370
+ async function cmdMailpitClear() {
260
371
  const { mailpitClear } = await import('@beechcms/cli')
261
372
  await mailpitClear()
262
373
  }
@@ -270,16 +381,20 @@ async function cmdLogs(args) {
270
381
  async function cmdTest(args) {
271
382
  const coverage = args.includes('--coverage')
272
383
  const diff = args.includes('--diff')
384
+ const tierIdx = args.indexOf('--tier')
385
+ const tier = tierIdx !== -1 && args[tierIdx + 1] && !args[tierIdx + 1].startsWith('--')
386
+ ? args[tierIdx + 1]
387
+ : undefined
273
388
  const { test } = await import('@beechcms/cli')
274
- await test({ coverage, diff })
389
+ await test({ coverage, diff, tier })
275
390
  }
276
391
 
277
- async function cmdLint(args) {
392
+ async function cmdLint() {
278
393
  const { lint } = await import('@beechcms/cli')
279
394
  await lint()
280
395
  }
281
396
 
282
- async function cmdDoctor(args) {
397
+ async function cmdDoctor() {
283
398
  const { doctor } = await import('@beechcms/cli')
284
399
  await doctor()
285
400
  }
package/bin/create.mjs CHANGED
@@ -105,6 +105,7 @@ function buildWranglerJsonc(cfg) {
105
105
  }
106
106
 
107
107
  function buildDevVars(cloudflare) {
108
+ const PRIVACY_KEY_LINES = `# Master key for protected fields (AES-GCM + blind index). Do NOT change once data is stored.\nPRIVACY_MASTER_KEY=${randomBytes(32).toString('hex')}`
108
109
  if (cloudflare) {
109
110
  return [
110
111
  `# Cloudflare R2 S3 credentials (required for direct client upload via Presigned URLs)`,
@@ -113,6 +114,8 @@ function buildDevVars(cloudflare) {
113
114
  `R2_SECRET_ACCESS_KEY=${cloudflare.r2SecretKey}`,
114
115
  `R2_ENDPOINT=${cloudflare.r2Endpoint}`,
115
116
  `R2_BUCKET_NAME=${cloudflare.r2Bucket}`,
117
+ '',
118
+ PRIVACY_KEY_LINES,
116
119
  ].join('\n') + '\n'
117
120
  }
118
121
  return [
@@ -123,6 +126,8 @@ function buildDevVars(cloudflare) {
123
126
  'R2_SECRET_ACCESS_KEY=',
124
127
  'R2_ENDPOINT=https://<YOUR_ACCOUNT_ID>.r2.cloudflarestorage.com',
125
128
  'R2_BUCKET_NAME=',
129
+ '',
130
+ PRIVACY_KEY_LINES,
126
131
  ].join('\n') + '\n'
127
132
  }
128
133
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@beechcms/cms",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "Edge-Native, Schema-Driven Headless CMS built on Cloudflare Workers, D1, and R2. Features the Botanical Engine for alias-stable field management, a modular widget layer, and a React + Vite admin dashboard.",
5
5
  "keywords": [
6
6
  "cms",
@@ -40,17 +40,18 @@
40
40
  },
41
41
  "type": "module",
42
42
  "devDependencies": {
43
- "@eslint/js": "^9.39.1",
44
- "@types/node": "^24.10.1",
43
+ "@eslint/js": "^10.0.1",
44
+ "@types/node": "^26.5.1",
45
45
  "cross-env": "^10.1.0",
46
- "eslint": "^9.39.1",
47
- "execa": "^9.6.1",
48
- "globals": "^16.5.0",
46
+ "eslint": "^10.10.0",
47
+ "execa": "^10.0.1",
48
+ "globals": "^17.12.0",
49
49
  "husky": "^9.1.7",
50
- "ink": "^6.8.0",
50
+ "ink": "^7.1.1",
51
51
  "ink-spinner": "^5.0.0",
52
+ "knip": "^6.41.0",
52
53
  "react": "^19.2.7",
53
- "react-doctor": "^0.2.16",
54
+ "react-doctor": "^0.9.14",
54
55
  "strip-ansi": "^7.2.0",
55
56
  "tsx": "^4.22.4",
56
57
  "turbo": "^2.9.18",
@@ -58,10 +59,10 @@
58
59
  "typedoc-plugin-markdown": "^4.12.0",
59
60
  "typescript": "^5.9.3",
60
61
  "vitepress": "^1.6.4",
61
- "vitest": "^4.1.0"
62
+ "vitest": "^4.1.11"
62
63
  },
63
64
  "dependencies": {
64
- "@beechcms/cli": "^0.8.0",
65
+ "@beechcms/cli": "^0.9.0",
65
66
  "@clack/prompts": "^0.9.1",
66
67
  "picocolors": "^1.1.1"
67
68
  },
@@ -97,8 +98,13 @@
97
98
  "dev:reset": "docker compose -f docker/docker-compose.yml down -v",
98
99
  "build": "turbo run build",
99
100
  "lint": "turbo run lint",
101
+ "lint:tests": "node scripts/check-test-placement.mjs",
102
+ "knip": "knip",
103
+ "knip:production": "knip --production",
104
+ "knip:ci": "knip --include files,dependencies,unlisted,binaries,unresolved",
100
105
  "type-check": "turbo run type-check",
101
106
  "test": "node scripts/test-runner.mjs",
107
+ "test:nocache": "node scripts/test-runner.mjs --nocache",
102
108
  "test:coverage": "node scripts/test-runner.mjs --coverage",
103
109
  "test:diff": "node scripts/test-coverage-diff.mjs",
104
110
  "test:dev-cli": "vitest run --config scripts/dev-cli/vitest.config.ts",