mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-11-04 13:07:11 +00:00
fix(cli): catalog says the registry could not be read instead of listing nothing (#7015)
This commit is contained in:
@@ -73,6 +73,7 @@ describe("countUnindexed", () => {
|
||||
|
||||
const state = vi.hoisted(() => ({
|
||||
registry: [] as Array<{ name: string; type: string; tags?: string[] }>,
|
||||
registryUnreadable: "",
|
||||
artifactRevision: "revision-current",
|
||||
cachedVectorRevision: "revision-current",
|
||||
vectorFetches: 0,
|
||||
@@ -108,10 +109,10 @@ vi.mock("../registry/resolver.js", () => ({
|
||||
}));
|
||||
|
||||
vi.mock("../registry/remote.js", () => ({
|
||||
fetchRegistryManifest: async () => ({
|
||||
items: state.registry,
|
||||
catalogArtifact: { revision: state.artifactRevision },
|
||||
}),
|
||||
readRegistryManifest: async () => {
|
||||
if (state.registryUnreadable) throw new Error(state.registryUnreadable);
|
||||
return { items: state.registry, catalogArtifact: { revision: state.artifactRevision } };
|
||||
},
|
||||
}));
|
||||
|
||||
// The terminal and CI switches below decide attendance; the agent running the suite must not.
|
||||
@@ -276,6 +277,7 @@ async function runForExit(
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
state.registryUnreadable = "";
|
||||
state.modelStatus = "ready";
|
||||
state.artifactRevision = "revision-current";
|
||||
state.cachedVectorRevision = "revision-current";
|
||||
@@ -301,6 +303,20 @@ beforeEach(() => {
|
||||
];
|
||||
});
|
||||
|
||||
describe("catalog when the registry cannot be read", () => {
|
||||
it("exits non-zero with the reason instead of printing an empty list", async () => {
|
||||
state.registryUnreadable =
|
||||
"The registry could not be read: fetch failed (getaddrinfo ENOTFOUND raw.githubusercontent.com)";
|
||||
|
||||
const { exitCode, err } = await runForExit({ json: true });
|
||||
|
||||
expect(exitCode).toBe(1);
|
||||
expect(err).toContain("The registry could not be read: fetch failed");
|
||||
expect(err).toContain("raw.githubusercontent.com");
|
||||
expect(err).not.toContain("[]");
|
||||
});
|
||||
});
|
||||
|
||||
describe("catalog --json meaning search", () => {
|
||||
it("reports the registry moves meaning search cannot see", async () => {
|
||||
// Published after the user's artifact was fetched: in the registry, absent
|
||||
|
||||
@@ -14,7 +14,7 @@ import * as clack from "../ui/prompts.js";
|
||||
import { realpath, type ItemType, type RegistryItem } from "@hyperframes/core";
|
||||
import { c } from "../ui/colors.js";
|
||||
import { loadAllItems } from "../registry/resolver.js";
|
||||
import { fetchRegistryManifest } from "../registry/remote.js";
|
||||
import { readRegistryManifest } from "../registry/remote.js";
|
||||
import { loadProjectConfig, DEFAULT_PROJECT_CONFIG } from "../utils/projectConfig.js";
|
||||
import { relative, resolve } from "node:path";
|
||||
import { finishCommand } from "../utils/commandResult.js";
|
||||
@@ -232,9 +232,12 @@ export default defineCommand({
|
||||
|
||||
// Asked for the whole manifest on purpose: its item list defines coverage,
|
||||
// and its artifact revision is the one owner of vector freshness.
|
||||
const manifest = await fetchRegistryManifest(config.registry);
|
||||
const entries = manifest?.items ?? [];
|
||||
const artifactRevision = manifest?.catalogArtifact?.revision;
|
||||
const manifest = await readRegistryManifest(config.registry).catch((error: unknown) => {
|
||||
console.error(error instanceof Error ? error.message : String(error));
|
||||
return finishCommand(1);
|
||||
});
|
||||
const entries = manifest.items;
|
||||
const artifactRevision = manifest.catalogArtifact?.revision;
|
||||
const catalog = entries.filter((e) => e.type !== "hyperframes:example");
|
||||
const registryNames = new Set(catalog.map((e) => e.name));
|
||||
const filtered = typeFilter ? catalog.filter((e) => e.type === typeFilter) : catalog;
|
||||
|
||||
@@ -18,6 +18,7 @@ const {
|
||||
fetchItemFile,
|
||||
fetchItemManifest,
|
||||
fetchRegistryManifest,
|
||||
readRegistryManifest,
|
||||
DEFAULT_REGISTRY_URL,
|
||||
} = await import("./remote.js");
|
||||
|
||||
@@ -76,6 +77,21 @@ afterAll(() => {
|
||||
rmSync(scratchHome, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
describe("readRegistryManifest", () => {
|
||||
it("throws why when the registry host cannot be reached and nothing is cached", async () => {
|
||||
vi.spyOn(globalThis, "fetch").mockRejectedValue(
|
||||
Object.assign(new TypeError("fetch failed"), {
|
||||
cause: Object.assign(new Error("getaddrinfo ENOTFOUND raw.githubusercontent.com"), {
|
||||
code: "ENOTFOUND",
|
||||
}),
|
||||
}),
|
||||
);
|
||||
await expect(readRegistryManifest(DEFAULT_REGISTRY_URL)).rejects.toThrow(
|
||||
"The registry could not be read: fetch failed (getaddrinfo ENOTFOUND raw.githubusercontent.com)",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("fetchRegistryManifest", () => {
|
||||
it("rejects poisoned stale cache data instead of returning it on network failure", async () => {
|
||||
vi.spyOn(globalThis, "fetch").mockResolvedValueOnce(ok(MANIFEST));
|
||||
|
||||
@@ -123,10 +123,17 @@ async function fetchJson<T>(url: string, validate: (value: unknown) => value is
|
||||
* because "check for something newer" and "rather have nothing than this" are
|
||||
* different requests and only the first one is ever made.
|
||||
*/
|
||||
export async function fetchRegistryManifest(
|
||||
export const fetchRegistryManifest = (
|
||||
baseUrl: string = DEFAULT_REGISTRY_URL,
|
||||
options?: { skipCache?: boolean },
|
||||
): Promise<RegistryManifest | undefined> {
|
||||
): Promise<RegistryManifest | undefined> =>
|
||||
readRegistryManifest(baseUrl, options).catch(() => undefined);
|
||||
|
||||
/** The manifest as fetchRegistryManifest gives it, but unreachable and never cached throws why, never undefined. */
|
||||
export async function readRegistryManifest(
|
||||
baseUrl: string = DEFAULT_REGISTRY_URL,
|
||||
options?: { skipCache?: boolean },
|
||||
): Promise<RegistryManifest> {
|
||||
const url = registryPathUrl(baseUrl, "registry.json");
|
||||
const cacheFile = cachePath(baseUrl, "registry");
|
||||
const cached = readCacheEntry(cacheFile, validRegistryManifest);
|
||||
@@ -136,8 +143,9 @@ export async function fetchRegistryManifest(
|
||||
const manifest = await fetchJson(url, validRegistryManifest);
|
||||
writeCache(cacheFile, manifest);
|
||||
return manifest;
|
||||
} catch {
|
||||
return cached?.data;
|
||||
} catch (err) {
|
||||
if (cached) return cached.data;
|
||||
throw new Error(`The registry could not be read: ${describeCauseChain(err)}`, { cause: err });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user