@1e0zj/dsh-plugin-mall 0.4.2 → 0.4.3

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": "@1e0zj/dsh-plugin-mall",
3
- "version": "0.4.2",
3
+ "version": "0.4.3",
4
4
  "description": "dsh 插件市场:搜索 GitHub dsh-plugin 话题下的插件仓库,一键安装到本地 dsh profile(agent 工具 + 设置页插件市场 tab)",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
package/src/client.js CHANGED
@@ -499,10 +499,10 @@ window.__ModuleLoader__.load({
499
499
  busy: props.approving === job.spec,
500
500
  onApprove: function (names) {
501
501
  // 不先 drop:旧条目由重试任务的 track(carryFromId) 原子接管
502
- // (撤条目 + 日志接续一拍完成)。先删的话,call("install")
503
- // 要走数秒(重试还会重跑一次隔离预检),面板会空白一段,
504
- // 「批准后任务消失、开始安装才冒出来」的割裂就是这么来的。
505
- // 等待期间按钮由 approving 态显示「继续中…」。
502
+ // (撤条目 + 日志接续一拍完成)。install RPC 现在只做本地校验、
503
+ // 立刻返回新 job id,但保留这个次序仍然是它最稳的形态——
504
+ // 万一 RPC 失败,旧条目和日志还在。等待期间按钮由 approving
505
+ // 态显示「继续中…」。
506
506
  props.onApprove(job.spec, names, job.approvalToken, id);
507
507
  },
508
508
  onDismiss: function () { props.onDismiss(id); },
@@ -1056,7 +1056,10 @@ window.__ModuleLoader__.load({
1056
1056
  var spec = preflight.spec;
1057
1057
  var carry = preflight.jobId;
1058
1058
  setPreflight(null);
1059
- doRawInstall(spec, { acceptWarnings: true }, carry);
1059
+ // consentDigest 来自预检 job extras,装的时候与当前报告比对:
1060
+ // 用户点「继续」到安装真正开跑之间候选包或 profile 变了,
1061
+ // 布尔同意不得沿用,要重新看新的警告。
1062
+ doRawInstall(spec, { acceptWarnings: true, acceptedReportDigest: preflight.report.consentDigest }, carry);
1060
1063
  },
1061
1064
  onClose: function () {
1062
1065
  if (preflight && preflight.spec) delete approvalTokensRef.current[preflight.spec];
package/src/github.js CHANGED
@@ -173,16 +173,19 @@ function normalizeRegistry(registry) {
173
173
  * @param name - the npm package name.
174
174
  * @param options - `registry` defaults to npmjs; pass what pnpm installs from.
175
175
  * `signal` cancels the request (and, critically, keeps the cancellation out
176
- * of the cache — see below).
176
+ * of the cache — see below). `fresh` skips the read side of the cache: a
177
+ * cached "current version" is stale by construction, and the preflight
178
+ * staleness check exists precisely to compare against what the registry
179
+ * says NOW (the write side still populates the cache for other callers).
177
180
  * @returns `{latest, repositoryUrl, hostDeps}`, or null when unknown/unreachable.
178
181
  */
179
- export async function npmPackageInfo(name, { registry, signal } = {}) {
182
+ export async function npmPackageInfo(name, { registry, signal, fresh = false } = {}) {
180
183
  const clean = String(name ?? "").trim();
181
184
  if (clean.length === 0 || !/^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/i.test(clean)) return null;
182
185
  const base = normalizeRegistry(registry);
183
186
  const key = `${base}|${clean}`;
184
187
  const cached = npmCache.get(key);
185
- if (cached !== undefined && Date.now() - cached.at < NPM_CACHE_TTL) return cached.info;
188
+ if (fresh !== true && cached !== undefined && Date.now() - cached.at < NPM_CACHE_TTL) return cached.info;
186
189
  let info = null;
187
190
  try {
188
191
  // 单版本端点返回 latest 的完整 manifest(dependencies + repository 都在)。
@@ -215,6 +218,44 @@ export async function npmPackageInfo(name, { registry, signal } = {}) {
215
218
  return info;
216
219
  }
217
220
 
221
+ const npmVersionsCache = new Map();
222
+
223
+ /**
224
+ * All published version strings of a package, oldest to newest, for resolving
225
+ * a RANGE spec ("pkg@^1.2.0") to the version pnpm would actually pick today.
226
+ * `/latest` cannot answer that — a new release inside the range is invisible
227
+ * to it until it becomes latest. Fetches the full packument (one request),
228
+ * which is why this lives behind its own TTL like npmPackageInfo. Same
229
+ * cancellation rule: AbortError is rethrown, never cached as an empty answer.
230
+ * @returns string[], or null when unreachable/unknown.
231
+ */
232
+ export async function npmPackageVersions(name, { registry, signal, fresh = false } = {}) {
233
+ const clean = String(name ?? "").trim();
234
+ if (clean.length === 0 || !/^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/i.test(clean)) return null;
235
+ const base = normalizeRegistry(registry);
236
+ const key = `${base}|${clean}`;
237
+ const cached = npmVersionsCache.get(key);
238
+ if (fresh !== true && cached !== undefined && Date.now() - cached.at < NPM_CACHE_TTL) return cached.versions;
239
+ let versions = null;
240
+ try {
241
+ const response = await fetch(`${base}/${clean.replace("/", "%2F")}`, {
242
+ headers: { "User-Agent": "dsh-plugin-mall", Accept: "application/json" },
243
+ signal,
244
+ });
245
+ if (response.ok) {
246
+ const body = await response.json();
247
+ if (body?.versions !== null && typeof body?.versions === "object") {
248
+ versions = Object.keys(body.versions);
249
+ }
250
+ }
251
+ } catch (error) {
252
+ if (error?.name === "AbortError") throw error;
253
+ versions = null;
254
+ }
255
+ npmVersionsCache.set(key, { versions, at: Date.now() });
256
+ return versions;
257
+ }
258
+
218
259
  /**
219
260
  * Rewrite "github:owner/repo" (or "owner/repo") to the npm package name when
220
261
  * that package exists on npm AND its repository URL points back at the repo