33 *
44 * Creates an initial admin user when the database has no users yet.
55 * Safe to run multiple times — exits silently if an admin already exists.
6+ * Safe to run BEFORE the server starts — applies migrations automatically.
67 *
78 * Usage:
89 * node --experimental-strip-types src/seed-self-host.ts
9- * # or via npm script:
10+ * # or via npm script (from my-sonicjs-app/) :
1011 * npm run reset
1112 *
1213 * Configure via environment variables:
13- * SONICJS_DB_PATH Path to SQLite file (default: ./data/sonicjs.db)
14- * SONICJS_ADMIN_EMAIL Admin email (default: admin@sonicjs.com)
15- * SONICJS_ADMIN_PASSWORD Admin password (default: sonicjs!)
14+ * SONICJS_DB_PATH Path to SQLite file (default: ./data/sonicjs.db)
15+ * SONICJS_ADMIN_EMAIL Admin email (default: admin@sonicjs.com)
16+ * SONICJS_ADMIN_PASSWORD Admin password (default: sonicjs!)
1617 */
1718
18- import Database from 'better-sqlite3'
1919import { hashPassword } from '@better-auth/utils/password'
20- import { existsSync } from 'node:fs '
21- import { resolve } from 'node:path '
20+ import { resolve , dirname } from 'node:path '
21+ import { mkdirSync } from 'node:fs '
2222import { createSqliteDriver } from '@sonicjs-cms/core/adapters'
2323import { bootstrapDocumentTypes , RbacService } from '@sonicjs-cms/core'
2424
@@ -27,18 +27,17 @@ const ADMIN_EMAIL = process.env.SONICJS_ADMIN_EMAIL ?? 'admin@sonicjs.com'
2727const ADMIN_PASSWORD = process . env . SONICJS_ADMIN_PASSWORD ?? 'sonicjs!'
2828
2929async function seed ( ) {
30- if ( ! existsSync ( DB_PATH ) ) {
31- console . error ( `[seed] Database not found at ${ DB_PATH } ` )
32- console . error ( '[seed] Start the server first so it auto-migrates, then run this script.' )
33- process . exit ( 1 )
34- }
30+ // Ensure data directory exists before opening DB.
31+ mkdirSync ( dirname ( DB_PATH ) , { recursive : true } )
3532
36- const db = new Database ( DB_PATH )
33+ // createSqliteDriver: opens DB, enables WAL, auto-migrates schema if absent.
34+ // Safe to run before the server — no concurrent connection needed.
35+ const driver = await createSqliteDriver ( { dbPath : DB_PATH , autoMigrate : true } )
3736
3837 try {
3938 // Idempotency check — skip if any user already exists.
40- const existing = db . prepare ( 'SELECT COUNT(*) as n FROM auth_user' ) . get ( ) as { n : number }
41- if ( existing . n > 0 ) {
39+ const existing = await driver . prepare ( 'SELECT COUNT(*) as n FROM auth_user' ) . first < { n : number } > ( )
40+ if ( existing && existing . n > 0 ) {
4241 console . log ( '[seed] Admin already exists — skipping.' )
4342 return
4443 }
@@ -49,42 +48,36 @@ async function seed() {
4948
5049 const passwordHash = await hashPassword ( ADMIN_PASSWORD )
5150
52- db . prepare ( `
51+ await driver . prepare ( `
5352 INSERT INTO auth_user (
5453 id, email, name, first_name, last_name,
5554 role, is_super_admin, is_active,
5655 email_verified, created_at, updated_at
5756 ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
58- ` ) . run ( userId , ADMIN_EMAIL , 'Admin' , 'Admin' , 'User' , 'admin' , 1 , 1 , 0 , now , now )
57+ ` ) . bind ( userId , ADMIN_EMAIL , 'Admin' , 'Admin' , 'User' , 'admin' , 1 , 1 , 0 , now , now ) . run ( )
5958
60- db . prepare ( `
59+ await driver . prepare ( `
6160 INSERT INTO auth_account (
6261 id, user_id, account_id, provider_id, password, created_at, updated_at
6362 ) VALUES (?, ?, ?, ?, ?, ?, ?)
64- ` ) . run ( accountId , userId , userId , 'credential' , passwordHash , now , now )
65-
66- db . close ( )
63+ ` ) . bind ( accountId , userId , userId , 'credential' , passwordHash , now , now ) . run ( )
6764
6865 // Bootstrap RBAC so the admin user can access /admin (requireRbac('portal', 'access')).
69- // Uses the D1-compatible SqliteDriver so RbacService writes to the same SQLite file.
70- const driver = await createSqliteDriver ( { dbPath : DB_PATH , autoMigrate : false } )
7166 // eslint-disable-next-line @typescript-eslint/no-explicit-any
7267 await bootstrapDocumentTypes ( driver as any )
7368 // eslint-disable-next-line @typescript-eslint/no-explicit-any
7469 const rbac = new RbacService ( driver as any )
7570 await rbac . ensureSystemRbacSeed ( )
7671 await rbac . addUserRoleByName ( userId , 'admin' )
77- driver . close ( )
7872
7973 console . log ( '[seed] Admin user created:' )
8074 console . log ( ` Email: ${ ADMIN_EMAIL } ` )
8175 console . log ( ` Password: ${ ADMIN_PASSWORD } ` )
8276 if ( ADMIN_PASSWORD === 'sonicjs!' ) {
8377 console . log ( ' ⚠ Change this password after first login!' )
8478 }
85- return
8679 } finally {
87- if ( db . open ) db . close ( )
80+ driver . close ( )
8881 }
8982}
9083
0 commit comments