Skip to content

Commit b5dc49a

Browse files
committed
implement icon file upload
1 parent 734b1d1 commit b5dc49a

File tree

4 files changed

+121
-25
lines changed

4 files changed

+121
-25
lines changed

src/cmd/sign.js

+2
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ export default function sign(
4141
channel,
4242
amoMetadata,
4343
uploadSourceCode,
44+
amoIcon,
4445
webextVersion,
4546
},
4647
{
@@ -154,6 +155,7 @@ export default function sign(
154155
approvalCheckTimeout:
155156
approvalTimeout !== undefined ? approvalTimeout : timeout,
156157
submissionSource: uploadSourceCode,
158+
amoIcon,
157159
});
158160
} else {
159161
const {

src/program.js

+7
Original file line numberDiff line numberDiff line change
@@ -609,6 +609,13 @@ Example: $0 --help run.
609609
'details. Only used with `use-submission-api`',
610610
type: 'string',
611611
},
612+
'amo-icon': {
613+
describe:
614+
'Path to an image that should be displayed as the addon icon on ' +
615+
'addons.mozilla.org. Must be square; 128px x 128px is recommended. ' +
616+
'Only used with `use-submission-api`',
617+
type: 'string',
618+
},
612619
},
613620
)
614621
.command('run', 'Run the extension', commands.run, {

src/util/submit-addon.js

+37-24
Original file line numberDiff line numberDiff line change
@@ -186,10 +186,11 @@ export default class Client {
186186
}
187187

188188
async doFormDataPatch(data, addonId, versionId) {
189-
const patchUrl = new URL(
190-
`addon/${addonId}/versions/${versionId}/`,
191-
this.apiUrl,
192-
);
189+
let patchUrl = new URL(`addon/${addonId}/`, this.apiUrl);
190+
if (versionId) {
191+
patchUrl = new URL(`versions/${versionId}/`, patchUrl);
192+
}
193+
193194
try {
194195
const formData = new FormData();
195196
for (const field in data) {
@@ -207,10 +208,19 @@ export default class Client {
207208
}
208209

209210
async doAfterSubmit(addonId, newVersionId, editUrl, patchData) {
211+
const promises = [];
210212
if (patchData && patchData.version) {
211213
log.info(`Submitting ${Object.keys(patchData.version)} to version`);
212-
await this.doFormDataPatch(patchData.version, addonId, newVersionId);
214+
promises.push(
215+
this.doFormDataPatch(patchData.version, addonId, newVersionId),
216+
);
213217
}
218+
if (patchData && patchData.addon) {
219+
log.info(`Submitting ${Object.keys(patchData.addon)} to addon`);
220+
promises.push(this.doFormDataPatch(patchData.addon, addonId));
221+
}
222+
await Promise.all(promises);
223+
214224
if (this.approvalCheckTimeout > 0) {
215225
const fileUrl = new URL(
216226
await this.waitForApproval(addonId, newVersionId),
@@ -415,19 +425,16 @@ export async function signAddon({
415425
savedUploadUuidPath,
416426
metaDataJson = {},
417427
submissionSource,
428+
amoIcon,
418429
userAgentString,
419430
SubmitClient = Client,
420431
ApiAuthClass = JwtApiAuth,
421432
}) {
422-
try {
423-
const stats = await fsPromises.stat(xpiPath);
424-
425-
if (!stats.isFile()) {
426-
throw new Error('not a file');
427-
}
428-
} catch (statError) {
429-
throw new Error(`error with ${xpiPath}: ${statError}`);
430-
}
433+
await Promise.all([
434+
checkPathIsFile(xpiPath),
435+
submissionSource ? checkPathIsFile(submissionSource) : Promise.resolve(),
436+
amoIcon ? checkPathIsFile(amoIcon) : Promise.resolve(),
437+
]);
431438

432439
let baseUrl;
433440
try {
@@ -451,19 +458,13 @@ export async function signAddon({
451458
savedUploadUuidPath,
452459
);
453460
const patchData = {};
454-
// if we have a source file we need to upload we patch after the create
461+
// if we have a source or icon file we need to upload we patch after the create
455462
if (submissionSource) {
456-
try {
457-
const stats2 = await fsPromises.stat(submissionSource);
458-
459-
if (!stats2.isFile()) {
460-
throw new Error('not a file');
461-
}
462-
} catch (statError) {
463-
throw new Error(`error with ${submissionSource}: ${statError}`);
464-
}
465463
patchData.version = { source: client.fileFromSync(submissionSource) };
466464
}
465+
if (amoIcon) {
466+
patchData.addon = { icon: client.fileFromSync(amoIcon) };
467+
}
467468

468469
// We specifically need to know if `id` has not been passed as a parameter because
469470
// it's the indication that a new add-on should be created, rather than a new version.
@@ -526,3 +527,15 @@ export async function getUploadUuidFromFile(
526527

527528
return { uploadUuid: '', channel: '', xpiCrcHash: '' };
528529
}
530+
531+
async function checkPathIsFile(filePath) {
532+
try {
533+
const stats2 = await fsPromises.stat(filePath);
534+
535+
if (!stats2.isFile()) {
536+
throw new Error('not a file');
537+
}
538+
} catch (statError) {
539+
throw new Error(`error with ${filePath}: ${statError}`);
540+
}
541+
}

tests/unit/test-util/test.submit-addon.js

+75-1
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,44 @@ describe('util.submit-addon', () => {
235235
);
236236
});
237237

238+
it('includes icon data to be patched if amoIcon defined for new addon', async () => {
239+
const amoIcon = 'path/to/icon/image';
240+
statStub.onSecondCall().resolves({ isFile: () => true });
241+
await signAddon({
242+
...signAddonDefaults,
243+
amoIcon,
244+
});
245+
246+
sinon.assert.calledWith(fileFromSyncStub, amoIcon);
247+
sinon.assert.calledWith(
248+
postNewAddonStub,
249+
uploadUuid,
250+
signAddonDefaults.savedIdPath,
251+
{},
252+
{ addon: { icon: fakeFileFromSync } },
253+
);
254+
});
255+
256+
it('includes icon data to be patched if amoIcon defined for new version', async () => {
257+
const amoIcon = 'path/to/icon/image';
258+
statStub.onSecondCall().resolves({ isFile: () => true });
259+
const id = '@thisID';
260+
await signAddon({
261+
...signAddonDefaults,
262+
amoIcon,
263+
id,
264+
});
265+
266+
sinon.assert.calledWith(fileFromSyncStub, amoIcon);
267+
sinon.assert.calledWith(
268+
putVersionStub,
269+
uploadUuid,
270+
id,
271+
{},
272+
{ addon: { icon: fakeFileFromSync } },
273+
);
274+
});
275+
238276
it('throws error if submissionSource is not found', async () => {
239277
const submissionSource = 'path/to/source/zip';
240278
const signAddonPromise = signAddon({
@@ -1041,7 +1079,6 @@ describe('util.submit-addon', () => {
10411079
const downloadUrl = 'https://a.download/url';
10421080
const newVersionId = sampleVersionDetail.id;
10431081
const editUrl = sampleVersionDetail.editUrl;
1044-
const patchData = { version: { source: 'somesource' } };
10451082

10461083
let approvalStub;
10471084
let downloadStub;
@@ -1079,8 +1116,45 @@ describe('util.submit-addon', () => {
10791116

10801117
it('calls doFormDataPatch if patchData.version is defined', async () => {
10811118
client.approvalCheckTimeout = 0;
1119+
const patchData = { version: { source: 'somesource' } };
1120+
await client.doAfterSubmit(addonId, newVersionId, editUrl, patchData);
1121+
1122+
sinon.assert.calledOnce(doFormDataPatchStub);
1123+
sinon.assert.calledWith(
1124+
doFormDataPatchStub,
1125+
patchData.version,
1126+
addonId,
1127+
newVersionId,
1128+
);
1129+
});
1130+
1131+
it('calls doFormDataPatch if patchData.addon is defined', async () => {
1132+
client.approvalCheckTimeout = 0;
1133+
const patchData = { addon: { icon: 'someimage' } };
10821134
await client.doAfterSubmit(addonId, newVersionId, editUrl, patchData);
10831135

1136+
sinon.assert.calledOnce(doFormDataPatchStub);
1137+
sinon.assert.calledWith(
1138+
doFormDataPatchStub,
1139+
patchData.addon,
1140+
addonId,
1141+
);
1142+
});
1143+
1144+
it('calls doFormDataPatch twice if patchData.addon and patchData.version is defined', async () => {
1145+
client.approvalCheckTimeout = 0;
1146+
const patchData = {
1147+
version: { source: 'somesource' },
1148+
addon: { icon: 'someimage' },
1149+
};
1150+
await client.doAfterSubmit(addonId, newVersionId, editUrl, patchData);
1151+
1152+
sinon.assert.callCount(doFormDataPatchStub, 2);
1153+
sinon.assert.calledWith(
1154+
doFormDataPatchStub,
1155+
patchData.addon,
1156+
addonId,
1157+
);
10841158
sinon.assert.calledWith(
10851159
doFormDataPatchStub,
10861160
patchData.version,

0 commit comments

Comments
 (0)