@sanity/codegen
Version:
Codegen toolkit for Sanity.io
114 lines (113 loc) • 3.94 kB
JavaScript
import { error, log } from 'node:console';
import { isAbsolute, join, relative } from 'node:path';
import { styleText } from 'node:util';
import chokidar from 'chokidar';
import { debounce, mean } from 'lodash-es';
import { prepareConfig } from '../utils/config.js';
import { runTypegenGenerate } from './typegenGenerate.js';
const IGNORED_PATTERNS = [
'**/node_modules/**',
'**/.git/**',
'**/dist/**',
'**/lib/**',
'**/.sanity/**'
];
/**
* Creates a typegen runner with concurrency control.
* If generation is already running, queues one more generation to run after completion.
* Multiple queued requests are coalesced into a single pending generation.
*/ function createTypegenRunner(onGenerate) {
const state = {
isGenerating: false,
pendingGeneration: false
};
async function runGeneration() {
if (state.isGenerating) {
state.pendingGeneration = true;
return;
}
state.isGenerating = true;
state.pendingGeneration = false;
try {
await onGenerate();
} finally{
state.isGenerating = false;
// If a change came in during generation, run again
if (state.pendingGeneration) {
state.pendingGeneration = false;
await runGeneration();
}
}
}
return {
runGeneration,
state
};
}
/**
* Starts a file watcher that triggers typegen on changes.
* Watches both query files (via patterns) and the schema JSON file.
* Implements debouncing and concurrency control to prevent multiple generations.
*/ export function runTypegenWatcher(options) {
const { config, workDir } = options;
const { path, schema } = prepareConfig(config);
const stats = {
failedCount: 0,
startTime: Date.now(),
successfulDurations: []
};
const { runGeneration } = createTypegenRunner(async ()=>{
try {
const { duration } = await runTypegenGenerate({
...options
});
stats.successfulDurations.push(duration);
} catch (err) {
const errorMessage = err instanceof Error ? err.message : err;
error(` ${styleText('red', '›')} ${errorMessage}`);
stats.failedCount++;
}
});
// Debounced generation trigger
const debouncedGenerate = debounce(runGeneration, 1000);
// Build absolute patterns for query files and schema file
const paths = Array.isArray(path) ? path : [
path
];
const absoluteQueryPatterns = paths.map((pattern)=>isAbsolute(pattern) ? pattern : join(workDir, pattern));
const absoluteSchemaPath = isAbsolute(schema) ? schema : join(workDir, schema);
const watchTargets = [
...absoluteQueryPatterns,
absoluteSchemaPath
];
// perform initial generation
debouncedGenerate();
// set up watcher
const watcher = chokidar.watch(watchTargets, {
cwd: workDir,
ignored: IGNORED_PATTERNS,
ignoreInitial: true
});
watcher.on('all', (event, filePath)=>{
const timestamp = new Date().toLocaleTimeString();
const relativePath = isAbsolute(filePath) ? relative(workDir, filePath) : filePath;
log(`[${timestamp}] ${event}: ${relativePath}`);
debouncedGenerate();
});
watcher.on('error', (err)=>{
error(`Watcher error: ${err.message}`);
});
return {
getStats: ()=>({
averageGenerationDuration: mean(stats.successfulDurations) || 0,
generationFailedCount: stats.failedCount,
generationSuccessfulCount: stats.successfulDurations.length,
watcherDuration: Date.now() - stats.startTime
}),
stop: async ()=>{
await watcher.close();
},
watcher
};
}
//# sourceMappingURL=typegenWatch.js.map