@allbluecn/kernel 0.2.0 → 0.2.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/LICENSE +661 -0
- package/dist/bin.js +89 -14
- package/dist/index.d.ts +545 -8
- package/dist/index.js +2 -2
- package/dist/src-B6_ZEkl9.js +316 -0
- package/package.json +12 -10
- package/dist/src-Dvcggyr-.js +0 -122
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
//#region src/define-plugin.ts
|
|
2
|
+
const PLUGIN_ID_RE = /^@[a-z0-9-]+\/[a-z0-9-]+$/;
|
|
3
|
+
function definePlugin(def) {
|
|
4
|
+
if (!PLUGIN_ID_RE.test(def.id)) throw new Error(`插件 id 必须形如 '@scope/name'(小写),收到: ${def.id}`);
|
|
5
|
+
if (!/^\d+\.\d+\.\d+/.test(def.version)) throw new Error(`插件 version 必须是语义化版本,收到: ${def.version}`);
|
|
6
|
+
return deepFreeze(def);
|
|
7
|
+
}
|
|
8
|
+
function deepFreeze(value) {
|
|
9
|
+
if (value && typeof value === "object" && !Object.isFrozen(value)) {
|
|
10
|
+
Object.freeze(value);
|
|
11
|
+
for (const key of Object.keys(value)) deepFreeze(value[key]);
|
|
12
|
+
}
|
|
13
|
+
return value;
|
|
14
|
+
}
|
|
15
|
+
//#endregion
|
|
16
|
+
//#region src/define-profile.ts
|
|
17
|
+
const PROFILE_ID_RE = /^[a-z0-9][a-z0-9-]*$/;
|
|
18
|
+
function defineProfile(profile) {
|
|
19
|
+
if (!PROFILE_ID_RE.test(profile.id)) throw new Error(`profile id 只允许小写字母/数字/连字符,收到: ${profile.id}`);
|
|
20
|
+
const seen = /* @__PURE__ */ new Set();
|
|
21
|
+
for (const plugin of profile.plugins) {
|
|
22
|
+
if (seen.has(plugin.id)) throw new Error(`profile 中存在重复插件 id: ${plugin.id}`);
|
|
23
|
+
seen.add(plugin.id);
|
|
24
|
+
}
|
|
25
|
+
return Object.freeze({
|
|
26
|
+
...profile,
|
|
27
|
+
plugins: Object.freeze([...profile.plugins])
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
//#endregion
|
|
31
|
+
//#region src/resolve-order.ts
|
|
32
|
+
function resolvePluginOrder(plugins) {
|
|
33
|
+
const byId = /* @__PURE__ */ new Map();
|
|
34
|
+
for (const plugin of plugins) {
|
|
35
|
+
if (byId.has(plugin.id)) throw new Error(`重复插件 id: ${plugin.id}`);
|
|
36
|
+
byId.set(plugin.id, plugin);
|
|
37
|
+
}
|
|
38
|
+
for (const plugin of plugins) for (const dep of plugin.dependencies ?? []) if (!byId.has(dep)) throw new Error(`插件 ${plugin.id} 依赖的 ${dep} 未在 profile 中声明`);
|
|
39
|
+
const declarationIndex = new Map(plugins.map((p, i) => [p.id, i]));
|
|
40
|
+
const inDegree = /* @__PURE__ */ new Map();
|
|
41
|
+
const dependents = /* @__PURE__ */ new Map();
|
|
42
|
+
for (const plugin of plugins) {
|
|
43
|
+
inDegree.set(plugin.id, plugin.dependencies?.length ?? 0);
|
|
44
|
+
for (const dep of plugin.dependencies ?? []) dependents.set(dep, [...dependents.get(dep) ?? [], plugin.id]);
|
|
45
|
+
}
|
|
46
|
+
const ready = plugins.filter((p) => (inDegree.get(p.id) ?? 0) === 0).sort((a, b) => declarationIndex.get(a.id) - declarationIndex.get(b.id)).map((p) => p.id);
|
|
47
|
+
const orderIds = [];
|
|
48
|
+
while (ready.length > 0) {
|
|
49
|
+
const id = ready.shift();
|
|
50
|
+
orderIds.push(id);
|
|
51
|
+
for (const dependent of dependents.get(id) ?? []) {
|
|
52
|
+
const next = (inDegree.get(dependent) ?? 0) - 1;
|
|
53
|
+
inDegree.set(dependent, next);
|
|
54
|
+
if (next === 0) ready.push(dependent);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (orderIds.length !== plugins.length) {
|
|
58
|
+
const cyclePath = findCyclePath(plugins.filter((p) => !orderIds.includes(p.id)).map((p) => p.id), byId);
|
|
59
|
+
throw new Error(`检测到依赖环: ${cyclePath.join(" -> ")}`);
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
order: orderIds.map((id) => byId.get(id)),
|
|
63
|
+
ids: orderIds
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function findCyclePath(cyclicIds, byId) {
|
|
67
|
+
const start = cyclicIds[0];
|
|
68
|
+
const path = [start];
|
|
69
|
+
const visited = /* @__PURE__ */ new Set([start]);
|
|
70
|
+
let current = start;
|
|
71
|
+
while (true) {
|
|
72
|
+
const deps = byId.get(current)?.dependencies ?? [];
|
|
73
|
+
const next = deps.find((d) => cyclicIds.includes(d)) ?? deps[0];
|
|
74
|
+
if (!next) break;
|
|
75
|
+
if (next === start || visited.has(next)) {
|
|
76
|
+
path.push(next);
|
|
77
|
+
return path;
|
|
78
|
+
}
|
|
79
|
+
visited.add(next);
|
|
80
|
+
path.push(next);
|
|
81
|
+
current = next;
|
|
82
|
+
}
|
|
83
|
+
return path;
|
|
84
|
+
}
|
|
85
|
+
//#endregion
|
|
86
|
+
//#region src/assemble-input.ts
|
|
87
|
+
function parseProfileImports(source) {
|
|
88
|
+
const map = /* @__PURE__ */ new Map();
|
|
89
|
+
const re = /import\s+([A-Za-z_$][\w$]*)\s+from\s+['"]([^'"]+)['"]/g;
|
|
90
|
+
let m;
|
|
91
|
+
while ((m = re.exec(source)) !== null) map.set(m[1], m[2]);
|
|
92
|
+
return map;
|
|
93
|
+
}
|
|
94
|
+
function resolveImportSpecifiers(input) {
|
|
95
|
+
const byId = /* @__PURE__ */ new Map();
|
|
96
|
+
input.resolved.order.forEach((plugin, index) => {
|
|
97
|
+
const localName = input.pluginLocalNames[index];
|
|
98
|
+
if (!localName) throw new Error(`插件 ${plugin.id} 在 profile 源码中找不到对应标识符`);
|
|
99
|
+
const specifier = input.imports.get(localName);
|
|
100
|
+
if (!specifier) throw new Error(`标识符 ${localName} 没有 default import(约定:profile 中插件必须以 default import 引入)`);
|
|
101
|
+
byId.set(plugin.id, specifier);
|
|
102
|
+
});
|
|
103
|
+
return byId;
|
|
104
|
+
}
|
|
105
|
+
//#endregion
|
|
106
|
+
//#region src/generate-assembly.ts
|
|
107
|
+
function generateAssemblyCode(input) {
|
|
108
|
+
const { profileId, profileModulePath, resolved } = input;
|
|
109
|
+
const ids = resolved.ids.map((id) => `'${id}'`).join(", ");
|
|
110
|
+
return [
|
|
111
|
+
`// AUTO-GENERATED by @allbluecn/kernel — DO NOT EDIT`,
|
|
112
|
+
`// profile: ${profileId} (validated: ${resolved.order.length} plugins)`,
|
|
113
|
+
`// 拓扑序(被依赖者在前):${resolved.ids.join(" -> ")}`,
|
|
114
|
+
`import type { ProfileDefinition } from '@allbluecn/kernel'`,
|
|
115
|
+
``,
|
|
116
|
+
`export { default as profile } from '${profileModulePath}'`,
|
|
117
|
+
``,
|
|
118
|
+
`export const pluginIds: string[] = [${ids}]`,
|
|
119
|
+
``,
|
|
120
|
+
`declare const _profile: ProfileDefinition`
|
|
121
|
+
].join("\n");
|
|
122
|
+
}
|
|
123
|
+
const HEADER = "// AUTO-GENERATED by @allbluecn/kernel — DO NOT EDIT";
|
|
124
|
+
const ALL_FEATURES = [
|
|
125
|
+
"billing",
|
|
126
|
+
"hostedAi",
|
|
127
|
+
"sso",
|
|
128
|
+
"audit",
|
|
129
|
+
"permissions",
|
|
130
|
+
"license",
|
|
131
|
+
"versionHistory",
|
|
132
|
+
"automation",
|
|
133
|
+
"templates",
|
|
134
|
+
"analytics",
|
|
135
|
+
"globalSearch",
|
|
136
|
+
"compliance"
|
|
137
|
+
];
|
|
138
|
+
function generateAssemblyFiles(input) {
|
|
139
|
+
const specifiers = resolveImportSpecifiers(input);
|
|
140
|
+
const { resolved } = input;
|
|
141
|
+
const importLines = resolved.order.map((p, i) => {
|
|
142
|
+
return `import ${input.pluginLocalNames[i]} from '${specifiers.get(p.id)}'`;
|
|
143
|
+
});
|
|
144
|
+
const pluginList = input.pluginLocalNames.slice(0, resolved.order.length).join(", ");
|
|
145
|
+
const middlewareParts = resolved.order.map((p, i) => {
|
|
146
|
+
return ` ...${input.pluginLocalNames[i]}.contributes.serverMiddleware ?? [],`;
|
|
147
|
+
}).join("\n");
|
|
148
|
+
const pluginsGen = [
|
|
149
|
+
HEADER,
|
|
150
|
+
`// profile: ${input.profileId} (validated: ${resolved.order.length} plugins)`,
|
|
151
|
+
`// 拓扑序:${resolved.ids.join(" -> ")}`,
|
|
152
|
+
...importLines,
|
|
153
|
+
`import { defineProfile, type ProfileDefinition } from '@allbluecn/kernel'`,
|
|
154
|
+
``,
|
|
155
|
+
`export const profile: ProfileDefinition = defineProfile({`,
|
|
156
|
+
` id: '${input.profileId}',`,
|
|
157
|
+
` plugins: [${pluginList}],`,
|
|
158
|
+
`})`,
|
|
159
|
+
``,
|
|
160
|
+
`export const pluginIds: string[] = [${resolved.ids.map((i) => `'${i}'`).join(", ")}]`,
|
|
161
|
+
``,
|
|
162
|
+
`export const pluginMiddleware: unknown[] = [`,
|
|
163
|
+
middlewareParts,
|
|
164
|
+
`]`
|
|
165
|
+
].join("\n");
|
|
166
|
+
const implLines = ALL_FEATURES.map((f) => {
|
|
167
|
+
return ` ${f}: ${resolved.order.some((p) => p.contributes.services?.[f] !== void 0)},`;
|
|
168
|
+
});
|
|
169
|
+
const modulesGen = [
|
|
170
|
+
HEADER,
|
|
171
|
+
`import type { ProFeature } from '@allbluecn/kernel'`,
|
|
172
|
+
``,
|
|
173
|
+
`export const activePluginIds = [${resolved.ids.map((i) => `'${i}'`).join(", ")}] as const`,
|
|
174
|
+
`export type ActivePluginId = (typeof activePluginIds)[number]`,
|
|
175
|
+
``,
|
|
176
|
+
`export const implementedModules: Record<ProFeature, boolean> = {`,
|
|
177
|
+
...implLines,
|
|
178
|
+
`}`
|
|
179
|
+
].join("\n");
|
|
180
|
+
const uiImports = [];
|
|
181
|
+
const navItems = [];
|
|
182
|
+
const settingsTabs = [];
|
|
183
|
+
const widgets = [];
|
|
184
|
+
for (let i = 0; i < resolved.order.length; i++) {
|
|
185
|
+
const p = resolved.order[i];
|
|
186
|
+
const local = input.pluginLocalNames[i];
|
|
187
|
+
const spec = specifiers.get(p.id);
|
|
188
|
+
const slots = p.contributes.uiSlots;
|
|
189
|
+
if (!slots) continue;
|
|
190
|
+
for (const [key] of Object.entries(slots.navItems ?? {})) {
|
|
191
|
+
uiImports.push(`import { ${key} } from '${spec}/ui'`);
|
|
192
|
+
navItems.push(key);
|
|
193
|
+
}
|
|
194
|
+
if (slots.settingsTabs?.length) {
|
|
195
|
+
uiImports.push(`import { ${local}SettingsTabs } from '${spec}/ui'`);
|
|
196
|
+
settingsTabs.push(`...${local}SettingsTabs`);
|
|
197
|
+
}
|
|
198
|
+
for (const [key] of Object.entries(slots.dashboardWidgets ?? {})) {
|
|
199
|
+
uiImports.push(`import { ${key} } from '${spec}/ui'`);
|
|
200
|
+
widgets.push(key);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
const uiGen = [
|
|
204
|
+
HEADER,
|
|
205
|
+
...uiImports,
|
|
206
|
+
`import { type ComponentType } from 'react'`,
|
|
207
|
+
`import type { SettingsTabContribution } from '@allbluecn/kernel'`,
|
|
208
|
+
``,
|
|
209
|
+
`export const navItems: ComponentType[] = [${navItems.join(", ")}]`,
|
|
210
|
+
`export const settingsTabs: SettingsTabContribution[] = [${settingsTabs.join(", ")}]`,
|
|
211
|
+
`export const dashboardWidgets: ComponentType[] = [${widgets.join(", ")}]`
|
|
212
|
+
].join("\n");
|
|
213
|
+
const routeFiles = /* @__PURE__ */ new Map();
|
|
214
|
+
for (let i = 0; i < resolved.order.length; i++) {
|
|
215
|
+
const p = resolved.order[i];
|
|
216
|
+
const spec = specifiers.get(p.id);
|
|
217
|
+
p.contributes.routes?.forEach((r, j) => {
|
|
218
|
+
const file = routeContributionToFile(r.path);
|
|
219
|
+
const varName = `${input.pluginLocalNames[i]}_route_${j}`;
|
|
220
|
+
const routePath = r.path.endsWith("/") ? r.path.slice(0, -1) : r.path;
|
|
221
|
+
const lines = [
|
|
222
|
+
HEADER,
|
|
223
|
+
`import { createFileRoute } from '@tanstack/react-router'`,
|
|
224
|
+
`import type { PluginRouteContext } from '@allbluecn/kernel'`,
|
|
225
|
+
`import { ${varName} } from '${spec}/routes'`,
|
|
226
|
+
``,
|
|
227
|
+
`export const Route = createFileRoute('${routePath}')({`,
|
|
228
|
+
...r.beforeLoad ? [` beforeLoad: ((ctx: PluginRouteContext) => ${varName}.beforeLoad(ctx)) as any,`] : [],
|
|
229
|
+
...r.loader ? [` loader: ((ctx: PluginRouteContext) => ${varName}.loader(ctx)) as any,`] : [],
|
|
230
|
+
...r.validateSearch ? [` validateSearch: ${varName}.validateSearch as any,`] : [],
|
|
231
|
+
` component: ${varName}.Component as any,`,
|
|
232
|
+
`})`
|
|
233
|
+
];
|
|
234
|
+
routeFiles.set(file, lines.join("\n"));
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
return {
|
|
238
|
+
pluginsGen,
|
|
239
|
+
modulesGen,
|
|
240
|
+
uiGen,
|
|
241
|
+
routeFiles
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
function routeContributionToFile(path) {
|
|
245
|
+
if (path.endsWith("/")) return `src/routes${path.slice(0, -1) || ""}/index.tsx`;
|
|
246
|
+
return `src/routes${path}/route.tsx`;
|
|
247
|
+
}
|
|
248
|
+
//#endregion
|
|
249
|
+
//#region src/dump-modules.ts
|
|
250
|
+
function dumpModules(profileId, resolved) {
|
|
251
|
+
const lines = [`profile: ${profileId} (${resolved.order.length} plugins)`];
|
|
252
|
+
for (const plugin of resolved.order) {
|
|
253
|
+
const c = plugin.contributes;
|
|
254
|
+
const parts = [];
|
|
255
|
+
if (c.routes?.length) parts.push(`routes:${c.routes.length}`);
|
|
256
|
+
if (c.serverMiddleware?.length) parts.push(`middleware:${c.serverMiddleware.length}`);
|
|
257
|
+
if (c.queryOptions && Object.keys(c.queryOptions).length) parts.push(`queries:${Object.keys(c.queryOptions).length}`);
|
|
258
|
+
if (c.uiSlots && Object.keys(c.uiSlots).length) parts.push(`ui-slots:${Object.keys(c.uiSlots).length}`);
|
|
259
|
+
if (c.features?.length) parts.push(`features:${c.features.length}`);
|
|
260
|
+
if (c.aiTools?.length) parts.push(`ai-tools:${c.aiTools.length}`);
|
|
261
|
+
if (c.prisma?.models.length) parts.push(`prisma-models:${c.prisma.models.length}`);
|
|
262
|
+
lines.push(`${plugin.id}@${plugin.version} — ${parts.length ? parts.join(" ") : "no contributions"}`);
|
|
263
|
+
}
|
|
264
|
+
return lines.join("\n");
|
|
265
|
+
}
|
|
266
|
+
//#endregion
|
|
267
|
+
//#region src/pro-contract/billing.ts
|
|
268
|
+
var BillingUnavailableError = class BillingUnavailableError extends Error {
|
|
269
|
+
constructor(message = "计费模块不可用") {
|
|
270
|
+
super(message);
|
|
271
|
+
this.name = "BillingUnavailableError";
|
|
272
|
+
Object.setPrototypeOf(this, BillingUnavailableError.prototype);
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
var WebhookSignatureError = class WebhookSignatureError extends Error {
|
|
276
|
+
constructor(message = "Webhook 签名验证失败") {
|
|
277
|
+
super(message);
|
|
278
|
+
this.name = "WebhookSignatureError";
|
|
279
|
+
Object.setPrototypeOf(this, WebhookSignatureError.prototype);
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
var SeatsExceededError = class SeatsExceededError extends Error {
|
|
283
|
+
current;
|
|
284
|
+
max;
|
|
285
|
+
constructor(current, max) {
|
|
286
|
+
super(`席位不足:当前 ${current}/${max}`);
|
|
287
|
+
this.name = "SeatsExceededError";
|
|
288
|
+
this.current = current;
|
|
289
|
+
this.max = max;
|
|
290
|
+
Object.setPrototypeOf(this, SeatsExceededError.prototype);
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
//#endregion
|
|
294
|
+
//#region src/pro-contract/hosted-ai.ts
|
|
295
|
+
var HostedAiError = class extends Error {
|
|
296
|
+
code;
|
|
297
|
+
details;
|
|
298
|
+
constructor(code, message, details) {
|
|
299
|
+
super(message);
|
|
300
|
+
this.code = code;
|
|
301
|
+
this.details = details;
|
|
302
|
+
this.name = "HostedAiError";
|
|
303
|
+
}
|
|
304
|
+
};
|
|
305
|
+
//#endregion
|
|
306
|
+
//#region src/server-seam.ts
|
|
307
|
+
let seam = null;
|
|
308
|
+
function registerServerSeam(impl) {
|
|
309
|
+
seam = impl;
|
|
310
|
+
}
|
|
311
|
+
function getServerSeam() {
|
|
312
|
+
if (!seam) throw new Error("ServerSeam 未注册:宿主需在 start.ts 中调用 registerServerSeam()");
|
|
313
|
+
return seam;
|
|
314
|
+
}
|
|
315
|
+
//#endregion
|
|
316
|
+
export { SeatsExceededError as a, generateAssemblyCode as c, resolveImportSpecifiers as d, resolvePluginOrder as f, BillingUnavailableError as i, generateAssemblyFiles as l, definePlugin as m, registerServerSeam as n, WebhookSignatureError as o, defineProfile as p, HostedAiError as r, dumpModules as s, getServerSeam as t, parseProfileImports as u };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@allbluecn/kernel",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"license": "AGPL-3.0-or-later",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "AllBlue 插件协议:类型定义 + 构建期装配器(零运行时)",
|
|
@@ -15,14 +15,9 @@
|
|
|
15
15
|
"bin": {
|
|
16
16
|
"allblue": "./dist/bin.js"
|
|
17
17
|
},
|
|
18
|
-
"files": [
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
"lint": "tsc --noEmit",
|
|
22
|
-
"test": "vitest",
|
|
23
|
-
"test:run": "vitest run",
|
|
24
|
-
"typecheck": "tsc --noEmit"
|
|
25
|
-
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist"
|
|
20
|
+
],
|
|
26
21
|
"peerDependencies": {
|
|
27
22
|
"@tanstack/react-start": "1.168.46",
|
|
28
23
|
"@tanstack/ai": "0.43.0",
|
|
@@ -37,5 +32,12 @@
|
|
|
37
32
|
"tsdown": "^0.22.0",
|
|
38
33
|
"typescript": "^7",
|
|
39
34
|
"vitest": "4.1.5"
|
|
35
|
+
},
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "tsdown",
|
|
38
|
+
"lint": "tsc --noEmit",
|
|
39
|
+
"test": "vitest",
|
|
40
|
+
"test:run": "vitest run",
|
|
41
|
+
"typecheck": "tsc --noEmit"
|
|
40
42
|
}
|
|
41
|
-
}
|
|
43
|
+
}
|
package/dist/src-Dvcggyr-.js
DELETED
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
//#region src/define-plugin.ts
|
|
2
|
-
const PLUGIN_ID_RE = /^@[a-z0-9-]+\/[a-z0-9-]+$/;
|
|
3
|
-
function definePlugin(def) {
|
|
4
|
-
if (!PLUGIN_ID_RE.test(def.id)) throw new Error(`插件 id 必须形如 '@scope/name'(小写),收到: ${def.id}`);
|
|
5
|
-
if (!/^\d+\.\d+\.\d+/.test(def.version)) throw new Error(`插件 version 必须是语义化版本,收到: ${def.version}`);
|
|
6
|
-
return deepFreeze(def);
|
|
7
|
-
}
|
|
8
|
-
function deepFreeze(value) {
|
|
9
|
-
if (value && typeof value === "object" && !Object.isFrozen(value)) {
|
|
10
|
-
Object.freeze(value);
|
|
11
|
-
for (const key of Object.keys(value)) deepFreeze(value[key]);
|
|
12
|
-
}
|
|
13
|
-
return value;
|
|
14
|
-
}
|
|
15
|
-
//#endregion
|
|
16
|
-
//#region src/define-profile.ts
|
|
17
|
-
const PROFILE_ID_RE = /^[a-z0-9][a-z0-9-]*$/;
|
|
18
|
-
function defineProfile(profile) {
|
|
19
|
-
if (!PROFILE_ID_RE.test(profile.id)) throw new Error(`profile id 只允许小写字母/数字/连字符,收到: ${profile.id}`);
|
|
20
|
-
const seen = /* @__PURE__ */ new Set();
|
|
21
|
-
for (const plugin of profile.plugins) {
|
|
22
|
-
if (seen.has(plugin.id)) throw new Error(`profile 中存在重复插件 id: ${plugin.id}`);
|
|
23
|
-
seen.add(plugin.id);
|
|
24
|
-
}
|
|
25
|
-
return Object.freeze({
|
|
26
|
-
...profile,
|
|
27
|
-
plugins: Object.freeze([...profile.plugins])
|
|
28
|
-
});
|
|
29
|
-
}
|
|
30
|
-
//#endregion
|
|
31
|
-
//#region src/resolve-order.ts
|
|
32
|
-
function resolvePluginOrder(plugins) {
|
|
33
|
-
const byId = /* @__PURE__ */ new Map();
|
|
34
|
-
for (const plugin of plugins) {
|
|
35
|
-
if (byId.has(plugin.id)) throw new Error(`重复插件 id: ${plugin.id}`);
|
|
36
|
-
byId.set(plugin.id, plugin);
|
|
37
|
-
}
|
|
38
|
-
for (const plugin of plugins) for (const dep of plugin.dependencies ?? []) if (!byId.has(dep)) throw new Error(`插件 ${plugin.id} 依赖的 ${dep} 未在 profile 中声明`);
|
|
39
|
-
const declarationIndex = new Map(plugins.map((p, i) => [p.id, i]));
|
|
40
|
-
const inDegree = /* @__PURE__ */ new Map();
|
|
41
|
-
const dependents = /* @__PURE__ */ new Map();
|
|
42
|
-
for (const plugin of plugins) {
|
|
43
|
-
inDegree.set(plugin.id, plugin.dependencies?.length ?? 0);
|
|
44
|
-
for (const dep of plugin.dependencies ?? []) dependents.set(dep, [...dependents.get(dep) ?? [], plugin.id]);
|
|
45
|
-
}
|
|
46
|
-
const ready = plugins.filter((p) => (inDegree.get(p.id) ?? 0) === 0).sort((a, b) => declarationIndex.get(a.id) - declarationIndex.get(b.id)).map((p) => p.id);
|
|
47
|
-
const orderIds = [];
|
|
48
|
-
while (ready.length > 0) {
|
|
49
|
-
const id = ready.shift();
|
|
50
|
-
orderIds.push(id);
|
|
51
|
-
for (const dependent of dependents.get(id) ?? []) {
|
|
52
|
-
const next = (inDegree.get(dependent) ?? 0) - 1;
|
|
53
|
-
inDegree.set(dependent, next);
|
|
54
|
-
if (next === 0) ready.push(dependent);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
if (orderIds.length !== plugins.length) {
|
|
58
|
-
const cyclePath = findCyclePath(plugins.filter((p) => !orderIds.includes(p.id)).map((p) => p.id), byId);
|
|
59
|
-
throw new Error(`检测到依赖环: ${cyclePath.join(" -> ")}`);
|
|
60
|
-
}
|
|
61
|
-
return {
|
|
62
|
-
order: orderIds.map((id) => byId.get(id)),
|
|
63
|
-
ids: orderIds
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
function findCyclePath(cyclicIds, byId) {
|
|
67
|
-
const start = cyclicIds[0];
|
|
68
|
-
const path = [start];
|
|
69
|
-
const visited = /* @__PURE__ */ new Set([start]);
|
|
70
|
-
let current = start;
|
|
71
|
-
while (true) {
|
|
72
|
-
const deps = byId.get(current)?.dependencies ?? [];
|
|
73
|
-
const next = deps.find((d) => cyclicIds.includes(d)) ?? deps[0];
|
|
74
|
-
if (!next) break;
|
|
75
|
-
if (next === start || visited.has(next)) {
|
|
76
|
-
path.push(next);
|
|
77
|
-
return path;
|
|
78
|
-
}
|
|
79
|
-
visited.add(next);
|
|
80
|
-
path.push(next);
|
|
81
|
-
current = next;
|
|
82
|
-
}
|
|
83
|
-
return path;
|
|
84
|
-
}
|
|
85
|
-
//#endregion
|
|
86
|
-
//#region src/generate-assembly.ts
|
|
87
|
-
function generateAssemblyCode(input) {
|
|
88
|
-
const { profileId, profileModulePath, resolved } = input;
|
|
89
|
-
const ids = resolved.ids.map((id) => `'${id}'`).join(", ");
|
|
90
|
-
return [
|
|
91
|
-
`// AUTO-GENERATED by @allbluecn/kernel — DO NOT EDIT`,
|
|
92
|
-
`// profile: ${profileId} (validated: ${resolved.order.length} plugins)`,
|
|
93
|
-
`// 拓扑序(被依赖者在前):${resolved.ids.join(" -> ")}`,
|
|
94
|
-
`import type { ProfileDefinition } from '@allbluecn/kernel'`,
|
|
95
|
-
``,
|
|
96
|
-
`export { default as profile } from '${profileModulePath}'`,
|
|
97
|
-
``,
|
|
98
|
-
`export const pluginIds: string[] = [${ids}]`,
|
|
99
|
-
``,
|
|
100
|
-
`declare const _profile: ProfileDefinition`
|
|
101
|
-
].join("\n");
|
|
102
|
-
}
|
|
103
|
-
//#endregion
|
|
104
|
-
//#region src/dump-modules.ts
|
|
105
|
-
function dumpModules(profileId, resolved) {
|
|
106
|
-
const lines = [`profile: ${profileId} (${resolved.order.length} plugins)`];
|
|
107
|
-
for (const plugin of resolved.order) {
|
|
108
|
-
const c = plugin.contributes;
|
|
109
|
-
const parts = [];
|
|
110
|
-
if (c.routes?.length) parts.push(`routes:${c.routes.length}`);
|
|
111
|
-
if (c.serverMiddleware?.length) parts.push(`middleware:${c.serverMiddleware.length}`);
|
|
112
|
-
if (c.queryOptions && Object.keys(c.queryOptions).length) parts.push(`queries:${Object.keys(c.queryOptions).length}`);
|
|
113
|
-
if (c.uiSlots && Object.keys(c.uiSlots).length) parts.push(`ui-slots:${Object.keys(c.uiSlots).length}`);
|
|
114
|
-
if (c.features?.length) parts.push(`features:${c.features.length}`);
|
|
115
|
-
if (c.aiTools?.length) parts.push(`ai-tools:${c.aiTools.length}`);
|
|
116
|
-
if (c.prisma?.models.length) parts.push(`prisma-models:${c.prisma.models.length}`);
|
|
117
|
-
lines.push(`${plugin.id}@${plugin.version} — ${parts.length ? parts.join(" ") : "no contributions"}`);
|
|
118
|
-
}
|
|
119
|
-
return lines.join("\n");
|
|
120
|
-
}
|
|
121
|
-
//#endregion
|
|
122
|
-
export { definePlugin as a, defineProfile as i, generateAssemblyCode as n, resolvePluginOrder as r, dumpModules as t };
|