@agents-at-scale/ark 0.1.35 → 0.1.36-rc1

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 (223) hide show
  1. package/dist/arkServices.d.ts +42 -0
  2. package/dist/arkServices.js +138 -0
  3. package/dist/arkServices.spec.d.ts +1 -0
  4. package/dist/arkServices.spec.js +24 -0
  5. package/dist/charts/charts.d.ts +5 -0
  6. package/dist/charts/charts.js +6 -0
  7. package/dist/charts/dependencies.d.ts +6 -0
  8. package/dist/charts/dependencies.js +50 -0
  9. package/dist/charts/types.d.ts +40 -0
  10. package/dist/charts/types.js +1 -0
  11. package/dist/commands/agents/index.d.ts +3 -0
  12. package/dist/commands/agents/index.js +65 -0
  13. package/dist/commands/agents/index.spec.d.ts +1 -0
  14. package/dist/commands/agents/index.spec.js +67 -0
  15. package/dist/commands/agents/selector.d.ts +8 -0
  16. package/dist/commands/agents/selector.js +53 -0
  17. package/dist/commands/agents.d.ts +2 -0
  18. package/dist/commands/agents.js +53 -0
  19. package/dist/commands/chat/index.d.ts +3 -0
  20. package/dist/commands/chat/index.js +29 -0
  21. package/dist/commands/chat.d.ts +2 -0
  22. package/dist/commands/chat.js +45 -0
  23. package/dist/commands/cluster/get.d.ts +2 -0
  24. package/dist/commands/cluster/get.js +39 -0
  25. package/dist/commands/cluster/get.spec.d.ts +1 -0
  26. package/dist/commands/cluster/get.spec.js +92 -0
  27. package/dist/commands/cluster/index.d.ts +2 -1
  28. package/dist/commands/cluster/index.js +3 -5
  29. package/dist/commands/cluster/index.spec.d.ts +1 -0
  30. package/dist/commands/cluster/index.spec.js +24 -0
  31. package/dist/commands/completion/index.d.ts +3 -0
  32. package/dist/commands/completion/index.js +230 -0
  33. package/dist/commands/completion/index.spec.d.ts +1 -0
  34. package/dist/commands/completion/index.spec.js +34 -0
  35. package/dist/commands/completion.js +159 -2
  36. package/dist/commands/config/index.d.ts +3 -0
  37. package/dist/commands/config/index.js +42 -0
  38. package/dist/commands/config/index.spec.d.ts +1 -0
  39. package/dist/commands/config/index.spec.js +78 -0
  40. package/dist/commands/config.d.ts +0 -3
  41. package/dist/commands/config.js +38 -321
  42. package/dist/commands/dashboard/index.d.ts +4 -0
  43. package/dist/commands/dashboard/index.js +39 -0
  44. package/dist/commands/dashboard.d.ts +3 -0
  45. package/dist/commands/dashboard.js +39 -0
  46. package/dist/commands/dev/index.d.ts +3 -0
  47. package/dist/commands/dev/index.js +9 -0
  48. package/dist/commands/dev/tool/check.d.ts +2 -0
  49. package/dist/commands/dev/tool/check.js +142 -0
  50. package/dist/commands/dev/tool/clean.d.ts +2 -0
  51. package/dist/commands/dev/tool/clean.js +153 -0
  52. package/dist/commands/dev/tool/generate.d.ts +2 -0
  53. package/dist/commands/dev/tool/generate.js +28 -0
  54. package/dist/commands/dev/tool/index.d.ts +2 -0
  55. package/dist/commands/dev/tool/index.js +14 -0
  56. package/dist/commands/dev/tool/init.d.ts +2 -0
  57. package/dist/commands/dev/tool/init.js +320 -0
  58. package/dist/commands/dev/tool/shared.d.ts +5 -0
  59. package/dist/commands/dev/tool/shared.js +258 -0
  60. package/dist/commands/dev/tool/status.d.ts +2 -0
  61. package/dist/commands/dev/tool/status.js +136 -0
  62. package/dist/commands/dev/tool-generate.spec.d.ts +1 -0
  63. package/dist/commands/dev/tool-generate.spec.js +163 -0
  64. package/dist/commands/dev/tool.d.ts +2 -0
  65. package/dist/commands/dev/tool.js +559 -0
  66. package/dist/commands/dev/tool.spec.d.ts +1 -0
  67. package/dist/commands/dev/tool.spec.js +48 -0
  68. package/dist/commands/docs/index.d.ts +4 -0
  69. package/dist/commands/docs/index.js +18 -0
  70. package/dist/commands/generate/config.js +5 -24
  71. package/dist/commands/generate/generators/mcpserver.d.ts +2 -1
  72. package/dist/commands/generate/generators/mcpserver.js +26 -5
  73. package/dist/commands/generate/generators/project.js +22 -41
  74. package/dist/commands/generate/index.d.ts +2 -1
  75. package/dist/commands/generate/index.js +1 -1
  76. package/dist/commands/install/index.d.ts +8 -0
  77. package/dist/commands/install/index.js +295 -0
  78. package/dist/commands/install/index.spec.d.ts +1 -0
  79. package/dist/commands/install/index.spec.js +143 -0
  80. package/dist/commands/install.d.ts +3 -0
  81. package/dist/commands/install.js +147 -0
  82. package/dist/commands/models/create.d.ts +1 -0
  83. package/dist/commands/models/create.js +213 -0
  84. package/dist/commands/models/create.spec.d.ts +1 -0
  85. package/dist/commands/models/create.spec.js +125 -0
  86. package/dist/commands/models/index.d.ts +3 -0
  87. package/dist/commands/models/index.js +75 -0
  88. package/dist/commands/models/index.spec.d.ts +1 -0
  89. package/dist/commands/models/index.spec.js +96 -0
  90. package/dist/commands/models/selector.d.ts +8 -0
  91. package/dist/commands/models/selector.js +53 -0
  92. package/dist/commands/query/index.d.ts +3 -0
  93. package/dist/commands/query/index.js +24 -0
  94. package/dist/commands/query/index.spec.d.ts +1 -0
  95. package/dist/commands/query/index.spec.js +53 -0
  96. package/dist/commands/routes/index.d.ts +3 -0
  97. package/dist/commands/routes/index.js +93 -0
  98. package/dist/commands/routes.d.ts +2 -0
  99. package/dist/commands/routes.js +101 -0
  100. package/dist/commands/status/index.d.ts +3 -0
  101. package/dist/commands/status/index.js +281 -0
  102. package/dist/commands/status.d.ts +3 -0
  103. package/dist/commands/status.js +33 -0
  104. package/dist/commands/targets/index.d.ts +3 -0
  105. package/dist/commands/targets/index.js +72 -0
  106. package/dist/commands/targets/index.spec.d.ts +1 -0
  107. package/dist/commands/targets/index.spec.js +154 -0
  108. package/dist/commands/targets.d.ts +2 -0
  109. package/dist/commands/targets.js +65 -0
  110. package/dist/commands/teams/index.d.ts +3 -0
  111. package/dist/commands/teams/index.js +64 -0
  112. package/dist/commands/teams/index.spec.d.ts +1 -0
  113. package/dist/commands/teams/index.spec.js +70 -0
  114. package/dist/commands/teams/selector.d.ts +8 -0
  115. package/dist/commands/teams/selector.js +55 -0
  116. package/dist/commands/tools/index.d.ts +3 -0
  117. package/dist/commands/tools/index.js +49 -0
  118. package/dist/commands/tools/index.spec.d.ts +1 -0
  119. package/dist/commands/tools/index.spec.js +70 -0
  120. package/dist/commands/tools/selector.d.ts +8 -0
  121. package/dist/commands/tools/selector.js +53 -0
  122. package/dist/commands/uninstall/index.d.ts +3 -0
  123. package/dist/commands/uninstall/index.js +101 -0
  124. package/dist/commands/uninstall/index.spec.d.ts +1 -0
  125. package/dist/commands/uninstall/index.spec.js +125 -0
  126. package/dist/commands/uninstall.d.ts +2 -0
  127. package/dist/commands/uninstall.js +83 -0
  128. package/dist/components/ChatUI.d.ts +16 -0
  129. package/dist/components/ChatUI.js +801 -0
  130. package/dist/components/StatusView.d.ts +10 -0
  131. package/dist/components/StatusView.js +39 -0
  132. package/dist/components/statusChecker.d.ts +14 -24
  133. package/dist/components/statusChecker.js +295 -129
  134. package/dist/config.d.ts +3 -22
  135. package/dist/config.js +10 -161
  136. package/dist/index.d.ts +1 -1
  137. package/dist/index.js +42 -42
  138. package/dist/lib/arkApiClient.d.ts +53 -0
  139. package/dist/lib/arkApiClient.js +102 -0
  140. package/dist/lib/arkApiProxy.d.ts +9 -0
  141. package/dist/lib/arkApiProxy.js +22 -0
  142. package/dist/lib/arkServiceProxy.d.ts +14 -0
  143. package/dist/lib/arkServiceProxy.js +95 -0
  144. package/dist/lib/arkStatus.d.ts +10 -0
  145. package/dist/lib/arkStatus.js +79 -0
  146. package/dist/lib/arkStatus.spec.d.ts +1 -0
  147. package/dist/lib/arkStatus.spec.js +49 -0
  148. package/dist/lib/chatClient.d.ts +33 -0
  149. package/dist/lib/chatClient.js +93 -0
  150. package/dist/lib/cluster.d.ts +2 -1
  151. package/dist/lib/cluster.js +37 -16
  152. package/dist/lib/cluster.spec.d.ts +1 -0
  153. package/dist/lib/cluster.spec.js +338 -0
  154. package/dist/lib/commandUtils.d.ts +4 -0
  155. package/dist/lib/commandUtils.js +18 -0
  156. package/dist/lib/commandUtils.test.d.ts +1 -0
  157. package/dist/lib/commandUtils.test.js +44 -0
  158. package/dist/lib/commands.d.ts +16 -0
  159. package/dist/lib/commands.js +29 -0
  160. package/dist/lib/commands.spec.d.ts +1 -0
  161. package/dist/lib/commands.spec.js +146 -0
  162. package/dist/lib/config.d.ts +26 -80
  163. package/dist/lib/config.js +70 -205
  164. package/dist/lib/config.spec.d.ts +1 -0
  165. package/dist/lib/config.spec.js +99 -0
  166. package/dist/lib/config.test.d.ts +1 -0
  167. package/dist/lib/config.test.js +93 -0
  168. package/dist/lib/consts.d.ts +0 -1
  169. package/dist/lib/consts.js +0 -2
  170. package/dist/lib/consts.spec.d.ts +1 -0
  171. package/dist/lib/consts.spec.js +15 -0
  172. package/dist/lib/dev/tools/analyzer.d.ts +30 -0
  173. package/dist/lib/dev/tools/analyzer.js +190 -0
  174. package/dist/lib/dev/tools/discover_tools.py +392 -0
  175. package/dist/lib/dev/tools/mcp-types.d.ts +28 -0
  176. package/dist/lib/dev/tools/mcp-types.js +86 -0
  177. package/dist/lib/dev/tools/types.d.ts +50 -0
  178. package/dist/lib/dev/tools/types.js +1 -0
  179. package/dist/lib/errors.js +1 -1
  180. package/dist/lib/errors.spec.d.ts +1 -0
  181. package/dist/lib/errors.spec.js +221 -0
  182. package/dist/lib/exec.d.ts +0 -4
  183. package/dist/lib/exec.js +0 -11
  184. package/dist/lib/executeQuery.d.ts +20 -0
  185. package/dist/lib/executeQuery.js +135 -0
  186. package/dist/lib/executeQuery.spec.d.ts +1 -0
  187. package/dist/lib/executeQuery.spec.js +170 -0
  188. package/dist/lib/nextSteps.d.ts +4 -0
  189. package/dist/lib/nextSteps.js +20 -0
  190. package/dist/lib/nextSteps.spec.d.ts +1 -0
  191. package/dist/lib/nextSteps.spec.js +59 -0
  192. package/dist/lib/output.d.ts +36 -0
  193. package/dist/lib/output.js +89 -0
  194. package/dist/lib/output.spec.d.ts +1 -0
  195. package/dist/lib/output.spec.js +123 -0
  196. package/dist/lib/portUtils.d.ts +8 -0
  197. package/dist/lib/portUtils.js +39 -0
  198. package/dist/lib/queryRunner.d.ts +22 -0
  199. package/dist/lib/queryRunner.js +142 -0
  200. package/dist/lib/startup.d.ts +9 -0
  201. package/dist/lib/startup.js +87 -0
  202. package/dist/lib/startup.spec.d.ts +1 -0
  203. package/dist/lib/startup.spec.js +152 -0
  204. package/dist/lib/types.d.ts +87 -3
  205. package/dist/lib/versions.d.ts +23 -0
  206. package/dist/lib/versions.js +51 -0
  207. package/dist/types/types.d.ts +40 -0
  208. package/dist/types/types.js +1 -0
  209. package/dist/ui/AgentSelector.d.ts +8 -0
  210. package/dist/ui/AgentSelector.js +53 -0
  211. package/dist/ui/MainMenu.d.ts +5 -1
  212. package/dist/ui/MainMenu.js +226 -91
  213. package/dist/ui/ModelSelector.d.ts +8 -0
  214. package/dist/ui/ModelSelector.js +53 -0
  215. package/dist/ui/TeamSelector.d.ts +8 -0
  216. package/dist/ui/TeamSelector.js +55 -0
  217. package/dist/ui/ToolSelector.d.ts +8 -0
  218. package/dist/ui/ToolSelector.js +53 -0
  219. package/dist/ui/statusFormatter.d.ts +22 -7
  220. package/dist/ui/statusFormatter.js +39 -39
  221. package/dist/ui/statusFormatter.spec.d.ts +1 -0
  222. package/dist/ui/statusFormatter.spec.js +58 -0
  223. package/package.json +16 -5
@@ -0,0 +1,338 @@
1
+ import { jest } from '@jest/globals';
2
+ const mockExeca = jest.fn();
3
+ jest.unstable_mockModule('execa', () => ({
4
+ execa: mockExeca,
5
+ }));
6
+ const { getClusterInfo, detectClusterType } = await import('./cluster.js');
7
+ describe('cluster', () => {
8
+ beforeEach(() => {
9
+ jest.clearAllMocks();
10
+ });
11
+ describe('detectClusterType', () => {
12
+ it('detects minikube cluster', async () => {
13
+ mockExeca.mockResolvedValue({ stdout: 'minikube' });
14
+ const result = await detectClusterType();
15
+ expect(result).toEqual({ type: 'minikube', context: 'minikube' });
16
+ expect(mockExeca).toHaveBeenCalledWith('kubectl', [
17
+ 'config',
18
+ 'current-context',
19
+ ]);
20
+ });
21
+ it('detects kind cluster', async () => {
22
+ mockExeca.mockResolvedValue({ stdout: 'kind-kind' });
23
+ const result = await detectClusterType();
24
+ expect(result).toEqual({ type: 'kind', context: 'kind-kind' });
25
+ });
26
+ it('detects k3s cluster', async () => {
27
+ mockExeca.mockResolvedValue({ stdout: 'k3s-default' });
28
+ const result = await detectClusterType();
29
+ expect(result).toEqual({ type: 'k3s', context: 'k3s-default' });
30
+ });
31
+ it('detects docker-desktop cluster', async () => {
32
+ mockExeca.mockResolvedValue({ stdout: 'docker-desktop' });
33
+ const result = await detectClusterType();
34
+ expect(result).toEqual({
35
+ type: 'docker-desktop',
36
+ context: 'docker-desktop',
37
+ });
38
+ });
39
+ it('detects gke cloud cluster', async () => {
40
+ mockExeca.mockResolvedValue({ stdout: 'gke_project_zone_cluster' });
41
+ const result = await detectClusterType();
42
+ expect(result).toEqual({
43
+ type: 'cloud',
44
+ context: 'gke_project_zone_cluster',
45
+ });
46
+ });
47
+ it('detects eks cloud cluster', async () => {
48
+ mockExeca.mockResolvedValue({
49
+ stdout: 'arn:aws:eks:region:account:cluster/name',
50
+ });
51
+ const result = await detectClusterType();
52
+ expect(result).toEqual({
53
+ type: 'cloud',
54
+ context: 'arn:aws:eks:region:account:cluster/name',
55
+ });
56
+ });
57
+ it('detects aks cloud cluster', async () => {
58
+ mockExeca.mockResolvedValue({ stdout: 'aks-cluster-name' });
59
+ const result = await detectClusterType();
60
+ expect(result).toEqual({ type: 'cloud', context: 'aks-cluster-name' });
61
+ });
62
+ it('returns unknown for unrecognized cluster', async () => {
63
+ mockExeca.mockResolvedValue({ stdout: 'some-other-cluster' });
64
+ const result = await detectClusterType();
65
+ expect(result).toEqual({ type: 'unknown', context: 'some-other-cluster' });
66
+ });
67
+ it('handles kubectl error', async () => {
68
+ mockExeca.mockRejectedValue(new Error('kubectl not found'));
69
+ const result = await detectClusterType();
70
+ expect(result).toEqual({ type: 'unknown', error: 'kubectl not found' });
71
+ });
72
+ });
73
+ describe('getClusterInfo', () => {
74
+ const mockConfig = {
75
+ 'current-context': 'minikube',
76
+ contexts: [
77
+ {
78
+ name: 'minikube',
79
+ context: {
80
+ namespace: 'default',
81
+ },
82
+ },
83
+ ],
84
+ };
85
+ it('gets minikube cluster info with IP', async () => {
86
+ mockExeca
87
+ .mockResolvedValueOnce({ stdout: JSON.stringify(mockConfig) })
88
+ .mockResolvedValueOnce({ stdout: 'minikube' })
89
+ .mockResolvedValueOnce({ stdout: '192.168.49.2' });
90
+ const result = await getClusterInfo();
91
+ expect(result).toEqual({
92
+ type: 'minikube',
93
+ context: 'minikube',
94
+ namespace: 'default',
95
+ ip: '192.168.49.2',
96
+ });
97
+ expect(mockExeca).toHaveBeenCalledWith('kubectl', [
98
+ 'config',
99
+ 'view',
100
+ '--minify',
101
+ '-o',
102
+ 'json',
103
+ ]);
104
+ expect(mockExeca).toHaveBeenCalledWith('kubectl', [
105
+ 'config',
106
+ 'current-context',
107
+ ]);
108
+ expect(mockExeca).toHaveBeenCalledWith('minikube', ['ip']);
109
+ });
110
+ it('falls back to kubectl for minikube IP if minikube command fails', async () => {
111
+ mockExeca
112
+ .mockResolvedValueOnce({ stdout: JSON.stringify(mockConfig) })
113
+ .mockResolvedValueOnce({ stdout: 'minikube' })
114
+ .mockRejectedValueOnce(new Error('minikube not found'))
115
+ .mockResolvedValueOnce({ stdout: '192.168.49.2' });
116
+ const result = await getClusterInfo();
117
+ expect(result.ip).toBe('192.168.49.2');
118
+ expect(mockExeca).toHaveBeenCalledWith('kubectl', [
119
+ 'get',
120
+ 'nodes',
121
+ '-o',
122
+ 'jsonpath={.items[0].status.addresses[?(@.type=="InternalIP")].address}',
123
+ ]);
124
+ });
125
+ it('gets kind cluster info with IP', async () => {
126
+ const kindConfig = {
127
+ 'current-context': 'kind-kind',
128
+ contexts: [
129
+ {
130
+ name: 'kind-kind',
131
+ context: {
132
+ namespace: 'kube-system',
133
+ },
134
+ },
135
+ ],
136
+ };
137
+ mockExeca
138
+ .mockResolvedValueOnce({ stdout: JSON.stringify(kindConfig) })
139
+ .mockResolvedValueOnce({ stdout: 'kind-kind' })
140
+ .mockResolvedValueOnce({ stdout: '172.18.0.2' });
141
+ const result = await getClusterInfo();
142
+ expect(result).toEqual({
143
+ type: 'kind',
144
+ context: 'kind-kind',
145
+ namespace: 'kube-system',
146
+ ip: '172.18.0.2',
147
+ });
148
+ });
149
+ it('gets docker-desktop cluster info', async () => {
150
+ const dockerConfig = {
151
+ 'current-context': 'docker-desktop',
152
+ contexts: [
153
+ {
154
+ name: 'docker-desktop',
155
+ context: {},
156
+ },
157
+ ],
158
+ };
159
+ mockExeca
160
+ .mockResolvedValueOnce({ stdout: JSON.stringify(dockerConfig) })
161
+ .mockResolvedValueOnce({ stdout: 'docker-desktop' });
162
+ const result = await getClusterInfo();
163
+ expect(result).toEqual({
164
+ type: 'docker-desktop',
165
+ context: 'docker-desktop',
166
+ namespace: 'default',
167
+ ip: 'localhost',
168
+ });
169
+ });
170
+ it('gets cloud cluster info with load balancer IP', async () => {
171
+ const cloudConfig = {
172
+ 'current-context': 'gke_project_zone_cluster',
173
+ contexts: [
174
+ {
175
+ name: 'gke_project_zone_cluster',
176
+ context: {
177
+ namespace: 'production',
178
+ },
179
+ },
180
+ ],
181
+ };
182
+ mockExeca
183
+ .mockResolvedValueOnce({ stdout: JSON.stringify(cloudConfig) })
184
+ .mockResolvedValueOnce({ stdout: 'gke_project_zone_cluster' })
185
+ .mockResolvedValueOnce({ stdout: '35.201.125.17' });
186
+ const result = await getClusterInfo();
187
+ expect(result).toEqual({
188
+ type: 'cloud',
189
+ context: 'gke_project_zone_cluster',
190
+ namespace: 'production',
191
+ ip: '35.201.125.17',
192
+ });
193
+ expect(mockExeca).toHaveBeenCalledWith('kubectl', [
194
+ 'get',
195
+ 'svc',
196
+ '-n',
197
+ 'istio-system',
198
+ 'istio-ingressgateway',
199
+ '-o',
200
+ 'jsonpath={.status.loadBalancer.ingress[0].ip}',
201
+ ]);
202
+ });
203
+ it('falls back to hostname for cloud cluster if no IP', async () => {
204
+ const cloudConfig = {
205
+ 'current-context': 'eks-cluster',
206
+ contexts: [
207
+ {
208
+ name: 'eks-cluster',
209
+ context: {},
210
+ },
211
+ ],
212
+ };
213
+ mockExeca
214
+ .mockResolvedValueOnce({ stdout: JSON.stringify(cloudConfig) })
215
+ .mockResolvedValueOnce({ stdout: 'eks-cluster' })
216
+ .mockResolvedValueOnce({ stdout: '' })
217
+ .mockResolvedValueOnce({ stdout: 'a1234.elb.amazonaws.com' });
218
+ const result = await getClusterInfo();
219
+ expect(result.ip).toBe('a1234.elb.amazonaws.com');
220
+ });
221
+ it('falls back to external node IP for cloud cluster', async () => {
222
+ const cloudConfig = {
223
+ 'current-context': 'gke-cluster',
224
+ contexts: [
225
+ {
226
+ name: 'gke-cluster',
227
+ context: {},
228
+ },
229
+ ],
230
+ };
231
+ mockExeca
232
+ .mockResolvedValueOnce({ stdout: JSON.stringify(cloudConfig) })
233
+ .mockResolvedValueOnce({ stdout: 'gke-cluster' })
234
+ .mockRejectedValueOnce(new Error('service not found'))
235
+ .mockResolvedValueOnce({ stdout: '35.201.125.18' });
236
+ const result = await getClusterInfo();
237
+ expect(result.ip).toBe('35.201.125.18');
238
+ expect(mockExeca).toHaveBeenCalledWith('kubectl', [
239
+ 'get',
240
+ 'nodes',
241
+ '-o',
242
+ 'jsonpath={.items[0].status.addresses[?(@.type=="ExternalIP")].address}',
243
+ ]);
244
+ });
245
+ it('gets k3s cluster info', async () => {
246
+ const k3sConfig = {
247
+ 'current-context': 'k3s-default',
248
+ contexts: [
249
+ {
250
+ name: 'k3s-default',
251
+ context: {},
252
+ },
253
+ ],
254
+ };
255
+ mockExeca
256
+ .mockResolvedValueOnce({ stdout: JSON.stringify(k3sConfig) })
257
+ .mockResolvedValueOnce({ stdout: 'k3s-default' })
258
+ .mockResolvedValueOnce({ stdout: '10.0.0.5' });
259
+ const result = await getClusterInfo();
260
+ expect(result).toEqual({
261
+ type: 'k3s',
262
+ context: 'k3s-default',
263
+ namespace: 'default',
264
+ ip: '10.0.0.5',
265
+ });
266
+ });
267
+ it('uses provided context parameter', async () => {
268
+ const multiConfig = {
269
+ 'current-context': 'kind-staging',
270
+ contexts: [
271
+ {
272
+ name: 'kind-staging',
273
+ context: {
274
+ namespace: 'staging-ns',
275
+ },
276
+ },
277
+ ],
278
+ };
279
+ mockExeca
280
+ .mockResolvedValueOnce({ stdout: JSON.stringify(multiConfig) })
281
+ .mockResolvedValueOnce({ stdout: 'kind-staging' })
282
+ .mockResolvedValueOnce({ stdout: '172.18.0.3' });
283
+ const result = await getClusterInfo('kind-staging');
284
+ expect(result.context).toBe('kind-staging');
285
+ expect(mockExeca).toHaveBeenCalledWith('kubectl', [
286
+ 'config',
287
+ 'view',
288
+ '--minify',
289
+ '-o',
290
+ 'json',
291
+ '--context',
292
+ 'kind-staging',
293
+ ]);
294
+ });
295
+ it('handles unknown cluster type', async () => {
296
+ const unknownConfig = {
297
+ 'current-context': 'custom-cluster',
298
+ contexts: [
299
+ {
300
+ name: 'custom-cluster',
301
+ context: {},
302
+ },
303
+ ],
304
+ };
305
+ mockExeca
306
+ .mockResolvedValueOnce({ stdout: JSON.stringify(unknownConfig) })
307
+ .mockResolvedValueOnce({ stdout: 'custom-cluster' })
308
+ .mockResolvedValueOnce({ stdout: '10.0.0.1' });
309
+ const result = await getClusterInfo();
310
+ expect(result).toEqual({
311
+ type: 'unknown',
312
+ context: 'custom-cluster',
313
+ namespace: 'default',
314
+ ip: '10.0.0.1',
315
+ });
316
+ });
317
+ it('handles kubectl config error', async () => {
318
+ mockExeca.mockRejectedValue(new Error('kubectl not configured'));
319
+ const result = await getClusterInfo();
320
+ expect(result).toEqual({
321
+ type: 'unknown',
322
+ error: 'kubectl not configured',
323
+ });
324
+ });
325
+ it('handles missing context in config', async () => {
326
+ const emptyConfig = {
327
+ contexts: [],
328
+ };
329
+ mockExeca
330
+ .mockResolvedValueOnce({ stdout: JSON.stringify(emptyConfig) })
331
+ .mockResolvedValueOnce({ stdout: '' })
332
+ .mockResolvedValueOnce({ stdout: '10.0.0.1' });
333
+ const result = await getClusterInfo();
334
+ expect(result.context).toBe('');
335
+ expect(result.namespace).toBe('default');
336
+ });
337
+ });
338
+ });
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Check if a command is available in the system
3
+ */
4
+ export declare function isCommandAvailable(command: string): Promise<boolean>;
@@ -0,0 +1,18 @@
1
+ import { execa } from 'execa';
2
+ /**
3
+ * Check if a command is available in the system
4
+ */
5
+ export async function isCommandAvailable(command) {
6
+ try {
7
+ if (process.platform === 'win32') {
8
+ await execa('where', [command]);
9
+ }
10
+ else {
11
+ await execa('sh', ['-c', `command -v ${command}`]);
12
+ }
13
+ return true;
14
+ }
15
+ catch (_error) {
16
+ return false;
17
+ }
18
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,44 @@
1
+ import { exec } from 'child_process';
2
+ import { isCommandAvailable } from './commandUtils';
3
+ jest.mock('child_process');
4
+ describe('commandUtils', () => {
5
+ describe('isCommandAvailable', () => {
6
+ const mockedExec = exec;
7
+ beforeEach(() => {
8
+ jest.clearAllMocks();
9
+ });
10
+ it('should return true when command exists on unix', async () => {
11
+ const originalPlatform = Object.getOwnPropertyDescriptor(process, 'platform');
12
+ Object.defineProperty(process, 'platform', { value: 'darwin' });
13
+ mockedExec.mockImplementation((cmd, callback) => {
14
+ callback(null, '/usr/local/bin/helm', '');
15
+ });
16
+ const result = await isCommandAvailable('helm');
17
+ expect(result).toBe(true);
18
+ expect(mockedExec).toHaveBeenCalledWith('command -v helm', expect.any(Function));
19
+ if (originalPlatform) {
20
+ Object.defineProperty(process, 'platform', originalPlatform);
21
+ }
22
+ });
23
+ it('should return true when command exists on windows', async () => {
24
+ const originalPlatform = Object.getOwnPropertyDescriptor(process, 'platform');
25
+ Object.defineProperty(process, 'platform', { value: 'win32' });
26
+ mockedExec.mockImplementation((cmd, callback) => {
27
+ callback(null, 'C:\\Program Files\\helm\\helm.exe', '');
28
+ });
29
+ const result = await isCommandAvailable('helm');
30
+ expect(result).toBe(true);
31
+ expect(mockedExec).toHaveBeenCalledWith('where helm', expect.any(Function));
32
+ if (originalPlatform) {
33
+ Object.defineProperty(process, 'platform', originalPlatform);
34
+ }
35
+ });
36
+ it('should return false when command does not exist', async () => {
37
+ mockedExec.mockImplementation((cmd, callback) => {
38
+ callback(new Error('command not found'), null, 'command not found');
39
+ });
40
+ const result = await isCommandAvailable('nonexistent');
41
+ expect(result).toBe(false);
42
+ });
43
+ });
44
+ });
@@ -0,0 +1,16 @@
1
+ import { type Options } from 'execa';
2
+ /**
3
+ * Check if a command exists and is executable by running it with specified args
4
+ */
5
+ export declare function checkCommandExists(command: string, args?: string[]): Promise<boolean>;
6
+ export { checkCommandExists as isCommandAvailable };
7
+ /**
8
+ * Execute a command with optional verbose output
9
+ * @param command The command to execute
10
+ * @param args Array of arguments
11
+ * @param execaOptions Standard execa options
12
+ * @param additionalOptions Additional options for execute (e.g., verbose)
13
+ */
14
+ export declare function execute(command: string, args?: string[], execaOptions?: Options, additionalOptions?: {
15
+ verbose?: boolean;
16
+ }): Promise<import("execa").Result<Options>>;
@@ -0,0 +1,29 @@
1
+ import { execa } from 'execa';
2
+ import chalk from 'chalk';
3
+ /**
4
+ * Check if a command exists and is executable by running it with specified args
5
+ */
6
+ export async function checkCommandExists(command, args = ['--version']) {
7
+ try {
8
+ await execa(command, args);
9
+ return true;
10
+ }
11
+ catch {
12
+ return false;
13
+ }
14
+ }
15
+ export { checkCommandExists as isCommandAvailable };
16
+ /**
17
+ * Execute a command with optional verbose output
18
+ * @param command The command to execute
19
+ * @param args Array of arguments
20
+ * @param execaOptions Standard execa options
21
+ * @param additionalOptions Additional options for execute (e.g., verbose)
22
+ */
23
+ export async function execute(command, args = [], execaOptions = {}, additionalOptions = {}) {
24
+ const { verbose = false } = additionalOptions;
25
+ if (verbose) {
26
+ console.log(chalk.gray(`$ ${command} ${args.join(' ')}`));
27
+ }
28
+ return await execa(command, args, execaOptions);
29
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,146 @@
1
+ import { describe, it, expect, jest, beforeEach } from '@jest/globals';
2
+ // Mock chalk to avoid ANSI codes in tests
3
+ jest.unstable_mockModule('chalk', () => ({
4
+ default: {
5
+ gray: (str) => str,
6
+ },
7
+ }));
8
+ // Mock execa using unstable_mockModule
9
+ jest.unstable_mockModule('execa', () => ({
10
+ execa: jest.fn(),
11
+ }));
12
+ // Dynamic imports after mock
13
+ const { execa } = await import('execa');
14
+ const { checkCommandExists, execute } = await import('./commands.js');
15
+ // Type the mock properly
16
+ const mockExeca = execa;
17
+ describe('commands', () => {
18
+ describe('checkCommandExists', () => {
19
+ beforeEach(() => {
20
+ jest.clearAllMocks();
21
+ });
22
+ it('returns true when command executes successfully', async () => {
23
+ mockExeca.mockResolvedValue({
24
+ stdout: 'v1.0.0',
25
+ stderr: '',
26
+ exitCode: 0,
27
+ });
28
+ const result = await checkCommandExists('helm', ['version']);
29
+ expect(result).toBe(true);
30
+ expect(mockExeca).toHaveBeenCalledWith('helm', ['version']);
31
+ });
32
+ it('returns false when command fails', async () => {
33
+ mockExeca.mockRejectedValue(new Error('Command not found'));
34
+ const result = await checkCommandExists('nonexistent', ['--version']);
35
+ expect(result).toBe(false);
36
+ expect(mockExeca).toHaveBeenCalledWith('nonexistent', ['--version']);
37
+ });
38
+ it('uses default --version arg when no args provided', async () => {
39
+ mockExeca.mockResolvedValue({
40
+ stdout: '1.0.0',
41
+ stderr: '',
42
+ exitCode: 0,
43
+ });
44
+ const result = await checkCommandExists('node');
45
+ expect(result).toBe(true);
46
+ expect(mockExeca).toHaveBeenCalledWith('node', ['--version']);
47
+ });
48
+ it('uses custom args when provided', async () => {
49
+ mockExeca.mockResolvedValue({
50
+ stdout: 'Client Version: v1.28.0',
51
+ stderr: '',
52
+ exitCode: 0,
53
+ });
54
+ const result = await checkCommandExists('kubectl', [
55
+ 'version',
56
+ '--client',
57
+ ]);
58
+ expect(result).toBe(true);
59
+ expect(mockExeca).toHaveBeenCalledWith('kubectl', [
60
+ 'version',
61
+ '--client',
62
+ ]);
63
+ });
64
+ it('handles empty args array', async () => {
65
+ mockExeca.mockResolvedValue({
66
+ stdout: '',
67
+ stderr: '',
68
+ exitCode: 0,
69
+ });
70
+ const result = await checkCommandExists('echo', []);
71
+ expect(result).toBe(true);
72
+ expect(mockExeca).toHaveBeenCalledWith('echo', []);
73
+ });
74
+ });
75
+ describe('execute', () => {
76
+ let mockConsoleLog;
77
+ beforeEach(() => {
78
+ jest.clearAllMocks();
79
+ mockConsoleLog = jest.spyOn(console, 'log').mockImplementation(() => { });
80
+ });
81
+ afterEach(() => {
82
+ mockConsoleLog.mockRestore();
83
+ });
84
+ it('executes command without verbose output by default', async () => {
85
+ mockExeca.mockResolvedValue({
86
+ stdout: 'success',
87
+ stderr: '',
88
+ exitCode: 0,
89
+ });
90
+ await execute('helm', ['install', 'test'], { stdio: 'inherit' });
91
+ expect(mockConsoleLog).not.toHaveBeenCalled();
92
+ expect(mockExeca).toHaveBeenCalledWith('helm', ['install', 'test'], {
93
+ stdio: 'inherit',
94
+ });
95
+ });
96
+ it('prints command when verbose is true', async () => {
97
+ mockExeca.mockResolvedValue({
98
+ stdout: 'success',
99
+ stderr: '',
100
+ exitCode: 0,
101
+ });
102
+ await execute('helm', ['install', 'test'], { stdio: 'inherit' }, { verbose: true });
103
+ expect(mockConsoleLog).toHaveBeenCalledWith('$ helm install test');
104
+ expect(mockExeca).toHaveBeenCalledWith('helm', ['install', 'test'], {
105
+ stdio: 'inherit',
106
+ });
107
+ });
108
+ it('works with empty args array', async () => {
109
+ mockExeca.mockResolvedValue({
110
+ stdout: '',
111
+ stderr: '',
112
+ exitCode: 0,
113
+ });
114
+ await execute('ls', [], {}, { verbose: true });
115
+ expect(mockConsoleLog).toHaveBeenCalledWith('$ ls ');
116
+ expect(mockExeca).toHaveBeenCalledWith('ls', [], {});
117
+ });
118
+ it('passes through execa options correctly', async () => {
119
+ mockExeca.mockResolvedValue({
120
+ stdout: '',
121
+ stderr: '',
122
+ exitCode: 0,
123
+ });
124
+ const execaOpts = { stdio: 'pipe', timeout: 5000, cwd: '/tmp' };
125
+ await execute('kubectl', ['get', 'pods'], execaOpts);
126
+ expect(mockConsoleLog).not.toHaveBeenCalled();
127
+ expect(mockExeca).toHaveBeenCalledWith('kubectl', ['get', 'pods'], execaOpts);
128
+ });
129
+ it('handles command failure', async () => {
130
+ const error = new Error('Command failed');
131
+ mockExeca.mockRejectedValue(error);
132
+ await expect(execute('fail', ['now'])).rejects.toThrow('Command failed');
133
+ expect(mockExeca).toHaveBeenCalledWith('fail', ['now'], {});
134
+ });
135
+ it('defaults to no verbose when additionalOptions not provided', async () => {
136
+ mockExeca.mockResolvedValue({
137
+ stdout: 'ok',
138
+ stderr: '',
139
+ exitCode: 0,
140
+ });
141
+ await execute('echo', ['test']);
142
+ expect(mockConsoleLog).not.toHaveBeenCalled();
143
+ expect(mockExeca).toHaveBeenCalledWith('echo', ['test'], {});
144
+ });
145
+ });
146
+ });