|
| 1 | +import type { PtySession } from './lib/pty.ts' |
| 2 | +import { fileURLToPath } from 'node:url' |
| 3 | + |
| 4 | +interface CaptureContext { |
| 5 | + session: PtySession |
| 6 | + /** Directory the capture runs in. */ |
| 7 | + cwd: string |
| 8 | + /** Absolute path to `bin/nuxi.mjs` under test. */ |
| 9 | + bin: string |
| 10 | +} |
| 11 | + |
| 12 | +export interface Capture { |
| 13 | + id: string |
| 14 | + title: string |
| 15 | + /** Shell command run inside the pty. `$NUXT` expands to the CLI entry under test, `$CREATE_NUXT` to the workspace `create-nuxt` bin. */ |
| 16 | + command: string |
| 17 | + /** Directory to run in, relative to the capture workdir. */ |
| 18 | + cwd?: string |
| 19 | + columns?: number |
| 20 | + rows?: number |
| 21 | + animated?: boolean |
| 22 | + /** Rules from `lib/scrub.ts` applied to the captured bytes. */ |
| 23 | + scrub?: string[] |
| 24 | + /** Drives the session: waits, keystrokes, file edits. */ |
| 25 | + drive?: (context: CaptureContext) => Promise<void> |
| 26 | + /** Extra environment for the pty. */ |
| 27 | + env?: Record<string, string> |
| 28 | + /** Kill the command once `drive` returns rather than waiting for it to exit. */ |
| 29 | + stopAfterDrive?: boolean |
| 30 | +} |
| 31 | + |
| 32 | +/** |
| 33 | + * Rendering scrubs are privacy and environment only: real timings, versions |
| 34 | + * and spinner phases stay in the SVG so it still reads as a real recording. |
| 35 | + * Whether a re-recording *replaces* a committed SVG is decided separately, by |
| 36 | + * a fingerprint that pins all of those (see `FINGERPRINT_RULES` in record.ts). |
| 37 | + */ |
| 38 | +const DEFAULT_SCRUB = ['ports', 'hostnames', 'paths'] |
| 39 | + |
| 40 | +/** Dev output also shows a QR code encoding the machine's real address. */ |
| 41 | +const DEV_SCRUB = [...DEFAULT_SCRUB, 'qr'] |
| 42 | + |
| 43 | +export const captures: Capture[] = [ |
| 44 | + { |
| 45 | + id: 'nuxt-dev', |
| 46 | + title: 'nuxt dev', |
| 47 | + command: '$NUXT dev --no-clear --takeover', |
| 48 | + cwd: 'app', |
| 49 | + animated: true, |
| 50 | + rows: 26, |
| 51 | + scrub: DEV_SCRUB, |
| 52 | + stopAfterDrive: true, |
| 53 | + async drive({ session }) { |
| 54 | + await session.waitFor(/warmed up|Vite client built/, 180_000) |
| 55 | + await session.wait(2500) |
| 56 | + }, |
| 57 | + }, |
| 58 | + { |
| 59 | + id: 'nuxt-dev-static', |
| 60 | + title: 'nuxt dev (ready)', |
| 61 | + command: '$NUXT dev --no-clear --takeover', |
| 62 | + cwd: 'app', |
| 63 | + animated: false, |
| 64 | + rows: 26, |
| 65 | + scrub: DEV_SCRUB, |
| 66 | + stopAfterDrive: true, |
| 67 | + async drive({ session }) { |
| 68 | + await session.waitFor(/warmed up|Vite client built/, 180_000) |
| 69 | + await session.wait(2000) |
| 70 | + }, |
| 71 | + }, |
| 72 | + { |
| 73 | + id: 'nuxt-dev-restart', |
| 74 | + title: 'nuxt dev (restart on config change)', |
| 75 | + command: '$NUXT dev --no-clear --takeover', |
| 76 | + cwd: 'app', |
| 77 | + animated: true, |
| 78 | + rows: 26, |
| 79 | + scrub: DEV_SCRUB, |
| 80 | + stopAfterDrive: true, |
| 81 | + async drive({ session, cwd }) { |
| 82 | + const { readFileSync, writeFileSync } = await import('node:fs') |
| 83 | + const { join } = await import('node:path') |
| 84 | + await session.waitFor(/warmed up|Vite client built/, 180_000) |
| 85 | + await session.wait(1500) |
| 86 | + const config = join(cwd, 'nuxt.config.ts') |
| 87 | + const original = readFileSync(config, 'utf8') |
| 88 | + try { |
| 89 | + writeFileSync(config, original.replace('compatibilityDate', `devtools: { enabled: false },\n compatibilityDate`)) |
| 90 | + await session.waitFor(/Reloading Nuxt|Restarting Nuxt/, 30_000) |
| 91 | + await session.wait(4000) |
| 92 | + } |
| 93 | + finally { |
| 94 | + writeFileSync(config, original) |
| 95 | + } |
| 96 | + await session.wait(1000) |
| 97 | + }, |
| 98 | + }, |
| 99 | + { |
| 100 | + id: 'nuxt-init', |
| 101 | + title: 'npm create nuxt', |
| 102 | + command: '$CREATE_NUXT my-app --packageManager npm --no-install --no-gitInit', |
| 103 | + cwd: 'scratch', |
| 104 | + animated: true, |
| 105 | + rows: 24, |
| 106 | + scrub: DEFAULT_SCRUB, |
| 107 | + async drive({ session }) { |
| 108 | + await session.waitFor(/Which template/, 60_000) |
| 109 | + await session.wait(1200) |
| 110 | + session.send('\r') |
| 111 | + await session.waitFor(/browse and install modules/, 60_000) |
| 112 | + await session.wait(800) |
| 113 | + session.send('\r') |
| 114 | + await session.waitFor(/Happy building!/, 120_000) |
| 115 | + await session.wait(800) |
| 116 | + }, |
| 117 | + }, |
| 118 | + { |
| 119 | + id: 'nuxt-curl', |
| 120 | + title: 'nuxt curl', |
| 121 | + command: '$NUXT curl /api/hello -i', |
| 122 | + cwd: 'app', |
| 123 | + animated: false, |
| 124 | + rows: 16, |
| 125 | + scrub: DEFAULT_SCRUB, |
| 126 | + }, |
| 127 | + { |
| 128 | + id: 'nuxt-task-list', |
| 129 | + title: 'nuxt task list', |
| 130 | + command: '$NUXT task list', |
| 131 | + cwd: 'app', |
| 132 | + animated: false, |
| 133 | + rows: 12, |
| 134 | + scrub: DEFAULT_SCRUB, |
| 135 | + }, |
| 136 | + { |
| 137 | + id: 'nuxt-module-search', |
| 138 | + title: 'nuxt module search', |
| 139 | + command: '$NUXT module search image', |
| 140 | + cwd: 'app', |
| 141 | + animated: false, |
| 142 | + rows: 22, |
| 143 | + scrub: DEFAULT_SCRUB, |
| 144 | + // The live API drifts (star counts, new modules), so the capture would |
| 145 | + // invalidate on every re-record; answer it from committed fixture data. |
| 146 | + env: { |
| 147 | + NODE_OPTIONS: `--import=${new URL('lib/fetch-stub.mjs', import.meta.url).href}`, |
| 148 | + CAPTURE_FETCH_STUBS: JSON.stringify({ |
| 149 | + 'https://api.nuxt.com/modules': fileURLToPath(new URL('fixture-data/modules.json', import.meta.url)), |
| 150 | + }), |
| 151 | + }, |
| 152 | + }, |
| 153 | +] |
| 154 | + |
| 155 | +/** Captures that need a dev server already running in the fixture. */ |
| 156 | +export const NEEDS_DEV_SERVER = new Set(['nuxt-curl', 'nuxt-task-list']) |
0 commit comments