Skip to content

Commit 656ead8

Browse files
authored
Feat: write or update changelog, tag new version, release new version (#2)
* Feat: delete node_modules and install node_modules with yarn or npm * Feat: generate all possibile semver versions from a single version * Feat: get all commits since the last tag or creation of repo * Feat: get the latest version from this repo * Feat: check if a repo already have remotes * Feat: create or update an CHANGELOG.md * Feat: task update changelog * Feat: task delete node_modules and reinstall * Feat: tasks dir is a repo, has added changes and it has new commits * Feat: task add, commit, tag and release * Feat: task npm test * Feat: update package.json * Feat: combine all subtasks in main tasks * Feat: inquirer questions * Feat: add cli entry point * Test: add all tests and fixtures * Fix: isGit on current working dir * Refactor: change error handling * Fix: change using current working dir * Test: update tests * Chore: allow _ for tests * Refactor: update tasks * Chore: update dependencies * Chore: add prepush hook * Refactor: update git checks * Test: update tests and fixtures * Refactor: update to shellSync * Fix: remove isGit and update errors * Fix: remove isGit and update errors * Fix: update errors * Fix: update errors * Refactor: remove duplicate git tasks * Refactor: remove git tasks * Chore: update nyc * Fix: remove console.log * Docs: add new gif for readme * Fix: make sure to push all fixtures as they are needed for the tests * Chore: update dependencies * Refactor: remove getCommits, getRemotes -> new dependencies * Refactor: update to use new packages * Docs: update readme * Docs: update tasks and fix typos
1 parent b732894 commit 656ead8

File tree

65 files changed

+1501
-169
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

65 files changed

+1501
-169
lines changed

.eslintrc

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,17 @@
22
"extends": "airbnb-base",
33
"plugins": [
44
"import"
5-
]
5+
],
6+
"rules": {
7+
"arrow-parens": 0,
8+
"no-underscore-dangle": [
9+
"error",
10+
{
11+
"allow": [
12+
"_tasks"
13+
],
14+
"allowAfterThis": false
15+
}
16+
]
17+
}
618
}

README.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,51 @@
11
# semantic-git-release-cli
2+
3+
[![Build Status](https://travis-ci.org/aichbauer/node-semantic-git-release-cli.svg?branch=master)](https://travis-ci.org/aichbauer/node-semantic-git-release-cli)
4+
[![Build status](https://ci.appveyor.com/api/projects/status/7kedayu8diw41day?svg=true)](https://ci.appveyor.com/project/rudolfsonjunior/node-semantic-git-release-cli)
5+
[![Coverage Status](https://coveralls.io/repos/github/aichbauer/node-semantic-git-release-cli/badge.svg?branch=master)](https://coveralls.io/github/aichbauer/node-semantic-git-release-cli?branch=master)
6+
7+
> A CLI for semantic releases. Writes a changelog and tags the new version.
8+
9+
<img src="https://raw.githubusercontent.com/aichbauer/node-semantic-git-release-cli/master/media/screenshot.gif">
10+
11+
## Why?
12+
13+
Many projects need versioning. It is always the same: testing, writing the changelog, updating the version, tagging the commit, and finally releasing the new version.
14+
15+
`sgr` will take care of all of them, so you can focus on the more important stuff: **code**
16+
17+
## Installation
18+
19+
```sh
20+
$ npm i -g semantic-git-release-cli
21+
```
22+
23+
or
24+
25+
```sh
26+
$ yarn add global semantic-git-release-cli
27+
```
28+
29+
## Usage
30+
31+
Forget the times when you had to manually write changelogs, update versions, tag commits. Now just type:
32+
33+
```sh
34+
$ sgr
35+
```
36+
37+
or if you already have an alias for sgr, use following instead:
38+
39+
```sh
40+
$ semantic-git-release
41+
```
42+
43+
## Tasks
44+
45+
So there are a few tasks `semantic-git-release-cli` will do for you:
46+
47+
- removes and reinstalls `node_modules` (prefers `yarn` by default)
48+
- runs tests by calling `npm test`
49+
- updates the `version` in `package.json`
50+
- creates or updates the `CHANGELOG.md`
51+
- commits and tags the new `version`

lib/cli.js

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
#!/usr/bin/env node
2+
3+
import inquirer from 'inquirer';
4+
import isAdded from 'is-git-added';
5+
import isGit from 'is-git-repository';
6+
import getGitRemotes from 'get-git-remotes';
7+
import getCommitRange from 'git-commit-range';
8+
import taggedCommit from 'tagged-git-commit';
9+
import updateNotifier from 'update-notifier';
10+
import yargs from 'yargs';
11+
12+
import generateVersions from './helpers/generateVersions';
13+
import getLatestVersion from './helpers/getLatestVersion';
14+
import pkg from '../package.json';
15+
import questions from './questions';
16+
import tasks from './tasks';
17+
18+
const argv = yargs
19+
.usage('Usage: $0')
20+
.alias('v', 'version')
21+
.describe('v', 'Version number')
22+
.help('h')
23+
.alias('h', 'help')
24+
.argv;
25+
26+
updateNotifier({ pkg }).notify();
27+
28+
const cli = () => {
29+
const cwd = process.cwd();
30+
const latestTaggedCommit = taggedCommit({ path: cwd });
31+
const commits = getCommitRange({ path: cwd, from: latestTaggedCommit });
32+
const latestVersion = getLatestVersion();
33+
const newVersions = generateVersions(latestVersion);
34+
const questionsList = questions(newVersions);
35+
36+
if (!isGit(cwd)) {
37+
return console.warn('Error: this is not a git repository... make sure you are in the right directory');
38+
} else if (!isAdded(cwd) && commits.length === 0) {
39+
return console.warn('Error: no changes... try to git add <files>');
40+
} else if (commits.length === 0) {
41+
return console.warn('Error: no commits yet... try to git commit -m <message>');
42+
} else if (!getGitRemotes(cwd)) {
43+
return console.warn('Error: it seems you do not have a remote repository set... try to git remote add origin <remote-url>');
44+
}
45+
46+
return inquirer
47+
.prompt(questionsList)
48+
.then((answers) => {
49+
if (answers.ownVersion) {
50+
return tasks(commits, answers.ownVersion)
51+
.run()
52+
.catch(() => console.warn('Error: whoops, try to solve the problem mentioned above...'));
53+
}
54+
55+
return tasks(commits, answers.version)
56+
.run()
57+
.catch(() => console.warn('Error: whoops, try to solve the problem mentioned above...'));
58+
})
59+
.catch((err) => console.warn(err));
60+
};
61+
62+
if (argv.v) {
63+
console.info(`v${pkg.version}`);
64+
} else {
65+
cli();
66+
}

lib/helpers/cleanNodeModules.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import execa from 'execa';
2+
import fs from 'fs-extra';
3+
import path from 'path';
4+
5+
const deleteNodeModules = () => {
6+
const cwd = process.cwd();
7+
8+
return fs.removeSync(path.join(cwd, 'node_modules'));
9+
};
10+
11+
const installNodeModules = (hasYarn) => {
12+
if (hasYarn) {
13+
return execa.shellSync('yarn');
14+
}
15+
16+
return execa.shellSync('npm i');
17+
};
18+
19+
export {
20+
deleteNodeModules,
21+
installNodeModules,
22+
};

lib/helpers/generateVersions.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import semver from 'semver';
2+
3+
const versions = (latestVersion) => {
4+
const patch = semver.inc(latestVersion, 'patch');
5+
const minor = semver.inc(latestVersion, 'minor');
6+
const major = semver.inc(latestVersion, 'major');
7+
const prereleaseAlpha = semver.inc(latestVersion, 'prerelease', 'alpha');
8+
const prereleaseBeta = semver.inc(latestVersion, 'prerelease', 'beta');
9+
const prereleaseRC = semver.inc(latestVersion, 'prerelease', 'rc');
10+
11+
return [
12+
patch,
13+
minor,
14+
major,
15+
prereleaseAlpha,
16+
prereleaseBeta,
17+
prereleaseRC,
18+
'Other (specify)',
19+
];
20+
};
21+
22+
export default versions;

lib/helpers/getLatestVersion.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import taggedCommits from 'tagged-git-commit';
2+
import path from 'path';
3+
import fs from 'fs-extra';
4+
5+
const getLatestVersion = () => {
6+
const cwd = process.cwd();
7+
const exitstsPkg = fs.existsSync(path.join(cwd, 'package.json'));
8+
9+
if (!exitstsPkg) {
10+
return '';
11+
}
12+
13+
const pkg = fs.readJsonSync(path.join(cwd, 'package.json'));
14+
const latestPkgVersion = pkg.version;
15+
const latestTag = taggedCommits({ path: cwd });
16+
17+
let latestVersion;
18+
let latestTaggedVersion;
19+
20+
if (latestTag[0]) {
21+
latestTaggedVersion = latestTag[0].split('refs/tags/')[1];
22+
} else {
23+
latestTaggedVersion = undefined;
24+
}
25+
26+
if (`v${latestPkgVersion}` !== `${latestTaggedVersion}`
27+
&& latestTaggedVersion === undefined) {
28+
latestVersion = `v${latestPkgVersion}`;
29+
} else if (`v${latestPkgVersion}` === `${latestTaggedVersion}`) {
30+
latestVersion = `v${latestPkgVersion}`;
31+
} else {
32+
latestVersion = '';
33+
}
34+
35+
return latestVersion;
36+
};
37+
38+
export default getLatestVersion;

lib/helpers/updateChangelog.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import fs from 'fs';
2+
import gitCommitInfo from 'git-commit-info';
3+
import moment from 'moment';
4+
import path from 'path';
5+
6+
const writeToFile = (commits = [], version, exists) => {
7+
const cwd = process.cwd();
8+
9+
let changelogData;
10+
11+
if (exists) {
12+
const changelog = fs.readFileSync(path.join(cwd, 'CHANGELOG.md'), 'utf8');
13+
changelogData = `\n${changelog}`;
14+
} else {
15+
changelogData = '';
16+
}
17+
18+
const stream = fs.createWriteStream(path.join(cwd, 'CHANGELOG.md'), 'utf8');
19+
20+
stream.write(`${version} - ${moment().format('MMMM, DD YYYY')}\n\n`);
21+
22+
commits.forEach((commithash) => {
23+
const commitInfo = gitCommitInfo({ commit: commithash, cwd });
24+
25+
stream.write(`* ${commitInfo.shortHash} ${commitInfo.message} (${commitInfo.author})\n`);
26+
});
27+
28+
stream.write(changelogData);
29+
30+
return stream.end();
31+
};
32+
33+
const updateChangelog = (commits = [], version) => {
34+
try {
35+
const cwd = process.cwd();
36+
37+
const exists = fs.existsSync(path.join(cwd, 'CHANGELOG.md'));
38+
39+
return writeToFile(commits, version, exists);
40+
} catch (err) {
41+
return false;
42+
}
43+
};
44+
45+
export default updateChangelog;

lib/questions.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
const questions = (versions) => {
2+
const questionsList = [
3+
{
4+
type: 'list',
5+
name: 'version',
6+
message: 'Select semver increment or specify new version:',
7+
choices: versions,
8+
},
9+
{
10+
type: 'input',
11+
name: 'ownVersion',
12+
message: 'Version',
13+
when: answers => answers.version === versions[versions.length - 1],
14+
},
15+
];
16+
17+
return questionsList;
18+
};
19+
20+
export default questions;

lib/sub-tasks/changelog.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import Listr from 'listr';
2+
3+
import updateChangelog from '../helpers/updateChangelog';
4+
5+
const changelogTasks = (commits, version) => (
6+
new Listr([
7+
{
8+
title: 'generate or update changelog',
9+
task: () => {
10+
const updated = updateChangelog(commits, version);
11+
12+
if (updated === 'could not write') {
13+
throw new Error('could not write CHANGELOG.md... make sure you have write and read access to it');
14+
}
15+
},
16+
},
17+
])
18+
);
19+
20+
export default changelogTasks;

lib/sub-tasks/cleanup.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import Listr from 'listr';
2+
import hasYarn from 'has-yarn';
3+
import fs from 'fs-extra';
4+
import path from 'path';
5+
import {
6+
deleteNodeModules,
7+
installNodeModules,
8+
} from '../helpers/cleanNodeModules';
9+
10+
const cleanup = () => {
11+
const cwd = process.cwd();
12+
return new Listr([
13+
{
14+
title: 'check package.json',
15+
task: () => (
16+
fs
17+
.pathExists(path.join(cwd, 'package.json'))
18+
.then((exists) => {
19+
if (!exists) {
20+
throw new Error('Error: no package.json found... make sure you are in the correct directory');
21+
}
22+
})
23+
),
24+
},
25+
{
26+
title: 'delete node_modules',
27+
task: () => deleteNodeModules(),
28+
},
29+
{
30+
title: 'install dependencies',
31+
task: () => installNodeModules(hasYarn),
32+
},
33+
]);
34+
};
35+
36+
export default cleanup;

lib/sub-tasks/release.js

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import execa from 'execa';
2+
import Listr from 'listr';
3+
4+
const releaseTasks = (version) => (
5+
new Listr([
6+
{
7+
title: 'git add CHANGELOG.md and package.json',
8+
task: /* istanbul ignore next */ () => (
9+
execa
10+
.shell('git add .')
11+
.catch(() => {
12+
throw new Error('Error: could not git add <files>');
13+
})
14+
),
15+
},
16+
{
17+
title: 'git commit CHANGELOG.md and package.json',
18+
task: /* istanbul ignore next */ () => (
19+
execa
20+
.shell(`git commit -m "${version}"`)
21+
.catch(() => {
22+
throw new Error('Error: could not git commit -m <message>');
23+
})
24+
),
25+
},
26+
{
27+
title: `git tag -a v${version} -m ${version}`,
28+
task: /* istanbul ignore next */ () => (
29+
execa
30+
.shell(`git tag -a v${version} -m ${version}`)
31+
.catch(() => {
32+
throw new Error(`Error: could not tag with v${version}... make sure this version is not tagged already`);
33+
})
34+
),
35+
},
36+
{
37+
title: `release new version v${version}`,
38+
task: /* istanbul ignore next */ () => (
39+
execa
40+
.shell('git push --follow-tags')
41+
.catch(() => {
42+
throw new Error('Error: could not push to remote repository... make sure you have access to it');
43+
})
44+
),
45+
},
46+
])
47+
);
48+
49+
export default releaseTasks;

0 commit comments

Comments
 (0)