@vellumai/cli 0.10.11-dev.202607220701.38c4eff → 0.10.11-dev.202607220955.d4134f8
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/package.json +1 -1
- package/src/__tests__/flags.test.ts +1 -8
- package/src/__tests__/input-history.test.ts +1 -6
- package/src/__tests__/login-loopback.test.ts +6 -2
- package/src/__tests__/pair.test.ts +435 -1
- package/src/__tests__/platform-client.test.ts +10 -2
- package/src/__tests__/statefulset.test.ts +3 -1
- package/src/__tests__/tailscale-tunnel.test.ts +229 -0
- package/src/__tests__/wake.test.ts +32 -16
- package/src/commands/backup.ts +22 -16
- package/src/commands/exec.ts +5 -1
- package/src/commands/pair.ts +121 -10
- package/src/commands/rollback.ts +2 -1
- package/src/commands/ssh.ts +1 -4
- package/src/commands/tunnel.ts +19 -1
- package/src/lib/__tests__/step-runner.test.ts +6 -1
- package/src/lib/__tests__/terminal-session.test.ts +5 -14
- package/src/lib/cloudflare-tunnel.ts +5 -52
- package/src/lib/device-id.ts +1 -3
- package/src/lib/flag-args.test.ts +1 -6
- package/src/lib/guardian-token.ts +18 -18
- package/src/lib/ingress-config.ts +75 -0
- package/src/lib/nginx-ingress.ts +1 -22
- package/src/lib/ngrok.ts +7 -62
- package/src/lib/retire-apple-container.ts +7 -5
- package/src/lib/tailscale-tunnel.ts +319 -0
- package/src/lib/upgrade-preflight.ts +10 -7
package/package.json
CHANGED
|
@@ -188,14 +188,7 @@ describe("vellum flags --assistant routing", () => {
|
|
|
188
188
|
],
|
|
189
189
|
"alice-1",
|
|
190
190
|
);
|
|
191
|
-
process.argv = [
|
|
192
|
-
"bun",
|
|
193
|
-
"vellum",
|
|
194
|
-
"flags",
|
|
195
|
-
"set",
|
|
196
|
-
"voice-mode",
|
|
197
|
-
"true",
|
|
198
|
-
];
|
|
191
|
+
process.argv = ["bun", "vellum", "flags", "set", "voice-mode", "true"];
|
|
199
192
|
|
|
200
193
|
await flags();
|
|
201
194
|
|
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
-
import {
|
|
3
|
-
existsSync,
|
|
4
|
-
mkdtempSync,
|
|
5
|
-
readFileSync,
|
|
6
|
-
rmSync,
|
|
7
|
-
} from "node:fs";
|
|
2
|
+
import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
8
3
|
import { homedir, tmpdir } from "node:os";
|
|
9
4
|
import { join } from "node:path";
|
|
10
5
|
|
|
@@ -3,7 +3,9 @@ import { describe, expect, test } from "bun:test";
|
|
|
3
3
|
import { startLoopbackListener } from "../commands/login.js";
|
|
4
4
|
|
|
5
5
|
/** Resolve "settled"/"pending" — proves whether `waitForCode` resolved. */
|
|
6
|
-
async function settleState(
|
|
6
|
+
async function settleState(
|
|
7
|
+
p: Promise<unknown>,
|
|
8
|
+
): Promise<"settled" | "pending"> {
|
|
7
9
|
return Promise.race([
|
|
8
10
|
p.then(
|
|
9
11
|
() => "settled" as const,
|
|
@@ -49,7 +51,9 @@ describe("startLoopbackListener", () => {
|
|
|
49
51
|
() => null,
|
|
50
52
|
(e: Error) => e,
|
|
51
53
|
);
|
|
52
|
-
const res = await fetch(
|
|
54
|
+
const res = await fetch(
|
|
55
|
+
`${listener.redirectUri}?error=access_denied&state=st`,
|
|
56
|
+
);
|
|
53
57
|
expect(res.status).toBe(400);
|
|
54
58
|
const err = await settled;
|
|
55
59
|
expect(err?.message).toMatch(/access_denied/);
|
|
@@ -15,7 +15,7 @@ import { join } from "node:path";
|
|
|
15
15
|
const testDir = mkdtempSync(join(tmpdir(), "pair-command-test-"));
|
|
16
16
|
process.env.VELLUM_LOCKFILE_DIR = testDir;
|
|
17
17
|
|
|
18
|
-
import { pair } from "../commands/pair.js";
|
|
18
|
+
import { buildAppConnectUrl, pair } from "../commands/pair.js";
|
|
19
19
|
|
|
20
20
|
// Distinct loopback (mint) vs reachable (advertised) URLs to verify the split.
|
|
21
21
|
const LOCAL_URL = "http://127.0.0.1:7830";
|
|
@@ -800,4 +800,438 @@ describe("pair command", () => {
|
|
|
800
800
|
`${LOCAL_URL}/v1/assistants/pair-test/feature-flags`,
|
|
801
801
|
);
|
|
802
802
|
});
|
|
803
|
+
|
|
804
|
+
test("buildAppConnectUrl composes and encodes the connect link", () => {
|
|
805
|
+
expect(
|
|
806
|
+
buildAppConnectUrl(
|
|
807
|
+
"vellum-assistant",
|
|
808
|
+
"https://pair.example.ts.net",
|
|
809
|
+
"device-code",
|
|
810
|
+
),
|
|
811
|
+
).toBe(
|
|
812
|
+
"vellum-assistant://connect?url=https%3A%2F%2Fpair.example.ts.net&code=device-code",
|
|
813
|
+
);
|
|
814
|
+
// Path prefixes and fragment-hostile characters survive the encoding.
|
|
815
|
+
expect(
|
|
816
|
+
buildAppConnectUrl(
|
|
817
|
+
"vellum-assistant-dev",
|
|
818
|
+
"https://host.example.ts.net/assistant-123",
|
|
819
|
+
"a+b/c=",
|
|
820
|
+
),
|
|
821
|
+
).toBe(
|
|
822
|
+
"vellum-assistant-dev://connect?url=https%3A%2F%2Fhost.example.ts.net%2Fassistant-123&code=a%2Bb%2Fc%3D",
|
|
823
|
+
);
|
|
824
|
+
});
|
|
825
|
+
|
|
826
|
+
test("--qr --app emits an app connect URL alongside the browser URL", async () => {
|
|
827
|
+
const origFetch = globalThis.fetch;
|
|
828
|
+
globalThis.fetch = (async (url: string) => {
|
|
829
|
+
if (url === `${LOCAL_URL}/v1/assistants/pair-test/feature-flags`) {
|
|
830
|
+
return new Response(
|
|
831
|
+
JSON.stringify({
|
|
832
|
+
flags: [{ key: "web-remote-ingress", enabled: true }],
|
|
833
|
+
}),
|
|
834
|
+
{ status: 200, headers: { "content-type": "application/json" } },
|
|
835
|
+
);
|
|
836
|
+
}
|
|
837
|
+
if (url === `${LOCAL_URL}/v1/remote-web/pairing-challenge`) {
|
|
838
|
+
return new Response(
|
|
839
|
+
JSON.stringify({
|
|
840
|
+
deviceCode: "device-code",
|
|
841
|
+
userCode: "ABCD-EFGH",
|
|
842
|
+
verificationUri: "https://pair.example.ts.net/assistant/pair",
|
|
843
|
+
expiresAt: "2026-06-04T00:10:00.000Z",
|
|
844
|
+
expiresInSeconds: 600,
|
|
845
|
+
}),
|
|
846
|
+
{ status: 200, headers: { "content-type": "application/json" } },
|
|
847
|
+
);
|
|
848
|
+
}
|
|
849
|
+
if (url === `${LOCAL_URL}/v1/remote-web/pairing-verification`) {
|
|
850
|
+
return new Response(
|
|
851
|
+
JSON.stringify({
|
|
852
|
+
status: "approved",
|
|
853
|
+
verificationUri: "https://pair.example.ts.net/assistant/pair",
|
|
854
|
+
expiresAt: "2026-06-04T00:10:00.000Z",
|
|
855
|
+
}),
|
|
856
|
+
{ status: 200, headers: { "content-type": "application/json" } },
|
|
857
|
+
);
|
|
858
|
+
}
|
|
859
|
+
return new Response("not found", { status: 404 });
|
|
860
|
+
}) as unknown as typeof fetch;
|
|
861
|
+
|
|
862
|
+
const logs: string[] = [];
|
|
863
|
+
const logSpy = spyOn(console, "log").mockImplementation(
|
|
864
|
+
(...a: unknown[]) => {
|
|
865
|
+
logs.push(a.join(" "));
|
|
866
|
+
},
|
|
867
|
+
);
|
|
868
|
+
|
|
869
|
+
process.argv = [
|
|
870
|
+
"bun",
|
|
871
|
+
"vellum",
|
|
872
|
+
"pair",
|
|
873
|
+
"--qr",
|
|
874
|
+
"--app",
|
|
875
|
+
"--app-scheme",
|
|
876
|
+
"vellum-assistant-dev",
|
|
877
|
+
"--url",
|
|
878
|
+
"https://pair.example.ts.net",
|
|
879
|
+
"--json",
|
|
880
|
+
];
|
|
881
|
+
try {
|
|
882
|
+
await pair();
|
|
883
|
+
} finally {
|
|
884
|
+
logSpy.mockRestore();
|
|
885
|
+
globalThis.fetch = origFetch;
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
const out = JSON.parse(logs.join("\n"));
|
|
889
|
+
expect(out.appUrl).toBe(
|
|
890
|
+
"vellum-assistant-dev://connect?url=https%3A%2F%2Fpair.example.ts.net&code=device-code",
|
|
891
|
+
);
|
|
892
|
+
// The browser URL stays available as the no-app fallback.
|
|
893
|
+
expect(out.pairUrl).toBe(
|
|
894
|
+
"https://pair.example.ts.net/assistant/pair#device_code=device-code",
|
|
895
|
+
);
|
|
896
|
+
expect(out.deviceCode).toBe("device-code");
|
|
897
|
+
});
|
|
898
|
+
|
|
899
|
+
test("--app without --qr is refused", async () => {
|
|
900
|
+
let fetchCalled = false;
|
|
901
|
+
const origFetch = globalThis.fetch;
|
|
902
|
+
globalThis.fetch = (async () => {
|
|
903
|
+
fetchCalled = true;
|
|
904
|
+
return new Response("{}", { status: 200 });
|
|
905
|
+
}) as unknown as typeof fetch;
|
|
906
|
+
const errors: string[] = [];
|
|
907
|
+
const errSpy = spyOn(console, "error").mockImplementation(
|
|
908
|
+
(...a: unknown[]) => {
|
|
909
|
+
errors.push(a.join(" "));
|
|
910
|
+
},
|
|
911
|
+
);
|
|
912
|
+
const exitSpy = spyOn(process, "exit").mockImplementation(((
|
|
913
|
+
code?: number,
|
|
914
|
+
) => {
|
|
915
|
+
throw new Error(`exit:${code}`);
|
|
916
|
+
}) as never);
|
|
917
|
+
|
|
918
|
+
process.argv = ["bun", "vellum", "pair", "--app"];
|
|
919
|
+
let exited = false;
|
|
920
|
+
try {
|
|
921
|
+
await pair();
|
|
922
|
+
} catch (e) {
|
|
923
|
+
exited = (e as Error).message === "exit:1";
|
|
924
|
+
} finally {
|
|
925
|
+
errSpy.mockRestore();
|
|
926
|
+
exitSpy.mockRestore();
|
|
927
|
+
globalThis.fetch = origFetch;
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
expect(exited).toBe(true);
|
|
931
|
+
expect(errors.join("\n")).toContain("--qr");
|
|
932
|
+
expect(fetchCalled).toBe(false);
|
|
933
|
+
});
|
|
934
|
+
|
|
935
|
+
test("--qr with no --url uses the tunnel-saved ingress URL", async () => {
|
|
936
|
+
const workspaceDir = mkdtempSync(join(tmpdir(), "vellum-pair-ingress-"));
|
|
937
|
+
// Point the synthesized instance dir at an empty temp XDG root so the
|
|
938
|
+
// resolution deterministically falls through to VELLUM_WORKSPACE_DIR.
|
|
939
|
+
const xdgDir = mkdtempSync(join(tmpdir(), "vellum-pair-xdg-"));
|
|
940
|
+
process.env.XDG_DATA_HOME = xdgDir;
|
|
941
|
+
writeFileSync(
|
|
942
|
+
join(workspaceDir, "config.json"),
|
|
943
|
+
JSON.stringify({
|
|
944
|
+
ingress: {
|
|
945
|
+
publicBaseUrl: "https://saved.example.ts.net",
|
|
946
|
+
enabled: true,
|
|
947
|
+
},
|
|
948
|
+
}),
|
|
949
|
+
);
|
|
950
|
+
process.env.VELLUM_WORKSPACE_DIR = workspaceDir;
|
|
951
|
+
|
|
952
|
+
const calls: string[] = [];
|
|
953
|
+
const origFetch = globalThis.fetch;
|
|
954
|
+
globalThis.fetch = (async (url: string, init?: RequestInit) => {
|
|
955
|
+
calls.push(url);
|
|
956
|
+
if (url === `${LOCAL_URL}/v1/assistants/pair-test/feature-flags`) {
|
|
957
|
+
return new Response(
|
|
958
|
+
JSON.stringify({
|
|
959
|
+
flags: [{ key: "web-remote-ingress", enabled: true }],
|
|
960
|
+
}),
|
|
961
|
+
{ status: 200, headers: { "content-type": "application/json" } },
|
|
962
|
+
);
|
|
963
|
+
}
|
|
964
|
+
if (url === `${LOCAL_URL}/v1/remote-web/pairing-challenge`) {
|
|
965
|
+
// The minted challenge must advertise the SAVED ingress URL.
|
|
966
|
+
expect(JSON.parse(init?.body as string)).toEqual({
|
|
967
|
+
publicBaseUrl: "https://saved.example.ts.net",
|
|
968
|
+
});
|
|
969
|
+
return new Response(
|
|
970
|
+
JSON.stringify({
|
|
971
|
+
deviceCode: "device-code",
|
|
972
|
+
userCode: "ABCD-EFGH",
|
|
973
|
+
verificationUri: "https://saved.example.ts.net/assistant/pair",
|
|
974
|
+
expiresAt: "2026-06-04T00:10:00.000Z",
|
|
975
|
+
expiresInSeconds: 600,
|
|
976
|
+
}),
|
|
977
|
+
{ status: 200, headers: { "content-type": "application/json" } },
|
|
978
|
+
);
|
|
979
|
+
}
|
|
980
|
+
if (url === `${LOCAL_URL}/v1/remote-web/pairing-verification`) {
|
|
981
|
+
return new Response(
|
|
982
|
+
JSON.stringify({
|
|
983
|
+
status: "approved",
|
|
984
|
+
verificationUri: "https://saved.example.ts.net/assistant/pair",
|
|
985
|
+
expiresAt: "2026-06-04T00:10:00.000Z",
|
|
986
|
+
}),
|
|
987
|
+
{ status: 200, headers: { "content-type": "application/json" } },
|
|
988
|
+
);
|
|
989
|
+
}
|
|
990
|
+
return new Response("not found", { status: 404 });
|
|
991
|
+
}) as unknown as typeof fetch;
|
|
992
|
+
|
|
993
|
+
const logs: string[] = [];
|
|
994
|
+
const logSpy = spyOn(console, "log").mockImplementation(
|
|
995
|
+
(...a: unknown[]) => {
|
|
996
|
+
logs.push(a.join(" "));
|
|
997
|
+
},
|
|
998
|
+
);
|
|
999
|
+
|
|
1000
|
+
process.argv = ["bun", "vellum", "pair", "--qr"];
|
|
1001
|
+
try {
|
|
1002
|
+
await pair();
|
|
1003
|
+
} finally {
|
|
1004
|
+
logSpy.mockRestore();
|
|
1005
|
+
globalThis.fetch = origFetch;
|
|
1006
|
+
delete process.env.VELLUM_WORKSPACE_DIR;
|
|
1007
|
+
delete process.env.XDG_DATA_HOME;
|
|
1008
|
+
rmSync(workspaceDir, { recursive: true, force: true });
|
|
1009
|
+
rmSync(xdgDir, { recursive: true, force: true });
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
expect(calls).toContain(`${LOCAL_URL}/v1/remote-web/pairing-challenge`);
|
|
1013
|
+
const output = logs.join("\n");
|
|
1014
|
+
expect(output).toContain(
|
|
1015
|
+
"Using saved ingress URL https://saved.example.ts.net",
|
|
1016
|
+
);
|
|
1017
|
+
expect(output).toContain(
|
|
1018
|
+
"https://saved.example.ts.net/assistant/pair#device_code=device-code",
|
|
1019
|
+
);
|
|
1020
|
+
});
|
|
1021
|
+
|
|
1022
|
+
test("--url beats the saved ingress URL", async () => {
|
|
1023
|
+
const workspaceDir = mkdtempSync(join(tmpdir(), "vellum-pair-ingress-"));
|
|
1024
|
+
// Point the synthesized instance dir at an empty temp XDG root so the
|
|
1025
|
+
// resolution deterministically falls through to VELLUM_WORKSPACE_DIR.
|
|
1026
|
+
const xdgDir = mkdtempSync(join(tmpdir(), "vellum-pair-xdg-"));
|
|
1027
|
+
process.env.XDG_DATA_HOME = xdgDir;
|
|
1028
|
+
writeFileSync(
|
|
1029
|
+
join(workspaceDir, "config.json"),
|
|
1030
|
+
JSON.stringify({
|
|
1031
|
+
ingress: {
|
|
1032
|
+
publicBaseUrl: "https://saved.example.ts.net",
|
|
1033
|
+
enabled: true,
|
|
1034
|
+
},
|
|
1035
|
+
}),
|
|
1036
|
+
);
|
|
1037
|
+
process.env.VELLUM_WORKSPACE_DIR = workspaceDir;
|
|
1038
|
+
|
|
1039
|
+
const origFetch = globalThis.fetch;
|
|
1040
|
+
globalThis.fetch = (async (url: string, init?: RequestInit) => {
|
|
1041
|
+
if (url === `${LOCAL_URL}/v1/assistants/pair-test/feature-flags`) {
|
|
1042
|
+
return new Response(
|
|
1043
|
+
JSON.stringify({
|
|
1044
|
+
flags: [{ key: "web-remote-ingress", enabled: true }],
|
|
1045
|
+
}),
|
|
1046
|
+
{ status: 200, headers: { "content-type": "application/json" } },
|
|
1047
|
+
);
|
|
1048
|
+
}
|
|
1049
|
+
if (url === `${LOCAL_URL}/v1/remote-web/pairing-challenge`) {
|
|
1050
|
+
expect(JSON.parse(init?.body as string)).toEqual({
|
|
1051
|
+
publicBaseUrl: "https://explicit.example.ts.net",
|
|
1052
|
+
});
|
|
1053
|
+
return new Response(
|
|
1054
|
+
JSON.stringify({
|
|
1055
|
+
deviceCode: "device-code",
|
|
1056
|
+
userCode: "ABCD-EFGH",
|
|
1057
|
+
verificationUri: "https://explicit.example.ts.net/assistant/pair",
|
|
1058
|
+
expiresAt: "2026-06-04T00:10:00.000Z",
|
|
1059
|
+
expiresInSeconds: 600,
|
|
1060
|
+
}),
|
|
1061
|
+
{ status: 200, headers: { "content-type": "application/json" } },
|
|
1062
|
+
);
|
|
1063
|
+
}
|
|
1064
|
+
if (url === `${LOCAL_URL}/v1/remote-web/pairing-verification`) {
|
|
1065
|
+
return new Response(
|
|
1066
|
+
JSON.stringify({
|
|
1067
|
+
status: "approved",
|
|
1068
|
+
verificationUri: "https://explicit.example.ts.net/assistant/pair",
|
|
1069
|
+
expiresAt: "2026-06-04T00:10:00.000Z",
|
|
1070
|
+
}),
|
|
1071
|
+
{ status: 200, headers: { "content-type": "application/json" } },
|
|
1072
|
+
);
|
|
1073
|
+
}
|
|
1074
|
+
return new Response("not found", { status: 404 });
|
|
1075
|
+
}) as unknown as typeof fetch;
|
|
1076
|
+
|
|
1077
|
+
const logs: string[] = [];
|
|
1078
|
+
const logSpy = spyOn(console, "log").mockImplementation(
|
|
1079
|
+
(...a: unknown[]) => {
|
|
1080
|
+
logs.push(a.join(" "));
|
|
1081
|
+
},
|
|
1082
|
+
);
|
|
1083
|
+
|
|
1084
|
+
process.argv = [
|
|
1085
|
+
"bun",
|
|
1086
|
+
"vellum",
|
|
1087
|
+
"pair",
|
|
1088
|
+
"--qr",
|
|
1089
|
+
"--url",
|
|
1090
|
+
"https://explicit.example.ts.net",
|
|
1091
|
+
];
|
|
1092
|
+
try {
|
|
1093
|
+
await pair();
|
|
1094
|
+
} finally {
|
|
1095
|
+
logSpy.mockRestore();
|
|
1096
|
+
globalThis.fetch = origFetch;
|
|
1097
|
+
delete process.env.VELLUM_WORKSPACE_DIR;
|
|
1098
|
+
delete process.env.XDG_DATA_HOME;
|
|
1099
|
+
rmSync(workspaceDir, { recursive: true, force: true });
|
|
1100
|
+
rmSync(xdgDir, { recursive: true, force: true });
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
expect(logs.join("\n")).not.toContain("Using saved ingress URL");
|
|
1104
|
+
});
|
|
1105
|
+
|
|
1106
|
+
test("a non-https saved ingress URL is ignored", async () => {
|
|
1107
|
+
const workspaceDir = mkdtempSync(join(tmpdir(), "vellum-pair-ingress-"));
|
|
1108
|
+
// Point the synthesized instance dir at an empty temp XDG root so the
|
|
1109
|
+
// resolution deterministically falls through to VELLUM_WORKSPACE_DIR.
|
|
1110
|
+
const xdgDir = mkdtempSync(join(tmpdir(), "vellum-pair-xdg-"));
|
|
1111
|
+
process.env.XDG_DATA_HOME = xdgDir;
|
|
1112
|
+
writeFileSync(
|
|
1113
|
+
join(workspaceDir, "config.json"),
|
|
1114
|
+
JSON.stringify({
|
|
1115
|
+
ingress: {
|
|
1116
|
+
publicBaseUrl: "http://insecure.example.com",
|
|
1117
|
+
enabled: true,
|
|
1118
|
+
},
|
|
1119
|
+
}),
|
|
1120
|
+
);
|
|
1121
|
+
process.env.VELLUM_WORKSPACE_DIR = workspaceDir;
|
|
1122
|
+
|
|
1123
|
+
const origFetch = globalThis.fetch;
|
|
1124
|
+
let minted = false;
|
|
1125
|
+
globalThis.fetch = (async (url: string) => {
|
|
1126
|
+
if (url === `${LOCAL_URL}/v1/assistants/pair-test/feature-flags`) {
|
|
1127
|
+
return new Response(
|
|
1128
|
+
JSON.stringify({
|
|
1129
|
+
flags: [{ key: "web-remote-ingress", enabled: true }],
|
|
1130
|
+
}),
|
|
1131
|
+
{ status: 200, headers: { "content-type": "application/json" } },
|
|
1132
|
+
);
|
|
1133
|
+
}
|
|
1134
|
+
minted = true;
|
|
1135
|
+
return new Response("{}", { status: 200 });
|
|
1136
|
+
}) as unknown as typeof fetch;
|
|
1137
|
+
|
|
1138
|
+
const errors: string[] = [];
|
|
1139
|
+
const errSpy = spyOn(console, "error").mockImplementation(
|
|
1140
|
+
(...a: unknown[]) => {
|
|
1141
|
+
errors.push(a.join(" "));
|
|
1142
|
+
},
|
|
1143
|
+
);
|
|
1144
|
+
const exitSpy = spyOn(process, "exit").mockImplementation(((
|
|
1145
|
+
code?: number,
|
|
1146
|
+
) => {
|
|
1147
|
+
throw new Error(`exit:${code}`);
|
|
1148
|
+
}) as never);
|
|
1149
|
+
|
|
1150
|
+
process.argv = ["bun", "vellum", "pair", "--qr"];
|
|
1151
|
+
let exited = false;
|
|
1152
|
+
try {
|
|
1153
|
+
await pair();
|
|
1154
|
+
} catch (e) {
|
|
1155
|
+
exited = (e as Error).message === "exit:1";
|
|
1156
|
+
} finally {
|
|
1157
|
+
errSpy.mockRestore();
|
|
1158
|
+
exitSpy.mockRestore();
|
|
1159
|
+
globalThis.fetch = origFetch;
|
|
1160
|
+
delete process.env.VELLUM_WORKSPACE_DIR;
|
|
1161
|
+
delete process.env.XDG_DATA_HOME;
|
|
1162
|
+
rmSync(workspaceDir, { recursive: true, force: true });
|
|
1163
|
+
rmSync(xdgDir, { recursive: true, force: true });
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
// The saved http URL is skipped, so --qr falls through to the (non-https)
|
|
1167
|
+
// runtime URL and refuses — proving the saved value was not advertised.
|
|
1168
|
+
expect(exited).toBe(true);
|
|
1169
|
+
expect(errors.join("\n")).toContain(RUNTIME_URL);
|
|
1170
|
+
expect(minted).toBe(false);
|
|
1171
|
+
});
|
|
1172
|
+
|
|
1173
|
+
test("a saved ingress URL with ingress disabled is ignored", async () => {
|
|
1174
|
+
const workspaceDir = mkdtempSync(join(tmpdir(), "vellum-pair-ingress-"));
|
|
1175
|
+
// Point the synthesized instance dir at an empty temp XDG root so the
|
|
1176
|
+
// resolution deterministically falls through to VELLUM_WORKSPACE_DIR.
|
|
1177
|
+
const xdgDir = mkdtempSync(join(tmpdir(), "vellum-pair-xdg-"));
|
|
1178
|
+
process.env.XDG_DATA_HOME = xdgDir;
|
|
1179
|
+
writeFileSync(
|
|
1180
|
+
join(workspaceDir, "config.json"),
|
|
1181
|
+
JSON.stringify({
|
|
1182
|
+
ingress: {
|
|
1183
|
+
publicBaseUrl: "https://saved.example.ts.net",
|
|
1184
|
+
enabled: false,
|
|
1185
|
+
},
|
|
1186
|
+
}),
|
|
1187
|
+
);
|
|
1188
|
+
process.env.VELLUM_WORKSPACE_DIR = workspaceDir;
|
|
1189
|
+
|
|
1190
|
+
const origFetch = globalThis.fetch;
|
|
1191
|
+
let minted = false;
|
|
1192
|
+
globalThis.fetch = (async (url: string) => {
|
|
1193
|
+
if (url === `${LOCAL_URL}/v1/assistants/pair-test/feature-flags`) {
|
|
1194
|
+
return new Response(
|
|
1195
|
+
JSON.stringify({
|
|
1196
|
+
flags: [{ key: "web-remote-ingress", enabled: true }],
|
|
1197
|
+
}),
|
|
1198
|
+
{ status: 200, headers: { "content-type": "application/json" } },
|
|
1199
|
+
);
|
|
1200
|
+
}
|
|
1201
|
+
minted = true;
|
|
1202
|
+
return new Response("{}", { status: 200 });
|
|
1203
|
+
}) as unknown as typeof fetch;
|
|
1204
|
+
|
|
1205
|
+
const errors: string[] = [];
|
|
1206
|
+
const errSpy = spyOn(console, "error").mockImplementation(
|
|
1207
|
+
(...a: unknown[]) => {
|
|
1208
|
+
errors.push(a.join(" "));
|
|
1209
|
+
},
|
|
1210
|
+
);
|
|
1211
|
+
const exitSpy = spyOn(process, "exit").mockImplementation(((
|
|
1212
|
+
code?: number,
|
|
1213
|
+
) => {
|
|
1214
|
+
throw new Error(`exit:${code}`);
|
|
1215
|
+
}) as never);
|
|
1216
|
+
|
|
1217
|
+
process.argv = ["bun", "vellum", "pair", "--qr"];
|
|
1218
|
+
let exited = false;
|
|
1219
|
+
try {
|
|
1220
|
+
await pair();
|
|
1221
|
+
} catch (e) {
|
|
1222
|
+
exited = (e as Error).message === "exit:1";
|
|
1223
|
+
} finally {
|
|
1224
|
+
errSpy.mockRestore();
|
|
1225
|
+
exitSpy.mockRestore();
|
|
1226
|
+
globalThis.fetch = origFetch;
|
|
1227
|
+
delete process.env.VELLUM_WORKSPACE_DIR;
|
|
1228
|
+
delete process.env.XDG_DATA_HOME;
|
|
1229
|
+
rmSync(workspaceDir, { recursive: true, force: true });
|
|
1230
|
+
rmSync(xdgDir, { recursive: true, force: true });
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
expect(exited).toBe(true);
|
|
1234
|
+
expect(errors.join("\n")).toContain(RUNTIME_URL);
|
|
1235
|
+
expect(minted).toBe(false);
|
|
1236
|
+
});
|
|
803
1237
|
});
|
|
@@ -240,7 +240,11 @@ describe("fetchAssistantDetail / fetchUpgradeInProgress", () => {
|
|
|
240
240
|
current_release_version: "0.7.0",
|
|
241
241
|
release_channel: "preview",
|
|
242
242
|
});
|
|
243
|
-
const detail = await fetchAssistantDetail(
|
|
243
|
+
const detail = await fetchAssistantDetail(
|
|
244
|
+
TOKEN,
|
|
245
|
+
ASSISTANT_ID,
|
|
246
|
+
PLATFORM_URL,
|
|
247
|
+
);
|
|
244
248
|
expect(detail).toEqual({
|
|
245
249
|
currentReleaseVersion: "0.7.0",
|
|
246
250
|
releaseChannel: "preview",
|
|
@@ -251,7 +255,11 @@ describe("fetchAssistantDetail / fetchUpgradeInProgress", () => {
|
|
|
251
255
|
|
|
252
256
|
test("fetchAssistantDetail defaults missing fields", async () => {
|
|
253
257
|
mockFetchJson({});
|
|
254
|
-
const detail = await fetchAssistantDetail(
|
|
258
|
+
const detail = await fetchAssistantDetail(
|
|
259
|
+
TOKEN,
|
|
260
|
+
ASSISTANT_ID,
|
|
261
|
+
PLATFORM_URL,
|
|
262
|
+
);
|
|
255
263
|
expect(detail).toEqual({
|
|
256
264
|
currentReleaseVersion: null,
|
|
257
265
|
releaseChannel: "stable",
|
|
@@ -56,7 +56,9 @@ describe("getBuilderManagedEnvKeys", () => {
|
|
|
56
56
|
|
|
57
57
|
test("gateway hostForwarded equals the three spec host entries", () => {
|
|
58
58
|
const { hostForwarded } = getBuilderManagedEnvKeys("gateway");
|
|
59
|
-
const sorted = [...hostForwarded].sort((a, b) =>
|
|
59
|
+
const sorted = [...hostForwarded].sort((a, b) =>
|
|
60
|
+
a.name.localeCompare(b.name),
|
|
61
|
+
);
|
|
60
62
|
expect(sorted).toEqual([
|
|
61
63
|
{ name: "VELAY_BASE_URL", hostVar: "VELAY_BASE_URL" },
|
|
62
64
|
{ name: "VELLUM_ENVIRONMENT", hostVar: "VELLUM_ENVIRONMENT" },
|