From 3c3d80aa723d426ba4644e1ea4e3726d392c7813 Mon Sep 17 00:00:00 2001 From: Joe Bottigliero Date: Tue, 6 Aug 2019 21:56:58 -0500 Subject: [PATCH] docs: updates README, slight change to the API --- README.md | 229 +++++++++++------- lib/lifecycles/bump.js | 27 +-- lib/updaters/index.js | 29 +++ lib/{package-files => updaters/types}/json.js | 0 .../types/plain-text.js} | 0 test.js | 17 ++ test/mocks/VERSION-1.0.0.txt | 1 + test/mocks/mix.exs | 24 +- 8 files changed, 217 insertions(+), 110 deletions(-) create mode 100644 lib/updaters/index.js rename lib/{package-files => updaters/types}/json.js (100%) rename lib/{package-files/version.txt.js => updaters/types/plain-text.js} (100%) create mode 100644 test/mocks/VERSION-1.0.0.txt diff --git a/README.md b/README.md index 159095687..e0af7cd23 100644 --- a/README.md +++ b/README.md @@ -1,37 +1,48 @@ # Standard Version +A utility for versioning using [semver](https://semver.org/) and CHANGELOG generation powered by [Conventional Commits](https://conventionalcommits.org). + [![Build Status](https://travis-ci.org/conventional-changelog/standard-version.svg?branch=master)](https://travis-ci.org/conventional-changelog/standard-version) [![NPM version](https://img.shields.io/npm/v/standard-version.svg)](https://www.npmjs.com/package/standard-version) [![Coverage Status](https://coveralls.io/repos/conventional-changelog/standard-version/badge.svg?branch=)](https://coveralls.io/r/conventional-changelog/standard-version?branch=master) [![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-yellow.svg)](https://conventionalcommits.org) -[![community slack](http://devtoolscommunity.herokuapp.com/badge.svg)](http://devtoolscommunity.herokuapp.com) +[![Community slack](http://devtoolscommunity.herokuapp.com/badge.svg)](http://devtoolscommunity.herokuapp.com) + +_Having problems? Want to contribute? Join us on the [node-tooling community Slack](http://devtoolscommunity.herokuapp.com)_. + + +_How It Works:_ -_Having problems? want to contribute? join our [community slack](http://devtoolscommunity.herokuapp.com)_. +1. Follow the [Conventional Commits Specification](https://conventionalcommits.org) in your repository. +2. When you're ready to release, run `standard-version`. +`standard-version` will then do the following: -Automate versioning and CHANGELOG generation, with [semver](https://semver.org/) and -[conventional commit messages](https://conventionalcommits.org). +1. Retreive the current version of your repository by looking at `bumpFiles`[1](), falling back to the last `git tag`. +2. `bump` the version in `bumpFiles`[1]() based on your commits. +4. Generates a `changelog` based on your commints (uses [conventional-changelog](https://github.com/conventional-changelog/conventional-changelog) under the hood). +5. Creates a new `commit` including your `bumpFiles`[1]() and updated CHANGELOG. +6. Creates a new `tag` with the new version number. -_how it works:_ -1. when you land commits on your `master` branch, select the _Squash and Merge_ option. -2. add a title and body that follows the [Conventional Commits Specification](https://conventionalcommits.org). -3. when you're ready to release: - 1. `git checkout master; git pull origin master` - 2. run `standard-version` - 3. `git push --follow-tags origin master && npm publish` - _(or, `docker push`, `gem push`, etc.)_ +### `bumpFiles`, `packageFiles` and `updaters` -`standard-version` does the following: +`standard-version` uses a few key concepts for handling version bumping in your project. -1. bumps the version in metadata files (package.json, composer.json, etc). -2. uses [conventional-changelog](https://github.com/conventional-changelog/conventional-changelog) to update _CHANGELOG.md_ -3. commits _package.json (et al.)_ and _CHANGELOG.md_ -4. tags a new release +- **`packageFiles`** – User-defined files where versions can be read from _and_ "bumped". + - Examples: `package.json`, `manifest.json` + - In most cases (including the default), `packageFiles` are a subset of `bumpFiles`. +- **`bumpFiles`** – User-defined files where versions should be "bumped", but not explicitly read from. + - Examples: `package-lock.json`, `npm-shrinkwrap.json` +- **`updaters`** – Simple modules used for reading `packageFiles` and writing to `bumpFiles`. -## Installation +By default, `standard-version` assumes you're working in a NodeJS based project... because of this, for the majority of projects you might never need to interact with these options. -### As `npm run` script +That said, if you find your self asking ["How can I use `standard-version` for additional metadata files, languages or version files?"](#how-can-I-use-standard-version-for-additional metadata-files-languages-or-version-files) – these configuration options will help! + +## Installing `standard-version` + +### As a local `npm run` script Install and add to `devDependencies`: @@ -39,7 +50,7 @@ Install and add to `devDependencies`: npm i --save-dev standard-version ``` -Add an [`npm run` script](https://docs.npmjs.com/cli/run-script) to your _package.json_: +Add an [`npm run` script](https://docs.npmjs.com/cli/run-script) to your `package.json`: ```json { @@ -53,7 +64,7 @@ Now you can use `npm run release` in place of `npm version`. This has the benefit of making your repo/package more portable, so that other developers can cut releases without having to globally install `standard-version` on their machine. -### As global bin +### As global `bin` Install globally (add to your `PATH`): @@ -65,28 +76,33 @@ Now you can use `standard-version` in place of `npm version`. This has the benefit of allowing you to use `standard-version` on any repo/package without adding a dev dependency to each one. +### Using `npx` + +As of `npm@5.2.0`, `npx` is installed alongside `npm`. Using `npx` you can use `standard-version` without having to keep a `package.json` file by running: `npx standard-version`. + +This method is especially useful when using `standard-version` in non-JavaScript projects. + ## Configuration You can configure `standard-version` either by: -1. Placing a `standard-version` stanza in your `package.json` (assuming - your project is JavaScript). -1. Creating a `.versionrc` or `.versionrc.json`. +1. Placing a `standard-version` stanza in your `package.json` (assuming your project is JavaScript-based). +2. Creating a `.versionrc` or `.versionrc.json` file. -Any of the command line paramters accepted by `standard-version` can instead +Any of the command line parameters accepted by `standard-version` can instead be provided via configuration. Please refer to the [conventional-changelog-config-spec](https://github.com/conventional-changelog/conventional-changelog-config-spec/) for details on available configuration options. ### Customizing CHANGELOG Generation -By default, `standard-version` uses the [conventionalcommits preset](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-conventionalcommits). +By default (as of `6.0.0`), `standard-version` uses the [conventionalcommits preset](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-conventionalcommits). This preset: -* adheres closely to the [conventionalcommits.org](https://www.conventionalcommits.org) +* Adheres closely to the [conventionalcommits.org](https://www.conventionalcommits.org) specification. -* is highly configurable, following the configuration specification +* Is highly configurable, following the configuration specification [maintained here](https://github.com/conventional-changelog/conventional-changelog-config-spec). - * _we've documented these config settings as a recommendation to other tooling makers._ + * _We've documented these config settings as a recommendation to other tooling makers._ There are a variety of dials and knobs you can turn related to CHANGELOG generation. @@ -109,15 +125,17 @@ To generate your changelog for your first release, simply do: ```sh # npm run script npm run release -- --first-release -# or global bin +# global bin standard-version --first-release +# npx +npx standard-version --first-release ``` -This will tag a release **without bumping the version in package.json (_et al._)**. +This will tag a release **without bumping the version `bumpFiles`[1]()**. -When ready, push the git tag and `npm publish` your first release. \o/ +When you are ready, push the git tag and `npm publish` your first release. \o/ -### Cut a Release +### Cutting Releases If you typically use `npm version` to cut a new release, do this instead: @@ -132,7 +150,7 @@ As long as your git commit messages are conventional and accurate, you no longer After you cut a release, you can push the new git tag and `npm publish` (or `npm publish --tag next`) when you're ready. -### Release as a pre-release +### Release as a Pre-Release Use the flag `--prerelease` to generate pre-releases: @@ -142,7 +160,7 @@ Suppose the last version of your code is `1.0.0`, and your code to be committed # npm run script npm run release -- --prerelease ``` -you will get version `1.0.1-0`. +This will tag your version as: `1.0.1-0`. If you want to name the pre-release, you specify the name via `--prerelease `. @@ -153,14 +171,14 @@ For example, suppose your pre-release should contain the `alpha` prefix: npm run release -- --prerelease alpha ``` -this will tag the version `1.0.1-alpha.0` +This will tag the version as: `1.0.1-alpha.0` -### Release as a target type imperatively like `npm version` +### Release as a Target Type Imperatively (`npm version`-like) -To forgo the automated version bump use `--release-as` with the argument `major`, `minor` or `patch`: +To forgo the automated version bump use `--release-as` with the argument `major`, `minor` or `patch`. Suppose the last version of your code is `1.0.0`, you've only landed `fix:` commits, but -you would like your next release to be a `minor`. Simply do: +you would like your next release to be a `minor`. Simply run the following: ```bash # npm run script @@ -169,7 +187,7 @@ npm run release -- --release-as minor npm run release -- --release-as 1.1.0 ``` -you will get version `1.1.0` rather than the auto generated version `1.0.1`. +you will get version `1.1.0` rather than what would be the auto-generated version `1.0.1`. > **NOTE:** you can combine `--release-as` and `--prerelease` to generate a release. This is useful when publishing experimental feature(s). @@ -184,11 +202,11 @@ npm run release -- --no-verify standard-version --no-verify ``` -### Signing commits and tags +### Signing Commits and Tags If you have your GPG key set up, add the `--sign` or `-s` flag to your `standard-version` command. -### Lifecycle scripts +### Lifecycle Scripts `standard-version` supports lifecycle scripts. These allow you to execute your own supplementary commands during the release. The following @@ -229,7 +247,7 @@ with a link to your Jira - assuming you have already installed [replace](https:/ } ``` -### Skipping lifecycle steps +### Skipping Lifecycle Steps You can skip any of the lifecycle steps (`bump`, `changelog`, `commit`, `tag`), by adding the following to your package.json: @@ -244,7 +262,7 @@ by adding the following to your package.json: } ``` -### Committing generated artifacts in the release commit +### Committing Generated Artifacts in the Release Commit If you want to commit generated artifacts in the release commit (e.g. [#96](https://github.com/conventional-changelog/standard-version/issues/96)), you can use the `--commit-all` or `-a` flag. You will need to stage the artifacts you want to commit, so your `release` command could look like this: @@ -253,7 +271,7 @@ If you want to commit generated artifacts in the release commit (e.g. [#96](http "release": "git add && standard-version -a" ``` -### Dry run mode +### Dry Run Mode running `standard-version` with the flag `--dry-run` allows you to see what commands would be run, without committing to git or updating files. @@ -286,10 +304,7 @@ npm run release -- --help standard-version --help ``` -## Code usage - -Use the `silent` option to stop `standard-version` from printing anything -to the console. +## Code Usage ```js const standardVersion = require('standard-version') @@ -307,69 +322,111 @@ standardVersion({ }) ``` -## Commit Message Convention, at a Glance +_TIP: Use the `silent` option to prevent `standard-version` from printing to the `console`._ -_patches:_ +## FAQ -```sh -git commit -a -m "fix(parsing): fixed a bug in our parser" -``` +### How is `standard-version` different from `semantic-release`? -_features:_ +[`semantic-release`](https://github.com/semantic-release/semantic-release) is described as: -```sh -git commit -a -m "feat(parser): we now have a parser \o/" -``` +> semantic-release automates the whole package release workflow including: determining the next version number, generating the release notes and publishing the package. -_breaking changes:_ +While both are based on the same foundation of structured commit messages, `standard-version` takes a different approach by handling versioning, changelog generation, and git tagging for you **without** automatic pushing (to GitHub) or publishing (to an npm registry). Use of `standard-version` only affects your local git repo - it doesn't affect remote resources at all. After you run `standard-version`, you can review your release state, correct mistakes and follow the release strategy that makes the most sense for your codebase. -```sh -git commit -a -m "feat(new-parser): introduces a new parsing library -BREAKING CHANGE: new library does not support foo-construct" -``` +We think they are both fantastic tools, and we encourage folks to use `semantic-release` instead of `standard-version` if it makes sense for their use-case. -_other changes:_ +### Should I always squash commits when merging PRs? -You decide, e.g., docs, chore, etc. +The instructions to squash commits when merging pull requests assumes that **one PR equals, at most, one feature or fix**. -```sh -git commit -a -m "docs: fixed up the docs a bit" -``` +If you have multiple features or fixes landing in a single PR and each commit uses a structured message, then you can do a standard merge when accepting the PR. This will preserve the commit history from your branch after the merge. -_but wait, there's more!_ +Although this will allow each commit to be included as separate entries in your CHANGELOG, the entries will **not** be able to reference the PR that pulled the changes in because the preserved commit messages do not include the PR number. -Github usernames (`@bcoe`) and issue references (#133) will be swapped out for the -appropriate URLs in your CHANGELOG. +For this reason, we recommend keeping the scope of each PR to one general feature or fix. In practice, this allows you to use unstructured commit messages when committing each little change and then squash them into a single commit with a structured message (referencing the PR number) once they have been reviewed and accepted. -## Badges! +### Can I use `standard-version` for additional metadata files, languages or version files? -Tell your users that you adhere to the Conventional Commits specification: +YES! Using `bumpFiles` (and `packageFiles`) configurations you should be able to configure `standard-version` to work for you. -```markdown -[![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-yellow.svg)](https://conventionalcommits.org) +1. Specify a custom `bumpFile` "`file`", this is the path to the file you want to "bump" +2. Specify the `bumpFile` "`updater`", this is _how_ the file will be bumped. + + a. If your using a common type, you can use one of `standard-version`'s built-in `updaters` by specifying a `type`. + + b. If your using an less-common version file, you can create your own `updater`. + +```json +// .versionrc +{ + "bumpFiles": [ + { + "file": "MY_VERSION_TRACKER.txt", + // The `plain-text` updater assumes the file contents represents the version. + "type": "plain-text" + }, + { + "file": "a/deep/package/dot/json/file/package.json", + // The `json` updater assumes the version is available under a `version` key in the provided JSON document. + "type": "json" + } + { + "file": "VERSION_TRACKER.json", + // See "Custom `updater`s" for more details. + "updater": "standard-version-updater.js" + } + ] +} ``` -## FAQ +#### Custom `updater`s -### How is `standard-version` different from `semantic-release`? +An `updater` is expected to be a Javascript module with _atleast_ two methods exposed: `readVersion` and `writeVersion`. -[`semantic-release`](https://github.com/semantic-release/semantic-release) is described as: +##### `readVersion(contents = string): string` -> semantic-release automates the whole package release workflow including: determining the next version number, generating the release notes and publishing the package. +This method is used to read the version from the provided file contents. -While both are based on the same foundation of structured commit messages, `standard-version` takes a different approach by handling versioning, changelog generation, and git tagging for you **without** automatic pushing (to GitHub) or publishing (to an npm registry). Use of `standard-version` only affects your local git repo - it doesn't affect remote resources at all. After you run `standard-version`, you can review your release state, correct mistakes and follow the release strategy that makes the most sense for your codebase. +##### `writeVersion(contents = string, version: string): string` -We think they are both fantastic tools, and we encourage folks to use `semantic-release` instead of `standard-version` if it makes sense for their use-case. +This method is used to write the version to the provided contents. -### Should I always squash commits when merging PRs? +--- -The instructions to squash commits when merging pull requests assumes that **one PR equals, at most, one feature or fix**. +Let's assume our `VERSION_TRACKER.json` has the following contents: -If you have multiple features or fixes landing in a single PR and each commit uses a structured message, then you can do a standard merge when accepting the PR. This will preserve the commit history from your branch after the merge. +```json +{ + "tracker": { + "package": { + "version": "1.0.0" + } + } +} -Although this will allow each commit to be included as separate entries in your CHANGELOG, the entries will **not** be able to reference the PR that pulled the changes in because the preserved commit messages do not include the PR number. +``` -For this reason, we recommend keeping the scope of each PR to one general feature or fix. In practice, this allows you to use unstructured commit messages when committing each little change and then squash them into a single commit with a structured message (referencing the PR number) once they have been reviewed and accepted. +An acceptable `standard-version-updater.js` would be: + +```js +// standard-version-updater.js +const stringifyPackage = require('stringify-package') +const detectIndent = require('detect-indent') +const detectNewline = require('detect-newline') + +module.exports.readVersion = function (contents) { + return JSON.parse(contents).tracker.package.version; +} + +module.exports.writeVersion = function (contents, version) { + const json = JSON.parse(contents) + let indent = detectIndent(contents).indent + let newline = detectNewline(contents) + json.tracker.package.version = version + return stringifyPackage(json, indent, newline) +} +``` ## License diff --git a/lib/lifecycles/bump.js b/lib/lifecycles/bump.js index 2cc34cd04..6692c8d74 100644 --- a/lib/lifecycles/bump.js +++ b/lib/lifecycles/bump.js @@ -11,8 +11,7 @@ const presetLoader = require('../preset-loader') const runLifecycleScript = require('../run-lifecycle-script') const semver = require('semver') const writeFile = require('../write-file') -const JSON_BUMP_FILES = require('../../defaults').bumpFiles - +const { getUpdaterByFilename, getUpdaterByType, getCustomUpdater } = require('../updaters') let configsToUpdate = {} function Bump (args, version) { @@ -138,19 +137,6 @@ function bumpVersion (releaseAs, currentVersion, args) { }) } -function getUpdaterByFileName (filename) { - if (JSON_BUMP_FILES.includes(filename)) { - return require('../package-files/json') - } - try { - return require(`../package-files/${filename}.js`) - } catch (err) { - throw Error( - `Unsupported file (${filename}) provided for updating – please provide a custom updater.` - ) - } -} - /** * attempt to update the version number in provided `bumpFiles` * @param args config object @@ -172,13 +158,14 @@ function updateConfigs (args, newVersion) { if (!stat.isFile()) return - if (!bumpFile.updater) { - bumpFile.updater = getUpdaterByFileName(bumpFile.filename) + if (bumpFile.updater) { + bumpFile.updater = getCustomUpdater(bumpFile.updater) + } else if (bumpFile.type) { + bumpFile.updater = getUpdaterByType(bumpFile.type) } else { - bumpFile.updater = require( - path.resolve(process.cwd(), bumpFile.updater) - ) + bumpFile.updater = getUpdaterByFilename(bumpFile.filename) } + let contents = fs.readFileSync(configPath, 'utf8') checkpoint( args, diff --git a/lib/updaters/index.js b/lib/updaters/index.js new file mode 100644 index 000000000..a49fc23de --- /dev/null +++ b/lib/updaters/index.js @@ -0,0 +1,29 @@ +const path = require('path') +const JSON_BUMP_FILES = require('../../defaults').bumpFiles +const PLAIN_TEXT_BUMP_FILES = ['VERSION.txt', 'version.txt'] + +function getUpdaterByType (type) { + try { + return require(`./types/${type}`) + } catch (e) { + throw Error(`Unable to locate updated for provided type (${type}).`) + } +} + +module.exports.getUpdaterByType = getUpdaterByType + +module.exports.getUpdaterByFilename = function (filename) { + if (JSON_BUMP_FILES.includes(filename)) { + return getUpdaterByType('json') + } + if (PLAIN_TEXT_BUMP_FILES.includes(filename)) { + return getUpdaterByType('plain-text') + } + throw Error( + `Unsupported file (${filename}) provided for bumping.\n Please specifcy the updater \`type\` or use a custom \`updater\`.` + ) +} + +module.exports.getCustomUpdater = function (updater) { + return require(path.resolve(process.cwd(), updater)) +} diff --git a/lib/package-files/json.js b/lib/updaters/types/json.js similarity index 100% rename from lib/package-files/json.js rename to lib/updaters/types/json.js diff --git a/lib/package-files/version.txt.js b/lib/updaters/types/plain-text.js similarity index 100% rename from lib/package-files/version.txt.js rename to lib/updaters/types/plain-text.js diff --git a/test.js b/test.js index 2ccd84366..3c1359a11 100644 --- a/test.js +++ b/test.js @@ -946,6 +946,23 @@ describe('standard-version', function () { fs.readFileSync('version.txt', 'utf-8').should.equal('1.1.0') }) }) + + it('bumps a custom `plain-text` file', function () { + fs.copyFileSync('../test/mocks/VERSION-1.0.0.txt', 'VERSION_TRACKER.txt') + commit('feat: first commit') + return require('./index')({ + silent: true, + bumpFiles: [ + { + filename: 'VERSION_TRACKER.txt', + type: 'plain-text' + } + ] + }) + .then(() => { + fs.readFileSync('VERSION_TRACKER.txt', 'utf-8').should.equal('1.1.0') + }) + }) }) describe('npm-shrinkwrap.json support', function () { diff --git a/test/mocks/VERSION-1.0.0.txt b/test/mocks/VERSION-1.0.0.txt new file mode 100644 index 000000000..afaf360d3 --- /dev/null +++ b/test/mocks/VERSION-1.0.0.txt @@ -0,0 +1 @@ +1.0.0 \ No newline at end of file diff --git a/test/mocks/mix.exs b/test/mocks/mix.exs index c48db130c..bc539135e 100644 --- a/test/mocks/mix.exs +++ b/test/mocks/mix.exs @@ -1,12 +1,28 @@ defmodule StandardVersion.MixProject do use Mix.Project + def project do [ - app: :standard_version - version: "0.0.1", - elixir: "~> 1.8", + app: :standard_version, + version: "0.1.0", + elixir: "~> 1.9", start_permanent: Mix.env() == :prod, deps: deps() ] end -end \ No newline at end of file + + # Run "mix help compile.app" to learn about applications. + def application do + [ + extra_applications: [:logger] + ] + end + + # Run "mix help deps" to learn about dependencies. + defp deps do + [ + # {:dep_from_hexpm, "~> 0.3.0"}, + # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"} + ] + end +end