Summary
scripts/build-vite.js goes to real trouble to stay portable — stageArtifact() carries a six-line comment about GNU tar vs bsdtar on Windows and runs tar from the archive's directory to avoid the C: drive-letter-as-remote-host parse (:65-71), and it uses a hand-written copyDir() (:42-50) rather than shelling out.
scripts/fetch-apps.js does neither:
// :155, :241
execSync(`tar -xzf "${tarPath}" -C "${workDir}"`, { stdio: 'pipe' });
// :171, :251
execSync(`cp -r "${doc.docDir}" "${appsSlugDir}"`, { stdio: 'pipe' });
cp does not exist on Windows outside a POSIX shell — npm run build (the GitHub-fetch path) fails there, while npm run build:local works.
- The
tar calls pass absolute paths with a drive letter as -f, which is exactly the failure build-vite.js documents and works around.
The result is that the primary build path is the non-portable one and only the test/local path is portable, which is the wrong way round — and CI (ubuntu-latest, hermetic fixtures) never exercises the GitHub path at all, so this cannot be caught there.
Suggested fix
Replace both shell-outs with what build-vite.js already has:
cp -r → cpSync(src, dest, { recursive: true }) (Node ≥ 16) or the existing copyDir(), exported from a shared module so the two files stop diverging.
tar -xzf → the same stageArtifact() helper, moved into a shared module.
That also removes two more unvalidated-shell-string call sites (see the GITHUB_TOKEN/injection issue) and gives one place to add the archive-entry validation from the path-traversal issue.
While in the file: writeFileSync is imported at :14 and never used.
Summary
scripts/build-vite.jsgoes to real trouble to stay portable —stageArtifact()carries a six-line comment about GNU tar vs bsdtar on Windows and runstarfrom the archive's directory to avoid theC:drive-letter-as-remote-host parse (:65-71), and it uses a hand-writtencopyDir()(:42-50) rather than shelling out.scripts/fetch-apps.jsdoes neither:cpdoes not exist on Windows outside a POSIX shell —npm run build(the GitHub-fetch path) fails there, whilenpm run build:localworks.tarcalls pass absolute paths with a drive letter as-f, which is exactly the failurebuild-vite.jsdocuments and works around.The result is that the primary build path is the non-portable one and only the test/local path is portable, which is the wrong way round — and CI (
ubuntu-latest, hermetic fixtures) never exercises the GitHub path at all, so this cannot be caught there.Suggested fix
Replace both shell-outs with what
build-vite.jsalready has:cp -r→cpSync(src, dest, { recursive: true })(Node ≥ 16) or the existingcopyDir(), exported from a shared module so the two files stop diverging.tar -xzf→ the samestageArtifact()helper, moved into a shared module.That also removes two more unvalidated-shell-string call sites (see the
GITHUB_TOKEN/injection issue) and gives one place to add the archive-entry validation from the path-traversal issue.While in the file:
writeFileSyncis imported at:14and never used.