slickgrid
Version:
A lightning fast JavaScript grid/spreadsheet
277 lines (239 loc) • 10.1 kB
JavaScript
const chalk = require('chalk');
const fs = require('fs-extra');
const readline = require('readline');
const path = require('path');
const semver = require('semver');
const yargs = require('yargs');
const changelogUtil = require('./changelog.js');
const gitUtils = require('./git-utils.js');
const githubRelease = require('./github-release.js');
const minify = require('./minify.js');
const npmUtils = require('./npm-utils.js');
const pkg = require('../package.json');
const TAG_PREFIX = '';
const VERSION_PREFIX = 'v';
const RELEASE_COMMIT_MSG = 'chore(release): publish version %s';
const argv = yargs.argv;
const options = argv;
const cwd = process.cwd();
const childProcess = require('./child-process.js');
/**
* Main entry, this script will execute the following steps
* 1. Ask for version bump type
* 2. Bump version in "package.json" and "slick.grid.js"
* 3. Run minify script and add the new version number to each minified file headers
* 4. Create/Update changelog.md
* 5. Update (sync) npm lock file with new version
* 6. Add all changed files to Git ("package.json", "slick.grid.js", "CHANGELOG.md" and all minified files)
* 7. Create git tag of the new release
* 8. Commit all files changed to git
* 9. Push git tags and all commits to origin
* 10. NPM publish
* 11. Create GitHub Release
*/
(async function main() {
let dryRunPrefix = options.dryRun ? '[dry-run]' : '';
let newTag;
if (options.dryRun) {
console.info(`-- ${chalk.bgMagenta('DRY-RUN')} mode --`);
}
await gitUtils.hasUncommittedChanges(options);
const repo = githubRelease.parseGitRepo();
console.log(`🚀 Let's create a new release for "${repo.owner}/${repo.name}" (currently at ${pkg.version})\n`);
// 1. choose bump type
const bumpTypes = [
{ bump: 'patch', desc: ' - Bug Fixes' },
{ bump: 'minor', desc: ' - Features & Fixes' },
{ bump: 'major', desc: ' - Breaking Change' },
{ bump: 'preminor.alpha', desc: '' },
{ bump: 'preminor.beta', desc: '' },
{ bump: 'premajor.alpha', desc: '' },
{ bump: 'premajor.beta', desc: '' },
]
const versionIncrements = [];
for (const bumpType of bumpTypes) {
versionIncrements.push({
key: bumpType.bump,
name: `${bumpType.bump} (${chalk.bold.magenta(bumpVersion(bumpType.bump, false))}) ${bumpType.desc}`,
value: bumpType.bump
});
}
versionIncrements.push(
{ key: 'o', name: 'Other, please specify...', value: 'other' },
{ key: 'q', name: 'QUIT', value: 'quit' }
);
const whichBumpType = await promptConfirmation(
`${chalk.bgMagenta(dryRunPrefix)} Select increment to apply (next version)`,
versionIncrements,
defaultIndex = versionIncrements.length - 1
);
if (whichBumpType !== 'quit') {
let newVersion = '';
if (whichBumpType === 'other') {
newVersion = await getConsoleInput('Please enter a valid version number (or type "q" to quit):');
if (newVersion === 'q') {
return;
}
} else {
newVersion = bumpVersion(whichBumpType, false);
}
newTag = `${TAG_PREFIX}${newVersion}`;
console.log(`${chalk.bgMagenta(dryRunPrefix)} Bumping new version to "${newTag}"`);
// 2. update package.json & slick.grid.js with new version
await updatePackageVersion(newVersion);
await updateSlickGridVersion(newVersion);
// 3. minify JS/CSS files
const changedFiles = await minify.execute(newVersion);
changedFiles.add(path.resolve('./package.json'));
changedFiles.add(path.resolve('./slick.grid.js'));
// 4. Create/Update changelog.md
const { location: changelogPath, newEntry: newChangelogEntry } = await changelogUtil.updateChangelog({
infile: './CHANGELOG.md',
preset: 'angular',
tagPrefix: TAG_PREFIX,
}, newVersion);
changedFiles.add(changelogPath);
// 5. Update (sync) npm lock file
await npmUtils.syncLockFile({ cwd, dryRun: options.dryRun });
changedFiles.add(path.resolve('./package-lock.json'));
// 6. "git add" all changed files
options.dryRun
? await gitUtils.gitAdd(null, { cwd, dryRun: options.dryRun })
: await gitUtils.gitAdd(Array.from(changedFiles), { cwd, dryRun: options.dryRun });
// show git changes to user so he can confirm the changes are ok
const shouldCommitChanges = await promptConfirmation(`${chalk.bgMagenta(dryRunPrefix)} Ready to tag version "${newTag}" and push commits to remote? Choose No to cancel.`);
if (shouldCommitChanges) {
// 7. create git tag of new release
await gitUtils.gitTag(newTag, { cwd, dryRun: options.dryRun });
// 8. Commit all files changed to git
await gitUtils.gitCommit(RELEASE_COMMIT_MSG.replace(/%s/g, newVersion), { cwd, dryRun: options.dryRun });
// 9. Push git tags and all commits to origin
await gitUtils.gitTagPushRemote(newTag, 'origin', { cwd, dryRun: options.dryRun });
await gitUtils.gitPushToCurrentBranch('origin', { cwd, dryRun: options.dryRun });
// 10. NPM publish
const shouldPublish = await promptConfirmation(`${chalk.bgMagenta(dryRunPrefix)} Are you ready to publish "${newTag}" to npm?`);
if (shouldPublish) {
let publishTagName;
if (whichBumpType.includes('alpha')) {
publishTagName = 'alpha';
} else if (whichBumpType.includes('beta')) {
publishTagName = 'beta';
}
await npmUtils.publishPackage(publishTagName, { cwd, dryRun: options.dryRun });
console.log(`${chalk.bgMagenta(dryRunPrefix)} 🔗 https://www.npmjs.com/package/${pkg.name} 📦 (npm)`.trim())
}
// 11. Create GitHub Release
if (options.createRelease) {
const releaseNote = { name: pkg.name, notes: newChangelogEntry };
const releaseClient = githubRelease.createReleaseClient(options.createRelease);
await githubRelease.createRelease(
releaseClient,
{ tag: newTag, releaseNote },
{ gitRemote: 'origin', execOpts: { cwd: process.cwd() } },
options.dryRun
);
}
// END
console.log(`🏁 Done (in ${Math.floor(process.uptime())}s.)`);
}
}
process.exit();
})();
/**
* Use semver to increment the version given a bump type
* @param {String} bump
* @returns {String}
*/
function bumpVersion(bump) {
const isPreReleased = bump.startsWith('pre');
const oldVersion = pkg.version;
if (isPreReleased) {
if (bump.includes('.alpha') || bump.includes('.beta')) {
const [semverBump, preReleaseType] = bump.split('.');
// const [oldSemVersion] = oldVersion.match(/^(\d\.\d\.\d)(\-)?((alpha|beta|next)\.\d)?$/) || [];
if ((preReleaseType === 'alpha' && oldVersion.includes('alpha.'))
|| (preReleaseType === 'beta' && oldVersion.includes('beta.'))
|| (preReleaseType === 'beta' && oldVersion.includes('alpha.'))
) {
return semver.inc(oldVersion, 'prerelease', preReleaseType);
}
return semver.inc(oldVersion, semverBump, true, preReleaseType);
} else {
return semver.inc(oldVersion, bump, true, 'alpha');
}
}
return semver.inc(oldVersion, bump);
}
/**
* Update version property into "package.json"
* @param {String} newVersion
*/
function updatePackageVersion(newVersion) {
pkg.version = newVersion;
if (options.dryRun) {
console.log(`${chalk.magenta('[dry-run]')}`);
}
fs.writeJsonSync(path.resolve(__dirname, '../package.json'), pkg, { spaces: 2 });
console.log('-- updating "package.json" --');
console.log(` "version": "${pkg.version}"`);
console.log('---------------------------\n');
}
/**
* Update version property into "slick.grid.json"
* @param {String} newVersion
*/
function updateSlickGridVersion(newVersion) {
const slickGridJs = fs.readFileSync(path.resolve(__dirname, '../slick.grid.js'), { encoding: 'utf8', flag: 'r' });
const updatedSlickGridJs = slickGridJs.replace(/(SlickGrid v)([0-9-.alpha|beta]*)/gi, `$1${newVersion}`);
if (options.dryRun) {
console.log(`${chalk.magenta('[dry-run]')}`);
}
fs.writeFileSync(path.resolve(__dirname, '../slick.grid.js'), updatedSlickGridJs);
console.log('-- updating "slick.grid.js" --');
console.log(` SlickGrid ${VERSION_PREFIX}${newVersion}`)
console.log('----------------------------\n');
}
/**
* Get console input using the 'readLine' lib
* @param {String} promptText - prompt question message
* @returns {Promise<String>} - the entered input
*/
function getConsoleInput(promptText) {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
return new Promise(resolve => rl.question(promptText, ans => {
rl.close();
resolve(ans);
}))
}
/**
* Simple function to select an item from a passed list of choices
* @param {String} message - prompt question message
* @param {Array<String>} [choices] - prompt list of choices, defaults to Yes/No
* @param {Number} [defaultIndex]
* @returns {Promise<String>} - value property of selected choice
* @returns
*/
async function promptConfirmation(message, choices, defaultIndex) {
if (!choices) {
choices = [
{ key: 'y', name: 'Yes', value: true },
{ key: 'n', name: 'No', value: false },
];
defaultIndex = 0;
}
// display propmpt message and choices
console.log(message.trim());
for (var i=0; i<choices.length; i++) {
console.log(' ' + (i+1) + ' - ' + choices[i].name);
}
// get and process input
const input = await getConsoleInput("Enter value " + " (default " + (defaultIndex + 1) + ') ');
var index = !isNaN(input) && !isNaN(parseFloat(input)) ? +input - 1 : defaultIndex;
if (index < 0 || index >= choices.length) {
throw Error('The input ' + input + ' could not be matched to a selection');
}
return choices[index].value;
}