@ripeseed/rs-tunnel 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/README.md +3 -2
  2. package/dist/commands/doctor.js +34 -4
  3. package/dist/commands/doctor.js.map +1 -1
  4. package/dist/commands/doctor.test.d.ts +1 -0
  5. package/dist/commands/doctor.test.js +80 -0
  6. package/dist/commands/doctor.test.js.map +1 -0
  7. package/dist/commands/list.js +45 -7
  8. package/dist/commands/list.js.map +1 -1
  9. package/dist/commands/list.test.d.ts +1 -0
  10. package/dist/commands/list.test.js +84 -0
  11. package/dist/commands/list.test.js.map +1 -0
  12. package/dist/commands/login.d.ts +18 -1
  13. package/dist/commands/login.js +131 -23
  14. package/dist/commands/login.js.map +1 -1
  15. package/dist/commands/login.test.d.ts +1 -0
  16. package/dist/commands/login.test.js +219 -0
  17. package/dist/commands/login.test.js.map +1 -0
  18. package/dist/commands/logout.js +18 -1
  19. package/dist/commands/logout.js.map +1 -1
  20. package/dist/commands/logout.test.d.ts +1 -0
  21. package/dist/commands/logout.test.js +78 -0
  22. package/dist/commands/logout.test.js.map +1 -0
  23. package/dist/commands/stop.js +18 -1
  24. package/dist/commands/stop.js.map +1 -1
  25. package/dist/commands/stop.test.d.ts +1 -0
  26. package/dist/commands/stop.test.js +46 -0
  27. package/dist/commands/stop.test.js.map +1 -0
  28. package/dist/commands/up.d.ts +12 -2
  29. package/dist/commands/up.js +349 -74
  30. package/dist/commands/up.js.map +1 -1
  31. package/dist/commands/up.test.js +589 -233
  32. package/dist/commands/up.test.js.map +1 -1
  33. package/dist/index.js +4 -1
  34. package/dist/index.js.map +1 -1
  35. package/dist/lib/api-client.d.ts +6 -1
  36. package/dist/lib/api-client.js +9 -1
  37. package/dist/lib/api-client.js.map +1 -1
  38. package/dist/lib/clipboard.d.ts +1 -0
  39. package/dist/lib/clipboard.js +50 -0
  40. package/dist/lib/clipboard.js.map +1 -0
  41. package/dist/lib/local-target.d.ts +12 -0
  42. package/dist/lib/local-target.js +48 -0
  43. package/dist/lib/local-target.js.map +1 -0
  44. package/dist/lib/local-target.test.d.ts +1 -0
  45. package/dist/lib/local-target.test.js +23 -0
  46. package/dist/lib/local-target.test.js.map +1 -0
  47. package/dist/lib/up-dashboard.test.js +141 -51
  48. package/dist/lib/up-dashboard.test.js.map +1 -1
  49. package/dist/lib/up-runtime.d.ts +110 -0
  50. package/dist/lib/up-runtime.js +455 -0
  51. package/dist/lib/up-runtime.js.map +1 -0
  52. package/dist/ui/index.d.ts +2 -0
  53. package/dist/ui/index.js +3 -0
  54. package/dist/ui/index.js.map +1 -0
  55. package/dist/ui/output.d.ts +20 -0
  56. package/dist/ui/output.js +50 -0
  57. package/dist/ui/output.js.map +1 -0
  58. package/dist/ui/output.test.d.ts +1 -0
  59. package/dist/ui/output.test.js +84 -0
  60. package/dist/ui/output.test.js.map +1 -0
  61. package/dist/ui/primitives.d.ts +50 -0
  62. package/dist/ui/primitives.js +102 -0
  63. package/dist/ui/primitives.js.map +1 -0
  64. package/dist/ui/primitives.test.d.ts +1 -0
  65. package/dist/ui/primitives.test.js +46 -0
  66. package/dist/ui/primitives.test.js.map +1 -0
  67. package/dist/ui/test-utils.d.ts +10 -0
  68. package/dist/ui/test-utils.js +71 -0
  69. package/dist/ui/test-utils.js.map +1 -0
  70. package/dist/ui/up/UpApp.d.ts +5 -0
  71. package/dist/ui/up/UpApp.js +47 -0
  72. package/dist/ui/up/UpApp.js.map +1 -0
  73. package/dist/ui/up/index.d.ts +4 -0
  74. package/dist/ui/up/index.js +3 -0
  75. package/dist/ui/up/index.js.map +1 -0
  76. package/dist/ui/up/types.d.ts +1 -0
  77. package/dist/ui/up/types.js +2 -0
  78. package/dist/ui/up/types.js.map +1 -0
  79. package/dist/ui/up/up-app.d.ts +2 -0
  80. package/dist/ui/up/up-app.js +2 -0
  81. package/dist/ui/up/up-app.js.map +1 -0
  82. package/dist/ui/up/up-app.test.d.ts +1 -0
  83. package/dist/ui/up/up-app.test.js +266 -0
  84. package/dist/ui/up/up-app.test.js.map +1 -0
  85. package/dist/ui/up/utils.d.ts +19 -0
  86. package/dist/ui/up/utils.js +141 -0
  87. package/dist/ui/up/utils.js.map +1 -0
  88. package/dist/ui/up/views.d.ts +9 -0
  89. package/dist/ui/up/views.js +61 -0
  90. package/dist/ui/up/views.js.map +1 -0
  91. package/package.json +7 -3
  92. package/dist/lib/local-callback.d.ts +0 -9
  93. package/dist/lib/local-callback.js +0 -68
  94. package/dist/lib/local-callback.js.map +0 -1
@@ -0,0 +1,219 @@
1
+ import { afterEach, describe, expect, it, vi } from 'vitest';
2
+ import { loginCommand } from './login.js';
3
+ import * as outputModule from '../ui/output.js';
4
+ import { setTerminalTTY } from '../ui/test-utils.js';
5
+ describe('loginCommand', () => {
6
+ afterEach(() => {
7
+ vi.restoreAllMocks();
8
+ });
9
+ it('prints the auth URL when browser auto-open is skipped', async () => {
10
+ const restoreTTY = setTerminalTTY(false);
11
+ const startSlackAuth = vi.fn(async () => ({
12
+ authorizeUrl: 'https://slack.example.com/auth',
13
+ state: 'expected-state',
14
+ }));
15
+ const getSlackAuthStatus = vi.fn(async () => ({
16
+ status: 'authorized',
17
+ loginCode: 'login-code',
18
+ }));
19
+ const exchangeLoginCode = vi.fn(async () => ({
20
+ accessToken: 'access-token',
21
+ refreshToken: 'refresh-token',
22
+ expiresInSec: 900,
23
+ profile: {
24
+ email: 'test@example.com',
25
+ slackUserId: 'U1',
26
+ slackTeamId: 'T1',
27
+ },
28
+ }));
29
+ const saveSession = vi.fn(async () => { });
30
+ const openUrl = vi.fn(async () => { });
31
+ const logSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
32
+ const dependencies = {
33
+ getCliConfig: () => ({ apiBaseUrl: 'https://api.example.com' }),
34
+ createApiClient: () => ({
35
+ startSlackAuth,
36
+ getSlackAuthStatus,
37
+ exchangeLoginCode,
38
+ }),
39
+ createPkcePair: () => ({
40
+ verifier: 'verifier-value',
41
+ challenge: 'challenge-value',
42
+ }),
43
+ openUrl,
44
+ saveSession,
45
+ sleep: vi.fn(async () => { }),
46
+ };
47
+ try {
48
+ await loginCommand('test@example.com', { skipBrowserOpen: true }, dependencies);
49
+ expect(startSlackAuth).toHaveBeenCalledWith({
50
+ email: 'test@example.com',
51
+ codeChallenge: 'challenge-value',
52
+ });
53
+ expect(openUrl).not.toHaveBeenCalled();
54
+ expect(getSlackAuthStatus).toHaveBeenCalledWith({
55
+ state: 'expected-state',
56
+ });
57
+ expect(exchangeLoginCode).toHaveBeenCalledWith({
58
+ loginCode: 'login-code',
59
+ codeVerifier: 'verifier-value',
60
+ });
61
+ expect(saveSession).toHaveBeenCalledWith(expect.objectContaining({
62
+ accessToken: 'access-token',
63
+ refreshToken: 'refresh-token',
64
+ profile: expect.objectContaining({
65
+ email: 'test@example.com',
66
+ }),
67
+ }));
68
+ expect(logSpy).toHaveBeenCalledWith('Slack Auth URL: https://slack.example.com/auth');
69
+ expect(logSpy).toHaveBeenCalledWith('Waiting for Slack OAuth confirmation...');
70
+ expect(logSpy).toHaveBeenCalledWith('Logged in as test@example.com');
71
+ }
72
+ finally {
73
+ restoreTTY();
74
+ }
75
+ });
76
+ it('opens the browser by default', async () => {
77
+ const restoreTTY = setTerminalTTY(false);
78
+ const openUrl = vi.fn(async () => { });
79
+ const logSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
80
+ const dependencies = {
81
+ getCliConfig: () => ({ apiBaseUrl: 'https://api.example.com' }),
82
+ createApiClient: () => ({
83
+ startSlackAuth: vi.fn(async () => ({
84
+ authorizeUrl: 'https://slack.example.com/auth',
85
+ state: 'expected-state',
86
+ })),
87
+ getSlackAuthStatus: vi.fn(async () => ({
88
+ status: 'authorized',
89
+ loginCode: 'login-code',
90
+ })),
91
+ exchangeLoginCode: vi.fn(async () => ({
92
+ accessToken: 'access-token',
93
+ refreshToken: 'refresh-token',
94
+ expiresInSec: 900,
95
+ profile: {
96
+ email: 'test@example.com',
97
+ slackUserId: 'U1',
98
+ slackTeamId: 'T1',
99
+ },
100
+ })),
101
+ }),
102
+ createPkcePair: () => ({
103
+ verifier: 'verifier-value',
104
+ challenge: 'challenge-value',
105
+ }),
106
+ openUrl,
107
+ saveSession: vi.fn(async () => { }),
108
+ sleep: vi.fn(async () => { }),
109
+ };
110
+ try {
111
+ await loginCommand('test@example.com', {}, dependencies);
112
+ expect(openUrl).toHaveBeenCalledWith('https://slack.example.com/auth');
113
+ expect(logSpy).not.toHaveBeenCalledWith('Slack Auth URL: https://slack.example.com/auth');
114
+ }
115
+ finally {
116
+ restoreTTY();
117
+ }
118
+ });
119
+ it('falls back to printing the URL when browser opening fails', async () => {
120
+ const restoreTTY = setTerminalTTY(false);
121
+ const openUrl = vi.fn(async () => {
122
+ throw new Error('browser unavailable');
123
+ });
124
+ const logSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
125
+ const dependencies = {
126
+ getCliConfig: () => ({ apiBaseUrl: 'https://api.example.com' }),
127
+ createApiClient: () => ({
128
+ startSlackAuth: vi.fn(async () => ({
129
+ authorizeUrl: 'https://slack.example.com/auth',
130
+ state: 'expected-state',
131
+ })),
132
+ getSlackAuthStatus: vi.fn(async () => ({
133
+ status: 'authorized',
134
+ loginCode: 'login-code',
135
+ })),
136
+ exchangeLoginCode: vi.fn(async () => ({
137
+ accessToken: 'access-token',
138
+ refreshToken: 'refresh-token',
139
+ expiresInSec: 900,
140
+ profile: {
141
+ email: 'test@example.com',
142
+ slackUserId: 'U1',
143
+ slackTeamId: 'T1',
144
+ },
145
+ })),
146
+ }),
147
+ createPkcePair: () => ({
148
+ verifier: 'verifier-value',
149
+ challenge: 'challenge-value',
150
+ }),
151
+ openUrl,
152
+ saveSession: vi.fn(async () => { }),
153
+ sleep: vi.fn(async () => { }),
154
+ };
155
+ try {
156
+ await loginCommand('test@example.com', {}, dependencies);
157
+ expect(openUrl).toHaveBeenCalledWith('https://slack.example.com/auth');
158
+ expect(logSpy).toHaveBeenCalledWith('Open this URL in your browser:\nhttps://slack.example.com/auth');
159
+ expect(logSpy).toHaveBeenCalledWith('Waiting for Slack OAuth confirmation...');
160
+ expect(logSpy).toHaveBeenCalledWith('Logged in as test@example.com');
161
+ }
162
+ finally {
163
+ restoreTTY();
164
+ }
165
+ });
166
+ it('prints the auth URL and tears down Ink in interactive skip-browser mode', async () => {
167
+ const restoreTTY = setTerminalTTY(true);
168
+ const logSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
169
+ const inkApp = {
170
+ rerender: vi.fn(),
171
+ unmount: vi.fn(),
172
+ waitUntilExit: vi.fn(async () => { }),
173
+ };
174
+ const renderCliAppSpy = vi.spyOn(outputModule, 'renderCliApp').mockReturnValue(inkApp);
175
+ const stopCliAppSpy = vi.spyOn(outputModule, 'stopCliApp');
176
+ const dependencies = {
177
+ getCliConfig: () => ({ apiBaseUrl: 'https://api.example.com' }),
178
+ createApiClient: () => ({
179
+ startSlackAuth: vi.fn(async () => ({
180
+ authorizeUrl: 'https://slack.example.com/auth',
181
+ state: 'expected-state',
182
+ })),
183
+ getSlackAuthStatus: vi.fn(async () => ({
184
+ status: 'authorized',
185
+ loginCode: 'login-code',
186
+ })),
187
+ exchangeLoginCode: vi.fn(async () => ({
188
+ accessToken: 'access-token',
189
+ refreshToken: 'refresh-token',
190
+ expiresInSec: 900,
191
+ profile: {
192
+ email: 'test@example.com',
193
+ slackUserId: 'U1',
194
+ slackTeamId: 'T1',
195
+ },
196
+ })),
197
+ }),
198
+ createPkcePair: () => ({
199
+ verifier: 'verifier-value',
200
+ challenge: 'challenge-value',
201
+ }),
202
+ openUrl: vi.fn(async () => { }),
203
+ saveSession: vi.fn(async () => { }),
204
+ sleep: vi.fn(async () => { }),
205
+ };
206
+ try {
207
+ await loginCommand('test@example.com', { skipBrowserOpen: true }, dependencies);
208
+ expect(renderCliAppSpy).toHaveBeenCalledTimes(1);
209
+ expect(logSpy).toHaveBeenCalledWith('Slack Auth URL: https://slack.example.com/auth');
210
+ expect(stopCliAppSpy).toHaveBeenCalledWith(inkApp);
211
+ expect(inkApp.unmount).toHaveBeenCalledTimes(1);
212
+ expect(inkApp.waitUntilExit).toHaveBeenCalledTimes(1);
213
+ }
214
+ finally {
215
+ restoreTTY();
216
+ }
217
+ });
218
+ });
219
+ //# sourceMappingURL=login.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"login.test.js","sourceRoot":"","sources":["../../src/commands/login.test.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,9 @@
1
+ import { createElement } from 'react';
1
2
  import { getCliConfig } from '../config.js';
2
3
  import { ApiClient } from '../lib/api-client.js';
3
4
  import { clearSession, loadSession } from '../store/credentials.js';
5
+ import { renderCliApp, stopCliApp, withCliOutput, writePlainBlock } from '../ui/output.js';
6
+ import { ResultCard } from '../ui/primitives.js';
4
7
  export async function logoutCommand() {
5
8
  const config = getCliConfig();
6
9
  const apiClient = new ApiClient(config.apiBaseUrl);
@@ -14,6 +17,20 @@ export async function logoutCommand() {
14
17
  }
15
18
  }
16
19
  await clearSession();
17
- console.log('Logged out.');
20
+ await withCliOutput({
21
+ branch: {
22
+ interactive: async () => {
23
+ const inkApp = renderCliApp(createElement(ResultCard, {
24
+ title: 'RS Tunnel • Logout',
25
+ subtitle: 'Logged out.',
26
+ tone: 'success',
27
+ }), {}, { patchConsole: false, exitOnCtrlC: false });
28
+ await stopCliApp(inkApp);
29
+ },
30
+ plain: async ({ stdout }) => {
31
+ writePlainBlock(stdout, 'Logged out.');
32
+ },
33
+ },
34
+ });
18
35
  }
19
36
  //# sourceMappingURL=logout.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"logout.js","sourceRoot":"","sources":["../../src/commands/logout.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAEpE,MAAM,CAAC,KAAK,UAAU,aAAa;IACjC,MAAM,MAAM,GAAG,YAAY,EAAE,CAAC;IAC9B,MAAM,SAAS,GAAG,IAAI,SAAS,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IACnD,MAAM,OAAO,GAAG,MAAM,WAAW,EAAE,CAAC;IAEpC,IAAI,OAAO,EAAE,CAAC;QACZ,IAAI,CAAC;YACH,MAAM,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC,WAAW,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC;QACpE,CAAC;QAAC,MAAM,CAAC;YACP,mEAAmE;QACrE,CAAC;IACH,CAAC;IAED,MAAM,YAAY,EAAE,CAAC;IACrB,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;AAC7B,CAAC"}
1
+ {"version":3,"file":"logout.js","sourceRoot":"","sources":["../../src/commands/logout.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAEtC,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACpE,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAC3F,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAEjD,MAAM,CAAC,KAAK,UAAU,aAAa;IACjC,MAAM,MAAM,GAAG,YAAY,EAAE,CAAC;IAC9B,MAAM,SAAS,GAAG,IAAI,SAAS,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IACnD,MAAM,OAAO,GAAG,MAAM,WAAW,EAAE,CAAC;IAEpC,IAAI,OAAO,EAAE,CAAC;QACZ,IAAI,CAAC;YACH,MAAM,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC,WAAW,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC;QACpE,CAAC;QAAC,MAAM,CAAC;YACP,mEAAmE;QACrE,CAAC;IACH,CAAC;IAED,MAAM,YAAY,EAAE,CAAC;IACrB,MAAM,aAAa,CAAC;QAClB,MAAM,EAAE;YACN,WAAW,EAAE,KAAK,IAAI,EAAE;gBACtB,MAAM,MAAM,GAAG,YAAY,CACzB,aAAa,CAAC,UAAU,EAAE;oBACxB,KAAK,EAAE,sBAAsB;oBAC7B,QAAQ,EAAE,aAAa;oBACvB,IAAI,EAAE,SAAS;iBAChB,CAAC,EACF,EAAE,EACF,EAAE,YAAY,EAAE,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,CAC5C,CAAC;gBAEF,MAAM,UAAU,CAAC,MAAM,CAAC,CAAC;YAC3B,CAAC;YACD,KAAK,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE;gBAC1B,eAAe,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;YACzC,CAAC;SACF;KACF,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,78 @@
1
+ import { afterEach, describe, expect, it, vi } from 'vitest';
2
+ import { logoutCommand } from './logout.js';
3
+ import { ApiClient } from '../lib/api-client.js';
4
+ import * as credentialsModule from '../store/credentials.js';
5
+ import * as configModule from '../config.js';
6
+ import { setTerminalTTY } from '../ui/test-utils.js';
7
+ describe('logoutCommand', () => {
8
+ afterEach(() => {
9
+ vi.restoreAllMocks();
10
+ });
11
+ it('revokes the session when one exists and clears it locally', async () => {
12
+ const restoreTTY = setTerminalTTY(false);
13
+ const writes = [];
14
+ const writeSpy = vi.spyOn(process.stdout, 'write').mockImplementation(((chunk) => {
15
+ writes.push(Buffer.isBuffer(chunk) ? chunk.toString('utf8') : String(chunk));
16
+ return true;
17
+ }));
18
+ const logout = vi.spyOn(ApiClient.prototype, 'logout').mockResolvedValue();
19
+ const clearSession = vi.spyOn(credentialsModule, 'clearSession').mockResolvedValue();
20
+ vi.spyOn(credentialsModule, 'loadSession').mockResolvedValue({
21
+ accessToken: 'access-token',
22
+ refreshToken: 'refresh-token',
23
+ expiresAtEpochSec: 1,
24
+ profile: {
25
+ email: 'test@example.com',
26
+ slackUserId: 'U1',
27
+ slackTeamId: 'T1',
28
+ },
29
+ });
30
+ vi.spyOn(configModule, 'getCliConfig').mockReturnValue({
31
+ apiBaseUrl: 'https://api.example.com',
32
+ });
33
+ try {
34
+ await logoutCommand();
35
+ expect(logout).toHaveBeenCalledWith('access-token', 'refresh-token');
36
+ expect(clearSession).toHaveBeenCalledTimes(1);
37
+ expect(writes.join('')).toContain('Logged out.');
38
+ }
39
+ finally {
40
+ restoreTTY();
41
+ writeSpy.mockRestore();
42
+ }
43
+ });
44
+ it('still clears the local session when remote logout fails', async () => {
45
+ const restoreTTY = setTerminalTTY(false);
46
+ const writes = [];
47
+ const writeSpy = vi.spyOn(process.stdout, 'write').mockImplementation(((chunk) => {
48
+ writes.push(Buffer.isBuffer(chunk) ? chunk.toString('utf8') : String(chunk));
49
+ return true;
50
+ }));
51
+ const logout = vi.spyOn(ApiClient.prototype, 'logout').mockRejectedValue(new Error('remote unavailable'));
52
+ const clearSession = vi.spyOn(credentialsModule, 'clearSession').mockResolvedValue();
53
+ vi.spyOn(credentialsModule, 'loadSession').mockResolvedValue({
54
+ accessToken: 'access-token',
55
+ refreshToken: 'refresh-token',
56
+ expiresAtEpochSec: 1,
57
+ profile: {
58
+ email: 'test@example.com',
59
+ slackUserId: 'U1',
60
+ slackTeamId: 'T1',
61
+ },
62
+ });
63
+ vi.spyOn(configModule, 'getCliConfig').mockReturnValue({
64
+ apiBaseUrl: 'https://api.example.com',
65
+ });
66
+ try {
67
+ await logoutCommand();
68
+ expect(logout).toHaveBeenCalledTimes(1);
69
+ expect(clearSession).toHaveBeenCalledTimes(1);
70
+ expect(writes.join('')).toContain('Logged out.');
71
+ }
72
+ finally {
73
+ restoreTTY();
74
+ writeSpy.mockRestore();
75
+ }
76
+ });
77
+ });
78
+ //# sourceMappingURL=logout.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logout.test.js","sourceRoot":"","sources":["../../src/commands/logout.test.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,9 @@
1
+ import { createElement } from 'react';
1
2
  import { getCliConfig } from '../config.js';
2
3
  import { ApiClient } from '../lib/api-client.js';
3
4
  import { withAuthenticatedSession } from '../lib/session.js';
5
+ import { renderCliApp, stopCliApp, withCliOutput, writePlainBlock } from '../ui/output.js';
6
+ import { ResultCard } from '../ui/primitives.js';
4
7
  export async function stopCommand(tunnelIdentifier) {
5
8
  if (!tunnelIdentifier) {
6
9
  throw new Error('Tunnel identifier is required.');
@@ -10,6 +13,20 @@ export async function stopCommand(tunnelIdentifier) {
10
13
  await withAuthenticatedSession(apiClient, async (session) => {
11
14
  await apiClient.stopTunnel(session.accessToken, tunnelIdentifier);
12
15
  });
13
- console.log(`Stopped tunnel ${tunnelIdentifier}`);
16
+ await withCliOutput({
17
+ branch: {
18
+ interactive: async () => {
19
+ const inkApp = renderCliApp(createElement(ResultCard, {
20
+ title: 'RS Tunnel • Stop',
21
+ subtitle: `Stopped tunnel ${tunnelIdentifier}`,
22
+ tone: 'success',
23
+ }), {}, { patchConsole: false, exitOnCtrlC: false });
24
+ await stopCliApp(inkApp);
25
+ },
26
+ plain: async ({ stdout }) => {
27
+ writePlainBlock(stdout, `Stopped tunnel ${tunnelIdentifier}`);
28
+ },
29
+ },
30
+ });
14
31
  }
15
32
  //# sourceMappingURL=stop.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"stop.js","sourceRoot":"","sources":["../../src/commands/stop.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AAE7D,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,gBAAwB;IACxD,IAAI,CAAC,gBAAgB,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;IACpD,CAAC;IAED,MAAM,MAAM,GAAG,YAAY,EAAE,CAAC;IAC9B,MAAM,SAAS,GAAG,IAAI,SAAS,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IAEnD,MAAM,wBAAwB,CAAC,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;QAC1D,MAAM,SAAS,CAAC,UAAU,CAAC,OAAO,CAAC,WAAW,EAAE,gBAAgB,CAAC,CAAC;IACpE,CAAC,CAAC,CAAC;IAEH,OAAO,CAAC,GAAG,CAAC,kBAAkB,gBAAgB,EAAE,CAAC,CAAC;AACpD,CAAC"}
1
+ {"version":3,"file":"stop.js","sourceRoot":"","sources":["../../src/commands/stop.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAEtC,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AAC7D,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAC3F,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAEjD,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,gBAAwB;IACxD,IAAI,CAAC,gBAAgB,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;IACpD,CAAC;IAED,MAAM,MAAM,GAAG,YAAY,EAAE,CAAC;IAC9B,MAAM,SAAS,GAAG,IAAI,SAAS,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IAEnD,MAAM,wBAAwB,CAAC,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;QAC1D,MAAM,SAAS,CAAC,UAAU,CAAC,OAAO,CAAC,WAAW,EAAE,gBAAgB,CAAC,CAAC;IACpE,CAAC,CAAC,CAAC;IAEH,MAAM,aAAa,CAAC;QAClB,MAAM,EAAE;YACN,WAAW,EAAE,KAAK,IAAI,EAAE;gBACtB,MAAM,MAAM,GAAG,YAAY,CACzB,aAAa,CAAC,UAAU,EAAE;oBACxB,KAAK,EAAE,oBAAoB;oBAC3B,QAAQ,EAAE,kBAAkB,gBAAgB,EAAE;oBAC9C,IAAI,EAAE,SAAS;iBAChB,CAAC,EACF,EAAE,EACF,EAAE,YAAY,EAAE,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,CAC5C,CAAC;gBAEF,MAAM,UAAU,CAAC,MAAM,CAAC,CAAC;YAC3B,CAAC;YACD,KAAK,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE;gBAC1B,eAAe,CAAC,MAAM,EAAE,kBAAkB,gBAAgB,EAAE,CAAC,CAAC;YAChE,CAAC;SACF;KACF,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,46 @@
1
+ import { afterEach, describe, expect, it, vi } from 'vitest';
2
+ import { stopCommand } from './stop.js';
3
+ import { ApiClient } from '../lib/api-client.js';
4
+ import * as configModule from '../config.js';
5
+ import * as sessionModule from '../lib/session.js';
6
+ import { setTerminalTTY } from '../ui/test-utils.js';
7
+ describe('stopCommand', () => {
8
+ afterEach(() => {
9
+ vi.restoreAllMocks();
10
+ });
11
+ it('stops a tunnel and confirms the identifier in non-interactive terminals', async () => {
12
+ const restoreTTY = setTerminalTTY(false);
13
+ const writes = [];
14
+ const writeSpy = vi.spyOn(process.stdout, 'write').mockImplementation(((chunk) => {
15
+ writes.push(Buffer.isBuffer(chunk) ? chunk.toString('utf8') : String(chunk));
16
+ return true;
17
+ }));
18
+ const stopTunnel = vi.spyOn(ApiClient.prototype, 'stopTunnel').mockResolvedValue();
19
+ vi.spyOn(configModule, 'getCliConfig').mockReturnValue({
20
+ apiBaseUrl: 'https://api.example.com',
21
+ });
22
+ vi.spyOn(sessionModule, 'withAuthenticatedSession').mockImplementation(async (_apiClient, run) => run({
23
+ accessToken: 'access-token',
24
+ refreshToken: 'refresh-token',
25
+ expiresAtEpochSec: 1,
26
+ profile: {
27
+ email: 'test@example.com',
28
+ slackUserId: 'U1',
29
+ slackTeamId: 'T1',
30
+ },
31
+ }));
32
+ try {
33
+ await stopCommand('tunnel-1');
34
+ expect(stopTunnel).toHaveBeenCalledWith('access-token', 'tunnel-1');
35
+ expect(writes.join('')).toContain('Stopped tunnel tunnel-1');
36
+ }
37
+ finally {
38
+ restoreTTY();
39
+ writeSpy.mockRestore();
40
+ }
41
+ });
42
+ it('rejects when the identifier is missing', async () => {
43
+ await expect(stopCommand('')).rejects.toThrow('Tunnel identifier is required.');
44
+ });
45
+ });
46
+ //# sourceMappingURL=stop.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stop.test.js","sourceRoot":"","sources":["../../src/commands/stop.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAE7D,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,KAAK,YAAY,MAAM,cAAc,CAAC;AAC7C,OAAO,KAAK,aAAa,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAErD,QAAQ,CAAC,aAAa,EAAE,GAAG,EAAE;IAC3B,SAAS,CAAC,GAAG,EAAE;QACb,EAAE,CAAC,eAAe,EAAE,CAAC;IACvB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yEAAyE,EAAE,KAAK,IAAI,EAAE;QACvF,MAAM,UAAU,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC;QACzC,MAAM,MAAM,GAAa,EAAE,CAAC;QAC5B,MAAM,QAAQ,GAAG,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,KAA0B,EAAE,EAAE;YACpG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;YAC7E,OAAO,IAAI,CAAC;QACd,CAAC,CAAgC,CAAC,CAAC;QACnC,MAAM,UAAU,GAAG,EAAE,CAAC,KAAK,CAAC,SAAS,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC,iBAAiB,EAAE,CAAC;QAEnF,EAAE,CAAC,KAAK,CAAC,YAAY,EAAE,cAAc,CAAC,CAAC,eAAe,CAAC;YACrD,UAAU,EAAE,yBAAyB;SACtC,CAAC,CAAC;QACH,EAAE,CAAC,KAAK,CAAC,aAAa,EAAE,0BAA0B,CAAC,CAAC,kBAAkB,CAAC,KAAK,EAAE,UAAU,EAAE,GAAG,EAAE,EAAE,CAC/F,GAAG,CAAC;YACF,WAAW,EAAE,cAAc;YAC3B,YAAY,EAAE,eAAe;YAC7B,iBAAiB,EAAE,CAAC;YACpB,OAAO,EAAE;gBACP,KAAK,EAAE,kBAAkB;gBACzB,WAAW,EAAE,IAAI;gBACjB,WAAW,EAAE,IAAI;aAClB;SACF,CAAC,CACH,CAAC;QAEF,IAAI,CAAC;YACH,MAAM,WAAW,CAAC,UAAU,CAAC,CAAC;YAE9B,MAAM,CAAC,UAAU,CAAC,CAAC,oBAAoB,CAAC,cAAc,EAAE,UAAU,CAAC,CAAC;YACpE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,yBAAyB,CAAC,CAAC;QAC/D,CAAC;gBAAS,CAAC;YACT,UAAU,EAAE,CAAC;YACb,QAAQ,CAAC,WAAW,EAAE,CAAC;QACzB,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wCAAwC,EAAE,KAAK,IAAI,EAAE;QACtD,MAAM,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,gCAAgC,CAAC,CAAC;IAClF,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -1,8 +1,10 @@
1
1
  import { spawn } from 'node:child_process';
2
2
  import type { StoredSession } from '../types.js';
3
3
  import { ApiClient } from '../lib/api-client.js';
4
+ import { copyToClipboard } from '../lib/clipboard.js';
5
+ import { probeLocalTarget } from '../lib/local-target.js';
4
6
  import { startLocalProxy } from '../lib/local-proxy.js';
5
- import { createUpDashboard } from '../lib/up-dashboard.js';
7
+ import { createUpRuntimeController, type UpRuntimeController } from '../lib/up-runtime.js';
6
8
  type UpInput = {
7
9
  port: number;
8
10
  url?: string;
@@ -13,8 +15,15 @@ type UpCommandDependencies = {
13
15
  requireSession: (apiClient: ApiClient) => Promise<StoredSession>;
14
16
  saveSession: (session: StoredSession) => Promise<void>;
15
17
  ensureCloudflaredInstalled: () => Promise<string>;
18
+ probeLocalTarget: typeof probeLocalTarget;
19
+ copyToClipboard: typeof copyToClipboard;
20
+ openUrl: (url: string) => Promise<unknown>;
16
21
  startLocalProxy: typeof startLocalProxy;
17
- createUpDashboard: typeof createUpDashboard;
22
+ createUpRuntimeController: typeof createUpRuntimeController;
23
+ renderUpApp: (controller: UpRuntimeController) => {
24
+ stop: () => Promise<void>;
25
+ } | null;
26
+ isInteractiveTerminal: () => boolean;
18
27
  getCliVersion: () => string;
19
28
  spawn: typeof spawn;
20
29
  processRef: {
@@ -22,6 +31,7 @@ type UpCommandDependencies = {
22
31
  removeListener: NodeJS.Process['removeListener'];
23
32
  exit: (code?: number) => never;
24
33
  stderr: NodeJS.WriteStream;
34
+ stdout?: NodeJS.WriteStream;
25
35
  };
26
36
  setInterval: typeof setInterval;
27
37
  clearInterval: typeof clearInterval;