@1e0zj/dsh-plugin-mall 0.1.10 → 0.1.11
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 +1 -1
- package/src/client.js +22 -10
- package/src/github.js +46 -3
- package/src/index.js +9 -1
package/package.json
CHANGED
package/src/client.js
CHANGED
|
@@ -43,10 +43,13 @@ window.__ModuleLoader__.load({
|
|
|
43
43
|
".mkt_cardActions{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-top:auto;padding-top:4px}",
|
|
44
44
|
".mkt_cardActions .mkt_btn{text-decoration:none}",
|
|
45
45
|
".mkt_panelTitle{font-size:13px;font-weight:600;color:var(--dsw-alias-label-primary);margin:0}",
|
|
46
|
+
".mkt_panelRow{display:flex;align-items:center;gap:8px}",
|
|
47
|
+
".mkt_panelRow .mkt_link{margin-left:auto}",
|
|
46
48
|
".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
49
|
".mkt_ok{color:var(--dsw-alias-state-success-primary,#2f855a)}",
|
|
48
50
|
".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)}",
|
|
49
51
|
".mkt_badgeOk{border-color:var(--dsw-alias-state-success-primary,#2f855a);color:var(--dsw-alias-state-success-primary,#2f855a)}",
|
|
52
|
+
".mkt_badgeBad{border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary)}",
|
|
50
53
|
".mkt_check{display:flex;align-items:center;gap:4px;font-size:12.5px;color:var(--dsw-alias-label-secondary);cursor:pointer;white-space:nowrap}",
|
|
51
54
|
".mkt_link{color:var(--dsw-alias-state-business-primary);font-size:12px;text-decoration:none;cursor:pointer}",
|
|
52
55
|
".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}",
|
|
@@ -127,7 +130,11 @@ window.__ModuleLoader__.load({
|
|
|
127
130
|
jobsRef.current = Object.assign({}, jobsRef.current, { [id]: { status: "running", spec: spec, output: "" } });
|
|
128
131
|
setJobs(Object.assign({}, jobsRef.current));
|
|
129
132
|
}, []);
|
|
130
|
-
|
|
133
|
+
var clear = useCallback(function () {
|
|
134
|
+
jobsRef.current = {};
|
|
135
|
+
setJobs({});
|
|
136
|
+
}, []);
|
|
137
|
+
return { jobs: jobs, track: track, clear: clear };
|
|
131
138
|
}
|
|
132
139
|
|
|
133
140
|
// ── plugin verification badge ───────────────────────────────────────────
|
|
@@ -135,6 +142,9 @@ window.__ModuleLoader__.load({
|
|
|
135
142
|
// dsh.bundle/dsh.client 声明,进程内缓存)。unknown 不显示徽章。
|
|
136
143
|
function verifyBadge(verified) {
|
|
137
144
|
if (verified === undefined || verified === null) return null;
|
|
145
|
+
if (verified.hostDeps !== undefined && verified.hostDeps.length > 0) {
|
|
146
|
+
return h("span", { className: "mkt_badge mkt_badgeBad", title: verified.hostDeps.join(", ") }, "宿主依赖风险");
|
|
147
|
+
}
|
|
138
148
|
if (verified.kind === "bundle") return h("span", { className: "mkt_badge mkt_badgeOk" }, "宿主插件");
|
|
139
149
|
if (verified.kind === "client") return h("span", { className: "mkt_badge mkt_badgeOk" }, "UI插件");
|
|
140
150
|
if (verified.kind === "plain") return h("span", { className: "mkt_badge" }, "未声明");
|
|
@@ -181,7 +191,10 @@ window.__ModuleLoader__.load({
|
|
|
181
191
|
var ids = Object.keys(props.jobs);
|
|
182
192
|
if (ids.length === 0) return null;
|
|
183
193
|
return h("div", { className: "mkt_card" },
|
|
184
|
-
h("
|
|
194
|
+
h("div", { className: "mkt_panelRow" },
|
|
195
|
+
h("p", { className: "mkt_panelTitle" }, "任务"),
|
|
196
|
+
props.onClear ? h("span", { className: "mkt_link", onClick: props.onClear }, "清空") : null
|
|
197
|
+
),
|
|
185
198
|
ids.map(function (id) {
|
|
186
199
|
var job = props.jobs[id];
|
|
187
200
|
var done = job.status === "completed" || job.status === "failed" || job.status === "killed";
|
|
@@ -393,6 +406,7 @@ window.__ModuleLoader__.load({
|
|
|
393
406
|
var polling = useJobPolling(call, refreshInstalled);
|
|
394
407
|
var track = polling.track;
|
|
395
408
|
var jobs = polling.jobs;
|
|
409
|
+
var clearJobs = polling.clear;
|
|
396
410
|
|
|
397
411
|
useEffect(function () {
|
|
398
412
|
doSearch();
|
|
@@ -472,16 +486,15 @@ window.__ModuleLoader__.load({
|
|
|
472
486
|
refreshInstalled();
|
|
473
487
|
}, [refreshInstalled]);
|
|
474
488
|
|
|
475
|
-
//
|
|
476
|
-
|
|
477
|
-
for (var jid in jobs) { if (jobs[jid] && jobs[jid].status === "running") { jobsActive = true; break; } }
|
|
478
|
-
|
|
489
|
+
// 任务面板位置固定(已装面板之后):不随任务状态在顶部/底部之间
|
|
490
|
+
// 跳——npm 安装几秒即完成,"完成即落底"曾让结果凭空消失。
|
|
479
491
|
// "只看已验证"开关下的可见项:verify 判定 bundle/client 的才算插件。
|
|
480
492
|
// verifyPending:当前加载的仓库里还有未验证完的(verified map 尚未覆盖)。
|
|
481
493
|
var visibleItems = results === null ? [] : results.items.filter(function (it) {
|
|
482
494
|
if (verifiedOnly !== true) return true;
|
|
483
495
|
var v = verified[it.fullName];
|
|
484
|
-
return v !== undefined && (v.kind === "bundle" || v.kind === "client")
|
|
496
|
+
return v !== undefined && (v.kind === "bundle" || v.kind === "client")
|
|
497
|
+
&& !(v.hostDeps !== undefined && v.hostDeps.length > 0);
|
|
485
498
|
});
|
|
486
499
|
var verifyPending = results !== null && results.items.some(function (it) { return verified[it.fullName] === undefined; });
|
|
487
500
|
|
|
@@ -516,7 +529,7 @@ window.__ModuleLoader__.load({
|
|
|
516
529
|
),
|
|
517
530
|
error ? h("div", { className: "mkt_error" }, error) : null,
|
|
518
531
|
h(InstalledPanel, { installed: installed, removing: removing, updates: updates, onUninstall: doUninstall, onInstallSpec: doInstallSpec }),
|
|
519
|
-
|
|
532
|
+
h(JobsPanel, { jobs: jobs, onClear: clearJobs }),
|
|
520
533
|
h("div", { className: "mkt_list" },
|
|
521
534
|
results == null
|
|
522
535
|
? h("div", { className: "mkt_meta mkt_listHead" }, loading ? "正在加载最热插件…" : "—")
|
|
@@ -552,8 +565,7 @@ window.__ModuleLoader__.load({
|
|
|
552
565
|
? h("div", { className: "mkt_loadMore", ref: sentinelRef }, loadingMore ? "加载中…" : Date.now() < retryAt ? "GitHub 限流中,稍后再下滑加载" : "下滑加载更多")
|
|
553
566
|
: h("div", { className: "mkt_loadMore" }, reachedLimit ? "已达 GitHub 搜索上限(前 1000 个结果)" : "已显示全部 " + results.items.length + " 个")
|
|
554
567
|
)
|
|
555
|
-
)
|
|
556
|
-
jobsActive ? null : h(JobsPanel, { jobs: jobs })
|
|
568
|
+
)
|
|
557
569
|
);
|
|
558
570
|
}
|
|
559
571
|
|
package/src/github.js
CHANGED
|
@@ -137,7 +137,7 @@ export async function npmPackageInfo(name) {
|
|
|
137
137
|
if (typeof latest === "string") {
|
|
138
138
|
const rawRepository = body?.repository;
|
|
139
139
|
const repositoryUrl = typeof rawRepository === "string" ? rawRepository : rawRepository?.url;
|
|
140
|
-
info = { latest, repositoryUrl: typeof repositoryUrl === "string" ? repositoryUrl : undefined };
|
|
140
|
+
info = { latest, repositoryUrl: typeof repositoryUrl === "string" ? repositoryUrl : undefined, hostDeps: hostShadowDependencies(body) };
|
|
141
141
|
}
|
|
142
142
|
}
|
|
143
143
|
} catch {
|
|
@@ -166,6 +166,32 @@ export async function preferNpmSpec({ spec }) {
|
|
|
166
166
|
return pointsBack ? declaredName : raw;
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
+
/**
|
|
170
|
+
* Refuse to install a package whose `dependencies` shadow host framework
|
|
171
|
+
* packages. Host copies inside a profile split module identities and crash
|
|
172
|
+
* all tool scheduling — the dsh contract is peerDependencies, but the host
|
|
173
|
+
* enforces nothing, so the marketplace is the last line of defense.
|
|
174
|
+
* Sources: registry abbreviated metadata for npm names, the verify cache
|
|
175
|
+
* (raw package.json) for github: specs.
|
|
176
|
+
*/
|
|
177
|
+
export async function assertSafeToInstall({ spec }) {
|
|
178
|
+
const raw = String(spec ?? "");
|
|
179
|
+
let hostDeps;
|
|
180
|
+
if (/^(?:@[\w.-]+\/)?[\w.-]+$/.test(raw)) {
|
|
181
|
+
const info = await npmPackageInfo(raw);
|
|
182
|
+
hostDeps = info?.hostDeps;
|
|
183
|
+
} else {
|
|
184
|
+
const match = /^github:([^/\s]+\/[^/\s]+?)(?:\.git)?$/i.exec(raw);
|
|
185
|
+
if (match !== null) {
|
|
186
|
+
const { results } = await verifyPlugins({ repos: [match[1]] });
|
|
187
|
+
hostDeps = results[match[1]]?.hostDeps;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (hostDeps !== undefined && hostDeps.length > 0) {
|
|
191
|
+
throw new Error(`${raw} declares ${hostDeps.length} host framework package(s) as dependencies (${hostDeps.slice(0, 3).join(", ")}${hostDeps.length > 3 ? ", …" : ""}) — installing it would duplicate host modules and crash dsh tool scheduling. Ask the plugin author to move @deepseek-ai/* to peerDependencies.`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
169
195
|
/** Loose semver-ish comparison: "0.2.10" vs "0.2.9" → 1. Non-numeric parts read as 0. */
|
|
170
196
|
export function compareVersions(a, b) {
|
|
171
197
|
const pa = String(a ?? "").split(".");
|
|
@@ -222,10 +248,27 @@ async function fetchRawPackageJson(repo, signal) {
|
|
|
222
248
|
throw lastError ?? new Error("no raw source reachable");
|
|
223
249
|
}
|
|
224
250
|
|
|
251
|
+
/**
|
|
252
|
+
* Framework packages the host provides via the profiles/node_modules fallback.
|
|
253
|
+
* A plugin declaring any of these as `dependencies` (instead of
|
|
254
|
+
* peerDependencies) installs real copies into the profile — the loader then
|
|
255
|
+
* holds two module instances, symbol identities split, and every tool call
|
|
256
|
+
* crashes with an undiagnosable "reading 'prepare'". See installer docs.
|
|
257
|
+
*/
|
|
258
|
+
const HOST_PACKAGES = /^(@deepseek-ai\/|cosmokit$|schemastery$)/;
|
|
259
|
+
|
|
260
|
+
/** The @deepseek-ai/* (and cordis runtime) entries inside `dependencies`. */
|
|
261
|
+
function hostShadowDependencies(pkg) {
|
|
262
|
+
if (pkg === undefined || typeof pkg !== "object") return undefined;
|
|
263
|
+
const deps = Object.keys(pkg.dependencies ?? {});
|
|
264
|
+
const host = deps.filter((name) => HOST_PACKAGES.test(name));
|
|
265
|
+
return host.length > 0 ? host : undefined;
|
|
266
|
+
}
|
|
267
|
+
|
|
225
268
|
/**
|
|
226
269
|
* Verify repositories as real dsh plugins by their package.json declaration.
|
|
227
270
|
* @param {{repos: string[], signal?: AbortSignal}} options - "owner/name" list.
|
|
228
|
-
* @returns the {results} map: fullName -> {kind: "bundle"|"client"|"plain"|"no-manifest"|"unknown", name?, version?}.
|
|
271
|
+
* @returns the {results} map: fullName -> {kind: "bundle"|"client"|"plain"|"no-manifest"|"unknown", name?, version?, hostDeps?}.
|
|
229
272
|
*/
|
|
230
273
|
export async function verifyPlugins({ repos, signal }) {
|
|
231
274
|
const wanted = [...new Set((Array.isArray(repos) ? repos : []).map(String)
|
|
@@ -241,7 +284,7 @@ export async function verifyPlugins({ repos, signal }) {
|
|
|
241
284
|
: typeof pkg.dsh?.bundle?.patch === "string" ? "bundle"
|
|
242
285
|
: pkg.dsh?.client !== undefined ? "client"
|
|
243
286
|
: "plain";
|
|
244
|
-
verifyCache.set(repo, { kind, name: pkg?.name, version: pkg?.version });
|
|
287
|
+
verifyCache.set(repo, { kind, name: pkg?.name, version: pkg?.version, hostDeps: hostShadowDependencies(pkg) });
|
|
245
288
|
} catch (error) {
|
|
246
289
|
if (error?.name === "AbortError") throw error;
|
|
247
290
|
verifyCache.set(repo, { kind: "unknown" });
|
package/src/index.js
CHANGED
|
@@ -19,7 +19,7 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
19
19
|
import { join } from "node:path";
|
|
20
20
|
import { spawn } from "node:child_process";
|
|
21
21
|
import { resolveProfileDir } from "@deepseek-ai/dsh-app-boot";
|
|
22
|
-
import { repoInfo, searchPlugins, verifyPlugins, preferNpmSpec, npmPackageInfo, compareVersions } from "./github.js";
|
|
22
|
+
import { repoInfo, searchPlugins, verifyPlugins, preferNpmSpec, npmPackageInfo, compareVersions, assertSafeToInstall } from "./github.js";
|
|
23
23
|
import { ensureProfile, listInstalled, normalizeSpec, runInstall, runRemove, createJobTracker } from "./installer.js";
|
|
24
24
|
|
|
25
25
|
export const name = "@1e0zj/dsh-plugin-mall";
|
|
@@ -212,6 +212,13 @@ async function rpcDispatch(ctx, endpoint, payload, config, token, tracker) {
|
|
|
212
212
|
// npm tarball 优先(小而快、带 integrity);registry 条目不同源的包名
|
|
213
213
|
// 视为抢注,回退 github: 全仓库 spec。
|
|
214
214
|
spec = await preferNpmSpec({ spec });
|
|
215
|
+
// 宿主依赖硬拦:dependencies 里拖着 @deepseek-ai/* 的包装进 profile
|
|
216
|
+
// 就是双模块实例 + 全工具调度崩溃(宿主无任何护栏,市场是最后防线)。
|
|
217
|
+
try {
|
|
218
|
+
await assertSafeToInstall({ spec });
|
|
219
|
+
} catch (error) {
|
|
220
|
+
return rpcFail(error);
|
|
221
|
+
}
|
|
215
222
|
try {
|
|
216
223
|
const profileDir = resolveProfileDir(profile);
|
|
217
224
|
if (!existsSync(join(profileDir, "package.json"))) ensureProfile(profile);
|
|
@@ -419,6 +426,7 @@ export function apply(ctx, config = {}) {
|
|
|
419
426
|
async execute(args, exec) {
|
|
420
427
|
const profile = String(args.profile ?? defaultProfile).trim();
|
|
421
428
|
const spec = await preferNpmSpec({ spec: normalizeSpec(args.spec) });
|
|
429
|
+
await assertSafeToInstall({ spec });
|
|
422
430
|
let profileDir;
|
|
423
431
|
try {
|
|
424
432
|
profileDir = resolveProfileDir(profile);
|