@marcusrbrown/infra 0.3.1 → 0.3.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.
package/package.json
CHANGED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import {resolve} from 'node:path'
|
|
2
|
+
import {afterEach, beforeEach, describe, expect, it} from 'bun:test'
|
|
3
|
+
|
|
4
|
+
const cliDir = resolve(import.meta.dir, '../..')
|
|
5
|
+
|
|
6
|
+
const envKeys = ['CLIPROXY_DOMAIN', 'HOME', 'PATH', 'SSH_AUTH_SOCK'] as const
|
|
7
|
+
|
|
8
|
+
type ManagedEnvKey = (typeof envKeys)[number]
|
|
9
|
+
|
|
10
|
+
let originalEnv: Partial<Record<ManagedEnvKey, string | undefined>>
|
|
11
|
+
|
|
12
|
+
function restoreManagedEnv(): void {
|
|
13
|
+
for (const key of envKeys) {
|
|
14
|
+
const value = originalEnv[key]
|
|
15
|
+
|
|
16
|
+
if (value === undefined) {
|
|
17
|
+
delete process.env[key]
|
|
18
|
+
continue
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
process.env[key] = value
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function runLoginCommand(
|
|
26
|
+
args: string[],
|
|
27
|
+
envOverrides: Partial<Record<ManagedEnvKey, string | undefined>> = {},
|
|
28
|
+
): Promise<{stdout: string; stderr: string; exitCode: number}> {
|
|
29
|
+
const env = {...process.env}
|
|
30
|
+
|
|
31
|
+
for (const [key, value] of Object.entries(envOverrides)) {
|
|
32
|
+
if (value === undefined) {
|
|
33
|
+
delete env[key]
|
|
34
|
+
continue
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
env[key] = value
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const proc = Bun.spawn(['bun', 'src/cli.ts', 'cliproxy', 'login', ...args], {
|
|
41
|
+
cwd: cliDir,
|
|
42
|
+
env: {
|
|
43
|
+
...env,
|
|
44
|
+
HOME: env.HOME ?? '/tmp/test-home',
|
|
45
|
+
NO_COLOR: '1',
|
|
46
|
+
PATH: env.PATH ?? '/usr/bin:/bin',
|
|
47
|
+
SSH_AUTH_SOCK: env.SSH_AUTH_SOCK ?? '/tmp/test-sock',
|
|
48
|
+
},
|
|
49
|
+
stdout: 'pipe',
|
|
50
|
+
stderr: 'pipe',
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
const [stdout, stderr, exitCode] = await Promise.all([
|
|
54
|
+
new Response(proc.stdout).text(),
|
|
55
|
+
new Response(proc.stderr).text(),
|
|
56
|
+
proc.exited,
|
|
57
|
+
])
|
|
58
|
+
|
|
59
|
+
return {stdout, stderr, exitCode}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
describe('cliproxy login', () => {
|
|
63
|
+
beforeEach(() => {
|
|
64
|
+
originalEnv = Object.fromEntries(envKeys.map(key => [key, process.env[key]]))
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
afterEach(() => {
|
|
68
|
+
restoreManagedEnv()
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
describe('validation', () => {
|
|
72
|
+
it('rejects unsupported providers', async () => {
|
|
73
|
+
const {stderr, exitCode} = await runLoginCommand(['openai'])
|
|
74
|
+
expect(exitCode).not.toBe(0)
|
|
75
|
+
expect(stderr).toContain('Unsupported provider')
|
|
76
|
+
expect(stderr).toContain('only "claude" is supported')
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it('requires interactive terminal (checked before SSH_AUTH_SOCK)', async () => {
|
|
80
|
+
const {stderr, exitCode} = await runLoginCommand(['claude'], {SSH_AUTH_SOCK: undefined})
|
|
81
|
+
expect(exitCode).not.toBe(0)
|
|
82
|
+
expect(stderr).toContain('interactive terminal')
|
|
83
|
+
})
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
describe('host resolution', () => {
|
|
87
|
+
it('uses CLIPROXY_DOMAIN env var', async () => {
|
|
88
|
+
const {stderr, exitCode} = await runLoginCommand(['claude'], {
|
|
89
|
+
CLIPROXY_DOMAIN: 'custom.host.example',
|
|
90
|
+
})
|
|
91
|
+
expect(exitCode).not.toBe(0)
|
|
92
|
+
expect(stderr).toContain('interactive terminal')
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it('uses --host flag', async () => {
|
|
96
|
+
const {stderr, exitCode} = await runLoginCommand(['claude', '--host', 'custom.host.example'])
|
|
97
|
+
expect(exitCode).not.toBe(0)
|
|
98
|
+
expect(stderr).toContain('interactive terminal')
|
|
99
|
+
})
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
describe('unit: resolveHost', () => {
|
|
103
|
+
it('returns input when provided', async () => {
|
|
104
|
+
const {resolveHost} = await import('./cliproxy-login')
|
|
105
|
+
expect(resolveHost('test.example.com')).toBe('test.example.com')
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
it('falls back to CLIPROXY_DOMAIN', async () => {
|
|
109
|
+
process.env.CLIPROXY_DOMAIN = 'env.example.com'
|
|
110
|
+
const {resolveHost} = await import('./cliproxy-login')
|
|
111
|
+
expect(resolveHost()).toBe('env.example.com')
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
it('falls back to default host', async () => {
|
|
115
|
+
delete process.env.CLIPROXY_DOMAIN
|
|
116
|
+
const {resolveHost} = await import('./cliproxy-login')
|
|
117
|
+
expect(resolveHost()).toBe('cliproxy.fro.bot')
|
|
118
|
+
})
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
describe('unit: requireSshAuthSock', () => {
|
|
122
|
+
it('returns SSH_AUTH_SOCK when set', async () => {
|
|
123
|
+
process.env.SSH_AUTH_SOCK = '/tmp/test-agent.sock'
|
|
124
|
+
const {requireSshAuthSock} = await import('./cliproxy-login')
|
|
125
|
+
expect(requireSshAuthSock()).toBe('/tmp/test-agent.sock')
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
it('throws when SSH_AUTH_SOCK is not set', async () => {
|
|
129
|
+
delete process.env.SSH_AUTH_SOCK
|
|
130
|
+
const {requireSshAuthSock} = await import('./cliproxy-login')
|
|
131
|
+
expect(() => requireSshAuthSock()).toThrow('SSH_AUTH_SOCK is required')
|
|
132
|
+
})
|
|
133
|
+
})
|
|
134
|
+
})
|
|
@@ -43,6 +43,10 @@ export function registerCliproxyLogin(cli: ReturnType<typeof goke>): void {
|
|
|
43
43
|
throw new Error(`Unsupported provider "${provider}". Currently only "claude" is supported.`)
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
if (!process.stdin.isTTY) {
|
|
47
|
+
throw new Error('cliproxy login requires an interactive terminal. Run from a shell with TTY attached.')
|
|
48
|
+
}
|
|
49
|
+
|
|
46
50
|
const host = resolveHost(options.host)
|
|
47
51
|
const sshAuthSock = requireSshAuthSock()
|
|
48
52
|
const path = process.env.PATH
|
|
@@ -60,13 +64,23 @@ export function registerCliproxyLogin(cli: ReturnType<typeof goke>): void {
|
|
|
60
64
|
'cd /opt/cliproxy && docker compose exec cli-proxy-api /CLIProxyAPI/CLIProxyAPI --no-browser --claude-login'
|
|
61
65
|
|
|
62
66
|
const child = Bun.spawn(
|
|
63
|
-
[
|
|
67
|
+
[
|
|
68
|
+
'ssh',
|
|
69
|
+
'-tt',
|
|
70
|
+
'-o',
|
|
71
|
+
'BatchMode=yes',
|
|
72
|
+
'-o',
|
|
73
|
+
'ConnectTimeout=10',
|
|
74
|
+
`${DEFAULT_REMOTE_USER}@${host}`,
|
|
75
|
+
remoteCommand,
|
|
76
|
+
],
|
|
64
77
|
{
|
|
65
78
|
env: {
|
|
66
79
|
PATH: path,
|
|
67
80
|
HOME: home,
|
|
68
81
|
SSH_AUTH_SOCK: sshAuthSock,
|
|
69
82
|
},
|
|
83
|
+
stdin: 'inherit',
|
|
70
84
|
stdout: 'inherit',
|
|
71
85
|
stderr: 'inherit',
|
|
72
86
|
},
|