Skip to content

Commit

Permalink
implement source file upload (#2859)
Browse files Browse the repository at this point in the history
* implement source file upload

* version-source to upload-source-code; reword helptext;
versionSource in submit-addon.js to submissionSource;
versionPatchData to patchData.version

* refactor so call to doFormDataPatch isn't duplicated

* validate submissionSource and raise if not valid
  • Loading branch information
eviljeff authored Sep 26, 2023
1 parent 4c0ba34 commit 734b1d1
Show file tree
Hide file tree
Showing 5 changed files with 234 additions and 11 deletions.
2 changes: 2 additions & 0 deletions src/cmd/sign.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export default function sign(
verbose,
channel,
amoMetadata,
uploadSourceCode,
webextVersion,
},
{
Expand Down Expand Up @@ -152,6 +153,7 @@ export default function sign(
validationCheckTimeout: timeout,
approvalCheckTimeout:
approvalTimeout !== undefined ? approvalTimeout : timeout,
submissionSource: uploadSourceCode,
});
} else {
const {
Expand Down
8 changes: 8 additions & 0 deletions src/program.js
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,14 @@ Example: $0 --help run.
'Only used with `use-submission-api`',
type: 'string',
},
'upload-source-code': {
describe:
'Path to an archive file containing human readable source code of this submission, ' +
'if the code in --source-dir has been processed to make it unreadable. ' +
'See https://extensionworkshop.com/documentation/publish/source-code-submission/ for ' +
'details. Only used with `use-submission-api`',
type: 'string',
},
},
)
.command('run', 'Run the extension', commands.run, {
Expand Down
62 changes: 54 additions & 8 deletions src/util/submit-addon.js
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,32 @@ export default class Client {
return this.fetchJson(url, 'PUT', JSON.stringify(jsonData));
}

async doAfterSubmit(addonId, newVersionId, editUrl) {
async doFormDataPatch(data, addonId, versionId) {
const patchUrl = new URL(
`addon/${addonId}/versions/${versionId}/`,
this.apiUrl,
);
try {
const formData = new FormData();
for (const field in data) {
formData.set(field, data[field]);
}

const response = await this.fetch(patchUrl, 'PATCH', formData);
if (!response.ok) {
throw new Error(`response status was ${response.status}`);
}
} catch (error) {
log.info(`Upload of ${Object.keys(data)} failed: ${error}.`);
throw new Error(`Uploading ${Object.keys(data)} failed`);
}
}

async doAfterSubmit(addonId, newVersionId, editUrl, patchData) {
if (patchData && patchData.version) {
log.info(`Submitting ${Object.keys(patchData.version)} to version`);
await this.doFormDataPatch(patchData.version, addonId, newVersionId);
}
if (this.approvalCheckTimeout > 0) {
const fileUrl = new URL(
await this.waitForApproval(addonId, newVersionId),
Expand Down Expand Up @@ -237,7 +262,7 @@ export default class Client {
}

async fetch(url, method = 'GET', body) {
log.info(`Fetching URL: ${url.href}`);
log.info(`${method}ing URL: ${url.href}`);
let headers = {
Authorization: await this.apiAuth.getAuthHeader(),
Accept: 'application/json',
Expand Down Expand Up @@ -350,6 +375,7 @@ export default class Client {
uploadUuid,
savedIdPath,
metaDataJson,
patchData,
saveIdToFileFunc = saveIdToFile,
) {
const {
Expand All @@ -362,15 +388,15 @@ export default class Client {
log.info('You must add the following to your manifest:');
log.info(`"browser_specific_settings": {"gecko": {"id": "${addonId}"}}`);

return this.doAfterSubmit(addonId, newVersionId, editUrl);
return this.doAfterSubmit(addonId, newVersionId, editUrl, patchData);
}

async putVersion(uploadUuid, addonId, metaDataJson) {
async putVersion(uploadUuid, addonId, metaDataJson, patchData) {
const {
version: { id: newVersionId, edit_url: editUrl },
} = await this.doNewAddonOrVersionSubmit(addonId, uploadUuid, metaDataJson);

return this.doAfterSubmit(addonId, newVersionId, editUrl);
return this.doAfterSubmit(addonId, newVersionId, editUrl, patchData);
}
}

Expand All @@ -388,6 +414,7 @@ export async function signAddon({
savedIdPath,
savedUploadUuidPath,
metaDataJson = {},
submissionSource,
userAgentString,
SubmitClient = Client,
ApiAuthClass = JwtApiAuth,
Expand All @@ -396,7 +423,7 @@ export async function signAddon({
const stats = await fsPromises.stat(xpiPath);

if (!stats.isFile()) {
throw new Error(`not a file: ${xpiPath}`);
throw new Error('not a file');
}
} catch (statError) {
throw new Error(`error with ${xpiPath}: ${statError}`);
Expand All @@ -423,14 +450,33 @@ export async function signAddon({
channel,
savedUploadUuidPath,
);
const patchData = {};
// if we have a source file we need to upload we patch after the create
if (submissionSource) {
try {
const stats2 = await fsPromises.stat(submissionSource);

if (!stats2.isFile()) {
throw new Error('not a file');
}
} catch (statError) {
throw new Error(`error with ${submissionSource}: ${statError}`);
}
patchData.version = { source: client.fileFromSync(submissionSource) };
}

// We specifically need to know if `id` has not been passed as a parameter because
// it's the indication that a new add-on should be created, rather than a new version.
if (id === undefined) {
return client.postNewAddon(uploadUuid, savedIdPath, metaDataJson);
return client.postNewAddon(
uploadUuid,
savedIdPath,
metaDataJson,
patchData,
);
}

return client.putVersion(uploadUuid, id, metaDataJson);
return client.putVersion(uploadUuid, id, metaDataJson, patchData);
}

export async function saveIdToFile(filePath, id) {
Expand Down
17 changes: 17 additions & 0 deletions tests/unit/test-cmd/test.sign.js
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,23 @@ describe('sign', () => {
});
}));

it('passes the uploadSourceCode parameter to submissionAPI signer as submissionSource', () =>
withTempDir((tmpDir) => {
const stubs = getStubs();
const uploadSourceCode = 'path/to/source.zip';
return sign(tmpDir, stubs, {
extraArgs: {
uploadSourceCode,
useSubmissionApi: true,
channel: 'unlisted',
},
}).then(() => {
sinon.assert.called(stubs.signingOptions.submitAddon);
sinon.assert.calledWithMatch(stubs.signingOptions.submitAddon, {
submissionSource: uploadSourceCode,
});
});
}));
it('returns a signing result', () =>
withTempDir((tmpDir) => {
const stubs = getStubs();
Expand Down
Loading

0 comments on commit 734b1d1

Please sign in to comment.