@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,45 @@
|
|
|
1
|
+
apiVersion: pkg.crossplane.io/v1beta1
|
|
2
|
+
kind: DeploymentRuntimeConfig
|
|
3
|
+
metadata:
|
|
4
|
+
name: crossplane-provider-helm
|
|
5
|
+
spec:
|
|
6
|
+
deploymentTemplate:
|
|
7
|
+
spec:
|
|
8
|
+
selector: {}
|
|
9
|
+
template:
|
|
10
|
+
spec:
|
|
11
|
+
containers:
|
|
12
|
+
- name: package-runtime
|
|
13
|
+
serviceAccountName: crossplane-provider-helm
|
|
14
|
+
---
|
|
15
|
+
apiVersion: pkg.crossplane.io/v1
|
|
16
|
+
kind: Provider
|
|
17
|
+
metadata:
|
|
18
|
+
name: crossplane-provider-helm
|
|
19
|
+
spec:
|
|
20
|
+
package: xpkg.upbound.io/crossplane-contrib/provider-helm:v0.19.0
|
|
21
|
+
runtimeConfigRef:
|
|
22
|
+
name: crossplane-provider-helm
|
|
23
|
+
---
|
|
24
|
+
apiVersion: pkg.crossplane.io/v1beta1
|
|
25
|
+
kind: DeploymentRuntimeConfig
|
|
26
|
+
metadata:
|
|
27
|
+
name: crossplane-provider-kubernetes
|
|
28
|
+
spec:
|
|
29
|
+
deploymentTemplate:
|
|
30
|
+
spec:
|
|
31
|
+
selector: {}
|
|
32
|
+
template:
|
|
33
|
+
spec:
|
|
34
|
+
containers:
|
|
35
|
+
- name: package-runtime
|
|
36
|
+
serviceAccountName: crossplane-provider-kubernetes
|
|
37
|
+
---
|
|
38
|
+
apiVersion: pkg.crossplane.io/v1
|
|
39
|
+
kind: Provider
|
|
40
|
+
metadata:
|
|
41
|
+
name: crossplane-provider-kubernetes
|
|
42
|
+
spec:
|
|
43
|
+
package: xpkg.upbound.io/crossplane-contrib/provider-kubernetes:v0.15.0
|
|
44
|
+
runtimeConfigRef:
|
|
45
|
+
name: crossplane-provider-kubernetes
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
apiVersion: rbac.authorization.k8s.io/v1
|
|
2
|
+
kind: ClusterRole
|
|
3
|
+
metadata:
|
|
4
|
+
name: crossplane-all
|
|
5
|
+
labels:
|
|
6
|
+
rbac.crossplane.io/aggregate-to-crossplane: "true"
|
|
7
|
+
rules:
|
|
8
|
+
- apiGroups: ["*"]
|
|
9
|
+
resources: ["*"]
|
|
10
|
+
verbs: ["*"]
|
|
11
|
+
---
|
|
12
|
+
apiVersion: v1
|
|
13
|
+
kind: ServiceAccount
|
|
14
|
+
metadata:
|
|
15
|
+
name: crossplane-provider-helm
|
|
16
|
+
namespace: crossplane-system
|
|
17
|
+
---
|
|
18
|
+
apiVersion: rbac.authorization.k8s.io/v1
|
|
19
|
+
kind: ClusterRoleBinding
|
|
20
|
+
metadata:
|
|
21
|
+
name: crossplane-provider-helm
|
|
22
|
+
subjects:
|
|
23
|
+
- kind: ServiceAccount
|
|
24
|
+
name: crossplane-provider-helm
|
|
25
|
+
namespace: crossplane-system
|
|
26
|
+
roleRef:
|
|
27
|
+
kind: ClusterRole
|
|
28
|
+
name: cluster-admin
|
|
29
|
+
apiGroup: rbac.authorization.k8s.io
|
|
30
|
+
---
|
|
31
|
+
apiVersion: v1
|
|
32
|
+
kind: ServiceAccount
|
|
33
|
+
metadata:
|
|
34
|
+
name: crossplane-provider-kubernetes
|
|
35
|
+
namespace: crossplane-system
|
|
36
|
+
---
|
|
37
|
+
apiVersion: rbac.authorization.k8s.io/v1
|
|
38
|
+
kind: ClusterRoleBinding
|
|
39
|
+
metadata:
|
|
40
|
+
name: crossplane-provider-kubernetes
|
|
41
|
+
subjects:
|
|
42
|
+
- kind: ServiceAccount
|
|
43
|
+
name: crossplane-provider-kubernetes
|
|
44
|
+
namespace: crossplane-system
|
|
45
|
+
roleRef:
|
|
46
|
+
kind: ClusterRole
|
|
47
|
+
name: cluster-admin
|
|
48
|
+
apiGroup: rbac.authorization.k8s.io
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
apiVersion: apps/v1
|
|
2
|
+
kind: Deployment
|
|
3
|
+
metadata:
|
|
4
|
+
name: invalid-app
|
|
5
|
+
spec:
|
|
6
|
+
# Missing required selector field
|
|
7
|
+
replicas: "not-a-number" # Should be integer
|
|
8
|
+
template:
|
|
9
|
+
metadata:
|
|
10
|
+
labels:
|
|
11
|
+
app: invalid-app
|
|
12
|
+
spec:
|
|
13
|
+
containers:
|
|
14
|
+
- name: app
|
|
15
|
+
# Missing required image field
|
|
16
|
+
ports:
|
|
17
|
+
- containerPort: "80" # Should be integer
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
apiVersion: apps/v1
|
|
2
|
+
kind: Deployment
|
|
3
|
+
metadata:
|
|
4
|
+
name: test-app
|
|
5
|
+
labels:
|
|
6
|
+
app: test-app
|
|
7
|
+
spec:
|
|
8
|
+
replicas: 3
|
|
9
|
+
selector:
|
|
10
|
+
matchLabels:
|
|
11
|
+
app: test-app
|
|
12
|
+
template:
|
|
13
|
+
metadata:
|
|
14
|
+
labels:
|
|
15
|
+
app: test-app
|
|
16
|
+
spec:
|
|
17
|
+
containers:
|
|
18
|
+
- name: app
|
|
19
|
+
image: nginx:1.21
|
|
20
|
+
ports:
|
|
21
|
+
- containerPort: 80
|
|
22
|
+
resources:
|
|
23
|
+
requests:
|
|
24
|
+
memory: "64Mi"
|
|
25
|
+
cpu: "250m"
|
|
26
|
+
limits:
|
|
27
|
+
memory: "128Mi"
|
|
28
|
+
cpu: "500m"
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
apiVersion: v1
|
|
2
|
+
kind: ConfigMap
|
|
3
|
+
metadata:
|
|
4
|
+
name: test-configmap-integration
|
|
5
|
+
namespace: default
|
|
6
|
+
labels:
|
|
7
|
+
app: test-integration
|
|
8
|
+
test-type: integration
|
|
9
|
+
data:
|
|
10
|
+
config.yaml: |
|
|
11
|
+
setting: value
|
|
12
|
+
environment: test
|
|
13
|
+
app.properties: |
|
|
14
|
+
debug=true
|
|
15
|
+
port=8080
|
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test Infrastructure Validation
|
|
3
|
+
*
|
|
4
|
+
* These tests ensure our test infrastructure is properly configured
|
|
5
|
+
* and working before we implement any actual functionality.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { existsSync, readFileSync, mkdirSync } from 'fs';
|
|
9
|
+
import * as path from 'path';
|
|
10
|
+
import * as yaml from 'yaml';
|
|
11
|
+
|
|
12
|
+
describe('Test Infrastructure', () => {
|
|
13
|
+
describe('Project Structure', () => {
|
|
14
|
+
test('should have package.json with correct structure', () => {
|
|
15
|
+
const packageJson = JSON.parse(readFileSync('package.json', 'utf8'));
|
|
16
|
+
|
|
17
|
+
expect(packageJson.name).toBe('@vfarcic/dot-ai');
|
|
18
|
+
expect(packageJson.main).toBe('dist/index.js');
|
|
19
|
+
expect(packageJson.bin).toHaveProperty('dot-ai');
|
|
20
|
+
expect(packageJson.exports['.']).toBeDefined();
|
|
21
|
+
expect(packageJson.exports['./mcp']).toBeDefined();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('should have TypeScript configuration', () => {
|
|
25
|
+
const tsConfig = JSON.parse(readFileSync('tsconfig.json', 'utf8'));
|
|
26
|
+
|
|
27
|
+
expect(tsConfig.compilerOptions.strict).toBe(true);
|
|
28
|
+
expect(tsConfig.compilerOptions.outDir).toBe('./dist');
|
|
29
|
+
expect(tsConfig.compilerOptions.rootDir).toBe('./src');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('should have Jest configuration in package.json', () => {
|
|
33
|
+
const packageJson = JSON.parse(readFileSync('package.json', 'utf8'));
|
|
34
|
+
|
|
35
|
+
expect(packageJson.jest).toBeDefined();
|
|
36
|
+
expect(packageJson.jest.preset).toBe('ts-jest');
|
|
37
|
+
expect(packageJson.jest.testEnvironment).toBe('node');
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe('Directory Structure Expectations', () => {
|
|
42
|
+
test('should be able to create src directory structure', () => {
|
|
43
|
+
// Test that we can create the expected directory structure
|
|
44
|
+
const expectedDirs = [
|
|
45
|
+
'src',
|
|
46
|
+
'src/core',
|
|
47
|
+
'src/interfaces',
|
|
48
|
+
'bin'
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
expectedDirs.forEach(dir => {
|
|
52
|
+
if (!existsSync(dir)) {
|
|
53
|
+
mkdirSync(dir, { recursive: true });
|
|
54
|
+
}
|
|
55
|
+
expect(existsSync(dir)).toBe(true);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('should be able to validate expected file locations', () => {
|
|
60
|
+
// Test paths that should exist or be creatable
|
|
61
|
+
const expectedFiles = [
|
|
62
|
+
{ path: 'src/index.ts', shouldExist: false }, // Will be created
|
|
63
|
+
{ path: 'src/core/index.ts', shouldExist: false }, // Will be created
|
|
64
|
+
{ path: 'src/interfaces/cli.ts', shouldExist: false }, // Will be created
|
|
65
|
+
{ path: 'src/interfaces/mcp.ts', shouldExist: false }, // Will be created
|
|
66
|
+
{ path: 'bin/dot-ai', shouldExist: false } // Will be created
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
expectedFiles.forEach(({ path: filePath, shouldExist }) => {
|
|
70
|
+
if (shouldExist) {
|
|
71
|
+
expect(existsSync(filePath)).toBe(true);
|
|
72
|
+
} else {
|
|
73
|
+
// Test that the directory exists for future file creation
|
|
74
|
+
const dir = path.dirname(filePath);
|
|
75
|
+
expect(existsSync(dir)).toBe(true);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe('TypeScript Environment', () => {
|
|
82
|
+
test('should support TypeScript compilation', () => {
|
|
83
|
+
// This test validates that our TypeScript environment is working
|
|
84
|
+
// by using TypeScript features that would fail if not properly configured
|
|
85
|
+
|
|
86
|
+
interface TestInterface {
|
|
87
|
+
name: string;
|
|
88
|
+
value: number;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const testObject: TestInterface = {
|
|
92
|
+
name: 'test',
|
|
93
|
+
value: 42
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
expect(testObject.name).toBe('test');
|
|
97
|
+
expect(testObject.value).toBe(42);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test('should support ES2022 features', () => {
|
|
101
|
+
// Test that our target ES2022 is working
|
|
102
|
+
const testArray = [1, 2, 3, 4, 5];
|
|
103
|
+
const result = testArray.at(-1); // ES2022 feature
|
|
104
|
+
|
|
105
|
+
expect(result).toBe(5);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test('should support async/await', async () => {
|
|
109
|
+
// Test async support which we'll need for Kubernetes API calls
|
|
110
|
+
const asyncFunction = async (): Promise<string> => {
|
|
111
|
+
return Promise.resolve('async works');
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const result = await asyncFunction();
|
|
115
|
+
expect(result).toBe('async works');
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe('Test Utilities', () => {
|
|
120
|
+
test('should be able to mock modules', () => {
|
|
121
|
+
// Test that Jest mocking is working
|
|
122
|
+
const mockFunction = jest.fn();
|
|
123
|
+
mockFunction.mockReturnValue('mocked value');
|
|
124
|
+
|
|
125
|
+
expect(mockFunction()).toBe('mocked value');
|
|
126
|
+
expect(mockFunction).toHaveBeenCalledTimes(1);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test('should support test environment setup', () => {
|
|
130
|
+
// Test that our test environment variables and setup are working
|
|
131
|
+
expect(process.env.NODE_ENV).toBe('test');
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
describe('Smoke Tests', () => {
|
|
137
|
+
test('Jest is working correctly', () => {
|
|
138
|
+
expect(1 + 1).toBe(2);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test('TypeScript compilation is working', () => {
|
|
142
|
+
const message: string = 'TypeScript works';
|
|
143
|
+
expect(typeof message).toBe('string');
|
|
144
|
+
expect(message).toBe('TypeScript works');
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
describe('CI/CD Pipeline Infrastructure', () => {
|
|
149
|
+
const rootDir = process.cwd();
|
|
150
|
+
const githubWorkflowsDir = path.join(rootDir, '.github', 'workflows');
|
|
151
|
+
|
|
152
|
+
describe('GitHub Actions Configuration', () => {
|
|
153
|
+
test('should have .github/workflows directory', () => {
|
|
154
|
+
expect(existsSync(githubWorkflowsDir)).toBe(true);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test('should have main CI workflow file', () => {
|
|
158
|
+
const ciWorkflowPath = path.join(githubWorkflowsDir, 'ci.yml');
|
|
159
|
+
expect(existsSync(ciWorkflowPath)).toBe(true);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test('should have consolidated CI & security workflow', () => {
|
|
163
|
+
const ciWorkflowPath = path.join(githubWorkflowsDir, 'ci.yml');
|
|
164
|
+
expect(existsSync(ciWorkflowPath)).toBe(true);
|
|
165
|
+
|
|
166
|
+
// Should NOT have separate security.yml (consolidated into ci.yml)
|
|
167
|
+
const securityWorkflowPath = path.join(githubWorkflowsDir, 'security.yml');
|
|
168
|
+
expect(existsSync(securityWorkflowPath)).toBe(false);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test('should have renovate configuration for dependency management', () => {
|
|
172
|
+
const renovatePath = path.join(rootDir, 'renovate.json');
|
|
173
|
+
expect(existsSync(renovatePath)).toBe(true);
|
|
174
|
+
|
|
175
|
+
const renovateContent = readFileSync(renovatePath, 'utf-8');
|
|
176
|
+
const renovateConfig = JSON.parse(renovateContent);
|
|
177
|
+
expect(renovateConfig.extends).toContain('config:base');
|
|
178
|
+
expect(renovateConfig.schedule).toBeDefined();
|
|
179
|
+
expect(renovateConfig.packageRules).toBeDefined();
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
describe('CI Workflow Validation', () => {
|
|
184
|
+
let ciWorkflow: any;
|
|
185
|
+
|
|
186
|
+
beforeAll(() => {
|
|
187
|
+
const ciWorkflowPath = path.join(githubWorkflowsDir, 'ci.yml');
|
|
188
|
+
if (existsSync(ciWorkflowPath)) {
|
|
189
|
+
const content = readFileSync(ciWorkflowPath, 'utf-8');
|
|
190
|
+
ciWorkflow = yaml.parse(content);
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
test('should trigger on push and pull request', () => {
|
|
195
|
+
expect(ciWorkflow?.on).toBeDefined();
|
|
196
|
+
expect(ciWorkflow.on.push).toBeDefined();
|
|
197
|
+
expect(ciWorkflow.on.pull_request).toBeDefined();
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
test('should have test job with proper Node.js setup', () => {
|
|
201
|
+
expect(ciWorkflow?.jobs?.test).toBeDefined();
|
|
202
|
+
const testJob = ciWorkflow.jobs.test;
|
|
203
|
+
|
|
204
|
+
expect(testJob['runs-on']).toBe('ubuntu-latest');
|
|
205
|
+
|
|
206
|
+
// Should use single Node.js 20.x (no matrix for performance)
|
|
207
|
+
expect(testJob.strategy?.matrix).toBeUndefined();
|
|
208
|
+
|
|
209
|
+
// Should use Node.js 20.x in setup step
|
|
210
|
+
const nodeSetupStep = testJob?.steps?.find((step: any) => step.uses?.includes('actions/setup-node'));
|
|
211
|
+
expect(nodeSetupStep?.with?.['node-version']).toBe('20.x');
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
test('should include all required CI steps', () => {
|
|
215
|
+
const testJob = ciWorkflow?.jobs?.test;
|
|
216
|
+
const stepNames = testJob?.steps?.map((step: any) => step.name || step.uses) || [];
|
|
217
|
+
|
|
218
|
+
expect(stepNames.some((name: string) => name.toLowerCase().includes('checkout'))).toBe(true);
|
|
219
|
+
expect(stepNames.some((name: string) => name.includes('Node.js'))).toBe(true);
|
|
220
|
+
expect(stepNames.some((name: string) => name.toLowerCase().includes('install'))).toBe(true);
|
|
221
|
+
expect(stepNames.some((name: string) => name.toLowerCase().includes('lint'))).toBe(true);
|
|
222
|
+
expect(stepNames.some((name: string) => name.toLowerCase().includes('build'))).toBe(true);
|
|
223
|
+
expect(stepNames.some((name: string) => name.toLowerCase().includes('test'))).toBe(true);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
test('should focus on unit testing without cluster setup', () => {
|
|
227
|
+
const ciContent = readFileSync('.github/workflows/ci.yml', 'utf8');
|
|
228
|
+
const ciWorkflow = yaml.parse(ciContent);
|
|
229
|
+
|
|
230
|
+
const testJob = ciWorkflow.jobs?.test;
|
|
231
|
+
expect(testJob).toBeDefined();
|
|
232
|
+
|
|
233
|
+
const stepNames = testJob?.steps?.map((step: any) => step.name || step.uses) || [];
|
|
234
|
+
|
|
235
|
+
// Should NOT have cluster setup since we use mocked tests
|
|
236
|
+
expect(stepNames.some((name: string) => name.toLowerCase().includes('kind'))).toBe(false);
|
|
237
|
+
expect(stepNames.some((name: string) => name.toLowerCase().includes('cluster'))).toBe(false);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
test('should use npm ci for efficient dependency installation', () => {
|
|
241
|
+
const testJob = ciWorkflow?.jobs?.test;
|
|
242
|
+
const stepCommands = testJob?.steps?.map((step: any) => step.run) || [];
|
|
243
|
+
|
|
244
|
+
// Should use npm ci (which handles caching automatically)
|
|
245
|
+
expect(stepCommands.some((cmd: string) => cmd?.includes('npm ci'))).toBe(true);
|
|
246
|
+
|
|
247
|
+
// Should have Node.js cache enabled in setup step
|
|
248
|
+
const nodeSetupStep = testJob?.steps?.find((step: any) => step.uses?.includes('actions/setup-node'));
|
|
249
|
+
expect(nodeSetupStep?.with?.cache).toBe('npm');
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
describe('Consolidated Security Features Validation', () => {
|
|
254
|
+
let ciWorkflow: any;
|
|
255
|
+
|
|
256
|
+
beforeAll(() => {
|
|
257
|
+
const ciWorkflowPath = path.join(githubWorkflowsDir, 'ci.yml');
|
|
258
|
+
if (existsSync(ciWorkflowPath)) {
|
|
259
|
+
const content = readFileSync(ciWorkflowPath, 'utf-8');
|
|
260
|
+
ciWorkflow = yaml.parse(content);
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
test('should have separate security job with dependency audit', () => {
|
|
265
|
+
expect(ciWorkflow?.jobs?.security).toBeDefined();
|
|
266
|
+
const securityJob = ciWorkflow.jobs.security;
|
|
267
|
+
|
|
268
|
+
const auditStep = securityJob?.steps?.find((step: any) =>
|
|
269
|
+
step.name?.toLowerCase().includes('audit') ||
|
|
270
|
+
step.run?.includes('npm audit')
|
|
271
|
+
);
|
|
272
|
+
expect(auditStep).toBeDefined();
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
test('should include CodeQL analysis job', () => {
|
|
276
|
+
expect(ciWorkflow?.jobs?.security).toBeDefined();
|
|
277
|
+
const securityJob = ciWorkflow.jobs.security;
|
|
278
|
+
|
|
279
|
+
const codeqlInitStep = securityJob?.steps?.find((step: any) =>
|
|
280
|
+
step.uses?.includes('github/codeql-action/init')
|
|
281
|
+
);
|
|
282
|
+
expect(codeqlInitStep).toBeDefined();
|
|
283
|
+
|
|
284
|
+
const codeqlAnalyzeStep = securityJob?.steps?.find((step: any) =>
|
|
285
|
+
step.uses?.includes('github/codeql-action/analyze')
|
|
286
|
+
);
|
|
287
|
+
expect(codeqlAnalyzeStep).toBeDefined();
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
test('should have proper permissions for security scanning and publishing', () => {
|
|
291
|
+
expect(ciWorkflow?.permissions).toBeDefined();
|
|
292
|
+
expect(ciWorkflow.permissions.actions).toBe('read');
|
|
293
|
+
expect(ciWorkflow.permissions.contents).toBe('write'); // Needed for publishing and tagging
|
|
294
|
+
expect(ciWorkflow.permissions['security-events']).toBe('write');
|
|
295
|
+
expect(ciWorkflow.permissions['id-token']).toBe('write'); // Needed for npm publishing
|
|
296
|
+
});
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
describe('Renovate Configuration Validation', () => {
|
|
300
|
+
let renovateConfig: any;
|
|
301
|
+
|
|
302
|
+
beforeAll(() => {
|
|
303
|
+
const renovatePath = path.join(rootDir, 'renovate.json');
|
|
304
|
+
if (existsSync(renovatePath)) {
|
|
305
|
+
const content = readFileSync(renovatePath, 'utf-8');
|
|
306
|
+
renovateConfig = JSON.parse(content);
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
test('should extend base configuration', () => {
|
|
311
|
+
expect(renovateConfig?.extends).toBeDefined();
|
|
312
|
+
expect(renovateConfig.extends).toContain('config:base');
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
test('should have scheduled updates', () => {
|
|
316
|
+
expect(renovateConfig?.schedule).toBeDefined();
|
|
317
|
+
expect(Array.isArray(renovateConfig.schedule)).toBe(true);
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
test('should have package rules for intelligent grouping', () => {
|
|
321
|
+
expect(renovateConfig?.packageRules).toBeDefined();
|
|
322
|
+
expect(Array.isArray(renovateConfig.packageRules)).toBe(true);
|
|
323
|
+
expect(renovateConfig.packageRules.length).toBeGreaterThan(0);
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
test('should configure automerge for safe updates', () => {
|
|
327
|
+
const automergeRule = renovateConfig?.packageRules?.find((rule: any) =>
|
|
328
|
+
rule.automerge === true
|
|
329
|
+
);
|
|
330
|
+
expect(automergeRule).toBeDefined();
|
|
331
|
+
});
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
describe('Workflow Security and Best Practices', () => {
|
|
335
|
+
test('workflows should use specific action versions (not @main)', () => {
|
|
336
|
+
const workflowFiles = ['ci.yml', 'security.yml'];
|
|
337
|
+
|
|
338
|
+
workflowFiles.forEach(file => {
|
|
339
|
+
const workflowPath = path.join(githubWorkflowsDir, file);
|
|
340
|
+
if (existsSync(workflowPath)) {
|
|
341
|
+
const content = readFileSync(workflowPath, 'utf-8');
|
|
342
|
+
const workflow = yaml.parse(content);
|
|
343
|
+
|
|
344
|
+
// Check all jobs for action versions
|
|
345
|
+
Object.values(workflow.jobs || {}).forEach((job: any) => {
|
|
346
|
+
job.steps?.forEach((step: any) => {
|
|
347
|
+
if (step.uses && !step.uses.includes('@v')) {
|
|
348
|
+
// Allow some exceptions for well-known stable actions
|
|
349
|
+
const allowedMainActions = ['actions/checkout@main'];
|
|
350
|
+
if (!allowedMainActions.includes(step.uses)) {
|
|
351
|
+
expect(step.uses).toMatch(/@v\d+/);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
});
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
test('workflows should use secure action versions', () => {
|
|
361
|
+
const ciWorkflowPath = path.join(githubWorkflowsDir, 'ci.yml');
|
|
362
|
+
if (existsSync(ciWorkflowPath)) {
|
|
363
|
+
const content = readFileSync(ciWorkflowPath, 'utf-8');
|
|
364
|
+
const workflow = yaml.parse(content);
|
|
365
|
+
|
|
366
|
+
// Simple workflows don't need explicit permissions for basic read/test operations
|
|
367
|
+
// but should use versioned actions for security
|
|
368
|
+
Object.values(workflow.jobs || {}).forEach((job: any) => {
|
|
369
|
+
job.steps?.forEach((step: any) => {
|
|
370
|
+
if (step.uses) {
|
|
371
|
+
expect(step.uses).toMatch(/@v\d+/);
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
describe('Package.json CI/CD Integration', () => {
|
|
380
|
+
let packageJson: any;
|
|
381
|
+
|
|
382
|
+
beforeAll(() => {
|
|
383
|
+
const packagePath = path.join(rootDir, 'package.json');
|
|
384
|
+
const content = readFileSync(packagePath, 'utf-8');
|
|
385
|
+
packageJson = JSON.parse(content);
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
test('should have CI-friendly scripts', () => {
|
|
389
|
+
expect(packageJson.scripts['ci']).toBeDefined();
|
|
390
|
+
expect(packageJson.scripts['ci:test']).toBeDefined();
|
|
391
|
+
expect(packageJson.scripts['ci:build']).toBeDefined();
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
test('should have engines field for Node.js version', () => {
|
|
395
|
+
expect(packageJson.engines?.node).toBeDefined();
|
|
396
|
+
expect(packageJson.engines.node).toMatch(/>=\s*18/);
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
test('should have repository field for GitHub integration', () => {
|
|
400
|
+
expect(packageJson.repository).toBeDefined();
|
|
401
|
+
expect(typeof packageJson.repository === 'string' || packageJson.repository.type).toBe('git');
|
|
402
|
+
});
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
describe('Development Dependencies for CI/CD', () => {
|
|
406
|
+
let packageJson: any;
|
|
407
|
+
|
|
408
|
+
beforeAll(() => {
|
|
409
|
+
const packagePath = path.join(rootDir, 'package.json');
|
|
410
|
+
const content = readFileSync(packagePath, 'utf-8');
|
|
411
|
+
packageJson = JSON.parse(content);
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
test('should include security scanning tools', () => {
|
|
415
|
+
const allDeps = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
416
|
+
// Should have audit tools or rely on npm audit
|
|
417
|
+
expect(packageJson.scripts).toHaveProperty('audit');
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
test('should include linting and formatting for CI', () => {
|
|
421
|
+
const devDeps = packageJson.devDependencies || {};
|
|
422
|
+
expect(devDeps.eslint).toBeDefined();
|
|
423
|
+
expect(devDeps.prettier).toBeDefined();
|
|
424
|
+
});
|
|
425
|
+
});
|
|
426
|
+
});
|