@ticatec/omniflow-core 0.1.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 +264 -0
- package/README_CN.md +265 -0
- package/dist/context/index.d.ts +27 -0
- package/dist/context/index.d.ts.map +1 -0
- package/dist/context/index.js +62 -0
- package/dist/context/index.js.map +1 -0
- package/dist/context/storage.d.ts +8 -0
- package/dist/context/storage.d.ts.map +1 -0
- package/dist/context/storage.js +7 -0
- package/dist/context/storage.js.map +1 -0
- package/dist/context/types.d.ts +64 -0
- package/dist/context/types.d.ts.map +1 -0
- package/dist/context/types.js +5 -0
- package/dist/context/types.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/primitives/docker.d.ts +41 -0
- package/dist/primitives/docker.d.ts.map +1 -0
- package/dist/primitives/docker.js +109 -0
- package/dist/primitives/docker.js.map +1 -0
- package/dist/primitives/git.d.ts +19 -0
- package/dist/primitives/git.d.ts.map +1 -0
- package/dist/primitives/git.js +84 -0
- package/dist/primitives/git.js.map +1 -0
- package/dist/primitives/index.d.ts +5 -0
- package/dist/primitives/index.d.ts.map +1 -0
- package/dist/primitives/index.js +5 -0
- package/dist/primitives/index.js.map +1 -0
- package/dist/primitives/shell.d.ts +42 -0
- package/dist/primitives/shell.d.ts.map +1 -0
- package/dist/primitives/shell.js +141 -0
- package/dist/primitives/shell.js.map +1 -0
- package/dist/primitives/ssh.d.ts +41 -0
- package/dist/primitives/ssh.d.ts.map +1 -0
- package/dist/primitives/ssh.js +128 -0
- package/dist/primitives/ssh.js.map +1 -0
- package/dist/toolchain/index.d.ts +7 -0
- package/dist/toolchain/index.d.ts.map +1 -0
- package/dist/toolchain/index.js +7 -0
- package/dist/toolchain/index.js.map +1 -0
- package/dist/toolchain/providers/GradleToolchain.d.ts +15 -0
- package/dist/toolchain/providers/GradleToolchain.d.ts.map +1 -0
- package/dist/toolchain/providers/GradleToolchain.js +130 -0
- package/dist/toolchain/providers/GradleToolchain.js.map +1 -0
- package/dist/toolchain/providers/MavenToolchain.d.ts +12 -0
- package/dist/toolchain/providers/MavenToolchain.d.ts.map +1 -0
- package/dist/toolchain/providers/MavenToolchain.js +57 -0
- package/dist/toolchain/providers/MavenToolchain.js.map +1 -0
- package/dist/toolchain/providers/NodeToolchain.d.ts +15 -0
- package/dist/toolchain/providers/NodeToolchain.d.ts.map +1 -0
- package/dist/toolchain/providers/NodeToolchain.js +93 -0
- package/dist/toolchain/providers/NodeToolchain.js.map +1 -0
- package/dist/toolchain/providers/pom.d.ts +18 -0
- package/dist/toolchain/providers/pom.d.ts.map +1 -0
- package/dist/toolchain/providers/pom.js +112 -0
- package/dist/toolchain/providers/pom.js.map +1 -0
- package/dist/toolchain/registry.d.ts +48 -0
- package/dist/toolchain/registry.d.ts.map +1 -0
- package/dist/toolchain/registry.js +97 -0
- package/dist/toolchain/registry.js.map +1 -0
- package/dist/toolchain/types.d.ts +33 -0
- package/dist/toolchain/types.d.ts.map +1 -0
- package/dist/toolchain/types.js +5 -0
- package/dist/toolchain/types.js.map +1 -0
- package/dist/utils/mask.d.ts +15 -0
- package/dist/utils/mask.d.ts.map +1 -0
- package/dist/utils/mask.js +50 -0
- package/dist/utils/mask.js.map +1 -0
- package/dist/utils/template.d.ts +12 -0
- package/dist/utils/template.d.ts.map +1 -0
- package/dist/utils/template.js +55 -0
- package/dist/utils/template.js.map +1 -0
- package/package.json +57 -0
package/README.md
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
# @omniflow/core
|
|
2
|
+
|
|
3
|
+
[](https://opensource.org/licenses/MIT)
|
|
4
|
+
[](https://nodejs.org/)
|
|
5
|
+
|
|
6
|
+
[中文文档](./README_CN.md)
|
|
7
|
+
|
|
8
|
+
**@omniflow/core** is the foundational core package for the OmniFlow CI/CD orchestrator. It provides execution context management, standard operational primitives (`shell`, `ssh`, `git`, `docker`), sensitive credential masking, and an extensible Toolchain SPI.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Highlights
|
|
13
|
+
|
|
14
|
+
- **Static Decoupled Primitives**: Import `shell`, `ssh`, `git`, and `docker` directly. No cumbersome `ctx.commands` or object passing required.
|
|
15
|
+
- **Transparent Context Injection (`AsyncLocalStorage`)**: Primitives automatically resolve `cwd`, environment variables `env`, and `logger` from the active async call context.
|
|
16
|
+
- **Automatic `--dry-run` Interception**: When running in simulation mode, destructive operations are intercepted by the core layer automatically, logged safely, and mocked without extra boilerplate in plugins.
|
|
17
|
+
- **Automatic Secret Masking**: Credentials and sensitive tokens in logs and commands are automatically masked (`***`).
|
|
18
|
+
- **Extensible Toolchain SPI**: Built-in support for Maven, Gradle, and Node.js (pnpm / yarn / npm / bun), with priority-based override and registration for custom language toolchains (e.g. Go, Rust, Python).
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Architecture Overview
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
┌────────────────────────────────────────────────────────┐
|
|
26
|
+
│ Plugin / Pipeline Command │
|
|
27
|
+
│ import { shell, ssh, git, docker } from '@omniflow/core'│
|
|
28
|
+
└─────────────────────────┬──────────────────────────────┘
|
|
29
|
+
│ calls primitives
|
|
30
|
+
▼
|
|
31
|
+
┌────────────────────────────────────────────────────────┐
|
|
32
|
+
│ @omniflow/core │
|
|
33
|
+
│ ┌──────────────────────────────────────────────────┐ │
|
|
34
|
+
│ │ AsyncLocalStorage Context (cwd, env, dryRun, log)│ │
|
|
35
|
+
│ └──────────────────────┬───────────────────────────┘ │
|
|
36
|
+
│ ▼ │
|
|
37
|
+
│ [Dry-Run Check] ──► [Credential Masker] │
|
|
38
|
+
│ ▼ │
|
|
39
|
+
│ ┌───────────────┬──────────────┬───────────────────┐ │
|
|
40
|
+
│ │ shell.run/sh │ ssh.exec/cp │ git & docker ops │ │
|
|
41
|
+
│ └───────────────┴──────────────┴───────────────────┘ │
|
|
42
|
+
│ ▼ │
|
|
43
|
+
│ ┌──────────────────────────────────────────────────┐ │
|
|
44
|
+
│ │ Toolchain SPI (Maven, Gradle, Node, Custom...) │ │
|
|
45
|
+
│ └──────────────────────────────────────────────────┘ │
|
|
46
|
+
└────────────────────────────────────────────────────────┘
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Installation
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pnpm add @omniflow/core
|
|
55
|
+
# or
|
|
56
|
+
npm install @omniflow/core
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Requires **Node.js >= 20.0.0**.
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Core Primitives
|
|
64
|
+
|
|
65
|
+
### 1. `shell`
|
|
66
|
+
Executes local shell commands using safe tagged template literals or string execution.
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
import { shell } from '@omniflow/core'
|
|
70
|
+
|
|
71
|
+
// Tagged template execution (safely splits arguments without shell injection vulnerabilities)
|
|
72
|
+
await shell.run`mvn clean package -DskipTests`
|
|
73
|
+
|
|
74
|
+
// Explicit cwd or options
|
|
75
|
+
await shell.run({ cwd: '/workspace/service-a' })`npm run build`
|
|
76
|
+
|
|
77
|
+
// Shell execution for compound commands (pipes, redirections, &&, ||)
|
|
78
|
+
await shell.sh('cat coverage/lcov.info | grep -v "test" > coverage/filtered.info')
|
|
79
|
+
|
|
80
|
+
// Capture output without throwing on non-zero exit
|
|
81
|
+
const res = await shell.output('git status --porcelain')
|
|
82
|
+
console.log(res.stdout, res.exitCode)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**Dry-Run & Context Awareness**:
|
|
86
|
+
Inside a `runWithContext` scope with `dryRun: true`, `shell.run` and `shell.sh` will print the planned command to the context logger and return mock results (`{ stdout: '[dry-run] ...', exitCode: 0 }`) without executing.
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
### 2. `ssh`
|
|
91
|
+
Executes remote commands and securely copies files via SSH/SCP with built-in connection pooling.
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
import { ssh } from '@omniflow/core'
|
|
95
|
+
|
|
96
|
+
const target = {
|
|
97
|
+
host: 'prod-app-01.internal',
|
|
98
|
+
user: 'deploy',
|
|
99
|
+
port: 22,
|
|
100
|
+
privateKey: process.env.SSH_PRIVATE_KEY
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Remote command execution
|
|
104
|
+
await ssh.exec(target, 'systemctl restart my-app.service')
|
|
105
|
+
|
|
106
|
+
// File transfer (SCP)
|
|
107
|
+
await ssh.cp(target, 'dist/app.tar.gz', '/opt/deploy/app.tar.gz')
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
### 3. `git`
|
|
113
|
+
Provides essential Git repository inspection and workspace lifecycle management.
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
import { git } from '@omniflow/core'
|
|
117
|
+
|
|
118
|
+
const branch = await git.currentBranch()
|
|
119
|
+
const commit = await git.currentCommit()
|
|
120
|
+
const dirty = await git.isDirty()
|
|
121
|
+
|
|
122
|
+
// Workspace promotion and synchronization
|
|
123
|
+
await git.fetch({ remote: 'origin', branch: 'main' })
|
|
124
|
+
await git.resetHard('origin/main')
|
|
125
|
+
await git.clean()
|
|
126
|
+
await git.checkout('release/v1.0')
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
### 4. `docker`
|
|
132
|
+
Encapsulates common container build, tag, push, and compose orchestration.
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
import { docker } from '@omniflow/core'
|
|
136
|
+
|
|
137
|
+
// Build and tag
|
|
138
|
+
await docker.build({
|
|
139
|
+
image: 'registry.internal/api',
|
|
140
|
+
tag: 'v1.2.0',
|
|
141
|
+
dockerfile: 'Dockerfile.prod',
|
|
142
|
+
buildArgs: { NODE_ENV: 'production' }
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
// Push to registry
|
|
146
|
+
await docker.push('registry.internal/api:v1.2.0')
|
|
147
|
+
|
|
148
|
+
// Docker Compose management
|
|
149
|
+
await docker.composeUp({ file: 'docker-compose.prod.yml', detached: true })
|
|
150
|
+
await docker.composeDown({ file: 'docker-compose.prod.yml' })
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## Execution Context & AsyncLocalStorage
|
|
156
|
+
|
|
157
|
+
The execution context manages runtime configuration, logging, and environment variables across asynchronous chains.
|
|
158
|
+
|
|
159
|
+
### Runner / Orchestrator Example:
|
|
160
|
+
```typescript
|
|
161
|
+
import { runWithContext, createMockContext } from '@omniflow/core'
|
|
162
|
+
|
|
163
|
+
const ctx = {
|
|
164
|
+
runId: 'run-20260912-001',
|
|
165
|
+
project: 'order-service',
|
|
166
|
+
env: 'staging',
|
|
167
|
+
dryRun: false,
|
|
168
|
+
workspace: '/workspaces/order-service',
|
|
169
|
+
variables: { REGISTRY: 'harbor.company.com' },
|
|
170
|
+
secrets: { DOCKER_TOKEN: 'secret-token-value' },
|
|
171
|
+
logger: {
|
|
172
|
+
info: (msg) => console.log(`[INFO] ${msg}`),
|
|
173
|
+
warn: (msg) => console.warn(`[WARN] ${msg}`),
|
|
174
|
+
error: (msg) => console.error(`[ERROR] ${msg}`),
|
|
175
|
+
debug: (msg) => console.debug(`[DEBUG] ${msg}`)
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
await runWithContext(ctx, async () => {
|
|
180
|
+
// Any function called here (directly or deeply nested) can call getContext()
|
|
181
|
+
// and primitives automatically use ctx.workspace, ctx.env, ctx.dryRun, and ctx.logger
|
|
182
|
+
await executePipelineSteps()
|
|
183
|
+
})
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Plugin / Step Example:
|
|
187
|
+
```typescript
|
|
188
|
+
import { getContext, tryGetContext, shell } from '@omniflow/core'
|
|
189
|
+
|
|
190
|
+
export async function myPluginTask() {
|
|
191
|
+
const ctx = getContext() // Throws if outside runWithContext
|
|
192
|
+
ctx.logger.info(`Building in ${ctx.workspace} for ${ctx.env}`)
|
|
193
|
+
|
|
194
|
+
await shell.run`npm test`
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## Toolchain SPI
|
|
201
|
+
|
|
202
|
+
OmniFlow Core abstracts language build systems using a clean Service Provider Interface (SPI).
|
|
203
|
+
|
|
204
|
+
### Built-in Toolchains
|
|
205
|
+
1. **Maven** (`MavenToolchain`): Detects `pom.xml`, parses groupId/artifactId/version, runs `./mvnw` or `mvn`.
|
|
206
|
+
2. **Gradle** (`GradleToolchain`): Detects `build.gradle` / `build.gradle.kts`, resolves Gradle Wrapper `./gradlew`, generates build commands.
|
|
207
|
+
3. **Node** (`NodeToolchain`): Detects `package.json`, auto-selects `pnpm`, `yarn`, `npm`, or `bun` from lockfiles, parses package coordinates.
|
|
208
|
+
|
|
209
|
+
### Detecting and Using Toolchains:
|
|
210
|
+
```typescript
|
|
211
|
+
import { resolveToolchain } from '@omniflow/core'
|
|
212
|
+
|
|
213
|
+
const { provider, detection } = await resolveToolchain('/path/to/project')
|
|
214
|
+
console.log(`Detected toolchain ${provider.name} because: ${detection.reason}`)
|
|
215
|
+
|
|
216
|
+
const info = await provider.projectInfo('/path/to/project')
|
|
217
|
+
console.log(`Project: ${info.name}@${info.version}`)
|
|
218
|
+
|
|
219
|
+
// Execute dependency install and build
|
|
220
|
+
await provider.install('/path/to/project')
|
|
221
|
+
await provider.build('/path/to/project', ['-DskipTests'])
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Registering Custom / Overriding Toolchains:
|
|
225
|
+
```typescript
|
|
226
|
+
import { registerToolchain, type ToolchainProvider } from '@omniflow/core'
|
|
227
|
+
|
|
228
|
+
const goToolchain: ToolchainProvider = {
|
|
229
|
+
name: 'go',
|
|
230
|
+
priority: 20, // Higher priority takes precedence over lower priority providers
|
|
231
|
+
detect: async (cwd) => {
|
|
232
|
+
// Check if go.mod exists
|
|
233
|
+
return { detected: true, confidence: 0.95 }
|
|
234
|
+
},
|
|
235
|
+
getProjectInfo: async (cwd) => ({
|
|
236
|
+
name: 'user-service',
|
|
237
|
+
version: '1.0.0'
|
|
238
|
+
}),
|
|
239
|
+
getBuildCommand: (cwd, options) => 'go build -v -o dist/app ./cmd/main.go'
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
registerToolchain(goToolchain)
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
> For a complete step-by-step guide on creating custom toolchains (Go, Rust) and enterprise overrides, see [Toolchain Extension Guide](./docs/toolchain-extension.md).
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
## Subpath Exports
|
|
250
|
+
|
|
251
|
+
`@omniflow/core` supports clean subpath imports in accordance with modern ESM standards:
|
|
252
|
+
|
|
253
|
+
| Export Path | Description |
|
|
254
|
+
|:---|:---|
|
|
255
|
+
| `@omniflow/core` | Full bundle: context, primitives, toolchain SPI, and utils |
|
|
256
|
+
| `@omniflow/core/context` | Execution context, `AsyncLocalStorage`, and mocking utilities |
|
|
257
|
+
| `@omniflow/core/primitives` | Core commands (`shell`, `ssh`, `git`, `docker`) |
|
|
258
|
+
| `@omniflow/core/toolchain` | Toolchain SPI, registry, and built-in providers |
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## License
|
|
263
|
+
|
|
264
|
+
MIT © [Ticatec](https://github.com/ticatec)
|
package/README_CN.md
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
# @omniflow/core
|
|
2
|
+
|
|
3
|
+
[](https://opensource.org/licenses/MIT)
|
|
4
|
+
[](https://nodejs.org/)
|
|
5
|
+
|
|
6
|
+
[English](./README.md)
|
|
7
|
+
|
|
8
|
+
**@omniflow/core** 是 OmniFlow CI/CD 统一编排器的基础核心库。它提供了运行时执行上下文管理、标准化命令原语(`shell`、`ssh`、`git`、`docker`)、敏感凭据脱敏以及可扩展的构建工具链 SPI(Toolchain SPI)。
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## 核心特性
|
|
13
|
+
|
|
14
|
+
- **原语与上下文解耦**:开发者与插件作者可直接通过静态 `import { shell, ssh, git, docker } from '@omniflow/core'` 导入命令,无需在函数间层层显式传递 `ctx`,移除原有的 `ctx.commands` 耦合。
|
|
15
|
+
- **透明上下文注入 (`AsyncLocalStorage`)**:底层基于 Node.js 原生 `AsyncLocalStorage`,基础原语在被调用时,会自动从当前的异步执行链路中解析 `cwd`(工作目录)、`env`(环境变量)、`dryRun`(模拟模式)与 `logger`。
|
|
16
|
+
- **开箱即用的 `--dry-run` 拦截保护**:在模拟运行模式下,破坏性操作(执行脚本、远程 SSH、推送镜像等)均由 Core 层底层自动拦截,安全记录日志并返回模拟结果,插件层无需编写冗余的 `if (ctx.dryRun)` 逻辑。
|
|
17
|
+
- **敏感信息自动脱敏 (Masking)**:命令参数与环境变量中包含的敏感词(如 `token`、`secret`、`password`、`key`)在格式化及输出至日志时自动替换为 `***`,杜绝日志泄露。
|
|
18
|
+
- **统一可扩展的 Toolchain SPI**:内置对 Maven、Gradle、Node.js(自动识别 pnpm / yarn / npm / bun)的识别与命令生成,同时支持第三方插件按优先级注册与覆盖扩展(如 Go、Rust、Python 等)。
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 架构概览
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
┌────────────────────────────────────────────────────────┐
|
|
26
|
+
│ 插件 / 流水线命令 │
|
|
27
|
+
│ import { shell, ssh, git, docker } from '@omniflow/core'│
|
|
28
|
+
└─────────────────────────┬──────────────────────────────┘
|
|
29
|
+
│ 直接调用底层原语
|
|
30
|
+
▼
|
|
31
|
+
┌────────────────────────────────────────────────────────┐
|
|
32
|
+
│ @omniflow/core │
|
|
33
|
+
│ ┌──────────────────────────────────────────────────┐ │
|
|
34
|
+
│ │ AsyncLocalStorage 上下文 (cwd, env, dryRun, log) │ │
|
|
35
|
+
│ └──────────────────────┬───────────────────────────┘ │
|
|
36
|
+
│ ▼ │
|
|
37
|
+
│ [Dry-Run 模式拦截] ──► [敏感凭据自动脱敏] │
|
|
38
|
+
│ ▼ │
|
|
39
|
+
│ ┌───────────────┬──────────────┬───────────────────┐ │
|
|
40
|
+
│ │ shell.run/sh │ ssh.exec/cp │ git & docker 操作 │ │
|
|
41
|
+
│ └───────────────┴──────────────┴───────────────────┘ │
|
|
42
|
+
│ ▼ │
|
|
43
|
+
│ ┌──────────────────────────────────────────────────┐ │
|
|
44
|
+
│ │ Toolchain SPI (Maven, Gradle, Node, 自定义工具链) │ │
|
|
45
|
+
│ └──────────────────────────────────────────────────┘ │
|
|
46
|
+
└────────────────────────────────────────────────────────┘
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## 安装
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pnpm add @omniflow/core
|
|
55
|
+
# 或
|
|
56
|
+
npm install @omniflow/core
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
运行环境要求:**Node.js >= 20.0.0**。
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## 核心原语说明
|
|
64
|
+
|
|
65
|
+
### 1. `shell`
|
|
66
|
+
用于执行本地 Shell 命令,推荐使用标签模板字符串(Tagged Template Literals)方式执行,原生防止命令行注入。
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
import { shell } from '@omniflow/core'
|
|
70
|
+
|
|
71
|
+
// 标签模板安全执行(自动拆分参数,防止参数注入)
|
|
72
|
+
await shell.run`mvn clean package -DskipTests`
|
|
73
|
+
|
|
74
|
+
// 支持显式指定工作目录或执行选项
|
|
75
|
+
await shell.run({ cwd: '/workspace/service-a' })`npm run build`
|
|
76
|
+
|
|
77
|
+
// Shell 复合命令(支持管道、重定向、&&、|| 等复合语法)
|
|
78
|
+
await shell.sh('cat coverage/lcov.info | grep -v "test" > coverage/filtered.info')
|
|
79
|
+
|
|
80
|
+
// 静默执行并捕获输出(非零退出码不主动抛出异常)
|
|
81
|
+
const res = await shell.output('git status --porcelain')
|
|
82
|
+
console.log(res.stdout, res.exitCode)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**Dry-Run 机制**:
|
|
86
|
+
在包含 `dryRun: true` 的 `runWithContext` 环境中,`shell.run` 和 `shell.sh` 会自动将待执行命令格式化并打入当前日志,同时返回 `{ stdout: '[dry-run] ...', exitCode: 0 }`,不会在系统上实际派生子进程。
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
### 2. `ssh`
|
|
91
|
+
提供跨节点远程命令执行与基于 SCP 的文件安全传输,内置连接池复用机制。
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
import { ssh } from '@omniflow/core'
|
|
95
|
+
|
|
96
|
+
const target = {
|
|
97
|
+
host: 'prod-app-01.internal',
|
|
98
|
+
user: 'deploy',
|
|
99
|
+
port: 22,
|
|
100
|
+
privateKey: process.env.SSH_PRIVATE_KEY
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// 远程执行命令
|
|
104
|
+
await ssh.exec(target, 'systemctl restart my-app.service')
|
|
105
|
+
|
|
106
|
+
// 文件安全分发 (SCP)
|
|
107
|
+
await ssh.cp(target, 'dist/app.tar.gz', '/opt/deploy/app.tar.gz')
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
### 3. `git`
|
|
113
|
+
提供仓库状态感知、分支切换、远端同步与工作区重置原语。
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
import { git } from '@omniflow/core'
|
|
117
|
+
|
|
118
|
+
const branch = await git.currentBranch()
|
|
119
|
+
const commit = await git.currentCommit()
|
|
120
|
+
const dirty = await git.isDirty()
|
|
121
|
+
|
|
122
|
+
// 晋升与工作区同步
|
|
123
|
+
await git.fetch({ remote: 'origin', branch: 'main' })
|
|
124
|
+
await git.resetHard('origin/main')
|
|
125
|
+
await git.clean()
|
|
126
|
+
await git.checkout('release/v1.0')
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
### 4. `docker`
|
|
132
|
+
封装常见的容器构建、打标签、推送与 Compose 编排生命周期。
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
import { docker } from '@omniflow/core'
|
|
136
|
+
|
|
137
|
+
// 镜像构建
|
|
138
|
+
await docker.build({
|
|
139
|
+
image: 'registry.internal/api',
|
|
140
|
+
tag: 'v1.2.0',
|
|
141
|
+
dockerfile: 'Dockerfile.prod',
|
|
142
|
+
buildArgs: { NODE_ENV: 'production' }
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
// 推送到远程仓库
|
|
146
|
+
await docker.push('registry.internal/api:v1.2.0')
|
|
147
|
+
|
|
148
|
+
// Docker Compose 服务生命周期管理
|
|
149
|
+
await docker.composeUp({ file: 'docker-compose.prod.yml', detached: true })
|
|
150
|
+
await docker.composeDown({ file: 'docker-compose.prod.yml' })
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## 执行上下文与 AsyncLocalStorage
|
|
156
|
+
|
|
157
|
+
执行上下文管理整个生命周期的项目元数据、环境变量、日志通道及模拟开关。
|
|
158
|
+
|
|
159
|
+
### 运行器 (Runner) 示例:
|
|
160
|
+
```typescript
|
|
161
|
+
import { runWithContext, createMockContext } from '@omniflow/core'
|
|
162
|
+
|
|
163
|
+
const ctx = {
|
|
164
|
+
runId: 'run-20260912-001',
|
|
165
|
+
project: 'order-service',
|
|
166
|
+
env: 'staging',
|
|
167
|
+
dryRun: false,
|
|
168
|
+
workspace: '/workspaces/order-service',
|
|
169
|
+
variables: { REGISTRY: 'harbor.company.com' },
|
|
170
|
+
secrets: { DOCKER_TOKEN: 'secret-token-value' },
|
|
171
|
+
logger: {
|
|
172
|
+
info: (msg) => console.log(`[INFO] ${msg}`),
|
|
173
|
+
warn: (msg) => console.warn(`[WARN] ${msg}`),
|
|
174
|
+
error: (msg) => console.error(`[ERROR] ${msg}`),
|
|
175
|
+
debug: (msg) => console.debug(`[DEBUG] ${msg}`)
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
await runWithContext(ctx, async () => {
|
|
180
|
+
// 在此处调用链中的任何深层异步函数,均可安全访问上下文
|
|
181
|
+
await executePipelineSteps()
|
|
182
|
+
})
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### 插件 (Plugin) 内部调用示例:
|
|
186
|
+
```typescript
|
|
187
|
+
import { getContext, tryGetContext, shell } from '@omniflow/core'
|
|
188
|
+
|
|
189
|
+
export async function myPluginTask() {
|
|
190
|
+
const ctx = getContext() // 若不在 runWithContext 环境中则抛出明确错误
|
|
191
|
+
ctx.logger.info(`正在构建工作区 ${ctx.workspace},目标环境:${ctx.env}`)
|
|
192
|
+
|
|
193
|
+
// 底层 shell 自动采用 ctx.workspace 作为 cwd,自动继承环境变量
|
|
194
|
+
await shell.run`npm test`
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## 工具链 SPI (Toolchain SPI)
|
|
201
|
+
|
|
202
|
+
OmniFlow Core 将不同编程语言的构建体系抽象为统一的 Service Provider Interface (SPI)。
|
|
203
|
+
|
|
204
|
+
### 内置工具链
|
|
205
|
+
1. **Maven** (`MavenToolchain`):检测 `pom.xml`,解析 groupId/artifactId/version 坐标,优先调用 `./mvnw` 或 `mvn`。
|
|
206
|
+
2. **Gradle** (`GradleToolchain`):检测 `build.gradle` / `build.gradle.kts`,优先使用 Gradle Wrapper `./gradlew`,支持多项目构建。
|
|
207
|
+
3. **Node** (`NodeToolchain`):检测 `package.json`,根据 lock 文件自动选择 `pnpm`、`yarn`、`npm` 或 `bun`,提取包名与版本。
|
|
208
|
+
|
|
209
|
+
### 使用与解析工具链:
|
|
210
|
+
```typescript
|
|
211
|
+
import { resolveToolchain } from '@omniflow/core'
|
|
212
|
+
|
|
213
|
+
const { provider, detection } = await resolveToolchain('/path/to/project')
|
|
214
|
+
console.log(`检测到工具链 ${provider.name},原因: ${detection.reason}`)
|
|
215
|
+
|
|
216
|
+
const info = await provider.projectInfo('/path/to/project')
|
|
217
|
+
console.log(`工程信息: ${info.name}@${info.version}`)
|
|
218
|
+
|
|
219
|
+
// 执行依赖安装与构建
|
|
220
|
+
await provider.install('/path/to/project')
|
|
221
|
+
await provider.build('/path/to/project', ['-DskipTests'])
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### 注册自定义工具链或覆盖默认行为:
|
|
225
|
+
```typescript
|
|
226
|
+
import { registerToolchain, type ToolchainProvider } from '@omniflow/core'
|
|
227
|
+
|
|
228
|
+
const goToolchain: ToolchainProvider = {
|
|
229
|
+
name: 'go',
|
|
230
|
+
priority: 20, // 优先级高于内置默认 Provider (10)
|
|
231
|
+
detect: async (cwd) => {
|
|
232
|
+
// 检测是否包含 go.mod
|
|
233
|
+
return { detected: true, confidence: 0.95 }
|
|
234
|
+
},
|
|
235
|
+
getProjectInfo: async (cwd) => ({
|
|
236
|
+
name: 'user-service',
|
|
237
|
+
version: '1.0.0'
|
|
238
|
+
}),
|
|
239
|
+
getBuildCommand: (cwd, options) => 'go build -v -o dist/app ./cmd/main.go'
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// 注册至全局注册表
|
|
243
|
+
registerToolchain(goToolchain)
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
> 关于创建自定义工具链(如 Go、Rust)及企业定制覆写的完整教程,详见 [Toolchain SPI 扩展指南](./docs/toolchain-extension_CN.md)。
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## 模块子路径导出
|
|
251
|
+
|
|
252
|
+
`@omniflow/core` 遵循现代 ESM 规范,支持精细化的子路径导入:
|
|
253
|
+
|
|
254
|
+
| 导出子路径 | 包含功能说明 |
|
|
255
|
+
|:---|:---|
|
|
256
|
+
| `@omniflow/core` | 全量包入口:包含上下文、原语、工具链 SPI 以及工具函数 |
|
|
257
|
+
| `@omniflow/core/context` | 执行上下文、`AsyncLocalStorage` 封装、Mock 测试辅助工具 |
|
|
258
|
+
| `@omniflow/core/primitives` | 命令原语(`shell`、`ssh`、`git`、`docker`) |
|
|
259
|
+
| `@omniflow/core/toolchain` | Toolchain SPI 接口、注册表与内置 Provider |
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
## 开源协议
|
|
264
|
+
|
|
265
|
+
MIT © [Ticatec](https://github.com/ticatec)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ExecutionContext } from './types.js';
|
|
2
|
+
export * from './types.js';
|
|
3
|
+
export { executionLocalStorage } from './storage.js';
|
|
4
|
+
/**
|
|
5
|
+
* Return the active ExecutionContext for the current asynchronous call stack.
|
|
6
|
+
*
|
|
7
|
+
* @throws Error if called outside an active execution scope (i.e., not inside runWithContext).
|
|
8
|
+
*/
|
|
9
|
+
export declare function getContext(): ExecutionContext;
|
|
10
|
+
/**
|
|
11
|
+
* Return the active ExecutionContext if available, or undefined.
|
|
12
|
+
*/
|
|
13
|
+
export declare function tryGetContext(): ExecutionContext | undefined;
|
|
14
|
+
/**
|
|
15
|
+
* Check whether an active ExecutionContext is available in the current scope.
|
|
16
|
+
*/
|
|
17
|
+
export declare function hasContext(): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Run a function within an ExecutionContext scope. All asynchronous calls
|
|
20
|
+
* initiated within `fn` will have access to `ctx` via `getContext()`.
|
|
21
|
+
*/
|
|
22
|
+
export declare function runWithContext<T>(ctx: ExecutionContext, fn: () => Promise<T> | T): Promise<T>;
|
|
23
|
+
/**
|
|
24
|
+
* Helper to build a fallback or testing ExecutionContext.
|
|
25
|
+
*/
|
|
26
|
+
export declare function createMockContext(overrides?: Partial<ExecutionContext>): ExecutionContext;
|
|
27
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/context/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAU,MAAM,YAAY,CAAA;AAE1D,cAAc,YAAY,CAAA;AAC1B,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAA;AAEpD;;;;GAIG;AACH,wBAAgB,UAAU,IAAI,gBAAgB,CAS7C;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,gBAAgB,GAAG,SAAS,CAE5D;AAED;;GAEG;AACH,wBAAgB,UAAU,IAAI,OAAO,CAEpC;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAAC,CAAC,EACpC,GAAG,EAAE,gBAAgB,EACrB,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,GACvB,OAAO,CAAC,CAAC,CAAC,CAEZ;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,GAAE,OAAO,CAAC,gBAAgB,CAAM,GAAG,gBAAgB,CAuB7F"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { executionLocalStorage } from './storage.js';
|
|
2
|
+
export * from './types.js';
|
|
3
|
+
export { executionLocalStorage } from './storage.js';
|
|
4
|
+
/**
|
|
5
|
+
* Return the active ExecutionContext for the current asynchronous call stack.
|
|
6
|
+
*
|
|
7
|
+
* @throws Error if called outside an active execution scope (i.e., not inside runWithContext).
|
|
8
|
+
*/
|
|
9
|
+
export function getContext() {
|
|
10
|
+
const ctx = executionLocalStorage.getStore();
|
|
11
|
+
if (!ctx) {
|
|
12
|
+
throw new Error('No active OmniFlow ExecutionContext found in this asynchronous scope. ' +
|
|
13
|
+
'Ensure this function is executed within runWithContext(ctx, fn).');
|
|
14
|
+
}
|
|
15
|
+
return ctx;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Return the active ExecutionContext if available, or undefined.
|
|
19
|
+
*/
|
|
20
|
+
export function tryGetContext() {
|
|
21
|
+
return executionLocalStorage.getStore();
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Check whether an active ExecutionContext is available in the current scope.
|
|
25
|
+
*/
|
|
26
|
+
export function hasContext() {
|
|
27
|
+
return executionLocalStorage.getStore() !== undefined;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Run a function within an ExecutionContext scope. All asynchronous calls
|
|
31
|
+
* initiated within `fn` will have access to `ctx` via `getContext()`.
|
|
32
|
+
*/
|
|
33
|
+
export async function runWithContext(ctx, fn) {
|
|
34
|
+
return executionLocalStorage.run(ctx, fn);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Helper to build a fallback or testing ExecutionContext.
|
|
38
|
+
*/
|
|
39
|
+
export function createMockContext(overrides = {}) {
|
|
40
|
+
const defaultLogger = {
|
|
41
|
+
info: (msg) => console.log(`[INFO] ${msg}`),
|
|
42
|
+
warn: (msg) => console.warn(`[WARN] ${msg}`),
|
|
43
|
+
error: (msg) => console.error(`[ERROR] ${msg}`),
|
|
44
|
+
debug: (msg) => console.debug(`[DEBUG] ${msg}`)
|
|
45
|
+
};
|
|
46
|
+
return {
|
|
47
|
+
runId: overrides.runId ?? `test-run-${Date.now()}`,
|
|
48
|
+
workspace: overrides.workspace ?? process.cwd(),
|
|
49
|
+
projectRoot: overrides.projectRoot ?? process.cwd(),
|
|
50
|
+
project: overrides.project ?? 'test-project',
|
|
51
|
+
module: overrides.module,
|
|
52
|
+
moduleFolder: overrides.moduleFolder,
|
|
53
|
+
environment: overrides.environment ?? 'test',
|
|
54
|
+
git: overrides.git ?? { branch: 'main', commit: '0000000', dirty: false },
|
|
55
|
+
env: overrides.env ?? {},
|
|
56
|
+
logger: overrides.logger ?? defaultLogger,
|
|
57
|
+
dryRun: overrides.dryRun ?? false,
|
|
58
|
+
sourceMode: overrides.sourceMode ?? 'local',
|
|
59
|
+
logFile: overrides.logFile
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/context/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAA;AAGpD,cAAc,YAAY,CAAA;AAC1B,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAA;AAEpD;;;;GAIG;AACH,MAAM,UAAU,UAAU;IACxB,MAAM,GAAG,GAAG,qBAAqB,CAAC,QAAQ,EAAE,CAAA;IAC5C,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,IAAI,KAAK,CACb,wEAAwE;YACxE,kEAAkE,CACnE,CAAA;IACH,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,aAAa;IAC3B,OAAO,qBAAqB,CAAC,QAAQ,EAAE,CAAA;AACzC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,UAAU;IACxB,OAAO,qBAAqB,CAAC,QAAQ,EAAE,KAAK,SAAS,CAAA;AACvD,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,GAAqB,EACrB,EAAwB;IAExB,OAAO,qBAAqB,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,CAAC,CAAA;AAC3C,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAAC,YAAuC,EAAE;IACzE,MAAM,aAAa,GAAW;QAC5B,IAAI,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,GAAG,EAAE,CAAC;QACnD,IAAI,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,GAAG,EAAE,CAAC;QACpD,KAAK,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,WAAW,GAAG,EAAE,CAAC;QACvD,KAAK,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,WAAW,GAAG,EAAE,CAAC;KACxD,CAAA;IAED,OAAO;QACL,KAAK,EAAE,SAAS,CAAC,KAAK,IAAI,YAAY,IAAI,CAAC,GAAG,EAAE,EAAE;QAClD,SAAS,EAAE,SAAS,CAAC,SAAS,IAAI,OAAO,CAAC,GAAG,EAAE;QAC/C,WAAW,EAAE,SAAS,CAAC,WAAW,IAAI,OAAO,CAAC,GAAG,EAAE;QACnD,OAAO,EAAE,SAAS,CAAC,OAAO,IAAI,cAAc;QAC5C,MAAM,EAAE,SAAS,CAAC,MAAM;QACxB,YAAY,EAAE,SAAS,CAAC,YAAY;QACpC,WAAW,EAAE,SAAS,CAAC,WAAW,IAAI,MAAM;QAC5C,GAAG,EAAE,SAAS,CAAC,GAAG,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,EAAE;QACzE,GAAG,EAAE,SAAS,CAAC,GAAG,IAAI,EAAE;QACxB,MAAM,EAAE,SAAS,CAAC,MAAM,IAAI,aAAa;QACzC,MAAM,EAAE,SAAS,CAAC,MAAM,IAAI,KAAK;QACjC,UAAU,EAAE,SAAS,CAAC,UAAU,IAAI,OAAO;QAC3C,OAAO,EAAE,SAAS,CAAC,OAAO;KAC3B,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
|
+
import type { ExecutionContext } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Node.js AsyncLocalStorage instance managing the active ExecutionContext
|
|
5
|
+
* across asynchronous function calls.
|
|
6
|
+
*/
|
|
7
|
+
export declare const executionLocalStorage: AsyncLocalStorage<ExecutionContext>;
|
|
8
|
+
//# sourceMappingURL=storage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../src/context/storage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AACpD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAElD;;;GAGG;AACH,eAAO,MAAM,qBAAqB,qCAA4C,CAAA"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
|
+
/**
|
|
3
|
+
* Node.js AsyncLocalStorage instance managing the active ExecutionContext
|
|
4
|
+
* across asynchronous function calls.
|
|
5
|
+
*/
|
|
6
|
+
export const executionLocalStorage = new AsyncLocalStorage();
|
|
7
|
+
//# sourceMappingURL=storage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.js","sourceRoot":"","sources":["../../src/context/storage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AAGpD;;;GAGG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,IAAI,iBAAiB,EAAoB,CAAA"}
|