Skip to content

Commit 0812ade

Browse files
authored
Merge pull request #525 from objectstack-ai/copilot/fix-authentication-error
2 parents 44532b3 + b32369e commit 0812ade

1 file changed

Lines changed: 107 additions & 19 deletions

File tree

packages/spec/scripts/build-schemas.ts

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

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

9+
// Retry and delay configuration
10+
const RETRY_DELAY_BASE_MS = 100; // Base delay in ms, multiplied by retry attempt number
11+
const FS_SYNC_DELAY_MS = 50; // Delay after rmSync to ensure filesystem consistency
12+
const MAX_RETRIES = 3; // Maximum number of retry attempts
13+
14+
/**
15+
* Synchronous sleep utility using a busy-wait loop
16+
* Only use for short delays in build scripts where blocking is acceptable
17+
*
18+
* Note: This blocks the event loop and consumes CPU. For production code,
19+
* use async/await with setTimeout. For build scripts, this simple synchronous
20+
* approach is acceptable as we need to ensure filesystem operations complete
21+
* before proceeding.
22+
*/
23+
function sleepSync(ms: number): void {
24+
const end = Date.now() + ms;
25+
while (Date.now() < end) {
26+
// Busy wait
27+
}
28+
}
29+
30+
/**
31+
* Safely ensure directory exists with retry logic
32+
*/
33+
function ensureDir(dirPath: string, retries = MAX_RETRIES): void {
34+
for (let i = 0; i < retries; i++) {
35+
try {
36+
if (!fs.existsSync(dirPath)) {
37+
fs.mkdirSync(dirPath, { recursive: true });
38+
}
39+
// Verify the directory was created successfully
40+
if (fs.existsSync(dirPath) && fs.statSync(dirPath).isDirectory()) {
41+
return;
42+
}
43+
} catch (error) {
44+
if (i === retries - 1) {
45+
throw new Error(`Failed to create directory ${dirPath}: ${error}`);
46+
}
47+
// Wait a bit before retrying with exponential backoff
48+
const delay = RETRY_DELAY_BASE_MS * (i + 1);
49+
sleepSync(delay);
50+
}
51+
}
52+
}
53+
54+
/**
55+
* Safely write file with retry logic
56+
*/
57+
function writeFileWithRetry(filePath: string, content: string, retries = MAX_RETRIES): void {
58+
for (let i = 0; i < retries; i++) {
59+
try {
60+
// Ensure the parent directory exists
61+
const dir = path.dirname(filePath);
62+
ensureDir(dir);
63+
64+
fs.writeFileSync(filePath, content);
65+
return;
66+
} catch (error) {
67+
if (i === retries - 1) {
68+
throw new Error(`Failed to write file ${filePath}: ${error}`);
69+
}
70+
// Wait a bit before retrying with exponential backoff
71+
const delay = RETRY_DELAY_BASE_MS * (i + 1);
72+
sleepSync(delay);
73+
}
74+
}
75+
}
76+
977
// Clean output directory ensures no stale files remain
1078
if (fs.existsSync(OUT_DIR)) {
1179
console.log(`Cleaning output directory: ${OUT_DIR}`);
12-
fs.rmSync(OUT_DIR, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
80+
fs.rmSync(OUT_DIR, { recursive: true, force: true, maxRetries: MAX_RETRIES, retryDelay: RETRY_DELAY_BASE_MS });
81+
82+
// Wait a bit to ensure file system has synced
83+
// This prevents ENOENT errors on some file systems, particularly in CI environments
84+
sleepSync(FS_SYNC_DELAY_MS);
1385
}
1486

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

2090
console.log(`Generating JSON Schemas to ${OUT_DIR}...`);
2191

2292
let count = 0;
93+
let errorCount = 0;
2394

2495
// Protocol now exports namespaces (Data, UI, System, AI, API)
2596
// We need to iterate through each namespace
2697
for (const [namespaceName, namespaceExports] of Object.entries(Protocol)) {
2798
if (typeof namespaceExports === 'object' && namespaceExports !== null) {
2899
// Create category subdirectory (e.g., data, ui, system, ai, api)
29100
const categoryDir = path.join(OUT_DIR, namespaceName.toLowerCase());
30-
if (!fs.existsSync(categoryDir)) {
31-
fs.mkdirSync(categoryDir, { recursive: true });
101+
102+
try {
103+
ensureDir(categoryDir);
104+
} catch (error) {
105+
console.error(`Failed to create directory for namespace ${namespaceName}:`, error);
106+
errorCount++;
107+
continue;
32108
}
33109

34110
console.log(`\n[${namespaceName}]`);
@@ -39,21 +115,33 @@ for (const [namespaceName, namespaceExports] of Object.entries(Protocol)) {
39115
if (value instanceof z.ZodType) {
40116
const schemaName = key.endsWith('Schema') ? key.replace('Schema', '') : key;
41117

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++;
118+
try {
119+
// Convert to JSON Schema
120+
const jsonSchema = zodToJsonSchema(value, {
121+
name: schemaName,
122+
$refStrategy: "none" // We want self-contained schemas for now
123+
});
124+
125+
const fileName = `${schemaName}.json`;
126+
const filePath = path.join(categoryDir, fileName);
127+
128+
writeFileWithRetry(filePath, JSON.stringify(jsonSchema, null, 2));
129+
console.log(`✓ ${namespaceName.toLowerCase()}/${fileName}`);
130+
count++;
131+
} catch (error) {
132+
console.error(`Failed to generate schema for ${namespaceName}.${key}:`, error);
133+
errorCount++;
134+
}
54135
}
55136
}
56137
}
57138
}
58139

59-
console.log(`\nSuccessfully generated ${count} schemas.`);
140+
if (errorCount > 0) {
141+
console.error(`\n❌ Completed with ${errorCount} error(s). ${count} schemas generated successfully.`);
142+
console.error(`\nNote: Partial schema generation occurred. Some schemas may be missing.`);
143+
console.error(`This typically indicates a Zod schema definition error or file system issue.`);
144+
process.exit(1);
145+
}
146+
147+
console.log(`\n✅ Successfully generated ${count} schemas.`);

0 commit comments

Comments
 (0)