Compare commits
No commits in common. "a5d53eff7412a03971d8c1746acde83cca65f3b5" and "8f5358323cf72d363f4c7e5bb8f92b589241d539" have entirely different histories.
a5d53eff74
...
8f5358323c
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "real-debrid-downloader",
|
"name": "real-debrid-downloader",
|
||||||
"version": "1.7.88",
|
"version": "1.7.87",
|
||||||
"description": "Desktop downloader",
|
"description": "Desktop downloader",
|
||||||
"main": "build/main/main/main.js",
|
"main": "build/main/main/main.js",
|
||||||
"author": "Sucukdeluxe",
|
"author": "Sucukdeluxe",
|
||||||
|
|||||||
@ -1110,7 +1110,7 @@ export function buildAutoRenameBaseNameFromFoldersWithOptions(
|
|||||||
|
|
||||||
export function resolveArchiveItemsFromList(archiveName: string, items: DownloadItem[]): DownloadItem[] {
|
export function resolveArchiveItemsFromList(archiveName: string, items: DownloadItem[]): DownloadItem[] {
|
||||||
const normalizeArchiveMatchName = (value: string): string =>
|
const normalizeArchiveMatchName = (value: string): string =>
|
||||||
stripDuplicateSuffixBeforeExtension(path.basename(String(value || "")));
|
path.basename(String(value || "")).replace(/ \(\d+\)(?=\.[^.]+$)/, "");
|
||||||
const entryLower = normalizeArchiveMatchName(archiveName).toLowerCase();
|
const entryLower = normalizeArchiveMatchName(archiveName).toLowerCase();
|
||||||
|
|
||||||
// Helper: get item basename (try targetPath first, then fileName)
|
// Helper: get item basename (try targetPath first, then fileName)
|
||||||
@ -1192,54 +1192,6 @@ export function resolveArchiveItemsFromList(archiveName: string, items: Download
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
function stripDuplicateSuffixBeforeExtension(fileName: string): string {
|
|
||||||
return String(fileName || "").replace(/ \(\d+\)(?=\.[^.]+$)/, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
function hasDuplicateSuffixBeforeExtension(fileName: string): boolean {
|
|
||||||
const normalized = stripDuplicateSuffixBeforeExtension(fileName);
|
|
||||||
return normalized !== String(fileName || "");
|
|
||||||
}
|
|
||||||
|
|
||||||
function startupDuplicateStateRank(item: DownloadItem, diskExists: boolean): number {
|
|
||||||
let rank = diskExists ? 40 : 0;
|
|
||||||
switch (item.status) {
|
|
||||||
case "completed":
|
|
||||||
rank += 40;
|
|
||||||
break;
|
|
||||||
case "downloading":
|
|
||||||
case "validating":
|
|
||||||
case "integrity_check":
|
|
||||||
rank += 25;
|
|
||||||
break;
|
|
||||||
case "queued":
|
|
||||||
case "reconnect_wait":
|
|
||||||
case "paused":
|
|
||||||
rank += 10;
|
|
||||||
break;
|
|
||||||
case "failed":
|
|
||||||
rank += 5;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
const fullStatus = String(item.fullStatus || "").trim();
|
|
||||||
if (isExtractedLabel(fullStatus)) {
|
|
||||||
rank += 65;
|
|
||||||
} else if (/^fertig\b/i.test(fullStatus)) {
|
|
||||||
rank += 30;
|
|
||||||
} else if (isTransientExtractStatus(fullStatus)) {
|
|
||||||
rank += 20;
|
|
||||||
} else if (isExtractErrorLabel(fullStatus)) {
|
|
||||||
rank += 5;
|
|
||||||
}
|
|
||||||
rank += Math.max(0, Math.min(9, Math.floor(Number(item.progressPercent || 0) / 12)));
|
|
||||||
if (Number(item.downloadedBytes || 0) > 0) {
|
|
||||||
rank += 1;
|
|
||||||
}
|
|
||||||
return rank;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function extractArchiveNameFromExtractorLogMessage(message: string): string | null {
|
export function extractArchiveNameFromExtractorLogMessage(message: string): string | null {
|
||||||
const text = String(message || "").trim();
|
const text = String(message || "").trim();
|
||||||
if (!text) {
|
if (!text) {
|
||||||
@ -1423,11 +1375,11 @@ export class DownloadManager extends EventEmitter {
|
|||||||
}
|
}
|
||||||
this.applyOnStartCleanupPolicy();
|
this.applyOnStartCleanupPolicy();
|
||||||
this.normalizeSessionStatuses();
|
this.normalizeSessionStatuses();
|
||||||
this.restoreTargetPathReservations();
|
|
||||||
this.resolveExistingQueuedOpaqueFilenames();
|
|
||||||
this.revalidateCompletedItems();
|
|
||||||
void this.recoverRetryableItems("startup").catch((err) => logger.warn(`recoverRetryableItems Fehler (startup): ${compactErrorText(err)}`));
|
void this.recoverRetryableItems("startup").catch((err) => logger.warn(`recoverRetryableItems Fehler (startup): ${compactErrorText(err)}`));
|
||||||
this.recoverPostProcessingOnStartup();
|
this.recoverPostProcessingOnStartup();
|
||||||
|
this.resolveExistingQueuedOpaqueFilenames();
|
||||||
|
this.restoreTargetPathReservations();
|
||||||
|
this.revalidateCompletedItems();
|
||||||
this.checkExistingRapidgatorLinks();
|
this.checkExistingRapidgatorLinks();
|
||||||
void this.cleanupExistingExtractedArchives().catch((err) => logger.warn(`cleanupExistingExtractedArchives Fehler (constructor): ${compactErrorText(err)}`));
|
void this.cleanupExistingExtractedArchives().catch((err) => logger.warn(`cleanupExistingExtractedArchives Fehler (constructor): ${compactErrorText(err)}`));
|
||||||
}
|
}
|
||||||
@ -4976,137 +4928,10 @@ export class DownloadManager extends EventEmitter {
|
|||||||
if (restored > 0) {
|
if (restored > 0) {
|
||||||
logger.info(`restoreTargetPathReservations: ${restored} Pfade aus Session wiederhergestellt`);
|
logger.info(`restoreTargetPathReservations: ${restored} Pfade aus Session wiederhergestellt`);
|
||||||
}
|
}
|
||||||
this.reconcileDuplicateSuffixSessionItems();
|
|
||||||
// Fix legacy (N) suffix files: rename back to original if original path is free
|
// Fix legacy (N) suffix files: rename back to original if original path is free
|
||||||
this.fixDuplicateSuffixFiles();
|
this.fixDuplicateSuffixFiles();
|
||||||
}
|
}
|
||||||
|
|
||||||
private reconcileDuplicateSuffixSessionItems(): void {
|
|
||||||
let merged = 0;
|
|
||||||
const touchedPackageIds = new Set<string>();
|
|
||||||
|
|
||||||
for (const packageId of this.session.packageOrder) {
|
|
||||||
const pkg = this.session.packages[packageId];
|
|
||||||
if (!pkg) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const itemId of [...pkg.itemIds]) {
|
|
||||||
const duplicateItem = this.session.items[itemId];
|
|
||||||
if (!duplicateItem) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const duplicateTargetPath = String(duplicateItem.targetPath || "").trim();
|
|
||||||
if (!duplicateTargetPath) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const duplicateBaseName = path.basename(duplicateTargetPath);
|
|
||||||
if (!hasDuplicateSuffixBeforeExtension(duplicateBaseName)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const canonicalBaseName = stripDuplicateSuffixBeforeExtension(duplicateBaseName);
|
|
||||||
const canonicalPath = path.join(path.dirname(duplicateTargetPath), canonicalBaseName);
|
|
||||||
const canonicalKey = pathKey(canonicalPath);
|
|
||||||
let primaryItem = Object.values(this.session.items).find((candidate) =>
|
|
||||||
candidate.packageId === packageId
|
|
||||||
&& candidate.id !== duplicateItem.id
|
|
||||||
&& (
|
|
||||||
pathKey(String(candidate.targetPath || "")) === canonicalKey
|
|
||||||
|| (
|
|
||||||
!candidate.targetPath
|
|
||||||
&& stripDuplicateSuffixBeforeExtension(candidate.fileName || "") === canonicalBaseName
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
if (!primaryItem) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const duplicateExists = fs.existsSync(duplicateTargetPath);
|
|
||||||
let canonicalExists = fs.existsSync(canonicalPath);
|
|
||||||
const primaryWins = startupDuplicateStateRank(primaryItem, canonicalExists) >= startupDuplicateStateRank(duplicateItem, duplicateExists);
|
|
||||||
|
|
||||||
if (duplicateExists && !canonicalExists) {
|
|
||||||
try {
|
|
||||||
fs.renameSync(duplicateTargetPath, canonicalPath);
|
|
||||||
canonicalExists = true;
|
|
||||||
logger.info(`startupDuplicateMerge: ${path.basename(duplicateTargetPath)} → ${canonicalBaseName}`);
|
|
||||||
} catch (err) {
|
|
||||||
logger.warn(`startupDuplicateMerge: Umbenennung fehlgeschlagen ${duplicateTargetPath}: ${compactErrorText(err)}`);
|
|
||||||
}
|
|
||||||
} else if (duplicateExists && canonicalExists && primaryWins) {
|
|
||||||
try {
|
|
||||||
fs.rmSync(duplicateTargetPath, { force: true });
|
|
||||||
} catch {
|
|
||||||
// ignore, stale duplicate can remain on disk if Windows still holds a handle
|
|
||||||
}
|
|
||||||
} else if (duplicateExists && canonicalExists && !primaryWins && primaryItem.status !== "completed") {
|
|
||||||
try {
|
|
||||||
fs.rmSync(canonicalPath, { force: true });
|
|
||||||
fs.renameSync(duplicateTargetPath, canonicalPath);
|
|
||||||
canonicalExists = true;
|
|
||||||
logger.info(`startupDuplicateMerge: ersetze verwaisten Originalpfad ${canonicalBaseName} durch ${path.basename(duplicateTargetPath)}`);
|
|
||||||
} catch (err) {
|
|
||||||
logger.warn(`startupDuplicateMerge: Austausch fehlgeschlagen ${canonicalPath}: ${compactErrorText(err)}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const duplicateShouldWin = !primaryWins || (duplicateItem.status === "completed" && primaryItem.status !== "completed");
|
|
||||||
if (duplicateShouldWin) {
|
|
||||||
primaryItem.status = duplicateItem.status;
|
|
||||||
primaryItem.fullStatus = duplicateItem.fullStatus;
|
|
||||||
primaryItem.lastError = duplicateItem.lastError;
|
|
||||||
primaryItem.downloadedBytes = Math.max(Number(primaryItem.downloadedBytes || 0), Number(duplicateItem.downloadedBytes || 0));
|
|
||||||
primaryItem.totalBytes = Math.max(Number(primaryItem.totalBytes || 0), Number(duplicateItem.totalBytes || 0)) || primaryItem.totalBytes;
|
|
||||||
primaryItem.progressPercent = Math.max(Number(primaryItem.progressPercent || 0), Number(duplicateItem.progressPercent || 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (canonicalExists) {
|
|
||||||
try {
|
|
||||||
const stat = fs.statSync(canonicalPath);
|
|
||||||
primaryItem.downloadedBytes = Math.max(Number(primaryItem.downloadedBytes || 0), stat.size);
|
|
||||||
if (!primaryItem.totalBytes || primaryItem.totalBytes < stat.size) {
|
|
||||||
primaryItem.totalBytes = stat.size;
|
|
||||||
}
|
|
||||||
if (primaryItem.status === "completed") {
|
|
||||||
primaryItem.progressPercent = 100;
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// ignore stat failures; persisted metadata remains as-is
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
primaryItem.fileName = canonicalBaseName;
|
|
||||||
primaryItem.targetPath = canonicalPath;
|
|
||||||
primaryItem.updatedAt = Math.max(Number(primaryItem.updatedAt || 0), Number(duplicateItem.updatedAt || 0), nowMs());
|
|
||||||
this.claimedTargetPathByItem.set(primaryItem.id, canonicalPath);
|
|
||||||
this.reservedTargetPaths.set(canonicalKey, primaryItem.id);
|
|
||||||
|
|
||||||
this.retryAfterByItem.delete(duplicateItem.id);
|
|
||||||
this.retryStateByItem.delete(duplicateItem.id);
|
|
||||||
this.releaseTargetPath(duplicateItem.id);
|
|
||||||
this.dropItemContribution(duplicateItem.id);
|
|
||||||
delete this.session.items[duplicateItem.id];
|
|
||||||
pkg.itemIds = pkg.itemIds.filter((candidateId) => candidateId !== duplicateItem.id);
|
|
||||||
this.itemCount = Math.max(0, this.itemCount - 1);
|
|
||||||
merged += 1;
|
|
||||||
touchedPackageIds.add(packageId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (merged > 0) {
|
|
||||||
for (const packageId of touchedPackageIds) {
|
|
||||||
const pkg = this.session.packages[packageId];
|
|
||||||
if (pkg) {
|
|
||||||
this.refreshPackageStatus(pkg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
logger.info(`reconcileDuplicateSuffixSessionItems: ${merged} Duplikat-Items zusammengeführt`);
|
|
||||||
this.persistSoon();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Re-validate "completed" items on startup: if the file on disk is significantly
|
/** Re-validate "completed" items on startup: if the file on disk is significantly
|
||||||
* smaller than expected, the item was incorrectly marked completed (e.g. by the
|
* smaller than expected, the item was incorrectly marked completed (e.g. by the
|
||||||
* old 50% recovery threshold). Reset to "queued" so it gets re-downloaded. */
|
* old 50% recovery threshold). Reset to "queued" so it gets re-downloaded. */
|
||||||
|
|||||||
@ -319,196 +319,6 @@ describe("download manager", () => {
|
|||||||
expect((manager as any).session.packages[packageId].status).toBe("queued");
|
expect((manager as any).session.packages[packageId].status).toBe("queued");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("merges duplicate-suffixed completed startup items back into the canonical queued item", () => {
|
|
||||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), "rd-startup-dup-merge-"));
|
|
||||||
tempDirs.push(root);
|
|
||||||
|
|
||||||
const session = emptySession();
|
|
||||||
const packageId = "startup-dup-pkg";
|
|
||||||
const originalItemId = "startup-dup-original";
|
|
||||||
const duplicateItemId = "startup-dup-copy";
|
|
||||||
const createdAt = Date.now() - 20_000;
|
|
||||||
const outputDir = path.join(root, "downloads", "Startup Duplicate Merge");
|
|
||||||
const extractDir = path.join(root, "extract", "Startup Duplicate Merge");
|
|
||||||
fs.mkdirSync(outputDir, { recursive: true });
|
|
||||||
fs.mkdirSync(extractDir, { recursive: true });
|
|
||||||
|
|
||||||
const canonicalPath = path.join(outputDir, "episode.part1.rar");
|
|
||||||
const duplicatePath = path.join(outputDir, "episode.part1 (1).rar");
|
|
||||||
fs.writeFileSync(duplicatePath, Buffer.alloc(128, 7));
|
|
||||||
|
|
||||||
session.packageOrder = [packageId];
|
|
||||||
session.packages[packageId] = {
|
|
||||||
id: packageId,
|
|
||||||
name: "Startup Duplicate Merge",
|
|
||||||
outputDir,
|
|
||||||
extractDir,
|
|
||||||
status: "failed",
|
|
||||||
itemIds: [originalItemId, duplicateItemId],
|
|
||||||
cancelled: false,
|
|
||||||
enabled: true,
|
|
||||||
priority: "normal",
|
|
||||||
createdAt,
|
|
||||||
updatedAt: createdAt
|
|
||||||
};
|
|
||||||
session.items[originalItemId] = {
|
|
||||||
id: originalItemId,
|
|
||||||
packageId,
|
|
||||||
url: "https://example.com/episode.part1.rar",
|
|
||||||
provider: "realdebrid",
|
|
||||||
status: "queued",
|
|
||||||
retries: 0,
|
|
||||||
speedBps: 0,
|
|
||||||
downloadedBytes: 0,
|
|
||||||
totalBytes: 128,
|
|
||||||
progressPercent: 0,
|
|
||||||
fileName: "episode.part1.rar",
|
|
||||||
targetPath: canonicalPath,
|
|
||||||
resumable: true,
|
|
||||||
attempts: 0,
|
|
||||||
lastError: "",
|
|
||||||
fullStatus: "Wartet",
|
|
||||||
createdAt,
|
|
||||||
updatedAt: createdAt
|
|
||||||
};
|
|
||||||
session.items[duplicateItemId] = {
|
|
||||||
id: duplicateItemId,
|
|
||||||
packageId,
|
|
||||||
url: "https://example.com/episode.part1.rar",
|
|
||||||
provider: "realdebrid",
|
|
||||||
status: "completed",
|
|
||||||
retries: 0,
|
|
||||||
speedBps: 0,
|
|
||||||
downloadedBytes: 128,
|
|
||||||
totalBytes: 128,
|
|
||||||
progressPercent: 100,
|
|
||||||
fileName: "episode.part1.rar",
|
|
||||||
targetPath: duplicatePath,
|
|
||||||
resumable: true,
|
|
||||||
attempts: 1,
|
|
||||||
lastError: "",
|
|
||||||
fullStatus: "Fertig (128 B)",
|
|
||||||
createdAt,
|
|
||||||
updatedAt: createdAt + 5_000
|
|
||||||
};
|
|
||||||
|
|
||||||
const manager = new DownloadManager(
|
|
||||||
{
|
|
||||||
...defaultSettings(),
|
|
||||||
token: "rd-token",
|
|
||||||
outputDir: path.join(root, "downloads"),
|
|
||||||
extractDir: path.join(root, "extract"),
|
|
||||||
autoExtract: false
|
|
||||||
},
|
|
||||||
session,
|
|
||||||
createStoragePaths(path.join(root, "state"))
|
|
||||||
);
|
|
||||||
|
|
||||||
const current = (manager as any).session;
|
|
||||||
expect(current.packages[packageId].itemIds).toEqual([originalItemId]);
|
|
||||||
expect(current.items[duplicateItemId]).toBeUndefined();
|
|
||||||
expect(current.items[originalItemId].status).toBe("completed");
|
|
||||||
expect(current.items[originalItemId].fullStatus).toBe("Fertig (128 B)");
|
|
||||||
expect(current.items[originalItemId].targetPath).toBe(canonicalPath);
|
|
||||||
expect(fs.existsSync(canonicalPath)).toBe(true);
|
|
||||||
expect(fs.existsSync(duplicatePath)).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("keeps a stronger extracted canonical startup state when removing stale duplicate copies", () => {
|
|
||||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), "rd-startup-dup-keep-"));
|
|
||||||
tempDirs.push(root);
|
|
||||||
|
|
||||||
const session = emptySession();
|
|
||||||
const packageId = "startup-dup-keep-pkg";
|
|
||||||
const originalItemId = "startup-dup-keep-original";
|
|
||||||
const duplicateItemId = "startup-dup-keep-copy";
|
|
||||||
const createdAt = Date.now() - 20_000;
|
|
||||||
const outputDir = path.join(root, "downloads", "Startup Duplicate Keep");
|
|
||||||
const extractDir = path.join(root, "extract", "Startup Duplicate Keep");
|
|
||||||
fs.mkdirSync(outputDir, { recursive: true });
|
|
||||||
fs.mkdirSync(extractDir, { recursive: true });
|
|
||||||
|
|
||||||
const canonicalPath = path.join(outputDir, "episode.part1.rar");
|
|
||||||
const duplicatePath = path.join(outputDir, "episode.part1 (1).rar");
|
|
||||||
fs.writeFileSync(duplicatePath, Buffer.alloc(256, 9));
|
|
||||||
|
|
||||||
session.packageOrder = [packageId];
|
|
||||||
session.packages[packageId] = {
|
|
||||||
id: packageId,
|
|
||||||
name: "Startup Duplicate Keep",
|
|
||||||
outputDir,
|
|
||||||
extractDir,
|
|
||||||
status: "completed",
|
|
||||||
itemIds: [originalItemId, duplicateItemId],
|
|
||||||
cancelled: false,
|
|
||||||
enabled: true,
|
|
||||||
priority: "normal",
|
|
||||||
createdAt,
|
|
||||||
updatedAt: createdAt
|
|
||||||
};
|
|
||||||
session.items[originalItemId] = {
|
|
||||||
id: originalItemId,
|
|
||||||
packageId,
|
|
||||||
url: "https://example.com/episode.part1.rar",
|
|
||||||
provider: "realdebrid",
|
|
||||||
status: "completed",
|
|
||||||
retries: 0,
|
|
||||||
speedBps: 0,
|
|
||||||
downloadedBytes: 256,
|
|
||||||
totalBytes: 256,
|
|
||||||
progressPercent: 100,
|
|
||||||
fileName: "episode.part1.rar",
|
|
||||||
targetPath: canonicalPath,
|
|
||||||
resumable: true,
|
|
||||||
attempts: 1,
|
|
||||||
lastError: "",
|
|
||||||
fullStatus: "Entpackt - Done (1.0s)",
|
|
||||||
createdAt,
|
|
||||||
updatedAt: createdAt + 10_000
|
|
||||||
};
|
|
||||||
session.items[duplicateItemId] = {
|
|
||||||
id: duplicateItemId,
|
|
||||||
packageId,
|
|
||||||
url: "https://example.com/episode.part1.rar",
|
|
||||||
provider: "realdebrid",
|
|
||||||
status: "completed",
|
|
||||||
retries: 0,
|
|
||||||
speedBps: 0,
|
|
||||||
downloadedBytes: 256,
|
|
||||||
totalBytes: 256,
|
|
||||||
progressPercent: 100,
|
|
||||||
fileName: "episode.part1.rar",
|
|
||||||
targetPath: duplicatePath,
|
|
||||||
resumable: true,
|
|
||||||
attempts: 1,
|
|
||||||
lastError: "Checksum error",
|
|
||||||
fullStatus: "Entpack-Fehler: Checksum error",
|
|
||||||
createdAt,
|
|
||||||
updatedAt: createdAt + 5_000
|
|
||||||
};
|
|
||||||
|
|
||||||
const manager = new DownloadManager(
|
|
||||||
{
|
|
||||||
...defaultSettings(),
|
|
||||||
token: "rd-token",
|
|
||||||
outputDir: path.join(root, "downloads"),
|
|
||||||
extractDir: path.join(root, "extract"),
|
|
||||||
autoExtract: false
|
|
||||||
},
|
|
||||||
session,
|
|
||||||
createStoragePaths(path.join(root, "state"))
|
|
||||||
);
|
|
||||||
|
|
||||||
const current = (manager as any).session;
|
|
||||||
expect(current.packages[packageId].itemIds).toEqual([originalItemId]);
|
|
||||||
expect(current.items[duplicateItemId]).toBeUndefined();
|
|
||||||
expect(current.items[originalItemId].status).toBe("completed");
|
|
||||||
expect(current.items[originalItemId].fullStatus).toBe("Entpackt - Done (1.0s)");
|
|
||||||
expect(current.items[originalItemId].targetPath).toBe(canonicalPath);
|
|
||||||
expect(fs.existsSync(canonicalPath)).toBe(true);
|
|
||||||
expect(fs.existsSync(duplicatePath)).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
function createCompletedArchiveSession(root: string, packageName: string, extractedFileName: string): {
|
function createCompletedArchiveSession(root: string, packageName: string, extractedFileName: string): {
|
||||||
session: ReturnType<typeof emptySession>;
|
session: ReturnType<typeof emptySession>;
|
||||||
packageId: string;
|
packageId: string;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user