Skip to content

Commit d47403c

Browse files
Copilothotlong
andcommitted
Add retry logic and better error handling to schema generation
- Add ensureDir helper with retry logic for directory creation - Add writeFileWithRetry helper with retry logic for file writes - Add sync delay after rmSync to ensure filesystem consistency - Add error tracking and detailed error messages - Gracefully handle errors and report them without stopping the entire build This fixes intermittent ENOENT errors in CI when writing schema files, particularly the ApiRegistry.json file. Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
1 parent c069910 commit d47403c

1 file changed

Lines changed: 84 additions & 18 deletions

File tree

packages/spec/scripts/build-schemas.ts

Lines changed: 84 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,29 +6,85 @@ import * as Protocol from '../src/index';
66

77
const OUT_DIR = path.resolve(__dirname, '../json-schema');
88

9+
/**
10+
* Safely ensure directory exists with retry logic
11+
*/
12+
function ensureDir(dirPath: string, retries = 3): void {
13+
for (let i = 0; i < retries; i++) {
14+
try {
15+
if (!fs.existsSync(dirPath)) {
16+
fs.mkdirSync(dirPath, { recursive: true });
17+
}
18+
// Verify the directory was created successfully
19+
if (fs.existsSync(dirPath) && fs.statSync(dirPath).isDirectory()) {
20+
return;
21+
}
22+
} catch (error) {
23+
if (i === retries - 1) {
24+
throw new Error(`Failed to create directory ${dirPath}: ${error}`);
25+
}
26+
// Wait a bit before retrying
27+
const delay = 100 * (i + 1);
28+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, delay);
29+
}
30+
}
31+
}
32+
33+
/**
34+
* Safely write file with retry logic
35+
*/
36+
function writeFileWithRetry(filePath: string, content: string, retries = 3): void {
37+
for (let i = 0; i < retries; i++) {
38+
try {
39+
// Ensure the parent directory exists
40+
const dir = path.dirname(filePath);
41+
ensureDir(dir);
42+
43+
fs.writeFileSync(filePath, content);
44+
return;
45+
} catch (error) {
46+
if (i === retries - 1) {
47+
throw new Error(`Failed to write file ${filePath}: ${error}`);
48+
}
49+
// Wait a bit before retrying
50+
const delay = 100 * (i + 1);
51+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, delay);
52+
}
53+
}
54+
}
55+
956
// Clean output directory ensures no stale files remain
1057
if (fs.existsSync(OUT_DIR)) {
1158
console.log(`Cleaning output directory: ${OUT_DIR}`);
1259
fs.rmSync(OUT_DIR, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
60+
61+
// Wait a bit to ensure file system has synced
62+
// This prevents ENOENT errors on some file systems
63+
const syncDelay = 50;
64+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, syncDelay);
1365
}
1466

1567
// Ensure output directory exists
16-
if (!fs.existsSync(OUT_DIR)) {
17-
fs.mkdirSync(OUT_DIR, { recursive: true });
18-
}
68+
ensureDir(OUT_DIR);
1969

2070
console.log(`Generating JSON Schemas to ${OUT_DIR}...`);
2171

2272
let count = 0;
73+
let errorCount = 0;
2374

2475
// Protocol now exports namespaces (Data, UI, System, AI, API)
2576
// We need to iterate through each namespace
2677
for (const [namespaceName, namespaceExports] of Object.entries(Protocol)) {
2778
if (typeof namespaceExports === 'object' && namespaceExports !== null) {
2879
// Create category subdirectory (e.g., data, ui, system, ai, api)
2980
const categoryDir = path.join(OUT_DIR, namespaceName.toLowerCase());
30-
if (!fs.existsSync(categoryDir)) {
31-
fs.mkdirSync(categoryDir, { recursive: true });
81+
82+
try {
83+
ensureDir(categoryDir);
84+
} catch (error) {
85+
console.error(`Failed to create directory for namespace ${namespaceName}:`, error);
86+
errorCount++;
87+
continue;
3288
}
3389

3490
console.log(`\n[${namespaceName}]`);
@@ -39,21 +95,31 @@ for (const [namespaceName, namespaceExports] of Object.entries(Protocol)) {
3995
if (value instanceof z.ZodType) {
4096
const schemaName = key.endsWith('Schema') ? key.replace('Schema', '') : key;
4197

42-
// Convert to JSON Schema
43-
const jsonSchema = zodToJsonSchema(value, {
44-
name: schemaName,
45-
$refStrategy: "none" // We want self-contained schemas for now
46-
});
47-
48-
const fileName = `${schemaName}.json`;
49-
const filePath = path.join(categoryDir, fileName);
50-
51-
fs.writeFileSync(filePath, JSON.stringify(jsonSchema, null, 2));
52-
console.log(`✓ ${namespaceName.toLowerCase()}/${fileName}`);
53-
count++;
98+
try {
99+
// Convert to JSON Schema
100+
const jsonSchema = zodToJsonSchema(value, {
101+
name: schemaName,
102+
$refStrategy: "none" // We want self-contained schemas for now
103+
});
104+
105+
const fileName = `${schemaName}.json`;
106+
const filePath = path.join(categoryDir, fileName);
107+
108+
writeFileWithRetry(filePath, JSON.stringify(jsonSchema, null, 2));
109+
console.log(`✓ ${namespaceName.toLowerCase()}/${fileName}`);
110+
count++;
111+
} catch (error) {
112+
console.error(`Failed to generate schema for ${namespaceName}.${key}:`, error);
113+
errorCount++;
114+
}
54115
}
55116
}
56117
}
57118
}
58119

59-
console.log(`\nSuccessfully generated ${count} schemas.`);
120+
if (errorCount > 0) {
121+
console.error(`\n❌ Completed with ${errorCount} error(s). ${count} schemas generated successfully.`);
122+
process.exit(1);
123+
}
124+
125+
console.log(`\n✅ Successfully generated ${count} schemas.`);

0 commit comments

Comments
 (0)