diff --git a/src/cmd/sign.js b/src/cmd/sign.js index 15d88d426d..e495369581 100644 --- a/src/cmd/sign.js +++ b/src/cmd/sign.js @@ -41,6 +41,7 @@ export default function sign( channel, amoMetadata, uploadSourceCode, + iconFile, webextVersion, }, { @@ -154,6 +155,7 @@ export default function sign( approvalCheckTimeout: approvalTimeout !== undefined ? approvalTimeout : timeout, submissionSource: uploadSourceCode, + iconFile, }); } else { const { diff --git a/src/program.js b/src/program.js index 50a6b5f7b1..6483dbe6e9 100644 --- a/src/program.js +++ b/src/program.js @@ -609,6 +609,13 @@ Example: $0 --help run. 'details. Only used with `use-submission-api`', type: 'string', }, + 'icon-file': { + describe: + 'Path to an image that should be displayed as the addon icon on ' + + 'addons.mozilla.org. Must be square; 128px x 128px is recommended. ' + + 'Only used with `use-submission-api`', + type: 'string', + }, }, ) .command('run', 'Run the extension', commands.run, { diff --git a/src/util/submit-addon.js b/src/util/submit-addon.js index add240e24a..69285d9057 100644 --- a/src/util/submit-addon.js +++ b/src/util/submit-addon.js @@ -186,10 +186,18 @@ export default class Client { } async doFormDataPatch(data, addonId, versionId) { - const patchUrl = new URL( - `addon/${addonId}/versions/${versionId}/`, - this.apiUrl, - ); + let patchUrl; + if (versionId) { + log.info(`Submitting ${Object.keys(data)} to the version`); + patchUrl = new URL( + `addon/${addonId}/versions/${versionId}/`, + this.apiUrl, + ); + } else { + log.info(`Submitting ${Object.keys(data)} to the addon`); + patchUrl = new URL(`addon/${addonId}/`, this.apiUrl); + } + try { const formData = new FormData(); for (const field in data) { @@ -207,15 +215,20 @@ export default class Client { } async doAfterSubmit(addonId, newVersionId, editUrl, patchData) { + const promises = []; if (patchData && patchData.version) { - log.info(`Submitting ${Object.keys(patchData.version)} to version`); - await this.doFormDataPatch(patchData.version, addonId, newVersionId); + promises.push( + this.doFormDataPatch(patchData.version, addonId, newVersionId), + ); + } + if (patchData && patchData.addon) { + promises.push(this.doFormDataPatch(patchData.addon, addonId)); } + await Promise.all(promises); + if (this.approvalCheckTimeout > 0) { - const fileUrl = new URL( - await this.waitForApproval(addonId, newVersionId), - ); - return this.downloadSignedFile(fileUrl, addonId); + const fileUrl = await this.waitForApproval(addonId, newVersionId); + return this.downloadSignedFile(new URL(fileUrl), addonId); } else { log.info('Waiting for approval and download of signed xpi skipped.'); log.info( @@ -415,6 +428,7 @@ export async function signAddon({ savedUploadUuidPath, metaDataJson = {}, submissionSource, + iconFile, userAgentString, SubmitClient = Client, ApiAuthClass = JwtApiAuth, @@ -450,11 +464,12 @@ export async function signAddon({ channel, savedUploadUuidPath, ); - // if we have a source file we need to upload we patch after the create + // if we have a source or icon file we need to upload we patch after the create const patchData = { version: submissionSource ? { source: client.fileFromSync(submissionSource) } : undefined, + addon: iconFile ? { icon: client.fileFromSync(iconFile) } : undefined, }; // We specifically need to know if `id` has not been passed as a parameter because diff --git a/tests/unit/test-util/test.submit-addon.js b/tests/unit/test-util/test.submit-addon.js index 713c4b1e8f..d0b7946d2a 100644 --- a/tests/unit/test-util/test.submit-addon.js +++ b/tests/unit/test-util/test.submit-addon.js @@ -208,7 +208,7 @@ describe('util.submit-addon', () => { uploadUuid, signAddonDefaults.savedIdPath, {}, - { version: { source: fakeFileFromSync } }, + { version: { source: fakeFileFromSync }, addon: undefined }, ); }); @@ -227,7 +227,43 @@ describe('util.submit-addon', () => { uploadUuid, id, {}, - { version: { source: fakeFileFromSync } }, + { version: { source: fakeFileFromSync }, addon: undefined }, + ); + }); + + it('includes icon data to be patched if iconFile defined for new addon', async () => { + const iconFile = 'path/to/icon/image'; + await signAddon({ + ...signAddonDefaults, + iconFile, + }); + + sinon.assert.calledWith(fileFromSyncStub, iconFile); + sinon.assert.calledWith( + postNewAddonStub, + uploadUuid, + signAddonDefaults.savedIdPath, + {}, + { version: undefined, addon: { icon: fakeFileFromSync } }, + ); + }); + + it('includes icon data to be patched if iconFile defined for new version', async () => { + const iconFile = 'path/to/icon/image'; + const id = '@thisID'; + await signAddon({ + ...signAddonDefaults, + iconFile, + id, + }); + + sinon.assert.calledWith(fileFromSyncStub, iconFile); + sinon.assert.calledWith( + putVersionStub, + uploadUuid, + id, + {}, + { version: undefined, addon: { icon: fakeFileFromSync } }, ); }); }); @@ -1009,7 +1045,6 @@ describe('util.submit-addon', () => { const downloadUrl = 'https://a.download/url'; const newVersionId = sampleVersionDetail.id; const editUrl = sampleVersionDetail.editUrl; - const patchData = { version: { source: 'somesource' } }; let approvalStub; let downloadStub; @@ -1047,8 +1082,45 @@ describe('util.submit-addon', () => { it('calls doFormDataPatch if patchData.version is defined', async () => { client.approvalCheckTimeout = 0; + const patchData = { version: { source: 'somesource' } }; await client.doAfterSubmit(addonId, newVersionId, editUrl, patchData); + sinon.assert.calledOnce(doFormDataPatchStub); + sinon.assert.calledWith( + doFormDataPatchStub, + patchData.version, + addonId, + newVersionId, + ); + }); + + it('calls doFormDataPatch if patchData.addon is defined', async () => { + client.approvalCheckTimeout = 0; + const patchData = { addon: { icon: 'someimage' } }; + await client.doAfterSubmit(addonId, newVersionId, editUrl, patchData); + + sinon.assert.calledOnce(doFormDataPatchStub); + sinon.assert.calledWith( + doFormDataPatchStub, + patchData.addon, + addonId, + ); + }); + + it('calls doFormDataPatch twice if patchData.addon and patchData.version is defined', async () => { + client.approvalCheckTimeout = 0; + const patchData = { + version: { source: 'somesource' }, + addon: { icon: 'someimage' }, + }; + await client.doAfterSubmit(addonId, newVersionId, editUrl, patchData); + + sinon.assert.callCount(doFormDataPatchStub, 2); + sinon.assert.calledWith( + doFormDataPatchStub, + patchData.addon, + addonId, + ); sinon.assert.calledWith( doFormDataPatchStub, patchData.version,