From 652a77c41ce7b068d0dcd3866073be06bcdfca6c Mon Sep 17 00:00:00 2001 From: William Elwood Date: Sat, 10 Feb 2018 07:45:30 +0000 Subject: [PATCH] feat: Added `channel` to specify a release channel Fixes #248 --- README.md | 4 ++++ src/amo-client.js | 12 +++++++++++- src/index.js | 5 +++++ tests/test.amo-client.js | 23 +++++++++++++++++++++++ tests/test.sign.js | 10 ++++++++++ 5 files changed, 53 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0933c307..f5d287cb 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,10 @@ signAddon( // WebExtensions do not require an ID. // See the notes below about dealing with IDs. id: 'your-addon-id@somewhere', + // The release channel (listed or unlisted). + // Ignored for new add-ons, which are always unlisted. + // Default: most recently used channel. + channel: undefined, // Save downloaded files to this directory. // Default: current working directory. downloadDir: undefined, diff --git a/src/amo-client.js b/src/amo-client.js index 407b72ee..fe5c116c 100644 --- a/src/amo-client.js +++ b/src/amo-client.js @@ -77,12 +77,13 @@ export class Client { * - `xpiPath` Path to xpi file. * - `guid` Optional add-on GUID, aka the ID in install.rdf. * - `version` add-on version string. + * - `channel` release channel (listed, unlisted). * @return {Promise} signingResult with keys: * - success: boolean * - downloadedFiles: Array of file objects * - id: string identifier for the signed add-on */ - sign({guid, version, xpiPath}) { + sign({guid, version, channel, xpiPath}) { const formData = { upload: this._fs.createReadStream(xpiPath), @@ -93,11 +94,20 @@ export class Client { // PUT to a specific URL for this add-on + version. addonUrl += encodeURIComponent(guid) + "/versions/" + encodeURIComponent(version) + "/"; + if (channel) { + formData.channel = channel; + } } else { // POST to a generic URL to create a new add-on. this.debug("Signing add-on without an ID"); method = "post"; formData.version = version; + if (channel) { + this.logger.warn( + "Specifying a channel for a new add-on is unsupported. " + + "New add-ons are always in the unlisted channel." + ); + } } const doRequest = this[method].bind(this); diff --git a/src/index.js b/src/index.js index bc7e52d1..40999191 100644 --- a/src/index.js +++ b/src/index.js @@ -24,6 +24,10 @@ export default function signAddon( // This must match the expiration time that the API server accepts. apiJwtExpiresIn, verbose=false, + // The release channel (listed or unlisted). + // Ignored for new add-ons, which are always unlisted. + // Defaults to most recently used channel. + channel, // Number of milleseconds to wait before giving up on a // response from Mozilla's web service. timeout, @@ -87,6 +91,7 @@ export default function signAddon( xpiPath: xpiPath, guid: id, version: version, + channel: channel, }); }); diff --git a/tests/test.amo-client.js b/tests/test.amo-client.js index e9aaa032..6eff0b94 100644 --- a/tests/test.amo-client.js +++ b/tests/test.amo-client.js @@ -138,6 +138,8 @@ describe("amoClient.Client", function() { expect(putCall.conf.formData.upload).to.be.equal("fake-read-stream"); // When doing a PUT, the version is in the URL not the form data. expect(putCall.conf.formData.version).to.be.undefined; + // When no channel is supplied, the API is expected to use the most recent channel. + expect(putCall.conf.formData.channel).to.be.undefined; expect(waitForSignedAddon.called).to.be.equal(true); expect(waitForSignedAddon.firstCall.args[0]) @@ -171,6 +173,8 @@ describe("amoClient.Client", function() { expect(call.conf.url).to.match(/\/addons\/$/); expect(call.conf.formData.upload).to.be.equal("fake-read-stream"); expect(call.conf.formData.version).to.be.equal(conf.version); + // Channel is not a valid parameter for new add-ons. + expect(call.conf.formData.channel).to.be.undefined; expect(waitForSignedAddon.called).to.be.equal(true); expect(waitForSignedAddon.firstCall.args[0]) @@ -178,6 +182,25 @@ describe("amoClient.Client", function() { }); }); + it("lets you sign an add-on on a specific channel", function() { + var conf = { + guid: "a-guid", + version: "a-version", + channel: "listed", + }; + var waitForSignedAddon = sinon.spy(() => {}); + this.client.waitForSignedAddon = waitForSignedAddon; + + this.client._request = new MockRequest({ + httpResponse: {statusCode: 202}, + }); + + return this.sign(conf).then(() => { + expect(this.client._request.calls[0].conf.formData.channel) + .to.be.equal("listed"); + }); + }); + it("handles already validated add-ons", function() { var waitForSignedAddon = sinon.spy(() => {}); this.client.waitForSignedAddon = waitForSignedAddon; diff --git a/tests/test.sign.js b/tests/test.sign.js index 46fcb88f..a05649fd 100644 --- a/tests/test.sign.js +++ b/tests/test.sign.js @@ -97,6 +97,16 @@ describe("sign", function() { }); }); + it("passes release channel to the signer", () => { + const channel = "listed"; + return runSignCmd({ + cmdOptions: {channel}, + }).then(function() { + expect(signingCall.called).to.be.equal(true); + expect(signingCall.firstCall.args[0].channel).to.be.equal(channel); + }); + }); + it("passes JWT expiration to the signing client", () => { const expiresIn = 60 * 15; // 15 minutes return runSignCmd({