@youcan/cli-kit 2.2.0 → 2.3.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/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/internal/node/ui.js +1 -0
- package/dist/node/cli.d.ts +4 -0
- package/dist/node/cli.js +38 -3
- package/dist/node/filesystem.d.ts +4 -0
- package/dist/node/filesystem.js +31 -4
- package/dist/node/http.js +2 -2
- package/dist/node/system.d.ts +4 -1
- package/dist/node/system.js +14 -3
- package/dist/node/worker.js +2 -2
- 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/Error.d.ts +6 -0
- package/dist/ui/components/Error.js +18 -0
- package/dist/ui/index.d.ts +1 -0
- package/dist/ui/index.js +1 -0
- package/package.json +2 -1
package/dist/index.d.ts
CHANGED
|
@@ -14,5 +14,6 @@ export * as Session from './node/session';
|
|
|
14
14
|
export * as Callback from './node/callback';
|
|
15
15
|
export * as Filesystem from './node/filesystem';
|
|
16
16
|
export * as UI from './ui';
|
|
17
|
+
export * as Services from './services';
|
|
17
18
|
export * as String from './common/string';
|
|
18
19
|
export { default as Color } from 'kleur';
|
package/dist/index.js
CHANGED
|
@@ -30,6 +30,8 @@ import * as filesystem from './node/filesystem.js';
|
|
|
30
30
|
export { filesystem as Filesystem };
|
|
31
31
|
import * as index from './ui/index.js';
|
|
32
32
|
export { index as UI };
|
|
33
|
+
import * as index$1 from './services/index.js';
|
|
34
|
+
export { index$1 as Services };
|
|
33
35
|
import * as string from './common/string.js';
|
|
34
36
|
export { string as String };
|
|
35
37
|
export { default as Color } from 'kleur';
|
package/dist/internal/node/ui.js
CHANGED
package/dist/node/cli.d.ts
CHANGED
|
@@ -18,4 +18,8 @@ export declare abstract class Command extends BaseCommand {
|
|
|
18
18
|
clear(): void;
|
|
19
19
|
exit(code?: number): never;
|
|
20
20
|
}
|
|
21
|
+
export declare class CommandError extends Error {
|
|
22
|
+
readonly suggestions?: string[] | undefined;
|
|
23
|
+
constructor(message: string, suggestions?: string[] | undefined);
|
|
24
|
+
}
|
|
21
25
|
export {};
|
package/dist/node/cli.js
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
import { Flags, Command as Command$1, ux } from '@oclif/core';
|
|
2
2
|
import prompts from 'prompts';
|
|
3
|
+
import 'simple-git';
|
|
4
|
+
import 'find-process';
|
|
5
|
+
import 'tcp-port-used';
|
|
6
|
+
import 'execa';
|
|
7
|
+
import 'env-paths';
|
|
8
|
+
import 'node-fetch';
|
|
9
|
+
import 'ramda';
|
|
10
|
+
import 'change-case';
|
|
11
|
+
import 'formdata-node';
|
|
12
|
+
import 'formdata-node/file-from-path';
|
|
13
|
+
import 'kleur';
|
|
14
|
+
import 'conf';
|
|
15
|
+
import 'dayjs';
|
|
16
|
+
import './filesystem.js';
|
|
17
|
+
import '../ui/components/DevOutput.js';
|
|
18
|
+
import 'react';
|
|
19
|
+
import 'ink';
|
|
20
|
+
import { renderError } from '../ui/components/Error.js';
|
|
3
21
|
import { truthy } from './context/helpers.js';
|
|
4
22
|
import { isDevelopment } from './context/local.js';
|
|
5
23
|
|
|
@@ -19,17 +37,27 @@ function setupColorMode() {
|
|
|
19
37
|
process.env.FORCE_COLOR = '0';
|
|
20
38
|
}
|
|
21
39
|
}
|
|
40
|
+
function errorHandler(error) {
|
|
41
|
+
let suggestions = ['Run the command again'];
|
|
42
|
+
const message = error.message;
|
|
43
|
+
if (error instanceof CommandError && error.suggestions) {
|
|
44
|
+
suggestions = error.suggestions;
|
|
45
|
+
}
|
|
46
|
+
renderError({ message, suggestions });
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
22
49
|
async function exec(options) {
|
|
23
50
|
setupEnvVars(options);
|
|
24
51
|
setupColorMode();
|
|
25
|
-
const { run, settings, flush
|
|
52
|
+
const { run, settings, flush } = await import('@oclif/core');
|
|
26
53
|
if (isDevelopment()) {
|
|
27
54
|
settings.debug = true;
|
|
28
55
|
}
|
|
29
56
|
run(undefined, options.moduleUrl)
|
|
30
57
|
.then(() => flush())
|
|
31
|
-
.catch(
|
|
58
|
+
.catch(errorHandler);
|
|
32
59
|
}
|
|
60
|
+
process.on('uncaughtException', errorHandler);
|
|
33
61
|
async function execCreate(cmdlet, options) {
|
|
34
62
|
setupEnvVars(options);
|
|
35
63
|
const initIndex = process.argv
|
|
@@ -65,5 +93,12 @@ class Command extends Command$1 {
|
|
|
65
93
|
return process.exit(code);
|
|
66
94
|
}
|
|
67
95
|
}
|
|
96
|
+
class CommandError extends Error {
|
|
97
|
+
suggestions;
|
|
98
|
+
constructor(message, suggestions) {
|
|
99
|
+
super(message);
|
|
100
|
+
this.suggestions = suggestions;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
68
103
|
|
|
69
|
-
export { Command, commonFlags, exec, execCreate };
|
|
104
|
+
export { Command, CommandError, commonFlags, exec, execCreate };
|
|
@@ -4,9 +4,11 @@
|
|
|
4
4
|
import type { Mode, OpenMode, PathLike, Stats } from 'fs';
|
|
5
5
|
import type { GlobOptions } from 'glob';
|
|
6
6
|
export declare function exists(path: string): Promise<boolean>;
|
|
7
|
+
export declare function isExecutable(path: string): Promise<boolean>;
|
|
7
8
|
export declare function isDirectory(path: string): Promise<boolean>;
|
|
8
9
|
export declare function tapIntoTmp<T>(callback: (tmp: string) => T | Promise<T>): Promise<T>;
|
|
9
10
|
export declare function mkdir(path: string): Promise<void>;
|
|
11
|
+
export declare function rm(path: string): Promise<void>;
|
|
10
12
|
interface MoveFileOptions {
|
|
11
13
|
overwrite?: boolean;
|
|
12
14
|
}
|
|
@@ -27,4 +29,6 @@ export declare function unlink(path: string): Promise<void>;
|
|
|
27
29
|
export declare function readdir(path: string): Promise<string[]>;
|
|
28
30
|
export declare function stat(path: string): Promise<Stats>;
|
|
29
31
|
export declare const watch: typeof import("chokidar").watch;
|
|
32
|
+
export declare function decompressGzip(file: string, destination: string, mode?: number): Promise<void>;
|
|
33
|
+
export declare function extractTar(file: string, cwd: string, mode: number): Promise<void>;
|
|
30
34
|
export {};
|
package/dist/node/filesystem.js
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import FilesystemPromises from 'node:fs/promises';
|
|
2
|
-
import {
|
|
2
|
+
import { createGunzip } from 'node:zlib';
|
|
3
|
+
import { pipeline } from 'node:stream/promises';
|
|
4
|
+
import { createWriteStream, createReadStream } from 'node:fs';
|
|
3
5
|
import { temporaryDirectoryTask } from 'tempy';
|
|
4
6
|
import FsExtra from 'fs-extra';
|
|
5
7
|
import archiver from 'archiver';
|
|
6
8
|
import chokidar from 'chokidar';
|
|
7
9
|
import { glob as glob$1 } from 'glob';
|
|
10
|
+
import * as tar from 'tar';
|
|
8
11
|
import './cli.js';
|
|
9
12
|
import 'simple-git';
|
|
10
|
-
import 'execa';
|
|
11
|
-
import 'tcp-port-used';
|
|
12
13
|
import 'find-process';
|
|
14
|
+
import 'tcp-port-used';
|
|
15
|
+
import 'execa';
|
|
13
16
|
import 'env-paths';
|
|
14
17
|
import { resolve } from './path.js';
|
|
15
18
|
import 'node-fetch';
|
|
@@ -33,6 +36,18 @@ async function exists(path) {
|
|
|
33
36
|
return false;
|
|
34
37
|
}
|
|
35
38
|
}
|
|
39
|
+
async function isExecutable(path) {
|
|
40
|
+
if (!await exists(path)) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
await FilesystemPromises.access(path, FilesystemPromises.constants.X_OK);
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
36
51
|
async function isDirectory(path) {
|
|
37
52
|
try {
|
|
38
53
|
const stats = await FilesystemPromises.stat(path);
|
|
@@ -48,6 +63,9 @@ async function tapIntoTmp(callback) {
|
|
|
48
63
|
async function mkdir(path) {
|
|
49
64
|
await FilesystemPromises.mkdir(path, { recursive: true });
|
|
50
65
|
}
|
|
66
|
+
async function rm(path) {
|
|
67
|
+
await FilesystemPromises.rm(path, { recursive: true });
|
|
68
|
+
}
|
|
51
69
|
async function move(src, dest, options = {}) {
|
|
52
70
|
await FsExtra.move(src, dest, options);
|
|
53
71
|
}
|
|
@@ -103,5 +121,14 @@ async function stat(path) {
|
|
|
103
121
|
return await FilesystemPromises.stat(path);
|
|
104
122
|
}
|
|
105
123
|
const watch = chokidar.watch;
|
|
124
|
+
async function decompressGzip(file, destination, mode = 0o755) {
|
|
125
|
+
const unzip = createGunzip();
|
|
126
|
+
const readStream = createReadStream(file);
|
|
127
|
+
const writeStream = createWriteStream(destination, { mode });
|
|
128
|
+
await pipeline(readStream, unzip, writeStream);
|
|
129
|
+
}
|
|
130
|
+
async function extractTar(file, cwd, mode) {
|
|
131
|
+
await tar.extract({ cwd, file, mode });
|
|
132
|
+
}
|
|
106
133
|
|
|
107
|
-
export { archived, exists, glob, isDirectory, mkdir, move, readFile, readJsonFile, readdir, stat, tapIntoTmp, unlink, watch, writeFile, writeJsonFile };
|
|
134
|
+
export { archived, decompressGzip, exists, extractTar, glob, isDirectory, isExecutable, mkdir, move, readFile, readJsonFile, readdir, rm, stat, tapIntoTmp, unlink, watch, writeFile, writeJsonFile };
|
package/dist/node/http.js
CHANGED
|
@@ -2,9 +2,9 @@ import fetch from 'node-fetch';
|
|
|
2
2
|
import { is, mergeDeepLeft } from 'ramda';
|
|
3
3
|
import './cli.js';
|
|
4
4
|
import 'simple-git';
|
|
5
|
-
import 'execa';
|
|
6
|
-
import 'tcp-port-used';
|
|
7
5
|
import 'find-process';
|
|
6
|
+
import 'tcp-port-used';
|
|
7
|
+
import 'execa';
|
|
8
8
|
import { get as get$1 } from './env.js';
|
|
9
9
|
import 'formdata-node';
|
|
10
10
|
import 'formdata-node/file-from-path';
|
package/dist/node/system.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
2
|
/// <reference types="node" />
|
|
3
3
|
import type { Readable, Writable } from 'stream';
|
|
4
|
+
import type { ExecaError } from 'execa';
|
|
4
5
|
export interface ExecOptions {
|
|
5
6
|
cwd?: string;
|
|
6
7
|
env?: {
|
|
@@ -12,12 +13,14 @@ export interface ExecOptions {
|
|
|
12
13
|
stdio?: 'inherit';
|
|
13
14
|
input?: string;
|
|
14
15
|
signal?: AbortSignal;
|
|
15
|
-
errorHandler?: (error: unknown) => Promise<void>;
|
|
16
|
+
errorHandler?: (error: unknown | ExecaError) => Promise<void>;
|
|
16
17
|
}
|
|
17
18
|
export declare function exec(command: string, args: string[], options?: ExecOptions): Promise<void>;
|
|
18
19
|
export declare function isPortAvailable(port: number): Promise<boolean>;
|
|
20
|
+
export declare function getNextAvailablePort(port: number): Promise<number>;
|
|
19
21
|
export declare function getPortProcessName(port: number): Promise<string>;
|
|
20
22
|
export declare function killPortProcess(port: number): Promise<void>;
|
|
21
23
|
export declare function open(url: string): Promise<void>;
|
|
24
|
+
export declare function sleep(seconds: number): Promise<void>;
|
|
22
25
|
export type PackageManagerType = 'pnpm' | 'npm' | 'yarn';
|
|
23
26
|
export declare function inferUserPackageManager(): PackageManagerType;
|
package/dist/node/system.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
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
5
|
function buildExec(command, args, options) {
|
|
6
6
|
const env = options?.env ?? process.env;
|
|
@@ -49,6 +49,12 @@ async function exec(command, args, options) {
|
|
|
49
49
|
async function isPortAvailable(port) {
|
|
50
50
|
return !await tpu.check(port);
|
|
51
51
|
}
|
|
52
|
+
async function getNextAvailablePort(port) {
|
|
53
|
+
if (await isPortAvailable(port)) {
|
|
54
|
+
return port;
|
|
55
|
+
}
|
|
56
|
+
return await getNextAvailablePort(port + 1);
|
|
57
|
+
}
|
|
52
58
|
async function getPortProcessName(port) {
|
|
53
59
|
const info = await findProcess('port', port);
|
|
54
60
|
return (info && info.length > 0) ? `(${info[0]?.name})` : '';
|
|
@@ -61,6 +67,11 @@ async function open(url) {
|
|
|
61
67
|
const _open = await import('open');
|
|
62
68
|
await _open.default(url);
|
|
63
69
|
}
|
|
70
|
+
async function sleep(seconds) {
|
|
71
|
+
return new Promise((resolve) => {
|
|
72
|
+
setTimeout(resolve, 1000 * seconds);
|
|
73
|
+
});
|
|
74
|
+
}
|
|
64
75
|
function inferUserPackageManager() {
|
|
65
76
|
const defaultPackageManager = 'npm';
|
|
66
77
|
const packageManagersMap = {
|
|
@@ -77,4 +88,4 @@ function inferUserPackageManager() {
|
|
|
77
88
|
return defaultPackageManager;
|
|
78
89
|
}
|
|
79
90
|
|
|
80
|
-
export { exec, getPortProcessName, inferUserPackageManager, isPortAvailable, killPortProcess, open };
|
|
91
|
+
export { exec, getNextAvailablePort, getPortProcessName, inferUserPackageManager, isPortAvailable, killPortProcess, open, sleep };
|
package/dist/node/worker.js
CHANGED
|
@@ -2,9 +2,9 @@ import { Writable } from 'node:stream';
|
|
|
2
2
|
import dayjs from 'dayjs';
|
|
3
3
|
import './cli.js';
|
|
4
4
|
import 'simple-git';
|
|
5
|
-
import 'execa';
|
|
6
|
-
import 'tcp-port-used';
|
|
7
5
|
import 'find-process';
|
|
6
|
+
import 'tcp-port-used';
|
|
7
|
+
import 'execa';
|
|
8
8
|
import 'env-paths';
|
|
9
9
|
import 'node-fetch';
|
|
10
10
|
import 'ramda';
|
|
@@ -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,18 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, Newline, Box, Text } from 'ink';
|
|
3
|
+
|
|
4
|
+
const Error = ({ message, suggestions = [] }) => {
|
|
5
|
+
return (React.createElement(React.Fragment, null,
|
|
6
|
+
React.createElement(Newline, null),
|
|
7
|
+
React.createElement(Box, { flexDirection: 'column', borderStyle: 'round', borderColor: 'red', paddingLeft: 1 },
|
|
8
|
+
React.createElement(Text, { color: 'redBright' }, "Error"),
|
|
9
|
+
React.createElement(Text, { color: 'white' }, message),
|
|
10
|
+
suggestions.length > 0 && (React.createElement(Box, { marginTop: 1, flexDirection: 'column' },
|
|
11
|
+
React.createElement(Text, { color: 'yellow' }, "Suggestions:"),
|
|
12
|
+
suggestions.map((suggestion, index) => (React.createElement(Text, { key: index },
|
|
13
|
+
" - ",
|
|
14
|
+
suggestion))))))));
|
|
15
|
+
};
|
|
16
|
+
const renderError = (props) => render(React.createElement(Error, { ...props }));
|
|
17
|
+
|
|
18
|
+
export { renderError };
|
package/dist/ui/index.d.ts
CHANGED
package/dist/ui/index.js
CHANGED
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.3.1",
|
|
5
5
|
"description": "Utilities for the YouCan CLI",
|
|
6
6
|
"author": "YouCan <contact@youcan.shop> (https://youcan.shop)",
|
|
7
7
|
"license": "MIT",
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
"react": "^18.3.1",
|
|
42
42
|
"rxjs": "^7.8.1",
|
|
43
43
|
"simple-git": "^3.20.0",
|
|
44
|
+
"tar": "^7.4.3",
|
|
44
45
|
"tcp-port-used": "^1.0.2",
|
|
45
46
|
"tempy": "^3.1.0",
|
|
46
47
|
"worker": "^0.4.0"
|