voko-dev-user.js 2.79 KB
#!/usr/bin/env node
import fs from 'node:fs'
import { randomBytes, randomUUID, scryptSync } from 'node:crypto'
import path from 'node:path'
import { createRequire } from 'node:module'
import { fileURLToPath } from 'node:url'
import { devMysqlUrl } from '@drama/db'

const DEFAULT_DATABASE_URL = devMysqlUrl('voko')
const here = path.dirname(fileURLToPath(import.meta.url))
const repoRoot = path.resolve(here, '../..')
const envFile = path.resolve(repoRoot, 'apps/voko-server/.env')
const requireFromVokoServer = createRequire(new URL('../../apps/voko-server/package.json', import.meta.url))
const { createDb } = await import(requireFromVokoServer.resolve('@drama/db'))

function hashPassword(password) {
  const salt = randomBytes(16)
  const derived = scryptSync(String(password), salt, 64)
  return `scrypt$${salt.toString('hex')}$${derived.toString('hex')}`
}

async function main() {
  const email = process.argv[2] || 'admin@example.test'
  const password = process.argv[3] || 'admin123'
  const displayName = process.argv[4] || 'VOKO Admin'
  const role = process.argv[5] || 'admin'
  if (!['admin', 'operator', 'user'].includes(role)) {
    throw new Error(`voko dev user role 必须是 admin、operator 或 user: ${role}`)
  }
  const databaseUrl = process.env.DATABASE_URL || readDotenv(envFile).DATABASE_URL || DEFAULT_DATABASE_URL
  const db = createDb({ url: databaseUrl, pool: { min: 0, max: 1 } })
  const normalizedEmail = email.trim().toLowerCase()
  const now = new Date()
  const user = {
    id: randomUUID(),
    email: normalizedEmail,
    display_name: displayName,
    status: 'active',
    password_hash: hashPassword(password),
    locale: 'zh',
    email_verified_at: now,
    role,
    created_at: now,
    updated_at: now,
  }

  try {
    await db('users').insert(user).onConflict('email').merge({
      display_name: user.display_name,
      status: user.status,
      password_hash: user.password_hash,
      locale: user.locale,
      email_verified_at: db.raw('COALESCE(email_verified_at, ?)', [now]),
      role: user.role,
      updated_at: now,
    })
    console.log(`voko dev user ready: ${normalizedEmail}, role=${role}`)
  } finally {
    await db.destroy()
  }
}

function readDotenv(file) {
  if (!fs.existsSync(file)) return {}
  const env = {}
  for (const raw of fs.readFileSync(file, 'utf8').split(/\r?\n/)) {
    const line = raw.trim()
    if (!line || line.startsWith('#')) continue
    const separator = line.indexOf('=')
    if (separator < 0) continue
    const key = line.slice(0, separator).trim()
    let value = line.slice(separator + 1).trim()
    if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
      value = value.slice(1, -1)
    }
    env[key] = value
  }
  return env
}

main().catch(error => {
  console.error(error)
  process.exit(1)
})