@aws/ml-container-creator 0.2.5 → 0.3.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.
- package/bin/cli.js +45 -4
- package/config/bootstrap-stack.json +14 -0
- package/infra/ci-harness/package-lock.json +22 -9
- package/package.json +7 -8
- package/servers/base-image-picker/index.js +3 -3
- package/servers/base-image-picker/manifest.json +4 -2
- package/servers/instance-sizer/index.js +564 -0
- package/servers/instance-sizer/lib/instance-ranker.js +270 -0
- package/servers/instance-sizer/lib/model-resolver.js +269 -0
- package/servers/instance-sizer/lib/vram-estimator.js +177 -0
- package/servers/instance-sizer/manifest.json +17 -0
- package/servers/instance-sizer/package.json +15 -0
- package/servers/{instance-recommender → lib}/catalogs/instances.json +136 -34
- package/servers/{base-image-picker → lib}/catalogs/model-servers.json +302 -254
- package/servers/lib/catalogs/model-sizes.json +131 -0
- package/servers/lib/catalogs/models.json +632 -0
- package/servers/{model-picker → lib}/catalogs/popular-diffusors.json +32 -10
- package/servers/{model-picker → lib}/catalogs/popular-transformers.json +59 -26
- package/servers/{base-image-picker → lib}/catalogs/python-slim.json +12 -12
- package/servers/lib/schemas/image-catalog.schema.json +6 -12
- package/servers/lib/schemas/instances.schema.json +29 -0
- package/servers/lib/schemas/model-catalog.schema.json +12 -10
- package/servers/lib/schemas/unified-model-catalog.schema.json +129 -0
- package/servers/model-picker/index.js +4 -4
- package/servers/model-picker/manifest.json +2 -3
- package/servers/region-picker/index.js +1 -1
- package/servers/region-picker/manifest.json +1 -1
- package/src/app.js +36 -0
- package/src/lib/architecture-sync.js +171 -0
- package/src/lib/arn-detection.js +22 -0
- package/src/lib/bootstrap-command-handler.js +120 -0
- package/src/lib/cli-handler.js +3 -3
- package/src/lib/config-manager.js +47 -1
- package/src/lib/configuration-manager.js +2 -2
- package/src/lib/cross-cutting-checker.js +460 -0
- package/src/lib/deployment-entry-schema.js +1 -2
- package/src/lib/dry-run-validator.js +78 -0
- package/src/lib/generation-validator.js +102 -0
- package/src/lib/mcp-validator-config.js +89 -0
- package/src/lib/payload-builder.js +153 -0
- package/src/lib/prompt-runner.js +866 -149
- package/src/lib/prompts.js +2 -2
- package/src/lib/registry-command-handler.js +236 -0
- package/src/lib/registry-loader.js +5 -5
- package/src/lib/schema-sync.js +203 -0
- package/src/lib/schema-validation-engine.js +195 -0
- package/src/lib/secret-classification.js +56 -0
- package/src/lib/secrets-command-handler.js +550 -0
- package/src/lib/service-model-parser.js +102 -0
- package/src/lib/validate-runner.js +216 -0
- package/src/lib/validation-report.js +140 -0
- package/src/lib/validators/base-validator.js +36 -0
- package/src/lib/validators/catalog-validator.js +177 -0
- package/src/lib/validators/enum-validator.js +120 -0
- package/src/lib/validators/required-field-validator.js +150 -0
- package/src/lib/validators/type-validator.js +313 -0
- package/src/prompt-adapter.js +3 -2
- package/templates/Dockerfile +1 -1
- package/templates/do/build +37 -5
- package/templates/do/config +15 -3
- package/templates/do/deploy +60 -5
- package/templates/do/logs +18 -3
- package/templates/do/run +15 -1
- package/templates/do/validate +61 -0
- package/servers/instance-recommender/LICENSE +0 -202
- package/servers/instance-recommender/index.js +0 -284
- package/servers/instance-recommender/manifest.json +0 -16
- package/servers/instance-recommender/package.json +0 -15
- /package/servers/{model-picker → lib}/catalogs/jumpstart-public.json +0 -0
- /package/servers/{region-picker → lib}/catalogs/regions.json +0 -0
- /package/servers/{base-image-picker → lib}/catalogs/triton-backends.json +0 -0
- /package/servers/{base-image-picker → lib}/catalogs/triton.json +0 -0
package/src/lib/prompts.js
CHANGED
|
@@ -14,7 +14,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
14
14
|
|
|
15
15
|
const __promptsFilename = fileURLToPath(import.meta.url);
|
|
16
16
|
const __promptsDir = dirname(__promptsFilename);
|
|
17
|
-
const instancesCatalogPath = resolve(__promptsDir, '../../servers/
|
|
17
|
+
const instancesCatalogPath = resolve(__promptsDir, '../../servers/lib/catalogs/instances.json');
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
20
|
* Load instance types from the instances.json catalog and transform
|
|
@@ -1053,7 +1053,7 @@ function formatImageChoices(entries, isTransformer) {
|
|
|
1053
1053
|
? `${entry.repository.padEnd(30)} ${entry.tag.padEnd(16)} ${entry.architecture.padEnd(7)} ${cuda.padEnd(6)} ${python.padEnd(8)} ${date}`
|
|
1054
1054
|
: `${entry.repository.padEnd(30)} ${entry.tag.padEnd(16)} ${entry.architecture.padEnd(7)} ${python.padEnd(8)} ${date}`;
|
|
1055
1055
|
|
|
1056
|
-
return { name, value: entry.image };
|
|
1056
|
+
return { name, value: entry.image, _meta: { labels: entry.labels, accelerator: entry.accelerator } };
|
|
1057
1057
|
});
|
|
1058
1058
|
}
|
|
1059
1059
|
|
|
@@ -24,6 +24,8 @@ import { readFileSync } from 'node:fs';
|
|
|
24
24
|
import { execSync } from 'node:child_process';
|
|
25
25
|
import { fileURLToPath } from 'node:url';
|
|
26
26
|
import DeploymentRegistry, { reconstructReplayFlags } from './deployment-registry.js';
|
|
27
|
+
import { syncArchitectures } from './architecture-sync.js';
|
|
28
|
+
import HuggingFaceClient from './huggingface-client.js';
|
|
27
29
|
|
|
28
30
|
const PERSONAL_REGISTRY_PATH = path.join(os.homedir(), '.ml-container-creator', 'registry.json');
|
|
29
31
|
const PROJECT_REGISTRY_PATH = path.join(process.cwd(), '.ml-container-creator', 'registry.json');
|
|
@@ -71,6 +73,15 @@ export default class RegistryCommandHandler {
|
|
|
71
73
|
case 'search':
|
|
72
74
|
this._handleSearch(options);
|
|
73
75
|
break;
|
|
76
|
+
case 'sync-architectures':
|
|
77
|
+
await this._handleSyncArchitectures();
|
|
78
|
+
break;
|
|
79
|
+
case 'list-architectures':
|
|
80
|
+
this._handleListArchitectures(args, options);
|
|
81
|
+
break;
|
|
82
|
+
case 'check':
|
|
83
|
+
await this._handleCheck(args);
|
|
84
|
+
break;
|
|
74
85
|
default:
|
|
75
86
|
console.log(`Unknown registry subcommand: ${subcommand}`);
|
|
76
87
|
this._showRegistryHelp();
|
|
@@ -431,6 +442,220 @@ export default class RegistryCommandHandler {
|
|
|
431
442
|
console.log('');
|
|
432
443
|
}
|
|
433
444
|
|
|
445
|
+
/**
|
|
446
|
+
* registry sync-architectures
|
|
447
|
+
*
|
|
448
|
+
* Fetches model registry source files from server GitHub repositories
|
|
449
|
+
* and populates supportedModelTypes in the model-servers catalog.
|
|
450
|
+
*/
|
|
451
|
+
async _handleSyncArchitectures() {
|
|
452
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
453
|
+
const __dirname = path.dirname(__filename);
|
|
454
|
+
const catalogPath = path.resolve(__dirname, '../../servers/lib/catalogs/model-servers.json');
|
|
455
|
+
|
|
456
|
+
console.log('\n📋 Syncing model architecture registry...\n');
|
|
457
|
+
|
|
458
|
+
const summary = await syncArchitectures(catalogPath);
|
|
459
|
+
|
|
460
|
+
console.log('\n── Summary ──────────────────────────────────────');
|
|
461
|
+
if (summary.servers.length > 0) {
|
|
462
|
+
console.log('\n Architectures synced:');
|
|
463
|
+
for (const { server, version, count } of summary.servers) {
|
|
464
|
+
console.log(` ${server} ${version}: ${count} architectures`);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
if (summary.failures.length > 0) {
|
|
468
|
+
console.log('\n Failures:');
|
|
469
|
+
for (const { server, version, reason } of summary.failures) {
|
|
470
|
+
console.log(` ${server} ${version}: ${reason}`);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
if (summary.servers.length === 0 && summary.failures.length === 0) {
|
|
474
|
+
console.log('\n No server entries found with matching registry sources.');
|
|
475
|
+
}
|
|
476
|
+
console.log('');
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* registry list-architectures [--server <name>] [--verbose]
|
|
481
|
+
*
|
|
482
|
+
* Displays a table of server versions and their supported architecture counts.
|
|
483
|
+
* With --server or --verbose, shows the full list of supported model types.
|
|
484
|
+
*
|
|
485
|
+
* @param {object} options - Parsed CLI options
|
|
486
|
+
*/
|
|
487
|
+
_handleListArchitectures(args, options) {
|
|
488
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
489
|
+
const __dirname = path.dirname(__filename);
|
|
490
|
+
const catalogPath = path.resolve(__dirname, '../../servers/lib/catalogs/model-servers.json');
|
|
491
|
+
|
|
492
|
+
let catalog;
|
|
493
|
+
try {
|
|
494
|
+
catalog = JSON.parse(readFileSync(catalogPath, 'utf8'));
|
|
495
|
+
} catch (err) {
|
|
496
|
+
console.log(`Error: Could not read model-servers catalog: ${err.message}`);
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// Parse --server and --verbose from pass-through args (Commander's passThroughOptions
|
|
501
|
+
// puts options after the subcommand into the args array)
|
|
502
|
+
let serverFilter = options.server || null;
|
|
503
|
+
let verbose = options.verbose || false;
|
|
504
|
+
for (const arg of args) {
|
|
505
|
+
if (arg.startsWith('--server=')) {
|
|
506
|
+
serverFilter = arg.split('=')[1];
|
|
507
|
+
} else if (arg === '--server' && args.indexOf(arg) + 1 < args.length) {
|
|
508
|
+
serverFilter = args[args.indexOf(arg) + 1];
|
|
509
|
+
} else if (arg === '--verbose') {
|
|
510
|
+
verbose = true;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// Collect rows: { server, version, count, types }
|
|
515
|
+
const rows = [];
|
|
516
|
+
for (const [server, entries] of Object.entries(catalog)) {
|
|
517
|
+
if (serverFilter && server !== serverFilter) continue;
|
|
518
|
+
for (const entry of entries) {
|
|
519
|
+
const version = entry.labels?.framework_version || '(unknown)';
|
|
520
|
+
const types = entry.supportedModelTypes || [];
|
|
521
|
+
rows.push({ server, version, count: types.length, types });
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
if (rows.length === 0) {
|
|
526
|
+
if (serverFilter) {
|
|
527
|
+
console.log(`No entries found for server "${serverFilter}".`);
|
|
528
|
+
} else {
|
|
529
|
+
console.log('No server entries found in catalog.');
|
|
530
|
+
}
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// Display summary table
|
|
535
|
+
console.log('\nModel Architecture Support:\n');
|
|
536
|
+
console.log(' Server Version Architectures');
|
|
537
|
+
console.log(' ──────────────────── ─────────── ─────────────');
|
|
538
|
+
for (const row of rows) {
|
|
539
|
+
const srv = row.server.padEnd(20);
|
|
540
|
+
const ver = row.version.padEnd(11);
|
|
541
|
+
const cnt = row.count === 0 ? '(not synced)' : String(row.count);
|
|
542
|
+
console.log(` ${srv} ${ver} ${cnt}`);
|
|
543
|
+
}
|
|
544
|
+
console.log('');
|
|
545
|
+
|
|
546
|
+
// Show full list when --server or --verbose is set
|
|
547
|
+
if (serverFilter || verbose) {
|
|
548
|
+
for (const row of rows) {
|
|
549
|
+
if (row.types.length === 0) continue;
|
|
550
|
+
console.log(` ${row.server} ${row.version} supported model types:`);
|
|
551
|
+
console.log(` ${row.types.join(', ')}`);
|
|
552
|
+
console.log('');
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* registry check <model-id>
|
|
559
|
+
*
|
|
560
|
+
* Fetches a model's config.json from HuggingFace, extracts the model_type,
|
|
561
|
+
* and checks compatibility against all server versions in the catalog.
|
|
562
|
+
*
|
|
563
|
+
* @param {string[]} args - Remaining positional args (args[1] = model-id)
|
|
564
|
+
*/
|
|
565
|
+
async _handleCheck(args) {
|
|
566
|
+
const modelId = args[1];
|
|
567
|
+
|
|
568
|
+
if (!modelId) {
|
|
569
|
+
console.log('Usage: ml-container-creator registry check <model-id>');
|
|
570
|
+
console.log('Example: ml-container-creator registry check meta-llama/Llama-2-7b-chat-hf');
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
575
|
+
const __dirname = path.dirname(__filename);
|
|
576
|
+
const catalogPath = path.resolve(__dirname, '../../servers/lib/catalogs/model-servers.json');
|
|
577
|
+
|
|
578
|
+
// Fetch model's config.json from HuggingFace
|
|
579
|
+
console.log(`\n🔍 Checking model: ${modelId}\n`);
|
|
580
|
+
console.log(' Fetching model config from HuggingFace...');
|
|
581
|
+
|
|
582
|
+
const hfClient = new HuggingFaceClient({ timeout: 10000 });
|
|
583
|
+
const config = await hfClient.fetchModelConfig(modelId);
|
|
584
|
+
|
|
585
|
+
if (!config) {
|
|
586
|
+
console.log(`\n ❌ Could not fetch config.json for "${modelId}".`);
|
|
587
|
+
console.log(' Verify the model ID is correct and accessible on HuggingFace.');
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
const modelType = config.model_type;
|
|
592
|
+
if (!modelType) {
|
|
593
|
+
console.log(`\n ❌ No "model_type" field found in config.json for "${modelId}".`);
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
console.log(` Model type: ${modelType}`);
|
|
598
|
+
|
|
599
|
+
// Load model-servers catalog
|
|
600
|
+
let catalog;
|
|
601
|
+
try {
|
|
602
|
+
catalog = JSON.parse(readFileSync(catalogPath, 'utf8'));
|
|
603
|
+
} catch (err) {
|
|
604
|
+
console.log(`\n ❌ Could not read model-servers catalog: ${err.message}`);
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// Check model_type against all server entries
|
|
609
|
+
const compatible = [];
|
|
610
|
+
const incompatible = [];
|
|
611
|
+
let hasAnyData = false;
|
|
612
|
+
|
|
613
|
+
for (const [server, entries] of Object.entries(catalog)) {
|
|
614
|
+
for (const entry of entries) {
|
|
615
|
+
const version = entry.labels?.framework_version || '(unknown)';
|
|
616
|
+
const supported = entry.supportedModelTypes;
|
|
617
|
+
|
|
618
|
+
if (!supported || supported.length === 0) continue;
|
|
619
|
+
|
|
620
|
+
hasAnyData = true;
|
|
621
|
+
const modelTypeLower = modelType.toLowerCase();
|
|
622
|
+
if (supported.includes(modelTypeLower) || supported.includes(modelType)) {
|
|
623
|
+
compatible.push({ server, version });
|
|
624
|
+
} else {
|
|
625
|
+
incompatible.push({ server, version });
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// Display results
|
|
631
|
+
if (!hasAnyData) {
|
|
632
|
+
console.log('\n ⚠️ No architecture data available. Run "registry sync-architectures" first.');
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
if (compatible.length > 0) {
|
|
637
|
+
console.log('\n ✅ Compatible server versions:');
|
|
638
|
+
for (const { server, version } of compatible) {
|
|
639
|
+
console.log(` • ${server} ${version}`);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
if (incompatible.length > 0) {
|
|
644
|
+
console.log('\n ⚠️ Potentially incompatible server versions:');
|
|
645
|
+
for (const { server, version } of incompatible) {
|
|
646
|
+
console.log(` • ${server} ${version}`);
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
if (compatible.length === 0) {
|
|
651
|
+
console.log(`\n ⚠️ Model architecture "${modelType}" was not found in any server's supported types.`);
|
|
652
|
+
console.log(' This may indicate the model requires a newer server version,');
|
|
653
|
+
console.log(' or it may work via trust_remote_code. Check server documentation for details.');
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
console.log('');
|
|
657
|
+
}
|
|
658
|
+
|
|
434
659
|
/**
|
|
435
660
|
* Show registry usage help.
|
|
436
661
|
*/
|
|
@@ -449,6 +674,9 @@ SUBCOMMANDS:
|
|
|
449
674
|
export [id] [--status <status>] Export entries as JSON
|
|
450
675
|
import <file> [--merge|--replace] Import entries from JSON
|
|
451
676
|
search [filters] Search entries with glob matching
|
|
677
|
+
sync-architectures Sync supported model types from server repos
|
|
678
|
+
list-architectures Show supported architectures per server version
|
|
679
|
+
check <model-id> Check model compatibility with server versions
|
|
452
680
|
|
|
453
681
|
FILTER OPTIONS (for list and search):
|
|
454
682
|
--backend <backend> Filter by backend (e.g., vllm, flask)
|
|
@@ -467,6 +695,10 @@ IMPORT OPTIONS:
|
|
|
467
695
|
--merge Keep both existing and imported on conflict
|
|
468
696
|
--replace Overwrite existing with imported on conflict
|
|
469
697
|
|
|
698
|
+
LIST-ARCHITECTURES OPTIONS:
|
|
699
|
+
--server <name> Show full model type list for a specific server
|
|
700
|
+
--verbose Show full model type list for all servers
|
|
701
|
+
|
|
470
702
|
OTHER OPTIONS:
|
|
471
703
|
--project Use project-level registry instead of personal
|
|
472
704
|
|
|
@@ -481,6 +713,10 @@ EXAMPLES:
|
|
|
481
713
|
ml-container-creator registry export a1b2c3d4
|
|
482
714
|
ml-container-creator registry import team-deployments.json --merge
|
|
483
715
|
ml-container-creator registry search --model "meta-llama/*" --backend vllm
|
|
716
|
+
ml-container-creator registry list-architectures
|
|
717
|
+
ml-container-creator registry list-architectures --server vllm
|
|
718
|
+
ml-container-creator registry list-architectures --verbose
|
|
719
|
+
ml-container-creator registry check meta-llama/Llama-2-7b-chat-hf
|
|
484
720
|
`);
|
|
485
721
|
}
|
|
486
722
|
|
|
@@ -18,11 +18,11 @@ const __dirname = dirname(__filename);
|
|
|
18
18
|
|
|
19
19
|
// Catalog file paths relative to this module
|
|
20
20
|
const CATALOG_PATHS = {
|
|
21
|
-
modelServers: resolve(__dirname, '../../servers/
|
|
22
|
-
tritonBackends: resolve(__dirname, '../../servers/
|
|
23
|
-
instances: resolve(__dirname, '../../servers/
|
|
24
|
-
popularTransformers: resolve(__dirname, '../../servers/
|
|
25
|
-
popularDiffusors: resolve(__dirname, '../../servers/
|
|
21
|
+
modelServers: resolve(__dirname, '../../servers/lib/catalogs/model-servers.json'),
|
|
22
|
+
tritonBackends: resolve(__dirname, '../../servers/lib/catalogs/triton-backends.json'),
|
|
23
|
+
instances: resolve(__dirname, '../../servers/lib/catalogs/instances.json'),
|
|
24
|
+
popularTransformers: resolve(__dirname, '../../servers/lib/catalogs/popular-transformers.json'),
|
|
25
|
+
popularDiffusors: resolve(__dirname, '../../servers/lib/catalogs/popular-diffusors.json')
|
|
26
26
|
};
|
|
27
27
|
|
|
28
28
|
class RegistryLoader {
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Schema Sync — Downloads AWS service model files from the AWS SDK GitHub source
|
|
6
|
+
* and stores them in the local schema registry.
|
|
7
|
+
*
|
|
8
|
+
* Requirements: 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 10.1
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import https from 'node:https';
|
|
12
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'node:fs';
|
|
13
|
+
import path from 'node:path';
|
|
14
|
+
import os from 'node:os';
|
|
15
|
+
|
|
16
|
+
const SERVICES = ['sagemaker', 'iam', 'ecr', 's3'];
|
|
17
|
+
|
|
18
|
+
const SOURCE_BASE_URL = 'https://raw.githubusercontent.com/aws/aws-sdk-js-v3/main/codegen/sdk-codegen/aws-models';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Get the default schema registry path.
|
|
22
|
+
* @returns {string}
|
|
23
|
+
*/
|
|
24
|
+
export function getRegistryPath() {
|
|
25
|
+
return path.join(os.homedir(), '.ml-container-creator', 'schemas');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Download a file from a URL using the built-in https module.
|
|
30
|
+
* @param {string} url - URL to download
|
|
31
|
+
* @returns {Promise<string>} - Response body as string
|
|
32
|
+
*/
|
|
33
|
+
function downloadFile(url) {
|
|
34
|
+
return new Promise((resolve, reject) => {
|
|
35
|
+
https.get(url, (res) => {
|
|
36
|
+
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
37
|
+
downloadFile(res.headers.location).then(resolve).catch(reject);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (res.statusCode !== 200) {
|
|
42
|
+
reject(new Error(`HTTP ${res.statusCode} for ${url}`));
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const chunks = [];
|
|
47
|
+
res.on('data', (chunk) => chunks.push(chunk));
|
|
48
|
+
res.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
|
|
49
|
+
res.on('error', reject);
|
|
50
|
+
}).on('error', reject);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Count shapes and enums in a service model.
|
|
56
|
+
* @param {object} model - Parsed service model JSON
|
|
57
|
+
* @returns {{ shapeCount: number, enumCount: number, version: string }}
|
|
58
|
+
*/
|
|
59
|
+
function getModelStats(model) {
|
|
60
|
+
const shapes = model.shapes || {};
|
|
61
|
+
let shapeCount = 0;
|
|
62
|
+
let enumCount = 0;
|
|
63
|
+
|
|
64
|
+
for (const shape of Object.values(shapes)) {
|
|
65
|
+
shapeCount++;
|
|
66
|
+
if (shape.type === 'string' && shape.enum) {
|
|
67
|
+
enumCount++;
|
|
68
|
+
}
|
|
69
|
+
// Smithy models use traits for enums
|
|
70
|
+
if (shape.traits && shape.traits['smithy.api#enum']) {
|
|
71
|
+
enumCount++;
|
|
72
|
+
}
|
|
73
|
+
// Smithy v2 enum shapes
|
|
74
|
+
if (shape.type === 'enum') {
|
|
75
|
+
enumCount++;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const version = model.metadata?.apiVersion || '';
|
|
80
|
+
|
|
81
|
+
return { shapeCount, enumCount, version };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Sync all service models from the AWS SDK GitHub source.
|
|
86
|
+
* @param {object} [options]
|
|
87
|
+
* @param {string} [options.registryPath] - Override registry path
|
|
88
|
+
* @param {function} [options.downloadFn] - Override download function (for testing)
|
|
89
|
+
* @returns {Promise<{ success: boolean, services: object }>}
|
|
90
|
+
*/
|
|
91
|
+
export async function syncSchemas(options = {}) {
|
|
92
|
+
const registryPath = options.registryPath || getRegistryPath();
|
|
93
|
+
const download = options.downloadFn || downloadFile;
|
|
94
|
+
|
|
95
|
+
// Ensure registry directory exists
|
|
96
|
+
mkdirSync(registryPath, { recursive: true });
|
|
97
|
+
|
|
98
|
+
const serviceStats = {};
|
|
99
|
+
let hasErrors = false;
|
|
100
|
+
|
|
101
|
+
for (const service of SERVICES) {
|
|
102
|
+
const url = `${SOURCE_BASE_URL}/${service}.json`;
|
|
103
|
+
const serviceDir = path.join(registryPath, service);
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
console.log(` Syncing ${service}...`);
|
|
107
|
+
const content = await download(url);
|
|
108
|
+
|
|
109
|
+
// Parse to extract stats
|
|
110
|
+
let model;
|
|
111
|
+
try {
|
|
112
|
+
model = JSON.parse(content);
|
|
113
|
+
} catch (parseErr) {
|
|
114
|
+
console.log(` ⚠️ ${service}: Failed to parse model — ${parseErr.message}`);
|
|
115
|
+
hasErrors = true;
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const stats = getModelStats(model);
|
|
120
|
+
|
|
121
|
+
// Store the file
|
|
122
|
+
mkdirSync(serviceDir, { recursive: true });
|
|
123
|
+
writeFileSync(path.join(serviceDir, 'service-2.json'), content, 'utf8');
|
|
124
|
+
|
|
125
|
+
serviceStats[service] = {
|
|
126
|
+
shapeCount: stats.shapeCount,
|
|
127
|
+
enumCount: stats.enumCount,
|
|
128
|
+
version: stats.version
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
console.log(` ✅ ${service}: ${stats.shapeCount} shapes, ${stats.enumCount} enums`);
|
|
132
|
+
} catch (err) {
|
|
133
|
+
console.log(` ⚠️ ${service}: ${err.message}`);
|
|
134
|
+
hasErrors = true;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Write manifest
|
|
139
|
+
const manifest = {
|
|
140
|
+
lastSynced: new Date().toISOString(),
|
|
141
|
+
services: serviceStats,
|
|
142
|
+
source: 'https://github.com/aws/aws-sdk-js-v3/tree/main/codegen/sdk-codegen/aws-models'
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
writeFileSync(
|
|
146
|
+
path.join(registryPath, 'manifest.json'),
|
|
147
|
+
JSON.stringify(manifest, null, 4),
|
|
148
|
+
'utf8'
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
return { success: !hasErrors, services: serviceStats, manifest };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Load the manifest from the schema registry.
|
|
156
|
+
* @param {string} [registryPath] - Override registry path
|
|
157
|
+
* @returns {object|null} Parsed manifest or null if not found
|
|
158
|
+
*/
|
|
159
|
+
export function loadManifest(registryPath) {
|
|
160
|
+
const regPath = registryPath || getRegistryPath();
|
|
161
|
+
const manifestPath = path.join(regPath, 'manifest.json');
|
|
162
|
+
|
|
163
|
+
if (!existsSync(manifestPath)) {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
try {
|
|
168
|
+
return JSON.parse(readFileSync(manifestPath, 'utf8'));
|
|
169
|
+
} catch {
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Load a service model from the registry.
|
|
176
|
+
* @param {string} serviceName - Service name (e.g., 'sagemaker')
|
|
177
|
+
* @param {string} [registryPath] - Override registry path
|
|
178
|
+
* @returns {string|null} Raw file content or null if not found
|
|
179
|
+
*/
|
|
180
|
+
export function loadServiceModel(serviceName, registryPath) {
|
|
181
|
+
const regPath = registryPath || getRegistryPath();
|
|
182
|
+
const modelPath = path.join(regPath, serviceName, 'service-2.json');
|
|
183
|
+
|
|
184
|
+
if (!existsSync(modelPath)) {
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return readFileSync(modelPath, 'utf8');
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Store a service model in the registry.
|
|
193
|
+
* @param {string} serviceName - Service name (e.g., 'sagemaker')
|
|
194
|
+
* @param {string} content - Raw file content to store
|
|
195
|
+
* @param {string} [registryPath] - Override registry path
|
|
196
|
+
*/
|
|
197
|
+
export function storeServiceModel(serviceName, content, registryPath) {
|
|
198
|
+
const regPath = registryPath || getRegistryPath();
|
|
199
|
+
const serviceDir = path.join(regPath, serviceName);
|
|
200
|
+
|
|
201
|
+
mkdirSync(serviceDir, { recursive: true });
|
|
202
|
+
writeFileSync(path.join(serviceDir, 'service-2.json'), content, 'utf8');
|
|
203
|
+
}
|