@chamoji/cli 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +9 -0
- package/dist/run.d.ts +9 -0
- package/dist/run.js +112 -0
- package/package.json +32 -3
- package/readme.md +94 -0
- package/README.md +0 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 chamoji contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import process from 'node:process';
|
|
3
|
+
import { run } from './run.js';
|
|
4
|
+
process.exitCode = await run(process.argv.slice(2), {
|
|
5
|
+
cwd: process.cwd(),
|
|
6
|
+
stdout: text => process.stdout.write(text),
|
|
7
|
+
stderr: text => process.stderr.write(text),
|
|
8
|
+
});
|
|
9
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/run.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type Io = {
|
|
2
|
+
cwd: string;
|
|
3
|
+
stdout: (text: string) => void;
|
|
4
|
+
stderr: (text: string) => void;
|
|
5
|
+
/** `YYYY-MM-DD` used for `--release`; defaults to today. */
|
|
6
|
+
today?: string;
|
|
7
|
+
};
|
|
8
|
+
export declare const NO_RELEASE_NEEDED = 3;
|
|
9
|
+
export declare function run(argv: string[], io: Io): Promise<number>;
|
package/dist/run.js
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { writeFile } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { buildChangelog, loadConfig, render, } from '@chamoji/core';
|
|
4
|
+
import meow from 'meow';
|
|
5
|
+
const HELP = `
|
|
6
|
+
Usage
|
|
7
|
+
$ chamoji changelog [options] Generate a changelog from git history
|
|
8
|
+
$ chamoji next-version [options] Print the next semver version
|
|
9
|
+
(exits 3 when no release is needed)
|
|
10
|
+
|
|
11
|
+
Options
|
|
12
|
+
--format markdown (default), md, json or html
|
|
13
|
+
--output Write to this file instead of stdout
|
|
14
|
+
--from Leave out this release tag and everything older
|
|
15
|
+
--to Ref to treat as the head of history (default: HEAD)
|
|
16
|
+
--release Name the unreleased section with this version
|
|
17
|
+
--config Config file (default: chamoji.config.json if present)
|
|
18
|
+
|
|
19
|
+
Examples
|
|
20
|
+
$ chamoji changelog --output CHANGELOG.md
|
|
21
|
+
$ version=$(chamoji next-version) && chamoji changelog --release "$version" -o CHANGELOG.md
|
|
22
|
+
$ chamoji changelog --format html --output changelog.html
|
|
23
|
+
`;
|
|
24
|
+
export const NO_RELEASE_NEEDED = 3;
|
|
25
|
+
const FORMATS = {
|
|
26
|
+
markdown: 'markdown',
|
|
27
|
+
md: 'markdown',
|
|
28
|
+
json: 'json',
|
|
29
|
+
html: 'html',
|
|
30
|
+
};
|
|
31
|
+
export async function run(argv, io) {
|
|
32
|
+
const cli = meow(HELP, {
|
|
33
|
+
importMeta: import.meta,
|
|
34
|
+
argv,
|
|
35
|
+
autoHelp: false,
|
|
36
|
+
autoVersion: false,
|
|
37
|
+
flags: {
|
|
38
|
+
format: { type: 'string', default: 'markdown' },
|
|
39
|
+
output: { type: 'string', shortFlag: 'o' },
|
|
40
|
+
from: { type: 'string' },
|
|
41
|
+
to: { type: 'string', default: 'HEAD' },
|
|
42
|
+
release: { type: 'string' },
|
|
43
|
+
config: { type: 'string' },
|
|
44
|
+
help: { type: 'boolean', shortFlag: 'h' },
|
|
45
|
+
version: { type: 'boolean', shortFlag: 'v' },
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
const { flags } = cli;
|
|
49
|
+
if (flags.help) {
|
|
50
|
+
io.stdout(`${cli.help}\n`);
|
|
51
|
+
return 0;
|
|
52
|
+
}
|
|
53
|
+
if (flags.version) {
|
|
54
|
+
io.stdout(`${cli.pkg.version ?? 'unknown'}\n`);
|
|
55
|
+
return 0;
|
|
56
|
+
}
|
|
57
|
+
const [command] = cli.input;
|
|
58
|
+
try {
|
|
59
|
+
switch (command) {
|
|
60
|
+
case 'changelog': {
|
|
61
|
+
const format = Object.hasOwn(FORMATS, flags.format)
|
|
62
|
+
? FORMATS[flags.format]
|
|
63
|
+
: undefined;
|
|
64
|
+
if (!format) {
|
|
65
|
+
throw new Error(`Unknown format "${flags.format}" (use markdown, json or html)`);
|
|
66
|
+
}
|
|
67
|
+
const changelog = await buildChangelog({
|
|
68
|
+
cwd: io.cwd,
|
|
69
|
+
config: await loadConfig(io.cwd, flags.config),
|
|
70
|
+
to: flags.to,
|
|
71
|
+
from: flags.from,
|
|
72
|
+
release: flags.release,
|
|
73
|
+
today: io.today,
|
|
74
|
+
});
|
|
75
|
+
const output = render(changelog, format);
|
|
76
|
+
if (flags.output) {
|
|
77
|
+
await writeFile(path.resolve(io.cwd, flags.output), output);
|
|
78
|
+
io.stderr(`Wrote ${flags.output}\n`);
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
io.stdout(output);
|
|
82
|
+
}
|
|
83
|
+
return 0;
|
|
84
|
+
}
|
|
85
|
+
case 'next-version': {
|
|
86
|
+
const { releases } = await buildChangelog({
|
|
87
|
+
cwd: io.cwd,
|
|
88
|
+
config: await loadConfig(io.cwd, flags.config),
|
|
89
|
+
to: flags.to,
|
|
90
|
+
});
|
|
91
|
+
const next = releases.find(release => release.tag === null)?.nextVersion;
|
|
92
|
+
if (!next) {
|
|
93
|
+
// Non-zero and silent on stdout, so `$(chamoji next-version)` in a
|
|
94
|
+
// release script stops instead of producing a release called "none".
|
|
95
|
+
io.stderr('chamoji: no release needed (no commits since the last tag imply a version bump)\n');
|
|
96
|
+
return NO_RELEASE_NEEDED;
|
|
97
|
+
}
|
|
98
|
+
io.stdout(`${next}\n`);
|
|
99
|
+
return 0;
|
|
100
|
+
}
|
|
101
|
+
default: {
|
|
102
|
+
io.stderr(`${command ? `chamoji: unknown command "${command}"\n` : ''}${cli.help}\n`);
|
|
103
|
+
return 2;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
io.stderr(`chamoji: ${error.message}\n`);
|
|
109
|
+
return 1;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=run.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,35 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chamoji/cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Changelogs from gitmoji and Conventional Commits",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=22"
|
|
9
|
+
},
|
|
10
|
+
"bin": {
|
|
11
|
+
"chamoji": "./dist/cli.js"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist",
|
|
15
|
+
"!dist/**/*.map"
|
|
16
|
+
],
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"meow": "^14.1.0",
|
|
19
|
+
"@chamoji/core": "0.1.0"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"changelog",
|
|
23
|
+
"gitmoji",
|
|
24
|
+
"conventional-commits",
|
|
25
|
+
"semver",
|
|
26
|
+
"release",
|
|
27
|
+
"cli"
|
|
28
|
+
],
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public"
|
|
31
|
+
},
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "tsc -p ."
|
|
34
|
+
}
|
|
6
35
|
}
|
package/readme.md
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# chamoji
|
|
2
|
+
|
|
3
|
+
Changelogs from your git history. Chamoji reads [gitmoji](https://gitmoji.dev) and [Conventional Commits](https://www.conventionalcommits.org) messages, groups them into Keep a Changelog sections, and suggests the next semver version.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install --global @chamoji/cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or run it without installing: `npx @chamoji/cli changelog`.
|
|
12
|
+
|
|
13
|
+
Requires Node.js 22 or newer and git.
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
chamoji changelog # Markdown to stdout
|
|
19
|
+
chamoji changelog --output CHANGELOG.md # write a file
|
|
20
|
+
chamoji changelog --format html --output changelog.html
|
|
21
|
+
chamoji changelog --format json # machine-readable
|
|
22
|
+
chamoji next-version # e.g. 1.3.0
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`next-version` exits with code 3 and prints nothing to stdout when no commit since the last tag implies a version bump (only docs, tests or refactors, say).
|
|
26
|
+
|
|
27
|
+
| Option | Meaning |
|
|
28
|
+
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
29
|
+
| `--format` | `markdown` (default), `md`, `json` or `html` |
|
|
30
|
+
| `--output`, `-o` | Write to a file instead of stdout |
|
|
31
|
+
| `--from <tag>` | Leave out this release and everything older |
|
|
32
|
+
| `--to <ref>` | Ref to treat as the head of history (default `HEAD`) |
|
|
33
|
+
| `--release <version>` | Name and date the unreleased section, e.g. when cutting a release. Its compare link targets the tag it will get (`v<version>` when your tags use a `v`) |
|
|
34
|
+
| `--config <file>` | Config file (default `chamoji.config.json` when present) |
|
|
35
|
+
|
|
36
|
+
Typical release flow:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
set -e
|
|
40
|
+
version=$(chamoji next-version) # stops here when nothing needs releasing
|
|
41
|
+
chamoji changelog --release "$version" --output CHANGELOG.md
|
|
42
|
+
git commit -am "🔖 Release v$version"
|
|
43
|
+
git tag "v$version"
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## How commits are read
|
|
47
|
+
|
|
48
|
+
- **Releases** are semver tags reachable from `--to` (`v1.2.0` or `1.2.0`, prereleases included), ordered by semver. Commits after the newest tag form the Unreleased section.
|
|
49
|
+
- **Merge commits** are skipped.
|
|
50
|
+
- **`git revert` commits** (`Revert "✨ Add login"`) are listed as ⏪ reverts, not by the emoji of the commit they undo.
|
|
51
|
+
- **The gitmoji** can be a character (`✨`, with or without the invisible U+FE0F variation selector) or a shortcode (`:sparkles:`). Chamoji uses the earliest one in the subject. The list comes from the official [`gitmojis`](https://www.npmjs.com/package/gitmojis) package.
|
|
52
|
+
- **These subject forms all work:**
|
|
53
|
+
- `✨ Add login`
|
|
54
|
+
- `✨ (auth): Add login`
|
|
55
|
+
- `feat(auth): ✨ Add login`
|
|
56
|
+
- `feat: Add login`. A Conventional type without an emoji maps to one: `feat`→✨, `fix`→🐛, `docs`→📝, and so on.
|
|
57
|
+
- **Breaking changes** are 💥, a `!` after any type (`feat!:`, `deps!:`), or a `BREAKING CHANGE:` footer. They are listed under Breaking changes and give a major bump.
|
|
58
|
+
- **The next version** comes from each gitmoji's `semver` field (✨ minor, 🐛 patch, …).
|
|
59
|
+
- **Commit trailers** (`Key: value` lines at the end of a message) are parsed and included in the JSON output.
|
|
60
|
+
|
|
61
|
+
## Config
|
|
62
|
+
|
|
63
|
+
`chamoji.config.json`:
|
|
64
|
+
|
|
65
|
+
```json
|
|
66
|
+
{
|
|
67
|
+
"repositoryUrl": "https://github.com/acme/widgets",
|
|
68
|
+
"groups": [
|
|
69
|
+
{"group": "added", "label": "Added", "emojis": ["sparkles", "tada"]},
|
|
70
|
+
{"group": "fixed", "label": "Fixed", "emojis": ["bug", "ambulance"]},
|
|
71
|
+
{
|
|
72
|
+
"group": "useless",
|
|
73
|
+
"label": "Useless",
|
|
74
|
+
"emojis": ["bookmark"],
|
|
75
|
+
"hidden": true
|
|
76
|
+
},
|
|
77
|
+
{"group": "misc", "label": "Miscellaneous", "emojis": []}
|
|
78
|
+
]
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
- `repositoryUrl` defaults to the `origin` remote. It is used for commit, issue (`#12`) and compare links.
|
|
83
|
+
- `groups` replaces the default sections. Emojis are gitmoji codes without colons. Commits that match no group go to `misc` when that group exists. Hidden groups are left out of the output.
|
|
84
|
+
- A plain JSON array of groups, the older format, is also accepted.
|
|
85
|
+
|
|
86
|
+
## Development
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
pnpm install
|
|
90
|
+
pnpm test # vitest
|
|
91
|
+
pnpm check # build (type-check) + prettier
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
`packages/core` is the engine: git reading, parsing, grouping and rendering, with no UI. `packages/cli` is the `chamoji` command. See `docs/research/` for where the project is heading.
|
package/README.md
DELETED