import { pathToFileURL } from 'node:url' import { issueOperatorPasswordResetToken, TokenDigester, type IssueOperatorPasswordResetRequest, type IssuedPasswordReset, } from '@devrunbook/application' import { closeDatabase, DrizzlePasswordResetStore } from '@devrunbook/db' type IssueReset = ( request: IssueOperatorPasswordResetRequest, ) => Promise export async function runOperatorPasswordReset( arguments_: readonly string[], environment: Record, writeOutput: (value: string) => void, issueReset?: IssueReset, ): Promise { if (arguments_.length !== 1 || !arguments_[0]) { throw new Error( 'Usage: pnpm --filter @devrunbook/worker operator:password-reset -- ', ) } const sessionSecret = environment.SESSION_SECRET const publicBaseUrl = environment.PUBLIC_BASE_URL if (!sessionSecret || sessionSecret.length < 32) { throw new Error('SESSION_SECRET must contain at least 32 characters') } if (!publicBaseUrl) throw new Error('PUBLIC_BASE_URL is required') const issue = issueReset ?? ((request) => issueOperatorPasswordResetToken( { store: new DrizzlePasswordResetStore(), digester: new TokenDigester(Buffer.from(sessionSecret, 'utf8')), }, request, )) const result = await issue({ email: arguments_[0], publicBaseUrl, }) // Successful stdout is deliberately one line containing only the reset URL. writeOutput(result.resetUrl) } async function main() { try { await runOperatorPasswordReset( process.argv.slice(2), process.env, (value) => console.log(value), ) } catch (error) { const message = error instanceof Error ? error.message : 'Password reset failed' console.error(message) process.exitCode = 1 } finally { await closeDatabase() } } const entryPoint = process.argv[1] if (entryPoint && import.meta.url === pathToFileURL(entryPoint).href) { await main() }