@caleuche/cli 0.5.3 → 0.6.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # @caleuche/cli
2
2
 
3
+ ## 0.6.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 2758d63: Add test override functionality
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies [2758d63]
12
+ - @caleuche/core@0.6.0
13
+
3
14
  ## 0.5.3
4
15
 
5
16
  ### Patch Changes
package/README.md CHANGED
@@ -58,6 +58,7 @@ che batch <batch-file> [options]
58
58
  **Options:**
59
59
 
60
60
  - `-d, --output-dir <outputDir>`: Base output directory for all compiled samples (defaults to the batch file's directory)
61
+ - `-t, --test-overrides <json>`: JSON string with test overrides to generate test samples in a `test/` subfolder (e.g., `'{"key": "value"}'`)
61
62
 
62
63
  The batch command is ideal for generating multiple variations of samples, such as producing the same template with development and production configurations, or creating examples across different scenarios.
63
64
 
@@ -532,6 +533,9 @@ samples: # (required) List of templates to compile
532
533
  value: variant-name
533
534
  tags: # (optional) Override or add tags for this variant
534
535
  key: value
536
+ testOverrides: # (optional) Test overrides for this variant
537
+ input:
538
+ key: test-value
535
539
  ```
536
540
 
537
541
  **Fields:**
@@ -548,6 +552,7 @@ samples: # (required) List of templates to compile
548
552
  - An object with `type: "path"` and `value` pointing to a data file
549
553
  - An object with `type: "reference"` and `value` naming a variant
550
554
  - `tags`: Optional metadata to add/override for this specific variant
555
+ - `testOverrides`: Optional test parameter overrides. When specified, a `test/` subfolder is generated with samples using overridden input values.
551
556
 
552
557
  **Input Types:**
553
558
 
@@ -577,6 +582,55 @@ After compilation, the output directory will contain:
577
582
 
578
583
  3. **Tags file** (when tags are defined): `tags.yaml` containing the metadata
579
584
 
585
+ 4. **Test subfolder** (when test overrides are defined): `test/` directory containing samples compiled with test-specific input overrides
586
+
587
+ ### Test Overrides
588
+
589
+ Test overrides allow you to generate test variants of your samples with modified input values. This is useful for creating samples with test-specific configurations, mock endpoints, or test credentials.
590
+
591
+ **Precedence (most specific wins):**
592
+ 1. Variant-level `testOverrides` (highest priority)
593
+ 2. Sample-level `testOverrides` (in sample.yaml)
594
+ 3. Command-level `--test-overrides` (lowest priority)
595
+
596
+ Overrides are merged, so you can set base test values at the command level and override specific values at the variant level.
597
+
598
+ **Example: Command-level test overrides**
599
+
600
+ ```bash
601
+ che batch ./batch.yaml -t '{"endpoint": "https://test-api.example.com", "apiKey": "test-key"}'
602
+ ```
603
+
604
+ This generates a `test/` subfolder for each sample with `endpoint` and `apiKey` overridden.
605
+
606
+ **Example: Variant-level test overrides in batch.yaml**
607
+
608
+ ```yaml
609
+ samples:
610
+ - templatePath: ./my-sample
611
+ variants:
612
+ - output: ./output/default
613
+ input:
614
+ type: object
615
+ properties:
616
+ endpoint: https://endpoint.com
617
+ testOverrides:
618
+ input:
619
+ endpoint: https://test-endpoint.com
620
+ ```
621
+
622
+ **Output structure with test overrides:**
623
+
624
+ ```
625
+ output/
626
+ default/
627
+ sample.py
628
+ requirements.txt
629
+ test/
630
+ sample.py # Uses test endpoint and model
631
+ requirements.txt
632
+ ```
633
+
580
634
  ## License
581
635
 
582
636
  MIT
package/dist/batch.js CHANGED
@@ -66,11 +66,37 @@ function resolveVariantDefinition(variant, variantRegistry, workingDirectory) {
66
66
  return null;
67
67
  }
68
68
  }
69
+ /**
70
+ * Resolves the effective testOverrides by merging with precedence:
71
+ * variant-level > sample-level > command-level
72
+ */
73
+ function resolveTestOverrides(commandLevelOverrides, sampleLevelOverrides, variantLevelOverrides) {
74
+ if (!commandLevelOverrides && !sampleLevelOverrides && !variantLevelOverrides) {
75
+ return undefined;
76
+ }
77
+ const mergedInput = {
78
+ ...(commandLevelOverrides || {}),
79
+ ...(sampleLevelOverrides?.input || {}),
80
+ ...(variantLevelOverrides?.input || {}),
81
+ };
82
+ return { input: mergedInput };
83
+ }
69
84
  function batchCompile(batchFile, options) {
70
85
  if (!(0, utils_1.isFile)(batchFile)) {
71
86
  console.error(`Batch file "${batchFile}" does not exist or is not a file.`);
72
87
  process.exit(1);
73
88
  }
89
+ // Parse command-level test overrides if provided
90
+ let commandTestOverrides;
91
+ if (options.testOverrides) {
92
+ try {
93
+ commandTestOverrides = JSON.parse(options.testOverrides);
94
+ }
95
+ catch (e) {
96
+ console.error(`Failed to parse --test-overrides JSON: ${options.testOverrides}`);
97
+ process.exit(1);
98
+ }
99
+ }
74
100
  const workingDirectory = (0, utils_1.getAbsoluteDirectoryPath)(batchFile);
75
101
  console.log(`Working directory: ${workingDirectory}`);
76
102
  const batchDefinition = (0, utils_1.parse)(batchFile);
@@ -98,9 +124,10 @@ function batchCompile(batchFile, options) {
98
124
  process.exit(1);
99
125
  }
100
126
  sample.tags = variant.tags;
127
+ sample.testOverrides = resolveTestOverrides(commandTestOverrides, sample.testOverrides, variant.testOverrides);
101
128
  const effectiveOutputPath = path_1.default.join(options?.outputDir || workingDirectory, variant.output);
102
129
  if (!(0, common_1.compileAndWriteOutput)(sample, resolvedVariant.properties, effectiveOutputPath, {
103
- project: sample.dependencies && sample.dependencies.length > 0,
130
+ project: (sample.dependencies?.length ?? 0) > 0,
104
131
  })) {
105
132
  console.error(`Sample: ${sampleDefinition.templatePath}, Variant: ${JSON.stringify(variant)}`);
106
133
  process.exit(1);
package/dist/common.js CHANGED
@@ -48,9 +48,9 @@ function compileAndWriteOutput(sample, input, outputPath, options) {
48
48
  return false;
49
49
  }
50
50
  try {
51
- (0, utils_1.createOutputDirectory)(outputPath);
52
51
  for (const { fileName, content } of output.items) {
53
52
  const itemOutputPath = path_1.default.join(outputPath, fileName);
53
+ (0, utils_1.createOutputDirectory)(path_1.default.dirname(itemOutputPath));
54
54
  fs_1.default.writeFileSync(itemOutputPath, content);
55
55
  }
56
56
  }
package/dist/index.js CHANGED
@@ -20,5 +20,6 @@ commander_1.program
20
20
  .command("batch")
21
21
  .argument("<batch-file>", "Path to the batch file")
22
22
  .option("-d, --output-dir <outputDir>", "Output directory for compiled samples")
23
+ .option("-t, --test-overrides <json>", 'JSON string with test overrides, e.g. \'{"input": {"key": "value"}}\'')
23
24
  .action(batch_1.batchCompile);
24
25
  commander_1.program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@caleuche/cli",
3
- "version": "0.5.3",
3
+ "version": "0.6.0",
4
4
  "main": "dist/index.js",
5
5
  "bin": {
6
6
  "che": "dist/index.js"
@@ -19,7 +19,7 @@
19
19
  "license": "MIT",
20
20
  "description": "Caleuche CLI",
21
21
  "dependencies": {
22
- "@caleuche/core": "^0.5.0",
22
+ "@caleuche/core": "^0.6.0",
23
23
  "commander": "^14.0.0",
24
24
  "yaml": "^2.8.0"
25
25
  },
package/src/batch.ts CHANGED
@@ -80,14 +80,48 @@ function resolveVariantDefinition(
80
80
  }
81
81
  }
82
82
 
83
+ /**
84
+ * Resolves the effective testOverrides by merging with precedence:
85
+ * variant-level > sample-level > command-level
86
+ */
87
+ function resolveTestOverrides(
88
+ commandLevelOverrides: Record<string, any> | undefined,
89
+ sampleLevelOverrides: { input: Record<string, any> } | undefined,
90
+ variantLevelOverrides: { input: Record<string, any> } | undefined,
91
+ ): { input: Record<string, any> } | undefined {
92
+ if (!commandLevelOverrides && !sampleLevelOverrides && !variantLevelOverrides) {
93
+ return undefined;
94
+ }
95
+
96
+ const mergedInput: Record<string, any> = {
97
+ ...(commandLevelOverrides || {}),
98
+ ...(sampleLevelOverrides?.input || {}),
99
+ ...(variantLevelOverrides?.input || {}),
100
+ };
101
+
102
+ return { input: mergedInput };
103
+ }
104
+
83
105
  export function batchCompile(
84
106
  batchFile: string,
85
- options: { outputDir?: string },
107
+ options:{ outputDir?: string; testOverrides?: string },
86
108
  ) {
87
109
  if (!isFile(batchFile)) {
88
110
  console.error(`Batch file "${batchFile}" does not exist or is not a file.`);
89
111
  process.exit(1);
90
112
  }
113
+
114
+ // Parse command-level test overrides if provided
115
+ let commandTestOverrides: Record<string, any> | undefined;
116
+ if (options.testOverrides) {
117
+ try {
118
+ commandTestOverrides = JSON.parse(options.testOverrides);
119
+ } catch (e) {
120
+ console.error(`Failed to parse --test-overrides JSON: ${options.testOverrides}`);
121
+ process.exit(1);
122
+ }
123
+ }
124
+
91
125
  const workingDirectory = getAbsoluteDirectoryPath(batchFile);
92
126
  console.log(`Working directory: ${workingDirectory}`);
93
127
  const batchDefinition = parse<BatchCompileDescription>(batchFile);
@@ -130,6 +164,12 @@ export function batchCompile(
130
164
 
131
165
  sample.tags = variant.tags;
132
166
 
167
+ sample.testOverrides = resolveTestOverrides(
168
+ commandTestOverrides,
169
+ sample.testOverrides,
170
+ variant.testOverrides,
171
+ );
172
+
133
173
  const effectiveOutputPath = path.join(
134
174
  options?.outputDir || workingDirectory,
135
175
  variant.output,
@@ -141,7 +181,7 @@ export function batchCompile(
141
181
  resolvedVariant.properties,
142
182
  effectiveOutputPath,
143
183
  {
144
- project: sample.dependencies && sample.dependencies.length > 0,
184
+ project: (sample.dependencies?.length ?? 0) > 0,
145
185
  },
146
186
  )
147
187
  ) {
package/src/common.ts CHANGED
@@ -54,10 +54,9 @@ export function compileAndWriteOutput(
54
54
  }
55
55
 
56
56
  try {
57
- createOutputDirectory(outputPath);
58
-
59
57
  for (const { fileName, content } of output.items) {
60
58
  const itemOutputPath = path.join(outputPath, fileName);
59
+ createOutputDirectory(path.dirname(itemOutputPath));
61
60
  fs.writeFileSync(itemOutputPath, content);
62
61
  }
63
62
  } catch {
package/src/index.ts CHANGED
@@ -37,6 +37,10 @@ program
37
37
  "-d, --output-dir <outputDir>",
38
38
  "Output directory for compiled samples",
39
39
  )
40
+ .option(
41
+ "-t, --test-overrides <json>",
42
+ 'JSON string with test overrides, e.g. \'{"input": {"key": "value"}}\'',
43
+ )
40
44
  .action(batchCompile);
41
45
 
42
46
  program.parse();
package/src/interfaces.ts CHANGED
@@ -18,10 +18,15 @@ type SampleVariantInput =
18
18
  | SampleVariantInputReference
19
19
  | SampleVariantInputPath;
20
20
 
21
+ interface TestOverrides {
22
+ input: Record<string, any>;
23
+ }
24
+
21
25
  interface SampleVariantConfig {
22
26
  output: string;
23
27
  input: SampleVariantInput | string;
24
28
  tags?: Record<string, any>;
29
+ testOverrides?: TestOverrides;
25
30
  }
26
31
 
27
32
  interface SampleDefinition {
package/test/bach.test.ts CHANGED
@@ -402,4 +402,267 @@ describe("batchCompile", () => {
402
402
  `,
403
403
  );
404
404
  });
405
+
406
+ describe("testOverrides", () => {
407
+ it("should exit if --test-overrides JSON is invalid", () => {
408
+ const batchFilePath = getPath("batch.yaml");
409
+ const batchFileContent = multiline`
410
+ samples:
411
+ - templatePath: sample.yaml
412
+ variants:
413
+ - output: out
414
+ input:
415
+ type: object
416
+ properties:
417
+ var: value
418
+ `;
419
+ fs.writeFileSync(batchFilePath, batchFileContent);
420
+ const sampleFilePath = getPath("sample.yaml");
421
+ const sampleContent = multiline`
422
+ template: sample.js.template
423
+ type: javascript
424
+ dependencies:
425
+ input:
426
+ - name: var
427
+ type: string
428
+ required: true
429
+ `;
430
+ fs.writeFileSync(sampleFilePath, sampleContent);
431
+
432
+ expect(() => {
433
+ batchCompile(batchFilePath, { testOverrides: "invalid json" });
434
+ }).toThrow("process.exit");
435
+ expect(mockConsoleError).toHaveBeenCalledWith(
436
+ "Failed to parse --test-overrides JSON: invalid json",
437
+ );
438
+ expect(mockExit).toHaveBeenCalledWith(1);
439
+ });
440
+
441
+ it("should pass command-level testOverrides to sample", () => {
442
+ mockCompileSample.mockReturnValue({
443
+ items: [{ fileName: "sample.js", content: "code" }],
444
+ });
445
+ const batchFilePath = getPath("batch.yaml");
446
+ const batchFileContent = multiline`
447
+ samples:
448
+ - templatePath: sample.yaml
449
+ variants:
450
+ - output: out
451
+ input:
452
+ type: object
453
+ properties:
454
+ var: value
455
+ `;
456
+ fs.writeFileSync(batchFilePath, batchFileContent);
457
+ const sampleFilePath = getPath("sample.yaml");
458
+ const sampleContent = multiline`
459
+ template: sample.js.template
460
+ type: javascript
461
+ dependencies:
462
+ input:
463
+ - name: var
464
+ type: string
465
+ required: true
466
+ `;
467
+ fs.writeFileSync(sampleFilePath, sampleContent);
468
+
469
+ batchCompile(batchFilePath, {
470
+ testOverrides: '{"endpoint": "https://test.com"}',
471
+ });
472
+
473
+ expect(mockCompileSample).toHaveBeenCalledWith(
474
+ expect.objectContaining({
475
+ testOverrides: { input: { endpoint: "https://test.com" } },
476
+ }),
477
+ expect.any(Object),
478
+ expect.any(Object),
479
+ );
480
+ });
481
+
482
+ it("should pass variant-level testOverrides to sample", () => {
483
+ mockCompileSample.mockReturnValue({
484
+ items: [{ fileName: "sample.js", content: "code" }],
485
+ });
486
+ const batchFilePath = getPath("batch.yaml");
487
+ const batchFileContent = multiline`
488
+ samples:
489
+ - templatePath: sample.yaml
490
+ variants:
491
+ - output: out
492
+ input:
493
+ type: object
494
+ properties:
495
+ var: value
496
+ testOverrides:
497
+ input:
498
+ endpoint: https://variant-test.com
499
+ `;
500
+ fs.writeFileSync(batchFilePath, batchFileContent);
501
+ const sampleFilePath = getPath("sample.yaml");
502
+ const sampleContent = multiline`
503
+ template: sample.js.template
504
+ type: javascript
505
+ dependencies:
506
+ input:
507
+ - name: var
508
+ type: string
509
+ required: true
510
+ `;
511
+ fs.writeFileSync(sampleFilePath, sampleContent);
512
+
513
+ batchCompile(batchFilePath, {});
514
+
515
+ expect(mockCompileSample).toHaveBeenCalledWith(
516
+ expect.objectContaining({
517
+ testOverrides: { input: { endpoint: "https://variant-test.com" } },
518
+ }),
519
+ expect.any(Object),
520
+ expect.any(Object),
521
+ );
522
+ });
523
+
524
+ it("should merge testOverrides with variant-level taking precedence over command-level", () => {
525
+ mockCompileSample.mockReturnValue({
526
+ items: [{ fileName: "sample.js", content: "code" }],
527
+ });
528
+ const batchFilePath = getPath("batch.yaml");
529
+ const batchFileContent = multiline`
530
+ samples:
531
+ - templatePath: sample.yaml
532
+ variants:
533
+ - output: out
534
+ input:
535
+ type: object
536
+ properties:
537
+ var: value
538
+ testOverrides:
539
+ input:
540
+ endpoint: https://variant-wins.com
541
+ variantOnly: true
542
+ `;
543
+ fs.writeFileSync(batchFilePath, batchFileContent);
544
+ const sampleFilePath = getPath("sample.yaml");
545
+ const sampleContent = multiline`
546
+ template: sample.js.template
547
+ type: javascript
548
+ dependencies:
549
+ input:
550
+ - name: var
551
+ type: string
552
+ required: true
553
+ `;
554
+ fs.writeFileSync(sampleFilePath, sampleContent);
555
+
556
+ batchCompile(batchFilePath, {
557
+ testOverrides: '{"endpoint": "https://command-level.com", "commandOnly": "value"}',
558
+ });
559
+
560
+ expect(mockCompileSample).toHaveBeenCalledWith(
561
+ expect.objectContaining({
562
+ testOverrides: {
563
+ input: {
564
+ endpoint: "https://variant-wins.com",
565
+ commandOnly: "value",
566
+ variantOnly: true,
567
+ },
568
+ },
569
+ }),
570
+ expect.any(Object),
571
+ expect.any(Object),
572
+ );
573
+ });
574
+
575
+ it("should merge testOverrides with sample-level in precedence chain", () => {
576
+ mockCompileSample.mockReturnValue({
577
+ items: [{ fileName: "sample.js", content: "code" }],
578
+ });
579
+ const batchFilePath = getPath("batch.yaml");
580
+ const batchFileContent = multiline`
581
+ samples:
582
+ - templatePath: sample.yaml
583
+ variants:
584
+ - output: out
585
+ input:
586
+ type: object
587
+ properties:
588
+ var: value
589
+ testOverrides:
590
+ input:
591
+ variantKey: variantValue
592
+ `;
593
+ fs.writeFileSync(batchFilePath, batchFileContent);
594
+ const sampleFilePath = getPath("sample.yaml");
595
+ const sampleContent = multiline`
596
+ template: sample.js.template
597
+ type: javascript
598
+ dependencies:
599
+ input:
600
+ - name: var
601
+ type: string
602
+ required: true
603
+ testOverrides:
604
+ input:
605
+ sampleKey: sampleValue
606
+ variantKey: sampleShouldLose
607
+ `;
608
+ fs.writeFileSync(sampleFilePath, sampleContent);
609
+
610
+ batchCompile(batchFilePath, {
611
+ testOverrides: '{"commandKey": "commandValue", "sampleKey": "commandShouldLose"}',
612
+ });
613
+
614
+ expect(mockCompileSample).toHaveBeenCalledWith(
615
+ expect.objectContaining({
616
+ testOverrides: {
617
+ input: {
618
+ commandKey: "commandValue",
619
+ sampleKey: "sampleValue",
620
+ variantKey: "variantValue",
621
+ },
622
+ },
623
+ }),
624
+ expect.any(Object),
625
+ expect.any(Object),
626
+ );
627
+ });
628
+
629
+ it("should not set testOverrides when none are provided", () => {
630
+ mockCompileSample.mockReturnValue({
631
+ items: [{ fileName: "sample.js", content: "code" }],
632
+ });
633
+ const batchFilePath = getPath("batch.yaml");
634
+ const batchFileContent = multiline`
635
+ samples:
636
+ - templatePath: sample.yaml
637
+ variants:
638
+ - output: out
639
+ input:
640
+ type: object
641
+ properties:
642
+ var: value
643
+ `;
644
+ fs.writeFileSync(batchFilePath, batchFileContent);
645
+ const sampleFilePath = getPath("sample.yaml");
646
+ const sampleContent = multiline`
647
+ template: sample.js.template
648
+ type: javascript
649
+ dependencies:
650
+ input:
651
+ - name: var
652
+ type: string
653
+ required: true
654
+ `;
655
+ fs.writeFileSync(sampleFilePath, sampleContent);
656
+
657
+ batchCompile(batchFilePath, {});
658
+
659
+ expect(mockCompileSample).toHaveBeenCalledWith(
660
+ expect.objectContaining({
661
+ testOverrides: undefined,
662
+ }),
663
+ expect.any(Object),
664
+ expect.any(Object),
665
+ );
666
+ });
667
+ });
405
668
  });