Skip to content

Instantly share code, notes, and snippets.

@scytacki
Created November 15, 2025 14:01
Show Gist options
  • Select an option

  • Save scytacki/1c814dc3845f4cc6049e76ad75acd24c to your computer and use it in GitHub Desktop.

Select an option

Save scytacki/1c814dc3845f4cc6049e76ad75acd24c to your computer and use it in GitHub Desktop.
A script to count the MST action and view blocks in each MST model in a repository.
#!/usr/bin/env node
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
/**
* Recursively find all files in a directory
*/
function findFiles(dir, fileList = []) {
const files = fs.readdirSync(dir);
files.forEach(file => {
const filePath = path.join(dir, file);
const stat = fs.statSync(filePath);
if (stat.isDirectory()) {
findFiles(filePath, fileList);
} else if (file.endsWith('.ts') || file.endsWith('.tsx')) {
fileList.push(filePath);
}
});
return fileList;
}
/**
* Extract model name from a model definition line
*/
function extractModelName(line) {
// Match types.model("ModelName", ...) or types.model('ModelName', ...)
const typesModelMatch = line.match(/types\.model\(\s*["']([^"']+)["']/);
if (typesModelMatch) {
return typesModelMatch[1];
}
// Match .named("ModelName") or .named('ModelName')
const namedMatch = line.match(/\.named\(\s*["']([^"']+)["']/);
if (namedMatch) {
return namedMatch[1];
}
// Try to extract from variable name like: export const SomeModel = types.model(
const varMatch = line.match(/(?:export\s+)?const\s+(\w+)\s*=/);
if (varMatch) {
return varMatch[1];
}
return null;
}
/**
* Count MST .actions() and .views() patterns per model in a file
*/
function countPatternsInFile(filePath) {
const content = fs.readFileSync(filePath, 'utf8');
const lines = content.split('\n');
const models = [];
// Find all model definitions (lines with types.model( or .named()
const modelStarts = [];
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (/types\.model\(|\.named\(/.test(line)) {
const modelName = extractModelName(line);
if (modelName) {
modelStarts.push({ line: i, name: modelName });
}
}
}
// For each model, count patterns until the next model or end of file
for (let i = 0; i < modelStarts.length; i++) {
const start = modelStarts[i];
const endLine = i < modelStarts.length - 1 ? modelStarts[i + 1].line : lines.length;
let count = 0;
const patterns = [];
// Count patterns in this model's section
for (let j = start.line; j < endLine; j++) {
const matches = lines[j].match(/\.actions\(|\.views\(/g);
if (matches) {
count += matches.length;
patterns.push(...matches);
}
}
if (count > 0) {
models.push({
name: start.name,
startLine: start.line + 1,
endLine,
count,
patterns
});
}
}
const totalCount = models.reduce((sum, model) => sum + model.count, 0);
return {
count: totalCount,
models,
hasMultipleModels: models.length > 1
};
}
/**
* Main function
*/
function main() {
const modelsDir = path.join(__dirname, '..', 'src', 'models');
if (!fs.existsSync(modelsDir)) {
console.error(`Error: Directory not found: ${modelsDir}`);
process.exit(1);
}
console.log(`Searching for .actions() and .views() patterns in: ${modelsDir}\n`);
const files = findFiles(modelsDir);
const results = [];
let totalCount = 0;
let totalModels = 0;
files.forEach(file => {
const result = countPatternsInFile(file);
if (result.count > 0) {
const relativePath = path.relative(process.cwd(), file);
results.push({
file: relativePath,
count: result.count,
models: result.models,
hasMultipleModels: result.hasMultipleModels
});
totalCount += result.count;
totalModels += result.models.length;
}
});
// Sort by count (descending)
results.sort((a, b) => b.count - a.count);
// Print results
console.log('Files with .actions() or .views() patterns:\n');
console.log('Count | File');
console.log('------|-----');
results.forEach(result => {
console.log(` ${result.count.toString().padStart(2)} | ${result.file}`);
// Show per-model breakdown for files with multiple models
if (result.hasMultipleModels && result.models.length > 1) {
result.models.forEach(model => {
if (model.count > 0) {
console.log(` | └─ ${model.name}: ${model.count}`);
}
});
}
});
console.log('\n' + '='.repeat(60));
console.log(`Total files scanned: ${files.length}`);
console.log(`Files with patterns: ${results.length}`);
console.log(`Total models found: ${totalModels}`);
console.log(`Total pattern occurrences: ${totalCount}`);
const highCountLimit = 7;
// Warn about models (not just files) with many patterns
const modelsWithHighCount = [];
results.forEach(result => {
result.models.forEach(model => {
if (model.count > highCountLimit) {
modelsWithHighCount.push({
file: result.file,
model: model.name,
count: model.count
});
}
});
});
if (modelsWithHighCount.length > 0) {
console.log(`\n⚠️ Warning: Models with more than ${highCountLimit} .actions()/.views() blocks:`);
modelsWithHighCount
.sort((a, b) => b.count - a.count)
.forEach(item => {
console.log(` - ${item.model} in ${item.file} (${item.count} occurrences)`);
});
console.log('\nThese models may cause TypeScript compilation issues.');
}
}
main();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment