diff --git a/changelog.md b/changelog.md index fc01361..58a05dc 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,12 @@ # Changelog +## 2026-03-05 - 4.1.16 - fix(mod_unpack) +handle partial readdir results from signal-interrupted getdents64 when unpacking to ensure sibling removal and nested moves complete + +- Loop readdir calls for destination directory until only the source folder remains to avoid partial-listing leftovers +- Loop readdir calls for nested directory and repeatedly rename entries until the nested directory is empty +- Prevents leftover files and incomplete moves when readdir returns partial results under signals + ## 2026-03-05 - 4.1.15 - fix(mod_unpack) flatten nested output directory without temporary rename steps to avoid race conditions diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index 9bee49b..40d192f 100644 --- a/ts/00_commitinfo_data.ts +++ b/ts/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@git.zone/tsbuild', - version: '4.1.15', + version: '4.1.16', description: 'A tool for compiling TypeScript files using the latest nightly features, offering flexible APIs and a CLI for streamlined development.' } diff --git a/ts/mod_unpack/classes.tsunpacker.ts b/ts/mod_unpack/classes.tsunpacker.ts index 3815702..20cc9a4 100644 --- a/ts/mod_unpack/classes.tsunpacker.ts +++ b/ts/mod_unpack/classes.tsunpacker.ts @@ -108,20 +108,28 @@ export class TsUnpacker { const nestedPath = this.getNestedPath(); // Step 1: Remove sibling entries (everything in dest except the source folder) - const destEntries = await fs.promises.readdir(this.destDir); - for (const entry of destEntries) { - if (entry !== this.sourceFolderName) { - await fs.promises.rm(path.join(this.destDir, entry), { recursive: true, force: true }); + // Loop handles partial readdir results from signal-interrupted getdents64 + let destEntries = await fs.promises.readdir(this.destDir); + while (destEntries.some(e => e !== this.sourceFolderName)) { + for (const entry of destEntries) { + if (entry !== this.sourceFolderName) { + await fs.promises.rm(path.join(this.destDir, entry), { recursive: true, force: true }); + } } + destEntries = await fs.promises.readdir(this.destDir); } // Step 2: Move all contents from nested dir up to dest dir - const nestedEntries = await fs.promises.readdir(nestedPath); - for (const entry of nestedEntries) { - await fs.promises.rename( - path.join(nestedPath, entry), - path.join(this.destDir, entry), - ); + // Loop handles partial readdir results from signal-interrupted getdents64 + let nestedEntries = await fs.promises.readdir(nestedPath); + while (nestedEntries.length > 0) { + for (const entry of nestedEntries) { + await fs.promises.rename( + path.join(nestedPath, entry), + path.join(this.destDir, entry), + ); + } + nestedEntries = await fs.promises.readdir(nestedPath); } // Step 3: Remove the now-empty nested directory