@allbluecn/kernel 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -0
- package/dist/bin.d.ts +1 -0
- package/dist/bin.js +50 -0
- package/dist/index.d.ts +64 -0
- package/dist/index.js +2 -0
- package/dist/src-Dvcggyr-.js +122 -0
- package/package.json +41 -0
package/README.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# @allbluecn/kernel
|
|
2
|
+
|
|
3
|
+
AllBlue 插件协议:类型定义 + 构建期装配器(零运行时)。
|
|
4
|
+
|
|
5
|
+
## 安装
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @allbluecn/kernel
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## 用法
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { definePlugin, defineProfile, resolvePluginOrder } from '@allbluecn/kernel'
|
|
15
|
+
|
|
16
|
+
const auth = definePlugin({
|
|
17
|
+
id: '@allblue/auth',
|
|
18
|
+
version: '0.1.0',
|
|
19
|
+
contributes: {
|
|
20
|
+
features: [{ key: 'sso', tier: 'pro', module: 'auth' }],
|
|
21
|
+
},
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
const profile = defineProfile({
|
|
25
|
+
id: 'oss',
|
|
26
|
+
plugins: [auth],
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
const resolved = resolvePluginOrder(profile.plugins)
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### CLI
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pnpm allblue modules --profile oss
|
|
36
|
+
```
|
package/dist/bin.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {}
|
package/dist/bin.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { n as generateAssemblyCode, r as resolvePluginOrder, t as dumpModules } from "./src-Dvcggyr-.js";
|
|
3
|
+
import { parseArgs } from "node:util";
|
|
4
|
+
import { pathToFileURL } from "node:url";
|
|
5
|
+
import { dirname, relative, resolve } from "node:path";
|
|
6
|
+
//#region src/bin.ts
|
|
7
|
+
const { values } = parseArgs({
|
|
8
|
+
allowPositionals: true,
|
|
9
|
+
options: {
|
|
10
|
+
profile: {
|
|
11
|
+
type: "string",
|
|
12
|
+
short: "p"
|
|
13
|
+
},
|
|
14
|
+
"gen-out": { type: "string" }
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
async function main() {
|
|
18
|
+
const profileArg = values.profile;
|
|
19
|
+
if (!profileArg) {
|
|
20
|
+
console.error("用法: allblue modules --profile <oss|saas|desktop> [--gen-out <file>]");
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
const profilePath = resolve(profileArg.endsWith(".ts") ? profileArg : `profiles/${profileArg}.ts`);
|
|
24
|
+
const profile = (await import(pathToFileURL(profilePath).href)).default;
|
|
25
|
+
const resolved = resolvePluginOrder(profile.plugins);
|
|
26
|
+
if (values["gen-out"]) {
|
|
27
|
+
const outPath = resolve(values["gen-out"]);
|
|
28
|
+
const relProfile = relImportPath(outPath, profilePath);
|
|
29
|
+
const code = generateAssemblyCode({
|
|
30
|
+
profileId: profile.id,
|
|
31
|
+
profileModulePath: relProfile,
|
|
32
|
+
resolved
|
|
33
|
+
});
|
|
34
|
+
const { writeFile } = await import("node:fs/promises");
|
|
35
|
+
await writeFile(outPath, code + "\n", "utf8");
|
|
36
|
+
console.log(`已生成 ${outPath} (${resolved.order.length} plugins)`);
|
|
37
|
+
}
|
|
38
|
+
console.log(dumpModules(profile.id, resolved));
|
|
39
|
+
}
|
|
40
|
+
function relImportPath(fromFile, toFile) {
|
|
41
|
+
let rel = relative(dirname(fromFile), toFile).replace(/\.ts$/, "");
|
|
42
|
+
if (!rel.startsWith(".")) rel = `./${rel}`;
|
|
43
|
+
return rel;
|
|
44
|
+
}
|
|
45
|
+
main().catch((error) => {
|
|
46
|
+
console.error(error instanceof Error ? error.message : error);
|
|
47
|
+
process.exit(1);
|
|
48
|
+
});
|
|
49
|
+
//#endregion
|
|
50
|
+
export {};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { ComponentType } from "react";
|
|
2
|
+
//#region src/contributions.d.ts
|
|
3
|
+
type ServerMiddleware = unknown;
|
|
4
|
+
interface RouteContribution {
|
|
5
|
+
path: string;
|
|
6
|
+
route: unknown;
|
|
7
|
+
}
|
|
8
|
+
type QueryFactoryMap = Record<string, () => unknown>;
|
|
9
|
+
type SlotComponentMap = Record<string, ComponentType>;
|
|
10
|
+
interface FeatureContribution {
|
|
11
|
+
key: string;
|
|
12
|
+
tier: string;
|
|
13
|
+
module: string;
|
|
14
|
+
}
|
|
15
|
+
interface PrismaContribution {
|
|
16
|
+
models: string[];
|
|
17
|
+
}
|
|
18
|
+
interface PluginContributions {
|
|
19
|
+
routes?: RouteContribution[];
|
|
20
|
+
serverMiddleware?: ServerMiddleware[];
|
|
21
|
+
queryOptions?: QueryFactoryMap;
|
|
22
|
+
uiSlots?: SlotComponentMap;
|
|
23
|
+
features?: FeatureContribution[];
|
|
24
|
+
aiTools?: unknown[];
|
|
25
|
+
prisma?: PrismaContribution;
|
|
26
|
+
}
|
|
27
|
+
//#endregion
|
|
28
|
+
//#region src/types.d.ts
|
|
29
|
+
interface PluginDefinition {
|
|
30
|
+
id: string;
|
|
31
|
+
version: string;
|
|
32
|
+
dependencies?: string[];
|
|
33
|
+
contributes: PluginContributions;
|
|
34
|
+
}
|
|
35
|
+
//#endregion
|
|
36
|
+
//#region src/define-profile.d.ts
|
|
37
|
+
interface ProfileDefinition {
|
|
38
|
+
id: string;
|
|
39
|
+
plugins: PluginDefinition[];
|
|
40
|
+
}
|
|
41
|
+
declare function defineProfile(profile: ProfileDefinition): ProfileDefinition;
|
|
42
|
+
//#endregion
|
|
43
|
+
//#region src/define-plugin.d.ts
|
|
44
|
+
declare function definePlugin(def: PluginDefinition): PluginDefinition;
|
|
45
|
+
//#endregion
|
|
46
|
+
//#region src/resolve-order.d.ts
|
|
47
|
+
interface ResolveResult {
|
|
48
|
+
order: PluginDefinition[];
|
|
49
|
+
ids: string[];
|
|
50
|
+
}
|
|
51
|
+
declare function resolvePluginOrder(plugins: PluginDefinition[]): ResolveResult;
|
|
52
|
+
//#endregion
|
|
53
|
+
//#region src/generate-assembly.d.ts
|
|
54
|
+
interface GenerateAssemblyInput {
|
|
55
|
+
profileId: string;
|
|
56
|
+
profileModulePath: string;
|
|
57
|
+
resolved: ResolveResult;
|
|
58
|
+
}
|
|
59
|
+
declare function generateAssemblyCode(input: GenerateAssemblyInput): string;
|
|
60
|
+
//#endregion
|
|
61
|
+
//#region src/dump-modules.d.ts
|
|
62
|
+
declare function dumpModules(profileId: string, resolved: ResolveResult): string;
|
|
63
|
+
//#endregion
|
|
64
|
+
export { type FeatureContribution, type PluginContributions, type PluginDefinition, type PrismaContribution, type ProfileDefinition, type QueryFactoryMap, type ResolveResult, type RouteContribution, type SlotComponentMap, definePlugin, defineProfile, dumpModules, generateAssemblyCode, resolvePluginOrder };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
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 };
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@allbluecn/kernel",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"license": "AGPL-3.0-or-later",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"description": "AllBlue 插件协议:类型定义 + 构建期装配器(零运行时)",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"default": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"bin": {
|
|
16
|
+
"allblue": "./dist/bin.js"
|
|
17
|
+
},
|
|
18
|
+
"files": ["dist"],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsdown",
|
|
21
|
+
"lint": "tsc --noEmit",
|
|
22
|
+
"test": "vitest",
|
|
23
|
+
"test:run": "vitest run",
|
|
24
|
+
"typecheck": "tsc --noEmit"
|
|
25
|
+
},
|
|
26
|
+
"peerDependencies": {
|
|
27
|
+
"@tanstack/react-start": "1.168.46",
|
|
28
|
+
"@tanstack/ai": "0.43.0",
|
|
29
|
+
"react": "^19"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@tanstack/react-start": "1.168.46",
|
|
33
|
+
"@tanstack/ai": "0.43.0",
|
|
34
|
+
"@types/node": "^24.13.3",
|
|
35
|
+
"@types/react": "^19",
|
|
36
|
+
"react": "^19",
|
|
37
|
+
"tsdown": "^0.22.0",
|
|
38
|
+
"typescript": "^7",
|
|
39
|
+
"vitest": "4.1.5"
|
|
40
|
+
}
|
|
41
|
+
}
|