@1e0zj/dsh-plugin-mall 0.1.6 → 0.1.7

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.1.6",
3
+ "version": "0.1.7",
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
@@ -39,10 +39,8 @@ window.__ModuleLoader__.load({
39
39
  ".mkt_meta{font-size:12px;color:var(--dsw-alias-label-tertiary)}",
40
40
  ".mkt_desc{font-size:12.5px;color:var(--dsw-alias-label-secondary);overflow-wrap:anywhere}",
41
41
  ".mkt_cardActions{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-top:auto;padding-top:4px}",
42
- ".mkt_infoCard{max-width:640px}",
42
+ ".mkt_cardActions .mkt_btn{text-decoration:none}",
43
43
  ".mkt_panelTitle{font-size:13px;font-weight:600;color:var(--dsw-alias-label-primary);margin:0}",
44
- ".mkt_panelRow{display:flex;align-items:center;gap:8px}",
45
- ".mkt_panelRow .mkt_link{margin-left:auto}",
46
44
  ".mkt_pre{font-family:Consolas,Monaco,monospace;font-size:11.5px;line-height:16px;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-secondary,#f6f7f8);border-radius:6px;padding:8px;max-height:220px;overflow:auto;white-space:pre-wrap;word-break:break-all}",
47
45
  ".mkt_ok{color:var(--dsw-alias-state-success-primary,#2f855a)}",
48
46
  ".mkt_badge{display:inline-block;border-radius:999px;padding:1px 8px;font-size:11px;border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-tertiary)}",
@@ -131,6 +129,15 @@ window.__ModuleLoader__.load({
131
129
  function RepoCard(props) {
132
130
  var item = props.item;
133
131
  var installing = props.installing === true;
132
+ var job = props.installJob;
133
+ var installLabel = "安装";
134
+ var installDisabled = installing;
135
+ if (!installing && job) {
136
+ if (job.status === "running") { installLabel = "安装中…"; installDisabled = true; }
137
+ else if (job.status === "completed") { installLabel = "✓ 已装 · 重启生效"; installDisabled = true; }
138
+ else if (job.status === "failed") { installLabel = "安装失败 · 见任务日志"; }
139
+ else { installLabel = "已取消 · 重试"; }
140
+ }
134
141
  return h("div", { className: "mkt_card" },
135
142
  h("div", { className: "mkt_cardHead" },
136
143
  h("span", { className: "mkt_name" }, item.fullName),
@@ -142,46 +149,13 @@ window.__ModuleLoader__.load({
142
149
  item.description ? h("div", { className: "mkt_desc" }, clip(item.description, 180)) : null,
143
150
  h("div", { className: "mkt_meta" }, "更新 " + (item.updatedAt || "").slice(0, 10)),
144
151
  h("div", { className: "mkt_cardActions" },
145
- h("button", { className: "mkt_btn mkt_btnPrimary", disabled: installing, onClick: function () { props.onInstall(item.fullName); } },
146
- installing ? "安装中…" : "安装"),
147
- h("button", { className: "mkt_btn", onClick: function () { props.onInfo(item.fullName); } }, "详情")
152
+ h("button", { className: "mkt_btn mkt_btnPrimary", disabled: installDisabled, onClick: function () { props.onInstall(item.fullName); } },
153
+ installLabel),
154
+ h("a", { className: "mkt_btn", href: item.htmlUrl, target: "_blank", rel: "noreferrer" }, "前往仓库 ↗")
148
155
  )
149
156
  );
150
157
  }
151
158
 
152
- // ── info panel ──────────────────────────────────────────────────────────
153
- function InfoPanel(props) {
154
- var info = props.info;
155
- if (!info) return null;
156
- var data = info.data;
157
- return h("div", { className: "mkt_card mkt_infoCard", ref: props.panelRef },
158
- h("div", { className: "mkt_panelRow" },
159
- h("p", { className: "mkt_panelTitle" }, "仓库详情"),
160
- props.onClose ? h("span", { className: "mkt_link", onClick: props.onClose }, "✕ 关闭") : null
161
- ),
162
- info.loading ? h("div", { className: "mkt_meta" }, "加载中…")
163
- : info.error ? h("div", { className: "mkt_error" }, info.error)
164
- : !data ? null
165
- : h(React.Fragment, null,
166
- h("div", { className: "mkt_name" }, data.meta.fullName),
167
- h("div", { className: "mkt_desc" }, clip(data.meta.description, 240)),
168
- h("div", { className: "mkt_meta" },
169
- "★" + data.meta.stars + " · " + (data.meta.language || "-") + " · " + (data.meta.license || "-") + " · 分支 " + data.meta.defaultBranch),
170
- data.packageJson == null
171
- ? h("div", { className: "mkt_meta" }, "package.json: 未找到(可能不是 npm 插件)")
172
- : h(React.Fragment, null,
173
- h("div", { className: "mkt_meta" }, "package.json: " + (data.packageJson.name || "?") + "@" + (data.packageJson.version || "?")),
174
- data.packageJson.dshBundlePatch != null
175
- ? h("div", { className: "mkt_ok" }, "✓ dsh.bundle 宿主插件")
176
- : data.packageJson.dshClientPlatform != null
177
- ? h("div", { className: "mkt_ok" }, "✓ dsh.client 浏览器UI插件")
178
- : h("div", { className: "mkt_meta" }, "⚠ 无 bundle/client 声明,装上是普通依赖")),
179
- h("button", { className: "mkt_btn mkt_btnPrimary", onClick: function () { props.onInstall(data.meta.fullName); } }, "安装 github:" + data.meta.fullName),
180
- h("a", { className: "mkt_link", href: data.meta.htmlUrl, target: "_blank", rel: "noreferrer" }, "打开仓库 ↗")
181
- )
182
- );
183
- }
184
-
185
159
  // ── jobs panel ──────────────────────────────────────────────────────────
186
160
  function JobsPanel(props) {
187
161
  var ids = Object.keys(props.jobs);
@@ -262,9 +236,6 @@ window.__ModuleLoader__.load({
262
236
  var _error = useState(null);
263
237
  var error = _error[0];
264
238
  var setError = _error[1];
265
- var _info = useState(null);
266
- var info = _info[0];
267
- var setInfo = _info[1];
268
239
  var _installed = useState(null);
269
240
  var installed = _installed[0];
270
241
  var setInstalled = _installed[1];
@@ -274,7 +245,6 @@ window.__ModuleLoader__.load({
274
245
  var _removing = useState({});
275
246
  var removing = _removing[0];
276
247
  var setRemoving = _removing[1];
277
- var infoRef = useRef(null);
278
248
 
279
249
  var call = useCallback(function (endpoint, payload) {
280
250
  return rpc.call("/market", endpoint, payload || {}).then(function (res) {
@@ -295,26 +265,6 @@ window.__ModuleLoader__.load({
295
265
  });
296
266
  }, [call, query, sort]);
297
267
 
298
- var doInfo = useCallback(function (repo) {
299
- setInfo({ repo: repo, loading: true });
300
- call("info", { repo: repo }).then(function (value) {
301
- setInfo({ repo: repo, data: value });
302
- }).catch(function (e) {
303
- setInfo({ repo: repo, error: errorText(e) });
304
- });
305
- }, [call]);
306
-
307
- var doCloseInfo = useCallback(function () {
308
- setInfo(null);
309
- }, []);
310
-
311
- // 详情面板在列表上方;点"详情"时平滑滚到面板,免得用户找不到。
312
- useEffect(function () {
313
- if (info && infoRef.current && typeof infoRef.current.scrollIntoView === "function") {
314
- infoRef.current.scrollIntoView({ behavior: "smooth", block: "nearest" });
315
- }
316
- }, [info]);
317
-
318
268
  var refreshInstalled = useCallback(function () {
319
269
  call("installed", {}).then(function (value) {
320
270
  setInstalled(value);
@@ -376,6 +326,10 @@ window.__ModuleLoader__.load({
376
326
  refreshInstalled();
377
327
  }, [refreshInstalled]);
378
328
 
329
+ // 活动任务置顶(紧跟已装面板,点安装立刻看到进度);全部静止后归档到底部当日志。
330
+ var jobsActive = false;
331
+ for (var jid in jobs) { if (jobs[jid] && jobs[jid].status === "running") { jobsActive = true; break; } }
332
+
379
333
  return h("div", { className: "mkt_root" },
380
334
  h("div", { className: "mkt_head" },
381
335
  h("div", { className: "mkt_title" }, "插件市场"),
@@ -403,7 +357,7 @@ window.__ModuleLoader__.load({
403
357
  ),
404
358
  error ? h("div", { className: "mkt_error" }, error) : null,
405
359
  h(InstalledPanel, { installed: installed, removing: removing, onUninstall: doUninstall }),
406
- h(InfoPanel, { info: info, onInstall: doInstall, onClose: doCloseInfo, panelRef: infoRef }),
360
+ jobsActive ? h(JobsPanel, { jobs: jobs }) : null,
407
361
  h("div", { className: "mkt_list" },
408
362
  results == null
409
363
  ? h("div", { className: "mkt_meta mkt_listHead" }, loading ? "正在加载最热插件…" : "—")
@@ -412,17 +366,21 @@ window.__ModuleLoader__.load({
412
366
  : h(React.Fragment, null,
413
367
  h("div", { className: "mkt_meta mkt_listHead" }, "共 " + results.total + " 个仓库(显示 " + results.items.length + " 个)"),
414
368
  results.items.map(function (item) {
369
+ var installJob = null;
370
+ for (var jobId in jobs) {
371
+ if (jobs[jobId] && jobs[jobId].spec === "github:" + item.fullName) { installJob = jobs[jobId]; break; }
372
+ }
415
373
  return h(RepoCard, {
416
374
  key: item.fullName,
417
375
  item: item,
418
376
  installing: installing[item.fullName] === true,
377
+ installJob: installJob,
419
378
  onInstall: doInstall,
420
- onInfo: doInfo,
421
379
  });
422
380
  })
423
381
  )
424
382
  ),
425
- h(JobsPanel, { jobs: jobs })
383
+ jobsActive ? null : h(JobsPanel, { jobs: jobs })
426
384
  );
427
385
  }
428
386
 
package/src/installer.js CHANGED
@@ -218,19 +218,26 @@ export function removeClientRow(profileDir, packageName) {
218
218
  // ── build-script allow-listing ──────────────────────────────────────────────
219
219
 
220
220
  /** Extract package names from pnpm's "Ignored build scripts: ..." output. */
221
+ /** Valid npm package name (scoped or bare) — anything else is not a name. */
222
+ const NPM_NAME_RE = /^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/i;
223
+
221
224
  function parseIgnoredBuilds(output) {
222
225
  const names = new Set();
223
- const pattern = /(?:Ignored build scripts|allowBuilds|onlyBuiltDependencies)\s*:\s*([^\n]+)/gi;
226
+ // Only pnpm's own notice line is a parsing source. "allowBuilds" also
227
+ // appears in pnpm's advice/error text (never followed by a name list), and
228
+ // matching it fed error echoes into the allow-list, corrupting the YAML.
229
+ const pattern = /(?:Ignored build scripts|onlyBuiltDependencies)\s*:\s*([^\n]+)/gi;
224
230
  let match;
225
231
  while ((match = pattern.exec(output)) !== null) {
226
232
  for (const raw of match[1].split(",")) {
227
- const trimmed = raw.trim();
228
- if (trimmed.length === 0) continue;
229
- // Strip a trailing @version so `foo@1.2.3` -> `foo`, `@s/n@1.0.0` -> `@s/n`.
230
- const name = trimmed.includes("@") && !trimmed.startsWith("@")
231
- ? trimmed.split("@").slice(0, -1).join("@")
232
- : trimmed.replace(/@[^@/]+$/, "");
233
- if (name.length > 0) names.add(name);
233
+ const candidate = raw.trim();
234
+ if (candidate.length === 0) continue;
235
+ // Strip a trailing @version or @tarball-url so `foo@1.2.3` -> `foo`,
236
+ // `@s/n@1.0.0` -> `@s/n`, `@s/n@https://…` -> `@s/n`. Whatever remains
237
+ // must be a valid npm name; pnpm error echoes ("9 | - pkg", advice
238
+ // sentences) are dropped instead of being written to the YAML.
239
+ const name = candidate.replace(/@(?:[\w.+-]+|https?:\/\/\S+|file:\S+|link:\S+|github:\S+)$/, "");
240
+ if (NPM_NAME_RE.test(name)) names.add(name);
234
241
  }
235
242
  }
236
243
  return [...names];
@@ -238,6 +245,8 @@ function parseIgnoredBuilds(output) {
238
245
 
239
246
  /** Merge new names into the profile's pnpm-workspace.yaml `allowBuilds` list. */
240
247
  function ensureAllowBuilds(profileDir, names) {
248
+ const valid = names.filter((name) => NPM_NAME_RE.test(name));
249
+ if (valid.length === 0) return;
241
250
  const workspacePath = join(profileDir, "pnpm-workspace.yaml");
242
251
  let content = existsSync(workspacePath)
243
252
  ? readFileSync(workspacePath, "utf8")
@@ -246,22 +255,24 @@ function ensureAllowBuilds(profileDir, names) {
246
255
  const keyIndex = lines.findIndex((line) => /^allowBuilds\s*:/.test(line));
247
256
  if (keyIndex === -1) {
248
257
  if (!content.endsWith("\n")) content += "\n";
249
- content += `\nallowBuilds:\n${names.map((name) => ` - ${name}`).join("\n")}\n`;
258
+ // Quote every entry: a bare `@scope/name` starts with YAML's reserved
259
+ // `@` indicator and fails to parse.
260
+ content += `\nallowBuilds:\n${valid.map((name) => ` - '${name}'`).join("\n")}\n`;
250
261
  } else {
251
262
  const existing = new Set();
252
263
  let insertIndex = keyIndex + 1;
253
264
  for (let index = keyIndex + 1; index < lines.length; index++) {
254
265
  const item = /^\s*-\s+(.+?)\s*$/.exec(lines[index]);
255
266
  if (item) {
256
- existing.add(item[1]);
267
+ existing.add(item[1].replace(/^['"](.*)['"]$/, "$1"));
257
268
  insertIndex = index + 1;
258
269
  continue;
259
270
  }
260
271
  if (/^\S/.test(lines[index])) break;
261
272
  }
262
- const additions = names.filter((name) => !existing.has(name));
273
+ const additions = valid.filter((name) => !existing.has(name));
263
274
  if (additions.length > 0) {
264
- lines.splice(insertIndex, 0, ...additions.map((name) => ` - ${name}`));
275
+ lines.splice(insertIndex, 0, ...additions.map((name) => ` - '${name}'`));
265
276
  content = lines.join("\n");
266
277
  }
267
278
  }