@@ -14,8 +14,8 @@ import type {
1414import { normalize } from '@sentry/core' ;
1515import { createBasicSentryServer } from '@sentry-internal/test-utils' ;
1616import { execSync , spawn , spawnSync } from 'child_process' ;
17- import { existsSync , readFileSync , unlinkSync , writeFileSync } from 'fs' ;
18- import { join } from 'path' ;
17+ import { existsSync , mkdirSync , readFileSync , rmSync , unlinkSync , writeFileSync } from 'fs' ;
18+ import { basename , join } from 'path' ;
1919import { inspect } from 'util' ;
2020import { afterAll , beforeAll , describe , test } from 'vitest' ;
2121import {
@@ -174,7 +174,7 @@ export function createEsmAndCjsTests(
174174 testFn : typeof test | typeof test . fails ,
175175 mode : 'esm' | 'cjs' ,
176176 ) => void ,
177- options ?: { failsOnCjs ?: boolean ; failsOnEsm ?: boolean } ,
177+ options ?: { failsOnCjs ?: boolean ; failsOnEsm ?: boolean ; additionalDependencies ?: Record < string , string > } ,
178178) : void {
179179 const mjsScenarioPath = join ( cwd , scenarioPath ) ;
180180 const mjsInstrumentPath = join ( cwd , instrumentPath ) ;
@@ -187,33 +187,110 @@ export function createEsmAndCjsTests(
187187 throw new Error ( `Instrument file not found: ${ mjsInstrumentPath } ` ) ;
188188 }
189189
190- const cjsScenarioPath = join ( cwd , `tmp_${ scenarioPath . replace ( '.mjs' , '.cjs' ) } ` ) ;
191- const cjsInstrumentPath = join ( cwd , `tmp_${ instrumentPath . replace ( '.mjs' , '.cjs' ) } ` ) ;
190+ // If additionalDependencies are provided, we create a dedicated tmp directory that includes
191+ // copied ESM & CJS scenario/instrument files and a nested package.json with those dependencies installed.
192+ const useTmpDir = Boolean ( options ?. additionalDependencies && Object . keys ( options . additionalDependencies ) . length > 0 ) ;
193+
194+ let tmpDirPath : string | undefined ;
195+ let esmScenarioPathForRun = mjsScenarioPath ;
196+ let esmInstrumentPathForRun = mjsInstrumentPath ;
197+ let cjsScenarioPath = join ( cwd , `tmp_${ scenarioPath . replace ( '.mjs' , '.cjs' ) } ` ) ;
198+ let cjsInstrumentPath = join ( cwd , `tmp_${ instrumentPath . replace ( '.mjs' , '.cjs' ) } ` ) ;
199+
200+ if ( useTmpDir ) {
201+ // Create unique tmp directory within the suite folder
202+ const uniqueId = `${ Date . now ( ) . toString ( 36 ) } _${ Math . random ( ) . toString ( 36 ) . slice ( 2 , 8 ) } ` ;
203+ tmpDirPath = join ( cwd , `tmp_${ uniqueId } ` ) ;
204+ mkdirSync ( tmpDirPath ) ;
205+
206+ // Copy ESM files as-is into tmp dir
207+ const esmScenarioBasename = basename ( scenarioPath ) ;
208+ const esmInstrumentBasename = basename ( instrumentPath ) ;
209+ esmScenarioPathForRun = join ( tmpDirPath , esmScenarioBasename ) ;
210+ esmInstrumentPathForRun = join ( tmpDirPath , esmInstrumentBasename ) ;
211+ writeFileSync ( esmScenarioPathForRun , readFileSync ( mjsScenarioPath , 'utf8' ) ) ;
212+ writeFileSync ( esmInstrumentPathForRun , readFileSync ( mjsInstrumentPath , 'utf8' ) ) ;
213+
214+ // Pre-create CJS converted files inside tmp dir
215+ cjsScenarioPath = join ( tmpDirPath , esmScenarioBasename . replace ( '.mjs' , '.cjs' ) ) ;
216+ cjsInstrumentPath = join ( tmpDirPath , esmInstrumentBasename . replace ( '.mjs' , '.cjs' ) ) ;
217+ convertEsmFileToCjs ( esmScenarioPathForRun , cjsScenarioPath ) ;
218+ convertEsmFileToCjs ( esmInstrumentPathForRun , cjsInstrumentPath ) ;
219+
220+ // Create a minimal package.json with requested dependencies
221+ const additionalDependencies = options ?. additionalDependencies ?? { } ;
222+ const packageJson = {
223+ name : 'tmp-integration-test' ,
224+ private : true ,
225+ version : '0.0.0' ,
226+ dependencies : additionalDependencies ,
227+ } as const ;
228+
229+ writeFileSync ( join ( tmpDirPath , 'package.json' ) , JSON . stringify ( packageJson , null , 2 ) ) ;
230+
231+ // Install the requested dependencies using yarn
232+ try {
233+ const deps = Object . entries ( additionalDependencies ) . map ( ( [ name , range ] ) => {
234+ if ( ! range || typeof range !== 'string' ) {
235+ throw new Error ( `Invalid version range for "${ name } ": ${ String ( range ) } ` ) ;
236+ }
237+ return `${ name } @${ range } ` ;
238+ } ) ;
239+
240+ if ( deps . length > 0 ) {
241+ // Run yarn add non-interactively, keep output visible when DEBUG is set
242+ spawnSync ( 'yarn' , [ 'add' , '--non-interactive' , ...deps ] , {
243+ cwd : tmpDirPath ,
244+ stdio : process . env . DEBUG ? 'inherit' : 'ignore' ,
245+ } ) ;
246+ }
247+ } catch ( e ) {
248+ // eslint-disable-next-line no-console
249+ console . error ( 'Failed to install additionalDependencies:' , e ) ;
250+ throw e ;
251+ }
252+ }
192253
193254 describe ( 'esm' , ( ) => {
194255 const testFn = options ?. failsOnEsm ? test . fails : test ;
195- callback ( ( ) => createRunner ( mjsScenarioPath ) . withFlags ( '--import' , mjsInstrumentPath ) , testFn , 'esm' ) ;
256+ callback ( ( ) => createRunner ( esmScenarioPathForRun ) . withFlags ( '--import' , esmInstrumentPathForRun ) , testFn , 'esm' ) ;
196257 } ) ;
197258
198259 describe ( 'cjs' , ( ) => {
199- beforeAll ( ( ) => {
200- // For the CJS runner, we create some temporary files...
201- convertEsmFileToCjs ( mjsScenarioPath , cjsScenarioPath ) ;
202- convertEsmFileToCjs ( mjsInstrumentPath , cjsInstrumentPath ) ;
203- } ) ;
204-
205- afterAll ( ( ) => {
206- try {
207- unlinkSync ( cjsInstrumentPath ) ;
208- } catch {
209- // Ignore errors here
210- }
211- try {
212- unlinkSync ( cjsScenarioPath ) ;
213- } catch {
214- // Ignore errors here
215- }
216- } ) ;
260+ if ( ! useTmpDir ) {
261+ beforeAll ( ( ) => {
262+ // For the CJS runner, we create some temporary files...
263+ convertEsmFileToCjs ( mjsScenarioPath , cjsScenarioPath ) ;
264+ convertEsmFileToCjs ( mjsInstrumentPath , cjsInstrumentPath ) ;
265+ } ) ;
266+
267+ afterAll ( ( ) => {
268+ try {
269+ unlinkSync ( cjsInstrumentPath ) ;
270+ } catch {
271+ // Ignore errors here
272+ }
273+ try {
274+ unlinkSync ( cjsScenarioPath ) ;
275+ } catch {
276+ // Ignore errors here
277+ }
278+ } ) ;
279+ } else {
280+ // When using a tmp dir, clean up the entire directory once CJS tests are finished
281+ afterAll ( ( ) => {
282+ if ( tmpDirPath ) {
283+ try {
284+ rmSync ( tmpDirPath , { recursive : true , force : true } ) ;
285+ } catch {
286+ if ( process . env . DEBUG ) {
287+ // eslint-disable-next-line no-console
288+ console . error ( `Failed to remove tmp dir: ${ tmpDirPath } ` ) ;
289+ }
290+ }
291+ }
292+ } ) ;
293+ }
217294
218295 const testFn = options ?. failsOnCjs ? test . fails : test ;
219296 callback ( ( ) => createRunner ( cjsScenarioPath ) . withFlags ( '--require' , cjsInstrumentPath ) , testFn , 'cjs' ) ;
0 commit comments