feat(updater): aggregate Scoop & npm alongside winget/Chocolatey#227
feat(updater): aggregate Scoop & npm alongside winget/Chocolatey#227dbfx wants to merge 7 commits into
Conversation
Evolve the Software Updater from a single-manager model into a
UniGetUI-style aggregator on Windows: every enabled package manager is
scanned concurrently and merged into one list, with each package routed
back to its owning manager on update via its `source`.
- Add Scoop and npm (global) adapters — availability probe, outdated /
installed parsers, and upgrade pipelines. Both run through PowerShell
since they are `.cmd` shims rather than native `.exe`.
- Rewrite the Windows check to fan out over the user's enabled managers
(new `windowsPackageManagers` setting) and merge results; report
per-manager status via `UpdateCheckResult.managers`.
- Route updates per package: the run IPC now takes `{id, source}` items
and groups them by manager (CLI + cloud-agent paths updated too).
- Replace the manager dropdown with enable/disable toggle chips.
- Use a composite `source␟id` identity in the renderer store so packages
present under two managers (e.g. choco + scoop "7zip") don't collide.
- Add parser + validation tests for the new managers.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 87585503fb
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const configured = getSettings().windowsPackageManagers | ||
| if (!configured || configured.length === 0) return WINDOWS_MANAGERS | ||
| return WINDOWS_MANAGERS.filter((m) => configured.includes(m)) |
There was a problem hiding this comment.
Honor the existing Windows manager preference
This reads only the new windowsPackageManagers array, but existing installs and the Settings page still write windowsPackageManager. Because readStore deep-merges defaults, migrated configs get the default ['winget','choco','scoop','npm'] array and the user's previous/visible winget-vs-Chocolatey choice is ignored, so checks suddenly scan every manager. Seed windowsPackageManagers from the legacy field when absent or update the Settings page to persist the new list.
Useful? React with 👍 / 👎.
| const sourceById = new Map(check.apps.map(a => [a.id, a.source])) | ||
| const items = toUpdate.map(id => ({ id, source: sourceById.get(id) ?? check.packageManagerName ?? 'winget' })) |
There was a problem hiding this comment.
Preserve manager source for CLI --all updates
When aggregation returns the same package id from two managers, --all first reduces the list to ids and then this Map keeps only one source for that id. For a result containing both choco/git and scoop/git, the CLI sends two items to the same manager and never updates the other package; build --all items directly from check.apps (id + source) instead of re-looking them up by id.
Useful? React with 👍 / 👎.
| // fall back to the primary manager for ids not found in the current scan. | ||
| const check = await checkForUpdates() | ||
| const sourceById = new Map(check.apps.map((a) => [a.id, a.source])) | ||
| const items = appIds.map((id) => ({ id, source: sourceById.get(id) ?? check.packageManagerName ?? 'winget' })) |
There was a problem hiding this comment.
Validate cloud update IDs per resolved manager
The new managers can report IDs that the legacy isValidAppId check above rejects, for example npm scoped packages like @angular/cli or Scoop package names containing +, but the source is resolved only here after that validation has already run. In those cases cloud software-update-run fails before the manager-specific validators in runUpdates can accept the package, so resolve source first and validate with the corresponding manager pattern.
Useful? React with 👍 / 👎.
…oud id validation - Seed windowsPackageManagers from a deliberate legacy 'choco' preference on first load so migrated configs keep their manager choice instead of silently scanning all managers; remove the now-orphaned Settings-page dropdown. - CLI `updates run --all` builds items directly from check.apps so aggregation duplicates (choco/git + scoop/git) each keep their own manager. - Validate cloud update ids with the owning manager's pattern (isValidAppIdForSource) after resolving source, so npm scoped names and Scoop '+' names aren't rejected by the legacy winget pattern.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0e9ddd0fbe
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const failedIds = new Set(result.errors.map((e) => e.appId)) | ||
| const succeededIds = ids.filter((id) => !failedIds.has(id)) | ||
| s.removeApps(succeededIds) | ||
| const succeededKeys = appsToUpdate.filter((a) => !failedIds.has(a.id)).map(appKey) |
There was a problem hiding this comment.
Track update failures by source as well as id
With Windows aggregation, the same package id can be present under multiple managers. This still reduces failures to appId, so if updating choco/git and scoop/git and only one fails, failedIds contains git and the successfully updated row is not removed; the history accounting below uses the same id-only set and marks the successful duplicate as failed too. Use the same composite source+id identity used by the store, or include source in UpdateResult.errors.
Useful? React with 👍 / 👎.
| const sourceById = new Map(check.apps.map((a) => [a.id, a.source])) | ||
| const items = appIds.map((id) => ({ id, source: sourceById.get(id) ?? check.packageManagerName ?? 'winget' })) |
There was a problem hiding this comment.
Preserve manager source for cloud update runs
The cloud update check now aggregates multiple managers, but software-update-run still receives only package ids and this Map collapses duplicate ids to a single source. If the check reports both choco/git and scoop/git, sending git for the other row—or sending it twice to update both—routes every item to whichever source wins the map, so one manager's package cannot be updated remotely. Include the source in the cloud response/request or key requests by a composite source+id.
Useful? React with 👍 / 👎.
|
@codex review |
Follow-up to Codex review on aggregation: - Carry `source` on UpdateResult error entries (Windows aggregation) and match failures by source+id in the updater page, so a failed choco/git no longer strips a succeeded scoop/git from the list or the history counts. - Cloud `software-update-run` now expands a requested id to every manager that reports it outdated (choco/git + scoop/git both update) instead of collapsing to a single guessed source.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0328c8762c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| onToggle={() => useUpdaterStore.getState().toggleAppSelected(appKey(app))} | ||
| onUpdate={() => handleUpdate([app])} | ||
| onIgnore={() => useUpdaterStore.getState().ignoreApp(app.id)} |
There was a problem hiding this comment.
Key ignored packages by source
When aggregation returns the same package id from two managers, for example choco/git and scoop/git, this row now uses appKey for selection/update but still ignores by bare app.id; the store also persists and filters ignoredIds by a.id, so ignoring one manager's package hides every package with that id across managers on this scan and future scans.
Useful? React with 👍 / 👎.
| 'powershell.exe', | ||
| ['-NoProfile', '-Command', psUtf8(cmd)], |
There was a problem hiding this comment.
Use npm.cmd instead of PowerShell for npm
On Windows machines with Restricted/AllSigned PowerShell execution policy, powershell.exe -Command npm ... can resolve the npm.ps1 shim and fail before npm runs, so the new npm manager appears unavailable or cannot update even though npm.cmd works; Microsoft documents that execution policy controls whether scripts run and defaults Windows clients to Restricted (https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.security/set-executionpolicy), and Node tracks this exact npm.ps1 failure under signed policies (nodejs/node#60075). Invoke npm.cmd directly or via cmd.exe /c instead of routing npm through PowerShell.
Useful? React with 👍 / 👎.
…ignore by source Address Codex review round 3: - Seed windowsPackageManagers from the legacy winget/choco preference on first load so existing installs keep their exact prior single-manager behavior; only fresh installs get the aggregate-all default. - Run the scoop/npm .cmd shims through cmd.exe instead of powershell.exe. PowerShell can resolve the .ps1 shim (npm.ps1/scoop.ps1), which fails under the default Restricted/AllSigned execution policy; cmd.exe resolves the .cmd shim (PATHEXT excludes .ps1) and works regardless of policy. - Key ignored packages by source (composite key) so ignoring choco/git no longer hides scoop/git; bare-id fallback preserves pre-existing ignore lists.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5513f8714c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| }) | ||
| } else { | ||
| failed++ | ||
| errors.push({ appId, name: appId, reason: result.error || 'Upgrade failed', source }) |
There was a problem hiding this comment.
Preserve the original source when reporting failures
When a winget-owned package comes from a non-manager source such as msstore, the grouping code normalizes it to winget; returning that normalized value in the error means the renderer's failedKeys lookup no longer matches the original app key (msstore␟id). In any mixed update where at least one other app succeeds, a failed Store/custom-source winget update is treated as successful and removed from the list/history counts, so the failure is effectively hidden. Return the original item source in the error (or include both display source and manager) so the UI can match the failed app correctly.
Useful? React with 👍 / 👎.
…ate" setting When the ExcludeWUDriversInQualityUpdate policy or the per-machine SearchOrderConfig device-installation setting disables driver updates, the WU COM search still returns driver updates and ignores those settings. Detect the setting and skip the scan, surfacing an informational banner (UI) / message (CLI) instead of offering updates the user opted out of. Also render sub-0.1 MB downloads as "< 0.1 MB" rather than "0 MB".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d04e800c7f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| } catch (err: any) { | ||
| if (err?.stdout) output = err.stdout | ||
| else return { success: false, error: err?.message || 'Unknown error' } |
There was a problem hiding this comment.
Treat nonzero Scoop updates as failures when stderr has the error
When scoop update exits nonzero after writing any progress to stdout, this branch discards stderr and continues with only stdout; because the fallback below treats ambiguous output as success, a failed Scoop update can be reported as succeeded and removed from the update list. This affects failures that emit diagnostics on stderr after producing stdout, so preserve stderr/message in output or return failure on the caught nonzero exit unless a clear success pattern is present.
Useful? React with 👍 / 👎.
Summary
Evolves the Software Updater from a single-manager model into a UniGetUI-style aggregator on Windows — the first increment of gradually growing Kudu toward the "definitive Windows tool" the feature request asked for.
Every enabled package manager is scanned concurrently and merged into one list; each package keeps its
sourceand is routed back to its owning manager on update.What's new
.cmdshims, not native.exe.checkForUpdatesWindowsfans out over the user's enabled managers (newwindowsPackageManagerssetting) and merges results; per-manager availability + counts reported viaUpdateCheckResult.managers.{ id, source }items and groups them by manager. CLI (kudu --cli updates run) and cloud-agent remote-update paths updated to resolve sources too.source␟ididentity so a package present under two managers (e.g. choco + scoop7zip) doesn't collide in selection/removal.Tests
parseScoopStatus,parseScoopExport,parseNpmOutdated,parseNpmListGlobal, pluswindowsPackageManagersvalidation cases and updated IPC contract tests.Notes / follow-ups