// "Templates e patterns para scripts de automação (migration, bulk processing, maintenance). TRIGGERS: 'script', 'automação', 'migration', 'bulk processing'. Use quando criar scripts para dados, database ops, content migration, automation."
| name | tokenmilagre-scripts |
| description | Templates e patterns para scripts de automação (migration, bulk processing, maintenance). TRIGGERS: 'script', 'automação', 'migration', 'bulk processing'. Use quando criar scripts para dados, database ops, content migration, automation. |
| license | MIT |
Templates e patterns padronizados para scripts de automação
Fornecer templates reutilizáveis e patterns para scripts que automatizam:
Use esta skill quando:
Estrutura padrão para TODOS scripts:
#!/usr/bin/env ts-node
/**
* Script: [nome].ts
* Purpose: [Descrição em 1 linha]
* Usage: npx ts-node scripts/[nome].ts [--dry-run] [--limit N]
*
* Examples:
* npx ts-node scripts/[nome].ts --dry-run # Preview sem executar
* npx ts-node scripts/[nome].ts --limit 10 # Processar apenas 10
* npx ts-node scripts/[nome].ts --verbose # Logs detalhados
*/
import { PrismaClient } from '../lib/generated/prisma';
const prisma = new PrismaClient();
// ========================================
// CONFIGURATION
// ========================================
interface Config {
dryRun: boolean; // Preview mode (não modifica dados)
limit?: number; // Processar apenas N registros
verbose: boolean; // Logs detalhados
}
function parseArgs(): Config {
const args = process.argv.slice(2);
return {
dryRun: args.includes('--dry-run'),
limit: args.includes('--limit')
? parseInt(args[args.indexOf('--limit') + 1])
: undefined,
verbose: args.includes('--verbose') || args.includes('-v')
};
}
// ========================================
// MAIN LOGIC
// ========================================
async function main() {
const config = parseArgs();
console.log('🚀 Starting:', process.argv[1]);
console.log('📋 Config:', config);
try {
// Fetch records
const records = await prisma.MODEL.findMany({
take: config.limit,
where: { /* critérios */ }
});
console.log(`✅ Found ${records.length} records`);
// Process each
let processed = 0;
let errors = 0;
for (const record of records) {
try {
if (config.verbose) {
console.log(`Processing: ${record.id}`);
}
// LOGIC HERE
if (!config.dryRun) {
await prisma.MODEL.update({
where: { id: record.id },
data: { /* updates */ }
});
}
processed++;
} catch (error) {
errors++;
console.error(`❌ Error processing ${record.id}:`, error);
}
}
// Summary
console.log('\n📊 Summary:');
console.log(` ✅ Processed: ${processed}`);
console.log(` ❌ Errors: ${errors}`);
console.log(` 🏷️ Mode: ${config.dryRun ? 'DRY RUN' : 'LIVE'}`);
} catch (error) {
console.error('💥 Fatal error:', error);
process.exit(1);
} finally {
await prisma.$disconnect();
}
}
// ========================================
// RUN
// ========================================
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
Use quando: Atualizar campo em múltiplos registros
// Atualizar 1000 artigos de uma vez
const updated = await prisma.article.updateMany({
where: { published: false },
data: { status: 'DRAFT' }
});
console.log(`Updated ${updated.count} articles`);
Use quando: Processar muitos registros sem estourar memória
const BATCH_SIZE = 100;
let offset = 0;
let hasMore = true;
while (hasMore) {
const batch = await prisma.article.findMany({
take: BATCH_SIZE,
skip: offset
});
if (batch.length === 0) {
hasMore = false;
break;
}
// Process batch
for (const item of batch) {
await processItem(item);
}
offset += BATCH_SIZE;
console.log(`Processed ${offset} total...`);
}
Use quando: Operações que DEVEM todas suceder ou todas falhar
await prisma.$transaction(async (tx) => {
// Deletar artigo
await tx.article.delete({ where: { id: articleId } });
// Deletar comments órfãos
await tx.comment.deleteMany({ where: { articleId } });
// Log activity
await tx.activity.create({
data: { action: 'ARTICLE_DELETED', articleId }
});
});
Use quando: Scripts longos (>30seg) precisam feedback
const total = await prisma.article.count();
let processed = 0;
for (const article of articles) {
await processArticle(article);
processed++;
// Progress bar
if (processed % 10 === 0) {
const percent = (processed / total * 100).toFixed(1);
console.log(`[${percent}%] ${processed}/${total}`);
}
}
Use quando: Script pode falhar parcialmente mas deve continuar
const failed: string[] = [];
for (const item of items) {
try {
await processItem(item);
} catch (error) {
console.error(`Failed ${item.id}:`, error.message);
failed.push(item.id);
// Continua processando outros
}
}
// Report failures no final
if (failed.length > 0) {
console.warn(`\n⚠️ ${failed.length} items failed:`);
console.warn(failed.join(', '));
}
if (config.dryRun) {
console.log('DRY RUN - Preview only');
// Mostrar o que FARIA sem executar
return;
}
Por quê: Previne acidentes (deletar DB prod, etc.)
// ❌ RUIM - Pode falhar no meio
await prisma.user.delete({ where: { id } });
await prisma.article.deleteMany({ where: { authorId: id } });
// ✅ BOM - Atômico
await prisma.$transaction([
prisma.user.delete({ where: { id } }),
prisma.article.deleteMany({ where: { authorId: id } })
]);
console.log('\n📊 Summary:');
console.log(` Total: ${total}`);
console.log(` ✅ Success: ${success}`);
console.log(` ❌ Failed: ${failed}`);
console.log(` ⏱️ Duration: ${duration}ms`);
// ❌ RUIM
const API_KEY = 'sk-abc123...';
// ✅ BOM
const API_KEY = process.env.PERPLEXITY_API_KEY;
if (!API_KEY) throw new Error('Missing PERPLEXITY_API_KEY');
main()
.catch(console.error)
.finally(() => prisma.$disconnect());
// Parsing avançado
function parseArgs() {
const args = process.argv.slice(2);
return {
dryRun: args.includes('--dry-run'),
limit: getArgValue(args, '--limit', parseInt),
model: getArgValue(args, '--model'),
verbose: args.includes('-v') || args.includes('--verbose'),
force: args.includes('--force')
};
}
function getArgValue<T>(
args: string[],
flag: string,
parser: (v: string) => T = (v) => v as T
): T | undefined {
const index = args.indexOf(flag);
if (index === -1) return undefined;
return parser(args[index + 1]);
}
function validateConfig(config: Config) {
if (config.limit && config.limit < 1) {
throw new Error('--limit must be positive');
}
if (!config.dryRun && !config.force) {
throw new Error('Use --force for LIVE mode (or --dry-run for preview)');
}
}
Scripts já criados no projeto (ver scripts/ directory):
Como usar como referência:
# Ler script existente
cat scripts/migrate-slugs.ts
# Copiar como template
cp scripts/migrate-slugs.ts scripts/my-new-script.ts
if (config.verbose) {
console.log('🔍 Debug:', {
recordId: record.id,
before: record.status,
after: newStatus,
metadata: record.metadata
});
}
# Sempre testar com dry-run ANTES
npx ts-node scripts/my-script.ts --dry-run --limit 5
# Se OK, rodar de verdade
npx ts-node scripts/my-script.ts --limit 5
# Se ainda OK, rodar sem limit
npx ts-node scripts/my-script.ts
// Testar com 1 registro primeiro
if (process.env.NODE_ENV !== 'production') {
console.warn('⚠️ TEST MODE: Processing only 1 record');
records = records.slice(0, 1);
}
When creating scripts using this skill:
Checklist antes de rodar script:
tokenmilagre-database - Prisma patterns, migrationstokenmilagre-testing - Testar scriptstroubleshooting - Debug de scripts que falharamSkill criada por: Claude Code Última atualização: 2025-11-17 Mudanças recentes: