11/**
2- * Bundle the instrumented Sentry test apps for every (app, mode) cell and POST
3- * the tarballs to the Sentry Lighthouse lab (https://lighthouse.sentry.gg). The
4- * lab runs Lighthouse asynchronously and ships results to Sentry on its own
5- * schedule — this script exits as soon as the upload succeeds, it does NOT wait
6- * for the lab to finish.
2+ * Bundle the `lighthouse-react` test app for each mode (no-sentry, init-only,
3+ * tracing-replay) and POST the three tarballs to the Sentry Lighthouse lab
4+ * (https://lighthouse.sentry.gg). The lab runs Lighthouse asynchronously and
5+ * ships results to Sentry on its own schedule — this script exits as soon as
6+ * the upload succeeds.
7+ *
8+ * Single-app static matrix: 1 app × 3 modes = 3 cells.
9+ * See plan scratchpad #182 for design details.
710 *
811 * Wire protocol: ~/Projects/sentry-lhci/docs/sentry-javascript-handoff.md
912 *
1720/* eslint-disable no-console */
1821
1922import { execFileSync } from 'node:child_process' ;
20- import { copyFile , mkdir , readdir , readFile , rm , stat , writeFile } from 'node:fs/promises' ;
23+ import { mkdir , readFile , rm , stat } from 'node:fs/promises' ;
2124import path from 'node:path' ;
2225
2326const LAB_URL = process . env . LIGHTHOUSE_LAB_URL ;
@@ -31,31 +34,10 @@ const RUNNER_TEMP = process.env.RUNNER_TEMP ?? path.join(WORKSPACE, '.tmp');
3134const PACKED_DIR = path . join ( WORKSPACE , 'dev-packages/e2e-tests/packed' ) ;
3235const E2E_DIR = path . join ( WORKSPACE , 'dev-packages/e2e-tests' ) ;
3336
34- /**
35- * The matrix. Adding an app here requires:
36- * 1. The test app reads SENTRY_LIGHTHOUSE_MODE (or its bundler-specific
37- * prefix variant) and branches its Sentry init.
38- * 2. For SSR apps, the lab's runner must accept a startCmd + readyPattern.
39- */
40- const APPS = [
41- { app : 'default-browser' , serve : 'static' , staticDir : 'build' } ,
42- { app : 'react-19' , serve : 'static' , staticDir : 'build' } ,
43- {
44- app : 'nextjs-16' ,
45- serve : 'server' ,
46- startCmd : 'pnpm start' ,
47- readyPattern : 'Ready in' ,
48- // Lab side: pnpm 9.15.9 is on the image via corepack. We strip the lockfile
49- // and devDependencies from the SSR bundle (CI generates the lockfile with
50- // workspace-absolute paths that don't survive the move; devDeps include
51- // workspace links like @sentry -internal/test-utils that would also fail).
52- // --no-frozen-lockfile lets pnpm regenerate from the rewritten package.json,
53- // --prefer-offline uses the lab's persistent pnpm store (/data/.pnpm-store).
54- installCmd : 'pnpm install --no-frozen-lockfile --prefer-offline' ,
55- } ,
56- ] ;
57-
37+ const APP = 'lighthouse-react' ;
38+ const APP_DIR = 'lighthouse-react' ;
5839const MODES = [ 'no-sentry' , 'init-only' , 'tracing-replay' ] ;
40+ const STATIC_DIR = 'dist' ;
5941
6042async function run ( ) {
6143 // Fail fast if the lab is down so we don't waste minutes building bundles.
@@ -69,13 +51,10 @@ async function run() {
6951 await mkdir ( path . join ( RUNNER_TEMP , 'bundles' ) , { recursive : true } ) ;
7052 const bundles = [ ] ;
7153
72- for ( const def of APPS ) {
73- for ( const mode of MODES ) {
74- const fieldName = `bundle-${ bundles . length } ` ;
75- console . log ( `\n=== Preparing ${ def . app } (${ mode } ) → ${ fieldName } ===` ) ;
76- const bundle = await prepareCell ( def , mode , fieldName ) ;
77- bundles . push ( bundle ) ;
78- }
54+ for ( const mode of MODES ) {
55+ const fieldName = `bundle-${ bundles . length } ` ;
56+ console . log ( `\n=== Preparing ${ APP } (${ mode } ) → ${ fieldName } ===` ) ;
57+ bundles . push ( await prepareCell ( mode , fieldName ) ) ;
7958 }
8059
8160 console . log ( `\n=== Uploading ${ bundles . length } bundles to ${ LAB_URL } /api/builds ===` ) ;
@@ -87,24 +66,19 @@ async function run() {
8766}
8867
8968/**
90- * Build a single (app, mode) cell:
91- * 1. Copy the app to a unique temp dir (concurrent cells can't collide) .
69+ * Build a single (mode) cell:
70+ * 1. Copy the app to a unique temp dir.
9271 * 2. Apply pnpm overrides (existing helper).
93- * 3. Run `pnpm test:build` with SENTRY_LIGHTHOUSE_MODE + the framework
94- * prefix variants (NEXT_PUBLIC_*, REACT_APP_*) — each app's bundler picks
95- * up whichever it knows about.
96- * 4. Static cells: tar just the build dir.
97- * SSR cells: copy packed tgzs into the bundle, rewrite package.json to
98- * relative `file:./packed/...` paths, drop devDependencies, tar without
99- * node_modules and the lockfile.
72+ * 3. Run `pnpm install` then `pnpm build:<mode>`.
73+ * 4. Tar the `dist/` output dir.
10074 * 5. Return cell metadata for the upload.
10175 */
102- async function prepareCell ( def , mode , fieldName ) {
103- const tempApp = path . join ( RUNNER_TEMP , `app-${ def . app } -${ mode } ` ) ;
76+ async function prepareCell ( mode , fieldName ) {
77+ const tempApp = path . join ( RUNNER_TEMP , `app-${ APP } -${ mode } ` ) ;
10478 await rm ( tempApp , { recursive : true , force : true } ) ;
10579
10680 // Copy app to temp (fixes file:/link: deps to workspace-absolute paths)
107- execFileSync ( 'yarn' , [ 'ci:copy-to-temp' , `./test-applications/${ def . app } ` , tempApp ] , {
81+ execFileSync ( 'yarn' , [ 'ci:copy-to-temp' , `./test-applications/${ APP_DIR } ` , tempApp ] , {
10882 cwd : E2E_DIR ,
10983 stdio : 'inherit' ,
11084 } ) ;
@@ -115,106 +89,30 @@ async function prepareCell(def, mode, fieldName) {
11589 stdio : 'inherit' ,
11690 } ) ;
11791
118- // Build with the right mode env var. We set all common bundler prefixes so each
119- // app's bundler picks up whichever variant it knows about — apps that don't read
120- // a prefix simply ignore extra vars.
121- execFileSync ( 'pnpm' , [ 'test:build' ] , {
92+ // Install deps
93+ execFileSync ( 'pnpm' , [ 'install' ] , { cwd : tempApp , stdio : 'inherit' } ) ;
94+
95+ // Build with the Vite mode — mode selection lives inside the per-mode npm
96+ // script, no extra env vars needed for routing. VITE_E2E_TEST_DSN is passed
97+ // so the tracing-replay build's Sentry.init has a DSN at build time.
98+ execFileSync ( 'pnpm' , [ `build:${ mode } ` ] , {
12299 cwd : tempApp ,
123100 stdio : 'inherit' ,
124101 env : {
125102 ...process . env ,
126- SENTRY_E2E_WORKSPACE_ROOT : WORKSPACE ,
127- SENTRY_LIGHTHOUSE_MODE : mode ,
128- NEXT_PUBLIC_SENTRY_LIGHTHOUSE_MODE : mode ,
129- REACT_APP_SENTRY_LIGHTHOUSE_MODE : mode ,
103+ VITE_E2E_TEST_DSN : process . env . VITE_E2E_TEST_DSN ?? 'https://username@domain/123' ,
130104 } ,
131105 } ) ;
132106
133- const tarPath = path . join ( RUNNER_TEMP , 'bundles' , `${ def . app } -${ mode } .tar.gz` ) ;
134-
135- if ( def . serve === 'static' ) {
136- // Static cell — tar just the build dir. Lab serves it with a static HTTP server.
137- execFileSync ( 'tar' , [ '-czf' , tarPath , '-C' , tempApp , def . staticDir ] , { stdio : 'inherit' } ) ;
138- console . log ( `Static bundle: ${ tarPath } (${ await formatSize ( tarPath ) } )` ) ;
139- return {
140- fieldName,
141- tarPath,
142- cell : {
143- app : def . app ,
144- mode,
145- bundleField : fieldName ,
146- serve : 'static' ,
147- staticDir : def . staticDir ,
148- } ,
149- } ;
150- }
107+ const tarPath = path . join ( RUNNER_TEMP , 'bundles' , `${ APP } -${ mode } .tar.gz` ) ;
108+ execFileSync ( 'tar' , [ '-czf' , tarPath , '-C' , tempApp , STATIC_DIR ] , { stdio : 'inherit' } ) ;
109+ console . log ( `Static bundle: ${ tarPath } (${ await formatSize ( tarPath ) } )` ) ;
151110
152- // SSR cell — prep for `pnpm install && pnpm start` on the lab.
153- await prepareSsrBundle ( tempApp ) ;
154- execFileSync (
155- 'tar' ,
156- [
157- '-czf' ,
158- tarPath ,
159- '--exclude=node_modules' ,
160- '--exclude=.git' ,
161- '--exclude=pnpm-lock.yaml' ,
162- '-C' ,
163- path . dirname ( tempApp ) ,
164- path . basename ( tempApp ) ,
165- ] ,
166- { stdio : 'inherit' } ,
167- ) ;
168- console . log ( `SSR bundle: ${ tarPath } (${ await formatSize ( tarPath ) } )` ) ;
169111 return {
170112 fieldName,
171113 tarPath,
172- cell : {
173- app : def . app ,
174- mode,
175- bundleField : fieldName ,
176- serve : 'server' ,
177- startCmd : def . startCmd ,
178- readyPattern : def . readyPattern ,
179- installCmd : def . installCmd ,
180- } ,
181- } ;
182- }
183-
184- /**
185- * For SSR cells: copy the packed Sentry tarballs into the bundle, rewrite
186- * package.json deps + pnpm.overrides to relative `file:./packed/...` paths so
187- * the lab's `pnpm install` can resolve them from inside the extracted bundle,
188- * and drop devDependencies entirely (not needed at runtime; some are workspace
189- * links like @sentry-internal/test-utils that don't survive the move).
190- */
191- async function prepareSsrBundle ( tempApp ) {
192- // Copy packed tgz files into <bundle>/packed/
193- const inBundlePacked = path . join ( tempApp , 'packed' ) ;
194- await mkdir ( inBundlePacked , { recursive : true } ) ;
195- for ( const name of await readdir ( PACKED_DIR ) ) {
196- if ( name . endsWith ( '.tgz' ) ) {
197- await copyFile ( path . join ( PACKED_DIR , name ) , path . join ( inBundlePacked , name ) ) ;
198- }
199- }
200-
201- // Rewrite all workspace-absolute `file:.../sentry-*-packed.tgz` references in
202- // package.json to `./packed/<file>`, and drop devDependencies wholesale.
203- const pkgPath = path . join ( tempApp , 'package.json' ) ;
204- const pkg = JSON . parse ( await readFile ( pkgPath , 'utf8' ) ) ;
205- const rewrite = obj => {
206- if ( ! obj ) return ;
207- for ( const [ name , val ] of Object . entries ( obj ) ) {
208- const m = typeof val === 'string' ? val . match ( / s e n t r y - [ a - z 0 - 9 - ] + - p a c k e d \. t g z $ / ) : null ;
209- if ( m && ( val . startsWith ( 'file:' ) || val . startsWith ( 'link:' ) ) ) {
210- obj [ name ] = `file:./packed/${ m [ 0 ] } ` ;
211- }
212- }
114+ cell : { app : APP , mode, bundleField : fieldName , serve : 'static' , staticDir : STATIC_DIR } ,
213115 } ;
214- rewrite ( pkg . dependencies ) ;
215- rewrite ( pkg . pnpm ?. overrides ) ;
216- delete pkg . devDependencies ;
217- await writeFile ( pkgPath , JSON . stringify ( pkg , null , 2 ) ) ;
218116}
219117
220118/**
0 commit comments