@dsh-workbench/core 0.1.15 → 0.1.17
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/README.md +19 -0
- package/bin/dsh-workbench +5 -1
- package/client.js +660 -128
- package/cordis.patch.yml +22 -0
- package/dist/client/client.body.js +660 -128
- package/dist/client/index.js +660 -128
- package/dist/host/index.js +1733 -324
- package/dist/types/app/client.d.ts +124 -9
- package/dist/types/app/layouts/market/add-module-text.d.ts +2 -0
- package/dist/types/app/layouts/market/use-add-module.d.ts +4 -3
- package/dist/types/app/layouts/market/use-module-versions.d.ts +2 -0
- package/dist/types/app/layouts/rightbar/MarketView.d.ts +3 -1
- package/dist/types/app/layouts/rightbar/__tests__/market-rows-plugins.test.d.ts +1 -0
- package/dist/types/app/layouts/rightbar/market-mock.d.ts +9 -0
- package/dist/types/app/layouts/rightbar/market-plugin-rows.d.ts +8 -0
- package/dist/types/app/layouts/rightbar/market-rows.d.ts +14 -3
- package/dist/types/app/layouts/rightbar/registry.d.ts +0 -1
- package/dist/types/app/units/__tests__/manifests.test.d.ts +1 -0
- package/dist/types/app/units/fixture-unit-mock.d.ts +3 -3
- package/dist/types/app/units/host.d.ts +2 -2
- package/dist/types/app/units/manifests.d.ts +5 -0
- package/dist/types/app/units/registry.d.ts +10 -2
- package/dist/types/modules/panels/__tests__/market-env.test.d.ts +1 -0
- package/dist/types/modules/panels/__tests__/market-plugin.test.d.ts +1 -0
- package/dist/types/modules/panels/__tests__/market-route.test.d.ts +1 -0
- package/dist/types/modules/panels/__tests__/plugin-actions.test.d.ts +1 -0
- package/dist/types/modules/panels/__tests__/plugin-ledger.test.d.ts +1 -0
- package/dist/types/modules/panels/__tests__/plugin-section.test.d.ts +1 -0
- package/dist/types/modules/panels/__tests__/scope.test.d.ts +1 -0
- package/dist/types/modules/panels/__tests__/service-release.test.d.ts +1 -0
- package/dist/types/modules/panels/index.d.ts +2 -2
- package/dist/types/modules/panels/model/config.d.ts +27 -9
- package/dist/types/modules/panels/model/market-dispatch.d.ts +9 -0
- package/dist/types/modules/panels/model/market-env.d.ts +20 -0
- package/dist/types/modules/panels/model/market-route.d.ts +26 -0
- package/dist/types/modules/panels/model/market-types.d.ts +71 -0
- package/dist/types/modules/panels/model/market.d.ts +7 -66
- package/dist/types/modules/panels/model/plugin-actions.d.ts +50 -0
- package/dist/types/modules/panels/model/plugin-ledger.d.ts +22 -0
- package/dist/types/modules/panels/model/plugin-name.d.ts +8 -0
- package/dist/types/modules/panels/model/plugin-name.test.d.ts +1 -0
- package/dist/types/modules/panels/model/plugin-section.d.ts +13 -0
- package/dist/types/modules/panels/model/service.d.ts +8 -6
- package/dist/types/shared/api/bundle-install.d.ts +8 -1
- package/dist/types/shared/api/host/__tests__/registry-declaration.test.d.ts +1 -0
- package/dist/types/shared/api/host/__tests__/unit-roster-facts.test.d.ts +1 -0
- package/dist/types/shared/api/host/bundle-routes.d.ts +1 -0
- package/dist/types/shared/api/host/installed-module.d.ts +7 -0
- package/dist/types/shared/api/host/mount-units.d.ts +5 -3
- package/dist/types/shared/api/host/package-content.d.ts +6 -0
- package/dist/types/shared/api/host/package-content.test.d.ts +1 -0
- package/dist/types/shared/api/host/package-kind.d.ts +21 -0
- package/dist/types/shared/api/host/package-kind.test.d.ts +1 -0
- package/dist/types/shared/api/host/package-route.d.ts +23 -0
- package/dist/types/shared/api/host/package-route.test.d.ts +1 -0
- package/dist/types/shared/api/host/registry-declaration.d.ts +23 -0
- package/dist/types/shared/api/host/runtime-seed.d.ts +40 -0
- package/dist/types/shared/api/host/runtime-seed.test.d.ts +1 -0
- package/dist/types/shared/api/host/unit-registry.d.ts +33 -4
- package/dist/types/shared/api/host/unit-roster-facts.d.ts +5 -0
- package/dist/types/shared/api/installed-plugins.d.ts +12 -0
- package/dist/types/shared/api/unit-manifests.d.ts +5 -0
- package/dist/types/shared/api/unit-manifests.test.d.ts +1 -0
- package/dist/types/shared/hooks/bundle-queries.d.ts +2 -0
- package/dist/types/shared/hooks/use-facts.d.ts +1 -0
- package/docs/HANDOFF.md +82 -44
- package/docs/OPERATIONS.md +22 -1
- package/package.json +5 -4
- package/scripts/seed-workshop.sh +216 -0
- package/skills/workbench-module-diy/BUILD.md +5 -5
- package/skills/workbench-module-diy/DESIGN.md +7 -1
- package/skills/workbench-module-diy/REVIEW.md +3 -2
- package/skills/workbench-module-diy/TOOLING.md +2 -2
- package/skills/workbench-module-diy/TOOLS.md +9 -2
package/dist/host/index.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
-
import fs, { existsSync, readFileSync, readdirSync, realpathSync, statSync } from "node:fs";
|
|
3
|
-
import path, { join, resolve, sep } from "node:path";
|
|
2
|
+
import fs, { chmodSync, closeSync, existsSync, mkdirSync, openSync, readFileSync, readdirSync, readlinkSync, realpathSync, rmSync, statSync, symlinkSync, writeFileSync } from "node:fs";
|
|
3
|
+
import path, { delimiter, dirname, join, resolve, sep } from "node:path";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import { spawn } from "node:child_process";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
4
7
|
//#region ../shared/dist/config/constants.js
|
|
5
8
|
var PLUGIN_ID = "@dsh-workbench/core";
|
|
6
9
|
var API_PREFIX = "/workbench/api";
|
|
@@ -47,6 +50,121 @@ function parseMarkers(text) {
|
|
|
47
50
|
}
|
|
48
51
|
return refs;
|
|
49
52
|
}
|
|
53
|
+
var SUPPORTED_API_VERSIONS = [1];
|
|
54
|
+
var DECLARATION_KEYS = [
|
|
55
|
+
"apiVersion",
|
|
56
|
+
"unit",
|
|
57
|
+
"card",
|
|
58
|
+
"tools",
|
|
59
|
+
"host",
|
|
60
|
+
"env"
|
|
61
|
+
];
|
|
62
|
+
var DECLARATION_REQUIRED_KEYS = [
|
|
63
|
+
"apiVersion",
|
|
64
|
+
"unit",
|
|
65
|
+
"card",
|
|
66
|
+
"tools",
|
|
67
|
+
"host"
|
|
68
|
+
];
|
|
69
|
+
var CARD_KEYS = ["titleKey", "order"];
|
|
70
|
+
var TOOL_KEYS = ["name", "writes"];
|
|
71
|
+
var HOST_KEYS = ["inject", "optional"];
|
|
72
|
+
function text$1(value) {
|
|
73
|
+
return typeof value === "string" ? value.trim() : "";
|
|
74
|
+
}
|
|
75
|
+
function isRecord(value) {
|
|
76
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
77
|
+
}
|
|
78
|
+
function outsideKeys(value, allowed) {
|
|
79
|
+
return Object.keys(value).filter((key) => !allowed.includes(key));
|
|
80
|
+
}
|
|
81
|
+
function listOf(keys) {
|
|
82
|
+
return keys.map((key) => JSON.stringify(key)).join(" / ");
|
|
83
|
+
}
|
|
84
|
+
function stringArrayProblems(at, value) {
|
|
85
|
+
if (!Array.isArray(value)) return [`${at} 要是一串字符串,现在是 ${JSON.stringify(value)}`];
|
|
86
|
+
return value.flatMap((item, index) => text$1(item) === "" ? [`${at}[${index}] 不是非空字符串:${JSON.stringify(item)}`] : []);
|
|
87
|
+
}
|
|
88
|
+
function declarationProblemsOf(subject, raw) {
|
|
89
|
+
if (!isRecord(raw)) return [`${subject}: dsh.workbench 不是一个对象 —— ${JSON.stringify(raw)}`];
|
|
90
|
+
const version = raw.apiVersion;
|
|
91
|
+
if (version === void 0) return [`${subject}: dsh.workbench 没有 apiVersion —— 协议从 v1 起,写 \`"apiVersion": 1\`。缺它 = 外壳不认这个模块(ADR-0038 §2.3)`];
|
|
92
|
+
if (!Number.isInteger(version) || !SUPPORTED_API_VERSIONS.includes(version)) return [`${subject}: dsh.workbench.apiVersion 是 ${JSON.stringify(version)},这里只认 ${SUPPORTED_API_VERSIONS.join(" / ")}(要整数)`];
|
|
93
|
+
const problems = [];
|
|
94
|
+
const unknown = outsideKeys(raw, DECLARATION_KEYS);
|
|
95
|
+
if (unknown.length > 0) problems.push(`${subject}: dsh.workbench 里有协议 v1 不认识的键 ${listOf(unknown)} —— 只有 ${listOf(DECLARATION_KEYS)}。(拼错的键会被外壳**静默忽略**:那句声明等于没写)`);
|
|
96
|
+
for (const key of DECLARATION_REQUIRED_KEYS) if (raw[key] === void 0) problems.push(`${subject}: dsh.workbench 缺 ${key}` + (key === "card" ? "(这个模块没有卡片就写 `\"card\": null`)" : ""));
|
|
97
|
+
const card = raw.card;
|
|
98
|
+
if (card !== void 0 && card !== null) {
|
|
99
|
+
if (!isRecord(card)) problems.push(`${subject}: dsh.workbench.card 要么是对象、要么是 null,现在是 ${JSON.stringify(card)}`);
|
|
100
|
+
else {
|
|
101
|
+
const extra = outsideKeys(card, CARD_KEYS);
|
|
102
|
+
if (extra.length > 0) problems.push(`${subject}: dsh.workbench.card 里有不认的键 ${listOf(extra)} —— card 只有 ${listOf(CARD_KEYS)}(卡片默认显不显示由运行期的面板配置决定,清单里没有这一位)`);
|
|
103
|
+
if (text$1(card.titleKey) === "") problems.push(`${subject}: dsh.workbench.card.titleKey 要是非空字符串(面板那一格的文案键)`);
|
|
104
|
+
if (typeof card.order !== "number" || !Number.isFinite(card.order)) problems.push(`${subject}: dsh.workbench.card.order 要是数字,现在是 ${JSON.stringify(card.order)}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
const tools = raw.tools;
|
|
108
|
+
if (tools !== void 0) {
|
|
109
|
+
if (!Array.isArray(tools)) problems.push(`${subject}: dsh.workbench.tools 要是一串声明,现在是 ${JSON.stringify(tools)}`);
|
|
110
|
+
else {
|
|
111
|
+
const seen = /* @__PURE__ */ new Set();
|
|
112
|
+
tools.forEach((tool, index) => {
|
|
113
|
+
const at = `dsh.workbench.tools[${index}]`;
|
|
114
|
+
if (!isRecord(tool)) {
|
|
115
|
+
problems.push(`${subject}: ${at} 要是对象,现在是 ${JSON.stringify(tool)}`);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const extra = outsideKeys(tool, TOOL_KEYS);
|
|
119
|
+
if (extra.length > 0) problems.push(`${subject}: ${at} 里有不认的键 ${listOf(extra)} —— tools 只有 ${listOf(TOOL_KEYS)}`);
|
|
120
|
+
const name = text$1(tool.name);
|
|
121
|
+
if (name === "") problems.push(`${subject}: ${at}.name 要是非空字符串`);
|
|
122
|
+
else if (seen.has(name)) problems.push(`${subject}: ${at}.name「${name}」与前面那条撞了 —— 工具名会进 dsh 的工具表,不许重`);
|
|
123
|
+
else seen.add(name);
|
|
124
|
+
if (typeof tool.writes !== "boolean") problems.push(`${subject}: ${at}.writes 要是 true / false(它是"这会写"的唯一声明,缺了审批无从认领)`);
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
const host = raw.host;
|
|
129
|
+
if (host !== void 0) {
|
|
130
|
+
if (!isRecord(host)) problems.push(`${subject}: dsh.workbench.host 要是对象,现在是 ${JSON.stringify(host)}`);
|
|
131
|
+
else {
|
|
132
|
+
const extra = outsideKeys(host, HOST_KEYS);
|
|
133
|
+
if (extra.length > 0) problems.push(`${subject}: dsh.workbench.host 里有不认的键 ${listOf(extra)} —— host 只有 ${listOf(HOST_KEYS)}`);
|
|
134
|
+
if (host.inject === void 0) problems.push(`${subject}: dsh.workbench.host.inject 没写 —— 它声明这个模块要外壳的哪几个服务,外壳靠它判"能不能挂"。一个都不要也要写 \`"inject": []\``);
|
|
135
|
+
else problems.push(...stringArrayProblems(`${subject}: dsh.workbench.host.inject`, host.inject));
|
|
136
|
+
if (host.optional !== void 0) problems.push(...stringArrayProblems(`${subject}: dsh.workbench.host.optional`, host.optional));
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (raw.env !== void 0 && !isRecord(raw.env)) problems.push(`${subject}: dsh.workbench.env 要是对象(command / plugins / libs)—— 语义由门禁的 env 判据查,这里只查形状`);
|
|
140
|
+
return problems;
|
|
141
|
+
}
|
|
142
|
+
function readDeclaration(subject, raw) {
|
|
143
|
+
const problems = declarationProblemsOf(subject, raw);
|
|
144
|
+
if (problems.length > 0) return { problems };
|
|
145
|
+
const declaration = raw;
|
|
146
|
+
const card = declaration.card;
|
|
147
|
+
return {
|
|
148
|
+
declaration,
|
|
149
|
+
problems: [],
|
|
150
|
+
manifest: {
|
|
151
|
+
id: text$1(declaration.unit),
|
|
152
|
+
card: card === null ? null : {
|
|
153
|
+
titleKey: text$1(card.titleKey),
|
|
154
|
+
order: card.order
|
|
155
|
+
},
|
|
156
|
+
tools: declaration.tools.map((tool) => ({
|
|
157
|
+
name: text$1(tool.name),
|
|
158
|
+
writes: tool.writes
|
|
159
|
+
})),
|
|
160
|
+
host: {
|
|
161
|
+
inject: [...declaration.host.inject ?? []],
|
|
162
|
+
...declaration.host.optional === void 0 ? {} : { optional: [...declaration.host.optional] }
|
|
163
|
+
},
|
|
164
|
+
...declaration.env === void 0 ? {} : { env: declaration.env }
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
}
|
|
50
168
|
function unitFactScope(unit) {
|
|
51
169
|
return `${unit}*`;
|
|
52
170
|
}
|
|
@@ -149,27 +267,27 @@ var PERMISSION_CODES = /* @__PURE__ */ new Set([
|
|
|
149
267
|
"NO_PERMISSION",
|
|
150
268
|
"EACCES"
|
|
151
269
|
]);
|
|
152
|
-
function asRecord$
|
|
270
|
+
function asRecord$9(value) {
|
|
153
271
|
return typeof value === "object" && value !== null ? value : void 0;
|
|
154
272
|
}
|
|
155
|
-
function textOf$
|
|
273
|
+
function textOf$4(value) {
|
|
156
274
|
return typeof value === "string" ? value.trim() : "";
|
|
157
275
|
}
|
|
158
276
|
function codeOf(page) {
|
|
159
|
-
const record = asRecord$
|
|
160
|
-
return record === void 0 ? "" : textOf$
|
|
277
|
+
const record = asRecord$9(page);
|
|
278
|
+
return record === void 0 ? "" : textOf$4(record["code"]);
|
|
161
279
|
}
|
|
162
280
|
function messageOf$1(page) {
|
|
163
|
-
const record = asRecord$
|
|
164
|
-
return record === void 0 ? "" : textOf$
|
|
281
|
+
const record = asRecord$9(page);
|
|
282
|
+
return record === void 0 ? "" : textOf$4(record["message"]);
|
|
165
283
|
}
|
|
166
284
|
function hintOf$1(page) {
|
|
167
|
-
const record = asRecord$
|
|
168
|
-
const hint = record === void 0 ? "" : textOf$
|
|
285
|
+
const record = asRecord$9(page);
|
|
286
|
+
const hint = record === void 0 ? "" : textOf$4(record["hint"]);
|
|
169
287
|
return hint === "" ? void 0 : hint;
|
|
170
288
|
}
|
|
171
289
|
function pageFailed(page) {
|
|
172
|
-
const record = asRecord$
|
|
290
|
+
const record = asRecord$9(page);
|
|
173
291
|
return record !== void 0 && record["ok"] === false;
|
|
174
292
|
}
|
|
175
293
|
function blockerFor(code) {
|
|
@@ -393,41 +511,41 @@ var DEFAULT_SETTLE_MS = 2500;
|
|
|
393
511
|
var URL_TIMEOUT_MS = 8e3;
|
|
394
512
|
var DEFAULT_LIMIT = 50;
|
|
395
513
|
var DEFAULT_TEXT_MAX = 200;
|
|
396
|
-
function asRecord$
|
|
514
|
+
function asRecord$8(value) {
|
|
397
515
|
return typeof value === "object" && value !== null ? value : void 0;
|
|
398
516
|
}
|
|
399
|
-
function textOf$
|
|
517
|
+
function textOf$3(value) {
|
|
400
518
|
return typeof value === "string" ? value.trim() : "";
|
|
401
519
|
}
|
|
402
520
|
function entriesOf(value) {
|
|
403
|
-
const record = asRecord$
|
|
521
|
+
const record = asRecord$8(value);
|
|
404
522
|
const raw = record === void 0 ? void 0 : record["entries"];
|
|
405
523
|
if (!Array.isArray(raw)) return [];
|
|
406
524
|
const entries = [];
|
|
407
525
|
for (const one of raw) {
|
|
408
|
-
const item = asRecord$
|
|
526
|
+
const item = asRecord$8(one);
|
|
409
527
|
if (item === void 0) continue;
|
|
410
528
|
const nth = typeof item["nth"] === "number" ? item["nth"] : entries.length;
|
|
411
529
|
entries.push({
|
|
412
|
-
text: textOf$
|
|
413
|
-
tag: textOf$
|
|
530
|
+
text: textOf$3(item["text"]),
|
|
531
|
+
tag: textOf$3(item["tag"]),
|
|
414
532
|
nth
|
|
415
533
|
});
|
|
416
534
|
}
|
|
417
535
|
return entries;
|
|
418
536
|
}
|
|
419
537
|
function reasonOf(stdout, stderr) {
|
|
420
|
-
const parsed = asRecord$
|
|
421
|
-
const error = parsed === void 0 ? void 0 : asRecord$
|
|
422
|
-
const message = error === void 0 ? "" : textOf$
|
|
538
|
+
const parsed = asRecord$8(parseJsonLoose(stdout));
|
|
539
|
+
const error = parsed === void 0 ? void 0 : asRecord$8(parsed["error"]);
|
|
540
|
+
const message = error === void 0 ? "" : textOf$3(error["message"]);
|
|
423
541
|
if (message !== "") return message;
|
|
424
542
|
const lines = (stderr.trim() === "" ? stdout : stderr).split("\n").map((one) => one.trim()).filter((one) => one !== "");
|
|
425
543
|
return (lines[lines.length - 1] ?? "").slice(-200);
|
|
426
544
|
}
|
|
427
545
|
function hintOf(stdout) {
|
|
428
|
-
const parsed = asRecord$
|
|
429
|
-
const error = parsed === void 0 ? void 0 : asRecord$
|
|
430
|
-
const hint = error === void 0 ? "" : textOf$
|
|
546
|
+
const parsed = asRecord$8(parseJsonLoose(stdout));
|
|
547
|
+
const error = parsed === void 0 ? void 0 : asRecord$8(parsed["error"]);
|
|
548
|
+
const hint = error === void 0 ? "" : textOf$3(error["hint"]);
|
|
431
549
|
return hint === "" ? void 0 : hint;
|
|
432
550
|
}
|
|
433
551
|
function sameOrigin(left, right) {
|
|
@@ -440,8 +558,8 @@ function sameOrigin(left, right) {
|
|
|
440
558
|
function urlOf(stdout) {
|
|
441
559
|
const bare = (stdout.split("\n").map((one) => one.trim()).filter((one) => one !== "")[0] ?? "").replace(/^url:\s*/i, "").trim().replace(/^["']+|["']+$/g, "");
|
|
442
560
|
if (bare.startsWith("{")) {
|
|
443
|
-
const parsed = asRecord$
|
|
444
|
-
const url = parsed === void 0 ? "" : textOf$
|
|
561
|
+
const parsed = asRecord$8(parseJsonLoose(bare));
|
|
562
|
+
const url = parsed === void 0 ? "" : textOf$3(parsed["url"]);
|
|
445
563
|
if (url !== "") return url;
|
|
446
564
|
}
|
|
447
565
|
return bare;
|
|
@@ -664,7 +782,9 @@ var PROVISION_ACTIONS = [
|
|
|
664
782
|
"update-bundle",
|
|
665
783
|
"remove-bundle",
|
|
666
784
|
"cancel-install",
|
|
667
|
-
"update-dependency"
|
|
785
|
+
"update-dependency",
|
|
786
|
+
"update-plugin",
|
|
787
|
+
"uninstall-plugin"
|
|
668
788
|
];
|
|
669
789
|
var DEPENDENCY_ACTIONS = ["update-dependency"];
|
|
670
790
|
function isDependencyAction(action) {
|
|
@@ -674,6 +794,7 @@ function provisionTargetOf(action) {
|
|
|
674
794
|
if (action === "update-dependency") return "id";
|
|
675
795
|
if (action === "install-bundle") return "spec";
|
|
676
796
|
if (action === "remove-bundle") return "name";
|
|
797
|
+
if (action === "update-plugin" || action === "uninstall-plugin") return "name";
|
|
677
798
|
if (action === "cancel-install") return "requestId";
|
|
678
799
|
return "unit";
|
|
679
800
|
}
|
|
@@ -684,7 +805,7 @@ var TARGET_CODE = {
|
|
|
684
805
|
name: "workbench/no-name",
|
|
685
806
|
requestId: "workbench/no-request-id"
|
|
686
807
|
};
|
|
687
|
-
function asRecord$
|
|
808
|
+
function asRecord$7(value) {
|
|
688
809
|
return typeof value === "object" && value !== null ? value : {};
|
|
689
810
|
}
|
|
690
811
|
function stringsOf$2(value) {
|
|
@@ -705,6 +826,10 @@ function requestOf$1(action, value) {
|
|
|
705
826
|
action,
|
|
706
827
|
name: value
|
|
707
828
|
};
|
|
829
|
+
if (action === "update-plugin" || action === "uninstall-plugin") return {
|
|
830
|
+
action,
|
|
831
|
+
name: value
|
|
832
|
+
};
|
|
708
833
|
if (action === "cancel-install") return {
|
|
709
834
|
action,
|
|
710
835
|
requestId: value
|
|
@@ -715,7 +840,7 @@ function requestOf$1(action, value) {
|
|
|
715
840
|
};
|
|
716
841
|
}
|
|
717
842
|
function parseProvisionBody(body) {
|
|
718
|
-
const raw = asRecord$
|
|
843
|
+
const raw = asRecord$7(body);
|
|
719
844
|
const action = raw.action;
|
|
720
845
|
if (typeof action !== "string" || !PROVISION_ACTIONS.includes(action)) return {
|
|
721
846
|
ok: false,
|
|
@@ -1257,6 +1382,9 @@ function createLongTaskHost(options) {
|
|
|
1257
1382
|
function isolatedRoot() {
|
|
1258
1383
|
return `${dshHome()}/workbench/opencli`;
|
|
1259
1384
|
}
|
|
1385
|
+
function isolatedHome() {
|
|
1386
|
+
return `${isolatedRoot()}/home`;
|
|
1387
|
+
}
|
|
1260
1388
|
var OPENCLI_NPM_PACKAGE = "@jackwener/opencli";
|
|
1261
1389
|
function toolchainDir() {
|
|
1262
1390
|
return `${isolatedRoot()}/toolchain`;
|
|
@@ -1302,6 +1430,105 @@ function isolatedInstallCommand() {
|
|
|
1302
1430
|
return `npm i --prefix '${toolchainDir()}' ${OPENCLI_NPM_PACKAGE}@${OPENCLI_PIN} --cache '${npmCacheDir()}'`;
|
|
1303
1431
|
}
|
|
1304
1432
|
//#endregion
|
|
1433
|
+
//#region ../shared/dist/host/plugin-ledger.js
|
|
1434
|
+
var PLUGIN_LEDGER_FILE = "plugins.json";
|
|
1435
|
+
function ledgerKeyOf(entry) {
|
|
1436
|
+
return `${entry.opencliHome}\u0000${entry.package}`;
|
|
1437
|
+
}
|
|
1438
|
+
function textOf$2(value) {
|
|
1439
|
+
if (typeof value !== "string") return void 0;
|
|
1440
|
+
const trimmed = value.trim();
|
|
1441
|
+
return trimmed === "" ? void 0 : trimmed;
|
|
1442
|
+
}
|
|
1443
|
+
function entryOf(value) {
|
|
1444
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
1445
|
+
const box = value;
|
|
1446
|
+
const opencliHome = textOf$2(box["opencliHome"]);
|
|
1447
|
+
const pkg = textOf$2(box["package"]);
|
|
1448
|
+
const installedAt = textOf$2(box["installedAt"]);
|
|
1449
|
+
if (opencliHome === void 0 || pkg === void 0 || installedAt === void 0) return void 0;
|
|
1450
|
+
const name = textOf$2(box["name"]);
|
|
1451
|
+
const registry = textOf$2(box["registry"]);
|
|
1452
|
+
const releasedAt = textOf$2(box["releasedAt"]);
|
|
1453
|
+
return {
|
|
1454
|
+
opencliHome,
|
|
1455
|
+
package: pkg,
|
|
1456
|
+
installedAt,
|
|
1457
|
+
...name === void 0 ? {} : { name },
|
|
1458
|
+
...registry === void 0 ? {} : { registry },
|
|
1459
|
+
...releasedAt === void 0 ? {} : { releasedAt },
|
|
1460
|
+
...box["local"] === true ? { local: true } : {}
|
|
1461
|
+
};
|
|
1462
|
+
}
|
|
1463
|
+
function parsePluginLedger(raw) {
|
|
1464
|
+
if (typeof raw !== "object" || raw === null) return [];
|
|
1465
|
+
const list = raw["entries"];
|
|
1466
|
+
if (!Array.isArray(list)) return [];
|
|
1467
|
+
const entries = [];
|
|
1468
|
+
for (const item of list) {
|
|
1469
|
+
const entry = entryOf(item);
|
|
1470
|
+
if (entry === void 0) continue;
|
|
1471
|
+
if (entries.some((known) => ledgerKeyOf(known) === ledgerKeyOf(entry))) continue;
|
|
1472
|
+
entries.push(entry);
|
|
1473
|
+
}
|
|
1474
|
+
return entries;
|
|
1475
|
+
}
|
|
1476
|
+
function readPluginLedger(store) {
|
|
1477
|
+
return parsePluginLedger(store.read(PLUGIN_LEDGER_FILE));
|
|
1478
|
+
}
|
|
1479
|
+
function writePluginLedger(store, entries) {
|
|
1480
|
+
if (entries.length === 0) {
|
|
1481
|
+
store.remove(PLUGIN_LEDGER_FILE);
|
|
1482
|
+
return;
|
|
1483
|
+
}
|
|
1484
|
+
store.write(PLUGIN_LEDGER_FILE, { entries });
|
|
1485
|
+
}
|
|
1486
|
+
function pluginEntriesOfHome(entries, opencliHome) {
|
|
1487
|
+
return entries.filter((entry) => entry.opencliHome === opencliHome);
|
|
1488
|
+
}
|
|
1489
|
+
function isHeldEntry(entry) {
|
|
1490
|
+
return entry.releasedAt === void 0;
|
|
1491
|
+
}
|
|
1492
|
+
function heldEntriesOfHome(entries, opencliHome) {
|
|
1493
|
+
return pluginEntriesOfHome(entries, opencliHome).filter(isHeldEntry);
|
|
1494
|
+
}
|
|
1495
|
+
function heldEntryOfPlugin(entries, opencliHome, plugin) {
|
|
1496
|
+
const key = plugin.trim();
|
|
1497
|
+
if (key === "") return void 0;
|
|
1498
|
+
return heldEntriesOfHome(entries, opencliHome).find((entry) => entry.name === key || entry.package === key);
|
|
1499
|
+
}
|
|
1500
|
+
function withReleasedEntry(entries, opencliHome, pkg, releasedAt) {
|
|
1501
|
+
const entry = pluginEntryOf(entries, opencliHome, pkg);
|
|
1502
|
+
if (entry === void 0 || entry.releasedAt !== void 0) return [...entries];
|
|
1503
|
+
return withPluginEntry(entries, {
|
|
1504
|
+
...entry,
|
|
1505
|
+
releasedAt
|
|
1506
|
+
});
|
|
1507
|
+
}
|
|
1508
|
+
function pluginRetentionOf(options) {
|
|
1509
|
+
if (options.held) return "keep-held";
|
|
1510
|
+
if (!options.trustworthy) return "keep-unsure";
|
|
1511
|
+
return options.claimedBy.length > 0 ? "keep-claimed" : "remove";
|
|
1512
|
+
}
|
|
1513
|
+
function pluginEntryOf(entries, opencliHome, pkg) {
|
|
1514
|
+
const key = ledgerKeyOf({
|
|
1515
|
+
opencliHome,
|
|
1516
|
+
package: pkg
|
|
1517
|
+
});
|
|
1518
|
+
return entries.find((entry) => ledgerKeyOf(entry) === key);
|
|
1519
|
+
}
|
|
1520
|
+
function withPluginEntry(entries, entry) {
|
|
1521
|
+
const key = ledgerKeyOf(entry);
|
|
1522
|
+
return [...entries.filter((item) => ledgerKeyOf(item) !== key), entry];
|
|
1523
|
+
}
|
|
1524
|
+
function withoutPluginEntry(entries, opencliHome, pkg) {
|
|
1525
|
+
const key = ledgerKeyOf({
|
|
1526
|
+
opencliHome,
|
|
1527
|
+
package: pkg
|
|
1528
|
+
});
|
|
1529
|
+
return entries.filter((entry) => ledgerKeyOf(entry) !== key);
|
|
1530
|
+
}
|
|
1531
|
+
//#endregion
|
|
1305
1532
|
//#region ../shared/dist/host/plugin-source.js
|
|
1306
1533
|
function mergeLines(commands) {
|
|
1307
1534
|
return commands.map((item) => item.optional === true ? `(${item.line} || true)` : item.line).join(" && ");
|
|
@@ -1589,6 +1816,7 @@ var PROBLEMS = [
|
|
|
1589
1816
|
"not-found",
|
|
1590
1817
|
"not-a-package",
|
|
1591
1818
|
"not-a-bundle",
|
|
1819
|
+
"not-a-workbench-module",
|
|
1592
1820
|
"network",
|
|
1593
1821
|
"unknown"
|
|
1594
1822
|
];
|
|
@@ -1615,7 +1843,7 @@ var CANCELLATIONS = [
|
|
|
1615
1843
|
"too-late",
|
|
1616
1844
|
"not-running"
|
|
1617
1845
|
];
|
|
1618
|
-
function asRecord$
|
|
1846
|
+
function asRecord$6(value) {
|
|
1619
1847
|
return typeof value === "object" && value !== null ? value : void 0;
|
|
1620
1848
|
}
|
|
1621
1849
|
function asText$4(value) {
|
|
@@ -1632,7 +1860,7 @@ function asTextArray(value) {
|
|
|
1632
1860
|
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
1633
1861
|
}
|
|
1634
1862
|
function asRegistries(raw) {
|
|
1635
|
-
const record = asRecord$
|
|
1863
|
+
const record = asRecord$6(raw);
|
|
1636
1864
|
if (record === void 0) return null;
|
|
1637
1865
|
const registry = asRegistry(record.registry);
|
|
1638
1866
|
if (registry === void 0) return null;
|
|
@@ -1643,7 +1871,7 @@ function asRegistries(raw) {
|
|
|
1643
1871
|
};
|
|
1644
1872
|
}
|
|
1645
1873
|
function asPackageResult(raw) {
|
|
1646
|
-
const record = asRecord$
|
|
1874
|
+
const record = asRecord$6(raw);
|
|
1647
1875
|
if (record === void 0) return void 0;
|
|
1648
1876
|
const kind = pick(FAILURE_KINDS, record.kind);
|
|
1649
1877
|
return {
|
|
@@ -1655,12 +1883,12 @@ function asPackageResult(raw) {
|
|
|
1655
1883
|
};
|
|
1656
1884
|
}
|
|
1657
1885
|
function asChange(raw) {
|
|
1658
|
-
const record = asRecord$
|
|
1886
|
+
const record = asRecord$6(raw);
|
|
1659
1887
|
if (record === void 0) return null;
|
|
1660
1888
|
const application = pick(APPLICATIONS, record.application);
|
|
1661
1889
|
const stage = pick(STAGES, record.stage);
|
|
1662
1890
|
if (application === void 0 || stage === void 0) return null;
|
|
1663
|
-
const error = asRecord$
|
|
1891
|
+
const error = asRecord$6(record.error);
|
|
1664
1892
|
const packageResult = asPackageResult(record.packageResult);
|
|
1665
1893
|
const pendingBuilds = asTextArray(record.pendingBuilds);
|
|
1666
1894
|
const failedAt = pick(["registry", "spec-host"], record.failedAt);
|
|
@@ -1681,7 +1909,7 @@ function asChange(raw) {
|
|
|
1681
1909
|
};
|
|
1682
1910
|
}
|
|
1683
1911
|
function asInspection(raw) {
|
|
1684
|
-
const record = asRecord$
|
|
1912
|
+
const record = asRecord$6(raw);
|
|
1685
1913
|
if (record === void 0) return null;
|
|
1686
1914
|
const status = asText$4(record.status);
|
|
1687
1915
|
if (status === "refused") {
|
|
@@ -1739,14 +1967,14 @@ function readBundleManager(ctx) {
|
|
|
1739
1967
|
},
|
|
1740
1968
|
async cancel(requestId) {
|
|
1741
1969
|
if (typeof face.cancelInstall !== "function") return null;
|
|
1742
|
-
return pick(CANCELLATIONS, (asRecord$
|
|
1970
|
+
return pick(CANCELLATIONS, (asRecord$6(await face.cancelInstall(requestId)) ?? {}).status) ?? null;
|
|
1743
1971
|
}
|
|
1744
1972
|
};
|
|
1745
1973
|
}
|
|
1746
1974
|
//#endregion
|
|
1747
1975
|
//#region src/shared/api/host/installed-module.ts
|
|
1748
1976
|
var PACKAGE_NAME$1 = /^(?:@[a-z0-9][a-z0-9._~-]*\/)?[a-z0-9][a-z0-9._~-]*$/;
|
|
1749
|
-
function asRecord$
|
|
1977
|
+
function asRecord$5(value) {
|
|
1750
1978
|
return typeof value === "object" && value !== null ? value : void 0;
|
|
1751
1979
|
}
|
|
1752
1980
|
function asText$3(value) {
|
|
@@ -1763,18 +1991,19 @@ function profileDirOf(ctx) {
|
|
|
1763
1991
|
function stringsOf$1(value) {
|
|
1764
1992
|
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
1765
1993
|
}
|
|
1766
|
-
function
|
|
1767
|
-
if (profileDir === null) return
|
|
1768
|
-
let manifest;
|
|
1994
|
+
function profileManifestOf(profileDir) {
|
|
1995
|
+
if (profileDir === null) return void 0;
|
|
1769
1996
|
try {
|
|
1770
|
-
|
|
1997
|
+
return asRecord$5(JSON.parse(fs.readFileSync(path.join(profileDir, "package.json"), "utf8")));
|
|
1771
1998
|
} catch {
|
|
1772
|
-
return
|
|
1999
|
+
return;
|
|
1773
2000
|
}
|
|
1774
|
-
|
|
2001
|
+
}
|
|
2002
|
+
function profileModules(profileDir) {
|
|
2003
|
+
const record = profileManifestOf(profileDir);
|
|
1775
2004
|
if (record === void 0) return [];
|
|
1776
|
-
const bundles = stringsOf$1(asRecord$
|
|
1777
|
-
const dependencies = asRecord$
|
|
2005
|
+
const bundles = stringsOf$1(asRecord$5(asRecord$5(record.dsh)?.profile)?.bundles);
|
|
2006
|
+
const dependencies = asRecord$5(record.dependencies) ?? {};
|
|
1778
2007
|
const names = bundles.length > 0 ? bundles : Object.keys(dependencies);
|
|
1779
2008
|
const found = [];
|
|
1780
2009
|
for (const pkg of names) {
|
|
@@ -1808,6 +2037,27 @@ function registryFromLockfile(profileDir, pkg) {
|
|
|
1808
2037
|
if (cut <= 8) return void 0;
|
|
1809
2038
|
return tarball.slice(0, cut);
|
|
1810
2039
|
}
|
|
2040
|
+
function moduleDeclarationOf(profileDir, pkg) {
|
|
2041
|
+
if (profileDir === null || !isPackageName(pkg)) return void 0;
|
|
2042
|
+
try {
|
|
2043
|
+
const text = fs.readFileSync(path.join(profileDir, "node_modules", pkg, "package.json"), "utf8");
|
|
2044
|
+
return asRecord$5(asRecord$5(asRecord$5(JSON.parse(text))?.dsh)?.workbench);
|
|
2045
|
+
} catch {
|
|
2046
|
+
return;
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
2049
|
+
function declarationGaps(profileDir) {
|
|
2050
|
+
if (profileManifestOf(profileDir) === void 0) return void 0;
|
|
2051
|
+
const gaps = [];
|
|
2052
|
+
for (const module of profileModules(profileDir)) {
|
|
2053
|
+
const problems = declarationProblemsOf(module.pkg, moduleDeclarationOf(profileDir, module.pkg));
|
|
2054
|
+
if (problems.length > 0) gaps.push({
|
|
2055
|
+
pkg: module.pkg,
|
|
2056
|
+
problems
|
|
2057
|
+
});
|
|
2058
|
+
}
|
|
2059
|
+
return gaps;
|
|
2060
|
+
}
|
|
1811
2061
|
function readInstalledModule(profileDir, pkg) {
|
|
1812
2062
|
if (profileDir === null || !isPackageName(pkg)) return {
|
|
1813
2063
|
kind: "unknown",
|
|
@@ -1822,18 +2072,18 @@ function readInstalledModule(profileDir, pkg) {
|
|
|
1822
2072
|
pkg
|
|
1823
2073
|
};
|
|
1824
2074
|
}
|
|
1825
|
-
const record = asRecord$
|
|
2075
|
+
const record = asRecord$5(manifest);
|
|
1826
2076
|
if (record === void 0) return {
|
|
1827
2077
|
kind: "unknown",
|
|
1828
2078
|
pkg
|
|
1829
2079
|
};
|
|
1830
|
-
const declared = asRecord$
|
|
2080
|
+
const declared = asRecord$5(asRecord$5(record.dsh)?.workbench);
|
|
1831
2081
|
const unit = declared === void 0 ? "" : asText$3(declared.unit);
|
|
1832
2082
|
if (unit === "") return {
|
|
1833
2083
|
kind: "not-module",
|
|
1834
2084
|
pkg
|
|
1835
2085
|
};
|
|
1836
|
-
const declaredRegistry = asText$3(asRecord$
|
|
2086
|
+
const declaredRegistry = asText$3(asRecord$5(record.publishConfig)?.registry);
|
|
1837
2087
|
const registry = declaredRegistry === "" ? registryFromLockfile(profileDir, pkg) : declaredRegistry;
|
|
1838
2088
|
return {
|
|
1839
2089
|
kind: "module",
|
|
@@ -1969,7 +2219,7 @@ function workshopPathOf(ctx) {
|
|
|
1969
2219
|
}
|
|
1970
2220
|
//#endregion
|
|
1971
2221
|
//#region src/shared/api/host/diy-workshop.ts
|
|
1972
|
-
function asRecord$
|
|
2222
|
+
function asRecord$4(value) {
|
|
1973
2223
|
return typeof value === "object" && value !== null ? value : void 0;
|
|
1974
2224
|
}
|
|
1975
2225
|
function asText$2(value) {
|
|
@@ -1995,8 +2245,8 @@ function workshopModules(packagesDir, installed = []) {
|
|
|
1995
2245
|
} catch {
|
|
1996
2246
|
return [];
|
|
1997
2247
|
}
|
|
1998
|
-
const record = asRecord$
|
|
1999
|
-
const declared = record === void 0 ? void 0 : asRecord$
|
|
2248
|
+
const record = asRecord$4(manifest);
|
|
2249
|
+
const declared = record === void 0 ? void 0 : asRecord$4(asRecord$4(record.dsh)?.workbench);
|
|
2000
2250
|
const unit = declared === void 0 ? "" : asText$2(declared.unit);
|
|
2001
2251
|
if (unit === "") return [];
|
|
2002
2252
|
return [{
|
|
@@ -2019,7 +2269,6 @@ var MODULES_TOOL = "workbench_modules";
|
|
|
2019
2269
|
var DIY_TOOL = "workbench_diy";
|
|
2020
2270
|
var panelsUnit = {
|
|
2021
2271
|
id: "panels",
|
|
2022
|
-
kind: "ui",
|
|
2023
2272
|
card: null,
|
|
2024
2273
|
tools: [
|
|
2025
2274
|
{
|
|
@@ -2054,9 +2303,10 @@ function actionOf$1(call) {
|
|
|
2054
2303
|
const raw = call.arguments;
|
|
2055
2304
|
if (typeof raw !== "object" || raw === null) return {};
|
|
2056
2305
|
const value = raw;
|
|
2306
|
+
const target = typeof value.plugin === "string" ? value.plugin : typeof value.name === "string" ? value.name : typeof value.id === "string" ? value.id : typeof value.unit === "string" ? value.unit : void 0;
|
|
2057
2307
|
return {
|
|
2058
2308
|
...typeof value.action === "string" ? { action: value.action } : {},
|
|
2059
|
-
...
|
|
2309
|
+
...target === void 0 ? {} : { id: target }
|
|
2060
2310
|
};
|
|
2061
2311
|
}
|
|
2062
2312
|
function whatOf(id) {
|
|
@@ -2077,6 +2327,17 @@ var panelsApproval = (call) => {
|
|
|
2077
2327
|
const what = whatOf(id);
|
|
2078
2328
|
if (call.name === "workbench_modules") {
|
|
2079
2329
|
if (readAction) return READ;
|
|
2330
|
+
if (action === "update-plugin" || action === "uninstall-plugin") {
|
|
2331
|
+
const verb = action === "update-plugin" ? "更新" : "卸载";
|
|
2332
|
+
const verbEn = action === "update-plugin" ? "update" : "remove";
|
|
2333
|
+
return askOf(`workbench_modules ${action} ${id ?? ""} changes an opencli plugin on this machine`, {
|
|
2334
|
+
en: `it ${verbEn}s that opencli plugin on this machine. Allow?`,
|
|
2335
|
+
zh: `会${verb}这个 opencli 插件。允许吗?`
|
|
2336
|
+
}, {
|
|
2337
|
+
en: `The agent wants to ${verbEn} the opencli plugin ${what} (it changes what is installed on this machine). Allow?`,
|
|
2338
|
+
zh: `agent 想${verb} opencli 插件 ${what}(会改这台机器上装的东西)。允许吗?`
|
|
2339
|
+
});
|
|
2340
|
+
}
|
|
2080
2341
|
if (action === "update-dependency") return askOf(`workbench_modules update-dependency ${id ?? ""} upgrades a shared dependency on this machine`, {
|
|
2081
2342
|
en: "it upgrades that shared dependency on this machine. Allow?",
|
|
2082
2343
|
zh: `会升级共用依赖 ${what}。允许吗?`
|
|
@@ -2143,13 +2404,102 @@ var panelsApproval = (call) => {
|
|
|
2143
2404
|
});
|
|
2144
2405
|
};
|
|
2145
2406
|
//#endregion
|
|
2407
|
+
//#region src/modules/panels/model/config.ts
|
|
2408
|
+
var EMPTY_BOARD = {
|
|
2409
|
+
shown: [],
|
|
2410
|
+
hidden: []
|
|
2411
|
+
};
|
|
2412
|
+
var BOARD_TARGETS = ["workspace", "machine"];
|
|
2413
|
+
var DEFAULT_BOARD_TARGET = "workspace";
|
|
2414
|
+
function resolveScope(query) {
|
|
2415
|
+
const { id, boards } = query;
|
|
2416
|
+
const board = boards.workspace ?? boards.machine;
|
|
2417
|
+
if (board === null) return false;
|
|
2418
|
+
if (board.hidden.includes(id)) return false;
|
|
2419
|
+
return board.shown.includes(id);
|
|
2420
|
+
}
|
|
2421
|
+
function strings(raw) {
|
|
2422
|
+
return Array.isArray(raw) ? raw.filter((item) => typeof item === "string") : [];
|
|
2423
|
+
}
|
|
2424
|
+
function parseBoard(raw) {
|
|
2425
|
+
if (typeof raw !== "object" || raw === null) return EMPTY_BOARD;
|
|
2426
|
+
const value = raw;
|
|
2427
|
+
return {
|
|
2428
|
+
shown: strings(value.shown),
|
|
2429
|
+
hidden: strings(value.hidden)
|
|
2430
|
+
};
|
|
2431
|
+
}
|
|
2432
|
+
function parseReleased(raw) {
|
|
2433
|
+
if (typeof raw !== "object" || raw === null) return [];
|
|
2434
|
+
return strings(raw.ids);
|
|
2435
|
+
}
|
|
2436
|
+
function resolvePanels(manifests, boards) {
|
|
2437
|
+
const ordered = boards.workspace?.shown ?? boards.machine?.shown ?? [];
|
|
2438
|
+
const rank = new Map(ordered.map((id, index) => [id, index]));
|
|
2439
|
+
return manifests.filter((manifest) => manifest.card !== null).map((manifest) => {
|
|
2440
|
+
const on = resolveScope({
|
|
2441
|
+
id: manifest.id,
|
|
2442
|
+
boards
|
|
2443
|
+
});
|
|
2444
|
+
const order = rank.get(manifest.id) ?? ordered.length + (manifest.card?.order ?? 0);
|
|
2445
|
+
return {
|
|
2446
|
+
id: manifest.id,
|
|
2447
|
+
titleKey: manifest.card?.titleKey ?? manifest.id,
|
|
2448
|
+
order,
|
|
2449
|
+
shown: on,
|
|
2450
|
+
source: on && (boards.workspace?.shown.includes(manifest.id) ?? false) ? "user" : "default",
|
|
2451
|
+
hasCard: true
|
|
2452
|
+
};
|
|
2453
|
+
}).sort((a, b) => a.order - b.order || a.id.localeCompare(b.id));
|
|
2454
|
+
}
|
|
2455
|
+
function withoutReleased(released, id) {
|
|
2456
|
+
return released.filter((item) => item !== id);
|
|
2457
|
+
}
|
|
2458
|
+
function withReleased(released, id) {
|
|
2459
|
+
return [.../* @__PURE__ */ new Set([...released, id])];
|
|
2460
|
+
}
|
|
2461
|
+
function added(list, id) {
|
|
2462
|
+
return list.includes(id) ? [...list] : [...list, id];
|
|
2463
|
+
}
|
|
2464
|
+
function dropped(list, id) {
|
|
2465
|
+
return list.filter((item) => item !== id);
|
|
2466
|
+
}
|
|
2467
|
+
function withShown(board, id) {
|
|
2468
|
+
return {
|
|
2469
|
+
shown: added(board.shown, id),
|
|
2470
|
+
hidden: dropped(board.hidden, id)
|
|
2471
|
+
};
|
|
2472
|
+
}
|
|
2473
|
+
function withHidden(board, id) {
|
|
2474
|
+
return {
|
|
2475
|
+
shown: dropped(board.shown, id),
|
|
2476
|
+
hidden: added(board.hidden, id)
|
|
2477
|
+
};
|
|
2478
|
+
}
|
|
2479
|
+
function withoutPanel(board, id) {
|
|
2480
|
+
return {
|
|
2481
|
+
shown: dropped(board.shown, id),
|
|
2482
|
+
hidden: dropped(board.hidden, id)
|
|
2483
|
+
};
|
|
2484
|
+
}
|
|
2485
|
+
function withMoved(board, id, index) {
|
|
2486
|
+
const current = [...board.shown];
|
|
2487
|
+
const from = current.indexOf(id);
|
|
2488
|
+
const max = from === -1 ? current.length : current.length - 1;
|
|
2489
|
+
const to = Math.max(0, Math.min(max, Math.trunc(index)));
|
|
2490
|
+
if (from === -1) current.splice(to, 0, id);
|
|
2491
|
+
else if (from !== to) current.splice(to, 0, ...current.splice(from, 1));
|
|
2492
|
+
return {
|
|
2493
|
+
shown: current,
|
|
2494
|
+
hidden: dropped(board.hidden, id)
|
|
2495
|
+
};
|
|
2496
|
+
}
|
|
2497
|
+
//#endregion
|
|
2146
2498
|
//#region src/shared/api/host/diy-facts.ts
|
|
2147
2499
|
function asExported(dir) {
|
|
2148
2500
|
try {
|
|
2149
2501
|
const manifest = JSON.parse(readFileSync(join(dir, "package.json"), "utf8"));
|
|
2150
|
-
|
|
2151
|
-
if (built.length > 0) return built;
|
|
2152
|
-
return Object.values(manifest.dsh?.workbench?.faces ?? {}).filter((one) => typeof one === "string");
|
|
2502
|
+
return (typeof manifest.exports === "object" && manifest.exports !== null ? Object.values(manifest.exports) : []).filter((one) => typeof one === "string");
|
|
2153
2503
|
} catch {
|
|
2154
2504
|
return [];
|
|
2155
2505
|
}
|
|
@@ -2454,7 +2804,7 @@ var OUTPUT_SCHEMA$2 = {
|
|
|
2454
2804
|
},
|
|
2455
2805
|
additionalProperties: true
|
|
2456
2806
|
};
|
|
2457
|
-
function asRecord$
|
|
2807
|
+
function asRecord$3(value) {
|
|
2458
2808
|
return typeof value === "object" && value !== null ? value : void 0;
|
|
2459
2809
|
}
|
|
2460
2810
|
function asNumber(value) {
|
|
@@ -2470,7 +2820,7 @@ function asText$1(value) {
|
|
|
2470
2820
|
function plainJson(value) {
|
|
2471
2821
|
return JSON.parse(JSON.stringify(value));
|
|
2472
2822
|
}
|
|
2473
|
-
function verdictOf(change) {
|
|
2823
|
+
function verdictOf$1(change) {
|
|
2474
2824
|
if (change.application === "applied") return "装好了,已生效";
|
|
2475
2825
|
if (change.application === "restart-required") return "装好了,要重启一次才生效(装新模块、同名重装、替换已有包都走这一支)";
|
|
2476
2826
|
if (change.application === "overridden") return "被更高层的声明盖住了,这次没生效";
|
|
@@ -2614,7 +2964,7 @@ function registerDiyTools(ctx, market, panels) {
|
|
|
2614
2964
|
}
|
|
2615
2965
|
},
|
|
2616
2966
|
async execute(raw, exec) {
|
|
2617
|
-
const args = asRecord$
|
|
2967
|
+
const args = asRecord$3(raw) ?? {};
|
|
2618
2968
|
const action = asText$1(args.action) || "list";
|
|
2619
2969
|
if (!ACTIONS$1.includes(action)) throw new Error(`不认识的 action:${action}(可用:${ACTIONS$1.join(" / ")})`);
|
|
2620
2970
|
const packagesDir = workshopPackagesDir(ctx);
|
|
@@ -2739,7 +3089,7 @@ function registerDiyTools(ctx, market, panels) {
|
|
|
2739
3089
|
});
|
|
2740
3090
|
}
|
|
2741
3091
|
const outcome = await market.installBundle(found.dir);
|
|
2742
|
-
const verdict = verdictOf(outcome.change);
|
|
3092
|
+
const verdict = verdictOf$1(outcome.change);
|
|
2743
3093
|
const restartRequired = outcome.change.application === "restart-required";
|
|
2744
3094
|
const panel = outcome.change.application === "applied" ? showOrReason(panels, unit, workspace) : {
|
|
2745
3095
|
ok: false,
|
|
@@ -2984,77 +3334,633 @@ function sourceProblemText(problem, env) {
|
|
|
2984
3334
|
return "这份安装没声明安装方式 —— 没声明就不猜地址,只能自己动手。";
|
|
2985
3335
|
}
|
|
2986
3336
|
//#endregion
|
|
2987
|
-
//#region src/
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
return at > 0 ? spec.slice(0, at) : spec;
|
|
3337
|
+
//#region src/shared/api/host/registry-declaration.ts
|
|
3338
|
+
var cache$1 = /* @__PURE__ */ new Map();
|
|
3339
|
+
function asRecord$2(value) {
|
|
3340
|
+
return typeof value === "object" && value !== null ? value : void 0;
|
|
2992
3341
|
}
|
|
2993
|
-
function
|
|
2994
|
-
const
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3342
|
+
function localDirOf(spec) {
|
|
3343
|
+
const dir = spec.trim().replace(/^(?:file|link):/, "");
|
|
3344
|
+
return path.isAbsolute(dir) ? dir : void 0;
|
|
3345
|
+
}
|
|
3346
|
+
function packageManifestOf(dir) {
|
|
3347
|
+
try {
|
|
3348
|
+
return asRecord$2(JSON.parse(fs.readFileSync(path.join(dir, "package.json"), "utf8")));
|
|
3349
|
+
} catch {
|
|
3350
|
+
return;
|
|
3001
3351
|
}
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3352
|
+
}
|
|
3353
|
+
function dshRecordOf(dir) {
|
|
3354
|
+
return packageManifestOf(dir)?.dsh;
|
|
3355
|
+
}
|
|
3356
|
+
function packageNameOf(dir) {
|
|
3357
|
+
const name = packageManifestOf(dir)?.name;
|
|
3358
|
+
return typeof name === "string" && name.trim() !== "" ? name.trim() : void 0;
|
|
3359
|
+
}
|
|
3360
|
+
function pathDeclarationOf(spec) {
|
|
3361
|
+
const dir = localDirOf(spec);
|
|
3362
|
+
return dir === void 0 ? void 0 : asRecord$2(dshRecordOf(dir))?.workbench;
|
|
3363
|
+
}
|
|
3364
|
+
async function registryDshOf(options) {
|
|
3365
|
+
const { shell, name, registry, force = false } = options;
|
|
3366
|
+
const key = `${registry ?? ""}|${name}`;
|
|
3367
|
+
const hit = cache$1.get(key);
|
|
3368
|
+
if (!force && hit !== void 0 && Date.now() - hit.at < 6e5) return {
|
|
3369
|
+
readable: true,
|
|
3370
|
+
raw: hit.raw
|
|
3371
|
+
};
|
|
3372
|
+
const at = registry === void 0 ? "" : ` --registry ${quoteShellArg(registry)}`;
|
|
3373
|
+
const result = await runShell(shell, `npm view ${quoteShellArg(name)} dsh --json${at} --cache ${quoteShellArg(npmCacheDir())}`, { timeoutMs: VERSION_CHECK_TIMEOUT_MS }).catch(() => void 0);
|
|
3374
|
+
const parsed = result?.exitCode === 0 ? parseJsonLoose(result.stdout) : void 0;
|
|
3375
|
+
if (parsed === void 0) return {
|
|
3376
|
+
readable: false,
|
|
3377
|
+
raw: void 0
|
|
3378
|
+
};
|
|
3379
|
+
cache$1.set(key, {
|
|
3380
|
+
at: Date.now(),
|
|
3381
|
+
raw: parsed
|
|
3382
|
+
});
|
|
3383
|
+
return {
|
|
3384
|
+
readable: true,
|
|
3385
|
+
raw: parsed
|
|
3386
|
+
};
|
|
3387
|
+
}
|
|
3388
|
+
async function registryDeclarationOf(options) {
|
|
3389
|
+
const found = await registryDshOf(options);
|
|
3390
|
+
return found.readable ? asRecord$2(found.raw)?.workbench : void 0;
|
|
3391
|
+
}
|
|
3392
|
+
function declarationRefusalOf(name, raw) {
|
|
3393
|
+
if (raw === void 0) return [];
|
|
3394
|
+
return declarationProblemsOf(name, raw);
|
|
3395
|
+
}
|
|
3396
|
+
//#endregion
|
|
3397
|
+
//#region src/shared/api/host/package-kind.ts
|
|
3398
|
+
var OPENCLI_MANIFEST_FILE = "opencli-plugin.json";
|
|
3399
|
+
var VERSION = /^[a-zA-Z0-9][a-zA-Z0-9._+-]*$/;
|
|
3400
|
+
function splitNpmSpec(spec) {
|
|
3401
|
+
const value = spec.trim();
|
|
3402
|
+
const at = value.lastIndexOf("@");
|
|
3403
|
+
const name = at > 0 ? value.slice(0, at) : value;
|
|
3404
|
+
const version = at > 0 ? value.slice(at + 1) : "";
|
|
3405
|
+
if (!isPackageName(name)) return void 0;
|
|
3406
|
+
if (version !== "" && !VERSION.test(version)) return void 0;
|
|
3407
|
+
return {
|
|
3408
|
+
name,
|
|
3409
|
+
...version === "" ? {} : { version }
|
|
3410
|
+
};
|
|
3411
|
+
}
|
|
3412
|
+
function asRecord$1(value) {
|
|
3413
|
+
return typeof value === "object" && value !== null ? value : void 0;
|
|
3414
|
+
}
|
|
3415
|
+
function isCommandFile(name) {
|
|
3416
|
+
if (name.endsWith(".d.ts") || name.endsWith(".test.ts")) return false;
|
|
3417
|
+
if (name.endsWith(".d.js")) return false;
|
|
3418
|
+
return name.endsWith(".ts") || name.endsWith(".js");
|
|
3419
|
+
}
|
|
3420
|
+
function pluginManifestOf(dir) {
|
|
3421
|
+
try {
|
|
3422
|
+
const parsed = JSON.parse(fs.readFileSync(path.join(dir, OPENCLI_MANIFEST_FILE), "utf8"));
|
|
3423
|
+
if (Array.isArray(parsed)) return void 0;
|
|
3424
|
+
return asRecord$1(parsed);
|
|
3425
|
+
} catch {
|
|
3426
|
+
return;
|
|
3010
3427
|
}
|
|
3011
|
-
|
|
3012
|
-
|
|
3428
|
+
}
|
|
3429
|
+
function hasCommandFile(dir) {
|
|
3430
|
+
try {
|
|
3431
|
+
return fs.readdirSync(dir).some(isCommandFile);
|
|
3432
|
+
} catch {
|
|
3433
|
+
return false;
|
|
3013
3434
|
}
|
|
3014
|
-
|
|
3015
|
-
|
|
3435
|
+
}
|
|
3436
|
+
function pluginAt(dir) {
|
|
3437
|
+
const manifest = pluginManifestOf(dir);
|
|
3438
|
+
if (manifest === void 0 || !hasCommandFile(dir)) return void 0;
|
|
3439
|
+
const name = manifest.name;
|
|
3440
|
+
return typeof name === "string" && name.trim() !== "" ? { name: name.trim() } : {};
|
|
3441
|
+
}
|
|
3442
|
+
function dshMarkersOf(raw) {
|
|
3443
|
+
const record = asRecord$1(raw);
|
|
3444
|
+
return record !== void 0 && ("bundle" in record || "workbench" in record);
|
|
3445
|
+
}
|
|
3446
|
+
function verdictOf(dir) {
|
|
3447
|
+
const plugin = pluginAt(dir);
|
|
3448
|
+
if (packageManifestOf(dir) === void 0 && plugin === void 0) return void 0;
|
|
3449
|
+
const dsh = dshMarkersOf(dshRecordOf(dir));
|
|
3450
|
+
const kind = plugin !== void 0 && dsh ? "both" : plugin !== void 0 ? "opencli-plugin" : dsh ? "dsh-bundle" : "neither";
|
|
3451
|
+
const pkg = packageNameOf(dir);
|
|
3452
|
+
return {
|
|
3453
|
+
kind,
|
|
3454
|
+
...pkg === void 0 ? {} : { pkg },
|
|
3455
|
+
...plugin?.name === void 0 ? {} : { pluginName: plugin.name }
|
|
3456
|
+
};
|
|
3457
|
+
}
|
|
3458
|
+
function refusalReason() {
|
|
3459
|
+
return "这个包既不是工作台的模块,也不是 opencli 的插件,装了也用不上";
|
|
3460
|
+
}
|
|
3461
|
+
//#endregion
|
|
3462
|
+
//#region src/shared/api/host/package-content.ts
|
|
3463
|
+
function packedFileFrom(stdout, root) {
|
|
3464
|
+
const parsed = parseJsonLoose(stdout);
|
|
3465
|
+
if (Array.isArray(parsed)) {
|
|
3466
|
+
const first = parsed[0];
|
|
3467
|
+
const filename = typeof first === "object" && first !== null ? first.filename : void 0;
|
|
3468
|
+
if (typeof filename === "string" && filename.trim() !== "") {
|
|
3469
|
+
const at = path.join(root, path.basename(filename.trim()));
|
|
3470
|
+
if (fs.existsSync(at)) return at;
|
|
3471
|
+
}
|
|
3016
3472
|
}
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3473
|
+
try {
|
|
3474
|
+
const found = fs.readdirSync(root).filter((name) => name.endsWith(".tgz"));
|
|
3475
|
+
return found.length === 1 ? path.join(root, found[0] ?? "") : void 0;
|
|
3476
|
+
} catch {
|
|
3477
|
+
return;
|
|
3022
3478
|
}
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
purpose: need.purpose
|
|
3036
|
-
})),
|
|
3037
|
-
capability: await capabilityOf(unit, force)
|
|
3038
|
-
};
|
|
3479
|
+
}
|
|
3480
|
+
async function withPackageDir(options, visit) {
|
|
3481
|
+
const local = localDirOf(options.spec);
|
|
3482
|
+
if (local !== void 0) return visit(local);
|
|
3483
|
+
if (splitNpmSpec(options.spec) === void 0) return void 0;
|
|
3484
|
+
const shell = options.shell;
|
|
3485
|
+
if (shell === void 0) return void 0;
|
|
3486
|
+
let root;
|
|
3487
|
+
try {
|
|
3488
|
+
root = fs.mkdtempSync(path.join(os.tmpdir(), "wb-package-"));
|
|
3489
|
+
} catch {
|
|
3490
|
+
return;
|
|
3039
3491
|
}
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
const
|
|
3043
|
-
if (
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
}
|
|
3492
|
+
try {
|
|
3493
|
+
const at = options.registry === void 0 ? "" : ` --registry ${quoteShellArg(options.registry)}`;
|
|
3494
|
+
const packed = await runShell(shell, `npm pack ${quoteShellArg(options.spec)} --ignore-scripts --json --pack-destination ${quoteShellArg(root)} --cache ${quoteShellArg(npmCacheDir())}${at}`, { timeoutMs: INSTALL_TIMEOUT_MS }).catch(() => void 0);
|
|
3495
|
+
if (packed === void 0 || packed.exitCode !== 0) return void 0;
|
|
3496
|
+
const file = packedFileFrom(packed.stdout, root);
|
|
3497
|
+
if (file === void 0) return void 0;
|
|
3498
|
+
const unpacked = await runShell(shell, `tar -xzf ${quoteShellArg(file)} -C ${quoteShellArg(root)}`, { timeoutMs: INSTALL_TIMEOUT_MS }).catch(() => void 0);
|
|
3499
|
+
if (unpacked === void 0 || unpacked.exitCode !== 0) return void 0;
|
|
3500
|
+
const dir = path.join(root, "package");
|
|
3501
|
+
if (!fs.existsSync(dir)) return void 0;
|
|
3502
|
+
return visit(dir);
|
|
3503
|
+
} finally {
|
|
3504
|
+
fs.rmSync(root, {
|
|
3505
|
+
recursive: true,
|
|
3506
|
+
force: true
|
|
3507
|
+
});
|
|
3047
3508
|
}
|
|
3048
|
-
|
|
3049
|
-
|
|
3509
|
+
}
|
|
3510
|
+
//#endregion
|
|
3511
|
+
//#region src/shared/api/host/package-route.ts
|
|
3512
|
+
var LOCAL_PLUGIN_ENV = "DSH_WB_OPENCLI_PLUGIN_PATH";
|
|
3513
|
+
var PLUGIN_REGISTRY_ENV = "DSH_WB_OPENCLI_PLUGIN_REGISTRY";
|
|
3514
|
+
var cache = /* @__PURE__ */ new Map();
|
|
3515
|
+
function routeOfVerdict(verdict, fallback) {
|
|
3516
|
+
const pkg = verdict.pkg ?? fallback;
|
|
3517
|
+
if (verdict.kind === "neither") return {
|
|
3518
|
+
kind: "neither",
|
|
3519
|
+
...pkg === void 0 ? {} : { pkg }
|
|
3520
|
+
};
|
|
3521
|
+
if (verdict.kind === "dsh-bundle" || verdict.kind === "both") return { kind: "dsh-bundle" };
|
|
3522
|
+
return {
|
|
3523
|
+
kind: "opencli-plugin",
|
|
3524
|
+
...pkg === void 0 ? {} : { pkg },
|
|
3525
|
+
...verdict.pluginName === void 0 ? {} : { pluginName: verdict.pluginName }
|
|
3526
|
+
};
|
|
3527
|
+
}
|
|
3528
|
+
async function packageRouteOf(options) {
|
|
3529
|
+
const local = localDirOf(options.spec);
|
|
3530
|
+
if (local !== void 0) {
|
|
3531
|
+
const verdict = verdictOf(local);
|
|
3532
|
+
return verdict === void 0 ? void 0 : routeOfVerdict(verdict, splitNpmSpec(options.spec)?.name);
|
|
3050
3533
|
}
|
|
3051
|
-
|
|
3052
|
-
|
|
3534
|
+
const ref = splitNpmSpec(options.spec);
|
|
3535
|
+
if (ref === void 0) return void 0;
|
|
3536
|
+
const shell = options.shell;
|
|
3537
|
+
const registry = options.registry;
|
|
3538
|
+
if (shell === void 0 || registry === void 0) return void 0;
|
|
3539
|
+
const key = `${registry}|${options.spec}`;
|
|
3540
|
+
const hit = cache.get(key);
|
|
3541
|
+
if (options.force !== true && hit !== void 0 && Date.now() - hit.at < 6e5) return hit.route ?? void 0;
|
|
3542
|
+
const found = await registryDshOf({
|
|
3543
|
+
shell,
|
|
3544
|
+
name: options.spec,
|
|
3545
|
+
registry
|
|
3546
|
+
});
|
|
3547
|
+
let route;
|
|
3548
|
+
if (found.readable && dshMarkersOf(found.raw)) route = { kind: "dsh-bundle" };
|
|
3549
|
+
else {
|
|
3550
|
+
const verdict = await withPackageDir({
|
|
3551
|
+
shell,
|
|
3552
|
+
spec: options.spec,
|
|
3553
|
+
registry
|
|
3554
|
+
}, (dir) => verdictOf(dir));
|
|
3555
|
+
route = verdict === void 0 ? void 0 : routeOfVerdict(verdict, ref.name);
|
|
3053
3556
|
}
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3557
|
+
cache.set(key, {
|
|
3558
|
+
at: Date.now(),
|
|
3559
|
+
route: route ?? null
|
|
3560
|
+
});
|
|
3561
|
+
return route;
|
|
3562
|
+
}
|
|
3563
|
+
function pluginInstallPlan(options) {
|
|
3564
|
+
const local = localDirOf(options.spec);
|
|
3565
|
+
if (local !== void 0) return resolvePluginSource({
|
|
3566
|
+
kind: "local",
|
|
3567
|
+
env: LOCAL_PLUGIN_ENV
|
|
3568
|
+
}, { [LOCAL_PLUGIN_ENV]: local }, { command: options.command });
|
|
3569
|
+
const ref = splitNpmSpec(options.spec);
|
|
3570
|
+
if (ref === void 0) return {
|
|
3571
|
+
ok: false,
|
|
3572
|
+
problem: "malformed"
|
|
3573
|
+
};
|
|
3574
|
+
return resolvePluginSource({
|
|
3575
|
+
kind: "npm",
|
|
3576
|
+
package: ref.name,
|
|
3577
|
+
registryEnv: PLUGIN_REGISTRY_ENV,
|
|
3578
|
+
...options.registry === void 0 ? {} : { registryDefault: options.registry }
|
|
3579
|
+
}, { [PLUGIN_REGISTRY_ENV]: options.registry }, {
|
|
3580
|
+
command: options.command,
|
|
3581
|
+
...options.pluginName === void 0 ? {} : { plugin: options.pluginName },
|
|
3582
|
+
...ref.version === void 0 ? {} : { version: ref.version },
|
|
3583
|
+
mode: "install"
|
|
3584
|
+
});
|
|
3585
|
+
}
|
|
3586
|
+
//#endregion
|
|
3587
|
+
//#region src/modules/panels/model/plugin-name.ts
|
|
3588
|
+
function pluginDirOf(entry) {
|
|
3589
|
+
return entry.local === true ? entry.package : `${vendorPluginsDir()}/${entry.package}`;
|
|
3590
|
+
}
|
|
3591
|
+
function pluginOnDisk(entry) {
|
|
3592
|
+
return pluginAt(pluginDirOf(entry)) !== void 0;
|
|
3593
|
+
}
|
|
3594
|
+
function pluginNameOf(entry) {
|
|
3595
|
+
if (entry.name !== void 0) return entry.name;
|
|
3596
|
+
const name = pluginAt(pluginDirOf(entry))?.name?.trim() ?? "";
|
|
3597
|
+
return name === "" ? void 0 : name;
|
|
3598
|
+
}
|
|
3599
|
+
//#endregion
|
|
3600
|
+
//#region src/modules/panels/model/market-route.ts
|
|
3601
|
+
function bundleNameOf(change, spec) {
|
|
3602
|
+
if (change.bundle !== void 0 && change.bundle !== "") return change.bundle;
|
|
3603
|
+
const at = spec.lastIndexOf("@");
|
|
3604
|
+
return at > 0 ? spec.slice(0, at) : spec;
|
|
3605
|
+
}
|
|
3606
|
+
function installedPluginName(spec, pkg) {
|
|
3607
|
+
const local = localDirOf(spec);
|
|
3608
|
+
return pluginNameOf(local === void 0 ? { package: pkg } : {
|
|
3609
|
+
package: local,
|
|
3610
|
+
local: true
|
|
3611
|
+
});
|
|
3612
|
+
}
|
|
3613
|
+
function outcomeOf(deps, pkg, change, plugin) {
|
|
3614
|
+
return {
|
|
3615
|
+
change,
|
|
3616
|
+
module: deps.installed?.(pkg) ?? {
|
|
3617
|
+
kind: "unknown",
|
|
3618
|
+
pkg
|
|
3619
|
+
},
|
|
3620
|
+
supply: { state: "no-needs" },
|
|
3621
|
+
...plugin === void 0 ? {} : { plugin }
|
|
3622
|
+
};
|
|
3623
|
+
}
|
|
3624
|
+
async function installDshBundle(deps, spec, options) {
|
|
3625
|
+
const change = await deps.manager.install({
|
|
3626
|
+
spec,
|
|
3627
|
+
...options?.registry === void 0 ? {} : { registry: options.registry },
|
|
3628
|
+
...options?.requestId === void 0 ? {} : { requestId: options.requestId },
|
|
3629
|
+
...options?.approvedBuilds === void 0 ? {} : { approvedBuilds: options.approvedBuilds }
|
|
3630
|
+
});
|
|
3631
|
+
if (change === null) throw new Error("插件管理器回的安装结果看不懂。");
|
|
3632
|
+
const pkg = bundleNameOf(change, spec);
|
|
3633
|
+
const module = deps.installed?.(pkg) ?? {
|
|
3634
|
+
kind: "unknown",
|
|
3635
|
+
pkg
|
|
3636
|
+
};
|
|
3637
|
+
await deps.rememberSource(deps.manager, pkg, options?.registry);
|
|
3638
|
+
return {
|
|
3639
|
+
change,
|
|
3640
|
+
module,
|
|
3641
|
+
supply: await deps.supplyAfter(module, change)
|
|
3642
|
+
};
|
|
3643
|
+
}
|
|
3644
|
+
async function installRouted(deps) {
|
|
3645
|
+
const { spec, options, registry, shell } = deps;
|
|
3646
|
+
const route = await packageRouteOf({
|
|
3647
|
+
spec,
|
|
3648
|
+
...shell === void 0 ? {} : { shell },
|
|
3649
|
+
...registry === void 0 ? {} : { registry }
|
|
3650
|
+
});
|
|
3651
|
+
if (route === void 0 || route.kind === "dsh-bundle") return installDshBundle(deps, spec, options);
|
|
3652
|
+
const pkg = route.pkg ?? splitNpmSpec(spec)?.name ?? spec.trim();
|
|
3653
|
+
if (route.kind === "neither") {
|
|
3654
|
+
deps.logger?.warn?.(`[dsh-workbench] 不装 ${pkg}:${refusalReason()}`);
|
|
3655
|
+
return outcomeOf(deps, pkg, {
|
|
3656
|
+
application: "failed",
|
|
3657
|
+
stage: "install",
|
|
3658
|
+
changed: false,
|
|
3659
|
+
target: pkg,
|
|
3660
|
+
errorCode: "not-a-module-or-plugin",
|
|
3661
|
+
errorDetail: refusalReason()
|
|
3662
|
+
});
|
|
3663
|
+
}
|
|
3664
|
+
const plan = pluginInstallPlan({
|
|
3665
|
+
spec,
|
|
3666
|
+
command: deps.command,
|
|
3667
|
+
...registry === void 0 ? {} : { registry },
|
|
3668
|
+
...route.pluginName === void 0 ? {} : { pluginName: route.pluginName }
|
|
3669
|
+
});
|
|
3670
|
+
if (!plan.ok) throw new Error(sourceProblemText(plan.problem, plan.env));
|
|
3671
|
+
deps.ledger?.intend({
|
|
3672
|
+
package: pkg,
|
|
3673
|
+
...registry === void 0 ? {} : { registry },
|
|
3674
|
+
...localDirOf(spec) === void 0 ? {} : { local: true }
|
|
3675
|
+
});
|
|
3676
|
+
try {
|
|
3677
|
+
await deps.runAll(plan.commands);
|
|
3678
|
+
} catch (error) {
|
|
3679
|
+
deps.ledger?.drop(pkg);
|
|
3680
|
+
throw error;
|
|
3681
|
+
}
|
|
3682
|
+
deps.ledger?.complete(pkg, installedPluginName(spec, pkg));
|
|
3683
|
+
deps.logger?.info?.(`[dsh-workbench] 把 ${pkg} 装进了 opencli 的插件区`);
|
|
3684
|
+
return outcomeOf(deps, pkg, {
|
|
3685
|
+
application: "applied",
|
|
3686
|
+
stage: "install",
|
|
3687
|
+
changed: true,
|
|
3688
|
+
target: pkg
|
|
3689
|
+
}, {
|
|
3690
|
+
kind: "opencli-plugin",
|
|
3691
|
+
pkg,
|
|
3692
|
+
...route.pluginName === void 0 ? {} : { name: route.pluginName }
|
|
3693
|
+
});
|
|
3694
|
+
}
|
|
3695
|
+
//#endregion
|
|
3696
|
+
//#region src/modules/panels/model/plugin-actions.ts
|
|
3697
|
+
function pluginKeptOf(retention, detail) {
|
|
3698
|
+
if (retention === "keep-held") return { kind: "held" };
|
|
3699
|
+
if (retention === "keep-unsure") return {
|
|
3700
|
+
kind: "unsure",
|
|
3701
|
+
unreadable: [...detail.unreadable]
|
|
3702
|
+
};
|
|
3703
|
+
return {
|
|
3704
|
+
kind: "claimed",
|
|
3705
|
+
by: [...detail.by]
|
|
3706
|
+
};
|
|
3707
|
+
}
|
|
3708
|
+
function pluginKeptText(kept) {
|
|
3709
|
+
if (kept.kind === "held") return "文件留着:你自己装的那一份,模块放手不动它。";
|
|
3710
|
+
if (kept.kind === "unnamed") return "文件先留着:看不出这是哪个插件,说不准谁在用它。";
|
|
3711
|
+
if (kept.kind === "unsure") return kept.unreadable.length === 0 ? "文件先留着:说不准还有谁在用它。" : `文件先留着:${kept.unreadable.join("、")} 读不出来用了什么,说不准它们在用它。`;
|
|
3712
|
+
return `文件留着:${kept.by.join("、")} 还要用它。`;
|
|
3713
|
+
}
|
|
3714
|
+
function pluginRemovedText(pkg) {
|
|
3715
|
+
return `「${pkg}」卸载干净了:没人再用它,文件也删了。`;
|
|
3716
|
+
}
|
|
3717
|
+
function pluginSweptText(pkg) {
|
|
3718
|
+
return `清掉了 ${pkg}:先前要它的都放手了,也没别人在用,文件跟着删了。`;
|
|
3719
|
+
}
|
|
3720
|
+
function createPluginActions(options) {
|
|
3721
|
+
const { ledger, shell, command, runAll, logger } = options;
|
|
3722
|
+
function requireLedger() {
|
|
3723
|
+
if (ledger === void 0) throw new Error("这个市场没有台账(插件记录),用户装的插件动不了。");
|
|
3724
|
+
return ledger;
|
|
3725
|
+
}
|
|
3726
|
+
function requireHeld(pkg) {
|
|
3727
|
+
const held = requireLedger();
|
|
3728
|
+
const direct = held.read(pkg);
|
|
3729
|
+
if (direct !== void 0) return {
|
|
3730
|
+
entry: direct,
|
|
3731
|
+
key: direct.package
|
|
3732
|
+
};
|
|
3733
|
+
const named = held.heldAsPlugin(pkg);
|
|
3734
|
+
if (named !== void 0) return {
|
|
3735
|
+
entry: named,
|
|
3736
|
+
key: named.package
|
|
3737
|
+
};
|
|
3738
|
+
const known = held.list().map((entry) => entry.name ?? entry.package);
|
|
3739
|
+
throw new Error(`台账里没有「${pkg}」这一笔(包名与插件名都认)—— 你装的插件有:${known.join("、") || "(空)"}`);
|
|
3740
|
+
}
|
|
3741
|
+
function referencesOf() {
|
|
3742
|
+
return options.references?.() ?? {
|
|
3743
|
+
trustworthy: false,
|
|
3744
|
+
unreadable: []
|
|
3745
|
+
};
|
|
3746
|
+
}
|
|
3747
|
+
function decide(entry, held) {
|
|
3748
|
+
const plugin = pluginNameOf(entry);
|
|
3749
|
+
if (plugin === void 0) return {
|
|
3750
|
+
act: "keep",
|
|
3751
|
+
kept: { kind: "unnamed" }
|
|
3752
|
+
};
|
|
3753
|
+
const refs = referencesOf();
|
|
3754
|
+
const by = options.claimedBy?.(plugin) ?? [];
|
|
3755
|
+
const retention = pluginRetentionOf({
|
|
3756
|
+
held,
|
|
3757
|
+
claimedBy: by,
|
|
3758
|
+
trustworthy: refs.trustworthy
|
|
3759
|
+
});
|
|
3760
|
+
if (retention === "remove") return {
|
|
3761
|
+
act: "remove",
|
|
3762
|
+
plugin
|
|
3763
|
+
};
|
|
3764
|
+
return {
|
|
3765
|
+
act: "keep",
|
|
3766
|
+
kept: pluginKeptOf(retention, {
|
|
3767
|
+
by,
|
|
3768
|
+
unreadable: refs.unreadable
|
|
3769
|
+
}),
|
|
3770
|
+
plugin
|
|
3771
|
+
};
|
|
3772
|
+
}
|
|
3773
|
+
async function removeFiles(plugin) {
|
|
3774
|
+
if (shell === void 0) throw new Error("没有执行缝(shell 服务不可用),删不掉插件文件。");
|
|
3775
|
+
await runAll([{ line: `${quoteShellArg(command)} plugin uninstall ${quoteShellArg(plugin)}` }]);
|
|
3776
|
+
}
|
|
3777
|
+
return {
|
|
3778
|
+
async update(pkg) {
|
|
3779
|
+
const { entry } = requireHeld(pkg);
|
|
3780
|
+
if (entry.local === true || entry.registry === void 0) throw new Error(`「${entry.package}」是从本地装的,没有地方查新版。`);
|
|
3781
|
+
if (shell === void 0) throw new Error("没有执行缝(shell 服务不可用),更新做不了。");
|
|
3782
|
+
const latest = await latestVersion({
|
|
3783
|
+
shell,
|
|
3784
|
+
name: entry.package,
|
|
3785
|
+
registry: entry.registry,
|
|
3786
|
+
force: true
|
|
3787
|
+
});
|
|
3788
|
+
if (latest === void 0) throw new Error(`问不到「${entry.package}」的新版本。`);
|
|
3789
|
+
const plan = pluginInstallPlan({
|
|
3790
|
+
spec: `${entry.package}@${latest}`,
|
|
3791
|
+
command,
|
|
3792
|
+
registry: entry.registry,
|
|
3793
|
+
...entry.name === void 0 ? {} : { pluginName: entry.name }
|
|
3794
|
+
});
|
|
3795
|
+
if (!plan.ok) throw new Error(sourceProblemText(plan.problem, plan.env));
|
|
3796
|
+
await runAll(plan.commands);
|
|
3797
|
+
logger?.info?.(`[dsh-workbench] 把 ${entry.package} 更新到 ${latest}`);
|
|
3798
|
+
return { name: pluginNameOf(entry) ?? entry.name ?? entry.package };
|
|
3799
|
+
},
|
|
3800
|
+
async uninstall(pkg) {
|
|
3801
|
+
const ledger = requireLedger();
|
|
3802
|
+
const { entry, key } = requireHeld(pkg);
|
|
3803
|
+
ledger.release(key);
|
|
3804
|
+
const decision = decide(entry, false);
|
|
3805
|
+
if (decision.act === "keep") {
|
|
3806
|
+
logger?.info?.(`[dsh-workbench] ${pluginKeptText(decision.kept)}`);
|
|
3807
|
+
return {
|
|
3808
|
+
removed: false,
|
|
3809
|
+
...decision.plugin === void 0 ? {} : { name: decision.plugin },
|
|
3810
|
+
kept: decision.kept
|
|
3811
|
+
};
|
|
3812
|
+
}
|
|
3813
|
+
await removeFiles(decision.plugin);
|
|
3814
|
+
ledger.drop(key);
|
|
3815
|
+
logger?.info?.(`[dsh-workbench] ${pluginRemovedText(key)}`);
|
|
3816
|
+
return {
|
|
3817
|
+
removed: true,
|
|
3818
|
+
name: decision.plugin
|
|
3819
|
+
};
|
|
3820
|
+
},
|
|
3821
|
+
async sweep() {
|
|
3822
|
+
if (ledger === void 0) return [];
|
|
3823
|
+
const removed = [];
|
|
3824
|
+
for (const entry of ledger.all()) {
|
|
3825
|
+
if (entry.releasedAt === void 0) continue;
|
|
3826
|
+
const decision = decide(entry, false);
|
|
3827
|
+
if (decision.act === "keep") continue;
|
|
3828
|
+
try {
|
|
3829
|
+
await removeFiles(decision.plugin);
|
|
3830
|
+
} catch (error) {
|
|
3831
|
+
logger?.warn?.(`[dsh-workbench] 清 ${entry.package} 没成:${String(error)}`);
|
|
3832
|
+
continue;
|
|
3833
|
+
}
|
|
3834
|
+
ledger.drop(entry.package);
|
|
3835
|
+
removed.push(entry.package);
|
|
3836
|
+
logger?.info?.(`[dsh-workbench] ${pluginSweptText(entry.package)}`);
|
|
3837
|
+
}
|
|
3838
|
+
return removed;
|
|
3839
|
+
}
|
|
3840
|
+
};
|
|
3841
|
+
}
|
|
3842
|
+
//#endregion
|
|
3843
|
+
//#region src/modules/panels/model/market-env.ts
|
|
3844
|
+
function createEnvDropper(options) {
|
|
3845
|
+
const { catalog, isReleased, ledger, command, run, logger } = options;
|
|
3846
|
+
function referencesOf() {
|
|
3847
|
+
return options.references?.() ?? {
|
|
3848
|
+
trustworthy: false,
|
|
3849
|
+
unreadable: []
|
|
3850
|
+
};
|
|
3851
|
+
}
|
|
3852
|
+
function usersOf(plugin, except) {
|
|
3853
|
+
return catalog().filter((item) => item.id !== except && !(isReleased?.(item.id) ?? false) && (item.env?.plugins ?? []).some((need) => need.name === plugin)).map((item) => item.id);
|
|
3854
|
+
}
|
|
3855
|
+
function libUsersOf(need, unit) {
|
|
3856
|
+
return catalog().filter((item) => item.id !== unit && !(isReleased?.(item.id) ?? false) && (item.env?.libs ?? []).some((other) => other.name === need.name)).map((item) => item.id);
|
|
3857
|
+
}
|
|
3858
|
+
return {
|
|
3859
|
+
usersOf,
|
|
3860
|
+
async drop(unit, env, before) {
|
|
3861
|
+
const kept = /* @__PURE__ */ new Set();
|
|
3862
|
+
const onMachine = new Set((before.plugins ?? []).filter((state) => state.status !== "missing").map((state) => state.name));
|
|
3863
|
+
const unknown = before.plugins === void 0;
|
|
3864
|
+
for (const need of env?.plugins ?? []) {
|
|
3865
|
+
const users = usersOf(need.name, unit);
|
|
3866
|
+
const retention = pluginRetentionOf({
|
|
3867
|
+
held: ledger?.heldAsPlugin(need.name) !== void 0,
|
|
3868
|
+
claimedBy: users,
|
|
3869
|
+
trustworthy: referencesOf().trustworthy
|
|
3870
|
+
});
|
|
3871
|
+
if (retention !== "remove") {
|
|
3872
|
+
for (const id of users) kept.add(id);
|
|
3873
|
+
const reason = pluginKeptOf(retention, {
|
|
3874
|
+
by: users,
|
|
3875
|
+
unreadable: referencesOf().unreadable
|
|
3876
|
+
});
|
|
3877
|
+
logger?.info?.(`[dsh-workbench] ${pluginKeptText(reason)}`);
|
|
3878
|
+
continue;
|
|
3879
|
+
}
|
|
3880
|
+
if (!unknown && !onMachine.has(need.name)) continue;
|
|
3881
|
+
await run(`${quoteShellArg(command)} plugin uninstall ${quoteShellArg(need.name)}`);
|
|
3882
|
+
}
|
|
3883
|
+
for (const need of env?.libs ?? []) {
|
|
3884
|
+
const keepers = libUsersOf(need, unit);
|
|
3885
|
+
if (keepers.length > 0) {
|
|
3886
|
+
for (const id of keepers) kept.add(id);
|
|
3887
|
+
continue;
|
|
3888
|
+
}
|
|
3889
|
+
const present = (before.libs ?? []).some((state) => state.name === need.name && state.status !== "missing");
|
|
3890
|
+
if (before.libs !== void 0 && !present) continue;
|
|
3891
|
+
await run(`npm uninstall --prefix ${quoteShellArg(libsPrefix())} ${quoteShellArg(need.name)}`, true);
|
|
3892
|
+
}
|
|
3893
|
+
return [...kept];
|
|
3894
|
+
}
|
|
3895
|
+
};
|
|
3896
|
+
}
|
|
3897
|
+
//#endregion
|
|
3898
|
+
//#region src/modules/panels/model/market.ts
|
|
3899
|
+
function createMarketService(options) {
|
|
3900
|
+
const { manifests, manifestsNow, probe, shell, logger, onInstalled, onUninstalled, isReleased, bundles, installed, modules, registries, ledger, references } = options;
|
|
3901
|
+
const command = options.command ?? "opencli";
|
|
3902
|
+
const catalog = () => (manifestsNow?.() ?? manifests).filter((manifest) => (manifest.env?.plugins ?? []).length > 0 || (manifest.env?.libs ?? []).length > 0);
|
|
3903
|
+
function requireEntry(unit) {
|
|
3904
|
+
const manifest = catalog().find((item) => item.id === unit);
|
|
3905
|
+
if (manifest === void 0) throw new Error(`市场里没有这个模块:${unit}(可安装:${catalog().map((item) => item.id).join(", ") || "(空)"})`);
|
|
3906
|
+
return manifest;
|
|
3907
|
+
}
|
|
3908
|
+
async function capabilityOf(unit, force = false) {
|
|
3909
|
+
return await probe?.(unit, { force }) ?? {
|
|
3910
|
+
unit,
|
|
3911
|
+
status: "unchecked",
|
|
3912
|
+
required: [],
|
|
3913
|
+
missing: [],
|
|
3914
|
+
detail: "没有探测能力(缺少执行缝或探测服务)"
|
|
3915
|
+
};
|
|
3916
|
+
}
|
|
3917
|
+
function needsOf(unit) {
|
|
3918
|
+
return requireEntry(unit).env?.plugins ?? [];
|
|
3919
|
+
}
|
|
3920
|
+
function libsOf(unit) {
|
|
3921
|
+
return requireEntry(unit).env?.libs ?? [];
|
|
3922
|
+
}
|
|
3923
|
+
function libCommand(need, version) {
|
|
3924
|
+
return libInstallCommand({
|
|
3925
|
+
need,
|
|
3926
|
+
...version === void 0 ? {} : { version }
|
|
3927
|
+
});
|
|
3928
|
+
}
|
|
3929
|
+
async function entryOf(unit, force = false) {
|
|
3930
|
+
const manifest = requireEntry(unit);
|
|
3931
|
+
return {
|
|
3932
|
+
unit,
|
|
3933
|
+
titleKey: manifest.card?.titleKey ?? unit,
|
|
3934
|
+
plugins: (manifest.env?.plugins ?? []).map((need) => ({
|
|
3935
|
+
name: need.name,
|
|
3936
|
+
...need.installSource === void 0 ? {} : { source: need.installSource }
|
|
3937
|
+
})),
|
|
3938
|
+
libs: (manifest.env?.libs ?? []).map((need) => ({
|
|
3939
|
+
name: need.name,
|
|
3940
|
+
...need.version === void 0 ? {} : { version: need.version },
|
|
3941
|
+
purpose: need.purpose
|
|
3942
|
+
})),
|
|
3943
|
+
capability: await capabilityOf(unit, force)
|
|
3944
|
+
};
|
|
3945
|
+
}
|
|
3946
|
+
async function run(line, optional = false) {
|
|
3947
|
+
if (shell === void 0) throw new Error("没有执行缝(shell 服务不可用),装 / 卸做不了。");
|
|
3948
|
+
const result = await runShell(shell, line, { timeoutMs: INSTALL_TIMEOUT_MS });
|
|
3949
|
+
if (result.exitCode !== 0 && !optional) {
|
|
3950
|
+
const tail = tailLines(result.stderr || result.stdout, 6);
|
|
3951
|
+
throw new Error(`命令失败(退出码 ${result.exitCode})${tail === "" ? "" : `:${tail}`}`);
|
|
3952
|
+
}
|
|
3953
|
+
}
|
|
3954
|
+
async function runAll(commands) {
|
|
3955
|
+
for (const step of commands) await run(step.line, step.optional === true);
|
|
3956
|
+
}
|
|
3957
|
+
async function probeSharing(request) {
|
|
3958
|
+
for (const id of affectedUnitsOf(catalog(), request)) if (id !== request.unit) await capabilityOf(id, true);
|
|
3959
|
+
}
|
|
3960
|
+
async function snapshot(force = false) {
|
|
3961
|
+
const modules = await Promise.all(catalog().map((manifest) => entryOf(manifest.id, force)));
|
|
3962
|
+
const capabilities = new Map(modules.map((entry) => [entry.unit, entry.capability]));
|
|
3963
|
+
return {
|
|
3058
3964
|
modules,
|
|
3059
3965
|
shared: sharedDependencies({
|
|
3060
3966
|
manifests: catalog(),
|
|
@@ -3096,30 +4002,28 @@ function createMarketService(options) {
|
|
|
3096
4002
|
if (bundles === void 0) throw new Error("这个 profile 里没有插件管理器,装不了模块包。");
|
|
3097
4003
|
return bundles;
|
|
3098
4004
|
}
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
await run(`npm uninstall --prefix ${quoteShellArg(libsPrefix())} ${quoteShellArg(need.name)}`, true);
|
|
4005
|
+
const envDropper = createEnvDropper({
|
|
4006
|
+
catalog,
|
|
4007
|
+
...isReleased === void 0 ? {} : { isReleased },
|
|
4008
|
+
...ledger === void 0 ? {} : { ledger },
|
|
4009
|
+
...references === void 0 ? {} : { references },
|
|
4010
|
+
command,
|
|
4011
|
+
run,
|
|
4012
|
+
...logger === void 0 ? {} : { logger }
|
|
4013
|
+
});
|
|
4014
|
+
function pluginUsersOf(plugin, except) {
|
|
4015
|
+
return envDropper.usersOf(plugin, except);
|
|
4016
|
+
}
|
|
4017
|
+
async function reprobePlugin(pkg, name) {
|
|
4018
|
+
const keys = name === void 0 || name === pkg ? [pkg] : [name, pkg];
|
|
4019
|
+
for (const key of keys) {
|
|
4020
|
+
const id = `plugin:${key}`;
|
|
4021
|
+
if (findDependency(catalog(), id, isReleased) === void 0) continue;
|
|
4022
|
+
await probeSharing({
|
|
4023
|
+
action: "update-dependency",
|
|
4024
|
+
id
|
|
4025
|
+
});
|
|
3121
4026
|
}
|
|
3122
|
-
return [...kept];
|
|
3123
4027
|
}
|
|
3124
4028
|
async function supplyAfter(module, change) {
|
|
3125
4029
|
if (module.kind !== "module") return { state: "not-a-module" };
|
|
@@ -3170,6 +4074,38 @@ function createMarketService(options) {
|
|
|
3170
4074
|
logger?.warn?.(`[dsh-workbench] 卸 ${pkg} 撞上源:补 ${added.join("、")} 后重试一次`);
|
|
3171
4075
|
return await manager.remove(pkg).catch(() => null) ?? change;
|
|
3172
4076
|
}
|
|
4077
|
+
function routeDeps(manager) {
|
|
4078
|
+
return {
|
|
4079
|
+
manager,
|
|
4080
|
+
command,
|
|
4081
|
+
runAll,
|
|
4082
|
+
rememberSource: rememberInstalledSource,
|
|
4083
|
+
supplyAfter,
|
|
4084
|
+
...shell === void 0 ? {} : { shell },
|
|
4085
|
+
...installed === void 0 ? {} : { installed },
|
|
4086
|
+
...ledger === void 0 ? {} : { ledger },
|
|
4087
|
+
...logger === void 0 ? {} : { logger }
|
|
4088
|
+
};
|
|
4089
|
+
}
|
|
4090
|
+
const plugins = createPluginActions({
|
|
4091
|
+
...ledger === void 0 ? {} : { ledger },
|
|
4092
|
+
...shell === void 0 ? {} : { shell },
|
|
4093
|
+
command,
|
|
4094
|
+
runAll,
|
|
4095
|
+
claimedBy: (plugin) => pluginUsersOf(plugin),
|
|
4096
|
+
...references === void 0 ? {} : { references },
|
|
4097
|
+
...logger === void 0 ? {} : { logger }
|
|
4098
|
+
});
|
|
4099
|
+
async function installByRoute(spec, options) {
|
|
4100
|
+
const manager = requireBundles();
|
|
4101
|
+
const registry = options?.registry ?? (await manager.registries().catch(() => null))?.resolved ?? void 0;
|
|
4102
|
+
return installRouted({
|
|
4103
|
+
...routeDeps(manager),
|
|
4104
|
+
spec,
|
|
4105
|
+
...options === void 0 ? {} : { options },
|
|
4106
|
+
...registry === void 0 ? {} : { registry }
|
|
4107
|
+
});
|
|
4108
|
+
}
|
|
3173
4109
|
const service = {
|
|
3174
4110
|
async list() {
|
|
3175
4111
|
return snapshot();
|
|
@@ -3256,7 +4192,8 @@ function createMarketService(options) {
|
|
|
3256
4192
|
};
|
|
3257
4193
|
}
|
|
3258
4194
|
const before = await capabilityOf(unit, true);
|
|
3259
|
-
const keptFor = await
|
|
4195
|
+
const keptFor = await envDropper.drop(unit, requireEntry(unit).env, before);
|
|
4196
|
+
await plugins.sweep();
|
|
3260
4197
|
await probeSharing({
|
|
3261
4198
|
action: "uninstall",
|
|
3262
4199
|
unit
|
|
@@ -3275,25 +4212,7 @@ function createMarketService(options) {
|
|
|
3275
4212
|
};
|
|
3276
4213
|
},
|
|
3277
4214
|
async installBundle(spec, options) {
|
|
3278
|
-
|
|
3279
|
-
const change = await manager.install({
|
|
3280
|
-
spec,
|
|
3281
|
-
...options?.registry === void 0 ? {} : { registry: options.registry },
|
|
3282
|
-
...options?.requestId === void 0 ? {} : { requestId: options.requestId },
|
|
3283
|
-
...options?.approvedBuilds === void 0 ? {} : { approvedBuilds: options.approvedBuilds }
|
|
3284
|
-
});
|
|
3285
|
-
if (change === null) throw new Error("插件管理器回的安装结果看不懂。");
|
|
3286
|
-
const pkg = bundleNameOf(change, spec);
|
|
3287
|
-
const module = installed?.(pkg) ?? {
|
|
3288
|
-
kind: "unknown",
|
|
3289
|
-
pkg
|
|
3290
|
-
};
|
|
3291
|
-
await rememberInstalledSource(manager, pkg, options?.registry);
|
|
3292
|
-
return {
|
|
3293
|
-
change,
|
|
3294
|
-
module,
|
|
3295
|
-
supply: await supplyAfter(module, change)
|
|
3296
|
-
};
|
|
4215
|
+
return installByRoute(spec, options);
|
|
3297
4216
|
},
|
|
3298
4217
|
async removeBundle(pkg) {
|
|
3299
4218
|
const manager = requireBundles();
|
|
@@ -3311,7 +4230,8 @@ function createMarketService(options) {
|
|
|
3311
4230
|
change,
|
|
3312
4231
|
keptFor: []
|
|
3313
4232
|
};
|
|
3314
|
-
const keptFor = manifest === void 0 || before === void 0 ? [] : await
|
|
4233
|
+
const keptFor = manifest === void 0 || before === void 0 ? [] : await envDropper.drop(unit, manifest.env, before);
|
|
4234
|
+
await plugins.sweep();
|
|
3315
4235
|
onUninstalled?.(unit);
|
|
3316
4236
|
return {
|
|
3317
4237
|
change,
|
|
@@ -3336,10 +4256,21 @@ function createMarketService(options) {
|
|
|
3336
4256
|
force: true
|
|
3337
4257
|
});
|
|
3338
4258
|
const spec = latest === void 0 ? pkg : `${pkg}@${latest}`;
|
|
3339
|
-
const outcome = await
|
|
4259
|
+
const outcome = await installDshBundle(routeDeps(requireBundles()), spec, { ...registry === void 0 ? {} : { registry } });
|
|
4260
|
+
await plugins.sweep();
|
|
3340
4261
|
await service.update(unit).catch(() => void 0);
|
|
3341
4262
|
return outcome;
|
|
3342
4263
|
},
|
|
4264
|
+
async updatePlugin(pkg) {
|
|
4265
|
+
const outcome = await plugins.update(pkg);
|
|
4266
|
+
await reprobePlugin(pkg, outcome.name);
|
|
4267
|
+
return outcome;
|
|
4268
|
+
},
|
|
4269
|
+
async uninstallPlugin(pkg) {
|
|
4270
|
+
const outcome = await plugins.uninstall(pkg);
|
|
4271
|
+
await reprobePlugin(pkg, outcome.name);
|
|
4272
|
+
return outcome;
|
|
4273
|
+
},
|
|
3343
4274
|
updateDependency: updateDependencyRow
|
|
3344
4275
|
};
|
|
3345
4276
|
return service;
|
|
@@ -3394,6 +4325,22 @@ async function dispatchMarket(market, request) {
|
|
|
3394
4325
|
action: "cancel-install",
|
|
3395
4326
|
cancellation: await market.cancelInstall(request.requestId ?? "")
|
|
3396
4327
|
};
|
|
4328
|
+
if (request.action === "update-plugin") {
|
|
4329
|
+
await market.updatePlugin(request.name ?? "");
|
|
4330
|
+
return {
|
|
4331
|
+
ok: true,
|
|
4332
|
+
action: "update-plugin"
|
|
4333
|
+
};
|
|
4334
|
+
}
|
|
4335
|
+
if (request.action === "uninstall-plugin") {
|
|
4336
|
+
const outcome = await market.uninstallPlugin(request.name ?? "");
|
|
4337
|
+
return {
|
|
4338
|
+
ok: true,
|
|
4339
|
+
action: "uninstall-plugin",
|
|
4340
|
+
removed: outcome.removed,
|
|
4341
|
+
...outcome.kept === void 0 ? {} : { kept: outcome.kept }
|
|
4342
|
+
};
|
|
4343
|
+
}
|
|
3397
4344
|
const unit = request.unit ?? "";
|
|
3398
4345
|
if (request.action === "uninstall") {
|
|
3399
4346
|
const { entry, keptFor, change } = await market.uninstall(unit);
|
|
@@ -3468,13 +4415,24 @@ var OUTPUT_SCHEMA$1 = {
|
|
|
3468
4415
|
},
|
|
3469
4416
|
additionalProperties: true
|
|
3470
4417
|
};
|
|
4418
|
+
function describePluginUninstall(pkg, outcome) {
|
|
4419
|
+
if (outcome.removed) return `已卸载 opencli 插件 ${pkg}:没人再用它,文件也删了。`;
|
|
4420
|
+
return `已从「你装的插件」里去掉 ${pkg}。${outcome.kept === void 0 ? "" : pluginKeptText(outcome.kept)}`;
|
|
4421
|
+
}
|
|
4422
|
+
function usersOf(shared, names) {
|
|
4423
|
+
const hit = shared.find((item) => names.some((name) => name !== void 0 && name !== "" && item.id === `plugin:${name}`));
|
|
4424
|
+
if (hit === void 0 || hit.usedBy.length === 0) return "";
|
|
4425
|
+
return `它被 ${hit.usedBy.join("、")} 用着(动的是它们的供给,它们的状态跟着重探了一遍)`;
|
|
4426
|
+
}
|
|
3471
4427
|
var ACTIONS = [
|
|
3472
4428
|
"list",
|
|
3473
4429
|
"install",
|
|
3474
4430
|
"update",
|
|
3475
4431
|
"uninstall",
|
|
3476
4432
|
"install-bundle",
|
|
3477
|
-
"update-dependency"
|
|
4433
|
+
"update-dependency",
|
|
4434
|
+
"update-plugin",
|
|
4435
|
+
"uninstall-plugin"
|
|
3478
4436
|
];
|
|
3479
4437
|
function registerMarketTools(ctx, market) {
|
|
3480
4438
|
const tools = ctx.get("tools");
|
|
@@ -3484,13 +4442,13 @@ function registerMarketTools(ctx, market) {
|
|
|
3484
4442
|
}
|
|
3485
4443
|
const tool = {
|
|
3486
4444
|
name: "workbench_modules",
|
|
3487
|
-
description: "看 / 装 / 升 /
|
|
4445
|
+
description: "看 / 装 / 升 / 卸工作台的面板模块,以及用户自己装的 opencli 插件。install-bundle 按 spec(包名,可带版本;git / tarball / 本地路径也行)把模块包装进 profile —— 装完会连带装它声明的 opencli 插件;其余 action 按模块 id 动它要的外部供给;update-plugin / uninstall-plugin 按包名或插件名动用户装的那份插件。卸载只动工作台运行时,不影响终端那套;共用依赖只有升级。",
|
|
3488
4446
|
parameters: {
|
|
3489
4447
|
type: "object",
|
|
3490
4448
|
properties: {
|
|
3491
4449
|
action: {
|
|
3492
4450
|
type: "string",
|
|
3493
|
-
description: "install-bundle=按 spec 装模块包(要 spec),update-dependency
|
|
4451
|
+
description: "install-bundle=按 spec 装模块包(要 spec),update-dependency=升级共用依赖(要 id),update-plugin / uninstall-plugin=动用户自己装的 opencli 插件(要 plugin),其余=对模块(要 id)。",
|
|
3494
4452
|
enum: [...ACTIONS]
|
|
3495
4453
|
},
|
|
3496
4454
|
id: {
|
|
@@ -3504,6 +4462,10 @@ function registerMarketTools(ctx, market) {
|
|
|
3504
4462
|
registry: {
|
|
3505
4463
|
type: "string",
|
|
3506
4464
|
description: "install-bundle 用:先问哪个源(不填就用这个 profile 自己配的源)。"
|
|
4465
|
+
},
|
|
4466
|
+
plugin: {
|
|
4467
|
+
type: "string",
|
|
4468
|
+
description: "update-plugin / uninstall-plugin 用:opencli 插件的包名或插件名(就是市场「已安装 opencli 插件」那一段里的名字,如 cmdb 或 opencli-plugin-cmdb)。"
|
|
3507
4469
|
}
|
|
3508
4470
|
},
|
|
3509
4471
|
required: ["action"],
|
|
@@ -3542,6 +4504,23 @@ function registerMarketTools(ctx, market) {
|
|
|
3542
4504
|
text: describeInstall(spec, outcome.module, outcome.supply, outcome.change)
|
|
3543
4505
|
};
|
|
3544
4506
|
}
|
|
4507
|
+
if (action === "update-plugin" || action === "uninstall-plugin") {
|
|
4508
|
+
const plugin = args?.plugin?.trim() ?? "";
|
|
4509
|
+
if (plugin === "") throw new Error(`${action} 需要一个 plugin:opencli 插件的包名或插件名 —— 就是市场「已安装 opencli 插件」那一段里的名字。`);
|
|
4510
|
+
if (action === "update-plugin") {
|
|
4511
|
+
const updated = await market.updatePlugin(plugin);
|
|
4512
|
+
const users = usersOf((await market.list()).shared, [updated.name, plugin]);
|
|
4513
|
+
return { text: `已更新 opencli 插件 ${plugin}${users === "" ? "。" : `;${users}。`}` };
|
|
4514
|
+
}
|
|
4515
|
+
const outcome = await market.uninstallPlugin(plugin);
|
|
4516
|
+
const users = usersOf((await market.list()).shared, [outcome.name, plugin]);
|
|
4517
|
+
return {
|
|
4518
|
+
removed: outcome.removed,
|
|
4519
|
+
...outcome.name === void 0 ? {} : { name: outcome.name },
|
|
4520
|
+
...outcome.kept === void 0 ? {} : { kept: outcome.kept },
|
|
4521
|
+
text: `${describePluginUninstall(plugin, outcome)}${users === "" ? "" : `${users}。`}`
|
|
4522
|
+
};
|
|
4523
|
+
}
|
|
3545
4524
|
const id = args?.id;
|
|
3546
4525
|
if (id === void 0 || id.trim() === "") {
|
|
3547
4526
|
const { modules, shared } = await market.list();
|
|
@@ -3592,7 +4571,7 @@ function applyPanelsPref(panels, payload) {
|
|
|
3592
4571
|
const path = action.path ?? null;
|
|
3593
4572
|
if (action.action === "reset") return {
|
|
3594
4573
|
ok: true,
|
|
3595
|
-
units: panels.
|
|
4574
|
+
units: panels.reset(path)
|
|
3596
4575
|
};
|
|
3597
4576
|
if (action.id === void 0) throw new Error(`${action.action} 需要一个 id(当前套件:${panels.list(path).map((entry) => entry.id).join(", ")})`);
|
|
3598
4577
|
if (action.action === "show") return {
|
|
@@ -3653,66 +4632,12 @@ function panelsFactsOf(call, manifests) {
|
|
|
3653
4632
|
];
|
|
3654
4633
|
}
|
|
3655
4634
|
//#endregion
|
|
3656
|
-
//#region src/modules/panels/model/config.ts
|
|
3657
|
-
function strings(raw) {
|
|
3658
|
-
return Array.isArray(raw) ? raw.filter((item) => typeof item === "string") : [];
|
|
3659
|
-
}
|
|
3660
|
-
function parseShown(raw) {
|
|
3661
|
-
if (typeof raw !== "object" || raw === null) return [];
|
|
3662
|
-
return strings(raw.shown);
|
|
3663
|
-
}
|
|
3664
|
-
function parseReleased(raw) {
|
|
3665
|
-
if (typeof raw !== "object" || raw === null) return [];
|
|
3666
|
-
return strings(raw.ids);
|
|
3667
|
-
}
|
|
3668
|
-
function resolvePanels(manifests, panels) {
|
|
3669
|
-
const shown = panels ?? [];
|
|
3670
|
-
const rank = new Map(shown.map((id, index) => [id, index]));
|
|
3671
|
-
return manifests.filter((manifest) => manifest.card !== null).map((manifest) => {
|
|
3672
|
-
const isShown = rank.has(manifest.id);
|
|
3673
|
-
const order = rank.get(manifest.id) ?? shown.length + (manifest.card?.order ?? 0);
|
|
3674
|
-
return {
|
|
3675
|
-
id: manifest.id,
|
|
3676
|
-
titleKey: manifest.card?.titleKey ?? manifest.id,
|
|
3677
|
-
order,
|
|
3678
|
-
shown: isShown,
|
|
3679
|
-
source: isShown ? "user" : "default",
|
|
3680
|
-
hasCard: true
|
|
3681
|
-
};
|
|
3682
|
-
}).sort((a, b) => a.order - b.order || a.id.localeCompare(b.id));
|
|
3683
|
-
}
|
|
3684
|
-
function withoutReleased(released, id) {
|
|
3685
|
-
return released.filter((item) => item !== id);
|
|
3686
|
-
}
|
|
3687
|
-
function withReleased(released, id) {
|
|
3688
|
-
return [.../* @__PURE__ */ new Set([...released, id])];
|
|
3689
|
-
}
|
|
3690
|
-
function withShown(panels, id) {
|
|
3691
|
-
return panels.includes(id) ? [...panels] : [...panels, id];
|
|
3692
|
-
}
|
|
3693
|
-
function withHidden(panels, id) {
|
|
3694
|
-
return panels.filter((item) => item !== id);
|
|
3695
|
-
}
|
|
3696
|
-
function withMoved(panels, id, index) {
|
|
3697
|
-
const current = [...panels];
|
|
3698
|
-
const from = current.indexOf(id);
|
|
3699
|
-
const max = from === -1 ? current.length : current.length - 1;
|
|
3700
|
-
const to = Math.max(0, Math.min(max, Math.trunc(index)));
|
|
3701
|
-
if (from === -1) {
|
|
3702
|
-
current.splice(to, 0, id);
|
|
3703
|
-
return current;
|
|
3704
|
-
}
|
|
3705
|
-
if (from === to) return current;
|
|
3706
|
-
current.splice(to, 0, ...current.splice(from, 1));
|
|
3707
|
-
return current;
|
|
3708
|
-
}
|
|
3709
|
-
//#endregion
|
|
3710
4635
|
//#region src/modules/panels/model/service.ts
|
|
3711
4636
|
var PANELS_MODULE_ID = "panels";
|
|
3712
4637
|
var PANELS_FILE = "config.json";
|
|
3713
4638
|
var RELEASED_FILE = "released.json";
|
|
3714
4639
|
function createPanelsService(options) {
|
|
3715
|
-
const { manifests, store, logger } = options;
|
|
4640
|
+
const { manifests, store, owned, logger } = options;
|
|
3716
4641
|
const listeners = /* @__PURE__ */ new Set();
|
|
3717
4642
|
const machine = store.machine(PANELS_MODULE_ID);
|
|
3718
4643
|
const folders = /* @__PURE__ */ new Map();
|
|
@@ -3723,12 +4648,36 @@ function createPanelsService(options) {
|
|
|
3723
4648
|
folders.set(workspace, folder);
|
|
3724
4649
|
return folder;
|
|
3725
4650
|
}
|
|
3726
|
-
function
|
|
3727
|
-
|
|
4651
|
+
function readBoard(folder) {
|
|
4652
|
+
const raw = folder.read(PANELS_FILE);
|
|
4653
|
+
return raw === null ? null : parseBoard(raw);
|
|
4654
|
+
}
|
|
4655
|
+
function boardIn(folder) {
|
|
4656
|
+
return readBoard(folder) ?? EMPTY_BOARD;
|
|
4657
|
+
}
|
|
4658
|
+
function writeBoard(folder, next) {
|
|
4659
|
+
folder.write(PANELS_FILE, {
|
|
4660
|
+
shown: next.shown,
|
|
4661
|
+
hidden: next.hidden
|
|
4662
|
+
});
|
|
4663
|
+
}
|
|
4664
|
+
function boardsOf(workspace) {
|
|
4665
|
+
const key = workspace?.trim() ?? "";
|
|
4666
|
+
return {
|
|
4667
|
+
workspace: key === "" ? null : readBoard(folderOf(key)),
|
|
4668
|
+
machine: readBoard(machine)
|
|
4669
|
+
};
|
|
3728
4670
|
}
|
|
3729
|
-
function
|
|
3730
|
-
if (
|
|
3731
|
-
|
|
4671
|
+
function targetOf(target, workspace) {
|
|
4672
|
+
if (target === "machine") return {
|
|
4673
|
+
folder: machine,
|
|
4674
|
+
key: null
|
|
4675
|
+
};
|
|
4676
|
+
const key = requireWorkspace(workspace);
|
|
4677
|
+
return {
|
|
4678
|
+
folder: folderOf(key),
|
|
4679
|
+
key
|
|
4680
|
+
};
|
|
3732
4681
|
}
|
|
3733
4682
|
let released;
|
|
3734
4683
|
function readReleased() {
|
|
@@ -3747,10 +4696,10 @@ function createPanelsService(options) {
|
|
|
3747
4696
|
logger?.warn?.(`[dsh-workbench] 面板配置订阅者报错:${String(error)}`);
|
|
3748
4697
|
}
|
|
3749
4698
|
}
|
|
3750
|
-
function withPersisted(
|
|
3751
|
-
|
|
4699
|
+
function withPersisted(folder, next, key) {
|
|
4700
|
+
writeBoard(folder, next);
|
|
3752
4701
|
notify();
|
|
3753
|
-
return resolvePanels(manifests(),
|
|
4702
|
+
return resolvePanels(manifests(), boardsOf(key));
|
|
3754
4703
|
}
|
|
3755
4704
|
function takeBackReleased(id) {
|
|
3756
4705
|
const current = readReleased();
|
|
@@ -3771,16 +4720,19 @@ function createPanelsService(options) {
|
|
|
3771
4720
|
return key;
|
|
3772
4721
|
}
|
|
3773
4722
|
return {
|
|
3774
|
-
list(workspace) {
|
|
3775
|
-
|
|
3776
|
-
|
|
4723
|
+
list(workspace, target) {
|
|
4724
|
+
return resolvePanels(manifests(), target === "machine" ? boardsOf(null) : boardsOf(workspace));
|
|
4725
|
+
},
|
|
4726
|
+
boards(workspace) {
|
|
4727
|
+
return boardsOf(workspace);
|
|
3777
4728
|
},
|
|
3778
4729
|
isEnabled(id, workspace) {
|
|
3779
|
-
|
|
3780
|
-
if (
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
4730
|
+
if (!manifests().some((item) => item.id === id)) return false;
|
|
4731
|
+
if (owned?.().includes(id) === true) return true;
|
|
4732
|
+
return resolveScope({
|
|
4733
|
+
id,
|
|
4734
|
+
boards: boardsOf(workspace)
|
|
4735
|
+
});
|
|
3784
4736
|
},
|
|
3785
4737
|
subscribe(listener) {
|
|
3786
4738
|
listeners.add(listener);
|
|
@@ -3794,36 +4746,40 @@ function createPanelsService(options) {
|
|
|
3794
4746
|
const seen = /* @__PURE__ */ new Set();
|
|
3795
4747
|
const drop = (folder) => {
|
|
3796
4748
|
seen.add(folder.dir);
|
|
3797
|
-
const current =
|
|
3798
|
-
if (current
|
|
4749
|
+
const current = readBoard(folder);
|
|
4750
|
+
if (current === null) return;
|
|
4751
|
+
if (!current.shown.includes(id) && !current.hidden.includes(id)) return;
|
|
4752
|
+
writeBoard(folder, withoutPanel(current, id));
|
|
3799
4753
|
};
|
|
3800
4754
|
for (const folder of store.scan(PANELS_MODULE_ID)) drop(folder);
|
|
3801
4755
|
for (const folder of folders.values()) if (!seen.has(folder.dir)) drop(folder);
|
|
4756
|
+
drop(machine);
|
|
3802
4757
|
notify();
|
|
3803
4758
|
},
|
|
3804
4759
|
accept(id) {
|
|
3805
4760
|
takeBackReleased(id);
|
|
3806
4761
|
},
|
|
3807
|
-
show(id, workspace) {
|
|
4762
|
+
show(id, workspace, target) {
|
|
3808
4763
|
requireSwitchable(id);
|
|
3809
|
-
const key =
|
|
4764
|
+
const { folder, key } = targetOf(target ?? "workspace", workspace);
|
|
3810
4765
|
takeBackReleased(id);
|
|
3811
|
-
return withPersisted(
|
|
4766
|
+
return withPersisted(folder, withShown(boardIn(folder), id), key);
|
|
3812
4767
|
},
|
|
3813
|
-
hide(id, workspace) {
|
|
4768
|
+
hide(id, workspace, target) {
|
|
3814
4769
|
requireSwitchable(id);
|
|
3815
|
-
const key =
|
|
3816
|
-
return withPersisted(
|
|
4770
|
+
const { folder, key } = targetOf(target ?? "workspace", workspace);
|
|
4771
|
+
return withPersisted(folder, withHidden(boardIn(folder), id), key);
|
|
3817
4772
|
},
|
|
3818
|
-
move(id, index, workspace) {
|
|
4773
|
+
move(id, index, workspace, target) {
|
|
3819
4774
|
requireSwitchable(id);
|
|
3820
|
-
const key =
|
|
3821
|
-
const current =
|
|
3822
|
-
if (!current.includes(id)) takeBackReleased(id);
|
|
3823
|
-
return withPersisted(
|
|
4775
|
+
const { folder, key } = targetOf(target ?? "workspace", workspace);
|
|
4776
|
+
const current = boardIn(folder);
|
|
4777
|
+
if (!current.shown.includes(id)) takeBackReleased(id);
|
|
4778
|
+
return withPersisted(folder, withMoved(current, id, index), key);
|
|
3824
4779
|
},
|
|
3825
|
-
|
|
3826
|
-
|
|
4780
|
+
reset(workspace, target) {
|
|
4781
|
+
const { folder, key } = targetOf(target ?? "workspace", workspace);
|
|
4782
|
+
return withPersisted(folder, EMPTY_BOARD, key);
|
|
3827
4783
|
}
|
|
3828
4784
|
};
|
|
3829
4785
|
}
|
|
@@ -3845,11 +4801,119 @@ function panelRoutes(panels, values) {
|
|
|
3845
4801
|
} };
|
|
3846
4802
|
}
|
|
3847
4803
|
//#endregion
|
|
4804
|
+
//#region src/modules/panels/model/plugin-ledger.ts
|
|
4805
|
+
function createPluginLedger(options) {
|
|
4806
|
+
const folder = options.store.machine(PANELS_MODULE_ID);
|
|
4807
|
+
const home = options.home;
|
|
4808
|
+
const all = () => readPluginLedger(folder);
|
|
4809
|
+
const save = (entries) => writePluginLedger(folder, entries);
|
|
4810
|
+
return {
|
|
4811
|
+
home,
|
|
4812
|
+
list: () => heldEntriesOfHome(all(), home),
|
|
4813
|
+
all: () => pluginEntriesOfHome(all(), home),
|
|
4814
|
+
read: (pkg) => {
|
|
4815
|
+
const entry = pluginEntryOf(all(), home, pkg);
|
|
4816
|
+
return entry !== void 0 && entry.releasedAt === void 0 ? entry : void 0;
|
|
4817
|
+
},
|
|
4818
|
+
held: (pkg) => {
|
|
4819
|
+
const entry = pluginEntryOf(all(), home, pkg);
|
|
4820
|
+
return entry !== void 0 && entry.releasedAt === void 0;
|
|
4821
|
+
},
|
|
4822
|
+
heldAsPlugin: (plugin) => {
|
|
4823
|
+
const key = plugin.trim();
|
|
4824
|
+
if (key === "") return void 0;
|
|
4825
|
+
const known = heldEntryOfPlugin(all(), home, key);
|
|
4826
|
+
if (known !== void 0) return known;
|
|
4827
|
+
return heldEntriesOfHome(all(), home).find((entry) => pluginNameOf(entry) === key);
|
|
4828
|
+
},
|
|
4829
|
+
intend(intent) {
|
|
4830
|
+
const registry = intent.registry?.trim() ?? "";
|
|
4831
|
+
save(withPluginEntry(all(), {
|
|
4832
|
+
opencliHome: home,
|
|
4833
|
+
package: intent.package,
|
|
4834
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4835
|
+
...registry === "" ? {} : { registry },
|
|
4836
|
+
...intent.local === true ? { local: true } : {}
|
|
4837
|
+
}));
|
|
4838
|
+
},
|
|
4839
|
+
complete(pkg, name) {
|
|
4840
|
+
const trimmed = name?.trim() ?? "";
|
|
4841
|
+
if (trimmed === "") return;
|
|
4842
|
+
const current = pluginEntryOf(all(), home, pkg);
|
|
4843
|
+
if (current === void 0 || current.releasedAt !== void 0) return;
|
|
4844
|
+
save(withPluginEntry(all(), {
|
|
4845
|
+
...current,
|
|
4846
|
+
name: trimmed
|
|
4847
|
+
}));
|
|
4848
|
+
},
|
|
4849
|
+
release(pkg) {
|
|
4850
|
+
save(withReleasedEntry(all(), home, pkg, (/* @__PURE__ */ new Date()).toISOString()));
|
|
4851
|
+
},
|
|
4852
|
+
drop(pkg) {
|
|
4853
|
+
save(withoutPluginEntry(all(), home, pkg));
|
|
4854
|
+
}
|
|
4855
|
+
};
|
|
4856
|
+
}
|
|
4857
|
+
//#endregion
|
|
4858
|
+
//#region src/shared/api/installed-plugins.ts
|
|
4859
|
+
var INSTALLED_PLUGINS_ROUTE = "panels/plugins";
|
|
4860
|
+
//#endregion
|
|
4861
|
+
//#region src/modules/panels/model/plugin-section.ts
|
|
4862
|
+
async function installedPlugins(options) {
|
|
4863
|
+
const { ledger, shell, force = false } = options;
|
|
4864
|
+
const rows = [];
|
|
4865
|
+
for (const entry of ledger.list()) {
|
|
4866
|
+
const release = shell === void 0 || entry.local === true || entry.registry === void 0 ? void 0 : await probeRelease({
|
|
4867
|
+
shell,
|
|
4868
|
+
package: entry.package,
|
|
4869
|
+
registry: entry.registry,
|
|
4870
|
+
force
|
|
4871
|
+
});
|
|
4872
|
+
rows.push({
|
|
4873
|
+
pkg: entry.package,
|
|
4874
|
+
...entry.name === void 0 ? {} : { name: entry.name },
|
|
4875
|
+
...release === void 0 ? {} : {
|
|
4876
|
+
installed: release.installed,
|
|
4877
|
+
...release.latest === void 0 ? {} : { latest: release.latest }
|
|
4878
|
+
},
|
|
4879
|
+
...entry.local === true ? { local: true } : {},
|
|
4880
|
+
...pluginOnDisk(entry) ? {} : { broken: true }
|
|
4881
|
+
});
|
|
4882
|
+
}
|
|
4883
|
+
return rows;
|
|
4884
|
+
}
|
|
4885
|
+
function pluginRoutes(options) {
|
|
4886
|
+
return { [INSTALLED_PLUGINS_ROUTE]: async (search) => {
|
|
4887
|
+
return {
|
|
4888
|
+
ok: true,
|
|
4889
|
+
plugins: await installedPlugins({
|
|
4890
|
+
ledger: options.ledger,
|
|
4891
|
+
...options.shell === void 0 ? {} : { shell: options.shell },
|
|
4892
|
+
force: param(search, "force") === "1"
|
|
4893
|
+
})
|
|
4894
|
+
};
|
|
4895
|
+
} };
|
|
4896
|
+
}
|
|
4897
|
+
//#endregion
|
|
3848
4898
|
//#region src/modules/panels/model/tools.ts
|
|
3849
|
-
|
|
4899
|
+
var MACHINE_LABEL = "机器级默认板 —— 还没配过的工作区默认开这些:";
|
|
4900
|
+
var MACHINE_CAVEAT = "机器级默认板只是还没配过的工作区的默认值:已经自己配过的工作区保持自己的选择,不会被它改。";
|
|
4901
|
+
function listed(entries) {
|
|
4902
|
+
return entries.map((entry, index) => `${index + 1}. ${entry.id} · ${entry.shown ? "已启用" : "未启用"}${entry.hasCard ? "" : "(无卡片)"}${entry.source === "user" ? " · 用户改过" : ""}`).join("\n");
|
|
4903
|
+
}
|
|
4904
|
+
function render(entries, workspace, scope) {
|
|
4905
|
+
if (scope === "machine") {
|
|
4906
|
+
if (entries.length === 0) return "机器级默认板是空的(还没配过的工作区默认什么卡片都不开)——用 show 放一个上去。";
|
|
4907
|
+
return `${MACHINE_LABEL}\n${listed(entries)}`;
|
|
4908
|
+
}
|
|
3850
4909
|
const where = workspace === null ? "(不知道当前会话在哪个工作区)" : workspace;
|
|
3851
4910
|
if (entries.length === 0) return `这个工作区的面板是空的(${where})——用 show 放一个上去。`;
|
|
3852
|
-
return `当前工作区:${where}\n${entries
|
|
4911
|
+
return `当前工作区:${where}\n${listed(entries)}`;
|
|
4912
|
+
}
|
|
4913
|
+
function parseScope(raw) {
|
|
4914
|
+
if (raw === void 0) return DEFAULT_BOARD_TARGET;
|
|
4915
|
+
if (typeof raw === "string" && BOARD_TARGETS.includes(raw)) return raw;
|
|
4916
|
+
throw new Error(`不认识的 scope:${String(raw)}(可用:workspace / machine)`);
|
|
3853
4917
|
}
|
|
3854
4918
|
var OUTPUT_SCHEMA = {
|
|
3855
4919
|
type: "object",
|
|
@@ -3880,13 +4944,13 @@ function registerPanelTools(ctx, panels, values) {
|
|
|
3880
4944
|
}
|
|
3881
4945
|
const tool = {
|
|
3882
4946
|
name: "workbench_panels",
|
|
3883
|
-
description: "
|
|
4947
|
+
description: "看或调右侧面板:启用哪些套件、什么顺序。启用/收起 = 卡片与 agent 工具一起上下线(别的项目不受影响,不删数据)。收起是\"这个工作区明确不要它\",连那些在机器级默认板上开着的套件也照样在这个工作区收起来,之后 show 能放回来。默认只动当前工作区(scope=workspace);scope=machine 动的是\"机器级默认板\" —— 机器级默认板只是还没配过的工作区的默认值,已经自己配过的工作区保持自己的选择,不会被它改。在那份板上 hide = 明确写下\"不要它\",这一笔比名单更管用:名单里还列着它也算不要,没配过的工作区因此看不到它;show 会把这笔划掉、把它放回去。",
|
|
3884
4948
|
parameters: {
|
|
3885
4949
|
type: "object",
|
|
3886
4950
|
properties: {
|
|
3887
4951
|
action: {
|
|
3888
4952
|
type: "string",
|
|
3889
|
-
description: "show
|
|
4953
|
+
description: "show=这个工作区要它(之前收起的也放回来);hide=这个工作区明确不要它(卡片与工具一起下线,默认在机器上也开着的套件同样收起,别的项目不受影响);move=调顺序;reset=这个工作区不再有自己的选择(之前收起、启用的都忘掉;它从此明确\"什么都不要\",机器级默认板也管不到它,要哪一块用 show 放回来)。",
|
|
3890
4954
|
enum: [
|
|
3891
4955
|
"list",
|
|
3892
4956
|
"show",
|
|
@@ -3902,6 +4966,11 @@ function registerPanelTools(ctx, panels, values) {
|
|
|
3902
4966
|
index: {
|
|
3903
4967
|
type: "integer",
|
|
3904
4968
|
description: "move 的位置,0 基(0=最上面)。"
|
|
4969
|
+
},
|
|
4970
|
+
scope: {
|
|
4971
|
+
type: "string",
|
|
4972
|
+
description: "workspace(默认)=只动当前工作区;machine=动机器级默认板,那是还没配过的工作区的默认值 —— 已经自己配过的工作区保持自己的选择,不受它影响。",
|
|
4973
|
+
enum: ["workspace", "machine"]
|
|
3905
4974
|
}
|
|
3906
4975
|
},
|
|
3907
4976
|
required: ["action"],
|
|
@@ -3920,8 +4989,9 @@ function registerPanelTools(ctx, panels, values) {
|
|
|
3920
4989
|
async execute(args, exec) {
|
|
3921
4990
|
const action = args?.action ?? "list";
|
|
3922
4991
|
const id = args?.id;
|
|
4992
|
+
const scope = parseScope(args?.scope);
|
|
3923
4993
|
const workspace = sessionWorkspace(exec);
|
|
3924
|
-
const list = () => panels.list(workspace);
|
|
4994
|
+
const list = () => panels.list(workspace, scope);
|
|
3925
4995
|
if (![
|
|
3926
4996
|
"list",
|
|
3927
4997
|
"show",
|
|
@@ -3931,39 +5001,41 @@ function registerPanelTools(ctx, panels, values) {
|
|
|
3931
5001
|
].includes(action)) throw new Error(`不认识的 action:${action}(可用:list / show / hide / move / reset)`);
|
|
3932
5002
|
if (action === "list") {
|
|
3933
5003
|
const units = list();
|
|
3934
|
-
|
|
3935
|
-
|
|
5004
|
+
if (scope === "workspace") {
|
|
5005
|
+
const entry = panelsHandoffEntry(workspace === null ? {} : { path: workspace });
|
|
5006
|
+
if (entry !== void 0) values?.hand(entry, units);
|
|
5007
|
+
}
|
|
3936
5008
|
return {
|
|
3937
5009
|
units,
|
|
3938
|
-
text: render(units, workspace)
|
|
5010
|
+
text: render(units, workspace, scope)
|
|
3939
5011
|
};
|
|
3940
5012
|
}
|
|
3941
5013
|
if (action === "reset") {
|
|
3942
|
-
const units = panels.
|
|
5014
|
+
const units = panels.reset(workspace, scope);
|
|
3943
5015
|
return {
|
|
3944
5016
|
units,
|
|
3945
|
-
text:
|
|
5017
|
+
text: scope === "machine" ? `机器级默认板已清空(是"配成空",不是"没配过")——还没配过的工作区从此默认什么都不开。${MACHINE_CAVEAT}\n${render(units, workspace, scope)}` : `这个工作区不再有自己的面板选择(之前收起、启用的都忘掉了;它从此明确"什么都不要"—— 机器级默认板也管不到它,要哪一块用 show 放回来)。\n${render(units, workspace, scope)}`
|
|
3946
5018
|
};
|
|
3947
5019
|
}
|
|
3948
5020
|
if (id === void 0) throw new Error(`${action} 需要一个 id(例如 coding-issues)。当前套件:${list().map((entry) => entry.id).join(", ")}`);
|
|
3949
5021
|
if (action === "show") {
|
|
3950
|
-
const units = panels.show(id, workspace);
|
|
5022
|
+
const units = panels.show(id, workspace, scope);
|
|
3951
5023
|
return {
|
|
3952
5024
|
units,
|
|
3953
|
-
text: `已启用 ${id} ——
|
|
5025
|
+
text: scope === "machine" ? `已把 ${id} 放进机器级默认板 —— 还没配过的工作区会带上它(卡片与工具一起上线);那份板上要是给它写过"不要",这一下也划掉了。${MACHINE_CAVEAT}\n${render(units, workspace, scope)}` : `已启用 ${id} —— 这个工作区要它:之前收起的也放回来了,卡片与工具一起上线。\n${render(units, workspace, scope)}`
|
|
3954
5026
|
};
|
|
3955
5027
|
}
|
|
3956
5028
|
if (action === "hide") {
|
|
3957
|
-
const units = panels.hide(id, workspace);
|
|
5029
|
+
const units = panels.hide(id, workspace, scope);
|
|
3958
5030
|
return {
|
|
3959
5031
|
units,
|
|
3960
|
-
text: `已收起 ${id}
|
|
5032
|
+
text: scope === "machine" ? `已把 ${id} 从机器级默认板划掉,并在那份板上明确写下"不要它" —— 还没配过的工作区从此看不到它(卡片与工具一起下线);名单里还列着它也一样不算数,要放回来用 show。${MACHINE_CAVEAT}\n${render(units, workspace, scope)}` : `已收起 ${id} —— 这个工作区明确不要它:卡片与工具一起下线(默认在机器上也开着的套件同样收起),别的项目不受影响;要放回来用 show。\n${render(units, workspace, scope)}`
|
|
3961
5033
|
};
|
|
3962
5034
|
}
|
|
3963
|
-
const units = panels.move(id, args?.index ?? 0, workspace);
|
|
5035
|
+
const units = panels.move(id, args?.index ?? 0, workspace, scope);
|
|
3964
5036
|
return {
|
|
3965
5037
|
units,
|
|
3966
|
-
text: `已把 ${id} 放到第 ${(args?.index ?? 0) + 1} 位。\n${render(units, workspace)}`
|
|
5038
|
+
text: scope === "machine" ? `已把 ${id} 放到机器级默认板第 ${(args?.index ?? 0) + 1} 位 —— 只影响还没配过的工作区。${MACHINE_CAVEAT}\n${render(units, workspace, scope)}` : `已把 ${id} 放到第 ${(args?.index ?? 0) + 1} 位。\n${render(units, workspace, scope)}`
|
|
3967
5039
|
};
|
|
3968
5040
|
}
|
|
3969
5041
|
};
|
|
@@ -3971,26 +5043,44 @@ function registerPanelTools(ctx, panels, values) {
|
|
|
3971
5043
|
}
|
|
3972
5044
|
//#endregion
|
|
3973
5045
|
//#region src/app/units/host.ts
|
|
3974
|
-
var
|
|
5046
|
+
var OWNED = [{
|
|
5047
|
+
unit: panelsUnit.id,
|
|
3975
5048
|
manifest: panelsUnit,
|
|
3976
|
-
mount({ ctx, logger, manifests, opencliCommand, probe, store, values }) {
|
|
5049
|
+
mount({ ctx, logger, manifests, ownedUnits, opencliCommand, probe, store, values }) {
|
|
3977
5050
|
const panels = createPanelsService({
|
|
3978
5051
|
manifests,
|
|
3979
5052
|
store,
|
|
5053
|
+
owned: ownedUnits,
|
|
3980
5054
|
logger
|
|
3981
5055
|
});
|
|
3982
5056
|
ctx.provide(PANELS_SERVICE, panels);
|
|
3983
5057
|
const profileDir = profileDirOf(ctx);
|
|
5058
|
+
const shell = ctx.get("shell");
|
|
5059
|
+
const ledger = createPluginLedger({
|
|
5060
|
+
store,
|
|
5061
|
+
home: isolatedHome()
|
|
5062
|
+
});
|
|
3984
5063
|
const market = createMarketService({
|
|
3985
5064
|
manifests: manifests(),
|
|
3986
5065
|
manifestsNow: manifests,
|
|
3987
5066
|
...probe === void 0 ? {} : { probe },
|
|
3988
|
-
shell
|
|
5067
|
+
shell,
|
|
3989
5068
|
...opencliCommand === void 0 ? {} : { command: opencliCommand },
|
|
3990
5069
|
bundles: readBundleManager(ctx),
|
|
3991
5070
|
installed: (pkg) => readInstalledModule(profileDir, pkg),
|
|
3992
5071
|
modules: () => profileModules(profileDir),
|
|
3993
5072
|
registries: createRegistryNotebook(profileDir),
|
|
5073
|
+
ledger,
|
|
5074
|
+
references: () => {
|
|
5075
|
+
const gaps = declarationGaps(profileDir);
|
|
5076
|
+
return gaps === void 0 ? {
|
|
5077
|
+
trustworthy: false,
|
|
5078
|
+
unreadable: []
|
|
5079
|
+
} : {
|
|
5080
|
+
trustworthy: gaps.length === 0,
|
|
5081
|
+
unreadable: gaps.map((gap) => gap.pkg)
|
|
5082
|
+
};
|
|
5083
|
+
},
|
|
3994
5084
|
onUninstalled: (unit) => {
|
|
3995
5085
|
panels.release(unit);
|
|
3996
5086
|
},
|
|
@@ -4001,7 +5091,13 @@ var HOST_UNITS = [{
|
|
|
4001
5091
|
logger
|
|
4002
5092
|
});
|
|
4003
5093
|
return {
|
|
4004
|
-
routes:
|
|
5094
|
+
routes: {
|
|
5095
|
+
...panelRoutes(panels, values),
|
|
5096
|
+
...pluginRoutes({
|
|
5097
|
+
ledger,
|
|
5098
|
+
...shell === void 0 ? {} : { shell }
|
|
5099
|
+
})
|
|
5100
|
+
},
|
|
4005
5101
|
registerTools: () => [
|
|
4006
5102
|
...registerPanelTools(ctx, panels, values),
|
|
4007
5103
|
...registerMarketTools(ctx, market),
|
|
@@ -4010,7 +5106,14 @@ var HOST_UNITS = [{
|
|
|
4010
5106
|
factsOf: (call) => panelsFactsOf(call, manifests()),
|
|
4011
5107
|
needsApproval: panelsApproval,
|
|
4012
5108
|
gate: {
|
|
4013
|
-
deniedTools: (workspace) =>
|
|
5109
|
+
deniedTools: (workspace) => {
|
|
5110
|
+
const boards = panels.boards(workspace);
|
|
5111
|
+
const owned = new Set(ownedUnits());
|
|
5112
|
+
return [...manifests().flatMap((manifest) => owned.has(manifest.id) || resolveScope({
|
|
5113
|
+
id: manifest.id,
|
|
5114
|
+
boards
|
|
5115
|
+
}) ? [] : manifest.tools.map((tool) => tool.name)), ...inWorkshopSession(ctx, workspace) ? [] : [DIY_TOOL]];
|
|
5116
|
+
},
|
|
4014
5117
|
subscribe: (listener) => panels.subscribe(listener)
|
|
4015
5118
|
},
|
|
4016
5119
|
prefs: {
|
|
@@ -4025,7 +5128,154 @@ var HOST_UNITS = [{
|
|
|
4025
5128
|
}
|
|
4026
5129
|
}];
|
|
4027
5130
|
function assembleHostUnits(registry) {
|
|
4028
|
-
for (const face of
|
|
5131
|
+
for (const face of OWNED) registry.registerOwned(face);
|
|
5132
|
+
}
|
|
5133
|
+
//#endregion
|
|
5134
|
+
//#region src/shared/api/host/runtime-seed.ts
|
|
5135
|
+
var WORKSHOP_SKILLS_DIR = "workbench-module-diy";
|
|
5136
|
+
function runtimeSeedPaths(root) {
|
|
5137
|
+
const opencliRoot = join(root, "opencli");
|
|
5138
|
+
const opencliBinDir = join(opencliRoot, "bin");
|
|
5139
|
+
const workshop = join(root, "diy");
|
|
5140
|
+
return {
|
|
5141
|
+
root,
|
|
5142
|
+
entryDir: join(root, "bin"),
|
|
5143
|
+
opencliRoot,
|
|
5144
|
+
opencliBinDir,
|
|
5145
|
+
opencliBin: join(opencliBinDir, "opencli"),
|
|
5146
|
+
opencliHome: join(opencliRoot, "home"),
|
|
5147
|
+
workshop,
|
|
5148
|
+
workshopPackages: join(workshop, "packages"),
|
|
5149
|
+
workshopManifest: join(workshop, "package.json"),
|
|
5150
|
+
skillsLink: join(workshop, ".agents", "skills", WORKSHOP_SKILLS_DIR)
|
|
5151
|
+
};
|
|
5152
|
+
}
|
|
5153
|
+
function runtimeSeedRootOf(ctx) {
|
|
5154
|
+
const homePath = ctx.get("dshHomePath");
|
|
5155
|
+
if (typeof homePath !== "function") return null;
|
|
5156
|
+
try {
|
|
5157
|
+
return homePath("workbench");
|
|
5158
|
+
} catch {
|
|
5159
|
+
return null;
|
|
5160
|
+
}
|
|
5161
|
+
}
|
|
5162
|
+
function packageRootOf(moduleUrl) {
|
|
5163
|
+
return dirname(dirname(dirname(fileURLToPath(moduleUrl))));
|
|
5164
|
+
}
|
|
5165
|
+
function isInstalledCopy(packageRoot) {
|
|
5166
|
+
return packageRoot.split(sep).includes("node_modules");
|
|
5167
|
+
}
|
|
5168
|
+
function opencliWrapperScript(packageRoot, home) {
|
|
5169
|
+
const script = join(packageRoot, "bin", "opencli-isolated");
|
|
5170
|
+
return `#!/bin/sh\nexec env DSH_HOME=${quoteShellArg(home)} bash ${quoteShellArg(script)} "$@"\n`;
|
|
5171
|
+
}
|
|
5172
|
+
function workshopSeedPlan(packageRoot, home, root) {
|
|
5173
|
+
const script = join(packageRoot, "scripts", "seed-workshop.sh");
|
|
5174
|
+
const env = { DSH_HOME: home };
|
|
5175
|
+
const log = join(root, "seed.log");
|
|
5176
|
+
return {
|
|
5177
|
+
entry: {
|
|
5178
|
+
file: "bash",
|
|
5179
|
+
args: [script, "entry"],
|
|
5180
|
+
env,
|
|
5181
|
+
log
|
|
5182
|
+
},
|
|
5183
|
+
full: {
|
|
5184
|
+
file: "bash",
|
|
5185
|
+
args: [script],
|
|
5186
|
+
env,
|
|
5187
|
+
log
|
|
5188
|
+
}
|
|
5189
|
+
};
|
|
5190
|
+
}
|
|
5191
|
+
function runDetached(command, onError, spawnImpl = spawn) {
|
|
5192
|
+
let fd = null;
|
|
5193
|
+
try {
|
|
5194
|
+
fd = openSync(command.log, "a");
|
|
5195
|
+
const child = spawnImpl(command.file, [...command.args], {
|
|
5196
|
+
env: {
|
|
5197
|
+
...process.env,
|
|
5198
|
+
...command.env
|
|
5199
|
+
},
|
|
5200
|
+
detached: true,
|
|
5201
|
+
stdio: [
|
|
5202
|
+
"ignore",
|
|
5203
|
+
fd,
|
|
5204
|
+
fd
|
|
5205
|
+
]
|
|
5206
|
+
});
|
|
5207
|
+
child.on("error", (error) => onError(`自举脚本没跑起来(${command.file}):${String(error)}`));
|
|
5208
|
+
child.unref();
|
|
5209
|
+
} catch (error) {
|
|
5210
|
+
onError(`自举脚本没跑起来(${command.file}):${String(error)}`);
|
|
5211
|
+
} finally {
|
|
5212
|
+
if (fd !== null) closeQuietly(fd);
|
|
5213
|
+
}
|
|
5214
|
+
}
|
|
5215
|
+
function closeQuietly(fd) {
|
|
5216
|
+
try {
|
|
5217
|
+
closeSync(fd);
|
|
5218
|
+
} catch {
|
|
5219
|
+
return;
|
|
5220
|
+
}
|
|
5221
|
+
}
|
|
5222
|
+
function prependPath(entryDir, env = process.env) {
|
|
5223
|
+
const parts = (env.PATH ?? "").split(delimiter).filter((part) => part !== "");
|
|
5224
|
+
if (parts.includes(entryDir)) return false;
|
|
5225
|
+
env.PATH = [entryDir, ...parts].join(delimiter);
|
|
5226
|
+
return true;
|
|
5227
|
+
}
|
|
5228
|
+
function seedRuntimeFiles(options) {
|
|
5229
|
+
const paths = runtimeSeedPaths(options.root);
|
|
5230
|
+
const done = [];
|
|
5231
|
+
try {
|
|
5232
|
+
for (const dir of [
|
|
5233
|
+
paths.entryDir,
|
|
5234
|
+
paths.opencliBinDir,
|
|
5235
|
+
paths.opencliHome,
|
|
5236
|
+
paths.workshopPackages
|
|
5237
|
+
]) mkdirSync(dir, { recursive: true });
|
|
5238
|
+
const wanted = opencliWrapperScript(options.packageRoot, options.home);
|
|
5239
|
+
if (readText(paths.opencliBin) !== wanted) {
|
|
5240
|
+
rmSync(paths.opencliBin, { force: true });
|
|
5241
|
+
writeFileSync(paths.opencliBin, wanted, "utf8");
|
|
5242
|
+
chmodSync(paths.opencliBin, 493);
|
|
5243
|
+
done.push(paths.opencliBin);
|
|
5244
|
+
}
|
|
5245
|
+
if (linkSkills(paths.skillsLink, join(options.packageRoot, "skills", "workbench-module-diy"), options.log)) done.push(paths.skillsLink);
|
|
5246
|
+
} catch (error) {
|
|
5247
|
+
options.log?.(`自举没做完:${String(error)}`);
|
|
5248
|
+
}
|
|
5249
|
+
return done;
|
|
5250
|
+
}
|
|
5251
|
+
function readText(file) {
|
|
5252
|
+
try {
|
|
5253
|
+
return readFileSync(file, "utf8");
|
|
5254
|
+
} catch {
|
|
5255
|
+
return null;
|
|
5256
|
+
}
|
|
5257
|
+
}
|
|
5258
|
+
function readLink(link) {
|
|
5259
|
+
try {
|
|
5260
|
+
return readlinkSync(link);
|
|
5261
|
+
} catch {
|
|
5262
|
+
return null;
|
|
5263
|
+
}
|
|
5264
|
+
}
|
|
5265
|
+
function linkSkills(link, source, log) {
|
|
5266
|
+
if (!existsSync(source) || readLink(link) === source) return false;
|
|
5267
|
+
try {
|
|
5268
|
+
mkdirSync(dirname(link), { recursive: true });
|
|
5269
|
+
rmSync(link, {
|
|
5270
|
+
force: true,
|
|
5271
|
+
recursive: true
|
|
5272
|
+
});
|
|
5273
|
+
symlinkSync(source, link, "dir");
|
|
5274
|
+
return true;
|
|
5275
|
+
} catch (error) {
|
|
5276
|
+
log?.(`DIY 指引没挂上(${link}):${String(error)}`);
|
|
5277
|
+
return false;
|
|
5278
|
+
}
|
|
4029
5279
|
}
|
|
4030
5280
|
//#endregion
|
|
4031
5281
|
//#region src/shared/api/host/events.ts
|
|
@@ -4197,6 +5447,7 @@ function bundleRegistriesHandler(ctx) {
|
|
|
4197
5447
|
};
|
|
4198
5448
|
};
|
|
4199
5449
|
}
|
|
5450
|
+
var refusalLog = /* @__PURE__ */ new Map();
|
|
4200
5451
|
function moduleVersionsHandler(ctx, shell) {
|
|
4201
5452
|
return async (search) => {
|
|
4202
5453
|
const manager = readBundleManager(ctx);
|
|
@@ -4204,7 +5455,9 @@ function moduleVersionsHandler(ctx, shell) {
|
|
|
4204
5455
|
ok: true,
|
|
4205
5456
|
modules: []
|
|
4206
5457
|
};
|
|
4207
|
-
const
|
|
5458
|
+
const dir = profileDirOf(ctx);
|
|
5459
|
+
const found = profileModules(dir);
|
|
5460
|
+
const refusals = new Map((ctx.get("workbenchUnits")?.refusals() ?? []).filter((one) => one.unit !== "").map((one) => [one.unit, one.reasons]));
|
|
4208
5461
|
const force = param(search, "force") === "1";
|
|
4209
5462
|
const fallback = (await manager.registries())?.resolved ?? void 0;
|
|
4210
5463
|
return {
|
|
@@ -4217,17 +5470,66 @@ function moduleVersionsHandler(ctx, shell) {
|
|
|
4217
5470
|
registry,
|
|
4218
5471
|
force
|
|
4219
5472
|
});
|
|
5473
|
+
const raw = moduleDeclarationOf(dir, module.pkg);
|
|
5474
|
+
const declared = raw === void 0 ? [] : declarationProblemsOf(module.pkg, raw);
|
|
5475
|
+
const refused = refusals.get(module.unit) ?? (declared.length === 0 ? void 0 : declared);
|
|
5476
|
+
if (refused === void 0) refusalLog.delete(module.pkg);
|
|
5477
|
+
else {
|
|
5478
|
+
const line = refused.join(";");
|
|
5479
|
+
if (refusalLog.get(module.pkg) !== line) {
|
|
5480
|
+
refusalLog.set(module.pkg, line);
|
|
5481
|
+
ctx.logger?.warn?.(`[dsh-workbench] ${module.pkg} 装上了但用不了:${line}`);
|
|
5482
|
+
}
|
|
5483
|
+
}
|
|
4220
5484
|
return {
|
|
4221
5485
|
unit: module.unit,
|
|
4222
5486
|
pkg: module.pkg,
|
|
4223
5487
|
installed: module.version,
|
|
4224
5488
|
...latest === void 0 ? {} : { latest },
|
|
4225
|
-
...module.local === true ? { local: true } : {}
|
|
5489
|
+
...module.local === true ? { local: true } : {},
|
|
5490
|
+
...refused === void 0 ? {} : { refused }
|
|
4226
5491
|
};
|
|
4227
5492
|
}))
|
|
4228
5493
|
};
|
|
4229
5494
|
};
|
|
4230
5495
|
}
|
|
5496
|
+
function refusedInspection(problems) {
|
|
5497
|
+
if (problems.length === 0) return void 0;
|
|
5498
|
+
return {
|
|
5499
|
+
status: "refused",
|
|
5500
|
+
problem: "not-a-workbench-module",
|
|
5501
|
+
reason: problems.join(";")
|
|
5502
|
+
};
|
|
5503
|
+
}
|
|
5504
|
+
async function declarationRefusal(spec, inspection, ctx, manager) {
|
|
5505
|
+
if (inspection.status !== "accepted" || inspection.name === void 0) return void 0;
|
|
5506
|
+
if (inspection.kind === "path") return refusedInspection(declarationRefusalOf(inspection.name, pathDeclarationOf(spec)));
|
|
5507
|
+
if (inspection.kind !== "registry") return void 0;
|
|
5508
|
+
const shell = ctx.get("shell");
|
|
5509
|
+
if (shell === void 0) return void 0;
|
|
5510
|
+
const registry = typeof inspection.registry === "string" ? inspection.registry : (await manager.registries())?.resolved ?? void 0;
|
|
5511
|
+
if (registry === void 0) return void 0;
|
|
5512
|
+
const raw = await registryDeclarationOf({
|
|
5513
|
+
shell,
|
|
5514
|
+
name: inspection.name,
|
|
5515
|
+
registry
|
|
5516
|
+
});
|
|
5517
|
+
return refusedInspection(declarationRefusalOf(inspection.name, raw));
|
|
5518
|
+
}
|
|
5519
|
+
async function packageRefusal(spec, inspection, ctx, manager) {
|
|
5520
|
+
if (inspection.status !== "accepted") return void 0;
|
|
5521
|
+
const shell = ctx.get("shell");
|
|
5522
|
+
const registry = typeof inspection.registry === "string" ? inspection.registry : (await manager.registries())?.resolved ?? void 0;
|
|
5523
|
+
return (await packageRouteOf({
|
|
5524
|
+
spec,
|
|
5525
|
+
...shell === void 0 ? {} : { shell },
|
|
5526
|
+
...registry === void 0 ? {} : { registry }
|
|
5527
|
+
}))?.kind === "neither" ? {
|
|
5528
|
+
status: "refused",
|
|
5529
|
+
problem: "not-a-module-or-plugin",
|
|
5530
|
+
reason: refusalReason()
|
|
5531
|
+
} : void 0;
|
|
5532
|
+
}
|
|
4231
5533
|
function bundleInspectHandler(ctx) {
|
|
4232
5534
|
return async (search) => {
|
|
4233
5535
|
const spec = param(search, "spec");
|
|
@@ -4256,7 +5558,7 @@ function bundleInspectHandler(ctx) {
|
|
|
4256
5558
|
};
|
|
4257
5559
|
return {
|
|
4258
5560
|
ok: true,
|
|
4259
|
-
inspection
|
|
5561
|
+
inspection: await packageRefusal(spec, inspection, ctx, manager) ?? await declarationRefusal(spec, inspection, ctx, manager) ?? inspection
|
|
4260
5562
|
};
|
|
4261
5563
|
};
|
|
4262
5564
|
}
|
|
@@ -4267,6 +5569,9 @@ var WORKSHOP_TITLE = "工作台DIY";
|
|
|
4267
5569
|
//#region src/shared/api/dsh-plugins.ts
|
|
4268
5570
|
var DSH_PLUGINS_ROUTE = "dsh-plugins";
|
|
4269
5571
|
//#endregion
|
|
5572
|
+
//#region src/shared/api/unit-manifests.ts
|
|
5573
|
+
var UNIT_MANIFESTS_ROUTE = "unit-manifests";
|
|
5574
|
+
//#endregion
|
|
4270
5575
|
//#region src/shared/api/host/context-inject.ts
|
|
4271
5576
|
var CONTEXT_SOURCE_KIND = "workbench";
|
|
4272
5577
|
function collectContextRefs(messages) {
|
|
@@ -4965,7 +6270,7 @@ function failureReasonOf(error) {
|
|
|
4965
6270
|
function fail(ledger, item, reason) {
|
|
4966
6271
|
ledger.failed.add(item);
|
|
4967
6272
|
ledger.failures.push({
|
|
4968
|
-
id: item.
|
|
6273
|
+
id: item.host.manifest.id,
|
|
4969
6274
|
reason
|
|
4970
6275
|
});
|
|
4971
6276
|
}
|
|
@@ -4975,7 +6280,7 @@ function collectRoutes(mounted, ledger) {
|
|
|
4975
6280
|
const routesOfUnit = /* @__PURE__ */ new Map();
|
|
4976
6281
|
for (const item of mounted) {
|
|
4977
6282
|
if (ledger.failed.has(item)) continue;
|
|
4978
|
-
const unit = item.
|
|
6283
|
+
const unit = item.host.manifest.id;
|
|
4979
6284
|
const accepted = [];
|
|
4980
6285
|
let problem;
|
|
4981
6286
|
for (const [key, handler] of Object.entries(item.result.routes)) {
|
|
@@ -5013,7 +6318,7 @@ function collectNamed(mounted, ledger, pickMany, label) {
|
|
|
5013
6318
|
const out = [];
|
|
5014
6319
|
for (const item of mounted) {
|
|
5015
6320
|
if (ledger.failed.has(item)) continue;
|
|
5016
|
-
const unit = item.
|
|
6321
|
+
const unit = item.host.manifest.id;
|
|
5017
6322
|
const entries = pickMany(item);
|
|
5018
6323
|
if (entries.length === 0) continue;
|
|
5019
6324
|
const clash = entries.find((entry) => owner.has(entry.resource));
|
|
@@ -5036,7 +6341,7 @@ function collectContextRenderers(mounted, ledger) {
|
|
|
5036
6341
|
const owner = /* @__PURE__ */ new Map();
|
|
5037
6342
|
for (const item of mounted) {
|
|
5038
6343
|
if (ledger.failed.has(item)) continue;
|
|
5039
|
-
const unit = item.
|
|
6344
|
+
const unit = item.host.manifest.id;
|
|
5040
6345
|
const context = item.result.context;
|
|
5041
6346
|
if (context === void 0) continue;
|
|
5042
6347
|
const clash = context.kinds.find((kind) => owner.has(kind));
|
|
@@ -5060,10 +6365,10 @@ function pickProvider(mounted, ledger, pick, label) {
|
|
|
5060
6365
|
if (value === void 0) continue;
|
|
5061
6366
|
if (chosen === void 0) {
|
|
5062
6367
|
chosen = value;
|
|
5063
|
-
chosenBy = item.
|
|
6368
|
+
chosenBy = item.host.manifest.id;
|
|
5064
6369
|
continue;
|
|
5065
6370
|
}
|
|
5066
|
-
fail(ledger, item, `${label}只能有一个提供者,现在是:${chosenBy}、${item.
|
|
6371
|
+
fail(ledger, item, `${label}只能有一个提供者,现在是:${chosenBy}、${item.host.manifest.id}`);
|
|
5067
6372
|
}
|
|
5068
6373
|
return chosen;
|
|
5069
6374
|
}
|
|
@@ -5081,8 +6386,9 @@ function collectWarmReads(mounted) {
|
|
|
5081
6386
|
return reads;
|
|
5082
6387
|
};
|
|
5083
6388
|
}
|
|
5084
|
-
function mountUnits(
|
|
6389
|
+
function mountUnits(units, deps) {
|
|
5085
6390
|
const store = deps.store ?? createWorkspaceStore({ root: null });
|
|
6391
|
+
const ownedUnits = deps.ownedUnits ?? (() => []);
|
|
5086
6392
|
const mounted = [];
|
|
5087
6393
|
const skipped = [];
|
|
5088
6394
|
const longTaskDisposers = /* @__PURE__ */ new Map();
|
|
@@ -5090,16 +6396,16 @@ function mountUnits(faces, deps) {
|
|
|
5090
6396
|
failed: /* @__PURE__ */ new Set(),
|
|
5091
6397
|
failures: []
|
|
5092
6398
|
};
|
|
5093
|
-
for (const
|
|
5094
|
-
const readiness = unitReadiness(
|
|
6399
|
+
for (const host of units) {
|
|
6400
|
+
const readiness = unitReadiness(host.manifest, (dep) => deps.ctx.get(dep));
|
|
5095
6401
|
if (!readiness.canMount) {
|
|
5096
6402
|
skipped.push({
|
|
5097
|
-
id:
|
|
6403
|
+
id: host.manifest.id,
|
|
5098
6404
|
missing: readiness.missingHard
|
|
5099
6405
|
});
|
|
5100
6406
|
continue;
|
|
5101
6407
|
}
|
|
5102
|
-
const unit =
|
|
6408
|
+
const unit = host.manifest.id;
|
|
5103
6409
|
const longTasks = deps.longTasks === void 0 ? void 0 : createLongTaskHost({
|
|
5104
6410
|
scope: deps.longTasks.scope,
|
|
5105
6411
|
unit,
|
|
@@ -5107,10 +6413,12 @@ function mountUnits(faces, deps) {
|
|
|
5107
6413
|
});
|
|
5108
6414
|
let result;
|
|
5109
6415
|
try {
|
|
5110
|
-
result = face.mount({
|
|
6416
|
+
result = host.face.mount({
|
|
6417
|
+
manifest: host.manifest,
|
|
5111
6418
|
ctx: deps.ctx,
|
|
5112
6419
|
logger: deps.logger,
|
|
5113
6420
|
manifests: deps.manifests,
|
|
6421
|
+
ownedUnits,
|
|
5114
6422
|
...deps.opencliCommand === void 0 ? {} : { opencliCommand: deps.opencliCommand },
|
|
5115
6423
|
...deps.probe === void 0 ? {} : { probe: deps.probe },
|
|
5116
6424
|
...deps.values === void 0 ? {} : { values: deps.values },
|
|
@@ -5126,7 +6434,7 @@ function mountUnits(faces, deps) {
|
|
|
5126
6434
|
continue;
|
|
5127
6435
|
}
|
|
5128
6436
|
const item = {
|
|
5129
|
-
|
|
6437
|
+
host,
|
|
5130
6438
|
readiness,
|
|
5131
6439
|
result: withRunsRoute(result, unit)
|
|
5132
6440
|
};
|
|
@@ -5142,7 +6450,7 @@ function mountUnits(faces, deps) {
|
|
|
5142
6450
|
for (const item of mounted) {
|
|
5143
6451
|
if (ledger.failed.has(item)) continue;
|
|
5144
6452
|
const declare = item.result.factsOf;
|
|
5145
|
-
if (declare !== void 0) factsOf.set(item.
|
|
6453
|
+
if (declare !== void 0) factsOf.set(item.host.manifest.id, declare);
|
|
5146
6454
|
}
|
|
5147
6455
|
const gate = pickProvider(mounted, ledger, (item) => item.result.gate, "启停闸门");
|
|
5148
6456
|
const provision = pickProvider(mounted, ledger, (item) => item.result.provision, "机器供给");
|
|
@@ -5175,30 +6483,71 @@ function mountUnits(faces, deps) {
|
|
|
5175
6483
|
}
|
|
5176
6484
|
//#endregion
|
|
5177
6485
|
//#region src/shared/api/host/unit-registry.ts
|
|
5178
|
-
function
|
|
5179
|
-
|
|
5180
|
-
|
|
6486
|
+
function sourceLabel(source) {
|
|
6487
|
+
return source.kind === "owned" ? "外壳自己" : source.pkg;
|
|
6488
|
+
}
|
|
6489
|
+
function createUnitRegistry(options) {
|
|
6490
|
+
const refusals = [];
|
|
6491
|
+
const hosts = [];
|
|
6492
|
+
const byUnit = /* @__PURE__ */ new Map();
|
|
5181
6493
|
const listeners = /* @__PURE__ */ new Set();
|
|
5182
6494
|
function notify() {
|
|
5183
6495
|
for (const listener of listeners) listener();
|
|
5184
6496
|
}
|
|
6497
|
+
function add(host) {
|
|
6498
|
+
const taken = byUnit.get(host.unit);
|
|
6499
|
+
if (taken !== void 0) throw new Error(`套件 id 重复:${host.unit}(id 会进持久化配置,不能撞)—— 已被 ${sourceLabel(taken.source)} 占着`);
|
|
6500
|
+
byUnit.set(host.unit, host);
|
|
6501
|
+
hosts.push(host);
|
|
6502
|
+
notify();
|
|
6503
|
+
return () => {
|
|
6504
|
+
if (byUnit.get(host.unit) !== host) return;
|
|
6505
|
+
byUnit.delete(host.unit);
|
|
6506
|
+
const index = hosts.indexOf(host);
|
|
6507
|
+
if (index >= 0) hosts.splice(index, 1);
|
|
6508
|
+
notify();
|
|
6509
|
+
};
|
|
6510
|
+
}
|
|
5185
6511
|
return {
|
|
5186
6512
|
register(face) {
|
|
5187
|
-
const
|
|
5188
|
-
|
|
5189
|
-
|
|
5190
|
-
|
|
5191
|
-
|
|
5192
|
-
|
|
5193
|
-
|
|
5194
|
-
|
|
5195
|
-
faces.splice(index, 1);
|
|
5196
|
-
ids.delete(id);
|
|
6513
|
+
const unit = typeof face?.unit === "string" ? face.unit.trim() : "";
|
|
6514
|
+
const refuse = (pkg, reasons) => {
|
|
6515
|
+
refusals.push({
|
|
6516
|
+
unit,
|
|
6517
|
+
...pkg === void 0 ? {} : { pkg },
|
|
6518
|
+
reasons
|
|
6519
|
+
});
|
|
6520
|
+
options.logger?.warn?.(`[dsh-workbench] ${pkg ?? "这个模块"}没挂上(unit ${unit === "" ? "没写" : unit}):${reasons.join(";")}`);
|
|
5197
6521
|
notify();
|
|
6522
|
+
return () => {};
|
|
5198
6523
|
};
|
|
6524
|
+
if (unit === "") return refuse(void 0, ["register({ unit, mount }) 里的 unit 是空的 —— U1b 之前的旧形状是 register({ manifest, mount });现在清单由外壳从**已装包**的 dsh.workbench 读出来,模块里的 id 从 deps.manifest.id 拿(ADR-0038 §2.2)"]);
|
|
6525
|
+
const source = options.declarationOf(unit);
|
|
6526
|
+
if (source === void 0) return refuse(void 0, [`没有哪个已装模块声明了 unit「${unit}」—— 查过这份 profile 的已装清单。要么 package.json 里 dsh.workbench.unit 写错了(改完要重启工作台),要么这个包根本没被装进这份 profile`]);
|
|
6527
|
+
const read = readDeclaration(source.pkg, source.declaration);
|
|
6528
|
+
if (read.manifest === void 0) return refuse(source.pkg, read.problems);
|
|
6529
|
+
return add({
|
|
6530
|
+
unit,
|
|
6531
|
+
manifest: read.manifest,
|
|
6532
|
+
face,
|
|
6533
|
+
source: {
|
|
6534
|
+
kind: "package",
|
|
6535
|
+
pkg: source.pkg
|
|
6536
|
+
}
|
|
6537
|
+
});
|
|
6538
|
+
},
|
|
6539
|
+
registerOwned(face) {
|
|
6540
|
+
return add({
|
|
6541
|
+
unit: face.manifest.id,
|
|
6542
|
+
manifest: face.manifest,
|
|
6543
|
+
face,
|
|
6544
|
+
source: { kind: "owned" }
|
|
6545
|
+
});
|
|
5199
6546
|
},
|
|
5200
|
-
all: () =>
|
|
5201
|
-
|
|
6547
|
+
all: () => hosts,
|
|
6548
|
+
owned: () => hosts.filter((host) => host.source.kind === "owned").map((host) => host.unit),
|
|
6549
|
+
refusals: () => refusals,
|
|
6550
|
+
manifests: () => hosts.map((host) => host.manifest),
|
|
5202
6551
|
subscribe(listener) {
|
|
5203
6552
|
listeners.add(listener);
|
|
5204
6553
|
return () => listeners.delete(listener);
|
|
@@ -5206,6 +6555,20 @@ function createUnitRegistry() {
|
|
|
5206
6555
|
};
|
|
5207
6556
|
}
|
|
5208
6557
|
//#endregion
|
|
6558
|
+
//#region src/shared/api/host/unit-roster-facts.ts
|
|
6559
|
+
function rosterKeyOf(units) {
|
|
6560
|
+
return units.all().map((host) => host.unit).join("\0");
|
|
6561
|
+
}
|
|
6562
|
+
function bindRosterFacts(options) {
|
|
6563
|
+
let seen = rosterKeyOf(options.units);
|
|
6564
|
+
return options.units.subscribe(() => {
|
|
6565
|
+
const now = rosterKeyOf(options.units);
|
|
6566
|
+
if (now === seen) return;
|
|
6567
|
+
seen = now;
|
|
6568
|
+
options.bump([UNIT_MANIFESTS_ROUTE]);
|
|
6569
|
+
});
|
|
6570
|
+
}
|
|
6571
|
+
//#endregion
|
|
5209
6572
|
//#region src/shared/api/host/tool-scope.ts
|
|
5210
6573
|
function agentOf(value) {
|
|
5211
6574
|
return typeof value === "object" && value !== null ? value : void 0;
|
|
@@ -5294,9 +6657,34 @@ var inject = ["webServer"];
|
|
|
5294
6657
|
function asArgs(value) {
|
|
5295
6658
|
return typeof value === "object" && value !== null ? value : {};
|
|
5296
6659
|
}
|
|
6660
|
+
function warn(ctx, message) {
|
|
6661
|
+
ctx.logger?.warn?.(`[dsh-workbench] ${message}`);
|
|
6662
|
+
}
|
|
6663
|
+
function seedWorkbenchRuntime(ctx) {
|
|
6664
|
+
const root = runtimeSeedRootOf(ctx);
|
|
6665
|
+
if (root === null) return;
|
|
6666
|
+
const home = dirname(root);
|
|
6667
|
+
const packageRoot = packageRootOf(import.meta.url);
|
|
6668
|
+
const paths = runtimeSeedPaths(root);
|
|
6669
|
+
const log = (message) => warn(ctx, message);
|
|
6670
|
+
for (const file of seedRuntimeFiles({
|
|
6671
|
+
root,
|
|
6672
|
+
home,
|
|
6673
|
+
packageRoot,
|
|
6674
|
+
log
|
|
6675
|
+
})) log(`自举写下 ${file}`);
|
|
6676
|
+
if (prependPath(paths.entryDir)) log(`PATH 前插 ${paths.entryDir}`);
|
|
6677
|
+
const plan = workshopSeedPlan(packageRoot, home, root);
|
|
6678
|
+
runDetached(plan.entry, log);
|
|
6679
|
+
if (isInstalledCopy(packageRoot) && !existsSync(paths.workshopManifest)) {
|
|
6680
|
+
log("DIY 工坊骨架不在:后台建起来(第一次会下载依赖,几百 MB)");
|
|
6681
|
+
runDetached(plan.full, log);
|
|
6682
|
+
}
|
|
6683
|
+
}
|
|
5297
6684
|
function apply(ctx) {
|
|
5298
6685
|
const facts = createFactLog();
|
|
5299
6686
|
const values = createValueHandoff();
|
|
6687
|
+
seedWorkbenchRuntime(ctx);
|
|
5300
6688
|
const opencli = opencliCommandOf(ctx);
|
|
5301
6689
|
const routes = {
|
|
5302
6690
|
health: healthHandler(ctx, opencli),
|
|
@@ -5308,8 +6696,24 @@ function apply(ctx) {
|
|
|
5308
6696
|
};
|
|
5309
6697
|
const renderers = /* @__PURE__ */ new Map();
|
|
5310
6698
|
const shell = ctx.get("shell");
|
|
5311
|
-
|
|
6699
|
+
let roster;
|
|
6700
|
+
const profileDir = profileDirOf(ctx);
|
|
6701
|
+
const units = createUnitRegistry({
|
|
6702
|
+
declarationOf: (unit) => {
|
|
6703
|
+
if (roster === void 0) roster = new Map(profileModules(profileDir).map((row) => [row.unit, row.pkg]));
|
|
6704
|
+
const pkg = roster.get(unit);
|
|
6705
|
+
return pkg === void 0 ? void 0 : {
|
|
6706
|
+
pkg,
|
|
6707
|
+
declaration: moduleDeclarationOf(profileDir, pkg)
|
|
6708
|
+
};
|
|
6709
|
+
},
|
|
6710
|
+
logger: ctx.logger
|
|
6711
|
+
});
|
|
5312
6712
|
ctx.provide(UNITS_SERVICE, units);
|
|
6713
|
+
routes[UNIT_MANIFESTS_ROUTE] = () => ({
|
|
6714
|
+
ok: true,
|
|
6715
|
+
manifests: units.manifests()
|
|
6716
|
+
});
|
|
5313
6717
|
assembleHostUnits(units);
|
|
5314
6718
|
ctx.inject(["workspaceRegistry"], (scoped) => {
|
|
5315
6719
|
const registry = scoped.get("workspaceRegistry");
|
|
@@ -5374,14 +6778,15 @@ function apply(ctx) {
|
|
|
5374
6778
|
for (const unit of item.plan.routesOfUnit.keys()) routesOfUnitMap.delete(unit);
|
|
5375
6779
|
for (const resource of item.plan.owners.keys()) ownersMap.delete(resource);
|
|
5376
6780
|
}
|
|
5377
|
-
function mountUnit(
|
|
5378
|
-
const id =
|
|
6781
|
+
function mountUnit(host) {
|
|
6782
|
+
const id = host.unit;
|
|
5379
6783
|
let one;
|
|
5380
6784
|
try {
|
|
5381
|
-
one = mountUnits([
|
|
6785
|
+
one = mountUnits([host], {
|
|
5382
6786
|
ctx,
|
|
5383
6787
|
logger: ctx.logger,
|
|
5384
6788
|
manifests: liveManifests,
|
|
6789
|
+
ownedUnits: () => units.owned(),
|
|
5385
6790
|
store,
|
|
5386
6791
|
opencliCommand: opencli.command,
|
|
5387
6792
|
probe: (unit, options) => probeOne(unit, options?.force ?? false),
|
|
@@ -5394,7 +6799,7 @@ function apply(ctx) {
|
|
|
5394
6799
|
} catch (error) {
|
|
5395
6800
|
mountFailures.set(id, {
|
|
5396
6801
|
reason: failureReasonOf(error),
|
|
5397
|
-
|
|
6802
|
+
host
|
|
5398
6803
|
});
|
|
5399
6804
|
ctx.logger?.warn?.(`[dsh-workbench] ${id} 套件起不来(装配期):${failureReasonOf(error)}`);
|
|
5400
6805
|
return;
|
|
@@ -5403,7 +6808,7 @@ function apply(ctx) {
|
|
|
5403
6808
|
for (const failure of one.failed) {
|
|
5404
6809
|
mountFailures.set(failure.id, {
|
|
5405
6810
|
reason: failure.reason,
|
|
5406
|
-
|
|
6811
|
+
host
|
|
5407
6812
|
});
|
|
5408
6813
|
ctx.logger?.warn?.(`[dsh-workbench] ${failure.id} 套件起不来,已摘掉它这一份:${failure.reason}`);
|
|
5409
6814
|
}
|
|
@@ -5412,7 +6817,7 @@ function apply(ctx) {
|
|
|
5412
6817
|
one.disposeLongTasks();
|
|
5413
6818
|
return;
|
|
5414
6819
|
}
|
|
5415
|
-
if (slot.readiness.missingSoft.length > 0) ctx.logger?.warn?.(`[dsh-workbench] ${
|
|
6820
|
+
if (slot.readiness.missingSoft.length > 0) ctx.logger?.warn?.(`[dsh-workbench] ${host.unit} 套件降级:缺 ${slot.readiness.missingSoft.join(", ")}`);
|
|
5416
6821
|
const added = [];
|
|
5417
6822
|
try {
|
|
5418
6823
|
Object.assign(routes, one.routes);
|
|
@@ -5421,7 +6826,7 @@ function apply(ctx) {
|
|
|
5421
6826
|
for (const [unit, fn] of one.factsOf) factsOfMap.set(unit, fn);
|
|
5422
6827
|
for (const [unit, list] of one.routesOfUnit) routesOfUnitMap.set(unit, list);
|
|
5423
6828
|
for (const [resource, unit] of one.owners) ownersMap.set(resource, unit);
|
|
5424
|
-
mounted.set(
|
|
6829
|
+
mounted.set(host.unit, {
|
|
5425
6830
|
result: slot.result,
|
|
5426
6831
|
plan: one,
|
|
5427
6832
|
toolDisposers: slot.result.registerTools(),
|
|
@@ -5437,7 +6842,7 @@ function apply(ctx) {
|
|
|
5437
6842
|
one.disposeLongTasks();
|
|
5438
6843
|
mountFailures.set(id, {
|
|
5439
6844
|
reason: failureReasonOf(error),
|
|
5440
|
-
|
|
6845
|
+
host
|
|
5441
6846
|
});
|
|
5442
6847
|
ctx.logger?.warn?.(`[dsh-workbench] ${id} 套件起不来(注册期):${failureReasonOf(error)}`);
|
|
5443
6848
|
}
|
|
@@ -5461,14 +6866,14 @@ function apply(ctx) {
|
|
|
5461
6866
|
if (provision !== void 0) undoPost.push(registerProvision(ctx, provision, facts));
|
|
5462
6867
|
}
|
|
5463
6868
|
function sync() {
|
|
5464
|
-
const present = new Set(units.all().map((
|
|
6869
|
+
const present = new Set(units.all().map((one) => one.unit));
|
|
5465
6870
|
for (const id of [...mounted.keys()]) if (!present.has(id)) unmountUnit(id);
|
|
5466
6871
|
for (const id of [...mountFailures.keys()]) if (!present.has(id)) mountFailures.delete(id);
|
|
5467
|
-
for (const
|
|
5468
|
-
if (mounted.has(
|
|
5469
|
-
const already = mountFailures.get(
|
|
5470
|
-
if (already !== void 0 && already.
|
|
5471
|
-
mountUnit(
|
|
6872
|
+
for (const host of units.all()) {
|
|
6873
|
+
if (mounted.has(host.unit)) continue;
|
|
6874
|
+
const already = mountFailures.get(host.unit);
|
|
6875
|
+
if (already !== void 0 && already.host === host) continue;
|
|
6876
|
+
mountUnit(host);
|
|
5472
6877
|
}
|
|
5473
6878
|
rebindPost();
|
|
5474
6879
|
}
|
|
@@ -5508,6 +6913,10 @@ function apply(ctx) {
|
|
|
5508
6913
|
for (const dispose of undoPost) dispose?.();
|
|
5509
6914
|
}, `${PLUGIN_ID}: 卸载时把单元摘干净`);
|
|
5510
6915
|
ctx.effect(() => units.subscribe(sync), `${PLUGIN_ID}: 注册表变了就增删(装/卸一个模块 = 挂/卸它的面)`);
|
|
6916
|
+
ctx.effect(() => bindRosterFacts({
|
|
6917
|
+
units,
|
|
6918
|
+
bump: (names) => facts.bump(names)
|
|
6919
|
+
}), `${PLUGIN_ID}: 花名册变了就把清单那条事实记一笔(浏览器据此重读,不用刷新页面)`);
|
|
5511
6920
|
ctx.effect(() => {
|
|
5512
6921
|
const off = ctx.on?.("tools/post-execute", (exec, result, next) => {
|
|
5513
6922
|
const name = exec?.name === void 0 ? void 0 : String(exec.name);
|