Skip to content

Commit 1ad411e

Browse files
ericyangpanclaude
andcommitted
chore(scripts): add migration scripts and update documentation
Add temporary migration scripts for schema update and update script documentation. - Add temp-update-model-schema.mjs for schema migration - Add temp-add-release-date.mjs for adding releaseDate field - Update scripts/README.md to remove outdated validation script references - Update runner.mjs formatting These migration scripts were used to update all model manifests to the new schema. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent 85896e9 commit 1ad411e

File tree

4 files changed

+537
-17
lines changed

4 files changed

+537
-17
lines changed

scripts/README.md

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
# Scripts Documentation
22

3-
This directory contains utility scripts for managing and validating the AI Coding Stack project. Scripts are organized into four categories:
3+
This directory contains utility scripts for managing and validating the AI Coding Stack project. Scripts are organized into three categories:
44

5-
- **`validate/`** - Validation scripts that check data integrity
65
- **`generate/`** - Generation scripts that create derived files
76
- **`refactor/`** - Refactoring scripts that reorganize or reformat data
87
- **`fetch/`** - Data fetching scripts that retrieve external data
@@ -11,11 +10,6 @@ This directory contains utility scripts for managing and validating the AI Codin
1110

1211
```
1312
scripts/
14-
├── validate/
15-
│ ├── index.mjs # Entry point for all validation scripts
16-
│ ├── validate-manifests.mjs
17-
│ ├── validate-github-stars.mjs
18-
│ └── validate-urls.mjs
1913
├── generate/
2014
│ ├── index.mjs # Entry point for all generation scripts
2115
│ ├── generate-manifest-indexes.mjs
@@ -53,10 +47,6 @@ npm run fetch
5347
You can also run individual scripts by passing the script name to the entry point:
5448

5549
```bash
56-
# Validation tests
57-
npm run test:validate
58-
npm run test:urls
59-
6050
# Generation scripts
6151
npm run generate:manifests
6252
npm run generate:metadata
@@ -71,9 +61,6 @@ npm run fetch:github-stars
7161
Or directly using Node:
7262

7363
```bash
74-
# Run validation tests
75-
node ./node_modules/vitest/vitest.mjs run tests/validate --reporter=verbose
76-
7764
# Run generation/fetch scripts
7865
node scripts/generate/index.mjs metadata
7966
node scripts/fetch/index.mjs github-stars

scripts/_shared/runner.mjs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,11 @@ export function getCategoryDir(categoryDir) {
5555
*
5656
* Script names are generated by:
5757
* 1. Removing .mjs extension
58-
* 2. If filename starts with category prefix (e.g., "validate-"), remove it
58+
* 2. If filename starts with category prefix (e.g., "generate-", "fetch-"), remove it
5959
* 3. Otherwise, use the full filename without extension
6060
*
6161
* Examples:
62-
* - "validate-manifests.mjs" in validate/ -> "manifests"
62+
* - "generate-manifest-indexes.mjs" in generate/ -> "manifest-indexes"
6363
* - "sort-manifest-fields.mjs" in refactor/ -> "sort-manifest-fields" (no prefix)
6464
* - "refactor-sort-fields.mjs" in refactor/ -> "sort-fields" (prefix removed)
6565
*/
@@ -75,7 +75,7 @@ async function discoverScripts(categoryDir) {
7575
// Generate script name from filename (without .mjs extension)
7676
const baseName = entry.name.replace(/\.mjs$/, '')
7777

78-
// Remove category prefix if present (e.g., "generate-", "validate-", "fetch-")
78+
// Remove category prefix if present (e.g., "generate-", "fetch-")
7979
// If filename doesn't start with prefix, use full name without extension
8080
const categoryPrefix = `${path.basename(categoryDir)}-`
8181
const scriptName = baseName.startsWith(categoryPrefix)

scripts/temp-add-release-date.mjs

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
#!/usr/bin/env node
2+
3+
/**
4+
* Temporary script to add releaseDate field to all model manifest files
5+
* Attempts to extract release date from description, otherwise sets to null
6+
*/
7+
8+
import fs from 'node:fs/promises'
9+
import path from 'node:path'
10+
import { fileURLToPath } from 'node:url'
11+
12+
const __filename = fileURLToPath(import.meta.url)
13+
const __dirname = path.dirname(__filename)
14+
const ROOT_DIR = path.resolve(__dirname, '..')
15+
const MODELS_DIR = path.join(ROOT_DIR, 'manifests', 'models')
16+
17+
/**
18+
* Extract release date from description text
19+
* Looks for patterns like "Released in August 2025", "Released in April 2025", etc.
20+
* Returns ISO 8601 date string (YYYY-MM-DD) or null if not found
21+
*/
22+
function extractReleaseDate(description) {
23+
if (!description) return null
24+
25+
// Pattern: "Released in [Month] [Year]"
26+
const releasedPattern = /Released in\s+(\w+)\s+(\d{4})/i
27+
const match = description.match(releasedPattern)
28+
29+
if (match) {
30+
const monthName = match[1]
31+
const year = match[2]
32+
33+
// Map month names to numbers
34+
const monthMap = {
35+
january: '01',
36+
february: '02',
37+
march: '03',
38+
april: '04',
39+
may: '05',
40+
june: '06',
41+
july: '07',
42+
august: '08',
43+
september: '09',
44+
october: '10',
45+
november: '11',
46+
december: '12',
47+
}
48+
49+
const month = monthMap[monthName.toLowerCase()]
50+
if (month) {
51+
// Use first day of the month as default
52+
return `${year}-${month}-01`
53+
}
54+
}
55+
56+
return null
57+
}
58+
59+
/**
60+
* Load and parse a JSON file
61+
*/
62+
async function loadJSON(filePath) {
63+
try {
64+
const content = await fs.readFile(filePath, 'utf-8')
65+
const cleanContent = content.replace(/^\uFEFF/, '').trim()
66+
if (!cleanContent) {
67+
throw new Error('File is empty')
68+
}
69+
return JSON.parse(cleanContent)
70+
} catch (error) {
71+
if (error instanceof SyntaxError) {
72+
throw new Error(`JSON parse error: ${error.message} (file: ${filePath})`)
73+
}
74+
throw error
75+
}
76+
}
77+
78+
/**
79+
* Process a single model file
80+
*/
81+
async function processModelFile(filePath, fileName) {
82+
try {
83+
const manifest = await loadJSON(filePath)
84+
85+
// Skip if releaseDate already exists
86+
if (manifest.releaseDate !== undefined) {
87+
console.log(` ⏭️ ${fileName} - releaseDate already exists: ${manifest.releaseDate}`)
88+
return { skipped: true }
89+
}
90+
91+
// Try to extract release date from description
92+
let releaseDate = extractReleaseDate(manifest.description)
93+
94+
// If not found in main description, check translations
95+
if (!releaseDate && manifest.translations) {
96+
for (const locale of Object.values(manifest.translations)) {
97+
if (locale.description) {
98+
releaseDate = extractReleaseDate(locale.description)
99+
if (releaseDate) break
100+
}
101+
}
102+
}
103+
104+
// Set to null if still not found
105+
if (!releaseDate) {
106+
releaseDate = null
107+
}
108+
109+
// Insert releaseDate after platformUrls
110+
const orderedManifest = {}
111+
let inserted = false
112+
113+
for (const [key, value] of Object.entries(manifest)) {
114+
orderedManifest[key] = value
115+
if (key === 'platformUrls' && !inserted) {
116+
orderedManifest.releaseDate = releaseDate
117+
inserted = true
118+
}
119+
}
120+
121+
// If platformUrls doesn't exist, add releaseDate before benchmarks
122+
if (!inserted) {
123+
const newManifest = {}
124+
for (const [key, value] of Object.entries(manifest)) {
125+
if (key === 'benchmarks' && !inserted) {
126+
newManifest.releaseDate = releaseDate
127+
inserted = true
128+
}
129+
newManifest[key] = value
130+
}
131+
// If benchmarks also doesn't exist, just append at the end
132+
if (!inserted) {
133+
Object.assign(orderedManifest, manifest)
134+
orderedManifest.releaseDate = releaseDate
135+
} else {
136+
Object.assign(orderedManifest, newManifest)
137+
}
138+
}
139+
140+
// Write back to file
141+
const jsonContent = `${JSON.stringify(orderedManifest, null, 2)}\n`
142+
await fs.writeFile(filePath, jsonContent, 'utf-8')
143+
144+
const dateStr = releaseDate || 'null'
145+
console.log(` ✅ ${fileName} - Added releaseDate: ${dateStr}`)
146+
return { skipped: false, releaseDate }
147+
} catch (error) {
148+
console.error(` ❌ ${fileName} - Error: ${error.message}`)
149+
return { skipped: false, error: error.message }
150+
}
151+
}
152+
153+
/**
154+
* Main function
155+
*/
156+
async function main() {
157+
console.log('🔄 Adding releaseDate field to all model manifests...\n')
158+
159+
try {
160+
const entries = await fs.readdir(MODELS_DIR, { withFileTypes: true })
161+
const jsonFiles = entries
162+
.filter(entry => entry.isFile() && entry.name.endsWith('.json'))
163+
.map(entry => entry.name)
164+
.sort()
165+
166+
if (jsonFiles.length === 0) {
167+
console.log(' ⚠️ No model files found')
168+
return
169+
}
170+
171+
console.log(`Found ${jsonFiles.length} model file(s)\n`)
172+
173+
const stats = {
174+
total: jsonFiles.length,
175+
processed: 0,
176+
skipped: 0,
177+
errors: 0,
178+
extracted: 0,
179+
nullDates: 0,
180+
}
181+
182+
for (const fileName of jsonFiles) {
183+
const filePath = path.join(MODELS_DIR, fileName)
184+
const result = await processModelFile(filePath, fileName)
185+
186+
if (result.skipped) {
187+
stats.skipped++
188+
} else if (result.error) {
189+
stats.errors++
190+
} else {
191+
stats.processed++
192+
if (result.releaseDate) {
193+
stats.extracted++
194+
} else {
195+
stats.nullDates++
196+
}
197+
}
198+
}
199+
200+
// Print summary
201+
console.log(`\n${'='.repeat(60)}`)
202+
console.log('📊 Summary')
203+
console.log('='.repeat(60))
204+
console.log(`Total files: ${stats.total}`)
205+
console.log(`Processed: ${stats.processed}`)
206+
console.log(`Skipped (already has releaseDate): ${stats.skipped}`)
207+
console.log(`Extracted from description: ${stats.extracted}`)
208+
console.log(`Set to null (not found): ${stats.nullDates}`)
209+
if (stats.errors > 0) {
210+
console.log(`Errors: ${stats.errors}`)
211+
}
212+
console.log('='.repeat(60))
213+
214+
if (stats.errors === 0) {
215+
console.log('\n✨ All files processed successfully!')
216+
} else {
217+
console.log('\n⚠️ Processing completed with some errors.')
218+
}
219+
} catch (error) {
220+
console.error('❌ Fatal error:', error.message)
221+
process.exit(1)
222+
}
223+
}
224+
225+
main().catch(console.error)

0 commit comments

Comments
 (0)