@aws/ml-container-creator 0.9.1 → 0.10.3

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 (90) hide show
  1. package/LICENSE-THIRD-PARTY +9304 -0
  2. package/bin/cli.js +2 -0
  3. package/config/bootstrap-e2e-stack.json +341 -0
  4. package/config/bootstrap-stack.json +40 -3
  5. package/config/parameter-schema-v2.json +2049 -0
  6. package/config/tune-catalog.json +1781 -0
  7. package/infra/ci-harness/buildspec.yml +1 -0
  8. package/infra/ci-harness/lambda/path-prover/brain.ts +306 -0
  9. package/infra/ci-harness/lambda/path-prover/write-results.ts +152 -0
  10. package/infra/ci-harness/lib/ci-harness-stack.ts +837 -7
  11. package/infra/ci-harness/state-machines/path-prover.asl.json +496 -0
  12. package/package.json +53 -68
  13. package/servers/base-image-picker/index.js +121 -121
  14. package/servers/e2e-status/index.js +297 -0
  15. package/servers/e2e-status/manifest.json +14 -0
  16. package/servers/e2e-status/package.json +15 -0
  17. package/servers/endpoint-picker/LICENSE +202 -0
  18. package/servers/endpoint-picker/index.js +536 -0
  19. package/servers/endpoint-picker/manifest.json +14 -0
  20. package/servers/endpoint-picker/package.json +18 -0
  21. package/servers/hyperpod-cluster-picker/index.js +125 -125
  22. package/servers/instance-sizer/index.js +138 -138
  23. package/servers/instance-sizer/lib/instance-ranker.js +76 -76
  24. package/servers/instance-sizer/lib/model-resolver.js +61 -61
  25. package/servers/instance-sizer/lib/quota-resolver.js +113 -113
  26. package/servers/instance-sizer/lib/vram-estimator.js +31 -31
  27. package/servers/lib/bedrock-client.js +38 -38
  28. package/servers/lib/catalogs/jumpstart-public.json +101 -16
  29. package/servers/lib/catalogs/model-servers.json +201 -3
  30. package/servers/lib/catalogs/models.json +182 -26
  31. package/servers/lib/custom-validators.js +13 -13
  32. package/servers/lib/dynamic-resolver.js +4 -4
  33. package/servers/marketplace-picker/index.js +342 -0
  34. package/servers/marketplace-picker/manifest.json +14 -0
  35. package/servers/marketplace-picker/package.json +18 -0
  36. package/servers/model-picker/index.js +382 -382
  37. package/servers/region-picker/index.js +56 -56
  38. package/servers/workload-picker/LICENSE +202 -0
  39. package/servers/workload-picker/catalogs/workload-profiles.json +67 -0
  40. package/servers/workload-picker/index.js +171 -0
  41. package/servers/workload-picker/manifest.json +16 -0
  42. package/servers/workload-picker/package.json +16 -0
  43. package/src/app.js +4 -390
  44. package/src/lib/bootstrap-command-handler.js +710 -1148
  45. package/src/lib/bootstrap-config.js +36 -0
  46. package/src/lib/bootstrap-profile-manager.js +641 -0
  47. package/src/lib/bootstrap-provisioners.js +421 -0
  48. package/src/lib/ci-register-helpers.js +74 -0
  49. package/src/lib/config-loader.js +408 -0
  50. package/src/lib/config-manager.js +66 -1685
  51. package/src/lib/config-mcp-client.js +118 -0
  52. package/src/lib/config-validator.js +634 -0
  53. package/src/lib/cuda-resolver.js +149 -0
  54. package/src/lib/e2e-catalog-validator.js +251 -3
  55. package/src/lib/e2e-ci-recorder.js +103 -0
  56. package/src/lib/generated/cli-options.js +315 -311
  57. package/src/lib/generated/parameter-matrix.js +671 -0
  58. package/src/lib/generated/validation-rules.js +71 -71
  59. package/src/lib/marketplace-flow.js +276 -0
  60. package/src/lib/mcp-query-runner.js +768 -0
  61. package/src/lib/parameter-schema-validator.js +62 -18
  62. package/src/lib/path-prover-brain.js +607 -0
  63. package/src/lib/prompt-runner.js +41 -1504
  64. package/src/lib/prompts/feature-prompts.js +172 -0
  65. package/src/lib/prompts/index.js +48 -0
  66. package/src/lib/prompts/infrastructure-prompts.js +690 -0
  67. package/src/lib/prompts/model-prompts.js +552 -0
  68. package/src/lib/prompts/project-prompts.js +82 -0
  69. package/src/lib/prompts.js +2 -1446
  70. package/src/lib/registry-command-handler.js +135 -3
  71. package/src/lib/secrets-prompt-runner.js +251 -0
  72. package/src/lib/template-variable-resolver.js +422 -0
  73. package/src/lib/tune-catalog-validator.js +37 -4
  74. package/templates/Dockerfile +9 -0
  75. package/templates/code/adapter_sidecar.py +444 -0
  76. package/templates/code/serve +6 -0
  77. package/templates/code/serve.d/vllm.ejs +1 -1
  78. package/templates/do/.benchmark_writer.py +1476 -0
  79. package/templates/do/.tune_helper.py +982 -57
  80. package/templates/do/__pycache__/.benchmark_writer.cpython-312.pyc +0 -0
  81. package/templates/do/adapter +149 -0
  82. package/templates/do/benchmark +639 -85
  83. package/templates/do/config +108 -5
  84. package/templates/do/deploy.d/managed-inference.ejs +192 -11
  85. package/templates/do/optimize +106 -37
  86. package/templates/do/register +89 -0
  87. package/templates/do/test +13 -0
  88. package/templates/do/tune +378 -59
  89. package/templates/do/validate +44 -4
  90. package/config/parameter-schema.json +0 -88
@@ -16,17 +16,16 @@
16
16
  * 9. Prompting (fallback)
17
17
  */
18
18
 
19
- import fs from 'fs';
20
19
  import path from 'path';
21
20
  import { readFileSync } from 'node:fs';
22
21
  import { resolve, dirname } from 'node:path';
23
22
  import { fileURLToPath } from 'node:url';
24
- import { McpClient } from './mcp-client.js';
25
23
  import DeploymentConfigResolver from './deployment-config-resolver.js';
26
- import BootstrapConfig from './bootstrap-config.js';
27
- import { parseKeyValue } from './key-value-parser.js';
28
24
  import ParameterSchemaValidator from './parameter-schema-validator.js';
29
- import { validationRules } from './generated/validation-rules.js';
25
+ import ConfigLoader from './config-loader.js';
26
+ import ConfigMcpClient from './config-mcp-client.js';
27
+ import ConfigValidator from './config-validator.js';
28
+ import { parameterMatrix } from './generated/parameter-matrix.js';
30
29
 
31
30
  const __configMgrFilename = fileURLToPath(import.meta.url);
32
31
  const __configMgrDir = dirname(__configMgrFilename);
@@ -86,6 +85,17 @@ export default class ConfigManager {
86
85
  this.mcpSources = {};
87
86
  this.mcpChoices = {};
88
87
  this._sourceManifest = [];
88
+ this.GENERATOR_ROOT = GENERATOR_ROOT;
89
+
90
+ // Delegate modules
91
+ this._loader = new ConfigLoader(this);
92
+ this._mcpClient = new ConfigMcpClient(this);
93
+ this._validator = new ConfigValidator(this);
94
+ }
95
+
96
+ /** Delegate to config-loader for backward compatibility with tests */
97
+ _applyJsonConfig(config) {
98
+ return this._loader._applyJsonConfig(config);
89
99
  }
90
100
 
91
101
  /**
@@ -192,7 +202,16 @@ export default class ConfigManager {
192
202
  // For http architecture, engine comes from the --engine CLI option or prompt
193
203
  if (parts.architecture === 'http') {
194
204
  if (!finalConfig.engine) {
195
- finalConfig.engine = parts.engine;
205
+ // Infer engine from model format if possible
206
+ const formatToEngine = {
207
+ 'pkl': 'sklearn',
208
+ 'joblib': 'sklearn',
209
+ 'json': 'xgboost',
210
+ 'keras': 'tensorflow',
211
+ 'h5': 'tensorflow',
212
+ 'savedmodel': 'tensorflow'
213
+ };
214
+ finalConfig.engine = (finalConfig.modelFormat && formatToEngine[finalConfig.modelFormat]) || 'sklearn';
196
215
  }
197
216
  } else {
198
217
  finalConfig.engine = parts.engine;
@@ -278,7 +297,14 @@ export default class ConfigManager {
278
297
 
279
298
  if (projectNameFromArgument &&
280
299
  !explicitDestination &&
281
- finalConfig.destinationDir === '.') {
300
+ (!finalConfig.destinationDir || finalConfig.destinationDir === '.')) {
301
+ finalConfig.destinationDir = `./${finalConfig.projectName}`;
302
+ }
303
+
304
+ // Ensure destinationDir is never null — derive from projectName if not set.
305
+ // This covers interactive mode where destinationDir is non-promptable and no
306
+ // CLI positional argument was provided.
307
+ if (!finalConfig.destinationDir) {
282
308
  finalConfig.destinationDir = `./${finalConfig.projectName}`;
283
309
  }
284
310
 
@@ -438,671 +464,11 @@ export default class ConfigManager {
438
464
  }
439
465
 
440
466
  /**
441
- * Gets the parameter matrix configuration
467
+ * Gets the parameter matrix configuration (generated from schema)
442
468
  * @private
443
469
  */
444
470
  _getParameterMatrix() {
445
- return {
446
- deploymentConfig: {
447
- cliOption: 'deployment-config',
448
- envVar: null,
449
- configFile: true,
450
- packageJson: false,
451
- mcp: false,
452
- promptable: true,
453
- required: true,
454
- default: null,
455
- valueSpace: 'bounded'
456
- },
457
- architecture: {
458
- cliOption: null,
459
- envVar: null,
460
- configFile: false,
461
- packageJson: false,
462
- mcp: false,
463
- promptable: false,
464
- required: false,
465
- default: null,
466
- valueSpace: 'bounded'
467
- },
468
- backend: {
469
- cliOption: null,
470
- envVar: null,
471
- configFile: false,
472
- packageJson: false,
473
- mcp: false,
474
- promptable: false,
475
- required: false,
476
- default: null,
477
- valueSpace: 'bounded'
478
- },
479
- engine: {
480
- cliOption: 'engine',
481
- envVar: null,
482
- configFile: true,
483
- packageJson: false,
484
- mcp: false,
485
- promptable: true,
486
- required: false,
487
- default: null,
488
- valueSpace: 'bounded'
489
- },
490
- modelFormat: {
491
- cliOption: 'model-format',
492
- envVar: null,
493
- configFile: true,
494
- packageJson: false,
495
- mcp: false,
496
- promptable: true,
497
- required: true,
498
- default: null,
499
- valueSpace: 'bounded'
500
- },
501
- modelName: {
502
- cliOption: 'model-name',
503
- envVar: null,
504
- configFile: true,
505
- packageJson: false,
506
- mcp: false,
507
- promptable: true,
508
- required: false,
509
- default: 'openai/gpt-oss-20b',
510
- valueSpace: 'bounded'
511
- },
512
- includeSampleModel: {
513
- cliOption: 'include-sample',
514
- envVar: null,
515
- configFile: true,
516
- packageJson: false,
517
- mcp: false,
518
- promptable: true,
519
- required: true,
520
- default: false,
521
- valueSpace: 'bounded'
522
- },
523
- includeTesting: {
524
- cliOption: 'include-testing',
525
- envVar: null,
526
- configFile: true,
527
- packageJson: false,
528
- mcp: false,
529
- promptable: false,
530
- required: false,
531
- default: true,
532
- valueSpace: 'bounded'
533
- },
534
- instanceType: {
535
- cliOption: 'instance-type',
536
- envVar: 'ML_INSTANCE_TYPE',
537
- configFile: true,
538
- packageJson: false,
539
- mcp: true,
540
- promptable: true,
541
- required: true,
542
- default: null,
543
- valueSpace: 'unbounded'
544
- },
545
- awsRegion: {
546
- cliOption: 'region',
547
- envVar: 'AWS_REGION',
548
- ambientEnvVar: true, // AWS_REGION is commonly set in shells; treat as default, not explicit override
549
- configFile: true,
550
- packageJson: true,
551
- mcp: true,
552
- promptable: true,
553
- required: false,
554
- default: 'us-east-1',
555
- valueSpace: 'unbounded'
556
- },
557
- awsRoleArn: {
558
- cliOption: 'role-arn',
559
- envVar: 'AWS_ROLE',
560
- configFile: true,
561
- packageJson: true,
562
- mcp: true,
563
- promptable: true,
564
- required: false,
565
- default: null,
566
- valueSpace: 'unbounded'
567
- },
568
- configFile: {
569
- cliOption: 'config',
570
- envVar: 'ML_CONTAINER_CREATOR_CONFIG',
571
- configFile: false,
572
- packageJson: true,
573
- mcp: false,
574
- promptable: true,
575
- required: false,
576
- default: null,
577
- valueSpace: 'bounded'
578
- },
579
- skipPrompts: {
580
- cliOption: 'skip-prompts',
581
- envVar: null,
582
- configFile: false,
583
- packageJson: false,
584
- mcp: false,
585
- promptable: false,
586
- required: false,
587
- default: false,
588
- valueSpace: 'bounded'
589
- },
590
- projectName: {
591
- cliOption: 'project-name',
592
- envVar: null,
593
- configFile: true,
594
- packageJson: true,
595
- mcp: false,
596
- promptable: false,
597
- required: true,
598
- default: null,
599
- valueSpace: 'bounded'
600
- },
601
- destinationDir: {
602
- cliOption: 'project-dir',
603
- envVar: null,
604
- configFile: true,
605
- packageJson: true,
606
- mcp: false,
607
- promptable: false,
608
- required: true,
609
- default: '.',
610
- valueSpace: 'bounded'
611
- },
612
- buildTarget: {
613
- cliOption: 'build-target',
614
- envVar: 'ML_BUILD_TARGET',
615
- configFile: true,
616
- packageJson: false,
617
- mcp: false,
618
- promptable: true,
619
- required: true,
620
- default: 'codebuild',
621
- valueSpace: 'bounded'
622
- },
623
- codebuildComputeType: {
624
- cliOption: 'codebuild-compute-type',
625
- envVar: 'ML_CODEBUILD_COMPUTE_TYPE',
626
- configFile: true,
627
- packageJson: false,
628
- mcp: false,
629
- promptable: true,
630
- required: false,
631
- default: 'BUILD_GENERAL1_MEDIUM',
632
- valueSpace: 'bounded'
633
- },
634
- codebuildProjectName: {
635
- cliOption: null,
636
- envVar: null,
637
- configFile: true,
638
- packageJson: false,
639
- mcp: false,
640
- promptable: false,
641
- required: false,
642
- default: null,
643
- valueSpace: 'bounded'
644
- },
645
- hfToken: {
646
- cliOption: 'hf-token',
647
- envVar: null,
648
- configFile: true,
649
- packageJson: false,
650
- mcp: false,
651
- promptable: true,
652
- required: false,
653
- default: null,
654
- valueSpace: 'bounded'
655
- },
656
- hfTokenArn: {
657
- cliOption: 'hf-token-arn',
658
- envVar: null,
659
- configFile: true,
660
- packageJson: false,
661
- mcp: false,
662
- promptable: false,
663
- required: false,
664
- default: null,
665
- valueSpace: 'bounded'
666
- },
667
- ngcTokenArn: {
668
- cliOption: 'ngc-token-arn',
669
- envVar: null,
670
- configFile: true,
671
- packageJson: false,
672
- mcp: false,
673
- promptable: false,
674
- required: false,
675
- default: null,
676
- valueSpace: 'bounded'
677
- },
678
- deploymentTarget: {
679
- cliOption: 'deployment-target',
680
- envVar: 'ML_DEPLOYMENT_TARGET',
681
- configFile: true,
682
- packageJson: false,
683
- mcp: false,
684
- promptable: true,
685
- required: true,
686
- default: 'realtime-inference',
687
- valueSpace: 'bounded'
688
- },
689
- hyperPodCluster: {
690
- cliOption: 'hyperpod-cluster',
691
- envVar: null,
692
- configFile: true,
693
- packageJson: false,
694
- mcp: true,
695
- promptable: true,
696
- required: false,
697
- default: null,
698
- valueSpace: 'unbounded'
699
- },
700
- hyperPodNamespace: {
701
- cliOption: 'hyperpod-namespace',
702
- envVar: null,
703
- configFile: true,
704
- packageJson: false,
705
- mcp: false,
706
- promptable: true,
707
- required: false,
708
- default: 'default',
709
- valueSpace: 'bounded'
710
- },
711
- hyperPodReplicas: {
712
- cliOption: 'hyperpod-replicas',
713
- envVar: null,
714
- configFile: true,
715
- packageJson: false,
716
- mcp: false,
717
- promptable: true,
718
- required: false,
719
- default: 1,
720
- valueSpace: 'bounded'
721
- },
722
- fsxVolumeHandle: {
723
- cliOption: 'fsx-volume-handle',
724
- envVar: null,
725
- configFile: true,
726
- packageJson: false,
727
- mcp: false,
728
- promptable: true,
729
- required: false,
730
- default: null,
731
- valueSpace: 'bounded'
732
- },
733
- baseImage: {
734
- cliOption: 'base-image',
735
- envVar: null,
736
- configFile: true,
737
- packageJson: false,
738
- mcp: true,
739
- promptable: true,
740
- required: false,
741
- default: null,
742
- valueSpace: 'unbounded'
743
- },
744
- asyncS3OutputPath: {
745
- cliOption: 'async-s3-output-path',
746
- envVar: 'ML_ASYNC_S3_OUTPUT_PATH',
747
- configFile: true,
748
- packageJson: false,
749
- mcp: true,
750
- promptable: true,
751
- required: false,
752
- default: null,
753
- valueSpace: 'unbounded'
754
- },
755
- asyncSnsSuccessTopic: {
756
- cliOption: 'async-sns-success-topic',
757
- envVar: null,
758
- configFile: true,
759
- packageJson: false,
760
- mcp: true,
761
- promptable: true,
762
- required: false,
763
- default: null,
764
- valueSpace: 'unbounded'
765
- },
766
- asyncSnsErrorTopic: {
767
- cliOption: 'async-sns-error-topic',
768
- envVar: null,
769
- configFile: true,
770
- packageJson: false,
771
- mcp: true,
772
- promptable: true,
773
- required: false,
774
- default: null,
775
- valueSpace: 'unbounded'
776
- },
777
- asyncMaxConcurrentInvocations: {
778
- cliOption: 'async-max-concurrent',
779
- envVar: null,
780
- configFile: true,
781
- packageJson: false,
782
- mcp: false,
783
- promptable: true,
784
- required: false,
785
- default: 1,
786
- valueSpace: 'bounded'
787
- },
788
- batchInputPath: {
789
- cliOption: 'batch-input-path',
790
- envVar: 'ML_BATCH_INPUT_PATH',
791
- configFile: true,
792
- packageJson: false,
793
- mcp: true,
794
- promptable: true,
795
- required: false,
796
- default: null,
797
- valueSpace: 'unbounded'
798
- },
799
- batchOutputPath: {
800
- cliOption: 'batch-output-path',
801
- envVar: 'ML_BATCH_OUTPUT_PATH',
802
- configFile: true,
803
- packageJson: false,
804
- mcp: true,
805
- promptable: true,
806
- required: false,
807
- default: null,
808
- valueSpace: 'unbounded'
809
- },
810
- batchInstanceCount: {
811
- cliOption: 'batch-instance-count',
812
- envVar: null,
813
- configFile: true,
814
- packageJson: false,
815
- mcp: false,
816
- promptable: true,
817
- required: false,
818
- default: 1,
819
- valueSpace: 'bounded'
820
- },
821
- batchSplitType: {
822
- cliOption: 'batch-split-type',
823
- envVar: null,
824
- configFile: true,
825
- packageJson: false,
826
- mcp: false,
827
- promptable: true,
828
- required: false,
829
- default: 'Line',
830
- valueSpace: 'bounded'
831
- },
832
- batchStrategy: {
833
- cliOption: 'batch-strategy',
834
- envVar: null,
835
- configFile: true,
836
- packageJson: false,
837
- mcp: false,
838
- promptable: true,
839
- required: false,
840
- default: 'MultiRecord',
841
- valueSpace: 'bounded'
842
- },
843
- batchJoinSource: {
844
- cliOption: 'batch-join-source',
845
- envVar: null,
846
- configFile: true,
847
- packageJson: false,
848
- mcp: false,
849
- promptable: true,
850
- required: false,
851
- default: 'None',
852
- valueSpace: 'bounded'
853
- },
854
- batchMaxConcurrentTransforms: {
855
- cliOption: 'batch-max-concurrent',
856
- envVar: null,
857
- configFile: true,
858
- packageJson: false,
859
- mcp: false,
860
- promptable: true,
861
- required: false,
862
- default: 1,
863
- valueSpace: 'bounded'
864
- },
865
- batchMaxPayloadInMB: {
866
- cliOption: 'batch-max-payload',
867
- envVar: null,
868
- configFile: true,
869
- packageJson: false,
870
- mcp: false,
871
- promptable: true,
872
- required: false,
873
- default: 6,
874
- valueSpace: 'bounded'
875
- },
876
- endpointInitialInstanceCount: {
877
- cliOption: 'endpoint-initial-instance-count',
878
- envVar: null,
879
- configFile: true,
880
- packageJson: false,
881
- mcp: false,
882
- promptable: false,
883
- required: false,
884
- default: 1,
885
- valueSpace: 'bounded',
886
- schemaValidated: true
887
- },
888
- endpointDataCapturePercent: {
889
- cliOption: 'endpoint-data-capture-percent',
890
- envVar: null,
891
- configFile: true,
892
- packageJson: false,
893
- mcp: false,
894
- promptable: false,
895
- required: false,
896
- default: 0,
897
- valueSpace: 'bounded',
898
- schemaValidated: true
899
- },
900
- endpointVariantName: {
901
- cliOption: 'endpoint-variant-name',
902
- envVar: null,
903
- configFile: true,
904
- packageJson: false,
905
- mcp: false,
906
- promptable: false,
907
- required: false,
908
- default: 'AllTraffic',
909
- valueSpace: 'bounded',
910
- schemaValidated: true
911
- },
912
- endpointVolumeSize: {
913
- cliOption: 'endpoint-volume-size',
914
- envVar: null,
915
- configFile: true,
916
- packageJson: false,
917
- mcp: false,
918
- promptable: false,
919
- required: false,
920
- default: null,
921
- valueSpace: 'bounded',
922
- schemaValidated: true
923
- },
924
- icCpuCount: {
925
- cliOption: 'ic-cpu-count',
926
- envVar: null,
927
- configFile: true,
928
- packageJson: false,
929
- mcp: false,
930
- promptable: false,
931
- required: false,
932
- default: null,
933
- valueSpace: 'bounded',
934
- schemaValidated: true
935
- },
936
- icMemorySize: {
937
- cliOption: 'ic-memory-size',
938
- envVar: null,
939
- configFile: true,
940
- packageJson: false,
941
- mcp: false,
942
- promptable: false,
943
- required: false,
944
- default: null,
945
- valueSpace: 'bounded',
946
- schemaValidated: true
947
- },
948
- icGpuCount: {
949
- cliOption: 'ic-gpu-count',
950
- envVar: null,
951
- configFile: true,
952
- packageJson: false,
953
- mcp: false,
954
- promptable: false,
955
- required: false,
956
- default: null,
957
- valueSpace: 'bounded',
958
- schemaValidated: true
959
- },
960
- icCopyCount: {
961
- cliOption: 'ic-copy-count',
962
- envVar: null,
963
- configFile: true,
964
- packageJson: false,
965
- mcp: false,
966
- promptable: false,
967
- required: false,
968
- default: 1,
969
- valueSpace: 'bounded',
970
- schemaValidated: true
971
- },
972
- icModelWeight: {
973
- cliOption: 'ic-model-weight',
974
- envVar: null,
975
- configFile: true,
976
- packageJson: false,
977
- mcp: false,
978
- promptable: false,
979
- required: false,
980
- default: 1.0,
981
- valueSpace: 'bounded',
982
- schemaValidated: true
983
- },
984
- includeBenchmark: {
985
- cliOption: 'include-benchmark',
986
- envVar: 'ML_INCLUDE_BENCHMARK',
987
- configFile: true,
988
- packageJson: false,
989
- mcp: false,
990
- promptable: true,
991
- required: false,
992
- default: false,
993
- valueSpace: 'bounded'
994
- },
995
- benchmarkConcurrency: {
996
- cliOption: 'benchmark-concurrency',
997
- envVar: null,
998
- configFile: true,
999
- packageJson: false,
1000
- mcp: false,
1001
- promptable: true,
1002
- required: false,
1003
- default: 10,
1004
- valueSpace: 'bounded'
1005
- },
1006
- benchmarkInputTokensMean: {
1007
- cliOption: 'benchmark-input-tokens',
1008
- envVar: null,
1009
- configFile: true,
1010
- packageJson: false,
1011
- mcp: false,
1012
- promptable: true,
1013
- required: false,
1014
- default: 550,
1015
- valueSpace: 'bounded'
1016
- },
1017
- benchmarkOutputTokensMean: {
1018
- cliOption: 'benchmark-output-tokens',
1019
- envVar: null,
1020
- configFile: true,
1021
- packageJson: false,
1022
- mcp: false,
1023
- promptable: true,
1024
- required: false,
1025
- default: 150,
1026
- valueSpace: 'bounded'
1027
- },
1028
- benchmarkStreaming: {
1029
- cliOption: 'benchmark-streaming',
1030
- envVar: null,
1031
- configFile: true,
1032
- packageJson: false,
1033
- mcp: false,
1034
- promptable: true,
1035
- required: false,
1036
- default: true,
1037
- valueSpace: 'bounded'
1038
- },
1039
- benchmarkRequestCount: {
1040
- cliOption: 'benchmark-request-count',
1041
- envVar: null,
1042
- configFile: true,
1043
- packageJson: false,
1044
- mcp: false,
1045
- promptable: true,
1046
- required: false,
1047
- default: null,
1048
- valueSpace: 'bounded'
1049
- },
1050
- benchmarkS3OutputPath: {
1051
- cliOption: 'benchmark-s3-output-path',
1052
- envVar: 'ML_BENCHMARK_S3_OUTPUT_PATH',
1053
- configFile: true,
1054
- packageJson: false,
1055
- mcp: false,
1056
- promptable: true,
1057
- required: false,
1058
- default: null,
1059
- valueSpace: 'bounded'
1060
- },
1061
- enableLora: {
1062
- cliOption: 'enable-lora',
1063
- envVar: null,
1064
- configFile: true,
1065
- packageJson: false,
1066
- mcp: false,
1067
- promptable: true,
1068
- required: false,
1069
- default: false,
1070
- valueSpace: 'bounded'
1071
- },
1072
- maxLoras: {
1073
- cliOption: 'max-loras',
1074
- envVar: null,
1075
- configFile: true,
1076
- packageJson: false,
1077
- mcp: false,
1078
- promptable: true,
1079
- required: false,
1080
- default: 30,
1081
- valueSpace: 'bounded'
1082
- },
1083
- maxLoraRank: {
1084
- cliOption: 'max-lora-rank',
1085
- envVar: null,
1086
- configFile: true,
1087
- packageJson: false,
1088
- mcp: false,
1089
- promptable: true,
1090
- required: false,
1091
- default: 64,
1092
- valueSpace: 'bounded'
1093
- },
1094
- modelPackageArn: {
1095
- cliOption: 'model-package-arn',
1096
- envVar: null,
1097
- configFile: true,
1098
- packageJson: false,
1099
- mcp: true,
1100
- promptable: true,
1101
- required: false,
1102
- default: null,
1103
- valueSpace: 'unbounded'
1104
- }
1105
- };
471
+ return parameterMatrix;
1106
472
  }
1107
473
 
1108
474
  /**
@@ -1199,30 +565,7 @@ export default class ConfigManager {
1199
565
  * @private
1200
566
  */
1201
567
  async _loadBootstrapConfig() {
1202
- try {
1203
- const bootstrapConfig = new BootstrapConfig();
1204
- const activeProfile = bootstrapConfig.getActiveProfile();
1205
- if (!activeProfile) {
1206
- return;
1207
- }
1208
-
1209
- const profileConfig = activeProfile.config;
1210
- const mapped = {};
1211
-
1212
- if (profileConfig.roleArn) {
1213
- mapped.awsRoleArn = profileConfig.roleArn;
1214
- }
1215
- if (profileConfig.awsRegion) {
1216
- mapped.awsRegion = profileConfig.awsRegion;
1217
- }
1218
- if (profileConfig.awsProfile) {
1219
- mapped.awsProfile = profileConfig.awsProfile;
1220
- }
1221
-
1222
- this._mergeConfig(mapped);
1223
- } catch (error) {
1224
- // Ignore errors — config file may not exist or may be malformed
1225
- }
568
+ return this._loader._loadBootstrapConfig();
1226
569
  }
1227
570
 
1228
571
  /**
@@ -1230,25 +573,7 @@ export default class ConfigManager {
1230
573
  * @private
1231
574
  */
1232
575
  async _loadPackageJsonConfig() {
1233
- try {
1234
- const packageJsonPath = path.resolve(process.cwd(), 'package.json');
1235
- if (fs.existsSync(packageJsonPath)) {
1236
- const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
1237
- const generatorConfig = packageJson['ml-container-creator'];
1238
- if (generatorConfig) {
1239
- // Filter config to only include parameters supported in package.json
1240
- const filteredConfig = {};
1241
- Object.entries(generatorConfig).forEach(([key, value]) => {
1242
- if (this._isSourceSupported(key, 'packageJson')) {
1243
- filteredConfig[key] = this._parseValue(key, value);
1244
- }
1245
- });
1246
- this._mergeConfig(filteredConfig);
1247
- }
1248
- }
1249
- } catch (error) {
1250
- // Ignore errors - this is optional
1251
- }
576
+ return this._loader._loadPackageJsonConfig();
1252
577
  }
1253
578
 
1254
579
  /**
@@ -1256,215 +581,15 @@ export default class ConfigManager {
1256
581
  * @private
1257
582
  */
1258
583
  async _loadCustomConfigFile() {
1259
- try {
1260
- const configPath = path.join(GENERATOR_ROOT, 'config', 'mcp.json');
1261
- if (fs.existsSync(configPath)) {
1262
- const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
1263
- this._mergeConfig(config);
1264
- }
1265
- } catch (error) {
1266
- // Ignore errors - this is optional
1267
- }
584
+ return this._loader._loadCustomConfigFile();
1268
585
  }
1269
586
 
1270
587
  /**
1271
588
  * Load from CLI --config file or --config-json inline string.
1272
- *
1273
- * --config-json accepts either:
1274
- * 1. An inline JSON string: --config-json='{"deploymentConfig":"transformers-vllm"}'
1275
- * 2. A path to a JSON file: --config-json=config.json
1276
- *
1277
- * When both --config and --config-json are provided, --config-json wins
1278
- * (it is applied second, so its values override --config values).
1279
- *
1280
- * Also checks the ML_CONTAINER_CREATOR_CONFIG environment variable as a
1281
- * fallback for --config.
1282
- *
1283
589
  * @private
1284
590
  */
1285
591
  async _loadCliConfigFile() {
1286
- let configFile = this.options.config;
1287
-
1288
- // Check environment variable if CLI option not provided
1289
- if (!configFile && process.env.ML_CONTAINER_CREATOR_CONFIG) {
1290
- configFile = process.env.ML_CONTAINER_CREATOR_CONFIG;
1291
- }
1292
-
1293
- if (configFile) {
1294
- this._loadConfigFromFile(configFile);
1295
- }
1296
-
1297
- // --config-json: inline JSON string or path to a JSON file
1298
- const configJson = this.options['config-json'];
1299
- if (configJson) {
1300
- this._loadConfigFromJson(configJson);
1301
- }
1302
- }
1303
-
1304
- /**
1305
- * Load configuration from a JSON file path.
1306
- * @param {string} configFile - Path to the JSON config file
1307
- * @private
1308
- */
1309
- _loadConfigFromFile(configFile) {
1310
- try {
1311
- const configPath = path.resolve(configFile);
1312
- if (!fs.existsSync(configPath)) {
1313
- throw new ConfigurationError(
1314
- `Config file not found: ${configPath}`,
1315
- 'configFile',
1316
- 'cli'
1317
- );
1318
- }
1319
-
1320
- // Check if file is readable
1321
- try {
1322
- fs.accessSync(configPath, fs.constants.R_OK);
1323
- } catch (accessError) {
1324
- throw new ConfigurationError(
1325
- `Config file is not readable: ${configPath}`,
1326
- 'configFile',
1327
- 'cli'
1328
- );
1329
- }
1330
-
1331
- const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
1332
- this._applyJsonConfig(config);
1333
- } catch (error) {
1334
- if (error instanceof ConfigurationError) {
1335
- throw error;
1336
- } else {
1337
- throw new ConfigurationError(
1338
- `Failed to load config file ${configFile}: ${error.message}`,
1339
- 'configFile',
1340
- 'cli'
1341
- );
1342
- }
1343
- }
1344
- }
1345
-
1346
- /**
1347
- * Load configuration from an inline JSON string or a JSON file path.
1348
- * Tries to parse as JSON first; if that fails and the value looks like
1349
- * a file path, reads and parses the file instead.
1350
- *
1351
- * @param {string} configJson - Inline JSON string or path to a JSON file
1352
- * @private
1353
- */
1354
- _loadConfigFromJson(configJson) {
1355
- let config;
1356
- try {
1357
- config = JSON.parse(configJson);
1358
- } catch {
1359
- // Not valid JSON — try as a file path
1360
- try {
1361
- const configPath = path.resolve(configJson);
1362
- if (!fs.existsSync(configPath)) {
1363
- throw new ConfigurationError(
1364
- `--config-json value is not valid JSON and file not found: ${configJson}`,
1365
- 'configJson',
1366
- 'cli'
1367
- );
1368
- }
1369
- config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
1370
- } catch (error) {
1371
- if (error instanceof ConfigurationError) {
1372
- throw error;
1373
- }
1374
- throw new ConfigurationError(
1375
- `Failed to parse --config-json: ${error.message}`,
1376
- 'configJson',
1377
- 'cli'
1378
- );
1379
- }
1380
- }
1381
- this._applyJsonConfig(config);
1382
- }
1383
-
1384
- /**
1385
- * Apply a parsed JSON config object, filtering to supported parameters.
1386
- * Handles nested objects for endpoint, iC, and env var configuration.
1387
- * @param {Object} config - Parsed JSON config object
1388
- * @private
1389
- */
1390
- _applyJsonConfig(config) {
1391
- const filteredConfig = {};
1392
- Object.entries(config).forEach(([key, value]) => {
1393
- // Handle nested endpointConfig object
1394
- if (key === 'endpointConfig' && typeof value === 'object' && value !== null) {
1395
- const endpointMapping = {
1396
- initialInstanceCount: 'endpointInitialInstanceCount',
1397
- dataCapturePercent: 'endpointDataCapturePercent',
1398
- variantName: 'endpointVariantName',
1399
- volumeSize: 'endpointVolumeSize'
1400
- };
1401
- Object.entries(value).forEach(([nestedKey, nestedValue]) => {
1402
- const flatKey = endpointMapping[nestedKey];
1403
- if (flatKey && this._isSourceSupported(flatKey, 'configFile')) {
1404
- filteredConfig[flatKey] = nestedValue;
1405
- this._recordSource(flatKey, nestedValue, 'config-file');
1406
- }
1407
- });
1408
- return;
1409
- }
1410
-
1411
- // Handle nested icConfig object
1412
- if (key === 'icConfig' && typeof value === 'object' && value !== null) {
1413
- const icMapping = {
1414
- cpuCount: 'icCpuCount',
1415
- memorySize: 'icMemorySize',
1416
- gpuCount: 'icGpuCount',
1417
- copyCount: 'icCopyCount',
1418
- modelWeight: 'icModelWeight'
1419
- };
1420
- Object.entries(value).forEach(([nestedKey, nestedValue]) => {
1421
- const flatKey = icMapping[nestedKey];
1422
- if (flatKey && this._isSourceSupported(flatKey, 'configFile')) {
1423
- filteredConfig[flatKey] = nestedValue;
1424
- this._recordSource(flatKey, nestedValue, 'config-file');
1425
- }
1426
- });
1427
- return;
1428
- }
1429
-
1430
- // Handle modelEnvVars object (merge with CLI, CLI takes precedence)
1431
- if (key === 'modelEnvVars' && typeof value === 'object' && value !== null) {
1432
- if (!this.config.modelEnvVars) {
1433
- this.config.modelEnvVars = {};
1434
- }
1435
- // Only set keys not already provided by CLI (CLI has higher precedence)
1436
- const cliModelEnvVars = (this.explicitConfig && this.explicitConfig.modelEnvVars) || {};
1437
- Object.entries(value).forEach(([envKey, envValue]) => {
1438
- if (!(envKey in cliModelEnvVars)) {
1439
- this.config.modelEnvVars[envKey] = envValue;
1440
- this._recordSource(`modelEnvVars.${envKey}`, envValue, 'config-file');
1441
- }
1442
- });
1443
- return;
1444
- }
1445
-
1446
- // Handle serverEnvVars object (merge with CLI, CLI takes precedence)
1447
- if (key === 'serverEnvVars' && typeof value === 'object' && value !== null) {
1448
- if (!this.config.serverEnvVars) {
1449
- this.config.serverEnvVars = {};
1450
- }
1451
- // Only set keys not already provided by CLI (CLI has higher precedence)
1452
- const cliServerEnvVars = (this.explicitConfig && this.explicitConfig.serverEnvVars) || {};
1453
- Object.entries(value).forEach(([envKey, envValue]) => {
1454
- if (!(envKey in cliServerEnvVars)) {
1455
- this.config.serverEnvVars[envKey] = envValue;
1456
- this._recordSource(`serverEnvVars.${envKey}`, envValue, 'config-file');
1457
- }
1458
- });
1459
- return;
1460
- }
1461
-
1462
- if (this._isSourceSupported(key, 'configFile')) {
1463
- filteredConfig[key] = this._parseValue(key, value);
1464
- this._recordSource(key, this._parseValue(key, value), 'config-file');
1465
- }
1466
- });
1467
- this._mergeConfig(filteredConfig);
592
+ return this._loader._loadCliConfigFile();
1468
593
  }
1469
594
 
1470
595
  /**
@@ -1472,29 +597,7 @@ export default class ConfigManager {
1472
597
  * @private
1473
598
  */
1474
599
  async _loadEnvironmentVariables() {
1475
- // Build environment variable mapping from parameter matrix
1476
- const envMapping = {};
1477
- Object.entries(this.parameterMatrix).forEach(([param, config]) => {
1478
- if (config.envVar) {
1479
- envMapping[config.envVar] = { param, ambient: config.ambientEnvVar === true };
1480
- }
1481
- });
1482
-
1483
- Object.entries(envMapping).forEach(([envVar, { param: configKey, ambient }]) => {
1484
- const value = process.env[envVar];
1485
- if (value !== undefined && value !== '' && this._isSourceSupported(configKey, 'envVar')) {
1486
- this.config[configKey] = this._parseValue(configKey, value);
1487
- this._recordSource(configKey, this._parseValue(configKey, value), 'env-var');
1488
- // Track as explicit configuration — unless the env var is ambient
1489
- // (e.g. AWS_REGION is commonly set in shells as a default, not an override)
1490
- if (!ambient) {
1491
- if (!this.explicitConfig) {
1492
- this.explicitConfig = {};
1493
- }
1494
- this.explicitConfig[configKey] = this._parseValue(configKey, value);
1495
- }
1496
- }
1497
- });
600
+ return this._loader._loadEnvironmentVariables();
1498
601
  }
1499
602
 
1500
603
  /**
@@ -1502,17 +605,7 @@ export default class ConfigManager {
1502
605
  * @private
1503
606
  */
1504
607
  async _loadCliArguments() {
1505
- // First positional argument is project name
1506
- if (this.args && this.args.length > 0) {
1507
- this.config.projectName = this.args[0];
1508
- // Track as explicit configuration
1509
- if (!this.explicitConfig) {
1510
- this.explicitConfig = {};
1511
- }
1512
- this.explicitConfig.projectName = this.args[0];
1513
- // Track that project name came from positional argument (for subdirectory creation)
1514
- this.projectNameFromArgument = true;
1515
- }
608
+ return this._loader._loadCliArguments();
1516
609
  }
1517
610
 
1518
611
  /**
@@ -1520,95 +613,25 @@ export default class ConfigManager {
1520
613
  * @private
1521
614
  */
1522
615
  async _loadCliOptions() {
1523
- const options = this.options;
1524
-
1525
- // Build CLI option mapping from parameter matrix
1526
- Object.entries(this.parameterMatrix).forEach(([param, config]) => {
1527
- if (config.cliOption && options[config.cliOption] !== undefined) {
1528
- this.config[param] = this._parseValue(param, options[config.cliOption]);
1529
- this._recordSource(param, this._parseValue(param, options[config.cliOption]), 'cli');
1530
- // Track as explicit configuration
1531
- if (!this.explicitConfig) {
1532
- this.explicitConfig = {};
1533
- }
1534
- this.explicitConfig[param] = this._parseValue(param, options[config.cliOption]);
1535
- }
1536
- });
1537
-
1538
- // Parse --model-env KEY=VALUE pairs
1539
- this._parseEnvVarOptions('model-env', 'modelEnvVars');
1540
-
1541
- // Parse --server-env KEY=VALUE pairs
1542
- this._parseEnvVarOptions('server-env', 'serverEnvVars');
616
+ return this._loader._loadCliOptions();
1543
617
  }
1544
618
 
1545
619
  /**
1546
620
  * Normalizes deprecated parameter values to their canonical equivalents.
1547
- * Prints a deprecation warning when a deprecated value is encountered.
1548
621
  * @private
1549
622
  */
1550
623
  _normalizeDeprecatedValues() {
1551
- const DEPRECATED_VALUES = {
1552
- deploymentTarget: {
1553
- 'managed-inference': {
1554
- canonical: 'realtime-inference',
1555
- message: '--deployment-target=managed-inference is deprecated, use realtime-inference instead'
1556
- }
1557
- }
1558
- };
1559
-
1560
- for (const [param, aliases] of Object.entries(DEPRECATED_VALUES)) {
1561
- const currentValue = this.config[param];
1562
- if (currentValue && aliases[currentValue]) {
1563
- const { canonical, message } = aliases[currentValue];
1564
- console.log(`\n⚠️ Deprecation: ${message}`);
1565
- this.config[param] = canonical;
1566
- // Also update explicit config if it was set there
1567
- if (this.explicitConfig && this.explicitConfig[param] === currentValue) {
1568
- this.explicitConfig[param] = canonical;
1569
- }
1570
- }
1571
- }
624
+ return this._loader._normalizeDeprecatedValues();
1572
625
  }
1573
626
 
1574
627
  /**
1575
628
  * Parse --model-env or --server-env CLI options into env var collections.
1576
- * Supports both array (multiple flags) and single string values.
1577
- * Performs eager format validation at parse time.
1578
- * @param {string} optionName - CLI option name (e.g., 'model-env')
1579
- * @param {string} configKey - Config key to store results (e.g., 'modelEnvVars')
629
+ * @param {string} optionName - CLI option name
630
+ * @param {string} configKey - Config key to store results
1580
631
  * @private
1581
632
  */
1582
633
  _parseEnvVarOptions(optionName, configKey) {
1583
- const rawValue = this.options[optionName];
1584
- if (rawValue === undefined || rawValue === null) {
1585
- return;
1586
- }
1587
-
1588
- // Normalize to array (may receive a single string or an array)
1589
- const values = Array.isArray(rawValue) ? rawValue : [rawValue];
1590
-
1591
- // Initialize collection if not already present
1592
- if (!this.config[configKey] || typeof this.config[configKey] !== 'object') {
1593
- this.config[configKey] = {};
1594
- }
1595
-
1596
- for (const entry of values) {
1597
- if (typeof entry !== 'string' || entry.trim() === '') {
1598
- continue;
1599
- }
1600
- const { key, value } = parseKeyValue(entry);
1601
- this.config[configKey][key] = value;
1602
- this._recordSource(`${configKey}.${key}`, value, 'cli');
1603
- }
1604
-
1605
- // Track as explicit configuration
1606
- if (Object.keys(this.config[configKey]).length > 0) {
1607
- if (!this.explicitConfig) {
1608
- this.explicitConfig = {};
1609
- }
1610
- this.explicitConfig[configKey] = { ...this.config[configKey] };
1611
- }
634
+ return this._loader._parseEnvVarOptions(optionName, configKey);
1612
635
  }
1613
636
 
1614
637
  /**
@@ -1618,8 +641,7 @@ export default class ConfigManager {
1618
641
  * @private
1619
642
  */
1620
643
  async _queryMcpServers() {
1621
- // No-op: MCP queries now happen on-demand during prompting
1622
- // via queryMcpServer(). This method is kept for backward compatibility.
644
+ return this._mcpClient._queryMcpServers();
1623
645
  }
1624
646
 
1625
647
  /**
@@ -1630,70 +652,7 @@ export default class ConfigManager {
1630
652
  * @returns {Promise<{ values: object, choices: object } | null>}
1631
653
  */
1632
654
  async queryMcpServer(serverName, context = {}) {
1633
- let mcpServerConfigs;
1634
- try {
1635
- const configPath = path.join(GENERATOR_ROOT, 'config', 'mcp.json');
1636
- if (!fs.existsSync(configPath)) return null;
1637
- const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
1638
- mcpServerConfigs = config.mcpServers;
1639
- } catch {
1640
- return null;
1641
- }
1642
-
1643
- if (!mcpServerConfigs || !mcpServerConfigs[serverName]) return null;
1644
-
1645
- const smart = this.options.smart === true;
1646
- const discover = this.options.discover !== false;
1647
- const serverConfig = mcpServerConfigs[serverName];
1648
-
1649
- // Build a custom McpClient that passes context through
1650
- const client = new McpClient(serverConfig, {
1651
- timeout: 15000,
1652
- parameterMatrix: this.parameterMatrix,
1653
- smart,
1654
- discover
1655
- });
1656
-
1657
- // Override the _buildContext to merge our search context
1658
- const origBuildContext = client._buildContext.bind(client);
1659
- client._buildContext = () => ({ ...origBuildContext(), ...context });
1660
-
1661
- try {
1662
- const result = await client.query();
1663
- await client.close();
1664
-
1665
- if (!result) {
1666
- const diag = client.getDiagnosticMessage();
1667
- if (diag) console.log(` ⚠️ ${serverName}: ${diag}`);
1668
- return null;
1669
- }
1670
-
1671
- // Store values
1672
- for (const [param, value] of Object.entries(result.values || {})) {
1673
- const paramConfig = this.parameterMatrix[param];
1674
- if (paramConfig && paramConfig.valueSpace === 'unbounded' && paramConfig.mcp === true) {
1675
- this.mcpSources[param] = {
1676
- server: serverName,
1677
- value,
1678
- timestamp: new Date().toISOString()
1679
- };
1680
- }
1681
- }
1682
-
1683
- // Store choices
1684
- for (const [param, choices] of Object.entries(result.choices || {})) {
1685
- const paramConfig = this.parameterMatrix[param];
1686
- if (paramConfig && paramConfig.valueSpace === 'unbounded' && paramConfig.mcp === true && Array.isArray(choices)) {
1687
- this.mcpChoices[param] = choices;
1688
- }
1689
- }
1690
-
1691
- return result;
1692
- } catch (err) {
1693
- await client.close().catch(() => {});
1694
- console.log(` ⚠️ ${serverName}: ${err.message}`);
1695
- return null;
1696
- }
655
+ return this._mcpClient.queryMcpServer(serverName, context);
1697
656
  }
1698
657
 
1699
658
  /**
@@ -1701,14 +660,7 @@ export default class ConfigManager {
1701
660
  * @returns {string[]}
1702
661
  */
1703
662
  getMcpServerNames() {
1704
- try {
1705
- const configPath = path.join(GENERATOR_ROOT, 'config', 'mcp.json');
1706
- if (!fs.existsSync(configPath)) return [];
1707
- const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
1708
- return Object.keys(config.mcpServers || {});
1709
- } catch {
1710
- return [];
1711
- }
663
+ return this._mcpClient.getMcpServerNames();
1712
664
  }
1713
665
 
1714
666
  /**
@@ -1766,377 +718,52 @@ export default class ConfigManager {
1766
718
  }
1767
719
  return true;
1768
720
  });
721
+
722
+ // Also require key parameters that are needed for generation
723
+ const essentialParams = ['projectName', 'instanceType'];
724
+ const allRequired = [...new Set([...requiredForConfig, ...essentialParams])];
1769
725
 
1770
- return requiredForConfig.every(key =>
726
+ return allRequired.every(key =>
1771
727
  this.config[key] !== undefined && this.config[key] !== null
1772
728
  );
1773
729
  }
1774
730
 
1775
731
  /**
1776
732
  * Validates the current configuration against the parameter matrix
1777
- * Only reports errors for parameters that cannot be resolved through prompting or auto-generation
1778
733
  * @returns {Array} Array of validation errors
1779
734
  */
1780
735
  validateConfiguration() {
1781
- const errors = [];
1782
-
1783
- // Old-format deployment-config migration messages
1784
- const oldFormatMigration = {
1785
- 'sklearn-flask': 'Use --deployment-config=http-flask --engine=sklearn instead',
1786
- 'sklearn-fastapi': 'Use --deployment-config=http-fastapi --engine=sklearn instead',
1787
- 'xgboost-flask': 'Use --deployment-config=http-flask --engine=xgboost instead',
1788
- 'xgboost-fastapi': 'Use --deployment-config=http-fastapi --engine=xgboost instead',
1789
- 'tensorflow-flask': 'Use --deployment-config=http-flask --engine=tensorflow instead',
1790
- 'tensorflow-fastapi': 'Use --deployment-config=http-fastapi --engine=tensorflow instead'
1791
- };
1792
-
1793
- // Validate deployment-config
1794
- if (this.config.deploymentConfig) {
1795
- const migrationMsg = oldFormatMigration[this.config.deploymentConfig];
1796
- if (migrationMsg) {
1797
- errors.push(`Unsupported deployment-config: ${this.config.deploymentConfig}. This value has been replaced. ${migrationMsg}`);
1798
- } else if (!this.deploymentConfigResolver.isValid(this.config.deploymentConfig)) {
1799
- const valid = this.deploymentConfigResolver.getAllConfigs().join(', ');
1800
- errors.push(`Unsupported deployment-config: ${this.config.deploymentConfig}. Valid configs: ${valid}`);
1801
- }
1802
- }
1803
-
1804
- // Validate engine (only valid for http architecture)
1805
- if (this.config.engine) {
1806
- const validEngines = ['sklearn', 'xgboost', 'tensorflow'];
1807
- if (!validEngines.includes(this.config.engine)) {
1808
- errors.push(`Unsupported engine: ${this.config.engine}. Supported: ${validEngines.join(', ')}`);
1809
- }
1810
- }
1811
-
1812
- // Validate model format based on architecture/engine
1813
- if (this.config.modelFormat && this.config.deploymentConfig) {
1814
- try {
1815
- const parts = this.deploymentConfigResolver.decompose(this.config.deploymentConfig);
1816
- if (parts.architecture === 'http') {
1817
- const engine = this.config.engine || parts.engine;
1818
- if (engine) {
1819
- const supportedOptions = this._getSupportedOptions();
1820
- const validFormats = supportedOptions.modelFormats[engine] || [];
1821
- if (validFormats.length > 0 && !validFormats.includes(this.config.modelFormat)) {
1822
- errors.push(`Unsupported model format '${this.config.modelFormat}' for engine '${engine}'. Supported: ${validFormats.join(', ')}`);
1823
- }
1824
- }
1825
- }
1826
- } catch {
1827
- // deploymentConfig already flagged as invalid above
1828
- }
1829
- }
1830
-
1831
- // Validate mutual exclusion: plaintext token and ARN cannot both be set
1832
- if (this.config.hfToken && this.config.hfTokenArn) {
1833
- errors.push('Cannot specify both --hf-token and --hf-token-arn. Use one or the other.');
1834
- }
1835
- if (this.config.ngcTokenArn) {
1836
- // Check ngcToken from CLI options (Commander converts --ngc-token to ngcToken)
1837
- const ngcTokenFromCli = this.options['ngc-token'];
1838
- if (ngcTokenFromCli) {
1839
- errors.push('Cannot specify both --ngc-token and --ngc-token-arn. Use one or the other.');
1840
- }
1841
- }
1842
-
1843
- // Validate AWS Role ARN format if provided
1844
- if (this.config.awsRoleArn) {
1845
- try {
1846
- this._isValidArn(this.config.awsRoleArn);
1847
- } catch (error) {
1848
- if (error instanceof ValidationError) {
1849
- errors.push(error.message);
1850
- } else {
1851
- errors.push(`Invalid AWS Role ARN format: ${this.config.awsRoleArn}. Expected format: arn:aws:iam::123456789012:role/RoleName`);
1852
- }
1853
- }
1854
- }
1855
-
1856
- // Validate build target (renamed from deployTarget)
1857
- const buildTarget = this.config.buildTarget || this.config.deployTarget;
1858
- if (buildTarget && !this._getSupportedOptions().buildTargets.includes(buildTarget)) {
1859
- errors.push(`Unsupported build target: ${buildTarget}. Supported targets: ${this._getSupportedOptions().buildTargets.join(', ')}`);
1860
- }
1861
-
1862
- // Validate CodeBuild compute type
1863
- if (this.config.codebuildComputeType && !this._getSupportedOptions().codebuildComputeTypes.includes(this.config.codebuildComputeType)) {
1864
- errors.push(`Unsupported CodeBuild compute type: ${this.config.codebuildComputeType}. Supported types: ${this._getSupportedOptions().codebuildComputeTypes.join(', ')}`);
1865
- }
1866
-
1867
- // Validate CodeBuild project name format
1868
- if (this.config.codebuildProjectName) {
1869
- const projectNamePattern = /^[a-zA-Z0-9][a-zA-Z0-9\-_]{1,254}$/;
1870
- if (!projectNamePattern.test(this.config.codebuildProjectName)) {
1871
- errors.push(`Invalid CodeBuild project name: ${this.config.codebuildProjectName}. Project names must be 2-255 characters, start with a letter or number, and contain only letters, numbers, hyphens, and underscores.`);
1872
- }
1873
- }
1874
-
1875
- // Validate model package ARN format if provided
1876
- if (this.config.modelPackageArn) {
1877
- const modelPackageArnPattern = /^arn:aws:sagemaker:[a-z0-9-]+:\d{12}:model-package\/[a-zA-Z0-9]([a-zA-Z0-9-])*\/\d+$/;
1878
- if (!modelPackageArnPattern.test(this.config.modelPackageArn)) {
1879
- errors.push('❌ Invalid model package ARN format. Expected: arn:aws:sagemaker:<region>:<account>:model-package/<name>/<version>');
1880
- }
1881
- }
1882
-
1883
- // Only validate required parameters if we're skipping prompts
1884
- // If prompts are available, missing parameters can be collected later
1885
- if (this.skipPrompts) {
1886
- Object.entries(this.parameterMatrix).forEach(([param, config]) => {
1887
- if (config.required &&
1888
- (this.config[param] === null || this.config[param] === undefined)) {
1889
-
1890
- // Special case: modelFormat is not required for transformers/triton/diffusors
1891
- if (param === 'modelFormat') {
1892
- try {
1893
- const parts = this.deploymentConfigResolver.decompose(this.config.deploymentConfig);
1894
- if (parts.architecture === 'transformers' || parts.architecture === 'triton' || parts.architecture === 'diffusors') {
1895
- return;
1896
- }
1897
- } catch {
1898
- // If deploymentConfig is invalid, skip this check
1899
- return;
1900
- }
1901
- }
1902
-
1903
- // Only error for promptable required parameters that have no default and can't be auto-generated
1904
- if (config.promptable && config.default === null && !this._canAutoGenerate(param)) {
1905
- errors.push(`Required parameter '${param}' is missing and prompts are disabled`);
1906
- }
1907
- }
1908
- });
1909
-
1910
- // Validate that modelName is provided for diffusors architecture
1911
- if (this.config.deploymentConfig) {
1912
- try {
1913
- const parts = this.deploymentConfigResolver.decompose(this.config.deploymentConfig);
1914
- if (parts.architecture === 'diffusors') {
1915
- const explicitModelName = this.explicitConfig && this.explicitConfig.modelName;
1916
- if (!explicitModelName) {
1917
- errors.push('Model name is required for diffusors architecture. Use --model-name to specify a HuggingFace diffusion model.');
1918
- }
1919
- }
1920
- } catch {
1921
- // deploymentConfig already flagged as invalid above
1922
- }
1923
- }
1924
- }
1925
-
1926
- // Validate schema-validated parameters (endpoint, iC)
1927
- Object.entries(this.parameterMatrix).forEach(([param, config]) => {
1928
- if (config.schemaValidated && this.config[param] !== null && this.config[param] !== undefined) {
1929
- const result = this.schemaValidator.validate(param, this.config[param], this.config.deploymentTarget);
1930
- if (!result.valid) {
1931
- errors.push(result.error);
1932
- }
1933
- }
1934
- });
1935
-
1936
- return errors;
736
+ return this._validator.validateConfiguration();
1937
737
  }
1938
738
 
1939
739
  /**
1940
740
  * Validates required parameters before file generation
1941
- * This is called after all configuration sources have been processed and prompting is complete
1942
741
  * @param {Object} finalConfig - The complete configuration object
1943
742
  * @returns {Array} Array of validation errors for missing required parameters
1944
743
  */
1945
744
  validateRequiredParameters(finalConfig) {
1946
- const errors = [];
1947
-
1948
- // First, validate individual parameter values
1949
- Object.entries(finalConfig).forEach(([param, value]) => {
1950
- if (value !== null && value !== undefined && value !== '') {
1951
- try {
1952
- this._validateParameterValue(param, value, finalConfig);
1953
- } catch (error) {
1954
- if (error instanceof ValidationError) {
1955
- errors.push(error.message);
1956
- } else {
1957
- errors.push(`Invalid value for parameter '${param}': ${error.message}`);
1958
- }
1959
- }
1960
- }
1961
- });
1962
-
1963
- // Then, validate required parameters are present
1964
- Object.entries(this.parameterMatrix).forEach(([param, config]) => {
1965
- if (config.required) {
1966
- const value = finalConfig[param];
1967
- const isEmpty = value === null || value === undefined || value === '';
1968
-
1969
- // Special case: modelFormat is not required for transformers/triton/diffusors/marketplace
1970
- if (param === 'modelFormat' && (finalConfig.architecture === 'transformers' || finalConfig.architecture === 'triton' || finalConfig.architecture === 'diffusors' || finalConfig.architecture === 'marketplace')) {
1971
- return; // Skip validation
1972
- }
1973
-
1974
- // Special case: marketplace projects don't need container-related parameters
1975
- if (finalConfig.architecture === 'marketplace' && (param === 'includeSampleModel' || param === 'buildTarget')) {
1976
- return; // Skip validation — marketplace has no container to build
1977
- }
1978
-
1979
- // Special case: instanceType is not required for hyperpod-eks
1980
- // when not provided (backward compatibility) — but it IS prompted now
1981
- // so it should normally be present
1982
- if (param === 'instanceType' && finalConfig.deploymentTarget === 'hyperpod-eks' && !finalConfig.instanceType) {
1983
- return; // Skip validation only if truly missing for backward compat
1984
- }
1985
-
1986
- // Special case: instanceType is not required when attaching to an existing endpoint
1987
- // The instance type is inherited from the existing endpoint configuration
1988
- if (param === 'instanceType' && finalConfig.existingEndpointName) {
1989
- return; // Skip validation — instance is inherited from existing endpoint
1990
- }
1991
-
1992
- if (isEmpty) {
1993
- if (config.promptable) {
1994
- // Promptable required parameter is missing - this should not happen after prompting
1995
- errors.push(`Required parameter '${param}' is missing. This parameter is required for ${finalConfig.architecture || 'the selected'} architecture.`);
1996
- } else {
1997
- // Non-promptable required parameter is missing - this is a configuration error
1998
- errors.push(`Required non-promptable parameter '${param}' is missing. This parameter must be provided through CLI options, environment variables, or configuration files.`);
1999
- }
2000
- }
2001
- }
2002
- });
2003
-
2004
- // Finally, validate parameter combinations and dependencies
2005
- const combinationErrors = this._validateParameterCombinations(finalConfig);
2006
- errors.push(...combinationErrors);
2007
-
2008
- return errors;
745
+ return this._validator.validateRequiredParameters(finalConfig);
2009
746
  }
2010
747
 
2011
748
  /**
2012
- * Validates parameter combinations and dependencies
2013
- * @param {Object} config - The configuration object to validate
2014
- * @returns {Array} Array of validation errors for invalid combinations
2015
749
  * @private
2016
750
  */
2017
751
  _validateParameterCombinations(config) {
2018
- const errors = [];
2019
-
2020
- // Additional combination validations that aren't covered by individual parameter validation
2021
- // For example, complex business rules that involve multiple parameters
2022
-
2023
- // Validate that transformers architecture has sample model disabled
2024
- if (config.architecture === 'transformers' && config.includeSampleModel === true) {
2025
- errors.push(`Architecture '${config.architecture}' does not support sample models. The 'includeSampleModel' parameter will be automatically set to false.`);
2026
- }
2027
- // Validate that diffusors architecture has sample model disabled
2028
- if (config.architecture === 'diffusors' && config.includeSampleModel === true) {
2029
- errors.push(`Architecture '${config.architecture}' does not support sample models. The 'includeSampleModel' parameter will be automatically set to false.`);
2030
- }
2031
- // Validate that ineligible Triton backends have sample model disabled
2032
- if (config.architecture === 'triton' && config.includeSampleModel === true) {
2033
- const backendMeta = tritonBackends[config.backend];
2034
- if (!backendMeta || !backendMeta.supportsSampleModel) {
2035
- errors.push(`Triton backend '${config.backend}' does not support sample models. The 'includeSampleModel' parameter will be automatically set to false.`);
2036
- }
2037
- }
2038
-
2039
- return errors;
752
+ return this._validator._validateParameterCombinations(config);
2040
753
  }
2041
754
 
2042
755
  /**
2043
- * Checks if a parameter can be auto-generated when missing
2044
- * @param {string} param - Parameter name
2045
- * @returns {boolean} True if parameter can be auto-generated
2046
756
  * @private
2047
757
  */
2048
758
  _canAutoGenerate(param) {
2049
- // Parameters that can be auto-generated even when missing
2050
- const autoGeneratable = [
2051
- 'modelFormat', // Can be inferred from engine
2052
- 'includeSampleModel', // Has default
2053
- 'includeTesting', // Has default
2054
- 'instanceType' // Has default
2055
- ];
2056
-
2057
- return autoGeneratable.includes(param);
759
+ return this._validator._canAutoGenerate(param);
2058
760
  }
2059
761
 
2060
762
  /**
2061
- * Fills auto-prompt defaults for parameters that have sensible defaults
2062
- * or can be inferred from the current config. Promotes these into
2063
- * explicitConfig so the wizard skips them.
2064
- *
2065
- * Only fills parameters that:
2066
- * - Have a non-null default in the parameter matrix, OR
2067
- * - Can be auto-generated (instanceType, modelFormat, etc.)
2068
- *
2069
- * Does NOT fill parameters that are truly ambiguous and need user input
2070
- * (e.g., deploymentConfig when not provided).
2071
763
  * @private
2072
764
  */
2073
765
  _fillAutoPromptDefaults() {
2074
- if (!this.explicitConfig) {
2075
- this.explicitConfig = {};
2076
- }
2077
-
2078
- // Derive architecture from deploymentConfig if available
2079
- let architecture = this.config.architecture;
2080
- if (!architecture && this.config.deploymentConfig) {
2081
- try {
2082
- const parts = this.deploymentConfigResolver.decompose(this.config.deploymentConfig);
2083
- architecture = parts.architecture;
2084
- this.config.architecture = parts.architecture;
2085
- this.config.backend = parts.backend;
2086
- this.config.engine = parts.engine;
2087
- } catch {
2088
- // Invalid deploymentConfig — will be caught by validation
2089
- }
2090
- }
2091
-
2092
- Object.entries(this.parameterMatrix).forEach(([param, config]) => {
2093
- // Skip if already explicitly set
2094
- if (this.explicitConfig[param] !== undefined && this.explicitConfig[param] !== null) {
2095
- return;
2096
- }
2097
-
2098
- // For optional parameters: mark them as explicit (with null) so the wizard skips them.
2099
- // The downstream template logic handles defaults for optional params.
2100
- if (!config.required) {
2101
- // Don't override if there's already a value in config
2102
- if (this.config[param] !== undefined && this.config[param] !== null) {
2103
- this.explicitConfig[param] = this.config[param];
2104
- } else if (config.default !== null && config.default !== undefined) {
2105
- this.config[param] = config.default;
2106
- this.explicitConfig[param] = config.default;
2107
- }
2108
- return;
2109
- }
2110
-
2111
- // For required parameters: fill auto-generatable values
2112
- if (this.config[param] === undefined || this.config[param] === null) {
2113
- if (param === 'instanceType') {
2114
- // If instance-sizer is configured and model is known, defer to sizer
2115
- // The sizer query happens in PromptRunner after model is selected
2116
- // For now, set a heuristic default that may be overridden by the sizer
2117
- const arch = architecture || 'http';
2118
- this.config[param] = arch === 'http' ? 'ml.m5.large' : 'ml.g5.xlarge';
2119
- } else if (param === 'modelFormat') {
2120
- if (architecture === 'transformers' || architecture === 'triton' || architecture === 'diffusors') {
2121
- return; // Not needed for these architectures
2122
- }
2123
- const engine = this.config.engine || 'sklearn';
2124
- const formatMap = { sklearn: 'pkl', xgboost: 'json', tensorflow: 'keras' };
2125
- this.config[param] = formatMap[engine] || 'pkl';
2126
- } else if (param === 'projectName') {
2127
- this.config[param] = this._generateProjectName(architecture);
2128
- } else {
2129
- return; // Can't fill — leave for prompting
2130
- }
2131
- }
2132
-
2133
- // Promote non-null values to explicitConfig so the wizard skips them
2134
- if (this.config[param] !== undefined && this.config[param] !== null) {
2135
- if (config.default !== null || this._canAutoGenerate(param)) {
2136
- this.explicitConfig[param] = this.config[param];
2137
- }
2138
- }
2139
- });
766
+ return this._validator._fillAutoPromptDefaults();
2140
767
  }
2141
768
 
2142
769
  /**
@@ -2144,49 +771,15 @@ export default class ConfigManager {
2144
771
  * @returns {boolean}
2145
772
  */
2146
773
  isAutoPrompt() {
2147
- return this.autoPrompt;
774
+ return this._validator.isAutoPrompt();
2148
775
  }
2149
776
 
2150
777
  /**
2151
- * Gets the list of required parameters that are truly missing and cannot be
2152
- * auto-generated or defaulted. Used by auto-prompt mode to determine which
2153
- * specific prompts to show.
2154
- *
778
+ * Gets the list of required parameters that are truly missing.
2155
779
  * @returns {string[]} Array of parameter names that need prompting
2156
780
  */
2157
781
  getMissingRequiredParameters() {
2158
- const missing = [];
2159
-
2160
- Object.entries(this.parameterMatrix).forEach(([param, config]) => {
2161
- if (!config.required || !config.promptable) return;
2162
-
2163
- const value = this.config[param];
2164
- const hasValue = value !== undefined && value !== null;
2165
-
2166
- if (hasValue) return;
2167
-
2168
- // Special case: modelFormat is not required for transformers/triton/diffusors
2169
- if (param === 'modelFormat') {
2170
- const architecture = this.config.architecture;
2171
- if (architecture === 'transformers' || architecture === 'triton' || architecture === 'diffusors') {
2172
- return;
2173
- }
2174
- // Can be inferred from engine
2175
- if (this.config.engine || this.config.deploymentConfig) {
2176
- return;
2177
- }
2178
- }
2179
-
2180
- // Skip params that can be auto-generated
2181
- if (this._canAutoGenerate(param)) return;
2182
-
2183
- // Skip params that have a non-null default
2184
- if (config.default !== null && config.default !== undefined) return;
2185
-
2186
- missing.push(param);
2187
- });
2188
-
2189
- return missing;
782
+ return this._validator.getMissingRequiredParameters();
2190
783
  }
2191
784
 
2192
785
  /**
@@ -2254,243 +847,31 @@ export default class ConfigManager {
2254
847
  }
2255
848
 
2256
849
  /**
2257
- * Validates a single parameter value
2258
- * @param {string} parameter - Parameter name
2259
- * @param {*} value - Parameter value
2260
- * @param {Object} context - Additional context (e.g., other parameter values)
2261
- * @throws {ValidationError} If parameter value is invalid
2262
850
  * @private
2263
851
  */
2264
852
  _validateParameterValue(parameter, value, context = {}) {
2265
- // First pass: schema-derived validation rules (type, range, pattern, enum)
2266
- // Skip deprecated params — they have relaxed validation handled by the switch below
2267
- const schemaRule = validationRules[parameter];
2268
- if (schemaRule && value !== null && value !== undefined) {
2269
- // Don't apply strict enum validation to internally-derived values
2270
- // The switch statement below handles context-dependent validation
2271
- const skipSchemaValidation = ['framework', 'modelServer', 'deploymentConfig'].includes(parameter);
2272
- if (!skipSchemaValidation) {
2273
- const error = schemaRule(value);
2274
- if (error) {
2275
- throw new ValidationError(error, parameter, value);
2276
- }
2277
- }
2278
- }
2279
-
2280
- // Second pass: context-dependent validations that require runtime state
2281
- const supportedOptions = this._getSupportedOptions();
2282
-
2283
- switch (parameter) {
2284
- case 'deploymentConfig':
2285
- if (value) {
2286
- // Check for old-format configs with migration messages
2287
- const oldFormatMigration = {
2288
- 'sklearn-flask': 'Use --deployment-config=http-flask --engine=sklearn instead',
2289
- 'sklearn-fastapi': 'Use --deployment-config=http-fastapi --engine=sklearn instead',
2290
- 'xgboost-flask': 'Use --deployment-config=http-flask --engine=xgboost instead',
2291
- 'xgboost-fastapi': 'Use --deployment-config=http-fastapi --engine=xgboost instead',
2292
- 'tensorflow-flask': 'Use --deployment-config=http-flask --engine=tensorflow instead',
2293
- 'tensorflow-fastapi': 'Use --deployment-config=http-fastapi --engine=tensorflow instead'
2294
- };
2295
- const migrationMsg = oldFormatMigration[value];
2296
- if (migrationMsg) {
2297
- throw new ValidationError(
2298
- `Unsupported deployment-config: ${value}. This value has been replaced. ${migrationMsg}`,
2299
- parameter,
2300
- value
2301
- );
2302
- }
2303
- if (!this.deploymentConfigResolver.isValid(value)) {
2304
- const valid = this.deploymentConfigResolver.getAllConfigs().join(', ');
2305
- throw new ValidationError(
2306
- `Unsupported deployment-config: ${value}. Valid configs: ${valid}`,
2307
- parameter,
2308
- value
2309
- );
2310
- }
2311
- }
2312
- break;
2313
-
2314
- case 'engine':
2315
- if (value) {
2316
- const validEngines = ['sklearn', 'xgboost', 'tensorflow'];
2317
- if (!validEngines.includes(value)) {
2318
- throw new ValidationError(
2319
- `Unsupported engine: ${value}. Supported: ${validEngines.join(', ')}`,
2320
- parameter,
2321
- value
2322
- );
2323
- }
2324
- }
2325
- break;
2326
-
2327
- case 'modelFormat':
2328
- if (value && context.architecture === 'http' && context.engine) {
2329
- const validFormats = supportedOptions.modelFormats[context.engine] || [];
2330
- if (validFormats.length > 0 && !validFormats.includes(value)) {
2331
- throw new ValidationError(
2332
- `Model format '${value}' is not compatible with engine '${context.engine}'. Compatible formats: ${validFormats.join(', ')}`,
2333
- parameter,
2334
- value
2335
- );
2336
- }
2337
- }
2338
- break;
2339
-
2340
- case 'instanceType':
2341
- if (value) {
2342
- // Validate AWS SageMaker instance type format
2343
- const instancePattern = /^ml\.[a-z0-9]+\.(nano|micro|small|medium|large|xlarge|[0-9]+xlarge)$/;
2344
- if (!instancePattern.test(value)) {
2345
- throw new ValidationError(
2346
- `Invalid instance type format: ${value}. Expected format: ml.{family}.{size} (e.g., ml.m5.large, ml.g4dn.xlarge)`,
2347
- parameter,
2348
- value
2349
- );
2350
- }
2351
- // Warn about CPU instances for transformers/triton (but don't block)
2352
- if (context.architecture === 'transformers' || context.architecture === 'triton') {
2353
- const cpuFamilies = ['t2', 't3', 't3a', 't4g', 'm4', 'm5', 'm5a', 'm5ad', 'm5d', 'm5dn', 'm5n', 'm5zn', 'm6a', 'm6g', 'm6gd', 'm6i', 'm6id', 'm6idn', 'm6in', 'c4', 'c5', 'c5a', 'c5ad', 'c5d', 'c5n', 'c6a', 'c6g', 'c6gd', 'c6gn', 'c6i', 'c6id', 'c6in', 'r4', 'r5', 'r5a', 'r5ad', 'r5b', 'r5d', 'r5dn', 'r5n', 'r6a', 'r6g', 'r6gd', 'r6i', 'r6id', 'r6idn', 'r6in'];
2354
- const instanceFamily = value.split('.')[1];
2355
- if (cpuFamilies.includes(instanceFamily)) {
2356
- console.warn(`⚠️ Warning: Using CPU instance ${value} with ${context.architecture} architecture. GPU instances are recommended for better performance.`);
2357
- }
2358
- }
2359
- }
2360
- break;
2361
-
2362
- case 'awsRegion':
2363
- if (value && !supportedOptions.awsRegions.includes(value)) {
2364
- throw new ValidationError(
2365
- `Unsupported AWS region: ${value}. Supported regions: ${supportedOptions.awsRegions.join(', ')}`,
2366
- parameter,
2367
- value
2368
- );
2369
- }
2370
- break;
2371
-
2372
- case 'awsRoleArn':
2373
- if (value) {
2374
- this._isValidArn(value);
2375
- }
2376
- break;
2377
-
2378
- case 'buildTarget':
2379
- case 'deployTarget':
2380
- if (value && !supportedOptions.buildTargets.includes(value)) {
2381
- throw new ValidationError(
2382
- `Unsupported build target: ${value}. Supported targets: ${supportedOptions.buildTargets.join(', ')}`,
2383
- parameter,
2384
- value
2385
- );
2386
- }
2387
- break;
2388
-
2389
- case 'codebuildComputeType':
2390
- if (value && !supportedOptions.codebuildComputeTypes.includes(value)) {
2391
- throw new ValidationError(
2392
- `Unsupported CodeBuild compute type: ${value}. Supported types: ${supportedOptions.codebuildComputeTypes.join(', ')}`,
2393
- parameter,
2394
- value
2395
- );
2396
- }
2397
- break;
2398
-
2399
- case 'codebuildProjectName':
2400
- if (value) {
2401
- // AWS CodeBuild project names must follow specific naming rules
2402
- const projectNamePattern = /^[a-zA-Z0-9][a-zA-Z0-9\-_]{1,254}$/;
2403
- if (!projectNamePattern.test(value)) {
2404
- throw new ValidationError(
2405
- `Invalid CodeBuild project name: ${value}. Project names must be 2-255 characters, start with a letter or number, and contain only letters, numbers, hyphens, and underscores.`,
2406
- parameter,
2407
- value
2408
- );
2409
- }
2410
- }
2411
- break;
2412
-
2413
- case 'modelPackageArn':
2414
- if (value) {
2415
- const modelPackageArnPattern = /^arn:aws:sagemaker:[a-z0-9-]+:\d{12}:model-package\/[a-zA-Z0-9]([a-zA-Z0-9-])*\/\d+$/;
2416
- if (!modelPackageArnPattern.test(value)) {
2417
- throw new ValidationError(
2418
- '❌ Invalid model package ARN format. Expected: arn:aws:sagemaker:<region>:<account>:model-package/<name>/<version>',
2419
- parameter,
2420
- value
2421
- );
2422
- }
2423
- }
2424
- break;
2425
- }
853
+ return this._validator._validateParameterValue(parameter, value, context);
2426
854
  }
2427
855
 
2428
856
  /**
2429
- * Resolves HF_TOKEN references to actual token values
2430
- * @param {string} tokenValue - The token value or "$HF_TOKEN" reference
2431
- * @returns {string|null} Resolved token value
2432
857
  * @private
2433
858
  */
2434
859
  _resolveHfToken(tokenValue) {
2435
- if (!tokenValue || tokenValue.trim() === '') {
2436
- return null;
2437
- }
2438
-
2439
- // Check if it's an environment variable reference
2440
- if (tokenValue.trim() === '$HF_TOKEN') {
2441
- const envToken = process.env.HF_TOKEN;
2442
- if (!envToken) {
2443
- console.warn('⚠️ Warning: $HF_TOKEN specified but HF_TOKEN environment variable is not set');
2444
- console.warn(' The container will be built without authentication.');
2445
- return null;
2446
- }
2447
- return envToken;
2448
- }
2449
-
2450
- // Direct token value
2451
- return tokenValue;
860
+ return this._validator._resolveHfToken(tokenValue);
2452
861
  }
2453
862
 
2454
863
  /**
2455
- * Validates AWS Role ARN format
2456
- * @param {string} arn - The ARN to validate
2457
- * @throws {ValidationError} If ARN format is invalid
2458
864
  * @private
2459
865
  */
2460
866
  _isValidArn(arn) {
2461
- const arnPattern = /^arn:aws:iam::\d{12}:role\/[\w+=,.@-]+$/;
2462
- if (!arnPattern.test(arn)) {
2463
- throw new ValidationError(
2464
- `Invalid AWS Role ARN format: ${arn}. Expected format: arn:aws:iam::123456789012:role/RoleName`,
2465
- 'awsRoleArn',
2466
- arn
2467
- );
2468
- }
2469
- return true;
867
+ return this._validator._isValidArn(arn);
2470
868
  }
2471
869
 
2472
870
  /**
2473
- * Gets supported options for validation
2474
871
  * @private
2475
872
  */
2476
873
  _getSupportedOptions() {
2477
- return {
2478
- deploymentConfigs: this.deploymentConfigResolver.getAllConfigs(),
2479
- engines: ['sklearn', 'xgboost', 'tensorflow'],
2480
- modelFormats: {
2481
- 'sklearn': ['pkl', 'joblib'],
2482
- 'xgboost': ['json', 'model', 'ubj'],
2483
- 'tensorflow': ['keras', 'h5', 'SavedModel']
2484
- },
2485
- buildTargets: ['codebuild'],
2486
- codebuildComputeTypes: ['BUILD_GENERAL1_SMALL', 'BUILD_GENERAL1_MEDIUM', 'BUILD_GENERAL1_LARGE'],
2487
- awsRegions: [
2488
- 'us-east-1', 'us-east-2', 'us-west-1', 'us-west-2',
2489
- 'eu-west-1', 'eu-west-2', 'eu-central-1', 'eu-north-1',
2490
- 'ap-southeast-1', 'ap-southeast-2', 'ap-northeast-1',
2491
- 'ca-central-1', 'sa-east-1'
2492
- ]
2493
- };
874
+ return this._validator._getSupportedOptions();
2494
875
  }
2495
876
  }
2496
877