@1e0zj/dsh-plugin-mall 0.1.4 → 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.4",
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
@@ -30,8 +30,6 @@ window.__ModuleLoader__.load({
30
30
  ".mkt_btnDanger:hover:not(:disabled){border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary)}",
31
31
  ".mkt_btnSm{padding:3px 10px;font-size:12px}",
32
32
  ".mkt_error{color:var(--dsw-alias-state-error-primary);font-size:12px;line-height:18px}",
33
- ".mkt_columns{display:grid;grid-template-columns:1fr 320px;gap:14px;align-items:start}",
34
- "@media (max-width:860px){.mkt_columns{grid-template-columns:1fr}}",
35
33
  ".mkt_list{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px;align-items:stretch;min-width:0}",
36
34
  "@media (max-width:820px){.mkt_list{grid-template-columns:minmax(0,1fr)}}",
37
35
  ".mkt_listHead{grid-column:1/-1;font-size:12px;color:var(--dsw-alias-label-tertiary)}",
@@ -41,7 +39,7 @@ window.__ModuleLoader__.load({
41
39
  ".mkt_meta{font-size:12px;color:var(--dsw-alias-label-tertiary)}",
42
40
  ".mkt_desc{font-size:12.5px;color:var(--dsw-alias-label-secondary);overflow-wrap:anywhere}",
43
41
  ".mkt_cardActions{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-top:auto;padding-top:4px}",
44
- ".mkt_rail{display:flex;flex-direction:column;gap:10px;min-width:0}",
42
+ ".mkt_cardActions .mkt_btn{text-decoration:none}",
45
43
  ".mkt_panelTitle{font-size:13px;font-weight:600;color:var(--dsw-alias-label-primary);margin:0}",
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)}",
@@ -49,8 +47,8 @@ window.__ModuleLoader__.load({
49
47
  ".mkt_link{color:var(--dsw-alias-state-business-primary);font-size:12px;text-decoration:none;cursor:pointer}",
50
48
  ".mkt_installedHead{display:flex;align-items:center;gap:8px;width:100%;background:none;border:0;padding:0;margin:0;cursor:pointer;font:inherit;text-align:left}",
51
49
  ".mkt_installedHead:hover .mkt_panelTitle{color:var(--dsw-alias-state-business-primary)}",
52
- ".mkt_depList{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:6px 14px;margin-top:8px}",
53
- "@media (max-width:820px){.mkt_depList{grid-template-columns:minmax(0,1fr)}}",
50
+ ".mkt_installedToggle{margin-left:auto}",
51
+ ".mkt_depList{display:flex;flex-direction:column;gap:6px;margin-top:8px}",
54
52
  ".mkt_depRow{display:flex;justify-content:space-between;align-items:center;gap:8px}",
55
53
  ].join("\n");
56
54
  var tagId = "@1e0zj/dsh-plugin-mall/market-tab.css";
@@ -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,43 +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" },
158
- h("p", { className: "mkt_panelTitle" }, "仓库详情"),
159
- info.loading ? h("div", { className: "mkt_meta" }, "加载中…")
160
- : info.error ? h("div", { className: "mkt_error" }, info.error)
161
- : !data ? null
162
- : h(React.Fragment, null,
163
- h("div", { className: "mkt_name" }, data.meta.fullName),
164
- h("div", { className: "mkt_desc" }, clip(data.meta.description, 240)),
165
- h("div", { className: "mkt_meta" },
166
- "★" + data.meta.stars + " · " + (data.meta.language || "-") + " · " + (data.meta.license || "-") + " · 分支 " + data.meta.defaultBranch),
167
- data.packageJson == null
168
- ? h("div", { className: "mkt_meta" }, "package.json: 未找到(可能不是 npm 插件)")
169
- : h(React.Fragment, null,
170
- h("div", { className: "mkt_meta" }, "package.json: " + (data.packageJson.name || "?") + "@" + (data.packageJson.version || "?")),
171
- data.packageJson.dshBundlePatch != null
172
- ? h("div", { className: "mkt_ok" }, "✓ dsh.bundle 宿主插件")
173
- : data.packageJson.dshClientPlatform != null
174
- ? h("div", { className: "mkt_ok" }, "✓ dsh.client 浏览器UI插件")
175
- : h("div", { className: "mkt_meta" }, "⚠ 无 bundle/client 声明,装上是普通依赖")),
176
- h("button", { className: "mkt_btn mkt_btnPrimary", onClick: function () { props.onInstall(data.meta.fullName); } }, "安装 github:" + data.meta.fullName),
177
- h("a", { className: "mkt_link", href: data.meta.htmlUrl, target: "_blank", rel: "noreferrer" }, "打开仓库 ↗")
178
- )
179
- );
180
- }
181
-
182
159
  // ── jobs panel ──────────────────────────────────────────────────────────
183
160
  function JobsPanel(props) {
184
161
  var ids = Object.keys(props.jobs);
@@ -220,7 +197,7 @@ window.__ModuleLoader__.load({
220
197
  },
221
198
  h("span", { className: "mkt_panelTitle" }, "已装插件"),
222
199
  h("span", { className: "mkt_badge" }, count + " 个"),
223
- h("span", { className: "mkt_meta" }, open ? "收起 ▲" : "展开 ▼")
200
+ h("span", { className: "mkt_meta mkt_installedToggle" }, open ? "收起 ▲" : "展开 ▼")
224
201
  ),
225
202
  installed.error
226
203
  ? h("div", { className: "mkt_error" }, installed.error)
@@ -259,9 +236,6 @@ window.__ModuleLoader__.load({
259
236
  var _error = useState(null);
260
237
  var error = _error[0];
261
238
  var setError = _error[1];
262
- var _info = useState(null);
263
- var info = _info[0];
264
- var setInfo = _info[1];
265
239
  var _installed = useState(null);
266
240
  var installed = _installed[0];
267
241
  var setInstalled = _installed[1];
@@ -291,15 +265,6 @@ window.__ModuleLoader__.load({
291
265
  });
292
266
  }, [call, query, sort]);
293
267
 
294
- var doInfo = useCallback(function (repo) {
295
- setInfo({ repo: repo, loading: true });
296
- call("info", { repo: repo }).then(function (value) {
297
- setInfo({ repo: repo, data: value });
298
- }).catch(function (e) {
299
- setInfo({ repo: repo, error: errorText(e) });
300
- });
301
- }, [call]);
302
-
303
268
  var refreshInstalled = useCallback(function () {
304
269
  call("installed", {}).then(function (value) {
305
270
  setInstalled(value);
@@ -361,6 +326,10 @@ window.__ModuleLoader__.load({
361
326
  refreshInstalled();
362
327
  }, [refreshInstalled]);
363
328
 
329
+ // 活动任务置顶(紧跟已装面板,点安装立刻看到进度);全部静止后归档到底部当日志。
330
+ var jobsActive = false;
331
+ for (var jid in jobs) { if (jobs[jid] && jobs[jid].status === "running") { jobsActive = true; break; } }
332
+
364
333
  return h("div", { className: "mkt_root" },
365
334
  h("div", { className: "mkt_head" },
366
335
  h("div", { className: "mkt_title" }, "插件市场"),
@@ -388,30 +357,30 @@ window.__ModuleLoader__.load({
388
357
  ),
389
358
  error ? h("div", { className: "mkt_error" }, error) : null,
390
359
  h(InstalledPanel, { installed: installed, removing: removing, onUninstall: doUninstall }),
391
- h("div", { className: "mkt_columns" },
392
- h("div", { className: "mkt_list" },
393
- results == null
394
- ? h("div", { className: "mkt_meta mkt_listHead" }, loading ? "正在加载最热插件…" : "—")
395
- : results.items.length === 0
396
- ? h("div", { className: "mkt_meta mkt_listHead" }, "没有匹配的仓库。")
397
- : h(React.Fragment, null,
398
- h("div", { className: "mkt_meta mkt_listHead" }, "共 " + results.total + " 个仓库(显示 " + results.items.length + " 个)"),
399
- results.items.map(function (item) {
400
- return h(RepoCard, {
401
- key: item.fullName,
402
- item: item,
403
- installing: installing[item.fullName] === true,
404
- onInstall: doInstall,
405
- onInfo: doInfo,
406
- });
407
- })
408
- )
409
- ),
410
- h("div", { className: "mkt_rail" },
411
- h(InfoPanel, { info: info, onInstall: doInstall }),
412
- h(JobsPanel, { jobs: jobs })
413
- )
414
- )
360
+ jobsActive ? h(JobsPanel, { jobs: jobs }) : null,
361
+ h("div", { className: "mkt_list" },
362
+ results == null
363
+ ? h("div", { className: "mkt_meta mkt_listHead" }, loading ? "正在加载最热插件…" : "—")
364
+ : results.items.length === 0
365
+ ? h("div", { className: "mkt_meta mkt_listHead" }, "没有匹配的仓库。")
366
+ : h(React.Fragment, null,
367
+ h("div", { className: "mkt_meta mkt_listHead" }, "共 " + results.total + " 个仓库(显示 " + results.items.length + " 个)"),
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
+ }
373
+ return h(RepoCard, {
374
+ key: item.fullName,
375
+ item: item,
376
+ installing: installing[item.fullName] === true,
377
+ installJob: installJob,
378
+ onInstall: doInstall,
379
+ });
380
+ })
381
+ )
382
+ ),
383
+ jobsActive ? null : h(JobsPanel, { jobs: jobs })
415
384
  );
416
385
  }
417
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
  }