All files / src/install-from-boilerplate copy-system-files.js

88.37% Statements 38/43
71.42% Branches 5/7
100% Functions 4/4
88.37% Lines 38/43

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 1012x 2x           2x 2x           9x 9x 9x 9x               2x 2x   2x         2x 2x 2x 2x             2x 4x 4x 2x   2x 2x                           2x   2x 8x 8x           2x 2x 2x           2x 4x 4x 3x 3x   1x 1x           2x   2x     2x  
const fs = require('fs-extra');
const path = require('node:path');
const {
    systemFiles,
    systemDirsReplace,
    systemFilesNoOverwrite,
    systemFilesToRemove,
} = require('./config-system-files.js');
const Logger = require('../utils/logger');
 
/**
 * Copies a single boilerplate file to the consuming project.
 */
function copySingleFile(sourceDir, destRoot, item) {
    const srcPath = path.join(sourceDir, item);
    const destPath = path.join(destRoot, item);
    fs.cpSync(srcPath, destPath, { recursive: true });
    Logger.success(`Copied ${item} to ${destPath}`);
}
 
/**
 * Replaces a directory in the consuming project with the boilerplate version.
 * Extra files that are not in the boilerplate are removed.
 */
function replaceDir(sourceDir, destRoot, item) {
    const srcPath = path.join(sourceDir, item);
    const destPath = path.join(destRoot, item);
 
    Iif (!fs.existsSync(srcPath)) {
        Logger.error(`Boilerplate directory not found: ${item}`);
        return;
    }
 
    fs.rmSync(destPath, { recursive: true, force: true });
    fs.mkdirSync(path.dirname(destPath), { recursive: true });
    fs.cpSync(srcPath, destPath, { recursive: true });
    Logger.success(`Replaced ${item} with boilerplate copy`);
}
 
/**
 * Removes stale files left over from previous boilerplate versions.
 */
function removeStaleFiles(destRoot, files) {
    for (const item of files) {
        const destPath = path.join(destRoot, item);
        if (!fs.existsSync(destPath)) {
            continue;
        }
        fs.rmSync(destPath, { force: true });
        Logger.success(`Removed stale file ${item}`);
    }
}
 
/**
 * Copies system files from the boilerplate directory to the root of the project.
 * System files are defined in the `config-system-files.js` file.
 * Files in `systemFiles` are copied recursively and can be safely overwritten.
 * Directories in `systemDirsReplace` are replaced 1:1 (extras are removed).
 * Files in `systemFilesNoOverwrite` are only copied when they do not already
 * exist, so user customisations are preserved across updates.
 * Files in `systemFilesToRemove` are deleted from the consuming project.
 */
function copySystemFiles(destRoot = process.cwd()) {
    const sourceDir = path.join(__dirname, './', 'boilerplate');
 
    for (const item of systemFiles) {
        try {
            copySingleFile(sourceDir, destRoot, item);
        } catch (error) {
            Logger.error(`Failed to copy ${item}:`, error);
        }
    }
 
    for (const item of systemDirsReplace) {
        try {
            replaceDir(sourceDir, destRoot, item);
        } catch (error) {
            Logger.error(`Failed to replace ${item}:`, error);
        }
    }
 
    for (const item of systemFilesNoOverwrite) {
        const destPath = path.join(destRoot, item);
        if (fs.existsSync(destPath)) {
            Logger.info(`Skipped ${item} (already exists)`);
            continue;
        }
        try {
            copySingleFile(sourceDir, destRoot, item);
        } catch (error) {
            Logger.error(`Failed to copy ${item}:`, error);
        }
    }
 
    removeStaleFiles(destRoot, systemFilesToRemove);
 
    Logger.success('Copied system files to current directory');
}
 
module.exports = copySystemFiles;