Version Bump: Add CLI utility (#34555)

This commit is contained in:
Paul Sealock 2022-09-08 19:48:01 +12:00 committed by GitHub
parent 52ba69d712
commit fa2e7f7e96
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 3540 additions and 4343 deletions

View File

@ -0,0 +1,4 @@
Significance: minor
Type: dev
Add version number to composer.json

View File

@ -6,6 +6,7 @@
"license": "GPL-3.0-or-later",
"prefer-stable": true,
"minimum-stability": "dev",
"version": "2.1.0",
"require": {
"composer/installers": "~1.7"
},

File diff suppressed because it is too large Load Diff

View File

@ -8,4 +8,4 @@ packages:
- 'tools/package-release'
- 'tools/cherry-pick'
- 'tools/release-post-generator'
- 'tools/version-bump'

View File

@ -13,4 +13,4 @@ import './commands/release-post';
dotenv.config();
// Start the program
program.parse();
program.parse( process.argv );

View File

@ -0,0 +1,3 @@
{
"extends": [ "plugin:@woocommerce/eslint-plugin/recommended" ]
}

View File

@ -0,0 +1,49 @@
/**
* External dependencies
*/
import { prerelease } from 'semver';
/**
* Internal dependencies
*/
import { program } from '../program';
import { validateArgs, stripPrereleaseParameters } from '../lib/validate';
import {
updatePluginFile,
updateReadmeChangelog,
updateJSON,
updateClassPluginFile,
updateReadmeStableTag,
} from '../lib/update';
program
.command( 'bump' )
.description( 'CLI to automate version bumping.' )
.argument( '<plugin>', 'Monorepo plugin' )
.requiredOption( '-v, --version <string>', 'Version to bump to' )
.action( async ( plugin: string, options ) => {
await validateArgs( plugin, options );
let nextVersion = options.version;
const prereleaseParameters = prerelease( nextVersion );
const isDevVersionBump =
prereleaseParameters && prereleaseParameters[ 0 ] === 'dev';
await updatePluginFile( plugin, nextVersion );
// Any updated files besides the plugin file get a version stripped of prerelease parameters.
nextVersion = stripPrereleaseParameters( nextVersion );
if ( isDevVersionBump ) {
// Bumping the dev version means updating the readme's changelog.
await updateReadmeChangelog( plugin, nextVersion );
} else {
// Only update stable tag on real releases.
await updateReadmeStableTag( plugin, nextVersion );
}
await updateJSON( 'composer', plugin, nextVersion );
await updateJSON( 'package', plugin, nextVersion );
await updateClassPluginFile( plugin, nextVersion );
} );

View File

@ -0,0 +1,8 @@
/**
* Internal dependencies
*/
import { program } from './program';
import './commands/bump';
// Start the program
program.parse();

View File

@ -0,0 +1,7 @@
/**
* External dependencies
*/
import { dirname } from 'path';
// Escape from ./tools/package-release/src
export const MONOREPO_ROOT = dirname( dirname( dirname( __dirname ) ) );

View File

@ -0,0 +1,16 @@
/**
* External dependencies
*/
const chalk = require( 'chalk' );
const { error } = console;
/**
* This is a temporary Logger until a common one is built.
*/
export class Logger {
static error( message: string ) {
error( chalk.red( message ) );
process.exit( 1 );
}
}

View File

@ -0,0 +1,152 @@
/**
* External dependencies
*/
import { readFile, writeFile, stat } from 'fs/promises';
import { join } from 'path';
/**
* Internal dependencies
*/
import { Logger } from './logger';
import { MONOREPO_ROOT } from './const';
/**
* Update plugin readme stable tag.
*
* @param plugin plugin to update
* @param nextVersion version to bump to
*/
export const updateReadmeStableTag = async (
plugin: string,
nextVersion: string
): Promise< void > => {
const filePath = join( MONOREPO_ROOT, `plugins/${ plugin }/readme.txt` );
try {
const readmeContents = await readFile( filePath, 'utf8' );
const updatedReadmeContents = readmeContents.replace(
/Stable tag: \d.\d.\d\n/m,
`Stable tag: ${ nextVersion }\n`
);
await writeFile( filePath, updatedReadmeContents );
} catch ( e ) {
Logger.error( 'Unable to update readme stable tag' );
}
};
/**
* Update plugin readme changelog.
*
* @param plugin plugin to update
* @param nextVersion version to bump to
*/
export const updateReadmeChangelog = async (
plugin: string,
nextVersion: string
): Promise< void > => {
const filePath = join( MONOREPO_ROOT, `plugins/${ plugin }/readme.txt` );
try {
const readmeContents = await readFile( filePath, 'utf8' );
const updatedReadmeContents = readmeContents.replace(
/= \d.\d.\d \d\d\d\d-XX-XX =\n/m,
`= ${ nextVersion } ${ new Date().getFullYear() }-XX-XX =\n`
);
await writeFile( filePath, updatedReadmeContents );
} catch ( e ) {
Logger.error( 'Unable to update readme changelog' );
}
};
/**
* Update plugin class file.
*
* @param plugin plugin to update
* @param nextVersion version to bump to
*/
export const updateClassPluginFile = async (
plugin: string,
nextVersion: string
): Promise< void > => {
const filePath = join(
MONOREPO_ROOT,
`plugins/${ plugin }/includes/class-${ plugin }.php`
);
try {
await stat( filePath );
} catch ( e ) {
// Class file does not exist, return early.
return;
}
try {
const classPluginFileContents = await readFile( filePath, 'utf8' );
const updatedClassPluginFileContents = classPluginFileContents.replace(
/public \$version = '\d.\d.\d';\n/m,
`public $version = '${ nextVersion }';\n`
);
await writeFile( filePath, updatedClassPluginFileContents );
} catch ( e ) {
Logger.error( 'Unable to update plugin file.' );
}
};
/**
* Update plugin JSON files.
*
* @param {string} type plugin to update
* @param {string} plugin plugin to update
* @param {string} nextVersion version to bump to
*/
export const updateJSON = async (
type: 'package' | 'composer',
plugin: string,
nextVersion: string
): Promise< void > => {
const filePath = join(
MONOREPO_ROOT,
`plugins/${ plugin }/${ type }.json`
);
try {
const composerJson = JSON.parse( await readFile( filePath, 'utf8' ) );
composerJson.version = nextVersion;
await writeFile(
filePath,
JSON.stringify( composerJson, null, '\t' ) + '\n'
);
} catch ( e ) {
Logger.error( 'Unable to update composer.json' );
}
};
/**
* Update plugin main file.
*
* @param plugin plugin to update
* @param nextVersion version to bump to
*/
export const updatePluginFile = async (
plugin: string,
nextVersion: string
): Promise< void > => {
const filePath = join(
MONOREPO_ROOT,
`plugins/${ plugin }/${ plugin }.php`
);
try {
const pluginFileContents = await readFile( filePath, 'utf8' );
const updatedPluginFileContents = pluginFileContents.replace(
/Version: \d.\d.\d.*\n/m,
`Version: ${ nextVersion }\n`
);
await writeFile( filePath, updatedPluginFileContents );
} catch ( e ) {
Logger.error( 'Unable to update plugin file.' );
}
};

View File

@ -0,0 +1,75 @@
/**
* External dependencies
*/
import { valid, lt as versionLessThan, prerelease, parse } from 'semver';
import { readFile } from 'fs/promises';
/**
* Internal dependencies
*/
import { Logger } from './logger';
/**
* Get a plugin's current version.
*
* @param plugin plugin to update.
*/
export const getCurrentVersion = async (
plugin: string
): Promise< string | void > => {
try {
const composerJSON = JSON.parse(
await readFile( `plugins/${ plugin }/composer.json`, 'utf8' )
);
return composerJSON.version;
} catch ( e ) {
Logger.error( 'Unable to read current version.' );
}
};
/**
* When given a prerelease version, return just the version.
*
* @param {string} prereleaseVersion version with prerelease params
* @return {string} version
*/
export const stripPrereleaseParameters = (
prereleaseVersion: string
): string => {
const parsedVersion = parse( prereleaseVersion );
if ( parsedVersion ) {
const { major, minor, patch } = parsedVersion;
return `${ major }.${ minor }.${ patch }`;
}
return prereleaseVersion;
};
/**
* Validate inputs.
*
* @param plugin plugin
* @param options options
* @param options.version version
*/
export const validateArgs = async (
plugin: string,
options: { version: string }
): Promise< void > => {
const nextVersion = options.version;
if ( ! valid( nextVersion ) ) {
Logger.error(
'Invalid version supplied, please pass in a semantically correct version.'
);
}
const currentVersion = await getCurrentVersion( plugin );
if ( ! currentVersion ) {
Logger.error( 'Unable to determine current version' );
} else if ( versionLessThan( nextVersion, currentVersion ) ) {
Logger.error(
'The version supplied is less than the current version, please supply a valid version.'
);
}
};

View File

@ -0,0 +1,23 @@
{
"name": "version-bump",
"version": "0.1.0",
"description": "Automate version bumping for WooCommerce plugins",
"main": " ",
"scripts": {},
"author": "",
"license": "GPL-2.0-or-later",
"devDependencies": {
"@tsconfig/node16": "^1.0.3",
"@types/express": "^4.17.13",
"typescript": "^4.7.4"
},
"dependencies": {
"@commander-js/extra-typings": "^0.1.0",
"chalk": "^4.1.2",
"commander": "9.4.0",
"express": "^4.18.1",
"ora": "^5.4.1",
"semver": "^7.3.2",
"ts-node": "^10.9.1"
}
}

View File

@ -0,0 +1,10 @@
/**
* External dependencies
*/
import { Command } from '@commander-js/extra-typings';
export const program = new Command();
program
.name( 'version-bump' )
.description( 'CLI to automate version bumping for WooCommerce plugins.' );

View File

@ -0,0 +1,7 @@
{
"extends": "@tsconfig/node16/tsconfig.json",
"ts-node": {
"transpileOnly": true,
"files": true
}
}