@dsh-workbench/core 1.1.5 → 1.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/client.js +714 -520
- package/dist/client/client.body.js +714 -520
- package/dist/client/index.js +585 -391
- package/dist/host/index.js +732 -168
- package/dist/types/app/layouts/market/market-check.d.ts +7 -0
- package/dist/types/app/layouts/market/market-header.d.ts +3 -1
- package/dist/types/app/layouts/market/section-check.d.ts +12 -0
- package/dist/types/app/layouts/market/use-market-reload.d.ts +14 -0
- package/dist/types/app/layouts/market/use-module-versions.d.ts +20 -4
- package/dist/types/app/layouts/rightbar/FailureCard.d.ts +3 -3
- package/dist/types/app/layouts/rightbar/MarketView.d.ts +5 -3
- package/dist/types/app/layouts/rightbar/failure-card-text.d.ts +6 -0
- package/dist/types/app/layouts/rightbar/market-row-types.d.ts +2 -0
- package/dist/types/app/layouts/rightbar/market-rows.d.ts +2 -0
- package/dist/types/app/layouts/rightbar/market-uninstall.d.ts +10 -0
- package/dist/types/app/layouts/rightbar/market-update-verdict.d.ts +2 -0
- package/dist/types/kernel/contract/market-index.d.ts +1 -1
- package/dist/types/modules/panels/model/market-env.d.ts +2 -2
- package/dist/types/modules/panels/model/market-swap-stuck.d.ts +20 -0
- package/dist/types/modules/panels/model/market-swap-stuck.test.d.ts +1 -0
- package/dist/types/modules/panels/model/market-types.d.ts +3 -0
- package/dist/types/modules/panels/model/market-update-bundle.d.ts +5 -0
- package/dist/types/modules/panels/model/plugin-actions.d.ts +1 -1
- package/dist/types/shared/api/host/self-update-marker.d.ts +5 -0
- package/dist/types/shared/api/host/self-update-marker.test.d.ts +1 -0
- package/package.json +3 -3
- package/skills/workbench-module-diy/TOOLS.md +11 -1
- package/skills/workbench-module-diy/WIRING.md +2 -1
package/dist/host/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import path, { basename, delimiter, dirname, join, resolve } from "node:path";
|
|
1
|
+
import path, { basename, delimiter, dirname, join, relative, resolve, sep } from "node:path";
|
|
2
2
|
import dayjs from "dayjs";
|
|
3
3
|
import utc from "dayjs/plugin/utc.js";
|
|
4
4
|
import { createHash, randomUUID } from "node:crypto";
|
|
@@ -144,7 +144,8 @@ var NEED_KEYS = [
|
|
|
144
144
|
"sources",
|
|
145
145
|
"secret",
|
|
146
146
|
"install",
|
|
147
|
-
"docs"
|
|
147
|
+
"docs",
|
|
148
|
+
"runs"
|
|
148
149
|
];
|
|
149
150
|
var SOURCE_KEYS = [
|
|
150
151
|
"kind",
|
|
@@ -251,6 +252,15 @@ function binInstallProblemsOf(raw, at) {
|
|
|
251
252
|
}
|
|
252
253
|
return errors;
|
|
253
254
|
}
|
|
255
|
+
function libRunsProblemsOf(raw, at) {
|
|
256
|
+
if (raw === void 0) return [];
|
|
257
|
+
if (!Array.isArray(raw)) return [`${at}.runs 要是数组`];
|
|
258
|
+
if (raw.length === 0) return [`${at}.runs 是空数组 —— 不自己跑命令就整条删掉`];
|
|
259
|
+
const names = raw.map((one) => text$3(one));
|
|
260
|
+
if (names.some((one) => one === "")) return [`${at}.runs 里有空字符串`];
|
|
261
|
+
if (new Set(names).size !== names.length) return [`${at}.runs 里有重复的运行时`];
|
|
262
|
+
return [];
|
|
263
|
+
}
|
|
254
264
|
function perKindProblemsOf(kind, raw, at) {
|
|
255
265
|
const errors = [];
|
|
256
266
|
if (kind === "bin" || kind === "lib" || kind === "env") {
|
|
@@ -258,6 +268,7 @@ function perKindProblemsOf(kind, raw, at) {
|
|
|
258
268
|
}
|
|
259
269
|
if (kind === "bin") errors.push(...binInstallProblemsOf(raw["install"], at));
|
|
260
270
|
if (raw["docs"] !== void 0 && text$3(raw["docs"]) === "") errors.push(`${at}.docs 是空串`);
|
|
271
|
+
if (kind === "lib") errors.push(...libRunsProblemsOf(raw["runs"], at));
|
|
261
272
|
if (kind === "extension") {
|
|
262
273
|
if (text$3(raw["runtime"]) === "") errors.push(`${at}.runtime 没写 —— 扩展属于哪个运行时(如 opencli)`);
|
|
263
274
|
if (text$3(raw["name"]) === "") errors.push(`${at}.name 没写`);
|
|
@@ -1134,7 +1145,7 @@ var PHASES = [
|
|
|
1134
1145
|
function asRecord$7(value) {
|
|
1135
1146
|
return typeof value === "object" && value !== null ? value : void 0;
|
|
1136
1147
|
}
|
|
1137
|
-
function textOf$
|
|
1148
|
+
function textOf$6(value, max) {
|
|
1138
1149
|
if (typeof value !== "string") return "";
|
|
1139
1150
|
const trimmed = value.trim();
|
|
1140
1151
|
return trimmed.length > max ? trimmed.slice(0, max) : trimmed;
|
|
@@ -1147,8 +1158,8 @@ function atOf(value) {
|
|
|
1147
1158
|
function errorOf(raw) {
|
|
1148
1159
|
const record = asRecord$7(raw);
|
|
1149
1160
|
if (record === void 0) return void 0;
|
|
1150
|
-
const code = textOf$
|
|
1151
|
-
const message = textOf$
|
|
1161
|
+
const code = textOf$6(record["code"], 120);
|
|
1162
|
+
const message = textOf$6(record["message"], PANEL_STATE_MAX_TEXT);
|
|
1152
1163
|
if (code === "" && message === "") return void 0;
|
|
1153
1164
|
return {
|
|
1154
1165
|
code: code === "" ? "unknown" : code,
|
|
@@ -1167,7 +1178,7 @@ function parseCardsReport(payload) {
|
|
|
1167
1178
|
ok: false,
|
|
1168
1179
|
message: "请求体要是一个对象"
|
|
1169
1180
|
};
|
|
1170
|
-
const workspace = textOf$
|
|
1181
|
+
const workspace = textOf$6(record["workspace"], PANEL_STATE_MAX_TEXT);
|
|
1171
1182
|
if (workspace === "") return {
|
|
1172
1183
|
ok: false,
|
|
1173
1184
|
message: "workspace 不能是空的 —— 面板按工作区存"
|
|
@@ -1181,9 +1192,9 @@ function parseCardsReport(payload) {
|
|
|
1181
1192
|
for (const item of raw.slice(0, 32)) {
|
|
1182
1193
|
const card = asRecord$7(item);
|
|
1183
1194
|
if (card === void 0) continue;
|
|
1184
|
-
const unit = textOf$
|
|
1185
|
-
const route = textOf$
|
|
1186
|
-
const phase = textOf$
|
|
1195
|
+
const unit = textOf$6(card["unit"], 200);
|
|
1196
|
+
const route = textOf$6(card["route"], 400);
|
|
1197
|
+
const phase = textOf$6(card["phase"], 20);
|
|
1187
1198
|
const at = atOf(card["at"]);
|
|
1188
1199
|
if (unit === "" || route === "" || at === null || !PHASES.includes(phase)) continue;
|
|
1189
1200
|
const error = errorOf(card["error"]);
|
|
@@ -1218,9 +1229,9 @@ function parseCrashReport(payload) {
|
|
|
1218
1229
|
ok: false,
|
|
1219
1230
|
message: "请求体要是一个对象"
|
|
1220
1231
|
};
|
|
1221
|
-
const workspace = textOf$
|
|
1222
|
-
const unit = textOf$
|
|
1223
|
-
const reason = textOf$
|
|
1232
|
+
const workspace = textOf$6(record["workspace"], PANEL_STATE_MAX_TEXT);
|
|
1233
|
+
const unit = textOf$6(record["unit"], 200);
|
|
1234
|
+
const reason = textOf$6(record["reason"], PANEL_STATE_MAX_TEXT);
|
|
1224
1235
|
if (workspace === "") return {
|
|
1225
1236
|
ok: false,
|
|
1226
1237
|
message: "workspace 不能是空的"
|
|
@@ -2033,7 +2044,7 @@ var PLUGIN_LEDGER_FILE = "plugins.json";
|
|
|
2033
2044
|
function ledgerKeyOf(entry) {
|
|
2034
2045
|
return `${entry.opencliHome}\u0000${entry.package}`;
|
|
2035
2046
|
}
|
|
2036
|
-
function textOf$
|
|
2047
|
+
function textOf$5(value) {
|
|
2037
2048
|
if (typeof value !== "string") return void 0;
|
|
2038
2049
|
const trimmed = value.trim();
|
|
2039
2050
|
return trimmed === "" ? void 0 : trimmed;
|
|
@@ -2041,13 +2052,13 @@ function textOf$4(value) {
|
|
|
2041
2052
|
function entryOf$3(value) {
|
|
2042
2053
|
if (typeof value !== "object" || value === null) return void 0;
|
|
2043
2054
|
const box = value;
|
|
2044
|
-
const opencliHome = textOf$
|
|
2045
|
-
const pkg = textOf$
|
|
2046
|
-
const installedAt = textOf$
|
|
2055
|
+
const opencliHome = textOf$5(box["opencliHome"]);
|
|
2056
|
+
const pkg = textOf$5(box["package"]);
|
|
2057
|
+
const installedAt = textOf$5(box["installedAt"]);
|
|
2047
2058
|
if (opencliHome === void 0 || pkg === void 0 || installedAt === void 0) return void 0;
|
|
2048
|
-
const name = textOf$
|
|
2049
|
-
const registry = textOf$
|
|
2050
|
-
const releasedAt = textOf$
|
|
2059
|
+
const name = textOf$5(box["name"]);
|
|
2060
|
+
const registry = textOf$5(box["registry"]);
|
|
2061
|
+
const releasedAt = textOf$5(box["releasedAt"]);
|
|
2051
2062
|
return {
|
|
2052
2063
|
opencliHome,
|
|
2053
2064
|
package: pkg,
|
|
@@ -2350,6 +2361,93 @@ function sessionIdOf(exec) {
|
|
|
2350
2361
|
return typeof id === "string" && id.trim() !== "" ? id.trim() : null;
|
|
2351
2362
|
}
|
|
2352
2363
|
//#endregion
|
|
2364
|
+
//#region ../shared/dist/host/opencli-runner.js
|
|
2365
|
+
var AUTH_REQUIRED_EXIT_CODE = 77;
|
|
2366
|
+
function textOf$4(value) {
|
|
2367
|
+
return typeof value === "string" ? value : "";
|
|
2368
|
+
}
|
|
2369
|
+
function failedOf(args, error) {
|
|
2370
|
+
const head = `Command failed: opencli ${args.join(" ")}`;
|
|
2371
|
+
const auth = error?.pageCode === "auth-required";
|
|
2372
|
+
const helpUrl = textOf$4(error?.helpUrl);
|
|
2373
|
+
const detail = textOf$4(error?.detail);
|
|
2374
|
+
const hint = textOf$4(error?.hint);
|
|
2375
|
+
const failed = new Error([
|
|
2376
|
+
head,
|
|
2377
|
+
...auth ? ["code: AUTH_REQUIRED", `message: ${textOf$4(error?.message)}`] : [textOf$4(error?.message) || "opencli 这条命令没跑通"],
|
|
2378
|
+
...detail === "" ? [] : [detail],
|
|
2379
|
+
...hint === "" ? [] : [hint],
|
|
2380
|
+
...auth && helpUrl !== "" ? [`help: Please open Chrome or Chromium and log in to ${helpUrl}`] : []
|
|
2381
|
+
].join("\n"));
|
|
2382
|
+
if (auth) failed.code = AUTH_REQUIRED_EXIT_CODE;
|
|
2383
|
+
return failed;
|
|
2384
|
+
}
|
|
2385
|
+
/**
|
|
2386
|
+
* 把读缝包成一条 opencli 命令的执行器:给还按 `execFile('opencli', args)` 收结果的编排库用,
|
|
2387
|
+
* 让它们跑在面板用的那份 opencli 上(同一套插件、同一份登录态)。
|
|
2388
|
+
*
|
|
2389
|
+
* 失败按 opencli 自己的约定还原:未登录是 `code: AUTH_REQUIRED` + 退出码 77,编排库的
|
|
2390
|
+
* 「等登录再重试」才接得上;其它失败保留 `Command failed: opencli …` 那句外壳。
|
|
2391
|
+
* @param read 模块手里的读缝(`data.read`)。
|
|
2392
|
+
* @param options `timeoutMs` 透传给读缝;不传就用读缝自己的默认值。
|
|
2393
|
+
* @returns 执行器;直接交给编排库的 `setOpencliRunner`。
|
|
2394
|
+
*/
|
|
2395
|
+
function opencliRunnerOf(read, options = {}) {
|
|
2396
|
+
return async (args) => {
|
|
2397
|
+
const result = await read({
|
|
2398
|
+
kind: "command",
|
|
2399
|
+
adapter: "opencli",
|
|
2400
|
+
argv: [...args],
|
|
2401
|
+
...options.timeoutMs === void 0 ? {} : { timeoutMs: options.timeoutMs }
|
|
2402
|
+
});
|
|
2403
|
+
if (result.ok) return { stdout: JSON.stringify(result.data) };
|
|
2404
|
+
throw failedOf(args, result.error);
|
|
2405
|
+
};
|
|
2406
|
+
}
|
|
2407
|
+
//#endregion
|
|
2408
|
+
//#region ../shared/dist/host/lib-runners.js
|
|
2409
|
+
function runnerOf(runtimes, runtime) {
|
|
2410
|
+
return runtime === "opencli" ? runtimes.opencli : void 0;
|
|
2411
|
+
}
|
|
2412
|
+
function setterNameOf(runtime) {
|
|
2413
|
+
return `set${runtime.charAt(0).toUpperCase()}${runtime.slice(1)}Runner`;
|
|
2414
|
+
}
|
|
2415
|
+
/**
|
|
2416
|
+
* 给声明了 `runs` 的 lib 注入「跑这个运行时」的执行器:库侧按约定暴露 `setOpencliRunner`
|
|
2417
|
+
* (运行时名字首字母大写 + `Runner`),外壳在每次 `load` 之后把执行器交给它。
|
|
2418
|
+
*
|
|
2419
|
+
* 没暴露的(版本旧)就保持它自己的行为(按 PATH 找),只记一条 warn;不熟悉的运行时同样只 warn。
|
|
2420
|
+
* @param libs 原来的读库口。
|
|
2421
|
+
* @param runtimes 外壳手上有的执行器,按运行时名字给。
|
|
2422
|
+
* @param logger 记 warn 的地方(默认不记)。
|
|
2423
|
+
* @returns 包好的读库口;`load` 的签名与返回不变。
|
|
2424
|
+
*/
|
|
2425
|
+
function withLibRunners(libs, runtimes, logger) {
|
|
2426
|
+
const warned = /* @__PURE__ */ new Set();
|
|
2427
|
+
const warnOnce = (key, message) => {
|
|
2428
|
+
if (warned.has(key)) return;
|
|
2429
|
+
warned.add(key);
|
|
2430
|
+
logger?.warn?.(message);
|
|
2431
|
+
};
|
|
2432
|
+
return { async load(need, entry) {
|
|
2433
|
+
const module = await libs.load(need, entry);
|
|
2434
|
+
for (const runtime of need.runs ?? []) {
|
|
2435
|
+
const runner = runnerOf(runtimes, runtime);
|
|
2436
|
+
if (runner === void 0) {
|
|
2437
|
+
warnOnce(`${need.name}:${runtime}:unsupported`, `[dsh-workbench] ${need.name} 声明了自己会跑 ${runtime},但工作台没有这个执行器 —— 它会按自己的方式找`);
|
|
2438
|
+
continue;
|
|
2439
|
+
}
|
|
2440
|
+
const setter = typeof module === "object" && module !== null ? module[setterNameOf(runtime)] : void 0;
|
|
2441
|
+
if (typeof setter !== "function") {
|
|
2442
|
+
warnOnce(`${need.name}:${entry}:${runtime}:missing`, `[dsh-workbench] ${need.name} 声明了自己会跑 ${runtime},但这一版没有 ${setterNameOf(runtime)} —— 它会按 PATH 找`);
|
|
2443
|
+
continue;
|
|
2444
|
+
}
|
|
2445
|
+
setter.call(module, runner);
|
|
2446
|
+
}
|
|
2447
|
+
return module;
|
|
2448
|
+
} };
|
|
2449
|
+
}
|
|
2450
|
+
//#endregion
|
|
2353
2451
|
//#region ../shared/dist/host/value-handoff.js
|
|
2354
2452
|
function handoffId(entry) {
|
|
2355
2453
|
return `${String(entry.fact.length)}:${entry.fact}${entry.key}`;
|
|
@@ -4896,6 +4994,11 @@ function checkVersionRange(contract, version) {
|
|
|
4896
4994
|
if (parsed === null) return "malformed";
|
|
4897
4995
|
return import_semver.default.satisfies(parsed, range) ? "satisfied" : "unsatisfied";
|
|
4898
4996
|
}
|
|
4997
|
+
//#endregion
|
|
4998
|
+
//#region ../shared/dist/contract/protocol/market-index.js
|
|
4999
|
+
var MARKET_INDEX_DIR = "market";
|
|
5000
|
+
var MARKET_ARTIFACTS_DIR = "artifacts";
|
|
5001
|
+
var MARKET_INDEX_PATH = "market.json";
|
|
4899
5002
|
var MARKET_DISCOVER_ROUTE = "panels/market-discover";
|
|
4900
5003
|
var MARKET_DOC_ROUTE = "panels/market-doc";
|
|
4901
5004
|
var MARKET_GIT_PROBE_ROUTE = "panels/git-probe";
|
|
@@ -4917,34 +5020,39 @@ function marketText(value, locale) {
|
|
|
4917
5020
|
return locale.toLowerCase().startsWith("zh") ? zh || en : en || zh;
|
|
4918
5021
|
}
|
|
4919
5022
|
//#endregion
|
|
4920
|
-
//#region ../shared/dist/host/market-index.js
|
|
4921
|
-
var MARKET_INDEX_SEED = "{\n \"format\": 1,\n \"plugins\": []\n}\n";
|
|
4922
|
-
var MARKET_INDEX_README_LINES = [
|
|
4923
|
-
"# 模块市场索引",
|
|
4924
|
-
"",
|
|
4925
|
-
"这个仓库给 DeepSeek Harness 工作台当模块市场索引:`market.json`(format 1)列出可安装的模块与版本。",
|
|
4926
|
-
"",
|
|
4927
|
-
"## 发布模块",
|
|
4928
|
-
"",
|
|
4929
|
-
"在模块包里运行:",
|
|
4930
|
-
"",
|
|
4931
|
-
"```bash",
|
|
4932
|
-
"dsh-wb-module release --index <本仓库地址> --index-policy artifacts-in-repo",
|
|
4933
|
-
"```",
|
|
4934
|
-
"",
|
|
4935
|
-
"产物会提交到 `packages/<包名>/`(带 scope 时是 `packages/@scope/名字/`),并更新 `market.json`。",
|
|
4936
|
-
"",
|
|
4937
|
-
"一个包名归第一次发布的人(`market.json` 里的 `owner`)。换一个署名发同名会被拒;要换人,先把这一条的 `owner` 改成新的人。",
|
|
4938
|
-
"同一个市场里,一个模块 id(`metadata.unit`)只能被一个条目使用;另一个包名想用同一个 id / 产物目录也会被拒。",
|
|
4939
|
-
"",
|
|
4940
|
-
"## 使用这个索引",
|
|
4941
|
-
"",
|
|
4942
|
-
"在工作台的「模块市场 → 发现」里添加本仓库地址,即可浏览与安装。",
|
|
4943
|
-
""
|
|
4944
|
-
];
|
|
5023
|
+
//#region ../shared/dist/host/market-index-parse.js
|
|
4945
5024
|
var NPM_NAME = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/;
|
|
4946
5025
|
var GIT_COMMIT = /^[0-9a-f]{40}$/;
|
|
4947
5026
|
var MAX_ICON_CHARS = 32768;
|
|
5027
|
+
function compareSemver(left, right) {
|
|
5028
|
+
return compareVersions(left, right);
|
|
5029
|
+
}
|
|
5030
|
+
function httpUrlProblem(url) {
|
|
5031
|
+
let parsed;
|
|
5032
|
+
try {
|
|
5033
|
+
parsed = new URL(url);
|
|
5034
|
+
} catch {
|
|
5035
|
+
return "不是合法 URL";
|
|
5036
|
+
}
|
|
5037
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return "只收 http / https";
|
|
5038
|
+
if (parsed.username !== "" || parsed.password !== "") return "URL 不能带凭据";
|
|
5039
|
+
if (parsed.search !== "" || parsed.hash !== "") return "URL 不能带 query / fragment";
|
|
5040
|
+
}
|
|
5041
|
+
function gitUrlOk(url) {
|
|
5042
|
+
if (url === "") return false;
|
|
5043
|
+
if (/^[^@\s]+@[^@\s:]+:[^@\s]+$/.test(url)) return true;
|
|
5044
|
+
let parsed;
|
|
5045
|
+
try {
|
|
5046
|
+
parsed = new URL(url);
|
|
5047
|
+
} catch {
|
|
5048
|
+
return false;
|
|
5049
|
+
}
|
|
5050
|
+
if (parsed.protocol !== "https:" && parsed.protocol !== "ssh:" && parsed.protocol !== "http:") return false;
|
|
5051
|
+
if (parsed.password !== "") return false;
|
|
5052
|
+
if (parsed.username !== "" && parsed.protocol !== "ssh:") return false;
|
|
5053
|
+
if (parsed.search !== "" || parsed.hash !== "") return false;
|
|
5054
|
+
return true;
|
|
5055
|
+
}
|
|
4948
5056
|
function asRecord$4(value) {
|
|
4949
5057
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
4950
5058
|
}
|
|
@@ -4964,22 +5072,19 @@ function asLocalized(value) {
|
|
|
4964
5072
|
...en === "" ? {} : { en }
|
|
4965
5073
|
};
|
|
4966
5074
|
}
|
|
4967
|
-
function problem(kind, detail, at) {
|
|
5075
|
+
function problem$1(kind, detail, at) {
|
|
4968
5076
|
return {
|
|
4969
5077
|
kind,
|
|
4970
5078
|
detail,
|
|
4971
5079
|
...at === void 0 ? {} : { at }
|
|
4972
5080
|
};
|
|
4973
5081
|
}
|
|
4974
|
-
function compareSemver(left, right) {
|
|
4975
|
-
return compareVersions(left, right);
|
|
4976
|
-
}
|
|
4977
5082
|
function sourceOf(raw, at) {
|
|
4978
5083
|
const record = asRecord$4(raw);
|
|
4979
|
-
if (record === void 0) return problem("source", "source 必须是一个对象", at);
|
|
5084
|
+
if (record === void 0) return problem$1("source", "source 必须是一个对象", at);
|
|
4980
5085
|
if (record["type"] === "npm") {
|
|
4981
5086
|
const registry = record["registry"];
|
|
4982
|
-
if (registry !== void 0 && asText$4(registry) === "") return problem("source", "npm source 的 registry 必须是非空字符串", at);
|
|
5087
|
+
if (registry !== void 0 && asText$4(registry) === "") return problem$1("source", "npm source 的 registry 必须是非空字符串", at);
|
|
4983
5088
|
return {
|
|
4984
5089
|
type: "npm",
|
|
4985
5090
|
...registry === void 0 ? {} : { registry: asText$4(registry) }
|
|
@@ -4988,8 +5093,8 @@ function sourceOf(raw, at) {
|
|
|
4988
5093
|
if (record["type"] === "git") {
|
|
4989
5094
|
const url = asText$4(record["url"]);
|
|
4990
5095
|
const commit = asText$4(record["commit"]);
|
|
4991
|
-
if (url === "") return problem("source", "git source 缺 url", at);
|
|
4992
|
-
if (!GIT_COMMIT.test(commit)) return problem("git-commit", `git source 的 commit 必须是 40 位小写 SHA(现在是 ${JSON.stringify(commit)})`, at);
|
|
5096
|
+
if (url === "") return problem$1("source", "git source 缺 url", at);
|
|
5097
|
+
if (!GIT_COMMIT.test(commit)) return problem$1("git-commit", `git source 的 commit 必须是 40 位小写 SHA(现在是 ${JSON.stringify(commit)})`, at);
|
|
4993
5098
|
const path = record["path"];
|
|
4994
5099
|
return {
|
|
4995
5100
|
type: "git",
|
|
@@ -4998,17 +5103,17 @@ function sourceOf(raw, at) {
|
|
|
4998
5103
|
...path === void 0 || asText$4(path) === "" ? {} : { path: asText$4(path) }
|
|
4999
5104
|
};
|
|
5000
5105
|
}
|
|
5001
|
-
return problem("source", "source.type 只能是 npm 或 git", at);
|
|
5106
|
+
return problem$1("source", "source.type 只能是 npm 或 git", at);
|
|
5002
5107
|
}
|
|
5003
5108
|
function versionOf(raw, at) {
|
|
5004
5109
|
const record = asRecord$4(raw);
|
|
5005
|
-
if (record === void 0) return problem("version", "版本必须是一个对象", at);
|
|
5110
|
+
if (record === void 0) return problem$1("version", "版本必须是一个对象", at);
|
|
5006
5111
|
const version = asText$4(record["version"]);
|
|
5007
|
-
if (!isValidVersion(version)) return problem("version", `版本号不是合法 SemVer:${JSON.stringify(version)}`, at);
|
|
5112
|
+
if (!isValidVersion(version)) return problem$1("version", `版本号不是合法 SemVer:${JSON.stringify(version)}`, at);
|
|
5008
5113
|
const source = sourceOf(record["source"], `${at}.source`);
|
|
5009
5114
|
if ("kind" in source) return source;
|
|
5010
5115
|
const timestamp = record["publishTimestamp"];
|
|
5011
|
-
if (timestamp !== void 0 && (!Number.isInteger(timestamp) || timestamp < 0)) return problem("timestamp", "publishTimestamp 必须是 Unix 秒整数", at);
|
|
5116
|
+
if (timestamp !== void 0 && (!Number.isInteger(timestamp) || timestamp < 0)) return problem$1("timestamp", "publishTimestamp 必须是 Unix 秒整数", at);
|
|
5012
5117
|
const engines = asRecord$4(record["engines"]);
|
|
5013
5118
|
const dsh = engines === void 0 ? "" : asText$4(engines["dsh"]);
|
|
5014
5119
|
return {
|
|
@@ -5019,33 +5124,33 @@ function versionOf(raw, at) {
|
|
|
5019
5124
|
};
|
|
5020
5125
|
}
|
|
5021
5126
|
function versionsOf$1(raw, at) {
|
|
5022
|
-
if (!Array.isArray(raw) || raw.length === 0) return problem("versions", "versions 必须是非空数组", at);
|
|
5127
|
+
if (!Array.isArray(raw) || raw.length === 0) return problem$1("versions", "versions 必须是非空数组", at);
|
|
5023
5128
|
const versions = [];
|
|
5024
5129
|
const seen = /* @__PURE__ */ new Set();
|
|
5025
5130
|
for (const [index, item] of raw.entries()) {
|
|
5026
5131
|
const parsed = versionOf(item, `${at}[${String(index)}]`);
|
|
5027
5132
|
if ("kind" in parsed) return parsed;
|
|
5028
|
-
if (seen.has(parsed.version)) return problem("versions", `版本重复:${parsed.version}`, at);
|
|
5133
|
+
if (seen.has(parsed.version)) return problem$1("versions", `版本重复:${parsed.version}`, at);
|
|
5029
5134
|
seen.add(parsed.version);
|
|
5030
5135
|
versions.push(parsed);
|
|
5031
5136
|
}
|
|
5032
5137
|
for (let index = 1; index < versions.length; index += 1) {
|
|
5033
5138
|
const order = compareSemver(versions[index - 1]?.version ?? "", versions[index]?.version ?? "");
|
|
5034
|
-
if (order === void 0 || order < 0) return problem("version-order", "versions 必须按 SemVer 降序", at);
|
|
5139
|
+
if (order === void 0 || order < 0) return problem$1("version-order", "versions 必须按 SemVer 降序", at);
|
|
5035
5140
|
}
|
|
5036
5141
|
return versions;
|
|
5037
5142
|
}
|
|
5038
5143
|
function iconProblem(icon) {
|
|
5039
|
-
if (icon.length > MAX_ICON_CHARS) return problem("icon", "icon 超过 32 KiB");
|
|
5040
|
-
if (icon.startsWith("data:image/")) return icon.includes(";base64,") ? void 0 : problem("icon", "data URL 必须是 base64");
|
|
5144
|
+
if (icon.length > MAX_ICON_CHARS) return problem$1("icon", "icon 超过 32 KiB");
|
|
5145
|
+
if (icon.startsWith("data:image/")) return icon.includes(";base64,") ? void 0 : problem$1("icon", "data URL 必须是 base64");
|
|
5041
5146
|
if (/^https?:\/\//i.test(icon)) return void 0;
|
|
5042
|
-
return problem("icon", "icon 只能是 base64 data URL 或 http(s) URL");
|
|
5147
|
+
return problem$1("icon", "icon 只能是 base64 data URL 或 http(s) URL");
|
|
5043
5148
|
}
|
|
5044
5149
|
var MAX_OWNER_CHARS = 200;
|
|
5045
5150
|
var UNIT_ID = /^[a-z][a-z0-9-]*$/;
|
|
5046
5151
|
function ownerProblem(owner) {
|
|
5047
|
-
if (/[\r\n]/u.test(owner)) return problem("owner", "owner 只能是一行署名");
|
|
5048
|
-
if (owner.length > MAX_OWNER_CHARS) return problem("owner", "owner 超过 200 字");
|
|
5152
|
+
if (/[\r\n]/u.test(owner)) return problem$1("owner", "owner 只能是一行署名");
|
|
5153
|
+
if (owner.length > MAX_OWNER_CHARS) return problem$1("owner", "owner 超过 200 字");
|
|
5049
5154
|
}
|
|
5050
5155
|
function metadataOf(raw) {
|
|
5051
5156
|
const record = asRecord$4(raw);
|
|
@@ -5068,9 +5173,9 @@ function metadataOf(raw) {
|
|
|
5068
5173
|
}
|
|
5069
5174
|
function entryOf$1(raw, at) {
|
|
5070
5175
|
const record = asRecord$4(raw);
|
|
5071
|
-
if (record === void 0) return problem("plugin", "插件条目必须是对象", at);
|
|
5176
|
+
if (record === void 0) return problem$1("plugin", "插件条目必须是对象", at);
|
|
5072
5177
|
const name = asText$4(record["name"]);
|
|
5073
|
-
if (name === "" || name.length > 214 || !NPM_NAME.test(name)) return problem("name", `不是合法 npm 包名:${JSON.stringify(name)}`, at);
|
|
5178
|
+
if (name === "" || name.length > 214 || !NPM_NAME.test(name)) return problem$1("name", `不是合法 npm 包名:${JSON.stringify(name)}`, at);
|
|
5074
5179
|
const versions = versionsOf$1(record["versions"], `${at}.versions`);
|
|
5075
5180
|
if ("kind" in versions) return versions;
|
|
5076
5181
|
const icon = asText$4(record["icon"]);
|
|
@@ -5088,7 +5193,7 @@ function entryOf$1(raw, at) {
|
|
|
5088
5193
|
}
|
|
5089
5194
|
const homepage = asText$4(record["homepage"]);
|
|
5090
5195
|
const metadata = metadataOf(record["metadata"]);
|
|
5091
|
-
if (metadata?.unit !== void 0 && !UNIT_ID.test(metadata.unit)) return problem("unit", `metadata.unit 只能是小写字母开头的 a-z0-9-(现在是 ${JSON.stringify(metadata.unit)})`, at);
|
|
5196
|
+
if (metadata?.unit !== void 0 && !UNIT_ID.test(metadata.unit)) return problem$1("unit", `metadata.unit 只能是小写字母开头的 a-z0-9-(现在是 ${JSON.stringify(metadata.unit)})`, at);
|
|
5092
5197
|
return {
|
|
5093
5198
|
name,
|
|
5094
5199
|
versions,
|
|
@@ -5105,16 +5210,16 @@ function parseMarketIndex(raw) {
|
|
|
5105
5210
|
const record = asRecord$4(raw);
|
|
5106
5211
|
if (record === void 0) return {
|
|
5107
5212
|
ok: false,
|
|
5108
|
-
problems: [problem("not-object", "索引必须是一个 JSON 对象")]
|
|
5213
|
+
problems: [problem$1("not-object", "索引必须是一个 JSON 对象")]
|
|
5109
5214
|
};
|
|
5110
5215
|
if (record["format"] !== 1) return {
|
|
5111
5216
|
ok: false,
|
|
5112
|
-
problems: [problem("format", `format 必须是 ${String(1)}(现在是 ${JSON.stringify(record["format"])})`)]
|
|
5217
|
+
problems: [problem$1("format", `format 必须是 ${String(1)}(现在是 ${JSON.stringify(record["format"])})`)]
|
|
5113
5218
|
};
|
|
5114
5219
|
const plugins = record["plugins"];
|
|
5115
5220
|
if (!Array.isArray(plugins)) return {
|
|
5116
5221
|
ok: false,
|
|
5117
|
-
problems: [problem("plugins", "plugins 必须是数组")]
|
|
5222
|
+
problems: [problem$1("plugins", "plugins 必须是数组")]
|
|
5118
5223
|
};
|
|
5119
5224
|
const entries = [];
|
|
5120
5225
|
const problems = [];
|
|
@@ -5126,7 +5231,7 @@ function parseMarketIndex(raw) {
|
|
|
5126
5231
|
continue;
|
|
5127
5232
|
}
|
|
5128
5233
|
if (seen.has(parsed.name)) {
|
|
5129
|
-
problems.push(problem("duplicate-name", `插件名重复:${parsed.name}`, `plugins[${String(index)}]`));
|
|
5234
|
+
problems.push(problem$1("duplicate-name", `插件名重复:${parsed.name}`, `plugins[${String(index)}]`));
|
|
5130
5235
|
continue;
|
|
5131
5236
|
}
|
|
5132
5237
|
seen.add(parsed.name);
|
|
@@ -5156,10 +5261,13 @@ function parseMarketIndexJson(text) {
|
|
|
5156
5261
|
} catch (error) {
|
|
5157
5262
|
return {
|
|
5158
5263
|
ok: false,
|
|
5159
|
-
problems: [problem("not-json", `JSON 解析失败:${error instanceof Error ? error.message : String(error)}`)]
|
|
5264
|
+
problems: [problem$1("not-json", `JSON 解析失败:${error instanceof Error ? error.message : String(error)}`)]
|
|
5160
5265
|
};
|
|
5161
5266
|
}
|
|
5162
5267
|
}
|
|
5268
|
+
function sortMarketVersions(versions) {
|
|
5269
|
+
return [...versions].sort((left, right) => compareSemver(right.version, left.version) ?? 0);
|
|
5270
|
+
}
|
|
5163
5271
|
function marketEntryLabels(entry) {
|
|
5164
5272
|
return entry.metadata?.labels ?? [];
|
|
5165
5273
|
}
|
|
@@ -5195,7 +5303,7 @@ function parseSourceConfig(raw) {
|
|
|
5195
5303
|
const record = asRecord$4(raw);
|
|
5196
5304
|
if (record === void 0) return {
|
|
5197
5305
|
ok: false,
|
|
5198
|
-
problem: problem("source", "索引源必须是对象")
|
|
5306
|
+
problem: problem$1("source", "索引源必须是对象")
|
|
5199
5307
|
};
|
|
5200
5308
|
const id = asText$4(record["id"]);
|
|
5201
5309
|
const label = asText$4(record["label"]);
|
|
@@ -5203,21 +5311,21 @@ function parseSourceConfig(raw) {
|
|
|
5203
5311
|
const url = asText$4(record["url"]);
|
|
5204
5312
|
if (id === "") return {
|
|
5205
5313
|
ok: false,
|
|
5206
|
-
problem: problem("source", "索引源缺 id")
|
|
5314
|
+
problem: problem$1("source", "索引源缺 id")
|
|
5207
5315
|
};
|
|
5208
5316
|
if (label === "") return {
|
|
5209
5317
|
ok: false,
|
|
5210
|
-
problem: problem("source", "索引源缺 label")
|
|
5318
|
+
problem: problem$1("source", "索引源缺 label")
|
|
5211
5319
|
};
|
|
5212
5320
|
if (kind !== "http" && kind !== "git") return {
|
|
5213
5321
|
ok: false,
|
|
5214
|
-
problem: problem("source", "kind 只能是 http 或 git")
|
|
5322
|
+
problem: problem$1("source", "kind 只能是 http 或 git")
|
|
5215
5323
|
};
|
|
5216
5324
|
if (kind === "http") {
|
|
5217
5325
|
const bad = httpUrlProblem(url);
|
|
5218
5326
|
if (bad !== void 0) return {
|
|
5219
5327
|
ok: false,
|
|
5220
|
-
problem: problem("source", bad)
|
|
5328
|
+
problem: problem$1("source", bad)
|
|
5221
5329
|
};
|
|
5222
5330
|
return {
|
|
5223
5331
|
ok: true,
|
|
@@ -5231,7 +5339,7 @@ function parseSourceConfig(raw) {
|
|
|
5231
5339
|
}
|
|
5232
5340
|
if (!gitUrlOk(url)) return {
|
|
5233
5341
|
ok: false,
|
|
5234
|
-
problem: problem("source", "git 地址不合格(只收 https / ssh / scp 形式,且不能带凭据)")
|
|
5342
|
+
problem: problem$1("source", "git 地址不合格(只收 https / ssh / scp 形式,且不能带凭据)")
|
|
5235
5343
|
};
|
|
5236
5344
|
const ref = asText$4(record["ref"]);
|
|
5237
5345
|
const path = asText$4(record["path"]);
|
|
@@ -5239,7 +5347,7 @@ function parseSourceConfig(raw) {
|
|
|
5239
5347
|
const policy = record["policy"];
|
|
5240
5348
|
if (policy !== void 0 && policy !== "artifacts-in-repo" && policy !== "catalog-only") return {
|
|
5241
5349
|
ok: false,
|
|
5242
|
-
problem: problem("source", "policy 只能是 artifacts-in-repo 或 catalog-only")
|
|
5350
|
+
problem: problem$1("source", "policy 只能是 artifacts-in-repo 或 catalog-only")
|
|
5243
5351
|
};
|
|
5244
5352
|
return {
|
|
5245
5353
|
ok: true,
|
|
@@ -5255,31 +5363,270 @@ function parseSourceConfig(raw) {
|
|
|
5255
5363
|
}
|
|
5256
5364
|
};
|
|
5257
5365
|
}
|
|
5258
|
-
function
|
|
5259
|
-
|
|
5260
|
-
|
|
5261
|
-
|
|
5262
|
-
|
|
5263
|
-
|
|
5366
|
+
function docFieldsOf(record, at) {
|
|
5367
|
+
const name = asText$4(record["name"]);
|
|
5368
|
+
if (name === "" || name.length > 214 || !NPM_NAME.test(name)) return problem$1("name", `不是合法 npm 包名:${JSON.stringify(name)}`, at);
|
|
5369
|
+
const icon = asText$4(record["icon"]);
|
|
5370
|
+
if (icon !== "") {
|
|
5371
|
+
const badIcon = iconProblem(icon);
|
|
5372
|
+
if (badIcon !== void 0) return badIcon;
|
|
5264
5373
|
}
|
|
5265
|
-
|
|
5266
|
-
if (
|
|
5267
|
-
|
|
5374
|
+
const owner = asText$4(record["owner"]);
|
|
5375
|
+
if (owner !== "") {
|
|
5376
|
+
const badOwner = ownerProblem(owner);
|
|
5377
|
+
if (badOwner !== void 0) return badOwner;
|
|
5378
|
+
}
|
|
5379
|
+
const metadata = metadataOf(record["metadata"]);
|
|
5380
|
+
if (metadata?.unit !== void 0 && !UNIT_ID.test(metadata.unit)) return problem$1("unit", `metadata.unit 只能是小写字母开头的 a-z0-9-(现在是 ${JSON.stringify(metadata.unit)})`, at);
|
|
5381
|
+
const title = asLocalized(record["title"]);
|
|
5382
|
+
const description = asLocalized(record["description"]);
|
|
5383
|
+
const author = asText$4(record["author"]);
|
|
5384
|
+
const homepage = asText$4(record["homepage"]);
|
|
5385
|
+
return {
|
|
5386
|
+
name,
|
|
5387
|
+
...title === void 0 ? {} : { title },
|
|
5388
|
+
...description === void 0 ? {} : { description },
|
|
5389
|
+
...author === "" ? {} : { author },
|
|
5390
|
+
...owner === "" ? {} : { owner },
|
|
5391
|
+
...homepage === "" ? {} : { homepage },
|
|
5392
|
+
...icon === "" ? {} : { icon },
|
|
5393
|
+
...metadata === void 0 ? {} : { metadata }
|
|
5394
|
+
};
|
|
5268
5395
|
}
|
|
5269
|
-
function
|
|
5270
|
-
|
|
5271
|
-
if (
|
|
5272
|
-
|
|
5273
|
-
|
|
5274
|
-
|
|
5275
|
-
|
|
5276
|
-
|
|
5396
|
+
function parseMarketEntryDoc(raw) {
|
|
5397
|
+
const record = asRecord$4(raw);
|
|
5398
|
+
if (record === void 0) return {
|
|
5399
|
+
ok: false,
|
|
5400
|
+
problems: [problem$1("entry", "条目文件必须是一个 JSON 对象")]
|
|
5401
|
+
};
|
|
5402
|
+
const fields = docFieldsOf(record, "entry");
|
|
5403
|
+
if ("kind" in fields) return {
|
|
5404
|
+
ok: false,
|
|
5405
|
+
problems: [fields]
|
|
5406
|
+
};
|
|
5407
|
+
const version = asText$4(record["version"]);
|
|
5408
|
+
const timestamp = record["publishTimestamp"];
|
|
5409
|
+
if (timestamp !== void 0 && (!Number.isInteger(timestamp) || timestamp < 0)) return {
|
|
5410
|
+
ok: false,
|
|
5411
|
+
problems: [problem$1("timestamp", "publishTimestamp 必须是 Unix 秒整数", "entry")]
|
|
5412
|
+
};
|
|
5413
|
+
if (version === "") {
|
|
5414
|
+
if (timestamp === void 0) return {
|
|
5415
|
+
ok: false,
|
|
5416
|
+
problems: [problem$1("timestamp", "元数据文件要有 publishTimestamp", "entry")]
|
|
5417
|
+
};
|
|
5418
|
+
const epoch = record["epoch"];
|
|
5419
|
+
if (epoch !== void 0 && (!Number.isInteger(epoch) || epoch < 0)) return {
|
|
5420
|
+
ok: false,
|
|
5421
|
+
problems: [problem$1("timestamp", "epoch 必须是非负整数", "entry")]
|
|
5422
|
+
};
|
|
5423
|
+
return {
|
|
5424
|
+
ok: true,
|
|
5425
|
+
doc: {
|
|
5426
|
+
...fields,
|
|
5427
|
+
publishTimestamp: timestamp,
|
|
5428
|
+
...epoch === void 0 ? {} : { epoch }
|
|
5429
|
+
}
|
|
5430
|
+
};
|
|
5277
5431
|
}
|
|
5278
|
-
if (
|
|
5279
|
-
|
|
5280
|
-
|
|
5281
|
-
|
|
5282
|
-
|
|
5432
|
+
if (!isValidVersion(version)) return {
|
|
5433
|
+
ok: false,
|
|
5434
|
+
problems: [problem$1("version", `版本号不是合法 SemVer:${JSON.stringify(version)}`, "entry")]
|
|
5435
|
+
};
|
|
5436
|
+
const source = sourceOf(record["source"], "entry.source");
|
|
5437
|
+
if ("kind" in source) return {
|
|
5438
|
+
ok: false,
|
|
5439
|
+
problems: [source]
|
|
5440
|
+
};
|
|
5441
|
+
const engines = asRecord$4(record["engines"]);
|
|
5442
|
+
const dsh = engines === void 0 ? "" : asText$4(engines["dsh"]);
|
|
5443
|
+
return {
|
|
5444
|
+
ok: true,
|
|
5445
|
+
doc: {
|
|
5446
|
+
...fields,
|
|
5447
|
+
version,
|
|
5448
|
+
source,
|
|
5449
|
+
...timestamp === void 0 ? {} : { publishTimestamp: timestamp },
|
|
5450
|
+
...dsh === "" ? {} : { engines: { dsh } }
|
|
5451
|
+
}
|
|
5452
|
+
};
|
|
5453
|
+
}
|
|
5454
|
+
function indexPathBase(dir) {
|
|
5455
|
+
return dir.replace(/^\/+|\/+$/g, "") || "market";
|
|
5456
|
+
}
|
|
5457
|
+
function marketEntryPath(dir, name, version) {
|
|
5458
|
+
if (!NPM_NAME.test(name)) throw new Error(`不是合法包名:${JSON.stringify(name)}`);
|
|
5459
|
+
if (!isValidVersion(version)) throw new Error(`不是合法版本号:${JSON.stringify(version)}`);
|
|
5460
|
+
return `${indexPathBase(dir)}/${name}/${version}.json`;
|
|
5461
|
+
}
|
|
5462
|
+
//#endregion
|
|
5463
|
+
//#region ../shared/dist/host/market-index.js
|
|
5464
|
+
var MARKET_ENTRIES_README_LINES = [
|
|
5465
|
+
"# 索引目录",
|
|
5466
|
+
"",
|
|
5467
|
+
"一个包一个目录,一个版本一个文件:`market/<包名>/<版本>.json`;产物在 `artifacts/<包名>/<版本>/`。",
|
|
5468
|
+
"",
|
|
5469
|
+
"`meta-<epoch>-<随机后缀>.json` 是只改标题 / 署名 / 说明时追加的元数据快照,读取时按 epoch 取最新的一份。"
|
|
5470
|
+
];
|
|
5471
|
+
var MARKET_INDEX_README_LINES = [
|
|
5472
|
+
"# 模块市场索引",
|
|
5473
|
+
"",
|
|
5474
|
+
"这个仓库给 DeepSeek Harness 工作台当模块市场索引:条目在 `market/<包名>/<版本>.json`,产物在 `artifacts/<包名>/<版本>/`。",
|
|
5475
|
+
"",
|
|
5476
|
+
"## 发布模块",
|
|
5477
|
+
"",
|
|
5478
|
+
"在模块包里运行:",
|
|
5479
|
+
"",
|
|
5480
|
+
"```bash",
|
|
5481
|
+
"dsh-wb-module release --index <本仓库地址> --index-policy artifacts-in-repo",
|
|
5482
|
+
"```",
|
|
5483
|
+
"",
|
|
5484
|
+
"产物会提交到 `artifacts/<包名>/<版本>/`,条目提交到 `market/<包名>/<版本>.json`。",
|
|
5485
|
+
"发布只新增文件、不改已有文件,两台机器同时发也不会冲突;老条目的 `packages/<包名>/` 路径继续可用。",
|
|
5486
|
+
"",
|
|
5487
|
+
"一个包名归第一次发布的人(条目文件里的 `owner`)。换一个署名发同名会被拒;要换人,先把这一条的 `owner` 改成新的人。",
|
|
5488
|
+
"同一个市场里,一个模块 id(`metadata.unit`)只能被一个条目使用;另一个包名想用同一个 id / 产物目录也会被拒。",
|
|
5489
|
+
"",
|
|
5490
|
+
"## 使用这个索引",
|
|
5491
|
+
"",
|
|
5492
|
+
"在工作台的「模块市场 → 发现」里添加本仓库地址,即可浏览与安装。",
|
|
5493
|
+
""
|
|
5494
|
+
];
|
|
5495
|
+
function problem(kind, detail, at) {
|
|
5496
|
+
return {
|
|
5497
|
+
kind,
|
|
5498
|
+
detail,
|
|
5499
|
+
...at === void 0 ? {} : { at }
|
|
5500
|
+
};
|
|
5501
|
+
}
|
|
5502
|
+
function docFieldsFromEntry(entry) {
|
|
5503
|
+
return {
|
|
5504
|
+
name: entry.name,
|
|
5505
|
+
...entry.title === void 0 ? {} : { title: entry.title },
|
|
5506
|
+
...entry.description === void 0 ? {} : { description: entry.description },
|
|
5507
|
+
...entry.author === void 0 ? {} : { author: entry.author },
|
|
5508
|
+
...entry.owner === void 0 ? {} : { owner: entry.owner },
|
|
5509
|
+
...entry.homepage === void 0 ? {} : { homepage: entry.homepage },
|
|
5510
|
+
...entry.icon === void 0 ? {} : { icon: entry.icon },
|
|
5511
|
+
...entry.metadata === void 0 ? {} : { metadata: entry.metadata }
|
|
5512
|
+
};
|
|
5513
|
+
}
|
|
5514
|
+
function marketVersionDocOf(entry, version) {
|
|
5515
|
+
return {
|
|
5516
|
+
...docFieldsFromEntry(entry),
|
|
5517
|
+
...version
|
|
5518
|
+
};
|
|
5519
|
+
}
|
|
5520
|
+
function assembleMarketIndex(files) {
|
|
5521
|
+
const problems = [];
|
|
5522
|
+
const groups = /* @__PURE__ */ new Map();
|
|
5523
|
+
const ordered = [...files].sort((left, right) => left.path < right.path ? -1 : left.path > right.path ? 1 : 0);
|
|
5524
|
+
for (const file of ordered) {
|
|
5525
|
+
let raw;
|
|
5526
|
+
try {
|
|
5527
|
+
raw = JSON.parse(file.text);
|
|
5528
|
+
} catch (error) {
|
|
5529
|
+
problems.push(problem("not-json", `JSON 解析失败:${error instanceof Error ? error.message : String(error)}`, file.path));
|
|
5530
|
+
continue;
|
|
5531
|
+
}
|
|
5532
|
+
const parsed = parseMarketEntryDoc(raw);
|
|
5533
|
+
if (!parsed.ok) {
|
|
5534
|
+
for (const one of parsed.problems) problems.push({
|
|
5535
|
+
...one,
|
|
5536
|
+
at: file.path
|
|
5537
|
+
});
|
|
5538
|
+
continue;
|
|
5539
|
+
}
|
|
5540
|
+
const doc = parsed.doc;
|
|
5541
|
+
const group = groups.get(doc.name) ?? {
|
|
5542
|
+
versions: /* @__PURE__ */ new Map(),
|
|
5543
|
+
meta: void 0,
|
|
5544
|
+
fallback: void 0
|
|
5545
|
+
};
|
|
5546
|
+
const at = doc.publishTimestamp ?? 0;
|
|
5547
|
+
if ("version" in doc) {
|
|
5548
|
+
if (group.versions.has(doc.version)) problems.push(problem("duplicate-version", `版本重复:${doc.version}`, file.path));
|
|
5549
|
+
group.versions.set(doc.version, {
|
|
5550
|
+
version: doc.version,
|
|
5551
|
+
source: doc.source,
|
|
5552
|
+
...doc.publishTimestamp === void 0 ? {} : { publishTimestamp: doc.publishTimestamp },
|
|
5553
|
+
...doc.engines === void 0 ? {} : { engines: doc.engines }
|
|
5554
|
+
});
|
|
5555
|
+
if (group.fallback === void 0 || at > group.fallback.at || at === group.fallback.at && file.path > group.fallback.path) group.fallback = {
|
|
5556
|
+
at,
|
|
5557
|
+
path: file.path,
|
|
5558
|
+
doc
|
|
5559
|
+
};
|
|
5560
|
+
} else {
|
|
5561
|
+
const epoch = doc.epoch ?? 0;
|
|
5562
|
+
if (group.meta === void 0 || epoch > group.meta.epoch || epoch === group.meta.epoch && at > group.meta.at || epoch === group.meta.epoch && at === group.meta.at && file.path > group.meta.path) group.meta = {
|
|
5563
|
+
epoch,
|
|
5564
|
+
at,
|
|
5565
|
+
path: file.path,
|
|
5566
|
+
doc
|
|
5567
|
+
};
|
|
5568
|
+
}
|
|
5569
|
+
groups.set(doc.name, group);
|
|
5570
|
+
}
|
|
5571
|
+
if (problems.length > 0) return {
|
|
5572
|
+
ok: false,
|
|
5573
|
+
problems
|
|
5574
|
+
};
|
|
5575
|
+
const plugins = [...groups.entries()].map(([name, group]) => {
|
|
5576
|
+
const meta = group.meta?.doc ?? group.fallback?.doc;
|
|
5577
|
+
return {
|
|
5578
|
+
name,
|
|
5579
|
+
versions: sortMarketVersions([...group.versions.values()]),
|
|
5580
|
+
...meta?.title === void 0 ? {} : { title: meta.title },
|
|
5581
|
+
...meta?.description === void 0 ? {} : { description: meta.description },
|
|
5582
|
+
...meta?.author === void 0 ? {} : { author: meta.author },
|
|
5583
|
+
...meta?.owner === void 0 ? {} : { owner: meta.owner },
|
|
5584
|
+
...meta?.homepage === void 0 ? {} : { homepage: meta.homepage },
|
|
5585
|
+
...meta?.icon === void 0 ? {} : { icon: meta.icon },
|
|
5586
|
+
...meta?.metadata === void 0 ? {} : { metadata: meta.metadata }
|
|
5587
|
+
};
|
|
5588
|
+
});
|
|
5589
|
+
plugins.sort((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0);
|
|
5590
|
+
return {
|
|
5591
|
+
ok: true,
|
|
5592
|
+
index: {
|
|
5593
|
+
format: 1,
|
|
5594
|
+
plugins
|
|
5595
|
+
}
|
|
5596
|
+
};
|
|
5597
|
+
}
|
|
5598
|
+
function marketEntryFilesOf(index, dir = MARKET_INDEX_DIR) {
|
|
5599
|
+
const files = [];
|
|
5600
|
+
for (const entry of index.plugins) for (const version of entry.versions) files.push({
|
|
5601
|
+
path: marketEntryPath(dir, entry.name, version.version),
|
|
5602
|
+
text: `${JSON.stringify(marketVersionDocOf(entry, version), null, 2)}\n`
|
|
5603
|
+
});
|
|
5604
|
+
return files;
|
|
5605
|
+
}
|
|
5606
|
+
function collectMarketEntryFiles(dir) {
|
|
5607
|
+
if (!existsSync(dir)) return [];
|
|
5608
|
+
const files = [];
|
|
5609
|
+
const walk = (current) => {
|
|
5610
|
+
for (const item of readdirSync(current, { withFileTypes: true })) {
|
|
5611
|
+
const full = join(current, item.name);
|
|
5612
|
+
if (item.isDirectory()) {
|
|
5613
|
+
walk(full);
|
|
5614
|
+
continue;
|
|
5615
|
+
}
|
|
5616
|
+
if (!item.name.endsWith(".json")) continue;
|
|
5617
|
+
files.push({
|
|
5618
|
+
path: relative(dir, full).split(sep).join("/"),
|
|
5619
|
+
text: readFileSync(full, "utf8")
|
|
5620
|
+
});
|
|
5621
|
+
}
|
|
5622
|
+
};
|
|
5623
|
+
walk(dir);
|
|
5624
|
+
return files;
|
|
5625
|
+
}
|
|
5626
|
+
function marketEntryFilesOfLegacy(text, dir = MARKET_INDEX_DIR) {
|
|
5627
|
+
const parsed = parseMarketIndexJson(text);
|
|
5628
|
+
if (!parsed.ok) return void 0;
|
|
5629
|
+
return marketEntryFilesOf(parsed.index, dir);
|
|
5283
5630
|
}
|
|
5284
5631
|
//#endregion
|
|
5285
5632
|
//#region ../shared/dist/host/market-index-claim.js
|
|
@@ -5324,8 +5671,11 @@ function claimOfEntry(entry, publisher) {
|
|
|
5324
5671
|
owner
|
|
5325
5672
|
};
|
|
5326
5673
|
}
|
|
5327
|
-
function
|
|
5328
|
-
return
|
|
5674
|
+
function marketArtifactRoot(name) {
|
|
5675
|
+
return `${MARKET_ARTIFACTS_DIR}/${name}`;
|
|
5676
|
+
}
|
|
5677
|
+
function marketArtifactPathFor(name, version) {
|
|
5678
|
+
return `${MARKET_ARTIFACTS_DIR}/${name}/${version}`;
|
|
5329
5679
|
}
|
|
5330
5680
|
function entryUnitOf(entry) {
|
|
5331
5681
|
const unit = entry.metadata?.unit?.trim();
|
|
@@ -6470,7 +6820,7 @@ function publishClaimDenyOf(claim, name) {
|
|
|
6470
6820
|
if (claim === void 0) return void 0;
|
|
6471
6821
|
if (claim.state === "unit-taken") return `市场里 ${claim.entry} 已经用了这个模块 id —— 两个模块不能共用一个,装在一起会撞。给这个模块换一个 unit(dsh.workbench.unit)再发。`;
|
|
6472
6822
|
if (claim.state === "path-taken") return `市场仓库里的产物目录已经被 ${claim.entry} 占了 —— 换一个包名再发。`;
|
|
6473
|
-
if (claim.state === "conflict") return `市场里 ${name} 已经有主了(${claim.owner})—— 同一个名字不能换人发。换一个带团队 scope 的包名;你就是原来的作者(只是换了机器或署名),用 author 参数带上原来的署名;确实是接手,让索引维护者改 market
|
|
6823
|
+
if (claim.state === "conflict") return `市场里 ${name} 已经有主了(${claim.owner})—— 同一个名字不能换人发。换一个带团队 scope 的包名;你就是原来的作者(只是换了机器或署名),用 author 参数带上原来的署名;确实是接手,让索引维护者改 market/<包名>/ 条目文件里的 owner。`;
|
|
6474
6824
|
}
|
|
6475
6825
|
//#endregion
|
|
6476
6826
|
//#region src/modules/panels/model/approval.ts
|
|
@@ -10148,10 +10498,16 @@ var EMPTY_INDEX = {
|
|
|
10148
10498
|
plugins: []
|
|
10149
10499
|
};
|
|
10150
10500
|
function publishedIndexOf(source) {
|
|
10151
|
-
const
|
|
10152
|
-
if (!existsSync(file)) return void 0;
|
|
10501
|
+
const root = join(workbenchRoot(), "market-index", marketSourceKey(source), "worktree");
|
|
10153
10502
|
try {
|
|
10154
|
-
const
|
|
10503
|
+
const files = collectMarketEntryFiles(join(root, source.path ?? "market"));
|
|
10504
|
+
if (files.length > 0) {
|
|
10505
|
+
const assembled = assembleMarketIndex(files);
|
|
10506
|
+
return assembled.ok ? assembled.index : void 0;
|
|
10507
|
+
}
|
|
10508
|
+
const legacy = join(root, "market.json");
|
|
10509
|
+
if (!existsSync(legacy)) return void 0;
|
|
10510
|
+
const parsed = parseMarketIndexJson(readFileSync(legacy, "utf8"));
|
|
10155
10511
|
return parsed.ok ? parsed.index : void 0;
|
|
10156
10512
|
} catch {
|
|
10157
10513
|
return;
|
|
@@ -10192,8 +10548,8 @@ function marketOf(dir, name, version) {
|
|
|
10192
10548
|
market: {
|
|
10193
10549
|
url: source.url,
|
|
10194
10550
|
...source.ref === void 0 ? {} : { ref: source.ref },
|
|
10195
|
-
indexPath: source.path ?? "market
|
|
10196
|
-
artifactPath:
|
|
10551
|
+
indexPath: source.path ?? "market",
|
|
10552
|
+
artifactPath: marketArtifactPathFor(name, nextVersion ?? version),
|
|
10197
10553
|
files: ARTIFACTS.filter((one) => existsSync(join(dir, one))),
|
|
10198
10554
|
...nextVersion === void 0 ? {} : { nextVersion }
|
|
10199
10555
|
},
|
|
@@ -10290,7 +10646,7 @@ function publishPreviewOf(root, unit, options = {}) {
|
|
|
10290
10646
|
name,
|
|
10291
10647
|
publisher: author,
|
|
10292
10648
|
unit,
|
|
10293
|
-
artifactPath:
|
|
10649
|
+
artifactPath: marketArtifactRoot(name)
|
|
10294
10650
|
});
|
|
10295
10651
|
return {
|
|
10296
10652
|
name,
|
|
@@ -12391,7 +12747,82 @@ function dependencyStillBehind(target, capabilities) {
|
|
|
12391
12747
|
}
|
|
12392
12748
|
}
|
|
12393
12749
|
//#endregion
|
|
12750
|
+
//#region src/shared/api/host/self-update-marker.ts
|
|
12751
|
+
function readSelfUpdateMarker(file) {
|
|
12752
|
+
try {
|
|
12753
|
+
const raw = JSON.parse(readFileSync(file, "utf8"));
|
|
12754
|
+
return {
|
|
12755
|
+
...typeof raw.pkg === "string" ? { pkg: raw.pkg } : {},
|
|
12756
|
+
...typeof raw.state === "string" ? { state: raw.state } : {}
|
|
12757
|
+
};
|
|
12758
|
+
} catch {
|
|
12759
|
+
return {};
|
|
12760
|
+
}
|
|
12761
|
+
}
|
|
12762
|
+
//#endregion
|
|
12763
|
+
//#region src/modules/panels/model/market-swap-stuck.ts
|
|
12764
|
+
var DEFAULT_POLL_MS = 400;
|
|
12765
|
+
var DEFAULT_WAIT_MS = 6e4;
|
|
12766
|
+
function sleep(ms) {
|
|
12767
|
+
return new Promise((resolve) => {
|
|
12768
|
+
setTimeout(resolve, ms);
|
|
12769
|
+
});
|
|
12770
|
+
}
|
|
12771
|
+
function landedIn(after, input) {
|
|
12772
|
+
if (after.kind !== "module") return false;
|
|
12773
|
+
return input.to === "" ? after.version !== input.from : after.version === input.to;
|
|
12774
|
+
}
|
|
12775
|
+
async function swapExternally(deps, input) {
|
|
12776
|
+
let marker;
|
|
12777
|
+
try {
|
|
12778
|
+
marker = deps.supply.startSelfUpdate({
|
|
12779
|
+
dshHome: deps.dshHome,
|
|
12780
|
+
profile: deps.profile,
|
|
12781
|
+
pkg: input.pkg,
|
|
12782
|
+
from: input.from,
|
|
12783
|
+
to: input.to,
|
|
12784
|
+
pinned: input.pinned
|
|
12785
|
+
}, (message) => deps.logger?.warn?.(`[dsh-workbench] ${message}`)).paths.marker;
|
|
12786
|
+
} catch (error) {
|
|
12787
|
+
deps.logger?.warn?.(`[dsh-workbench] 外部换版起不来(${input.pkg}):${error instanceof Error ? error.message : String(error)}`);
|
|
12788
|
+
return;
|
|
12789
|
+
}
|
|
12790
|
+
const pollMs = deps.pollMs ?? DEFAULT_POLL_MS;
|
|
12791
|
+
const deadline = Date.now() + (deps.waitMs ?? DEFAULT_WAIT_MS);
|
|
12792
|
+
let state = "timeout";
|
|
12793
|
+
while (Date.now() < deadline) {
|
|
12794
|
+
const read = readSelfUpdateMarker(marker);
|
|
12795
|
+
if (read.pkg === input.pkg && read.state !== void 0 && read.state !== "running") {
|
|
12796
|
+
state = read.state;
|
|
12797
|
+
break;
|
|
12798
|
+
}
|
|
12799
|
+
await sleep(pollMs);
|
|
12800
|
+
}
|
|
12801
|
+
const after = deps.installed?.(input.pkg) ?? {
|
|
12802
|
+
kind: "unknown",
|
|
12803
|
+
pkg: input.pkg
|
|
12804
|
+
};
|
|
12805
|
+
const version = after.kind === "module" ? after.version : "读不到";
|
|
12806
|
+
deps.logger?.info?.(`[dsh-workbench] 外部换版 ${input.pkg}:${state},盘上 ${version}`);
|
|
12807
|
+
return landedIn(after, input) ? after : void 0;
|
|
12808
|
+
}
|
|
12809
|
+
//#endregion
|
|
12394
12810
|
//#region src/modules/panels/model/market-update-bundle.ts
|
|
12811
|
+
var SHIPPED_APPLICATIONS = /* @__PURE__ */ new Set([
|
|
12812
|
+
"applied",
|
|
12813
|
+
"restart-required",
|
|
12814
|
+
"overridden"
|
|
12815
|
+
]);
|
|
12816
|
+
function stillStuck(change, after, before) {
|
|
12817
|
+
if (!SHIPPED_APPLICATIONS.has(change.application)) return true;
|
|
12818
|
+
return before !== void 0 && after.kind === "module" && after.version === before;
|
|
12819
|
+
}
|
|
12820
|
+
function remedyOf(deps, change, before, afterVersion, spec) {
|
|
12821
|
+
const name = deps.profileName ?? deps.profile;
|
|
12822
|
+
if (name === void 0) return void 0;
|
|
12823
|
+
if (!!SHIPPED_APPLICATIONS.has(change.application) && !(before !== void 0 && afterVersion === before)) return void 0;
|
|
12824
|
+
return { command: `dsh plugin --profile ${name} add ${quoteShellArg(spec)}` };
|
|
12825
|
+
}
|
|
12395
12826
|
async function restoreRemovedBundle(pkg, before, registry, deps) {
|
|
12396
12827
|
if (before === void 0) return void 0;
|
|
12397
12828
|
try {
|
|
@@ -12446,7 +12877,7 @@ async function updateModuleBundle(unit, deps) {
|
|
|
12446
12877
|
kind: "unknown",
|
|
12447
12878
|
pkg
|
|
12448
12879
|
}).kind === "module" ? void 0 : await restoreRemovedBundle(pkg, before, registry, deps);
|
|
12449
|
-
|
|
12880
|
+
let after = deps.installed?.(pkg) ?? {
|
|
12450
12881
|
kind: "unknown",
|
|
12451
12882
|
pkg
|
|
12452
12883
|
};
|
|
@@ -12456,12 +12887,43 @@ async function updateModuleBundle(unit, deps) {
|
|
|
12456
12887
|
after,
|
|
12457
12888
|
...restored === void 0 ? {} : { restored }
|
|
12458
12889
|
});
|
|
12890
|
+
let change = verified.change;
|
|
12891
|
+
let restoredVersion = verified.restored;
|
|
12892
|
+
const swap = stillStuck(change, after, before) && before !== void 0 && deps.dshHome !== void 0 && deps.profileName !== void 0 ? await swapExternally({
|
|
12893
|
+
supply,
|
|
12894
|
+
installed: deps.installed,
|
|
12895
|
+
dshHome: deps.dshHome,
|
|
12896
|
+
profile: deps.profileName,
|
|
12897
|
+
...deps.logger === void 0 ? {} : { logger: deps.logger },
|
|
12898
|
+
...deps.swapWaitMs === void 0 ? {} : { waitMs: deps.swapWaitMs },
|
|
12899
|
+
...deps.swapPollMs === void 0 ? {} : { pollMs: deps.swapPollMs }
|
|
12900
|
+
}, {
|
|
12901
|
+
pkg,
|
|
12902
|
+
from: before,
|
|
12903
|
+
to: latest ?? "",
|
|
12904
|
+
pinned: latest !== void 0
|
|
12905
|
+
}) : void 0;
|
|
12906
|
+
if (swap !== void 0) {
|
|
12907
|
+
const swapVersion = swap.kind === "module" ? swap.version : "";
|
|
12908
|
+
after = swap;
|
|
12909
|
+
restoredVersion = void 0;
|
|
12910
|
+
change = {
|
|
12911
|
+
application: "restart-required",
|
|
12912
|
+
stage: "install",
|
|
12913
|
+
changed: true,
|
|
12914
|
+
target: pkg,
|
|
12915
|
+
bundle: pkg,
|
|
12916
|
+
...swapVersion === "" ? {} : { version: swapVersion }
|
|
12917
|
+
};
|
|
12918
|
+
}
|
|
12459
12919
|
await deps.refreshSupply(unit).catch(() => void 0);
|
|
12920
|
+
const remedy = stillStuck(change, after, before) ? remedyOf(deps, change, before, after.kind === "module" ? after.version : void 0, spec) : void 0;
|
|
12460
12921
|
return {
|
|
12461
12922
|
...outcome,
|
|
12462
|
-
change
|
|
12923
|
+
change,
|
|
12463
12924
|
module: after,
|
|
12464
|
-
...
|
|
12925
|
+
...restoredVersion === void 0 ? {} : { restored: restoredVersion },
|
|
12926
|
+
...remedy === void 0 ? {} : { remedy }
|
|
12465
12927
|
};
|
|
12466
12928
|
}
|
|
12467
12929
|
//#endregion
|
|
@@ -12778,14 +13240,14 @@ function createPluginActions(options) {
|
|
|
12778
13240
|
unreadable: []
|
|
12779
13241
|
};
|
|
12780
13242
|
}
|
|
12781
|
-
function decide(entry, held) {
|
|
13243
|
+
function decide(entry, held, claimedBy) {
|
|
12782
13244
|
const plugin = pluginNameOf(entry);
|
|
12783
13245
|
if (plugin === void 0) return {
|
|
12784
13246
|
act: "keep",
|
|
12785
13247
|
kept: { kind: "unnamed" }
|
|
12786
13248
|
};
|
|
12787
13249
|
const refs = referencesOf();
|
|
12788
|
-
const by = options.claimedBy?.(plugin) ?? [];
|
|
13250
|
+
const by = claimedBy?.(plugin) ?? options.claimedBy?.(plugin) ?? [];
|
|
12789
13251
|
const retention = pluginRetentionOf({
|
|
12790
13252
|
held,
|
|
12791
13253
|
claimedBy: by,
|
|
@@ -12852,12 +13314,12 @@ function createPluginActions(options) {
|
|
|
12852
13314
|
name: decision.plugin
|
|
12853
13315
|
};
|
|
12854
13316
|
},
|
|
12855
|
-
async sweep() {
|
|
13317
|
+
async sweep(claimedBy) {
|
|
12856
13318
|
if (ledger === void 0) return [];
|
|
12857
13319
|
const removed = [];
|
|
12858
13320
|
for (const entry of ledger.all()) {
|
|
12859
13321
|
if (entry.releasedAt === void 0) continue;
|
|
12860
|
-
const decision = decide(entry, false);
|
|
13322
|
+
const decision = decide(entry, false, claimedBy);
|
|
12861
13323
|
if (decision.act === "keep") continue;
|
|
12862
13324
|
try {
|
|
12863
13325
|
await removeFiles(decision.plugin);
|
|
@@ -12886,17 +13348,18 @@ function createEnvDropper(options) {
|
|
|
12886
13348
|
unreadable: []
|
|
12887
13349
|
};
|
|
12888
13350
|
}
|
|
12889
|
-
function usersOf(plugin, except) {
|
|
12890
|
-
return catalog().filter((item) => item.id !== except && !(isReleased?.(item.id) ?? false) && needsOf(item).some((need) => need.kind === "extension" && need.name === plugin)).map((item) => item.id);
|
|
13351
|
+
function usersOf(plugin, except, roster) {
|
|
13352
|
+
return (roster ?? catalog()).filter((item) => item.id !== except && !(isReleased?.(item.id) ?? false) && needsOf(item).some((need) => need.kind === "extension" && need.name === plugin)).map((item) => item.id);
|
|
12891
13353
|
}
|
|
12892
|
-
function libUsersOf(name, unit) {
|
|
12893
|
-
return
|
|
13354
|
+
function libUsersOf(name, unit, roster) {
|
|
13355
|
+
return roster.filter((item) => item.id !== unit && !(isReleased?.(item.id) ?? false) && needsOf(item).some((need) => need.kind === "lib" && need.name === name)).map((item) => item.id);
|
|
12894
13356
|
}
|
|
12895
13357
|
return {
|
|
12896
13358
|
usersOf,
|
|
12897
|
-
async drop(unit, needs, before) {
|
|
13359
|
+
async drop(unit, needs, before, roster) {
|
|
12898
13360
|
const kept = /* @__PURE__ */ new Set();
|
|
12899
|
-
const
|
|
13361
|
+
const known = roster ?? catalog();
|
|
13362
|
+
const index = buildSupplyIndex(known.filter((item) => options.isReleased?.(item.id) !== true).map((item) => ({
|
|
12900
13363
|
id: item.id,
|
|
12901
13364
|
needs: needsOf(item)
|
|
12902
13365
|
})), { env: process.env });
|
|
@@ -12905,7 +13368,7 @@ function createEnvDropper(options) {
|
|
|
12905
13368
|
const onMachine = new Set(before.needs.filter((state) => state.kind === "extension" && state.status !== "missing").map((state) => state.name));
|
|
12906
13369
|
const unknown = !before.needs.some((state) => state.kind === "extension");
|
|
12907
13370
|
for (const need of extensions) {
|
|
12908
|
-
const users = usersOf(need.name, unit);
|
|
13371
|
+
const users = usersOf(need.name, unit, known);
|
|
12909
13372
|
const retention = pluginRetentionOf({
|
|
12910
13373
|
held: ledger?.heldAsPlugin(need.name) !== void 0,
|
|
12911
13374
|
claimedBy: users,
|
|
@@ -12925,7 +13388,7 @@ function createEnvDropper(options) {
|
|
|
12925
13388
|
await run(`${quoteShellArg(command)} plugin uninstall ${quoteShellArg(need.name)}`);
|
|
12926
13389
|
}
|
|
12927
13390
|
for (const need of libs) {
|
|
12928
|
-
const keepers = libUsersOf(need.name, unit);
|
|
13391
|
+
const keepers = libUsersOf(need.name, unit, known);
|
|
12929
13392
|
if (keepers.length > 0) {
|
|
12930
13393
|
for (const id of keepers) kept.add(id);
|
|
12931
13394
|
continue;
|
|
@@ -13234,7 +13697,8 @@ function createMarketService(options) {
|
|
|
13234
13697
|
pkg
|
|
13235
13698
|
};
|
|
13236
13699
|
const unit = module.kind === "module" ? module.unit : void 0;
|
|
13237
|
-
const
|
|
13700
|
+
const roster = catalog();
|
|
13701
|
+
const manifest = unit === void 0 ? void 0 : roster.find((item) => item.id === unit);
|
|
13238
13702
|
const before = unit === void 0 ? void 0 : await capabilityOf(unit, true);
|
|
13239
13703
|
const change = await removeDshBundle({
|
|
13240
13704
|
manager,
|
|
@@ -13247,8 +13711,8 @@ function createMarketService(options) {
|
|
|
13247
13711
|
change,
|
|
13248
13712
|
keptFor: []
|
|
13249
13713
|
};
|
|
13250
|
-
const keptFor = manifest === void 0 || before === void 0 ? [] : await envDropper.drop(unit, needsOf(manifest), before);
|
|
13251
|
-
await plugins.sweep();
|
|
13714
|
+
const keptFor = manifest === void 0 || before === void 0 ? [] : await envDropper.drop(unit, needsOf(manifest), before, roster);
|
|
13715
|
+
await plugins.sweep((plugin) => envDropper.usersOf(plugin, unit, roster));
|
|
13252
13716
|
onUninstalled?.(unit);
|
|
13253
13717
|
return {
|
|
13254
13718
|
change,
|
|
@@ -13260,15 +13724,22 @@ function createMarketService(options) {
|
|
|
13260
13724
|
},
|
|
13261
13725
|
async updateBundle(unit) {
|
|
13262
13726
|
const manager = requireBundles();
|
|
13727
|
+
const home = dshHome?.();
|
|
13728
|
+
const profile = profileName?.() ?? (profileDir === void 0 || profileDir === null ? void 0 : basename(profileDir));
|
|
13263
13729
|
return updateModuleBundle(unit, {
|
|
13264
13730
|
supply,
|
|
13265
13731
|
modules,
|
|
13266
13732
|
installed,
|
|
13267
13733
|
shell,
|
|
13268
13734
|
manager,
|
|
13735
|
+
...profile === void 0 ? {} : {
|
|
13736
|
+
profile,
|
|
13737
|
+
profileName: profile
|
|
13738
|
+
},
|
|
13739
|
+
...home === void 0 ? {} : { dshHome: home },
|
|
13269
13740
|
routeDeps: routeDeps(manager),
|
|
13270
13741
|
sweep: async () => {
|
|
13271
|
-
await plugins.sweep();
|
|
13742
|
+
await plugins.sweep((plugin) => envDropper.usersOf(plugin, void 0, catalog()));
|
|
13272
13743
|
},
|
|
13273
13744
|
refreshSupply: async (id) => {
|
|
13274
13745
|
await service.update(id);
|
|
@@ -14000,7 +14471,7 @@ function createMarketIndexReader(options) {
|
|
|
14000
14471
|
}
|
|
14001
14472
|
};
|
|
14002
14473
|
const repo = repoOf(options.root, source);
|
|
14003
|
-
const path = (source.path ?? "market
|
|
14474
|
+
const path = ((source.path ?? "market").replace(/^\/+/, "") || "market").replace(/\/+$/, "");
|
|
14004
14475
|
const ref = (source.ref ?? "").trim();
|
|
14005
14476
|
let revision = "FETCH_HEAD";
|
|
14006
14477
|
mkdirSync(directoryOf(options.root, source), { recursive: true });
|
|
@@ -14025,17 +14496,6 @@ function createMarketIndexReader(options) {
|
|
|
14025
14496
|
detail: "仓库还没有任何提交"
|
|
14026
14497
|
}
|
|
14027
14498
|
}, repo);
|
|
14028
|
-
const shown = await run(`git -C ${quoteShellArg(repo)} show ${revision}:${quoteShellArg(path)}`);
|
|
14029
|
-
if (!shown.ok) {
|
|
14030
|
-
if (shown.problem.kind === "git-file") return withBranch({
|
|
14031
|
-
ok: false,
|
|
14032
|
-
problem: {
|
|
14033
|
-
kind: "git-missing-index",
|
|
14034
|
-
detail: `索引文件 ${path} 不在仓库里`
|
|
14035
|
-
}
|
|
14036
|
-
}, repo);
|
|
14037
|
-
return shown;
|
|
14038
|
-
}
|
|
14039
14499
|
const head = await run(`git -C ${quoteShellArg(repo)} rev-parse ${revision}`);
|
|
14040
14500
|
if (!head.ok) return head;
|
|
14041
14501
|
const commit = head.stdout.trim();
|
|
@@ -14046,26 +14506,105 @@ function createMarketIndexReader(options) {
|
|
|
14046
14506
|
detail: `读到的 commit 不是 40 位:${commit}`
|
|
14047
14507
|
}
|
|
14048
14508
|
};
|
|
14049
|
-
const
|
|
14050
|
-
if (!
|
|
14509
|
+
const listed = await run(`git -C ${quoteShellArg(repo)} ls-tree -r ${quoteShellArg(commit)} -- ${quoteShellArg(path)}`);
|
|
14510
|
+
if (!listed.ok) {
|
|
14511
|
+
if (listed.problem.kind === "git-file") return withBranch({
|
|
14512
|
+
ok: false,
|
|
14513
|
+
problem: {
|
|
14514
|
+
kind: "git-missing-index",
|
|
14515
|
+
detail: `索引 ${path} 不在仓库里`
|
|
14516
|
+
}
|
|
14517
|
+
}, repo);
|
|
14518
|
+
return listed;
|
|
14519
|
+
}
|
|
14520
|
+
const rows = listed.stdout.split("\n").map((line) => line.trim()).filter((line) => line !== "").flatMap((line) => {
|
|
14521
|
+
const [meta, file] = line.split(" ");
|
|
14522
|
+
const type = (meta ?? "").split(" ")[1] ?? "";
|
|
14523
|
+
return file === void 0 ? [] : [{
|
|
14524
|
+
type,
|
|
14525
|
+
file
|
|
14526
|
+
}];
|
|
14527
|
+
});
|
|
14528
|
+
const legacy = rows.find((one) => one.type === "blob" && one.file === path);
|
|
14529
|
+
const entries = rows.filter((one) => one.type === "blob" && one.file !== path && one.file.startsWith(`${path}/`) && one.file.endsWith(".json"));
|
|
14530
|
+
const finish = async (index, text) => {
|
|
14531
|
+
const fetchedAt = now();
|
|
14532
|
+
await writeFileAtomic(indexFileOf(options.root, source), text);
|
|
14533
|
+
await writeMeta(metaFileOf(options.root, source), {
|
|
14534
|
+
fetchedAt,
|
|
14535
|
+
commit
|
|
14536
|
+
});
|
|
14537
|
+
return {
|
|
14538
|
+
ok: true,
|
|
14539
|
+
index,
|
|
14540
|
+
fetchedAt,
|
|
14541
|
+
commit
|
|
14542
|
+
};
|
|
14543
|
+
};
|
|
14544
|
+
const readLegacy = async (file) => {
|
|
14545
|
+
const shown = await run(`git -C ${quoteShellArg(repo)} show ${quoteShellArg(commit)}:${quoteShellArg(file)}`);
|
|
14546
|
+
if (!shown.ok) return void 0;
|
|
14547
|
+
const parsed = parseMarketIndexJson(shown.stdout);
|
|
14548
|
+
if (!parsed.ok) return {
|
|
14549
|
+
ok: false,
|
|
14550
|
+
problem: {
|
|
14551
|
+
kind: "format",
|
|
14552
|
+
detail: parsed.problems.map((one) => one.detail).join(";")
|
|
14553
|
+
}
|
|
14554
|
+
};
|
|
14555
|
+
return finish(parsed.index, shown.stdout);
|
|
14556
|
+
};
|
|
14557
|
+
if (legacy !== void 0) {
|
|
14558
|
+
const outcome = await readLegacy(path);
|
|
14559
|
+
if (outcome !== void 0) return outcome;
|
|
14560
|
+
}
|
|
14561
|
+
if (entries.length > 0) {
|
|
14562
|
+
const parts = [];
|
|
14563
|
+
let failed;
|
|
14564
|
+
const queue = [...entries];
|
|
14565
|
+
const worker = async () => {
|
|
14566
|
+
for (;;) {
|
|
14567
|
+
const next = queue.shift();
|
|
14568
|
+
if (next === void 0 || failed !== void 0) return;
|
|
14569
|
+
const shown = await run(`git -C ${quoteShellArg(repo)} show ${quoteShellArg(commit)}:${quoteShellArg(next.file)}`);
|
|
14570
|
+
if (!shown.ok) {
|
|
14571
|
+
failed = shown;
|
|
14572
|
+
return;
|
|
14573
|
+
}
|
|
14574
|
+
parts.push({
|
|
14575
|
+
path: next.file,
|
|
14576
|
+
text: shown.stdout
|
|
14577
|
+
});
|
|
14578
|
+
}
|
|
14579
|
+
};
|
|
14580
|
+
await Promise.all(Array.from({ length: Math.min(6, entries.length) }, () => worker()));
|
|
14581
|
+
if (failed !== void 0) return failed;
|
|
14582
|
+
const assembled = assembleMarketIndex(parts);
|
|
14583
|
+
if (!assembled.ok) return {
|
|
14584
|
+
ok: false,
|
|
14585
|
+
problem: {
|
|
14586
|
+
kind: "format",
|
|
14587
|
+
detail: assembled.problems.map((one) => one.detail).join(";")
|
|
14588
|
+
}
|
|
14589
|
+
};
|
|
14590
|
+
return finish(assembled.index, `${JSON.stringify(assembled.index, null, 2)}\n`);
|
|
14591
|
+
}
|
|
14592
|
+
if (listed.stdout.trim() !== "") {
|
|
14593
|
+
const empty = {
|
|
14594
|
+
format: 1,
|
|
14595
|
+
plugins: []
|
|
14596
|
+
};
|
|
14597
|
+
return finish(empty, `${JSON.stringify(empty, null, 2)}\n`);
|
|
14598
|
+
}
|
|
14599
|
+
const legacyText = await readLegacy(MARKET_INDEX_PATH);
|
|
14600
|
+
if (legacyText !== void 0) return legacyText;
|
|
14601
|
+
return withBranch({
|
|
14051
14602
|
ok: false,
|
|
14052
14603
|
problem: {
|
|
14053
|
-
kind: "
|
|
14054
|
-
detail:
|
|
14604
|
+
kind: "git-missing-index",
|
|
14605
|
+
detail: `索引 ${path} 不在仓库里`
|
|
14055
14606
|
}
|
|
14056
|
-
};
|
|
14057
|
-
const fetchedAt = now();
|
|
14058
|
-
await writeFileAtomic(indexFileOf(options.root, source), shown.stdout);
|
|
14059
|
-
await writeMeta(metaFileOf(options.root, source), {
|
|
14060
|
-
fetchedAt,
|
|
14061
|
-
commit
|
|
14062
|
-
});
|
|
14063
|
-
return {
|
|
14064
|
-
ok: true,
|
|
14065
|
-
index: parsed.index,
|
|
14066
|
-
fetchedAt,
|
|
14067
|
-
commit
|
|
14068
|
-
};
|
|
14607
|
+
}, repo);
|
|
14069
14608
|
}
|
|
14070
14609
|
async function read(source, callOptions = {}) {
|
|
14071
14610
|
const key = marketSourceKey(source);
|
|
@@ -14413,16 +14952,31 @@ function createMarketSourceInitializer(options) {
|
|
|
14413
14952
|
], workdir);
|
|
14414
14953
|
if (created.exitCode !== 0) throw new Error(`建分支 ${branch} 没过:${tailLines(created.stderr, 4)}`);
|
|
14415
14954
|
}
|
|
14416
|
-
const indexPath = (source.path ?? "market
|
|
14417
|
-
const
|
|
14955
|
+
const indexPath = ((source.path ?? "market").replace(/^\/+/, "") || "market").replace(/\/+$/, "");
|
|
14956
|
+
const indexDir = join(workdir, indexPath);
|
|
14957
|
+
const indexMarker = join(indexDir, "README.md");
|
|
14418
14958
|
const stampOnly = request.stampOnly === true;
|
|
14419
|
-
if (stampOnly && !existsSync(
|
|
14959
|
+
if (stampOnly && !existsSync(indexDir)) throw new Error(`仓库里还没有 ${indexPath}/ —— 先选中这个源,按发现页的提示把它初始化成索引`);
|
|
14420
14960
|
const written = [];
|
|
14421
14961
|
if (!stampOnly) {
|
|
14422
|
-
|
|
14423
|
-
|
|
14424
|
-
|
|
14425
|
-
|
|
14962
|
+
const legacyFile = join(workdir, "market.json");
|
|
14963
|
+
if (indexPath !== "market.json" && existsSync(legacyFile)) {
|
|
14964
|
+
const migrated = marketEntryFilesOfLegacy(readFileSync(legacyFile, "utf8"), indexPath);
|
|
14965
|
+
if (migrated !== void 0) {
|
|
14966
|
+
for (const file of migrated) {
|
|
14967
|
+
const target = join(workdir, ...file.path.split("/"));
|
|
14968
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
14969
|
+
writeFileSync(target, file.text);
|
|
14970
|
+
written.push(file.path);
|
|
14971
|
+
}
|
|
14972
|
+
rmSync(legacyFile);
|
|
14973
|
+
written.push("market.json");
|
|
14974
|
+
}
|
|
14975
|
+
}
|
|
14976
|
+
if (!existsSync(indexMarker)) {
|
|
14977
|
+
mkdirSync(indexDir, { recursive: true });
|
|
14978
|
+
writeFileSync(indexMarker, MARKET_ENTRIES_README_LINES.join("\n"));
|
|
14979
|
+
written.push(`${indexPath}/README.md`);
|
|
14426
14980
|
}
|
|
14427
14981
|
const readmeFile = join(workdir, "README.md");
|
|
14428
14982
|
if (!existsSync(readmeFile)) {
|
|
@@ -14691,7 +15245,7 @@ function failureText(change) {
|
|
|
14691
15245
|
const code = change.errorCode ?? change.application;
|
|
14692
15246
|
if (code === "already-installed") return "它已经在这份 profile 里,而这次没能把它卸下来(这一版 dsh 不肯就地覆盖装,得先卸再装)";
|
|
14693
15247
|
const surface = hostSurface();
|
|
14694
|
-
return REMOVE_BLOCKED.has(code) ?
|
|
15248
|
+
return REMOVE_BLOCKED.has(code) ? `旧版卸不掉(dsh 不肯就地覆盖正在用的模块),自动从外部换也没成 —— 让用户从终端跑 dsh plugin --profile <名字> add <包>@<版本>,跑完${surface.restartHint}` : code;
|
|
14695
15249
|
}
|
|
14696
15250
|
function describeInstall(spec, module, supply, change) {
|
|
14697
15251
|
return `${change.application === "applied" ? "装好了,已生效" : change.application === "restart-required" ? `装好了,要${hostSurface().restartHint} 才生效` : `没成功:${failureText(change)}`}:${module.kind === "module" ? `${module.unit}(${module.pkg})` : module.pkg}${supply.state === "installed" ? ";它要的 opencli 插件也装上了" : supply.state === "pending-restart" ? ";重启后再装它要的 opencli 插件" : supply.state === "not-a-module" ? ";但它不是工作台模块(没有 dsh.workbench 声明,不会出现在面板上)" : supply.state === "failed" ? `;但它要的插件没装上:${supply.reason}` : ""}(spec:${spec})`;
|
|
@@ -14825,6 +15379,14 @@ function registerMarketTools(ctx, market, sources) {
|
|
|
14825
15379
|
async execute(raw) {
|
|
14826
15380
|
const id = await idOrList(raw, MODULES_UNINSTALL_TOOL);
|
|
14827
15381
|
const outcome = await market.uninstall(id);
|
|
15382
|
+
const change = outcome.change;
|
|
15383
|
+
if (change !== void 0 && change.application !== "applied") {
|
|
15384
|
+
const code = change.errorCode ?? change.application;
|
|
15385
|
+
return {
|
|
15386
|
+
module: outcome.entry,
|
|
15387
|
+
text: `没卸下来:${id} 还在这份 profile 里(${code})—— 这一版 dsh 不肯卸正在加载的模块。让用户从终端跑 dsh plugin --profile <profile> remove ${id} 的包,跑完重启一次工作台;别再调这个工具重试。`
|
|
15388
|
+
};
|
|
15389
|
+
}
|
|
14828
15390
|
const kept = outcome.keptFor.length === 0 ? "" : `(还有模块在面板上要用其中一份安装,所以那份留着:${outcome.keptFor.join("、")})`;
|
|
14829
15391
|
return {
|
|
14830
15392
|
module: outcome.entry,
|
|
@@ -14980,7 +15542,7 @@ function registerMarketTools(ctx, market, sources) {
|
|
|
14980
15542
|
branch: result.branch,
|
|
14981
15543
|
commit: result.commit,
|
|
14982
15544
|
changed: true,
|
|
14983
|
-
text: `已把「${source.label}」设为可发布(${source.url},分支 ${result.branch})—— 仓库本身没动。之后发布可以落到它的 ${source.path ?? "market
|
|
15545
|
+
text: `已把「${source.label}」设为可发布(${source.url},分支 ${result.branch})—— 仓库本身没动。之后发布可以落到它的 ${source.path ?? "market"}/。`
|
|
14984
15546
|
};
|
|
14985
15547
|
}
|
|
14986
15548
|
};
|
|
@@ -17022,6 +17584,7 @@ function notifyLongTaskStop(agents, unit, change) {
|
|
|
17022
17584
|
//#endregion
|
|
17023
17585
|
//#region src/app/host.ts
|
|
17024
17586
|
var name = "dsh-workbench";
|
|
17587
|
+
var LIB_OPENCLI_TIMEOUT_MS = 18e5;
|
|
17025
17588
|
var inject = ["webServer"];
|
|
17026
17589
|
async function defaultAdapterSet(ctx) {
|
|
17027
17590
|
return (await import("@dsh-workbench/adapters-default")).createAdapterSet(ctx);
|
|
@@ -17042,6 +17605,7 @@ async function apply(ctx, config) {
|
|
|
17042
17605
|
else if (loaded.failure !== "not-configured") ctx.logger?.warn?.(`[dsh-workbench] 适配器集合没生效(${loaded.failure}):${loaded.detail};先用默认集合`);
|
|
17043
17606
|
ctx.provide(WORKBENCH_ADAPTERS, adapters);
|
|
17044
17607
|
const opencli = adapters.runtime.opencliCommand(ctx);
|
|
17608
|
+
const libRunners = adapters.libs === void 0 ? void 0 : withLibRunners(adapters.libs, { opencli: opencliRunnerOf((request) => adapters.data.read(request), { timeoutMs: LIB_OPENCLI_TIMEOUT_MS }) }, ctx.logger);
|
|
17045
17609
|
const shell = ctx.get("shell");
|
|
17046
17610
|
const routes = createHostRoutes({
|
|
17047
17611
|
ctx,
|
|
@@ -17218,7 +17782,7 @@ async function apply(ctx, config) {
|
|
|
17218
17782
|
installable: () => installableUnitsOf(units),
|
|
17219
17783
|
store,
|
|
17220
17784
|
data: adapters.data,
|
|
17221
|
-
libs:
|
|
17785
|
+
libs: libRunners,
|
|
17222
17786
|
git: adapters.git,
|
|
17223
17787
|
commands: adapters.commands,
|
|
17224
17788
|
probe: (unit, options) => probeOne(unit, options?.force ?? false),
|