@aws/ml-container-creator 0.2.1 → 0.2.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 (36) hide show
  1. package/bin/cli.js +88 -86
  2. package/config/bootstrap-stack.json +211 -0
  3. package/config/parameter-schema.json +88 -0
  4. package/infra/ci-harness/bin/ci-harness.ts +26 -0
  5. package/infra/ci-harness/buildspec.yml +352 -0
  6. package/infra/ci-harness/cdk.json +27 -0
  7. package/infra/ci-harness/lambda/scanner/index.ts +199 -0
  8. package/infra/ci-harness/lib/ci-harness-stack.ts +609 -0
  9. package/infra/ci-harness/package-lock.json +3979 -0
  10. package/infra/ci-harness/package.json +32 -0
  11. package/infra/ci-harness/tsconfig.json +38 -0
  12. package/package.json +13 -3
  13. package/src/app.js +318 -318
  14. package/src/copy-tpl.js +19 -19
  15. package/src/lib/asset-manager.js +74 -74
  16. package/src/lib/aws-profile-parser.js +45 -45
  17. package/src/lib/bootstrap-command-handler.js +560 -547
  18. package/src/lib/bootstrap-config.js +45 -45
  19. package/src/lib/ci-register-helpers.js +19 -19
  20. package/src/lib/ci-report-helpers.js +37 -37
  21. package/src/lib/ci-stage-helpers.js +49 -49
  22. package/src/lib/comment-generator.js +4 -4
  23. package/src/lib/config-manager.js +105 -105
  24. package/src/lib/deployment-config-resolver.js +10 -10
  25. package/src/lib/deployment-registry.js +153 -153
  26. package/src/lib/engine-prefix-resolver.js +8 -8
  27. package/src/lib/key-value-parser.js +6 -6
  28. package/src/lib/manifest-cli.js +108 -108
  29. package/src/lib/prompt-runner.js +224 -224
  30. package/src/lib/prompts.js +121 -121
  31. package/src/lib/registry-command-handler.js +174 -174
  32. package/src/lib/registry-loader.js +52 -52
  33. package/src/lib/sensitive-redactor.js +9 -9
  34. package/src/lib/template-engine.js +1 -1
  35. package/src/lib/template-manager.js +62 -62
  36. package/src/prompt-adapter.js +18 -18
@@ -31,9 +31,9 @@
31
31
  * - ciTableName (string): Name of the DynamoDB CI table. Defaults to "mlcc-ci-table".
32
32
  */
33
33
 
34
- import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs'
35
- import { dirname, join } from 'node:path'
36
- import { homedir } from 'node:os'
34
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
35
+ import { dirname, join } from 'node:path';
36
+ import { homedir } from 'node:os';
37
37
 
38
38
  export default class BootstrapConfig {
39
39
  /**
@@ -41,7 +41,7 @@ export default class BootstrapConfig {
41
41
  * Defaults to ~/.ml-container-creator/config.json
42
42
  */
43
43
  constructor(configPath) {
44
- this.configPath = configPath || join(homedir(), '.ml-container-creator', 'config.json')
44
+ this.configPath = configPath || join(homedir(), '.ml-container-creator', 'config.json');
45
45
  }
46
46
 
47
47
  /**
@@ -51,11 +51,11 @@ export default class BootstrapConfig {
51
51
  */
52
52
  read() {
53
53
  if (!existsSync(this.configPath)) {
54
- return null
54
+ return null;
55
55
  }
56
56
 
57
- const raw = readFileSync(this.configPath, 'utf8')
58
- return JSON.parse(raw)
57
+ const raw = readFileSync(this.configPath, 'utf8');
58
+ return JSON.parse(raw);
59
59
  }
60
60
 
61
61
  /**
@@ -66,12 +66,12 @@ export default class BootstrapConfig {
66
66
  * @param {Object} config - The config object to write
67
67
  */
68
68
  write(config) {
69
- const dir = dirname(this.configPath)
69
+ const dir = dirname(this.configPath);
70
70
  if (!existsSync(dir)) {
71
- mkdirSync(dir, { recursive: true })
71
+ mkdirSync(dir, { recursive: true });
72
72
  }
73
73
 
74
- writeFileSync(this.configPath, JSON.stringify(config, null, 2) + '\n')
74
+ writeFileSync(this.configPath, `${JSON.stringify(config, null, 2) }\n`);
75
75
  }
76
76
 
77
77
  /**
@@ -80,7 +80,7 @@ export default class BootstrapConfig {
80
80
  * @returns {boolean} true if the config file exists
81
81
  */
82
82
  exists() {
83
- return existsSync(this.configPath)
83
+ return existsSync(this.configPath);
84
84
  }
85
85
 
86
86
  /**
@@ -89,17 +89,17 @@ export default class BootstrapConfig {
89
89
  * @returns {{ name: string, config: Object }|null} The active profile, or null if no active profile
90
90
  */
91
91
  getActiveProfile() {
92
- const config = this.read()
92
+ const config = this.read();
93
93
  if (!config || !config.activeProfile || !config.profiles) {
94
- return null
94
+ return null;
95
95
  }
96
96
 
97
- const profileConfig = config.profiles[config.activeProfile]
97
+ const profileConfig = config.profiles[config.activeProfile];
98
98
  if (!profileConfig) {
99
- return null
99
+ return null;
100
100
  }
101
101
 
102
- return { name: config.activeProfile, config: profileConfig }
102
+ return { name: config.activeProfile, config: profileConfig };
103
103
  }
104
104
 
105
105
  /**
@@ -109,12 +109,12 @@ export default class BootstrapConfig {
109
109
  * @returns {Object|null} The profile config object, or null if not found
110
110
  */
111
111
  getProfile(name) {
112
- const config = this.read()
112
+ const config = this.read();
113
113
  if (!config || !config.profiles) {
114
- return null
114
+ return null;
115
115
  }
116
116
 
117
- return config.profiles[name] || null
117
+ return config.profiles[name] || null;
118
118
  }
119
119
 
120
120
  /**
@@ -126,16 +126,16 @@ export default class BootstrapConfig {
126
126
  * @returns {Object|null} The profile config with CI defaults, or null if not found
127
127
  */
128
128
  getProfileWithDefaults(name) {
129
- const profile = this.getProfile(name)
129
+ const profile = this.getProfile(name);
130
130
  if (!profile) {
131
- return null
131
+ return null;
132
132
  }
133
133
 
134
134
  return {
135
135
  ciInfraProvisioned: false,
136
136
  ciTableName: 'mlcc-ci-table',
137
137
  ...profile
138
- }
138
+ };
139
139
  }
140
140
 
141
141
  /**
@@ -146,9 +146,9 @@ export default class BootstrapConfig {
146
146
  * @returns {{ name: string, config: Object }|null} The active profile with CI defaults, or null
147
147
  */
148
148
  getActiveProfileWithDefaults() {
149
- const active = this.getActiveProfile()
149
+ const active = this.getActiveProfile();
150
150
  if (!active) {
151
- return null
151
+ return null;
152
152
  }
153
153
 
154
154
  return {
@@ -158,7 +158,7 @@ export default class BootstrapConfig {
158
158
  ciTableName: 'mlcc-ci-table',
159
159
  ...active.config
160
160
  }
161
- }
161
+ };
162
162
  }
163
163
 
164
164
  /**
@@ -169,17 +169,17 @@ export default class BootstrapConfig {
169
169
  * @param {Object} profileData - The profile configuration data
170
170
  */
171
171
  setProfile(name, profileData) {
172
- let config = this.read()
172
+ let config = this.read();
173
173
  if (!config) {
174
- config = { activeProfile: null, profiles: {} }
174
+ config = { activeProfile: null, profiles: {} };
175
175
  }
176
176
  if (!config.profiles) {
177
- config.profiles = {}
177
+ config.profiles = {};
178
178
  }
179
179
 
180
- config.profiles[name] = profileData
181
- config.activeProfile = name
182
- this.write(config)
180
+ config.profiles[name] = profileData;
181
+ config.activeProfile = name;
182
+ this.write(config);
183
183
  }
184
184
 
185
185
  /**
@@ -191,20 +191,20 @@ export default class BootstrapConfig {
191
191
  * @returns {boolean} true if the profile was removed, false if not found
192
192
  */
193
193
  removeProfile(name) {
194
- const config = this.read()
194
+ const config = this.read();
195
195
  if (!config || !config.profiles || !config.profiles[name]) {
196
- return false
196
+ return false;
197
197
  }
198
198
 
199
- delete config.profiles[name]
199
+ delete config.profiles[name];
200
200
 
201
201
  if (config.activeProfile === name) {
202
- const remaining = Object.keys(config.profiles)
203
- config.activeProfile = remaining.length > 0 ? remaining[0] : null
202
+ const remaining = Object.keys(config.profiles);
203
+ config.activeProfile = remaining.length > 0 ? remaining[0] : null;
204
204
  }
205
205
 
206
- this.write(config)
207
- return true
206
+ this.write(config);
207
+ return true;
208
208
  }
209
209
 
210
210
  /**
@@ -213,12 +213,12 @@ export default class BootstrapConfig {
213
213
  * @returns {string[]} Array of profile name strings
214
214
  */
215
215
  listProfiles() {
216
- const config = this.read()
216
+ const config = this.read();
217
217
  if (!config || !config.profiles) {
218
- return []
218
+ return [];
219
219
  }
220
220
 
221
- return Object.keys(config.profiles)
221
+ return Object.keys(config.profiles);
222
222
  }
223
223
 
224
224
  /**
@@ -227,12 +227,12 @@ export default class BootstrapConfig {
227
227
  * @param {string} name - The profile name to set as active
228
228
  */
229
229
  setActiveProfile(name) {
230
- const config = this.read()
230
+ const config = this.read();
231
231
  if (!config) {
232
- return
232
+ return;
233
233
  }
234
234
 
235
- config.activeProfile = name
236
- this.write(config)
235
+ config.activeProfile = name;
236
+ this.write(config);
237
237
  }
238
238
  }
@@ -12,7 +12,7 @@
12
12
  * behavior without executing the bash template directly.
13
13
  */
14
14
 
15
- import { createHash } from 'node:crypto'
15
+ import { createHash } from 'node:crypto';
16
16
 
17
17
  /**
18
18
  * Compute a deterministic configId from canonical deployment fields.
@@ -29,9 +29,9 @@ import { createHash } from 'node:crypto'
29
29
  * @returns {string} 16-character lowercase hex string
30
30
  */
31
31
  export function computeConfigId(deploymentConfig, modelName, instanceType, region, deploymentTarget) {
32
- const input = `${deploymentConfig}:${modelName || 'none'}:${instanceType}:${region}:${deploymentTarget}`
33
- const hash = createHash('sha256').update(input).digest('hex')
34
- return hash.slice(0, 16)
32
+ const input = `${deploymentConfig}:${modelName || 'none'}:${instanceType}:${region}:${deploymentTarget}`;
33
+ const hash = createHash('sha256').update(input).digest('hex');
34
+ return hash.slice(0, 16);
35
35
  }
36
36
 
37
37
  /**
@@ -49,7 +49,7 @@ export function computeConfigId(deploymentConfig, modelName, instanceType, regio
49
49
  * @returns {object} DynamoDB item structure (plain JS object, not DynamoDB JSON)
50
50
  */
51
51
  export function buildCiRecord(configId, configJson, promotedAttrs) {
52
- const createdAt = new Date().toISOString().replace(/\.\d{3}Z$/, 'Z')
52
+ const createdAt = new Date().toISOString().replace(/\.\d{3}Z$/, 'Z');
53
53
  return {
54
54
  configId,
55
55
  schemaVersion: 1,
@@ -61,7 +61,7 @@ export function buildCiRecord(configId, configJson, promotedAttrs) {
61
61
  baseImageVersion: promotedAttrs.baseImageVersion || '',
62
62
  projectName: promotedAttrs.projectName || '',
63
63
  createdAt
64
- }
64
+ };
65
65
  }
66
66
 
67
67
  /**
@@ -75,36 +75,36 @@ export function buildCiRecord(configId, configJson, promotedAttrs) {
75
75
  */
76
76
  export function applyRecordDefaults(record) {
77
77
  if (record.schemaVersion === undefined || record.schemaVersion === null) {
78
- record.schemaVersion = 1
78
+ record.schemaVersion = 1;
79
79
  }
80
80
  if (!record.testStatus) {
81
- record.testStatus = 'untested'
81
+ record.testStatus = 'untested';
82
82
  }
83
83
  if (!record.lastTestTimestamp) {
84
- record.lastTestTimestamp = '1970-01-01T00:00:00Z'
84
+ record.lastTestTimestamp = '1970-01-01T00:00:00Z';
85
85
  }
86
86
  if (!record.buildStrategy) {
87
- record.buildStrategy = 'codebuild-submit'
87
+ record.buildStrategy = 'codebuild-submit';
88
88
  }
89
89
  if (!record.stageResults) {
90
- record.stageResults = {}
90
+ record.stageResults = {};
91
91
  }
92
92
  if (!record.errorMessage && record.errorMessage !== '') {
93
- record.errorMessage = ''
93
+ record.errorMessage = '';
94
94
  }
95
95
  if (!record.deploymentConfig) {
96
- record.deploymentConfig = ''
96
+ record.deploymentConfig = '';
97
97
  }
98
98
  if (!record.baseImage) {
99
- record.baseImage = ''
99
+ record.baseImage = '';
100
100
  }
101
101
  if (!record.baseImageVersion) {
102
- record.baseImageVersion = ''
102
+ record.baseImageVersion = '';
103
103
  }
104
104
  if (!record.projectName) {
105
- record.projectName = ''
105
+ record.projectName = '';
106
106
  }
107
- return record
107
+ return record;
108
108
  }
109
109
 
110
110
  /**
@@ -118,7 +118,7 @@ export function applyRecordDefaults(record) {
118
118
  */
119
119
  export function extractBaseImageVersion(baseImage) {
120
120
  if (!baseImage || !baseImage.includes(':')) {
121
- return ''
121
+ return '';
122
122
  }
123
- return baseImage.split(':').pop()
123
+ return baseImage.split(':').pop();
124
124
  }
@@ -32,7 +32,7 @@ export const KNOWN_DEPLOYMENT_CONFIGS = [
32
32
  'diffusors-vllm',
33
33
  'diffusors-sglang',
34
34
  'diffusors-comfyui'
35
- ]
35
+ ];
36
36
 
37
37
  /**
38
38
  * Group an array of CI records by their deploymentConfig field.
@@ -41,15 +41,15 @@ export const KNOWN_DEPLOYMENT_CONFIGS = [
41
41
  * @returns {Map<string, object[]>} Map from deploymentConfig to array of records
42
42
  */
43
43
  export function groupByDeploymentConfig(records) {
44
- const groups = new Map()
44
+ const groups = new Map();
45
45
  for (const record of records) {
46
- const key = record.deploymentConfig || ''
46
+ const key = record.deploymentConfig || '';
47
47
  if (!groups.has(key)) {
48
- groups.set(key, [])
48
+ groups.set(key, []);
49
49
  }
50
- groups.get(key).push(record)
50
+ groups.get(key).push(record);
51
51
  }
52
- return groups
52
+ return groups;
53
53
  }
54
54
 
55
55
  /**
@@ -64,10 +64,10 @@ export function groupByDeploymentConfig(records) {
64
64
  */
65
65
  export function detectRegressions(records) {
66
66
  return records.filter(record => {
67
- const current = record.testStatus || ''
68
- const previous = record.previousTestStatus || ''
69
- return current.startsWith('fail-') && previous === 'pass'
70
- })
67
+ const current = record.testStatus || '';
68
+ const previous = record.previousTestStatus || '';
69
+ return current.startsWith('fail-') && previous === 'pass';
70
+ });
71
71
  }
72
72
 
73
73
  /**
@@ -87,41 +87,41 @@ export function detectRegressions(records) {
87
87
  * @returns {string[]} return.untestedConfigs - Known configs with no CI_Record
88
88
  */
89
89
  export function computeCoverageReport(records, knownConfigs) {
90
- const grouped = groupByDeploymentConfig(records)
90
+ const grouped = groupByDeploymentConfig(records);
91
91
 
92
92
  // Determine which known configs have been tested
93
- const testedConfigSet = new Set()
94
- const passingSet = new Set()
95
- const failingSet = new Set()
93
+ const testedConfigSet = new Set();
94
+ const passingSet = new Set();
95
+ const failingSet = new Set();
96
96
 
97
- const configurations = []
97
+ const configurations = [];
98
98
 
99
99
  for (const config of knownConfigs) {
100
- const configRecords = grouped.get(config) || []
100
+ const configRecords = grouped.get(config) || [];
101
101
  if (configRecords.length === 0) {
102
102
  configurations.push({
103
103
  deploymentConfig: config,
104
104
  status: 'untested',
105
105
  recordCount: 0
106
- })
107
- continue
106
+ });
107
+ continue;
108
108
  }
109
109
 
110
- testedConfigSet.add(config)
110
+ testedConfigSet.add(config);
111
111
 
112
112
  // Use the most recent record (by lastTestTimestamp) to determine status
113
113
  const sorted = [...configRecords].sort((a, b) => {
114
- const tsA = a.lastTestTimestamp || '1970-01-01T00:00:00Z'
115
- const tsB = b.lastTestTimestamp || '1970-01-01T00:00:00Z'
116
- return tsB.localeCompare(tsA)
117
- })
118
- const latest = sorted[0]
119
- const status = latest.testStatus || 'untested'
114
+ const tsA = a.lastTestTimestamp || '1970-01-01T00:00:00Z';
115
+ const tsB = b.lastTestTimestamp || '1970-01-01T00:00:00Z';
116
+ return tsB.localeCompare(tsA);
117
+ });
118
+ const latest = sorted[0];
119
+ const status = latest.testStatus || 'untested';
120
120
 
121
121
  if (status === 'pass') {
122
- passingSet.add(config)
122
+ passingSet.add(config);
123
123
  } else if (status.startsWith('fail-')) {
124
- failingSet.add(config)
124
+ failingSet.add(config);
125
125
  }
126
126
 
127
127
  configurations.push({
@@ -129,20 +129,20 @@ export function computeCoverageReport(records, knownConfigs) {
129
129
  status,
130
130
  recordCount: configRecords.length,
131
131
  latestRecord: latest
132
- })
132
+ });
133
133
  }
134
134
 
135
- const total = knownConfigs.length
136
- const tested = testedConfigSet.size
137
- const untested = total - tested
138
- const passing = passingSet.size
139
- const failing = failingSet.size
135
+ const total = knownConfigs.length;
136
+ const tested = testedConfigSet.size;
137
+ const untested = total - tested;
138
+ const passing = passingSet.size;
139
+ const failing = failingSet.size;
140
140
  const coveragePercent = total > 0
141
141
  ? Math.round((tested / total) * 1000) / 10
142
- : 0
142
+ : 0;
143
143
 
144
- const untestedConfigs = knownConfigs.filter(c => !testedConfigSet.has(c))
145
- const regressions = detectRegressions(records)
144
+ const untestedConfigs = knownConfigs.filter(c => !testedConfigSet.has(c));
145
+ const regressions = detectRegressions(records);
146
146
 
147
147
  return {
148
148
  total,
@@ -154,5 +154,5 @@ export function computeCoverageReport(records, knownConfigs) {
154
154
  configurations,
155
155
  regressions,
156
156
  untestedConfigs
157
- }
157
+ };
158
158
  }