fix(export): не падать с EPERM при сохранении в корень диска на Windows

Перед записью zip больше не вызываем mkdir для E:\ / C:\ — на Windows это даёт operation not permitted.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Ivan Fontosh
2026-09-10 18:09:48 +08:00
parent 48a9c933af
commit 3d0474fe97
5 changed files with 63 additions and 12 deletions
+12 -11
View File
@@ -112,6 +112,7 @@ import { getAppSemanticVersion } from '../versionInfo';
import { reconcileAssetFiles } from './assetPrune';
import { recoverOrphanProjectZipTmpInRoot, replaceFileAtomic } from './atomicReplace';
import { ensureParentDir } from './ensureParentDir';
import { rmWithRetries } from './fsRetry';
import { optimizeImageBufferVisuallyLossless } from './optimizeImageImport.lib.mjs';
import { getLegacyProjectsRootDirs, getProjectsCacheRootDir, getProjectsRootDir } from './paths';
@@ -475,7 +476,7 @@ export class ZipProjectStore {
const relPath = `assets/${id}_${safeOrig}`;
const abs = path.join(open.cacheDir, relPath);
const sha256 = crypto.createHash('sha256').update(buf).digest('hex');
await fs.mkdir(path.dirname(abs), { recursive: true });
await ensureParentDir(abs);
await fs.writeFile(abs, buf);
const asset = buildMediaAsset(id, kind0, orig, relPath, sha256, buf.length);
@@ -1165,7 +1166,7 @@ export class ZipProjectStore {
const safeOrig = sanitizeFileName(orig);
const relPath = `assets/${id}_${safeOrig}`;
const abs = path.join(open.cacheDir, relPath);
await fs.mkdir(path.dirname(abs), { recursive: true });
await ensureParentDir(abs);
await fs.copyFile(filePath, abs);
staged.push(buildMediaAsset(id, kind, orig, relPath, sha256, buf.length));
}
@@ -1226,7 +1227,7 @@ export class ZipProjectStore {
const safeOrig = sanitizeFileName(orig);
const relPath = `assets/${id}_${safeOrig}`;
const abs = path.join(open.cacheDir, relPath);
await fs.mkdir(path.dirname(abs), { recursive: true });
await ensureParentDir(abs);
await fs.copyFile(filePath, abs);
staged.push(buildMediaAsset(id, kind, orig, relPath, sha256, buf.length));
}
@@ -1316,7 +1317,7 @@ export class ZipProjectStore {
const safeOrig = sanitizeFileName(orig);
const relPath = `assets/${id}_${safeOrig}`;
const abs = path.join(open.cacheDir, relPath);
await fs.mkdir(path.dirname(abs), { recursive: true });
await ensureParentDir(abs);
await fs.writeFile(abs, buf);
stagedAsset = buildMediaAsset(id, kind, orig, relPath, sha256, buf.length);
nextAssetId = id;
@@ -1551,7 +1552,7 @@ export class ZipProjectStore {
const safeOrig = sanitizeFileName(orig);
const relPath = `assets/${id}_${safeOrig}`;
const abs = path.join(open.cacheDir, relPath);
await fs.mkdir(path.dirname(abs), { recursive: true });
await ensureParentDir(abs);
await fs.writeFile(abs, buf);
stagedAsset = buildMediaAsset(id, kind, orig, relPath, sha256, buf.length);
nextAssetId = id;
@@ -2038,7 +2039,7 @@ export class ZipProjectStore {
private async packZipFromCache(cacheDir: string, zipPath: string): Promise<void> {
const tmpPath = `${zipPath}.tmp`;
await fs.mkdir(path.dirname(zipPath), { recursive: true });
await ensureParentDir(zipPath);
await zipDir(cacheDir, tmpPath);
const st = await fs.stat(tmpPath).catch(() => null);
if (!st?.isFile() || st.size < 22) {
@@ -2178,7 +2179,7 @@ export class ZipProjectStore {
}
const src = path.join(getProjectsRootDir(), entry.fileName);
const dest = path.resolve(destinationPath);
await fs.mkdir(path.dirname(dest), { recursive: true });
await ensureParentDir(dest);
if (onProgress) onProgress({ stage: 'copy', percent: 1, detail: 'Копирование…' });
await copyFileWithProgress(src, dest, (pct) => {
if (onProgress) onProgress({ stage: 'copy', percent: pct, detail: 'Копирование…' });
@@ -2291,7 +2292,7 @@ export class ZipProjectStore {
if (!a) continue;
const srcAbs = path.join(snap.cacheDir, a.relPath);
const destAbs = path.join(exportCache, a.relPath);
await fs.mkdir(path.dirname(destAbs), { recursive: true });
await ensureParentDir(destAbs);
await fs.copyFile(srcAbs, destAbs);
if (onProgress) {
const pct = Math.round(((i + 1) / Math.max(1, assetIds.length)) * 75);
@@ -2428,7 +2429,7 @@ export class ZipProjectStore {
if (!srcAsset || !tgtMeta) continue;
const srcAbs = path.join(sourceCache, srcAsset.relPath);
const destAbs = path.join(targetCache, tgtMeta.relPath);
await fs.mkdir(path.dirname(destAbs), { recursive: true });
await ensureParentDir(destAbs);
await fs.copyFile(srcAbs, destAbs);
if (onProgress) {
const pct = Math.round(((i + 1) / Math.max(1, assetCopies.length)) * 90);
@@ -2894,7 +2895,7 @@ async function copyFileWithProgress(
onPercent(100);
return;
}
await fs.mkdir(path.dirname(dest), { recursive: true });
await ensureParentDir(dest);
await new Promise<void>((resolve, reject) => {
let done = 0;
const rs = fssync.createReadStream(src);
@@ -3027,7 +3028,7 @@ async function zipDir(srcDir: string, outZipPath: string): Promise<void> {
}
zipfile.addBuffer(buf, rel, zipOptionsForRelativeEntry(rel));
}
await fs.mkdir(path.dirname(outZipPath), { recursive: true });
await ensureParentDir(outZipPath);
const out = fssync.createWriteStream(outZipPath);
const done = new Promise<void>((resolve, reject) => {
out.on('close', resolve);