-
Notifications
You must be signed in to change notification settings - Fork 366
feat: Automatically generate updateManifest files from previous updateManifests #552
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
src/cmd/sign.js
Outdated
| downloadDir: artifactsDir, | ||
| }); | ||
|
|
||
| if (updateLink && updateLink.length > 0 && signingResult.success) { |
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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], |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
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)
|
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. |
|
Thanks for the feedback, I'm working on it! |
…ns for testability
|
I've pushed a version where I split-up the function into three parts, @kumar303 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? |
kumar303
left a comment
There was a problem hiding this 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, |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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)); |
There was a problem hiding this comment.
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}`); |
There was a problem hiding this comment.
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 || |
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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} ' + |
There was a problem hiding this comment.
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', |
There was a problem hiding this comment.
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]> { |
There was a problem hiding this comment.
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.
|
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! |
|
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. |
|
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. |
|
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. |
|
@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 |
…pendency to perform http-requests
|
I've pushed the changes where I fix the issues you pointed out. |
|
Thanks! Can you merge in master and resolve the conflicts? That will make it easier for me to review and test. |
|
Hey @Azeirah, would it be possible to pick this up any time soon again? |
|
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. |
#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_urlproperty. 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
{xpiFileName})sign.commandWhat still needs to be done: