mergelog 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/LICENSE +21 -0
- package/README.md +69 -0
- package/dist/mergelog.js +171 -0
- package/package.json +43 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Morten Daniel Fornes
|
|
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/README.md
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# mergelog
|
|
2
|
+
|
|
3
|
+
> Format git commits as Markdown for merge and pull request descriptions
|
|
4
|
+
|
|
5
|
+
## Why
|
|
6
|
+
|
|
7
|
+
- Generate clean Markdown directly from `git log`
|
|
8
|
+
- Optimized for GitLab and GitHub merge request descriptions
|
|
9
|
+
- Automatically formats code-like terms with backticks
|
|
10
|
+
- Detects issue references like `#1234`
|
|
11
|
+
- Detects the repository main branch automatically
|
|
12
|
+
- Supports clipboard output
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
npm install mergelog -g
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
$ mergelog --help
|
|
24
|
+
|
|
25
|
+
mergelog
|
|
26
|
+
|
|
27
|
+
Format git commits as Markdown for merge/pull request descriptions.
|
|
28
|
+
|
|
29
|
+
Usage
|
|
30
|
+
mergelog
|
|
31
|
+
mergelog 3
|
|
32
|
+
mergelog origin/main..HEAD
|
|
33
|
+
mergelog 3 --no-code
|
|
34
|
+
mergelog 3 --clipboard
|
|
35
|
+
|
|
36
|
+
Options
|
|
37
|
+
-h, --help Show this help screen
|
|
38
|
+
-C, --no-code Disable automatic backticks
|
|
39
|
+
-c, --clipboard Copy Markdown to the clipboard
|
|
40
|
+
|
|
41
|
+
Defaults
|
|
42
|
+
No range: detected upstream/default branch .. HEAD
|
|
43
|
+
Number argument: last N commits, e.g. 3 -> -3
|
|
44
|
+
|
|
45
|
+
Output
|
|
46
|
+
### Commit subject
|
|
47
|
+
|
|
48
|
+
Commit body
|
|
49
|
+
|
|
50
|
+
Close #1234
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Example output
|
|
54
|
+
|
|
55
|
+
```md
|
|
56
|
+
### Tools: Improve startup logging
|
|
57
|
+
|
|
58
|
+
Show clearer restart and shutdown messages during local development.
|
|
59
|
+
|
|
60
|
+
### API: Deprecate timezone fields
|
|
61
|
+
|
|
62
|
+
Mark legacy timezone fields as deprecated in the API schema.
|
|
63
|
+
|
|
64
|
+
Close #1234
|
|
65
|
+
|
|
66
|
+
### Calendar: Fix week rendering for January 2027
|
|
67
|
+
|
|
68
|
+
Render calendar weeks using actual dates instead of ISO week numbers.
|
|
69
|
+
```
|
package/dist/mergelog.js
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execFileSync } from "node:child_process";
|
|
3
|
+
const colors = {
|
|
4
|
+
bold: "\x1b[1m",
|
|
5
|
+
dim: "\x1b[2m",
|
|
6
|
+
cyan: "\x1b[36m",
|
|
7
|
+
green: "\x1b[32m",
|
|
8
|
+
yellow: "\x1b[33m",
|
|
9
|
+
reset: "\x1b[0m",
|
|
10
|
+
};
|
|
11
|
+
const args = process.argv.slice(2);
|
|
12
|
+
if (args.includes("-h") || args.includes("--help")) {
|
|
13
|
+
help();
|
|
14
|
+
process.exit(0);
|
|
15
|
+
}
|
|
16
|
+
const autoCode = !args.includes("-C") && !args.includes("--no-code");
|
|
17
|
+
const clipboard = args.includes("-c") || args.includes("--clipboard");
|
|
18
|
+
const arg = args.find(x => !x.startsWith("-"));
|
|
19
|
+
main();
|
|
20
|
+
/** Format git commits as Markdown for merge/pull request descriptions. */
|
|
21
|
+
function main() {
|
|
22
|
+
const range = resolveRange(arg);
|
|
23
|
+
const log = git(["log", range, "--reverse", "--format=%x1e%B%x1f"], { showErrors: true });
|
|
24
|
+
const markdown = formatCommits(log);
|
|
25
|
+
if (clipboard) {
|
|
26
|
+
copyToClipboard(markdown);
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
console.log(markdown);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/** Resolves a user argument to a git log range. */
|
|
33
|
+
function resolveRange(arg) {
|
|
34
|
+
if (arg === undefined)
|
|
35
|
+
return `${getDefaultBaseRef()}..HEAD`;
|
|
36
|
+
if (/^\d+$/.test(arg))
|
|
37
|
+
return `-${arg}`;
|
|
38
|
+
return arg;
|
|
39
|
+
}
|
|
40
|
+
/** Formats raw git log output as Markdown sections. */
|
|
41
|
+
function formatCommits(log) {
|
|
42
|
+
return log
|
|
43
|
+
.split("\x1f")
|
|
44
|
+
.map(x => x.replace(/\x1e/g, "").trim())
|
|
45
|
+
.filter(Boolean)
|
|
46
|
+
.map(formatCommit)
|
|
47
|
+
.join("\n\n");
|
|
48
|
+
}
|
|
49
|
+
/** Formats one commit message as a Markdown heading and body. */
|
|
50
|
+
function formatCommit(commit) {
|
|
51
|
+
const [subject = "", ...bodyLines] = commit.split("\n");
|
|
52
|
+
const body = codeify(bodyLines.join("\n").trim());
|
|
53
|
+
return [`### ${subject}`, body].filter(Boolean).join("\n\n");
|
|
54
|
+
}
|
|
55
|
+
/** Adds inline-code formatting to likely code identifiers. */
|
|
56
|
+
function codeify(text) {
|
|
57
|
+
if (!autoCode)
|
|
58
|
+
return text;
|
|
59
|
+
return text
|
|
60
|
+
.split(/(`[^`]*`)/g)
|
|
61
|
+
.map(codeifyNonCodeSpan)
|
|
62
|
+
.join("");
|
|
63
|
+
}
|
|
64
|
+
/** Adds inline-code formatting outside existing backtick spans. */
|
|
65
|
+
function codeifyNonCodeSpan(part) {
|
|
66
|
+
if (part.startsWith("`") && part.endsWith("`"))
|
|
67
|
+
return part;
|
|
68
|
+
return (part
|
|
69
|
+
// Files and paths with a generic alphanumeric extension.
|
|
70
|
+
.replace(/(?<!`)\b([\w./-]*[\w-]+\.[A-Za-z0-9][A-Za-z0-9-]*)\b(?!`)/g, "`$1`")
|
|
71
|
+
// Dotfiles such as .env.
|
|
72
|
+
.replace(/(?<!`)(^|\s)(\.[A-Za-z0-9_-]+)(?!`)/g, "$1`$2`")
|
|
73
|
+
// Environment variables, optionally including simple assignments.
|
|
74
|
+
.replace(/(?<!`)\b([A-Z][A-Z0-9]+(?:_[A-Z0-9]+)+(?:=[^\s`]+)?)\b(?!`)/g, "`$1`")
|
|
75
|
+
// Package scripts or namespaced identifiers such as help:plain.
|
|
76
|
+
.replace(/(?<!`)\b([A-Za-z0-9_-]+:[A-Za-z0-9_:-]+)\b(?!`)/g, "`$1`")
|
|
77
|
+
// Short and long CLI flags such as -r and --rebuild.
|
|
78
|
+
.replace(/(^|\s)(?<!`)(-{1,2}[a-zA-Z0-9][a-zA-Z0-9-]*)(?!`)/g, "$1`$2`")
|
|
79
|
+
// Simple function or method-call expressions without nested parentheses.
|
|
80
|
+
.replace(/(?<!`)\b([A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*\([^`()\n]*\))(?!`)/g, "`$1`")
|
|
81
|
+
// Dotted identifiers such as yEditor.saveChanges.
|
|
82
|
+
.replace(/(?<!`)\b([A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)+)\b(?!`)/g, "`$1`")
|
|
83
|
+
// camelCase identifiers, while avoiding all-uppercase acronyms.
|
|
84
|
+
.replace(/(?<!`)\b([a-z][A-Za-z0-9_$]*[A-Z][\w$]*)\b(?!`)/g, "`$1`"));
|
|
85
|
+
}
|
|
86
|
+
/** Finds the best default branch to compare the current branch against. */
|
|
87
|
+
function getDefaultBaseRef() {
|
|
88
|
+
const upstream = tryGit(["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{upstream}"]);
|
|
89
|
+
if (upstream)
|
|
90
|
+
return upstream;
|
|
91
|
+
const originHead = tryGit(["symbolic-ref", "--quiet", "--short", "refs/remotes/origin/HEAD"]);
|
|
92
|
+
if (originHead)
|
|
93
|
+
return originHead;
|
|
94
|
+
for (const ref of ["origin/main", "origin/master", "main", "master"]) {
|
|
95
|
+
if (gitRefExists(ref))
|
|
96
|
+
return ref;
|
|
97
|
+
}
|
|
98
|
+
throw new Error("Could not detect base branch. Pass a range or number explicitly.");
|
|
99
|
+
}
|
|
100
|
+
/** Returns true if the given git ref exists locally. */
|
|
101
|
+
function gitRefExists(ref) {
|
|
102
|
+
return tryGit(["rev-parse", "--verify", "--quiet", ref]) !== undefined;
|
|
103
|
+
}
|
|
104
|
+
/** Runs git and returns undefined if the command fails. */
|
|
105
|
+
function tryGit(args) {
|
|
106
|
+
try {
|
|
107
|
+
return git(args);
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/** Runs a git command and returns trimmed stdout. */
|
|
114
|
+
function git(args, options = {}) {
|
|
115
|
+
return execFileSync("git", args, {
|
|
116
|
+
encoding: "utf8",
|
|
117
|
+
stdio: ["ignore", "pipe", options.showErrors ? "inherit" : "ignore"],
|
|
118
|
+
}).trim();
|
|
119
|
+
}
|
|
120
|
+
/** Copies text to the system clipboard. */
|
|
121
|
+
function copyToClipboard(text) {
|
|
122
|
+
if (process.platform === "darwin") {
|
|
123
|
+
execFileSync("pbcopy", { input: text });
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
if (process.platform === "win32") {
|
|
127
|
+
execFileSync("clip", { input: text });
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
try {
|
|
131
|
+
execFileSync("wl-copy", { input: text });
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
catch { }
|
|
135
|
+
try {
|
|
136
|
+
execFileSync("xclip", ["-selection", "clipboard"], { input: text });
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
catch { }
|
|
140
|
+
throw new Error("Could not find a clipboard command. Install wl-copy or xclip, or pipe the output manually.");
|
|
141
|
+
}
|
|
142
|
+
/** Prints command usage and options. */
|
|
143
|
+
function help() {
|
|
144
|
+
console.log(`${colors.bold}${colors.cyan}mergelog${colors.reset}
|
|
145
|
+
|
|
146
|
+
${colors.dim}Format git log output as Markdown for merge and pull request descriptions.${colors.reset}
|
|
147
|
+
|
|
148
|
+
${colors.bold}Usage${colors.reset}
|
|
149
|
+
${colors.green}mergelog${colors.reset}
|
|
150
|
+
${colors.green}mergelog${colors.reset} ${colors.yellow}3${colors.reset}
|
|
151
|
+
${colors.green}mergelog${colors.reset} ${colors.yellow}origin/main..HEAD${colors.reset}
|
|
152
|
+
${colors.green}mergelog${colors.reset} ${colors.yellow}3 --no-code${colors.reset}
|
|
153
|
+
${colors.green}mergelog${colors.reset} ${colors.yellow}3 --clipboard${colors.reset}
|
|
154
|
+
|
|
155
|
+
${colors.bold}Options${colors.reset}
|
|
156
|
+
${colors.yellow}-h, --help${colors.reset} Show this help screen
|
|
157
|
+
${colors.yellow}-C, --no-code${colors.reset} Disable automatic backticks
|
|
158
|
+
${colors.yellow}-c, --clipboard${colors.reset} Copy Markdown to the clipboard
|
|
159
|
+
|
|
160
|
+
${colors.bold}Defaults${colors.reset}
|
|
161
|
+
No range: detected upstream/default branch .. HEAD
|
|
162
|
+
Number argument: last N commits, e.g. ${colors.yellow}3${colors.reset} -> ${colors.yellow}-3${colors.reset}
|
|
163
|
+
|
|
164
|
+
${colors.bold}Output${colors.reset}
|
|
165
|
+
### Commit subject
|
|
166
|
+
|
|
167
|
+
Commit body
|
|
168
|
+
|
|
169
|
+
Close #1234
|
|
170
|
+
`);
|
|
171
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mergelog",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Format git log output as Markdown for merge and pull request descriptions",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"git",
|
|
7
|
+
"git-log",
|
|
8
|
+
"markdown",
|
|
9
|
+
"merge-request",
|
|
10
|
+
"pull-request",
|
|
11
|
+
"changelog",
|
|
12
|
+
"cli",
|
|
13
|
+
"developer-tools",
|
|
14
|
+
"productivity"
|
|
15
|
+
],
|
|
16
|
+
"homepage": "https://github.com/mortend/tstools/mergelog",
|
|
17
|
+
"bugs": {
|
|
18
|
+
"url": "https://github.com/mortend/tstools/issues"
|
|
19
|
+
},
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/mortend/tstools.git"
|
|
23
|
+
},
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"author": "mortend.co",
|
|
26
|
+
"type": "module",
|
|
27
|
+
"bin": {
|
|
28
|
+
"mergelog": "dist/mergelog.js"
|
|
29
|
+
},
|
|
30
|
+
"scripts": {
|
|
31
|
+
"prepack": "cp ../LICENSE . && tsc",
|
|
32
|
+
"postpack": "rm -f LICENSE",
|
|
33
|
+
"test": "tsx mergelog.ts"
|
|
34
|
+
},
|
|
35
|
+
"files": [
|
|
36
|
+
"dist/*",
|
|
37
|
+
"LICENSE",
|
|
38
|
+
"README.md"
|
|
39
|
+
],
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": ">=22"
|
|
42
|
+
}
|
|
43
|
+
}
|