@marcusrbrown/infra 0.13.0 → 0.13.2

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 (108) hide show
  1. package/dist/cli.js +4977 -0
  2. package/package.json +7 -3
  3. package/src/__snapshots__/cli.test.ts.snap +0 -219
  4. package/src/__test__/mcp-ctx-fixture.test.ts +0 -108
  5. package/src/__test__/mcp-ctx-fixture.ts +0 -104
  6. package/src/cli.test.ts +0 -66
  7. package/src/cli.ts +0 -43
  8. package/src/commands/cliproxy/config.test.ts +0 -475
  9. package/src/commands/cliproxy/config.ts +0 -238
  10. package/src/commands/cliproxy/deploy.test.ts +0 -181
  11. package/src/commands/cliproxy/deploy.ts +0 -157
  12. package/src/commands/cliproxy/host.test.ts +0 -73
  13. package/src/commands/cliproxy/host.ts +0 -31
  14. package/src/commands/cliproxy/index.ts +0 -21
  15. package/src/commands/cliproxy/keys.test.ts +0 -326
  16. package/src/commands/cliproxy/keys.ts +0 -201
  17. package/src/commands/cliproxy/login.test.ts +0 -415
  18. package/src/commands/cliproxy/login.ts +0 -131
  19. package/src/commands/cliproxy/models.test.ts +0 -592
  20. package/src/commands/cliproxy/models.ts +0 -237
  21. package/src/commands/cliproxy/open.test.ts +0 -132
  22. package/src/commands/cliproxy/open.ts +0 -78
  23. package/src/commands/cliproxy/setup/gh.test.ts +0 -218
  24. package/src/commands/cliproxy/setup/gh.ts +0 -250
  25. package/src/commands/cliproxy/setup/preview.test.ts +0 -159
  26. package/src/commands/cliproxy/setup/preview.ts +0 -41
  27. package/src/commands/cliproxy/setup/prompts.test.ts +0 -58
  28. package/src/commands/cliproxy/setup/prompts.ts +0 -99
  29. package/src/commands/cliproxy/setup/providers.test.ts +0 -245
  30. package/src/commands/cliproxy/setup/providers.ts +0 -136
  31. package/src/commands/cliproxy/setup/smoke-test.test.ts +0 -821
  32. package/src/commands/cliproxy/setup/smoke-test.ts +0 -223
  33. package/src/commands/cliproxy/setup/templates.test.ts +0 -357
  34. package/src/commands/cliproxy/setup/templates.ts +0 -145
  35. package/src/commands/cliproxy/setup/validation.test.ts +0 -567
  36. package/src/commands/cliproxy/setup/validation.ts +0 -209
  37. package/src/commands/cliproxy/setup/workflow-analyzer.test.ts +0 -363
  38. package/src/commands/cliproxy/setup/workflow-analyzer.ts +0 -136
  39. package/src/commands/cliproxy/setup.test.ts +0 -2366
  40. package/src/commands/cliproxy/setup.ts +0 -809
  41. package/src/commands/cliproxy/shared.test.ts +0 -118
  42. package/src/commands/cliproxy/shared.ts +0 -84
  43. package/src/commands/cliproxy/status.test.ts +0 -1157
  44. package/src/commands/cliproxy/status.ts +0 -597
  45. package/src/commands/dashboard/deploy.test.ts +0 -215
  46. package/src/commands/dashboard/deploy.ts +0 -135
  47. package/src/commands/dashboard/host.test.ts +0 -79
  48. package/src/commands/dashboard/host.ts +0 -33
  49. package/src/commands/dashboard/index.ts +0 -13
  50. package/src/commands/dashboard/logs.test.ts +0 -317
  51. package/src/commands/dashboard/logs.ts +0 -180
  52. package/src/commands/dashboard/status.test.ts +0 -708
  53. package/src/commands/dashboard/status.ts +0 -293
  54. package/src/commands/gateway/backup.test.ts +0 -426
  55. package/src/commands/gateway/backup.ts +0 -205
  56. package/src/commands/gateway/deploy.test.ts +0 -491
  57. package/src/commands/gateway/deploy.ts +0 -173
  58. package/src/commands/gateway/host.test.ts +0 -89
  59. package/src/commands/gateway/host.ts +0 -33
  60. package/src/commands/gateway/index.ts +0 -17
  61. package/src/commands/gateway/logs.test.ts +0 -263
  62. package/src/commands/gateway/logs.ts +0 -160
  63. package/src/commands/gateway/restore.test.ts +0 -650
  64. package/src/commands/gateway/restore.ts +0 -308
  65. package/src/commands/gateway/status.test.ts +0 -755
  66. package/src/commands/gateway/status.ts +0 -255
  67. package/src/commands/keeweb/deploy.test.ts +0 -177
  68. package/src/commands/keeweb/deploy.ts +0 -191
  69. package/src/commands/keeweb/index.ts +0 -11
  70. package/src/commands/keeweb/open.test.ts +0 -54
  71. package/src/commands/keeweb/open.ts +0 -30
  72. package/src/commands/keeweb/status.test.ts +0 -509
  73. package/src/commands/keeweb/status.ts +0 -359
  74. package/src/commands/mcp.test.ts +0 -260
  75. package/src/commands/mcp.ts +0 -58
  76. package/src/commands/status.test.ts +0 -222
  77. package/src/commands/status.ts +0 -161
  78. package/src/commands/umami/deploy.test.ts +0 -202
  79. package/src/commands/umami/deploy.ts +0 -132
  80. package/src/commands/umami/host.test.ts +0 -79
  81. package/src/commands/umami/host.ts +0 -33
  82. package/src/commands/umami/index.ts +0 -13
  83. package/src/commands/umami/logs.test.ts +0 -191
  84. package/src/commands/umami/logs.ts +0 -163
  85. package/src/commands/umami/status.test.ts +0 -633
  86. package/src/commands/umami/status.ts +0 -285
  87. package/src/commands/vpn/client.test.ts +0 -949
  88. package/src/commands/vpn/client.ts +0 -467
  89. package/src/commands/vpn/deploy.test.ts +0 -208
  90. package/src/commands/vpn/deploy.ts +0 -143
  91. package/src/commands/vpn/host.test.ts +0 -89
  92. package/src/commands/vpn/host.ts +0 -36
  93. package/src/commands/vpn/index.ts +0 -15
  94. package/src/commands/vpn/logs.test.ts +0 -234
  95. package/src/commands/vpn/logs.ts +0 -136
  96. package/src/commands/vpn/peers.test.ts +0 -478
  97. package/src/commands/vpn/shared.test.ts +0 -454
  98. package/src/commands/vpn/shared.ts +0 -170
  99. package/src/commands/vpn/status.test.ts +0 -142
  100. package/src/commands/vpn/status.ts +0 -78
  101. package/src/conventions.test.ts +0 -1180
  102. package/src/lib/action-ctx.ts +0 -25
  103. package/src/lib/known-hosts.test.ts +0 -192
  104. package/src/lib/known-hosts.ts +0 -76
  105. package/src/lib/redact.ts +0 -21
  106. package/src/lib/ssh-identity.test.ts +0 -134
  107. package/src/lib/ssh-identity.ts +0 -76
  108. /package/{src/resources/known_hosts → dist/known_hosts-tk724cp0.} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marcusrbrown/infra",
3
- "version": "0.13.0",
3
+ "version": "0.13.2",
4
4
  "description": "Infrastructure management CLI — deploy automation, health checks, and MCP bridge",
5
5
  "keywords": [
6
6
  "infra",
@@ -22,13 +22,16 @@
22
22
  "./vpn/peers": "./src/commands/vpn/peers.ts"
23
23
  },
24
24
  "bin": {
25
- "infra": "src/cli.ts"
25
+ "infra": "./dist/cli.js"
26
26
  },
27
27
  "files": [
28
- "src/"
28
+ "dist",
29
+ "src/commands/vpn/peers.ts"
29
30
  ],
30
31
  "scripts": {
32
+ "build": "bun run scripts/build.ts",
31
33
  "cli": "bun run src/cli.ts",
34
+ "prepack": "bun run build",
32
35
  "test": "bun test"
33
36
  },
34
37
  "dependencies": {
@@ -39,6 +42,7 @@
39
42
  "zod": "^4.3.6"
40
43
  },
41
44
  "devDependencies": {
45
+ "@marcusrbrown/infra-shared": "workspace:*",
42
46
  "@modelcontextprotocol/sdk": "1.29.0",
43
47
  "yaml": "2.9.0"
44
48
  },
@@ -1,219 +0,0 @@
1
- // Bun Snapshot v1, https://bun.sh/docs/test/snapshots
2
-
3
- exports[`infra CLI shows root help with registered top-level commands 1`] = `
4
- "infra/x.x.x
5
-
6
-
7
- Usage:
8
- $ infra <command> [options]
9
-
10
-
11
- Commands:
12
- keeweb status Show operational health of the KeeWeb deployment
13
-
14
- --verbose Enable verbose output for all commands
15
-
16
-
17
- keeweb deploy Trigger KeeWeb deployment. Default mode dispatches the GitHub Deploy KeeWeb workflow. Use --local to run apps/keeweb/deploy.sh directly from this repo.
18
-
19
- --local Run local deployment using apps/keeweb/deploy.sh instead of triggering GitHub Actions. (default: false)
20
- --nginx Include nginx config deployment. Valid only with --local and passed through to deploy.sh as --nginx. (default: false)
21
- --dry-run Print planned actions without validating preconditions, executing deploy.sh, or triggering GitHub Actions. (default: false)
22
-
23
-
24
- keeweb open Open KeeWeb in the default browser
25
-
26
-
27
- cliproxy status Show operational health of CLIProxyAPI and its management endpoints.
28
-
29
- --url [url] Base URL for CLIProxyAPI health checks. Falls back to CLIPROXY_URL or https://cliproxy.fro.bot.
30
- --key [key] Management API key. Falls back to CLIPROXY_MANAGEMENT_KEY when omitted.
31
- --api-key [key] Downstream API key (bearer) for the upstream provider-auth probe. Falls back to CLIPROXY_API_KEY. Skipped when absent.
32
- --verbose Enable verbose output for all commands
33
-
34
-
35
- cliproxy deploy Deploy CLIProxyAPI. Default mode triggers the GitHub Deploy CLIProxy workflow, while --local runs apps/cliproxy/src/deploy.ts directly with Bun.
36
-
37
- --local Run local deployment with Bun using apps/cliproxy/src/deploy.ts instead of triggering GitHub Actions. (default: false)
38
- --force-config Force upload config.yaml even if it exists on the server. WARNING: overwrites runtime API keys and settings. (default: false)
39
- --dry-run Validate deploy prerequisites and print planned actions without executing local deploy or dispatching workflow. (default: false)
40
-
41
-
42
- cliproxy config get Fetch current CLIProxyAPI config as JSON.
43
-
44
- --url [url] Base URL for CLIProxyAPI management requests. Falls back to CLIPROXY_URL or https://cliproxy.fro.bot.
45
- --key [key] Management API key. Falls back to CLIPROXY_MANAGEMENT_KEY when omitted.
46
- --output [file] Write config JSON to a file instead of stdout. File permissions set to 0600 (owner-read-only).
47
- --json Output raw JSON instead of aligned key: value columns.
48
-
49
-
50
- cliproxy config set <field> <value> Update a mutable CLIProxyAPI config field via its dedicated management endpoint.
51
-
52
- --url [url] Base URL for CLIProxyAPI management requests. Falls back to CLIPROXY_URL or https://cliproxy.fro.bot.
53
- --key [key] Management API key. Falls back to CLIPROXY_MANAGEMENT_KEY when omitted.
54
-
55
-
56
- cliproxy keys list List CLIProxyAPI API keys from the management API.
57
-
58
- --url [url] Base URL for CLIProxyAPI management requests. Falls back to CLIPROXY_URL or https://cliproxy.fro.bot.
59
- --key [key] Management API key. Falls back to CLIPROXY_MANAGEMENT_KEY when omitted.
60
- --json Output raw JSON array instead of a numbered list.
61
-
62
-
63
- cliproxy keys add <key> Add an API key by fetching current keys, appending the value, and replacing full key set.
64
-
65
- --url [url] Base URL for CLIProxyAPI management requests. Falls back to CLIPROXY_URL or https://cliproxy.fro.bot.
66
- --key [key] Management API key. Falls back to CLIPROXY_MANAGEMENT_KEY when omitted.
67
-
68
-
69
- cliproxy keys remove <key> Remove an API key via management API endpoint query parameter.
70
-
71
- --url [url] Base URL for CLIProxyAPI management requests. Falls back to CLIPROXY_URL or https://cliproxy.fro.bot.
72
- --key [key] Management API key. Falls back to CLIPROXY_MANAGEMENT_KEY when omitted.
73
-
74
-
75
- cliproxy login <provider> Run provider login on the remote CLIProxyAPI host. Supported providers: claude, codex.
76
-
77
- --host [host] CLIProxyAPI droplet host for SSH execution. Falls back to CLIPROXY_DOMAIN or cliproxy.fro.bot.
78
-
79
-
80
- cliproxy models [provider] List the models CLIProxyAPI serves.
81
-
82
- --url [url] Base URL for CLIProxyAPI. Falls back to CLIPROXY_URL or https://cliproxy.fro.bot.
83
- --key [key] API key (bearer). Falls back to CLIPROXY_API_KEY when omitted.
84
- --verbose Show owned_by and created date for each model.
85
-
86
-
87
- cliproxy open Launch CLIProxyAPI TUI dashboard via SSH.
88
-
89
- --host [host] SSH host for CLIProxyAPI server. Falls back to CLIPROXY_DOMAIN or cliproxy.fro.bot.
90
-
91
-
92
- cliproxy setup Interactively onboard a GitHub repository to CLIProxyAPI by creating or reusing a key and wiring the required GitHub secrets and variables.
93
-
94
- --key [key] Existing CLIProxyAPI API key value. When provided, setup skips key creation and reuses this key for GitHub secrets.
95
- --repo [repo] Target GitHub repository in owner/repo format. Skips the repository prompt when provided.
96
- --harness [harness] Harness template to configure. Choose opencode, claude-code, or generic. Generic remains interactive-only.
97
- --providers [providers] Comma-separated list of providers to enable. Default: anthropic. Supported values: anthropic, openai. Example: --providers anthropic,openai
98
- --model [model] Override the default model. Must be provider-prefixed and lowercase. Required when multiple providers selected. Examples: anthropic/claude-sonnet-4-6, openai/gpt-4o
99
- --force Overwrite existing GitHub secrets and variables without prompting.
100
- --dry-run Print the plan without applying any changes.
101
- --verify-smoke Run a smoke test against the proxy after setup completes.
102
- --ack-key-reuse Acknowledge that --key matches the bearer token inside the existing OPENCODE_AUTH_JSON. Required in non-interactive mode when --key is supplied for a repo with existing OPENCODE_AUTH_JSON. (default: false)
103
-
104
-
105
- gateway status Show operational health of the gateway deployment via docker compose ps.
106
-
107
- --key [key] Environment variable name holding the SSH host. Falls back to GATEWAY_HOST when omitted.
108
-
109
-
110
- gateway deploy Deploy the gateway. Default mode triggers the GitHub Deploy Gateway workflow, while --local runs apps/gateway deploy directly with Bun.
111
-
112
- --local Run local deployment with Bun using apps/gateway instead of triggering GitHub Actions. (default: false)
113
- --dry-run Validate deploy prerequisites and print planned actions without executing local deploy or dispatching workflow. (default: false)
114
- --force-recreate Force recreate containers. Forwarded only in local mode. (default: false)
115
-
116
-
117
- gateway logs [service] Stream logs from a gateway service via SSH and docker compose.
118
-
119
- --tail [n] Number of log lines to tail from each service. (default: 100)
120
- --allow-ci Allow log streaming in CI environments. Logs may contain sensitive credentials. (default: false)
121
- --key [key] Environment variable name holding the SSH host. Falls back to GATEWAY_HOST when omitted.
122
-
123
-
124
- gateway backup Back up the mitmproxy CA certificate and private key from the gateway Docker volume to a local tarball. The output contains the CA private trust anchor — treat it as sensitive material.
125
-
126
- --output [file] Local path to write the CA tarball. File permissions are set to 0600 after writing. (default: apps/gateway/.local/mitmproxy-ca.tar)
127
- --include-ca Include the mitmproxy CA certificate and private key in the backup. Currently the only supported backup target. (default: true)
128
-
129
-
130
- gateway restore Restore the mitmproxy CA certificate and private key from a local tarball into the gateway Docker volume. Restarts mitmproxy and gateway services after restore and confirms byte-equal content before reporting success.
131
-
132
- --input <file> Path to the tarball produced by \`gateway backup --include-ca\`. Required.
133
- --include-ca Restore the mitmproxy CA certificate and private key. Currently the only supported restore target. (default: true)
134
-
135
-
136
- umami status Show operational health of the Umami analytics deployment via docker compose ps.
137
-
138
- --key [key] Environment variable name holding the SSH host. Falls back to UMAMI_DOMAIN when omitted.
139
-
140
-
141
- umami deploy Deploy Umami analytics. Default mode triggers the GitHub Deploy Umami workflow, while --local runs apps/umami deploy directly with Bun.
142
-
143
- --local Run local deployment with Bun using apps/umami instead of triggering GitHub Actions. (default: false)
144
- --dry-run Validate deploy prerequisites and print planned actions without executing local deploy or dispatching workflow. (default: false)
145
-
146
-
147
- umami logs [service] Stream logs from an Umami service via SSH and docker compose.
148
-
149
- --tail [n] Number of log lines to tail from each service. (default: 100)
150
- --allow-ci Allow log streaming in CI environments. Logs may contain sensitive credentials. (default: false)
151
- --key [key] Environment variable name holding the SSH host. Falls back to UMAMI_DOMAIN when omitted.
152
-
153
-
154
- dashboard status Show operational health of the dashboard deployment via docker compose ps.
155
-
156
- --key [key] Environment variable name holding the SSH host. Falls back to DASHBOARD_DOMAIN when omitted.
157
-
158
-
159
- dashboard deploy Deploy the dashboard. Default mode triggers the GitHub Deploy Dashboard workflow, while --local runs apps/dashboard deploy directly with Bun.
160
-
161
- --local Run local deployment with Bun using apps/dashboard instead of triggering GitHub Actions. (default: false)
162
- --dry-run Validate deploy prerequisites and print planned actions without executing local deploy or dispatching workflow. (default: false)
163
-
164
-
165
- dashboard logs [service] Stream logs from a dashboard service via SSH and docker compose.
166
-
167
- --tail [n] Number of log lines to tail from each service. (default: 100)
168
- --allow-ci Allow log streaming in CI environments. Logs may contain sensitive credentials. (default: false)
169
- --key [key] Environment variable name holding the SSH host. Falls back to DASHBOARD_DOMAIN when omitted.
170
-
171
-
172
- vpn status Show operational health of the VPN box via SSH and wg show.
173
-
174
- --key [key] Environment variable name holding the SSH host. Falls back to VPN_HOST when omitted.
175
-
176
-
177
- vpn deploy Deploy the VPN. Default mode triggers the GitHub Deploy VPN workflow, while --local runs apps/vpn deploy directly with Bun.
178
-
179
- --local Run local deployment with Bun using apps/vpn instead of triggering GitHub Actions. (default: false)
180
- --dry-run Validate deploy prerequisites and print planned actions without executing local deploy or dispatching workflow. (default: false)
181
- --force-server-key Force regeneration of the server WireGuard keypair. WARNING: invalidates all existing client configs. (default: false)
182
-
183
-
184
- vpn logs Stream WireGuard VPN logs via SSH and journalctl.
185
-
186
- --tail [n] Number of log lines to tail. (default: 100)
187
- --allow-ci Allow log streaming in CI environments. Logs may contain sensitive VPN data. (default: false)
188
- --key [key] Environment variable name holding the SSH host. Falls back to VPN_HOST when omitted.
189
-
190
-
191
- vpn client add <name> Add a new VPN client peer. Generates a keypair, assigns a tunnel IP, writes the client .conf to apps/vpn/clients/, and syncs the roster to the VPN_PEERS GitHub secret (best-effort).
192
-
193
- --split-tunnel [cidrs] Comma-separated CIDRs for split-tunnel routing. Defaults to 0.0.0.0/0 (full tunnel).
194
- --dns [dns] DNS server for the client config.
195
- --server-pubkey [key] Server public key. Falls back to VPN_SERVER_PUBKEY env var.
196
- --endpoint [host] VPN server endpoint (IP or hostname). Falls back to VPN_HOST env var.
197
-
198
-
199
- vpn client list List all VPN client peers from peers.json.
200
-
201
-
202
- vpn client remove <name> Remove a VPN client peer from peers.json and sync the roster to the VPN_PEERS GitHub secret (best-effort). Run \`infra vpn deploy\` to revoke access.
203
-
204
-
205
- status Show status of all deployments
206
-
207
- --json Output machine-readable JSON with keeweb, cliproxy, gateway, umami, dashboard, and vpn summary objects.
208
- --verbose Include verbose per-app health check details when building the summary rows.
209
-
210
-
211
- mcp Start a stdio MCP server exposing all CLI commands as tools for coding agents
212
-
213
-
214
- Options:
215
- --verbose Enable verbose output for all commands
216
- -h, --help Display this message
217
- -v, --version Display version number
218
- "
219
- `;
@@ -1,108 +0,0 @@
1
- import {describe, expect, it} from 'bun:test'
2
-
3
- import {createCapturedCtx, expectCapturedToInclude} from './mcp-ctx-fixture'
4
-
5
- describe('createCapturedCtx', () => {
6
- describe('ctx.console.log', () => {
7
- it('populates captured.stdout with a single string arg', () => {
8
- const {ctx, captured} = createCapturedCtx()
9
- ctx.console.log('hello')
10
- expect(captured.stdout).toEqual(['hello'])
11
- })
12
-
13
- it('space-joins multiple args matching console.log behavior', () => {
14
- const {ctx, captured} = createCapturedCtx()
15
- ctx.console.log('foo', 'bar', 42)
16
- expect(captured.stdout).toEqual(['foo bar 42'])
17
- })
18
- })
19
-
20
- describe('ctx.process.stdout.write', () => {
21
- it('pushes a string chunk to captured.stdout', () => {
22
- const {ctx, captured} = createCapturedCtx()
23
- ctx.process.stdout.write('chunk')
24
- expect(captured.stdout).toEqual(['chunk'])
25
- })
26
-
27
- it('decodes a Uint8Array chunk as utf-8 and pushes to captured.stdout', () => {
28
- const {ctx, captured} = createCapturedCtx()
29
- ctx.process.stdout.write(new TextEncoder().encode('buf'))
30
- expect(captured.stdout).toEqual(['buf'])
31
- })
32
- })
33
-
34
- describe('ctx.process.stderr.write', () => {
35
- it('pushes a string chunk to captured.stderr', () => {
36
- const {ctx, captured} = createCapturedCtx()
37
- ctx.process.stderr.write('err-chunk')
38
- expect(captured.stderr).toEqual(['err-chunk'])
39
- })
40
- })
41
-
42
- describe('ctx.console.error', () => {
43
- it('pushes formatted string to captured.stderr', () => {
44
- const {ctx, captured} = createCapturedCtx()
45
- ctx.console.error('oops', 99)
46
- expect(captured.stderr).toEqual(['oops 99'])
47
- })
48
- })
49
-
50
- describe('ctx.process.exit', () => {
51
- it('throws after populating captured.exit with code 1', () => {
52
- const {ctx, captured} = createCapturedCtx()
53
- let threw = false
54
- try {
55
- ctx.process.exit(1)
56
- } catch {
57
- threw = true
58
- }
59
- expect(threw).toBe(true)
60
- expect(captured.exit).toEqual({code: 1})
61
- })
62
-
63
- it('also throws for exit code 0', () => {
64
- const {ctx, captured} = createCapturedCtx()
65
- let threw = false
66
- try {
67
- ctx.process.exit(0)
68
- } catch {
69
- threw = true
70
- }
71
- expect(threw).toBe(true)
72
- expect(captured.exit).toEqual({code: 0})
73
- })
74
- })
75
-
76
- it('starts with empty stdout, stderr, and null exit', () => {
77
- const {captured} = createCapturedCtx()
78
- expect(captured.stdout).toEqual([])
79
- expect(captured.stderr).toEqual([])
80
- expect(captured.exit).toBeNull()
81
- })
82
- })
83
-
84
- describe('expectCapturedToInclude', () => {
85
- it('returns true when stdout contains the string marker', () => {
86
- const {ctx, captured} = createCapturedCtx()
87
- ctx.console.log('hello world')
88
- expect(expectCapturedToInclude(captured, 'hello')).toBe(true)
89
- })
90
-
91
- it('returns false when stdout does not contain the string marker', () => {
92
- const {ctx, captured} = createCapturedCtx()
93
- ctx.console.log('goodbye')
94
- expect(expectCapturedToInclude(captured, 'hello')).toBe(false)
95
- })
96
-
97
- it('returns true when stdout matches a regex marker', () => {
98
- const {ctx, captured} = createCapturedCtx()
99
- ctx.console.log('hello world')
100
- expect(expectCapturedToInclude(captured, /^hello/)).toBe(true)
101
- })
102
-
103
- it('returns false when stdout does not match a regex marker', () => {
104
- const {ctx, captured} = createCapturedCtx()
105
- ctx.console.log('hello world')
106
- expect(expectCapturedToInclude(captured, /^world/)).toBe(false)
107
- })
108
- })
@@ -1,104 +0,0 @@
1
- import {format} from 'node:util'
2
-
3
- export type {ActionCtx} from '../lib/action-ctx'
4
-
5
- /**
6
- * Captured output from a `createCapturedCtx()` invocation.
7
- */
8
- export interface CapturedOutput {
9
- stdout: string[]
10
- stderr: string[]
11
- exit: {code: number} | null
12
- }
13
-
14
- /**
15
- * Error thrown by `ctx.process.exit()` in the mock context.
16
- * Mirrors the `GokeProcessExit` pattern from goke — exit always throws
17
- * so action code cannot silently swallow it.
18
- */
19
- export class MockProcessExit extends Error {
20
- readonly code: number
21
-
22
- constructor(code: number) {
23
- super(`MockProcessExit: process.exit(${code})`)
24
- this.name = 'MockProcessExit'
25
- this.code = code
26
- }
27
- }
28
-
29
- /**
30
- * Minimal surface of `GokeExecutionContext` that the capture fixture provides.
31
- * Matches `ctx.console.{log,error}`, `ctx.process.{stdout,stderr}.write`,
32
- * and `ctx.process.exit` — the same shape produced by
33
- * `createCallToolExecutionContext` in `@goke/mcp`.
34
- */
35
- export interface CapturedCtx {
36
- console: {
37
- log: (...args: unknown[]) => void
38
- error: (...args: unknown[]) => void
39
- }
40
- process: {
41
- stdout: {write: (chunk: string | Uint8Array) => void}
42
- stderr: {write: (chunk: string | Uint8Array) => void}
43
- exit: (code: number) => never
44
- }
45
- }
46
-
47
- function decodeChunk(chunk: string | Uint8Array): string {
48
- if (typeof chunk === 'string') return chunk
49
- return new TextDecoder().decode(chunk)
50
- }
51
-
52
- /**
53
- * Create a fresh mock `GokeExecutionContext` with output capture.
54
- *
55
- * Each call returns an independent `{ctx, captured}` pair — no shared state.
56
- * Use one call per test to keep tests isolated.
57
- */
58
- export function createCapturedCtx(): {ctx: CapturedCtx; captured: CapturedOutput} {
59
- const captured: CapturedOutput = {
60
- stdout: [],
61
- stderr: [],
62
- exit: null,
63
- }
64
-
65
- const ctx: CapturedCtx = {
66
- console: {
67
- log(...args: unknown[]) {
68
- captured.stdout.push(format(...args))
69
- },
70
- error(...args: unknown[]) {
71
- captured.stderr.push(format(...args))
72
- },
73
- },
74
- process: {
75
- stdout: {
76
- write(chunk: string | Uint8Array) {
77
- captured.stdout.push(decodeChunk(chunk))
78
- },
79
- },
80
- stderr: {
81
- write(chunk: string | Uint8Array) {
82
- captured.stderr.push(decodeChunk(chunk))
83
- },
84
- },
85
- exit(code: number): never {
86
- captured.exit = {code}
87
- throw new MockProcessExit(code)
88
- },
89
- },
90
- }
91
-
92
- return {ctx, captured}
93
- }
94
-
95
- /**
96
- * Returns `true` when the concatenated stdout contains `marker`.
97
- *
98
- * Composable — does not throw. Use with `expect(...).toBe(true)`.
99
- */
100
- export function expectCapturedToInclude(captured: CapturedOutput, marker: string | RegExp): boolean {
101
- const text = captured.stdout.join('')
102
- if (typeof marker === 'string') return text.includes(marker)
103
- return marker.test(text)
104
- }
package/src/cli.test.ts DELETED
@@ -1,66 +0,0 @@
1
- import {resolve} from 'node:path'
2
- import {describe, expect, it} from 'bun:test'
3
-
4
- declare const process: {
5
- env: Record<string, string | undefined>
6
- }
7
-
8
- const cliDir = resolve(import.meta.dir, '..')
9
-
10
- async function runCli(...args: string[]) {
11
- const proc = Bun.spawn(['bun', 'src/cli.ts', ...args], {
12
- cwd: cliDir,
13
- env: {...process.env, NO_COLOR: '1'},
14
- stdout: 'pipe',
15
- stderr: 'pipe',
16
- })
17
-
18
- const [stdout, stderr, exitCode] = await Promise.all([
19
- new Response(proc.stdout).text(),
20
- new Response(proc.stderr).text(),
21
- proc.exited,
22
- ])
23
-
24
- return {stdout, stderr, exitCode}
25
- }
26
-
27
- describe('infra CLI', () => {
28
- it('shows root help with registered top-level commands', async () => {
29
- const {stdout, stderr, exitCode} = await runCli('--help')
30
-
31
- expect(exitCode).toBe(0)
32
- expect(stderr).toBe('')
33
- expect(stdout).toContain('keeweb')
34
- expect(stdout).toContain('status')
35
- expect(stdout).toContain('mcp')
36
- // Normalize version so snapshot survives changeset bumps
37
- const stableOutput = stdout.replace(/infra\/\d+\.\d+\.\d+/, 'infra/x.x.x')
38
- expect(stableOutput).toMatchSnapshot()
39
- })
40
-
41
- it('shows keeweb deploy help with expected flags', async () => {
42
- const {stdout, stderr, exitCode} = await runCli('keeweb', 'deploy', '--help')
43
-
44
- expect(exitCode).toBe(0)
45
- expect(stderr).toBe('')
46
- expect(stdout).toContain('--local')
47
- expect(stdout).toContain('--nginx')
48
- expect(stdout).toContain('--dry-run')
49
- })
50
-
51
- it('falls back to root help for an unknown command', async () => {
52
- const {stdout, stderr, exitCode} = await runCli('nonexistent')
53
-
54
- expect(exitCode).toBe(0)
55
- expect(stderr).toBe('')
56
- expect(stdout).toContain('Usage:')
57
- })
58
-
59
- it('exits non-zero and prints an error for an invalid option', async () => {
60
- const {stdout, stderr, exitCode} = await runCli('keeweb', 'deploy', '--bogus')
61
-
62
- expect(exitCode).not.toBe(0)
63
- expect(stdout).toBe('')
64
- expect(stderr).toContain('--bogus')
65
- })
66
- })
package/src/cli.ts DELETED
@@ -1,43 +0,0 @@
1
- #!/usr/bin/env bun
2
-
3
- import {goke} from 'goke'
4
- import pkg from '../package.json' with {type: 'json'}
5
- import {registerCliproxyCommands} from './commands/cliproxy'
6
- import {registerDashboardCommands} from './commands/dashboard'
7
- import {registerGatewayCommands} from './commands/gateway'
8
- import {registerKeewebCommands} from './commands/keeweb'
9
- import {registerMcp} from './commands/mcp'
10
- import {registerStatus} from './commands/status'
11
- import {registerUmamiCommands} from './commands/umami'
12
- import {registerVpnCommands} from './commands/vpn'
13
-
14
- declare const process: {
15
- argv: string[]
16
- exit: (code?: number) => never
17
- }
18
-
19
- const cli = goke('infra')
20
-
21
- cli.option('--verbose', 'Enable verbose output for all commands')
22
-
23
- registerKeewebCommands(cli)
24
- registerCliproxyCommands(cli)
25
- registerGatewayCommands(cli)
26
- registerUmamiCommands(cli)
27
- registerDashboardCommands(cli)
28
- registerVpnCommands(cli)
29
- registerStatus(cli)
30
- registerMcp(cli)
31
-
32
- cli.help()
33
- cli.version(pkg.version)
34
-
35
- try {
36
- cli.parse(process.argv, {run: false})
37
- await cli.runMatchedCommand()
38
- } catch (error) {
39
- if (error instanceof Error) {
40
- console.error(error.message)
41
- }
42
- process.exit(1)
43
- }