Source: Functions/Reload/index.js

/* eslint-disable max-len */
/* Requires */
const chokidar = require('chokidar');
const path = require('path');
const fs = require('fs');
const Indexer = require('../../index');

/* JSON */
const envInfo = JSON.parse(fs.readFileSync(`${__dirname}/utils.json`));

/* Define os arquivos já em monitoramento, fora da envInfo, para caso de resetar */
let monitoredFiles = [];
const chokExecs = {};

/**
 * Retorna todos os detalhes do ambiente (`envInfo`).
 *
 * @returns {Object} O objeto `envInfo`, que contém os detalhes do ambiente da execução.
 */
function ambientDetails() {
    /* Retorna a envData */
    return envInfo;
}

/* Função para log de eventos */
function logEvent(eventType, what, message) {
    /* Se permite */
    if (envInfo.settings.logconsole.value === true) {
        /* Printa */
        console.log(
            '[',
            Indexer('color').echo(what, 'brightGreen').value,
            Indexer('color').echo(`| ${eventType}`, 'brightRed').value,
            ']',
            '→',
            Indexer('color').echo(message, 'brightYellow').value,
        );
    }
}

/*
    Função que recarrega os arquivos em casos de mudanças em tempo real.
    Não recomendado para longos usos, apenas para quando você quer editar e testar em tempo real.
*/
function startMonitore(
    watchList = envInfo.settings.monitore.value,
) {
    /* Reseta a success */
    envInfo.results.success = false;

    /* Try-Catch para casos de erro */
    try {
        /* Verifica se o local existe */
        if (!monitoredFiles.includes(watchList) && fs.existsSync(watchList)) {
            /* Inicia o monitoramento */
            const watchedFolder = chokidar.watch(watchList, {
                ignored: (locarel) => {
                    /* Normaliza barras (Windows -> Unix) */
                    const normalizedPath = locarel.replace(/\\/g, '/');

                    /* Ignora arquivos que NÃO terminam com .js ou .json */
                    if (/^.*\.(?!js|json$)[^.]+$/.test(locarel)) return true;

                    /* Pastas a ignorar (verifica se o caminho contém algum desses padrões) */
                    const ignoredFolders = envInfo.settings.ignore.value;

                    /* Retorna a verificação dos dados */
                    return ignoredFolders.some((folder) => normalizedPath.includes(folder));
                },
                ignoreInitial: true,
            });

            /* Mapeia os eventos de adição e remoção */
            const eventMappings = {
                add: { eventType: 'ADDED FILE', message: 'Novo arquivo inicializado, ele já pode ser utilizado normalmente...' },
                addDir: { eventType: 'ADDED DIR', message: 'Nova pasta inserida, ela já pode ser utilizada normalmente...' },
                unlink: { eventType: 'PURGE FILE', message: 'Arquivo removido do sistema, essa ação pode causar erros, cuidado...' },
                unlinkDir: { eventType: 'PURGE DIR', message: 'Pasta removida do sistema, essa ação pode causar erros, cuidado...' },
            };

            /* Em caso de mudanças */
            watchedFolder.on('change', (filename) => {
                /* Continua o recarregamento se configurado para isso */
                if (envInfo.settings.enabling.value !== false) {
                    /* Verifica se o require contém o arquivo a recarregar */
                    if (Object.keys(require.cache).includes(path.resolve(filename))) {
                        /* Faz o log de edição */
                        logEvent('CHANGE', filename, 'Detectei uma edição de arquivo require, recarregando sistemas...');

                        /* Limpa a Indexer */
                        /* eslint-disable-next-line no-underscore-dangle */
                        Indexer._internal.clearCache();

                        /* Deleta o cache do módulo */
                        delete require.cache[require.resolve(path.resolve(filename))];

                        /* Realiza o require dele novamente */
                        /* eslint-disable-next-line import/no-dynamic-require, global-require */
                        require(path.resolve(filename));

                        /* Avisa que pode usar */
                        logEvent('OK!', filename, 'Arquivo recarregado! Testa, Testa!');

                        /* Se não tem o arquivo no require, diz que editou, mas tá sem uso */
                    } else logEvent('MODIFIED', filename, 'O arquivo foi editado, mas ele não requer recarregamento...');
                }
            });

            /* Adiciona listeners para eventos de adição e remoção */
            ['add', 'addDir', 'unlink', 'unlinkDir'].forEach((event) => {
                /* Se algum acontecer, printa no console */
                watchedFolder.on(event, (what) => logEvent(eventMappings[event].eventType, what, eventMappings[event].message));
            });

            /* Caso obtenha erros */
            watchedFolder.on('error', (error) => {
                /* Insere tudo na envInfo */
                logging.echoError(error, envInfo, __dirname);

                /* Retorna um valor */
                return envInfo.results.success;
            });

            /* Define o monitor na chokExecs */
            chokExecs[watchList] = watchedFolder;

            /* Insere na lista dos arquivos já monitorados, para evitar duplicidade */
            monitoredFiles.push(watchList);

            /* Insere na envInfo */
            envInfo.parameters.already.value = monitoredFiles;

            /* Insere as funções na envInfo para poder usar o close */
            envInfo.parameters.choks.value = chokExecs;

            /* Define como sucesso */
            envInfo.results.success = true;
        }

        /* Caso de algum erro */
    } catch (error) {
        /* Insere tudo na envInfo */
        logging.echoError(error, envInfo, __dirname);
    }

    /* Aumenta as funções rodadas na inicialização */
    global.tasksComplete += 1;

    /* Retorna false como sinal de já ter */
    return envInfo.results.success;
}

/* Para o monitoramento */
async function stopMonitore(
    stopWhat = monitoredFiles[0],
) {
    /* Reseta a success */
    envInfo.results.success = false;

    /* Try-Catch para caso de erro */
    try {
        /* Verifica se o monitor contém o que vai parar */
        if (chokExecs[stopWhat]) {
            /* Para o monitoramento e faz o restante */
            await chokExecs[stopWhat].close();

            /* Deleta da Object e da Array */
            delete chokExecs[stopWhat];
            monitoredFiles = monitoredFiles.filter((mnt) => mnt !== stopWhat);

            /* Refaz a envInfo */
            envInfo.parameters.already.value = monitoredFiles;
            envInfo.parameters.choks.value = chokExecs;

            /* Se o dono permitir mostrar as funções */
            if (envInfo.settings.logconsole.value === true) {
                /* Printa na tela que recarregou */
                console.log('\x1b[31m[STOP] →\x1B[39m \x1b[33mO arquivo ou pasta\x1B[39m', `\x1b[31m'${stopWhat}'\x1B[39m \x1b[33m`, 'não está mais em monitoramento...\x1B[39m');
            }

            /* Define como sucesso */
            envInfo.results.success = true;
        }

        /* Caso de algum erro */
    } catch (error) {
        /* Insere tudo na envInfo */
        logging.echoError(error, envInfo, __dirname);
    }

    /* Retorna que não tem nada a parar */
    return envInfo.results.success;
}

/* Reset profundo para evitar circular */
/**
 * Restaura o ambiente e atualiza as exportações do módulo com a funcionalidade principal
 * @param {Object} [changeKey={}] - Chaves personalizadas para atualizar o envInfo
 * @param {Object} [envFile=envInfo] - Objeto com informações do ambiente
 * @param {string} [dirname=__dirname] - Caminho do diretório atual
 * @returns {Object} Exportações do módulo com todas as funções configuradas
 */
/* eslint-disable-next-line no-return-assign */
const resetLocal = (
    changeKey = {},
    envFile = envInfo,
    dirname = __dirname,
) => module.exports = logging.resetAmbient({
    functions: {
        [envInfo.exports.env]: { value: ambientDetails },
        [envInfo.exports.watcher]: { value: startMonitore },
        [envInfo.exports.stopwatch]: { value: stopMonitore },
        [envInfo.exports.messedup]: { value: logging.echoError },
        [envInfo.exports.reset]: { value: resetLocal },
    },
    parameters: {
        location: { value: __filename },
        already: { value: monitoredFiles },
        choks: { value: chokExecs },
    },
}, envFile, changeKey, dirname);
resetLocal();