@codefresh-io/gitops-release 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/README.md +397 -0
- package/dist/commands/cherry-pick.d.ts +14 -0
- package/dist/commands/cherry-pick.d.ts.map +1 -0
- package/dist/commands/cherry-pick.js +337 -0
- package/dist/commands/cherry-pick.js.map +1 -0
- package/dist/commands/cherry-pick.test.d.ts +2 -0
- package/dist/commands/cherry-pick.test.d.ts.map +1 -0
- package/dist/commands/cherry-pick.test.js +377 -0
- package/dist/commands/cherry-pick.test.js.map +1 -0
- package/dist/commands/completion.d.ts +11 -0
- package/dist/commands/completion.d.ts.map +1 -0
- package/dist/commands/completion.js +195 -0
- package/dist/commands/completion.js.map +1 -0
- package/dist/commands/completion.test.d.ts +2 -0
- package/dist/commands/completion.test.d.ts.map +1 -0
- package/dist/commands/completion.test.js +142 -0
- package/dist/commands/completion.test.js.map +1 -0
- package/dist/commands/create.d.ts +15 -0
- package/dist/commands/create.d.ts.map +1 -0
- package/dist/commands/create.js +107 -0
- package/dist/commands/create.js.map +1 -0
- package/dist/commands/create.test.d.ts +2 -0
- package/dist/commands/create.test.d.ts.map +1 -0
- package/dist/commands/create.test.js +274 -0
- package/dist/commands/create.test.js.map +1 -0
- package/dist/commands/list.d.ts +14 -0
- package/dist/commands/list.d.ts.map +1 -0
- package/dist/commands/list.js +69 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/commands/notes.d.ts +15 -0
- package/dist/commands/notes.d.ts.map +1 -0
- package/dist/commands/notes.js +374 -0
- package/dist/commands/notes.js.map +1 -0
- package/dist/commands/publish.d.ts +14 -0
- package/dist/commands/publish.d.ts.map +1 -0
- package/dist/commands/publish.js +220 -0
- package/dist/commands/publish.js.map +1 -0
- package/dist/commands/publish.test.d.ts +2 -0
- package/dist/commands/publish.test.d.ts.map +1 -0
- package/dist/commands/publish.test.js +371 -0
- package/dist/commands/publish.test.js.map +1 -0
- package/dist/commands/status.d.ts +8 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +258 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/context.d.ts +35 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +48 -0
- package/dist/context.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +152 -0
- package/dist/index.js.map +1 -0
- package/dist/output/formatter.d.ts +161 -0
- package/dist/output/formatter.d.ts.map +1 -0
- package/dist/output/formatter.js +398 -0
- package/dist/output/formatter.js.map +1 -0
- package/dist/output/formatter.test.d.ts +2 -0
- package/dist/output/formatter.test.d.ts.map +1 -0
- package/dist/output/formatter.test.js +223 -0
- package/dist/output/formatter.test.js.map +1 -0
- package/dist/services/ai.d.ts +69 -0
- package/dist/services/ai.d.ts.map +1 -0
- package/dist/services/ai.js +235 -0
- package/dist/services/ai.js.map +1 -0
- package/dist/services/ai.test.d.ts +2 -0
- package/dist/services/ai.test.d.ts.map +1 -0
- package/dist/services/ai.test.js +101 -0
- package/dist/services/ai.test.js.map +1 -0
- package/dist/services/github.d.ts +200 -0
- package/dist/services/github.d.ts.map +1 -0
- package/dist/services/github.js +465 -0
- package/dist/services/github.js.map +1 -0
- package/dist/services/github.test.d.ts +2 -0
- package/dist/services/github.test.d.ts.map +1 -0
- package/dist/services/github.test.js +64 -0
- package/dist/services/github.test.js.map +1 -0
- package/dist/services/version.d.ts +106 -0
- package/dist/services/version.d.ts.map +1 -0
- package/dist/services/version.js +158 -0
- package/dist/services/version.js.map +1 -0
- package/dist/services/version.test.d.ts +2 -0
- package/dist/services/version.test.d.ts.map +1 -0
- package/dist/services/version.test.js +136 -0
- package/dist/services/version.test.js.map +1 -0
- package/dist/utils/errors.d.ts +66 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +154 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/errors.test.d.ts +2 -0
- package/dist/utils/errors.test.d.ts.map +1 -0
- package/dist/utils/errors.test.js +147 -0
- package/dist/utils/errors.test.js.map +1 -0
- package/dist/utils/prompts.d.ts +54 -0
- package/dist/utils/prompts.d.ts.map +1 -0
- package/dist/utils/prompts.js +108 -0
- package/dist/utils/prompts.js.map +1 -0
- package/package.json +70 -0
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
// ABOUTME: Unit tests for output formatter utilities.
|
|
2
|
+
// ABOUTME: Tests color detection, relative time formatting, and output modes.
|
|
3
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
|
4
|
+
import { shouldUseColors, createFormatter, Formatter } from "./formatter.js";
|
|
5
|
+
describe("shouldUseColors", () => {
|
|
6
|
+
const originalEnv = process.env;
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
process.env = { ...originalEnv };
|
|
9
|
+
delete process.env.NO_COLOR;
|
|
10
|
+
});
|
|
11
|
+
afterEach(() => {
|
|
12
|
+
process.env = originalEnv;
|
|
13
|
+
});
|
|
14
|
+
it("returns false when noColor option is true", () => {
|
|
15
|
+
expect(shouldUseColors({ noColor: true })).toBe(false);
|
|
16
|
+
});
|
|
17
|
+
it("returns false when NO_COLOR env is set", () => {
|
|
18
|
+
process.env.NO_COLOR = "1";
|
|
19
|
+
expect(shouldUseColors()).toBe(false);
|
|
20
|
+
});
|
|
21
|
+
it("returns false when NO_COLOR env is empty string", () => {
|
|
22
|
+
process.env.NO_COLOR = "";
|
|
23
|
+
expect(shouldUseColors()).toBe(false);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
describe("Formatter", () => {
|
|
27
|
+
describe("constructor", () => {
|
|
28
|
+
it("creates formatter with default options", () => {
|
|
29
|
+
const formatter = createFormatter();
|
|
30
|
+
expect(formatter).toBeInstanceOf(Formatter);
|
|
31
|
+
});
|
|
32
|
+
it("creates formatter with custom options", () => {
|
|
33
|
+
const options = { json: true, quiet: true };
|
|
34
|
+
const formatter = createFormatter(options);
|
|
35
|
+
expect(formatter.isJsonMode()).toBe(true);
|
|
36
|
+
expect(formatter.isQuietMode()).toBe(true);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
describe("isJsonMode", () => {
|
|
40
|
+
it("returns false by default", () => {
|
|
41
|
+
const formatter = createFormatter();
|
|
42
|
+
expect(formatter.isJsonMode()).toBe(false);
|
|
43
|
+
});
|
|
44
|
+
it("returns true when json option is set", () => {
|
|
45
|
+
const formatter = createFormatter({ json: true });
|
|
46
|
+
expect(formatter.isJsonMode()).toBe(true);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
describe("isQuietMode", () => {
|
|
50
|
+
it("returns false by default", () => {
|
|
51
|
+
const formatter = createFormatter();
|
|
52
|
+
expect(formatter.isQuietMode()).toBe(false);
|
|
53
|
+
});
|
|
54
|
+
it("returns true when quiet option is set", () => {
|
|
55
|
+
const formatter = createFormatter({ quiet: true });
|
|
56
|
+
expect(formatter.isQuietMode()).toBe(true);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
describe("relativeTime", () => {
|
|
60
|
+
it("returns 'just now' for times less than a minute ago", () => {
|
|
61
|
+
const formatter = createFormatter();
|
|
62
|
+
const now = new Date();
|
|
63
|
+
expect(formatter.relativeTime(now)).toBe("just now");
|
|
64
|
+
});
|
|
65
|
+
it("returns minutes for times less than an hour ago", () => {
|
|
66
|
+
const formatter = createFormatter();
|
|
67
|
+
const thirtyMinsAgo = new Date(Date.now() - 30 * 60 * 1000);
|
|
68
|
+
expect(formatter.relativeTime(thirtyMinsAgo)).toBe("30m ago");
|
|
69
|
+
});
|
|
70
|
+
it("returns hours for times less than a day ago", () => {
|
|
71
|
+
const formatter = createFormatter();
|
|
72
|
+
const fiveHoursAgo = new Date(Date.now() - 5 * 60 * 60 * 1000);
|
|
73
|
+
expect(formatter.relativeTime(fiveHoursAgo)).toBe("5h ago");
|
|
74
|
+
});
|
|
75
|
+
it("returns days for times less than 30 days ago", () => {
|
|
76
|
+
const formatter = createFormatter();
|
|
77
|
+
const fiveDaysAgo = new Date(Date.now() - 5 * 24 * 60 * 60 * 1000);
|
|
78
|
+
expect(formatter.relativeTime(fiveDaysAgo)).toBe("5d ago");
|
|
79
|
+
});
|
|
80
|
+
it("returns formatted date for times over 30 days ago", () => {
|
|
81
|
+
const formatter = createFormatter();
|
|
82
|
+
const sixtyDaysAgo = new Date(Date.now() - 60 * 24 * 60 * 60 * 1000);
|
|
83
|
+
const result = formatter.relativeTime(sixtyDaysAgo);
|
|
84
|
+
// Should be a date string, not relative
|
|
85
|
+
expect(result).not.toContain("ago");
|
|
86
|
+
expect(result).toMatch(/\d/);
|
|
87
|
+
});
|
|
88
|
+
it("accepts string dates", () => {
|
|
89
|
+
const formatter = createFormatter();
|
|
90
|
+
const isoDate = new Date().toISOString();
|
|
91
|
+
expect(formatter.relativeTime(isoDate)).toBe("just now");
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
describe("yesNo", () => {
|
|
95
|
+
it("returns Yes-like string for true", () => {
|
|
96
|
+
const formatter = createFormatter({ noColor: true });
|
|
97
|
+
expect(formatter.yesNo(true)).toBe("Yes");
|
|
98
|
+
});
|
|
99
|
+
it("returns No-like string for false", () => {
|
|
100
|
+
const formatter = createFormatter({ noColor: true });
|
|
101
|
+
expect(formatter.yesNo(false)).toBe("No");
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
describe("status", () => {
|
|
105
|
+
const formatter = createFormatter({ noColor: true });
|
|
106
|
+
it("formats passing status", () => {
|
|
107
|
+
const result = formatter.status("passing");
|
|
108
|
+
expect(result).toContain("Passing");
|
|
109
|
+
});
|
|
110
|
+
it("formats failing status", () => {
|
|
111
|
+
const result = formatter.status("failing");
|
|
112
|
+
expect(result).toContain("Failing");
|
|
113
|
+
});
|
|
114
|
+
it("formats pending status", () => {
|
|
115
|
+
const result = formatter.status("pending");
|
|
116
|
+
expect(result).toContain("Pending");
|
|
117
|
+
});
|
|
118
|
+
it("formats draft status", () => {
|
|
119
|
+
const result = formatter.status("draft");
|
|
120
|
+
expect(result).toContain("Draft");
|
|
121
|
+
});
|
|
122
|
+
it("formats published status", () => {
|
|
123
|
+
const result = formatter.status("published");
|
|
124
|
+
expect(result).toContain("Published");
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
describe("link", () => {
|
|
128
|
+
it("returns text with URL in parentheses when colors disabled", () => {
|
|
129
|
+
const formatter = createFormatter({ noColor: true });
|
|
130
|
+
const result = formatter.link("GitHub", "https://github.com");
|
|
131
|
+
expect(result).toBe("GitHub (https://github.com)");
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
describe("maybeJson", () => {
|
|
135
|
+
it("returns false and does not output when not in JSON mode", () => {
|
|
136
|
+
const formatter = createFormatter({ json: false });
|
|
137
|
+
const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => { });
|
|
138
|
+
const result = formatter.maybeJson({ test: "data" });
|
|
139
|
+
expect(result).toBe(false);
|
|
140
|
+
expect(consoleSpy).not.toHaveBeenCalled();
|
|
141
|
+
consoleSpy.mockRestore();
|
|
142
|
+
});
|
|
143
|
+
it("returns true and outputs JSON when in JSON mode", () => {
|
|
144
|
+
const formatter = createFormatter({ json: true });
|
|
145
|
+
const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => { });
|
|
146
|
+
const result = formatter.maybeJson({ test: "data" });
|
|
147
|
+
expect(result).toBe(true);
|
|
148
|
+
expect(consoleSpy).toHaveBeenCalled();
|
|
149
|
+
consoleSpy.mockRestore();
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
describe("spinner", () => {
|
|
153
|
+
it("returns a no-op spinner in quiet mode", () => {
|
|
154
|
+
const formatter = createFormatter({ quiet: true });
|
|
155
|
+
const spinner = formatter.spinner("Loading...");
|
|
156
|
+
// NoOpSpinner just returns itself for chaining
|
|
157
|
+
expect(spinner.start()).toBe(spinner);
|
|
158
|
+
expect(spinner.stop()).toBe(spinner);
|
|
159
|
+
expect(spinner.succeed()).toBe(spinner);
|
|
160
|
+
expect(spinner.fail()).toBe(spinner);
|
|
161
|
+
});
|
|
162
|
+
it("returns a no-op spinner in JSON mode", () => {
|
|
163
|
+
const formatter = createFormatter({ json: true });
|
|
164
|
+
const spinner = formatter.spinner("Loading...");
|
|
165
|
+
expect(spinner.start()).toBe(spinner);
|
|
166
|
+
expect(spinner.stop()).toBe(spinner);
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
describe("output methods in quiet mode", () => {
|
|
170
|
+
let consoleSpy;
|
|
171
|
+
beforeEach(() => {
|
|
172
|
+
consoleSpy = vi.spyOn(console, "log").mockImplementation(() => { });
|
|
173
|
+
});
|
|
174
|
+
afterEach(() => {
|
|
175
|
+
consoleSpy.mockRestore();
|
|
176
|
+
});
|
|
177
|
+
it("success() does not output in quiet mode", () => {
|
|
178
|
+
const formatter = createFormatter({ quiet: true });
|
|
179
|
+
formatter.success("message");
|
|
180
|
+
expect(consoleSpy).not.toHaveBeenCalled();
|
|
181
|
+
});
|
|
182
|
+
it("info() does not output in quiet mode", () => {
|
|
183
|
+
const formatter = createFormatter({ quiet: true });
|
|
184
|
+
formatter.info("message");
|
|
185
|
+
expect(consoleSpy).not.toHaveBeenCalled();
|
|
186
|
+
});
|
|
187
|
+
it("text() does not output in quiet mode", () => {
|
|
188
|
+
const formatter = createFormatter({ quiet: true });
|
|
189
|
+
formatter.text("message");
|
|
190
|
+
expect(consoleSpy).not.toHaveBeenCalled();
|
|
191
|
+
});
|
|
192
|
+
it("newline() does not output in quiet mode", () => {
|
|
193
|
+
const formatter = createFormatter({ quiet: true });
|
|
194
|
+
formatter.newline();
|
|
195
|
+
expect(consoleSpy).not.toHaveBeenCalled();
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
describe("output methods in JSON mode", () => {
|
|
199
|
+
let consoleSpy;
|
|
200
|
+
beforeEach(() => {
|
|
201
|
+
consoleSpy = vi.spyOn(console, "log").mockImplementation(() => { });
|
|
202
|
+
});
|
|
203
|
+
afterEach(() => {
|
|
204
|
+
consoleSpy.mockRestore();
|
|
205
|
+
});
|
|
206
|
+
it("success() does not output in JSON mode", () => {
|
|
207
|
+
const formatter = createFormatter({ json: true });
|
|
208
|
+
formatter.success("message");
|
|
209
|
+
expect(consoleSpy).not.toHaveBeenCalled();
|
|
210
|
+
});
|
|
211
|
+
it("info() does not output in JSON mode", () => {
|
|
212
|
+
const formatter = createFormatter({ json: true });
|
|
213
|
+
formatter.info("message");
|
|
214
|
+
expect(consoleSpy).not.toHaveBeenCalled();
|
|
215
|
+
});
|
|
216
|
+
it("text() does not output in JSON mode", () => {
|
|
217
|
+
const formatter = createFormatter({ json: true });
|
|
218
|
+
formatter.text("message");
|
|
219
|
+
expect(consoleSpy).not.toHaveBeenCalled();
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
//# sourceMappingURL=formatter.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"formatter.test.js","sourceRoot":"","sources":["../../src/output/formatter.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { CommitInfo } from "./github.js";
|
|
2
|
+
/**
|
|
3
|
+
* ArtifactHub changelog entry format.
|
|
4
|
+
* https://artifacthub.io/docs/topics/annotations/helm/#supported-annotations
|
|
5
|
+
*/
|
|
6
|
+
export interface ArtifactHubChange {
|
|
7
|
+
kind: "added" | "changed" | "deprecated" | "removed" | "fixed" | "security";
|
|
8
|
+
description: string;
|
|
9
|
+
links?: Array<{
|
|
10
|
+
name: string;
|
|
11
|
+
url: string;
|
|
12
|
+
}>;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Generated release notes containing both ArtifactHub and GitHub formats.
|
|
16
|
+
*/
|
|
17
|
+
export interface GeneratedReleaseNotes {
|
|
18
|
+
artifactHubChangelog: ArtifactHubChange[];
|
|
19
|
+
githubReleaseNotes: string;
|
|
20
|
+
commitsAnalyzed: number;
|
|
21
|
+
previousVersion: string | null;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Input data for release notes generation.
|
|
25
|
+
*/
|
|
26
|
+
export interface ReleaseNotesInput {
|
|
27
|
+
version: string;
|
|
28
|
+
previousVersion: string | null;
|
|
29
|
+
commits: CommitInfo[];
|
|
30
|
+
prDescriptions: string[];
|
|
31
|
+
changedFiles?: string[];
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Validates that ANTHROPIC_API_KEY is set and returns it.
|
|
35
|
+
*/
|
|
36
|
+
export declare function requireAnthropicApiKey(): string;
|
|
37
|
+
/**
|
|
38
|
+
* AI service for generating release notes.
|
|
39
|
+
*/
|
|
40
|
+
export declare class AIService {
|
|
41
|
+
private client;
|
|
42
|
+
constructor(apiKey: string);
|
|
43
|
+
/**
|
|
44
|
+
* Generates release notes from commit and PR data.
|
|
45
|
+
*/
|
|
46
|
+
generateReleaseNotes(input: ReleaseNotesInput): Promise<GeneratedReleaseNotes>;
|
|
47
|
+
/**
|
|
48
|
+
* Builds the prompt for release notes generation.
|
|
49
|
+
*/
|
|
50
|
+
private buildPrompt;
|
|
51
|
+
/**
|
|
52
|
+
* Parses the AI response into structured release notes.
|
|
53
|
+
*/
|
|
54
|
+
private parseResponse;
|
|
55
|
+
/**
|
|
56
|
+
* Parses the ArtifactHub YAML changelog.
|
|
57
|
+
* Uses a simple parser to avoid full YAML dependency here.
|
|
58
|
+
*/
|
|
59
|
+
private parseArtifactHubYaml;
|
|
60
|
+
/**
|
|
61
|
+
* Validates and normalizes a changelog kind value.
|
|
62
|
+
*/
|
|
63
|
+
private validateKind;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Formats ArtifactHub changelog as YAML string.
|
|
67
|
+
*/
|
|
68
|
+
export declare function formatArtifactHubYaml(changes: ArtifactHubChange[]): string;
|
|
69
|
+
//# sourceMappingURL=ai.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai.d.ts","sourceRoot":"","sources":["../../src/services/ai.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,OAAO,GAAG,SAAS,GAAG,YAAY,GAAG,SAAS,GAAG,OAAO,GAAG,UAAU,CAAC;IAC5E,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,KAAK,CAAC;QACZ,IAAI,EAAE,MAAM,CAAC;QACb,GAAG,EAAE,MAAM,CAAC;KACb,CAAC,CAAC;CACJ;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,oBAAoB,EAAE,iBAAiB,EAAE,CAAC;IAC1C,kBAAkB,EAAE,MAAM,CAAC;IAC3B,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;CACzB;AAED;;GAEG;AACH,wBAAgB,sBAAsB,IAAI,MAAM,CAS/C;AAED;;GAEG;AACH,qBAAa,SAAS;IACpB,OAAO,CAAC,MAAM,CAAY;gBAEd,MAAM,EAAE,MAAM;IAI1B;;OAEG;IACG,oBAAoB,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,qBAAqB,CAAC;IA8BpF;;OAEG;IACH,OAAO,CAAC,WAAW;IAuFnB;;OAEG;IACH,OAAO,CAAC,aAAa;IA2BrB;;;OAGG;IACH,OAAO,CAAC,oBAAoB;IA6C5B;;OAEG;IACH,OAAO,CAAC,YAAY;CAWrB;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,iBAAiB,EAAE,GAAG,MAAM,CAa1E"}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
// ABOUTME: AI service for generating release notes using Anthropic's Claude API.
|
|
2
|
+
// ABOUTME: Analyzes commits and PRs to produce ArtifactHub changelog and GitHub release notes.
|
|
3
|
+
import Anthropic from "@anthropic-ai/sdk";
|
|
4
|
+
import { AIError, ValidationError } from "../utils/errors.js";
|
|
5
|
+
/**
|
|
6
|
+
* Validates that ANTHROPIC_API_KEY is set and returns it.
|
|
7
|
+
*/
|
|
8
|
+
export function requireAnthropicApiKey() {
|
|
9
|
+
const key = process.env.ANTHROPIC_API_KEY;
|
|
10
|
+
if (!key) {
|
|
11
|
+
throw new ValidationError("ANTHROPIC_API_KEY environment variable is required for the notes command.\n" +
|
|
12
|
+
"Get your API key at https://console.anthropic.com/");
|
|
13
|
+
}
|
|
14
|
+
return key;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* AI service for generating release notes.
|
|
18
|
+
*/
|
|
19
|
+
export class AIService {
|
|
20
|
+
client;
|
|
21
|
+
constructor(apiKey) {
|
|
22
|
+
this.client = new Anthropic({ apiKey });
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Generates release notes from commit and PR data.
|
|
26
|
+
*/
|
|
27
|
+
async generateReleaseNotes(input) {
|
|
28
|
+
const prompt = this.buildPrompt(input);
|
|
29
|
+
try {
|
|
30
|
+
const response = await this.client.messages.create({
|
|
31
|
+
model: "claude-opus-4-5",
|
|
32
|
+
max_tokens: 4096,
|
|
33
|
+
messages: [
|
|
34
|
+
{
|
|
35
|
+
role: "user",
|
|
36
|
+
content: prompt,
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
});
|
|
40
|
+
const content = response.content[0];
|
|
41
|
+
if (content.type !== "text") {
|
|
42
|
+
throw new AIError("Unexpected response format from AI");
|
|
43
|
+
}
|
|
44
|
+
return this.parseResponse(content.text, input);
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
if (error instanceof AIError) {
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
const message = error instanceof Error ? error.message : "Unknown error occurred";
|
|
51
|
+
throw new AIError(`Failed to generate release notes: ${message}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Builds the prompt for release notes generation.
|
|
56
|
+
*/
|
|
57
|
+
buildPrompt(input) {
|
|
58
|
+
const commitList = input.commits
|
|
59
|
+
.map((c) => {
|
|
60
|
+
const shortSha = c.sha.substring(0, 7);
|
|
61
|
+
const firstLine = c.message.split("\n")[0];
|
|
62
|
+
return `- ${shortSha}: ${firstLine}`;
|
|
63
|
+
})
|
|
64
|
+
.join("\n");
|
|
65
|
+
const prDescriptionList = input.prDescriptions.length > 0
|
|
66
|
+
? input.prDescriptions.map((desc) => `---\n${desc}\n---`).join("\n\n")
|
|
67
|
+
: "No PR descriptions available.";
|
|
68
|
+
const changedFilesList = input.changedFiles?.join("\n") ?? "Not available";
|
|
69
|
+
return `You are a technical writer generating release notes for a Helm chart release.
|
|
70
|
+
|
|
71
|
+
## Context
|
|
72
|
+
- This is version ${input.version} of the gitops-runtime Helm chart
|
|
73
|
+
- Previous version: ${input.previousVersion ?? "N/A (first release)"}
|
|
74
|
+
- The chart deploys GitOps components including ArgoCD, app-proxy, event-reporter, and more
|
|
75
|
+
|
|
76
|
+
## Commits Since Last Release
|
|
77
|
+
${commitList || "No commits found."}
|
|
78
|
+
|
|
79
|
+
## PR Descriptions
|
|
80
|
+
${prDescriptionList}
|
|
81
|
+
|
|
82
|
+
## Changed Files
|
|
83
|
+
${changedFilesList}
|
|
84
|
+
|
|
85
|
+
## Your Task
|
|
86
|
+
Generate release notes in two formats:
|
|
87
|
+
|
|
88
|
+
### 1. ArtifactHub Changelog (YAML format)
|
|
89
|
+
Output a YAML array of changes. Each entry must have:
|
|
90
|
+
- kind: one of "added", "changed", "deprecated", "removed", "fixed", "security"
|
|
91
|
+
- description: concise description (max 100 chars)
|
|
92
|
+
|
|
93
|
+
Example format:
|
|
94
|
+
\`\`\`yaml
|
|
95
|
+
- kind: added
|
|
96
|
+
description: Support for ArgoCD 2.10 application sets
|
|
97
|
+
- kind: fixed
|
|
98
|
+
description: Memory leak in event-reporter under high load
|
|
99
|
+
\`\`\`
|
|
100
|
+
|
|
101
|
+
### 2. GitHub Release Notes (Markdown format)
|
|
102
|
+
The release notes MUST start with this exact installation section (replace VERSION with actual version):
|
|
103
|
+
|
|
104
|
+
\`\`\`markdown
|
|
105
|
+
# Installation
|
|
106
|
+
|
|
107
|
+
To get Helm chart for this release run:
|
|
108
|
+
|
|
109
|
+
\`\`\`sh
|
|
110
|
+
helm pull oci://quay.io/codefresh/gitops-runtime --version VERSION
|
|
111
|
+
\`\`\`
|
|
112
|
+
\`\`\`
|
|
113
|
+
|
|
114
|
+
After the installation section, add user-friendly release notes with sections like:
|
|
115
|
+
- What's New / Features
|
|
116
|
+
- Improvements
|
|
117
|
+
- Bug Fixes
|
|
118
|
+
- Breaking Changes (if any)
|
|
119
|
+
- Component Updates (table format if versions changed)
|
|
120
|
+
|
|
121
|
+
## Output Format
|
|
122
|
+
Respond with exactly two sections, clearly marked:
|
|
123
|
+
|
|
124
|
+
<ARTIFACTHUB>
|
|
125
|
+
(YAML array here)
|
|
126
|
+
</ARTIFACTHUB>
|
|
127
|
+
|
|
128
|
+
<GITHUB>
|
|
129
|
+
(Markdown content here, starting with Installation section)
|
|
130
|
+
</GITHUB>
|
|
131
|
+
|
|
132
|
+
Important:
|
|
133
|
+
- Focus on user-facing changes, not internal refactoring
|
|
134
|
+
- Group related changes together
|
|
135
|
+
- Use clear, concise language
|
|
136
|
+
- Include PR/issue numbers where referenced in commits
|
|
137
|
+
- If a commit updates a component version, note the new version`;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Parses the AI response into structured release notes.
|
|
141
|
+
*/
|
|
142
|
+
parseResponse(text, input) {
|
|
143
|
+
// Extract ArtifactHub section
|
|
144
|
+
const artifactHubMatch = text.match(/<ARTIFACTHUB>\s*([\s\S]*?)\s*<\/ARTIFACTHUB>/);
|
|
145
|
+
if (!artifactHubMatch) {
|
|
146
|
+
throw new AIError("Failed to parse AI response: missing ARTIFACTHUB section");
|
|
147
|
+
}
|
|
148
|
+
// Extract GitHub section
|
|
149
|
+
const githubMatch = text.match(/<GITHUB>\s*([\s\S]*?)\s*<\/GITHUB>/);
|
|
150
|
+
if (!githubMatch) {
|
|
151
|
+
throw new AIError("Failed to parse AI response: missing GITHUB section");
|
|
152
|
+
}
|
|
153
|
+
const artifactHubYaml = artifactHubMatch[1].trim();
|
|
154
|
+
const githubMarkdown = githubMatch[1].trim();
|
|
155
|
+
// Parse ArtifactHub YAML
|
|
156
|
+
const artifactHubChangelog = this.parseArtifactHubYaml(artifactHubYaml);
|
|
157
|
+
return {
|
|
158
|
+
artifactHubChangelog,
|
|
159
|
+
githubReleaseNotes: githubMarkdown,
|
|
160
|
+
commitsAnalyzed: input.commits.length,
|
|
161
|
+
previousVersion: input.previousVersion,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Parses the ArtifactHub YAML changelog.
|
|
166
|
+
* Uses a simple parser to avoid full YAML dependency here.
|
|
167
|
+
*/
|
|
168
|
+
parseArtifactHubYaml(yaml) {
|
|
169
|
+
const changes = [];
|
|
170
|
+
const lines = yaml.split("\n");
|
|
171
|
+
let currentChange = null;
|
|
172
|
+
for (const line of lines) {
|
|
173
|
+
const trimmed = line.trim();
|
|
174
|
+
// Skip empty lines and yaml code fence markers
|
|
175
|
+
if (!trimmed || trimmed === "```yaml" || trimmed === "```") {
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
// New entry starts with "- kind:"
|
|
179
|
+
if (trimmed.startsWith("- kind:")) {
|
|
180
|
+
if (currentChange && currentChange.kind && currentChange.description) {
|
|
181
|
+
changes.push(currentChange);
|
|
182
|
+
}
|
|
183
|
+
const kindValue = trimmed.replace("- kind:", "").trim();
|
|
184
|
+
currentChange = {
|
|
185
|
+
kind: this.validateKind(kindValue),
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
else if (trimmed.startsWith("description:") && currentChange) {
|
|
189
|
+
// Description can be on same line or next line
|
|
190
|
+
let desc = trimmed.replace("description:", "").trim();
|
|
191
|
+
// Remove surrounding quotes if present
|
|
192
|
+
if ((desc.startsWith('"') && desc.endsWith('"')) ||
|
|
193
|
+
(desc.startsWith("'") && desc.endsWith("'"))) {
|
|
194
|
+
desc = desc.slice(1, -1);
|
|
195
|
+
}
|
|
196
|
+
currentChange.description = desc;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
// Don't forget the last entry
|
|
200
|
+
if (currentChange && currentChange.kind && currentChange.description) {
|
|
201
|
+
changes.push(currentChange);
|
|
202
|
+
}
|
|
203
|
+
return changes;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Validates and normalizes a changelog kind value.
|
|
207
|
+
*/
|
|
208
|
+
validateKind(kind) {
|
|
209
|
+
const normalized = kind.toLowerCase().trim();
|
|
210
|
+
const validKinds = ["added", "changed", "deprecated", "removed", "fixed", "security"];
|
|
211
|
+
if (validKinds.includes(normalized)) {
|
|
212
|
+
return normalized;
|
|
213
|
+
}
|
|
214
|
+
// Default to "changed" for unknown kinds
|
|
215
|
+
return "changed";
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Formats ArtifactHub changelog as YAML string.
|
|
220
|
+
*/
|
|
221
|
+
export function formatArtifactHubYaml(changes) {
|
|
222
|
+
return changes
|
|
223
|
+
.map((change) => {
|
|
224
|
+
let entry = `- kind: ${change.kind}\n description: "${change.description.replace(/"/g, '\\"')}"`;
|
|
225
|
+
if (change.links && change.links.length > 0) {
|
|
226
|
+
entry += "\n links:";
|
|
227
|
+
for (const link of change.links) {
|
|
228
|
+
entry += `\n - name: ${link.name}\n url: ${link.url}`;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return entry;
|
|
232
|
+
})
|
|
233
|
+
.join("\n");
|
|
234
|
+
}
|
|
235
|
+
//# sourceMappingURL=ai.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai.js","sourceRoot":"","sources":["../../src/services/ai.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai.test.d.ts","sourceRoot":"","sources":["../../src/services/ai.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// ABOUTME: Unit tests for the AI service.
|
|
2
|
+
// ABOUTME: Tests utility functions and YAML formatting without actual API calls.
|
|
3
|
+
import { describe, it, expect, afterEach } from "vitest";
|
|
4
|
+
import { formatArtifactHubYaml, requireAnthropicApiKey } from "./ai.js";
|
|
5
|
+
import { ValidationError } from "../utils/errors.js";
|
|
6
|
+
describe("AI Service", () => {
|
|
7
|
+
describe("formatArtifactHubYaml", () => {
|
|
8
|
+
it("formats a single change correctly", () => {
|
|
9
|
+
const changes = [
|
|
10
|
+
{
|
|
11
|
+
kind: "added",
|
|
12
|
+
description: "New feature for users",
|
|
13
|
+
},
|
|
14
|
+
];
|
|
15
|
+
const result = formatArtifactHubYaml(changes);
|
|
16
|
+
expect(result).toBe('- kind: added\n description: "New feature for users"');
|
|
17
|
+
});
|
|
18
|
+
it("formats multiple changes correctly", () => {
|
|
19
|
+
const changes = [
|
|
20
|
+
{ kind: "added", description: "Feature A" },
|
|
21
|
+
{ kind: "fixed", description: "Bug B" },
|
|
22
|
+
{ kind: "changed", description: "Update C" },
|
|
23
|
+
];
|
|
24
|
+
const result = formatArtifactHubYaml(changes);
|
|
25
|
+
expect(result).toContain("- kind: added");
|
|
26
|
+
expect(result).toContain('description: "Feature A"');
|
|
27
|
+
expect(result).toContain("- kind: fixed");
|
|
28
|
+
expect(result).toContain('description: "Bug B"');
|
|
29
|
+
expect(result).toContain("- kind: changed");
|
|
30
|
+
expect(result).toContain('description: "Update C"');
|
|
31
|
+
});
|
|
32
|
+
it("escapes double quotes in descriptions", () => {
|
|
33
|
+
const changes = [
|
|
34
|
+
{
|
|
35
|
+
kind: "changed",
|
|
36
|
+
description: 'Updated "config" option',
|
|
37
|
+
},
|
|
38
|
+
];
|
|
39
|
+
const result = formatArtifactHubYaml(changes);
|
|
40
|
+
expect(result).toContain('description: "Updated \\"config\\" option"');
|
|
41
|
+
});
|
|
42
|
+
it("includes links when provided", () => {
|
|
43
|
+
const changes = [
|
|
44
|
+
{
|
|
45
|
+
kind: "security",
|
|
46
|
+
description: "Fixed CVE-2024-1234",
|
|
47
|
+
links: [{ name: "CVE Details", url: "https://cve.mitre.org/CVE-2024-1234" }],
|
|
48
|
+
},
|
|
49
|
+
];
|
|
50
|
+
const result = formatArtifactHubYaml(changes);
|
|
51
|
+
expect(result).toContain("links:");
|
|
52
|
+
expect(result).toContain("name: CVE Details");
|
|
53
|
+
expect(result).toContain("url: https://cve.mitre.org/CVE-2024-1234");
|
|
54
|
+
});
|
|
55
|
+
it("handles all valid change kinds", () => {
|
|
56
|
+
const kinds = [
|
|
57
|
+
"added",
|
|
58
|
+
"changed",
|
|
59
|
+
"deprecated",
|
|
60
|
+
"removed",
|
|
61
|
+
"fixed",
|
|
62
|
+
"security",
|
|
63
|
+
];
|
|
64
|
+
for (const kind of kinds) {
|
|
65
|
+
const changes = [{ kind, description: `Test ${kind}` }];
|
|
66
|
+
const result = formatArtifactHubYaml(changes);
|
|
67
|
+
expect(result).toContain(`kind: ${kind}`);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
it("returns empty string for empty array", () => {
|
|
71
|
+
const result = formatArtifactHubYaml([]);
|
|
72
|
+
expect(result).toBe("");
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
describe("requireAnthropicApiKey", () => {
|
|
76
|
+
const originalEnv = process.env.ANTHROPIC_API_KEY;
|
|
77
|
+
afterEach(() => {
|
|
78
|
+
if (originalEnv !== undefined) {
|
|
79
|
+
process.env.ANTHROPIC_API_KEY = originalEnv;
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
delete process.env.ANTHROPIC_API_KEY;
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
it("returns API key when set", () => {
|
|
86
|
+
process.env.ANTHROPIC_API_KEY = "test-api-key-123";
|
|
87
|
+
const result = requireAnthropicApiKey();
|
|
88
|
+
expect(result).toBe("test-api-key-123");
|
|
89
|
+
});
|
|
90
|
+
it("throws ValidationError when not set", () => {
|
|
91
|
+
delete process.env.ANTHROPIC_API_KEY;
|
|
92
|
+
expect(() => requireAnthropicApiKey()).toThrow(ValidationError);
|
|
93
|
+
});
|
|
94
|
+
it("includes helpful message in error", () => {
|
|
95
|
+
delete process.env.ANTHROPIC_API_KEY;
|
|
96
|
+
expect(() => requireAnthropicApiKey()).toThrow(/ANTHROPIC_API_KEY environment variable is required/);
|
|
97
|
+
expect(() => requireAnthropicApiKey()).toThrow(/console\.anthropic\.com/);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
//# sourceMappingURL=ai.test.js.map
|