@vfarcic/dot-ai 0.4.9 → 0.5.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.
Files changed (145) hide show
  1. package/.claude/commands/context-load.md +11 -0
  2. package/.claude/commands/context-save.md +16 -0
  3. package/.claude/commands/prd-done.md +115 -0
  4. package/.claude/commands/prd-get.md +25 -0
  5. package/.claude/commands/prd-start.md +87 -0
  6. package/.claude/commands/task-done.md +77 -0
  7. package/.claude/commands/tests-reminder.md +32 -0
  8. package/.claude/settings.local.json +20 -0
  9. package/.eslintrc.json +25 -0
  10. package/.github/workflows/ci.yml +170 -0
  11. package/.prettierrc.json +10 -0
  12. package/.teller.yml +8 -0
  13. package/CLAUDE.md +162 -0
  14. package/assets/images/logo.png +0 -0
  15. package/bin/dot-ai.ts +47 -0
  16. package/destroy.sh +45 -0
  17. package/devbox.json +13 -0
  18. package/devbox.lock +225 -0
  19. package/docs/API.md +449 -0
  20. package/docs/CONTEXT.md +49 -0
  21. package/docs/DEVELOPMENT.md +203 -0
  22. package/docs/NEXT_STEPS.md +97 -0
  23. package/docs/STAGE_BASED_API.md +97 -0
  24. package/docs/cli-guide.md +798 -0
  25. package/docs/design.md +750 -0
  26. package/docs/discovery-engine.md +515 -0
  27. package/docs/error-handling.md +429 -0
  28. package/docs/function-registration.md +157 -0
  29. package/docs/mcp-guide.md +416 -0
  30. package/package.json +2 -123
  31. package/renovate.json +51 -0
  32. package/setup.sh +111 -0
  33. package/{dist/cli.js → src/cli.ts} +26 -19
  34. package/src/core/claude.ts +280 -0
  35. package/src/core/deploy-operation.ts +127 -0
  36. package/src/core/discovery.ts +900 -0
  37. package/src/core/error-handling.ts +562 -0
  38. package/src/core/index.ts +143 -0
  39. package/src/core/kubernetes-utils.ts +218 -0
  40. package/src/core/memory.ts +148 -0
  41. package/src/core/schema.ts +830 -0
  42. package/src/core/session-utils.ts +97 -0
  43. package/src/core/workflow.ts +234 -0
  44. package/src/index.ts +18 -0
  45. package/src/interfaces/cli.ts +872 -0
  46. package/src/interfaces/mcp.ts +183 -0
  47. package/src/mcp/server.ts +131 -0
  48. package/src/tools/answer-question.ts +807 -0
  49. package/src/tools/choose-solution.ts +169 -0
  50. package/src/tools/deploy-manifests.ts +94 -0
  51. package/src/tools/generate-manifests.ts +502 -0
  52. package/src/tools/index.ts +41 -0
  53. package/src/tools/recommend.ts +370 -0
  54. package/tests/__mocks__/@kubernetes/client-node.ts +106 -0
  55. package/tests/build-system.test.ts +345 -0
  56. package/tests/configuration.test.ts +226 -0
  57. package/tests/core/deploy-operation.test.ts +38 -0
  58. package/tests/core/discovery.test.ts +1648 -0
  59. package/tests/core/error-handling.test.ts +632 -0
  60. package/tests/core/schema.test.ts +1658 -0
  61. package/tests/core/session-utils.test.ts +245 -0
  62. package/tests/core.test.ts +439 -0
  63. package/tests/fixtures/configmap-no-labels.yaml +8 -0
  64. package/tests/fixtures/crossplane-app-configuration.yaml +6 -0
  65. package/tests/fixtures/crossplane-providers.yaml +45 -0
  66. package/tests/fixtures/crossplane-rbac.yaml +48 -0
  67. package/tests/fixtures/invalid-configmap.yaml +8 -0
  68. package/tests/fixtures/invalid-deployment.yaml +17 -0
  69. package/tests/fixtures/test-deployment.yaml +28 -0
  70. package/tests/fixtures/valid-configmap.yaml +15 -0
  71. package/tests/infrastructure.test.ts +426 -0
  72. package/tests/interfaces/cli.test.ts +1036 -0
  73. package/tests/interfaces/mcp.test.ts +139 -0
  74. package/tests/kubernetes-utils.test.ts +200 -0
  75. package/tests/mcp/server.test.ts +126 -0
  76. package/tests/setup.ts +31 -0
  77. package/tests/tools/answer-question.test.ts +367 -0
  78. package/tests/tools/choose-solution.test.ts +481 -0
  79. package/tests/tools/deploy-manifests.test.ts +185 -0
  80. package/tests/tools/generate-manifests.test.ts +441 -0
  81. package/tests/tools/index.test.ts +111 -0
  82. package/tests/tools/recommend.test.ts +180 -0
  83. package/tsconfig.json +34 -0
  84. package/dist/cli.d.ts +0 -3
  85. package/dist/cli.d.ts.map +0 -1
  86. package/dist/core/claude.d.ts +0 -42
  87. package/dist/core/claude.d.ts.map +0 -1
  88. package/dist/core/claude.js +0 -229
  89. package/dist/core/deploy-operation.d.ts +0 -38
  90. package/dist/core/deploy-operation.d.ts.map +0 -1
  91. package/dist/core/deploy-operation.js +0 -101
  92. package/dist/core/discovery.d.ts +0 -162
  93. package/dist/core/discovery.d.ts.map +0 -1
  94. package/dist/core/discovery.js +0 -758
  95. package/dist/core/error-handling.d.ts +0 -167
  96. package/dist/core/error-handling.d.ts.map +0 -1
  97. package/dist/core/error-handling.js +0 -399
  98. package/dist/core/index.d.ts +0 -42
  99. package/dist/core/index.d.ts.map +0 -1
  100. package/dist/core/index.js +0 -123
  101. package/dist/core/kubernetes-utils.d.ts +0 -38
  102. package/dist/core/kubernetes-utils.d.ts.map +0 -1
  103. package/dist/core/kubernetes-utils.js +0 -177
  104. package/dist/core/memory.d.ts +0 -45
  105. package/dist/core/memory.d.ts.map +0 -1
  106. package/dist/core/memory.js +0 -113
  107. package/dist/core/schema.d.ts +0 -187
  108. package/dist/core/schema.d.ts.map +0 -1
  109. package/dist/core/schema.js +0 -655
  110. package/dist/core/session-utils.d.ts +0 -29
  111. package/dist/core/session-utils.d.ts.map +0 -1
  112. package/dist/core/session-utils.js +0 -121
  113. package/dist/core/workflow.d.ts +0 -70
  114. package/dist/core/workflow.d.ts.map +0 -1
  115. package/dist/core/workflow.js +0 -161
  116. package/dist/index.d.ts +0 -15
  117. package/dist/index.d.ts.map +0 -1
  118. package/dist/index.js +0 -32
  119. package/dist/interfaces/cli.d.ts +0 -74
  120. package/dist/interfaces/cli.d.ts.map +0 -1
  121. package/dist/interfaces/cli.js +0 -769
  122. package/dist/interfaces/mcp.d.ts +0 -30
  123. package/dist/interfaces/mcp.d.ts.map +0 -1
  124. package/dist/interfaces/mcp.js +0 -105
  125. package/dist/mcp/server.d.ts +0 -9
  126. package/dist/mcp/server.d.ts.map +0 -1
  127. package/dist/mcp/server.js +0 -151
  128. package/dist/tools/answer-question.d.ts +0 -27
  129. package/dist/tools/answer-question.d.ts.map +0 -1
  130. package/dist/tools/answer-question.js +0 -696
  131. package/dist/tools/choose-solution.d.ts +0 -23
  132. package/dist/tools/choose-solution.d.ts.map +0 -1
  133. package/dist/tools/choose-solution.js +0 -171
  134. package/dist/tools/deploy-manifests.d.ts +0 -25
  135. package/dist/tools/deploy-manifests.d.ts.map +0 -1
  136. package/dist/tools/deploy-manifests.js +0 -74
  137. package/dist/tools/generate-manifests.d.ts +0 -23
  138. package/dist/tools/generate-manifests.d.ts.map +0 -1
  139. package/dist/tools/generate-manifests.js +0 -424
  140. package/dist/tools/index.d.ts +0 -11
  141. package/dist/tools/index.d.ts.map +0 -1
  142. package/dist/tools/index.js +0 -34
  143. package/dist/tools/recommend.d.ts +0 -23
  144. package/dist/tools/recommend.d.ts.map +0 -1
  145. package/dist/tools/recommend.js +0 -332
@@ -0,0 +1,370 @@
1
+ /**
2
+ * Recommend Tool - AI-powered Kubernetes resource recommendations
3
+ */
4
+
5
+ import { z } from 'zod';
6
+ import { ErrorHandler, ErrorCategory, ErrorSeverity } from '../core/error-handling';
7
+ import { ResourceRecommender, AIRankingConfig } from '../core/schema';
8
+ import { ClaudeIntegration } from '../core/claude';
9
+ import { DotAI } from '../core/index';
10
+ import { Logger } from '../core/error-handling';
11
+ import * as fs from 'fs';
12
+ import * as path from 'path';
13
+ import * as crypto from 'crypto';
14
+ import { getAndValidateSessionDirectory } from '../core/session-utils';
15
+
16
+ // Tool metadata for direct MCP registration
17
+ export const RECOMMEND_TOOL_NAME = 'recommend';
18
+ export const RECOMMEND_TOOL_DESCRIPTION = 'Deploy, create, run, or setup applications on Kubernetes with AI-powered recommendations. Ask the user to describe their application first, then use their response here.';
19
+
20
+ // Zod schema for MCP registration
21
+ export const RECOMMEND_TOOL_INPUT_SCHEMA = {
22
+ intent: z.string().min(1).max(1000).describe('What the user wants to deploy, create, run, or setup on Kubernetes (based on their description). Ask the user to describe their application first, then use their response here. Examples: "deploy a web application", "create a database cluster", "run my Node.js API", "setup a Redis cache", "launch a microservice", "build a CI/CD pipeline", "deploy a WordPress site", "create a monitoring stack", "run a Python Flask app", "setup MongoDB", "deploy a React frontend", "create a load balancer"')
23
+ };
24
+
25
+
26
+ /**
27
+ * Validate intent meaningfulness using AI
28
+ */
29
+ async function validateIntentWithAI(intent: string, claudeIntegration: any): Promise<void> {
30
+ try {
31
+ // Load prompt template
32
+ const promptPath = path.join(process.cwd(), 'prompts', 'intent-validation.md');
33
+ const template = fs.readFileSync(promptPath, 'utf8');
34
+
35
+ // Replace template variables
36
+ const validationPrompt = template.replace('{intent}', intent);
37
+
38
+ // Send to Claude for validation
39
+ const response = await claudeIntegration.sendMessage(validationPrompt);
40
+
41
+ // Parse JSON response with robust error handling
42
+ let jsonContent = response.content;
43
+
44
+ // Try to find JSON object wrapped in code blocks
45
+ const codeBlockMatch = response.content.match(/```(?:json)?\s*(\{[\s\S]*?\})\s*```/);
46
+ if (codeBlockMatch) {
47
+ jsonContent = codeBlockMatch[1];
48
+ } else {
49
+ // Try to find JSON object that starts with { and find the matching closing }
50
+ const startIndex = response.content.indexOf('{');
51
+ if (startIndex !== -1) {
52
+ let braceCount = 0;
53
+ let endIndex = startIndex;
54
+
55
+ for (let i = startIndex; i < response.content.length; i++) {
56
+ if (response.content[i] === '{') braceCount++;
57
+ if (response.content[i] === '}') braceCount--;
58
+ if (braceCount === 0) {
59
+ endIndex = i;
60
+ break;
61
+ }
62
+ }
63
+
64
+ if (braceCount === 0) {
65
+ jsonContent = response.content.substring(startIndex, endIndex + 1);
66
+ }
67
+ }
68
+ }
69
+
70
+ const validation = JSON.parse(jsonContent.trim());
71
+
72
+ // Validate response structure
73
+ if (typeof validation.isSpecific !== 'boolean' ||
74
+ typeof validation.reason !== 'string' ||
75
+ !Array.isArray(validation.suggestions)) {
76
+ throw new Error('AI response has invalid structure');
77
+ }
78
+
79
+ // If intent is not specific enough, throw error with suggestions
80
+ if (!validation.isSpecific) {
81
+ const suggestions = validation.suggestions.length
82
+ ? validation.suggestions.map((s: string) => `• ${s}`).join('\n')
83
+ : '• Include specific technology (Node.js, PostgreSQL, React, etc.)\n• Describe the purpose or function\n• Add context about requirements';
84
+
85
+ throw new Error(
86
+ `Intent needs more specificity: ${validation.reason}\n\n` +
87
+ `Suggestions to improve your intent:\n${suggestions}\n\n` +
88
+ `Original intent: "${intent}"`
89
+ );
90
+ }
91
+
92
+ } catch (error) {
93
+ // If it's our validation error, re-throw it
94
+ if (error instanceof Error && error.message.includes('Intent needs more specificity')) {
95
+ throw error;
96
+ }
97
+
98
+ // For other errors (AI service issues, JSON parsing, etc.),
99
+ // continue without blocking the user - log the issue but don't fail
100
+ console.warn('Intent validation failed, continuing with original intent:', error);
101
+ return;
102
+ }
103
+ }
104
+
105
+
106
+ /**
107
+ * Generate unique solution ID with timestamp and random component
108
+ */
109
+ function generateSolutionId(): string {
110
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '').split('T');
111
+ const dateTime = timestamp[0] + 'T' + timestamp[1].substring(0, 6);
112
+ const randomHex = crypto.randomBytes(6).toString('hex');
113
+ return `sol_${dateTime}_${randomHex}`;
114
+ }
115
+
116
+ /**
117
+ * Write solution data to file atomically (temp file + rename)
118
+ */
119
+ function writeSolutionFile(sessionDir: string, solutionId: string, solutionData: any): void {
120
+ const fileName = `${solutionId}.json`;
121
+ const filePath = path.join(sessionDir, fileName);
122
+ const tempPath = filePath + '.tmp';
123
+
124
+ try {
125
+ // Write to temporary file first
126
+ fs.writeFileSync(tempPath, JSON.stringify(solutionData, null, 2));
127
+
128
+ // Atomically rename to final location
129
+ fs.renameSync(tempPath, filePath);
130
+ } catch (error) {
131
+ // Clean up temp file if it exists
132
+ try {
133
+ if (fs.existsSync(tempPath)) {
134
+ fs.unlinkSync(tempPath);
135
+ }
136
+ } catch (cleanupError) {
137
+ // Ignore cleanup errors
138
+ }
139
+
140
+ throw new Error(`Failed to write solution file ${fileName}: ${error}`);
141
+ }
142
+ }
143
+
144
+ /**
145
+ * Direct MCP tool handler for recommend functionality
146
+ */
147
+ export async function handleRecommendTool(
148
+ args: { intent: string },
149
+ dotAI: DotAI,
150
+ logger: Logger,
151
+ requestId: string
152
+ ): Promise<{ content: { type: 'text'; text: string }[] }> {
153
+ return await ErrorHandler.withErrorHandling(
154
+ async () => {
155
+ logger.debug('Handling recommend request', { requestId, intent: args?.intent });
156
+
157
+ // Input validation is handled automatically by MCP SDK with Zod schema
158
+ // args are already validated and typed when we reach this point
159
+
160
+ // Check for Claude API key
161
+ const claudeApiKey = dotAI.getAnthropicApiKey();
162
+ if (!claudeApiKey) {
163
+ throw ErrorHandler.createError(
164
+ ErrorCategory.AI_SERVICE,
165
+ ErrorSeverity.HIGH,
166
+ 'ANTHROPIC_API_KEY environment variable must be set for AI-powered resource recommendations',
167
+ {
168
+ operation: 'api_key_check',
169
+ component: 'RecommendTool',
170
+ requestId,
171
+ suggestedActions: [
172
+ 'Set ANTHROPIC_API_KEY environment variable',
173
+ 'Verify the API key is valid and active',
174
+ 'Check that the API key has sufficient credits'
175
+ ]
176
+ }
177
+ );
178
+ }
179
+
180
+ // Validate session directory configuration
181
+ let sessionDir: string;
182
+ try {
183
+ sessionDir = getAndValidateSessionDirectory(args, true); // requireWrite=true
184
+ logger.debug('Session directory validated', { requestId, sessionDir });
185
+ } catch (error) {
186
+ throw ErrorHandler.createError(
187
+ ErrorCategory.VALIDATION,
188
+ ErrorSeverity.HIGH,
189
+ `Session directory validation failed: ${error instanceof Error ? error.message : 'Unknown error'}`,
190
+ {
191
+ operation: 'session_directory_validation',
192
+ component: 'RecommendTool',
193
+ requestId,
194
+ suggestedActions: [
195
+ 'Ensure session directory exists and is writable',
196
+ 'Set --session-dir parameter or DOT_AI_SESSION_DIR environment variable',
197
+ 'Check directory permissions'
198
+ ]
199
+ },
200
+ error instanceof Error ? error : new Error(String(error))
201
+ );
202
+ }
203
+
204
+ logger.info('Starting resource recommendation process', {
205
+ requestId,
206
+ intent: args.intent,
207
+ hasApiKey: !!claudeApiKey
208
+ });
209
+
210
+ // Validate intent specificity with AI before expensive resource discovery
211
+ logger.debug('Validating intent specificity', { requestId, intent: args.intent });
212
+ try {
213
+ const claudeIntegration = new ClaudeIntegration(claudeApiKey);
214
+ await validateIntentWithAI(args.intent, claudeIntegration);
215
+ logger.debug('Intent validation passed', { requestId });
216
+ } catch (error) {
217
+ if (error instanceof Error && error.message.includes('Intent needs more specificity')) {
218
+ // This is a validation error that should be returned to the user
219
+ throw ErrorHandler.createError(
220
+ ErrorCategory.VALIDATION,
221
+ ErrorSeverity.MEDIUM,
222
+ error.message,
223
+ {
224
+ operation: 'intent_validation',
225
+ component: 'RecommendTool',
226
+ requestId,
227
+ input: { intent: args.intent },
228
+ suggestedActions: [
229
+ 'Provide more specific details about your deployment',
230
+ 'Include technology stack information',
231
+ 'Describe the purpose or function of what you want to deploy'
232
+ ]
233
+ },
234
+ error
235
+ );
236
+ }
237
+ // For other errors, log but continue (don't block user due to AI service issues)
238
+ logger.warn('Intent validation failed, continuing with recommendation', { requestId, error: error instanceof Error ? error.message : 'Unknown error' });
239
+ }
240
+
241
+ // Initialize AI-powered ResourceRecommender
242
+ const rankingConfig: AIRankingConfig = { claudeApiKey };
243
+ const recommender = new ResourceRecommender(rankingConfig);
244
+
245
+ // Create discovery functions
246
+ const discoverResourcesFn = async () => {
247
+ logger.debug('Discovering cluster resources', { requestId });
248
+ return await dotAI.discovery.discoverResources();
249
+ };
250
+
251
+ const explainResourceFn = async (resource: string) => {
252
+ logger.debug(`Explaining resource: ${resource}`, { requestId });
253
+ return await dotAI.discovery.explainResource(resource);
254
+ };
255
+
256
+ // Find best solutions for the user intent
257
+ logger.debug('Generating recommendations with AI', { requestId });
258
+ const solutions = await recommender.findBestSolutions(
259
+ args.intent,
260
+ discoverResourcesFn,
261
+ explainResourceFn
262
+ );
263
+
264
+ logger.info('Recommendation process completed', {
265
+ requestId,
266
+ solutionCount: solutions.length,
267
+ topScore: solutions[0]?.score
268
+ });
269
+
270
+ // Create solution files and build response
271
+ const solutionSummaries = [];
272
+ const timestamp = new Date().toISOString();
273
+
274
+ // Limit to top 5 solutions (respecting quality thresholds from AI ranking)
275
+ const topSolutions = solutions.slice(0, 5);
276
+
277
+ for (const solution of topSolutions) {
278
+ const solutionId = generateSolutionId();
279
+
280
+ // Create complete solution file with all data
281
+ const solutionFileData = {
282
+ solutionId,
283
+ intent: args.intent,
284
+ type: solution.type,
285
+ score: solution.score,
286
+ description: solution.description,
287
+ reasons: solution.reasons,
288
+ analysis: solution.analysis,
289
+ resources: solution.resources.map(r => ({
290
+ kind: r.kind,
291
+ apiVersion: r.apiVersion,
292
+ group: r.group,
293
+ description: r.description
294
+ })),
295
+ questions: solution.questions,
296
+ answers: {}, // Empty initially - will be filled by answerQuestion tool
297
+ timestamp
298
+ };
299
+
300
+ // Write solution to file
301
+ try {
302
+ writeSolutionFile(sessionDir, solutionId, solutionFileData);
303
+ logger.debug('Solution file created', { requestId, solutionId, fileName: `${solutionId}.json` });
304
+ } catch (error) {
305
+ throw ErrorHandler.createError(
306
+ ErrorCategory.STORAGE,
307
+ ErrorSeverity.HIGH,
308
+ `Failed to store solution file: ${error instanceof Error ? error.message : 'Unknown error'}`,
309
+ {
310
+ operation: 'solution_file_creation',
311
+ component: 'RecommendTool',
312
+ requestId,
313
+ input: { solutionId },
314
+ suggestedActions: [
315
+ 'Check session directory write permissions',
316
+ 'Ensure sufficient disk space',
317
+ 'Verify session directory is accessible'
318
+ ]
319
+ },
320
+ error instanceof Error ? error : new Error(String(error))
321
+ );
322
+ }
323
+
324
+ // Add to response summary (decision-making data only)
325
+ solutionSummaries.push({
326
+ solutionId,
327
+ type: solution.type,
328
+ score: solution.score,
329
+ description: solution.description,
330
+ primaryResources: solution.resources.slice(0, 3).map(r => r.kind),
331
+ reasons: solution.reasons,
332
+ analysis: solution.analysis
333
+ });
334
+ }
335
+
336
+ // Build new response format
337
+ const response = {
338
+ intent: args.intent,
339
+ solutions: solutionSummaries,
340
+ nextAction: "Call chooseSolution with your preferred solutionId",
341
+ guidance: "🛑 NEVER choose automatically - Present ALL solutions to user and ask them to choose by calling chooseSolution(solutionId)",
342
+ timestamp
343
+ };
344
+
345
+ logger.info('Solution files created and response prepared', {
346
+ requestId,
347
+ solutionCount: solutionSummaries.length,
348
+ sessionDir
349
+ });
350
+
351
+
352
+ return {
353
+ content: [{
354
+ type: 'text' as const,
355
+ text: JSON.stringify(response, null, 2)
356
+ }]
357
+ };
358
+ },
359
+ {
360
+ operation: 'recommend_tool',
361
+ component: 'RecommendTool',
362
+ requestId,
363
+ input: args
364
+ },
365
+ {
366
+ convertToMcp: true,
367
+ retryCount: 1
368
+ }
369
+ );
370
+ }
@@ -0,0 +1,106 @@
1
+ // Mock for @kubernetes/client-node to handle ES module compatibility issues
2
+ export const KubeConfig = jest.fn().mockImplementation(() => ({
3
+ loadFromDefault: jest.fn(),
4
+ loadFromFile: jest.fn().mockImplementation((path: string) => {
5
+ // Simulate error for invalid paths in tests
6
+ if (path.includes('/invalid/path') || path.includes('nonexistent')) {
7
+ throw new Error(`Failed to load kubeconfig from ${path}`);
8
+ }
9
+ }),
10
+ loadFromString: jest.fn(),
11
+ loadFromCluster: jest.fn(),
12
+ getCurrentContext: jest.fn().mockReturnValue('test-context'),
13
+ getCurrentCluster: jest.fn().mockReturnValue({
14
+ name: 'test-cluster',
15
+ server: 'https://test-cluster.example.com'
16
+ }),
17
+ makeApiClient: jest.fn().mockReturnValue({
18
+ listNamespace: jest.fn().mockImplementation(() => {
19
+ // Check if this is being called from an invalid discovery instance
20
+ // by checking the call stack or using a flag
21
+ return Promise.resolve({
22
+ items: [
23
+ { metadata: { name: 'default' } },
24
+ { metadata: { name: 'kube-system' } },
25
+ { metadata: { name: 'test-namespace' } }
26
+ ]
27
+ });
28
+ }),
29
+ listCustomResourceDefinition: jest.fn().mockResolvedValue({
30
+ items: [
31
+ {
32
+ metadata: { name: 'test-crd.example.com' },
33
+ spec: {
34
+ group: 'example.com',
35
+ versions: [{ name: 'v1' }],
36
+ names: { kind: 'TestCRD' },
37
+ scope: 'Namespaced'
38
+ }
39
+ }
40
+ ]
41
+ }),
42
+ getAPIVersions: jest.fn().mockResolvedValue({
43
+ groups: [
44
+ { name: 'apps', preferredVersion: { groupVersion: 'apps/v1' } },
45
+ { name: 'v1', preferredVersion: { groupVersion: 'v1' } }
46
+ ]
47
+ }),
48
+ // Add more API methods as needed
49
+ getNamespace: jest.fn().mockResolvedValue({
50
+ metadata: { name: 'test-namespace' }
51
+ }),
52
+ listNode: jest.fn().mockResolvedValue({
53
+ items: [
54
+ { metadata: { name: 'test-node' } }
55
+ ]
56
+ })
57
+ })
58
+ }));
59
+
60
+ export const CoreV1Api = jest.fn().mockImplementation(() => ({
61
+ listNamespace: jest.fn().mockResolvedValue({
62
+ items: [
63
+ { metadata: { name: 'default' } },
64
+ { metadata: { name: 'kube-system' } }
65
+ ]
66
+ }),
67
+ getNamespace: jest.fn().mockResolvedValue({
68
+ metadata: { name: 'test-namespace' }
69
+ }),
70
+ listNode: jest.fn().mockResolvedValue({
71
+ items: [{ metadata: { name: 'test-node' } }]
72
+ })
73
+ }));
74
+
75
+ export const ApiextensionsV1Api = jest.fn().mockImplementation(() => ({
76
+ listCustomResourceDefinition: jest.fn().mockResolvedValue({
77
+ items: [
78
+ {
79
+ metadata: { name: 'test-crd.example.com' },
80
+ spec: {
81
+ group: 'example.com',
82
+ versions: [{ name: 'v1' }],
83
+ names: { kind: 'TestCRD' },
84
+ scope: 'Namespaced'
85
+ }
86
+ }
87
+ ]
88
+ })
89
+ }));
90
+
91
+ export const VersionApi = jest.fn().mockImplementation(() => ({
92
+ getCode: jest.fn().mockResolvedValue({
93
+ gitVersion: 'v1.28.0',
94
+ platform: 'linux/amd64'
95
+ })
96
+ }));
97
+
98
+ // Export all the common types that might be used
99
+ export const k8s = {
100
+ KubeConfig,
101
+ CoreV1Api,
102
+ ApiextensionsV1Api,
103
+ VersionApi
104
+ };
105
+
106
+ export default k8s;