Skip to content
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

refactor: commands into command classes #424

Merged
merged 12 commits into from
Jan 12, 2022
Merged

Conversation

erunion
Copy link
Member

@erunion erunion commented Jan 9, 2022

🧰 Changes

⚠️ There's a lot of whitespace changes in this PR so make sure to view it with that hidden.

While thinking around with Typescript here this evening, in order for TS to understand each command I began migrating every command into a formal class that implements a core Command interface.

Because I quite liked how that work felt, having command code nestled inside of a more formal class environment, I've reworked that work here so that every command has a constructor where it sets up its arguments and --help screen docs and an async run method to run the command.

If I manage to get the Typescript work reviewable it'll make that PR a lot easier to read with this refactoring out of the way.

🧬 QA & Testing

  • Tests still pass?
  • Do you like these changes as much as I do? 🥺

@erunion erunion added the refactor Issues about tackling technical debt label Jan 9, 2022
@erunion erunion marked this pull request as draft January 9, 2022 09:22
@erunion erunion marked this pull request as ready for review January 9, 2022 09:24
@erunion erunion requested review from kanadgupta, a team, Dashron and julshotal and removed request for a team January 9, 2022 09:24
is_hidden: promptResponse.is_stable ? false : !(isPublic === 'true' || promptResponse.is_hidden),
}),
})
.then(handleRes)
Copy link
Member Author

Choose a reason for hiding this comment

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

Moved this over to use handleRes because we can here.

Copy link
Member

Choose a reason for hiding this comment

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

niiiice


exports.run = () => {
if (!configStore.has('email') || !configStore.has('project')) {
return Promise.reject(new Error(`Please login using \`${config.get('cli')} ${loginCmd.command}\`.`));
Copy link
Member Author

Choose a reason for hiding this comment

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

I stopped directly calling the login command for its name here because we know it. It's login.

exports.run = async function (opts) {
let { email, password, project, token } = opts;
/* istanbul ignore next */
async function getCredentials() {
Copy link
Member Author

Choose a reason for hiding this comment

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

Since I've moved this inside of run we don't need to pass it opts anymore.

}

if (version === undefined) {
return Promise.resolve(VersionsCommand.getVersionsAsTable(versions));
Copy link
Member Author

Choose a reason for hiding this comment

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

Since getVersionsAsTable is a static method we need to call it off the main class, this.getVersionsAsTable won't work. The method doesn't really need to be static, but since it doesn't use any internal command data it probably makes sense for it to be static?

If not, we'll need to alias this to self at the top of run to get around scope issues within the fetch thenable that this line is at.

Copy link
Member

@kanadgupta kanadgupta left a comment

Choose a reason for hiding this comment

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

I generally don't love using classes in JS if I can avoid it, but it makes sense with some of our command patterns and cleans things up nicely in certain ways. LGTM!

exports.position = openapi.position + 1;
module.exports = class SwaggerCommand extends OpenAPICommand {
constructor() {
super();
Copy link
Member

Choose a reason for hiding this comment

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

Much cleaner 🎉

is_hidden: promptResponse.is_stable ? false : !(isPublic === 'true' || promptResponse.is_hidden),
}),
})
.then(handleRes)
Copy link
Member

Choose a reason for hiding this comment

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

niiiice

@@ -2,6 +2,8 @@
"extends": ["@readme/eslint-config"],
"root": true,
"rules": {
"class-methods-use-this": ["error", { "exceptMethods": ["run"] }],
Copy link
Member

Choose a reason for hiding this comment

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

Wanna add a small comment explaining why we need this?


commands.push({
file,
command,
command: new Command(),
Copy link

Choose a reason for hiding this comment

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

Thoughts on new here and load vs. module.exports = new (commandClass)(); in each file?

I've used both techniques in the past. new here and load is nice to ensure that we don't have side effects leaking between calls, and might be what I prefer, but I figured I'd bring it up if you thought it would be useful.

Copy link
Member Author

Choose a reason for hiding this comment

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

I think I prefer to do the instantiation on load instead of in the export.

@Dashron
Copy link

Dashron commented Jan 11, 2022

I def like the classes, clean and allows for interesting, clean improvements like inheritance.

Base automatically changed from fix/quirks to main January 11, 2022 21:11
@@ -52,7 +54,9 @@ describe('rdme versions*', () => {
.basicAuth({ user: key })
.reply(200, [versionPayload, version2Payload]);

await expect(versions.run({ key })).resolves.toMatchSnapshot();
const table = await versions.run({ key });
Copy link
Member Author

@erunion erunion Jan 11, 2022

Choose a reason for hiding this comment

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

Reverted this back to using these two assertions because the FORCE_COLOR env variable doesn't seem to always get used within the colors dependency of cli-table.

@erunion erunion requested a review from kanadgupta January 11, 2022 22:43
coverage
node_modules/
coverage/
swagger.json
Copy link
Member

Choose a reason for hiding this comment

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

We technically shouldn’t need this since a successful test run will result in this file being deleted properly… but I suppose doesn’t hurt!

@kanadgupta
Copy link
Member

Also wait we probably don't need both of these right 👀

env:
# `chalk` has troubles with color detection while on CI.
# https://github.com/chalk/supports-color/issues/106
FORCE_COLOR: 1

// The `chalk` and `colors` libraries have trouble with Jest sometimes in test snapshots so we're disabling
// colorization here for all tests.
process.env.FORCE_COLOR = 0;

@erunion
Copy link
Member Author

erunion commented Jan 12, 2022

Ah good eye I'll remove the one in the workflow config.

@erunion erunion merged commit 7b8b378 into main Jan 12, 2022
@erunion erunion deleted the refactor/command-classes branch January 12, 2022 17:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
refactor Issues about tackling technical debt
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants