From b90cac85baffefeb7bc660bcaca29aa26fd0167d Mon Sep 17 00:00:00 2001 From: Sam Chau Date: Sun, 8 Feb 2026 16:22:32 +1030 Subject: [PATCH] chore(e2e): add manual repo reset task and ignore CODEX.md --- .gitignore | 1 + deno.json | 1 + scripts/e2e-reset.ts | 215 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 217 insertions(+) create mode 100644 scripts/e2e-reset.ts diff --git a/.gitignore b/.gitignore index 15437fd2..b9b527db 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ node_modules # Claude context CLAUDE.md +CODEX.md .claudeignore AGENTS.md diff --git a/deno.json b/deno.json index b230dadb..b6a31e36 100644 --- a/deno.json +++ b/deno.json @@ -56,6 +56,7 @@ "test:e2e": "deno run -A scripts/e2e.ts", "test:e2e:headed": "deno run -A scripts/e2e.ts --headed", "test:e2e:debug": "deno run -A scripts/e2e.ts --debug", + "test:e2e:reset": "deno run -A scripts/e2e-reset.ts", "stats": "bash scripts/stats.sh" }, "compilerOptions": { diff --git a/scripts/e2e-reset.ts b/scripts/e2e-reset.ts new file mode 100644 index 00000000..5d495042 --- /dev/null +++ b/scripts/e2e-reset.ts @@ -0,0 +1,215 @@ +/** + * Manual E2E repo reset helper. + * + * Examples: + * deno task test:e2e:reset -- --database-name "E2E Dev" --head + * deno task test:e2e:reset -- --database-id 12 --commit + * deno task test:e2e:reset -- --database-name "E2E Dev" --commit --push + */ + +import { Database } from '@jsr/db__sqlite'; +import path from 'node:path'; + +type DatabaseRow = { + id: number; + name: string; + local_path: string; +}; + +type CliOptions = { + databaseId: number | null; + databaseName: string | null; + headOnly: boolean; + commit: string | null; + push: boolean; +}; + +function loadDotEnv(): void { + const envPath = path.resolve('.env'); + let content = ''; + try { + content = Deno.readTextFileSync(envPath); + } catch { + return; + } + + for (const line of content.split('\n')) { + const trimmed = line.trim(); + if (!trimmed || trimmed.startsWith('#')) continue; + + const eqIndex = trimmed.indexOf('='); + if (eqIndex === -1) continue; + + const key = trimmed.slice(0, eqIndex).trim(); + const value = trimmed + .slice(eqIndex + 1) + .trim() + .replace(/^['"]|['"]$/g, ''); + if (!Deno.env.get(key)) { + Deno.env.set(key, value); + } + } +} + +function printHelp(): void { + console.log('E2E repo reset helper'); + console.log(''); + console.log('Usage:'); + console.log(' deno task test:e2e:reset -- --database-id --head'); + console.log(' deno task test:e2e:reset -- --database-name "" --head'); + console.log(' deno task test:e2e:reset -- --database-id --commit [--push]'); + console.log(' deno task test:e2e:reset -- --database-name "" --commit [--push]'); + console.log(''); + console.log('Flags:'); + console.log(' --database-id Database instance id from profilarr.db'); + console.log(' --database-name Database instance name from profilarr.db'); + console.log(' --head Print current HEAD commit and exit'); + console.log(' --commit Reset local clone to this commit'); + console.log(' --push Force-push after reset'); + console.log(' --help, -h Show this help'); +} + +function fail(message: string): never { + console.error(message); + console.error(''); + printHelp(); + Deno.exit(1); +} + +function parseArgs(args: string[]): CliOptions { + const options: CliOptions = { + databaseId: null, + databaseName: null, + headOnly: false, + commit: null, + push: false + }; + + for (let i = 0; i < args.length; i += 1) { + const arg = args[i]; + + if (arg === '--') { + continue; + } + + if (arg === '--help' || arg === '-h') { + printHelp(); + Deno.exit(0); + } + + if (arg === '--database-id') { + const value = args[i + 1]; + if (!value) fail('Missing value for --database-id'); + const parsed = Number.parseInt(value, 10); + if (!Number.isFinite(parsed)) fail(`Invalid --database-id: ${value}`); + options.databaseId = parsed; + i += 1; + continue; + } + + if (arg === '--database-name') { + const value = args[i + 1]; + if (!value) fail('Missing value for --database-name'); + options.databaseName = value; + i += 1; + continue; + } + + if (arg === '--head') { + options.headOnly = true; + continue; + } + + if (arg === '--commit') { + const value = args[i + 1]; + if (!value) fail('Missing value for --commit'); + options.commit = value; + i += 1; + continue; + } + + if (arg === '--push') { + options.push = true; + continue; + } + + fail(`Unknown argument: ${arg}`); + } + + const hasId = options.databaseId !== null; + const hasName = !!options.databaseName; + if (hasId === hasName) { + fail('Provide exactly one of --database-id or --database-name'); + } + + if (!options.headOnly && !options.commit) { + fail('Provide --head or --commit '); + } + + return options; +} + +function getDatabaseRow(dbPath: string, options: CliOptions): DatabaseRow { + const db = new Database(dbPath, { readonly: true }); + try { + let row: DatabaseRow | undefined; + if (options.databaseId !== null) { + row = db + .prepare('SELECT id, name, local_path FROM database_instances WHERE id = ?') + .get(options.databaseId) as DatabaseRow | undefined; + } else { + row = db + .prepare('SELECT id, name, local_path FROM database_instances WHERE name = ?') + .get(options.databaseName) as DatabaseRow | undefined; + } + + if (!row) { + const label = + options.databaseId !== null ? `id ${options.databaseId}` : `name "${options.databaseName}"`; + throw new Error(`Database instance not found for ${label}`); + } + + return row; + } finally { + db.close(); + } +} + +async function runGit(args: string[], cwd: string): Promise { + const command = new Deno.Command('git', { + args, + cwd, + stdout: 'piped', + stderr: 'piped' + }); + const { code, stdout, stderr } = await command.output(); + const stderrText = new TextDecoder().decode(stderr).trim(); + if (code !== 0) { + throw new Error(stderrText || `git ${args.join(' ')} failed with exit code ${code}`); + } + return new TextDecoder().decode(stdout).trim(); +} + +loadDotEnv(); +const options = parseArgs(Deno.args); +const dbPath = path.resolve(Deno.env.get('DB_PATH') || 'dist/dev/data/profilarr.db'); +const dbRow = getDatabaseRow(dbPath, options); + +if (options.headOnly) { + const head = await runGit(['rev-parse', 'HEAD'], dbRow.local_path); + console.log(head); + Deno.exit(0); +} + +const targetCommit = options.commit as string; +await runGit(['rev-parse', '--verify', `${targetCommit}^{commit}`], dbRow.local_path); +await runGit(['reset', '--hard', targetCommit], dbRow.local_path); + +if (options.push) { + await runGit(['push', '--force'], dbRow.local_path); +} + +const newHead = await runGit(['rev-parse', 'HEAD'], dbRow.local_path); +console.log( + `Reset database ${dbRow.id} "${dbRow.name}" to ${newHead}${options.push ? ' and force-pushed origin' : ''}.` +);