Skip to content

Commit 8675db8

Browse files
committed
feat: Add generic TOML updater
1 parent 9324af7 commit 8675db8

8 files changed

Lines changed: 254 additions & 3 deletions

File tree

__snapshots__/generic-toml.js

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
exports['GenericToml updateContent updates deep entry in toml 1'] = `
2+
[package]
3+
name = "rust-test-repo"
4+
version = "12.0.0"
5+
6+
# To learn about other keys, check out Cargo's documentation
7+
8+
[dependencies]
9+
normal-dep = "1.2.3"
10+
11+
[dev-dependencies]
12+
dev-dep = { version = "2.3.4" }
13+
dev-dep-2 = { path = "../dev-dep-2" }
14+
15+
[build-dependencies]
16+
# this is using a private registry
17+
build-dep = { version = "1.2.3", registry = "private", path = ".." } # trailing comment
18+
19+
[target.'cfg(windows)'.dev-dependencies]
20+
windows-dep = { version = "1.2.3", registry = "private", path = ".." }
21+
22+
[target.'cfg(unix)'.dependencies]
23+
unix-dep = { version = "1.2.3", registry = "private", path = ".." }
24+
25+
[target.'cfg(target_arch = "x86")'.dependencies]
26+
x86-dep = { version = "1.2.3", registry = "private", path = ".." }
27+
28+
[target.'cfg(target_arch = "x86_64")'.dependencies]
29+
x86-64-dep = { version = "1.2.3", registry = "private", path = ".." }
30+
31+
[target.'cfg(foobar)'.dependencies]
32+
foobar-dep = "1.2.3"
33+
34+
`
35+
36+
exports['GenericToml updateContent updates matching entry 1'] = `
37+
[package]
38+
name = "rust-test-repo"
39+
version = "2.3.4"
40+
41+
# To learn about other keys, check out Cargo's documentation
42+
43+
[dependencies]
44+
normal-dep = "1.2.3"
45+
46+
[dev-dependencies]
47+
dev-dep = { version = "1.2.3" }
48+
dev-dep-2 = { path = "../dev-dep-2" }
49+
50+
[build-dependencies]
51+
# this is using a private registry
52+
build-dep = { version = "1.2.3", registry = "private", path = ".." } # trailing comment
53+
54+
[target.'cfg(windows)'.dev-dependencies]
55+
windows-dep = { version = "1.2.3", registry = "private", path = ".." }
56+
57+
[target.'cfg(unix)'.dependencies]
58+
unix-dep = { version = "1.2.3", registry = "private", path = ".." }
59+
60+
[target.'cfg(target_arch = "x86")'.dependencies]
61+
x86-dep = { version = "1.2.3", registry = "private", path = ".." }
62+
63+
[target.'cfg(target_arch = "x86_64")'.dependencies]
64+
x86-64-dep = { version = "1.2.3", registry = "private", path = ".." }
65+
66+
[target.'cfg(foobar)'.dependencies]
67+
foobar-dep = "1.2.3"
68+
69+
`

src/manifest.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,12 +70,19 @@ type ExtraPomFile = {
7070
path: string;
7171
glob?: boolean;
7272
};
73+
type ExtraTomlFile = {
74+
type: 'toml';
75+
path: string;
76+
jsonpath: string;
77+
glob?: boolean;
78+
};
7379
export type ExtraFile =
7480
| string
7581
| ExtraJsonFile
7682
| ExtraYamlFile
7783
| ExtraXmlFile
78-
| ExtraPomFile;
84+
| ExtraPomFile
85+
| ExtraTomlFile;
7986
/**
8087
* These are configurations provided to each strategy per-path.
8188
*/

src/strategies/base.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import {GenericJson} from '../updaters/generic-json';
4141
import {GenericXml} from '../updaters/generic-xml';
4242
import {PomXml} from '../updaters/java/pom-xml';
4343
import {GenericYaml} from '../updaters/generic-yaml';
44+
import {GenericToml} from '../updaters/generic-toml';
4445

4546
const DEFAULT_CHANGELOG_PATH = 'CHANGELOG.md';
4647

@@ -398,6 +399,13 @@ export abstract class BaseStrategy implements Strategy {
398399
updater: new GenericYaml(extraFile.jsonpath, version),
399400
});
400401
break;
402+
case 'toml':
403+
extraFileUpdates.push({
404+
path: this.addPath(path),
405+
createIfMissing: false,
406+
updater: new GenericToml(extraFile.jsonpath, version),
407+
});
408+
break;
401409
case 'xml':
402410
extraFileUpdates.push({
403411
path: this.addPath(path),

src/updaters/generic-toml.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// Copyright 2023 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import {Updater} from '../update';
16+
import {Version} from '../version';
17+
import * as jp from 'jsonpath';
18+
import {parseWith, replaceTomlValue} from '../util/toml-edit';
19+
import * as toml from '@iarna/toml';
20+
import {logger as defaultLogger, Logger} from '../util/logger';
21+
22+
/**
23+
* Updates TOML document according to given JSONPath.
24+
*
25+
* Note that used parser does reformat the document and removes all comments,
26+
* and converts everything to pure TOML.
27+
* If you want to retain formatting, use generic updater with comment hints.
28+
*/
29+
export class GenericToml implements Updater {
30+
readonly jsonpath: string;
31+
readonly version: Version;
32+
33+
constructor(jsonpath: string, version: Version) {
34+
this.jsonpath = jsonpath;
35+
this.version = version;
36+
}
37+
/**
38+
* Given initial file contents, return updated contents.
39+
* @param {string} content The initial content
40+
* @returns {string} The updated content
41+
*/
42+
updateContent(content: string, logger: Logger = defaultLogger): string {
43+
let data: toml.JsonMap;
44+
try {
45+
data = parseWith(content);
46+
} catch (e) {
47+
logger.warn('Invalid toml, cannot be parsed', e);
48+
return content;
49+
}
50+
51+
const paths = jp.paths(data, this.jsonpath);
52+
if (!paths || paths.length === 0) {
53+
logger.warn(`No entries modified in ${this.jsonpath}`);
54+
return content;
55+
}
56+
57+
let processed = content;
58+
paths.forEach(path => {
59+
if (path[0] === '$') path = path.slice(1);
60+
processed = replaceTomlValue(processed, path, this.version.toString());
61+
});
62+
63+
return processed;
64+
}
65+
}

src/util/toml-edit.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,10 @@ class TaggedTOMLParser extends TOMLParser {
8686
* @param input A string
8787
* @param parserType The TOML parser to use (might be custom)
8888
*/
89-
function parseWith(input: string, parserType: typeof TOMLParser): JsonMap {
89+
export function parseWith(
90+
input: string,
91+
parserType: typeof TOMLParser = TaggedTOMLParser
92+
): JsonMap {
9093
const parser = new parserType();
9194
parser.parse(input);
9295
return parser.finish();
@@ -114,7 +117,7 @@ function isTaggedValue(x: unknown): x is TaggedValue {
114117
*/
115118
export function replaceTomlValue(
116119
input: string,
117-
path: string[],
120+
path: (string | number)[],
118121
newValue: string
119122
) {
120123
// our pointer into the object "tree", initially points to the root.

test/strategies/base.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import {Generic} from '../../src/updaters/generic';
3030
import {GenericXml} from '../../src/updaters/generic-xml';
3131
import {PomXml} from '../../src/updaters/java/pom-xml';
3232
import {GenericYaml} from '../../src/updaters/generic-yaml';
33+
import {GenericToml} from '../../src/updaters/generic-toml';
3334

3435
const sandbox = sinon.createSandbox();
3536

@@ -145,6 +146,23 @@ describe('Strategy', () => {
145146
assertHasUpdate(updates!, '0', Generic);
146147
assertHasUpdate(updates!, '3.yaml', GenericYaml);
147148
});
149+
it('updates extra TOML files', async () => {
150+
const strategy = new TestStrategy({
151+
targetBranch: 'main',
152+
github,
153+
component: 'google-cloud-automl',
154+
extraFiles: ['0', {type: 'toml', path: '/3.toml', jsonpath: '$.foo'}],
155+
});
156+
const pullRequest = await strategy.buildReleasePullRequest(
157+
buildMockConventionalCommit('fix: a bugfix'),
158+
undefined
159+
);
160+
expect(pullRequest).to.exist;
161+
const updates = pullRequest?.updates;
162+
expect(updates).to.be.an('array');
163+
assertHasUpdate(updates!, '0', Generic);
164+
assertHasUpdate(updates!, '3.toml', GenericToml);
165+
});
148166
it('updates extra Xml files', async () => {
149167
const strategy = new TestStrategy({
150168
targetBranch: 'main',
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
invalid =

test/updaters/generic-toml.ts

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// Copyright 2023 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import {readFileSync} from 'fs';
16+
import {resolve} from 'path';
17+
import * as snapshot from 'snap-shot-it';
18+
import {describe, it} from 'mocha';
19+
import {Version} from '../../src/version';
20+
import {expect, assert} from 'chai';
21+
import {GenericToml} from '../../src/updaters/generic-toml';
22+
23+
const fixturesPath = './test/updaters/fixtures';
24+
25+
describe('GenericToml', () => {
26+
describe('updateContent', () => {
27+
it('updates matching entry', async () => {
28+
const oldContent = readFileSync(
29+
resolve(fixturesPath, './Cargo.toml'),
30+
'utf8'
31+
).replace(/\r\n/g, '\n');
32+
const updater = new GenericToml(
33+
'$.package.version',
34+
Version.parse('v2.3.4')
35+
);
36+
const newContent = updater.updateContent(oldContent);
37+
snapshot(newContent);
38+
});
39+
it('updates deep entry in toml', async () => {
40+
const oldContent = readFileSync(
41+
resolve(fixturesPath, './Cargo.toml'),
42+
'utf8'
43+
).replace(/\r\n/g, '\n');
44+
const updater = new GenericToml(
45+
"$['dev-dependencies']..version",
46+
Version.parse('v2.3.4')
47+
);
48+
const newContent = updater.updateContent(oldContent);
49+
snapshot(newContent);
50+
});
51+
it('ignores non-matching entry', async () => {
52+
const oldContent = readFileSync(
53+
resolve(fixturesPath, './Cargo.toml'),
54+
'utf8'
55+
).replace(/\r\n/g, '\n');
56+
const updater = new GenericToml('$.nonExistent', Version.parse('v2.3.4'));
57+
const newContent = updater.updateContent(oldContent);
58+
expect(newContent).to.eql(oldContent);
59+
});
60+
it('warns on invalid jsonpath', async () => {
61+
const oldContent = readFileSync(
62+
resolve(fixturesPath, './Cargo.toml'),
63+
'utf8'
64+
).replace(/\r\n/g, '\n');
65+
const updater = new GenericToml('bad jsonpath', Version.parse('v2.3.4'));
66+
assert.throws(() => {
67+
updater.updateContent(oldContent);
68+
});
69+
});
70+
it('ignores invalid file', async () => {
71+
const oldContent = readFileSync(
72+
resolve(fixturesPath, './toml/invalid.txt'),
73+
'utf8'
74+
).replace(/\r\n/g, '\n');
75+
const updater = new GenericToml('$.boo', Version.parse('v2.3.4'));
76+
const newContent = updater.updateContent(oldContent);
77+
expect(newContent).to.eql(oldContent);
78+
});
79+
});
80+
});

0 commit comments

Comments
 (0)