Skip to content

Conversation

@Azeirah
Copy link

@Azeirah Azeirah commented Oct 3, 2016

#507

This pull request is not ready. Tests need to be written, documentation needs to be updated, and a short code-review would be really helpful. I've done some ad-hoc testing with this command

Usage and testing:

This feature expects a manifest.json to exist. For it to generate a new updateManifest, an older one needs to exist on a web server somewhere. This is configured in the manifest.json's applications.gecko.update_url property. For my ad-hoc tests I've simply used my personal web-server.

The command that I used

./web-ext sign --api-key {YOUR-USER} --api-secret {YOUR-API} --api-url-prefix https://addons-dev.allizom.org/api/v3 --update-link https://example.com/{xpiFileName}

What I did

  • src/cmd/sign.js, add function generateNewUpdateManifest(...)
    • Fetch an updateManifest.json from the location pointed to by manifest.applications.gecko.update_url
    • Error messages (show the user an error message, and exit with error code)
      • the old, fetched updateManifest.json is invalid
      • unable to fetch updateManifest.json due to connection error
      • argument passed to --update-link flag is invalid (== is missing {xpiFileName})
    • Warning messages
      • The user passed the --update-link flag, but manifest.json lacks an applications.gecko.update_url property
      • The fetched updateManifest.json had no previous release of an addon with this extension-id. Warn the user that if this should not be the case to check her extension-id
    • Export the generated update manifest to the artifacts directory
  • all code complies to flow
  • all code complies to the linter
  • test cases?
  • program.js, add --update-link param to sign .command
  • documentation
    • add description of the --update-link param
    • add explanation on how to auto-generate updateManifest

What still needs to be done:

  1. Automated tests
  2. Code review
  3. Additions to the official documentation

src/cmd/sign.js Outdated
downloadDir: artifactsDir,
});

if (updateLink && updateLink.length > 0 && signingResult.success) {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updateLink.length > 0 is unnecessary, overlooked this one

src/cmd/sign.js Outdated
if (error) {
reject(error);
}
resolve();
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should probably return something, {success: Boolean, newUpdateManifestPath: String, ...?}

src/cmd/sign.js Outdated
extensionID = id;
}
await generateNewUpdateManifest(
extensionID, manifestData.version, signingResult.downloadedFiles[0],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keep in mind that signingResult.downloadedFiles is an array of many files. I'm not entirely sure if newer WebExtensions will have multiple files but historically AMO would sign one file for desktop, one file for Android, and so on (this was mostly for old add-ons with binary components though). In any case, I suppose we should try to support an array of files?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand that's the case, but how would that fit inside an updateManifest.json file? Here's what I don't get:

id: 123456789
version: 1.2
android: someGeneratedXPI-ANDROID-v1.2.xpi
desktop: someGeneratedXPI-DESKTOP-v1.2.xpi

The older updateManifest.json file looks like this

{
    "addons": {
        "123456789": {
            "updates": []
        }
    }
}

So let's update it with the given data

{
    "addons": {
        "123456789": {
            "updates": [
                {
                    "version": "1.2",
                    "update_link": "https://example.com/someGeneratedXPI-ANDROID-v1.2.xpi"
                },
                {
                    // also version 1.2?
                    // this shouldn't be possible, right?
                    // the android XPI has the same extensionID, no?
                    "version": "1.2",
                    "update_link": "https://example.com/someGeneratedXPI-DESKTOP-v1.2.xpi"
                }
            ]
        }
    }
}

src/cmd/sign.js Outdated
`${manifestData.applications.gecko.update_url}`);
}

if (updateLink.indexOf('{xpiFileName}') === -1) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All of these usage errors should happen at the start of the sign command so that developers see them immediately rather than having to wait until the signing process completes

src/cmd/sign.js Outdated

return new Promise(function(resolve, reject) {
log.info('Saving to', path.join(artifactsDir, updateManifestFileName));
fs.writeFile(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can just await fs.writeFile() (it returns a promise since we're using mz/fs)

@kumar303
Copy link
Contributor

kumar303 commented Oct 4, 2016

Looks like you're on the right track! Since there are a lot of error cases, I'd suggest breaking it into smaller functions so that it's easier to test.

@Azeirah
Copy link
Author

Azeirah commented Oct 6, 2016

Thanks for the feedback, I'm working on it!

@Azeirah
Copy link
Author

Azeirah commented Oct 10, 2016

I've pushed a version where I split-up the function into three parts, @kumar303
However, now some of the existing tests are failing because of a timeout, I'm checking that out atm

Fixed that, it was because I removed a check that was there before.

Now tests need to be written, and some documentation. Unless you still have some remarks?

Copy link
Contributor

@kumar303 kumar303 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I took a quick look and requested some changes. I can take a closer look when you start adding tests.

I can do another review once you start adding tests if you just want some early feedback before finishing all tests. Thanks for all the follow-ups!

src/cmd/sign.js Outdated
let oldUpdateManifestDataMakeFlowHappy =
oldUpdateManifestData == null ? {} : oldUpdateManifestData;
await createNewUpdateManifest(
extensionID, signingResult.downloadedFiles[0], artifactsDir,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, I looked more into the case where multiple files could exist in signingResult.downloadedFiles and this is just for legacy (non-webextension) add-ons. So, yeah, it should never happen. However, I'd like to see something that checks the length of this array and throws an error if it's longer than one. The error should just explain how such a situation is not supported.

src/cmd/sign.js Outdated
// eslint-disable-next-line
async function createNewUpdateManifest(
id: ?string, XPIPath: string, artifactsDir,
manifestData: Object, updateLink: string, oldUpdateManifestData: Object
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All arguments should be keyword args to make them more readable and easier to change later on. This is a simple change using ES6 destructors (just add braces):

async function createNewUpdateManifest({
  id: ?string, XPIPath: string, artifactsDir, ...
}) {

src/cmd/sign.js Outdated
}

try {
resolve(JSON.parse(oldUpdateManifest));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you need resolve() here? I think if you declare fetchUpdateManifest as async then you can simply do return JSON.parse(oldUpdateManifest) on this line.

src/cmd/sign.js Outdated
} catch (e) {
throw new WebExtError(
`Unable to parse ${updateManifestFileName} file located at ` +
`${manifestData.applications.gecko.update_url}`);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This messages needs to include the original error message and try to avoid one character variables. I would suggest calling it catch(error)

// note: `var == null` is equal to
// `(variable === undefined || variable === null)`
// see: https://stackoverflow.com/questions/2647867
if (manifestData == null ||
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the top-level sign() function accepts the id parameter which gets a value when someone does web-ext sign --id=custom@example. This validation check looks like it is not honoring that id variable (for when it's not undefined).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what you mean here, the prevalidateUpdateManifestParams function doesn't check anything related to the id, it is left alone completely.
The right id value gets selected earlier on in the code

if (id && manifestId) {
  throw new WebExtError(
    `Cannot set custom ID ${id} because manifest.json ` +
    `declares ID ${manifestId}`);
}

if (manifestId) {
  id = manifestId;
}

if (!id && idFromSourceDir) {
  log.info(
    `Using previously auto-generated extension ID: ${idFromSourceDir}`);
  id = idFromSourceDir;
}

if (!id) {
  log.warn('No extension ID specified (it will be auto-generated)');
}

And this id later gets passed on to the createNewUpdateManifest function

let signingResult = await signAddon({...});
[...]
let extensionID;
if (signingResult.id) {
  extensionID = signingResult.id;
} else {
  extensionID = id;
}
[...]
await createNewUpdateManifest(
  extensionID, signingResult.downloadedFiles[0], artifactsDir,
  manifestData, updateLink, oldUpdateManifestData,
);

I'm probably just not completely getting what you mean here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, sorry, I have no idea what I was talking about either. I guess ignore it for now. I will do another review when you've addressed the other change requests anyway.

src/cmd/sign.js Outdated
'as a substitute for the XPI file');
}

return true;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this return value doesn't look like it's used by anything

src/cmd/sign.js Outdated
return fs.writeFile(
path.join(artifactsDir, updateManifestFileName),
newUpdateManifest,
function(error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can't use the error callback here since you are returning a promise -- i.e. the caller needs be notified about the error too. You can use chaining instead:

return fs.writeFile(...)
  .catch((error) => {
    throw new WebExtError(
      `Was unable to write updated ${addonName}: ${error}`);
  });

src/program.js Outdated
'update-link': {
describe:
'A URL to where your XPI files will be hosted. ' +
'ex: https://example.com/plugins/{xpiFileName} ' +
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New sentences should start with a capital letter. I think it would also be helpful to spell out Example:

src/program.js Outdated
'ex: https://example.com/plugins/{xpiFileName} ' +
'if you select this option, an updateManifest ' +
'file will be generated ' +
'in the web-ext-artifacts folder',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Say in the --artifacts-dir folder instead since this value is customizable.

src/util/net.js Outdated
* Returns a promise resolving to a tuple of [body, statusCode]
* Will throw an error on connection errors
*/
export function httpFetchFile(url: string): Promise<[string, number]> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code would be easier to write (and easier to test) if you use the request module. I don't mind adding it as a dependency.

@kumar303
Copy link
Contributor

Hi @Azeirah. There's no rush on this but I just wanted to check in and see if you had any questions? Thanks for your continued work on it -- I think this will be a useful feature!

@Azeirah
Copy link
Author

Azeirah commented Dec 5, 2016

Hey @kumar303, I ran into some walls while developing this feature. I came here expecting that it would be a breeze to implement the whole thing, and that was partially true, the feature wasn't too hard to implement. Everything else was a bit overwhelming for me, complying to fairly strict coding standards (flux), learning several new ES6 features and learning how to write tests. Now that some time has passed I'll try to tackle the issue again.

The feature itself would be very useful for me as well, so I will continue work on this. I'll let you know if I run into any troubles.

@kumar303
Copy link
Contributor

kumar303 commented Dec 7, 2016

I understand completely. Each patch takes a bit more time on this project, especially in writing the tests. We require automated tests to make sure we don't regress the features accidentally. Let me know if you need any help.

@kumar303
Copy link
Contributor

kumar303 commented Jan 3, 2017

Hi @Azeirah . Would it help to try landing a smaller patch for this feature? It still seems like a nice feature to have. Let me know if you are stuck on anything.

@Azeirah
Copy link
Author

Azeirah commented Jan 20, 2017

@kumar303 I've picked it up again, just finished doing my exams at school so I have some time again. Apologies for the little communication

@kumar303 kumar303 self-assigned this Jan 23, 2017
@Azeirah
Copy link
Author

Azeirah commented Jan 24, 2017

I've pushed the changes where I fix the issues you pointed out.

@kumar303
Copy link
Contributor

Thanks! Can you merge in master and resolve the conflicts? That will make it easier for me to review and test.

@kumar303 kumar303 changed the title Add feature to automatically generate updateManifest files from previous updateManifests feat: Automatically generate updateManifest files from previous updateManifests Feb 2, 2017
@kumar303 kumar303 removed their assignment Feb 16, 2017
@shubheksha
Copy link
Contributor

Hey @Azeirah, would it be possible to pick this up any time soon again?

@rpl
Copy link
Member

rpl commented Jun 6, 2022

Closing as "state: do not merge", the issue is still open and technically not covered yet by web-ext itself, unfortunately this PR has been branched out quite some time ago and it is unlikely to be resumed from this old branch.

@rpl rpl closed this Jun 6, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants