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

feat: custom 'bumpFiles' and 'packageFiles' support #372

Merged
merged 8 commits into from Dec 6, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
226 changes: 144 additions & 82 deletions README.md

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions command.js
Expand Up @@ -5,6 +5,14 @@ const { START_OF_LAST_RELEASE_PATTERN } = require('./lib/lifecycles/changelog')

const yargs = require('yargs')
.usage('Usage: $0 [options]')
.option('packageFiles', {
default: defaults.packageFiles,
array: true
})
.option('bumpFiles', {
default: defaults.bumpFiles,
array: true
})
.option('release-as', {
alias: 'r',
describe: 'Specify the release type manually (like npm version <major|minor|patch>)',
Expand Down
13 changes: 13 additions & 0 deletions defaults.js
Expand Up @@ -23,4 +23,17 @@ Object.keys(spec.properties).forEach(propertyKey => {
defaults[propertyKey] = property.default
})

defaults.packageFiles = [
'package.json',
'bower.json',
'manifest.json',
'composer.json'
]

defaults.bumpFiles = defaults.packageFiles.concat([
'package-lock.json',
'npm-shrinkwrap.json',
'composer.lock'
])

module.exports = defaults
18 changes: 11 additions & 7 deletions index.js
Expand Up @@ -6,8 +6,10 @@ const latestSemverTag = require('./lib/latest-semver-tag')
const path = require('path')
const printError = require('./lib/print-error')
const tag = require('./lib/lifecycles/tag')
const { resolveUpdaterObjectFromArgument } = require('./lib/updaters')

module.exports = function standardVersion (argv) {
const defaults = require('./defaults')
/**
* `--message` (`-m`) support will be removed in the next major version.
*/
Expand All @@ -24,19 +26,21 @@ module.exports = function standardVersion (argv) {
}
}

const args = Object.assign({}, defaults, argv)
let pkg
bump.pkgFiles.forEach((filename) => {
args.packageFiles.forEach((packageFile) => {
if (pkg) return
const pkgPath = path.resolve(process.cwd(), filename)
const updater = resolveUpdaterObjectFromArgument(packageFile)
const pkgPath = path.resolve(process.cwd(), updater.filename)
try {
const data = fs.readFileSync(pkgPath, 'utf8')
pkg = JSON.parse(data)
const contents = fs.readFileSync(pkgPath, 'utf8')
pkg = {
version: updater.updater.readVersion(contents),
private: typeof updater.updater.isPrivate === 'function' ? updater.updater.isPrivate(contents) : false
}
} catch (err) {}
})
let newVersion
const defaults = require('./defaults')
const args = Object.assign({}, defaults, argv)

return Promise.resolve()
.then(() => {
if (!pkg && args.gitTagFallback) {
Expand Down
62 changes: 26 additions & 36 deletions lib/lifecycles/bump.js
Expand Up @@ -3,18 +3,15 @@
const chalk = require('chalk')
const checkpoint = require('../checkpoint')
const conventionalRecommendedBump = require('conventional-recommended-bump')
const detectIndent = require('detect-indent')
const detectNewline = require('detect-newline')
const figures = require('figures')
const fs = require('fs')
const DotGitignore = require('dotgitignore')
const path = require('path')
const presetLoader = require('../preset-loader')
const runLifecycleScript = require('../run-lifecycle-script')
const semver = require('semver')
const stringifyPackage = require('stringify-package')
const writeFile = require('../write-file')

const { resolveUpdaterObjectFromArgument } = require('../updaters')
let configsToUpdate = {}

function Bump (args, version) {
Expand Down Expand Up @@ -51,19 +48,6 @@ Bump.getUpdatedConfigs = function () {
return configsToUpdate
}

Bump.pkgFiles = [
'package.json',
'bower.json',
'manifest.json',
'composer.json'
]

Bump.lockFiles = [
'package-lock.json',
'npm-shrinkwrap.json',
'composer.lock'
]

function getReleaseType (prerelease, expectedReleaseType, currentVersion) {
if (isString(prerelease)) {
if (isInPrerelease(currentVersion)) {
Expand Down Expand Up @@ -154,32 +138,38 @@ function bumpVersion (releaseAs, currentVersion, args) {
}

/**
* attempt to update the version # in a collection of common config
* files, e.g., package.json, bower.json.
*
* attempt to update the version number in provided `bumpFiles`
* @param args config object
* @param newVersion version # to update to.
* @return {string}
* @param newVersion version number to update to.
* @return void
*/
function updateConfigs (args, newVersion) {
const dotgit = DotGitignore()
Bump.pkgFiles.concat(Bump.lockFiles).forEach(function (filename) {
const configPath = path.resolve(process.cwd(), filename)
args.bumpFiles.forEach(function (bumpFile) {
const updater = resolveUpdaterObjectFromArgument(bumpFile)
if (!updater) {
return
}
const configPath = path.resolve(process.cwd(), updater.filename)
try {
if (dotgit.ignore(configPath)) return
const stat = fs.lstatSync(configPath)
if (stat.isFile()) {
const data = fs.readFileSync(configPath, 'utf8')
const indent = detectIndent(data).indent
const newline = detectNewline(data)
const config = JSON.parse(data)
checkpoint(args, 'bumping version in ' + filename + ' from %s to %s', [config.version, newVersion])
config.version = newVersion
writeFile(args, configPath, stringifyPackage(config, indent, newline))
// flag any config files that we modify the version # for
// as having been updated.
configsToUpdate[filename] = true
}

if (!stat.isFile()) return
const contents = fs.readFileSync(configPath, 'utf8')
checkpoint(
args,
'bumping version in ' + updater.filename + ' from %s to %s',
[updater.updater.readVersion(contents), newVersion]
)
writeFile(
args,
configPath,
updater.updater.writeVersion(contents, newVersion)
)
// flag any config files that we modify the version # for
// as having been updated.
configsToUpdate[updater.filename] = true
} catch (err) {
if (err.code !== 'ENOENT') console.warn(err.message)
}
Expand Down
59 changes: 59 additions & 0 deletions lib/updaters/index.js
@@ -0,0 +1,59 @@
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}).`)
}
}

function getUpdaterByFilename (filename) {
if (JSON_BUMP_FILES.includes(path.basename(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\`.`
)
}

function getCustomUpdater (updater) {
return require(path.resolve(process.cwd(), updater))
}

module.exports.resolveUpdaterObjectFromArgument = function (arg) {
/**
* If an Object was not provided, we assume it's the path/filename
* of the updater.
*/
let updater = arg
if (typeof updater !== 'object') {
updater = {
filename: arg
}
}
try {
if (updater.updater) {
updater.updater = getCustomUpdater(updater.updater)
} else if (updater.type) {
updater.updater = getUpdaterByType(updater.type)
} else {
updater.updater = getUpdaterByFilename(updater.filename)
}
} catch (err) {
if (err.code !== 'ENOENT') console.warn(err.message)
}
/**
* We weren't able to resolve an updater for the argument.
*/
if (!updater.updater) {
return false
}

return updater
}
19 changes: 19 additions & 0 deletions lib/updaters/types/json.js
@@ -0,0 +1,19 @@
const stringifyPackage = require('stringify-package')
const detectIndent = require('detect-indent')
const detectNewline = require('detect-newline')

module.exports.readVersion = function (contents) {
return JSON.parse(contents).version
}

module.exports.writeVersion = function (contents, version) {
const json = JSON.parse(contents)
const indent = detectIndent(contents).indent
const newline = detectNewline(contents)
json.version = version
return stringifyPackage(json, indent, newline)
}

module.exports.isPrivate = function (contents) {
return JSON.parse(contents).private
}
7 changes: 7 additions & 0 deletions lib/updaters/types/plain-text.js
@@ -0,0 +1,7 @@
module.exports.readVersion = function (contents) {
return contents
}

module.exports.writeVersion = function (_contents, version) {
return version
}
68 changes: 68 additions & 0 deletions test.js
Expand Up @@ -922,6 +922,74 @@ describe('standard-version', function () {
})
})

describe('custom `bumpFiles` support', function () {
it('mix.exs + version.txt', function () {
// @todo This file path is relative to the `tmp` directory, which is a little confusing
fs.copyFileSync('../test/mocks/mix.exs', 'mix.exs')
fs.copyFileSync('../test/mocks/version.txt', 'version.txt')
fs.copyFileSync('../test/mocks/updater/customer-updater.js', 'custom-updater.js')
commit('feat: first commit')
shell.exec('git tag -a v1.0.0 -m "my awesome first release"')
commit('feat: new feature!')
return require('./index')({
silent: true,
bumpFiles: [
'version.txt',
{
filename: 'mix.exs',
updater: 'custom-updater.js'
}
]
})
.then(() => {
fs.readFileSync('mix.exs', 'utf-8').should.contain('version: "1.1.0"')
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('custom `packageFiles` support', function () {
it('reads and writes to a custom `plain-text` file', function () {
fs.copyFileSync('../test/mocks/VERSION-6.3.1.txt', 'VERSION_TRACKER.txt')
commit('feat: yet another commit')
return require('./index')({
silent: true,
packageFiles: [
{
filename: 'VERSION_TRACKER.txt',
type: 'plain-text'
}
],
bumpFiles: [
{
filename: 'VERSION_TRACKER.txt',
type: 'plain-text'
}
]
})
.then(() => {
fs.readFileSync('VERSION_TRACKER.txt', 'utf-8').should.equal('6.4.0')
})
})
})

describe('npm-shrinkwrap.json support', function () {
beforeEach(function () {
writeNpmShrinkwrapJson('1.0.0')
Expand Down
1 change: 1 addition & 0 deletions test/mocks/VERSION-1.0.0.txt
@@ -0,0 +1 @@
1.0.0
1 change: 1 addition & 0 deletions test/mocks/VERSION-6.3.1.txt
@@ -0,0 +1 @@
6.3.1
28 changes: 28 additions & 0 deletions test/mocks/mix.exs
@@ -0,0 +1,28 @@
defmodule StandardVersion.MixProject do
use Mix.Project

def project do
[
app: :standard_version,
version: "0.1.0",
elixir: "~> 1.9",
start_permanent: Mix.env() == :prod,
deps: deps()
]
end

# 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
12 changes: 12 additions & 0 deletions test/mocks/updater/customer-updater.js
@@ -0,0 +1,12 @@
const REPLACER = /version: "(.*)"/

module.exports.readVersion = function (contents) {
return REPLACER.exec(contents)[1]
}

module.exports.writeVersion = function (contents, version) {
return contents.replace(
REPLACER.exec(contents)[0],
`version: "${version}"`
)
}
Empty file added test/mocks/version.txt
Empty file.