@vfarcic/dot-ai 0.5.0 → 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.
- package/.claude/commands/context-load.md +11 -0
- package/.claude/commands/context-save.md +16 -0
- package/.claude/commands/prd-done.md +115 -0
- package/.claude/commands/prd-get.md +25 -0
- package/.claude/commands/prd-start.md +87 -0
- package/.claude/commands/task-done.md +77 -0
- package/.claude/commands/tests-reminder.md +32 -0
- package/.claude/settings.local.json +20 -0
- package/.eslintrc.json +25 -0
- package/.github/workflows/ci.yml +170 -0
- package/.prettierrc.json +10 -0
- package/.teller.yml +8 -0
- package/CLAUDE.md +162 -0
- package/assets/images/logo.png +0 -0
- package/bin/dot-ai.ts +47 -0
- package/destroy.sh +45 -0
- package/devbox.json +13 -0
- package/devbox.lock +225 -0
- package/docs/API.md +449 -0
- package/docs/CONTEXT.md +49 -0
- package/docs/DEVELOPMENT.md +203 -0
- package/docs/NEXT_STEPS.md +97 -0
- package/docs/STAGE_BASED_API.md +97 -0
- package/docs/cli-guide.md +798 -0
- package/docs/design.md +750 -0
- package/docs/discovery-engine.md +515 -0
- package/docs/error-handling.md +429 -0
- package/docs/function-registration.md +157 -0
- package/docs/mcp-guide.md +416 -0
- package/package.json +2 -121
- package/renovate.json +51 -0
- package/setup.sh +111 -0
- package/{dist/cli.js → src/cli.ts} +26 -19
- package/src/core/claude.ts +280 -0
- package/src/core/deploy-operation.ts +127 -0
- package/src/core/discovery.ts +900 -0
- package/src/core/error-handling.ts +562 -0
- package/src/core/index.ts +143 -0
- package/src/core/kubernetes-utils.ts +218 -0
- package/src/core/memory.ts +148 -0
- package/src/core/schema.ts +830 -0
- package/src/core/session-utils.ts +97 -0
- package/src/core/workflow.ts +234 -0
- package/src/index.ts +18 -0
- package/src/interfaces/cli.ts +872 -0
- package/src/interfaces/mcp.ts +183 -0
- package/src/mcp/server.ts +131 -0
- package/src/tools/answer-question.ts +807 -0
- package/src/tools/choose-solution.ts +169 -0
- package/src/tools/deploy-manifests.ts +94 -0
- package/src/tools/generate-manifests.ts +502 -0
- package/src/tools/index.ts +41 -0
- package/src/tools/recommend.ts +370 -0
- package/tests/__mocks__/@kubernetes/client-node.ts +106 -0
- package/tests/build-system.test.ts +345 -0
- package/tests/configuration.test.ts +226 -0
- package/tests/core/deploy-operation.test.ts +38 -0
- package/tests/core/discovery.test.ts +1648 -0
- package/tests/core/error-handling.test.ts +632 -0
- package/tests/core/schema.test.ts +1658 -0
- package/tests/core/session-utils.test.ts +245 -0
- package/tests/core.test.ts +439 -0
- package/tests/fixtures/configmap-no-labels.yaml +8 -0
- package/tests/fixtures/crossplane-app-configuration.yaml +6 -0
- package/tests/fixtures/crossplane-providers.yaml +45 -0
- package/tests/fixtures/crossplane-rbac.yaml +48 -0
- package/tests/fixtures/invalid-configmap.yaml +8 -0
- package/tests/fixtures/invalid-deployment.yaml +17 -0
- package/tests/fixtures/test-deployment.yaml +28 -0
- package/tests/fixtures/valid-configmap.yaml +15 -0
- package/tests/infrastructure.test.ts +426 -0
- package/tests/interfaces/cli.test.ts +1036 -0
- package/tests/interfaces/mcp.test.ts +139 -0
- package/tests/kubernetes-utils.test.ts +200 -0
- package/tests/mcp/server.test.ts +126 -0
- package/tests/setup.ts +31 -0
- package/tests/tools/answer-question.test.ts +367 -0
- package/tests/tools/choose-solution.test.ts +481 -0
- package/tests/tools/deploy-manifests.test.ts +185 -0
- package/tests/tools/generate-manifests.test.ts +441 -0
- package/tests/tools/index.test.ts +111 -0
- package/tests/tools/recommend.test.ts +180 -0
- package/tsconfig.json +34 -0
- package/dist/cli.d.ts +0 -3
- package/dist/cli.d.ts.map +0 -1
- package/dist/core/claude.d.ts +0 -42
- package/dist/core/claude.d.ts.map +0 -1
- package/dist/core/claude.js +0 -229
- package/dist/core/deploy-operation.d.ts +0 -38
- package/dist/core/deploy-operation.d.ts.map +0 -1
- package/dist/core/deploy-operation.js +0 -101
- package/dist/core/discovery.d.ts +0 -162
- package/dist/core/discovery.d.ts.map +0 -1
- package/dist/core/discovery.js +0 -758
- package/dist/core/error-handling.d.ts +0 -167
- package/dist/core/error-handling.d.ts.map +0 -1
- package/dist/core/error-handling.js +0 -399
- package/dist/core/index.d.ts +0 -42
- package/dist/core/index.d.ts.map +0 -1
- package/dist/core/index.js +0 -123
- package/dist/core/kubernetes-utils.d.ts +0 -38
- package/dist/core/kubernetes-utils.d.ts.map +0 -1
- package/dist/core/kubernetes-utils.js +0 -177
- package/dist/core/memory.d.ts +0 -45
- package/dist/core/memory.d.ts.map +0 -1
- package/dist/core/memory.js +0 -113
- package/dist/core/schema.d.ts +0 -187
- package/dist/core/schema.d.ts.map +0 -1
- package/dist/core/schema.js +0 -655
- package/dist/core/session-utils.d.ts +0 -29
- package/dist/core/session-utils.d.ts.map +0 -1
- package/dist/core/session-utils.js +0 -121
- package/dist/core/workflow.d.ts +0 -70
- package/dist/core/workflow.d.ts.map +0 -1
- package/dist/core/workflow.js +0 -161
- package/dist/index.d.ts +0 -15
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -32
- package/dist/interfaces/cli.d.ts +0 -74
- package/dist/interfaces/cli.d.ts.map +0 -1
- package/dist/interfaces/cli.js +0 -769
- package/dist/interfaces/mcp.d.ts +0 -30
- package/dist/interfaces/mcp.d.ts.map +0 -1
- package/dist/interfaces/mcp.js +0 -105
- package/dist/mcp/server.d.ts +0 -9
- package/dist/mcp/server.d.ts.map +0 -1
- package/dist/mcp/server.js +0 -151
- package/dist/tools/answer-question.d.ts +0 -27
- package/dist/tools/answer-question.d.ts.map +0 -1
- package/dist/tools/answer-question.js +0 -696
- package/dist/tools/choose-solution.d.ts +0 -23
- package/dist/tools/choose-solution.d.ts.map +0 -1
- package/dist/tools/choose-solution.js +0 -171
- package/dist/tools/deploy-manifests.d.ts +0 -25
- package/dist/tools/deploy-manifests.d.ts.map +0 -1
- package/dist/tools/deploy-manifests.js +0 -74
- package/dist/tools/generate-manifests.d.ts +0 -23
- package/dist/tools/generate-manifests.d.ts.map +0 -1
- package/dist/tools/generate-manifests.js +0 -424
- package/dist/tools/index.d.ts +0 -11
- package/dist/tools/index.d.ts.map +0 -1
- package/dist/tools/index.js +0 -34
- package/dist/tools/recommend.d.ts +0 -23
- package/dist/tools/recommend.d.ts.map +0 -1
- package/dist/tools/recommend.js +0 -332
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build System Tests (TDD Implementation)
|
|
3
|
+
*
|
|
4
|
+
* Tests written FIRST to define requirements for production-ready build system
|
|
5
|
+
* Supporting both CLI binary and MCP server from unified TypeScript package
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as fs from 'fs';
|
|
9
|
+
import * as path from 'path';
|
|
10
|
+
import { execSync } from 'child_process';
|
|
11
|
+
|
|
12
|
+
describe('Build System Validation (TDD)', () => {
|
|
13
|
+
const projectRoot = process.cwd();
|
|
14
|
+
const distDir = path.join(projectRoot, 'dist');
|
|
15
|
+
const packageJsonPath = path.join(projectRoot, 'package.json');
|
|
16
|
+
|
|
17
|
+
beforeAll(() => {
|
|
18
|
+
// Ensure clean build for testing
|
|
19
|
+
if (fs.existsSync(distDir)) {
|
|
20
|
+
fs.rmSync(distDir, { recursive: true });
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe('TypeScript Compilation Requirements', () => {
|
|
25
|
+
test('should compile TypeScript without errors', () => {
|
|
26
|
+
expect(() => {
|
|
27
|
+
execSync('npx tsc --noEmit', { cwd: projectRoot, stdio: 'pipe' });
|
|
28
|
+
}).not.toThrow();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('should generate clean JavaScript output', () => {
|
|
32
|
+
execSync('npm run build', { cwd: projectRoot, stdio: 'pipe' });
|
|
33
|
+
|
|
34
|
+
expect(fs.existsSync(distDir)).toBe(true);
|
|
35
|
+
expect(fs.existsSync(path.join(distDir, 'core', 'index.js'))).toBe(true);
|
|
36
|
+
expect(fs.existsSync(path.join(distDir, 'interfaces', 'cli.js'))).toBe(true);
|
|
37
|
+
expect(fs.existsSync(path.join(distDir, 'interfaces', 'mcp.js'))).toBe(true);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('should preserve module structure in output', () => {
|
|
41
|
+
const coreDir = path.join(distDir, 'core');
|
|
42
|
+
const interfacesDir = path.join(distDir, 'interfaces');
|
|
43
|
+
|
|
44
|
+
expect(fs.existsSync(coreDir)).toBe(true);
|
|
45
|
+
expect(fs.existsSync(interfacesDir)).toBe(true);
|
|
46
|
+
|
|
47
|
+
// Check core modules
|
|
48
|
+
const coreModules = ['index.js', 'discovery.js', 'memory.js', 'workflow.js', 'claude.js'];
|
|
49
|
+
coreModules.forEach(module => {
|
|
50
|
+
expect(fs.existsSync(path.join(coreDir, module))).toBe(true);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Check interface modules
|
|
54
|
+
const interfaceModules = ['cli.js', 'mcp.js'];
|
|
55
|
+
interfaceModules.forEach(module => {
|
|
56
|
+
expect(fs.existsSync(path.join(interfacesDir, module))).toBe(true);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('should generate TypeScript declaration files', () => {
|
|
61
|
+
expect(fs.existsSync(path.join(distDir, 'core', 'index.d.ts'))).toBe(true);
|
|
62
|
+
expect(fs.existsSync(path.join(distDir, 'interfaces', 'cli.d.ts'))).toBe(true);
|
|
63
|
+
expect(fs.existsSync(path.join(distDir, 'interfaces', 'mcp.d.ts'))).toBe(true);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe('CLI Binary Build Requirements', () => {
|
|
68
|
+
test('should create executable CLI binary', () => {
|
|
69
|
+
const cliBinary = path.join(projectRoot, 'bin', 'dot-ai.ts');
|
|
70
|
+
expect(fs.existsSync(cliBinary)).toBe(true);
|
|
71
|
+
|
|
72
|
+
// Check that binary is executable
|
|
73
|
+
const stats = fs.statSync(cliBinary);
|
|
74
|
+
expect(stats.mode & parseInt('111', 8)).toBeGreaterThan(0);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test('should allow CLI binary to run without errors', () => {
|
|
78
|
+
expect(() => {
|
|
79
|
+
const projectKubeconfig = path.join(projectRoot, 'kubeconfig.yaml');
|
|
80
|
+
// Use the compiled JavaScript version for CI compatibility
|
|
81
|
+
const output = execSync(`node dist/cli.js --kubeconfig "${projectKubeconfig}" --help`, {
|
|
82
|
+
cwd: projectRoot,
|
|
83
|
+
stdio: 'pipe'
|
|
84
|
+
});
|
|
85
|
+
expect(output.toString()).toContain('dot-ai');
|
|
86
|
+
}).not.toThrow();
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test('should include all required dependencies in CLI build', () => {
|
|
90
|
+
const projectKubeconfig = path.join(projectRoot, 'kubeconfig.yaml');
|
|
91
|
+
// Use the compiled JavaScript version for CI compatibility
|
|
92
|
+
const output = execSync(`node dist/cli.js --kubeconfig "${projectKubeconfig}" --version`, {
|
|
93
|
+
cwd: projectRoot,
|
|
94
|
+
stdio: 'pipe'
|
|
95
|
+
});
|
|
96
|
+
expect(output.toString().trim()).toMatch(/\d+\.\d+\.\d+/);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
describe('MCP Server Build Requirements', () => {
|
|
101
|
+
test('should build MCP server without errors', () => {
|
|
102
|
+
const mcpServer = path.join(distDir, 'interfaces', 'mcp.js');
|
|
103
|
+
expect(fs.existsSync(mcpServer)).toBe(true);
|
|
104
|
+
|
|
105
|
+
// Test that MCP server can be imported
|
|
106
|
+
expect(() => {
|
|
107
|
+
const mcpModule = require(mcpServer);
|
|
108
|
+
expect(mcpModule.MCPServer).toBeDefined();
|
|
109
|
+
}).not.toThrow();
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test('should include MCP server startup script', () => {
|
|
113
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
114
|
+
expect(packageJson.scripts).toHaveProperty('start:mcp');
|
|
115
|
+
expect(packageJson.scripts['start:mcp']).toContain('mcp');
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test('should validate MCP server dependencies are available', () => {
|
|
119
|
+
const mcpServer = require(path.join(distDir, 'interfaces', 'mcp.js'));
|
|
120
|
+
const { DotAI } = require(path.join(distDir, 'core', 'index.js'));
|
|
121
|
+
|
|
122
|
+
expect(() => {
|
|
123
|
+
// Use project's working kubeconfig.yaml for integration tests
|
|
124
|
+
const projectKubeconfig = path.join(process.cwd(), 'kubeconfig.yaml');
|
|
125
|
+
const dotAI = new DotAI({ kubernetesConfig: projectKubeconfig });
|
|
126
|
+
const config = { name: 'test', version: '1.0.0', description: 'Test' };
|
|
127
|
+
const server = new mcpServer.MCPServer(dotAI, config);
|
|
128
|
+
expect(server).toBeDefined();
|
|
129
|
+
expect(server.isReady()).toBe(false); // Should not be ready until initialized
|
|
130
|
+
}).not.toThrow();
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
describe('Bundle Size and Performance Requirements', () => {
|
|
135
|
+
test('should keep core module bundle size reasonable', () => {
|
|
136
|
+
const coreIndexPath = path.join(distDir, 'core', 'index.js');
|
|
137
|
+
const stats = fs.statSync(coreIndexPath);
|
|
138
|
+
|
|
139
|
+
// Core module should be under 100KB
|
|
140
|
+
expect(stats.size).toBeLessThan(100 * 1024);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test('should keep CLI interface bundle size reasonable', () => {
|
|
144
|
+
const cliPath = path.join(distDir, 'interfaces', 'cli.js');
|
|
145
|
+
const stats = fs.statSync(cliPath);
|
|
146
|
+
|
|
147
|
+
// CLI interface should be under 50KB
|
|
148
|
+
expect(stats.size).toBeLessThan(50 * 1024);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test('should keep MCP interface bundle size reasonable', () => {
|
|
152
|
+
const mcpPath = path.join(distDir, 'interfaces', 'mcp.js');
|
|
153
|
+
const stats = fs.statSync(mcpPath);
|
|
154
|
+
|
|
155
|
+
// MCP interface should be under 50KB
|
|
156
|
+
expect(stats.size).toBeLessThan(50 * 1024);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test('should not include unnecessary files in dist', () => {
|
|
160
|
+
const distContents = fs.readdirSync(distDir);
|
|
161
|
+
|
|
162
|
+
// Should not contain test files
|
|
163
|
+
expect(distContents.some(file => file.includes('.test.'))).toBe(false);
|
|
164
|
+
expect(distContents.some(file => file.includes('.spec.'))).toBe(false);
|
|
165
|
+
|
|
166
|
+
// Should not contain source maps in production build
|
|
167
|
+
const jsFiles = distContents.filter(file => file.endsWith('.js'));
|
|
168
|
+
jsFiles.forEach(file => {
|
|
169
|
+
const filePath = path.join(distDir, file);
|
|
170
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
171
|
+
expect(content).not.toContain('//# sourceMappingURL=');
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
describe('Cross-Platform Compatibility Requirements', () => {
|
|
177
|
+
test('should use cross-platform path handling', () => {
|
|
178
|
+
const coreFiles = fs.readdirSync(path.join(distDir, 'core'));
|
|
179
|
+
|
|
180
|
+
coreFiles.forEach(file => {
|
|
181
|
+
if (file.endsWith('.js')) {
|
|
182
|
+
const filePath = path.join(distDir, 'core', file);
|
|
183
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
184
|
+
|
|
185
|
+
// Should not contain problematic hardcoded Unix paths (allow standard ones like .kube)
|
|
186
|
+
expect(content).not.toMatch(/\/usr\/local\/|\/opt\/|\/tmp\/[^\/\s"']+\/|\/var\/[^\/\s"']+\//g);
|
|
187
|
+
|
|
188
|
+
// Note: Files may contain the word "path" in error messages, variable names, etc.
|
|
189
|
+
// without needing path.join/resolve. The important check is avoiding hardcoded Unix paths above.
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
test('should handle different node environments', () => {
|
|
195
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
196
|
+
|
|
197
|
+
// Should specify node engine requirements
|
|
198
|
+
expect(packageJson.engines).toBeDefined();
|
|
199
|
+
expect(packageJson.engines.node).toBeDefined();
|
|
200
|
+
|
|
201
|
+
// Should use compatible module target (CommonJS for broader compatibility)
|
|
202
|
+
const tsconfigPath = path.join(projectRoot, 'tsconfig.json');
|
|
203
|
+
const tsconfig = JSON.parse(fs.readFileSync(tsconfigPath, 'utf8'));
|
|
204
|
+
expect(tsconfig.compilerOptions.target).toBe('ES2022');
|
|
205
|
+
expect(['commonjs', 'ESNext']).toContain(tsconfig.compilerOptions.module);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
test('should work with different package managers', () => {
|
|
209
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
210
|
+
|
|
211
|
+
// Should not have package-lock.json dependencies in scripts
|
|
212
|
+
Object.values(packageJson.scripts || {}).forEach(script => {
|
|
213
|
+
expect(script).not.toContain('package-lock.json');
|
|
214
|
+
expect(script).not.toContain('yarn.lock');
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
describe('Production Build Optimization Requirements', () => {
|
|
220
|
+
test('should minify JavaScript output for production', () => {
|
|
221
|
+
// Run production build if available
|
|
222
|
+
try {
|
|
223
|
+
execSync('npm run build:prod', { cwd: projectRoot, stdio: 'pipe' });
|
|
224
|
+
} catch {
|
|
225
|
+
// Production build script may not exist yet, skip for now
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const coreIndexPath = path.join(distDir, 'core', 'index.js');
|
|
230
|
+
const content = fs.readFileSync(coreIndexPath, 'utf8');
|
|
231
|
+
|
|
232
|
+
// Production build should have minimal whitespace
|
|
233
|
+
const lines = content.split('\n');
|
|
234
|
+
const nonEmptyLines = lines.filter(line => line.trim().length > 0);
|
|
235
|
+
expect(nonEmptyLines.length / lines.length).toBeGreaterThan(0.8);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
test('should tree-shake unused dependencies', () => {
|
|
239
|
+
const mcpPath = path.join(distDir, 'interfaces', 'mcp.js');
|
|
240
|
+
const content = fs.readFileSync(mcpPath, 'utf8');
|
|
241
|
+
|
|
242
|
+
// Should not include unused imports
|
|
243
|
+
expect(content).not.toContain('import * as');
|
|
244
|
+
|
|
245
|
+
// Should use specific imports
|
|
246
|
+
if (content.includes('require(')) {
|
|
247
|
+
const requireStatements = content.match(/require\(['"][^'"]+['"]\)/g) || [];
|
|
248
|
+
requireStatements.forEach(stmt => {
|
|
249
|
+
expect(stmt).not.toContain('*');
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
test('should validate all imports resolve correctly', () => {
|
|
255
|
+
const interfaceFiles = ['cli.js', 'mcp.js'];
|
|
256
|
+
|
|
257
|
+
interfaceFiles.forEach(file => {
|
|
258
|
+
const filePath = path.join(distDir, 'interfaces', file);
|
|
259
|
+
expect(() => {
|
|
260
|
+
require(filePath);
|
|
261
|
+
}).not.toThrow();
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
describe('Package Distribution Requirements', () => {
|
|
267
|
+
test('should include all necessary files for npm distribution', () => {
|
|
268
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
269
|
+
|
|
270
|
+
// Should have proper main entry point
|
|
271
|
+
expect(packageJson.main).toBeDefined();
|
|
272
|
+
expect(fs.existsSync(path.join(projectRoot, packageJson.main))).toBe(true);
|
|
273
|
+
|
|
274
|
+
// Should have types definition
|
|
275
|
+
expect(packageJson.types).toBeDefined();
|
|
276
|
+
expect(fs.existsSync(path.join(projectRoot, packageJson.types))).toBe(true);
|
|
277
|
+
|
|
278
|
+
// Should have bin entry for CLI
|
|
279
|
+
expect(packageJson.bin).toBeDefined();
|
|
280
|
+
expect(typeof packageJson.bin === 'object' || typeof packageJson.bin === 'string').toBe(true);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
test('should have proper files field for npm publishing', () => {
|
|
284
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
285
|
+
|
|
286
|
+
if (packageJson.files) {
|
|
287
|
+
expect(Array.isArray(packageJson.files)).toBe(true);
|
|
288
|
+
expect(packageJson.files).toContain('dist');
|
|
289
|
+
expect(packageJson.files).toContain('prompts');
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
test('should validate package.json completeness', () => {
|
|
294
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
295
|
+
|
|
296
|
+
// Essential package.json fields
|
|
297
|
+
expect(packageJson.name).toBeDefined();
|
|
298
|
+
expect(packageJson.version).toBeDefined();
|
|
299
|
+
expect(packageJson.description).toBeDefined();
|
|
300
|
+
expect(packageJson.license).toBeDefined();
|
|
301
|
+
expect(packageJson.repository).toBeDefined();
|
|
302
|
+
expect(packageJson.keywords).toBeDefined();
|
|
303
|
+
expect(Array.isArray(packageJson.keywords)).toBe(true);
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
describe('Build Script Integration Requirements', () => {
|
|
308
|
+
test('should have all required build scripts', () => {
|
|
309
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
310
|
+
const scripts = packageJson.scripts;
|
|
311
|
+
|
|
312
|
+
expect(scripts).toHaveProperty('build');
|
|
313
|
+
expect(scripts).toHaveProperty('build:cli');
|
|
314
|
+
expect(scripts).toHaveProperty('build:mcp');
|
|
315
|
+
expect(scripts).toHaveProperty('build:watch');
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
test('should have development and production build modes', () => {
|
|
319
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
320
|
+
const scripts = packageJson.scripts;
|
|
321
|
+
|
|
322
|
+
// Should have development build
|
|
323
|
+
expect(scripts.build).toBeDefined();
|
|
324
|
+
|
|
325
|
+
// Should have watch mode for development
|
|
326
|
+
expect(scripts['build:watch']).toBeDefined();
|
|
327
|
+
expect(scripts['build:watch']).toContain('watch');
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
test('should clean build directory before building', () => {
|
|
331
|
+
// Create a test file in dist to verify it gets cleaned
|
|
332
|
+
if (!fs.existsSync(distDir)) {
|
|
333
|
+
fs.mkdirSync(distDir, { recursive: true });
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const testFile = path.join(distDir, 'test-cleanup.txt');
|
|
337
|
+
fs.writeFileSync(testFile, 'should be removed');
|
|
338
|
+
|
|
339
|
+
execSync('npm run build', { cwd: projectRoot, stdio: 'pipe' });
|
|
340
|
+
|
|
341
|
+
// Test file should be gone after build
|
|
342
|
+
expect(fs.existsSync(testFile)).toBe(false);
|
|
343
|
+
});
|
|
344
|
+
});
|
|
345
|
+
});
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration Validation Tests
|
|
3
|
+
*
|
|
4
|
+
* These tests define the requirements for our package.json and TypeScript configuration
|
|
5
|
+
* Following TDD approach - these tests define what we SHOULD have before we implement it
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import fs from 'fs';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
|
|
11
|
+
describe('Package.json Configuration', () => {
|
|
12
|
+
let packageJson: any;
|
|
13
|
+
|
|
14
|
+
beforeAll(() => {
|
|
15
|
+
packageJson = JSON.parse(fs.readFileSync('package.json', 'utf8'));
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
describe('Basic Package Information', () => {
|
|
19
|
+
test('should have correct package metadata', () => {
|
|
20
|
+
expect(packageJson.name).toBe('@vfarcic/dot-ai');
|
|
21
|
+
expect(packageJson.version).toMatch(/^\d+\.\d+\.\d+$/);
|
|
22
|
+
expect(packageJson.description).toContain('Kubernetes');
|
|
23
|
+
expect(packageJson.description).toContain('CLI');
|
|
24
|
+
expect(packageJson.description).toContain('MCP');
|
|
25
|
+
expect(packageJson.license).toBe('MIT');
|
|
26
|
+
expect(packageJson.author).toBeDefined();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test('should have proper keywords for discoverability', () => {
|
|
30
|
+
expect(packageJson.keywords).toContain('kubernetes');
|
|
31
|
+
expect(packageJson.keywords).toContain('cli');
|
|
32
|
+
expect(packageJson.keywords).toContain('mcp');
|
|
33
|
+
expect(packageJson.keywords).toContain('deployment');
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe('Entry Points and Exports', () => {
|
|
38
|
+
test('should support both CLI and MCP entry points', () => {
|
|
39
|
+
expect(packageJson.main).toBe('dist/index.js');
|
|
40
|
+
expect(packageJson.bin).toHaveProperty('dot-ai');
|
|
41
|
+
expect(packageJson.exports['.']).toBe('./dist/index.js');
|
|
42
|
+
expect(packageJson.exports['./mcp']).toBe('./dist/mcp/server.js');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('should have TypeScript declaration files', () => {
|
|
46
|
+
expect(packageJson.types || packageJson.typings).toBeDefined();
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe('Scripts', () => {
|
|
51
|
+
test('should have comprehensive build scripts', () => {
|
|
52
|
+
expect(packageJson.scripts.build).toBeDefined();
|
|
53
|
+
expect(packageJson.scripts['build:watch']).toBeDefined();
|
|
54
|
+
expect(packageJson.scripts.test).toBeDefined();
|
|
55
|
+
expect(packageJson.scripts['test:watch']).toBeDefined();
|
|
56
|
+
expect(packageJson.scripts['test:coverage']).toBeDefined();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('should have development scripts', () => {
|
|
60
|
+
expect(packageJson.scripts.dev).toBeDefined();
|
|
61
|
+
expect(packageJson.scripts.lint).toBeDefined();
|
|
62
|
+
expect(packageJson.scripts.format).toBeDefined();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('should have CLI and MCP specific scripts', () => {
|
|
66
|
+
expect(packageJson.scripts['start:cli']).toBeDefined();
|
|
67
|
+
expect(packageJson.scripts['start:mcp']).toBeDefined();
|
|
68
|
+
expect(packageJson.scripts['build:cli']).toBeDefined();
|
|
69
|
+
expect(packageJson.scripts['build:mcp']).toBeDefined();
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe('Dependencies', () => {
|
|
74
|
+
test('should have required runtime dependencies', () => {
|
|
75
|
+
expect(packageJson.dependencies['@kubernetes/client-node']).toBeDefined();
|
|
76
|
+
expect(packageJson.dependencies['commander']).toBeDefined();
|
|
77
|
+
expect(packageJson.dependencies['@anthropic-ai/sdk']).toBeDefined();
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test('should have proper dev dependencies', () => {
|
|
81
|
+
expect(packageJson.devDependencies['@types/jest']).toBeDefined();
|
|
82
|
+
expect(packageJson.devDependencies['@types/node']).toBeDefined();
|
|
83
|
+
expect(packageJson.devDependencies['typescript']).toBeDefined();
|
|
84
|
+
expect(packageJson.devDependencies['jest']).toBeDefined();
|
|
85
|
+
expect(packageJson.devDependencies['ts-jest']).toBeDefined();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test('should have code quality dependencies', () => {
|
|
89
|
+
expect(packageJson.devDependencies['eslint']).toBeDefined();
|
|
90
|
+
expect(packageJson.devDependencies['prettier']).toBeDefined();
|
|
91
|
+
expect(packageJson.devDependencies['@typescript-eslint/eslint-plugin']).toBeDefined();
|
|
92
|
+
expect(packageJson.devDependencies['@typescript-eslint/parser']).toBeDefined();
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
describe('Jest Configuration', () => {
|
|
97
|
+
test('should have comprehensive Jest setup', () => {
|
|
98
|
+
expect(packageJson.jest.preset).toBe('ts-jest');
|
|
99
|
+
expect(packageJson.jest.testEnvironment).toBe('node');
|
|
100
|
+
expect(packageJson.jest.roots).toContain('<rootDir>/src');
|
|
101
|
+
expect(packageJson.jest.roots).toContain('<rootDir>/tests');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test('should have proper coverage configuration', () => {
|
|
105
|
+
expect(packageJson.jest.collectCoverageFrom).toContain('src/**/*.ts');
|
|
106
|
+
expect(packageJson.jest.coverageDirectory).toBe('coverage');
|
|
107
|
+
expect(packageJson.jest.coverageReporters).toContain('text');
|
|
108
|
+
expect(packageJson.jest.coverageReporters).toContain('lcov');
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
describe('TypeScript Configuration', () => {
|
|
114
|
+
let tsConfig: any;
|
|
115
|
+
|
|
116
|
+
beforeAll(() => {
|
|
117
|
+
tsConfig = JSON.parse(fs.readFileSync('tsconfig.json', 'utf8'));
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
describe('Compiler Options', () => {
|
|
121
|
+
test('should have strict TypeScript settings', () => {
|
|
122
|
+
expect(tsConfig.compilerOptions.strict).toBe(true);
|
|
123
|
+
expect(tsConfig.compilerOptions.noImplicitAny).toBe(true);
|
|
124
|
+
expect(tsConfig.compilerOptions.strictNullChecks).toBe(true);
|
|
125
|
+
expect(tsConfig.compilerOptions.noImplicitReturns).toBe(true);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test('should target appropriate JavaScript version', () => {
|
|
129
|
+
expect(tsConfig.compilerOptions.target).toBe('ES2022');
|
|
130
|
+
expect(tsConfig.compilerOptions.lib).toContain('ES2022');
|
|
131
|
+
expect(tsConfig.compilerOptions.module).toBe('commonjs');
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test('should have proper output configuration', () => {
|
|
135
|
+
expect(tsConfig.compilerOptions.outDir).toBe('./dist');
|
|
136
|
+
expect(tsConfig.compilerOptions.rootDir).toBe('./src');
|
|
137
|
+
expect(tsConfig.compilerOptions.declaration).toBe(true);
|
|
138
|
+
expect(tsConfig.compilerOptions.sourceMap).toBe(true);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test('should support module resolution', () => {
|
|
142
|
+
expect(tsConfig.compilerOptions.moduleResolution).toBe('node');
|
|
143
|
+
expect(tsConfig.compilerOptions.esModuleInterop).toBe(true);
|
|
144
|
+
expect(tsConfig.compilerOptions.resolveJsonModule).toBe(true);
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
describe('Path Resolution', () => {
|
|
149
|
+
test('should have proper include/exclude patterns', () => {
|
|
150
|
+
expect(tsConfig.include).toContain('src/**/*');
|
|
151
|
+
expect(tsConfig.exclude).toContain('node_modules');
|
|
152
|
+
expect(tsConfig.exclude).toContain('dist');
|
|
153
|
+
expect(tsConfig.exclude).toContain('**/*.test.ts');
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test('should support path mapping for core modules', () => {
|
|
157
|
+
if (tsConfig.compilerOptions.paths) {
|
|
158
|
+
expect(tsConfig.compilerOptions.paths['@core/*']).toBeDefined();
|
|
159
|
+
expect(tsConfig.compilerOptions.paths['@interfaces/*']).toBeDefined();
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
describe('Build Configuration', () => {
|
|
166
|
+
test('should be able to compile TypeScript successfully', async () => {
|
|
167
|
+
// This test validates that our TypeScript configuration actually works
|
|
168
|
+
const { exec } = require('child_process');
|
|
169
|
+
const { promisify } = require('util');
|
|
170
|
+
const execAsync = promisify(exec);
|
|
171
|
+
|
|
172
|
+
try {
|
|
173
|
+
await execAsync('npx tsc --noEmit');
|
|
174
|
+
// If we reach here, TypeScript compilation succeeded
|
|
175
|
+
expect(true).toBe(true);
|
|
176
|
+
} catch (error) {
|
|
177
|
+
// If TypeScript compilation fails, we'll get details in the error
|
|
178
|
+
throw new Error(`TypeScript compilation failed: ${error}`);
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test('should have working linting configuration', async () => {
|
|
183
|
+
// Test that our linting setup works
|
|
184
|
+
expect(fs.existsSync('.eslintrc.js') || fs.existsSync('.eslintrc.json')).toBe(true);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
test('should have working prettier configuration', async () => {
|
|
188
|
+
// Test that prettier is properly configured
|
|
189
|
+
const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf8'));
|
|
190
|
+
expect(
|
|
191
|
+
fs.existsSync('.prettierrc') ||
|
|
192
|
+
fs.existsSync('.prettierrc.json') ||
|
|
193
|
+
fs.existsSync('prettier.config.js') ||
|
|
194
|
+
packageJson.prettier
|
|
195
|
+
).toBe(true);
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
describe('Module Resolution Tests', () => {
|
|
200
|
+
test('should be able to resolve core modules', () => {
|
|
201
|
+
// Test that our module paths will work
|
|
202
|
+
const corePath = path.resolve('src/core');
|
|
203
|
+
const interfacesPath = path.resolve('src/interfaces');
|
|
204
|
+
|
|
205
|
+
expect(fs.existsSync(corePath)).toBe(true);
|
|
206
|
+
expect(fs.existsSync(interfacesPath)).toBe(true);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
test('should support importing from planned modules', () => {
|
|
210
|
+
// This validates our module structure expectations
|
|
211
|
+
const expectedModules = [
|
|
212
|
+
'src/core/index.ts',
|
|
213
|
+
'src/core/discovery.ts',
|
|
214
|
+
'src/core/memory.ts',
|
|
215
|
+
'src/core/workflow.ts',
|
|
216
|
+
'src/interfaces/cli.ts',
|
|
217
|
+
'src/interfaces/mcp.ts'
|
|
218
|
+
];
|
|
219
|
+
|
|
220
|
+
expectedModules.forEach(modulePath => {
|
|
221
|
+
const dir = path.dirname(modulePath);
|
|
222
|
+
expect(fs.existsSync(dir)).toBe(true);
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { DeployOperation } from '../../src/core/deploy-operation';
|
|
2
|
+
|
|
3
|
+
// Mock the entire deploy-operation module to avoid complex async mocking
|
|
4
|
+
jest.mock('child_process', () => ({
|
|
5
|
+
exec: jest.fn()
|
|
6
|
+
}));
|
|
7
|
+
|
|
8
|
+
jest.mock('fs/promises', () => ({
|
|
9
|
+
access: jest.fn()
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
jest.mock('util', () => ({
|
|
13
|
+
promisify: jest.fn(() => jest.fn())
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
describe('DeployOperation', () => {
|
|
17
|
+
let deployOp: DeployOperation;
|
|
18
|
+
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
deployOp = new DeployOperation();
|
|
21
|
+
jest.clearAllMocks();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe('constructor', () => {
|
|
25
|
+
it('should create DeployOperation instance', () => {
|
|
26
|
+
expect(deployOp).toBeInstanceOf(DeployOperation);
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe('deploy method exists', () => {
|
|
31
|
+
it('should have deploy method', () => {
|
|
32
|
+
expect(typeof deployOp.deploy).toBe('function');
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Note: Full integration testing of deploy() is done via CLI manual testing
|
|
37
|
+
// to avoid complex async mocking that could cause test timeouts
|
|
38
|
+
});
|