#!/usr/bin/env bun /** * Mint a NextGraph wallet and write it as a `.ngw` — the one-off a person runs to provision a * deployment. * * ── Why this exists next to the library function ───────────────────────────── * An application that hands a wallet out serves a `.ngw` at the URL it passes to * `configure({ sharedWallet: { fileUrl, password } })`, and nothing produces that file: minting * one means driving the wallet application in a browser, which is exactly what * `mintWalletBytes` already does for the suites. So this is not a second implementation — it is * that call, a `writeFileSync`, and the two lines a human needs to fill the configuration in. * * ── What it does NOT do ────────────────────────────────────────────────────── * It does not invent a password. The password is what opens the wallet for everyone the * deployment lets in; one chosen here would be a secret the tool knows and the operator does * not, printed to a terminal and never chosen by anybody. It is a required argument. * * It also refuses to overwrite an existing file unless told to. A `.ngw` is an identity, and * the identities it holds exist nowhere else — a clobbered one is not recoverable from the * broker or anywhere else. * * Usage: * bun run packages/ng-e2e-helpers/bin/mint-wallet.ts --password [--out ] [--name ] [--force] */ import * as fs from "node:fs"; import * as path from "node:path"; import { DEFAULT_WALLET_NAME, mintWalletBytes } from "../src/wallet"; const USAGE = "usage: mint-wallet --password [--out ] [--name ] [--force]"; interface Options { readonly password: string; readonly name: string; readonly out: string; readonly force: boolean; } /** `--k v` and `--k=v` both, because a person types whichever one they learnt first. */ function parseArguments(argv: readonly string[]): Options { const values = new Map(); let force = false; for (let i = 0; i < argv.length; i++) { const arg = argv[i]!; if (arg === "--force") { force = true; continue; } if (!arg.startsWith("--")) throw new Error(`unexpected argument ${arg}\n${USAGE}`); const equals = arg.indexOf("="); const key = equals === -1 ? arg.slice(2) : arg.slice(2, equals); let value: string | undefined; if (equals !== -1) { value = arg.slice(equals + 1); } else { value = argv[++i]; } if (value === undefined) throw new Error(`--${key} needs a value\n${USAGE}`); if (!["password", "out", "name"].includes(key)) { throw new Error(`unknown option --${key}\n${USAGE}`); } values.set(key, value); } const password = values.get("password"); if (password === undefined || password === "") { throw new Error(`--password is required — this tool does not invent one\n${USAGE}`); } const name = values.get("name") ?? DEFAULT_WALLET_NAME; // Relative to where the person is standing, which is the only path they can predict. Any // default landing inside a checkout is covered by the repository-wide `*.ngw` ignore. const out = path.resolve(process.cwd(), values.get("out") ?? `${name}.ngw`); return { password, name, out, force }; } async function main(): Promise { const options = parseArguments(process.argv.slice(2)); if (!options.force && fs.existsSync(options.out)) { throw new Error( `${options.out} already exists — a .ngw is an identity, so this refuses to overwrite one.\n` + "Pass --force if that file is genuinely disposable.", ); } console.log(`[mint-wallet] minting the wallet "${options.name}" (this drives a real browser)...`); const bytes = await mintWalletBytes(options.password, options.name); fs.mkdirSync(path.dirname(options.out), { recursive: true }); fs.writeFileSync(options.out, bytes); console.log(""); console.log(`[mint-wallet] wrote ${bytes.length} bytes`); console.log(` file: ${options.out}`); console.log(` password: ${options.password}`); console.log(""); console.log("Serve that file, and give the application its URL and this password:"); console.log(" configure({ sharedWallet: { fileUrl: , password: } })"); } main().then( () => process.exit(0), (e: unknown) => { console.error(`[mint-wallet] ${e instanceof Error ? e.message : String(e)}`); process.exit(1); }, );