argsbarg 4.1.0 → 4.1.1

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.
Files changed (103) hide show
  1. package/CHANGELOG.md +48 -1
  2. package/README.md +90 -84
  3. package/docs/README.md +6 -6
  4. package/docs/bundled-docs.md +1 -1
  5. package/docs/cli-program.md +5 -3
  6. package/docs/config-schema.md +36 -9
  7. package/docs/developing.md +7 -7
  8. package/docs/distribution-homebrew.md +103 -0
  9. package/docs/install.md +105 -189
  10. package/docs/mcp.md +2 -3
  11. package/docs/output-schema.md +1 -1
  12. package/examples/full-example/Formula/.gitkeep +0 -0
  13. package/examples/full-example/README.md +98 -0
  14. package/examples/full-example/biome.json +22 -0
  15. package/examples/{consumer-app → full-example}/bun.lock +2 -0
  16. package/examples/full-example/justfile +134 -0
  17. package/examples/{consumer-app → full-example}/package.json +10 -3
  18. package/examples/{consumer-app → full-example}/schemas/generated/app-config.json +1 -1
  19. package/examples/{consumer-app → full-example}/schemas/generated/status.json +1 -1
  20. package/examples/full-example/scripts/create-identity.ts +11 -0
  21. package/examples/full-example/scripts/formula-shared.ts +73 -0
  22. package/examples/full-example/scripts/gen-dev-formula.ts +26 -0
  23. package/examples/full-example/scripts/print-identity.ts +27 -0
  24. package/examples/full-example/src/commands/echo/command.ts +21 -0
  25. package/examples/full-example/src/commands/status/command.test.ts +10 -0
  26. package/examples/full-example/src/commands/status/command.ts +36 -0
  27. package/examples/{consumer-app → full-example}/src/commands/status/types.ts +1 -1
  28. package/examples/full-example/src/index.ts +10 -0
  29. package/examples/full-example/src/program.ts +57 -0
  30. package/examples/{consumer-app → full-example}/src/types.ts +1 -1
  31. package/examples/nested.ts +1 -3
  32. package/index.d.ts +27 -66
  33. package/package.json +2 -2
  34. package/src/builtins/builtins.test.ts +22 -23
  35. package/src/builtins/completion-group.ts +17 -15
  36. package/src/builtins/dispatch.ts +25 -1
  37. package/src/builtins/install.ts +22 -52
  38. package/src/builtins/registry.ts +2 -0
  39. package/src/builtins/uninstall.ts +80 -0
  40. package/src/capabilities.ts +1 -3
  41. package/src/cli-tool/cli-smoke.test.ts +19 -0
  42. package/src/cli-tool/create.test.ts +119 -0
  43. package/src/cli-tool/create.ts +380 -0
  44. package/{examples/consumer-app/capabilities.test.ts → src/cli-tool/full-example-capabilities.test.ts} +15 -14
  45. package/src/cli-tool/main.ts +8 -0
  46. package/src/cli-tool/post-create.ts +111 -0
  47. package/src/cli-tool/program.ts +82 -0
  48. package/src/cli-tool/prompt.ts +28 -0
  49. package/src/cli-tool/run-create.ts +149 -0
  50. package/src/cli.ts +0 -2
  51. package/src/config/bootstrap.ts +16 -9
  52. package/src/config/resolve.test.ts +167 -0
  53. package/src/config/resolve.ts +50 -6
  54. package/src/docs/api-guide.test.ts +4 -5
  55. package/src/docs/docs.test.ts +2 -1
  56. package/src/docs/mcp-guide.ts +7 -8
  57. package/src/index.ts +3 -10
  58. package/src/install/binary-placement.test.ts +101 -0
  59. package/src/install/binary-placement.ts +47 -0
  60. package/src/install/index.ts +117 -123
  61. package/src/install/install.test.ts +89 -105
  62. package/src/install/normalize-uninstall.ts +11 -0
  63. package/src/install/normalize.ts +4 -19
  64. package/src/install/paths.ts +0 -22
  65. package/src/install/plan.ts +14 -6
  66. package/src/install/shell.ts +0 -14
  67. package/src/install/status.test.ts +6 -6
  68. package/src/install/status.ts +0 -6
  69. package/src/install/target-effective.ts +0 -2
  70. package/src/install/target-scope.ts +15 -28
  71. package/src/install/target-types.ts +0 -16
  72. package/src/install/targets/app.ts +19 -28
  73. package/src/install/targets/configure.ts +5 -1
  74. package/src/install/targets/index.ts +0 -3
  75. package/src/install/targets.test.ts +24 -42
  76. package/src/mcp/env.test.ts +92 -0
  77. package/src/mcp/env.ts +15 -14
  78. package/src/parse.test.ts +3 -2
  79. package/src/prompt.ts +10 -0
  80. package/src/schema.ts +9 -1
  81. package/src/types.ts +27 -9
  82. package/src/validate.ts +5 -11
  83. package/docs/templates/cursor/rules/cli-program.mdc +0 -31
  84. package/examples/config-app/main.ts +0 -20
  85. package/examples/config-app/program.ts +0 -78
  86. package/examples/config-app/schema.ts +0 -37
  87. package/examples/config-app/types.ts +0 -19
  88. package/examples/consumer-app/README.md +0 -56
  89. package/examples/consumer-app/src/main.ts +0 -15
  90. package/examples/consumer-app/src/program.ts +0 -108
  91. package/src/install/app.ts +0 -94
  92. package/src/install/bootstrap.ts +0 -22
  93. package/src/install/completions.ts +0 -56
  94. package/src/install/targets/completions.ts +0 -133
  95. package/src/install/update.test.ts +0 -123
  96. package/src/install/update.ts +0 -54
  97. /package/examples/{consumer-app → full-example}/schemas/configSchemas.ts +0 -0
  98. /package/examples/{consumer-app → full-example}/schemas/outputSchemas.ts +0 -0
  99. /package/examples/{consumer-app → full-example}/scripts/schemagen/discover-schema-roots.test.ts +0 -0
  100. /package/examples/{consumer-app → full-example}/scripts/schemagen/discover-schema-roots.ts +0 -0
  101. /package/examples/{consumer-app → full-example}/scripts/schemagen/naming.ts +0 -0
  102. /package/examples/{consumer-app → full-example}/scripts/schemagen.ts +0 -0
  103. /package/examples/{consumer-app → full-example}/tsconfig.json +0 -0
@@ -1,78 +0,0 @@
1
- /*
2
- CliProgram for the config-app example — program.appConfig with jsonSchema + metadata overlay.
3
- */
4
-
5
- import pkg from "../../package.json" with { type: "json" };
6
- import {
7
- type CliAppConfig,
8
- type CliAppConfigEntry,
9
- CliOptionKind,
10
- type CliProgram,
11
- } from "../../src/index.ts";
12
- import { APP_CONFIG_JSON_SCHEMA } from "./schema.ts";
13
-
14
- const configSchema = {
15
- apiToken: {
16
- description: "Create at https://example.com/settings/tokens",
17
- env: "CONFIG_APP_API_TOKEN",
18
- sensitive: true,
19
- },
20
- defaultRegion: {
21
- description: "AWS region for API calls.",
22
- required: false,
23
- },
24
- maxRetries: {
25
- description: "HTTP retry count (0–10).",
26
- },
27
- prefs: {
28
- description: "Local cache preferences (not exported to env).",
29
- required: false,
30
- },
31
- } as const satisfies Record<string, CliAppConfigEntry>;
32
-
33
- export const program = {
34
- key: "config-app",
35
- version: pkg.version,
36
- description: "Demonstrates program.appConfig, ctx.appConfig, and built-in config get/set.",
37
- appConfig: {
38
- jsonSchema: APP_CONFIG_JSON_SCHEMA,
39
- entries: configSchema,
40
- } satisfies CliAppConfig,
41
- commands: [
42
- {
43
- key: "show",
44
- description: "Print resolved config (secrets redacted).",
45
- options: [
46
- {
47
- name: "json",
48
- description: "Emit JSON.",
49
- kind: CliOptionKind.Presence,
50
- },
51
- ],
52
- handler: (ctx) => {
53
- const out = {
54
- defaultRegion: ctx.appConfig.get("defaultRegion"),
55
- maxRetries: ctx.appConfig.get("maxRetries"),
56
- prefs: ctx.appConfig.get("prefs"),
57
- apiTokenSet: ctx.appConfig.get("apiToken") !== undefined,
58
- };
59
- if (ctx.hasFlag("json")) {
60
- console.log(JSON.stringify(out, null, 2));
61
- } else {
62
- console.log(`region=${out.defaultRegion ?? "(not set)"}`);
63
- console.log(`maxRetries=${out.maxRetries ?? "(not set)"}`);
64
- console.log(`prefs=${out.prefs ? JSON.stringify(out.prefs) : "(not set)"}`);
65
- console.log(`apiToken=${out.apiTokenSet ? "set" : "missing"}`);
66
- }
67
- },
68
- },
69
- {
70
- key: "ping",
71
- description: "Require apiToken and print a short confirmation.",
72
- handler: (ctx) => {
73
- const token = ctx.appConfig.require("apiToken");
74
- console.log(`ok (token length ${String(token).length})`);
75
- },
76
- },
77
- ],
78
- } satisfies CliProgram;
@@ -1,37 +0,0 @@
1
- /*
2
- Inline draft-07 JSON Schema for AppConfig.
3
- Production apps commit generated JSON and import via configSchemas.ts — see docs/config-schema.md.
4
- */
5
-
6
- import type { AppConfig } from "./types.ts";
7
-
8
- /** JSON Schema root for program.appConfig.jsonSchema (hand-written stand-in for schemagen). */
9
- export const APP_CONFIG_JSON_SCHEMA = {
10
- $schema: "http://json-schema.org/draft-07/schema#",
11
- type: "object",
12
- additionalProperties: false,
13
- required: ["apiToken", "maxRetries"],
14
- properties: {
15
- apiToken: { type: "string", minLength: 1 },
16
- defaultRegion: { type: "string", default: "us-east-1" },
17
- maxRetries: { type: "integer", minimum: 0, maximum: 10, default: 3 },
18
- prefs: {
19
- type: "object",
20
- additionalProperties: false,
21
- required: ["ttl"],
22
- properties: {
23
- ttl: { type: "integer", minimum: 1 },
24
- },
25
- },
26
- },
27
- } as const satisfies Record<string, unknown>;
28
-
29
- /** Compile-time check that schema keys align with AppConfig (documentation only). */
30
- type _SchemaKeys = keyof typeof APP_CONFIG_JSON_SCHEMA.properties;
31
- type _ConfigKeys = keyof AppConfig;
32
- const _assertKeysAlign: _SchemaKeys extends _ConfigKeys
33
- ? _ConfigKeys extends _SchemaKeys
34
- ? true
35
- : never
36
- : never = true;
37
- void _assertKeysAlign;
@@ -1,19 +0,0 @@
1
- /**
2
- * Config schema
3
- *
4
- * Application settings persisted in a flat JSON file (`program.appConfig`).
5
- * In a production app, generate `APP_CONFIG_JSON_SCHEMA` from this interface
6
- * with ts-json-schema-generator — see docs/config-schema.md.
7
- */
8
- export interface AppConfig {
9
- /** API token from the provider dashboard. */
10
- apiToken: string;
11
- /** AWS region (default us-east-1). */
12
- defaultRegion?: string;
13
- /** HTTP retry count (default 3). */
14
- maxRetries: number;
15
- /** Local preferences (file-only; not mapped to process.env). */
16
- prefs?: {
17
- ttl: number;
18
- };
19
- }
@@ -1,56 +0,0 @@
1
- # consumer-app
2
-
3
- **Kitchen-sink argsbarg reference** — copy this layout when bootstrapping a production CLI. For a minimal `program.appConfig` intro, see [`../config-app/`](../config-app/).
4
-
5
- ## What this demonstrates
6
-
7
- | Area | Files / wiring |
8
- | --- | --- |
9
- | All builtins | `completion`, `version`, `install` (+ `--update`), `docs`, `mcp`, `config get`/`set` |
10
- | `program.appConfig` | `src/types.ts` (`AppConfig`) → `schemas/configSchemas.ts` |
11
- | `outputSchema` | `src/commands/status/types.ts` (`StatusJsonOutput`) → `schemas/outputSchemas.ts` |
12
- | Schemagen | `scripts/schemagen.ts` + `scripts/schemagen/discover-schema-roots.ts` |
13
- | Handler access | `ctx.appConfig` in `src/program.ts` |
14
- | MCP doc topics | `docs.topics` auto-exposed as `<key>://docs/<topic>` resources when docs + MCP enabled |
15
- | Package import | `from "argsbarg"` (not relative to argsbarg `src/`) |
16
-
17
- ## Quick start (in this repo)
18
-
19
- ```bash
20
- cd examples/consumer-app
21
- bun install
22
- bun run schemagen # after changing src/**/types.ts
23
- CONSUMER_APP_API_TOKEN=dev bun run start status --json
24
- CONSUMER_APP_API_TOKEN=dev bun run start config get apiToken --json
25
- CONSUMER_APP_API_TOKEN=dev bun run start docs readme
26
- ```
27
-
28
- ## Copy into a new app
29
-
30
- 1. Copy this directory into your repo (e.g. `apps/my-cli/`).
31
- 2. Set `"argsbarg": "^<version>"` in `package.json` (replace `file:../..`).
32
- 3. Run `bun run schemagen` and commit `schemas/generated/` + bridge `.ts` files.
33
- 4. Copy [`node_modules/argsbarg/docs/templates/cursor/rules/cli-program.mdc`](../../docs/templates/cursor/rules/cli-program.mdc) to `.cursor/rules/`.
34
-
35
- ## Schemagen markers
36
-
37
- | Marker in interface JSDoc | Artifact |
38
- | --- | --- |
39
- | `Config schema` | `schemas/configSchemas.ts` + `schemas/generated/*-config.json` |
40
- | `JSON payload` | `schemas/outputSchemas.ts` + `schemas/generated/*.json` |
41
-
42
- Discovery walks `src/**/types.ts` only.
43
-
44
- ## Environment
45
-
46
- | Variable | Purpose |
47
- | --- | --- |
48
- | `CONSUMER_APP_API_TOKEN` | Overrides `apiToken` via `program.appConfig` env mapping |
49
-
50
- ## Maintainers (argsbarg repo)
51
-
52
- When adding or changing builtins, update this example and run:
53
-
54
- ```bash
55
- just consumer-app-schemagen
56
- ```
@@ -1,15 +0,0 @@
1
- #!/usr/bin/env bun
2
- /*
3
- Kitchen-sink argsbarg consumer reference.
4
-
5
- cd examples/consumer-app && bun install && bun run schemagen
6
- CONSUMER_APP_API_TOKEN=dev bun run start status --json
7
- CONSUMER_APP_API_TOKEN=dev bun run start config get
8
- CONSUMER_APP_API_TOKEN=dev bun run start docs readme
9
- */
10
-
11
- import { Cli } from "argsbarg";
12
- import { program } from "./program.ts";
13
-
14
- const cli = new Cli(program);
15
- await cli.run();
@@ -1,108 +0,0 @@
1
- /*
2
- Kitchen-sink CliProgram — every argsbarg builtin enabled.
3
- */
4
-
5
- import {
6
- type CliAppConfig,
7
- type CliAppConfigEntry,
8
- CliOptionKind,
9
- type CliProgram,
10
- } from "argsbarg";
11
- import readmeText from "../README.md" with { type: "text" };
12
- import { APP_CONFIG_JSON_SCHEMA } from "../schemas/configSchemas.ts";
13
- import { STATUS_JSON_OUTPUT_SCHEMA } from "../schemas/outputSchemas.ts";
14
- import type { StatusJsonOutput } from "./commands/status/types.ts";
15
-
16
- const configSchema = {
17
- apiToken: {
18
- description: "Create at https://example.com/settings/tokens",
19
- env: "CONSUMER_APP_API_TOKEN",
20
- sensitive: true,
21
- },
22
- defaultRegion: {
23
- description: "AWS region for API calls.",
24
- required: false,
25
- },
26
- maxRetries: {
27
- description: "HTTP retry count (0–10).",
28
- },
29
- prefs: {
30
- description: "Local cache preferences (not exported to env).",
31
- required: false,
32
- },
33
- } as const satisfies Record<string, CliAppConfigEntry>;
34
-
35
- export const program = {
36
- key: "consumer-app",
37
- version: "1.0.0",
38
- description: "Argsbarg kitchen-sink reference — all builtins, schemagen, ctx.appConfig.",
39
- appConfig: {
40
- jsonSchema: APP_CONFIG_JSON_SCHEMA,
41
- entries: configSchema,
42
- } satisfies CliAppConfig,
43
- docs: {
44
- enabled: true,
45
- topics: {
46
- readme: {
47
- text: readmeText,
48
- },
49
- },
50
- },
51
- mcpServer: {
52
- enabled: true,
53
- mcpd: true,
54
- claudePlugin: true,
55
- },
56
- install: {
57
- // Defaults: app + completions + configure; agentIntegration picks skill vs MCP for --all.
58
- updateGetLatest: async () => ({
59
- path: process.execPath,
60
- version: "1.0.0",
61
- }),
62
- },
63
- commands: [
64
- {
65
- key: "status",
66
- description: "Show resolved config and app version.",
67
- options: [
68
- {
69
- name: "json",
70
- description: "Emit JSON.",
71
- kind: CliOptionKind.Presence,
72
- },
73
- ],
74
- outputSchema: STATUS_JSON_OUTPUT_SCHEMA,
75
- handler: (ctx) => {
76
- const out: StatusJsonOutput = {
77
- defaultRegion: ctx.appConfig.get("defaultRegion") as string | undefined,
78
- maxRetries: ctx.appConfig.get("maxRetries") as number | undefined,
79
- apiTokenSet: ctx.appConfig.get("apiToken") !== undefined,
80
- version: ctx.program.version,
81
- };
82
- if (ctx.hasFlag("json")) {
83
- console.log(JSON.stringify(out, null, 2));
84
- } else {
85
- console.log(`version=${out.version}`);
86
- console.log(`region=${out.defaultRegion ?? "(not set)"}`);
87
- console.log(`maxRetries=${out.maxRetries ?? "(not set)"}`);
88
- console.log(`apiToken=${out.apiTokenSet ? "set" : "missing"}`);
89
- }
90
- },
91
- },
92
- {
93
- key: "echo",
94
- description: "Echo a message (MCP-friendly leaf).",
95
- options: [
96
- {
97
- name: "message",
98
- description: "Text to print.",
99
- kind: CliOptionKind.String,
100
- required: true,
101
- },
102
- ],
103
- handler: (ctx) => {
104
- console.log(ctx.stringOpt("message") ?? "");
105
- },
106
- },
107
- ],
108
- } satisfies CliProgram;
@@ -1,94 +0,0 @@
1
- import {
2
- copyFileSync,
3
- existsSync,
4
- mkdirSync,
5
- readFileSync,
6
- unlinkSync,
7
- writeFileSync,
8
- } from "node:fs";
9
- import { dirname, join } from "node:path";
10
- import type { CliProgram } from "../types.ts";
11
- import type { InstallPaths } from "./paths.ts";
12
- import {
13
- buildPathRcBlock,
14
- buildZshFpathRcBlock,
15
- detectShells,
16
- hasRcBlock,
17
- removeRcBlock,
18
- } from "./shell.ts";
19
-
20
- export interface AppInstallResult {
21
- changedFiles: string[];
22
- patchedBashRc: boolean;
23
- patchedZshRc: boolean;
24
- }
25
-
26
- /** Copies the app executable to the install path and patches rc files when shells are detected. */
27
- export function installApp(
28
- root: CliProgram,
29
- paths: InstallPaths,
30
- dry: boolean,
31
- sourcePath: string = process.execPath,
32
- ): AppInstallResult {
33
- const changed: string[] = [];
34
- const shells = detectShells();
35
- let patchedBashRc = false;
36
- let patchedZshRc = false;
37
-
38
- if (!dry) {
39
- mkdirSync(paths.appDir, { recursive: true });
40
- copyFileSync(sourcePath, paths.appPath);
41
- }
42
- changed.push(paths.appPath);
43
-
44
- if (shells.bash && existsSync(dirname(paths.bashRc))) {
45
- const block = buildPathRcBlock(root.key, paths.appDir);
46
- let content = existsSync(paths.bashRc) ? readFileSync(paths.bashRc, "utf8") : "";
47
- if (!hasRcBlock(content, root.key, "path")) {
48
- if (!content.endsWith("\n") && content.length > 0) content += "\n";
49
- content += `${block}\n`;
50
- if (!dry) writeFileSync(paths.bashRc, content, "utf8");
51
- changed.push(paths.bashRc);
52
- patchedBashRc = true;
53
- }
54
- }
55
-
56
- if (shells.zsh) {
57
- const completionsDir = join(dirname(paths.zshCompletion));
58
- const block = buildZshFpathRcBlock(root.key, completionsDir);
59
- let content = existsSync(paths.zshRc) ? readFileSync(paths.zshRc, "utf8") : "";
60
- if (!hasRcBlock(content, root.key, "fpath")) {
61
- if (!content.endsWith("\n") && content.length > 0) content += "\n";
62
- content += `${block}\n`;
63
- if (!dry) writeFileSync(paths.zshRc, content, "utf8");
64
- changed.push(paths.zshRc);
65
- patchedZshRc = true;
66
- }
67
- }
68
-
69
- return { changedFiles: changed, patchedBashRc, patchedZshRc };
70
- }
71
-
72
- /** Removes the installed app and rc marker blocks. */
73
- export function uninstallApp(root: CliProgram, paths: InstallPaths, dry: boolean): string[] {
74
- const changed: string[] = [];
75
- if (existsSync(paths.appPath)) {
76
- if (!dry) unlinkSync(paths.appPath);
77
- changed.push(paths.appPath);
78
- }
79
-
80
- for (const [rcPath, tag] of [
81
- [paths.bashRc, "path"],
82
- [paths.zshRc, "fpath"],
83
- ] as const) {
84
- if (!existsSync(rcPath)) continue;
85
- const content = readFileSync(rcPath, "utf8");
86
- if (hasRcBlock(content, root.key, tag)) {
87
- const next = removeRcBlock(content, root.key, tag);
88
- if (!dry) writeFileSync(rcPath, next, "utf8");
89
- changed.push(rcPath);
90
- }
91
- }
92
-
93
- return changed;
94
- }
@@ -1,22 +0,0 @@
1
- import { existsSync } from "node:fs";
2
- import type { CliProgram } from "../types.ts";
3
- import { resolveAppDir } from "./paths.ts";
4
-
5
- /** Rewrites empty argv to `install` when the app is not yet installed (TTY only). */
6
- export function maybeBootstrapInstallArgv(argv: string[], program: CliProgram): string[] {
7
- if (argv.length !== 0) {
8
- return argv;
9
- }
10
- if (program.install?.enabled === false) {
11
- return argv;
12
- }
13
- if (!process.stdin.isTTY) {
14
- return argv;
15
- }
16
- const appDir = resolveAppDir();
17
- const appPath = `${appDir}/${program.key}`;
18
- if (existsSync(appPath)) {
19
- return argv;
20
- }
21
- return ["install"];
22
- }
@@ -1,56 +0,0 @@
1
- import { existsSync, mkdirSync, unlinkSync, writeFileSync } from "node:fs";
2
- import { dirname } from "node:path";
3
- import {
4
- completionBashScript,
5
- completionFishScript,
6
- completionZshScript,
7
- } from "../builtins/index.ts";
8
- import { cliPresentationRoot } from "../builtins/presentation.ts";
9
- import type { CliProgram } from "../types.ts";
10
- import type { InstallPaths } from "./paths.ts";
11
- import { detectShells } from "./shell.ts";
12
-
13
- /** Writes shell completion scripts for detected shells. */
14
- export function installCompletions(root: CliProgram, paths: InstallPaths, dry: boolean): string[] {
15
- const changed: string[] = [];
16
- const shells = detectShells();
17
- const schema = cliPresentationRoot(root);
18
-
19
- if (shells.bash) {
20
- if (!dry) {
21
- mkdirSync(dirname(paths.bashCompletion), { recursive: true });
22
- writeFileSync(paths.bashCompletion, completionBashScript(schema), "utf8");
23
- }
24
- changed.push(paths.bashCompletion);
25
- }
26
-
27
- if (shells.zsh) {
28
- if (!dry) {
29
- mkdirSync(dirname(paths.zshCompletion), { recursive: true });
30
- writeFileSync(paths.zshCompletion, completionZshScript(schema), "utf8");
31
- }
32
- changed.push(paths.zshCompletion);
33
- }
34
-
35
- if (shells.fish) {
36
- if (!dry) {
37
- mkdirSync(dirname(paths.fishCompletion), { recursive: true });
38
- writeFileSync(paths.fishCompletion, completionFishScript(schema), "utf8");
39
- }
40
- changed.push(paths.fishCompletion);
41
- }
42
-
43
- return changed;
44
- }
45
-
46
- /** Removes shell completion files. */
47
- export function uninstallCompletions(paths: InstallPaths, dry: boolean): string[] {
48
- const changed: string[] = [];
49
- for (const p of [paths.bashCompletion, paths.zshCompletion, paths.fishCompletion]) {
50
- if (existsSync(p)) {
51
- if (!dry) unlinkSync(p);
52
- changed.push(p);
53
- }
54
- }
55
- return changed;
56
- }
@@ -1,133 +0,0 @@
1
- import { existsSync } from "node:fs";
2
- import type { CliProgram } from "../../types.ts";
3
- import { installCompletions, uninstallCompletions } from "../completions.ts";
4
- import { displayInstallPath, type InstallPaths } from "../paths.ts";
5
- import { detectShells } from "../shell.ts";
6
- import { InstallTarget } from "../target-base.ts";
7
- import type {
8
- DetectedSnapshot,
9
- InstallAction,
10
- InstalledArtifacts,
11
- InstallStatus,
12
- TargetPlanContext,
13
- UninstallAction,
14
- } from "../target-types.ts";
15
-
16
- type ShellKind = "bash" | "zsh" | "fish";
17
-
18
- interface ShellCompletionSpec {
19
- shell: ShellKind;
20
- label: string;
21
- path: (paths: InstallPaths) => string;
22
- detectedKey: "bashCompletion" | "zshCompletion" | "fishCompletion";
23
- }
24
-
25
- const SHELLS: ShellCompletionSpec[] = [
26
- {
27
- shell: "bash",
28
- label: "bash completion",
29
- path: (p) => p.bashCompletion,
30
- detectedKey: "bashCompletion",
31
- },
32
- {
33
- shell: "zsh",
34
- label: "zsh completion",
35
- path: (p) => p.zshCompletion,
36
- detectedKey: "zshCompletion",
37
- },
38
- {
39
- shell: "fish",
40
- label: "fish completion",
41
- path: (p) => p.fishCompletion,
42
- detectedKey: "fishCompletion",
43
- },
44
- ];
45
-
46
- /** Shell tab-completion scripts (bash / zsh / fish). */
47
- class CompletionsInstallTarget extends InstallTarget {
48
- readonly key = "completions" as const;
49
- readonly actionKind = "completions" as const;
50
- readonly category = "core" as const;
51
-
52
- isAvailable(_root: CliProgram, _paths: InstallPaths): boolean {
53
- return true;
54
- }
55
-
56
- isDetected(paths: InstallPaths, _root: CliProgram): boolean {
57
- return SHELLS.some((s) => existsSync(s.path(paths)));
58
- }
59
-
60
- applyDetected(paths: InstallPaths, _root: CliProgram, out: InstalledArtifacts): void {
61
- for (const s of SHELLS) {
62
- out[s.detectedKey] = existsSync(s.path(paths));
63
- }
64
- }
65
-
66
- protected isDetectedFromSnapshot(detected: DetectedSnapshot): boolean {
67
- return detected.bashCompletion || detected.zshCompletion || detected.fishCompletion;
68
- }
69
-
70
- protected formatStatusLine(_paths: InstallPaths, _root: CliProgram): string {
71
- return "";
72
- }
73
-
74
- protected assignStatusLine(_status: InstallStatus, _line: string): void {}
75
-
76
- contributeStatus(
77
- paths: InstallPaths,
78
- _root: CliProgram,
79
- detected: InstalledArtifacts,
80
- status: InstallStatus,
81
- ): void {
82
- if (detected.bashCompletion) {
83
- status.bashCompletion = displayInstallPath(paths.bashCompletion);
84
- }
85
- if (detected.zshCompletion) {
86
- status.zshCompletion = displayInstallPath(paths.zshCompletion);
87
- }
88
- if (detected.fishCompletion) {
89
- status.fishCompletion = displayInstallPath(paths.fishCompletion);
90
- }
91
- }
92
-
93
- protected buildInstallActions(ctx: TargetPlanContext): InstallAction[] {
94
- const shells = detectShells();
95
- const actions: InstallAction[] = [];
96
- const refresh = ctx.mode === "refresh";
97
-
98
- for (const s of SHELLS) {
99
- const onPath = shells[s.shell];
100
- const path = s.path(ctx.paths);
101
- const wasInstalled = ctx.detected[s.detectedKey];
102
- if (!onPath || (refresh && !wasInstalled)) continue;
103
-
104
- actions.push({
105
- kind: "completions",
106
- summary: `${s.label}: ${displayInstallPath(path)}`,
107
- message: `Writing ${s.label} to ${displayInstallPath(path)}`,
108
- run: () => {
109
- const all = installCompletions(ctx.root, ctx.paths, ctx.dry);
110
- return all.filter((p) => p === path);
111
- },
112
- });
113
- }
114
- return actions;
115
- }
116
-
117
- protected buildUninstallActions(ctx: TargetPlanContext): UninstallAction[] {
118
- const actions: UninstallAction[] = [];
119
- for (const s of SHELLS) {
120
- if (!ctx.detected[s.detectedKey]) continue;
121
- const path = s.path(ctx.paths);
122
- actions.push({
123
- kind: "completions",
124
- summary: `${s.label}: ${displayInstallPath(path)}`,
125
- message: `Removing ${s.label} ${displayInstallPath(path)}`,
126
- run: () => uninstallCompletions(ctx.paths, ctx.dry).filter((p) => p === path),
127
- });
128
- }
129
- return actions;
130
- }
131
- }
132
-
133
- export const completionsTarget = new CompletionsInstallTarget();