@youcan/cli-kit 2.3.3 → 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 +2 -2
- package/dist/services/cloudflared.js +6 -4
- package/dist/services/cloudflared.test.js +114 -0
- package/package.json +4 -3
- package/dist/scripts/install-cloudflared.js +0 -25
- /package/dist/{scripts/install-cloudflared.d.ts → services/cloudflared.test.d.ts} +0 -0
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,8 +3,8 @@ 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>;
|
|
7
|
-
install
|
|
6
|
+
tunnel(port: number, host?: string, signal?: AbortSignal): Promise<void>;
|
|
7
|
+
private install;
|
|
8
8
|
private composeTunnelingCommand;
|
|
9
9
|
private exec;
|
|
10
10
|
getUrl(): string | null;
|
|
@@ -163,9 +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
|
+
await this.install();
|
|
167
168
|
const { bin, args } = this.composeTunnelingCommand(port, host);
|
|
168
|
-
this.exec(bin, args);
|
|
169
|
+
this.exec(bin, args, 3, signal);
|
|
169
170
|
}
|
|
170
171
|
async install() {
|
|
171
172
|
if (await isExecutable(this.bin)) {
|
|
@@ -180,7 +181,7 @@ class Cloudflared {
|
|
|
180
181
|
args: ['tunnel', `--url=${host}:${port}`, '--no-autoupdate'],
|
|
181
182
|
};
|
|
182
183
|
}
|
|
183
|
-
async exec(bin, args, maxRetries = 3) {
|
|
184
|
+
async exec(bin, args, maxRetries = 3, signal) {
|
|
184
185
|
if (this.getUrl()) {
|
|
185
186
|
return;
|
|
186
187
|
}
|
|
@@ -191,8 +192,9 @@ class Cloudflared {
|
|
|
191
192
|
await exec(bin, args, {
|
|
192
193
|
// Weird choice of cloudflared to write to stderr.
|
|
193
194
|
stderr: this.output,
|
|
195
|
+
signal,
|
|
194
196
|
errorHandler: async () => {
|
|
195
|
-
await this.exec(bin, args, maxRetries - 1);
|
|
197
|
+
await this.exec(bin, args, maxRetries - 1, signal);
|
|
196
198
|
},
|
|
197
199
|
});
|
|
198
200
|
}
|
|
@@ -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
|
+
"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,8 +60,9 @@
|
|
|
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
|
-
"type-check": "tsc --noEmit"
|
|
65
|
-
"postinstall": "node -e \"import('fs').then(fs => { if (fs.existsSync('./dist/scripts/install-cloudflared.js')) import('./dist/scripts/install-cloudflared.js'); });\""
|
|
66
|
+
"type-check": "tsc --noEmit"
|
|
66
67
|
}
|
|
67
68
|
}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import 'change-case';
|
|
2
|
-
import '../node/cli.js';
|
|
3
|
-
import 'conf';
|
|
4
|
-
import 'env-paths';
|
|
5
|
-
import '../node/filesystem.js';
|
|
6
|
-
import 'formdata-node';
|
|
7
|
-
import 'formdata-node/file-from-path';
|
|
8
|
-
import 'simple-git';
|
|
9
|
-
import 'execa';
|
|
10
|
-
import 'find-process';
|
|
11
|
-
import 'get-port';
|
|
12
|
-
import 'tcp-port-used';
|
|
13
|
-
import 'node-fetch';
|
|
14
|
-
import 'ramda';
|
|
15
|
-
import 'kleur';
|
|
16
|
-
import 'dayjs';
|
|
17
|
-
import { Cloudflared } from '../services/cloudflared.js';
|
|
18
|
-
import '../ui/components/DevOutput.js';
|
|
19
|
-
import 'react';
|
|
20
|
-
import 'ink';
|
|
21
|
-
|
|
22
|
-
const cloudflared = new Cloudflared();
|
|
23
|
-
console.log('Installing Cloudflared...');
|
|
24
|
-
await cloudflared.install();
|
|
25
|
-
console.log('Successfully Installed Cloudflared');
|
|
File without changes
|