claudeup 4.37.0 → 4.38.0
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/package.json +4 -4
- package/scripts/verify-community-registry.ts +272 -0
- package/src/__tests__/community-fetch.test.ts +545 -0
- package/src/__tests__/community-registry.test.ts +269 -0
- package/src/__tests__/community-staleness.test.ts +722 -0
- package/src/__tests__/open-file.test.ts +59 -0
- package/src/__tests__/style-wrap.test.ts +220 -0
- package/src/__tests__/styles-manager.test.ts +1124 -0
- package/src/__tests__/styles-origins.test.ts +416 -0
- package/src/__tests__/styles-screen-state.test.ts +460 -0
- package/src/__tests__/styles-status-line.test.ts +72 -0
- package/src/__tests__/styles-sync.test.ts +452 -0
- package/src/__tests__/tabbar-layout.test.ts +62 -0
- package/src/__tests__/terminology-filler.test.ts +214 -0
- package/src/data/community-styles.ts +521 -0
- package/src/main.tsx +15 -0
- package/src/services/catalog-cache-store.ts +101 -7
- package/src/services/community-fetcher.ts +90 -0
- package/src/services/community-styles.ts +1194 -0
- package/src/services/styles-manager.ts +1400 -0
- package/src/services/terminology-filler.ts +266 -0
- package/src/ui/App.tsx +15 -3
- package/src/ui/adapters/stylesAdapter.ts +403 -0
- package/src/ui/components/TabBar.tsx +43 -9
- package/src/ui/components/primitives/ActionHints.tsx +4 -1
- package/src/ui/components/primitives/ListCategoryRow.tsx +10 -1
- package/src/ui/registry.ts +6 -0
- package/src/ui/renderers/styleRenderers.tsx +809 -0
- package/src/ui/screens/StylesScreen.tsx +1089 -0
- package/src/ui/screens/index.ts +1 -0
- package/src/ui/state/reducer.ts +113 -1
- package/src/ui/state/types.ts +60 -2
- package/src/utils/open-file.ts +84 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* community-fetcher.ts — the one thing in the community-styles feature that
|
|
3
|
+
* knows `fetch` exists.
|
|
4
|
+
*
|
|
5
|
+
* ## Why a port at all
|
|
6
|
+
*
|
|
7
|
+
* Exactly one boundary gets an interface: the network. The domain here is thin,
|
|
8
|
+
* and an interface-per-class reflex would be waste — so there is no Strategy
|
|
9
|
+
* registry, no adapter factory, and nothing else in the feature is abstracted.
|
|
10
|
+
* The gain is the one the constraints demand: no test can reach GitHub, because
|
|
11
|
+
* every exported function in `community-styles.ts` takes a `StyleFetcher` as a
|
|
12
|
+
* REQUIRED argument. Omitting it is a type error, not a forgotten mock.
|
|
13
|
+
*
|
|
14
|
+
* ## Why it does not speak `Response`
|
|
15
|
+
*
|
|
16
|
+
* The classic way a port leaks its adapter. A `Response` drags in streaming
|
|
17
|
+
* semantics, a body that can only be read once, and a `fetch`-shaped mental
|
|
18
|
+
* model into every test fake. `{ status, body, etag }` is what the caller
|
|
19
|
+
* actually uses.
|
|
20
|
+
*
|
|
21
|
+
* `headers` is the one deliberate exception. `github-budget.ts` parses
|
|
22
|
+
* `retry-after` / `x-ratelimit-reset` / `x-ratelimit-remaining` itself, and it
|
|
23
|
+
* is the module that knows the two hosts differ in what they report. Re-deriving
|
|
24
|
+
* that here would fork the rate-limit logic, which is precisely what §5.3 of the
|
|
25
|
+
* design forbids. A fake supplies `new Headers({...})`, which is a standard
|
|
26
|
+
* built-in, not a fetch import.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/** What the application needs from an HTTP response. Never a `Response`. */
|
|
30
|
+
export interface StyleFetchResponse {
|
|
31
|
+
status: number;
|
|
32
|
+
/** Empty for a 304 and for any status with no body. */
|
|
33
|
+
body: string;
|
|
34
|
+
etag: string | null;
|
|
35
|
+
contentType: string | null;
|
|
36
|
+
/** Handed to `github-budget` unchanged — see the header note. */
|
|
37
|
+
headers: Headers;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export type StyleFetcher = (
|
|
41
|
+
url: string,
|
|
42
|
+
opts?: { etag?: string | null; timeoutMs?: number; accept?: string },
|
|
43
|
+
) => Promise<StyleFetchResponse>;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* 10s, matching `marketplace-fetcher.ts`. A style is a few kilobytes over a CDN
|
|
47
|
+
* — measured at ~340ms — so anything past ten seconds is a broken route rather
|
|
48
|
+
* than a slow one, and the user is waiting on a keypress they just made.
|
|
49
|
+
*/
|
|
50
|
+
export const FETCH_TIMEOUT_MS = 10_000;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The real adapter. Wraps global `fetch` and nothing more: no retry, no
|
|
54
|
+
* classification, no budget check. Those are policy and belong in the service,
|
|
55
|
+
* where they can be tested without a network.
|
|
56
|
+
*
|
|
57
|
+
* Sends `GITHUB_TOKEN` / `GITHUB_PERSONAL_ACCESS_TOKEN` to `api.github.com`
|
|
58
|
+
* ONLY — exactly as `skills-manager.ts` already does. The token raises that
|
|
59
|
+
* host's 60/hr unauthenticated budget to 5000/hr and is the documented escape
|
|
60
|
+
* hatch when the advisory says the budget is spent. It is never sent to
|
|
61
|
+
* `raw.githubusercontent.com`, which needs no auth and would receive a
|
|
62
|
+
* credential it has no business seeing.
|
|
63
|
+
*/
|
|
64
|
+
export const githubFetcher: StyleFetcher = async (url, opts = {}) => {
|
|
65
|
+
const headers: Record<string, string> = {
|
|
66
|
+
accept: opts.accept ?? "text/plain, */*",
|
|
67
|
+
"user-agent": "claudeup",
|
|
68
|
+
};
|
|
69
|
+
if (opts.etag) headers["if-none-match"] = opts.etag;
|
|
70
|
+
|
|
71
|
+
if (new URL(url).hostname === "api.github.com") {
|
|
72
|
+
const token =
|
|
73
|
+
process.env.GITHUB_TOKEN || process.env.GITHUB_PERSONAL_ACCESS_TOKEN;
|
|
74
|
+
if (token) headers.authorization = `Bearer ${token}`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const response = await fetch(url, {
|
|
78
|
+
headers,
|
|
79
|
+
signal: AbortSignal.timeout(opts.timeoutMs ?? FETCH_TIMEOUT_MS),
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
status: response.status,
|
|
84
|
+
// 304 carries no body; reading it is still safe and yields "".
|
|
85
|
+
body: response.status === 304 ? "" : await response.text(),
|
|
86
|
+
etag: response.headers.get("etag"),
|
|
87
|
+
contentType: response.headers.get("content-type"),
|
|
88
|
+
headers: response.headers,
|
|
89
|
+
};
|
|
90
|
+
};
|