@beechcms/cms 0.4.0-preview.2 → 0.4.0-preview.4

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.
Files changed (3) hide show
  1. package/bin/cli.mjs +41 -17
  2. package/bin/create.mjs +57 -43
  3. package/package.json +2 -2
package/bin/cli.mjs CHANGED
@@ -1,9 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  // @ts-check
3
3
 
4
- import { execSync } from 'node:child_process'
4
+ import { spawnSync } from 'node:child_process'
5
5
  import { existsSync } from 'node:fs'
6
6
  import { resolve } from 'node:path'
7
+ import { pathToFileURL } from 'node:url'
7
8
 
8
9
 
9
10
  const [,, command, ...args] = process.argv
@@ -30,24 +31,43 @@ function help() {
30
31
  }
31
32
 
32
33
  function cmdBuild() {
33
- const corePath = resolve(process.cwd(), 'packages', 'core')
34
-
35
- if (!existsSync(corePath)) {
36
- console.error(
37
- '\nError: packages/core not found.\n' +
38
- 'Make sure you are running this command from the root of a BeechCMS project.\n'
39
- )
40
- process.exit(1)
34
+ console.log(
35
+ '\nNo build step needed for BeechCMS projects.\n' +
36
+ 'Edit seeds.ts then run `npx beech seed:load` to sync schema changes to D1.\n'
37
+ )
38
+ }
39
+
40
+ async function tryLoadLocalRegistry() {
41
+ const cwd = process.cwd()
42
+
43
+ // Try compiled JS first
44
+ for (const name of ['seeds.js', 'seeds.mjs']) {
45
+ const p = resolve(cwd, name)
46
+ if (existsSync(p)) {
47
+ try {
48
+ const mod = await import(pathToFileURL(p).href)
49
+ if (mod.SEED_REGISTRY && typeof mod.SEED_REGISTRY === 'object') {
50
+ return mod.SEED_REGISTRY
51
+ }
52
+ } catch {}
53
+ }
41
54
  }
42
55
 
43
- console.log('\nBuilding @beechcms/core…\n')
44
- try {
45
- execSync('npm run build -w @beechcms/core', { stdio: 'inherit' })
46
- console.log('\n✔ @beechcms/core built your seed changes are now live.\n')
47
- } catch {
48
- console.error('\nBuild failed. Check the output above for errors.\n')
49
- process.exit(1)
56
+ // Try seeds.ts via Node's experimental strip-types (Node 22.6+)
57
+ const tsPath = resolve(cwd, 'seeds.ts')
58
+ if (existsSync(tsPath)) {
59
+ const result = spawnSync(process.execPath, [
60
+ '--experimental-strip-types',
61
+ '--input-type=module',
62
+ '--eval',
63
+ `import { SEED_REGISTRY } from ${JSON.stringify(pathToFileURL(tsPath).href)}; process.stdout.write(JSON.stringify(SEED_REGISTRY))`,
64
+ ], { encoding: 'utf-8' })
65
+ if (result.status === 0 && result.stdout) {
66
+ try { return JSON.parse(result.stdout) } catch {}
67
+ }
50
68
  }
69
+
70
+ return null
51
71
  }
52
72
 
53
73
  async function cmdSeedLoad(args) {
@@ -57,14 +77,18 @@ async function cmdSeedLoad(args) {
57
77
  const dbIdx = args.indexOf('--db')
58
78
  const db = dbIdx !== -1 ? args[dbIdx + 1] : undefined
59
79
 
80
+ const registry = await tryLoadLocalRegistry()
81
+
60
82
  const { seedLoad } = await import('@beechcms/cli')
61
- await seedLoad({ dryRun, diff, local: !remote, db })
83
+ await seedLoad({ dryRun, diff, local: !remote, db, registry })
62
84
  }
63
85
 
64
86
  const handler = COMMANDS[command]
65
87
  if (!handler) {
66
88
  help()
67
89
  if (command) process.exit(1)
90
+ } else if (args.includes('--help') || args.includes('-h')) {
91
+ help()
68
92
  } else {
69
93
  await handler(args)
70
94
  }
package/bin/create.mjs CHANGED
@@ -85,9 +85,9 @@ function writeFile(path, content) {
85
85
  function buildWorkerTs() {
86
86
  return `/// <reference types="@cloudflare/workers-types" />
87
87
  import { createBeechApp } from '@beechcms/api'
88
- import { seeds } from './seeds'
88
+ import { SEED_REGISTRY } from './seeds'
89
89
 
90
- export default createBeechApp({ seeds })
90
+ export default createBeechApp({ seeds: Object.values(SEED_REGISTRY) })
91
91
  `
92
92
  }
93
93
 
@@ -108,8 +108,8 @@ function buildPackageJson(name) {
108
108
  'db:reset:local': 'node -e "require(\'fs\').rmSync(\'.wrangler/state\',{recursive:true,force:true})" && npm run db:migrate:local',
109
109
  },
110
110
  dependencies: {
111
- '@beechcms/api': '^0.4.0-preview.1',
112
- '@beechcms/core': '^0.4.0-preview.1',
111
+ '@beechcms/api': '^0.4.0-preview.4',
112
+ '@beechcms/core': '^0.4.0-preview.4',
113
113
  },
114
114
  devDependencies: {
115
115
  '@cloudflare/workers-types': '^4.0.0',
@@ -272,54 +272,68 @@ async function askCloudflareConfig(name) {
272
272
  // ── Main ──────────────────────────────────────────────────────────────────────
273
273
 
274
274
  async function main() {
275
+ const argv = process.argv.slice(2)
276
+ const silent = argv.includes('--yes') || argv.includes('-y') || !process.stdout.isTTY
277
+
275
278
  console.log()
276
279
  p.intro(pc.bgGreen(pc.black(' @beechcms/cms ')))
277
280
 
278
- // Project name
279
- const projectName = await p.text({
280
- message: 'Project name',
281
- placeholder: 'my-website',
282
- validate: (v) => {
283
- if (!v.trim()) return 'Required'
284
- if (!/^[a-z0-9][a-z0-9-]*$/.test(v.trim())) return 'Lowercase letters, numbers and hyphens only'
285
- },
286
- })
287
- if (p.isCancel(projectName)) { p.cancel('Cancelled'); process.exit(0) }
288
- const name = projectName.trim()
289
- const targetDir = resolve(process.cwd(), name)
281
+ let name, selectedTemplates, cloudflare
282
+
283
+ if (silent) {
284
+ // Non-interactive: use first positional arg or default name, no templates, skip Cloudflare
285
+ const positional = argv.find((a) => !a.startsWith('-'))
286
+ name = positional ?? 'my-beech-project'
287
+ selectedTemplates = []
288
+ cloudflare = null
289
+ console.log(pc.dim(` Running in non-interactive mode. Project name: ${name}`))
290
+ } else {
291
+ // Project name
292
+ const projectName = await p.text({
293
+ message: 'Project name',
294
+ placeholder: 'my-website',
295
+ validate: (v) => {
296
+ if (!v.trim()) return 'Required'
297
+ if (!/^[a-z0-9][a-z0-9-]*$/.test(v.trim())) return 'Lowercase letters, numbers and hyphens only'
298
+ },
299
+ })
300
+ if (p.isCancel(projectName)) { p.cancel('Cancelled'); process.exit(0) }
301
+ name = projectName.trim()
302
+
303
+ // Content types
304
+ const tmpl = await p.multiselect({
305
+ message: 'Which content types do you need?',
306
+ hint: 'Space to select, Enter to confirm. You can add more later in seeds.ts',
307
+ options: [
308
+ { value: 'blog', label: 'Blog', hint: 'posts with rich text, cover image, tags and authors' },
309
+ { value: 'gallery', label: 'Gallery', hint: 'media items with image, tags and featured flag' },
310
+ { value: 'contact', label: 'Contact', hint: 'public form submissions with masked email and read status' },
311
+ ],
312
+ required: false,
313
+ })
314
+ if (p.isCancel(tmpl)) { p.cancel('Cancelled'); process.exit(0) }
315
+ selectedTemplates = tmpl
316
+
317
+ // Cloudflare now or later?
318
+ const configureNow = await p.confirm({
319
+ message: 'Configure Cloudflare credentials now?',
320
+ hint: 'Choose "No" to scaffold the project and fill in the values later',
321
+ initialValue: true,
322
+ })
323
+ if (p.isCancel(configureNow)) { p.cancel('Cancelled'); process.exit(0) }
324
+
325
+ if (configureNow) {
326
+ cloudflare = await askCloudflareConfig(name)
327
+ if (!cloudflare) { p.cancel('Cancelled'); process.exit(0) }
328
+ }
329
+ }
290
330
 
331
+ const targetDir = resolve(process.cwd(), name)
291
332
  if (existsSync(targetDir)) {
292
333
  p.cancel(`Directory '${name}' already exists. Choose a different name or delete the folder.`)
293
334
  process.exit(1)
294
335
  }
295
336
 
296
- // Content types
297
- const selectedTemplates = await p.multiselect({
298
- message: 'Which content types do you need?',
299
- hint: 'Space to select, Enter to confirm. You can add more later in seeds.ts',
300
- options: [
301
- { value: 'blog', label: 'Blog', hint: 'posts with rich text, cover image, tags and authors' },
302
- { value: 'gallery', label: 'Gallery', hint: 'media items with image, tags and featured flag' },
303
- { value: 'contact', label: 'Contact', hint: 'public form submissions with masked email and read status' },
304
- ],
305
- required: false,
306
- })
307
- if (p.isCancel(selectedTemplates)) { p.cancel('Cancelled'); process.exit(0) }
308
-
309
- // Cloudflare now or later?
310
- const configureNow = await p.confirm({
311
- message: 'Configure Cloudflare credentials now?',
312
- hint: 'Choose "No" to scaffold the project and fill in the values later',
313
- initialValue: true,
314
- })
315
- if (p.isCancel(configureNow)) { p.cancel('Cancelled'); process.exit(0) }
316
-
317
- let cloudflare = null
318
- if (configureNow) {
319
- cloudflare = await askCloudflareConfig(name)
320
- if (!cloudflare) { p.cancel('Cancelled'); process.exit(0) }
321
- }
322
-
323
337
  const jwtSecret = generateSecret(32)
324
338
  const publicReadKey = generateSecret(16)
325
339
  const publicWriteKey = generateSecret(16)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@beechcms/cms",
3
- "version": "0.4.0-preview.2",
3
+ "version": "0.4.0-preview.4",
4
4
  "packageManager": "npm@11.9.0",
5
5
  "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.",
6
6
  "keywords": [
@@ -54,7 +54,7 @@
54
54
  "typescript": "^5.9.3"
55
55
  },
56
56
  "dependencies": {
57
- "@beechcms/cli": "^0.4.0-preview.2",
57
+ "@beechcms/cli": "^0.4.0-preview.4",
58
58
  "@clack/prompts": "^0.9.1",
59
59
  "picocolors": "^1.1.1"
60
60
  }