@dsh-workbench/core 1.1.5 → 1.1.6
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 +704 -520
- package/dist/client/client.body.js +704 -520
- package/dist/client/index.js +575 -391
- package/dist/host/index.js +613 -149
- 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/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";
|
|
@@ -4896,6 +4896,11 @@ function checkVersionRange(contract, version) {
|
|
|
4896
4896
|
if (parsed === null) return "malformed";
|
|
4897
4897
|
return import_semver.default.satisfies(parsed, range) ? "satisfied" : "unsatisfied";
|
|
4898
4898
|
}
|
|
4899
|
+
//#endregion
|
|
4900
|
+
//#region ../shared/dist/contract/protocol/market-index.js
|
|
4901
|
+
var MARKET_INDEX_DIR = "market";
|
|
4902
|
+
var MARKET_ARTIFACTS_DIR = "artifacts";
|
|
4903
|
+
var MARKET_INDEX_PATH = "market.json";
|
|
4899
4904
|
var MARKET_DISCOVER_ROUTE = "panels/market-discover";
|
|
4900
4905
|
var MARKET_DOC_ROUTE = "panels/market-doc";
|
|
4901
4906
|
var MARKET_GIT_PROBE_ROUTE = "panels/git-probe";
|
|
@@ -4917,34 +4922,39 @@ function marketText(value, locale) {
|
|
|
4917
4922
|
return locale.toLowerCase().startsWith("zh") ? zh || en : en || zh;
|
|
4918
4923
|
}
|
|
4919
4924
|
//#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
|
-
];
|
|
4925
|
+
//#region ../shared/dist/host/market-index-parse.js
|
|
4945
4926
|
var NPM_NAME = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/;
|
|
4946
4927
|
var GIT_COMMIT = /^[0-9a-f]{40}$/;
|
|
4947
4928
|
var MAX_ICON_CHARS = 32768;
|
|
4929
|
+
function compareSemver(left, right) {
|
|
4930
|
+
return compareVersions(left, right);
|
|
4931
|
+
}
|
|
4932
|
+
function httpUrlProblem(url) {
|
|
4933
|
+
let parsed;
|
|
4934
|
+
try {
|
|
4935
|
+
parsed = new URL(url);
|
|
4936
|
+
} catch {
|
|
4937
|
+
return "不是合法 URL";
|
|
4938
|
+
}
|
|
4939
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return "只收 http / https";
|
|
4940
|
+
if (parsed.username !== "" || parsed.password !== "") return "URL 不能带凭据";
|
|
4941
|
+
if (parsed.search !== "" || parsed.hash !== "") return "URL 不能带 query / fragment";
|
|
4942
|
+
}
|
|
4943
|
+
function gitUrlOk(url) {
|
|
4944
|
+
if (url === "") return false;
|
|
4945
|
+
if (/^[^@\s]+@[^@\s:]+:[^@\s]+$/.test(url)) return true;
|
|
4946
|
+
let parsed;
|
|
4947
|
+
try {
|
|
4948
|
+
parsed = new URL(url);
|
|
4949
|
+
} catch {
|
|
4950
|
+
return false;
|
|
4951
|
+
}
|
|
4952
|
+
if (parsed.protocol !== "https:" && parsed.protocol !== "ssh:" && parsed.protocol !== "http:") return false;
|
|
4953
|
+
if (parsed.password !== "") return false;
|
|
4954
|
+
if (parsed.username !== "" && parsed.protocol !== "ssh:") return false;
|
|
4955
|
+
if (parsed.search !== "" || parsed.hash !== "") return false;
|
|
4956
|
+
return true;
|
|
4957
|
+
}
|
|
4948
4958
|
function asRecord$4(value) {
|
|
4949
4959
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
4950
4960
|
}
|
|
@@ -4964,22 +4974,19 @@ function asLocalized(value) {
|
|
|
4964
4974
|
...en === "" ? {} : { en }
|
|
4965
4975
|
};
|
|
4966
4976
|
}
|
|
4967
|
-
function problem(kind, detail, at) {
|
|
4977
|
+
function problem$1(kind, detail, at) {
|
|
4968
4978
|
return {
|
|
4969
4979
|
kind,
|
|
4970
4980
|
detail,
|
|
4971
4981
|
...at === void 0 ? {} : { at }
|
|
4972
4982
|
};
|
|
4973
4983
|
}
|
|
4974
|
-
function compareSemver(left, right) {
|
|
4975
|
-
return compareVersions(left, right);
|
|
4976
|
-
}
|
|
4977
4984
|
function sourceOf(raw, at) {
|
|
4978
4985
|
const record = asRecord$4(raw);
|
|
4979
|
-
if (record === void 0) return problem("source", "source 必须是一个对象", at);
|
|
4986
|
+
if (record === void 0) return problem$1("source", "source 必须是一个对象", at);
|
|
4980
4987
|
if (record["type"] === "npm") {
|
|
4981
4988
|
const registry = record["registry"];
|
|
4982
|
-
if (registry !== void 0 && asText$4(registry) === "") return problem("source", "npm source 的 registry 必须是非空字符串", at);
|
|
4989
|
+
if (registry !== void 0 && asText$4(registry) === "") return problem$1("source", "npm source 的 registry 必须是非空字符串", at);
|
|
4983
4990
|
return {
|
|
4984
4991
|
type: "npm",
|
|
4985
4992
|
...registry === void 0 ? {} : { registry: asText$4(registry) }
|
|
@@ -4988,8 +4995,8 @@ function sourceOf(raw, at) {
|
|
|
4988
4995
|
if (record["type"] === "git") {
|
|
4989
4996
|
const url = asText$4(record["url"]);
|
|
4990
4997
|
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);
|
|
4998
|
+
if (url === "") return problem$1("source", "git source 缺 url", at);
|
|
4999
|
+
if (!GIT_COMMIT.test(commit)) return problem$1("git-commit", `git source 的 commit 必须是 40 位小写 SHA(现在是 ${JSON.stringify(commit)})`, at);
|
|
4993
5000
|
const path = record["path"];
|
|
4994
5001
|
return {
|
|
4995
5002
|
type: "git",
|
|
@@ -4998,17 +5005,17 @@ function sourceOf(raw, at) {
|
|
|
4998
5005
|
...path === void 0 || asText$4(path) === "" ? {} : { path: asText$4(path) }
|
|
4999
5006
|
};
|
|
5000
5007
|
}
|
|
5001
|
-
return problem("source", "source.type 只能是 npm 或 git", at);
|
|
5008
|
+
return problem$1("source", "source.type 只能是 npm 或 git", at);
|
|
5002
5009
|
}
|
|
5003
5010
|
function versionOf(raw, at) {
|
|
5004
5011
|
const record = asRecord$4(raw);
|
|
5005
|
-
if (record === void 0) return problem("version", "版本必须是一个对象", at);
|
|
5012
|
+
if (record === void 0) return problem$1("version", "版本必须是一个对象", at);
|
|
5006
5013
|
const version = asText$4(record["version"]);
|
|
5007
|
-
if (!isValidVersion(version)) return problem("version", `版本号不是合法 SemVer:${JSON.stringify(version)}`, at);
|
|
5014
|
+
if (!isValidVersion(version)) return problem$1("version", `版本号不是合法 SemVer:${JSON.stringify(version)}`, at);
|
|
5008
5015
|
const source = sourceOf(record["source"], `${at}.source`);
|
|
5009
5016
|
if ("kind" in source) return source;
|
|
5010
5017
|
const timestamp = record["publishTimestamp"];
|
|
5011
|
-
if (timestamp !== void 0 && (!Number.isInteger(timestamp) || timestamp < 0)) return problem("timestamp", "publishTimestamp 必须是 Unix 秒整数", at);
|
|
5018
|
+
if (timestamp !== void 0 && (!Number.isInteger(timestamp) || timestamp < 0)) return problem$1("timestamp", "publishTimestamp 必须是 Unix 秒整数", at);
|
|
5012
5019
|
const engines = asRecord$4(record["engines"]);
|
|
5013
5020
|
const dsh = engines === void 0 ? "" : asText$4(engines["dsh"]);
|
|
5014
5021
|
return {
|
|
@@ -5019,33 +5026,33 @@ function versionOf(raw, at) {
|
|
|
5019
5026
|
};
|
|
5020
5027
|
}
|
|
5021
5028
|
function versionsOf$1(raw, at) {
|
|
5022
|
-
if (!Array.isArray(raw) || raw.length === 0) return problem("versions", "versions 必须是非空数组", at);
|
|
5029
|
+
if (!Array.isArray(raw) || raw.length === 0) return problem$1("versions", "versions 必须是非空数组", at);
|
|
5023
5030
|
const versions = [];
|
|
5024
5031
|
const seen = /* @__PURE__ */ new Set();
|
|
5025
5032
|
for (const [index, item] of raw.entries()) {
|
|
5026
5033
|
const parsed = versionOf(item, `${at}[${String(index)}]`);
|
|
5027
5034
|
if ("kind" in parsed) return parsed;
|
|
5028
|
-
if (seen.has(parsed.version)) return problem("versions", `版本重复:${parsed.version}`, at);
|
|
5035
|
+
if (seen.has(parsed.version)) return problem$1("versions", `版本重复:${parsed.version}`, at);
|
|
5029
5036
|
seen.add(parsed.version);
|
|
5030
5037
|
versions.push(parsed);
|
|
5031
5038
|
}
|
|
5032
5039
|
for (let index = 1; index < versions.length; index += 1) {
|
|
5033
5040
|
const order = compareSemver(versions[index - 1]?.version ?? "", versions[index]?.version ?? "");
|
|
5034
|
-
if (order === void 0 || order < 0) return problem("version-order", "versions 必须按 SemVer 降序", at);
|
|
5041
|
+
if (order === void 0 || order < 0) return problem$1("version-order", "versions 必须按 SemVer 降序", at);
|
|
5035
5042
|
}
|
|
5036
5043
|
return versions;
|
|
5037
5044
|
}
|
|
5038
5045
|
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");
|
|
5046
|
+
if (icon.length > MAX_ICON_CHARS) return problem$1("icon", "icon 超过 32 KiB");
|
|
5047
|
+
if (icon.startsWith("data:image/")) return icon.includes(";base64,") ? void 0 : problem$1("icon", "data URL 必须是 base64");
|
|
5041
5048
|
if (/^https?:\/\//i.test(icon)) return void 0;
|
|
5042
|
-
return problem("icon", "icon 只能是 base64 data URL 或 http(s) URL");
|
|
5049
|
+
return problem$1("icon", "icon 只能是 base64 data URL 或 http(s) URL");
|
|
5043
5050
|
}
|
|
5044
5051
|
var MAX_OWNER_CHARS = 200;
|
|
5045
5052
|
var UNIT_ID = /^[a-z][a-z0-9-]*$/;
|
|
5046
5053
|
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 字");
|
|
5054
|
+
if (/[\r\n]/u.test(owner)) return problem$1("owner", "owner 只能是一行署名");
|
|
5055
|
+
if (owner.length > MAX_OWNER_CHARS) return problem$1("owner", "owner 超过 200 字");
|
|
5049
5056
|
}
|
|
5050
5057
|
function metadataOf(raw) {
|
|
5051
5058
|
const record = asRecord$4(raw);
|
|
@@ -5068,9 +5075,9 @@ function metadataOf(raw) {
|
|
|
5068
5075
|
}
|
|
5069
5076
|
function entryOf$1(raw, at) {
|
|
5070
5077
|
const record = asRecord$4(raw);
|
|
5071
|
-
if (record === void 0) return problem("plugin", "插件条目必须是对象", at);
|
|
5078
|
+
if (record === void 0) return problem$1("plugin", "插件条目必须是对象", at);
|
|
5072
5079
|
const name = asText$4(record["name"]);
|
|
5073
|
-
if (name === "" || name.length > 214 || !NPM_NAME.test(name)) return problem("name", `不是合法 npm 包名:${JSON.stringify(name)}`, at);
|
|
5080
|
+
if (name === "" || name.length > 214 || !NPM_NAME.test(name)) return problem$1("name", `不是合法 npm 包名:${JSON.stringify(name)}`, at);
|
|
5074
5081
|
const versions = versionsOf$1(record["versions"], `${at}.versions`);
|
|
5075
5082
|
if ("kind" in versions) return versions;
|
|
5076
5083
|
const icon = asText$4(record["icon"]);
|
|
@@ -5088,7 +5095,7 @@ function entryOf$1(raw, at) {
|
|
|
5088
5095
|
}
|
|
5089
5096
|
const homepage = asText$4(record["homepage"]);
|
|
5090
5097
|
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);
|
|
5098
|
+
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
5099
|
return {
|
|
5093
5100
|
name,
|
|
5094
5101
|
versions,
|
|
@@ -5105,16 +5112,16 @@ function parseMarketIndex(raw) {
|
|
|
5105
5112
|
const record = asRecord$4(raw);
|
|
5106
5113
|
if (record === void 0) return {
|
|
5107
5114
|
ok: false,
|
|
5108
|
-
problems: [problem("not-object", "索引必须是一个 JSON 对象")]
|
|
5115
|
+
problems: [problem$1("not-object", "索引必须是一个 JSON 对象")]
|
|
5109
5116
|
};
|
|
5110
5117
|
if (record["format"] !== 1) return {
|
|
5111
5118
|
ok: false,
|
|
5112
|
-
problems: [problem("format", `format 必须是 ${String(1)}(现在是 ${JSON.stringify(record["format"])})`)]
|
|
5119
|
+
problems: [problem$1("format", `format 必须是 ${String(1)}(现在是 ${JSON.stringify(record["format"])})`)]
|
|
5113
5120
|
};
|
|
5114
5121
|
const plugins = record["plugins"];
|
|
5115
5122
|
if (!Array.isArray(plugins)) return {
|
|
5116
5123
|
ok: false,
|
|
5117
|
-
problems: [problem("plugins", "plugins 必须是数组")]
|
|
5124
|
+
problems: [problem$1("plugins", "plugins 必须是数组")]
|
|
5118
5125
|
};
|
|
5119
5126
|
const entries = [];
|
|
5120
5127
|
const problems = [];
|
|
@@ -5126,7 +5133,7 @@ function parseMarketIndex(raw) {
|
|
|
5126
5133
|
continue;
|
|
5127
5134
|
}
|
|
5128
5135
|
if (seen.has(parsed.name)) {
|
|
5129
|
-
problems.push(problem("duplicate-name", `插件名重复:${parsed.name}`, `plugins[${String(index)}]`));
|
|
5136
|
+
problems.push(problem$1("duplicate-name", `插件名重复:${parsed.name}`, `plugins[${String(index)}]`));
|
|
5130
5137
|
continue;
|
|
5131
5138
|
}
|
|
5132
5139
|
seen.add(parsed.name);
|
|
@@ -5156,10 +5163,13 @@ function parseMarketIndexJson(text) {
|
|
|
5156
5163
|
} catch (error) {
|
|
5157
5164
|
return {
|
|
5158
5165
|
ok: false,
|
|
5159
|
-
problems: [problem("not-json", `JSON 解析失败:${error instanceof Error ? error.message : String(error)}`)]
|
|
5166
|
+
problems: [problem$1("not-json", `JSON 解析失败:${error instanceof Error ? error.message : String(error)}`)]
|
|
5160
5167
|
};
|
|
5161
5168
|
}
|
|
5162
5169
|
}
|
|
5170
|
+
function sortMarketVersions(versions) {
|
|
5171
|
+
return [...versions].sort((left, right) => compareSemver(right.version, left.version) ?? 0);
|
|
5172
|
+
}
|
|
5163
5173
|
function marketEntryLabels(entry) {
|
|
5164
5174
|
return entry.metadata?.labels ?? [];
|
|
5165
5175
|
}
|
|
@@ -5195,7 +5205,7 @@ function parseSourceConfig(raw) {
|
|
|
5195
5205
|
const record = asRecord$4(raw);
|
|
5196
5206
|
if (record === void 0) return {
|
|
5197
5207
|
ok: false,
|
|
5198
|
-
problem: problem("source", "索引源必须是对象")
|
|
5208
|
+
problem: problem$1("source", "索引源必须是对象")
|
|
5199
5209
|
};
|
|
5200
5210
|
const id = asText$4(record["id"]);
|
|
5201
5211
|
const label = asText$4(record["label"]);
|
|
@@ -5203,21 +5213,21 @@ function parseSourceConfig(raw) {
|
|
|
5203
5213
|
const url = asText$4(record["url"]);
|
|
5204
5214
|
if (id === "") return {
|
|
5205
5215
|
ok: false,
|
|
5206
|
-
problem: problem("source", "索引源缺 id")
|
|
5216
|
+
problem: problem$1("source", "索引源缺 id")
|
|
5207
5217
|
};
|
|
5208
5218
|
if (label === "") return {
|
|
5209
5219
|
ok: false,
|
|
5210
|
-
problem: problem("source", "索引源缺 label")
|
|
5220
|
+
problem: problem$1("source", "索引源缺 label")
|
|
5211
5221
|
};
|
|
5212
5222
|
if (kind !== "http" && kind !== "git") return {
|
|
5213
5223
|
ok: false,
|
|
5214
|
-
problem: problem("source", "kind 只能是 http 或 git")
|
|
5224
|
+
problem: problem$1("source", "kind 只能是 http 或 git")
|
|
5215
5225
|
};
|
|
5216
5226
|
if (kind === "http") {
|
|
5217
5227
|
const bad = httpUrlProblem(url);
|
|
5218
5228
|
if (bad !== void 0) return {
|
|
5219
5229
|
ok: false,
|
|
5220
|
-
problem: problem("source", bad)
|
|
5230
|
+
problem: problem$1("source", bad)
|
|
5221
5231
|
};
|
|
5222
5232
|
return {
|
|
5223
5233
|
ok: true,
|
|
@@ -5231,7 +5241,7 @@ function parseSourceConfig(raw) {
|
|
|
5231
5241
|
}
|
|
5232
5242
|
if (!gitUrlOk(url)) return {
|
|
5233
5243
|
ok: false,
|
|
5234
|
-
problem: problem("source", "git 地址不合格(只收 https / ssh / scp 形式,且不能带凭据)")
|
|
5244
|
+
problem: problem$1("source", "git 地址不合格(只收 https / ssh / scp 形式,且不能带凭据)")
|
|
5235
5245
|
};
|
|
5236
5246
|
const ref = asText$4(record["ref"]);
|
|
5237
5247
|
const path = asText$4(record["path"]);
|
|
@@ -5239,7 +5249,7 @@ function parseSourceConfig(raw) {
|
|
|
5239
5249
|
const policy = record["policy"];
|
|
5240
5250
|
if (policy !== void 0 && policy !== "artifacts-in-repo" && policy !== "catalog-only") return {
|
|
5241
5251
|
ok: false,
|
|
5242
|
-
problem: problem("source", "policy 只能是 artifacts-in-repo 或 catalog-only")
|
|
5252
|
+
problem: problem$1("source", "policy 只能是 artifacts-in-repo 或 catalog-only")
|
|
5243
5253
|
};
|
|
5244
5254
|
return {
|
|
5245
5255
|
ok: true,
|
|
@@ -5255,31 +5265,270 @@ function parseSourceConfig(raw) {
|
|
|
5255
5265
|
}
|
|
5256
5266
|
};
|
|
5257
5267
|
}
|
|
5258
|
-
function
|
|
5259
|
-
|
|
5260
|
-
|
|
5261
|
-
|
|
5262
|
-
|
|
5263
|
-
|
|
5268
|
+
function docFieldsOf(record, at) {
|
|
5269
|
+
const name = asText$4(record["name"]);
|
|
5270
|
+
if (name === "" || name.length > 214 || !NPM_NAME.test(name)) return problem$1("name", `不是合法 npm 包名:${JSON.stringify(name)}`, at);
|
|
5271
|
+
const icon = asText$4(record["icon"]);
|
|
5272
|
+
if (icon !== "") {
|
|
5273
|
+
const badIcon = iconProblem(icon);
|
|
5274
|
+
if (badIcon !== void 0) return badIcon;
|
|
5264
5275
|
}
|
|
5265
|
-
|
|
5266
|
-
if (
|
|
5267
|
-
|
|
5276
|
+
const owner = asText$4(record["owner"]);
|
|
5277
|
+
if (owner !== "") {
|
|
5278
|
+
const badOwner = ownerProblem(owner);
|
|
5279
|
+
if (badOwner !== void 0) return badOwner;
|
|
5280
|
+
}
|
|
5281
|
+
const metadata = metadataOf(record["metadata"]);
|
|
5282
|
+
if (metadata?.unit !== void 0 && !UNIT_ID.test(metadata.unit)) return problem$1("unit", `metadata.unit 只能是小写字母开头的 a-z0-9-(现在是 ${JSON.stringify(metadata.unit)})`, at);
|
|
5283
|
+
const title = asLocalized(record["title"]);
|
|
5284
|
+
const description = asLocalized(record["description"]);
|
|
5285
|
+
const author = asText$4(record["author"]);
|
|
5286
|
+
const homepage = asText$4(record["homepage"]);
|
|
5287
|
+
return {
|
|
5288
|
+
name,
|
|
5289
|
+
...title === void 0 ? {} : { title },
|
|
5290
|
+
...description === void 0 ? {} : { description },
|
|
5291
|
+
...author === "" ? {} : { author },
|
|
5292
|
+
...owner === "" ? {} : { owner },
|
|
5293
|
+
...homepage === "" ? {} : { homepage },
|
|
5294
|
+
...icon === "" ? {} : { icon },
|
|
5295
|
+
...metadata === void 0 ? {} : { metadata }
|
|
5296
|
+
};
|
|
5268
5297
|
}
|
|
5269
|
-
function
|
|
5270
|
-
|
|
5271
|
-
if (
|
|
5272
|
-
|
|
5273
|
-
|
|
5274
|
-
|
|
5275
|
-
|
|
5276
|
-
|
|
5298
|
+
function parseMarketEntryDoc(raw) {
|
|
5299
|
+
const record = asRecord$4(raw);
|
|
5300
|
+
if (record === void 0) return {
|
|
5301
|
+
ok: false,
|
|
5302
|
+
problems: [problem$1("entry", "条目文件必须是一个 JSON 对象")]
|
|
5303
|
+
};
|
|
5304
|
+
const fields = docFieldsOf(record, "entry");
|
|
5305
|
+
if ("kind" in fields) return {
|
|
5306
|
+
ok: false,
|
|
5307
|
+
problems: [fields]
|
|
5308
|
+
};
|
|
5309
|
+
const version = asText$4(record["version"]);
|
|
5310
|
+
const timestamp = record["publishTimestamp"];
|
|
5311
|
+
if (timestamp !== void 0 && (!Number.isInteger(timestamp) || timestamp < 0)) return {
|
|
5312
|
+
ok: false,
|
|
5313
|
+
problems: [problem$1("timestamp", "publishTimestamp 必须是 Unix 秒整数", "entry")]
|
|
5314
|
+
};
|
|
5315
|
+
if (version === "") {
|
|
5316
|
+
if (timestamp === void 0) return {
|
|
5317
|
+
ok: false,
|
|
5318
|
+
problems: [problem$1("timestamp", "元数据文件要有 publishTimestamp", "entry")]
|
|
5319
|
+
};
|
|
5320
|
+
const epoch = record["epoch"];
|
|
5321
|
+
if (epoch !== void 0 && (!Number.isInteger(epoch) || epoch < 0)) return {
|
|
5322
|
+
ok: false,
|
|
5323
|
+
problems: [problem$1("timestamp", "epoch 必须是非负整数", "entry")]
|
|
5324
|
+
};
|
|
5325
|
+
return {
|
|
5326
|
+
ok: true,
|
|
5327
|
+
doc: {
|
|
5328
|
+
...fields,
|
|
5329
|
+
publishTimestamp: timestamp,
|
|
5330
|
+
...epoch === void 0 ? {} : { epoch }
|
|
5331
|
+
}
|
|
5332
|
+
};
|
|
5277
5333
|
}
|
|
5278
|
-
if (
|
|
5279
|
-
|
|
5280
|
-
|
|
5281
|
-
|
|
5282
|
-
|
|
5334
|
+
if (!isValidVersion(version)) return {
|
|
5335
|
+
ok: false,
|
|
5336
|
+
problems: [problem$1("version", `版本号不是合法 SemVer:${JSON.stringify(version)}`, "entry")]
|
|
5337
|
+
};
|
|
5338
|
+
const source = sourceOf(record["source"], "entry.source");
|
|
5339
|
+
if ("kind" in source) return {
|
|
5340
|
+
ok: false,
|
|
5341
|
+
problems: [source]
|
|
5342
|
+
};
|
|
5343
|
+
const engines = asRecord$4(record["engines"]);
|
|
5344
|
+
const dsh = engines === void 0 ? "" : asText$4(engines["dsh"]);
|
|
5345
|
+
return {
|
|
5346
|
+
ok: true,
|
|
5347
|
+
doc: {
|
|
5348
|
+
...fields,
|
|
5349
|
+
version,
|
|
5350
|
+
source,
|
|
5351
|
+
...timestamp === void 0 ? {} : { publishTimestamp: timestamp },
|
|
5352
|
+
...dsh === "" ? {} : { engines: { dsh } }
|
|
5353
|
+
}
|
|
5354
|
+
};
|
|
5355
|
+
}
|
|
5356
|
+
function indexPathBase(dir) {
|
|
5357
|
+
return dir.replace(/^\/+|\/+$/g, "") || "market";
|
|
5358
|
+
}
|
|
5359
|
+
function marketEntryPath(dir, name, version) {
|
|
5360
|
+
if (!NPM_NAME.test(name)) throw new Error(`不是合法包名:${JSON.stringify(name)}`);
|
|
5361
|
+
if (!isValidVersion(version)) throw new Error(`不是合法版本号:${JSON.stringify(version)}`);
|
|
5362
|
+
return `${indexPathBase(dir)}/${name}/${version}.json`;
|
|
5363
|
+
}
|
|
5364
|
+
//#endregion
|
|
5365
|
+
//#region ../shared/dist/host/market-index.js
|
|
5366
|
+
var MARKET_ENTRIES_README_LINES = [
|
|
5367
|
+
"# 索引目录",
|
|
5368
|
+
"",
|
|
5369
|
+
"一个包一个目录,一个版本一个文件:`market/<包名>/<版本>.json`;产物在 `artifacts/<包名>/<版本>/`。",
|
|
5370
|
+
"",
|
|
5371
|
+
"`meta-<epoch>-<随机后缀>.json` 是只改标题 / 署名 / 说明时追加的元数据快照,读取时按 epoch 取最新的一份。"
|
|
5372
|
+
];
|
|
5373
|
+
var MARKET_INDEX_README_LINES = [
|
|
5374
|
+
"# 模块市场索引",
|
|
5375
|
+
"",
|
|
5376
|
+
"这个仓库给 DeepSeek Harness 工作台当模块市场索引:条目在 `market/<包名>/<版本>.json`,产物在 `artifacts/<包名>/<版本>/`。",
|
|
5377
|
+
"",
|
|
5378
|
+
"## 发布模块",
|
|
5379
|
+
"",
|
|
5380
|
+
"在模块包里运行:",
|
|
5381
|
+
"",
|
|
5382
|
+
"```bash",
|
|
5383
|
+
"dsh-wb-module release --index <本仓库地址> --index-policy artifacts-in-repo",
|
|
5384
|
+
"```",
|
|
5385
|
+
"",
|
|
5386
|
+
"产物会提交到 `artifacts/<包名>/<版本>/`,条目提交到 `market/<包名>/<版本>.json`。",
|
|
5387
|
+
"发布只新增文件、不改已有文件,两台机器同时发也不会冲突;老条目的 `packages/<包名>/` 路径继续可用。",
|
|
5388
|
+
"",
|
|
5389
|
+
"一个包名归第一次发布的人(条目文件里的 `owner`)。换一个署名发同名会被拒;要换人,先把这一条的 `owner` 改成新的人。",
|
|
5390
|
+
"同一个市场里,一个模块 id(`metadata.unit`)只能被一个条目使用;另一个包名想用同一个 id / 产物目录也会被拒。",
|
|
5391
|
+
"",
|
|
5392
|
+
"## 使用这个索引",
|
|
5393
|
+
"",
|
|
5394
|
+
"在工作台的「模块市场 → 发现」里添加本仓库地址,即可浏览与安装。",
|
|
5395
|
+
""
|
|
5396
|
+
];
|
|
5397
|
+
function problem(kind, detail, at) {
|
|
5398
|
+
return {
|
|
5399
|
+
kind,
|
|
5400
|
+
detail,
|
|
5401
|
+
...at === void 0 ? {} : { at }
|
|
5402
|
+
};
|
|
5403
|
+
}
|
|
5404
|
+
function docFieldsFromEntry(entry) {
|
|
5405
|
+
return {
|
|
5406
|
+
name: entry.name,
|
|
5407
|
+
...entry.title === void 0 ? {} : { title: entry.title },
|
|
5408
|
+
...entry.description === void 0 ? {} : { description: entry.description },
|
|
5409
|
+
...entry.author === void 0 ? {} : { author: entry.author },
|
|
5410
|
+
...entry.owner === void 0 ? {} : { owner: entry.owner },
|
|
5411
|
+
...entry.homepage === void 0 ? {} : { homepage: entry.homepage },
|
|
5412
|
+
...entry.icon === void 0 ? {} : { icon: entry.icon },
|
|
5413
|
+
...entry.metadata === void 0 ? {} : { metadata: entry.metadata }
|
|
5414
|
+
};
|
|
5415
|
+
}
|
|
5416
|
+
function marketVersionDocOf(entry, version) {
|
|
5417
|
+
return {
|
|
5418
|
+
...docFieldsFromEntry(entry),
|
|
5419
|
+
...version
|
|
5420
|
+
};
|
|
5421
|
+
}
|
|
5422
|
+
function assembleMarketIndex(files) {
|
|
5423
|
+
const problems = [];
|
|
5424
|
+
const groups = /* @__PURE__ */ new Map();
|
|
5425
|
+
const ordered = [...files].sort((left, right) => left.path < right.path ? -1 : left.path > right.path ? 1 : 0);
|
|
5426
|
+
for (const file of ordered) {
|
|
5427
|
+
let raw;
|
|
5428
|
+
try {
|
|
5429
|
+
raw = JSON.parse(file.text);
|
|
5430
|
+
} catch (error) {
|
|
5431
|
+
problems.push(problem("not-json", `JSON 解析失败:${error instanceof Error ? error.message : String(error)}`, file.path));
|
|
5432
|
+
continue;
|
|
5433
|
+
}
|
|
5434
|
+
const parsed = parseMarketEntryDoc(raw);
|
|
5435
|
+
if (!parsed.ok) {
|
|
5436
|
+
for (const one of parsed.problems) problems.push({
|
|
5437
|
+
...one,
|
|
5438
|
+
at: file.path
|
|
5439
|
+
});
|
|
5440
|
+
continue;
|
|
5441
|
+
}
|
|
5442
|
+
const doc = parsed.doc;
|
|
5443
|
+
const group = groups.get(doc.name) ?? {
|
|
5444
|
+
versions: /* @__PURE__ */ new Map(),
|
|
5445
|
+
meta: void 0,
|
|
5446
|
+
fallback: void 0
|
|
5447
|
+
};
|
|
5448
|
+
const at = doc.publishTimestamp ?? 0;
|
|
5449
|
+
if ("version" in doc) {
|
|
5450
|
+
if (group.versions.has(doc.version)) problems.push(problem("duplicate-version", `版本重复:${doc.version}`, file.path));
|
|
5451
|
+
group.versions.set(doc.version, {
|
|
5452
|
+
version: doc.version,
|
|
5453
|
+
source: doc.source,
|
|
5454
|
+
...doc.publishTimestamp === void 0 ? {} : { publishTimestamp: doc.publishTimestamp },
|
|
5455
|
+
...doc.engines === void 0 ? {} : { engines: doc.engines }
|
|
5456
|
+
});
|
|
5457
|
+
if (group.fallback === void 0 || at > group.fallback.at || at === group.fallback.at && file.path > group.fallback.path) group.fallback = {
|
|
5458
|
+
at,
|
|
5459
|
+
path: file.path,
|
|
5460
|
+
doc
|
|
5461
|
+
};
|
|
5462
|
+
} else {
|
|
5463
|
+
const epoch = doc.epoch ?? 0;
|
|
5464
|
+
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 = {
|
|
5465
|
+
epoch,
|
|
5466
|
+
at,
|
|
5467
|
+
path: file.path,
|
|
5468
|
+
doc
|
|
5469
|
+
};
|
|
5470
|
+
}
|
|
5471
|
+
groups.set(doc.name, group);
|
|
5472
|
+
}
|
|
5473
|
+
if (problems.length > 0) return {
|
|
5474
|
+
ok: false,
|
|
5475
|
+
problems
|
|
5476
|
+
};
|
|
5477
|
+
const plugins = [...groups.entries()].map(([name, group]) => {
|
|
5478
|
+
const meta = group.meta?.doc ?? group.fallback?.doc;
|
|
5479
|
+
return {
|
|
5480
|
+
name,
|
|
5481
|
+
versions: sortMarketVersions([...group.versions.values()]),
|
|
5482
|
+
...meta?.title === void 0 ? {} : { title: meta.title },
|
|
5483
|
+
...meta?.description === void 0 ? {} : { description: meta.description },
|
|
5484
|
+
...meta?.author === void 0 ? {} : { author: meta.author },
|
|
5485
|
+
...meta?.owner === void 0 ? {} : { owner: meta.owner },
|
|
5486
|
+
...meta?.homepage === void 0 ? {} : { homepage: meta.homepage },
|
|
5487
|
+
...meta?.icon === void 0 ? {} : { icon: meta.icon },
|
|
5488
|
+
...meta?.metadata === void 0 ? {} : { metadata: meta.metadata }
|
|
5489
|
+
};
|
|
5490
|
+
});
|
|
5491
|
+
plugins.sort((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0);
|
|
5492
|
+
return {
|
|
5493
|
+
ok: true,
|
|
5494
|
+
index: {
|
|
5495
|
+
format: 1,
|
|
5496
|
+
plugins
|
|
5497
|
+
}
|
|
5498
|
+
};
|
|
5499
|
+
}
|
|
5500
|
+
function marketEntryFilesOf(index, dir = MARKET_INDEX_DIR) {
|
|
5501
|
+
const files = [];
|
|
5502
|
+
for (const entry of index.plugins) for (const version of entry.versions) files.push({
|
|
5503
|
+
path: marketEntryPath(dir, entry.name, version.version),
|
|
5504
|
+
text: `${JSON.stringify(marketVersionDocOf(entry, version), null, 2)}\n`
|
|
5505
|
+
});
|
|
5506
|
+
return files;
|
|
5507
|
+
}
|
|
5508
|
+
function collectMarketEntryFiles(dir) {
|
|
5509
|
+
if (!existsSync(dir)) return [];
|
|
5510
|
+
const files = [];
|
|
5511
|
+
const walk = (current) => {
|
|
5512
|
+
for (const item of readdirSync(current, { withFileTypes: true })) {
|
|
5513
|
+
const full = join(current, item.name);
|
|
5514
|
+
if (item.isDirectory()) {
|
|
5515
|
+
walk(full);
|
|
5516
|
+
continue;
|
|
5517
|
+
}
|
|
5518
|
+
if (!item.name.endsWith(".json")) continue;
|
|
5519
|
+
files.push({
|
|
5520
|
+
path: relative(dir, full).split(sep).join("/"),
|
|
5521
|
+
text: readFileSync(full, "utf8")
|
|
5522
|
+
});
|
|
5523
|
+
}
|
|
5524
|
+
};
|
|
5525
|
+
walk(dir);
|
|
5526
|
+
return files;
|
|
5527
|
+
}
|
|
5528
|
+
function marketEntryFilesOfLegacy(text, dir = MARKET_INDEX_DIR) {
|
|
5529
|
+
const parsed = parseMarketIndexJson(text);
|
|
5530
|
+
if (!parsed.ok) return void 0;
|
|
5531
|
+
return marketEntryFilesOf(parsed.index, dir);
|
|
5283
5532
|
}
|
|
5284
5533
|
//#endregion
|
|
5285
5534
|
//#region ../shared/dist/host/market-index-claim.js
|
|
@@ -5324,8 +5573,11 @@ function claimOfEntry(entry, publisher) {
|
|
|
5324
5573
|
owner
|
|
5325
5574
|
};
|
|
5326
5575
|
}
|
|
5327
|
-
function
|
|
5328
|
-
return
|
|
5576
|
+
function marketArtifactRoot(name) {
|
|
5577
|
+
return `${MARKET_ARTIFACTS_DIR}/${name}`;
|
|
5578
|
+
}
|
|
5579
|
+
function marketArtifactPathFor(name, version) {
|
|
5580
|
+
return `${MARKET_ARTIFACTS_DIR}/${name}/${version}`;
|
|
5329
5581
|
}
|
|
5330
5582
|
function entryUnitOf(entry) {
|
|
5331
5583
|
const unit = entry.metadata?.unit?.trim();
|
|
@@ -6470,7 +6722,7 @@ function publishClaimDenyOf(claim, name) {
|
|
|
6470
6722
|
if (claim === void 0) return void 0;
|
|
6471
6723
|
if (claim.state === "unit-taken") return `市场里 ${claim.entry} 已经用了这个模块 id —— 两个模块不能共用一个,装在一起会撞。给这个模块换一个 unit(dsh.workbench.unit)再发。`;
|
|
6472
6724
|
if (claim.state === "path-taken") return `市场仓库里的产物目录已经被 ${claim.entry} 占了 —— 换一个包名再发。`;
|
|
6473
|
-
if (claim.state === "conflict") return `市场里 ${name} 已经有主了(${claim.owner})—— 同一个名字不能换人发。换一个带团队 scope 的包名;你就是原来的作者(只是换了机器或署名),用 author 参数带上原来的署名;确实是接手,让索引维护者改 market
|
|
6725
|
+
if (claim.state === "conflict") return `市场里 ${name} 已经有主了(${claim.owner})—— 同一个名字不能换人发。换一个带团队 scope 的包名;你就是原来的作者(只是换了机器或署名),用 author 参数带上原来的署名;确实是接手,让索引维护者改 market/<包名>/ 条目文件里的 owner。`;
|
|
6474
6726
|
}
|
|
6475
6727
|
//#endregion
|
|
6476
6728
|
//#region src/modules/panels/model/approval.ts
|
|
@@ -10148,10 +10400,16 @@ var EMPTY_INDEX = {
|
|
|
10148
10400
|
plugins: []
|
|
10149
10401
|
};
|
|
10150
10402
|
function publishedIndexOf(source) {
|
|
10151
|
-
const
|
|
10152
|
-
if (!existsSync(file)) return void 0;
|
|
10403
|
+
const root = join(workbenchRoot(), "market-index", marketSourceKey(source), "worktree");
|
|
10153
10404
|
try {
|
|
10154
|
-
const
|
|
10405
|
+
const files = collectMarketEntryFiles(join(root, source.path ?? "market"));
|
|
10406
|
+
if (files.length > 0) {
|
|
10407
|
+
const assembled = assembleMarketIndex(files);
|
|
10408
|
+
return assembled.ok ? assembled.index : void 0;
|
|
10409
|
+
}
|
|
10410
|
+
const legacy = join(root, "market.json");
|
|
10411
|
+
if (!existsSync(legacy)) return void 0;
|
|
10412
|
+
const parsed = parseMarketIndexJson(readFileSync(legacy, "utf8"));
|
|
10155
10413
|
return parsed.ok ? parsed.index : void 0;
|
|
10156
10414
|
} catch {
|
|
10157
10415
|
return;
|
|
@@ -10192,8 +10450,8 @@ function marketOf(dir, name, version) {
|
|
|
10192
10450
|
market: {
|
|
10193
10451
|
url: source.url,
|
|
10194
10452
|
...source.ref === void 0 ? {} : { ref: source.ref },
|
|
10195
|
-
indexPath: source.path ?? "market
|
|
10196
|
-
artifactPath:
|
|
10453
|
+
indexPath: source.path ?? "market",
|
|
10454
|
+
artifactPath: marketArtifactPathFor(name, nextVersion ?? version),
|
|
10197
10455
|
files: ARTIFACTS.filter((one) => existsSync(join(dir, one))),
|
|
10198
10456
|
...nextVersion === void 0 ? {} : { nextVersion }
|
|
10199
10457
|
},
|
|
@@ -10290,7 +10548,7 @@ function publishPreviewOf(root, unit, options = {}) {
|
|
|
10290
10548
|
name,
|
|
10291
10549
|
publisher: author,
|
|
10292
10550
|
unit,
|
|
10293
|
-
artifactPath:
|
|
10551
|
+
artifactPath: marketArtifactRoot(name)
|
|
10294
10552
|
});
|
|
10295
10553
|
return {
|
|
10296
10554
|
name,
|
|
@@ -12391,7 +12649,82 @@ function dependencyStillBehind(target, capabilities) {
|
|
|
12391
12649
|
}
|
|
12392
12650
|
}
|
|
12393
12651
|
//#endregion
|
|
12652
|
+
//#region src/shared/api/host/self-update-marker.ts
|
|
12653
|
+
function readSelfUpdateMarker(file) {
|
|
12654
|
+
try {
|
|
12655
|
+
const raw = JSON.parse(readFileSync(file, "utf8"));
|
|
12656
|
+
return {
|
|
12657
|
+
...typeof raw.pkg === "string" ? { pkg: raw.pkg } : {},
|
|
12658
|
+
...typeof raw.state === "string" ? { state: raw.state } : {}
|
|
12659
|
+
};
|
|
12660
|
+
} catch {
|
|
12661
|
+
return {};
|
|
12662
|
+
}
|
|
12663
|
+
}
|
|
12664
|
+
//#endregion
|
|
12665
|
+
//#region src/modules/panels/model/market-swap-stuck.ts
|
|
12666
|
+
var DEFAULT_POLL_MS = 400;
|
|
12667
|
+
var DEFAULT_WAIT_MS = 6e4;
|
|
12668
|
+
function sleep(ms) {
|
|
12669
|
+
return new Promise((resolve) => {
|
|
12670
|
+
setTimeout(resolve, ms);
|
|
12671
|
+
});
|
|
12672
|
+
}
|
|
12673
|
+
function landedIn(after, input) {
|
|
12674
|
+
if (after.kind !== "module") return false;
|
|
12675
|
+
return input.to === "" ? after.version !== input.from : after.version === input.to;
|
|
12676
|
+
}
|
|
12677
|
+
async function swapExternally(deps, input) {
|
|
12678
|
+
let marker;
|
|
12679
|
+
try {
|
|
12680
|
+
marker = deps.supply.startSelfUpdate({
|
|
12681
|
+
dshHome: deps.dshHome,
|
|
12682
|
+
profile: deps.profile,
|
|
12683
|
+
pkg: input.pkg,
|
|
12684
|
+
from: input.from,
|
|
12685
|
+
to: input.to,
|
|
12686
|
+
pinned: input.pinned
|
|
12687
|
+
}, (message) => deps.logger?.warn?.(`[dsh-workbench] ${message}`)).paths.marker;
|
|
12688
|
+
} catch (error) {
|
|
12689
|
+
deps.logger?.warn?.(`[dsh-workbench] 外部换版起不来(${input.pkg}):${error instanceof Error ? error.message : String(error)}`);
|
|
12690
|
+
return;
|
|
12691
|
+
}
|
|
12692
|
+
const pollMs = deps.pollMs ?? DEFAULT_POLL_MS;
|
|
12693
|
+
const deadline = Date.now() + (deps.waitMs ?? DEFAULT_WAIT_MS);
|
|
12694
|
+
let state = "timeout";
|
|
12695
|
+
while (Date.now() < deadline) {
|
|
12696
|
+
const read = readSelfUpdateMarker(marker);
|
|
12697
|
+
if (read.pkg === input.pkg && read.state !== void 0 && read.state !== "running") {
|
|
12698
|
+
state = read.state;
|
|
12699
|
+
break;
|
|
12700
|
+
}
|
|
12701
|
+
await sleep(pollMs);
|
|
12702
|
+
}
|
|
12703
|
+
const after = deps.installed?.(input.pkg) ?? {
|
|
12704
|
+
kind: "unknown",
|
|
12705
|
+
pkg: input.pkg
|
|
12706
|
+
};
|
|
12707
|
+
const version = after.kind === "module" ? after.version : "读不到";
|
|
12708
|
+
deps.logger?.info?.(`[dsh-workbench] 外部换版 ${input.pkg}:${state},盘上 ${version}`);
|
|
12709
|
+
return landedIn(after, input) ? after : void 0;
|
|
12710
|
+
}
|
|
12711
|
+
//#endregion
|
|
12394
12712
|
//#region src/modules/panels/model/market-update-bundle.ts
|
|
12713
|
+
var SHIPPED_APPLICATIONS = /* @__PURE__ */ new Set([
|
|
12714
|
+
"applied",
|
|
12715
|
+
"restart-required",
|
|
12716
|
+
"overridden"
|
|
12717
|
+
]);
|
|
12718
|
+
function stillStuck(change, after, before) {
|
|
12719
|
+
if (!SHIPPED_APPLICATIONS.has(change.application)) return true;
|
|
12720
|
+
return before !== void 0 && after.kind === "module" && after.version === before;
|
|
12721
|
+
}
|
|
12722
|
+
function remedyOf(deps, change, before, afterVersion, spec) {
|
|
12723
|
+
const name = deps.profileName ?? deps.profile;
|
|
12724
|
+
if (name === void 0) return void 0;
|
|
12725
|
+
if (!!SHIPPED_APPLICATIONS.has(change.application) && !(before !== void 0 && afterVersion === before)) return void 0;
|
|
12726
|
+
return { command: `dsh plugin --profile ${name} add ${quoteShellArg(spec)}` };
|
|
12727
|
+
}
|
|
12395
12728
|
async function restoreRemovedBundle(pkg, before, registry, deps) {
|
|
12396
12729
|
if (before === void 0) return void 0;
|
|
12397
12730
|
try {
|
|
@@ -12446,7 +12779,7 @@ async function updateModuleBundle(unit, deps) {
|
|
|
12446
12779
|
kind: "unknown",
|
|
12447
12780
|
pkg
|
|
12448
12781
|
}).kind === "module" ? void 0 : await restoreRemovedBundle(pkg, before, registry, deps);
|
|
12449
|
-
|
|
12782
|
+
let after = deps.installed?.(pkg) ?? {
|
|
12450
12783
|
kind: "unknown",
|
|
12451
12784
|
pkg
|
|
12452
12785
|
};
|
|
@@ -12456,12 +12789,43 @@ async function updateModuleBundle(unit, deps) {
|
|
|
12456
12789
|
after,
|
|
12457
12790
|
...restored === void 0 ? {} : { restored }
|
|
12458
12791
|
});
|
|
12792
|
+
let change = verified.change;
|
|
12793
|
+
let restoredVersion = verified.restored;
|
|
12794
|
+
const swap = stillStuck(change, after, before) && before !== void 0 && deps.dshHome !== void 0 && deps.profileName !== void 0 ? await swapExternally({
|
|
12795
|
+
supply,
|
|
12796
|
+
installed: deps.installed,
|
|
12797
|
+
dshHome: deps.dshHome,
|
|
12798
|
+
profile: deps.profileName,
|
|
12799
|
+
...deps.logger === void 0 ? {} : { logger: deps.logger },
|
|
12800
|
+
...deps.swapWaitMs === void 0 ? {} : { waitMs: deps.swapWaitMs },
|
|
12801
|
+
...deps.swapPollMs === void 0 ? {} : { pollMs: deps.swapPollMs }
|
|
12802
|
+
}, {
|
|
12803
|
+
pkg,
|
|
12804
|
+
from: before,
|
|
12805
|
+
to: latest ?? "",
|
|
12806
|
+
pinned: latest !== void 0
|
|
12807
|
+
}) : void 0;
|
|
12808
|
+
if (swap !== void 0) {
|
|
12809
|
+
const swapVersion = swap.kind === "module" ? swap.version : "";
|
|
12810
|
+
after = swap;
|
|
12811
|
+
restoredVersion = void 0;
|
|
12812
|
+
change = {
|
|
12813
|
+
application: "restart-required",
|
|
12814
|
+
stage: "install",
|
|
12815
|
+
changed: true,
|
|
12816
|
+
target: pkg,
|
|
12817
|
+
bundle: pkg,
|
|
12818
|
+
...swapVersion === "" ? {} : { version: swapVersion }
|
|
12819
|
+
};
|
|
12820
|
+
}
|
|
12459
12821
|
await deps.refreshSupply(unit).catch(() => void 0);
|
|
12822
|
+
const remedy = stillStuck(change, after, before) ? remedyOf(deps, change, before, after.kind === "module" ? after.version : void 0, spec) : void 0;
|
|
12460
12823
|
return {
|
|
12461
12824
|
...outcome,
|
|
12462
|
-
change
|
|
12825
|
+
change,
|
|
12463
12826
|
module: after,
|
|
12464
|
-
...
|
|
12827
|
+
...restoredVersion === void 0 ? {} : { restored: restoredVersion },
|
|
12828
|
+
...remedy === void 0 ? {} : { remedy }
|
|
12465
12829
|
};
|
|
12466
12830
|
}
|
|
12467
12831
|
//#endregion
|
|
@@ -12778,14 +13142,14 @@ function createPluginActions(options) {
|
|
|
12778
13142
|
unreadable: []
|
|
12779
13143
|
};
|
|
12780
13144
|
}
|
|
12781
|
-
function decide(entry, held) {
|
|
13145
|
+
function decide(entry, held, claimedBy) {
|
|
12782
13146
|
const plugin = pluginNameOf(entry);
|
|
12783
13147
|
if (plugin === void 0) return {
|
|
12784
13148
|
act: "keep",
|
|
12785
13149
|
kept: { kind: "unnamed" }
|
|
12786
13150
|
};
|
|
12787
13151
|
const refs = referencesOf();
|
|
12788
|
-
const by = options.claimedBy?.(plugin) ?? [];
|
|
13152
|
+
const by = claimedBy?.(plugin) ?? options.claimedBy?.(plugin) ?? [];
|
|
12789
13153
|
const retention = pluginRetentionOf({
|
|
12790
13154
|
held,
|
|
12791
13155
|
claimedBy: by,
|
|
@@ -12852,12 +13216,12 @@ function createPluginActions(options) {
|
|
|
12852
13216
|
name: decision.plugin
|
|
12853
13217
|
};
|
|
12854
13218
|
},
|
|
12855
|
-
async sweep() {
|
|
13219
|
+
async sweep(claimedBy) {
|
|
12856
13220
|
if (ledger === void 0) return [];
|
|
12857
13221
|
const removed = [];
|
|
12858
13222
|
for (const entry of ledger.all()) {
|
|
12859
13223
|
if (entry.releasedAt === void 0) continue;
|
|
12860
|
-
const decision = decide(entry, false);
|
|
13224
|
+
const decision = decide(entry, false, claimedBy);
|
|
12861
13225
|
if (decision.act === "keep") continue;
|
|
12862
13226
|
try {
|
|
12863
13227
|
await removeFiles(decision.plugin);
|
|
@@ -12886,17 +13250,18 @@ function createEnvDropper(options) {
|
|
|
12886
13250
|
unreadable: []
|
|
12887
13251
|
};
|
|
12888
13252
|
}
|
|
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);
|
|
13253
|
+
function usersOf(plugin, except, roster) {
|
|
13254
|
+
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
13255
|
}
|
|
12892
|
-
function libUsersOf(name, unit) {
|
|
12893
|
-
return
|
|
13256
|
+
function libUsersOf(name, unit, roster) {
|
|
13257
|
+
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
13258
|
}
|
|
12895
13259
|
return {
|
|
12896
13260
|
usersOf,
|
|
12897
|
-
async drop(unit, needs, before) {
|
|
13261
|
+
async drop(unit, needs, before, roster) {
|
|
12898
13262
|
const kept = /* @__PURE__ */ new Set();
|
|
12899
|
-
const
|
|
13263
|
+
const known = roster ?? catalog();
|
|
13264
|
+
const index = buildSupplyIndex(known.filter((item) => options.isReleased?.(item.id) !== true).map((item) => ({
|
|
12900
13265
|
id: item.id,
|
|
12901
13266
|
needs: needsOf(item)
|
|
12902
13267
|
})), { env: process.env });
|
|
@@ -12905,7 +13270,7 @@ function createEnvDropper(options) {
|
|
|
12905
13270
|
const onMachine = new Set(before.needs.filter((state) => state.kind === "extension" && state.status !== "missing").map((state) => state.name));
|
|
12906
13271
|
const unknown = !before.needs.some((state) => state.kind === "extension");
|
|
12907
13272
|
for (const need of extensions) {
|
|
12908
|
-
const users = usersOf(need.name, unit);
|
|
13273
|
+
const users = usersOf(need.name, unit, known);
|
|
12909
13274
|
const retention = pluginRetentionOf({
|
|
12910
13275
|
held: ledger?.heldAsPlugin(need.name) !== void 0,
|
|
12911
13276
|
claimedBy: users,
|
|
@@ -12925,7 +13290,7 @@ function createEnvDropper(options) {
|
|
|
12925
13290
|
await run(`${quoteShellArg(command)} plugin uninstall ${quoteShellArg(need.name)}`);
|
|
12926
13291
|
}
|
|
12927
13292
|
for (const need of libs) {
|
|
12928
|
-
const keepers = libUsersOf(need.name, unit);
|
|
13293
|
+
const keepers = libUsersOf(need.name, unit, known);
|
|
12929
13294
|
if (keepers.length > 0) {
|
|
12930
13295
|
for (const id of keepers) kept.add(id);
|
|
12931
13296
|
continue;
|
|
@@ -13234,7 +13599,8 @@ function createMarketService(options) {
|
|
|
13234
13599
|
pkg
|
|
13235
13600
|
};
|
|
13236
13601
|
const unit = module.kind === "module" ? module.unit : void 0;
|
|
13237
|
-
const
|
|
13602
|
+
const roster = catalog();
|
|
13603
|
+
const manifest = unit === void 0 ? void 0 : roster.find((item) => item.id === unit);
|
|
13238
13604
|
const before = unit === void 0 ? void 0 : await capabilityOf(unit, true);
|
|
13239
13605
|
const change = await removeDshBundle({
|
|
13240
13606
|
manager,
|
|
@@ -13247,8 +13613,8 @@ function createMarketService(options) {
|
|
|
13247
13613
|
change,
|
|
13248
13614
|
keptFor: []
|
|
13249
13615
|
};
|
|
13250
|
-
const keptFor = manifest === void 0 || before === void 0 ? [] : await envDropper.drop(unit, needsOf(manifest), before);
|
|
13251
|
-
await plugins.sweep();
|
|
13616
|
+
const keptFor = manifest === void 0 || before === void 0 ? [] : await envDropper.drop(unit, needsOf(manifest), before, roster);
|
|
13617
|
+
await plugins.sweep((plugin) => envDropper.usersOf(plugin, unit, roster));
|
|
13252
13618
|
onUninstalled?.(unit);
|
|
13253
13619
|
return {
|
|
13254
13620
|
change,
|
|
@@ -13260,15 +13626,22 @@ function createMarketService(options) {
|
|
|
13260
13626
|
},
|
|
13261
13627
|
async updateBundle(unit) {
|
|
13262
13628
|
const manager = requireBundles();
|
|
13629
|
+
const home = dshHome?.();
|
|
13630
|
+
const profile = profileName?.() ?? (profileDir === void 0 || profileDir === null ? void 0 : basename(profileDir));
|
|
13263
13631
|
return updateModuleBundle(unit, {
|
|
13264
13632
|
supply,
|
|
13265
13633
|
modules,
|
|
13266
13634
|
installed,
|
|
13267
13635
|
shell,
|
|
13268
13636
|
manager,
|
|
13637
|
+
...profile === void 0 ? {} : {
|
|
13638
|
+
profile,
|
|
13639
|
+
profileName: profile
|
|
13640
|
+
},
|
|
13641
|
+
...home === void 0 ? {} : { dshHome: home },
|
|
13269
13642
|
routeDeps: routeDeps(manager),
|
|
13270
13643
|
sweep: async () => {
|
|
13271
|
-
await plugins.sweep();
|
|
13644
|
+
await plugins.sweep((plugin) => envDropper.usersOf(plugin, void 0, catalog()));
|
|
13272
13645
|
},
|
|
13273
13646
|
refreshSupply: async (id) => {
|
|
13274
13647
|
await service.update(id);
|
|
@@ -14000,7 +14373,7 @@ function createMarketIndexReader(options) {
|
|
|
14000
14373
|
}
|
|
14001
14374
|
};
|
|
14002
14375
|
const repo = repoOf(options.root, source);
|
|
14003
|
-
const path = (source.path ?? "market
|
|
14376
|
+
const path = ((source.path ?? "market").replace(/^\/+/, "") || "market").replace(/\/+$/, "");
|
|
14004
14377
|
const ref = (source.ref ?? "").trim();
|
|
14005
14378
|
let revision = "FETCH_HEAD";
|
|
14006
14379
|
mkdirSync(directoryOf(options.root, source), { recursive: true });
|
|
@@ -14025,17 +14398,6 @@ function createMarketIndexReader(options) {
|
|
|
14025
14398
|
detail: "仓库还没有任何提交"
|
|
14026
14399
|
}
|
|
14027
14400
|
}, 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
14401
|
const head = await run(`git -C ${quoteShellArg(repo)} rev-parse ${revision}`);
|
|
14040
14402
|
if (!head.ok) return head;
|
|
14041
14403
|
const commit = head.stdout.trim();
|
|
@@ -14046,26 +14408,105 @@ function createMarketIndexReader(options) {
|
|
|
14046
14408
|
detail: `读到的 commit 不是 40 位:${commit}`
|
|
14047
14409
|
}
|
|
14048
14410
|
};
|
|
14049
|
-
const
|
|
14050
|
-
if (!
|
|
14411
|
+
const listed = await run(`git -C ${quoteShellArg(repo)} ls-tree -r ${quoteShellArg(commit)} -- ${quoteShellArg(path)}`);
|
|
14412
|
+
if (!listed.ok) {
|
|
14413
|
+
if (listed.problem.kind === "git-file") return withBranch({
|
|
14414
|
+
ok: false,
|
|
14415
|
+
problem: {
|
|
14416
|
+
kind: "git-missing-index",
|
|
14417
|
+
detail: `索引 ${path} 不在仓库里`
|
|
14418
|
+
}
|
|
14419
|
+
}, repo);
|
|
14420
|
+
return listed;
|
|
14421
|
+
}
|
|
14422
|
+
const rows = listed.stdout.split("\n").map((line) => line.trim()).filter((line) => line !== "").flatMap((line) => {
|
|
14423
|
+
const [meta, file] = line.split(" ");
|
|
14424
|
+
const type = (meta ?? "").split(" ")[1] ?? "";
|
|
14425
|
+
return file === void 0 ? [] : [{
|
|
14426
|
+
type,
|
|
14427
|
+
file
|
|
14428
|
+
}];
|
|
14429
|
+
});
|
|
14430
|
+
const legacy = rows.find((one) => one.type === "blob" && one.file === path);
|
|
14431
|
+
const entries = rows.filter((one) => one.type === "blob" && one.file !== path && one.file.startsWith(`${path}/`) && one.file.endsWith(".json"));
|
|
14432
|
+
const finish = async (index, text) => {
|
|
14433
|
+
const fetchedAt = now();
|
|
14434
|
+
await writeFileAtomic(indexFileOf(options.root, source), text);
|
|
14435
|
+
await writeMeta(metaFileOf(options.root, source), {
|
|
14436
|
+
fetchedAt,
|
|
14437
|
+
commit
|
|
14438
|
+
});
|
|
14439
|
+
return {
|
|
14440
|
+
ok: true,
|
|
14441
|
+
index,
|
|
14442
|
+
fetchedAt,
|
|
14443
|
+
commit
|
|
14444
|
+
};
|
|
14445
|
+
};
|
|
14446
|
+
const readLegacy = async (file) => {
|
|
14447
|
+
const shown = await run(`git -C ${quoteShellArg(repo)} show ${quoteShellArg(commit)}:${quoteShellArg(file)}`);
|
|
14448
|
+
if (!shown.ok) return void 0;
|
|
14449
|
+
const parsed = parseMarketIndexJson(shown.stdout);
|
|
14450
|
+
if (!parsed.ok) return {
|
|
14451
|
+
ok: false,
|
|
14452
|
+
problem: {
|
|
14453
|
+
kind: "format",
|
|
14454
|
+
detail: parsed.problems.map((one) => one.detail).join(";")
|
|
14455
|
+
}
|
|
14456
|
+
};
|
|
14457
|
+
return finish(parsed.index, shown.stdout);
|
|
14458
|
+
};
|
|
14459
|
+
if (legacy !== void 0) {
|
|
14460
|
+
const outcome = await readLegacy(path);
|
|
14461
|
+
if (outcome !== void 0) return outcome;
|
|
14462
|
+
}
|
|
14463
|
+
if (entries.length > 0) {
|
|
14464
|
+
const parts = [];
|
|
14465
|
+
let failed;
|
|
14466
|
+
const queue = [...entries];
|
|
14467
|
+
const worker = async () => {
|
|
14468
|
+
for (;;) {
|
|
14469
|
+
const next = queue.shift();
|
|
14470
|
+
if (next === void 0 || failed !== void 0) return;
|
|
14471
|
+
const shown = await run(`git -C ${quoteShellArg(repo)} show ${quoteShellArg(commit)}:${quoteShellArg(next.file)}`);
|
|
14472
|
+
if (!shown.ok) {
|
|
14473
|
+
failed = shown;
|
|
14474
|
+
return;
|
|
14475
|
+
}
|
|
14476
|
+
parts.push({
|
|
14477
|
+
path: next.file,
|
|
14478
|
+
text: shown.stdout
|
|
14479
|
+
});
|
|
14480
|
+
}
|
|
14481
|
+
};
|
|
14482
|
+
await Promise.all(Array.from({ length: Math.min(6, entries.length) }, () => worker()));
|
|
14483
|
+
if (failed !== void 0) return failed;
|
|
14484
|
+
const assembled = assembleMarketIndex(parts);
|
|
14485
|
+
if (!assembled.ok) return {
|
|
14486
|
+
ok: false,
|
|
14487
|
+
problem: {
|
|
14488
|
+
kind: "format",
|
|
14489
|
+
detail: assembled.problems.map((one) => one.detail).join(";")
|
|
14490
|
+
}
|
|
14491
|
+
};
|
|
14492
|
+
return finish(assembled.index, `${JSON.stringify(assembled.index, null, 2)}\n`);
|
|
14493
|
+
}
|
|
14494
|
+
if (listed.stdout.trim() !== "") {
|
|
14495
|
+
const empty = {
|
|
14496
|
+
format: 1,
|
|
14497
|
+
plugins: []
|
|
14498
|
+
};
|
|
14499
|
+
return finish(empty, `${JSON.stringify(empty, null, 2)}\n`);
|
|
14500
|
+
}
|
|
14501
|
+
const legacyText = await readLegacy(MARKET_INDEX_PATH);
|
|
14502
|
+
if (legacyText !== void 0) return legacyText;
|
|
14503
|
+
return withBranch({
|
|
14051
14504
|
ok: false,
|
|
14052
14505
|
problem: {
|
|
14053
|
-
kind: "
|
|
14054
|
-
detail:
|
|
14506
|
+
kind: "git-missing-index",
|
|
14507
|
+
detail: `索引 ${path} 不在仓库里`
|
|
14055
14508
|
}
|
|
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
|
-
};
|
|
14509
|
+
}, repo);
|
|
14069
14510
|
}
|
|
14070
14511
|
async function read(source, callOptions = {}) {
|
|
14071
14512
|
const key = marketSourceKey(source);
|
|
@@ -14413,16 +14854,31 @@ function createMarketSourceInitializer(options) {
|
|
|
14413
14854
|
], workdir);
|
|
14414
14855
|
if (created.exitCode !== 0) throw new Error(`建分支 ${branch} 没过:${tailLines(created.stderr, 4)}`);
|
|
14415
14856
|
}
|
|
14416
|
-
const indexPath = (source.path ?? "market
|
|
14417
|
-
const
|
|
14857
|
+
const indexPath = ((source.path ?? "market").replace(/^\/+/, "") || "market").replace(/\/+$/, "");
|
|
14858
|
+
const indexDir = join(workdir, indexPath);
|
|
14859
|
+
const indexMarker = join(indexDir, "README.md");
|
|
14418
14860
|
const stampOnly = request.stampOnly === true;
|
|
14419
|
-
if (stampOnly && !existsSync(
|
|
14861
|
+
if (stampOnly && !existsSync(indexDir)) throw new Error(`仓库里还没有 ${indexPath}/ —— 先选中这个源,按发现页的提示把它初始化成索引`);
|
|
14420
14862
|
const written = [];
|
|
14421
14863
|
if (!stampOnly) {
|
|
14422
|
-
|
|
14423
|
-
|
|
14424
|
-
|
|
14425
|
-
|
|
14864
|
+
const legacyFile = join(workdir, "market.json");
|
|
14865
|
+
if (indexPath !== "market.json" && existsSync(legacyFile)) {
|
|
14866
|
+
const migrated = marketEntryFilesOfLegacy(readFileSync(legacyFile, "utf8"), indexPath);
|
|
14867
|
+
if (migrated !== void 0) {
|
|
14868
|
+
for (const file of migrated) {
|
|
14869
|
+
const target = join(workdir, ...file.path.split("/"));
|
|
14870
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
14871
|
+
writeFileSync(target, file.text);
|
|
14872
|
+
written.push(file.path);
|
|
14873
|
+
}
|
|
14874
|
+
rmSync(legacyFile);
|
|
14875
|
+
written.push("market.json");
|
|
14876
|
+
}
|
|
14877
|
+
}
|
|
14878
|
+
if (!existsSync(indexMarker)) {
|
|
14879
|
+
mkdirSync(indexDir, { recursive: true });
|
|
14880
|
+
writeFileSync(indexMarker, MARKET_ENTRIES_README_LINES.join("\n"));
|
|
14881
|
+
written.push(`${indexPath}/README.md`);
|
|
14426
14882
|
}
|
|
14427
14883
|
const readmeFile = join(workdir, "README.md");
|
|
14428
14884
|
if (!existsSync(readmeFile)) {
|
|
@@ -14691,7 +15147,7 @@ function failureText(change) {
|
|
|
14691
15147
|
const code = change.errorCode ?? change.application;
|
|
14692
15148
|
if (code === "already-installed") return "它已经在这份 profile 里,而这次没能把它卸下来(这一版 dsh 不肯就地覆盖装,得先卸再装)";
|
|
14693
15149
|
const surface = hostSurface();
|
|
14694
|
-
return REMOVE_BLOCKED.has(code) ?
|
|
15150
|
+
return REMOVE_BLOCKED.has(code) ? `旧版卸不掉(dsh 不肯就地覆盖正在用的模块),自动从外部换也没成 —— 让用户从终端跑 dsh plugin --profile <名字> add <包>@<版本>,跑完${surface.restartHint}` : code;
|
|
14695
15151
|
}
|
|
14696
15152
|
function describeInstall(spec, module, supply, change) {
|
|
14697
15153
|
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 +15281,14 @@ function registerMarketTools(ctx, market, sources) {
|
|
|
14825
15281
|
async execute(raw) {
|
|
14826
15282
|
const id = await idOrList(raw, MODULES_UNINSTALL_TOOL);
|
|
14827
15283
|
const outcome = await market.uninstall(id);
|
|
15284
|
+
const change = outcome.change;
|
|
15285
|
+
if (change !== void 0 && change.application !== "applied") {
|
|
15286
|
+
const code = change.errorCode ?? change.application;
|
|
15287
|
+
return {
|
|
15288
|
+
module: outcome.entry,
|
|
15289
|
+
text: `没卸下来:${id} 还在这份 profile 里(${code})—— 这一版 dsh 不肯卸正在加载的模块。让用户从终端跑 dsh plugin --profile <profile> remove ${id} 的包,跑完重启一次工作台;别再调这个工具重试。`
|
|
15290
|
+
};
|
|
15291
|
+
}
|
|
14828
15292
|
const kept = outcome.keptFor.length === 0 ? "" : `(还有模块在面板上要用其中一份安装,所以那份留着:${outcome.keptFor.join("、")})`;
|
|
14829
15293
|
return {
|
|
14830
15294
|
module: outcome.entry,
|
|
@@ -14980,7 +15444,7 @@ function registerMarketTools(ctx, market, sources) {
|
|
|
14980
15444
|
branch: result.branch,
|
|
14981
15445
|
commit: result.commit,
|
|
14982
15446
|
changed: true,
|
|
14983
|
-
text: `已把「${source.label}」设为可发布(${source.url},分支 ${result.branch})—— 仓库本身没动。之后发布可以落到它的 ${source.path ?? "market
|
|
15447
|
+
text: `已把「${source.label}」设为可发布(${source.url},分支 ${result.branch})—— 仓库本身没动。之后发布可以落到它的 ${source.path ?? "market"}/。`
|
|
14984
15448
|
};
|
|
14985
15449
|
}
|
|
14986
15450
|
};
|