mirror of
https://github.com/locomotivemtl/locomotive-boilerplate.git
synced 2026-01-15 00:55:08 +08:00
Improve asset versioning task
Add support for "increment" format to increment an integer version number with support for "increment:semver" to increment the build or patch of a SemVer version number.
Usage:
```json
"versions": [
{
"format": "increment",
"key": "version",
"outfile": "assets.json"
}
]
```
```jsonc
{
"version": 16 // → 17
}
```
```json
"versions": [
{
"format": "increment:semver",
"key": "version",
"outfile": "assets.json"
}
]
```
```jsonc
{
"version": "1.0.0" // → 1.0.1
}
```
```jsonc
{
"version": "1.0.0+1" // → 1.0.0+2
}
```
This commit is contained in:
@@ -21,6 +21,8 @@ import {
|
|||||||
} from 'node:path';
|
} from 'node:path';
|
||||||
import readline from 'node:readline';
|
import readline from 'node:readline';
|
||||||
|
|
||||||
|
export const REGEXP_SEMVER = /^(?<major>0|[1-9]\d*)\.(?<minor>0|[1-9]\d*)\.(?<patch>0|[1-9]\d*)(?:-(?<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?<buildmetadata>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {object} VersionOptions
|
* @typedef {object} VersionOptions
|
||||||
* @property {string|number|null} prettyPrint - A string or number to insert
|
* @property {string|number|null} prettyPrint - A string or number to insert
|
||||||
@@ -135,32 +137,64 @@ export default async function bumpVersions(versionOptions = null) {
|
|||||||
/**
|
/**
|
||||||
* Creates a formatted version number or string.
|
* Creates a formatted version number or string.
|
||||||
*
|
*
|
||||||
* @param {string} format - The version format.
|
* @param {string} format - The version format.
|
||||||
|
* @param {?string} [oldValue] - The old version value.
|
||||||
* @return {string|number}
|
* @return {string|number}
|
||||||
|
* @throws TypeError If the format or value are invalid.
|
||||||
*/
|
*/
|
||||||
function createVersionNumber(format) {
|
function createVersionNumber(format, oldValue = null) {
|
||||||
let [ type, modifier ] = format.split(':', 2);
|
let [ type, modifier ] = format.split(':', 2);
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'hex':
|
case 'hex':
|
||||||
case 'hexadecimal':
|
case 'hexadecimal':
|
||||||
modifier = Number.parseInt(modifier);
|
try {
|
||||||
|
modifier = Number.parseInt(modifier);
|
||||||
|
|
||||||
if (Number.isNaN(modifier)) {
|
if (Number.isNaN(modifier)) {
|
||||||
modifier = 6;
|
modifier = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
return randomBytes(modifier).toString('hex');
|
||||||
|
} catch (err) {
|
||||||
|
throw new TypeError(
|
||||||
|
`${err.message} for \'format\' type "hexadecimal"`,
|
||||||
|
{ cause: err }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return randomBytes(modifier).toString('hex');
|
case 'inc':
|
||||||
|
case 'increment':
|
||||||
|
try {
|
||||||
|
if (modifier === 'semver') {
|
||||||
|
return incrementSemVer(oldValue, [ 'buildmetadata', 'patch' ]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return incrementNumber(oldValue, modifier);
|
||||||
|
} catch (err) {
|
||||||
|
throw new TypeError(
|
||||||
|
`${err.message} for \'format\' type "increment"`,
|
||||||
|
{ cause: err }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
case 'regex':
|
case 'regex':
|
||||||
return new RegExp(modifier);
|
case 'regexp':
|
||||||
|
try {
|
||||||
|
return new RegExp(modifier);
|
||||||
|
} catch (err) {
|
||||||
|
throw new TypeError(
|
||||||
|
`${err.message} for \'format\' type "regexp"`,
|
||||||
|
{ cause: err }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
case 'timestamp':
|
case 'timestamp':
|
||||||
return Date.now().valueOf();
|
return Date.now().valueOf();
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new TypeError(
|
throw new TypeError(
|
||||||
'Expected \'format\' to be either "timestamp" or "hexadecimal"'
|
'Expected \'format\' to be either "timestamp", "increment", or "hexadecimal"'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,9 +255,7 @@ async function handleBumpVersionInJson(outfile, label, options) {
|
|||||||
await mkdir(dirname(outfile), { recursive: true });
|
await mkdir(dirname(outfile), { recursive: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
const version = createVersionNumber(options.format);
|
json[options.key] = createVersionNumber(options.format, json?.[options.key]);
|
||||||
|
|
||||||
json[options.key] = version;
|
|
||||||
|
|
||||||
return await writeFile(
|
return await writeFile(
|
||||||
outfile,
|
outfile,
|
||||||
@@ -254,7 +286,7 @@ async function handleBumpVersionWithRegExp(outfile, label, options) {
|
|||||||
input: createReadStream(bckfile),
|
input: createReadStream(bckfile),
|
||||||
});
|
});
|
||||||
|
|
||||||
const version = createVersionNumber(options.format);
|
let newVersion = null;
|
||||||
|
|
||||||
const writeStream = createWriteStream(outfile, { encoding: 'utf8' });
|
const writeStream = createWriteStream(outfile, { encoding: 'utf8' });
|
||||||
|
|
||||||
@@ -262,12 +294,12 @@ async function handleBumpVersionWithRegExp(outfile, label, options) {
|
|||||||
const found = line.match(options.key);
|
const found = line.match(options.key);
|
||||||
|
|
||||||
if (found) {
|
if (found) {
|
||||||
const groups = (found.groups ?? {});
|
const groups = (found.groups ?? {});
|
||||||
const replace = found[0].replace(
|
const oldVersion = (groups.build ?? groups.version ?? found[1] ?? found[0]);
|
||||||
(groups.build ?? groups.version ?? found[1] ?? found[0]),
|
const newVersion = createVersionNumber(options.format, oldVersion);
|
||||||
version
|
const replacement = found[0].replace(oldVersion, newVersion);
|
||||||
);
|
|
||||||
line = line.replace(found[0], replace);
|
line = line.replace(found[0], replacement);
|
||||||
}
|
}
|
||||||
|
|
||||||
writeStream.write(line + "\n");
|
writeStream.write(line + "\n");
|
||||||
@@ -285,6 +317,88 @@ async function handleBumpVersionWithRegExp(outfile, label, options) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increments the given integer.
|
||||||
|
*
|
||||||
|
* @param {string|int} value - The number to increment.
|
||||||
|
* @param {string|int} [increment=1] - The amount to increment by.
|
||||||
|
* @return {int}
|
||||||
|
* @throws TypeError If the version number is invalid.
|
||||||
|
*/
|
||||||
|
function incrementNumber(value, increment = 1) {
|
||||||
|
const version = Number.parseInt(value);
|
||||||
|
if (Number.isNaN(version)) {
|
||||||
|
throw new TypeError(
|
||||||
|
`Expected an integer version number, received [${value}]`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
increment = Number.parseInt(increment);
|
||||||
|
if (Number.isNaN(increment)) {
|
||||||
|
throw new TypeError(
|
||||||
|
'Expected an integer increment number'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (version + increment);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increments the given SemVer version number.
|
||||||
|
*
|
||||||
|
* @param {string} value - The version to mutate.
|
||||||
|
* @param {string|string[]} [target] - The segment to increment, one of:
|
||||||
|
* 'major', 'minor', 'patch', ~~'prerelease'~~, 'buildmetadata'.
|
||||||
|
* @param {string|int} [increment=1] - The amount to increment by.
|
||||||
|
* @return {string}
|
||||||
|
* @throws TypeError If the version or target are invalid.
|
||||||
|
*/
|
||||||
|
function incrementSemVer(value, target = 'patch', increment = 1) {
|
||||||
|
const found = value.match(REGEXP_SEMVER);
|
||||||
|
if (!found) {
|
||||||
|
throw new TypeError(
|
||||||
|
`Expected a SemVer version number, received [${value}]`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(target)) {
|
||||||
|
for (const group of target) {
|
||||||
|
if (found.groups[group] != null) {
|
||||||
|
target = group;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!target || !found.groups[target]) {
|
||||||
|
throw new TypeError(
|
||||||
|
`Expected a supported SemVer segment, received [${target}]`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const segments = {
|
||||||
|
'major': '',
|
||||||
|
'minor': '.',
|
||||||
|
'patch': '.',
|
||||||
|
'prerelease': '-',
|
||||||
|
'buildmetadata': '+',
|
||||||
|
};
|
||||||
|
|
||||||
|
let replacement = '';
|
||||||
|
|
||||||
|
for (const [ segment, delimiter ] of Object.entries(segments)) {
|
||||||
|
if (found.groups?.[segment] != null) {
|
||||||
|
const newVersion = (segment === target)
|
||||||
|
? incrementNumber(found.groups[segment], increment)
|
||||||
|
: found.groups[segment];
|
||||||
|
|
||||||
|
replacement += `${delimiter}${newVersion}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return value.replace(found[0], replacement);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses the version key.
|
* Parses the version key.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -338,8 +338,8 @@ See [`svgs.js`](../build/tasks/svgs.js) for details.
|
|||||||
|
|
||||||
A task to create and update values for use in versioning assets.
|
A task to create and update values for use in versioning assets.
|
||||||
|
|
||||||
Can generate a hexadecimal value (using random bytes) or
|
Can generate a hexadecimal value (using random bytes), use the current timestamp,
|
||||||
use the current timestamp.
|
or increment a number.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
@@ -355,6 +355,11 @@ Example:
|
|||||||
"format": "hex:8",
|
"format": "hex:8",
|
||||||
"key": "hex",
|
"key": "hex",
|
||||||
"outfile": "./assets.json"
|
"outfile": "./assets.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": "inc:semver",
|
||||||
|
"key": "inc",
|
||||||
|
"outfile": "./assets.json"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -363,7 +368,8 @@ Example:
|
|||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"now": 1665071717350,
|
"now": 1665071717350,
|
||||||
"hex": "6ef54181c4ba"
|
"hex": "6ef54181c4ba",
|
||||||
|
"hex": "1.0.2"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user