293 lines
8.6 KiB
JavaScript
293 lines
8.6 KiB
JavaScript
import path from 'path';
|
|
import { fileURLToPath } from 'url';
|
|
import moment from 'moment';
|
|
import replace from 'replace-in-file';
|
|
import inquirer from 'inquirer';
|
|
import semver from 'semver';
|
|
import {
|
|
displayErrorMessage,
|
|
displayConfirmationMessage
|
|
} from './index.mjs';
|
|
|
|
import mainPackageJson from '../../package.json' with { type: 'json' };
|
|
import hotConfig from '../../hot.config.js';
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
|
|
const workspacePackages = mainPackageJson.workspaces;
|
|
|
|
/**
|
|
* Current version of the monorepo (before updating).
|
|
*
|
|
* @type {string}
|
|
*/
|
|
export const CURRENT_VERSION = hotConfig.HOT_VERSION;
|
|
|
|
/**
|
|
* Check if the provided version number is a valid semver version number.
|
|
*
|
|
* @param {string} version Version number.
|
|
* @returns {boolean} `true` if the version number is a valid semver version number, `false` otherwise.
|
|
*/
|
|
export function isVersionValid(version) {
|
|
return !!semver.valid(version);
|
|
}
|
|
|
|
/**
|
|
* Check if the provided release date in a format of 'DD/MM/YYYY' is a valid future date.
|
|
*
|
|
* @param {string} date The date in format of 'DD/MM/YYYY'.
|
|
* @returns {object} Object containing information about the release validity in a form of `{valid: boolean, error:
|
|
* string}`.
|
|
*/
|
|
export function validateReleaseDate(date) {
|
|
const dateObj = moment(date, 'DD/MM/YYYY', true);
|
|
const now = moment();
|
|
const returnObj = {
|
|
valid: true,
|
|
error: null
|
|
};
|
|
|
|
if (!dateObj.isValid()) {
|
|
returnObj.valid = false;
|
|
returnObj.error = 'The provided date is invalid.';
|
|
|
|
} else if (!dateObj.isAfter(now)) {
|
|
returnObj.valid = false;
|
|
returnObj.error = 'The release date has to be a future date.';
|
|
}
|
|
|
|
return returnObj;
|
|
}
|
|
|
|
/**
|
|
* Set the provided version number to the Handsontable's `package.json` and other packages' `dependency` fields.
|
|
*
|
|
* @param {string} version The version number.
|
|
* @param {Array} [packages] Array of package paths. Defaults to the workspace config.
|
|
*/
|
|
export function setVersion(version, packages = workspacePackages) {
|
|
// Set the new version number to hot.config.js.
|
|
const hotConfigPath = path.resolve(__dirname, '../../hot.config.js');
|
|
|
|
validateReplacementStatus(replace.sync({
|
|
files: hotConfigPath,
|
|
from: /HOT_VERSION: '(.*)'/,
|
|
to: `HOT_VERSION: '${version}'`,
|
|
}), version);
|
|
|
|
// Set the new version number to all the packages.
|
|
packages.forEach((packagesLocation) => {
|
|
validateReplacementStatus(replace.sync({
|
|
files: `${packagesLocation}${packagesLocation === '.' ? '' : '*'}/package.json`,
|
|
from: [/"version": "(.*)"/, /"handsontable": "([^\d]*)((\d+)\.(\d+).(\d+)(.*))"/g],
|
|
to: (fullMatch, ...[semverPrefix, previousVersion]) => {
|
|
if (fullMatch.indexOf('version') > 0) {
|
|
// Replace the version with the new version.
|
|
return `"version": "${version}"`;
|
|
|
|
} else {
|
|
const isPreRelease = version.includes('-next-');
|
|
const newVersion = isPreRelease ? version : `${semverPrefix}${semver.major(
|
|
semver.maxSatisfying([version, previousVersion], '*')
|
|
)}.0.0`;
|
|
|
|
// Replace the `handsontable` dependency with the current major (or previous major, if it's a prerelease).
|
|
return `"handsontable": "${newVersion}"`;
|
|
}
|
|
},
|
|
ignore: [
|
|
`${packagesLocation}*/node_modules/**/*`,
|
|
`${packagesLocation}*/projects/hot-table/package.json`,
|
|
`${packagesLocation}*/dist/hot-table/package.json`,
|
|
],
|
|
}), version);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Set the provided release date in the `hot.config.js` file.
|
|
*
|
|
* @param {string} date The release date in a format of 'DD/MM/YYYY'.
|
|
*/
|
|
export function setReleaseDate(date) {
|
|
const hotConfigPath = path.resolve(__dirname, '../../hot.config.js');
|
|
const replacementStatus = replace.sync({
|
|
files: hotConfigPath,
|
|
from: /HOT_RELEASE_DATE: '(.*)'/,
|
|
to: `HOT_RELEASE_DATE: '${date}'`,
|
|
});
|
|
|
|
validateReplacementStatus(replacementStatus, date);
|
|
}
|
|
|
|
/**
|
|
* Get the new version from the provided release type (major/minor/patch).
|
|
*
|
|
* @param {string} type 'major'/'minor'/'patch'.
|
|
* @param {string} currentVersion Current version string.
|
|
* @returns {string} A new semver-based version.
|
|
*/
|
|
export function getVersionFromReleaseType(type, currentVersion) {
|
|
return semver.inc(currentVersion, type);
|
|
}
|
|
|
|
/**
|
|
* Schedule the release by setting the version number to the `package.json` files in all of the packages and release
|
|
* date in the Handsontable config.
|
|
*
|
|
* @param {string} [version] Version number.
|
|
* @param {string} [releaseDate] Release date in the `DD/MM/YYYY` format.
|
|
* @returns {object}
|
|
*/
|
|
export async function scheduleRelease(version, releaseDate) {
|
|
const currentVersion = hotConfig.HOT_VERSION;
|
|
const questions = [
|
|
{
|
|
type: 'list',
|
|
name: 'changeType',
|
|
message: 'Select the type of the release.',
|
|
choices: [
|
|
'Major',
|
|
'Minor',
|
|
'Patch',
|
|
'Custom',
|
|
],
|
|
filter: value => value.toLowerCase(),
|
|
},
|
|
{
|
|
type: 'input',
|
|
name: 'customVersion',
|
|
message: 'Enter the custom version number.',
|
|
when: answers => answers.changeType === 'custom',
|
|
validate: (value) => {
|
|
if (isVersionValid(value)) {
|
|
return true;
|
|
}
|
|
|
|
return 'The provided version is not a proper semver version number.';
|
|
},
|
|
},
|
|
{
|
|
type: 'input',
|
|
name: 'releaseDate',
|
|
message: 'Enter the release date in a form of DD/MM/YYYY.',
|
|
validate: (value) => {
|
|
const releaseDateValidity = validateReleaseDate(value);
|
|
|
|
if (releaseDateValidity.valid) {
|
|
return true;
|
|
}
|
|
|
|
return releaseDateValidity.error;
|
|
},
|
|
}
|
|
];
|
|
const getConfirmationQuestion = (newVersion, formattedDate) => [
|
|
{
|
|
type: 'confirm',
|
|
name: 'isReleaseDateConfirmed',
|
|
message: `
|
|
|
|
* New version: ${newVersion}
|
|
* Release date: ${formattedDate}
|
|
|
|
Are the version number and release date above correct?`,
|
|
default: true,
|
|
},
|
|
];
|
|
let finalVersion = null;
|
|
let finalReleaseDate = null;
|
|
|
|
// Version and release date were passed as arguments.
|
|
if (version && releaseDate) {
|
|
const semverBumpOptions = ['major', 'minor', 'patch'];
|
|
const releaseDateValidity = validateReleaseDate(releaseDate);
|
|
let newVersion = '';
|
|
|
|
if (!releaseDateValidity.valid) {
|
|
displayErrorMessage(releaseDateValidity.error);
|
|
process.exit(1);
|
|
}
|
|
|
|
if (isVersionValid(version)) {
|
|
newVersion = version;
|
|
|
|
} else if (semverBumpOptions.includes(version)) {
|
|
newVersion = semver.inc(currentVersion, version);
|
|
|
|
} else {
|
|
displayErrorMessage(
|
|
`${version} is not a valid version number, nor a semver change type (major/minor/patch).`);
|
|
process.exit(1);
|
|
}
|
|
|
|
if (!newVersion) {
|
|
displayErrorMessage('Something went wrong while updating the version number with semver.');
|
|
process.exit(1);
|
|
}
|
|
|
|
displayConfirmationMessage(
|
|
`\nChanging the version number to ${newVersion}, to be released on ${releaseDate}. \n`
|
|
);
|
|
|
|
finalVersion = newVersion;
|
|
finalReleaseDate = releaseDate;
|
|
|
|
setVersion(newVersion, workspacePackages);
|
|
setReleaseDate(releaseDate);
|
|
|
|
} else {
|
|
const answers = await inquirer.prompt(questions);
|
|
const releaseDateObj = moment(answers.releaseDate, 'DD/MM/YYYY', true);
|
|
const newVersion =
|
|
answers.changeType !== 'custom' ?
|
|
getVersionFromReleaseType(answers.changeType, currentVersion) :
|
|
answers.customVersion;
|
|
const confirmationAnswers = await inquirer.prompt(
|
|
getConfirmationQuestion(newVersion, releaseDateObj.format('DD MMMM YYYY'))
|
|
);
|
|
|
|
if (confirmationAnswers.isReleaseDateConfirmed) {
|
|
|
|
finalVersion = newVersion;
|
|
finalReleaseDate = answers.releaseDate;
|
|
|
|
setVersion(newVersion);
|
|
setReleaseDate(answers.releaseDate);
|
|
}
|
|
}
|
|
|
|
return {
|
|
version: finalVersion,
|
|
releaseDate: finalReleaseDate
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Helper function validating the return status of `replace-in-file`'s `replace` method.
|
|
*
|
|
* @private
|
|
* @param {Array} replacementStatus Replacement status array returned from `replace-in-file`'s `replace` method.
|
|
* @param {string} replacedString The string replaced in the source file.
|
|
*/
|
|
function validateReplacementStatus(replacementStatus, replacedString) {
|
|
let versionReplaced = true;
|
|
|
|
replacementStatus.forEach((infoObj) => {
|
|
const filePath = infoObj.file.replace('./', '');
|
|
|
|
if (!infoObj.hasChanged) {
|
|
displayErrorMessage(`${filePath} was not modified.`);
|
|
versionReplaced = false;
|
|
|
|
} else {
|
|
displayConfirmationMessage(`- Saved '${replacedString}' to ${path.relative(process.cwd(), filePath)}.`);
|
|
}
|
|
});
|
|
|
|
if (!versionReplaced) {
|
|
process.exit(1);
|
|
}
|
|
}
|