@beechcms/cms 0.7.0 → 0.8.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/bin/cli.mjs +85 -121
- package/bin/create.mjs +63 -129
- package/package.json +11 -4
- package/bin/templates/blog.ts +0 -35
- package/bin/templates/commerce.ts +0 -29
- package/bin/templates/contact.ts +0 -23
- package/bin/templates/empty.ts +0 -41
- package/bin/templates/gallery.ts +0 -16
- package/bin/templates/tasks.ts +0 -12
package/bin/cli.mjs
CHANGED
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// @ts-check
|
|
3
3
|
|
|
4
|
-
import { spawnSync } from 'node:child_process'
|
|
5
|
-
import { existsSync } from 'node:fs'
|
|
6
|
-
import { resolve } from 'node:path'
|
|
7
|
-
import { pathToFileURL } from 'node:url'
|
|
8
4
|
import pc from 'picocolors'
|
|
9
5
|
|
|
10
6
|
let [,, command, ...args] = process.argv
|
|
@@ -13,6 +9,14 @@ if (command && args[0] && ['db', 'seed', 'schema', 'dev', 'generate', 'mailpit']
|
|
|
13
9
|
command = `${command}:${args.shift()}`
|
|
14
10
|
}
|
|
15
11
|
|
|
12
|
+
if (command === 'gen' && args[0] === 'types') {
|
|
13
|
+
command = 'gen-types'
|
|
14
|
+
args.shift()
|
|
15
|
+
if (args[0] === 'typescript') {
|
|
16
|
+
args.shift()
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
16
20
|
const COMMANDS = {
|
|
17
21
|
build: cmdBuild,
|
|
18
22
|
'seed:load': cmdSeedLoad,
|
|
@@ -24,7 +28,12 @@ const COMMANDS = {
|
|
|
24
28
|
'update': cmdUpdate,
|
|
25
29
|
'onboard': cmdOnboard,
|
|
26
30
|
'reset': cmdReset,
|
|
27
|
-
'
|
|
31
|
+
'forms': cmdForms,
|
|
32
|
+
'form': cmdForms,
|
|
33
|
+
'forms:add': cmdForms,
|
|
34
|
+
'gen-types': cmdGenerateTypes,
|
|
35
|
+
'gen:types': cmdGenerateTypes,
|
|
36
|
+
'generate:types': cmdGenerateTypes,
|
|
28
37
|
// New unified command mappings:
|
|
29
38
|
'db:migrate': cmdDbMigrate,
|
|
30
39
|
'db:reset': cmdDbReset,
|
|
@@ -38,6 +47,8 @@ const COMMANDS = {
|
|
|
38
47
|
'test': cmdTest,
|
|
39
48
|
'lint': cmdLint,
|
|
40
49
|
'doctor': cmdDoctor,
|
|
50
|
+
'setup:cloudflare': cmdSetupCloudflare,
|
|
51
|
+
'setup:cf': cmdSetupCloudflare,
|
|
41
52
|
}
|
|
42
53
|
|
|
43
54
|
function help() {
|
|
@@ -50,7 +61,7 @@ function help() {
|
|
|
50
61
|
--remote Target remote D1 instead of local (default: local)
|
|
51
62
|
--db-name <n> Override D1 database name
|
|
52
63
|
--yes, -y Run in non-interactive mode
|
|
53
|
-
${pc.cyan('onboard')} One-command local provisioning (init --db
|
|
64
|
+
${pc.cyan('onboard')} One-command local provisioning (init --db)
|
|
54
65
|
--remote Target remote D1 instead of local (default: local)
|
|
55
66
|
--yes, -y Skip all interactive prompts (non-interactive mode)
|
|
56
67
|
--db <name> Override D1 database name
|
|
@@ -60,24 +71,23 @@ function help() {
|
|
|
60
71
|
${pc.cyan('db:migrate')} Apply all pending local migrations
|
|
61
72
|
${pc.cyan('db:reset')} Remove local Wrangler state and re-bootstrap database
|
|
62
73
|
|
|
63
|
-
${pc.bold('3.
|
|
64
|
-
${pc.cyan('
|
|
65
|
-
|
|
66
|
-
--
|
|
67
|
-
--
|
|
68
|
-
--remote Execute against remote D1 (default: local)
|
|
69
|
-
--db <name> Override D1 database name
|
|
70
|
-
${pc.cyan('schema:diff')} Diff SEED_REGISTRY vs D1 and generate additive SQL migration
|
|
71
|
-
--write Write the migration file (default: preview only)
|
|
72
|
-
--name <name> Migration name used in the filename
|
|
73
|
-
--remote Diff against remote D1 (default: local)
|
|
74
|
+
${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
|
|
74
79
|
--db <name> Override D1 database name
|
|
75
|
-
|
|
76
|
-
${pc.cyan('
|
|
77
|
-
--out <path> Output file (default: src/types/beech.ts)
|
|
78
|
-
--local Read from seeds.ts instead of querying live D1
|
|
80
|
+
-o, --output Output file path (default: standard output)
|
|
81
|
+
${pc.cyan('validate')} Validate runtime schema status
|
|
79
82
|
|
|
80
|
-
${pc.bold('4.
|
|
83
|
+
${pc.bold('4. Forms & Frontend Generation')}
|
|
84
|
+
${pc.cyan('forms / form')} Interactive wizard to generate React, Vue, Svelte, or Web Component forms
|
|
85
|
+
--framework <f> Framework: react, vue, svelte, vanilla
|
|
86
|
+
--seed <slug> Seed slug to bind to (e.g. clienti)
|
|
87
|
+
--mode <mode> styled (Tailwind) or headless
|
|
88
|
+
--yes, -y Skip interactive prompts
|
|
89
|
+
|
|
90
|
+
${pc.bold('5. Local Stack & Docker')}
|
|
81
91
|
${pc.cyan('dev / start')} Start the local dev environment (Docker + API + Dashboard)
|
|
82
92
|
--plain Avoid Ink visual TUI and run clean log streaming
|
|
83
93
|
${pc.cyan('dev:stop')} Stop Docker containers without wiping data
|
|
@@ -85,16 +95,19 @@ function help() {
|
|
|
85
95
|
${pc.cyan('dev:tunnel')} Display Cloudflare tunnel public testing URL
|
|
86
96
|
${pc.cyan('mailpit:clear')} Clear local test inbox in Mailpit
|
|
87
97
|
|
|
88
|
-
${pc.bold('
|
|
98
|
+
${pc.bold('6. Logs Streaming')}
|
|
89
99
|
${pc.cyan('logs <service>')} Show streaming logs for docker service: mailpit, db, tunnel, storage
|
|
90
100
|
|
|
91
|
-
${pc.bold('
|
|
101
|
+
${pc.bold('7. Quality & Deployment')}
|
|
92
102
|
${pc.cyan('test')} Run the test suite via Turborepo / Vitest
|
|
93
103
|
--coverage Generate coverage reports
|
|
94
104
|
--diff Run test coverage only for files modified on the branch
|
|
95
105
|
${pc.cyan('lint')} Run ESLint quality checks
|
|
106
|
+
${pc.cyan('setup:cloudflare')} (alias: ${pc.cyan('setup:cf')})
|
|
107
|
+
Interactive 1-step Cloudflare provisioning (D1, R2, Presigned S3 secrets)
|
|
108
|
+
--name <n> Project name override
|
|
109
|
+
--yes, -y Non-interactive mode
|
|
96
110
|
${pc.cyan('deploy')} Compile, test, deploy to Cloudflare environment
|
|
97
|
-
--skip-seed Skip remote seed:load step
|
|
98
111
|
--skip-check Skip /admin reachability check
|
|
99
112
|
${pc.cyan('doctor')} Execute React diagnostics check on Dashboard
|
|
100
113
|
`)
|
|
@@ -103,78 +116,10 @@ function help() {
|
|
|
103
116
|
function cmdBuild() {
|
|
104
117
|
console.log(
|
|
105
118
|
'\nNo build step needed for BeechCMS projects.\n' +
|
|
106
|
-
'
|
|
119
|
+
'Manage content types dynamically in the BeechCMS Dashboard at /admin.\n'
|
|
107
120
|
)
|
|
108
121
|
}
|
|
109
122
|
|
|
110
|
-
async function tryLoadLocalRegistry() {
|
|
111
|
-
const cwd = process.cwd()
|
|
112
|
-
|
|
113
|
-
// Try compiled JS first (root or apps/api)
|
|
114
|
-
const searchDirs = [cwd, resolve(cwd, 'apps', 'api')]
|
|
115
|
-
for (const dir of searchDirs) {
|
|
116
|
-
for (const name of ['seeds.js', 'seeds.mjs', 'seed.js', 'seed.mjs']) {
|
|
117
|
-
const p = resolve(dir, name)
|
|
118
|
-
if (existsSync(p)) {
|
|
119
|
-
try {
|
|
120
|
-
const mod = await import(pathToFileURL(p).href)
|
|
121
|
-
if (mod.SEED_REGISTRY && typeof mod.SEED_REGISTRY === 'object') {
|
|
122
|
-
return mod.SEED_REGISTRY
|
|
123
|
-
}
|
|
124
|
-
if (Array.isArray(mod.seeds)) {
|
|
125
|
-
return Object.fromEntries(mod.seeds.map(s => [s.slug, s]))
|
|
126
|
-
}
|
|
127
|
-
if (Array.isArray(mod.default)) {
|
|
128
|
-
return Object.fromEntries(mod.default.map(s => [s.slug, s]))
|
|
129
|
-
}
|
|
130
|
-
} catch {}
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// Try seeds.ts / seed.ts (root or apps/api)
|
|
136
|
-
let tsPath = null
|
|
137
|
-
for (const dir of searchDirs) {
|
|
138
|
-
const p = existsSync(resolve(dir, 'seeds.ts'))
|
|
139
|
-
? resolve(dir, 'seeds.ts')
|
|
140
|
-
: resolve(dir, 'seed.ts')
|
|
141
|
-
if (existsSync(p)) {
|
|
142
|
-
tsPath = p
|
|
143
|
-
break
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
if (tsPath) {
|
|
148
|
-
const result = spawnSync(process.execPath, [
|
|
149
|
-
'--experimental-strip-types',
|
|
150
|
-
'--input-type=module',
|
|
151
|
-
'--eval',
|
|
152
|
-
`
|
|
153
|
-
import * as mod from ${JSON.stringify(pathToFileURL(tsPath).href)};
|
|
154
|
-
let out = null;
|
|
155
|
-
if (mod.SEED_REGISTRY && typeof mod.SEED_REGISTRY === 'object') out = mod.SEED_REGISTRY;
|
|
156
|
-
else if (Array.isArray(mod.seeds)) out = Object.fromEntries(mod.seeds.map(s => [s.slug, s]));
|
|
157
|
-
else if (Array.isArray(mod.default)) out = Object.fromEntries(mod.default.map(s => [s.slug, s]));
|
|
158
|
-
if (out) process.stdout.write(JSON.stringify(out));
|
|
159
|
-
`.trim(),
|
|
160
|
-
], { encoding: 'utf-8' })
|
|
161
|
-
|
|
162
|
-
if (result.status === 0 && result.stdout) {
|
|
163
|
-
try { return JSON.parse(result.stdout) } catch (err) {
|
|
164
|
-
console.error(' Failed to parse seeds.ts output:', err)
|
|
165
|
-
}
|
|
166
|
-
} else if (result.status !== 0) {
|
|
167
|
-
console.error(' Error loading seeds.ts:')
|
|
168
|
-
console.error(result.stderr || result.stdout || 'Unknown error')
|
|
169
|
-
if (process.version.slice(1).split('.')[0] < 22) {
|
|
170
|
-
console.warn(' Note: Node.js 22.6+ is required to load .ts files directly. Current version:', process.version)
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
return null
|
|
176
|
-
}
|
|
177
|
-
|
|
178
123
|
async function cmdInit(args) {
|
|
179
124
|
const initDb = args.includes('--db')
|
|
180
125
|
const remote = args.includes('--remote')
|
|
@@ -183,26 +128,17 @@ async function cmdInit(args) {
|
|
|
183
128
|
const yes = args.includes('--yes') || args.includes('-y')
|
|
184
129
|
|
|
185
130
|
const { init } = await import('@beechcms/cli')
|
|
186
|
-
await init({ initDb, local: !remote, db, yes })
|
|
131
|
+
await init({ initDb, local: !remote, db, nonInteractive: yes })
|
|
187
132
|
}
|
|
188
133
|
|
|
189
|
-
async function cmdSeedLoad(
|
|
190
|
-
const dryRun = args.includes('--dry-run')
|
|
191
|
-
const diff = args.includes('--diff')
|
|
192
|
-
const remote = args.includes('--remote')
|
|
193
|
-
const dbIdx = args.indexOf('--db')
|
|
194
|
-
const db = dbIdx !== -1 ? args[dbIdx + 1] : undefined
|
|
195
|
-
|
|
196
|
-
const registry = await tryLoadLocalRegistry()
|
|
197
|
-
|
|
134
|
+
async function cmdSeedLoad(_args) {
|
|
198
135
|
const { seedLoad } = await import('@beechcms/cli')
|
|
199
|
-
await seedLoad({
|
|
136
|
+
await seedLoad({})
|
|
200
137
|
}
|
|
201
138
|
|
|
202
|
-
async function cmdValidate(
|
|
203
|
-
const registry = await tryLoadLocalRegistry()
|
|
139
|
+
async function cmdValidate(_args) {
|
|
204
140
|
const { validate } = await import('@beechcms/cli')
|
|
205
|
-
await validate({
|
|
141
|
+
await validate({})
|
|
206
142
|
}
|
|
207
143
|
|
|
208
144
|
async function cmdSeedCreate(_args) {
|
|
@@ -213,9 +149,8 @@ async function cmdSeedCreate(_args) {
|
|
|
213
149
|
async function cmdDeploy(args) {
|
|
214
150
|
const skipSeed = args.includes('--skip-seed')
|
|
215
151
|
const skipCheck = args.includes('--skip-check')
|
|
216
|
-
const registry = skipSeed ? null : await tryLoadLocalRegistry()
|
|
217
152
|
const { deploy } = await import('@beechcms/cli')
|
|
218
|
-
await deploy({
|
|
153
|
+
await deploy({ skipSeed, skipCheck })
|
|
219
154
|
}
|
|
220
155
|
|
|
221
156
|
async function cmdUpdate(_args) {
|
|
@@ -228,9 +163,8 @@ async function cmdOnboard(args) {
|
|
|
228
163
|
const yes = args.includes('--yes') || args.includes('-y')
|
|
229
164
|
const dbIdx = args.indexOf('--db')
|
|
230
165
|
const db = dbIdx !== -1 ? args[dbIdx + 1] : undefined
|
|
231
|
-
const registry = await tryLoadLocalRegistry()
|
|
232
166
|
const { onboard } = await import('@beechcms/cli')
|
|
233
|
-
await onboard({ local, yes, db
|
|
167
|
+
await onboard({ local, yes, db })
|
|
234
168
|
}
|
|
235
169
|
|
|
236
170
|
async function cmdReset(args) {
|
|
@@ -250,22 +184,44 @@ async function cmdSchemaDiff(args) {
|
|
|
250
184
|
const name = nameIdx !== -1 ? args[nameIdx + 1] : undefined
|
|
251
185
|
const dbIdx = args.indexOf('--db')
|
|
252
186
|
const db = dbIdx !== -1 ? args[dbIdx + 1] : undefined
|
|
253
|
-
const registry = await tryLoadLocalRegistry()
|
|
254
187
|
const { schemaDiff } = await import('@beechcms/cli')
|
|
255
|
-
await schemaDiff({ local: !remote, write, name, db
|
|
188
|
+
await schemaDiff({ local: !remote, write, name, db })
|
|
256
189
|
}
|
|
257
190
|
|
|
258
191
|
async function cmdGenerateTypes(args) {
|
|
192
|
+
const remote = args.includes('--remote')
|
|
193
|
+
const local = !remote
|
|
194
|
+
|
|
195
|
+
let out = null
|
|
259
196
|
const outIdx = args.indexOf('--out')
|
|
260
|
-
const
|
|
261
|
-
const
|
|
262
|
-
|
|
263
|
-
|
|
197
|
+
const outputIdx = args.indexOf('--output')
|
|
198
|
+
const oIdx = args.indexOf('-o')
|
|
199
|
+
|
|
200
|
+
if (outIdx !== -1 && args[outIdx + 1]) out = args[outIdx + 1]
|
|
201
|
+
else if (outputIdx !== -1 && args[outputIdx + 1]) out = args[outputIdx + 1]
|
|
202
|
+
else if (oIdx !== -1 && args[oIdx + 1]) out = args[oIdx + 1]
|
|
264
203
|
|
|
265
|
-
const
|
|
204
|
+
const dbIdx = args.indexOf('--db')
|
|
205
|
+
const db = dbIdx !== -1 ? args[dbIdx + 1] : undefined
|
|
266
206
|
|
|
267
207
|
const { generateTypes } = await import('@beechcms/cli')
|
|
268
|
-
await generateTypes({ out, local, db
|
|
208
|
+
await generateTypes({ out, local, db })
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
async function cmdForms(args) {
|
|
212
|
+
const yes = args.includes('--yes') || args.includes('-y')
|
|
213
|
+
const json = args.includes('--json')
|
|
214
|
+
const frameworkIdx = args.indexOf('--framework')
|
|
215
|
+
const framework = frameworkIdx !== -1 ? args[frameworkIdx + 1] : undefined
|
|
216
|
+
const seedIdx = args.indexOf('--seed')
|
|
217
|
+
const seed = seedIdx !== -1 ? args[seedIdx + 1] : undefined
|
|
218
|
+
const modeIdx = args.indexOf('--mode')
|
|
219
|
+
const mode = modeIdx !== -1 ? args[modeIdx + 1] : undefined
|
|
220
|
+
const outIdx = args.indexOf('--out')
|
|
221
|
+
const out = outIdx !== -1 ? args[outIdx + 1] : undefined
|
|
222
|
+
|
|
223
|
+
const { forms } = await import('@beechcms/cli')
|
|
224
|
+
await forms({ framework, seed, mode, out, yes, json })
|
|
269
225
|
}
|
|
270
226
|
|
|
271
227
|
// New unified command wrappers:
|
|
@@ -328,10 +284,18 @@ async function cmdDoctor(args) {
|
|
|
328
284
|
await doctor()
|
|
329
285
|
}
|
|
330
286
|
|
|
287
|
+
async function cmdSetupCloudflare(args) {
|
|
288
|
+
const yes = args.includes('--yes') || args.includes('-y')
|
|
289
|
+
const nameIdx = args.indexOf('--name')
|
|
290
|
+
const projectName = nameIdx !== -1 ? args[nameIdx + 1] : undefined
|
|
291
|
+
const { setupCloudflare } = await import('@beechcms/cli')
|
|
292
|
+
await setupCloudflare({ projectName, nonInteractive: yes })
|
|
293
|
+
}
|
|
294
|
+
|
|
331
295
|
const handler = COMMANDS[command]
|
|
332
296
|
if (!handler) {
|
|
333
297
|
help()
|
|
334
|
-
if (command) process.exit(1)
|
|
298
|
+
if (command && command !== '--help' && command !== '-h' && command !== 'help') process.exit(1)
|
|
335
299
|
} else if (args.includes('--help') || args.includes('-h')) {
|
|
336
300
|
help()
|
|
337
301
|
} else {
|
package/bin/create.mjs
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
import * as p from '@clack/prompts'
|
|
5
5
|
import pc from 'picocolors'
|
|
6
|
-
import { execSync } from 'node:child_process'
|
|
6
|
+
import { spawnSync, execSync } from 'node:child_process'
|
|
7
7
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
|
|
8
8
|
import { resolve, join } from 'node:path'
|
|
9
9
|
import { randomBytes } from 'node:crypto'
|
|
@@ -12,78 +12,6 @@ import { dirname } from 'node:path'
|
|
|
12
12
|
|
|
13
13
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
14
14
|
|
|
15
|
-
// ── Template registry ─────────────────────────────────────────────────────────
|
|
16
|
-
|
|
17
|
-
const TEMPLATES = {
|
|
18
|
-
blog: {
|
|
19
|
-
label: 'Blog',
|
|
20
|
-
hint: 'posts with rich text, cover image, tags and authors',
|
|
21
|
-
file: 'blog.ts',
|
|
22
|
-
registryEntries: ['posts: POST_SEED', 'authors: AUTHOR_SEED'],
|
|
23
|
-
},
|
|
24
|
-
gallery: {
|
|
25
|
-
label: 'Gallery',
|
|
26
|
-
hint: 'media items with image, tags and featured flag',
|
|
27
|
-
file: 'gallery.ts',
|
|
28
|
-
registryEntries: ['gallery: GALLERY_SEED'],
|
|
29
|
-
},
|
|
30
|
-
contact: {
|
|
31
|
-
label: 'Contact',
|
|
32
|
-
hint: 'public form submissions with masked email and read status',
|
|
33
|
-
file: 'contact.ts',
|
|
34
|
-
registryEntries: ['messages: MESSAGE_SEED'],
|
|
35
|
-
},
|
|
36
|
-
commerce: {
|
|
37
|
-
label: 'Commerce',
|
|
38
|
-
hint: 'e-commerce product catalog with prices, inventory and ratings',
|
|
39
|
-
file: 'commerce.ts',
|
|
40
|
-
registryEntries: ['products: PRODUCT_SEED', 'reviews: REVIEW_SEED'],
|
|
41
|
-
},
|
|
42
|
-
tasks: {
|
|
43
|
-
label: 'Tasks',
|
|
44
|
-
hint: 'project management tasks with progress slider',
|
|
45
|
-
file: 'tasks.ts',
|
|
46
|
-
registryEntries: ['tasks: TASK_SEED'],
|
|
47
|
-
},
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function readTemplate(filename) {
|
|
51
|
-
return readFileSync(join(__dirname, 'templates', filename), 'utf8')
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function buildSeedsFile(selectedKeys) {
|
|
55
|
-
const header = `import type { Seed } from '@beechcms/core'\n\n`
|
|
56
|
-
|
|
57
|
-
if (selectedKeys.length === 0) {
|
|
58
|
-
const example = readFileSync(join(__dirname, 'templates', 'empty.ts'), 'utf8')
|
|
59
|
-
return (
|
|
60
|
-
header +
|
|
61
|
-
example +
|
|
62
|
-
'\nexport const SEED_REGISTRY: Record<string, Seed> = {}\n\n' +
|
|
63
|
-
'export function getSeed(slug: string): Seed | null {\n' +
|
|
64
|
-
' return SEED_REGISTRY[slug] ?? null\n' +
|
|
65
|
-
'}\n'
|
|
66
|
-
)
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const blocks = selectedKeys.map((key) =>
|
|
70
|
-
readFileSync(join(__dirname, 'templates', TEMPLATES[key].file), 'utf8')
|
|
71
|
-
)
|
|
72
|
-
|
|
73
|
-
const registryEntries = selectedKeys.flatMap((key) => TEMPLATES[key].registryEntries)
|
|
74
|
-
const registry =
|
|
75
|
-
'export const SEED_REGISTRY: Record<string, Seed> = {\n' +
|
|
76
|
-
registryEntries.map((e) => ` ${e},`).join('\n') +
|
|
77
|
-
'\n}\n'
|
|
78
|
-
|
|
79
|
-
const getSeed =
|
|
80
|
-
'\nexport function getSeed(slug: string): Seed | null {\n' +
|
|
81
|
-
' return SEED_REGISTRY[slug] ?? null\n' +
|
|
82
|
-
'}\n'
|
|
83
|
-
|
|
84
|
-
return header + blocks.join('\n') + '\n' + registry + getSeed
|
|
85
|
-
}
|
|
86
|
-
|
|
87
15
|
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
88
16
|
|
|
89
17
|
function generateSecret(bytes = 32) {
|
|
@@ -97,16 +25,11 @@ function writeFile(path, content) {
|
|
|
97
25
|
function buildWorkerTs() {
|
|
98
26
|
return `/// <reference types="@cloudflare/workers-types" />
|
|
99
27
|
import { createBeechApp } from '@beechcms/api'
|
|
100
|
-
import { SEED_REGISTRY } from './seeds'
|
|
101
28
|
|
|
102
|
-
export default createBeechApp({ seeds:
|
|
29
|
+
export default createBeechApp({ seeds: [] })
|
|
103
30
|
`
|
|
104
31
|
}
|
|
105
32
|
|
|
106
|
-
function buildSeedsTs(selectedKeys) {
|
|
107
|
-
return buildSeedsFile(selectedKeys)
|
|
108
|
-
}
|
|
109
|
-
|
|
110
33
|
function getCurrentVersion() {
|
|
111
34
|
try {
|
|
112
35
|
const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8'))
|
|
@@ -126,8 +49,6 @@ function buildPackageJson(name) {
|
|
|
126
49
|
scripts: {
|
|
127
50
|
dev: 'wrangler dev --port 8789',
|
|
128
51
|
deploy: 'wrangler deploy --minify',
|
|
129
|
-
'seed:load': 'npx beech seed:load',
|
|
130
|
-
'seed:load:local': 'npx beech seed:load --local',
|
|
131
52
|
'db:migrate:local': 'wrangler d1 migrations apply ' + name + '-db --local',
|
|
132
53
|
'db:reset:local': 'node -e "require(\'fs\').rmSync(\'.wrangler/state\',{recursive:true,force:true})" && npm run db:migrate:local',
|
|
133
54
|
},
|
|
@@ -136,6 +57,7 @@ function buildPackageJson(name) {
|
|
|
136
57
|
'@beechcms/core': version,
|
|
137
58
|
},
|
|
138
59
|
devDependencies: {
|
|
60
|
+
'@beechcms/cms': version,
|
|
139
61
|
'@cloudflare/workers-types': '^4.0.0',
|
|
140
62
|
wrangler: '^4.0.0',
|
|
141
63
|
typescript: '^5.0.0',
|
|
@@ -148,6 +70,7 @@ function buildWranglerJsonc(cfg) {
|
|
|
148
70
|
"name": "${cfg.name}-api",
|
|
149
71
|
"main": "worker.ts",
|
|
150
72
|
"compatibility_date": "2025-01-01",
|
|
73
|
+
"compatibility_flags": ["nodejs_compat"],
|
|
151
74
|
|
|
152
75
|
"vars": {
|
|
153
76
|
"JWT_SECRET": "${cfg.jwtSecret}",
|
|
@@ -184,6 +107,8 @@ function buildWranglerJsonc(cfg) {
|
|
|
184
107
|
function buildDevVars(cloudflare) {
|
|
185
108
|
if (cloudflare) {
|
|
186
109
|
return [
|
|
110
|
+
`# Cloudflare R2 S3 credentials (required for direct client upload via Presigned URLs)`,
|
|
111
|
+
`# Guide: https://developers.cloudflare.com/r2/api/s3/tokens/`,
|
|
187
112
|
`R2_ACCESS_KEY_ID=${cloudflare.r2AccessKey}`,
|
|
188
113
|
`R2_SECRET_ACCESS_KEY=${cloudflare.r2SecretKey}`,
|
|
189
114
|
`R2_ENDPOINT=${cloudflare.r2Endpoint}`,
|
|
@@ -191,9 +116,8 @@ function buildDevVars(cloudflare) {
|
|
|
191
116
|
].join('\n') + '\n'
|
|
192
117
|
}
|
|
193
118
|
return [
|
|
194
|
-
'# R2 credentials
|
|
195
|
-
'#
|
|
196
|
-
'# Fill these in only when testing production media behaviour:',
|
|
119
|
+
'# Cloudflare R2 S3 credentials (required for direct client upload via Presigned URLs)',
|
|
120
|
+
'# Create an R2 API Token: Cloudflare Dashboard → R2 → "Manage R2 API Tokens" (Object Read & Write)',
|
|
197
121
|
'# Guide: https://developers.cloudflare.com/r2/api/s3/tokens/',
|
|
198
122
|
'R2_ACCESS_KEY_ID=',
|
|
199
123
|
'R2_SECRET_ACCESS_KEY=',
|
|
@@ -238,35 +162,57 @@ async function askCloudflareConfig(name) {
|
|
|
238
162
|
})
|
|
239
163
|
if (p.isCancel(accountId)) return null
|
|
240
164
|
|
|
241
|
-
const
|
|
242
|
-
message:
|
|
243
|
-
|
|
244
|
-
|
|
165
|
+
const autoCreate = await p.confirm({
|
|
166
|
+
message: `Auto-create Cloudflare D1 (${name}-db) and R2 (${name}-media) now?`,
|
|
167
|
+
hint: 'Runs `wrangler d1 create` and `wrangler r2 bucket create`',
|
|
168
|
+
initialValue: true,
|
|
245
169
|
})
|
|
246
|
-
if (p.isCancel(
|
|
170
|
+
if (p.isCancel(autoCreate)) return null
|
|
171
|
+
|
|
172
|
+
let d1Id = ''
|
|
173
|
+
const d1Name = `${name}-db`
|
|
174
|
+
const r2Bucket = `${name}-media`
|
|
175
|
+
|
|
176
|
+
if (autoCreate) {
|
|
177
|
+
const s = p.spinner()
|
|
178
|
+
s.start(`Creating D1 database: ${pc.bold(d1Name)}…`)
|
|
179
|
+
const d1Res = spawnSync('npx', ['wrangler', 'd1', 'create', d1Name], { encoding: 'utf-8', shell: true })
|
|
180
|
+
const d1Out = (d1Res.stdout || '') + (d1Res.stderr || '')
|
|
181
|
+
const idMatch = d1Out.match(/database_id\s*=\s*["']?([a-f0-9-]{36})["']?/i) ||
|
|
182
|
+
d1Out.match(/"database_id":\s*"([a-f0-9-]{36})"/i) ||
|
|
183
|
+
d1Out.match(/([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})/i)
|
|
184
|
+
if (idMatch) {
|
|
185
|
+
d1Id = idMatch[1]
|
|
186
|
+
s.stop(pc.green(`✓ D1 database ready (${d1Id})`))
|
|
187
|
+
} else {
|
|
188
|
+
s.stop(pc.yellow(`ℹ D1 status: ${d1Out.trim().slice(0, 100)}`))
|
|
189
|
+
}
|
|
247
190
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
validate: (v) => { if (!v.trim()) return 'Required — create the D1 database first and paste its ID here' },
|
|
253
|
-
})
|
|
254
|
-
if (p.isCancel(d1Id)) return null
|
|
191
|
+
s.start(`Creating R2 bucket: ${pc.bold(r2Bucket)}…`)
|
|
192
|
+
spawnSync('npx', ['wrangler', 'r2', 'bucket', 'create', r2Bucket], { encoding: 'utf-8', shell: true })
|
|
193
|
+
s.stop(pc.green(`✓ R2 bucket ready (${r2Bucket})`))
|
|
194
|
+
}
|
|
255
195
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
196
|
+
if (!d1Id) {
|
|
197
|
+
const d1IdInput = await p.text({
|
|
198
|
+
message: 'D1 Database ID',
|
|
199
|
+
placeholder: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
|
|
200
|
+
hint: `Run: npx wrangler d1 create ${d1Name} — paste the "database_id" here`,
|
|
201
|
+
validate: (v) => { if (!v.trim()) return 'Required — paste database_id from D1' },
|
|
202
|
+
})
|
|
203
|
+
if (p.isCancel(d1IdInput)) return null
|
|
204
|
+
d1Id = d1IdInput.trim()
|
|
205
|
+
}
|
|
262
206
|
|
|
263
207
|
p.note(
|
|
264
208
|
[
|
|
209
|
+
'Direct client uploads use Presigned URLs (SigV4) to stream files directly to R2.',
|
|
265
210
|
'Create an R2 API token:',
|
|
266
211
|
' Cloudflare Dashboard → R2 → "Manage R2 API Tokens"',
|
|
267
212
|
` → Create Token → Object Read & Write → bucket: ${r2Bucket}`,
|
|
213
|
+
'Guide: https://developers.cloudflare.com/r2/api/s3/tokens/',
|
|
268
214
|
].join('\n'),
|
|
269
|
-
'R2
|
|
215
|
+
'R2 S3 Credentials (for Presigned URLs)'
|
|
270
216
|
)
|
|
271
217
|
|
|
272
218
|
const r2AccessKey = await p.text({
|
|
@@ -309,17 +255,14 @@ async function main() {
|
|
|
309
255
|
console.log()
|
|
310
256
|
p.intro(pc.bgGreen(pc.black(' @beechcms/cms ')))
|
|
311
257
|
|
|
312
|
-
let name,
|
|
258
|
+
let name, cloudflare
|
|
313
259
|
|
|
314
260
|
if (silent) {
|
|
315
261
|
// Non-interactive: use first positional arg or default name, skip Cloudflare
|
|
316
262
|
const positional = argv.find((a) => !a.startsWith('-'))
|
|
317
263
|
name = positional ?? 'my-beech-project'
|
|
318
|
-
const withExamples = argv.includes('--with-examples') || argv.includes('--examples')
|
|
319
|
-
selectedTemplates = withExamples ? ['blog'] : []
|
|
320
264
|
cloudflare = null
|
|
321
|
-
|
|
322
|
-
console.log(pc.dim(` Running in non-interactive mode. Project name: ${name}${examplesNote}`))
|
|
265
|
+
console.log(pc.dim(` Running in non-interactive mode. Project name: ${name}`))
|
|
323
266
|
} else {
|
|
324
267
|
// Project name
|
|
325
268
|
const projectName = await p.text({
|
|
@@ -333,22 +276,6 @@ async function main() {
|
|
|
333
276
|
if (p.isCancel(projectName)) { p.cancel('Cancelled'); process.exit(0) }
|
|
334
277
|
name = projectName.trim()
|
|
335
278
|
|
|
336
|
-
// Content types
|
|
337
|
-
const tmpl = await p.multiselect({
|
|
338
|
-
message: 'Which content types do you need?',
|
|
339
|
-
hint: 'Space to select, Enter to confirm. You can add more later in seeds.ts',
|
|
340
|
-
options: [
|
|
341
|
-
{ value: 'blog', label: 'Blog', hint: 'posts with rich text, cover image, tags and authors' },
|
|
342
|
-
{ value: 'gallery', label: 'Gallery', hint: 'media items with image, tags and featured flag' },
|
|
343
|
-
{ value: 'contact', label: 'Contact', hint: 'public form submissions with masked email and read status' },
|
|
344
|
-
{ value: 'commerce',label: 'Commerce',hint: 'e-commerce product catalog with prices, inventory and ratings' },
|
|
345
|
-
{ value: 'tasks', label: 'Tasks', hint: 'project management tasks with progress slider' },
|
|
346
|
-
],
|
|
347
|
-
required: false,
|
|
348
|
-
})
|
|
349
|
-
if (p.isCancel(tmpl)) { p.cancel('Cancelled'); process.exit(0) }
|
|
350
|
-
selectedTemplates = tmpl
|
|
351
|
-
|
|
352
279
|
// Cloudflare now or later?
|
|
353
280
|
const configureNow = await p.confirm({
|
|
354
281
|
message: 'Configure Cloudflare credentials now?',
|
|
@@ -384,7 +311,6 @@ async function main() {
|
|
|
384
311
|
|
|
385
312
|
mkdirSync(targetDir, { recursive: true })
|
|
386
313
|
|
|
387
|
-
writeFile(join(targetDir, 'seeds.ts'), buildSeedsTs(selectedTemplates))
|
|
388
314
|
writeFile(join(targetDir, 'worker.ts'), buildWorkerTs())
|
|
389
315
|
writeFile(join(targetDir, 'package.json'), buildPackageJson(name))
|
|
390
316
|
writeFile(join(targetDir, 'tsconfig.json'), buildTsConfig())
|
|
@@ -429,10 +355,17 @@ async function main() {
|
|
|
429
355
|
...(pendingConfig ? [
|
|
430
356
|
`${pc.bold('3. Complete Cloudflare configuration')} ${pc.yellow('← pending')}`,
|
|
431
357
|
` Edit ${pc.underline('wrangler.jsonc')} → fill in ${pc.yellow('database_id')} (D1) and ${pc.yellow('bucket_name')} (R2)`,
|
|
432
|
-
`
|
|
433
|
-
`
|
|
358
|
+
` Set R2 S3 secrets for Presigned uploads in ${pc.underline('.dev.vars')} (dev) and via ${pc.cyan('npx wrangler secret put')} (prod)`,
|
|
359
|
+
` Guide: https://developers.cloudflare.com/r2/api/s3/tokens/`,
|
|
434
360
|
'',
|
|
435
|
-
] : [
|
|
361
|
+
] : [
|
|
362
|
+
`${pc.bold('3. Production R2 secrets')} (when deploying to Cloudflare)`,
|
|
363
|
+
` ${pc.cyan('npx wrangler secret put R2_ACCESS_KEY_ID')}`,
|
|
364
|
+
` ${pc.cyan('npx wrangler secret put R2_SECRET_ACCESS_KEY')}`,
|
|
365
|
+
` ${pc.cyan('npx wrangler secret put R2_ENDPOINT')} → ${cloudflare.r2Endpoint}`,
|
|
366
|
+
` ${pc.cyan('npx wrangler secret put R2_BUCKET_NAME')} → ${cloudflare.r2Bucket}`,
|
|
367
|
+
'',
|
|
368
|
+
]),
|
|
436
369
|
`${step(3)}. Run local migrations`,
|
|
437
370
|
` ${pc.cyan('npm run db:migrate:local')}`,
|
|
438
371
|
'',
|
|
@@ -443,7 +376,7 @@ async function main() {
|
|
|
443
376
|
`${step(5)}. Deploy to production`,
|
|
444
377
|
` ${pc.cyan('npm run deploy')}`,
|
|
445
378
|
'',
|
|
446
|
-
`${pc.dim('
|
|
379
|
+
`${pc.dim('Manage content types dynamically in the BeechCMS Dashboard at /admin.')}`,
|
|
447
380
|
`${pc.dim('JWT secret and API keys have been auto-generated.')}`,
|
|
448
381
|
].join('\n'),
|
|
449
382
|
'Next steps'
|
|
@@ -455,3 +388,4 @@ main().catch((err) => {
|
|
|
455
388
|
console.error(err)
|
|
456
389
|
process.exit(1)
|
|
457
390
|
})
|
|
391
|
+
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@beechcms/cms",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.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",
|
|
@@ -33,6 +33,8 @@
|
|
|
33
33
|
"node": ">=18.0.0"
|
|
34
34
|
},
|
|
35
35
|
"bin": {
|
|
36
|
+
"cms": "bin/create.mjs",
|
|
37
|
+
"@beechcms/cms": "bin/create.mjs",
|
|
36
38
|
"beechcms": "bin/create.mjs",
|
|
37
39
|
"beech": "bin/cli.mjs"
|
|
38
40
|
},
|
|
@@ -59,7 +61,7 @@
|
|
|
59
61
|
"vitest": "^4.1.0"
|
|
60
62
|
},
|
|
61
63
|
"dependencies": {
|
|
62
|
-
"@beechcms/cli": "^0.
|
|
64
|
+
"@beechcms/cli": "^0.8.0",
|
|
63
65
|
"@clack/prompts": "^0.9.1",
|
|
64
66
|
"picocolors": "^1.1.1"
|
|
65
67
|
},
|
|
@@ -76,9 +78,10 @@
|
|
|
76
78
|
}
|
|
77
79
|
},
|
|
78
80
|
"scripts": {
|
|
79
|
-
"docs:generate": "typedoc",
|
|
81
|
+
"docs:generate": "turbo run build && typedoc",
|
|
80
82
|
"docs:dev": "vitepress dev docs",
|
|
81
|
-
"docs:
|
|
83
|
+
"docs:check": "node scripts/docs-fact-check.mjs",
|
|
84
|
+
"docs:build": "turbo run build && typedoc && node scripts/docs-fact-check.mjs && vitepress build docs",
|
|
82
85
|
"docs:preview": "vitepress preview docs",
|
|
83
86
|
"beech": "node bin/cli.mjs",
|
|
84
87
|
"dev": "node scripts/dev.mjs",
|
|
@@ -103,6 +106,10 @@
|
|
|
103
106
|
"setup:graph": "node scripts/setup-graph.mjs",
|
|
104
107
|
"release": "node scripts/release.mjs",
|
|
105
108
|
"release:preview": "node scripts/release.mjs --preview",
|
|
109
|
+
"release:current": "node scripts/release.mjs --current",
|
|
110
|
+
"version:list": "node scripts/release.mjs list",
|
|
111
|
+
"version:get": "node scripts/release.mjs get",
|
|
112
|
+
"version:set": "node scripts/release.mjs set",
|
|
106
113
|
"postinstall": "node scripts/check-docker.mjs",
|
|
107
114
|
"doctor": "react-doctor -y --no-dead-code"
|
|
108
115
|
}
|
package/bin/templates/blog.ts
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
/** Post: blog article with rich text, cover image, tags and SEO */
|
|
2
|
-
export const POST_SEED: Seed = {
|
|
3
|
-
slug: 'posts',
|
|
4
|
-
label: 'Post',
|
|
5
|
-
labelPlural: 'Posts',
|
|
6
|
-
displayNameAlias: 'title',
|
|
7
|
-
allowPublicRead: true,
|
|
8
|
-
allowDrafts: true,
|
|
9
|
-
branches: [
|
|
10
|
-
{ id: 'pst_01', alias: 'title', label: 'Title', type: 'text', requiredOnCreate: true },
|
|
11
|
-
{ id: 'pst_02', alias: 'publishedAt', label: 'Published at', type: 'date' },
|
|
12
|
-
{ id: 'pst_03', alias: 'coverImage', label: 'Cover image', type: 'file', fileOptions: { accept: 'image' } },
|
|
13
|
-
{ id: 'pst_04', alias: 'excerpt', label: 'Excerpt', type: 'text' },
|
|
14
|
-
{ id: 'pst_05', alias: 'tags', label: 'Tags', type: 'json', options: ['news', 'tutorial', 'release', 'guide', 'opinion'] },
|
|
15
|
-
{ id: 'pst_06', alias: 'body', label: 'Body', type: 'richtext' },
|
|
16
|
-
{ id: 'pst_07', alias: 'metaTitle', label: 'Meta title (SEO)', type: 'text' },
|
|
17
|
-
{ id: 'pst_08', alias: 'metaDescription', label: 'Meta description (SEO)', type: 'text' },
|
|
18
|
-
],
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/** Author: content creator profile with photo and social link */
|
|
22
|
-
export const AUTHOR_SEED: Seed = {
|
|
23
|
-
slug: 'authors',
|
|
24
|
-
label: 'Author',
|
|
25
|
-
labelPlural: 'Authors',
|
|
26
|
-
displayNameAlias: 'name',
|
|
27
|
-
allowPublicRead: true,
|
|
28
|
-
branches: [
|
|
29
|
-
{ id: 'aut_01', alias: 'name', label: 'Name', type: 'text', requiredOnCreate: true },
|
|
30
|
-
{ id: 'aut_02', alias: 'bio', label: 'Bio', type: 'text' },
|
|
31
|
-
{ id: 'aut_03', alias: 'photo', label: 'Photo', type: 'file', fileOptions: { accept: 'image' } },
|
|
32
|
-
{ id: 'aut_04', alias: 'website', label: 'Website URL', type: 'text' },
|
|
33
|
-
{ id: 'aut_05', alias: 'active', label: 'Active', type: 'boolean' },
|
|
34
|
-
],
|
|
35
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
/** Product: e-commerce product catalog with prices, inventory and ratings */
|
|
2
|
-
export const PRODUCT_SEED: Seed = {
|
|
3
|
-
slug: 'products',
|
|
4
|
-
label: 'Product',
|
|
5
|
-
labelPlural: 'Products',
|
|
6
|
-
displayNameAlias: 'name',
|
|
7
|
-
allowPublicRead: true,
|
|
8
|
-
branches: [
|
|
9
|
-
{ alias: 'name', label: 'Product Name', type: 'text', requiredOnCreate: true },
|
|
10
|
-
{ alias: 'description', label: 'Description', type: 'richtext' },
|
|
11
|
-
{ alias: 'price', label: 'Price', type: 'number', numberOptions: { format: 'currency', currency: 'EUR', decimals: 2, min: 0, control: 'input', prefix: '€' } },
|
|
12
|
-
{ alias: 'inventory', label: 'Stock level', type: 'number', numberOptions: { min: 0, step: 1, control: 'stepper' } },
|
|
13
|
-
{ alias: 'rating', label: 'Rating', type: 'number', numberOptions: { min: 1, max: 5, step: 0.5, control: 'rating' } }
|
|
14
|
-
],
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/** Review: product review */
|
|
18
|
-
export const REVIEW_SEED: Seed = {
|
|
19
|
-
slug: 'reviews',
|
|
20
|
-
label: 'Review',
|
|
21
|
-
labelPlural: 'Reviews',
|
|
22
|
-
displayNameAlias: 'title',
|
|
23
|
-
allowPublicRead: true,
|
|
24
|
-
branches: [
|
|
25
|
-
{ alias: 'title', label: 'Review Title', type: 'text', requiredOnCreate: true },
|
|
26
|
-
{ alias: 'body', label: 'Body', type: 'text' },
|
|
27
|
-
{ alias: 'score', label: 'Score', type: 'number', numberOptions: { min: 1, max: 5, step: 1, control: 'rating' } }
|
|
28
|
-
],
|
|
29
|
-
}
|
package/bin/templates/contact.ts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Message: public contact form submission.
|
|
3
|
-
*
|
|
4
|
-
* allowPublicPost: true — the Public API accepts POST requests without authentication,
|
|
5
|
-
* so any frontend form can submit directly to this seed.
|
|
6
|
-
* email visibility:masked — the email is stored in full but returned partially
|
|
7
|
-
* redacted in API responses (e.g. "jo**@example.com").
|
|
8
|
-
* email public:false — the email is never exposed through the Public API.
|
|
9
|
-
*/
|
|
10
|
-
export const MESSAGE_SEED: Seed = {
|
|
11
|
-
slug: 'messages',
|
|
12
|
-
label: 'Message',
|
|
13
|
-
labelPlural: 'Messages',
|
|
14
|
-
displayNameAlias: 'name',
|
|
15
|
-
allowPublicPost: true,
|
|
16
|
-
branches: [
|
|
17
|
-
{ id: 'msg_01', alias: 'name', label: 'Name', type: 'text', requiredOnCreate: true },
|
|
18
|
-
{ id: 'msg_02', alias: 'email', label: 'Email', type: 'text', requiredOnCreate: true, policies: { visibility: 'masked', public: false } },
|
|
19
|
-
{ id: 'msg_03', alias: 'subject', label: 'Subject', type: 'text', requiredOnCreate: true },
|
|
20
|
-
{ id: 'msg_04', alias: 'message', label: 'Message', type: 'richtext', requiredOnCreate: true },
|
|
21
|
-
{ id: 'msg_05', alias: 'read', label: 'Read', type: 'boolean' },
|
|
22
|
-
],
|
|
23
|
-
}
|
package/bin/templates/empty.ts
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Your seeds go here.
|
|
3
|
-
*
|
|
4
|
-
* A Seed defines a content type: its slug (used in API URLs), its display labels,
|
|
5
|
-
* and its branches (fields).
|
|
6
|
-
*
|
|
7
|
-
* Each Branch has:
|
|
8
|
-
* id — immutable database key (e.g. 'abc_01'). Never change this after
|
|
9
|
-
* the first migration or stored data will break.
|
|
10
|
-
* alias — the name used in API payloads (e.g. 'title'). Safe to rename.
|
|
11
|
-
* label — human-readable label shown in the dashboard.
|
|
12
|
-
* type — 'text' | 'number' | 'boolean' | 'date' | 'richtext' | 'file' | 'json'
|
|
13
|
-
*
|
|
14
|
-
* Seed-level flags:
|
|
15
|
-
* allowPublicRead — expose entries via the unauthenticated Public API (GET).
|
|
16
|
-
* allowPublicPost — accept submissions via the Public API (POST). Useful for forms.
|
|
17
|
-
* allowDrafts — enable a pending-draft workflow for this content type.
|
|
18
|
-
* displayNameAlias — which branch alias is used as the entry title in the dashboard.
|
|
19
|
-
*
|
|
20
|
-
* Branch policies (all optional):
|
|
21
|
-
* policies.visibility — 'full' (default) | 'masked' | 'hidden'
|
|
22
|
-
* policies.public — false to exclude the field from Public API responses
|
|
23
|
-
* policies.search — false to exclude the field from full-text search
|
|
24
|
-
*
|
|
25
|
-
* Example — uncomment and adapt to define your first content type:
|
|
26
|
-
*
|
|
27
|
-
* export const PROJECT_SEED: Seed = {
|
|
28
|
-
* slug: 'projects',
|
|
29
|
-
* label: 'Project',
|
|
30
|
-
* labelPlural: 'Projects',
|
|
31
|
-
* displayNameAlias: 'title',
|
|
32
|
-
* allowPublicRead: true,
|
|
33
|
-
* branches: [
|
|
34
|
-
* { id: 'prj_01', alias: 'title', label: 'Title', type: 'text', requiredOnCreate: true },
|
|
35
|
-
* { id: 'prj_02', alias: 'description', label: 'Description', type: 'text' },
|
|
36
|
-
* { id: 'prj_03', alias: 'coverImage', label: 'Cover image', type: 'file' },
|
|
37
|
-
* { id: 'prj_04', alias: 'tags', label: 'Tags', type: 'json' },
|
|
38
|
-
* { id: 'prj_05', alias: 'published', label: 'Published', type: 'boolean' },
|
|
39
|
-
* ],
|
|
40
|
-
* }
|
|
41
|
-
*/
|
package/bin/templates/gallery.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
/** Gallery item: image or media asset with title, tags and featured flag */
|
|
2
|
-
export const GALLERY_SEED: Seed = {
|
|
3
|
-
slug: 'gallery',
|
|
4
|
-
label: 'Item',
|
|
5
|
-
labelPlural: 'Gallery',
|
|
6
|
-
displayNameAlias: 'title',
|
|
7
|
-
allowPublicRead: true,
|
|
8
|
-
branches: [
|
|
9
|
-
{ id: 'gal_01', alias: 'title', label: 'Title', type: 'text', requiredOnCreate: true },
|
|
10
|
-
{ id: 'gal_02', alias: 'description', label: 'Description', type: 'text' },
|
|
11
|
-
{ id: 'gal_03', alias: 'image', label: 'Image', type: 'file', requiredOnCreate: true, fileOptions: { accept: 'image' } },
|
|
12
|
-
{ id: 'gal_04', alias: 'tags', label: 'Tags', type: 'json' },
|
|
13
|
-
{ id: 'gal_05', alias: 'date', label: 'Date', type: 'date' },
|
|
14
|
-
{ id: 'gal_06', alias: 'featured', label: 'Featured', type: 'boolean' },
|
|
15
|
-
],
|
|
16
|
-
}
|
package/bin/templates/tasks.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
/** Task: project management tasks with progress slider */
|
|
2
|
-
export const TASK_SEED: Seed = {
|
|
3
|
-
slug: 'tasks',
|
|
4
|
-
label: 'Task',
|
|
5
|
-
labelPlural: 'Tasks',
|
|
6
|
-
displayNameAlias: 'title',
|
|
7
|
-
branches: [
|
|
8
|
-
{ alias: 'title', label: 'Task Title', type: 'text', requiredOnCreate: true },
|
|
9
|
-
{ alias: 'taskStatus', label: 'Status', type: 'text', options: ['todo', 'in_progress', 'done'] },
|
|
10
|
-
{ alias: 'completion', label: 'Completion', type: 'number', numberOptions: { format: 'percentage', min: 0, max: 100, step: 5, control: 'slider' } }
|
|
11
|
-
],
|
|
12
|
-
}
|