@dp-bohrium/widget-cli 0.5.0 → 0.6.0
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/AGENTS.md +13 -5
- package/CHANGELOG.md +28 -0
- package/assets/schemas/widget-runtime-contracts.schema.json +906 -0
- package/assets/toolchain.json +8 -8
- package/dist/cli.mjs +299 -28
- package/dist/core.d.mts +16 -361
- package/dist/core.mjs +1 -1
- package/dist/development-host.d.mts +40 -4
- package/dist/{dsh-BkPMTfZ_.mjs → dsh-BL6cMLrg.mjs} +11087 -10124
- package/dist/generated-runtime-contracts-CbZY-iss.d.mts +500 -0
- package/llms.txt +2 -2
- package/package.json +8 -11
- package/assets/licenses/widget-contracts.LICENSE +0 -21
package/assets/toolchain.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"formatVersion": 3,
|
|
3
3
|
"packageName": "@dp-bohrium/widget-cli",
|
|
4
|
-
"cli": "0.
|
|
4
|
+
"cli": "0.6.0",
|
|
5
5
|
"profile": {
|
|
6
|
-
"id": "widget-sdk-0.
|
|
7
|
-
"sdk": "0.
|
|
6
|
+
"id": "widget-sdk-0.9.0",
|
|
7
|
+
"sdk": "0.9.0"
|
|
8
8
|
},
|
|
9
9
|
"protocol": {
|
|
10
10
|
"schemaVersion": 1,
|
|
11
11
|
"hostApiVersion": 1,
|
|
12
|
-
"digest": "sha256:
|
|
12
|
+
"digest": "sha256:a4d13070cc867aa5ddb838201ed47eee8871c32d48fd1485ce6c531b7ebad2b9"
|
|
13
13
|
},
|
|
14
14
|
"hostAcceptance": "unknown",
|
|
15
15
|
"documentation": {
|
|
@@ -20,9 +20,9 @@
|
|
|
20
20
|
"llms": "https://dev.dp.tech/bohrium-widget-sdk/llms.txt"
|
|
21
21
|
},
|
|
22
22
|
"digests": {
|
|
23
|
-
"compiler": "sha256:
|
|
24
|
-
"validators": "sha256:
|
|
25
|
-
"schemas": "sha256:
|
|
26
|
-
"templates": "sha256:
|
|
23
|
+
"compiler": "sha256:a45a53ac079b9249e629ecdcfc78510aca276e8c3db786bd66daded8e75d08e3",
|
|
24
|
+
"validators": "sha256:27340218c481b2b3b3964b598533f07d4cec820de148ef7ad9d00adc2e8162f9",
|
|
25
|
+
"schemas": "sha256:a4d13070cc867aa5ddb838201ed47eee8871c32d48fd1485ce6c531b7ebad2b9",
|
|
26
|
+
"templates": "sha256:85be81d7ede0a1631c08a464ce7857ffcce71691bb8d75717d97513c6496a8e5"
|
|
27
27
|
}
|
|
28
28
|
}
|
package/dist/cli.mjs
CHANGED
|
@@ -1,13 +1,123 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as normalizeArchivePath, c as
|
|
2
|
+
import { a as normalizeArchivePath, c as loadContractValidators, i as prepareWidgetPackage, l as compileAuthorManifest, n as scaffoldDshWidget, o as checkWidgetProject, s as resolveWidgetProjects, t as auditDshPlugin } from "./dsh-BL6cMLrg.mjs";
|
|
3
3
|
import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
|
|
4
|
-
import { constants, existsSync, lstatSync, readFileSync, readdirSync, realpathSync, statSync, watch } from "node:fs";
|
|
4
|
+
import { constants, existsSync, fstatSync, lstatSync, readFileSync, readdirSync, realpathSync, statSync, watch } from "node:fs";
|
|
5
5
|
import { chmod, lstat, mkdir, mkdtemp, open, readFile, readdir, realpath, rename, rm, rmdir, stat, writeFile } from "node:fs/promises";
|
|
6
6
|
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
8
|
import { spawn, spawnSync } from "node:child_process";
|
|
9
9
|
import { createServer } from "node:http";
|
|
10
10
|
import { isDeepStrictEqual } from "node:util";
|
|
11
|
+
//#region ../../scripts/widget/widget-development-observations.mjs
|
|
12
|
+
const JOURNAL_CAPACITY = 32;
|
|
13
|
+
const ATTEMPT_CAPACITY = 32;
|
|
14
|
+
const ID_PATTERN = /^[a-zA-Z0-9_-]{1,128}$/u;
|
|
15
|
+
const exact = (value, keys) => value && typeof value === "object" && !Array.isArray(value) && Object.keys(value).length === keys.length && keys.every((key) => Object.hasOwn(value, key));
|
|
16
|
+
/** 每个 SDK 开发会话独占;只有受保护的 Host control 能写入。 */
|
|
17
|
+
async function createDevelopmentObservations() {
|
|
18
|
+
const { validateWidgetDevelopmentDiagnostic, createWidgetRecoveryPlan, isWidgetRenderResult } = await loadContractValidators();
|
|
19
|
+
let cursor = 0;
|
|
20
|
+
let current = {
|
|
21
|
+
state: "unknown",
|
|
22
|
+
cursor
|
|
23
|
+
};
|
|
24
|
+
const attempts = /* @__PURE__ */ new Map();
|
|
25
|
+
const entries = [];
|
|
26
|
+
let previousFailure;
|
|
27
|
+
return {
|
|
28
|
+
snapshot(includeEntries = true) {
|
|
29
|
+
return structuredClone({
|
|
30
|
+
...current,
|
|
31
|
+
cursor,
|
|
32
|
+
oldestCursor: entries[0]?.cursor ?? cursor,
|
|
33
|
+
...includeEntries ? { entries } : {}
|
|
34
|
+
});
|
|
35
|
+
},
|
|
36
|
+
reset(generation) {
|
|
37
|
+
attempts.clear();
|
|
38
|
+
current = {
|
|
39
|
+
state: "unknown",
|
|
40
|
+
generation,
|
|
41
|
+
cursor
|
|
42
|
+
};
|
|
43
|
+
},
|
|
44
|
+
accept(input, generation) {
|
|
45
|
+
if (!exact(input, [
|
|
46
|
+
"generation",
|
|
47
|
+
"attemptId",
|
|
48
|
+
"instanceId",
|
|
49
|
+
"sequence",
|
|
50
|
+
"event"
|
|
51
|
+
]) || input.generation !== generation || typeof input.attemptId !== "string" || !ID_PATTERN.test(input.attemptId) || !(typeof input.instanceId === "string" && /^[a-zA-Z0-9_.:%-]{1,1024}$/u.test(input.instanceId)) || !Number.isSafeInteger(input.sequence) || input.sequence < 1) throw new Error("invalid observation identity");
|
|
52
|
+
const event = input.event;
|
|
53
|
+
const connected = exact(event, ["type", "supported"]) && event.type === "connected" && typeof event.supported === "boolean";
|
|
54
|
+
const diagnostic = exact(event, ["type", "diagnostic"]) && event.type === "diagnostic" && validateWidgetDevelopmentDiagnostic(event.diagnostic).ok;
|
|
55
|
+
const result = exact(event, ["type", "result"]) && event.type === "render-result" && isWidgetRenderResult(event.result);
|
|
56
|
+
if (!connected && !diagnostic && !result) throw new Error("invalid observation contract");
|
|
57
|
+
const known = attempts.get(input.attemptId);
|
|
58
|
+
if (known && (known.instanceId !== input.instanceId || input.sequence <= known.sequence)) throw new Error("stale observation");
|
|
59
|
+
if (!connected && (current.attemptId !== input.attemptId || !known)) throw new Error("inactive observation attempt");
|
|
60
|
+
if (!connected && !known.supported && (result || event.diagnostic.source === "sdk")) throw new Error("development extension was not negotiated");
|
|
61
|
+
if (connected && (known || attempts.size >= ATTEMPT_CAPACITY)) throw new Error("reused or excessive observation attempt");
|
|
62
|
+
attempts.set(input.attemptId, {
|
|
63
|
+
instanceId: input.instanceId,
|
|
64
|
+
sequence: input.sequence,
|
|
65
|
+
supported: connected ? event.supported : known.supported
|
|
66
|
+
});
|
|
67
|
+
if (connected) current = {
|
|
68
|
+
state: event.supported ? "connected" : "unsupported",
|
|
69
|
+
generation,
|
|
70
|
+
attemptId: input.attemptId,
|
|
71
|
+
instanceId: input.instanceId
|
|
72
|
+
};
|
|
73
|
+
const failure = diagnostic ? event.diagnostic : result && event.result.outcome === "failed" ? event.result.diagnostic : void 0;
|
|
74
|
+
if (failure) {
|
|
75
|
+
const fingerprint = JSON.stringify([
|
|
76
|
+
failure.source,
|
|
77
|
+
failure.code,
|
|
78
|
+
failure.phase,
|
|
79
|
+
failure.operation,
|
|
80
|
+
failure.diagnostics
|
|
81
|
+
]);
|
|
82
|
+
const repeated = previousFailure?.fingerprint === fingerprint && previousFailure.generation === generation;
|
|
83
|
+
const recovery = createWidgetRecoveryPlan(failure, {
|
|
84
|
+
executionState: "finished",
|
|
85
|
+
mayHaveExecuted: true,
|
|
86
|
+
progress: {
|
|
87
|
+
repeated,
|
|
88
|
+
hasNewEvidence: !repeated
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
previousFailure = {
|
|
92
|
+
fingerprint,
|
|
93
|
+
generation
|
|
94
|
+
};
|
|
95
|
+
current = {
|
|
96
|
+
...current,
|
|
97
|
+
state: "failed",
|
|
98
|
+
diagnostic: failure,
|
|
99
|
+
recovery,
|
|
100
|
+
renderResult: result ? event.result : void 0
|
|
101
|
+
};
|
|
102
|
+
} else if (result && event.result.outcome === "handled") {
|
|
103
|
+
const { diagnostic: _diagnostic, recovery: _recovery, ...identity } = current;
|
|
104
|
+
current = {
|
|
105
|
+
...identity,
|
|
106
|
+
state: "handled",
|
|
107
|
+
renderResult: event.result
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
const entry = {
|
|
111
|
+
cursor: ++cursor,
|
|
112
|
+
...input
|
|
113
|
+
};
|
|
114
|
+
entries.push(entry);
|
|
115
|
+
if (entries.length > JOURNAL_CAPACITY) entries.shift();
|
|
116
|
+
return this.snapshot(false);
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
//#endregion
|
|
11
121
|
//#region ../../scripts/widget/widget-dev.mjs
|
|
12
122
|
const repoRoot = resolve(fileURLToPath(new URL("../..", import.meta.url)));
|
|
13
123
|
const DEFAULT_PORT = 4300;
|
|
@@ -105,6 +215,7 @@ function usage$1() {
|
|
|
105
215
|
--watch 监听 src、manifest.json 和 package.json,变化后重新 build
|
|
106
216
|
--no-build 使用现有的 manifest.widgetConfig.entry,不执行 build
|
|
107
217
|
--port <port> 固定 Profile 服务端口(默认 ${DEFAULT_PORT})
|
|
218
|
+
--listen-fd <fd> 接管宿主已监听的 IPv4 loopback TCP socket;仅与 --no-build 一起使用
|
|
108
219
|
--help 显示帮助
|
|
109
220
|
|
|
110
221
|
开发流程:
|
|
@@ -116,6 +227,7 @@ function usage$1() {
|
|
|
116
227
|
}
|
|
117
228
|
function parseArgs(argv) {
|
|
118
229
|
const positional = [];
|
|
230
|
+
let portSpecified = false;
|
|
119
231
|
const options = {
|
|
120
232
|
open: false,
|
|
121
233
|
watch: false,
|
|
@@ -160,10 +272,17 @@ function parseArgs(argv) {
|
|
|
160
272
|
const value = Number(argv[++index]);
|
|
161
273
|
if (!Number.isInteger(value) || value < 1024 || value > 65535) throw new Error("--port 必须是 1024 到 65535 之间的整数");
|
|
162
274
|
options.port = value;
|
|
275
|
+
portSpecified = true;
|
|
276
|
+
} else if (argument === "--listen-fd") {
|
|
277
|
+
const value = argv[++index];
|
|
278
|
+
const fd = Number(value);
|
|
279
|
+
if (!/^\d+$/u.test(value) || fd < 3 || fd > 2 ** 31 - 1) throw new Error("--listen-fd 需要宿主传入的文件描述符(fd ≥ 3)");
|
|
280
|
+
options.listenFd = fd;
|
|
163
281
|
} else if (argument.startsWith("-")) throw new Error(`未知选项:${argument}`);
|
|
164
282
|
else positional.push(argument);
|
|
165
283
|
}
|
|
166
284
|
if (positional.length !== 1) throw new Error("需要一个 Widget 工程目录或 manifest.json");
|
|
285
|
+
if (options.listenFd !== void 0 && (portSpecified || options.build || options.watch)) throw new Error("--listen-fd 必须与 --no-build 一起使用,不能与 --port 或 --watch 同用");
|
|
167
286
|
return {
|
|
168
287
|
help: false,
|
|
169
288
|
project: positional[0],
|
|
@@ -721,6 +840,28 @@ function runBuild(projectRoot, { capture = false } = {}) {
|
|
|
721
840
|
});
|
|
722
841
|
});
|
|
723
842
|
}
|
|
843
|
+
async function listenForDevelopment(server, { port, listenFd }) {
|
|
844
|
+
if (listenFd !== void 0 && !fstatSync(listenFd).isSocket()) throw new Error("--listen-fd 需要宿主传入已监听的 127.0.0.1 TCP socket");
|
|
845
|
+
await new Promise((resolveListen, rejectListen) => {
|
|
846
|
+
server.once("error", rejectListen);
|
|
847
|
+
const onListen = () => {
|
|
848
|
+
server.off("error", rejectListen);
|
|
849
|
+
resolveListen();
|
|
850
|
+
};
|
|
851
|
+
if (listenFd === void 0) server.listen(port, "127.0.0.1", onListen);
|
|
852
|
+
else server.listen({
|
|
853
|
+
fd: listenFd,
|
|
854
|
+
exclusive: true
|
|
855
|
+
}, onListen);
|
|
856
|
+
});
|
|
857
|
+
const address = server.address();
|
|
858
|
+
if (address?.family !== "IPv4" || address.address !== "127.0.0.1" || !address.port) {
|
|
859
|
+
server.close();
|
|
860
|
+
server.closeAllConnections();
|
|
861
|
+
throw new Error("--listen-fd 需要宿主传入已监听的 127.0.0.1 TCP socket");
|
|
862
|
+
}
|
|
863
|
+
return `http://${address.address}:${address.port}`;
|
|
864
|
+
}
|
|
724
865
|
async function startWidgetDevelopment(projectRoot, options) {
|
|
725
866
|
const buildProject = options.buildProject ?? runBuild;
|
|
726
867
|
const emit = options.emit ?? ((line, error = false) => error ? console.error(line) : console.log(line));
|
|
@@ -761,6 +902,8 @@ async function startWidgetDevelopment(projectRoot, options) {
|
|
|
761
902
|
},
|
|
762
903
|
lastBuildError: null
|
|
763
904
|
};
|
|
905
|
+
const observations = await createDevelopmentObservations();
|
|
906
|
+
observations.reset(state.generation);
|
|
764
907
|
const eventClients = /* @__PURE__ */ new Map();
|
|
765
908
|
let eventId = 0;
|
|
766
909
|
let session;
|
|
@@ -783,7 +926,7 @@ async function startWidgetDevelopment(projectRoot, options) {
|
|
|
783
926
|
}
|
|
784
927
|
});
|
|
785
928
|
const developmentSnapshot = () => {
|
|
786
|
-
const origin =
|
|
929
|
+
const { origin } = session.descriptor;
|
|
787
930
|
const development = panelDevelopmentDescriptor(successfulGeneration, `${origin}${V1_PROFILE_PATH}`, `${origin}${V1_STATUS_PATH}`);
|
|
788
931
|
return {
|
|
789
932
|
protocolVersion: 1,
|
|
@@ -798,7 +941,8 @@ async function startWidgetDevelopment(projectRoot, options) {
|
|
|
798
941
|
assets: successfulGeneration.assets,
|
|
799
942
|
watch: state.watch,
|
|
800
943
|
lastBuild: state.lastBuild,
|
|
801
|
-
lastBuildError: state.lastBuildError
|
|
944
|
+
lastBuildError: state.lastBuildError,
|
|
945
|
+
runtime: observations.snapshot()
|
|
802
946
|
};
|
|
803
947
|
};
|
|
804
948
|
const generationReadyData = () => {
|
|
@@ -813,6 +957,10 @@ async function startWidgetDevelopment(projectRoot, options) {
|
|
|
813
957
|
};
|
|
814
958
|
};
|
|
815
959
|
const server = createServer((request, response) => {
|
|
960
|
+
if (!session) {
|
|
961
|
+
response.destroy();
|
|
962
|
+
return;
|
|
963
|
+
}
|
|
816
964
|
const url = new URL(request.url ?? "/", "http://127.0.0.1");
|
|
817
965
|
if (request.method === "OPTIONS") {
|
|
818
966
|
response.writeHead(405, secureHeaders("text/plain; charset=utf-8")).end();
|
|
@@ -877,8 +1025,30 @@ async function startWidgetDevelopment(projectRoot, options) {
|
|
|
877
1025
|
request.on("end", () => {
|
|
878
1026
|
try {
|
|
879
1027
|
const control = JSON.parse(body || "{}");
|
|
880
|
-
if (control.protocolVersion !== 1 || ![
|
|
881
|
-
|
|
1028
|
+
if (control.protocolVersion !== 1 || ![
|
|
1029
|
+
"reload",
|
|
1030
|
+
"disconnect",
|
|
1031
|
+
"report-runtime",
|
|
1032
|
+
"refresh-built-output"
|
|
1033
|
+
].includes(control.action)) throw new Error("invalid control action");
|
|
1034
|
+
if (control.action === "report-runtime") {
|
|
1035
|
+
json$1(response, 200, {
|
|
1036
|
+
ok: true,
|
|
1037
|
+
protocolVersion: 1,
|
|
1038
|
+
runtime: observations.accept(control.observation, state.generation)
|
|
1039
|
+
});
|
|
1040
|
+
return;
|
|
1041
|
+
}
|
|
1042
|
+
if (control.action === "refresh-built-output") {
|
|
1043
|
+
const candidate = createDevelopmentGeneration(projectRoot, successfulGeneration.number + 1, GENERATION_LIMITS, options.manifestOptions);
|
|
1044
|
+
successfulGeneration = candidate;
|
|
1045
|
+
retainedGenerations.set(candidate.guestCapability, candidate);
|
|
1046
|
+
while (retainedGenerations.size > RETAINED_GENERATIONS) retainedGenerations.delete(retainedGenerations.keys().next().value);
|
|
1047
|
+
state.generation = candidate.number;
|
|
1048
|
+
observations.reset(candidate.number);
|
|
1049
|
+
state.lastBuildError = null;
|
|
1050
|
+
sendEvent("generation-ready", generationReadyData());
|
|
1051
|
+
} else if (control.action === "disconnect") {
|
|
882
1052
|
sendEvent("disconnected", { reason: "host-request" });
|
|
883
1053
|
session.disconnect();
|
|
884
1054
|
} else sendEvent("reload-requested", { generation: state.generation });
|
|
@@ -915,7 +1085,7 @@ async function startWidgetDevelopment(projectRoot, options) {
|
|
|
915
1085
|
});
|
|
916
1086
|
return;
|
|
917
1087
|
}
|
|
918
|
-
const origin =
|
|
1088
|
+
const { origin } = session.descriptor;
|
|
919
1089
|
const launchUrl = `${origin}${guestGenerationRoot(successfulGeneration)}/launch.html`;
|
|
920
1090
|
json$1(response, 200, createDevelopmentConfig({
|
|
921
1091
|
manifest: successfulGeneration.manifest,
|
|
@@ -928,7 +1098,7 @@ async function startWidgetDevelopment(projectRoot, options) {
|
|
|
928
1098
|
}
|
|
929
1099
|
if (url.pathname === PROFILE_PATH) {
|
|
930
1100
|
const runId = url.searchParams.get("runId") || "widget-dev-run";
|
|
931
|
-
const origin =
|
|
1101
|
+
const { origin } = session.descriptor;
|
|
932
1102
|
json$1(response, 200, createDevelopmentConfig({
|
|
933
1103
|
manifest: successfulGeneration.manifest,
|
|
934
1104
|
runId,
|
|
@@ -987,11 +1157,7 @@ async function startWidgetDevelopment(projectRoot, options) {
|
|
|
987
1157
|
}
|
|
988
1158
|
serveGenerationAsset(successfulGeneration, assetPath, response);
|
|
989
1159
|
});
|
|
990
|
-
await
|
|
991
|
-
server.once("error", rejectListen);
|
|
992
|
-
server.listen(options.port, "127.0.0.1", resolveListen);
|
|
993
|
-
});
|
|
994
|
-
const origin = `http://127.0.0.1:${server.address().port}`;
|
|
1160
|
+
const origin = await listenForDevelopment(server, options);
|
|
995
1161
|
session = createWidgetDevelopmentSession({ origin });
|
|
996
1162
|
const profileUrl = `${origin}${PROFILE_PATH}`;
|
|
997
1163
|
const descriptorUrl = `${origin}${DESCRIPTOR_PATH}`;
|
|
@@ -1054,6 +1220,7 @@ async function startWidgetDevelopment(projectRoot, options) {
|
|
|
1054
1220
|
retainedGenerations.set(candidate.guestCapability, candidate);
|
|
1055
1221
|
while (retainedGenerations.size > RETAINED_GENERATIONS) retainedGenerations.delete(retainedGenerations.keys().next().value);
|
|
1056
1222
|
state.generation = candidate.number;
|
|
1223
|
+
observations.reset(candidate.number);
|
|
1057
1224
|
state.lastBuild = {
|
|
1058
1225
|
ok: true,
|
|
1059
1226
|
digest: candidate.digest,
|
|
@@ -1269,13 +1436,14 @@ function commandTopics(context) {
|
|
|
1269
1436
|
options: [
|
|
1270
1437
|
"--template <模板> panel 或 file-preview(默认 panel)",
|
|
1271
1438
|
"--ui <模式> vanilla 或 react(默认 vanilla)",
|
|
1272
|
-
"--panel-id <id>
|
|
1439
|
+
"--panel-id <id> 自定义面板标识,仅用于 panel;默认取规范化后的工程名",
|
|
1273
1440
|
"--extensions <列表> 仅用于 file-preview;逗号分隔扩展名(默认 txt)",
|
|
1274
1441
|
"--tool <名称> 按指定工具成功结果激活,不注册工具卡片",
|
|
1275
1442
|
HOST_OPTION,
|
|
1276
1443
|
JSON_OPTION
|
|
1277
1444
|
],
|
|
1278
1445
|
examples: [
|
|
1446
|
+
"bohr-widget create my-widget --ui react --panel-id my-widget",
|
|
1279
1447
|
"bohr-widget create workspace-browser --ui react --panel-id workspace-files",
|
|
1280
1448
|
"bohr-widget create text-preview --template file-preview --extensions txt,csv --ui vanilla --json",
|
|
1281
1449
|
"bohr-widget create search-panel --tool ScholarSearch --json"
|
|
@@ -1283,8 +1451,9 @@ function commandTopics(context) {
|
|
|
1283
1451
|
notes: [
|
|
1284
1452
|
`默认组合为 panel + vanilla;生成工程的构建环境要求 Node.js ${context.generatedProjectNode}。`,
|
|
1285
1453
|
"不会自动安装依赖;创建后进入目录,运行 pnpm install 或 npm install。",
|
|
1286
|
-
"
|
|
1287
|
-
"
|
|
1454
|
+
"Panel ID 可自定义,用于工作台识别面板;打包后按目标工作台的安装流程加载。",
|
|
1455
|
+
"workspace-files 是可选的文件浏览面板预设,会补齐 files.list、files.metadata、files.read 和目录引用样例;文件树界面与逻辑需自行实现。",
|
|
1456
|
+
"生成 README.md 和 AGENTS.md;先读取项目指引与公开文档,再安装依赖和执行工程命令。",
|
|
1288
1457
|
"将 widget-toolchain.json 与包管理器 lockfile 一起保存。"
|
|
1289
1458
|
],
|
|
1290
1459
|
quickStart: true
|
|
@@ -1313,6 +1482,7 @@ function commandTopics(context) {
|
|
|
1313
1482
|
"--open 连接或唤起 Widget 开发宿主(默认关闭)",
|
|
1314
1483
|
"--no-build 跳过首次构建,使用现有入口(默认先构建)",
|
|
1315
1484
|
"--port <端口> 本地服务端口,1024–65535(默认 4300)",
|
|
1485
|
+
"--listen-fd <fd> 接管宿主的 IPv4 回环监听;须与 --no-build 同用,排除 --port 和 --watch",
|
|
1316
1486
|
"--scheme <scheme> 宿主 deep link scheme(默认 widget-host)",
|
|
1317
1487
|
"--bridge <url> 指定 loopback HTTP(S) bridge;默认探测标准端口",
|
|
1318
1488
|
HOST_OPTION,
|
|
@@ -1473,7 +1643,8 @@ function documentationLines(documentation, topic = {}) {
|
|
|
1473
1643
|
...topic.agentDocs ? [
|
|
1474
1644
|
`文档站:${documentation.url}`,
|
|
1475
1645
|
`Agent 索引:${documentation.llms}`,
|
|
1476
|
-
`完整文档:${documentation.llmsFull}
|
|
1646
|
+
`完整文档:${documentation.llmsFull}`,
|
|
1647
|
+
"工程内的作者指引见 node_modules/@dp-bohrium/widget-sdk/llms.txt。"
|
|
1477
1648
|
] : []
|
|
1478
1649
|
];
|
|
1479
1650
|
}
|
|
@@ -1519,7 +1690,7 @@ SDK ${sdk}(协议与执行工具链均由 SDK 内置)
|
|
|
1519
1690
|
CLI 要求 Node.js >=20;默认生成工程要求 Node.js ${generatedProjectNode}。
|
|
1520
1691
|
|
|
1521
1692
|
典型开发流程:
|
|
1522
|
-
pnpm dlx ${packageName} create my-widget --
|
|
1693
|
+
pnpm dlx ${packageName} create my-widget --template panel --ui react
|
|
1523
1694
|
cd my-widget
|
|
1524
1695
|
pnpm install
|
|
1525
1696
|
pnpm exec bohr-widget build .
|
|
@@ -1554,7 +1725,7 @@ function renderHelp(path = [], context) {
|
|
|
1554
1725
|
/**
|
|
1555
1726
|
* 作者清单(VS Code 风格 `contributes` 清单)的本地静态检查。
|
|
1556
1727
|
*
|
|
1557
|
-
*
|
|
1728
|
+
* SDK 的 host schema 只在 `compileAuthorManifest` 之后生效,
|
|
1558
1729
|
* 而编译只保留宿主运行时需要的字段:作者的 `version`、`displayName` 和未知顶层键会被
|
|
1559
1730
|
* 静默丢弃,于是"清单写错但 check 全绿"。这里只补下游永远看不到的作者侧约束,报错文案
|
|
1560
1731
|
* 直接指向作者填的字段名。
|
|
@@ -1812,7 +1983,7 @@ async function validateProjectToolchain(projectRoot, toolchain, { requireInstall
|
|
|
1812
1983
|
]) for (const name of ["@dp-bohrium/widget-contracts", "@dp-bohrium/widget-cli"]) if (pkg[section]?.[name] !== void 0) throw failure("PROJECT_SDK_MISMATCH", "作者工程仍直接声明 contracts 或 CLI,需统一使用 SDK 入口", {
|
|
1813
1984
|
projectRoot: root,
|
|
1814
1985
|
package: name
|
|
1815
|
-
}, ["作者工程统一依赖 SDK
|
|
1986
|
+
}, ["作者工程统一依赖 SDK;协议从 SDK 的 /contracts 与 /schemas 出口读取。保留作者源码,在独立候选中移除重复依赖、重新安装和构建。"]);
|
|
1816
1987
|
const declared = {};
|
|
1817
1988
|
const installed = {};
|
|
1818
1989
|
for (const [key, name] of Object.entries(PACKAGES)) {
|
|
@@ -2413,11 +2584,35 @@ function projectPackage({ name, ui, sdkVersion }) {
|
|
|
2413
2584
|
}
|
|
2414
2585
|
};
|
|
2415
2586
|
}
|
|
2416
|
-
function
|
|
2587
|
+
function projectAgentGuide({ sdkVersion, sourceFile, hostFile, documentation, agentSetupUrl }) {
|
|
2588
|
+
return `# 项目开发指引
|
|
2589
|
+
|
|
2590
|
+
创建时 SDK:\`@dp-bohrium/widget-sdk@${sdkVersion}\`。
|
|
2591
|
+
|
|
2592
|
+
以下路径均相对于本工程根目录:
|
|
2593
|
+
|
|
2594
|
+
- \`${sourceFile}\`:本工程的 Widget 界面与业务逻辑,通常在这里修改功能。
|
|
2595
|
+
- \`${hostFile}\`:本工程的启动文件,调用 SDK 的 \`runWidgetHost\` 启动 Widget。
|
|
2596
|
+
- \`manifest.json\`:本工程的 Widget 声明。
|
|
2597
|
+
- \`samples/default.json\`:本工程的样例输入。
|
|
2598
|
+
|
|
2599
|
+
本工程的依赖版本与工具链记录在 \`package.json\`、\`widget-toolchain.json\` 和包管理器 lockfile 中。
|
|
2600
|
+
|
|
2601
|
+
阅读入口:
|
|
2602
|
+
|
|
2603
|
+
- [README.md](./README.md):本工程的环境、安装、命令和验收说明。
|
|
2604
|
+
- [开发 Agent 引导](${agentSetupUrl}):通用的能力选型与开发阅读路径。
|
|
2605
|
+
- [文档索引](${documentation.llms}):按当前任务查找指南、API 和示例。
|
|
2606
|
+
`;
|
|
2607
|
+
}
|
|
2608
|
+
function projectReadme({ name, template, ui, sourceFile, hostFile, sdkVersion, documentation, agentSetupUrl }) {
|
|
2417
2609
|
return `# ${name}
|
|
2418
2610
|
|
|
2419
2611
|
Bohrium Widget ${template} 插件工程(${ui})。
|
|
2420
2612
|
|
|
2613
|
+
创建时 SDK:\`@dp-bohrium/widget-sdk@${sdkVersion}\`。项目上下文与 Agent 阅读入口见 [AGENTS.md](./AGENTS.md);安装依赖前即可读取[开发 Agent 引导](${agentSetupUrl})、[文档索引](${documentation.llms})和[快速开始](${documentation.quickStart})。
|
|
2614
|
+
文档独立更新;接口以本工程固定 SDK 和目标 Host 实际支持为准。
|
|
2615
|
+
|
|
2421
2616
|
默认构建器要求 Node.js \`${GENERATED_PROJECT_NODE}\`,高于执行器自身的 Node.js 20 要求。
|
|
2422
2617
|
工程已经精确依赖提供命令的 \`${PROJECT_EXECUTOR_PACKAGE}\`;在本目录安装后使用本地命令,无需全局安装或源码仓库。
|
|
2423
2618
|
|
|
@@ -2431,8 +2626,9 @@ pnpm run widget:pack --json
|
|
|
2431
2626
|
使用 npm 时,安装后可执行 \`npm run build\`、\`npm run check\`、\`npm run widget:pack\`;需要构建 receipt 时执行 \`npx --no-install bohr-widget build .\`。
|
|
2432
2627
|
Widget ZIP 使用 \`pnpm run widget:pack\` 或 \`npm run widget:pack\`;\`pnpm pack\` 是制作 npm \`.tgz\` 包的内置命令。
|
|
2433
2628
|
|
|
2434
|
-
-
|
|
2629
|
+
- Widget 界面与业务逻辑在 ${sourceFile} 中;本工程的 ${hostFile} 调用 SDK 的 runWidgetHost 启动 Widget。
|
|
2435
2630
|
- 高度链、布局与滚动策略直接写在 ${sourceFile} 中,样式随 activate 安装并在释放时清理;JSON 展示元素单独处理换行。
|
|
2631
|
+
- 在 [SDK 本地索引](./node_modules/@dp-bohrium/widget-sdk/llms.txt) 查看「Widget 尺寸与滚动」。
|
|
2436
2632
|
- manifest.json 是作者清单,打包时由 CLI 编译为宿主清单。
|
|
2437
2633
|
- 提交 widget-toolchain.json 与依赖 lockfile;dist/widget.js 与生成的 ZIP 不提交。
|
|
2438
2634
|
- check 只验证结构、声明和样例,不执行 JavaScript;pack 成功不代表首帧渲染、真实授权或业务验收成功。
|
|
@@ -2983,6 +3179,17 @@ async function commandCreate(directory, options) {
|
|
|
2983
3179
|
await writeFile(join(root, "samples/default.json"), `${JSON.stringify(sample, null, 2)}\n`);
|
|
2984
3180
|
const sourceFile = options.ui === "react" ? "src/widget.tsx" : "src/widget.ts";
|
|
2985
3181
|
const hostFile = "src/widget-host.ts";
|
|
3182
|
+
const documentation = getDocumentation();
|
|
3183
|
+
const agentSetupUrl = new URL("agent-setup/prompt.md", documentation.url).href;
|
|
3184
|
+
const projectGuidance = {
|
|
3185
|
+
sdkVersion: sdkPackage.version,
|
|
3186
|
+
sourceFile,
|
|
3187
|
+
hostFile,
|
|
3188
|
+
documentation,
|
|
3189
|
+
agentSetupUrl
|
|
3190
|
+
};
|
|
3191
|
+
const readmePath = join(root, "README.md");
|
|
3192
|
+
const agentGuidePath = join(root, "AGENTS.md");
|
|
2986
3193
|
const source = options.ui === "react" && template.reactSource ? template.reactSource({ name }) : typeof template.source === "function" ? template.source({
|
|
2987
3194
|
ui: options.ui,
|
|
2988
3195
|
name
|
|
@@ -2991,13 +3198,13 @@ async function commandCreate(directory, options) {
|
|
|
2991
3198
|
await writeFile(join(root, hostFile), template.hostSource({ sourceFile }));
|
|
2992
3199
|
await writeFile(join(root, "scripts/rename-iife.mjs"), `import { rename } from "node:fs/promises";\n\nawait rename("dist/widget.iife.js", "dist/widget.js");\n`);
|
|
2993
3200
|
await writeFile(join(root, ".gitignore"), "node_modules/\ndist/\n.artifacts/\n.widget-build-receipt.json\n*.zip\n");
|
|
2994
|
-
await writeFile(
|
|
3201
|
+
await writeFile(readmePath, projectReadme({
|
|
3202
|
+
...projectGuidance,
|
|
2995
3203
|
name,
|
|
2996
3204
|
template: options.template,
|
|
2997
|
-
ui: options.ui
|
|
2998
|
-
sourceFile,
|
|
2999
|
-
hostFile
|
|
3205
|
+
ui: options.ui
|
|
3000
3206
|
}));
|
|
3207
|
+
await writeFile(agentGuidePath, projectAgentGuide(projectGuidance));
|
|
3001
3208
|
await writeFile(join(root, "tsconfig.json"), `${JSON.stringify({
|
|
3002
3209
|
compilerOptions: {
|
|
3003
3210
|
target: "ES2022",
|
|
@@ -3021,7 +3228,60 @@ async function commandCreate(directory, options) {
|
|
|
3021
3228
|
const warnings = [];
|
|
3022
3229
|
await writeProjectToolchain(root, toolchain);
|
|
3023
3230
|
if (name !== directoryName) warnings.push(`目录名 "${directoryName}" 不是合法标识符,manifest 的 name 已取 "${name}";需要其他取值请直接编辑 manifest.json。`);
|
|
3024
|
-
|
|
3231
|
+
const nextSteps = [
|
|
3232
|
+
{
|
|
3233
|
+
cwd: root,
|
|
3234
|
+
command: "pnpm",
|
|
3235
|
+
args: ["install"]
|
|
3236
|
+
},
|
|
3237
|
+
{
|
|
3238
|
+
cwd: root,
|
|
3239
|
+
command: "pnpm",
|
|
3240
|
+
args: [
|
|
3241
|
+
"exec",
|
|
3242
|
+
"bohr-widget",
|
|
3243
|
+
"integration",
|
|
3244
|
+
"--project",
|
|
3245
|
+
".",
|
|
3246
|
+
"--json"
|
|
3247
|
+
]
|
|
3248
|
+
},
|
|
3249
|
+
{
|
|
3250
|
+
cwd: root,
|
|
3251
|
+
command: "pnpm",
|
|
3252
|
+
args: [
|
|
3253
|
+
"exec",
|
|
3254
|
+
"bohr-widget",
|
|
3255
|
+
"build",
|
|
3256
|
+
".",
|
|
3257
|
+
"--json"
|
|
3258
|
+
]
|
|
3259
|
+
},
|
|
3260
|
+
{
|
|
3261
|
+
cwd: root,
|
|
3262
|
+
command: "pnpm",
|
|
3263
|
+
args: [
|
|
3264
|
+
"exec",
|
|
3265
|
+
"bohr-widget",
|
|
3266
|
+
"check",
|
|
3267
|
+
".",
|
|
3268
|
+
"--json"
|
|
3269
|
+
]
|
|
3270
|
+
},
|
|
3271
|
+
{
|
|
3272
|
+
cwd: root,
|
|
3273
|
+
command: "pnpm",
|
|
3274
|
+
args: [
|
|
3275
|
+
"exec",
|
|
3276
|
+
"bohr-widget",
|
|
3277
|
+
"pack",
|
|
3278
|
+
".",
|
|
3279
|
+
"--out",
|
|
3280
|
+
"artifacts/my-widget.zip",
|
|
3281
|
+
"--json"
|
|
3282
|
+
]
|
|
3283
|
+
}
|
|
3284
|
+
];
|
|
3025
3285
|
const result = {
|
|
3026
3286
|
ok: true,
|
|
3027
3287
|
command: "create",
|
|
@@ -3033,6 +3293,11 @@ async function commandCreate(directory, options) {
|
|
|
3033
3293
|
...panelId ? { panelId } : {},
|
|
3034
3294
|
manifestPath: join(root, "manifest.json"),
|
|
3035
3295
|
packagePath: join(root, "package.json"),
|
|
3296
|
+
readmePath,
|
|
3297
|
+
agentGuidePath,
|
|
3298
|
+
documentation,
|
|
3299
|
+
agentSetupUrl,
|
|
3300
|
+
nextSteps,
|
|
3036
3301
|
profile: toolchain.profile,
|
|
3037
3302
|
hostCompatibility,
|
|
3038
3303
|
...warnings.length === 0 ? {} : { warnings }
|
|
@@ -3040,8 +3305,14 @@ async function commandCreate(directory, options) {
|
|
|
3040
3305
|
if (options.json) console.log(JSON.stringify(result, null, 2));
|
|
3041
3306
|
else {
|
|
3042
3307
|
console.log(`已创建 Widget 工程:${root}(manifest name: ${name})`);
|
|
3308
|
+
if (panelId) console.log(`Panel ID:${panelId}(可自定义)`);
|
|
3309
|
+
console.log(`创建时 SDK:@dp-bohrium/widget-sdk@${sdkPackage.version}`);
|
|
3310
|
+
console.log(`项目说明:${readmePath}\nAgent 指引:${agentGuidePath}`);
|
|
3311
|
+
console.log(`文档站:${documentation.url}\nAgent 阅读入口:${agentSetupUrl}`);
|
|
3312
|
+
console.log(`文档索引:${documentation.llms}`);
|
|
3043
3313
|
for (const warning of warnings) console.warn(`提示:${warning}`);
|
|
3044
|
-
console.log(
|
|
3314
|
+
console.log(`先读取项目指引和公开文档,再进入工程目录:${root}`);
|
|
3315
|
+
console.log(`依次执行:\n${nextSteps.map((step) => [step.command, ...step.args].join(" ")).join("\n")}`);
|
|
3045
3316
|
}
|
|
3046
3317
|
return result;
|
|
3047
3318
|
}
|