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

release-it v11: the one with the plugins #501

Closed
webpro opened this issue Apr 7, 2019 · 7 comments
Closed

release-it v11: the one with the plugins #501

webpro opened this issue Apr 7, 2019 · 7 comments

Comments

@webpro
Copy link
Collaborator

webpro commented Apr 7, 2019

Plugins

Recently, I've been evolving release-it into more of a pluggable task runner (in the v11 branch). It was previously limited to Git-based repositories, GitHub/GitLab releases, and npm packages, but this is no longer the case. As long as it can either be written in Node.js, or executed from the shell, it can be integrated in the release-it process.

Plugins allow additional and custom actions in the release process, such as:

  • Publish the package to any registry (this is language-agnostic, e.g. Ruby, Python, ...).
  • Use a different VCS (e.g. Mercurial).
  • Implement a different strategy to generate changelogs and/or release notes.
  • Trigger web hooks (e.g. post a message to a Slack channel)
  • Use Node.js directly (instead of executing shell scripts configured in scripts.*).
  • Not yet possible: replace existing plugins. For instance, integrate with the npm registry using their programmatic API (as opposed to calling npm publish in a child process like release-it itself does).

Please see docs/plugins/README.md for the documentation on plugins.

npm

Internally, release-it uses its own plugin architecture. Especially for npm there are a few improvements: it is only enabled if there's a package.json file. Additionally, release-it defers bumping to npm version, so package-lock.json is also updated if it's there.

conventional-changelog

Another advantage of plugins is that we can get rid of ugly configurations like this:

"increment": "conventional:angular",
"scripts": {
  "changelog": "npx conventional-changelog -p angular -u | tail -n +3",
  "beforeStage": "npx conventional-changelog -p angular -i CHANGELOG.md -s"
}

The new conventional-changelog plugin allows to defer the work to a separate plugin and pass options:

{
  "plugins": {
    "@release-it/conventional-changelog": {
      "preset": "angular",
      "infile": "CHANGELOG.md"
    }
  }
}

Breaking changes

  • The special increment values such as conventional:angular are no longer valid. Use the @release-it/conventional-changelog plugin instead (see above).
  • The pkgFiles option has been removed. If there's a need to bump other files than what npm version bumps, it should be (part of) a plugin.
  • The preReleaseId and use options have been removed.
  • The scripts.changelog option has been moved to git.changelog

Try it

Currently, v11 is in alpha state. I'm using it for releases, but depending on your configuration there may still be bugs.

npm install release-it@alpha

Feedback

So here's looking for feedback about this idea. Please let me know:

  • Are plugins useful at all with release-it?
  • Does it make sense to inherit from the Plugin class, or are there better ways/patterns?
  • Anything else...?
@brunogirin
Copy link

Thanks for a great piece of software, this is exactly what I needed and the plugin concept expands its capabilities dramatically.

It took me very little time to write my own plugin to get a version number from app.json rather than package.json and update it back there (I'm working on a react-native mobile app). I would have probably needed some much more convoluted code to get it working without the plugin concept.

The current design makes writing plugins very easy so I like it. Inheriting from the Plugin class is simple.

In terms of improvements, here are a few ideas:

  • Add a plugin extension point to get the name of the app you're releasing. Once I had disabled the npm plugin, the first message said 🚀 Let's release undefined (currently at 1.0.0).
  • Make it possible to configure the npm plugin further than what it currently allows. For example, it would be good to allow it to manage the package.json file but not interact with the npm repository at all: this would be useful for private packages that are not meant to be published. Alternatively, this could be done by splitting the npm plugin in two: one for package.json, the other one for the npm repo.
  • Make it possible to control the order in which the plugins are run: a plugin that just changes the version number in a file should run before the github/gitlab plugin for example.

@webpro
Copy link
Collaborator Author

webpro commented Apr 9, 2019

Thanks a lot, @brunogirin, this is exactly the kind of feedback I was looking for.

  • You disabled the npm plugin by removing package.json, right? You can implement plugin.getName() to get the name (sorry, should be documented properly). Or are you saying that release-it should provide it (e.g. based on directory name).
  • Private packages should have private: true in package.json and release-it should not attempt to publish it. Are you missing any other config option? Interesting idea about splitting it up, I'll give it some more thoughts.
  • Execution order is definitely a challenge. Plugins are not executed one after another. So, e.g. init is executed for each plugin, and then beforeBump for each plugin, and so on. Within that, user plugins first, then core plugins. Except for the [before|after]release methods, here the core plugins go first. Somehow it makes sense when I work with it, but I understand this sounds confusing at best. The least I can do is to document this better. Any ideas welcome!

Thanks again for sharing your experiences.

@brunogirin
Copy link

Hi @webpro, here are answers to your questions:

  • I disabled the npm plugin by setting npm: false in the release-it block in package.json. For a react native app, all the version information is in app.js but it still needs package.json for the build so I can't remove it.
  • plugin.getName() is exactly what I wanted, I implemented it and it works fine, thanks!
  • I have private: true set in package.json but even with that, the npm package tries to check the npm repository and fails because I'm not authenticated to it. In such a case, it should probably bypass all interaction with the repository because that project will never be published.
  • Execution order is not an issue if the life-cycle is clear so scrap that comment. The only query I have on this is: what happens is multiple plugins return a value for getLatestVersion? Is the latest one kept?

@webpro
Copy link
Collaborator Author

webpro commented Apr 12, 2019

  • Cool, I was also asking since this was only available since 11.0.0-alpha.5 :)
  • Check
  • Yes, that makes sense. I'll dive into this when I have the time.
  • The first one that returns something will be used. That's why user plugin methods are invoked first, so they can override internal ones.

@rwjblue
Copy link
Contributor

rwjblue commented May 8, 2019

👋 Hello! I've just started trying to migrate from np to release-it because of the new plugin system. I believe that plugins will allow me to standardize the release process of the many packages that I am involved with and ease some of the gaps/pain points that I have now with np, thank you for working on it!

As I went through the process I tried to catalogue the pain points that I hit so that we might be able to improve the process:

  • Having plugins depend on release-it seems vaguely odd to me (my plugin is only used by folks with release-it, why do I also depend on release-it?), and I worry that we will end up in some bit of versioning hell where the Plugin base class changes inside release-it and now the host project any my plugin do not agree on the "contract". Not sure of the best solution here, but there are a number of plausible solutions.
  • Discovering existing plugins is difficult. Perhaps we could suggest a release-it-plugin keyword on npm for discoverability?
  • The documentation (and existing shared plugins) do not discuss how to handle --dry-run at all. For example, @release-it/conventional-changelog always updates infile even when this.global.isDryRun would be true. I think this is probably just a "teaching moment" and the docs here can be beefed up to discuss how to handle things that have side effects vs those that are gathering data.
  • Testing a user land plugin is not as easy as I think it could be. The tests in @release-it/conventional-changelog are a good start, but I worry about the stubbing (e.g. proxyrequire) leading to a scenario that passes tests but doesn't actual function. Not sure exactly what could be done here.
  • Exact ordering between plugins is unclear. It seems logical that the order is whatever the user specifies them in in the configuration, but how does that relate to the built-in plugins (e.g. npm/git/github)?
  • How can plugins communicate with each other? For example, when creating a changelog related plugin, it would have been useful to know what the latest tag and current tag are (as opposed to manually templating the git.tagName for those that use v${version} there).

@webpro
Copy link
Collaborator Author

webpro commented May 9, 2019

Thanks for the valuable feedback, @rwjblue! Glad you're giving a release-it a spin.

Having plugins depend on release-it

I totally see your point. The history is that release-it had these modules such as "git" and "npm", and these have been refactored into classes. Later on I took the opportunity to refactor them further into "plugins" (inherit from Plugin with release-cycle methods like they do now). This simplified the main task runner and by using dependency injection the testing got much simpler. This enabled external plugins, but the dependency injection does mean they need release-it classes (although even if this was meant for testing purposes, you can inject your own classes). For the first iteration I'm OK with this, it means logging, steps/prompts/spinners are used like release-it itself does.

versioning hell

As long as the Plugin class comes packaged with release-it, it's part of its API so breaking changes mean major bumps.

Discovering existing plugins is difficult.

I had exactly the same idea to add such keywords to package.json. Not sure why I did not push release-it/plugin-starterkit until just now :)

documentation

This whole plugin thing is new and documentation should definitely be improved. Indeed it should explain the difference between read/write operations and how things can be used/implemented.

Testing a user land plugin is not as easy as I think it could be.

This sounds like a generic challenge with testing, proxyquire is just one way to stub a dependency.

Exact ordering between plugins is unclear.

It is. Like I stated above: plugins are not executed one after another. So, e.g. init is executed for each plugin, and then beforeBump for each plugin, and so on. Within that, user plugins first, then core plugins. Except for the [before|after]release methods, here the core plugins go first. Somehow it makes sense when I work with it, but I understand this sounds confusing at best. The least I can do is to document this better. Any ideas welcome!

How can plugins communicate with each other?

Plugins can't communicate with each other, and I think that's a good thing. For instance, a plugin can't know where the latestVersion comes from. By default, it's coming from the npm plugin (if there's a package.json), otherwise it falls back to the version the git plugin provides from parsing the latest tag. But a project may not use any of this, and use Mercurial instead. So I think there should be some form of global state available which is available through this.config.getContext. Also:

  • the getInitialOptions(options) method can be implemented in the plugin. This receives the full configuration object (for all plugins), so e.g. git.tagName is there.
  • The bumped version is sent to the plugin when the task runner calls plugin.bump(version).
  • As you found out already, latestVersion can be retrieved from the injected config using e.g. this.config.getContext('latestVersion)`.

Still there's plenty of room for improvements here (the hassle with formatting the tagName is clear).

Hopefully this clears things up a little bit. Happy to discuss further, and I'm planning to improve docs in the next week.

@webpro
Copy link
Collaborator Author

webpro commented May 17, 2019

Plugin docs have been updated: https://github.com/release-it/release-it/blob/master/docs/plugins/README.md

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants