CoolFace
Apppublic

Pq234/robot-learning-tutorial

sourceHugging Faceupdated 11mo agoView on Hugging Face
0likes
sync-template.mjs362 linesDownload Raw Back to scripts
1#!/usr/bin/env node2 3/**4 * Template synchronization script for research-article-template5 * 6 * This script:7 * 1. Clones or updates the template repo in a temporary directory8 * 2. Copies all files EXCEPT those in ./src/content which contain specific content9 * 3. Preserves important local configuration files10 * 4. Creates backups of files that will be overwritten11 * 12 * Usage: npm run sync:template [--dry-run] [--backup] [--force]13 */14 15import { execSync } from 'child_process';16import fs from 'fs/promises';17import path from 'path';18import { fileURLToPath } from 'url';19 20const __dirname = path.dirname(fileURLToPath(import.meta.url));21const APP_ROOT = path.resolve(__dirname, '..');22const PROJECT_ROOT = path.resolve(APP_ROOT, '..');23const TEMP_DIR = path.join(PROJECT_ROOT, '.temp-template-sync');24const TEMPLATE_REPO = 'https://huggingface.co/spaces/tfrere/research-article-template';25 26// Files and directories to PRESERVE (do not overwrite)27const PRESERVE_PATHS = [28    // Project-specific content29    'app/src/content',30 31    // Public data (symlink to our data) - CRITICAL: preserve this symlink32    'app/public/data',33 34    // Local configuration35    'app/package-lock.json',36    'app/node_modules',37 38    // Project-specific scripts (preserve our sync script)39    'app/scripts/sync-template.mjs',40 41    // Project configuration files42    'README.md',43    'tools',44 45    // Backup and temporary files46    '.backup-*',47    '.temp-*',48 49    // Git50    '.git',51    '.gitignore'52];53 54// Files to handle with caution (require confirmation)55const SENSITIVE_FILES = [56    'app/package.json',57    'app/astro.config.mjs',58    'Dockerfile',59    'nginx.conf'60];61 62const args = process.argv.slice(2);63const isDryRun = args.includes('--dry-run');64const shouldBackup = args.includes('--backup'); // Disabled by default, use --backup to enable65const isForce = args.includes('--force');66 67console.log('🔄 Template synchronization script for research-article-template');68console.log(`📁 Working directory: ${PROJECT_ROOT}`);69console.log(`🎯 Template source: ${TEMPLATE_REPO}`);70if (isDryRun) console.log('🔍 DRY-RUN mode enabled - no files will be modified');71if (shouldBackup) console.log('💾 Backup enabled');72if (!shouldBackup) console.log('🚫 Backup disabled (use --backup to enable)');73console.log('');74 75async function executeCommand(command, options = {}) {76    try {77        if (isDryRun && !options.allowInDryRun) {78            console.log(`[DRY-RUN] Command: ${command}`);79            return '';80        }81        console.log(`$ ${command}`);82        const result = execSync(command, {83            encoding: 'utf8',84            cwd: options.cwd || PROJECT_ROOT,85            stdio: options.quiet ? 'pipe' : 'inherit'86        });87        return result;88    } catch (error) {89        console.error(`❌ Error during execution: ${command}`);90        console.error(error.message);91        throw error;92    }93}94 95async function pathExists(filePath) {96    try {97        await fs.access(filePath);98        return true;99    } catch {100        return false;101    }102}103 104async function isPathPreserved(relativePath) {105    return PRESERVE_PATHS.some(preserve =>106        relativePath === preserve ||107        relativePath.startsWith(preserve + '/')108    );109}110 111async function createBackup(filePath) {112    if (!shouldBackup || isDryRun) return;113 114    const timestamp = new Date().toISOString().replace(/[:.]/g, '-');115    const backupPath = `${filePath}.backup-${timestamp}`;116 117    try {118        await fs.copyFile(filePath, backupPath);119        console.log(`💾 Backup created: ${path.relative(PROJECT_ROOT, backupPath)}`);120    } catch (error) {121        console.warn(`⚠️  Unable to create backup for ${filePath}: ${error.message}`);122    }123}124 125async function syncFile(sourcePath, targetPath) {126    const relativeTarget = path.relative(PROJECT_ROOT, targetPath);127 128    // Check if the file should be preserved129    if (await isPathPreserved(relativeTarget)) {130        console.log(`🔒 PRESERVED: ${relativeTarget}`);131        return;132    }133 134    // Check if it's a sensitive file135    if (SENSITIVE_FILES.includes(relativeTarget)) {136        if (!isForce) {137            console.log(`⚠️  SENSITIVE (ignored): ${relativeTarget} (use --force to overwrite)`);138            return;139        } else {140            console.log(`⚠️  SENSITIVE (forced): ${relativeTarget}`);141        }142    }143 144    // Check if target file is a symbolic link to preserve145    if (await pathExists(targetPath)) {146        try {147            const targetStats = await fs.lstat(targetPath);148            if (targetStats.isSymbolicLink()) {149                console.log(`🔗 SYMLINK TARGET (preserved): ${relativeTarget}`);150                return;151            }152        } catch (error) {153            console.warn(`⚠️  Impossible de vérifier ${targetPath}: ${error.message}`);154        }155    }156 157    // Create backup if file already exists (and is not a symbolic link)158    if (await pathExists(targetPath)) {159        try {160            const stats = await fs.lstat(targetPath);161            if (!stats.isSymbolicLink()) {162                await createBackup(targetPath);163            }164        } catch (error) {165            console.warn(`⚠️  Impossible de vérifier ${targetPath}: ${error.message}`);166        }167    }168 169    if (isDryRun) {170        console.log(`[DRY-RUN] COPY: ${relativeTarget}`);171        return;172    }173 174    // Assurer que le répertoire parent existe175    await fs.mkdir(path.dirname(targetPath), { recursive: true });176 177    // Check if source is a symbolic link178    try {179        const sourceStats = await fs.lstat(sourcePath);180        if (sourceStats.isSymbolicLink()) {181            console.log(`🔗 SYMLINK SOURCE (ignored): ${relativeTarget}`);182            return;183        }184    } catch (error) {185        console.warn(`⚠️  Unable to check source ${sourcePath}: ${error.message}`);186        return;187    }188 189    // Remove target file if it exists (to handle symbolic links)190    if (await pathExists(targetPath)) {191        await fs.rm(targetPath, { recursive: true, force: true });192    }193 194    // Copier le fichier195    await fs.copyFile(sourcePath, targetPath);196    console.log(`✅ COPIED: ${relativeTarget}`);197}198 199async function syncDirectory(sourceDir, targetDir) {200    const items = await fs.readdir(sourceDir, { withFileTypes: true });201 202    for (const item of items) {203        const sourcePath = path.join(sourceDir, item.name);204        const targetPath = path.join(targetDir, item.name);205        const relativeTarget = path.relative(PROJECT_ROOT, targetPath);206 207        if (await isPathPreserved(relativeTarget)) {208            console.log(`🔒 DOSSIER PRÉSERVÉ: ${relativeTarget}/`);209            continue;210        }211 212        if (item.isDirectory()) {213            if (!isDryRun) {214                await fs.mkdir(targetPath, { recursive: true });215            }216            await syncDirectory(sourcePath, targetPath);217        } else {218            await syncFile(sourcePath, targetPath);219        }220    }221}222 223async function cloneOrUpdateTemplate() {224    console.log('📥 Fetching template...');225 226    // Nettoyer le dossier temporaire s'il existe227    if (await pathExists(TEMP_DIR)) {228        await fs.rm(TEMP_DIR, { recursive: true, force: true });229        if (isDryRun) {230            console.log(`[DRY-RUN] Suppression: ${TEMP_DIR}`);231        }232    }233 234    // Clone template repo (even in dry-run to be able to compare)235    await executeCommand(`git clone ${TEMPLATE_REPO} "${TEMP_DIR}"`, { allowInDryRun: true });236 237    return TEMP_DIR;238}239 240async function ensureDataSymlink() {241    const dataSymlinkPath = path.join(APP_ROOT, 'public', 'data');242    const dataSourcePath = path.join(APP_ROOT, 'src', 'content', 'assets', 'data');243 244    // Check if symlink exists and is correct245    if (await pathExists(dataSymlinkPath)) {246        try {247            const stats = await fs.lstat(dataSymlinkPath);248            if (stats.isSymbolicLink()) {249                const target = await fs.readlink(dataSymlinkPath);250                const expectedTarget = path.relative(path.dirname(dataSymlinkPath), dataSourcePath);251                if (target === expectedTarget) {252                    console.log('🔗 Data symlink is correct');253                    return;254                } else {255                    console.log(`⚠️  Data symlink points to wrong target: ${target} (expected: ${expectedTarget})`);256                }257            } else {258                console.log('⚠️  app/public/data exists but is not a symlink');259            }260        } catch (error) {261            console.log(`⚠️  Error checking symlink: ${error.message}`);262        }263    }264 265    // Recreate symlink266    if (!isDryRun) {267        if (await pathExists(dataSymlinkPath)) {268            await fs.rm(dataSymlinkPath, { recursive: true, force: true });269        }270        await fs.symlink(path.relative(path.dirname(dataSymlinkPath), dataSourcePath), dataSymlinkPath);271        console.log('✅ Data symlink recreated');272    } else {273        console.log('[DRY-RUN] Would recreate data symlink');274    }275}276 277async function showSummary(templateDir) {278    console.log('\n📊 SYNCHRONIZATION SUMMARY');279    console.log('================================');280 281    console.log('\n🔒 Preserved files/directories:');282    for (const preserve of PRESERVE_PATHS) {283        const fullPath = path.join(PROJECT_ROOT, preserve);284        if (await pathExists(fullPath)) {285            console.log(`   ✓ ${preserve}`);286        } else {287            console.log(`   - ${preserve} (n'existe pas)`);288        }289    }290 291    console.log('\n⚠️  Sensitive files (require --force):');292    for (const sensitive of SENSITIVE_FILES) {293        const fullPath = path.join(PROJECT_ROOT, sensitive);294        if (await pathExists(fullPath)) {295            console.log(`   ! ${sensitive}`);296        }297    }298 299    if (isDryRun) {300        console.log('\n🔍 To execute for real: npm run sync:template');301        console.log('🔧 To force sensitive files: npm run sync:template -- --force');302    }303}304 305async function cleanup() {306    console.log('\n🧹 Cleaning up...');307    if (await pathExists(TEMP_DIR)) {308        if (!isDryRun) {309            await fs.rm(TEMP_DIR, { recursive: true, force: true });310        }311        console.log(`🗑️  Temporary directory removed: ${TEMP_DIR}`);312    }313}314 315async function main() {316    try {317        // Verify we're in the correct directory318        const packageJsonPath = path.join(APP_ROOT, 'package.json');319        if (!(await pathExists(packageJsonPath))) {320            throw new Error(`Package.json not found in ${APP_ROOT}. Are you in the correct directory?`);321        }322 323        // Clone the template324        const templateDir = await cloneOrUpdateTemplate();325 326        // Synchroniser327        console.log('\n🔄 Synchronisation en cours...');328        await syncDirectory(templateDir, PROJECT_ROOT);329 330        // S'assurer que le lien symbolique des données est correct331        console.log('\n🔗 Vérification du lien symbolique des données...');332        await ensureDataSymlink();333 334        // Afficher le résumé335        await showSummary(templateDir);336 337        console.log('\n✅ Synchronization completed!');338 339    } catch (error) {340        console.error('\n❌ Error during synchronization:');341        console.error(error.message);342        process.exit(1);343    } finally {344        await cleanup();345    }346}347 348// Signal handling to clean up on interruption349process.on('SIGINT', async () => {350    console.log('\n\n⚠️  Interruption detected, cleaning up...');351    await cleanup();352    process.exit(1);353});354 355process.on('SIGTERM', async () => {356    console.log('\n\n⚠️  Shutdown requested, cleaning up...');357    await cleanup();358    process.exit(1);359});360 361main();362