Skip to content

feat(updater): aggregate Scoop & npm alongside winget/Chocolatey#227

Open
dbfx wants to merge 7 commits into
mainfrom
feat/package-manager-aggregation
Open

feat(updater): aggregate Scoop & npm alongside winget/Chocolatey#227
dbfx wants to merge 7 commits into
mainfrom
feat/package-manager-aggregation

Conversation

@dbfx

@dbfx dbfx commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

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 source and is routed back to its owning manager on update.

What's new

  • Scoop + npm (global) support — full adapters: availability probe, outdated/installed parsers, and upgrade pipelines. Both run through PowerShell since they're .cmd shims, not native .exe.
  • AggregationcheckForUpdatesWindows fans out over the user's enabled managers (new windowsPackageManagers setting) and merges results; per-manager availability + counts reported via UpdateCheckResult.managers.
  • Per-package update routing — the run IPC now takes { id, source } items and groups them by manager. CLI (kudu --cli updates run) and cloud-agent remote-update paths updated to resolve sources too.
  • UI — the single manager dropdown becomes enable/disable toggle chips (winget / Chocolatey / Scoop / npm), with an "enabled but not installed" hint.
  • Collision safety — the renderer store keys packages by a composite source␟id identity so a package present under two managers (e.g. choco + scoop 7zip) doesn't collide in selection/removal.

Tests

  • Added parser tests for parseScoopStatus, parseScoopExport, parseNpmOutdated, parseNpmListGlobal, plus windowsPackageManagers validation cases and updated IPC contract tests.
  • Full suite green: 2149 passed. Touched files typecheck clean.

Notes / follow-ups

  • npm is scoped to the Windows aggregation for now; mac/linux remain single-manager. npm is cross-platform, so extending aggregation there is a natural next step.
  • Scoop/npm parsers are tested against documented output formats, but the live commands weren't exercised in CI (Windows-only, tools not installed) — worth a real run before release.
  • Remaining UniGetUI managers (cargo, pipx, dotnet tool, PowerShell Gallery) follow the same adapter shape and can land incrementally.

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.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 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".

Comment on lines +989 to +991
const configured = getSettings().windowsPackageManagers
if (!configured || configured.length === 0) return WINDOWS_MANAGERS
return WINDOWS_MANAGERS.filter((m) => configured.includes(m))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Comment thread src/main/cli.ts Outdated
Comment on lines +991 to +992
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' }))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Comment thread src/main/services/cloud-agent.ts Outdated
// 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' }))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 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".

Comment on lines +205 to +206
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)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Comment thread src/main/services/cloud-agent.ts Outdated
Comment on lines +2415 to +2416
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' }))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

@dbfx

dbfx commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

@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.
@dbfx

dbfx commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 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".

Comment on lines 785 to 787
onToggle={() => useUpdaterStore.getState().toggleAppSelected(appKey(app))}
onUpdate={() => handleUpdate([app])}
onIgnore={() => useUpdaterStore.getState().ignoreApp(app.id)}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Comment thread src/main/services/software-updater.ts Outdated
Comment on lines +855 to +856
'powershell.exe',
['-NoProfile', '-Command', psUtf8(cmd)],

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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.
@dbfx

dbfx commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 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 })

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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".

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 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".

Comment on lines +841 to +843
} catch (err: any) {
if (err?.stdout) output = err.stdout
else return { success: false, error: err?.message || 'Unknown error' }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant