@dsh-workbench/core 1.1.4 → 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 +1298 -510
- package/dist/client/client.body.js +1298 -510
- package/dist/client/index.js +1160 -372
- package/dist/host/index.js +1151 -255
- package/dist/types/app/layouts/approval-detail.d.ts +6 -3
- package/dist/types/app/layouts/market/add-module-registry-prefs.d.ts +11 -0
- package/dist/types/app/layouts/market/market-check.d.ts +10 -1
- 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/AskAgentButton.d.ts +6 -0
- package/dist/types/app/layouts/rightbar/FailureCard.d.ts +8 -0
- package/dist/types/app/layouts/rightbar/MarketView.d.ts +6 -3
- package/dist/types/app/layouts/rightbar/SharedSection.d.ts +5 -2
- package/dist/types/app/layouts/rightbar/failure-card-text.d.ts +20 -0
- package/dist/types/app/layouts/rightbar/market-act.d.ts +2 -5
- package/dist/types/app/layouts/rightbar/market-change.d.ts +1 -0
- package/dist/types/app/layouts/rightbar/market-hint.d.ts +2 -0
- package/dist/types/app/layouts/rightbar/market-install.d.ts +11 -0
- package/dist/types/app/layouts/rightbar/market-row-types.d.ts +9 -1
- package/dist/types/app/layouts/rightbar/market-rows.d.ts +14 -1
- package/dist/types/app/layouts/rightbar/market-section.d.ts +5 -3
- package/dist/types/app/layouts/rightbar/market-transient-state.d.ts +3 -8
- 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/app/layouts/rightbar/market-view-text.d.ts +2 -0
- package/dist/types/kernel/client/registry.d.ts +1 -0
- package/dist/types/kernel/contract/bundle-install.d.ts +1 -1
- 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-install-error.d.ts +3 -0
- package/dist/types/modules/panels/model/market-install-error.test.d.ts +1 -0
- package/dist/types/modules/panels/model/market-registry.d.ts +2 -1
- package/dist/types/modules/panels/model/market-remove.d.ts +13 -0
- package/dist/types/modules/panels/model/market-remove.test.d.ts +1 -0
- package/dist/types/modules/panels/model/market-route.d.ts +1 -1
- 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/market.d.ts +1 -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 -2
- package/skills/workbench-module-diy/WIRING.md +2 -1
package/dist/host/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import path, { basename, 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";
|
|
5
|
-
import fs, { existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
5
|
+
import fs, { accessSync, constants, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
|
|
6
6
|
import { createRequire } from "node:module";
|
|
7
7
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
8
8
|
import writeFileAtomic from "write-file-atomic";
|
|
@@ -142,7 +142,9 @@ var NEED_KEYS = [
|
|
|
142
142
|
"commands",
|
|
143
143
|
"version",
|
|
144
144
|
"sources",
|
|
145
|
-
"secret"
|
|
145
|
+
"secret",
|
|
146
|
+
"install",
|
|
147
|
+
"docs"
|
|
146
148
|
];
|
|
147
149
|
var SOURCE_KEYS = [
|
|
148
150
|
"kind",
|
|
@@ -203,7 +205,7 @@ function sourcesProblemsOf(raw, at, allowed) {
|
|
|
203
205
|
if (raw === void 0) return [];
|
|
204
206
|
if (!Array.isArray(raw)) return [`${at}.sources 要是数组`];
|
|
205
207
|
if (raw.length === 0) return [`${at}.sources 是空数组 —— 不代装就整条删掉`];
|
|
206
|
-
if (allowed.length === 0) return [`${at} 这种需要不支持 sources ——
|
|
208
|
+
if (allowed.length === 0) return [`${at} 这种需要不支持 sources —— 要代装就写 install(一条命令)`];
|
|
207
209
|
const errors = [];
|
|
208
210
|
raw.forEach((source, index) => {
|
|
209
211
|
const one = sourceProblemsOf(source, `${at}.sources[${index}]`);
|
|
@@ -222,11 +224,40 @@ function commonProblemsOf(raw, at) {
|
|
|
222
224
|
if (raw["id"] !== void 0 && text$3(raw["id"]) === "") errors.push(`${at}.id 是空串`);
|
|
223
225
|
return errors;
|
|
224
226
|
}
|
|
227
|
+
function binInstallProblemsOf(raw, at) {
|
|
228
|
+
if (raw === void 0) return [];
|
|
229
|
+
if (!isRecord$1(raw)) return [`${at}.install 要是对象`];
|
|
230
|
+
const errors = [];
|
|
231
|
+
for (const key of Object.keys(raw)) if (![
|
|
232
|
+
"kind",
|
|
233
|
+
"line",
|
|
234
|
+
"platforms",
|
|
235
|
+
"package",
|
|
236
|
+
"registryEnv",
|
|
237
|
+
"registryDefault"
|
|
238
|
+
].includes(key)) errors.push(`${at}.install 有不认识的字段 ${key}`);
|
|
239
|
+
const kind = text$3(raw["kind"]);
|
|
240
|
+
if (kind !== "command" && kind !== "npm") errors.push(`${at}.install.kind 只认 command(一条命令)或 npm(一个包,装进隔离运行时)`);
|
|
241
|
+
if (kind === "command" && text$3(raw["line"]) === "") errors.push(`${at}.install.line 没写 —— 要一条能直接粘进终端的命令`);
|
|
242
|
+
if (kind === "npm" && text$3(raw["package"]) === "") errors.push(`${at}.install.package 没写 —— 这个命令由哪个 npm 包提供`);
|
|
243
|
+
if (raw["registryDefault"] !== void 0 && !REGISTRY_PATTERN.test(text$3(raw["registryDefault"]))) errors.push(`${at}.install.registryDefault 要是 http(s):// 或 file:// 的源地址`);
|
|
244
|
+
if (raw["platforms"] !== void 0) {
|
|
245
|
+
if (!Array.isArray(raw["platforms"]) || raw["platforms"].length === 0) errors.push(`${at}.install.platforms 要是非空数组`);
|
|
246
|
+
else if (raw["platforms"].some((one) => ![
|
|
247
|
+
"darwin",
|
|
248
|
+
"linux",
|
|
249
|
+
"win32"
|
|
250
|
+
].includes(text$3(one)))) errors.push(`${at}.install.platforms 只认 darwin / linux / win32`);
|
|
251
|
+
}
|
|
252
|
+
return errors;
|
|
253
|
+
}
|
|
225
254
|
function perKindProblemsOf(kind, raw, at) {
|
|
226
255
|
const errors = [];
|
|
227
256
|
if (kind === "bin" || kind === "lib" || kind === "env") {
|
|
228
257
|
if (text$3(raw["name"]) === "") errors.push(`${at}.name 没写`);
|
|
229
258
|
}
|
|
259
|
+
if (kind === "bin") errors.push(...binInstallProblemsOf(raw["install"], at));
|
|
260
|
+
if (raw["docs"] !== void 0 && text$3(raw["docs"]) === "") errors.push(`${at}.docs 是空串`);
|
|
230
261
|
if (kind === "extension") {
|
|
231
262
|
if (text$3(raw["runtime"]) === "") errors.push(`${at}.runtime 没写 —— 扩展属于哪个运行时(如 opencli)`);
|
|
232
263
|
if (text$3(raw["name"]) === "") errors.push(`${at}.name 没写`);
|
|
@@ -1100,7 +1131,7 @@ var PHASES = [
|
|
|
1100
1131
|
"ready",
|
|
1101
1132
|
"error"
|
|
1102
1133
|
];
|
|
1103
|
-
function asRecord$
|
|
1134
|
+
function asRecord$7(value) {
|
|
1104
1135
|
return typeof value === "object" && value !== null ? value : void 0;
|
|
1105
1136
|
}
|
|
1106
1137
|
function textOf$5(value, max) {
|
|
@@ -1114,7 +1145,7 @@ function atOf(value) {
|
|
|
1114
1145
|
return Math.trunc(value);
|
|
1115
1146
|
}
|
|
1116
1147
|
function errorOf(raw) {
|
|
1117
|
-
const record = asRecord$
|
|
1148
|
+
const record = asRecord$7(raw);
|
|
1118
1149
|
if (record === void 0) return void 0;
|
|
1119
1150
|
const code = textOf$5(record["code"], 120);
|
|
1120
1151
|
const message = textOf$5(record["message"], PANEL_STATE_MAX_TEXT);
|
|
@@ -1131,7 +1162,7 @@ function errorOf(raw) {
|
|
|
1131
1162
|
* 或一条都认不出(`unit` / `route` / `phase` / `at` 缺一)时 `ok: false` + 中文原因;认不出的单条静默跳过。
|
|
1132
1163
|
*/
|
|
1133
1164
|
function parseCardsReport(payload) {
|
|
1134
|
-
const record = asRecord$
|
|
1165
|
+
const record = asRecord$7(payload);
|
|
1135
1166
|
if (record === void 0) return {
|
|
1136
1167
|
ok: false,
|
|
1137
1168
|
message: "请求体要是一个对象"
|
|
@@ -1148,7 +1179,7 @@ function parseCardsReport(payload) {
|
|
|
1148
1179
|
};
|
|
1149
1180
|
const cards = [];
|
|
1150
1181
|
for (const item of raw.slice(0, 32)) {
|
|
1151
|
-
const card = asRecord$
|
|
1182
|
+
const card = asRecord$7(item);
|
|
1152
1183
|
if (card === void 0) continue;
|
|
1153
1184
|
const unit = textOf$5(card["unit"], 200);
|
|
1154
1185
|
const route = textOf$5(card["route"], 400);
|
|
@@ -1182,7 +1213,7 @@ function parseCardsReport(payload) {
|
|
|
1182
1213
|
* @returns `workspace` / `unit` / `reason` 都不能空,缺一给 `ok: false` + 中文原因;`at` 缺省或不合法时取当前时间。
|
|
1183
1214
|
*/
|
|
1184
1215
|
function parseCrashReport(payload) {
|
|
1185
|
-
const record = asRecord$
|
|
1216
|
+
const record = asRecord$7(payload);
|
|
1186
1217
|
if (record === void 0) return {
|
|
1187
1218
|
ok: false,
|
|
1188
1219
|
message: "请求体要是一个对象"
|
|
@@ -1343,7 +1374,7 @@ var TARGET_CODE = {
|
|
|
1343
1374
|
name: "workbench/no-name",
|
|
1344
1375
|
requestId: "workbench/no-request-id"
|
|
1345
1376
|
};
|
|
1346
|
-
function asRecord$
|
|
1377
|
+
function asRecord$6(value) {
|
|
1347
1378
|
return typeof value === "object" && value !== null ? value : {};
|
|
1348
1379
|
}
|
|
1349
1380
|
function stringsOf$3(value) {
|
|
@@ -1386,7 +1417,7 @@ function requestOf$1(action, value) {
|
|
|
1386
1417
|
};
|
|
1387
1418
|
}
|
|
1388
1419
|
function parseProvisionBody(body) {
|
|
1389
|
-
const raw = asRecord$
|
|
1420
|
+
const raw = asRecord$6(body);
|
|
1390
1421
|
const action = raw.action;
|
|
1391
1422
|
if (typeof action !== "string" || !PROVISION_ACTIONS.includes(action)) return {
|
|
1392
1423
|
ok: false,
|
|
@@ -1426,6 +1457,7 @@ var WorkbenchDataError = class extends Error {
|
|
|
1426
1457
|
hint;
|
|
1427
1458
|
project;
|
|
1428
1459
|
helpUrl;
|
|
1460
|
+
failure;
|
|
1429
1461
|
constructor(code, message, extra = {}) {
|
|
1430
1462
|
super(message);
|
|
1431
1463
|
this.name = "WorkbenchDataError";
|
|
@@ -1434,6 +1466,7 @@ var WorkbenchDataError = class extends Error {
|
|
|
1434
1466
|
if (extra.hint !== void 0) this.hint = extra.hint;
|
|
1435
1467
|
if (extra.project !== void 0) this.project = extra.project;
|
|
1436
1468
|
if (extra.helpUrl !== void 0) this.helpUrl = extra.helpUrl;
|
|
1469
|
+
if (extra.failure !== void 0) this.failure = extra.failure;
|
|
1437
1470
|
}
|
|
1438
1471
|
};
|
|
1439
1472
|
//#endregion
|
|
@@ -1481,7 +1514,8 @@ function toErrorPayload(error) {
|
|
|
1481
1514
|
...data.detail === void 0 ? {} : { detail: data.detail },
|
|
1482
1515
|
...data.hint === void 0 ? {} : { hint: data.hint },
|
|
1483
1516
|
...data.helpUrl === void 0 ? {} : { helpUrl: data.helpUrl },
|
|
1484
|
-
...data.project === void 0 ? {} : { project: data.project }
|
|
1517
|
+
...data.project === void 0 ? {} : { project: data.project },
|
|
1518
|
+
...data.failure === void 0 ? {} : { failure: data.failure }
|
|
1485
1519
|
};
|
|
1486
1520
|
}
|
|
1487
1521
|
return {
|
|
@@ -2231,7 +2265,10 @@ function tokenFamilyOf(name) {
|
|
|
2231
2265
|
return cut === -1 ? rest : rest.slice(0, cut);
|
|
2232
2266
|
}
|
|
2233
2267
|
//#endregion
|
|
2234
|
-
//#region ../shared/dist/
|
|
2268
|
+
//#region ../shared/dist/contract/approval.js
|
|
2269
|
+
/**
|
|
2270
|
+
* agent 自己那句"这次想干什么"的参数名:插件命令族必填,审批屏把它画在详情座里,不进标题。
|
|
2271
|
+
*/
|
|
2235
2272
|
var APPROVAL_NOTE_ARG = "approval_note";
|
|
2236
2273
|
function oneLine(text) {
|
|
2237
2274
|
return text.replace(/\s+/g, " ").trim();
|
|
@@ -2295,10 +2332,11 @@ async function approvalFor(call, context = {}) {
|
|
|
2295
2332
|
reason: facts.blocked
|
|
2296
2333
|
};
|
|
2297
2334
|
if (context.skipAsk === true) return void 0;
|
|
2335
|
+
const note = context.declaredNote === true && verdict.noteInDetail !== true ? approvalNoteOf(call) : void 0;
|
|
2298
2336
|
return {
|
|
2299
2337
|
kind: "ask",
|
|
2300
2338
|
reason: verdict.reason,
|
|
2301
|
-
displayReason: compose(facts, verdict,
|
|
2339
|
+
displayReason: compose(facts, verdict, note)
|
|
2302
2340
|
};
|
|
2303
2341
|
}
|
|
2304
2342
|
//#endregion
|
|
@@ -2518,6 +2556,188 @@ function backgroundLine(command, argv) {
|
|
|
2518
2556
|
].join(" ");
|
|
2519
2557
|
}
|
|
2520
2558
|
//#endregion
|
|
2559
|
+
//#region ../shared/dist/host/install-failure.js
|
|
2560
|
+
var TARGET_FLAGS = [
|
|
2561
|
+
"--prefix",
|
|
2562
|
+
"--cache",
|
|
2563
|
+
"--dir",
|
|
2564
|
+
"--store-dir"
|
|
2565
|
+
];
|
|
2566
|
+
var INSTALL_VERBS = /^\s*npm\s+(i|install|add)\b/;
|
|
2567
|
+
var NPM_PATH = /^npm (?:error|ERR!) path (.+)$/m;
|
|
2568
|
+
function commandTokens(command) {
|
|
2569
|
+
const tokens = [];
|
|
2570
|
+
const token = /'([^']*)'|"([^"]*)"|(\S+)/g;
|
|
2571
|
+
for (let match = token.exec(command); match !== null; match = token.exec(command)) tokens.push(match[1] ?? match[2] ?? match[3] ?? "");
|
|
2572
|
+
return tokens;
|
|
2573
|
+
}
|
|
2574
|
+
function installTargets(command) {
|
|
2575
|
+
const tokens = commandTokens(command);
|
|
2576
|
+
const targets = [];
|
|
2577
|
+
for (let index = 0; index < tokens.length - 1; index += 1) if (TARGET_FLAGS.includes(tokens[index] ?? "")) targets.push(tokens[index + 1] ?? "");
|
|
2578
|
+
return targets.filter((target) => target !== "");
|
|
2579
|
+
}
|
|
2580
|
+
function failurePathIn(output) {
|
|
2581
|
+
const path = NPM_PATH.exec(output)?.[1]?.trim();
|
|
2582
|
+
return path === void 0 || path === "" ? void 0 : path;
|
|
2583
|
+
}
|
|
2584
|
+
function nearestDir(path) {
|
|
2585
|
+
let at = path;
|
|
2586
|
+
for (;;) {
|
|
2587
|
+
const here = () => {
|
|
2588
|
+
try {
|
|
2589
|
+
return statSync(at).isDirectory();
|
|
2590
|
+
} catch {
|
|
2591
|
+
return false;
|
|
2592
|
+
}
|
|
2593
|
+
};
|
|
2594
|
+
if (here()) return at;
|
|
2595
|
+
const up = dirname(at);
|
|
2596
|
+
if (up === at) return void 0;
|
|
2597
|
+
at = up;
|
|
2598
|
+
}
|
|
2599
|
+
}
|
|
2600
|
+
function writeStateOf(dir) {
|
|
2601
|
+
try {
|
|
2602
|
+
accessSync(dir, constants.W_OK);
|
|
2603
|
+
return "ok";
|
|
2604
|
+
} catch (error) {
|
|
2605
|
+
return error.code === "EACCES" ? "denied" : "unknown";
|
|
2606
|
+
}
|
|
2607
|
+
}
|
|
2608
|
+
function dirFacts(dir) {
|
|
2609
|
+
try {
|
|
2610
|
+
const stats = statSync(dir);
|
|
2611
|
+
return {
|
|
2612
|
+
mode: (stats.mode & 511).toString(8),
|
|
2613
|
+
owner: {
|
|
2614
|
+
uid: stats.uid,
|
|
2615
|
+
gid: stats.gid
|
|
2616
|
+
},
|
|
2617
|
+
writable: writeStateOf(dir) === "ok"
|
|
2618
|
+
};
|
|
2619
|
+
} catch {
|
|
2620
|
+
return;
|
|
2621
|
+
}
|
|
2622
|
+
}
|
|
2623
|
+
function currentUser() {
|
|
2624
|
+
return {
|
|
2625
|
+
uid: process.getuid?.() ?? -1,
|
|
2626
|
+
gid: process.getgid?.() ?? -1
|
|
2627
|
+
};
|
|
2628
|
+
}
|
|
2629
|
+
function classifyInstallFailure(output) {
|
|
2630
|
+
const text = output.toLowerCase();
|
|
2631
|
+
if (/enospc|no space left/.test(text)) return "disk-full";
|
|
2632
|
+
if (/eacces|eperm|permission denied|operation not permitted|access is denied/.test(text)) return "permission";
|
|
2633
|
+
return "unknown";
|
|
2634
|
+
}
|
|
2635
|
+
function installFailureOf(facts) {
|
|
2636
|
+
const kind = facts.kind ?? classifyInstallFailure(facts.output);
|
|
2637
|
+
const path = facts.path;
|
|
2638
|
+
const base = {
|
|
2639
|
+
command: facts.command,
|
|
2640
|
+
exitCode: facts.exitCode,
|
|
2641
|
+
output: facts.output,
|
|
2642
|
+
...path === void 0 ? {} : { path },
|
|
2643
|
+
...facts.mode === void 0 ? {} : { mode: facts.mode },
|
|
2644
|
+
...facts.owner === void 0 ? {} : { owner: facts.owner },
|
|
2645
|
+
current: facts.current
|
|
2646
|
+
};
|
|
2647
|
+
if (kind === "disk-full") return {
|
|
2648
|
+
...base,
|
|
2649
|
+
kind,
|
|
2650
|
+
fix: path === void 0 ? { kind: "handoff" } : {
|
|
2651
|
+
kind: "space",
|
|
2652
|
+
path
|
|
2653
|
+
}
|
|
2654
|
+
};
|
|
2655
|
+
if (kind === "permission" && path !== void 0) {
|
|
2656
|
+
const owner = facts.owner;
|
|
2657
|
+
if (owner !== void 0 && owner.uid !== facts.current.uid && facts.current.uid >= 0) return {
|
|
2658
|
+
...base,
|
|
2659
|
+
kind,
|
|
2660
|
+
fix: {
|
|
2661
|
+
kind: "chown",
|
|
2662
|
+
path,
|
|
2663
|
+
uid: facts.current.uid,
|
|
2664
|
+
gid: facts.current.gid
|
|
2665
|
+
}
|
|
2666
|
+
};
|
|
2667
|
+
if (facts.writable === false) return {
|
|
2668
|
+
...base,
|
|
2669
|
+
kind,
|
|
2670
|
+
fix: {
|
|
2671
|
+
kind: "chmod",
|
|
2672
|
+
path
|
|
2673
|
+
}
|
|
2674
|
+
};
|
|
2675
|
+
if (facts.writable === true) return {
|
|
2676
|
+
...base,
|
|
2677
|
+
kind: "sandbox",
|
|
2678
|
+
fix: { kind: "handoff" }
|
|
2679
|
+
};
|
|
2680
|
+
}
|
|
2681
|
+
return {
|
|
2682
|
+
...base,
|
|
2683
|
+
kind,
|
|
2684
|
+
fix: { kind: "handoff" }
|
|
2685
|
+
};
|
|
2686
|
+
}
|
|
2687
|
+
function diagnoseInstallFailure(input) {
|
|
2688
|
+
const current = currentUser();
|
|
2689
|
+
const candidates = [
|
|
2690
|
+
failurePathIn(input.output),
|
|
2691
|
+
...installTargets(input.command),
|
|
2692
|
+
input.fallback
|
|
2693
|
+
].filter((path) => path !== void 0 && path !== "");
|
|
2694
|
+
for (const candidate of candidates) {
|
|
2695
|
+
const dir = nearestDir(candidate);
|
|
2696
|
+
if (dir === void 0) continue;
|
|
2697
|
+
const facts = dirFacts(dir);
|
|
2698
|
+
if (facts === void 0) continue;
|
|
2699
|
+
return installFailureOf({
|
|
2700
|
+
command: input.command,
|
|
2701
|
+
exitCode: input.exitCode,
|
|
2702
|
+
output: input.output,
|
|
2703
|
+
path: dir,
|
|
2704
|
+
mode: facts.mode,
|
|
2705
|
+
owner: facts.owner,
|
|
2706
|
+
writable: facts.writable,
|
|
2707
|
+
current
|
|
2708
|
+
});
|
|
2709
|
+
}
|
|
2710
|
+
return installFailureOf({
|
|
2711
|
+
command: input.command,
|
|
2712
|
+
exitCode: input.exitCode,
|
|
2713
|
+
output: input.output,
|
|
2714
|
+
current
|
|
2715
|
+
});
|
|
2716
|
+
}
|
|
2717
|
+
function installWriteProblem(input) {
|
|
2718
|
+
if (!INSTALL_VERBS.test(input.command)) return void 0;
|
|
2719
|
+
const current = currentUser();
|
|
2720
|
+
const candidates = [...installTargets(input.command), input.fallback].filter((path) => path !== void 0 && path !== "");
|
|
2721
|
+
for (const candidate of candidates) {
|
|
2722
|
+
const dir = nearestDir(candidate);
|
|
2723
|
+
if (dir === void 0) continue;
|
|
2724
|
+
if (writeStateOf(dir) !== "denied") continue;
|
|
2725
|
+
const facts = dirFacts(dir);
|
|
2726
|
+
if (facts === void 0) continue;
|
|
2727
|
+
return installFailureOf({
|
|
2728
|
+
command: input.command,
|
|
2729
|
+
exitCode: null,
|
|
2730
|
+
output: "",
|
|
2731
|
+
kind: "permission",
|
|
2732
|
+
path: dir,
|
|
2733
|
+
mode: facts.mode,
|
|
2734
|
+
owner: facts.owner,
|
|
2735
|
+
writable: facts.writable,
|
|
2736
|
+
current
|
|
2737
|
+
});
|
|
2738
|
+
}
|
|
2739
|
+
}
|
|
2740
|
+
//#endregion
|
|
2521
2741
|
//#region ../shared/dist/host/paths.js
|
|
2522
2742
|
function isolatedRoot() {
|
|
2523
2743
|
return `${workbenchRoot()}/opencli`;
|
|
@@ -2531,6 +2751,46 @@ function vendorPluginsDir() {
|
|
|
2531
2751
|
function libsPrefix() {
|
|
2532
2752
|
return `${isolatedRoot()}/libs`;
|
|
2533
2753
|
}
|
|
2754
|
+
var OPENCLI_NPM_PACKAGE = "@jackwener/opencli";
|
|
2755
|
+
function toolchainOpencliDir() {
|
|
2756
|
+
return `${isolatedRoot()}/toolchain/node_modules/${OPENCLI_NPM_PACKAGE}`;
|
|
2757
|
+
}
|
|
2758
|
+
function libsBinDir() {
|
|
2759
|
+
return `${libsPrefix()}/node_modules/.bin`;
|
|
2760
|
+
}
|
|
2761
|
+
/**
|
|
2762
|
+
* 把隔离运行时的共用依赖 bin 目录放到 PATH 最前面(可执行文件都从这儿找)。
|
|
2763
|
+
* @param env 改哪份环境(默认当前进程)。
|
|
2764
|
+
* @returns 真的插进去了就 `true`;已经在里面就 `false`。
|
|
2765
|
+
*/
|
|
2766
|
+
function ensureLibsBinOnPath(env = process.env) {
|
|
2767
|
+
const dir = libsBinDir();
|
|
2768
|
+
const parts = (env.PATH ?? "").split(delimiter).filter((one) => one !== "");
|
|
2769
|
+
if (parts.includes(dir)) return false;
|
|
2770
|
+
env.PATH = [dir, ...parts].join(delimiter);
|
|
2771
|
+
return true;
|
|
2772
|
+
}
|
|
2773
|
+
//#endregion
|
|
2774
|
+
//#region ../shared/dist/host/runtime-links.js
|
|
2775
|
+
/**
|
|
2776
|
+
* 让共用依赖也能 import opencli:把隔离运行时里那份 opencli 链接到 libs 的 node_modules。
|
|
2777
|
+
* 跑在 libs 里的命令(比如 opencli-workflow 带的 gitops)会按包名找 @jackwener/opencli,
|
|
2778
|
+
* 找不到就起不来 —— 它不在 libs 的依赖里,是运行时提供的那一份。
|
|
2779
|
+
* @returns 这次真的建了链接就 `true`;已经有了、或运行时那份还没装,就是 `false`。
|
|
2780
|
+
*/
|
|
2781
|
+
function ensureLibsOpencliPeer() {
|
|
2782
|
+
const link = `${libsPrefix()}/node_modules/${OPENCLI_NPM_PACKAGE}`;
|
|
2783
|
+
if (existsSync(link)) return false;
|
|
2784
|
+
const target = toolchainOpencliDir();
|
|
2785
|
+
if (!existsSync(target)) return false;
|
|
2786
|
+
try {
|
|
2787
|
+
mkdirSync(dirname(link), { recursive: true });
|
|
2788
|
+
symlinkSync(target, link);
|
|
2789
|
+
return true;
|
|
2790
|
+
} catch {
|
|
2791
|
+
return false;
|
|
2792
|
+
}
|
|
2793
|
+
}
|
|
2534
2794
|
//#endregion
|
|
2535
2795
|
//#region ../shared/dist/host/adapters.js
|
|
2536
2796
|
var WORKBENCH_ADAPTERS = "workbenchAdapters";
|
|
@@ -4066,7 +4326,7 @@ function registryFailure(change) {
|
|
|
4066
4326
|
//#endregion
|
|
4067
4327
|
//#region ../shared/dist/host/plugin.js
|
|
4068
4328
|
var OPENCLI_MANIFEST_FILE = "opencli-plugin.json";
|
|
4069
|
-
function asRecord$
|
|
4329
|
+
function asRecord$5(value) {
|
|
4070
4330
|
return typeof value === "object" && value !== null ? value : void 0;
|
|
4071
4331
|
}
|
|
4072
4332
|
function isCommandFile(name) {
|
|
@@ -4078,7 +4338,7 @@ function pluginManifestOf(dir) {
|
|
|
4078
4338
|
try {
|
|
4079
4339
|
const parsed = JSON.parse(readFileSync(join(dir, OPENCLI_MANIFEST_FILE), "utf8"));
|
|
4080
4340
|
if (Array.isArray(parsed)) return void 0;
|
|
4081
|
-
return asRecord$
|
|
4341
|
+
return asRecord$5(parsed);
|
|
4082
4342
|
} catch {
|
|
4083
4343
|
return;
|
|
4084
4344
|
}
|
|
@@ -4636,6 +4896,11 @@ function checkVersionRange(contract, version) {
|
|
|
4636
4896
|
if (parsed === null) return "malformed";
|
|
4637
4897
|
return import_semver.default.satisfies(parsed, range) ? "satisfied" : "unsatisfied";
|
|
4638
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";
|
|
4639
4904
|
var MARKET_DISCOVER_ROUTE = "panels/market-discover";
|
|
4640
4905
|
var MARKET_DOC_ROUTE = "panels/market-doc";
|
|
4641
4906
|
var MARKET_GIT_PROBE_ROUTE = "panels/git-probe";
|
|
@@ -4657,100 +4922,102 @@ function marketText(value, locale) {
|
|
|
4657
4922
|
return locale.toLowerCase().startsWith("zh") ? zh || en : en || zh;
|
|
4658
4923
|
}
|
|
4659
4924
|
//#endregion
|
|
4660
|
-
//#region ../shared/dist/host/market-index.js
|
|
4661
|
-
var MARKET_INDEX_SEED = "{\n \"format\": 1,\n \"plugins\": []\n}\n";
|
|
4662
|
-
var MARKET_INDEX_README_LINES = [
|
|
4663
|
-
"# 模块市场索引",
|
|
4664
|
-
"",
|
|
4665
|
-
"这个仓库给 DeepSeek Harness 工作台当模块市场索引:`market.json`(format 1)列出可安装的模块与版本。",
|
|
4666
|
-
"",
|
|
4667
|
-
"## 发布模块",
|
|
4668
|
-
"",
|
|
4669
|
-
"在模块包里运行:",
|
|
4670
|
-
"",
|
|
4671
|
-
"```bash",
|
|
4672
|
-
"dsh-wb-module release --index <本仓库地址> --index-policy artifacts-in-repo",
|
|
4673
|
-
"```",
|
|
4674
|
-
"",
|
|
4675
|
-
"产物会提交到 `packages/<包名>/`(带 scope 时是 `packages/@scope/名字/`),并更新 `market.json`。",
|
|
4676
|
-
"",
|
|
4677
|
-
"一个包名归第一次发布的人(`market.json` 里的 `owner`)。换一个署名发同名会被拒;要换人,先把这一条的 `owner` 改成新的人。",
|
|
4678
|
-
"同一个市场里,一个模块 id(`metadata.unit`)只能被一个条目使用;另一个包名想用同一个 id / 产物目录也会被拒。",
|
|
4679
|
-
"",
|
|
4680
|
-
"## 使用这个索引",
|
|
4681
|
-
"",
|
|
4682
|
-
"在工作台的「模块市场 → 发现」里添加本仓库地址,即可浏览与安装。",
|
|
4683
|
-
""
|
|
4684
|
-
];
|
|
4925
|
+
//#region ../shared/dist/host/market-index-parse.js
|
|
4685
4926
|
var NPM_NAME = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/;
|
|
4686
4927
|
var GIT_COMMIT = /^[0-9a-f]{40}$/;
|
|
4687
4928
|
var MAX_ICON_CHARS = 32768;
|
|
4688
|
-
function
|
|
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
|
+
}
|
|
4958
|
+
function asRecord$4(value) {
|
|
4689
4959
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
4690
4960
|
}
|
|
4691
|
-
function asText$
|
|
4961
|
+
function asText$4(value) {
|
|
4692
4962
|
return typeof value === "string" ? value.trim() : "";
|
|
4693
4963
|
}
|
|
4694
4964
|
function asTextArray(value) {
|
|
4695
4965
|
return Array.isArray(value) ? value.flatMap((item) => typeof item === "string" && item.trim() !== "" ? [item.trim()] : []) : [];
|
|
4696
4966
|
}
|
|
4697
4967
|
function asLocalized(value) {
|
|
4698
|
-
const record = asRecord$
|
|
4968
|
+
const record = asRecord$4(value);
|
|
4699
4969
|
if (record === void 0) return void 0;
|
|
4700
|
-
const zh = asText$
|
|
4701
|
-
const en = asText$
|
|
4970
|
+
const zh = asText$4(record["zh"]);
|
|
4971
|
+
const en = asText$4(record["en"]);
|
|
4702
4972
|
return zh === "" && en === "" ? void 0 : {
|
|
4703
4973
|
...zh === "" ? {} : { zh },
|
|
4704
4974
|
...en === "" ? {} : { en }
|
|
4705
4975
|
};
|
|
4706
4976
|
}
|
|
4707
|
-
function problem(kind, detail, at) {
|
|
4977
|
+
function problem$1(kind, detail, at) {
|
|
4708
4978
|
return {
|
|
4709
4979
|
kind,
|
|
4710
4980
|
detail,
|
|
4711
4981
|
...at === void 0 ? {} : { at }
|
|
4712
4982
|
};
|
|
4713
4983
|
}
|
|
4714
|
-
function compareSemver(left, right) {
|
|
4715
|
-
return compareVersions(left, right);
|
|
4716
|
-
}
|
|
4717
4984
|
function sourceOf(raw, at) {
|
|
4718
|
-
const record = asRecord$
|
|
4719
|
-
if (record === void 0) return problem("source", "source 必须是一个对象", at);
|
|
4985
|
+
const record = asRecord$4(raw);
|
|
4986
|
+
if (record === void 0) return problem$1("source", "source 必须是一个对象", at);
|
|
4720
4987
|
if (record["type"] === "npm") {
|
|
4721
4988
|
const registry = record["registry"];
|
|
4722
|
-
if (registry !== void 0 && asText$
|
|
4989
|
+
if (registry !== void 0 && asText$4(registry) === "") return problem$1("source", "npm source 的 registry 必须是非空字符串", at);
|
|
4723
4990
|
return {
|
|
4724
4991
|
type: "npm",
|
|
4725
|
-
...registry === void 0 ? {} : { registry: asText$
|
|
4992
|
+
...registry === void 0 ? {} : { registry: asText$4(registry) }
|
|
4726
4993
|
};
|
|
4727
4994
|
}
|
|
4728
4995
|
if (record["type"] === "git") {
|
|
4729
|
-
const url = asText$
|
|
4730
|
-
const commit = asText$
|
|
4731
|
-
if (url === "") return problem("source", "git source 缺 url", at);
|
|
4732
|
-
if (!GIT_COMMIT.test(commit)) return problem("git-commit", `git source 的 commit 必须是 40 位小写 SHA(现在是 ${JSON.stringify(commit)})`, at);
|
|
4996
|
+
const url = asText$4(record["url"]);
|
|
4997
|
+
const commit = asText$4(record["commit"]);
|
|
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);
|
|
4733
5000
|
const path = record["path"];
|
|
4734
5001
|
return {
|
|
4735
5002
|
type: "git",
|
|
4736
5003
|
url,
|
|
4737
5004
|
commit,
|
|
4738
|
-
...path === void 0 || asText$
|
|
5005
|
+
...path === void 0 || asText$4(path) === "" ? {} : { path: asText$4(path) }
|
|
4739
5006
|
};
|
|
4740
5007
|
}
|
|
4741
|
-
return problem("source", "source.type 只能是 npm 或 git", at);
|
|
5008
|
+
return problem$1("source", "source.type 只能是 npm 或 git", at);
|
|
4742
5009
|
}
|
|
4743
5010
|
function versionOf(raw, at) {
|
|
4744
|
-
const record = asRecord$
|
|
4745
|
-
if (record === void 0) return problem("version", "版本必须是一个对象", at);
|
|
4746
|
-
const version = asText$
|
|
4747
|
-
if (!isValidVersion(version)) return problem("version", `版本号不是合法 SemVer:${JSON.stringify(version)}`, at);
|
|
5011
|
+
const record = asRecord$4(raw);
|
|
5012
|
+
if (record === void 0) return problem$1("version", "版本必须是一个对象", at);
|
|
5013
|
+
const version = asText$4(record["version"]);
|
|
5014
|
+
if (!isValidVersion(version)) return problem$1("version", `版本号不是合法 SemVer:${JSON.stringify(version)}`, at);
|
|
4748
5015
|
const source = sourceOf(record["source"], `${at}.source`);
|
|
4749
5016
|
if ("kind" in source) return source;
|
|
4750
5017
|
const timestamp = record["publishTimestamp"];
|
|
4751
|
-
if (timestamp !== void 0 && (!Number.isInteger(timestamp) || timestamp < 0)) return problem("timestamp", "publishTimestamp 必须是 Unix 秒整数", at);
|
|
4752
|
-
const engines = asRecord$
|
|
4753
|
-
const dsh = engines === void 0 ? "" : asText$
|
|
5018
|
+
if (timestamp !== void 0 && (!Number.isInteger(timestamp) || timestamp < 0)) return problem$1("timestamp", "publishTimestamp 必须是 Unix 秒整数", at);
|
|
5019
|
+
const engines = asRecord$4(record["engines"]);
|
|
5020
|
+
const dsh = engines === void 0 ? "" : asText$4(engines["dsh"]);
|
|
4754
5021
|
return {
|
|
4755
5022
|
version,
|
|
4756
5023
|
source,
|
|
@@ -4759,43 +5026,43 @@ function versionOf(raw, at) {
|
|
|
4759
5026
|
};
|
|
4760
5027
|
}
|
|
4761
5028
|
function versionsOf$1(raw, at) {
|
|
4762
|
-
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);
|
|
4763
5030
|
const versions = [];
|
|
4764
5031
|
const seen = /* @__PURE__ */ new Set();
|
|
4765
5032
|
for (const [index, item] of raw.entries()) {
|
|
4766
5033
|
const parsed = versionOf(item, `${at}[${String(index)}]`);
|
|
4767
5034
|
if ("kind" in parsed) return parsed;
|
|
4768
|
-
if (seen.has(parsed.version)) return problem("versions", `版本重复:${parsed.version}`, at);
|
|
5035
|
+
if (seen.has(parsed.version)) return problem$1("versions", `版本重复:${parsed.version}`, at);
|
|
4769
5036
|
seen.add(parsed.version);
|
|
4770
5037
|
versions.push(parsed);
|
|
4771
5038
|
}
|
|
4772
5039
|
for (let index = 1; index < versions.length; index += 1) {
|
|
4773
5040
|
const order = compareSemver(versions[index - 1]?.version ?? "", versions[index]?.version ?? "");
|
|
4774
|
-
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);
|
|
4775
5042
|
}
|
|
4776
5043
|
return versions;
|
|
4777
5044
|
}
|
|
4778
5045
|
function iconProblem(icon) {
|
|
4779
|
-
if (icon.length > MAX_ICON_CHARS) return problem("icon", "icon 超过 32 KiB");
|
|
4780
|
-
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");
|
|
4781
5048
|
if (/^https?:\/\//i.test(icon)) return void 0;
|
|
4782
|
-
return problem("icon", "icon 只能是 base64 data URL 或 http(s) URL");
|
|
5049
|
+
return problem$1("icon", "icon 只能是 base64 data URL 或 http(s) URL");
|
|
4783
5050
|
}
|
|
4784
5051
|
var MAX_OWNER_CHARS = 200;
|
|
4785
5052
|
var UNIT_ID = /^[a-z][a-z0-9-]*$/;
|
|
4786
5053
|
function ownerProblem(owner) {
|
|
4787
|
-
if (/[\r\n]/u.test(owner)) return problem("owner", "owner 只能是一行署名");
|
|
4788
|
-
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 字");
|
|
4789
5056
|
}
|
|
4790
5057
|
function metadataOf(raw) {
|
|
4791
|
-
const record = asRecord$
|
|
5058
|
+
const record = asRecord$4(raw);
|
|
4792
5059
|
if (record === void 0) return void 0;
|
|
4793
5060
|
const workbench = record["workbench"] === true;
|
|
4794
|
-
const unit = asText$
|
|
4795
|
-
const category = asText$
|
|
5061
|
+
const unit = asText$4(record["unit"]);
|
|
5062
|
+
const category = asText$4(record["category"]);
|
|
4796
5063
|
const labels = asTextArray(record["labels"]);
|
|
4797
5064
|
const keywords = asTextArray(record["keywords"]);
|
|
4798
|
-
const sortKeysRecord = asRecord$
|
|
5065
|
+
const sortKeysRecord = asRecord$4(record["sortKeys"]);
|
|
4799
5066
|
const sortKeys = sortKeysRecord === void 0 ? void 0 : Object.fromEntries(Object.entries(sortKeysRecord).flatMap(([key, value]) => typeof value === "string" || typeof value === "number" ? [[key, value]] : []));
|
|
4800
5067
|
return {
|
|
4801
5068
|
...workbench ? { workbench: true } : {},
|
|
@@ -4807,28 +5074,28 @@ function metadataOf(raw) {
|
|
|
4807
5074
|
};
|
|
4808
5075
|
}
|
|
4809
5076
|
function entryOf$1(raw, at) {
|
|
4810
|
-
const record = asRecord$
|
|
4811
|
-
if (record === void 0) return problem("plugin", "插件条目必须是对象", at);
|
|
4812
|
-
const name = asText$
|
|
4813
|
-
if (name === "" || name.length > 214 || !NPM_NAME.test(name)) return problem("name", `不是合法 npm 包名:${JSON.stringify(name)}`, at);
|
|
5077
|
+
const record = asRecord$4(raw);
|
|
5078
|
+
if (record === void 0) return problem$1("plugin", "插件条目必须是对象", at);
|
|
5079
|
+
const name = asText$4(record["name"]);
|
|
5080
|
+
if (name === "" || name.length > 214 || !NPM_NAME.test(name)) return problem$1("name", `不是合法 npm 包名:${JSON.stringify(name)}`, at);
|
|
4814
5081
|
const versions = versionsOf$1(record["versions"], `${at}.versions`);
|
|
4815
5082
|
if ("kind" in versions) return versions;
|
|
4816
|
-
const icon = asText$
|
|
5083
|
+
const icon = asText$4(record["icon"]);
|
|
4817
5084
|
if (icon !== "") {
|
|
4818
5085
|
const badIcon = iconProblem(icon);
|
|
4819
5086
|
if (badIcon !== void 0) return badIcon;
|
|
4820
5087
|
}
|
|
4821
5088
|
const title = asLocalized(record["title"]);
|
|
4822
5089
|
const description = asLocalized(record["description"]);
|
|
4823
|
-
const author = asText$
|
|
4824
|
-
const owner = asText$
|
|
5090
|
+
const author = asText$4(record["author"]);
|
|
5091
|
+
const owner = asText$4(record["owner"]);
|
|
4825
5092
|
if (owner !== "") {
|
|
4826
5093
|
const badOwner = ownerProblem(owner);
|
|
4827
5094
|
if (badOwner !== void 0) return badOwner;
|
|
4828
5095
|
}
|
|
4829
|
-
const homepage = asText$
|
|
5096
|
+
const homepage = asText$4(record["homepage"]);
|
|
4830
5097
|
const metadata = metadataOf(record["metadata"]);
|
|
4831
|
-
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);
|
|
4832
5099
|
return {
|
|
4833
5100
|
name,
|
|
4834
5101
|
versions,
|
|
@@ -4842,19 +5109,19 @@ function entryOf$1(raw, at) {
|
|
|
4842
5109
|
};
|
|
4843
5110
|
}
|
|
4844
5111
|
function parseMarketIndex(raw) {
|
|
4845
|
-
const record = asRecord$
|
|
5112
|
+
const record = asRecord$4(raw);
|
|
4846
5113
|
if (record === void 0) return {
|
|
4847
5114
|
ok: false,
|
|
4848
|
-
problems: [problem("not-object", "索引必须是一个 JSON 对象")]
|
|
5115
|
+
problems: [problem$1("not-object", "索引必须是一个 JSON 对象")]
|
|
4849
5116
|
};
|
|
4850
5117
|
if (record["format"] !== 1) return {
|
|
4851
5118
|
ok: false,
|
|
4852
|
-
problems: [problem("format", `format 必须是 ${String(1)}(现在是 ${JSON.stringify(record["format"])})`)]
|
|
5119
|
+
problems: [problem$1("format", `format 必须是 ${String(1)}(现在是 ${JSON.stringify(record["format"])})`)]
|
|
4853
5120
|
};
|
|
4854
5121
|
const plugins = record["plugins"];
|
|
4855
5122
|
if (!Array.isArray(plugins)) return {
|
|
4856
5123
|
ok: false,
|
|
4857
|
-
problems: [problem("plugins", "plugins 必须是数组")]
|
|
5124
|
+
problems: [problem$1("plugins", "plugins 必须是数组")]
|
|
4858
5125
|
};
|
|
4859
5126
|
const entries = [];
|
|
4860
5127
|
const problems = [];
|
|
@@ -4866,7 +5133,7 @@ function parseMarketIndex(raw) {
|
|
|
4866
5133
|
continue;
|
|
4867
5134
|
}
|
|
4868
5135
|
if (seen.has(parsed.name)) {
|
|
4869
|
-
problems.push(problem("duplicate-name", `插件名重复:${parsed.name}`, `plugins[${String(index)}]`));
|
|
5136
|
+
problems.push(problem$1("duplicate-name", `插件名重复:${parsed.name}`, `plugins[${String(index)}]`));
|
|
4870
5137
|
continue;
|
|
4871
5138
|
}
|
|
4872
5139
|
seen.add(parsed.name);
|
|
@@ -4878,7 +5145,7 @@ function parseMarketIndex(raw) {
|
|
|
4878
5145
|
};
|
|
4879
5146
|
const title = asLocalized(record["title"]);
|
|
4880
5147
|
const description = asLocalized(record["description"]);
|
|
4881
|
-
const metadata = asRecord$
|
|
5148
|
+
const metadata = asRecord$4(record["metadata"]);
|
|
4882
5149
|
return {
|
|
4883
5150
|
ok: true,
|
|
4884
5151
|
index: {
|
|
@@ -4896,10 +5163,13 @@ function parseMarketIndexJson(text) {
|
|
|
4896
5163
|
} catch (error) {
|
|
4897
5164
|
return {
|
|
4898
5165
|
ok: false,
|
|
4899
|
-
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)}`)]
|
|
4900
5167
|
};
|
|
4901
5168
|
}
|
|
4902
5169
|
}
|
|
5170
|
+
function sortMarketVersions(versions) {
|
|
5171
|
+
return [...versions].sort((left, right) => compareSemver(right.version, left.version) ?? 0);
|
|
5172
|
+
}
|
|
4903
5173
|
function marketEntryLabels(entry) {
|
|
4904
5174
|
return entry.metadata?.labels ?? [];
|
|
4905
5175
|
}
|
|
@@ -4932,32 +5202,32 @@ function marketSourceKey(source) {
|
|
|
4932
5202
|
return createHash("sha256").update(canonical).digest("hex").slice(0, 16);
|
|
4933
5203
|
}
|
|
4934
5204
|
function parseSourceConfig(raw) {
|
|
4935
|
-
const record = asRecord$
|
|
5205
|
+
const record = asRecord$4(raw);
|
|
4936
5206
|
if (record === void 0) return {
|
|
4937
5207
|
ok: false,
|
|
4938
|
-
problem: problem("source", "索引源必须是对象")
|
|
5208
|
+
problem: problem$1("source", "索引源必须是对象")
|
|
4939
5209
|
};
|
|
4940
|
-
const id = asText$
|
|
4941
|
-
const label = asText$
|
|
5210
|
+
const id = asText$4(record["id"]);
|
|
5211
|
+
const label = asText$4(record["label"]);
|
|
4942
5212
|
const kind = record["kind"];
|
|
4943
|
-
const url = asText$
|
|
5213
|
+
const url = asText$4(record["url"]);
|
|
4944
5214
|
if (id === "") return {
|
|
4945
5215
|
ok: false,
|
|
4946
|
-
problem: problem("source", "索引源缺 id")
|
|
5216
|
+
problem: problem$1("source", "索引源缺 id")
|
|
4947
5217
|
};
|
|
4948
5218
|
if (label === "") return {
|
|
4949
5219
|
ok: false,
|
|
4950
|
-
problem: problem("source", "索引源缺 label")
|
|
5220
|
+
problem: problem$1("source", "索引源缺 label")
|
|
4951
5221
|
};
|
|
4952
5222
|
if (kind !== "http" && kind !== "git") return {
|
|
4953
5223
|
ok: false,
|
|
4954
|
-
problem: problem("source", "kind 只能是 http 或 git")
|
|
5224
|
+
problem: problem$1("source", "kind 只能是 http 或 git")
|
|
4955
5225
|
};
|
|
4956
5226
|
if (kind === "http") {
|
|
4957
5227
|
const bad = httpUrlProblem(url);
|
|
4958
5228
|
if (bad !== void 0) return {
|
|
4959
5229
|
ok: false,
|
|
4960
|
-
problem: problem("source", bad)
|
|
5230
|
+
problem: problem$1("source", bad)
|
|
4961
5231
|
};
|
|
4962
5232
|
return {
|
|
4963
5233
|
ok: true,
|
|
@@ -4971,15 +5241,15 @@ function parseSourceConfig(raw) {
|
|
|
4971
5241
|
}
|
|
4972
5242
|
if (!gitUrlOk(url)) return {
|
|
4973
5243
|
ok: false,
|
|
4974
|
-
problem: problem("source", "git 地址不合格(只收 https / ssh / scp 形式,且不能带凭据)")
|
|
5244
|
+
problem: problem$1("source", "git 地址不合格(只收 https / ssh / scp 形式,且不能带凭据)")
|
|
4975
5245
|
};
|
|
4976
|
-
const ref = asText$
|
|
4977
|
-
const path = asText$
|
|
4978
|
-
const localPath = asText$
|
|
5246
|
+
const ref = asText$4(record["ref"]);
|
|
5247
|
+
const path = asText$4(record["path"]);
|
|
5248
|
+
const localPath = asText$4(record["localPath"]);
|
|
4979
5249
|
const policy = record["policy"];
|
|
4980
5250
|
if (policy !== void 0 && policy !== "artifacts-in-repo" && policy !== "catalog-only") return {
|
|
4981
5251
|
ok: false,
|
|
4982
|
-
problem: problem("source", "policy 只能是 artifacts-in-repo 或 catalog-only")
|
|
5252
|
+
problem: problem$1("source", "policy 只能是 artifacts-in-repo 或 catalog-only")
|
|
4983
5253
|
};
|
|
4984
5254
|
return {
|
|
4985
5255
|
ok: true,
|
|
@@ -4995,31 +5265,270 @@ function parseSourceConfig(raw) {
|
|
|
4995
5265
|
}
|
|
4996
5266
|
};
|
|
4997
5267
|
}
|
|
4998
|
-
function
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
|
|
5003
|
-
|
|
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;
|
|
5004
5275
|
}
|
|
5005
|
-
|
|
5006
|
-
if (
|
|
5007
|
-
|
|
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
|
+
};
|
|
5008
5297
|
}
|
|
5009
|
-
function
|
|
5010
|
-
|
|
5011
|
-
if (
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
|
|
5015
|
-
|
|
5016
|
-
|
|
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
|
+
};
|
|
5017
5333
|
}
|
|
5018
|
-
if (
|
|
5019
|
-
|
|
5020
|
-
|
|
5021
|
-
|
|
5022
|
-
|
|
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);
|
|
5023
5532
|
}
|
|
5024
5533
|
//#endregion
|
|
5025
5534
|
//#region ../shared/dist/host/market-index-claim.js
|
|
@@ -5064,8 +5573,11 @@ function claimOfEntry(entry, publisher) {
|
|
|
5064
5573
|
owner
|
|
5065
5574
|
};
|
|
5066
5575
|
}
|
|
5067
|
-
function
|
|
5068
|
-
return
|
|
5576
|
+
function marketArtifactRoot(name) {
|
|
5577
|
+
return `${MARKET_ARTIFACTS_DIR}/${name}`;
|
|
5578
|
+
}
|
|
5579
|
+
function marketArtifactPathFor(name, version) {
|
|
5580
|
+
return `${MARKET_ARTIFACTS_DIR}/${name}/${version}`;
|
|
5069
5581
|
}
|
|
5070
5582
|
function entryUnitOf(entry) {
|
|
5071
5583
|
const unit = entry.metadata?.unit?.trim();
|
|
@@ -5285,6 +5797,72 @@ function normalizeWhitespace(rows) {
|
|
|
5285
5797
|
return out.join("\n").replace(/\n+$/, "");
|
|
5286
5798
|
}
|
|
5287
5799
|
//#endregion
|
|
5800
|
+
//#region ../shared/dist/host/installed-sources.js
|
|
5801
|
+
var FILE_NAME = ".dsh-workbench-sources.json";
|
|
5802
|
+
var FILE_VERSION = 1;
|
|
5803
|
+
function asRecord$3(value) {
|
|
5804
|
+
return typeof value === "object" && value !== null ? value : void 0;
|
|
5805
|
+
}
|
|
5806
|
+
function asText$3(value) {
|
|
5807
|
+
return typeof value === "string" ? value.trim() : "";
|
|
5808
|
+
}
|
|
5809
|
+
function installedSourcesFile(profileDir) {
|
|
5810
|
+
return path.join(profileDir, FILE_NAME);
|
|
5811
|
+
}
|
|
5812
|
+
function readInstalledSources(profileDir) {
|
|
5813
|
+
if (profileDir === null) return {};
|
|
5814
|
+
let parsed;
|
|
5815
|
+
try {
|
|
5816
|
+
parsed = JSON.parse(fs.readFileSync(installedSourcesFile(profileDir), "utf8"));
|
|
5817
|
+
} catch {
|
|
5818
|
+
return {};
|
|
5819
|
+
}
|
|
5820
|
+
const packages = asRecord$3(asRecord$3(parsed)?.packages);
|
|
5821
|
+
if (packages === void 0) return {};
|
|
5822
|
+
const found = {};
|
|
5823
|
+
for (const [pkg, value] of Object.entries(packages)) {
|
|
5824
|
+
const record = asRecord$3(value);
|
|
5825
|
+
if (record === void 0) continue;
|
|
5826
|
+
const version = asText$3(record.version);
|
|
5827
|
+
const registry = asText$3(record.registry);
|
|
5828
|
+
if (pkg === "" || version === "" || registry === "") continue;
|
|
5829
|
+
found[pkg] = {
|
|
5830
|
+
version,
|
|
5831
|
+
registry
|
|
5832
|
+
};
|
|
5833
|
+
}
|
|
5834
|
+
return found;
|
|
5835
|
+
}
|
|
5836
|
+
function packageSourceOf(profileDir, pkg) {
|
|
5837
|
+
return readInstalledSources(profileDir)[pkg];
|
|
5838
|
+
}
|
|
5839
|
+
function writeInstalledSources(profileDir, sources) {
|
|
5840
|
+
const file = installedSourcesFile(profileDir);
|
|
5841
|
+
const temp = `${file}.tmp`;
|
|
5842
|
+
const text = `${JSON.stringify({
|
|
5843
|
+
version: FILE_VERSION,
|
|
5844
|
+
packages: sources
|
|
5845
|
+
}, null, 2)}\n`;
|
|
5846
|
+
fs.writeFileSync(temp, text, { mode: 384 });
|
|
5847
|
+
fs.renameSync(temp, file);
|
|
5848
|
+
}
|
|
5849
|
+
function rememberPackageSource(profileDir, pkg, source) {
|
|
5850
|
+
const trimmed = pkg.trim();
|
|
5851
|
+
if (trimmed === "" || source.version.trim() === "" || source.registry.trim() === "") return;
|
|
5852
|
+
const all = { ...readInstalledSources(profileDir) };
|
|
5853
|
+
all[trimmed] = {
|
|
5854
|
+
version: source.version.trim(),
|
|
5855
|
+
registry: source.registry.trim()
|
|
5856
|
+
};
|
|
5857
|
+
writeInstalledSources(profileDir, all);
|
|
5858
|
+
}
|
|
5859
|
+
function forgetPackageSource(profileDir, pkg) {
|
|
5860
|
+
const all = { ...readInstalledSources(profileDir) };
|
|
5861
|
+
if (all[pkg] === void 0) return;
|
|
5862
|
+
delete all[pkg];
|
|
5863
|
+
writeInstalledSources(profileDir, all);
|
|
5864
|
+
}
|
|
5865
|
+
//#endregion
|
|
5288
5866
|
//#region ../shared/dist/contract/protocol/dsh-plugins.js
|
|
5289
5867
|
var DSH_PLUGINS_ROUTE = "dsh-plugins";
|
|
5290
5868
|
//#endregion
|
|
@@ -5483,8 +6061,10 @@ function moduleVersionsHandler(ctx, shell, running, selfUpdate) {
|
|
|
5483
6061
|
const force = param(search, "force") === "1";
|
|
5484
6062
|
const fallback = (await manager.registries())?.resolved ?? void 0;
|
|
5485
6063
|
const modules = await Promise.all(found.map(async (module) => {
|
|
5486
|
-
const
|
|
5487
|
-
const
|
|
6064
|
+
const local = module.local === true;
|
|
6065
|
+
const fromRegistry = module.fromRegistry !== false;
|
|
6066
|
+
const registry = fromRegistry ? module.registry ?? fallback : void 0;
|
|
6067
|
+
const latest = local || !fromRegistry || shell === void 0 || registry === void 0 ? void 0 : await supply.latestVersion({
|
|
5488
6068
|
shell,
|
|
5489
6069
|
name: module.pkg,
|
|
5490
6070
|
registry,
|
|
@@ -5516,7 +6096,10 @@ function moduleVersionsHandler(ctx, shell, running, selfUpdate) {
|
|
|
5516
6096
|
...title === void 0 ? {} : { title },
|
|
5517
6097
|
...latest === void 0 ? {} : { latest },
|
|
5518
6098
|
...latestBlocked === void 0 ? {} : { latestBlocked },
|
|
5519
|
-
...
|
|
6099
|
+
...local ? {
|
|
6100
|
+
local: true,
|
|
6101
|
+
latestUnavailable: "local"
|
|
6102
|
+
} : latest !== void 0 ? {} : { latestUnavailable: !fromRegistry ? "not-registry" : registry === void 0 ? "no-registry" : "probe-failed" },
|
|
5520
6103
|
...refused === void 0 ? {} : { refused }
|
|
5521
6104
|
};
|
|
5522
6105
|
}));
|
|
@@ -6139,7 +6722,7 @@ function publishClaimDenyOf(claim, name) {
|
|
|
6139
6722
|
if (claim === void 0) return void 0;
|
|
6140
6723
|
if (claim.state === "unit-taken") return `市场里 ${claim.entry} 已经用了这个模块 id —— 两个模块不能共用一个,装在一起会撞。给这个模块换一个 unit(dsh.workbench.unit)再发。`;
|
|
6141
6724
|
if (claim.state === "path-taken") return `市场仓库里的产物目录已经被 ${claim.entry} 占了 —— 换一个包名再发。`;
|
|
6142
|
-
if (claim.state === "conflict") return `市场里 ${name} 已经有主了(${claim.owner})—— 同一个名字不能换人发。换一个带团队 scope 的包名;你就是原来的作者(只是换了机器或署名),用 author 参数带上原来的署名;确实是接手,让索引维护者改 market
|
|
6725
|
+
if (claim.state === "conflict") return `市场里 ${name} 已经有主了(${claim.owner})—— 同一个名字不能换人发。换一个带团队 scope 的包名;你就是原来的作者(只是换了机器或署名),用 author 参数带上原来的署名;确实是接手,让索引维护者改 market/<包名>/ 条目文件里的 owner。`;
|
|
6143
6726
|
}
|
|
6144
6727
|
//#endregion
|
|
6145
6728
|
//#region src/modules/panels/model/approval.ts
|
|
@@ -9817,10 +10400,16 @@ var EMPTY_INDEX = {
|
|
|
9817
10400
|
plugins: []
|
|
9818
10401
|
};
|
|
9819
10402
|
function publishedIndexOf(source) {
|
|
9820
|
-
const
|
|
9821
|
-
if (!existsSync(file)) return void 0;
|
|
10403
|
+
const root = join(workbenchRoot(), "market-index", marketSourceKey(source), "worktree");
|
|
9822
10404
|
try {
|
|
9823
|
-
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"));
|
|
9824
10413
|
return parsed.ok ? parsed.index : void 0;
|
|
9825
10414
|
} catch {
|
|
9826
10415
|
return;
|
|
@@ -9861,8 +10450,8 @@ function marketOf(dir, name, version) {
|
|
|
9861
10450
|
market: {
|
|
9862
10451
|
url: source.url,
|
|
9863
10452
|
...source.ref === void 0 ? {} : { ref: source.ref },
|
|
9864
|
-
indexPath: source.path ?? "market
|
|
9865
|
-
artifactPath:
|
|
10453
|
+
indexPath: source.path ?? "market",
|
|
10454
|
+
artifactPath: marketArtifactPathFor(name, nextVersion ?? version),
|
|
9866
10455
|
files: ARTIFACTS.filter((one) => existsSync(join(dir, one))),
|
|
9867
10456
|
...nextVersion === void 0 ? {} : { nextVersion }
|
|
9868
10457
|
},
|
|
@@ -9959,7 +10548,7 @@ function publishPreviewOf(root, unit, options = {}) {
|
|
|
9959
10548
|
name,
|
|
9960
10549
|
publisher: author,
|
|
9961
10550
|
unit,
|
|
9962
|
-
artifactPath:
|
|
10551
|
+
artifactPath: marketArtifactRoot(name)
|
|
9963
10552
|
});
|
|
9964
10553
|
return {
|
|
9965
10554
|
name,
|
|
@@ -11708,6 +12297,22 @@ function registerRecordTools(ctx, store) {
|
|
|
11708
12297
|
];
|
|
11709
12298
|
}
|
|
11710
12299
|
//#endregion
|
|
12300
|
+
//#region src/modules/panels/model/market-install-error.ts
|
|
12301
|
+
var INSTALL_PROBLEM = {
|
|
12302
|
+
permission: "没有写入权限",
|
|
12303
|
+
"disk-full": "磁盘满了",
|
|
12304
|
+
sandbox: "被系统拦住了",
|
|
12305
|
+
unknown: "命令失败"
|
|
12306
|
+
};
|
|
12307
|
+
function installError(failure) {
|
|
12308
|
+
const tail = tailLines(failure.output, 6);
|
|
12309
|
+
const stamp = failure.exitCode === null ? "" : `(退出码 ${failure.exitCode})`;
|
|
12310
|
+
return new WorkbenchDataError("install-failed", `${INSTALL_PROBLEM[failure.kind]}${stamp}${tail === "" ? "" : `:${tail}`}`, {
|
|
12311
|
+
detail: failure.output,
|
|
12312
|
+
failure
|
|
12313
|
+
});
|
|
12314
|
+
}
|
|
12315
|
+
//#endregion
|
|
11711
12316
|
//#region src/modules/panels/model/market-dependency.ts
|
|
11712
12317
|
var DEPENDENCY_KINDS = ["extension", "lib"];
|
|
11713
12318
|
function dependencyId(kind, name) {
|
|
@@ -11929,7 +12534,8 @@ async function settle(deps, spec, options, change) {
|
|
|
11929
12534
|
kind: "unknown",
|
|
11930
12535
|
pkg
|
|
11931
12536
|
};
|
|
11932
|
-
|
|
12537
|
+
const registry = options?.registry ?? (module.kind === "module" ? module.registry : void 0);
|
|
12538
|
+
await deps.rememberSource(deps.manager, pkg, registry, module.kind === "module" ? module.version : void 0);
|
|
11933
12539
|
return {
|
|
11934
12540
|
change,
|
|
11935
12541
|
module,
|
|
@@ -12043,7 +12649,82 @@ function dependencyStillBehind(target, capabilities) {
|
|
|
12043
12649
|
}
|
|
12044
12650
|
}
|
|
12045
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
|
|
12046
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
|
+
}
|
|
12047
12728
|
async function restoreRemovedBundle(pkg, before, registry, deps) {
|
|
12048
12729
|
if (before === void 0) return void 0;
|
|
12049
12730
|
try {
|
|
@@ -12098,7 +12779,7 @@ async function updateModuleBundle(unit, deps) {
|
|
|
12098
12779
|
kind: "unknown",
|
|
12099
12780
|
pkg
|
|
12100
12781
|
}).kind === "module" ? void 0 : await restoreRemovedBundle(pkg, before, registry, deps);
|
|
12101
|
-
|
|
12782
|
+
let after = deps.installed?.(pkg) ?? {
|
|
12102
12783
|
kind: "unknown",
|
|
12103
12784
|
pkg
|
|
12104
12785
|
};
|
|
@@ -12108,15 +12789,150 @@ async function updateModuleBundle(unit, deps) {
|
|
|
12108
12789
|
after,
|
|
12109
12790
|
...restored === void 0 ? {} : { restored }
|
|
12110
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
|
+
}
|
|
12111
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;
|
|
12112
12823
|
return {
|
|
12113
12824
|
...outcome,
|
|
12114
|
-
change
|
|
12825
|
+
change,
|
|
12115
12826
|
module: after,
|
|
12116
|
-
...
|
|
12827
|
+
...restoredVersion === void 0 ? {} : { restored: restoredVersion },
|
|
12828
|
+
...remedy === void 0 ? {} : { remedy }
|
|
12117
12829
|
};
|
|
12118
12830
|
}
|
|
12119
12831
|
//#endregion
|
|
12832
|
+
//#region src/modules/panels/model/market-registry.ts
|
|
12833
|
+
async function rememberInstalledSource(deps, manager, pkg, registry, version) {
|
|
12834
|
+
const { registries, profileDir, logger } = deps;
|
|
12835
|
+
const wanted = registry?.trim() ?? "";
|
|
12836
|
+
if (wanted === "") return;
|
|
12837
|
+
if (profileDir !== void 0 && profileDir !== null && version !== void 0 && version.trim() !== "") try {
|
|
12838
|
+
rememberPackageSource(profileDir, pkg, {
|
|
12839
|
+
version,
|
|
12840
|
+
registry: wanted
|
|
12841
|
+
});
|
|
12842
|
+
} catch (error) {
|
|
12843
|
+
logger?.warn?.(`[dsh-workbench] 记 ${pkg} 这次的来源失败:${String(error)}`);
|
|
12844
|
+
}
|
|
12845
|
+
if (registries === void 0) return;
|
|
12846
|
+
const scope = scopeOf$2(pkg);
|
|
12847
|
+
if (scope === null) return;
|
|
12848
|
+
const known = await manager.registries().catch(() => null);
|
|
12849
|
+
if (known !== null && sameRegistry(known.resolved, wanted)) return;
|
|
12850
|
+
try {
|
|
12851
|
+
const added = registries.remember([{
|
|
12852
|
+
scope,
|
|
12853
|
+
registry: wanted
|
|
12854
|
+
}]);
|
|
12855
|
+
if (added.length > 0) logger?.info?.(`[dsh-workbench] 把 ${added.join("、")} 记进 profile 的 .npmrc(卸载只认 pnpm 的配置)`);
|
|
12856
|
+
} catch (error) {
|
|
12857
|
+
logger?.warn?.(`[dsh-workbench] 记这个 scope 的源失败:${String(error)}`);
|
|
12858
|
+
}
|
|
12859
|
+
}
|
|
12860
|
+
async function retryAfterRegistryFix(deps, manager, pkg, change) {
|
|
12861
|
+
const { registries, logger } = deps;
|
|
12862
|
+
if (registries === void 0 || !registryFailure(change)) return change;
|
|
12863
|
+
const known = await manager.registries().catch(() => null);
|
|
12864
|
+
const missing = registries.sources().filter((entry) => !sameRegistry(entry.registry, known?.resolved ?? null));
|
|
12865
|
+
if (missing.length === 0) return change;
|
|
12866
|
+
let added;
|
|
12867
|
+
try {
|
|
12868
|
+
added = registries.remember(missing);
|
|
12869
|
+
} catch (error) {
|
|
12870
|
+
logger?.warn?.(`[dsh-workbench] 卸 ${pkg} 撞上源,但补配置失败:${String(error)}`);
|
|
12871
|
+
return change;
|
|
12872
|
+
}
|
|
12873
|
+
if (added.length === 0) return change;
|
|
12874
|
+
logger?.warn?.(`[dsh-workbench] 卸 ${pkg} 撞上源:补 ${added.join("、")} 后重试一次`);
|
|
12875
|
+
return await manager.remove(pkg).catch(() => null) ?? change;
|
|
12876
|
+
}
|
|
12877
|
+
//#endregion
|
|
12878
|
+
//#region src/modules/panels/model/market-remove.ts
|
|
12879
|
+
var chain = Promise.resolve();
|
|
12880
|
+
function serialized(operation) {
|
|
12881
|
+
const run = chain.then(operation, operation);
|
|
12882
|
+
chain = run.then(() => void 0, () => void 0);
|
|
12883
|
+
return run;
|
|
12884
|
+
}
|
|
12885
|
+
function forgetSource(options) {
|
|
12886
|
+
const dir = options.profileDir;
|
|
12887
|
+
if (dir === void 0 || dir === null) return;
|
|
12888
|
+
try {
|
|
12889
|
+
forgetPackageSource(dir, options.pkg);
|
|
12890
|
+
} catch (error) {
|
|
12891
|
+
options.logger?.warn?.(`[dsh-workbench] 清 ${options.pkg} 的来源记录失败:${String(error)}`);
|
|
12892
|
+
}
|
|
12893
|
+
}
|
|
12894
|
+
function swapScopeFor(options) {
|
|
12895
|
+
const dir = options.profileDir;
|
|
12896
|
+
const notebook = options.registryFix.registries;
|
|
12897
|
+
if (dir === void 0 || dir === null || notebook?.swapScope === void 0) return void 0;
|
|
12898
|
+
const source = packageSourceOf(dir, options.pkg);
|
|
12899
|
+
const scope = scopeOf$2(options.pkg);
|
|
12900
|
+
if (source === void 0 || scope === null) return void 0;
|
|
12901
|
+
try {
|
|
12902
|
+
return notebook.swapScope(scope, source.registry);
|
|
12903
|
+
} catch (error) {
|
|
12904
|
+
options.logger?.warn?.(`[dsh-workbench] 临时把 ${scope} 指到 ${source.registry} 失败,卸载照走:${String(error)}`);
|
|
12905
|
+
return;
|
|
12906
|
+
}
|
|
12907
|
+
}
|
|
12908
|
+
async function removeOnce(options) {
|
|
12909
|
+
const { manager, pkg, registryFix, logger } = options;
|
|
12910
|
+
const first = await manager.remove(pkg);
|
|
12911
|
+
if (first === null) throw new Error("插件管理器回的卸载结果看不懂。");
|
|
12912
|
+
const settled = await retryAfterRegistryFix(registryFix, manager, pkg, first);
|
|
12913
|
+
if (settled.application !== "failed" && settled.application !== "cancelled") {
|
|
12914
|
+
forgetSource(options);
|
|
12915
|
+
return settled;
|
|
12916
|
+
}
|
|
12917
|
+
const put = await manager.setEnabled?.(pkg, true);
|
|
12918
|
+
logger?.warn?.(`[dsh-workbench] ${pkg} 没卸成(${settled.errorCode ?? settled.application})—— 已把它那一行放回名单,别让一次失败留在"装着但关着"`);
|
|
12919
|
+
return put ?? settled;
|
|
12920
|
+
}
|
|
12921
|
+
function removeDshBundle(options) {
|
|
12922
|
+
return serialized(async () => {
|
|
12923
|
+
const restore = swapScopeFor(options);
|
|
12924
|
+
try {
|
|
12925
|
+
return await removeOnce(options);
|
|
12926
|
+
} finally {
|
|
12927
|
+
try {
|
|
12928
|
+
restore?.();
|
|
12929
|
+
} catch (error) {
|
|
12930
|
+
options.logger?.warn?.(`[dsh-workbench] 还原 .npmrc 失败:${String(error)}`);
|
|
12931
|
+
}
|
|
12932
|
+
}
|
|
12933
|
+
});
|
|
12934
|
+
}
|
|
12935
|
+
//#endregion
|
|
12120
12936
|
//#region src/modules/panels/model/market-dependency-update.ts
|
|
12121
12937
|
async function updateDependencyRow(target, deps) {
|
|
12122
12938
|
if (target.conflict !== void 0) throw new Error(`这一行有两份来源,先统一声明再更新:${target.conflict}`);
|
|
@@ -12326,14 +13142,14 @@ function createPluginActions(options) {
|
|
|
12326
13142
|
unreadable: []
|
|
12327
13143
|
};
|
|
12328
13144
|
}
|
|
12329
|
-
function decide(entry, held) {
|
|
13145
|
+
function decide(entry, held, claimedBy) {
|
|
12330
13146
|
const plugin = pluginNameOf(entry);
|
|
12331
13147
|
if (plugin === void 0) return {
|
|
12332
13148
|
act: "keep",
|
|
12333
13149
|
kept: { kind: "unnamed" }
|
|
12334
13150
|
};
|
|
12335
13151
|
const refs = referencesOf();
|
|
12336
|
-
const by = options.claimedBy?.(plugin) ?? [];
|
|
13152
|
+
const by = claimedBy?.(plugin) ?? options.claimedBy?.(plugin) ?? [];
|
|
12337
13153
|
const retention = pluginRetentionOf({
|
|
12338
13154
|
held,
|
|
12339
13155
|
claimedBy: by,
|
|
@@ -12400,12 +13216,12 @@ function createPluginActions(options) {
|
|
|
12400
13216
|
name: decision.plugin
|
|
12401
13217
|
};
|
|
12402
13218
|
},
|
|
12403
|
-
async sweep() {
|
|
13219
|
+
async sweep(claimedBy) {
|
|
12404
13220
|
if (ledger === void 0) return [];
|
|
12405
13221
|
const removed = [];
|
|
12406
13222
|
for (const entry of ledger.all()) {
|
|
12407
13223
|
if (entry.releasedAt === void 0) continue;
|
|
12408
|
-
const decision = decide(entry, false);
|
|
13224
|
+
const decision = decide(entry, false, claimedBy);
|
|
12409
13225
|
if (decision.act === "keep") continue;
|
|
12410
13226
|
try {
|
|
12411
13227
|
await removeFiles(decision.plugin);
|
|
@@ -12434,17 +13250,18 @@ function createEnvDropper(options) {
|
|
|
12434
13250
|
unreadable: []
|
|
12435
13251
|
};
|
|
12436
13252
|
}
|
|
12437
|
-
function usersOf(plugin, except) {
|
|
12438
|
-
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);
|
|
12439
13255
|
}
|
|
12440
|
-
function libUsersOf(name, unit) {
|
|
12441
|
-
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);
|
|
12442
13258
|
}
|
|
12443
13259
|
return {
|
|
12444
13260
|
usersOf,
|
|
12445
|
-
async drop(unit, needs, before) {
|
|
13261
|
+
async drop(unit, needs, before, roster) {
|
|
12446
13262
|
const kept = /* @__PURE__ */ new Set();
|
|
12447
|
-
const
|
|
13263
|
+
const known = roster ?? catalog();
|
|
13264
|
+
const index = buildSupplyIndex(known.filter((item) => options.isReleased?.(item.id) !== true).map((item) => ({
|
|
12448
13265
|
id: item.id,
|
|
12449
13266
|
needs: needsOf(item)
|
|
12450
13267
|
})), { env: process.env });
|
|
@@ -12453,7 +13270,7 @@ function createEnvDropper(options) {
|
|
|
12453
13270
|
const onMachine = new Set(before.needs.filter((state) => state.kind === "extension" && state.status !== "missing").map((state) => state.name));
|
|
12454
13271
|
const unknown = !before.needs.some((state) => state.kind === "extension");
|
|
12455
13272
|
for (const need of extensions) {
|
|
12456
|
-
const users = usersOf(need.name, unit);
|
|
13273
|
+
const users = usersOf(need.name, unit, known);
|
|
12457
13274
|
const retention = pluginRetentionOf({
|
|
12458
13275
|
held: ledger?.heldAsPlugin(need.name) !== void 0,
|
|
12459
13276
|
claimedBy: users,
|
|
@@ -12473,7 +13290,7 @@ function createEnvDropper(options) {
|
|
|
12473
13290
|
await run(`${quoteShellArg(command)} plugin uninstall ${quoteShellArg(need.name)}`);
|
|
12474
13291
|
}
|
|
12475
13292
|
for (const need of libs) {
|
|
12476
|
-
const keepers = libUsersOf(need.name, unit);
|
|
13293
|
+
const keepers = libUsersOf(need.name, unit, known);
|
|
12477
13294
|
if (keepers.length > 0) {
|
|
12478
13295
|
for (const id of keepers) kept.add(id);
|
|
12479
13296
|
continue;
|
|
@@ -12489,42 +13306,6 @@ function createEnvDropper(options) {
|
|
|
12489
13306
|
};
|
|
12490
13307
|
}
|
|
12491
13308
|
//#endregion
|
|
12492
|
-
//#region src/modules/panels/model/market-registry.ts
|
|
12493
|
-
async function rememberInstalledSource(deps, manager, pkg, registry) {
|
|
12494
|
-
const { registries, logger } = deps;
|
|
12495
|
-
if (registries === void 0 || registry === void 0) return;
|
|
12496
|
-
const scope = scopeOf$2(pkg);
|
|
12497
|
-
if (scope === null) return;
|
|
12498
|
-
const known = await manager.registries().catch(() => null);
|
|
12499
|
-
if (known !== null && sameRegistry(known.resolved, registry)) return;
|
|
12500
|
-
try {
|
|
12501
|
-
const added = registries.remember([{
|
|
12502
|
-
scope,
|
|
12503
|
-
registry
|
|
12504
|
-
}]);
|
|
12505
|
-
if (added.length > 0) logger?.info?.(`[dsh-workbench] 把 ${added.join("、")} 记进 profile 的 .npmrc(卸载只认 pnpm 的配置)`);
|
|
12506
|
-
} catch (error) {
|
|
12507
|
-
logger?.warn?.(`[dsh-workbench] 记这个 scope 的源失败:${String(error)}`);
|
|
12508
|
-
}
|
|
12509
|
-
}
|
|
12510
|
-
async function retryAfterRegistryFix(deps, manager, pkg, change) {
|
|
12511
|
-
const { registries, logger } = deps;
|
|
12512
|
-
if (registries === void 0 || !registryFailure(change)) return change;
|
|
12513
|
-
const known = await manager.registries().catch(() => null);
|
|
12514
|
-
const missing = registries.sources().filter((entry) => !sameRegistry(entry.registry, known?.resolved ?? null));
|
|
12515
|
-
if (missing.length === 0) return change;
|
|
12516
|
-
let added;
|
|
12517
|
-
try {
|
|
12518
|
-
added = registries.remember(missing);
|
|
12519
|
-
} catch (error) {
|
|
12520
|
-
logger?.warn?.(`[dsh-workbench] 卸 ${pkg} 撞上源,但补配置失败:${String(error)}`);
|
|
12521
|
-
return change;
|
|
12522
|
-
}
|
|
12523
|
-
if (added.length === 0) return change;
|
|
12524
|
-
logger?.warn?.(`[dsh-workbench] 卸 ${pkg} 撞上源:补 ${added.join("、")} 后重试一次`);
|
|
12525
|
-
return await manager.remove(pkg).catch(() => null) ?? change;
|
|
12526
|
-
}
|
|
12527
|
-
//#endregion
|
|
12528
13309
|
//#region src/modules/panels/model/market-restart.ts
|
|
12529
13310
|
function restartLogLine(change) {
|
|
12530
13311
|
if (change === null || change === void 0 || change.application !== "restart-required") return;
|
|
@@ -12534,7 +13315,7 @@ function restartLogLine(change) {
|
|
|
12534
13315
|
//#endregion
|
|
12535
13316
|
//#region src/modules/panels/model/market.ts
|
|
12536
13317
|
function createMarketService(options) {
|
|
12537
|
-
const { manifests, manifestsNow, probe, shell, logger, supply: supplyOption, onInstalled, onUninstalled, isReleased, bundles, installed, modules, workbench, registries, ledger, references, dshHome, profileName } = options;
|
|
13318
|
+
const { manifests, manifestsNow, probe, shell, logger, supply: supplyOption, onInstalled, onUninstalled, isReleased, bundles, installed, modules, workbench, registries, profileDir, ledger, references, dshHome, profileName } = options;
|
|
12538
13319
|
const supply = supplyOption ?? requireSupply();
|
|
12539
13320
|
const command = options.command ?? "opencli";
|
|
12540
13321
|
const catalog = () => manifestsNow?.() ?? manifests;
|
|
@@ -12573,11 +13354,18 @@ function createMarketService(options) {
|
|
|
12573
13354
|
}
|
|
12574
13355
|
async function run(line, optional = false) {
|
|
12575
13356
|
if (shell === void 0) throw new Error("没有执行缝(shell 服务不可用),装 / 卸做不了。");
|
|
13357
|
+
const blocked = installWriteProblem({
|
|
13358
|
+
command: line,
|
|
13359
|
+
fallback: isolatedRoot()
|
|
13360
|
+
});
|
|
13361
|
+
if (blocked !== void 0) throw installError(blocked);
|
|
12576
13362
|
const result = await runShell(shell, line, { timeoutMs: INSTALL_TIMEOUT_MS });
|
|
12577
|
-
if (result.exitCode !== 0 && !optional) {
|
|
12578
|
-
|
|
12579
|
-
|
|
12580
|
-
|
|
13363
|
+
if (result.exitCode !== 0 && !optional) throw installError(diagnoseInstallFailure({
|
|
13364
|
+
command: line,
|
|
13365
|
+
exitCode: result.exitCode,
|
|
13366
|
+
output: result.stderr || result.stdout,
|
|
13367
|
+
fallback: isolatedRoot()
|
|
13368
|
+
}));
|
|
12581
13369
|
}
|
|
12582
13370
|
async function runAll(commands) {
|
|
12583
13371
|
for (const step of commands) await run(step.line, step.optional === true);
|
|
@@ -12662,26 +13450,24 @@ function createMarketService(options) {
|
|
|
12662
13450
|
}
|
|
12663
13451
|
const registryFix = {
|
|
12664
13452
|
...registries === void 0 ? {} : { registries },
|
|
13453
|
+
...profileDir === void 0 || profileDir === null ? {} : { profileDir },
|
|
12665
13454
|
...logger === void 0 ? {} : { logger }
|
|
12666
13455
|
};
|
|
12667
|
-
async function removeInstalledBundle(manager, pkg) {
|
|
12668
|
-
const first = await manager.remove(pkg);
|
|
12669
|
-
if (first === null) throw new Error("插件管理器回的卸载结果看不懂。");
|
|
12670
|
-
const settled = await retryAfterRegistryFix(registryFix, manager, pkg, first);
|
|
12671
|
-
if (settled.application !== "failed" && settled.application !== "cancelled") return settled;
|
|
12672
|
-
const put = await manager.setEnabled?.(pkg, true);
|
|
12673
|
-
logger?.warn?.(`[dsh-workbench] ${pkg} 没卸成(${settled.errorCode ?? settled.application})—— 已把它那一行放回名单,别让一次失败留在"装着但关着"`);
|
|
12674
|
-
return put ?? settled;
|
|
12675
|
-
}
|
|
12676
13456
|
function routeDeps(manager) {
|
|
12677
13457
|
return {
|
|
12678
13458
|
manager,
|
|
12679
13459
|
command,
|
|
12680
13460
|
supply,
|
|
12681
13461
|
runAll,
|
|
12682
|
-
rememberSource: (manager, pkg, registry) => rememberInstalledSource(registryFix, manager, pkg, registry),
|
|
13462
|
+
rememberSource: (manager, pkg, registry, version) => rememberInstalledSource(registryFix, manager, pkg, registry, version),
|
|
12683
13463
|
supplyAfter,
|
|
12684
|
-
remove: (pkg) =>
|
|
13464
|
+
remove: (pkg) => removeDshBundle({
|
|
13465
|
+
manager,
|
|
13466
|
+
pkg,
|
|
13467
|
+
profileDir,
|
|
13468
|
+
registryFix,
|
|
13469
|
+
...logger === void 0 ? {} : { logger }
|
|
13470
|
+
}),
|
|
12685
13471
|
...shell === void 0 ? {} : { shell },
|
|
12686
13472
|
...installed === void 0 ? {} : { installed },
|
|
12687
13473
|
...ledger === void 0 ? {} : { ledger },
|
|
@@ -12716,7 +13502,7 @@ function createMarketService(options) {
|
|
|
12716
13502
|
return snapshot();
|
|
12717
13503
|
},
|
|
12718
13504
|
async install(unit) {
|
|
12719
|
-
const absent = (await capabilityOf(unit, true)).needs.filter((state) => state.status === "missing" && (state.kind === "extension" || state.kind === "lib"));
|
|
13505
|
+
const absent = (await capabilityOf(unit, true)).needs.filter((state) => state.status === "missing" && (state.kind === "extension" || state.kind === "lib" || state.kind === "bin"));
|
|
12720
13506
|
if (absent.length === 0) {
|
|
12721
13507
|
onInstalled?.(unit);
|
|
12722
13508
|
return entryOf(unit, true);
|
|
@@ -12728,6 +13514,8 @@ function createMarketService(options) {
|
|
|
12728
13514
|
}
|
|
12729
13515
|
if (state.command === void 0) throw new Error(`没有可执行的安装命令(${state.detail})`);
|
|
12730
13516
|
await run(state.command);
|
|
13517
|
+
ensureLibsOpencliPeer();
|
|
13518
|
+
ensureLibsBinOnPath();
|
|
12731
13519
|
}
|
|
12732
13520
|
await probeSharing({
|
|
12733
13521
|
action: "install",
|
|
@@ -12811,15 +13599,22 @@ function createMarketService(options) {
|
|
|
12811
13599
|
pkg
|
|
12812
13600
|
};
|
|
12813
13601
|
const unit = module.kind === "module" ? module.unit : void 0;
|
|
12814
|
-
const
|
|
13602
|
+
const roster = catalog();
|
|
13603
|
+
const manifest = unit === void 0 ? void 0 : roster.find((item) => item.id === unit);
|
|
12815
13604
|
const before = unit === void 0 ? void 0 : await capabilityOf(unit, true);
|
|
12816
|
-
const change = await
|
|
13605
|
+
const change = await removeDshBundle({
|
|
13606
|
+
manager,
|
|
13607
|
+
pkg,
|
|
13608
|
+
profileDir,
|
|
13609
|
+
registryFix,
|
|
13610
|
+
...logger === void 0 ? {} : { logger }
|
|
13611
|
+
});
|
|
12817
13612
|
if (change.application !== "applied" || unit === void 0) return {
|
|
12818
13613
|
change,
|
|
12819
13614
|
keptFor: []
|
|
12820
13615
|
};
|
|
12821
|
-
const keptFor = manifest === void 0 || before === void 0 ? [] : await envDropper.drop(unit, needsOf(manifest), before);
|
|
12822
|
-
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));
|
|
12823
13618
|
onUninstalled?.(unit);
|
|
12824
13619
|
return {
|
|
12825
13620
|
change,
|
|
@@ -12831,15 +13626,22 @@ function createMarketService(options) {
|
|
|
12831
13626
|
},
|
|
12832
13627
|
async updateBundle(unit) {
|
|
12833
13628
|
const manager = requireBundles();
|
|
13629
|
+
const home = dshHome?.();
|
|
13630
|
+
const profile = profileName?.() ?? (profileDir === void 0 || profileDir === null ? void 0 : basename(profileDir));
|
|
12834
13631
|
return updateModuleBundle(unit, {
|
|
12835
13632
|
supply,
|
|
12836
13633
|
modules,
|
|
12837
13634
|
installed,
|
|
12838
13635
|
shell,
|
|
12839
13636
|
manager,
|
|
13637
|
+
...profile === void 0 ? {} : {
|
|
13638
|
+
profile,
|
|
13639
|
+
profileName: profile
|
|
13640
|
+
},
|
|
13641
|
+
...home === void 0 ? {} : { dshHome: home },
|
|
12840
13642
|
routeDeps: routeDeps(manager),
|
|
12841
13643
|
sweep: async () => {
|
|
12842
|
-
await plugins.sweep();
|
|
13644
|
+
await plugins.sweep((plugin) => envDropper.usersOf(plugin, void 0, catalog()));
|
|
12843
13645
|
},
|
|
12844
13646
|
refreshSupply: async (id) => {
|
|
12845
13647
|
await service.update(id);
|
|
@@ -13571,7 +14373,7 @@ function createMarketIndexReader(options) {
|
|
|
13571
14373
|
}
|
|
13572
14374
|
};
|
|
13573
14375
|
const repo = repoOf(options.root, source);
|
|
13574
|
-
const path = (source.path ?? "market
|
|
14376
|
+
const path = ((source.path ?? "market").replace(/^\/+/, "") || "market").replace(/\/+$/, "");
|
|
13575
14377
|
const ref = (source.ref ?? "").trim();
|
|
13576
14378
|
let revision = "FETCH_HEAD";
|
|
13577
14379
|
mkdirSync(directoryOf(options.root, source), { recursive: true });
|
|
@@ -13596,17 +14398,6 @@ function createMarketIndexReader(options) {
|
|
|
13596
14398
|
detail: "仓库还没有任何提交"
|
|
13597
14399
|
}
|
|
13598
14400
|
}, repo);
|
|
13599
|
-
const shown = await run(`git -C ${quoteShellArg(repo)} show ${revision}:${quoteShellArg(path)}`);
|
|
13600
|
-
if (!shown.ok) {
|
|
13601
|
-
if (shown.problem.kind === "git-file") return withBranch({
|
|
13602
|
-
ok: false,
|
|
13603
|
-
problem: {
|
|
13604
|
-
kind: "git-missing-index",
|
|
13605
|
-
detail: `索引文件 ${path} 不在仓库里`
|
|
13606
|
-
}
|
|
13607
|
-
}, repo);
|
|
13608
|
-
return shown;
|
|
13609
|
-
}
|
|
13610
14401
|
const head = await run(`git -C ${quoteShellArg(repo)} rev-parse ${revision}`);
|
|
13611
14402
|
if (!head.ok) return head;
|
|
13612
14403
|
const commit = head.stdout.trim();
|
|
@@ -13617,26 +14408,105 @@ function createMarketIndexReader(options) {
|
|
|
13617
14408
|
detail: `读到的 commit 不是 40 位:${commit}`
|
|
13618
14409
|
}
|
|
13619
14410
|
};
|
|
13620
|
-
const
|
|
13621
|
-
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({
|
|
13622
14504
|
ok: false,
|
|
13623
14505
|
problem: {
|
|
13624
|
-
kind: "
|
|
13625
|
-
detail:
|
|
14506
|
+
kind: "git-missing-index",
|
|
14507
|
+
detail: `索引 ${path} 不在仓库里`
|
|
13626
14508
|
}
|
|
13627
|
-
};
|
|
13628
|
-
const fetchedAt = now();
|
|
13629
|
-
await writeFileAtomic(indexFileOf(options.root, source), shown.stdout);
|
|
13630
|
-
await writeMeta(metaFileOf(options.root, source), {
|
|
13631
|
-
fetchedAt,
|
|
13632
|
-
commit
|
|
13633
|
-
});
|
|
13634
|
-
return {
|
|
13635
|
-
ok: true,
|
|
13636
|
-
index: parsed.index,
|
|
13637
|
-
fetchedAt,
|
|
13638
|
-
commit
|
|
13639
|
-
};
|
|
14509
|
+
}, repo);
|
|
13640
14510
|
}
|
|
13641
14511
|
async function read(source, callOptions = {}) {
|
|
13642
14512
|
const key = marketSourceKey(source);
|
|
@@ -13984,16 +14854,31 @@ function createMarketSourceInitializer(options) {
|
|
|
13984
14854
|
], workdir);
|
|
13985
14855
|
if (created.exitCode !== 0) throw new Error(`建分支 ${branch} 没过:${tailLines(created.stderr, 4)}`);
|
|
13986
14856
|
}
|
|
13987
|
-
const indexPath = (source.path ?? "market
|
|
13988
|
-
const
|
|
14857
|
+
const indexPath = ((source.path ?? "market").replace(/^\/+/, "") || "market").replace(/\/+$/, "");
|
|
14858
|
+
const indexDir = join(workdir, indexPath);
|
|
14859
|
+
const indexMarker = join(indexDir, "README.md");
|
|
13989
14860
|
const stampOnly = request.stampOnly === true;
|
|
13990
|
-
if (stampOnly && !existsSync(
|
|
14861
|
+
if (stampOnly && !existsSync(indexDir)) throw new Error(`仓库里还没有 ${indexPath}/ —— 先选中这个源,按发现页的提示把它初始化成索引`);
|
|
13991
14862
|
const written = [];
|
|
13992
14863
|
if (!stampOnly) {
|
|
13993
|
-
|
|
13994
|
-
|
|
13995
|
-
|
|
13996
|
-
|
|
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`);
|
|
13997
14882
|
}
|
|
13998
14883
|
const readmeFile = join(workdir, "README.md");
|
|
13999
14884
|
if (!existsSync(readmeFile)) {
|
|
@@ -14262,7 +15147,7 @@ function failureText(change) {
|
|
|
14262
15147
|
const code = change.errorCode ?? change.application;
|
|
14263
15148
|
if (code === "already-installed") return "它已经在这份 profile 里,而这次没能把它卸下来(这一版 dsh 不肯就地覆盖装,得先卸再装)";
|
|
14264
15149
|
const surface = hostSurface();
|
|
14265
|
-
return REMOVE_BLOCKED.has(code) ?
|
|
15150
|
+
return REMOVE_BLOCKED.has(code) ? `旧版卸不掉(dsh 不肯就地覆盖正在用的模块),自动从外部换也没成 —— 让用户从终端跑 dsh plugin --profile <名字> add <包>@<版本>,跑完${surface.restartHint}` : code;
|
|
14266
15151
|
}
|
|
14267
15152
|
function describeInstall(spec, module, supply, change) {
|
|
14268
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})`;
|
|
@@ -14396,6 +15281,14 @@ function registerMarketTools(ctx, market, sources) {
|
|
|
14396
15281
|
async execute(raw) {
|
|
14397
15282
|
const id = await idOrList(raw, MODULES_UNINSTALL_TOOL);
|
|
14398
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
|
+
}
|
|
14399
15292
|
const kept = outcome.keptFor.length === 0 ? "" : `(还有模块在面板上要用其中一份安装,所以那份留着:${outcome.keptFor.join("、")})`;
|
|
14400
15293
|
return {
|
|
14401
15294
|
module: outcome.entry,
|
|
@@ -14551,7 +15444,7 @@ function registerMarketTools(ctx, market, sources) {
|
|
|
14551
15444
|
branch: result.branch,
|
|
14552
15445
|
commit: result.commit,
|
|
14553
15446
|
changed: true,
|
|
14554
|
-
text: `已把「${source.label}」设为可发布(${source.url},分支 ${result.branch})—— 仓库本身没动。之后发布可以落到它的 ${source.path ?? "market
|
|
15447
|
+
text: `已把「${source.label}」设为可发布(${source.url},分支 ${result.branch})—— 仓库本身没动。之后发布可以落到它的 ${source.path ?? "market"}/。`
|
|
14555
15448
|
};
|
|
14556
15449
|
}
|
|
14557
15450
|
};
|
|
@@ -15342,6 +16235,7 @@ var OWNED = [{
|
|
|
15342
16235
|
modules: () => supply.profileModules(profileDir),
|
|
15343
16236
|
workbench: (pkg) => supply.readProfilePackage(profileDir, pkg),
|
|
15344
16237
|
registries: supply.createRegistryNotebook(profileDir),
|
|
16238
|
+
profileDir,
|
|
15345
16239
|
ledger,
|
|
15346
16240
|
references: () => {
|
|
15347
16241
|
const gaps = supply.declarationGaps(profileDir);
|
|
@@ -16878,6 +17772,8 @@ async function apply(ctx, config) {
|
|
|
16878
17772
|
}
|
|
16879
17773
|
if (shell === void 0) ctx.logger?.warn?.("[dsh-workbench] 没有 shell 服务:需要执行缝的套件会降级");
|
|
16880
17774
|
else {
|
|
17775
|
+
ensureLibsOpencliPeer();
|
|
17776
|
+
if (ensureLibsBinOnPath() && ctx.logger?.info !== void 0) ctx.logger.info("[dsh-workbench] PATH 前插共用依赖的 bin 目录:装了的包,命令直接可用");
|
|
16881
17777
|
adapters.probe.registerPreflight({
|
|
16882
17778
|
routes,
|
|
16883
17779
|
ctx,
|