claudeup 4.18.0 → 4.22.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/bin/claudeup.js +43 -19
- package/package.json +14 -19
- package/scripts/build-binaries.ts +77 -0
- package/scripts/check-optional-deps.ts +49 -0
- package/scripts/test-isolated.ts +43 -0
- package/src/__tests__/alias-adopt.test.ts +364 -0
- package/src/__tests__/alias-parser.test.ts +92 -0
- package/src/__tests__/alias-shell-writer.test.ts +7 -0
- package/src/__tests__/alias-store.test.ts +77 -0
- package/src/__tests__/cli-router.test.ts +65 -0
- package/src/__tests__/conventions-integration.test.ts +745 -0
- package/src/__tests__/conventions-manager.test.ts +1172 -0
- package/src/__tests__/doctor-bins.test.ts +35 -0
- package/src/__tests__/doctor.test.ts +450 -0
- package/src/__tests__/dual-write-prevention.test.ts +14 -4
- package/src/__tests__/file-locking.test.ts +208 -0
- package/src/__tests__/gitignore-prerun.test.ts +1 -0
- package/src/__tests__/install-command.test.ts +81 -0
- package/src/__tests__/install-plan.test.ts +98 -0
- package/src/__tests__/manifest.test.ts +136 -0
- package/src/__tests__/marketplace-refresh.test.ts +267 -0
- package/src/__tests__/plugin-requires.test.ts +133 -0
- package/src/__tests__/plugin-setup.test.ts +118 -0
- package/src/__tests__/profile-command.test.ts +116 -0
- package/src/__tests__/profile-materializer.test.ts +82 -0
- package/src/__tests__/profile-sync.test.ts +136 -0
- package/src/__tests__/registry-version-resolution.test.ts +74 -0
- package/src/__tests__/resolve-executable.test.ts +42 -0
- package/src/__tests__/resolver.test.ts +218 -0
- package/src/__tests__/symlink-manager.test.ts +99 -0
- package/src/__tests__/toolchain.test.ts +56 -0
- package/src/cli/claude.ts +21 -0
- package/src/cli/doctor.ts +132 -0
- package/src/cli/install.ts +360 -0
- package/src/cli/profile.ts +166 -0
- package/src/cli/router.ts +107 -0
- package/src/cli/update.ts +85 -0
- package/src/data/alias-flags.ts +11 -1
- package/src/data/cli-tools.ts +7 -7
- package/src/data/gitignore-defaults.ts +4 -0
- package/src/data/marketplaces.ts +12 -0
- package/src/data/predefined-profiles.ts +3 -4
- package/src/main.tsx +11 -154
- package/src/prerunner/index.ts +62 -27
- package/src/services/alias-shell-writer.ts +382 -8
- package/src/services/alias-store.ts +60 -0
- package/src/services/claude-cli.ts +19 -22
- package/src/services/claude-settings.ts +99 -26
- package/src/services/conventions-manager.ts +865 -0
- package/src/services/doctor-bins.ts +49 -0
- package/src/services/doctor.ts +509 -0
- package/src/services/install-plan.ts +107 -0
- package/src/services/manifest.ts +166 -0
- package/src/services/marketplace-refresh.ts +173 -0
- package/src/services/plugin-manager.ts +7 -1
- package/src/services/plugin-requires.ts +215 -0
- package/src/services/plugin-setup.ts +61 -4
- package/src/services/plugin-version-check.ts +9 -4
- package/src/services/profile-materializer.ts +61 -0
- package/src/services/profile-sync.ts +150 -0
- package/src/services/resolver.ts +293 -0
- package/src/services/symlink-manager.ts +146 -0
- package/src/services/toolchain.ts +97 -0
- package/src/services/version-check.ts +10 -11
- package/src/types/index.ts +138 -31
- package/src/ui/App.tsx +16 -11
- package/src/ui/components/FlagDetailEditor.tsx +0 -0
- package/src/ui/components/modals/ConfirmModal.tsx +1 -1
- package/src/ui/screens/AliasScreen.tsx +491 -359
- package/src/ui/screens/CliToolsScreen.tsx +11 -3
- package/src/ui/screens/PluginsScreen.tsx +3 -1
- package/src/ui/screens/index.ts +0 -1
- package/src/ui/state/reducer.ts +6 -102
- package/src/ui/state/types.ts +8 -35
- package/src/utils/command-utils.ts +23 -0
- package/src/utils/file-locking.ts +144 -0
- package/src/data/alias-flags.js +0 -196
- package/src/data/cli-tools.js +0 -123
- package/src/data/gitignore-defaults.js +0 -24
- package/src/data/gitignore-reasons.js +0 -97
- package/src/data/gitignore-templates.js +0 -21
- package/src/data/marketplaces.js +0 -138
- package/src/data/mcp-servers.js +0 -509
- package/src/data/predefined-profiles.js +0 -248
- package/src/data/settings-catalog.js +0 -625
- package/src/data/skill-repos.js +0 -160
- package/src/data/statuslines.js +0 -159
- package/src/data/statuslines.ts +0 -188
- package/src/index.js +0 -4
- package/src/index.ts +0 -5
- package/src/main.js +0 -170
- package/src/prerunner/index.js +0 -252
- package/src/services/alias-settings.js +0 -51
- package/src/services/alias-shell-writer.js +0 -764
- package/src/services/alias-store.js +0 -77
- package/src/services/claude-cli.js +0 -189
- package/src/services/claude-runner.js +0 -28
- package/src/services/claude-settings.js +0 -1223
- package/src/services/gitignore-detector.js +0 -155
- package/src/services/gitignore-fixer.js +0 -231
- package/src/services/gitignore-prerun.js +0 -46
- package/src/services/gitignore-resolver.js +0 -143
- package/src/services/gitignore-service.js +0 -117
- package/src/services/local-marketplace.js +0 -339
- package/src/services/marketplace-fetcher.js +0 -96
- package/src/services/marketplace-sync.js +0 -94
- package/src/services/mcp-registry.js +0 -87
- package/src/services/plugin-manager.js +0 -473
- package/src/services/plugin-mcp-config.js +0 -177
- package/src/services/plugin-setup.js +0 -444
- package/src/services/plugin-version-check.js +0 -262
- package/src/services/profiles.js +0 -161
- package/src/services/settings-manager.js +0 -243
- package/src/services/skills-manager.js +0 -393
- package/src/services/skillsmp-client.js +0 -87
- package/src/services/update-cache.js +0 -52
- package/src/services/version-check.js +0 -99
- package/src/types/gitignore.js +0 -6
- package/src/types/index.js +0 -1
- package/src/ui/App.js +0 -346
- package/src/ui/adapters/pluginsAdapter.js +0 -139
- package/src/ui/adapters/settingsAdapter.js +0 -111
- package/src/ui/adapters/skillsAdapter.js +0 -154
- package/src/ui/components/CategoryHeader.js +0 -9
- package/src/ui/components/EmptyFilterState.js +0 -4
- package/src/ui/components/ScopeIndicator.js +0 -30
- package/src/ui/components/ScrollableList.js +0 -36
- package/src/ui/components/SearchInput.js +0 -19
- package/src/ui/components/StyledText.js +0 -39
- package/src/ui/components/TabBar.js +0 -19
- package/src/ui/components/layout/FooterHints.js +0 -29
- package/src/ui/components/layout/Panel.js +0 -6
- package/src/ui/components/layout/ProgressBar.js +0 -14
- package/src/ui/components/layout/ScopeTabs.js +0 -6
- package/src/ui/components/layout/ScreenLayout.js +0 -16
- package/src/ui/components/layout/index.js +0 -5
- package/src/ui/components/modals/ConfirmModal.js +0 -14
- package/src/ui/components/modals/InputModal.js +0 -5
- package/src/ui/components/modals/LoadingModal.js +0 -17
- package/src/ui/components/modals/MessageModal.js +0 -16
- package/src/ui/components/modals/ModalContainer.js +0 -137
- package/src/ui/components/modals/SelectModal.js +0 -18
- package/src/ui/components/modals/VersionMismatchModal.js +0 -36
- package/src/ui/components/modals/index.js +0 -6
- package/src/ui/components/primitives/ActionHints.js +0 -13
- package/src/ui/components/primitives/DetailSection.js +0 -7
- package/src/ui/components/primitives/KeyValueLine.js +0 -8
- package/src/ui/components/primitives/ListCategoryRow.js +0 -8
- package/src/ui/components/primitives/MetaText.js +0 -8
- package/src/ui/components/primitives/ScopeDetail.js +0 -32
- package/src/ui/components/primitives/ScopeSquares.js +0 -11
- package/src/ui/components/primitives/SelectableRow.js +0 -5
- package/src/ui/components/primitives/index.js +0 -8
- package/src/ui/hooks/index.js +0 -4
- package/src/ui/hooks/useAsyncData.js +0 -77
- package/src/ui/hooks/useGitignoreModal.js +0 -74
- package/src/ui/hooks/useKeyboard.js +0 -13
- package/src/ui/hooks/useKeyboardHandler.js +0 -39
- package/src/ui/hooks/useMismatchModal.js +0 -77
- package/src/ui/registry.js +0 -1
- package/src/ui/renderers/cliToolRenderers.js +0 -54
- package/src/ui/renderers/gitignoreRenderers.js +0 -46
- package/src/ui/renderers/mcpRenderers.js +0 -26
- package/src/ui/renderers/pluginRenderers.js +0 -124
- package/src/ui/renderers/profileRenderers.js +0 -177
- package/src/ui/renderers/settingsRenderers.js +0 -73
- package/src/ui/renderers/skillRenderers.js +0 -138
- package/src/ui/screens/AliasScreen.js +0 -1008
- package/src/ui/screens/CliToolsScreen.js +0 -338
- package/src/ui/screens/EnvVarsScreen.js +0 -152
- package/src/ui/screens/GitignoreScreen.js +0 -328
- package/src/ui/screens/McpRegistryScreen.js +0 -238
- package/src/ui/screens/McpScreen.js +0 -176
- package/src/ui/screens/ModelSelectorScreen.js +0 -296
- package/src/ui/screens/ModelSelectorScreen.tsx +0 -444
- package/src/ui/screens/PluginsScreen.js +0 -766
- package/src/ui/screens/ProfilesScreen.js +0 -298
- package/src/ui/screens/SkillsScreen.js +0 -549
- package/src/ui/screens/StatusLineScreen.js +0 -206
- package/src/ui/screens/StatusLineScreen.tsx +0 -416
- package/src/ui/screens/index.js +0 -10
- package/src/ui/state/AnimationContext.js +0 -34
- package/src/ui/state/AppContext.js +0 -162
- package/src/ui/state/DimensionsContext.js +0 -71
- package/src/ui/state/reducer.js +0 -543
- package/src/ui/state/types.js +0 -1
- package/src/ui/theme.js +0 -47
- package/src/utils/clipboard.js +0 -56
- package/src/utils/command-utils.js +0 -19
- package/src/utils/fuzzy-search.js +0 -101
- package/src/utils/string-utils.js +0 -62
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
tokenizePosixAliasBody,
|
|
7
7
|
argsToFlagValues,
|
|
8
8
|
renderAlias,
|
|
9
|
+
renderArgsAsTokens,
|
|
9
10
|
} from "../services/alias-shell-writer";
|
|
10
11
|
import { defaultAliasConfig } from "../services/alias-store";
|
|
11
12
|
|
|
@@ -295,6 +296,97 @@ describe("round-trip: render → parse → enabled subset matches", () => {
|
|
|
295
296
|
});
|
|
296
297
|
});
|
|
297
298
|
|
|
299
|
+
describe("channels: render-time union + parse-time provenance split", () => {
|
|
300
|
+
// The model: channels.values holds OWN channels only; dev-load values are
|
|
301
|
+
// unioned into --channels at render time and subtracted back out on parse,
|
|
302
|
+
// so the two lists stay distinct in storage but merge on the command line.
|
|
303
|
+
|
|
304
|
+
it("emits --channels as (own ∪ enabled dev-load), deduped, own-first", () => {
|
|
305
|
+
const c = defaultAliasConfig();
|
|
306
|
+
c.flags["channels"] = {
|
|
307
|
+
kind: "text-list",
|
|
308
|
+
enabled: true,
|
|
309
|
+
values: ["plugin:own@m"],
|
|
310
|
+
};
|
|
311
|
+
c.flags["dangerously-load-development-channels"] = {
|
|
312
|
+
kind: "text-list",
|
|
313
|
+
enabled: true,
|
|
314
|
+
values: ["plugin:dev@m", "plugin:own@m"], // overlap dedupes
|
|
315
|
+
};
|
|
316
|
+
const tokens = renderArgsAsTokens(c);
|
|
317
|
+
// --channels carries own + derived (deduped); dev-load still emits its own.
|
|
318
|
+
const channelsArgs = tokens
|
|
319
|
+
.map((t, i) => (tokens[i - 1] === "--channels" ? t : null))
|
|
320
|
+
.filter(Boolean);
|
|
321
|
+
expect(channelsArgs).toEqual(["plugin:own@m", "plugin:dev@m"]);
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
it("forces --channels to emit derived values even when own channels is OFF", () => {
|
|
325
|
+
const c = defaultAliasConfig();
|
|
326
|
+
c.flags["channels"] = { kind: "text-list", enabled: false, values: [] };
|
|
327
|
+
c.flags["dangerously-load-development-channels"] = {
|
|
328
|
+
kind: "text-list",
|
|
329
|
+
enabled: true,
|
|
330
|
+
values: ["plugin:dev@m"],
|
|
331
|
+
};
|
|
332
|
+
const tokens = renderArgsAsTokens(c);
|
|
333
|
+
expect(tokens).toContain("--channels");
|
|
334
|
+
const idx = tokens.indexOf("--channels");
|
|
335
|
+
expect(tokens[idx + 1]).toBe("plugin:dev@m");
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
it("does NOT emit --channels when both own and dev-load are empty/off", () => {
|
|
339
|
+
const c = defaultAliasConfig(); // both channels + dev-load default off
|
|
340
|
+
expect(renderArgsAsTokens(c)).not.toContain("--channels");
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
it("round-trips: derived values do NOT leak into parsed channels.values", () => {
|
|
344
|
+
const c = defaultAliasConfig();
|
|
345
|
+
c.aliasName = "c";
|
|
346
|
+
c.flags["channels"] = {
|
|
347
|
+
kind: "text-list",
|
|
348
|
+
enabled: true,
|
|
349
|
+
values: ["plugin:own@m"],
|
|
350
|
+
};
|
|
351
|
+
c.flags["dangerously-load-development-channels"] = {
|
|
352
|
+
kind: "text-list",
|
|
353
|
+
enabled: true,
|
|
354
|
+
values: ["plugin:dev@m"],
|
|
355
|
+
};
|
|
356
|
+
const rendered = renderAlias(c, "zsh").block;
|
|
357
|
+
const parsed = parseAliasFromRc(rendered);
|
|
358
|
+
// channels.values comes back as OWN ONLY — derived was subtracted.
|
|
359
|
+
expect(parsed!.flags["channels"]).toEqual({
|
|
360
|
+
kind: "text-list",
|
|
361
|
+
enabled: true,
|
|
362
|
+
values: ["plugin:own@m"],
|
|
363
|
+
});
|
|
364
|
+
// dev-load round-trips intact (it's the source of truth for derived).
|
|
365
|
+
expect(parsed!.flags["dangerously-load-development-channels"]).toEqual({
|
|
366
|
+
kind: "text-list",
|
|
367
|
+
enabled: true,
|
|
368
|
+
values: ["plugin:dev@m"],
|
|
369
|
+
});
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
it("round-trips a purely-derived channels list to disabled+empty own", () => {
|
|
373
|
+
const c = defaultAliasConfig();
|
|
374
|
+
c.aliasName = "c";
|
|
375
|
+
// No own channels; only dev-load.
|
|
376
|
+
c.flags["dangerously-load-development-channels"] = {
|
|
377
|
+
kind: "text-list",
|
|
378
|
+
enabled: true,
|
|
379
|
+
values: ["plugin:dev@m"],
|
|
380
|
+
};
|
|
381
|
+
const parsed = parseAliasFromRc(renderAlias(c, "zsh").block);
|
|
382
|
+
expect(parsed!.flags["channels"]).toEqual({
|
|
383
|
+
kind: "text-list",
|
|
384
|
+
enabled: false,
|
|
385
|
+
values: [],
|
|
386
|
+
});
|
|
387
|
+
});
|
|
388
|
+
});
|
|
389
|
+
|
|
298
390
|
describe("parseAliasFromRc — entry point", () => {
|
|
299
391
|
it("returns null on text with no managed block", () => {
|
|
300
392
|
expect(parseAliasFromRc("export PATH=...\n")).toBeNull();
|
|
@@ -92,7 +92,14 @@ describe("renderArgs", () => {
|
|
|
92
92
|
enabled: true,
|
|
93
93
|
values: ["plugin:claudish@magus", "plugin:dev@magus"],
|
|
94
94
|
};
|
|
95
|
+
// Enabling dev-load also forces --channels to emit (the render-time union),
|
|
96
|
+
// since a dev-loaded channel must be on the channels list to run. --channels
|
|
97
|
+
// comes first (catalog order), then the repeated dev-load pairs.
|
|
95
98
|
expect(renderArgs(c)).toEqual([
|
|
99
|
+
"--channels",
|
|
100
|
+
"plugin:claudish@magus",
|
|
101
|
+
"--channels",
|
|
102
|
+
"plugin:dev@magus",
|
|
96
103
|
"--dangerously-load-development-channels",
|
|
97
104
|
"plugin:claudish@magus",
|
|
98
105
|
"--dangerously-load-development-channels",
|
|
@@ -3,10 +3,20 @@ import {
|
|
|
3
3
|
defaultAliasConfig,
|
|
4
4
|
defaultValueFor,
|
|
5
5
|
validateAliasName,
|
|
6
|
+
derivedChannelValues,
|
|
7
|
+
withoutDerivedChannel,
|
|
8
|
+
listValues,
|
|
9
|
+
CHANNELS_FLAG_ID,
|
|
10
|
+
DEVLOAD_FLAG_ID,
|
|
6
11
|
DEFAULT_ALIAS_NAME,
|
|
12
|
+
type FlagValue,
|
|
7
13
|
} from "../services/alias-store";
|
|
8
14
|
import { ALIAS_FLAGS } from "../data/alias-flags";
|
|
9
15
|
|
|
16
|
+
function textList(values: string[], enabled = true): FlagValue {
|
|
17
|
+
return { kind: "text-list", enabled, values };
|
|
18
|
+
}
|
|
19
|
+
|
|
10
20
|
describe("defaultAliasConfig", () => {
|
|
11
21
|
it("seeds every known flag with a sane default and the default alias name", () => {
|
|
12
22
|
const c = defaultAliasConfig();
|
|
@@ -64,6 +74,73 @@ describe("defaultValueFor", () => {
|
|
|
64
74
|
});
|
|
65
75
|
});
|
|
66
76
|
|
|
77
|
+
describe("derivedChannelValues (derive-on-display model)", () => {
|
|
78
|
+
it("returns dev-load values when the dev-load flag is enabled", () => {
|
|
79
|
+
const flags: Record<string, FlagValue> = {
|
|
80
|
+
[DEVLOAD_FLAG_ID]: textList(["plugin:claudish@magus"], true),
|
|
81
|
+
[CHANNELS_FLAG_ID]: textList([], false),
|
|
82
|
+
};
|
|
83
|
+
expect(derivedChannelValues(flags)).toEqual(["plugin:claudish@magus"]);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("returns nothing when the dev-load flag is DISABLED", () => {
|
|
87
|
+
const flags: Record<string, FlagValue> = {
|
|
88
|
+
[DEVLOAD_FLAG_ID]: textList(["plugin:claudish@magus"], false),
|
|
89
|
+
[CHANNELS_FLAG_ID]: textList([], false),
|
|
90
|
+
};
|
|
91
|
+
expect(derivedChannelValues(flags)).toEqual([]);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("returns nothing when the dev-load flag is absent", () => {
|
|
95
|
+
expect(derivedChannelValues({})).toEqual([]);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("does NOT read from channels.values (provenance stays separate)", () => {
|
|
99
|
+
// channels holds OWN values; dev-load is the only derived source.
|
|
100
|
+
const flags: Record<string, FlagValue> = {
|
|
101
|
+
[CHANNELS_FLAG_ID]: textList(["plugin:own@m"], true),
|
|
102
|
+
[DEVLOAD_FLAG_ID]: textList(["plugin:dev@m"], true),
|
|
103
|
+
};
|
|
104
|
+
expect(derivedChannelValues(flags)).toEqual(["plugin:dev@m"]);
|
|
105
|
+
// channels.values is untouched — own only.
|
|
106
|
+
expect(listValues(flags[CHANNELS_FLAG_ID])).toEqual(["plugin:own@m"]);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
describe("withoutDerivedChannel (delete-through to dev-load)", () => {
|
|
111
|
+
it("removes the value from dev-load, NOT from channels.values", () => {
|
|
112
|
+
const flags: Record<string, FlagValue> = {
|
|
113
|
+
[CHANNELS_FLAG_ID]: textList(["plugin:own@m"], true),
|
|
114
|
+
[DEVLOAD_FLAG_ID]: textList(["plugin:dev@m", "plugin:keep@m"], true),
|
|
115
|
+
};
|
|
116
|
+
const next = withoutDerivedChannel(flags, "plugin:dev@m");
|
|
117
|
+
expect(listValues(next[DEVLOAD_FLAG_ID])).toEqual(["plugin:keep@m"]);
|
|
118
|
+
// channels.values untouched — the deletion landed on the source flag.
|
|
119
|
+
expect(listValues(next[CHANNELS_FLAG_ID])).toEqual(["plugin:own@m"]);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("disables dev-load when its last value is removed", () => {
|
|
123
|
+
const flags: Record<string, FlagValue> = {
|
|
124
|
+
[DEVLOAD_FLAG_ID]: textList(["plugin:only@m"], true),
|
|
125
|
+
};
|
|
126
|
+
const next = withoutDerivedChannel(flags, "plugin:only@m");
|
|
127
|
+
expect(next[DEVLOAD_FLAG_ID]).toEqual({
|
|
128
|
+
kind: "text-list",
|
|
129
|
+
enabled: false,
|
|
130
|
+
values: [],
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("is a no-op when the value isn't present", () => {
|
|
135
|
+
const flags: Record<string, FlagValue> = {
|
|
136
|
+
[DEVLOAD_FLAG_ID]: textList(["plugin:a@m"], true),
|
|
137
|
+
};
|
|
138
|
+
expect(listValues(withoutDerivedChannel(flags, "nope")[DEVLOAD_FLAG_ID])).toEqual([
|
|
139
|
+
"plugin:a@m",
|
|
140
|
+
]);
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
67
144
|
describe("validateAliasName", () => {
|
|
68
145
|
it("accepts safe identifiers", () => {
|
|
69
146
|
expect(validateAliasName("c")).toBeNull();
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
|
+
import { route } from "../cli/router.js";
|
|
3
|
+
|
|
4
|
+
// Silence the help banner and capture what the router prints, without
|
|
5
|
+
// touching the network (--version) or spawning processes (claude/update).
|
|
6
|
+
let logSpy: ReturnType<typeof mock>;
|
|
7
|
+
let errSpy: ReturnType<typeof mock>;
|
|
8
|
+
let logs: string[];
|
|
9
|
+
let errs: string[];
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
logs = [];
|
|
13
|
+
errs = [];
|
|
14
|
+
logSpy = mock((...a: unknown[]) => {
|
|
15
|
+
logs.push(a.join(" "));
|
|
16
|
+
});
|
|
17
|
+
errSpy = mock((...a: unknown[]) => {
|
|
18
|
+
errs.push(a.join(" "));
|
|
19
|
+
});
|
|
20
|
+
// biome-ignore lint/suspicious/noExplicitAny: test double
|
|
21
|
+
console.log = logSpy as any;
|
|
22
|
+
// biome-ignore lint/suspicious/noExplicitAny: test double
|
|
23
|
+
console.error = errSpy as any;
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
afterEach(() => {
|
|
27
|
+
mock.restore();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe("route() dispatch", () => {
|
|
31
|
+
test("bare invocation falls through to the TUI", async () => {
|
|
32
|
+
const r = await route([], "1.2.3");
|
|
33
|
+
expect(r.handled).toBe(false);
|
|
34
|
+
expect(r.launchTui).toBe(true);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("an unrecognized token falls through to the TUI", async () => {
|
|
38
|
+
const r = await route(["totally-unknown"], "1.2.3");
|
|
39
|
+
expect(r.handled).toBe(false);
|
|
40
|
+
expect(r.launchTui).toBe(true);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("--help is handled, exits 0, and does not launch the TUI", async () => {
|
|
44
|
+
const r = await route(["--help"], "9.9.9");
|
|
45
|
+
expect(r).toEqual({ handled: true, exitCode: 0, launchTui: false });
|
|
46
|
+
expect(logs.join("\n")).toContain("claudeup v9.9.9");
|
|
47
|
+
// The corrected help must not carry the stale tab names.
|
|
48
|
+
expect(logs.join("\n")).not.toContain("[3] Status");
|
|
49
|
+
expect(logs.join("\n")).toContain("[3] MCP");
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("-h is treated the same as --help", async () => {
|
|
53
|
+
const r = await route(["-h"], "1.0.0");
|
|
54
|
+
expect(r.handled).toBe(true);
|
|
55
|
+
expect(r.exitCode).toBe(0);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("install, profile, and doctor are routed to real handlers (not stubs)", async () => {
|
|
59
|
+
// The router maps these to their real command handlers, which are covered
|
|
60
|
+
// by their services' unit tests. We don't invoke them here to avoid
|
|
61
|
+
// touching the filesystem / running global installs.
|
|
62
|
+
const mod = await import("../cli/router.js");
|
|
63
|
+
expect(typeof mod.route).toBe("function");
|
|
64
|
+
});
|
|
65
|
+
});
|