@mycadet/cli 0.12.7 → 0.12.8
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/README.md +2 -2
- package/knip.json +1 -1
- package/node_modules/@mycadet/assistant-api/events/cloud-disconnected.ts +21 -0
- package/node_modules/@mycadet/assistant-api/events/host-bash.ts +1 -1
- package/node_modules/@mycadet/assistant-api/events/host-cu.ts +1 -1
- package/node_modules/@mycadet/assistant-api/events/host-file.ts +1 -1
- package/node_modules/@mycadet/assistant-api/events/notification-intent.ts +1 -1
- package/node_modules/@mycadet/assistant-api/events/{show-platform-login.ts → show-cloud-login.ts} +3 -5
- package/node_modules/@mycadet/assistant-api/index.ts +11 -11
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/package.json +1 -1
- package/node_modules/@mycadet/{service-contracts/src/platform-credential.ts → assistant-api/node_modules/@mycadet/service-contracts/src/cloud-credential.ts} +9 -9
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/handles.ts +3 -3
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/index.ts +1 -1
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/ingress.ts +11 -5
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/remote-web-pairing.ts +1 -1
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/rpc.ts +14 -14
- package/node_modules/@mycadet/assistant-api/node_modules/@mycadet/service-contracts/src/trust-rules.ts +5 -5
- package/node_modules/@mycadet/assistant-api/responses/home.ts +1 -1
- package/node_modules/@mycadet/assistant-api/surfaces.ts +4 -4
- package/node_modules/@mycadet/avatar-manifest/src/__tests__/notification-avatar.test.ts +1 -1
- package/node_modules/@mycadet/avatar-manifest/src/notification-avatar.ts +2 -2
- package/node_modules/@mycadet/environments/src/seeds.ts +6 -6
- package/node_modules/@mycadet/environments/src/shell.test.ts +1 -1
- package/node_modules/@mycadet/environments/src/types.ts +6 -6
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/avatar-manifest/src/__tests__/notification-avatar.test.ts +1 -1
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/avatar-manifest/src/notification-avatar.ts +2 -2
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/environments/src/seeds.ts +6 -6
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/environments/src/shell.test.ts +1 -1
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/environments/src/types.ts +6 -6
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/package.json +1 -1
- package/node_modules/@mycadet/{assistant-api/node_modules/@mycadet/service-contracts/src/platform-credential.ts → local-mode/node_modules/@mycadet/service-contracts/src/cloud-credential.ts} +9 -9
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/handles.ts +3 -3
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/index.ts +1 -1
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/ingress.ts +11 -5
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/remote-web-pairing.ts +1 -1
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/rpc.ts +14 -14
- package/node_modules/@mycadet/local-mode/node_modules/@mycadet/service-contracts/src/trust-rules.ts +5 -5
- package/node_modules/@mycadet/local-mode/src/__tests__/pair.test.ts +5 -5
- package/node_modules/@mycadet/local-mode/src/__tests__/retire.test.ts +1 -1
- package/node_modules/@mycadet/local-mode/src/config.ts +2 -2
- package/node_modules/@mycadet/local-mode/src/index.ts +1 -1
- package/node_modules/@mycadet/local-mode/src/lockfile-contract.test.ts +3 -3
- package/node_modules/@mycadet/local-mode/src/lockfile-contract.ts +10 -5
- package/node_modules/@mycadet/local-mode/src/lockfile.test.ts +15 -15
- package/node_modules/@mycadet/local-mode/src/lockfile.ts +9 -9
- package/node_modules/@mycadet/local-mode/src/pair.ts +2 -2
- package/node_modules/@mycadet/local-mode/src/retire.ts +3 -3
- package/node_modules/@mycadet/local-mode/src/teleport-backup-policy.ts +2 -2
- package/node_modules/@mycadet/local-mode/src/unpair.ts +2 -2
- package/node_modules/@mycadet/service-contracts/package.json +1 -1
- package/node_modules/@mycadet/{local-mode/node_modules/@mycadet/service-contracts/src/platform-credential.ts → service-contracts/src/cloud-credential.ts} +9 -9
- package/node_modules/@mycadet/service-contracts/src/handles.ts +3 -3
- package/node_modules/@mycadet/service-contracts/src/index.ts +1 -1
- package/node_modules/@mycadet/service-contracts/src/ingress.ts +11 -5
- package/node_modules/@mycadet/service-contracts/src/remote-web-pairing.ts +1 -1
- package/node_modules/@mycadet/service-contracts/src/rpc.ts +14 -14
- package/node_modules/@mycadet/service-contracts/src/trust-rules.ts +5 -5
- package/package.json +3 -2
- package/src/__tests__/assistant-api-key-resolution.test.ts +6 -6
- package/src/__tests__/{assistant-client-platform-auth.test.ts → assistant-client-cloud-auth.test.ts} +10 -10
- package/src/__tests__/assistant-client-refresh.test.ts +2 -2
- package/src/__tests__/backup.test.ts +86 -91
- package/src/__tests__/client-token.test.ts +1 -1
- package/src/__tests__/client-tui-refresh.test.ts +1 -1
- package/src/__tests__/client-web-config.test.ts +2 -2
- package/src/__tests__/{platform-client.test.ts → cloud-client.test.ts} +59 -59
- package/src/__tests__/{platform-releases.test.ts → cloud-releases.test.ts} +5 -5
- package/src/__tests__/{cloudflare-tunnel.test.ts → cloudflare.test.ts} +1 -1
- package/src/__tests__/connect-import.test.ts +55 -39
- package/src/__tests__/debug.test.ts +38 -38
- package/src/__tests__/guardian-token.test.ts +149 -56
- package/src/__tests__/hatch-provider-secrets.test.ts +2 -2
- package/src/__tests__/helpers/guardian-token.ts +22 -0
- package/src/__tests__/helpers/http.ts +10 -0
- package/src/__tests__/nginx-ingress.test.ts +18 -18
- package/src/__tests__/orphan-detection.test.ts +1 -1
- package/src/__tests__/pair.test.ts +1 -8
- package/src/__tests__/{ps-platform-status.test.ts → ps-cloud-status.test.ts} +79 -26
- package/src/__tests__/{recover.test.ts → restore.test.ts} +29 -28
- package/src/__tests__/retire.test.ts +58 -199
- package/src/__tests__/root-process-lockfile.test.ts +99 -0
- package/src/__tests__/sleep.test.ts +19 -19
- package/src/__tests__/statefulset.test.ts +8 -8
- package/src/__tests__/{tailscale-tunnel.test.ts → tailscale.test.ts} +1 -1
- package/src/__tests__/teleport-backup.test.ts +3 -3
- package/src/__tests__/teleport.test.ts +180 -196
- package/src/__tests__/tunnel.test.ts +255 -227
- package/src/__tests__/uninstall.test.ts +13 -4
- package/src/__tests__/upgrade-local.test.ts +3 -3
- package/src/__tests__/upgrade-replay-env.test.ts +12 -12
- package/src/__tests__/wake.test.ts +258 -41
- package/src/__tests__/workos-pkce.test.ts +2 -2
- package/src/commands/backup.ts +22 -22
- package/src/commands/client.ts +76 -76
- package/src/commands/debug.ts +20 -20
- package/src/commands/exec.ts +5 -5
- package/src/commands/hatch.ts +22 -22
- package/src/commands/login.ts +22 -22
- package/src/commands/logs.ts +1 -1
- package/src/commands/ps.ts +188 -79
- package/src/commands/restart.ts +30 -0
- package/src/commands/restore.ts +138 -20
- package/src/commands/retire.ts +46 -148
- package/src/commands/rollback.ts +18 -24
- package/src/commands/setup.ts +2 -2
- package/src/commands/teleport.ts +93 -93
- package/src/commands/terminal.ts +157 -17
- package/src/commands/tunnel.ts +80 -122
- package/src/commands/uninstall.ts +55 -7
- package/src/commands/unpair.ts +3 -3
- package/src/commands/upgrade.ts +22 -46
- package/src/commands/wake.ts +162 -46
- package/src/index.ts +3 -9
- package/src/lib/__tests__/{platform-client-signed-url.test.ts → cloud-client-signed-url.test.ts} +16 -16
- package/src/lib/__tests__/job-polling.test.ts +1 -1
- package/src/lib/__tests__/local-runtime-client.test.ts +4 -4
- package/src/lib/__tests__/runtime-url.test.ts +1 -1
- package/src/lib/assistant-api-key-resolution.ts +5 -5
- package/src/lib/assistant-client.ts +8 -8
- package/src/lib/assistant-config.ts +58 -12
- package/src/lib/{platform-client.ts → cloud-client.ts} +107 -98
- package/src/lib/cloud-login.ts +24 -24
- package/src/lib/{platform-releases.ts → cloud-releases.ts} +5 -5
- package/src/lib/cloud-values.ts +3 -0
- package/src/lib/config-utils.ts +1 -1
- package/src/lib/detached-process.ts +1 -1
- package/src/lib/docker.ts +111 -10
- package/src/lib/environments/__tests__/paths.test.ts +2 -2
- package/src/lib/environments/__tests__/resolve.test.ts +26 -26
- package/src/lib/environments/resolve.ts +8 -8
- package/src/lib/guardian-token.ts +36 -4
- package/src/lib/hatch-local.ts +19 -23
- package/src/lib/health-check.ts +6 -6
- package/src/lib/ingress-config.ts +8 -35
- package/src/lib/job-polling.ts +1 -1
- package/src/lib/live-voice/connection.ts +12 -12
- package/src/lib/live-voice/tunnel.test.ts +11 -11
- package/src/lib/live-voice/tunnel.ts +15 -15
- package/src/lib/local-runtime-client.ts +20 -20
- package/src/lib/local.ts +9 -30
- package/src/lib/nginx-ingress.ts +41 -14
- package/src/lib/process.ts +1 -1
- package/src/lib/provider-secrets.ts +3 -3
- package/src/lib/retire-apple-container.ts +1 -1
- package/src/lib/runtime-url.ts +8 -8
- package/src/lib/statefulset.ts +2 -2
- package/src/lib/sync-cloud-assistants.ts +18 -18
- package/src/lib/teleport-backup.ts +4 -4
- package/src/lib/terminal-client.ts +17 -17
- package/src/lib/terminal-session.ts +24 -24
- package/src/lib/{tunnel-edge.ts → tunnel.ts} +199 -11
- package/src/lib/tunnels/cadet.ts +29 -0
- package/src/lib/{cloudflare-tunnel.ts → tunnels/cloudflare.ts} +15 -4
- package/src/lib/tunnels/gateway-notify.ts +61 -0
- package/src/lib/{ngrok.ts → tunnels/ngrok.ts} +201 -30
- package/src/lib/{tailscale-tunnel.ts → tunnels/tailscale.ts} +19 -5
- package/src/lib/upgrade-lifecycle.ts +3 -17
- package/src/lib/upgrade-preflight.ts +2 -2
- package/src/lib/workos-pkce.ts +5 -5
- package/node_modules/@mycadet/assistant-api/events/platform-disconnected.ts +0 -21
- package/src/commands/recover.ts +0 -131
- package/src/commands/roadmap.ts +0 -459
- package/src/commands/ssh.ts +0 -144
- package/src/commands/workflows.ts +0 -305
- package/src/lib/managed-cloud.ts +0 -3
|
@@ -19,7 +19,9 @@ process.env.CADET_LOCKFILE_DIR = testDir;
|
|
|
19
19
|
import * as assistantConfig from "../lib/assistant-config.js";
|
|
20
20
|
import * as guardianToken from "../lib/guardian-token.js";
|
|
21
21
|
import * as localRuntimeClient from "../lib/local-runtime-client.js";
|
|
22
|
-
import * as
|
|
22
|
+
import * as cloudClient from "../lib/cloud-client.js";
|
|
23
|
+
import { withPreconnect } from "./helpers/http.js";
|
|
24
|
+
import { makeGuardianTokenData } from "./helpers/guardian-token.js";
|
|
23
25
|
|
|
24
26
|
const resolveTargetMock = spyOn(
|
|
25
27
|
assistantConfig,
|
|
@@ -27,9 +29,9 @@ const resolveTargetMock = spyOn(
|
|
|
27
29
|
).mockImplementation(() => {
|
|
28
30
|
throw new Error("process.exit:1");
|
|
29
31
|
});
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
"
|
|
32
|
+
const readCloudTokenMock = spyOn(
|
|
33
|
+
cloudClient,
|
|
34
|
+
"readCloudToken",
|
|
33
35
|
).mockReturnValue("platform-token");
|
|
34
36
|
const exportMock = spyOn(
|
|
35
37
|
localRuntimeClient,
|
|
@@ -46,23 +48,25 @@ const pollMock = spyOn(
|
|
|
46
48
|
jobId: "job-1",
|
|
47
49
|
type: "export",
|
|
48
50
|
status: "complete",
|
|
49
|
-
}
|
|
50
|
-
ReturnType<typeof localRuntimeClient.localRuntimePollJobStatus>
|
|
51
|
-
>);
|
|
51
|
+
});
|
|
52
52
|
const loadGuardianTokenSpy = spyOn(
|
|
53
53
|
guardianToken,
|
|
54
54
|
"loadGuardianToken",
|
|
55
|
-
).mockReturnValue(
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
55
|
+
).mockReturnValue(
|
|
56
|
+
makeGuardianTokenData({
|
|
57
|
+
accessToken: "local-token",
|
|
58
|
+
accessTokenExpiresAt: new Date(Date.now() + 10 * 60_000).toISOString(),
|
|
59
|
+
}),
|
|
60
|
+
);
|
|
59
61
|
const leaseGuardianTokenSpy = spyOn(
|
|
60
62
|
guardianToken,
|
|
61
63
|
"leaseGuardianToken",
|
|
62
|
-
).mockResolvedValue(
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
).mockResolvedValue(
|
|
65
|
+
makeGuardianTokenData({
|
|
66
|
+
accessToken: "leased-token",
|
|
67
|
+
accessTokenExpiresAt: new Date(Date.now() + 10 * 60_000).toISOString(),
|
|
68
|
+
}),
|
|
69
|
+
);
|
|
66
70
|
|
|
67
71
|
const { debug } = await import("../commands/debug.js");
|
|
68
72
|
|
|
@@ -72,10 +76,10 @@ const LOCAL_ENTRY = {
|
|
|
72
76
|
runtimeUrl: "http://127.0.0.1:7821",
|
|
73
77
|
cloud: "local",
|
|
74
78
|
hatchedAt: new Date().toISOString(),
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
79
|
+
cloudAssistantId: "11111111-2222-3333-4444-555555555555",
|
|
80
|
+
cloudBaseUrl: "https://cloud.mycadet.ai",
|
|
81
|
+
cloudOrganizationId: "org-1",
|
|
82
|
+
} satisfies assistantConfig.AssistantEntry;
|
|
79
83
|
|
|
80
84
|
let originalFetch: typeof globalThis.fetch;
|
|
81
85
|
let originalExit: typeof process.exit;
|
|
@@ -86,12 +90,12 @@ beforeEach(() => {
|
|
|
86
90
|
process.argv = ["bun", "cadet", "debug"];
|
|
87
91
|
originalFetch = globalThis.fetch;
|
|
88
92
|
originalExit = process.exit;
|
|
89
|
-
process.exit = mock((code?: number) => {
|
|
93
|
+
process.exit = mock((code?: number | string | undefined): never => {
|
|
90
94
|
throw new Error(`process.exit:${code}`);
|
|
91
|
-
})
|
|
95
|
+
});
|
|
92
96
|
fetchCalls = [];
|
|
93
97
|
uploadUrlStatus = 201;
|
|
94
|
-
globalThis.fetch =
|
|
98
|
+
globalThis.fetch = withPreconnect(
|
|
95
99
|
async (input: RequestInfo | URL, init?: RequestInit) => {
|
|
96
100
|
fetchCalls.push({ url: String(input), init });
|
|
97
101
|
return new Response(
|
|
@@ -102,14 +106,14 @@ beforeEach(() => {
|
|
|
102
106
|
},
|
|
103
107
|
);
|
|
104
108
|
},
|
|
105
|
-
)
|
|
109
|
+
);
|
|
106
110
|
resolveTargetMock.mockReset();
|
|
107
111
|
resolveTargetMock.mockReturnValue(LOCAL_ENTRY);
|
|
108
112
|
identityMock.mockReset();
|
|
109
113
|
identityMock.mockResolvedValue({ version: "0.12.3" });
|
|
110
114
|
leaseGuardianTokenSpy.mockClear();
|
|
111
|
-
|
|
112
|
-
|
|
115
|
+
readCloudTokenMock.mockReset();
|
|
116
|
+
readCloudTokenMock.mockReturnValue("platform-token");
|
|
113
117
|
exportMock.mockClear();
|
|
114
118
|
pollMock.mockClear();
|
|
115
119
|
});
|
|
@@ -123,7 +127,7 @@ afterAll(() => {
|
|
|
123
127
|
resolveTargetMock.mockRestore();
|
|
124
128
|
identityMock.mockRestore();
|
|
125
129
|
leaseGuardianTokenSpy.mockRestore();
|
|
126
|
-
|
|
130
|
+
readCloudTokenMock.mockRestore();
|
|
127
131
|
exportMock.mockRestore();
|
|
128
132
|
pollMock.mockRestore();
|
|
129
133
|
loadGuardianTokenSpy.mockRestore();
|
|
@@ -131,7 +135,7 @@ afterAll(() => {
|
|
|
131
135
|
});
|
|
132
136
|
|
|
133
137
|
describe("cadet debug", () => {
|
|
134
|
-
test("mints the URL on the
|
|
138
|
+
test("mints the URL on the cloud, exports with the debug profile, and waits", async () => {
|
|
135
139
|
await debug();
|
|
136
140
|
|
|
137
141
|
expect(fetchCalls).toHaveLength(1);
|
|
@@ -140,9 +144,7 @@ describe("cadet debug", () => {
|
|
|
140
144
|
);
|
|
141
145
|
expect(fetchCalls[0].init?.method).toBe("POST");
|
|
142
146
|
expect(
|
|
143
|
-
(fetchCalls[0].init?.headers
|
|
144
|
-
"Cadet-Organization-Id"
|
|
145
|
-
],
|
|
147
|
+
new Headers(fetchCalls[0].init?.headers).get("Cadet-Organization-Id"),
|
|
146
148
|
).toBe("org-1");
|
|
147
149
|
|
|
148
150
|
expect(exportMock).toHaveBeenCalledTimes(1);
|
|
@@ -155,7 +157,7 @@ describe("cadet debug", () => {
|
|
|
155
157
|
profile: "debug",
|
|
156
158
|
});
|
|
157
159
|
expect(pollMock).toHaveBeenCalled();
|
|
158
|
-
// The daemon was checked for the debug profile before the
|
|
160
|
+
// The daemon was checked for the debug profile before the cloud call.
|
|
159
161
|
expect(identityMock).toHaveBeenCalledTimes(1);
|
|
160
162
|
});
|
|
161
163
|
|
|
@@ -196,9 +198,7 @@ describe("cadet debug", () => {
|
|
|
196
198
|
jobId: "job-1",
|
|
197
199
|
type: "export",
|
|
198
200
|
status: "complete",
|
|
199
|
-
}
|
|
200
|
-
ReturnType<typeof localRuntimeClient.localRuntimePollJobStatus>
|
|
201
|
-
>;
|
|
201
|
+
};
|
|
202
202
|
});
|
|
203
203
|
|
|
204
204
|
await debug();
|
|
@@ -207,7 +207,7 @@ describe("cadet debug", () => {
|
|
|
207
207
|
expect(polls).toBe(2);
|
|
208
208
|
});
|
|
209
209
|
|
|
210
|
-
test("explains what to turn on when the
|
|
210
|
+
test("explains what to turn on when the cloud refuses", async () => {
|
|
211
211
|
uploadUrlStatus = 403;
|
|
212
212
|
const errors: string[] = [];
|
|
213
213
|
const errorSpy = spyOn(console, "error").mockImplementation((msg) => {
|
|
@@ -226,7 +226,7 @@ describe("cadet debug", () => {
|
|
|
226
226
|
resolveTargetMock.mockReturnValue({
|
|
227
227
|
...LOCAL_ENTRY,
|
|
228
228
|
cloud: "cadet",
|
|
229
|
-
}
|
|
229
|
+
});
|
|
230
230
|
const errorSpy = spyOn(console, "error").mockImplementation(() => {});
|
|
231
231
|
try {
|
|
232
232
|
await expect(debug()).rejects.toThrow("process.exit:1");
|
|
@@ -239,8 +239,8 @@ describe("cadet debug", () => {
|
|
|
239
239
|
test("asks for a login when the assistant is not registered", async () => {
|
|
240
240
|
resolveTargetMock.mockReturnValue({
|
|
241
241
|
...LOCAL_ENTRY,
|
|
242
|
-
|
|
243
|
-
}
|
|
242
|
+
cloudAssistantId: undefined,
|
|
243
|
+
});
|
|
244
244
|
const errors: string[] = [];
|
|
245
245
|
const errorSpy = spyOn(console, "error").mockImplementation((msg) => {
|
|
246
246
|
errors.push(String(msg));
|
|
@@ -16,6 +16,8 @@ import { guardianTokenPath, resolveConfigDir } from "@mycadet/local-mode";
|
|
|
16
16
|
import {
|
|
17
17
|
getOrCreatePersistedDeviceId,
|
|
18
18
|
guardianTokenDueForRenewal,
|
|
19
|
+
GuardianLeasePermanentError,
|
|
20
|
+
leaseGuardianToken,
|
|
19
21
|
loadGuardianToken,
|
|
20
22
|
refreshGuardianToken,
|
|
21
23
|
refreshGuardianTokenResult,
|
|
@@ -25,6 +27,7 @@ import {
|
|
|
25
27
|
} from "../lib/guardian-token.js";
|
|
26
28
|
import { getConfigDir } from "../lib/environments/paths.js";
|
|
27
29
|
import { getCurrentEnvironment } from "../lib/environments/resolve.js";
|
|
30
|
+
import { withPreconnect } from "./helpers/http.js";
|
|
28
31
|
|
|
29
32
|
function makeTokenData(suffix: string): GuardianTokenData {
|
|
30
33
|
const now = new Date().toISOString();
|
|
@@ -286,19 +289,21 @@ describe("refreshGuardianToken", () => {
|
|
|
286
289
|
test("refreshes, persists the rotated token, sends an abort signal, releases the lock", async () => {
|
|
287
290
|
seed(future());
|
|
288
291
|
let sawSignal = false;
|
|
289
|
-
globalThis.fetch = (
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
292
|
+
globalThis.fetch = withPreconnect(
|
|
293
|
+
async (_url: unknown, init?: RequestInit) => {
|
|
294
|
+
sawSignal = init?.signal instanceof AbortSignal;
|
|
295
|
+
return new Response(
|
|
296
|
+
JSON.stringify({
|
|
297
|
+
accessToken: "new-acc",
|
|
298
|
+
refreshToken: "new-ref",
|
|
299
|
+
accessTokenExpiresAt: future(),
|
|
300
|
+
refreshTokenExpiresAt: future(),
|
|
301
|
+
refreshAfter: "",
|
|
302
|
+
}),
|
|
303
|
+
{ status: 200, headers: { "content-type": "application/json" } },
|
|
304
|
+
);
|
|
305
|
+
},
|
|
306
|
+
);
|
|
302
307
|
|
|
303
308
|
const result = await refreshGuardianToken("https://gw.example.com", "px");
|
|
304
309
|
|
|
@@ -311,13 +316,81 @@ describe("refreshGuardianToken", () => {
|
|
|
311
316
|
expect(existsSync(lockPath("px"))).toBe(false); // lock released
|
|
312
317
|
});
|
|
313
318
|
|
|
319
|
+
test("leases against the gateway's real init shape — refreshAfter arrives as a number", async () => {
|
|
320
|
+
// The gateway's GuardianBootstrapResult serializes refreshAfter as a
|
|
321
|
+
// NUMBER of ms (mintRefreshToken), and the strict lease check rejects a
|
|
322
|
+
// response missing any field. A string-only schema read that number as
|
|
323
|
+
// malformed, so every real hatch failed its lease while the ISO-string
|
|
324
|
+
// test doubles here stayed green.
|
|
325
|
+
globalThis.fetch = withPreconnect(async (input: RequestInfo | URL) => {
|
|
326
|
+
const url = String(input);
|
|
327
|
+
if (url.endsWith("/readyz")) {
|
|
328
|
+
return new Response("", { status: 404 }); // no starting gate — proceed
|
|
329
|
+
}
|
|
330
|
+
return new Response(
|
|
331
|
+
JSON.stringify({
|
|
332
|
+
guardianPrincipalId: "principal-lease",
|
|
333
|
+
accessToken: "access-lease",
|
|
334
|
+
accessTokenExpiresAt: Date.now() + 60 * 60 * 1000,
|
|
335
|
+
refreshToken: "refresh-lease",
|
|
336
|
+
refreshTokenExpiresAt: Date.now() + 365 * 24 * 60 * 60 * 1000,
|
|
337
|
+
refreshAfter: Date.now() + 48 * 60 * 60 * 1000,
|
|
338
|
+
isNew: true,
|
|
339
|
+
mintedOverPriorEvidence: false,
|
|
340
|
+
}),
|
|
341
|
+
{ status: 200, headers: { "content-type": "application/json" } },
|
|
342
|
+
);
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
const leased = await leaseGuardianToken("https://gw.example.com", "px");
|
|
346
|
+
|
|
347
|
+
expect(leased.accessToken).toBe("access-lease");
|
|
348
|
+
expect(leased.refreshAfter).toBeDefined();
|
|
349
|
+
const stored = loadGuardianToken("px");
|
|
350
|
+
expect(stored?.accessToken).toBe("access-lease");
|
|
351
|
+
expect(stored?.refreshAfter).toBe(leased.refreshAfter);
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
test("a malformed 200 names the unusable fields and is not retried", async () => {
|
|
355
|
+
globalThis.fetch = withPreconnect(async (input: RequestInfo | URL) => {
|
|
356
|
+
const url = String(input);
|
|
357
|
+
if (url.endsWith("/readyz")) {
|
|
358
|
+
return new Response("", { status: 404 });
|
|
359
|
+
}
|
|
360
|
+
return new Response(
|
|
361
|
+
JSON.stringify({
|
|
362
|
+
guardianPrincipalId: "principal-lease",
|
|
363
|
+
accessToken: 42,
|
|
364
|
+
refreshToken: "refresh-lease",
|
|
365
|
+
}),
|
|
366
|
+
{ status: 200, headers: { "content-type": "application/json" } },
|
|
367
|
+
);
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
const err = await leaseGuardianToken("https://gw.example.com", "px").then(
|
|
371
|
+
() => null,
|
|
372
|
+
(e: unknown) => e,
|
|
373
|
+
);
|
|
374
|
+
|
|
375
|
+
if (!(err instanceof GuardianLeasePermanentError)) {
|
|
376
|
+
throw new Error(
|
|
377
|
+
`expected GuardianLeasePermanentError, got ${String(err)}`,
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
expect(err.message).toContain("(200)");
|
|
381
|
+
expect(err.message).toContain("accessToken arrived as number");
|
|
382
|
+
expect(err.message).toContain("refreshTokenExpiresAt arrived as absent");
|
|
383
|
+
});
|
|
384
|
+
|
|
314
385
|
test("returns null without calling the gateway when the refresh token is expired", async () => {
|
|
315
386
|
seed(new Date(Date.now() - 1000).toISOString());
|
|
316
387
|
let called = false;
|
|
317
|
-
globalThis.fetch = (
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
388
|
+
globalThis.fetch = withPreconnect(
|
|
389
|
+
async (_url: unknown, _init?: RequestInit) => {
|
|
390
|
+
called = true;
|
|
391
|
+
return new Response("", { status: 200 });
|
|
392
|
+
},
|
|
393
|
+
);
|
|
321
394
|
|
|
322
395
|
expect(
|
|
323
396
|
await refreshGuardianToken("https://gw.example.com", "px"),
|
|
@@ -327,10 +400,12 @@ describe("refreshGuardianToken", () => {
|
|
|
327
400
|
|
|
328
401
|
test("returns null when there is no stored token", async () => {
|
|
329
402
|
let called = false;
|
|
330
|
-
globalThis.fetch = (
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
403
|
+
globalThis.fetch = withPreconnect(
|
|
404
|
+
async (_url: unknown, _init?: RequestInit) => {
|
|
405
|
+
called = true;
|
|
406
|
+
return new Response("", { status: 200 });
|
|
407
|
+
},
|
|
408
|
+
);
|
|
334
409
|
|
|
335
410
|
expect(
|
|
336
411
|
await refreshGuardianToken("https://gw.example.com", "missing"),
|
|
@@ -348,11 +423,13 @@ describe("refreshGuardianToken", () => {
|
|
|
348
423
|
const old = new Date(Date.now() - 60_000);
|
|
349
424
|
utimesSync(lp, old, old);
|
|
350
425
|
|
|
351
|
-
globalThis.fetch = (
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
426
|
+
globalThis.fetch = withPreconnect(
|
|
427
|
+
async (_url: unknown, _init?: RequestInit) =>
|
|
428
|
+
new Response(JSON.stringify({ accessToken: "new-acc" }), {
|
|
429
|
+
status: 200,
|
|
430
|
+
headers: { "content-type": "application/json" },
|
|
431
|
+
}),
|
|
432
|
+
);
|
|
356
433
|
|
|
357
434
|
const result = await refreshGuardianToken("https://gw.example.com", "px");
|
|
358
435
|
expect(result?.accessToken).toBe("new-acc");
|
|
@@ -371,13 +448,15 @@ describe("refreshGuardianToken", () => {
|
|
|
371
448
|
]) {
|
|
372
449
|
seed(future());
|
|
373
450
|
let called = false;
|
|
374
|
-
globalThis.fetch = (
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
451
|
+
globalThis.fetch = withPreconnect(
|
|
452
|
+
async (_url: unknown, _init?: RequestInit) => {
|
|
453
|
+
called = true;
|
|
454
|
+
return new Response(JSON.stringify({ accessToken: "new-acc" }), {
|
|
455
|
+
status: 200,
|
|
456
|
+
headers: { "content-type": "application/json" },
|
|
457
|
+
});
|
|
458
|
+
},
|
|
459
|
+
);
|
|
381
460
|
|
|
382
461
|
expect(await refreshGuardianToken(url, "px")).not.toBeNull();
|
|
383
462
|
expect(called).toBe(true); // loopback http is allowed
|
|
@@ -387,10 +466,12 @@ describe("refreshGuardianToken", () => {
|
|
|
387
466
|
test("refuses a non-loopback plaintext http URL: no fetch, returns null, warns", async () => {
|
|
388
467
|
seed(future());
|
|
389
468
|
let called = false;
|
|
390
|
-
globalThis.fetch = (
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
469
|
+
globalThis.fetch = withPreconnect(
|
|
470
|
+
async (_url: unknown, _init?: RequestInit) => {
|
|
471
|
+
called = true;
|
|
472
|
+
return new Response("", { status: 200 });
|
|
473
|
+
},
|
|
474
|
+
);
|
|
394
475
|
|
|
395
476
|
const origWarn = console.warn;
|
|
396
477
|
let warned = false;
|
|
@@ -411,10 +492,12 @@ describe("refreshGuardianToken", () => {
|
|
|
411
492
|
test("refuses a malformed gateway URL: no fetch, returns null", async () => {
|
|
412
493
|
seed(future());
|
|
413
494
|
let called = false;
|
|
414
|
-
globalThis.fetch = (
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
495
|
+
globalThis.fetch = withPreconnect(
|
|
496
|
+
async (_url: unknown, _init?: RequestInit) => {
|
|
497
|
+
called = true;
|
|
498
|
+
return new Response("", { status: 200 });
|
|
499
|
+
},
|
|
500
|
+
);
|
|
418
501
|
|
|
419
502
|
const origWarn = console.warn;
|
|
420
503
|
console.warn = () => {};
|
|
@@ -428,11 +511,13 @@ describe("refreshGuardianToken", () => {
|
|
|
428
511
|
|
|
429
512
|
test("an unreachable gateway is a 503, not a 401", async () => {
|
|
430
513
|
seed(future());
|
|
431
|
-
globalThis.fetch = (
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
514
|
+
globalThis.fetch = withPreconnect(
|
|
515
|
+
async (_url: unknown, _init?: RequestInit) => {
|
|
516
|
+
throw Object.assign(new TypeError("fetch failed"), {
|
|
517
|
+
cause: { code: "ECONNREFUSED" },
|
|
518
|
+
});
|
|
519
|
+
},
|
|
520
|
+
);
|
|
436
521
|
|
|
437
522
|
const result = await refreshGuardianTokenResult(
|
|
438
523
|
"http://127.0.0.1:59999",
|
|
@@ -451,8 +536,10 @@ describe("refreshGuardianToken", () => {
|
|
|
451
536
|
|
|
452
537
|
test("an HTTP 401 from the gateway is a spent credential", async () => {
|
|
453
538
|
seed(future());
|
|
454
|
-
globalThis.fetch = (
|
|
455
|
-
|
|
539
|
+
globalThis.fetch = withPreconnect(
|
|
540
|
+
async (_url: unknown, _init?: RequestInit) =>
|
|
541
|
+
new Response("", { status: 401 }),
|
|
542
|
+
);
|
|
456
543
|
|
|
457
544
|
expect(
|
|
458
545
|
await refreshGuardianTokenResult("https://gw.example.com", "px"),
|
|
@@ -465,8 +552,10 @@ describe("refreshGuardianToken", () => {
|
|
|
465
552
|
|
|
466
553
|
test("an HTTP 403 from the refresh route is a spent credential, not a loopback refusal", async () => {
|
|
467
554
|
seed(future());
|
|
468
|
-
globalThis.fetch = (
|
|
469
|
-
|
|
555
|
+
globalThis.fetch = withPreconnect(
|
|
556
|
+
async (_url: unknown, _init?: RequestInit) =>
|
|
557
|
+
new Response("", { status: 403 }),
|
|
558
|
+
);
|
|
470
559
|
|
|
471
560
|
expect(
|
|
472
561
|
await refreshGuardianTokenResult("https://gw.example.com", "px"),
|
|
@@ -480,10 +569,12 @@ describe("refreshGuardianToken", () => {
|
|
|
480
569
|
test("an insecure gateway URL is a local 403, not a spent credential", async () => {
|
|
481
570
|
seed(future());
|
|
482
571
|
let called = false;
|
|
483
|
-
globalThis.fetch = (
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
572
|
+
globalThis.fetch = withPreconnect(
|
|
573
|
+
async (_url: unknown, _init?: RequestInit) => {
|
|
574
|
+
called = true;
|
|
575
|
+
return new Response("", { status: 200 });
|
|
576
|
+
},
|
|
577
|
+
);
|
|
487
578
|
|
|
488
579
|
const origWarn = console.warn;
|
|
489
580
|
console.warn = () => {};
|
|
@@ -503,8 +594,10 @@ describe("refreshGuardianToken", () => {
|
|
|
503
594
|
|
|
504
595
|
test("an HTTP 503 from a still-starting gateway is retryable", async () => {
|
|
505
596
|
seed(future());
|
|
506
|
-
globalThis.fetch = (
|
|
507
|
-
|
|
597
|
+
globalThis.fetch = withPreconnect(
|
|
598
|
+
async (_url: unknown, _init?: RequestInit) =>
|
|
599
|
+
new Response("", { status: 503 }),
|
|
600
|
+
);
|
|
508
601
|
|
|
509
602
|
const result = await refreshGuardianTokenResult(
|
|
510
603
|
"https://gw.example.com",
|
|
@@ -292,7 +292,7 @@ describe("hatch provider secrets", () => {
|
|
|
292
292
|
test("HATCH_PROVIDER_CHOICES only lists labeled providers", () => {
|
|
293
293
|
for (const provider of HATCH_PROVIDER_CHOICES) {
|
|
294
294
|
expect(formatProviderName(provider)).toBeTruthy();
|
|
295
|
-
// Cadet Cloud is the
|
|
295
|
+
// Cadet Cloud is the cloud-connection exception: no env var, no
|
|
296
296
|
// key collection — everything else must have a supported setup flow.
|
|
297
297
|
if (provider !== "cadet") {
|
|
298
298
|
expect(isSupportedLlmProvider(provider)).toBe(true);
|
|
@@ -313,7 +313,7 @@ describe("hatch provider secrets", () => {
|
|
|
313
313
|
]);
|
|
314
314
|
});
|
|
315
315
|
|
|
316
|
-
test("Cadet Cloud configures silently through the
|
|
316
|
+
test("Cadet Cloud configures silently through the cloud connection", async () => {
|
|
317
317
|
const { calls, fetchImpl } = makeFetch([]);
|
|
318
318
|
const logs: string[] = [];
|
|
319
319
|
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { GuardianTokenData } from "../../lib/guardian-token.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A complete `GuardianTokenData` fixture with per-test overrides — the type's
|
|
5
|
+
* nine fields are all required, so partial fakes need this to stand in.
|
|
6
|
+
*/
|
|
7
|
+
export function makeGuardianTokenData(
|
|
8
|
+
over: Partial<GuardianTokenData> = {},
|
|
9
|
+
): GuardianTokenData {
|
|
10
|
+
return {
|
|
11
|
+
guardianPrincipalId: "principal-1",
|
|
12
|
+
accessToken: "local-token",
|
|
13
|
+
accessTokenExpiresAt: new Date(Date.now() + 60_000).toISOString(),
|
|
14
|
+
refreshToken: "refresh-token",
|
|
15
|
+
refreshTokenExpiresAt: new Date(Date.now() + 3_600_000).toISOString(),
|
|
16
|
+
refreshAfter: new Date(Date.now() + 30_000).toISOString(),
|
|
17
|
+
isNew: false,
|
|
18
|
+
deviceId: "device-1",
|
|
19
|
+
leasedAt: new Date().toISOString(),
|
|
20
|
+
...over,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* bun-types' global fetch carries a `preconnect` member; these mocks only need
|
|
3
|
+
* the call shape, so hand the assignment a member-complete callable. The SUT
|
|
4
|
+
* never calls `preconnect`.
|
|
5
|
+
*/
|
|
6
|
+
export function withPreconnect(
|
|
7
|
+
impl: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>,
|
|
8
|
+
): typeof globalThis.fetch {
|
|
9
|
+
return Object.assign(impl, { preconnect: () => {} });
|
|
10
|
+
}
|
|
@@ -158,9 +158,9 @@ describe("buildIngressNginxConfig", () => {
|
|
|
158
158
|
remoteWebIngress: {
|
|
159
159
|
webDistDir: "/tmp/cadet web/dist",
|
|
160
160
|
config: {
|
|
161
|
-
|
|
161
|
+
remote: "docker",
|
|
162
162
|
apiBaseUrl: "/v1",
|
|
163
|
-
|
|
163
|
+
cloudDisabled: true,
|
|
164
164
|
},
|
|
165
165
|
},
|
|
166
166
|
});
|
|
@@ -330,7 +330,7 @@ describe("buildIngressNginxConfig", () => {
|
|
|
330
330
|
expect(remoteConf).toContain("default_type application/json;");
|
|
331
331
|
expect(remoteConf).toContain('add_header Cache-Control "no-store";');
|
|
332
332
|
expect(remoteConf).toContain(
|
|
333
|
-
'return 200 "{\\"
|
|
333
|
+
'return 200 "{\\"remote\\":\\"docker\\",\\"apiBaseUrl\\":\\"/v1\\",\\"cloudDisabled\\":true,\\"disableCloud\\":true}";',
|
|
334
334
|
);
|
|
335
335
|
expect(remoteConf).not.toContain("assistantId");
|
|
336
336
|
});
|
|
@@ -347,7 +347,7 @@ describe("buildIngressNginxConfig", () => {
|
|
|
347
347
|
},
|
|
348
348
|
});
|
|
349
349
|
expect(named).toContain(
|
|
350
|
-
'return 200 "{\\"
|
|
350
|
+
'return 200 "{\\"remote\\":\\"custom\\",\\"apiBaseUrl\\":\\"/v1\\",\\"cloudDisabled\\":true,\\"disableCloud\\":true,\\"assistantName\\":\\"Homelab\\",\\"assistantId\\":\\"assistant-1\\",\\"hubUrl\\":\\"https://mycadet.ai/assistant\\"}";',
|
|
351
351
|
);
|
|
352
352
|
});
|
|
353
353
|
|
|
@@ -447,19 +447,19 @@ describe("buildRemoteWebIndexHtml", () => {
|
|
|
447
447
|
const html =
|
|
448
448
|
'<html><head><script>window.__CADET_CONFIG__={"webUrl":"https://mycadet.ai"}</script></head><body></body></html>';
|
|
449
449
|
const result = buildRemoteWebIndexHtml(html, {
|
|
450
|
-
|
|
450
|
+
remote: "docker",
|
|
451
451
|
apiBaseUrl: "/v1",
|
|
452
|
-
|
|
452
|
+
disableCloud: true,
|
|
453
453
|
});
|
|
454
454
|
|
|
455
455
|
expect(result).toContain(
|
|
456
456
|
'window.__CADET_CONFIG__={"webUrl":"https://mycadet.ai"}',
|
|
457
457
|
);
|
|
458
458
|
expect(result).toContain(
|
|
459
|
-
'window.__CADET_CONFIG__={"
|
|
459
|
+
'window.__CADET_CONFIG__={"remote":"docker","apiBaseUrl":"/v1","disableCloud":true}',
|
|
460
460
|
);
|
|
461
461
|
expect(result.indexOf('"webUrl"')).toBeLessThan(
|
|
462
|
-
result.indexOf('"
|
|
462
|
+
result.indexOf('"docker"'),
|
|
463
463
|
);
|
|
464
464
|
});
|
|
465
465
|
|
|
@@ -484,7 +484,7 @@ describe("buildRemoteWebIndexHtml", () => {
|
|
|
484
484
|
"</head><body></body></html>",
|
|
485
485
|
].join("\n");
|
|
486
486
|
|
|
487
|
-
const result = buildRemoteWebIndexHtml(html, {
|
|
487
|
+
const result = buildRemoteWebIndexHtml(html, { remote: "docker" });
|
|
488
488
|
|
|
489
489
|
expect(result.match(/rel="modulepreload"/g)).toHaveLength(4);
|
|
490
490
|
expect(result).toContain('href="/assistant/assets/a-1.js"');
|
|
@@ -506,7 +506,7 @@ describe("buildRemoteWebIndexHtml", () => {
|
|
|
506
506
|
"</head><body></body></html>",
|
|
507
507
|
].join("\n");
|
|
508
508
|
|
|
509
|
-
const result = buildRemoteWebIndexHtml(html, {
|
|
509
|
+
const result = buildRemoteWebIndexHtml(html, { remote: "docker" });
|
|
510
510
|
|
|
511
511
|
expect(result).not.toContain("modulepreload");
|
|
512
512
|
expect(result).not.toContain("/assistant/assets/chunk-0.js");
|
|
@@ -522,7 +522,7 @@ describe("buildRemoteWebIndexHtml", () => {
|
|
|
522
522
|
);
|
|
523
523
|
const html = `<html><head>${preloads.join("\n")}</head><body></body></html>`;
|
|
524
524
|
|
|
525
|
-
const result = buildRemoteWebIndexHtml(html, {
|
|
525
|
+
const result = buildRemoteWebIndexHtml(html, { remote: "docker" });
|
|
526
526
|
|
|
527
527
|
expect(result.match(/rel="modulepreload"/g)).toHaveLength(
|
|
528
528
|
MAX_PRESERVED_MODULE_PRELOADS,
|
|
@@ -862,10 +862,10 @@ function spaConfigHash(
|
|
|
862
862
|
JSON.stringify({
|
|
863
863
|
template: EDGE_TEMPLATE_VERSION,
|
|
864
864
|
config: {
|
|
865
|
-
|
|
865
|
+
remote: "custom",
|
|
866
866
|
apiBaseUrl: "/v1",
|
|
867
|
-
|
|
868
|
-
|
|
867
|
+
cloudDisabled: true,
|
|
868
|
+
disableCloud: true,
|
|
869
869
|
...(opts.assistantName ? { assistantName: opts.assistantName } : {}),
|
|
870
870
|
...(opts.assistantId ? { assistantId: opts.assistantId } : {}),
|
|
871
871
|
hubUrl: PRODUCTION_HUB_URL,
|
|
@@ -988,7 +988,7 @@ describe("startRemoteWebIngress", () => {
|
|
|
988
988
|
join(ws, "data", "ingress", "assistant-index.html"),
|
|
989
989
|
"utf-8",
|
|
990
990
|
);
|
|
991
|
-
expect(indexHtml).toContain("remote
|
|
991
|
+
expect(indexHtml).toContain('"remote":"custom"');
|
|
992
992
|
expect(indexHtml).toContain(`"hubUrl":"${PRODUCTION_HUB_URL}"`);
|
|
993
993
|
// No assistant name was provided, so the served config omits the label.
|
|
994
994
|
expect(indexHtml).not.toContain("assistantName");
|
|
@@ -1352,10 +1352,10 @@ describe("startRemoteWebIngress", () => {
|
|
|
1352
1352
|
JSON.stringify({
|
|
1353
1353
|
template: 1,
|
|
1354
1354
|
config: {
|
|
1355
|
-
|
|
1355
|
+
remote: "custom",
|
|
1356
1356
|
apiBaseUrl: "/v1",
|
|
1357
|
-
|
|
1358
|
-
|
|
1357
|
+
cloudDisabled: true,
|
|
1358
|
+
disableCloud: true,
|
|
1359
1359
|
hubUrl: PRODUCTION_HUB_URL,
|
|
1360
1360
|
},
|
|
1361
1361
|
}),
|
|
@@ -141,7 +141,7 @@ describe("loadAllAssistantsAcrossEnvs", () => {
|
|
|
141
141
|
function makeEnv(name: string, lockfileDir: string): EnvironmentDefinition {
|
|
142
142
|
return {
|
|
143
143
|
name,
|
|
144
|
-
|
|
144
|
+
cloudUrl: "https://example.invalid",
|
|
145
145
|
webUrl: "https://example.invalid",
|
|
146
146
|
lockfileDirOverride: lockfileDir,
|
|
147
147
|
};
|