lechnerio-git-hooks 1.3.0 → 1.3.1
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 +1 -1
- package/package.json +1 -1
- package/scripts/generate-changelog.mjs +42 -24
package/README.md
CHANGED
|
@@ -36,7 +36,7 @@ On install, the package automatically:
|
|
|
36
36
|
|
|
37
37
|
## AI release summaries
|
|
38
38
|
|
|
39
|
-
On Minor and Major version bumps, the `post-commit` hook sends the version's non-skipped commit messages to the Gemini API and stores the result as
|
|
39
|
+
On Minor and Major version bumps, the `post-commit` hook sends the version's non-skipped commit messages to the Gemini API and stores the result as `summary_en` and `summary_ger` fields (English and German) on the version entry in `data/changelog.json`. This requires `GITHOOKS_GEMINI_API_KEY` in the project's `.env` file or environment; without it the summary step is skipped silently.
|
|
40
40
|
|
|
41
41
|
## Peer dependencies (auto-installed)
|
|
42
42
|
|
package/package.json
CHANGED
|
@@ -36,7 +36,14 @@ if (existsSync("data/changelog.json")) {
|
|
|
36
36
|
try {
|
|
37
37
|
const existing = JSON.parse(readFileSync("data/changelog.json", "utf-8"))
|
|
38
38
|
for (const entry of existing) {
|
|
39
|
-
|
|
39
|
+
const summaryEn = entry.summary_en ?? entry.summary
|
|
40
|
+
const summaryGer = entry.summary_ger
|
|
41
|
+
if (typeof summaryEn === "string" || typeof summaryGer === "string") {
|
|
42
|
+
const saved = {}
|
|
43
|
+
if (typeof summaryEn === "string") saved.summary_en = summaryEn
|
|
44
|
+
if (typeof summaryGer === "string") saved.summary_ger = summaryGer
|
|
45
|
+
summaries.set(entry.version, saved)
|
|
46
|
+
}
|
|
40
47
|
for (const commit of entry.commits ?? []) {
|
|
41
48
|
skipFlags.set(commit.message, commit.skip === true)
|
|
42
49
|
}
|
|
@@ -86,10 +93,11 @@ const baseVersion = (v) => v.replace(/[a-z]+$/i, "")
|
|
|
86
93
|
const grouped = []
|
|
87
94
|
for (const entry of changelog) {
|
|
88
95
|
const base = baseVersion(entry.version)
|
|
89
|
-
const existing = grouped.find((g) => g.version === base
|
|
96
|
+
const existing = grouped.find((g) => g.version === base)
|
|
90
97
|
if (existing) {
|
|
91
98
|
existing.commits.push(...entry.commits)
|
|
92
99
|
if (entry.date < existing.date) existing.date = entry.date
|
|
100
|
+
if (entry.tag && (!existing.tag || entry.tag.replace(/^v/, "") === base)) existing.tag = entry.tag
|
|
93
101
|
} else {
|
|
94
102
|
grouped.push({ ...entry, version: base })
|
|
95
103
|
}
|
|
@@ -106,7 +114,10 @@ for (const entry of grouped) {
|
|
|
106
114
|
|
|
107
115
|
for (const entry of grouped) {
|
|
108
116
|
const saved = summaries.get(entry.version)
|
|
109
|
-
if (saved
|
|
117
|
+
if (saved) {
|
|
118
|
+
if (saved.summary_en && !entry.summary_en) entry.summary_en = saved.summary_en
|
|
119
|
+
if (saved.summary_ger && !entry.summary_ger) entry.summary_ger = saved.summary_ger
|
|
120
|
+
}
|
|
110
121
|
}
|
|
111
122
|
|
|
112
123
|
if (process.argv.includes("--summarize")) {
|
|
@@ -114,33 +125,40 @@ if (process.argv.includes("--summarize")) {
|
|
|
114
125
|
if (!apiKey) {
|
|
115
126
|
console.warn("GITHOOKS_GEMINI_API_KEY not set, skipping AI summary")
|
|
116
127
|
} else {
|
|
117
|
-
const target = grouped.find((e) => e.
|
|
118
|
-
if (target && !target.
|
|
128
|
+
const target = grouped.find((e) => e.version === baseVersion(tags[0].replace(/^v/, "")))
|
|
129
|
+
if (target && !(target.summary_en && target.summary_ger)) {
|
|
119
130
|
const messages = target.commits.filter((c) => !c.skip).map((c) => c.message)
|
|
120
131
|
if (messages.length === 0) {
|
|
121
132
|
console.warn("no commits to summarize")
|
|
122
133
|
} else {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
134
|
+
const model = loadEnvKey("GITHOOKS_GEMINI_MODEL") ?? "gemini-flash-latest"
|
|
135
|
+
const prompt = `Summarize these commit messages into a short user-facing release summary for version ${target.version}. Write 1-3 plain sentences per language, no markdown, no bullet points, no preamble. Return a JSON object with exactly two string keys: "summary_en" (English) and "summary_ger" (German).\n\n${messages.join("\n")}`
|
|
136
|
+
for (let attempt = 1; attempt <= 2; attempt++) {
|
|
137
|
+
try {
|
|
138
|
+
const res = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent`, {
|
|
139
|
+
method: "POST",
|
|
140
|
+
headers: { "Content-Type": "application/json", "x-goog-api-key": apiKey },
|
|
141
|
+
body: JSON.stringify({
|
|
142
|
+
contents: [{ parts: [{ text: prompt }] }],
|
|
143
|
+
generationConfig: { responseMimeType: "application/json", thinkingConfig: { thinkingBudget: 0 } },
|
|
144
|
+
}),
|
|
145
|
+
signal: AbortSignal.timeout(60000),
|
|
146
|
+
})
|
|
147
|
+
if (!res.ok) throw new Error(`gemini api returned ${res.status}`)
|
|
148
|
+
const data = await res.json()
|
|
149
|
+
const text = data.candidates?.[0]?.content?.parts?.[0]?.text?.trim()
|
|
150
|
+
if (!text) throw new Error("empty summary response")
|
|
151
|
+
const parsed = JSON.parse(text)
|
|
152
|
+
if (typeof parsed.summary_en !== "string" || typeof parsed.summary_ger !== "string") {
|
|
153
|
+
throw new Error("unexpected summary response shape")
|
|
154
|
+
}
|
|
155
|
+
target.summary_en = parsed.summary_en.trim()
|
|
156
|
+
target.summary_ger = parsed.summary_ger.trim()
|
|
140
157
|
console.log(`ai summary generated for ${target.version}`)
|
|
158
|
+
break
|
|
159
|
+
} catch (err) {
|
|
160
|
+
if (attempt === 2) console.warn(`ai summary failed: ${err.message}`)
|
|
141
161
|
}
|
|
142
|
-
} catch (err) {
|
|
143
|
-
console.warn(`ai summary failed: ${err.message}`)
|
|
144
162
|
}
|
|
145
163
|
}
|
|
146
164
|
}
|