@beechcms/cms 0.4.0-preview.1 → 0.4.0-preview.11
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/bin/cli.mjs +77 -18
- package/bin/create.mjs +66 -45
- package/package.json +6 -4
package/bin/cli.mjs
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// @ts-check
|
|
3
3
|
|
|
4
|
-
import {
|
|
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
|
|
@@ -25,29 +26,83 @@ function help() {
|
|
|
25
26
|
--remote Execute against remote D1 (default: local)
|
|
26
27
|
--db <name> Override D1 database name
|
|
27
28
|
|
|
28
|
-
Run npx
|
|
29
|
+
Run npx @beechcms/cms to scaffold a new project.
|
|
29
30
|
`)
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
function cmdBuild() {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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 (root or apps/api)
|
|
44
|
+
const searchDirs = [cwd, resolve(cwd, 'apps', 'api')]
|
|
45
|
+
for (const dir of searchDirs) {
|
|
46
|
+
for (const name of ['seeds.js', 'seeds.mjs', 'seed.js', 'seed.mjs']) {
|
|
47
|
+
const p = resolve(dir, name)
|
|
48
|
+
if (existsSync(p)) {
|
|
49
|
+
try {
|
|
50
|
+
const mod = await import(pathToFileURL(p).href)
|
|
51
|
+
if (mod.SEED_REGISTRY && typeof mod.SEED_REGISTRY === 'object') {
|
|
52
|
+
return mod.SEED_REGISTRY
|
|
53
|
+
}
|
|
54
|
+
if (Array.isArray(mod.seeds)) {
|
|
55
|
+
return Object.fromEntries(mod.seeds.map(s => [s.slug, s]))
|
|
56
|
+
}
|
|
57
|
+
if (Array.isArray(mod.default)) {
|
|
58
|
+
return Object.fromEntries(mod.default.map(s => [s.slug, s]))
|
|
59
|
+
}
|
|
60
|
+
} catch {}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Try seeds.ts / seed.ts (root or apps/api)
|
|
66
|
+
let tsPath = null
|
|
67
|
+
for (const dir of searchDirs) {
|
|
68
|
+
const p = existsSync(resolve(dir, 'seeds.ts'))
|
|
69
|
+
? resolve(dir, 'seeds.ts')
|
|
70
|
+
: resolve(dir, 'seed.ts')
|
|
71
|
+
if (existsSync(p)) {
|
|
72
|
+
tsPath = p
|
|
73
|
+
break
|
|
74
|
+
}
|
|
41
75
|
}
|
|
42
76
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
77
|
+
if (tsPath) {
|
|
78
|
+
const result = spawnSync(process.execPath, [
|
|
79
|
+
'--experimental-strip-types',
|
|
80
|
+
'--input-type=module',
|
|
81
|
+
'--eval',
|
|
82
|
+
`
|
|
83
|
+
import * as mod from ${JSON.stringify(pathToFileURL(tsPath).href)};
|
|
84
|
+
let out = null;
|
|
85
|
+
if (mod.SEED_REGISTRY && typeof mod.SEED_REGISTRY === 'object') out = mod.SEED_REGISTRY;
|
|
86
|
+
else if (Array.isArray(mod.seeds)) out = Object.fromEntries(mod.seeds.map(s => [s.slug, s]));
|
|
87
|
+
else if (Array.isArray(mod.default)) out = Object.fromEntries(mod.default.map(s => [s.slug, s]));
|
|
88
|
+
if (out) process.stdout.write(JSON.stringify(out));
|
|
89
|
+
`.trim(),
|
|
90
|
+
], { encoding: 'utf-8' })
|
|
91
|
+
|
|
92
|
+
if (result.status === 0 && result.stdout) {
|
|
93
|
+
try { return JSON.parse(result.stdout) } catch (err) {
|
|
94
|
+
console.error(' Failed to parse seeds.ts output:', err)
|
|
95
|
+
}
|
|
96
|
+
} else if (result.status !== 0) {
|
|
97
|
+
console.error(' Error loading seeds.ts:')
|
|
98
|
+
console.error(result.stderr || result.stdout || 'Unknown error')
|
|
99
|
+
if (process.version.slice(1).split('.')[0] < 22) {
|
|
100
|
+
console.warn(' Note: Node.js 22.6+ is required to load .ts files directly. Current version:', process.version)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
50
103
|
}
|
|
104
|
+
|
|
105
|
+
return null
|
|
51
106
|
}
|
|
52
107
|
|
|
53
108
|
async function cmdSeedLoad(args) {
|
|
@@ -57,14 +112,18 @@ async function cmdSeedLoad(args) {
|
|
|
57
112
|
const dbIdx = args.indexOf('--db')
|
|
58
113
|
const db = dbIdx !== -1 ? args[dbIdx + 1] : undefined
|
|
59
114
|
|
|
115
|
+
const registry = await tryLoadLocalRegistry()
|
|
116
|
+
|
|
60
117
|
const { seedLoad } = await import('@beechcms/cli')
|
|
61
|
-
await seedLoad({ dryRun, diff, local: !remote, db })
|
|
118
|
+
await seedLoad({ dryRun, diff, local: !remote, db, registry })
|
|
62
119
|
}
|
|
63
120
|
|
|
64
121
|
const handler = COMMANDS[command]
|
|
65
122
|
if (!handler) {
|
|
66
123
|
help()
|
|
67
124
|
if (command) process.exit(1)
|
|
125
|
+
} else if (args.includes('--help') || args.includes('-h')) {
|
|
126
|
+
help()
|
|
68
127
|
} else {
|
|
69
128
|
await handler(args)
|
|
70
129
|
}
|
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 {
|
|
88
|
+
import { SEED_REGISTRY } from './seeds'
|
|
89
89
|
|
|
90
|
-
export default createBeechApp({ seeds })
|
|
90
|
+
export default createBeechApp({ seeds: SEED_REGISTRY })
|
|
91
91
|
`
|
|
92
92
|
}
|
|
93
93
|
|
|
@@ -104,12 +104,14 @@ function buildPackageJson(name) {
|
|
|
104
104
|
scripts: {
|
|
105
105
|
dev: 'wrangler dev --port 8789',
|
|
106
106
|
deploy: 'wrangler deploy --minify',
|
|
107
|
+
'seed:load': 'npx beech seed:load',
|
|
108
|
+
'seed:load:local': 'npx beech seed:load --local',
|
|
107
109
|
'db:migrate:local': 'wrangler d1 migrations apply ' + name + '-db --local',
|
|
108
110
|
'db:reset:local': 'node -e "require(\'fs\').rmSync(\'.wrangler/state\',{recursive:true,force:true})" && npm run db:migrate:local',
|
|
109
111
|
},
|
|
110
112
|
dependencies: {
|
|
111
|
-
'@beechcms/api': '^0.4.0-preview.
|
|
112
|
-
'@beechcms/core': '^0.4.0-preview.
|
|
113
|
+
'@beechcms/api': '^0.4.0-preview.6',
|
|
114
|
+
'@beechcms/core': '^0.4.0-preview.6',
|
|
113
115
|
},
|
|
114
116
|
devDependencies: {
|
|
115
117
|
'@cloudflare/workers-types': '^4.0.0',
|
|
@@ -133,6 +135,11 @@ function buildWranglerJsonc(cfg) {
|
|
|
133
135
|
"APP_URL": "${cfg.appUrl || 'http://localhost:5173'}"
|
|
134
136
|
},
|
|
135
137
|
|
|
138
|
+
"assets": {
|
|
139
|
+
"binding": "ASSETS",
|
|
140
|
+
"directory": "node_modules/@beechcms/api/assets/dashboard"
|
|
141
|
+
},
|
|
142
|
+
|
|
136
143
|
"d1_databases": [
|
|
137
144
|
{
|
|
138
145
|
"binding": "DB",
|
|
@@ -272,54 +279,68 @@ async function askCloudflareConfig(name) {
|
|
|
272
279
|
// ── Main ──────────────────────────────────────────────────────────────────────
|
|
273
280
|
|
|
274
281
|
async function main() {
|
|
282
|
+
const argv = process.argv.slice(2)
|
|
283
|
+
const silent = argv.includes('--yes') || argv.includes('-y') || !process.stdout.isTTY
|
|
284
|
+
|
|
275
285
|
console.log()
|
|
276
|
-
p.intro(pc.bgGreen(pc.black('
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
})
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
286
|
+
p.intro(pc.bgGreen(pc.black(' @beechcms/cms ')))
|
|
287
|
+
|
|
288
|
+
let name, selectedTemplates, cloudflare
|
|
289
|
+
|
|
290
|
+
if (silent) {
|
|
291
|
+
// Non-interactive: use first positional arg or default name, no templates, skip Cloudflare
|
|
292
|
+
const positional = argv.find((a) => !a.startsWith('-'))
|
|
293
|
+
name = positional ?? 'my-beech-project'
|
|
294
|
+
selectedTemplates = []
|
|
295
|
+
cloudflare = null
|
|
296
|
+
console.log(pc.dim(` Running in non-interactive mode. Project name: ${name}`))
|
|
297
|
+
} else {
|
|
298
|
+
// Project name
|
|
299
|
+
const projectName = await p.text({
|
|
300
|
+
message: 'Project name',
|
|
301
|
+
placeholder: 'my-website',
|
|
302
|
+
validate: (v) => {
|
|
303
|
+
if (!v.trim()) return 'Required'
|
|
304
|
+
if (!/^[a-z0-9][a-z0-9-]*$/.test(v.trim())) return 'Lowercase letters, numbers and hyphens only'
|
|
305
|
+
},
|
|
306
|
+
})
|
|
307
|
+
if (p.isCancel(projectName)) { p.cancel('Cancelled'); process.exit(0) }
|
|
308
|
+
name = projectName.trim()
|
|
309
|
+
|
|
310
|
+
// Content types
|
|
311
|
+
const tmpl = await p.multiselect({
|
|
312
|
+
message: 'Which content types do you need?',
|
|
313
|
+
hint: 'Space to select, Enter to confirm. You can add more later in seeds.ts',
|
|
314
|
+
options: [
|
|
315
|
+
{ value: 'blog', label: 'Blog', hint: 'posts with rich text, cover image, tags and authors' },
|
|
316
|
+
{ value: 'gallery', label: 'Gallery', hint: 'media items with image, tags and featured flag' },
|
|
317
|
+
{ value: 'contact', label: 'Contact', hint: 'public form submissions with masked email and read status' },
|
|
318
|
+
],
|
|
319
|
+
required: false,
|
|
320
|
+
})
|
|
321
|
+
if (p.isCancel(tmpl)) { p.cancel('Cancelled'); process.exit(0) }
|
|
322
|
+
selectedTemplates = tmpl
|
|
323
|
+
|
|
324
|
+
// Cloudflare now or later?
|
|
325
|
+
const configureNow = await p.confirm({
|
|
326
|
+
message: 'Configure Cloudflare credentials now?',
|
|
327
|
+
hint: 'Choose "No" to scaffold the project and fill in the values later',
|
|
328
|
+
initialValue: true,
|
|
329
|
+
})
|
|
330
|
+
if (p.isCancel(configureNow)) { p.cancel('Cancelled'); process.exit(0) }
|
|
331
|
+
|
|
332
|
+
if (configureNow) {
|
|
333
|
+
cloudflare = await askCloudflareConfig(name)
|
|
334
|
+
if (!cloudflare) { p.cancel('Cancelled'); process.exit(0) }
|
|
335
|
+
}
|
|
336
|
+
}
|
|
290
337
|
|
|
338
|
+
const targetDir = resolve(process.cwd(), name)
|
|
291
339
|
if (existsSync(targetDir)) {
|
|
292
340
|
p.cancel(`Directory '${name}' already exists. Choose a different name or delete the folder.`)
|
|
293
341
|
process.exit(1)
|
|
294
342
|
}
|
|
295
343
|
|
|
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
344
|
const jwtSecret = generateSecret(32)
|
|
324
345
|
const publicReadKey = generateSecret(16)
|
|
325
346
|
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.
|
|
3
|
+
"version": "0.4.0-preview.11",
|
|
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": [
|
|
@@ -38,14 +38,16 @@
|
|
|
38
38
|
"node": ">=18.0.0"
|
|
39
39
|
},
|
|
40
40
|
"bin": {
|
|
41
|
-
"
|
|
41
|
+
"beechcms": "bin/create.mjs",
|
|
42
42
|
"beech": "bin/cli.mjs"
|
|
43
43
|
},
|
|
44
44
|
"scripts": {
|
|
45
45
|
"dev": "turbo run dev --parallel",
|
|
46
46
|
"build": "turbo run build",
|
|
47
47
|
"test": "turbo run test",
|
|
48
|
-
"test:coverage": "turbo run test:coverage"
|
|
48
|
+
"test:coverage": "turbo run test:coverage",
|
|
49
|
+
"release": "node scripts/release.mjs",
|
|
50
|
+
"release:preview": "node scripts/release.mjs --preview"
|
|
49
51
|
},
|
|
50
52
|
"type": "module",
|
|
51
53
|
"devDependencies": {
|
|
@@ -54,7 +56,7 @@
|
|
|
54
56
|
"typescript": "^5.9.3"
|
|
55
57
|
},
|
|
56
58
|
"dependencies": {
|
|
57
|
-
"@beechcms/cli": "^0.4.0-preview.
|
|
59
|
+
"@beechcms/cli": "^0.4.0-preview.11",
|
|
58
60
|
"@clack/prompts": "^0.9.1",
|
|
59
61
|
"picocolors": "^1.1.1"
|
|
60
62
|
}
|