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