opencode-plugin-hub 0.1.2 → 0.1.4

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-plugin-hub",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Plugin manager for OpenCode Desktop: browse community plugins, search npm, inspect README and downloads, install and update without editing JSON.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -45,13 +45,30 @@ interface SnapshotEntry {
45
45
  tags: string[]
46
46
  }
47
47
 
48
+ interface Source {
49
+ url: string
50
+ /** Extra `Accept` value this source needs. The GitHub contents API only returns the raw file for its raw media type. */
51
+ accept?: string
52
+ }
53
+
48
54
  /**
49
- * Two community mirrors of the same curated list. The first is the upstream project; the
50
- * second is a fork that occasionally carries newer entries, so it only acts as a fallback.
55
+ * The same curated list, reachable several ways. `raw.githubusercontent.com` is TLS-reset on some
56
+ * networks (the handshake is dropped with "connection reset by peer") while `api.github.com` stays
57
+ * reachable, so every repository is offered through both: the CDN first — no rate limit, and it is
58
+ * the canonical copy — then the contents API as a fallback. The fork occasionally carries newer
59
+ * entries and only matters when the upstream copies fail.
51
60
  */
52
- const SOURCES = [
53
- "https://raw.githubusercontent.com/awesome-opencode/awesome-opencode/main/dist/registry.json",
54
- "https://raw.githubusercontent.com/Gracdjd/awesome-opencode/main/dist/registry.json",
61
+ const SOURCES: Source[] = [
62
+ { url: "https://raw.githubusercontent.com/awesome-opencode/awesome-opencode/main/dist/registry.json" },
63
+ {
64
+ url: "https://api.github.com/repos/awesome-opencode/awesome-opencode/contents/dist/registry.json?ref=main",
65
+ accept: "application/vnd.github.raw+json",
66
+ },
67
+ { url: "https://raw.githubusercontent.com/Gracdjd/awesome-opencode/main/dist/registry.json" },
68
+ {
69
+ url: "https://api.github.com/repos/Gracdjd/awesome-opencode/contents/dist/registry.json?ref=main",
70
+ accept: "application/vnd.github.raw+json",
71
+ },
55
72
  ]
56
73
 
57
74
  /** Re-fetch the remote list at most this often; otherwise the cache wins. */
@@ -133,11 +150,12 @@ function toEntries(raw: RawEntry[], installed: Set<string>): CatalogEntryValue[]
133
150
 
134
151
  async function fetchRemote(): Promise<{ entries: RawEntry[]; url: string }> {
135
152
  let lastError = ""
136
- for (const url of SOURCES) {
153
+ for (const source of SOURCES) {
137
154
  try {
138
- const response = await fetch(url, { signal: AbortSignal.timeout(12_000), headers: { accept: "application/json" } })
155
+ const headers: Record<string, string> = { accept: source.accept ?? "application/json" }
156
+ const response = await fetch(source.url, { signal: AbortSignal.timeout(12_000), headers })
139
157
  if (!response.ok) {
140
- lastError = `${new URL(url).host} 返回 ${response.status}`
158
+ lastError = `${new URL(source.url).host} 返回 ${response.status}`
141
159
  continue
142
160
  }
143
161
  const body = (await response.json()) as unknown
@@ -145,7 +163,7 @@ async function fetchRemote(): Promise<{ entries: RawEntry[]; url: string }> {
145
163
  lastError = "registry 格式不是数组"
146
164
  continue
147
165
  }
148
- return { entries: body as RawEntry[], url }
166
+ return { entries: body as RawEntry[], url: source.url }
149
167
  } catch (error) {
150
168
  lastError = describe(error)
151
169
  }
@@ -182,6 +200,9 @@ async function writeCache(entries: RawEntry[], url: string): Promise<number> {
182
200
  return fetchedAt
183
201
  }
184
202
 
203
+ /** The first source's URL, used when a cached payload predates the `url` field. */
204
+ const PRIMARY_URL = SOURCES[0]!.url
205
+
185
206
  /**
186
207
  * The curated community list. Network first (cached for half a day), then the cache, then the
187
208
  * snapshot baked into this bundle — so the page is never empty, even offline.
@@ -193,7 +214,7 @@ export async function browse(installed: Set<string>, force: boolean): Promise<Ca
193
214
  return {
194
215
  entries: toEntries(cache.entries, installed),
195
216
  source: "cache",
196
- registry: cache.url ?? SOURCES[0]!,
217
+ registry: cache.url ?? PRIMARY_URL,
197
218
  fetchedAt: cache.fetchedAt,
198
219
  error: "",
199
220
  }
@@ -213,7 +234,7 @@ export async function browse(installed: Set<string>, force: boolean): Promise<Ca
213
234
  return {
214
235
  entries: toEntries(cache.entries, installed),
215
236
  source: "cache",
216
- registry: cache.url ?? SOURCES[0]!,
237
+ registry: cache.url ?? PRIMARY_URL,
217
238
  fetchedAt: cache.fetchedAt,
218
239
  error: describe(error),
219
240
  }
@@ -221,7 +242,7 @@ export async function browse(installed: Set<string>, force: boolean): Promise<Ca
221
242
  return {
222
243
  entries: toEntries(SNAPSHOT.map(toRaw), installed),
223
244
  source: "builtin",
224
- registry: SOURCES[0]!,
245
+ registry: PRIMARY_URL,
225
246
  fetchedAt: 0,
226
247
  error: describe(error),
227
248
  }
@@ -240,6 +261,14 @@ function toRaw(entry: SnapshotEntry): RawEntry {
240
261
  }
241
262
 
242
263
  function describe(error: unknown): string {
243
- if (error instanceof Error) return error.message
244
- return String(error)
264
+ const message = error instanceof Error ? error.message : String(error)
265
+ // `fetch failed` is undici's wrapper for every socket-level failure; on its own it says nothing
266
+ // about what to do. Name the likely causes so the notice is actionable instead of alarming.
267
+ if (/fetch failed|network|ECONNRESET|ETIMEDOUT|ENOTFOUND|EAI_AGAIN|socket hang up/i.test(message)) {
268
+ return "无法连接插件目录(网络受限或连接被重置),已改用本地缓存"
269
+ }
270
+ if (/aborted|timeout/i.test(message)) {
271
+ return "连接插件目录超时,已改用本地缓存"
272
+ }
273
+ return message
245
274
  }