@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
package/src/app.js CHANGED
@@ -1,28 +1,28 @@
1
1
  // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
2
  // SPDX-License-Identifier: Apache-2.0
3
3
 
4
- import fs from 'fs'
5
- import path from 'path'
6
- import { fileURLToPath } from 'url'
7
- import { spawn } from 'child_process'
8
-
9
- import { copyTpl } from './copy-tpl.js'
10
- import { runPrompts } from './prompt-adapter.js'
11
- import ConfigManager from './lib/config-manager.js'
12
- import PromptRunner from './lib/prompt-runner.js'
13
- import TemplateManager from './lib/template-manager.js'
14
- import DeploymentConfigResolver from './lib/deployment-config-resolver.js'
15
- import CommentGenerator from './lib/comment-generator.js'
16
- import ConfigurationManager from './lib/configuration-manager.js'
17
- import RegistryLoader from './lib/registry-loader.js'
18
- import { resolvePrefixedEnvVars } from './lib/engine-prefix-resolver.js'
19
- import ejs from 'ejs'
20
-
21
- const __filename = fileURLToPath(import.meta.url)
22
- const __dirname = path.dirname(__filename)
23
- const GENERATOR_ROOT = path.resolve(__dirname, '..')
24
- const TEMPLATE_DIR = path.join(GENERATOR_ROOT, 'templates')
25
- const LIB_DIR = path.join(GENERATOR_ROOT, 'src', 'lib')
4
+ import fs from 'fs';
5
+ import path from 'path';
6
+ import { fileURLToPath } from 'url';
7
+ import { spawn } from 'child_process';
8
+
9
+ import { copyTpl } from './copy-tpl.js';
10
+ import { runPrompts } from './prompt-adapter.js';
11
+ import ConfigManager from './lib/config-manager.js';
12
+ import PromptRunner from './lib/prompt-runner.js';
13
+ import TemplateManager from './lib/template-manager.js';
14
+ import DeploymentConfigResolver from './lib/deployment-config-resolver.js';
15
+ import CommentGenerator from './lib/comment-generator.js';
16
+ import ConfigurationManager from './lib/configuration-manager.js';
17
+ import RegistryLoader from './lib/registry-loader.js';
18
+ import { resolvePrefixedEnvVars } from './lib/engine-prefix-resolver.js';
19
+ import ejs from 'ejs';
20
+
21
+ const __filename = fileURLToPath(import.meta.url);
22
+ const __dirname = path.dirname(__filename);
23
+ const GENERATOR_ROOT = path.resolve(__dirname, '..');
24
+ const TEMPLATE_DIR = path.join(GENERATOR_ROOT, 'templates');
25
+ const LIB_DIR = path.join(GENERATOR_ROOT, 'src', 'lib');
26
26
 
27
27
  /**
28
28
  * Main application entry point.
@@ -37,41 +37,41 @@ export async function run(projectName, options) {
37
37
  // --- Phase: Initializing ---
38
38
  // Convert commander's camelCase options to kebab-case for ConfigManager compatibility
39
39
  // (ConfigManager expects kebab-case format for option keys)
40
- const kebabOptions = _toKebabCaseOptions(options)
40
+ const kebabOptions = _toKebabCaseOptions(options);
41
41
 
42
42
  // Build a lightweight adapter that satisfies ConfigManager's generator interface
43
- const generatorAdapter = _createGeneratorAdapter(projectName, kebabOptions)
44
- const args = projectName ? [projectName] : []
43
+ const generatorAdapter = _createGeneratorAdapter(projectName, kebabOptions);
44
+ const args = projectName ? [projectName] : [];
45
45
 
46
- const configManager = new ConfigManager({ options: kebabOptions, args })
46
+ const configManager = new ConfigManager({ options: kebabOptions, args });
47
47
 
48
- let baseConfig
48
+ let baseConfig;
49
49
  try {
50
- baseConfig = await configManager.loadConfiguration()
50
+ baseConfig = await configManager.loadConfiguration();
51
51
  } catch (error) {
52
- console.log(`⚠️ ${error.message}`)
53
- return
52
+ console.log(`⚠️ ${error.message}`);
53
+ return;
54
54
  }
55
55
 
56
- const errors = configManager.validateConfiguration()
56
+ const errors = configManager.validateConfiguration();
57
57
  if (errors.length > 0) {
58
- console.log(`⚠️ ${errors[0]}`)
59
- return
58
+ console.log(`⚠️ ${errors[0]}`);
59
+ return;
60
60
  }
61
61
 
62
62
  // Initialize registry system
63
- let registryConfigManager = null
64
- let tritonBackends = {}
63
+ let registryConfigManager = null;
64
+ let tritonBackends = {};
65
65
  try {
66
- const validateEnvVars = kebabOptions['validate-env-vars'] !== false
67
- const validateWithDocker = kebabOptions['validate-with-docker'] === true
68
- const offline = kebabOptions['offline'] === true
66
+ const validateEnvVars = kebabOptions['validate-env-vars'] !== false;
67
+ const validateWithDocker = kebabOptions['validate-with-docker'] === true;
68
+ const offline = kebabOptions['offline'] === true;
69
69
 
70
- let effectiveValidateWithDocker = validateWithDocker
70
+ let effectiveValidateWithDocker = validateWithDocker;
71
71
  if (validateWithDocker && !validateEnvVars) {
72
- console.log('\n⚠️ Warning: --validate-with-docker requires --validate-env-vars to be enabled')
73
- console.log(' Docker validation will be disabled')
74
- effectiveValidateWithDocker = false
72
+ console.log('\n⚠️ Warning: --validate-with-docker requires --validate-env-vars to be enabled');
73
+ console.log(' Docker validation will be disabled');
74
+ effectiveValidateWithDocker = false;
75
75
  }
76
76
 
77
77
  registryConfigManager = new ConfigurationManager({
@@ -79,82 +79,82 @@ export async function run(projectName, options) {
79
79
  validateWithDocker: effectiveValidateWithDocker,
80
80
  offline,
81
81
  hfTimeout: 5000
82
- })
82
+ });
83
83
 
84
- await registryConfigManager.loadRegistries()
84
+ await registryConfigManager.loadRegistries();
85
85
 
86
- const registryLoader = new RegistryLoader()
87
- tritonBackends = await registryLoader.loadTritonBackends()
86
+ const registryLoader = new RegistryLoader();
87
+ tritonBackends = await registryLoader.loadTritonBackends();
88
88
 
89
- console.log('\n📚 Registry System Initialized')
90
- console.log(' • Framework Registry: Loaded')
91
- console.log(' • Model Registry: Loaded')
92
- console.log(' • Instance Accelerator Mapping: Loaded')
89
+ console.log('\n📚 Registry System Initialized');
90
+ console.log(' • Framework Registry: Loaded');
91
+ console.log(' • Model Registry: Loaded');
92
+ console.log(' • Instance Accelerator Mapping: Loaded');
93
93
 
94
94
  if (validateEnvVars) {
95
- console.log(' • Environment Variable Validation: Enabled')
95
+ console.log(' • Environment Variable Validation: Enabled');
96
96
  if (effectiveValidateWithDocker) {
97
- console.log(' • Docker Introspection Validation: Enabled (experimental)')
97
+ console.log(' • Docker Introspection Validation: Enabled (experimental)');
98
98
  }
99
99
  } else {
100
- console.log(' • Environment Variable Validation: Disabled')
100
+ console.log(' • Environment Variable Validation: Disabled');
101
101
  }
102
102
 
103
103
  if (offline) {
104
- console.log(' • HuggingFace API: Offline mode')
104
+ console.log(' • HuggingFace API: Offline mode');
105
105
  }
106
106
  } catch (error) {
107
- console.log('\n⚠️ Registry system initialization failed, using defaults')
108
- console.log(` Error: ${error.message}`)
109
- registryConfigManager = null
110
- tritonBackends = {}
107
+ console.log('\n⚠️ Registry system initialization failed, using defaults');
108
+ console.log(` Error: ${error.message}`);
109
+ registryConfigManager = null;
110
+ tritonBackends = {};
111
111
  }
112
112
 
113
113
  // Attach registry info to the adapter so PromptRunner can access it
114
- generatorAdapter.registryConfigManager = registryConfigManager
115
- generatorAdapter.configManager = configManager
116
- generatorAdapter.baseConfig = baseConfig
114
+ generatorAdapter.registryConfigManager = registryConfigManager;
115
+ generatorAdapter.configManager = configManager;
116
+ generatorAdapter.baseConfig = baseConfig;
117
117
 
118
118
  // --- Phase: Prompting ---
119
- let answers
119
+ let answers;
120
120
  if (configManager.shouldSkipPrompts()) {
121
- console.log('\n🚀 Skipping prompts - using configuration from other sources')
122
- answers = configManager.getFinalConfiguration()
121
+ console.log('\n🚀 Skipping prompts - using configuration from other sources');
122
+ answers = configManager.getFinalConfiguration();
123
123
 
124
124
  // Infer modelSource from model name prefix if not set
125
- const modelName = answers.modelName
125
+ const modelName = answers.modelName;
126
126
  if (!answers.modelSource && modelName) {
127
127
  if (modelName.startsWith('s3://')) {
128
- answers.modelSource = 's3'
128
+ answers.modelSource = 's3';
129
129
  if (!answers.artifactUri) {
130
- answers.artifactUri = modelName
130
+ answers.artifactUri = modelName;
131
131
  }
132
132
  } else if (modelName.startsWith('jumpstart://')) {
133
- answers.modelSource = 'jumpstart'
133
+ answers.modelSource = 'jumpstart';
134
134
  } else if (modelName.startsWith('jumpstart-hub://')) {
135
- answers.modelSource = 'jumpstart-hub'
135
+ answers.modelSource = 'jumpstart-hub';
136
136
  } else if (modelName.startsWith('registry://')) {
137
- answers.modelSource = 'registry'
137
+ answers.modelSource = 'registry';
138
138
  }
139
139
  }
140
140
 
141
141
  // Warn about unsupported model sources
142
142
  if (answers.modelSource === 'jumpstart-hub') {
143
- console.log('\n ⚠️ JumpStart Private Hub models are not yet fully supported.')
144
- console.log(' The generated project will not be able to download model artifacts at runtime.')
145
- console.log(' This feature is tracked for a future release.')
146
- console.log(' Falling back to HuggingFace source.\n')
147
- answers.modelSource = 'huggingface'
148
- delete answers.artifactUri
143
+ console.log('\n ⚠️ JumpStart Private Hub models are not yet fully supported.');
144
+ console.log(' The generated project will not be able to download model artifacts at runtime.');
145
+ console.log(' This feature is tracked for a future release.');
146
+ console.log(' Falling back to HuggingFace source.\n');
147
+ answers.modelSource = 'huggingface';
148
+ delete answers.artifactUri;
149
149
  }
150
150
 
151
151
  // Note about registry model requirements
152
152
  if (answers.modelSource === 'registry') {
153
- console.log('\n ℹ️ Registry model: the container will resolve the artifact URI at startup')
154
- console.log(' via DescribeModelPackage. Ensure the model package has a valid')
155
- console.log(' InferenceSpecification with ModelDataUrl or S3DataSource.')
156
- console.log(' If your model package lacks an InferenceSpecification, use the S3 path')
157
- console.log(' directly instead: --model-name="s3://bucket/path/model.tar.gz"\n')
153
+ console.log('\n ℹ️ Registry model: the container will resolve the artifact URI at startup');
154
+ console.log(' via DescribeModelPackage. Ensure the model package has a valid');
155
+ console.log(' InferenceSpecification with ModelDataUrl or S3DataSource.');
156
+ console.log(' If your model package lacks an InferenceSpecification, use the S3 path');
157
+ console.log(' directly instead: --model-name="s3://bucket/path/model.tar.gz"\n');
158
158
  }
159
159
  } else {
160
160
  const promptRunner = new PromptRunner({
@@ -162,25 +162,25 @@ export async function run(projectName, options) {
162
162
  options: kebabOptions,
163
163
  registryConfigManager,
164
164
  baseConfig
165
- })
166
- const promptAnswers = await promptRunner.run()
167
- answers = configManager.getFinalConfiguration(promptAnswers)
165
+ });
166
+ const promptAnswers = await promptRunner.run();
167
+ answers = configManager.getFinalConfiguration(promptAnswers);
168
168
  }
169
169
 
170
170
  // Ensure template variables have defaults and enrich with registry data
171
- await _ensureTemplateVariables(answers, registryConfigManager)
171
+ await _ensureTemplateVariables(answers, registryConfigManager);
172
172
 
173
173
  // --- Phase: Writing ---
174
- const destDir = path.resolve(answers.destinationDir)
175
- fs.mkdirSync(destDir, { recursive: true })
174
+ const destDir = path.resolve(answers.destinationDir);
175
+ fs.mkdirSync(destDir, { recursive: true });
176
176
 
177
- await writeProject(TEMPLATE_DIR, destDir, answers, registryConfigManager, tritonBackends, configManager)
177
+ await writeProject(TEMPLATE_DIR, destDir, answers, registryConfigManager, tritonBackends, configManager);
178
178
 
179
179
  // --- Phase: End ---
180
- await postGenerate(destDir, answers, tritonBackends)
180
+ await postGenerate(destDir, answers, tritonBackends);
181
181
 
182
- console.log('\n✅ Project generated successfully!')
183
- console.log(` 📁 ${destDir}`)
182
+ console.log('\n✅ Project generated successfully!');
183
+ console.log(` 📁 ${destDir}`);
184
184
  }
185
185
 
186
186
  /**
@@ -196,46 +196,46 @@ export async function run(projectName, options) {
196
196
  export async function writeProject(templateDir, destDir, answers, registryConfigManager = null, tritonBackends = {}, configManager = null) {
197
197
  // Validate required parameters via ConfigManager
198
198
  if (configManager) {
199
- const requiredParamErrors = configManager.validateRequiredParameters(answers)
199
+ const requiredParamErrors = configManager.validateRequiredParameters(answers);
200
200
  if (requiredParamErrors.length > 0) {
201
- console.log('\n❌ Required Parameter Validation Failed:')
201
+ console.log('\n❌ Required Parameter Validation Failed:');
202
202
  requiredParamErrors.forEach(error => {
203
- console.log(` • ${error}`)
204
- })
205
- console.log('\nPlease provide the missing required parameters and try again.')
206
- throw new Error('Required parameters are missing. Cannot proceed with file generation.')
203
+ console.log(` • ${error}`);
204
+ });
205
+ console.log('\nPlease provide the missing required parameters and try again.');
206
+ throw new Error('Required parameters are missing. Cannot proceed with file generation.');
207
207
  }
208
208
  }
209
209
 
210
210
  // Validate environment variables if registry system is available
211
211
  if (registryConfigManager && (answers.frameworkVersion || answers.architecture === 'triton')) {
212
- await _validateEnvironmentVariables(answers, registryConfigManager)
212
+ await _validateEnvironmentVariables(answers, registryConfigManager);
213
213
  }
214
214
 
215
215
  // Validate template configuration
216
- const templateManager = new TemplateManager(answers)
217
- templateManager.validate()
216
+ const templateManager = new TemplateManager(answers);
217
+ templateManager.validate();
218
218
 
219
219
  // Generate comments for templates
220
- const commentGenerator = new CommentGenerator()
221
- const comments = commentGenerator.generateDockerfileComments(answers)
220
+ const commentGenerator = new CommentGenerator();
221
+ const comments = commentGenerator.generateDockerfileComments(answers);
222
222
 
223
223
  // Prepare ordered environment variables
224
- const orderedEnvVars = _getOrderedEnvVars(answers.envVars || {})
224
+ const orderedEnvVars = _getOrderedEnvVars(answers.envVars || {});
225
225
 
226
226
  // Append model env vars and prefixed server env vars
227
- const modelEnvVars = answers.modelEnvVars || {}
228
- const serverEnvVars = answers.serverEnvVars || {}
229
- const engine = answers.modelServer || answers.backend || ''
227
+ const modelEnvVars = answers.modelEnvVars || {};
228
+ const serverEnvVars = answers.serverEnvVars || {};
229
+ const engine = answers.modelServer || answers.backend || '';
230
230
 
231
231
  Object.entries(modelEnvVars).forEach(([key, value]) => {
232
- orderedEnvVars.push({ key, value })
233
- })
232
+ orderedEnvVars.push({ key, value });
233
+ });
234
234
 
235
- const prefixedServerEnvVars = resolvePrefixedEnvVars(engine, serverEnvVars)
235
+ const prefixedServerEnvVars = resolvePrefixedEnvVars(engine, serverEnvVars);
236
236
  Object.entries(prefixedServerEnvVars).forEach(([key, value]) => {
237
- orderedEnvVars.push({ key, value })
238
- })
237
+ orderedEnvVars.push({ key, value });
238
+ });
239
239
 
240
240
  // Prepare template variables
241
241
  const templateVars = {
@@ -243,130 +243,130 @@ export async function writeProject(templateDir, destDir, answers, registryConfig
243
243
  comments,
244
244
  orderedEnvVars,
245
245
  serverEnvVars: prefixedServerEnvVars
246
- }
246
+ };
247
247
 
248
248
  // Build ignore patterns
249
- const ignorePatterns = []
249
+ const ignorePatterns = [];
250
250
 
251
251
  if (answers.deploymentTarget !== 'hyperpod-eks') {
252
- ignorePatterns.push('**/hyperpod/**')
252
+ ignorePatterns.push('**/hyperpod/**');
253
253
  }
254
254
 
255
255
  // Resolve architecture
256
- const resolver = new DeploymentConfigResolver()
257
- let architecture = answers.architecture
256
+ const resolver = new DeploymentConfigResolver();
257
+ let architecture = answers.architecture;
258
258
 
259
259
  if (!architecture && answers.deploymentConfig) {
260
260
  try {
261
- const parts = resolver.decompose(answers.deploymentConfig)
262
- architecture = parts.architecture
261
+ const parts = resolver.decompose(answers.deploymentConfig);
262
+ architecture = parts.architecture;
263
263
  } catch (e) {
264
- architecture = answers.framework === 'transformers' ? 'transformers' : 'http'
264
+ architecture = answers.framework === 'transformers' ? 'transformers' : 'http';
265
265
  }
266
266
  } else if (!architecture) {
267
- architecture = answers.framework === 'transformers' ? 'transformers' : 'http'
267
+ architecture = answers.framework === 'transformers' ? 'transformers' : 'http';
268
268
  }
269
269
 
270
270
  // Exclude sample_model when not needed
271
271
  if (!answers.includeSampleModel || architecture === 'transformers' || architecture === 'diffusors') {
272
- ignorePatterns.push('**/sample_model/**')
272
+ ignorePatterns.push('**/sample_model/**');
273
273
  }
274
274
 
275
275
  // Always exclude triton and diffusors source directories
276
- ignorePatterns.push('**/triton/**')
277
- ignorePatterns.push('**/diffusors/**')
276
+ ignorePatterns.push('**/triton/**');
277
+ ignorePatterns.push('**/diffusors/**');
278
278
 
279
279
  // For triton and diffusors, exclude the default Dockerfile
280
280
  if (architecture === 'triton' || architecture === 'diffusors') {
281
- ignorePatterns.push('**/Dockerfile')
281
+ ignorePatterns.push('**/Dockerfile');
282
282
  }
283
283
 
284
284
  // Copy all templates with EJS rendering
285
- copyTpl(templateDir, destDir, templateVars, ignorePatterns)
285
+ copyTpl(templateDir, destDir, templateVars, ignorePatterns);
286
286
 
287
287
  // Architecture-specific file routing (delete files that don't belong)
288
288
  switch (architecture) {
289
289
  case 'http':
290
- _unlinkIfExists(path.join(destDir, 'code/chat_template.jinja'))
291
- _unlinkIfExists(path.join(destDir, 'code/serve'))
292
- _unlinkIfExists(path.join(destDir, 'code/serving.properties'))
293
- _unlinkIfExists(path.join(destDir, 'code/start_server.sh'))
290
+ _unlinkIfExists(path.join(destDir, 'code/chat_template.jinja'));
291
+ _unlinkIfExists(path.join(destDir, 'code/serve'));
292
+ _unlinkIfExists(path.join(destDir, 'code/serving.properties'));
293
+ _unlinkIfExists(path.join(destDir, 'code/start_server.sh'));
294
294
 
295
295
  if (answers.modelServer !== 'flask' && answers.backend !== 'flask') {
296
- _unlinkIfExists(path.join(destDir, 'code/flask/wsgi.py'))
297
- _unlinkIfExists(path.join(destDir, 'code/flask/gunicorn_config.py'))
296
+ _unlinkIfExists(path.join(destDir, 'code/flask/wsgi.py'));
297
+ _unlinkIfExists(path.join(destDir, 'code/flask/gunicorn_config.py'));
298
298
  }
299
- break
299
+ break;
300
300
 
301
301
  case 'transformers':
302
- _unlinkIfExists(path.join(destDir, 'code/model_handler.py'))
303
- _unlinkIfExists(path.join(destDir, 'code/serve.py'))
304
- _unlinkIfExists(path.join(destDir, 'code/start_server.py'))
305
- _unlinkIfExists(path.join(destDir, 'nginx-predictors.conf'))
306
- _unlinkIfExists(path.join(destDir, 'code/flask/wsgi.py'))
307
- _unlinkIfExists(path.join(destDir, 'code/flask/gunicorn_config.py'))
308
- break
302
+ _unlinkIfExists(path.join(destDir, 'code/model_handler.py'));
303
+ _unlinkIfExists(path.join(destDir, 'code/serve.py'));
304
+ _unlinkIfExists(path.join(destDir, 'code/start_server.py'));
305
+ _unlinkIfExists(path.join(destDir, 'nginx-predictors.conf'));
306
+ _unlinkIfExists(path.join(destDir, 'code/flask/wsgi.py'));
307
+ _unlinkIfExists(path.join(destDir, 'code/flask/gunicorn_config.py'));
308
+ break;
309
309
 
310
310
  case 'triton':
311
- _unlinkIfExists(path.join(destDir, 'code/serve.py'))
312
- _unlinkIfExists(path.join(destDir, 'code/model_handler.py'))
313
- _unlinkIfExists(path.join(destDir, 'code/start_server.py'))
314
- _unlinkIfExists(path.join(destDir, 'nginx-predictors.conf'))
315
- _unlinkIfExists(path.join(destDir, 'code/flask/wsgi.py'))
316
- _unlinkIfExists(path.join(destDir, 'code/flask/gunicorn_config.py'))
317
- _unlinkIfExists(path.join(destDir, 'code/chat_template.jinja'))
318
- _unlinkIfExists(path.join(destDir, 'code/serve'))
319
- _unlinkIfExists(path.join(destDir, 'code/serving.properties'))
320
- _unlinkIfExists(path.join(destDir, 'code/start_server.sh'))
311
+ _unlinkIfExists(path.join(destDir, 'code/serve.py'));
312
+ _unlinkIfExists(path.join(destDir, 'code/model_handler.py'));
313
+ _unlinkIfExists(path.join(destDir, 'code/start_server.py'));
314
+ _unlinkIfExists(path.join(destDir, 'nginx-predictors.conf'));
315
+ _unlinkIfExists(path.join(destDir, 'code/flask/wsgi.py'));
316
+ _unlinkIfExists(path.join(destDir, 'code/flask/gunicorn_config.py'));
317
+ _unlinkIfExists(path.join(destDir, 'code/chat_template.jinja'));
318
+ _unlinkIfExists(path.join(destDir, 'code/serve'));
319
+ _unlinkIfExists(path.join(destDir, 'code/serving.properties'));
320
+ _unlinkIfExists(path.join(destDir, 'code/start_server.sh'));
321
321
 
322
322
  // Generate Triton-specific files
323
- _generateTritonFiles(templateDir, destDir, templateVars, answers, tritonBackends)
324
- break
323
+ _generateTritonFiles(templateDir, destDir, templateVars, answers, tritonBackends);
324
+ break;
325
325
 
326
326
  case 'diffusors':
327
- _unlinkIfExists(path.join(destDir, 'code/model_handler.py'))
328
- _unlinkIfExists(path.join(destDir, 'code/serve.py'))
329
- _unlinkIfExists(path.join(destDir, 'code/start_server.py'))
330
- _unlinkIfExists(path.join(destDir, 'nginx-predictors.conf'))
331
- _unlinkIfExists(path.join(destDir, 'code/flask/wsgi.py'))
332
- _unlinkIfExists(path.join(destDir, 'code/flask/gunicorn_config.py'))
333
- _unlinkIfExists(path.join(destDir, 'code/chat_template.jinja'))
334
- _unlinkIfExists(path.join(destDir, 'code/serving.properties'))
327
+ _unlinkIfExists(path.join(destDir, 'code/model_handler.py'));
328
+ _unlinkIfExists(path.join(destDir, 'code/serve.py'));
329
+ _unlinkIfExists(path.join(destDir, 'code/start_server.py'));
330
+ _unlinkIfExists(path.join(destDir, 'nginx-predictors.conf'));
331
+ _unlinkIfExists(path.join(destDir, 'code/flask/wsgi.py'));
332
+ _unlinkIfExists(path.join(destDir, 'code/flask/gunicorn_config.py'));
333
+ _unlinkIfExists(path.join(destDir, 'code/chat_template.jinja'));
334
+ _unlinkIfExists(path.join(destDir, 'code/serving.properties'));
335
335
 
336
336
  // Copy diffusors-specific templates
337
- _renderTemplate(path.join(templateDir, 'diffusors/Dockerfile'), path.join(destDir, 'Dockerfile'), templateVars)
338
- _renderTemplate(path.join(templateDir, 'diffusors/serve'), path.join(destDir, 'code/serve'), templateVars)
339
- _renderTemplate(path.join(templateDir, 'diffusors/start_server.sh'), path.join(destDir, 'code/start_server.sh'), templateVars)
340
- _copyFile(path.join(templateDir, 'diffusors/patch_image_api.py'), path.join(destDir, 'code/patch_image_api.py'))
341
- break
337
+ _renderTemplate(path.join(templateDir, 'diffusors/Dockerfile'), path.join(destDir, 'Dockerfile'), templateVars);
338
+ _renderTemplate(path.join(templateDir, 'diffusors/serve'), path.join(destDir, 'code/serve'), templateVars);
339
+ _renderTemplate(path.join(templateDir, 'diffusors/start_server.sh'), path.join(destDir, 'code/start_server.sh'), templateVars);
340
+ _copyFile(path.join(templateDir, 'diffusors/patch_image_api.py'), path.join(destDir, 'code/patch_image_api.py'));
341
+ break;
342
342
 
343
343
  default:
344
344
  // Fallback to HTTP behavior
345
- _unlinkIfExists(path.join(destDir, 'code/chat_template.jinja'))
346
- _unlinkIfExists(path.join(destDir, 'code/serve'))
347
- _unlinkIfExists(path.join(destDir, 'code/serving.properties'))
348
- _unlinkIfExists(path.join(destDir, 'code/start_server.sh'))
345
+ _unlinkIfExists(path.join(destDir, 'code/chat_template.jinja'));
346
+ _unlinkIfExists(path.join(destDir, 'code/serve'));
347
+ _unlinkIfExists(path.join(destDir, 'code/serving.properties'));
348
+ _unlinkIfExists(path.join(destDir, 'code/start_server.sh'));
349
349
  }
350
350
 
351
351
  // nginx-tensorrt.conf: only needed for TensorRT-LLM
352
352
  if (answers.modelServer !== 'tensorrt-llm' && answers.backend !== 'tensorrt-llm') {
353
- _unlinkIfExists(path.join(destDir, 'nginx-tensorrt.conf'))
353
+ _unlinkIfExists(path.join(destDir, 'nginx-tensorrt.conf'));
354
354
  }
355
355
 
356
356
  // nginx-diffusors.conf: only needed for diffusors architecture
357
357
  if (answers.architecture !== 'diffusors') {
358
- _unlinkIfExists(path.join(destDir, 'nginx-diffusors.conf'))
358
+ _unlinkIfExists(path.join(destDir, 'nginx-diffusors.conf'));
359
359
  }
360
360
 
361
361
  // Copy PROJECT_README.md as README.md (overwriting the template README)
362
- _renderTemplate(path.join(templateDir, 'PROJECT_README.md'), path.join(destDir, 'README.md'), templateVars)
362
+ _renderTemplate(path.join(templateDir, 'PROJECT_README.md'), path.join(destDir, 'README.md'), templateVars);
363
363
 
364
364
  // Copy do/lib/ Node.js modules (plain copy, no EJS)
365
- const doLibDir = path.join(destDir, 'do', 'lib')
366
- fs.mkdirSync(doLibDir, { recursive: true })
367
- _copyFile(path.join(LIB_DIR, 'manifest-cli.js'), path.join(doLibDir, 'manifest-cli.js'))
368
- _copyFile(path.join(LIB_DIR, 'asset-manager.js'), path.join(doLibDir, 'asset-manager.js'))
369
- _copyFile(path.join(LIB_DIR, 'bootstrap-config.js'), path.join(doLibDir, 'bootstrap-config.js'))
365
+ const doLibDir = path.join(destDir, 'do', 'lib');
366
+ fs.mkdirSync(doLibDir, { recursive: true });
367
+ _copyFile(path.join(LIB_DIR, 'manifest-cli.js'), path.join(doLibDir, 'manifest-cli.js'));
368
+ _copyFile(path.join(LIB_DIR, 'asset-manager.js'), path.join(doLibDir, 'asset-manager.js'));
369
+ _copyFile(path.join(LIB_DIR, 'bootstrap-config.js'), path.join(doLibDir, 'bootstrap-config.js'));
370
370
  }
371
371
 
372
372
  /**
@@ -379,15 +379,15 @@ export async function writeProject(templateDir, destDir, answers, registryConfig
379
379
  */
380
380
  export async function postGenerate(destDir, answers, tritonBackends = {}) {
381
381
  // Set executable permissions on shell scripts
382
- _setExecutablePermissions(destDir)
382
+ _setExecutablePermissions(destDir);
383
383
 
384
384
  // Run sample model training if requested
385
- const architecture = answers.architecture
385
+ const architecture = answers.architecture;
386
386
  const skipSampleTraining = architecture === 'transformers' ||
387
- (architecture === 'triton' && !tritonBackends[answers.backend]?.supportsSampleModel)
387
+ (architecture === 'triton' && !tritonBackends[answers.backend]?.supportsSampleModel);
388
388
 
389
389
  if (answers.includeSampleModel && !skipSampleTraining) {
390
- await _runSampleModelTraining(destDir)
390
+ await _runSampleModelTraining(destDir);
391
391
  }
392
392
  }
393
393
 
@@ -402,13 +402,13 @@ export async function postGenerate(destDir, answers, tritonBackends = {}) {
402
402
  * @returns {object} Options with kebab-case keys
403
403
  */
404
404
  function _toKebabCaseOptions(options) {
405
- const kebabOptions = {}
405
+ const kebabOptions = {};
406
406
  for (const [key, value] of Object.entries(options)) {
407
407
  // Convert camelCase to kebab-case
408
- const kebabKey = key.replace(/([A-Z])/g, '-$1').toLowerCase()
409
- kebabOptions[kebabKey] = value
408
+ const kebabKey = key.replace(/([A-Z])/g, '-$1').toLowerCase();
409
+ kebabOptions[kebabKey] = value;
410
410
  }
411
- return kebabOptions
411
+ return kebabOptions;
412
412
  }
413
413
 
414
414
  /**
@@ -420,31 +420,31 @@ function _toKebabCaseOptions(options) {
420
420
  * @returns {object} Generator-like adapter
421
421
  */
422
422
  function _createGeneratorAdapter(projectName, options) {
423
- const args = projectName ? [projectName] : []
424
- let _destinationPath = process.cwd()
423
+ const args = projectName ? [projectName] : [];
424
+ let _destinationPath = process.cwd();
425
425
 
426
426
  const adapter = {
427
427
  options,
428
428
  args,
429
429
  destinationPath(...segments) {
430
- if (segments.length === 0) return _destinationPath
431
- return path.join(_destinationPath, ...segments)
430
+ if (segments.length === 0) return _destinationPath;
431
+ return path.join(_destinationPath, ...segments);
432
432
  },
433
433
  destinationRoot(newRoot) {
434
434
  if (newRoot !== undefined) {
435
- _destinationPath = path.resolve(newRoot)
435
+ _destinationPath = path.resolve(newRoot);
436
436
  }
437
- return _destinationPath
437
+ return _destinationPath;
438
438
  },
439
439
  registryConfigManager: null,
440
440
  configManager: null,
441
441
  baseConfig: {},
442
442
  async prompt(prompts) {
443
- return runPrompts(prompts)
443
+ return runPrompts(prompts);
444
444
  }
445
- }
445
+ };
446
446
 
447
- return adapter
447
+ return adapter;
448
448
  }
449
449
 
450
450
  /**
@@ -491,62 +491,62 @@ async function _ensureTemplateVariables(answers, registryConfigManager = null) {
491
491
  modelSource: 'huggingface',
492
492
  artifactUri: '',
493
493
  modelLoadStrategy: 'runtime'
494
- }
494
+ };
495
495
 
496
496
  Object.entries(defaults).forEach(([key, value]) => {
497
497
  if (answers[key] === undefined) {
498
- answers[key] = value
498
+ answers[key] = value;
499
499
  }
500
- })
500
+ });
501
501
 
502
502
  // Backward compatibility: populate framework and modelServer from architecture/backend
503
503
  if (!answers.framework && answers.architecture) {
504
- answers.framework = answers.architecture
504
+ answers.framework = answers.architecture;
505
505
  }
506
506
  if (!answers.modelServer && answers.backend) {
507
- answers.modelServer = answers.backend
507
+ answers.modelServer = answers.backend;
508
508
  }
509
509
 
510
510
  // Always include testing with all available test types
511
- answers.includeTesting = true
511
+ answers.includeTesting = true;
512
512
  if (!answers.testTypes || answers.testTypes.length === 0) {
513
513
  if (answers.architecture === 'transformers' || answers.framework === 'transformers') {
514
- answers.testTypes = ['hosted-model-endpoint']
514
+ answers.testTypes = ['hosted-model-endpoint'];
515
515
  } else {
516
- answers.testTypes = ['local-model-cli', 'local-model-server', 'hosted-model-endpoint']
516
+ answers.testTypes = ['local-model-cli', 'local-model-server', 'hosted-model-endpoint'];
517
517
  }
518
518
  }
519
519
 
520
520
  // Merge catalog env vars into answers.envVars with correct precedence
521
- await _mergeEnvVarsWithPrecedence(answers, registryConfigManager)
521
+ await _mergeEnvVarsWithPrecedence(answers, registryConfigManager);
522
522
 
523
523
  // For Triton architecture, set default base image fallback
524
524
  if (answers.architecture === 'triton' && !answers.baseImage) {
525
525
  // Try to look up base image from framework registry using deployment-config key
526
- const tritonRegistryKey = answers.deploymentConfig
526
+ const tritonRegistryKey = answers.deploymentConfig;
527
527
  if (tritonRegistryKey && registryConfigManager?.frameworkRegistry) {
528
- const tritonFrameworkConfig = registryConfigManager.frameworkRegistry[tritonRegistryKey]
528
+ const tritonFrameworkConfig = registryConfigManager.frameworkRegistry[tritonRegistryKey];
529
529
  if (tritonFrameworkConfig) {
530
530
  const versions = Object.keys(tritonFrameworkConfig).sort((a, b) =>
531
531
  b.localeCompare(a, undefined, { numeric: true })
532
- )
532
+ );
533
533
  if (versions.length > 0) {
534
- const latestConfig = tritonFrameworkConfig[versions[0]]
534
+ const latestConfig = tritonFrameworkConfig[versions[0]];
535
535
  if (latestConfig.baseImage) {
536
- answers.baseImage = latestConfig.baseImage
536
+ answers.baseImage = latestConfig.baseImage;
537
537
  }
538
538
  if (latestConfig.inferenceAmiVersion && !answers.inferenceAmiVersion) {
539
- answers.inferenceAmiVersion = latestConfig.inferenceAmiVersion
539
+ answers.inferenceAmiVersion = latestConfig.inferenceAmiVersion;
540
540
  }
541
541
  if (latestConfig.accelerator) {
542
- answers.accelerator = latestConfig.accelerator
542
+ answers.accelerator = latestConfig.accelerator;
543
543
  }
544
544
  }
545
545
  }
546
546
  }
547
547
  // Final fallback: hardcoded default Triton base image
548
548
  if (!answers.baseImage) {
549
- answers.baseImage = 'nvcr.io/nvidia/tritonserver:24.08-py3'
549
+ answers.baseImage = 'nvcr.io/nvidia/tritonserver:24.08-py3';
550
550
  }
551
551
  }
552
552
 
@@ -554,34 +554,34 @@ async function _ensureTemplateVariables(answers, registryConfigManager = null) {
554
554
  if (answers.framework === 'transformers' && answers.modelName && registryConfigManager) {
555
555
  try {
556
556
  // Fetch HuggingFace data for model-specific info
557
- const hfData = await registryConfigManager._fetchHuggingFaceData(answers.modelName)
557
+ const hfData = await registryConfigManager._fetchHuggingFaceData(answers.modelName);
558
558
 
559
559
  // Merge chatTemplate if available and not already set
560
560
  if (hfData && hfData.chatTemplate && !answers.chatTemplate) {
561
- answers.chatTemplate = hfData.chatTemplate
562
- answers.chatTemplateSource = 'HuggingFace_Hub_API'
561
+ answers.chatTemplate = hfData.chatTemplate;
562
+ answers.chatTemplateSource = 'HuggingFace_Hub_API';
563
563
  }
564
564
 
565
565
  // Check Model Registry for chatTemplate overrides
566
566
  if (registryConfigManager.modelRegistry) {
567
- const modelConfig = _findModelConfig(answers.modelName, registryConfigManager)
567
+ const modelConfig = _findModelConfig(answers.modelName, registryConfigManager);
568
568
 
569
569
  if (modelConfig && modelConfig.chatTemplate) {
570
- answers.chatTemplate = modelConfig.chatTemplate
571
- answers.chatTemplateSource = 'Model_Registry'
570
+ answers.chatTemplate = modelConfig.chatTemplate;
571
+ answers.chatTemplateSource = 'Model_Registry';
572
572
  }
573
573
  }
574
574
 
575
575
  // Set framework-level metadata (non-envVar fields)
576
576
  if (answers.frameworkVersion && registryConfigManager.frameworkRegistry) {
577
- const frameworkConfig = registryConfigManager.frameworkRegistry[answers.framework]?.[answers.frameworkVersion]
577
+ const frameworkConfig = registryConfigManager.frameworkRegistry[answers.framework]?.[answers.frameworkVersion];
578
578
 
579
579
  if (frameworkConfig) {
580
580
  if (frameworkConfig.inferenceAmiVersion && !answers.inferenceAmiVersion) {
581
- answers.inferenceAmiVersion = frameworkConfig.inferenceAmiVersion
581
+ answers.inferenceAmiVersion = frameworkConfig.inferenceAmiVersion;
582
582
  }
583
583
  if (frameworkConfig.accelerator) {
584
- answers.accelerator = frameworkConfig.accelerator
584
+ answers.accelerator = frameworkConfig.accelerator;
585
585
  }
586
586
  }
587
587
  }
@@ -598,7 +598,7 @@ async function _ensureTemplateVariables(answers, registryConfigManager = null) {
598
598
  * @returns {Array<{key: string, value: string}>} Ordered array
599
599
  */
600
600
  function _getOrderedEnvVars(envVars) {
601
- const entries = Object.entries(envVars)
601
+ const entries = Object.entries(envVars);
602
602
 
603
603
  const priorities = {
604
604
  'LD_LIBRARY_PATH': 1,
@@ -617,26 +617,26 @@ function _getOrderedEnvVars(envVars) {
617
617
  'WORKER': 4,
618
618
  'THREAD': 4,
619
619
  'default': 5
620
- }
620
+ };
621
621
 
622
622
  function getPriority(key) {
623
- if (priorities[key]) return priorities[key]
623
+ if (priorities[key]) return priorities[key];
624
624
  for (const [pattern, priority] of Object.entries(priorities)) {
625
625
  if (pattern !== 'default' && key.includes(pattern)) {
626
- return priority
626
+ return priority;
627
627
  }
628
628
  }
629
- return priorities.default
629
+ return priorities.default;
630
630
  }
631
631
 
632
632
  const sorted = entries.sort(([keyA], [keyB]) => {
633
- const priorityA = getPriority(keyA)
634
- const priorityB = getPriority(keyB)
635
- if (priorityA !== priorityB) return priorityA - priorityB
636
- return keyA.localeCompare(keyB)
637
- })
633
+ const priorityA = getPriority(keyA);
634
+ const priorityB = getPriority(keyB);
635
+ if (priorityA !== priorityB) return priorityA - priorityB;
636
+ return keyA.localeCompare(keyB);
637
+ });
638
638
 
639
- return sorted.map(([key, value]) => ({ key, value }))
639
+ return sorted.map(([key, value]) => ({ key, value }));
640
640
  }
641
641
 
642
642
  /**
@@ -649,60 +649,60 @@ function _getOrderedEnvVars(envVars) {
649
649
  async function _validateEnvironmentVariables(answers, registryConfigManager) {
650
650
  // Get framework configuration
651
651
  // For Triton configs, look up using deploymentConfig key (e.g. 'triton-fil')
652
- let frameworkConfig
652
+ let frameworkConfig;
653
653
  if (answers.architecture === 'triton' && answers.deploymentConfig) {
654
- const tritonEntry = registryConfigManager.frameworkRegistry?.[answers.deploymentConfig]
654
+ const tritonEntry = registryConfigManager.frameworkRegistry?.[answers.deploymentConfig];
655
655
  if (tritonEntry) {
656
- const versions = Object.keys(tritonEntry)
656
+ const versions = Object.keys(tritonEntry);
657
657
  if (versions.length > 0) {
658
- frameworkConfig = tritonEntry[versions[0]]
658
+ frameworkConfig = tritonEntry[versions[0]];
659
659
  }
660
660
  }
661
661
  }
662
662
  if (!frameworkConfig) {
663
- frameworkConfig = registryConfigManager.frameworkRegistry?.[answers.framework]?.[answers.frameworkVersion]
663
+ frameworkConfig = registryConfigManager.frameworkRegistry?.[answers.framework]?.[answers.frameworkVersion];
664
664
  }
665
665
 
666
666
  if (!frameworkConfig || !frameworkConfig.envVars) {
667
- return // No env vars to validate
667
+ return; // No env vars to validate
668
668
  }
669
669
 
670
- console.log('\n🔍 Validating environment variables...')
670
+ console.log('\n🔍 Validating environment variables...');
671
671
 
672
672
  // Validate environment variables
673
673
  const validationResult = registryConfigManager.validateEnvironmentVariables(
674
674
  frameworkConfig.envVars,
675
675
  frameworkConfig
676
- )
676
+ );
677
677
 
678
678
  // Display validation results
679
679
  if (validationResult.errors && validationResult.errors.length > 0) {
680
- console.log('\n❌ Environment Variable Validation Errors:')
680
+ console.log('\n❌ Environment Variable Validation Errors:');
681
681
  validationResult.errors.forEach(error => {
682
- console.log(` • ${error.key}: ${error.message}`)
683
- })
682
+ console.log(` • ${error.key}: ${error.message}`);
683
+ });
684
684
  }
685
685
 
686
686
  if (validationResult.warnings && validationResult.warnings.length > 0) {
687
- console.log('\n⚠️ Environment Variable Validation Warnings:')
687
+ console.log('\n⚠️ Environment Variable Validation Warnings:');
688
688
  validationResult.warnings.forEach(warning => {
689
- console.log(` • ${warning.key ? `${warning.key}: ` : ''}${warning.message}`)
690
- })
689
+ console.log(` • ${warning.key ? `${warning.key}: ` : ''}${warning.message}`);
690
+ });
691
691
  }
692
692
 
693
693
  if (validationResult.strategiesUsed && validationResult.strategiesUsed.length > 0) {
694
- console.log(`\n✅ Validation methods used: ${validationResult.strategiesUsed.join(', ')}`)
694
+ console.log(`\n✅ Validation methods used: ${validationResult.strategiesUsed.join(', ')}`);
695
695
  }
696
696
 
697
697
  if (!validationResult.errors || validationResult.errors.length === 0) {
698
698
  if (!validationResult.warnings || validationResult.warnings.length === 0) {
699
- console.log(' ✅ All environment variables validated successfully')
699
+ console.log(' ✅ All environment variables validated successfully');
700
700
  }
701
701
  }
702
702
 
703
703
  // In non-interactive mode (skip-prompts), throw on errors
704
704
  if (validationResult.errors && validationResult.errors.length > 0) {
705
- throw new Error('Environment variable validation failed. Please fix the errors and try again.')
705
+ throw new Error('Environment variable validation failed. Please fix the errors and try again.');
706
706
  }
707
707
  }
708
708
 
@@ -719,60 +719,60 @@ async function _validateEnvironmentVariables(answers, registryConfigManager) {
719
719
  * @param {object|null} registryConfigManager - Registry configuration manager
720
720
  */
721
721
  async function _mergeEnvVarsWithPrecedence(answers, registryConfigManager) {
722
- if (!registryConfigManager) return
722
+ if (!registryConfigManager) return;
723
723
 
724
724
  // Capture CLI-provided env vars before merging (highest precedence)
725
- const cliEnvVars = { ...answers.envVars }
725
+ const cliEnvVars = { ...answers.envVars };
726
726
 
727
727
  // Resolve the framework config for the selected framework + version
728
- const frameworkName = answers.framework || answers.deploymentConfig
729
- const frameworkVersion = answers.frameworkVersion
730
- let frameworkConfig = null
728
+ const frameworkName = answers.framework || answers.deploymentConfig;
729
+ const frameworkVersion = answers.frameworkVersion;
730
+ let frameworkConfig = null;
731
731
 
732
732
  if (frameworkName && registryConfigManager.frameworkRegistry) {
733
- const frameworkVersions = registryConfigManager.frameworkRegistry[frameworkName]
733
+ const frameworkVersions = registryConfigManager.frameworkRegistry[frameworkName];
734
734
  if (frameworkVersions) {
735
735
  if (frameworkVersion && frameworkVersions[frameworkVersion]) {
736
- frameworkConfig = frameworkVersions[frameworkVersion]
736
+ frameworkConfig = frameworkVersions[frameworkVersion];
737
737
  } else {
738
738
  // Fall back to latest version for Triton and other non-versioned lookups
739
739
  const versions = Object.keys(frameworkVersions).sort((a, b) =>
740
740
  b.localeCompare(a, undefined, { numeric: true })
741
- )
741
+ );
742
742
  if (versions.length > 0) {
743
- frameworkConfig = frameworkVersions[versions[0]]
743
+ frameworkConfig = frameworkVersions[versions[0]];
744
744
  }
745
745
  }
746
746
  }
747
747
  }
748
748
 
749
749
  // Resolve the model config (exact match or pattern match)
750
- let modelConfig = null
750
+ let modelConfig = null;
751
751
  if (answers.modelName && registryConfigManager.modelRegistry) {
752
- modelConfig = _findModelConfig(answers.modelName, registryConfigManager)
752
+ modelConfig = _findModelConfig(answers.modelName, registryConfigManager);
753
753
  }
754
754
 
755
755
  // Layer 1: catalog defaults (Image_Entry defaults.envVars)
756
- const catalogDefaults = frameworkConfig?.envVars || {}
756
+ const catalogDefaults = frameworkConfig?.envVars || {};
757
757
 
758
758
  // Layer 2: framework profile envVars
759
- let frameworkProfileEnvVars = {}
759
+ let frameworkProfileEnvVars = {};
760
760
  if (answers.frameworkProfile && frameworkConfig?.profiles) {
761
- const profile = frameworkConfig.profiles[answers.frameworkProfile]
761
+ const profile = frameworkConfig.profiles[answers.frameworkProfile];
762
762
  if (profile?.envVars) {
763
- frameworkProfileEnvVars = profile.envVars
763
+ frameworkProfileEnvVars = profile.envVars;
764
764
  }
765
765
  }
766
766
 
767
767
  // Layer 3: model entry envVars
768
- const modelEntryEnvVars = modelConfig?.envVars || {}
768
+ const modelEntryEnvVars = modelConfig?.envVars || {};
769
769
 
770
770
  // Layer 4: model profile envVars
771
- let modelProfileEnvVars = {}
771
+ let modelProfileEnvVars = {};
772
772
  if (answers.modelProfile && modelConfig?.profiles) {
773
- const profile = modelConfig.profiles[answers.modelProfile]
773
+ const profile = modelConfig.profiles[answers.modelProfile];
774
774
  if (profile?.envVars) {
775
- modelProfileEnvVars = profile.envVars
775
+ modelProfileEnvVars = profile.envVars;
776
776
  }
777
777
  }
778
778
 
@@ -785,7 +785,7 @@ async function _mergeEnvVarsWithPrecedence(answers, registryConfigManager) {
785
785
  ...modelEntryEnvVars,
786
786
  ...modelProfileEnvVars,
787
787
  ...cliEnvVars
788
- }
788
+ };
789
789
  }
790
790
 
791
791
  /**
@@ -796,23 +796,23 @@ async function _mergeEnvVarsWithPrecedence(answers, registryConfigManager) {
796
796
  * @returns {object|null} Model configuration or null
797
797
  */
798
798
  function _findModelConfig(modelName, registryConfigManager) {
799
- if (!registryConfigManager?.modelRegistry) return null
799
+ if (!registryConfigManager?.modelRegistry) return null;
800
800
 
801
801
  // Exact match first
802
- const exact = registryConfigManager.modelRegistry[modelName]
803
- if (exact) return exact
802
+ const exact = registryConfigManager.modelRegistry[modelName];
803
+ if (exact) return exact;
804
804
 
805
805
  // Pattern matching with glob-style wildcards
806
806
  for (const [pattern, config] of Object.entries(registryConfigManager.modelRegistry)) {
807
807
  if (pattern.includes('*')) {
808
- const regex = new RegExp(`^${pattern.replace(/\*/g, '.*')}$`)
808
+ const regex = new RegExp(`^${pattern.replace(/\*/g, '.*')}$`);
809
809
  if (regex.test(modelName)) {
810
- return config
810
+ return config;
811
811
  }
812
812
  }
813
813
  }
814
814
 
815
- return null
815
+ return null;
816
816
  }
817
817
 
818
818
  /**
@@ -824,33 +824,33 @@ function _findModelConfig(modelName, registryConfigManager) {
824
824
  * @param {object} answers - Configuration answers
825
825
  * @param {object} tritonBackends - Triton backends catalog
826
826
  */
827
- function _generateTritonFiles(templateDir, destDir, templateVars, answers, tritonBackends) {
828
- const modelName = answers.modelName || 'model'
829
- const backend = answers.backend
827
+ function _generateTritonFiles(templateDir, destDir, templateVars, answers, _tritonBackends) {
828
+ const modelName = answers.modelName || 'model';
829
+ const backend = answers.backend;
830
830
 
831
831
  // Copy Triton Dockerfile
832
832
  _renderTemplate(
833
833
  path.join(templateDir, 'triton/Dockerfile'),
834
834
  path.join(destDir, 'Dockerfile'),
835
835
  templateVars
836
- )
836
+ );
837
837
 
838
838
  // Create model repository directory structure
839
- const modelRepoPath = path.join(destDir, `model_repository/${modelName}`)
840
- fs.mkdirSync(path.join(modelRepoPath, '1'), { recursive: true })
839
+ const modelRepoPath = path.join(destDir, `model_repository/${modelName}`);
840
+ fs.mkdirSync(path.join(modelRepoPath, '1'), { recursive: true });
841
841
 
842
842
  // Copy config.pbtxt
843
843
  _renderTemplate(
844
844
  path.join(templateDir, 'triton/config.pbtxt'),
845
845
  path.join(modelRepoPath, 'config.pbtxt'),
846
846
  templateVars
847
- )
847
+ );
848
848
 
849
849
  // Create version 1 directory with .gitkeep
850
850
  fs.writeFileSync(
851
851
  path.join(modelRepoPath, '1/.gitkeep'),
852
852
  '# Placeholder for model artifacts\n'
853
- )
853
+ );
854
854
 
855
855
  // For triton-python backend: copy model.py and requirements.txt
856
856
  if (backend === 'python') {
@@ -858,12 +858,12 @@ function _generateTritonFiles(templateDir, destDir, templateVars, answers, trito
858
858
  path.join(templateDir, 'triton/model.py'),
859
859
  path.join(modelRepoPath, '1/model.py'),
860
860
  templateVars
861
- )
861
+ );
862
862
  _renderTemplate(
863
863
  path.join(templateDir, 'triton/requirements.txt'),
864
864
  path.join(destDir, 'triton/requirements.txt'),
865
865
  templateVars
866
- )
866
+ );
867
867
  }
868
868
  }
869
869
 
@@ -875,10 +875,10 @@ function _generateTritonFiles(templateDir, destDir, templateVars, answers, trito
875
875
  * @param {object} vars - Template variables
876
876
  */
877
877
  function _renderTemplate(src, dest, vars) {
878
- fs.mkdirSync(path.dirname(dest), { recursive: true })
879
- const content = fs.readFileSync(src, 'utf8')
880
- const rendered = ejs.render(content, vars, { filename: src })
881
- fs.writeFileSync(dest, rendered)
878
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
879
+ const content = fs.readFileSync(src, 'utf8');
880
+ const rendered = ejs.render(content, vars, { filename: src });
881
+ fs.writeFileSync(dest, rendered);
882
882
  }
883
883
 
884
884
  /**
@@ -888,8 +888,8 @@ function _renderTemplate(src, dest, vars) {
888
888
  * @param {string} dest - Destination file path
889
889
  */
890
890
  function _copyFile(src, dest) {
891
- fs.mkdirSync(path.dirname(dest), { recursive: true })
892
- fs.copyFileSync(src, dest)
891
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
892
+ fs.copyFileSync(src, dest);
893
893
  }
894
894
 
895
895
  /**
@@ -900,7 +900,7 @@ function _copyFile(src, dest) {
900
900
  function _unlinkIfExists(filePath) {
901
901
  try {
902
902
  if (fs.existsSync(filePath)) {
903
- fs.unlinkSync(filePath)
903
+ fs.unlinkSync(filePath);
904
904
  }
905
905
  } catch (e) {
906
906
  // Silently continue
@@ -926,20 +926,20 @@ function _setExecutablePermissions(destDir) {
926
926
  'do/register',
927
927
  'do/ci',
928
928
  'do/manifest'
929
- ]
929
+ ];
930
930
 
931
931
  shellScripts.forEach(script => {
932
- const scriptPath = path.join(destDir, script)
932
+ const scriptPath = path.join(destDir, script);
933
933
  try {
934
934
  if (fs.existsSync(scriptPath)) {
935
- const stats = fs.statSync(scriptPath)
936
- const newMode = stats.mode | 0o755
937
- fs.chmodSync(scriptPath, newMode)
935
+ const stats = fs.statSync(scriptPath);
936
+ const newMode = stats.mode | 0o755;
937
+ fs.chmodSync(scriptPath, newMode);
938
938
  }
939
939
  } catch (error) {
940
940
  // Silently continue if chmod fails (e.g., on Windows)
941
941
  }
942
- })
942
+ });
943
943
  }
944
944
 
945
945
  /**
@@ -949,33 +949,33 @@ function _setExecutablePermissions(destDir) {
949
949
  * @param {string} destDir - Path to the generated project directory
950
950
  */
951
951
  async function _runSampleModelTraining(destDir) {
952
- const trainingScriptName = 'train_abalone.py'
953
- const trainingScript = path.join(destDir, `sample_model/${trainingScriptName}`)
954
- const sampleModelDir = path.join(destDir, 'sample_model')
955
- const requirementsFile = path.join(destDir, 'requirements.txt')
952
+ const trainingScriptName = 'train_abalone.py';
953
+ const trainingScript = path.join(destDir, `sample_model/${trainingScriptName}`);
954
+ const sampleModelDir = path.join(destDir, 'sample_model');
955
+ const requirementsFile = path.join(destDir, 'requirements.txt');
956
956
 
957
- console.log('\n🤖 Training sample model...')
958
- console.log('This will generate the model file needed for Docker build.')
957
+ console.log('\n🤖 Training sample model...');
958
+ console.log('This will generate the model file needed for Docker build.');
959
959
 
960
960
  try {
961
961
  if (!fs.existsSync(trainingScript)) {
962
- console.log('⚠️ Training script not found, skipping model training')
963
- return
962
+ console.log('⚠️ Training script not found, skipping model training');
963
+ return;
964
964
  }
965
965
 
966
966
  // Install dependencies
967
967
  if (fs.existsSync(requirementsFile)) {
968
- console.log('📦 Installing dependencies from requirements.txt...')
969
- await _spawnAsync('pip', ['install', '-q', '-r', requirementsFile], { cwd: destDir })
968
+ console.log('📦 Installing dependencies from requirements.txt...');
969
+ await _spawnAsync('pip', ['install', '-q', '-r', requirementsFile], { cwd: destDir });
970
970
  }
971
971
 
972
972
  // Run training script
973
- await _spawnAsync('python', [trainingScriptName], { cwd: sampleModelDir })
974
- console.log('✅ Sample model training completed successfully!')
975
- console.log(`📁 Model file saved in: ${sampleModelDir}`)
973
+ await _spawnAsync('python', [trainingScriptName], { cwd: sampleModelDir });
974
+ console.log('✅ Sample model training completed successfully!');
975
+ console.log(`📁 Model file saved in: ${sampleModelDir}`);
976
976
  } catch (error) {
977
- console.log('⚠️ Error during sample model training:', error.message)
978
- console.log(`Please run manually: python sample_model/${trainingScriptName}`)
977
+ console.log('⚠️ Error during sample model training:', error.message);
978
+ console.log(`Please run manually: python sample_model/${trainingScriptName}`);
979
979
  }
980
980
  }
981
981
 
@@ -990,18 +990,18 @@ async function _runSampleModelTraining(destDir) {
990
990
  */
991
991
  function _spawnAsync(command, args, opts = {}) {
992
992
  return new Promise((resolve, reject) => {
993
- const proc = spawn(command, args, { ...opts, stdio: 'inherit' })
993
+ const proc = spawn(command, args, { ...opts, stdio: 'inherit' });
994
994
 
995
995
  proc.on('close', (code) => {
996
996
  if (code === 0) {
997
- resolve()
997
+ resolve();
998
998
  } else {
999
- reject(new Error(`${command} exited with code ${code}`))
999
+ reject(new Error(`${command} exited with code ${code}`));
1000
1000
  }
1001
- })
1001
+ });
1002
1002
 
1003
1003
  proc.on('error', (error) => {
1004
- reject(error)
1005
- })
1006
- })
1004
+ reject(error);
1005
+ });
1006
+ });
1007
1007
  }