Skip to content

Conversation

@zackspear
Copy link
Contributor

@zackspear zackspear commented Mar 27, 2025

Previously the historical script only use to copy built web components into the webgui repo.

The new script provides additional commands to find files in the API repo's plugin dir and attempts to find "new" files. Then a command to act upon each of these new file to sync in either direction, skip it, or ignore it.

Summary by CodeRabbit

  • New Features

    • Introduced an interactive synchronization tool that streamlines file transfers between projects.
    • Added a new command to easily trigger the synchronization process, leveraging enhanced file management and notification capabilities.
  • Chores

    • Updated version control settings to ignore temporary synchronization files.
    • Removed an outdated file copying script for improved maintenance.
    • Added new dependencies to support enhanced functionality.
    • Modified a script to exclude specific files from deletion during the activation code setup process.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 27, 2025

Walkthrough

This pull request introduces updates to configuration files and sync-related scripts. The .gitignore file is modified to include new ignore patterns, while package.json now features a new script and additional dependencies for file synchronization. The legacy Bash script (copy-to-webgui-repo.sh) is removed and replaced by a new Node.js script (sync-webgui-repo.js) that implements a file synchronization workflow with modular components and interactive command-line functionality.

Changes

Files Change Summary
.gitignore, package.json Updated configuration to support web GUI synchronization: added new ignore pattern for web/scripts/.sync-webgui-repo-*, a new script "sync-webgui-repo": "node web/scripts/sync-webgui-repo.js", and new dependencies (chalk, diff, and ignore).
web/scripts/copy-to-webgui-repo.sh, web/scripts/sync-webgui-repo.js Removed legacy Bash script for copying files to the web GUI repo and introduced a new Node.js sync script that implements file synchronization with modules for file system operations, user interface, state management, and interactive menu.
plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/data/activation-data.php, plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/include/activation-code-extractor.php, plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/include/web-components-extractor.php, plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/scripts/activation_code_remove Removed activation-data.php and modified related files: updated documentation in activation-code-extractor.php, added input sanitization in web-components-extractor.php, and adjusted file deletion logic in activation_code_remove.

Sequence Diagram(s)

sequenceDiagram
    participant U as User
    participant A as App (sync-webgui-repo.js)
    participant M as Menu
    participant UI as UI Module
    participant FO as FileOps Module
    participant FS as FileSystem Module

    U->>A: Start sync process
    A->>M: Initialize and display menu
    M->>U: Present action options
    U->>M: Choose a synchronization action
    M->>UI: Relay selected action
    UI->>FO: Request file operations (e.g., load gitignore, diff files)
    FO->>FS: Execute file system tasks (read/write, copy files)
    FS-->>FO: Return results
    FO-->>UI: Provide synchronization status
    UI-->>M: Log operation feedback
    M-->>A: Conclude action
    A-->>U: Notify synchronization complete
Loading

Suggested reviewers

  • elibosley
  • pujitm

Poem

In the realm of code where changes flow,
New scripts and flags begin to glow.
Files sync in a digital ballet,
Bash steps out, as Node leads the way.
Cheers to the code that sings today! 🎉


🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai plan to trigger planning for file edits and PR creation.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (7)
.gitignore (1)

104-106: Add a brief explanation of why these files are ignored.
Consider appending a quick comment describing the purpose of .sync-webgui-repo-* so that new contributors understand the rationale behind ignoring these script-related files.

web/scripts/sync-webgui-repo.js (6)

1-24: Centralize error handling for missing dependencies.
The script gracefully imports multiple modules (fs, path, etc.) but does not handle cases where dependencies might be missing or incorrectly installed. Adding a quick dependency check or a user-friendly warning if a module import fails could improve reliability.


26-78: Consider switching to asynchronous methods.
All file operations are synchronous (e.g., readFileSync, copyFileSync). While acceptable for a quick CLI tool, asynchronous methods could provide better scalability if the script grows in complexity or needs parallel processing.


80-117: Ensure cross-platform support for sound notifications.
The UI.playSound() method only checks known paths for macOS, Linux, and Windows. If users have custom system setups or different file paths, consider allowing them to configure sound commands or handle missing commands gracefully.


119-239: Validate file paths before usage.
The State and FileOps objects read from and write to JSON files. If paths are invalid or corrupted JSON is encountered, consider gracefully handling errors and prompting users to fix the files or reinitialize them.


241-508: Evaluate build errors for partial successes.
When building web components in handleWebComponentBuild(), the script treats the entire process as a single success/fail step. For complex builds, consider capturing partial successes or logging more granular build output to help diagnose smaller step failures.


610-630: Include automated tests.
The App.init() flow orchestrates user prompts and environment setup but lacks automated tests. Adding a few unit or integration tests (possibly mocking UI and file operations) would help ensure reliability for repeated usage.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro (Legacy)

📥 Commits

Reviewing files that changed from the base of the PR and between d23a89f and 115c53a.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (4)
  • .gitignore (1 hunks)
  • package.json (2 hunks)
  • web/scripts/copy-to-webgui-repo.sh (0 hunks)
  • web/scripts/sync-webgui-repo.js (1 hunks)
💤 Files with no reviewable changes (1)
  • web/scripts/copy-to-webgui-repo.sh
⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: Build Web App
  • GitHub Check: Test API
  • GitHub Check: Build API
  • GitHub Check: Cloudflare Pages
🔇 Additional comments (2)
package.json (2)

14-15: Ensure script clarity.
Adding "sync-webgui-repo": "node web/scripts/sync-webgui-repo.js" is good for future reference. If further arguments or environment variables are needed, consider updating the script interface to accommodate them.


37-40:

Details

❓ Verification inconclusive

Check for security updates.
While these dependencies are helpful for file operations and user interactions, ensure they are kept up to date to avoid potential vulnerabilities.

Please run a quick check on known vulnerabilities for these packages. For instance:


🏁 Script executed:

#!/bin/bash
npm audit

Length of output: 360


Security Audit Required: Missing Lockfile Detected
It appears that running the audit failed due to a missing lock file. Please create a lock file (using npm i --package-lock-only) and then re-run npm audit to confirm that these dependencies have no known vulnerabilities.

  • Ensure a package-lock file is present:
    npm i --package-lock-only
    npm audit
  • Verify that the following dependencies are free from security issues:
    • @manypkg/cli
    • chalk
    • diff
    • ignore

@zackspear zackspear changed the title feat(: copy to webgui repo script docs + wc build options feat: copy to webgui repo script docs + wc build options Mar 27, 2025
@zackspear zackspear force-pushed the feat/web-copy-to-webgui-repo-script-improvements branch from 115c53a to bfdfb14 Compare March 27, 2025 21:26
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (4)
web/scripts/sync-webgui-repo.js (4)

154-194: Consider enhancing diff visualization.

The diff visualization works but could be improved for better clarity, especially for larger diffs.

Consider adding line numbers and limiting the context of diffs for very large files:

 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.cyan(path.basename(apiFile)));
     console.log(chalk.red('--- webgui:'), webguiFile);
     console.log(chalk.green('+++ api:  '), apiFile);

+    // Limit large diffs to improve readability
+    const diffLines = differences.split('\n').slice(5);
+    const maxLines = 50;
+    const showTruncationMessage = diffLines.length > maxLines;
+    const linesToShow = showTruncationMessage ? diffLines.slice(0, maxLines) : diffLines;
+
+    let lineCounter = 0;
     differences
       .split('\n')
       .slice(5)
+      .slice(0, maxLines)
       .forEach((line) => {
-        if (line.startsWith('+')) console.log(chalk.green(line));
-        else if (line.startsWith('-')) console.log(chalk.red(line));
-        else if (line.startsWith('@')) console.log(chalk.cyan(line));
-        else console.log(line);
+        lineCounter++;
+        const lineNum = `${lineCounter}`.padStart(4);
+        if (line.startsWith('+')) console.log(`${lineNum} ${chalk.green(line)}`);
+        else if (line.startsWith('-')) console.log(`${lineNum} ${chalk.red(line)}`);
+        else if (line.startsWith('@')) console.log(`${lineNum} ${chalk.cyan(line)}`);
+        else console.log(`${lineNum} ${line}`);
       });
+      
+    if (showTruncationMessage) {
+      console.log(chalk.yellow(`\n... (${diffLines.length - maxLines} more lines not shown)`));
+      console.log(chalk.yellow(`Total diff size: ${diffLines.length} lines\n`));
+    }
     return true;
   }
   return false;
 },

196-210: Enhance user experience in handleFileDiff with better prompts.

The prompt message "What should I do fam?" is informal, which may be fine for a personal tool but could be improved for better clarity.

Consider improving the prompt with more detailed options:

 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) '
+    'Action to take: (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';
   }
 },

212-239: Enhance directory traversal with more explicit filtering.

The traversal function has inline filtering logic that could be extracted to make the code more maintainable.

Consider refactoring to make the filtering logic more explicit:

 walkDirectory(currentPath, baseDir, projectFiles, gitignoreRules) {
   const files = fs.readdirSync(currentPath);

+  // Helper function to determine if a file should be skipped
+  const shouldSkipFile = (file, relativePath) => {
+    if (file.startsWith('.')) return true;
+    if (relativePath.includes('test') || relativePath.includes('tests')) return true;
+    if (CONSTANTS.IGNORE_PATTERNS.some((pattern) => pattern.test(file))) return true;
+    if (gitignoreRules?.ignores(relativePath)) return true;
+    return false;
+  };

   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;
+    if (shouldSkipFile(file, 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);
     }
   });
 },

349-444: Improve error handling in findMissingPluginFiles function.

The function contains a nested walkDir function with verbose logging but could benefit from more robust error handling.

Consider adding try/catch blocks around file operations within the walkDir function:

 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;
     }

+    try {
       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');
       }
+    } catch (err) {
+      UI.log(`Error processing file ${file}: ${err.message}`, 'error');
+    }
   });
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro (Legacy)

📥 Commits

Reviewing files that changed from the base of the PR and between 115c53a and bfdfb14.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (4)
  • .gitignore (1 hunks)
  • package.json (2 hunks)
  • web/scripts/copy-to-webgui-repo.sh (0 hunks)
  • web/scripts/sync-webgui-repo.js (1 hunks)
💤 Files with no reviewable changes (1)
  • web/scripts/copy-to-webgui-repo.sh
🚧 Files skipped from review as they are similar to previous changes (2)
  • .gitignore
  • package.json
⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: Build API
  • GitHub Check: Build Web App
  • GitHub Check: Test API
  • GitHub Check: Cloudflare Pages
🔇 Additional comments (13)
web/scripts/sync-webgui-repo.js (13)

1-8: Necessary dependencies are properly imported.

The script correctly imports all required Node.js core modules and external dependencies needed for file operations, diffing, and UI interactions.


9-24: Well-organized constants structure.

The CONSTANTS object is well-structured with clear categorization of paths, ignore patterns, and directory locations. This makes the configuration easy to understand and maintain.


26-78: FileSystem module implements robust file operations.

The module provides comprehensive file handling functionality with proper error handling. The copyDirectory method correctly handles recursive copying of directories and files.


80-117: Good UI interaction implementation.

The UI module effectively handles user interactions with proper promise-based question handling and visual feedback through icons. The cross-platform sound implementation is a nice touch for user experience.


119-152: Robust state management.

The State module provides clean interfaces for persisting configuration and state between runs. The path normalization in savePaths ensures consistent path formats.


241-268: Good implementation of path setup.

The setupPaths function provides a good user experience by remembering previous paths and allowing users to confirm or change them.


269-298: Web component build process handles errors properly.

The build process implementation correctly streams output and properly resolves/rejects the promise based on the exit code.


300-327: Web component sync handles old file cleanup properly.

The sync process correctly removes the old directory before copying new files, which prevents orphaned files from previous builds.


329-347: findMatchingFiles effectively compares files across repositories.

The function correctly identifies files that exist in both repositories by comparing filenames.


446-508: Good implementation of handleNewFiles with user interaction.

The function provides an interactive workflow for handling new files with options to copy, ignore, or skip, and properly updates the state after handling files.


511-608: Menu implementation provides clear options for the user.

The menu structure is clear and provides good error handling, ensuring the application doesn't crash when unexpected inputs occur.


610-623: App initialization module is well-structured.

The App module cleanly encapsulates the initialization logic and properly handles errors.


625-629: Appropriate error handling for the main application.

The script properly handles any uncaught errors in the main promise chain.

zackspear and others added 13 commits March 28, 2025 14:37
- checks for new missing plugin files
- interactively handle new missing plugin files
- checks for files to update between repos, displaying diff, with interaction to update in either direction
- build and sync web components
…ction

- Modified the menu display to keep showing until the user chooses to exit.
- Enhanced error handling to ensure the application exits gracefully on errors.
- Maintained existing functionality for handling new plugin files, syncing, and building web components.
- Revised user prompts for clarity in file copy operations.
- Adjusted the switch cases to ensure correct file copy direction based on user input.
Per discussion w/ Eli removed the activation code debugger file from the
repo. I kept it locally though, hence the `.gitignore` update. Which
means I kept the helper method in the activation code extractor class so
I could still use it.

coderabbit also made a good suggestion in the webgui repo where I synced
changes from this repo for web-component-extractor so I implemented and
tested it. Included via [`ec29069`
(#1287)](ec29069)
@zackspear zackspear force-pushed the feat/web-copy-to-webgui-repo-script-improvements branch from 90a775e to c78d1c2 Compare March 28, 2025 21:37
@github-actions
Copy link
Contributor

This plugin has been deployed to Cloudflare R2 and is available for testing.
Download it at this URL:

https://preview.dl.unraid.net/unraid-api/tag/PR1285/dynamix.unraid.net.plg

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (4)
web/scripts/sync-webgui-repo.js (4)

1-8: Consider adding module documentation.

Adding a brief module header comment explaining the purpose of this script would help other developers understand its functionality at a glance.

+/**
+ * Synchronizes files between API and WebGUI repositories
+ * Supports finding new plugin files, handling diffs, and syncing web components
+ */
 const fs = require('fs');
 const path = require('path');
 const ignore = require('ignore');

165-194: Improve diff visualization with additional context.

The diff function could be enhanced by providing more context about what's being compared and adding color indicators for clarity.

 showDiff(apiFile, webguiFile) {
   const content1 = fs.readFileSync(apiFile, 'utf8');
   const content2 = fs.readFileSync(webguiFile, 'utf8');

+  // Add error handling for file reads
+  try {
     const differences = diff.createPatch(
       path.basename(apiFile),
       content2,
       content1,
       'webgui version',
       'api version'
     );

     if (differences.split('\n').length > 5) {
-      console.log('\nDiff for', chalk.cyan(path.basename(apiFile)));
+      console.log('\n' + chalk.bold(`Diff for ${path.basename(apiFile)}`));
       console.log(chalk.red('--- webgui:'), webguiFile);
       console.log(chalk.green('+++ api:  '), apiFile);

       differences
         .split('\n')
         .slice(5)
         .forEach((line) => {
-          if (line.startsWith('+')) console.log(chalk.green(line));
-          else if (line.startsWith('-')) console.log(chalk.red(line));
+          if (line.startsWith('+')) console.log(chalk.green(line) + ' ← API');
+          else if (line.startsWith('-')) console.log(chalk.red(line) + ' ← WebGUI');
           else if (line.startsWith('@')) console.log(chalk.cyan(line));
           else console.log(line);
         });
       return true;
     }
     return false;
+  } catch (err) {
+    UI.log(`Error comparing files: ${err.message}`, 'error');
+    return false;
+  }
 },

224-229: Optimize file stat operations.

You're calling both lstatSync and statSync on the same file, which is inefficient. Consider consolidating these operations.

- const lstat = fs.lstatSync(fullPath);
- if (lstat.isSymbolicLink()) return;
-
- const stats = fs.statSync(fullPath);
+ // Get file stats with the withFileTypes option to determine type
+ const stats = fs.statSync(fullPath, { throwIfNoEntry: false });
+ if (!stats) return;
+ 
+ // Use fs.lstatSync only when checking for symbolic links
+ if (stats.isSymbolicLink?.() || fs.lstatSync(fullPath).isSymbolicLink()) return;

610-624: Add validation for project paths.

The script doesn't verify if the paths provided by the user are valid repository roots. Add some basic validation to ensure they point to actual repositories.

 const App = {
   async init() {
     try {
       const paths = await Features.setupPaths();
+      
+      // Validate paths point to valid repositories
+      const validateRepo = (dir, name) => {
+        if (!fs.existsSync(dir)) {
+          throw new Error(`${name} directory doesn't exist: ${dir}`);
+        }
+        
+        // Check for common repository indicators
+        const hasGit = fs.existsSync(path.join(dir, '.git'));
+        const hasPackageJson = fs.existsSync(path.join(dir, 'package.json'));
+        
+        if (!hasGit && !hasPackageJson) {
+          UI.log(`Warning: ${name} directory might not be a valid repository.`, 'warning');
+          return UI.confirm(`Continue with this path anyway?`);
+        }
+        return true;
+      };
+      
+      // Validate both paths
+      const apiValid = await validateRepo(paths.apiProjectDir, 'API');
+      const webguiValid = await validateRepo(paths.webguiProjectDir, 'WebGUI');
+      
+      if (!apiValid || !webguiValid) {
+        throw new Error('Invalid repository paths. Please restart and provide valid paths.');
+      }
+      
       global.apiProjectDir = paths.apiProjectDir;
       global.webguiProjectDir = paths.webguiProjectDir;
       await Menu.show();
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro (Legacy)

📥 Commits

Reviewing files that changed from the base of the PR and between 90a775e and c78d1c2.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (8)
  • .gitignore (1 hunks)
  • package.json (2 hunks)
  • plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/data/activation-data.php (0 hunks)
  • plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/include/activation-code-extractor.php (0 hunks)
  • plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/include/web-components-extractor.php (1 hunks)
  • plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/scripts/activation_code_remove (0 hunks)
  • web/scripts/copy-to-webgui-repo.sh (0 hunks)
  • web/scripts/sync-webgui-repo.js (1 hunks)
💤 Files with no reviewable changes (4)
  • plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/include/activation-code-extractor.php
  • plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/scripts/activation_code_remove
  • plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/data/activation-data.php
  • web/scripts/copy-to-webgui-repo.sh
🚧 Files skipped from review as they are similar to previous changes (3)
  • plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/include/web-components-extractor.php
  • .gitignore
  • package.json
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: Build Web App
  • GitHub Check: Build API
  • GitHub Check: Cloudflare Pages
🔇 Additional comments (2)
web/scripts/sync-webgui-repo.js (2)

511-608: Consider adding a batch operation mode for file sync.

The interactive design requires handling each file individually. For users with many files, a batch operation option would be more efficient.

 const Menu = {
   async show() {
     while (true) { // Keep showing menu until exit
       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('3b. Batch sync files (copy all API→WebGUI or WebGUI→API)');
         console.log('4. Build web components');
         console.log('5. Sync web components');
         console.log('6. Exit\n');

-        const answer = await UI.question('Choose an option (1-6): ');
+        const answer = await UI.question('Choose an option (1-6, 3b): ');

         switch (answer) {
           // ... existing cases
           
+          case '3b': {
+            UI.log('Batch sync mode activated...', 'info');
+            const matchingFiles = Features.findMatchingFiles(
+              global.apiProjectDir,
+              global.webguiProjectDir
+            );
+
+            if (matchingFiles.size === 0) {
+              UI.log('No matching files found!', 'info');
+              break;
+            }
+
+            UI.log(`Found ${matchingFiles.size} matching files.`, 'info');
+            const direction = await UI.question('Sync direction? (a=API→WebGUI, w=WebGUI→API, c=cancel): ');
+            
+            if (direction.toLowerCase() === 'c') {
+              UI.log('Batch sync canceled.', 'info');
+              break;
+            }
+            
+            let syncCount = 0;
+            for (const [filename, paths] of matchingFiles) {
+              const [apiPath, webguiPath] = paths;
+              const apiHash = FileSystem.getFileHash(apiPath);
+              const webguiHash = FileSystem.getFileHash(webguiPath);
+              
+              if (apiHash !== webguiHash) {
+                if (direction.toLowerCase() === 'a' && FileSystem.copyFile(apiPath, webguiPath)) {
+                  UI.log(`Copied to WebGUI: ${filename}`, 'success');
+                  syncCount++;
+                } else if (direction.toLowerCase() === 'w' && FileSystem.copyFile(webguiPath, apiPath)) {
+                  UI.log(`Copied to API: ${filename}`, 'success');
+                  syncCount++;
+                }
+              }
+            }
+            
+            UI.log(`Batch sync complete. Synced ${syncCount} files.`, 'success');
+            break;
+          }

625-629: LGTM! The error handling looks good here.

The global error handling ensures that uncaught exceptions are properly logged before exiting.

Comment on lines +57 to +62
getFileHash(filePath) {
const fileBuffer = fs.readFileSync(filePath);
const hashSum = crypto.createHash('sha256');
hashSum.update(fileBuffer);
return hashSum.digest('hex');
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error handling for file reads in getFileHash.

This function will throw an uncaught exception if the file doesn't exist or can't be read.

 getFileHash(filePath) {
-    const fileBuffer = fs.readFileSync(filePath);
-    const hashSum = crypto.createHash('sha256');
-    hashSum.update(fileBuffer);
-    return hashSum.digest('hex');
+    try {
+      const fileBuffer = fs.readFileSync(filePath);
+      const hashSum = crypto.createHash('sha256');
+      hashSum.update(fileBuffer);
+      return hashSum.digest('hex');
+    } catch (err) {
+      UI.log(`Failed to get hash for ${filePath}: ${err.message}`, 'error');
+      return null;
+    }
 },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
getFileHash(filePath) {
const fileBuffer = fs.readFileSync(filePath);
const hashSum = crypto.createHash('sha256');
hashSum.update(fileBuffer);
return hashSum.digest('hex');
},
getFileHash(filePath) {
try {
const fileBuffer = fs.readFileSync(filePath);
const hashSum = crypto.createHash('sha256');
hashSum.update(fileBuffer);
return hashSum.digest('hex');
} catch (err) {
UI.log(`Failed to get hash for ${filePath}: ${err.message}`, 'error');
return null;
}
},

Comment on lines +107 to +116
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);
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider error handling for playSound.

If the sound command fails, it could throw an uncaught exception. Also, there's a potential security risk with direct command execution.

 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);
+  if (sound) {
+    try {
+      require('child_process').exec(sound, (error) => {
+        if (error) console.error('Sound playback error:', error);
+      });
+    } catch (err) {
+      console.error('Failed to play sound:', err);
+    }
+  }
 },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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);
},
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) {
try {
require('child_process').exec(sound, (error) => {
if (error) console.error('Sound playback error:', error);
});
} catch (err) {
console.error('Failed to play sound:', err);
}
}
},

Comment on lines +366 to +405

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;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider extracting the walkDir function to a more reusable form.

The nested walkDir function duplicates logic from FileOps.walkDirectory. Consider refactoring to reduce code duplication.

-  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;
-      }
+  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');
+
+    try {
+      const files = fs.readdirSync(currentPath);
+      
+      for (const file of files) {
+        if (file.startsWith('.')) {
+          UI.log(`Skipping dot file/dir: ${file}`, 'skip');
+          continue;
+        }
+
+        const fullPath = path.join(currentPath, file);
+        const relativePath = path.relative(apiPluginsPath, fullPath);
+
+        // Skip files matching ignore patterns
+        if (CONSTANTS.IGNORE_PATTERNS.some((pattern) => pattern.test(file))) {
+          UI.log(`Skipping ignored pattern: ${file}`, 'skip');
+          continue;
+        }
+
+        if (gitignore1?.ignores(relativePath)) {
+          UI.log(`Skipping gitignored file: ${file}`, 'skip');
+          continue;
+        }
+
+        try {
+          const lstat = fs.lstatSync(fullPath);
+          if (lstat.isSymbolicLink()) {
+            UI.log(`Skipping symlink: ${file}`, 'skip');
+            continue;
+          }
+
+          const stats = fs.statSync(fullPath);
+          if (stats.isDirectory()) {
+            UI.log(`Found subdirectory: ${file}`, 'info');
+            walkDir(fullPath, baseDir);
+            continue;
+          }

Committable suggestion skipped: line range outside the PR's diff.

@elibosley elibosley merged commit e54f189 into main Mar 31, 2025
8 checks passed
@elibosley elibosley deleted the feat/web-copy-to-webgui-repo-script-improvements branch March 31, 2025 18:36
elibosley pushed a commit that referenced this pull request Apr 2, 2025
🤖 I have created a release *beep* *boop*
---


## [4.5.0](v4.4.1...v4.5.0)
(2025-04-02)


### Features

* add webgui theme switcher component
([#1304](#1304))
([e2d00dc](e2d00dc))
* api plugin system & offline versioned dependency vendoring
([#1252](#1252))
([9f492bf](9f492bf))
* **api:** add `unraid-api --delete` command
([#1289](#1289))
([2f09445](2f09445))
* basic array controls
([#1291](#1291))
([61fe696](61fe696))
* basic docker controls
([#1292](#1292))
([12eddf8](12eddf8))
* copy to webgui repo script docs + wc build options
([#1285](#1285))
([e54f189](e54f189))


### Bug Fixes

* additional url fixes
([4b2763c](4b2763c))
* **api:** redirect benign pnpm postinstall warning to log file
([#1290](#1290))
([7fb7849](7fb7849))
* **deps:** update dependency chalk to v5
([#1296](#1296))
([6bed638](6bed638))
* **deps:** update dependency diff to v7
([#1297](#1297))
([3c6683c](3c6683c))
* disable all config watchers
([#1306](#1306))
([5c1b435](5c1b435))
* extract callbacks to library
([#1280](#1280))
([2266139](2266139))
* OEM plugin issues ([#1288](#1288))
([d5a3d0d](d5a3d0d))
* replace files lost during pruning
([d0d2ff6](d0d2ff6))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants