From db4b7e29bbbea737c8d504cf2382e9ba268a3e3e Mon Sep 17 00:00:00 2001 From: Evgeny Shurakov Date: Thu, 20 Aug 2026 13:50:59 +0200 Subject: [PATCH] feat(seed): add --admin flag to app:create-user Allow seeding local admin users, and grant is_admin on an existing user when --admin is passed instead of failing on duplicate email. --- dev/seed/app/create-user.ts | 64 ++++++++++++++++++++++++++++++++----- 1 file changed, 56 insertions(+), 8 deletions(-) diff --git a/dev/seed/app/create-user.ts b/dev/seed/app/create-user.ts index b7d7905637..0194b7312c 100644 --- a/dev/seed/app/create-user.ts +++ b/dev/seed/app/create-user.ts @@ -8,7 +8,7 @@ import { normalizeSeedEmail } from '../lib/email'; import { createSeedStripeCustomer, deleteSeedStripeCustomer } from '../lib/stripe'; import type { SeedResult } from '../index'; -export const usage = ' '; +export const usage = ' [--admin]'; function printUsage(): void { console.log(`Usage: pnpm dev:seed app:create-user ${usage}`); @@ -18,8 +18,14 @@ function printUsage(): void { console.log('app, but the user has no auth provider linked. Sign in via the normal flow if'); console.log('you need a real session.'); console.log(''); + console.log('Options:'); + console.log( + ' --admin Set is_admin=true (also grants it if the user already exists)' + ); + console.log(''); console.log('Examples:'); console.log(' pnpm dev:seed app:create-user "Ada Lovelace" ada@example.com'); + console.log(' pnpm dev:seed app:create-user "Evgeny" evgeny@kilocode.ai --admin'); } function isValidEmail(email: string): boolean { @@ -33,7 +39,21 @@ export async function run(...args: string[]): Promise { return; } - const [name, email, ...rest] = args; + const positional: string[] = []; + let isAdmin = false; + for (const arg of args) { + if (arg === '--admin') { + isAdmin = true; + continue; + } + if (arg.startsWith('--')) { + printUsage(); + throw new Error(`Unknown argument: ${arg}`); + } + positional.push(arg); + } + + const [name, email, ...rest] = positional; if (!name || !email) { printUsage(); throw new Error('name and email are required'); @@ -52,16 +72,41 @@ export async function run(...args: string[]): Promise { const normalizedEmail = normalizeSeedEmail(trimmedEmail); const existing = await db - .select({ id: kilocode_users.id }) + .select({ + id: kilocode_users.id, + stripeCustomerId: kilocode_users.stripe_customer_id, + isAdmin: kilocode_users.is_admin, + }) .from(kilocode_users) .where(eq(kilocode_users.normalized_email, normalizedEmail)) .limit(1); - if (existing.length > 0) { - throw new Error( - `A user with email ${trimmedEmail} already exists (id=${existing[0].id}). ` + - `Delete it first or pick a different email.` - ); + const existingUser = existing[0]; + if (existingUser) { + if (!isAdmin) { + throw new Error( + `A user with email ${trimmedEmail} already exists (id=${existingUser.id}). ` + + `Delete it first or pick a different email.` + ); + } + + if (!existingUser.isAdmin) { + await db + .update(kilocode_users) + .set({ is_admin: true }) + .where(eq(kilocode_users.id, existingUser.id)); + } + + return { + userId: existingUser.id, + name: trimmedName, + email: trimmedEmail, + stripeCustomerId: existingUser.stripeCustomerId, + hasValidationStytch: true, + customerSource: 'dev-seed', + isAdmin: true, + created: false, + }; } const userId = randomUUID(); @@ -90,6 +135,7 @@ export async function run(...args: string[]): Promise { normalized_email: normalizedEmail, has_validation_stytch: true, customer_source: 'dev-seed', + is_admin: isAdmin, }); } catch (error) { // The DB insert failed after we already created a Stripe customer; clean @@ -105,5 +151,7 @@ export async function run(...args: string[]): Promise { stripeCustomerId: stripeCustomer.id, hasValidationStytch: true, customerSource: 'dev-seed', + isAdmin, + created: true, }; }