@serverless-devs/engine 0.0.1-beta.3 → 0.0.1-beta.31
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/lib/actions/index.d.ts +80 -1
- package/lib/actions/index.js +223 -28
- package/lib/actions/index.js.map +1 -1
- package/lib/constants/index.d.ts +6 -0
- package/lib/constants/index.js +12 -0
- package/lib/constants/index.js.map +1 -1
- package/lib/index.d.ts +76 -2
- package/lib/index.js +338 -136
- package/lib/index.js.map +1 -1
- package/lib/types.d.ts +13 -7
- package/lib/types.js +1 -1
- package/lib/types.js.map +1 -1
- package/lib/utils/index.d.ts +5 -3
- package/lib/utils/index.js +19 -31
- package/lib/utils/index.js.map +1 -1
- package/package.json +13 -9
package/lib/index.js
CHANGED
|
@@ -22,6 +22,9 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
22
22
|
__setModuleDefault(result, mod);
|
|
23
23
|
return result;
|
|
24
24
|
};
|
|
25
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
26
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
27
|
+
};
|
|
25
28
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
26
29
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
27
30
|
return new (P || (P = Promise))(function (resolve, reject) {
|
|
@@ -41,63 +44,119 @@ const types_1 = require("./types");
|
|
|
41
44
|
const utils_1 = require("./utils");
|
|
42
45
|
const parse_spec_1 = __importStar(require("@serverless-devs/parse-spec"));
|
|
43
46
|
const path_1 = __importDefault(require("path"));
|
|
44
|
-
const js_yaml_1 = __importDefault(require("js-yaml"));
|
|
45
47
|
const chalk_1 = __importDefault(require("chalk"));
|
|
46
48
|
const actions_1 = __importDefault(require("./actions"));
|
|
47
49
|
const credential_1 = __importDefault(require("@serverless-devs/credential"));
|
|
48
50
|
const load_component_1 = __importDefault(require("@serverless-devs/load-component"));
|
|
49
51
|
const logger_1 = __importDefault(require("@serverless-devs/logger"));
|
|
50
|
-
const
|
|
52
|
+
const utils_2 = require("@serverless-devs/utils");
|
|
53
|
+
const constants_1 = require("./constants");
|
|
54
|
+
const assert_1 = __importDefault(require("assert"));
|
|
55
|
+
__exportStar(require("./types"), exports);
|
|
51
56
|
const debug = require('@serverless-cd/debug')('serverless-devs:engine');
|
|
57
|
+
/**
|
|
58
|
+
* Engine Class
|
|
59
|
+
*
|
|
60
|
+
* This class provides an engine to handle Serverless Devs operations and steps.
|
|
61
|
+
* It operates based on the xstate state machine library, ensuring that execution follows
|
|
62
|
+
* the predefined flow and states.
|
|
63
|
+
*/
|
|
52
64
|
class Engine {
|
|
53
65
|
constructor(options) {
|
|
54
66
|
this.options = options;
|
|
55
|
-
this.context = {
|
|
56
|
-
|
|
67
|
+
this.context = {
|
|
68
|
+
status: types_1.STEP_STATUS.PENDING,
|
|
69
|
+
completed: false,
|
|
70
|
+
error: [],
|
|
71
|
+
};
|
|
72
|
+
this.record = { status: types_1.STEP_STATUS.PENDING, editStatusAble: true };
|
|
57
73
|
this.spec = {};
|
|
58
74
|
debug('engine start');
|
|
59
75
|
// 初始化参数
|
|
60
76
|
this.options.args = (0, lodash_1.get)(this.options, 'args', process.argv.slice(2));
|
|
61
77
|
this.options.cwd = (0, lodash_1.get)(this.options, 'cwd', process.cwd());
|
|
62
|
-
this.options.template =
|
|
78
|
+
this.options.template = (0, utils_2.getAbsolutePath)((0, lodash_1.get)(this.options, 'template'), this.options.cwd);
|
|
63
79
|
debug(`engine options: ${(0, utils_1.stringify)(options)}`);
|
|
64
80
|
}
|
|
81
|
+
/**
|
|
82
|
+
* Initialization steps before starting the engine.
|
|
83
|
+
*/
|
|
84
|
+
beforeStart() {
|
|
85
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
86
|
+
// 初始化 logger
|
|
87
|
+
this.glog = this.getLogger();
|
|
88
|
+
this.logger = this.glog.__generate('engine');
|
|
89
|
+
// 初始化 spec
|
|
90
|
+
this.parseSpecInstance = new parse_spec_1.default((0, lodash_1.get)(this.options, 'template'), {
|
|
91
|
+
argv: this.options.args,
|
|
92
|
+
logger: this.logger,
|
|
93
|
+
});
|
|
94
|
+
this.spec = yield this.parseSpecInstance.start();
|
|
95
|
+
// 初始化行参环境变量 > .env (parse-spec require .env)
|
|
96
|
+
(0, lodash_1.each)(this.options.env, (value, key) => {
|
|
97
|
+
process.env[key] = value;
|
|
98
|
+
});
|
|
99
|
+
const { steps: _steps } = this.spec;
|
|
100
|
+
// 参数校验
|
|
101
|
+
this.validate();
|
|
102
|
+
this.context.steps = yield this.download(_steps);
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Start the engine.
|
|
107
|
+
*
|
|
108
|
+
* This is the primary execution function for the engine. It is responsible for starting
|
|
109
|
+
* the entire engine and handling each step.
|
|
110
|
+
*
|
|
111
|
+
* @note engine应收敛所有的异常,不应该抛出异常
|
|
112
|
+
*/
|
|
65
113
|
start() {
|
|
66
114
|
return __awaiter(this, void 0, void 0, function* () {
|
|
67
115
|
this.context.status = types_1.STEP_STATUS.RUNNING;
|
|
68
|
-
//
|
|
116
|
+
// Haoran: should set this.record.status to RUNNING?
|
|
117
|
+
this.record.status = types_1.STEP_STATUS.RUNNING;
|
|
69
118
|
try {
|
|
70
|
-
this.
|
|
71
|
-
this.spec = this.parseSpecInstance.start();
|
|
119
|
+
yield this.beforeStart();
|
|
72
120
|
}
|
|
73
121
|
catch (error) {
|
|
74
|
-
this.context.
|
|
122
|
+
this.context.status = types_1.STEP_STATUS.FAILURE;
|
|
123
|
+
this.context.completed = true;
|
|
124
|
+
this.context.error.push(error);
|
|
75
125
|
return this.context;
|
|
76
126
|
}
|
|
77
|
-
|
|
78
|
-
(0, lodash_1.
|
|
79
|
-
|
|
80
|
-
});
|
|
81
|
-
const { steps: _steps, yaml, access = yaml.access } = this.spec;
|
|
82
|
-
this.validate();
|
|
83
|
-
this.glog = this.getLogger();
|
|
84
|
-
this.logger = this.glog.__generate('engine');
|
|
85
|
-
const steps = yield this.download(_steps);
|
|
127
|
+
const { steps: _steps, yaml, command, access = yaml.access } = this.spec;
|
|
128
|
+
this.logger.write(`${(0, utils_2.emoji)('⌛')} Steps for [${command}] of [${(0, lodash_1.get)(this.spec, 'yaml.appName')}]\n${chalk_1.default.gray('====================')}`);
|
|
129
|
+
// 初始化全局的 action
|
|
86
130
|
this.globalActionInstance = new actions_1.default(yaml.actions, {
|
|
87
131
|
hookLevel: parse_spec_1.IActionLevel.GLOBAL,
|
|
88
132
|
logger: this.logger,
|
|
89
133
|
skipActions: this.spec.skipActions,
|
|
90
134
|
});
|
|
91
135
|
const credential = yield (0, utils_1.getCredential)(access, this.logger);
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
136
|
+
this.context.credential = credential;
|
|
137
|
+
// 处理 global-pre
|
|
138
|
+
try {
|
|
139
|
+
this.globalActionInstance.setValue('magic', this.getFilterContext());
|
|
140
|
+
this.globalActionInstance.setValue('command', command);
|
|
141
|
+
yield this.globalActionInstance.start(parse_spec_1.IHookType.PRE, { access, credential });
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
this.context.error.push(error);
|
|
145
|
+
this.context.status = types_1.STEP_STATUS.FAILURE;
|
|
146
|
+
yield this.doCompleted();
|
|
147
|
+
return this.context;
|
|
148
|
+
}
|
|
149
|
+
// Assign the id, pending status and etc for all steps.
|
|
150
|
+
this.context.steps = (0, lodash_1.map)(this.context.steps, item => {
|
|
151
|
+
return Object.assign(Object.assign({}, item), { stepCount: (0, lodash_1.uniqueId)(), status: types_1.STEP_STATUS.PENDING, done: false });
|
|
95
152
|
});
|
|
96
153
|
const res = yield new Promise((resolve) => __awaiter(this, void 0, void 0, function* () {
|
|
154
|
+
// Every states object has two fixed states, "init" and "final".
|
|
97
155
|
const states = {
|
|
98
156
|
init: {
|
|
99
157
|
on: {
|
|
100
|
-
|
|
158
|
+
// Haoran: May this.context.steps be empty?
|
|
159
|
+
INIT: this.context.steps.length === 0 ? 'final' : (0, lodash_1.get)(this.context.steps, '[0].stepCount'),
|
|
101
160
|
},
|
|
102
161
|
},
|
|
103
162
|
final: {
|
|
@@ -105,12 +164,11 @@ class Engine {
|
|
|
105
164
|
invoke: {
|
|
106
165
|
src: () => __awaiter(this, void 0, void 0, function* () {
|
|
107
166
|
// 执行终态是 error-with-continue 的时候,改为 success
|
|
108
|
-
const status = this.record.status === types_1.STEP_STATUS.ERROR_WITH_CONTINUE
|
|
109
|
-
? types_1.STEP_STATUS.SUCCESS
|
|
110
|
-
: this.record.status;
|
|
167
|
+
const status = this.record.status === types_1.STEP_STATUS.ERROR_WITH_CONTINUE ? types_1.STEP_STATUS.SUCCESS : this.record.status;
|
|
111
168
|
this.context.status = status;
|
|
112
169
|
yield this.doCompleted();
|
|
113
|
-
this.context.steps = (0, lodash_1.map)(this.context.steps,
|
|
170
|
+
this.context.steps = (0, lodash_1.map)(this.context.steps, item => (0, lodash_1.omit)(item, ['instance']));
|
|
171
|
+
this.context.output = this.getOutput();
|
|
114
172
|
debug(`context: ${(0, utils_1.stringify)(this.context)}`);
|
|
115
173
|
debug('engine end');
|
|
116
174
|
resolve(this.context);
|
|
@@ -118,40 +176,25 @@ class Engine {
|
|
|
118
176
|
},
|
|
119
177
|
},
|
|
120
178
|
};
|
|
179
|
+
// Every states object has dynamic state, that based on the step.StepCount.
|
|
121
180
|
(0, lodash_1.each)(this.context.steps, (item, index) => {
|
|
122
|
-
const target = this.context.steps[index + 1]
|
|
123
|
-
|
|
124
|
-
: 'final';
|
|
125
|
-
const flowProject = yaml.useFlow
|
|
126
|
-
? (0, lodash_1.filter)(this.context.steps, (o) => o.flowId === item.flowId)
|
|
127
|
-
: [item];
|
|
181
|
+
const target = this.context.steps[index + 1] ? (0, lodash_1.get)(this.context.steps, `[${index + 1}].stepCount`) : 'final';
|
|
182
|
+
const flowProject = yaml.useFlow ? (0, lodash_1.filter)(this.context.steps, o => o.flowId === item.flowId) : [item];
|
|
128
183
|
states[item.stepCount] = {
|
|
129
184
|
invoke: {
|
|
130
185
|
id: item.stepCount,
|
|
131
186
|
src: () => __awaiter(this, void 0, void 0, function* () {
|
|
132
|
-
//
|
|
187
|
+
// 并行时如果已经执行,则跳过。
|
|
133
188
|
if (item.done)
|
|
134
189
|
return;
|
|
135
190
|
this.record.startTime = Date.now();
|
|
136
191
|
// 记录 context
|
|
137
192
|
this.recordContext(item, { status: types_1.STEP_STATUS.RUNNING });
|
|
138
|
-
// 先判断if条件,成功则执行该步骤。
|
|
139
|
-
if (item.if) {
|
|
140
|
-
// 替换 failure()
|
|
141
|
-
item.if = (0, lodash_1.replace)(item.if, types_1.STEP_IF.FAILURE, this.record.status === types_1.STEP_STATUS.FAILURE ? 'true' : 'false');
|
|
142
|
-
// 替换 success()
|
|
143
|
-
item.if = (0, lodash_1.replace)(item.if, types_1.STEP_IF.SUCCESS, this.record.status !== types_1.STEP_STATUS.FAILURE ? 'true' : 'false');
|
|
144
|
-
// 替换 always()
|
|
145
|
-
item.if = (0, lodash_1.replace)(item.if, types_1.STEP_IF.ALWAYS, 'true');
|
|
146
|
-
return item.if === 'true'
|
|
147
|
-
? yield Promise.all((0, lodash_1.map)(flowProject, (o) => this.handleSrc(o)))
|
|
148
|
-
: yield Promise.all((0, lodash_1.map)(flowProject, (o) => this.doSkip(o)));
|
|
149
|
-
}
|
|
150
193
|
// 检查全局的执行状态,如果是failure,则不执行该步骤, 并记录状态为 skipped
|
|
151
194
|
if (this.record.status === types_1.STEP_STATUS.FAILURE) {
|
|
152
|
-
return yield Promise.all((0, lodash_1.map)(flowProject,
|
|
195
|
+
return yield Promise.all((0, lodash_1.map)(flowProject, o => this.doSkip(o)));
|
|
153
196
|
}
|
|
154
|
-
return yield Promise.all((0, lodash_1.map)(flowProject,
|
|
197
|
+
return yield Promise.all((0, lodash_1.map)(flowProject, o => this.handleSrc(o)));
|
|
155
198
|
}),
|
|
156
199
|
onDone: {
|
|
157
200
|
target,
|
|
@@ -167,44 +210,41 @@ class Engine {
|
|
|
167
210
|
states,
|
|
168
211
|
});
|
|
169
212
|
const stepService = (0, xstate_1.interpret)(fetchMachine)
|
|
170
|
-
.onTransition(
|
|
213
|
+
.onTransition(state => state.value)
|
|
171
214
|
.start();
|
|
172
215
|
stepService.send('INIT');
|
|
173
216
|
}));
|
|
217
|
+
this.glog.__clear();
|
|
174
218
|
return res;
|
|
175
219
|
});
|
|
176
220
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
const
|
|
183
|
-
(0, lodash_1.each)(this.context.steps,
|
|
184
|
-
if (!(0, lodash_1.
|
|
185
|
-
(0, lodash_1.set)(
|
|
221
|
+
/**
|
|
222
|
+
* Extracts and returns an object containing the output of each step.
|
|
223
|
+
* The object's key is the project name and the value is the output of that project.
|
|
224
|
+
*/
|
|
225
|
+
getOutput() {
|
|
226
|
+
const output = {};
|
|
227
|
+
(0, lodash_1.each)(this.context.steps, item => {
|
|
228
|
+
if (!(0, lodash_1.isNil)(item.output)) {
|
|
229
|
+
(0, lodash_1.set)(output, item.projectName, item.output);
|
|
186
230
|
}
|
|
187
231
|
});
|
|
188
|
-
|
|
189
|
-
return console.log(JSON.stringify(data, null, 2));
|
|
190
|
-
}
|
|
191
|
-
if (output === parse_spec_1.IOutput.RAW) {
|
|
192
|
-
return console.log(JSON.stringify(data));
|
|
193
|
-
}
|
|
194
|
-
if (output === parse_spec_1.IOutput.YAML) {
|
|
195
|
-
return console.log(js_yaml_1.default.dump(data));
|
|
196
|
-
}
|
|
197
|
-
return this.logger.output(data, 0);
|
|
232
|
+
return output;
|
|
198
233
|
}
|
|
234
|
+
/**
|
|
235
|
+
* Validates the 'steps' and 'command' present in 'this.spec'.
|
|
236
|
+
* Throws an error if either 'steps' or 'command' are missing.
|
|
237
|
+
*/
|
|
199
238
|
validate() {
|
|
200
|
-
const { steps,
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
}
|
|
204
|
-
if ((0, lodash_1.isEmpty)(method)) {
|
|
205
|
-
throw new Error('method is empty');
|
|
206
|
-
}
|
|
239
|
+
const { steps, command } = this.spec;
|
|
240
|
+
(0, assert_1.default)(!(0, lodash_1.isEmpty)(steps), 'steps is required');
|
|
241
|
+
(0, assert_1.default)(command, 'command is required');
|
|
207
242
|
}
|
|
243
|
+
/**
|
|
244
|
+
* Asynchronously downloads and initializes the given steps.
|
|
245
|
+
* For each step, it loads the specified component and associates a logger with it.
|
|
246
|
+
* Returns an array containing the initialized steps.
|
|
247
|
+
*/
|
|
208
248
|
download(steps) {
|
|
209
249
|
return __awaiter(this, void 0, void 0, function* () {
|
|
210
250
|
const newSteps = [];
|
|
@@ -223,24 +263,30 @@ class Engine {
|
|
|
223
263
|
const customLogger = (0, lodash_1.get)(this.options, 'logConfig.customLogger');
|
|
224
264
|
if (customLogger) {
|
|
225
265
|
debug('use custom logger');
|
|
226
|
-
if (customLogger
|
|
266
|
+
if ((customLogger === null || customLogger === void 0 ? void 0 : customLogger.CODE) === logger_1.default.CODE) {
|
|
227
267
|
return customLogger;
|
|
228
268
|
}
|
|
229
269
|
throw new Error('customLogger must be instance of Logger');
|
|
230
270
|
}
|
|
231
|
-
return new logger_1.default(Object.assign(Object.assign({ traceId: (0,
|
|
271
|
+
return new logger_1.default(Object.assign(Object.assign({ traceId: (0, utils_2.traceid)(), logDir: path_1.default.join((0, utils_2.getRootHome)(), 'logs') }, this.options.logConfig), { level: (0, lodash_1.get)(this.options, 'logConfig.level', this.spec.debug ? 'DEBUG' : undefined) }));
|
|
232
272
|
}
|
|
273
|
+
/**
|
|
274
|
+
* Updates the context for the given step based on the provided options.
|
|
275
|
+
*
|
|
276
|
+
* @param item - The current step being processed.
|
|
277
|
+
* @param options - An object containing details like status, error, output, etc. to update the step's context.
|
|
278
|
+
*/
|
|
233
279
|
recordContext(item, options = {}) {
|
|
234
280
|
const { status, error, output, process_time, props, done } = options;
|
|
235
281
|
this.context.stepCount = item.stepCount;
|
|
236
|
-
this.context.steps = (0, lodash_1.map)(this.context.steps,
|
|
282
|
+
this.context.steps = (0, lodash_1.map)(this.context.steps, obj => {
|
|
237
283
|
if (obj.stepCount === item.stepCount) {
|
|
238
284
|
if (status) {
|
|
239
285
|
obj.status = status;
|
|
240
286
|
}
|
|
241
287
|
if (error) {
|
|
242
288
|
obj.error = error;
|
|
243
|
-
this.context.error
|
|
289
|
+
this.context.error.push(error);
|
|
244
290
|
}
|
|
245
291
|
if (props) {
|
|
246
292
|
obj.props = props;
|
|
@@ -258,65 +304,150 @@ class Engine {
|
|
|
258
304
|
return obj;
|
|
259
305
|
});
|
|
260
306
|
}
|
|
307
|
+
/**
|
|
308
|
+
* Generates a context data for the given step containing details like cwd, vars, and other steps' outputs and props.
|
|
309
|
+
* @param item - The current step being processed.
|
|
310
|
+
* @returns - The generated context data.
|
|
311
|
+
*/
|
|
261
312
|
getFilterContext(item) {
|
|
262
313
|
const data = {
|
|
263
314
|
cwd: path_1.default.dirname(this.spec.yaml.path),
|
|
264
315
|
vars: this.spec.yaml.vars,
|
|
265
|
-
|
|
316
|
+
resources: {},
|
|
266
317
|
};
|
|
267
318
|
for (const obj of this.context.steps) {
|
|
268
|
-
data[obj.projectName] = { output: obj.output || {}, props: obj.props || {} };
|
|
319
|
+
data.resources[obj.projectName] = { output: obj.output || {}, props: obj.props || {} };
|
|
320
|
+
}
|
|
321
|
+
if (item) {
|
|
322
|
+
data.credential = item.credential;
|
|
323
|
+
data.that = {
|
|
324
|
+
name: item.projectName,
|
|
325
|
+
access: item.access,
|
|
326
|
+
component: item.component,
|
|
327
|
+
props: data.resources[item.projectName].props,
|
|
328
|
+
output: data.resources[item.projectName].output,
|
|
329
|
+
};
|
|
269
330
|
}
|
|
270
|
-
data.that = {
|
|
271
|
-
name: item.projectName,
|
|
272
|
-
access: item.access,
|
|
273
|
-
component: item.component,
|
|
274
|
-
props: data[item.projectName].props,
|
|
275
|
-
output: data[item.projectName].output,
|
|
276
|
-
};
|
|
277
331
|
return data;
|
|
278
332
|
}
|
|
333
|
+
/**
|
|
334
|
+
* Handles the subsequent operations after a step has been completed.
|
|
335
|
+
* 1. Marks the step as completed.
|
|
336
|
+
* 2. If the step status is FAILURE, attempts to trigger the global FAIL hook.
|
|
337
|
+
* 3. If the step status is SUCCESS, attempts to trigger the global SUCCESS hook.
|
|
338
|
+
* 4. Regardless of the step's status, tries to trigger the global COMPLETE hook to denote completion.
|
|
339
|
+
*/
|
|
279
340
|
doCompleted() {
|
|
280
341
|
return __awaiter(this, void 0, void 0, function* () {
|
|
281
342
|
this.context.completed = true;
|
|
343
|
+
if (this.context.status === types_1.STEP_STATUS.FAILURE) {
|
|
344
|
+
try {
|
|
345
|
+
yield this.globalActionInstance.start(parse_spec_1.IHookType.FAIL, this.context);
|
|
346
|
+
}
|
|
347
|
+
catch (error) {
|
|
348
|
+
this.context.status = types_1.STEP_STATUS.FAILURE;
|
|
349
|
+
this.context.error.push(error);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
282
352
|
if (this.context.status === types_1.STEP_STATUS.SUCCESS) {
|
|
283
|
-
|
|
353
|
+
try {
|
|
354
|
+
yield this.globalActionInstance.start(parse_spec_1.IHookType.SUCCESS, this.context);
|
|
355
|
+
}
|
|
356
|
+
catch (error) {
|
|
357
|
+
this.context.status = types_1.STEP_STATUS.FAILURE;
|
|
358
|
+
this.context.error.push(error);
|
|
359
|
+
}
|
|
284
360
|
}
|
|
285
|
-
|
|
286
|
-
yield this.globalActionInstance.start(parse_spec_1.IHookType.
|
|
361
|
+
try {
|
|
362
|
+
yield this.globalActionInstance.start(parse_spec_1.IHookType.COMPLETE, this.context);
|
|
363
|
+
}
|
|
364
|
+
catch (error) {
|
|
365
|
+
this.context.status = types_1.STEP_STATUS.FAILURE;
|
|
366
|
+
this.context.error.push(error);
|
|
287
367
|
}
|
|
288
|
-
yield this.globalActionInstance.start(parse_spec_1.IHookType.COMPLETE, this.context);
|
|
289
368
|
});
|
|
290
369
|
}
|
|
370
|
+
/**
|
|
371
|
+
* Handles the execution process for a project step.
|
|
372
|
+
* @param item - The project step to handle.
|
|
373
|
+
*/
|
|
291
374
|
handleSrc(item) {
|
|
375
|
+
var _a, _b, _c;
|
|
292
376
|
return __awaiter(this, void 0, void 0, function* () {
|
|
293
|
-
|
|
377
|
+
const { command } = this.spec;
|
|
378
|
+
// Attempt to execute pre-hook and component logic for the project step.
|
|
294
379
|
try {
|
|
380
|
+
// project pre hook and project component
|
|
295
381
|
yield this.handleAfterSrc(item);
|
|
296
|
-
// 项目的output, 再次获取魔法变量
|
|
297
|
-
this.actionInstance.setValue('magic', this.getFilterContext(item));
|
|
298
|
-
const newInputs = yield this.getProps(item);
|
|
299
|
-
yield this.actionInstance.start(parse_spec_1.IHookType.SUCCESS, newInputs);
|
|
300
382
|
}
|
|
301
383
|
catch (error) {
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
384
|
+
// On error, attempt to trigger the project's fail hook and update the recorded context.
|
|
385
|
+
try {
|
|
386
|
+
const res = yield ((_a = this.actionInstance) === null || _a === void 0 ? void 0 : _a.start(parse_spec_1.IHookType.FAIL, this.record.componentProps));
|
|
387
|
+
this.recordContext(item, (0, lodash_1.get)(res, 'pluginOutput', {}));
|
|
388
|
+
}
|
|
389
|
+
catch (error) {
|
|
390
|
+
this.record.status = types_1.STEP_STATUS.FAILURE;
|
|
391
|
+
this.recordContext(item, { error });
|
|
392
|
+
}
|
|
309
393
|
}
|
|
310
394
|
// 若记录的全局状态为true,则进行输出成功的日志
|
|
311
|
-
|
|
312
|
-
|
|
395
|
+
// If the global record status is SUCCESS, attempt to trigger the project's success hook.
|
|
396
|
+
if (this.record.status === types_1.STEP_STATUS.SUCCESS) {
|
|
397
|
+
// project success hook
|
|
398
|
+
try {
|
|
399
|
+
// 项目的output, 再次获取魔法变量
|
|
400
|
+
this.actionInstance.setValue('magic', this.getFilterContext(item));
|
|
401
|
+
const res = yield ((_b = this.actionInstance) === null || _b === void 0 ? void 0 : _b.start(parse_spec_1.IHookType.SUCCESS, Object.assign(Object.assign({}, this.record.componentProps), { output: (0, lodash_1.get)(item, 'output', {}) })));
|
|
402
|
+
this.recordContext(item, (0, lodash_1.get)(res, 'pluginOutput', {}));
|
|
403
|
+
}
|
|
404
|
+
catch (error) {
|
|
405
|
+
this.record.status = types_1.STEP_STATUS.FAILURE;
|
|
406
|
+
this.recordContext(item, { error });
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
// Attempt to trigger the project's complete hook regardless of status.
|
|
410
|
+
try {
|
|
411
|
+
const res = yield ((_c = this.actionInstance) === null || _c === void 0 ? void 0 : _c.start(parse_spec_1.IHookType.COMPLETE, Object.assign(Object.assign({}, this.record.componentProps), { output: (0, lodash_1.get)(item, 'output', {}) })));
|
|
412
|
+
this.recordContext(item, (0, lodash_1.get)(res, 'pluginOutput', {}));
|
|
413
|
+
}
|
|
414
|
+
catch (error) {
|
|
415
|
+
this.record.status = types_1.STEP_STATUS.FAILURE;
|
|
416
|
+
this.recordContext(item, { error });
|
|
417
|
+
}
|
|
418
|
+
// 记录项目已经执行完成
|
|
419
|
+
const process_time = (0, utils_1.getProcessTime)(this.record.startTime);
|
|
420
|
+
this.recordContext(item, { done: true, process_time });
|
|
421
|
+
// Log output based on the record status.
|
|
422
|
+
if (this.record.status === types_1.STEP_STATUS.SUCCESS) {
|
|
423
|
+
this.logger.write(`${chalk_1.default.green('✔')} ${chalk_1.default.gray(`[${item.projectName}] completed (${process_time}s)`)}`);
|
|
424
|
+
}
|
|
425
|
+
if (this.record.status === types_1.STEP_STATUS.FAILURE) {
|
|
426
|
+
this.logger.write(`${chalk_1.default.red('✖')} ${chalk_1.default.gray(`[${item.projectName}] failed to [${command}] (${process_time}s)`)}`);
|
|
427
|
+
}
|
|
428
|
+
// step执行完成后,释放logger
|
|
429
|
+
this.glog.__unset(item.projectName);
|
|
313
430
|
});
|
|
314
431
|
}
|
|
432
|
+
/**
|
|
433
|
+
* Handles the logic after a project step's execution.
|
|
434
|
+
* @param item - The project step to handle.
|
|
435
|
+
*/
|
|
315
436
|
handleAfterSrc(item) {
|
|
437
|
+
var _a;
|
|
316
438
|
return __awaiter(this, void 0, void 0, function* () {
|
|
317
439
|
try {
|
|
440
|
+
// Print detailed information of the project step for debugging purposes.
|
|
318
441
|
debug(`project item: ${(0, utils_1.stringify)(item)}`);
|
|
442
|
+
// Extract the command from the specification.
|
|
443
|
+
const { command } = this.spec;
|
|
444
|
+
// Retrieve the credentials for the project step.
|
|
319
445
|
item.credential = yield (0, utils_1.getCredential)(item.access, this.logger);
|
|
446
|
+
// Set a secret for each credential.
|
|
447
|
+
(0, lodash_1.each)(item.credential, v => {
|
|
448
|
+
this.glog.__setSecret([v]);
|
|
449
|
+
});
|
|
450
|
+
// Parse actions for the project step and initialize a new action instance.
|
|
320
451
|
const newAction = this.parseSpecInstance.parseActions(item.actions, parse_spec_1.IActionLevel.PROJECT);
|
|
321
452
|
debug(`project actions: ${JSON.stringify(newAction)}`);
|
|
322
453
|
this.actionInstance = new actions_1.default(newAction, {
|
|
@@ -325,26 +456,33 @@ class Engine {
|
|
|
325
456
|
logger: item.logger,
|
|
326
457
|
skipActions: this.spec.skipActions,
|
|
327
458
|
});
|
|
459
|
+
// Set values for the action instance.
|
|
328
460
|
this.actionInstance.setValue('magic', this.getFilterContext(item));
|
|
461
|
+
this.actionInstance.setValue('step', item);
|
|
462
|
+
this.actionInstance.setValue('command', command);
|
|
463
|
+
// Retrieve properties for the project step.
|
|
329
464
|
const newInputs = yield this.getProps(item);
|
|
330
|
-
|
|
465
|
+
this.actionInstance.setValue('componentProps', newInputs);
|
|
466
|
+
// Start the pre-hook and execute the logic for the project step.
|
|
467
|
+
const pluginResult = yield ((_a = this.actionInstance) === null || _a === void 0 ? void 0 : _a.start(parse_spec_1.IHookType.PRE, newInputs));
|
|
331
468
|
const response = yield this.doSrc(item, pluginResult);
|
|
332
469
|
// 记录全局的执行状态
|
|
333
470
|
if (this.record.editStatusAble) {
|
|
334
471
|
this.record.status = types_1.STEP_STATUS.SUCCESS;
|
|
335
472
|
}
|
|
336
|
-
//
|
|
473
|
+
// If the project step has an ID, update the corresponding record status and output.
|
|
337
474
|
if (item.id) {
|
|
338
475
|
this.record.steps = Object.assign(Object.assign({}, this.record.steps), { [item.id]: {
|
|
339
476
|
status: types_1.STEP_STATUS.SUCCESS,
|
|
340
477
|
output: response,
|
|
341
478
|
} });
|
|
342
479
|
}
|
|
343
|
-
|
|
344
|
-
this.recordContext(item, { status: types_1.STEP_STATUS.SUCCESS, output: response
|
|
480
|
+
// Update the project step's context to SUCCESS.
|
|
481
|
+
this.recordContext(item, { status: types_1.STEP_STATUS.SUCCESS, output: response });
|
|
345
482
|
}
|
|
346
483
|
catch (e) {
|
|
347
484
|
const error = e;
|
|
485
|
+
// Determine the status based on the project step's "continue-on-error" attribute.
|
|
348
486
|
const status = item['continue-on-error'] === true ? types_1.STEP_STATUS.ERROR_WITH_CONTINUE : types_1.STEP_STATUS.FAILURE;
|
|
349
487
|
// 记录全局的执行状态
|
|
350
488
|
if (this.record.editStatusAble) {
|
|
@@ -354,87 +492,151 @@ class Engine {
|
|
|
354
492
|
// 全局的执行状态一旦失败,便不可修改
|
|
355
493
|
this.record.editStatusAble = false;
|
|
356
494
|
}
|
|
495
|
+
// If the project step has an ID, update the corresponding record status.
|
|
357
496
|
if (item.id) {
|
|
358
497
|
this.record.steps = Object.assign(Object.assign({}, this.record.steps), { [item.id]: {
|
|
359
498
|
status,
|
|
360
499
|
} });
|
|
361
500
|
}
|
|
362
|
-
|
|
501
|
+
// Handle the error based on the project step's "continue-on-error" attribute.
|
|
363
502
|
if (item['continue-on-error']) {
|
|
364
|
-
this.recordContext(item, { status
|
|
503
|
+
this.recordContext(item, { status });
|
|
365
504
|
}
|
|
366
505
|
else {
|
|
367
|
-
this.recordContext(item, { status, error
|
|
506
|
+
this.recordContext(item, { status, error });
|
|
368
507
|
throw error;
|
|
369
508
|
}
|
|
370
509
|
}
|
|
371
510
|
});
|
|
372
511
|
}
|
|
512
|
+
/**
|
|
513
|
+
* Retrieve properties for a specific project step.
|
|
514
|
+
* @param item - The project step for which properties are to be retrieved.
|
|
515
|
+
* @returns An object containing properties related to the project step.
|
|
516
|
+
*/
|
|
373
517
|
getProps(item) {
|
|
374
518
|
return __awaiter(this, void 0, void 0, function* () {
|
|
375
519
|
const magic = this.getFilterContext(item);
|
|
376
520
|
debug(`magic context: ${JSON.stringify(magic)}`);
|
|
377
521
|
const newInputs = (0, parse_spec_1.getInputs)(item.props, magic);
|
|
378
|
-
const { projectName,
|
|
379
|
-
// TODO: inputs数据
|
|
522
|
+
const { projectName, command } = this.spec;
|
|
380
523
|
const result = {
|
|
524
|
+
cwd: this.options.cwd,
|
|
525
|
+
name: (0, lodash_1.get)(this.spec, 'yaml.appName'),
|
|
381
526
|
props: newInputs,
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
527
|
+
command,
|
|
528
|
+
args: (0, lodash_1.filter)(this.options.args, o => !(0, lodash_1.includes)([projectName, command], o)),
|
|
529
|
+
yaml: {
|
|
530
|
+
path: (0, lodash_1.get)(this.spec, 'yaml.path'),
|
|
531
|
+
},
|
|
532
|
+
resource: {
|
|
533
|
+
name: item.projectName,
|
|
534
|
+
component: item.component,
|
|
535
|
+
access: item.access,
|
|
536
|
+
},
|
|
537
|
+
outputs: this.getOutput(),
|
|
538
|
+
getCredential: () => __awaiter(this, void 0, void 0, function* () {
|
|
539
|
+
const res = yield new credential_1.default({ logger: this.logger }).get(item.access);
|
|
540
|
+
return (0, lodash_1.get)(res, 'credential', {});
|
|
541
|
+
}),
|
|
389
542
|
};
|
|
390
543
|
this.recordContext(item, { props: newInputs });
|
|
391
544
|
debug(`get props: ${JSON.stringify(result)}`);
|
|
392
545
|
return result;
|
|
393
546
|
});
|
|
394
547
|
}
|
|
548
|
+
/**
|
|
549
|
+
* Executes the appropriate action based on the provided project step.
|
|
550
|
+
* @param item - The project step to be executed.
|
|
551
|
+
* @param data - Additional data which may contain plugin output.
|
|
552
|
+
* @returns Result of the executed action, if applicable.
|
|
553
|
+
*/
|
|
395
554
|
doSrc(item, data = {}) {
|
|
396
555
|
return __awaiter(this, void 0, void 0, function* () {
|
|
397
|
-
|
|
556
|
+
// Extract command and projectName from the specification.
|
|
557
|
+
const { command = '', projectName } = this.spec;
|
|
558
|
+
// Retrieve properties for the given project step.
|
|
398
559
|
const newInputs = yield this.getProps(item);
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
this.
|
|
560
|
+
// Set component properties based on the provided data or the newly retrieved properties.
|
|
561
|
+
this.record.componentProps = (0, lodash_1.isEmpty)(data.pluginOutput) ? newInputs : data.pluginOutput;
|
|
562
|
+
debug(`component props: ${(0, utils_1.stringify)(this.record.componentProps)}`);
|
|
563
|
+
this.actionInstance.setValue('componentProps', this.record.componentProps);
|
|
402
564
|
// 服务级操作
|
|
403
565
|
if (projectName) {
|
|
404
|
-
if ((0, lodash_1.isFunction)(item.instance[
|
|
566
|
+
if ((0, lodash_1.isFunction)(item.instance[command])) {
|
|
405
567
|
// 方法存在,执行报错,退出码101
|
|
406
568
|
try {
|
|
407
|
-
return yield item.instance[
|
|
569
|
+
return yield item.instance[command](this.record.componentProps);
|
|
408
570
|
}
|
|
409
|
-
catch (
|
|
410
|
-
(0, utils_1.
|
|
571
|
+
catch (e) {
|
|
572
|
+
const useAllowFailure = (0, utils_1.getAllowFailure)(item.allow_failure, {
|
|
573
|
+
exitCode: constants_1.EXIT_CODE.COMPONENT,
|
|
574
|
+
command,
|
|
575
|
+
});
|
|
576
|
+
if (useAllowFailure)
|
|
577
|
+
return;
|
|
578
|
+
const error = e;
|
|
579
|
+
throw new utils_2.DevsError(error.message, {
|
|
580
|
+
data: (0, lodash_1.get)(e, 'data'),
|
|
581
|
+
stack: error.stack,
|
|
582
|
+
exitCode: constants_1.EXIT_CODE.COMPONENT,
|
|
583
|
+
prefix: `[${item.projectName}] failed to [${command}]:`,
|
|
584
|
+
});
|
|
411
585
|
}
|
|
412
586
|
}
|
|
587
|
+
const useAllowFailure = (0, utils_1.getAllowFailure)(item.allow_failure, {
|
|
588
|
+
exitCode: constants_1.EXIT_CODE.DEVS,
|
|
589
|
+
command,
|
|
590
|
+
});
|
|
591
|
+
if (useAllowFailure)
|
|
592
|
+
return;
|
|
413
593
|
// 方法不存在,此时系统将会认为是未找到组件方法,系统的exit code为100;
|
|
414
|
-
|
|
594
|
+
throw new utils_2.DevsError(`The [${command}] command was not found.`, {
|
|
595
|
+
exitCode: constants_1.EXIT_CODE.DEVS,
|
|
596
|
+
tips: `Please check the component ${item.component} has the ${command} command. Serverless Devs documents:${chalk_1.default.underline('https://github.com/Serverless-Devs/Serverless-Devs/blob/master/docs/zh/command')}`,
|
|
597
|
+
prefix: `[${item.projectName}] failed to [${command}]:`,
|
|
598
|
+
});
|
|
415
599
|
}
|
|
416
600
|
// 应用级操作
|
|
417
|
-
if ((0, lodash_1.isFunction)(item.instance[
|
|
601
|
+
if ((0, lodash_1.isFunction)(item.instance[command])) {
|
|
418
602
|
// 方法存在,执行报错,退出码101
|
|
419
603
|
try {
|
|
420
|
-
return yield item.instance[
|
|
604
|
+
return yield item.instance[command](this.record.componentProps);
|
|
421
605
|
}
|
|
422
|
-
catch (
|
|
423
|
-
(0, utils_1.
|
|
606
|
+
catch (e) {
|
|
607
|
+
const useAllowFailure = (0, utils_1.getAllowFailure)(item.allow_failure, {
|
|
608
|
+
exitCode: constants_1.EXIT_CODE.COMPONENT,
|
|
609
|
+
command,
|
|
610
|
+
});
|
|
611
|
+
if (useAllowFailure)
|
|
612
|
+
return;
|
|
613
|
+
const error = e;
|
|
614
|
+
throw new utils_2.DevsError(error.message, {
|
|
615
|
+
data: (0, lodash_1.get)(e, 'data'),
|
|
616
|
+
stack: error.stack,
|
|
617
|
+
exitCode: constants_1.EXIT_CODE.COMPONENT,
|
|
618
|
+
prefix: `[${item.projectName}] failed to [${command}]:`,
|
|
619
|
+
});
|
|
424
620
|
}
|
|
425
621
|
}
|
|
426
622
|
// 方法不存在,进行警告,但是并不会报错,最终的exit code为0;
|
|
427
|
-
|
|
623
|
+
this.logger.tips(`The [${command}] command was not found.`, `Please check the component ${item.component} has the ${command} command. Serverless Devs documents:https://github.com/Serverless-Devs/Serverless-Devs/blob/master/docs/zh/command`);
|
|
428
624
|
});
|
|
429
625
|
}
|
|
626
|
+
/**
|
|
627
|
+
* Handles the project step that is marked to be skipped.
|
|
628
|
+
* @param item - The project step to be skipped.
|
|
629
|
+
* @returns A resolved Promise.
|
|
630
|
+
*/
|
|
430
631
|
doSkip(item) {
|
|
431
632
|
return __awaiter(this, void 0, void 0, function* () {
|
|
432
|
-
// id
|
|
633
|
+
// If the step has an 'id', set its status to 'SKIP' in the record.
|
|
433
634
|
if (item.id) {
|
|
434
635
|
this.record.steps = Object.assign(Object.assign({}, this.record.steps), { [item.id]: {
|
|
435
636
|
status: types_1.STEP_STATUS.SKIP,
|
|
436
637
|
} });
|
|
437
638
|
}
|
|
639
|
+
// Mark the step's status as 'SKIP' and set its processing time to 0.
|
|
438
640
|
this.recordContext(item, { status: types_1.STEP_STATUS.SKIP, process_time: 0 });
|
|
439
641
|
return Promise.resolve();
|
|
440
642
|
});
|