web-ext 7.12.0 → 8.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -16
- package/bin/web-ext.js +1 -1
- package/lib/cmd/build.js +1 -3
- package/lib/cmd/build.js.map +1 -1
- package/lib/cmd/docs.js +1 -0
- package/lib/cmd/docs.js.map +1 -1
- package/lib/cmd/dump-config.js +6 -0
- package/lib/cmd/dump-config.js.map +1 -0
- package/lib/cmd/index.js +16 -8
- package/lib/cmd/index.js.map +1 -1
- package/lib/cmd/lint.js +0 -8
- package/lib/cmd/lint.js.map +1 -1
- package/lib/cmd/run.js +0 -7
- package/lib/cmd/run.js.map +1 -1
- package/lib/cmd/sign.js +29 -85
- package/lib/cmd/sign.js.map +1 -1
- package/lib/config.js +53 -7
- package/lib/config.js.map +1 -1
- package/lib/errors.js.map +1 -1
- package/lib/extension-runners/chromium.js +11 -3
- package/lib/extension-runners/chromium.js.map +1 -1
- package/lib/extension-runners/firefox-android.js +15 -3
- package/lib/extension-runners/firefox-android.js.map +1 -1
- package/lib/extension-runners/firefox-desktop.js +7 -8
- package/lib/extension-runners/firefox-desktop.js.map +1 -1
- package/lib/extension-runners/index.js +2 -2
- package/lib/extension-runners/index.js.map +1 -1
- package/lib/firefox/index.js +1 -8
- package/lib/firefox/index.js.map +1 -1
- package/lib/firefox/package-identifiers.js +3 -1
- package/lib/firefox/package-identifiers.js.map +1 -1
- package/lib/firefox/preferences.js +0 -2
- package/lib/firefox/preferences.js.map +1 -1
- package/lib/firefox/rdp-client.js +10 -2
- package/lib/firefox/rdp-client.js.map +1 -1
- package/lib/firefox/remote.js +3 -2
- package/lib/firefox/remote.js.map +1 -1
- package/lib/main.js.map +1 -1
- package/lib/program.js +25 -48
- package/lib/program.js.map +1 -1
- package/lib/util/adb.js +15 -9
- package/lib/util/adb.js.map +1 -1
- package/lib/util/artifacts.js.map +1 -1
- package/lib/util/desktop-notifier.js.map +1 -1
- package/lib/util/file-exists.js +1 -0
- package/lib/util/file-exists.js.map +1 -1
- package/lib/util/file-filter.js +2 -0
- package/lib/util/file-filter.js.map +1 -1
- package/lib/util/is-directory.js.map +1 -1
- package/lib/util/logger.js +5 -5
- package/lib/util/logger.js.map +1 -1
- package/lib/util/manifest.js +1 -1
- package/lib/util/manifest.js.map +1 -1
- package/lib/util/promisify.js +0 -7
- package/lib/util/promisify.js.map +1 -1
- package/lib/util/stdin.js +0 -2
- package/lib/util/stdin.js.map +1 -1
- package/lib/util/submit-addon.js +214 -50
- package/lib/util/submit-addon.js.map +1 -1
- package/lib/util/temp-dir.js +2 -0
- package/lib/util/temp-dir.js.map +1 -1
- package/lib/util/updates.js.map +1 -1
- package/lib/watcher.js.map +1 -1
- package/package.json +38 -40
- package/lib/extension-runners/base.js +0 -2
- package/lib/extension-runners/base.js.map +0 -1
package/lib/util/manifest.js
CHANGED
|
@@ -51,7 +51,7 @@ export default async function getValidatedManifest(sourceDir) {
|
|
|
51
51
|
export function getManifestId(manifestData) {
|
|
52
52
|
const manifestApps = [manifestData.browser_specific_settings, manifestData.applications];
|
|
53
53
|
for (const apps of manifestApps) {
|
|
54
|
-
// If both bss and
|
|
54
|
+
// If both bss and applications contain a defined gecko property,
|
|
55
55
|
// we prefer bss even if the id property isn't available.
|
|
56
56
|
// This match what Firefox does in this particular scenario, see
|
|
57
57
|
// https://searchfox.org/mozilla-central/rev/828f2319c0195d7f561ed35533aef6fe183e68e3/toolkit/mozapps/extensions/internal/XPIInstall.jsm#470-474,488
|
package/lib/util/manifest.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manifest.js","names":["path","fs","parseJSON","stripBom","stripJsonComments","InvalidManifest","createLogger","log","import","meta","url","getValidatedManifest","sourceDir","manifestFile","join","debug","manifestContents","readFile","encoding","error","manifestData","errors","name","push","version","applications","gecko","length","getManifestId","manifestApps","browser_specific_settings","apps","id","undefined"],"sources":["../../src/util/manifest.js"],"sourcesContent":["
|
|
1
|
+
{"version":3,"file":"manifest.js","names":["path","fs","parseJSON","stripBom","stripJsonComments","InvalidManifest","createLogger","log","import","meta","url","getValidatedManifest","sourceDir","manifestFile","join","debug","manifestContents","readFile","encoding","error","manifestData","errors","name","push","version","applications","gecko","length","getManifestId","manifestApps","browser_specific_settings","apps","id","undefined"],"sources":["../../src/util/manifest.js"],"sourcesContent":["import path from 'path';\n\nimport { fs } from 'mz';\nimport parseJSON from 'parse-json';\nimport stripBom from 'strip-bom';\nimport stripJsonComments from 'strip-json-comments';\n\nimport { InvalidManifest } from '../errors.js';\nimport { createLogger } from './logger.js';\n\nconst log = createLogger(import.meta.url);\n\n// getValidatedManifest helper types and implementation\n\nexport default async function getValidatedManifest(sourceDir) {\n const manifestFile = path.join(sourceDir, 'manifest.json');\n log.debug(`Validating manifest at ${manifestFile}`);\n\n let manifestContents;\n\n try {\n manifestContents = await fs.readFile(manifestFile, { encoding: 'utf-8' });\n } catch (error) {\n throw new InvalidManifest(\n `Could not read manifest.json file at ${manifestFile}: ${error}`,\n );\n }\n\n manifestContents = stripBom(manifestContents);\n\n let manifestData;\n\n try {\n manifestData = parseJSON(stripJsonComments(manifestContents));\n } catch (error) {\n throw new InvalidManifest(\n `Error parsing manifest.json file at ${manifestFile}: ${error}`,\n );\n }\n\n const errors = [];\n // This is just some basic validation of what web-ext needs, not\n // what Firefox will need to run the extension.\n // TODO: integrate with the addons-linter for actual validation.\n if (!manifestData.name) {\n errors.push('missing \"name\" property');\n }\n if (!manifestData.version) {\n errors.push('missing \"version\" property');\n }\n\n if (manifestData.applications && !manifestData.applications.gecko) {\n // Since the applications property only applies to gecko, make\n // sure 'gecko' exists when 'applications' is defined. This should\n // make introspection of gecko properties easier.\n errors.push('missing \"applications.gecko\" property');\n }\n\n if (errors.length) {\n throw new InvalidManifest(\n `Manifest at ${manifestFile} is invalid: ${errors.join('; ')}`,\n );\n }\n\n return manifestData;\n}\n\nexport function getManifestId(manifestData) {\n const manifestApps = [\n manifestData.browser_specific_settings,\n manifestData.applications,\n ];\n for (const apps of manifestApps) {\n // If both bss and applications contain a defined gecko property,\n // we prefer bss even if the id property isn't available.\n // This match what Firefox does in this particular scenario, see\n // https://searchfox.org/mozilla-central/rev/828f2319c0195d7f561ed35533aef6fe183e68e3/toolkit/mozapps/extensions/internal/XPIInstall.jsm#470-474,488\n if (apps?.gecko) {\n return apps.gecko.id;\n }\n }\n\n return undefined;\n}\n"],"mappings":"AAAA,OAAOA,IAAI,MAAM,MAAM;AAEvB,SAASC,EAAE,QAAQ,IAAI;AACvB,OAAOC,SAAS,MAAM,YAAY;AAClC,OAAOC,QAAQ,MAAM,WAAW;AAChC,OAAOC,iBAAiB,MAAM,qBAAqB;AAEnD,SAASC,eAAe,QAAQ,cAAc;AAC9C,SAASC,YAAY,QAAQ,aAAa;AAE1C,MAAMC,GAAG,GAAGD,YAAY,CAACE,MAAM,CAACC,IAAI,CAACC,GAAG,CAAC;;AAEzC;;AAEA,eAAe,eAAeC,oBAAoBA,CAACC,SAAS,EAAE;EAC5D,MAAMC,YAAY,GAAGb,IAAI,CAACc,IAAI,CAACF,SAAS,EAAE,eAAe,CAAC;EAC1DL,GAAG,CAACQ,KAAK,CAAC,0BAA0BF,YAAY,EAAE,CAAC;EAEnD,IAAIG,gBAAgB;EAEpB,IAAI;IACFA,gBAAgB,GAAG,MAAMf,EAAE,CAACgB,QAAQ,CAACJ,YAAY,EAAE;MAAEK,QAAQ,EAAE;IAAQ,CAAC,CAAC;EAC3E,CAAC,CAAC,OAAOC,KAAK,EAAE;IACd,MAAM,IAAId,eAAe,CACvB,wCAAwCQ,YAAY,KAAKM,KAAK,EAChE,CAAC;EACH;EAEAH,gBAAgB,GAAGb,QAAQ,CAACa,gBAAgB,CAAC;EAE7C,IAAII,YAAY;EAEhB,IAAI;IACFA,YAAY,GAAGlB,SAAS,CAACE,iBAAiB,CAACY,gBAAgB,CAAC,CAAC;EAC/D,CAAC,CAAC,OAAOG,KAAK,EAAE;IACd,MAAM,IAAId,eAAe,CACvB,uCAAuCQ,YAAY,KAAKM,KAAK,EAC/D,CAAC;EACH;EAEA,MAAME,MAAM,GAAG,EAAE;EACjB;EACA;EACA;EACA,IAAI,CAACD,YAAY,CAACE,IAAI,EAAE;IACtBD,MAAM,CAACE,IAAI,CAAC,yBAAyB,CAAC;EACxC;EACA,IAAI,CAACH,YAAY,CAACI,OAAO,EAAE;IACzBH,MAAM,CAACE,IAAI,CAAC,4BAA4B,CAAC;EAC3C;EAEA,IAAIH,YAAY,CAACK,YAAY,IAAI,CAACL,YAAY,CAACK,YAAY,CAACC,KAAK,EAAE;IACjE;IACA;IACA;IACAL,MAAM,CAACE,IAAI,CAAC,uCAAuC,CAAC;EACtD;EAEA,IAAIF,MAAM,CAACM,MAAM,EAAE;IACjB,MAAM,IAAItB,eAAe,CACvB,eAAeQ,YAAY,gBAAgBQ,MAAM,CAACP,IAAI,CAAC,IAAI,CAAC,EAC9D,CAAC;EACH;EAEA,OAAOM,YAAY;AACrB;AAEA,OAAO,SAASQ,aAAaA,CAACR,YAAY,EAAE;EAC1C,MAAMS,YAAY,GAAG,CACnBT,YAAY,CAACU,yBAAyB,EACtCV,YAAY,CAACK,YAAY,CAC1B;EACD,KAAK,MAAMM,IAAI,IAAIF,YAAY,EAAE;IAC/B;IACA;IACA;IACA;IACA,IAAIE,IAAI,aAAJA,IAAI,eAAJA,IAAI,CAAEL,KAAK,EAAE;MACf,OAAOK,IAAI,CAACL,KAAK,CAACM,EAAE;IACtB;EACF;EAEA,OAAOC,SAAS;AAClB","ignoreList":[]}
|
package/lib/util/promisify.js
CHANGED
|
@@ -1,11 +1,4 @@
|
|
|
1
1
|
import { promisify } from 'util';
|
|
2
|
-
|
|
3
|
-
// promisify.custom is missing from the node types know to flow,
|
|
4
|
-
// and it triggers flow-check errors if used directly.
|
|
5
|
-
// By using the value exported here, flow-check passes successfully
|
|
6
|
-
// using a single FLOW_IGNORE suppress comment.
|
|
7
|
-
|
|
8
|
-
// $FlowIgnore: promisify.custom is missing in flow type signatures.
|
|
9
2
|
export const promisifyCustom = promisify.custom;
|
|
10
3
|
|
|
11
4
|
/*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"promisify.js","names":["promisify","promisifyCustom","custom","multiArgsPromisedFn","fn","callerArgs","Promise","resolve","reject","err","rest"],"sources":["../../src/util/promisify.js"],"sourcesContent":["
|
|
1
|
+
{"version":3,"file":"promisify.js","names":["promisify","promisifyCustom","custom","multiArgsPromisedFn","fn","callerArgs","Promise","resolve","reject","err","rest"],"sources":["../../src/util/promisify.js"],"sourcesContent":["import { promisify } from 'util';\n\nexport const promisifyCustom = promisify.custom;\n\n/*\n * A small promisify helper to make it easier to customize a\n * function promisified (using the 'util' module available in\n * nodejs >= 8) to resolve to an array of results:\n *\n * import {promisify} from 'util';\n * import {multiArgsPromisedFn} from '../util/promisify';\n *\n * aCallbackBasedFn[promisify.custom] = multiArgsPromisedFn(tmp.dir);\n * ...\n */\nexport function multiArgsPromisedFn(fn) {\n return (...callerArgs) => {\n return new Promise((resolve, reject) => {\n fn(...callerArgs, (err, ...rest) => {\n if (err) {\n reject(err);\n } else {\n resolve(rest);\n }\n });\n });\n };\n}\n"],"mappings":"AAAA,SAASA,SAAS,QAAQ,MAAM;AAEhC,OAAO,MAAMC,eAAe,GAAGD,SAAS,CAACE,MAAM;;AAE/C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,mBAAmBA,CAACC,EAAE,EAAE;EACtC,OAAO,CAAC,GAAGC,UAAU,KAAK;IACxB,OAAO,IAAIC,OAAO,CAAC,CAACC,OAAO,EAAEC,MAAM,KAAK;MACtCJ,EAAE,CAAC,GAAGC,UAAU,EAAE,CAACI,GAAG,EAAE,GAAGC,IAAI,KAAK;QAClC,IAAID,GAAG,EAAE;UACPD,MAAM,CAACC,GAAG,CAAC;QACb,CAAC,MAAM;UACLF,OAAO,CAACG,IAAI,CAAC;QACf;MACF,CAAC,CAAC;IACJ,CAAC,CAAC;EACJ,CAAC;AACH","ignoreList":[]}
|
package/lib/util/stdin.js
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
export function isTTY(stream) {
|
|
2
|
-
// $FlowFixMe: flow complains that stream may not provide isTTY as a property.
|
|
3
2
|
return stream.isTTY;
|
|
4
3
|
}
|
|
5
4
|
export function setRawMode(stream, rawMode) {
|
|
6
|
-
// $FlowFixMe: flow complains that stdin may not provide setRawMode.
|
|
7
5
|
stream.setRawMode(rawMode);
|
|
8
6
|
}
|
|
9
7
|
//# sourceMappingURL=stdin.js.map
|
package/lib/util/stdin.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stdin.js","names":["isTTY","stream","setRawMode","rawMode"],"sources":["../../src/util/stdin.js"],"sourcesContent":["
|
|
1
|
+
{"version":3,"file":"stdin.js","names":["isTTY","stream","setRawMode","rawMode"],"sources":["../../src/util/stdin.js"],"sourcesContent":["export function isTTY(stream) {\n return stream.isTTY;\n}\n\nexport function setRawMode(stream, rawMode) {\n stream.setRawMode(rawMode);\n}\n"],"mappings":"AAAA,OAAO,SAASA,KAAKA,CAACC,MAAM,EAAE;EAC5B,OAAOA,MAAM,CAACD,KAAK;AACrB;AAEA,OAAO,SAASE,UAAUA,CAACD,MAAM,EAAEE,OAAO,EAAE;EAC1CF,MAAM,CAACC,UAAU,CAACC,OAAO,CAAC;AAC5B","ignoreList":[]}
|
package/lib/util/submit-addon.js
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
|
+
import { createHash } from 'crypto';
|
|
1
2
|
import { createWriteStream, promises as fsPromises } from 'fs';
|
|
2
|
-
import {
|
|
3
|
-
import { promisify } from 'util';
|
|
3
|
+
import { promises as streamPromises } from 'stream';
|
|
4
4
|
|
|
5
5
|
// eslint-disable-next-line no-shadow
|
|
6
|
-
import fetch, { FormData, fileFromSync
|
|
6
|
+
import fetch, { FormData, fileFromSync } from 'node-fetch';
|
|
7
7
|
import { SignJWT } from 'jose';
|
|
8
|
+
import JSZip from 'jszip';
|
|
9
|
+
import { HttpsProxyAgent } from 'https-proxy-agent';
|
|
10
|
+
import { isErrorWithCode } from '../errors.js';
|
|
8
11
|
import { createLogger } from './../util/logger.js';
|
|
9
12
|
const log = createLogger(import.meta.url);
|
|
13
|
+
export const defaultAsyncFsReadFile = fsPromises.readFile;
|
|
10
14
|
export class JwtApiAuth {
|
|
11
15
|
#apiKey;
|
|
12
16
|
#apiSecret;
|
|
@@ -37,8 +41,18 @@ export class JwtApiAuth {
|
|
|
37
41
|
}
|
|
38
42
|
}
|
|
39
43
|
export default class Client {
|
|
44
|
+
apiAuth;
|
|
45
|
+
apiProxy;
|
|
46
|
+
apiUr;
|
|
47
|
+
validationCheckInterval;
|
|
48
|
+
validationCheckTimeout;
|
|
49
|
+
approvalCheckInterval;
|
|
50
|
+
approvalCheckTimeout;
|
|
51
|
+
downloadDir;
|
|
52
|
+
userAgentString;
|
|
40
53
|
constructor({
|
|
41
54
|
apiAuth,
|
|
55
|
+
apiProxy,
|
|
42
56
|
baseUrl,
|
|
43
57
|
validationCheckInterval = 1000,
|
|
44
58
|
validationCheckTimeout = 300000,
|
|
@@ -50,6 +64,9 @@ export default class Client {
|
|
|
50
64
|
userAgentString
|
|
51
65
|
}) {
|
|
52
66
|
this.apiAuth = apiAuth;
|
|
67
|
+
if (apiProxy) {
|
|
68
|
+
this.apiProxy = apiProxy;
|
|
69
|
+
}
|
|
53
70
|
if (!baseUrl.pathname.endsWith('/')) {
|
|
54
71
|
baseUrl = new URL(baseUrl.href);
|
|
55
72
|
baseUrl.pathname += '/';
|
|
@@ -68,12 +85,14 @@ export default class Client {
|
|
|
68
85
|
nodeFetch(url, {
|
|
69
86
|
method,
|
|
70
87
|
headers,
|
|
71
|
-
body
|
|
88
|
+
body,
|
|
89
|
+
agent
|
|
72
90
|
}) {
|
|
73
91
|
return fetch(url, {
|
|
74
92
|
method,
|
|
75
93
|
headers,
|
|
76
|
-
body
|
|
94
|
+
body,
|
|
95
|
+
agent
|
|
77
96
|
});
|
|
78
97
|
}
|
|
79
98
|
async doUploadSubmit(xpiPath, channel) {
|
|
@@ -83,19 +102,23 @@ export default class Client {
|
|
|
83
102
|
formData.set('upload', this.fileFromSync(xpiPath));
|
|
84
103
|
const {
|
|
85
104
|
uuid
|
|
86
|
-
} = await this.fetchJson(url, 'POST', formData);
|
|
105
|
+
} = await this.fetchJson(url, 'POST', formData, 'Upload failed');
|
|
87
106
|
return this.waitForValidation(uuid);
|
|
88
107
|
}
|
|
89
|
-
waitRetry(successFunc, checkUrl, checkInterval, abortInterval, context) {
|
|
108
|
+
waitRetry(successFunc, checkUrl, checkInterval, abortInterval, context, editUrl = null) {
|
|
90
109
|
let checkTimeout;
|
|
91
110
|
return new Promise((resolve, reject) => {
|
|
92
111
|
const abortTimeout = setTimeout(() => {
|
|
93
112
|
clearTimeout(checkTimeout);
|
|
94
|
-
|
|
113
|
+
let errorMessage = `${context}: timeout exceeded.`;
|
|
114
|
+
if (editUrl) {
|
|
115
|
+
errorMessage += ` When approved the signed XPI file can be downloaded from ${editUrl}`;
|
|
116
|
+
}
|
|
117
|
+
reject(new Error(errorMessage));
|
|
95
118
|
}, abortInterval);
|
|
96
119
|
const pollStatus = async () => {
|
|
97
120
|
try {
|
|
98
|
-
const responseData = await this.fetchJson(checkUrl, 'GET', undefined, 'Getting details failed
|
|
121
|
+
const responseData = await this.fetchJson(checkUrl, 'GET', undefined, 'Getting details failed');
|
|
99
122
|
const success = successFunc(responseData);
|
|
100
123
|
if (success) {
|
|
101
124
|
clearTimeout(abortTimeout);
|
|
@@ -113,17 +136,16 @@ export default class Client {
|
|
|
113
136
|
});
|
|
114
137
|
}
|
|
115
138
|
waitForValidation(uuid) {
|
|
116
|
-
log.info('Waiting for
|
|
139
|
+
log.info('Waiting for validation...');
|
|
117
140
|
return this.waitRetry(detailResponseData => {
|
|
118
141
|
if (!detailResponseData.processed) {
|
|
119
142
|
return null;
|
|
120
143
|
}
|
|
121
|
-
log.
|
|
144
|
+
log.debug('Validation results:', detailResponseData.validation);
|
|
122
145
|
if (detailResponseData.valid) {
|
|
123
146
|
return detailResponseData.uuid;
|
|
124
147
|
}
|
|
125
|
-
|
|
126
|
-
throw new Error('Validation failed, open the following URL for more information: ' + `${detailResponseData.url}`);
|
|
148
|
+
throw new Error(['Validation failed:\n', JSON.stringify(detailResponseData.validation, null, 2)].join(''));
|
|
127
149
|
}, new URL(`upload/${uuid}/`, this.apiUrl), this.validationCheckInterval, this.validationCheckTimeout, 'Validation');
|
|
128
150
|
}
|
|
129
151
|
async doNewAddonSubmit(uuid, metaDataJson) {
|
|
@@ -135,7 +157,7 @@ export default class Client {
|
|
|
135
157
|
...metaDataJson.version
|
|
136
158
|
}
|
|
137
159
|
};
|
|
138
|
-
return this.fetchJson(url, 'POST', JSON.stringify(jsonData));
|
|
160
|
+
return this.fetchJson(url, 'POST', JSON.stringify(jsonData), 'Submission failed (1)');
|
|
139
161
|
}
|
|
140
162
|
doNewAddonOrVersionSubmit(addonId, uuid, metaDataJson) {
|
|
141
163
|
const url = new URL(`addon/${addonId}/`, this.apiUrl);
|
|
@@ -146,10 +168,38 @@ export default class Client {
|
|
|
146
168
|
...metaDataJson.version
|
|
147
169
|
}
|
|
148
170
|
};
|
|
149
|
-
return this.
|
|
171
|
+
return this.fetchJson(url, 'PUT', JSON.stringify(jsonData), 'Submission failed (2)');
|
|
150
172
|
}
|
|
151
|
-
|
|
152
|
-
|
|
173
|
+
async doFormDataPatch(data, addonId, versionId) {
|
|
174
|
+
const patchUrl = new URL(`addon/${addonId}/versions/${versionId}/`, this.apiUrl);
|
|
175
|
+
try {
|
|
176
|
+
const formData = new FormData();
|
|
177
|
+
for (const field in data) {
|
|
178
|
+
formData.set(field, data[field]);
|
|
179
|
+
}
|
|
180
|
+
const response = await this.fetch(patchUrl, 'PATCH', formData);
|
|
181
|
+
if (!response.ok) {
|
|
182
|
+
throw new Error(`response status was ${response.status}`);
|
|
183
|
+
}
|
|
184
|
+
} catch (error) {
|
|
185
|
+
log.warn(`Upload of ${Object.keys(data)} failed: ${error}.`);
|
|
186
|
+
throw new Error(`Uploading ${Object.keys(data)} failed`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
async doAfterSubmit(addonId, newVersionId, editUrl, patchData) {
|
|
190
|
+
if (patchData && patchData.version) {
|
|
191
|
+
log.info(`Submitting ${Object.keys(patchData.version)} to version`);
|
|
192
|
+
await this.doFormDataPatch(patchData.version, addonId, newVersionId);
|
|
193
|
+
}
|
|
194
|
+
if (this.approvalCheckTimeout === 0) {
|
|
195
|
+
log.info(['Waiting for approval and download of signed XPI skipped.', `When approved the signed XPI file can be downloaded from ${editUrl}`].join(' '));
|
|
196
|
+
return this.returnResult(addonId);
|
|
197
|
+
}
|
|
198
|
+
const fileUrl = new URL(await this.waitForApproval(addonId, newVersionId, editUrl));
|
|
199
|
+
return this.downloadSignedFile(fileUrl, addonId);
|
|
200
|
+
}
|
|
201
|
+
waitForApproval(addonId, versionId, editUrl) {
|
|
202
|
+
log.info('Waiting for approval...');
|
|
153
203
|
return this.waitRetry(detailResponseData => {
|
|
154
204
|
const {
|
|
155
205
|
file
|
|
@@ -158,7 +208,7 @@ export default class Client {
|
|
|
158
208
|
return file.url;
|
|
159
209
|
}
|
|
160
210
|
return null;
|
|
161
|
-
}, new URL(`addon/${addonId}/versions/${versionId}/`, this.apiUrl), this.approvalCheckInterval, this.approvalCheckTimeout, 'Approval');
|
|
211
|
+
}, new URL(`addon/${addonId}/versions/${versionId}/`, this.apiUrl), this.approvalCheckInterval, this.approvalCheckTimeout, 'Approval', editUrl);
|
|
162
212
|
}
|
|
163
213
|
async fetchJson(url, method = 'GET', body, errorMsg = 'Bad Request') {
|
|
164
214
|
const response = await this.fetch(url, method, body);
|
|
@@ -167,13 +217,12 @@ export default class Client {
|
|
|
167
217
|
}
|
|
168
218
|
const data = await response.json();
|
|
169
219
|
if (!response.ok) {
|
|
170
|
-
|
|
171
|
-
throw new Error(`${errorMsg}: ${response.statusText || response.status}.`);
|
|
220
|
+
throw new Error([`${errorMsg}: ${response.statusText || response.status}`, JSON.stringify(data, null, 2)].join('\n'));
|
|
172
221
|
}
|
|
173
222
|
return data;
|
|
174
223
|
}
|
|
175
224
|
async fetch(url, method = 'GET', body) {
|
|
176
|
-
log.
|
|
225
|
+
log.debug(`${method}ing URL: ${url.href}`);
|
|
177
226
|
let headers = {
|
|
178
227
|
Authorization: await this.apiAuth.getAuthHeader(),
|
|
179
228
|
Accept: 'application/json',
|
|
@@ -185,12 +234,23 @@ export default class Client {
|
|
|
185
234
|
'Content-Type': 'application/json'
|
|
186
235
|
};
|
|
187
236
|
}
|
|
237
|
+
let agent;
|
|
238
|
+
if (this.apiProxy) {
|
|
239
|
+
agent = new HttpsProxyAgent(this.apiProxy);
|
|
240
|
+
}
|
|
188
241
|
return this.nodeFetch(url, {
|
|
189
242
|
method,
|
|
190
243
|
body,
|
|
191
|
-
headers
|
|
244
|
+
headers,
|
|
245
|
+
agent
|
|
192
246
|
});
|
|
193
247
|
}
|
|
248
|
+
returnResult(addonId, downloadedFiles) {
|
|
249
|
+
return {
|
|
250
|
+
id: addonId,
|
|
251
|
+
downloadedFiles
|
|
252
|
+
};
|
|
253
|
+
}
|
|
194
254
|
async downloadSignedFile(fileUrl, addonId) {
|
|
195
255
|
const filename = fileUrl.pathname.split('/').pop(); // get the name from fileUrl
|
|
196
256
|
const dest = `${this.downloadDir}/${filename}`;
|
|
@@ -204,54 +264,102 @@ export default class Client {
|
|
|
204
264
|
log.info(`Download of signed xpi failed: ${error}.`);
|
|
205
265
|
throw new Error(`Downloading ${filename} failed`);
|
|
206
266
|
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
downloadedFiles: [filename]
|
|
210
|
-
};
|
|
267
|
+
log.info(`Signed xpi downloaded: ${dest}`);
|
|
268
|
+
return this.returnResult(addonId, [filename]);
|
|
211
269
|
}
|
|
212
270
|
async saveToFile(contents, destPath) {
|
|
213
|
-
return
|
|
271
|
+
return streamPromises.pipeline(contents, createWriteStream(destPath));
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/*
|
|
275
|
+
This function aims to quickly hash the contents of the zip file that's being uploaded,
|
|
276
|
+
to compare it to the previous zip file that was uploaded, so we can skip the upload for
|
|
277
|
+
efficiency.
|
|
278
|
+
CRCs are used from the zip to avoid having to extract and hash all the files.
|
|
279
|
+
Two zips that have different byte contents in their files must have a different hash;
|
|
280
|
+
but returning a different hash when the contents are the same in some cases is acceptable
|
|
281
|
+
- a false mismatch does not result in lost data.
|
|
282
|
+
*/
|
|
283
|
+
async hashXpiCrcs(filePath, asyncFsReadFile = defaultAsyncFsReadFile) {
|
|
284
|
+
const zip = await JSZip.loadAsync(asyncFsReadFile(filePath, {
|
|
285
|
+
createFolders: true
|
|
286
|
+
}));
|
|
287
|
+
const hash = createHash('sha256');
|
|
288
|
+
const entries = [];
|
|
289
|
+
zip.forEach((relativePath, entry) => {
|
|
290
|
+
var _entry$_data;
|
|
291
|
+
let path = relativePath.replace(/\/+$/, '');
|
|
292
|
+
if (entry.dir) {
|
|
293
|
+
path += '/';
|
|
294
|
+
}
|
|
295
|
+
// if the file is 0 bytes or a dir `_data` is missing so assume crc is 0
|
|
296
|
+
entries.push({
|
|
297
|
+
path,
|
|
298
|
+
crc32: ((_entry$_data = entry._data) === null || _entry$_data === void 0 ? void 0 : _entry$_data.crc32) || 0
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
entries.sort((a, b) => a.path === b.path ? 0 : a.path > b.path ? 1 : -1);
|
|
302
|
+
hash.update(JSON.stringify(entries));
|
|
303
|
+
return hash.digest('hex');
|
|
214
304
|
}
|
|
215
|
-
async
|
|
216
|
-
const
|
|
217
|
-
|
|
305
|
+
async getPreviousUuidOrUploadXpi(xpiPath, channel, savedUploadUuidPath, saveUploadUuidToFileFunc = saveUploadUuidToFile, getUploadUuidFromFileFunc = getUploadUuidFromFile) {
|
|
306
|
+
const [{
|
|
307
|
+
uploadUuid: previousUuid,
|
|
308
|
+
channel: previousChannel,
|
|
309
|
+
xpiCrcHash: previousHash
|
|
310
|
+
}, xpiCrcHash] = await Promise.all([getUploadUuidFromFileFunc(savedUploadUuidPath), this.hashXpiCrcs(xpiPath)]);
|
|
311
|
+
let uploadUuid;
|
|
312
|
+
if (previousChannel !== channel || xpiCrcHash !== previousHash) {
|
|
313
|
+
uploadUuid = await this.doUploadSubmit(xpiPath, channel);
|
|
314
|
+
await saveUploadUuidToFileFunc(savedUploadUuidPath, {
|
|
315
|
+
uploadUuid,
|
|
316
|
+
channel,
|
|
317
|
+
xpiCrcHash
|
|
318
|
+
});
|
|
319
|
+
} else {
|
|
320
|
+
uploadUuid = previousUuid;
|
|
321
|
+
}
|
|
322
|
+
return uploadUuid;
|
|
323
|
+
}
|
|
324
|
+
async postNewAddon(uploadUuid, savedIdPath, metaDataJson, patchData, saveIdToFileFunc = saveIdToFile) {
|
|
218
325
|
const {
|
|
219
326
|
guid: addonId,
|
|
220
|
-
|
|
221
|
-
id: newVersionId
|
|
327
|
+
version: {
|
|
328
|
+
id: newVersionId,
|
|
329
|
+
edit_url: editUrl
|
|
222
330
|
}
|
|
223
331
|
} = await this.doNewAddonSubmit(uploadUuid, metaDataJson);
|
|
224
332
|
await saveIdToFileFunc(savedIdPath, addonId);
|
|
225
333
|
log.info(`Generated extension ID: ${addonId}.`);
|
|
226
334
|
log.info('You must add the following to your manifest:');
|
|
227
335
|
log.info(`"browser_specific_settings": {"gecko": {"id": "${addonId}"}}`);
|
|
228
|
-
|
|
229
|
-
return this.downloadSignedFile(fileUrl, addonId);
|
|
336
|
+
return this.doAfterSubmit(addonId, newVersionId, editUrl, patchData);
|
|
230
337
|
}
|
|
231
|
-
async putVersion(
|
|
232
|
-
const uploadUuid = await this.doUploadSubmit(xpiPath, channel);
|
|
233
|
-
await this.doNewAddonOrVersionSubmit(addonId, uploadUuid, metaDataJson);
|
|
234
|
-
const url = new URL(`addon/${addonId}/versions/?filter=all_with_unlisted`, this.apiUrl);
|
|
338
|
+
async putVersion(uploadUuid, addonId, metaDataJson, patchData) {
|
|
235
339
|
const {
|
|
236
|
-
|
|
237
|
-
id: newVersionId
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
return this.
|
|
340
|
+
version: {
|
|
341
|
+
id: newVersionId,
|
|
342
|
+
edit_url: editUrl
|
|
343
|
+
}
|
|
344
|
+
} = await this.doNewAddonOrVersionSubmit(addonId, uploadUuid, metaDataJson);
|
|
345
|
+
return this.doAfterSubmit(addonId, newVersionId, editUrl, patchData);
|
|
242
346
|
}
|
|
243
347
|
}
|
|
244
348
|
export async function signAddon({
|
|
245
349
|
apiKey,
|
|
246
350
|
apiSecret,
|
|
351
|
+
apiProxy,
|
|
247
352
|
amoBaseUrl,
|
|
248
|
-
|
|
353
|
+
validationCheckTimeout,
|
|
354
|
+
approvalCheckTimeout,
|
|
249
355
|
id,
|
|
250
356
|
xpiPath,
|
|
251
357
|
downloadDir,
|
|
252
358
|
channel,
|
|
253
359
|
savedIdPath,
|
|
360
|
+
savedUploadUuidPath,
|
|
254
361
|
metaDataJson = {},
|
|
362
|
+
submissionSource,
|
|
255
363
|
userAgentString,
|
|
256
364
|
SubmitClient = Client,
|
|
257
365
|
ApiAuthClass = JwtApiAuth
|
|
@@ -259,7 +367,7 @@ export async function signAddon({
|
|
|
259
367
|
try {
|
|
260
368
|
const stats = await fsPromises.stat(xpiPath);
|
|
261
369
|
if (!stats.isFile()) {
|
|
262
|
-
throw new Error(
|
|
370
|
+
throw new Error('not a file');
|
|
263
371
|
}
|
|
264
372
|
} catch (statError) {
|
|
265
373
|
throw new Error(`error with ${xpiPath}: ${statError}`);
|
|
@@ -275,22 +383,78 @@ export async function signAddon({
|
|
|
275
383
|
apiKey,
|
|
276
384
|
apiSecret
|
|
277
385
|
}),
|
|
386
|
+
apiProxy,
|
|
278
387
|
baseUrl,
|
|
279
|
-
validationCheckTimeout
|
|
280
|
-
approvalCheckTimeout
|
|
388
|
+
validationCheckTimeout,
|
|
389
|
+
approvalCheckTimeout,
|
|
281
390
|
downloadDir,
|
|
282
391
|
userAgentString
|
|
283
392
|
});
|
|
393
|
+
const uploadUuid = await client.getPreviousUuidOrUploadXpi(xpiPath, channel, savedUploadUuidPath);
|
|
394
|
+
const patchData = {};
|
|
395
|
+
// if we have a source file we need to upload we patch after the create
|
|
396
|
+
if (submissionSource) {
|
|
397
|
+
try {
|
|
398
|
+
const stats2 = await fsPromises.stat(submissionSource);
|
|
399
|
+
if (!stats2.isFile()) {
|
|
400
|
+
throw new Error('not a file');
|
|
401
|
+
}
|
|
402
|
+
} catch (statError) {
|
|
403
|
+
throw new Error(`error with ${submissionSource}: ${statError}`);
|
|
404
|
+
}
|
|
405
|
+
patchData.version = {
|
|
406
|
+
source: client.fileFromSync(submissionSource)
|
|
407
|
+
};
|
|
408
|
+
}
|
|
284
409
|
|
|
285
410
|
// We specifically need to know if `id` has not been passed as a parameter because
|
|
286
411
|
// it's the indication that a new add-on should be created, rather than a new version.
|
|
287
412
|
if (id === undefined) {
|
|
288
|
-
return client.postNewAddon(
|
|
413
|
+
return client.postNewAddon(uploadUuid, savedIdPath, metaDataJson, patchData);
|
|
289
414
|
}
|
|
290
|
-
return client.putVersion(
|
|
415
|
+
return client.putVersion(uploadUuid, id, metaDataJson, patchData);
|
|
291
416
|
}
|
|
292
417
|
export async function saveIdToFile(filePath, id) {
|
|
293
418
|
await fsPromises.writeFile(filePath, ['# This file was created by https://github.com/mozilla/web-ext', '# Your auto-generated extension ID for addons.mozilla.org is:', id.toString()].join('\n'));
|
|
294
419
|
log.debug(`Saved auto-generated ID ${id} to ${filePath}`);
|
|
295
420
|
}
|
|
421
|
+
export async function saveUploadUuidToFile(filePath, {
|
|
422
|
+
uploadUuid,
|
|
423
|
+
channel,
|
|
424
|
+
xpiCrcHash
|
|
425
|
+
}) {
|
|
426
|
+
await fsPromises.writeFile(filePath, JSON.stringify({
|
|
427
|
+
uploadUuid,
|
|
428
|
+
channel,
|
|
429
|
+
xpiCrcHash
|
|
430
|
+
}));
|
|
431
|
+
log.debug(`Saved upload UUID ${uploadUuid}, xpi crc hash ${xpiCrcHash}, and channel ${channel} to ${filePath}`);
|
|
432
|
+
}
|
|
433
|
+
export async function getUploadUuidFromFile(filePath, asyncFsReadFile = defaultAsyncFsReadFile) {
|
|
434
|
+
try {
|
|
435
|
+
const content = await asyncFsReadFile(filePath, 'utf-8');
|
|
436
|
+
const {
|
|
437
|
+
uploadUuid,
|
|
438
|
+
channel,
|
|
439
|
+
xpiCrcHash
|
|
440
|
+
} = JSON.parse(content);
|
|
441
|
+
log.debug(`Found upload uuid:${uploadUuid}, channel:${channel}, hash:${xpiCrcHash} in ${filePath}`);
|
|
442
|
+
return {
|
|
443
|
+
uploadUuid,
|
|
444
|
+
channel,
|
|
445
|
+
xpiCrcHash
|
|
446
|
+
};
|
|
447
|
+
} catch (error) {
|
|
448
|
+
if (isErrorWithCode('ENOENT', error)) {
|
|
449
|
+
log.debug(`No upload uuid file found at: ${filePath}`);
|
|
450
|
+
} else {
|
|
451
|
+
log.debug(`Invalid upload uuid file contents in ${filePath}: ${error}`);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
return {
|
|
455
|
+
uploadUuid: '',
|
|
456
|
+
channel: '',
|
|
457
|
+
xpiCrcHash: ''
|
|
458
|
+
};
|
|
459
|
+
}
|
|
296
460
|
//# sourceMappingURL=submit-addon.js.map
|