@tbox.cn/app-cli 0.1.3 → 0.4.2
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 +6 -4
- package/dist/chunk-GY4SXKTS.js +16 -0
- package/dist/chunk-SGF4P5YN.js +2 -0
- package/dist/contracts-resolver-UQGGTGAO.js +2 -0
- package/dist/declared-4SBML2FU.js +2 -0
- package/dist/index.js +199 -3323
- package/package.json +14 -11
- package/starter/agent-mall.md +56 -0
- package/starter/agent-support.md +38 -0
- package/starter/agent.md +46 -0
package/dist/index.js
CHANGED
|
@@ -1,481 +1,32 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
return path.join(appDir, ".tbox", "app.json");
|
|
20
|
-
}
|
|
21
|
-
function readAppManifest(appDir) {
|
|
22
|
-
const file = manifestPath(appDir);
|
|
23
|
-
if (!existsSync(file)) return null;
|
|
24
|
-
try {
|
|
25
|
-
const raw = JSON.parse(readFileSync(file, "utf8"));
|
|
26
|
-
const templateVersion = raw?.templateVersion;
|
|
27
|
-
if (typeof templateVersion !== "string" || templateVersion.trim().length === 0) return null;
|
|
28
|
-
return {
|
|
29
|
-
templateVersion: templateVersion.trim(),
|
|
30
|
-
npmModules: Array.isArray(raw?.npmModules) ? raw.npmModules : []
|
|
31
|
-
};
|
|
32
|
-
} catch {
|
|
33
|
-
return null;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
async function writeAppManifest(appDir, manifest) {
|
|
37
|
-
const file = manifestPath(appDir);
|
|
38
|
-
await mkdir(path.dirname(file), { recursive: true });
|
|
39
|
-
await writeFile(file, JSON.stringify(manifest, null, 2) + "\n", "utf8");
|
|
40
|
-
}
|
|
41
|
-
function upsertNpmModule(manifest, entry) {
|
|
42
|
-
const rest = manifest.npmModules.filter((m) => m.id !== entry.id);
|
|
43
|
-
return { ...manifest, npmModules: [...rest, entry] };
|
|
44
|
-
}
|
|
45
|
-
function removeNpmModule(manifest, id) {
|
|
46
|
-
return { ...manifest, npmModules: manifest.npmModules.filter((m) => m.id !== id) };
|
|
47
|
-
}
|
|
48
|
-
function updateNpmModule(manifest, id, patch) {
|
|
49
|
-
return {
|
|
50
|
-
...manifest,
|
|
51
|
-
npmModules: manifest.npmModules.map((m) => m.id === id ? { ...m, ...patch } : m)
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
function isValidModuleId(id) {
|
|
55
|
-
return /^[a-z0-9]+(-[a-z0-9]+)*$/.test(id) && !id.includes("..");
|
|
56
|
-
}
|
|
57
|
-
function assertValidModuleId(id, source) {
|
|
58
|
-
if (!isValidModuleId(id)) {
|
|
59
|
-
throw new Error(`\u975E\u6CD5\u6A21\u5757 id "${id}"\uFF08\u6765\u81EA ${source}\uFF09\uFF1A\u4EC5\u5141\u8BB8\u5C0F\u5199\u5B57\u6BCD/\u6570\u5B57/\u4E2D\u5212\u7EBF`);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// src/source.ts
|
|
64
|
-
import { existsSync as existsSync2, readFileSync as readFileSync2, mkdtempSync, rmSync } from "fs";
|
|
65
|
-
import { execFileSync as execFileSync2 } from "child_process";
|
|
66
|
-
import { tmpdir } from "os";
|
|
67
|
-
import { readFile, readdir, stat } from "fs/promises";
|
|
68
|
-
import path3 from "path";
|
|
69
|
-
|
|
70
|
-
// src/registry.ts
|
|
71
|
-
import { execFileSync } from "child_process";
|
|
72
|
-
import path2 from "path";
|
|
73
|
-
var cachedRegistry = null;
|
|
74
|
-
function resolveRegistry(pkgName) {
|
|
75
|
-
if (pkgName?.startsWith("@tbox.cn/")) {
|
|
76
|
-
try {
|
|
77
|
-
const scopeOut = execFileSync("pnpm", ["config", "get", "@tbox.cn:registry"], { encoding: "utf8" });
|
|
78
|
-
const scopeReg = scopeOut.trim().split(/\r?\n/).pop() ?? "";
|
|
79
|
-
if (scopeReg) return scopeReg;
|
|
80
|
-
} catch {
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
if (cachedRegistry) return cachedRegistry;
|
|
84
|
-
try {
|
|
85
|
-
const out = execFileSync("pnpm", ["config", "get", "registry"], { encoding: "utf8" });
|
|
86
|
-
cachedRegistry = out.trim().split(/\r?\n/).pop() ?? "https://registry.npmjs.org/";
|
|
87
|
-
} catch {
|
|
88
|
-
cachedRegistry = "https://registry.npmjs.org/";
|
|
89
|
-
}
|
|
90
|
-
return cachedRegistry;
|
|
91
|
-
}
|
|
92
|
-
function isRegistrySpec(spec) {
|
|
93
|
-
return /^@[^/]+\/[^@/]+(@.+)?$/.test(spec);
|
|
94
|
-
}
|
|
95
|
-
function specName(spec) {
|
|
96
|
-
return spec.replace(/@[^/]+$/, "");
|
|
97
|
-
}
|
|
98
|
-
function packFromRegistry(spec, outDir, registry) {
|
|
99
|
-
const reg = registry ?? resolveRegistry(specName(spec));
|
|
100
|
-
const viewArgs = ["view", spec, "dist.tarball"];
|
|
101
|
-
if (registry) viewArgs.push("--registry", registry);
|
|
102
|
-
const out = execFileSync("pnpm", viewArgs, { encoding: "utf8" });
|
|
103
|
-
const url = out.trim().split(/\r?\n/).pop() ?? "";
|
|
104
|
-
if (!url) throw new Error(`registry \u62C9\u53D6\u5931\u8D25\uFF1A${spec} \u65E0 dist.tarball\uFF08registry=${reg}\uFF09`);
|
|
105
|
-
const file = url.split("/").pop() ?? `${spec.replace(/^@/, "").replace(/\//g, "-").split("@")[0]}.tgz`;
|
|
106
|
-
const dest = path2.join(path2.resolve(outDir), file);
|
|
107
|
-
execFileSync("curl", ["-fsSL", "-o", dest, url], { stdio: "ignore" });
|
|
108
|
-
return dest;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// src/source.ts
|
|
112
|
-
var EXCLUDED_DIRS = /* @__PURE__ */ new Set(["node_modules", "dist", "dist-ssr", ".git", ".tbox"]);
|
|
113
|
-
async function walkDir(dir, base, out) {
|
|
114
|
-
const entries = await readdir(dir, { withFileTypes: true });
|
|
115
|
-
for (const entry of entries) {
|
|
116
|
-
const full = path3.join(dir, entry.name);
|
|
117
|
-
const rel = path3.relative(base, full).split(path3.sep).join("/");
|
|
118
|
-
if (entry.isDirectory()) {
|
|
119
|
-
if (EXCLUDED_DIRS.has(entry.name)) continue;
|
|
120
|
-
await walkDir(full, base, out);
|
|
121
|
-
} else if (entry.isFile()) {
|
|
122
|
-
out.push(rel);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
var LocalDirSource = class {
|
|
127
|
-
constructor(dir) {
|
|
128
|
-
this.dir = dir;
|
|
129
|
-
this.name = path3.basename(path3.resolve(dir));
|
|
130
|
-
}
|
|
131
|
-
dir;
|
|
132
|
-
name;
|
|
133
|
-
getDir() {
|
|
134
|
-
return this.dir;
|
|
135
|
-
}
|
|
136
|
-
async listFiles() {
|
|
137
|
-
const files = [];
|
|
138
|
-
await walkDir(this.dir, this.dir, files);
|
|
139
|
-
return files.sort();
|
|
140
|
-
}
|
|
141
|
-
async readFile(rel) {
|
|
142
|
-
return readFile(path3.join(this.dir, rel), "utf8");
|
|
143
|
-
}
|
|
144
|
-
/** 目录是否可读(存在且含 package.json) */
|
|
145
|
-
static isUsable(dir) {
|
|
146
|
-
return existsSync2(path3.join(dir, "package.json"));
|
|
147
|
-
}
|
|
148
|
-
resolveVersion() {
|
|
149
|
-
return readPackageNameVersion(this.dir).version;
|
|
150
|
-
}
|
|
151
|
-
};
|
|
152
|
-
function readPackageNameVersion(pkgDir) {
|
|
153
|
-
let raw;
|
|
154
|
-
try {
|
|
155
|
-
raw = readFileSync2(path3.join(pkgDir, "package.json"), "utf8");
|
|
156
|
-
} catch {
|
|
157
|
-
return { version: "0.0.0" };
|
|
158
|
-
}
|
|
159
|
-
try {
|
|
160
|
-
const pkg = JSON.parse(raw);
|
|
161
|
-
return { name: pkg?.name, version: pkg?.version ?? "0.0.0" };
|
|
162
|
-
} catch {
|
|
163
|
-
return { version: "0.0.0" };
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
var RegistrySource = class {
|
|
167
|
-
constructor(spec, registry) {
|
|
168
|
-
this.spec = spec;
|
|
169
|
-
this.registry = registry;
|
|
170
|
-
this.name = spec;
|
|
171
|
-
this.packDir = mkdtempSync(path3.join(tmpdir(), "tbox-reg-"));
|
|
172
|
-
}
|
|
173
|
-
spec;
|
|
174
|
-
registry;
|
|
175
|
-
name;
|
|
176
|
-
inner = null;
|
|
177
|
-
packDir;
|
|
178
|
-
resolvedName = "";
|
|
179
|
-
resolvedVersion = "";
|
|
180
|
-
resolve() {
|
|
181
|
-
if (this.inner) return this.inner;
|
|
182
|
-
const pkgName = this.spec.replace(/@[^/]+$/, "");
|
|
183
|
-
const reg = this.registry ?? resolveRegistry(pkgName);
|
|
184
|
-
console.log(`\u{1F4E6} \u4ECE registry \u62C9\u53D6 ${this.spec}\uFF08${reg}\uFF09`);
|
|
185
|
-
const tgzPath = packFromRegistry(this.spec, this.packDir, this.registry);
|
|
186
|
-
this.inner = new TarballSource(tgzPath);
|
|
187
|
-
let name;
|
|
188
|
-
let version = "";
|
|
189
|
-
try {
|
|
190
|
-
const pkg = JSON.parse(readFileSync2(path3.join(this.inner.getDir(), "package.json"), "utf8"));
|
|
191
|
-
name = pkg.name;
|
|
192
|
-
version = pkg.version ?? "";
|
|
193
|
-
} catch {
|
|
194
|
-
}
|
|
195
|
-
this.resolvedName = name ?? this.spec;
|
|
196
|
-
this.resolvedVersion = version;
|
|
197
|
-
console.log(` \u2192 ${this.resolvedName}@${this.resolvedVersion}`);
|
|
198
|
-
return this.inner;
|
|
199
|
-
}
|
|
200
|
-
listFiles() {
|
|
201
|
-
return Promise.resolve(this.resolve()).then((s) => s.listFiles());
|
|
202
|
-
}
|
|
203
|
-
readFile(rel) {
|
|
204
|
-
return Promise.resolve(this.resolve()).then((s) => s.readFile(rel));
|
|
205
|
-
}
|
|
206
|
-
resolveVersion() {
|
|
207
|
-
this.resolve();
|
|
208
|
-
return this.resolvedVersion;
|
|
209
|
-
}
|
|
210
|
-
getDir() {
|
|
211
|
-
return this.resolve().getDir();
|
|
212
|
-
}
|
|
213
|
-
get resolvedPackage() {
|
|
214
|
-
return { name: this.resolvedName, version: this.resolvedVersion };
|
|
215
|
-
}
|
|
216
|
-
dispose() {
|
|
217
|
-
if (this.inner) this.inner.dispose();
|
|
218
|
-
try {
|
|
219
|
-
if (this.packDir.startsWith(tmpdir())) {
|
|
220
|
-
rmSync(this.packDir, { recursive: true, force: true });
|
|
221
|
-
}
|
|
222
|
-
} catch {
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
};
|
|
226
|
-
function openSource(sourcePath, registry) {
|
|
227
|
-
if (sourcePath.endsWith(".tgz") && existsSync2(sourcePath)) {
|
|
228
|
-
const tarball = new TarballSource(sourcePath);
|
|
229
|
-
return { sourceDir: tarball.getDir(), source: tarball };
|
|
230
|
-
}
|
|
231
|
-
if (!existsSync2(sourcePath) && isRegistrySpec(sourcePath)) {
|
|
232
|
-
const regSource = new RegistrySource(sourcePath, registry);
|
|
233
|
-
return { sourceDir: regSource.getDir(), source: regSource };
|
|
234
|
-
}
|
|
235
|
-
return { sourceDir: sourcePath, source: new LocalDirSource(sourcePath) };
|
|
236
|
-
}
|
|
237
|
-
var TarballSource = class {
|
|
238
|
-
name;
|
|
239
|
-
inner;
|
|
240
|
-
tmpRoot;
|
|
241
|
-
constructor(tgzPath) {
|
|
242
|
-
const abs = path3.resolve(tgzPath);
|
|
243
|
-
this.name = path3.basename(abs, ".tgz");
|
|
244
|
-
this.tmpRoot = mkdtempSync(path3.join(tmpdir(), "tbox-tgz-"));
|
|
245
|
-
execFileSync2("tar", ["-xzf", abs, "-C", this.tmpRoot], { stdio: "ignore" });
|
|
246
|
-
const pkgDir = path3.join(this.tmpRoot, "package");
|
|
247
|
-
this.inner = new LocalDirSource(existsSync2(pkgDir) ? pkgDir : this.tmpRoot);
|
|
248
|
-
}
|
|
249
|
-
getDir() {
|
|
250
|
-
return this.inner.getDir();
|
|
251
|
-
}
|
|
252
|
-
listFiles() {
|
|
253
|
-
return this.inner.listFiles();
|
|
254
|
-
}
|
|
255
|
-
readFile(rel) {
|
|
256
|
-
return this.inner.readFile(rel);
|
|
257
|
-
}
|
|
258
|
-
resolveVersion() {
|
|
259
|
-
return this.inner.resolveVersion();
|
|
260
|
-
}
|
|
261
|
-
/** 释放临时目录(仅删本实例创建的 tmpRoot,防误删系统 tmp 父目录) */
|
|
262
|
-
dispose() {
|
|
263
|
-
try {
|
|
264
|
-
if (this.tmpRoot.startsWith(tmpdir())) {
|
|
265
|
-
rmSync(this.tmpRoot, { recursive: true, force: true });
|
|
266
|
-
}
|
|
267
|
-
} catch {
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
};
|
|
271
|
-
|
|
272
|
-
// src/commands/create.ts
|
|
273
|
-
var EXCLUDE_DIRS = /* @__PURE__ */ new Set(["node_modules", "dist", "dist-ssr", ".git", ".DS_Store"]);
|
|
274
|
-
var EXCLUDE_FILES = /* @__PURE__ */ new Set(["pnpm-lock.yaml"]);
|
|
275
|
-
var TEMPLATE_WORKSPACE_FILE = "pnpm-workspace.template.yaml";
|
|
276
|
-
var APP_WORKSPACE_FILE = "pnpm-workspace.yaml";
|
|
277
|
-
function buildPlatformOverrides(tgzDir, appDir) {
|
|
278
|
-
const files = readdirSync(tgzDir).filter((f) => /^tbox\.cn-app-(sdk|contracts)-.+\.tgz$/.test(f)).sort();
|
|
279
|
-
if (files.length === 0) return null;
|
|
280
|
-
const appReal = realpathSync(appDir);
|
|
281
|
-
const seen = /* @__PURE__ */ new Set();
|
|
282
|
-
const lines = [];
|
|
283
|
-
for (const f of files) {
|
|
284
|
-
const rest = f.replace(/^tbox\.cn-app-/, "").replace(/\.tgz$/, "");
|
|
285
|
-
const i = rest.lastIndexOf("-");
|
|
286
|
-
const name = "@tbox.cn/app-" + rest.slice(0, i);
|
|
287
|
-
if (seen.has(name)) {
|
|
288
|
-
throw new Error(`--local-deps \u76EE\u5F55\u542B\u540C\u4E00\u5E73\u53F0\u5305\u591A\u7248\u672C\uFF08overrides \u91CD\u590D key \u6B67\u4E49\uFF0C\u987B\u5355\u7248\u672C\u76EE\u5F55\uFF09: ${name}`);
|
|
289
|
-
}
|
|
290
|
-
seen.add(name);
|
|
291
|
-
const rel = path4.relative(appReal, realpathSync(path4.join(tgzDir, f)));
|
|
292
|
-
lines.push(` ${JSON.stringify(name)}: ${JSON.stringify(`file:${rel}`)}`);
|
|
293
|
-
}
|
|
294
|
-
return "\n# create --local-deps \u6CE8\u5165\uFF1A\u5E73\u53F0\u5305\u7ECF file: \u672C\u5730\u53D1\u5E03\u6001 tgz \u6D88\u8D39\uFF08005 v0.4.0 \u9A8C\u8BC1\u6001\uFF1B\u751F\u4EA7 B \u6863\u79FB\u9664\u672C\u5757\u8D70 registry\uFF09\noverrides:\n" + lines.join("\n") + "\n";
|
|
295
|
-
}
|
|
296
|
-
function validateLocalDeps(tgzDir) {
|
|
297
|
-
const files = readdirSync(tgzDir).filter((f) => /^tbox\.cn-app-(sdk|contracts)-.+\.tgz$/.test(f));
|
|
298
|
-
if (files.length === 0) {
|
|
299
|
-
throw new Error(`--local-deps \u76EE\u5F55\u65E0\u5E73\u53F0\u5305\u4EA7\u7269\uFF08\u7F3A tbox.cn-app-(sdk|contracts)-*.tgz\uFF09: ${tgzDir}`);
|
|
300
|
-
}
|
|
301
|
-
const seen = /* @__PURE__ */ new Set();
|
|
302
|
-
for (const f of files) {
|
|
303
|
-
const rest = f.replace(/^tbox\.cn-app-/, "").replace(/\.tgz$/, "");
|
|
304
|
-
const i = rest.lastIndexOf("-");
|
|
305
|
-
const name = "@tbox.cn/app-" + rest.slice(0, i);
|
|
306
|
-
if (seen.has(name)) {
|
|
307
|
-
throw new Error(`--local-deps \u76EE\u5F55\u542B\u540C\u4E00\u5E73\u53F0\u5305\u591A\u7248\u672C\uFF08overrides \u91CD\u590D key \u6B67\u4E49\uFF0C\u987B\u5355\u7248\u672C\u76EE\u5F55\uFF09: ${name}`);
|
|
308
|
-
}
|
|
309
|
-
seen.add(name);
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
async function createApp(opts) {
|
|
313
|
-
const target = path4.resolve(process.cwd(), opts.name);
|
|
314
|
-
if (existsSync3(target) && !opts.force) {
|
|
315
|
-
throw new Error(`\u76EE\u5F55\u5DF2\u5B58\u5728: ${opts.name}\uFF08\u52A0 --force \u8986\u76D6\uFF09`);
|
|
316
|
-
}
|
|
317
|
-
const rawSource = opts.source;
|
|
318
|
-
const source = path4.resolve(rawSource);
|
|
319
|
-
const isReg = !existsSync3(source) && isRegistrySpec(rawSource);
|
|
320
|
-
const fromTgz = source.endsWith(".tgz");
|
|
321
|
-
if (!isReg && !fromTgz && !LocalDirSource.isUsable(source)) {
|
|
322
|
-
throw new Error(`\u6A21\u677F\u6E90\u65E0\u6548\uFF08\u7F3A package.json\uFF09: ${source}`);
|
|
323
|
-
}
|
|
324
|
-
if (!isReg && fromTgz && !existsSync3(source)) {
|
|
325
|
-
throw new Error(`\u6A21\u677F\u6E90\u65E0\u6548\uFF08tgz \u4E0D\u5B58\u5728\uFF09: ${source}`);
|
|
326
|
-
}
|
|
327
|
-
if (isReg && opts.localDeps) {
|
|
328
|
-
throw new Error("registry \u6E90\uFF08--source @scope/pkg\uFF09\u4E0E --local-deps \u4E92\u65A5\uFF1Aregistry \u5B89\u88C5\u65E0\u9700/\u4E0D\u5141\u8BB8\u672C\u5730\u4F9D\u8D56\u6CE8\u5165");
|
|
329
|
-
}
|
|
330
|
-
if (opts.localDeps) {
|
|
331
|
-
validateLocalDeps(path4.resolve(opts.localDeps));
|
|
332
|
-
}
|
|
333
|
-
const opened = openSource(isReg ? rawSource : source, opts.registry);
|
|
334
|
-
const sourceDir = opened.sourceDir;
|
|
335
|
-
try {
|
|
336
|
-
const srcManifest = readAppManifest(sourceDir);
|
|
337
|
-
if (!srcManifest) {
|
|
338
|
-
throw new Error(`\u6A21\u677F\u7ED3\u6784\u5F02\u5E38: ${source} \u7684 .tbox/app.json \u7F3A\u5931\u3001\u635F\u574F\u6216\u7F3A\u5C11 templateVersion\uFF08\u51FA\u751F\u7248\u672C\uFF09\u5B57\u6BB5`);
|
|
339
|
-
}
|
|
340
|
-
await cp(sourceDir, target, {
|
|
341
|
-
recursive: true,
|
|
342
|
-
force: true,
|
|
343
|
-
filter: (src) => {
|
|
344
|
-
const name = path4.basename(src);
|
|
345
|
-
if (EXCLUDE_FILES.has(name)) return false;
|
|
346
|
-
if (EXCLUDE_DIRS.has(name)) return false;
|
|
347
|
-
return true;
|
|
348
|
-
}
|
|
349
|
-
});
|
|
350
|
-
const tplWs = path4.join(target, TEMPLATE_WORKSPACE_FILE);
|
|
351
|
-
if (existsSync3(tplWs)) {
|
|
352
|
-
await rename(tplWs, path4.join(target, APP_WORKSPACE_FILE));
|
|
353
|
-
}
|
|
354
|
-
if (opts.localDeps) {
|
|
355
|
-
const block = buildPlatformOverrides(path4.resolve(opts.localDeps), target);
|
|
356
|
-
await appendFile(path4.join(target, APP_WORKSPACE_FILE), block, "utf8");
|
|
357
|
-
}
|
|
358
|
-
const pkgFile = path4.join(target, "package.json");
|
|
359
|
-
if (existsSync3(pkgFile)) {
|
|
360
|
-
const pkg = JSON.parse(await readFile2(pkgFile, "utf8"));
|
|
361
|
-
pkg.name = opts.name;
|
|
362
|
-
await writeFile2(pkgFile, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
363
|
-
}
|
|
364
|
-
const manifest = { templateVersion: srcManifest.templateVersion, npmModules: [] };
|
|
365
|
-
await writeAppManifest(target, manifest);
|
|
366
|
-
console.log(`\u2705 \u5DF2\u521B\u5EFA\u5E94\u7528 ${opts.name}`);
|
|
367
|
-
if (isReg) {
|
|
368
|
-
console.log(` \u26A1 \u5DF2\u4ECE registry \u6A21\u677F\u5305 ${opened.source.name} \u5C55\u5F00\uFF08\u53D1\u5E03\u6001\uFF09`);
|
|
369
|
-
} else if (fromTgz) {
|
|
370
|
-
console.log(` \u26A1 \u5DF2\u4ECE\u6A21\u677F\u5305 ${path4.basename(source)} \u5C55\u5F00\uFF08\u53D1\u5E03\u6001\uFF09`);
|
|
371
|
-
} else {
|
|
372
|
-
console.log(` \u26A1 \u5DF2\u4ECE\u6A21\u677F\u76EE\u5F55\u5C55\u5F00\uFF08\u672C\u5730\u5F00\u53D1\u6E90\uFF09`);
|
|
373
|
-
}
|
|
374
|
-
if (opts.localDeps) {
|
|
375
|
-
console.log(` \u26A1 \u5DF2\u6CE8\u5165\u5E73\u53F0 overrides\uFF08--local-deps: ${opts.localDeps}\uFF0Cinstall \u79BB\u7EBF\u6D88\u8D39\u672C\u5730\u53D1\u5E03\u6001\u4EA7\u7269\uFF09`);
|
|
376
|
-
}
|
|
377
|
-
console.log(` \u26A1 \u5DF2\u751F\u6210 pnpm-workspace.yaml\uFF08\u5E94\u7528 workspace\uFF09`);
|
|
378
|
-
console.log(` \u4E0B\u4E00\u6B65: cd ${opts.name} && pnpm install`);
|
|
379
|
-
console.log(` \u{1F510} \u9274\u6743\uFF08006\uFF09: \u751F\u4EA7\u8BF7\u7531\u5E73\u53F0\u6CE8\u5165 AUTH_TOKEN_SECRET \u4E14\u786E\u4FDD NODE_ENV=production\uFF08dev-login \u5F3A\u5236\u5173\u95ED\uFF09\uFF1B`);
|
|
380
|
-
console.log(` \u672C\u5730\u5F00\u53D1\u9ED8\u8BA4\u5F00\u542F dev-login\uFF08\u975E production\uFF09\uFF1B\u5982\u9700\u5173\u95ED\u8BBE AUTH_DEV_LOGIN_DISABLED=true\u3002`);
|
|
381
|
-
} finally {
|
|
382
|
-
if (opened.source instanceof TarballSource) opened.source.dispose();
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
// src/commands/add.ts
|
|
387
|
-
import { existsSync as existsSync20, readFileSync as readFileSync9, readdirSync as readdirSync2 } from "fs";
|
|
388
|
-
import path24 from "path";
|
|
389
|
-
|
|
390
|
-
// src/expand.ts
|
|
391
|
-
import { mkdir as mkdir2, rm, writeFile as writeFile3 } from "fs/promises";
|
|
392
|
-
import path5 from "path";
|
|
393
|
-
|
|
394
|
-
// src/pkg-family.ts
|
|
395
|
-
var PLATFORM_RE = /^@tbox\.cn\/app-(sdk|contracts)/;
|
|
396
|
-
function isPlatform(name) {
|
|
397
|
-
return PLATFORM_RE.test(name);
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
// src/expand.ts
|
|
401
|
-
function isWorkspacePkg(name) {
|
|
402
|
-
return isPlatform(name);
|
|
403
|
-
}
|
|
404
|
-
function rewritePlatformRefs(deps) {
|
|
405
|
-
for (const name of Object.keys(deps)) {
|
|
406
|
-
if (isWorkspacePkg(name)) deps[name] = "catalog:";
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
function deriveModuleId(pkgName) {
|
|
410
|
-
return pkgName.replace(/^@tbox\.cn\/app-/, "").replace(/^@[^/]+\//, "");
|
|
411
|
-
}
|
|
412
|
-
function escapeRegExp(s) {
|
|
413
|
-
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
414
|
-
}
|
|
415
|
-
function rewriteLoadModuleSkillsCall(content, fromName, toName) {
|
|
416
|
-
return content.replaceAll(
|
|
417
|
-
new RegExp(`loadModuleSkills\\(\\s*['"]${escapeRegExp(fromName)}['"]`, "g"),
|
|
418
|
-
`loadModuleSkills('${toName}'`
|
|
419
|
-
);
|
|
420
|
-
}
|
|
421
|
-
function rewriteExportsToSrc(pkg) {
|
|
422
|
-
const exports = pkg.exports;
|
|
423
|
-
if (!exports || typeof exports !== "object") return;
|
|
424
|
-
const next = {};
|
|
425
|
-
for (const [sub, val] of Object.entries(exports)) {
|
|
426
|
-
if (sub === "./package.json") {
|
|
427
|
-
next[sub] = val;
|
|
428
|
-
continue;
|
|
429
|
-
}
|
|
430
|
-
if (typeof val !== "object" || val === null) continue;
|
|
431
|
-
const base = sub === "." ? "./src/index.ts" : `./src/${sub.replace(/^\.\//, "")}/index.ts`;
|
|
432
|
-
next[sub] = { types: base, import: base };
|
|
433
|
-
}
|
|
434
|
-
if (next["./package.json"] === void 0) next["./package.json"] = "./package.json";
|
|
435
|
-
pkg.exports = next;
|
|
436
|
-
}
|
|
437
|
-
async function expandCodegen(appDir, sourceDir, entry) {
|
|
438
|
-
const src = new LocalDirSource(sourceDir);
|
|
439
|
-
const targetDir = path5.join(appDir, "packages", entry.id);
|
|
440
|
-
await rm(targetDir, { recursive: true, force: true });
|
|
441
|
-
await mkdir2(targetDir, { recursive: true });
|
|
442
|
-
const rawPkg = JSON.parse(await src.readFile("package.json"));
|
|
443
|
-
const fromName = rawPkg.name ?? "";
|
|
444
|
-
const toName = `@app/${entry.id}`;
|
|
445
|
-
for (const rel of await src.listFiles()) {
|
|
446
|
-
if (rel === "package.json") continue;
|
|
447
|
-
let content = await src.readFile(rel);
|
|
448
|
-
if (fromName && fromName !== toName && rel.endsWith(".ts")) {
|
|
449
|
-
content = rewriteLoadModuleSkillsCall(content, fromName, toName);
|
|
450
|
-
}
|
|
451
|
-
const out = path5.join(targetDir, rel);
|
|
452
|
-
await mkdir2(path5.dirname(out), { recursive: true });
|
|
453
|
-
await writeFile3(out, content);
|
|
454
|
-
}
|
|
455
|
-
const pkg = rawPkg;
|
|
456
|
-
pkg.name = toName;
|
|
457
|
-
pkg.private = true;
|
|
458
|
-
if (pkg.dependencies && typeof pkg.dependencies === "object") {
|
|
459
|
-
rewritePlatformRefs(pkg.dependencies);
|
|
460
|
-
}
|
|
461
|
-
if (pkg.peerDependencies && typeof pkg.peerDependencies === "object") {
|
|
462
|
-
rewritePlatformRefs(pkg.peerDependencies);
|
|
463
|
-
}
|
|
464
|
-
rewriteExportsToSrc(pkg);
|
|
465
|
-
await writeFile3(path5.join(targetDir, "package.json"), JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
466
|
-
}
|
|
467
|
-
var LOCAL_SERVER_ENTRY = (id) => `import type { ServerContext, ServerModule } from '@tbox.cn/app-sdk/server';
|
|
2
|
+
import{a as Ee,b as _r,c as Re,d as Nr,e as Vr,f as Jr,g as zr,h as Ur,i as Br,j as Wr,k as Hr,l as Kr,m as Gr,n as qr,o as Xr,p as Ne,q as nt,r as qt,s as z,t as ue}from"./chunk-GY4SXKTS.js";import{d as rt}from"./chunk-SGF4P5YN.js";import{Command as fp}from"commander";import{createRequire as mp}from"module";import{fileURLToPath as gp}from"url";import{realpathSync as gi}from"fs";import{existsSync as Y,readdirSync as pt,realpathSync as lt,statSync as er,chmodSync as $i}from"fs";import{appendFile as ji,cp as Qt,readFile as Ei,rename as ln,writeFile as Ri}from"fs/promises";import T from"path";function B(e){return zr(e)}async function te(e,t){Jr(e,t)}function Yr(e,t){return Ur(e,t)}function Zr(e,t){return Br(e,t)}function Qr(e,t){return Wr(e,t)}function en(e,t,r){return Hr(e,t,r)}function ke(e,t){Kr(e,t)}import{existsSync as st,readFileSync as Xt,writeFileSync as yi}from"fs";import{execFileSync as hi}from"child_process";import ot from"path";function De(e){if(!st(ot.join(e,"pnpm-lock.yaml")))return!1;try{return hi("pnpm",["install","--lockfile-only"],{cwd:e,stdio:"pipe",encoding:"utf8"}),!0}catch{return console.warn(" ⚠️ lockfile 刷新失败(pnpm install --lockfile-only)——请手动 pnpm install 后提交 pnpm-lock.yaml"),!1}}function tn(e){let t=ot.join(e,"pnpm-lock.yaml"),r=ot.join(e,"pnpm-workspace.yaml"),n=ot.join(e,"package.json");if(!st(t)||!st(r)||!st(n))return 0;try{let s=JSON.parse(Xt(n,"utf8")),i=new Map;for(let f of[s.dependencies,s.devDependencies,s.optionalDependencies])for(let[g,m]of Object.entries(f??{}))m!=="catalog:"&&!m.startsWith("workspace:")&&!m.startsWith("file:")&&i.set(g,m);if(i.size===0)return 0;let o=vi(Xt(r,"utf8")),a=Xt(t,"utf8").split(`
|
|
3
|
+
`),c=!1,l=!1,p=!1,u=null,d=0;for(let f=0;f<a.length;f++){let g=a[f];if(!g.trim()||g.startsWith("#"))continue;if(!/^ /.test(g)){c=/^importers:/.test(g),l=!1,p=!1,u=null;continue}if(!c)continue;if(/^ {2}\S/.test(g)){l=/^ {2}\.:\s*$/.test(g),p=!1,u=null;continue}if(!l)continue;if(/^ {4}\S/.test(g)){p=/^( {4})(dependencies|devDependencies|optionalDependencies):\s*$/.test(g),u=null;continue}if(!p)continue;let m=g.match(/^ {6}['"]?([^'":\s]+)['"]?:\s*$/);if(m){u=m[1];continue}if(g.match(/^ {8}specifier:\s*['"]?catalog:['"]?\s*$/)&&u){if(i.has(u)){let v=o.get(u);v!==void 0&&(a[f]=g.replace(/^( {8}specifier:\s*)['"]?catalog:['"]?(\s*)$/,`$1${v}$2`),d++)}u=null}}return d>0&&yi(t,a.join(`
|
|
4
|
+
`)),d}catch(s){return console.warn(` ⚠️ 携带锁 specifiers 对齐失败(降级跳过——frozen install 如失败请手动 pnpm install): ${s.message}`),-1}}function vi(e){let t=new Map,r=!1;for(let n of e.split(`
|
|
5
|
+
`)){let s=n.replace(/\r$/,"");if(!s.trim()||s.trimStart().startsWith("#"))continue;if(!/^ /.test(s)){r=/^catalog\s*:/.test(s);continue}if(!r)continue;let i=s.match(/^\s+["']?([^"':#\s]+)["']?\s*:\s*(.+?)\s*(?:#.*)?$/);i&&t.set(i[1],i[2].replace(/^['"]|['"]$/g,""))}return t}import{existsSync as at,readFileSync as sn,mkdtempSync as on,rmSync as an}from"fs";import{execFileSync as bi}from"child_process";import{tmpdir as ct}from"os";import{readFile as ki,readdir as xi,stat as Op}from"fs/promises";import K from"path";import{execFileSync as it}from"child_process";import rn from"path";var Ve=null;function Yt(e){if(e?.startsWith("@tbox.cn/"))try{let r=it("pnpm",["config","get","@tbox.cn:registry"],{encoding:"utf8"}).trim().split(/\r?\n/).pop()??"";if(r)return r}catch{}if(Ve)return Ve;try{Ve=it("pnpm",["config","get","registry"],{encoding:"utf8"}).trim().split(/\r?\n/).pop()??"https://registry.npmjs.org/"}catch{Ve="https://registry.npmjs.org/"}return Ve}function Ce(e){return/^@[^/]+\/[^@/]+(@.+)?$/.test(e)}function wi(e){return e.replace(/@[^/]+$/,"")}function nn(e,t,r){let n=r??Yt(wi(e)),s=["view",e,"dist.tarball"];r&&s.push("--registry",r);let o=it("pnpm",s,{encoding:"utf8"}).trim().split(/\r?\n/).pop()??"";if(!o)throw new Error(`registry 拉取失败:${e} 无 dist.tarball(registry=${n})`);let a=o.split("/").pop()??`${e.replace(/^@/,"").replace(/\//g,"-").split("@")[0]}.tgz`,c=rn.join(rn.resolve(t),a);return it("curl",["-fsSL","-o",c,o],{stdio:"ignore"}),c}var Si=new Set(["node_modules","dist","dist-ssr",".git",".tbox"]);async function cn(e,t,r){let n=await xi(e,{withFileTypes:!0});for(let s of n){let i=K.join(e,s.name),o=K.relative(t,i).split(K.sep).join("/");if(s.isDirectory()){if(Si.has(s.name))continue;await cn(i,t,r)}else s.isFile()&&r.push(o)}}var re=class{constructor(t){this.dir=t;this.name=K.basename(K.resolve(t))}dir;name;getDir(){return this.dir}async listFiles(){let t=[];return await cn(this.dir,this.dir,t),t.sort()}async readFile(t){return ki(K.join(this.dir,t),"utf8")}static isUsable(t){return at(K.join(t,"package.json"))}resolveVersion(){return Zt(this.dir).version}};function Zt(e){let t;try{t=sn(K.join(e,"package.json"),"utf8")}catch{return{version:"0.0.0"}}try{let r=JSON.parse(t);return{name:r?.name,version:r?.version??"0.0.0"}}catch{return{version:"0.0.0"}}}var de=class{constructor(t,r){this.spec=t;this.registry=r;this.name=t,this.packDir=on(K.join(ct(),"tbox-reg-"))}spec;registry;name;inner=null;packDir;resolvedName="";resolvedVersion="";resolve(){if(this.inner)return this.inner;let t=this.spec.replace(/@[^/]+$/,""),r=this.registry??Yt(t);console.log(`📦 从 registry 拉取 ${this.spec}(${r})`);let n=nn(this.spec,this.packDir,this.registry);this.inner=new U(n);let s,i="";try{let o=JSON.parse(sn(K.join(this.inner.getDir(),"package.json"),"utf8"));s=o.name,i=o.version??""}catch{}return this.resolvedName=s??this.spec,this.resolvedVersion=i,console.log(` → ${this.resolvedName}@${this.resolvedVersion}`),this.inner}listFiles(){return Promise.resolve(this.resolve()).then(t=>t.listFiles())}readFile(t){return Promise.resolve(this.resolve()).then(r=>r.readFile(t))}resolveVersion(){return this.resolve(),this.resolvedVersion}getDir(){return this.resolve().getDir()}get resolvedPackage(){return{name:this.resolvedName,version:this.resolvedVersion}}dispose(){this.inner&&this.inner.dispose();try{this.packDir.startsWith(ct())&&an(this.packDir,{recursive:!0,force:!0})}catch{}}};function fe(e,t){if(e.endsWith(".tgz")&&at(e)){let r=new U(e);return{sourceDir:r.getDir(),source:r}}if(!at(e)&&Ce(e)){let r=new de(e,t);return{sourceDir:r.getDir(),source:r}}return{sourceDir:e,source:new re(e)}}var U=class{name;inner;tmpRoot;constructor(t){let r=K.resolve(t);this.name=K.basename(r,".tgz"),this.tmpRoot=on(K.join(ct(),"tbox-tgz-")),bi("tar",["-xzf",r,"-C",this.tmpRoot],{stdio:"ignore"});let n=K.join(this.tmpRoot,"package");this.inner=new re(at(n)?n:this.tmpRoot)}getDir(){return this.inner.getDir()}listFiles(){return this.inner.listFiles()}readFile(t){return this.inner.readFile(t)}resolveVersion(){return this.inner.resolveVersion()}dispose(){try{this.tmpRoot.startsWith(ct())&&an(this.tmpRoot,{recursive:!0,force:!0})}catch{}}};var Di=new Set(["node_modules","dist","dist-ssr",".git",".DS_Store"]),Ci=new Set(["pnpm-lock.yaml"]),Ti="pnpm-workspace.template.yaml",pn="pnpm-workspace.yaml",Mi="pnpm-lock.template.yaml",Ii="pnpm-lock.yaml";function Ai(e,t){let r=pt(e).filter(o=>/^tbox\.cn-app-(sdk|toolkit|contracts)-.+\.tgz$/.test(o)).sort();if(r.length===0)return null;let n=lt(t),s=new Set,i=[];for(let o of r){let a=o.replace(/^tbox\.cn-app-/,"").replace(/\.tgz$/,""),c=a.lastIndexOf("-"),l="@tbox.cn/app-"+a.slice(0,c);if(s.has(l))throw new Error(`--local-deps 目录含同一平台包多版本(overrides 重复 key 歧义,须单版本目录): ${l}`);s.add(l);let p=T.relative(n,lt(T.join(e,o)));i.push(` ${JSON.stringify(l)}: ${JSON.stringify(`file:${p}`)}`)}return`
|
|
6
|
+
# create --local-deps 注入:平台包经 file: 本地发布态 tgz 消费(005 v0.4.0 验证态;生产 B 档移除本块走 registry)
|
|
7
|
+
overrides:
|
|
8
|
+
`+i.join(`
|
|
9
|
+
`)+`
|
|
10
|
+
`}function Oi(e){let t=pt(e).filter(n=>/^tbox\.cn-app-(sdk|contracts)-.+\.tgz$/.test(n));if(t.length===0)throw new Error(`--local-deps 目录无平台包产物(缺 sdk/contracts tgz): ${e}`);let r=new Set;for(let n of t){let s=n.replace(/^tbox\.cn-app-/,"").replace(/\.tgz$/,""),i=s.lastIndexOf("-"),o="@tbox.cn/app-"+s.slice(0,i);if(r.has(o))throw new Error(`--local-deps 目录含同一平台包多版本(overrides 重复 key 歧义,须单版本目录): ${o}`);r.add(o)}}function un(e){let t=0;for(let r of pt(e,{withFileTypes:!0}))r.name===".DS_Store"||r.name==="node_modules"||(r.isDirectory()?t+=un(T.join(e,r.name)):t+=1);return t}function Pi(e){if(!Y(e)||!er(e).isDirectory())throw new Error(`--config-dir 不是目录: ${e}`);if(un(e)===0)throw new Error(`--config-dir 目录为空(无任何文件可合并): ${e}`)}var Li=/^(?:@[a-z0-9._-]+\/)?[a-z0-9][a-z0-9._-]*$/;function Fi(e){let t=T.join(e,"scripts");if(!Y(t))return;function r(n){for(let s of pt(n,{withFileTypes:!0})){let i=T.join(n,s.name);s.isDirectory()?r(i):s.isFile()&&$i(i,493)}}r(t)}function _i(e){if(!Li.test(e))throw new Error(`应用包名无效: ${e}(需为 npm 包名;使用 --name 指定)`)}function Ni(e){return e===T.resolve(process.cwd())}function Vi(e,t){let r=T.relative(e,t);return r!==""&&r!==".."&&!r.startsWith(`..${T.sep}`)&&!T.isAbsolute(r)}function Ji(e,t){if(!Y(e))return;let r=er(e),n=lt(e),s=Y(t)?lt(t):T.resolve(t);if(n===s||r.isDirectory()&&Vi(n,s))throw new Error(`模板源与目标目录重合或目标位于模板源内,无法初始化: ${e}`)}async function dn(e){let t=T.resolve(process.cwd(),e.name),r=Y(t),n=Ni(t);if(r&&!er(t).isDirectory())throw new Error(`目标不是目录: ${e.name}`);if(r&&!n&&!e.force)throw new Error(`目录已存在: ${e.name}(加 --force 覆盖)`);if(n&&Y(T.join(t,".tbox","app.json"))&&!e.force)throw new Error("当前目录已有 tbox-app 应用(存在 .tbox/app.json;重新初始化请加 --force)");let s=e.appName??(n?T.basename(t):e.name);if(!s)throw new Error("无法从目标目录推导应用包名,请使用 --name 指定");e.appName&&_i(s);let i=Array.isArray(e.configDir)?e.configDir:e.configDir?[e.configDir]:[],o=e.source,a=T.resolve(o),c=!Y(a)&&Ce(o),l=a.endsWith(".tgz");if(!c&&!l&&!re.isUsable(a))throw new Error(`模板源无效(缺 package.json): ${a}`);if(!c&&l&&!Y(a))throw new Error(`模板源无效(tgz 不存在): ${a}`);if(Ji(a,t),c&&e.localDeps)throw new Error("registry 源(--source @scope/pkg)与 --local-deps 互斥:registry 安装无需/不允许本地依赖注入");e.localDeps&&Oi(T.resolve(e.localDeps));for(let d of i)Pi(T.resolve(d));let p=fe(c?o:a,e.registry),u=p.sourceDir;try{let d=B(u);if(!d)throw new Error(`模板结构异常: ${a} 的 .tbox/app.json 缺失、损坏或缺少 templateVersion(出生版本)字段`);await Qt(u,t,{recursive:!0,force:!0,filter:S=>{let $=T.basename(S);return!(Ci.has($)||Di.has($))}}),Fi(t);let f=T.join(t,Ti);Y(f)&&await ln(f,T.join(t,pn));let g=T.join(t,Mi),m=Y(g);if(m){await ln(g,T.join(t,Ii));let S=tn(t);S>0&&console.log(` 🔒 携带锁 specifiers 已对齐 registry 归一态(${S} 项 catalog: → 字面量)`)}if(e.localDeps){let S=Ai(T.resolve(e.localDeps),t);await ji(T.join(t,pn),S,"utf8"),m&&De(t)}for(let S of i)await Qt(T.resolve(S),T.join(t,"config"),{recursive:!0,force:!0,filter:$=>T.basename($)!==".DS_Store"&&T.basename($)!=="node_modules"});e.envFile&&await Qt(T.resolve(e.envFile),T.join(t,"config","env.sh"),{force:!0});let h=T.join(t,"package.json");if(Y(h)){let S=JSON.parse(await Ei(h,"utf8"));S.name=s,await Ri(h,JSON.stringify(S,null,2)+`
|
|
11
|
+
`,"utf8")}let v={templateVersion:d.templateVersion,npmModules:[]};await te(t,v),console.log(`✅ 已创建应用 ${s}`),console.log(c?` ⚡ 已从 registry 模板包 ${p.source.name} 展开(发布态)`:l?` ⚡ 已从模板包 ${T.basename(a)} 展开(发布态)`:" ⚡ 已从模板目录展开(本地开发源)"),e.localDeps?(console.log(` ⚡ 已注入平台 overrides(--local-deps: ${e.localDeps},install 离线消费本地发布态产物)`),m&&console.log(" 🔒 携带锁已增量刷新为 file: 形态(仅平台三包换形态,间接依赖保钉)——B 档移除 overrides 块后 plain install 回 registry 形态、frozen 恢复可用")):m&&console.log(" 🔒 依赖已由 pnpm-lock.yaml 冻结(直接+间接全精确)——请随应用提交,安装可 --frozen-lockfile"),i.length>0&&console.log(` ⚡ 已合并应用配置(--config-dir: ${i.join(" + ")} → config/)`),console.log(" ⚡ 已生成 pnpm-workspace.yaml(应用 workspace)"),console.log(n?" 下一步: pnpm install":` 下一步: cd ${e.name} && pnpm install`),console.log(" 📱 支付宝小程序壳: tbox-app miniapp-container init(web-view 承载 H5 + JSAPI 桥代理)"),console.log(" 🔐 鉴权(006): 生产请由平台注入 AUTH_TOKEN_SECRET 且确保 NODE_ENV=production(dev-login 强制关闭);"),console.log(" 本地开发默认开启 dev-login(非 production);如需关闭设 AUTH_DEV_LOGIN_DISABLED=true。")}finally{p.source instanceof U&&p.source.dispose()}}import{existsSync as ve,readFileSync as wr,readdirSync as cc}from"fs";import Q from"path";import{copyFile as Zi,mkdir as kt,rm as xn,writeFile as ne}from"fs/promises";import W from"path";import{existsSync as ze,readFileSync as mt}from"fs";import{readFile as dt,readdir as Bi,writeFile as Wi}from"fs/promises";import _ from"path";import V from"typescript";var zi=/^@tbox\.cn\/app-(?:sdk|toolkit|contracts(?:-|$))/,Ui=new Set(["@tbox.cn/app-contracts-mall"]);function ut(e){return zi.test(e)}function Je(e){return Ui.has(e)}var mn=["dependencies","optionalDependencies","peerDependencies","devDependencies"],Hi=new Set(["name","version","private","exports"]),gt=/\.(?:ts|tsx|mts|cts|js|jsx|mjs|cjs)$/,Ki=new Set(["node_modules","dist",".git",".tbox"]),Gi=new Map([["@tbox.cn/app-contracts-mall",{exports:{".":{types:"./src/index.ts",import:"./src/runtime.ts"},"./server":{types:"./src/server/index.ts",import:"./src/server/index.ts"},"./package.json":"./package.json"},files:{"src/index.ts":`export * from '@tbox.cn/app-contracts-mall';
|
|
12
|
+
`,"src/runtime.ts":`export * from '@tbox.cn/app-contracts-mall';
|
|
13
|
+
`,"src/server/index.ts":`export * from '@tbox.cn/app-contracts-mall/server';
|
|
14
|
+
`}}]]);function yt(e){return`@app/${e}`}function gn(e,t){return Je(e)||t?"codegen":"sdk"}function xe(e){return{id:e.id,publicName:e.package,localName:yt(e.id),version:e.version}}function qi(e){let t=new Map;for(let r of e?.npmModules??[]){if(r.mode!=="codegen")continue;let n=xe(r);t.set(n.publicName,n)}return t}function yn(e,t){let r=new Map(e);return r.set(t.publicName,t),r}function Se(e){return qi(B(e))}function ht(e){try{return JSON.parse(mt(_.join(e,"package.json"),"utf8")).tboxCodegenShim===!0}catch{return!1}}function Xi(e,t,r){let n=[...t].sort((s,i)=>{let o=r==="local"?s.publicName:s.localName;return(r==="local"?i.publicName:i.localName).length-o.length});for(let s of n){let i=r==="local"?s.publicName:s.localName,o=r==="local"?s.localName:s.publicName;if(e===i)return o;if(e.startsWith(`${i}/`))return`${o}${e.slice(i.length)}`}return e}function Ue(e,t,r,n="local"){let s=[...r];if(s.length===0)return e;let i=t.endsWith(".tsx")?V.ScriptKind.TSX:t.endsWith(".jsx")?V.ScriptKind.JSX:t.endsWith(".json")?V.ScriptKind.JSON:V.ScriptKind.TS,o=V.createSourceFile(t,e,V.ScriptTarget.Latest,!0,i),a=[];function c(u){let d=Xi(u.text,s,n);if(d===u.text)return;let f=e.slice(u.getStart(o),u.end),g=f[0];(g==="'"||g==='"'||g==="`")&&f.at(-1)===g&&a.push({start:u.getStart(o),end:u.end,value:`${g}${d}${g}`})}function l(u){if(V.isImportDeclaration(u)&&V.isStringLiteralLike(u.moduleSpecifier))c(u.moduleSpecifier);else if(V.isExportDeclaration(u)&&u.moduleSpecifier&&V.isStringLiteralLike(u.moduleSpecifier))c(u.moduleSpecifier);else if(V.isImportTypeNode(u)&&V.isLiteralTypeNode(u.argument)&&V.isStringLiteralLike(u.argument.literal))c(u.argument.literal);else if(V.isCallExpression(u)&&u.arguments.length>0&&V.isStringLiteralLike(u.arguments[0])){let d=u.expression.kind===V.SyntaxKind.ImportKeyword,f=V.isIdentifier(u.expression)&&u.expression.text==="loadModuleSkills";(d||f)&&c(u.arguments[0])}else if(V.isPropertyAssignment(u)&&V.isArrayLiteralExpression(u.initializer)){let d=u.name.getText(o).replace(/["']/g,"");if(d==="external"||d==="noExternal")for(let f of u.initializer.elements)V.isStringLiteralLike(f)&&c(f)}V.forEachChild(u,l)}l(o);let p=e;for(let u of a.sort((d,f)=>f.start-d.start))p=p.slice(0,u.start)+u.value+p.slice(u.end);return p}function hn(e,t,r,n){let s=[...t];for(let i of mn){let o=e[i];if(!o||typeof o!="object"||Array.isArray(o))continue;let a={...o};for(let c of s){let l=r==="local"?c.publicName:c.localName,p=r==="local"?c.localName:c.publicName;l in a&&(delete a[l],a[p]=r==="local"?"workspace:*":"catalog:")}for(let c of Object.keys(a))ut(c)?a[c]="catalog:":a[c]==="catalog:"&&n?.has(c)&&(a[c]=n.get(c));e[i]=a}}function Yi(e){if(e==="./package.json"||!e.startsWith("./dist/"))return e;let t=e.replace(/^\.\/dist\//,"./src/");return t.endsWith(".d.ts")?`${t.slice(0,-5)}.ts`:t.endsWith(".js")?`${t.slice(0,-3)}.ts`:t}function tr(e){return typeof e=="string"?Yi(e):Array.isArray(e)?e.map(tr):!e||typeof e!="object"?e:Object.fromEntries(Object.entries(e).map(([t,r])=>[t,tr(r)]))}function vn(e){if(!e.exports||typeof e.exports!="object")return;let t=tr(e.exports);"./package.json"in t||(t["./package.json"]="./package.json"),e.exports=t}function Te(e,t,r,n){let s=JSON.parse(JSON.stringify(e));return s.name=yt(t.id),s.private=!0,hn(s,r,"local",n),vn(s),s}function ce(e){let t=/^\s+["']?([^"':#\s]+)["']?\s*:\s*(.+?)\s*(?:#.*)?$/,r=_.resolve(e);for(;;){let n=_.join(r,"pnpm-workspace.yaml");if(ze(n)){let i=new Map,o=!1;for(let a of mt(n,"utf8").split(`
|
|
15
|
+
`)){let c=a.replace(/\r$/,"");if(/^\S/.test(c)){o=/^catalog\s*:/.test(c);continue}if(!o)continue;let l=c.match(t);l&&i.set(l[1],l[2].replace(/^['"]|['"]$/g,""))}return i.size>0?i:void 0}let s=_.dirname(r);if(s===r)return;r=s}}function fn(e,t){return JSON.stringify(e)===JSON.stringify(t)}function wn(e,t,r,n={}){let s={},i=new Set([...Object.keys(e),...Object.keys(t),...Object.keys(r)]);for(let o of i){if(Hi.has(o)){let a=n.ours?t[o]:r[o];a!==void 0&&(s[o]=a);continue}if(n.ours){t[o]!==void 0&&(s[o]=t[o]);continue}if(n.theirs){r[o]!==void 0&&(s[o]=r[o]);continue}fn(t[o],e[o])?r[o]!==void 0&&(s[o]=r[o]):fn(r[o],e[o])?t[o]!==void 0&&(s[o]=t[o]):r[o]!==void 0?s[o]=r[o]:t[o]!==void 0&&(s[o]=t[o])}return s}function vt(e){if(!ze(_.join(e,"package.json")))throw new Error(`codegen 源无效(缺 package.json): ${e}`);let t;try{t=JSON.parse(mt(_.join(e,"package.json"),"utf8")).name}catch{throw new Error(`codegen 源 package.json 无法解析: ${e}`)}if(t&&Je(t)&&!ze(_.join(e,"src","index.ts")))throw new Error(`codegen 源缺少 src/index.ts:请使用包含源码载荷的契约包版本(${e})`)}function wt(e,t){let r=[...t];return new Map([...e].map(([n,s])=>[n,gt.test(n)?Ue(s,n,r):s]))}async function bn(e,t){let r=Gi.get(t.publicName);if(!r)return!1;let n=_.join(e,"packages",t.id),{mkdir:s,rm:i,writeFile:o}=await import("fs/promises");await i(n,{recursive:!0,force:!0}),await s(n,{recursive:!0});let a={name:t.localName,version:"0.0.0",private:!0,type:"module",exports:r.exports,scripts:{typecheck:"tsc --noEmit",test:"vitest run --passWithNoTests"},dependencies:{[t.publicName]:"catalog:"},devDependencies:{"@types/node":"catalog:",typescript:"catalog:",vitest:"catalog:"},tboxCodegenShim:!0};await o(_.join(n,"package.json"),JSON.stringify(a,null,2)+`
|
|
16
|
+
`,"utf8");for(let[c,l]of Object.entries(r.files)){let p=_.join(n,c);await s(_.dirname(p),{recursive:!0}),await o(p,l,"utf8")}return!0}async function ft(e,t){if(ze(e))for(let r of await Bi(e,{withFileTypes:!0})){if(Ki.has(r.name))continue;let n=_.join(e,r.name);r.isDirectory()?await ft(n,t):r.isFile()&&(gt.test(r.name)||r.name==="package.json")&&t.push(n)}}async function bt(e,t,r="local"){let n=[...t],s=ce(e),i=[];await ft(_.join(e,"packages"),i);let o=[];for(let a of i){let c=_.relative(e,a).split(_.sep).join("/"),l=await dt(a,"utf8"),p=l;if(_.basename(a)==="package.json")try{let u=JSON.parse(l);hn(u,n,r,s),p=JSON.stringify(u,null,2)+`
|
|
17
|
+
`}catch{continue}else p=Ue(l,c,n,r);p!==l&&(await Wi(a,p,"utf8"),o.push(c))}return o}async function kn(e){let t=Se(e),r=[],n=[];await ft(_.join(e,"apps"),n),await ft(_.join(e,"packages"),n);for(let s of t.values()){let i=_.join(e,"packages",s.id);if(!ze(i))r.push(`packages/${s.id}: codegen 包缺失`);else{let o=_.join(i,"package.json");try{JSON.parse(await dt(o,"utf8")).name!==s.localName&&r.push(`packages/${s.id}/package.json: name 应为 ${s.localName}`)}catch{r.push(`packages/${s.id}/package.json: 无法解析`)}}}for(let s of n){let i=_.relative(e,s).split(_.sep).join("/");if(_.basename(s)==="package.json"){try{let a=JSON.parse(await dt(s,"utf8"));for(let c of mn){let l=a[c];if(!l||typeof l!="object"||Array.isArray(l))continue;let p=l;for(let u of t.values())(u.publicName in p||u.localName in p&&p[u.localName]!=="workspace:*")&&r.push(`${i}: codegen 依赖未归一为 ${u.localName}: workspace:*`)}}catch{}continue}let o=await dt(s,"utf8");Ue(o,i,t.values())!==o&&r.push(`${i}: 源码仍引用 codegen 发布包名`)}return[...new Set(r)]}function Be(e){return JSON.parse(mt(_.join(e,"package.json"),"utf8"))}function Sn(e){return e.replace(/^@tbox\.cn\/app-/,"").replace(/^@[^/]+\//,"")}async function $n(e,t,r,n=[]){let s=new re(t),i=W.join(e,"packages",r.id);await xn(i,{recursive:!0,force:!0}),await kt(i,{recursive:!0});let o=Be(t),a=typeof o.name=="string"?o.name:r.package,c=xe({id:r.id,package:a,version:r.version}),l=yn(new Map([...n].map(u=>[u.publicName,u])),c);for(let u of await s.listFiles()){if(u==="package.json")continue;let d=W.join(i,u);if(await kt(W.dirname(d),{recursive:!0}),!gt.test(u)){await Zi(W.join(t,u),d);continue}let f=await s.readFile(u);await ne(d,Ue(f,u,l.values()))}let p=Te(o,r,l.values(),ce(t));await ne(W.join(i,"package.json"),JSON.stringify(p,null,2)+`
|
|
18
|
+
`,"utf8")}var Qi=e=>`import type { ServerContext, ServerModule } from '@tbox.cn/app-sdk/server';
|
|
468
19
|
|
|
469
20
|
/**
|
|
470
|
-
* ${
|
|
471
|
-
*
|
|
472
|
-
* cards map
|
|
21
|
+
* ${e} 本地模块(local 模式,app 自有包)注册入口。
|
|
22
|
+
* 在此注册工具 / 卡片 meta / handler / 路由 / Service(只注册不解析,resolveService 仅 handle/事件回调内)。
|
|
23
|
+
* cards map 的 key 约定 = meta.cardType;无卡片模块写空 map。
|
|
473
24
|
*/
|
|
474
25
|
const cards = {} as const;
|
|
475
26
|
|
|
476
27
|
export const serverModule = {
|
|
477
28
|
cards,
|
|
478
|
-
//
|
|
29
|
+
// 参数命名 _ctx:骨架方法体为空,规避 tsconfig.base noUnusedParameters(TS6133)
|
|
479
30
|
register(_ctx: ServerContext): void {
|
|
480
31
|
// _ctx.tools.register(...);
|
|
481
32
|
// _ctx.cards.registerMap(cards);
|
|
@@ -483,25 +34,22 @@ export const serverModule = {
|
|
|
483
34
|
// _ctx.services.register('member', member);
|
|
484
35
|
},
|
|
485
36
|
} satisfies ServerModule;
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
* Service \u5C42\uFF08\u4E1A\u52A1\u4E13\u5C5E\u5B9E\u73B0\uFF0C\u76F4\u63A5 import \u6216\u7ECF ctx.services \u6CE8\u518C\u4F9B\u8DE8\u6A21\u5757\u6D88\u8D39\uFF09\u3002
|
|
37
|
+
`,ea=`/**
|
|
38
|
+
* Service 层(业务专属实现,直接 import 或经 ctx.services 注册供跨模块消费)。
|
|
489
39
|
*/
|
|
490
40
|
export {};
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
* Handler \u5B9A\u4E49\uFF08001 \xA73.2\uFF09\u3002
|
|
41
|
+
`,ta=`/**
|
|
42
|
+
* Handler 定义(001 §3.2)。
|
|
494
43
|
*/
|
|
495
44
|
import type { HandlerDefinition } from '@tbox.cn/app-sdk/server';
|
|
496
45
|
|
|
497
46
|
export const exampleHandler: HandlerDefinition = {
|
|
498
47
|
id: 'example',
|
|
499
|
-
handle: async () => ({ cards: [], text: '
|
|
48
|
+
handle: async () => ({ cards: [], text: '本地模块示例' }),
|
|
500
49
|
};
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
*
|
|
504
|
-
* cards map \u7684 key \u7EA6\u5B9A = cardType\uFF1B\u65E0\u5361\u7247\u6A21\u5757\u5199\u7A7A map\u3002
|
|
50
|
+
`,ra=`/**
|
|
51
|
+
* 客户端入口:导出 clientModule(CLI sync 装配读取;与 serverModule 逐行对称)。
|
|
52
|
+
* cards map 的 key 约定 = cardType;无卡片模块写空 map。
|
|
505
53
|
*/
|
|
506
54
|
import type { ClientContext, ClientModule } from '@tbox.cn/app-sdk/client';
|
|
507
55
|
|
|
@@ -509,20 +57,18 @@ const cards = {} as const;
|
|
|
509
57
|
|
|
510
58
|
export const clientModule = {
|
|
511
59
|
cards,
|
|
512
|
-
//
|
|
60
|
+
// 参数命名 _ctx:骨架方法体为空,规避 tsconfig.base noUnusedParameters(TS6133)
|
|
513
61
|
register(_ctx: ClientContext): void {
|
|
514
62
|
// _ctx.cards.registerMap(cards);
|
|
515
63
|
},
|
|
516
64
|
} satisfies ClientModule;
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
*
|
|
520
|
-
* \u6D88\u8D39\u65B9\u901A\u5E38\u8D70 ./server / ./client \u5B50\u8DEF\u5F84\uFF1B\u4E3B\u5165\u53E3\u805A\u5408\u5BFC\u51FA\u4FBF\u4E8E\u76F4\u63A5\u5F15\u7528\u3002
|
|
65
|
+
`,na=`/**
|
|
66
|
+
* 本地模块主入口(package.json exports "." 指向)。
|
|
67
|
+
* 消费方通常走 ./server / ./client 子路径;主入口聚合导出便于直接引用。
|
|
521
68
|
*/
|
|
522
69
|
export * from './server';
|
|
523
70
|
export * from './client';
|
|
524
|
-
|
|
525
|
-
var LOCAL_TSCONFIG = `{
|
|
71
|
+
`,sa=`{
|
|
526
72
|
"extends": "../../tsconfig.base.json",
|
|
527
73
|
"compilerOptions": {
|
|
528
74
|
"jsx": "react-jsx",
|
|
@@ -530,2849 +76,179 @@ var LOCAL_TSCONFIG = `{
|
|
|
530
76
|
},
|
|
531
77
|
"include": ["src"]
|
|
532
78
|
}
|
|
533
|
-
`;
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
private: true,
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
"./server": { types: "./src/server/index.ts", import: "./src/server/index.ts" },
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
)
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
await writeFile3(path5.join(dir, "src/server/service.ts"), LOCAL_SERVICE, "utf8");
|
|
597
|
-
await writeFile3(path5.join(dir, "src/server/handler.ts"), LOCAL_HANDLER, "utf8");
|
|
598
|
-
await writeFile3(path5.join(dir, "src/client/index.ts"), LOCAL_CLIENT_INDEX, "utf8");
|
|
599
|
-
await writeFile3(path5.join(dir, "src/index.ts"), LOCAL_ROOT_INDEX, "utf8");
|
|
600
|
-
return id;
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
// src/apps-deps.ts
|
|
604
|
-
import { existsSync as existsSync4 } from "fs";
|
|
605
|
-
import { readFileSync as readFileSync3 } from "fs";
|
|
606
|
-
import { writeFile as writeFile4 } from "fs/promises";
|
|
607
|
-
import path6 from "path";
|
|
608
|
-
var APP_TARGETS = ["apps/server/package.json", "apps/client/package.json"];
|
|
609
|
-
async function updateAppsDependency(appDir, pkgName, action) {
|
|
610
|
-
let changed = false;
|
|
611
|
-
for (const rel of APP_TARGETS) {
|
|
612
|
-
const file = path6.join(appDir, rel);
|
|
613
|
-
if (!existsSync4(file)) continue;
|
|
614
|
-
const pkg = JSON.parse(readFileSync3(file, "utf8"));
|
|
615
|
-
const deps = pkg.dependencies ??= {};
|
|
616
|
-
if (action === "add" && !deps[pkgName]) {
|
|
617
|
-
deps[pkgName] = "workspace:*";
|
|
618
|
-
changed = true;
|
|
619
|
-
} else if (action === "remove" && deps[pkgName]) {
|
|
620
|
-
delete deps[pkgName];
|
|
621
|
-
changed = true;
|
|
622
|
-
}
|
|
623
|
-
if (changed) await writeFile4(file, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
624
|
-
}
|
|
625
|
-
return changed;
|
|
626
|
-
}
|
|
627
|
-
async function removePackageDir(appDir, id) {
|
|
628
|
-
const dir = path6.join(appDir, "packages", id);
|
|
629
|
-
if (!existsSync4(dir)) return false;
|
|
630
|
-
const { rm: rm2 } = await import("fs/promises");
|
|
631
|
-
await rm2(dir, { recursive: true, force: true });
|
|
632
|
-
return true;
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
// src/sync-all.ts
|
|
636
|
-
import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
|
|
637
|
-
import { readFile as readFile3, writeFile as writeFile6 } from "fs/promises";
|
|
638
|
-
import path9 from "path";
|
|
639
|
-
|
|
640
|
-
// src/assembly.ts
|
|
641
|
-
var ASSEMBLY_FILES = [
|
|
642
|
-
{ path: "apps/server/src/modules.ts", kind: "server-modules" },
|
|
643
|
-
{ path: "apps/client/src/modules.ts", kind: "client-modules" }
|
|
644
|
-
];
|
|
645
|
-
function camelCaseId(id) {
|
|
646
|
-
return id.split(/[^A-Za-z0-9]+/).filter(Boolean).map(
|
|
647
|
-
(part, i) => i === 0 ? part.toLowerCase() : part.charAt(0).toUpperCase() + part.slice(1).toLowerCase()
|
|
648
|
-
).join("");
|
|
649
|
-
}
|
|
650
|
-
function importAlias(kind, id) {
|
|
651
|
-
return camelCaseId(id);
|
|
652
|
-
}
|
|
653
|
-
function contributesTo(kind, m) {
|
|
654
|
-
return KINDS[kind].contributes(m);
|
|
655
|
-
}
|
|
656
|
-
function idFromPkg(pkg) {
|
|
657
|
-
return pkg.replace(/^@[^/]+\//, "").replace(/\/(server|client)$/, "").replace(/\/$/, "");
|
|
658
|
-
}
|
|
659
|
-
var KINDS = {
|
|
660
|
-
"server-modules": {
|
|
661
|
-
mapMarker: "register",
|
|
662
|
-
importExport: "serverModule",
|
|
663
|
-
importSuffix: "server",
|
|
664
|
-
entryPattern: /^\s*([A-Za-z_$][\w$]*),\s*$/,
|
|
665
|
-
header: "const modules = [",
|
|
666
|
-
footer: "];",
|
|
667
|
-
contributes: (m) => m.hasServerEntry
|
|
668
|
-
},
|
|
669
|
-
"client-modules": {
|
|
670
|
-
mapMarker: "register",
|
|
671
|
-
importExport: "clientModule",
|
|
672
|
-
importSuffix: "client",
|
|
673
|
-
entryPattern: /^\s*([A-Za-z_$][\w$]*),\s*$/,
|
|
674
|
-
header: "const modules = [",
|
|
675
|
-
footer: "];",
|
|
676
|
-
// 客户端装配仅收录"有卡片 + 有 clientModule 入口"的模块;老形态(无 clientModule)跳过并由
|
|
677
|
-
// assembly-drift 告警提示升级(D8:新 app + 老模块 → 探测缺失跳过)
|
|
678
|
-
contributes: (m) => m.hasClientEntry && m.hasCards
|
|
679
|
-
}
|
|
680
|
-
};
|
|
681
|
-
var IMPORT_LINE_PATTERN = /^import\s*\{\s*([\w$]+)\s+as\s+([\w$]+)\s*\}\s*from\s*['"]([^'"]+)['"];\s*$/;
|
|
682
|
-
function extractBlock(content, marker) {
|
|
683
|
-
const begin = `// @tbox:${marker}-begin`;
|
|
684
|
-
const end = `// @tbox:${marker}-end`;
|
|
685
|
-
const bIdx = content.indexOf(begin);
|
|
686
|
-
if (bIdx === -1) return null;
|
|
687
|
-
const eIdx = content.indexOf(end, bIdx);
|
|
688
|
-
if (eIdx === -1) return null;
|
|
689
|
-
const start = bIdx + begin.length;
|
|
690
|
-
return { block: content.slice(start, eIdx), start, end: eIdx };
|
|
691
|
-
}
|
|
692
|
-
function replaceBlock(content, range, newBlock) {
|
|
693
|
-
return content.slice(0, range.start) + newBlock + content.slice(range.end);
|
|
694
|
-
}
|
|
695
|
-
function parseAssemblyFile(content, kind) {
|
|
696
|
-
const spec = KINDS[kind];
|
|
697
|
-
const moduleIds = [];
|
|
698
|
-
const nonStandardLines = [];
|
|
699
|
-
const broken = [];
|
|
700
|
-
const imports = extractBlock(content, "imports");
|
|
701
|
-
if (imports) {
|
|
702
|
-
for (const line of imports.block.split(/\r?\n/)) {
|
|
703
|
-
const m = line.match(IMPORT_LINE_PATTERN);
|
|
704
|
-
if (m) {
|
|
705
|
-
moduleIds.push(idFromPkg(m[3]));
|
|
706
|
-
} else if (line.trim()) {
|
|
707
|
-
nonStandardLines.push(`imports: ${line.trim()}`);
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
} else {
|
|
711
|
-
broken.push("imports");
|
|
712
|
-
}
|
|
713
|
-
const map = extractBlock(content, spec.mapMarker);
|
|
714
|
-
if (map) {
|
|
715
|
-
const lines = map.block.split(/\r?\n/);
|
|
716
|
-
const headIdx = lines.findIndex((l) => l.trim().startsWith(spec.header));
|
|
717
|
-
const tailIdx = [...lines].reverse().findIndex((l) => l.trim() === spec.footer);
|
|
718
|
-
const lastIdx = tailIdx === -1 ? -1 : lines.length - 1 - tailIdx;
|
|
719
|
-
if (headIdx === -1 || lastIdx <= headIdx) {
|
|
720
|
-
broken.push(spec.mapMarker);
|
|
721
|
-
} else {
|
|
722
|
-
for (const line of lines.slice(headIdx + 1, lastIdx)) {
|
|
723
|
-
if (!line.match(spec.entryPattern) && line.trim()) {
|
|
724
|
-
nonStandardLines.push(`${spec.mapMarker}: ${line.trim()}`);
|
|
725
|
-
}
|
|
726
|
-
}
|
|
727
|
-
}
|
|
728
|
-
} else {
|
|
729
|
-
broken.push(spec.mapMarker);
|
|
730
|
-
}
|
|
731
|
-
const aliasToId = /* @__PURE__ */ new Map();
|
|
732
|
-
if (imports) {
|
|
733
|
-
for (const line of imports.block.split(/\r?\n/)) {
|
|
734
|
-
const m = line.match(IMPORT_LINE_PATTERN);
|
|
735
|
-
if (m) aliasToId.set(m[2], idFromPkg(m[3]));
|
|
736
|
-
}
|
|
737
|
-
}
|
|
738
|
-
const mapIds = [];
|
|
739
|
-
if (map) {
|
|
740
|
-
const lines = map.block.split(/\r?\n/);
|
|
741
|
-
const headIdx = lines.findIndex((l) => l.trim().startsWith(spec.header));
|
|
742
|
-
const tailIdx = [...lines].reverse().findIndex((l) => l.trim() === spec.footer);
|
|
743
|
-
const lastIdx = tailIdx === -1 ? -1 : lines.length - 1 - tailIdx;
|
|
744
|
-
if (headIdx !== -1 && lastIdx > headIdx) {
|
|
745
|
-
for (const line of lines.slice(headIdx + 1, lastIdx)) {
|
|
746
|
-
const m = line.match(spec.entryPattern);
|
|
747
|
-
if (m) {
|
|
748
|
-
const id = aliasToId.get(m[1]);
|
|
749
|
-
if (id) mapIds.push(id);
|
|
750
|
-
}
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
|
-
}
|
|
754
|
-
return {
|
|
755
|
-
moduleIds: [.../* @__PURE__ */ new Set([...moduleIds, ...mapIds])],
|
|
756
|
-
nonStandardLines,
|
|
757
|
-
broken
|
|
758
|
-
};
|
|
759
|
-
}
|
|
760
|
-
function syncAssemblyFile(content, kind, declared, force = false) {
|
|
761
|
-
const spec = KINDS[kind];
|
|
762
|
-
const warnings = [];
|
|
763
|
-
const desired = declared.filter(spec.contributes);
|
|
764
|
-
const desiredByAlias = new Map(desired.map((m) => [importAlias(kind, m.id), m]));
|
|
765
|
-
let next = content;
|
|
766
|
-
let changed = false;
|
|
767
|
-
const importsRange = extractBlock(next, "imports");
|
|
768
|
-
if (!importsRange) {
|
|
769
|
-
warnings.push(`[${kind}] imports \u951A\u533A\u7F3A\u5931/\u7834\u574F\uFF0C\u8DF3\u8FC7`);
|
|
770
|
-
return { content: next, changed, warnings, moduleIds: [] };
|
|
771
|
-
}
|
|
772
|
-
const importLines = importsRange.block.split(/\r?\n/);
|
|
773
|
-
const keptImports = [];
|
|
774
|
-
for (const line of importLines) {
|
|
775
|
-
const m = line.match(IMPORT_LINE_PATTERN);
|
|
776
|
-
if (!m) {
|
|
777
|
-
if (line.trim() === "") continue;
|
|
778
|
-
if (!force) warnings.push(`[${kind}] imports \u951A\u533A\u975E\u6807\u884C\u4FDD\u7559: ${line.trim()}`);
|
|
779
|
-
if (!force) keptImports.push(line);
|
|
780
|
-
continue;
|
|
781
|
-
}
|
|
782
|
-
const localName = m[2];
|
|
783
|
-
const pkg = m[3];
|
|
784
|
-
const target = desiredByAlias.get(localName);
|
|
785
|
-
if (target && idFromPkg(target.pkg) === idFromPkg(pkg)) {
|
|
786
|
-
keptImports.push(line);
|
|
787
|
-
} else if (target) {
|
|
788
|
-
keptImports.push(specImportLine(kind, localName, target.pkg));
|
|
789
|
-
changed = true;
|
|
790
|
-
} else {
|
|
791
|
-
changed = true;
|
|
792
|
-
}
|
|
793
|
-
}
|
|
794
|
-
for (const m of desired) {
|
|
795
|
-
const localName = importAlias(kind, m.id);
|
|
796
|
-
if (!keptImports.some((l) => l.includes(`as ${localName} } from`))) {
|
|
797
|
-
keptImports.push(specImportLine(kind, localName, m.pkg));
|
|
798
|
-
changed = true;
|
|
799
|
-
}
|
|
800
|
-
}
|
|
801
|
-
const newImportsBlock = normalizeBlock(keptImports.join("\n"));
|
|
802
|
-
if (newImportsBlock !== importsRange.block) {
|
|
803
|
-
next = replaceBlock(next, importsRange, newImportsBlock);
|
|
804
|
-
changed = true;
|
|
805
|
-
}
|
|
806
|
-
const mapRange = extractBlock(next, spec.mapMarker);
|
|
807
|
-
if (!mapRange) {
|
|
808
|
-
warnings.push(`[${kind}] ${spec.mapMarker} \u951A\u533A\u7F3A\u5931/\u7834\u574F\uFF0C\u8DF3\u8FC7`);
|
|
809
|
-
return { content: next, changed, warnings, moduleIds: [] };
|
|
810
|
-
}
|
|
811
|
-
const mapLines = mapRange.block.split(/\r?\n/);
|
|
812
|
-
const headIdx = mapLines.findIndex((l) => l.trim().startsWith(spec.header));
|
|
813
|
-
const reverseTailIdx = [...mapLines].reverse().findIndex((l) => l.trim() === spec.footer);
|
|
814
|
-
const tailIdx = reverseTailIdx === -1 ? -1 : mapLines.length - 1 - reverseTailIdx;
|
|
815
|
-
if (headIdx === -1 || tailIdx <= headIdx) {
|
|
816
|
-
if (force) {
|
|
817
|
-
const forcedBlock = normalizeBlock([spec.header, ...desired.map((m) => specEntryLine(kind, importAlias(kind, m.id))), spec.footer].join("\n"));
|
|
818
|
-
if (forcedBlock !== mapRange.block) {
|
|
819
|
-
next = replaceBlock(next, mapRange, forcedBlock);
|
|
820
|
-
changed = true;
|
|
821
|
-
}
|
|
822
|
-
return { content: next, changed, warnings, moduleIds: desired.map((m) => m.id) };
|
|
823
|
-
}
|
|
824
|
-
warnings.push(`[${kind}] ${spec.mapMarker} \u951A\u533A\u7ED3\u6784\u4E0D\u7B26\u5408\u9884\u671F\uFF0C\u964D\u7EA7\u4E3A\u544A\u8B66\u4E0D\u8986\u76D6`);
|
|
825
|
-
return { content: next, changed, warnings, moduleIds: [] };
|
|
826
|
-
}
|
|
827
|
-
const preservedHead = mapLines.slice(0, headIdx);
|
|
828
|
-
const preservedTail = mapLines.slice(tailIdx + 1);
|
|
829
|
-
const body = mapLines.slice(headIdx + 1, tailIdx);
|
|
830
|
-
const keptEntries = [];
|
|
831
|
-
for (const line of body) {
|
|
832
|
-
const m = line.match(spec.entryPattern);
|
|
833
|
-
if (!m) {
|
|
834
|
-
if (line.trim() === "") continue;
|
|
835
|
-
if (!force) warnings.push(`[${kind}] ${spec.mapMarker} \u951A\u533A\u975E\u6807\u884C\u4FDD\u7559: ${line.trim()}`);
|
|
836
|
-
if (!force) keptEntries.push(line);
|
|
837
|
-
continue;
|
|
838
|
-
}
|
|
839
|
-
const localName = m[1];
|
|
840
|
-
if (desiredByAlias.has(localName)) {
|
|
841
|
-
keptEntries.push(specEntryLine(kind, localName));
|
|
842
|
-
} else {
|
|
843
|
-
changed = true;
|
|
844
|
-
}
|
|
845
|
-
}
|
|
846
|
-
for (const m of desired) {
|
|
847
|
-
const localName = importAlias(kind, m.id);
|
|
848
|
-
if (!keptEntries.some((l) => l.includes(localName))) {
|
|
849
|
-
keptEntries.push(specEntryLine(kind, localName));
|
|
850
|
-
changed = true;
|
|
851
|
-
}
|
|
852
|
-
}
|
|
853
|
-
const newMapBlock = normalizeBlock(
|
|
854
|
-
[...preservedHead, spec.header, ...keptEntries, spec.footer, ...preservedTail].join("\n")
|
|
855
|
-
);
|
|
856
|
-
if (newMapBlock !== mapRange.block) {
|
|
857
|
-
next = replaceBlock(next, mapRange, newMapBlock);
|
|
858
|
-
changed = true;
|
|
859
|
-
}
|
|
860
|
-
return { content: next, changed, warnings, moduleIds: desired.map((m) => m.id) };
|
|
861
|
-
}
|
|
862
|
-
function normalizeBlock(block) {
|
|
863
|
-
const lines = block.split(/\r?\n/);
|
|
864
|
-
while (lines.length > 0 && lines[0].trim() === "") lines.shift();
|
|
865
|
-
while (lines.length > 0 && lines[lines.length - 1].trim() === "") lines.pop();
|
|
866
|
-
return "\n" + lines.join("\n") + "\n";
|
|
867
|
-
}
|
|
868
|
-
function specImportLine(kind, localName, pkg) {
|
|
869
|
-
const spec = KINDS[kind];
|
|
870
|
-
return `import { ${spec.importExport} as ${localName} } from '${pkg}/${spec.importSuffix}';`;
|
|
871
|
-
}
|
|
872
|
-
function specEntryLine(kind, localName) {
|
|
873
|
-
return ` ${localName},`;
|
|
874
|
-
}
|
|
875
|
-
|
|
876
|
-
// src/declared.ts
|
|
877
|
-
import { existsSync as existsSync5 } from "fs";
|
|
878
|
-
import { readFileSync as readFileSync4 } from "fs";
|
|
879
|
-
import { readdir as readdir2 } from "fs/promises";
|
|
880
|
-
import path7 from "path";
|
|
881
|
-
|
|
882
|
-
// src/component-schema.ts
|
|
883
|
-
import { z } from "zod";
|
|
884
|
-
var cardContribSchema = z.object({
|
|
885
|
-
cardType: z.string(),
|
|
886
|
-
schemaVersion: z.number().optional()
|
|
887
|
-
});
|
|
888
|
-
var pageContribSchema = z.object({
|
|
889
|
-
route: z.string(),
|
|
890
|
-
component: z.string()
|
|
891
|
-
});
|
|
892
|
-
var tabContribSchema = z.object({
|
|
893
|
-
route: z.string(),
|
|
894
|
-
label: z.string(),
|
|
895
|
-
icon: z.string().optional(),
|
|
896
|
-
order: z.number().optional()
|
|
897
|
-
});
|
|
898
|
-
var moduleDependencySchema = z.object({
|
|
899
|
-
id: z.string(),
|
|
900
|
-
range: z.string().optional(),
|
|
901
|
-
required: z.boolean().default(true)
|
|
902
|
-
});
|
|
903
|
-
var componentDescriptorSchema = z.object({
|
|
904
|
-
schemaVersion: z.number().default(1),
|
|
905
|
-
name: z.string(),
|
|
906
|
-
version: z.string().default("0.0.0"),
|
|
907
|
-
kind: z.enum(["platform", "business", "third-party"]).default("business"),
|
|
908
|
-
risk: z.object({
|
|
909
|
-
level: z.enum(["high", "medium", "low"]).default("low"),
|
|
910
|
-
writeBoundary: z.enum(["sdk-enforced", "source"]).default("source")
|
|
911
|
-
}).default({}),
|
|
912
|
-
distribution: z.object({
|
|
913
|
-
defaultMode: z.enum(["hybrid", "sdk", "codegen", "local"]).default("codegen")
|
|
914
|
-
}).default({}),
|
|
915
|
-
contributes: z.object({
|
|
916
|
-
handlers: z.array(z.string()).default([]),
|
|
917
|
-
tools: z.array(z.string()).default([]),
|
|
918
|
-
cards: z.array(cardContribSchema).default([]),
|
|
919
|
-
routes: z.array(z.string()).default([]),
|
|
920
|
-
pages: z.array(pageContribSchema).default([]),
|
|
921
|
-
tabs: z.array(tabContribSchema).default([])
|
|
922
|
-
}).default({}),
|
|
923
|
-
dependencies: z.object({
|
|
924
|
-
modules: z.array(moduleDependencySchema).default([])
|
|
925
|
-
}).default({}),
|
|
926
|
-
env: z.array(z.string()).default([])
|
|
927
|
-
}).strict();
|
|
928
|
-
function parseComponentDescriptor(raw) {
|
|
929
|
-
const result = componentDescriptorSchema.safeParse(raw);
|
|
930
|
-
if (result.success) return { ok: true, value: result.data };
|
|
931
|
-
return {
|
|
932
|
-
ok: false,
|
|
933
|
-
errors: result.error.issues.map(
|
|
934
|
-
(issue) => `${issue.path.join(".") || "(root)"}: ${issue.message}`
|
|
935
|
-
)
|
|
936
|
-
};
|
|
937
|
-
}
|
|
938
|
-
|
|
939
|
-
// src/declared.ts
|
|
940
|
-
function readComponentFile(dir) {
|
|
941
|
-
const file = path7.join(dir, "tbox.component.json");
|
|
942
|
-
if (!existsSync5(file)) return null;
|
|
943
|
-
try {
|
|
944
|
-
const parsed = parseComponentDescriptor(JSON.parse(readFileSync4(file, "utf8")));
|
|
945
|
-
return parsed.ok ? parsed.value : null;
|
|
946
|
-
} catch {
|
|
947
|
-
return null;
|
|
948
|
-
}
|
|
949
|
-
}
|
|
950
|
-
function probeModuleObjectExport(dir, sub, symbol) {
|
|
951
|
-
const file = path7.join(dir, "src", sub, "index.ts");
|
|
952
|
-
if (!existsSync5(file)) return false;
|
|
953
|
-
try {
|
|
954
|
-
const src = readFileSync4(file, "utf8");
|
|
955
|
-
return new RegExp(`export\\s+const\\s+${symbol}\\b`).test(src);
|
|
956
|
-
} catch {
|
|
957
|
-
return false;
|
|
958
|
-
}
|
|
959
|
-
}
|
|
960
|
-
function readServerSource(dir) {
|
|
961
|
-
const file = path7.join(dir, "src", "server", "index.ts");
|
|
962
|
-
if (!existsSync5(file)) return "";
|
|
963
|
-
try {
|
|
964
|
-
return readFileSync4(file, "utf8");
|
|
965
|
-
} catch {
|
|
966
|
-
return "";
|
|
967
|
-
}
|
|
968
|
-
}
|
|
969
|
-
function extractComponentId(source) {
|
|
970
|
-
const match = source.match(/declaration\s*:\s*\{[\s\S]*?componentId\s*:\s*['"]([^'"]+)['"]/);
|
|
971
|
-
return match?.[1];
|
|
972
|
-
}
|
|
973
|
-
async function listLocalModules(appDir) {
|
|
974
|
-
const packagesDir = path7.join(appDir, "packages");
|
|
975
|
-
const result = [];
|
|
976
|
-
if (!existsSync5(packagesDir)) return result;
|
|
977
|
-
const entries = await readdir2(packagesDir, { withFileTypes: true });
|
|
978
|
-
for (const entry of entries) {
|
|
979
|
-
if (!entry.isDirectory()) continue;
|
|
980
|
-
const dir = path7.join(packagesDir, entry.name);
|
|
981
|
-
if (!existsSync5(path7.join(dir, "package.json"))) continue;
|
|
982
|
-
const descriptor = readComponentFile(dir);
|
|
983
|
-
if (descriptor) {
|
|
984
|
-
result.push({ id: descriptor.name ?? entry.name, dir, descriptor });
|
|
985
|
-
}
|
|
986
|
-
}
|
|
987
|
-
return result.sort((a, b) => a.id.localeCompare(b.id));
|
|
988
|
-
}
|
|
989
|
-
function readDescriptorFromNodeModules(appDir, pkg) {
|
|
990
|
-
try {
|
|
991
|
-
const p = path7.join(appDir, "node_modules", ...pkg.split("/"), "tbox.component.json");
|
|
992
|
-
if (!existsSync5(p)) return null;
|
|
993
|
-
const parsed = parseComponentDescriptor(JSON.parse(readFileSync4(p, "utf8")));
|
|
994
|
-
return parsed.ok ? parsed.value : null;
|
|
995
|
-
} catch {
|
|
996
|
-
return null;
|
|
997
|
-
}
|
|
998
|
-
}
|
|
999
|
-
async function collectDeclared(appDir) {
|
|
1000
|
-
const manifest = readAppManifest(appDir);
|
|
1001
|
-
const declared = [];
|
|
1002
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1003
|
-
for (const entry of manifest?.npmModules ?? []) {
|
|
1004
|
-
seen.add(entry.id);
|
|
1005
|
-
const local = path7.join(appDir, "packages", entry.id);
|
|
1006
|
-
const descriptor = readComponentFile(local) ?? readDescriptorFromNodeModules(appDir, entry.package);
|
|
1007
|
-
const probeDir = existsSync5(path7.join(local, "src")) ? local : path7.join(appDir, "node_modules", ...entry.package.split("/"));
|
|
1008
|
-
const c = descriptor?.contributes;
|
|
1009
|
-
const hasServerEntry = descriptor !== null && probeModuleObjectExport(probeDir, "server", "serverModule");
|
|
1010
|
-
declared.push({
|
|
1011
|
-
id: entry.id,
|
|
1012
|
-
pkg: entry.mode === "sdk" ? entry.package : `@app/${entry.id}`,
|
|
1013
|
-
mode: entry.mode,
|
|
1014
|
-
hasServerEntry,
|
|
1015
|
-
hasClientEntry: descriptor !== null && probeModuleObjectExport(probeDir, "client", "clientModule"),
|
|
1016
|
-
hasCards: (c?.cards?.length ?? 0) > 0,
|
|
1017
|
-
// 011 评审 M3:declaration.componentId(deployment 对账真源;老模块无 declaration → undefined)
|
|
1018
|
-
...hasServerEntry ? { componentId: extractComponentId(readServerSource(probeDir)) } : {}
|
|
1019
|
-
});
|
|
1020
|
-
}
|
|
1021
|
-
for (const mod of await listLocalModules(appDir)) {
|
|
1022
|
-
if (seen.has(mod.id)) continue;
|
|
1023
|
-
const c = mod.descriptor.contributes;
|
|
1024
|
-
const hasServerEntry = probeModuleObjectExport(mod.dir, "server", "serverModule");
|
|
1025
|
-
declared.push({
|
|
1026
|
-
id: mod.id,
|
|
1027
|
-
pkg: `@app/${mod.id}`,
|
|
1028
|
-
mode: "local",
|
|
1029
|
-
hasServerEntry,
|
|
1030
|
-
hasClientEntry: probeModuleObjectExport(mod.dir, "client", "clientModule"),
|
|
1031
|
-
hasCards: (c.cards?.length ?? 0) > 0,
|
|
1032
|
-
// 011 评审 M3:declaration.componentId(同 npmModules 路径)
|
|
1033
|
-
...hasServerEntry ? { componentId: extractComponentId(readServerSource(mod.dir)) } : {}
|
|
1034
|
-
});
|
|
1035
|
-
}
|
|
1036
|
-
return declared;
|
|
1037
|
-
}
|
|
1038
|
-
|
|
1039
|
-
// src/deps-merge.ts
|
|
1040
|
-
import { writeFile as writeFile5 } from "fs/promises";
|
|
1041
|
-
import path8 from "path";
|
|
1042
|
-
import { readFileSync as readFileSync5 } from "fs";
|
|
1043
|
-
function readJsonOrNull(p) {
|
|
1044
|
-
try {
|
|
1045
|
-
return JSON.parse(readFileSyncSafe(p) ?? "{}");
|
|
1046
|
-
} catch {
|
|
1047
|
-
return null;
|
|
1048
|
-
}
|
|
1049
|
-
}
|
|
1050
|
-
async function mergeModuleDeps(appDir, targetPackageJson, moduleDeps) {
|
|
1051
|
-
const file = path8.join(appDir, targetPackageJson);
|
|
1052
|
-
const pkg = readJsonOrNull(file) ?? {};
|
|
1053
|
-
const deps = pkg.dependencies ?? {};
|
|
1054
|
-
const merged = {};
|
|
1055
|
-
const conflicts = [];
|
|
1056
|
-
for (const [name, spec] of Object.entries(moduleDeps)) {
|
|
1057
|
-
if (isPlatform(name)) continue;
|
|
1058
|
-
if (spec === "workspace:*" || spec.startsWith("catalog:")) continue;
|
|
1059
|
-
const existing = deps[name];
|
|
1060
|
-
if (!existing) {
|
|
1061
|
-
deps[name] = spec;
|
|
1062
|
-
merged[name] = spec;
|
|
1063
|
-
continue;
|
|
1064
|
-
}
|
|
1065
|
-
if (existing === spec) continue;
|
|
1066
|
-
const inter = intersectRanges(existing, spec);
|
|
1067
|
-
if (inter) {
|
|
1068
|
-
if (inter !== existing) {
|
|
1069
|
-
deps[name] = inter;
|
|
1070
|
-
merged[name] = inter;
|
|
1071
|
-
}
|
|
1072
|
-
} else {
|
|
1073
|
-
conflicts.push(`${name}: \u5DF2\u6709 ${existing}\uFF0C\u6A21\u5757\u8981\u6C42 ${spec}`);
|
|
1074
|
-
}
|
|
1075
|
-
}
|
|
1076
|
-
pkg.dependencies = deps;
|
|
1077
|
-
await writeFile5(file, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
1078
|
-
return { merged, conflicts };
|
|
1079
|
-
}
|
|
1080
|
-
function intersectRanges(a, b) {
|
|
1081
|
-
const clean = (s) => s.replace(/^\^/, "");
|
|
1082
|
-
const pa = parseVer(clean(a));
|
|
1083
|
-
const pb = parseVer(clean(b));
|
|
1084
|
-
if (!pa || !pb) return null;
|
|
1085
|
-
if (pa.major !== pb.major) return null;
|
|
1086
|
-
if (pa.minor > pb.minor) return a;
|
|
1087
|
-
if (pb.minor > pa.minor) return b;
|
|
1088
|
-
return pa.patch > pb.patch ? a : b;
|
|
1089
|
-
}
|
|
1090
|
-
function parseVer(v) {
|
|
1091
|
-
const m = v.match(/^(\d+)\.(\d+)(?:\.(\d+))?/);
|
|
1092
|
-
if (!m) return null;
|
|
1093
|
-
return { major: Number(m[1]), minor: Number(m[2]), patch: Number(m[3] ?? 0) };
|
|
1094
|
-
}
|
|
1095
|
-
function readFileSyncSafe(p) {
|
|
1096
|
-
try {
|
|
1097
|
-
return readFileSync5(p, "utf8");
|
|
1098
|
-
} catch {
|
|
1099
|
-
return void 0;
|
|
1100
|
-
}
|
|
1101
|
-
}
|
|
1102
|
-
|
|
1103
|
-
// src/sync-all.ts
|
|
1104
|
-
async function syncAll(appDir, opts = {}) {
|
|
1105
|
-
const declared = await collectDeclared(appDir);
|
|
1106
|
-
const warnings = [];
|
|
1107
|
-
const changedFiles = [];
|
|
1108
|
-
const depConflicts = [];
|
|
1109
|
-
for (const file of ASSEMBLY_FILES) {
|
|
1110
|
-
const full = path9.join(appDir, file.path);
|
|
1111
|
-
if (!existsSync6(full)) {
|
|
1112
|
-
warnings.push(`\u7F3A\u5C11\u88C5\u914D\u6587\u4EF6 ${file.path}`);
|
|
1113
|
-
continue;
|
|
1114
|
-
}
|
|
1115
|
-
const content = await readFile3(full, "utf8");
|
|
1116
|
-
const result = syncAssemblyFile(content, file.kind, declared, opts.force ?? false);
|
|
1117
|
-
warnings.push(...result.warnings.map((w) => `${file.path}: ${w}`));
|
|
1118
|
-
if (result.changed) {
|
|
1119
|
-
await writeFile6(full, result.content, "utf8");
|
|
1120
|
-
changedFiles.push(file.path);
|
|
1121
|
-
}
|
|
1122
|
-
}
|
|
1123
|
-
const targets = ["apps/server/package.json", "apps/client/package.json"];
|
|
1124
|
-
for (const mod of declared) {
|
|
1125
|
-
const pkgFile = path9.join(appDir, "packages", mod.id, "package.json");
|
|
1126
|
-
if (!existsSync6(pkgFile)) continue;
|
|
1127
|
-
let deps = {};
|
|
1128
|
-
try {
|
|
1129
|
-
deps = JSON.parse(readFileSync6(pkgFile, "utf8")).dependencies ?? {};
|
|
1130
|
-
} catch {
|
|
1131
|
-
continue;
|
|
1132
|
-
}
|
|
1133
|
-
for (const target of targets) {
|
|
1134
|
-
if (!existsSync6(path9.join(appDir, target))) continue;
|
|
1135
|
-
const result = await mergeModuleDeps(appDir, target, deps);
|
|
1136
|
-
depConflicts.push(...result.conflicts.map((c) => `${mod.id} \u2192 ${target}: ${c}`));
|
|
1137
|
-
}
|
|
1138
|
-
}
|
|
1139
|
-
return { declared, changedFiles, warnings, depConflicts };
|
|
1140
|
-
}
|
|
1141
|
-
|
|
1142
|
-
// src/diff.ts
|
|
1143
|
-
import { existsSync as existsSync7 } from "fs";
|
|
1144
|
-
import { readFile as readFile4 } from "fs/promises";
|
|
1145
|
-
import path10 from "path";
|
|
1146
|
-
async function collectActual(appDir) {
|
|
1147
|
-
const result = {};
|
|
1148
|
-
for (const file of ASSEMBLY_FILES) {
|
|
1149
|
-
const full = path10.join(appDir, file.path);
|
|
1150
|
-
if (!existsSync7(full)) {
|
|
1151
|
-
result[file.kind] = { moduleIds: [], nonStandardLines: [], broken: ["missing-file"] };
|
|
1152
|
-
continue;
|
|
1153
|
-
}
|
|
1154
|
-
const content = await readFile4(full, "utf8");
|
|
1155
|
-
const parsed = parseAssemblyFile(content, file.kind);
|
|
1156
|
-
result[file.kind] = {
|
|
1157
|
-
moduleIds: parsed.moduleIds,
|
|
1158
|
-
nonStandardLines: parsed.nonStandardLines,
|
|
1159
|
-
broken: parsed.broken
|
|
1160
|
-
};
|
|
1161
|
-
}
|
|
1162
|
-
return result;
|
|
1163
|
-
}
|
|
1164
|
-
|
|
1165
|
-
// src/doctor/rules/workspace-dag.ts
|
|
1166
|
-
import { existsSync as existsSync8 } from "fs";
|
|
1167
|
-
import { readdir as readdir4 } from "fs/promises";
|
|
1168
|
-
import path12 from "path";
|
|
1169
|
-
|
|
1170
|
-
// src/doctor/util.ts
|
|
1171
|
-
import { readFileSync as readFileSync7 } from "fs";
|
|
1172
|
-
import { readFile as readFile5, readdir as readdir3 } from "fs/promises";
|
|
1173
|
-
import path11 from "path";
|
|
1174
|
-
async function walkFiles(dir, base, out) {
|
|
1175
|
-
const entries = await readdir3(dir, { withFileTypes: true });
|
|
1176
|
-
for (const entry of entries) {
|
|
1177
|
-
if (entry.name === "node_modules" || entry.name === "dist" || entry.name === ".git") continue;
|
|
1178
|
-
const full = path11.join(dir, entry.name);
|
|
1179
|
-
if (entry.isDirectory()) await walkFiles(full, base, out);
|
|
1180
|
-
else if (entry.isFile() && /\.(ts|tsx|mjs|js)$/.test(entry.name)) {
|
|
1181
|
-
out.push(path11.relative(base, full).split(path11.sep).join("/"));
|
|
1182
|
-
}
|
|
1183
|
-
}
|
|
1184
|
-
}
|
|
1185
|
-
async function readTextOrEmpty(p) {
|
|
1186
|
-
try {
|
|
1187
|
-
return await readFile5(p, "utf8");
|
|
1188
|
-
} catch {
|
|
1189
|
-
return "";
|
|
1190
|
-
}
|
|
1191
|
-
}
|
|
1192
|
-
function readJsonOrNull2(p) {
|
|
1193
|
-
try {
|
|
1194
|
-
return JSON.parse(readFileSync7(p, "utf8"));
|
|
1195
|
-
} catch {
|
|
1196
|
-
return null;
|
|
1197
|
-
}
|
|
1198
|
-
}
|
|
1199
|
-
function readPkgJson(dir) {
|
|
1200
|
-
return readJsonOrNull2(path11.join(dir, "package.json"));
|
|
1201
|
-
}
|
|
1202
|
-
function intersectRanges2(a, b) {
|
|
1203
|
-
const clean = (s) => s.replace(/^\^/, "");
|
|
1204
|
-
const pa = parseVer2(clean(a));
|
|
1205
|
-
const pb = parseVer2(clean(b));
|
|
1206
|
-
if (!pa || !pb) return null;
|
|
1207
|
-
if (pa.major !== pb.major) return null;
|
|
1208
|
-
if (pa.minor > pb.minor) return a;
|
|
1209
|
-
if (pb.minor > pa.minor) return b;
|
|
1210
|
-
return pa.patch > pb.patch ? a : b;
|
|
1211
|
-
}
|
|
1212
|
-
function parseVer2(v) {
|
|
1213
|
-
const m = v.match(/^(\d+)\.(\d+)(?:\.(\d+))?/);
|
|
1214
|
-
if (!m) return null;
|
|
1215
|
-
return { major: Number(m[1]), minor: Number(m[2]), patch: Number(m[3] ?? 0) };
|
|
1216
|
-
}
|
|
1217
|
-
|
|
1218
|
-
// src/doctor/rules/workspace-dag.ts
|
|
1219
|
-
async function workspaceDagRule(ctx) {
|
|
1220
|
-
const issues = [];
|
|
1221
|
-
const packagesDir = path12.join(ctx.appDir, "packages");
|
|
1222
|
-
if (!existsSync8(packagesDir)) return issues;
|
|
1223
|
-
for (const entry of await readdir4(packagesDir, { withFileTypes: true })) {
|
|
1224
|
-
if (!entry.isDirectory()) continue;
|
|
1225
|
-
const dir = path12.join(packagesDir, entry.name);
|
|
1226
|
-
if (!existsSync8(path12.join(dir, "tbox.component.json"))) continue;
|
|
1227
|
-
const pkg = readPkgJson(dir);
|
|
1228
|
-
const allDeps = { ...pkg?.dependencies ?? {}, ...pkg?.peerDependencies ?? {} };
|
|
1229
|
-
for (const name of Object.keys(allDeps)) {
|
|
1230
|
-
if (/^@app\/(module|scenario)-/.test(name) || /^@tbox\.cn\/app-(module|scenario)-/.test(name)) {
|
|
1231
|
-
issues.push({
|
|
1232
|
-
rule: "workspace-dag",
|
|
1233
|
-
level: "error",
|
|
1234
|
-
message: `${entry.name} \u4F9D\u8D56\u4E1A\u52A1\u6A21\u5757 ${name}\uFF08module \u4E0D\u5F97\u4F9D\u8D56 module/scenario\uFF09`
|
|
1235
|
-
});
|
|
1236
|
-
}
|
|
1237
|
-
}
|
|
1238
|
-
}
|
|
1239
|
-
return issues;
|
|
1240
|
-
}
|
|
1241
|
-
|
|
1242
|
-
// src/doctor/rules/register-pure.ts
|
|
1243
|
-
import { existsSync as existsSync9 } from "fs";
|
|
1244
|
-
import { readdir as readdir5 } from "fs/promises";
|
|
1245
|
-
import path13 from "path";
|
|
1246
|
-
|
|
1247
|
-
// src/doctor/ast.ts
|
|
1248
|
-
import ts from "typescript";
|
|
1249
|
-
function parseSource(src) {
|
|
1250
|
-
return ts.createSourceFile("entry.ts", src, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
|
|
1251
|
-
}
|
|
1252
|
-
function unwrapAs(expr) {
|
|
1253
|
-
if (!expr) return expr;
|
|
1254
|
-
if (ts.isAsExpression(expr) || ts.isSatisfiesExpression(expr)) return expr.expression;
|
|
1255
|
-
return expr;
|
|
1256
|
-
}
|
|
1257
|
-
function findFunctionBody(src, name) {
|
|
1258
|
-
const sf = parseSource(src);
|
|
1259
|
-
let result = null;
|
|
1260
|
-
function visit(node) {
|
|
1261
|
-
if (result) return;
|
|
1262
|
-
if (ts.isFunctionDeclaration(node) && node.name?.text === name && node.body) {
|
|
1263
|
-
result = node.body;
|
|
1264
|
-
return;
|
|
1265
|
-
}
|
|
1266
|
-
if (ts.isVariableStatement(node) && node.declarationList.declarations.some(
|
|
1267
|
-
(d) => ts.isIdentifier(d.name) && d.name.text === name
|
|
1268
|
-
)) {
|
|
1269
|
-
const decl = node.declarationList.declarations.find(
|
|
1270
|
-
(d) => ts.isIdentifier(d.name) && d.name.text === name
|
|
1271
|
-
);
|
|
1272
|
-
const init = unwrapAs(decl.initializer);
|
|
1273
|
-
if (init && ts.isArrowFunction(init) && init.body && ts.isBlock(init.body)) {
|
|
1274
|
-
result = init.body;
|
|
1275
|
-
return;
|
|
1276
|
-
}
|
|
1277
|
-
const methodBody = objectMethodBody(init, "register");
|
|
1278
|
-
if (methodBody) {
|
|
1279
|
-
result = methodBody;
|
|
1280
|
-
return;
|
|
1281
|
-
}
|
|
1282
|
-
}
|
|
1283
|
-
ts.forEachChild(node, visit);
|
|
1284
|
-
}
|
|
1285
|
-
visit(sf);
|
|
1286
|
-
return result;
|
|
1287
|
-
}
|
|
1288
|
-
function objectMethodBody(init, methodName) {
|
|
1289
|
-
if (!init || !ts.isObjectLiteralExpression(init)) return null;
|
|
1290
|
-
for (const prop of init.properties) {
|
|
1291
|
-
if (ts.isMethodDeclaration(prop) && prop.name.getText() === methodName && prop.body) {
|
|
1292
|
-
return prop.body;
|
|
1293
|
-
}
|
|
1294
|
-
if (ts.isPropertyAssignment(prop) && prop.name.getText() === methodName && prop.initializer) {
|
|
1295
|
-
const fn = prop.initializer;
|
|
1296
|
-
if (ts.isArrowFunction(fn) && fn.body && ts.isBlock(fn.body)) return fn.body;
|
|
1297
|
-
if (ts.isFunctionExpression(fn) && fn.body) return fn.body;
|
|
1298
|
-
}
|
|
79
|
+
`;async function jn(e,t){let r=t.startsWith("module-")?t:`module-${t}`,n=W.join(e,"packages",r);await xn(n,{recursive:!0,force:!0}),await kt(W.join(n,"src/server"),{recursive:!0}),await kt(W.join(n,"src/client"),{recursive:!0});let s={name:`@app/${r}`,version:"0.1.0",private:!0,type:"module",exports:{".":{types:"./src/index.ts",import:"./src/index.ts"},"./server":{types:"./src/server/index.ts",import:"./src/server/index.ts"},"./client":{types:"./src/client/index.ts",import:"./src/client/index.ts"},"./package.json":"./package.json"},scripts:{typecheck:"tsc --noEmit",test:"vitest run --passWithNoTests"},dependencies:{"@tbox.cn/app-sdk":"catalog:","@tbox.cn/app-contracts":"catalog:"},peerDependencies:{zod:"catalog:",react:"catalog:","@mastra/core":"catalog:"},devDependencies:{typescript:"catalog:",vitest:"catalog:","@types/react":"catalog:","@types/node":"catalog:"}},i={schemaVersion:1,name:r,version:"0.1.0",kind:"business",risk:{level:"low",writeBoundary:"source"},distribution:{defaultMode:"local"},contributes:{handlers:[],tools:[],cards:[],routes:[],pages:[],tabs:[],resources:[]},dependencies:{modules:[]},env:[]};return await ne(W.join(n,"package.json"),JSON.stringify(s,null,2)+`
|
|
80
|
+
`,"utf8"),await ne(W.join(n,"tbox.module.json"),JSON.stringify(i,null,2)+`
|
|
81
|
+
`,"utf8"),await ne(W.join(n,"tsconfig.json"),sa,"utf8"),await ne(W.join(n,"src/server/index.ts"),Qi(r),"utf8"),await ne(W.join(n,"src/server/service.ts"),ea,"utf8"),await ne(W.join(n,"src/server/handler.ts"),ta,"utf8"),await ne(W.join(n,"src/client/index.ts"),ra,"utf8"),await ne(W.join(n,"src/index.ts"),na,"utf8"),r}import{existsSync as En}from"fs";import{readFileSync as oa}from"fs";import{writeFile as ia}from"fs/promises";import Rn from"path";var aa=["apps/server/package.json","apps/client/package.json"];async function We(e,t,r){let n=!1;for(let s of aa){let i=Rn.join(e,s);if(!En(i))continue;let o=JSON.parse(oa(i,"utf8")),a=o.dependencies??={};r==="add"&&!a[t]?(a[t]="workspace:*",n=!0):r==="remove"&&a[t]&&(delete a[t],n=!0),n&&await ia(i,JSON.stringify(o,null,2)+`
|
|
82
|
+
`,"utf8")}return n}async function Dn(e,t){let r=Rn.join(e,"packages",t);if(!En(r))return!1;let{rm:n}=await import("fs/promises");return await n(r,{recursive:!0,force:!0}),!0}import{existsSync as ar,readFileSync as ma}from"fs";import{readFile as ga,writeFile as ya}from"fs/promises";import cr from"path";var se=[{path:"apps/server/src/modules.ts",kind:"server-modules"},{path:"apps/client/src/modules.ts",kind:"client-modules"}];function ca(e){return e.split(/[^A-Za-z0-9]+/).filter(Boolean).map((t,r)=>r===0?t.toLowerCase():t.charAt(0).toUpperCase()+t.slice(1).toLowerCase()).join("")}function xt(e,t){return ca(t)}function jt(e,t){return Et[e].contributes(t)}function St(e){return e.replace(/^@[^/]+\//,"").replace(/\/(server|client)$/,"").replace(/\/$/,"")}var Et={"server-modules":{mapMarker:"register",importExport:"serverModule",importSuffix:"server",entryPattern:/^\s*([A-Za-z_$][\w$]*),\s*$/,header:"const modules = [",footer:"];",contributes:e=>e.hasServerEntry},"client-modules":{mapMarker:"register",importExport:"clientModule",importSuffix:"client",entryPattern:/^\s*([A-Za-z_$][\w$]*),\s*$/,header:"const modules = [",footer:"];",contributes:e=>e.hasClientEntry&&e.hasCards}},or=/^import\s*\{\s*([\w$]+)\s+as\s+([\w$]+)\s*\}\s*from\s*['"]([^'"]+)['"];\s*$/,ir=/^\]\s*(?:as\s+const)?;\s*$/;function $t(e,t){let r=`// @tbox:${t}-begin`,n=`// @tbox:${t}-end`,s=e.indexOf(r);if(s===-1)return null;let i=e.indexOf(n,s);if(i===-1)return null;let o=s+r.length;return{block:e.slice(o,i),start:o,end:i}}function rr(e,t,r){return e.slice(0,t.start)+r+e.slice(t.end)}function Tn(e,t){let r=Et[t],n=[],s=[],i=[],o=$t(e,"imports");if(o)for(let p of o.block.split(/\r?\n/)){let u=p.match(or);u?n.push(St(u[3])):p.trim()&&s.push(`imports: ${p.trim()}`)}else i.push("imports");let a=$t(e,r.mapMarker);if(a){let p=a.block.split(/\r?\n/),u=p.findIndex(g=>g.trim().startsWith(r.header)),d=[...p].reverse().findIndex(g=>ir.test(g.trim())),f=d===-1?-1:p.length-1-d;if(u===-1||f<=u)i.push(r.mapMarker);else for(let g of p.slice(u+1,f))!g.match(r.entryPattern)&&g.trim()&&s.push(`${r.mapMarker}: ${g.trim()}`)}else i.push(r.mapMarker);let c=new Map;if(o)for(let p of o.block.split(/\r?\n/)){let u=p.match(or);u&&c.set(u[2],St(u[3]))}let l=[];if(a){let p=a.block.split(/\r?\n/),u=p.findIndex(g=>g.trim().startsWith(r.header)),d=[...p].reverse().findIndex(g=>ir.test(g.trim())),f=d===-1?-1:p.length-1-d;if(u!==-1&&f>u)for(let g of p.slice(u+1,f)){let m=g.match(r.entryPattern);if(m){let h=c.get(m[1]);h&&l.push(h)}}}return{moduleIds:[...new Set([...n,...l])],nonStandardLines:s,broken:i}}function Rt(e,t,r,n=!1){let s=Et[t],i=[],o=r.filter(s.contributes),a=new Map(o.map(w=>[xt(t,w.id),w])),c=e,l=!1,p=$t(c,"imports");if(!p)return i.push(`[${t}] imports 锚区缺失/破坏,跳过`),{content:c,changed:l,warnings:i,moduleIds:[]};let u=p.block.split(/\r?\n/),d=[];for(let w of u){let D=w.match(or);if(!D){if(w.trim()==="")continue;n||i.push(`[${t}] imports 锚区非标行保留: ${w.trim()}`),n||d.push(w);continue}let M=D[2],b=D[3],k=a.get(M);k&&St(k.pkg)===St(b)?d.push(w):(k&&d.push(Cn(t,M,k.pkg)),l=!0)}for(let w of o){let D=xt(t,w.id);d.some(M=>M.includes(`as ${D} } from`))||(d.push(Cn(t,D,w.pkg)),l=!0)}let f=nr(d.join(`
|
|
83
|
+
`));f!==p.block&&(c=rr(c,p,f),l=!0);let g=$t(c,s.mapMarker);if(!g)return i.push(`[${t}] ${s.mapMarker} 锚区缺失/破坏,跳过`),{content:c,changed:l,warnings:i,moduleIds:[]};let m=g.block.split(/\r?\n/),h=m.findIndex(w=>w.trim().startsWith(s.header)),v=[...m].reverse().findIndex(w=>ir.test(w.trim())),S=v===-1?-1:m.length-1-v;if(h===-1||S<=h){if(n){let w=nr([s.header,...o.map(D=>sr(t,xt(t,D.id))),s.footer].join(`
|
|
84
|
+
`));return w!==g.block&&(c=rr(c,g,w),l=!0),{content:c,changed:l,warnings:i,moduleIds:o.map(D=>D.id)}}return i.push(`[${t}] ${s.mapMarker} 锚区结构不符合预期,降级为告警不覆盖`),{content:c,changed:l,warnings:i,moduleIds:[]}}let $=m.slice(0,h),R=m.slice(S+1),F=m.slice(h+1,S),A=[];for(let w of F){let D=w.match(s.entryPattern);if(!D){if(w.trim()==="")continue;n||i.push(`[${t}] ${s.mapMarker} 锚区非标行保留: ${w.trim()}`),n||A.push(w);continue}let M=D[1];a.has(M)?A.push(sr(t,M)):l=!0}for(let w of o){let D=xt(t,w.id);A.some(M=>M.includes(D))||(A.push(sr(t,D)),l=!0)}let N=m[S],P=nr([...$,s.header,...A,N,...R].join(`
|
|
85
|
+
`));return P!==g.block&&(c=rr(c,g,P),l=!0),{content:c,changed:l,warnings:i,moduleIds:o.map(w=>w.id)}}function nr(e){let t=e.split(/\r?\n/);for(;t.length>0&&t[0].trim()==="";)t.shift();for(;t.length>0&&t[t.length-1].trim()==="";)t.pop();return`
|
|
86
|
+
`+t.join(`
|
|
87
|
+
`)+`
|
|
88
|
+
`}function Cn(e,t,r){let n=Et[e];return`import { ${n.importExport} as ${t} } from '${r}/${n.importSuffix}';`}function sr(e,t){return` ${t},`}import{writeFile as la}from"fs/promises";import pa from"path";import{readFileSync as da}from"fs";function ua(e){try{return JSON.parse(fa(e)??"{}")}catch{return null}}async function An(e,t,r){let n=pa.join(e,t),s=ua(n)??{},i=s.dependencies??{},o={},a=[];for(let[c,l]of Object.entries(r)){if(ut(c)||l==="workspace:*"||l.startsWith("catalog:"))continue;let p=i[c];if(!p){i[c]=l,o[c]=l;continue}if(p===l)continue;if(p==="catalog:"){let d=ce(e)?.get(c);d&&!Mn(d,l)&&a.push(`${c}: 应用 catalog ${d} 与模块要求 ${l} 无交集`);continue}let u=Mn(p,l);u?u!==p&&(i[c]=u,o[c]=u):a.push(`${c}: 已有 ${p},模块要求 ${l}`)}return s.dependencies=i,await la(n,JSON.stringify(s,null,2)+`
|
|
89
|
+
`,"utf8"),{merged:o,conflicts:a}}function Mn(e,t){let r=In(e),n=In(t);if(!r||!n||r.lower.major!==n.lower.major)return null;let s=(a,c)=>a.major-c.major||a.minor-c.minor||a.patch-c.patch,i=s(r.lower,n.lower)>=0?r.lower:n.lower,o=s(r.upper,n.upper)<=0?r.upper:n.upper;return s(i,o)>=0?null:s(r.lower,n.lower)>0?e:s(n.lower,r.lower)>0?t:s(r.upper,n.upper)<=0?e:t}function In(e){let t=e.match(/^([\^~]?)(\d+)\.(\d+)(?:\.(\d+))?/);if(!t)return null;let r={major:Number(t[2]),minor:Number(t[3]),patch:Number(t[4]??0)},n;return t[1]==="~"?n={major:r.major,minor:r.minor+1,patch:0}:t[1]==="^"?n=r.major===0?{major:0,minor:r.minor+1,patch:0}:{major:r.major+1,minor:0,patch:0}:n={...r,patch:r.patch+1},{lower:r,upper:n}}function fa(e){try{return da(e,"utf8")}catch{return}}async function me(e,t={}){let r=await ue(e),n=[],s=[],i=[];for(let c of se){let l=cr.join(e,c.path);if(!ar(l)){n.push(`缺少装配文件 ${c.path}`);continue}let p=await ga(l,"utf8"),u=Rt(p,c.kind,r,t.force??!1);n.push(...u.warnings.map(d=>`${c.path}: ${d}`)),u.changed&&(await ya(l,u.content,"utf8"),s.push(c.path))}let o=["apps/server/package.json","apps/client/package.json"];for(let c of r){let l=cr.join(e,"packages",c.id,"package.json");if(!ar(l))continue;let p={};try{p=JSON.parse(ma(l,"utf8")).dependencies??{}}catch{continue}for(let u of o){if(!ar(cr.join(e,u)))continue;let d=await An(e,u,p);i.push(...d.conflicts.map(f=>`${c.id} → ${u}: ${f}`))}}let a=De(e);return{declared:r,changedFiles:s,warnings:n,depConflicts:i,lockfileRefreshed:a}}import{existsSync as ha}from"fs";import{readFile as va}from"fs/promises";import wa from"path";async function G(e){let t={};for(let r of se){let n=wa.join(e,r.path);if(!ha(n)){t[r.kind]={moduleIds:[],nonStandardLines:[],broken:["missing-file"]};continue}let s=await va(n,"utf8"),i=Tn(s,r.kind);t[r.kind]={moduleIds:i.moduleIds,nonStandardLines:i.nonStandardLines,broken:i.broken}}return t}import{existsSync as Pn}from"fs";import{readdir as $a}from"fs/promises";import lr from"path";import{readFileSync as ba}from"fs";import{readFile as ka,readdir as xa}from"fs/promises";import Dt from"path";async function $e(e,t,r){let n=await xa(e,{withFileTypes:!0});for(let s of n){if(s.name==="node_modules"||s.name==="dist"||s.name===".git")continue;let i=Dt.join(e,s.name);s.isDirectory()?await $e(i,t,r):s.isFile()&&/\.(ts|tsx|mjs|js)$/.test(s.name)&&r.push(Dt.relative(t,i).split(Dt.sep).join("/"))}}async function L(e){try{return await ka(e,"utf8")}catch{return""}}function Sa(e){try{return JSON.parse(ba(e,"utf8"))}catch{return null}}function Z(e){return Sa(Dt.join(e,"package.json"))}function Ct(e,t){let r=On(e),n=On(t);if(!r||!n||r.lower.major!==n.lower.major)return null;let s=(a,c)=>a.major-c.major||a.minor-c.minor||a.patch-c.patch,i=s(r.lower,n.lower)>=0?r.lower:n.lower,o=s(r.upper,n.upper)<=0?r.upper:n.upper;return s(i,o)>=0?null:s(r.lower,n.lower)>0?e:s(n.lower,r.lower)>0?t:s(r.upper,n.upper)<=0?e:t}function On(e){let t=e.match(/^([\^~]?)(\d+)\.(\d+)(?:\.(\d+))?/);if(!t)return null;let r={major:Number(t[2]),minor:Number(t[3]),patch:Number(t[4]??0)},n;return t[1]==="~"?n={major:r.major,minor:r.minor+1,patch:0}:t[1]==="^"?n=r.major===0?{major:0,minor:r.minor+1,patch:0}:{major:r.major+1,minor:0,patch:0}:n={...r,patch:r.patch+1},{lower:r,upper:n}}async function Ln(e){let t=[],r=lr.join(e.appDir,"packages");if(!Pn(r))return t;for(let n of await $a(r,{withFileTypes:!0})){if(!n.isDirectory())continue;let s=lr.join(r,n.name);if(!Pn(lr.join(s,"tbox.module.json")))continue;let i=/^provider-/.test(n.name),o=Z(s),a={...o?.dependencies??{},...o?.peerDependencies??{}};for(let c of Object.keys(a))(/^@app\/(module|scenario)-/.test(c)||/^@tbox\.cn\/app-(module|scenario)-/.test(c))&&t.push({rule:"workspace-dag",level:"error",message:`${n.name} 依赖业务模块 ${c}(module 不得依赖 module/scenario)`}),i&&(/^@app\/(module|scenario)-/.test(c)||/^@tbox\.cn\/app-(module|scenario)-/.test(c))&&t.push({rule:"workspace-dag",level:"error",message:`${n.name} 依赖业务模块 ${c}(provider 不得依赖 module/scenario——实现自持)`})}return t}import{existsSync as Jn}from"fs";import{readdir as Ea}from"fs/promises";import zn from"path";import C from"typescript";function oe(e){return C.createSourceFile("entry.ts",e,C.ScriptTarget.Latest,!0,C.ScriptKind.TS)}function Fn(e){return e&&(C.isAsExpression(e)||C.isSatisfiesExpression(e)?e.expression:e)}function ja(e,t){let r=oe(e),n=null;function s(i){if(!n){if(C.isFunctionDeclaration(i)&&i.name?.text===t&&i.body){n=i.body;return}if(C.isVariableStatement(i)&&i.declarationList.declarations.some(o=>C.isIdentifier(o.name)&&o.name.text===t)){let o=i.declarationList.declarations.find(l=>C.isIdentifier(l.name)&&l.name.text===t),a=Fn(o.initializer);if(a&&C.isArrowFunction(a)&&a.body&&C.isBlock(a.body)){n=a.body;return}let c=_n(a,"register");if(c){n=c;return}}C.forEachChild(i,s)}}return s(r),n}function _n(e,t){if(!e||!C.isObjectLiteralExpression(e))return null;for(let r of e.properties){if(C.isMethodDeclaration(r)&&r.name.getText()===t&&r.body)return r.body;if(C.isPropertyAssignment(r)&&r.name.getText()===t&&r.initializer){let n=r.initializer;if(C.isArrowFunction(n)&&n.body&&C.isBlock(n.body)||C.isFunctionExpression(n)&&n.body)return n.body}}return null}function He(e,t){let r=oe(e),n=null;function s(i){if(!n){if(C.isVariableStatement(i)&&i.declarationList.declarations.some(o=>C.isIdentifier(o.name)&&o.name.text===t)){let o=i.declarationList.declarations.find(c=>C.isIdentifier(c.name)&&c.name.text===t),a=_n(Fn(o.initializer),"register");if(a){n=a;return}}C.forEachChild(i,s)}}return s(r),n??ja(e,"registerServer")}function Nn(e){let t=!1;function r(n){if(!t&&!C.isArrowFunction(n)){if(C.isCallExpression(n)){let s=n.expression;if(C.isIdentifier(s)&&s.text==="resolveService"){t=!0;return}let i=s.getText();if(i==="ctx.resolveService"||i.endsWith(".services.resolve")){t=!0;return}}C.forEachChild(n,r)}}return r(e),t}function pr(e){let t=!1;function r(n){if(!t){if(C.isCallExpression(n)){let s=n.expression.getText();if(s.endsWith(".cards.registerMap")||s.endsWith(".cards.register")){t=!0;return}}C.forEachChild(n,r)}}return r(e),t}function Vn(e){let t=oe(e),r=[];function n(s){if(C.isCallExpression(s)&&s.expression.getText().endsWith(".cards.register")){let o=s.arguments[0];o&&r.push(o.getText().replace(/['"]/g,""))}C.forEachChild(s,n)}return n(t),r}function ur(e){let t=oe(e),r={};function n(s){if(C.isImportDeclaration(s)&&C.isStringLiteral(s.moduleSpecifier)){let i=s.moduleSpecifier.text;if(i.startsWith("./")||i.startsWith("../")){let o=s.importClause;o?.name&&(r[o.name.text]=i)}}C.forEachChild(s,n)}return n(t),r}async function Un(e){let t=[],r=zn.join(e.appDir,"packages");if(!Jn(r))return t;for(let n of await Ea(r,{withFileTypes:!0})){if(!n.isDirectory())continue;let s=zn.join(r,n.name,"src/server/index.ts");if(!Jn(s))continue;let i=await L(s),o=He(i,"serverModule");o&&Nn(o)&&t.push({rule:"register-pure",level:"error",message:`${n.name}: 注册入口(serverModule.register)内出现 resolveService(只允许在 handle/事件回调内)`})}return t}import{existsSync as Ge}from"fs";import ye from"path";import E from"typescript";function ge(e){return E.isAsExpression(e)||E.isSatisfiesExpression(e)?e.expression:e}function Mt(e,t){let r=oe(e),n=null;function s(i){let o={};for(let a of i.properties){if(!E.isPropertyAssignment(a))continue;let c=a.name,l=E.isIdentifier(c)||E.isStringLiteral(c)?c.text:"";l&&(o[l]=a.initializer)}return o}for(let i of r.statements)if(E.isVariableStatement(i)){for(let o of i.declarationList.declarations)if(!(!E.isIdentifier(o.name)||!o.initializer)){if(o.name.text==="cards"){let a=ge(o.initializer);if(E.isObjectLiteralExpression(a))return n=s(a),n}if(t&&o.name.text===t){let a=ge(o.initializer);if(E.isObjectLiteralExpression(a)){for(let c of a.properties)if(E.isPropertyAssignment(c)&&E.isIdentifier(c.name)&&c.name.text==="cards"){let l=ge(c.initializer);if(E.isObjectLiteralExpression(l))return n=s(l),n}}}}}return n}function Ra(e){let t=Mt(e,"clientModule");return t?Object.keys(t):[]}function Ke(e){let t=e.match(/cardType:\s*['"]([^'"]+)['"]/),r=e.match(/schemaVersion:\s*(\d+)/);return{cardType:t?.[1],schemaVersion:r?Number(r[1]):void 0}}function Wn(e,t){let r=[t,`${t}.ts`,`${t}.tsx`,`${t}/index.ts`,`${t}/index.tsx`];for(let n of r)if(Ge(ye.join(e,n)))return ye.join(e,n);return null}function Hn(e,t,r){let n=ge(e);if(E.isObjectLiteralExpression(n)){let s=Ke(n.getText());return{cardType:s.cardType,schemaVersion:s.schemaVersion}}if(E.isStringLiteral(n))return{cardType:n.text};if(E.isIdentifier(n)){let s=t[n.text];if(!s)return null;let i=Wn(r,s.replace(/^\.\//,""));return i?{resolved:i}:null}return null}async function Da(e){let t=ye.join(e,"src/server/index.ts");if(!Ge(t))return{cardTypes:[],schemaVersions:{},keyMismatches:[],registerMissing:!1};let r=await L(t),n=ur(r),s=ye.join(e,"src/server"),i=[],o={},a=[],c=Mt(r,"serverModule");if(c){let l=!1;if(Object.keys(c).length>0){let p=He(r,"serverModule");l=!p||!pr(p)}for(let[p,u]of Object.entries(c)){let d=Hn(u,n,s);if(d)if(d.resolved){let f=await L(d.resolved),g=Ke(f);g.cardType&&(i.push(g.cardType),g.schemaVersion&&(o[g.cardType]=g.schemaVersion),p!==g.cardType&&a.push({key:p,cardType:g.cardType}))}else d.cardType&&(i.push(d.cardType),d.schemaVersion&&(o[d.cardType]=d.schemaVersion),p!==d.cardType&&a.push({key:p,cardType:d.cardType}))}return{cardTypes:[...new Set(i)],schemaVersions:o,keyMismatches:a,registerMissing:l}}for(let l of Vn(r)){if(l.includes("cardType:")){let g=Ke(l);g.cardType&&(i.push(g.cardType),g.schemaVersion&&(o[g.cardType]=g.schemaVersion));continue}if(/^[a-zA-Z0-9][\w-]*$/.test(l)&&!n[l]){i.push(l);continue}let p=n[l];if(!p)continue;let u=Wn(s,p.replace(/^\.\//,""));if(!u)continue;let d=await L(u),f=Ke(d);f.cardType&&(i.push(f.cardType),f.schemaVersion&&(o[f.cardType]=f.schemaVersion))}return{cardTypes:[...new Set(i)],schemaVersions:o,keyMismatches:a,registerMissing:!1}}function Ca(e,t){return oe(e).statements.some(n=>E.isVariableStatement(n)&&n.declarationList.declarations.some(s=>E.isIdentifier(s.name)&&s.name.text===t))}async function Ta(e){let t=ye.join(e,"src/client/index.ts");if(!Ge(t))return!1;let r=await L(t);if(!Ca(r,"clientModule"))return!1;let n=Mt(r,"clientModule");if(!n||Object.keys(n).length===0)return!1;let s=He(r,"clientModule");return!s||!pr(s)}async function qe(e){let t=await z(e.appDir),r=[];for(let n of t){let s={};for(let c of n.descriptor.contributes.cards??[])s[c.cardType]=c.schemaVersion??1;let i=ye.join(n.dir,"src/client/index.ts"),o=Ge(i)?Ra(await L(i)):[],a=await Da(n.dir);r.push({id:n.id,declared:s,clientMap:o,serverRegistered:a.cardTypes,serverSchemaVersions:a.schemaVersions,serverKeyMismatches:a.keyMismatches,serverRegisterMissing:a.registerMissing,clientRegisterMissing:await Ta(n.dir)})}return r}function Tt(e){return E.isStringLiteral(e)||E.isNumericLiteral(e)||e.kind===E.SyntaxKind.NullKeyword||e.kind===E.SyntaxKind.TrueKeyword||e.kind===E.SyntaxKind.FalseKeyword?!0:E.isPrefixUnaryExpression(e)&&e.operator===E.SyntaxKind.MinusToken?Tt(e.operand):E.isArrayLiteralExpression(e)?e.elements.every(t=>E.isOmittedExpression(t)||Tt(t)):E.isObjectLiteralExpression(e)?e.properties.every(t=>E.isPropertyAssignment(t)&&Tt(t.initializer)):!1}function Bn(e){for(let t of e.properties)if(E.isPropertyAssignment(t)&&E.isIdentifier(t.name)&&t.name.text==="sampleData"){let r=ge(t.initializer);return E.isObjectLiteralExpression(r)?r.properties.length===0?"empty":Tt(r)?"ok":"non-literal":"non-literal"}return"missing"}function Ma(e){let t=oe(e),r={};for(let n of t.statements){if(E.isVariableStatement(n)){for(let s of n.declarationList.declarations)if(E.isIdentifier(s.name)&&s.initializer){let i=ge(s.initializer);E.isObjectLiteralExpression(i)&&(r[s.name.text]=i)}}if(E.isExportAssignment(n)&&!n.isExportEquals){let s=ge(n.expression);if(E.isObjectLiteralExpression(s))return s;if(E.isIdentifier(s)&&r[s.text])return r[s.text]}}return null}async function Kn(e){let t=ye.join(e,"src/server/index.ts");if(!Ge(t))return[];let r=await L(t),n=ur(r),s=ye.join(e,"src/server"),i=Mt(r,"serverModule");if(!i)return[];let o=[];for(let[a,c]of Object.entries(i)){let l=Hn(c,n,s);if(l){if(l.resolved){let p=await L(l.resolved),u=Ke(p).cardType??a,d=Ma(p);o.push({cardType:u,status:d?Bn(d):"missing",source:l.resolved})}else if(l.cardType){let p=ge(c),u=E.isObjectLiteralExpression(p)?Bn(p):"missing";o.push({cardType:l.cardType,status:u,source:"inline"})}}}return o}async function Gn(e){let t=[];for(let r of await qe(e)){let n=Object.keys(r.declared);if(n.length===0&&r.serverKeyMismatches.length===0&&!r.serverRegisterMissing&&!r.clientRegisterMissing)continue;for(let c of r.serverKeyMismatches)t.push({rule:"card-type-consistent",level:"error",message:`${r.id}: serverModule.cards key "${c.key}" !== meta.cardType "${c.cardType}"(key 约定 = cardType)`});r.serverRegisterMissing&&t.push({rule:"card-type-consistent",level:"error",message:`${r.id}: serverModule 声明了 cards 但 register 内未调用 ctx.cards.registerMap(cards)(注册动作缺失,运行时缺卡)`}),r.clientRegisterMissing&&t.push({rule:"card-type-consistent",level:"error",message:`${r.id}: clientModule 声明了 cards 但 register 内未调用 ctx.cards.registerMap(cards)(注册动作缺失,运行时缺卡)`});let s=n.filter(c=>!r.clientMap.includes(c));s.length>0&&t.push({rule:"card-type-consistent",level:"error",message:`${r.id}: 声明卡片 ${s.join(",")} 未在客户端 cards map 导出`});let i=n.filter(c=>!r.serverRegistered.includes(c));i.length>0&&t.push({rule:"card-type-consistent",level:"error",message:`${r.id}: 声明卡片 ${i.join(",")} 未在服务端注册(serverModule.cards meta / cards.register)`});let o=r.clientMap.filter(c=>!n.includes(c));o.length>0&&t.push({rule:"card-type-consistent",level:"warning",message:`${r.id}: 客户端导出未声明的卡片 ${o.join(",")}`});let a=r.serverRegistered.filter(c=>!n.includes(c));a.length>0&&t.push({rule:"card-type-consistent",level:"warning",message:`${r.id}: 服务端注册未声明的卡片 ${a.join(",")}`})}return t}async function qn(e){let t=[],r=await qe(e),n={};for(let i of r)for(let o of i.serverRegistered)o in n?n[o]+=`,${i.id}`:n[o]=i.id;if(Object.keys(n).length===0)return t;let s=new Set;for(let i of r)for(let o of i.clientMap)s.add(o);for(let[i,o]of Object.entries(n))s.has(i)||t.push({rule:"client-card-cover",level:"error",message:`服务端注册卡片 ${i}(模块 ${o})未出现在应用客户端装配(漏装配)`});return t}import{existsSync as dr}from"fs";import{readdir as fr,stat as Ia}from"fs/promises";import Me from"path";async function Xn(e){let t=[],r=Me.join(e.appDir,"packages");if(!dr(r))return t;let n=new Map;for(let i of await fr(r,{withFileTypes:!0})){if(!i.isDirectory())continue;let o=Z(Me.join(r,i.name));o?.name&&o.name.startsWith("@tbox.cn/app-contracts")&&n.set(o.name,o.version??"0.0.0")}let s=Me.join(e.appDir,"node_modules","@tbox.cn");if(dr(s))for(let i of await fr(s,{withFileTypes:!0})){let o=Me.join(s,i.name),a=i.isDirectory();if(!a&&i.isSymbolicLink())try{a=(await Ia(o)).isDirectory()}catch{a=!1}if(!a)continue;let c=Z(o);c?.name&&c.name.startsWith("@tbox.cn/app-contracts")&&n.set(c.name,c.version??"0.0.0")}for(let i of await fr(r,{withFileTypes:!0})){if(!i.isDirectory())continue;let o=Me.join(r,i.name);if(!dr(Me.join(o,"tbox.module.json")))continue;let a=Z(o),c={...a?.dependencies??{},...a?.peerDependencies??{}};for(let[l,p]of Object.entries(c)){if(!l.startsWith("@tbox.cn/app-contracts"))continue;let u=n.get(l);if(!u||p==="*"||p.startsWith("workspace:")||p.startsWith("catalog:")||p.replace(/^[\^~]/,"")===u)continue;Ct(p,u)||t.push({rule:"contract-version",level:"error",message:`${i.name}: 契约 ${l} 范围 ${p} 与实装 ${u} 无交集`})}}return t}import{existsSync as Aa}from"fs";import{readdir as Oa}from"fs/promises";import Pa from"path";async function Yn(e){let t=[],r=new Set,n=Pa.join(e.appDir,"packages");if(Aa(n))for(let f of await Oa(n,{withFileTypes:!0}))f.isDirectory()&&r.add(f.name);for(let f of e.declared)r.add(f.id);let s=new Set;for(let f of se)for(let g of e.actual[f.kind]?.moduleIds??[])s.add(g);let i=new Map,o=await z(e.appDir);for(let f of o){let g=(f.descriptor.dependencies?.modules??[]).map(m=>m.id);i.set(f.id,g)}let a=0,c=1,l=2,p=new Map,u=[];function d(f,g){p.set(f,c),g.push(f);for(let m of i.get(f)??[]){let h=p.get(m)??a;if(h===c){let v=g.indexOf(m);v>=0&&u.push([...g.slice(v),m])}else h===a&&d(m,g)}g.pop(),p.set(f,l)}for(let f of i.keys())(p.get(f)??a)===a&&d(f,[]);for(let f of u)t.push({rule:"module-deps-assembled",level:"error",message:`dependencies.modules 声明层存在循环依赖:${f.join(" → ")}`});for(let f of o)for(let g of f.descriptor.dependencies?.modules??[]){if(g.required&&!r.has(g.id)){t.push({rule:"module-deps-assembled",level:"error",message:`${f.id}: 依赖模块 ${g.id} 未安装(缺失 packages/${g.id} 或 npm 清单)`});continue}o.some(h=>h.id===g.id)&&g.required&&!s.has(g.id)&&t.push({rule:"module-deps-assembled",level:"error",message:`${f.id}: 依赖业务模块 ${g.id} 已安装但未装配(modules.ts 缺行,运行 sync)`})}return t}import{existsSync as mr}from"fs";import{readdir as La}from"fs/promises";import{createRequire as Fa}from"module";import{realpathSync as _a}from"fs";import Ie from"path";async function Zn(e){let t=[],r=["react","zod","@mastra/core"],n=["apps/client","apps/server","packages/app-sdk","packages/contracts"],s=Ie.join(e.appDir,"packages");if(mr(s))for(let i of await La(s,{withFileTypes:!0}))i.isDirectory()&&mr(Ie.join(s,i.name,"package.json"))&&n.push(Ie.join("packages",i.name));for(let i of r){let o=new Map;for(let a of n){let c=Ie.join(e.appDir,a);if(!mr(Ie.join(c,"package.json")))continue;let l=Na(c,i);if(l){let p=o.get(l)??[];p.push(a),o.set(l,p)}}o.size>1&&t.push({rule:"singleton",level:"error",message:`${i} 解析结果不唯一:${[...o.entries()].map(([a,c])=>`${a}(${c.join(",")})`).join(" vs ")}`})}return t}function Na(e,t){try{let r=Fa(Ie.join(e,"__noop__.js"));return _a(r.resolve(t))}catch{return null}}import{existsSync as Qn}from"fs";import gr from"path";var Va=new Set(["@tbox.cn/app-sdk","@tbox.cn/app-sdk/server","@tbox.cn/app-sdk/client","@tbox.cn/app-sdk/platform","@tbox.cn/app-sdk/platform/code-inspect"]),Ja=[{re:/^@mastra\/core/,label:"@mastra/core*(框架内部,改经 @tbox.cn/app-sdk)"},{re:/^@ag-ui\//,label:"@ag-ui/*(框架内部,改经 @tbox.cn/app-sdk)"},{re:/^@ai-sdk\//,label:"@ai-sdk/*(框架内部)"},{re:/^ai$/,label:"ai(框架内部)"},{re:/^zustand$/,label:"zustand(框架内部)"}];async function es(e){let t=[],r=["apps","packages"];for(let n of r){let s=gr.join(e.appDir,n);if(!Qn(s))continue;let i=[];await $e(s,e.appDir,i);for(let o of i){if(o.startsWith("packages/app-sdk/")||o.startsWith("packages/contracts/"))continue;let a=await L(gr.join(e.appDir,o));for(let c of a.matchAll(/from\s+['"]([^'"]+)['"]/g)){let l=c[1];if(l.startsWith("@tbox.cn/app-sdk/")&&!Va.has(l))t.push({rule:"sdk-internal-import",level:"error",message:`${o}: 不允许 import SDK 内部路径 ${l}`});else if(l.startsWith("@tbox.cn/app-contracts/"))t.push({rule:"sdk-internal-import",level:"error",message:`${o}: 不允许 import 契约子路径 ${l}(仅 @tbox.cn/app-contracts)`});else if(za(e.appDir,o))for(let p of Ja)p.re.test(l)&&t.push({rule:"sdk-internal-import",level:"error",message:`${o}: 模块不允许直接 import ${p.label}`})}}}return t}function za(e,t){let r=t.match(/^packages\/([^/]+)\//);return r?Qn(gr.join(e,"packages",r[1],"tbox.module.json")):!1}function ts(e){let t=[];for(let r of se){let n=new Set(e.declared.filter(c=>jt(r.kind,c)).map(c=>c.id)),s=new Set(e.actual[r.kind]?.moduleIds??[]),i=[...n].filter(c=>!s.has(c)),o=[...s].filter(c=>!n.has(c));i.length>0&&t.push({rule:"assembly-drift",level:"warning",message:`${r.path}: 已声明未装配 → ${i.join(", ")}`}),o.length>0&&t.push({rule:"assembly-drift",level:"warning",message:`${r.path}: 已装配未声明 → ${o.join(", ")}`});let a=e.actual[r.kind]?.broken??[];a.length>0&&t.push({rule:"assembly-drift",level:"warning",message:`${r.path}: 锚区破坏 → ${a.join(", ")}`})}return t}async function rs(e){let t=[];for(let r of await qe(e))for(let[n,s]of Object.entries(r.declared)){let i=r.serverSchemaVersions[n]??1;i!==s&&t.push({rule:"schema-version",level:"error",message:`${r.id}: 卡片 ${n} contributes.schemaVersion=${s} 与 meta schemaVersion=${i} 不一致`})}return t}import{existsSync as Ua}from"fs";import ns from"path";var Ba=["/webhook","/upload","/demo","/run"];async function ss(e){let t=[];for(let r of await z(e.appDir)){let n=ns.join(r.dir,"src/server");if(!Ua(n))continue;let s=[];await $e(n,r.dir,s);for(let i of s){let o=await L(ns.join(r.dir,i));for(let a of o.matchAll(/\.(?:post|put|patch|delete)\(\s*['"]([^'"]+)['"]/g)){let c=a[1];if(!c.startsWith("/"))continue;Ba.some(p=>c.startsWith(p))||t.push({rule:"naked-write-route",level:"warning",message:`${r.id}/${i}: 裸写路由 ${a[0].replace("("," (")} 建议改走 ctx.actions.registerAction(全局动作分发器)`})}}}return t}import{existsSync as os}from"fs";import{readdir as Wa}from"fs/promises";import is from"path";async function as(e){let t=[],r=is.join(e.appDir,"packages");if(!os(r))return t;for(let n of await Wa(r,{withFileTypes:!0})){if(!n.isDirectory())continue;let s=is.join(r,n.name,"tbox.module.json");if(os(s))try{let i=Ee(JSON.parse(await L(s)));i.ok||t.push({rule:"descriptor-schema",level:"error",message:`${n.name}/tbox.module.json: ${i.errors.join("; ")}`})}catch{t.push({rule:"descriptor-schema",level:"error",message:`${n.name}/tbox.module.json: JSON 解析失败`})}}return t}import{existsSync as cs}from"fs";import{readdir as Ha}from"fs/promises";import Xe from"path";async function ls(e){let t=[];if(cs(Xe.join(e.appDir,"pnpm-workspace.template.yaml")))return t;let r=new Map;for(let n of["apps","packages"]){let s=Xe.join(e.appDir,n);if(cs(s))for(let i of await Ha(s,{withFileTypes:!0})){if(!i.isDirectory())continue;let o=Xe.join(s,i.name),a=Z(o);a?.name&&r.set(a.name,Xe.join(n,i.name))}}if(r.size===0)return t;for(let[n,s]of r){let i=Z(Xe.join(e.appDir,s));if(i)for(let o of["dependencies","peerDependencies","devDependencies"]){let a=i[o];if(!(!a||typeof a!="object"))for(let[c,l]of Object.entries(a))typeof l!="string"||!l.startsWith("workspace:")||r.has(c)||t.push({rule:"workspace-ref-resolved",level:"error",message:`${s}: workspace:* 引用 ${c} 未命中任何成员(packages/ 或 apps/)`})}}return t}import{existsSync as Ka}from"fs";import Ga from"path";var qa=["apps/server/src/auth","apps/server/src/agent","apps/server/src/gateway","apps/server/src/http","apps/server/src/plugins/integration.ts","apps/server/src/config/tts-config.ts","apps/server/src/config/model-providers.ts","apps/client/src/env","apps/client/src/bridge","apps/client/src/adapters","apps/client/src/services/http.ts","apps/client/src/services/auth-client.ts","apps/client/src/services/user-id.ts","apps/client/src/services/tbox-session.ts"];function ps(e){let t=[];for(let r of qa)Ka(Ga.join(e.appDir,r))&&t.push({rule:"legacy-platform-files",level:"error",message:`${r} 为旧形态平台文件(007:平台能力已下沉 @tbox.cn/app-sdk)。请删除并改用 SDK API(server: createAuthRuntime/createPlatformApiRouter/createTtsProxy/createModelProvider/createPluginIntegration;client: createAuthClient/createPlatformAdapters/createPlatformApi/configureRuntime),详见 .agents/notes/archived/architecture/2026-08-12-platform-capabilities-sink.md。`});return t}import{existsSync as us,readFileSync as Xa}from"fs";import{readdir as Ya}from"fs/promises";import{createRequire as Za}from"module";import Ye from"path";function ds(e,t){try{let n=Za(Ye.join(e.appDir,"package.json")).resolve(`${t}/package.json`);return Ye.dirname(n)}catch{return}}function Qa(e){let t=[Ye.join(e,"src/server/index.ts"),Ye.join(e,"src/server/index.tsx")];for(let r of t)if(us(r))return Xa(r,"utf8");return""}function ec(e){let t=[];for(let r of e.matchAll(/patterns\s*:\s*\[([^\]]*)\]/g))for(let n of r[1].matchAll(/['"]([^'"]+)['"]/g))t.push(n[1]);return t}async function fs(e){let t=[],r=new Map;for(let n of e.declared){if(!n.hasServerEntry)continue;let s=ds(e,n.pkg);if(!s)continue;let i=ec(Qa(s));for(let o of i){let a=r.get(o)??[];a.push(n.id),r.set(o,a)}}for(let[n,s]of r)s.length>1&&t.push({rule:"intent-overlap",level:"warning",message:`意图词 "${n}" 在多个模块声明(${s.join(", ")})——先注册先命中`});return t}async function ms(e){let t=[],r=new Map;for(let n of e.declared){if(!n.hasServerEntry)continue;let s=ds(e,n.pkg);if(!s)continue;let i=Ye.join(s,"skills");if(us(i))try{let o=(await Ya(i,{withFileTypes:!0})).filter(a=>a.isDirectory()).map(a=>a.name);for(let a of o){let c=r.get(a)??[];c.push(n.id),r.set(a,c)}}catch{}}for(let[n,s]of r)s.length>1&&t.push({rule:"skill-name-conflict",level:"warning",message:`skill "${n}" 在多个模块声明(${s.join(", ")})——后注册覆盖`});return t}async function gs(e){let t=[];for(let r of await z(e.appDir))for(let n of await Kn(r.dir)){if(n.status==="ok")continue;let s=n.status==="missing"?"缺失(dev 卡片预览页 / 模块单测不可用)":n.status==="empty"?"为空对象(至少一组可展示数据)":"非字面量(引用常量/函数调用,AST 无法离线求值——静态检查无法求值,运行时预览不受影响)";t.push({rule:"sample-data-valid",level:"warning",message:`${r.id}/${n.cardType}: sampleData ${s}`})}return t}import{existsSync as ys}from"fs";import{readdir as hs}from"fs/promises";import It from"path";async function vs(e){let t=[],r=It.join(e.appDir,"packages");if(!ys(r))return t;for(let n of await hs(r,{withFileTypes:!0})){if(!n.isDirectory()||n.name.startsWith("contracts-"))continue;let s=It.join(r,n.name,"src");if(!ys(s))continue;let i=[];await ws(s,s,i),i.length>0&&t.push({rule:"module-console-logger",level:"warning",message:`${n.name}: 源码含裸 console 调用 ${i.length} 处(${i.slice(0,3).join("、")}${i.length>3?"…":""})——建议改用 ctx.logger(见 docs/reference/modules.md 模块日志规范)`})}return t}async function ws(e,t,r){for(let n of await hs(e,{withFileTypes:!0})){let s=It.join(e,n.name);if(n.isDirectory()){if(n.name==="__tests__"||n.name==="node_modules")continue;await ws(s,t,r);continue}if(!/\.tsx?$/.test(n.name)||/\.test\.tsx?$/.test(n.name))continue;let i=await L(s),o=It.relative(t,s);i.split(`
|
|
90
|
+
`).forEach((c,l)=>{/console\.(log|warn|error|info|debug)\s*\(/.test(c)&&r.push(`${o}:${l+1}`)})}}import{existsSync as bs}from"fs";import{readdir as ks}from"fs/promises";import At from"path";async function xs(e){let t=[],r=At.join(e.appDir,"packages");if(!bs(r))return t;for(let n of await ks(r,{withFileTypes:!0})){if(!n.isDirectory()||n.name.startsWith("contracts-"))continue;let s=At.join(r,n.name,"src");if(!bs(s))continue;let i=[];await Ss(s,s,i),i.length>0&&t.push({rule:"module-mall-scope",level:"warning",message:`${n.name}: 源码含手解 attributes.mall ${i.length} 处(${i.slice(0,3).join("、")}${i.length>3?"…":""})——改用 parseMallScope/extractMallScope/withMallScopeFromQuery(见 docs/reference/modules.md 读取入口族)`})}return t}var tc=/\.attributes\??\.?\bmall\b|\.attributes\??\.?\[\s*['"]mall['"]\s*\]/,rc=/as\s*\{[^}]*\bmall\b/,nc=/^\s*(\*|\/\/|\/\*)/;async function Ss(e,t,r){for(let n of await ks(e,{withFileTypes:!0})){let s=At.join(e,n.name);if(n.isDirectory()){if(n.name==="__tests__"||n.name==="node_modules")continue;await Ss(s,t,r);continue}if(!/\.tsx?$/.test(n.name)||/\.test\.tsx?$/.test(n.name))continue;let i=await L(s),o=At.relative(t,s);i.split(`
|
|
91
|
+
`).forEach((c,l)=>{nc.test(c)||(tc.test(c)||rc.test(c))&&r.push(`${o}:${l+1}`)})}}import{existsSync as $s,readFileSync as js}from"fs";import Es from"path";function sc(e,t=process.env,r="<config>"){let n=_r(e,t);if(n.missing.length>0)throw new Error(`配置文件 ${r} 引用的环境变量缺失:${n.missing.join("、")}(先 source .real-env/env.sh <vendor>[-<variant>] 后重试)`);return n.text??e}async function Rs(e){let t=Es.join(e.appDir,"config","deployments","development.json");if($s(t))try{let m=JSON.parse(js(t,"utf8"));if(m&&typeof m=="object"&&"malls"in m)return[{rule:"integration-services",level:"error",message:"config/deployments/development.json 已退役(部署控制面并入 config/integrations.json 的 instances 注册表 + defaultInstanceId)——迁移后删除该文件(malls → instances[].id/attributes,defaultMallId → defaultInstanceId)"}]}catch{}let r=Es.join(e.appDir,"config","integrations.json");if(!$s(r))return[];let n=await rt(e.appDir);if(!n.strictValidation){let m=n.version===null;return[{rule:"integration-services",level:m?"warning":"error",message:m?"integrations: contracts 未安装——pnpm install 后重试(安装后启用严格校验与求值诊断)":`integrations: contracts 严格校验面不可用(${n.version},需 ≥0.9)——${n.notes[0]??"升级应用 @tbox.cn/app-contracts 依赖后可用"}`}]}let{resolveEffectiveService:s,buildSupplyLookup:i,moduleDomainOf:o,normalizeInstances:a,RESOURCE_TYPE_IDS:c}=n.module,l;try{l=JSON.parse(sc(js(r,"utf8"),process.env,r))}catch(m){return[{rule:"integration-services",level:"error",message:`config/integrations.json 非法 JSON:${m.message}`}]}let p=l?.services;if(!p||typeof p!="object"||Array.isArray(p))return[{rule:"integration-services",level:"error",message:"config/integrations.json 缺 services(Record<service, {provider, instances}>)"}];let u=Gr(e.appDir),d=i(qr(e.appDir)),f=Vr(e.appDir,u,l,process.env);return Nr({contracts:{resolveEffectiveService:s,buildSupplyLookup:i,moduleDomainOf:o,normalizeInstances:a,RESOURCE_TYPE_IDS:c},integrations:l,supplies:d,...f}).map(m=>({rule:"integration-services",level:m.severity,message:m.message,...m.code!==void 0?{code:m.code}:{},...m.service!==void 0?{service:m.service}:{},...m.module!==void 0?{module:m.module}:{},...m.instance!==void 0?{instance:m.instance}:{}}))}async function Ds(e){return(await kn(e.appDir)).map(r=>({rule:"codegen-ref-resolved",level:"error",message:r}))}import{existsSync as Cs}from"fs";import{readdir as Ts}from"fs/promises";import Ot from"path";async function Ms(e){let t=[],r=Ot.join(e.appDir,"packages");if(!Cs(r))return t;for(let n of await Ts(r,{withFileTypes:!0})){if(!n.isDirectory()||n.name.startsWith("contracts-"))continue;let s=Ot.join(r,n.name,"src");if(!Cs(s))continue;let i=[];await Is(s,s,i),i.length>0&&t.push({rule:"module-auth-context",level:"error",message:`${n.name}: 源码读 ctx.auth 全局单例 ${i.length} 处(${i.slice(0,3).join("、")}${i.length>3?"…":""})——ctx.auth 已于 app-sdk 0.11.0 拆除,改用显式通道(handle 第四参 reqCtx / getToolRequestContext,见 docs/reference/modules.md)`})}return t}async function Is(e,t,r){for(let n of await Ts(e,{withFileTypes:!0})){let s=Ot.join(e,n.name);if(n.isDirectory()){if(n.name==="__tests__"||n.name==="node_modules")continue;await Is(s,t,r);continue}if(!/\.tsx?$/.test(n.name)||/\.test\.tsx?$/.test(n.name))continue;let i=await L(s),o=Ot.relative(t,s);i.split(`
|
|
92
|
+
`).forEach((c,l)=>{/ctx\.auth\.(getContext|setContext|requireIdentity|isAnonymous)\s*\(/.test(c)&&r.push(`${o}:${l+1}`)})}}import yr from"path";import he from"typescript";var As="presentation",Os="labelsEmbedded",oc=new Set([".ts",".tsx"]);function ic(e){let t;try{t=oe(e)}catch{return[]}let r=[],n=s=>{if(he.isPropertyAssignment(s)&&(he.isIdentifier(s.name)||he.isStringLiteral(s.name))&&s.name.text===As&&he.isObjectLiteralExpression(s.initializer)){let i=s.initializer.properties.some(c=>c.name!==void 0&&he.isIdentifier(c.name)&&c.name.text==="variant"),o=s.initializer.properties.some(c=>c.name!==void 0&&he.isIdentifier(c.name)&&c.name.text===Os),a=s.initializer.properties.some(he.isSpreadAssignment);i&&!o&&!a&&r.push(t.getLineAndCharacterOfPosition(s.getStart(t)).line+1)}he.forEachChild(s,n)};return n(t),r}async function Ps(e){let t=[];for(let r of await z(e.appDir)){let n=yr.join(r.dir,"src"),s=[];await $e(n,n,s);for(let i of s){if(!oc.has(yr.extname(i)))continue;let o=await L(yr.join(n,i));if(o.includes(As))for(let a of ic(o))t.push({rule:"card-face-labels",level:"error",message:`${r.id}: src/${i}:${String(a)} 的 presentation 缺 ${Os}——卡面图自带卡名与「我的积分」字样,缺省时前端会再叠一层导致文字重叠;卡图确实无字请显式写 false`})}}return t}import{existsSync as Ls}from"fs";import{readdir as Fs}from"fs/promises";import hr from"path";async function _s(e){let t=[],r=hr.join(e.appDir,"packages");if(!Ls(r))return t;for(let n of await Fs(r,{withFileTypes:!0})){if(!n.isDirectory()||n.name.startsWith("contracts-"))continue;let s=hr.join(r,n.name,"src");if(!Ls(s))continue;let i=await Ns(s);if(i.length===0)continue;let o=i.map(u=>u.text).join(`
|
|
93
|
+
`),a=new Set;for(let u of o.matchAll(/internalTool\s*\(\s*(\w+)\s*\)/g))a.add(u[1]);let c=[...o.matchAll(/const\s+(\w+)\s*=\s*createTool\s*\(/g)],l=[],p=[];for(let u=0;u<c.length;u++){let d=c[u],f=d[1],g=d.index??0,m=u+1<c.length?c[u+1].index??g+4e3:g+4e3,v=o.slice(g,Math.min(m,g+8e3)).includes("模型勿调用"),S=a.has(f);v&&!S?l.push(f):S&&!v&&p.push(f)}l.length>0&&t.push({rule:"module-internal-tool",level:"warning",message:`${n.name}: 工具 description 含「模型勿调用」但未 internalTool 标记(${l.join("、")})——模型面照常可见,撑大静态前缀且有幻觉调用面(见 docs/reference/modules.md 内部工具规约)`}),p.length>0&&t.push({rule:"module-internal-tool",level:"warning",message:`${n.name}: internalTool 标记的工具 description 缺「模型勿调用」标注(${p.join("、")})——标注是 doctor 静态面唯一可查锚点,补齐描述`})}return t}async function Ns(e,t=""){let r=[];for(let n of await Fs(e,{withFileTypes:!0})){let s=hr.join(e,n.name),i=t?`${t}/${n.name}`:n.name;if(n.isDirectory()){if(n.name==="__tests__"||n.name==="node_modules")continue;r.push(...await Ns(s,i));continue}/\.tsx?$/.test(n.name)&&(/\.test\.tsx?$/.test(n.name)||r.push({rel:i,text:await L(s)}))}return r}var ac=[{name:"workspace-dag",run:Ln},{name:"register-pure",run:Un},{name:"card-type-consistent",run:Gn},{name:"client-card-cover",run:qn},{name:"contract-version",run:Xn},{name:"module-deps-assembled",run:Yn},{name:"singleton",run:Zn},{name:"sdk-internal-import",run:es},{name:"assembly-drift",run:ts},{name:"schema-version",run:rs},{name:"naked-write-route",run:ss},{name:"descriptor-schema",run:as},{name:"workspace-ref-resolved",run:ls},{name:"legacy-platform-files",run:ps},{name:"intent-overlap",run:fs},{name:"skill-name-conflict",run:ms},{name:"sample-data-valid",run:gs},{name:"module-console-logger",run:vs},{name:"module-mall-scope",run:xs},{name:"integration-services",run:Rs},{name:"codegen-ref-resolved",run:Ds},{name:"module-auth-context",run:Ms},{name:"card-face-labels",run:Ps},{name:"module-internal-tool",run:_s}];async function q(e,t){let r=[],n=t?.only?.length?new Set(t.only):null,s=new Set(t?.skip??[]);for(let i of ac)if(!(n&&!n.has(i.name))&&!s.has(i.name))try{r.push(...await i.run(e))}catch(o){r.push({rule:i.name,level:"error",message:`规则执行异常: ${o?.message??String(o)}`})}return r}function Vs(e,t){return!t.has(e)||e.startsWith("contracts-")}async function Js(e,t,r,n=[]){let s=await z(e),i=new Set(s.map(b=>b.id)),o=new Set(r?.map(b=>b.id)??[]),a=new Map;for(let b of s)a.set(b.id,b.descriptor.version??"0.0.0");let c=new Map(n.map(b=>[b.id,b.deps])),l=new Set,p=[],u=[...t,...n.map(b=>b.id)];for(;u.length>0;){let b=u.shift();if(l.has(b))continue;l.add(b);let k=s.find(j=>j.id===b),O=c.get(b)??k?.descriptor.dependencies?.modules??[];for(let j of O)p.push(j),!l.has(j.id)&&(i.has(j.id)||c.has(j.id))&&u.push(j.id)}let d=0,f=1,g=2,m=new Map,h=[];function v(b,k){m.set(b,f),k.push(b);for(let O of S.get(b)??[]){let j=m.get(O)??d;if(j===f){let ee=k.indexOf(O);ee>=0&&h.push([...k.slice(ee),O])}else j===d&&v(O,k)}k.pop(),m.set(b,g)}let S=new Map,$=new Map(s.map(b=>[b.id,b]));for(let b of l){let k=$.get(b),O=c.get(b)??k?.descriptor.dependencies?.modules??[];S.set(b,O.filter(j=>l.has(j.id)).map(j=>j.id))}for(let b of l)(m.get(b)??d)===d&&v(b,[]);let R=[...l].filter(b=>!o.has(b)),F=new Map;for(let b of R)F.set(b,0);let A=new Map;for(let b of R)for(let k of S.get(b)??[])if(F.has(k)){F.set(b,(F.get(b)??0)+1);let O=A.get(k);O?O.push(b):A.set(k,[b])}let N=R.filter(b=>(F.get(b)??0)===0),P=[];for(;N.length>0;){let b=N.shift();P.push(b);for(let k of A.get(b)??[]){let O=(F.get(k)??0)-1;F.set(k,O),O===0&&N.push(k)}}P.length,R.length;let w=new Set,D=new Set,M=[];for(let b of p){let k=b.id;if((!o.has(k)||!a.has(k))&&(Vs(k,i)?w.add(k):Vs(k,i)||D.add(k)),b.range&&a.has(k)){let O=a.get(k);b.range!=="*"&&!b.range.startsWith("workspace:")&&(Ct(b.range,O)||M.push(`${k}: 依赖范围 ${b.range} 与目标版本 ${O} 无交集`))}}return{order:P,contracts:[...w],business:[...D],cycles:h,conflicts:M}}function lc(e){let t=Q.join(e,"tbox.module.json");if(!ve(t))return null;try{let r=Ee(JSON.parse(wr(t,"utf8")));return r.ok?r.value:null}catch{return null}}function pc(e,t){if(!e)return null;let r=Q.dirname(e),n=Q.join(r,t);if(ve(Q.join(n,"package.json")))return n;try{let s=cc(r).find(i=>i.endsWith(".tgz")&&i.includes(`-${t}-`));if(s)return Q.join(r,s)}catch{}return null}function vr(e,t){let r=Q.join(e,"packages",t);return ve(r)&&!ht(r)?!0:B(e)?.npmModules.some(s=>s.id===t&&s.mode==="sdk")??!1}async function zs(e){await Us(e,new Set,!1)}async function Us(e,t,r){let n=[];try{await uc(e,t,r,n)}finally{for(let s of n)s.dispose()}}async function uc(e,t,r,n){let s=process.cwd();if(!B(s))throw new Error("当前目录不是 tbox-app 应用(缺 .tbox/app.json)");let i,o=null,a=null,c=!!e.source&&Ce(e.source);if(e.mode==="local"){let h=e.pkg;i=h.startsWith("module-")?h:`module-${h}`,ke(i,`--mode local ${e.pkg}`)}else{if(!e.source)throw new Error(`--mode ${e.mode} 需要 --source <目录或 tgz 或 registry 包>`);if(!(c&&!ve(Q.resolve(e.source)))&&!e.source.endsWith(".tgz")&&!re.isUsable(e.source))throw new Error(`模块源无效(缺 package.json): ${e.source}`);a=fe(e.source,e.registry),(a.source instanceof U||a.source instanceof de)&&n.push(a.source);let v=e.pkg.replace(/@[^/]+$/,"");i=Sn(v),ke(i,`--mode ${e.mode} ${e.pkg}`),o=lc(a.sourceDir)}if(r&&vr(s,i)){console.log(` ⚠️ 依赖 ${i} 已安装,跳过`);return}if(!r&&vr(s,i)){let h=ve(Q.join(s,"packages",i))?`packages/${i}`:"manifest 已登记(sdk 包,无本地目录)";throw new Error(`模块 ${i} 已存在(${h})。若要重新展开请先 remove 或手动处理;装配唯一覆盖入口为 sync --force`)}if(t.has(i))return;if(t.add(i),!e.noDeps){let h=o?.dependencies?.modules??[],v=await Js(s,[],void 0,[{id:i,deps:h}]);if(v.cycles.length>0)throw new Error(`依赖环:${v.cycles.map($=>$.join(" → ")).join(";")}`);for(let $ of v.conflicts)console.warn(` ⚠️ 依赖版本冲突: ${$}`);let S=[...v.contracts,...v.order].filter($=>$!==i&&!vr(s,$));for(let $ of[...new Set(S)]){let R=/^(module|scenario|contracts|provider)-/.test($)?`@tbox.cn/app-${$}`:`@tbox.cn/${$}`,F=c?R:pc(e.source,$);if(!F){console.warn(` ⚠️ 依赖 ${$} 未安装且未找到源(--source 兄弟目录/tgz),可 --no-deps 跳过`);continue}let A=fe(F,e.registry);(A.source instanceof U||A.source instanceof de)&&n.push(A.source);let N=ve(Q.join(A.sourceDir,"tbox.module.json")),P=gn(R,N);await Us({pkg:R,mode:P,source:F,registry:e.registry},t,!0)}}let l;if(e.mode==="local"){let h=e.pkg,v=h.startsWith("module-")?h:`module-${h}`;await jn(s,h),l={id:v,package:`@app/${v}`,version:"0.1.0",mode:"local"},await We(s,`@app/${v}`,"add")}else{let h=a.sourceDir,v=a.source.resolveVersion();e.mode==="codegen"&&(vt(h),await $n(s,h,{id:i,package:e.pkg.replace(/@[^/]+$/,""),version:v,mode:"codegen",baseVersion:v},Se(s).values()),await We(s,yt(i),"add")),l={id:i,package:e.pkg.replace(/@[^/]+$/,""),version:v,mode:e.mode,...e.mode==="codegen"?{baseVersion:v}:{}}}let p=B(s);if(await te(s,Yr(p,l)),l.mode==="codegen"){let h=await bt(s,Se(s).values());h.length>0&&console.log(` codegen 引用同步: ${h.join(", ")}`)}let u=await me(s);u.changedFiles.length>0&&console.log(` 装配更新: ${u.changedFiles.join(", ")}`);for(let h of u.warnings)console.warn(` ⚠️ ${h}`);for(let h of u.depConflicts)console.warn(` ⚠️ 依赖冲突: ${h}`);let d=await G(s),g=(await q({appDir:s,declared:u.declared,actual:d},{skip:["integration-services"]})).filter(h=>h.level==="error");if(g.length>0){console.error("❌ doctor 错误:");for(let h of g)console.error(` - [${h.rule}] ${h.message}`);process.exitCode=1}let m=await Ne(s);m.generated?console.log(` ⚡ 已生成 mock 槽绑定(config/integrations.json:${m.services.join(", ")})`):m.reason==="in-place"&&console.log(" ℹ config/integrations.json 已在场,跳过 mock 绑定生成"),console.log(`✅ 已安装 ${l.package}(mode=${e.mode}${r?", 依赖":""})`),dc(s,l.id),e.mode!=="local"&&console.log(" 下一步: pnpm install && tbox-app module doctor")}function dc(e,t){try{let r=Q.join(e,"packages",t,"package.json");if(!ve(r))return;let s=JSON.parse(wr(r,"utf8")).dependencies?.["@tbox.cn/app-contracts-mall"],i=typeof s=="string"?s.replace(/[\^~]/,""):void 0;if(!i)return;let o=Q.join(e,"pnpm-workspace.yaml");if(!ve(o))return;let l=wr(o,"utf8").match(/['"]?@tbox\.cn\/app-contracts-mall['"]?\s*:\s*['"]([^'"]+)['"]/)?.[1]?.replace(/[\^~]/,"");if(!l)return;fc(i,l)>0&&console.warn(` ⚠️ 版本配套:${t} 依赖 contracts-mall ${i}+,当前应用 catalog 为 ${l}——请升级模板或应用 catalog(015 W1)`)}catch{}}function fc(e,t){let r=e.split(".").map(Number),n=t.split(".").map(Number),s=(r[0]??0)-(n[0]??0);return Math.sign(s!==0?s:(r[1]??0)-(n[1]??0))}import mc from"path";async function Bs(e,t={}){let r=process.cwd();ke(e,"remove 参数");let n=B(r);if(!n)throw new Error("当前目录不是 tbox-app 应用(缺 .tbox/app.json)");if(!t.force){let m=[];for(let h of await z(r)){if(h.id===e)continue;let v=(h.descriptor.dependencies?.modules??[]).find(S=>S.id===e);v&&v.required&&m.push(h.id)}if(m.length>0)throw new Error(`模块 ${e} 被依赖,无法移除:${m.join(", ")}(--force 忽略)`)}let s=n.npmModules.find(m=>m.id===e),i=!!s;i&&await te(r,Zr(n,e));let o=mc.join(r,"packages",e),c=ht(o)&&(s?.mode==="sdk"||!s);(c?!1:await Dn(r,e))?console.log(` 已删除 packages/${e}`):c?console.log(" 已移除 manifest 登记(保留 contracts-mall SDK shim)"):s?.mode==="sdk"&&console.log(" 已移除 manifest 登记(sdk 包,无本地目录)");let p=s?.mode==="codegen"&&Je(s.package)?await bn(r,xe(s)):!1;p&&console.log(" 已恢复 contracts-mall SDK shim(@app/contracts-mall)"),!p&&!c&&(s?.mode==="codegen"||s?.mode==="local"||!i)&&await We(r,`@app/${e}`,"remove");let u=await me(r);u.changedFiles.length>0&&console.log(` 装配更新: ${u.changedFiles.join(", ")}`);for(let m of u.warnings)console.warn(` ⚠️ ${m}`);for(let m of u.depConflicts)console.warn(` ⚠️ 依赖冲突: ${m}`);let d=await G(r),g=(await q({appDir:r,declared:u.declared,actual:d})).filter(m=>m.level==="error");if(g.length>0){console.error("❌ doctor 错误:");for(let m of g)console.error(` - [${m.rule}] ${m.message}`);process.exitCode=1}console.log(c&&!i?`✅ ${e} 未登记,保留 SDK shim`:`✅ 已移除 ${e}`)}async function Ws(e={}){let t=process.cwd(),r=await me(t,{force:e.force??!1});r.changedFiles.length===0?console.log("装配已同步,无变更"):console.log(`已更新装配文件: ${r.changedFiles.join(", ")}`);for(let o of r.warnings)console.warn(` ⚠️ ${o}`);for(let o of r.depConflicts)console.warn(` ⚠️ 依赖冲突: ${o}`);let n=await G(t),s=await q({appDir:t,declared:r.declared,actual:n}),i=s.filter(o=>o.level==="error");if(i.length>0){console.error("❌ doctor 错误:");for(let o of i)console.error(` - [${o.rule}] ${o.message}`);process.exitCode=1}else console.log("✅ doctor 无错误");for(let o of s.filter(a=>a.level==="warning"))console.warn(` ⚠️ [${o.rule}] ${o.message}`)}async function Hs(){let e=process.cwd(),t=B(e),r=await ue(e);if(console.log(`模板版本: ${t?.templateVersion??"未知"}`),r.length===0){console.log("(无已装模块)");return}for(let i of r)console.log(` ${i.id.padEnd(28)} ${i.pkg} [${i.mode}]`);let n=new Set((t?.npmModules??[]).map(i=>i.id)),s=(await z(e)).filter(i=>!n.has(i.id));if(s.length>0){console.log(" 本地模块(未在 app.json 登记):");for(let i of s)console.log(` ${i.id}`)}}async function Ks(e){let t=process.cwd(),r=await ue(t),n=await G(t),s=!1;for(let i of se){let o=i.kind,a=r.filter(g=>jt(o,g)).map(g=>g.id),c=n[o]?.moduleIds??[],l=a.filter(g=>!c.includes(g)),p=c.filter(g=>!a.includes(g));e&&(l=l.filter(g=>g===e),p=p.filter(g=>g===e));let u=n[o]?.broken??[],d=n[o]?.nonStandardLines??[],f=[];u.length>0&&f.push(`⚠️ 锚区破坏: ${u.join(", ")}`),l.length>0&&f.push(` - 已声明未装配: ${l.join(", ")}`),p.length>0&&f.push(` + 已装配未声明: ${p.join(", ")}`);for(let g of d)f.push(` ? 非标行: ${g}`);if(f.length>0){s=!0,console.log(`${i.path}`);for(let g of f)console.log(` ${g}`)}}s||console.log("无漂移:声明集合与实际装配集合一致")}async function Gs(e={}){let t=process.cwd(),r=await ue(t),n=await G(t),s=await q({appDir:t,declared:r,actual:n},{only:e.only?e.only.split(",").map(a=>a.trim()).filter(Boolean):void 0,skip:e.skip?e.skip.split(",").map(a=>a.trim()).filter(Boolean):void 0}),i=s.filter(a=>a.level==="error"),o=s.filter(a=>a.level==="warning");if(e.json)return console.log(JSON.stringify({errors:i,warnings:o,rules:s},null,2)),i.length>0&&(process.exitCode=1),{issues:s};if(s.length===0)console.log("✅ doctor 全绿(结构校验通过)");else{for(let a of i)console.error(`❌ [${a.rule}] ${a.message}`);for(let a of o)console.warn(`⚠️ [${a.rule}] ${a.message}`);i.length>0?(console.error(`doctor 失败: ${i.length} 个错误`),process.exitCode=1):console.log(`doctor 通过(${o.length} 个告警)`)}return{issues:s}}import{existsSync as kr}from"fs";import{readdir as yc,readFile as Zs,unlink as hc,writeFile as vc}from"fs/promises";import Ae from"path";import{diffArrays as gc}from"diff";function qs(e,t){let r=gc(e,t),n=[],s=0,i=null;for(let o of r)o.removed?(i?i.oldLines.push(...o.value):i={start:s,oldLines:[...o.value],newLines:[]},s+=o.value.length):o.added?i?i.newLines.push(...o.value):i={start:s,oldLines:[],newLines:[...o.value]}:(i&&(n.push(i),i=null),s+=o.value.length);return i&&n.push(i),n}function Xs(e,t,r){let n=e===""?[]:e.split(`
|
|
94
|
+
`),s=t===""?[]:t.split(`
|
|
95
|
+
`),i=r===""?[]:r.split(`
|
|
96
|
+
`),o=qs(n,s),a=qs(n,i),c=new Map;for(let m of o)c.set(m.start,{ours:m});for(let m of a){let h=c.get(m.start);h?h.theirs=m:c.set(m.start,{theirs:m})}let l=[],p=[],u=new Set,d=(m,h)=>{let v=Math.max(m?.oldLines.length??0,h?.oldLines.length??0);return m&&h?JSON.stringify(m.oldLines)===JSON.stringify(h.oldLines)&&JSON.stringify(m.newLines)===JSON.stringify(h.newLines)?l.push(...m.newLines):(p.push([...m.newLines,"--",...h.newLines].join(`
|
|
97
|
+
`)),l.push("<<<<<<< ours",...m.newLines,"=======",...h.newLines,">>>>>>> theirs")):m?l.push(...m.newLines):l.push(...h.newLines),v},f=0;for(;f<n.length;){let m=c.get(f);if(!m||u.has(f)){l.push(n[f]),f++;continue}u.add(f);let h=d(m.ours,m.theirs);h>0&&(f+=h)}let g=c.get(n.length);return g&&!u.has(n.length)&&d(g.ours,g.theirs),{merged:l.join(`
|
|
98
|
+
`),conflicts:p}}var wc=new Set(["node_modules","dist",".git",".tbox"]);function Ys(e){if(e.endsWith(".tgz")&&kr(e)){let t=new U(e);return{dir:t.getDir(),tarball:t}}return{dir:Ae.resolve(e),tarball:null}}async function br(e){let t=new Map;async function r(n){let s=Ae.join(e,n),i=await yc(s,{withFileTypes:!0});for(let o of i){if(wc.has(o.name))continue;let a=n?`${n}/${o.name}`:o.name,c=Ae.join(s,o.name);if(o.isDirectory())await r(a);else try{t.set(a,await Zs(c,"utf8"))}catch{}}}return await r(""),t}async function Qs(e){let t=process.cwd(),r=B(t);if(!r)throw new Error("当前目录不是 tbox-app 应用(缺 .tbox/app.json)");let n=r.npmModules.find(a=>a.id===e.module);if(!n)throw new Error(`模块 ${e.module} 未在 .tbox/app.json 登记`);if(n.mode==="sdk"){if(e.dryRun){console.log(`[dry-run] pnpm update ${n.package}`);return}let{execFileSync:a}=await import("child_process");a("pnpm",["update",n.package],{stdio:"inherit",cwd:t}),console.log(`✅ 已升级 ${n.package}(sdk 模式)`);return}let s=Ae.join(t,"packages",e.module);if(!kr(s))throw new Error(`缺少 packages/${e.module}`);let i=await br(s),o=[];try{let a,c,l;if(e.source){let k=Ys(e.source);k.tarball&&o.push(k.tarball),n.mode==="codegen"&&vt(k.dir),l=k.dir,a=await br(k.dir),c=k.tarball?k.tarball.resolveVersion():Z(k.dir)?.version??n.version}else throw new Error("codegen 升级需要 --source <新版本地目录或 tgz>(npm 拉取为 manual,真实发布后回落)");let p=Se(t),u=n.mode==="codegen"?xe(n):null,d=u?new Map([...p,[u.publicName,u]]):p,f=Be(l),g=Be(s),m=ce(l),h=n.mode==="codegen"&&f?Te(f,n,d.values(),m):f,v=new Map,S=null;if(e.baseSource){let k=Ys(e.baseSource);k.tarball&&o.push(k.tarball),S=k.dir,v=await br(k.dir)}let $=n.mode==="codegen"?wt(i,d.values()):i,R=n.mode==="codegen"?wt(v,d.values()):v,F=n.mode==="codegen"?wt(a,d.values()):a,A=new Set([...R.keys(),...$.keys(),...F.keys()]);A.delete("package.json");let N=new Map,P=[];for(let k of A){let O=R.get(k)??"",j=$.get(k)??"",ee=F.get(k)??"";if(e.ours){j&&N.set(k,j);continue}if(e.theirs){ee&&N.set(k,ee);continue}let Gt=Xs(O,j,ee);N.set(k,Gt.merged),Gt.conflicts.length>0&&P.push(`${k}: ${Gt.conflicts.length} 处冲突`)}if(n.mode==="codegen"&&h){let k={};try{k=JSON.parse(v.get("package.json")??"{}")}catch{k={}}let O=Te(k,n,d.values(),S?ce(S):void 0),j=Te(g,n,d.values(),ce(t)),ee=wn(O,j,h,{ours:e.ours,theirs:e.theirs});N.set("package.json",JSON.stringify(ee,null,2)+`
|
|
99
|
+
`)}else N.set("package.json",await Zs(Ae.join(s,"package.json"),"utf8"));if(e.dryRun){console.log(`[dry-run] ${e.module}: ${A.size} 文件合并,冲突 ${P.length} 处`);for(let k of P)console.log(` ⚠️ ${k}`);return}for(let[k,O]of N){let j=Ae.join(s,k);if(O===""&&kr(j)){await hc(j),console.log(` 🗑 已删除 ${k}(升级源删除该文件)`);continue}await vc(j,O,"utf8")}await te(t,en(r,e.module,{baseVersion:c,version:c})),n.mode==="codegen"&&await bt(t,Se(t).values());let w=await me(t),D=await G(t),b=(await q({appDir:t,declared:w.declared,actual:D})).filter(k=>k.level==="error");if(b.length>0){console.error("❌ doctor 错误:");for(let k of b)console.error(` - [${k.rule}] ${k.message}`);process.exitCode=1}if(P.length>0){console.warn("⚠️ 升级完成,存在冲突标记(<<<<<<< ours / >>>>>>> theirs),请人工/Agent 解决后运行 doctor:");for(let k of P)console.warn(` - ${k}`)}else console.log(`✅ 已升级 ${e.module} → ${c}`)}finally{for(let a of o)a.dispose()}}import{existsSync as jr,readFileSync as Ec,mkdtempSync as so,rmSync as Rc}from"fs";import{tmpdir as oo}from"os";import le from"path";import{existsSync as ie,readFileSync as eo,readdirSync as bc}from"fs";import{execFileSync as kc}from"child_process";import J from"path";function to(e,t){return`${e.replace(/^@/,"").replace("/","-")}-${t}.tgz`}function xc(e){if(!ie(e)||!ie(J.join(e,"package.json")))throw new Error(`模块目录无效(缺 package.json): ${e}`);let t;try{t=JSON.parse(eo(J.join(e,"package.json"),"utf8"))}catch{throw new Error(`package.json 解析失败: ${J.join(e,"package.json")}`)}let r=typeof t.name=="string"?t.name:"";if(!/^@tbox\.cn\//.test(r))throw new Error(`发布包名必须为 @tbox.cn/* scope: ${r||"(缺失)"}`);let n=typeof t.version=="string"?t.version:"";if(!/^\d+\.\d+\.\d+/.test(n))throw new Error(`版本号无效(需 semver x.y.z): ${n||"(缺失)"}`);return{name:r,version:n}}function xr(e,t){let{name:r,version:n}=xc(e),s=J.resolve(t);return kc("pnpm",["pack","--pack-destination",s],{stdio:"inherit",cwd:e}),J.join(s,to(r,n))}function ro(e,t){return ie(J.join(e,"pnpm-workspace.template.yaml"))?"template":ie(J.join(e,"tbox.module.json"))?"module":/^@[^/]+\/app-contracts(?:-|$)/.test(t)?"contract":"generic"}function Sc(e){let t=[];function r(n){let s=J.join(e,n);for(let i of bc(s,{withFileTypes:!0})){if(i.name==="node_modules"||i.name===".git")continue;let o=n?`${n}/${i.name}`:i.name;i.isDirectory()?r(o):t.push(o)}}return r(""),t}function $c(e){let t=[],r=n=>{typeof n=="string"&&t.push(n)};if(typeof e=="string")r(e);else if(e&&typeof e=="object"){for(let n of Object.values(e))if(typeof n=="string")r(n);else if(n&&typeof n=="object")for(let s of Object.values(n))r(s)}return t}function jc(e,t){let r=[],n=J.join(e,"package.json"),s;try{s=JSON.parse(eo(n,"utf8"))}catch{return r.push({rule:"pack-manifest",level:"error",message:"产物缺少可解析的 package.json"}),r}let i=t??ro(e,s.name??""),o=Sc(e);for(let p of["dependencies","optionalDependencies","peerDependencies","devDependencies"]){let u=s[p];if(!(!u||typeof u!="object"))for(let[d,f]of Object.entries(u))typeof f=="string"&&/^(workspace:|catalog:)/.test(f)&&r.push({rule:"pack-protocol-residue",level:"error",message:`${d}: 发布态残留 pnpm 私有协议 "${f}"(应已归一化为实装版本)`})}if(s.exports!==void 0)for(let p of $c(s.exports))p.includes("/src/")||p==="src"?r.push({rule:"pack-exports",level:"error",message:`exports 指向开发态源码 "${p}"(发布态应指向 dist/)`}):p.startsWith("./")&&!ie(J.join(e,p))&&r.push({rule:"pack-exports",level:"error",message:`exports 目标缺失(产物内不存在): ${p}`});let a=s.files;if(Array.isArray(a)&&a.length>0){let p=new Set([...a.map(d=>String(d)),"package.json"]),u=o.filter(d=>![...p].some(f=>d===f||d.startsWith(`${f}/`)));u.length>0&&r.push({rule:"pack-whitelist",level:"error",message:`产物含白名单外文件: ${u.slice(0,5).join(", ")}${u.length>5?` 等 ${u.length} 个`:""}`})}else r.push({rule:"pack-whitelist",level:"warning",message:"未声明 files 白名单,产物范围不可控(建议声明 files 限制打包内容)"});let c=o.filter(p=>/(^|\/)\.env$/.test(p)||/(^|\/)\.npmrc$/.test(p));if(c.length>0&&r.push({rule:"pack-leak",level:"error",message:`产物含敏感文件: ${c.join(", ")}`}),i==="module")for(let p of["src","dist","tbox.module.json"])!o.includes(p)&&!ie(J.join(e,p))&&r.push({rule:"pack-files",level:"error",message:`业务模块产物缺少 ${p}`});else if(i==="template"){for(let p of["package.json","pnpm-workspace.template.yaml","apps/server/package.json","apps/client/package.json",".tbox/app.json"])ie(J.join(e,p))||r.push({rule:"pack-files",level:"error",message:`模板产物缺少 ${p}`});for(let p of["pnpm-workspace.yaml","pnpm-lock.yaml"])ie(J.join(e,p))&&r.push({rule:"pack-files",level:"error",message:`模板产物泄漏 ${p}(应仅含 pnpm-workspace.template.yaml / pnpm-lock.template.yaml)`});ie(J.join(e,"pnpm-lock.template.yaml"))||r.push({rule:"pack-files",level:"warning",message:"模板产物未携带 pnpm-lock.template.yaml(间接依赖未冻结——平台包发布后跑 sync-template-lockfile 生成)"})}else if(i==="contract")o.filter(u=>u.startsWith("dist/")&&u.endsWith(".d.ts")).length===0&&r.push({rule:"pack-files",level:"error",message:"契约包产物缺少 dist/*.d.ts"}),s.name==="@tbox.cn/app-contracts-mall"&&!ie(J.join(e,"src","index.ts"))&&r.push({rule:"pack-files",level:"error",message:"contracts-mall codegen 载荷缺少 src/index.ts"});else{let p=s.bin,u=typeof p=="string"?[p]:p&&typeof p=="object"?Object.values(p):[];for(let d of u)typeof d=="string"&&!o.includes(d)&&r.push({rule:"pack-files",level:"error",message:`bin 指向文件缺失: ${d}`})}let l=Array.isArray(a)&&a.length>0;for(let p of["LICENSE","README.md"])o.includes(p)||r.push({rule:"pack-docs",level:"warning",message:`产物缺少 ${p}${l?"(files 白名单已声明;发布完备性 backlog)":"(建议补充以完备发布元数据)"}`});return r}async function Sr(e){let t=new U(e);try{let r=t.getDir(),n=JSON.parse(await t.readFile("package.json")),s=ro(r,n.name??""),i=jc(r,s);if(n.name&&n.version){let o=to(n.name,n.version);J.basename(e)!==o&&i.push({rule:"pack-filename",level:"error",message:`tgz 文件名 ${J.basename(e)} 与包 ${n.name}@${n.version} 期望 ${o} 不一致`})}return i}finally{t.dispose()}}function $r(e){for(let t of e)console.log(` ${t.level==="error"?"❌":"⚠️"} [${t.rule}] ${t.message}`)}async function no(e){let t=J.resolve(process.cwd(),e.module),r=xr(t,e.out);if(console.log(`✅ 已打包 ${J.basename(r)} → ${J.resolve(e.out)}`),e.check){let n=await Sr(r);$r(n),n.some(s=>s.level==="error")&&(process.exitCode=1)}}async function io(e){let t=process.cwd(),r=le.resolve(t,e.module);if(!jr(r)||!jr(le.join(r,"package.json")))throw new Error(`模块目录无效(缺 package.json): ${e.module}`);let n=(()=>{try{return JSON.parse(Ec(le.join(r,"package.json"),"utf8"))}catch{throw new Error(`模块 package.json 解析失败: ${e.module}`)}})();if(n.private===!0)throw new Error(`模块 ${e.module}(${n.name??"?"})声明 private:true——防误发布门禁拦截(本地 tgz/目录分发;恢复发布见仓库 docs/reference/operations.md 版本策略)`);let s,i=null;if(e.tgz){if(s=le.resolve(e.tgz),!jr(s))throw new Error(`tgz 产物不存在: ${e.tgz}`);let o=new U(s);try{let a=JSON.parse(await o.readFile("package.json"));if(a.name!==n.name)throw new Error(`--tgz 包名 ${a.name??"(未知)"} 与模块 ${n.name??"(未知)"} 不一致(请确认 --tgz 与模块匹配)`);if(a.private===!0)throw new Error(`--tgz 产物 ${le.basename(s)} 标记 private:true——防误发布门禁拦截`)}finally{o.dispose()}}else i=so(le.join(oo(),"tbox-publish-")),s=xr(r,i);try{let o=await Sr(s);o.length>0&&$r(o);let a=o.filter(p=>p.level==="error");if(a.length>0)throw new Error(`产物门禁失败(${a.length} 处 error),已中止发布: ${le.basename(s)}`);i||(i=so(le.join(oo(),"tbox-publish-")));let{execFileSync:c}=await import("child_process"),l=e.registry?["--registry",e.registry]:[];c("pnpm",["publish",s,"--access",e.access??"public",...l,...e.dryRun?["--dry-run"]:[]],{stdio:"inherit",cwd:i}),console.log(`✅ 已发布 ${le.basename(s)}${e.registry?` → ${e.registry}`:""}${e.dryRun?"(dry-run)":""}`)}finally{if(i)try{Rc(i,{recursive:!0,force:!0})}catch{}}}import{existsSync as Pt}from"fs";import{cp as Dc,readFile as Cc,rm as Tc,writeFile as Mc}from"fs/promises";import Oe from"path";function Ic(e){return Pt(Oe.join(e,"mini.project.json"))&&Pt(Oe.join(e,"app.json"))&&Pt(Oe.join(e,"pages"))}async function ao(e){let t=e.platform??"alipay";ke(t,"miniapp-container init");let r=process.cwd(),n=B(r);if(!n)throw new Error("当前目录不是 tbox-app 应用(缺 .tbox/app.json)");let s=`@tbox.cn/app-miniapp-container-${t}`,i;if(e.source)i=fe(e.source,e.registry);else try{i=fe(s,e.registry)}catch(o){throw new Error(`未找到 ${s}(registry 拉取失败:${o.message})。离线场景用 --source <容器目录或 tgz>`)}try{let o=i.sourceDir;if(!Ic(o))throw new Error(`源不是小程序容器工程(缺 mini.project.json/app.json/pages): ${e.source??s}`);let a=Oe.join(r,"apps",`miniapp-container-${t}`);if(Pt(a)){if(!e.force)throw new Error(`容器已存在: apps/miniapp-container-${t}(--force 删除重展开)`);await Tc(a,{recursive:!0,force:!0})}await Dc(o,a,{recursive:!0,force:!0,filter:d=>{let f=Oe.basename(d);return!["node_modules","dist",".git",".DS_Store",".tbox"].includes(f)}});let c=Oe.join(a,"package.json"),l=JSON.parse(await Cc(c,"utf8")),p=typeof l.name=="string"?l.name:s;l.name=`@app/miniapp-container-${t}`,l.private=!0,await Mc(c,JSON.stringify(l,null,2)+`
|
|
100
|
+
`,"utf8");let u=Zt(o);await te(r,Qr(n,{id:t,package:u.name??s,version:i.source.resolveVersion()||u.version,dir:`apps/miniapp-container-${t}`})),De(r),console.log(`✅ 已初始化容器 @app/miniapp-container-${t}(v${i.source.resolveVersion()||u.version},源 ${p})`),console.log(` ⚡ 展开至 apps/miniapp-container-${t}(workspace apps/* 自动纳管)`),console.log(` 下一步: pnpm install && pnpm --filter @app/miniapp-container-${t} dev`)}finally{(i.source instanceof U||i.source instanceof de)&&i.source.dispose()}}import{existsSync as co,readFileSync as Ac}from"fs";import Ze from"path";function Er(e){return Ze.join(e,"config","integrations.json")}async function lo(e){if(co(Er(e)))return;let t=await Ne(e);t.generated?console.log(` ⚡ 已生成 mock 槽绑定(config/integrations.json:${t.services.join(", ")})`):(Re(Er(e),JSON.stringify({services:{}},null,2)+`
|
|
101
|
+
`),console.log(" ⚡ 已创建 config/integrations.json 空骨架(无 local 供给可派生 mock 绑定)"))}function Lt(e){let t=Er(e);if(!co(t))throw new Error("config/integrations.json 不存在(provider bind 需已初始化的槽位配置面——create 产物或手动创建)");let r=Xr(e);if(r===null){try{JSON.parse(Ac(t,"utf8"))}catch(n){throw new Error(`config/integrations.json 解析失败:${n.message}`)}throw new Error("config/integrations.json 解析失败")}if(!r.services||typeof r.services!="object")throw new Error("integrations.json 缺 services");return r}async function we(e,t){await nt(e).writeIntegrationsConfig(t)}async function Ft(e){let{collectDeclared:t}=await import("./declared-4SBML2FU.js"),r=await t(e),n=await G(e),s=await q({appDir:e,declared:r,actual:n},{only:["integration-services"]}),i=s.filter(o=>o.level==="error");for(let o of i)console.error(` ❌ [${o.rule}] ${o.message}`);for(let o of s.filter(a=>a.level==="warning"))console.warn(` ⚠️ [${o.rule}] ${o.message}`);i.length>0&&(process.exitCode=1)}async function _t(e){let t=await rt(e);if(!t.strictValidation){let r=t.notes[0]??"升级应用 @tbox.cn/app-contracts 依赖后可用";throw new Error(`integrations: contracts 严格校验面不可用(${t.version??"未安装"},需 ≥0.9)——${r}`)}}async function po(e,t,r,n={}){let s=Ze.resolve(n.appDir??process.cwd());await _t(s),await lo(s);let i=Lt(s);if(n.instance){let o=i.services[e]??{instances:{}};o.instances??={};let a={...o.instances[n.instance],provider:t};r?a.implementation=r:delete a.implementation,o.instances[n.instance]=a,i.services[e]=o,await we(s,i),console.log(r?`✅ 已绑定 ${e}@${n.instance} → ${t}/${r}(实例覆盖位)`:`✅ 已绑定 ${e}@${n.instance} → ${t}(实例覆盖位——implementation 派生:catalog 唯一供给)`)}else{let o=i.services[e];o?(o.provider=t,r?o.implementation=r:delete o.implementation):i.services[e]=r?{provider:t,implementation:r}:{provider:t},await we(s,i),console.log(r?`✅ 已绑定 ${e} → ${t}/${r}${o?"(实例集保留)":"(新槽——instances 缺席 = 通配激活,立即生效;限定实例集请补 instances 显式键)"}`:`✅ 已绑定 ${e} → ${t}(implementation 派生:catalog 唯一供给)${o?"(实例集保留)":"(新槽——instances 缺席 = 通配激活,立即生效;限定实例集请补 instances 显式键)"}`)}await Ft(s)}async function uo(e,t={}){let r=Ze.resolve(t.appDir??process.cwd());await _t(r),await lo(r);let n=Lt(r);if(t.domain)n.domains??={},n.domains[t.domain]={...n.domains[t.domain],provider:e},await we(r,n),console.log(`✅ 域默认厂商 ${t.domain} → ${e}(该域全部槽生效——槽级显式绑定除外)`);else{n.provider=e,await we(r,n),console.log(`✅ 应用默认厂商 → ${e}(全部未显式绑定槽生效——instances 缺席槽即通配激活)`);let s=Object.entries(n.domains??{}).filter(([,i])=>typeof i.provider=="string"&&i.provider!==e).map(([i])=>i);s.length>0&&console.log(` ℹ 域级覆盖仍优先生效:${s.join(", ")}`)}await Ft(r)}async function fo(e={}){let t=Ze.resolve(e.appDir??process.cwd());await _t(t);let r=Lt(t);if(e.domain){let n=r.domains?.[e.domain];if(!n||n.provider===void 0)throw new Error(`域 ${e.domain} 无默认厂商(无可删项)`);delete n.provider,Object.keys(n).length===0&&delete r.domains?.[e.domain],r.domains&&Object.keys(r.domains).length===0&&delete r.domains,await we(t,r),console.log(`✅ 已清除域默认厂商 ${e.domain}(该域槽回落槽级/root 继承)`)}else{if(r.provider===void 0)throw new Error("root 无默认厂商(无可删项——provider use 设置)");delete r.provider,await we(t,r),console.log("✅ 已清除应用默认厂商(未显式绑定槽将 binding-unresolved——doctor 即时反馈)")}await Ft(t)}async function mo(e,t={}){let r=Ze.resolve(t.appDir??process.cwd());await _t(r);let n=Lt(r),s=n.services[e];if(!s)throw new Error(`服务槽 ${e} 未配置(无可解绑项)`);if(t.instance){if(!s.instances?.[t.instance])throw new Error(`服务槽 ${e} 无实例 ${t.instance}(无可解绑项)`);delete s.instances[t.instance],await we(r,n),console.log(`✅ 已解绑 ${e}@${t.instance}(实例覆盖删除——回落槽级默认)`)}else delete n.services[e],await we(r,n),console.log(`✅ 已解绑 ${e}(槽与实例集删除——模块走降级链)`);await Ft(r)}import{existsSync as go,readFileSync as yo}from"fs";import je from"path";async function Oc(e){let t=new Map,r=s=>{typeof s.provider!="string"||typeof s.implementation!="string"||t.set(`${s.provider}-${s.implementation}`,{provider:s.provider,implementation:s.implementation})};for(let s of await z(e))for(let i of s.descriptor.contributes.providers?.slots??[])r(i);let n=je.join(e,".tbox","app.json");if(go(n))try{let s=JSON.parse(yo(n,"utf8"));for(let i of s.npmModules??[]){if(typeof i.id!="string"||typeof i.package!="string")continue;let o=qt(je.join(e,"packages",i.id))??qt(je.join(Pc(e),...i.package.split("/")));for(let a of o?.contributes.providers?.slots??[])r(a)}}catch{}return t}function Pc(e){return je.join(e,"node_modules")}function Lc(e,t){return t.get(e)}async function ho(e={}){let t=je.resolve(e.appDir??process.cwd()),r=je.join(t,"config","integrations.json");if(!go(r))throw new Error(`config/integrations.json 不存在:${r}`);let n=JSON.parse(yo(r,"utf8"));if(!n.services||typeof n.services!="object")throw new Error("integrations.json 缺 services");let s=await Oc(t),i=[],o=[],a=0,c=(l,p)=>{let u=Lc(p,s);return u?(i.push(` ${l}: '${p}' → provider='${u.provider}', implementation='${u.implementation}'`),a++):o.push(`${l}: '${p}'`),u};for(let[l,p]of Object.entries(n.services)){if(typeof p.provider=="string"&&p.provider){let u=c(`services.${l}.provider`,p.provider);u&&(p.provider=u.provider,p.implementation=u.implementation)}else if(p.provider&&typeof p.provider=="object"){let u=p.provider;typeof u.providerId=="string"&&(u.provider=u.providerId,delete u.providerId,i.push(` services.${l}.provider: 内联声明 providerId → provider(直译)`),a++)}for(let[u,d]of Object.entries(p.instances??{})){if(typeof d.provider=="string"&&d.provider){let f=c(`services.${l}.instances.${u}.provider`,d.provider);f&&(d.provider=f.provider,d.implementation=f.implementation)}d.enabled===!0&&delete d.enabled}}console.log(`provider migrate:${a} 项迁移(反查表 ${s.size} 条)`);for(let l of i)console.log(l);if(o.length>0){console.warn("⚠️ 未命中反查表(人工处理,保留原值):");for(let l of o)console.warn(` ${l}`);process.exitCode=1}e.write?(await nt(t).writeIntegrationsConfig(n),console.log(`✅ 已写回 ${je.relative(process.cwd(),r)||r}`)):console.log("(dry-run:加 --write 落盘)")}import{spawn as Fc}from"child_process";import{existsSync as I,mkdirSync as Rr,readFileSync as X,realpathSync as ae,readdirSync as _c,rmSync as Nc,symlinkSync as Vc,unlinkSync as $o,writeFileSync as Nt}from"fs";import{createHash as Jc}from"crypto";import x from"path";import{fileURLToPath as zc}from"url";var Pe=".tbox-dev.local",vo="templates/template-agent-mall",Uc=["apps","server"],Bc=8e3;function Wc(e){let t=x.join(e,".real-env","config");return I(t)?_c(t).filter(r=>I(x.join(t,r,"integrations.json"))).sort():[]}var wo="'../../.tbox-dev.local/vite-override.mjs'",Hc="TBOX_CONFIG_DIR";function Kc(){if(process.env.NODE_ENV==="test"&&process.env.TBOX_DEV_REPO_ROOT)return ae(process.env.TBOX_DEV_REPO_ROOT);let e=x.dirname(zc(import.meta.url));for(let t=0;t<10;t++){if(I(x.join(e,"pnpm-workspace.yaml")))return e;let r=x.dirname(e);if(r===e)break;e=r}throw new Error("未能定位仓库根(缺少 pnpm-workspace.yaml)——请在仓库内执行 tbox-app dev")}function Gc(e){let t=x.join(e,"package.json");if(!I(t))throw new Error(`目录缺 package.json:${e}`);try{let r=JSON.parse(X(t,"utf8"));if(typeof r.name!="string"||!r.name)throw new Error("name 缺失");return{name:r.name,version:String(r.version??"0.0.0")}}catch(r){throw new Error(`package.json 解析失败(${e}):${r.message}`)}}function qc(e){let t=x.join(e,"tbox.module.json");if(!I(t))throw new Error(`目录缺 tbox.module.json:${e}(是否传了非模块目录?)`);let r=Ee(JSON.parse(X(t,"utf8")));if(!r.ok)throw new Error(`tbox.module.json 校验失败(${e}):
|
|
102
|
+
${r.errors.join(`
|
|
103
|
+
`)}`);return r.value}function bo(e,t,r){let n=x.join(e,"src",t,"index.ts");if(!I(n))return!1;try{return new RegExp(`export\\s+const\\s+${r}\\b`).test(X(n,"utf8"))}catch{return!1}}function Xc(e){let t=x.join(e,"apps","client","vite.config.ts"),r=x.join(e,"apps","server","src","index.ts");if(!I(t))throw new Error(`模板缺 apps/client/vite.config.ts:${e}`);if(!I(r))throw new Error(`模板缺 apps/server/src/index.ts:${e}`);if(!X(t,"utf8").includes(wo))throw new Error(`模板 vite.config.ts 缺 dev 能力探测标记("${wo}")——模板过旧,请升级模板后重试`);let n=x.join(e,"apps","server","src","app.ts"),s=[X(r,"utf8"),...I(n)?[X(n,"utf8")]:[]].join(`
|
|
104
|
+
`);if((s.includes("registerAppConfig")||s.includes("loadChatConfig"))&&!s.includes(Hc))throw new Error("模板 server 装配源(apps/server/src/index.ts|app.ts)缺 TBOX_CONFIG_DIR 锚点——模板过旧,请升级模板后重试");if(!I(x.join(e,"apps","server","src","modules.ts")))throw new Error(`模板缺 apps/server/src/modules.ts 装配文件:${e}`);if(!I(x.join(e,"apps","client","src","modules.ts")))throw new Error(`模板缺 apps/client/src/modules.ts 装配文件:${e}`)}function Yc(e){if((e==="wanda"||e==="joycity")&&!process.env.TBOX_API_KEY)throw new Error(`选择真实档 --real-env ${e} 但环境缺 TBOX_API_KEY —— 请先 source .real-env/env.sh 再运行`)}function jo(e){if(!Number.isInteger(e)||e<=0)return!1;try{return process.kill(e,0),!0}catch{return!1}}var Zc=new Set(["contracts-mall"]);function Qc(e,t){return{id:e,pkg:`@tbox.cn/app-${e}`,version:"",dir:t,skeleton:!0,descriptor:{schemaVersion:1,name:e,version:"0.0.0",kind:"business",risk:{level:"low",writeBoundary:"source"},distribution:{defaultMode:"codegen"},contributes:{handlers:[],tools:[],cards:[],routes:[],pages:[],tabs:[],providers:{slots:[]},services:[],resources:[]},dependencies:{modules:[]},env:[]}}}function el(e,t,r){let n=new Map,s=[],i=c=>{let l=Gc(c),p=qc(c);if(n.has(p.name))return n.get(p.name);let u={id:p.name,pkg:l.name,version:l.version,dir:c,descriptor:p};return n.set(p.name,u),u},o=(c,l,p)=>{if(n.has(l))return n.get(l);if(Zc.has(l)){let d=I(x.join(r,"packages",l))?ae(x.join(r,"packages",l)):x.join(t,"modules",l);if(!I(d)){if(!p)return null;throw new Error(`依赖契约包 "${l}" 真源缺席(模板 packages/ 与 modules/ 均无该目录)`)}let f=Qc(l,d);return n.set(l,f),f}let u=x.join(t,"modules",l);if(!I(u)){if(!p)return null;throw new Error(`依赖模块 <${c.id}> 声明依赖 "${l}",但仓库无 <repoRoot>/modules/${l}——请确认目录名或先 clone 对应模块`)}return i(ae(u))},a=c=>{if(s.includes(c.id))throw new Error(`模块依赖环:${[...s.slice(s.indexOf(c.id)),c.id].join(" → ")}`);s=[...s,c.id];for(let l of c.descriptor.dependencies.modules)a(o(c,l.id,l.required)??c);s=s.slice(0,-1)};for(let c of e){if(!I(c))throw new Error(`--module 目录不存在:${c}(相对路径按仓库根解析)`);a(i(ae(c)))}return[...n.values()]}function tl(e){return{id:e.id,pkg:e.pkg,mode:"local",hasServerEntry:bo(e.dir,"server","serverModule"),hasClientEntry:bo(e.dir,"client","clientModule"),hasCards:e.descriptor.contributes.cards.length>0}}function rl(e,t,r){let n=t.map(tl),s=[],i=[["server-modules",x.join(e,"apps","server","src","modules.ts"),"modules.server.ts"],["client-modules",x.join(e,"apps","client","src","modules.ts"),"modules.client.ts"]];for(let[o,a,c]of i){let l=X(a,"utf8"),p=l;try{let u=Rt(l,o,n);p=u.content,s.push(...u.warnings)}catch(u){s.push(`[${o}] 锚点变换异常,原文兜底:${u.message}`)}Nt(x.join(r,c),p,"utf8")}return s}function nl(e,t,r,n){let s={},i=(o,a)=>{s[o]=x.relative(t,a).split(x.sep).join("/")};for(let o of r)o.skeleton||(i(o.pkg,o.dir),i(`@app/${o.id}`,o.dir));i("@tbox.cn/app-sdk",x.join(e,"packages","app-sdk")),i("@tbox.cn/app-contracts",x.join(e,"packages","contracts"));for(let[o,a]of Object.entries(s))if(!I(x.resolve(t,a)))throw new Error(`redirect 目标缺失:${o} → ${a}`);return s}function ko(e,t,r){let n=[],s=0;for(let[i,o]of Object.entries(r)){let a=ae(x.resolve(x.join(e,Pe),o)),c=i.split("/"),l=x.join(e,"node_modules",c[0]),p=x.join(l,c[1]);if(!(()=>{if(!I(p))return!0;let d=ae(p);return d===a?!1:d.startsWith(x.join(t,"modules")+x.sep)||x.join(t,"modules")===d})()){s++;continue}if(I(p)){let d=ae(p);if(!d.startsWith(x.join(t,"modules")))throw new Error(`refuse 覆盖非 dev 管理的既有链接:${p} → ${d}(疑为 workspace/依赖原生链接;请人工确认)`)}Rr(l,{recursive:!0}),Vc(x.relative(l,a),p),n.push(i)}return{planted:n,skipped:s}}function sl(e){let t=JSON.parse(X(e,"utf8")),r=new Set;typeof t.provider=="string"&&t.provider&&r.add(t.provider);for(let n of Object.values(t.domains??{}))typeof n?.provider=="string"&&n.provider&&r.add(n.provider);for(let n of Object.values(t.services??{})){let s=typeof n.provider=="string"?n.provider:n.provider?.provider;typeof s=="string"&&s&&r.add(s)}return r}function ol(e,t,r,n,s=[]){if(n){for(let a of["packages/app-sdk","packages/contracts"])if(!I(x.join(e,a)))throw new Error(`--link-sdk 要求平台真源在场:<repoRoot>/${a}`)}let i={},o;for(let a of s){let c=x.isAbsolute(a)?a:x.resolve(e,a);if(!I(c))throw new Error(`独立配置档目录不存在:${c}(--config-dir 须为存在的目录)`);o=c}if(r){let a=r.includes("-")?r.slice(0,r.indexOf("-")):r,c=x.join(e,".real-env","config",a);if(!I(c))throw new Error(`档位目录不存在:${c}(可用:${Wc(e).join("/")})`);o||(o=c)}if(o){let a=x.join(o,"integrations.json");if(I(a)){let c=sl(a),l=new Set;for(let u of t)for(let d of u.descriptor.contributes.providers.slots)l.add(d.provider);let p=[...c].filter(u=>!l.has(u));if(p.length>0)throw new Error(`一致性校验失败:provider ${p.join(", ")} 被 ${x.relative(e,o)||o} 档 integrations.json 引用但未随 dev 挂载——请追加 --module modules/provider-… 或更换 --config-dir / --real-env 档位`);i.TBOX_INTEGRATIONS_FILE=a}i.TBOX_CONFIG_DIR=o}return i}function il(e,t,r,n,s){let i=JSON.stringify({templateDir:e,tier:s.tier??null,linkSdk:!!s.linkSdk,port:s.port??null,redirects:Object.fromEntries(Object.entries(t).sort(([o],[a])=>o<a?-1:1)),configs:Object.fromEntries(Object.entries(r).sort(([o],[a])=>o<a?-1:1)),modules:n.map(o=>`${o.id}@${o.version}`).sort()});return Jc("sha256").update(i).digest("hex").slice(0,16)}var al=`// tbox-app dev 生成态构件(勿手改;regenerated by 'tbox-app dev')
|
|
105
|
+
import { register } from 'node:module';
|
|
106
|
+
register('./hook-core.mjs', import.meta.url);
|
|
107
|
+
`,cl=`// tbox-app dev 生成态构件(勿手改;regenerated by 'tbox-app dev')。
|
|
108
|
+
// resolve 钩子:① 相对 modules.js/modules.ts/modules → 物化装配 ② manifest 前缀重定向(mini exports)。
|
|
109
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
110
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
111
|
+
import path from 'node:path';
|
|
112
|
+
|
|
113
|
+
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
114
|
+
let REDIRECTS = {};
|
|
115
|
+
try {
|
|
116
|
+
REDIRECTS = JSON.parse(readFileSync(path.join(HERE, 'dev-manifest.json'), 'utf8')).redirects ?? {};
|
|
117
|
+
} catch {}
|
|
118
|
+
const KEYS = Object.keys(REDIRECTS).sort((a, b) => b.length - a.length);
|
|
119
|
+
|
|
120
|
+
/** 按真身 package.json exports 解析子路径(铁律:exports 恒指 src/);无 exports → src 约定 */
|
|
121
|
+
function resolveSub(dir, sub) {
|
|
122
|
+
const pkgFile = path.join(dir, 'package.json');
|
|
123
|
+
if (existsSync(pkgFile)) {
|
|
124
|
+
try {
|
|
125
|
+
const pkg = JSON.parse(readFileSync(pkgFile, 'utf8'));
|
|
126
|
+
const exp = pkg.exports ? pkg.exports[sub === '' ? '.' : sub] : undefined;
|
|
127
|
+
const target =
|
|
128
|
+
typeof exp === 'string'
|
|
129
|
+
? exp
|
|
130
|
+
: exp && typeof exp === 'object'
|
|
131
|
+
? (exp.import ?? exp.default)
|
|
132
|
+
: sub === ''
|
|
133
|
+
? pkg.main
|
|
134
|
+
: undefined;
|
|
135
|
+
if (typeof target === 'string') return pathToFileURL(path.resolve(dir, target)).href;
|
|
136
|
+
} catch {}
|
|
137
|
+
}
|
|
138
|
+
const guess = { '': 'src/index.ts', '/server': 'src/server/index.ts', '/client': 'src/client/index.ts' }[sub];
|
|
139
|
+
if (guess) {
|
|
140
|
+
const file = path.join(dir, guess);
|
|
141
|
+
if (existsSync(file)) return pathToFileURL(file).href;
|
|
1299
142
|
}
|
|
1300
143
|
return null;
|
|
1301
144
|
}
|
|
1302
|
-
function findModuleRegisterBody(src, objectName) {
|
|
1303
|
-
const sf = parseSource(src);
|
|
1304
|
-
let result = null;
|
|
1305
|
-
function visit(node) {
|
|
1306
|
-
if (result) return;
|
|
1307
|
-
if (ts.isVariableStatement(node) && node.declarationList.declarations.some(
|
|
1308
|
-
(d) => ts.isIdentifier(d.name) && d.name.text === objectName
|
|
1309
|
-
)) {
|
|
1310
|
-
const decl = node.declarationList.declarations.find(
|
|
1311
|
-
(d) => ts.isIdentifier(d.name) && d.name.text === objectName
|
|
1312
|
-
);
|
|
1313
|
-
const methodBody = objectMethodBody(unwrapAs(decl.initializer), "register");
|
|
1314
|
-
if (methodBody) {
|
|
1315
|
-
result = methodBody;
|
|
1316
|
-
return;
|
|
1317
|
-
}
|
|
1318
|
-
}
|
|
1319
|
-
ts.forEachChild(node, visit);
|
|
1320
|
-
}
|
|
1321
|
-
visit(sf);
|
|
1322
|
-
return result ?? findFunctionBody(src, "registerServer");
|
|
1323
|
-
}
|
|
1324
|
-
function hasTopLevelResolveService(body) {
|
|
1325
|
-
let found = false;
|
|
1326
|
-
function visit(node) {
|
|
1327
|
-
if (found) return;
|
|
1328
|
-
if (ts.isArrowFunction(node)) return;
|
|
1329
|
-
if (ts.isCallExpression(node)) {
|
|
1330
|
-
const expr = node.expression;
|
|
1331
|
-
if (ts.isIdentifier(expr) && expr.text === "resolveService") {
|
|
1332
|
-
found = true;
|
|
1333
|
-
return;
|
|
1334
|
-
}
|
|
1335
|
-
const text = expr.getText();
|
|
1336
|
-
if (text === "ctx.resolveService" || text.endsWith(".services.resolve")) {
|
|
1337
|
-
found = true;
|
|
1338
|
-
return;
|
|
1339
|
-
}
|
|
1340
|
-
}
|
|
1341
|
-
ts.forEachChild(node, visit);
|
|
1342
|
-
}
|
|
1343
|
-
visit(body);
|
|
1344
|
-
return found;
|
|
1345
|
-
}
|
|
1346
|
-
function hasCardsRegisterCall(body) {
|
|
1347
|
-
let found = false;
|
|
1348
|
-
function visit(node) {
|
|
1349
|
-
if (found) return;
|
|
1350
|
-
if (ts.isCallExpression(node)) {
|
|
1351
|
-
const text = node.expression.getText();
|
|
1352
|
-
if (text.endsWith(".cards.registerMap") || text.endsWith(".cards.register")) {
|
|
1353
|
-
found = true;
|
|
1354
|
-
return;
|
|
1355
|
-
}
|
|
1356
|
-
}
|
|
1357
|
-
ts.forEachChild(node, visit);
|
|
1358
|
-
}
|
|
1359
|
-
visit(body);
|
|
1360
|
-
return found;
|
|
1361
|
-
}
|
|
1362
|
-
function extractCardsRegisterArgs(src) {
|
|
1363
|
-
const sf = parseSource(src);
|
|
1364
|
-
const args = [];
|
|
1365
|
-
function visit(node) {
|
|
1366
|
-
if (ts.isCallExpression(node)) {
|
|
1367
|
-
const exprText = node.expression.getText();
|
|
1368
|
-
if (exprText.endsWith(".cards.register")) {
|
|
1369
|
-
const arg = node.arguments[0];
|
|
1370
|
-
if (arg) args.push(arg.getText().replace(/['"]/g, ""));
|
|
1371
|
-
}
|
|
1372
|
-
}
|
|
1373
|
-
ts.forEachChild(node, visit);
|
|
1374
|
-
}
|
|
1375
|
-
visit(sf);
|
|
1376
|
-
return args;
|
|
1377
|
-
}
|
|
1378
|
-
function extractRelativeImports(src) {
|
|
1379
|
-
const sf = parseSource(src);
|
|
1380
|
-
const map = {};
|
|
1381
|
-
function visit(node) {
|
|
1382
|
-
if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) {
|
|
1383
|
-
const spec = node.moduleSpecifier.text;
|
|
1384
|
-
if (spec.startsWith("./") || spec.startsWith("../")) {
|
|
1385
|
-
const clause = node.importClause;
|
|
1386
|
-
if (clause?.name) {
|
|
1387
|
-
map[clause.name.text] = spec;
|
|
1388
|
-
}
|
|
1389
|
-
}
|
|
1390
|
-
}
|
|
1391
|
-
ts.forEachChild(node, visit);
|
|
1392
|
-
}
|
|
1393
|
-
visit(sf);
|
|
1394
|
-
return map;
|
|
1395
|
-
}
|
|
1396
|
-
|
|
1397
|
-
// src/doctor/rules/register-pure.ts
|
|
1398
|
-
async function registerPureRule(ctx) {
|
|
1399
|
-
const issues = [];
|
|
1400
|
-
const packagesDir = path13.join(ctx.appDir, "packages");
|
|
1401
|
-
if (!existsSync9(packagesDir)) return issues;
|
|
1402
|
-
for (const entry of await readdir5(packagesDir, { withFileTypes: true })) {
|
|
1403
|
-
if (!entry.isDirectory()) continue;
|
|
1404
|
-
const entryFile = path13.join(packagesDir, entry.name, "src/server/index.ts");
|
|
1405
|
-
if (!existsSync9(entryFile)) continue;
|
|
1406
|
-
const src = await readTextOrEmpty(entryFile);
|
|
1407
|
-
const body = findModuleRegisterBody(src, "serverModule");
|
|
1408
|
-
if (body && hasTopLevelResolveService(body)) {
|
|
1409
|
-
issues.push({
|
|
1410
|
-
rule: "register-pure",
|
|
1411
|
-
level: "error",
|
|
1412
|
-
message: `${entry.name}: \u6CE8\u518C\u5165\u53E3\uFF08serverModule.register\uFF09\u5185\u51FA\u73B0 resolveService\uFF08\u53EA\u5141\u8BB8\u5728 handle/\u4E8B\u4EF6\u56DE\u8C03\u5185\uFF09`
|
|
1413
|
-
});
|
|
1414
|
-
}
|
|
1415
|
-
}
|
|
1416
|
-
return issues;
|
|
1417
|
-
}
|
|
1418
145
|
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
}
|
|
1466
|
-
}
|
|
1467
|
-
return result;
|
|
1468
|
-
}
|
|
1469
|
-
function parseClientCardsMap(src) {
|
|
1470
|
-
const map = parseCardsObject(src, "clientModule");
|
|
1471
|
-
return map ? Object.keys(map) : [];
|
|
1472
|
-
}
|
|
1473
|
-
function extractMetaFields(src) {
|
|
1474
|
-
const ct = src.match(/cardType:\s*['"]([^'"]+)['"]/);
|
|
1475
|
-
const sv = src.match(/schemaVersion:\s*(\d+)/);
|
|
1476
|
-
return {
|
|
1477
|
-
cardType: ct?.[1],
|
|
1478
|
-
schemaVersion: sv ? Number(sv[1]) : void 0
|
|
1479
|
-
};
|
|
1480
|
-
}
|
|
1481
|
-
function resolveTsPath(base, rel) {
|
|
1482
|
-
const candidates = [rel, `${rel}.ts`, `${rel}.tsx`, `${rel}/index.ts`, `${rel}/index.tsx`];
|
|
1483
|
-
for (const c of candidates) {
|
|
1484
|
-
if (existsSync10(path14.join(base, c))) return path14.join(base, c);
|
|
1485
|
-
}
|
|
1486
|
-
return null;
|
|
1487
|
-
}
|
|
1488
|
-
function resolveValueCardType(expr, imports, baseDir) {
|
|
1489
|
-
const e = unwrapAs2(expr);
|
|
1490
|
-
if (ts2.isObjectLiteralExpression(e)) {
|
|
1491
|
-
const fields = extractMetaFields(e.getText());
|
|
1492
|
-
return { cardType: fields.cardType, schemaVersion: fields.schemaVersion };
|
|
1493
|
-
}
|
|
1494
|
-
if (ts2.isStringLiteral(e)) {
|
|
1495
|
-
return { cardType: e.text };
|
|
1496
|
-
}
|
|
1497
|
-
if (ts2.isIdentifier(e)) {
|
|
1498
|
-
const rel = imports[e.text];
|
|
1499
|
-
if (!rel) return null;
|
|
1500
|
-
const resolved = resolveTsPath(baseDir, rel.replace(/^\.\//, ""));
|
|
1501
|
-
if (!resolved) return null;
|
|
1502
|
-
return { resolved };
|
|
146
|
+
export async function resolve(specifier, context, nextResolve) {
|
|
147
|
+
// ① 装配文件改指:tsx 可能先把 .js 改写为 .ts,两个形态都指向物化装配
|
|
148
|
+
if (/^\\.{1,2}\\/modules(?:\\.(?:js|ts))?$/.test(specifier)) {
|
|
149
|
+
return { url: pathToFileURL(path.join(HERE, 'modules.server.ts')).href, shortCircuit: true };
|
|
150
|
+
}
|
|
151
|
+
// ② manifest 前缀重定向(最少惊讶:种植链接为主通道,此处兜底 + linkSdk 单向)
|
|
152
|
+
const base = specifier.split('?')[0];
|
|
153
|
+
const hit = KEYS.find((k) => base === k || base.startsWith(k + '/'));
|
|
154
|
+
if (hit) {
|
|
155
|
+
const sub = base === hit ? '' : base.slice(hit.length);
|
|
156
|
+
const url = resolveSub(path.resolve(HERE, REDIRECTS[hit]), sub);
|
|
157
|
+
if (url) return { url, shortCircuit: true };
|
|
158
|
+
}
|
|
159
|
+
return nextResolve(specifier, context);
|
|
160
|
+
}
|
|
161
|
+
`,ll=`// tbox-app dev 生成态构件(勿手改;regenerated by 'tbox-app dev')。
|
|
162
|
+
// Vite 插件:client 装配改指物化构件 + manifest 前缀重定向 + fs 门面扩容。
|
|
163
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
164
|
+
import { fileURLToPath } from 'node:url';
|
|
165
|
+
import path from 'node:path';
|
|
166
|
+
|
|
167
|
+
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
168
|
+
const MANIFEST = JSON.parse(readFileSync(path.join(HERE, 'dev-manifest.json'), 'utf8'));
|
|
169
|
+
const KEYS = Object.keys(MANIFEST.redirects ?? {}).sort((a, b) => b.length - a.length);
|
|
170
|
+
|
|
171
|
+
function resolveSub(dir, sub) {
|
|
172
|
+
const pkgFile = path.join(dir, 'package.json');
|
|
173
|
+
if (existsSync(pkgFile)) {
|
|
174
|
+
try {
|
|
175
|
+
const pkg = JSON.parse(readFileSync(pkgFile, 'utf8'));
|
|
176
|
+
const exp = pkg.exports ? pkg.exports[sub === '' ? '.' : sub] : undefined;
|
|
177
|
+
const target =
|
|
178
|
+
typeof exp === 'string'
|
|
179
|
+
? exp
|
|
180
|
+
: exp && typeof exp === 'object'
|
|
181
|
+
? (exp.import ?? exp.default)
|
|
182
|
+
: sub === ''
|
|
183
|
+
? pkg.main
|
|
184
|
+
: undefined;
|
|
185
|
+
if (typeof target === 'string') return path.resolve(dir, target);
|
|
186
|
+
} catch {}
|
|
187
|
+
}
|
|
188
|
+
const guess = { '': 'src/index.ts', '/server': 'src/server/index.ts', '/client': 'src/client/index.ts' }[sub];
|
|
189
|
+
if (guess) {
|
|
190
|
+
const file = path.join(dir, guess);
|
|
191
|
+
if (existsSync(file)) return file;
|
|
1503
192
|
}
|
|
1504
193
|
return null;
|
|
1505
194
|
}
|
|
1506
|
-
async function resolveServerRegisteredCards(modDir) {
|
|
1507
|
-
const entryFile = path14.join(modDir, "src/server/index.ts");
|
|
1508
|
-
if (!existsSync10(entryFile)) return { cardTypes: [], schemaVersions: {}, keyMismatches: [], registerMissing: false };
|
|
1509
|
-
const entrySrc = await readTextOrEmpty(entryFile);
|
|
1510
|
-
const imports = extractRelativeImports(entrySrc);
|
|
1511
|
-
const baseDir = path14.join(modDir, "src/server");
|
|
1512
|
-
const cardTypes = [];
|
|
1513
|
-
const schemaVersions = {};
|
|
1514
|
-
const keyMismatches = [];
|
|
1515
|
-
const cardsMap = parseCardsObject(entrySrc, "serverModule");
|
|
1516
|
-
if (cardsMap) {
|
|
1517
|
-
let registerMissing = false;
|
|
1518
|
-
if (Object.keys(cardsMap).length > 0) {
|
|
1519
|
-
const registerBody = findModuleRegisterBody(entrySrc, "serverModule");
|
|
1520
|
-
registerMissing = !registerBody || !hasCardsRegisterCall(registerBody);
|
|
1521
|
-
}
|
|
1522
|
-
for (const [key, expr] of Object.entries(cardsMap)) {
|
|
1523
|
-
const value = resolveValueCardType(expr, imports, baseDir);
|
|
1524
|
-
if (!value) continue;
|
|
1525
|
-
if (value.resolved) {
|
|
1526
|
-
const metaSrc = await readTextOrEmpty(value.resolved);
|
|
1527
|
-
const fields = extractMetaFields(metaSrc);
|
|
1528
|
-
if (fields.cardType) {
|
|
1529
|
-
cardTypes.push(fields.cardType);
|
|
1530
|
-
if (fields.schemaVersion) schemaVersions[fields.cardType] = fields.schemaVersion;
|
|
1531
|
-
if (key !== fields.cardType) keyMismatches.push({ key, cardType: fields.cardType });
|
|
1532
|
-
}
|
|
1533
|
-
} else if (value.cardType) {
|
|
1534
|
-
cardTypes.push(value.cardType);
|
|
1535
|
-
if (value.schemaVersion) schemaVersions[value.cardType] = value.schemaVersion;
|
|
1536
|
-
if (key !== value.cardType) keyMismatches.push({ key, cardType: value.cardType });
|
|
1537
|
-
}
|
|
1538
|
-
}
|
|
1539
|
-
return { cardTypes: [...new Set(cardTypes)], schemaVersions, keyMismatches, registerMissing };
|
|
1540
|
-
}
|
|
1541
|
-
for (const arg of extractCardsRegisterArgs(entrySrc)) {
|
|
1542
|
-
if (arg.includes("cardType:")) {
|
|
1543
|
-
const fields2 = extractMetaFields(arg);
|
|
1544
|
-
if (fields2.cardType) {
|
|
1545
|
-
cardTypes.push(fields2.cardType);
|
|
1546
|
-
if (fields2.schemaVersion) schemaVersions[fields2.cardType] = fields2.schemaVersion;
|
|
1547
|
-
}
|
|
1548
|
-
continue;
|
|
1549
|
-
}
|
|
1550
|
-
if (/^[a-zA-Z0-9][\w-]*$/.test(arg) && !imports[arg]) {
|
|
1551
|
-
cardTypes.push(arg);
|
|
1552
|
-
continue;
|
|
1553
|
-
}
|
|
1554
|
-
const rel = imports[arg];
|
|
1555
|
-
if (!rel) continue;
|
|
1556
|
-
const resolved = resolveTsPath(baseDir, rel.replace(/^\.\//, ""));
|
|
1557
|
-
if (!resolved) continue;
|
|
1558
|
-
const metaSrc = await readTextOrEmpty(resolved);
|
|
1559
|
-
const fields = extractMetaFields(metaSrc);
|
|
1560
|
-
if (fields.cardType) {
|
|
1561
|
-
cardTypes.push(fields.cardType);
|
|
1562
|
-
if (fields.schemaVersion) schemaVersions[fields.cardType] = fields.schemaVersion;
|
|
1563
|
-
}
|
|
1564
|
-
}
|
|
1565
|
-
return { cardTypes: [...new Set(cardTypes)], schemaVersions, keyMismatches, registerMissing: false };
|
|
1566
|
-
}
|
|
1567
|
-
function hasModuleObject(src, objectName) {
|
|
1568
|
-
const sf = parseSource(src);
|
|
1569
|
-
return sf.statements.some(
|
|
1570
|
-
(stmt) => ts2.isVariableStatement(stmt) && stmt.declarationList.declarations.some(
|
|
1571
|
-
(d) => ts2.isIdentifier(d.name) && d.name.text === objectName
|
|
1572
|
-
)
|
|
1573
|
-
);
|
|
1574
|
-
}
|
|
1575
|
-
async function resolveClientRegisterMissing(modDir) {
|
|
1576
|
-
const entryFile = path14.join(modDir, "src/client/index.ts");
|
|
1577
|
-
if (!existsSync10(entryFile)) return false;
|
|
1578
|
-
const entrySrc = await readTextOrEmpty(entryFile);
|
|
1579
|
-
if (!hasModuleObject(entrySrc, "clientModule")) return false;
|
|
1580
|
-
const cardsMap = parseCardsObject(entrySrc, "clientModule");
|
|
1581
|
-
if (!cardsMap || Object.keys(cardsMap).length === 0) return false;
|
|
1582
|
-
const registerBody = findModuleRegisterBody(entrySrc, "clientModule");
|
|
1583
|
-
return !registerBody || !hasCardsRegisterCall(registerBody);
|
|
1584
|
-
}
|
|
1585
|
-
async function collectModuleCards(ctx) {
|
|
1586
|
-
const modules = await listLocalModules(ctx.appDir);
|
|
1587
|
-
const result = [];
|
|
1588
|
-
for (const mod of modules) {
|
|
1589
|
-
const declared = {};
|
|
1590
|
-
for (const c of mod.descriptor.contributes.cards ?? []) {
|
|
1591
|
-
declared[c.cardType] = c.schemaVersion ?? 1;
|
|
1592
|
-
}
|
|
1593
|
-
const clientFile = path14.join(mod.dir, "src/client/index.ts");
|
|
1594
|
-
const clientMap = existsSync10(clientFile) ? parseClientCardsMap(await readTextOrEmpty(clientFile)) : [];
|
|
1595
|
-
const server = await resolveServerRegisteredCards(mod.dir);
|
|
1596
|
-
result.push({
|
|
1597
|
-
id: mod.id,
|
|
1598
|
-
declared,
|
|
1599
|
-
clientMap,
|
|
1600
|
-
serverRegistered: server.cardTypes,
|
|
1601
|
-
serverSchemaVersions: server.schemaVersions,
|
|
1602
|
-
serverKeyMismatches: server.keyMismatches,
|
|
1603
|
-
serverRegisterMissing: server.registerMissing,
|
|
1604
|
-
clientRegisterMissing: await resolveClientRegisterMissing(mod.dir)
|
|
1605
|
-
});
|
|
1606
|
-
}
|
|
1607
|
-
return result;
|
|
1608
|
-
}
|
|
1609
|
-
|
|
1610
|
-
// src/doctor/rules/card-type-consistent.ts
|
|
1611
|
-
async function cardTypeConsistentRule(ctx) {
|
|
1612
|
-
const issues = [];
|
|
1613
|
-
for (const mod of await collectModuleCards(ctx)) {
|
|
1614
|
-
const declared = Object.keys(mod.declared);
|
|
1615
|
-
if (declared.length === 0 && mod.serverKeyMismatches.length === 0 && !mod.serverRegisterMissing && !mod.clientRegisterMissing) {
|
|
1616
|
-
continue;
|
|
1617
|
-
}
|
|
1618
|
-
for (const mm of mod.serverKeyMismatches) {
|
|
1619
|
-
issues.push({
|
|
1620
|
-
rule: "card-type-consistent",
|
|
1621
|
-
level: "error",
|
|
1622
|
-
message: `${mod.id}: serverModule.cards key "${mm.key}" !== meta.cardType "${mm.cardType}"\uFF08key \u7EA6\u5B9A = cardType\uFF09`
|
|
1623
|
-
});
|
|
1624
|
-
}
|
|
1625
|
-
if (mod.serverRegisterMissing) {
|
|
1626
|
-
issues.push({
|
|
1627
|
-
rule: "card-type-consistent",
|
|
1628
|
-
level: "error",
|
|
1629
|
-
message: `${mod.id}: serverModule \u58F0\u660E\u4E86 cards \u4F46 register \u5185\u672A\u8C03\u7528 ctx.cards.registerMap(cards)\uFF08\u6CE8\u518C\u52A8\u4F5C\u7F3A\u5931\uFF0C\u8FD0\u884C\u65F6\u7F3A\u5361\uFF09`
|
|
1630
|
-
});
|
|
1631
|
-
}
|
|
1632
|
-
if (mod.clientRegisterMissing) {
|
|
1633
|
-
issues.push({
|
|
1634
|
-
rule: "card-type-consistent",
|
|
1635
|
-
level: "error",
|
|
1636
|
-
message: `${mod.id}: clientModule \u58F0\u660E\u4E86 cards \u4F46 register \u5185\u672A\u8C03\u7528 ctx.cards.registerMap(cards)\uFF08\u6CE8\u518C\u52A8\u4F5C\u7F3A\u5931\uFF0C\u8FD0\u884C\u65F6\u7F3A\u5361\uFF09`
|
|
1637
|
-
});
|
|
1638
|
-
}
|
|
1639
|
-
const missingClient = declared.filter((t) => !mod.clientMap.includes(t));
|
|
1640
|
-
if (missingClient.length > 0) {
|
|
1641
|
-
issues.push({
|
|
1642
|
-
rule: "card-type-consistent",
|
|
1643
|
-
level: "error",
|
|
1644
|
-
message: `${mod.id}: \u58F0\u660E\u5361\u7247 ${missingClient.join(",")} \u672A\u5728\u5BA2\u6237\u7AEF cards map \u5BFC\u51FA`
|
|
1645
|
-
});
|
|
1646
|
-
}
|
|
1647
|
-
const missingServer = declared.filter((t) => !mod.serverRegistered.includes(t));
|
|
1648
|
-
if (missingServer.length > 0) {
|
|
1649
|
-
issues.push({
|
|
1650
|
-
rule: "card-type-consistent",
|
|
1651
|
-
level: "error",
|
|
1652
|
-
message: `${mod.id}: \u58F0\u660E\u5361\u7247 ${missingServer.join(",")} \u672A\u5728\u670D\u52A1\u7AEF\u6CE8\u518C\uFF08serverModule.cards meta / cards.register\uFF09`
|
|
1653
|
-
});
|
|
1654
|
-
}
|
|
1655
|
-
const extraClient = mod.clientMap.filter((t) => !declared.includes(t));
|
|
1656
|
-
if (extraClient.length > 0) {
|
|
1657
|
-
issues.push({
|
|
1658
|
-
rule: "card-type-consistent",
|
|
1659
|
-
level: "warning",
|
|
1660
|
-
message: `${mod.id}: \u5BA2\u6237\u7AEF\u5BFC\u51FA\u672A\u58F0\u660E\u7684\u5361\u7247 ${extraClient.join(",")}`
|
|
1661
|
-
});
|
|
1662
|
-
}
|
|
1663
|
-
const extraServer = mod.serverRegistered.filter((t) => !declared.includes(t));
|
|
1664
|
-
if (extraServer.length > 0) {
|
|
1665
|
-
issues.push({
|
|
1666
|
-
rule: "card-type-consistent",
|
|
1667
|
-
level: "warning",
|
|
1668
|
-
message: `${mod.id}: \u670D\u52A1\u7AEF\u6CE8\u518C\u672A\u58F0\u660E\u7684\u5361\u7247 ${extraServer.join(",")}`
|
|
1669
|
-
});
|
|
1670
|
-
}
|
|
1671
|
-
}
|
|
1672
|
-
return issues;
|
|
1673
|
-
}
|
|
1674
|
-
async function clientCardCoverRule(ctx) {
|
|
1675
|
-
const issues = [];
|
|
1676
|
-
const mods = await collectModuleCards(ctx);
|
|
1677
|
-
const registered = {};
|
|
1678
|
-
for (const mod of mods) {
|
|
1679
|
-
for (const t of mod.serverRegistered) {
|
|
1680
|
-
if (t in registered) registered[t] += `,${mod.id}`;
|
|
1681
|
-
else registered[t] = mod.id;
|
|
1682
|
-
}
|
|
1683
|
-
}
|
|
1684
|
-
if (Object.keys(registered).length === 0) return issues;
|
|
1685
|
-
const assembled = /* @__PURE__ */ new Set();
|
|
1686
|
-
for (const mod of mods) {
|
|
1687
|
-
for (const t of mod.clientMap) assembled.add(t);
|
|
1688
|
-
}
|
|
1689
|
-
for (const [cardType, moduleIds] of Object.entries(registered)) {
|
|
1690
|
-
if (!assembled.has(cardType)) {
|
|
1691
|
-
issues.push({
|
|
1692
|
-
rule: "client-card-cover",
|
|
1693
|
-
level: "error",
|
|
1694
|
-
message: `\u670D\u52A1\u7AEF\u6CE8\u518C\u5361\u7247 ${cardType}\uFF08\u6A21\u5757 ${moduleIds}\uFF09\u672A\u51FA\u73B0\u5728\u5E94\u7528\u5BA2\u6237\u7AEF\u88C5\u914D\uFF08\u6F0F\u88C5\u914D\uFF09`
|
|
1695
|
-
});
|
|
1696
|
-
}
|
|
1697
|
-
}
|
|
1698
|
-
return issues;
|
|
1699
|
-
}
|
|
1700
|
-
|
|
1701
|
-
// src/doctor/rules/contract-version.ts
|
|
1702
|
-
import { existsSync as existsSync11 } from "fs";
|
|
1703
|
-
import { readdir as readdir6, stat as stat2 } from "fs/promises";
|
|
1704
|
-
import path15 from "path";
|
|
1705
|
-
async function contractVersionRule(ctx) {
|
|
1706
|
-
const issues = [];
|
|
1707
|
-
const packagesDir = path15.join(ctx.appDir, "packages");
|
|
1708
|
-
if (!existsSync11(packagesDir)) return issues;
|
|
1709
|
-
const installed = /* @__PURE__ */ new Map();
|
|
1710
|
-
for (const entry of await readdir6(packagesDir, { withFileTypes: true })) {
|
|
1711
|
-
if (!entry.isDirectory()) continue;
|
|
1712
|
-
const pkg = readPkgJson(path15.join(packagesDir, entry.name));
|
|
1713
|
-
if (pkg?.name && pkg.name.startsWith("@tbox.cn/app-contracts")) {
|
|
1714
|
-
installed.set(pkg.name, pkg.version ?? "0.0.0");
|
|
1715
|
-
}
|
|
1716
|
-
}
|
|
1717
|
-
const nmScope = path15.join(ctx.appDir, "node_modules", "@tbox.cn");
|
|
1718
|
-
if (existsSync11(nmScope)) {
|
|
1719
|
-
for (const entry of await readdir6(nmScope, { withFileTypes: true })) {
|
|
1720
|
-
const full = path15.join(nmScope, entry.name);
|
|
1721
|
-
let isDir = entry.isDirectory();
|
|
1722
|
-
if (!isDir && entry.isSymbolicLink()) {
|
|
1723
|
-
try {
|
|
1724
|
-
isDir = (await stat2(full)).isDirectory();
|
|
1725
|
-
} catch {
|
|
1726
|
-
isDir = false;
|
|
1727
|
-
}
|
|
1728
|
-
}
|
|
1729
|
-
if (!isDir) continue;
|
|
1730
|
-
const pkg = readPkgJson(full);
|
|
1731
|
-
if (pkg?.name && pkg.name.startsWith("@tbox.cn/app-contracts")) {
|
|
1732
|
-
installed.set(pkg.name, pkg.version ?? "0.0.0");
|
|
1733
|
-
}
|
|
1734
|
-
}
|
|
1735
|
-
}
|
|
1736
|
-
for (const entry of await readdir6(packagesDir, { withFileTypes: true })) {
|
|
1737
|
-
if (!entry.isDirectory()) continue;
|
|
1738
|
-
const dir = path15.join(packagesDir, entry.name);
|
|
1739
|
-
if (!existsSync11(path15.join(dir, "tbox.component.json"))) continue;
|
|
1740
|
-
const pkg = readPkgJson(dir);
|
|
1741
|
-
const allDeps = { ...pkg?.dependencies ?? {}, ...pkg?.peerDependencies ?? {} };
|
|
1742
|
-
for (const [name, range] of Object.entries(allDeps)) {
|
|
1743
|
-
if (!name.startsWith("@tbox.cn/app-contracts")) continue;
|
|
1744
|
-
const installedVersion = installed.get(name);
|
|
1745
|
-
if (!installedVersion) continue;
|
|
1746
|
-
if (range === "*" || range.startsWith("workspace:") || range.startsWith("catalog:")) continue;
|
|
1747
|
-
const cleanRange = range.replace(/^\^/, "");
|
|
1748
|
-
if (cleanRange === installedVersion) continue;
|
|
1749
|
-
const inter = intersectRanges2(range, installedVersion);
|
|
1750
|
-
if (!inter) {
|
|
1751
|
-
issues.push({
|
|
1752
|
-
rule: "contract-version",
|
|
1753
|
-
level: "error",
|
|
1754
|
-
message: `${entry.name}: \u5951\u7EA6 ${name} \u8303\u56F4 ${range} \u4E0E\u5B9E\u88C5 ${installedVersion} \u65E0\u4EA4\u96C6`
|
|
1755
|
-
});
|
|
1756
|
-
}
|
|
1757
|
-
}
|
|
1758
|
-
}
|
|
1759
|
-
return issues;
|
|
1760
|
-
}
|
|
1761
|
-
|
|
1762
|
-
// src/doctor/rules/module-deps-assembled.ts
|
|
1763
|
-
import { existsSync as existsSync12 } from "fs";
|
|
1764
|
-
import { readdir as readdir7 } from "fs/promises";
|
|
1765
|
-
import path16 from "path";
|
|
1766
|
-
async function moduleDepsAssembledRule(ctx) {
|
|
1767
|
-
const issues = [];
|
|
1768
|
-
const installedIds = /* @__PURE__ */ new Set();
|
|
1769
|
-
const packagesDir = path16.join(ctx.appDir, "packages");
|
|
1770
|
-
if (existsSync12(packagesDir)) {
|
|
1771
|
-
for (const entry of await readdir7(packagesDir, { withFileTypes: true })) {
|
|
1772
|
-
if (entry.isDirectory()) installedIds.add(entry.name);
|
|
1773
|
-
}
|
|
1774
|
-
}
|
|
1775
|
-
for (const m of ctx.declared) installedIds.add(m.id);
|
|
1776
|
-
const assembledIds = /* @__PURE__ */ new Set();
|
|
1777
|
-
for (const file of ASSEMBLY_FILES) {
|
|
1778
|
-
for (const id of ctx.actual[file.kind]?.moduleIds ?? []) assembledIds.add(id);
|
|
1779
|
-
}
|
|
1780
|
-
const depGraph = /* @__PURE__ */ new Map();
|
|
1781
|
-
const localMods = await listLocalModules(ctx.appDir);
|
|
1782
|
-
for (const mod of localMods) {
|
|
1783
|
-
const deps = (mod.descriptor.dependencies?.modules ?? []).map((d) => d.id);
|
|
1784
|
-
depGraph.set(mod.id, deps);
|
|
1785
|
-
}
|
|
1786
|
-
const WHITE = 0;
|
|
1787
|
-
const GRAY = 1;
|
|
1788
|
-
const BLACK = 2;
|
|
1789
|
-
const color = /* @__PURE__ */ new Map();
|
|
1790
|
-
const cycleStacks = [];
|
|
1791
|
-
function dfs(id, stack) {
|
|
1792
|
-
color.set(id, GRAY);
|
|
1793
|
-
stack.push(id);
|
|
1794
|
-
for (const dep of depGraph.get(id) ?? []) {
|
|
1795
|
-
const c = color.get(dep) ?? WHITE;
|
|
1796
|
-
if (c === GRAY) {
|
|
1797
|
-
const idx = stack.indexOf(dep);
|
|
1798
|
-
if (idx >= 0) cycleStacks.push([...stack.slice(idx), dep]);
|
|
1799
|
-
} else if (c === WHITE) {
|
|
1800
|
-
dfs(dep, stack);
|
|
1801
|
-
}
|
|
1802
|
-
}
|
|
1803
|
-
stack.pop();
|
|
1804
|
-
color.set(id, BLACK);
|
|
1805
|
-
}
|
|
1806
|
-
for (const id of depGraph.keys()) {
|
|
1807
|
-
if ((color.get(id) ?? WHITE) === WHITE) dfs(id, []);
|
|
1808
|
-
}
|
|
1809
|
-
for (const cycle of cycleStacks) {
|
|
1810
|
-
issues.push({
|
|
1811
|
-
rule: "module-deps-assembled",
|
|
1812
|
-
level: "error",
|
|
1813
|
-
message: `dependencies.modules \u58F0\u660E\u5C42\u5B58\u5728\u5FAA\u73AF\u4F9D\u8D56\uFF1A${cycle.join(" \u2192 ")}`
|
|
1814
|
-
});
|
|
1815
|
-
}
|
|
1816
|
-
for (const mod of localMods) {
|
|
1817
|
-
for (const dep of mod.descriptor.dependencies?.modules ?? []) {
|
|
1818
|
-
if (dep.required && !installedIds.has(dep.id)) {
|
|
1819
|
-
issues.push({
|
|
1820
|
-
rule: "module-deps-assembled",
|
|
1821
|
-
level: "error",
|
|
1822
|
-
message: `${mod.id}: \u4F9D\u8D56\u6A21\u5757 ${dep.id} \u672A\u5B89\u88C5\uFF08\u7F3A\u5931 packages/${dep.id} \u6216 npm \u6E05\u5355\uFF09`
|
|
1823
|
-
});
|
|
1824
|
-
continue;
|
|
1825
|
-
}
|
|
1826
|
-
const depIsBusiness = localMods.some((m) => m.id === dep.id);
|
|
1827
|
-
if (depIsBusiness && dep.required && !assembledIds.has(dep.id)) {
|
|
1828
|
-
issues.push({
|
|
1829
|
-
rule: "module-deps-assembled",
|
|
1830
|
-
level: "error",
|
|
1831
|
-
message: `${mod.id}: \u4F9D\u8D56\u4E1A\u52A1\u6A21\u5757 ${dep.id} \u5DF2\u5B89\u88C5\u4F46\u672A\u88C5\u914D\uFF08modules.ts \u7F3A\u884C\uFF0C\u8FD0\u884C sync\uFF09`
|
|
1832
|
-
});
|
|
1833
|
-
}
|
|
1834
|
-
}
|
|
1835
|
-
}
|
|
1836
|
-
return issues;
|
|
1837
|
-
}
|
|
1838
|
-
|
|
1839
|
-
// src/doctor/rules/singleton.ts
|
|
1840
|
-
import { existsSync as existsSync13 } from "fs";
|
|
1841
|
-
import { readdir as readdir8 } from "fs/promises";
|
|
1842
|
-
import { createRequire } from "module";
|
|
1843
|
-
import { realpathSync as realpathSync2 } from "fs";
|
|
1844
|
-
import path17 from "path";
|
|
1845
|
-
async function singletonRule(ctx) {
|
|
1846
|
-
const issues = [];
|
|
1847
|
-
const candidates = ["react", "zod", "@mastra/core"];
|
|
1848
|
-
const dirs = ["apps/client", "apps/server", "packages/app-sdk", "packages/contracts"];
|
|
1849
|
-
const packagesDir = path17.join(ctx.appDir, "packages");
|
|
1850
|
-
if (existsSync13(packagesDir)) {
|
|
1851
|
-
for (const entry of await readdir8(packagesDir, { withFileTypes: true })) {
|
|
1852
|
-
if (entry.isDirectory() && existsSync13(path17.join(packagesDir, entry.name, "package.json"))) {
|
|
1853
|
-
dirs.push(path17.join("packages", entry.name));
|
|
1854
|
-
}
|
|
1855
|
-
}
|
|
1856
|
-
}
|
|
1857
|
-
for (const name of candidates) {
|
|
1858
|
-
const seen = /* @__PURE__ */ new Map();
|
|
1859
|
-
for (const rel of dirs) {
|
|
1860
|
-
const dir = path17.join(ctx.appDir, rel);
|
|
1861
|
-
if (!existsSync13(path17.join(dir, "package.json"))) continue;
|
|
1862
|
-
const resolved = resolveFrom(dir, name);
|
|
1863
|
-
if (resolved) {
|
|
1864
|
-
const list = seen.get(resolved) ?? [];
|
|
1865
|
-
list.push(rel);
|
|
1866
|
-
seen.set(resolved, list);
|
|
1867
|
-
}
|
|
1868
|
-
}
|
|
1869
|
-
if (seen.size > 1) {
|
|
1870
|
-
issues.push({
|
|
1871
|
-
rule: "singleton",
|
|
1872
|
-
level: "error",
|
|
1873
|
-
message: `${name} \u89E3\u6790\u7ED3\u679C\u4E0D\u552F\u4E00\uFF1A${[...seen.entries()].map(([p, dirs2]) => `${p}(${dirs2.join(",")})`).join(" vs ")}`
|
|
1874
|
-
});
|
|
1875
|
-
}
|
|
1876
|
-
}
|
|
1877
|
-
return issues;
|
|
1878
|
-
}
|
|
1879
|
-
function resolveFrom(dir, name) {
|
|
1880
|
-
try {
|
|
1881
|
-
const req = createRequire(path17.join(dir, "__noop__.js"));
|
|
1882
|
-
return realpathSync2(req.resolve(name));
|
|
1883
|
-
} catch {
|
|
1884
|
-
return null;
|
|
1885
|
-
}
|
|
1886
|
-
}
|
|
1887
|
-
|
|
1888
|
-
// src/doctor/rules/sdk-internal-import.ts
|
|
1889
|
-
import { existsSync as existsSync14 } from "fs";
|
|
1890
|
-
import path18 from "path";
|
|
1891
|
-
var SDK_PUBLIC_ENTRIES = /* @__PURE__ */ new Set([
|
|
1892
|
-
"@tbox.cn/app-sdk",
|
|
1893
|
-
"@tbox.cn/app-sdk/server",
|
|
1894
|
-
"@tbox.cn/app-sdk/client",
|
|
1895
|
-
"@tbox.cn/app-sdk/platform"
|
|
1896
|
-
]);
|
|
1897
|
-
var FRAMEWORK_INTERNAL_PATTERNS = [
|
|
1898
|
-
{ re: /^@mastra\/core/, label: "@mastra/core*\uFF08\u6846\u67B6\u5185\u90E8\uFF0C\u6539\u7ECF @tbox.cn/app-sdk\uFF09" },
|
|
1899
|
-
{ re: /^@ag-ui\//, label: "@ag-ui/*\uFF08\u6846\u67B6\u5185\u90E8\uFF0C\u6539\u7ECF @tbox.cn/app-sdk\uFF09" },
|
|
1900
|
-
{ re: /^@ai-sdk\//, label: "@ai-sdk/*\uFF08\u6846\u67B6\u5185\u90E8\uFF09" },
|
|
1901
|
-
{ re: /^ai$/, label: "ai\uFF08\u6846\u67B6\u5185\u90E8\uFF09" },
|
|
1902
|
-
{ re: /^zustand$/, label: "zustand\uFF08\u6846\u67B6\u5185\u90E8\uFF09" }
|
|
1903
|
-
];
|
|
1904
|
-
async function sdkInternalImportRule(ctx) {
|
|
1905
|
-
const issues = [];
|
|
1906
|
-
const roots = ["apps", "packages"];
|
|
1907
|
-
for (const root of roots) {
|
|
1908
|
-
const rootDir = path18.join(ctx.appDir, root);
|
|
1909
|
-
if (!existsSync14(rootDir)) continue;
|
|
1910
|
-
const files = [];
|
|
1911
|
-
await walkFiles(rootDir, ctx.appDir, files);
|
|
1912
|
-
for (const rel of files) {
|
|
1913
|
-
if (rel.startsWith("packages/app-sdk/") || rel.startsWith("packages/contracts/")) continue;
|
|
1914
|
-
const src = await readTextOrEmpty(path18.join(ctx.appDir, rel));
|
|
1915
|
-
for (const spec of src.matchAll(/from\s+['"]([^'"]+)['"]/g)) {
|
|
1916
|
-
const target = spec[1];
|
|
1917
|
-
if (target.startsWith("@tbox.cn/app-sdk/") && !SDK_PUBLIC_ENTRIES.has(target)) {
|
|
1918
|
-
issues.push({
|
|
1919
|
-
rule: "sdk-internal-import",
|
|
1920
|
-
level: "error",
|
|
1921
|
-
message: `${rel}: \u4E0D\u5141\u8BB8 import SDK \u5185\u90E8\u8DEF\u5F84 ${target}`
|
|
1922
|
-
});
|
|
1923
|
-
} else if (target.startsWith("@tbox.cn/app-contracts/")) {
|
|
1924
|
-
issues.push({
|
|
1925
|
-
rule: "sdk-internal-import",
|
|
1926
|
-
level: "error",
|
|
1927
|
-
message: `${rel}: \u4E0D\u5141\u8BB8 import \u5951\u7EA6\u5B50\u8DEF\u5F84 ${target}\uFF08\u4EC5 @tbox.cn/app-contracts\uFF09`
|
|
1928
|
-
});
|
|
1929
|
-
} else if (isModuleFile(ctx.appDir, rel)) {
|
|
1930
|
-
for (const pat of FRAMEWORK_INTERNAL_PATTERNS) {
|
|
1931
|
-
if (pat.re.test(target)) {
|
|
1932
|
-
issues.push({
|
|
1933
|
-
rule: "sdk-internal-import",
|
|
1934
|
-
level: "error",
|
|
1935
|
-
message: `${rel}: \u6A21\u5757\u4E0D\u5141\u8BB8\u76F4\u63A5 import ${pat.label}`
|
|
1936
|
-
});
|
|
1937
|
-
}
|
|
1938
|
-
}
|
|
1939
|
-
}
|
|
1940
|
-
}
|
|
1941
|
-
}
|
|
1942
|
-
}
|
|
1943
|
-
return issues;
|
|
1944
|
-
}
|
|
1945
|
-
function isModuleFile(appDir, rel) {
|
|
1946
|
-
const m = rel.match(/^packages\/([^/]+)\//);
|
|
1947
|
-
if (!m) return false;
|
|
1948
|
-
return existsSync14(path18.join(appDir, "packages", m[1], "tbox.component.json"));
|
|
1949
|
-
}
|
|
1950
|
-
|
|
1951
|
-
// src/doctor/rules/assembly-drift.ts
|
|
1952
|
-
function assemblyDriftRule(ctx) {
|
|
1953
|
-
const issues = [];
|
|
1954
|
-
for (const file of ASSEMBLY_FILES) {
|
|
1955
|
-
const declaredIds = new Set(
|
|
1956
|
-
ctx.declared.filter((m) => contributesTo(file.kind, m)).map((m) => m.id)
|
|
1957
|
-
);
|
|
1958
|
-
const actualIds = new Set(ctx.actual[file.kind]?.moduleIds ?? []);
|
|
1959
|
-
const missing = [...declaredIds].filter((id) => !actualIds.has(id));
|
|
1960
|
-
const extra = [...actualIds].filter((id) => !declaredIds.has(id));
|
|
1961
|
-
if (missing.length > 0) {
|
|
1962
|
-
issues.push({
|
|
1963
|
-
rule: "assembly-drift",
|
|
1964
|
-
level: "warning",
|
|
1965
|
-
message: `${file.path}: \u5DF2\u58F0\u660E\u672A\u88C5\u914D \u2192 ${missing.join(", ")}`
|
|
1966
|
-
});
|
|
1967
|
-
}
|
|
1968
|
-
if (extra.length > 0) {
|
|
1969
|
-
issues.push({
|
|
1970
|
-
rule: "assembly-drift",
|
|
1971
|
-
level: "warning",
|
|
1972
|
-
message: `${file.path}: \u5DF2\u88C5\u914D\u672A\u58F0\u660E \u2192 ${extra.join(", ")}`
|
|
1973
|
-
});
|
|
1974
|
-
}
|
|
1975
|
-
const broken = ctx.actual[file.kind]?.broken ?? [];
|
|
1976
|
-
if (broken.length > 0) {
|
|
1977
|
-
issues.push({
|
|
1978
|
-
rule: "assembly-drift",
|
|
1979
|
-
level: "warning",
|
|
1980
|
-
message: `${file.path}: \u951A\u533A\u7834\u574F \u2192 ${broken.join(", ")}`
|
|
1981
|
-
});
|
|
1982
|
-
}
|
|
1983
|
-
}
|
|
1984
|
-
return issues;
|
|
1985
|
-
}
|
|
1986
|
-
|
|
1987
|
-
// src/doctor/rules/schema-version.ts
|
|
1988
|
-
async function schemaVersionRule(ctx) {
|
|
1989
|
-
const issues = [];
|
|
1990
|
-
for (const mod of await collectModuleCards(ctx)) {
|
|
1991
|
-
for (const [cardType, declaredSv] of Object.entries(mod.declared)) {
|
|
1992
|
-
const serverSv = mod.serverSchemaVersions[cardType] ?? 1;
|
|
1993
|
-
if (serverSv !== declaredSv) {
|
|
1994
|
-
issues.push({
|
|
1995
|
-
rule: "schema-version",
|
|
1996
|
-
level: "error",
|
|
1997
|
-
message: `${mod.id}: \u5361\u7247 ${cardType} contributes.schemaVersion=${declaredSv} \u4E0E meta schemaVersion=${serverSv} \u4E0D\u4E00\u81F4`
|
|
1998
|
-
});
|
|
1999
|
-
}
|
|
2000
|
-
}
|
|
2001
|
-
}
|
|
2002
|
-
return issues;
|
|
2003
|
-
}
|
|
2004
|
-
|
|
2005
|
-
// src/doctor/rules/naked-write-route.ts
|
|
2006
|
-
import { existsSync as existsSync15 } from "fs";
|
|
2007
|
-
import path19 from "path";
|
|
2008
|
-
var ALLOWED_POST_PREFIXES = ["/webhook", "/upload", "/demo", "/run"];
|
|
2009
|
-
async function nakedWriteRouteRule(ctx) {
|
|
2010
|
-
const issues = [];
|
|
2011
|
-
for (const mod of await listLocalModules(ctx.appDir)) {
|
|
2012
|
-
const serverDir = path19.join(mod.dir, "src/server");
|
|
2013
|
-
if (!existsSync15(serverDir)) continue;
|
|
2014
|
-
const files = [];
|
|
2015
|
-
await walkFiles(serverDir, mod.dir, files);
|
|
2016
|
-
for (const rel of files) {
|
|
2017
|
-
const src = await readTextOrEmpty(path19.join(mod.dir, rel));
|
|
2018
|
-
for (const m of src.matchAll(/\.(?:post|put|patch|delete)\(\s*['"]([^'"]+)['"]/g)) {
|
|
2019
|
-
const route = m[1];
|
|
2020
|
-
if (!route.startsWith("/")) continue;
|
|
2021
|
-
const allowed = ALLOWED_POST_PREFIXES.some((p) => route.startsWith(p));
|
|
2022
|
-
if (!allowed) {
|
|
2023
|
-
issues.push({
|
|
2024
|
-
rule: "naked-write-route",
|
|
2025
|
-
level: "warning",
|
|
2026
|
-
message: `${mod.id}/${rel}: \u88F8\u5199\u8DEF\u7531 ${m[0].replace("(", " (")} \u5EFA\u8BAE\u6539\u8D70 ctx.actions.registerAction\uFF08\u5168\u5C40\u52A8\u4F5C\u5206\u53D1\u5668\uFF09`
|
|
2027
|
-
});
|
|
2028
|
-
}
|
|
2029
|
-
}
|
|
2030
|
-
}
|
|
2031
|
-
}
|
|
2032
|
-
return issues;
|
|
2033
|
-
}
|
|
2034
|
-
|
|
2035
|
-
// src/doctor/rules/descriptor-schema.ts
|
|
2036
|
-
import { existsSync as existsSync16 } from "fs";
|
|
2037
|
-
import { readdir as readdir9 } from "fs/promises";
|
|
2038
|
-
import path20 from "path";
|
|
2039
|
-
async function descriptorSchemaRule(ctx) {
|
|
2040
|
-
const issues = [];
|
|
2041
|
-
const packagesDir = path20.join(ctx.appDir, "packages");
|
|
2042
|
-
if (!existsSync16(packagesDir)) return issues;
|
|
2043
|
-
for (const entry of await readdir9(packagesDir, { withFileTypes: true })) {
|
|
2044
|
-
if (!entry.isDirectory()) continue;
|
|
2045
|
-
const file = path20.join(packagesDir, entry.name, "tbox.component.json");
|
|
2046
|
-
if (!existsSync16(file)) continue;
|
|
2047
|
-
try {
|
|
2048
|
-
const parsed = parseComponentDescriptor(JSON.parse(await readTextOrEmpty(file)));
|
|
2049
|
-
if (!parsed.ok) {
|
|
2050
|
-
issues.push({
|
|
2051
|
-
rule: "descriptor-schema",
|
|
2052
|
-
level: "error",
|
|
2053
|
-
message: `${entry.name}/tbox.component.json: ${parsed.errors.join("; ")}`
|
|
2054
|
-
});
|
|
2055
|
-
}
|
|
2056
|
-
} catch {
|
|
2057
|
-
issues.push({
|
|
2058
|
-
rule: "descriptor-schema",
|
|
2059
|
-
level: "error",
|
|
2060
|
-
message: `${entry.name}/tbox.component.json: JSON \u89E3\u6790\u5931\u8D25`
|
|
2061
|
-
});
|
|
2062
|
-
}
|
|
2063
|
-
}
|
|
2064
|
-
return issues;
|
|
2065
|
-
}
|
|
2066
|
-
|
|
2067
|
-
// src/doctor/rules/workspace-ref-resolved.ts
|
|
2068
|
-
import { existsSync as existsSync17 } from "fs";
|
|
2069
|
-
import { readdir as readdir10 } from "fs/promises";
|
|
2070
|
-
import path21 from "path";
|
|
2071
|
-
async function workspaceRefResolvedRule(ctx) {
|
|
2072
|
-
const issues = [];
|
|
2073
|
-
if (existsSync17(path21.join(ctx.appDir, "pnpm-workspace.template.yaml"))) return issues;
|
|
2074
|
-
const memberIndex = /* @__PURE__ */ new Map();
|
|
2075
|
-
for (const root of ["apps", "packages"]) {
|
|
2076
|
-
const rootDir = path21.join(ctx.appDir, root);
|
|
2077
|
-
if (!existsSync17(rootDir)) continue;
|
|
2078
|
-
for (const entry of await readdir10(rootDir, { withFileTypes: true })) {
|
|
2079
|
-
if (!entry.isDirectory()) continue;
|
|
2080
|
-
const dir = path21.join(rootDir, entry.name);
|
|
2081
|
-
const pkg = readPkgJson(dir);
|
|
2082
|
-
if (pkg?.name) memberIndex.set(pkg.name, path21.join(root, entry.name));
|
|
2083
|
-
}
|
|
2084
|
-
}
|
|
2085
|
-
if (memberIndex.size === 0) return issues;
|
|
2086
|
-
for (const [name, rel] of memberIndex) {
|
|
2087
|
-
const pkg = readPkgJson(path21.join(ctx.appDir, rel));
|
|
2088
|
-
if (!pkg) continue;
|
|
2089
|
-
for (const key of ["dependencies", "peerDependencies", "devDependencies"]) {
|
|
2090
|
-
const deps = pkg[key];
|
|
2091
|
-
if (!deps || typeof deps !== "object") continue;
|
|
2092
|
-
for (const [depName, spec] of Object.entries(deps)) {
|
|
2093
|
-
if (typeof spec !== "string" || !spec.startsWith("workspace:")) continue;
|
|
2094
|
-
if (!memberIndex.has(depName)) {
|
|
2095
|
-
issues.push({
|
|
2096
|
-
rule: "workspace-ref-resolved",
|
|
2097
|
-
level: "error",
|
|
2098
|
-
message: `${rel}: workspace:* \u5F15\u7528 ${depName} \u672A\u547D\u4E2D\u4EFB\u4F55\u6210\u5458\uFF08packages/ \u6216 apps/\uFF09`
|
|
2099
|
-
});
|
|
2100
|
-
}
|
|
2101
|
-
}
|
|
2102
|
-
}
|
|
2103
|
-
}
|
|
2104
|
-
return issues;
|
|
2105
|
-
}
|
|
2106
|
-
|
|
2107
|
-
// src/doctor/rules/legacy-platform-files.ts
|
|
2108
|
-
import { existsSync as existsSync18 } from "fs";
|
|
2109
|
-
import path22 from "path";
|
|
2110
|
-
var LEGACY_PATHS = [
|
|
2111
|
-
"apps/server/src/auth",
|
|
2112
|
-
"apps/server/src/agent",
|
|
2113
|
-
"apps/server/src/gateway",
|
|
2114
|
-
"apps/server/src/http",
|
|
2115
|
-
"apps/server/src/plugins/integration.ts",
|
|
2116
|
-
"apps/server/src/config/tts-config.ts",
|
|
2117
|
-
"apps/server/src/config/model-providers.ts",
|
|
2118
|
-
"apps/client/src/env",
|
|
2119
|
-
"apps/client/src/bridge",
|
|
2120
|
-
"apps/client/src/adapters",
|
|
2121
|
-
"apps/client/src/services/http.ts",
|
|
2122
|
-
"apps/client/src/services/auth-client.ts",
|
|
2123
|
-
"apps/client/src/services/user-id.ts",
|
|
2124
|
-
"apps/client/src/services/tbox-session.ts"
|
|
2125
|
-
];
|
|
2126
|
-
function legacyPlatformFilesRule(ctx) {
|
|
2127
|
-
const issues = [];
|
|
2128
|
-
for (const rel of LEGACY_PATHS) {
|
|
2129
|
-
if (existsSync18(path22.join(ctx.appDir, rel))) {
|
|
2130
|
-
issues.push({
|
|
2131
|
-
rule: "legacy-platform-files",
|
|
2132
|
-
level: "error",
|
|
2133
|
-
message: `${rel} \u4E3A\u65E7\u5F62\u6001\u5E73\u53F0\u6587\u4EF6\uFF08007\uFF1A\u5E73\u53F0\u80FD\u529B\u5DF2\u4E0B\u6C89 @tbox.cn/app-sdk\uFF09\u3002\u8BF7\u5220\u9664\u5E76\u6539\u7528 SDK API\uFF08server: createAuthRuntime/createPlatformApiRouter/createTtsProxy/createModelProvider/createPluginIntegration\uFF1Bclient: createAuthClient/createPlatformAdapters/createPlatformApi/configureRuntime\uFF09\uFF0C\u8BE6\u89C1 docs/proposals/007\u3002`
|
|
2134
|
-
});
|
|
2135
|
-
}
|
|
2136
|
-
}
|
|
2137
|
-
return issues;
|
|
2138
|
-
}
|
|
2139
|
-
|
|
2140
|
-
// src/doctor/rules/mall-deployment.ts
|
|
2141
|
-
import { existsSync as existsSync19, readFileSync as readFileSync8 } from "fs";
|
|
2142
|
-
import { readdir as readdir11 } from "fs/promises";
|
|
2143
|
-
import { createRequire as createRequire2 } from "module";
|
|
2144
|
-
import path23 from "path";
|
|
2145
|
-
function resolveModuleRoot(ctx, pkg) {
|
|
2146
|
-
try {
|
|
2147
|
-
const req = createRequire2(path23.join(ctx.appDir, "package.json"));
|
|
2148
|
-
const resolved = req.resolve(`${pkg}/package.json`);
|
|
2149
|
-
return path23.dirname(resolved);
|
|
2150
|
-
} catch {
|
|
2151
|
-
return void 0;
|
|
2152
|
-
}
|
|
2153
|
-
}
|
|
2154
|
-
function readServerSource2(moduleRoot) {
|
|
2155
|
-
const candidates = [
|
|
2156
|
-
path23.join(moduleRoot, "src/server/index.ts"),
|
|
2157
|
-
path23.join(moduleRoot, "src/server/index.tsx")
|
|
2158
|
-
];
|
|
2159
|
-
for (const c of candidates) {
|
|
2160
|
-
if (existsSync19(c)) return readFileSync8(c, "utf8");
|
|
2161
|
-
}
|
|
2162
|
-
return "";
|
|
2163
|
-
}
|
|
2164
|
-
function extractFeatures(source) {
|
|
2165
|
-
const match = source.match(/features\s*:\s*\[([^\]]*)\]/);
|
|
2166
|
-
if (!match) return [];
|
|
2167
|
-
return [...match[1].matchAll(/['"]([^'"]+)['"]/g)].map((m) => m[1]);
|
|
2168
|
-
}
|
|
2169
|
-
function extractIntentPatterns(source) {
|
|
2170
|
-
const patterns = [];
|
|
2171
|
-
for (const block of source.matchAll(/patterns\s*:\s*\[([^\]]*)\]/g)) {
|
|
2172
|
-
for (const m of block[1].matchAll(/['"]([^'"]+)['"]/g)) {
|
|
2173
|
-
patterns.push(m[1]);
|
|
2174
|
-
}
|
|
2175
|
-
}
|
|
2176
|
-
return patterns;
|
|
2177
|
-
}
|
|
2178
|
-
function extractFeatureRefs(source) {
|
|
2179
|
-
return [...source.matchAll(/isFeatureEnabled\s*\(\s*['"]([^'"]+)['"]\s*\)/g)].map((m) => m[1]);
|
|
2180
|
-
}
|
|
2181
|
-
async function deploymentConsistencyRule(ctx) {
|
|
2182
|
-
const issues = [];
|
|
2183
|
-
const deploymentDir = path23.join(ctx.appDir, "config/deployments");
|
|
2184
|
-
if (!existsSync19(deploymentDir)) return issues;
|
|
2185
|
-
const entries = await readdir11(deploymentDir);
|
|
2186
|
-
const jsonFiles = entries.filter((e) => e.endsWith(".json"));
|
|
2187
|
-
if (jsonFiles.length === 0) return issues;
|
|
2188
|
-
for (const file of jsonFiles) {
|
|
2189
|
-
let parsed;
|
|
2190
|
-
try {
|
|
2191
|
-
parsed = JSON.parse(readFileSync8(path23.join(deploymentDir, file), "utf8"));
|
|
2192
|
-
} catch {
|
|
2193
|
-
issues.push({ rule: "deployment-consistency", level: "error", message: `deployment \u914D\u7F6E\u975E\u6CD5 JSON\uFF1Aconfig/deployments/${file}` });
|
|
2194
|
-
continue;
|
|
2195
|
-
}
|
|
2196
|
-
const components = parsed.deployment?.components ?? [];
|
|
2197
|
-
const declaredPkgs = new Map(ctx.declared.map((d) => [d.componentId ?? d.id, d]));
|
|
2198
|
-
for (const component of components) {
|
|
2199
|
-
const componentId = component.componentId;
|
|
2200
|
-
if (!componentId) {
|
|
2201
|
-
issues.push({
|
|
2202
|
-
rule: "deployment-consistency",
|
|
2203
|
-
level: "error",
|
|
2204
|
-
message: `config/deployments/${file}\uFF1Acomponents \u9879\u7F3A componentId`
|
|
2205
|
-
});
|
|
2206
|
-
continue;
|
|
2207
|
-
}
|
|
2208
|
-
const declared = declaredPkgs.get(componentId);
|
|
2209
|
-
if (!declared) {
|
|
2210
|
-
issues.push({
|
|
2211
|
-
rule: "deployment-consistency",
|
|
2212
|
-
level: component.optional ? "warning" : "error",
|
|
2213
|
-
message: `config/deployments/${file}\uFF1AcomponentId "${componentId}" \u672A\u5B89\u88C5\u5BF9\u5E94\u6A21\u5757` + (component.optional ? "\uFF08optional \u7EC4\u4EF6\uFF0C\u964D\u7EA7 warning\uFF09" : "\uFF08\u5B89\u88C5\u5BF9\u5E94\u6A21\u5757\u6216\u6807\u8BB0 optional\uFF09")
|
|
2214
|
-
});
|
|
2215
|
-
continue;
|
|
2216
|
-
}
|
|
2217
|
-
const moduleRoot = resolveModuleRoot(ctx, declared.pkg);
|
|
2218
|
-
if (moduleRoot && component.enabledFeatures?.length) {
|
|
2219
|
-
const source = readServerSource2(moduleRoot);
|
|
2220
|
-
const declaredFeatures = extractFeatures(source);
|
|
2221
|
-
if (declaredFeatures.length > 0) {
|
|
2222
|
-
for (const feature of component.enabledFeatures) {
|
|
2223
|
-
if (!declaredFeatures.includes(feature)) {
|
|
2224
|
-
issues.push({
|
|
2225
|
-
rule: "deployment-consistency",
|
|
2226
|
-
level: "error",
|
|
2227
|
-
message: `config/deployments/${file}\uFF1Afeature "${feature}" \u4E0D\u5728 ${componentId} \u7684 declaration.features \u5185\uFF08\u5DF2\u58F0\u660E\uFF1A${declaredFeatures.join(", ") || "(\u65E0)"}\uFF09`
|
|
2228
|
-
});
|
|
2229
|
-
}
|
|
2230
|
-
}
|
|
2231
|
-
}
|
|
2232
|
-
}
|
|
2233
|
-
}
|
|
2234
|
-
}
|
|
2235
|
-
return issues;
|
|
2236
|
-
}
|
|
2237
|
-
async function intentOverlapRule(ctx) {
|
|
2238
|
-
const issues = [];
|
|
2239
|
-
const byPattern = /* @__PURE__ */ new Map();
|
|
2240
|
-
for (const declared of ctx.declared) {
|
|
2241
|
-
if (!declared.hasServerEntry) continue;
|
|
2242
|
-
const root = resolveModuleRoot(ctx, declared.pkg);
|
|
2243
|
-
if (!root) continue;
|
|
2244
|
-
const patterns = extractIntentPatterns(readServerSource2(root));
|
|
2245
|
-
for (const p of patterns) {
|
|
2246
|
-
const list = byPattern.get(p) ?? [];
|
|
2247
|
-
list.push(declared.id);
|
|
2248
|
-
byPattern.set(p, list);
|
|
2249
|
-
}
|
|
2250
|
-
}
|
|
2251
|
-
for (const [pattern, modules] of byPattern) {
|
|
2252
|
-
if (modules.length > 1) {
|
|
2253
|
-
issues.push({
|
|
2254
|
-
rule: "intent-overlap",
|
|
2255
|
-
level: "warning",
|
|
2256
|
-
message: `\u610F\u56FE\u8BCD "${pattern}" \u5728\u591A\u4E2A\u6A21\u5757\u58F0\u660E\uFF08${modules.join(", ")}\uFF09\u2014\u2014\u5148\u6CE8\u518C\u5148\u547D\u4E2D`
|
|
2257
|
-
});
|
|
2258
|
-
}
|
|
2259
|
-
}
|
|
2260
|
-
return issues;
|
|
2261
|
-
}
|
|
2262
|
-
async function skillNameConflictRule(ctx) {
|
|
2263
|
-
const issues = [];
|
|
2264
|
-
const byName = /* @__PURE__ */ new Map();
|
|
2265
|
-
for (const declared of ctx.declared) {
|
|
2266
|
-
if (!declared.hasServerEntry) continue;
|
|
2267
|
-
const root = resolveModuleRoot(ctx, declared.pkg);
|
|
2268
|
-
if (!root) continue;
|
|
2269
|
-
const skillsDir = path23.join(root, "skills");
|
|
2270
|
-
if (!existsSync19(skillsDir)) continue;
|
|
2271
|
-
try {
|
|
2272
|
-
const names = (await readdir11(skillsDir, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
2273
|
-
for (const name of names) {
|
|
2274
|
-
const list = byName.get(name) ?? [];
|
|
2275
|
-
list.push(declared.id);
|
|
2276
|
-
byName.set(name, list);
|
|
2277
|
-
}
|
|
2278
|
-
} catch {
|
|
2279
|
-
}
|
|
2280
|
-
}
|
|
2281
|
-
for (const [name, modules] of byName) {
|
|
2282
|
-
if (modules.length > 1) {
|
|
2283
|
-
issues.push({
|
|
2284
|
-
rule: "skill-name-conflict",
|
|
2285
|
-
level: "warning",
|
|
2286
|
-
message: `skill "${name}" \u5728\u591A\u4E2A\u6A21\u5757\u58F0\u660E\uFF08${modules.join(", ")}\uFF09\u2014\u2014\u540E\u6CE8\u518C\u8986\u76D6`
|
|
2287
|
-
});
|
|
2288
|
-
}
|
|
2289
|
-
}
|
|
2290
|
-
return issues;
|
|
2291
|
-
}
|
|
2292
|
-
async function featureDeclarationRule(ctx) {
|
|
2293
|
-
const issues = [];
|
|
2294
|
-
for (const declared of ctx.declared) {
|
|
2295
|
-
if (!declared.hasServerEntry) continue;
|
|
2296
|
-
const root = resolveModuleRoot(ctx, declared.pkg);
|
|
2297
|
-
if (!root) continue;
|
|
2298
|
-
const source = readServerSource2(root);
|
|
2299
|
-
if (!source) continue;
|
|
2300
|
-
const declaredFeatures = extractFeatures(source);
|
|
2301
|
-
const refs = extractFeatureRefs(source);
|
|
2302
|
-
for (const ref of refs) {
|
|
2303
|
-
if (declaredFeatures.length > 0 && !declaredFeatures.includes(ref)) {
|
|
2304
|
-
issues.push({
|
|
2305
|
-
rule: "feature-declaration",
|
|
2306
|
-
level: "error",
|
|
2307
|
-
message: `\u6A21\u5757 ${declared.id}\uFF1AisFeatureEnabled("${ref}") \u4E0D\u5728 declaration.features \u5185\uFF08\u5DF2\u58F0\u660E\uFF1A${declaredFeatures.join(", ") || "(\u65E0)"}\uFF09`
|
|
2308
|
-
});
|
|
2309
|
-
}
|
|
2310
|
-
}
|
|
2311
|
-
}
|
|
2312
|
-
return issues;
|
|
2313
|
-
}
|
|
2314
|
-
|
|
2315
|
-
// src/doctor/index.ts
|
|
2316
|
-
var DOCTOR_RULES = [
|
|
2317
|
-
{ name: "workspace-dag", run: workspaceDagRule },
|
|
2318
|
-
{ name: "register-pure", run: registerPureRule },
|
|
2319
|
-
{ name: "card-type-consistent", run: cardTypeConsistentRule },
|
|
2320
|
-
{ name: "client-card-cover", run: clientCardCoverRule },
|
|
2321
|
-
{ name: "contract-version", run: contractVersionRule },
|
|
2322
|
-
{ name: "module-deps-assembled", run: moduleDepsAssembledRule },
|
|
2323
|
-
{ name: "singleton", run: singletonRule },
|
|
2324
|
-
{ name: "sdk-internal-import", run: sdkInternalImportRule },
|
|
2325
|
-
{ name: "assembly-drift", run: assemblyDriftRule },
|
|
2326
|
-
{ name: "schema-version", run: schemaVersionRule },
|
|
2327
|
-
{ name: "naked-write-route", run: nakedWriteRouteRule },
|
|
2328
|
-
{ name: "descriptor-schema", run: descriptorSchemaRule },
|
|
2329
|
-
{ name: "workspace-ref-resolved", run: workspaceRefResolvedRule },
|
|
2330
|
-
{ name: "legacy-platform-files", run: legacyPlatformFilesRule },
|
|
2331
|
-
{ name: "deployment-consistency", run: deploymentConsistencyRule },
|
|
2332
|
-
{ name: "intent-overlap", run: intentOverlapRule },
|
|
2333
|
-
{ name: "skill-name-conflict", run: skillNameConflictRule },
|
|
2334
|
-
{ name: "feature-declaration", run: featureDeclarationRule }
|
|
2335
|
-
];
|
|
2336
|
-
async function runDoctor(ctx, opts) {
|
|
2337
|
-
const issues = [];
|
|
2338
|
-
const only = opts?.only?.length ? new Set(opts.only) : null;
|
|
2339
|
-
const skip = new Set(opts?.skip ?? []);
|
|
2340
|
-
for (const rule of DOCTOR_RULES) {
|
|
2341
|
-
if (only && !only.has(rule.name)) continue;
|
|
2342
|
-
if (skip.has(rule.name)) continue;
|
|
2343
|
-
try {
|
|
2344
|
-
issues.push(...await rule.run(ctx));
|
|
2345
|
-
} catch (err) {
|
|
2346
|
-
issues.push({
|
|
2347
|
-
rule: rule.name,
|
|
2348
|
-
level: "error",
|
|
2349
|
-
message: `\u89C4\u5219\u6267\u884C\u5F02\u5E38: ${err?.message ?? String(err)}`
|
|
2350
|
-
});
|
|
2351
|
-
}
|
|
2352
|
-
}
|
|
2353
|
-
return issues;
|
|
2354
|
-
}
|
|
2355
195
|
|
|
2356
|
-
|
|
2357
|
-
function isContractId(id, localIds) {
|
|
2358
|
-
return !localIds.has(id) || id.startsWith("contracts-");
|
|
2359
|
-
}
|
|
2360
|
-
async function resolveDependencies(appDir, rootIds, declared, extraRoots = []) {
|
|
2361
|
-
const mods = await listLocalModules(appDir);
|
|
2362
|
-
const localIds = new Set(mods.map((m) => m.id));
|
|
2363
|
-
const installedIds = new Set(declared?.map((m) => m.id) ?? []);
|
|
2364
|
-
const pkgVersion = /* @__PURE__ */ new Map();
|
|
2365
|
-
for (const mod of mods) {
|
|
2366
|
-
pkgVersion.set(mod.id, mod.descriptor.version ?? "0.0.0");
|
|
2367
|
-
}
|
|
2368
|
-
const extraDeps = new Map(extraRoots.map((r) => [r.id, r.deps]));
|
|
2369
|
-
const visited = /* @__PURE__ */ new Set();
|
|
2370
|
-
const edges = [];
|
|
2371
|
-
const queue = [...rootIds, ...extraRoots.map((r) => r.id)];
|
|
2372
|
-
while (queue.length > 0) {
|
|
2373
|
-
const id = queue.shift();
|
|
2374
|
-
if (visited.has(id)) continue;
|
|
2375
|
-
visited.add(id);
|
|
2376
|
-
const mod = mods.find((m) => m.id === id);
|
|
2377
|
-
const deps = extraDeps.get(id) ?? mod?.descriptor.dependencies?.modules ?? [];
|
|
2378
|
-
for (const dep of deps) {
|
|
2379
|
-
edges.push(dep);
|
|
2380
|
-
if (!visited.has(dep.id) && (localIds.has(dep.id) || extraDeps.has(dep.id))) queue.push(dep.id);
|
|
2381
|
-
}
|
|
2382
|
-
}
|
|
2383
|
-
const WHITE = 0;
|
|
2384
|
-
const GRAY = 1;
|
|
2385
|
-
const BLACK = 2;
|
|
2386
|
-
const color = /* @__PURE__ */ new Map();
|
|
2387
|
-
const cycles = [];
|
|
2388
|
-
function dfs(id, stack) {
|
|
2389
|
-
color.set(id, GRAY);
|
|
2390
|
-
stack.push(id);
|
|
2391
|
-
for (const dep of adjacency.get(id) ?? []) {
|
|
2392
|
-
const c = color.get(dep) ?? WHITE;
|
|
2393
|
-
if (c === GRAY) {
|
|
2394
|
-
const idx = stack.indexOf(dep);
|
|
2395
|
-
if (idx >= 0) cycles.push([...stack.slice(idx), dep]);
|
|
2396
|
-
} else if (c === WHITE) {
|
|
2397
|
-
dfs(dep, stack);
|
|
2398
|
-
}
|
|
2399
|
-
}
|
|
2400
|
-
stack.pop();
|
|
2401
|
-
color.set(id, BLACK);
|
|
2402
|
-
}
|
|
2403
|
-
const adjacency = /* @__PURE__ */ new Map();
|
|
2404
|
-
const modById = new Map(mods.map((m) => [m.id, m]));
|
|
2405
|
-
for (const id of visited) {
|
|
2406
|
-
const mod = modById.get(id);
|
|
2407
|
-
const deps = extraDeps.get(id) ?? mod?.descriptor.dependencies?.modules ?? [];
|
|
2408
|
-
adjacency.set(id, deps.filter((d) => visited.has(d.id)).map((d) => d.id));
|
|
2409
|
-
}
|
|
2410
|
-
for (const id of visited) {
|
|
2411
|
-
if ((color.get(id) ?? WHITE) === WHITE) dfs(id, []);
|
|
2412
|
-
}
|
|
2413
|
-
const toInstall = [...visited].filter((id) => !installedIds.has(id));
|
|
2414
|
-
const inDegree = /* @__PURE__ */ new Map();
|
|
2415
|
-
for (const id of toInstall) inDegree.set(id, 0);
|
|
2416
|
-
const dependents = /* @__PURE__ */ new Map();
|
|
2417
|
-
for (const id of toInstall) {
|
|
2418
|
-
for (const dep of adjacency.get(id) ?? []) {
|
|
2419
|
-
if (inDegree.has(dep)) {
|
|
2420
|
-
inDegree.set(id, (inDegree.get(id) ?? 0) + 1);
|
|
2421
|
-
const list = dependents.get(dep);
|
|
2422
|
-
if (list) list.push(id);
|
|
2423
|
-
else dependents.set(dep, [id]);
|
|
2424
|
-
}
|
|
2425
|
-
}
|
|
2426
|
-
}
|
|
2427
|
-
const ready = toInstall.filter((id) => (inDegree.get(id) ?? 0) === 0);
|
|
2428
|
-
const order = [];
|
|
2429
|
-
while (ready.length > 0) {
|
|
2430
|
-
const id = ready.shift();
|
|
2431
|
-
order.push(id);
|
|
2432
|
-
for (const dependent of dependents.get(id) ?? []) {
|
|
2433
|
-
const nd = (inDegree.get(dependent) ?? 0) - 1;
|
|
2434
|
-
inDegree.set(dependent, nd);
|
|
2435
|
-
if (nd === 0) ready.push(dependent);
|
|
2436
|
-
}
|
|
2437
|
-
}
|
|
2438
|
-
if (order.length !== toInstall.length) {
|
|
2439
|
-
}
|
|
2440
|
-
const contracts = /* @__PURE__ */ new Set();
|
|
2441
|
-
const business = /* @__PURE__ */ new Set();
|
|
2442
|
-
const conflicts = [];
|
|
2443
|
-
for (const edge of edges) {
|
|
2444
|
-
const id = edge.id;
|
|
2445
|
-
if (!installedIds.has(id) || !pkgVersion.has(id)) {
|
|
2446
|
-
if (isContractId(id, localIds)) contracts.add(id);
|
|
2447
|
-
else if (!isContractId(id, localIds)) business.add(id);
|
|
2448
|
-
}
|
|
2449
|
-
if (edge.range && pkgVersion.has(id)) {
|
|
2450
|
-
const targetVersion = pkgVersion.get(id);
|
|
2451
|
-
if (edge.range !== "*" && !edge.range.startsWith("workspace:")) {
|
|
2452
|
-
const inter = intersectRanges2(edge.range, targetVersion);
|
|
2453
|
-
if (!inter) {
|
|
2454
|
-
conflicts.push(`${id}: \u4F9D\u8D56\u8303\u56F4 ${edge.range} \u4E0E\u76EE\u6807\u7248\u672C ${targetVersion} \u65E0\u4EA4\u96C6`);
|
|
2455
|
-
}
|
|
2456
|
-
}
|
|
2457
|
-
}
|
|
2458
|
-
}
|
|
196
|
+
export default function tboxDevOverride() {
|
|
2459
197
|
return {
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
}
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
}
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
}
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
const manifest = readAppManifest(appDir);
|
|
2496
|
-
return manifest?.npmModules.some((m) => m.id === id && m.mode === "sdk") ?? false;
|
|
2497
|
-
}
|
|
2498
|
-
async function addModule(opts) {
|
|
2499
|
-
await addModuleWithDeps(opts, /* @__PURE__ */ new Set(), false);
|
|
2500
|
-
}
|
|
2501
|
-
async function addModuleWithDeps(opts, visited, asDependency) {
|
|
2502
|
-
const tarballs = [];
|
|
2503
|
-
try {
|
|
2504
|
-
await addModuleWithDepsCore(opts, visited, asDependency, tarballs);
|
|
2505
|
-
} finally {
|
|
2506
|
-
for (const t of tarballs) t.dispose();
|
|
2507
|
-
}
|
|
2508
|
-
}
|
|
2509
|
-
async function addModuleWithDepsCore(opts, visited, asDependency, tarballs) {
|
|
2510
|
-
const appDir = process.cwd();
|
|
2511
|
-
if (!readAppManifest(appDir)) throw new Error("\u5F53\u524D\u76EE\u5F55\u4E0D\u662F tbox-app \u5E94\u7528\uFF08\u7F3A .tbox/app.json\uFF09");
|
|
2512
|
-
let id;
|
|
2513
|
-
let sourceDescriptor = null;
|
|
2514
|
-
let opened = null;
|
|
2515
|
-
const isReg = !!opts.source && isRegistrySpec(opts.source);
|
|
2516
|
-
if (opts.mode === "local") {
|
|
2517
|
-
const short = opts.pkg;
|
|
2518
|
-
id = short.startsWith("module-") ? short : `module-${short}`;
|
|
2519
|
-
assertValidModuleId(id, `--mode local ${opts.pkg}`);
|
|
2520
|
-
} else {
|
|
2521
|
-
if (!opts.source) throw new Error(`--mode ${opts.mode} \u9700\u8981 --source <\u76EE\u5F55\u6216 tgz \u6216 registry \u5305>`);
|
|
2522
|
-
const isRegSource = isReg && !existsSync20(path24.resolve(opts.source));
|
|
2523
|
-
if (!isRegSource && !opts.source.endsWith(".tgz") && !LocalDirSource.isUsable(opts.source)) {
|
|
2524
|
-
throw new Error(`\u6A21\u5757\u6E90\u65E0\u6548\uFF08\u7F3A package.json\uFF09: ${opts.source}`);
|
|
2525
|
-
}
|
|
2526
|
-
opened = openSource(opts.source, opts.registry);
|
|
2527
|
-
if (opened.source instanceof TarballSource || opened.source instanceof RegistrySource) {
|
|
2528
|
-
tarballs.push(opened.source);
|
|
2529
|
-
}
|
|
2530
|
-
const pkgOnly = opts.pkg.replace(/@[^/]+$/, "");
|
|
2531
|
-
id = deriveModuleId(pkgOnly);
|
|
2532
|
-
assertValidModuleId(id, `--mode ${opts.mode} ${opts.pkg}`);
|
|
2533
|
-
sourceDescriptor = readSourceDescriptor(opened.sourceDir);
|
|
2534
|
-
}
|
|
2535
|
-
if (asDependency && isInstalled(appDir, id)) {
|
|
2536
|
-
console.log(` \u26A0\uFE0F \u4F9D\u8D56 ${id} \u5DF2\u5B89\u88C5\uFF0C\u8DF3\u8FC7`);
|
|
2537
|
-
return;
|
|
2538
|
-
}
|
|
2539
|
-
if (!asDependency && isInstalled(appDir, id)) {
|
|
2540
|
-
const where = existsSync20(path24.join(appDir, "packages", id)) ? `packages/${id}` : "manifest \u5DF2\u767B\u8BB0\uFF08sdk \u5305\uFF0C\u65E0\u672C\u5730\u76EE\u5F55\uFF09";
|
|
2541
|
-
throw new Error(`\u6A21\u5757 ${id} \u5DF2\u5B58\u5728\uFF08${where}\uFF09\u3002\u82E5\u8981\u91CD\u65B0\u5C55\u5F00\u8BF7\u5148 remove \u6216\u624B\u52A8\u5904\u7406\uFF1B\u88C5\u914D\u552F\u4E00\u8986\u76D6\u5165\u53E3\u4E3A sync --force`);
|
|
2542
|
-
}
|
|
2543
|
-
if (visited.has(id)) return;
|
|
2544
|
-
visited.add(id);
|
|
2545
|
-
if (!opts.noDeps) {
|
|
2546
|
-
const ownDeps = sourceDescriptor?.dependencies?.modules ?? [];
|
|
2547
|
-
const resolution = await resolveDependencies(appDir, [], void 0, [
|
|
2548
|
-
{ id, deps: ownDeps }
|
|
2549
|
-
]);
|
|
2550
|
-
if (resolution.cycles.length > 0) {
|
|
2551
|
-
throw new Error(
|
|
2552
|
-
`\u4F9D\u8D56\u73AF\uFF1A${resolution.cycles.map((c) => c.join(" \u2192 ")).join("\uFF1B")}`
|
|
2553
|
-
);
|
|
2554
|
-
}
|
|
2555
|
-
for (const c of resolution.conflicts) console.warn(` \u26A0\uFE0F \u4F9D\u8D56\u7248\u672C\u51B2\u7A81: ${c}`);
|
|
2556
|
-
const installSeq = [
|
|
2557
|
-
...resolution.contracts,
|
|
2558
|
-
...resolution.order
|
|
2559
|
-
].filter((depId) => depId !== id && !isInstalled(appDir, depId));
|
|
2560
|
-
for (const depId of [...new Set(installSeq)]) {
|
|
2561
|
-
const depPkg = /^(module|scenario|contracts)-/.test(depId) ? `@tbox.cn/app-${depId}` : `@tbox.cn/${depId}`;
|
|
2562
|
-
const depSource = isReg ? depPkg : resolveDepSource(opts.source, depId);
|
|
2563
|
-
if (!depSource) {
|
|
2564
|
-
console.warn(` \u26A0\uFE0F \u4F9D\u8D56 ${depId} \u672A\u5B89\u88C5\u4E14\u672A\u627E\u5230\u6E90\uFF08--source \u5144\u5F1F\u76EE\u5F55/tgz\uFF09\uFF0C\u53EF --no-deps \u8DF3\u8FC7`);
|
|
2565
|
-
continue;
|
|
2566
|
-
}
|
|
2567
|
-
const depOpened = openSource(depSource, opts.registry);
|
|
2568
|
-
if (depOpened.source instanceof TarballSource || depOpened.source instanceof RegistrySource) {
|
|
2569
|
-
tarballs.push(depOpened.source);
|
|
2570
|
-
}
|
|
2571
|
-
const depIsBusiness = existsSync20(path24.join(depOpened.sourceDir, "tbox.component.json"));
|
|
2572
|
-
await addModuleWithDeps(
|
|
2573
|
-
{ pkg: depPkg, mode: depIsBusiness ? "codegen" : "sdk", source: depSource, registry: opts.registry },
|
|
2574
|
-
visited,
|
|
2575
|
-
true
|
|
2576
|
-
);
|
|
2577
|
-
}
|
|
2578
|
-
}
|
|
2579
|
-
let entry;
|
|
2580
|
-
if (opts.mode === "local") {
|
|
2581
|
-
const short = opts.pkg;
|
|
2582
|
-
const localId = short.startsWith("module-") ? short : `module-${short}`;
|
|
2583
|
-
await createLocalSkeleton(appDir, short);
|
|
2584
|
-
entry = { id: localId, package: `@app/${localId}`, version: "0.1.0", mode: "local" };
|
|
2585
|
-
await updateAppsDependency(appDir, `@app/${localId}`, "add");
|
|
2586
|
-
} else {
|
|
2587
|
-
const sourceDir = opened.sourceDir;
|
|
2588
|
-
const version = opened.source.resolveVersion();
|
|
2589
|
-
if (opts.mode === "codegen") {
|
|
2590
|
-
await expandCodegen(appDir, sourceDir, { id, package: `@app/${id}`, version, mode: "codegen", baseVersion: version });
|
|
2591
|
-
await updateAppsDependency(appDir, `@app/${id}`, "add");
|
|
2592
|
-
}
|
|
2593
|
-
entry = {
|
|
2594
|
-
id,
|
|
2595
|
-
package: opts.pkg.replace(/@[^/]+$/, ""),
|
|
2596
|
-
version,
|
|
2597
|
-
mode: opts.mode,
|
|
2598
|
-
...opts.mode === "codegen" ? { baseVersion: version } : {}
|
|
2599
|
-
};
|
|
2600
|
-
}
|
|
2601
|
-
const manifest = readAppManifest(appDir);
|
|
2602
|
-
await writeAppManifest(appDir, upsertNpmModule(manifest, entry));
|
|
2603
|
-
const sync = await syncAll(appDir);
|
|
2604
|
-
if (sync.changedFiles.length > 0) {
|
|
2605
|
-
console.log(` \u88C5\u914D\u66F4\u65B0: ${sync.changedFiles.join(", ")}`);
|
|
2606
|
-
}
|
|
2607
|
-
for (const w of sync.warnings) console.warn(` \u26A0\uFE0F ${w}`);
|
|
2608
|
-
for (const c of sync.depConflicts) console.warn(` \u26A0\uFE0F \u4F9D\u8D56\u51B2\u7A81: ${c}`);
|
|
2609
|
-
const actual = await collectActual(appDir);
|
|
2610
|
-
const issues = await runDoctor({ appDir, declared: sync.declared, actual });
|
|
2611
|
-
const errors = issues.filter((i) => i.level === "error");
|
|
2612
|
-
if (errors.length > 0) {
|
|
2613
|
-
console.error("\u274C doctor \u9519\u8BEF\uFF1A");
|
|
2614
|
-
for (const e of errors) console.error(` - [${e.rule}] ${e.message}`);
|
|
2615
|
-
process.exitCode = 1;
|
|
2616
|
-
}
|
|
2617
|
-
console.log(`\u2705 \u5DF2\u5B89\u88C5 ${entry.package}\uFF08mode=${opts.mode}${asDependency ? ", \u4F9D\u8D56" : ""}\uFF09`);
|
|
2618
|
-
if (opts.mode !== "local") {
|
|
2619
|
-
console.log(` \u4E0B\u4E00\u6B65: pnpm install && tbox-app module doctor`);
|
|
2620
|
-
}
|
|
2621
|
-
}
|
|
2622
|
-
|
|
2623
|
-
// src/commands/remove.ts
|
|
2624
|
-
async function removeModule(id, opts = {}) {
|
|
2625
|
-
const appDir = process.cwd();
|
|
2626
|
-
assertValidModuleId(id, "remove \u53C2\u6570");
|
|
2627
|
-
const manifest = readAppManifest(appDir);
|
|
2628
|
-
if (!manifest) throw new Error("\u5F53\u524D\u76EE\u5F55\u4E0D\u662F tbox-app \u5E94\u7528\uFF08\u7F3A .tbox/app.json\uFF09");
|
|
2629
|
-
if (!opts.force) {
|
|
2630
|
-
const blockers = [];
|
|
2631
|
-
for (const mod of await listLocalModules(appDir)) {
|
|
2632
|
-
if (mod.id === id) continue;
|
|
2633
|
-
const dep = (mod.descriptor.dependencies?.modules ?? []).find((d) => d.id === id);
|
|
2634
|
-
if (dep && dep.required) blockers.push(mod.id);
|
|
2635
|
-
}
|
|
2636
|
-
if (blockers.length > 0) {
|
|
2637
|
-
throw new Error(`\u6A21\u5757 ${id} \u88AB\u4F9D\u8D56\uFF0C\u65E0\u6CD5\u79FB\u9664\uFF1A${blockers.join(", ")}\uFF08--force \u5FFD\u7565\uFF09`);
|
|
2638
|
-
}
|
|
2639
|
-
}
|
|
2640
|
-
const entry = manifest.npmModules.find((m) => m.id === id);
|
|
2641
|
-
const isNpmModule = Boolean(entry);
|
|
2642
|
-
if (isNpmModule) {
|
|
2643
|
-
await writeAppManifest(appDir, removeNpmModule(manifest, id));
|
|
2644
|
-
}
|
|
2645
|
-
const removedDir = await removePackageDir(appDir, id);
|
|
2646
|
-
if (removedDir) console.log(` \u5DF2\u5220\u9664 packages/${id}`);
|
|
2647
|
-
else if (entry?.mode === "sdk") console.log(` \u5DF2\u79FB\u9664 manifest \u767B\u8BB0\uFF08sdk \u5305\uFF0C\u65E0\u672C\u5730\u76EE\u5F55\uFF09`);
|
|
2648
|
-
if (entry?.mode === "codegen" || entry?.mode === "local" || !isNpmModule) {
|
|
2649
|
-
await updateAppsDependency(appDir, `@app/${id}`, "remove");
|
|
2650
|
-
}
|
|
2651
|
-
const sync = await syncAll(appDir);
|
|
2652
|
-
if (sync.changedFiles.length > 0) {
|
|
2653
|
-
console.log(` \u88C5\u914D\u66F4\u65B0: ${sync.changedFiles.join(", ")}`);
|
|
2654
|
-
}
|
|
2655
|
-
for (const w of sync.warnings) console.warn(` \u26A0\uFE0F ${w}`);
|
|
2656
|
-
for (const c of sync.depConflicts) console.warn(` \u26A0\uFE0F \u4F9D\u8D56\u51B2\u7A81: ${c}`);
|
|
2657
|
-
const actual = await collectActual(appDir);
|
|
2658
|
-
const issues = await runDoctor({ appDir, declared: sync.declared, actual });
|
|
2659
|
-
const errors = issues.filter((i) => i.level === "error");
|
|
2660
|
-
if (errors.length > 0) {
|
|
2661
|
-
console.error("\u274C doctor \u9519\u8BEF\uFF1A");
|
|
2662
|
-
for (const e of errors) console.error(` - [${e.rule}] ${e.message}`);
|
|
2663
|
-
process.exitCode = 1;
|
|
2664
|
-
}
|
|
2665
|
-
console.log(`\u2705 \u5DF2\u79FB\u9664 ${id}`);
|
|
2666
|
-
}
|
|
2667
|
-
|
|
2668
|
-
// src/commands/sync.ts
|
|
2669
|
-
async function syncCommand(opts = {}) {
|
|
2670
|
-
const appDir = process.cwd();
|
|
2671
|
-
const sync = await syncAll(appDir, { force: opts.force ?? false });
|
|
2672
|
-
if (sync.changedFiles.length === 0) {
|
|
2673
|
-
console.log("\u88C5\u914D\u5DF2\u540C\u6B65\uFF0C\u65E0\u53D8\u66F4");
|
|
2674
|
-
} else {
|
|
2675
|
-
console.log(`\u5DF2\u66F4\u65B0\u88C5\u914D\u6587\u4EF6: ${sync.changedFiles.join(", ")}`);
|
|
2676
|
-
}
|
|
2677
|
-
for (const w of sync.warnings) console.warn(` \u26A0\uFE0F ${w}`);
|
|
2678
|
-
for (const c of sync.depConflicts) console.warn(` \u26A0\uFE0F \u4F9D\u8D56\u51B2\u7A81: ${c}`);
|
|
2679
|
-
const actual = await collectActual(appDir);
|
|
2680
|
-
const issues = await runDoctor({ appDir, declared: sync.declared, actual });
|
|
2681
|
-
const errors = issues.filter((i) => i.level === "error");
|
|
2682
|
-
if (errors.length > 0) {
|
|
2683
|
-
console.error("\u274C doctor \u9519\u8BEF\uFF1A");
|
|
2684
|
-
for (const e of errors) console.error(` - [${e.rule}] ${e.message}`);
|
|
2685
|
-
process.exitCode = 1;
|
|
2686
|
-
} else {
|
|
2687
|
-
console.log("\u2705 doctor \u65E0\u9519\u8BEF");
|
|
2688
|
-
}
|
|
2689
|
-
for (const w of issues.filter((i) => i.level === "warning")) {
|
|
2690
|
-
console.warn(` \u26A0\uFE0F [${w.rule}] ${w.message}`);
|
|
2691
|
-
}
|
|
2692
|
-
}
|
|
2693
|
-
|
|
2694
|
-
// src/commands/list.ts
|
|
2695
|
-
async function listCommand() {
|
|
2696
|
-
const appDir = process.cwd();
|
|
2697
|
-
const manifest = readAppManifest(appDir);
|
|
2698
|
-
const declared = await collectDeclared(appDir);
|
|
2699
|
-
console.log(`\u6A21\u677F\u7248\u672C: ${manifest?.templateVersion ?? "\u672A\u77E5"}`);
|
|
2700
|
-
if (declared.length === 0) {
|
|
2701
|
-
console.log("\uFF08\u65E0\u5DF2\u88C5\u6A21\u5757\uFF09");
|
|
2702
|
-
return;
|
|
2703
|
-
}
|
|
2704
|
-
for (const m of declared) {
|
|
2705
|
-
console.log(` ${m.id.padEnd(28)} ${m.pkg} [${m.mode}]`);
|
|
2706
|
-
}
|
|
2707
|
-
const npmIds = new Set((manifest?.npmModules ?? []).map((m) => m.id));
|
|
2708
|
-
const localOnly = (await listLocalModules(appDir)).filter((m) => !npmIds.has(m.id));
|
|
2709
|
-
if (localOnly.length > 0) {
|
|
2710
|
-
console.log(" \u672C\u5730\u6A21\u5757\uFF08\u672A\u5728 app.json \u767B\u8BB0\uFF09:");
|
|
2711
|
-
for (const m of localOnly) console.log(` ${m.id}`);
|
|
2712
|
-
}
|
|
2713
|
-
}
|
|
2714
|
-
|
|
2715
|
-
// src/commands/diff.ts
|
|
2716
|
-
async function diffCommand(module) {
|
|
2717
|
-
const appDir = process.cwd();
|
|
2718
|
-
const declared = await collectDeclared(appDir);
|
|
2719
|
-
const actual = await collectActual(appDir);
|
|
2720
|
-
let dirty = false;
|
|
2721
|
-
for (const file of ASSEMBLY_FILES) {
|
|
2722
|
-
const kind = file.kind;
|
|
2723
|
-
const declaredIds = declared.filter((m) => contributesTo(kind, m)).map((m) => m.id);
|
|
2724
|
-
const actualIds = actual[kind]?.moduleIds ?? [];
|
|
2725
|
-
let missing = declaredIds.filter((id) => !actualIds.includes(id));
|
|
2726
|
-
let extra = actualIds.filter((id) => !declaredIds.includes(id));
|
|
2727
|
-
if (module) {
|
|
2728
|
-
missing = missing.filter((id) => id === module);
|
|
2729
|
-
extra = extra.filter((id) => id === module);
|
|
2730
|
-
}
|
|
2731
|
-
const broken = actual[kind]?.broken ?? [];
|
|
2732
|
-
const nonStandard = actual[kind]?.nonStandardLines ?? [];
|
|
2733
|
-
const lines = [];
|
|
2734
|
-
if (broken.length > 0) lines.push(`\u26A0\uFE0F \u951A\u533A\u7834\u574F: ${broken.join(", ")}`);
|
|
2735
|
-
if (missing.length > 0) lines.push(` - \u5DF2\u58F0\u660E\u672A\u88C5\u914D: ${missing.join(", ")}`);
|
|
2736
|
-
if (extra.length > 0) lines.push(` + \u5DF2\u88C5\u914D\u672A\u58F0\u660E: ${extra.join(", ")}`);
|
|
2737
|
-
for (const ns of nonStandard) lines.push(` ? \u975E\u6807\u884C: ${ns}`);
|
|
2738
|
-
if (lines.length > 0) {
|
|
2739
|
-
dirty = true;
|
|
2740
|
-
console.log(`${file.path}`);
|
|
2741
|
-
for (const l of lines) console.log(` ${l}`);
|
|
2742
|
-
}
|
|
2743
|
-
}
|
|
2744
|
-
if (!dirty) {
|
|
2745
|
-
console.log("\u65E0\u6F02\u79FB\uFF1A\u58F0\u660E\u96C6\u5408\u4E0E\u5B9E\u9645\u88C5\u914D\u96C6\u5408\u4E00\u81F4");
|
|
2746
|
-
}
|
|
2747
|
-
}
|
|
2748
|
-
|
|
2749
|
-
// src/commands/doctor.ts
|
|
2750
|
-
async function doctorCommand(opts = {}) {
|
|
2751
|
-
const appDir = process.cwd();
|
|
2752
|
-
const declared = await collectDeclared(appDir);
|
|
2753
|
-
const actual = await collectActual(appDir);
|
|
2754
|
-
const issues = await runDoctor(
|
|
2755
|
-
{ appDir, declared, actual },
|
|
2756
|
-
{
|
|
2757
|
-
only: opts.only ? opts.only.split(",").map((s) => s.trim()).filter(Boolean) : void 0,
|
|
2758
|
-
skip: opts.skip ? opts.skip.split(",").map((s) => s.trim()).filter(Boolean) : void 0
|
|
2759
|
-
}
|
|
2760
|
-
);
|
|
2761
|
-
const errors = issues.filter((i) => i.level === "error");
|
|
2762
|
-
const warnings = issues.filter((i) => i.level === "warning");
|
|
2763
|
-
if (opts.json) {
|
|
2764
|
-
console.log(JSON.stringify({ errors, warnings, rules: issues }, null, 2));
|
|
2765
|
-
if (errors.length > 0) process.exitCode = 1;
|
|
2766
|
-
return { issues };
|
|
2767
|
-
}
|
|
2768
|
-
if (issues.length === 0) {
|
|
2769
|
-
console.log("\u2705 doctor \u5168\u7EFF\uFF08\u7ED3\u6784\u6821\u9A8C\u901A\u8FC7\uFF09");
|
|
2770
|
-
} else {
|
|
2771
|
-
for (const e of errors) console.error(`\u274C [${e.rule}] ${e.message}`);
|
|
2772
|
-
for (const w of warnings) console.warn(`\u26A0\uFE0F [${w.rule}] ${w.message}`);
|
|
2773
|
-
if (errors.length > 0) {
|
|
2774
|
-
console.error(`doctor \u5931\u8D25: ${errors.length} \u4E2A\u9519\u8BEF`);
|
|
2775
|
-
process.exitCode = 1;
|
|
2776
|
-
} else {
|
|
2777
|
-
console.log(`doctor \u901A\u8FC7\uFF08${warnings.length} \u4E2A\u544A\u8B66\uFF09`);
|
|
2778
|
-
}
|
|
2779
|
-
}
|
|
2780
|
-
return { issues };
|
|
2781
|
-
}
|
|
2782
|
-
|
|
2783
|
-
// src/commands/update.ts
|
|
2784
|
-
import { existsSync as existsSync21 } from "fs";
|
|
2785
|
-
import { readdir as readdir12, readFile as readFile6, unlink, writeFile as writeFile7 } from "fs/promises";
|
|
2786
|
-
import path25 from "path";
|
|
2787
|
-
|
|
2788
|
-
// src/merge3.ts
|
|
2789
|
-
import { diffArrays } from "diff";
|
|
2790
|
-
function computeHunks(base, target) {
|
|
2791
|
-
const changes = diffArrays(base, target);
|
|
2792
|
-
const hunks = [];
|
|
2793
|
-
let baseIdx = 0;
|
|
2794
|
-
let pending = null;
|
|
2795
|
-
for (const c of changes) {
|
|
2796
|
-
if (c.removed) {
|
|
2797
|
-
if (!pending) pending = { start: baseIdx, oldLines: [...c.value], newLines: [] };
|
|
2798
|
-
else pending.oldLines.push(...c.value);
|
|
2799
|
-
baseIdx += c.value.length;
|
|
2800
|
-
} else if (c.added) {
|
|
2801
|
-
if (!pending) pending = { start: baseIdx, oldLines: [], newLines: [...c.value] };
|
|
2802
|
-
else pending.newLines.push(...c.value);
|
|
2803
|
-
} else {
|
|
2804
|
-
if (pending) {
|
|
2805
|
-
hunks.push(pending);
|
|
2806
|
-
pending = null;
|
|
2807
|
-
}
|
|
2808
|
-
baseIdx += c.value.length;
|
|
2809
|
-
}
|
|
2810
|
-
}
|
|
2811
|
-
if (pending) hunks.push(pending);
|
|
2812
|
-
return hunks;
|
|
2813
|
-
}
|
|
2814
|
-
function merge3(base, ours, theirs) {
|
|
2815
|
-
const baseLines = base === "" ? [] : base.split("\n");
|
|
2816
|
-
const oursLines = ours === "" ? [] : ours.split("\n");
|
|
2817
|
-
const theirsLines = theirs === "" ? [] : theirs.split("\n");
|
|
2818
|
-
const oursHunks = computeHunks(baseLines, oursLines);
|
|
2819
|
-
const theirsHunks = computeHunks(baseLines, theirsLines);
|
|
2820
|
-
const byStart = /* @__PURE__ */ new Map();
|
|
2821
|
-
for (const h of oursHunks) byStart.set(h.start, { ours: h });
|
|
2822
|
-
for (const h of theirsHunks) {
|
|
2823
|
-
const existing = byStart.get(h.start);
|
|
2824
|
-
if (existing) existing.theirs = h;
|
|
2825
|
-
else byStart.set(h.start, { theirs: h });
|
|
2826
|
-
}
|
|
2827
|
-
const out = [];
|
|
2828
|
-
const conflicts = [];
|
|
2829
|
-
const consumed = /* @__PURE__ */ new Set();
|
|
2830
|
-
const applyHunk = (o, t) => {
|
|
2831
|
-
const oldLen = Math.max(o?.oldLines.length ?? 0, t?.oldLines.length ?? 0);
|
|
2832
|
-
if (o && t) {
|
|
2833
|
-
const same = JSON.stringify(o.oldLines) === JSON.stringify(t.oldLines) && JSON.stringify(o.newLines) === JSON.stringify(t.newLines);
|
|
2834
|
-
if (same) {
|
|
2835
|
-
out.push(...o.newLines);
|
|
2836
|
-
} else {
|
|
2837
|
-
conflicts.push([...o.newLines, "--", ...t.newLines].join("\n"));
|
|
2838
|
-
out.push("<<<<<<< ours", ...o.newLines, "=======", ...t.newLines, ">>>>>>> theirs");
|
|
2839
|
-
}
|
|
2840
|
-
} else if (o) {
|
|
2841
|
-
out.push(...o.newLines);
|
|
2842
|
-
} else {
|
|
2843
|
-
out.push(...t.newLines);
|
|
2844
|
-
}
|
|
2845
|
-
return oldLen;
|
|
2846
|
-
};
|
|
2847
|
-
let i = 0;
|
|
2848
|
-
while (i < baseLines.length) {
|
|
2849
|
-
const entry = byStart.get(i);
|
|
2850
|
-
if (!entry || consumed.has(i)) {
|
|
2851
|
-
out.push(baseLines[i]);
|
|
2852
|
-
i++;
|
|
2853
|
-
continue;
|
|
2854
|
-
}
|
|
2855
|
-
consumed.add(i);
|
|
2856
|
-
const oldLen = applyHunk(entry.ours, entry.theirs);
|
|
2857
|
-
if (oldLen > 0) i += oldLen;
|
|
2858
|
-
}
|
|
2859
|
-
const tail = byStart.get(baseLines.length);
|
|
2860
|
-
if (tail && !consumed.has(baseLines.length)) {
|
|
2861
|
-
applyHunk(tail.ours, tail.theirs);
|
|
2862
|
-
}
|
|
2863
|
-
return { merged: out.join("\n"), conflicts };
|
|
2864
|
-
}
|
|
2865
|
-
|
|
2866
|
-
// src/commands/update.ts
|
|
2867
|
-
var EXCLUDED = /* @__PURE__ */ new Set(["node_modules", "dist", ".git", ".tbox"]);
|
|
2868
|
-
function openSourceDir(source) {
|
|
2869
|
-
if (source.endsWith(".tgz") && existsSync21(source)) {
|
|
2870
|
-
const tarball = new TarballSource(source);
|
|
2871
|
-
return { dir: tarball.getDir(), tarball };
|
|
2872
|
-
}
|
|
2873
|
-
return { dir: path25.resolve(source), tarball: null };
|
|
2874
|
-
}
|
|
2875
|
-
async function readPackageFiles(dir) {
|
|
2876
|
-
const map = /* @__PURE__ */ new Map();
|
|
2877
|
-
async function walk(rel) {
|
|
2878
|
-
const full = path25.join(dir, rel);
|
|
2879
|
-
const entries = await readdir12(full, { withFileTypes: true });
|
|
2880
|
-
for (const entry of entries) {
|
|
2881
|
-
if (EXCLUDED.has(entry.name)) continue;
|
|
2882
|
-
const child = rel ? `${rel}/${entry.name}` : entry.name;
|
|
2883
|
-
const childFull = path25.join(full, entry.name);
|
|
2884
|
-
if (entry.isDirectory()) {
|
|
2885
|
-
await walk(child);
|
|
2886
|
-
} else {
|
|
2887
|
-
try {
|
|
2888
|
-
map.set(child, await readFile6(childFull, "utf8"));
|
|
2889
|
-
} catch {
|
|
2890
|
-
}
|
|
2891
|
-
}
|
|
2892
|
-
}
|
|
2893
|
-
}
|
|
2894
|
-
await walk("");
|
|
2895
|
-
return map;
|
|
2896
|
-
}
|
|
2897
|
-
async function updateModule(opts) {
|
|
2898
|
-
const appDir = process.cwd();
|
|
2899
|
-
const manifest = readAppManifest(appDir);
|
|
2900
|
-
if (!manifest) throw new Error("\u5F53\u524D\u76EE\u5F55\u4E0D\u662F tbox-app \u5E94\u7528\uFF08\u7F3A .tbox/app.json\uFF09");
|
|
2901
|
-
const entry = manifest.npmModules.find((m) => m.id === opts.module);
|
|
2902
|
-
if (!entry) throw new Error(`\u6A21\u5757 ${opts.module} \u672A\u5728 .tbox/app.json \u767B\u8BB0`);
|
|
2903
|
-
if (entry.mode === "sdk") {
|
|
2904
|
-
if (opts.dryRun) {
|
|
2905
|
-
console.log(`[dry-run] pnpm update ${entry.package}`);
|
|
2906
|
-
return;
|
|
2907
|
-
}
|
|
2908
|
-
const { execFileSync: execFileSync4 } = await import("child_process");
|
|
2909
|
-
execFileSync4("pnpm", ["update", entry.package], { stdio: "inherit", cwd: appDir });
|
|
2910
|
-
console.log(`\u2705 \u5DF2\u5347\u7EA7 ${entry.package}\uFF08sdk \u6A21\u5F0F\uFF09`);
|
|
2911
|
-
return;
|
|
2912
|
-
}
|
|
2913
|
-
const pkgDir = path25.join(appDir, "packages", opts.module);
|
|
2914
|
-
if (!existsSync21(pkgDir)) throw new Error(`\u7F3A\u5C11 packages/${opts.module}`);
|
|
2915
|
-
const ours = await readPackageFiles(pkgDir);
|
|
2916
|
-
const tarballs = [];
|
|
2917
|
-
try {
|
|
2918
|
-
let theirs;
|
|
2919
|
-
let theirsVersion;
|
|
2920
|
-
if (opts.source) {
|
|
2921
|
-
const src = openSourceDir(opts.source);
|
|
2922
|
-
if (src.tarball) tarballs.push(src.tarball);
|
|
2923
|
-
theirs = await readPackageFiles(src.dir);
|
|
2924
|
-
theirsVersion = src.tarball ? src.tarball.resolveVersion() : readPkgJson(src.dir)?.version ?? entry.version;
|
|
2925
|
-
} else {
|
|
2926
|
-
throw new Error("codegen \u5347\u7EA7\u9700\u8981 --source <\u65B0\u7248\u672C\u5730\u76EE\u5F55\u6216 tgz>\uFF08npm \u62C9\u53D6\u4E3A manual\uFF0C\u771F\u5B9E\u53D1\u5E03\u540E\u56DE\u843D\uFF09");
|
|
2927
|
-
}
|
|
2928
|
-
let base = /* @__PURE__ */ new Map();
|
|
2929
|
-
if (opts.baseSource) {
|
|
2930
|
-
const bsrc = openSourceDir(opts.baseSource);
|
|
2931
|
-
if (bsrc.tarball) tarballs.push(bsrc.tarball);
|
|
2932
|
-
base = await readPackageFiles(bsrc.dir);
|
|
2933
|
-
}
|
|
2934
|
-
const allKeys = /* @__PURE__ */ new Set([...base.keys(), ...ours.keys(), ...theirs.keys()]);
|
|
2935
|
-
let mergedFiles = /* @__PURE__ */ new Map();
|
|
2936
|
-
const conflictList = [];
|
|
2937
|
-
for (const key of allKeys) {
|
|
2938
|
-
const b = base.get(key) ?? "";
|
|
2939
|
-
const o = ours.get(key) ?? "";
|
|
2940
|
-
const t = theirs.get(key) ?? "";
|
|
2941
|
-
if (opts.ours) {
|
|
2942
|
-
if (o) mergedFiles.set(key, o);
|
|
2943
|
-
continue;
|
|
2944
|
-
}
|
|
2945
|
-
if (opts.theirs) {
|
|
2946
|
-
if (t) mergedFiles.set(key, t);
|
|
2947
|
-
continue;
|
|
2948
|
-
}
|
|
2949
|
-
const result = merge3(b, o, t);
|
|
2950
|
-
mergedFiles.set(key, result.merged);
|
|
2951
|
-
if (result.conflicts.length > 0) {
|
|
2952
|
-
conflictList.push(`${key}: ${result.conflicts.length} \u5904\u51B2\u7A81`);
|
|
2953
|
-
}
|
|
2954
|
-
}
|
|
2955
|
-
if (opts.dryRun) {
|
|
2956
|
-
console.log(`[dry-run] ${opts.module}: ${allKeys.size} \u6587\u4EF6\u5408\u5E76\uFF0C\u51B2\u7A81 ${conflictList.length} \u5904`);
|
|
2957
|
-
for (const c of conflictList) console.log(` \u26A0\uFE0F ${c}`);
|
|
2958
|
-
return;
|
|
2959
|
-
}
|
|
2960
|
-
for (const [key, content] of mergedFiles) {
|
|
2961
|
-
const out = path25.join(pkgDir, key);
|
|
2962
|
-
if (content === "" && existsSync21(out)) {
|
|
2963
|
-
await unlink(out);
|
|
2964
|
-
console.log(` \u{1F5D1} \u5DF2\u5220\u9664 ${key}\uFF08\u5347\u7EA7\u6E90\u5220\u9664\u8BE5\u6587\u4EF6\uFF09`);
|
|
2965
|
-
continue;
|
|
2966
|
-
}
|
|
2967
|
-
await writeFile7(out, content, "utf8");
|
|
2968
|
-
}
|
|
2969
|
-
await writeAppManifest(appDir, updateNpmModule(manifest, opts.module, { baseVersion: theirsVersion, version: theirsVersion }));
|
|
2970
|
-
const sync = await syncAll(appDir);
|
|
2971
|
-
const actual = await collectActual(appDir);
|
|
2972
|
-
const issues = await runDoctor({ appDir, declared: sync.declared, actual });
|
|
2973
|
-
const errors = issues.filter((i) => i.level === "error");
|
|
2974
|
-
if (errors.length > 0) {
|
|
2975
|
-
console.error("\u274C doctor \u9519\u8BEF\uFF1A");
|
|
2976
|
-
for (const e of errors) console.error(` - [${e.rule}] ${e.message}`);
|
|
2977
|
-
process.exitCode = 1;
|
|
2978
|
-
}
|
|
2979
|
-
if (conflictList.length > 0) {
|
|
2980
|
-
console.warn("\u26A0\uFE0F \u5347\u7EA7\u5B8C\u6210\uFF0C\u5B58\u5728\u51B2\u7A81\u6807\u8BB0\uFF08<<<<<<< ours / >>>>>>> theirs\uFF09\uFF0C\u8BF7\u4EBA\u5DE5/Agent \u89E3\u51B3\u540E\u8FD0\u884C doctor:");
|
|
2981
|
-
for (const c of conflictList) console.warn(` - ${c}`);
|
|
2982
|
-
} else {
|
|
2983
|
-
console.log(`\u2705 \u5DF2\u5347\u7EA7 ${opts.module} \u2192 ${theirsVersion}`);
|
|
2984
|
-
}
|
|
2985
|
-
} finally {
|
|
2986
|
-
for (const t of tarballs) t.dispose();
|
|
2987
|
-
}
|
|
2988
|
-
}
|
|
2989
|
-
|
|
2990
|
-
// src/commands/publish.ts
|
|
2991
|
-
import { existsSync as existsSync23, readFileSync as readFileSync11, mkdtempSync as mkdtempSync2, rmSync as rmSync2 } from "fs";
|
|
2992
|
-
import { tmpdir as tmpdir2 } from "os";
|
|
2993
|
-
import path27 from "path";
|
|
2994
|
-
|
|
2995
|
-
// src/commands/pack.ts
|
|
2996
|
-
import { existsSync as existsSync22, readFileSync as readFileSync10, readdirSync as readdirSync3 } from "fs";
|
|
2997
|
-
import { execFileSync as execFileSync3 } from "child_process";
|
|
2998
|
-
import path26 from "path";
|
|
2999
|
-
function tarballFileName(name, version) {
|
|
3000
|
-
const plain = name.replace(/^@/, "").replace("/", "-");
|
|
3001
|
-
return `${plain}-${version}.tgz`;
|
|
3002
|
-
}
|
|
3003
|
-
function validatePublishableModule(moduleDir) {
|
|
3004
|
-
if (!existsSync22(moduleDir) || !existsSync22(path26.join(moduleDir, "package.json"))) {
|
|
3005
|
-
throw new Error(`\u6A21\u5757\u76EE\u5F55\u65E0\u6548\uFF08\u7F3A package.json\uFF09: ${moduleDir}`);
|
|
3006
|
-
}
|
|
3007
|
-
let pkg;
|
|
3008
|
-
try {
|
|
3009
|
-
pkg = JSON.parse(readFileSync10(path26.join(moduleDir, "package.json"), "utf8"));
|
|
3010
|
-
} catch {
|
|
3011
|
-
throw new Error(`package.json \u89E3\u6790\u5931\u8D25: ${path26.join(moduleDir, "package.json")}`);
|
|
3012
|
-
}
|
|
3013
|
-
const name = typeof pkg.name === "string" ? pkg.name : "";
|
|
3014
|
-
if (!/^@tbox\.cn\//.test(name)) {
|
|
3015
|
-
throw new Error(`\u53D1\u5E03\u5305\u540D\u5FC5\u987B\u4E3A @tbox.cn/* scope: ${name || "(\u7F3A\u5931)"}`);
|
|
3016
|
-
}
|
|
3017
|
-
const version = typeof pkg.version === "string" ? pkg.version : "";
|
|
3018
|
-
if (!/^\d+\.\d+\.\d+/.test(version)) {
|
|
3019
|
-
throw new Error(`\u7248\u672C\u53F7\u65E0\u6548\uFF08\u9700 semver x.y.z\uFF09: ${version || "(\u7F3A\u5931)"}`);
|
|
3020
|
-
}
|
|
3021
|
-
return { name, version };
|
|
3022
|
-
}
|
|
3023
|
-
function packModule(moduleDir, outDir) {
|
|
3024
|
-
const { name, version } = validatePublishableModule(moduleDir);
|
|
3025
|
-
const absOut = path26.resolve(outDir);
|
|
3026
|
-
execFileSync3("pnpm", ["pack", "--pack-destination", absOut], { stdio: "inherit", cwd: moduleDir });
|
|
3027
|
-
return path26.join(absOut, tarballFileName(name, version));
|
|
3028
|
-
}
|
|
3029
|
-
function detectKind(pkgDir, name) {
|
|
3030
|
-
if (existsSync22(path26.join(pkgDir, "pnpm-workspace.template.yaml"))) return "template";
|
|
3031
|
-
if (existsSync22(path26.join(pkgDir, "tbox.component.json"))) return "module";
|
|
3032
|
-
if (/^@[^/]+\/app-contracts/.test(name)) return "contract";
|
|
3033
|
-
return "generic";
|
|
3034
|
-
}
|
|
3035
|
-
function listAllFiles(dir) {
|
|
3036
|
-
const out = [];
|
|
3037
|
-
function walk(rel) {
|
|
3038
|
-
const full = path26.join(dir, rel);
|
|
3039
|
-
for (const entry of readdirSync3(full, { withFileTypes: true })) {
|
|
3040
|
-
if (entry.name === "node_modules" || entry.name === ".git") continue;
|
|
3041
|
-
const child = rel ? `${rel}/${entry.name}` : entry.name;
|
|
3042
|
-
if (entry.isDirectory()) walk(child);
|
|
3043
|
-
else out.push(child);
|
|
3044
|
-
}
|
|
3045
|
-
}
|
|
3046
|
-
walk("");
|
|
3047
|
-
return out;
|
|
3048
|
-
}
|
|
3049
|
-
function collectExportTargets(exports) {
|
|
3050
|
-
const targets = [];
|
|
3051
|
-
const push = (v) => {
|
|
3052
|
-
if (typeof v === "string") targets.push(v);
|
|
198
|
+
name: 'tbox-dev-in-place',
|
|
199
|
+
enforce: 'pre',
|
|
200
|
+
resolveId(source, importer) {
|
|
201
|
+
// client 装配改指:apps/client 内相对引用 modules → 物化构件(兼容 tsx 的 .ts 改写)
|
|
202
|
+
if (
|
|
203
|
+
typeof importer === 'string' &&
|
|
204
|
+
importer.includes(\`\${path.sep}apps\${path.sep}client\${path.sep}\`) &&
|
|
205
|
+
/^\\.{1,2}\\/modules(?:\\.(?:js|ts))?$/.test(source)
|
|
206
|
+
) {
|
|
207
|
+
return path.join(HERE, 'modules.client.ts');
|
|
208
|
+
}
|
|
209
|
+
const base = String(source).split('?')[0];
|
|
210
|
+
const hit = KEYS.find((k) => base === k || base.startsWith(k + '/'));
|
|
211
|
+
if (hit) {
|
|
212
|
+
const sub = base === hit ? '' : base.slice(hit.length);
|
|
213
|
+
return resolveSub(path.resolve(HERE, MANIFEST.redirects[hit]), sub) ?? null;
|
|
214
|
+
}
|
|
215
|
+
return null;
|
|
216
|
+
},
|
|
217
|
+
config() {
|
|
218
|
+
// dev 源解析域白名单收敛(安全:/@fs 经 0.0.0.0 监听面可达,禁止 repoRoot 全仓——
|
|
219
|
+
// 恒不含 .real-env/docs/dotfiles)。四源 = 模板根 + 模板家族目录 + 模块/平台真身 + 依赖库。
|
|
220
|
+
const allow = [
|
|
221
|
+
MANIFEST.templateDir,
|
|
222
|
+
path.join(MANIFEST.repoRoot, 'templates'),
|
|
223
|
+
path.join(MANIFEST.repoRoot, 'modules'),
|
|
224
|
+
path.join(MANIFEST.repoRoot, 'packages'),
|
|
225
|
+
path.join(MANIFEST.repoRoot, 'node_modules'),
|
|
226
|
+
];
|
|
227
|
+
return {
|
|
228
|
+
server: { fs: { allow } },
|
|
229
|
+
// identity 单向代价(R-9b):redirects 键全部豁免预打包(含 sdk/contracts),冷启动略慢
|
|
230
|
+
optimizeDeps: { exclude: Object.keys(MANIFEST.redirects ?? {}) },
|
|
231
|
+
};
|
|
232
|
+
},
|
|
3053
233
|
};
|
|
3054
|
-
if (typeof exports === "string") push(exports);
|
|
3055
|
-
else if (exports && typeof exports === "object") {
|
|
3056
|
-
for (const v of Object.values(exports)) {
|
|
3057
|
-
if (typeof v === "string") push(v);
|
|
3058
|
-
else if (v && typeof v === "object") {
|
|
3059
|
-
for (const t of Object.values(v)) push(t);
|
|
3060
|
-
}
|
|
3061
|
-
}
|
|
3062
|
-
}
|
|
3063
|
-
return targets;
|
|
3064
|
-
}
|
|
3065
|
-
function verifyPkgDir(pkgDir, kind) {
|
|
3066
|
-
const issues = [];
|
|
3067
|
-
const pkgFile = path26.join(pkgDir, "package.json");
|
|
3068
|
-
let pkg;
|
|
3069
|
-
try {
|
|
3070
|
-
pkg = JSON.parse(readFileSync10(pkgFile, "utf8"));
|
|
3071
|
-
} catch {
|
|
3072
|
-
issues.push({ rule: "pack-manifest", level: "error", message: "\u4EA7\u7269\u7F3A\u5C11\u53EF\u89E3\u6790\u7684 package.json" });
|
|
3073
|
-
return issues;
|
|
3074
|
-
}
|
|
3075
|
-
const kindOf = kind ?? detectKind(pkgDir, pkg.name ?? "");
|
|
3076
|
-
const files = listAllFiles(pkgDir);
|
|
3077
|
-
for (const key of ["dependencies", "peerDependencies", "devDependencies"]) {
|
|
3078
|
-
const deps = pkg[key];
|
|
3079
|
-
if (!deps || typeof deps !== "object") continue;
|
|
3080
|
-
for (const [n, spec] of Object.entries(deps)) {
|
|
3081
|
-
if (typeof spec === "string" && /^(workspace:|catalog:)/.test(spec)) {
|
|
3082
|
-
issues.push({
|
|
3083
|
-
rule: "pack-protocol-residue",
|
|
3084
|
-
level: "error",
|
|
3085
|
-
message: `${n}: \u53D1\u5E03\u6001\u6B8B\u7559 pnpm \u79C1\u6709\u534F\u8BAE "${spec}"\uFF08\u5E94\u5DF2\u5F52\u4E00\u5316\u4E3A\u5B9E\u88C5\u7248\u672C\uFF09`
|
|
3086
|
-
});
|
|
3087
|
-
}
|
|
3088
|
-
}
|
|
3089
|
-
}
|
|
3090
|
-
if (pkg.exports !== void 0) {
|
|
3091
|
-
for (const t of collectExportTargets(pkg.exports)) {
|
|
3092
|
-
if (t.includes("/src/") || t === "src") {
|
|
3093
|
-
issues.push({
|
|
3094
|
-
rule: "pack-exports",
|
|
3095
|
-
level: "error",
|
|
3096
|
-
message: `exports \u6307\u5411\u5F00\u53D1\u6001\u6E90\u7801 "${t}"\uFF08\u53D1\u5E03\u6001\u5E94\u6307\u5411 dist/\uFF09`
|
|
3097
|
-
});
|
|
3098
|
-
} else if (t.startsWith("./") && !existsSync22(path26.join(pkgDir, t))) {
|
|
3099
|
-
issues.push({
|
|
3100
|
-
rule: "pack-exports",
|
|
3101
|
-
level: "error",
|
|
3102
|
-
message: `exports \u76EE\u6807\u7F3A\u5931\uFF08\u4EA7\u7269\u5185\u4E0D\u5B58\u5728\uFF09: ${t}`
|
|
3103
|
-
});
|
|
3104
|
-
}
|
|
3105
|
-
}
|
|
3106
|
-
}
|
|
3107
|
-
const filesField = pkg.files;
|
|
3108
|
-
if (Array.isArray(filesField) && filesField.length > 0) {
|
|
3109
|
-
const allow = /* @__PURE__ */ new Set([...filesField.map((w) => String(w)), "package.json"]);
|
|
3110
|
-
const bad = files.filter((f) => ![...allow].some((w) => f === w || f.startsWith(`${w}/`)));
|
|
3111
|
-
if (bad.length > 0) {
|
|
3112
|
-
issues.push({
|
|
3113
|
-
rule: "pack-whitelist",
|
|
3114
|
-
level: "error",
|
|
3115
|
-
message: `\u4EA7\u7269\u542B\u767D\u540D\u5355\u5916\u6587\u4EF6: ${bad.slice(0, 5).join(", ")}${bad.length > 5 ? ` \u7B49 ${bad.length} \u4E2A` : ""}`
|
|
3116
|
-
});
|
|
3117
|
-
}
|
|
3118
|
-
} else {
|
|
3119
|
-
issues.push({
|
|
3120
|
-
rule: "pack-whitelist",
|
|
3121
|
-
level: "warning",
|
|
3122
|
-
message: "\u672A\u58F0\u660E files \u767D\u540D\u5355\uFF0C\u4EA7\u7269\u8303\u56F4\u4E0D\u53EF\u63A7\uFF08\u5EFA\u8BAE\u58F0\u660E files \u9650\u5236\u6253\u5305\u5185\u5BB9\uFF09"
|
|
3123
|
-
});
|
|
3124
|
-
}
|
|
3125
|
-
const leaked = files.filter((f) => /(^|\/)\.env$/.test(f) || /(^|\/)\.npmrc$/.test(f));
|
|
3126
|
-
if (leaked.length > 0) {
|
|
3127
|
-
issues.push({ rule: "pack-leak", level: "error", message: `\u4EA7\u7269\u542B\u654F\u611F\u6587\u4EF6: ${leaked.join(", ")}` });
|
|
3128
|
-
}
|
|
3129
|
-
if (kindOf === "module") {
|
|
3130
|
-
for (const required of ["src", "dist", "tbox.component.json"]) {
|
|
3131
|
-
if (!files.includes(required) && !existsSync22(path26.join(pkgDir, required))) {
|
|
3132
|
-
issues.push({ rule: "pack-files", level: "error", message: `\u4E1A\u52A1\u6A21\u5757\u4EA7\u7269\u7F3A\u5C11 ${required}` });
|
|
3133
|
-
}
|
|
3134
|
-
}
|
|
3135
|
-
} else if (kindOf === "template") {
|
|
3136
|
-
for (const required of [
|
|
3137
|
-
"pnpm-workspace.template.yaml",
|
|
3138
|
-
"apps/server/package.json",
|
|
3139
|
-
"apps/client/package.json",
|
|
3140
|
-
"packages/module-weather/package.json",
|
|
3141
|
-
".tbox/app.json",
|
|
3142
|
-
"tsconfig.base.json",
|
|
3143
|
-
"scripts/verify-generated.mjs"
|
|
3144
|
-
]) {
|
|
3145
|
-
if (!existsSync22(path26.join(pkgDir, required))) {
|
|
3146
|
-
issues.push({ rule: "pack-files", level: "error", message: `\u6A21\u677F\u4EA7\u7269\u7F3A\u5C11 ${required}` });
|
|
3147
|
-
}
|
|
3148
|
-
}
|
|
3149
|
-
for (const leaked2 of ["pnpm-workspace.yaml", "pnpm-lock.yaml"]) {
|
|
3150
|
-
if (existsSync22(path26.join(pkgDir, leaked2))) {
|
|
3151
|
-
issues.push({
|
|
3152
|
-
rule: "pack-files",
|
|
3153
|
-
level: "error",
|
|
3154
|
-
message: `\u6A21\u677F\u4EA7\u7269\u6CC4\u6F0F ${leaked2}\uFF08\u5E94\u4EC5\u542B pnpm-workspace.template.yaml\uFF09`
|
|
3155
|
-
});
|
|
3156
|
-
}
|
|
3157
|
-
}
|
|
3158
|
-
} else if (kindOf === "contract") {
|
|
3159
|
-
const dts = files.filter((f) => f.startsWith("dist/") && f.endsWith(".d.ts"));
|
|
3160
|
-
if (dts.length === 0) {
|
|
3161
|
-
issues.push({ rule: "pack-files", level: "error", message: "\u5951\u7EA6\u5305\u4EA7\u7269\u7F3A\u5C11 dist/*.d.ts" });
|
|
3162
|
-
}
|
|
3163
|
-
} else {
|
|
3164
|
-
const bin = pkg.bin;
|
|
3165
|
-
const binTargets = typeof bin === "string" ? [bin] : bin && typeof bin === "object" ? Object.values(bin) : [];
|
|
3166
|
-
for (const target of binTargets) {
|
|
3167
|
-
if (typeof target === "string" && !files.includes(target)) {
|
|
3168
|
-
issues.push({ rule: "pack-files", level: "error", message: `bin \u6307\u5411\u6587\u4EF6\u7F3A\u5931: ${target}` });
|
|
3169
|
-
}
|
|
3170
|
-
}
|
|
3171
|
-
}
|
|
3172
|
-
const declaredDocs = Array.isArray(filesField) && filesField.length > 0;
|
|
3173
|
-
for (const doc of ["LICENSE", "README.md"]) {
|
|
3174
|
-
if (!files.includes(doc)) {
|
|
3175
|
-
issues.push({
|
|
3176
|
-
rule: "pack-docs",
|
|
3177
|
-
level: "warning",
|
|
3178
|
-
message: `\u4EA7\u7269\u7F3A\u5C11 ${doc}${declaredDocs ? "\uFF08files \u767D\u540D\u5355\u5DF2\u58F0\u660E\uFF1B\u53D1\u5E03\u5B8C\u5907\u6027 backlog\uFF09" : "\uFF08\u5EFA\u8BAE\u8865\u5145\u4EE5\u5B8C\u5907\u53D1\u5E03\u5143\u6570\u636E\uFF09"}`
|
|
3179
|
-
});
|
|
3180
|
-
}
|
|
3181
|
-
}
|
|
3182
|
-
return issues;
|
|
3183
|
-
}
|
|
3184
|
-
async function verifyTarball(tgzPath) {
|
|
3185
|
-
const tarball = new TarballSource(tgzPath);
|
|
3186
|
-
try {
|
|
3187
|
-
const pkgDir = tarball.getDir();
|
|
3188
|
-
const pkg = JSON.parse(await tarball.readFile("package.json"));
|
|
3189
|
-
const kind = detectKind(pkgDir, pkg.name ?? "");
|
|
3190
|
-
const issues = verifyPkgDir(pkgDir, kind);
|
|
3191
|
-
if (pkg.name && pkg.version) {
|
|
3192
|
-
const expect = tarballFileName(pkg.name, pkg.version);
|
|
3193
|
-
if (path26.basename(tgzPath) !== expect) {
|
|
3194
|
-
issues.push({
|
|
3195
|
-
rule: "pack-filename",
|
|
3196
|
-
level: "error",
|
|
3197
|
-
message: `tgz \u6587\u4EF6\u540D ${path26.basename(tgzPath)} \u4E0E\u5305 ${pkg.name}@${pkg.version} \u671F\u671B ${expect} \u4E0D\u4E00\u81F4`
|
|
3198
|
-
});
|
|
3199
|
-
}
|
|
3200
|
-
}
|
|
3201
|
-
return issues;
|
|
3202
|
-
} finally {
|
|
3203
|
-
tarball.dispose();
|
|
3204
|
-
}
|
|
3205
|
-
}
|
|
3206
|
-
function printIssues(issues) {
|
|
3207
|
-
for (const i of issues) {
|
|
3208
|
-
console.log(` ${i.level === "error" ? "\u274C" : "\u26A0\uFE0F"} [${i.rule}] ${i.message}`);
|
|
3209
|
-
}
|
|
3210
|
-
}
|
|
3211
|
-
async function packCommand(opts) {
|
|
3212
|
-
const moduleDir = path26.resolve(process.cwd(), opts.module);
|
|
3213
|
-
const tgzPath = packModule(moduleDir, opts.out);
|
|
3214
|
-
console.log(`\u2705 \u5DF2\u6253\u5305 ${path26.basename(tgzPath)} \u2192 ${path26.resolve(opts.out)}`);
|
|
3215
|
-
if (opts.check) {
|
|
3216
|
-
const issues = await verifyTarball(tgzPath);
|
|
3217
|
-
printIssues(issues);
|
|
3218
|
-
if (issues.some((i) => i.level === "error")) process.exitCode = 1;
|
|
3219
|
-
}
|
|
3220
|
-
}
|
|
3221
|
-
|
|
3222
|
-
// src/commands/publish.ts
|
|
3223
|
-
async function publishModule(opts) {
|
|
3224
|
-
const cwd = process.cwd();
|
|
3225
|
-
const moduleDir = path27.resolve(cwd, opts.module);
|
|
3226
|
-
if (!existsSync23(moduleDir) || !existsSync23(path27.join(moduleDir, "package.json"))) {
|
|
3227
|
-
throw new Error(`\u6A21\u5757\u76EE\u5F55\u65E0\u6548\uFF08\u7F3A package.json\uFF09: ${opts.module}`);
|
|
3228
|
-
}
|
|
3229
|
-
let tgzPath;
|
|
3230
|
-
let workDir = null;
|
|
3231
|
-
if (opts.tgz) {
|
|
3232
|
-
tgzPath = path27.resolve(opts.tgz);
|
|
3233
|
-
if (!existsSync23(tgzPath)) throw new Error(`tgz \u4EA7\u7269\u4E0D\u5B58\u5728: ${opts.tgz}`);
|
|
3234
|
-
const tarball = new TarballSource(tgzPath);
|
|
3235
|
-
try {
|
|
3236
|
-
const tgzPkg = JSON.parse(await tarball.readFile("package.json"));
|
|
3237
|
-
const modulePkg = JSON.parse(readFileSync11(path27.join(moduleDir, "package.json"), "utf8"));
|
|
3238
|
-
if (tgzPkg.name !== modulePkg.name) {
|
|
3239
|
-
throw new Error(
|
|
3240
|
-
`--tgz \u5305\u540D ${tgzPkg.name ?? "(\u672A\u77E5)"} \u4E0E\u6A21\u5757 ${modulePkg.name ?? "(\u672A\u77E5)"} \u4E0D\u4E00\u81F4\uFF08\u8BF7\u786E\u8BA4 --tgz \u4E0E\u6A21\u5757\u5339\u914D\uFF09`
|
|
3241
|
-
);
|
|
3242
|
-
}
|
|
3243
|
-
} finally {
|
|
3244
|
-
tarball.dispose();
|
|
3245
|
-
}
|
|
3246
|
-
} else {
|
|
3247
|
-
workDir = mkdtempSync2(path27.join(tmpdir2(), "tbox-publish-"));
|
|
3248
|
-
tgzPath = packModule(moduleDir, workDir);
|
|
3249
|
-
}
|
|
3250
|
-
try {
|
|
3251
|
-
const issues = await verifyTarball(tgzPath);
|
|
3252
|
-
if (issues.length > 0) printIssues(issues);
|
|
3253
|
-
const errors = issues.filter((i) => i.level === "error");
|
|
3254
|
-
if (errors.length > 0) {
|
|
3255
|
-
throw new Error(`\u4EA7\u7269\u95E8\u7981\u5931\u8D25\uFF08${errors.length} \u5904 error\uFF09\uFF0C\u5DF2\u4E2D\u6B62\u53D1\u5E03: ${path27.basename(tgzPath)}`);
|
|
3256
|
-
}
|
|
3257
|
-
if (!workDir) workDir = mkdtempSync2(path27.join(tmpdir2(), "tbox-publish-"));
|
|
3258
|
-
const { execFileSync: execFileSync4 } = await import("child_process");
|
|
3259
|
-
const registryArgs = opts.registry ? ["--registry", opts.registry] : [];
|
|
3260
|
-
execFileSync4(
|
|
3261
|
-
"pnpm",
|
|
3262
|
-
["publish", tgzPath, "--access", opts.access ?? "public", ...registryArgs, ...opts.dryRun ? ["--dry-run"] : []],
|
|
3263
|
-
{ stdio: "inherit", cwd: workDir }
|
|
3264
|
-
);
|
|
3265
|
-
console.log(
|
|
3266
|
-
`\u2705 \u5DF2\u53D1\u5E03 ${path27.basename(tgzPath)}${opts.registry ? ` \u2192 ${opts.registry}` : ""}${opts.dryRun ? "\uFF08dry-run\uFF09" : ""}`
|
|
3267
|
-
);
|
|
3268
|
-
} finally {
|
|
3269
|
-
if (workDir) {
|
|
3270
|
-
try {
|
|
3271
|
-
rmSync2(workDir, { recursive: true, force: true });
|
|
3272
|
-
} catch {
|
|
3273
|
-
}
|
|
3274
|
-
}
|
|
3275
|
-
}
|
|
3276
234
|
}
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
console.error("\u274C", err.message);
|
|
3297
|
-
process.exitCode = 1;
|
|
3298
|
-
}
|
|
3299
|
-
});
|
|
3300
|
-
moduleCmd.command("remove <id>").description("\u5378\u8F7D\u6A21\u5757\uFF08\u5220\u5305 + \u6E05\u7406\u88C5\u914D\uFF09").option("--force", "\u5FFD\u7565\u53CD\u5411\u4F9D\u8D56\u68C0\u67E5\u5F3A\u5236\u79FB\u9664").action(async (id, opts) => {
|
|
3301
|
-
try {
|
|
3302
|
-
await removeModule(id, { force: opts.force });
|
|
3303
|
-
} catch (err) {
|
|
3304
|
-
console.error("\u274C", err.message);
|
|
3305
|
-
process.exitCode = 1;
|
|
3306
|
-
}
|
|
3307
|
-
});
|
|
3308
|
-
moduleCmd.command("list").description("\u5217\u51FA\u5DF2\u88C5\u6A21\u5757").action(async () => {
|
|
3309
|
-
try {
|
|
3310
|
-
await listCommand();
|
|
3311
|
-
} catch (err) {
|
|
3312
|
-
console.error("\u274C", err.message);
|
|
3313
|
-
process.exitCode = 1;
|
|
3314
|
-
}
|
|
3315
|
-
});
|
|
3316
|
-
moduleCmd.command("sync").description("\u589E\u91CF\u88C5\u914D 4 \u6587\u4EF6\uFF08\u4E0D\u8986\u76D6\uFF1B\u7ED3\u6784\u7834\u574F\u964D\u7EA7\u4E3A\u544A\u8B66\uFF09").option("--force", "\u5168\u91CF\u8986\u76D6\u88C5\u914D\u6587\u4EF6\uFF08002 \xA76.5\uFF1A\u552F\u4E00\u8986\u76D6\u5165\u53E3\uFF09").action(async (opts) => {
|
|
3317
|
-
try {
|
|
3318
|
-
await syncCommand({ force: opts.force });
|
|
3319
|
-
} catch (err) {
|
|
3320
|
-
console.error("\u274C", err.message);
|
|
3321
|
-
process.exitCode = 1;
|
|
3322
|
-
}
|
|
3323
|
-
});
|
|
3324
|
-
moduleCmd.command("diff [module]").description("\u88C5\u914D\u6F02\u79FB\u53CC\u5411 diff\uFF08module \u53EF\u6307\u5B9A\uFF0C\u9ED8\u8BA4\u5168\u90E8\uFF09").action(async (module) => {
|
|
3325
|
-
try {
|
|
3326
|
-
await diffCommand(module);
|
|
3327
|
-
} catch (err) {
|
|
3328
|
-
console.error("\u274C", err.message);
|
|
3329
|
-
process.exitCode = 1;
|
|
3330
|
-
}
|
|
3331
|
-
});
|
|
3332
|
-
moduleCmd.command("doctor").description("\u7ED3\u6784\u6821\u9A8C\uFF08doctor \u5B8C\u6574\u7248 13 \u6761\u89C4\u5219\uFF09").option("--json", "\u53EA\u8F93\u51FA JSON \u7ED3\u679C").option("--only <\u89C4\u5219\u5217\u8868>", "\u53EA\u8FD0\u884C\u6307\u5B9A\u89C4\u5219\uFF08\u9017\u53F7\u5206\u9694\uFF09").option("--skip <\u89C4\u5219\u5217\u8868>", "\u8DF3\u8FC7\u6307\u5B9A\u89C4\u5219\uFF08\u9017\u53F7\u5206\u9694\uFF09").action(async (opts) => {
|
|
3333
|
-
try {
|
|
3334
|
-
await doctorCommand(opts);
|
|
3335
|
-
} catch (err) {
|
|
3336
|
-
console.error("\u274C", err.message);
|
|
3337
|
-
process.exitCode = 1;
|
|
3338
|
-
}
|
|
3339
|
-
});
|
|
3340
|
-
moduleCmd.command("update <module>").description("\u5347\u7EA7\u7EC4\u4EF6\uFF08codegen\u21923-way merge\uFF1Bsdk\u2192pnpm update\uFF09").option("--dry-run", "\u53EA\u6253\u5370\u5408\u5E76\u7ED3\u679C\u4E0D\u5199\u56DE").option("--ours", "\u51B2\u7A81\u5FEB\u901F\u53D6\u672C\u5730\u7248\u672C").option("--theirs", "\u51B2\u7A81\u5FEB\u901F\u53D6\u65B0\u7248\u7248\u672C").option("--source <\u8DEF\u5F84>", "\u65B0\u7248\u672C\u5730\u6E90\u76EE\u5F55\u6216 tgz").option("--base-source <\u8DEF\u5F84>", "base \u672C\u5730\u6E90\u76EE\u5F55\u6216 tgz").action(async (module, opts) => {
|
|
3341
|
-
try {
|
|
3342
|
-
await updateModule({ module, ...opts });
|
|
3343
|
-
} catch (err) {
|
|
3344
|
-
console.error("\u274C", err.message);
|
|
3345
|
-
process.exitCode = 1;
|
|
3346
|
-
}
|
|
3347
|
-
});
|
|
3348
|
-
moduleCmd.command("pack <module>").description("\u672C\u5730\u6253\u5305\u4E0D\u53D1\u5E03\uFF08\u6A21\u5757\u76EE\u5F55\u5982 modules/module-member\uFF1Bpnpm pack \u5185\u5EFA\u5F52\u4E00\u5316 \u2192 \u53D1\u5E03\u6001 tgz\uFF09").requiredOption("--out <dir>", "\u4EA7\u7269\u76EE\u5F55\uFF08pnpm \u81EA\u52A8\u521B\u5EFA\uFF0C\u540C\u540D\u8986\u76D6\u5199\uFF09").option("--check", "\u4EA7\u7269\u65AD\u8A00\uFF08\u534F\u8BAE\u6B8B\u7559 / exports / \u767D\u540D\u5355 / \u6CC4\u6F0F / \u6587\u4EF6\u4E00\u81F4\u6027\uFF09").action(async (module, opts) => {
|
|
3349
|
-
try {
|
|
3350
|
-
await packCommand({ module, out: opts.out, check: opts.check });
|
|
3351
|
-
} catch (err) {
|
|
3352
|
-
console.error("\u274C", err.message);
|
|
3353
|
-
process.exitCode = 1;
|
|
3354
|
-
}
|
|
3355
|
-
});
|
|
3356
|
-
moduleCmd.command("publish <module>").description("\u6A21\u5757\u664B\u5347 npm\uFF08\u6A21\u5757\u76EE\u5F55\u5982 modules/module-member\uFF1B\u6821\u9A8C + \u6253\u5305 + \u4EA7\u7269\u95E8\u7981 + pnpm publish <tgz>\uFF09").option("--tgz <path>", "\u590D\u7528\u5DF2\u9A8C\u8BC1\u4EA7\u7269\uFF08\u7F3A\u7701\u73B0\u573A pack \u5230\u4E34\u65F6\u76EE\u5F55\uFF09").option("--dry-run", "\u6821\u9A8C + \u95E8\u7981 + \u6253\u5370\uFF0C\u4E0D\u771F\u5B9E\u53D1\u5E03").option("--access <public|restricted>", "npm access", "public").option("--registry <url>", "\u53D1\u5E03 registry\uFF08\u7F3A\u7701 pnpm \u914D\u7F6E\u89E3\u6790\uFF1A@tbox.cn scope \u4F18\u5148\uFF09").action(async (module, opts) => {
|
|
3357
|
-
try {
|
|
3358
|
-
await publishModule({
|
|
3359
|
-
module,
|
|
3360
|
-
tgz: opts.tgz,
|
|
3361
|
-
dryRun: opts.dryRun,
|
|
3362
|
-
access: opts.access,
|
|
3363
|
-
registry: opts.registry
|
|
3364
|
-
});
|
|
3365
|
-
} catch (err) {
|
|
3366
|
-
console.error("\u274C", err.message);
|
|
3367
|
-
process.exitCode = 1;
|
|
3368
|
-
}
|
|
3369
|
-
});
|
|
3370
|
-
return program;
|
|
3371
|
-
}
|
|
3372
|
-
var isMain = typeof process.argv[1] === "string" && realpathSync3(fileURLToPath(import.meta.url)) === realpathSync3(process.argv[1]);
|
|
3373
|
-
if (isMain) {
|
|
3374
|
-
createProgram().parseAsync(process.argv);
|
|
3375
|
-
}
|
|
3376
|
-
export {
|
|
3377
|
-
createProgram
|
|
3378
|
-
};
|
|
235
|
+
`;async function Eo(e){let t=Kc();if(e.prune){let m=e.template?x.resolve(t,e.template):x.resolve(t,vo);if(!I(m))throw new Error(`--template 目录不存在:${m}`);await ul(ae(m),t);return}Yc(e.realEnv);let r=ae(e.template?x.resolve(t,e.template):x.resolve(t,vo));if(!I(x.join(r,".tbox","app.json")))throw new Error(`--template 目录缺 .tbox/app.json(不是应用/模板根?):${r}`);Xc(r);let n=x.join(r,Pe),s=x.join(n,"session.lock"),i=x.join(n,"dev-manifest.json"),o=(e.module??[]).map(m=>x.resolve(t,m)),a=el(o,t,r);if(a.length===0)throw new Error("未指定任何模块:请传 --module modules/<id>(至少一个);或使用 scripts/dev-mall-agent.sh 快速开始");let c=nl(t,n,a,!!e.linkSdk),l=ol(t,a,e.realEnv,!!e.linkSdk,e.configDir??[]),p=il(r,c,l,a,e),u={version:1,templateDir:r,repoRoot:t,tier:e.realEnv,linkSdk:!!e.linkSdk,redirects:c,modules:a.map(m=>({id:m.id,pkg:m.pkg,dir:x.relative(t,m.dir),kind:m.descriptor.kind})),configs:l};if(I(s)){let m=null;try{m=JSON.parse(X(s,"utf8"))}catch{}if(m&&jo(m.pid)&&m.pid!==process.pid){if(m.hash===p){console.log(`↩︎ 检测到同集合 dev 会话(pid=${m.pid})——attach 模式,直接拉起进程`),Rr(n,{recursive:!0});let h=ko(r,t,c);console.log(`🔗 链接断言:planted=${h.planted.length} skipped=${h.skipped}`),await xo(r,l,e.port,s,c);return}throw new Error(`已有不同集合的 dev 会话运行中(pid=${m.pid},集合 ${m.hash} ≠ 本次 ${p})——并发会话会互相覆盖 .tbox-dev.local/ 构件。请先停掉对方或运行 'tbox-app dev --prune'`)}console.log("🧹 吸收残留死锁(pid 已不存在)")}Rr(n,{recursive:!0});let d=rl(r,a,n);for(let m of d)console.warn(`⚠️ ${m}`);let f=ko(r,t,c);console.log(`🔗 链接种植:planted=${f.planted.length} skipped=${f.skipped}`),Re(x.join(n,"package.json"),JSON.stringify({type:"module"},null,2)+`
|
|
236
|
+
`),Nt(x.join(n,"boot.mjs"),al,"utf8"),Nt(x.join(n,"hook-core.mjs"),cl,"utf8"),Nt(x.join(n,"vite-override.mjs"),ll,"utf8"),Re(i,JSON.stringify(u,null,2)+`
|
|
237
|
+
`),Re(s,JSON.stringify({pid:process.pid,cmd:"tbox-app dev",hash:p,ts:new Date().toISOString()},null,2)+`
|
|
238
|
+
`);let g=e.port??Bc;console.log("─".repeat(64)),console.log(`📦 dev 模块集:${a.map(m=>m.id).join(", ")}`),Object.keys(l).length>0?(console.log(`🗂 integrations=${l.TBOX_INTEGRATIONS_FILE??"— (absent:登录将落内置换码器,缺 ALIPAY_* 凭证即 503)"}`),console.log(`🗂 config-dir=${l.TBOX_CONFIG_DIR??"—"}`)):console.log("🗂 configs:无注入(未指定 --real-env / --config-dir;裸跑形态)"),console.log(`🚀 dev ready → http://127.0.0.1:${g}`),console.log("ℹ️ dev 链接形态 ≠ codegen 发布态(L2≠L3):发布判据仍以 verify-cli 为准"),console.log("─".repeat(64)),await xo(r,l,e.port,s,c)}async function xo(e,t,r,n,s={}){let i=x.join(e,...Uc),a=JSON.parse(X(x.join(i,"package.json"),"utf8")).scripts?.dev??"",c=a.split(/\s+/);if(c[0]!=="tsx"||c[1]!=="watch"||c.length<3)throw new Error(`apps/server scripts.dev 形态不受支持(需 "tsx watch … <entry>"):"${a}"——请升级模板`);let l=c[c.length-1],p=x.join(e,Pe,"boot.mjs"),u=[...new Set(Object.values(s).map(h=>x.resolve(x.join(e,Pe),h)))],d=["--watch-path=./src",`--watch-path=${x.join(e,Pe)}`,...u.map(h=>`--watch-path=${h}`)],f={...process.env,...t,...r!==void 0?{PORT:String(r)}:{}},g=Fc("node",[...d,"--watch","--import","tsx","--import",p,l],{cwd:i,env:f,stdio:"inherit",detached:!0});Vt=g.pid,pl();let m=await new Promise(h=>{let v=!1,S=$=>{v||(v=!0,Vt=void 0,h($))};g.on("exit",($,R)=>S($??(R?130:1))),g.on("error",$=>{console.error("❌ dev 子进程启动失败:",$),S(1)})});try{$o(n)}catch{}process.exitCode=m??1}var Vt,So=!1;function pl(){if(So)return;So=!0;let e=t=>{if(Vt)try{process.kill(-Vt,t)}catch{}};process.on("SIGINT",()=>e("SIGINT")),process.on("SIGTERM",()=>e("SIGTERM"))}async function ul(e,t){let r=x.join(e,Pe),n=x.join(r,"dev-manifest.json"),s=x.join(r,"session.lock");if(!I(r)){console.log("✨ 构件区不存在,无需清理");return}if(I(s))try{let o=JSON.parse(X(s,"utf8"));if(o.pid!==process.pid&&jo(o.pid))throw new Error(`dev 会话仍在运行(pid=${o.pid})——请先 Ctrl+C 停止再 --prune`)}catch(o){if(o.message.startsWith("dev 会话"))throw o}let i=0;if(I(n))try{let o=JSON.parse(X(n,"utf8"));for(let a of Object.keys(o.redirects??{})){let c=a.split("/"),l=x.join(e,"node_modules",c[0],c[1]);if(!I(l))continue;let p=ae(l);[x.join(t,"modules"),x.join(t,"packages")].some(d=>p===d||p.startsWith(d+x.sep))&&($o(l),i++)}}catch{}Nc(r,{recursive:!0,force:!0}),console.log(`✨ prune 完成:构件区已清、种植链接回收 ${i} 条(模板:${x.relative(t,e)})`)}import{readFileSync as dl}from"fs";var Jt=[{type:"agent",description:"通用 AI Agent 应用(Base 模板)",templatePackage:"@tbox.cn/app-template-agent",documentFile:"agent.md"},{type:"agent-mall",description:"商圈 AI Agent 应用(Mall 模板)",templatePackage:"@tbox.cn/app-template-agent-mall",documentFile:"agent-mall.md"},{type:"agent-support",description:"客服支持 AI Agent 应用(Support 模板)",templatePackage:"@tbox.cn/app-template-agent-support",documentFile:"agent-support.md"}];function fl(e){return dl(new URL(`../starter/${e}`,import.meta.url),"utf8")}function ml(e){let t=Jt.map(({type:r,description:n,templatePackage:s})=>({type:r,description:n,templatePackage:s}));return e?JSON.stringify({starters:t},null,2)+`
|
|
239
|
+
`:["可用 starter 类型:",...t.map(r=>` ${r.type.padEnd(11)} ${r.description}(${r.templatePackage})`),"",`查询文档: tbox-app starter --type <${Jt.map(r=>r.type).join("|")}>`].join(`
|
|
240
|
+
`)+`
|
|
241
|
+
`}function Ro(e){if(e.type&&e.list)throw new Error("--type 与 --list 互斥");if(e.json&&!e.list)throw new Error("--json 仅配合 --list 使用");if(!e.type||e.list){process.stdout.write(ml(!!e.json));return}let t=Jt.find(r=>r.type===e.type);if(!t)throw new Error(`未知 starter 类型: ${e.type}(可用:${Jt.map(r=>r.type).join("、")};使用 --list 查看)`);process.stdout.write(fl(t.documentFile))}import{createHash as hl}from"crypto";import{readdir as vl,readFile as wl,stat as bl}from"fs/promises";import{join as Le,relative as kl,basename as Io}from"path";import{parse as xl}from"yaml";import{z as et}from"zod";import{z as y}from"zod";var Dr=y.object({version:y.literal(1),name:y.string().min(1),description:y.string().default(""),defaultTurnTimeoutMs:y.number().int().min(5e3).default(15e4),driver:y.enum(["agui","support"]).optional()}).strict(),Cr=y.object({version:y.literal(1),description:y.string().default(""),standards:y.array(y.string().min(1)).min(1)}).strict(),Do=y.union([y.string().min(1),y.array(y.string().min(1)).min(1)]),To=y.union([Do,y.object({any_of:y.array(Do).min(1)}).strict()]),gl=To;function Co(e){let t=r=>r==="none"||r==="direct-out"?[]:[r];return typeof e=="string"?t(e):Array.isArray(e)?e:e.any_of.flatMap(r=>typeof r=="string"?t(r):r)}var yl=y.object({expect:To,optional:y.array(y.string().min(1)).default([]),forbid:y.array(y.string().min(1)).default([]),pre:y.array(y.string().min(1)).default([]),mode:y.enum(["strict","soft"]).default("strict")}).strict().refine(e=>e.expect!=="direct-out"||e.pre.length===0,{message:"direct-out 与 pre 互斥(direct-out 断言零模型调用,pre 断言走 preTool 轨)"}).refine(e=>e.optional.every(t=>!Co(e.expect).includes(t)),{message:"optional 与 expect 重叠(optional 豁免会使 expect 永不满足)"}).refine(e=>e.forbid.every(t=>!Co(e.expect).includes(t)),{message:"forbid 与 expect 重叠(契约自相矛盾,恒 fail)"}),Tr=y.object({id:y.string().regex(/^[A-Za-z0-9][A-Za-z0-9-]{2,50}$/),priority:y.enum(["P0","P1","P2"]),tags:y.array(y.string().min(1)).default([]),skip:y.string().min(1).optional(),turns:y.array(y.object({text:y.string().trim().min(1,"不能为空白"),newSession:y.boolean().default(!1)}).strict()).min(1),tools:yl,cards:y.object({expect:gl}).strict().optional(),assertions:y.array(y.string().trim().min(1,"不能为空白")).min(1),judge_hints:y.array(y.string().min(1)).default([])}).strict(),ty=y.object({suite:Dr,rubric:Cr,cases:y.array(Tr).min(1)}).strict(),Qe=y.object({version:y.literal(1),runId:y.string(),startedAt:y.string(),finishedAt:y.string(),dataset:y.object({name:y.string(),hash:y.string(),caseCount:y.number().int()}),rubricHash:y.string(),judgeModel:y.string().nullable(),instance:y.object({baseUrl:y.string(),pid:y.number().nullable(),startedAt:y.string().nullable(),journalRoot:y.string().nullable(),degraded:y.boolean()}),devUserPrefix:y.string(),dimensions:y.record(y.string(),y.object({pass:y.number(),fail:y.number(),na:y.number()})),cases:y.array(y.object({id:y.string(),status:y.enum(["pass","fail","inconclusive","skipped"]),skipReason:y.string().optional(),dims:y.record(y.string(),y.enum(["pass","fail","warning","na"])),failures:y.array(y.string()).default([])})),slices:y.record(y.string(),y.record(y.string(),y.object({pass:y.number(),total:y.number()}))),ops:y.object({durationMs:y.number(),turnCount:y.number(),infraErrors:y.number(),aborted:y.boolean(),llmTokens:y.number().nullable(),judgeFailures:y.number().int().default(0)})}).strict(),Mo=y.object({version:y.literal(1),pid:y.number().int(),port:y.number().int(),baseUrl:y.string(),journalRoot:y.string(),startedAt:y.string(),detached:y.boolean()}).strict();var Sl=et.object({tools:et.array(et.string().min(1)).min(1),cards:et.array(et.string().min(1)).min(1)}).strict(),$l=/^[a-zA-Z][a-zA-Z0-9_-]*$/,jl=new Set(["none","direct-out"]);function Mr(e){return typeof e=="string"?[e]:Array.isArray(e)?e:e.any_of.flatMap(Mr)}function El(e){return[...Mr(e.expect),...e.optional,...e.forbid,...e.pre]}function Oo(e,t){return e<t?-1:e>t?1:0}var Rl=(e,t)=>Oo(e.name,t.name);function Ao(e){return hl("sha256").update(e.join(`
|
|
242
|
+
\0
|
|
243
|
+
`)).digest("hex")}async function zt(e,t,r){let n;try{n=await wl(e,"utf-8")}catch(o){throw new Error(`读取 ${r} 失败(${e}): ${o.message}`)}let s;try{s=xl(n)}catch(o){throw new Error(`${r} YAML 语法错误(${e}): ${o.message}`)}let i=t.safeParse(s);if(!i.success){let o=i.error.issues.map(a=>` - ${a.path.join(".")||"<root>"}: ${a.message}`).join(`
|
|
244
|
+
`);throw new Error(`${r} 契约校验失败(${e}):
|
|
245
|
+
${o}`)}return i.data}async function Dl(e){let t=[],r=async n=>{let s;try{s=await vl(n,{withFileTypes:!0})}catch{return}for(let i of s.sort(Rl)){let o=Le(n,i.name);i.isDirectory()?await r(o):i.isFile()&&(i.name.endsWith(".yaml")||i.name.endsWith(".yml"))&&t.push(o)}};return await r(e),t.sort()}async function Ut(e){let t=Le(e,"suite.yaml"),r=Le(e,"rubric.yaml"),n=await zt(t,Dr,"suite.yaml"),s=await zt(r,Cr,"rubric.yaml"),i=await Dl(Le(e,"cases"));if(i.length===0)throw new Error(`数据集为空:${Le(e,"cases")} 下未发现任何 .yaml 用例`);let o=[],a=[],c=new Map;for(let v of i){let S=kl(e,v);try{let $=await zt(v,Tr,"case");Io(v).replace(/\.ya?ml$/,"")!==$.id&&a.push(` - ${S}: 文件名与用例 id 不符(文件 ${Io(v)},id ${$.id})`),o.push($),c.set($.id,S)}catch($){a.push(` - ${S}: ${$.message.split(`
|
|
246
|
+
`).slice(1).join(";")||$.message}`)}}let l=new Map;for(let v of o)l.set(v.id,(l.get(v.id)??0)+1);let p=[...l.entries()].filter(([,v])=>v>1);if(p.length>0&&a.push(` - 用例 id 重复: ${p.map(([v,S])=>`${v}×${S}`).join(", ")}`),n.driver==="support")for(let v of o)v.turns.some(S=>S.newSession)&&a.push(` - ${c.get(v.id)??`cases/${v.id}.yaml`}: driver=support 暂不支持 newSession(support 会话绑定连接)`);let u=null,d=Le(e,"tools-manifest.yaml"),f=!1;try{f=(await bl(d)).isFile()}catch{f=!1}if(f){let v=await zt(d,Sl,"tools-manifest.yaml");u={tools:new Set(v.tools),cards:new Set(v.cards)};for(let S of o){let $=c.get(S.id)??`cases/${S.id}.yaml`;for(let R of El(S.tools))jl.has(R)||($l.test(R)?u.tools.has(R)||a.push(` - ${$}: 工具引用 '${R}' 不在词表(tools-manifest.yaml)——检查拼写或更新词表`):a.push(` - ${$}: 工具引用 '${R}' 非法形态(须 ^[a-zA-Z][a-zA-Z0-9_-]*$)——疑似期望文案粘连进 DSL`));if(S.cards)for(let R of Mr(S.cards.expect))u.cards.has(R)||a.push(` - ${$}: 卡片引用 '${R}' 不在词表(tools-manifest.yaml cards 节)`)}}if(a.length>0)throw new Error(`数据集校验失败(${e},${a.length} 处):
|
|
247
|
+
${a.join(`
|
|
248
|
+
`)}`);let g=Ao([JSON.stringify(s)]),m=[...o].sort((v,S)=>Oo(v.id,S.id)),h=Ao([JSON.stringify(n),JSON.stringify(s),...m.map(v=>JSON.stringify(v))]);return{dir:e,suite:n,rubric:s,cases:o,datasetHash:h,rubricHash:g,manifest:u}}function Po(e){let t={},r={},n={},s={},i=0,o=0;for(let a of e.cases){a.skip&&(t[a.skip]=(t[a.skip]??0)+1),r[a.priority]=(r[a.priority]??0)+1;let c=a.tags[2]??"<none>";n[c]=(n[c]??0)+1;let l=1;for(let p of a.turns)p.newSession&&(l+=1);s[String(l)]=(s[String(l)]??0)+1,a.turns.length>1&&(i+=1),a.tools.mode==="soft"&&(o+=1)}return{total:e.cases.length,evaluated:e.cases.filter(a=>!a.skip).length,skippedByReason:t,byPriority:r,byCaseType:n,sessionHistogram:s,multiTurnCases:i,softModeCases:o}}import{randomBytes as op}from"crypto";import{mkdir as ip,readFile as ci}from"fs/promises";import{join as Fr}from"path";import{readdir as Lo,readFile as No}from"fs/promises";import{join as Fo}from"path";var H=class extends Error{constructor(t){super(t),this.name="InfraError"}},_o=400,Cl=1e4,Tl=1500,Ml=300;async function Ir(e,t,r){let n=r??fetch,s;try{s=await n(`${e}/api/auth/dev-login`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({devUser:t})})}catch(o){throw new H(`dev-login 请求失败(${e}): ${o.message}`)}if(!s.ok)throw new H(`dev-login 失败 HTTP ${s.status}(devUser=${t};prod 档会 403——检查实例启动语义)`);let i=await s.json().catch(()=>{});if(!i?.token)throw new H("dev-login 响应缺 token");return i.token}function Il(e){if(e==null)return;let t=typeof e=="string"?e:JSON.stringify(e);return t.length>_o?`${t.slice(0,_o)}…`:t}function Al(e,t){let r=e.type;if(!t.started){if(r==="RUN_FINISHED"||r==="RUN_ERROR")return;t.started=!0}let n=e.toolCallId;switch(r){case"TEXT_MESSAGE_CONTENT":t.reply+=e.delta??"";break;case"TOOL_CALL_START":{let s=e.toolCallName;s==="__tbox_pretool"?t.preTools.set(n??`pre-${t.preTools.size}`,{}):t.toolCalls.set(n??`tool-${t.toolCalls.size}`,{name:s,argsBuf:""});break}case"TOOL_CALL_ARGS":{if(n===void 0)break;let s=t.toolCalls.get(n);if(s){s.argsBuf=(s.argsBuf??"")+(e.delta??"");break}let i=t.preTools.get(n);i&&(i.argsRaw=(i.argsRaw??"")+(e.delta??""));break}case"TOOL_CALL_RESULT":{if(n===void 0)break;let s=t.toolCalls.get(n);if(s){e.isError===!0&&(s.isError=!0),s.resultSummary=Il(e.content);break}let i=t.preTools.get(n);i&&(i.resultRaw=typeof e.content=="string"?e.content:JSON.stringify(e.content??""));break}case"CUSTOM":if(e.name==="tbox:card"){let s=e.value;s?.cardType&&t.cards.push({cardType:s.cardType,status:s.status??"ready"})}break;case"RUN_FINISHED":t.runStatus="finished";break;case"RUN_ERROR":t.runStatus="error";break;case"SUPPORT_ERROR":t.runStatus="error";break;default:break}}function Ol(e,t,r,n,s){let i=[];for(let a of e.toolCalls.values()){let c;if(a.argsBuf)try{c=JSON.parse(a.argsBuf)}catch{c=void 0}i.push({name:a.name,...c!==void 0?{args:c}:{},...a.isError?{isError:!0}:{},...a.resultSummary!==void 0?{resultSummary:a.resultSummary}:{}})}let o=[];for(let a of e.preTools.values()){let c=a.toolName,l;try{a.argsRaw&&(c=JSON.parse(a.argsRaw).toolName??c)}catch{}try{a.resultRaw&&(l=JSON.parse(a.resultRaw).status)}catch{}c&&o.push({name:c,outcome:l==="error"?"error":l==="done"?"ok":l??"ok"})}return{index:t,conversationId:r,userText:n,reply:e.reply,toolCalls:i,preTools:o,cards:e.cards,runStatus:e.runStatus,durationMs:s}}function Pl(e){let t=e.WebSocketCtor??globalThis.WebSocket;if(typeof t!="function")throw new H("当前 Node 缺少全局 WebSocket:评测驱动需要 Node ≥22(仓库基准 Node 24)");return t}async function Vo(e,t,r,n,s){let i=await Ir(e.baseUrl,r,e.fetchFn),o=`${e.baseUrl.replace(/^http/,"ws")}/ws`,a=Pl(e),c=await new Promise((f,g)=>{let m,h=setTimeout(()=>g(new H(`WS 连接超时(${o})`)),Cl);try{m=new a(o)}catch(R){clearTimeout(h),g(new H(`WS 创建失败: ${R.message}`));return}let v=()=>{$(),f(m)},S=()=>{$(),g(new H(`WS 连接失败(${o})`))},$=()=>{clearTimeout(h),m.removeEventListener("open",v),m.removeEventListener("error",S)};m.addEventListener("open",v),m.addEventListener("error",S)}),l=[],p=()=>{},u=f=>{p(`WS 传输层故障(${f.type})`)};c.addEventListener("error",u),c.addEventListener("close",u);let d=e.driver??"agui";try{let f;if(await new Promise((v,S)=>{let $=A=>{let N=A.data;try{let P=JSON.parse(N);if(P.type==="HELLO_ACK"){let w=P.supportSession;typeof w?.publicConversationId=="string"&&(f=w.publicConversationId)}["RUN_STARTED","HELLO_ACK","TEXT_MESSAGE_START"].includes(P.type)&&(R(),v())}catch{}},R=()=>{clearTimeout(F),c.removeEventListener("message",$)},F=setTimeout(()=>{R(),v()},Tl);c.addEventListener("message",$);try{c.send(JSON.stringify({type:"HELLO",token:i}))}catch(A){R(),S(new H(`WS send 失败(连接已关闭?): ${A.message}`))}}),d==="support"&&f===void 0)throw new H("support 驱动未取得服务端会话 id(HELLO_ACK 缺席或无 supportSession.publicConversationId)");let g=1,m=d==="support"?f:`evalconv-${n}-${t.id}-s${g}`,h=!1;for(let v=0;v<t.turns.length;v++){let S=t.turns[v];if(v>0&&S.newSession){if(d==="support")throw new H(`support 驱动暂不支持 newSession(用例 ${t.id})——会话绑定在连接上,跨会话需重连握手`);g+=1,m=`evalconv-${n}-${t.id}-s${g}`}let $={reply:"",toolCalls:new Map,preTools:new Map,cards:[],runStatus:"timeout",started:!h},R=Date.now();await new Promise((F,A)=>{p=M=>{w(),A(new H(M))};let P=M=>{try{let b=JSON.parse(M.data);Al(b,$),$.runStatus!=="timeout"&&(w(),F())}catch{}},w=()=>{clearTimeout(D),c.removeEventListener("message",P),p=()=>{}},D=setTimeout(()=>{w(),F()},s);c.addEventListener("message",P);try{c.send(JSON.stringify({type:"SEND_MESSAGE",content:S.text,conversationId:m}))}catch(M){w(),A(new H(`WS send 失败(连接已关闭?): ${M.message}`))}}),l.push(Ol($,v,m,S.text,Date.now()-R)),h=$.runStatus==="timeout",d==="support"&&v<t.turns.length-1&&await new Promise(F=>setTimeout(F,Ml))}}finally{p=()=>{};try{c.removeEventListener("error",u),c.removeEventListener("close",u),c.close()}catch{}}return{caseId:t.id,devUser:r,turns:l}}async function Ll(e,t){let r;try{r=await Lo(e)}catch{return}for(let n of r){let s;try{s=await Lo(Fo(e,n))}catch{continue}for(let i of s){let o=Fo(e,n,i,"conversations",t,"journal.jsonl");try{return await No(o,"utf-8"),o}catch{continue}}}}async function Jo(e,t){try{let r={},n=[...new Set(e.turns.map(s=>s.conversationId))];for(let s of n){let i=await Ll(t,s);if(!i)continue;let o=await No(i,"utf-8");for(let a of o.split(`
|
|
249
|
+
`)){if(!a.trim())continue;let c;try{c=JSON.parse(a)}catch{continue}if(c.type==="intent_route"&&c.data?.continued===!1&&(r.intentDirectOut=!0),c.type==="run_end"){let l=c.data?.llm;l&&(r.inputTokens=(r.inputTokens??0)+(l.inputTokens??0),r.outputTokens=(r.outputTokens??0)+(l.outputTokens??0))}}}return{...e,...Object.keys(r).length>0?{journal:r}:{}}}catch(r){return console.warn(`[eval] journal 富化跳过: ${r.message}`),e}}var Fl="__tbox_",_l="agent-ui-cards";function Bt(e){return!e.startsWith(Fl)&&!e.startsWith(_l)}function Uo(e,t,r="agui"){let n=r==="support"?{dim:"tools",status:"na",detail:["support 驱动:TOOL_CALL_* 不出边界,tools 维不判定"]}:zl(e.tools,t);return[Nl(t),n,Bl(e,t)]}function Nl(e){let t=e.turns.filter(r=>r.runStatus!=="finished");return t.length===0?{dim:"run",status:"pass",detail:[]}:{dim:"run",status:"fail",detail:t.map(r=>`第 ${r.index+1} 轮 runStatus=${r.runStatus}`)}}function Bo(e){return typeof e=="string"?e:Array.isArray(e)?`[${e.join(" + ")}]`:`any_of(${e.any_of.map(Bo).join(" | ")})`}function Vl(e){let t=new Set;for(let r of e.turns)for(let n of r.toolCalls)Bt(n.name)&&t.add(n.name);return t}function Jl(e){let t=new Set;for(let r of e.turns)for(let n of r.preTools)n.outcome!=="skip"&&t.add(n.name);return t}function Wo(e,t,r){return typeof e=="string"?zo(e,t,r):Array.isArray(e)?e.every(n=>zo(n,t,r)):e.any_of.some(n=>Wo(n,t,r))}function zo(e,t,r){return e==="none"||e==="direct-out"?t.size===0:t.has(e)||r.has(e)}function Ho(e){return typeof e=="string"?e==="none"||e==="direct-out"?[]:[e]:Array.isArray(e)?e:e.any_of.flatMap(t=>Ho(t))}function zl(e,t){let r=Vl(t),n=Jl(t),s=new Set(e.optional),i=new Set([...r].filter(f=>!s.has(f))),o=new Set([...r,...n]),a=[],c=!1,l=Ho(e.expect).filter(f=>!r.has(f)&&n.has(f));Wo(e.expect,i,o)?l.length>0&&a.push(`note: expect 经 preTool 预执行命中 [${l.sort().join(", ")}](模型侧未显式调用)`):(c=!0,a.push(`期望 ${Bo(e.expect)},实际业务调用 [${[...r].sort().join(", ")||"无"}] / preTool [${[...n].sort().join(", ")||"无"}]`)),e.expect==="direct-out"&&i.size===0&&t.journal?.intentDirectOut!==!0&&a.push("degraded: journal 缺席或无 intent_route 佐证,direct-out 退化为 none 判定");let u=e.forbid.filter(f=>i.has(f));u.length>0&&(c=!0,a.push(`禁用工具被调用: [${u.join(", ")}]`));let d=e.pre.filter(f=>!n.has(f));return d.length>0&&(c=!0,a.push(`期望 preTool [${d.join(", ")}] 未命中,实际 preTool [${[...n].sort().join(", ")||"无"}]`)),c?{dim:"tools",status:e.mode==="soft"?"warning":"fail",detail:a}:{dim:"tools",status:"pass",detail:a}}function Ul(e){let t=new Set;for(let r of e.turns)for(let n of r.cards)n.status!=="error"&&t.add(n.cardType);return t}function Bl(e,t){if(!e.cards)return{dim:"cards",status:"na",detail:[]};let r=e.cards.expect,n=typeof r=="string"?[r]:Array.isArray(r)?r:r.any_of.flatMap(o=>typeof o=="string"?[o]:o),s=Ul(t);return(Array.isArray(r)||typeof r=="string"?n.every(o=>s.has(o)):n.some(o=>s.has(o)))?{dim:"cards",status:"pass",detail:[]}:{dim:"cards",status:"fail",detail:[`期望卡片 [${n.join(", ")}] 未齐,实际出卡 [${[...s].sort().join(", ")||"无"}]`]}}import{mkdir as Ko,writeFile as Wt}from"fs/promises";import{join as Fe}from"path";function Go(e){let t={},r={priority:{},primary_category:{},case_type:{}},n=[],s=0,i=(c,l)=>{let p=t[c]??{pass:0,fail:0,na:0};l==="pass"||l==="warning"?p.pass+=1:l==="fail"?p.fail+=1:p.na+=1,t[c]=p},o=(c,l,p)=>{let u=r[c][l]??{pass:0,total:0};u.total+=1,p&&(u.pass+=1),r[c][l]=u};for(let c of e.results){if(c.spec.skip){n.push({id:c.spec.id,status:"skipped",skipReason:c.spec.skip,dims:{},failures:[]});continue}s+=c.trace?.turns.length??0;let l={},p=[];for(let g of c.dims)l[g.dim]=g.status,i(g.dim,g.status),g.status==="fail"&&p.push(`${g.dim}: ${g.detail.join(";")}`);let u=c.textStatus;l.text=u==="pass"?"pass":u==="fail"?"fail":"na",u==="pass"?i("text","pass"):u==="fail"?(i("text","fail"),p.push(...c.textFailures&&c.textFailures.length>0?c.textFailures:["text: LLM 判定失败(无明细)"])):i("text","na");let d;c.notExecuted?(d="inconclusive",p.push("未执行:infra-aborted 熔断中止")):p.length>0?d="fail":u==="inconclusive"?(d="inconclusive",p.push("text: 全部断言 unclear(judge 无法判定)")):d="pass",n.push({id:c.spec.id,status:d,dims:l,failures:p});let f=d==="pass";o("priority",c.spec.priority,f),o("primary_category",c.spec.tags[0]??"<none>",f),o("case_type",c.spec.tags[2]??"<none>",f)}let a={version:1,runId:e.runId,startedAt:e.startedAt,finishedAt:e.finishedAt,dataset:{name:e.datasetName,hash:e.datasetHash,caseCount:e.results.length},rubricHash:e.rubricHash,judgeModel:e.judgeModel,instance:e.instance,devUserPrefix:e.devUserPrefix,dimensions:t,cases:n.sort((c,l)=>c.id<l.id?-1:c.id>l.id?1:0),slices:r,ops:{durationMs:new Date(e.finishedAt).getTime()-new Date(e.startedAt).getTime(),turnCount:s,infraErrors:e.ops.infraErrors,aborted:e.ops.aborted,llmTokens:e.ops.llmTokens,judgeFailures:e.ops.judgeFailures}};return Qe.parse(a)}function qo(e,t){let r=new Map(t.cases.map(i=>[i.id,i.status])),n=new Map(e.cases.map(i=>[i.id,i.status])),s={newFailed:[],fixed:[],flipped:[]};for(let[i,o]of n){let a=r.get(i);a===void 0||a===o||(a!=="fail"&&o==="fail"?s.newFailed.push(i):a==="fail"&&o!=="fail"?s.fixed.push(i):s.flipped.push({id:i,from:a,to:o}))}return s}async function Xo(e,t,r,n){await Ko(Fe(e,"traces"),{recursive:!0}),await Wt(Fe(e,"report.json"),JSON.stringify(t,null,2),"utf-8"),await Wt(Fe(e,"report.md"),Hl(t),"utf-8");for(let[s,i]of r)await Wt(Fe(e,"traces",`${s}.json`),JSON.stringify(i,null,2),"utf-8");if(n.size>0){await Ko(Fe(e,"judge"),{recursive:!0});for(let[s,i]of n)await Wt(Fe(e,"judge",`${s}.md`),i,"utf-8")}}function Wl(e){return e.sort((t,r)=>r[1].total-t[1].total).map(([t,r])=>`${t} ${r.pass}/${r.total}`).join(" · ")}function Hl(e,t){let r=c=>e.cases.filter(l=>l.status===c),n=r("fail"),s=r("inconclusive"),i=r("skipped"),o=[];o.push(`# 评测报告 ${e.runId}`,""),o.push(`- 数据集: ${e.dataset.name}(${e.dataset.caseCount} 例,hash ${e.dataset.hash.slice(0,12)})`),o.push(`- 实例: ${e.instance.baseUrl}${e.instance.degraded?"(degraded:journal 不可达)":""}`),o.push(`- judge: ${e.judgeModel??"未启用"} rubric: ${e.rubricHash.slice(0,12)}`);let a=["pass","fail","inconclusive","skipped"].map(c=>`${c}=${r(c).length}`).join(" ");o.push(`- 结果: ${a} 用时 ${(e.ops.durationMs/1e3).toFixed(0)}s turns=${e.ops.turnCount}${e.ops.aborted?" ⚠️ infra-aborted(熔断)":""}${e.ops.judgeFailures>0?` ⚠️ judge 失败 ${e.ops.judgeFailures} 例(text 维降级,需人工复核)`:""}`,""),o.push("## 维度","");for(let[c,l]of Object.entries(e.dimensions))o.push(`- ${c}: pass=${l.pass} fail=${l.fail} na=${l.na}`);o.push("","## 切片","");for(let[c,l]of Object.entries(e.slices))o.push(`- ${c}: ${Wl(Object.entries(l))}`);if(t&&(o.push("","## 基线对比",""),o.push(`- 新失败: ${t.newFailed.length>0?t.newFailed.join(", "):"无"}`),o.push(`- 已修复: ${t.fixed.length>0?t.fixed.join(", "):"无"}`),t.flipped.length>0&&o.push(`- 翻转: ${t.flipped.map(c=>`${c.id} ${c.from}→${c.to}`).join(", ")}`)),n.length>0){o.push("","## 失败用例","");for(let c of n){o.push(`### ${c.id}(${Object.entries(c.dims).filter(([,l])=>l==="fail").map(([l])=>l).join("+")||"?"})`);for(let l of c.failures)o.push(`- ${l}`);o.push("")}}if(s.length>0){o.push("## Inconclusive(judge 全 unclear,不计失败)","");for(let c of s)o.push(`- ${c.id}`);o.push("")}if(i.length>0){o.push("## Skipped","");for(let c of i)o.push(`- ${c.id}(${c.skipReason})`);o.push("")}return o.join(`
|
|
250
|
+
`)}function Yo(e){return{userText:e.userText,reply:e.reply.length>4e3?`${e.reply.slice(0,4e3)}…`:e.reply,tools:e.toolCalls.filter(t=>Bt(t.name)).map(t=>({name:t.name,...t.resultSummary!==void 0?{result:t.resultSummary}:{}})),framework:e.toolCalls.filter(t=>!Bt(t.name)).map(t=>t.name),preTools:e.preTools.map(t=>`${t.name}(${t.outcome})`),cards:e.cards.map(t=>({cardType:t.cardType,status:t.status})),runStatus:e.runStatus}}import{z as _e}from"zod";function Zo(e){let t=e.EVAL_JUDGE_BASE_URL??e.APP_AI_BASE_URL,r=e.EVAL_JUDGE_API_KEY??e.APP_AI_API_KEY,n=e.EVAL_JUDGE_MODEL??e.APP_AI_MODEL_NAME;if(!t||!r||!n)throw new Error(`评测 judge 配置缺失(需 EVAL_JUDGE_BASE_URL/API_KEY/MODEL,或回退 APP_AI_BASE_URL/API_KEY/MODEL_NAME;缺: ${[!t&&"BASE_URL",!r&&"API_KEY",!n&&"MODEL"].filter(Boolean).join(", ")})`);return{baseUrl:t.replace(/\/$/,""),apiKey:r,model:n}}function Kl(e){let t=e.match(/```(?:json)?\s*([\s\S]*?)\s*```/);return t?t[1]:e}async function Qo(e,t,r,n,s){let i=s?.fetchFn??fetch,o=[{role:"system",content:t},{role:"user",content:r}],a="",c=3;for(let l=0;l<c;l++){l>0&&await new Promise(f=>setTimeout(f,800*l));let p=l===0?r:`${r}
|
|
251
|
+
|
|
252
|
+
(上次输出无法解析为 JSON,请只输出 JSON,不要任何其他文字)`,u;try{u=await i(`${e.baseUrl}/chat/completions`,{method:"POST",headers:{"content-type":"application/json",authorization:`Bearer ${e.apiKey}`,"accept-encoding":"identity"},body:JSON.stringify({model:e.model,temperature:0,messages:l===0?o:[o[0],{role:"user",content:p}]})})}catch(f){throw new Error(`judge LLM 请求失败: ${f.message}`)}if(!u.ok){let f=await u.text().catch(()=>"");throw new Error(`judge LLM HTTP ${u.status}: ${f.slice(0,300)}`)}if(a=(await u.json().catch(()=>{}))?.choices?.[0]?.message?.content??"",!!a.trim())try{return{parsed:n.parse(JSON.parse(Kl(a))),raw:a}}catch{}}throw new Error(`judge 输出无法解析为 JSON(${c} 次尝试): ${a.slice(0,300)||"<空响应>"}`)}var Gl=_e.object({assertions:_e.array(_e.object({text:_e.string().min(1),verdict:_e.enum(["pass","fail","unclear"]),reason:_e.string().min(1)})).min(1)});function ql(e,t,r){let n=["你是智能体行为评测裁判。依据评审标准,对每条断言独立判定 pass/fail/unclear。","判定基于证据中的实际回复与工具出参(判「如实」而非「应然」)。","证据轨别:tools 是模型主动调用;preTools 是系统预执行(非模型决策);__tbox_ 前缀是框架伪工具。判定「模型是否调用/误路由」类断言时只依据 tools。","断言按语义满足判定,不逐字匹配文案;断言与评审标准冲突、或断言前提在当前对话不成立时(如要求重试建议但重试已成功),以评审标准和实际行为为准。","证据不足或断言在当前对话中无法验证时判 unclear,不要猜测。",'只输出 JSON:{"assertions":[{"text":"断言原文","verdict":"pass|fail|unclear","reason":"判定理由"}]}',"","评审标准:",...r.standards.map((o,a)=>/^\d+[.、.]\s*/.test(o)?o:`${a+1}. ${o}`)].join(`
|
|
253
|
+
`),s=t.turns.map((o,a)=>({turn:a+1,...Yo(o)})),i=[`用例 ${e.id}(priority=${e.priority})`,`证据:${JSON.stringify(s)}`,"断言:",...e.assertions.map((o,a)=>`${a+1}. ${o}`)];return e.judge_hints.length>0&&i.push("评审备注(附加上下文,优先级低于评审标准):",...e.judge_hints.map(o=>`- ${o}`)),i.push("逐条输出判定 JSON。"),{system:n,user:i.join(`
|
|
254
|
+
`)}}function ei(e){let t=e.some(n=>n.verdict==="fail"),r=e.some(n=>n.verdict==="pass");return t?{status:"fail",failures:e.filter(n=>n.verdict==="fail").map(n=>`${n.text} —— ${n.reason}`)}:r?{status:"pass",failures:[]}:{status:"inconclusive",failures:[]}}async function ti(e,t,r,n,s){let{system:i,user:o}=ql(r,n,t),{parsed:a,raw:c}=await Qo(e,i,o,Gl,s),l=new Map(a.assertions.map(u=>[u.text.trim(),u]));return{verdicts:r.assertions.map(u=>l.get(u.trim())??{text:u,verdict:"unclear",reason:"judge 输出缺失该断言"}),raw:c}}import{spawn as ri,spawnSync as Xl}from"child_process";import{existsSync as si,openSync as Yl,readFileSync as Zl}from"fs";import{rm as Ql,writeFile as ep}from"fs/promises";import{join as be}from"path";var Or="apps/server",Ar="dist/index.js",Pr=".tbox-server.json",ni=".tbox-server.log",oi=60,tp=1e3;function Lr(e){return si(be(e,Or,"package.json"))?e:null}function tt(e){let t;try{t=Zl(be(e,Pr),"utf-8")}catch{return null}try{return Mo.parse(JSON.parse(t))}catch{return null}}async function rp(e,t){await ep(be(e,Pr),JSON.stringify(t,null,2),"utf-8")}async function Kt(e){await Ql(be(e,Pr),{force:!0})}function np(e){return`${e.replace(/\/$/,"")}/api/health`}async function Ht(e,t){let r=t?.attempts??oi,n=t?.intervalMs??tp;for(let s=0;s<r;s++){try{if((await fetch(np(e))).ok)return!0}catch{}await new Promise(i=>setTimeout(i,n))}return!1}async function ii(e){if(!Lr(e.appDir))throw new Error(`当前目录不是应用根(缺 ${Or}/package.json): ${e.appDir}`);let t=be(e.appDir,Or),r=be(t,Ar);if(e.build){let l=Xl("pnpm",["--filter","@app/server","build"],{cwd:e.appDir,stdio:"inherit"});if(l.status!==0)throw new Error(`构建失败(pnpm --filter @app/server build,exit=${l.status})`)}if(!si(r))throw new Error("应用未构建:先 --build,或使用 `tbox-app eval run --base-url` 评测外部实例");let n=tt(e.appDir);if(n&&await Ht(n.baseUrl,{attempts:2,intervalMs:500}))throw new Error(`已在运行 ${n.baseUrl}(pid=${n.pid});tbox-app stop 后再启动`);await Kt(e.appDir);let s=e.port??8e3,i=be(e.appDir,".eval-journal"),o={version:1,pid:0,port:s,baseUrl:`http://127.0.0.1:${s}`,journalRoot:i,startedAt:new Date().toISOString(),detached:e.detached},a;if(e.detached){let l=Yl(be(e.appDir,ni),"a");a=ri("node",[Ar],{cwd:t,env:{...process.env,PORT:String(s),TBOX_LOCAL_LOG:"on",TBOX_LOCAL_LOG_DIR:i,TBOX_APP_ID:"eval"},stdio:["ignore",l,l],detached:!0}),a.unref()}else a=ri("node",[Ar],{cwd:t,env:{...process.env,PORT:String(s),TBOX_LOCAL_LOG:"on",TBOX_LOCAL_LOG_DIR:i,TBOX_APP_ID:"eval"},stdio:"inherit"});if(o.pid=a.pid??0,!await Ht(o.baseUrl)){try{e.detached?process.kill(-o.pid,"SIGKILL"):a.kill("SIGKILL")}catch{}throw new Error(`server ${oi}s 未就绪(${o.baseUrl};日志 ${ni})`)}if(!(()=>{try{return process.kill(o.pid,0),!0}catch{return!1}})()||!e.detached&&a.exitCode!==null)throw new Error(`端口 ${s} 已被其他进程占用(health 来自占用者);--port 换端口后重试`);return await rp(e.appDir,o),{child:a,state:o}}async function ai(e,t){await sp(t.pid,t.detached,void 0),await Kt(e)}async function sp(e,t,r){let n=()=>{try{return process.kill(e,0),!0}catch{return!1}};if(!n())return;if(t)try{process.kill(-e,"SIGTERM")}catch{}else{r?.kill("SIGTERM");try{process.kill(e,"SIGTERM")}catch{}}let s=Date.now()+2e3;for(;n()&&Date.now()<s;)await new Promise(i=>setTimeout(i,100));if(n())try{t?process.kill(-e,"SIGKILL"):process.kill(e,"SIGKILL")}catch{}}var li=5;function pe(e,t){e||console.log(`[eval] ${t}`)}function ap(e,t){if(t.length===0)return e;let r=new Map;for(let s of t){let i=s.indexOf("=");if(i<=0)throw new Error(`--filter 形状非法: "${s}"(应为 key=value)`);let o=s.slice(0,i),a=s.slice(i+1);if(!["priority","tag","id","skip"].includes(o))throw new Error(`--filter 未知键: "${o}"(支持 priority/tag/id/skip)`);let c=r.get(o)??[];c.push({key:o,value:a}),r.set(o,c)}let n=(s,i,o)=>{switch(i){case"priority":return s.priority===o;case"id":return s.id===o;case"tag":return s.tags.includes(o);case"skip":return s.skip===o;default:return!1}};return e.filter(s=>[...r.values()].every(i=>i.some(o=>n(s,o.key,o.value))))}async function pi(e){if(e.max!==void 0&&(!Number.isInteger(e.max)||e.max<1))throw new Error("--max 需为 ≥1 的整数");if(!Number.isInteger(e.concurrency)||e.concurrency<1)throw new Error("--concurrency 需为 ≥1 的整数");let t=await Ut(e.dataset),r=ap(t.cases,e.filters);if(e.rerunFailedDir){let w=await ci(Fr(e.rerunFailedDir,"report.json"),"utf-8"),D=Qe.parse(JSON.parse(w)),M=new Set(D.cases.filter(b=>b.status==="fail"||b.status==="inconclusive").map(b=>b.id));r=r.filter(b=>M.has(b.id)),r.length===0&&pe(e.quiet,"--rerun-failed: 上次无 fail/inconclusive 用例,空跑")}e.max!==void 0&&(r=r.slice(0,e.max));let n,s=null,i=null,o=e.journalRoot??null;if(e.baseUrl)n=e.baseUrl.replace(/\/$/,"");else{let w=tt(e.cwd);if(!w)throw new Error("未发现可用实例:先 `tbox-app start -d`(状态文件 .tbox-server.json)或传 --base-url <url>");if(!await Ht(w.baseUrl,{attempts:3,intervalMs:500}))throw new Error(`状态文件实例不可达(${w.baseUrl}):实例可能已退出,重新 start -d 或传 --base-url`);n=w.baseUrl,s=w.pid,i=w.startedAt,o=o??w.journalRoot}let a=e.judge?Zo(process.env):null,c=op(4).toString("hex"),l=`eval-${c}`,p=e.outDir??Fr(e.cwd,".eval","runs",`${lp()}-${c}`),u=new Date().toISOString();pe(e.quiet,`runId=${c} dataset=${t.suite.name} cases=${r.length} concurrency=${e.concurrency} judge=${a?a.model:"off"}`),pe(e.quiet,`instance=${n}${o?` journal=${o}`:"(journal 缺席,direct-out 降级)"}`);let d=[];for(let w of r)w.skip&&d.push({spec:w,dims:[]});let f=new Map,g=new Map,m=0,h=0,v=!1,S=0,$=r.filter(w=>!w.skip),R=[...$],F=async()=>{for(;;){if(v)return;let w=R.shift();if(!w)return;let D=`eval-${c}-${w.id}`,M,b;try{M=await Vo({baseUrl:n,driver:t.suite.driver??"agui",...e.deps},w,D,c,t.suite.defaultTurnTimeoutMs),o&&(M=await Jo(M,o)),f.set(w.id,M)}catch(j){if(j instanceof H){m+=1,h+=1,h>=li&&(v=!0),d.push({spec:w,dims:[{dim:"run",status:"fail",detail:[j.message]}]}),pe(e.quiet,`✗ ${w.id} infra: ${j.message}`),v&&pe(e.quiet,`熔断(连续 ${li} 个 infra 错误),中止余量 ${R.length} 例`);continue}throw j}h=0;let k=Uo(w,M,t.suite.driver??"agui");if(a)try{let{verdicts:j,raw:ee}=await ti(a,t.rubric,w,M,e.deps);g.set(w.id,ee),b=ei(j)}catch(j){S+=1,b={status:"inconclusive",failures:[`judge 调用失败: ${j.message}`]},pe(e.quiet,`⚠ ${w.id} judge 失败: ${j.message}`)}d.push({spec:w,trace:M,dims:k,textStatus:b?.status,textFailures:b?.failures});let O=k.filter(j=>j.status==="fail").map(j=>j.dim);pe(e.quiet,`${O.length>0||b?.status==="fail"?"✗":"✓"} ${w.id}${O.length>0?` (${O.join(",")}${b?.status==="fail"?",text":""})`:""}`)}};await Promise.all(Array.from({length:Math.max(1,Math.min(e.concurrency,16))},F));for(let w of $)d.some(D=>D.spec.id===w.id)||d.push({spec:w,dims:[],notExecuted:!0});let A=[...f.values()].reduce((w,D)=>w+(D.journal?.inputTokens??0)+(D.journal?.outputTokens??0),0);await cp(n,$.map(w=>`eval-${c}-${w.id}`),e.deps?.fetchFn,e.quiet);let N=Go({runId:c,startedAt:u,finishedAt:new Date().toISOString(),datasetName:t.suite.name,datasetHash:t.datasetHash,rubricHash:t.rubricHash,judgeModel:a?.model??null,instance:{baseUrl:n,pid:s,startedAt:i,journalRoot:o,degraded:!o},devUserPrefix:l,results:d,ops:{infraErrors:m,aborted:v,llmTokens:A>0?A:null,judgeFailures:S}}),P;if(e.baselinePath){let w=await ci(e.baselinePath,"utf-8"),D=Qe.parse(JSON.parse(w));P=qo(N,D)}return await ip(p,{recursive:!0}),await Xo(p,N,f,g),console.log(`报告: ${Fr(p,"report.md")}`),process.env.EVAL_REPORT==="json"&&console.log(JSON.stringify(N)),P&&pe(e.quiet,`基线对比: 新失败=${P.newFailed.length} 已修复=${P.fixed.length} 翻转=${P.flipped.length}`),{exitCode:v?2:0,runDir:p,report:N}}async function cp(e,t,r,n){let s=r??fetch;for(let i of t)try{let o=await Ir(e,i,s),a=await s(`${e}/api/memory`,{headers:{authorization:`Bearer ${o}`}});if(!a.ok)continue;let l=((await a.json().catch(()=>{}))?.records??[]).map(p=>p.id).filter(p=>typeof p=="string");for(let p of l)await s(`${e}/api/memory/${encodeURIComponent(p)}`,{method:"DELETE",headers:{authorization:`Bearer ${o}`}}).catch(()=>{});l.length>0&&pe(n??!1,`KB 清理 ${i}: ${l.length} 条`)}catch(o){console.warn(`[eval] KB 清理跳过 ${i}: ${o.message}`)}}function lp(){let e=new Date,t=(r,n=2)=>String(r).padStart(n,"0");return`${e.getFullYear()}${t(e.getMonth()+1)}${t(e.getDate())}-${t(e.getHours())}${t(e.getMinutes())}${t(e.getSeconds())}`}async function ui(e){let t=await Ut(e.dataset),r=Po(t);console.log(`✅ 数据集校验通过: ${t.suite.name}(${t.dir})`),console.log(` datasetHash: ${t.datasetHash.slice(0,12)} rubricHash: ${t.rubricHash.slice(0,12)}`),console.log(` 词表: ${t.manifest?`tools=${t.manifest.tools.size} cards=${t.manifest.cards.size}(tools-manifest.yaml)`:"未提供(跳过词表校验)"}`),console.log(` 用例: ${r.total}(参评 ${r.evaluated}) 多轮: ${r.multiTurnCases} soft 模式: ${r.softModeCases}`);let n=Object.entries(r.skippedByReason);n.length>0&&console.log(` skip: ${n.map(([a,c])=>`${a}=${c}`).join(" ")}`);let s=Object.entries(r.byPriority).sort(([a],[c])=>a.localeCompare(c));console.log(` priority: ${s.map(([a,c])=>`${a}=${c}`).join(" ")}`);let i=Object.entries(r.sessionHistogram).sort(([a],[c])=>Number(a)-Number(c));console.log(` 会话数分布: ${i.map(([a,c])=>`${a}会话=${c}`).join(" ")}`);let o=Object.entries(r.byCaseType).sort(([a],[c])=>a.localeCompare(c));console.log(` case_type: ${o.map(([a,c])=>`${a}=${c}`).join(" ")}`)}async function di(e){let t=await pi({...e,cwd:process.cwd()});process.exitCode=t.exitCode}async function fi(e){let t=process.cwd();if(!Lr(t))throw new Error("当前目录不是应用根(缺 apps/server/package.json);请在生成应用目录执行");let r=await ii({appDir:t,port:e.port,build:e.build,detached:e.detached===!0});if(e.detached){console.log(`✅ server 运行中 ${r.state.baseUrl}(pid=${r.state.pid},日志 .tbox-server.log,journal .eval-journal/)`),console.log(" 停止: tbox-app stop 评测: tbox-app eval run --dataset <dir>");return}let n=r.child,s=i=>{n.kill(i)};process.on("SIGINT",()=>s("SIGINT")),process.on("SIGTERM",()=>s("SIGTERM")),console.log(`✅ server 运行中 ${r.state.baseUrl}(前台,Ctrl+C 停止)`),await new Promise(i=>{n.on("exit",()=>i())}),await Kt(t),console.log("server 已退出")}import{spawnSync as pp}from"child_process";import{existsSync as up}from"fs";import{join as dp}from"path";async function mi(){let e=process.cwd(),t=tt(e);if(t){if(t.detached)await ai(e,t);else{console.log(`server 运行中(前台 pid=${t.pid},${t.baseUrl})——在其终端 Ctrl+C 停止`);return}console.log(`✅ 已停止(pid=${t.pid})`);return}let r=dp(e,"scripts","stop.sh");if(up(r)){let n=pp("bash",["scripts/stop.sh"],{stdio:"inherit"});if(n.status!==0)throw new Error(`scripts/stop.sh 退出码 ${n.status}`);return}console.log("未发现运行中的 server(无 .tbox-server.json)")}function yp(){let e=new fp,t=mp(import.meta.url)("../package.json");e.name("tbox-app").description("tbox 组件化应用 CLI:starter / create / module add|remove|list|sync|diff|doctor / start|stop|eval").version(t.version),e.command("create <target>").description("从模板生成多包 workspace 应用骨架").option("--name <package-name>","生成应用 package.json.name(当前目录初始化时建议显式指定)").option("--source <路径>","模板源(本地目录 / 模板 pack tgz / registry 包 @scope/pkg[@version])").option("--local-deps <tgz目录>","注入平台 overrides(file: 本地发布态 tgz,install 离线验证态;生产 B 档不传)").option("--config-dir <目录>","合并应用配置目录进应用 config/(同名覆盖/异名保留;可重复传,后者覆盖前者同名文件——独立完整配置档单目录注入为主流用法;通用能力,与模板/registry 源正交)",(o,a)=>[...a,o],[]).option("--env-file <路径>","环境变量文件(结合 --config-dir 使用:拷入应用 config/env.sh——启动脚本 source 的档位环境;镜像部署链 build-app-image 使用)",o=>o).option("--registry <url>","拉取 registry(registry 源时生效;缺省自动解析环境镜像)").option("--force","覆盖已存在目录").action(async(o,a)=>{try{await dn({name:o,appName:a.name,source:a.source??"",force:a.force,localDeps:a.localDeps,configDir:a.configDir,envFile:a.envFile,registry:a.registry})}catch(c){console.error("❌",c.message),process.exitCode=1}}),e.command("starter").description("查询 starter 文档与可用类型").option("--type <agent|agent-mall|agent-support>","starter 类型").option("--list","列出可用 starter 类型").option("--json","以 JSON 输出(仅配合 --list)").action(o=>{try{Ro(o)}catch(a){console.error("❌",a.message),process.exitCode=1}});let r=e.command("module").description("模块管理(add/remove/list/sync/diff/doctor/update/publish/pack)");r.command("add <pkg>").description("安装/展开模块(--mode local|codegen|sdk;--source 支持目录/tgz/registry 包)").option("--mode <local|codegen|sdk>","分发模式","codegen").option("--source <路径>","模块源(本地目录/tgz/registry 包 @scope/pkg[@version])").option("--registry <url>","拉取 registry(registry 源时生效;缺省自动解析环境镜像)").option("--no-deps","跳过依赖自动解析(P3 W3-B 逃生门)").action(async(o,a)=>{try{await zs({pkg:o,mode:a.mode,source:a.source,registry:a.registry,noDeps:!a.deps})}catch(c){console.error("❌",c.message),process.exitCode=1}}),r.command("remove <id>").description("卸载模块(删包 + 清理装配)").option("--force","忽略反向依赖检查强制移除").action(async(o,a)=>{try{await Bs(o,{force:a.force})}catch(c){console.error("❌",c.message),process.exitCode=1}}),r.command("list").description("列出已装模块").action(async()=>{try{await Hs()}catch(o){console.error("❌",o.message),process.exitCode=1}}),r.command("sync").description("增量装配 4 文件(不覆盖;结构破坏降级为告警)").option("--force","全量覆盖装配文件(002 §6.5:唯一覆盖入口)").action(async o=>{try{await Ws({force:o.force})}catch(a){console.error("❌",a.message),process.exitCode=1}}),r.command("diff [module]").description("装配漂移双向 diff(module 可指定,默认全部)").action(async o=>{try{await Ks(o)}catch(a){console.error("❌",a.message),process.exitCode=1}}),r.command("doctor").description("结构校验(doctor 完整版 20 条规则)").option("--json","只输出 JSON 结果").option("--only <规则列表>","只运行指定规则(逗号分隔)").option("--skip <规则列表>","跳过指定规则(逗号分隔)").action(async o=>{try{await Gs(o)}catch(a){console.error("❌",a.message),process.exitCode=1}}),r.command("update <module>").description("升级组件(codegen→3-way merge;sdk→pnpm update)").option("--dry-run","只打印合并结果不写回").option("--ours","冲突快速取本地版本").option("--theirs","冲突快速取新版版本").option("--source <路径>","新版本地源目录或 tgz").option("--base-source <路径>","base 本地源目录或 tgz").action(async(o,a)=>{try{await Qs({module:o,...a})}catch(c){console.error("❌",c.message),process.exitCode=1}}),r.command("pack <module>").description("本地打包不发布(模块目录如 modules/module-member;pnpm pack 内建归一化 → 发布态 tgz)").requiredOption("--out <dir>","产物目录(pnpm 自动创建,同名覆盖写)").option("--check","产物断言(协议残留 / exports / 白名单 / 泄漏 / 文件一致性)").action(async(o,a)=>{try{await no({module:o,out:a.out,check:a.check})}catch(c){console.error("❌",c.message),process.exitCode=1}}),r.command("publish <module>").description("模块晋升 npm(模块目录如 modules/module-member;校验 + 打包 + 产物门禁 + pnpm publish <tgz>)").option("--tgz <path>","复用已验证产物(缺省现场 pack 到临时目录)").option("--dry-run","校验 + 门禁 + 打印,不真实发布").option("--access <public|restricted>","npm access","public").option("--registry <url>","发布 registry(缺省 pnpm 配置解析:@tbox.cn scope 优先)").action(async(o,a)=>{try{await io({module:o,tgz:a.tgz,dryRun:a.dryRun,access:a.access,registry:a.registry})}catch(c){console.error("❌",c.message),process.exitCode=1}});let n=e.command("miniapp-container").description("小程序容器管理(init)");e.command("dev").description("原地快速开发调试(真源直挂:装配物化 + 包身份种植 + 配置面注入;改源码即热重启)").option("--template <dir>","模板/应用根目录(相对仓库根;缺省 mall 模板)").option("--module <dir>","仓库模块目录,可多次传入(依赖闭包自动展开)",(o,a)=>[...a,o],[]).option("--real-env <tier>","real-env 档位(wanda|joycity|mock):config 重定向 + provider 纳入 + env 防呆").option("--config-dir <目录>","独立完整配置档:三路配置(integrations/deployments/config dir)独占注入该目录,不与档位主档叠加(可重复传,后者覆盖前者)。常用 .real-env/config/wanda-intranet 切内网端点",(o,a)=>[...a,o],[]).option("--link-sdk","平台真源在场断言(重定向总表恒含 app-sdk/contracts 兜底;本旗标为显式 identity 强化)").option("--port <n>","服务端口(缺省 8000)",o=>Number(o)).option("--prune","清理构件区与种植链接后退出").action(async o=>{try{await Eo(o)}catch(a){console.error("❌",a.message),process.exitCode=1}});let s=e.command("provider").description("Provider 绑定管理(bind/unbind/use/unuse/migrate——写 config/integrations.json)");s.command("bind <service> <provider> [implementation]").description("绑定服务槽:三参精确钉版(parking.query joycity joycity-c-parking@1);两参派生(implementation 经 catalog 唯一供给推导)").option("--instance <mallId>","per-instance 覆盖位键(mall 场景 = mallId);缺省写槽级默认位").action(async(o,a,c,l)=>{try{await po(o,a,c,{instance:l.instance})}catch(p){console.error("❌",p.message),process.exitCode=1}}),s.command("use <provider>").description("设置默认厂商:root(应用全局)或 --domain(域级);implementation 恒派生,激活仍需槽 instances 在场").option("--domain <domain>","写域级默认(domains.<域前缀>.provider);缺省写应用全局 root.provider").action(async(o,a)=>{try{await uo(o,{domain:a.domain})}catch(c){console.error("❌",c.message),process.exitCode=1}}),s.command("unuse").description("清除默认厂商:root 或 --domain(空域对象/空 domains 一并清除)").option("--domain <domain>","仅清除该域级默认;缺省清除应用全局 root.provider").action(async o=>{try{await fo({domain:o.domain})}catch(a){console.error("❌",a.message),process.exitCode=1}}),s.command("unbind <service>").description("解绑服务槽(--instance 删实例覆盖位;缺省槽与实例集删除)").option("--instance <mallId>","仅删除该实例覆盖位(回落槽级默认)").action(async(o,a)=>{try{await mo(o,{instance:a.instance})}catch(c){console.error("❌",c.message),process.exitCode=1}}),s.command("migrate").description("integrations v2 一次性迁移:旧单串 providerId → 双字段(缺省 dry-run,--write 落盘)").option("--write","落盘(缺省 dry-run 仅报告)").action(async o=>{try{await ho({write:o.write})}catch(a){console.error("❌",a.message),process.exitCode=1}}),n.command("init [platform]").description("初始化小程序容器到 apps/(缺省 alipay;--source 支持目录/tgz/registry 包)").option("--source <source>","容器源:目录 / tgz / registry 包(缺省 @tbox.cn/app-miniapp-container-<platform>)").option("--registry <registry>","拉取 registry(registry 源时生效;缺省自动解析环境镜像)").option("--force","已存在时删除重展开").action(async(o,a)=>{try{await ao({platform:o,source:a.source,registry:a.registry,force:a.force})}catch(c){console.error("❌",c.message),process.exitCode=1}}),e.command("start").description("启动本应用 server(编排语义;前台或 -d 后台;不设 NODE_ENV=production)").option("-d","后台运行(日志 .tbox-server.log,状态 .tbox-server.json)").option("--port <n>","端口(缺省 8000)","8000").option("--build","先构建 apps/server(tsup)").action(async o=>{try{await fi({detached:o.d,port:Number(o.port),build:o.build})}catch(a){console.error("❌",a.message),process.exitCode=1}}),e.command("stop").description("停止本应用 server(状态文件优先,scripts/stop.sh 兜底;幂等)").action(async()=>{try{await mi()}catch(o){console.error("❌",o.message),process.exitCode=1}});let i=e.command("eval").description("智能体行为评测(validate 契约校验 | run 评测编排)");return i.command("validate").description("校验评测数据集契约(零网络零进程;迁移 QA 与 nightly 候选)").requiredOption("--dataset <dir>","数据集目录(含 suite.yaml/rubric.yaml/cases/)").action(async o=>{try{await ui({dataset:o.dataset})}catch(a){console.error("❌",a.message),process.exitCode=1}}),i.command("run").description("执行评测(纯客户端;实例经 --base-url 或 .tbox-server.json 发现)").requiredOption("--dataset <dir>","数据集目录(必传——显式优于聪明)").option("--base-url <url>","外部实例地址(缺省读 <cwd>/.tbox-server.json)").option("--filter <k=v>","过滤用例(priority=P0 / tag=memory / id=<id>;可重复,AND)",(o,a)=>[...a,o],[]).option("--concurrency <n>","并发用例数(缺省 2;wanda 沙箱限速未知时保守起步)","2").option("--no-judge","跳过 text 维 LLM 判定(纯路由快速回路)").option("--baseline <report.json>","基线报告对比(新失败/已修复/翻转)").option("--rerun-failed <runDir>","只重跑上次 run 中 fail+inconclusive 的用例").option("--journal-root <dir>","显式 journal 根(--base-url 模式的 ingest 供给;缺席则降级)").option("--out <dir>","报告目录覆盖(缺省 <cwd>/.eval/runs/<ts>-<runId>)").option("--max <n>","截断用例数(调试用)").option("--quiet","静默进度输出").action(async o=>{try{await di({dataset:o.dataset,baseUrl:o.baseUrl,filters:o.filter,concurrency:Number(o.concurrency),judge:o.judge,baselinePath:o.baseline,rerunFailedDir:o.rerunFailed,journalRoot:o.journalRoot,outDir:o.out,max:o.max?Number(o.max):void 0,quiet:o.quiet??!1})}catch(a){console.error("❌",a.message),process.exitCode=1}}),e}var hp=typeof process.argv[1]=="string"&&gi(gp(import.meta.url))===gi(process.argv[1]);hp&&yp().parseAsync(process.argv);export{yp as createProgram};
|