@youcan/cli-kit 2.1.4 → 2.3.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.
- package/dist/common/string.d.ts +2 -2
- package/dist/common/string.js +11 -11
- package/dist/index.d.ts +19 -17
- package/dist/index.js +4 -0
- package/dist/internal/node/constants.d.ts +5 -5
- package/dist/internal/node/constants.js +10 -10
- package/dist/internal/node/ui.d.ts +11 -11
- package/dist/internal/node/ui.js +41 -40
- package/dist/node/callback.d.ts +5 -5
- package/dist/node/callback.js +53 -53
- package/dist/node/cli.d.ts +25 -21
- package/dist/node/cli.js +97 -62
- package/dist/node/config.d.ts +20 -20
- package/dist/node/config.js +20 -22
- package/dist/node/context/helpers.d.ts +1 -1
- package/dist/node/context/helpers.js +5 -5
- package/dist/node/context/local.d.ts +2 -2
- package/dist/node/context/local.js +2 -2
- package/dist/node/crypto.d.ts +12 -9
- package/dist/node/crypto.js +23 -23
- package/dist/node/env.d.ts +5 -6
- package/dist/node/env.js +36 -47
- package/dist/node/filesystem.d.ts +34 -29
- package/dist/node/filesystem.js +112 -82
- package/dist/node/form.d.ts +9 -9
- package/dist/node/form.js +39 -39
- package/dist/node/git.d.ts +10 -10
- package/dist/node/git.js +46 -46
- package/dist/node/github.d.ts +6 -6
- package/dist/node/github.js +8 -8
- package/dist/node/http.d.ts +4 -4
- package/dist/node/http.js +37 -34
- package/dist/node/path.d.ts +5 -5
- package/dist/node/path.js +14 -14
- package/dist/node/session.d.ts +8 -8
- package/dist/node/session.js +92 -78
- package/dist/node/system.d.ts +26 -21
- package/dist/node/system.js +87 -60
- package/dist/node/tasks.d.ts +8 -7
- package/dist/node/tasks.js +33 -25
- package/dist/node/worker.d.ts +16 -19
- package/dist/node/worker.js +43 -30
- package/dist/services/cloudflared.d.ts +12 -0
- package/dist/services/cloudflared.js +206 -0
- package/dist/services/index.d.ts +1 -0
- package/dist/services/index.js +1 -0
- package/dist/ui/components/DevOutput.d.ts +27 -0
- package/dist/ui/components/DevOutput.js +60 -0
- package/dist/ui/components/Error.d.ts +6 -0
- package/dist/ui/components/Error.js +18 -0
- package/dist/ui/components/HotKeys.d.ts +12 -0
- package/dist/ui/components/HotKeys.js +25 -0
- package/dist/ui/components/utils/symbols.d.ts +3 -0
- package/dist/ui/components/utils/symbols.js +7 -0
- package/dist/ui/index.d.ts +3 -0
- package/dist/ui/index.js +3 -0
- package/package.json +7 -2
package/dist/node/system.js
CHANGED
|
@@ -1,64 +1,91 @@
|
|
|
1
|
-
import { execa } from 'execa';
|
|
2
|
-
import tpu from 'tcp-port-used';
|
|
3
1
|
import findProcess from 'find-process';
|
|
2
|
+
import tpu from 'tcp-port-used';
|
|
3
|
+
import { execa } from 'execa';
|
|
4
4
|
|
|
5
|
-
function buildExec(command, args, options) {
|
|
6
|
-
const env = options?.env ?? process.env;
|
|
7
|
-
const commandProcess = execa(command, args, {
|
|
8
|
-
env,
|
|
9
|
-
cwd: options?.cwd,
|
|
10
|
-
input: options?.input,
|
|
11
|
-
stdio: options?.stdio,
|
|
12
|
-
stdin: options?.stdin,
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
5
|
+
function buildExec(command, args, options) {
|
|
6
|
+
const env = options?.env ?? process.env;
|
|
7
|
+
const commandProcess = execa(command, args, {
|
|
8
|
+
env,
|
|
9
|
+
cwd: options?.cwd,
|
|
10
|
+
input: options?.input,
|
|
11
|
+
stdio: options?.stdio,
|
|
12
|
+
stdin: options?.stdin,
|
|
13
|
+
signal: options?.signal,
|
|
14
|
+
stdout: options?.stdout === 'inherit' ? 'inherit' : undefined,
|
|
15
|
+
stderr: options?.stderr === 'inherit' ? 'inherit' : undefined,
|
|
16
|
+
windowsHide: false,
|
|
17
|
+
});
|
|
18
|
+
return commandProcess;
|
|
19
|
+
}
|
|
20
|
+
async function exec(command, args, options) {
|
|
21
|
+
const commandProcess = buildExec(command, args, options);
|
|
22
|
+
if (options?.stderr && options.stderr !== 'inherit') {
|
|
23
|
+
commandProcess.stderr?.pipe(options.stderr, { end: false });
|
|
24
|
+
}
|
|
25
|
+
if (options?.stdout && options.stdout !== 'inherit') {
|
|
26
|
+
commandProcess.stdout?.pipe(options.stdout, { end: false });
|
|
27
|
+
}
|
|
28
|
+
let aborted = false;
|
|
29
|
+
options?.signal?.addEventListener('abort', () => {
|
|
30
|
+
const pid = commandProcess.pid;
|
|
31
|
+
if (pid) {
|
|
32
|
+
aborted = true;
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
try {
|
|
36
|
+
await commandProcess;
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
if (aborted) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
if (options?.errorHandler) {
|
|
43
|
+
await options?.errorHandler(err);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
throw err;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
async function isPortAvailable(port) {
|
|
50
|
+
return !await tpu.check(port);
|
|
51
|
+
}
|
|
52
|
+
async function getNextAvailablePort(port) {
|
|
53
|
+
if (await isPortAvailable(port)) {
|
|
54
|
+
return port;
|
|
55
|
+
}
|
|
56
|
+
return await getNextAvailablePort(port + 1);
|
|
57
|
+
}
|
|
58
|
+
async function getPortProcessName(port) {
|
|
59
|
+
const info = await findProcess('port', port);
|
|
60
|
+
return (info && info.length > 0) ? `(${info[0]?.name})` : '';
|
|
61
|
+
}
|
|
62
|
+
async function killPortProcess(port) {
|
|
63
|
+
const { killPortProcess: kill } = await import('kill-port-process');
|
|
64
|
+
await kill(port);
|
|
65
|
+
}
|
|
66
|
+
async function open(url) {
|
|
67
|
+
const _open = await import('open');
|
|
68
|
+
await _open.default(url);
|
|
69
|
+
}
|
|
70
|
+
async function sleep(seconds) {
|
|
71
|
+
return new Promise((resolve) => {
|
|
72
|
+
setTimeout(resolve, 1000 * seconds);
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
function inferUserPackageManager() {
|
|
76
|
+
const defaultPackageManager = 'npm';
|
|
77
|
+
const packageManagersMap = {
|
|
78
|
+
'^npm/.*': 'npm',
|
|
79
|
+
'^pnpm/.*': 'pnpm',
|
|
80
|
+
'^yarn/.*': 'yarn',
|
|
81
|
+
};
|
|
82
|
+
const packageManagerUserAgent = process.env.npm_config_user_agent;
|
|
83
|
+
for (const key in packageManagersMap) {
|
|
84
|
+
if (new RegExp(key).test(packageManagerUserAgent)) {
|
|
85
|
+
return packageManagersMap[key];
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return defaultPackageManager;
|
|
62
89
|
}
|
|
63
90
|
|
|
64
|
-
export { exec, getPortProcessName, isPortAvailable, killPortProcess, open };
|
|
91
|
+
export { exec, getNextAvailablePort, getPortProcessName, inferUserPackageManager, isPortAvailable, killPortProcess, open, sleep };
|
package/dist/node/tasks.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
export interface Task<T = unknown> {
|
|
2
|
-
title: string;
|
|
3
|
-
errors?: Error[];
|
|
4
|
-
skip?: (ctx: T) => boolean;
|
|
5
|
-
task: (context: T, task: Task<T>) => Promise<void | Task<T>[]>;
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
export interface Task<T = unknown> {
|
|
2
|
+
title: string;
|
|
3
|
+
errors?: Error[];
|
|
4
|
+
skip?: (ctx: T) => boolean;
|
|
5
|
+
task: (context: T, task: Task<T>) => Promise<void | Task<T>[]>;
|
|
6
|
+
loadable?: false;
|
|
7
|
+
}
|
|
8
|
+
export declare function run<T = unknown>(ctx: T, tasks: Task<T>[]): Promise<T>;
|
package/dist/node/tasks.js
CHANGED
|
@@ -1,31 +1,39 @@
|
|
|
1
1
|
import { exit } from 'node:process';
|
|
2
2
|
import { Loader } from '../internal/node/ui.js';
|
|
3
3
|
|
|
4
|
-
async function runTask(task, ctx) {
|
|
5
|
-
if (task.skip?.(ctx)) {
|
|
6
|
-
return;
|
|
7
|
-
}
|
|
8
|
-
return await task.task(ctx, task);
|
|
9
|
-
}
|
|
10
|
-
async function run(ctx, tasks) {
|
|
11
|
-
for await (const task of tasks) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
4
|
+
async function runTask(task, ctx) {
|
|
5
|
+
if (task.skip?.(ctx)) {
|
|
6
|
+
return;
|
|
7
|
+
}
|
|
8
|
+
return await task.task(ctx, task);
|
|
9
|
+
}
|
|
10
|
+
async function run(ctx, tasks) {
|
|
11
|
+
for await (const task of tasks) {
|
|
12
|
+
const runner = async () => {
|
|
13
|
+
const subtasks = await runTask(task, ctx);
|
|
14
|
+
if (Array.isArray(subtasks) && subtasks.length > 0 && subtasks.every(t => 'task' in t)) {
|
|
15
|
+
for await (const subtask of subtasks) {
|
|
16
|
+
await runTask(subtask, ctx);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
if (task.loadable === false) {
|
|
21
|
+
process.stdout.write(`${task.title}\n`);
|
|
22
|
+
await runner();
|
|
23
|
+
return ctx;
|
|
24
|
+
}
|
|
25
|
+
await Loader.exec(task.title, async (loader) => {
|
|
26
|
+
try {
|
|
27
|
+
await runner();
|
|
28
|
+
loader.stop();
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
loader.error(String(err));
|
|
32
|
+
exit(1);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
return ctx;
|
|
29
37
|
}
|
|
30
38
|
|
|
31
39
|
export { run };
|
package/dist/node/worker.d.ts
CHANGED
|
@@ -1,19 +1,16 @@
|
|
|
1
|
-
/// <reference types="node" />
|
|
2
|
-
import { Writable } from 'stream';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
private
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
abstract boot(): Promise<void>;
|
|
18
|
-
abstract run(): Promise<void>;
|
|
19
|
-
}
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import { Writable } from 'stream';
|
|
3
|
+
export interface Interface {
|
|
4
|
+
run(): Promise<void>;
|
|
5
|
+
boot(): Promise<void>;
|
|
6
|
+
}
|
|
7
|
+
export declare class Logger extends Writable {
|
|
8
|
+
private readonly type;
|
|
9
|
+
private readonly color;
|
|
10
|
+
constructor(type: string, color: 'yellow' | 'cyan' | 'magenta' | 'green' | 'blue' | 'red' | 'dim');
|
|
11
|
+
write(chunk: unknown): boolean;
|
|
12
|
+
}
|
|
13
|
+
export declare abstract class Abstract implements Interface {
|
|
14
|
+
abstract run(): Promise<void>;
|
|
15
|
+
abstract boot(): Promise<void>;
|
|
16
|
+
}
|
package/dist/node/worker.js
CHANGED
|
@@ -1,36 +1,49 @@
|
|
|
1
1
|
import { Writable } from 'node:stream';
|
|
2
|
-
import { stdout, stderr } from 'node:process';
|
|
3
2
|
import dayjs from 'dayjs';
|
|
3
|
+
import './cli.js';
|
|
4
|
+
import 'simple-git';
|
|
5
|
+
import 'find-process';
|
|
6
|
+
import 'tcp-port-used';
|
|
7
|
+
import 'execa';
|
|
8
|
+
import 'env-paths';
|
|
9
|
+
import 'node-fetch';
|
|
10
|
+
import 'ramda';
|
|
11
|
+
import 'change-case';
|
|
12
|
+
import 'formdata-node';
|
|
13
|
+
import 'formdata-node/file-from-path';
|
|
14
|
+
import 'kleur';
|
|
15
|
+
import 'conf';
|
|
16
|
+
import './filesystem.js';
|
|
17
|
+
import { renderDevOutput } from '../ui/components/DevOutput.js';
|
|
18
|
+
import 'react';
|
|
19
|
+
import 'ink';
|
|
4
20
|
|
|
5
|
-
class Logger extends Writable {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
color
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
this.
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
return true;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
class Abstract {
|
|
21
|
+
class Logger extends Writable {
|
|
22
|
+
type;
|
|
23
|
+
color;
|
|
24
|
+
constructor(type, color) {
|
|
25
|
+
super();
|
|
26
|
+
this.type = type;
|
|
27
|
+
this.color = color;
|
|
28
|
+
}
|
|
29
|
+
write(chunk) {
|
|
30
|
+
if (!(chunk instanceof Buffer) && typeof chunk !== 'string') {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
const time = dayjs().format('HH:mm:ss:SSS');
|
|
34
|
+
const lines = chunk.toString().split('\n').map(s => s.trim()).filter(s => s !== '');
|
|
35
|
+
for (const line of lines) {
|
|
36
|
+
renderDevOutput.outputSubject.emit({
|
|
37
|
+
timestamp: time,
|
|
38
|
+
color: this.color,
|
|
39
|
+
label: this.type,
|
|
40
|
+
buffer: line,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
class Abstract {
|
|
34
47
|
}
|
|
35
48
|
|
|
36
49
|
export { Abstract, Logger };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare class Cloudflared {
|
|
2
|
+
private readonly bin;
|
|
3
|
+
private readonly system;
|
|
4
|
+
private readonly output;
|
|
5
|
+
constructor();
|
|
6
|
+
tunnel(port: number, host?: string): Promise<void>;
|
|
7
|
+
private install;
|
|
8
|
+
private composeTunnelingCommand;
|
|
9
|
+
private exec;
|
|
10
|
+
getUrl(): string | null;
|
|
11
|
+
getError(): string | null;
|
|
12
|
+
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { Writable, Readable } from 'node:stream';
|
|
2
|
+
import { pipeline } from 'node:stream/promises';
|
|
3
|
+
import { createWriteStream } from 'node:fs';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import '../node/cli.js';
|
|
6
|
+
import 'simple-git';
|
|
7
|
+
import { exec } from '../node/system.js';
|
|
8
|
+
import 'env-paths';
|
|
9
|
+
import { join, dirname, basename, resolve } from '../node/path.js';
|
|
10
|
+
import 'node-fetch';
|
|
11
|
+
import 'ramda';
|
|
12
|
+
import 'change-case';
|
|
13
|
+
import 'formdata-node';
|
|
14
|
+
import 'formdata-node/file-from-path';
|
|
15
|
+
import 'kleur';
|
|
16
|
+
import 'conf';
|
|
17
|
+
import 'dayjs';
|
|
18
|
+
import { isExecutable, tapIntoTmp, mkdir, decompressGzip, extractTar, move } from '../node/filesystem.js';
|
|
19
|
+
import '../ui/components/DevOutput.js';
|
|
20
|
+
import 'react';
|
|
21
|
+
import 'ink';
|
|
22
|
+
|
|
23
|
+
const LINUX_TARGET_NAMES = {
|
|
24
|
+
arm64: 'cloudflared-linux-arm64',
|
|
25
|
+
arm: 'cloudflared-linux-arm',
|
|
26
|
+
x64: 'cloudflared-linux-amd64',
|
|
27
|
+
ia32: 'cloudflared-linux-386',
|
|
28
|
+
};
|
|
29
|
+
const MACOS_TARGET_NAMES = {
|
|
30
|
+
arm64: 'cloudflared-darwin-arm64.tgz',
|
|
31
|
+
x64: 'cloudflared-darwin-amd64.tgz',
|
|
32
|
+
};
|
|
33
|
+
const WINDOWS_TARGET_NAMES = {
|
|
34
|
+
x64: 'cloudflared-windows-amd64.exe',
|
|
35
|
+
ia32: 'cloudflared-windows-386.exe',
|
|
36
|
+
arm64: 'cloudflared-windows-amd64.exe',
|
|
37
|
+
};
|
|
38
|
+
const TARGET_NAMES = {
|
|
39
|
+
linux: LINUX_TARGET_NAMES,
|
|
40
|
+
darwin: MACOS_TARGET_NAMES,
|
|
41
|
+
win32: WINDOWS_TARGET_NAMES,
|
|
42
|
+
};
|
|
43
|
+
function composeDownloadUrl(platform, arch) {
|
|
44
|
+
const releaseDownloadUrl = 'https://github.com/cloudflare/cloudflared/releases/download';
|
|
45
|
+
const supportedVersion = '2024.11.1';
|
|
46
|
+
const filename = TARGET_NAMES[platform][arch];
|
|
47
|
+
return `${releaseDownloadUrl}/${supportedVersion}/${filename}`;
|
|
48
|
+
}
|
|
49
|
+
function resolveBinaryPath(platform) {
|
|
50
|
+
const rootDir = fileURLToPath(new URL('../'.repeat(2), import.meta.url));
|
|
51
|
+
return join(rootDir, 'bin', platform === 'win32' ? 'cloudflared.exe' : 'cloudflared');
|
|
52
|
+
}
|
|
53
|
+
function isPlatformSupported(platform) {
|
|
54
|
+
return platform in TARGET_NAMES;
|
|
55
|
+
}
|
|
56
|
+
function isArchSupported(arch, platform) {
|
|
57
|
+
return arch in TARGET_NAMES[platform];
|
|
58
|
+
}
|
|
59
|
+
async function downloadFromRelease(url, downloadPath) {
|
|
60
|
+
const response = await fetch(url, { redirect: 'follow' });
|
|
61
|
+
if (!response.ok) {
|
|
62
|
+
throw new Error(`failed to download cloudflared: ${response.statusText}`);
|
|
63
|
+
}
|
|
64
|
+
const { body } = response;
|
|
65
|
+
const fileWriteStream = createWriteStream(downloadPath, { mode: 0o664 });
|
|
66
|
+
await pipeline(Readable.fromWeb(body), fileWriteStream);
|
|
67
|
+
}
|
|
68
|
+
async function installForMacOs(url, destination) {
|
|
69
|
+
await tapIntoTmp(async (tmpDir) => {
|
|
70
|
+
const parentDir = dirname(destination);
|
|
71
|
+
const binaryName = basename(destination);
|
|
72
|
+
const downloadedFile = resolve(tmpDir, `${binaryName}.tgz`);
|
|
73
|
+
const decompressedFile = resolve(tmpDir, `${binaryName}.gz`);
|
|
74
|
+
await mkdir(parentDir);
|
|
75
|
+
await mkdir(tmpDir);
|
|
76
|
+
await downloadFromRelease(url, downloadedFile);
|
|
77
|
+
await decompressGzip(downloadedFile, decompressedFile);
|
|
78
|
+
await extractTar(decompressedFile, tmpDir, 0o755);
|
|
79
|
+
await move(resolve(tmpDir, binaryName), destination, { overwrite: true });
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
async function installForLinux(url, destination) {
|
|
83
|
+
const parentDir = dirname(destination);
|
|
84
|
+
await mkdir(parentDir);
|
|
85
|
+
await downloadFromRelease(url, destination);
|
|
86
|
+
}
|
|
87
|
+
async function installForWindows(url, destination) {
|
|
88
|
+
const parentDir = dirname(destination);
|
|
89
|
+
mkdir(parentDir);
|
|
90
|
+
await downloadFromRelease(url, destination);
|
|
91
|
+
}
|
|
92
|
+
async function install(platform, downloadUrl, destinationPath) {
|
|
93
|
+
switch (platform) {
|
|
94
|
+
case 'darwin':
|
|
95
|
+
await installForMacOs(downloadUrl, destinationPath);
|
|
96
|
+
break;
|
|
97
|
+
case 'linux':
|
|
98
|
+
await installForLinux(downloadUrl, destinationPath);
|
|
99
|
+
break;
|
|
100
|
+
case 'win32':
|
|
101
|
+
await installForWindows(downloadUrl, destinationPath);
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
class OutputStream extends Writable {
|
|
106
|
+
tunnelUrl = null;
|
|
107
|
+
tunnelError = null;
|
|
108
|
+
buffer = '';
|
|
109
|
+
static ErrorsRegex = [
|
|
110
|
+
/failed to build quick tunnel request/,
|
|
111
|
+
/failed to request quick Tunnel/,
|
|
112
|
+
/failed to read quick-tunnel response/,
|
|
113
|
+
/failed to parse quick Tunnel ID/,
|
|
114
|
+
/Couldn't start tunnel/,
|
|
115
|
+
];
|
|
116
|
+
write(chunk, encoding, callback) {
|
|
117
|
+
if (!(chunk instanceof Buffer) && typeof chunk !== 'string') {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
this.buffer += chunk.toString();
|
|
121
|
+
if (this.tunnelUrl === null) {
|
|
122
|
+
this.tunnelUrl = this.extractTunnelUrl();
|
|
123
|
+
}
|
|
124
|
+
if (callback && typeof callback === 'function') {
|
|
125
|
+
callback();
|
|
126
|
+
}
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
extractTunnelUrl() {
|
|
130
|
+
const regex = /https:\/\/(?!api\.trycloudflare\.com)[^\s]+\.trycloudflare\.com/;
|
|
131
|
+
return this.buffer.match(regex)?.[0] || null;
|
|
132
|
+
}
|
|
133
|
+
extractError() {
|
|
134
|
+
for (const errorRegex of OutputStream.ErrorsRegex) {
|
|
135
|
+
if (errorRegex.test(this.buffer)) {
|
|
136
|
+
return errorRegex.source;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
getTunnelUrl() {
|
|
142
|
+
return this.tunnelUrl;
|
|
143
|
+
}
|
|
144
|
+
clearBuffer() {
|
|
145
|
+
this.buffer = '';
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
class Cloudflared {
|
|
149
|
+
bin;
|
|
150
|
+
system;
|
|
151
|
+
output = new OutputStream();
|
|
152
|
+
constructor() {
|
|
153
|
+
const platform = process.platform;
|
|
154
|
+
const arch = process.arch;
|
|
155
|
+
if (!isPlatformSupported(platform)) {
|
|
156
|
+
throw new Error(`unsupported platform: ${platform}`);
|
|
157
|
+
}
|
|
158
|
+
if (!isArchSupported(arch, platform)) {
|
|
159
|
+
throw new Error(`unsupported architecture: ${arch}`);
|
|
160
|
+
}
|
|
161
|
+
this.bin = resolveBinaryPath(platform);
|
|
162
|
+
this.system = { platform, arch };
|
|
163
|
+
}
|
|
164
|
+
async tunnel(port, host = 'localhost') {
|
|
165
|
+
await this.install();
|
|
166
|
+
const { bin, args } = this.composeTunnelingCommand(port, host);
|
|
167
|
+
this.exec(bin, args);
|
|
168
|
+
}
|
|
169
|
+
async install() {
|
|
170
|
+
if (await isExecutable(this.bin)) {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
const downloadUrl = composeDownloadUrl(this.system.platform, this.system.arch);
|
|
174
|
+
await install(this.system.platform, downloadUrl, this.bin);
|
|
175
|
+
}
|
|
176
|
+
composeTunnelingCommand(port, host = 'localhost') {
|
|
177
|
+
return {
|
|
178
|
+
bin: this.bin,
|
|
179
|
+
args: ['tunnel', `--url=${host}:${port}`, '--no-autoupdate'],
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
async exec(bin, args, maxRetries = 3) {
|
|
183
|
+
if (this.getUrl()) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
if (maxRetries === 0) {
|
|
187
|
+
throw new Error(this.output.extractError() ?? 'cloudflared failed for unknown reason');
|
|
188
|
+
}
|
|
189
|
+
this.output.clearBuffer();
|
|
190
|
+
await exec(bin, args, {
|
|
191
|
+
// Weird choice of cloudflared to write to stderr.
|
|
192
|
+
stderr: this.output,
|
|
193
|
+
errorHandler: async () => {
|
|
194
|
+
await this.exec(bin, args, maxRetries - 1);
|
|
195
|
+
},
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
getUrl() {
|
|
199
|
+
return this.output.getTunnelUrl();
|
|
200
|
+
}
|
|
201
|
+
getError() {
|
|
202
|
+
return this.output.extractError();
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export { Cloudflared };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './cloudflared';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Cloudflared } from './cloudflared.js';
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Instance, Text } from 'ink';
|
|
3
|
+
import { HotKeysPropsType } from './HotKeys';
|
|
4
|
+
import { Command } from '@/node/cli';
|
|
5
|
+
interface SubjectDataType {
|
|
6
|
+
timestamp: string;
|
|
7
|
+
buffer: string;
|
|
8
|
+
label: string;
|
|
9
|
+
color: Parameters<typeof Text>[0]['color'];
|
|
10
|
+
}
|
|
11
|
+
declare class OutputSubject {
|
|
12
|
+
private readonly subject;
|
|
13
|
+
listen(handler: (data: SubjectDataType) => void): void;
|
|
14
|
+
emit(data: SubjectDataType): void;
|
|
15
|
+
private pad;
|
|
16
|
+
}
|
|
17
|
+
export type DevOutputPropsType = {
|
|
18
|
+
hotKeys?: HotKeysPropsType['hotKeys'];
|
|
19
|
+
cmd: Command;
|
|
20
|
+
};
|
|
21
|
+
interface RenderDevOutputType {
|
|
22
|
+
(props: DevOutputPropsType): Instance;
|
|
23
|
+
outputSubject: OutputSubject;
|
|
24
|
+
}
|
|
25
|
+
export declare const DevOutput: ({ cmd, hotKeys }: DevOutputPropsType) => React.JSX.Element;
|
|
26
|
+
declare const renderDevOutput: RenderDevOutputType;
|
|
27
|
+
export { renderDevOutput };
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import { useInput, Static, Box, Text, render } from 'ink';
|
|
3
|
+
import { HotKeys } from './HotKeys.js';
|
|
4
|
+
import { VerticalDivider } from './utils/symbols.js';
|
|
5
|
+
import { map } from 'rxjs/operators';
|
|
6
|
+
import { ReplaySubject } from 'rxjs';
|
|
7
|
+
|
|
8
|
+
class OutputSubject {
|
|
9
|
+
subject = new ReplaySubject;
|
|
10
|
+
listen(handler) {
|
|
11
|
+
let lastLineKey = null;
|
|
12
|
+
this.subject.pipe(map(item => {
|
|
13
|
+
const currentLineKey = `${item.label}-${item.color}`;
|
|
14
|
+
if (currentLineKey !== lastLineKey) {
|
|
15
|
+
lastLineKey = currentLineKey;
|
|
16
|
+
return { ...item, label: this.pad(item.label, 10) };
|
|
17
|
+
}
|
|
18
|
+
return { ...item, label: this.pad('', 10) };
|
|
19
|
+
}))
|
|
20
|
+
.subscribe(handler);
|
|
21
|
+
}
|
|
22
|
+
emit(data) {
|
|
23
|
+
this.subject.next(data);
|
|
24
|
+
}
|
|
25
|
+
pad(subject, length, char = ' ') {
|
|
26
|
+
return subject.padEnd(length, char);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const outputSubject = new OutputSubject;
|
|
30
|
+
const DevOutput = ({ cmd, hotKeys = [] }) => {
|
|
31
|
+
const [linesBuffers, setLinesBuffers] = useState([]);
|
|
32
|
+
useInput((input, key) => {
|
|
33
|
+
if (input === 'c' && key.ctrl)
|
|
34
|
+
cmd.exit(130);
|
|
35
|
+
});
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
outputSubject.listen((data) => {
|
|
38
|
+
setLinesBuffers((previousLines) => [...previousLines, data]);
|
|
39
|
+
});
|
|
40
|
+
}, []);
|
|
41
|
+
return (React.createElement(React.Fragment, null,
|
|
42
|
+
React.createElement(Static, { items: linesBuffers }, (line, i) => (React.createElement(Box, { flexDirection: 'column', key: i },
|
|
43
|
+
React.createElement(Text, null,
|
|
44
|
+
React.createElement(Text, { dimColor: true }, line.timestamp),
|
|
45
|
+
' ',
|
|
46
|
+
React.createElement(VerticalDivider, null),
|
|
47
|
+
' ',
|
|
48
|
+
React.createElement(Text, { color: line.color }, line.label),
|
|
49
|
+
React.createElement(Text, null,
|
|
50
|
+
' ',
|
|
51
|
+
React.createElement(VerticalDivider, null),
|
|
52
|
+
' ',
|
|
53
|
+
line.buffer))))),
|
|
54
|
+
React.createElement(Box, { flexDirection: 'column', paddingTop: 1 },
|
|
55
|
+
React.createElement(HotKeys, { hotKeys: hotKeys }))));
|
|
56
|
+
};
|
|
57
|
+
const renderDevOutput = ((props) => render(React.createElement(DevOutput, { ...props }), { exitOnCtrlC: false }));
|
|
58
|
+
renderDevOutput.outputSubject = outputSubject;
|
|
59
|
+
|
|
60
|
+
export { DevOutput, renderDevOutput };
|