mirror of
https://github.com/unraid/api.git
synced 2026-01-01 22:20:05 -06:00
- enhances `pnpm sync-webgui-repo` to include Unraid UI build + sync options - changed the dev intended ThemeSwitcher to use local / session storage <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - The theme switcher now respects your saved preferences, ensuring a more personalized interface. - The component build and synchronization process has been enhanced with new options for UI components and clearer feedback during operations. - New computed properties added for better state management in account and purchase functionalities. - **Bug Fixes** - Improved error handling and logging during the component build process. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
697 lines
21 KiB
JavaScript
697 lines
21 KiB
JavaScript
const fs = require('fs');
|
||
const path = require('path');
|
||
const ignore = require('ignore');
|
||
const readline = require('readline');
|
||
const diff = require('diff');
|
||
const crypto = require('crypto');
|
||
let chalk;
|
||
(async () => {
|
||
chalk = await import('chalk');
|
||
})();
|
||
|
||
const CONSTANTS = {
|
||
PATHS: {
|
||
IGNORE_LIST: path.join(__dirname, '.sync-webgui-repo-ignored-files.json'),
|
||
NEW_FILES: path.join(__dirname, '.sync-webgui-repo-new-files.json'),
|
||
STATE: path.join(__dirname, '.sync-webgui-repo-state.json'),
|
||
},
|
||
IGNORE_PATTERNS: [/\.md$/i, /\.ico$/i, /\.cfg$/i, /\.json$/i, /^banner\.png$/i],
|
||
PLUGIN_PATHS: {
|
||
API: 'plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins',
|
||
WEBGUI: 'emhttp/plugins',
|
||
},
|
||
WEB_COMPONENTS: {
|
||
API_WEB_BUILD_PATH: 'web/.nuxt/nuxt-custom-elements/dist/unraid-components',
|
||
API_UI_BUILD_PATH: 'unraid-ui/dist-wc',
|
||
WEBGUI_BASE_PATH: 'emhttp/plugins/dynamix.my.servers/unraid-components',
|
||
WEBGUI_WEB_SUBPATH: 'nuxt',
|
||
WEBGUI_UI_SUBPATH: 'uui',
|
||
},
|
||
};
|
||
|
||
const FileSystem = {
|
||
readJsonFile(path, defaultValue = {}) {
|
||
try {
|
||
return fs.existsSync(path) ? JSON.parse(fs.readFileSync(path, 'utf8')) : defaultValue;
|
||
} catch {
|
||
return defaultValue;
|
||
}
|
||
},
|
||
|
||
writeJsonFile(path, data) {
|
||
fs.writeFileSync(path, JSON.stringify(data, null, 2));
|
||
},
|
||
|
||
ensureDir(dir) {
|
||
if (!fs.existsSync(dir)) {
|
||
fs.mkdirSync(dir, { recursive: true });
|
||
}
|
||
},
|
||
|
||
copyFile(source, dest) {
|
||
try {
|
||
const destDir = path.dirname(dest);
|
||
this.ensureDir(destDir);
|
||
fs.copyFileSync(source, dest);
|
||
return true;
|
||
} catch (err) {
|
||
UI.log(`Failed to copy ${source} to ${dest}: ${err.message}`, 'error');
|
||
return false;
|
||
}
|
||
},
|
||
|
||
getFileHash(filePath) {
|
||
const fileBuffer = fs.readFileSync(filePath);
|
||
const hashSum = crypto.createHash('sha256');
|
||
hashSum.update(fileBuffer);
|
||
return hashSum.digest('hex');
|
||
},
|
||
|
||
copyDirectory(source, destination) {
|
||
this.ensureDir(destination);
|
||
fs.readdirSync(source).forEach((file) => {
|
||
const sourcePath = path.join(source, file);
|
||
const destPath = path.join(destination, file);
|
||
const stats = fs.statSync(sourcePath);
|
||
|
||
if (stats.isDirectory()) {
|
||
this.copyDirectory(sourcePath, destPath);
|
||
} else {
|
||
fs.copyFileSync(sourcePath, destPath);
|
||
}
|
||
});
|
||
},
|
||
};
|
||
|
||
const UI = {
|
||
rl: readline.createInterface({
|
||
input: process.stdin,
|
||
output: process.stdout,
|
||
}),
|
||
|
||
async question(query) {
|
||
return new Promise((resolve) => this.rl.question(query, resolve));
|
||
},
|
||
|
||
async confirm(query, defaultYes = true) {
|
||
const answer = await this.question(`${query} (${defaultYes ? 'Y/n' : 'y/N'}) `);
|
||
return defaultYes ? answer.toLowerCase() !== 'n' : answer.toLowerCase() === 'y';
|
||
},
|
||
|
||
log(message, type) {
|
||
const icons = {
|
||
success: '✅',
|
||
error: '❌',
|
||
warning: '⚠️',
|
||
info: 'ℹ️',
|
||
skip: '⏭️',
|
||
new: '✨',
|
||
};
|
||
console.log(`${icons[type] || ''} ${message}`);
|
||
},
|
||
|
||
playSound() {
|
||
const sounds = {
|
||
darwin: 'afplay /System/Library/Sounds/Glass.aiff',
|
||
linux: 'paplay /usr/share/sounds/freedesktop/stereo/complete.oga',
|
||
win32:
|
||
'powershell.exe -c "(New-Object Media.SoundPlayer \'C:\\Windows\\Media\\Windows Default.wav\').PlaySync()"',
|
||
};
|
||
const sound = sounds[process.platform];
|
||
if (sound) require('child_process').exec(sound);
|
||
},
|
||
};
|
||
|
||
const State = {
|
||
loadIgnoredFiles() {
|
||
return FileSystem.readJsonFile(CONSTANTS.PATHS.IGNORE_LIST, []);
|
||
},
|
||
|
||
saveIgnoredFiles(files) {
|
||
FileSystem.writeJsonFile(CONSTANTS.PATHS.IGNORE_LIST, files);
|
||
},
|
||
|
||
loadPaths() {
|
||
return FileSystem.readJsonFile(CONSTANTS.PATHS.STATE);
|
||
},
|
||
|
||
savePaths(paths) {
|
||
Object.keys(paths).forEach((key) => {
|
||
if (typeof paths[key] === 'string') {
|
||
paths[key] = paths[key].endsWith('/') ? paths[key] : paths[key] + '/';
|
||
}
|
||
});
|
||
FileSystem.writeJsonFile(CONSTANTS.PATHS.STATE, paths);
|
||
},
|
||
|
||
getNewFiles() {
|
||
const data = FileSystem.readJsonFile(CONSTANTS.PATHS.NEW_FILES, { newFiles: {} });
|
||
return data.newFiles;
|
||
},
|
||
|
||
saveNewFiles(files) {
|
||
FileSystem.writeJsonFile(CONSTANTS.PATHS.NEW_FILES, {
|
||
timestamp: new Date().toISOString(),
|
||
newFiles: files,
|
||
});
|
||
},
|
||
};
|
||
|
||
const FileOps = {
|
||
loadGitignore(dirPath) {
|
||
const gitignorePath = path.join(dirPath, '.gitignore');
|
||
if (fs.existsSync(gitignorePath)) {
|
||
const ig = ignore();
|
||
ig.add(fs.readFileSync(gitignorePath, 'utf8'));
|
||
return ig;
|
||
}
|
||
return null;
|
||
},
|
||
|
||
showDiff(apiFile, webguiFile) {
|
||
const content1 = fs.readFileSync(apiFile, 'utf8');
|
||
const content2 = fs.readFileSync(webguiFile, 'utf8');
|
||
|
||
const differences = diff.createPatch(
|
||
path.basename(apiFile),
|
||
content2,
|
||
content1,
|
||
'webgui version',
|
||
'api version'
|
||
);
|
||
|
||
if (differences.split('\n').length > 5) {
|
||
console.log('\nDiff for', chalk.default.cyan(path.basename(apiFile)));
|
||
console.log(chalk.default.red('--- webgui:'), webguiFile);
|
||
console.log(chalk.default.green('+++ api: '), apiFile);
|
||
|
||
differences
|
||
.split('\n')
|
||
.slice(5)
|
||
.forEach((line) => {
|
||
if (line.startsWith('+')) console.log(chalk.default.green(line));
|
||
else if (line.startsWith('-')) console.log(chalk.default.red(line));
|
||
else if (line.startsWith('@')) console.log(chalk.default.cyan(line));
|
||
else console.log(line);
|
||
});
|
||
return true;
|
||
}
|
||
return false;
|
||
},
|
||
|
||
async handleFileDiff(apiFile, webguiFile) {
|
||
if (!this.showDiff(apiFile, webguiFile)) return 'identical';
|
||
|
||
const answer = await UI.question(
|
||
'What should I do fam? (w=copy to webgui/a=copy to API/s=skip) '
|
||
);
|
||
switch (answer.toLowerCase()) {
|
||
case 'w':
|
||
return FileSystem.copyFile(apiFile, webguiFile) ? 'webgui' : 'error';
|
||
case 'a':
|
||
return FileSystem.copyFile(webguiFile, apiFile) ? 'api' : 'error';
|
||
default:
|
||
return 'skip';
|
||
}
|
||
},
|
||
|
||
walkDirectory(currentPath, baseDir, projectFiles, gitignoreRules) {
|
||
const files = fs.readdirSync(currentPath);
|
||
|
||
files.forEach((file) => {
|
||
if (file.startsWith('.')) return;
|
||
|
||
const fullPath = path.join(currentPath, file);
|
||
const relativePath = path.relative(baseDir, fullPath);
|
||
|
||
if (relativePath.includes('test') || relativePath.includes('tests')) return;
|
||
if (CONSTANTS.IGNORE_PATTERNS.some((pattern) => pattern.test(file))) return;
|
||
if (gitignoreRules?.ignores(relativePath)) return;
|
||
|
||
const lstat = fs.lstatSync(fullPath);
|
||
if (lstat.isSymbolicLink()) return;
|
||
|
||
const stats = fs.statSync(fullPath);
|
||
if (stats.isDirectory()) {
|
||
this.walkDirectory(fullPath, baseDir, projectFiles, gitignoreRules);
|
||
} else {
|
||
if (!projectFiles.has(file)) {
|
||
projectFiles.set(file, []);
|
||
}
|
||
projectFiles.get(file).push(fullPath);
|
||
}
|
||
});
|
||
},
|
||
};
|
||
|
||
const Features = {
|
||
async setupPaths() {
|
||
const paths = State.loadPaths();
|
||
let changed = false;
|
||
|
||
if (
|
||
!paths.apiProjectDir ||
|
||
!(await UI.confirm(`Use last API repo path (${paths.apiProjectDir})?`))
|
||
) {
|
||
paths.apiProjectDir = await UI.question('Enter the path to your API repo: ');
|
||
changed = true;
|
||
}
|
||
|
||
if (
|
||
!paths.webguiProjectDir ||
|
||
!(await UI.confirm(`Use last webgui repo path (${paths.webguiProjectDir})?`))
|
||
) {
|
||
paths.webguiProjectDir = await UI.question('Enter the path to your webgui repo: ');
|
||
changed = true;
|
||
}
|
||
|
||
if (changed) {
|
||
State.savePaths(paths);
|
||
}
|
||
|
||
return paths;
|
||
},
|
||
|
||
async handleWebComponentBuild() {
|
||
const webDir = path.join(global.apiProjectDir, 'web');
|
||
if (!fs.existsSync(webDir)) {
|
||
UI.log('Web directory not found in API repo!', 'error');
|
||
return;
|
||
}
|
||
|
||
UI.log('Building web components...', 'info');
|
||
const { exec } = require('child_process');
|
||
|
||
try {
|
||
await new Promise((resolve, reject) => {
|
||
const buildProcess = exec('pnpm run build', { cwd: webDir });
|
||
buildProcess.stdout.on('data', (data) => process.stdout.write(data));
|
||
buildProcess.stderr.on('data', (data) => process.stderr.write(data));
|
||
buildProcess.on('exit', (code) => {
|
||
if (code === 0) {
|
||
UI.log('Web components build completed successfully!', 'success');
|
||
resolve();
|
||
} else {
|
||
reject(new Error(`Web components build failed with code ${code}`));
|
||
}
|
||
});
|
||
});
|
||
} catch (err) {
|
||
UI.log(`Error during build: ${err.message}`, 'error');
|
||
}
|
||
},
|
||
|
||
async handleUiComponentBuild() {
|
||
const uiDir = path.join(global.apiProjectDir, 'unraid-ui');
|
||
if (!fs.existsSync(uiDir)) {
|
||
UI.log('Unraid UI directory not found in API repo!', 'error');
|
||
return;
|
||
}
|
||
|
||
UI.log('Building UI components...', 'info');
|
||
const { exec } = require('child_process');
|
||
|
||
try {
|
||
await new Promise((resolve, reject) => {
|
||
const buildProcess = exec('pnpm run build && pnpm run build:wc', { cwd: uiDir });
|
||
buildProcess.stdout.on('data', (data) => process.stdout.write(data));
|
||
buildProcess.stderr.on('data', (data) => process.stderr.write(data));
|
||
buildProcess.on('exit', (code) => {
|
||
if (code === 0) {
|
||
UI.log('UI build completed successfully!', 'success');
|
||
resolve();
|
||
} else {
|
||
reject(new Error(`UI build failed with code ${code}`));
|
||
}
|
||
});
|
||
});
|
||
} catch (err) {
|
||
UI.log(`Error during build: ${err.message}`, 'error');
|
||
}
|
||
},
|
||
|
||
async handleWebComponentSync() {
|
||
const apiWebPath = path.join(global.apiProjectDir, CONSTANTS.WEB_COMPONENTS.API_WEB_BUILD_PATH);
|
||
const webguiBasePath = path.join(global.webguiProjectDir, CONSTANTS.WEB_COMPONENTS.WEBGUI_BASE_PATH);
|
||
const webguiWebPath = path.join(webguiBasePath, CONSTANTS.WEB_COMPONENTS.WEBGUI_WEB_SUBPATH);
|
||
|
||
try {
|
||
if (!fs.existsSync(apiWebPath)) {
|
||
UI.log('Web components source directory not found! Did you build the web components?', 'error');
|
||
return;
|
||
}
|
||
|
||
UI.log('Removing old web components...', 'info');
|
||
fs.rmSync(webguiWebPath, { recursive: true, force: true });
|
||
|
||
UI.log('Copying new web components...', 'info');
|
||
FileSystem.copyDirectory(apiWebPath, webguiWebPath);
|
||
|
||
const indexPath = path.join(webguiWebPath, 'index.html');
|
||
if (fs.existsSync(indexPath)) {
|
||
UI.log('Removing irrelevant index.html...', 'info');
|
||
fs.unlinkSync(indexPath);
|
||
}
|
||
|
||
UI.playSound();
|
||
UI.log('Web components copied successfully!', 'success');
|
||
} catch (err) {
|
||
UI.log(`Error during sync: ${err.message}`, 'error');
|
||
}
|
||
},
|
||
|
||
async handleUiComponentSync() {
|
||
const apiUiPath = path.join(global.apiProjectDir, CONSTANTS.WEB_COMPONENTS.API_UI_BUILD_PATH);
|
||
const webguiBasePath = path.join(global.webguiProjectDir, CONSTANTS.WEB_COMPONENTS.WEBGUI_BASE_PATH);
|
||
const webguiUiPath = path.join(webguiBasePath, CONSTANTS.WEB_COMPONENTS.WEBGUI_UI_SUBPATH);
|
||
|
||
try {
|
||
if (!fs.existsSync(apiUiPath)) {
|
||
UI.log('Unraid UI source directory not found!', 'error');
|
||
return;
|
||
}
|
||
|
||
UI.log('Removing old UI components...', 'info');
|
||
fs.rmSync(webguiUiPath, { recursive: true, force: true });
|
||
|
||
UI.log('Copying new UI components...', 'info');
|
||
FileSystem.copyDirectory(apiUiPath, webguiUiPath);
|
||
|
||
UI.playSound();
|
||
UI.log('UI components copied successfully!', 'success');
|
||
} catch (err) {
|
||
UI.log(`Error during sync: ${err.message}`, 'error');
|
||
}
|
||
},
|
||
|
||
findMatchingFiles(apiProjectDir, webguiProjectDir) {
|
||
const matches = new Map();
|
||
const apiFiles = new Map();
|
||
const webguiFiles = new Map();
|
||
|
||
const gitignore1 = FileOps.loadGitignore(apiProjectDir);
|
||
const gitignore2 = FileOps.loadGitignore(webguiProjectDir);
|
||
|
||
FileOps.walkDirectory(apiProjectDir, apiProjectDir, apiFiles, gitignore1);
|
||
FileOps.walkDirectory(webguiProjectDir, webguiProjectDir, webguiFiles, gitignore2);
|
||
|
||
apiFiles.forEach((paths1, filename) => {
|
||
if (webguiFiles.has(filename)) {
|
||
matches.set(filename, [...paths1, ...webguiFiles.get(filename)]);
|
||
}
|
||
});
|
||
|
||
return matches;
|
||
},
|
||
|
||
findMissingPluginFiles(apiProjectDir, webguiProjectDir, ignoredFiles) {
|
||
const missingFiles = new Map();
|
||
|
||
if (!apiProjectDir || !webguiProjectDir) {
|
||
UI.log('API project and webgui project directories are required!', 'error');
|
||
return missingFiles;
|
||
}
|
||
|
||
const apiPluginsPath = path.join(apiProjectDir, CONSTANTS.PLUGIN_PATHS.API);
|
||
const webguiPluginsPath = path.join(webguiProjectDir, CONSTANTS.PLUGIN_PATHS.WEBGUI);
|
||
|
||
if (!fs.existsSync(apiPluginsPath)) {
|
||
UI.log('API plugins directory not found: ' + apiPluginsPath, 'error');
|
||
return missingFiles;
|
||
}
|
||
|
||
const gitignore1 = FileOps.loadGitignore(apiProjectDir);
|
||
|
||
function walkDir(currentPath, baseDir) {
|
||
if (!fs.existsSync(currentPath)) {
|
||
UI.log(`Directory doesn't exist: ${currentPath}`, 'warning');
|
||
return;
|
||
}
|
||
|
||
UI.log(`Checking directory: ${path.relative(apiProjectDir, currentPath)}`, 'info');
|
||
|
||
fs.readdirSync(currentPath).forEach((file) => {
|
||
if (file.startsWith('.')) {
|
||
UI.log(`Skipping dot file/dir: ${file}`, 'skip');
|
||
return;
|
||
}
|
||
|
||
const fullPath = path.join(currentPath, file);
|
||
const relativePath = path.relative(apiPluginsPath, fullPath);
|
||
|
||
if (CONSTANTS.IGNORE_PATTERNS.some((pattern) => pattern.test(file))) {
|
||
UI.log(`Skipping ignored pattern: ${file}`, 'skip');
|
||
return;
|
||
}
|
||
|
||
if (gitignore1?.ignores(relativePath)) {
|
||
UI.log(`Skipping gitignored file: ${file}`, 'skip');
|
||
return;
|
||
}
|
||
|
||
const lstat = fs.lstatSync(fullPath);
|
||
if (lstat.isSymbolicLink()) {
|
||
UI.log(`Skipping symlink: ${file}`, 'skip');
|
||
return;
|
||
}
|
||
|
||
const stats = fs.statSync(fullPath);
|
||
if (stats.isDirectory()) {
|
||
UI.log(`Found subdirectory: ${file}`, 'info');
|
||
walkDir(fullPath, baseDir);
|
||
return;
|
||
}
|
||
|
||
if (ignoredFiles.includes(file)) {
|
||
UI.log(`Skipping manually ignored file: ${file}`, 'skip');
|
||
return;
|
||
}
|
||
|
||
const webguiPath = path.join(webguiPluginsPath, relativePath);
|
||
if (!fs.existsSync(webguiPath)) {
|
||
UI.log(`Found new file: ${relativePath}`, 'new');
|
||
missingFiles.set(relativePath, {
|
||
source: fullPath,
|
||
destinationPath: webguiPath,
|
||
relativePath,
|
||
});
|
||
} else {
|
||
UI.log(`File exists in both: ${relativePath}`, 'success');
|
||
}
|
||
});
|
||
}
|
||
|
||
UI.log('\nStarting directory scan...', 'info');
|
||
UI.log(`API plugins path: ${apiPluginsPath}`, 'info');
|
||
UI.log(`Webgui plugins path: ${webguiPluginsPath}\n`, 'info');
|
||
|
||
try {
|
||
walkDir(apiPluginsPath, apiPluginsPath);
|
||
if (missingFiles.size > 0) {
|
||
State.saveNewFiles(
|
||
Object.fromEntries(
|
||
Array.from(missingFiles).map(([relativePath, info]) => [relativePath, info])
|
||
)
|
||
);
|
||
}
|
||
} catch (err) {
|
||
UI.log(`Error while scanning directories: ${err.message}`, 'error');
|
||
}
|
||
|
||
return missingFiles;
|
||
},
|
||
|
||
async handleNewFiles() {
|
||
const newFiles = State.getNewFiles();
|
||
const fileCount = Object.keys(newFiles).length;
|
||
|
||
if (fileCount === 0) {
|
||
UI.log('No new files to copy bruv!', 'info');
|
||
return;
|
||
}
|
||
|
||
UI.log(`Found ${fileCount} files to review:`, 'info');
|
||
|
||
const handledFiles = new Set();
|
||
const ignoredFiles = State.loadIgnoredFiles();
|
||
|
||
for (const [relativePath, info] of Object.entries(newFiles)) {
|
||
console.log(`\nFile: ${relativePath}`);
|
||
console.log(`From: ${info.source}`);
|
||
console.log(`To: ${info.destinationPath}`);
|
||
|
||
const answer = await UI.question(
|
||
'What should I do fam? (w=copy to webgui/i=ignore forever/s=skip/q=quit) '
|
||
);
|
||
|
||
switch (answer.toLowerCase()) {
|
||
case 'w':
|
||
if (FileSystem.copyFile(info.source, info.destinationPath)) {
|
||
UI.log(`Copied: ${relativePath}`, 'success');
|
||
handledFiles.add(relativePath);
|
||
}
|
||
break;
|
||
|
||
case 'i':
|
||
ignoredFiles.push(path.basename(relativePath));
|
||
State.saveIgnoredFiles(ignoredFiles);
|
||
UI.log(`Added ${path.basename(relativePath)} to ignore list`, 'info');
|
||
handledFiles.add(relativePath);
|
||
break;
|
||
|
||
case 'q':
|
||
UI.log('Stopping here fam!', 'info');
|
||
break;
|
||
|
||
default:
|
||
UI.log(`Skipped: ${relativePath}`, 'skip');
|
||
handledFiles.add(relativePath);
|
||
break;
|
||
}
|
||
|
||
if (answer.toLowerCase() === 'q') break;
|
||
}
|
||
|
||
const updatedNewFiles = Object.fromEntries(
|
||
Object.entries(newFiles).filter(([relativePath]) => !handledFiles.has(relativePath))
|
||
);
|
||
|
||
State.saveNewFiles(updatedNewFiles);
|
||
|
||
const remainingCount = Object.keys(updatedNewFiles).length;
|
||
UI.log('All done for now bruv! 🔥', 'success');
|
||
if (remainingCount > 0) {
|
||
UI.log(`${remainingCount} files left to handle next time.`, 'info');
|
||
}
|
||
},
|
||
};
|
||
|
||
const Menu = {
|
||
async show() {
|
||
while (true) {
|
||
try {
|
||
console.log('\nWhat you trying to do fam?');
|
||
console.log('1. Find new plugin files in API project');
|
||
console.log('2. Handle new plugin files in API project');
|
||
console.log('3. Sync shared files between API and webgui');
|
||
console.log('4. Build UI components');
|
||
console.log('5. Sync UI components');
|
||
console.log('6. Build web components');
|
||
console.log('7. Sync web components');
|
||
console.log('8. Exit\n');
|
||
|
||
const answer = await UI.question('Choose an option (1-8): ');
|
||
|
||
switch (answer) {
|
||
case '1': {
|
||
UI.log('Checking plugin directories for missing files bruv...', 'info');
|
||
const ignoredFiles = State.loadIgnoredFiles();
|
||
const missingFiles = Features.findMissingPluginFiles(
|
||
global.apiProjectDir,
|
||
global.webguiProjectDir,
|
||
ignoredFiles
|
||
);
|
||
|
||
if (missingFiles.size > 0) {
|
||
UI.log(`Found ${missingFiles.size} new files! 🔍`, 'info');
|
||
if (await UI.confirm('Want to handle these new files now fam?', false)) {
|
||
await Features.handleNewFiles();
|
||
} else {
|
||
UI.log('Safe, you can handle them later with option 2!', 'info');
|
||
}
|
||
} else {
|
||
UI.log('\n');
|
||
UI.log('No new files found bruv! 👌', 'success');
|
||
}
|
||
break;
|
||
}
|
||
|
||
case '2':
|
||
await Features.handleNewFiles();
|
||
break;
|
||
|
||
case '3': {
|
||
UI.log('Checking for matching files bruv...', 'info');
|
||
const matchingFiles = Features.findMatchingFiles(
|
||
global.apiProjectDir,
|
||
global.webguiProjectDir
|
||
);
|
||
|
||
if (matchingFiles.size === 0) {
|
||
UI.log('No matching files found fam!', 'info');
|
||
} else {
|
||
UI.log(`Found ${matchingFiles.size} matching files:\n`, 'info');
|
||
|
||
for (const [filename, paths] of matchingFiles) {
|
||
const [apiPath, webguiPath] = paths;
|
||
console.log(`File: ${filename}`);
|
||
|
||
const apiHash = FileSystem.getFileHash(apiPath);
|
||
const webguiHash = FileSystem.getFileHash(webguiPath);
|
||
|
||
if (apiHash !== webguiHash) {
|
||
await FileOps.handleFileDiff(apiPath, webguiPath);
|
||
} else {
|
||
UI.log('Files are identical', 'success');
|
||
}
|
||
}
|
||
}
|
||
break;
|
||
}
|
||
|
||
case '4':
|
||
await Features.handleUiComponentBuild();
|
||
break;
|
||
|
||
case '5':
|
||
await Features.handleUiComponentSync();
|
||
break;
|
||
|
||
case '6':
|
||
await Features.handleWebComponentBuild();
|
||
break;
|
||
|
||
case '7':
|
||
await Features.handleWebComponentSync();
|
||
break;
|
||
|
||
case '8':
|
||
UI.log('Safe bruv, catch you later! 👋', 'success');
|
||
UI.rl.close();
|
||
process.exit(0);
|
||
return;
|
||
|
||
default:
|
||
UI.log("Nah fam, that's not a valid option!", 'error');
|
||
break;
|
||
}
|
||
} catch (error) {
|
||
UI.log(error.message, 'error');
|
||
UI.rl.close();
|
||
process.exit(1);
|
||
}
|
||
}
|
||
}
|
||
};
|
||
|
||
const App = {
|
||
async init() {
|
||
try {
|
||
const paths = await Features.setupPaths();
|
||
global.apiProjectDir = paths.apiProjectDir;
|
||
global.webguiProjectDir = paths.webguiProjectDir;
|
||
await Menu.show();
|
||
} catch (error) {
|
||
UI.log(error.message, 'error');
|
||
UI.rl.close();
|
||
process.exit(1);
|
||
}
|
||
},
|
||
};
|
||
|
||
App.init().catch((error) => {
|
||
UI.log(`Something went wrong fam: ${error.message}`, 'error');
|
||
UI.rl.close();
|
||
process.exit(1);
|
||
});
|