diff --git a/index.js b/index.js index 33e026d..20ec89e 100644 --- a/index.js +++ b/index.js @@ -5,6 +5,7 @@ import { castArray, get, set } from 'lodash-es'; import detectIndent from 'detect-indent'; import yaml from 'js-yaml'; import toml from '@iarna/toml'; +import { patch } from '@decimalturn/toml-patch'; import ini from 'ini'; import semver from 'semver'; import { Plugin } from 'release-it'; @@ -182,15 +183,8 @@ class Bumper extends Plugin { case 'yaml': return writeFileSync(file, yaml.dump(parsed, { indent: indent.length })); case 'toml': - var tomlContent = data; - - castArray(path).forEach(path => { - const latestPath = path.split('.').at(-1); - const versionMatch = new RegExp(`${latestPath}[\\W\\w]+?(${latestVersion.replaceAll('.', '\\.')})` || ''); - tomlContent = tomlContent.replace(versionMatch, (match, group1) => { - return match.replace(group1, versionPrefix + version); - }); - }); + let tomlContent = data + tomlContent = patch(tomlContent, parsed) return writeFileSync(file, tomlContent.replace(/(\r?\n)/g, newline)); case 'ini': diff --git a/package.json b/package.json index 4d08fb0..5dbdfa1 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ }, "author": "Lars Kappert ", "dependencies": { + "@decimalturn/toml-patch": "^1.3.0", "@iarna/toml": "^3.0.0", "cheerio": "^1.0.0", "detect-indent": "7.0.1", diff --git a/test/toml.test.js b/test/toml.test.js index 9f06365..214f63f 100644 --- a/test/toml.test.js +++ b/test/toml.test.js @@ -10,10 +10,27 @@ import { readFile } from './globals/file-utils.js'; mock({ './foo.toml': `[tool.test]${EOL}version = "${CURRENT_VERSION}"${EOL}`, + // includes another section with a "version" key with semver value + './without_target_version.toml': `[tool.test]${EOL}[tool.ignored]${EOL}version = "${CURRENT_VERSION}"${EOL}`, + './with_multiple_version.toml': `[tool.test]${EOL}[project]${EOL}`, + './with_comments_and_formatting.toml': `# Lead with some comments${EOL}${EOL}[workspace]${EOL}${EOL}${EOL}[tool.test]${EOL}name = "hello_world"${EOL}version = "${CURRENT_VERSION}"${EOL}`, + // project version is already at NEW_VERSION and has a dependency with a version containing the CURRENT_VERSION + './at_current_version.toml': `[tool.test]${EOL}version = "${NEW_VERSION}"${EOL}dependencies = [${EOL} "django = 1${CURRENT_VERSION}"${EOL}]${EOL}`, './cargo.toml': `[workspace]${EOL}${EOL}[package]${EOL}name = "hello_world"${EOL}version = "${CURRENT_VERSION}"${EOL}authors = [ "Alice ", "Bob " ]${EOL}${EOL}[dependencies]${EOL}time = "0.1.12"${EOL}`, - './pyproject.toml': `[project]${EOL}name = "foo"${EOL}version = "${CURRENT_VERSION}"${EOL}# these are authors${EOL}authors = [{ name = "Alice", email = "a@example.com" }]${EOL}` + './pyproject.toml': `[project]${EOL}name = "foo"${EOL}version = "${CURRENT_VERSION}"${EOL}authors = [{ name = "Alice", email = "a@example.com" }]${EOL}dependencies = [${EOL} "django = 1${CURRENT_VERSION}"${EOL}]${EOL}[tool.commitizen]${EOL}version = "${CURRENT_VERSION}"${EOL}`, }); +const readFilePostBumperTasks = async (namespaceOptions) => { + const options = { + [NAMESPACE]: namespaceOptions + } + + const plugin = await factory(Bumper, { NAMESPACE, options }); + await runTasks(plugin); + + return readFile(namespaceOptions.out.file) +} + describe('toml file', { concurrency: true }, () => { it('should return latest version', async () => { const options = { @@ -24,80 +41,110 @@ describe('toml file', { concurrency: true }, () => { assert.equal(version, CURRENT_VERSION); }); - it('should write', async () => { - const options = { - [NAMESPACE]: { - out: { - file: './foo.toml', - type: 'text/toml', - path: 'tool.test.version' - } + it('should add version at path when missing', async () => { + const namespaceOptions = { + out: { + file: './without_target_version.toml', + type: 'text/toml', + path: 'tool.test.version' } }; - const plugin = await factory(Bumper, { NAMESPACE, options }); - await runTasks(plugin); - assert.equal(readFile('./foo.toml'), `[tool.test]${EOL}version = "${NEW_VERSION}"${EOL}`); + const contents = await readFilePostBumperTasks(namespaceOptions) + assert.equal(contents, `[tool.test]${EOL}version = "${NEW_VERSION}"${EOL}[tool.ignored]${EOL}version = "${CURRENT_VERSION}"${EOL}`); + }); + + it('should update version at path', async () => { + const namespaceOptions = { + out: { + file: './foo.toml', + type: 'text/toml', + path: 'tool.test.version' + } + }; + const contents = await readFilePostBumperTasks(namespaceOptions); + assert.equal(contents, `[tool.test]${EOL}version = "${NEW_VERSION}"${EOL}`); + }); + + it('should update versions at multiple paths', async () => { + const namespaceOptions = { + out: { + file: './with_multiple_version.toml', + type: 'text/toml', + path: ['project.version', 'tool.test.version'] + } + }; + const contents = await readFilePostBumperTasks(namespaceOptions); + assert.equal(contents, `[tool.test]${EOL}version = "${NEW_VERSION}"${EOL}[project]${EOL}version = "${NEW_VERSION}"${EOL}`); }); it('should write without defining the type', async () => { - const options = { - [NAMESPACE]: { out: { file: './foo.toml', path: 'tool.test.version' } } + const namespaceOptions = { + out: { file: './foo.toml', path: 'tool.test.version' } }; - const plugin = await factory(Bumper, { NAMESPACE, options }); - await runTasks(plugin); - assert.equal(readFile('./foo.toml'), `[tool.test]${EOL}version = "${NEW_VERSION}"${EOL}`); + const contents = await readFilePostBumperTasks(namespaceOptions); + assert.equal(contents, `[tool.test]${EOL}version = "${NEW_VERSION}"${EOL}`); }); it('should read/write', async () => { - const options = { - [NAMESPACE]: { - in: { file: './foo.toml', type: 'application/toml', path: 'tool.test.version' }, - out: { file: './foo.toml', type: 'application/toml', path: 'tool.test.version' } - } + const namespaceOptions = { + in: { file: './foo.toml', type: 'application/toml', path: 'tool.test.version' }, + out: { file: './foo.toml', type: 'application/toml', path: 'tool.test.version' } }; - const plugin = await factory(Bumper, { NAMESPACE, options }); - await runTasks(plugin); - assert.equal(readFile('./foo.toml'), `[tool.test]${EOL}version = "${NEW_VERSION}"${EOL}`); + const contents = await readFilePostBumperTasks(namespaceOptions); + assert.equal(contents, `[tool.test]${EOL}version = "${NEW_VERSION}"${EOL}`); }); it('should read/write without defining the type', async () => { - const options = { - [NAMESPACE]: { - in: { file: './foo.toml', path: 'tool.test.version' }, - out: { file: './foo.toml', path: 'tool.test.version' } - } + const namespaceOptions = { + in: { file: './foo.toml', path: 'tool.test.version' }, + out: { file: './foo.toml', path: 'tool.test.version' } }; - const plugin = await factory(Bumper, { NAMESPACE, options }); - await runTasks(plugin); - assert.equal(readFile('./foo.toml'), `[tool.test]${EOL}version = "${NEW_VERSION}"${EOL}`); + const contents = await readFilePostBumperTasks(namespaceOptions); + assert.equal(contents, `[tool.test]${EOL}version = "${NEW_VERSION}"${EOL}`); }); it('should read/write without formatting', async () => { - const options = { - [NAMESPACE]: { - in: { file: './cargo.toml', path: 'package.version' }, - out: { file: './cargo.toml', path: 'package.version' } - } + const namespaceOptions = { + in: { file: './with_comments_and_formatting.toml', path: 'tool.test.version' }, + out: { file: './with_comments_and_formatting.toml', path: 'tool.test.version' } }; - const plugin = await factory(Bumper, { NAMESPACE, options }); - await runTasks(plugin); + const contents = await readFilePostBumperTasks(namespaceOptions); + assert.equal( + contents, + `# Lead with some comments${EOL}${EOL}[workspace]${EOL}${EOL}${EOL}[tool.test]${EOL}name = "hello_world"${EOL}version = "${NEW_VERSION}"${EOL}` + ); + }); + + it('should noop when the target version is already at the new version', async () => { + const namespaceOptions = { + out: { file: './at_current_version.toml', path: 'tool.test.version' } + }; + const contents = await readFilePostBumperTasks(namespaceOptions); assert.equal( - readFile('./cargo.toml'), + contents, + `[tool.test]${EOL}version = "${NEW_VERSION}"${EOL}dependencies = [${EOL} "django = 1${CURRENT_VERSION}"${EOL}]${EOL}` + ); + }); + + it('should handle example cargo.toml file', async () => { + const namespaceOptions = { + out: { file: './cargo.toml', path: 'package.version' } + }; + const contents = await readFilePostBumperTasks(namespaceOptions); + assert.equal( + contents, `[workspace]${EOL}${EOL}[package]${EOL}name = "hello_world"${EOL}version = "${NEW_VERSION}"${EOL}authors = [ "Alice ", "Bob " ]${EOL}${EOL}[dependencies]${EOL}time = "0.1.12"${EOL}` ); }); - it('should read/write minimal changes', async () => { - const options = { - [NAMESPACE]: { - out: { file: './pyproject.toml', path: 'project.version' } - } + it('should handle example pyproject.toml file', async () => { + const namespaceOptions = { + out: { file: './pyproject.toml', path: 'project.version' } }; - const plugin = await factory(Bumper, { NAMESPACE, options }); - await runTasks(plugin); + const contents = await readFilePostBumperTasks(namespaceOptions); assert.equal( - readFile('./pyproject.toml'), - `[project]${EOL}name = "foo"${EOL}version = "${NEW_VERSION}"${EOL}# these are authors${EOL}authors = [{ name = "Alice", email = "a@example.com" }]${EOL}` + contents, + `[project]${EOL}name = "foo"${EOL}version = "${NEW_VERSION}"${EOL}authors = [{ name = "Alice", email = "a@example.com" }]${EOL}dependencies = [${EOL} "django = 1${CURRENT_VERSION}"${EOL}]${EOL}[tool.commitizen]${EOL}version = "${CURRENT_VERSION}"${EOL}` ); }); -}); +}); \ No newline at end of file