Skip to content

Commit 24cf853

Browse files
committed
feat(schemas): add error handling and improve schema generation
- Introduced `handleBunError` utility for standardized error handling. - Updated schema generation scripts to use `handleBunError`. - Enhanced error messages with actionable advice and resolution steps. - Improved schema generation logic for better reliability. - Updated dependencies in `package.json` and `pnpm-lock.yaml`.
1 parent 18ed87a commit 24cf853

File tree

6 files changed

+208
-64
lines changed

6 files changed

+208
-64
lines changed

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@
156156
"@types/js-yaml": "^4.0.9",
157157
"@types/json-bigint": "^1.0.1",
158158
"@types/mkdirp": "^1.0.2",
159-
"@types/node": "^18.15.3",
159+
"@types/node": "^18.19.86",
160160
"@types/sanitize-html": "^2.8.0",
161161
"@types/tar-fs": "^2.0.1",
162162
"@types/unist": "^3.0.0",

pnpm-lock.yaml

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

scripts/schemas/README.md

+9
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,15 @@ This will:
2525
3. Create Zod schemas for each component in the API
2626
4. Update the package exports
2727

28+
### Error Handling
29+
30+
The schema generation scripts use a standardized error handling approach for Bun system calls through the `handleBunError` utility in `lib/error-handler.ts`. This provides clear, actionable error messages when something goes wrong, including:
31+
32+
- User-friendly error messages with actionable advice
33+
- Detection of common issues (missing modules, permissions, etc.)
34+
- Clear indications of what command failed
35+
- Suggestions for resolution
36+
2837
### Using Schemas
2938

3039
Import the schemas you need:

scripts/schemas/generate-schema/index.ts

+50-24
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import chalk from 'chalk';
77
import degit from 'degit';
88
import yaml from 'js-yaml';
99

10+
import { handleBunError } from '../lib/error-handler';
11+
1012
const { values } = parseArgs({
1113
args: argv,
1214
options: {
@@ -29,34 +31,58 @@ const openApiDefinitionsFile = join(
2931
);
3032
const openApiDefinitions = Bun.file(openApiDefinitionsFile);
3133

32-
if (await openApiDefinitions.exists()) {
33-
await rm(openAPIDefinitionsDirectory, { recursive: true });
34-
}
34+
try {
35+
if (await openApiDefinitions.exists()) {
36+
await rm(openAPIDefinitionsDirectory, { recursive: true });
37+
}
3538

36-
await mkdir(openAPIDefinitionsDirectory, { recursive: true });
39+
await mkdir(openAPIDefinitionsDirectory, { recursive: true });
3740

38-
const emitter = degit('temporalio/api', {
39-
cache: false,
40-
force: true,
41-
verbose: true,
42-
});
41+
const emitter = degit('temporalio/api', {
42+
cache: false,
43+
force: true,
44+
verbose: true,
45+
});
4346

44-
emitter.on('warn', (warning) => {
45-
console.warn(chalk.bgYellow(' WARN '), warning.message);
46-
});
47+
emitter.on('warn', (warning) => {
48+
console.warn(chalk.bgYellow(' WARN '), warning.message);
49+
});
4750

48-
await emitter.clone(openAPIDefinitionsDirectory);
49-
try {
50-
await $`npx openapi-typescript ${openApiDefinitionsFile} -o ${schema} --immutable-types --alphabetize --support-array-length --empty-objects-unknown -default-non-nullable`.quiet();
51-
} catch (error) {
52-
console.error(chalk.red('Error generating schema:'), error);
53-
throw error;
54-
}
51+
await emitter.clone(openAPIDefinitionsDirectory);
52+
53+
try {
54+
const args = [
55+
openApiDefinitionsFile,
56+
'-o',
57+
schema,
58+
'--immutable-types',
59+
'--alphabetize',
60+
'--support-array-length',
61+
'--empty-objects-unknown',
62+
'-default-non-nullable',
63+
];
5564

56-
const openApiSchema = await openApiDefinitions.text();
57-
const openApiSchemaObject = yaml.load(openApiSchema);
58-
const openApiSchemaString = JSON.stringify(openApiSchemaObject, null, 2);
65+
await $`npx openapi-typescript ${args}`;
66+
} catch (error) {
67+
handleBunError(error, 'Failed to generate OpenAPI schema', {
68+
showCommand: true,
69+
exitProcess: true,
70+
});
71+
}
5972

60-
await Bun.write('./scripts/schemas/lib/schema.json', openApiSchemaString);
73+
const openApiSchema = await openApiDefinitions.text();
74+
const openApiSchemaObject = yaml.load(openApiSchema);
75+
const openApiSchemaString = JSON.stringify(openApiSchemaObject, null, 2);
6176

62-
console.log(chalk.green('Schema generated successfully:'), chalk.blue(schema));
77+
await Bun.write('./scripts/schemas/lib/schema.json', openApiSchemaString);
78+
79+
console.log(
80+
chalk.green('Schema generated successfully:'),
81+
chalk.blue(schema),
82+
);
83+
} catch (error) {
84+
handleBunError(error, 'Schema generation failed', {
85+
showStackTrace: false,
86+
exitProcess: true,
87+
});
88+
}

scripts/schemas/generate-types/index.ts

+60-38
Original file line numberDiff line numberDiff line change
@@ -3,55 +3,77 @@ import chalk from 'chalk';
33
import { kebabCase } from 'change-case';
44
import { JsonSchema, jsonSchemaToZod } from 'json-schema-to-zod';
55

6+
import { handleBunError } from '../lib/error-handler';
67
import prettier from '../lib/prettier';
78
import schema from '../lib/schema.json' assert { type: 'json' };
89

9-
// if file doesnt exist create it
10-
const index = Bun.file('./src/lib/schemas/types/index.ts');
11-
if (!(await index.exists()))
10+
try {
11+
// if file doesnt exist create it
12+
const index = Bun.file('./src/lib/schemas/types/index.ts');
13+
if (!(await index.exists()))
14+
await Bun.write('./src/lib/schemas/types/index.ts', '');
15+
const writer = index.writer();
16+
1217
await Bun.write('./src/lib/schemas/types/index.ts', '');
13-
const writer = index.writer();
14-
15-
await Bun.write('./src/lib/schemas/types/index.ts', '');
16-
17-
for (const [name, component] of Object.entries(schema.components.schemas)) {
18-
const fileName = kebabCase(name);
19-
const filePath = `./src/lib/schemas/types/${fileName}.ts`;
20-
const schema = jsonSchemaToZod(component as JsonSchema, {
21-
name: `${name}`,
22-
type: true,
23-
withJsdocs: true,
24-
module: 'esm',
25-
});
2618

27-
const formatted = await prettier.format(schema, {
28-
parser: 'typescript',
29-
});
19+
for (const [name, component] of Object.entries(schema.components.schemas)) {
20+
try {
21+
const fileName = kebabCase(name);
22+
const filePath = `./src/lib/schemas/types/${fileName}.ts`;
23+
const schema = jsonSchemaToZod(component as JsonSchema, {
24+
name: `${name}`,
25+
type: true,
26+
withJsdocs: true,
27+
module: 'esm',
28+
});
3029

31-
await Bun.write(filePath, formatted);
30+
const formatted = await prettier.format(schema, {
31+
parser: 'typescript',
32+
});
3233

33-
const exists = await Bun.file(filePath).exists();
34+
await Bun.write(filePath, formatted);
3435

35-
if (!exists) {
36-
throw new Error(`Failed to generate schema file: ${filePath}`);
37-
}
36+
const exists = await Bun.file(filePath).exists();
3837

39-
console.log(
40-
chalk.cyan(
41-
'Schema generated successfully',
42-
chalk.magenta(name),
43-
chalk.yellow(filePath),
44-
),
45-
);
38+
if (!exists) {
39+
handleBunError(
40+
new Error(`Failed to generate schema file: ${filePath}`),
41+
`Failed to generate schema for ${name}`,
42+
{ exitProcess: false },
43+
);
44+
}
4645

47-
writer.write(`export * from './${fileName}'\n`);
48-
}
46+
console.log(
47+
chalk.cyan(
48+
'Schema generated successfully',
49+
chalk.magenta(name),
50+
chalk.yellow(filePath),
51+
),
52+
);
4953

50-
writer.end();
54+
writer.write(`export * from './${fileName}'\n`);
55+
} catch (error) {
56+
handleBunError(error, `Failed to process schema component: ${name}`, {
57+
exitProcess: false,
58+
showStackTrace: false,
59+
});
60+
}
61+
}
5162

52-
try {
53-
await $`npm run format`;
63+
writer.end();
64+
65+
try {
66+
await $`npm run format`;
67+
} catch (error) {
68+
handleBunError(error, 'Failed to run code formatter', {
69+
showCommand: true,
70+
exitProcess: false,
71+
});
72+
}
73+
74+
console.log(chalk.green('✅ All schema types generated successfully!'));
5475
} catch (error) {
55-
console.error(chalk.red('Error formatting schema:'), error);
56-
throw error;
76+
handleBunError(error, 'Failed to generate schema types', {
77+
showStackTrace: true,
78+
});
5779
}

scripts/schemas/lib/error-handler.ts

+87
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import chalk from 'chalk';
2+
3+
interface ErrorOptions {
4+
showCommand?: boolean;
5+
showStackTrace?: boolean;
6+
exitProcess?: boolean;
7+
exitCode?: number;
8+
}
9+
10+
const defaultOptions: ErrorOptions = {
11+
showCommand: true,
12+
showStackTrace: false,
13+
exitProcess: true,
14+
exitCode: 1,
15+
};
16+
17+
/**
18+
* Friendly error handler for Bun system calls
19+
*/
20+
export function handleBunError(
21+
error: unknown,
22+
message = 'Command execution failed',
23+
options: ErrorOptions = defaultOptions,
24+
): never {
25+
const { showCommand, showStackTrace, exitProcess, exitCode } = {
26+
...defaultOptions,
27+
...options,
28+
};
29+
30+
// Clear current line
31+
process.stdout.write('\r\x1b[K');
32+
33+
console.error(chalk.red('❌ ERROR: ') + chalk.bold(message));
34+
35+
if (error instanceof Error) {
36+
// Check for common error patterns from Bun system calls
37+
if (error.message.includes('ENOENT')) {
38+
console.error(
39+
chalk.yellow('⚠️ A required file was not found. This might mean:'),
40+
);
41+
console.error(
42+
' - You need to run ' + chalk.cyan('pnpm install') + ' first',
43+
);
44+
console.error(' - The file path is incorrect');
45+
} else if (error.message.includes('Command failed')) {
46+
if (showCommand && error.message.includes('Command:')) {
47+
const command = error.message.split('Command:')[1]?.trim();
48+
if (command) {
49+
console.error(chalk.yellow('⚠️ The following command failed:'));
50+
console.error(' ' + chalk.cyan(command));
51+
}
52+
}
53+
} else if (error.message.includes('EACCES')) {
54+
console.error(chalk.yellow('⚠️ Permission denied. You might need to:'));
55+
console.error(' - Run with higher permissions');
56+
console.error(' - Check file/directory permissions');
57+
} else {
58+
console.error(chalk.yellow('⚠️ Error details: ') + error.message);
59+
}
60+
61+
if (showStackTrace && error.stack) {
62+
console.error('');
63+
console.error(chalk.dim('Stack trace:'));
64+
console.error(chalk.dim(error.stack));
65+
}
66+
} else {
67+
console.error(chalk.yellow('⚠️ Unknown error:'), error);
68+
}
69+
70+
console.error('');
71+
console.error(chalk.blue('💡 Try the following:'));
72+
console.error(
73+
' - Run ' +
74+
chalk.cyan('pnpm install') +
75+
' to ensure dependencies are installed',
76+
);
77+
console.error(' - Check that all required files exist');
78+
console.error(
79+
' - Check the error details above for more specific guidance',
80+
);
81+
82+
if (exitProcess) {
83+
process.exit(exitCode);
84+
}
85+
86+
throw error;
87+
}

0 commit comments

Comments
 (0)