UNPKG

web-ext-deploy

Version:

A tool for deploying WebExtensions to multiple stores.

173 lines (172 loc) 6.58 kB
import Axios from "axios"; import chalk from "chalk"; import dedent from "dedent"; import { backOff } from "exponential-backoff"; import status from "http-status"; import { getErrorMessage, getExtJson, getVerboseMessage, logSuccessfullyPublished } from "../../utils.js"; import fs from "fs"; const STORE = "Edge"; let axios; async function handleRequestWithBackOff({ sendRequest, errorActionOnFailure, zip, productId, isGetLocation }) { while (true) { try { const { data, headers } = await sendRequest(); return [undefined, isGetLocation ? headers.location : data]; } catch (e) { const isServerError = e.response.status >= 500; if (isServerError) { await backOff(Promise.resolve, { maxDelay: 30_000, delayFirstAttempt: true, jitter: "full" }); continue; } if (e.response.status === status.TOO_MANY_REQUESTS) { const secondsToWait = Number(e.response.data.message.match(/\d+/)[0]); if (secondsToWait >= 60) { const newTime = new Date(Date.now() + secondsToWait * 1000).toLocaleTimeString(); console.log(chalk.yellow(getVerboseMessage({ store: STORE, message: dedent(` Too many requests. A retry will automatically be at ${newTime} Or, you can deploy manually: https://partner.microsoft.com/en-us/dashboard/microsoftedge/${productId}/packages/dashboard `), prefix: "Warning" }))); } await new Promise(resolve => setTimeout(resolve, secondsToWait * 1000)); continue; } // Some sort of client error return [ getErrorMessage({ store: STORE, error: e.response.statusText, actionName: errorActionOnFailure, zip }) ]; } } } async function checkStatusOfPackageUpload({ productId, operationId, zip }) { // https://learn.microsoft.com/en-us/microsoft-edge/extensions-chromium/publish/api/using-addons-api#checking-the-status-of-a-package-upload const sendRequest = () => axios(`/products/${productId}/submissions/draft/package/operations/${operationId}`); let data; let error; do { [error, data] = await handleRequestWithBackOff({ sendRequest, errorActionOnFailure: "verify upload of", zip, productId }); if (error) { return [error]; } } while (data.status === "InProgress"); if (data.status === "Failed") { const errors = data.errors.map(({ message }) => message).join("\n"); return [errors]; } return [undefined, data]; } async function uploadZip({ zip, productId }) { // https://learn.microsoft.com/en-us/microsoft-edge/extensions-chromium/publish/api/using-addons-api#uploading-a-package-to-update-an-existing-submission const sendRequest = () => axios.post(`/products/${productId}/submissions/draft/package`, fs.createReadStream(zip), { headers: { "Content-Type": "application/zip" } }); const [error, location] = await handleRequestWithBackOff({ sendRequest, errorActionOnFailure: "upload", zip, productId, isGetLocation: true }); if (error) { return [error]; } return [undefined, location]; } async function publishSubmission({ zip, productId, devChangelog }) { // https://learn.microsoft.com/en-us/microsoft-edge/extensions-chromium/publish/api/using-addons-api#publishing-the-submission const sendRequest = () => axios.post(`/products/${productId}/submissions`, { notes: devChangelog }); const [error, location] = await handleRequestWithBackOff({ sendRequest, errorActionOnFailure: "publish", productId, zip, isGetLocation: true }); if (error) { return [error]; } return [undefined, location]; } async function checkPublishStatus({ zip, productId, operationId }) { // https://learn.microsoft.com/en-us/microsoft-edge/extensions-chromium/publish/api/using-addons-api#checking-the-status-of-a-package-upload const sendRequest = () => axios(`/products/${productId}/submissions/operations/${operationId}`); const [error, data] = await handleRequestWithBackOff({ sendRequest, errorActionOnFailure: "check the submission status of", zip, productId }); if (error) { return [error]; } if (!("status" in data)) { return [data.message]; } if (data.status === "Failed") { const errors = []; for (const error of data.errors || []) { errors.push(error.message); } if (errors.length === 0) { errors.push(data.message); } return [errors.join("\n")]; } return [undefined, data]; } export async function deployToEdgePublishApi({ productId, clientId, apiKey, zip, verbose: isVerbose, devChangelog }) { axios = Axios.create({ baseURL: "https://api.addons.microsoftedge.microsoft.com/v1", headers: { Authorization: `ApiKey ${apiKey}`, "X-ClientID": clientId } }); const { name } = getExtJson(zip); if (isVerbose) { console.log(getVerboseMessage({ store: STORE, message: `Uploading zip of ${name} with product ID ${productId}` })); } let [error, operationId] = await uploadZip({ zip, productId }); if (error) { throw error; } if (isVerbose) { console.log(getVerboseMessage({ store: STORE, message: `Verifying upload` })); } [error] = await checkStatusOfPackageUpload({ zip, productId, operationId }); if (error) { throw error; } if (isVerbose) { console.log(getVerboseMessage({ store: STORE, message: `Publishing submission` })); } [error, operationId] = await publishSubmission({ zip, productId, devChangelog }); if (error) { throw error; } if (isVerbose) { console.log(getVerboseMessage({ store: STORE, message: `Checking the submission status` })); } [error] = await checkPublishStatus({ zip, productId, operationId }); if (error) { throw error; } logSuccessfullyPublished({ store: STORE, extId: productId, zip }); return true; }