mirror of
https://github.com/bookorbit/bookorbit.git
synced 2026-11-03 00:11:37 -06:00
fix(books): downgrade archiver to v7 to restore zip export (#41)
archiver v8 switched to pure ESM with no default export and a completely different API, breaking every zip export request with TypeError: (0 , _archiver.default) is not a function. The NestJS SWC build produces CJS output, which cannot import the new ESM-only package at runtime. Downgrade to ~7.0.1 (the last CJS release) and add a Dependabot ignore rule for archiver major bumps so this cannot recur silently. Fixes #31
This commit is contained in:
@@ -14,6 +14,10 @@ updates:
|
||||
- "npm"
|
||||
commit-message:
|
||||
prefix: "chore(deps)"
|
||||
ignore:
|
||||
# archiver v8 is ESM-only with a completely different API; pin to v7 until we migrate
|
||||
- dependency-name: "archiver"
|
||||
update-types: ["version-update:semver-major"]
|
||||
groups:
|
||||
production-dependencies:
|
||||
dependency-type: "production"
|
||||
|
||||
Generated
+12272
-6663
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -64,7 +64,7 @@
|
||||
"@nestjs/websockets": "^11.1.21",
|
||||
"@parcel/watcher": "^2.5.6",
|
||||
"@types/archiver": "^7.0.0",
|
||||
"archiver": "^8.0.0",
|
||||
"archiver": "~7.0.1",
|
||||
"bcryptjs": "^3.0.3",
|
||||
"cheerio": "^1.2.0",
|
||||
"class-transformer": "^0.5.1",
|
||||
|
||||
@@ -559,6 +559,98 @@ describe('BookController', () => {
|
||||
expect(archive.abort).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('aborts archive on raw "aborted" event', async () => {
|
||||
const { controller, bookService } = makeController();
|
||||
const { reply, emitRawEvent } = makeReply();
|
||||
bookService.getExportFiles.mockResolvedValue({
|
||||
files: [{ absolutePath: '/books/a.epub', zipPath: 'A.epub', sizeBytes: 10 }],
|
||||
projectedBytes: 10,
|
||||
bookCount: 1,
|
||||
scope: 'primary',
|
||||
});
|
||||
const archive = {
|
||||
pipe: vi.fn(),
|
||||
file: vi.fn(),
|
||||
on: vi.fn().mockReturnThis(),
|
||||
abort: vi.fn(),
|
||||
finalize: vi.fn().mockImplementation(() => {
|
||||
emitRawEvent('aborted');
|
||||
return Promise.reject(new Error('stream aborted'));
|
||||
}),
|
||||
};
|
||||
(archiver as unknown as vi.Mock).mockReturnValueOnce(archive);
|
||||
|
||||
await expect(controller.exportBooks({ bookIds: [1], allFormats: false }, makeUser(), reply)).resolves.toBeUndefined();
|
||||
expect(archive.abort).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('releases export slot when zip archive export fails', async () => {
|
||||
const { controller, bookService } = makeController();
|
||||
const { reply } = makeReply();
|
||||
const release = vi.fn();
|
||||
bookService.acquireExportSlot.mockReturnValueOnce(release);
|
||||
bookService.getExportFiles.mockResolvedValue({
|
||||
files: [{ absolutePath: '/books/a.epub', zipPath: 'A.epub', sizeBytes: 10 }],
|
||||
projectedBytes: 10,
|
||||
bookCount: 1,
|
||||
scope: 'primary',
|
||||
});
|
||||
const archive = {
|
||||
pipe: vi.fn(),
|
||||
file: vi.fn(),
|
||||
on: vi.fn().mockReturnThis(),
|
||||
abort: vi.fn(),
|
||||
finalize: vi.fn().mockRejectedValue(new Error('zip failure')),
|
||||
};
|
||||
(archiver as unknown as vi.Mock).mockReturnValueOnce(archive);
|
||||
|
||||
await expect(controller.exportBooks({ bookIds: [1], allFormats: false }, makeUser(), reply)).rejects.toThrow('zip failure');
|
||||
expect(release).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('archive warning event rejects the zip export', async () => {
|
||||
const { controller, bookService } = makeController();
|
||||
const { reply } = makeReply();
|
||||
bookService.getExportFiles.mockResolvedValue({
|
||||
files: [{ absolutePath: '/books/a.epub', zipPath: 'A.epub', sizeBytes: 10 }],
|
||||
projectedBytes: 10,
|
||||
bookCount: 1,
|
||||
scope: 'primary',
|
||||
});
|
||||
const listeners: Record<string, (err: Error) => void> = {};
|
||||
const archive = {
|
||||
pipe: vi.fn(),
|
||||
file: vi.fn(),
|
||||
on: vi.fn().mockImplementation((event: string, cb: (err: Error) => void) => {
|
||||
listeners[event] = cb;
|
||||
return archive;
|
||||
}),
|
||||
abort: vi.fn(),
|
||||
finalize: vi.fn().mockImplementation(() => {
|
||||
void Promise.resolve().then(() => listeners['warning']?.(new Error('archive warning')));
|
||||
return new Promise<void>(() => {});
|
||||
}),
|
||||
};
|
||||
(archiver as unknown as vi.Mock).mockReturnValueOnce(archive);
|
||||
|
||||
await expect(controller.exportBooks({ bookIds: [1], allFormats: false }, makeUser(), reply)).rejects.toThrow('archive warning');
|
||||
});
|
||||
|
||||
it('exportBooksDownload with scope=all passes through correctly', async () => {
|
||||
const { controller, bookService } = makeController();
|
||||
const { reply } = makeReply();
|
||||
bookService.getExportFiles.mockResolvedValue({
|
||||
files: [{ absolutePath: '/books/a.epub', zipPath: 'A.epub', sizeBytes: 10 }],
|
||||
projectedBytes: 10,
|
||||
bookCount: 1,
|
||||
scope: 'all',
|
||||
});
|
||||
|
||||
await controller.exportBooksDownload('5,6', 'all', makeUser(), reply);
|
||||
|
||||
expect(bookService.getExportFiles).toHaveBeenCalledWith([5, 6], expect.any(Object), 'all');
|
||||
});
|
||||
|
||||
it('delegates book-level progress endpoint to service with current user id', async () => {
|
||||
const { controller, bookService } = makeController();
|
||||
const user = makeUser();
|
||||
|
||||
Reference in New Issue
Block a user