@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 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,10 @@
1
+ const { increment } = require('./increment');
2
+ const { release } = require('./release');
3
+ const { develop } = require('./develop');
4
+
5
+
6
+ module.exports = {
7
+ develop,
8
+ increment,
9
+ release,
10
+ }
@@ -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();