magus-cli 0.0.0-placeholder → 7.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +309 -0
- package/bin/magus.js +170 -0
- package/package.json +67 -3
- package/scripts/build-binaries.ts +113 -0
- package/scripts/capture-builtin.ts +412 -0
- package/scripts/check-optional-deps.ts +49 -0
- package/scripts/test-isolated.ts +49 -0
- package/scripts/verify-binary.ts +95 -0
- package/scripts/verify-community-registry.ts +272 -0
- package/src/__tests__/active-closure.test.ts +184 -0
- package/src/__tests__/alias-adopt.test.ts +365 -0
- package/src/__tests__/alias-parser.test.ts +417 -0
- package/src/__tests__/alias-shell-writer.test.ts +681 -0
- package/src/__tests__/alias-store.test.ts +163 -0
- package/src/__tests__/binary-signing.test.ts +120 -0
- package/src/__tests__/blackbox-active-closure.test.ts +119 -0
- package/src/__tests__/blackbox-convergence.test.ts +334 -0
- package/src/__tests__/blackbox-parity.test.ts +725 -0
- package/src/__tests__/blackbox-plugin-state.test.ts +545 -0
- package/src/__tests__/blackbox-tool-state.test.ts +669 -0
- package/src/__tests__/cache-provenance.test.ts +84 -0
- package/src/__tests__/capture-builtin.test.ts +92 -0
- package/src/__tests__/catalog-cache-store.test.ts +309 -0
- package/src/__tests__/catalog-notice.test.ts +155 -0
- package/src/__tests__/cli-ansi.test.ts +297 -0
- package/src/__tests__/cli-apply-seams.test.ts +288 -0
- package/src/__tests__/cli-live.test.ts +391 -0
- package/src/__tests__/cli-router.test.ts +240 -0
- package/src/__tests__/cli-tool-commands.test.ts +239 -0
- package/src/__tests__/cli-tools-adapter.test.ts +294 -0
- package/src/__tests__/cli-update-view.test.ts +524 -0
- package/src/__tests__/community-fetch.test.ts +545 -0
- package/src/__tests__/community-registry.test.ts +269 -0
- package/src/__tests__/community-staleness.test.ts +720 -0
- package/src/__tests__/component-badge.test.ts +98 -0
- package/src/__tests__/component-icons.test.ts +94 -0
- package/src/__tests__/content-drift.test.ts +353 -0
- package/src/__tests__/conventions-integration.test.ts +830 -0
- package/src/__tests__/conventions-manager.test.ts +1236 -0
- package/src/__tests__/doctor-bins.test.ts +43 -0
- package/src/__tests__/doctor.test.ts +463 -0
- package/src/__tests__/dotenv.test.ts +195 -0
- package/src/__tests__/enabled-not-installed.test.ts +94 -0
- package/src/__tests__/file-locking.test.ts +208 -0
- package/src/__tests__/fixtures/tool-probe/brew-list-versions-tmux.txt +2 -0
- package/src/__tests__/fixtures/tool-probe/brew-outdated-behind-homebrew-spec.txt +29 -0
- package/src/__tests__/fixtures/tool-probe/brew-outdated-tmux-current.txt +7 -0
- package/src/__tests__/fixtures/tool-probe/brew-outdated-unknown-formula.txt +6 -0
- package/src/__tests__/fixtures/tool-probe/go-version-m-tmux-mcp.txt +27 -0
- package/src/__tests__/fixtures/tool-probe/npm-dist-tags-missing-scoped.txt +4 -0
- package/src/__tests__/fixtures/tool-probe/npm-dist-tags-openai-codex.txt +4 -0
- package/src/__tests__/fixtures/tool-probe/pypi-cowsay.txt +3 -0
- package/src/__tests__/fixtures/tool-probe/uv-receipt-browser-use.toml +10 -0
- package/src/__tests__/fixtures/tool-probe/uv-receipt-cowsay-git.toml +8 -0
- package/src/__tests__/fixtures/tool-probe/uv-receipt-cowsay-pinned.toml +8 -0
- package/src/__tests__/fixtures/tool-probe/uv-receipt-graphifyy.toml +7 -0
- package/src/__tests__/fixtures/tool-probe/uv-receipt-mcp.toml +6 -0
- package/src/__tests__/fixtures/tool-probe/uv-receipt-serena-agent.toml +9 -0
- package/src/__tests__/fixtures/tool-probe/uv-tool-list-outdated-pinned.txt +5 -0
- package/src/__tests__/fixtures/tool-probe/uv-tool-list-outdated.txt +11 -0
- package/src/__tests__/fixtures/tool-probe/uv-tool-list.txt +22 -0
- package/src/__tests__/footer-hints.test.ts +40 -0
- package/src/__tests__/format-command.test.ts +78 -0
- package/src/__tests__/git-worktree.test.ts +108 -0
- package/src/__tests__/github-budget.test.ts +206 -0
- package/src/__tests__/gitignore-check.test.ts +71 -0
- package/src/__tests__/gitignore-detector.test.ts +176 -0
- package/src/__tests__/gitignore-fixer.test.ts +317 -0
- package/src/__tests__/gitignore-resolver.test.ts +193 -0
- package/src/__tests__/gitignore-service.test.ts +137 -0
- package/src/__tests__/go-module.test.ts +43 -0
- package/src/__tests__/hook-import-policy.test.ts +90 -0
- package/src/__tests__/hook-process.test.ts +256 -0
- package/src/__tests__/hook-registration.test.ts +224 -0
- package/src/__tests__/install-command.test.ts +81 -0
- package/src/__tests__/install-plan.test.ts +104 -0
- package/src/__tests__/launcher-signal.test.ts +308 -0
- package/src/__tests__/local-marketplace-scan.test.ts +248 -0
- package/src/__tests__/manifest.test.ts +281 -0
- package/src/__tests__/markdown-renderer.test.ts +142 -0
- package/src/__tests__/marketplace-badge.test.ts +140 -0
- package/src/__tests__/marketplace-refresh.test.ts +340 -0
- package/src/__tests__/marketplace-version.test.ts +84 -0
- package/src/__tests__/marketplaces.test.ts +128 -0
- package/src/__tests__/mate-availability.test.ts +156 -0
- package/src/__tests__/mate-catalog.test.ts +295 -0
- package/src/__tests__/mcp-registry.test.ts +158 -0
- package/src/__tests__/model-visuals.test.tsx +2466 -0
- package/src/__tests__/models-adapter.test.ts +332 -0
- package/src/__tests__/models-cli.test.ts +273 -0
- package/src/__tests__/models-core.test.ts +802 -0
- package/src/__tests__/models-manager.test.ts +500 -0
- package/src/__tests__/models-presets-marketplace.test.ts +168 -0
- package/src/__tests__/models-screen-state.test.ts +315 -0
- package/src/__tests__/open-file.test.ts +59 -0
- package/src/__tests__/owned-marketplaces.test.ts +93 -0
- package/src/__tests__/plugin-checkout.test.ts +66 -0
- package/src/__tests__/plugin-cli-argv.test.ts +109 -0
- package/src/__tests__/plugin-contents.test.ts +371 -0
- package/src/__tests__/plugin-manager-fallback.test.ts +320 -0
- package/src/__tests__/plugin-releases.test.ts +98 -0
- package/src/__tests__/plugin-requires.test.ts +187 -0
- package/src/__tests__/plugin-row-text.test.ts +345 -0
- package/src/__tests__/plugin-setup.test.ts +1016 -0
- package/src/__tests__/profile-adopt.test.ts +177 -0
- package/src/__tests__/profile-command.test.ts +110 -0
- package/src/__tests__/profile-materializer.test.ts +238 -0
- package/src/__tests__/profile-sync.test.ts +154 -0
- package/src/__tests__/registry-version-resolution.test.ts +88 -0
- package/src/__tests__/resolve-executable.test.ts +42 -0
- package/src/__tests__/resolver.test.ts +351 -0
- package/src/__tests__/scope-action.test.ts +134 -0
- package/src/__tests__/scope-squares.test.tsx +164 -0
- package/src/__tests__/selection-scope.test.ts +100 -0
- package/src/__tests__/settings-file.test.ts +179 -0
- package/src/__tests__/shell-script-callers.test.ts +92 -0
- package/src/__tests__/skill-install-files.test.ts +136 -0
- package/src/__tests__/skills-catalog-client.test.ts +48 -0
- package/src/__tests__/style-wrap.test.ts +220 -0
- package/src/__tests__/styles-manager.test.ts +1098 -0
- package/src/__tests__/styles-origins.test.ts +414 -0
- package/src/__tests__/styles-screen-state.test.ts +457 -0
- package/src/__tests__/styles-status-line.test.ts +72 -0
- package/src/__tests__/styles-sync.test.ts +452 -0
- package/src/__tests__/symlink-manager.test.ts +180 -0
- package/src/__tests__/tabbar-layout.test.ts +100 -0
- package/src/__tests__/terminology-filler.test.ts +214 -0
- package/src/__tests__/theme-adaptive-colors.test.ts +354 -0
- package/src/__tests__/theme-env.test.ts +310 -0
- package/src/__tests__/theme-resolve.test.ts +380 -0
- package/src/__tests__/tool-probe.test.ts +1400 -0
- package/src/__tests__/toolchain.test.ts +232 -0
- package/src/__tests__/ui-version-writers.test.ts +88 -0
- package/src/__tests__/update-apply.test.ts +637 -0
- package/src/__tests__/update-plan.test.ts +1396 -0
- package/src/__tests__/uppercase-keybindings.test.ts +101 -0
- package/src/__tests__/useGitignoreModal.test.ts +79 -0
- package/src/__tests__/version-check-refresh.test.ts +45 -0
- package/src/__tests__/version-snapshot.test.ts +212 -0
- package/src/__tests__/version-unknown-state.test.ts +403 -0
- package/src/__tests__/worktree-registry-resolution.test.ts +107 -0
- package/src/cli/ansi.ts +528 -0
- package/src/cli/bootstrap.ts +134 -0
- package/src/cli/doctor.ts +218 -0
- package/src/cli/hook.ts +129 -0
- package/src/cli/install.ts +382 -0
- package/src/cli/live.ts +374 -0
- package/src/cli/models.ts +297 -0
- package/src/cli/profile.ts +197 -0
- package/src/cli/prompt.ts +32 -0
- package/src/cli/router.ts +264 -0
- package/src/cli/update-view.ts +717 -0
- package/src/cli/update.ts +600 -0
- package/src/cli/upgrade.ts +97 -0
- package/src/data/alias-flags.ts +301 -0
- package/src/data/cli-tools.ts +146 -0
- package/src/data/community-styles.ts +531 -0
- package/src/data/gitignore-defaults.ts +33 -0
- package/src/data/gitignore-reasons.ts +93 -0
- package/src/data/gitignore-templates.ts +25 -0
- package/src/data/marketplaces.ts +278 -0
- package/src/data/mcp-servers.ts +51 -0
- package/src/data/models-presets.ts +329 -0
- package/src/data/predefined-profiles.ts +252 -0
- package/src/data/settings-catalog.ts +714 -0
- package/src/data/skill-repos.ts +91 -0
- package/src/data/styles/asd-ste100.md +38 -0
- package/src/data/styles/calibrated.md +24 -0
- package/src/data/styles/direct.md +23 -0
- package/src/data/styles/evidence-first.md +22 -0
- package/src/data/styles/explanatory.md +23 -0
- package/src/data/styles/index.ts +57 -0
- package/src/data/styles/no-slop.md +32 -0
- package/src/data/styles/plain-language.md +23 -0
- package/src/data/styles/structured.md +30 -0
- package/src/data/styles/terminology.md +28 -0
- package/src/data/styles/terse.md +20 -0
- package/src/main.tsx +110 -0
- package/src/markdown.d.ts +13 -0
- package/src/opentui.d.ts +203 -0
- package/src/services/active-closure.ts +117 -0
- package/src/services/alias-settings.ts +60 -0
- package/src/services/alias-shell-writer.ts +1244 -0
- package/src/services/alias-store.ts +172 -0
- package/src/services/binary-signing.ts +78 -0
- package/src/services/catalog-cache-store.ts +316 -0
- package/src/services/claude-cli.ts +304 -0
- package/src/services/claude-runner.ts +31 -0
- package/src/services/claude-settings.ts +1564 -0
- package/src/services/cli-tool-commands.ts +133 -0
- package/src/services/community-fetcher.ts +90 -0
- package/src/services/community-styles.ts +1194 -0
- package/src/services/content-drift.ts +208 -0
- package/src/services/conventions-manager.ts +945 -0
- package/src/services/doctor-bins.ts +63 -0
- package/src/services/doctor.ts +570 -0
- package/src/services/dotenv.ts +181 -0
- package/src/services/git-worktree.ts +129 -0
- package/src/services/github-budget.ts +274 -0
- package/src/services/gitignore-check.ts +63 -0
- package/src/services/gitignore-detector.ts +167 -0
- package/src/services/gitignore-fixer.ts +280 -0
- package/src/services/gitignore-resolver.ts +208 -0
- package/src/services/gitignore-service.ts +167 -0
- package/src/services/hook-registration.ts +218 -0
- package/src/services/install-plan.ts +107 -0
- package/src/services/local-marketplace.ts +657 -0
- package/src/services/manifest.ts +250 -0
- package/src/services/marketplace-catalog-git.ts +173 -0
- package/src/services/marketplace-catalog.ts +129 -0
- package/src/services/marketplace-fetcher.ts +400 -0
- package/src/services/marketplace-refresh.ts +245 -0
- package/src/services/marketplace-sync.ts +145 -0
- package/src/services/mate-availability.ts +133 -0
- package/src/services/mate-catalog.ts +265 -0
- package/src/services/mcp-registry.ts +171 -0
- package/src/services/models-core.ts +969 -0
- package/src/services/models-manager.ts +606 -0
- package/src/services/plugin-checkout.ts +186 -0
- package/src/services/plugin-manager.ts +847 -0
- package/src/services/plugin-mcp-config.ts +242 -0
- package/src/services/plugin-releases.ts +47 -0
- package/src/services/plugin-requires.ts +227 -0
- package/src/services/plugin-setup.ts +965 -0
- package/src/services/profile-adopt.ts +206 -0
- package/src/services/profile-materializer.ts +133 -0
- package/src/services/profile-sync.ts +158 -0
- package/src/services/profiles.ts +225 -0
- package/src/services/resolver.ts +377 -0
- package/src/services/settings-file.ts +69 -0
- package/src/services/settings-manager.ts +307 -0
- package/src/services/skills-manager.ts +1111 -0
- package/src/services/skillsmp-client.ts +193 -0
- package/src/services/styles-manager.ts +1294 -0
- package/src/services/symlink-manager.ts +189 -0
- package/src/services/terminology-filler.ts +266 -0
- package/src/services/tool-probe.ts +1637 -0
- package/src/services/toolchain.ts +431 -0
- package/src/services/update-engine.ts +691 -0
- package/src/services/update-plan.ts +820 -0
- package/src/services/version-check.ts +127 -0
- package/src/services/version-snapshot.ts +189 -0
- package/src/tui.tsx +112 -0
- package/src/types/bun.d.ts +27 -0
- package/src/types/gitignore.ts +68 -0
- package/src/types/index.ts +447 -0
- package/src/ui/App.tsx +535 -0
- package/src/ui/adapters/catalogNotice.ts +122 -0
- package/src/ui/adapters/cliToolsAdapter.ts +176 -0
- package/src/ui/adapters/modelsAdapter.ts +213 -0
- package/src/ui/adapters/pluginsAdapter.ts +413 -0
- package/src/ui/adapters/settingsAdapter.ts +168 -0
- package/src/ui/adapters/skillsAdapter.ts +229 -0
- package/src/ui/adapters/stylesAdapter.ts +403 -0
- package/src/ui/components/CategoryHeader.tsx +64 -0
- package/src/ui/components/EmptyFilterState.tsx +32 -0
- package/src/ui/components/FlagDetailEditor.tsx +0 -0
- package/src/ui/components/ScopeIndicator.tsx +63 -0
- package/src/ui/components/ScrollableList.tsx +95 -0
- package/src/ui/components/SearchInput.tsx +57 -0
- package/src/ui/components/StyledText.tsx +75 -0
- package/src/ui/components/TabBar.tsx +101 -0
- package/src/ui/components/layout/FooterHints.tsx +78 -0
- package/src/ui/components/layout/Panel.tsx +63 -0
- package/src/ui/components/layout/ProgressBar.tsx +44 -0
- package/src/ui/components/layout/ScopeTabs.tsx +54 -0
- package/src/ui/components/layout/ScreenLayout.tsx +321 -0
- package/src/ui/components/layout/index.ts +9 -0
- package/src/ui/components/modals/ConfirmModal.tsx +62 -0
- package/src/ui/components/modals/InputModal.tsx +75 -0
- package/src/ui/components/modals/LoadingModal.tsx +51 -0
- package/src/ui/components/modals/MessageModal.tsx +66 -0
- package/src/ui/components/modals/ModalContainer.tsx +159 -0
- package/src/ui/components/modals/SelectModal.tsx +90 -0
- package/src/ui/components/modals/index.ts +6 -0
- package/src/ui/components/primitives/ActionHints.tsx +44 -0
- package/src/ui/components/primitives/DetailSection.tsx +23 -0
- package/src/ui/components/primitives/KeyValueLine.tsx +26 -0
- package/src/ui/components/primitives/ListCategoryRow.tsx +45 -0
- package/src/ui/components/primitives/MetaText.tsx +40 -0
- package/src/ui/components/primitives/ScopeDetail.tsx +67 -0
- package/src/ui/components/primitives/ScopeSquares.tsx +58 -0
- package/src/ui/components/primitives/SelectableRow.tsx +30 -0
- package/src/ui/components/primitives/index.ts +9 -0
- package/src/ui/hooks/index.ts +2 -0
- package/src/ui/hooks/useGitignoreModal.ts +95 -0
- package/src/ui/hooks/useKeyboard.ts +24 -0
- package/src/ui/hooks/useKeyboardHandler.ts +63 -0
- package/src/ui/registry.ts +33 -0
- package/src/ui/renderers/cliToolRenderers.tsx +326 -0
- package/src/ui/renderers/gitignoreRenderers.tsx +80 -0
- package/src/ui/renderers/markdownRenderer.tsx +302 -0
- package/src/ui/renderers/mcpRenderers.tsx +149 -0
- package/src/ui/renderers/modelRenderers.tsx +1309 -0
- package/src/ui/renderers/modelVisuals.tsx +1402 -0
- package/src/ui/renderers/pluginRenderers.tsx +1089 -0
- package/src/ui/renderers/pluginRowText.ts +70 -0
- package/src/ui/renderers/pluginStatusText.ts +56 -0
- package/src/ui/renderers/profileRenderers.tsx +406 -0
- package/src/ui/renderers/settingsRenderers.tsx +218 -0
- package/src/ui/renderers/skillRenderers.tsx +479 -0
- package/src/ui/renderers/styleRenderers.tsx +813 -0
- package/src/ui/screens/AliasScreen.tsx +1558 -0
- package/src/ui/screens/CliToolsScreen.tsx +367 -0
- package/src/ui/screens/EnvVarsScreen.tsx +221 -0
- package/src/ui/screens/GitignoreScreen.tsx +562 -0
- package/src/ui/screens/McpRegistryScreen.tsx +366 -0
- package/src/ui/screens/McpScreen.tsx +256 -0
- package/src/ui/screens/ModelsScreen.tsx +542 -0
- package/src/ui/screens/PluginsScreen.tsx +1423 -0
- package/src/ui/screens/ProfilesScreen.tsx +438 -0
- package/src/ui/screens/SkillsScreen.tsx +853 -0
- package/src/ui/screens/StylesScreen.tsx +1064 -0
- package/src/ui/screens/index.ts +11 -0
- package/src/ui/state/AppContext.tsx +233 -0
- package/src/ui/state/DimensionsContext.tsx +154 -0
- package/src/ui/state/reducer.ts +810 -0
- package/src/ui/state/types.ts +503 -0
- package/src/ui/theme-mode.ts +440 -0
- package/src/ui/theme-resolve.ts +147 -0
- package/src/ui/theme.ts +175 -0
- package/src/utils/clipboard.ts +58 -0
- package/src/utils/command-utils.ts +149 -0
- package/src/utils/config-dir.ts +45 -0
- package/src/utils/file-locking.ts +146 -0
- package/src/utils/fuzzy-search.ts +138 -0
- package/src/utils/go-module.ts +36 -0
- package/src/utils/open-file.ts +84 -0
- package/src/utils/run.ts +67 -0
- package/src/utils/string-utils.ts +88 -0
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The CLI-tools catalogue can no longer emit a bare `pip`, or shell syntax.
|
|
3
|
+
*
|
|
4
|
+
* Two defects lived in this screen, and both were data problems dressed as code
|
|
5
|
+
* problems:
|
|
6
|
+
*
|
|
7
|
+
* 1. **Bare `pip`.** `getUpdateCommand`'s `pip` and `default` arms returned the
|
|
8
|
+
* catalogue's `installCommand` verbatim, and `getUninstallCommand` emitted
|
|
9
|
+
* `pip uninstall -y`. `pip` is frequently a zsh alias rather than an
|
|
10
|
+
* executable, and a machine that manages Python tools with uv or pipx may
|
|
11
|
+
* have no `pip` at all. `services/toolchain.ts` already knew how to answer
|
|
12
|
+
* "how does Python work here"; this screen was not asking it.
|
|
13
|
+
* 2. **Shell syntax in data.** `aider`'s entry was
|
|
14
|
+
* `pip install aider-install && aider-install` — a `&&` chain in a catalogue
|
|
15
|
+
* field that got handed to `/bin/bash`.
|
|
16
|
+
*
|
|
17
|
+
* Both are now unrepresentable: the field is gone and the command is derived.
|
|
18
|
+
* These tests assert that over the whole catalogue rather than over an example,
|
|
19
|
+
* so a new entry cannot reintroduce either.
|
|
20
|
+
*
|
|
21
|
+
* Pure — no React, no network, no PATH dependency. Every Python installer is
|
|
22
|
+
* pinned explicitly, per the convention `toolchain.test.ts` established: the
|
|
23
|
+
* production default is resolved from PATH, so an unpinned assertion would
|
|
24
|
+
* encode whatever happens to be installed on the machine running the suite.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import { describe, expect, test } from "bun:test";
|
|
28
|
+
import { cliTools } from "../data/cli-tools.js";
|
|
29
|
+
import {
|
|
30
|
+
type InstallMethod,
|
|
31
|
+
cliToolInstall,
|
|
32
|
+
cliToolUninstall,
|
|
33
|
+
cliToolUpdate,
|
|
34
|
+
} from "../services/cli-tool-commands.js";
|
|
35
|
+
import { versionFromPypiJson } from "../services/tool-probe.js";
|
|
36
|
+
import {
|
|
37
|
+
PYTHON_CLI_INSTALLERS,
|
|
38
|
+
type PythonCliInstaller,
|
|
39
|
+
resolvePythonCliInstaller,
|
|
40
|
+
} from "../services/toolchain.js";
|
|
41
|
+
import { type Command, formatCommand } from "../utils/command-utils.js";
|
|
42
|
+
|
|
43
|
+
const byProbe = (probe: string): PythonCliInstaller => {
|
|
44
|
+
const found = PYTHON_CLI_INSTALLERS.find((i) => i.probe === probe);
|
|
45
|
+
if (!found) throw new Error(`no python installer with probe ${probe}`);
|
|
46
|
+
return found;
|
|
47
|
+
};
|
|
48
|
+
const PYTHON_PROBES = PYTHON_CLI_INSTALLERS.map((i) => i.probe);
|
|
49
|
+
|
|
50
|
+
/** The methods the resolver models, i.e. the ones that reach toolchain.ts. */
|
|
51
|
+
const RESOLVED_METHODS: InstallMethod[] = ["npm", "bun", "brew", "pip"];
|
|
52
|
+
|
|
53
|
+
describe("the catalogue is well-formed", () => {
|
|
54
|
+
test("it has entries, and each names a package manager and a package", () => {
|
|
55
|
+
// Guard: every assertion below iterates the catalogue, so an empty or
|
|
56
|
+
// malformed one would make them all pass vacuously.
|
|
57
|
+
expect(cliTools.length).toBeGreaterThan(5);
|
|
58
|
+
for (const tool of cliTools) {
|
|
59
|
+
expect(typeof tool.packageName).toBe("string");
|
|
60
|
+
expect(tool.packageName.length).toBeGreaterThan(0);
|
|
61
|
+
expect(["bun", "npm", "pip", "brew", "go"]).toContain(
|
|
62
|
+
tool.packageManager,
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
describe("no builder ever emits a bare `pip`", () => {
|
|
69
|
+
test("install: cmd is the resolver's probe, never `pip`", () => {
|
|
70
|
+
for (const tool of cliTools) {
|
|
71
|
+
const cmd = cliToolInstall(tool);
|
|
72
|
+
expect(cmd.cmd).not.toBe("pip");
|
|
73
|
+
if (tool.packageManager === "pip") {
|
|
74
|
+
// Whatever the machine running this resolved to, it must be one of
|
|
75
|
+
// the three modelled installers — and `cmd` must BE that probe,
|
|
76
|
+
// since that is the executable the syscall looks up.
|
|
77
|
+
expect(PYTHON_PROBES).toContain(cmd.cmd);
|
|
78
|
+
expect(cmd.cmd).toBe(resolvePythonCliInstaller().probe);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("update and uninstall: cmd is never `pip`, for any method", () => {
|
|
84
|
+
for (const tool of cliTools) {
|
|
85
|
+
for (const method of RESOLVED_METHODS) {
|
|
86
|
+
for (const cmd of [
|
|
87
|
+
cliToolUpdate(tool, method),
|
|
88
|
+
cliToolUninstall(tool, method),
|
|
89
|
+
]) {
|
|
90
|
+
if (!cmd) continue;
|
|
91
|
+
expect(cmd.cmd).not.toBe("pip");
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("each pinned Python installer drives all three of its own commands", () => {
|
|
98
|
+
// Pinned explicitly so this holds on a runner with no uv. The `pip`
|
|
99
|
+
// method must route to the SAME executable for install, update and
|
|
100
|
+
// uninstall — installing with uv and uninstalling with python3 would look
|
|
101
|
+
// like it worked and leave the tool on disk.
|
|
102
|
+
for (const probe of PYTHON_PROBES) {
|
|
103
|
+
const python = byProbe(probe);
|
|
104
|
+
expect(python.install("x").cmd).toBe(probe);
|
|
105
|
+
expect(python.upgrade("x").cmd).toBe(probe);
|
|
106
|
+
expect(python.uninstall("x").cmd).toBe(probe);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe("no builder can emit shell syntax", () => {
|
|
112
|
+
test("no argv element of any command contains &&, | or ;", () => {
|
|
113
|
+
// The catalogue's only `&&` chain was aider's installCommand. With the
|
|
114
|
+
// field gone there is nowhere left for shell syntax to enter, and this
|
|
115
|
+
// asserts that over every entry and every method rather than over that
|
|
116
|
+
// one case.
|
|
117
|
+
const all: Command[] = [];
|
|
118
|
+
for (const tool of cliTools) {
|
|
119
|
+
all.push(cliToolInstall(tool));
|
|
120
|
+
for (const method of [
|
|
121
|
+
...RESOLVED_METHODS,
|
|
122
|
+
"pnpm" as const,
|
|
123
|
+
"yarn" as const,
|
|
124
|
+
]) {
|
|
125
|
+
const u = cliToolUpdate(tool, method);
|
|
126
|
+
const r = cliToolUninstall(tool, method);
|
|
127
|
+
if (u) all.push(u);
|
|
128
|
+
if (r) all.push(r);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
expect(all.length).toBeGreaterThan(0);
|
|
132
|
+
|
|
133
|
+
for (const cmd of all) {
|
|
134
|
+
for (const token of [cmd.cmd, ...cmd.args]) {
|
|
135
|
+
expect(token).not.toContain("&&");
|
|
136
|
+
expect(token).not.toContain("|");
|
|
137
|
+
expect(token).not.toContain(";");
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
describe("the detected method is followed, not the catalogue's", () => {
|
|
144
|
+
test("pnpm and yarn keep their own shapes", () => {
|
|
145
|
+
// A tool the catalogue calls npm can still have been installed with
|
|
146
|
+
// pnpm. Updating it as npm would install a second copy beside the first,
|
|
147
|
+
// which is the conflict this screen exists to report.
|
|
148
|
+
const tool = cliTools.find((t) => t.packageManager === "npm")!;
|
|
149
|
+
expect(cliToolUpdate(tool, "pnpm")).toEqual({
|
|
150
|
+
cmd: "pnpm",
|
|
151
|
+
args: ["install", "-g", tool.packageName],
|
|
152
|
+
});
|
|
153
|
+
expect(cliToolUpdate(tool, "yarn")).toEqual({
|
|
154
|
+
cmd: "yarn",
|
|
155
|
+
args: ["global", "add", tool.packageName],
|
|
156
|
+
});
|
|
157
|
+
expect(cliToolUninstall(tool, "pnpm")).toEqual({
|
|
158
|
+
cmd: "pnpm",
|
|
159
|
+
args: ["remove", "-g", tool.packageName],
|
|
160
|
+
});
|
|
161
|
+
expect(cliToolUninstall(tool, "yarn")).toEqual({
|
|
162
|
+
cmd: "yarn",
|
|
163
|
+
args: ["global", "remove", tool.packageName],
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test("`unknown` returns null rather than guessing", () => {
|
|
168
|
+
// Preserves the observable behaviour: handleInstall reads
|
|
169
|
+
// `installed && updateCommand ? updateCommand : install…`, so null falls
|
|
170
|
+
// back to a plain install — which is what the old `default:` arm did by
|
|
171
|
+
// returning `tool.installCommand`.
|
|
172
|
+
const tool = cliTools[0]!;
|
|
173
|
+
expect(cliToolUpdate(tool, "unknown")).toBeNull();
|
|
174
|
+
expect(cliToolUninstall(tool, "unknown")).toBeNull();
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test("brew and go arms are pinned, since no catalogue entry exercises them", () => {
|
|
178
|
+
// Every entry today is bun/npm/pip, so these two ship untouched by real
|
|
179
|
+
// data. `go` has no uninstaller — `go install` writes a binary and
|
|
180
|
+
// records nothing — so null is correct rather than an invented `rm`.
|
|
181
|
+
const tool = cliTools[0]!;
|
|
182
|
+
expect(cliToolUpdate(tool, "brew", "some-formula")).toEqual({
|
|
183
|
+
cmd: "brew",
|
|
184
|
+
args: ["upgrade", "some-formula"],
|
|
185
|
+
});
|
|
186
|
+
expect(cliToolUninstall(tool, "brew", "some-formula")).toEqual({
|
|
187
|
+
cmd: "brew",
|
|
188
|
+
args: ["uninstall", "some-formula"],
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
describe("derived commands still read as they did", () => {
|
|
194
|
+
test("claude renders exactly the literal the catalogue used to carry", () => {
|
|
195
|
+
// The entry whose command SHOULD not change. Its old
|
|
196
|
+
// `installCommand: "npm install -g @anthropic-ai/claude-code"` is now
|
|
197
|
+
// derived from packageManager + packageName, and must render identically.
|
|
198
|
+
const claude = cliTools.find((t) => t.name === "claude")!;
|
|
199
|
+
expect(formatCommand(cliToolInstall(claude))).toBe(
|
|
200
|
+
"npm install -g @anthropic-ai/claude-code",
|
|
201
|
+
);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test("aider now installs the package its own fields already named", () => {
|
|
205
|
+
// The one deliberate behaviour change. The old command installed
|
|
206
|
+
// `aider-install`, a bootstrapper whose version nothing here ever read,
|
|
207
|
+
// while `packageName`, `checkCommand` and the PyPI query all said
|
|
208
|
+
// `aider-chat`. The installCommand was the field that was out of step.
|
|
209
|
+
const aider = cliTools.find((t) => t.name === "aider")!;
|
|
210
|
+
expect(aider.packageName).toBe("aider-chat");
|
|
211
|
+
expect(cliToolInstall(aider).args).toContain("aider-chat");
|
|
212
|
+
expect(formatCommand(cliToolInstall(aider))).not.toContain("aider-install");
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
describe("versionFromPypiJson", () => {
|
|
217
|
+
// The shape of a real https://pypi.org/pypi/<pkg>/json body, trimmed to the
|
|
218
|
+
// two fields that matter. Captured rather than mocked so the parser is
|
|
219
|
+
// tested against what the endpoint actually returns.
|
|
220
|
+
const body = {
|
|
221
|
+
info: { name: "aider-chat", version: "0.86.1" },
|
|
222
|
+
releases: {},
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
test("reads info.version", () => {
|
|
226
|
+
expect(versionFromPypiJson(body)).toBe("0.86.1");
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
test("anything unexpected yields undefined, never a throw", () => {
|
|
230
|
+
// A version check is advisory: it must degrade to "no update badge",
|
|
231
|
+
// never take the screen down. PyPI returning an error object, a 404 body,
|
|
232
|
+
// or nothing at all all land here.
|
|
233
|
+
expect(versionFromPypiJson({})).toBeUndefined();
|
|
234
|
+
expect(versionFromPypiJson({ info: {} })).toBeUndefined();
|
|
235
|
+
expect(versionFromPypiJson({ info: { version: 42 } })).toBeUndefined();
|
|
236
|
+
expect(versionFromPypiJson(null)).toBeUndefined();
|
|
237
|
+
expect(versionFromPypiJson("not json at all")).toBeUndefined();
|
|
238
|
+
});
|
|
239
|
+
});
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The TUI CLI Tools row is built from the shared probe facts and the shared
|
|
3
|
+
* classifier — the row can no longer disagree with `magus update`.
|
|
4
|
+
*
|
|
5
|
+
* Pure: `buildToolRow` over `specForCatalogTool`, with facts supplied the way
|
|
6
|
+
* `probeTools` would report them. No React, no IO.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { describe, expect, test } from "bun:test";
|
|
10
|
+
import { cliTools } from "../data/cli-tools.js";
|
|
11
|
+
import { cliToolInstall } from "../services/cli-tool-commands.js";
|
|
12
|
+
import {
|
|
13
|
+
type ToolFacts,
|
|
14
|
+
classifyTool,
|
|
15
|
+
specForCatalogTool,
|
|
16
|
+
} from "../services/tool-probe.js";
|
|
17
|
+
import { binCommandFor } from "../services/update-engine.js";
|
|
18
|
+
import { planBinUpdates } from "../services/update-plan.js";
|
|
19
|
+
import type { ResolvedBin } from "../types/index.js";
|
|
20
|
+
import {
|
|
21
|
+
buildToolRow,
|
|
22
|
+
cliToolStatusText,
|
|
23
|
+
countToolRows,
|
|
24
|
+
} from "../ui/adapters/cliToolsAdapter.js";
|
|
25
|
+
|
|
26
|
+
const catalogue = (name: string) => {
|
|
27
|
+
const tool = cliTools.find((t) => t.name === name);
|
|
28
|
+
if (!tool) throw new Error(`no catalogue tool ${name}`);
|
|
29
|
+
return tool;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
function facts(name: string, over: Partial<ToolFacts> = {}): ToolFacts {
|
|
33
|
+
return {
|
|
34
|
+
name,
|
|
35
|
+
present: true,
|
|
36
|
+
resolvedPath: `/x/${name}`,
|
|
37
|
+
installed: "1.0.0",
|
|
38
|
+
latest: null,
|
|
39
|
+
behind: null,
|
|
40
|
+
managed: true,
|
|
41
|
+
sources: [],
|
|
42
|
+
...over,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
describe("buildToolRow — which installer a catalogue row is judged through", () => {
|
|
47
|
+
test("catalogue says npm, detected bun: the spec is bun and the state is bun's", () => {
|
|
48
|
+
// codex and opencode on the reporter's machine: catalogued as npm,
|
|
49
|
+
// installed by bun into ~/.bun/install/global.
|
|
50
|
+
const codex = catalogue("codex");
|
|
51
|
+
expect(codex.packageManager).toBe("npm");
|
|
52
|
+
const install = { primary: "bun" as const, all: ["bun" as const] };
|
|
53
|
+
const spec = specForCatalogTool(codex, null, install);
|
|
54
|
+
expect(spec.via).toBe("bun");
|
|
55
|
+
|
|
56
|
+
const row = buildToolRow(
|
|
57
|
+
codex,
|
|
58
|
+
spec,
|
|
59
|
+
facts("codex", { installed: "0.153.2", latest: "0.154.0" }),
|
|
60
|
+
install,
|
|
61
|
+
);
|
|
62
|
+
expect(row.status).toMatchObject({
|
|
63
|
+
state: "upgrade",
|
|
64
|
+
target: "0.154.0",
|
|
65
|
+
});
|
|
66
|
+
// Enter names the same installer the row was probed through.
|
|
67
|
+
expect(row.updateCommand).toEqual({
|
|
68
|
+
cmd: "bun",
|
|
69
|
+
args: ["install", "-g", "@openai/codex@latest"],
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("declared by the closure with via npm: the closure wins, and the row equals the CLI plan", () => {
|
|
74
|
+
const codex = catalogue("codex");
|
|
75
|
+
const declared: ResolvedBin = {
|
|
76
|
+
name: "codex",
|
|
77
|
+
via: "npm",
|
|
78
|
+
package: "@openai/codex",
|
|
79
|
+
sources: ["profile:default.cliTools"],
|
|
80
|
+
};
|
|
81
|
+
const install = { primary: "bun" as const, all: ["bun" as const] };
|
|
82
|
+
const spec = specForCatalogTool(codex, [declared], install);
|
|
83
|
+
expect(spec).toBe(declared);
|
|
84
|
+
|
|
85
|
+
// Through npm, the bun-installed file is not npm's: not-managed.
|
|
86
|
+
const f = facts("codex", {
|
|
87
|
+
managed: false,
|
|
88
|
+
resolvedPath:
|
|
89
|
+
"/Users/j/.bun/install/global/node_modules/@openai/codex/bin/codex.js",
|
|
90
|
+
owner: "npm (root /Users/j/.local/lib/node_modules)",
|
|
91
|
+
});
|
|
92
|
+
const row = buildToolRow(codex, spec, f, install);
|
|
93
|
+
const [cli] = planBinUpdates([declared], [f]);
|
|
94
|
+
expect(row.status).toMatchObject({
|
|
95
|
+
state: cli!.action,
|
|
96
|
+
reason: cli!.reason,
|
|
97
|
+
note: cli!.note,
|
|
98
|
+
target: cli!.target,
|
|
99
|
+
});
|
|
100
|
+
expect(row.status?.reason).toBe("not-managed");
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("pinned and behind: the row's key installs the PIN, exactly what the CLI runs", () => {
|
|
104
|
+
// The defect this pins: the row read `→ v0.150.0` (the pin) while its
|
|
105
|
+
// key ran `npm install -g @openai/codex@latest`, which installs past the
|
|
106
|
+
// pin and leaves the tool `unknown / ahead-of-pin` for good.
|
|
107
|
+
const codex = catalogue("codex");
|
|
108
|
+
const declared: ResolvedBin = {
|
|
109
|
+
name: "codex",
|
|
110
|
+
via: "npm",
|
|
111
|
+
package: "@openai/codex",
|
|
112
|
+
version: "0.150.0",
|
|
113
|
+
sources: ["profile:default.cliTools"],
|
|
114
|
+
};
|
|
115
|
+
// Detected as bun — the installer the OLD command builder followed.
|
|
116
|
+
const install = { primary: "bun" as const, all: ["bun" as const] };
|
|
117
|
+
const spec = specForCatalogTool(codex, [declared], install);
|
|
118
|
+
const f = facts("codex", { installed: "0.149.0", managed: true });
|
|
119
|
+
|
|
120
|
+
const row = buildToolRow(codex, spec, f, install);
|
|
121
|
+
expect(row.status).toMatchObject({
|
|
122
|
+
state: "upgrade",
|
|
123
|
+
reason: "behind-pin",
|
|
124
|
+
target: "0.150.0",
|
|
125
|
+
});
|
|
126
|
+
expect(row.updateCommand).toEqual({
|
|
127
|
+
cmd: "npm",
|
|
128
|
+
args: ["install", "-g", "@openai/codex@0.150.0"],
|
|
129
|
+
});
|
|
130
|
+
// And it is the same command `magus update` would run for this row.
|
|
131
|
+
const [item] = planBinUpdates([declared], [f]);
|
|
132
|
+
expect(row.updateCommand).toEqual(binCommandFor(item!, declared)!);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("declared and absent: Enter installs at the pin, not the catalogue's @latest", () => {
|
|
136
|
+
const codex = catalogue("codex");
|
|
137
|
+
const declared: ResolvedBin = {
|
|
138
|
+
name: "codex",
|
|
139
|
+
via: "npm",
|
|
140
|
+
package: "@openai/codex",
|
|
141
|
+
version: "0.150.0",
|
|
142
|
+
sources: ["profile:default.cliTools"],
|
|
143
|
+
};
|
|
144
|
+
const row = buildToolRow(
|
|
145
|
+
codex,
|
|
146
|
+
specForCatalogTool(codex, [declared], undefined),
|
|
147
|
+
facts("codex", { present: false, installed: null }),
|
|
148
|
+
);
|
|
149
|
+
expect(row.status?.state).toBe("install");
|
|
150
|
+
expect(row.updateCommand).toBeUndefined();
|
|
151
|
+
expect(row.installCommand).toEqual({
|
|
152
|
+
cmd: "npm",
|
|
153
|
+
args: ["install", "-g", "@openai/codex@0.150.0"],
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test("declared, unpinned and behind: the closure's package, at latest", () => {
|
|
158
|
+
const codex = catalogue("codex");
|
|
159
|
+
const declared: ResolvedBin = {
|
|
160
|
+
name: "codex",
|
|
161
|
+
via: "npm",
|
|
162
|
+
package: "@openai/codex",
|
|
163
|
+
sources: ["plugin:dev@magus"],
|
|
164
|
+
};
|
|
165
|
+
const install = { primary: "bun" as const, all: ["bun" as const] };
|
|
166
|
+
const row = buildToolRow(
|
|
167
|
+
codex,
|
|
168
|
+
specForCatalogTool(codex, [declared], install),
|
|
169
|
+
facts("codex", { installed: "0.153.2", latest: "0.154.0" }),
|
|
170
|
+
install,
|
|
171
|
+
);
|
|
172
|
+
expect(row.status?.state).toBe("upgrade");
|
|
173
|
+
// npm, the PROFILE's installer — not bun, the detected one — because the
|
|
174
|
+
// row's state was judged through npm too.
|
|
175
|
+
expect(row.updateCommand).toEqual({
|
|
176
|
+
cmd: "npm",
|
|
177
|
+
args: ["install", "-g", "@openai/codex@latest"],
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test("undeclared: the screen's own rule survives — how it IS installed", () => {
|
|
182
|
+
const codex = catalogue("codex");
|
|
183
|
+
const install = { primary: "bun" as const, all: ["bun" as const] };
|
|
184
|
+
const row = buildToolRow(
|
|
185
|
+
codex,
|
|
186
|
+
specForCatalogTool(codex, null, install),
|
|
187
|
+
facts("codex", { installed: "0.153.2", latest: "0.154.0" }),
|
|
188
|
+
install,
|
|
189
|
+
);
|
|
190
|
+
expect(row.updateCommand).toEqual({
|
|
191
|
+
cmd: "bun",
|
|
192
|
+
args: ["install", "-g", "@openai/codex@latest"],
|
|
193
|
+
});
|
|
194
|
+
expect(row.installCommand).toEqual(cliToolInstall(codex));
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
test("detected pnpm → the catalogue's via, and a not-managed row naming the real path", () => {
|
|
198
|
+
const opencode = catalogue("opencode");
|
|
199
|
+
const install = { primary: "pnpm" as const, all: ["pnpm" as const] };
|
|
200
|
+
const spec = specForCatalogTool(opencode, null, install);
|
|
201
|
+
expect(spec.via).toBe(opencode.packageManager);
|
|
202
|
+
|
|
203
|
+
const f = facts("opencode", {
|
|
204
|
+
managed: false,
|
|
205
|
+
resolvedPath:
|
|
206
|
+
"/Users/j/.local/share/pnpm/global/5/node_modules/opencode-ai/bin/opencode",
|
|
207
|
+
owner: "npm (root /Users/j/.local/lib/node_modules)",
|
|
208
|
+
});
|
|
209
|
+
const row = buildToolRow(opencode, spec, f, install);
|
|
210
|
+
expect(row.status?.reason).toBe("not-managed");
|
|
211
|
+
expect(row.status?.note).toContain("/Users/j/.local/share/pnpm/");
|
|
212
|
+
// Enter still follows how it IS installed (pnpm).
|
|
213
|
+
expect(row.updateCommand?.cmd).toBe("pnpm");
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
test("no facts yet → checking, no status, no command", () => {
|
|
217
|
+
const tool = catalogue("claudish");
|
|
218
|
+
const row = buildToolRow(
|
|
219
|
+
tool,
|
|
220
|
+
specForCatalogTool(tool, null, undefined),
|
|
221
|
+
undefined,
|
|
222
|
+
);
|
|
223
|
+
expect(row).toMatchObject({ checking: true, status: null });
|
|
224
|
+
expect(row.updateCommand).toBeUndefined();
|
|
225
|
+
expect(cliToolStatusText(row).suffix).toBe(" ...");
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
test("absent → install, with no update command", () => {
|
|
229
|
+
const tool = catalogue("cline");
|
|
230
|
+
const spec = specForCatalogTool(tool, null, undefined);
|
|
231
|
+
const row = buildToolRow(
|
|
232
|
+
tool,
|
|
233
|
+
spec,
|
|
234
|
+
facts("cline", { present: false, installed: null }),
|
|
235
|
+
{ primary: "unknown", all: [] },
|
|
236
|
+
);
|
|
237
|
+
expect(row.status?.state).toBe("install");
|
|
238
|
+
expect(row.updateCommand).toBeUndefined();
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
describe("cliToolStatusText — the row's words", () => {
|
|
243
|
+
test("unknown renders as ? <note>, the note verbatim", () => {
|
|
244
|
+
const tool = catalogue("crush");
|
|
245
|
+
const spec = specForCatalogTool(tool, null, {
|
|
246
|
+
primary: "npm",
|
|
247
|
+
all: ["npm"],
|
|
248
|
+
});
|
|
249
|
+
const f = facts("crush", { managed: false, owner: "npm (root /l)" });
|
|
250
|
+
const row = buildToolRow(tool, spec, f);
|
|
251
|
+
const text = cliToolStatusText(row);
|
|
252
|
+
expect(text.tone).toBe("warning");
|
|
253
|
+
expect(text.suffix).toBe(` ? ${classifyTool(spec, f).note}`);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
test("upgrade names the target once, with one v", () => {
|
|
257
|
+
const tool = catalogue("mnemex");
|
|
258
|
+
const spec = specForCatalogTool(tool, null, {
|
|
259
|
+
primary: "bun",
|
|
260
|
+
all: ["bun"],
|
|
261
|
+
});
|
|
262
|
+
const row = buildToolRow(tool, spec, facts("mnemex", { latest: "v1.2.0" }));
|
|
263
|
+
expect(cliToolStatusText(row).suffix).toBe(" → v1.2.0");
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
test("current and install say nothing extra", () => {
|
|
267
|
+
const tool = catalogue("mnemex");
|
|
268
|
+
const spec = specForCatalogTool(tool, null, undefined);
|
|
269
|
+
expect(
|
|
270
|
+
cliToolStatusText(
|
|
271
|
+
buildToolRow(tool, spec, facts("mnemex", { behind: false })),
|
|
272
|
+
).suffix,
|
|
273
|
+
).toBe("");
|
|
274
|
+
});
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
describe("countToolRows — header counts from states", () => {
|
|
278
|
+
test("installed, updates and unverified", () => {
|
|
279
|
+
const tool = catalogue("mnemex");
|
|
280
|
+
const spec = specForCatalogTool(tool, null, undefined);
|
|
281
|
+
const rows = [
|
|
282
|
+
buildToolRow(tool, spec, facts("mnemex", { behind: false })),
|
|
283
|
+
buildToolRow(tool, spec, facts("mnemex", { behind: true })),
|
|
284
|
+
buildToolRow(tool, spec, facts("mnemex", { managed: false })),
|
|
285
|
+
buildToolRow(tool, spec, facts("mnemex", { present: false })),
|
|
286
|
+
buildToolRow(tool, spec, undefined),
|
|
287
|
+
];
|
|
288
|
+
expect(countToolRows(rows)).toEqual({
|
|
289
|
+
installed: 3,
|
|
290
|
+
updates: 1,
|
|
291
|
+
unverified: 1,
|
|
292
|
+
});
|
|
293
|
+
});
|
|
294
|
+
});
|