@youcan/cli-kit 2.4.0 → 2.4.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.
- package/dist/node/session.js +1 -1
- package/dist/node/system.d.ts +0 -1
- package/dist/node/worker.d.ts +2 -0
- package/dist/node/worker.js +2 -0
- package/dist/services/cloudflared.d.ts +1 -1
- package/dist/services/cloudflared.js +5 -4
- package/dist/services/cloudflared.test.d.ts +1 -0
- package/dist/services/cloudflared.test.js +114 -0
- package/package.json +3 -1
package/dist/node/session.js
CHANGED
|
@@ -73,7 +73,7 @@ async function authorize(command, state = randomHex(30)) {
|
|
|
73
73
|
code_challenge: challenge,
|
|
74
74
|
code_challenge_method: 'S256',
|
|
75
75
|
};
|
|
76
|
-
await command.output.anykey('Press any key to open the login page on your browser
|
|
76
|
+
await command.output.anykey('Press any key to open the login page on your browser');
|
|
77
77
|
const url = `http://${AUTHORIZATION_URL}/admin/oauth/authorize?${new URLSearchParams(params).toString()}`;
|
|
78
78
|
open(url);
|
|
79
79
|
const result = await listen(command, LS_HOST, LS_PORT, url);
|
package/dist/node/system.d.ts
CHANGED
package/dist/node/worker.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { Writable } from 'node:stream';
|
|
|
3
3
|
export interface Interface {
|
|
4
4
|
run: () => Promise<void>;
|
|
5
5
|
boot: () => Promise<void>;
|
|
6
|
+
cleanup: () => Promise<void>;
|
|
6
7
|
}
|
|
7
8
|
export declare class Logger extends Writable {
|
|
8
9
|
private readonly type;
|
|
@@ -13,4 +14,5 @@ export declare class Logger extends Writable {
|
|
|
13
14
|
export declare abstract class Abstract implements Interface {
|
|
14
15
|
abstract run(): Promise<void>;
|
|
15
16
|
abstract boot(): Promise<void>;
|
|
17
|
+
cleanup(): Promise<void>;
|
|
16
18
|
}
|
package/dist/node/worker.js
CHANGED
|
@@ -3,7 +3,7 @@ export declare class Cloudflared {
|
|
|
3
3
|
private readonly system;
|
|
4
4
|
private readonly output;
|
|
5
5
|
constructor();
|
|
6
|
-
tunnel(port: number, host?: string): Promise<void>;
|
|
6
|
+
tunnel(port: number, host?: string, signal?: AbortSignal): Promise<void>;
|
|
7
7
|
private install;
|
|
8
8
|
private composeTunnelingCommand;
|
|
9
9
|
private exec;
|
|
@@ -163,10 +163,10 @@ class Cloudflared {
|
|
|
163
163
|
this.bin = resolveBinaryPath(platform);
|
|
164
164
|
this.system = { platform, arch };
|
|
165
165
|
}
|
|
166
|
-
async tunnel(port, host = 'localhost') {
|
|
166
|
+
async tunnel(port, host = 'localhost', signal) {
|
|
167
167
|
await this.install();
|
|
168
168
|
const { bin, args } = this.composeTunnelingCommand(port, host);
|
|
169
|
-
this.exec(bin, args);
|
|
169
|
+
this.exec(bin, args, 3, signal);
|
|
170
170
|
}
|
|
171
171
|
async install() {
|
|
172
172
|
if (await isExecutable(this.bin)) {
|
|
@@ -181,7 +181,7 @@ class Cloudflared {
|
|
|
181
181
|
args: ['tunnel', `--url=${host}:${port}`, '--no-autoupdate'],
|
|
182
182
|
};
|
|
183
183
|
}
|
|
184
|
-
async exec(bin, args, maxRetries = 3) {
|
|
184
|
+
async exec(bin, args, maxRetries = 3, signal) {
|
|
185
185
|
if (this.getUrl()) {
|
|
186
186
|
return;
|
|
187
187
|
}
|
|
@@ -192,8 +192,9 @@ class Cloudflared {
|
|
|
192
192
|
await exec(bin, args, {
|
|
193
193
|
// Weird choice of cloudflared to write to stderr.
|
|
194
194
|
stderr: this.output,
|
|
195
|
+
signal,
|
|
195
196
|
errorHandler: async () => {
|
|
196
|
-
await this.exec(bin, args, maxRetries - 1);
|
|
197
|
+
await this.exec(bin, args, maxRetries - 1, signal);
|
|
197
198
|
},
|
|
198
199
|
});
|
|
199
200
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { vi, describe, beforeEach, afterAll, it, expect } from 'vitest';
|
|
2
|
+
import 'change-case';
|
|
3
|
+
import '../node/cli.js';
|
|
4
|
+
import 'conf';
|
|
5
|
+
import 'env-paths';
|
|
6
|
+
import { isExecutable } from '../node/filesystem.js';
|
|
7
|
+
import 'formdata-node';
|
|
8
|
+
import 'formdata-node/file-from-path';
|
|
9
|
+
import 'simple-git';
|
|
10
|
+
import { exec } from '../node/system.js';
|
|
11
|
+
import 'node-fetch';
|
|
12
|
+
import 'ramda';
|
|
13
|
+
import 'kleur';
|
|
14
|
+
import 'dayjs';
|
|
15
|
+
import { Cloudflared } from './cloudflared.js';
|
|
16
|
+
import '../ui/components/DevOutput.js';
|
|
17
|
+
import 'react';
|
|
18
|
+
import 'ink';
|
|
19
|
+
|
|
20
|
+
vi.mock('..', () => ({
|
|
21
|
+
System: {
|
|
22
|
+
exec: vi.fn(),
|
|
23
|
+
},
|
|
24
|
+
Filesystem: {
|
|
25
|
+
isExecutable: vi.fn(),
|
|
26
|
+
},
|
|
27
|
+
Path: {
|
|
28
|
+
join: vi.fn().mockReturnValue('/mock/path/cloudflared'),
|
|
29
|
+
},
|
|
30
|
+
}));
|
|
31
|
+
const originalPlatform = process.platform;
|
|
32
|
+
const originalArch = process.arch;
|
|
33
|
+
describe('cloudflared', () => {
|
|
34
|
+
let cloudflared;
|
|
35
|
+
let mockAbortController;
|
|
36
|
+
beforeEach(() => {
|
|
37
|
+
vi.clearAllMocks();
|
|
38
|
+
Object.defineProperty(process, 'platform', {
|
|
39
|
+
value: 'darwin',
|
|
40
|
+
configurable: true,
|
|
41
|
+
});
|
|
42
|
+
Object.defineProperty(process, 'arch', {
|
|
43
|
+
value: 'arm64',
|
|
44
|
+
configurable: true,
|
|
45
|
+
});
|
|
46
|
+
vi.mocked(isExecutable).mockResolvedValue(true);
|
|
47
|
+
mockAbortController = new AbortController();
|
|
48
|
+
cloudflared = new Cloudflared();
|
|
49
|
+
});
|
|
50
|
+
afterAll(() => {
|
|
51
|
+
Object.defineProperty(process, 'platform', {
|
|
52
|
+
value: originalPlatform,
|
|
53
|
+
configurable: true,
|
|
54
|
+
});
|
|
55
|
+
Object.defineProperty(process, 'arch', {
|
|
56
|
+
value: originalArch,
|
|
57
|
+
configurable: true,
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
describe('tunnel', () => {
|
|
61
|
+
it('should pass abort signal to System.exec', async () => {
|
|
62
|
+
vi.mocked(exec).mockResolvedValue();
|
|
63
|
+
await cloudflared.tunnel(3000, 'localhost', mockAbortController.signal);
|
|
64
|
+
expect(exec).toHaveBeenCalledWith(expect.any(String), expect.any(Array), expect.objectContaining({
|
|
65
|
+
signal: mockAbortController.signal,
|
|
66
|
+
}));
|
|
67
|
+
});
|
|
68
|
+
it('should use correct cloudflared arguments', async () => {
|
|
69
|
+
vi.mocked(exec).mockResolvedValue();
|
|
70
|
+
await cloudflared.tunnel(3001, 'localhost', mockAbortController.signal);
|
|
71
|
+
expect(exec).toHaveBeenCalledWith(expect.any(String), ['tunnel', '--url=localhost:3001', '--no-autoupdate'], expect.any(Object));
|
|
72
|
+
});
|
|
73
|
+
it('should work without abort signal', async () => {
|
|
74
|
+
vi.mocked(exec).mockResolvedValue();
|
|
75
|
+
await expect(cloudflared.tunnel(3000)).resolves.toBeUndefined();
|
|
76
|
+
expect(exec).toHaveBeenCalledWith(expect.any(String), expect.any(Array), expect.objectContaining({
|
|
77
|
+
signal: undefined,
|
|
78
|
+
}));
|
|
79
|
+
});
|
|
80
|
+
it('should use default host when not provided', async () => {
|
|
81
|
+
vi.mocked(exec).mockResolvedValue();
|
|
82
|
+
await cloudflared.tunnel(3000, undefined, mockAbortController.signal);
|
|
83
|
+
expect(exec).toHaveBeenCalledWith(expect.any(String), ['tunnel', '--url=localhost:3000', '--no-autoupdate'], expect.any(Object));
|
|
84
|
+
});
|
|
85
|
+
it('should check if cloudflared is executable before running', async () => {
|
|
86
|
+
vi.mocked(exec).mockResolvedValue();
|
|
87
|
+
await cloudflared.tunnel(3000, 'localhost', mockAbortController.signal);
|
|
88
|
+
expect(isExecutable).toHaveBeenCalledWith('/mock/path/cloudflared');
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
describe('exec retry logic with signal', () => {
|
|
92
|
+
it('should retry with the same signal on failure', async () => {
|
|
93
|
+
let callCount = 0;
|
|
94
|
+
vi.mocked(exec).mockImplementation(async (bin, args, options) => {
|
|
95
|
+
callCount++;
|
|
96
|
+
if (callCount < 2) {
|
|
97
|
+
if (options?.errorHandler) {
|
|
98
|
+
await options.errorHandler(new Error('Connection failed'));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
await cloudflared.tunnel(3000, 'localhost', mockAbortController.signal);
|
|
103
|
+
expect(exec).toHaveBeenCalledTimes(2);
|
|
104
|
+
expect(exec).toHaveBeenNthCalledWith(1, expect.any(String), expect.any(Array), expect.objectContaining({ signal: mockAbortController.signal }));
|
|
105
|
+
expect(exec).toHaveBeenNthCalledWith(2, expect.any(String), expect.any(Array), expect.objectContaining({ signal: mockAbortController.signal }));
|
|
106
|
+
});
|
|
107
|
+
it('should handle errors without crashing', async () => {
|
|
108
|
+
vi.mocked(exec).mockResolvedValue();
|
|
109
|
+
await expect(cloudflared.tunnel(3000, 'localhost', mockAbortController.signal))
|
|
110
|
+
.resolves
|
|
111
|
+
.toBeUndefined();
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@youcan/cli-kit",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "2.4.
|
|
4
|
+
"version": "2.4.1",
|
|
5
5
|
"description": "Utilities for the YouCan CLI",
|
|
6
6
|
"author": "YouCan <contact@youcan.shop> (https://youcan.shop)",
|
|
7
7
|
"license": "MIT",
|
|
@@ -60,6 +60,8 @@
|
|
|
60
60
|
"scripts": {
|
|
61
61
|
"build": "shx rm -rf dist && tsc --noEmit && rollup --config rollup.config.js",
|
|
62
62
|
"dev": "shx rm -rf dist && rollup --config rollup.config.js --watch",
|
|
63
|
+
"test": "vitest run",
|
|
64
|
+
"test:watch": "vitest",
|
|
63
65
|
"release": "pnpm publish --access public",
|
|
64
66
|
"type-check": "tsc --noEmit"
|
|
65
67
|
}
|