argsbarg 3.3.6 → 3.3.8
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/CHANGELOG.md +19 -1
- package/README.md +2 -2
- package/docs/cli-program.md +1 -1
- package/docs/install.md +3 -4
- package/index.d.ts +2 -2
- package/package.json +1 -1
- package/src/builtins/dispatch.ts +0 -27
- package/src/builtins/export.ts +0 -4
- package/src/builtins/install.ts +10 -1
- package/src/builtins/presentation.ts +0 -4
- package/src/capabilities.ts +0 -3
- package/src/docs/mcp-guide.ts +1 -1
- package/src/install/gh-release-update.ts +1 -1
- package/src/install/index.ts +43 -18
- package/src/install/plan.ts +1 -0
- package/src/install/update.test.ts +14 -12
- package/src/install/update.ts +1 -1
- package/src/invoke.ts +14 -3
- package/src/runtime.ts +0 -7
- package/src/schema.ts +1 -1
- package/src/types.ts +2 -2
- package/src/builtins/update.ts +0 -14
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [3.3.8] - 2026-06-21
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- **`install --update`** — downloads the latest release and reinstalls installed artifacts when `install.updateGetLatest` is set. Replaces the top-level `update` command and the old `--update` alias for `--reinstall`.
|
|
15
|
+
|
|
16
|
+
### Removed
|
|
17
|
+
|
|
18
|
+
- **`update` built-in** — use `myapp install --update` instead.
|
|
19
|
+
|
|
20
|
+
## [3.3.7] - 2026-06-21
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
|
|
24
|
+
- **`docs mcp`** — intro copy is user-facing (`exposes an MCP server with features similar to the CLI`) instead of describing argsbarg internals.
|
|
25
|
+
|
|
10
26
|
## [3.3.6] - 2026-06-21
|
|
11
27
|
|
|
12
28
|
### Added
|
|
@@ -282,7 +298,9 @@ const cli = { ... } satisfies CliProgram; // or : CliProgram
|
|
|
282
298
|
- Migrate schemas: rename every `children` property to **`commands`**; move positional definitions to **`CliPositional`** objects on `positionals` and strip `positional` / `argMin` / `argMax` from flag definitions under `options` (flags only carry `name`, `description`, `kind`, and optional `shortName`).
|
|
283
299
|
- Imports: use `CliPositional` where needed; replace `CliOptionDef` with `CliOption` or `CliPositional` as appropriate.
|
|
284
300
|
|
|
285
|
-
[Unreleased]: https://github.com/bdombro/bun-argsbarg/compare/v3.3.
|
|
301
|
+
[Unreleased]: https://github.com/bdombro/bun-argsbarg/compare/v3.3.8...HEAD
|
|
302
|
+
[3.3.8]: https://github.com/bdombro/bun-argsbarg/releases/tag/v3.3.8
|
|
303
|
+
[3.3.7]: https://github.com/bdombro/bun-argsbarg/releases/tag/v3.3.7
|
|
286
304
|
[3.3.6]: https://github.com/bdombro/bun-argsbarg/releases/tag/v3.3.6
|
|
287
305
|
[3.3.5]: https://github.com/bdombro/bun-argsbarg/releases/tag/v3.3.5
|
|
288
306
|
[3.3.4]: https://github.com/bdombro/bun-argsbarg/releases/tag/v3.3.4
|
package/README.md
CHANGED
|
@@ -122,7 +122,7 @@ myapp install --all --yes
|
|
|
122
122
|
|
|
123
123
|
This copies the binary to `~/.local/bin`, installs shell completions (bash/zsh/fish when each shell is on PATH), writes Cursor/Claude skills when agent directories exist, and merges MCP server entries into Cursor and Claude config files.
|
|
124
124
|
|
|
125
|
-
See **[docs/install.md](docs/install.md)** for `--reinstall`, `update`, `--status`, `--uninstall`, and flags.
|
|
125
|
+
See **[docs/install.md](docs/install.md)** for `--reinstall`, `install --update`, `--status`, `--uninstall`, and flags.
|
|
126
126
|
|
|
127
127
|
|
|
128
128
|
### Shell completions
|
|
@@ -187,7 +187,7 @@ Add `CliPositional` entries to the command’s `positionals` list (separate from
|
|
|
187
187
|
|
|
188
188
|
### Capabilities (built-ins)
|
|
189
189
|
|
|
190
|
-
`completion`, `version`, `install`,
|
|
190
|
+
`completion`, `version`, `install`, and `mcp` are not part of your schema — they are injected at runtime from program-level config (`mcpServer`, `install`, `docs`). Reserved command names: `completion` and `version` always; `install` unless `install.enabled: false`; `mcp` when `mcpServer.enabled` is `true`; `docs` when `docs.enabled` is `true`. When `install.updateGetLatest` is set, `install --update` is available (not a separate command).
|
|
191
191
|
|
|
192
192
|
|
|
193
193
|
|
package/docs/cli-program.md
CHANGED
|
@@ -196,7 +196,7 @@ Basic synchronous handlers do not need this structure — only commands with an
|
|
|
196
196
|
|
|
197
197
|
## Reserved names
|
|
198
198
|
|
|
199
|
-
Do not declare user commands named `completion`, `install`, `mcp`, `version`,
|
|
199
|
+
Do not declare user commands named `completion`, `install`, `mcp`, `version`, or `docs` at the root — ArgsBarg injects these when configured.
|
|
200
200
|
|
|
201
201
|
## Cursor rule for consumer repos
|
|
202
202
|
|
package/docs/install.md
CHANGED
|
@@ -12,7 +12,7 @@ myapp install --all --yes
|
|
|
12
12
|
myapp install --reinstall
|
|
13
13
|
|
|
14
14
|
# Download latest release (when install.updateGetLatest is configured)
|
|
15
|
-
myapp update
|
|
15
|
+
myapp install --update
|
|
16
16
|
|
|
17
17
|
# See what is installed
|
|
18
18
|
myapp install --status
|
|
@@ -54,7 +54,7 @@ install: {
|
|
|
54
54
|
}
|
|
55
55
|
```
|
|
56
56
|
|
|
57
|
-
When `updateGetLatest` is set, ArgsBarg
|
|
57
|
+
When `updateGetLatest` is set, ArgsBarg adds **`install --update`** (download latest release and reinstall installed artifacts).
|
|
58
58
|
|
|
59
59
|
### GitHub releases (`ghReleaseUpdateGetLatest`)
|
|
60
60
|
|
|
@@ -105,12 +105,11 @@ Environment:
|
|
|
105
105
|
| `--quiet` | Suppress summaries and per-step messages (requires `--yes`) |
|
|
106
106
|
| `--prefix <dir>` | Override binary install directory |
|
|
107
107
|
| `--reinstall` | Reinstall artifacts already on disk (implies `--bin` + `--yes`) |
|
|
108
|
+
| `--update` | Download latest release and reinstall installed artifacts (requires `install.updateGetLatest`; implies `--yes`) |
|
|
108
109
|
| `--from <path>` | Binary to copy with `--reinstall` (default: running executable) |
|
|
109
110
|
| `--status` | Read-only inventory |
|
|
110
111
|
| `--uninstall` | Remove artifacts in scope (`--all`, `--bin`, `--completions`, `--skill`, `--mcp`); skips targets not installed |
|
|
111
112
|
|
|
112
|
-
`--update` is accepted as a deprecated alias for `--reinstall`.
|
|
113
|
-
|
|
114
113
|
## MCP merge behavior
|
|
115
114
|
|
|
116
115
|
When `--mcp` runs, entries are merged into `mcpServers[<sanitized-key>]` with:
|
package/index.d.ts
CHANGED
|
@@ -175,7 +175,7 @@ export interface CliUpdateArtifact {
|
|
|
175
175
|
/** Called after reinstall completes (e.g. remove a temp download directory). */
|
|
176
176
|
cleanup?: () => void | Promise<void>;
|
|
177
177
|
}
|
|
178
|
-
/** Fetches the latest release binary for
|
|
178
|
+
/** Fetches the latest release binary for `install --update`. */
|
|
179
179
|
export type CliUpdateGetLatest = (ctx: {
|
|
180
180
|
version: string;
|
|
181
181
|
}) => Promise<CliUpdateArtifact>;
|
|
@@ -185,7 +185,7 @@ export interface CliInstallConfig {
|
|
|
185
185
|
/** Default bin directory (default: `~/.local/bin`). Overridden by `INSTALL_PREFIX` env and `--prefix`. */
|
|
186
186
|
prefix?: string;
|
|
187
187
|
/**
|
|
188
|
-
* When set, enables
|
|
188
|
+
* When set, enables `install --update` on the program root.
|
|
189
189
|
* Should download or locate the latest release binary and return its path.
|
|
190
190
|
*/
|
|
191
191
|
updateGetLatest?: CliUpdateGetLatest;
|
package/package.json
CHANGED
package/src/builtins/dispatch.ts
CHANGED
|
@@ -6,14 +6,12 @@ import { completionFishScript } from "./completion-fish.ts";
|
|
|
6
6
|
import { completionZshScript } from "./completion-zsh.ts";
|
|
7
7
|
import { cliBuiltinInstallCommand } from "./install.ts";
|
|
8
8
|
import { cliBuiltinMcpCommand } from "./mcp.ts";
|
|
9
|
-
import { cliBuiltinUpdateCommand } from "./update.ts";
|
|
10
9
|
import { cliBuiltinVersionCommand } from "./version.ts";
|
|
11
10
|
import { cliBuiltinCompletionGroup as completionGroup } from "./completion-group.ts";
|
|
12
11
|
import { cliPresentationRoot } from "./presentation.ts";
|
|
13
12
|
import { cliBuiltinDocsGroupIfEnabled } from "../docs/builtin.ts";
|
|
14
13
|
import { cliMcpServeStdio } from "../mcp.ts";
|
|
15
14
|
import { cliInstall } from "../install/index.ts";
|
|
16
|
-
import { cliUpdate } from "../install/update.ts";
|
|
17
15
|
import type { ParseResult } from "../parse.ts";
|
|
18
16
|
import { ParseKind } from "../parse.ts";
|
|
19
17
|
|
|
@@ -82,20 +80,6 @@ export async function dispatchBuiltin(
|
|
|
82
80
|
process.exit(0);
|
|
83
81
|
}
|
|
84
82
|
|
|
85
|
-
if (pr.path[0] === "update") {
|
|
86
|
-
if (!caps.update) {
|
|
87
|
-
process.stderr.write(
|
|
88
|
-
"update is not enabled. Set install.updateGetLatest on the program root.\n",
|
|
89
|
-
);
|
|
90
|
-
process.exit(1);
|
|
91
|
-
}
|
|
92
|
-
if (pr.path.length !== 1) {
|
|
93
|
-
process.stderr.write("Unknown subcommand: update " + pr.path.slice(1).join(" ") + "\n");
|
|
94
|
-
process.exit(1);
|
|
95
|
-
}
|
|
96
|
-
await cliUpdate(program);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
83
|
if (pr.path[0] === "install") {
|
|
100
84
|
if (!caps.install) {
|
|
101
85
|
process.stderr.write("install is disabled. Remove install.enabled: false from the program root.\n");
|
|
@@ -143,17 +127,6 @@ export function builtinInterceptRoot(
|
|
|
143
127
|
};
|
|
144
128
|
}
|
|
145
129
|
|
|
146
|
-
if (first === "update" && caps.update) {
|
|
147
|
-
return {
|
|
148
|
-
parseRoot: {
|
|
149
|
-
key: program.key,
|
|
150
|
-
description: program.description,
|
|
151
|
-
commands: [cliBuiltinUpdateCommand(program)],
|
|
152
|
-
},
|
|
153
|
-
isLeafCompletionIntercept: false,
|
|
154
|
-
};
|
|
155
|
-
}
|
|
156
|
-
|
|
157
130
|
if (first === "mcp" && caps.mcp) {
|
|
158
131
|
return {
|
|
159
132
|
parseRoot: {
|
package/src/builtins/export.ts
CHANGED
|
@@ -3,7 +3,6 @@ import type { CliFallbackMode, CliOption, CliPositional, CliProgram } from "../t
|
|
|
3
3
|
import { cliBuiltinCompletionGroup } from "./completion-group.ts";
|
|
4
4
|
import { cliBuiltinInstallCommand } from "./install.ts";
|
|
5
5
|
import { cliBuiltinMcpCommand } from "./mcp.ts";
|
|
6
|
-
import { cliBuiltinUpdateCommand } from "./update.ts";
|
|
7
6
|
import { cliBuiltinVersionCommand } from "./version.ts";
|
|
8
7
|
import { cliBuiltinDocsGroupIfEnabled } from "../docs/builtin.ts";
|
|
9
8
|
|
|
@@ -52,9 +51,6 @@ export function exportPresentationBuiltins(program: CliProgram, caps?: CliCapabi
|
|
|
52
51
|
if (resolved.install) {
|
|
53
52
|
builtins.push(exportBuiltinNode(cliBuiltinInstallCommand(program)));
|
|
54
53
|
}
|
|
55
|
-
if (resolved.update) {
|
|
56
|
-
builtins.push(exportBuiltinNode(cliBuiltinUpdateCommand(program)));
|
|
57
|
-
}
|
|
58
54
|
const docsGroup = cliBuiltinDocsGroupIfEnabled(program);
|
|
59
55
|
if (docsGroup) {
|
|
60
56
|
builtins.push(exportBuiltinNode(docsGroup));
|
package/src/builtins/install.ts
CHANGED
|
@@ -80,6 +80,15 @@ export function installBuiltinOptions(root: CliProgram): CliOption[] {
|
|
|
80
80
|
});
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
if (resolveCapabilities(root).update) {
|
|
84
|
+
const statusIdx = opts.findIndex((o) => o.name === "status");
|
|
85
|
+
opts.splice(statusIdx, 0, {
|
|
86
|
+
name: "update",
|
|
87
|
+
description: "Download and install the latest release.",
|
|
88
|
+
kind: CliOptionKind.Presence,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
83
92
|
return opts;
|
|
84
93
|
}
|
|
85
94
|
|
|
@@ -94,7 +103,7 @@ export function cliBuiltinInstallCommand(root: CliProgram): CliLeaf {
|
|
|
94
103
|
` ${app} install --all --yes\n\n` +
|
|
95
104
|
"Refresh after upgrading:\n" +
|
|
96
105
|
` ${app} install --reinstall\n` +
|
|
97
|
-
` ${app} update\n\n` +
|
|
106
|
+
` ${app} install --update\n\n` +
|
|
98
107
|
"See what is installed:\n" +
|
|
99
108
|
` ${app} install --status\n\n` +
|
|
100
109
|
"Remove everything installed with --all:\n" +
|
|
@@ -5,7 +5,6 @@ import { isCliLeaf, isCliRouter } from "../types.ts";
|
|
|
5
5
|
import { cliBuiltinCompletionGroup } from "./completion-group.ts";
|
|
6
6
|
import { cliBuiltinInstallCommand } from "./install.ts";
|
|
7
7
|
import { cliBuiltinMcpCommand } from "./mcp.ts";
|
|
8
|
-
import { cliBuiltinUpdateCommand } from "./update.ts";
|
|
9
8
|
import { cliBuiltinVersionCommand } from "./version.ts";
|
|
10
9
|
import { cliBuiltinDocsGroupIfEnabled } from "../docs/builtin.ts";
|
|
11
10
|
|
|
@@ -18,9 +17,6 @@ export function presentationBuiltins(program: CliProgram, caps: CliCapabilities)
|
|
|
18
17
|
if (caps.install) {
|
|
19
18
|
builtins.push(cliBuiltinInstallCommand(program));
|
|
20
19
|
}
|
|
21
|
-
if (caps.update) {
|
|
22
|
-
builtins.push(cliBuiltinUpdateCommand(program));
|
|
23
|
-
}
|
|
24
20
|
const docsGroup = cliBuiltinDocsGroupIfEnabled(program);
|
|
25
21
|
if (docsGroup) {
|
|
26
22
|
builtins.push(docsGroup);
|
package/src/capabilities.ts
CHANGED
package/src/docs/mcp-guide.ts
CHANGED
|
@@ -29,7 +29,7 @@ export function generateMcpGuide(root: CliProgram): string {
|
|
|
29
29
|
const lines: string[] = [
|
|
30
30
|
`# MCP server (${root.key})`,
|
|
31
31
|
"",
|
|
32
|
-
`${root.key} exposes an MCP server
|
|
32
|
+
`${root.key} exposes an MCP server with features similar to the CLI.`,
|
|
33
33
|
"",
|
|
34
34
|
"## Quick start",
|
|
35
35
|
"",
|
|
@@ -101,7 +101,7 @@ export function createGhVersionCheck(config: GhVersionCheckConfig): {
|
|
|
101
101
|
if (cached === null || isAlreadyCurrent(config.currentVersion, cached.latest)) {
|
|
102
102
|
return null;
|
|
103
103
|
}
|
|
104
|
-
return `Update available: v${cached.latest} (you have v${config.currentVersion}). Run \`${config.commandName} update\``;
|
|
104
|
+
return `Update available: v${cached.latest} (you have v${config.currentVersion}). Run \`${config.commandName} install --update\``;
|
|
105
105
|
},
|
|
106
106
|
|
|
107
107
|
refreshIfStale(): void {
|
package/src/install/index.ts
CHANGED
|
@@ -12,17 +12,18 @@ import {
|
|
|
12
12
|
import { resolveInstallPaths } from "./paths.ts";
|
|
13
13
|
import { installErr, installInfo, installOut, printInstallStatus } from "./status.ts";
|
|
14
14
|
import { buildUninstallPlan, uninstallSkillDir, type UninstallAction } from "./uninstall.ts";
|
|
15
|
+
import { cliUpdate } from "./update.ts";
|
|
15
16
|
|
|
16
17
|
export function parseInstallOpts(raw: Record<string, string>): InstallOpts {
|
|
17
18
|
const flag = (name: string) => raw[name] === "1";
|
|
18
|
-
const reinstall = flag("reinstall") || flag("update");
|
|
19
19
|
return {
|
|
20
20
|
all: flag("all"),
|
|
21
21
|
bin: flag("bin"),
|
|
22
22
|
completions: flag("completions"),
|
|
23
23
|
skill: flag("skill"),
|
|
24
24
|
mcp: flag("mcp"),
|
|
25
|
-
reinstall,
|
|
25
|
+
reinstall: flag("reinstall"),
|
|
26
|
+
update: flag("update"),
|
|
26
27
|
from: raw.from,
|
|
27
28
|
status: flag("status"),
|
|
28
29
|
uninstall: flag("uninstall"),
|
|
@@ -38,8 +39,8 @@ export function validateInstallOpts(opts: InstallOpts): string | null {
|
|
|
38
39
|
if (opts.quiet && opts.dry) {
|
|
39
40
|
return "--quiet cannot be combined with --dry.";
|
|
40
41
|
}
|
|
41
|
-
if (opts.quiet && !opts.yes && !opts.json && !opts.reinstall) {
|
|
42
|
-
return "--quiet requires --yes (or --json / --reinstall).";
|
|
42
|
+
if (opts.quiet && !opts.yes && !opts.json && !opts.reinstall && !opts.update) {
|
|
43
|
+
return "--quiet requires --yes (or --json / --reinstall / --update).";
|
|
43
44
|
}
|
|
44
45
|
if (opts.json) {
|
|
45
46
|
opts.yes = true;
|
|
@@ -48,22 +49,31 @@ export function validateInstallOpts(opts: InstallOpts): string | null {
|
|
|
48
49
|
opts.bin = true;
|
|
49
50
|
opts.yes = true;
|
|
50
51
|
}
|
|
52
|
+
if (opts.update) {
|
|
53
|
+
opts.yes = true;
|
|
54
|
+
}
|
|
51
55
|
|
|
52
56
|
const mutationFlags =
|
|
53
|
-
opts.all || opts.bin || opts.completions || opts.skill || opts.mcp || opts.reinstall || opts.uninstall;
|
|
57
|
+
opts.all || opts.bin || opts.completions || opts.skill || opts.mcp || opts.reinstall || opts.update || opts.uninstall;
|
|
54
58
|
if (opts.status && mutationFlags) {
|
|
55
59
|
return "--status is mutually exclusive with install/reinstall/uninstall targets.";
|
|
56
60
|
}
|
|
57
61
|
if (
|
|
58
62
|
opts.reinstall &&
|
|
59
|
-
(opts.all || opts.completions || opts.skill || opts.mcp || opts.uninstall || opts.status)
|
|
63
|
+
(opts.all || opts.completions || opts.skill || opts.mcp || opts.uninstall || opts.status || opts.update)
|
|
60
64
|
) {
|
|
61
65
|
return "--reinstall cannot be combined with other target flags.";
|
|
62
66
|
}
|
|
63
|
-
if (
|
|
64
|
-
|
|
67
|
+
if (
|
|
68
|
+
opts.update &&
|
|
69
|
+
(opts.all || opts.bin || opts.completions || opts.skill || opts.mcp || opts.uninstall || opts.status || opts.reinstall)
|
|
70
|
+
) {
|
|
71
|
+
return "--update cannot be combined with other target flags.";
|
|
72
|
+
}
|
|
73
|
+
if (opts.uninstall && (opts.reinstall || opts.update || opts.status)) {
|
|
74
|
+
return "--uninstall cannot be combined with --reinstall, --update, or --status.";
|
|
65
75
|
}
|
|
66
|
-
if (!opts.status && !opts.reinstall) {
|
|
76
|
+
if (!opts.status && !opts.reinstall && !opts.update) {
|
|
67
77
|
const hasTarget = opts.all || opts.bin || opts.completions || opts.skill || opts.mcp;
|
|
68
78
|
if (!hasTarget) {
|
|
69
79
|
return "Specify at least one target: --all, --bin, --completions, --skill, or --mcp.";
|
|
@@ -185,6 +195,21 @@ export async function runInstallMutation(
|
|
|
185
195
|
|
|
186
196
|
/** Main install command orchestrator. */
|
|
187
197
|
export async function cliInstall(root: CliProgram, rawOpts: Record<string, string>): Promise<never> {
|
|
198
|
+
const opts = parseInstallOpts(rawOpts);
|
|
199
|
+
const err = validateInstallOpts(opts);
|
|
200
|
+
if (err) {
|
|
201
|
+
installErr(err);
|
|
202
|
+
process.exit(1);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (opts.update) {
|
|
206
|
+
if (!resolveCapabilities(root).update) {
|
|
207
|
+
installErr("install --update requires install.updateGetLatest on the program root.");
|
|
208
|
+
process.exit(1);
|
|
209
|
+
}
|
|
210
|
+
await cliUpdate(root);
|
|
211
|
+
}
|
|
212
|
+
|
|
188
213
|
let result: Awaited<ReturnType<typeof runInstallMutation>>;
|
|
189
214
|
try {
|
|
190
215
|
result = await runInstallMutation(root, rawOpts);
|
|
@@ -193,26 +218,26 @@ export async function cliInstall(root: CliProgram, rawOpts: Record<string, strin
|
|
|
193
218
|
process.exit(1);
|
|
194
219
|
}
|
|
195
220
|
|
|
196
|
-
const { changed, opts, paths } = result;
|
|
221
|
+
const { changed, opts: mutationOpts, paths } = result;
|
|
197
222
|
|
|
198
|
-
if (
|
|
223
|
+
if (mutationOpts.status) {
|
|
199
224
|
process.exit(0);
|
|
200
225
|
}
|
|
201
226
|
|
|
202
|
-
if (
|
|
227
|
+
if (mutationOpts.json) {
|
|
203
228
|
process.stdout.write(JSON.stringify(changed, null, 2) + "\n");
|
|
204
229
|
process.exit(0);
|
|
205
230
|
}
|
|
206
231
|
|
|
207
|
-
if (!
|
|
208
|
-
const verb =
|
|
209
|
-
installOut(`${verb} ${changed.length} file(s).`,
|
|
232
|
+
if (!mutationOpts.quiet && changed.length > 0) {
|
|
233
|
+
const verb = mutationOpts.uninstall ? "Removed" : mutationOpts.reinstall ? "Reinstalled" : "Installed";
|
|
234
|
+
installOut(`${verb} ${changed.length} file(s).`, mutationOpts);
|
|
210
235
|
if (
|
|
211
|
-
!
|
|
212
|
-
(
|
|
236
|
+
!mutationOpts.uninstall &&
|
|
237
|
+
(mutationOpts.all || mutationOpts.bin) &&
|
|
213
238
|
changed.some((p) => p === paths.bashRc || p === paths.zshRc || p === paths.binaryPath)
|
|
214
239
|
) {
|
|
215
|
-
installOut("Open a new shell, or run: hash -r (bash) / rehash (zsh)",
|
|
240
|
+
installOut("Open a new shell, or run: hash -r (bash) / rehash (zsh)", mutationOpts);
|
|
216
241
|
}
|
|
217
242
|
}
|
|
218
243
|
|
package/src/install/plan.ts
CHANGED
|
@@ -36,23 +36,25 @@ function fixtureWithUpdate(hook: () => Promise<CliUpdateArtifact>): CliProgram {
|
|
|
36
36
|
};
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
test("update
|
|
39
|
+
test("user update command is allowed when updateGetLatest is set", () => {
|
|
40
40
|
const root: CliProgram = {
|
|
41
41
|
...fixtureWithUpdate(async () => ({ path: process.execPath })),
|
|
42
|
-
commands: [{ key: "update", description: "
|
|
42
|
+
commands: [{ key: "update", description: "Custom update", handler: () => {} }],
|
|
43
43
|
};
|
|
44
|
-
expect(() => cliValidateProgram(root)).toThrow(
|
|
44
|
+
expect(() => cliValidateProgram(root)).not.toThrow();
|
|
45
45
|
});
|
|
46
46
|
|
|
47
|
-
test("presentation
|
|
47
|
+
test("presentation exposes install --update when hook is set", () => {
|
|
48
48
|
const root = fixtureWithUpdate(async () => ({ path: process.execPath }));
|
|
49
49
|
const presentation = cliPresentationRoot(root);
|
|
50
|
-
|
|
50
|
+
const install = presentation.commands.find((c) => c.key === "install");
|
|
51
|
+
expect(install?.options?.some((o) => o.name === "update")).toBe(true);
|
|
52
|
+
expect(presentation.commands.some((c) => c.key === "update")).toBe(false);
|
|
51
53
|
});
|
|
52
54
|
|
|
53
|
-
test("parseInstallOpts
|
|
54
|
-
|
|
55
|
-
expect(
|
|
55
|
+
test("parseInstallOpts treats --update separately from --reinstall", () => {
|
|
56
|
+
expect(parseInstallOpts({ update: "1" }).update).toBe(true);
|
|
57
|
+
expect(parseInstallOpts({ update: "1" }).reinstall).toBe(false);
|
|
56
58
|
});
|
|
57
59
|
|
|
58
60
|
test("runInstallMutation honors --from for binary copy", async () => {
|
|
@@ -78,7 +80,7 @@ test("runInstallMutation honors --from for binary copy", async () => {
|
|
|
78
80
|
expect(readFileSync(dest, "utf8")).toContain("echo hi");
|
|
79
81
|
});
|
|
80
82
|
|
|
81
|
-
test("cliInvoke update uses hook and reinstalls", async () => {
|
|
83
|
+
test("cliInvoke install --update uses hook and reinstalls", async () => {
|
|
82
84
|
const source = join(home, "new-binary");
|
|
83
85
|
writeFileSync(source, "#!/bin/sh\necho hi\n", "utf8");
|
|
84
86
|
chmodSync(source, 0o755);
|
|
@@ -88,19 +90,19 @@ test("cliInvoke update uses hook and reinstalls", async () => {
|
|
|
88
90
|
version: "2.0.0",
|
|
89
91
|
}));
|
|
90
92
|
|
|
91
|
-
const result = await cliInvoke(root, ["update"]);
|
|
93
|
+
const result = await cliInvoke(root, ["install", "--update"]);
|
|
92
94
|
expect(result.exitCode).toBe(0);
|
|
93
95
|
expect(result.stdout).toContain("Updated testapp 1.0.0 → 2.0.0");
|
|
94
96
|
expect(existsSync(join(home, ".local", "bin", "testapp"))).toBe(true);
|
|
95
97
|
});
|
|
96
98
|
|
|
97
|
-
test("cliInvoke update reports already current", async () => {
|
|
99
|
+
test("cliInvoke install --update reports already current", async () => {
|
|
98
100
|
const root = fixtureWithUpdate(async () => ({
|
|
99
101
|
path: process.execPath,
|
|
100
102
|
version: "1.0.0",
|
|
101
103
|
}));
|
|
102
104
|
|
|
103
|
-
const result = await cliInvoke(root, ["update"]);
|
|
105
|
+
const result = await cliInvoke(root, ["install", "--update"]);
|
|
104
106
|
expect(result.exitCode).toBe(0);
|
|
105
107
|
expect(result.stdout).toContain("Already at v1.0.0");
|
|
106
108
|
});
|
package/src/install/update.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { CliProgram } from "../types.ts";
|
|
|
3
3
|
import { runInstallMutation } from "./index.ts";
|
|
4
4
|
import { installErr } from "./status.ts";
|
|
5
5
|
|
|
6
|
-
/** Downloads the latest release and reinstalls installed artifacts (`myapp update`). */
|
|
6
|
+
/** Downloads the latest release and reinstalls installed artifacts (`myapp install --update`). */
|
|
7
7
|
export async function cliUpdate(root: CliProgram): Promise<never> {
|
|
8
8
|
const hook = root.install?.updateGetLatest;
|
|
9
9
|
if (!hook) {
|
package/src/invoke.ts
CHANGED
|
@@ -5,10 +5,10 @@ process.exit so MCP tool calls can run handlers repeatedly.
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { CliContext } from "./context.ts";
|
|
8
|
-
import { builtinInterceptRoot } from "./builtins/dispatch.ts";
|
|
8
|
+
import { builtinInterceptRoot, dispatchBuiltin } from "./builtins/dispatch.ts";
|
|
9
9
|
import { cliPresentationRoot } from "./builtins/presentation.ts";
|
|
10
10
|
import { parse, postParseValidate, ParseKind } from "./parse.ts";
|
|
11
|
-
import { type CliNode, type CliProgram, isCliLeaf, isCliRouter } from "./types.ts";
|
|
11
|
+
import { type CliNode, type CliProgram, type CliRouter, isCliLeaf, isCliRouter } from "./types.ts";
|
|
12
12
|
import { format } from "node:util";
|
|
13
13
|
|
|
14
14
|
/** Outcome of a non-exiting CLI invocation. */
|
|
@@ -52,10 +52,17 @@ function findChild(cmds: CliNode[], name: string): CliNode | undefined {
|
|
|
52
52
|
*/
|
|
53
53
|
export async function cliInvoke(root: CliProgram, argv: string[]): Promise<CliInvokeResult> {
|
|
54
54
|
let parseRoot: CliNode = root;
|
|
55
|
+
let completionParseRoot: CliRouter = cliPresentationRoot(root);
|
|
56
|
+
let isLeafCompletionIntercept = false;
|
|
57
|
+
|
|
55
58
|
if (isCliLeaf(root)) {
|
|
56
59
|
const intercept = builtinInterceptRoot(root, argv);
|
|
57
|
-
if (intercept.parseRoot !== root) {
|
|
60
|
+
if (intercept.isLeafCompletionIntercept || intercept.parseRoot !== root) {
|
|
58
61
|
parseRoot = intercept.parseRoot;
|
|
62
|
+
completionParseRoot = isCliRouter(intercept.parseRoot)
|
|
63
|
+
? intercept.parseRoot
|
|
64
|
+
: cliPresentationRoot(root);
|
|
65
|
+
isLeafCompletionIntercept = intercept.isLeafCompletionIntercept;
|
|
59
66
|
}
|
|
60
67
|
} else {
|
|
61
68
|
parseRoot = cliPresentationRoot(root);
|
|
@@ -165,6 +172,10 @@ export async function cliInvoke(root: CliProgram, argv: string[]): Promise<CliIn
|
|
|
165
172
|
};
|
|
166
173
|
|
|
167
174
|
try {
|
|
175
|
+
if (pr.kind === ParseKind.Ok) {
|
|
176
|
+
await dispatchBuiltin(root, pr, { isLeafCompletionIntercept, parseRoot: completionParseRoot });
|
|
177
|
+
}
|
|
178
|
+
|
|
168
179
|
await Promise.resolve(handler(ctx));
|
|
169
180
|
return { kind: "ok", exitCode: 0, stdout, stderr };
|
|
170
181
|
} catch (err) {
|
package/src/runtime.ts
CHANGED
|
@@ -40,13 +40,6 @@ export async function cliRun(program: CliProgram, argv: string[] = process.argv.
|
|
|
40
40
|
process.exit(1);
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
if (argv.length >= 1 && argv[0] === "update" && !caps.update) {
|
|
44
|
-
process.stderr.write(
|
|
45
|
-
"update is not enabled. Set install.updateGetLatest on the program root.\n",
|
|
46
|
-
);
|
|
47
|
-
process.exit(1);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
43
|
if (argv.length >= 1 && argv[0] === "docs" && !caps.docs) {
|
|
51
44
|
process.stderr.write("docs is not enabled. Set docs: { enabled: true } on the program root.\n");
|
|
52
45
|
process.exit(1);
|
package/src/schema.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { type CliNode, type CliProgram, isCliLeaf, isCliRouter } from "./types.t
|
|
|
6
6
|
import { exportPresentationBuiltins, type CliSchemaExport } from "./builtins/export.ts";
|
|
7
7
|
import { cliResolveNotes } from "./help.ts";
|
|
8
8
|
|
|
9
|
-
const RESERVED = new Set(["completion", "install", "docs", "mcp", "version"
|
|
9
|
+
const RESERVED = new Set(["completion", "install", "docs", "mcp", "version"]);
|
|
10
10
|
|
|
11
11
|
function exportCommand(cmd: CliNode, root: CliProgram): CliSchemaExport {
|
|
12
12
|
const out: CliSchemaExport = {
|
package/src/types.ts
CHANGED
|
@@ -162,7 +162,7 @@ export interface CliUpdateArtifact {
|
|
|
162
162
|
cleanup?: () => void | Promise<void>;
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
-
/** Fetches the latest release binary for
|
|
165
|
+
/** Fetches the latest release binary for `install --update`. */
|
|
166
166
|
export type CliUpdateGetLatest = (ctx: { version: string }) => Promise<CliUpdateArtifact>;
|
|
167
167
|
|
|
168
168
|
export interface CliInstallConfig {
|
|
@@ -171,7 +171,7 @@ export interface CliInstallConfig {
|
|
|
171
171
|
/** Default bin directory (default: `~/.local/bin`). Overridden by `INSTALL_PREFIX` env and `--prefix`. */
|
|
172
172
|
prefix?: string;
|
|
173
173
|
/**
|
|
174
|
-
* When set, enables
|
|
174
|
+
* When set, enables `install --update` on the program root.
|
|
175
175
|
* Should download or locate the latest release binary and return its path.
|
|
176
176
|
*/
|
|
177
177
|
updateGetLatest?: CliUpdateGetLatest;
|
package/src/builtins/update.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import type { CliLeaf, CliProgram } from "../types.ts";
|
|
2
|
-
import { cliUpdate } from "../install/update.ts";
|
|
3
|
-
|
|
4
|
-
/** Built-in `update` command (enabled when `install.updateGetLatest` is set). */
|
|
5
|
-
export function cliBuiltinUpdateCommand(program: CliProgram): CliLeaf {
|
|
6
|
-
return {
|
|
7
|
-
key: "update",
|
|
8
|
-
description: "Download and install the latest release.",
|
|
9
|
-
mcpTool: { enabled: false },
|
|
10
|
-
handler: async () => {
|
|
11
|
-
await cliUpdate(program);
|
|
12
|
-
},
|
|
13
|
-
};
|
|
14
|
-
}
|