@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.
- package/LICENSE +21 -0
- package/README.md +112 -54
- package/README_CN.md +113 -55
- package/dist/index.d.ts +5 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/plugin/PluginContext.d.ts +39 -0
- package/dist/plugin/PluginContext.d.ts.map +1 -0
- package/dist/plugin/PluginContext.js +8 -0
- package/dist/plugin/PluginContext.js.map +1 -0
- package/dist/primitives/docker.d.ts.map +1 -1
- package/dist/primitives/docker.js +9 -0
- package/dist/primitives/docker.js.map +1 -1
- package/dist/primitives/git.d.ts +58 -16
- package/dist/primitives/git.d.ts.map +1 -1
- package/dist/primitives/git.js +91 -33
- package/dist/primitives/git.js.map +1 -1
- package/dist/primitives/shell.d.ts +4 -0
- package/dist/primitives/shell.d.ts.map +1 -1
- package/dist/primitives/shell.js +20 -60
- package/dist/primitives/shell.js.map +1 -1
- package/dist/primitives/ssh.d.ts +8 -5
- package/dist/primitives/ssh.d.ts.map +1 -1
- package/dist/primitives/ssh.js +49 -14
- package/dist/primitives/ssh.js.map +1 -1
- package/dist/primitives/subprocess.d.ts +52 -0
- package/dist/primitives/subprocess.d.ts.map +1 -0
- package/dist/primitives/subprocess.js +353 -0
- package/dist/primitives/subprocess.js.map +1 -0
- package/dist/toolchain/providers/GradleToolchain.d.ts +1 -1
- package/dist/toolchain/providers/GradleToolchain.d.ts.map +1 -1
- package/dist/toolchain/providers/GradleToolchain.js +4 -3
- package/dist/toolchain/providers/GradleToolchain.js.map +1 -1
- package/dist/toolchain/providers/MavenToolchain.d.ts +1 -1
- package/dist/toolchain/providers/MavenToolchain.d.ts.map +1 -1
- package/dist/toolchain/providers/MavenToolchain.js +3 -3
- package/dist/toolchain/providers/MavenToolchain.js.map +1 -1
- package/dist/toolchain/providers/NodeToolchain.d.ts +1 -1
- package/dist/toolchain/providers/NodeToolchain.d.ts.map +1 -1
- package/dist/toolchain/providers/NodeToolchain.js +61 -0
- package/dist/toolchain/providers/NodeToolchain.js.map +1 -1
- package/dist/toolchain/providers/pom.d.ts.map +1 -1
- package/dist/toolchain/providers/pom.js +13 -0
- package/dist/toolchain/providers/pom.js.map +1 -1
- package/dist/toolchain/registry.d.ts +11 -3
- package/dist/toolchain/registry.d.ts.map +1 -1
- package/dist/toolchain/registry.js +33 -8
- package/dist/toolchain/registry.js.map +1 -1
- package/dist/utils/mask.d.ts.map +1 -1
- package/dist/utils/mask.js +30 -5
- package/dist/utils/mask.js.map +1 -1
- package/docs/toolchain-extension.md +301 -0
- package/docs/toolchain-extension_CN.md +304 -0
- package/package.json +23 -1
- package/src/context/index.ts +74 -0
- package/src/context/storage.ts +8 -0
- package/src/context/types.ts +69 -0
- package/src/index.ts +97 -0
- package/src/plugin/PluginContext.ts +57 -0
- package/src/primitives/docker.ts +164 -0
- package/src/primitives/git.ts +172 -0
- package/src/primitives/index.ts +4 -0
- package/src/primitives/shell.ts +157 -0
- package/src/primitives/ssh.ts +249 -0
- package/src/primitives/subprocess.ts +389 -0
- package/src/toolchain/index.ts +6 -0
- package/src/toolchain/providers/GradleToolchain.ts +137 -0
- package/src/toolchain/providers/MavenToolchain.ts +64 -0
- package/src/toolchain/providers/NodeToolchain.ts +172 -0
- package/src/toolchain/providers/pom.ts +145 -0
- package/src/toolchain/registry.ts +161 -0
- package/src/toolchain/types.ts +40 -0
- package/src/utils/mask.ts +73 -0
- 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.
|
|
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
|
],
|