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: add xml and html file type support #40

Merged
merged 10 commits into from
Jan 8, 2025
Merged
34 changes: 33 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

This plugin reads and/or writes version/manifest files.

```
```sh
npm install --save-dev @release-it/bumper
```

Expand Down Expand Up @@ -36,6 +36,8 @@ The supported file types are:
| YAML | `.yaml` or `.yml` | `text/yaml` or `application-x-yaml` |
| TOML | `.toml` | `text/toml` or `application/toml` |
| INI | `.ini` | `text/x-properties` |
| XML | `.xml` | `text/xml` or `application/xml` |
| HTML | `.html` | `text/html` |
| TEXT | `.txt` | `text/*` |

Explicitly providing the (mime) `type` takes precedence over the file extension.
Expand Down Expand Up @@ -125,6 +127,36 @@ The `path` option (default: `"version"`) can be used to change a different prope
}
```

> For the `xml` type, the `path` option must be in the form of a unique [css selector](https://www.w3.org/TR/selectors-4/#overview). The following example will set the
`project > version` property to the new version in `pom.xml`:

```json
"plugins": {
"@release-it/bumper": {
"out": {
"file": "pom.xml",
"path": "project > version",
"type": "application/xml"
}
}
}
```

> For the `html` type, the `path` option must be in the form of a unique [css selector](https://www.w3.org/TR/selectors-4/#overview). The following example will set the
element text content with the css attribute `#version` to the new version in `foo.html`:

```json
"plugins": {
"@release-it/bumper": {
"out": {
"file": "foo.html",
"path": "#version",
"type": "text/html"
}
}
}
```

Multiple paths can be provided using an array.

The `versionPrefix` option (default: `''`) can be used in cases where you'd like to maintain a specific prefix for your version number (for example, in `package.json` where you might want versions like `^1.0.0`). This will prepend the specified prefix to the bumped version:
Expand Down
87 changes: 79 additions & 8 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@ import { EOL } from 'os';
import glob from 'fast-glob';
import { castArray, get, set } from 'lodash-es';
import detectIndent from 'detect-indent';
import { detectNewline } from 'detect-newline';
import yaml from 'js-yaml';
import toml from '@iarna/toml';
import ini from 'ini';
import jsdom from 'jsdom';
import serialize from 'w3c-xmlserializer';
import semver from 'semver';
import { Plugin } from 'release-it';

Expand All @@ -14,19 +17,25 @@ const isString = value => typeof value === 'string';

const mimeTypesMap = {
'application/json': 'json',
'text/yaml': 'yaml',
'application/yaml': 'yaml',
'application/x-yaml': 'yaml',
'text/toml': 'toml',
'text/yaml': 'yaml',
'application/toml': 'toml',
'text/x-properties': 'ini'
'text/toml': 'toml',
'text/x-properties': 'ini',
'application/xml': 'xml',
'text/xml': 'xml',
'text/html': 'html'
};

const extensionsMap = {
json: 'json',
yml: 'yaml',
yaml: 'yaml',
toml: 'toml',
ini: 'ini'
ini: 'ini',
xml: 'xml',
html: 'html'
};

const parseFileOption = option => {
Expand Down Expand Up @@ -54,7 +63,12 @@ const parse = async (data, type) => {
return toml.parse(data);
case 'ini':
return ini.parse(data);
default:
case 'xml':
case 'html':
const dom = new jsdom.JSDOM('');
const doc = new dom.window.DOMParser();
return doc.parseFromString(data, 'application/xml');
default: // text
return (data || '').toString();
}
};
Expand All @@ -75,7 +89,34 @@ class Bumper extends Plugin {
}

const parsed = await parse(data, type);
const version = isString(parsed) ? parsed.trim() : get(parsed, path);

let version = undefined;
switch (type) {
case 'json':
case 'yaml':
case 'toml':
version = get(parsed, path);
break;
case 'ini':
const pathProperties = path.split(' ');
if (pathProperties.length > 1) {
version = parsed[pathProperties[0]][pathProperties[1]];
} else {
version = parsed[path];
}
break;
case 'xml':
case 'html':
const element = parsed.querySelector(path);
if (!element) {
throw new Error(`Failed to find the element with the provided selector: ${path}`);
}
version = element.textContent;
break;
default: // text
version = parsed.trim();
}

const parsedVersion = semver.parse(version);
return parsedVersion ? parsedVersion.toString() : null;
}
Expand Down Expand Up @@ -126,6 +167,7 @@ class Bumper extends Plugin {

const parsed = await parse(data, type);
const indent = isString(data) ? detectIndent(data).indent || ' ' : null;
const newline = isString(data) ? detectNewline(data) || EOL : null;

if (typeof parsed !== 'string') {
castArray(path).forEach(path => set(parsed, path, versionPrefix + version));
Expand All @@ -137,12 +179,41 @@ class Bumper extends Plugin {
case 'yaml':
return writeFileSync(file, yaml.dump(parsed, { indent: indent.length }));
case 'toml':
return writeFileSync(file, toml.stringify(parsed));
const tomlString = toml.stringify(parsed);
// handle empty objects for sections
const tomlContent = tomlString.replace(/^([a-zA-Z0-9\.\-]+) \= \{ \}/g, '[$1]');
pbarton-andovercos marked this conversation as resolved.
Show resolved Hide resolved
return writeFileSync(file, tomlContent);
case 'ini':
return writeFileSync(file, ini.encode(parsed));
case 'xml':
const xmlElement = parsed.querySelector(path);
if (!xmlElement) {
throw new Error(`Failed to find the element with the provided selector: ${path}`);
}

xmlElement.textContent = version;

const xml = serialize(parsed);

const CRLF = '\r\n';
if (newline === CRLF) {
return writeFileSync(file, xml.replace(/\n/g, CRLF) + CRLF);
}

return writeFileSync(file, xml + '\n');
case 'html':
const htmlElement = parsed.querySelector(path);
if (!htmlElement) {
throw new Error(`Failed to find the element with the provided selector: ${path}`);
}

const previousContents = htmlElement.outerHTML;
htmlElement.textContent = version;
const html = data.replace(previousContents.trim(), htmlElement.outerHTML.trim());
return writeFileSync(file, html);
default:
const versionMatch = new RegExp(latestVersion || '', 'g');
const write = (parsed && !consumeWholeFile) ? parsed.replace(versionMatch, version) : version + EOL;
const write = parsed && !consumeWholeFile ? parsed.replace(versionMatch, version) : version + EOL;
return writeFileSync(file, write);
}
})
Expand Down
28 changes: 15 additions & 13 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"./package.json": "./package.json"
},
"scripts": {
"test": "bron test.js",
"test": "node --test test",
"release": "release-it"
},
"keywords": [
Expand All @@ -23,28 +23,29 @@
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/release-it/bumper.git"
"url": "git+https://github.com/release-it/bumper.git"
},
"homepage": "https://github.com/release-it/bumper#readme",
"bugs": "https://github.com/release-it/bumper/issues",
"author": {
"email": "[email protected]",
"name": "Lars Kappert"
"bugs": {
"url": "https://github.com/release-it/bumper/issues"
},
"author": "Lars Kappert <[email protected]>",
"dependencies": {
"@iarna/toml": "^2.2.5",
"@iarna/toml": "^3.0.0",
"detect-indent": "7.0.1",
"detect-newline": "4.0.1",
"fast-glob": "^3.3.2",
"ini": "^4.1.1",
"ini": "^5.0.0",
"js-yaml": "^4.1.0",
"jsdom": "^25.0.1",
"lodash-es": "^4.17.21",
"semver": "^7.3.7"
"semver": "^7.3.7",
"w3c-xmlserializer": "^5.0.0"
},
"devDependencies": {
"bron": "^2.0.3",
"mock-fs": "5.2.0",
"mock-fs": "5.4.1",
"release-it": "^17.0.0",
"sinon": "^15.2.0"
"sinon": "^19.0.2"
},
"peerDependencies": {
"release-it": "^17.0.0"
Expand All @@ -63,5 +64,6 @@
"submit": true
}
}
}
},
"main": "index.js"
}
Loading