mepcli 1.3.1 → 1.4.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 +1 -1
- package/dist/pipeline.d.ts +65 -14
- package/dist/pipeline.js +216 -24
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -226,7 +226,7 @@ const result = await MepCLI.pipeline()
|
|
|
226
226
|
|
|
227
227
|
## Keyboard & Mouse Support
|
|
228
228
|
|
|
229
|
-
For a detailed list of shortcuts and mouse interactions, please refer to the **[Shortcuts Documentation](./docs/shortcuts.md)**.
|
|
229
|
+
For a detailed list of shortcuts and mouse interactions, please refer to the **[Shortcuts Documentation](./docs/features/shortcuts.md)**.
|
|
230
230
|
|
|
231
231
|
## License
|
|
232
232
|
|
package/dist/pipeline.d.ts
CHANGED
|
@@ -1,11 +1,58 @@
|
|
|
1
1
|
import { TaskRunner } from './tasks';
|
|
2
|
-
export
|
|
3
|
-
export type
|
|
4
|
-
export
|
|
5
|
-
|
|
2
|
+
export declare const PipelineExit: unique symbol;
|
|
3
|
+
export type PipelineAction<Ctx, T> = (context: Ctx, tasks?: TaskRunner) => Promise<T | typeof PipelineExit> | T | typeof PipelineExit;
|
|
4
|
+
export type PipelineCondition<Ctx> = (context: Readonly<Ctx>) => boolean;
|
|
5
|
+
export interface ValidationSchema {
|
|
6
|
+
parse?: (data: any) => any;
|
|
7
|
+
safeParse?: (data: any) => {
|
|
8
|
+
success: boolean;
|
|
9
|
+
error?: any;
|
|
10
|
+
issues?: any;
|
|
11
|
+
data?: any;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
export type Validator = ((value: any) => boolean | string | Promise<boolean | string>) | ValidationSchema;
|
|
15
|
+
export type Transformer<Ctx> = (value: any, context: Readonly<Ctx>) => any | Promise<any>;
|
|
16
|
+
export interface PipelineStep<Ctx> extends StepConfig<Ctx> {
|
|
17
|
+
name?: keyof Ctx;
|
|
6
18
|
action: PipelineAction<Ctx, any>;
|
|
7
19
|
condition?: PipelineCondition<Ctx>;
|
|
8
20
|
}
|
|
21
|
+
export interface StepConfig<Ctx> {
|
|
22
|
+
validate?: Validator;
|
|
23
|
+
transform?: Transformer<Ctx>;
|
|
24
|
+
onError?: (error: unknown, context: Readonly<Ctx>) => void | Promise<void>;
|
|
25
|
+
fallback?: any | ((error: unknown, context: Readonly<Ctx>) => any | Promise<any>);
|
|
26
|
+
optional?: boolean;
|
|
27
|
+
timeout?: number;
|
|
28
|
+
}
|
|
29
|
+
export interface StepMetadata {
|
|
30
|
+
index: number;
|
|
31
|
+
name?: string;
|
|
32
|
+
type: 'named' | 'anonymous';
|
|
33
|
+
}
|
|
34
|
+
export interface PipelineOptions<Ctx> {
|
|
35
|
+
onStepStart?: (meta: StepMetadata, context: Readonly<Ctx>) => void | Promise<void>;
|
|
36
|
+
onStepComplete?: (meta: StepMetadata, context: Readonly<Ctx>) => void | Promise<void>;
|
|
37
|
+
onError?: (error: unknown, meta: StepMetadata, context: Readonly<Ctx>) => void | Promise<void>;
|
|
38
|
+
validate?: Validator;
|
|
39
|
+
onPipelineStart?: (context: Readonly<Ctx>) => void | Promise<void>;
|
|
40
|
+
onPipelineComplete?: (context: Readonly<Ctx>) => void | Promise<void>;
|
|
41
|
+
signal?: AbortSignal;
|
|
42
|
+
}
|
|
43
|
+
export declare class PipelineValidationError<Ctx = any> extends Error {
|
|
44
|
+
step?: StepMetadata;
|
|
45
|
+
context?: Ctx;
|
|
46
|
+
constructor(message: string, step?: StepMetadata, context?: Ctx);
|
|
47
|
+
}
|
|
48
|
+
export declare class PipelineTimeoutError<Ctx = any> extends Error {
|
|
49
|
+
step?: StepMetadata;
|
|
50
|
+
context?: Ctx;
|
|
51
|
+
constructor(message: string, step?: StepMetadata, context?: Ctx);
|
|
52
|
+
}
|
|
53
|
+
export declare class PipelineAbortError extends Error {
|
|
54
|
+
constructor(message?: string);
|
|
55
|
+
}
|
|
9
56
|
/**
|
|
10
57
|
* Pipeline (Workflow Engine)
|
|
11
58
|
*
|
|
@@ -16,20 +63,24 @@ export interface PipelineStep<Ctx> {
|
|
|
16
63
|
*/
|
|
17
64
|
export declare class Pipeline<Ctx extends Record<string, any> = Record<string, any>> {
|
|
18
65
|
private steps;
|
|
19
|
-
|
|
66
|
+
private options;
|
|
67
|
+
constructor(options?: PipelineOptions<Ctx>);
|
|
68
|
+
/**
|
|
69
|
+
* Adds a named step to the pipeline. The result of the action is stored in the context under the given name.
|
|
70
|
+
*/
|
|
71
|
+
step<K extends keyof Ctx>(name: K, action: PipelineAction<Ctx, Ctx[K]> | Pipeline<any>, config?: StepConfig<Ctx>): this;
|
|
72
|
+
/**
|
|
73
|
+
* Adds an anonymous step to the pipeline. The action can mutate the context directly or return a partial context to merge in.
|
|
74
|
+
*/
|
|
75
|
+
step(action: PipelineAction<Ctx, void | Partial<Ctx>> | Pipeline<any>, config?: StepConfig<Ctx>): this;
|
|
20
76
|
/**
|
|
21
|
-
* Adds a step to the pipeline.
|
|
22
|
-
* @param name The key in the context to store the result.
|
|
23
|
-
* @param action The function to execute, returning a Promise (usually a Prompt).
|
|
77
|
+
* Adds a conditional named step to the pipeline.
|
|
24
78
|
*/
|
|
25
|
-
|
|
79
|
+
stepIf<K extends keyof Ctx>(condition: PipelineCondition<Ctx>, name: K, action: PipelineAction<Ctx, Ctx[K]> | Pipeline<any>, config?: StepConfig<Ctx>): this;
|
|
26
80
|
/**
|
|
27
|
-
* Adds a conditional step to the pipeline.
|
|
28
|
-
* @param condition A function that returns true if the step should run.
|
|
29
|
-
* @param name The key in the context to store the result.
|
|
30
|
-
* @param action The function to execute.
|
|
81
|
+
* Adds a conditional anonymous step to the pipeline.
|
|
31
82
|
*/
|
|
32
|
-
stepIf
|
|
83
|
+
stepIf(condition: PipelineCondition<Ctx>, action: PipelineAction<Ctx, void | Partial<Ctx>> | Pipeline<any>, config?: StepConfig<Ctx>): this;
|
|
33
84
|
/**
|
|
34
85
|
* Executes the pipeline steps sequentially.
|
|
35
86
|
* @param initialContext Optional initial context.
|
package/dist/pipeline.js
CHANGED
|
@@ -1,6 +1,32 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.Pipeline = void 0;
|
|
3
|
+
exports.Pipeline = exports.PipelineAbortError = exports.PipelineTimeoutError = exports.PipelineValidationError = exports.PipelineExit = void 0;
|
|
4
|
+
exports.PipelineExit = Symbol.for('PipelineExit');
|
|
5
|
+
class PipelineValidationError extends Error {
|
|
6
|
+
constructor(message, step, context) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = 'PipelineValidationError';
|
|
9
|
+
this.step = step;
|
|
10
|
+
this.context = context;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
exports.PipelineValidationError = PipelineValidationError;
|
|
14
|
+
class PipelineTimeoutError extends Error {
|
|
15
|
+
constructor(message, step, context) {
|
|
16
|
+
super(message);
|
|
17
|
+
this.name = 'PipelineTimeoutError';
|
|
18
|
+
this.step = step;
|
|
19
|
+
this.context = context;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
exports.PipelineTimeoutError = PipelineTimeoutError;
|
|
23
|
+
class PipelineAbortError extends Error {
|
|
24
|
+
constructor(message = 'Pipeline aborted') {
|
|
25
|
+
super(message);
|
|
26
|
+
this.name = 'PipelineAbortError';
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
exports.PipelineAbortError = PipelineAbortError;
|
|
4
30
|
/**
|
|
5
31
|
* Pipeline (Workflow Engine)
|
|
6
32
|
*
|
|
@@ -10,26 +36,54 @@ exports.Pipeline = void 0;
|
|
|
10
36
|
* Philosophy: Enter-and-Forget (EAF), Zero-Dependency, Method Chaining.
|
|
11
37
|
*/
|
|
12
38
|
class Pipeline {
|
|
13
|
-
constructor() {
|
|
39
|
+
constructor(options) {
|
|
14
40
|
this.steps = [];
|
|
41
|
+
this.options = options || {};
|
|
15
42
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
43
|
+
step(nameOrAction, actionOrConfig, config) {
|
|
44
|
+
if (typeof nameOrAction === 'function' || nameOrAction instanceof Pipeline) {
|
|
45
|
+
const action = nameOrAction instanceof Pipeline
|
|
46
|
+
? ((ctx, tasks) => nameOrAction.run(ctx, tasks))
|
|
47
|
+
: nameOrAction;
|
|
48
|
+
this.steps.push({
|
|
49
|
+
action: action,
|
|
50
|
+
...(actionOrConfig || {})
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
const action = actionOrConfig instanceof Pipeline
|
|
55
|
+
? ((ctx, tasks) => actionOrConfig.run(ctx, tasks))
|
|
56
|
+
: actionOrConfig;
|
|
57
|
+
this.steps.push({
|
|
58
|
+
name: nameOrAction,
|
|
59
|
+
action: action,
|
|
60
|
+
...(config || {})
|
|
61
|
+
});
|
|
62
|
+
}
|
|
23
63
|
return this;
|
|
24
64
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
65
|
+
stepIf(condition, nameOrAction, actionOrConfig, config) {
|
|
66
|
+
if (typeof nameOrAction === 'function' || nameOrAction instanceof Pipeline) {
|
|
67
|
+
const action = nameOrAction instanceof Pipeline
|
|
68
|
+
? ((ctx, tasks) => nameOrAction.run(ctx, tasks))
|
|
69
|
+
: nameOrAction;
|
|
70
|
+
this.steps.push({
|
|
71
|
+
condition,
|
|
72
|
+
action: action,
|
|
73
|
+
...(actionOrConfig || {})
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
const action = actionOrConfig instanceof Pipeline
|
|
78
|
+
? ((ctx, tasks) => actionOrConfig.run(ctx, tasks))
|
|
79
|
+
: actionOrConfig;
|
|
80
|
+
this.steps.push({
|
|
81
|
+
condition,
|
|
82
|
+
name: nameOrAction,
|
|
83
|
+
action: action,
|
|
84
|
+
...(config || {})
|
|
85
|
+
});
|
|
86
|
+
}
|
|
33
87
|
return this;
|
|
34
88
|
}
|
|
35
89
|
/**
|
|
@@ -39,14 +93,152 @@ class Pipeline {
|
|
|
39
93
|
*/
|
|
40
94
|
async run(initialContext = {}, tasks) {
|
|
41
95
|
const context = { ...initialContext };
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
96
|
+
if (this.options.onPipelineStart) {
|
|
97
|
+
await this.options.onPipelineStart({ ...context });
|
|
98
|
+
}
|
|
99
|
+
try {
|
|
100
|
+
for (let i = 0; i < this.steps.length; i++) {
|
|
101
|
+
if (this.options.signal?.aborted) {
|
|
102
|
+
throw new PipelineAbortError();
|
|
103
|
+
}
|
|
104
|
+
const step = this.steps[i];
|
|
105
|
+
const meta = {
|
|
106
|
+
index: i,
|
|
107
|
+
name: step.name,
|
|
108
|
+
type: step.name ? 'named' : 'anonymous'
|
|
109
|
+
};
|
|
110
|
+
if (step.condition && !step.condition(context)) {
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (this.options.onStepStart) {
|
|
114
|
+
await this.options.onStepStart(meta, { ...context });
|
|
115
|
+
}
|
|
116
|
+
try {
|
|
117
|
+
let result;
|
|
118
|
+
if (step.timeout && step.timeout > 0) {
|
|
119
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
120
|
+
setTimeout(() => {
|
|
121
|
+
reject(new PipelineTimeoutError(`Step timed out after ${step.timeout}ms`, meta, context));
|
|
122
|
+
}, step.timeout);
|
|
123
|
+
});
|
|
124
|
+
result = await Promise.race([step.action(context, tasks), timeoutPromise]);
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
result = await step.action(context, tasks);
|
|
128
|
+
}
|
|
129
|
+
if (result === exports.PipelineExit) {
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
if (!step.name) {
|
|
133
|
+
// Anonymous action
|
|
134
|
+
if (result && typeof result === 'object' && !Array.isArray(result)) {
|
|
135
|
+
Object.assign(context, result);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
// Named action
|
|
140
|
+
if (step.transform) {
|
|
141
|
+
result = await step.transform(result, { ...context });
|
|
142
|
+
}
|
|
143
|
+
if (step.validate) {
|
|
144
|
+
const validator = step.validate;
|
|
145
|
+
if (typeof validator === 'function') {
|
|
146
|
+
const valid = await validator(result);
|
|
147
|
+
if (valid === false) {
|
|
148
|
+
throw new PipelineValidationError(`Validation failed for step at index ${i} (${String(step.name)})`, meta, context);
|
|
149
|
+
}
|
|
150
|
+
if (typeof valid === 'string') {
|
|
151
|
+
throw new PipelineValidationError(valid, meta, context);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
else if (typeof validator === 'object') {
|
|
155
|
+
if (typeof validator.safeParse === 'function') {
|
|
156
|
+
const res = validator.safeParse(result);
|
|
157
|
+
if (!res.success) {
|
|
158
|
+
const errorMsg = res.error?.message || res.issues?.[0]?.message || 'Schema validation failed';
|
|
159
|
+
throw new PipelineValidationError(`Validation failed: ${errorMsg}`, meta, context);
|
|
160
|
+
}
|
|
161
|
+
result = res.data !== undefined ? res.data : result;
|
|
162
|
+
}
|
|
163
|
+
else if (typeof validator.parse === 'function') {
|
|
164
|
+
try {
|
|
165
|
+
const parsed = validator.parse(result);
|
|
166
|
+
result = parsed !== undefined ? parsed : result;
|
|
167
|
+
}
|
|
168
|
+
catch (err) {
|
|
169
|
+
throw new PipelineValidationError(`Validation failed: ${err.message || 'Schema parsing error'}`, meta, context);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
context[step.name] = result;
|
|
175
|
+
}
|
|
176
|
+
if (this.options.onStepComplete) {
|
|
177
|
+
await this.options.onStepComplete(meta, { ...context });
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
catch (err) {
|
|
181
|
+
let handled = false;
|
|
182
|
+
if (step.onError) {
|
|
183
|
+
await step.onError(err, { ...context });
|
|
184
|
+
}
|
|
185
|
+
if ('fallback' in step) {
|
|
186
|
+
const fallbackValue = typeof step.fallback === 'function' ? await step.fallback(err, { ...context }) : step.fallback;
|
|
187
|
+
if (step.name) {
|
|
188
|
+
context[step.name] = fallbackValue;
|
|
189
|
+
}
|
|
190
|
+
handled = true;
|
|
191
|
+
}
|
|
192
|
+
else if (step.optional) {
|
|
193
|
+
if (step.name) {
|
|
194
|
+
context[step.name] = undefined;
|
|
195
|
+
}
|
|
196
|
+
handled = true;
|
|
197
|
+
}
|
|
198
|
+
if (!handled) {
|
|
199
|
+
if (this.options.onError) {
|
|
200
|
+
await this.options.onError(err, meta, { ...context });
|
|
201
|
+
}
|
|
202
|
+
throw err;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
if (this.options.validate) {
|
|
207
|
+
const validator = this.options.validate;
|
|
208
|
+
if (typeof validator === 'function') {
|
|
209
|
+
const valid = await validator(context);
|
|
210
|
+
if (valid === false) {
|
|
211
|
+
throw new PipelineValidationError(`Final validation failed`);
|
|
212
|
+
}
|
|
213
|
+
if (typeof valid === 'string') {
|
|
214
|
+
throw new PipelineValidationError(valid);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
else if (typeof validator === 'object') {
|
|
218
|
+
if (typeof validator.safeParse === 'function') {
|
|
219
|
+
const res = validator.safeParse(context);
|
|
220
|
+
if (!res.success) {
|
|
221
|
+
const errorMsg = res.error?.message || res.issues?.[0]?.message || 'Schema validation failed';
|
|
222
|
+
throw new PipelineValidationError(`Final validation failed: ${errorMsg}`);
|
|
223
|
+
}
|
|
224
|
+
Object.assign(context, res.data !== undefined ? res.data : context);
|
|
225
|
+
}
|
|
226
|
+
else if (typeof validator.parse === 'function') {
|
|
227
|
+
try {
|
|
228
|
+
const parsed = validator.parse(context);
|
|
229
|
+
Object.assign(context, parsed !== undefined ? parsed : context);
|
|
230
|
+
}
|
|
231
|
+
catch (err) {
|
|
232
|
+
throw new PipelineValidationError(`Final validation failed: ${err.message || 'Schema parsing error'}`);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
finally {
|
|
239
|
+
if (this.options.onPipelineComplete) {
|
|
240
|
+
await this.options.onPipelineComplete({ ...context });
|
|
45
241
|
}
|
|
46
|
-
// In EAF (Enter-And-Forget) philosophy, if a prompt fails or is cancelled,
|
|
47
|
-
// the exception will propagate up and stop the pipeline.
|
|
48
|
-
const result = await step.action(context, tasks);
|
|
49
|
-
context[step.name] = result;
|
|
50
242
|
}
|
|
51
243
|
return context;
|
|
52
244
|
}
|
package/package.json
CHANGED