Conversation
Renamed afterInstall.ps1 to postInstall.ps1 and added telemetry support for PostHog. Updated Python embeddable package URLs to version 3.14.0. Revised setup.iss to improve component granularity, update author info, and integrate the new post-install script and telemetry parameters.
Introduces a DownloadUrls.psd1 manifest for component URLs and refactors postInstall.ps1 to support modular, architecture-aware downloads and installations. Adds selection logic for components, improves error handling, and updates setup.iss to support new GTK3 and GTK-server options. This change enables easier maintenance and extensibility for future components.
Corrected syntax errors in component definitions and registry entries, updated post-install PowerShell command formatting, added AppVersion custom message, and implemented GetSelectedComponents function for improved component selection handling.
Deleted tessdata, tessdata_best, and tessdata_fast submodules. Updated DownloadUrls.psd1 to fix Python x86 URL and set gtk3runtime arm64 to null. Improved architecture detection in postInstall.ps1 and cleaned up component selection logic. Modified setup.iss to remove exclusions for tessdata assets and cleaned up run/uninstall sections.
Replaces the $process_person_profile property with a product property set to 'welsonjs' in the telemetry event payload for PostHog. This clarifies the product being tracked during installation.
Introduces WinDivert and Android Platform Tools as selectable components in the installer. Updates DownloadUrls.psd1 with their download URLs, adds their handling in postInstall.ps1, and registers them in setup.iss for installation.
Updated the component name from 'android_tools' to 'android_platform_tools' in DownloadUrls.psd1 and postInstall.ps1 for consistency and clarity. Adjusted variable names and references accordingly.
Updated the component name from 'android_tools' to 'android_platform_tools' in setup.iss for consistency and clarity.
Removed unnecessary installer arguments and ensured installers run with process waiting for completion in postInstall.ps1. Cleaned up setup.iss to remove redundant 'Components' field for PowerShell execution. Minor formatting fix in DownloadUrls.psd1.
Introduces extraction and installation logic for WinDivert and Android Platform Tools components in postInstall.ps1. Also updates log messages for consistency and clarity.
Changed the default adb binary path to use the app data directory for improved portability. Updated copyright and contributor information. Bumped VERSIONINFO to 0.2.3.
Fix the file path in adb.js
Use the `DownloadUrls.psd1` file instead of binaries_meta.json
Reviewer's GuideRefactors the installer to use a new, component-aware PowerShell post-install script that downloads/extracts optional runtime tools at install time, wires it into Inno Setup with telemetry support, and adjusts ADB to use a new Android platform tools layout while cleaning up bundled assets and metadata files. Sequence diagram for Inno Setup invoking the new component-aware post-install scriptsequenceDiagram
actor User
participant InnoSetup
participant GetSelectedComponents
participant PostInstallPS1
participant DownloadUrlsPsd1
participant RemoteCDNs
participant PostHogAPI
participant AppDataWelsonJS
participant TempDownloads
User->>InnoSetup: Run WelsonJS installer
InnoSetup->>User: Show component selection UI
User-->>InnoSetup: Select components
InnoSetup->>GetSelectedComponents: WizardSelectedComponents(False)
GetSelectedComponents-->>InnoSetup: components_string
InnoSetup->>PostInstallPS1: Start postInstall.ps1
note over InnoSetup,PostInstallPS1: Pass TelemetryProvider, TelemetryApiKey, Version, DistinctId, Components
PostInstallPS1->>PostInstallPS1: Resolve ScriptRoot and TargetDir
PostInstallPS1->>DownloadUrlsPsd1: Import-PowerShellDataFile
DownloadUrlsPsd1-->>PostInstallPS1: DownloadUrls table
PostInstallPS1->>PostInstallPS1: Parse Components string
PostInstallPS1->>PostInstallPS1: Detect native architecture
alt TelemetryProvider is posthog and TelemetryApiKey present
PostInstallPS1->>PostInstallPS1: Build anonymous event payload
PostInstallPS1->>PostHogAPI: POST /i/v0/e
PostHogAPI-->>PostInstallPS1: 2xx or error (ignored)
end
loop For each selected component
PostInstallPS1->>PostInstallPS1: Get-DownloadUrl(component, arch)
alt URL available
PostInstallPS1->>TempDownloads: Download-File
TempDownloads-->>PostInstallPS1: Archive/installer file
else URL missing
PostInstallPS1->>PostInstallPS1: Log skip for this component
end
end
loop For each downloaded component
alt Zip or similar archive
PostInstallPS1->>AppDataWelsonJS: Extract-CompressedFile
else TarGz archive
PostInstallPS1->>AppDataWelsonJS: Extract-TarGzArchive
else Native installer (gtk3runtime, npcap, nmap)
PostInstallPS1->>RemoteCDNs: Start-Process installer
end
end
PostInstallPS1-->>InnoSetup: Exit 0 on success
InnoSetup-->>User: Show completion and shortcuts
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. WalkthroughReplaces Changes
Sequence Diagram(s)sequenceDiagram
participant Installer as setup.iss
participant PostInstall as postInstall.ps1
participant DownloadCfg as data/DownloadUrls.psd1
participant Telemetry as PostHog
participant Remote as RemoteBinaries
participant FS as FileSystem
Installer->>PostInstall: Launch (Version, TelemetryProvider, Components, DistinctId)
PostInstall->>DownloadCfg: Load URL mappings
DownloadCfg-->>PostInstall: Return per-arch URLs
PostInstall->>PostInstall: Detect native architecture
PostInstall->>PostInstall: Parse selected components
alt Telemetry enabled
PostInstall->>Telemetry: Send app_installed event (non-blocking)
Telemetry-->>PostInstall: Ack / Error
end
loop For each selected component
PostInstall->>DownloadCfg: Resolve component URL for arch
DownloadCfg-->>PostInstall: URL or null
alt URL available
PostInstall->>Remote: Download (with retries)
Remote-->>FS: Save archive to TEMP
PostInstall->>FS: Extract/install into AppData\<component>\
else URL missing
PostInstall-->>PostInstall: Log skip
end
end
alt Component has external installer
PostInstall->>FS: Execute installer (wait) and handle post-steps (VC_redist, Npcap/Nmap)
FS-->>PostInstall: Install results
end
PostInstall->>Installer: Exit 0 / Exit 1
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes
Possibly related PRs
Suggested labels
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
PR Compliance Guide 🔍Below is a summary of compliance checks for this PR:
Compliance status legend🟢 - Fully Compliant🟡 - Partial Compliant 🔴 - Not Compliant ⚪ - Requires Further Human Verification 🏷️ - Compliance label |
|||||||||||||||||||||||||||||
There was a problem hiding this comment.
Hey there - I've reviewed your changes and found some issues that need to be addressed.
Blocking issues:
- Detected a Generic API Key, potentially exposing access to various services and sensitive operations. (link)
General comments:
- In
postInstall.ps1the Android Platform Tools extraction block is checkingTest-Path $WinDivertCompressedinstead of$AndroidPlatformToolsCompressed, so the Android tools will never be installed even when downloaded; this should be corrected to use the Android archive variable consistently. - The download/install handling in
postInstall.ps1is very repetitive per component; consider centralizing the component metadata (archive variable, target folder, extractor type) into a hashtable and looping over it to reduce duplication and make adding/removing components less error-prone.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In `postInstall.ps1` the Android Platform Tools extraction block is checking `Test-Path $WinDivertCompressed` instead of `$AndroidPlatformToolsCompressed`, so the Android tools will never be installed even when downloaded; this should be corrected to use the Android archive variable consistently.
- The download/install handling in `postInstall.ps1` is very repetitive per component; consider centralizing the component metadata (archive variable, target folder, extractor type) into a hashtable and looping over it to reduce duplication and make adding/removing components less error-prone.
## Individual Comments
### Comment 1
<location> `postInstall.ps1:568-577` </location>
<code_context>
+ }
+
+ # Android Platform Tools (component: android_platform_tools)
+ if (Test-ComponentSelected -Name "android_platform_tools") {
+ if (Test-Path $WinDivertCompressed) {
+ Extract-CompressedFile `
+ -CompressedPath $AndroidPlatformToolsCompressed `
</code_context>
<issue_to_address>
**issue (bug_risk):** Android Platform Tools extraction is guarded by the WinDivert archive path, so the Android tools will never be installed.
In the Android Platform Tools block you’re checking `Test-Path $WinDivertCompressed` instead of `$AndroidPlatformToolsCompressed`, so extraction only happens when the WinDivert archive exists and fails when only `android_platform_tools` is selected. This seems like a copy/paste error; please update the `Test-Path` to use `$AndroidPlatformToolsCompressed` to align with the download path.
</issue_to_address>
### Comment 2
<location> `lib/adb.js:94` </location>
<code_context>
// set the binary path
- this.binPath = "bin\\platform-tools_r33.0.0-windows\\platform-tools\\adb.exe";
+ this.binPath = SYS.getAppDataDir() + "\\android_platform_tools\\platform-tools\\adb.exe";
this._interface.setPrefix(this.binPath);
}
</code_context>
<issue_to_address>
**issue (bug_risk):** New adb binary path assumes the Android Platform Tools component was installed, which may break adb usage if that component is not selected.
The old path targeted a bundled `adb.exe`, but the new path relies on `%APPDATA%\android_platform_tools`, which only exists if the `android_platform_tools` component is installed and its post-install script succeeds. Without that, `adb.js` will reference a missing binary. Please add a runtime check with a clear error if the file is absent, or fall back to the bundled path when the AppData location is unavailable.
</issue_to_address>
### Comment 3
<location> `setup.iss:95` </location>
<code_context>
-Filename: "powershell.exe"; Parameters: "-ExecutionPolicy Bypass -NoProfile -File ""{app}\afterInstall.ps1"""; WorkingDir: "{app}"; Components: addtools; Flags: waituntilterminated
-Filename: {app}\installService.bat; Components: addtools; Flags: nowait
-Filename: "{userappdata}\{cm:AppName}\bin\WelsonJS.Launcher.exe"; Components: addtools; Flags: nowait
+Filename: "powershell.exe"; Parameters: "-ExecutionPolicy Bypass -NoProfile -File ""{app}\postInstall.ps1"" -TelemetryProvider posthog -TelemetryApiKey ""{cm:PostHogApiKey}"" -Version ""{cm:AppVersion}"" -DistinctId ""{computername}"" -Components ""{code:GetSelectedComponents}"""; WorkingDir: "{app}"; Flags: waituntilterminated
+Filename: {app}\installService.bat; Components: artifacts; Flags: nowait
+Filename: "{userappdata}\{cm:AppName}\bin\WelsonJS.Launcher.exe"; Components: artifacts; Flags: nowait
</code_context>
<issue_to_address>
**🚨 issue (security):** Embedding a PostHog API key in the installer increases the risk of key leakage or misuse.
Passing `PostHogApiKey` into `postInstall.ps1` via installer metadata makes it trivially extractable, so anything shipped here should be assumed public. If this key has elevated or non-ingestion permissions, please switch to a public/ingestion-only key plus server-side config, or an anonymous endpoint if supported. At minimum, confirm that this key is restricted and cannot be used for sensitive operations.
</issue_to_address>
### Comment 4
<location> `postInstall.ps1:87-93` </location>
<code_context>
+
+ # Build single event payload for PostHog /i/v0/e endpoint
+ # Anonymous event is default: $process_person_profile = false
+ $body = @{
+ api_key = $TelemetryApiKey
+ event = "app_installed"
+ distinct_id = $finalDistinctId
+ properties = @{
+ product = "welsonjs"
+ version = $Version
+ os = "windows"
+ source = "post-install.ps1"
+ components = $Components # Keep raw string here
+ }
+ timestamp = (Get-Date).ToString("o") # ISO 8601 format
+ } | ConvertTo-Json -Depth 5
</code_context>
<issue_to_address>
**🚨 suggestion (security):** Using the machine name as the telemetry distinct_id has privacy implications and may be more identifying than necessary.
When `DistinctId` is missing, this falls back to `$env:COMPUTERNAME` as PostHog’s `distinct_id`, which is often user- or org-identifying. To keep telemetry effectively anonymous, consider generating a random UUID on first run and persisting it locally, or deriving an ID from a salted hash instead of sending the raw machine name.
```suggestion
# Determine telemetry distinct id
# 1. Prefer explicit DistinctId if provided.
# 2. Otherwise, use a locally persisted anonymous id (do not send raw machine name).
$anonymousTelemetryId = $null
try {
$telemetryIdDirectory = Join-Path -Path $env:LOCALAPPDATA -ChildPath "WelsonJS"
$telemetryIdFile = Join-Path -Path $telemetryIdDirectory -ChildPath "telemetry-id.txt"
if (Test-Path -LiteralPath $telemetryIdFile) {
$anonymousTelemetryId = Get-Content -LiteralPath $telemetryIdFile -ErrorAction SilentlyContinue | Select-Object -First 1
}
if (-not $anonymousTelemetryId -or $anonymousTelemetryId.Trim() -eq "") {
$anonymousTelemetryId = [guid]::NewGuid().ToString()
if (-not (Test-Path -LiteralPath $telemetryIdDirectory)) {
New-Item -ItemType Directory -Path $telemetryIdDirectory -Force | Out-Null
}
$anonymousTelemetryId | Out-File -FilePath $telemetryIdFile -Encoding ASCII -Force
}
}
catch {
# If anything goes wrong generating/persisting the anonymous id,
# fall back to a one-off GUID for this run (still avoids machine name).
if (-not $anonymousTelemetryId -or $anonymousTelemetryId.Trim() -eq "") {
$anonymousTelemetryId = [guid]::NewGuid().ToString()
}
}
$finalDistinctId = if ($DistinctId -and $DistinctId.Trim() -ne "") {
$DistinctId
} else {
$anonymousTelemetryId
}
if ($finalDistinctId -and $finalDistinctId.Trim() -ne "") {
```
</issue_to_address>
### Comment 5
<location> `setup.iss:114` </location>
<code_context>
phc_pmRHJ0aVEhtULRT4ilexwCjYpGtE9VYRhlA05fwiYt8
</code_context>
<issue_to_address>
**security (generic-api-key):** Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
*Source: gitleaks*
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
|
|
||
| // set the binary path | ||
| this.binPath = "bin\\platform-tools_r33.0.0-windows\\platform-tools\\adb.exe"; | ||
| this.binPath = SYS.getAppDataDir() + "\\android_platform_tools\\platform-tools\\adb.exe"; |
There was a problem hiding this comment.
issue (bug_risk): New adb binary path assumes the Android Platform Tools component was installed, which may break adb usage if that component is not selected.
The old path targeted a bundled adb.exe, but the new path relies on %APPDATA%\android_platform_tools, which only exists if the android_platform_tools component is installed and its post-install script succeeds. Without that, adb.js will reference a missing binary. Please add a runtime check with a clear error if the file is absent, or fall back to the bundled path when the AppData location is unavailable.
| Filename: "powershell.exe"; Parameters: "-ExecutionPolicy Bypass -NoProfile -File ""{app}\afterInstall.ps1"""; WorkingDir: "{app}"; Components: addtools; Flags: waituntilterminated | ||
| Filename: {app}\installService.bat; Components: addtools; Flags: nowait | ||
| Filename: "{userappdata}\{cm:AppName}\bin\WelsonJS.Launcher.exe"; Components: addtools; Flags: nowait | ||
| Filename: "powershell.exe"; Parameters: "-ExecutionPolicy Bypass -NoProfile -File ""{app}\postInstall.ps1"" -TelemetryProvider posthog -TelemetryApiKey ""{cm:PostHogApiKey}"" -Version ""{cm:AppVersion}"" -DistinctId ""{computername}"" -Components ""{code:GetSelectedComponents}"""; WorkingDir: "{app}"; Flags: waituntilterminated |
There was a problem hiding this comment.
🚨 issue (security): Embedding a PostHog API key in the installer increases the risk of key leakage or misuse.
Passing PostHogApiKey into postInstall.ps1 via installer metadata makes it trivially extractable, so anything shipped here should be assumed public. If this key has elevated or non-ingestion permissions, please switch to a public/ingestion-only key plus server-side config, or an anonymous endpoint if supported. At minimum, confirm that this key is restricted and cannot be used for sensitive operations.
| $finalDistinctId = if ($DistinctId -and $DistinctId.Trim() -ne "") { | ||
| $DistinctId | ||
| } else { | ||
| $env:COMPUTERNAME | ||
| } | ||
|
|
||
| if ($finalDistinctId -and $finalDistinctId.Trim() -ne "") { |
There was a problem hiding this comment.
🚨 suggestion (security): Using the machine name as the telemetry distinct_id has privacy implications and may be more identifying than necessary.
When DistinctId is missing, this falls back to $env:COMPUTERNAME as PostHog’s distinct_id, which is often user- or org-identifying. To keep telemetry effectively anonymous, consider generating a random UUID on first run and persisting it locally, or deriving an ID from a salted hash instead of sending the raw machine name.
| $finalDistinctId = if ($DistinctId -and $DistinctId.Trim() -ne "") { | |
| $DistinctId | |
| } else { | |
| $env:COMPUTERNAME | |
| } | |
| if ($finalDistinctId -and $finalDistinctId.Trim() -ne "") { | |
| # Determine telemetry distinct id | |
| # 1. Prefer explicit DistinctId if provided. | |
| # 2. Otherwise, use a locally persisted anonymous id (do not send raw machine name). | |
| $anonymousTelemetryId = $null | |
| try { | |
| $telemetryIdDirectory = Join-Path -Path $env:LOCALAPPDATA -ChildPath "WelsonJS" | |
| $telemetryIdFile = Join-Path -Path $telemetryIdDirectory -ChildPath "telemetry-id.txt" | |
| if (Test-Path -LiteralPath $telemetryIdFile) { | |
| $anonymousTelemetryId = Get-Content -LiteralPath $telemetryIdFile -ErrorAction SilentlyContinue | Select-Object -First 1 | |
| } | |
| if (-not $anonymousTelemetryId -or $anonymousTelemetryId.Trim() -eq "") { | |
| $anonymousTelemetryId = [guid]::NewGuid().ToString() | |
| if (-not (Test-Path -LiteralPath $telemetryIdDirectory)) { | |
| New-Item -ItemType Directory -Path $telemetryIdDirectory -Force | Out-Null | |
| } | |
| $anonymousTelemetryId | Out-File -FilePath $telemetryIdFile -Encoding ASCII -Force | |
| } | |
| } | |
| catch { | |
| # If anything goes wrong generating/persisting the anonymous id, | |
| # fall back to a one-off GUID for this run (still avoids machine name). | |
| if (-not $anonymousTelemetryId -or $anonymousTelemetryId.Trim() -eq "") { | |
| $anonymousTelemetryId = [guid]::NewGuid().ToString() | |
| } | |
| } | |
| $finalDistinctId = if ($DistinctId -and $DistinctId.Trim() -ne "") { | |
| $DistinctId | |
| } else { | |
| $anonymousTelemetryId | |
| } | |
| if ($finalDistinctId -and $finalDistinctId.Trim() -ne "") { |
| function GetSelectedComponents(Value: string): string; | ||
| begin | ||
| Result := WizardSelectedComponents(False); | ||
| end; |
There was a problem hiding this comment.
security (generic-api-key): Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
Source: gitleaks
PR Code Suggestions ✨Explore these optional code suggestions:
|
|||||||||||||||||||
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
data/DownloadUrls.psd1 (1)
14-18: Third-party source for curl x86 build.The x86 curl URL points to a SourceForge project (
muldersoft/cURL) rather than the official curl.se distribution. Consider the supply chain security implications of using third-party builds, or document why the official source doesn't provide x86 builds.postInstall.ps1 (1)
183-210: ARM architecture detection may need refinement.Architecture value
5represents 32-bit ARM, not necessarily ARM64. On Windows ARM64, the processor may report differently. Consider using$env:PROCESSOR_ARCHITECTUREas an additional check, which returnsARM64on 64-bit ARM systems.switch ($proc.Architecture) { 0 { $arch = "x86" } # 32-bit Intel/AMD - 5 { $arch = "arm64" } # treat ARM as arm64 target + 5 { # ARM variant + if ([System.Environment]::Is64BitOperatingSystem) { + $arch = "arm64" + } else { + $arch = "x86" # fallback for 32-bit ARM + } + } 9 { $arch = "x64" } # 64-bit Intel/AMD
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (9)
afterInstall.ps1(0 hunks)app/assets/tessdata(0 hunks)app/assets/tessdata_best(0 hunks)app/assets/tessdata_fast(0 hunks)data/DownloadUrls.psd1(1 hunks)data/binaries_meta.json(0 hunks)lib/adb.js(3 hunks)postInstall.ps1(1 hunks)setup.iss(4 hunks)
💤 Files with no reviewable changes (5)
- app/assets/tessdata
- afterInstall.ps1
- app/assets/tessdata_fast
- app/assets/tessdata_best
- data/binaries_meta.json
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Analyze (javascript)
- GitHub Check: Sourcery review
🔇 Additional comments (9)
data/DownloadUrls.psd1 (2)
1-121: Well-structured centralized configuration.The URL configuration file is well-organized with clear section comments and proper handling of architecture-specific builds with
$nullfor unsupported platforms. The "any" fallback pattern for architecture-independent packages (tessdata) is a good design choice.
5-9: No action needed for Python version selection. Python 3.14.0 is a stable release published October 7, 2025, and is suitable for production use. While the x86 variant uses the older 3.13.9, using different stable versions across architectures is acceptable if intentional (e.g., due to architecture-specific requirements or testing considerations). If version consistency is desired, updating x86 to 3.14.0 would be appropriate, but this is an optional optimization rather than a stability concern.setup.iss (2)
104-105: Telemetry API key exposure is acceptable.The PostHog API key (
phc_...) is a public project API key designed for client-side use. This is the expected pattern for anonymous analytics telemetry.
111-114: LGTM - GetSelectedComponents implementation.The function correctly uses
WizardSelectedComponents(False)to return a comma-separated list of selected components without hierarchy prefixes, which aligns with the PowerShell script's parsing logic.postInstall.ps1 (4)
86-91: COMPUTERNAME as telemetry identifier.Using
$env:COMPUTERNAMEas thedistinct_idprovides machine-level correlation. While not directly identifying individuals, some organizations consider machine names sensitive. The setup.iss passes{computername}explicitly, so this is intentional. Users who prefer more anonymity could modify the installer.
292-294: Extraction clears existing directory contents.
Extract-CompressedFilecallsEnsure-EmptyDirectorywhich removes existing content in the destination directory before extraction. This is intentional for clean installs but could cause data loss if users have customized files in these directories.
239-278: Good retry logic with exponential backoff consideration.The download retry logic (3 attempts, 5-second delay) is solid. For robustness against transient network issues, consider exponential backoff, though the current fixed delay is acceptable for an installer.
854-860: Clean completion handling.The script exits with appropriate codes (0 for success, 1 for failure) and provides clear completion messaging.
lib/adb.js (1)
94-94: ADB binary path is correctly aligned with the archive structure.The Google-hosted platform-tools archive extracts with
platform-tools/at the root level, and adb.exe is located atplatform-tools/adb.exe. The hardcoded pathSYS.getAppDataDir() + "\\android_platform_tools\\platform-tools\\adb.exe"correctly accounts for both the extraction directory (android_platform_tools) and the archive's internal structure. No path adjustment is needed.
Fix mistypos in the post-install script, Fix decompression process when the file contains single root
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (3)
setup.iss (2)
43-43: Typo in component description."Download and Nmap and Npcap" should be "Download Nmap and Npcap".
104-105: API key embedded in installer metadata.This key is exposed in the compiled installer and should be treated as public. Confirm this is an ingestion-only key with minimal permissions.
postInstall.ps1 (1)
87-91: Privacy consideration: machine name as telemetry identifier.Using
$env:COMPUTERNAMEas the fallbackdistinct_idmay be more identifying than intended for anonymous telemetry.
🧹 Nitpick comments (3)
lib/adb.js (1)
94-95: Add validation for the adb binary path.The path now assumes
android_platform_toolswas installed to AppData. If this component wasn't selected during installation, calls togetDevices(),pull(),push(), etc. will fail. WhilesendShellsilently catches errors, other methods don't handle missing binaries gracefully.Consider adding a validation helper or fallback:
+ this.validateBinPath = function() { + var FSO = CreateObject("Scripting.FileSystemObject"); + if (!FSO.FileExists(this.binPath)) { + throw new Error("ADB binary not found at: " + this.binPath + + ". Ensure 'Android Platform Tools' component was installed."); + } + return this; + }; + // set the binary path this.binPath = SYS.getAppDataDir() + "\\android_platform_tools\\adb.exe"; this._interface.setPrefix(this.binPath);Alternatively, consumers can use
setBinPath()to override with a custom location when the default isn't available.postInstall.ps1 (2)
700-711: Consider silent installation flags for GTK runtime.The installer runs without arguments, which may display a GUI requiring user interaction. For unattended installations, consider adding silent flags if supported.
if (Test-Path $GtkRuntimeInstaller) { Write-Host "[*] Running GTK runtime installer (wait): $GtkRuntimeInstaller" - Start-Process -FilePath $GtkRuntimeInstaller -Wait -ErrorAction Stop + Start-Process -FilePath $GtkRuntimeInstaller -ArgumentList "/S" -Wait -ErrorAction Stop }Verify the correct silent flag for the GTK runtime installer (commonly
/Sor/silent).
776-792: Npcap/Nmap installers may require user interaction.These installers are executed without silent flags. Npcap in particular often prompts for driver installation approval. For fully automated installs, consider:
if (Test-Path $NpcapInstaller) { Write-Host "[*] Running Npcap installer (wait): $NpcapInstaller" - Start-Process -FilePath $NpcapInstaller -Wait -ErrorAction Stop + # /S for silent, /winpcap_mode for compatibility + Start-Process -FilePath $NpcapInstaller -ArgumentList "/S" -Wait -ErrorAction Stop }Note: Silent Npcap installation may require additional flags. Verify with Npcap documentation.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
lib/adb.js(3 hunks)postInstall.ps1(1 hunks)setup.iss(4 hunks)
🧰 Additional context used
🪛 Gitleaks (8.29.1)
setup.iss
[high] 105-105: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Analyze (javascript)
- GitHub Check: Sourcery review
🔇 Additional comments (9)
lib/adb.js (1)
136-136: LGTM on version bump.Version increment appropriately reflects the path resolution change.
setup.iss (2)
111-114: LGTM on component selection integration.
GetSelectedComponentscorrectly returns the comma-separated component list for the PowerShell script.
29-45: Well-structured component system.The modular component definitions with appropriate type assignments (full, compact, custom) provide good flexibility for users.
postInstall.ps1 (6)
843-856: LGTM - Android Platform Tools extraction uses correct variable.The
Test-Pathcheck correctly references$AndroidPlatformToolsCompressed.
257-278: Good retry logic implementation.The 3-attempt retry with delay is reasonable. For robustness, consider exponential backoff for future improvements, but current implementation is acceptable.
292-298: Note: Extraction clears destination directory.
Ensure-EmptyDirectoryon line 293 will remove any pre-existing content in the destination. This is likely intentional for clean installations but could cause data loss if users have customized files in these directories.Confirm this is the intended behavior for component updates/reinstalls.
595-603: Good error handling with proper exit codes.The structured try/catch with
exit 1on failure ensures the installer properly reports failures to the setup process.
148-170: Clean component selection implementation.The
Test-ComponentSelectedhelper with$AllComponentsSelectedfallback provides good backward compatibility when no components are specified.
30-45: Robust data file loading with fallback.Graceful handling of missing or malformed
DownloadUrls.psd1ensures the script doesn't crash, with appropriate warnings.
| switch ($proc.Architecture) { | ||
| 0 { $arch = "x86" } # 32-bit Intel/AMD | ||
| 5 { $arch = "arm64" } # treat ARM as arm64 target | ||
| 9 { $arch = "x64" } # 64-bit Intel/AMD | ||
| default { $arch = "x86" } # fallback | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
Win32_Processor Architecture property values Microsoft documentation
💡 Result:
Win32_Processor Architecture (uint16) values (per Microsoft Docs):
- 0 = x86. [1]
- 1 = MIPS. [1]
- 2 = Alpha. [1]
- 3 = PowerPC. [1]
- 5 = ARM. [1]
- 6 = ia64 (Itanium). [1]
- 9 = x64. [1]
- 12 = ARM64. [1]
Source: Microsoft Learn — Win32_Processor class (Architecture property). [1]
Correct ARM architecture mapping.
Per Microsoft documentation, Win32_Processor.Architecture value 5 is ARM (32-bit), while 12 is ARM64 (64-bit). The current mapping of 5 → arm64 is incorrect and will cause 64-bit ARM64 binaries to be downloaded on 32-bit ARM systems. Additionally, Architecture value 12 (ARM64) is not handled and will fall back to x86.
🤖 Prompt for AI Agents
In postInstall.ps1 around lines 192 to 197, the Win32_Processor.Architecture
mapping is wrong: value 5 is ARM (32-bit) and value 12 is ARM64 (64-bit), but
the current switch maps 5 → arm64 and omits 12; update the switch to map 5 to
"arm", 12 to "arm64", keep 9 → "x64" and 0 → "x86", and retain a sensible
default (e.g., "x86") so 32-bit ARM systems receive arm binaries and ARM64
systems receive arm64 binaries.
Add logo to postInstall.ps1
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (2)
postInstall.ps1 (2)
104-108: Privacy issue: Machine name used as telemetry identifier.This fallback to
$env:COMPUTERNAMEexposes the machine name in telemetry, which is user-identifying and not anonymous. A previous review suggested generating a persistent random UUID instead.Based on past review feedback, consider generating and persisting an anonymous UUID rather than sending the raw machine name.
209-214: Critical: Incorrect ARM architecture mapping.Win32_Processor.Architecture value
5is ARM (32-bit), not ARM64. Value12is ARM64 (64-bit) but is not handled and will fall back to x86. This causes incorrect binary downloads on ARM systems.Per Microsoft documentation, update the switch to:
switch ($proc.Architecture) { 0 { $arch = "x86" } # 32-bit Intel/AMD - 5 { $arch = "arm64" } # treat ARM as arm64 target + 5 { $arch = "arm" } # 32-bit ARM 9 { $arch = "x64" } # 64-bit Intel/AMD + 12 { $arch = "arm64" } # 64-bit ARM default { $arch = "x86" } # fallback }
🧹 Nitpick comments (5)
postInstall.ps1 (5)
281-281: Consider adding timeout to prevent indefinite hangs.
Invoke-WebRequestwithout a timeout can hang indefinitely on unresponsive endpoints. Adding-TimeoutSecwould improve resilience.- Invoke-WebRequest -Uri $Url -OutFile $DestinationPath -UseBasicParsing + Invoke-WebRequest -Uri $Url -OutFile $DestinationPath -UseBasicParsing -TimeoutSec 300
406-488: Optional: Reduce code duplication in download phase.The download logic is highly repetitive across components. Consider extracting into a helper function to improve maintainability.
Example refactoring:
function Download-Component { param( [string]$ComponentName, [string]$DestinationPath ) if (Test-ComponentSelected -Name $ComponentName) { $url = Get-DownloadUrl -Component $ComponentName -Arch $arch if ($url) { Download-File -Url $url -DestinationPath $DestinationPath } else { Write-Host "[*] $ComponentName URL not available for arch: $arch. Skipping download." } } else { Write-Host "[*] $ComponentName component not selected. Skipping download." } } # Then use: Download-Component -ComponentName "python" -DestinationPath $PythonCompressed
835-835: Consider logging VC_redist installer errors.Using
-ErrorAction SilentlyContinuesuppresses installer errors. If the VC redistributable is required, installation failures will go unnoticed.- Start-Process -FilePath $vcRedist.FullName -Wait -ErrorAction SilentlyContinue + try { + Start-Process -FilePath $vcRedist.FullName -Wait -ErrorAction Stop + } + catch { + Write-Host "[WARN] VC_redist.x86 installer failed: $($_.Exception.Message)" + }
716-720: Consider checking for administrator privileges.Installers like GTK runtime, Npcap, and Nmap typically require administrator privileges. The script doesn't check for or request elevation, which may cause silent failures.
Add an admin check at the script start:
function Test-Administrator { $currentUser = [Security.Principal.WindowsIdentity]::GetCurrent() $principal = New-Object Security.Principal.WindowsPrincipal($currentUser) return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) } if (-not (Test-Administrator)) { Write-Host "[WARN] Some components may require administrator privileges to install." Write-Host "[WARN] Consider running this script as administrator." }Also applies to: 794-796, 803-805
256-295: Recommend adding checksum validation for downloaded files.The script downloads and executes files without verifying their integrity. If URLs in DownloadUrls.psd1 are compromised or DNS is poisoned, malicious files could be installed.
Consider adding SHA256 checksums to DownloadUrls.psd1 and validating after download:
function Verify-FileChecksum { param( [string]$FilePath, [string]$ExpectedHash ) if (-not $ExpectedHash) { Write-Host "[WARN] No checksum provided for verification." return $true } $actualHash = (Get-FileHash -Path $FilePath -Algorithm SHA256).Hash if ($actualHash -eq $ExpectedHash) { Write-Host "[*] Checksum verified: $FilePath" return $true } else { Write-Host "[ERROR] Checksum mismatch for $FilePath" Write-Host " Expected: $ExpectedHash" Write-Host " Actual : $actualHash" return $false } }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
postInstall.ps1(1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Sourcery review
- GitHub Check: Analyze (javascript)
Fix the architecture number
|
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (2)
postInstall.ps1 (2)
104-108: Privacy concern: Machine name used as telemetry ID (duplicate issue).This issue was previously flagged but remains unaddressed. Using
$env:COMPUTERNAMEas the fallback fordistinct_idcan expose identifying information. The previous suggestion to generate and persist a random UUID instead of sending the raw machine name is still valid and should be implemented for better privacy.
209-214: Critical: Missing ARM 32-bit architecture support (duplicate issue).This issue was previously flagged but remains unaddressed. The switch statement is missing case 5 for ARM (32-bit). Per Microsoft documentation, Win32_Processor.Architecture value 5 represents ARM (32-bit), while 12 represents ARM64 (64-bit). The current code only handles 12, which means 32-bit ARM systems will incorrectly fall back to x86 binaries.
The previous review suggested adding case 5:
switch ($proc.Architecture) { 0 { $arch = "x86" } # 32-bit Intel/AMD + 5 { $arch = "arm" } # 32-bit ARM 12 { $arch = "arm64" } # treat ARM as arm64 target 9 { $arch = "x64" } # 64-bit Intel/AMD default { $arch = "x86" } # fallback }
🧹 Nitpick comments (1)
postInstall.ps1 (1)
835-835: Consider consistent error handling for installer execution.The VC_redist installer uses
-ErrorAction SilentlyContinue(line 835) while other installers (GTK runtime at line 720, Npcap at line 796, Nmap at line 805) use-ErrorAction Stop. If this is intentional because VC_redist is optional, consider adding a comment to clarify. Otherwise, using consistent error handling would improve maintainability.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
postInstall.ps1(1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Sourcery review
- GitHub Check: Analyze (javascript)
| # 0 = x86, 5 = ARM, 9 = x64 | ||
| # https://learn.microsoft.com/windows/win32/cimwin32prov/win32-processor |
There was a problem hiding this comment.
Misleading comment about architecture values.
The comment is incomplete and doesn't mention value 12 (ARM64), which is actually handled in the code below. Per Microsoft documentation, the complete mapping should include: 0=x86, 5=ARM (32-bit), 9=x64, 12=ARM64.
Update the comment:
- # 0 = x86, 5 = ARM, 9 = x64
- # https://learn.microsoft.com/windows/win32/cimwin32prov/win32-processor
+ # 0 = x86, 5 = ARM (32-bit), 9 = x64, 12 = ARM64 (64-bit)
+ # https://learn.microsoft.com/windows/win32/cimwin32prov/win32-processor🤖 Prompt for AI Agents
In postInstall.ps1 around lines 201 to 202, the architecture comment is
incomplete and omits the value 12 (ARM64); update the comment to list the full
mapping per Microsoft docs: 0 = x86, 5 = ARM (32-bit), 9 = x64, 12 = ARM64, and
keep the reference URL as-is.
| try { | ||
| # Python (component: python) | ||
| if (Test-ComponentSelected -Name "python") { | ||
| $url = Get-DownloadUrl -Component "python" -Arch $arch | ||
| if ($url) { | ||
| Download-File -Url $url -DestinationPath $PythonCompressed | ||
| } | ||
| else { | ||
| Write-Host "[*] Python URL not available for arch: $arch. Skipping download." | ||
| } | ||
| } | ||
| else { | ||
| Write-Host "[*] Python component not selected. Skipping download." | ||
| } | ||
|
|
||
| # curl (component: curl) | ||
| if (Test-ComponentSelected -Name "curl") { | ||
| $url = Get-DownloadUrl -Component "curl" -Arch $arch | ||
| if ($url) { | ||
| Download-File -Url $url -DestinationPath $CurlCompressed | ||
| } | ||
| else { | ||
| Write-Host "[*] curl URL not available for arch: $arch. Skipping download." | ||
| } | ||
| } | ||
| else { | ||
| Write-Host "[*] curl component not selected. Skipping download." | ||
| } | ||
|
|
||
| # YARA (component: yara) | ||
| if (Test-ComponentSelected -Name "yara") { | ||
| $url = Get-DownloadUrl -Component "yara" -Arch $arch | ||
| if ($url) { | ||
| Download-File -Url $url -DestinationPath $YaraCompressed | ||
| } | ||
| else { | ||
| Write-Host "[*] YARA URL not available for arch: $arch. Skipping download." | ||
| } | ||
| } | ||
| else { | ||
| Write-Host "[*] YARA component not selected. Skipping download." | ||
| } | ||
|
|
||
| # WAMR (component: wamr) | ||
| if (Test-ComponentSelected -Name "wamr") { | ||
| $url = Get-DownloadUrl -Component "wamr" -Arch $arch | ||
| if ($url) { | ||
| Download-File -Url $url -DestinationPath $WamrArchive | ||
| } | ||
| else { | ||
| Write-Host "[*] WAMR URL not available for arch: $arch. Skipping download." | ||
| } | ||
| } | ||
| else { | ||
| Write-Host "[*] WAMR component not selected. Skipping download." | ||
| } | ||
|
|
||
| # websocat (component: websocat) | ||
| if (Test-ComponentSelected -Name "websocat") { | ||
| $url = Get-DownloadUrl -Component "websocat" -Arch $arch | ||
| if ($url) { | ||
| Download-File -Url $url -DestinationPath $WebsocatCompressed | ||
| } | ||
| else { | ||
| Write-Host "[*] websocat URL not available for arch: $arch. Skipping download." | ||
| } | ||
| } | ||
| else { | ||
| Write-Host "[*] websocat component not selected. Skipping download." | ||
| } | ||
|
|
||
| # artifacts (component: artifacts) | ||
| if (Test-ComponentSelected -Name "artifacts") { | ||
| $url = Get-DownloadUrl -Component "artifacts" -Arch $arch | ||
| if ($url) { | ||
| Download-File -Url $url -DestinationPath $ArtifactsCompressed | ||
| } | ||
| else { | ||
| Write-Host "[*] artifacts URL not available for arch: $arch. Skipping download." | ||
| } | ||
| } | ||
| else { | ||
| Write-Host "[*] artifacts component not selected. Skipping download." | ||
| } | ||
|
|
||
| # GTK3 runtime (component: gtk3runtime) | ||
| if (Test-ComponentSelected -Name "gtk3runtime") { | ||
| $url = Get-DownloadUrl -Component "gtk3runtime" -Arch $arch | ||
| if ($url) { | ||
| Download-File -Url $url -DestinationPath $GtkRuntimeInstaller | ||
| } | ||
| else { | ||
| Write-Host "[*] gtk3runtime URL not available for arch: $arch. Skipping download." | ||
| } | ||
| } | ||
| else { | ||
| Write-Host "[*] gtk3runtime component not selected. Skipping download." | ||
| } | ||
|
|
||
| # GTK server (component: gtkserver) | ||
| if (Test-ComponentSelected -Name "gtkserver") { | ||
| $url = Get-DownloadUrl -Component "gtkserver" -Arch $arch | ||
| if ($url) { | ||
| Download-File -Url $url -DestinationPath $GtkServerCompressed | ||
| } | ||
| else { | ||
| Write-Host "[*] gtkserver URL not available for arch: $arch. Skipping download." | ||
| } | ||
| } | ||
| else { | ||
| Write-Host "[*] gtkserver component not selected. Skipping download." | ||
| } | ||
|
|
||
| # tessdata (component: tessdata) | ||
| if (Test-ComponentSelected -Name "tessdata") { | ||
| $url = Get-DownloadUrl -Component "tessdata" -Arch $arch | ||
| if ($url) { | ||
| Download-File -Url $url -DestinationPath $TessdataCompressed | ||
| } | ||
| else { | ||
| Write-Host "[*] tessdata URL not available. Skipping download." | ||
| } | ||
| } | ||
| else { | ||
| Write-Host "[*] tessdata component not selected. Skipping download." | ||
| } | ||
|
|
||
| # tessdata_best (component: tessdata_best) | ||
| if (Test-ComponentSelected -Name "tessdata_best") { | ||
| $url = Get-DownloadUrl -Component "tessdata_best" -Arch $arch | ||
| if ($url) { | ||
| Download-File -Url $url -DestinationPath $TessdataBestCompressed | ||
| } | ||
| else { | ||
| Write-Host "[*] tessdata_best URL not available. Skipping download." | ||
| } | ||
| } | ||
| else { | ||
| Write-Host "[*] tessdata_best component not selected. Skipping download." | ||
| } | ||
|
|
||
| # tessdata_fast (component: tessdata_fast) | ||
| if (Test-ComponentSelected -Name "tessdata_fast") { | ||
| $url = Get-DownloadUrl -Component "tessdata_fast" -Arch $arch | ||
| if ($url) { | ||
| Download-File -Url $url -DestinationPath $TessdataFastCompressed | ||
| } | ||
| else { | ||
| Write-Host "[*] tessdata_fast URL not available. Skipping download." | ||
| } | ||
| } | ||
| else { | ||
| Write-Host "[*] tessdata_fast component not selected. Skipping download." | ||
| } | ||
|
|
||
| # Nmap bundle (component: nmap) – includes Npcap + Nmap installer | ||
| if (Test-ComponentSelected -Name "nmap") { | ||
| # Npcap | ||
| $url = Get-DownloadUrl -Component "npcap" -Arch $arch | ||
| if ($url) { | ||
| Download-File -Url $url -DestinationPath $NpcapInstaller | ||
| } | ||
| else { | ||
| Write-Host "[*] npcap URL not available. Skipping npcap download." | ||
| } | ||
|
|
||
| # Nmap | ||
| $url = Get-DownloadUrl -Component "nmap" -Arch $arch | ||
| if ($url) { | ||
| Download-File -Url $url -DestinationPath $NmapInstaller | ||
| } | ||
| else { | ||
| Write-Host "[*] nmap URL not available. Skipping nmap download." | ||
| } | ||
| } | ||
| else { | ||
| Write-Host "[*] nmap component not selected. Skipping Npcap/Nmap download." | ||
| } | ||
|
|
||
| # windivert (component: windivert) | ||
| if (Test-ComponentSelected -Name "windivert") { | ||
| $url = Get-DownloadUrl -Component "windivert" -Arch $arch | ||
| if ($url) { | ||
| Download-File -Url $url -DestinationPath $WinDivertCompressed | ||
| } | ||
| else { | ||
| Write-Host "[*] WinDivert URL not available. Skipping download." | ||
| } | ||
| } | ||
| else { | ||
| Write-Host "[*] WinDivert component not selected. Skipping download." | ||
| } | ||
|
|
||
| # Android Platform Tools (component: android_platform_tools) | ||
| if (Test-ComponentSelected -Name "android_platform_tools") { | ||
| $url = Get-DownloadUrl -Component "android_platform_tools" -Arch $arch | ||
| if ($url) { | ||
| Download-File -Url $url -DestinationPath $AndroidPlatformToolsCompressed | ||
| } | ||
| else { | ||
| Write-Host "[*] Android Platform Tools URL not available. Skipping download." | ||
| } | ||
| } | ||
| else { | ||
| Write-Host "[*] Android Platform Tools component not selected. Skipping download." | ||
| } | ||
| } | ||
| catch { | ||
| Write-Host "[FATAL] Download phase failed." | ||
| if ($_ -is [System.Exception]) { | ||
| Write-Host $_.Exception.Message | ||
| } else { | ||
| Write-Host $_ | ||
| } | ||
| exit 1 | ||
| } |
There was a problem hiding this comment.
Consider adding checksum validation for downloaded files.
Downloaded files are not validated for integrity using checksums or signatures. This creates a security risk if the download source is compromised or subject to man-in-the-middle attacks. While this may be acceptable for an initial implementation, consider storing expected checksums in DownloadUrls.psd1 and validating downloads before extraction.
Example implementation approach:
function Verify-FileChecksum {
param(
[string]$FilePath,
[string]$ExpectedHash,
[string]$Algorithm = "SHA256"
)
$actualHash = (Get-FileHash -Path $FilePath -Algorithm $Algorithm).Hash
if ($actualHash -ne $ExpectedHash) {
throw "Checksum mismatch for $FilePath"
}
}Then update DownloadUrls.psd1 to include hashes alongside URLs and validate after each download.
🤖 Prompt for AI Agents
In postInstall.ps1 around lines 405 to 620, downloaded artifacts are not
validated; add checksum validation by (1) extending DownloadUrls.psd1 to include
an expected hash (and algorithm, default SHA256) alongside each URL, (2)
implementing a Verify-FileChecksum function that computes Get-FileHash for the
downloaded file and compares it to the expected hash (throwing on mismatch), (3)
calling Verify-FileChecksum immediately after each successful Download-File call
(log a clear error and exit with non-zero on mismatch), and (4) ensure the catch
block reports checksum failures clearly; keep behavior configurable so missing
hashes skip verification with a warning.
User description
Improve the post-install script
PR Type
Enhancement, Other
Description
Refactored installer with modular component selection system
Created centralized DownloadUrls.psd1 manifest for all component URLs
Replaced afterInstall.ps1 with new postInstall.ps1 supporting telemetry
Added WinDivert and Android Platform Tools as selectable components
Updated adb.js to use dynamic android_platform_tools path
Removed tessdata submodules and cleaned up installer exclusions
Diagram Walkthrough
File Walkthrough
2 files
Update ADB binary path and versionImplement modular post-install with telemetry5 files
Remove old post-install scriptRemove tessdata git submoduleRemove tessdata_best git submoduleRemove tessdata_fast git submoduleRemove obsolete binaries metadata file2 files
Create centralized component URL manifestRefactor installer components and integrate postInstallSummary by CodeRabbit
New Features
Bug Fixes
Chores
✏️ Tip: You can customize this high-level summary in your review settings.