@spaceflow/core 0.1.1

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.
Files changed (116) hide show
  1. package/CHANGELOG.md +1176 -0
  2. package/README.md +105 -0
  3. package/nest-cli.json +10 -0
  4. package/package.json +128 -0
  5. package/rspack.config.mjs +62 -0
  6. package/src/__mocks__/@opencode-ai/sdk.js +9 -0
  7. package/src/__mocks__/c12.ts +3 -0
  8. package/src/app.module.ts +18 -0
  9. package/src/config/ci.config.ts +29 -0
  10. package/src/config/config-loader.ts +101 -0
  11. package/src/config/config-reader.module.ts +16 -0
  12. package/src/config/config-reader.service.ts +133 -0
  13. package/src/config/feishu.config.ts +35 -0
  14. package/src/config/git-provider.config.ts +29 -0
  15. package/src/config/index.ts +29 -0
  16. package/src/config/llm.config.ts +110 -0
  17. package/src/config/schema-generator.service.ts +129 -0
  18. package/src/config/spaceflow.config.ts +292 -0
  19. package/src/config/storage.config.ts +33 -0
  20. package/src/extension-system/extension.interface.ts +221 -0
  21. package/src/extension-system/index.ts +1 -0
  22. package/src/index.ts +80 -0
  23. package/src/locales/en/translation.json +11 -0
  24. package/src/locales/zh-cn/translation.json +11 -0
  25. package/src/shared/claude-setup/claude-setup.module.ts +8 -0
  26. package/src/shared/claude-setup/claude-setup.service.ts +131 -0
  27. package/src/shared/claude-setup/index.ts +2 -0
  28. package/src/shared/editor-config/index.ts +23 -0
  29. package/src/shared/feishu-sdk/feishu-sdk.module.ts +77 -0
  30. package/src/shared/feishu-sdk/feishu-sdk.service.ts +130 -0
  31. package/src/shared/feishu-sdk/fieshu-card.service.ts +139 -0
  32. package/src/shared/feishu-sdk/index.ts +4 -0
  33. package/src/shared/feishu-sdk/types/card-action.ts +132 -0
  34. package/src/shared/feishu-sdk/types/card.ts +64 -0
  35. package/src/shared/feishu-sdk/types/common.ts +22 -0
  36. package/src/shared/feishu-sdk/types/index.ts +46 -0
  37. package/src/shared/feishu-sdk/types/message.ts +35 -0
  38. package/src/shared/feishu-sdk/types/module.ts +21 -0
  39. package/src/shared/feishu-sdk/types/user.ts +77 -0
  40. package/src/shared/git-provider/adapters/gitea.adapter.spec.ts +473 -0
  41. package/src/shared/git-provider/adapters/gitea.adapter.ts +499 -0
  42. package/src/shared/git-provider/adapters/github.adapter.spec.ts +341 -0
  43. package/src/shared/git-provider/adapters/github.adapter.ts +830 -0
  44. package/src/shared/git-provider/adapters/gitlab.adapter.ts +839 -0
  45. package/src/shared/git-provider/adapters/index.ts +3 -0
  46. package/src/shared/git-provider/detect-provider.spec.ts +195 -0
  47. package/src/shared/git-provider/detect-provider.ts +112 -0
  48. package/src/shared/git-provider/git-provider.interface.ts +188 -0
  49. package/src/shared/git-provider/git-provider.module.ts +73 -0
  50. package/src/shared/git-provider/git-provider.service.spec.ts +282 -0
  51. package/src/shared/git-provider/git-provider.service.ts +309 -0
  52. package/src/shared/git-provider/index.ts +7 -0
  53. package/src/shared/git-provider/parse-repo-url.spec.ts +221 -0
  54. package/src/shared/git-provider/parse-repo-url.ts +155 -0
  55. package/src/shared/git-provider/types.ts +434 -0
  56. package/src/shared/git-sdk/git-sdk-diff.utils.spec.ts +344 -0
  57. package/src/shared/git-sdk/git-sdk-diff.utils.ts +151 -0
  58. package/src/shared/git-sdk/git-sdk.module.ts +8 -0
  59. package/src/shared/git-sdk/git-sdk.service.ts +235 -0
  60. package/src/shared/git-sdk/git-sdk.types.ts +25 -0
  61. package/src/shared/git-sdk/index.ts +4 -0
  62. package/src/shared/i18n/i18n.spec.ts +96 -0
  63. package/src/shared/i18n/i18n.ts +86 -0
  64. package/src/shared/i18n/index.ts +1 -0
  65. package/src/shared/i18n/locale-detect.ts +134 -0
  66. package/src/shared/llm-jsonput/index.ts +94 -0
  67. package/src/shared/llm-jsonput/types.ts +17 -0
  68. package/src/shared/llm-proxy/adapters/claude-code.adapter.spec.ts +131 -0
  69. package/src/shared/llm-proxy/adapters/claude-code.adapter.ts +208 -0
  70. package/src/shared/llm-proxy/adapters/index.ts +4 -0
  71. package/src/shared/llm-proxy/adapters/llm-adapter.interface.ts +23 -0
  72. package/src/shared/llm-proxy/adapters/open-code.adapter.ts +342 -0
  73. package/src/shared/llm-proxy/adapters/openai.adapter.spec.ts +215 -0
  74. package/src/shared/llm-proxy/adapters/openai.adapter.ts +153 -0
  75. package/src/shared/llm-proxy/index.ts +6 -0
  76. package/src/shared/llm-proxy/interfaces/config.interface.ts +32 -0
  77. package/src/shared/llm-proxy/interfaces/index.ts +4 -0
  78. package/src/shared/llm-proxy/interfaces/message.interface.ts +48 -0
  79. package/src/shared/llm-proxy/interfaces/session.interface.ts +28 -0
  80. package/src/shared/llm-proxy/llm-proxy.module.ts +140 -0
  81. package/src/shared/llm-proxy/llm-proxy.service.spec.ts +303 -0
  82. package/src/shared/llm-proxy/llm-proxy.service.ts +132 -0
  83. package/src/shared/llm-proxy/llm-session.spec.ts +111 -0
  84. package/src/shared/llm-proxy/llm-session.ts +109 -0
  85. package/src/shared/llm-proxy/stream-logger.ts +97 -0
  86. package/src/shared/logger/index.ts +11 -0
  87. package/src/shared/logger/logger.interface.ts +93 -0
  88. package/src/shared/logger/logger.spec.ts +178 -0
  89. package/src/shared/logger/logger.ts +175 -0
  90. package/src/shared/logger/renderers/plain.renderer.ts +116 -0
  91. package/src/shared/logger/renderers/tui.renderer.ts +162 -0
  92. package/src/shared/mcp/index.ts +332 -0
  93. package/src/shared/output/index.ts +2 -0
  94. package/src/shared/output/output.module.ts +9 -0
  95. package/src/shared/output/output.service.ts +97 -0
  96. package/src/shared/package-manager/index.ts +115 -0
  97. package/src/shared/parallel/index.ts +1 -0
  98. package/src/shared/parallel/parallel-executor.ts +169 -0
  99. package/src/shared/rspack-config/index.ts +1 -0
  100. package/src/shared/rspack-config/rspack-config.ts +157 -0
  101. package/src/shared/source-utils/index.ts +130 -0
  102. package/src/shared/spaceflow-dir/index.ts +158 -0
  103. package/src/shared/storage/adapters/file.adapter.ts +113 -0
  104. package/src/shared/storage/adapters/index.ts +3 -0
  105. package/src/shared/storage/adapters/memory.adapter.ts +50 -0
  106. package/src/shared/storage/adapters/storage-adapter.interface.ts +48 -0
  107. package/src/shared/storage/index.ts +4 -0
  108. package/src/shared/storage/storage.module.ts +150 -0
  109. package/src/shared/storage/storage.service.ts +293 -0
  110. package/src/shared/storage/types.ts +51 -0
  111. package/src/shared/verbose/index.ts +73 -0
  112. package/test/app.e2e-spec.ts +22 -0
  113. package/tsconfig.build.json +4 -0
  114. package/tsconfig.json +25 -0
  115. package/tsconfig.skill.json +18 -0
  116. package/vitest.config.ts +58 -0
package/README.md ADDED
@@ -0,0 +1,105 @@
1
+ # @spaceflow/core
2
+
3
+ Spaceflow 核心能力库,提供共享模块、插件系统基础设施和平台适配层。
4
+
5
+ ## 安装
6
+
7
+ ```bash
8
+ pnpm add @spaceflow/core
9
+ ```
10
+
11
+ ## 共享模块
12
+
13
+ 核心库导出以下共享模块,供插件开发使用:
14
+
15
+ | 模块 | 导入路径 | 说明 |
16
+ | ----------------- | --------------------------------- | ----------------------------------------- |
17
+ | `git-provider` | `@spaceflow/core` | Git 平台适配器(GitHub / Gitea / GitLab) |
18
+ | `git-sdk` | `@spaceflow/core` | Git 命令操作封装 |
19
+ | `llm-proxy` | `@spaceflow/core/llm-proxy` | 多 LLM 适配器(OpenAI、Claude、Gemini) |
20
+ | `llm-jsonput` | `@spaceflow/core/llm-jsonput` | LLM JSON 结构化输出 |
21
+ | `feishu-sdk` | `@spaceflow/core` | 飞书 API 操作封装 |
22
+ | `storage` | `@spaceflow/core` | 通用存储服务 |
23
+ | `logger` | `@spaceflow/core` | 日志系统(TUI / Plain 双模式) |
24
+ | `parallel` | `@spaceflow/core/parallel` | 并行执行工具 |
25
+ | `verbose` | `@spaceflow/core/verbose` | 日志级别控制 |
26
+ | `editor-config` | `@spaceflow/core/editor-config` | 编辑器配置管理 |
27
+ | `source-utils` | `@spaceflow/core/source-utils` | 源码工具 |
28
+ | `package-manager` | `@spaceflow/core/package-manager` | 包管理器抽象 |
29
+ | `rspack-config` | `@spaceflow/core/rspack-config` | Rspack 构建配置 |
30
+
31
+ ## 作为库使用
32
+
33
+ ```typescript
34
+ import {
35
+ GitProviderService,
36
+ GitSdkService,
37
+ LlmProxyService,
38
+ FeishuSdkService,
39
+ StorageService,
40
+ Logger,
41
+
42
+ // NestJS 重导出
43
+ Command,
44
+ CommandRunner,
45
+ Module,
46
+ Injectable,
47
+ } from "@spaceflow/core";
48
+ ```
49
+
50
+ ## 目录结构
51
+
52
+ ```text
53
+ core/
54
+ ├── src/
55
+ │ ├── config/ # 配置管理(Zod Schema)
56
+ │ ├── extension-system/ # 插件系统核心
57
+ │ ├── locales/ # 国际化资源(i18next)
58
+ │ ├── shared/ # 共享模块
59
+ │ │ ├── git-provider/ # Git 平台适配器
60
+ │ │ │ └── adapters/ # GitHub / Gitea / GitLab 实现
61
+ │ │ ├── git-sdk/ # Git 命令封装
62
+ │ │ ├── llm-proxy/ # LLM 统一代理
63
+ │ │ ├── llm-jsonput/ # JSON 结构化输出
64
+ │ │ ├── feishu-sdk/ # 飞书 SDK
65
+ │ │ ├── logger/ # 日志系统(TUI/Plain)
66
+ │ │ ├── parallel/ # 并行执行工具
67
+ │ │ ├── storage/ # 通用存储服务
68
+ │ │ ├── editor-config/ # 编辑器配置管理
69
+ │ │ ├── verbose/ # 日志级别控制
70
+ │ │ ├── source-utils/ # 源码工具
71
+ │ │ ├── package-manager/ # 包管理器抽象
72
+ │ │ └── rspack-config/ # Rspack 构建配置
73
+ │ ├── app.module.ts # NestJS 根模块
74
+ │ └── index.ts # 库导出入口
75
+ └── test/ # E2E 测试
76
+ ```
77
+
78
+ ## 开发
79
+
80
+ ```bash
81
+ # 构建
82
+ pnpm run build
83
+
84
+ # 测试
85
+ pnpm run test
86
+
87
+ # 代码检查
88
+ pnpm run lint
89
+
90
+ # 代码格式化
91
+ pnpm run format
92
+ ```
93
+
94
+ ## 技术栈
95
+
96
+ - **NestJS** — 依赖注入框架
97
+ - **nest-commander** — CLI 命令框架
98
+ - **rspack** — 构建工具
99
+ - **i18next** — 国际化
100
+ - **Zod** — 配置校验
101
+ - **TypeScript** — 类型系统
102
+
103
+ ## 许可证
104
+
105
+ [MIT](../LICENSE)
package/nest-cli.json ADDED
@@ -0,0 +1,10 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/nest-cli",
3
+ "collection": "@nestjs/schematics",
4
+ "sourceRoot": "src",
5
+ "compilerOptions": {
6
+ "deleteOutDir": true,
7
+ "webpack": true,
8
+ "webpackConfigPath": "webpack.config.cjs"
9
+ }
10
+ }
package/package.json ADDED
@@ -0,0 +1,128 @@
1
+ {
2
+ "name": "@spaceflow/core",
3
+ "version": "0.1.1",
4
+ "description": "Spaceflow 核心能力库",
5
+ "license": "MIT",
6
+ "author": "Lydanne",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/Lydanne/spaceflow.git",
10
+ "directory": "packages/core"
11
+ },
12
+ "publishConfig": {
13
+ "access": "public"
14
+ },
15
+ "type": "module",
16
+ "main": "./dist/index.js",
17
+ "types": "./src/index.ts",
18
+ "exports": {
19
+ ".": {
20
+ "types": "./src/index.ts",
21
+ "import": "./dist/index.js"
22
+ },
23
+ "./verbose": {
24
+ "types": "./src/shared/verbose/index.ts",
25
+ "import": "./dist/index.js"
26
+ },
27
+ "./editor-config": {
28
+ "types": "./src/shared/editor-config/index.ts",
29
+ "import": "./dist/index.js"
30
+ },
31
+ "./source-utils": {
32
+ "types": "./src/shared/source-utils/index.ts",
33
+ "import": "./dist/index.js"
34
+ },
35
+ "./package-manager": {
36
+ "types": "./src/shared/package-manager/index.ts",
37
+ "import": "./dist/index.js"
38
+ },
39
+ "./spaceflow-dir": {
40
+ "types": "./src/shared/spaceflow-dir/index.ts",
41
+ "import": "./dist/index.js"
42
+ },
43
+ "./spaceflow.config": {
44
+ "types": "./src/config/spaceflow.config.ts",
45
+ "import": "./dist/index.js"
46
+ },
47
+ "./schema-generator.service": {
48
+ "types": "./src/config/schema-generator.service.ts",
49
+ "import": "./dist/index.js"
50
+ },
51
+ "./rspack-config": {
52
+ "types": "./src/shared/rspack-config/index.ts",
53
+ "import": "./dist/index.js"
54
+ },
55
+ "./llm-proxy": {
56
+ "types": "./src/shared/llm-proxy/index.ts",
57
+ "import": "./dist/index.js"
58
+ },
59
+ "./llm-jsonput": {
60
+ "types": "./src/shared/llm-jsonput/index.ts",
61
+ "import": "./dist/index.js"
62
+ },
63
+ "./parallel": {
64
+ "types": "./src/shared/parallel/index.ts",
65
+ "import": "./dist/index.js"
66
+ }
67
+ },
68
+ "dependencies": {
69
+ "@anthropic-ai/claude-agent-sdk": "^0.2.7",
70
+ "@larksuiteoapi/node-sdk": "^1.55.0",
71
+ "@modelcontextprotocol/sdk": "^1.26.0",
72
+ "@nestjs/common": "^11.0.1",
73
+ "@nestjs/config": "^4.0.2",
74
+ "@nestjs/core": "^11.0.1",
75
+ "@nestjs/event-emitter": "^3.0.1",
76
+ "@nestjs/platform-express": "^11.0.1",
77
+ "@nestjs/swagger": "^11.2.6",
78
+ "@opencode-ai/sdk": "^1.1.23",
79
+ "@release-it/conventional-changelog": "^10.0.4",
80
+ "@rspack/cli": "^1.7.4",
81
+ "@rspack/core": "^1.7.4",
82
+ "chalk": "^5.6.2",
83
+ "class-transformer": "^0.5.1",
84
+ "class-validator": "^0.14.3",
85
+ "i18next": "^25.8.4",
86
+ "json-stringify-pretty-compact": "^4.0.0",
87
+ "jsonrepair": "^3.13.1",
88
+ "log-update": "^7.1.0",
89
+ "micromatch": "^4.0.8",
90
+ "nest-commander": "^3.20.1",
91
+ "openai": "^6.1.0",
92
+ "ora": "^9.3.0",
93
+ "reflect-metadata": "^0.2.2",
94
+ "release-it": "^19.2.2",
95
+ "release-it-gitea": "^1.8.0",
96
+ "rxjs": "^7.8.1",
97
+ "zod": "^4.3.6",
98
+ "zod-to-json-schema": "^3.25.1"
99
+ },
100
+ "devDependencies": {
101
+ "@nestjs/cli": "^11.0.0",
102
+ "@nestjs/schematics": "^11.0.0",
103
+ "@nestjs/testing": "^11.0.1",
104
+ "@swc/core": "1.15.3",
105
+ "@types/express": "^5.0.0",
106
+ "@types/micromatch": "^4.0.10",
107
+ "@types/node": "^22.15.0",
108
+ "@types/supertest": "^6.0.2",
109
+ "source-map-support": "^0.5.21",
110
+ "supertest": "^7.0.0",
111
+ "ts-loader": "^9.5.2",
112
+ "ts-node": "^10.9.2",
113
+ "tsconfig-paths": "^4.2.0",
114
+ "typescript": "^5.7.3",
115
+ "typescript-eslint": "^8.20.0",
116
+ "@vitest/coverage-v8": "^4.0.18",
117
+ "unplugin-swc": "^1.5.9",
118
+ "vitest": "^4.0.18"
119
+ },
120
+ "scripts": {
121
+ "build": "rspack build -c rspack.config.mjs",
122
+ "format": "oxfmt src test --write",
123
+ "lint": "oxlint src test",
124
+ "test": "vitest run",
125
+ "test:watch": "vitest",
126
+ "test:cov": "vitest run --coverage"
127
+ }
128
+ }
@@ -0,0 +1,62 @@
1
+ import { fileURLToPath } from "url";
2
+ import { dirname, resolve } from "path";
3
+
4
+ const __dirname = dirname(fileURLToPath(import.meta.url));
5
+
6
+ export default {
7
+ optimization: {
8
+ minimize: false,
9
+ },
10
+ entry: {
11
+ index: "./src/index.ts",
12
+ },
13
+ plugins: [],
14
+ target: "node",
15
+ mode: process.env.NODE_ENV === "production" ? "production" : "development",
16
+ output: {
17
+ filename: "[name].js",
18
+ path: resolve(__dirname, "dist"),
19
+ library: { type: "module" },
20
+ chunkFormat: "module",
21
+ clean: true,
22
+ },
23
+ experiments: {
24
+ outputModule: true,
25
+ },
26
+ externalsType: "module-import",
27
+ externals: [
28
+ { micromatch: "node-commonjs micromatch" },
29
+ /^(?!src\/)[^./]/, // node_modules 包(排除 src/ 别名)
30
+ ],
31
+ resolve: {
32
+ extensions: [".ts", ".js"],
33
+ extensionAlias: {
34
+ ".js": [".ts", ".js"],
35
+ },
36
+ tsConfig: {
37
+ configFile: resolve(__dirname, "tsconfig.json"),
38
+ },
39
+ },
40
+ module: {
41
+ rules: [
42
+ {
43
+ test: /\.ts$/,
44
+ exclude: /node_modules/,
45
+ loader: "builtin:swc-loader",
46
+ options: {
47
+ jsc: {
48
+ parser: {
49
+ syntax: "typescript",
50
+ decorators: true,
51
+ },
52
+ transform: {
53
+ legacyDecorator: true,
54
+ decoratorMetadata: true,
55
+ },
56
+ target: "es2022",
57
+ },
58
+ },
59
+ },
60
+ ],
61
+ },
62
+ };
@@ -0,0 +1,9 @@
1
+ module.exports = {
2
+ createOpencodeClient: jest.fn().mockReturnValue({
3
+ session: {
4
+ create: jest.fn(),
5
+ prompt: jest.fn(),
6
+ delete: jest.fn(),
7
+ },
8
+ }),
9
+ };
@@ -0,0 +1,3 @@
1
+ export const loadConfig = jest.fn().mockResolvedValue({
2
+ config: {},
3
+ });
@@ -0,0 +1,18 @@
1
+ import { Module } from "@nestjs/common";
2
+ import { StorageModule } from "./shared/storage/storage.module";
3
+ import { OutputModule } from "./shared/output";
4
+ import { ConfigModule } from "@nestjs/config";
5
+ import { configLoaders, getEnvFilePaths } from "./config";
6
+
7
+ @Module({
8
+ imports: [
9
+ ConfigModule.forRoot({
10
+ isGlobal: true,
11
+ load: configLoaders,
12
+ envFilePath: getEnvFilePaths(),
13
+ }),
14
+ StorageModule.forFeature(),
15
+ OutputModule,
16
+ ],
17
+ })
18
+ export class AppModule {}
@@ -0,0 +1,29 @@
1
+ import { z } from "zod";
2
+ import { createEnvConfigLoader } from "./config-loader";
3
+
4
+ /** CI 配置 Schema */
5
+ const schemaFactory = () =>
6
+ z.object({
7
+ /** 仓库名称 (owner/repo 格式) */
8
+ repository: z
9
+ .string()
10
+ .default(process.env.GITHUB_REPOSITORY || "")
11
+ .describe("仓库名称 (owner/repo 格式)"),
12
+ /** 当前分支名称 */
13
+ refName: z
14
+ .string()
15
+ .default(process.env.GITHUB_REF_NAME || "")
16
+ .describe("当前分支名称"),
17
+ actor: z
18
+ .string()
19
+ .default(process.env.GITHUB_ACTOR || "")
20
+ .describe("当前操作者"),
21
+ });
22
+
23
+ /** CI 配置类型 */
24
+ export type CiConfig = z.infer<ReturnType<typeof schemaFactory>>;
25
+
26
+ export const ciConfig = createEnvConfigLoader({
27
+ configKey: "ci",
28
+ schemaFactory,
29
+ });
@@ -0,0 +1,101 @@
1
+ import { registerAs } from "@nestjs/config";
2
+ import { z } from "zod";
3
+ import { readConfigSync } from "./spaceflow.config";
4
+ import { registerPluginSchema } from "./schema-generator.service";
5
+
6
+ /**
7
+ * 配置加载器选项
8
+ */
9
+ export interface ConfigLoaderOptions<T extends z.ZodObject<z.ZodRawShape>> {
10
+ /** 配置 key(用于 registerAs 和从 spaceflow.json 读取) */
11
+ configKey: string;
12
+ /** zod schema(应包含 .default() 设置默认值) */
13
+ schemaFactory: () => T;
14
+ /** 配置描述(用于生成 JSON Schema) */
15
+ description?: string;
16
+ }
17
+
18
+ /**
19
+ * 创建配置加载器
20
+ * 统一使用 zod 进行配置验证和默认值填充
21
+ *
22
+ * @example
23
+ * ```ts
24
+ * const GitProviderConfigSchema = z.object({
25
+ * serverUrl: z.string().default(process.env.GITHUB_SERVER_URL || ""),
26
+ * token: z.string().default(process.env.GITHUB_TOKEN || ""),
27
+ * });
28
+ *
29
+ * export type GitProviderConfig = z.infer<typeof GitProviderConfigSchema>;
30
+ *
31
+ * export const gitProviderConfig = createConfigLoader({
32
+ * configKey: "gitProvider",
33
+ * schemaFactory: GitProviderConfigSchema,
34
+ * description: "Git Provider 服务配置",
35
+ * });
36
+ * ```
37
+ */
38
+ export function createConfigLoader<T extends z.ZodObject<z.ZodRawShape>>(
39
+ options: ConfigLoaderOptions<T>,
40
+ ) {
41
+ const { configKey, schemaFactory, description } = options;
42
+
43
+ // 创建配置加载器
44
+ return registerAs(configKey, (): z.infer<T> => {
45
+ const schema = schemaFactory();
46
+ // 注册 schema 用于 JSON Schema 生成
47
+ registerPluginSchema({
48
+ configKey,
49
+ schemaFactory: () => schema,
50
+ description,
51
+ });
52
+
53
+ const fileConfig = readConfigSync();
54
+ const rawConfig = fileConfig[configKey] ?? {};
55
+
56
+ // 使用 zod 验证并填充默认值
57
+ const result = schema.safeParse(rawConfig);
58
+
59
+ if (!result.success) {
60
+ const errors = result.error.issues
61
+ .map((e) => ` - ${e.path.join(".")}: ${e.message}`)
62
+ .join("\n");
63
+ throw new Error(`配置 "${configKey}" 验证失败:\n${errors}`);
64
+ }
65
+
66
+ return result.data;
67
+ });
68
+ }
69
+
70
+ /**
71
+ * 创建简单配置加载器(仅从环境变量读取,不读取配置文件)
72
+ * 适用于 CI 等纯环境变量配置
73
+ */
74
+ export function createEnvConfigLoader<T extends z.ZodObject<z.ZodRawShape>>(
75
+ options: Omit<ConfigLoaderOptions<T>, "description"> & { description?: string },
76
+ ) {
77
+ const { configKey, schemaFactory, description } = options;
78
+
79
+ return registerAs(configKey, (): z.infer<T> => {
80
+ const schema = schemaFactory();
81
+ // 注册 schema(如果有描述)
82
+ if (description) {
83
+ registerPluginSchema({
84
+ configKey,
85
+ schemaFactory: () => schema,
86
+ description,
87
+ });
88
+ }
89
+ // 直接使用空对象,让 schema 的 default 值生效
90
+ const result = schema.safeParse({});
91
+
92
+ if (!result.success) {
93
+ const errors = result.error.issues
94
+ .map((e) => ` - ${e.path.join(".")}: ${e.message}`)
95
+ .join("\n");
96
+ throw new Error(`配置 "${configKey}" 验证失败:\n${errors}`);
97
+ }
98
+
99
+ return result.data;
100
+ });
101
+ }
@@ -0,0 +1,16 @@
1
+ import { Global, Module } from "@nestjs/common";
2
+ import { ConfigReaderService } from "./config-reader.service";
3
+ import { SchemaGeneratorService } from "./schema-generator.service";
4
+
5
+ /**
6
+ * 配置读取模块
7
+ * 提供插件配置读取服务和 Schema 生成服务
8
+ *
9
+ * 插件的 defaultConfig 通过 getMetadata() 返回,在插件加载时自动注册
10
+ */
11
+ @Global()
12
+ @Module({
13
+ providers: [ConfigReaderService, SchemaGeneratorService],
14
+ exports: [ConfigReaderService, SchemaGeneratorService],
15
+ })
16
+ export class ConfigReaderModule {}
@@ -0,0 +1,133 @@
1
+ import { Injectable } from "@nestjs/common";
2
+ import { ConfigService } from "@nestjs/config";
3
+ import type { SpaceflowConfig } from "./spaceflow.config";
4
+
5
+ /**
6
+ * 系统配置(不包含插件配置)
7
+ */
8
+ export interface SystemConfig {
9
+ /** 已安装的技能包注册表 */
10
+ dependencies?: Record<string, string>;
11
+ /** 支持的编辑器列表 */
12
+ support?: string[];
13
+ }
14
+
15
+ /**
16
+ * 插件配置注册信息
17
+ */
18
+ export interface PluginConfigRegistry {
19
+ /** 插件名称 */
20
+ name: string;
21
+ /** 配置 key */
22
+ configKey?: string;
23
+ /** 依赖的其他插件配置 */
24
+ configDependencies?: string[];
25
+ /** 配置 schema 工厂函数 */
26
+ configSchema?: () => unknown;
27
+ }
28
+
29
+ /** 全局插件配置注册表(使用 global 确保跨模块共享) */
30
+ const PLUGIN_REGISTRY_KEY = Symbol.for("spaceflow.pluginRegistry");
31
+ const globalAny = global as any;
32
+ if (!globalAny[PLUGIN_REGISTRY_KEY]) {
33
+ globalAny[PLUGIN_REGISTRY_KEY] = new Map<string, PluginConfigRegistry>();
34
+ }
35
+ const pluginRegistry: Map<string, PluginConfigRegistry> = globalAny[PLUGIN_REGISTRY_KEY];
36
+
37
+ /**
38
+ * 注册插件配置(由插件加载器调用)
39
+ */
40
+ export function registerPluginConfig(registry: PluginConfigRegistry): void {
41
+ if (registry.configKey) {
42
+ pluginRegistry.set(registry.configKey, registry);
43
+ }
44
+ }
45
+
46
+ /**
47
+ * 获取已注册的插件配置
48
+ */
49
+ export function getRegisteredPluginConfig(configKey: string): PluginConfigRegistry | undefined {
50
+ return pluginRegistry.get(configKey);
51
+ }
52
+
53
+ /**
54
+ * 配置读取服务
55
+ * 提供三种配置读取方式:
56
+ * 1. getPluginConfig - 读取指定插件的配置
57
+ * 2. getOtherPluginConfig - 读取其他插件的配置(需要在 metadata 中声明依赖)
58
+ * 3. getSystemConfig - 读取系统配置
59
+ */
60
+ @Injectable()
61
+ export class ConfigReaderService {
62
+ constructor(protected readonly configService: ConfigService) {}
63
+
64
+ /**
65
+ * 读取插件的配置
66
+ * 使用 schema 验证并合并默认值
67
+ * @param configKey 插件的配置 key
68
+ * @returns 验证后的插件配置
69
+ */
70
+ getPluginConfig<T>(configKey: string): T {
71
+ const rawConfig = this.configService.get<Record<string, unknown>>("spaceflow");
72
+ const userConfig = rawConfig?.[configKey] ?? {};
73
+
74
+ // 从注册表获取 schema 工厂函数
75
+ const registry = pluginRegistry.get(configKey);
76
+ const schemaFactory = registry?.configSchema;
77
+
78
+ if (!schemaFactory || typeof schemaFactory !== "function") {
79
+ return userConfig as T;
80
+ }
81
+
82
+ // 调用工厂函数获取 schema
83
+ const schema = schemaFactory();
84
+ if (!schema || typeof (schema as any).parse !== "function") {
85
+ return userConfig as T;
86
+ }
87
+
88
+ // 使用 schema.parse() 验证并填充默认值
89
+ try {
90
+ return (schema as any).parse(userConfig) as T;
91
+ } catch (error) {
92
+ console.warn(`⚠️ 配置 "${configKey}" 验证失败:`, error);
93
+ return userConfig as T;
94
+ }
95
+ }
96
+
97
+ /**
98
+ * 读取其他插件的配置
99
+ * 必须在插件的 metadata.configDependencies 中声明依赖
100
+ * @param fromConfigKey 当前插件的配置 key
101
+ * @param targetConfigKey 目标插件的配置 key
102
+ * @returns 合并后的插件配置
103
+ */
104
+ getOtherPluginConfig<T>(fromConfigKey: string, targetConfigKey: string): T {
105
+ const fromRegistry = pluginRegistry.get(fromConfigKey);
106
+ if (!fromRegistry) {
107
+ throw new Error(`插件 "${fromConfigKey}" 未注册`);
108
+ }
109
+
110
+ // 检查是否已声明依赖
111
+ const dependencies = fromRegistry.configDependencies ?? [];
112
+ if (!dependencies.includes(targetConfigKey)) {
113
+ throw new Error(
114
+ `插件 "${fromRegistry.name}" 未声明对 "${targetConfigKey}" 配置的依赖。` +
115
+ `请在插件 metadata 的 configDependencies 中添加 "${targetConfigKey}"`,
116
+ );
117
+ }
118
+
119
+ return this.getPluginConfig<T>(targetConfigKey);
120
+ }
121
+
122
+ /**
123
+ * 读取系统配置(不包含插件配置)
124
+ * @returns 系统配置
125
+ */
126
+ getSystemConfig(): SystemConfig {
127
+ const rawConfig = this.configService.get<SpaceflowConfig>("spaceflow");
128
+ return {
129
+ dependencies: rawConfig?.dependencies,
130
+ support: rawConfig?.support,
131
+ };
132
+ }
133
+ }
@@ -0,0 +1,35 @@
1
+ import { z } from "zod";
2
+ import { createConfigLoader } from "./config-loader";
3
+
4
+ const schemaFactory = () =>
5
+ z.object({
6
+ /** 飞书应用 ID */
7
+ appId: z
8
+ .string()
9
+ .default(process.env.FEISHU_APP_ID || "")
10
+ .describe("飞书应用 ID"),
11
+ /** 飞书应用密钥 */
12
+ appSecret: z
13
+ .string()
14
+ .default(process.env.FEISHU_APP_SECRET || "")
15
+ .describe("飞书应用密钥"),
16
+ /** 应用类型:自建应用或商店应用 */
17
+ appType: z
18
+ .enum(["self_build", "store"])
19
+ .default((process.env.FEISHU_APP_TYPE as "self_build" | "store") || "self_build")
20
+ .describe("应用类型"),
21
+ /** 域名:飞书或 Lark */
22
+ domain: z
23
+ .enum(["feishu", "lark"])
24
+ .default((process.env.FEISHU_DOMAIN as "feishu" | "lark") || "feishu")
25
+ .describe("域名"),
26
+ });
27
+
28
+ /** 飞书配置类型 */
29
+ export type FeishuConfig = z.infer<ReturnType<typeof schemaFactory>>;
30
+
31
+ export const feishuConfig = createConfigLoader({
32
+ configKey: "feishu",
33
+ schemaFactory,
34
+ description: "飞书 SDK 配置",
35
+ });
@@ -0,0 +1,29 @@
1
+ import { z } from "zod";
2
+ import { createConfigLoader } from "./config-loader";
3
+ import { detectProvider } from "../shared/git-provider/detect-provider";
4
+
5
+ /** 从环境自动检测的默认值 */
6
+ const detected = detectProvider();
7
+
8
+ /** Git Provider 配置 Schema */
9
+ const schemaFactory = () =>
10
+ z.object({
11
+ /** Git Provider 类型(自动检测或手动指定) */
12
+ provider: z
13
+ .enum(["gitea", "github", "gitlab"])
14
+ .default(detected.provider)
15
+ .describe("Git Provider 类型 (github | gitea | gitlab),未指定时自动检测"),
16
+ /** Git Provider 服务器 URL */
17
+ serverUrl: z.string().default(detected.serverUrl).describe("Git Provider 服务器 URL"),
18
+ /** Git Provider API Token */
19
+ token: z.string().default(detected.token).describe("Git Provider API Token"),
20
+ });
21
+
22
+ /** Git Provider 配置类型 */
23
+ export type GitProviderConfig = z.infer<ReturnType<typeof schemaFactory>>;
24
+
25
+ export const gitProviderConfig = createConfigLoader({
26
+ configKey: "gitProvider",
27
+ schemaFactory,
28
+ description: "Git Provider 服务配置",
29
+ });