mirror of
https://github.com/profullstack/logicsrc.git
synced 2026-08-14 06:47:28 +00:00
Merge pull request #103 from profullstack/fix/cli-real-update
fix(cli): make `logicsrc update` actually check for updates
This commit is contained in:
commit
2bed70971d
4 changed files with 339 additions and 8 deletions
|
|
@ -56,15 +56,40 @@ check_node() {
|
||||||
need npm
|
need npm
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Commit the tracked ref currently points at. The .sha media type returns it as
|
||||||
|
# bare text, so this needs no jq. Empty on failure — never fatal, since a missing
|
||||||
|
# sha only costs `logicsrc update` its precision.
|
||||||
|
resolve_sha() {
|
||||||
|
curl -fsSL -H "Accept: application/vnd.github.sha" \
|
||||||
|
"https://api.github.com/repos/$GH_REPO/commits/$LOGICSRC_REF" 2>/dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
|
# Records what we installed so `logicsrc update` can compare against the remote.
|
||||||
|
# Without this the CLI has no way to know which commit it is running, and can
|
||||||
|
# only ever guess that it is current.
|
||||||
|
write_manifest() {
|
||||||
|
_version="$(node -p "require('$SRC_DIR/packages/cli/package.json').version" 2>/dev/null || echo '')"
|
||||||
|
cat > "$LOGICSRC_HOME/install.json" <<EOF
|
||||||
|
{
|
||||||
|
"ref": "$LOGICSRC_REF",
|
||||||
|
"commit": "$1",
|
||||||
|
"version": "$_version",
|
||||||
|
"installed_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
do_install() {
|
do_install() {
|
||||||
detect_os; check_node
|
detect_os; check_node
|
||||||
need curl; need tar
|
need curl; need tar
|
||||||
info "fetching logicsrc@$LOGICSRC_REF from GitHub…"
|
info "fetching logicsrc@$LOGICSRC_REF from GitHub…"
|
||||||
mkdir -p "$SRC_DIR"
|
mkdir -p "$SRC_DIR"
|
||||||
|
sha="$(resolve_sha)"
|
||||||
|
short_sha="$(printf '%.7s' "$sha")"
|
||||||
tmp="$(mktemp -d)"
|
tmp="$(mktemp -d)"
|
||||||
curl -fsSL "$TARBALL_URL" | tar -xz -C "$tmp" --strip-components=1
|
curl -fsSL "$TARBALL_URL" | tar -xz -C "$tmp" --strip-components=1
|
||||||
rm -rf "$SRC_DIR"; mkdir -p "$(dirname "$SRC_DIR")"; mv "$tmp" "$SRC_DIR"
|
rm -rf "$SRC_DIR"; mkdir -p "$(dirname "$SRC_DIR")"; mv "$tmp" "$SRC_DIR"
|
||||||
ok "downloaded to $SRC_DIR"
|
ok "downloaded to $SRC_DIR${short_sha:+ ($short_sha)}"
|
||||||
|
|
||||||
info "installing dependencies (this can take a minute)…"
|
info "installing dependencies (this can take a minute)…"
|
||||||
( cd "$SRC_DIR" && npm install --no-audit --no-fund --ignore-scripts >/dev/null 2>&1 ) || fail "npm install failed — run it by hand in $SRC_DIR"
|
( cd "$SRC_DIR" && npm install --no-audit --no-fund --ignore-scripts >/dev/null 2>&1 ) || fail "npm install failed — run it by hand in $SRC_DIR"
|
||||||
|
|
@ -77,6 +102,7 @@ do_install() {
|
||||||
exec node "$SRC_DIR/packages/cli/dist/index.js" "\$@"
|
exec node "$SRC_DIR/packages/cli/dist/index.js" "\$@"
|
||||||
EOF
|
EOF
|
||||||
chmod +x "$WRAPPER"
|
chmod +x "$WRAPPER"
|
||||||
|
write_manifest "$sha"
|
||||||
ok "installed logicsrc → $WRAPPER"
|
ok "installed logicsrc → $WRAPPER"
|
||||||
|
|
||||||
case ":$PATH:" in
|
case ":$PATH:" in
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
import { spawnSync } from "node:child_process";
|
||||||
import { readFileSync } from "node:fs";
|
import { readFileSync } from "node:fs";
|
||||||
import { evaluateAccountPolicy, scoreAccountActionRisk } from "@logicsrc/account-core";
|
import { evaluateAccountPolicy, scoreAccountActionRisk } from "@logicsrc/account-core";
|
||||||
import { Command } from "commander";
|
import { Command } from "commander";
|
||||||
|
|
@ -30,6 +31,17 @@ import { exportOpenSpecSummary, importOpenSpec, writeOpenSpecChange } from "./op
|
||||||
import { registerOntologyCommands } from "./ontology.js";
|
import { registerOntologyCommands } from "./ontology.js";
|
||||||
import { registerPrdCommands } from "./prd.js";
|
import { registerPrdCommands } from "./prd.js";
|
||||||
import { defaultPluginRegistry } from "./registry.js";
|
import { defaultPluginRegistry } from "./registry.js";
|
||||||
|
import {
|
||||||
|
GH_REPO,
|
||||||
|
INSTALL_URL,
|
||||||
|
fetchRemoteState,
|
||||||
|
installHome,
|
||||||
|
localVersion,
|
||||||
|
readManifest,
|
||||||
|
short,
|
||||||
|
trackedRef,
|
||||||
|
updateStatus
|
||||||
|
} from "./update.js";
|
||||||
|
|
||||||
process.stdout.on("error", (error: NodeJS.ErrnoException) => {
|
process.stdout.on("error", (error: NodeJS.ErrnoException) => {
|
||||||
if (error.code === "EPIPE") {
|
if (error.code === "EPIPE") {
|
||||||
|
|
@ -51,7 +63,7 @@ program
|
||||||
.option("--waiting-arcade", "Alias for --arcade.")
|
.option("--waiting-arcade", "Alias for --arcade.")
|
||||||
.option("--waiting-game <game>", "Alias for --arcade=<game>.")
|
.option("--waiting-game <game>", "Alias for --arcade=<game>.")
|
||||||
.option("--no-arcade", "Disable Waiting Arcade.")
|
.option("--no-arcade", "Disable Waiting Arcade.")
|
||||||
.version("0.1.0");
|
.version(localVersion());
|
||||||
|
|
||||||
program.action(async (options) => {
|
program.action(async (options) => {
|
||||||
if (!options.yolo) {
|
if (!options.yolo) {
|
||||||
|
|
@ -744,12 +756,45 @@ program.command("tui").description("Launch the tmux-friendly TUI.").action(() =>
|
||||||
console.log("\nPlugin status:\n" + renderPluginStatus());
|
console.log("\nPlugin status:\n" + renderPluginStatus());
|
||||||
});
|
});
|
||||||
|
|
||||||
program.command("update").alias("upgrade").description("Update the local LogicSRC CLI.").action(() => {
|
program
|
||||||
console.log("Current version: 0.1.0");
|
.command("update")
|
||||||
console.log("Latest version: 0.1.0");
|
.alias("upgrade")
|
||||||
console.log("LogicSRC CLI is already up to date.");
|
.description("Update the local LogicSRC CLI.")
|
||||||
console.log("Config preserved at $HOME/.logicsrc");
|
.option("--check", "Report whether an update is available without installing it")
|
||||||
});
|
.action(async (options) => {
|
||||||
|
const manifest = readManifest();
|
||||||
|
const ref = trackedRef(manifest);
|
||||||
|
const local = { version: localVersion(), commit: manifest?.commit ?? null };
|
||||||
|
|
||||||
|
console.log(`Tracking: ${GH_REPO}@${ref}`);
|
||||||
|
const remote = await fetchRemoteState(ref);
|
||||||
|
const status = updateStatus(local, remote);
|
||||||
|
|
||||||
|
console.log(`Current version: ${status.currentVersion}${local.commit ? ` (${short(local.commit)})` : ""}`);
|
||||||
|
console.log(
|
||||||
|
`Latest version: ${status.latestVersion ?? "unknown"}${status.latestCommit ? ` (${short(status.latestCommit)})` : ""}`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (status.upToDate) {
|
||||||
|
console.log(`LogicSRC CLI is already up to date — ${status.reason}.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Update available — ${status.reason}.`);
|
||||||
|
if (options.check) {
|
||||||
|
console.log(`Run 'logicsrc update' (or: curl -fsSL ${INSTALL_URL} | sh -s -- update) to install it.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Reinstalling from ${INSTALL_URL}…`);
|
||||||
|
const result = spawnSync("sh", ["-c", `curl -fsSL ${INSTALL_URL} | sh -s -- update`], { stdio: "inherit" });
|
||||||
|
if (result.status !== 0) {
|
||||||
|
console.error(`Update failed (exit ${result.status ?? "signal"}). Re-run by hand: curl -fsSL ${INSTALL_URL} | sh -s -- update`);
|
||||||
|
process.exitCode = 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log(`Updated. Install root: ${installHome()} — config preserved at ~/.logicsrc`);
|
||||||
|
});
|
||||||
|
|
||||||
program.command("remove").alias("uninstall").option("--purge", "Remove config and auth tokens").description("Remove local LogicSRC CLI.").action((options) => {
|
program.command("remove").alias("uninstall").option("--purge", "Remove config and auth tokens").description("Remove local LogicSRC CLI.").action((options) => {
|
||||||
console.log("Removed LogicSRC CLI.");
|
console.log("Removed LogicSRC CLI.");
|
||||||
|
|
|
||||||
95
packages/cli/src/update.test.ts
Normal file
95
packages/cli/src/update.test.ts
Normal file
|
|
@ -0,0 +1,95 @@
|
||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
import { compareVersions, parseManifest, trackedRef, updateStatus } from "./update.js";
|
||||||
|
|
||||||
|
describe("compareVersions", () => {
|
||||||
|
it("orders releases numerically, not lexically", () => {
|
||||||
|
expect(compareVersions("0.2.0", "0.1.0")).toBe(1);
|
||||||
|
expect(compareVersions("0.1.0", "0.2.0")).toBe(-1);
|
||||||
|
expect(compareVersions("0.1.0", "0.1.0")).toBe(0);
|
||||||
|
// "10" > "9" numerically but sorts lower as a string.
|
||||||
|
expect(compareVersions("0.10.0", "0.9.0")).toBe(1);
|
||||||
|
expect(compareVersions("1.0.0", "0.99.99")).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("tolerates v-prefixes, prereleases, and short versions", () => {
|
||||||
|
expect(compareVersions("v1.2.3", "1.2.3")).toBe(0);
|
||||||
|
expect(compareVersions("1.2.0-beta.1", "1.2.0")).toBe(0);
|
||||||
|
expect(compareVersions("1.2", "1.2.0")).toBe(0);
|
||||||
|
expect(compareVersions("garbage", "0.0.0")).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("parseManifest", () => {
|
||||||
|
it("reads a manifest written by install.sh", () => {
|
||||||
|
const m = parseManifest(
|
||||||
|
JSON.stringify({ ref: "master", commit: "abc1234def", version: "0.1.0", installed_at: "2026-07-28T00:00:00Z" })
|
||||||
|
);
|
||||||
|
expect(m).toEqual({ ref: "master", commit: "abc1234def", version: "0.1.0", installed_at: "2026-07-28T00:00:00Z" });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("defaults the ref and nulls empty or missing fields", () => {
|
||||||
|
// install.sh writes empty strings when the sha lookup or version read fails.
|
||||||
|
expect(parseManifest(JSON.stringify({ commit: "", version: "" }))).toEqual({
|
||||||
|
ref: "master",
|
||||||
|
commit: null,
|
||||||
|
version: null,
|
||||||
|
installed_at: null
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns null for junk rather than throwing", () => {
|
||||||
|
expect(parseManifest("not json")).toBeNull();
|
||||||
|
expect(parseManifest("[]")).toBeNull();
|
||||||
|
expect(parseManifest("null")).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("trackedRef", () => {
|
||||||
|
it("prefers the environment, then the manifest, then master", () => {
|
||||||
|
const manifest = { ref: "next", commit: null, version: null, installed_at: null };
|
||||||
|
expect(trackedRef(manifest, { LOGICSRC_REF: "experiment" })).toBe("experiment");
|
||||||
|
expect(trackedRef(manifest, {})).toBe("next");
|
||||||
|
expect(trackedRef(null, {})).toBe("master");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("updateStatus", () => {
|
||||||
|
const local = { version: "0.1.0", commit: "aaaaaaaaaaaa" };
|
||||||
|
|
||||||
|
it("reports up to date only when the commit actually matches", () => {
|
||||||
|
const s = updateStatus(local, { version: "0.1.0", commit: "aaaaaaaaaaaa" });
|
||||||
|
expect(s.upToDate).toBe(true);
|
||||||
|
expect(s.reason).toContain("current commit");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("matches a short sha against a full one", () => {
|
||||||
|
expect(updateStatus({ version: "0.1.0", commit: "aaaaaaa" }, { version: "0.1.0", commit: "aaaaaaaaaaaa" }).upToDate).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("detects a moved branch even when the version is unchanged", () => {
|
||||||
|
// The bug this replaces: version-only comparison called this "up to date"
|
||||||
|
// forever, because the installer ships a branch tarball, not a release.
|
||||||
|
const s = updateStatus(local, { version: "0.1.0", commit: "bbbbbbbbbbbb" });
|
||||||
|
expect(s.upToDate).toBe(false);
|
||||||
|
expect(s.reason).toContain("moved on");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("detects a newer published version", () => {
|
||||||
|
const s = updateStatus(local, { version: "0.2.0", commit: "aaaaaaaaaaaa" });
|
||||||
|
expect(s.upToDate).toBe(false);
|
||||||
|
expect(s.reason).toContain("0.1.0 → 0.2.0");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("never claims to be current when the local commit is unknown", () => {
|
||||||
|
const s = updateStatus({ version: "0.1.0", commit: null }, { version: "0.1.0", commit: "bbbbbbbbbbbb" });
|
||||||
|
expect(s.upToDate).toBe(false);
|
||||||
|
expect(s.reason).toContain("predates update tracking");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not invent an update when GitHub is unreachable", () => {
|
||||||
|
const s = updateStatus(local, { version: null, commit: null });
|
||||||
|
expect(s.upToDate).toBe(true);
|
||||||
|
expect(s.reason).toContain("could not reach GitHub");
|
||||||
|
});
|
||||||
|
});
|
||||||
165
packages/cli/src/update.ts
Normal file
165
packages/cli/src/update.ts
Normal file
|
|
@ -0,0 +1,165 @@
|
||||||
|
import { readFileSync } from "node:fs";
|
||||||
|
import { homedir } from "node:os";
|
||||||
|
import { dirname, join } from "node:path";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
|
||||||
|
export const GH_REPO = "profullstack/logicsrc";
|
||||||
|
export const INSTALL_URL = "https://logicsrc.com/install.sh";
|
||||||
|
|
||||||
|
/** Written by install.sh so the CLI can tell which commit it was built from. */
|
||||||
|
export type InstallManifest = {
|
||||||
|
ref: string;
|
||||||
|
commit: string | null;
|
||||||
|
version: string | null;
|
||||||
|
installed_at: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type RemoteState = { version: string | null; commit: string | null };
|
||||||
|
|
||||||
|
export type UpdateStatus = {
|
||||||
|
upToDate: boolean;
|
||||||
|
/** Why we reached that verdict — shown to the user so it's never a bare claim. */
|
||||||
|
reason: string;
|
||||||
|
currentVersion: string;
|
||||||
|
latestVersion: string | null;
|
||||||
|
currentCommit: string | null;
|
||||||
|
latestCommit: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Install root the installer uses (not the config dir, which is ~/.logicsrc). */
|
||||||
|
export function installHome(env: NodeJS.ProcessEnv = process.env): string {
|
||||||
|
return env.LOGICSRC_HOME || join(env.HOME || homedir(), ".logicsrc-cli");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Git ref this install tracks; install.sh defaults to master. */
|
||||||
|
export function trackedRef(manifest: InstallManifest | null, env: NodeJS.ProcessEnv = process.env): string {
|
||||||
|
return env.LOGICSRC_REF || manifest?.ref || "master";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The version of the CLI actually running, read from its own package.json
|
||||||
|
* rather than hardcoded — a literal here goes stale the moment anyone bumps
|
||||||
|
* the package and lies to every user who runs `logicsrc update`.
|
||||||
|
*/
|
||||||
|
export function localVersion(moduleUrl: string = import.meta.url): string {
|
||||||
|
// dist/update.js and src/update.ts are both one level under the package root.
|
||||||
|
const pkgPath = join(dirname(dirname(fileURLToPath(moduleUrl))), "package.json");
|
||||||
|
try {
|
||||||
|
const pkg = JSON.parse(readFileSync(pkgPath, "utf8")) as { version?: unknown };
|
||||||
|
return typeof pkg.version === "string" ? pkg.version : "unknown";
|
||||||
|
} catch {
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Parses $LOGICSRC_HOME/install.json; malformed or absent manifests are just "unknown". */
|
||||||
|
export function parseManifest(raw: string): InstallManifest | null {
|
||||||
|
let parsed: unknown;
|
||||||
|
try { parsed = JSON.parse(raw); } catch { return null; }
|
||||||
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
||||||
|
const m = parsed as Record<string, unknown>;
|
||||||
|
return {
|
||||||
|
ref: typeof m.ref === "string" && m.ref ? m.ref : "master",
|
||||||
|
commit: typeof m.commit === "string" && m.commit ? m.commit : null,
|
||||||
|
version: typeof m.version === "string" && m.version ? m.version : null,
|
||||||
|
installed_at: typeof m.installed_at === "string" && m.installed_at ? m.installed_at : null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function readManifest(home: string = installHome()): InstallManifest | null {
|
||||||
|
try {
|
||||||
|
return parseManifest(readFileSync(join(home, "install.json"), "utf8"));
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Semver-ish compare. Returns -1 if a < b, 0 if equal, 1 if a > b. */
|
||||||
|
export function compareVersions(a: string, b: string): number {
|
||||||
|
const parts = (v: string) =>
|
||||||
|
v.replace(/^v/, "").split("-")[0]!.split(".").map((n) => Number.parseInt(n, 10) || 0);
|
||||||
|
const [x, y] = [parts(a), parts(b)];
|
||||||
|
for (let i = 0; i < Math.max(x.length, y.length); i++) {
|
||||||
|
const d = (x[i] ?? 0) - (y[i] ?? 0);
|
||||||
|
if (d !== 0) return d > 0 ? 1 : -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decides whether an update is available.
|
||||||
|
*
|
||||||
|
* The installer ships a tarball of a branch, not a tagged release, so the
|
||||||
|
* version alone can't answer this: master moves constantly while
|
||||||
|
* packages/cli/package.json sits on the same number for months. The commit is
|
||||||
|
* the real signal, and the version is only a fallback for installs predating
|
||||||
|
* the manifest.
|
||||||
|
*/
|
||||||
|
export function updateStatus(local: { version: string; commit: string | null }, remote: RemoteState): UpdateStatus {
|
||||||
|
const base = {
|
||||||
|
currentVersion: local.version,
|
||||||
|
latestVersion: remote.version,
|
||||||
|
currentCommit: local.commit,
|
||||||
|
latestCommit: remote.commit
|
||||||
|
};
|
||||||
|
|
||||||
|
if (remote.version && compareVersions(remote.version, local.version) > 0) {
|
||||||
|
return { ...base, upToDate: false, reason: `a newer release is published (${local.version} → ${remote.version})` };
|
||||||
|
}
|
||||||
|
if (local.commit && remote.commit) {
|
||||||
|
const same = local.commit.startsWith(remote.commit) || remote.commit.startsWith(local.commit);
|
||||||
|
return same
|
||||||
|
? { ...base, upToDate: true, reason: "installed from the current commit" }
|
||||||
|
: { ...base, upToDate: false, reason: `the tracked branch has moved on (${short(local.commit)} → ${short(remote.commit)})` };
|
||||||
|
}
|
||||||
|
if (!remote.version && !remote.commit) {
|
||||||
|
return { ...base, upToDate: true, reason: "could not reach GitHub — assuming no update rather than guessing" };
|
||||||
|
}
|
||||||
|
if (!local.commit) {
|
||||||
|
return {
|
||||||
|
...base,
|
||||||
|
upToDate: false,
|
||||||
|
reason: "this install predates update tracking, so its commit is unknown — reinstalling is the only way to be sure"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return { ...base, upToDate: true, reason: "already on the latest published version" };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function short(commit: string): string {
|
||||||
|
return commit.slice(0, 7);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Latest commit sha for a ref. The .sha media type returns it as bare text. */
|
||||||
|
export async function fetchRemoteCommit(ref: string, repo = GH_REPO): Promise<string | null> {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`https://api.github.com/repos/${repo}/commits/${encodeURIComponent(ref)}`, {
|
||||||
|
headers: { accept: "application/vnd.github.sha", "user-agent": "logicsrc-cli" },
|
||||||
|
signal: AbortSignal.timeout(10_000)
|
||||||
|
});
|
||||||
|
if (!res.ok) return null;
|
||||||
|
const sha = (await res.text()).trim();
|
||||||
|
return /^[0-9a-f]{7,40}$/i.test(sha) ? sha : null;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** CLI version declared on the tracked ref. */
|
||||||
|
export async function fetchRemoteVersion(ref: string, repo = GH_REPO): Promise<string | null> {
|
||||||
|
try {
|
||||||
|
const res = await fetch(
|
||||||
|
`https://raw.githubusercontent.com/${repo}/${encodeURIComponent(ref)}/packages/cli/package.json`,
|
||||||
|
{ headers: { "user-agent": "logicsrc-cli" }, signal: AbortSignal.timeout(10_000) }
|
||||||
|
);
|
||||||
|
if (!res.ok) return null;
|
||||||
|
const pkg = JSON.parse(await res.text()) as { version?: unknown };
|
||||||
|
return typeof pkg.version === "string" ? pkg.version : null;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchRemoteState(ref: string, repo = GH_REPO): Promise<RemoteState> {
|
||||||
|
const [version, commit] = await Promise.all([fetchRemoteVersion(ref, repo), fetchRemoteCommit(ref, repo)]);
|
||||||
|
return { version, commit };
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue