/** * CI: clone backend repo at pinned ref (see e2e/backend.contract.json). * In CI (CI=true): E2E_BACKEND_REF is required — no fallback to main. */ import { readFileSync, existsSync, rmSync } from 'node:fs' import { dirname, join, resolve } from 'node:path' import { fileURLToPath } from 'node:url' import { spawnSync } from 'node:child_process' const scriptDir = dirname(fileURLToPath(import.meta.url)) const frontendRoot = join(scriptDir, '..') const contractPath = join(frontendRoot, 'e2e', 'backend.contract.json') const isCi = process.env.CI === 'true' || process.env.GITEA_ACTIONS === 'true' if (!existsSync(contractPath)) { console.error('Missing e2e/backend.contract.json') process.exit(1) } /** @type {{ repositoryUrl: string; checkoutPath: string; localRef?: string }} */ const contract = JSON.parse(readFileSync(contractPath, 'utf8')) const envRef = process.env.E2E_BACKEND_REF?.trim() let ref = envRef if (isCi) { if (!ref) { console.error( 'E2E backend ref is required in CI. Set repository variable E2E_BACKEND_REF to a full commit SHA (or immutable tag). Silent fallback to main is disabled.', ) process.exit(1) } } else if (!ref) { ref = contract.localRef?.trim() if (!ref) { console.error( 'Set E2E_BACKEND_REF to a backend commit SHA/tag, or localRef in e2e/backend.contract.json for clone-based local runs.', ) process.exit(1) } } const repoUrl = process.env.E2E_BACKEND_REPOSITORY_URL?.trim() || contract.repositoryUrl const checkoutPath = resolve( frontendRoot, process.env.E2E_BACKEND_ROOT?.trim() || contract.checkoutPath, ) const token = process.env.E2E_BACKEND_CHECKOUT_TOKEN?.trim() let cloneUrl = repoUrl if (token) { const u = new URL(repoUrl.replace(/^git\+/, '')) u.username = token u.password = 'x-oauth-basic' cloneUrl = u.toString() } if (existsSync(checkoutPath)) { rmSync(checkoutPath, { recursive: true, force: true }) } const git = 'git' const gitEnv = { ...process.env, GIT_TERMINAL_PROMPT: '0' } const gitOpts = { stdio: 'inherit', env: gitEnv, shell: process.platform === 'win32' } const isFullSha = /^[0-9a-f]{40}$/i.test(ref) console.log(`Cloning backend ref=${ref} into ${checkoutPath}`) let clone if (isFullSha) { clone = spawnSync(git, ['clone', '--depth', '1', '--no-checkout', cloneUrl, checkoutPath], gitOpts) if (clone.status !== 0) { console.error('Backend clone failed (init).') process.exit(clone.status ?? 1) } const fetch = spawnSync( git, ['fetch', '--depth', '1', 'origin', ref], { ...gitOpts, cwd: checkoutPath }, ) if (fetch.status !== 0) { console.error(`Backend fetch failed for SHA ${ref}.`) process.exit(fetch.status ?? 1) } const co = spawnSync(git, ['checkout', 'FETCH_HEAD'], { ...gitOpts, cwd: checkoutPath }) if (co.status !== 0) { console.error('Backend checkout failed.') process.exit(co.status ?? 1) } } else { clone = spawnSync( git, ['clone', '--depth', '1', '--branch', ref, cloneUrl, checkoutPath], gitOpts, ) if (clone.status !== 0) { console.error( 'Backend clone failed. For private repos set secret E2E_BACKEND_CHECKOUT_TOKEN (repo read).', ) process.exit(clone.status ?? 1) } } const head = spawnSync(git, ['rev-parse', 'HEAD'], { cwd: checkoutPath, encoding: 'utf8', shell: process.platform === 'win32', }) const checkedOutSha = head.stdout?.trim() if (checkedOutSha) { console.log(`Backend checkout commit: ${checkedOutSha}`) if (isFullSha && checkedOutSha.toLowerCase() !== ref.toLowerCase()) { console.error( `E2E_BACKEND_REF (${ref}) does not match cloned HEAD (${checkedOutSha}).`, ) process.exit(1) } } else { console.error('Could not read cloned backend commit (git rev-parse HEAD failed).') process.exit(1) } process.env.E2E_BACKEND_ROOT = checkoutPath