@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
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ticatec
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
# @omniflow
|
|
1
|
+
# @ticatec/omniflow-core
|
|
2
2
|
|
|
3
3
|
[](https://opensource.org/licenses/MIT)
|
|
4
4
|
[](https://nodejs.org/)
|
|
5
5
|
|
|
6
6
|
[中文文档](./README_CN.md)
|
|
7
7
|
|
|
8
|
-
**@omniflow
|
|
8
|
+
**@ticatec/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
9
|
|
|
10
10
|
---
|
|
11
11
|
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
- **Static Decoupled Primitives**: Import `shell`, `ssh`, `git`, and `docker` directly. No cumbersome `ctx.commands` or object passing required.
|
|
15
15
|
- **Transparent Context Injection (`AsyncLocalStorage`)**: Primitives automatically resolve `cwd`, environment variables `env`, and `logger` from the active async call context.
|
|
16
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 (
|
|
17
|
+
- **Automatic Secret Masking**: Credentials and sensitive tokens in logs and commands are automatically masked (`********`).
|
|
18
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
19
|
|
|
20
20
|
---
|
|
@@ -24,12 +24,12 @@
|
|
|
24
24
|
```
|
|
25
25
|
┌────────────────────────────────────────────────────────┐
|
|
26
26
|
│ Plugin / Pipeline Command │
|
|
27
|
-
│ import { shell, ssh, git, docker } from '@omniflow
|
|
27
|
+
│ import { shell, ssh, git, docker } from '@ticatec/omniflow-core'│
|
|
28
28
|
└─────────────────────────┬──────────────────────────────┘
|
|
29
29
|
│ calls primitives
|
|
30
30
|
▼
|
|
31
31
|
┌────────────────────────────────────────────────────────┐
|
|
32
|
-
│ @omniflow
|
|
32
|
+
│ @ticatec/omniflow-core │
|
|
33
33
|
│ ┌──────────────────────────────────────────────────┐ │
|
|
34
34
|
│ │ AsyncLocalStorage Context (cwd, env, dryRun, log)│ │
|
|
35
35
|
│ └──────────────────────┬───────────────────────────┘ │
|
|
@@ -51,9 +51,9 @@
|
|
|
51
51
|
## Installation
|
|
52
52
|
|
|
53
53
|
```bash
|
|
54
|
-
pnpm add @omniflow
|
|
54
|
+
pnpm add @ticatec/omniflow-core
|
|
55
55
|
# or
|
|
56
|
-
npm install @omniflow
|
|
56
|
+
npm install @ticatec/omniflow-core
|
|
57
57
|
```
|
|
58
58
|
|
|
59
59
|
Requires **Node.js >= 20.0.0**.
|
|
@@ -66,7 +66,7 @@ Requires **Node.js >= 20.0.0**.
|
|
|
66
66
|
Executes local shell commands using safe tagged template literals or string execution.
|
|
67
67
|
|
|
68
68
|
```typescript
|
|
69
|
-
import { shell } from '@omniflow
|
|
69
|
+
import { shell } from '@ticatec/omniflow-core'
|
|
70
70
|
|
|
71
71
|
// Tagged template execution (safely splits arguments without shell injection vulnerabilities)
|
|
72
72
|
await shell.run`mvn clean package -DskipTests`
|
|
@@ -83,16 +83,20 @@ console.log(res.stdout, res.exitCode)
|
|
|
83
83
|
```
|
|
84
84
|
|
|
85
85
|
**Dry-Run & Context Awareness**:
|
|
86
|
-
Inside a `runWithContext` scope with `dryRun: true`, `shell.run` and `
|
|
86
|
+
Inside a `runWithContext` scope with `dryRun: true`, `shell.run`, `shell.sh`, and `ssh.exec` will print the planned command to the context logger and return clean mock results (`{ stdout: '', stderr: '', exitCode: 0, failed: false }`) without executing or polluting stdout.
|
|
87
|
+
|
|
88
|
+
> [!NOTE]
|
|
89
|
+
> **Credential Masking & Tail Buffering**: Output streams piped to `logFile` pass through a stateful transform that prevents secret leakage across chunk boundaries by buffering a sliding tail up to `maxSecretLen - 1` bytes. When exceptionally large single-line secrets (e.g. 64KB tokens or keys) are configured in the environment, log output chunks smaller than the tail buffer are retained until the buffer capacity is reached or the stream completes (`flush`), guaranteeing 100% masking coverage before emitting to disk.
|
|
87
90
|
|
|
88
91
|
---
|
|
89
92
|
|
|
90
93
|
### 2. `ssh`
|
|
91
|
-
Executes remote commands and securely copies files via SSH/SCP with
|
|
94
|
+
Executes remote commands and securely copies files via SSH/SCP with configuration-based target resolution.
|
|
92
95
|
|
|
93
96
|
```typescript
|
|
94
|
-
import { ssh } from '@omniflow
|
|
97
|
+
import { ssh } from '@ticatec/omniflow-core'
|
|
95
98
|
|
|
99
|
+
// Option A: Explicit connection target
|
|
96
100
|
const target = {
|
|
97
101
|
host: 'prod-app-01.internal',
|
|
98
102
|
user: 'deploy',
|
|
@@ -105,6 +109,27 @@ await ssh.exec(target, 'systemctl restart my-app.service')
|
|
|
105
109
|
|
|
106
110
|
// File transfer (SCP)
|
|
107
111
|
await ssh.cp(target, 'dist/app.tar.gz', '/opt/deploy/app.tar.gz')
|
|
112
|
+
|
|
113
|
+
// Option B: Named targets via ssh.configure()
|
|
114
|
+
ssh.configure('prod', {
|
|
115
|
+
host: 'prod-app-01.internal',
|
|
116
|
+
user: 'deploy',
|
|
117
|
+
port: 22,
|
|
118
|
+
privateKeyFile: '/home/deploy/.ssh/id_rsa'
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
// Call by target name directly
|
|
122
|
+
await ssh.exec('prod', 'uptime')
|
|
123
|
+
await ssh.cp('prod', 'dist/app.tar.gz', '/opt/deploy/app.tar.gz')
|
|
124
|
+
|
|
125
|
+
// Clean up singleton config in tests:
|
|
126
|
+
ssh.reset()
|
|
127
|
+
|
|
128
|
+
// Option C: Isolated instance without touching module-level singleton
|
|
129
|
+
const client = ssh.createClient({
|
|
130
|
+
staging: { host: 'staging.internal', user: 'ci', privateKeyFile: '~/.ssh/id_rsa' }
|
|
131
|
+
})
|
|
132
|
+
await client.exec('staging', 'hostname')
|
|
108
133
|
```
|
|
109
134
|
|
|
110
135
|
---
|
|
@@ -113,7 +138,7 @@ await ssh.cp(target, 'dist/app.tar.gz', '/opt/deploy/app.tar.gz')
|
|
|
113
138
|
Provides essential Git repository inspection and workspace lifecycle management.
|
|
114
139
|
|
|
115
140
|
```typescript
|
|
116
|
-
import { git } from '@omniflow
|
|
141
|
+
import { git } from '@ticatec/omniflow-core'
|
|
117
142
|
|
|
118
143
|
const branch = await git.currentBranch()
|
|
119
144
|
const commit = await git.currentCommit()
|
|
@@ -132,12 +157,11 @@ await git.checkout('release/v1.0')
|
|
|
132
157
|
Encapsulates common container build, tag, push, and compose orchestration.
|
|
133
158
|
|
|
134
159
|
```typescript
|
|
135
|
-
import { docker } from '@omniflow
|
|
160
|
+
import { docker } from '@ticatec/omniflow-core'
|
|
136
161
|
|
|
137
162
|
// Build and tag
|
|
138
163
|
await docker.build({
|
|
139
|
-
image: 'registry.internal/api',
|
|
140
|
-
tag: 'v1.2.0',
|
|
164
|
+
image: 'registry.internal/api:v1.2.0',
|
|
141
165
|
dockerfile: 'Dockerfile.prod',
|
|
142
166
|
buildArgs: { NODE_ENV: 'production' }
|
|
143
167
|
})
|
|
@@ -146,8 +170,8 @@ await docker.build({
|
|
|
146
170
|
await docker.push('registry.internal/api:v1.2.0')
|
|
147
171
|
|
|
148
172
|
// Docker Compose management
|
|
149
|
-
await docker.composeUp({
|
|
150
|
-
await docker.composeDown({
|
|
173
|
+
await docker.composeUp({ files: 'docker-compose.prod.yml', detach: true })
|
|
174
|
+
await docker.composeDown({ files: 'docker-compose.prod.yml' })
|
|
151
175
|
```
|
|
152
176
|
|
|
153
177
|
---
|
|
@@ -158,23 +182,20 @@ The execution context manages runtime configuration, logging, and environment va
|
|
|
158
182
|
|
|
159
183
|
### Runner / Orchestrator Example:
|
|
160
184
|
```typescript
|
|
161
|
-
import { runWithContext, createMockContext } from '@omniflow
|
|
185
|
+
import { runWithContext, createMockContext } from '@ticatec/omniflow-core'
|
|
162
186
|
|
|
163
|
-
const ctx = {
|
|
187
|
+
const ctx = createMockContext({
|
|
164
188
|
runId: 'run-20260912-001',
|
|
165
189
|
project: 'order-service',
|
|
166
|
-
|
|
190
|
+
environment: 'staging',
|
|
167
191
|
dryRun: false,
|
|
168
192
|
workspace: '/workspaces/order-service',
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
warn: (msg) => console.warn(`[WARN] ${msg}`),
|
|
174
|
-
error: (msg) => console.error(`[ERROR] ${msg}`),
|
|
175
|
-
debug: (msg) => console.debug(`[DEBUG] ${msg}`)
|
|
193
|
+
projectRoot: '/workspaces/order-service',
|
|
194
|
+
env: {
|
|
195
|
+
REGISTRY: 'harbor.company.com',
|
|
196
|
+
DOCKER_TOKEN: 'secret-token-value'
|
|
176
197
|
}
|
|
177
|
-
}
|
|
198
|
+
})
|
|
178
199
|
|
|
179
200
|
await runWithContext(ctx, async () => {
|
|
180
201
|
// Any function called here (directly or deeply nested) can call getContext()
|
|
@@ -185,11 +206,11 @@ await runWithContext(ctx, async () => {
|
|
|
185
206
|
|
|
186
207
|
### Plugin / Step Example:
|
|
187
208
|
```typescript
|
|
188
|
-
import { getContext, tryGetContext, shell } from '@omniflow
|
|
209
|
+
import { getContext, tryGetContext, shell } from '@ticatec/omniflow-core'
|
|
189
210
|
|
|
190
211
|
export async function myPluginTask() {
|
|
191
212
|
const ctx = getContext() // Throws if outside runWithContext
|
|
192
|
-
ctx.logger.info(`Building in ${ctx.workspace} for ${ctx.
|
|
213
|
+
ctx.logger.info(`Building in ${ctx.workspace} for environment ${ctx.environment}`)
|
|
193
214
|
|
|
194
215
|
await shell.run`npm test`
|
|
195
216
|
}
|
|
@@ -202,13 +223,13 @@ export async function myPluginTask() {
|
|
|
202
223
|
OmniFlow Core abstracts language build systems using a clean Service Provider Interface (SPI).
|
|
203
224
|
|
|
204
225
|
### 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.
|
|
226
|
+
1. **Maven** (`MavenToolchain`): Detects `pom.xml`, parses groupId/artifactId/version, runs `./mvnw` or `mvn` (priority: 30).
|
|
227
|
+
2. **Gradle** (`GradleToolchain`): Detects `build.gradle` / `build.gradle.kts`, resolves Gradle Wrapper `./gradlew`, generates build commands (priority: 20).
|
|
228
|
+
3. **Node** (`NodeToolchain`): Detects `package.json`, auto-selects `pnpm`, `yarn`, `npm`, or `bun` from lockfiles, parses package coordinates (priority: 10).
|
|
208
229
|
|
|
209
230
|
### Detecting and Using Toolchains:
|
|
210
231
|
```typescript
|
|
211
|
-
import { resolveToolchain } from '@omniflow
|
|
232
|
+
import { resolveToolchain } from '@ticatec/omniflow-core'
|
|
212
233
|
|
|
213
234
|
const { provider, detection } = await resolveToolchain('/path/to/project')
|
|
214
235
|
console.log(`Detected toolchain ${provider.name} because: ${detection.reason}`)
|
|
@@ -221,41 +242,78 @@ await provider.install('/path/to/project')
|
|
|
221
242
|
await provider.build('/path/to/project', ['-DskipTests'])
|
|
222
243
|
```
|
|
223
244
|
|
|
245
|
+
> [!NOTE]
|
|
246
|
+
> **Reproducible CI Installation**: `NodeToolchain.install()` automatically enforces immutable lockfile installations (`npm ci`, `pnpm install --frozen-lockfile`, `yarn install --frozen-lockfile` / `--immutable`, `bun install --frozen-lockfile`) whenever a lockfile is present in the project directory, preventing accidental lockfile mutation or dependency drift during automated CI/CD runs. If no lockfile exists in a new project, it falls back to standard package installation without error.
|
|
247
|
+
|
|
224
248
|
### Registering Custom / Overriding Toolchains:
|
|
225
249
|
```typescript
|
|
226
|
-
import {
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
250
|
+
import {
|
|
251
|
+
registerToolchain,
|
|
252
|
+
shell,
|
|
253
|
+
type ToolchainProvider,
|
|
254
|
+
type DetectionResult,
|
|
255
|
+
type ProjectInfo
|
|
256
|
+
} from '@ticatec/omniflow-core'
|
|
257
|
+
import fs from 'node:fs/promises'
|
|
258
|
+
import path from 'node:path'
|
|
259
|
+
|
|
260
|
+
export class GoToolchain implements ToolchainProvider {
|
|
261
|
+
readonly name = 'go'
|
|
262
|
+
readonly priority: number = 50 // Higher priority takes precedence during automatic detection
|
|
263
|
+
|
|
264
|
+
async detect(projectDir: string): Promise<DetectionResult | null> {
|
|
265
|
+
try {
|
|
266
|
+
await fs.access(path.join(projectDir, 'go.mod'))
|
|
267
|
+
return { name: this.name, reason: 'found go.mod' }
|
|
268
|
+
} catch {
|
|
269
|
+
return null
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
async projectInfo(projectDir: string): Promise<ProjectInfo> {
|
|
274
|
+
return { name: 'user-service', version: '1.0.0', fullName: 'user-service' }
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
async install(projectDir: string, flags: string[] = []): Promise<void> {
|
|
278
|
+
await shell.run({ cwd: projectDir })`go mod download ${flags}`
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
async build(projectDir: string, flags: string[] = []): Promise<void> {
|
|
282
|
+
await shell.run({ cwd: projectDir })`go build -v -o dist/app ${flags}`
|
|
283
|
+
}
|
|
240
284
|
}
|
|
241
285
|
|
|
242
|
-
registerToolchain(
|
|
286
|
+
registerToolchain(new GoToolchain())
|
|
243
287
|
```
|
|
244
288
|
|
|
245
289
|
> 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
290
|
|
|
247
291
|
---
|
|
248
292
|
|
|
293
|
+
## Breaking Changes in v0.2.0
|
|
294
|
+
|
|
295
|
+
If upgrading from an earlier experimental version, note the following breaking API and behavior changes:
|
|
296
|
+
|
|
297
|
+
1. **`--dry-run` simulation output**: In dry-run mode, commands now return `{ stdout: '', stderr: '', exitCode: 0, failed: false }` instead of synthetic strings like `"[DRY-RUN] ..."`. The simulated command is logged cleanly to `ctx.logger.info` without polluting stdout.
|
|
298
|
+
2. **`docker.build` image requirement**: The `image` property in `DockerBuildOptions` is now strictly required. Implicit defaulting to `'latest'` and undocumented alias parameters (`tag`, `tags`) have been removed to avoid silent misconfiguration.
|
|
299
|
+
3. **`docker.composeUp` property names**: Standardized on `files?: string | string[]` and `detach?: boolean`. Legacy aliases (`file`, `detached`) have been removed.
|
|
300
|
+
4. **`ssh` configuration naming**: Target SSH configuration adheres strictly to camelCase: `privateKey`, `privateKeyFile`. Deprecated snake_case properties (`private_key`, `private_key_file`) have been removed.
|
|
301
|
+
5. **`resolveToolchain` options**: Options are normalized to `{ preferred?: string; fallback?: string }`. Deprecated alias `configured` has been removed.
|
|
302
|
+
6. **Command spawn failure exit code**: When a subprocess fails to spawn (e.g. executable not found), the runner returns `exitCode: -1` and `failed: true` instead of mistakenly reporting exit code `0`.
|
|
303
|
+
7. **`git.resetHard` & `git.checkout` string argument**: In v0.1.x, passing a single string argument was interpreted as the working directory (`resetHard(dir?, target?)`, `checkout(dir?, ref?)`). In v0.2.0, a bare string argument designates the target commit/ref (`resetHard(target?: string | GitResetOptions)`, `checkout(ref?: string | GitCheckoutOptions)`). To specify an explicit working directory, pass an options object with `dir`: `{ dir: '/path/to/repo' }`.
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
249
307
|
## Subpath Exports
|
|
250
308
|
|
|
251
|
-
`@omniflow
|
|
309
|
+
`@ticatec/omniflow-core` supports clean subpath imports in accordance with modern ESM standards:
|
|
252
310
|
|
|
253
311
|
| Export Path | Description |
|
|
254
312
|
|:---|:---|
|
|
255
|
-
| `@omniflow
|
|
256
|
-
| `@omniflow
|
|
257
|
-
| `@omniflow
|
|
258
|
-
| `@omniflow
|
|
313
|
+
| `@ticatec/omniflow-core` | Full bundle: context, primitives, toolchain SPI, and utils |
|
|
314
|
+
| `@ticatec/omniflow-core/context` | Execution context, `AsyncLocalStorage`, and mocking utilities |
|
|
315
|
+
| `@ticatec/omniflow-core/primitives` | Core commands (`shell`, `ssh`, `git`, `docker`) |
|
|
316
|
+
| `@ticatec/omniflow-core/toolchain` | Toolchain SPI, registry, and built-in providers |
|
|
259
317
|
|
|
260
318
|
---
|
|
261
319
|
|
package/README_CN.md
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
# @omniflow
|
|
1
|
+
# @ticatec/omniflow-core
|
|
2
2
|
|
|
3
3
|
[](https://opensource.org/licenses/MIT)
|
|
4
4
|
[](https://nodejs.org/)
|
|
5
5
|
|
|
6
6
|
[English](./README.md)
|
|
7
7
|
|
|
8
|
-
**@omniflow
|
|
8
|
+
**@ticatec/omniflow-core** 是 OmniFlow CI/CD 统一编排器的基础核心库。它提供了运行时执行上下文管理、标准化命令原语(`shell`、`ssh`、`git`、`docker`)、敏感凭据脱敏以及可扩展的构建工具链 SPI(Toolchain SPI)。
|
|
9
9
|
|
|
10
10
|
---
|
|
11
11
|
|
|
12
12
|
## 核心特性
|
|
13
13
|
|
|
14
|
-
- **原语与上下文解耦**:开发者与插件作者可直接通过静态 `import { shell, ssh, git, docker } from '@omniflow
|
|
14
|
+
- **原语与上下文解耦**:开发者与插件作者可直接通过静态 `import { shell, ssh, git, docker } from '@ticatec/omniflow-core'` 导入命令,无需在函数间层层显式传递 `ctx`,移除原有的 `ctx.commands` 耦合。
|
|
15
15
|
- **透明上下文注入 (`AsyncLocalStorage`)**:底层基于 Node.js 原生 `AsyncLocalStorage`,基础原语在被调用时,会自动从当前的异步执行链路中解析 `cwd`(工作目录)、`env`(环境变量)、`dryRun`(模拟模式)与 `logger`。
|
|
16
16
|
- **开箱即用的 `--dry-run` 拦截保护**:在模拟运行模式下,破坏性操作(执行脚本、远程 SSH、推送镜像等)均由 Core 层底层自动拦截,安全记录日志并返回模拟结果,插件层无需编写冗余的 `if (ctx.dryRun)` 逻辑。
|
|
17
|
-
- **敏感信息自动脱敏 (Masking)**:命令参数与环境变量中包含的敏感词(如 `token`、`secret`、`password`、`key`)在格式化及输出至日志时自动替换为
|
|
17
|
+
- **敏感信息自动脱敏 (Masking)**:命令参数与环境变量中包含的敏感词(如 `token`、`secret`、`password`、`key`)在格式化及输出至日志时自动替换为 `********`,杜绝日志泄露。
|
|
18
18
|
- **统一可扩展的 Toolchain SPI**:内置对 Maven、Gradle、Node.js(自动识别 pnpm / yarn / npm / bun)的识别与命令生成,同时支持第三方插件按优先级注册与覆盖扩展(如 Go、Rust、Python 等)。
|
|
19
19
|
|
|
20
20
|
---
|
|
@@ -24,12 +24,12 @@
|
|
|
24
24
|
```
|
|
25
25
|
┌────────────────────────────────────────────────────────┐
|
|
26
26
|
│ 插件 / 流水线命令 │
|
|
27
|
-
│ import { shell, ssh, git, docker } from '@omniflow
|
|
27
|
+
│ import { shell, ssh, git, docker } from '@ticatec/omniflow-core'│
|
|
28
28
|
└─────────────────────────┬──────────────────────────────┘
|
|
29
29
|
│ 直接调用底层原语
|
|
30
30
|
▼
|
|
31
31
|
┌────────────────────────────────────────────────────────┐
|
|
32
|
-
│ @omniflow
|
|
32
|
+
│ @ticatec/omniflow-core │
|
|
33
33
|
│ ┌──────────────────────────────────────────────────┐ │
|
|
34
34
|
│ │ AsyncLocalStorage 上下文 (cwd, env, dryRun, log) │ │
|
|
35
35
|
│ └──────────────────────┬───────────────────────────┘ │
|
|
@@ -51,9 +51,9 @@
|
|
|
51
51
|
## 安装
|
|
52
52
|
|
|
53
53
|
```bash
|
|
54
|
-
pnpm add @omniflow
|
|
54
|
+
pnpm add @ticatec/omniflow-core
|
|
55
55
|
# 或
|
|
56
|
-
npm install @omniflow
|
|
56
|
+
npm install @ticatec/omniflow-core
|
|
57
57
|
```
|
|
58
58
|
|
|
59
59
|
运行环境要求:**Node.js >= 20.0.0**。
|
|
@@ -66,7 +66,7 @@ npm install @omniflow/core
|
|
|
66
66
|
用于执行本地 Shell 命令,推荐使用标签模板字符串(Tagged Template Literals)方式执行,原生防止命令行注入。
|
|
67
67
|
|
|
68
68
|
```typescript
|
|
69
|
-
import { shell } from '@omniflow
|
|
69
|
+
import { shell } from '@ticatec/omniflow-core'
|
|
70
70
|
|
|
71
71
|
// 标签模板安全执行(自动拆分参数,防止参数注入)
|
|
72
72
|
await shell.run`mvn clean package -DskipTests`
|
|
@@ -83,16 +83,20 @@ console.log(res.stdout, res.exitCode)
|
|
|
83
83
|
```
|
|
84
84
|
|
|
85
85
|
**Dry-Run 机制**:
|
|
86
|
-
在包含 `dryRun: true` 的 `runWithContext` 环境中,`shell.run`
|
|
86
|
+
在包含 `dryRun: true` 的 `runWithContext` 环境中,`shell.run`、`shell.sh` 与 `ssh.exec` 会自动将待执行命令格式化并打入当前日志,同时返回纯净的默认结果(`{ stdout: '', stderr: '', exitCode: 0, failed: false }`),不会在系统上实际派生子进程,也不会用假字符串污染 stdout。
|
|
87
|
+
|
|
88
|
+
> [!NOTE]
|
|
89
|
+
> **凭据脱敏与尾缓冲机制**:输出写入 `logFile` 时会经过有状态 Transform 流处理,在数据块(chunk)边界保留长度为 `maxSecretLen - 1` 的滑动尾缓冲以防止凭据跨块泄漏。当环境中配置了极长单行凭据(如 64KB 的 Token 或密钥段)时,小于尾缓冲容量的日志输出会暂存在流缓冲中,直到后续输出填满缓冲或流结束(flush)时全量脱敏刷盘,确保跨块切分下 100% 零泄露。
|
|
87
90
|
|
|
88
91
|
---
|
|
89
92
|
|
|
90
93
|
### 2. `ssh`
|
|
91
|
-
提供跨节点远程命令执行与基于 SCP
|
|
94
|
+
提供跨节点远程命令执行与基于 SCP 的文件安全传输,支持配置化目标解析与安全执行。
|
|
92
95
|
|
|
93
96
|
```typescript
|
|
94
|
-
import { ssh } from '@omniflow
|
|
97
|
+
import { ssh } from '@ticatec/omniflow-core'
|
|
95
98
|
|
|
99
|
+
// 方式 A:显式连接目标对象
|
|
96
100
|
const target = {
|
|
97
101
|
host: 'prod-app-01.internal',
|
|
98
102
|
user: 'deploy',
|
|
@@ -105,6 +109,27 @@ await ssh.exec(target, 'systemctl restart my-app.service')
|
|
|
105
109
|
|
|
106
110
|
// 文件安全分发 (SCP)
|
|
107
111
|
await ssh.cp(target, 'dist/app.tar.gz', '/opt/deploy/app.tar.gz')
|
|
112
|
+
|
|
113
|
+
// 方式 B:通过 ssh.configure() 注册命名主机
|
|
114
|
+
ssh.configure('prod', {
|
|
115
|
+
host: 'prod-app-01.internal',
|
|
116
|
+
user: 'deploy',
|
|
117
|
+
port: 22,
|
|
118
|
+
privateKeyFile: '/home/deploy/.ssh/id_rsa'
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
// 直接按别名调用
|
|
122
|
+
await ssh.exec('prod', 'uptime')
|
|
123
|
+
await ssh.cp('prod', 'dist/app.tar.gz', '/opt/deploy/app.tar.gz')
|
|
124
|
+
|
|
125
|
+
// 测试清理单例配置:
|
|
126
|
+
ssh.reset()
|
|
127
|
+
|
|
128
|
+
// 方式 C:创建独立实例,避免污染模块级单例
|
|
129
|
+
const client = ssh.createClient({
|
|
130
|
+
staging: { host: 'staging.internal', user: 'ci', privateKeyFile: '~/.ssh/id_rsa' }
|
|
131
|
+
})
|
|
132
|
+
await client.exec('staging', 'hostname')
|
|
108
133
|
```
|
|
109
134
|
|
|
110
135
|
---
|
|
@@ -113,7 +138,7 @@ await ssh.cp(target, 'dist/app.tar.gz', '/opt/deploy/app.tar.gz')
|
|
|
113
138
|
提供仓库状态感知、分支切换、远端同步与工作区重置原语。
|
|
114
139
|
|
|
115
140
|
```typescript
|
|
116
|
-
import { git } from '@omniflow
|
|
141
|
+
import { git } from '@ticatec/omniflow-core'
|
|
117
142
|
|
|
118
143
|
const branch = await git.currentBranch()
|
|
119
144
|
const commit = await git.currentCommit()
|
|
@@ -132,12 +157,11 @@ await git.checkout('release/v1.0')
|
|
|
132
157
|
封装常见的容器构建、打标签、推送与 Compose 编排生命周期。
|
|
133
158
|
|
|
134
159
|
```typescript
|
|
135
|
-
import { docker } from '@omniflow
|
|
160
|
+
import { docker } from '@ticatec/omniflow-core'
|
|
136
161
|
|
|
137
162
|
// 镜像构建
|
|
138
163
|
await docker.build({
|
|
139
|
-
image: 'registry.internal/api',
|
|
140
|
-
tag: 'v1.2.0',
|
|
164
|
+
image: 'registry.internal/api:v1.2.0',
|
|
141
165
|
dockerfile: 'Dockerfile.prod',
|
|
142
166
|
buildArgs: { NODE_ENV: 'production' }
|
|
143
167
|
})
|
|
@@ -146,8 +170,8 @@ await docker.build({
|
|
|
146
170
|
await docker.push('registry.internal/api:v1.2.0')
|
|
147
171
|
|
|
148
172
|
// Docker Compose 服务生命周期管理
|
|
149
|
-
await docker.composeUp({
|
|
150
|
-
await docker.composeDown({
|
|
173
|
+
await docker.composeUp({ files: 'docker-compose.prod.yml', detach: true })
|
|
174
|
+
await docker.composeDown({ files: 'docker-compose.prod.yml' })
|
|
151
175
|
```
|
|
152
176
|
|
|
153
177
|
---
|
|
@@ -158,23 +182,20 @@ await docker.composeDown({ file: 'docker-compose.prod.yml' })
|
|
|
158
182
|
|
|
159
183
|
### 运行器 (Runner) 示例:
|
|
160
184
|
```typescript
|
|
161
|
-
import { runWithContext, createMockContext } from '@omniflow
|
|
185
|
+
import { runWithContext, createMockContext } from '@ticatec/omniflow-core'
|
|
162
186
|
|
|
163
|
-
const ctx = {
|
|
187
|
+
const ctx = createMockContext({
|
|
164
188
|
runId: 'run-20260912-001',
|
|
165
189
|
project: 'order-service',
|
|
166
|
-
|
|
190
|
+
environment: 'staging',
|
|
167
191
|
dryRun: false,
|
|
168
192
|
workspace: '/workspaces/order-service',
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
warn: (msg) => console.warn(`[WARN] ${msg}`),
|
|
174
|
-
error: (msg) => console.error(`[ERROR] ${msg}`),
|
|
175
|
-
debug: (msg) => console.debug(`[DEBUG] ${msg}`)
|
|
193
|
+
projectRoot: '/workspaces/order-service',
|
|
194
|
+
env: {
|
|
195
|
+
REGISTRY: 'harbor.company.com',
|
|
196
|
+
DOCKER_TOKEN: 'secret-token-value'
|
|
176
197
|
}
|
|
177
|
-
}
|
|
198
|
+
})
|
|
178
199
|
|
|
179
200
|
await runWithContext(ctx, async () => {
|
|
180
201
|
// 在此处调用链中的任何深层异步函数,均可安全访问上下文
|
|
@@ -184,11 +205,11 @@ await runWithContext(ctx, async () => {
|
|
|
184
205
|
|
|
185
206
|
### 插件 (Plugin) 内部调用示例:
|
|
186
207
|
```typescript
|
|
187
|
-
import { getContext, tryGetContext, shell } from '@omniflow
|
|
208
|
+
import { getContext, tryGetContext, shell } from '@ticatec/omniflow-core'
|
|
188
209
|
|
|
189
210
|
export async function myPluginTask() {
|
|
190
211
|
const ctx = getContext() // 若不在 runWithContext 环境中则抛出明确错误
|
|
191
|
-
ctx.logger.info(`正在构建工作区 ${ctx.workspace},目标环境:${ctx.
|
|
212
|
+
ctx.logger.info(`正在构建工作区 ${ctx.workspace},目标环境:${ctx.environment}`)
|
|
192
213
|
|
|
193
214
|
// 底层 shell 自动采用 ctx.workspace 作为 cwd,自动继承环境变量
|
|
194
215
|
await shell.run`npm test`
|
|
@@ -202,13 +223,13 @@ export async function myPluginTask() {
|
|
|
202
223
|
OmniFlow Core 将不同编程语言的构建体系抽象为统一的 Service Provider Interface (SPI)。
|
|
203
224
|
|
|
204
225
|
### 内置工具链
|
|
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
|
|
226
|
+
1. **Maven** (`MavenToolchain`):检测 `pom.xml`,解析 groupId/artifactId/version 坐标,优先调用 `./mvnw` 或 `mvn`(优先级:30)。
|
|
227
|
+
2. **Gradle** (`GradleToolchain`):检测 `build.gradle` / `build.gradle.kts`,优先使用 Gradle Wrapper `./gradlew`,支持多项目构建(优先级:20)。
|
|
228
|
+
3. **Node** (`NodeToolchain`):检测 `package.json`,根据 lock 文件自动选择 `pnpm`、`yarn`、`npm` 或 `bun`,提取包名与版本(优先级:10)。
|
|
208
229
|
|
|
209
230
|
### 使用与解析工具链:
|
|
210
231
|
```typescript
|
|
211
|
-
import { resolveToolchain } from '@omniflow
|
|
232
|
+
import { resolveToolchain } from '@ticatec/omniflow-core'
|
|
212
233
|
|
|
213
234
|
const { provider, detection } = await resolveToolchain('/path/to/project')
|
|
214
235
|
console.log(`检测到工具链 ${provider.name},原因: ${detection.reason}`)
|
|
@@ -221,42 +242,79 @@ await provider.install('/path/to/project')
|
|
|
221
242
|
await provider.build('/path/to/project', ['-DskipTests'])
|
|
222
243
|
```
|
|
223
244
|
|
|
245
|
+
> [!NOTE]
|
|
246
|
+
> **可重现的 CI 安装策略**:`NodeToolchain.install()` 默认遵循 CI/CD 环境的最佳实践——当工程目录下存在 lockfile 时自动启用严格锁定模式(`npm ci`、`pnpm install --frozen-lockfile`、`yarn install --frozen-lockfile` / `--immutable`、`bun install --frozen-lockfile`),防止流水线静默篡改 lockfile 或引入未经锁定的依赖版本;新仓库无 lockfile 时则安全降级为普通安装,不会直接报错。
|
|
247
|
+
|
|
224
248
|
### 注册自定义工具链或覆盖默认行为:
|
|
225
249
|
```typescript
|
|
226
|
-
import {
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
250
|
+
import {
|
|
251
|
+
registerToolchain,
|
|
252
|
+
shell,
|
|
253
|
+
type ToolchainProvider,
|
|
254
|
+
type DetectionResult,
|
|
255
|
+
type ProjectInfo
|
|
256
|
+
} from '@ticatec/omniflow-core'
|
|
257
|
+
import fs from 'node:fs/promises'
|
|
258
|
+
import path from 'node:path'
|
|
259
|
+
|
|
260
|
+
export class GoToolchain implements ToolchainProvider {
|
|
261
|
+
readonly name = 'go'
|
|
262
|
+
readonly priority: number = 50 // 优先级高于内置默认 Provider,在自动探测链中优先执行
|
|
263
|
+
|
|
264
|
+
async detect(projectDir: string): Promise<DetectionResult | null> {
|
|
265
|
+
try {
|
|
266
|
+
await fs.access(path.join(projectDir, 'go.mod'))
|
|
267
|
+
return { name: this.name, reason: 'found go.mod' }
|
|
268
|
+
} catch {
|
|
269
|
+
return null
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
async projectInfo(projectDir: string): Promise<ProjectInfo> {
|
|
274
|
+
return { name: 'user-service', version: '1.0.0', fullName: 'user-service' }
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
async install(projectDir: string, flags: string[] = []): Promise<void> {
|
|
278
|
+
await shell.run({ cwd: projectDir })`go mod download ${flags}`
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
async build(projectDir: string, flags: string[] = []): Promise<void> {
|
|
282
|
+
await shell.run({ cwd: projectDir })`go build -v -o dist/app ${flags}`
|
|
283
|
+
}
|
|
240
284
|
}
|
|
241
285
|
|
|
242
286
|
// 注册至全局注册表
|
|
243
|
-
registerToolchain(
|
|
287
|
+
registerToolchain(new GoToolchain())
|
|
244
288
|
```
|
|
245
289
|
|
|
246
290
|
> 关于创建自定义工具链(如 Go、Rust)及企业定制覆写的完整教程,详见 [Toolchain SPI 扩展指南](./docs/toolchain-extension_CN.md)。
|
|
247
291
|
|
|
248
292
|
---
|
|
249
293
|
|
|
294
|
+
## v0.2.0 不兼容变更 (Breaking Changes)
|
|
295
|
+
|
|
296
|
+
从早期实验性版本升级至 `0.2.0` 时,请注意以下行为与 API 变更:
|
|
297
|
+
|
|
298
|
+
1. **`--dry-run` 模拟输出**:在空跑模式下,命令原语返回 `{ stdout: '', stderr: '', exitCode: 0, failed: false }`,不再向 stdout 注入 `"[DRY-RUN] ..."` 等伪造输出字符串。模拟执行的命令行直接通过 `ctx.logger.info` 记录。
|
|
299
|
+
2. **`docker.build` 必填 image 参数**:`DockerBuildOptions` 中的 `image` 参数现为必填项。移除了隐式兜底至 `'latest'` 的行为及未文档化的别名参数(`tag`、`tags`),防止生产环境产生静默镜像错标。
|
|
300
|
+
3. **`docker.composeUp` 参数命名规范化**:参数属性统一为 `files?: string | string[]` 与 `detach?: boolean`。移除了未经规范的遗留别名(`file`、`detached`)。
|
|
301
|
+
4. **`ssh` 配置属性驼峰化**:SSH 目标配置严格遵循 camelCase 命名:`privateKey`、`privateKeyFile`。废弃并移除了 snake_case 别名(`private_key`、`private_key_file`)。
|
|
302
|
+
5. **`resolveToolchain` 选项规范化**:选项类型统一为 `{ preferred?: string; fallback?: string }`。移除了历史遗留别名 `configured`。
|
|
303
|
+
6. **命令启动失败退出码**:当子进程无法启动时(如可执行文件不存在导致 `exitCode` 为 `undefined`),统一返回 `exitCode: -1` 且 `failed: true`,修复了先前被误判为退出码 `0` 的缺陷。
|
|
304
|
+
7. **`git.resetHard` 与 `git.checkout` 字符串参数语义**:在 v0.1.x 中,传入的单个字符串参数被视作工作目录路径 `dir`(如 `resetHard(dir?, target?)`、`checkout(dir?, ref?)`)。在 v0.2.0 中,裸字符串统一代表目标 commit 或 ref(如 `resetHard(target?: string | GitResetOptions)`、`checkout(ref?: string | GitCheckoutOptions)`)。若需显式指定工作目录,需使用 options 对象传入:`{ dir: '/path/to/repo' }`。
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
|
|
250
308
|
## 模块子路径导出
|
|
251
309
|
|
|
252
|
-
`@omniflow
|
|
310
|
+
`@ticatec/omniflow-core` 遵循现代 ESM 规范,支持精细化的子路径导入:
|
|
253
311
|
|
|
254
312
|
| 导出子路径 | 包含功能说明 |
|
|
255
313
|
|:---|:---|
|
|
256
|
-
| `@omniflow
|
|
257
|
-
| `@omniflow
|
|
258
|
-
| `@omniflow
|
|
259
|
-
| `@omniflow
|
|
314
|
+
| `@ticatec/omniflow-core` | 全量包入口:包含上下文、原语、工具链 SPI 以及工具函数 |
|
|
315
|
+
| `@ticatec/omniflow-core/context` | 执行上下文、`AsyncLocalStorage` 封装、Mock 测试辅助工具 |
|
|
316
|
+
| `@ticatec/omniflow-core/primitives` | 命令原语(`shell`、`ssh`、`git`、`docker`) |
|
|
317
|
+
| `@ticatec/omniflow-core/toolchain` | Toolchain SPI 接口、注册表与内置 Provider |
|
|
260
318
|
|
|
261
319
|
---
|
|
262
320
|
|