-
Notifications
You must be signed in to change notification settings - Fork 6
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
feat: pass argument to main function #5
base: master
Are you sure you want to change the base?
Conversation
👍 thanks @saintsebastian |
index.js
Outdated
exports.main = function main() { | ||
if (!process.argv[2]) { | ||
exports.main = function main(dirPath) { | ||
if (!dirPath) { | ||
console.error(`${chalk.red("Missing project dir name.")}\n`); | ||
console.log(USAGE_MSG); | ||
process.exit(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.
@saintsebastian a process.exit(...)
when using this package as a library is super annoying (as well as logging on the console), we can throw an Error instance from here and move the process.exit(...)
and the logging in the CLI wrapper that we have in "bin/create-webextension", how that sounds to you?
b9174fd
to
12be9ae
Compare
I am contemplating how to make using the package as a library easier, maybe wrapping current code in |
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 think that what we moved in the "bin/create-webextension"
wrapper is not going to be needed when this package is used as a library, but the main function could probably use some additional parameter (e.g. to configure its behavior when used as a library, a "quite mode"
in which this library does not log anything on the console could be really helpful, and also to inject the dependencies and simplify its unit testing).
Also, at line 115 and 119 of the "index.js"
module, there are still two process.exit(1)
, it would be better to turn all these error conditions into rejections of the Promise returned by the main function (which would allow a caller to customize how these error conditions will be handled).
index.js
Outdated
process.exit(1); | ||
exports.main = function main(dirPath) { | ||
if (!dirPath) { | ||
throw new Error("Project directory name is a compulsory argument"); |
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.
Nit, how about mandatory
instead of compulsory
?
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.
that's the word i was looking for!
@rpl i moved console.log to |
bin/create-webextension
Outdated
require("..").main(process.argv[2]); | ||
require("..").main(process.argv[2]) | ||
.then(console.log) | ||
.catch(err => console.log(err.message)); |
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.
@saintsebastian This is definitely better for being able to use the module as a library but we still need to make the following tweaks:
- we want to be sure that the CLI command exits with an "error exit code" (e.g. using process.exit explicitly) when there we catch an error here
- we want to be sure that we also produce a stack trace when the caught error is not expected (which currently
means "anything but an 'EEXIST' error"), and we can do it by just re-throwing the caught error - The expected errors (currently only the one above) should not print a stack trace and they should be possibly be
printed using the color red
To achieve these behaviors we can use the same strategy that we are using in web-ext
:
- define a custom UsageError class, and throw an instance of this class for the "expected" errors
- then, in the bin wrapper, check for the errors that are
instanceof
the UsageError and change the behavior
based on it - just re-throw the "unexpected" errors, so that they will be logged with the stack trace in the console
@rpl Thanks for the review, i tried to add the behavior you were talking about. In case of |
80ce0fe
to
9b35d0e
Compare
9b35d0e
to
9b9e16c
Compare
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.
Hi @saintsebastian
This looks pretty nice, we need to fix some additional detail, but we are definitely almost there.
Thanks a lot for having started to also create some test units, we should probably move the test files into two subdirectories, e.g. unit and integration, to make it immediately clear the different testing approach).
It seems that we could easily add also a test for the main function itself, what do you think about it?
__tests__/errors.test.js
Outdated
const onlyInstancesOf = require("../errors").onlyInstancesOf; | ||
|
||
describe("onlyInstancesOf", () => { | ||
test("catches specified 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.
@saintsebastian this test looks great, but we need also one for the opposite scenario: it should re-throws instances of other errors.
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.
@rpl wrote simple version of the second test, web-ext test have makeSureItFails
helper to avoid accidentally passing. Should we implement some simple version of that for the future?
errors.js
Outdated
}; | ||
|
||
exports.UsageError = class UsageError extends ES6Error { | ||
// eslint-disable-next-line no-useless-constructor |
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.
@saintsebastian have you tried to declare the class without overriding the constructor with its default implementation? (instead of using the eslint-disable-next-line
comment)
errors.js
Outdated
if (error instanceof errorType) { | ||
return handler(error); | ||
// eslint-disable-next-line no-else-return | ||
} else { |
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.
@saintsebastian no need to an else block here (if you enter the if block you are going to return in any case. (that would probably "make eslint happy again" :-))
errors.js
Outdated
// eslint-disable-next-line no-else-return | ||
} else { | ||
console.log(error.stack); | ||
process.exit(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.
@saintsebastian process.exit(1)
seems a bit extreme here in a reusable helper, it will make the application to potentially exit immediatelly everywhere we use this helper, we should just throw error;
here and let the caller handle the re-throw exception as needed (e.g. logging the error stack and process.exit(1)
).
index.js
Outdated
@@ -110,16 +107,13 @@ exports.main = function main() { | |||
.then(() => getProjectReadme(projectDirName)) | |||
.then(projectReadme => fs.writeFile(path.join(projectPath, "README.md"), | |||
stripAnsi(projectReadme))) | |||
.then(() => getProjectCreatedMessage(projectPath)) | |||
.then(console.log); | |||
.then(() => getProjectCreatedMessage(projectPath)); |
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.
@saintsebastian we should probably return a more useful object to the caller, e.g. something like:
.then(() => {
const projectCreatedMessage = getProjectCreatedMessage(projectPath);
return {projectPath, projectCreatedMessage};
});
This should also make much more clear what line 16 of the bin/create-webextension
is actually doing.
index.js
Outdated
} | ||
}).catch((error) => { | ||
console.error(error); | ||
process.exit(1); | ||
throw 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.
@saintsebastian we can omit this catch, it is basically doing what it is going to happen if we omit it.
bin/create-webextension
Outdated
require("..").main(process.argv[2]) | ||
.then(console.log) | ||
.catch(onlyInstancesOf(UsageError, (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.
@saintsebastian it would be better to use console.error here (so that the logs are going to be printer in the stderr instead of stdout).
also, after this catch we should handle the other errors by chaining another catch:
.catch(onlyInstancesOf(UsageError, (error) => {
...
})
.catch((error) => {
console.error(`${chalk.red(error.message)}: ${error.stack}\n`); // or something similar that prints both the message and the stack
process.exit(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.
@rpl wanted to avoid chaining to catches, so thanks for making this clear.
hey @rpl! I added some tests and tried to address the thing in your review. Some tests are a bit clunky, so i would appreciate your opinion. |
__tests__/integration/index.test.js
Outdated
|
||
const execDirPath = path.join(__dirname, "..", "bin"); | ||
const execDirPath = path.join(__dirname, "../..", "bin"); |
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.
@saintsebastian this should be path.join(__dirname, "..", "..", "bin)
(so that nodejs can add the right path separator based on the current OS)
__tests__/unit/index.test.js
Outdated
async (tmpPath) => { | ||
const projName = "target"; | ||
const targetDir = path.join(tmpPath, projName); | ||
const expectedMessage = |
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.
@saintsebastian we could probably export the getProjectCreatedMessage
from the index.js module and use it to generate the expected message here and make this test less verbose (and not affected by changes to the message)
Then, we can test the exported getProjectCreatedMessage
on its own (possibly by using expect.stringMatching
or expect(...).toMatch(...)
)
__tests__/unit/index.test.js
Outdated
}); | ||
|
||
const contentStat = await fs.stat(path.join(targetDir, "content.js")); | ||
expect(contentStat.isDirectory()).toBeFalsy(); |
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 think that it would be better to assert expect(statRes.isFile()).toBeTruthy()
(e.g. because by not being a directory
we are also including symbolic links, char and block devices, socket files etc.)
__tests__/unit/index.test.js
Outdated
getPlaceholderIconFn: getPlaceholderIconMock, | ||
}); | ||
} catch (error) { | ||
expect(error.message).toMatch(/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.
we need to be sure that this test fails if it does not raise an error when the directory exists (and check that we have received the expected error message).
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.
@rpl this time i tried to achieve this using jest own methods, take a look maybe there is no need at all to have onlyInstanceOf
.
__tests__/unit/index.test.js
Outdated
async (tmpPath) => { | ||
const projName = "target"; | ||
const getPlaceholderIconMock = jest.fn(() => { | ||
throw new Error("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.
Why are we raising an exception from this helper?
I would have expected that this test would just try to run the comment when the project directory exists, am I reading it wrong?
index.js
Outdated
}); | ||
} | ||
|
||
module.exports = { |
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.
there should be no need to turn the export into a single module.exports
assignment, we should be able to export them one by one with exports.main = main
, export.asciiLogo = ...
etc.
index.js
Outdated
@@ -36,16 +35,17 @@ a WebExtension from the command line: | |||
${chalk.bold.blue("web-ext run -s /path/to/extension")} | |||
`; | |||
|
|||
const asciiLogo = fs.readFileSync( |
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.
uhm... I'm particularly not thrilled about adding an fs.readFileSync
call here, especially considering about this module used as a library, it basically means that the nodejs app that includes this module is going to "pause everything else" until it has loaded the asciiLogo.
is exporting the asciiLogo actually needed? we can discuss about why we need it and find another solution for it.
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.
@rpl I agree this is not ideal, i decided to do that because __dirname
is different for tests, but now I just pass correct directory.
index.js
Outdated
function main({ | ||
dirPath, | ||
baseDir = process.cwd(), | ||
getProjectManifestFn = getProjectManifest, |
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.
Do we need all this options here?
I think we should evaluate these options and decide if it makes sense for an app that includes this module as a library to redefine this helpers or if we need them only for testing reasons.
If they are only needed for testing reasons, with jest we can probably just move them in another module file and use the Jest Mocks feature to mock them (http://facebook.github.io/jest/docs/manual-mocks.html)
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.
@rpl i used auto mocking in this case, I think it acheives what you mean
fixes #3
A very small change, we could expand it further if need arises