create-sonicjs 2.0.0 → 2.0.6
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/README.md +15 -15
- package/package.json +4 -4
- package/src/cli.js +373 -17
- package/templates/starter/package.json +13 -12
package/README.md
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
# create-sonicjs
|
|
1
|
+
# create-sonicjs
|
|
2
2
|
|
|
3
3
|
> The easiest way to create a new SonicJS application
|
|
4
4
|
|
|
5
|
-
[](https://www.npmjs.com/package/create-sonicjs)
|
|
6
|
+
[](./LICENSE)
|
|
7
7
|
|
|
8
8
|
## Quick Start
|
|
9
9
|
|
|
10
10
|
```bash
|
|
11
|
-
npx create-sonicjs
|
|
11
|
+
npx create-sonicjs my-app
|
|
12
12
|
```
|
|
13
13
|
|
|
14
14
|
That's it! Follow the interactive prompts and you'll have a running SonicJS application in minutes.
|
|
15
15
|
|
|
16
16
|
## What It Does
|
|
17
17
|
|
|
18
|
-
`create-sonicjs
|
|
18
|
+
`create-sonicjs` sets up everything you need for a modern headless CMS on Cloudflare's edge:
|
|
19
19
|
|
|
20
20
|
- ✅ **Project scaffolding** - Complete project structure
|
|
21
21
|
- ✅ **Template selection** - Choose from pre-built templates
|
|
@@ -30,7 +30,7 @@ That's it! Follow the interactive prompts and you'll have a running SonicJS appl
|
|
|
30
30
|
### Interactive Mode (Recommended)
|
|
31
31
|
|
|
32
32
|
```bash
|
|
33
|
-
npx create-sonicjs
|
|
33
|
+
npx create-sonicjs
|
|
34
34
|
```
|
|
35
35
|
|
|
36
36
|
You'll be prompted for:
|
|
@@ -45,13 +45,13 @@ You'll be prompted for:
|
|
|
45
45
|
### With Project Name
|
|
46
46
|
|
|
47
47
|
```bash
|
|
48
|
-
npx create-sonicjs
|
|
48
|
+
npx create-sonicjs my-blog
|
|
49
49
|
```
|
|
50
50
|
|
|
51
51
|
### Command Line Options
|
|
52
52
|
|
|
53
53
|
```bash
|
|
54
|
-
npx create-sonicjs
|
|
54
|
+
npx create-sonicjs my-app --template=starter --skip-install
|
|
55
55
|
```
|
|
56
56
|
|
|
57
57
|
**Available flags:**
|
|
@@ -144,13 +144,13 @@ Works with all major package managers:
|
|
|
144
144
|
|
|
145
145
|
```bash
|
|
146
146
|
# npm
|
|
147
|
-
npx create-sonicjs
|
|
147
|
+
npx create-sonicjs my-app
|
|
148
148
|
|
|
149
149
|
# yarn
|
|
150
|
-
yarn create sonicjs
|
|
150
|
+
yarn create sonicjs my-app
|
|
151
151
|
|
|
152
152
|
# pnpm
|
|
153
|
-
pnpm create sonicjs
|
|
153
|
+
pnpm create sonicjs my-app
|
|
154
154
|
```
|
|
155
155
|
|
|
156
156
|
The CLI automatically detects your package manager from lock files.
|
|
@@ -217,7 +217,7 @@ You can create resources manually after project creation. See the [After Creatio
|
|
|
217
217
|
### Skip All Prompts (Non-Interactive Mode)
|
|
218
218
|
|
|
219
219
|
```bash
|
|
220
|
-
npx create-sonicjs
|
|
220
|
+
npx create-sonicjs my-app \
|
|
221
221
|
--template=starter \
|
|
222
222
|
--database=my-app-db \
|
|
223
223
|
--bucket=my-app-media \
|
|
@@ -230,7 +230,7 @@ npx create-sonicjs-app my-app \
|
|
|
230
230
|
### Use in CI/CD
|
|
231
231
|
|
|
232
232
|
```bash
|
|
233
|
-
npx create-sonicjs
|
|
233
|
+
npx create-sonicjs test-app \
|
|
234
234
|
--template=starter \
|
|
235
235
|
--database=test-db \
|
|
236
236
|
--bucket=test-bucket \
|
|
@@ -254,7 +254,7 @@ npx create-sonicjs-app test-app \
|
|
|
254
254
|
### Create a blog
|
|
255
255
|
|
|
256
256
|
```bash
|
|
257
|
-
npx create-sonicjs
|
|
257
|
+
npx create-sonicjs my-blog
|
|
258
258
|
# Select "Starter" template
|
|
259
259
|
# Include example collection: Yes
|
|
260
260
|
```
|
|
@@ -262,7 +262,7 @@ npx create-sonicjs-app my-blog
|
|
|
262
262
|
### Create without examples
|
|
263
263
|
|
|
264
264
|
```bash
|
|
265
|
-
npx create-sonicjs
|
|
265
|
+
npx create-sonicjs my-app
|
|
266
266
|
# Include example collection: No
|
|
267
267
|
```
|
|
268
268
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-sonicjs",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.6",
|
|
4
4
|
"description": "Create a new SonicJS application with zero configuration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -40,10 +40,10 @@
|
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"prompts": "^2.4.2",
|
|
42
42
|
"kleur": "^4.1.5",
|
|
43
|
-
"ora": "^8.
|
|
44
|
-
"execa": "^
|
|
43
|
+
"ora": "^8.1.3",
|
|
44
|
+
"execa": "^10.0.0",
|
|
45
45
|
"fs-extra": "^11.2.0",
|
|
46
|
-
"validate-npm-package-name": "^
|
|
46
|
+
"validate-npm-package-name": "^7.0.0"
|
|
47
47
|
},
|
|
48
48
|
"engines": {
|
|
49
49
|
"node": ">=18.0.0"
|
package/src/cli.js
CHANGED
|
@@ -12,7 +12,7 @@ import validatePackageName from 'validate-npm-package-name'
|
|
|
12
12
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
13
13
|
|
|
14
14
|
// Version
|
|
15
|
-
const VERSION = '2.0.0'
|
|
15
|
+
const VERSION = '2.0.0-beta.9'
|
|
16
16
|
|
|
17
17
|
// Templates available
|
|
18
18
|
const TEMPLATES = {
|
|
@@ -135,6 +135,40 @@ async function getProjectDetails(initialName) {
|
|
|
135
135
|
})
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
+
// Seed admin user
|
|
139
|
+
questions.push({
|
|
140
|
+
type: 'confirm',
|
|
141
|
+
name: 'seedAdmin',
|
|
142
|
+
message: 'Create admin user?',
|
|
143
|
+
initial: true
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
// Admin email (only if seeding)
|
|
147
|
+
questions.push({
|
|
148
|
+
type: (prev, values) => values.seedAdmin ? 'text' : null,
|
|
149
|
+
name: 'adminEmail',
|
|
150
|
+
message: 'Admin email:',
|
|
151
|
+
validate: (value) => {
|
|
152
|
+
if (!value) return 'Admin email is required'
|
|
153
|
+
// Basic email validation
|
|
154
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
|
155
|
+
if (!emailRegex.test(value)) return 'Please enter a valid email address'
|
|
156
|
+
return true
|
|
157
|
+
}
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
// Admin password (only if seeding)
|
|
161
|
+
questions.push({
|
|
162
|
+
type: (prev, values) => values.seedAdmin ? 'password' : null,
|
|
163
|
+
name: 'adminPassword',
|
|
164
|
+
message: 'Admin password:',
|
|
165
|
+
validate: (value) => {
|
|
166
|
+
if (!value) return 'Admin password is required'
|
|
167
|
+
if (value.length < 8) return 'Password must be at least 8 characters'
|
|
168
|
+
return true
|
|
169
|
+
}
|
|
170
|
+
})
|
|
171
|
+
|
|
138
172
|
// Include example collection (only ask if neither flag is set)
|
|
139
173
|
if (!flags.skipExample && !flags.includeExample) {
|
|
140
174
|
questions.push({
|
|
@@ -176,8 +210,12 @@ async function getProjectDetails(initialName) {
|
|
|
176
210
|
template: flags.template || answers.template,
|
|
177
211
|
databaseName: flags.databaseName || answers.databaseName || `${initialName || answers.projectName}-db`,
|
|
178
212
|
bucketName: flags.bucketName || answers.bucketName || `${initialName || answers.projectName}-media`,
|
|
213
|
+
seedAdmin: answers.seedAdmin !== undefined ? answers.seedAdmin : true,
|
|
214
|
+
adminEmail: answers.adminEmail,
|
|
215
|
+
adminPassword: answers.adminPassword,
|
|
179
216
|
includeExample: flags.skipExample ? false : (flags.includeExample ? true : (answers.includeExample !== undefined ? answers.includeExample : true)),
|
|
180
217
|
createResources: flags.skipCloudflare ? false : answers.createResources,
|
|
218
|
+
runMigrations: true, // Always run migrations automatically
|
|
181
219
|
initGit: flags.skipGit ? false : answers.initGit,
|
|
182
220
|
skipInstall: flags.skipInstall
|
|
183
221
|
}
|
|
@@ -189,8 +227,12 @@ async function createProject(answers, flags) {
|
|
|
189
227
|
template,
|
|
190
228
|
databaseName,
|
|
191
229
|
bucketName,
|
|
230
|
+
adminEmail,
|
|
231
|
+
adminPassword,
|
|
192
232
|
includeExample,
|
|
193
233
|
createResources,
|
|
234
|
+
runMigrations,
|
|
235
|
+
seedAdmin,
|
|
194
236
|
initGit,
|
|
195
237
|
skipInstall
|
|
196
238
|
} = answers
|
|
@@ -207,23 +249,37 @@ async function createProject(answers, flags) {
|
|
|
207
249
|
projectName,
|
|
208
250
|
databaseName,
|
|
209
251
|
bucketName,
|
|
252
|
+
seedAdmin,
|
|
253
|
+
adminEmail,
|
|
254
|
+
adminPassword,
|
|
210
255
|
includeExample
|
|
211
256
|
})
|
|
212
257
|
spinner.succeed('Copied template files')
|
|
213
258
|
|
|
214
259
|
// 2. Create Cloudflare resources
|
|
215
260
|
let databaseId = 'YOUR_DATABASE_ID'
|
|
261
|
+
let resourcesCreated = false
|
|
216
262
|
if (createResources) {
|
|
217
263
|
spinner.start('Creating Cloudflare resources...')
|
|
218
264
|
try {
|
|
219
|
-
|
|
220
|
-
|
|
265
|
+
const result = await createCloudflareResources(databaseName, bucketName, targetDir)
|
|
266
|
+
databaseId = result.databaseId || 'YOUR_DATABASE_ID'
|
|
267
|
+
resourcesCreated = result.success
|
|
268
|
+
if (resourcesCreated) {
|
|
269
|
+
spinner.succeed('Created Cloudflare resources')
|
|
270
|
+
} else {
|
|
271
|
+
spinner.warn('Cloudflare resources partially created - see details above')
|
|
272
|
+
}
|
|
221
273
|
} catch (error) {
|
|
222
274
|
spinner.warn('Failed to create Cloudflare resources')
|
|
223
275
|
console.log(kleur.dim(' You can create them manually later'))
|
|
224
276
|
}
|
|
225
277
|
}
|
|
226
278
|
|
|
279
|
+
// Store resources status for success message
|
|
280
|
+
answers.resourcesCreated = resourcesCreated
|
|
281
|
+
answers.databaseIdSet = databaseId !== 'YOUR_DATABASE_ID'
|
|
282
|
+
|
|
227
283
|
// 3. Update wrangler.toml with database ID
|
|
228
284
|
spinner.start('Updating configuration...')
|
|
229
285
|
await updateWranglerConfig(targetDir, { databaseName, databaseId, bucketName })
|
|
@@ -234,6 +290,11 @@ async function createProject(answers, flags) {
|
|
|
234
290
|
spinner.start('Installing dependencies...')
|
|
235
291
|
await installDependencies(targetDir)
|
|
236
292
|
spinner.succeed('Installed dependencies')
|
|
293
|
+
|
|
294
|
+
// Copy migrations after install
|
|
295
|
+
spinner.start('Copying database migrations...')
|
|
296
|
+
await copyMigrationsFromCore(targetDir)
|
|
297
|
+
spinner.succeed('Copied database migrations')
|
|
237
298
|
}
|
|
238
299
|
|
|
239
300
|
// 5. Initialize git
|
|
@@ -243,6 +304,45 @@ async function createProject(answers, flags) {
|
|
|
243
304
|
spinner.succeed('Initialized git repository')
|
|
244
305
|
}
|
|
245
306
|
|
|
307
|
+
// 6. Run migrations
|
|
308
|
+
if (runMigrations && !skipInstall && resourcesCreated) {
|
|
309
|
+
spinner.start('Running database migrations...')
|
|
310
|
+
try {
|
|
311
|
+
await runDatabaseMigrations(targetDir)
|
|
312
|
+
spinner.succeed('Database migrations completed')
|
|
313
|
+
answers.migrationsRan = true
|
|
314
|
+
} catch (error) {
|
|
315
|
+
spinner.warn('Failed to run migrations')
|
|
316
|
+
console.log(kleur.dim(` ${error.message}`))
|
|
317
|
+
console.log(kleur.dim(' You can run them manually with: npm run db:migrate:local'))
|
|
318
|
+
answers.migrationsRan = false
|
|
319
|
+
}
|
|
320
|
+
} else if (runMigrations && skipInstall) {
|
|
321
|
+
spinner.info('Skipping migrations - run after npm install')
|
|
322
|
+
answers.migrationsRan = false
|
|
323
|
+
} else if (runMigrations && !resourcesCreated) {
|
|
324
|
+
spinner.info('Skipping migrations - database not created yet')
|
|
325
|
+
answers.migrationsRan = false
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// 7. Seed admin user
|
|
329
|
+
if (seedAdmin && !skipInstall && answers.migrationsRan) {
|
|
330
|
+
spinner.start('Seeding admin user...')
|
|
331
|
+
try {
|
|
332
|
+
await seedAdminUser(targetDir)
|
|
333
|
+
spinner.succeed('Admin user created')
|
|
334
|
+
answers.adminSeeded = true
|
|
335
|
+
} catch (error) {
|
|
336
|
+
spinner.warn('Failed to seed admin user')
|
|
337
|
+
console.log(kleur.dim(` ${error.message}`))
|
|
338
|
+
console.log(kleur.dim(' You can run it manually with: npm run seed'))
|
|
339
|
+
answers.adminSeeded = false
|
|
340
|
+
}
|
|
341
|
+
} else if (seedAdmin && !answers.migrationsRan) {
|
|
342
|
+
spinner.info('Skipping seed - migrations not completed')
|
|
343
|
+
answers.adminSeeded = false
|
|
344
|
+
}
|
|
345
|
+
|
|
246
346
|
spinner.succeed(kleur.bold().green('✓ Project created successfully!'))
|
|
247
347
|
|
|
248
348
|
} catch (error) {
|
|
@@ -282,7 +382,7 @@ async function copyTemplate(templateName, targetDir, options) {
|
|
|
282
382
|
|
|
283
383
|
// Add @sonicjs-cms/core dependency
|
|
284
384
|
packageJson.dependencies = {
|
|
285
|
-
'@sonicjs-cms/core': '^2.0.
|
|
385
|
+
'@sonicjs-cms/core': '^2.0.5',
|
|
286
386
|
...packageJson.dependencies
|
|
287
387
|
}
|
|
288
388
|
|
|
@@ -302,6 +402,159 @@ async function copyTemplate(templateName, targetDir, options) {
|
|
|
302
402
|
await fs.remove(examplePath)
|
|
303
403
|
}
|
|
304
404
|
}
|
|
405
|
+
|
|
406
|
+
// Create admin seed script with provided credentials (only if creating admin user)
|
|
407
|
+
if (options.seedAdmin && options.adminEmail && options.adminPassword) {
|
|
408
|
+
await createAdminSeedScript(targetDir, {
|
|
409
|
+
email: options.adminEmail,
|
|
410
|
+
password: options.adminPassword
|
|
411
|
+
})
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
async function createAdminSeedScript(targetDir, { email, password }) {
|
|
416
|
+
const seedScriptContent = `import { createDb, users } from '@sonicjs-cms/core'
|
|
417
|
+
import { eq } from 'drizzle-orm'
|
|
418
|
+
import bcrypt from 'bcryptjs'
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Seed script to create initial admin user
|
|
422
|
+
*
|
|
423
|
+
* Run this script after migrations:
|
|
424
|
+
* npm run db:migrate:local
|
|
425
|
+
* npm run seed
|
|
426
|
+
*
|
|
427
|
+
* Admin credentials:
|
|
428
|
+
* Email: ${email}
|
|
429
|
+
* Password: [as entered during setup]
|
|
430
|
+
*/
|
|
431
|
+
|
|
432
|
+
interface Env {
|
|
433
|
+
DB: D1Database
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
async function seed() {
|
|
437
|
+
// Get D1 database from Cloudflare environment
|
|
438
|
+
// @ts-ignore - getPlatformProxy is available in wrangler
|
|
439
|
+
const { env } = await import('@cloudflare/workers-types/experimental')
|
|
440
|
+
const platform = (env as any).getPlatformProxy?.() || { env: {} }
|
|
441
|
+
|
|
442
|
+
if (!platform.env?.DB) {
|
|
443
|
+
console.error('❌ Error: DB binding not found')
|
|
444
|
+
console.error('')
|
|
445
|
+
console.error('Make sure you have:')
|
|
446
|
+
console.error('1. Created your D1 database: wrangler d1 create <database-name>')
|
|
447
|
+
console.error('2. Updated wrangler.toml with the database_id')
|
|
448
|
+
console.error('3. Run migrations: npm run db:migrate:local')
|
|
449
|
+
console.error('')
|
|
450
|
+
process.exit(1)
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
const db = createDb(platform.env.DB)
|
|
454
|
+
|
|
455
|
+
try {
|
|
456
|
+
// Check if admin user already exists
|
|
457
|
+
const existingUser = await db
|
|
458
|
+
.select()
|
|
459
|
+
.from(users)
|
|
460
|
+
.where(eq(users.email, '${email}'))
|
|
461
|
+
.get()
|
|
462
|
+
|
|
463
|
+
if (existingUser) {
|
|
464
|
+
console.log('✓ Admin user already exists')
|
|
465
|
+
console.log(\` Email: ${email}\`)
|
|
466
|
+
console.log(\` Role: \${existingUser.role}\`)
|
|
467
|
+
return
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// Hash password using bcrypt
|
|
471
|
+
const passwordHash = await bcrypt.hash('${password}', 10)
|
|
472
|
+
|
|
473
|
+
// Create admin user
|
|
474
|
+
await db
|
|
475
|
+
.insert(users)
|
|
476
|
+
.values({
|
|
477
|
+
email: '${email}',
|
|
478
|
+
username: '${email.split('@')[0]}',
|
|
479
|
+
password: passwordHash,
|
|
480
|
+
role: 'admin',
|
|
481
|
+
isActive: 1,
|
|
482
|
+
createdAt: new Date().toISOString(),
|
|
483
|
+
updatedAt: new Date().toISOString()
|
|
484
|
+
})
|
|
485
|
+
.run()
|
|
486
|
+
|
|
487
|
+
console.log('✓ Admin user created successfully')
|
|
488
|
+
console.log(\` Email: ${email}\`)
|
|
489
|
+
console.log(\` Role: admin\`)
|
|
490
|
+
console.log('')
|
|
491
|
+
console.log('You can now login at: http://localhost:8787/auth/login')
|
|
492
|
+
} catch (error) {
|
|
493
|
+
console.error('❌ Error creating admin user:', error)
|
|
494
|
+
process.exit(1)
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// Run seed
|
|
499
|
+
seed()
|
|
500
|
+
.then(() => {
|
|
501
|
+
console.log('')
|
|
502
|
+
console.log('✓ Seeding complete')
|
|
503
|
+
process.exit(0)
|
|
504
|
+
})
|
|
505
|
+
.catch((error) => {
|
|
506
|
+
console.error('❌ Seeding failed:', error)
|
|
507
|
+
process.exit(1)
|
|
508
|
+
})
|
|
509
|
+
`
|
|
510
|
+
|
|
511
|
+
// Create scripts directory
|
|
512
|
+
const scriptsDir = path.join(targetDir, 'scripts')
|
|
513
|
+
await fs.ensureDir(scriptsDir)
|
|
514
|
+
|
|
515
|
+
// Write seed script
|
|
516
|
+
const seedScriptPath = path.join(scriptsDir, 'seed-admin.ts')
|
|
517
|
+
await fs.writeFile(seedScriptPath, seedScriptContent)
|
|
518
|
+
|
|
519
|
+
// Add seed script to package.json
|
|
520
|
+
const packageJsonPath = path.join(targetDir, 'package.json')
|
|
521
|
+
const packageJson = await fs.readJson(packageJsonPath)
|
|
522
|
+
|
|
523
|
+
if (!packageJson.scripts) {
|
|
524
|
+
packageJson.scripts = {}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
packageJson.scripts.seed = 'tsx scripts/seed-admin.ts'
|
|
528
|
+
|
|
529
|
+
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 })
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
async function copyMigrationsFromCore(targetDir) {
|
|
533
|
+
// The migrations are in @sonicjs-cms/core/migrations
|
|
534
|
+
// After npm install, they'll be in node_modules/@sonicjs-cms/core/migrations
|
|
535
|
+
const coreMigrationsPath = path.join(targetDir, 'node_modules', '@sonicjs-cms', 'core', 'migrations')
|
|
536
|
+
const projectMigrationsPath = path.join(targetDir, 'migrations')
|
|
537
|
+
|
|
538
|
+
// Check if core migrations exist (they should after npm install)
|
|
539
|
+
if (fs.existsSync(coreMigrationsPath)) {
|
|
540
|
+
// Copy migrations to project directory
|
|
541
|
+
await fs.copy(coreMigrationsPath, projectMigrationsPath)
|
|
542
|
+
} else {
|
|
543
|
+
// If migrations don't exist yet (npm install hasn't run), create a note file
|
|
544
|
+
await fs.ensureDir(projectMigrationsPath)
|
|
545
|
+
const noteContent = `# Migrations
|
|
546
|
+
|
|
547
|
+
Migrations will be copied from @sonicjs-cms/core after running npm install.
|
|
548
|
+
|
|
549
|
+
To manually copy migrations:
|
|
550
|
+
1. Install dependencies: npm install
|
|
551
|
+
2. Copy from: node_modules/@sonicjs-cms/core/migrations/
|
|
552
|
+
3. Copy to: migrations/
|
|
553
|
+
|
|
554
|
+
Or they should be automatically available after installation.
|
|
555
|
+
`
|
|
556
|
+
await fs.writeFile(path.join(projectMigrationsPath, 'README.md'), noteContent)
|
|
557
|
+
}
|
|
305
558
|
}
|
|
306
559
|
|
|
307
560
|
async function createCloudflareResources(databaseName, bucketName, targetDir) {
|
|
@@ -312,10 +565,13 @@ async function createCloudflareResources(databaseName, bucketName, targetDir) {
|
|
|
312
565
|
throw new Error('wrangler is not installed. Install with: npm install -g wrangler')
|
|
313
566
|
}
|
|
314
567
|
|
|
315
|
-
// Create D1 database
|
|
316
568
|
let databaseId
|
|
569
|
+
let dbCreated = false
|
|
570
|
+
let bucketCreated = false
|
|
571
|
+
|
|
572
|
+
// Create D1 database
|
|
317
573
|
try {
|
|
318
|
-
const { stdout } = await execa('wrangler', ['d1', 'create', databaseName], {
|
|
574
|
+
const { stdout, stderr } = await execa('wrangler', ['d1', 'create', databaseName], {
|
|
319
575
|
cwd: targetDir
|
|
320
576
|
})
|
|
321
577
|
|
|
@@ -323,9 +579,22 @@ async function createCloudflareResources(databaseName, bucketName, targetDir) {
|
|
|
323
579
|
const match = stdout.match(/database_id\s*=\s*["']([^"']+)["']/)
|
|
324
580
|
if (match) {
|
|
325
581
|
databaseId = match[1]
|
|
582
|
+
dbCreated = true
|
|
583
|
+
} else {
|
|
584
|
+
console.log('')
|
|
585
|
+
console.log(kleur.yellow('⚠ Warning: Could not parse database_id from wrangler output'))
|
|
586
|
+
console.log(kleur.dim(' You may need to manually update wrangler.toml'))
|
|
326
587
|
}
|
|
327
588
|
} catch (error) {
|
|
328
|
-
console.log(
|
|
589
|
+
console.log('')
|
|
590
|
+
console.log(kleur.yellow('⚠ D1 database creation failed:'))
|
|
591
|
+
console.log(kleur.dim(` ${error.message}`))
|
|
592
|
+
if (error.stderr) {
|
|
593
|
+
console.log(kleur.dim(` ${error.stderr}`))
|
|
594
|
+
}
|
|
595
|
+
console.log('')
|
|
596
|
+
console.log(kleur.dim(' Create manually with:'))
|
|
597
|
+
console.log(kleur.dim(` wrangler d1 create ${databaseName}`))
|
|
329
598
|
}
|
|
330
599
|
|
|
331
600
|
// Create R2 bucket
|
|
@@ -333,11 +602,23 @@ async function createCloudflareResources(databaseName, bucketName, targetDir) {
|
|
|
333
602
|
await execa('wrangler', ['r2', 'bucket', 'create', bucketName], {
|
|
334
603
|
cwd: targetDir
|
|
335
604
|
})
|
|
605
|
+
bucketCreated = true
|
|
336
606
|
} catch (error) {
|
|
337
|
-
console.log(
|
|
607
|
+
console.log('')
|
|
608
|
+
console.log(kleur.yellow('⚠ R2 bucket creation failed:'))
|
|
609
|
+
console.log(kleur.dim(` ${error.message}`))
|
|
610
|
+
if (error.stderr) {
|
|
611
|
+
console.log(kleur.dim(` ${error.stderr}`))
|
|
612
|
+
}
|
|
613
|
+
console.log('')
|
|
614
|
+
console.log(kleur.dim(' Create manually with:'))
|
|
615
|
+
console.log(kleur.dim(` wrangler r2 bucket create ${bucketName}`))
|
|
338
616
|
}
|
|
339
617
|
|
|
340
|
-
return
|
|
618
|
+
return {
|
|
619
|
+
databaseId,
|
|
620
|
+
success: dbCreated && bucketCreated
|
|
621
|
+
}
|
|
341
622
|
}
|
|
342
623
|
|
|
343
624
|
async function updateWranglerConfig(targetDir, { databaseName, databaseId, bucketName }) {
|
|
@@ -396,8 +677,50 @@ async function initializeGit(targetDir) {
|
|
|
396
677
|
}
|
|
397
678
|
}
|
|
398
679
|
|
|
680
|
+
async function runDatabaseMigrations(targetDir) {
|
|
681
|
+
try {
|
|
682
|
+
const { stdout, stderr } = await execa('npm', ['run', 'db:migrate:local'], {
|
|
683
|
+
cwd: targetDir,
|
|
684
|
+
reject: false // Don't reject on non-zero exit code - check manually
|
|
685
|
+
})
|
|
686
|
+
|
|
687
|
+
// Check if migrations were successful - look for actual errors, not warnings
|
|
688
|
+
if (stderr && (stderr.toLowerCase().includes('error:') || stderr.toLowerCase().includes('failed'))) {
|
|
689
|
+
// Filter out wrangler version warnings
|
|
690
|
+
if (!stderr.includes('Migrations were successfully applied')) {
|
|
691
|
+
throw new Error(stderr)
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
return stdout
|
|
696
|
+
} catch (error) {
|
|
697
|
+
throw new Error(`Migration failed: ${error.message}`)
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
async function seedAdminUser(targetDir) {
|
|
702
|
+
try {
|
|
703
|
+
const { stdout, stderr } = await execa('npm', ['run', 'seed'], {
|
|
704
|
+
cwd: targetDir,
|
|
705
|
+
reject: false // Don't reject on non-zero exit code - check manually
|
|
706
|
+
})
|
|
707
|
+
|
|
708
|
+
// Check if seeding was successful - look for actual errors, not warnings
|
|
709
|
+
if (stderr && (stderr.toLowerCase().includes('error:') || stderr.toLowerCase().includes('failed'))) {
|
|
710
|
+
// Filter out wrangler version warnings
|
|
711
|
+
if (!stdout.includes('Admin user created') && !stdout.includes('Admin user already exists')) {
|
|
712
|
+
throw new Error(stderr)
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
return stdout
|
|
717
|
+
} catch (error) {
|
|
718
|
+
throw new Error(`Seeding failed: ${error.message}`)
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
|
|
399
722
|
function printSuccessMessage(answers) {
|
|
400
|
-
const { projectName, createResources, skipInstall } = answers
|
|
723
|
+
const { projectName, createResources, skipInstall, resourcesCreated, databaseIdSet, migrationsRan, adminSeeded, seedAdmin } = answers
|
|
401
724
|
|
|
402
725
|
console.log()
|
|
403
726
|
console.log(kleur.bold().green('🎉 Success!'))
|
|
@@ -408,24 +731,57 @@ function printSuccessMessage(answers) {
|
|
|
408
731
|
|
|
409
732
|
if (skipInstall) {
|
|
410
733
|
console.log(kleur.cyan(' npm install'))
|
|
734
|
+
console.log()
|
|
735
|
+
console.log(kleur.yellow('⚠ Important: After npm install, copy migrations:'))
|
|
736
|
+
console.log(kleur.dim(' cp -r node_modules/@sonicjs-cms/core/migrations ./'))
|
|
411
737
|
}
|
|
412
738
|
|
|
413
|
-
if
|
|
739
|
+
// Show resource creation steps if they weren't created or failed
|
|
740
|
+
if (!createResources || !resourcesCreated) {
|
|
414
741
|
console.log()
|
|
415
742
|
console.log(kleur.bold('Create Cloudflare resources:'))
|
|
416
|
-
|
|
417
|
-
|
|
743
|
+
if (!databaseIdSet) {
|
|
744
|
+
console.log(kleur.cyan(` wrangler d1 create ${answers.databaseName}`))
|
|
745
|
+
console.log(kleur.dim(' # Copy database_id to wrangler.toml'))
|
|
746
|
+
}
|
|
418
747
|
console.log(kleur.cyan(` wrangler r2 bucket create ${answers.bucketName}`))
|
|
419
748
|
}
|
|
420
749
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
750
|
+
// Only show migration/seed steps if they weren't completed (shouldn't normally happen)
|
|
751
|
+
const needsMigrations = !migrationsRan
|
|
752
|
+
const needsSeeding = seedAdmin && !adminSeeded
|
|
753
|
+
|
|
754
|
+
if (needsMigrations || needsSeeding) {
|
|
755
|
+
console.log()
|
|
756
|
+
console.log(kleur.bold('Complete setup:'))
|
|
757
|
+
if (needsMigrations) {
|
|
758
|
+
console.log(kleur.cyan(' npm run db:migrate:local'))
|
|
759
|
+
}
|
|
760
|
+
if (needsSeeding) {
|
|
761
|
+
console.log(kleur.cyan(' npm run seed'))
|
|
762
|
+
}
|
|
763
|
+
}
|
|
424
764
|
|
|
425
765
|
console.log()
|
|
426
|
-
|
|
766
|
+
if (migrationsRan && (!seedAdmin || adminSeeded)) {
|
|
767
|
+
console.log(kleur.bold().green('✓ Database is ready! Start development:'))
|
|
768
|
+
} else {
|
|
769
|
+
console.log(kleur.bold('Start development:'))
|
|
770
|
+
}
|
|
427
771
|
console.log(kleur.cyan(' npm run dev'))
|
|
428
772
|
|
|
773
|
+
if (seedAdmin && answers.adminEmail) {
|
|
774
|
+
console.log()
|
|
775
|
+
console.log(kleur.bold('Login credentials:'))
|
|
776
|
+
console.log(kleur.cyan(` Email: ${answers.adminEmail}`))
|
|
777
|
+
console.log(kleur.dim(` Password: [as entered]`))
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
if (migrationsRan && (!seedAdmin || adminSeeded)) {
|
|
781
|
+
console.log()
|
|
782
|
+
console.log(kleur.green('✓ Everything is set up! Just run npm run dev and login.'))
|
|
783
|
+
}
|
|
784
|
+
|
|
429
785
|
console.log()
|
|
430
786
|
console.log(kleur.bold('Visit:'))
|
|
431
787
|
console.log(kleur.cyan(' http://localhost:8787/admin'))
|
|
@@ -12,20 +12,21 @@
|
|
|
12
12
|
"db:studio": "drizzle-kit studio",
|
|
13
13
|
"type-check": "tsc --noEmit",
|
|
14
14
|
"test": "vitest --run",
|
|
15
|
-
"test:watch": "vitest"
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
"test:watch": "vitest",
|
|
16
|
+
"update": "npm install @sonicjs-cms/core@latest --save",
|
|
17
|
+
"update:beta": "npm install @sonicjs-cms/core@beta --save"
|
|
18
18
|
},
|
|
19
|
+
"dependencies": {},
|
|
19
20
|
"devDependencies": {
|
|
20
|
-
"@cloudflare/workers-types": "^4.
|
|
21
|
-
"@types/node": "^
|
|
22
|
-
"drizzle-kit": "^0.30.
|
|
23
|
-
"drizzle-orm": "^0.44.
|
|
24
|
-
"hono": "^4.
|
|
25
|
-
"typescript": "^5.
|
|
26
|
-
"vitest": "^
|
|
27
|
-
"wrangler": "^3.110.
|
|
28
|
-
"zod": "^
|
|
21
|
+
"@cloudflare/workers-types": "^4.20251014.0",
|
|
22
|
+
"@types/node": "^24.9.2",
|
|
23
|
+
"drizzle-kit": "^0.30.3",
|
|
24
|
+
"drizzle-orm": "^0.44.7",
|
|
25
|
+
"hono": "^4.10.4",
|
|
26
|
+
"typescript": "^5.9.3",
|
|
27
|
+
"vitest": "^4.0.5",
|
|
28
|
+
"wrangler": "^3.110.4",
|
|
29
|
+
"zod": "^4.1.12"
|
|
29
30
|
},
|
|
30
31
|
"engines": {
|
|
31
32
|
"node": ">=18.0.0"
|