@aws/ml-container-creator 0.9.1 → 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 (32) hide show
  1. package/config/parameter-schema-v2.json +2065 -0
  2. package/package.json +4 -4
  3. package/servers/lib/catalogs/jumpstart-public.json +101 -16
  4. package/servers/lib/catalogs/models.json +182 -26
  5. package/src/app.js +1 -389
  6. package/src/lib/bootstrap-command-handler.js +75 -1078
  7. package/src/lib/bootstrap-profile-manager.js +634 -0
  8. package/src/lib/bootstrap-provisioners.js +421 -0
  9. package/src/lib/config-loader.js +405 -0
  10. package/src/lib/config-manager.js +59 -1685
  11. package/src/lib/config-mcp-client.js +118 -0
  12. package/src/lib/config-validator.js +634 -0
  13. package/src/lib/cuda-resolver.js +140 -0
  14. package/src/lib/e2e-catalog-validator.js +251 -3
  15. package/src/lib/e2e-ci-recorder.js +103 -0
  16. package/src/lib/generated/cli-options.js +8 -4
  17. package/src/lib/generated/parameter-matrix.js +671 -0
  18. package/src/lib/generated/validation-rules.js +2 -2
  19. package/src/lib/marketplace-flow.js +276 -0
  20. package/src/lib/mcp-query-runner.js +768 -0
  21. package/src/lib/parameter-schema-validator.js +62 -18
  22. package/src/lib/prompt-runner.js +41 -1504
  23. package/src/lib/prompts/feature-prompts.js +172 -0
  24. package/src/lib/prompts/index.js +48 -0
  25. package/src/lib/prompts/infrastructure-prompts.js +690 -0
  26. package/src/lib/prompts/model-prompts.js +552 -0
  27. package/src/lib/prompts/project-prompts.js +70 -0
  28. package/src/lib/prompts.js +2 -1446
  29. package/src/lib/registry-command-handler.js +135 -3
  30. package/src/lib/secrets-prompt-runner.js +251 -0
  31. package/src/lib/template-variable-resolver.js +398 -0
  32. package/config/parameter-schema.json +0 -88
@@ -0,0 +1,405 @@
1
+ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ /**
5
+ * Config Loader - Handles loading configuration from all sources.
6
+ * Uses delegation pattern: receives parent ConfigManager reference to access shared state.
7
+ */
8
+
9
+ import fs from 'fs';
10
+ import path from 'path';
11
+ import BootstrapConfig from './bootstrap-config.js';
12
+ import { parseKeyValue } from './key-value-parser.js';
13
+ import { ConfigurationError } from './config-manager.js';
14
+
15
+ export default class ConfigLoader {
16
+ constructor(manager) {
17
+ this.manager = manager;
18
+ }
19
+
20
+ /**
21
+ * Load from bootstrap config (~/.ml-container-creator/config.json)
22
+ * @private
23
+ */
24
+ async _loadBootstrapConfig() {
25
+ try {
26
+ const bootstrapConfig = new BootstrapConfig();
27
+ const activeProfile = bootstrapConfig.getActiveProfile();
28
+ if (!activeProfile) {
29
+ return;
30
+ }
31
+
32
+ const profileConfig = activeProfile.config;
33
+ const mapped = {};
34
+
35
+ if (profileConfig.roleArn) {
36
+ mapped.awsRoleArn = profileConfig.roleArn;
37
+ }
38
+ if (profileConfig.awsRegion) {
39
+ mapped.awsRegion = profileConfig.awsRegion;
40
+ }
41
+ if (profileConfig.awsProfile) {
42
+ mapped.awsProfile = profileConfig.awsProfile;
43
+ }
44
+
45
+ this.manager._mergeConfig(mapped);
46
+ } catch (error) {
47
+ // Ignore errors — config file may not exist or may be malformed
48
+ }
49
+ }
50
+
51
+ /**
52
+ * Load from package.json "ml-container-creator" section (filtered by matrix)
53
+ * @private
54
+ */
55
+ async _loadPackageJsonConfig() {
56
+ try {
57
+ const packageJsonPath = path.resolve(process.cwd(), 'package.json');
58
+ if (fs.existsSync(packageJsonPath)) {
59
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
60
+ const generatorConfig = packageJson['ml-container-creator'];
61
+ if (generatorConfig) {
62
+ const filteredConfig = {};
63
+ Object.entries(generatorConfig).forEach(([key, value]) => {
64
+ if (this.manager._isSourceSupported(key, 'packageJson')) {
65
+ filteredConfig[key] = this.manager._parseValue(key, value);
66
+ }
67
+ });
68
+ this.manager._mergeConfig(filteredConfig);
69
+ }
70
+ }
71
+ } catch (error) {
72
+ // Ignore errors - this is optional
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Load from config/mcp.json
78
+ * @private
79
+ */
80
+ async _loadCustomConfigFile() {
81
+ try {
82
+ const configPath = path.join(this.manager.GENERATOR_ROOT, 'config', 'mcp.json');
83
+ if (fs.existsSync(configPath)) {
84
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
85
+ this.manager._mergeConfig(config);
86
+ }
87
+ } catch (error) {
88
+ // Ignore errors - this is optional
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Load from CLI --config file or --config-json inline string.
94
+ * @private
95
+ */
96
+ async _loadCliConfigFile() {
97
+ let configFile = this.manager.options.config;
98
+
99
+ // Check environment variable if CLI option not provided
100
+ if (!configFile && process.env.ML_CONTAINER_CREATOR_CONFIG) {
101
+ configFile = process.env.ML_CONTAINER_CREATOR_CONFIG;
102
+ }
103
+
104
+ if (configFile) {
105
+ this._loadConfigFromFile(configFile);
106
+ }
107
+
108
+ // --config-json: inline JSON string or path to a JSON file
109
+ const configJson = this.manager.options['config-json'];
110
+ if (configJson) {
111
+ this._loadConfigFromJson(configJson);
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Load configuration from a JSON file path.
117
+ * @param {string} configFile - Path to the JSON config file
118
+ * @private
119
+ */
120
+ _loadConfigFromFile(configFile) {
121
+ try {
122
+ const configPath = path.resolve(configFile);
123
+ if (!fs.existsSync(configPath)) {
124
+ throw new ConfigurationError(
125
+ `Config file not found: ${configPath}`,
126
+ 'configFile',
127
+ 'cli'
128
+ );
129
+ }
130
+
131
+ try {
132
+ fs.accessSync(configPath, fs.constants.R_OK);
133
+ } catch (accessError) {
134
+ throw new ConfigurationError(
135
+ `Config file is not readable: ${configPath}`,
136
+ 'configFile',
137
+ 'cli'
138
+ );
139
+ }
140
+
141
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
142
+ this._applyJsonConfig(config);
143
+ } catch (error) {
144
+ if (error instanceof ConfigurationError) {
145
+ throw error;
146
+ } else {
147
+ throw new ConfigurationError(
148
+ `Failed to load config file ${configFile}: ${error.message}`,
149
+ 'configFile',
150
+ 'cli'
151
+ );
152
+ }
153
+ }
154
+ }
155
+
156
+ /**
157
+ * Load configuration from an inline JSON string or a JSON file path.
158
+ * @param {string} configJson - Inline JSON string or path to a JSON file
159
+ * @private
160
+ */
161
+ _loadConfigFromJson(configJson) {
162
+ let config;
163
+ try {
164
+ config = JSON.parse(configJson);
165
+ } catch {
166
+ try {
167
+ const configPath = path.resolve(configJson);
168
+ if (!fs.existsSync(configPath)) {
169
+ throw new ConfigurationError(
170
+ `--config-json value is not valid JSON and file not found: ${configJson}`,
171
+ 'configJson',
172
+ 'cli'
173
+ );
174
+ }
175
+ config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
176
+ } catch (error) {
177
+ if (error instanceof ConfigurationError) {
178
+ throw error;
179
+ }
180
+ throw new ConfigurationError(
181
+ `Failed to parse --config-json: ${error.message}`,
182
+ 'configJson',
183
+ 'cli'
184
+ );
185
+ }
186
+ }
187
+ this._applyJsonConfig(config);
188
+ }
189
+
190
+ /**
191
+ * Apply a parsed JSON config object, filtering to supported parameters.
192
+ * @param {Object} config - Parsed JSON config object
193
+ * @private
194
+ */
195
+ _applyJsonConfig(config) {
196
+ const filteredConfig = {};
197
+ Object.entries(config).forEach(([key, value]) => {
198
+ // Handle nested endpointConfig object
199
+ if (key === 'endpointConfig' && typeof value === 'object' && value !== null) {
200
+ const endpointMapping = {
201
+ initialInstanceCount: 'endpointInitialInstanceCount',
202
+ dataCapturePercent: 'endpointDataCapturePercent',
203
+ variantName: 'endpointVariantName',
204
+ volumeSize: 'endpointVolumeSize'
205
+ };
206
+ Object.entries(value).forEach(([nestedKey, nestedValue]) => {
207
+ const flatKey = endpointMapping[nestedKey];
208
+ if (flatKey && this.manager._isSourceSupported(flatKey, 'configFile')) {
209
+ filteredConfig[flatKey] = nestedValue;
210
+ this.manager._recordSource(flatKey, nestedValue, 'config-file');
211
+ }
212
+ });
213
+ return;
214
+ }
215
+
216
+ // Handle nested icConfig object
217
+ if (key === 'icConfig' && typeof value === 'object' && value !== null) {
218
+ const icMapping = {
219
+ cpuCount: 'icCpuCount',
220
+ memorySize: 'icMemorySize',
221
+ gpuCount: 'icGpuCount',
222
+ copyCount: 'icCopyCount',
223
+ modelWeight: 'icModelWeight'
224
+ };
225
+ Object.entries(value).forEach(([nestedKey, nestedValue]) => {
226
+ const flatKey = icMapping[nestedKey];
227
+ if (flatKey && this.manager._isSourceSupported(flatKey, 'configFile')) {
228
+ filteredConfig[flatKey] = nestedValue;
229
+ this.manager._recordSource(flatKey, nestedValue, 'config-file');
230
+ }
231
+ });
232
+ return;
233
+ }
234
+
235
+ // Handle modelEnvVars object
236
+ if (key === 'modelEnvVars' && typeof value === 'object' && value !== null) {
237
+ if (!this.manager.config.modelEnvVars) {
238
+ this.manager.config.modelEnvVars = {};
239
+ }
240
+ const cliModelEnvVars = (this.manager.explicitConfig && this.manager.explicitConfig.modelEnvVars) || {};
241
+ Object.entries(value).forEach(([envKey, envValue]) => {
242
+ if (!(envKey in cliModelEnvVars)) {
243
+ this.manager.config.modelEnvVars[envKey] = envValue;
244
+ this.manager._recordSource(`modelEnvVars.${envKey}`, envValue, 'config-file');
245
+ }
246
+ });
247
+ return;
248
+ }
249
+
250
+ // Handle serverEnvVars object
251
+ if (key === 'serverEnvVars' && typeof value === 'object' && value !== null) {
252
+ if (!this.manager.config.serverEnvVars) {
253
+ this.manager.config.serverEnvVars = {};
254
+ }
255
+ const cliServerEnvVars = (this.manager.explicitConfig && this.manager.explicitConfig.serverEnvVars) || {};
256
+ Object.entries(value).forEach(([envKey, envValue]) => {
257
+ if (!(envKey in cliServerEnvVars)) {
258
+ this.manager.config.serverEnvVars[envKey] = envValue;
259
+ this.manager._recordSource(`serverEnvVars.${envKey}`, envValue, 'config-file');
260
+ }
261
+ });
262
+ return;
263
+ }
264
+
265
+ if (this.manager._isSourceSupported(key, 'configFile')) {
266
+ filteredConfig[key] = this.manager._parseValue(key, value);
267
+ this.manager._recordSource(key, this.manager._parseValue(key, value), 'config-file');
268
+ }
269
+ });
270
+ this.manager._mergeConfig(filteredConfig);
271
+ }
272
+
273
+ /**
274
+ * Load from environment variables (filtered by matrix)
275
+ * @private
276
+ */
277
+ async _loadEnvironmentVariables() {
278
+ const envMapping = {};
279
+ Object.entries(this.manager.parameterMatrix).forEach(([param, config]) => {
280
+ if (config.envVar) {
281
+ envMapping[config.envVar] = { param, ambient: false };
282
+ }
283
+ // Also check ambient env vars (e.g., AWS_REGION, AWS_ROLE, HF_TOKEN)
284
+ if (config.ambientEnvVar) {
285
+ envMapping[config.ambientEnvVar] = { param, ambient: true };
286
+ }
287
+ });
288
+
289
+ Object.entries(envMapping).forEach(([envVar, { param: configKey, ambient }]) => {
290
+ const value = process.env[envVar];
291
+ if (value !== undefined && value !== '' && this.manager._isSourceSupported(configKey, 'envVar')) {
292
+ this.manager.config[configKey] = this.manager._parseValue(configKey, value);
293
+ this.manager._recordSource(configKey, this.manager._parseValue(configKey, value), 'env-var');
294
+ if (!ambient) {
295
+ if (!this.manager.explicitConfig) {
296
+ this.manager.explicitConfig = {};
297
+ }
298
+ this.manager.explicitConfig[configKey] = this.manager._parseValue(configKey, value);
299
+ }
300
+ }
301
+ });
302
+ }
303
+
304
+ /**
305
+ * Load from CLI arguments (positional)
306
+ * @private
307
+ */
308
+ async _loadCliArguments() {
309
+ if (this.manager.args && this.manager.args.length > 0) {
310
+ this.manager.config.projectName = this.manager.args[0];
311
+ if (!this.manager.explicitConfig) {
312
+ this.manager.explicitConfig = {};
313
+ }
314
+ this.manager.explicitConfig.projectName = this.manager.args[0];
315
+ this.manager.projectNameFromArgument = true;
316
+ }
317
+ }
318
+
319
+ /**
320
+ * Load from CLI options (highest precedence, filtered by matrix)
321
+ * @private
322
+ */
323
+ async _loadCliOptions() {
324
+ const options = this.manager.options;
325
+
326
+ Object.entries(this.manager.parameterMatrix).forEach(([param, config]) => {
327
+ if (config.cliOption && options[config.cliOption] !== undefined) {
328
+ this.manager.config[param] = this.manager._parseValue(param, options[config.cliOption]);
329
+ this.manager._recordSource(param, this.manager._parseValue(param, options[config.cliOption]), 'cli');
330
+ if (!this.manager.explicitConfig) {
331
+ this.manager.explicitConfig = {};
332
+ }
333
+ this.manager.explicitConfig[param] = this.manager._parseValue(param, options[config.cliOption]);
334
+ }
335
+ });
336
+
337
+ // Parse --model-env KEY=VALUE pairs
338
+ this._parseEnvVarOptions('model-env', 'modelEnvVars');
339
+
340
+ // Parse --server-env KEY=VALUE pairs
341
+ this._parseEnvVarOptions('server-env', 'serverEnvVars');
342
+ }
343
+
344
+ /**
345
+ * Normalizes deprecated parameter values to their canonical equivalents.
346
+ * @private
347
+ */
348
+ _normalizeDeprecatedValues() {
349
+ const DEPRECATED_VALUES = {
350
+ deploymentTarget: {
351
+ 'managed-inference': {
352
+ canonical: 'realtime-inference',
353
+ message: '--deployment-target=managed-inference is deprecated, use realtime-inference instead'
354
+ }
355
+ }
356
+ };
357
+
358
+ for (const [param, aliases] of Object.entries(DEPRECATED_VALUES)) {
359
+ const currentValue = this.manager.config[param];
360
+ if (currentValue && aliases[currentValue]) {
361
+ const { canonical, message } = aliases[currentValue];
362
+ console.log(`\n⚠️ Deprecation: ${message}`);
363
+ this.manager.config[param] = canonical;
364
+ if (this.manager.explicitConfig && this.manager.explicitConfig[param] === currentValue) {
365
+ this.manager.explicitConfig[param] = canonical;
366
+ }
367
+ }
368
+ }
369
+ }
370
+
371
+ /**
372
+ * Parse --model-env or --server-env CLI options into env var collections.
373
+ * @param {string} optionName - CLI option name
374
+ * @param {string} configKey - Config key to store results
375
+ * @private
376
+ */
377
+ _parseEnvVarOptions(optionName, configKey) {
378
+ const rawValue = this.manager.options[optionName];
379
+ if (rawValue === undefined || rawValue === null) {
380
+ return;
381
+ }
382
+
383
+ const values = Array.isArray(rawValue) ? rawValue : [rawValue];
384
+
385
+ if (!this.manager.config[configKey] || typeof this.manager.config[configKey] !== 'object') {
386
+ this.manager.config[configKey] = {};
387
+ }
388
+
389
+ for (const entry of values) {
390
+ if (typeof entry !== 'string' || entry.trim() === '') {
391
+ continue;
392
+ }
393
+ const { key, value } = parseKeyValue(entry);
394
+ this.manager.config[configKey][key] = value;
395
+ this.manager._recordSource(`${configKey}.${key}`, value, 'cli');
396
+ }
397
+
398
+ if (Object.keys(this.manager.config[configKey]).length > 0) {
399
+ if (!this.manager.explicitConfig) {
400
+ this.manager.explicitConfig = {};
401
+ }
402
+ this.manager.explicitConfig[configKey] = { ...this.manager.config[configKey] };
403
+ }
404
+ }
405
+ }