Skip to content

Commit bde3375

Browse files
ochafikclaude
andcommitted
perf: add --if-changed flag to skip schema generation when up to date
- Add --if-changed flag that skips regeneration if outputs are newer than inputs - Use this flag in build script for faster incremental builds - Move prettier formatting inside the script for cleaner integration Benchmarks: - Full generation: ~5.3s - With --if-changed (skip): ~1.0s (tsx startup only) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 86e3e61 commit bde3375

File tree

2 files changed

+58
-4
lines changed

2 files changed

+58
-4
lines changed

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,9 @@
6868
],
6969
"scripts": {
7070
"fetch:spec-types": "tsx scripts/fetch-spec-types.ts",
71-
"generate:schemas": "tsx scripts/generate-schemas.ts && prettier --write \"src/generated/**/*\"",
71+
"generate:schemas": "tsx scripts/generate-schemas.ts",
7272
"typecheck": "tsgo --noEmit",
73-
"build": "npm run generate:schemas && npm run build:esm && npm run build:cjs",
73+
"build": "tsx scripts/generate-schemas.ts --if-changed && npm run build:esm && npm run build:cjs",
7474
"build:esm": "mkdir -p dist/esm && echo '{\"type\": \"module\"}' > dist/esm/package.json && tsc -p tsconfig.prod.json",
7575
"build:esm:w": "npm run build:esm -- -w",
7676
"build:cjs": "mkdir -p dist/cjs && echo '{\"type\": \"commonjs\"}' > dist/cjs/package.json && tsc -p tsconfig.cjs.json",

scripts/generate-schemas.ts

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@
3535
* @see https://github.com/fabien0102/ts-to-zod
3636
* @see https://github.com/dsherret/ts-morph
3737
*/
38-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
38+
import { execSync } from 'node:child_process';
39+
import { existsSync, mkdirSync, readFileSync, writeFileSync, statSync } from 'node:fs';
3940
import { dirname, join } from 'node:path';
4041
import { fileURLToPath } from 'node:url';
4142
import { generate } from 'ts-to-zod';
@@ -59,6 +60,42 @@ const SDK_TYPES_FILE = join(PROJECT_ROOT, 'src', 'generated', 'sdk.types.ts');
5960
const GENERATED_DIR = join(PROJECT_ROOT, 'src', 'generated');
6061
const SCHEMA_OUTPUT_FILE = join(GENERATED_DIR, 'sdk.schemas.ts');
6162
const SCHEMA_TEST_OUTPUT_FILE = join(GENERATED_DIR, 'sdk.schemas.zod.test.ts');
63+
const GENERATE_SCRIPT_FILE = join(PROJECT_ROOT, 'scripts', 'generate-schemas.ts');
64+
65+
// Input files that trigger regeneration
66+
const INPUT_FILES = [SPEC_TYPES_FILE, GENERATE_SCRIPT_FILE];
67+
// Output files that are generated
68+
const OUTPUT_FILES = [SDK_TYPES_FILE, SCHEMA_OUTPUT_FILE, SCHEMA_TEST_OUTPUT_FILE];
69+
70+
/**
71+
* Check if any input file is newer than any output file.
72+
* Returns true if regeneration is needed.
73+
*/
74+
function needsRegeneration(): boolean {
75+
// Get the newest input mtime
76+
let newestInput = 0;
77+
for (const file of INPUT_FILES) {
78+
if (!existsSync(file)) {
79+
console.log(` Input file missing: ${file}`);
80+
return true;
81+
}
82+
const mtime = statSync(file).mtimeMs;
83+
if (mtime > newestInput) newestInput = mtime;
84+
}
85+
86+
// Get the oldest output mtime
87+
let oldestOutput = Infinity;
88+
for (const file of OUTPUT_FILES) {
89+
if (!existsSync(file)) {
90+
console.log(` Output file missing: ${file}`);
91+
return true;
92+
}
93+
const mtime = statSync(file).mtimeMs;
94+
if (mtime < oldestOutput) oldestOutput = mtime;
95+
}
96+
97+
return newestInput > oldestOutput;
98+
}
6299

63100
// =============================================================================
64101
// Configuration: Field-level validation overrides
@@ -1344,7 +1381,17 @@ function addElicitationPreprocess(sourceFile: SourceFile): void {
13441381
// =============================================================================
13451382

13461383
async function main() {
1347-
console.log('🔧 Generating Zod schemas from spec.types.ts...\n');
1384+
const ifChanged = process.argv.includes('--if-changed');
1385+
1386+
if (ifChanged) {
1387+
if (!needsRegeneration()) {
1388+
console.log('✅ Schemas are up to date, skipping generation.');
1389+
return;
1390+
}
1391+
console.log('🔄 Input files changed, regenerating schemas...\n');
1392+
} else {
1393+
console.log('🔧 Generating Zod schemas from spec.types.ts...\n');
1394+
}
13481395

13491396
// Ensure generated directory exists
13501397
if (!existsSync(GENERATED_DIR)) {
@@ -1418,6 +1465,13 @@ ${cleanedTypesContent.replace(/^\/\*\*[\s\S]*?\*\/\n/, '')}`;
14181465
console.log(`✅ Written: ${SCHEMA_TEST_OUTPUT_FILE}`);
14191466
}
14201467

1468+
// Format generated files with prettier
1469+
console.log('\n📝 Formatting generated files...');
1470+
execSync('npx prettier --write "src/generated/**/*"', {
1471+
cwd: PROJECT_ROOT,
1472+
stdio: 'inherit'
1473+
});
1474+
14211475
console.log('\n🎉 Schema generation complete!');
14221476
}
14231477

0 commit comments

Comments
 (0)