@beechcms/cms 0.6.0-preview.4 → 0.6.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 CHANGED
@@ -1,270 +1,335 @@
1
- #!/usr/bin/env node
2
- // @ts-check
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
-
9
-
10
- const [,, command, ...args] = process.argv
11
-
12
- const COMMANDS = {
13
- build: cmdBuild,
14
- 'seed:load': cmdSeedLoad,
15
- 'seed:create': cmdSeedCreate,
16
- 'schema:diff': cmdSchemaDiff,
17
- 'init': cmdInit,
18
- 'validate': cmdValidate,
19
- 'deploy': cmdDeploy,
20
- 'update': cmdUpdate,
21
- 'onboard': cmdOnboard,
22
- 'reset': cmdReset,
23
- 'generate:types': cmdGenerateTypes,
24
- }
25
-
26
- function help() {
27
- console.log(`
28
- beech <command> [options]
29
-
30
- Commands:
31
- init Check project files and optionally initialise the database
32
- --db Also initialise the D1 database (system tables)
33
- --remote Target remote D1 instead of local (default: local)
34
- --db-name <n> Override D1 database name
35
-
36
- build Rebuild @beechcms/core after editing seeds.ts
37
-
38
- validate Validate SEED_REGISTRY for common errors (duplicate aliases,
39
- missing displayNameAlias, duplicate slugs). Exit code 1 on errors.
40
-
41
- seed:load Create/update content tables from SEED_REGISTRY
42
- --dry-run Print SQL without executing
43
- --diff Show schema differences vs current DB
44
- --remote Execute against remote D1 (default: local)
45
- --db <name> Override D1 database name
46
-
47
- seed:create Interactive wizard generate a new Seed definition and append
48
- it to seeds.ts, including SEED_REGISTRY entry
49
-
50
- schema:diff Diff SEED_REGISTRY vs the live D1 schema and generate an
51
- additive SQL migration in apps/api/migrations/
52
- --write Write the migration file (default: preview only)
53
- --name <name> Migration name used in the filename
54
- --remote Diff against remote D1 (default: local)
55
- --db <name> Override D1 database name
56
-
57
- deploy Deploy Worker, sync remote schema, and verify /admin
58
- --skip-seed Skip remote seed:load step
59
- --skip-check Skip /admin reachability check
60
-
61
- update Update @beechcms/api and @beechcms/core to latest, then
62
- apply any new system migrations to the local database
63
-
64
- onboard One-command local provisioning (init + seed:load). Designed
65
- for non-interactive use by agents and CI.
66
- --remote Target remote D1 instead of local (default: local)
67
- --yes Skip all interactive prompts (non-interactive mode)
68
- --db <name> Override D1 database name
69
-
70
- reset Reset database and/or Docker containers/volumes
71
- --db Wipe local Wrangler state & bootstrap D1 DB
72
- --docker Down Docker containers and wipe volumes
73
- --all Reset both (database & docker)
74
-
75
- generate:types Generate TypeScript interfaces from the Seed registry
76
- --out <path> Output file (default: src/types/beech.ts)
77
- --local Read from seeds.ts instead of querying remote D1
78
- --db <name> Override D1 database name (remote mode)
79
-
80
- Scaffold a new project (interactive, or pass --yes for non-interactive defaults):
81
- npm create @beechcms/cms [project-name] [--yes] [--with-examples]
82
-
83
- Golden path (local):
84
- npx beech onboard --local --yes # fully automated
85
- # or step by step:
86
- npx beech init --db --local
87
- npx beech seed:load --local
88
- npx wrangler dev
89
-
90
- Golden path (deploy):
91
- npx beech deploy
92
- npx beech init --db --remote # verify remote DB post-deploy
93
- `)
94
- }
95
-
96
- function cmdBuild() {
97
- console.log(
98
- '\nNo build step needed for BeechCMS projects.\n' +
99
- 'Edit seeds.ts then run `npx beech seed:load` to sync schema changes to D1.\n'
100
- )
101
- }
102
-
103
- async function tryLoadLocalRegistry() {
104
- const cwd = process.cwd()
105
-
106
- // Try compiled JS first (root or apps/api)
107
- const searchDirs = [cwd, resolve(cwd, 'apps', 'api')]
108
- for (const dir of searchDirs) {
109
- for (const name of ['seeds.js', 'seeds.mjs', 'seed.js', 'seed.mjs']) {
110
- const p = resolve(dir, name)
111
- if (existsSync(p)) {
112
- try {
113
- const mod = await import(pathToFileURL(p).href)
114
- if (mod.SEED_REGISTRY && typeof mod.SEED_REGISTRY === 'object') {
115
- return mod.SEED_REGISTRY
116
- }
117
- if (Array.isArray(mod.seeds)) {
118
- return Object.fromEntries(mod.seeds.map(s => [s.slug, s]))
119
- }
120
- if (Array.isArray(mod.default)) {
121
- return Object.fromEntries(mod.default.map(s => [s.slug, s]))
122
- }
123
- } catch {}
124
- }
125
- }
126
- }
127
-
128
- // Try seeds.ts / seed.ts (root or apps/api)
129
- let tsPath = null
130
- for (const dir of searchDirs) {
131
- const p = existsSync(resolve(dir, 'seeds.ts'))
132
- ? resolve(dir, 'seeds.ts')
133
- : resolve(dir, 'seed.ts')
134
- if (existsSync(p)) {
135
- tsPath = p
136
- break
137
- }
138
- }
139
-
140
- if (tsPath) {
141
- const result = spawnSync(process.execPath, [
142
- '--experimental-strip-types',
143
- '--input-type=module',
144
- '--eval',
145
- `
146
- import * as mod from ${JSON.stringify(pathToFileURL(tsPath).href)};
147
- let out = null;
148
- if (mod.SEED_REGISTRY && typeof mod.SEED_REGISTRY === 'object') out = mod.SEED_REGISTRY;
149
- else if (Array.isArray(mod.seeds)) out = Object.fromEntries(mod.seeds.map(s => [s.slug, s]));
150
- else if (Array.isArray(mod.default)) out = Object.fromEntries(mod.default.map(s => [s.slug, s]));
151
- if (out) process.stdout.write(JSON.stringify(out));
152
- `.trim(),
153
- ], { encoding: 'utf-8' })
154
-
155
- if (result.status === 0 && result.stdout) {
156
- try { return JSON.parse(result.stdout) } catch (err) {
157
- console.error(' Failed to parse seeds.ts output:', err)
158
- }
159
- } else if (result.status !== 0) {
160
- console.error(' Error loading seeds.ts:')
161
- console.error(result.stderr || result.stdout || 'Unknown error')
162
- if (process.version.slice(1).split('.')[0] < 22) {
163
- console.warn(' Note: Node.js 22.6+ is required to load .ts files directly. Current version:', process.version)
164
- }
165
- }
166
- }
167
-
168
- return null
169
- }
170
-
171
- async function cmdInit(args) {
172
- const initDb = args.includes('--db')
173
- const remote = args.includes('--remote')
174
- const dbIdx = args.indexOf('--db-name')
175
- const db = dbIdx !== -1 ? args[dbIdx + 1] : undefined
176
-
177
- const { init } = await import('@beechcms/cli')
178
- await init({ initDb, local: !remote, db })
179
- }
180
-
181
- async function cmdSeedLoad(args) {
182
- const dryRun = args.includes('--dry-run')
183
- const diff = args.includes('--diff')
184
- const remote = args.includes('--remote')
185
- const dbIdx = args.indexOf('--db')
186
- const db = dbIdx !== -1 ? args[dbIdx + 1] : undefined
187
-
188
- const registry = await tryLoadLocalRegistry()
189
-
190
- const { seedLoad } = await import('@beechcms/cli')
191
- await seedLoad({ dryRun, diff, local: !remote, db, registry })
192
- }
193
-
194
- async function cmdValidate(args) {
195
- const registry = await tryLoadLocalRegistry()
196
- const { validate } = await import('@beechcms/cli')
197
- await validate({ registry })
198
- }
199
-
200
- async function cmdSeedCreate(_args) {
201
- const { seedCreate } = await import('@beechcms/cli')
202
- await seedCreate({})
203
- }
204
-
205
- async function cmdDeploy(args) {
206
- const skipSeed = args.includes('--skip-seed')
207
- const skipCheck = args.includes('--skip-check')
208
- const registry = skipSeed ? null : await tryLoadLocalRegistry()
209
- const { deploy } = await import('@beechcms/cli')
210
- await deploy({ registry, skipSeed, skipCheck })
211
- }
212
-
213
- async function cmdUpdate(_args) {
214
- const { update } = await import('@beechcms/cli')
215
- await update({})
216
- }
217
-
218
- async function cmdOnboard(args) {
219
- const local = !args.includes('--remote')
220
- const yes = args.includes('--yes')
221
- const dbIdx = args.indexOf('--db')
222
- const db = dbIdx !== -1 ? args[dbIdx + 1] : undefined
223
- const registry = await tryLoadLocalRegistry()
224
- const { onboard } = await import('@beechcms/cli')
225
- await onboard({ local, yes, db, registry })
226
- }
227
-
228
- async function cmdReset(args) {
229
- const db = args.includes('--db')
230
- const docker = args.includes('--docker')
231
- const all = args.includes('--all')
232
-
233
- const { reset } = await import('@beechcms/cli')
234
- await reset({ db, docker, all })
235
- }
236
-
237
- async function cmdSchemaDiff(args) {
238
- const remote = args.includes('--remote')
239
- const write = args.includes('--write')
240
- const nameIdx = args.indexOf('--name')
241
- const name = nameIdx !== -1 ? args[nameIdx + 1] : undefined
242
- const dbIdx = args.indexOf('--db')
243
- const db = dbIdx !== -1 ? args[dbIdx + 1] : undefined
244
- const registry = await tryLoadLocalRegistry()
245
- const { schemaDiff } = await import('@beechcms/cli')
246
- await schemaDiff({ local: !remote, write, name, db, registry })
247
- }
248
-
249
- async function cmdGenerateTypes(args) {
250
- const outIdx = args.indexOf('--out')
251
- const out = outIdx !== -1 ? args[outIdx + 1] : 'src/types/beech.ts'
252
- const local = args.includes('--local')
253
- const dbIdx = args.indexOf('--db')
254
- const db = dbIdx !== -1 ? args[dbIdx + 1] : undefined
255
-
256
- const registry = local ? await tryLoadLocalRegistry() : null
257
-
258
- const { generateTypes } = await import('@beechcms/cli')
259
- await generateTypes({ out, local, db, registry })
260
- }
261
-
262
- const handler = COMMANDS[command]
263
- if (!handler) {
264
- help()
265
- if (command) process.exit(1)
266
- } else if (args.includes('--help') || args.includes('-h')) {
267
- help()
268
- } else {
269
- await handler(args)
270
- }
1
+ #!/usr/bin/env node
2
+ // @ts-check
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
+ import pc from 'picocolors'
9
+
10
+ const [,, command, ...args] = process.argv
11
+
12
+ const COMMANDS = {
13
+ build: cmdBuild,
14
+ 'seed:load': cmdSeedLoad,
15
+ 'seed:create': cmdSeedCreate,
16
+ 'schema:diff': cmdSchemaDiff,
17
+ 'init': cmdInit,
18
+ 'validate': cmdValidate,
19
+ 'deploy': cmdDeploy,
20
+ 'update': cmdUpdate,
21
+ 'onboard': cmdOnboard,
22
+ 'reset': cmdReset,
23
+ 'generate:types': cmdGenerateTypes,
24
+ // New unified command mappings:
25
+ 'db:migrate': cmdDbMigrate,
26
+ 'db:reset': cmdDbReset,
27
+ 'dev': cmdDev,
28
+ 'start': cmdDev,
29
+ 'dev:stop': cmdDevStop,
30
+ 'dev:reset': cmdDevReset,
31
+ 'dev:tunnel': cmdDevTunnel,
32
+ 'mailpit:clear': cmdMailpitClear,
33
+ 'logs': cmdLogs,
34
+ 'test': cmdTest,
35
+ 'lint': cmdLint,
36
+ 'doctor': cmdDoctor,
37
+ }
38
+
39
+ function help() {
40
+ console.log(`
41
+ ${pc.cyan('beech')} <command> [options]
42
+
43
+ ${pc.bold('1. Local Management & Onboarding')}
44
+ ${pc.cyan('init')} Check project files and optionally initialise the database
45
+ --db Also initialise the D1 database (system tables)
46
+ --remote Target remote D1 instead of local (default: local)
47
+ --db-name <n> Override D1 database name
48
+ --yes, -y Run in non-interactive mode
49
+ ${pc.cyan('onboard')} One-command local provisioning (init --db + seed:load)
50
+ --remote Target remote D1 instead of local (default: local)
51
+ --yes, -y Skip all interactive prompts (non-interactive mode)
52
+ --db <name> Override D1 database name
53
+ ${pc.cyan('update')} Update internals to latest, then apply system D1 migrations
54
+
55
+ ${pc.bold('2. Database & Migrations')}
56
+ ${pc.cyan('db:migrate')} Apply all pending local migrations
57
+ ${pc.cyan('db:reset')} Remove local Wrangler state and re-bootstrap database
58
+
59
+ ${pc.bold('3. Seed & Schema Management')}
60
+ ${pc.cyan('seed:create')} Interactive wizard — generate a new Seed schema in seeds.ts
61
+ ${pc.cyan('seed:load')} Create/update content tables from SEED_REGISTRY
62
+ --dry-run Print SQL without executing
63
+ --diff Show schema differences vs current DB
64
+ --remote Execute against remote D1 (default: local)
65
+ --db <name> Override D1 database name
66
+ ${pc.cyan('schema:diff')} Diff SEED_REGISTRY vs D1 and generate additive SQL migration
67
+ --write Write the migration file (default: preview only)
68
+ --name <name> Migration name used in the filename
69
+ --remote Diff against remote D1 (default: local)
70
+ --db <name> Override D1 database name
71
+ ${pc.cyan('validate')} Validate seeds registry for errors
72
+ ${pc.cyan('generate:types')} Generate TypeScript interfaces from seed definitions
73
+ --out <path> Output file (default: src/types/beech.ts)
74
+ --local Read from seeds.ts instead of querying live D1
75
+
76
+ ${pc.bold('4. Local Stack & Docker')}
77
+ ${pc.cyan('dev / start')} Start the local dev environment (Docker + API + Dashboard)
78
+ --plain Avoid Ink visual TUI and run clean log streaming
79
+ ${pc.cyan('dev:stop')} Stop Docker containers without wiping data
80
+ ${pc.cyan('dev:reset')} Stop Docker containers and remove all persistent volumes
81
+ ${pc.cyan('dev:tunnel')} Display Cloudflare tunnel public testing URL
82
+ ${pc.cyan('mailpit:clear')} Clear local test inbox in Mailpit
83
+
84
+ ${pc.bold('5. Logs Streaming')}
85
+ ${pc.cyan('logs <service>')} Show streaming logs for docker service: mailpit, db, tunnel, storage
86
+
87
+ ${pc.bold('6. Quality & Deployment')}
88
+ ${pc.cyan('test')} Run the test suite via Turborepo / Vitest
89
+ --coverage Generate coverage reports
90
+ --diff Run test coverage only for files modified on the branch
91
+ ${pc.cyan('lint')} Run ESLint quality checks
92
+ ${pc.cyan('deploy')} Compile, test, deploy to Cloudflare environment
93
+ --skip-seed Skip remote seed:load step
94
+ --skip-check Skip /admin reachability check
95
+ ${pc.cyan('doctor')} Execute React diagnostics check on Dashboard
96
+ `)
97
+ }
98
+
99
+ function cmdBuild() {
100
+ console.log(
101
+ '\nNo build step needed for BeechCMS projects.\n' +
102
+ 'Edit seeds.ts then run `npx beech seed:load` to sync schema changes to D1.\n'
103
+ )
104
+ }
105
+
106
+ async function tryLoadLocalRegistry() {
107
+ const cwd = process.cwd()
108
+
109
+ // Try compiled JS first (root or apps/api)
110
+ const searchDirs = [cwd, resolve(cwd, 'apps', 'api')]
111
+ for (const dir of searchDirs) {
112
+ for (const name of ['seeds.js', 'seeds.mjs', 'seed.js', 'seed.mjs']) {
113
+ const p = resolve(dir, name)
114
+ if (existsSync(p)) {
115
+ try {
116
+ const mod = await import(pathToFileURL(p).href)
117
+ if (mod.SEED_REGISTRY && typeof mod.SEED_REGISTRY === 'object') {
118
+ return mod.SEED_REGISTRY
119
+ }
120
+ if (Array.isArray(mod.seeds)) {
121
+ return Object.fromEntries(mod.seeds.map(s => [s.slug, s]))
122
+ }
123
+ if (Array.isArray(mod.default)) {
124
+ return Object.fromEntries(mod.default.map(s => [s.slug, s]))
125
+ }
126
+ } catch {}
127
+ }
128
+ }
129
+ }
130
+
131
+ // Try seeds.ts / seed.ts (root or apps/api)
132
+ let tsPath = null
133
+ for (const dir of searchDirs) {
134
+ const p = existsSync(resolve(dir, 'seeds.ts'))
135
+ ? resolve(dir, 'seeds.ts')
136
+ : resolve(dir, 'seed.ts')
137
+ if (existsSync(p)) {
138
+ tsPath = p
139
+ break
140
+ }
141
+ }
142
+
143
+ if (tsPath) {
144
+ const result = spawnSync(process.execPath, [
145
+ '--experimental-strip-types',
146
+ '--input-type=module',
147
+ '--eval',
148
+ `
149
+ import * as mod from ${JSON.stringify(pathToFileURL(tsPath).href)};
150
+ let out = null;
151
+ if (mod.SEED_REGISTRY && typeof mod.SEED_REGISTRY === 'object') out = mod.SEED_REGISTRY;
152
+ else if (Array.isArray(mod.seeds)) out = Object.fromEntries(mod.seeds.map(s => [s.slug, s]));
153
+ else if (Array.isArray(mod.default)) out = Object.fromEntries(mod.default.map(s => [s.slug, s]));
154
+ if (out) process.stdout.write(JSON.stringify(out));
155
+ `.trim(),
156
+ ], { encoding: 'utf-8' })
157
+
158
+ if (result.status === 0 && result.stdout) {
159
+ try { return JSON.parse(result.stdout) } catch (err) {
160
+ console.error(' Failed to parse seeds.ts output:', err)
161
+ }
162
+ } else if (result.status !== 0) {
163
+ console.error(' Error loading seeds.ts:')
164
+ console.error(result.stderr || result.stdout || 'Unknown error')
165
+ if (process.version.slice(1).split('.')[0] < 22) {
166
+ console.warn(' Note: Node.js 22.6+ is required to load .ts files directly. Current version:', process.version)
167
+ }
168
+ }
169
+ }
170
+
171
+ return null
172
+ }
173
+
174
+ async function cmdInit(args) {
175
+ const initDb = args.includes('--db')
176
+ const remote = args.includes('--remote')
177
+ const dbIdx = args.indexOf('--db-name')
178
+ const db = dbIdx !== -1 ? args[dbIdx + 1] : undefined
179
+ const yes = args.includes('--yes') || args.includes('-y')
180
+
181
+ const { init } = await import('@beechcms/cli')
182
+ await init({ initDb, local: !remote, db, yes })
183
+ }
184
+
185
+ async function cmdSeedLoad(args) {
186
+ const dryRun = args.includes('--dry-run')
187
+ const diff = args.includes('--diff')
188
+ const remote = args.includes('--remote')
189
+ const dbIdx = args.indexOf('--db')
190
+ const db = dbIdx !== -1 ? args[dbIdx + 1] : undefined
191
+
192
+ const registry = await tryLoadLocalRegistry()
193
+
194
+ const { seedLoad } = await import('@beechcms/cli')
195
+ await seedLoad({ dryRun, diff, local: !remote, db, registry })
196
+ }
197
+
198
+ async function cmdValidate(args) {
199
+ const registry = await tryLoadLocalRegistry()
200
+ const { validate } = await import('@beechcms/cli')
201
+ await validate({ registry })
202
+ }
203
+
204
+ async function cmdSeedCreate(_args) {
205
+ const { seedCreate } = await import('@beechcms/cli')
206
+ await seedCreate({})
207
+ }
208
+
209
+ async function cmdDeploy(args) {
210
+ const skipSeed = args.includes('--skip-seed')
211
+ const skipCheck = args.includes('--skip-check')
212
+ const registry = skipSeed ? null : await tryLoadLocalRegistry()
213
+ const { deploy } = await import('@beechcms/cli')
214
+ await deploy({ registry, skipSeed, skipCheck })
215
+ }
216
+
217
+ async function cmdUpdate(_args) {
218
+ const { update } = await import('@beechcms/cli')
219
+ await update({})
220
+ }
221
+
222
+ async function cmdOnboard(args) {
223
+ const local = !args.includes('--remote')
224
+ const yes = args.includes('--yes') || args.includes('-y')
225
+ const dbIdx = args.indexOf('--db')
226
+ const db = dbIdx !== -1 ? args[dbIdx + 1] : undefined
227
+ const registry = await tryLoadLocalRegistry()
228
+ const { onboard } = await import('@beechcms/cli')
229
+ await onboard({ local, yes, db, registry })
230
+ }
231
+
232
+ async function cmdReset(args) {
233
+ const db = args.includes('--db')
234
+ const docker = args.includes('--docker')
235
+ const all = args.includes('--all')
236
+ const yes = args.includes('--yes') || args.includes('-y')
237
+
238
+ const { reset } = await import('@beechcms/cli')
239
+ await reset({ db, docker, all, yes })
240
+ }
241
+
242
+ async function cmdSchemaDiff(args) {
243
+ const remote = args.includes('--remote')
244
+ const write = args.includes('--write')
245
+ const nameIdx = args.indexOf('--name')
246
+ const name = nameIdx !== -1 ? args[nameIdx + 1] : undefined
247
+ const dbIdx = args.indexOf('--db')
248
+ const db = dbIdx !== -1 ? args[dbIdx + 1] : undefined
249
+ const registry = await tryLoadLocalRegistry()
250
+ const { schemaDiff } = await import('@beechcms/cli')
251
+ await schemaDiff({ local: !remote, write, name, db, registry })
252
+ }
253
+
254
+ async function cmdGenerateTypes(args) {
255
+ const outIdx = args.indexOf('--out')
256
+ const out = outIdx !== -1 ? args[outIdx + 1] : 'src/types/beech.ts'
257
+ const local = args.includes('--local')
258
+ const dbIdx = args.indexOf('--db')
259
+ const db = dbIdx !== -1 ? args[dbIdx + 1] : undefined
260
+
261
+ const registry = local ? await tryLoadLocalRegistry() : null
262
+
263
+ const { generateTypes } = await import('@beechcms/cli')
264
+ await generateTypes({ out, local, db, registry })
265
+ }
266
+
267
+ // New unified command wrappers:
268
+ async function cmdDbMigrate(args) {
269
+ const { dbMigrate } = await import('@beechcms/cli')
270
+ await dbMigrate({})
271
+ }
272
+
273
+ async function cmdDbReset(args) {
274
+ const { dbReset } = await import('@beechcms/cli')
275
+ await dbReset({})
276
+ }
277
+
278
+ async function cmdDev(args) {
279
+ const plain = args.includes('--plain')
280
+ const { dev } = await import('@beechcms/cli')
281
+ await dev({ plain })
282
+ }
283
+
284
+ async function cmdDevStop(args) {
285
+ const { devStop } = await import('@beechcms/cli')
286
+ await devStop()
287
+ }
288
+
289
+ async function cmdDevReset(args) {
290
+ const { devReset } = await import('@beechcms/cli')
291
+ await devReset()
292
+ }
293
+
294
+ async function cmdDevTunnel(args) {
295
+ const { devTunnel } = await import('@beechcms/cli')
296
+ await devTunnel()
297
+ }
298
+
299
+ async function cmdMailpitClear(args) {
300
+ const { mailpitClear } = await import('@beechcms/cli')
301
+ await mailpitClear()
302
+ }
303
+
304
+ async function cmdLogs(args) {
305
+ const service = args[0]
306
+ const { logs } = await import('@beechcms/cli')
307
+ await logs({ service })
308
+ }
309
+
310
+ async function cmdTest(args) {
311
+ const coverage = args.includes('--coverage')
312
+ const diff = args.includes('--diff')
313
+ const { test } = await import('@beechcms/cli')
314
+ await test({ coverage, diff })
315
+ }
316
+
317
+ async function cmdLint(args) {
318
+ const { lint } = await import('@beechcms/cli')
319
+ await lint()
320
+ }
321
+
322
+ async function cmdDoctor(args) {
323
+ const { doctor } = await import('@beechcms/cli')
324
+ await doctor()
325
+ }
326
+
327
+ const handler = COMMANDS[command]
328
+ if (!handler) {
329
+ help()
330
+ if (command) process.exit(1)
331
+ } else if (args.includes('--help') || args.includes('-h')) {
332
+ help()
333
+ } else {
334
+ await handler(args)
335
+ }