@klarkxy/dsh-dev-index 0.2.5 → 0.2.6

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/README.md CHANGED
@@ -25,6 +25,7 @@ Replace `web` with your profile name and restart the profile.
25
25
  - The website follows the latest DSH release, which may differ from the version you run.
26
26
  - npm metadata is not proof that a plugin works with your DSH.
27
27
  - GitHub data comes from your local signed-in gh CLI or anonymous API access (a small hourly quota); the plugin stores no GitHub credentials. Manifest-based code search is only available when gh is signed in.
28
+ - Cancelling the last request using a GitHub CLI check waits for that check to stop. Unloading the plugin waits for its active GitHub requests and CLI processes to finish.
28
29
 
29
30
  ## Install from GitHub
30
31
 
package/README.zh-CN.md CHANGED
@@ -25,6 +25,7 @@ dsh plugin --profile web add @klarkxy/dsh-dev-index
25
25
  - 官网对应最新发布的 DSH,可能与你正在使用的版本不同。
26
26
  - npm 元数据不能证明插件与你的 DSH 兼容。
27
27
  - GitHub 数据来自本机已登录的 gh CLI 或匿名 API(匿名额度有限,约每小时几十次请求);插件不保存任何 GitHub 凭据。按 `dsh.plugin.json` 清单的代码搜索只有在 gh 已登录时可用。
28
+ - 取消最后一个使用 GitHub CLI 检查的请求时,会等待检查停止;卸载插件会等待正在进行的 GitHub 请求和 CLI 进程结束。
28
29
 
29
30
  ## 从 GitHub 安装
30
31
 
package/dsh.plugin.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "@klarkxy/dsh-dev-index",
3
- "version": "0.2.5",
3
+ "version": "0.2.6",
4
4
  "main": "lib/index.js",
5
5
  "description": "DSH Docs: read the official DSH documentation in the Plugins page; Creator mode searches docs and checks plugin metadata on npm and GitHub with native tools.",
6
6
  "engines": {
package/lib/gh.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { access } from "node:fs/promises";
2
2
  import { tmpdir } from "node:os";
3
3
  import { delimiter, join } from "node:path";
4
- import { runCli } from "@klarkxy/dsh-plugin-kit/cli-process";
4
+ import { cliEnvironment, runCli } from "@klarkxy/dsh-plugin-kit/cli-process";
5
5
  // gh api and gh auth status are long-stable subcommands; the floor only rejects
6
6
  // truly ancient binaries.
7
7
  export const MIN_GH_VERSION = [2, 0, 0];
@@ -28,7 +28,7 @@ export async function findGh(deps = {}) {
28
28
  const candidate = join(dir, file);
29
29
  try {
30
30
  await access(candidate);
31
- return { command: candidate, displayPath: candidate };
31
+ return { command: candidate, displayPath: candidate, env: cliEnvironment(process.env, ["GH_TOKEN", "GITHUB_TOKEN", "GH_ENTERPRISE_TOKEN", "GITHUB_ENTERPRISE_TOKEN", "GH_HOST", "GH_CONFIG_DIR"]) };
32
32
  }
33
33
  catch { /* keep looking */ }
34
34
  }
package/lib/github.d.ts CHANGED
@@ -37,7 +37,9 @@ export declare class GitHubClient {
37
37
  private readonly run;
38
38
  private readonly discoverProbe;
39
39
  constructor(deps?: GitHubDeps);
40
- dispose(): void;
40
+ private readonly work;
41
+ private track;
42
+ dispose(): Promise<void>;
41
43
  private abandonProbe;
42
44
  private ghBroken;
43
45
  private merged;
package/lib/github.js CHANGED
@@ -262,9 +262,19 @@ export class GitHubClient {
262
262
  this.run = deps.run ?? runCli;
263
263
  this.discoverProbe = deps.discover ?? ((signal) => probeGh({ run: this.run }, signal));
264
264
  }
265
- dispose() {
265
+ work = new Set();
266
+ track(work) {
267
+ this.work.add(work);
268
+ void work.then(() => this.work.delete(work), () => this.work.delete(work));
269
+ return work;
270
+ }
271
+ async dispose() {
266
272
  this.lifetime.abort();
273
+ this.confirmedProbe = undefined;
267
274
  this.abandonProbe(this.pendingProbe);
275
+ // Includes abandoned probes: their caller may already have stopped waiting.
276
+ while (this.work.size)
277
+ await Promise.allSettled([...this.work]);
268
278
  }
269
279
  // Detach and cancel a probe nobody wants any more, releasing its listener. Its
270
280
  // late resolution is rejected by the publish gate in startProbe.
@@ -288,8 +298,8 @@ export class GitHubClient {
288
298
  merged(signal) {
289
299
  return AbortSignal.any([this.lifetime.signal, AbortSignal.timeout(REQUEST_TIMEOUT_MS), ...(signal ? [signal] : [])]);
290
300
  }
291
- // Resolve gh for one caller: join the shared probe, but reject as soon as this
292
- // caller's own signal or the client lifetime aborts. An already-aborted caller
301
+ // Resolve gh for one caller: a peer can keep the shared probe alive, while
302
+ // the final cancelled waiter must await cleanup. An already-aborted caller
293
303
  // never spawns discovery. The throw runs inside the promise so no caller can
294
304
  // observe a partially started probe.
295
305
  probe(signal) {
@@ -298,8 +308,9 @@ export class GitHubClient {
298
308
  if (this.confirmedProbe)
299
309
  return this.confirmedProbe;
300
310
  const pending = this.pendingProbe ?? this.startProbe();
301
- pending.waiters += 1;
302
- return this.awaitProbe(pending, signal);
311
+ const waiter = { signal };
312
+ pending.waiters.add(waiter);
313
+ return this.awaitProbe(pending, waiter);
303
314
  });
304
315
  }
305
316
  startProbe() {
@@ -309,7 +320,7 @@ export class GitHubClient {
309
320
  // probes do not accumulate listeners on the lifetime signal.
310
321
  this.lifetime.signal.addEventListener("abort", onLifetimeAbort, { once: true });
311
322
  const probe = {
312
- controller, waiters: 0, promise: Promise.resolve(null), onLifetimeAbort,
323
+ controller, waiters: new Set(), promise: Promise.resolve(null), onLifetimeAbort,
313
324
  };
314
325
  // Discovery runs inside the promise chain so a synchronous throw is folded
315
326
  // into the same catch/finally: the listener is always released, pendingProbe
@@ -322,7 +333,8 @@ export class GitHubClient {
322
333
  .then((found) => {
323
334
  // Publish only from the current, live probe: an abandoned, cancelled or
324
335
  // disposed probe may still resolve late and must not overwrite the cache.
325
- if (found && this.pendingProbe === probe && !controller.signal.aborted && !this.lifetime.signal.aborted) {
336
+ if (found && this.pendingProbe === probe && !controller.signal.aborted && !this.lifetime.signal.aborted
337
+ && [...probe.waiters].some(waiter => !waiter.signal.aborted)) {
326
338
  this.confirmedProbe = found;
327
339
  }
328
340
  return found;
@@ -334,12 +346,14 @@ export class GitHubClient {
334
346
  this.pendingProbe = undefined;
335
347
  });
336
348
  this.pendingProbe = probe;
349
+ this.track(probe.promise);
337
350
  return probe;
338
351
  }
339
352
  // Race the shared probe against this caller's cancellation. A null result falls
340
- // through to anonymous access as before. Leaving early decrements the waiter
341
- // count; the last one out abandons the probe, while a peer keeps it alive.
342
- async awaitProbe(probe, signal) {
353
+ // through to anonymous access as before. The last cancelled waiter abandons
354
+ // and drains the probe, while a peer keeps it alive.
355
+ async awaitProbe(probe, waiter) {
356
+ const { signal } = waiter;
343
357
  let onAbort;
344
358
  let settled = false;
345
359
  try {
@@ -357,9 +371,11 @@ export class GitHubClient {
357
371
  finally {
358
372
  if (onAbort)
359
373
  signal.removeEventListener("abort", onAbort);
360
- probe.waiters -= 1;
361
- if (!settled && probe.waiters <= 0)
374
+ probe.waiters.delete(waiter);
375
+ if (!settled && probe.waiters.size === 0) {
362
376
  this.abandonProbe(probe);
377
+ await probe.promise;
378
+ }
363
379
  }
364
380
  }
365
381
  async request(path, signal) {
@@ -369,14 +385,14 @@ export class GitHubClient {
369
385
  merged.throwIfAborted();
370
386
  if (probe) {
371
387
  try {
372
- return { data: await this.viaGh(probe.executable, path, signal), transport: "gh" };
388
+ return { data: await this.track(this.viaGh(probe.executable, path, signal)), transport: "gh" };
373
389
  }
374
390
  catch (error) {
375
391
  if (!this.ghBroken(error))
376
392
  throw error;
377
393
  }
378
394
  }
379
- return { data: await this.viaHttp(path, signal), transport: "anonymous" };
395
+ return { data: await this.track(this.viaHttp(path, signal)), transport: "anonymous" };
380
396
  }
381
397
  async viaGh(executable, path, signal) {
382
398
  const merged = this.merged(signal);
@@ -393,6 +409,7 @@ export class GitHubClient {
393
409
  }
394
410
  throw error;
395
411
  }
412
+ merged.throwIfAborted();
396
413
  if (result.exitCode !== 0)
397
414
  throw classifyGhFailure(result.stderr);
398
415
  try {
@@ -521,7 +538,7 @@ export class GitHubClient {
521
538
  const path = `search/code?q=${encodeURIComponent(query)}&per_page=${PER_PAGE}&page=${page}`;
522
539
  let data;
523
540
  try {
524
- data = await this.viaGh(probe.executable, path, signal);
541
+ data = await this.track(this.viaGh(probe.executable, path, signal));
525
542
  }
526
543
  catch (error) {
527
544
  this.ghBroken(error); // code search has no anonymous lane, but the next call re-probes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@klarkxy/dsh-dev-index",
3
- "version": "0.2.5",
3
+ "version": "0.2.6",
4
4
  "description": "DSH Docs: read the official DSH documentation in the Plugins page; Creator mode searches docs and checks plugin metadata on npm and GitHub with native tools.",
5
5
  "icon": "./icon.svg",
6
6
  "keywords": [
@@ -24,7 +24,7 @@
24
24
  "url": "https://github.com/klarkxy"
25
25
  },
26
26
  "type": "module",
27
- "packageManager": "pnpm@10.29.2",
27
+ "packageManager": "pnpm@11.28.5",
28
28
  "main": "lib/index.js",
29
29
  "types": "lib/index.d.ts",
30
30
  "exports": {
@@ -80,7 +80,7 @@
80
80
  "@deepseek-ai/dsh-tools": ">=0.2.0-rc.2"
81
81
  },
82
82
  "dependencies": {
83
- "@klarkxy/dsh-plugin-kit": "0.3.4"
83
+ "@klarkxy/dsh-plugin-kit": "0.4.0"
84
84
  },
85
85
  "peerDependenciesMeta": {
86
86
  "@deepseek-ai/dsh-agent-preset-registry": {
@@ -91,22 +91,28 @@
91
91
  },
92
92
  "@deepseek-ai/dsh-tools": {
93
93
  "optional": true
94
+ },
95
+ "@deepseek-ai/cordis": {
96
+ "optional": true
97
+ },
98
+ "@deepseek-ai/schemastery": {
99
+ "optional": true
94
100
  }
95
101
  },
96
102
  "devDependencies": {
97
- "@deepseek-ai/cordis": "4.0.4",
98
- "@deepseek-ai/schemastery": "3.18.4",
99
- "@deepseek-ai/dsh-client-ui-primitives": "0.2.0-rc.2",
100
- "@deepseek-ai/dsh-agent-preset-registry": "0.2.0-rc.2",
101
- "@deepseek-ai/dsh-system-prompt": "0.2.0-rc.2",
102
- "@deepseek-ai/dsh-tools": "0.2.0-rc.2",
103
- "@deepseek-ai/dsh-scope": "0.2.0-rc.2"
103
+ "@deepseek-ai/cordis": "4.0.5",
104
+ "@deepseek-ai/schemastery": "3.18.5",
105
+ "@deepseek-ai/dsh-client-ui-primitives": "0.2.1-rc.1",
106
+ "@deepseek-ai/dsh-agent-preset-registry": "0.2.1-rc.1",
107
+ "@deepseek-ai/dsh-system-prompt": "0.2.1-rc.1",
108
+ "@deepseek-ai/dsh-tools": "0.2.1-rc.1",
109
+ "@deepseek-ai/dsh-scope": "0.2.1-rc.1"
104
110
  },
105
111
  "publishConfig": {
106
112
  "access": "public",
107
113
  "registry": "https://registry.npmjs.org/"
108
114
  },
109
115
  "dshRelease": {
110
- "contentHash": "293f3ffa69fb67d90c06419890c9fe2f0dbada1df9fc818cc2d3a2be9ab24485"
116
+ "contentHash": "377d5037bbd8893b148874afb94991f8b493eeeb7b00a60c4ea19fbe4a5a1926"
111
117
  }
112
118
  }