1import {
2 AuthEnvironmentMaintainScope,
3 sessionGrantsScope,
4 type AuthSessionState,
5 type ExecutionEnvironmentCapabilities,
6 type ServerProvider,
7} from "@t3tools/contracts";
8import {
9 cliReleaseChannelOf,
10 cliReleaseIndexPageUrl,
11 newestCliReleaseVersion,
12} from "@t3tools/shared/cliRelease";
13import { compareSemverVersions } from "@t3tools/shared/semver";
14import * as Schema from "effect/Schema";
15
16export function canMaintainEnvironment(session: AuthSessionState | null, connected: boolean) {
17 return (
18 connected &&
19 session?.authenticated === true &&
20 sessionGrantsScope(session, AuthEnvironmentMaintainScope)
21 );
22}
23
24export function supportsEnvironmentUpdate(
25 capabilities: Pick<ExecutionEnvironmentCapabilities, "serverSelfUpdate" | "desktopAppUpdate">,
26) {
27 return (
28 capabilities.serverSelfUpdate !== undefined &&
29 (capabilities.serverSelfUpdate !== "desktop-managed" || capabilities.desktopAppUpdate === true)
30 );
31}
32
33export function canUpdateEnvironmentProvider(provider: ServerProvider) {
34 const compatibility = provider.compatibilityAdvisory?.latestVersionStatus;
35 return (
36 provider.installed &&
37 provider.availability !== "unavailable" &&
38 provider.versionAdvisory?.status === "behind_latest" &&
39 provider.versionAdvisory.canUpdate &&
40 provider.versionAdvisory.latestVersion !== null &&
41 compatibility !== "broken" &&
42 compatibility !== "unsupported" &&
43 provider.updateState?.status !== "running" &&
44 provider.updateState?.status !== "queued"
45 );
46}
47
48const Releases = Schema.Array(
49 Schema.Struct({
50 tag_name: Schema.String,
51 draft: Schema.optionalKey(Schema.Boolean),
52 }),
53);
54const decodeReleases = Schema.decodeUnknownSync(Releases);
55
56/** Preserve the host's release channel and never offer a downgrade. */
57export async function findEnvironmentUpdate(currentVersion: string, signal: AbortSignal) {
58 const channel = cliReleaseChannelOf(currentVersion);
59 for (let page = 1; ; page++) {
60 const response = await fetch(cliReleaseIndexPageUrl(page), { signal });
61 if (!response.ok) throw new Error(`Could not check releases (${response.status}). Try again.`);
62 const releases = decodeReleases(await response.json());
63 const version = newestCliReleaseVersion(releases, channel);
64 if (version !== undefined) {
65 return compareSemverVersions(version, currentVersion) > 0 ? version : null;
66 }
67 if (releases.length < 100) throw new Error(`No ${channel} release was found.`);
68 }
69}