311 lines
8.1 KiB
TypeScript
311 lines
8.1 KiB
TypeScript
/**
|
|
* Script para sincronizar código do Arcádia Suite com GitHub
|
|
* Versão otimizada com rate limiting e filtros mais agressivos
|
|
*/
|
|
|
|
import { Octokit } from '@octokit/rest';
|
|
import * as fs from 'fs';
|
|
import * as path from 'path';
|
|
|
|
let connectionSettings: any;
|
|
|
|
async function getAccessToken() {
|
|
if (connectionSettings && connectionSettings.settings.expires_at && new Date(connectionSettings.settings.expires_at).getTime() > Date.now()) {
|
|
return connectionSettings.settings.access_token;
|
|
}
|
|
|
|
const hostname = process.env.REPLIT_CONNECTORS_HOSTNAME;
|
|
const xReplitToken = process.env.REPL_IDENTITY
|
|
? 'repl ' + process.env.REPL_IDENTITY
|
|
: process.env.WEB_REPL_RENEWAL
|
|
? 'depl ' + process.env.WEB_REPL_RENEWAL
|
|
: null;
|
|
|
|
if (!xReplitToken) {
|
|
throw new Error('X_REPLIT_TOKEN not found for repl/depl');
|
|
}
|
|
|
|
connectionSettings = await fetch(
|
|
'https://' + hostname + '/api/v2/connection?include_secrets=true&connector_names=github',
|
|
{
|
|
headers: {
|
|
'Accept': 'application/json',
|
|
'X_REPLIT_TOKEN': xReplitToken
|
|
}
|
|
}
|
|
).then(res => res.json()).then(data => data.items?.[0]);
|
|
|
|
const accessToken = connectionSettings?.settings?.access_token || connectionSettings.settings?.oauth?.credentials?.access_token;
|
|
|
|
if (!connectionSettings || !accessToken) {
|
|
throw new Error('GitHub not connected');
|
|
}
|
|
return accessToken;
|
|
}
|
|
|
|
async function getGitHubClient() {
|
|
const accessToken = await getAccessToken();
|
|
return new Octokit({ auth: accessToken });
|
|
}
|
|
|
|
const OWNER = 'JonasRodriguesPachceo';
|
|
const REPO = 'ArcadiaSuite-';
|
|
const BRANCH = 'main';
|
|
|
|
// Aggressive ignore patterns
|
|
const IGNORE_PATTERNS = [
|
|
'node_modules',
|
|
'.git',
|
|
'.cache',
|
|
'.upm',
|
|
'dist',
|
|
'.replit',
|
|
'replit.nix',
|
|
'.config',
|
|
'package-lock.json',
|
|
'__pycache__',
|
|
'.pyc',
|
|
'.env',
|
|
'plus/vendor',
|
|
'plus/node_modules',
|
|
'plus/storage',
|
|
'plus/bootstrap/cache',
|
|
'.local',
|
|
'.npm',
|
|
'.nix-profile',
|
|
'generated-icon.png',
|
|
'.breakpoints',
|
|
'attached_assets',
|
|
'tmp',
|
|
'.pythonlibs',
|
|
'.replit.nix',
|
|
];
|
|
|
|
// Only include these directories
|
|
const INCLUDE_DIRS = [
|
|
'client',
|
|
'server',
|
|
'shared',
|
|
'scripts',
|
|
'docs',
|
|
'plus/app',
|
|
'plus/config',
|
|
'plus/database',
|
|
'plus/resources',
|
|
'plus/routes',
|
|
'plus/public',
|
|
'plus/bootstrap/app.php',
|
|
'plus/artisan',
|
|
'plus/composer.json',
|
|
'plus/.env.example',
|
|
];
|
|
|
|
function shouldIgnore(filePath: string): boolean {
|
|
// Check ignore patterns
|
|
for (const pattern of IGNORE_PATTERNS) {
|
|
if (filePath.includes(pattern)) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function shouldInclude(filePath: string, projectRoot: string): boolean {
|
|
const relativePath = path.relative(projectRoot, filePath);
|
|
|
|
// Always include root files
|
|
if (!relativePath.includes('/')) {
|
|
const ext = path.extname(relativePath);
|
|
const allowed = ['.json', '.ts', '.js', '.md', '.txt', '.yml', '.yaml'];
|
|
return allowed.includes(ext) || relativePath === 'replit.md';
|
|
}
|
|
|
|
// Check if in included directories
|
|
for (const dir of INCLUDE_DIRS) {
|
|
if (relativePath.startsWith(dir)) return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function getAllFiles(dirPath: string, projectRoot: string, arrayOfFiles: string[] = []): string[] {
|
|
try {
|
|
const files = fs.readdirSync(dirPath);
|
|
|
|
files.forEach(file => {
|
|
const fullPath = path.join(dirPath, file);
|
|
if (shouldIgnore(fullPath)) return;
|
|
|
|
try {
|
|
const stats = fs.statSync(fullPath);
|
|
if (stats.isDirectory()) {
|
|
arrayOfFiles = getAllFiles(fullPath, projectRoot, arrayOfFiles);
|
|
} else if (stats.isFile() && shouldInclude(fullPath, projectRoot)) {
|
|
// Skip files larger than 1MB
|
|
if (stats.size <= 1024 * 1024) {
|
|
arrayOfFiles.push(fullPath);
|
|
}
|
|
}
|
|
} catch (e) {
|
|
// Ignore permission errors
|
|
}
|
|
});
|
|
} catch (e) {
|
|
// Ignore permission errors
|
|
}
|
|
|
|
return arrayOfFiles;
|
|
}
|
|
|
|
async function sleep(ms: number) {
|
|
return new Promise(r => setTimeout(r, ms));
|
|
}
|
|
|
|
async function syncToGitHub() {
|
|
console.log('🚀 Iniciando sincronização com GitHub...');
|
|
console.log(`📦 Repositório: ${OWNER}/${REPO}`);
|
|
|
|
const octokit = await getGitHubClient();
|
|
|
|
// Get current user
|
|
const { data: user } = await octokit.users.getAuthenticated();
|
|
console.log(`✅ Autenticado como: ${user.login}`);
|
|
|
|
// Get all files to upload
|
|
const projectRoot = process.cwd();
|
|
const files = getAllFiles(projectRoot, projectRoot);
|
|
console.log(`📁 Encontrados ${files.length} arquivos para sincronizar`);
|
|
|
|
// Get the latest commit SHA
|
|
let baseSha: string;
|
|
try {
|
|
const { data: ref } = await octokit.git.getRef({
|
|
owner: OWNER,
|
|
repo: REPO,
|
|
ref: `heads/${BRANCH}`
|
|
});
|
|
baseSha = ref.object.sha;
|
|
console.log(`📌 Branch ${BRANCH}, SHA: ${baseSha.substring(0, 7)}`);
|
|
} catch (e) {
|
|
console.error('❌ Branch não encontrado. Verifique se o repositório existe.');
|
|
return;
|
|
}
|
|
|
|
// Get the base tree
|
|
const { data: baseCommit } = await octokit.git.getCommit({
|
|
owner: OWNER,
|
|
repo: REPO,
|
|
commit_sha: baseSha
|
|
});
|
|
|
|
// Create blobs with rate limiting
|
|
console.log('📤 Enviando arquivos (com rate limiting)...');
|
|
const treeItems: any[] = [];
|
|
let uploaded = 0;
|
|
let errors = 0;
|
|
|
|
for (const filePath of files) {
|
|
try {
|
|
const relativePath = path.relative(projectRoot, filePath);
|
|
const content = fs.readFileSync(filePath);
|
|
|
|
// Rate limiting: wait 100ms between requests
|
|
if (uploaded > 0 && uploaded % 10 === 0) {
|
|
await sleep(1000);
|
|
}
|
|
|
|
const { data: blob } = await octokit.git.createBlob({
|
|
owner: OWNER,
|
|
repo: REPO,
|
|
content: content.toString('base64'),
|
|
encoding: 'base64'
|
|
});
|
|
|
|
treeItems.push({
|
|
path: relativePath,
|
|
mode: '100644',
|
|
type: 'blob',
|
|
sha: blob.sha
|
|
});
|
|
|
|
uploaded++;
|
|
if (uploaded % 50 === 0) {
|
|
console.log(` ${uploaded}/${files.length} arquivos enviados...`);
|
|
}
|
|
} catch (err: any) {
|
|
if (err.message?.includes('rate limit')) {
|
|
console.log('⏳ Rate limit atingido, aguardando 60s...');
|
|
await sleep(60000);
|
|
// Retry
|
|
try {
|
|
const relativePath = path.relative(projectRoot, filePath);
|
|
const content = fs.readFileSync(filePath);
|
|
const { data: blob } = await octokit.git.createBlob({
|
|
owner: OWNER,
|
|
repo: REPO,
|
|
content: content.toString('base64'),
|
|
encoding: 'base64'
|
|
});
|
|
treeItems.push({
|
|
path: relativePath,
|
|
mode: '100644',
|
|
type: 'blob',
|
|
sha: blob.sha
|
|
});
|
|
uploaded++;
|
|
} catch (retryErr) {
|
|
errors++;
|
|
}
|
|
} else {
|
|
errors++;
|
|
if (errors <= 3) {
|
|
console.error(`❌ Erro: ${err.message?.substring(0, 80)}`);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
console.log(`✅ ${uploaded} arquivos preparados${errors > 0 ? `, ${errors} erros` : ''}`);
|
|
|
|
if (treeItems.length === 0) {
|
|
console.log('⚠️ Nenhum arquivo para enviar');
|
|
return;
|
|
}
|
|
|
|
// Create new tree
|
|
console.log('🌳 Criando árvore de arquivos...');
|
|
const { data: newTree } = await octokit.git.createTree({
|
|
owner: OWNER,
|
|
repo: REPO,
|
|
base_tree: baseCommit.tree.sha,
|
|
tree: treeItems
|
|
});
|
|
|
|
// Create commit
|
|
const commitMessage = `Sync Arcádia Suite - ${new Date().toISOString().split('T')[0]}`;
|
|
console.log('💾 Criando commit...');
|
|
const { data: newCommit } = await octokit.git.createCommit({
|
|
owner: OWNER,
|
|
repo: REPO,
|
|
message: commitMessage,
|
|
tree: newTree.sha,
|
|
parents: [baseSha]
|
|
});
|
|
|
|
// Update branch reference
|
|
await octokit.git.updateRef({
|
|
owner: OWNER,
|
|
repo: REPO,
|
|
ref: `heads/${BRANCH}`,
|
|
sha: newCommit.sha
|
|
});
|
|
|
|
console.log('');
|
|
console.log('🎉 Sincronização concluída com sucesso!');
|
|
console.log(`📝 Commit: ${newCommit.sha.substring(0, 7)}`);
|
|
console.log(`🔗 URL: https://github.com/${OWNER}/${REPO}`);
|
|
}
|
|
|
|
syncToGitHub().catch(err => {
|
|
console.error('❌ Erro na sincronização:', err.message);
|
|
process.exit(1);
|
|
});
|