@vida-global/release 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +66 -0
- package/lib/release/develop.js +27 -0
- package/lib/release/git.js +86 -0
- package/lib/release/increment.js +56 -0
- package/lib/release/index.js +10 -0
- package/lib/release/release.js +30 -0
- package/lib/release/utils.js +44 -0
- package/package.json +15 -0
- package/scripts/release.js +88 -0
package/README.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Configuration
|
|
2
|
+
Add the following scripts to your package.json
|
|
3
|
+
```
|
|
4
|
+
"release": "node node_modules/@vida-global/release/scripts/release.js",
|
|
5
|
+
"develop": "node node_modules/@vida-global/release/scripts/release.js develop"
|
|
6
|
+
```
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
# Release Management
|
|
10
|
+
The release script provides serves two purposes, version management and production release management.
|
|
11
|
+
|
|
12
|
+
## Version Management
|
|
13
|
+
```
|
|
14
|
+
npm run release increment <major,minor,point>
|
|
15
|
+
```
|
|
16
|
+
Running this command will find the current `main` or `master` branch on your remote repo,
|
|
17
|
+
search for the highest remote version, create a new release branch with the incremented version,
|
|
18
|
+
and update the version in `package.json`
|
|
19
|
+
|
|
20
|
+
## Production Release Management
|
|
21
|
+
```
|
|
22
|
+
npm run release production [version]
|
|
23
|
+
```
|
|
24
|
+
Running this command will find the current highest version on the remote repo, or the one specified
|
|
25
|
+
by the version option, and replace `release/production` with that branch.
|
|
26
|
+
|
|
27
|
+
## Example
|
|
28
|
+
Given a current highest version of `release/1.5.3`
|
|
29
|
+
|
|
30
|
+
#### Increment point version
|
|
31
|
+
```
|
|
32
|
+
npm run release increment point
|
|
33
|
+
```
|
|
34
|
+
This will create a new branch of `origin/master` named `release/1.6.0` and will update `package.json`
|
|
35
|
+
to use `1.6.0` as the "version."
|
|
36
|
+
|
|
37
|
+
#### Release
|
|
38
|
+
```
|
|
39
|
+
npm run release production
|
|
40
|
+
```
|
|
41
|
+
This will replace `release/production` with a branch of `release/1.6.0`
|
|
42
|
+
|
|
43
|
+
#### Rollback a release
|
|
44
|
+
```
|
|
45
|
+
npm run release production
|
|
46
|
+
```
|
|
47
|
+
This will replace `release/production` with a branch of `release/1.6.0`
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# Development
|
|
51
|
+
```
|
|
52
|
+
npm run develop -- this is my new branch -i123
|
|
53
|
+
```
|
|
54
|
+
This will offer to create a new branch `feature/123/<YOUR_INITIALS>/this-is-my-new-branch`
|
|
55
|
+
|
|
56
|
+
## Options
|
|
57
|
+
#### -i --issue (required)
|
|
58
|
+
Provide the issue number this branch is associated with
|
|
59
|
+
|
|
60
|
+
#### -t --type (optional)
|
|
61
|
+
By default, the script creates a branch of type `feature`. Using the `-t` option, you can create a
|
|
62
|
+
branch of type `bugfix`, `chore`, `feature`, `hotfix`, or `refactor`
|
|
63
|
+
|
|
64
|
+
#### -s --source (optional)
|
|
65
|
+
By default, this script creates a branch of `origin/master`. By specifying the `-s` option and
|
|
66
|
+
providing a branch name, you will create a branch from the provided source.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const Git = require('./git');
|
|
2
|
+
const utils = require('./utils');
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
async function develop(branchType, issueNumber, description, sourceBranch) {
|
|
6
|
+
const initials = devInitials();
|
|
7
|
+
const parsedDescription = description.replace(/\s+/g, '-').toLowerCase().replace(/[^a-z0-9-]/g, '');;
|
|
8
|
+
|
|
9
|
+
const branchName = `${branchType}/${issueNumber}/${initials}/${parsedDescription}`;
|
|
10
|
+
|
|
11
|
+
const question = `Do you want to create branch ${branchName}?`;
|
|
12
|
+
const _confirm = await utils.confirmContinue(question);
|
|
13
|
+
if (!_confirm) return;
|
|
14
|
+
|
|
15
|
+
Git.createBranch(branchName, sourceBranch);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
function devInitials() {
|
|
20
|
+
const name = Git.getCurrentUser()
|
|
21
|
+
return name.split(' ').map(piece => piece[0]).join('').toLowerCase();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
module.exports = {
|
|
26
|
+
develop
|
|
27
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
const { execSync } = require('child_process');
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
function createBranch(branchName, branchToCopy=null) {
|
|
5
|
+
if (!branchToCopy) branchToCopy = getPrimaryBranch();
|
|
6
|
+
|
|
7
|
+
executeShellCommand(`git branch ${branchName} ${branchToCopy}`);
|
|
8
|
+
checkout(branchName)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
function getPrimaryBranch() {
|
|
13
|
+
const allBranches = branches();
|
|
14
|
+
if (allBranches.includes('origin/master')) return 'origin/master';
|
|
15
|
+
if (allBranches.includes('origin/main')) return 'origin/main';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
function checkout(branchName) {
|
|
20
|
+
executeShellCommand(`git checkout ${branchName}`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
function pull() {
|
|
25
|
+
executeShellCommand('git pull --prune');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
function push() {
|
|
30
|
+
const currBranch = getCurrentBranch();
|
|
31
|
+
executeShellCommand(`git push -u origin ${currBranch}`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
function getCurrentBranch() {
|
|
36
|
+
const allBranches = branches(false);
|
|
37
|
+
return allBranches.find(b => /^\s*\*\s+/.test(b)).replace('*', '').trim();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
function forceRemotePush(sourceBranch, destBranch) {
|
|
42
|
+
executeShellCommand(`git push origin origin/${sourceBranch}:${destBranch} --force`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
function add(file) {
|
|
47
|
+
executeShellCommand(`git add ${file}`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
function commit(msg) {
|
|
52
|
+
executeShellCommand(`git commit -m '${msg}'`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
function branches(remote=true) {
|
|
57
|
+
let cmd = 'git branch';
|
|
58
|
+
if (remote) cmd = `${cmd} -r`;
|
|
59
|
+
const branches = executeShellCommand(cmd);
|
|
60
|
+
return branches.toString().split("\n").map(b => b.trim());
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
function getCurrentUser() {
|
|
65
|
+
return executeShellCommand(`git config user.name`).toString();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
function executeShellCommand(command) {
|
|
70
|
+
return execSync(command);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
module.exports = {
|
|
75
|
+
add,
|
|
76
|
+
branches,
|
|
77
|
+
checkout,
|
|
78
|
+
commit,
|
|
79
|
+
createBranch,
|
|
80
|
+
forceRemotePush,
|
|
81
|
+
getCurrentBranch,
|
|
82
|
+
getCurrentUser,
|
|
83
|
+
getPrimaryBranch,
|
|
84
|
+
pull,
|
|
85
|
+
push
|
|
86
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const Git = require('./git');
|
|
3
|
+
const utils = require('./utils');
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
const packageJsonPath = `${process.cwd()}/package.json`;
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
async function increment(type) {
|
|
10
|
+
Git.pull();
|
|
11
|
+
const currVersion = utils.getCurrentVersion();
|
|
12
|
+
const newVersion = getNewVersion(currVersion, type);
|
|
13
|
+
|
|
14
|
+
const _confirm = await utils.confirmContinue(`Do you want to create version ${newVersion}?`);
|
|
15
|
+
if (!_confirm) return;
|
|
16
|
+
|
|
17
|
+
Git.createBranch(`release/${newVersion}`);
|
|
18
|
+
updatePackageVersion(newVersion)
|
|
19
|
+
Git.push();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
function getNewVersion(currVersion, type) {
|
|
24
|
+
const newVersion = currVersion.map(v => parseInt(v));
|
|
25
|
+
switch(type) {
|
|
26
|
+
case 'major':
|
|
27
|
+
newVersion[0] += 1;
|
|
28
|
+
newVersion[1] = 0;
|
|
29
|
+
newVersion[2] = 0;
|
|
30
|
+
break;
|
|
31
|
+
case 'minor':
|
|
32
|
+
newVersion[1] += 1;
|
|
33
|
+
newVersion[2] = 0;
|
|
34
|
+
break;
|
|
35
|
+
case 'point':
|
|
36
|
+
newVersion[2] += 1;
|
|
37
|
+
break;
|
|
38
|
+
default:
|
|
39
|
+
throw new Error('Invalid increment type');
|
|
40
|
+
}
|
|
41
|
+
return newVersion.join('.');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
function updatePackageVersion(newVersion) {
|
|
46
|
+
let packageJson = fs.readFileSync(packageJsonPath, {encoding: 'utf8'});
|
|
47
|
+
packageJson = packageJson.replace(/"version":\s*"\d+\.\d+\.\d+"/, `"version": "${newVersion}"`);
|
|
48
|
+
fs.writeFileSync(packageJsonPath, packageJson);
|
|
49
|
+
Git.add(packageJsonPath);
|
|
50
|
+
Git.commit(`build: incrementing build to ${newVersion}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
module.exports = {
|
|
55
|
+
increment
|
|
56
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const Git = require('./git');
|
|
2
|
+
const utils = require('./utils');
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
async function release(env, versionToRelease=null) {
|
|
6
|
+
Git.pull();
|
|
7
|
+
|
|
8
|
+
if (versionToRelease) {
|
|
9
|
+
const branchName = `origin/release/${versionToRelease}`;
|
|
10
|
+
const allBranches = Git.branches();
|
|
11
|
+
if (!allBranches.includes(branchName)) {
|
|
12
|
+
throw new Error(`release/${versionToRelease} does not exist`);
|
|
13
|
+
}
|
|
14
|
+
} else {
|
|
15
|
+
versionToRelease = utils.getCurrentVersion().join('.');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const question = `Do you want to release version ${versionToRelease} to ${env}?`;
|
|
19
|
+
const _confirm = await utils.confirmContinue(question);
|
|
20
|
+
if (!_confirm) return;
|
|
21
|
+
|
|
22
|
+
const sourceBranch = `release/${versionToRelease}`;
|
|
23
|
+
const releaseBranch = `release/${env}`;
|
|
24
|
+
Git.forceRemotePush(sourceBranch, releaseBranch);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
module.exports = {
|
|
29
|
+
release
|
|
30
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
const Git = require('./git');
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
function getCurrentVersion() {
|
|
5
|
+
const branches = Git.branches();
|
|
6
|
+
const releases = branches.filter(branchName => /release\/\d+\.\d+\.\d+/.test(branchName));
|
|
7
|
+
const versions = releases.map(branchName => {
|
|
8
|
+
return branchName.replace('origin/release/', '').split('.').map(n => parseInt(n));
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
versions.sort((v1,v2) => {
|
|
12
|
+
if (v1[0] != v2[0]) return v2[0] - v1[0];
|
|
13
|
+
if (v1[1] != v2[1]) return v2[1] - v1[1];
|
|
14
|
+
return v2[2] - v1[2];
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
return versions[0] || [1,0,0];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
async function confirmContinue(question) {
|
|
22
|
+
return await getInput(question, ['y','n']) == 'y';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
async function getInput(question, options) {
|
|
27
|
+
const readline = require('node:readline/promises').createInterface({
|
|
28
|
+
input: process.stdin,
|
|
29
|
+
output: process.stdout
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const response = await readline.question(`${question} [${options.join(',')}] `);
|
|
33
|
+
readline.close();
|
|
34
|
+
|
|
35
|
+
if (options.includes(response)) return response;
|
|
36
|
+
return getInput(question, options);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
module.exports = {
|
|
41
|
+
confirmContinue,
|
|
42
|
+
getCurrentVersion,
|
|
43
|
+
getInput,
|
|
44
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vida-global/release",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Tools for releasing Vida tools",
|
|
5
|
+
"author": "",
|
|
6
|
+
"license": "ISC",
|
|
7
|
+
"main": "index.js",
|
|
8
|
+
"dependencies": {
|
|
9
|
+
"commander": "^13.1.0",
|
|
10
|
+
"pino": "^9.6.0",
|
|
11
|
+
"pino-pretty": "^13.0.0"
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
const commander = require('commander');
|
|
2
|
+
const pino = require('pino');
|
|
3
|
+
const Release = require('../lib/release');
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
const logger = pino({
|
|
7
|
+
transport: {
|
|
8
|
+
target: 'pino-pretty',
|
|
9
|
+
options: {
|
|
10
|
+
colorize: true,
|
|
11
|
+
ignore: 'pid,hostname',
|
|
12
|
+
translateTime: 'SYS:standard',
|
|
13
|
+
messageFormat: '{msg}',
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
function validateIssueNumber(val) {
|
|
20
|
+
const issueNumber = parseInt(val, 10);
|
|
21
|
+
if (isNaN(issueNumber)) {
|
|
22
|
+
throw new commander.InvalidArgumentError('Invalid issue number');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return issueNumber;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
function validateProjectType(val) {
|
|
30
|
+
const types = ['bugfix', 'chore', 'feature', 'hotfix', 'refactor'];
|
|
31
|
+
if (!types.includes(val)) {
|
|
32
|
+
throw new commander.InvalidArgumentError(`must be one of: ${types.join(', ')}`);
|
|
33
|
+
}
|
|
34
|
+
return val;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
function validateVersionType(val) {
|
|
39
|
+
const types = ['major', 'minor', 'point'];
|
|
40
|
+
if (!types.includes(val)) {
|
|
41
|
+
throw new commander.InvalidArgumentError(`must be one of: ${types.join(', ')}`);
|
|
42
|
+
}
|
|
43
|
+
return val;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
const program = new commander.Command();
|
|
48
|
+
program.name('Vida Release')
|
|
49
|
+
.description('A CLI for generating vida releases')
|
|
50
|
+
.version('1.0.0');
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
program.command('increment')
|
|
54
|
+
.argument('<type>', 'version number to increment', validateVersionType)
|
|
55
|
+
.action(async (type) => {
|
|
56
|
+
try {
|
|
57
|
+
await Release.increment(type);
|
|
58
|
+
} catch(err) {
|
|
59
|
+
logger.error(err.message);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
program.command('production')
|
|
65
|
+
.argument('[version]', 'optional version release')
|
|
66
|
+
.action(async (version) => {
|
|
67
|
+
try {
|
|
68
|
+
await Release.release('production', version);
|
|
69
|
+
} catch(err) {
|
|
70
|
+
logger.error(err.message);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
program.command('develop')
|
|
76
|
+
.requiredOption('-i, --issue <issueNumber>', 'Must provide an issue number', validateIssueNumber)
|
|
77
|
+
.option('-t, --type [projectType]', 'Project type', validateProjectType, 'feature')
|
|
78
|
+
.option('-s, --source [sourceBranch]', 'Branch to copy')
|
|
79
|
+
.argument('<description...>', 'a description of your project')
|
|
80
|
+
.action(async (description, { issue, type, source }) => {
|
|
81
|
+
try {
|
|
82
|
+
await Release.develop(type, issue, description.join(' '), source);
|
|
83
|
+
} catch(err) {
|
|
84
|
+
logger.error(err.message);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
program.parse();
|