@adinsure-ops/ops-cli
Version:
Operations CLI for working with AdInsure
286 lines (285 loc) • 11.8 kB
JavaScript
import { __awaiter } from "tslib";
import { Args, Flags, ux } from '@oclif/core';
import axios from 'axios';
import chalk from 'chalk';
import fs from 'fs-extra';
import { globby } from 'globby';
import moment from 'moment';
import path from 'path';
import replaceInFile from 'replace-in-file';
import semver from 'semver';
import CommandBase from '../command.base.js';
class Version extends CommandBase {
/**
* Update NPM packages with wsrun
* @param type Release type of NPM package
* @param version Version
* @param filter Regular Expression filter for packages
* @param excludeDirs Excluded directories
* @returns {Promise<void>} Void
*/
bumpNpm(type, version, filter, excludeDirs) {
return __awaiter(this, void 0, void 0, function* () {
const currentPrereleaseDate = moment().format('YYYYMM');
const bumpedPackageName = [];
// Find all package.json files that are not in node modules
const searchPattern = ['**/package.json', '.**/package.json', '!**/node_modules'];
if (excludeDirs) {
for (const exclude of excludeDirs) {
searchPattern.push(`!${exclude}`);
}
}
const paths = yield globby(searchPattern);
for (const path of paths) {
try {
// Read the data from it
const data = yield fs.readJSON(path);
// Check if version exists
if (!data.version || !data.name) {
continue;
}
const regexp = new RegExp(filter);
if (!regexp.test(data.name)) {
continue;
}
// Increment the package version depending on the increment type
if (type === 'new') {
data.version = version;
}
else if (type === 'prerelease' && this.checkPrerelease(data.version, currentPrereleaseDate)) {
data.version = semver.inc(data.version, type, true);
}
else {
data.version = semver.inc(data.version, type, false, currentPrereleaseDate);
}
bumpedPackageName.push(data.name);
// Save file
yield fs.writeJSON(path, data, { spaces: 4, EOL: '\r\n' });
// eslint-disable-next-line @typescript-eslint/no-explicit-any
}
catch (error) {
if (error instanceof SyntaxError) {
this.warn(`Error reading package.json: ${chalk.yellow(error.message)}. Consider using '--exclude-dir'.`);
}
else {
return this.error(`Unknown error: ${chalk.red(error)}`);
}
}
}
return bumpedPackageName;
});
}
/**
* Validate args and flags
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
validateOptions(args, flags) {
if (args.type !== 'new' && flags.version) {
this.error(`${chalk.red('Version flag can only be used with new type')}`, { exit: 2 });
}
if (args.type === 'new' && !flags.version) {
this.error(`${chalk.red('Version flag is required with new type')}`, { exit: 2 });
}
if (args.type === 'new' && flags.increment) {
this.error(`${chalk.red('Increment flag not supported with new type')}`, { exit: 2 });
}
return true;
}
/**
* Update dependencies in package.json files for @adinsure packages
* @param version Version
* @param updatedPackages Array of updated packages
* @returns {void}
*/
updateDependencies(version, updatedPackages) {
return __awaiter(this, void 0, void 0, function* () {
// Find all package.json files that are not in node modules
const paths = yield globby(['**/package.json', '.**/package.json', '!**/node_modules']);
for (const path of paths) {
for (const packageName of updatedPackages) {
const re = new RegExp('"' + packageName + '": "\\d+\\.\\d+\\..*"', 'g');
yield replaceInFile({
files: path,
from: re,
to: `"${packageName}": "${version}"`,
});
}
}
});
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
insertScriptRequest(token, version, prefix) {
return __awaiter(this, void 0, void 0, function* () {
return axios.post(`https://ops.adinsure.com/api/script`, { version, prefix }, {
headers: {
Authorization: 'Bearer ' + token,
'Content-Type': 'application/json',
},
});
});
}
insertScript(token_1, version_1, prefix_1) {
return __awaiter(this, arguments, void 0, function* (token, version, prefix, skip = false) {
ux.action.start('Inserting new script version', undefined, { stdout: true });
if (skip) {
ux.action.stop(`${chalk.blue('skip')}`);
}
else if (token) {
yield this.insertScriptRequest(token, version, prefix)
.catch((error) => {
switch (error.response.status) {
case 401: {
this.error(`${chalk.red('You don\'t have permissions. If this was intentional command, please contact Operations for permissions, do git reset and rerun command.')}`);
}
default: {
this.error(`${chalk.red(error)}`);
}
}
});
ux.action.stop(`${chalk.green('done')}`);
}
else {
ux.action.stop(`${chalk.red('error')}`);
}
});
}
/**
* Check if version has valid prerelease as 'rc' or '201910' (year month)
* @param version Version
* @param month Month of release
* @returns {boolean} True or false
*/
checkPrerelease(version, month) {
const prereleaseVersion = semver.prerelease(version, true);
if (!prereleaseVersion)
return false;
if (prereleaseVersion[0].toString() === month)
return true;
if (prereleaseVersion[0].toString() === 'rc')
return true;
return false;
}
/**
* Write new version in VERSION file
* @param version The new version
* @returns {void}
*/
setVersion(version) {
return __awaiter(this, void 0, void 0, function* () {
const versionPath = path.resolve(yield this.getGitRoot(), 'VERSION');
if (!(yield fs.pathExists(versionPath))) {
throw new Error('VERSION file not found. File will not be updated.');
}
return fs.writeFileSync(versionPath, version, 'utf8');
});
}
run() {
const _super = Object.create(null, {
run: { get: () => super.run }
});
return __awaiter(this, void 0, void 0, function* () {
_super.run.call(this);
const { args, flags } = yield this.parse(Version);
const currentPrereleaseDate = moment().format('YYYYMM');
// Check if token is correct and save it for later use
let token = '';
if (!flags['no-script']) {
token = yield this.security.getToken();
}
// exit if validation fails
if (!this.validateOptions(args, flags)) {
return;
}
let newVersion = null;
if (args.type === 'new' && flags.version) {
this.log(`Using 'new' command. VERSION file ignored.`);
newVersion = semver.valid(flags.version);
}
else {
// Get current version
const fullVersion = yield this.getVersion();
this.log(`Current version is ${chalk.green(fullVersion)}`);
// Calculate new version
newVersion = semver.inc(fullVersion, args.type, true, currentPrereleaseDate);
// Force prerelease if prerelease is in same month and increment RC correctly
if (args.type === 'prerelease' && this.checkPrerelease(fullVersion, currentPrereleaseDate)) {
newVersion = semver.inc(fullVersion, args.type, true);
}
// Allow npm packages to be versioned seperately. Ultimately we want to force npm package to have same verion as VERSION file.
if (!flags.independent) {
args.type = 'new';
}
}
// Stop if version invalid
if (!newVersion) {
return this.error(`${chalk.red('Version in incorrect format')}`);
}
this.log(`Setting new version to ${chalk.green(newVersion)}`);
try {
yield this.setVersion(newVersion);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
}
catch (error) {
this.warn(`${chalk.yellow(error.message)}`);
}
ux.action.start('Bumping NPM packages', undefined, { stdout: true });
const packages = yield this.bumpNpm(args.type, newVersion, flags['only-npm-packages'], flags['exclude-dir']);
ux.action.stop(`${chalk.green('done')}`);
ux.action.start('Updating NPM dependencies in package.json files', undefined, { stdout: true });
if (flags['no-deps']) {
ux.action.stop(`${chalk.blue('skip')}`);
}
else {
yield this.updateDependencies(newVersion, packages);
ux.action.stop(`${chalk.green('done')}`);
}
yield this.insertScript(token, newVersion, flags['script-prefix'], flags['no-script']);
});
}
}
Version.description = 'release new version';
Version.usage = 'version <type> [flags]';
Version.examples = [
`$ ops version patch`,
`$ ops version prerelease -i`,
`$ ops version new -v 3.4.3`,
];
Version.args = {
type: Args.string({
description: 'Type of release',
options: ['patch', 'minor', 'major', 'prerelease', 'preminor', 'premajor', 'new'],
required: true,
}),
};
Version.flags = {
independent: Flags.boolean({
char: 'i',
description: 'increment npm packages independent of VERSION file',
default: false,
}),
version: Flags.string({
char: 'v',
description: 'force version',
}),
'script-prefix': Flags.string({
description: 'prefix for db script version creation. (default: 7.10)',
default: '7.10',
}),
'no-deps': Flags.boolean({
description: 'skip dependency update',
default: false,
}),
'no-script': Flags.boolean({
description: 'skip creation of script',
default: false,
}),
'only-npm-packages': Flags.string({
description: 'regular expression for which NPM packages to include',
default: '.*',
}),
'exclude-dir': Flags.string({
description: 'exclude directories where to search for package.json',
multiple: true,
}),
};
export default Version;