kubeagent 0.1.43 → 0.1.44

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.
Files changed (77) hide show
  1. package/package.json +1 -1
  2. package/dist/auth.d.ts +0 -29
  3. package/dist/check/index.d.ts +0 -34
  4. package/dist/check/index.test.d.ts +0 -1
  5. package/dist/check/index.test.js +0 -202
  6. package/dist/check/run.d.ts +0 -10
  7. package/dist/cli.d.ts +0 -2
  8. package/dist/config.d.ts +0 -75
  9. package/dist/debug.d.ts +0 -10
  10. package/dist/detector/cache.d.ts +0 -6
  11. package/dist/detector/cache.test.d.ts +0 -1
  12. package/dist/detector/cache.test.js +0 -54
  13. package/dist/detector/catalog.d.ts +0 -17
  14. package/dist/detector/catalog.test.d.ts +0 -1
  15. package/dist/detector/catalog.test.js +0 -33
  16. package/dist/detector/eol.d.ts +0 -13
  17. package/dist/detector/eol.test.d.ts +0 -1
  18. package/dist/detector/eol.test.js +0 -78
  19. package/dist/detector/image-parser.d.ts +0 -11
  20. package/dist/detector/image-parser.test.d.ts +0 -1
  21. package/dist/detector/image-parser.test.js +0 -87
  22. package/dist/detector/index.d.ts +0 -33
  23. package/dist/detector/index.test.d.ts +0 -1
  24. package/dist/detector/index.test.js +0 -210
  25. package/dist/detector/osv.d.ts +0 -12
  26. package/dist/detector/osv.test.d.ts +0 -1
  27. package/dist/detector/osv.test.js +0 -86
  28. package/dist/detector/report.d.ts +0 -6
  29. package/dist/detector/sources.d.ts +0 -55
  30. package/dist/detector/sources.test.d.ts +0 -1
  31. package/dist/detector/sources.test.js +0 -36
  32. package/dist/diagnoser/approval.test.d.ts +0 -1
  33. package/dist/diagnoser/approval.test.js +0 -49
  34. package/dist/diagnoser/index.d.ts +0 -33
  35. package/dist/diagnoser/tools.d.ts +0 -119
  36. package/dist/kb/loader.d.ts +0 -1
  37. package/dist/kb/writer.d.ts +0 -12
  38. package/dist/kubectl-config.d.ts +0 -7
  39. package/dist/kubectl.d.ts +0 -13
  40. package/dist/monitor/checks.d.ts +0 -71
  41. package/dist/monitor/index.d.ts +0 -27
  42. package/dist/monitor/resolve.test.d.ts +0 -1
  43. package/dist/monitor/resolve.test.js +0 -21
  44. package/dist/monitor/scope.test.d.ts +0 -1
  45. package/dist/monitor/scope.test.js +0 -87
  46. package/dist/monitor/types.d.ts +0 -11
  47. package/dist/notify/discord.d.ts +0 -4
  48. package/dist/notify/index.d.ts +0 -6
  49. package/dist/notify/pagerduty.d.ts +0 -9
  50. package/dist/notify/pagerduty.test.d.ts +0 -1
  51. package/dist/notify/pagerduty.test.js +0 -97
  52. package/dist/notify/setup.d.ts +0 -9
  53. package/dist/notify/slack.d.ts +0 -4
  54. package/dist/notify/ssrf-guard.d.ts +0 -5
  55. package/dist/notify/teams.d.ts +0 -3
  56. package/dist/notify/telegram.d.ts +0 -9
  57. package/dist/notify/webhook.d.ts +0 -3
  58. package/dist/notify/webhook.test.d.ts +0 -1
  59. package/dist/notify/webhook.test.js +0 -74
  60. package/dist/onboard/cluster-scan.d.ts +0 -42
  61. package/dist/onboard/code-scan.d.ts +0 -9
  62. package/dist/onboard/index.d.ts +0 -3
  63. package/dist/onboard/interview.d.ts +0 -12
  64. package/dist/onboard/project-matcher.d.ts +0 -25
  65. package/dist/orchestrator.d.ts +0 -3
  66. package/dist/proxy-client.d.ts +0 -27
  67. package/dist/render.d.ts +0 -5
  68. package/dist/telemetry.d.ts +0 -1
  69. package/dist/telemetry.test.d.ts +0 -1
  70. package/dist/telemetry.test.js +0 -18
  71. package/dist/update-notifier.d.ts +0 -1
  72. package/dist/update-notifier.test.d.ts +0 -1
  73. package/dist/update-notifier.test.js +0 -50
  74. package/dist/verifier.d.ts +0 -9
  75. package/dist/version-check.d.ts +0 -14
  76. package/dist/version-check.test.d.ts +0 -1
  77. package/dist/version-check.test.js +0 -179
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kubeagent",
3
- "version": "0.1.43",
3
+ "version": "0.1.44",
4
4
  "description": "AI-powered Kubernetes management CLI",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "repository": {
package/dist/auth.d.ts DELETED
@@ -1,29 +0,0 @@
1
- export interface AuthState {
2
- serverUrl: string;
3
- appUrl: string;
4
- token: string;
5
- email?: string;
6
- name?: string;
7
- apiKey?: string;
8
- }
9
- export declare function loadAuth(): AuthState | null;
10
- export declare function saveAuth(auth: AuthState): void;
11
- export declare function clearAuth(): void;
12
- export declare function loginBrowser(serverUrl: string, appUrl: string): Promise<AuthState>;
13
- export declare function loginDevice(serverUrl: string, appUrl: string): Promise<AuthState>;
14
- export declare function createApiKey(auth: AuthState, name: string): Promise<{
15
- key: string;
16
- prefix: string;
17
- }>;
18
- export interface BalanceInfo {
19
- monthlyRemaining: number;
20
- extraRemaining: number;
21
- totalRemaining: number;
22
- resetsAt: string;
23
- plan: string | null;
24
- planName: string | null;
25
- monthlyTotal: number;
26
- }
27
- export declare function showAccount(auth: AuthState): Promise<{
28
- balance: BalanceInfo;
29
- }>;
@@ -1,34 +0,0 @@
1
- import type { Issue, Severity } from "../monitor/types.js";
2
- export declare const SEVERITY_RANK: Record<Severity, number>;
3
- export declare function isValidSeverity(s: string): s is Severity;
4
- export interface CheckReportSummary {
5
- critical: number;
6
- warning: number;
7
- info: number;
8
- podCount: number;
9
- nodeCount: number;
10
- }
11
- export interface CheckReport {
12
- version: 1;
13
- context: string;
14
- namespaces: string[] | "all";
15
- scannedAt: string;
16
- durationMs: number;
17
- summary: CheckReportSummary;
18
- issues: Issue[];
19
- diagnosis: {
20
- analysis: string;
21
- } | null;
22
- exit: {
23
- code: 0 | 1;
24
- reason: string;
25
- };
26
- }
27
- export declare function filterIssuesByNamespace(issues: Issue[], namespaces: string[] | "all"): Issue[];
28
- export declare function summarizeIssues(issues: Issue[], podCount: number, nodeCount: number): CheckReportSummary;
29
- export declare function computeExitCode(summary: CheckReportSummary, failOn: Severity): {
30
- code: 0 | 1;
31
- reason: string;
32
- };
33
- export declare function renderJson(report: CheckReport): string;
34
- export declare function renderHuman(report: CheckReport): string;
@@ -1 +0,0 @@
1
- export {};
@@ -1,202 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { computeExitCode, filterIssuesByNamespace, isValidSeverity, renderHuman, renderJson, summarizeIssues, } from "./index.js";
3
- function makeIssue(overrides = {}) {
4
- return {
5
- kind: "pod_crashloop",
6
- severity: "critical",
7
- namespace: "default",
8
- resource: "test-pod",
9
- message: "CrashLoopBackOff",
10
- details: {},
11
- timestamp: new Date("2026-05-28T10:00:00Z"),
12
- ...overrides,
13
- };
14
- }
15
- describe("isValidSeverity", () => {
16
- it("accepts the three valid severities", () => {
17
- expect(isValidSeverity("info")).toBe(true);
18
- expect(isValidSeverity("warning")).toBe(true);
19
- expect(isValidSeverity("critical")).toBe(true);
20
- });
21
- it("rejects anything else", () => {
22
- expect(isValidSeverity("bogus")).toBe(false);
23
- expect(isValidSeverity("")).toBe(false);
24
- expect(isValidSeverity("CRITICAL")).toBe(false);
25
- });
26
- });
27
- describe("filterIssuesByNamespace", () => {
28
- const issues = [
29
- makeIssue({ namespace: "foo" }),
30
- makeIssue({ namespace: "bar" }),
31
- makeIssue({ namespace: "baz" }),
32
- ];
33
- it("returns all issues when scope is 'all'", () => {
34
- expect(filterIssuesByNamespace(issues, "all")).toHaveLength(3);
35
- });
36
- it("returns all issues when namespace list is empty", () => {
37
- expect(filterIssuesByNamespace(issues, [])).toHaveLength(3);
38
- });
39
- it("filters to the specified namespaces", () => {
40
- const result = filterIssuesByNamespace(issues, ["foo", "baz"]);
41
- expect(result.map((i) => i.namespace)).toEqual(["foo", "baz"]);
42
- });
43
- it("returns empty when no namespaces match", () => {
44
- expect(filterIssuesByNamespace(issues, ["nope"])).toHaveLength(0);
45
- });
46
- });
47
- describe("summarizeIssues", () => {
48
- it("counts each severity", () => {
49
- const issues = [
50
- makeIssue({ severity: "critical" }),
51
- makeIssue({ severity: "critical" }),
52
- makeIssue({ severity: "warning" }),
53
- makeIssue({ severity: "info" }),
54
- ];
55
- const s = summarizeIssues(issues, 10, 3);
56
- expect(s).toEqual({ critical: 2, warning: 1, info: 1, podCount: 10, nodeCount: 3 });
57
- });
58
- it("zeros when no issues", () => {
59
- expect(summarizeIssues([], 5, 2)).toEqual({
60
- critical: 0,
61
- warning: 0,
62
- info: 0,
63
- podCount: 5,
64
- nodeCount: 2,
65
- });
66
- });
67
- });
68
- describe("computeExitCode", () => {
69
- const empty = { critical: 0, warning: 0, info: 0, podCount: 0, nodeCount: 0 };
70
- it("exits 0 when no issues at any threshold", () => {
71
- expect(computeExitCode(empty, "critical").code).toBe(0);
72
- expect(computeExitCode(empty, "warning").code).toBe(0);
73
- expect(computeExitCode(empty, "info").code).toBe(0);
74
- });
75
- it("fail-on=critical only fails on critical", () => {
76
- expect(computeExitCode({ ...empty, critical: 1 }, "critical").code).toBe(1);
77
- expect(computeExitCode({ ...empty, warning: 5 }, "critical").code).toBe(0);
78
- expect(computeExitCode({ ...empty, info: 10 }, "critical").code).toBe(0);
79
- });
80
- it("fail-on=warning fails on warning or critical", () => {
81
- expect(computeExitCode({ ...empty, critical: 1 }, "warning").code).toBe(1);
82
- expect(computeExitCode({ ...empty, warning: 1 }, "warning").code).toBe(1);
83
- expect(computeExitCode({ ...empty, info: 1 }, "warning").code).toBe(0);
84
- });
85
- it("fail-on=info fails on anything", () => {
86
- expect(computeExitCode({ ...empty, info: 1 }, "info").code).toBe(1);
87
- expect(computeExitCode({ ...empty, warning: 1 }, "info").code).toBe(1);
88
- expect(computeExitCode({ ...empty, critical: 1 }, "info").code).toBe(1);
89
- });
90
- it("reason mentions all triggered severities", () => {
91
- const result = computeExitCode({ ...empty, critical: 1, warning: 1 }, "warning");
92
- expect(result.code).toBe(1);
93
- expect(result.reason).toContain("critical");
94
- expect(result.reason).toContain("warning");
95
- });
96
- });
97
- describe("renderJson", () => {
98
- function makeReport(overrides = {}) {
99
- return {
100
- version: 1,
101
- context: "hetzner-prod",
102
- namespaces: ["kubeagent"],
103
- scannedAt: "2026-05-28T10:00:00.000Z",
104
- durationMs: 1200,
105
- summary: { critical: 1, warning: 0, info: 0, podCount: 14, nodeCount: 3 },
106
- issues: [
107
- makeIssue({
108
- namespace: "kubeagent",
109
- resource: "kubeagent-server-7d9-xj2",
110
- message: "CrashLoopBackOff (5 restarts)",
111
- details: { restartCount: 5 },
112
- }),
113
- ],
114
- diagnosis: null,
115
- exit: { code: 1, reason: "fail-on=critical threshold met (critical)" },
116
- ...overrides,
117
- };
118
- }
119
- it("emits versioned schema", () => {
120
- const parsed = JSON.parse(renderJson(makeReport()));
121
- expect(parsed.version).toBe(1);
122
- });
123
- it("strips timestamp from issues (not part of v1 schema)", () => {
124
- const parsed = JSON.parse(renderJson(makeReport()));
125
- expect(parsed.issues[0]).not.toHaveProperty("timestamp");
126
- expect(parsed.issues[0].kind).toBe("pod_crashloop");
127
- expect(parsed.issues[0].details.restartCount).toBe(5);
128
- });
129
- it("emits diagnosis: null when not run", () => {
130
- const parsed = JSON.parse(renderJson(makeReport()));
131
- expect(parsed.diagnosis).toBeNull();
132
- });
133
- it("includes diagnosis when present", () => {
134
- const parsed = JSON.parse(renderJson(makeReport({ diagnosis: { analysis: "Root cause: DB unreachable" } })));
135
- expect(parsed.diagnosis.analysis).toContain("DB unreachable");
136
- });
137
- it("handles empty issues", () => {
138
- const parsed = JSON.parse(renderJson(makeReport({
139
- issues: [],
140
- summary: { critical: 0, warning: 0, info: 0, podCount: 14, nodeCount: 3 },
141
- exit: { code: 0, reason: "no issues at or above fail-on=critical" },
142
- })));
143
- expect(parsed.issues).toEqual([]);
144
- expect(parsed.exit.code).toBe(0);
145
- });
146
- });
147
- describe("renderHuman", () => {
148
- const stripAnsi = (s) => s.replace(/\x1b\[[0-9;]*m/g, "");
149
- function makeReport(overrides = {}) {
150
- return {
151
- version: 1,
152
- context: "hetzner-prod",
153
- namespaces: "all",
154
- scannedAt: "2026-05-28T10:00:00.000Z",
155
- durationMs: 1200,
156
- summary: { critical: 0, warning: 0, info: 0, podCount: 14, nodeCount: 3 },
157
- issues: [],
158
- diagnosis: null,
159
- exit: { code: 0, reason: "no issues at or above fail-on=critical" },
160
- ...overrides,
161
- };
162
- }
163
- it("shows 'all clear' when no issues", () => {
164
- const out = stripAnsi(renderHuman(makeReport()));
165
- expect(out).toContain("all clear");
166
- expect(out).toContain("Exit: 0");
167
- });
168
- it("renders critical issues with severity column", () => {
169
- const out = stripAnsi(renderHuman(makeReport({
170
- summary: { critical: 1, warning: 0, info: 0, podCount: 14, nodeCount: 3 },
171
- issues: [
172
- {
173
- kind: "pod_crashloop",
174
- severity: "critical",
175
- namespace: "kubeagent",
176
- resource: "server-xj2",
177
- message: "CrashLoopBackOff",
178
- details: {},
179
- timestamp: new Date(),
180
- },
181
- ],
182
- exit: { code: 1, reason: "fail-on=critical threshold met (critical)" },
183
- })));
184
- expect(out).toContain("1 critical");
185
- expect(out).toContain("server-xj2");
186
- expect(out).toContain("CrashLoopBackOff");
187
- expect(out).toContain("Exit: 1");
188
- });
189
- it("shows diagnosis block when present", () => {
190
- const out = stripAnsi(renderHuman(makeReport({ diagnosis: { analysis: "Root cause analysis here" } })));
191
- expect(out).toContain("Diagnosis");
192
- expect(out).toContain("Root cause analysis here");
193
- });
194
- it("shows 'all' for cluster-wide scope", () => {
195
- const out = stripAnsi(renderHuman(makeReport({ namespaces: "all" })));
196
- expect(out).toContain("namespace: all");
197
- });
198
- it("shows namespace list for scoped runs", () => {
199
- const out = stripAnsi(renderHuman(makeReport({ namespaces: ["foo", "bar"] })));
200
- expect(out).toContain("foo, bar");
201
- });
202
- });
@@ -1,10 +0,0 @@
1
- export interface CheckCliOptions {
2
- context?: string;
3
- namespace?: string[];
4
- allNamespaces?: boolean;
5
- failOn: string;
6
- format: string;
7
- diagnose?: boolean;
8
- timeout: string;
9
- }
10
- export declare function runCheck(opts: CheckCliOptions): Promise<void>;
package/dist/cli.d.ts DELETED
@@ -1,2 +0,0 @@
1
- #!/usr/bin/env node
2
- export {};
package/dist/config.d.ts DELETED
@@ -1,75 +0,0 @@
1
- export interface ProjectMapping {
2
- name: string;
3
- dir: string;
4
- deployment: string;
5
- namespace: string;
6
- kind: "Deployment" | "StatefulSet";
7
- }
8
- export interface ClusterConfig {
9
- context: string;
10
- interval: number;
11
- codepaths: string[];
12
- projects?: ProjectMapping[];
13
- }
14
- export type NotificationSeverity = "info" | "warning" | "critical";
15
- export interface SlackChannel {
16
- type: "slack";
17
- webhook_url: string;
18
- severity: NotificationSeverity;
19
- label?: string;
20
- }
21
- export interface TelegramChannel {
22
- type: "telegram";
23
- bot_token: string;
24
- chat_id: string;
25
- severity: NotificationSeverity;
26
- label?: string;
27
- }
28
- export interface WebhookChannel {
29
- type: "webhook";
30
- url: string;
31
- secret?: string;
32
- severity: NotificationSeverity;
33
- label?: string;
34
- }
35
- export interface PagerDutyChannel {
36
- type: "pagerduty";
37
- routing_key: string;
38
- severity: NotificationSeverity;
39
- label?: string;
40
- }
41
- export interface DiscordChannel {
42
- type: "discord";
43
- webhook_url: string;
44
- severity: NotificationSeverity;
45
- label?: string;
46
- }
47
- export interface TeamsChannel {
48
- type: "teams";
49
- webhook_url: string;
50
- severity: NotificationSeverity;
51
- label?: string;
52
- }
53
- export type NotificationChannel = SlackChannel | TelegramChannel | WebhookChannel | PagerDutyChannel | DiscordChannel | TeamsChannel;
54
- export declare const ALL_ACTIONS: readonly ["restart_pod", "rollout_restart", "scale_deployment", "set_resources"];
55
- export type RemediationAction = (typeof ALL_ACTIONS)[number];
56
- export interface KubeAgentConfig {
57
- clusters: ClusterConfig[];
58
- notifications: {
59
- terminal: boolean;
60
- channels: NotificationChannel[];
61
- };
62
- remediation: {
63
- auto_fix: boolean;
64
- max_retries: number;
65
- cooldown: number;
66
- safe_actions: RemediationAction[];
67
- };
68
- }
69
- export declare const DEFAULT_SAFE_ACTIONS: RemediationAction[];
70
- export declare function defaultConfig(): KubeAgentConfig;
71
- export declare function parseConfig(raw: Record<string, unknown>): KubeAgentConfig;
72
- export declare function configDir(): string;
73
- export declare function configPath(): string;
74
- export declare function loadConfig(): KubeAgentConfig;
75
- export declare function saveConfig(config: KubeAgentConfig): void;
package/dist/debug.d.ts DELETED
@@ -1,10 +0,0 @@
1
- /**
2
- * Debug logging — enabled with KUBEAGENT_DEBUG=1 (or any truthy value).
3
- *
4
- * Usage:
5
- * KUBEAGENT_DEBUG=1 kubeagent watch
6
- *
7
- * Logs to stderr so it doesn't interfere with piped output.
8
- */
9
- export declare const DEBUG: boolean;
10
- export declare function dbg(tag: string, message: string, data?: unknown): void;
@@ -1,6 +0,0 @@
1
- export declare function readCache<T>(kind: string, key: string): T | null;
2
- export declare function readStaleCache<T>(kind: string, key: string): {
3
- data: T;
4
- fetchedAt: number;
5
- } | null;
6
- export declare function writeCache<T>(kind: string, key: string, data: T): void;
@@ -1 +0,0 @@
1
- export {};
@@ -1,54 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
- import { mkdtempSync, rmSync, existsSync, writeFileSync } from "node:fs";
3
- import { join } from "node:path";
4
- import { tmpdir } from "node:os";
5
- let tmpHome;
6
- let originalHome;
7
- beforeEach(() => {
8
- originalHome = process.env.HOME;
9
- tmpHome = mkdtempSync(join(tmpdir(), "kubeagent-cache-test-"));
10
- process.env.HOME = tmpHome;
11
- });
12
- afterEach(() => {
13
- if (originalHome !== undefined)
14
- process.env.HOME = originalHome;
15
- else
16
- delete process.env.HOME;
17
- rmSync(tmpHome, { recursive: true, force: true });
18
- });
19
- describe("cache", () => {
20
- it("round-trips data within the TTL", async () => {
21
- const { readCache, writeCache } = await import("./cache.js");
22
- writeCache("osv", "redis@7.0.5", [{ id: "CVE-1" }]);
23
- const got = readCache("osv", "redis@7.0.5");
24
- expect(got).toEqual([{ id: "CVE-1" }]);
25
- });
26
- it("returns null for a missing entry", async () => {
27
- const { readCache } = await import("./cache.js");
28
- expect(readCache("osv", "nope")).toBeNull();
29
- });
30
- it("creates the cache directory on write", async () => {
31
- const { writeCache } = await import("./cache.js");
32
- writeCache("eol", "postgres", [{ cycle: "15" }]);
33
- expect(existsSync(join(tmpHome, ".kubeagent", "cache", "eol", "postgres.json"))).toBe(true);
34
- });
35
- it("sanitizes unsafe characters in cache keys", async () => {
36
- const { writeCache } = await import("./cache.js");
37
- writeCache("osv", "foo/../bar@1.0", { x: 1 });
38
- expect(existsSync(join(tmpHome, ".kubeagent", "cache", "osv", "foo_.._bar@1.0.json"))).toBe(true);
39
- });
40
- it("readStaleCache returns data and a fetchedAt timestamp", async () => {
41
- const { writeCache, readStaleCache } = await import("./cache.js");
42
- writeCache("osv", "k", "v");
43
- const stale = readStaleCache("osv", "k");
44
- expect(stale?.data).toBe("v");
45
- expect(typeof stale?.fetchedAt).toBe("number");
46
- });
47
- it("treats a malformed cache file as a miss", async () => {
48
- const { writeCache, readCache } = await import("./cache.js");
49
- writeCache("osv", "k", "v");
50
- const path = join(tmpHome, ".kubeagent", "cache", "osv", "k.json");
51
- writeFileSync(path, "not json");
52
- expect(readCache("osv", "k")).toBeNull();
53
- });
54
- });
@@ -1,17 +0,0 @@
1
- export interface OsvLookup {
2
- name: string;
3
- ecosystem?: string;
4
- }
5
- export interface ProductEntry {
6
- display: string;
7
- eolProduct?: string;
8
- osv?: OsvLookup;
9
- }
10
- export declare const PRODUCTS: Record<string, ProductEntry>;
11
- export declare const ALIASES: Record<string, string>;
12
- export interface CatalogMatch {
13
- canonical: string;
14
- entry: ProductEntry;
15
- }
16
- export declare function lookupCatalog(repository: string, name: string): CatalogMatch | null;
17
- export declare function lookupChart(chartName: string): CatalogMatch | null;
@@ -1 +0,0 @@
1
- export {};
@@ -1,33 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { lookupCatalog, lookupChart } from "./catalog.js";
3
- describe("lookupCatalog", () => {
4
- it("matches bare image names from the products map", () => {
5
- const m = lookupCatalog("library/postgres", "postgres");
6
- expect(m?.canonical).toBe("postgres");
7
- expect(m?.entry.display).toBe("PostgreSQL");
8
- });
9
- it("resolves aliases for bitnami charts", () => {
10
- const m = lookupCatalog("bitnami/postgresql", "postgresql");
11
- expect(m?.canonical).toBe("postgres");
12
- });
13
- it("resolves quay.io prometheus", () => {
14
- const m = lookupCatalog("prometheus/prometheus", "prometheus");
15
- expect(m?.canonical).toBe("prometheus");
16
- });
17
- it("handles case-insensitive lookups", () => {
18
- const m = lookupCatalog("Library/PostgreSQL", "PostgreSQL");
19
- expect(m?.canonical).toBe("postgres");
20
- });
21
- it("returns null for unknown repositories", () => {
22
- expect(lookupCatalog("acme/internal-app", "internal-app")).toBeNull();
23
- });
24
- });
25
- describe("lookupChart", () => {
26
- it("matches chart names to canonical products", () => {
27
- expect(lookupChart("postgresql")?.canonical).toBe("postgres");
28
- expect(lookupChart("redis")?.canonical).toBe("redis");
29
- });
30
- it("returns null for unknown charts", () => {
31
- expect(lookupChart("unknown-chart")).toBeNull();
32
- });
33
- });
@@ -1,13 +0,0 @@
1
- export interface EolStatus {
2
- product: string;
3
- cycle: string;
4
- eol: boolean;
5
- eolDate?: string;
6
- latest?: string;
7
- releaseDate?: string;
8
- lts: boolean;
9
- }
10
- export declare function queryEol(product: string, version: string, opts?: {
11
- noCache?: boolean;
12
- timeoutMs?: number;
13
- }): Promise<EolStatus | null>;
@@ -1 +0,0 @@
1
- export {};
@@ -1,78 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach } from "vitest";
2
- vi.mock("./cache.js", () => ({
3
- readCache: vi.fn(),
4
- readStaleCache: vi.fn(),
5
- writeCache: vi.fn(),
6
- }));
7
- import { readCache, readStaleCache, writeCache } from "./cache.js";
8
- import { queryEol } from "./eol.js";
9
- const mockFetch = vi.fn();
10
- vi.stubGlobal("fetch", mockFetch);
11
- const cacheRead = vi.mocked(readCache);
12
- const cacheStale = vi.mocked(readStaleCache);
13
- const cacheWrite = vi.mocked(writeCache);
14
- const POSTGRES_FIXTURE = [
15
- { cycle: "16", releaseDate: "2023-09-14", eol: "2028-11-09", latest: "16.2", lts: false },
16
- { cycle: "15", releaseDate: "2022-10-13", eol: "2027-11-11", latest: "15.6", lts: false },
17
- { cycle: "11", releaseDate: "2018-10-18", eol: "2023-11-09", latest: "11.22", lts: false },
18
- ];
19
- describe("queryEol", () => {
20
- beforeEach(() => {
21
- mockFetch.mockReset();
22
- cacheRead.mockReset();
23
- cacheStale.mockReset();
24
- cacheWrite.mockReset();
25
- cacheRead.mockReturnValue(null);
26
- cacheStale.mockReturnValue(null);
27
- });
28
- it("hits endoflife.date and picks the matching cycle by major version", async () => {
29
- mockFetch.mockResolvedValue({ ok: true, json: async () => POSTGRES_FIXTURE });
30
- const status = await queryEol("postgres", "15.2", { noCache: true });
31
- expect(mockFetch.mock.calls[0][0]).toBe("https://endoflife.date/api/postgres.json");
32
- expect(status).toMatchObject({
33
- product: "postgres",
34
- cycle: "15",
35
- eol: false,
36
- eolDate: "2027-11-11",
37
- latest: "15.6",
38
- });
39
- expect(cacheWrite).toHaveBeenCalledOnce();
40
- });
41
- it("flags a cycle as EOL when its date is in the past", async () => {
42
- mockFetch.mockResolvedValue({ ok: true, json: async () => POSTGRES_FIXTURE });
43
- const status = await queryEol("postgres", "11.18", { noCache: true });
44
- expect(status?.cycle).toBe("11");
45
- expect(status?.eol).toBe(true);
46
- expect(status?.eolDate).toBe("2023-11-09");
47
- });
48
- it("returns null when no cycle matches", async () => {
49
- mockFetch.mockResolvedValue({ ok: true, json: async () => POSTGRES_FIXTURE });
50
- const status = await queryEol("postgres", "99.0", { noCache: true });
51
- expect(status).toBeNull();
52
- });
53
- it("returns null on network error with no cache", async () => {
54
- mockFetch.mockRejectedValue(new Error("network down"));
55
- const status = await queryEol("postgres", "15.2", { noCache: true });
56
- expect(status).toBeNull();
57
- });
58
- it("serves a fresh cached entry without hitting the network", async () => {
59
- cacheRead.mockReturnValue(POSTGRES_FIXTURE);
60
- const status = await queryEol("postgres", "15.2");
61
- expect(mockFetch).not.toHaveBeenCalled();
62
- expect(status?.cycle).toBe("15");
63
- });
64
- it("falls back to stale cache on network error", async () => {
65
- cacheStale.mockReturnValue({ data: POSTGRES_FIXTURE, fetchedAt: 1 });
66
- mockFetch.mockRejectedValue(new Error("offline"));
67
- const status = await queryEol("postgres", "11.18", { noCache: true });
68
- expect(status?.eol).toBe(true);
69
- });
70
- it("treats eol: true as EOL regardless of date", async () => {
71
- mockFetch.mockResolvedValue({
72
- ok: true,
73
- json: async () => [{ cycle: "1.0", eol: true, latest: "1.0.5" }],
74
- });
75
- const status = await queryEol("oldthing", "1.0", { noCache: true });
76
- expect(status?.eol).toBe(true);
77
- });
78
- });
@@ -1,11 +0,0 @@
1
- export interface ParsedImage {
2
- registry: string;
3
- repository: string;
4
- name: string;
5
- tag: string;
6
- digest?: string;
7
- }
8
- export declare function parseImage(image: string): ParsedImage;
9
- export declare function normalizeVersion(tag: string): string;
10
- export declare function majorMinor(version: string): string;
11
- export declare function major(version: string): string;
@@ -1 +0,0 @@
1
- export {};