diff --git a/.talismanrc b/.talismanrc index 1038777..52db5da 100644 --- a/.talismanrc +++ b/.talismanrc @@ -16,5 +16,14 @@ fileignoreconfig: - filename: src/adapters/github.test.ts checksum: b3d3d3a3c14adde103152d2ac4e1c4b7121a5f7533a87c3cc600f6d25fb59d1b - filename: src/adapters/file-upload.test.ts - checksum: 962fd8b45578914926d20857ddc3d1bc2fa19e8b192c6dfdc03d214ae1898b59 -version: "1.0" \ No newline at end of file + allowed_patterns: + - "--(disable|enable)-cs-auth is passed" +- filename: src/adapters/file-upload.ts + allowed_patterns: + - "const \\{ formFieldKey, formFieldValue \\} of fields" + - "formData\\.append\\(formFieldKey, formFieldValue\\)" + - "headers: \\{ key: string; value: string \\}\\[\\]" + - "headers\\?\\.reduce\\(\\(acc, \\{ key, value \\}\\)" + - "\\(\\{ key, value \\}\\) => \\(\\{ key, value \\}\\)\\)" + - "const \\{ token, apiKey \\} = configHandler\\.get" +version: "1.0" diff --git a/package-lock.json b/package-lock.json index f288658..a184ab5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,7 +20,7 @@ "@rollup/plugin-typescript": "^12.1.2", "@types/express": "^4.17.21", "@types/express-serve-static-core": "^4.17.34", - "adm-zip": "^0.5.18", + "adm-zip": "^0.6.0", "chalk": "^4.1.2", "cross-fetch": "^4.1.0", "dotenv": "^16.4.7", @@ -38,7 +38,6 @@ }, "devDependencies": { "@oclif/test": "^4.1.3", - "@types/adm-zip": "^0.5.7", "@types/chai": "^4.3.20", "@types/ini": "^1.3.34", "@types/jest": "^29.5.14", @@ -3377,14 +3376,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/adm-zip": { - "version": "0.5.8", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/babel__core": { "version": "7.20.5", "dev": true, @@ -3951,12 +3942,12 @@ } }, "node_modules/adm-zip": { - "version": "0.5.18", - "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.18.tgz", - "integrity": "sha512-ufJnssQGbxzLNS1Ho9bCtX4rQKCCvoVuDLHoJyc3F9dOGDB4BkWs2Ci0kv53lqocAEQ/Cbi+I2XCsNYGqVYqng==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.6.0.tgz", + "integrity": "sha512-XleryMhbuksdKtofnWZ9Sk+4CUTbms4Mb/EU32SZwToAyZ5RgVos/ki8n+yr0LWHOGKuakbXTuuYNHLQjhddgg==", "license": "MIT", "engines": { - "node": ">=12.0" + "node": ">=14.0" } }, "node_modules/agent-base": { @@ -11653,7 +11644,9 @@ "license": "MIT" }, "node_modules/qs": { - "version": "6.15.3", + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz", + "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==", "license": "BSD-3-Clause", "dependencies": { "es-define-property": "^1.0.1", diff --git a/package.json b/package.json index 6a1e726..4e20ba3 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "@rollup/plugin-typescript": "^12.1.2", "@types/express": "^4.17.21", "@types/express-serve-static-core": "^4.17.34", - "adm-zip": "^0.5.18", + "adm-zip": "^0.6.0", "chalk": "^4.1.2", "cross-fetch": "^4.1.0", "dotenv": "^16.4.7", @@ -46,7 +46,6 @@ }, "devDependencies": { "@oclif/test": "^4.1.3", - "@types/adm-zip": "^0.5.7", "@types/chai": "^4.3.20", "@types/ini": "^1.3.34", "@types/jest": "^29.5.14", @@ -115,7 +114,7 @@ "@eslint/eslintrc": { "ajv": "^6.12.6" }, - "qs": "^6.15.2", + "qs": "^6.16.0", "tmp": "^0.2.4", "fast-uri": "^3.1.6", "js-yaml": "^4.3.1", diff --git a/src/adapters/file-upload.test.ts b/src/adapters/file-upload.test.ts index 7b0bd27..60aa10c 100644 --- a/src/adapters/file-upload.test.ts +++ b/src/adapters/file-upload.test.ts @@ -1,10 +1,26 @@ +import AdmZip from 'adm-zip'; import { fileUploadAdapter as cliUtilitiesJestMock } from '../test/mocks/cli-utilities'; import FileUpload from './file-upload'; import BaseClass from './base-class'; import { cliux } from '@contentstack/cli-utilities'; import { DeploymentStatus } from '../types/launch'; +import { getFileList } from '../util/fs'; jest.mock('@contentstack/cli-utilities', () => cliUtilitiesJestMock); +jest.mock('../util/fs', () => ({ getFileList: jest.fn() })); + +// adm-zip defines its methods as own properties on each instance, not on the prototype, +// so the constructor is mocked rather than spied on. esModuleInterop resolves the source's +// default import to `.default`, hence the __esModule shape. +const writeZipPromiseMock = jest.fn(); +jest.mock('adm-zip', () => ({ + __esModule: true, + default: jest.fn().mockImplementation(() => ({ + addLocalFile: jest.fn(), + addLocalFolder: jest.fn(), + writeZipPromise: writeZipPromiseMock, + })), +})); describe('FileUpload Adapter', () => { let logMock: jest.Mock; @@ -827,5 +843,51 @@ describe('FileUpload Adapter', () => { handleEnvImportFlowMock.mockRestore(); }); }); + + describe('archive', () => { + const createInstance = () => + new FileUpload({ + config: { + projectBasePath: '/tmp/launch-project', + fileUploadConfig: { exclude: [] }, + }, + log: logMock, + exit: exitMock, + } as any); + + beforeEach(() => { + (getFileList as jest.Mock).mockResolvedValue([]); + // the suite-wide afterEach calls jest.resetAllMocks(), which strips the + // constructor implementation registered in the jest.mock factory above. + (AdmZip as unknown as jest.Mock).mockImplementation(() => ({ + addLocalFile: jest.fn(), + addLocalFolder: jest.fn(), + writeZipPromise: writeZipPromiseMock, + })); + }); + + it('should log the failure once and exit when zipping rejects', async () => { + writeZipPromiseMock.mockRejectedValue(new Error('disk full')); + const fileUploadInstance = createInstance(); + + await expect(fileUploadInstance.archive()).rejects.toThrow('1'); + + expect(logMock).toHaveBeenCalledTimes(1); + expect(logMock).toHaveBeenCalledWith('Zipping project process failed! Please try again.'); + expect(exitMock).toHaveBeenCalledWith(1); + }); + + it('should return the zip details and not exit when zipping resolves', async () => { + writeZipPromiseMock.mockResolvedValue(undefined); + const fileUploadInstance = createInstance(); + + const result = await fileUploadInstance.archive(); + + expect(result.projectName).toBe('launch-project'); + expect(result.zipName).toMatch(/^\d+_launch-project\.zip$/); + expect(exitMock).not.toHaveBeenCalled(); + expect(logMock).not.toHaveBeenCalled(); + }); + }); }); diff --git a/src/adapters/file-upload.ts b/src/adapters/file-upload.ts index 7b2fd09..7959500 100755 --- a/src/adapters/file-upload.ts +++ b/src/adapters/file-upload.ts @@ -336,12 +336,9 @@ export default class FileUpload extends BaseClass { } } - const status = await zip.writeZipPromise(zipPath).catch(() => { - this.log('Zipping project process failed! Please try again.'); - this.exit(1); - }); - - if (!status) { + try { + await zip.writeZipPromise(zipPath); + } catch { this.log('Zipping project process failed! Please try again.'); this.exit(1); }