import { runScreenshotsPlan } from '../../src/screenshots/orchestrate' import { fetchOrgDeckFrames, type OrgDeckFrame } from '../../src/screenshots/org-deck' import { authHeaderValue, resolveCliAuth } from '../auth' interface RunScreenshotsOptions { port?: number verbose?: boolean } export async function runScreenshots( args: string[], _opts: RunScreenshotsOptions = {}, ): Promise { if (args.includes('--help') || args.includes('-h')) { console.log(` rnx screenshot appstore — capture and compose app-store screenshot sets usage: rnx screenshot appstore --plan [options] options: --plan screenshot plan yaml --sim reuse a live sim for capture --app override plan app target (port, url, or shell url) --device override plan capture device for this run --org overlay copy/colors from a team's org screenshot deck --repo repo for --org (both required to pull the org deck) --api-origin contrast origin to fetch the deck from (default: the signed-in session origin, else https://contrast.dev) --capture-only stop after raw/framed intermediates --compose-only skip flow capture, reuse existing raw screenshots examples: rnx screenshot appstore --plan app-store.yaml rnx screenshot appstore --plan app-store.yaml --sim a9 rnx screenshot appstore --plan app-store.yaml --org acme --repo mobile `) return 0 } const getFlag = (name: string) => args.find((_, i) => args[i - 1] === name) const consumedValueFlags = new Set([ '--plan', '--sim', '--app', '--device', '--org', '--repo', '--api-origin', ]) const positional = args.filter((arg, index) => { if (arg.startsWith('-')) return false const previous = args[index - 1] return !previous || !consumedValueFlags.has(previous) }) const planPath = getFlag('--plan') || positional[0] if (!planPath) { console.error(' error: screenshots plan path is required (`--plan `).') return 1 } const simId = getFlag('--sim')?.trim() || null const appTarget = getFlag('--app')?.trim() || null const deviceModel = getFlag('--device')?.trim() || null const org = getFlag('--org')?.trim() || null const repo = getFlag('--repo')?.trim() || null const apiOrigin = getFlag('--api-origin')?.trim() || null const captureOnly = args.includes('--capture-only') const composeOnly = args.includes('--compose-only') let orgDeckFrames: OrgDeckFrame[] | null = null if (org || repo) { if (!org || !repo) { console.error(' error: --org and --repo must be passed together.') return 1 } const auth = resolveCliAuth() if (!auth) { console.error( ' error: --org/--repo needs auth — run `rnx login` or set RNX_API_KEY.', ) return 1 } const origin = apiOrigin || (auth.kind === 'session' ? auth.origin : 'https://contrast.dev') try { orgDeckFrames = await fetchOrgDeckFrames({ org, repo, origin, authHeader: authHeaderValue(auth), }) } catch (err) { console.error(` ${(err as Error).message}`) return 1 } if (!orgDeckFrames || orgDeckFrames.length === 0) { console.log( ` note: no org deck for ${org}/${repo} — composing with the plan's copy/colors.`, ) } else { console.log(` org deck: ${orgDeckFrames.length} slide(s) from ${org}/${repo}`) } } try { const result = await runScreenshotsPlan(planPath, { simId, appTarget, deviceModel, captureOnly, composeOnly, orgDeckFrames, }) console.log(` capture manifest: ${result.capture.manifestPath}`) if (result.compose) { console.log(` compose manifest: ${result.compose.manifestPath}`) console.log(` exports: ${result.compose.outputs.length}`) } else { console.log(' compose skipped') } return 0 } catch (err) { console.error(` screenshots failed: ${(err as Error).message}`) return 1 } }