@nexusgpu/repterm-plugin-kubectl 0.1.0

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 (101) hide show
  1. package/README.md +277 -0
  2. package/dist/index.d.ts +314 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +544 -0
  5. package/dist/matchers.d.ts +113 -0
  6. package/dist/matchers.d.ts.map +1 -0
  7. package/dist/matchers.js +527 -0
  8. package/dist/plugin-kubectl/examples/00-simple-demo.d.ts +10 -0
  9. package/dist/plugin-kubectl/examples/00-simple-demo.d.ts.map +1 -0
  10. package/dist/plugin-kubectl/examples/00-simple-demo.js +51 -0
  11. package/dist/plugin-kubectl/examples/01-basic-kubectl.d.ts +13 -0
  12. package/dist/plugin-kubectl/examples/01-basic-kubectl.d.ts.map +1 -0
  13. package/dist/plugin-kubectl/examples/01-basic-kubectl.js +86 -0
  14. package/dist/plugin-kubectl/examples/02-debugging.d.ts +13 -0
  15. package/dist/plugin-kubectl/examples/02-debugging.d.ts.map +1 -0
  16. package/dist/plugin-kubectl/examples/02-debugging.js +80 -0
  17. package/dist/plugin-kubectl/examples/03-resource-management.d.ts +13 -0
  18. package/dist/plugin-kubectl/examples/03-resource-management.d.ts.map +1 -0
  19. package/dist/plugin-kubectl/examples/03-resource-management.js +134 -0
  20. package/dist/plugin-kubectl/examples/04-rollout.d.ts +13 -0
  21. package/dist/plugin-kubectl/examples/04-rollout.d.ts.map +1 -0
  22. package/dist/plugin-kubectl/examples/04-rollout.js +122 -0
  23. package/dist/plugin-kubectl/examples/05-matchers.d.ts +15 -0
  24. package/dist/plugin-kubectl/examples/05-matchers.d.ts.map +1 -0
  25. package/dist/plugin-kubectl/examples/05-matchers.js +138 -0
  26. package/dist/plugin-kubectl/examples/06-advanced.d.ts +14 -0
  27. package/dist/plugin-kubectl/examples/06-advanced.d.ts.map +1 -0
  28. package/dist/plugin-kubectl/examples/06-advanced.js +140 -0
  29. package/dist/plugin-kubectl/examples/tensor-fusion/00-prerequisites.d.ts +14 -0
  30. package/dist/plugin-kubectl/examples/tensor-fusion/00-prerequisites.d.ts.map +1 -0
  31. package/dist/plugin-kubectl/examples/tensor-fusion/00-prerequisites.js +66 -0
  32. package/dist/plugin-kubectl/examples/tensor-fusion/01-workload-allocation.d.ts +14 -0
  33. package/dist/plugin-kubectl/examples/tensor-fusion/01-workload-allocation.d.ts.map +1 -0
  34. package/dist/plugin-kubectl/examples/tensor-fusion/01-workload-allocation.js +145 -0
  35. package/dist/plugin-kubectl/examples/tensor-fusion/02-annotation-mode.d.ts +13 -0
  36. package/dist/plugin-kubectl/examples/tensor-fusion/02-annotation-mode.d.ts.map +1 -0
  37. package/dist/plugin-kubectl/examples/tensor-fusion/02-annotation-mode.js +123 -0
  38. package/dist/plugin-kubectl/examples/tensor-fusion/03-insufficient.d.ts +17 -0
  39. package/dist/plugin-kubectl/examples/tensor-fusion/03-insufficient.d.ts.map +1 -0
  40. package/dist/plugin-kubectl/examples/tensor-fusion/03-insufficient.js +96 -0
  41. package/dist/plugin-kubectl/examples/tensor-fusion/04-release.d.ts +13 -0
  42. package/dist/plugin-kubectl/examples/tensor-fusion/04-release.d.ts.map +1 -0
  43. package/dist/plugin-kubectl/examples/tensor-fusion/04-release.js +117 -0
  44. package/dist/plugin-kubectl/examples/tensor-fusion/05-multi-workload-shared-gpu.d.ts +14 -0
  45. package/dist/plugin-kubectl/examples/tensor-fusion/05-multi-workload-shared-gpu.d.ts.map +1 -0
  46. package/dist/plugin-kubectl/examples/tensor-fusion/05-multi-workload-shared-gpu.js +145 -0
  47. package/dist/plugin-kubectl/examples/tensor-fusion/06-workload-resource-resize.d.ts +14 -0
  48. package/dist/plugin-kubectl/examples/tensor-fusion/06-workload-resource-resize.d.ts.map +1 -0
  49. package/dist/plugin-kubectl/examples/tensor-fusion/06-workload-resource-resize.js +235 -0
  50. package/dist/plugin-kubectl/examples/tensor-fusion/07-workload-worker-pod-generation.d.ts +15 -0
  51. package/dist/plugin-kubectl/examples/tensor-fusion/07-workload-worker-pod-generation.d.ts.map +1 -0
  52. package/dist/plugin-kubectl/examples/tensor-fusion/07-workload-worker-pod-generation.js +146 -0
  53. package/dist/plugin-kubectl/examples/tensor-fusion/08-workload-replicas-scale.d.ts +13 -0
  54. package/dist/plugin-kubectl/examples/tensor-fusion/08-workload-replicas-scale.d.ts.map +1 -0
  55. package/dist/plugin-kubectl/examples/tensor-fusion/08-workload-replicas-scale.js +141 -0
  56. package/dist/plugin-kubectl/examples/tensor-fusion/09-gpu-remote-invocation.d.ts +15 -0
  57. package/dist/plugin-kubectl/examples/tensor-fusion/09-gpu-remote-invocation.d.ts.map +1 -0
  58. package/dist/plugin-kubectl/examples/tensor-fusion/09-gpu-remote-invocation.js +256 -0
  59. package/dist/plugin-kubectl/examples/tensor-fusion/_config.d.ts +71 -0
  60. package/dist/plugin-kubectl/examples/tensor-fusion/_config.d.ts.map +1 -0
  61. package/dist/plugin-kubectl/examples/tensor-fusion/_config.js +159 -0
  62. package/dist/plugin-kubectl/src/index.d.ts +314 -0
  63. package/dist/plugin-kubectl/src/index.d.ts.map +1 -0
  64. package/dist/plugin-kubectl/src/index.js +545 -0
  65. package/dist/plugin-kubectl/src/matchers.d.ts +113 -0
  66. package/dist/plugin-kubectl/src/matchers.d.ts.map +1 -0
  67. package/dist/plugin-kubectl/src/matchers.js +527 -0
  68. package/dist/plugin-kubectl/src/result.d.ts +80 -0
  69. package/dist/plugin-kubectl/src/result.d.ts.map +1 -0
  70. package/dist/plugin-kubectl/src/result.js +134 -0
  71. package/dist/repterm/src/api/describe.d.ts +18 -0
  72. package/dist/repterm/src/api/describe.d.ts.map +1 -0
  73. package/dist/repterm/src/api/describe.js +32 -0
  74. package/dist/repterm/src/api/expect.d.ts +43 -0
  75. package/dist/repterm/src/api/expect.d.ts.map +1 -0
  76. package/dist/repterm/src/api/expect.js +166 -0
  77. package/dist/repterm/src/api/hooks.d.ts +178 -0
  78. package/dist/repterm/src/api/hooks.d.ts.map +1 -0
  79. package/dist/repterm/src/api/hooks.js +230 -0
  80. package/dist/repterm/src/api/steps.d.ts +45 -0
  81. package/dist/repterm/src/api/steps.d.ts.map +1 -0
  82. package/dist/repterm/src/api/steps.js +105 -0
  83. package/dist/repterm/src/api/test.d.ts +101 -0
  84. package/dist/repterm/src/api/test.d.ts.map +1 -0
  85. package/dist/repterm/src/api/test.js +206 -0
  86. package/dist/repterm/src/index.d.ts +15 -0
  87. package/dist/repterm/src/index.d.ts.map +1 -0
  88. package/dist/repterm/src/index.js +23 -0
  89. package/dist/repterm/src/plugin/index.d.ts +47 -0
  90. package/dist/repterm/src/plugin/index.d.ts.map +1 -0
  91. package/dist/repterm/src/plugin/index.js +85 -0
  92. package/dist/repterm/src/plugin/withPlugins.d.ts +71 -0
  93. package/dist/repterm/src/plugin/withPlugins.d.ts.map +1 -0
  94. package/dist/repterm/src/plugin/withPlugins.js +100 -0
  95. package/dist/repterm/src/runner/models.d.ts +261 -0
  96. package/dist/repterm/src/runner/models.d.ts.map +1 -0
  97. package/dist/repterm/src/runner/models.js +4 -0
  98. package/dist/result.d.ts +80 -0
  99. package/dist/result.d.ts.map +1 -0
  100. package/dist/result.js +134 -0
  101. package/package.json +38 -0
@@ -0,0 +1,527 @@
1
+ /**
2
+ * Kubernetes Matchers for Repterm
3
+ *
4
+ * Provides expect() matchers for Kubernetes resources.
5
+ * All operations are executed through kubectl API (PTY visible).
6
+ *
7
+ * @packageDocumentation
8
+ */
9
+ import { expect } from 'bun:test';
10
+ import { KubectlResult } from './result.js';
11
+ // ===== K8sResource Wrapper =====
12
+ /**
13
+ * Wrapper class for Kubernetes resources
14
+ * Holds reference to kubectl methods for matcher operations
15
+ */
16
+ export class K8sResource {
17
+ kubectl;
18
+ kind;
19
+ name;
20
+ constructor(kubectl, kind, name) {
21
+ this.kubectl = kubectl;
22
+ this.kind = kind;
23
+ this.name = name;
24
+ }
25
+ }
26
+ // ===== Helper Functions =====
27
+ /**
28
+ * Create a Pod resource wrapper
29
+ */
30
+ export function pod(kubectl, name) {
31
+ return new K8sResource(kubectl, 'pod', name);
32
+ }
33
+ /**
34
+ * Create a Deployment resource wrapper
35
+ */
36
+ export function deployment(kubectl, name) {
37
+ return new K8sResource(kubectl, 'deployment', name);
38
+ }
39
+ /**
40
+ * Create a Service resource wrapper
41
+ */
42
+ export function service(kubectl, name) {
43
+ return new K8sResource(kubectl, 'service', name);
44
+ }
45
+ /**
46
+ * Create a StatefulSet resource wrapper
47
+ */
48
+ export function statefulset(kubectl, name) {
49
+ return new K8sResource(kubectl, 'statefulset', name);
50
+ }
51
+ /**
52
+ * Create a Job resource wrapper
53
+ */
54
+ export function job(kubectl, name) {
55
+ return new K8sResource(kubectl, 'job', name);
56
+ }
57
+ /**
58
+ * Create a ConfigMap resource wrapper
59
+ */
60
+ export function configmap(kubectl, name) {
61
+ return new K8sResource(kubectl, 'configmap', name);
62
+ }
63
+ /**
64
+ * Create a Secret resource wrapper
65
+ */
66
+ export function secret(kubectl, name) {
67
+ return new K8sResource(kubectl, 'secret', name);
68
+ }
69
+ /**
70
+ * Create a generic resource wrapper
71
+ */
72
+ export function resource(kubectl, kind, name) {
73
+ return new K8sResource(kubectl, kind, name);
74
+ }
75
+ // ===== Tensor Fusion CRD Wrappers =====
76
+ /**
77
+ * Create a GPUPool resource wrapper (Tensor Fusion CRD)
78
+ */
79
+ export function gpupool(kubectl, name) {
80
+ return new K8sResource(kubectl, 'gpupool', name);
81
+ }
82
+ /**
83
+ * Create a GPU resource wrapper (Tensor Fusion CRD)
84
+ */
85
+ export function gpu(kubectl, name) {
86
+ return new K8sResource(kubectl, 'gpu', name);
87
+ }
88
+ /**
89
+ * Create a TensorFusionWorkload resource wrapper (Tensor Fusion CRD)
90
+ */
91
+ export function tensorfusionworkload(kubectl, name) {
92
+ return new K8sResource(kubectl, 'tensorfusionworkload', name);
93
+ }
94
+ /**
95
+ * Create a TensorFusionConnection resource wrapper (Tensor Fusion CRD)
96
+ */
97
+ export function tensorfusionconnection(kubectl, name) {
98
+ return new K8sResource(kubectl, 'tensorfusionconnection', name);
99
+ }
100
+ /**
101
+ * Create a custom CRD resource wrapper with explicit API group
102
+ * @param kubectl - kubectl methods instance
103
+ * @param kind - Resource kind (e.g., 'gpupool.tensor-fusion.ai')
104
+ * @param name - Resource name
105
+ */
106
+ export function crd(kubectl, kind, name) {
107
+ return new K8sResource(kubectl, kind, name);
108
+ }
109
+ // ===== Type Guard =====
110
+ function isK8sResource(value) {
111
+ return value instanceof K8sResource;
112
+ }
113
+ function isKubectlResult(value) {
114
+ return value instanceof KubectlResult;
115
+ }
116
+ // ===== Register Matchers =====
117
+ /**
118
+ * Register K8s matchers with expect
119
+ */
120
+ export function registerK8sMatchers() {
121
+ expect.extend({
122
+ /**
123
+ * Assert that a kubectl operation succeeded
124
+ */
125
+ async toBeSuccessful(received) {
126
+ if (!isKubectlResult(received)) {
127
+ return {
128
+ pass: false,
129
+ message: () => 'Expected value to be a KubectlResult (from kubectl.apply/delete/patch/scale/label)',
130
+ };
131
+ }
132
+ const pass = received.successful;
133
+ return {
134
+ pass,
135
+ message: () => pass
136
+ ? 'Expected kubectl command to fail, but it succeeded'
137
+ : `Expected kubectl command to succeed, but failed:\n${received.output.slice(0, 500)}`,
138
+ actual: pass ? 'succeeded' : 'failed',
139
+ expected: 'succeeded',
140
+ };
141
+ },
142
+ /**
143
+ * Assert that a Pod is in Running state
144
+ */
145
+ async toBeRunning(received, ...args) {
146
+ const timeout = args[0] ?? 60000;
147
+ if (!isK8sResource(received)) {
148
+ return {
149
+ pass: false,
150
+ message: () => 'Expected value to be a K8sResource',
151
+ };
152
+ }
153
+ const { kubectl, kind, name } = received;
154
+ if (kind !== 'pod') {
155
+ return {
156
+ pass: false,
157
+ message: () => `toBeRunning() is only valid for pods, got ${kind}`,
158
+ };
159
+ }
160
+ try {
161
+ await kubectl.waitForPod(name, 'Running', timeout);
162
+ return {
163
+ pass: true,
164
+ message: () => `Expected pod/${name} not to be Running`,
165
+ };
166
+ }
167
+ catch {
168
+ return {
169
+ pass: false,
170
+ message: () => `Expected pod/${name} to be Running within ${timeout}ms`,
171
+ actual: 'Not Running',
172
+ expected: 'Running',
173
+ };
174
+ }
175
+ },
176
+ /**
177
+ * Assert that a Pod has a specific phase
178
+ */
179
+ async toHavePhase(received, ...args) {
180
+ const phase = args[0];
181
+ if (!isK8sResource(received)) {
182
+ return {
183
+ pass: false,
184
+ message: () => 'Expected value to be a K8sResource',
185
+ };
186
+ }
187
+ const { kubectl, kind, name } = received;
188
+ try {
189
+ const actualPhase = await kubectl.getJsonPath(kind, name, '.status.phase');
190
+ const pass = actualPhase === phase;
191
+ return {
192
+ pass,
193
+ message: () => pass
194
+ ? `Expected ${kind}/${name} not to have phase ${phase}`
195
+ : `Expected ${kind}/${name} to have phase ${phase}, got ${actualPhase}`,
196
+ actual: actualPhase,
197
+ expected: phase,
198
+ };
199
+ }
200
+ catch (e) {
201
+ return {
202
+ pass: false,
203
+ message: () => `Failed to get ${kind}/${name}: ${e}`,
204
+ };
205
+ }
206
+ },
207
+ /**
208
+ * Assert that a resource has a specific number of replicas
209
+ */
210
+ async toHaveReplicas(received, ...args) {
211
+ const count = args[0];
212
+ if (!isK8sResource(received)) {
213
+ return {
214
+ pass: false,
215
+ message: () => 'Expected value to be a K8sResource',
216
+ };
217
+ }
218
+ const { kubectl, kind, name } = received;
219
+ try {
220
+ const actual = await kubectl.getJsonPath(kind, name, '.status.replicas') ?? 0;
221
+ const pass = actual === count;
222
+ return {
223
+ pass,
224
+ message: () => pass
225
+ ? `Expected ${kind}/${name} not to have ${count} replicas`
226
+ : `Expected ${kind}/${name} to have ${count} replicas, got ${actual}`,
227
+ actual,
228
+ expected: count,
229
+ };
230
+ }
231
+ catch (e) {
232
+ return {
233
+ pass: false,
234
+ message: () => `Failed to get ${kind}/${name}: ${e}`,
235
+ };
236
+ }
237
+ },
238
+ /**
239
+ * Assert that a resource has a specific number of available replicas
240
+ */
241
+ async toHaveAvailableReplicas(received, ...args) {
242
+ const count = args[0];
243
+ if (!isK8sResource(received)) {
244
+ return {
245
+ pass: false,
246
+ message: () => 'Expected value to be a K8sResource',
247
+ };
248
+ }
249
+ const { kubectl, kind, name } = received;
250
+ try {
251
+ const actual = await kubectl.getJsonPath(kind, name, '.status.availableReplicas') ?? 0;
252
+ const pass = actual === count;
253
+ return {
254
+ pass,
255
+ message: () => pass
256
+ ? `Expected ${kind}/${name} not to have ${count} available replicas`
257
+ : `Expected ${kind}/${name} to have ${count} available replicas, got ${actual}`,
258
+ actual,
259
+ expected: count,
260
+ };
261
+ }
262
+ catch (e) {
263
+ return {
264
+ pass: false,
265
+ message: () => `Failed to get ${kind}/${name}: ${e}`,
266
+ };
267
+ }
268
+ },
269
+ /**
270
+ * Assert that a Deployment is available
271
+ */
272
+ async toBeAvailable(received) {
273
+ if (!isK8sResource(received)) {
274
+ return {
275
+ pass: false,
276
+ message: () => 'Expected value to be a K8sResource',
277
+ };
278
+ }
279
+ const { kubectl, kind, name } = received;
280
+ try {
281
+ const jsonPath = '.status.conditions[?(@.type=="Available")].status';
282
+ const statusValue = await kubectl.getJsonPath(kind, name, jsonPath);
283
+ // JSONPath may return multiple values separated by space, take first
284
+ const actualStatus = statusValue?.split(' ')?.[0];
285
+ const pass = actualStatus === 'True';
286
+ return {
287
+ pass,
288
+ message: () => pass
289
+ ? `Expected ${kind}/${name} not to be available`
290
+ : `Expected ${kind}/${name} to be available`,
291
+ actual: actualStatus,
292
+ expected: 'True',
293
+ };
294
+ }
295
+ catch (e) {
296
+ return {
297
+ pass: false,
298
+ message: () => `Failed to get ${kind}/${name}: ${e}`,
299
+ };
300
+ }
301
+ },
302
+ /**
303
+ * Assert that a resource exists in the cluster
304
+ */
305
+ async toExistInCluster(received) {
306
+ if (!isK8sResource(received)) {
307
+ return {
308
+ pass: false,
309
+ message: () => 'Expected value to be a K8sResource',
310
+ };
311
+ }
312
+ const { kubectl, kind, name } = received;
313
+ const exists = await kubectl.exists(kind, name);
314
+ return {
315
+ pass: exists,
316
+ message: () => exists
317
+ ? `Expected ${kind}/${name} not to exist in cluster`
318
+ : `Expected ${kind}/${name} to exist in cluster`,
319
+ };
320
+ },
321
+ /**
322
+ * Assert that a resource has a specific label
323
+ */
324
+ async toHaveLabel(received, ...args) {
325
+ const key = args[0];
326
+ const value = args[1];
327
+ if (!isK8sResource(received)) {
328
+ return {
329
+ pass: false,
330
+ message: () => 'Expected value to be a K8sResource',
331
+ };
332
+ }
333
+ const { kubectl, kind, name } = received;
334
+ try {
335
+ // Escape dots in label key for JSONPath
336
+ const escapedKey = key.replace(/\./g, '\\.');
337
+ const jsonPath = `.metadata.labels.${escapedKey}`;
338
+ const actualValue = await kubectl.getJsonPath(kind, name, jsonPath);
339
+ const pass = value !== undefined ? actualValue === value : actualValue !== undefined;
340
+ return {
341
+ pass,
342
+ message: () => {
343
+ if (value !== undefined) {
344
+ return pass
345
+ ? `Expected ${kind}/${name} not to have label ${key}=${value}`
346
+ : `Expected ${kind}/${name} to have label ${key}=${value}, got ${actualValue}`;
347
+ }
348
+ return pass
349
+ ? `Expected ${kind}/${name} not to have label ${key}`
350
+ : `Expected ${kind}/${name} to have label ${key}`;
351
+ },
352
+ actual: actualValue,
353
+ expected: value ?? `label "${key}" exists`,
354
+ };
355
+ }
356
+ catch (e) {
357
+ return {
358
+ pass: false,
359
+ message: () => `Failed to get ${kind}/${name}: ${e}`,
360
+ };
361
+ }
362
+ },
363
+ /**
364
+ * Assert that a resource has a specific annotation
365
+ */
366
+ async toHaveAnnotation(received, ...args) {
367
+ const key = args[0];
368
+ const value = args[1];
369
+ if (!isK8sResource(received)) {
370
+ return {
371
+ pass: false,
372
+ message: () => 'Expected value to be a K8sResource',
373
+ };
374
+ }
375
+ const { kubectl, kind, name } = received;
376
+ try {
377
+ // Escape dots in annotation key for JSONPath
378
+ const escapedKey = key.replace(/\./g, '\\.');
379
+ const jsonPath = `.metadata.annotations.${escapedKey}`;
380
+ const actualValue = await kubectl.getJsonPath(kind, name, jsonPath);
381
+ const pass = value !== undefined ? actualValue === value : actualValue !== undefined;
382
+ return {
383
+ pass,
384
+ message: () => {
385
+ if (value !== undefined) {
386
+ return pass
387
+ ? `Expected ${kind}/${name} not to have annotation ${key}=${value}`
388
+ : `Expected ${kind}/${name} to have annotation ${key}=${value}, got ${actualValue}`;
389
+ }
390
+ return pass
391
+ ? `Expected ${kind}/${name} not to have annotation ${key}`
392
+ : `Expected ${kind}/${name} to have annotation ${key}`;
393
+ },
394
+ actual: actualValue,
395
+ expected: value ?? `annotation "${key}" exists`,
396
+ };
397
+ }
398
+ catch (e) {
399
+ return {
400
+ pass: false,
401
+ message: () => `Failed to get ${kind}/${name}: ${e}`,
402
+ };
403
+ }
404
+ },
405
+ /**
406
+ * Assert that a resource has a specific condition
407
+ */
408
+ async toHaveCondition(received, ...args) {
409
+ const type = args[0];
410
+ const status = args[1];
411
+ if (!isK8sResource(received)) {
412
+ return {
413
+ pass: false,
414
+ message: () => 'Expected value to be a K8sResource',
415
+ };
416
+ }
417
+ const { kubectl, kind, name } = received;
418
+ try {
419
+ const jsonPath = `.status.conditions[?(@.type=="${type}")].status`;
420
+ const statusValue = await kubectl.getJsonPath(kind, name, jsonPath);
421
+ // JSONPath may return multiple values separated by space, take first
422
+ const actualStatus = statusValue?.split(' ')?.[0];
423
+ const pass = actualStatus === status;
424
+ return {
425
+ pass,
426
+ message: () => pass
427
+ ? `Expected ${kind}/${name} not to have condition ${type}=${status}`
428
+ : `Expected ${kind}/${name} to have condition ${type}=${status}, got ${actualStatus ?? 'not found'}`,
429
+ actual: actualStatus,
430
+ expected: status,
431
+ };
432
+ }
433
+ catch (e) {
434
+ return {
435
+ pass: false,
436
+ message: () => `Failed to get ${kind}/${name}: ${e}`,
437
+ };
438
+ }
439
+ },
440
+ /**
441
+ * Assert that a resource does not exist in the cluster
442
+ */
443
+ async toNotExistInCluster(received) {
444
+ if (!isK8sResource(received)) {
445
+ return {
446
+ pass: false,
447
+ message: () => 'Expected value to be a K8sResource',
448
+ };
449
+ }
450
+ const { kubectl, kind, name } = received;
451
+ const exists = await kubectl.exists(kind, name);
452
+ return {
453
+ pass: !exists,
454
+ message: () => !exists
455
+ ? `Expected ${kind}/${name} to exist in cluster`
456
+ : `Expected ${kind}/${name} not to exist in cluster`,
457
+ };
458
+ },
459
+ /**
460
+ * Assert that a resource has a specific number of ready replicas
461
+ */
462
+ async toHaveReadyReplicas(received, ...args) {
463
+ const count = args[0];
464
+ if (!isK8sResource(received)) {
465
+ return {
466
+ pass: false,
467
+ message: () => 'Expected value to be a K8sResource',
468
+ };
469
+ }
470
+ const { kubectl, kind, name } = received;
471
+ try {
472
+ const actual = await kubectl.getJsonPath(kind, name, '.status.readyReplicas') ?? 0;
473
+ const pass = actual === count;
474
+ return {
475
+ pass,
476
+ message: () => pass
477
+ ? `Expected ${kind}/${name} not to have ${count} ready replicas`
478
+ : `Expected ${kind}/${name} to have ${count} ready replicas, got ${actual}`,
479
+ actual,
480
+ expected: count,
481
+ };
482
+ }
483
+ catch (e) {
484
+ return {
485
+ pass: false,
486
+ message: () => `Failed to get ${kind}/${name}: ${e}`,
487
+ };
488
+ }
489
+ },
490
+ /**
491
+ * Assert that a resource has a specific status field value
492
+ * Supports dot notation for nested paths (e.g., 'phase', 'available.tflops')
493
+ */
494
+ async toHaveStatusField(received, ...args) {
495
+ const path = args[0];
496
+ const expectedValue = args[1];
497
+ if (!isK8sResource(received)) {
498
+ return {
499
+ pass: false,
500
+ message: () => 'Expected value to be a K8sResource',
501
+ };
502
+ }
503
+ const { kubectl, kind, name } = received;
504
+ try {
505
+ const jsonPath = `.status.${path}`;
506
+ const actual = await kubectl.getJsonPath(kind, name, jsonPath);
507
+ const pass = actual === expectedValue;
508
+ return {
509
+ pass,
510
+ message: () => pass
511
+ ? `Expected ${kind}/${name} not to have status.${path} = ${JSON.stringify(expectedValue)}`
512
+ : `Expected ${kind}/${name} to have status.${path} = ${JSON.stringify(expectedValue)}, got ${JSON.stringify(actual)}`,
513
+ actual,
514
+ expected: expectedValue,
515
+ };
516
+ }
517
+ catch (e) {
518
+ return {
519
+ pass: false,
520
+ message: () => `Failed to get ${kind}/${name}: ${e}`,
521
+ };
522
+ }
523
+ },
524
+ });
525
+ }
526
+ // Auto-register matchers when this module is imported
527
+ registerK8sMatchers();
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Kubectl Result Types
3
+ *
4
+ * Provides result classes for kubectl commands with intelligent success detection.
5
+ * Handles both normal mode (exitCode-based) and PTY/recording mode (output-based).
6
+ *
7
+ * @packageDocumentation
8
+ */
9
+ /**
10
+ * Base class for kubectl command execution results.
11
+ * Provides unified success detection logic for both PTY and non-PTY modes.
12
+ */
13
+ export declare abstract class KubectlResult {
14
+ /** Command output (stdout + stderr in PTY mode) */
15
+ readonly output: string;
16
+ /** The executed command */
17
+ readonly command: string;
18
+ /** Exit code (-1 in PTY mode where exit code is unavailable) */
19
+ readonly exitCode: number;
20
+ constructor(
21
+ /** Command output (stdout + stderr in PTY mode) */
22
+ output: string,
23
+ /** The executed command */
24
+ command: string,
25
+ /** Exit code (-1 in PTY mode where exit code is unavailable) */
26
+ exitCode: number);
27
+ /**
28
+ * Check if the command succeeded.
29
+ * - Non-PTY mode: Uses exitCode === 0
30
+ * - PTY mode (exitCode === -1): Delegates to subclass isOutputSuccessful()
31
+ */
32
+ get successful(): boolean;
33
+ /** Subclass implementation: determine success based on output content */
34
+ protected abstract isOutputSuccessful(): boolean;
35
+ /** Common error detection helper */
36
+ protected hasError(): boolean;
37
+ }
38
+ /**
39
+ * Result for kubectl apply command.
40
+ * Success indicators: "created", "configured", "unchanged"
41
+ */
42
+ export declare class ApplyResult extends KubectlResult {
43
+ protected isOutputSuccessful(): boolean;
44
+ }
45
+ /**
46
+ * Result for kubectl delete command.
47
+ * Success indicators: "deleted" or "not found" (with --ignore-not-found)
48
+ */
49
+ export declare class DeleteResult extends KubectlResult {
50
+ protected isOutputSuccessful(): boolean;
51
+ }
52
+ /**
53
+ * Result for kubectl patch command.
54
+ * Success indicators: "patched" or "(no change)"
55
+ */
56
+ export declare class PatchResult extends KubectlResult {
57
+ protected isOutputSuccessful(): boolean;
58
+ }
59
+ /**
60
+ * Result for kubectl scale command.
61
+ * Success indicators: "scaled"
62
+ */
63
+ export declare class ScaleResult extends KubectlResult {
64
+ protected isOutputSuccessful(): boolean;
65
+ }
66
+ /**
67
+ * Result for kubectl label/annotate commands.
68
+ * Success indicators: "labeled" or "annotated"
69
+ */
70
+ export declare class LabelResult extends KubectlResult {
71
+ protected isOutputSuccessful(): boolean;
72
+ }
73
+ /**
74
+ * Result for kubectl wait command.
75
+ * Success indicators: "condition met" or specific condition messages
76
+ */
77
+ export declare class WaitResult extends KubectlResult {
78
+ protected isOutputSuccessful(): boolean;
79
+ }
80
+ //# sourceMappingURL=result.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"result.d.ts","sourceRoot":"","sources":["../../../src/result.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH;;;GAGG;AACH,8BAAsB,aAAa;IAE3B,mDAAmD;aACnC,MAAM,EAAE,MAAM;IAC9B,2BAA2B;aACX,OAAO,EAAE,MAAM;IAC/B,gEAAgE;aAChD,QAAQ,EAAE,MAAM;;IALhC,mDAAmD;IACnC,MAAM,EAAE,MAAM;IAC9B,2BAA2B;IACX,OAAO,EAAE,MAAM;IAC/B,gEAAgE;IAChD,QAAQ,EAAE,MAAM;IAGpC;;;;OAIG;IACH,IAAI,UAAU,IAAI,OAAO,CAOxB;IAED,yEAAyE;IACzE,SAAS,CAAC,QAAQ,CAAC,kBAAkB,IAAI,OAAO;IAEhD,oCAAoC;IACpC,SAAS,CAAC,QAAQ,IAAI,OAAO;CAUhC;AAED;;;GAGG;AACH,qBAAa,WAAY,SAAQ,aAAa;IAC1C,SAAS,CAAC,kBAAkB,IAAI,OAAO;CAM1C;AAED;;;GAGG;AACH,qBAAa,YAAa,SAAQ,aAAa;IAC3C,SAAS,CAAC,kBAAkB,IAAI,OAAO;CAU1C;AAED;;;GAGG;AACH,qBAAa,WAAY,SAAQ,aAAa;IAC1C,SAAS,CAAC,kBAAkB,IAAI,OAAO;CAM1C;AAED;;;GAGG;AACH,qBAAa,WAAY,SAAQ,aAAa;IAC1C,SAAS,CAAC,kBAAkB,IAAI,OAAO;CAM1C;AAED;;;GAGG;AACH,qBAAa,WAAY,SAAQ,aAAa;IAC1C,SAAS,CAAC,kBAAkB,IAAI,OAAO;CAM1C;AAED;;;GAGG;AACH,qBAAa,UAAW,SAAQ,aAAa;IACzC,SAAS,CAAC,kBAAkB,IAAI,OAAO;CAS1C"}