@vellumai/cli 0.10.4 → 0.10.5-dev.202607022329.d89bf2f
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/@vellumai/local-mode/src/__tests__/retire.test.ts +79 -0
- package/node_modules/@vellumai/local-mode/src/index.ts +12 -3
- package/node_modules/@vellumai/local-mode/src/lockfile-contract.test.ts +11 -2
- package/node_modules/@vellumai/local-mode/src/lockfile-contract.ts +6 -0
- package/node_modules/@vellumai/local-mode/src/lockfile.test.ts +29 -1
- package/node_modules/@vellumai/local-mode/src/retire.ts +27 -5
- package/package.json +1 -1
- package/src/__tests__/platform-releases.test.ts +42 -0
- package/src/__tests__/retire.test.ts +353 -0
- package/src/__tests__/teleport.test.ts +8 -0
- package/src/__tests__/workos-pkce.test.ts +0 -7
- package/src/commands/hatch.ts +9 -1
- package/src/commands/login.ts +10 -2
- package/src/commands/retire.ts +197 -24
- package/src/commands/teleport.ts +9 -2
- package/src/lib/docker.ts +22 -4
- package/src/lib/platform-client.ts +14 -0
- package/src/lib/platform-releases.ts +38 -6
- package/src/lib/workos-pkce.ts +1 -2
package/src/commands/retire.ts
CHANGED
|
@@ -20,7 +20,9 @@ import { getConfigDir } from "../lib/environments/paths.js";
|
|
|
20
20
|
import { getCurrentEnvironment } from "../lib/environments/resolve.js";
|
|
21
21
|
import {
|
|
22
22
|
authHeaders,
|
|
23
|
+
authHeadersForKnownOrganization,
|
|
23
24
|
getPlatformUrl,
|
|
25
|
+
readGatewayCredential,
|
|
24
26
|
readPlatformToken,
|
|
25
27
|
} from "../lib/platform-client.js";
|
|
26
28
|
import { retireInstance as retireAwsInstance } from "../lib/aws.js";
|
|
@@ -45,6 +47,180 @@ interface RetireArgs {
|
|
|
45
47
|
yes: boolean;
|
|
46
48
|
}
|
|
47
49
|
|
|
50
|
+
interface SelfHostedPlatformRegistration {
|
|
51
|
+
platformAssistantId: string;
|
|
52
|
+
platformBaseUrl: string | null;
|
|
53
|
+
organizationId: string | null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const PLATFORM_TOKEN_ENV = "VELLUM_PLATFORM_TOKEN";
|
|
57
|
+
|
|
58
|
+
function readPlatformRetireToken(): string | null {
|
|
59
|
+
const envToken = process.env[PLATFORM_TOKEN_ENV]?.trim();
|
|
60
|
+
if (envToken) return envToken;
|
|
61
|
+
return readPlatformToken();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function platformOrigin(platformUrl: string): string | null {
|
|
65
|
+
try {
|
|
66
|
+
return new URL(platformUrl).origin;
|
|
67
|
+
} catch {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function resolveTrustedSelfHostedPlatformUrl(
|
|
73
|
+
storedPlatformUrl: string | null,
|
|
74
|
+
): string | null {
|
|
75
|
+
const configuredPlatformUrl = getPlatformUrl();
|
|
76
|
+
if (!storedPlatformUrl) return configuredPlatformUrl;
|
|
77
|
+
|
|
78
|
+
const storedOrigin = platformOrigin(storedPlatformUrl);
|
|
79
|
+
const configuredOrigin = platformOrigin(configuredPlatformUrl);
|
|
80
|
+
if (storedOrigin && configuredOrigin && storedOrigin === configuredOrigin) {
|
|
81
|
+
return configuredPlatformUrl;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
console.warn(
|
|
85
|
+
"⚠️ Stored platform URL does not match the configured platform URL; skipping platform unregister.",
|
|
86
|
+
);
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function nonEmptyString(value: string | null | undefined): string | null {
|
|
91
|
+
const trimmed = value?.trim();
|
|
92
|
+
return trimmed ? trimmed : null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function lockfileSelfHostedPlatformRegistration(
|
|
96
|
+
entry: AssistantEntry,
|
|
97
|
+
): SelfHostedPlatformRegistration | null {
|
|
98
|
+
const platformAssistantId = nonEmptyString(entry.platformAssistantId);
|
|
99
|
+
if (!platformAssistantId) return null;
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
platformAssistantId,
|
|
103
|
+
platformBaseUrl: nonEmptyString(entry.platformBaseUrl),
|
|
104
|
+
organizationId: nonEmptyString(entry.platformOrganizationId),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async function deletePlatformAssistant(
|
|
109
|
+
assistantId: string,
|
|
110
|
+
options: {
|
|
111
|
+
token: string;
|
|
112
|
+
platformUrl?: string;
|
|
113
|
+
organizationId?: string;
|
|
114
|
+
label: string;
|
|
115
|
+
successMessage: string;
|
|
116
|
+
alreadyGoneMessage: string;
|
|
117
|
+
},
|
|
118
|
+
): Promise<void> {
|
|
119
|
+
const platformUrl = options.platformUrl || getPlatformUrl();
|
|
120
|
+
const url = `${platformUrl}/v1/assistants/${encodeURIComponent(assistantId)}/retire/`;
|
|
121
|
+
const headers = options.organizationId
|
|
122
|
+
? authHeadersForKnownOrganization(options.token, options.organizationId)
|
|
123
|
+
: await authHeaders(options.token, platformUrl);
|
|
124
|
+
|
|
125
|
+
const response = await loopbackSafeFetch(url, {
|
|
126
|
+
method: "DELETE",
|
|
127
|
+
headers,
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
if (!response.ok && response.status !== 404) {
|
|
131
|
+
const body = await response.text();
|
|
132
|
+
throw new Error(`${options.label} failed (${response.status}): ${body}`);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (response.status === 404) {
|
|
136
|
+
console.log(options.alreadyGoneMessage);
|
|
137
|
+
} else {
|
|
138
|
+
console.log(options.successMessage);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async function readSelfHostedPlatformRegistration(
|
|
143
|
+
entry: AssistantEntry,
|
|
144
|
+
): Promise<SelfHostedPlatformRegistration | null> {
|
|
145
|
+
const fallback = lockfileSelfHostedPlatformRegistration(entry);
|
|
146
|
+
const gatewayUrl = entry.localUrl ?? entry.runtimeUrl;
|
|
147
|
+
const platformAssistant = await readGatewayCredential(
|
|
148
|
+
gatewayUrl,
|
|
149
|
+
"vellum:platform_assistant_id",
|
|
150
|
+
entry.bearerToken,
|
|
151
|
+
);
|
|
152
|
+
if (platformAssistant.unreachable) {
|
|
153
|
+
if (fallback) {
|
|
154
|
+
console.warn(
|
|
155
|
+
"\u26a0\ufe0f Could not read platform registration from the local assistant; using saved platform registration.",
|
|
156
|
+
);
|
|
157
|
+
return fallback;
|
|
158
|
+
}
|
|
159
|
+
console.warn(
|
|
160
|
+
"\u26a0\ufe0f Could not read platform registration from the local assistant and no saved platform registration is available; skipping platform unregister.",
|
|
161
|
+
);
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const platformAssistantId =
|
|
166
|
+
nonEmptyString(platformAssistant.value) ?? fallback?.platformAssistantId;
|
|
167
|
+
if (!platformAssistantId) return null;
|
|
168
|
+
|
|
169
|
+
const [platformBaseUrl, organizationId] = await Promise.all([
|
|
170
|
+
readGatewayCredential(
|
|
171
|
+
gatewayUrl,
|
|
172
|
+
"vellum:platform_base_url",
|
|
173
|
+
entry.bearerToken,
|
|
174
|
+
),
|
|
175
|
+
readGatewayCredential(
|
|
176
|
+
gatewayUrl,
|
|
177
|
+
"vellum:platform_organization_id",
|
|
178
|
+
entry.bearerToken,
|
|
179
|
+
),
|
|
180
|
+
]);
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
platformAssistantId,
|
|
184
|
+
platformBaseUrl:
|
|
185
|
+
nonEmptyString(platformBaseUrl.value) ??
|
|
186
|
+
fallback?.platformBaseUrl ??
|
|
187
|
+
null,
|
|
188
|
+
organizationId:
|
|
189
|
+
nonEmptyString(organizationId.value) ?? fallback?.organizationId ?? null,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async function unregisterSelfHostedLocalPlatformRecord(
|
|
194
|
+
entry: AssistantEntry,
|
|
195
|
+
): Promise<void> {
|
|
196
|
+
const token = readPlatformRetireToken();
|
|
197
|
+
if (!token) return;
|
|
198
|
+
|
|
199
|
+
const registration = await readSelfHostedPlatformRegistration(entry);
|
|
200
|
+
if (!registration) return;
|
|
201
|
+
|
|
202
|
+
const platformUrl = resolveTrustedSelfHostedPlatformUrl(
|
|
203
|
+
registration.platformBaseUrl,
|
|
204
|
+
);
|
|
205
|
+
if (!platformUrl) return;
|
|
206
|
+
if (!registration.organizationId) {
|
|
207
|
+
console.warn(
|
|
208
|
+
"⚠️ Could not read platform organization ID from the local assistant; skipping platform unregister.",
|
|
209
|
+
);
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
await deletePlatformAssistant(registration.platformAssistantId, {
|
|
214
|
+
token,
|
|
215
|
+
platformUrl,
|
|
216
|
+
organizationId: registration.organizationId,
|
|
217
|
+
label: "Platform self-hosted unregister",
|
|
218
|
+
successMessage: "\u2705 Platform self-hosted registration unregistered.",
|
|
219
|
+
alreadyGoneMessage:
|
|
220
|
+
"\u2705 Platform self-hosted registration already unregistered (404).",
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
48
224
|
async function retireCustom(entry: AssistantEntry): Promise<void> {
|
|
49
225
|
const host = extractHostFromUrl(entry.runtimeUrl);
|
|
50
226
|
const sshUser = entry.sshUser ?? "root";
|
|
@@ -86,7 +262,7 @@ async function retireVellum(
|
|
|
86
262
|
): Promise<void> {
|
|
87
263
|
console.log("\u{1F5D1}\ufe0f Retiring platform-hosted instance...\n");
|
|
88
264
|
|
|
89
|
-
const token =
|
|
265
|
+
const token = readPlatformRetireToken();
|
|
90
266
|
if (!token) {
|
|
91
267
|
console.error(
|
|
92
268
|
"Error: Not logged in. Run `vellum login --token <token>` first.",
|
|
@@ -94,33 +270,21 @@ async function retireVellum(
|
|
|
94
270
|
process.exit(1);
|
|
95
271
|
}
|
|
96
272
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
// cleanup avoids leaving a stale entry that would otherwise wedge the
|
|
108
|
-
// macOS app in a permanent health-check loop.
|
|
109
|
-
if (!response.ok && response.status !== 404) {
|
|
110
|
-
const body = await response.text();
|
|
273
|
+
try {
|
|
274
|
+
await deletePlatformAssistant(assistantId, {
|
|
275
|
+
token,
|
|
276
|
+
platformUrl: runtimeUrl,
|
|
277
|
+
label: "Platform retire",
|
|
278
|
+
successMessage: "\u2705 Platform-hosted instance retired.",
|
|
279
|
+
alreadyGoneMessage:
|
|
280
|
+
"\u2705 Platform-hosted instance already retired (404) \u2014 cleaning up local state.",
|
|
281
|
+
});
|
|
282
|
+
} catch (error) {
|
|
111
283
|
console.error(
|
|
112
|
-
`Error:
|
|
284
|
+
`Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
113
285
|
);
|
|
114
286
|
process.exit(1);
|
|
115
287
|
}
|
|
116
|
-
|
|
117
|
-
if (response.status === 404) {
|
|
118
|
-
console.log(
|
|
119
|
-
"\u2705 Platform-hosted instance already retired (404) — cleaning up local state.",
|
|
120
|
-
);
|
|
121
|
-
} else {
|
|
122
|
-
console.log("\u2705 Platform-hosted instance retired.");
|
|
123
|
-
}
|
|
124
288
|
}
|
|
125
289
|
|
|
126
290
|
function parseRetireArgs(args: string[]): RetireArgs {
|
|
@@ -303,6 +467,15 @@ async function retireInner(): Promise<void> {
|
|
|
303
467
|
} else if (cloud === "docker") {
|
|
304
468
|
await retireDocker(assistantId);
|
|
305
469
|
} else if (cloud === "local") {
|
|
470
|
+
try {
|
|
471
|
+
await unregisterSelfHostedLocalPlatformRecord(entry);
|
|
472
|
+
} catch (error) {
|
|
473
|
+
console.warn(
|
|
474
|
+
`⚠️ Platform self-hosted unregister failed; continuing local retire: ${
|
|
475
|
+
error instanceof Error ? error.message : String(error)
|
|
476
|
+
}`,
|
|
477
|
+
);
|
|
478
|
+
}
|
|
306
479
|
await retireLocal(assistantId, entry);
|
|
307
480
|
} else if (cloud === "custom") {
|
|
308
481
|
await retireCustom(entry);
|
package/src/commands/teleport.ts
CHANGED
|
@@ -1123,6 +1123,7 @@ async function tryInjectPlatformCredentials(
|
|
|
1123
1123
|
fetchCurrentVersion(entry.runtimeUrl),
|
|
1124
1124
|
fetchAssistantIngressUrl(entry.runtimeUrl, entry.bearerToken),
|
|
1125
1125
|
]);
|
|
1126
|
+
const platformUrl = getPlatformUrl();
|
|
1126
1127
|
const registration = await ensureSelfHostedLocalRegistration(
|
|
1127
1128
|
token,
|
|
1128
1129
|
orgId,
|
|
@@ -1130,9 +1131,15 @@ async function tryInjectPlatformCredentials(
|
|
|
1130
1131
|
entry.assistantId,
|
|
1131
1132
|
"cli",
|
|
1132
1133
|
assistantVersion,
|
|
1133
|
-
|
|
1134
|
+
platformUrl,
|
|
1134
1135
|
ingressUrl,
|
|
1135
1136
|
);
|
|
1137
|
+
saveAssistantEntry({
|
|
1138
|
+
...entry,
|
|
1139
|
+
platformAssistantId: registration.assistant.id,
|
|
1140
|
+
platformBaseUrl: platformUrl,
|
|
1141
|
+
platformOrganizationId: orgId,
|
|
1142
|
+
});
|
|
1136
1143
|
|
|
1137
1144
|
// Resolve the API key: 1) fresh from registration, 2) existing from
|
|
1138
1145
|
// daemon credential store, 3) reprovision as last resort (revokes old key).
|
|
@@ -1164,7 +1171,7 @@ async function tryInjectPlatformCredentials(
|
|
|
1164
1171
|
bearerToken: entry.bearerToken,
|
|
1165
1172
|
assistantApiKey,
|
|
1166
1173
|
platformAssistantId: registration.assistant.id,
|
|
1167
|
-
platformBaseUrl:
|
|
1174
|
+
platformBaseUrl: platformUrl,
|
|
1168
1175
|
organizationId: orgId,
|
|
1169
1176
|
userId: user.id,
|
|
1170
1177
|
webhookSecret: registration.webhook_secret,
|
package/src/lib/docker.ts
CHANGED
|
@@ -35,8 +35,9 @@ import {
|
|
|
35
35
|
loadImageViaHost,
|
|
36
36
|
} from "./host-image-loader.js";
|
|
37
37
|
import {
|
|
38
|
-
|
|
38
|
+
fetchLatestVersion,
|
|
39
39
|
resolveImageRefs,
|
|
40
|
+
type ReleaseChannel,
|
|
40
41
|
} from "./platform-releases.js";
|
|
41
42
|
import {
|
|
42
43
|
configureHatchProviderApiKey,
|
|
@@ -321,6 +322,15 @@ export interface HatchDockerParams {
|
|
|
321
322
|
* connection.
|
|
322
323
|
*/
|
|
323
324
|
assistantCaCertPath?: string;
|
|
325
|
+
/**
|
|
326
|
+
* Release channel to resolve published images from when hatching without a
|
|
327
|
+
* local source tree (the image-pull fallback). `stable` (default) keeps the
|
|
328
|
+
* latest-stable behavior; `preview` pulls the latest preview release. Only
|
|
329
|
+
* affects the pull path — a local source build ignores it. Falls back to
|
|
330
|
+
* the `VELLUM_HATCH_CHANNEL` env var when unset, so callers that hatch via
|
|
331
|
+
* env (e.g. evals) can opt in without changing the invocation.
|
|
332
|
+
*/
|
|
333
|
+
channel?: ReleaseChannel;
|
|
324
334
|
}
|
|
325
335
|
|
|
326
336
|
export type DockerProviderCredentialSetupAction =
|
|
@@ -1093,6 +1103,14 @@ export async function hatchDocker(params: HatchDockerParams): Promise<void> {
|
|
|
1093
1103
|
flagEnvVars = {},
|
|
1094
1104
|
} = params;
|
|
1095
1105
|
let watch = params.watch ?? false;
|
|
1106
|
+
// Resolve the release channel for the image-pull fallback: explicit param
|
|
1107
|
+
// wins, then the VELLUM_HATCH_CHANNEL env var, else stable. Any value other
|
|
1108
|
+
// than "preview" (case-insensitive) is treated as stable.
|
|
1109
|
+
const channel: ReleaseChannel =
|
|
1110
|
+
params.channel ??
|
|
1111
|
+
(process.env.VELLUM_HATCH_CHANNEL?.trim().toLowerCase() === "preview"
|
|
1112
|
+
? "preview"
|
|
1113
|
+
: "stable");
|
|
1096
1114
|
|
|
1097
1115
|
resetLogFile("hatch.log");
|
|
1098
1116
|
const provider =
|
|
@@ -1250,8 +1268,8 @@ export async function hatchDocker(params: HatchDockerParams): Promise<void> {
|
|
|
1250
1268
|
// Resolve image refs from a remote source that may have dev/local
|
|
1251
1269
|
// builds. If resolution is unavailable, fall back to the CLI's own
|
|
1252
1270
|
// version so a default tag can still be resolved.
|
|
1253
|
-
log(
|
|
1254
|
-
const latestVersion = await
|
|
1271
|
+
log(`🔍 Fetching latest ${channel} release...`);
|
|
1272
|
+
const latestVersion = await fetchLatestVersion(channel);
|
|
1255
1273
|
let versionTag: string;
|
|
1256
1274
|
if (latestVersion) {
|
|
1257
1275
|
versionTag = latestVersion.startsWith("v")
|
|
@@ -1265,7 +1283,7 @@ export async function hatchDocker(params: HatchDockerParams): Promise<void> {
|
|
|
1265
1283
|
);
|
|
1266
1284
|
}
|
|
1267
1285
|
log("🔍 Resolving image references...");
|
|
1268
|
-
const resolved = await resolveImageRefs(versionTag, log);
|
|
1286
|
+
const resolved = await resolveImageRefs(versionTag, log, channel);
|
|
1269
1287
|
imageTags.assistant = resolved.imageTags.assistant;
|
|
1270
1288
|
imageTags.gateway = resolved.imageTags.gateway;
|
|
1271
1289
|
imageTags["credential-executor"] =
|
|
@@ -96,6 +96,20 @@ function tokenAuthHeader(token: string): Record<string, string> {
|
|
|
96
96
|
return { "X-Session-Token": token };
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
export function authHeadersForKnownOrganization(
|
|
100
|
+
token: string,
|
|
101
|
+
organizationId: string,
|
|
102
|
+
): Record<string, string> {
|
|
103
|
+
const headers: Record<string, string> = {
|
|
104
|
+
"Content-Type": "application/json",
|
|
105
|
+
...tokenAuthHeader(token),
|
|
106
|
+
};
|
|
107
|
+
if (!token.startsWith(VAK_PREFIX)) {
|
|
108
|
+
headers["Vellum-Organization-Id"] = organizationId;
|
|
109
|
+
}
|
|
110
|
+
return headers;
|
|
111
|
+
}
|
|
112
|
+
|
|
99
113
|
/** Module-level cache for org IDs to avoid redundant fetches in polling loops. */
|
|
100
114
|
const orgIdCache = new Map<string, { orgId: string; expiresAt: number }>();
|
|
101
115
|
const ORG_ID_CACHE_TTL_MS = 60_000; // 60 seconds
|
|
@@ -9,6 +9,25 @@ export interface ResolvedImageRefs {
|
|
|
9
9
|
source: "platform" | "dockerhub";
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Release channel to resolve images from. `stable` is the default and maps
|
|
14
|
+
* to the server's `stable=true` filter (unchanged behavior). `preview` opts
|
|
15
|
+
* into the platform's preview channel — the newest release regardless of
|
|
16
|
+
* stability — used by callers (e.g. evals) that want to always hatch the
|
|
17
|
+
* latest preview image instead of latest-stable.
|
|
18
|
+
*/
|
|
19
|
+
export type ReleaseChannel = "stable" | "preview";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Map a channel to `fetchReleases` options. `stable` passes no `channel`
|
|
23
|
+
* param so the server keeps its `stable=true` default (we don't rely on an
|
|
24
|
+
* unverified `channel=stable` filter); `preview` requests the preview
|
|
25
|
+
* channel explicitly.
|
|
26
|
+
*/
|
|
27
|
+
function releasesOptsFor(channel: ReleaseChannel): { channel?: "preview" } {
|
|
28
|
+
return channel === "preview" ? { channel: "preview" } : {};
|
|
29
|
+
}
|
|
30
|
+
|
|
12
31
|
export interface ReleaseListItem {
|
|
13
32
|
version: string;
|
|
14
33
|
is_stable?: boolean;
|
|
@@ -51,15 +70,26 @@ export async function fetchReleases(opts?: {
|
|
|
51
70
|
}
|
|
52
71
|
|
|
53
72
|
/**
|
|
54
|
-
* Fetch the latest
|
|
73
|
+
* Fetch the latest release version for a channel from the platform API.
|
|
55
74
|
* Returns the version string (e.g. "0.7.0") or null if unavailable.
|
|
56
|
-
* The releases endpoint returns entries ordered newest-first
|
|
75
|
+
* The releases endpoint returns entries ordered newest-first, so the first
|
|
76
|
+
* entry is the latest for the requested channel.
|
|
57
77
|
*/
|
|
58
|
-
export async function
|
|
59
|
-
|
|
78
|
+
export async function fetchLatestVersion(
|
|
79
|
+
channel: ReleaseChannel = "stable",
|
|
80
|
+
): Promise<string | null> {
|
|
81
|
+
const releases = await fetchReleases(releasesOptsFor(channel));
|
|
60
82
|
return releases?.[0]?.version ?? null;
|
|
61
83
|
}
|
|
62
84
|
|
|
85
|
+
/**
|
|
86
|
+
* Fetch the latest stable release version. Thin back-compat wrapper around
|
|
87
|
+
* {@link fetchLatestVersion} for existing callers.
|
|
88
|
+
*/
|
|
89
|
+
export async function fetchLatestStableVersion(): Promise<string | null> {
|
|
90
|
+
return fetchLatestVersion("stable");
|
|
91
|
+
}
|
|
92
|
+
|
|
63
93
|
export type ImageRefResolution =
|
|
64
94
|
| { status: "platform"; imageTags: Record<ServiceName, string> }
|
|
65
95
|
| { status: "dockerhub-fallback"; imageTags: Record<ServiceName, string> }
|
|
@@ -87,10 +117,11 @@ function dockerhubImageTags(version: string): Record<ServiceName, string> {
|
|
|
87
117
|
export async function resolveImageRefsDetailed(
|
|
88
118
|
version: string,
|
|
89
119
|
log?: (msg: string) => void,
|
|
120
|
+
channel: ReleaseChannel = "stable",
|
|
90
121
|
): Promise<ImageRefResolution> {
|
|
91
122
|
log?.("Resolving image references...");
|
|
92
123
|
|
|
93
|
-
const releases = await fetchReleases();
|
|
124
|
+
const releases = await fetchReleases(releasesOptsFor(channel));
|
|
94
125
|
if (releases === null) {
|
|
95
126
|
log?.("Platform unreachable — falling back to DockerHub tags");
|
|
96
127
|
return {
|
|
@@ -151,8 +182,9 @@ export async function resolveImageRefsDetailed(
|
|
|
151
182
|
export async function resolveImageRefs(
|
|
152
183
|
version: string,
|
|
153
184
|
log?: (msg: string) => void,
|
|
185
|
+
channel: ReleaseChannel = "stable",
|
|
154
186
|
): Promise<ResolvedImageRefs> {
|
|
155
|
-
const resolution = await resolveImageRefsDetailed(version, log);
|
|
187
|
+
const resolution = await resolveImageRefsDetailed(version, log, channel);
|
|
156
188
|
if (resolution.status === "platform") {
|
|
157
189
|
log?.("Resolved image refs from platform API");
|
|
158
190
|
return { imageTags: resolution.imageTags, source: "platform" };
|
package/src/lib/workos-pkce.ts
CHANGED
|
@@ -33,7 +33,6 @@ export interface AuthorizeUrlOptions {
|
|
|
33
33
|
challenge: string;
|
|
34
34
|
state: string;
|
|
35
35
|
loginHint?: string;
|
|
36
|
-
providerHint?: string;
|
|
37
36
|
}
|
|
38
37
|
|
|
39
38
|
export function buildAuthorizeUrl(options: AuthorizeUrlOptions): string {
|
|
@@ -46,7 +45,7 @@ export function buildAuthorizeUrl(options: AuthorizeUrlOptions): string {
|
|
|
46
45
|
url.searchParams.set("code_challenge_method", "S256");
|
|
47
46
|
url.searchParams.set("state", options.state);
|
|
48
47
|
// No `prompt`: lets the browser's existing IdP session be reused.
|
|
49
|
-
url.searchParams.set("provider",
|
|
48
|
+
url.searchParams.set("provider", "authkit");
|
|
50
49
|
if (options.loginHint) url.searchParams.set("login_hint", options.loginHint);
|
|
51
50
|
return url.toString();
|
|
52
51
|
}
|