Skip to content

Commit 9ea2966

Browse files
miraoclaude
andcommitted
fix(typescript): unique temp file names to fix run-multiple race (#5642)
Transpiled TypeScript files were written to a fixed "<source>.temp.mjs" path next to the source. Under run-multiple, every forked worker transpiles the same files to the same temp paths and cleans them up independently, so one worker's cleanup deletes files the others still need to import — surfacing as "Cannot find module *.temp.mjs". Include process.pid plus a random suffix in the temp file name so each worker writes (and removes) its own files. The names still end in ".temp.mjs", so stack-trace remapping and fixErrorStack keep working. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 7cb5366 commit 9ea2966

2 files changed

Lines changed: 39 additions & 1 deletion

File tree

lib/utils/typescript.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -385,7 +385,9 @@ const __dirname = __dirname_fn(__filename);
385385
)
386386

387387
// Write the transpiled file with updated imports
388-
const tempFile = filePath.replace(/\.ts$/, '.temp.mjs')
388+
// Include process.pid + a random suffix so concurrent run-multiple workers
389+
// don't write to and delete each other's temp files (see issue #5642).
390+
const tempFile = filePath.replace(/\.ts$/, `.${process.pid}.${Math.random().toString(36).slice(2, 10)}.temp.mjs`)
389391
fs.writeFileSync(tempFile, jsContent)
390392
transpiledFiles.set(filePath, tempFile)
391393
}

test/unit/utils/typescript_test.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { expect } from 'chai'
2+
import { fileURLToPath } from 'url'
3+
import path from 'path'
4+
import { createRequire } from 'module'
5+
import { transpileTypeScript, cleanupTempFiles } from '../../../lib/utils/typescript.js'
6+
7+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
8+
const require = createRequire(import.meta.url)
9+
const typescript = require('typescript')
10+
11+
const configPath = path.resolve(__dirname, '../../data/typescript-config-imports/tests/api/codecept.conf.ts')
12+
13+
describe('TypeScript transpilation', () => {
14+
it('uses unique temp file names per invocation so concurrent run-multiple workers do not delete each other (#5642)', async () => {
15+
const first = await transpileTypeScript(configPath, typescript)
16+
const second = await transpileTypeScript(configPath, typescript)
17+
18+
try {
19+
expect(first.allTempFiles.length).to.be.greaterThan(0)
20+
expect(second.allTempFiles.length).to.equal(first.allTempFiles.length)
21+
22+
// Every temp file path is still recognisable as a transpiled file
23+
for (const file of [...first.allTempFiles, ...second.allTempFiles]) {
24+
expect(file).to.match(/\.temp\.mjs$/)
25+
}
26+
27+
// The two invocations must not share any temp file path, otherwise one
28+
// worker's cleanup would remove files the other still needs to import.
29+
const shared = first.allTempFiles.filter(f => second.allTempFiles.includes(f))
30+
expect(shared, `temp files were shared between invocations: ${shared}`).to.be.empty
31+
} finally {
32+
cleanupTempFiles(first.allTempFiles)
33+
cleanupTempFiles(second.allTempFiles)
34+
}
35+
})
36+
})

0 commit comments

Comments
 (0)