@mycadet/cli 0.12.8 → 0.12.9-dev.202610131620.d833e01
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/node_modules/@mycadet/assistant-api/events/show-cloud-login.ts +2 -2
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/package.json +1 -1
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/package.json +1 -1
- package/node_modules/@mycadet/service-contracts/package.json +1 -1
- package/package.json +4 -3
- package/src/__tests__/client-web-config.test.ts +0 -1
- package/src/__tests__/cloud-releases.test.ts +23 -13
- package/src/__tests__/ensure-binary.test.ts +182 -0
- package/src/__tests__/nginx-ingress.test.ts +6 -11
- package/src/__tests__/ngrok-spawn.test.ts +49 -0
- package/src/__tests__/pair.test.ts +149 -78
- package/src/__tests__/ps-cloud-status.test.ts +4 -3
- package/src/__tests__/ps-table.test.ts +79 -0
- package/src/__tests__/tailscale.test.ts +1 -1
- package/src/__tests__/teleport.test.ts +1 -16
- package/src/__tests__/tunnel.test.ts +274 -82
- package/src/__tests__/uninstall.test.ts +141 -9
- package/src/__tests__/wake.test.ts +2 -2
- package/src/commands/backup.ts +3 -3
- package/src/commands/client.ts +4 -6
- package/src/commands/completions.test.ts +104 -0
- package/src/commands/completions.ts +87 -0
- package/src/commands/exec.ts +7 -7
- package/src/commands/flags.ts +2 -2
- package/src/commands/hatch.ts +5 -2
- package/src/commands/logs.ts +7 -7
- package/src/commands/pair.ts +140 -37
- package/src/commands/ps.ts +63 -26
- package/src/commands/restore.ts +2 -2
- package/src/commands/retire.ts +2 -2
- package/src/commands/rollback.ts +2 -2
- package/src/commands/sleep.ts +2 -2
- package/src/commands/teleport.ts +18 -37
- package/src/commands/terminal.ts +1 -1
- package/src/commands/tunnel.ts +101 -78
- package/src/commands/uninstall.ts +108 -73
- package/src/commands/upgrade.ts +8 -8
- package/src/commands/use.ts +1 -1
- package/src/commands/wake.ts +6 -6
- package/src/index.ts +15 -0
- package/src/lib/__tests__/docker.test.ts +168 -20
- package/src/lib/__tests__/job-polling.test.ts +1 -1
- package/src/lib/__tests__/local-gateway-cli.test.ts +42 -0
- package/src/lib/cloud-client.ts +0 -3
- package/src/lib/cloud-login.ts +0 -1
- package/src/lib/cloud-releases.ts +6 -4
- package/src/lib/color.ts +4 -0
- package/src/lib/docker.ts +190 -102
- package/src/lib/ensure-binary.ts +171 -0
- package/src/lib/environments/__tests__/paths.test.ts +27 -1
- package/src/lib/environments/paths.ts +16 -8
- package/src/lib/environments/resolve.ts +2 -4
- package/src/lib/flag-args.test.ts +1 -1
- package/src/lib/flag-args.ts +1 -1
- package/src/lib/guardian-token.ts +1 -1
- package/src/lib/hatch-local.ts +15 -8
- package/src/lib/hatch-next-steps.ts +5 -9
- package/src/lib/job-polling.ts +1 -1
- package/src/lib/local.ts +37 -4
- package/src/lib/nginx-ingress.ts +3 -5
- package/src/lib/pair.ts +1 -1
- package/src/lib/step-runner.ts +6 -1
- package/src/lib/sync-cloud-assistants.ts +2 -2
- package/src/lib/tunnels/cloudflare.ts +21 -11
- package/src/lib/tunnels/ngrok.ts +198 -108
- package/src/lib/tunnels/tailscale.ts +34 -4
- package/src/lib/upgrade-lifecycle.ts +5 -4
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `
|
|
2
|
+
* `show_cloud_login` SSE event.
|
|
3
3
|
*
|
|
4
4
|
* Emitted by the cloud-connect route when no cloud credentials are
|
|
5
5
|
* stored yet, signalling connected clients to surface the cloud login UI.
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
import { z } from "zod";
|
|
13
13
|
|
|
14
14
|
export const ShowCloudLoginEventSchema = z.object({
|
|
15
|
-
type: z.literal("
|
|
15
|
+
type: z.literal("show_cloud_login"),
|
|
16
16
|
});
|
|
17
17
|
|
|
18
18
|
export type ShowCloudLoginEvent = z.infer<typeof ShowCloudLoginEventSchema>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mycadet/cli",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.9-dev.202610131620.d833e01",
|
|
4
4
|
"description": "CLI tools for mycadet",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"./src/commands/*": "./src/commands/*.ts"
|
|
16
16
|
},
|
|
17
17
|
"bin": {
|
|
18
|
-
"cadet": "./src/index.ts"
|
|
18
|
+
"cadet-dev": "./src/index.ts"
|
|
19
19
|
},
|
|
20
20
|
"scripts": {
|
|
21
21
|
"format": "prettier --write .",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"lint:unused": "knip --include files,dependencies,unlisted",
|
|
25
25
|
"compile:check": "bun build --compile src/index.ts --outfile /dev/null",
|
|
26
26
|
"test": "bun run scripts/run-tests.ts",
|
|
27
|
-
"typecheck": "bunx
|
|
27
|
+
"typecheck": "bunx tsgo --noEmit"
|
|
28
28
|
},
|
|
29
29
|
"author": "Cadet AI",
|
|
30
30
|
"license": "MIT",
|
|
@@ -57,6 +57,7 @@
|
|
|
57
57
|
"@types/bun": "1.3.11",
|
|
58
58
|
"@types/qrcode-terminal": "0.12.2",
|
|
59
59
|
"@types/react": "19.2.14",
|
|
60
|
+
"@typescript/native-preview": "7.0.0-dev.20260624.1",
|
|
60
61
|
"eslint": "10.1.0",
|
|
61
62
|
"knip": "5.88.1",
|
|
62
63
|
"prettier": "3.8.1",
|
|
@@ -7,7 +7,11 @@ import {
|
|
|
7
7
|
resolveOwnImageRefs,
|
|
8
8
|
} from "../lib/cloud-releases.js";
|
|
9
9
|
import { dockerhubImages } from "../lib/docker.js";
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
isDevVersion,
|
|
12
|
+
isLocalVersion,
|
|
13
|
+
isStagingVersion,
|
|
14
|
+
} from "../lib/version-compat.js";
|
|
11
15
|
|
|
12
16
|
const originalFetch = globalThis.fetch;
|
|
13
17
|
|
|
@@ -79,12 +83,12 @@ describe("fetchReleases", () => {
|
|
|
79
83
|
});
|
|
80
84
|
|
|
81
85
|
describe("resolveImageRefs", () => {
|
|
82
|
-
test("released CLI resolves un-suffixed repos tagged with the
|
|
86
|
+
test("released CLI resolves un-suffixed repos tagged with the bare version", () => {
|
|
83
87
|
const tags = resolveImageRefs("v0.12.6", "0.12.6");
|
|
84
|
-
expect(tags.assistant).toBe("mycadet/mycadet-assistant:
|
|
85
|
-
expect(tags.gateway).toBe("mycadet/mycadet-gateway:
|
|
88
|
+
expect(tags.assistant).toBe("mycadet/mycadet-assistant:0.12.6");
|
|
89
|
+
expect(tags.gateway).toBe("mycadet/mycadet-gateway:0.12.6");
|
|
86
90
|
expect(tags["credential-executor"]).toBe(
|
|
87
|
-
"mycadet/mycadet-credential-executor:
|
|
91
|
+
"mycadet/mycadet-credential-executor:0.12.6",
|
|
88
92
|
);
|
|
89
93
|
});
|
|
90
94
|
|
|
@@ -94,31 +98,32 @@ describe("resolveImageRefs", () => {
|
|
|
94
98
|
"0.12.6-dev.202610081023.3a27b77",
|
|
95
99
|
);
|
|
96
100
|
expect(tags.assistant).toBe(
|
|
97
|
-
"mycadet/mycadet-assistant-dev:
|
|
101
|
+
"mycadet/mycadet-assistant-dev:0.12.6-dev.202610081023.3a27b77",
|
|
98
102
|
);
|
|
99
103
|
expect(tags.gateway).toBe(
|
|
100
|
-
"mycadet/mycadet-gateway-dev:
|
|
104
|
+
"mycadet/mycadet-gateway-dev:0.12.6-dev.202610081023.3a27b77",
|
|
101
105
|
);
|
|
102
106
|
expect(tags["credential-executor"]).toBe(
|
|
103
|
-
"mycadet/mycadet-credential-executor-dev:
|
|
107
|
+
"mycadet/mycadet-credential-executor-dev:0.12.6-dev.202610081023.3a27b77",
|
|
104
108
|
);
|
|
105
109
|
});
|
|
106
110
|
|
|
107
111
|
test("repo suffix follows the CLI version, tag follows the target", () => {
|
|
108
112
|
const tags = resolveImageRefs("v0.12.5", "0.12.6-dev.202610081023.3a27b77");
|
|
109
|
-
expect(tags.assistant).toBe("mycadet/mycadet-assistant-dev:
|
|
113
|
+
expect(tags.assistant).toBe("mycadet/mycadet-assistant-dev:0.12.5");
|
|
110
114
|
});
|
|
111
115
|
|
|
112
116
|
test("does not consult the network", async () => {
|
|
113
117
|
mockFetchError();
|
|
114
118
|
const tags = resolveImageRefs("v0.12.6", "0.12.6");
|
|
115
|
-
expect(tags.assistant).toContain(":
|
|
119
|
+
expect(tags.assistant).toContain(":0.12.6");
|
|
116
120
|
});
|
|
117
121
|
|
|
118
|
-
test("normalizes the v-prefix
|
|
122
|
+
test("normalizes the v-prefix off the tag", () => {
|
|
119
123
|
const withoutPrefix = resolveImageRefs("0.12.6", "0.12.6");
|
|
120
124
|
const withPrefix = resolveImageRefs("v0.12.6", "0.12.6");
|
|
121
|
-
expect(withoutPrefix.assistant).toBe(
|
|
125
|
+
expect(withoutPrefix.assistant).toBe("mycadet/mycadet-assistant:0.12.6");
|
|
126
|
+
expect(withPrefix.assistant).toBe(withoutPrefix.assistant);
|
|
122
127
|
});
|
|
123
128
|
});
|
|
124
129
|
|
|
@@ -126,9 +131,14 @@ describe("resolveOwnImageRefs", () => {
|
|
|
126
131
|
test("uses the CLI's own version for both tag and repo suffix", () => {
|
|
127
132
|
const tags = resolveOwnImageRefs("0.12.6-dev.202610081023.3a27b77");
|
|
128
133
|
expect(tags.assistant).toBe(
|
|
129
|
-
"mycadet/mycadet-assistant-dev:
|
|
134
|
+
"mycadet/mycadet-assistant-dev:0.12.6-dev.202610081023.3a27b77",
|
|
130
135
|
);
|
|
131
136
|
});
|
|
137
|
+
|
|
138
|
+
test("falls back to the literal latest tag when the CLI version is unknown", () => {
|
|
139
|
+
const tags = resolveOwnImageRefs("");
|
|
140
|
+
expect(tags.assistant).toBe("mycadet/mycadet-assistant:latest");
|
|
141
|
+
});
|
|
132
142
|
});
|
|
133
143
|
|
|
134
144
|
describe("dockerhubImages", () => {
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { Readable, Writable } from "node:stream";
|
|
3
|
+
|
|
4
|
+
import { ensureBinaryInstalled, readLineText } from "../lib/ensure-binary.js";
|
|
5
|
+
|
|
6
|
+
/** A real Writable capturing everything written, as a test output stream. */
|
|
7
|
+
function makeOutput(): { writable: Writable; text(): string } {
|
|
8
|
+
const captured: string[] = [];
|
|
9
|
+
const writable = new Writable({
|
|
10
|
+
write(chunk, _encoding, callback) {
|
|
11
|
+
captured.push(chunk.toString());
|
|
12
|
+
callback();
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
return { writable, text: () => captured.join("") };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
describe("ensureBinaryInstalled", () => {
|
|
19
|
+
test("returns the existing probe result without prompting", async () => {
|
|
20
|
+
const output = makeOutput();
|
|
21
|
+
const version = await ensureBinaryInstalled({
|
|
22
|
+
binary: "nginx",
|
|
23
|
+
purpose: "the tunnel edge",
|
|
24
|
+
packages: { brew: ["brew", "install", "nginx"] },
|
|
25
|
+
manualHint: "install nginx",
|
|
26
|
+
probe: () => "nginx version 1.2",
|
|
27
|
+
confirmInstall: () => {
|
|
28
|
+
throw new Error("must not prompt for a present binary");
|
|
29
|
+
},
|
|
30
|
+
streams: { output: output.writable },
|
|
31
|
+
});
|
|
32
|
+
expect(version).toBe("nginx version 1.2");
|
|
33
|
+
expect(output.text()).toBe("");
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("offers to install and runs the first available package manager", async () => {
|
|
37
|
+
const output = makeOutput();
|
|
38
|
+
const installed: string[][] = [];
|
|
39
|
+
let probeCalls = 0;
|
|
40
|
+
const version = await ensureBinaryInstalled({
|
|
41
|
+
binary: "nginx",
|
|
42
|
+
purpose: "the tunnel edge",
|
|
43
|
+
packages: {
|
|
44
|
+
brew: ["brew", "install", "nginx"],
|
|
45
|
+
"apt-get": ["sudo", "apt-get", "install", "-y", "nginx"],
|
|
46
|
+
},
|
|
47
|
+
manualHint: "install nginx yourself",
|
|
48
|
+
probe: () => (++probeCalls > 1 ? "nginx version 1.2" : null),
|
|
49
|
+
pmAvailable: (pm) => pm === "apt-get",
|
|
50
|
+
runInstall: (argv) => {
|
|
51
|
+
installed.push(argv);
|
|
52
|
+
return true;
|
|
53
|
+
},
|
|
54
|
+
confirmInstall: async () => true,
|
|
55
|
+
streams: { output: output.writable },
|
|
56
|
+
});
|
|
57
|
+
expect(version).toBe("nginx version 1.2");
|
|
58
|
+
expect(installed).toEqual([["sudo", "apt-get", "install", "-y", "nginx"]]);
|
|
59
|
+
expect(output.text()).toContain("isn't installed");
|
|
60
|
+
expect(output.text()).not.toContain("apt-get install");
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("the default confirm takes Enter as yes", async () => {
|
|
64
|
+
const output = makeOutput();
|
|
65
|
+
const input = Object.assign(new Readable({ read() {} }), {
|
|
66
|
+
isTTY: true,
|
|
67
|
+
});
|
|
68
|
+
const installed: string[][] = [];
|
|
69
|
+
let probeCalls = 0;
|
|
70
|
+
const pending = ensureBinaryInstalled({
|
|
71
|
+
binary: "nginx",
|
|
72
|
+
purpose: "the tunnel edge",
|
|
73
|
+
packages: { brew: ["brew", "install", "nginx"] },
|
|
74
|
+
manualHint: "install nginx yourself",
|
|
75
|
+
probe: () => (++probeCalls > 1 ? "nginx version 1.2" : null),
|
|
76
|
+
pmAvailable: () => true,
|
|
77
|
+
runInstall: (argv) => {
|
|
78
|
+
installed.push(argv);
|
|
79
|
+
return true;
|
|
80
|
+
},
|
|
81
|
+
streams: { input, output: output.writable },
|
|
82
|
+
});
|
|
83
|
+
input.emit("data", "\n");
|
|
84
|
+
expect(await pending).toBe("nginx version 1.2");
|
|
85
|
+
expect(installed).toEqual([["brew", "install", "nginx"]]);
|
|
86
|
+
expect(output.text()).toContain("Install nginx now?");
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test("a declined offer prints the manual hint and installs nothing", async () => {
|
|
90
|
+
const output = makeOutput();
|
|
91
|
+
let ran = false;
|
|
92
|
+
const version = await ensureBinaryInstalled({
|
|
93
|
+
binary: "ngrok",
|
|
94
|
+
purpose: "the ngrok tunnel",
|
|
95
|
+
packages: { brew: ["brew", "install", "ngrok/ngrok/ngrok"] },
|
|
96
|
+
manualHint: "get ngrok from ngrok.com",
|
|
97
|
+
probe: () => null,
|
|
98
|
+
runInstall: () => {
|
|
99
|
+
ran = true;
|
|
100
|
+
return true;
|
|
101
|
+
},
|
|
102
|
+
confirmInstall: async () => false,
|
|
103
|
+
streams: { output: output.writable },
|
|
104
|
+
});
|
|
105
|
+
expect(version).toBe(null);
|
|
106
|
+
expect(ran).toBe(false);
|
|
107
|
+
expect(output.text()).toContain("get ngrok from ngrok.com");
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("a failed install falls back to the manual hint", async () => {
|
|
111
|
+
const output = makeOutput();
|
|
112
|
+
const version = await ensureBinaryInstalled({
|
|
113
|
+
binary: "nginx",
|
|
114
|
+
purpose: "the tunnel edge",
|
|
115
|
+
packages: { brew: ["brew", "install", "nginx"] },
|
|
116
|
+
manualHint: "install nginx yourself",
|
|
117
|
+
probe: () => null,
|
|
118
|
+
runInstall: () => false,
|
|
119
|
+
confirmInstall: async () => true,
|
|
120
|
+
streams: { output: output.writable },
|
|
121
|
+
});
|
|
122
|
+
expect(version).toBe(null);
|
|
123
|
+
expect(output.text()).toContain("install nginx yourself");
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test("no matching package manager prints the hint", async () => {
|
|
127
|
+
const output = makeOutput();
|
|
128
|
+
let ran = false;
|
|
129
|
+
const version = await ensureBinaryInstalled({
|
|
130
|
+
binary: "cloudflared",
|
|
131
|
+
purpose: "the cloudflare tunnel",
|
|
132
|
+
packages: { brew: ["brew", "install", "cloudflared"] },
|
|
133
|
+
manualHint: "cloudflared install docs",
|
|
134
|
+
probe: () => null,
|
|
135
|
+
pmAvailable: () => false,
|
|
136
|
+
runInstall: () => {
|
|
137
|
+
ran = true;
|
|
138
|
+
return true;
|
|
139
|
+
},
|
|
140
|
+
confirmInstall: async () => true,
|
|
141
|
+
streams: { output: output.writable },
|
|
142
|
+
});
|
|
143
|
+
expect(version).toBe(null);
|
|
144
|
+
expect(ran).toBe(false);
|
|
145
|
+
expect(output.text()).toContain("cloudflared install docs");
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("a non-interactive terminal declines instead of hanging", async () => {
|
|
149
|
+
const version = await ensureBinaryInstalled({
|
|
150
|
+
binary: "nginx",
|
|
151
|
+
purpose: "the tunnel edge",
|
|
152
|
+
packages: { brew: ["brew", "install", "nginx"] },
|
|
153
|
+
manualHint: "install nginx yourself",
|
|
154
|
+
probe: () => null,
|
|
155
|
+
confirmInstall: async () => {
|
|
156
|
+
throw new Error("Install confirm requires an interactive terminal.");
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
expect(version).toBe(null);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
describe("readLineText", () => {
|
|
164
|
+
test("resolves the first line and ignores the rest", async () => {
|
|
165
|
+
const output = makeOutput();
|
|
166
|
+
const input = new Readable({ read() {} });
|
|
167
|
+
const pending = readLineText("token: ", {
|
|
168
|
+
input,
|
|
169
|
+
output: output.writable,
|
|
170
|
+
});
|
|
171
|
+
input.emit("data", "abc123\nsecond line\n");
|
|
172
|
+
expect(await pending).toBe("abc123");
|
|
173
|
+
expect(output.text()).toBe("token: ");
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test("resolves an empty line on an immediate newline", async () => {
|
|
177
|
+
const input = new Readable({ read() {} });
|
|
178
|
+
const pending = readLineText("[Y/n] ", { input });
|
|
179
|
+
input.emit("data", "\n");
|
|
180
|
+
expect(await pending).toBe("");
|
|
181
|
+
});
|
|
182
|
+
});
|
|
@@ -160,7 +160,6 @@ describe("buildIngressNginxConfig", () => {
|
|
|
160
160
|
config: {
|
|
161
161
|
remote: "docker",
|
|
162
162
|
apiBaseUrl: "/v1",
|
|
163
|
-
cloudDisabled: true,
|
|
164
163
|
},
|
|
165
164
|
},
|
|
166
165
|
});
|
|
@@ -330,7 +329,7 @@ describe("buildIngressNginxConfig", () => {
|
|
|
330
329
|
expect(remoteConf).toContain("default_type application/json;");
|
|
331
330
|
expect(remoteConf).toContain('add_header Cache-Control "no-store";');
|
|
332
331
|
expect(remoteConf).toContain(
|
|
333
|
-
'return 200 "{\\"remote\\":\\"docker\\",\\"apiBaseUrl\\":\\"/v1\\"
|
|
332
|
+
'return 200 "{\\"remote\\":\\"docker\\",\\"apiBaseUrl\\":\\"/v1\\"}";',
|
|
334
333
|
);
|
|
335
334
|
expect(remoteConf).not.toContain("assistantId");
|
|
336
335
|
});
|
|
@@ -347,7 +346,7 @@ describe("buildIngressNginxConfig", () => {
|
|
|
347
346
|
},
|
|
348
347
|
});
|
|
349
348
|
expect(named).toContain(
|
|
350
|
-
'return 200 "{\\"remote\\":\\"custom\\",\\"apiBaseUrl\\":\\"/v1\\",\\"
|
|
349
|
+
'return 200 "{\\"remote\\":\\"custom\\",\\"apiBaseUrl\\":\\"/v1\\",\\"assistantName\\":\\"Homelab\\",\\"assistantId\\":\\"assistant-1\\",\\"hubUrl\\":\\"https://mycadet.ai/assistant\\"}";',
|
|
351
350
|
);
|
|
352
351
|
});
|
|
353
352
|
|
|
@@ -449,14 +448,13 @@ describe("buildRemoteWebIndexHtml", () => {
|
|
|
449
448
|
const result = buildRemoteWebIndexHtml(html, {
|
|
450
449
|
remote: "docker",
|
|
451
450
|
apiBaseUrl: "/v1",
|
|
452
|
-
disableCloud: true,
|
|
453
451
|
});
|
|
454
452
|
|
|
455
453
|
expect(result).toContain(
|
|
456
454
|
'window.__CADET_CONFIG__={"webUrl":"https://mycadet.ai"}',
|
|
457
455
|
);
|
|
458
456
|
expect(result).toContain(
|
|
459
|
-
'window.__CADET_CONFIG__={"remote":"docker","apiBaseUrl":"/v1"
|
|
457
|
+
'window.__CADET_CONFIG__={"remote":"docker","apiBaseUrl":"/v1"}',
|
|
460
458
|
);
|
|
461
459
|
expect(result.indexOf('"webUrl"')).toBeLessThan(
|
|
462
460
|
result.indexOf('"docker"'),
|
|
@@ -864,8 +862,6 @@ function spaConfigHash(
|
|
|
864
862
|
config: {
|
|
865
863
|
remote: "custom",
|
|
866
864
|
apiBaseUrl: "/v1",
|
|
867
|
-
cloudDisabled: true,
|
|
868
|
-
disableCloud: true,
|
|
869
865
|
...(opts.assistantName ? { assistantName: opts.assistantName } : {}),
|
|
870
866
|
...(opts.assistantId ? { assistantId: opts.assistantId } : {}),
|
|
871
867
|
hubUrl: PRODUCTION_HUB_URL,
|
|
@@ -1354,8 +1350,6 @@ describe("startRemoteWebIngress", () => {
|
|
|
1354
1350
|
config: {
|
|
1355
1351
|
remote: "custom",
|
|
1356
1352
|
apiBaseUrl: "/v1",
|
|
1357
|
-
cloudDisabled: true,
|
|
1358
|
-
disableCloud: true,
|
|
1359
1353
|
hubUrl: PRODUCTION_HUB_URL,
|
|
1360
1354
|
},
|
|
1361
1355
|
}),
|
|
@@ -2095,8 +2089,9 @@ describe("ensureTunnelEdge", () => {
|
|
|
2095
2089
|
gatewayPort: 7830,
|
|
2096
2090
|
});
|
|
2097
2091
|
|
|
2098
|
-
await expect(promise).rejects.toThrow(
|
|
2099
|
-
|
|
2092
|
+
await expect(promise).rejects.toThrow(
|
|
2093
|
+
"See https://nginx.org/en/docs/install.html for how to install",
|
|
2094
|
+
);
|
|
2100
2095
|
await expect(promise).rejects.toThrow("NGINX_BIN");
|
|
2101
2096
|
expect(spawnMock).not.toHaveBeenCalled();
|
|
2102
2097
|
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import { buildNgrokSpawnArgs, isNgrokAuthError } from "../lib/tunnels/ngrok.js";
|
|
4
|
+
|
|
5
|
+
describe("buildNgrokSpawnArgs", () => {
|
|
6
|
+
test("spawns a plain http agent against the loopback edge", () => {
|
|
7
|
+
expect(buildNgrokSpawnArgs(7830)).toEqual([
|
|
8
|
+
"http",
|
|
9
|
+
"127.0.0.1:7830",
|
|
10
|
+
"--log=stdout",
|
|
11
|
+
]);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test("never passes --config so the default (authtoken-bearing) config loads", () => {
|
|
15
|
+
const args = buildNgrokSpawnArgs(7830, "me.ngrok.app");
|
|
16
|
+
expect(args.some((arg) => arg.startsWith("--config"))).toBe(false);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test("binds a reserved domain when given", () => {
|
|
20
|
+
expect(buildNgrokSpawnArgs(7830, "my-assistant.ngrok.app")).toEqual([
|
|
21
|
+
"http",
|
|
22
|
+
"127.0.0.1:7830",
|
|
23
|
+
"--log=stdout",
|
|
24
|
+
"--domain=my-assistant.ngrok.app",
|
|
25
|
+
]);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe("isNgrokAuthError", () => {
|
|
30
|
+
test("recognizes the free-account authtoken requirement", () => {
|
|
31
|
+
expect(
|
|
32
|
+
isNgrokAuthError(
|
|
33
|
+
"FATAL: authentication failed: Usage of ngrok requires a free authtoken. ERR_NGROK_105",
|
|
34
|
+
),
|
|
35
|
+
).toBe(true);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("recognizes an unauthorized token", () => {
|
|
39
|
+
expect(isNgrokAuthError("unauthorized authtoken ERR_NGROK_401")).toBe(true);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("does not claim other startup failures are auth errors", () => {
|
|
43
|
+
expect(
|
|
44
|
+
isNgrokAuthError(
|
|
45
|
+
"creating tunnel session failed: dial tcp 3.219.x.x:443: connection refused",
|
|
46
|
+
),
|
|
47
|
+
).toBe(false);
|
|
48
|
+
});
|
|
49
|
+
});
|