@serverless-devs/engine 0.0.1-beta.3 → 0.0.1-beta.30

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