@dsh-workbench/core 1.1.3 → 1.1.5
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 +1370 -585
- package/dist/client/client.body.js +1370 -585
- package/dist/client/index.js +1225 -440
- package/dist/host/index.js +784 -114
- 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/discover-confirm.d.ts +3 -2
- package/dist/types/app/layouts/market/market-check.d.ts +12 -0
- package/dist/types/app/layouts/market/market-header.d.ts +1 -3
- package/dist/types/app/layouts/market/use-discover.d.ts +2 -1
- package/dist/types/app/layouts/market/use-git-transport.d.ts +2 -2
- package/dist/types/app/layouts/market/use-module-versions.d.ts +3 -0
- package/dist/types/app/layouts/market/use-settling-modules.d.ts +1 -0
- 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 +4 -1
- package/dist/types/app/layouts/rightbar/SharedSection.d.ts +5 -2
- package/dist/types/app/layouts/rightbar/failure-card-text.d.ts +14 -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-freshness.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 +11 -2
- package/dist/types/app/layouts/rightbar/market-rows.d.ts +14 -1
- package/dist/types/app/layouts/rightbar/market-section.d.ts +8 -4
- package/dist/types/app/layouts/rightbar/market-transient-state.d.ts +3 -8
- package/dist/types/app/layouts/rightbar/market-view-text.d.ts +2 -0
- package/dist/types/app/layouts/rightbar/use-market-default.d.ts +12 -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 +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-installed.d.ts +19 -0
- package/dist/types/modules/panels/model/market-installed.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 +2 -1
- package/dist/types/modules/panels/model/market.d.ts +1 -0
- package/dist/types/shared/api/host/git-probe.d.ts +3 -0
- package/dist/types/shared/api/host/market-sources-store.d.ts +2 -1
- package/dist/types/shared/api/host/profile-dependencies.d.ts +1 -0
- package/dist/types/shared/hooks/market-queries.d.ts +2 -1
- package/package.json +3 -3
- package/skills/workbench-module-diy/TOOLS.md +11 -2
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, resolve } 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 没写`);
|
|
@@ -388,6 +419,99 @@ function boardSituation(probe, workshop) {
|
|
|
388
419
|
if (probe.where !== "" && !insideWorkshop(workshop, probe.where)) return "off-workspace";
|
|
389
420
|
return "panel-closed";
|
|
390
421
|
}
|
|
422
|
+
//#endregion
|
|
423
|
+
//#region ../shared/dist/lib/git-address.js
|
|
424
|
+
var SCP = /^(?:([A-Za-z0-9._-]+)@)?([A-Za-z0-9.-]+):(?!\/)(\S+)$/;
|
|
425
|
+
function bare(url) {
|
|
426
|
+
return url.trim().replace(/^git\+/, "");
|
|
427
|
+
}
|
|
428
|
+
function pathOf(pathname) {
|
|
429
|
+
return pathname.replace(/^\/+/, "").replace(/\/+$/, "");
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* 把一个 git 地址拆成「哪台主机 + 哪个路径」,不管发布者当初用的是 https、ssh 还是
|
|
433
|
+
* scp 那种 `git@host:path` 写法;认不出来给 undefined。
|
|
434
|
+
*/
|
|
435
|
+
function gitAddressOf(url) {
|
|
436
|
+
const text = bare(url);
|
|
437
|
+
if (text === "") return void 0;
|
|
438
|
+
if (text.includes("://")) {
|
|
439
|
+
let parsed;
|
|
440
|
+
try {
|
|
441
|
+
parsed = new URL(text);
|
|
442
|
+
} catch {
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
if (parsed.protocol !== "https:" && parsed.protocol !== "http:" && parsed.protocol !== "ssh:") return;
|
|
446
|
+
const path = pathOf(parsed.pathname);
|
|
447
|
+
if (path === "") return void 0;
|
|
448
|
+
const port = parsed.port === "" ? void 0 : Number(parsed.port);
|
|
449
|
+
const shared = {
|
|
450
|
+
host: parsed.hostname,
|
|
451
|
+
path,
|
|
452
|
+
...port === void 0 ? {} : { port }
|
|
453
|
+
};
|
|
454
|
+
if (parsed.protocol !== "ssh:") return {
|
|
455
|
+
kind: "https",
|
|
456
|
+
...shared
|
|
457
|
+
};
|
|
458
|
+
return {
|
|
459
|
+
kind: "ssh",
|
|
460
|
+
...shared,
|
|
461
|
+
...parsed.username === "" ? {} : { user: parsed.username }
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
const scp = SCP.exec(text);
|
|
465
|
+
if (scp === null) return void 0;
|
|
466
|
+
const path = pathOf(scp[3] ?? "");
|
|
467
|
+
if (path === "") return void 0;
|
|
468
|
+
return {
|
|
469
|
+
kind: "ssh",
|
|
470
|
+
host: scp[2] ?? "",
|
|
471
|
+
path,
|
|
472
|
+
...scp[1] === void 0 ? {} : { user: scp[1] }
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
/** 一份地址的规范写法:ssh 一律成 `ssh://user@host/path`(pnpm 认这个),https 原样。 */
|
|
476
|
+
function gitAddressText(address) {
|
|
477
|
+
const port = address.port === void 0 ? "" : `:${String(address.port)}`;
|
|
478
|
+
if (address.kind === "ssh") return `ssh://${address.user === void 0 || address.user === "" ? "git" : address.user}@${address.host}${port}/${address.path}`;
|
|
479
|
+
return `https://${address.host}${port}/${address.path}`;
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* 同一个仓库的两种读法,发布者用的那种排前面。ssh 那条一律规范成 `ssh://` 形式,
|
|
483
|
+
* 因为 `git@host:path` 拼进安装 spec 时 pnpm 不认;https 那条保留发布者的原样(含 http)。
|
|
484
|
+
*/
|
|
485
|
+
function gitTransportCandidates(url) {
|
|
486
|
+
const address = gitAddressOf(url);
|
|
487
|
+
if (address === void 0) {
|
|
488
|
+
const only = bare(url);
|
|
489
|
+
return only === "" ? [] : [{
|
|
490
|
+
kind: only.startsWith("ssh") ? "ssh" : "https",
|
|
491
|
+
url: only
|
|
492
|
+
}];
|
|
493
|
+
}
|
|
494
|
+
const primary = {
|
|
495
|
+
kind: address.kind,
|
|
496
|
+
url: address.kind === "ssh" ? gitAddressText(address) : bare(url)
|
|
497
|
+
};
|
|
498
|
+
const other = address.kind === "https" ? {
|
|
499
|
+
kind: "ssh",
|
|
500
|
+
url: gitAddressText({
|
|
501
|
+
kind: "ssh",
|
|
502
|
+
host: address.host,
|
|
503
|
+
path: address.path
|
|
504
|
+
})
|
|
505
|
+
} : {
|
|
506
|
+
kind: "https",
|
|
507
|
+
url: gitAddressText({
|
|
508
|
+
kind: "https",
|
|
509
|
+
host: address.host,
|
|
510
|
+
path: address.path
|
|
511
|
+
})
|
|
512
|
+
};
|
|
513
|
+
return other.url === primary.url ? [primary] : [primary, other];
|
|
514
|
+
}
|
|
391
515
|
/**
|
|
392
516
|
* 外壳愿意认的协议版本;不在表里的模块当场被拒,不猜语义。
|
|
393
517
|
*/
|
|
@@ -1007,7 +1131,7 @@ var PHASES = [
|
|
|
1007
1131
|
"ready",
|
|
1008
1132
|
"error"
|
|
1009
1133
|
];
|
|
1010
|
-
function asRecord$
|
|
1134
|
+
function asRecord$7(value) {
|
|
1011
1135
|
return typeof value === "object" && value !== null ? value : void 0;
|
|
1012
1136
|
}
|
|
1013
1137
|
function textOf$5(value, max) {
|
|
@@ -1021,7 +1145,7 @@ function atOf(value) {
|
|
|
1021
1145
|
return Math.trunc(value);
|
|
1022
1146
|
}
|
|
1023
1147
|
function errorOf(raw) {
|
|
1024
|
-
const record = asRecord$
|
|
1148
|
+
const record = asRecord$7(raw);
|
|
1025
1149
|
if (record === void 0) return void 0;
|
|
1026
1150
|
const code = textOf$5(record["code"], 120);
|
|
1027
1151
|
const message = textOf$5(record["message"], PANEL_STATE_MAX_TEXT);
|
|
@@ -1038,7 +1162,7 @@ function errorOf(raw) {
|
|
|
1038
1162
|
* 或一条都认不出(`unit` / `route` / `phase` / `at` 缺一)时 `ok: false` + 中文原因;认不出的单条静默跳过。
|
|
1039
1163
|
*/
|
|
1040
1164
|
function parseCardsReport(payload) {
|
|
1041
|
-
const record = asRecord$
|
|
1165
|
+
const record = asRecord$7(payload);
|
|
1042
1166
|
if (record === void 0) return {
|
|
1043
1167
|
ok: false,
|
|
1044
1168
|
message: "请求体要是一个对象"
|
|
@@ -1055,7 +1179,7 @@ function parseCardsReport(payload) {
|
|
|
1055
1179
|
};
|
|
1056
1180
|
const cards = [];
|
|
1057
1181
|
for (const item of raw.slice(0, 32)) {
|
|
1058
|
-
const card = asRecord$
|
|
1182
|
+
const card = asRecord$7(item);
|
|
1059
1183
|
if (card === void 0) continue;
|
|
1060
1184
|
const unit = textOf$5(card["unit"], 200);
|
|
1061
1185
|
const route = textOf$5(card["route"], 400);
|
|
@@ -1089,7 +1213,7 @@ function parseCardsReport(payload) {
|
|
|
1089
1213
|
* @returns `workspace` / `unit` / `reason` 都不能空,缺一给 `ok: false` + 中文原因;`at` 缺省或不合法时取当前时间。
|
|
1090
1214
|
*/
|
|
1091
1215
|
function parseCrashReport(payload) {
|
|
1092
|
-
const record = asRecord$
|
|
1216
|
+
const record = asRecord$7(payload);
|
|
1093
1217
|
if (record === void 0) return {
|
|
1094
1218
|
ok: false,
|
|
1095
1219
|
message: "请求体要是一个对象"
|
|
@@ -1250,7 +1374,7 @@ var TARGET_CODE = {
|
|
|
1250
1374
|
name: "workbench/no-name",
|
|
1251
1375
|
requestId: "workbench/no-request-id"
|
|
1252
1376
|
};
|
|
1253
|
-
function asRecord$
|
|
1377
|
+
function asRecord$6(value) {
|
|
1254
1378
|
return typeof value === "object" && value !== null ? value : {};
|
|
1255
1379
|
}
|
|
1256
1380
|
function stringsOf$3(value) {
|
|
@@ -1293,7 +1417,7 @@ function requestOf$1(action, value) {
|
|
|
1293
1417
|
};
|
|
1294
1418
|
}
|
|
1295
1419
|
function parseProvisionBody(body) {
|
|
1296
|
-
const raw = asRecord$
|
|
1420
|
+
const raw = asRecord$6(body);
|
|
1297
1421
|
const action = raw.action;
|
|
1298
1422
|
if (typeof action !== "string" || !PROVISION_ACTIONS.includes(action)) return {
|
|
1299
1423
|
ok: false,
|
|
@@ -1333,6 +1457,7 @@ var WorkbenchDataError = class extends Error {
|
|
|
1333
1457
|
hint;
|
|
1334
1458
|
project;
|
|
1335
1459
|
helpUrl;
|
|
1460
|
+
failure;
|
|
1336
1461
|
constructor(code, message, extra = {}) {
|
|
1337
1462
|
super(message);
|
|
1338
1463
|
this.name = "WorkbenchDataError";
|
|
@@ -1341,6 +1466,7 @@ var WorkbenchDataError = class extends Error {
|
|
|
1341
1466
|
if (extra.hint !== void 0) this.hint = extra.hint;
|
|
1342
1467
|
if (extra.project !== void 0) this.project = extra.project;
|
|
1343
1468
|
if (extra.helpUrl !== void 0) this.helpUrl = extra.helpUrl;
|
|
1469
|
+
if (extra.failure !== void 0) this.failure = extra.failure;
|
|
1344
1470
|
}
|
|
1345
1471
|
};
|
|
1346
1472
|
//#endregion
|
|
@@ -1388,7 +1514,8 @@ function toErrorPayload(error) {
|
|
|
1388
1514
|
...data.detail === void 0 ? {} : { detail: data.detail },
|
|
1389
1515
|
...data.hint === void 0 ? {} : { hint: data.hint },
|
|
1390
1516
|
...data.helpUrl === void 0 ? {} : { helpUrl: data.helpUrl },
|
|
1391
|
-
...data.project === void 0 ? {} : { project: data.project }
|
|
1517
|
+
...data.project === void 0 ? {} : { project: data.project },
|
|
1518
|
+
...data.failure === void 0 ? {} : { failure: data.failure }
|
|
1392
1519
|
};
|
|
1393
1520
|
}
|
|
1394
1521
|
return {
|
|
@@ -2138,7 +2265,10 @@ function tokenFamilyOf(name) {
|
|
|
2138
2265
|
return cut === -1 ? rest : rest.slice(0, cut);
|
|
2139
2266
|
}
|
|
2140
2267
|
//#endregion
|
|
2141
|
-
//#region ../shared/dist/
|
|
2268
|
+
//#region ../shared/dist/contract/approval.js
|
|
2269
|
+
/**
|
|
2270
|
+
* agent 自己那句"这次想干什么"的参数名:插件命令族必填,审批屏把它画在详情座里,不进标题。
|
|
2271
|
+
*/
|
|
2142
2272
|
var APPROVAL_NOTE_ARG = "approval_note";
|
|
2143
2273
|
function oneLine(text) {
|
|
2144
2274
|
return text.replace(/\s+/g, " ").trim();
|
|
@@ -2202,10 +2332,11 @@ async function approvalFor(call, context = {}) {
|
|
|
2202
2332
|
reason: facts.blocked
|
|
2203
2333
|
};
|
|
2204
2334
|
if (context.skipAsk === true) return void 0;
|
|
2335
|
+
const note = context.declaredNote === true && verdict.noteInDetail !== true ? approvalNoteOf(call) : void 0;
|
|
2205
2336
|
return {
|
|
2206
2337
|
kind: "ask",
|
|
2207
2338
|
reason: verdict.reason,
|
|
2208
|
-
displayReason: compose(facts, verdict,
|
|
2339
|
+
displayReason: compose(facts, verdict, note)
|
|
2209
2340
|
};
|
|
2210
2341
|
}
|
|
2211
2342
|
//#endregion
|
|
@@ -2425,6 +2556,188 @@ function backgroundLine(command, argv) {
|
|
|
2425
2556
|
].join(" ");
|
|
2426
2557
|
}
|
|
2427
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
|
|
2428
2741
|
//#region ../shared/dist/host/paths.js
|
|
2429
2742
|
function isolatedRoot() {
|
|
2430
2743
|
return `${workbenchRoot()}/opencli`;
|
|
@@ -2438,6 +2751,46 @@ function vendorPluginsDir() {
|
|
|
2438
2751
|
function libsPrefix() {
|
|
2439
2752
|
return `${isolatedRoot()}/libs`;
|
|
2440
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
|
+
}
|
|
2441
2794
|
//#endregion
|
|
2442
2795
|
//#region ../shared/dist/host/adapters.js
|
|
2443
2796
|
var WORKBENCH_ADAPTERS = "workbenchAdapters";
|
|
@@ -3973,7 +4326,7 @@ function registryFailure(change) {
|
|
|
3973
4326
|
//#endregion
|
|
3974
4327
|
//#region ../shared/dist/host/plugin.js
|
|
3975
4328
|
var OPENCLI_MANIFEST_FILE = "opencli-plugin.json";
|
|
3976
|
-
function asRecord$
|
|
4329
|
+
function asRecord$5(value) {
|
|
3977
4330
|
return typeof value === "object" && value !== null ? value : void 0;
|
|
3978
4331
|
}
|
|
3979
4332
|
function isCommandFile(name) {
|
|
@@ -3985,7 +4338,7 @@ function pluginManifestOf(dir) {
|
|
|
3985
4338
|
try {
|
|
3986
4339
|
const parsed = JSON.parse(readFileSync(join(dir, OPENCLI_MANIFEST_FILE), "utf8"));
|
|
3987
4340
|
if (Array.isArray(parsed)) return void 0;
|
|
3988
|
-
return asRecord$
|
|
4341
|
+
return asRecord$5(parsed);
|
|
3989
4342
|
} catch {
|
|
3990
4343
|
return;
|
|
3991
4344
|
}
|
|
@@ -4546,6 +4899,7 @@ function checkVersionRange(contract, version) {
|
|
|
4546
4899
|
var MARKET_DISCOVER_ROUTE = "panels/market-discover";
|
|
4547
4900
|
var MARKET_DOC_ROUTE = "panels/market-doc";
|
|
4548
4901
|
var MARKET_GIT_PROBE_ROUTE = "panels/git-probe";
|
|
4902
|
+
var MARKET_INSTALLED_ROUTE = "panels/market-installed";
|
|
4549
4903
|
var MARKET_PUBLISH_PREVIEW_ROUTE = "panels/market-publish-preview";
|
|
4550
4904
|
var MARKET_SOURCES_RESOURCE = "market-sources";
|
|
4551
4905
|
var MARKET_SOURCES_ROUTE = "panels/market-sources";
|
|
@@ -4591,20 +4945,20 @@ var MARKET_INDEX_README_LINES = [
|
|
|
4591
4945
|
var NPM_NAME = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/;
|
|
4592
4946
|
var GIT_COMMIT = /^[0-9a-f]{40}$/;
|
|
4593
4947
|
var MAX_ICON_CHARS = 32768;
|
|
4594
|
-
function asRecord$
|
|
4948
|
+
function asRecord$4(value) {
|
|
4595
4949
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
4596
4950
|
}
|
|
4597
|
-
function asText$
|
|
4951
|
+
function asText$4(value) {
|
|
4598
4952
|
return typeof value === "string" ? value.trim() : "";
|
|
4599
4953
|
}
|
|
4600
4954
|
function asTextArray(value) {
|
|
4601
4955
|
return Array.isArray(value) ? value.flatMap((item) => typeof item === "string" && item.trim() !== "" ? [item.trim()] : []) : [];
|
|
4602
4956
|
}
|
|
4603
4957
|
function asLocalized(value) {
|
|
4604
|
-
const record = asRecord$
|
|
4958
|
+
const record = asRecord$4(value);
|
|
4605
4959
|
if (record === void 0) return void 0;
|
|
4606
|
-
const zh = asText$
|
|
4607
|
-
const en = asText$
|
|
4960
|
+
const zh = asText$4(record["zh"]);
|
|
4961
|
+
const en = asText$4(record["en"]);
|
|
4608
4962
|
return zh === "" && en === "" ? void 0 : {
|
|
4609
4963
|
...zh === "" ? {} : { zh },
|
|
4610
4964
|
...en === "" ? {} : { en }
|
|
@@ -4621,19 +4975,19 @@ function compareSemver(left, right) {
|
|
|
4621
4975
|
return compareVersions(left, right);
|
|
4622
4976
|
}
|
|
4623
4977
|
function sourceOf(raw, at) {
|
|
4624
|
-
const record = asRecord$
|
|
4978
|
+
const record = asRecord$4(raw);
|
|
4625
4979
|
if (record === void 0) return problem("source", "source 必须是一个对象", at);
|
|
4626
4980
|
if (record["type"] === "npm") {
|
|
4627
4981
|
const registry = record["registry"];
|
|
4628
|
-
if (registry !== void 0 && asText$
|
|
4982
|
+
if (registry !== void 0 && asText$4(registry) === "") return problem("source", "npm source 的 registry 必须是非空字符串", at);
|
|
4629
4983
|
return {
|
|
4630
4984
|
type: "npm",
|
|
4631
|
-
...registry === void 0 ? {} : { registry: asText$
|
|
4985
|
+
...registry === void 0 ? {} : { registry: asText$4(registry) }
|
|
4632
4986
|
};
|
|
4633
4987
|
}
|
|
4634
4988
|
if (record["type"] === "git") {
|
|
4635
|
-
const url = asText$
|
|
4636
|
-
const commit = asText$
|
|
4989
|
+
const url = asText$4(record["url"]);
|
|
4990
|
+
const commit = asText$4(record["commit"]);
|
|
4637
4991
|
if (url === "") return problem("source", "git source 缺 url", at);
|
|
4638
4992
|
if (!GIT_COMMIT.test(commit)) return problem("git-commit", `git source 的 commit 必须是 40 位小写 SHA(现在是 ${JSON.stringify(commit)})`, at);
|
|
4639
4993
|
const path = record["path"];
|
|
@@ -4641,22 +4995,22 @@ function sourceOf(raw, at) {
|
|
|
4641
4995
|
type: "git",
|
|
4642
4996
|
url,
|
|
4643
4997
|
commit,
|
|
4644
|
-
...path === void 0 || asText$
|
|
4998
|
+
...path === void 0 || asText$4(path) === "" ? {} : { path: asText$4(path) }
|
|
4645
4999
|
};
|
|
4646
5000
|
}
|
|
4647
5001
|
return problem("source", "source.type 只能是 npm 或 git", at);
|
|
4648
5002
|
}
|
|
4649
5003
|
function versionOf(raw, at) {
|
|
4650
|
-
const record = asRecord$
|
|
5004
|
+
const record = asRecord$4(raw);
|
|
4651
5005
|
if (record === void 0) return problem("version", "版本必须是一个对象", at);
|
|
4652
|
-
const version = asText$
|
|
5006
|
+
const version = asText$4(record["version"]);
|
|
4653
5007
|
if (!isValidVersion(version)) return problem("version", `版本号不是合法 SemVer:${JSON.stringify(version)}`, at);
|
|
4654
5008
|
const source = sourceOf(record["source"], `${at}.source`);
|
|
4655
5009
|
if ("kind" in source) return source;
|
|
4656
5010
|
const timestamp = record["publishTimestamp"];
|
|
4657
5011
|
if (timestamp !== void 0 && (!Number.isInteger(timestamp) || timestamp < 0)) return problem("timestamp", "publishTimestamp 必须是 Unix 秒整数", at);
|
|
4658
|
-
const engines = asRecord$
|
|
4659
|
-
const dsh = engines === void 0 ? "" : asText$
|
|
5012
|
+
const engines = asRecord$4(record["engines"]);
|
|
5013
|
+
const dsh = engines === void 0 ? "" : asText$4(engines["dsh"]);
|
|
4660
5014
|
return {
|
|
4661
5015
|
version,
|
|
4662
5016
|
source,
|
|
@@ -4694,14 +5048,14 @@ function ownerProblem(owner) {
|
|
|
4694
5048
|
if (owner.length > MAX_OWNER_CHARS) return problem("owner", "owner 超过 200 字");
|
|
4695
5049
|
}
|
|
4696
5050
|
function metadataOf(raw) {
|
|
4697
|
-
const record = asRecord$
|
|
5051
|
+
const record = asRecord$4(raw);
|
|
4698
5052
|
if (record === void 0) return void 0;
|
|
4699
5053
|
const workbench = record["workbench"] === true;
|
|
4700
|
-
const unit = asText$
|
|
4701
|
-
const category = asText$
|
|
5054
|
+
const unit = asText$4(record["unit"]);
|
|
5055
|
+
const category = asText$4(record["category"]);
|
|
4702
5056
|
const labels = asTextArray(record["labels"]);
|
|
4703
5057
|
const keywords = asTextArray(record["keywords"]);
|
|
4704
|
-
const sortKeysRecord = asRecord$
|
|
5058
|
+
const sortKeysRecord = asRecord$4(record["sortKeys"]);
|
|
4705
5059
|
const sortKeys = sortKeysRecord === void 0 ? void 0 : Object.fromEntries(Object.entries(sortKeysRecord).flatMap(([key, value]) => typeof value === "string" || typeof value === "number" ? [[key, value]] : []));
|
|
4706
5060
|
return {
|
|
4707
5061
|
...workbench ? { workbench: true } : {},
|
|
@@ -4713,26 +5067,26 @@ function metadataOf(raw) {
|
|
|
4713
5067
|
};
|
|
4714
5068
|
}
|
|
4715
5069
|
function entryOf$1(raw, at) {
|
|
4716
|
-
const record = asRecord$
|
|
5070
|
+
const record = asRecord$4(raw);
|
|
4717
5071
|
if (record === void 0) return problem("plugin", "插件条目必须是对象", at);
|
|
4718
|
-
const name = asText$
|
|
5072
|
+
const name = asText$4(record["name"]);
|
|
4719
5073
|
if (name === "" || name.length > 214 || !NPM_NAME.test(name)) return problem("name", `不是合法 npm 包名:${JSON.stringify(name)}`, at);
|
|
4720
5074
|
const versions = versionsOf$1(record["versions"], `${at}.versions`);
|
|
4721
5075
|
if ("kind" in versions) return versions;
|
|
4722
|
-
const icon = asText$
|
|
5076
|
+
const icon = asText$4(record["icon"]);
|
|
4723
5077
|
if (icon !== "") {
|
|
4724
5078
|
const badIcon = iconProblem(icon);
|
|
4725
5079
|
if (badIcon !== void 0) return badIcon;
|
|
4726
5080
|
}
|
|
4727
5081
|
const title = asLocalized(record["title"]);
|
|
4728
5082
|
const description = asLocalized(record["description"]);
|
|
4729
|
-
const author = asText$
|
|
4730
|
-
const owner = asText$
|
|
5083
|
+
const author = asText$4(record["author"]);
|
|
5084
|
+
const owner = asText$4(record["owner"]);
|
|
4731
5085
|
if (owner !== "") {
|
|
4732
5086
|
const badOwner = ownerProblem(owner);
|
|
4733
5087
|
if (badOwner !== void 0) return badOwner;
|
|
4734
5088
|
}
|
|
4735
|
-
const homepage = asText$
|
|
5089
|
+
const homepage = asText$4(record["homepage"]);
|
|
4736
5090
|
const metadata = metadataOf(record["metadata"]);
|
|
4737
5091
|
if (metadata?.unit !== void 0 && !UNIT_ID.test(metadata.unit)) return problem("unit", `metadata.unit 只能是小写字母开头的 a-z0-9-(现在是 ${JSON.stringify(metadata.unit)})`, at);
|
|
4738
5092
|
return {
|
|
@@ -4748,7 +5102,7 @@ function entryOf$1(raw, at) {
|
|
|
4748
5102
|
};
|
|
4749
5103
|
}
|
|
4750
5104
|
function parseMarketIndex(raw) {
|
|
4751
|
-
const record = asRecord$
|
|
5105
|
+
const record = asRecord$4(raw);
|
|
4752
5106
|
if (record === void 0) return {
|
|
4753
5107
|
ok: false,
|
|
4754
5108
|
problems: [problem("not-object", "索引必须是一个 JSON 对象")]
|
|
@@ -4784,7 +5138,7 @@ function parseMarketIndex(raw) {
|
|
|
4784
5138
|
};
|
|
4785
5139
|
const title = asLocalized(record["title"]);
|
|
4786
5140
|
const description = asLocalized(record["description"]);
|
|
4787
|
-
const metadata = asRecord$
|
|
5141
|
+
const metadata = asRecord$4(record["metadata"]);
|
|
4788
5142
|
return {
|
|
4789
5143
|
ok: true,
|
|
4790
5144
|
index: {
|
|
@@ -4838,15 +5192,15 @@ function marketSourceKey(source) {
|
|
|
4838
5192
|
return createHash("sha256").update(canonical).digest("hex").slice(0, 16);
|
|
4839
5193
|
}
|
|
4840
5194
|
function parseSourceConfig(raw) {
|
|
4841
|
-
const record = asRecord$
|
|
5195
|
+
const record = asRecord$4(raw);
|
|
4842
5196
|
if (record === void 0) return {
|
|
4843
5197
|
ok: false,
|
|
4844
5198
|
problem: problem("source", "索引源必须是对象")
|
|
4845
5199
|
};
|
|
4846
|
-
const id = asText$
|
|
4847
|
-
const label = asText$
|
|
5200
|
+
const id = asText$4(record["id"]);
|
|
5201
|
+
const label = asText$4(record["label"]);
|
|
4848
5202
|
const kind = record["kind"];
|
|
4849
|
-
const url = asText$
|
|
5203
|
+
const url = asText$4(record["url"]);
|
|
4850
5204
|
if (id === "") return {
|
|
4851
5205
|
ok: false,
|
|
4852
5206
|
problem: problem("source", "索引源缺 id")
|
|
@@ -4879,9 +5233,9 @@ function parseSourceConfig(raw) {
|
|
|
4879
5233
|
ok: false,
|
|
4880
5234
|
problem: problem("source", "git 地址不合格(只收 https / ssh / scp 形式,且不能带凭据)")
|
|
4881
5235
|
};
|
|
4882
|
-
const ref = asText$
|
|
4883
|
-
const path = asText$
|
|
4884
|
-
const localPath = asText$
|
|
5236
|
+
const ref = asText$4(record["ref"]);
|
|
5237
|
+
const path = asText$4(record["path"]);
|
|
5238
|
+
const localPath = asText$4(record["localPath"]);
|
|
4885
5239
|
const policy = record["policy"];
|
|
4886
5240
|
if (policy !== void 0 && policy !== "artifacts-in-repo" && policy !== "catalog-only") return {
|
|
4887
5241
|
ok: false,
|
|
@@ -5191,6 +5545,72 @@ function normalizeWhitespace(rows) {
|
|
|
5191
5545
|
return out.join("\n").replace(/\n+$/, "");
|
|
5192
5546
|
}
|
|
5193
5547
|
//#endregion
|
|
5548
|
+
//#region ../shared/dist/host/installed-sources.js
|
|
5549
|
+
var FILE_NAME = ".dsh-workbench-sources.json";
|
|
5550
|
+
var FILE_VERSION = 1;
|
|
5551
|
+
function asRecord$3(value) {
|
|
5552
|
+
return typeof value === "object" && value !== null ? value : void 0;
|
|
5553
|
+
}
|
|
5554
|
+
function asText$3(value) {
|
|
5555
|
+
return typeof value === "string" ? value.trim() : "";
|
|
5556
|
+
}
|
|
5557
|
+
function installedSourcesFile(profileDir) {
|
|
5558
|
+
return path.join(profileDir, FILE_NAME);
|
|
5559
|
+
}
|
|
5560
|
+
function readInstalledSources(profileDir) {
|
|
5561
|
+
if (profileDir === null) return {};
|
|
5562
|
+
let parsed;
|
|
5563
|
+
try {
|
|
5564
|
+
parsed = JSON.parse(fs.readFileSync(installedSourcesFile(profileDir), "utf8"));
|
|
5565
|
+
} catch {
|
|
5566
|
+
return {};
|
|
5567
|
+
}
|
|
5568
|
+
const packages = asRecord$3(asRecord$3(parsed)?.packages);
|
|
5569
|
+
if (packages === void 0) return {};
|
|
5570
|
+
const found = {};
|
|
5571
|
+
for (const [pkg, value] of Object.entries(packages)) {
|
|
5572
|
+
const record = asRecord$3(value);
|
|
5573
|
+
if (record === void 0) continue;
|
|
5574
|
+
const version = asText$3(record.version);
|
|
5575
|
+
const registry = asText$3(record.registry);
|
|
5576
|
+
if (pkg === "" || version === "" || registry === "") continue;
|
|
5577
|
+
found[pkg] = {
|
|
5578
|
+
version,
|
|
5579
|
+
registry
|
|
5580
|
+
};
|
|
5581
|
+
}
|
|
5582
|
+
return found;
|
|
5583
|
+
}
|
|
5584
|
+
function packageSourceOf(profileDir, pkg) {
|
|
5585
|
+
return readInstalledSources(profileDir)[pkg];
|
|
5586
|
+
}
|
|
5587
|
+
function writeInstalledSources(profileDir, sources) {
|
|
5588
|
+
const file = installedSourcesFile(profileDir);
|
|
5589
|
+
const temp = `${file}.tmp`;
|
|
5590
|
+
const text = `${JSON.stringify({
|
|
5591
|
+
version: FILE_VERSION,
|
|
5592
|
+
packages: sources
|
|
5593
|
+
}, null, 2)}\n`;
|
|
5594
|
+
fs.writeFileSync(temp, text, { mode: 384 });
|
|
5595
|
+
fs.renameSync(temp, file);
|
|
5596
|
+
}
|
|
5597
|
+
function rememberPackageSource(profileDir, pkg, source) {
|
|
5598
|
+
const trimmed = pkg.trim();
|
|
5599
|
+
if (trimmed === "" || source.version.trim() === "" || source.registry.trim() === "") return;
|
|
5600
|
+
const all = { ...readInstalledSources(profileDir) };
|
|
5601
|
+
all[trimmed] = {
|
|
5602
|
+
version: source.version.trim(),
|
|
5603
|
+
registry: source.registry.trim()
|
|
5604
|
+
};
|
|
5605
|
+
writeInstalledSources(profileDir, all);
|
|
5606
|
+
}
|
|
5607
|
+
function forgetPackageSource(profileDir, pkg) {
|
|
5608
|
+
const all = { ...readInstalledSources(profileDir) };
|
|
5609
|
+
if (all[pkg] === void 0) return;
|
|
5610
|
+
delete all[pkg];
|
|
5611
|
+
writeInstalledSources(profileDir, all);
|
|
5612
|
+
}
|
|
5613
|
+
//#endregion
|
|
5194
5614
|
//#region ../shared/dist/contract/protocol/dsh-plugins.js
|
|
5195
5615
|
var DSH_PLUGINS_ROUTE = "dsh-plugins";
|
|
5196
5616
|
//#endregion
|
|
@@ -5389,8 +5809,10 @@ function moduleVersionsHandler(ctx, shell, running, selfUpdate) {
|
|
|
5389
5809
|
const force = param(search, "force") === "1";
|
|
5390
5810
|
const fallback = (await manager.registries())?.resolved ?? void 0;
|
|
5391
5811
|
const modules = await Promise.all(found.map(async (module) => {
|
|
5392
|
-
const
|
|
5393
|
-
const
|
|
5812
|
+
const local = module.local === true;
|
|
5813
|
+
const fromRegistry = module.fromRegistry !== false;
|
|
5814
|
+
const registry = fromRegistry ? module.registry ?? fallback : void 0;
|
|
5815
|
+
const latest = local || !fromRegistry || shell === void 0 || registry === void 0 ? void 0 : await supply.latestVersion({
|
|
5394
5816
|
shell,
|
|
5395
5817
|
name: module.pkg,
|
|
5396
5818
|
registry,
|
|
@@ -5422,7 +5844,10 @@ function moduleVersionsHandler(ctx, shell, running, selfUpdate) {
|
|
|
5422
5844
|
...title === void 0 ? {} : { title },
|
|
5423
5845
|
...latest === void 0 ? {} : { latest },
|
|
5424
5846
|
...latestBlocked === void 0 ? {} : { latestBlocked },
|
|
5425
|
-
...
|
|
5847
|
+
...local ? {
|
|
5848
|
+
local: true,
|
|
5849
|
+
latestUnavailable: "local"
|
|
5850
|
+
} : latest !== void 0 ? {} : { latestUnavailable: !fromRegistry ? "not-registry" : registry === void 0 ? "no-registry" : "probe-failed" },
|
|
5426
5851
|
...refused === void 0 ? {} : { refused }
|
|
5427
5852
|
};
|
|
5428
5853
|
}));
|
|
@@ -5581,6 +6006,18 @@ async function probeGitAddress(shell, url, options = {}) {
|
|
|
5581
6006
|
...detail === "" ? {} : { detail }
|
|
5582
6007
|
};
|
|
5583
6008
|
}
|
|
6009
|
+
var PROBE_CACHE_MS = 6e4;
|
|
6010
|
+
var probeCache = /* @__PURE__ */ new Map();
|
|
6011
|
+
async function probeGitAddressCached(shell, url, options = {}) {
|
|
6012
|
+
const hit = probeCache.get(url);
|
|
6013
|
+
if (hit !== void 0 && Date.now() - hit.at < PROBE_CACHE_MS) return hit.payload;
|
|
6014
|
+
const payload = await probeGitAddress(shell, url, options);
|
|
6015
|
+
probeCache.set(url, {
|
|
6016
|
+
at: Date.now(),
|
|
6017
|
+
payload
|
|
6018
|
+
});
|
|
6019
|
+
return payload;
|
|
6020
|
+
}
|
|
5584
6021
|
//#endregion
|
|
5585
6022
|
//#region src/shared/api/host/git-routes.ts
|
|
5586
6023
|
function gitProbeHandler(ctx) {
|
|
@@ -11602,6 +12039,22 @@ function registerRecordTools(ctx, store) {
|
|
|
11602
12039
|
];
|
|
11603
12040
|
}
|
|
11604
12041
|
//#endregion
|
|
12042
|
+
//#region src/modules/panels/model/market-install-error.ts
|
|
12043
|
+
var INSTALL_PROBLEM = {
|
|
12044
|
+
permission: "没有写入权限",
|
|
12045
|
+
"disk-full": "磁盘满了",
|
|
12046
|
+
sandbox: "被系统拦住了",
|
|
12047
|
+
unknown: "命令失败"
|
|
12048
|
+
};
|
|
12049
|
+
function installError(failure) {
|
|
12050
|
+
const tail = tailLines(failure.output, 6);
|
|
12051
|
+
const stamp = failure.exitCode === null ? "" : `(退出码 ${failure.exitCode})`;
|
|
12052
|
+
return new WorkbenchDataError("install-failed", `${INSTALL_PROBLEM[failure.kind]}${stamp}${tail === "" ? "" : `:${tail}`}`, {
|
|
12053
|
+
detail: failure.output,
|
|
12054
|
+
failure
|
|
12055
|
+
});
|
|
12056
|
+
}
|
|
12057
|
+
//#endregion
|
|
11605
12058
|
//#region src/modules/panels/model/market-dependency.ts
|
|
11606
12059
|
var DEPENDENCY_KINDS = ["extension", "lib"];
|
|
11607
12060
|
function dependencyId(kind, name) {
|
|
@@ -11749,10 +12202,22 @@ function pluginNameOf(entry) {
|
|
|
11749
12202
|
}
|
|
11750
12203
|
//#endregion
|
|
11751
12204
|
//#region src/modules/panels/model/market-route.ts
|
|
12205
|
+
function packageNameInPath(path) {
|
|
12206
|
+
const parts = path.split("/").filter((part) => part !== "");
|
|
12207
|
+
const last = parts[parts.length - 1];
|
|
12208
|
+
if (last === void 0 || last === "") return void 0;
|
|
12209
|
+
const parent = parts[parts.length - 2];
|
|
12210
|
+
return parent !== void 0 && parent.startsWith("@") ? `${parent}/${last}` : last;
|
|
12211
|
+
}
|
|
11752
12212
|
function bundleNameOf(change, spec) {
|
|
11753
12213
|
if (change.bundle !== void 0 && change.bundle !== "") return change.bundle;
|
|
11754
|
-
const at = spec.
|
|
11755
|
-
|
|
12214
|
+
const at = spec.indexOf("&path:");
|
|
12215
|
+
if (at !== -1) {
|
|
12216
|
+
const inPath = packageNameInPath(spec.slice(at + 6));
|
|
12217
|
+
if (inPath !== void 0) return inPath;
|
|
12218
|
+
}
|
|
12219
|
+
const version = spec.lastIndexOf("@");
|
|
12220
|
+
return version > 0 ? spec.slice(0, version) : spec;
|
|
11756
12221
|
}
|
|
11757
12222
|
function installedPluginName(spec, pkg) {
|
|
11758
12223
|
const local = localDirOf(spec);
|
|
@@ -11811,7 +12276,8 @@ async function settle(deps, spec, options, change) {
|
|
|
11811
12276
|
kind: "unknown",
|
|
11812
12277
|
pkg
|
|
11813
12278
|
};
|
|
11814
|
-
|
|
12279
|
+
const registry = options?.registry ?? (module.kind === "module" ? module.registry : void 0);
|
|
12280
|
+
await deps.rememberSource(deps.manager, pkg, registry, module.kind === "module" ? module.version : void 0);
|
|
11815
12281
|
return {
|
|
11816
12282
|
change,
|
|
11817
12283
|
module,
|
|
@@ -11999,6 +12465,110 @@ async function updateModuleBundle(unit, deps) {
|
|
|
11999
12465
|
};
|
|
12000
12466
|
}
|
|
12001
12467
|
//#endregion
|
|
12468
|
+
//#region src/modules/panels/model/market-registry.ts
|
|
12469
|
+
async function rememberInstalledSource(deps, manager, pkg, registry, version) {
|
|
12470
|
+
const { registries, profileDir, logger } = deps;
|
|
12471
|
+
const wanted = registry?.trim() ?? "";
|
|
12472
|
+
if (wanted === "") return;
|
|
12473
|
+
if (profileDir !== void 0 && profileDir !== null && version !== void 0 && version.trim() !== "") try {
|
|
12474
|
+
rememberPackageSource(profileDir, pkg, {
|
|
12475
|
+
version,
|
|
12476
|
+
registry: wanted
|
|
12477
|
+
});
|
|
12478
|
+
} catch (error) {
|
|
12479
|
+
logger?.warn?.(`[dsh-workbench] 记 ${pkg} 这次的来源失败:${String(error)}`);
|
|
12480
|
+
}
|
|
12481
|
+
if (registries === void 0) return;
|
|
12482
|
+
const scope = scopeOf$2(pkg);
|
|
12483
|
+
if (scope === null) return;
|
|
12484
|
+
const known = await manager.registries().catch(() => null);
|
|
12485
|
+
if (known !== null && sameRegistry(known.resolved, wanted)) return;
|
|
12486
|
+
try {
|
|
12487
|
+
const added = registries.remember([{
|
|
12488
|
+
scope,
|
|
12489
|
+
registry: wanted
|
|
12490
|
+
}]);
|
|
12491
|
+
if (added.length > 0) logger?.info?.(`[dsh-workbench] 把 ${added.join("、")} 记进 profile 的 .npmrc(卸载只认 pnpm 的配置)`);
|
|
12492
|
+
} catch (error) {
|
|
12493
|
+
logger?.warn?.(`[dsh-workbench] 记这个 scope 的源失败:${String(error)}`);
|
|
12494
|
+
}
|
|
12495
|
+
}
|
|
12496
|
+
async function retryAfterRegistryFix(deps, manager, pkg, change) {
|
|
12497
|
+
const { registries, logger } = deps;
|
|
12498
|
+
if (registries === void 0 || !registryFailure(change)) return change;
|
|
12499
|
+
const known = await manager.registries().catch(() => null);
|
|
12500
|
+
const missing = registries.sources().filter((entry) => !sameRegistry(entry.registry, known?.resolved ?? null));
|
|
12501
|
+
if (missing.length === 0) return change;
|
|
12502
|
+
let added;
|
|
12503
|
+
try {
|
|
12504
|
+
added = registries.remember(missing);
|
|
12505
|
+
} catch (error) {
|
|
12506
|
+
logger?.warn?.(`[dsh-workbench] 卸 ${pkg} 撞上源,但补配置失败:${String(error)}`);
|
|
12507
|
+
return change;
|
|
12508
|
+
}
|
|
12509
|
+
if (added.length === 0) return change;
|
|
12510
|
+
logger?.warn?.(`[dsh-workbench] 卸 ${pkg} 撞上源:补 ${added.join("、")} 后重试一次`);
|
|
12511
|
+
return await manager.remove(pkg).catch(() => null) ?? change;
|
|
12512
|
+
}
|
|
12513
|
+
//#endregion
|
|
12514
|
+
//#region src/modules/panels/model/market-remove.ts
|
|
12515
|
+
var chain = Promise.resolve();
|
|
12516
|
+
function serialized(operation) {
|
|
12517
|
+
const run = chain.then(operation, operation);
|
|
12518
|
+
chain = run.then(() => void 0, () => void 0);
|
|
12519
|
+
return run;
|
|
12520
|
+
}
|
|
12521
|
+
function forgetSource(options) {
|
|
12522
|
+
const dir = options.profileDir;
|
|
12523
|
+
if (dir === void 0 || dir === null) return;
|
|
12524
|
+
try {
|
|
12525
|
+
forgetPackageSource(dir, options.pkg);
|
|
12526
|
+
} catch (error) {
|
|
12527
|
+
options.logger?.warn?.(`[dsh-workbench] 清 ${options.pkg} 的来源记录失败:${String(error)}`);
|
|
12528
|
+
}
|
|
12529
|
+
}
|
|
12530
|
+
function swapScopeFor(options) {
|
|
12531
|
+
const dir = options.profileDir;
|
|
12532
|
+
const notebook = options.registryFix.registries;
|
|
12533
|
+
if (dir === void 0 || dir === null || notebook?.swapScope === void 0) return void 0;
|
|
12534
|
+
const source = packageSourceOf(dir, options.pkg);
|
|
12535
|
+
const scope = scopeOf$2(options.pkg);
|
|
12536
|
+
if (source === void 0 || scope === null) return void 0;
|
|
12537
|
+
try {
|
|
12538
|
+
return notebook.swapScope(scope, source.registry);
|
|
12539
|
+
} catch (error) {
|
|
12540
|
+
options.logger?.warn?.(`[dsh-workbench] 临时把 ${scope} 指到 ${source.registry} 失败,卸载照走:${String(error)}`);
|
|
12541
|
+
return;
|
|
12542
|
+
}
|
|
12543
|
+
}
|
|
12544
|
+
async function removeOnce(options) {
|
|
12545
|
+
const { manager, pkg, registryFix, logger } = options;
|
|
12546
|
+
const first = await manager.remove(pkg);
|
|
12547
|
+
if (first === null) throw new Error("插件管理器回的卸载结果看不懂。");
|
|
12548
|
+
const settled = await retryAfterRegistryFix(registryFix, manager, pkg, first);
|
|
12549
|
+
if (settled.application !== "failed" && settled.application !== "cancelled") {
|
|
12550
|
+
forgetSource(options);
|
|
12551
|
+
return settled;
|
|
12552
|
+
}
|
|
12553
|
+
const put = await manager.setEnabled?.(pkg, true);
|
|
12554
|
+
logger?.warn?.(`[dsh-workbench] ${pkg} 没卸成(${settled.errorCode ?? settled.application})—— 已把它那一行放回名单,别让一次失败留在"装着但关着"`);
|
|
12555
|
+
return put ?? settled;
|
|
12556
|
+
}
|
|
12557
|
+
function removeDshBundle(options) {
|
|
12558
|
+
return serialized(async () => {
|
|
12559
|
+
const restore = swapScopeFor(options);
|
|
12560
|
+
try {
|
|
12561
|
+
return await removeOnce(options);
|
|
12562
|
+
} finally {
|
|
12563
|
+
try {
|
|
12564
|
+
restore?.();
|
|
12565
|
+
} catch (error) {
|
|
12566
|
+
options.logger?.warn?.(`[dsh-workbench] 还原 .npmrc 失败:${String(error)}`);
|
|
12567
|
+
}
|
|
12568
|
+
}
|
|
12569
|
+
});
|
|
12570
|
+
}
|
|
12571
|
+
//#endregion
|
|
12002
12572
|
//#region src/modules/panels/model/market-dependency-update.ts
|
|
12003
12573
|
async function updateDependencyRow(target, deps) {
|
|
12004
12574
|
if (target.conflict !== void 0) throw new Error(`这一行有两份来源,先统一声明再更新:${target.conflict}`);
|
|
@@ -12371,42 +12941,6 @@ function createEnvDropper(options) {
|
|
|
12371
12941
|
};
|
|
12372
12942
|
}
|
|
12373
12943
|
//#endregion
|
|
12374
|
-
//#region src/modules/panels/model/market-registry.ts
|
|
12375
|
-
async function rememberInstalledSource(deps, manager, pkg, registry) {
|
|
12376
|
-
const { registries, logger } = deps;
|
|
12377
|
-
if (registries === void 0 || registry === void 0) return;
|
|
12378
|
-
const scope = scopeOf$2(pkg);
|
|
12379
|
-
if (scope === null) return;
|
|
12380
|
-
const known = await manager.registries().catch(() => null);
|
|
12381
|
-
if (known !== null && sameRegistry(known.resolved, registry)) return;
|
|
12382
|
-
try {
|
|
12383
|
-
const added = registries.remember([{
|
|
12384
|
-
scope,
|
|
12385
|
-
registry
|
|
12386
|
-
}]);
|
|
12387
|
-
if (added.length > 0) logger?.info?.(`[dsh-workbench] 把 ${added.join("、")} 记进 profile 的 .npmrc(卸载只认 pnpm 的配置)`);
|
|
12388
|
-
} catch (error) {
|
|
12389
|
-
logger?.warn?.(`[dsh-workbench] 记这个 scope 的源失败:${String(error)}`);
|
|
12390
|
-
}
|
|
12391
|
-
}
|
|
12392
|
-
async function retryAfterRegistryFix(deps, manager, pkg, change) {
|
|
12393
|
-
const { registries, logger } = deps;
|
|
12394
|
-
if (registries === void 0 || !registryFailure(change)) return change;
|
|
12395
|
-
const known = await manager.registries().catch(() => null);
|
|
12396
|
-
const missing = registries.sources().filter((entry) => !sameRegistry(entry.registry, known?.resolved ?? null));
|
|
12397
|
-
if (missing.length === 0) return change;
|
|
12398
|
-
let added;
|
|
12399
|
-
try {
|
|
12400
|
-
added = registries.remember(missing);
|
|
12401
|
-
} catch (error) {
|
|
12402
|
-
logger?.warn?.(`[dsh-workbench] 卸 ${pkg} 撞上源,但补配置失败:${String(error)}`);
|
|
12403
|
-
return change;
|
|
12404
|
-
}
|
|
12405
|
-
if (added.length === 0) return change;
|
|
12406
|
-
logger?.warn?.(`[dsh-workbench] 卸 ${pkg} 撞上源:补 ${added.join("、")} 后重试一次`);
|
|
12407
|
-
return await manager.remove(pkg).catch(() => null) ?? change;
|
|
12408
|
-
}
|
|
12409
|
-
//#endregion
|
|
12410
12944
|
//#region src/modules/panels/model/market-restart.ts
|
|
12411
12945
|
function restartLogLine(change) {
|
|
12412
12946
|
if (change === null || change === void 0 || change.application !== "restart-required") return;
|
|
@@ -12416,7 +12950,7 @@ function restartLogLine(change) {
|
|
|
12416
12950
|
//#endregion
|
|
12417
12951
|
//#region src/modules/panels/model/market.ts
|
|
12418
12952
|
function createMarketService(options) {
|
|
12419
|
-
const { manifests, manifestsNow, probe, shell, logger, supply: supplyOption, onInstalled, onUninstalled, isReleased, bundles, installed, modules, workbench, registries, ledger, references, dshHome, profileName } = options;
|
|
12953
|
+
const { manifests, manifestsNow, probe, shell, logger, supply: supplyOption, onInstalled, onUninstalled, isReleased, bundles, installed, modules, workbench, registries, profileDir, ledger, references, dshHome, profileName } = options;
|
|
12420
12954
|
const supply = supplyOption ?? requireSupply();
|
|
12421
12955
|
const command = options.command ?? "opencli";
|
|
12422
12956
|
const catalog = () => manifestsNow?.() ?? manifests;
|
|
@@ -12455,11 +12989,18 @@ function createMarketService(options) {
|
|
|
12455
12989
|
}
|
|
12456
12990
|
async function run(line, optional = false) {
|
|
12457
12991
|
if (shell === void 0) throw new Error("没有执行缝(shell 服务不可用),装 / 卸做不了。");
|
|
12992
|
+
const blocked = installWriteProblem({
|
|
12993
|
+
command: line,
|
|
12994
|
+
fallback: isolatedRoot()
|
|
12995
|
+
});
|
|
12996
|
+
if (blocked !== void 0) throw installError(blocked);
|
|
12458
12997
|
const result = await runShell(shell, line, { timeoutMs: INSTALL_TIMEOUT_MS });
|
|
12459
|
-
if (result.exitCode !== 0 && !optional) {
|
|
12460
|
-
|
|
12461
|
-
|
|
12462
|
-
|
|
12998
|
+
if (result.exitCode !== 0 && !optional) throw installError(diagnoseInstallFailure({
|
|
12999
|
+
command: line,
|
|
13000
|
+
exitCode: result.exitCode,
|
|
13001
|
+
output: result.stderr || result.stdout,
|
|
13002
|
+
fallback: isolatedRoot()
|
|
13003
|
+
}));
|
|
12463
13004
|
}
|
|
12464
13005
|
async function runAll(commands) {
|
|
12465
13006
|
for (const step of commands) await run(step.line, step.optional === true);
|
|
@@ -12544,26 +13085,24 @@ function createMarketService(options) {
|
|
|
12544
13085
|
}
|
|
12545
13086
|
const registryFix = {
|
|
12546
13087
|
...registries === void 0 ? {} : { registries },
|
|
13088
|
+
...profileDir === void 0 || profileDir === null ? {} : { profileDir },
|
|
12547
13089
|
...logger === void 0 ? {} : { logger }
|
|
12548
13090
|
};
|
|
12549
|
-
async function removeInstalledBundle(manager, pkg) {
|
|
12550
|
-
const first = await manager.remove(pkg);
|
|
12551
|
-
if (first === null) throw new Error("插件管理器回的卸载结果看不懂。");
|
|
12552
|
-
const settled = await retryAfterRegistryFix(registryFix, manager, pkg, first);
|
|
12553
|
-
if (settled.application !== "failed" && settled.application !== "cancelled") return settled;
|
|
12554
|
-
const put = await manager.setEnabled?.(pkg, true);
|
|
12555
|
-
logger?.warn?.(`[dsh-workbench] ${pkg} 没卸成(${settled.errorCode ?? settled.application})—— 已把它那一行放回名单,别让一次失败留在"装着但关着"`);
|
|
12556
|
-
return put ?? settled;
|
|
12557
|
-
}
|
|
12558
13091
|
function routeDeps(manager) {
|
|
12559
13092
|
return {
|
|
12560
13093
|
manager,
|
|
12561
13094
|
command,
|
|
12562
13095
|
supply,
|
|
12563
13096
|
runAll,
|
|
12564
|
-
rememberSource: (manager, pkg, registry) => rememberInstalledSource(registryFix, manager, pkg, registry),
|
|
13097
|
+
rememberSource: (manager, pkg, registry, version) => rememberInstalledSource(registryFix, manager, pkg, registry, version),
|
|
12565
13098
|
supplyAfter,
|
|
12566
|
-
remove: (pkg) =>
|
|
13099
|
+
remove: (pkg) => removeDshBundle({
|
|
13100
|
+
manager,
|
|
13101
|
+
pkg,
|
|
13102
|
+
profileDir,
|
|
13103
|
+
registryFix,
|
|
13104
|
+
...logger === void 0 ? {} : { logger }
|
|
13105
|
+
}),
|
|
12567
13106
|
...shell === void 0 ? {} : { shell },
|
|
12568
13107
|
...installed === void 0 ? {} : { installed },
|
|
12569
13108
|
...ledger === void 0 ? {} : { ledger },
|
|
@@ -12598,7 +13137,7 @@ function createMarketService(options) {
|
|
|
12598
13137
|
return snapshot();
|
|
12599
13138
|
},
|
|
12600
13139
|
async install(unit) {
|
|
12601
|
-
const absent = (await capabilityOf(unit, true)).needs.filter((state) => state.status === "missing" && (state.kind === "extension" || state.kind === "lib"));
|
|
13140
|
+
const absent = (await capabilityOf(unit, true)).needs.filter((state) => state.status === "missing" && (state.kind === "extension" || state.kind === "lib" || state.kind === "bin"));
|
|
12602
13141
|
if (absent.length === 0) {
|
|
12603
13142
|
onInstalled?.(unit);
|
|
12604
13143
|
return entryOf(unit, true);
|
|
@@ -12610,6 +13149,8 @@ function createMarketService(options) {
|
|
|
12610
13149
|
}
|
|
12611
13150
|
if (state.command === void 0) throw new Error(`没有可执行的安装命令(${state.detail})`);
|
|
12612
13151
|
await run(state.command);
|
|
13152
|
+
ensureLibsOpencliPeer();
|
|
13153
|
+
ensureLibsBinOnPath();
|
|
12613
13154
|
}
|
|
12614
13155
|
await probeSharing({
|
|
12615
13156
|
action: "install",
|
|
@@ -12695,7 +13236,13 @@ function createMarketService(options) {
|
|
|
12695
13236
|
const unit = module.kind === "module" ? module.unit : void 0;
|
|
12696
13237
|
const manifest = unit === void 0 ? void 0 : catalog().find((item) => item.id === unit);
|
|
12697
13238
|
const before = unit === void 0 ? void 0 : await capabilityOf(unit, true);
|
|
12698
|
-
const change = await
|
|
13239
|
+
const change = await removeDshBundle({
|
|
13240
|
+
manager,
|
|
13241
|
+
pkg,
|
|
13242
|
+
profileDir,
|
|
13243
|
+
registryFix,
|
|
13244
|
+
...logger === void 0 ? {} : { logger }
|
|
13245
|
+
});
|
|
12699
13246
|
if (change.application !== "applied" || unit === void 0) return {
|
|
12700
13247
|
change,
|
|
12701
13248
|
keptFor: []
|
|
@@ -12994,6 +13541,85 @@ function marketDiscoverRoutes(discover) {
|
|
|
12994
13541
|
};
|
|
12995
13542
|
}
|
|
12996
13543
|
//#endregion
|
|
13544
|
+
//#region src/modules/panels/model/market-installed.ts
|
|
13545
|
+
async function transportOf(source, spec, probe, preferred) {
|
|
13546
|
+
if (source.type !== "git" || probe === void 0) return source;
|
|
13547
|
+
const candidates = gitTransportCandidates(source.url);
|
|
13548
|
+
if (candidates.length === 0) return source;
|
|
13549
|
+
const installed = spec === void 0 ? void 0 : gitAddressOf(spec);
|
|
13550
|
+
const first = preferred ?? installed?.kind;
|
|
13551
|
+
const ordered = first === void 0 ? candidates : [...candidates].sort((left, right) => Number(right.kind === first) - Number(left.kind === first));
|
|
13552
|
+
for (const candidate of ordered) if (await probe(candidate.url).catch(() => "unreachable") === "ok") return {
|
|
13553
|
+
...source,
|
|
13554
|
+
url: candidate.url
|
|
13555
|
+
};
|
|
13556
|
+
return {
|
|
13557
|
+
...source,
|
|
13558
|
+
url: ordered[0]?.url ?? source.url
|
|
13559
|
+
};
|
|
13560
|
+
}
|
|
13561
|
+
async function installedFromIndexes(input) {
|
|
13562
|
+
const modules = new Map(input.modules.map((module) => [module.pkg, module]));
|
|
13563
|
+
const rows = [];
|
|
13564
|
+
const seen = /* @__PURE__ */ new Set();
|
|
13565
|
+
for (const source of input.sources) {
|
|
13566
|
+
const index = input.cached(source);
|
|
13567
|
+
if (index === void 0) continue;
|
|
13568
|
+
for (const entry of index.plugins) {
|
|
13569
|
+
const module = modules.get(entry.name);
|
|
13570
|
+
const newest = entry.versions[0];
|
|
13571
|
+
if (module === void 0 || newest === void 0 || seen.has(entry.name)) continue;
|
|
13572
|
+
if (!input.isNewer(newest.version, module.version)) continue;
|
|
13573
|
+
seen.add(entry.name);
|
|
13574
|
+
rows.push({
|
|
13575
|
+
pkg: entry.name,
|
|
13576
|
+
unit: module.unit,
|
|
13577
|
+
version: newest.version,
|
|
13578
|
+
sourceId: source.id,
|
|
13579
|
+
sourceLabel: source.label,
|
|
13580
|
+
source: newest.source
|
|
13581
|
+
});
|
|
13582
|
+
const wanted = input.dependencyOf(entry.name);
|
|
13583
|
+
const host = gitAddressOf(source.url)?.host;
|
|
13584
|
+
const resolved = await transportOf(newest.source, wanted, input.probe, host === void 0 ? void 0 : input.preferred?.(host));
|
|
13585
|
+
rows[rows.length - 1] = {
|
|
13586
|
+
...rows[rows.length - 1],
|
|
13587
|
+
source: resolved
|
|
13588
|
+
};
|
|
13589
|
+
}
|
|
13590
|
+
}
|
|
13591
|
+
return rows;
|
|
13592
|
+
}
|
|
13593
|
+
function marketInstalledRoutes(read) {
|
|
13594
|
+
return { [MARKET_INSTALLED_ROUTE]: async () => ({
|
|
13595
|
+
ok: true,
|
|
13596
|
+
...await read()
|
|
13597
|
+
}) };
|
|
13598
|
+
}
|
|
13599
|
+
function transportOfSpec(spec) {
|
|
13600
|
+
const address = gitAddressOf(spec);
|
|
13601
|
+
return address === void 0 ? void 0 : {
|
|
13602
|
+
host: address.host,
|
|
13603
|
+
kind: address.kind
|
|
13604
|
+
};
|
|
13605
|
+
}
|
|
13606
|
+
//#endregion
|
|
13607
|
+
//#region src/shared/api/host/profile-dependencies.ts
|
|
13608
|
+
function dependencyReader(profileDir) {
|
|
13609
|
+
if (profileDir === null) return () => void 0;
|
|
13610
|
+
let dependencies;
|
|
13611
|
+
return (pkg) => {
|
|
13612
|
+
if (dependencies === void 0) try {
|
|
13613
|
+
const raw = JSON.parse(readFileSync(join(profileDir, "package.json"), "utf8")).dependencies;
|
|
13614
|
+
dependencies = typeof raw === "object" && raw !== null ? raw : {};
|
|
13615
|
+
} catch {
|
|
13616
|
+
dependencies = {};
|
|
13617
|
+
}
|
|
13618
|
+
const value = dependencies[pkg];
|
|
13619
|
+
return typeof value === "string" ? value : void 0;
|
|
13620
|
+
};
|
|
13621
|
+
}
|
|
13622
|
+
//#endregion
|
|
12997
13623
|
//#region src/modules/panels/model/market-sources-act.ts
|
|
12998
13624
|
function marketSourcesRoutes(store) {
|
|
12999
13625
|
return { [MARKET_SOURCES_ROUTE]: async () => ({
|
|
@@ -13581,6 +14207,15 @@ function createMarketDocReader(options) {
|
|
|
13581
14207
|
function fileOf(root) {
|
|
13582
14208
|
return join(root, "market-index", "sources.json");
|
|
13583
14209
|
}
|
|
14210
|
+
function transportsOf(raw) {
|
|
14211
|
+
const record = typeof raw === "object" && raw !== null ? raw : {};
|
|
14212
|
+
const found = {};
|
|
14213
|
+
for (const [host, kind] of Object.entries(record)) {
|
|
14214
|
+
if (host === "" || kind !== "https" && kind !== "ssh") continue;
|
|
14215
|
+
found[host] = kind;
|
|
14216
|
+
}
|
|
14217
|
+
return found;
|
|
14218
|
+
}
|
|
13584
14219
|
function readPayload(path, logger) {
|
|
13585
14220
|
let raw;
|
|
13586
14221
|
try {
|
|
@@ -13588,7 +14223,8 @@ function readPayload(path, logger) {
|
|
|
13588
14223
|
} catch {
|
|
13589
14224
|
return {
|
|
13590
14225
|
sources: [],
|
|
13591
|
-
selected: null
|
|
14226
|
+
selected: null,
|
|
14227
|
+
transports: {}
|
|
13592
14228
|
};
|
|
13593
14229
|
}
|
|
13594
14230
|
const record = typeof raw === "object" && raw !== null ? raw : {};
|
|
@@ -13605,7 +14241,8 @@ function readPayload(path, logger) {
|
|
|
13605
14241
|
const selectedRaw = record["selected"];
|
|
13606
14242
|
return {
|
|
13607
14243
|
sources,
|
|
13608
|
-
selected: typeof selectedRaw === "string" && sources.some((source) => source.id === selectedRaw) ? selectedRaw : sources[0]?.id ?? null
|
|
14244
|
+
selected: typeof selectedRaw === "string" && sources.some((source) => source.id === selectedRaw) ? selectedRaw : sources[0]?.id ?? null,
|
|
14245
|
+
transports: transportsOf(record["transports"])
|
|
13609
14246
|
};
|
|
13610
14247
|
}
|
|
13611
14248
|
async function writePayload(path, payload) {
|
|
@@ -13624,6 +14261,18 @@ function createMarketSourcesStore(options) {
|
|
|
13624
14261
|
function read() {
|
|
13625
14262
|
return readPayload(path, options.logger);
|
|
13626
14263
|
}
|
|
14264
|
+
async function rememberTransport(host, kind) {
|
|
14265
|
+
if (host === "") return;
|
|
14266
|
+
const current = read();
|
|
14267
|
+
const transports = {
|
|
14268
|
+
...current.transports ?? {},
|
|
14269
|
+
[host]: kind
|
|
14270
|
+
};
|
|
14271
|
+
await writePayload(path, {
|
|
14272
|
+
...current,
|
|
14273
|
+
transports
|
|
14274
|
+
});
|
|
14275
|
+
}
|
|
13627
14276
|
async function apply(action) {
|
|
13628
14277
|
const current = read();
|
|
13629
14278
|
if (action.action === "save") {
|
|
@@ -13655,7 +14304,8 @@ function createMarketSourcesStore(options) {
|
|
|
13655
14304
|
}
|
|
13656
14305
|
return {
|
|
13657
14306
|
read,
|
|
13658
|
-
apply
|
|
14307
|
+
apply,
|
|
14308
|
+
rememberTransport
|
|
13659
14309
|
};
|
|
13660
14310
|
}
|
|
13661
14311
|
//#endregion
|
|
@@ -15121,6 +15771,7 @@ var OWNED = [{
|
|
|
15121
15771
|
modules: () => supply.profileModules(profileDir),
|
|
15122
15772
|
workbench: (pkg) => supply.readProfilePackage(profileDir, pkg),
|
|
15123
15773
|
registries: supply.createRegistryNotebook(profileDir),
|
|
15774
|
+
profileDir,
|
|
15124
15775
|
ledger,
|
|
15125
15776
|
references: () => {
|
|
15126
15777
|
const gaps = supply.declarationGaps(profileDir);
|
|
@@ -15190,6 +15841,15 @@ var OWNED = [{
|
|
|
15190
15841
|
...diyRecordRoutes(record),
|
|
15191
15842
|
...marketDiscoverRoutes(discover),
|
|
15192
15843
|
...marketSourcesRoutes(marketSources),
|
|
15844
|
+
...marketInstalledRoutes(async () => ({ modules: await installedFromIndexes({
|
|
15845
|
+
sources: marketSources.read().sources,
|
|
15846
|
+
cached: (source) => marketIndex.cached(source)?.index,
|
|
15847
|
+
modules: supply.profileModules(profileDir),
|
|
15848
|
+
dependencyOf: dependencyReader(profileDir),
|
|
15849
|
+
isNewer: supply.isNewerVersion,
|
|
15850
|
+
preferred: (host) => marketSources.read().transports?.[host],
|
|
15851
|
+
...shell === void 0 ? {} : { probe: (url) => probeGitAddressCached(shell, url, { timeoutMs: 4e3 }).then((one) => one.status) }
|
|
15852
|
+
}) })),
|
|
15193
15853
|
...publishPreviewRoutes(workshopPathOf(ctx), (unit) => inferredAuthorOf(workshopPathOf(ctx), unit, shell))
|
|
15194
15854
|
},
|
|
15195
15855
|
registerTools: () => [
|
|
@@ -15227,7 +15887,15 @@ var OWNED = [{
|
|
|
15227
15887
|
handle: (payload) => applyPanelsPref(panels, payload)
|
|
15228
15888
|
},
|
|
15229
15889
|
provision: {
|
|
15230
|
-
handle: (request) =>
|
|
15890
|
+
handle: async (request) => {
|
|
15891
|
+
const result = await dispatchMarket(market, request);
|
|
15892
|
+
const application = result.change?.application;
|
|
15893
|
+
if (request.spec !== void 0 && (application === "applied" || application === "restart-required" || application === "overridden")) {
|
|
15894
|
+
const used = transportOfSpec(request.spec);
|
|
15895
|
+
if (used !== void 0) await marketSources.rememberTransport(used.host, used.kind);
|
|
15896
|
+
}
|
|
15897
|
+
return result;
|
|
15898
|
+
},
|
|
15231
15899
|
affectedUnits: (request) => affectedUnitsOf(manifests(), request)
|
|
15232
15900
|
}
|
|
15233
15901
|
};
|
|
@@ -16640,6 +17308,8 @@ async function apply(ctx, config) {
|
|
|
16640
17308
|
}
|
|
16641
17309
|
if (shell === void 0) ctx.logger?.warn?.("[dsh-workbench] 没有 shell 服务:需要执行缝的套件会降级");
|
|
16642
17310
|
else {
|
|
17311
|
+
ensureLibsOpencliPeer();
|
|
17312
|
+
if (ensureLibsBinOnPath() && ctx.logger?.info !== void 0) ctx.logger.info("[dsh-workbench] PATH 前插共用依赖的 bin 目录:装了的包,命令直接可用");
|
|
16643
17313
|
adapters.probe.registerPreflight({
|
|
16644
17314
|
routes,
|
|
16645
17315
|
ctx,
|