@ticatec/omniflow-core 0.1.1 → 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.
Files changed (75) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +112 -54
  3. package/README_CN.md +113 -55
  4. package/dist/index.d.ts +5 -4
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +2 -2
  7. package/dist/index.js.map +1 -1
  8. package/dist/plugin/PluginContext.d.ts +39 -0
  9. package/dist/plugin/PluginContext.d.ts.map +1 -0
  10. package/dist/plugin/PluginContext.js +8 -0
  11. package/dist/plugin/PluginContext.js.map +1 -0
  12. package/dist/primitives/docker.d.ts.map +1 -1
  13. package/dist/primitives/docker.js +9 -0
  14. package/dist/primitives/docker.js.map +1 -1
  15. package/dist/primitives/git.d.ts +58 -16
  16. package/dist/primitives/git.d.ts.map +1 -1
  17. package/dist/primitives/git.js +91 -33
  18. package/dist/primitives/git.js.map +1 -1
  19. package/dist/primitives/shell.d.ts +4 -0
  20. package/dist/primitives/shell.d.ts.map +1 -1
  21. package/dist/primitives/shell.js +20 -60
  22. package/dist/primitives/shell.js.map +1 -1
  23. package/dist/primitives/ssh.d.ts +8 -5
  24. package/dist/primitives/ssh.d.ts.map +1 -1
  25. package/dist/primitives/ssh.js +49 -14
  26. package/dist/primitives/ssh.js.map +1 -1
  27. package/dist/primitives/subprocess.d.ts +52 -0
  28. package/dist/primitives/subprocess.d.ts.map +1 -0
  29. package/dist/primitives/subprocess.js +353 -0
  30. package/dist/primitives/subprocess.js.map +1 -0
  31. package/dist/toolchain/providers/GradleToolchain.d.ts +1 -1
  32. package/dist/toolchain/providers/GradleToolchain.d.ts.map +1 -1
  33. package/dist/toolchain/providers/GradleToolchain.js +4 -3
  34. package/dist/toolchain/providers/GradleToolchain.js.map +1 -1
  35. package/dist/toolchain/providers/MavenToolchain.d.ts +1 -1
  36. package/dist/toolchain/providers/MavenToolchain.d.ts.map +1 -1
  37. package/dist/toolchain/providers/MavenToolchain.js +3 -3
  38. package/dist/toolchain/providers/MavenToolchain.js.map +1 -1
  39. package/dist/toolchain/providers/NodeToolchain.d.ts +1 -1
  40. package/dist/toolchain/providers/NodeToolchain.d.ts.map +1 -1
  41. package/dist/toolchain/providers/NodeToolchain.js +61 -0
  42. package/dist/toolchain/providers/NodeToolchain.js.map +1 -1
  43. package/dist/toolchain/providers/pom.d.ts.map +1 -1
  44. package/dist/toolchain/providers/pom.js +13 -0
  45. package/dist/toolchain/providers/pom.js.map +1 -1
  46. package/dist/toolchain/registry.d.ts +11 -3
  47. package/dist/toolchain/registry.d.ts.map +1 -1
  48. package/dist/toolchain/registry.js +33 -8
  49. package/dist/toolchain/registry.js.map +1 -1
  50. package/dist/utils/mask.d.ts.map +1 -1
  51. package/dist/utils/mask.js +30 -5
  52. package/dist/utils/mask.js.map +1 -1
  53. package/docs/toolchain-extension.md +301 -0
  54. package/docs/toolchain-extension_CN.md +304 -0
  55. package/package.json +23 -1
  56. package/src/context/index.ts +74 -0
  57. package/src/context/storage.ts +8 -0
  58. package/src/context/types.ts +69 -0
  59. package/src/index.ts +97 -0
  60. package/src/plugin/PluginContext.ts +57 -0
  61. package/src/primitives/docker.ts +164 -0
  62. package/src/primitives/git.ts +172 -0
  63. package/src/primitives/index.ts +4 -0
  64. package/src/primitives/shell.ts +157 -0
  65. package/src/primitives/ssh.ts +249 -0
  66. package/src/primitives/subprocess.ts +389 -0
  67. package/src/toolchain/index.ts +6 -0
  68. package/src/toolchain/providers/GradleToolchain.ts +137 -0
  69. package/src/toolchain/providers/MavenToolchain.ts +64 -0
  70. package/src/toolchain/providers/NodeToolchain.ts +172 -0
  71. package/src/toolchain/providers/pom.ts +145 -0
  72. package/src/toolchain/registry.ts +161 -0
  73. package/src/toolchain/types.ts +40 -0
  74. package/src/utils/mask.ts +73 -0
  75. package/src/utils/template.ts +62 -0
@@ -0,0 +1,301 @@
1
+ # Toolchain SPI Extension & Registration Guide
2
+
3
+ This document explains how to extend, register, and use custom build toolchains within `@ticatec/omniflow-core`.
4
+
5
+ [中文文档](./toolchain-extension_CN.md)
6
+
7
+ ---
8
+
9
+ ## 1. Architectural Background
10
+
11
+ In OmniFlow, a **Toolchain** encapsulates the build lifecycle and manifest conventions of a specific programming language ecosystem (such as Java Maven/Gradle, Node.js, Go, Rust, etc.). It exposes a unified interface:
12
+
13
+ - **Detection (`detect`)**: Determines whether a source directory belongs to the toolchain.
14
+ - **Identity & Metadata Extraction (`projectInfo`)**: Extracts project name, version, and coordinates from manifests (`pom.xml`, `package.json`, `go.mod`, `Cargo.toml`).
15
+ - **Dependency Fetching (`install`)**: Downloads or synchronizes dependencies.
16
+ - **Build Execution (`build`)**: Compiles, packages, and produces build artifacts.
17
+ - **Arbitrary Task Execution (`run`)**: Executes arbitrary scripts or test suites.
18
+
19
+ ```
20
+ ┌─────────────────────────┐
21
+ │ ToolchainRegistry │
22
+ └───────────┬─────────────┘
23
+ │ Scan by priority (descending)
24
+ ┌─────────────────────────┼─────────────────────────┐
25
+ ▼ ▼ ▼
26
+ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
27
+ │ GoToolchain │ │ MavenToolchain │ │ NodeToolchain │
28
+ │ (Custom, p=50) │ │ (Built-in, p=30) │ │ (Built-in, p=10) │
29
+ └──────────────────┘ └──────────────────┘ └──────────────────┘
30
+ ```
31
+
32
+ OmniFlow Core ships with built-in providers for Maven (`priority: 30`), Gradle (`priority: 20`), and Node.js (`priority: 10`). Third-party plugins or internal repositories can register new language toolchains or override built-in behavior with higher priority values.
33
+
34
+ ---
35
+
36
+ ## 2. Interface Specification
37
+
38
+ Custom toolchains must implement the `ToolchainProvider` interface (exported from `@ticatec/omniflow-core` or `@ticatec/omniflow-core/toolchain`):
39
+
40
+ ```typescript
41
+ export interface ProjectInfo {
42
+ name: string
43
+ version: string
44
+ fullName: string
45
+ namespace?: string
46
+ }
47
+
48
+ export interface DetectionResult {
49
+ name: string
50
+ reason: string
51
+ }
52
+
53
+ export interface ToolchainProvider {
54
+ /** Unique identifier, e.g. 'go', 'rust', 'python', 'maven' */
55
+ readonly name: string
56
+
57
+ /**
58
+ * Detection and resolution priority (higher numbers are evaluated first).
59
+ * Built-in providers: Maven (30), Gradle (20), Node.js (10).
60
+ * Custom providers typically use values >= 50 to evaluate before built-ins.
61
+ */
62
+ readonly priority?: number
63
+
64
+ /** Check if a project directory matches this toolchain */
65
+ detect(projectDir: string): Promise<DetectionResult | null>
66
+
67
+ /** Extract project identity (name, version) from project manifests */
68
+ projectInfo(projectDir: string): Promise<ProjectInfo>
69
+
70
+ /** Install or fetch dependencies */
71
+ install(projectDir: string, flags?: string[]): Promise<void>
72
+
73
+ /** Run the primary build/package task */
74
+ build(projectDir: string, flags?: string[]): Promise<void>
75
+
76
+ /** Run an arbitrary task or script (optional) */
77
+ run?(projectDir: string, task: string, flags?: string[]): Promise<void>
78
+ }
79
+ ```
80
+
81
+ > [!TIP]
82
+ > When implementing `install`, `build`, and `run`, always use `shell.run` or `shell.sh` from `@ticatec/omniflow-core`. This guarantees automatic inheritance of the execution context (`cwd`, `env`), automatic `--dry-run` simulation interception, and sensitive credential masking.
83
+
84
+ ---
85
+
86
+ ## 3. Practical Example 1: Custom Go Toolchain
87
+
88
+ The following example demonstrates adding complete Go build support to OmniFlow:
89
+
90
+ ```typescript
91
+ import fs from 'node:fs/promises'
92
+ import path from 'node:path'
93
+ import {
94
+ shell,
95
+ registerToolchain,
96
+ type ToolchainProvider,
97
+ type DetectionResult,
98
+ type ProjectInfo
99
+ } from '@ticatec/omniflow-core'
100
+
101
+ export class GoToolchain implements ToolchainProvider {
102
+ readonly name = 'go'
103
+ readonly priority: number = 50 // Evaluated before built-ins (Maven 30, Gradle 20, Node 10)
104
+
105
+ async detect(projectDir: string): Promise<DetectionResult | null> {
106
+ const modPath = path.join(projectDir, 'go.mod')
107
+ try {
108
+ await fs.access(modPath)
109
+ return {
110
+ name: this.name,
111
+ reason: 'found go.mod'
112
+ }
113
+ } catch {
114
+ return null
115
+ }
116
+ }
117
+
118
+ async projectInfo(projectDir: string): Promise<ProjectInfo> {
119
+ const modPath = path.join(projectDir, 'go.mod')
120
+ const content = await fs.readFile(modPath, 'utf-8')
121
+
122
+ // Parse 'module <name>' line
123
+ const match = content.match(/module\s+([^\s\r\n]+)/)
124
+ const moduleName = match ? match[1] : path.basename(projectDir)
125
+ const shortName = moduleName.split('/').pop() ?? moduleName
126
+
127
+ return {
128
+ name: shortName,
129
+ fullName: moduleName,
130
+ version: 'latest' // Go versions are commonly determined by Git tags
131
+ }
132
+ }
133
+
134
+ async install(projectDir: string, flags: string[] = []): Promise<void> {
135
+ await shell.run({ cwd: projectDir })`go mod download ${flags}`
136
+ }
137
+
138
+ async build(projectDir: string, flags: string[] = []): Promise<void> {
139
+ await shell.run({ cwd: projectDir })`go build -v -o dist/app . ${flags}`
140
+ }
141
+
142
+ async run(projectDir: string, task: string, flags: string[] = []): Promise<void> {
143
+ if (task === 'test') {
144
+ await shell.run({ cwd: projectDir })`go test ./... ${flags}`
145
+ } else {
146
+ await shell.run({ cwd: projectDir })`go run . ${flags}`
147
+ }
148
+ }
149
+ }
150
+
151
+ // Register into the global toolchain registry
152
+ registerToolchain(new GoToolchain())
153
+ ```
154
+
155
+ ---
156
+
157
+ ## 4. Practical Example 2: Custom Rust (Cargo) Toolchain
158
+
159
+ ```typescript
160
+ import fs from 'node:fs/promises'
161
+ import path from 'node:path'
162
+ import {
163
+ shell,
164
+ registerToolchain,
165
+ type ToolchainProvider,
166
+ type DetectionResult,
167
+ type ProjectInfo
168
+ } from '@ticatec/omniflow-core'
169
+
170
+ export class RustToolchain implements ToolchainProvider {
171
+ readonly name = 'rust'
172
+ readonly priority: number = 50
173
+
174
+ async detect(projectDir: string): Promise<DetectionResult | null> {
175
+ try {
176
+ await fs.access(path.join(projectDir, 'Cargo.toml'))
177
+ return { name: this.name, reason: 'found Cargo.toml' }
178
+ } catch {
179
+ return null
180
+ }
181
+ }
182
+
183
+ async projectInfo(projectDir: string): Promise<ProjectInfo> {
184
+ const cargoPath = path.join(projectDir, 'Cargo.toml')
185
+ const content = await fs.readFile(cargoPath, 'utf-8')
186
+
187
+ const nameMatch = content.match(/name\s*=\s*"([^"]+)"/)
188
+ const verMatch = content.match(/version\s*=\s*"([^"]+)"/)
189
+
190
+ const name = nameMatch ? nameMatch[1] : path.basename(projectDir)
191
+ const version = verMatch ? verMatch[1] : '0.1.0'
192
+
193
+ return { name, fullName: name, version }
194
+ }
195
+
196
+ async install(projectDir: string, flags: string[] = []): Promise<void> {
197
+ await shell.run({ cwd: projectDir })`cargo fetch ${flags}`
198
+ }
199
+
200
+ async build(projectDir: string, flags: string[] = []): Promise<void> {
201
+ await shell.run({ cwd: projectDir })`cargo build --release ${flags}`
202
+ }
203
+ }
204
+
205
+ registerToolchain(new RustToolchain())
206
+ ```
207
+
208
+ ---
209
+
210
+ ## 5. Practical Example 3: Overriding Built-in Toolchains (Enterprise Maven Customization)
211
+
212
+ **Scenario**: An enterprise policy mandates that all Java Maven builds must:
213
+ 1. Always point to an internal private nexus mirror with a customized `settings.xml`;
214
+ 2. Inject enterprise APM and vulnerability scanning flags;
215
+ 3. Never download directly from public central repositories.
216
+
217
+ You can override the default `MavenToolchain` by registering a provider with the same `name: 'maven'`. `registerToolchain()` replaces the registered provider for that name, while specifying a `priority` controls its evaluation order in `detect()`:
218
+
219
+ ```typescript
220
+ import {
221
+ shell,
222
+ registerToolchain,
223
+ MavenToolchain,
224
+ type ToolchainProvider
225
+ } from '@ticatec/omniflow-core'
226
+
227
+ export class EnterpriseMavenToolchain extends MavenToolchain {
228
+ readonly name = 'maven'
229
+ readonly priority: number = 50 // Evaluated earlier during automatic detection
230
+
231
+ private readonly customSettings = '/etc/omniflow/maven/settings-enterprise.xml'
232
+
233
+ override async build(projectDir: string, flags: string[] = []): Promise<void> {
234
+ const enterpriseFlags = [
235
+ '-s', this.customSettings,
236
+ '-Dorg.slf4j.simpleLogger.showDateTime=true',
237
+ ...flags
238
+ ]
239
+
240
+ await shell.run({ cwd: projectDir })`mvn clean package ${enterpriseFlags}`
241
+ }
242
+ }
243
+
244
+ // Register override
245
+ registerToolchain(new EnterpriseMavenToolchain())
246
+ ```
247
+
248
+ ---
249
+
250
+ ## 6. Resolving and Executing Toolchains
251
+
252
+ In runners or workflow steps, resolve toolchains agnostically using `resolveToolchain`:
253
+
254
+ ```typescript
255
+ import { resolveToolchain } from '@ticatec/omniflow-core'
256
+
257
+ async function buildProject(dir: string) {
258
+ // Automatically detects project type and returns matching provider & reason
259
+ const { provider, detection } = await resolveToolchain(dir)
260
+ console.log(`[Toolchain] Resolved ${provider.name}: ${detection.reason}`)
261
+
262
+ // Extract metadata
263
+ const info = await provider.projectInfo(dir)
264
+ console.log(`[Project] Building ${info.fullName}@${info.version}`)
265
+
266
+ // Install dependencies and run build
267
+ await provider.install(dir)
268
+ await provider.build(dir)
269
+ }
270
+ ```
271
+
272
+ ### Specifying a Preferred Toolchain
273
+ If a project contains multiple manifests (e.g. both `package.json` for frontend assets and `pom.xml` for backend packaging), pass a preferred toolchain name:
274
+
275
+ ```typescript
276
+ // Explicitly force Maven resolution
277
+ const { provider } = await resolveToolchain(dir, 'maven')
278
+ await provider.build(dir)
279
+ ```
280
+
281
+ ---
282
+
283
+ ## 7. Isolated Testing Best Practices
284
+
285
+ When unit testing toolchains, instantiate an isolated `ToolchainRegistry` to prevent mutating global state:
286
+
287
+ ```typescript
288
+ import { ToolchainRegistry } from '@ticatec/omniflow-core/toolchain'
289
+ import { GoToolchain } from './GoToolchain.js'
290
+
291
+ describe('GoToolchain', () => {
292
+ it('should detect go project in an isolated registry', async () => {
293
+ // Pass false to create a fresh registry without default providers
294
+ const registry = new ToolchainRegistry(false)
295
+ registry.register(new GoToolchain())
296
+
297
+ const res = await registry.detect('/fixtures/sample-go-app')
298
+ expect(res?.provider.name).toBe('go')
299
+ })
300
+ })
301
+ ```
@@ -0,0 +1,304 @@
1
+ # Toolchain SPI 扩展与注册指南
2
+
3
+ 本文档介绍如何在 `@ticatec/omniflow-core` 中扩展、注册并使用自定义构建工具链(Toolchain)。
4
+
5
+ ---
6
+
7
+ ## 1. 概念与架构背景
8
+
9
+ 在 OmniFlow 中,**Toolchain(工具链)** 是对特定语言构建生态(如 Java Maven/Gradle、Node.js、Golang、Rust 等)的抽象封装。它定义了统一的生命周期与元数据接口:
10
+
11
+ - **检测识别 (`detect`)**:判断某个源码目录是否属于该工具链管理的工程。
12
+ - **元数据提取 (`projectInfo`)**:从工程描述文件(如 `pom.xml`, `package.json`, `go.mod`, `Cargo.toml`)中提取名称、版本、命名空间等。
13
+ - **依赖拉取 (`install`)**:执行依赖下载与同步。
14
+ - **打包构建 (`build`)**:执行标准编译和打包。
15
+ - **通用任务执行 (`run`)**:执行自定义测试、代码检查或子脚本。
16
+
17
+ ```
18
+ ┌─────────────────────────┐
19
+ │ ToolchainRegistry │
20
+ └───────────┬─────────────┘
21
+ │ 按 priority 降序探测
22
+ ┌─────────────────────────┼─────────────────────────┐
23
+ ▼ ▼ ▼
24
+ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
25
+ │ GoToolchain │ │ MavenToolchain │ │ NodeToolchain │
26
+ │ (Custom, p=50) │ │ (Built-in, p=30) │ │ (Built-in, p=10) │
27
+ └──────────────────┘ └──────────────────┘ └──────────────────┘
28
+ ```
29
+
30
+ OmniFlow Core 内置了对 Maven (`priority: 30`)、Gradle (`priority: 20`) 与 Node.js (`priority: 10`) 的支持,开发者或第三方插件可以通过开放的 SPI 接口注册新的编程语言支持,或以更高的优先级覆盖内置工具链的行为。
31
+
32
+ ---
33
+
34
+ ## 2. 核心接口规范
35
+
36
+ 工具链必须实现 `ToolchainProvider` 接口(位于 `@ticatec/omniflow-core/toolchain` 或 `@ticatec/omniflow-core`):
37
+
38
+ ```typescript
39
+ export interface ProjectInfo {
40
+ name: string
41
+ version: string
42
+ fullName: string
43
+ namespace?: string
44
+ }
45
+
46
+ export interface DetectionResult {
47
+ name: string
48
+ reason: string
49
+ }
50
+
51
+ export interface ToolchainProvider {
52
+ /** 工具链唯一标识名,如 'go', 'rust', 'python', 'maven' */
53
+ readonly name: string
54
+
55
+ /**
56
+ * 探测与解析优先级(数值越大越优先探测)。
57
+ * 内置 Provider 优先级为:Maven (30)、Gradle (20)、Node.js (10)。
58
+ * 自定义 Provider 建议设置 >= 50 以优先于内置工具链探测。
59
+ */
60
+ readonly priority?: number
61
+
62
+ /** 检查指定目录是否符合当前工具链 */
63
+ detect(projectDir: string): Promise<DetectionResult | null>
64
+
65
+ /** 从工程配置中解析项目基本信息 */
66
+ projectInfo(projectDir: string): Promise<ProjectInfo>
67
+
68
+ /** 安装/拉取依赖 */
69
+ install(projectDir: string, flags?: string[]): Promise<void>
70
+
71
+ /** 执行标准构建/打包任务 */
72
+ build(projectDir: string, flags?: string[]): Promise<void>
73
+
74
+ /** 执行任意自定义任务或指令(可选) */
75
+ run?(projectDir: string, task: string, flags?: string[]): Promise<void>
76
+ }
77
+ ```
78
+
79
+ > [!TIP]
80
+ > 在实现 `install`、`build`、`run` 时,强烈建议使用 `@ticatec/omniflow-core` 提供的 `shell.run` 或 `shell.sh`。这样会自动继承当前的上下文环境变量、工作目录,并自动获得 `--dry-run` 拦截和日志凭据脱敏能力。
81
+
82
+ ---
83
+
84
+ ## 3. 实战示例 1:扩展全新工具链(Golang)
85
+
86
+ 下面展示如何为 OmniFlow 添加对 Go 语言工程的完整构建支持:
87
+
88
+ ```typescript
89
+ import fs from 'node:fs/promises'
90
+ import path from 'node:path'
91
+ import {
92
+ shell,
93
+ registerToolchain,
94
+ type ToolchainProvider,
95
+ type DetectionResult,
96
+ type ProjectInfo
97
+ } from '@ticatec/omniflow-core'
98
+
99
+ export class GoToolchain implements ToolchainProvider {
100
+ readonly name = 'go'
101
+ readonly priority: number = 50 // 优先级高于内置工具链(Maven 30, Gradle 20, Node 10)
102
+
103
+ async detect(projectDir: string): Promise<DetectionResult | null> {
104
+ const modPath = path.join(projectDir, 'go.mod')
105
+ try {
106
+ await fs.access(modPath)
107
+ return {
108
+ name: this.name,
109
+ reason: 'found go.mod'
110
+ }
111
+ } catch {
112
+ return null
113
+ }
114
+ }
115
+
116
+ async projectInfo(projectDir: string): Promise<ProjectInfo> {
117
+ const modPath = path.join(projectDir, 'go.mod')
118
+ const content = await fs.readFile(modPath, 'utf-8')
119
+
120
+ // 简单解析 module <name>
121
+ const match = content.match(/module\s+([^\s\r\n]+)/)
122
+ const moduleName = match ? match[1] : path.basename(projectDir)
123
+ const shortName = moduleName.split('/').pop() ?? moduleName
124
+
125
+ return {
126
+ name: shortName,
127
+ fullName: moduleName,
128
+ version: 'latest' // Go 工程版本多取自 Git Tag
129
+ }
130
+ }
131
+
132
+ async install(projectDir: string, flags: string[] = []): Promise<void> {
133
+ await shell.run({ cwd: projectDir })`go mod download ${flags}`
134
+ }
135
+
136
+ async build(projectDir: string, flags: string[] = []): Promise<void> {
137
+ await shell.run({ cwd: projectDir })`go build -v -o dist/app . ${flags}`
138
+ }
139
+
140
+ async run(projectDir: string, task: string, flags: string[] = []): Promise<void> {
141
+ if (task === 'test') {
142
+ await shell.run({ cwd: projectDir })`go test ./... ${flags}`
143
+ } else {
144
+ await shell.run({ cwd: projectDir })`go run . ${flags}`
145
+ }
146
+ }
147
+ }
148
+
149
+ // 注册到全局工具链注册表
150
+ registerToolchain(new GoToolchain())
151
+ ```
152
+
153
+ ---
154
+
155
+ ## 4. 实战示例 2:实战扩展 Rust (Cargo)
156
+
157
+ ```typescript
158
+ import fs from 'node:fs/promises'
159
+ import path from 'node:path'
160
+ import {
161
+ shell,
162
+ registerToolchain,
163
+ type ToolchainProvider,
164
+ type DetectionResult,
165
+ type ProjectInfo
166
+ } from '@ticatec/omniflow-core'
167
+
168
+ export class RustToolchain implements ToolchainProvider {
169
+ readonly name = 'rust'
170
+ readonly priority: number = 50
171
+
172
+ async detect(projectDir: string): Promise<DetectionResult | null> {
173
+ try {
174
+ await fs.access(path.join(projectDir, 'Cargo.toml'))
175
+ return { name: this.name, reason: 'found Cargo.toml' }
176
+ } catch {
177
+ return null
178
+ }
179
+ }
180
+
181
+ async projectInfo(projectDir: string): Promise<ProjectInfo> {
182
+ const cargoPath = path.join(projectDir, 'Cargo.toml')
183
+ const content = await fs.readFile(cargoPath, 'utf-8')
184
+
185
+ const nameMatch = content.match(/name\s*=\s*"([^"]+)"/)
186
+ const verMatch = content.match(/version\s*=\s*"([^"]+)"/)
187
+
188
+ const name = nameMatch ? nameMatch[1] : path.basename(projectDir)
189
+ const version = verMatch ? verMatch[1] : '0.1.0'
190
+
191
+ return { name, fullName: name, version }
192
+ }
193
+
194
+ async install(projectDir: string, flags: string[] = []): Promise<void> {
195
+ await shell.run({ cwd: projectDir })`cargo fetch ${flags}`
196
+ }
197
+
198
+ async build(projectDir: string, flags: string[] = []): Promise<void> {
199
+ await shell.run({ cwd: projectDir })`cargo build --release ${flags}`
200
+ }
201
+ }
202
+
203
+ registerToolchain(new RustToolchain())
204
+ ```
205
+
206
+ ---
207
+
208
+ ## 5. 实战示例 3:覆写内置工具链(企业级 Maven 定制)
209
+
210
+ **业务场景**:企业规定 Java 项目必须:
211
+ 1. 统一使用内网特定镜像仓库和私有 `settings.xml`;
212
+ 2. 注入企业代码安全探针参数;
213
+ 3. 禁止直接访问外部公共中央仓库。
214
+
215
+ 通过注册同名 Provider(`name: 'maven'`)可直接替换内置实现,配合显式声明 `priority`(如 `priority: number = 50`)可控制其在自动探测链中的优先级:
216
+
217
+ ```typescript
218
+ import {
219
+ shell,
220
+ registerToolchain,
221
+ MavenToolchain,
222
+ type ToolchainProvider
223
+ } from '@ticatec/omniflow-core'
224
+
225
+ export class EnterpriseMavenToolchain extends MavenToolchain {
226
+ // 保持与内置相同的 name: 'maven'
227
+ readonly name = 'maven'
228
+ // 设置显式数值类型,并在探测链中享有更高优先级
229
+ readonly priority: number = 50
230
+
231
+ private readonly customSettings = '/etc/omniflow/maven/settings-enterprise.xml'
232
+
233
+ override async build(projectDir: string, flags: string[] = []): Promise<void> {
234
+ const enterpriseFlags = [
235
+ '-s', this.customSettings,
236
+ '-Dorg.slf4j.simpleLogger.showDateTime=true',
237
+ ...flags
238
+ ]
239
+
240
+ // 调用定制构建命令,直接以数组形式传递 flags
241
+ await shell.run({ cwd: projectDir })`mvn clean package ${enterpriseFlags}`
242
+ }
243
+ }
244
+
245
+ // 注册覆盖
246
+ registerToolchain(new EnterpriseMavenToolchain())
247
+ ```
248
+
249
+ ---
250
+
251
+ ## 6. 使用与解析工具链
252
+
253
+ 在流水线执行器(Runner)或业务插件中,无需关心底层具体是哪种语言,通过 `resolveToolchain` 统一调用:
254
+
255
+ ```typescript
256
+ import { resolveToolchain } from '@ticatec/omniflow-core'
257
+
258
+ async function buildProject(dir: string) {
259
+ // 自动探测,返回命中的 provider 和识别原因
260
+ const { provider, detection } = await resolveToolchain(dir)
261
+ console.log(`[Toolchain] 命中 ${provider.name}: ${detection.reason}`)
262
+
263
+ // 读取工程信息
264
+ const info = await provider.projectInfo(dir)
265
+ console.log(`[Project] 正在构建 ${info.fullName} (${info.version})`)
266
+
267
+ // 安装依赖
268
+ await provider.install(dir)
269
+
270
+ // 执行构建
271
+ await provider.build(dir)
272
+ }
273
+ ```
274
+
275
+ ### 指定首选工具链
276
+ 如果某个目录下既有 `package.json`(前端脚手架),又有 Java `pom.xml`(后端集成),可以在调用 `resolveToolchain` 时传入显式偏好:
277
+
278
+ ```typescript
279
+ // 强制使用 maven 工具链处理该目录
280
+ const { provider } = await resolveToolchain(dir, 'maven')
281
+ await provider.build(dir)
282
+ ```
283
+
284
+ ---
285
+
286
+ ## 7. 独立注册表与测试最佳实践
287
+
288
+ 在编写单元测试时,如果需要隔离环境,避免全局状态污染,可以实例化独立的 `ToolchainRegistry`:
289
+
290
+ ```typescript
291
+ import { ToolchainRegistry } from '@ticatec/omniflow-core/toolchain'
292
+ import { GoToolchain } from './GoToolchain.js'
293
+
294
+ describe('GoToolchain', () => {
295
+ it('should detect go project in isolated registry', async () => {
296
+ // 传入 false 不加载默认 Provider,得到一个完全纯净的注册表
297
+ const registry = new ToolchainRegistry(false)
298
+ registry.register(new GoToolchain())
299
+
300
+ const res = await registry.detect('/fixtures/sample-go-app')
301
+ expect(res?.provider.name).toBe('go')
302
+ })
303
+ })
304
+ ```
package/package.json CHANGED
@@ -1,7 +1,25 @@
1
1
  {
2
2
  "name": "@ticatec/omniflow-core",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Core primitives, execution context, and toolchain SPI for OmniFlow CI/CD orchestrator",
5
+ "homepage": "https://github.com/ticatec/omniflow#readme",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/ticatec/omniflow.git",
9
+ "directory": "omniflow-core"
10
+ },
11
+ "bugs": {
12
+ "url": "https://github.com/ticatec/omniflow/issues"
13
+ },
14
+ "author": "Henry Feng <huili.f@gmail.com>",
15
+ "keywords": [
16
+ "ci",
17
+ "cd",
18
+ "pipeline",
19
+ "orchestration",
20
+ "primitives",
21
+ "toolchain"
22
+ ],
5
23
  "type": "module",
6
24
  "main": "./dist/index.js",
7
25
  "types": "./dist/index.d.ts",
@@ -48,6 +66,10 @@
48
66
  },
49
67
  "files": [
50
68
  "dist",
69
+ "src",
70
+ "docs/toolchain-extension.md",
71
+ "docs/toolchain-extension_CN.md",
72
+ "LICENSE",
51
73
  "README.md",
52
74
  "README_CN.md"
53
75
  ],