@beechcms/cli 0.6.0-preview.2 → 0.6.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 (57) hide show
  1. package/.turbo/turbo-build.log +5 -0
  2. package/.turbo/turbo-lint.log +1 -0
  3. package/coverage/coverage-summary.json +3 -0
  4. package/dist/index.js +563 -230
  5. package/package.json +12 -11
  6. package/src/commands/deploy.ts +126 -126
  7. package/src/commands/generate-types.ts +65 -0
  8. package/src/commands/init.ts +599 -599
  9. package/src/commands/onboard.ts +32 -32
  10. package/src/commands/reset.ts +157 -0
  11. package/src/commands/schema-diff.ts +78 -0
  12. package/src/commands/seed-create.ts +192 -192
  13. package/src/commands/seed-load.ts +206 -235
  14. package/src/commands/update.ts +54 -54
  15. package/src/commands/validate.ts +80 -80
  16. package/src/index.ts +24 -17
  17. package/src/lib/migration-writer.ts +106 -0
  18. package/src/lib/schema-diff.ts +175 -150
  19. package/src/lib/wrangler.ts +129 -129
  20. package/src/test/generate-types.test.ts +58 -0
  21. package/src/test/reset.test.ts +128 -0
  22. package/src/test/schema-diff.test.ts +232 -0
  23. package/src/test/seed-load.test.ts +158 -0
  24. package/src/test/validate.test.ts +261 -0
  25. package/tsconfig.json +16 -16
  26. package/tsconfig.tsbuildinfo +1 -1
  27. package/vitest.config.ts +33 -33
  28. package/coverage/base.css +0 -224
  29. package/coverage/block-navigation.js +0 -87
  30. package/coverage/favicon.png +0 -0
  31. package/coverage/index.html +0 -116
  32. package/coverage/lcov-report/base.css +0 -224
  33. package/coverage/lcov-report/block-navigation.js +0 -87
  34. package/coverage/lcov-report/favicon.png +0 -0
  35. package/coverage/lcov-report/index.html +0 -116
  36. package/coverage/lcov-report/prettify.css +0 -1
  37. package/coverage/lcov-report/prettify.js +0 -2
  38. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  39. package/coverage/lcov-report/sorter.js +0 -210
  40. package/coverage/lcov-report/validate.ts.html +0 -325
  41. package/coverage/lcov.info +0 -80
  42. package/coverage/prettify.css +0 -1
  43. package/coverage/prettify.js +0 -2
  44. package/coverage/sort-arrow-sprite.png +0 -0
  45. package/coverage/sorter.js +0 -210
  46. package/coverage/validate.ts.html +0 -325
  47. package/dist/commands/seed-load.d.ts +0 -8
  48. package/dist/commands/seed-load.d.ts.map +0 -1
  49. package/dist/commands/seed-load.js +0 -89
  50. package/dist/index.d.ts +0 -3
  51. package/dist/index.d.ts.map +0 -1
  52. package/dist/lib/schema-diff.d.ts +0 -15
  53. package/dist/lib/schema-diff.d.ts.map +0 -1
  54. package/dist/lib/schema-diff.js +0 -37
  55. package/dist/lib/wrangler.d.ts +0 -17
  56. package/dist/lib/wrangler.d.ts.map +0 -1
  57. package/dist/lib/wrangler.js +0 -65
@@ -1,32 +1,32 @@
1
- // SPDX-License-Identifier: MIT
2
- // Copyright (c) 2024–2026 Flavio De Musso
3
-
4
- import pc from 'picocolors'
5
- import type { Seed } from '@beechcms/core'
6
- import { init } from './init.js'
7
- import { seedLoad } from './seed-load.js'
8
-
9
- export interface OnboardOptions {
10
- local: boolean
11
- yes: boolean
12
- db?: string
13
- registry?: Record<string, Seed> | null
14
- }
15
-
16
- export async function onboard(args: OnboardOptions): Promise<void> {
17
- console.log(pc.cyan('\n beech onboard — full provisioning\n'))
18
-
19
- // Step 1: file check + DB init
20
- await init({ initDb: true, local: args.local, db: args.db, nonInteractive: args.yes })
21
-
22
- // Step 2: create content tables + register definitions + bump registry_version
23
- await seedLoad({ dryRun: false, diff: false, local: args.local, db: args.db, registry: args.registry ?? null })
24
-
25
- // Step 3: next steps
26
- console.log(pc.cyan('\n Provisioning complete.\n'))
27
- console.log(pc.dim(' Next steps:'))
28
- console.log(pc.cyan(' 1. npx wrangler dev'))
29
- console.log(pc.dim(' → start API + dashboard'))
30
- console.log(pc.dim(' 2. Open http://localhost:8789/admin'))
31
- console.log(pc.dim(' → complete setup wizard to create admin user\n'))
32
- }
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (c) 2024–2026 Flavio De Musso
3
+
4
+ import pc from 'picocolors'
5
+ import type { Seed } from '@beechcms/core'
6
+ import { init } from './init.js'
7
+ import { seedLoad } from './seed-load.js'
8
+
9
+ export interface OnboardOptions {
10
+ local: boolean
11
+ yes: boolean
12
+ db?: string
13
+ registry?: Record<string, Seed> | null
14
+ }
15
+
16
+ export async function onboard(args: OnboardOptions): Promise<void> {
17
+ console.log(pc.cyan('\n beech onboard — full provisioning\n'))
18
+
19
+ // Step 1: file check + DB init
20
+ await init({ initDb: true, local: args.local, db: args.db, nonInteractive: args.yes })
21
+
22
+ // Step 2: create content tables + register definitions + bump registry_version
23
+ await seedLoad({ dryRun: false, diff: false, local: args.local, db: args.db, registry: args.registry ?? null })
24
+
25
+ // Step 3: next steps
26
+ console.log(pc.cyan('\n Provisioning complete.\n'))
27
+ console.log(pc.dim(' Next steps:'))
28
+ console.log(pc.cyan(' 1. npx wrangler dev'))
29
+ console.log(pc.dim(' → start API + dashboard'))
30
+ console.log(pc.dim(' 2. Open http://localhost:8789/admin'))
31
+ console.log(pc.dim(' → complete setup wizard to create admin user\n'))
32
+ }
@@ -0,0 +1,157 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (c) 2024–2026 Flavio De Musso
3
+
4
+ import pc from 'picocolors'
5
+ import { spawnSync } from 'node:child_process'
6
+ import { existsSync, readFileSync, rmSync } from 'node:fs'
7
+ import { resolve } from 'node:path'
8
+ import { createInterface } from 'node:readline/promises'
9
+
10
+ export interface ResetOptions {
11
+ db?: boolean
12
+ docker?: boolean
13
+ all?: boolean
14
+ }
15
+
16
+ function isDockerInstalled(): boolean {
17
+ try {
18
+ const result = spawnSync('docker', ['--version'], { stdio: 'ignore', shell: true })
19
+ return result.status === 0
20
+ } catch {
21
+ return false
22
+ }
23
+ }
24
+
25
+ function isDockerRunning(): boolean {
26
+ try {
27
+ const result = spawnSync('docker', ['info'], { stdio: 'ignore', shell: true })
28
+ return result.status === 0
29
+ } catch {
30
+ return false
31
+ }
32
+ }
33
+
34
+ export async function reset(args: ResetOptions): Promise<void> {
35
+ console.log(pc.cyan('\n beech reset — cleanup environments\n'))
36
+
37
+ let resetDb = args.db || args.all
38
+ let resetDocker = args.docker || args.all
39
+
40
+ if (!args.db && !args.docker && !args.all) {
41
+ if (process.stdin.isTTY) {
42
+ const rl = createInterface({ input: process.stdin, output: process.stdout })
43
+ try {
44
+ const answer = (await rl.question(
45
+ pc.cyan(' → No options provided. Would you like to reset everything (DB & Docker)? (y/N): ')
46
+ )).trim().toLowerCase()
47
+ if (answer === 'y' || answer === 'yes') {
48
+ resetDb = true
49
+ resetDocker = true
50
+ } else {
51
+ console.log(pc.dim('\n Reset cancelled. Use --db, --docker, or --all.\n'))
52
+ return
53
+ }
54
+ } finally {
55
+ rl.close()
56
+ }
57
+ } else {
58
+ console.log(pc.red('\n ✗ Error: Please specify what to reset using --db, --docker, or --all.\n'))
59
+ process.exit(1)
60
+ }
61
+ }
62
+
63
+ const cwd = process.cwd()
64
+
65
+ // Reset Docker
66
+ if (resetDocker) {
67
+ if (!isDockerInstalled()) {
68
+ console.log(pc.red(' ✗ Docker is not installed or not found in your PATH.'))
69
+ console.log(pc.dim(' Please install Docker to reset Docker containers and volumes.\n'))
70
+ if (!args.all) {
71
+ process.exit(1)
72
+ }
73
+ } else if (!isDockerRunning()) {
74
+ console.log(pc.yellow(' ⚠ Docker is installed, but the Docker daemon is NOT running.'))
75
+ console.log(pc.dim(' Please start Docker Desktop or your Docker daemon to reset containers.\n'))
76
+ if (!args.all) {
77
+ process.exit(1)
78
+ }
79
+ } else {
80
+ console.log(pc.dim(' Resetting Docker containers and volumes…\n'))
81
+ const result = spawnSync('docker', ['compose', 'down', '-v'], {
82
+ stdio: 'inherit',
83
+ cwd,
84
+ shell: true,
85
+ })
86
+
87
+ if (result.status === 0) {
88
+ console.log(pc.green('\n ✓ Docker containers stopped and volumes removed.'))
89
+ } else {
90
+ console.log(pc.red('\n ✗ Docker reset failed.'))
91
+ }
92
+ }
93
+ }
94
+
95
+ // Reset DB
96
+ if (resetDb) {
97
+ console.log(pc.dim('\n Resetting local database…\n'))
98
+ let dbResetSuccess = false
99
+ const apiDir = resolve(cwd, 'apps', 'api')
100
+
101
+ if (existsSync(resolve(apiDir, 'package.json'))) {
102
+ const result = spawnSync('npm', ['run', 'db:reset:local'], {
103
+ stdio: 'inherit',
104
+ cwd: apiDir,
105
+ shell: true,
106
+ })
107
+ dbResetSuccess = result.status === 0
108
+ } else if (existsSync(resolve(cwd, 'package.json'))) {
109
+ const pkg = JSON.parse(readFileSync(resolve(cwd, 'package.json'), 'utf-8'))
110
+ if (pkg.scripts?.['db:reset:local']) {
111
+ const result = spawnSync('npm', ['run', 'db:reset:local'], {
112
+ stdio: 'inherit',
113
+ cwd,
114
+ shell: true,
115
+ })
116
+ dbResetSuccess = result.status === 0
117
+ } else {
118
+ const wranglerStateDir = resolve(cwd, '.wrangler/state')
119
+ if (existsSync(wranglerStateDir)) {
120
+ console.log(pc.dim(' Removing .wrangler/state…'))
121
+ rmSync(wranglerStateDir, { recursive: true, force: true })
122
+ }
123
+ if (existsSync(resolve(cwd, 'scripts', 'bootstrap-d1.mjs'))) {
124
+ const result = spawnSync('node', ['scripts/bootstrap-d1.mjs'], {
125
+ stdio: 'inherit',
126
+ cwd,
127
+ shell: true,
128
+ })
129
+ dbResetSuccess = result.status === 0
130
+ } else {
131
+ console.log(pc.yellow(' ⚠ Could not find database reset script.'))
132
+ const initResult = spawnSync('npx', ['beech', 'init', '--db', '--local'], {
133
+ stdio: 'inherit',
134
+ cwd,
135
+ shell: true,
136
+ })
137
+ dbResetSuccess = initResult.status === 0
138
+ }
139
+ }
140
+ } else {
141
+ const wranglerStateDir = resolve(cwd, '.wrangler/state')
142
+ if (existsSync(wranglerStateDir)) {
143
+ console.log(pc.dim(' Removing .wrangler/state…'))
144
+ rmSync(wranglerStateDir, { recursive: true, force: true })
145
+ }
146
+ dbResetSuccess = true
147
+ }
148
+
149
+ if (dbResetSuccess) {
150
+ console.log(pc.green('\n ✓ Local database reset completed.'))
151
+ } else {
152
+ console.log(pc.red('\n ✗ Database reset failed.'))
153
+ }
154
+ }
155
+
156
+ console.log(pc.dim('\n Reset process finished.\n'))
157
+ }
@@ -0,0 +1,78 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Copyright (c) 2024–2026 Flavio De Musso
3
+
4
+ import pc from 'picocolors'
5
+ import { resolve } from 'node:path'
6
+ import { SEED_REGISTRY, sortSeedsByDependencies, type Seed } from '@beechcms/core'
7
+ import { findWranglerConfig, resolveDbName, type WranglerOptions } from '../lib/wrangler.js'
8
+ import { diffSeed, renderSeedDiff, isSeedClean } from '../lib/schema-diff.js'
9
+ import { nextMigrationIndex, buildMigrationSql, writeMigrationFile } from '../lib/migration-writer.js'
10
+
11
+ export interface SchemaDiffOptions {
12
+ /** Compare against remote D1 (default: local). */
13
+ local: boolean
14
+ /** When set, write an additive migration file instead of just printing. */
15
+ write: boolean
16
+ /** Optional migration name (used in the filename). */
17
+ name?: string
18
+ /** Override migrations dir (default: <cwd>/apps/api/migrations). */
19
+ migrationsDir?: string
20
+ /** Override D1 database name. */
21
+ db?: string
22
+ registry?: Record<string, Seed> | null
23
+ }
24
+
25
+ function resolveMigrationsDir(override?: string): string {
26
+ if (override) return resolve(process.cwd(), override)
27
+ return resolve(process.cwd(), 'apps', 'api', 'migrations')
28
+ }
29
+
30
+ export async function schemaDiff(args: SchemaDiffOptions): Promise<void> {
31
+ const registry = args.registry ?? SEED_REGISTRY
32
+ if (Object.keys(registry).length === 0) {
33
+ console.log(pc.yellow('\n ✗ No seeds found — nothing to diff.\n'))
34
+ return
35
+ }
36
+
37
+ const configPath = findWranglerConfig()
38
+ const options: WranglerOptions = { db: args.db ?? resolveDbName(configPath), local: args.local, configPath }
39
+ const seeds = sortSeedsByDependencies(Object.values(registry))
40
+
41
+ console.log(pc.cyan(`\n Diffing schema vs ${args.local ? 'local' : 'remote'} D1 (${options.db})…\n`))
42
+ const diffs = []
43
+ let clean = true
44
+ for (const seed of seeds) {
45
+ const d = await diffSeed(seed, options)
46
+ diffs.push(d)
47
+ renderSeedDiff(d)
48
+ if (!isSeedClean(d)) clean = false
49
+ }
50
+
51
+ if (clean) { console.log(pc.green('\n Schema matches seeds. No migration needed.\n')); return }
52
+
53
+ const plan = buildMigrationSql(diffs, registry)
54
+ if (!args.write) {
55
+ console.log(pc.dim('\n -- proposed additive migration (preview):\n'))
56
+ console.log(plan.sql)
57
+ if (plan.destructiveSlugs.length) {
58
+ console.log(pc.yellow(`\n ⚠ Destructive drift in: ${plan.destructiveSlugs.join(', ')} — not auto-migrated.`))
59
+ }
60
+ console.log(pc.cyan('\n → Re-run with --write to save the migration file.\n'))
61
+ return
62
+ }
63
+
64
+ if (plan.additiveCount === 0) {
65
+ console.log(pc.yellow('\n ⚠ Only destructive drift detected — no additive migration written.'))
66
+ console.log(pc.dim(' Author a reviewed migration by hand for renames/drops/type changes.\n'))
67
+ return
68
+ }
69
+
70
+ const dir = resolveMigrationsDir(args.migrationsDir)
71
+ const index = nextMigrationIndex(dir)
72
+ const file = writeMigrationFile(dir, index, args.name ?? 'schema_sync', plan.sql)
73
+ console.log(pc.green(`\n ✓ Wrote ${file} (${plan.additiveCount} statement(s)).`))
74
+ console.log(pc.dim(' Review, commit, then `wrangler d1 migrations apply --remote` in CI.\n'))
75
+ if (plan.destructiveSlugs.length) {
76
+ console.log(pc.yellow(` ⚠ Destructive drift in ${plan.destructiveSlugs.join(', ')} was NOT included.\n`))
77
+ }
78
+ }