dshmarket 1.66.31 → 1.66.32

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/lib/regions.js CHANGED
@@ -107,6 +107,34 @@ const ROUTES = {
107
107
  ],
108
108
  },
109
109
  };
110
+ /**
111
+ * Every catalog source worth trying for a region: its own list, plus the
112
+ * catalog's published package on that region's own registry when the list
113
+ * does not already name it.
114
+ *
115
+ * The catalog hangs off a SECOND, independently blockable host (#891), and
116
+ * the probe that picks a region measures only the npm registry. A network
117
+ * where npm answers fast and GitHub Pages is blocked therefore measures
118
+ * `global` — whose one source is that very host — and since the region is
119
+ * decided once and persisted, nothing ever reconsiders: "catalog fetch
120
+ * failed" forever, on a machine whose registry is serving the catalog as a
121
+ * package all along.
122
+ *
123
+ * Appending the package closes that blind spot without asking the probe to
124
+ * learn about the second host, without revisiting the decision, and without
125
+ * sending anyone to another region's mirror: it rides the registry the probe
126
+ * already proved reachable. Last, not first — for everyone the official
127
+ * domain serves, this stays a source that is never contacted.
128
+ */
129
+ export function catalogSourcesFor(region) {
130
+ const routes = routesFor(region);
131
+ const sources = [...routes.catalog];
132
+ const served = sources.some(source => source.kind === 'npm'
133
+ && source.registry === routes.npmRegistry && source.pkg === CATALOG_PACKAGE);
134
+ if (!served)
135
+ sources.push({ kind: 'npm', registry: routes.npmRegistry, pkg: CATALOG_PACKAGE });
136
+ return sources;
137
+ }
110
138
  /** Read an environment override, treating blank as unset. */
111
139
  function override(env, name) {
112
140
  const raw = env[name];
package/lib/registry.js CHANGED
@@ -3,9 +3,10 @@
3
3
  * fresh on every request. See `loadRegistry` for why there is nothing
4
4
  * behind it any more.
5
5
  */
6
+ import { logEvent } from './log.js';
6
7
  import { configuredProxy, marketFetch } from './net.js';
7
8
  import { catalogFromPackage } from './catalog-npm.js';
8
- import { activeRegion, routesFor } from './regions.js';
9
+ import { activeRegion, catalogSourcesFor, routesFor } from './regions.js';
9
10
  /**
10
11
  * Category ids for one catalog entry, de-duplicated in declaration order.
11
12
  *
@@ -166,8 +167,23 @@ export async function loadRegistry(region = activeRegion()) {
166
167
  // the FIRST request the market makes, so a mirror that has gone down must
167
168
  // mean a slow market rather than an empty one — the list ends at the
168
169
  // address that has always worked.
169
- for (const source of routesFor(region).catalog) {
170
+ //
171
+ // The list runs one entry past the region's own sources on purpose (#891):
172
+ // the region probe measures the npm registry, so it cannot see a network
173
+ // where npm is fast and the catalog's host is blocked — and the region is
174
+ // decided once and persisted. The catalog's published package, on the
175
+ // registry the probe just proved reachable, is the repair that needs
176
+ // neither the probe nor the decision to change.
177
+ const ownSources = routesFor(region).catalog.length;
178
+ const catalogSources = catalogSourcesFor(region);
179
+ for (const [sourceIndex, source] of catalogSources.entries()) {
170
180
  const key = sourceKey(source);
181
+ const servedBy = (data) => {
182
+ if (sourceIndex >= ownSources) {
183
+ logEvent('warn', 'registry', `catalog: the ${region} sources did not answer; read the catalog package from ${key} instead (#891)`);
184
+ }
185
+ return data;
186
+ };
171
187
  // Two attempts each. A catalog fetch crossing a long, lossy path fails
172
188
  // transiently often enough that one retry is worth more than the second
173
189
  // or two it costs — and with nothing behind this call any more, a
@@ -183,12 +199,12 @@ export async function loadRegistry(region = activeRegion()) {
183
199
  const { version, data } = await catalogFromPackage(source.registry, source.pkg, reusable?.version ?? undefined);
184
200
  // `data === null` means the published version is the one in hand.
185
201
  if (data === null && reusable !== null)
186
- return reusable.data;
202
+ return servedBy(reusable.data);
187
203
  if (data === null)
188
204
  throw new Error('the catalog package reported no change with nothing to reuse');
189
205
  const parsed = asRegistry(data);
190
206
  served = { key, etag: null, modified: null, version, data: parsed };
191
- return parsed;
207
+ return servedBy(parsed);
192
208
  }
193
209
  // ETag first: it is exact, while a date has one-second resolution and
194
210
  // a catalog republished twice within the same second would validate
@@ -221,7 +237,7 @@ export async function loadRegistry(region = activeRegion()) {
221
237
  // otherwise surface as a confusing parse error on an empty body.
222
238
  if (reusable === null)
223
239
  throw new Error('the catalog answered "not modified" with nothing to revalidate');
224
- return reusable.data;
240
+ return servedBy(reusable.data);
225
241
  }
226
242
  if (!res.ok)
227
243
  throw new Error(`HTTP ${String(res.status)}`);
@@ -229,7 +245,7 @@ export async function loadRegistry(region = activeRegion()) {
229
245
  served = {
230
246
  key, etag: res.headers.get('etag'), modified: res.headers.get('last-modified'), version: null, data,
231
247
  };
232
- return data;
248
+ return servedBy(data);
233
249
  }
234
250
  catch (error) {
235
251
  last = error;
@@ -71,6 +71,26 @@ export interface RegionRoutes {
71
71
  */
72
72
  catalog: CatalogSource[];
73
73
  }
74
+ /**
75
+ * Every catalog source worth trying for a region: its own list, plus the
76
+ * catalog's published package on that region's own registry when the list
77
+ * does not already name it.
78
+ *
79
+ * The catalog hangs off a SECOND, independently blockable host (#891), and
80
+ * the probe that picks a region measures only the npm registry. A network
81
+ * where npm answers fast and GitHub Pages is blocked therefore measures
82
+ * `global` — whose one source is that very host — and since the region is
83
+ * decided once and persisted, nothing ever reconsiders: "catalog fetch
84
+ * failed" forever, on a machine whose registry is serving the catalog as a
85
+ * package all along.
86
+ *
87
+ * Appending the package closes that blind spot without asking the probe to
88
+ * learn about the second host, without revisiting the decision, and without
89
+ * sending anyone to another region's mirror: it rides the registry the probe
90
+ * already proved reachable. Last, not first — for everyone the official
91
+ * domain serves, this stays a source that is never contacted.
92
+ */
93
+ export declare function catalogSourcesFor(region: Region): CatalogSource[];
74
94
  /**
75
95
  * Normalize a user-maintained prefix, or reject it.
76
96
  *
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dshmarket",
3
3
  "description": "Visual plugin market inside DeepSeek Harness — browse, search, and one-click install community plugins. · DSH 可视化插件市场:逛一逛,点一下,装好。",
4
- "version": "1.66.31",
4
+ "version": "1.66.32",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "types": "lib/types/index.d.ts",
package/src/regions.ts CHANGED
@@ -157,6 +157,34 @@ const ROUTES: Record<Region, RegionRoutes> = {
157
157
  },
158
158
  }
159
159
 
160
+ /**
161
+ * Every catalog source worth trying for a region: its own list, plus the
162
+ * catalog's published package on that region's own registry when the list
163
+ * does not already name it.
164
+ *
165
+ * The catalog hangs off a SECOND, independently blockable host (#891), and
166
+ * the probe that picks a region measures only the npm registry. A network
167
+ * where npm answers fast and GitHub Pages is blocked therefore measures
168
+ * `global` — whose one source is that very host — and since the region is
169
+ * decided once and persisted, nothing ever reconsiders: "catalog fetch
170
+ * failed" forever, on a machine whose registry is serving the catalog as a
171
+ * package all along.
172
+ *
173
+ * Appending the package closes that blind spot without asking the probe to
174
+ * learn about the second host, without revisiting the decision, and without
175
+ * sending anyone to another region's mirror: it rides the registry the probe
176
+ * already proved reachable. Last, not first — for everyone the official
177
+ * domain serves, this stays a source that is never contacted.
178
+ */
179
+ export function catalogSourcesFor(region: Region): CatalogSource[] {
180
+ const routes = routesFor(region)
181
+ const sources = [...routes.catalog]
182
+ const served = sources.some(source => source.kind === 'npm'
183
+ && source.registry === routes.npmRegistry && source.pkg === CATALOG_PACKAGE)
184
+ if (!served) sources.push({ kind: 'npm', registry: routes.npmRegistry, pkg: CATALOG_PACKAGE })
185
+ return sources
186
+ }
187
+
160
188
  /** Read an environment override, treating blank as unset. */
161
189
  function override(env: NodeJS.ProcessEnv, name: string): string | null {
162
190
  const raw = env[name]
package/src/registry.ts CHANGED
@@ -4,9 +4,10 @@
4
4
  * behind it any more.
5
5
  */
6
6
 
7
+ import { logEvent } from './log.ts'
7
8
  import { configuredProxy, marketFetch } from './net.ts'
8
9
  import { catalogFromPackage } from './catalog-npm.ts'
9
- import { activeRegion, routesFor, type CatalogSource, type Region } from './regions.ts'
10
+ import { activeRegion, catalogSourcesFor, routesFor, type CatalogSource, type Region } from './regions.ts'
10
11
 
11
12
  export interface RegistryPlugin {
12
13
  name: string
@@ -245,8 +246,23 @@ export async function loadRegistry(region: Region = activeRegion()): Promise<Reg
245
246
  // the FIRST request the market makes, so a mirror that has gone down must
246
247
  // mean a slow market rather than an empty one — the list ends at the
247
248
  // address that has always worked.
248
- for (const source of routesFor(region).catalog) {
249
+ //
250
+ // The list runs one entry past the region's own sources on purpose (#891):
251
+ // the region probe measures the npm registry, so it cannot see a network
252
+ // where npm is fast and the catalog's host is blocked — and the region is
253
+ // decided once and persisted. The catalog's published package, on the
254
+ // registry the probe just proved reachable, is the repair that needs
255
+ // neither the probe nor the decision to change.
256
+ const ownSources = routesFor(region).catalog.length
257
+ const catalogSources = catalogSourcesFor(region)
258
+ for (const [sourceIndex, source] of catalogSources.entries()) {
249
259
  const key = sourceKey(source)
260
+ const servedBy = <T>(data: T): T => {
261
+ if (sourceIndex >= ownSources) {
262
+ logEvent('warn', 'registry', `catalog: the ${region} sources did not answer; read the catalog package from ${key} instead (#891)`)
263
+ }
264
+ return data
265
+ }
250
266
  // Two attempts each. A catalog fetch crossing a long, lossy path fails
251
267
  // transiently often enough that one retry is worth more than the second
252
268
  // or two it costs — and with nothing behind this call any more, a
@@ -263,11 +279,11 @@ export async function loadRegistry(region: Region = activeRegion()): Promise<Reg
263
279
  source.registry, source.pkg, reusable?.version ?? undefined,
264
280
  )
265
281
  // `data === null` means the published version is the one in hand.
266
- if (data === null && reusable !== null) return reusable.data
282
+ if (data === null && reusable !== null) return servedBy(reusable.data)
267
283
  if (data === null) throw new Error('the catalog package reported no change with nothing to reuse')
268
284
  const parsed = asRegistry(data)
269
285
  served = { key, etag: null, modified: null, version, data: parsed }
270
- return parsed
286
+ return servedBy(parsed)
271
287
  }
272
288
  // ETag first: it is exact, while a date has one-second resolution and
273
289
  // a catalog republished twice within the same second would validate
@@ -300,7 +316,7 @@ export async function loadRegistry(region: Region = activeRegion()): Promise<Reg
300
316
  // Guarded anyway: answering a 304 with nothing to reuse would
301
317
  // otherwise surface as a confusing parse error on an empty body.
302
318
  if (reusable === null) throw new Error('the catalog answered "not modified" with nothing to revalidate')
303
- return reusable.data
319
+ return servedBy(reusable.data)
304
320
  }
305
321
  if (!res.ok) throw new Error(`HTTP ${String(res.status)}`)
306
322
  const data = asRegistry(JSON.parse(new TextDecoder().decode(
@@ -309,7 +325,7 @@ export async function loadRegistry(region: Region = activeRegion()): Promise<Reg
309
325
  served = {
310
326
  key, etag: res.headers.get('etag'), modified: res.headers.get('last-modified'), version: null, data,
311
327
  }
312
- return data
328
+ return servedBy(data)
313
329
  } catch (error) {
314
330
  last = error
315
331
  failures.push({