diff --git a/package.json b/package.json index 6e69e9a4..60d44d6d 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "@parcel/reporter-cli": "^2.7.0", "ajv": "^8", "ajv-formats": "^2.1.1", - "archiver": "^7.0.1", + "archiver": "^8.0.0", "chalk": "^4", "chokidar": "^3.5.2", "execa": "^5.0.0", diff --git a/src/commands/app/pack.js b/src/commands/app/pack.js index dd83c478..16237201 100644 --- a/src/commands/app/pack.js +++ b/src/commands/app/pack.js @@ -14,7 +14,6 @@ const { Flags, Args } = require('@oclif/core') const path = require('node:path') const fs = require('fs-extra') const aioLogger = require('@adobe/aio-lib-core-logging')('@adobe/aio-cli-plugin-app:pack', { provider: 'debug' }) -const archiver = require('archiver') const yaml = require('js-yaml') const execa = require('execa') const { loadConfigFile, writeFile } = require('../../lib/import-helper') @@ -260,10 +259,12 @@ class Pack extends BaseCommand { * @param {boolean} pathInZip internal path in zip * @returns {Promise} returns with a blank promise when done */ - zipHelper (filePath, out, pathInZip = false) { + async zipHelper (filePath, out, pathInZip = false) { aioLogger.debug(`Creating zip of file/folder '${filePath}'`) + // archiver 8 is ESM-only and exposes a class API; load it lazily via dynamic import from this CommonJS command + const { ZipArchive } = await import('archiver') const stream = fs.createWriteStream(out) - const archive = archiver('zip', { zlib: { level: 9 } }) + const archive = new ZipArchive({ zlib: { level: 9 } }) return new Promise((resolve, reject) => { stream.on('close', () => resolve()) diff --git a/test/commands/app/pack.test.js b/test/commands/app/pack.test.js index 2d6319f1..0c6fd654 100644 --- a/test/commands/app/pack.test.js +++ b/test/commands/app/pack.test.js @@ -17,7 +17,6 @@ const fs = require('fs-extra') const path = require('node:path') const importHelper = require('../../../src/lib/import-helper') const yaml = require('js-yaml') -const archiver = require('archiver') const libConfig = require('@adobe/aio-cli-lib-app-config') @@ -25,7 +24,11 @@ const libConfig = require('@adobe/aio-cli-lib-app-config') jest.mock('execa') jest.mock('fs-extra') jest.mock('../../../src/lib/import-helper') -jest.mock('archiver') +// archiver 8 is ESM-only with a class API; the current mock instance is set per-test. +let currentArchiverMock +jest.unstable_mockModule('archiver', () => ({ + ZipArchive: jest.fn().mockImplementation(() => currentArchiverMock) +})) const mockGetFullConfig = jest.fn() @@ -63,8 +66,6 @@ beforeEach(() => { fs.copy.mockClear() fs.createWriteStream.mockClear() fs.lstatSync.mockClear() - - archiver.mockClear() }) test('exports', async () => { @@ -293,21 +294,29 @@ test('zipHelper', async () => { finalize: jest.fn() } - archiver.mockImplementation(() => archiverMock) + currentArchiverMock = archiverMock + + // zipHelper is async (loads ESM archiver via dynamic import); flush microtasks so the + // archive is set up before triggering the stream 'close'/'error' handlers. + const flush = () => new Promise(resolve => setImmediate(resolve)) const command = new TheCommand() command.argv = [] // not a directory, just a file (see lstatSync mock 1) - command.zipHelper('my-file', 'app.zip') + const p1 = command.zipHelper('my-file', 'app.zip') + await flush() endStream() + await p1 expect(archiverMock.directory).not.toHaveBeenCalled() expect(archiverMock.file).toHaveBeenCalledWith('my-file', { name: 'my-file' }) archiverMock.file.mockClear() // a directory (see lstatSync mock 2) - command.zipHelper('my-folder', 'app.zip') + const p2 = command.zipHelper('my-folder', 'app.zip') + await flush() endStream() + await p2 expect(archiverMock.file).not.toHaveBeenCalled() expect(archiverMock.directory).toHaveBeenCalledWith('my-folder', false) archiverMock.directory.mockClear() @@ -318,8 +327,10 @@ test('zipHelper', async () => { archiverMock.destroy.mockClear() // archiving error, for coverage (see lstatSync mock 4) - command.zipHelper('my-file', 'app.zip').catch(console.error) - onError() + const p4 = command.zipHelper('my-file', 'app.zip').catch(() => {}) + await flush() + onError(new Error('archive error')) + await p4 }) describe('filesToPack', () => {