@secureci/cli 0.0.0-stage → 0.1.1

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.
@@ -0,0 +1,554 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.DEFAULT_RUNNER_IMAGES = exports.DEFAULT_CONTAINER_IMAGE = exports.topologicalJobOrder = exports.expandMatrix = exports.parseRepositoryFromRemote = void 0;
37
+ exports.executeWorkflow = executeWorkflow;
38
+ const fs = __importStar(require("fs"));
39
+ const path = __importStar(require("path"));
40
+ const workflow_parser_1 = require("../parser/workflow-parser");
41
+ const validator_1 = require("../parser/validator");
42
+ const logger_1 = require("../utils/logger");
43
+ const action_fetcher_1 = require("./action-fetcher");
44
+ const repository_1 = require("../utils/repository");
45
+ const expression_1 = require("./expression");
46
+ const container_runtime_1 = require("./container-runtime");
47
+ const github_context_1 = require("./github-context");
48
+ const job_plan_1 = require("./job-plan");
49
+ const job_runtime_1 = require("./job-runtime");
50
+ const job_container_1 = require("./job-container");
51
+ const steps_1 = require("./steps");
52
+ const actions_1 = require("./actions");
53
+ // Public API, kept here so `executor` stays the entry point.
54
+ var github_context_2 = require("./github-context");
55
+ Object.defineProperty(exports, "parseRepositoryFromRemote", { enumerable: true, get: function () { return github_context_2.parseRepositoryFromRemote; } });
56
+ var job_plan_2 = require("./job-plan");
57
+ Object.defineProperty(exports, "expandMatrix", { enumerable: true, get: function () { return job_plan_2.expandMatrix; } });
58
+ Object.defineProperty(exports, "topologicalJobOrder", { enumerable: true, get: function () { return job_plan_2.topologicalJobOrder; } });
59
+ var job_container_2 = require("./job-container");
60
+ Object.defineProperty(exports, "DEFAULT_CONTAINER_IMAGE", { enumerable: true, get: function () { return job_container_2.DEFAULT_CONTAINER_IMAGE; } });
61
+ Object.defineProperty(exports, "DEFAULT_RUNNER_IMAGES", { enumerable: true, get: function () { return job_container_2.DEFAULT_RUNNER_IMAGES; } });
62
+ /**
63
+ * Execute a reusable workflow called via `jobs.<id>.uses:`: a local path
64
+ * (`./.github/workflows/x.yml`, relative to the repository root) or a
65
+ * remote `owner/repo/path.yml@ref`. `with:` and `secrets:` are resolved in
66
+ * the calling job's context; the called workflow sees only those secrets
67
+ * (all of them with `secrets: inherit`), and its `on.workflow_call.outputs`
68
+ * become the calling job's outputs.
69
+ */
70
+ async function executeReusableWorkflowJob(jobId, job, callerPath, options, ctx) {
71
+ const startTime = Date.now();
72
+ const uses = job.uses;
73
+ const failed = (error) => {
74
+ logger_1.logger.error(error);
75
+ return {
76
+ job: jobId,
77
+ success: false,
78
+ steps: [],
79
+ duration: Date.now() - startTime
80
+ };
81
+ };
82
+ let resolvedPath;
83
+ if ((0, actions_1.isLocalActionRef)(uses)) {
84
+ const candidates = [
85
+ path.resolve((0, repository_1.repositoryRoot)(callerPath), uses),
86
+ path.resolve(path.dirname(callerPath), uses)
87
+ ];
88
+ resolvedPath = candidates.find((file) => fs.existsSync(file)) ?? null;
89
+ if (!resolvedPath) {
90
+ return failed(`Reusable workflow not found: ${candidates[0]}`);
91
+ }
92
+ }
93
+ else {
94
+ resolvedPath = await (0, action_fetcher_1.fetchRepoFile)(uses, { offline: options.offline });
95
+ if (!resolvedPath) {
96
+ return failed(`Could not fetch reusable workflow ${uses}`);
97
+ }
98
+ }
99
+ const inputs = (0, expression_1.substituteRecord)(job.with, ctx);
100
+ const secrets = job.secrets === 'inherit'
101
+ ? { ...(options.secrets || {}) }
102
+ : {
103
+ // GITHUB_TOKEN is always available to called workflows.
104
+ ...(options.secrets?.GITHUB_TOKEN !== undefined
105
+ ? { GITHUB_TOKEN: options.secrets.GITHUB_TOKEN }
106
+ : {}),
107
+ ...(0, expression_1.substituteRecord)(job.secrets, ctx)
108
+ };
109
+ logger_1.logger.step(`Executing reusable workflow: ${uses} (job: ${jobId})`);
110
+ const subResult = await executeWorkflow(resolvedPath, {
111
+ ...options,
112
+ inputs,
113
+ secrets,
114
+ job: undefined
115
+ });
116
+ return {
117
+ job: jobId,
118
+ success: subResult.success,
119
+ steps: subResult.jobs.flatMap((j) => j.steps),
120
+ outputs: subResult.outputs ?? {},
121
+ duration: Date.now() - startTime
122
+ };
123
+ }
124
+ /**
125
+ * Execute a workflow or action file
126
+ */
127
+ async function executeWorkflow(filePath, options = {}) {
128
+ const startTime = Date.now();
129
+ logger_1.logger.registerSecrets(options.secrets);
130
+ // One run id for every job and called workflow of this run.
131
+ options = {
132
+ ...options,
133
+ runId: options.runId || process.env.GITHUB_RUN_ID || String(Date.now())
134
+ };
135
+ // Steps never fall back to running on the host: without the requested
136
+ // container runtime, stop before anything runs. The host is opt-in
137
+ // (--container-runtime host).
138
+ const preference = options.containerRuntime ?? 'host';
139
+ if (preference !== 'host' && !(await (0, container_runtime_1.resolveContainerRuntime)(preference))) {
140
+ logger_1.logger.error((0, job_container_1.noRuntimeMessage)(preference));
141
+ return { success: false, jobs: [], duration: Date.now() - startTime };
142
+ }
143
+ const runDir = (0, job_runtime_1.createRunDir)();
144
+ try {
145
+ const parsed = (0, workflow_parser_1.parseFile)(filePath);
146
+ if (parsed.type === 'workflow') {
147
+ // Like GitHub, refuse a workflow with an invalid if: before any job.
148
+ const invalid = (0, validator_1.workflowExpressionErrors)(parsed.file);
149
+ if (invalid.length > 0) {
150
+ logger_1.logger.error(`Invalid workflow file: ${filePath}`);
151
+ for (const error of invalid) {
152
+ logger_1.logger.error(` ${error.path}: ${error.message}`);
153
+ }
154
+ return { success: false, jobs: [], duration: Date.now() - startTime };
155
+ }
156
+ logger_1.logger.info(`Executing workflow: ${parsed.file.name}`);
157
+ options = {
158
+ ...options,
159
+ inputs: {
160
+ ...(0, github_context_1.declaredInputDefaults)(parsed.file.on),
161
+ ...(options.inputs ?? {})
162
+ }
163
+ };
164
+ const event = (0, github_context_1.loadEvent)(options, options.inputs ?? {}, parsed.file.on);
165
+ const inputs = (0, github_context_1.typedInputs)(parsed.file.on, options.inputs ?? {});
166
+ fs.writeFileSync(path.join(runDir, 'event.json'), JSON.stringify(event.payload));
167
+ const jobs = [];
168
+ const jobResults = new Map();
169
+ const jobOrder = (0, job_plan_1.topologicalJobOrder)(parsed.file.jobs);
170
+ for (const jobId of jobOrder) {
171
+ const job = parsed.file.jobs[jobId];
172
+ // Skip if specific job requested and this isn't it
173
+ if (options.job && jobId !== options.job) {
174
+ continue;
175
+ }
176
+ const github = (0, github_context_1.buildGithubContext)(parsed.file.name, jobId, process.cwd(), event, options.secrets?.GITHUB_TOKEN, options.runId);
177
+ // `needs` results feed the job's `if:` (implicitly `success()`,
178
+ // which requires every dependency to have succeeded). With --job,
179
+ // dependencies aren't run and count as successful.
180
+ const needsIds = job.needs
181
+ ? Array.isArray(job.needs)
182
+ ? job.needs
183
+ : [job.needs]
184
+ : [];
185
+ const needs = {};
186
+ for (const dep of needsIds) {
187
+ needs[dep] = jobResults.get(dep) ?? {
188
+ result: 'success',
189
+ outputs: {}
190
+ };
191
+ }
192
+ const vars = options.vars ?? {};
193
+ const jobIfCtx = {
194
+ ...(0, expression_1.createContext)({}, inputs, options.secrets, github),
195
+ needs,
196
+ vars,
197
+ dependencies: {
198
+ allSucceeded: Object.values(needs).every((n) => n.result === 'success'),
199
+ anyFailed: Object.values(needs).some((n) => n.result === 'failure')
200
+ }
201
+ };
202
+ let runJob;
203
+ try {
204
+ runJob = (0, expression_1.evaluateCondition)((0, expression_1.withImplicitSuccess)(job.if), jobIfCtx);
205
+ }
206
+ catch (error) {
207
+ // An if: that can't be evaluated fails the job, as on GitHub.
208
+ logger_1.logger.error((0, expression_1.ifEvaluationError)(`job '${jobId}'`, error));
209
+ jobs.push({ job: jobId, success: false, steps: [], duration: 0 });
210
+ jobResults.set(jobId, { result: 'failure', outputs: {} });
211
+ continue;
212
+ }
213
+ if (!runJob) {
214
+ const notOk = needsIds.find((dep) => needs[dep].result !== 'success');
215
+ logger_1.logger.info(notOk
216
+ ? `Skipping job: ${jobId} (dependency "${notOk}" ${needs[notOk].result === 'skipped' ? 'was skipped' : 'did not succeed'})`
217
+ : `Skipping job: ${jobId} (condition not met)`);
218
+ jobResults.set(jobId, { result: 'skipped', outputs: {} });
219
+ continue;
220
+ }
221
+ // Handle reusable workflow calls (once per matrix combination)
222
+ if (job.uses) {
223
+ const combinations = (0, job_plan_1.expandMatrix)(job.strategy?.matrix, jobIfCtx);
224
+ const callOutputs = {};
225
+ let callFailed = false;
226
+ for (const matrixValues of combinations) {
227
+ if (callFailed && job.strategy?.['fail-fast'] !== false)
228
+ break;
229
+ const label = combinations.length > 1
230
+ ? `${jobId} (${(0, job_plan_1.matrixLabel)(matrixValues)})`
231
+ : jobId;
232
+ const reusableResult = await executeReusableWorkflowJob(label, job, filePath, options, { ...jobIfCtx, matrix: matrixValues });
233
+ jobs.push(reusableResult);
234
+ Object.assign(callOutputs, reusableResult.outputs);
235
+ if (!reusableResult.success)
236
+ callFailed = true;
237
+ }
238
+ jobResults.set(jobId, {
239
+ result: callFailed ? 'failure' : 'success',
240
+ outputs: callOutputs
241
+ });
242
+ if (!callFailed) {
243
+ logger_1.logger.success(`Job ${jobId} completed successfully`);
244
+ }
245
+ continue;
246
+ }
247
+ const matrixCombinations = (0, job_plan_1.expandMatrix)(job.strategy?.matrix, jobIfCtx);
248
+ const failFast = job.strategy?.['fail-fast'] !== false;
249
+ let matrixFailed = false;
250
+ const jobOutputs = {};
251
+ for (const [jobIndex, matrixValues] of matrixCombinations.entries()) {
252
+ if (matrixFailed && failFast) {
253
+ logger_1.logger.info(`Skipping remaining matrix jobs for ${jobId} (fail-fast)`);
254
+ break;
255
+ }
256
+ const matrixSuffix = matrixCombinations.length > 1
257
+ ? ` (${(0, job_plan_1.matrixLabel)(matrixValues)})`
258
+ : '';
259
+ const jobLabel = `${jobId}${matrixSuffix}`;
260
+ const runtime = (0, job_runtime_1.createJobRuntime)(github, runDir, {
261
+ shell: job.defaults?.run?.shell ?? parsed.file.defaults?.run?.shell,
262
+ workingDirectory: job.defaults?.run?.['working-directory'] ??
263
+ parsed.file.defaults?.run?.['working-directory']
264
+ });
265
+ // Workflow-level env can't reference `env` itself; job-level env
266
+ // sees the workflow env plus matrix (matching GitHub's contexts).
267
+ const workflowEnv = {
268
+ ...options.env,
269
+ ...(0, expression_1.substituteRecord)(parsed.file.env, {
270
+ ...(0, expression_1.createContext)({ ...options.env }, inputs, options.secrets, github),
271
+ vars
272
+ })
273
+ };
274
+ const jobEnv = (0, expression_1.substituteRecord)(job.env, {
275
+ ...(0, expression_1.createContext)(workflowEnv, inputs, options.secrets, github, matrixValues),
276
+ needs,
277
+ vars
278
+ });
279
+ const jobCtx = {
280
+ ...(0, expression_1.createContext)({ ...workflowEnv, ...jobEnv }, inputs, options.secrets, github, matrixValues),
281
+ needs,
282
+ vars,
283
+ job: { status: 'success' },
284
+ runner: (0, job_runtime_1.runnerContext)(runtime),
285
+ strategy: {
286
+ 'fail-fast': failFast,
287
+ 'job-index': jobIndex,
288
+ 'job-total': matrixCombinations.length,
289
+ 'max-parallel': job.strategy?.['max-parallel'] ?? matrixCombinations.length
290
+ }
291
+ };
292
+ logger_1.logger.step(`Executing job: ${jobLabel}`);
293
+ const jobStartTime = Date.now();
294
+ const jobTimeoutMinutes = (0, job_plan_1.numberSetting)(job['timeout-minutes'], jobCtx);
295
+ const jobDeadline = jobTimeoutMinutes
296
+ ? jobStartTime + jobTimeoutMinutes * 60000
297
+ : undefined;
298
+ const jobTimeoutMessage = `The job running on runner secureci has exceeded the maximum execution time of ${jobTimeoutMinutes} minutes.`;
299
+ const stepResults = [];
300
+ if (!(await (0, job_container_1.startJobServices)(job, jobCtx, options, runtime)) ||
301
+ !(await (0, job_container_1.startJobContainer)(job, jobCtx, options, runtime, runDir))) {
302
+ await (0, job_container_1.stopJobContainer)(runtime);
303
+ jobs.push({
304
+ job: jobLabel,
305
+ success: false,
306
+ steps: [],
307
+ duration: Date.now() - jobStartTime
308
+ });
309
+ matrixFailed = true;
310
+ continue;
311
+ }
312
+ if (!runtime.container) {
313
+ (0, actions_1.warnUnsandboxedActions)(job.steps ?? [], jobCtx, options);
314
+ }
315
+ try {
316
+ await (0, actions_1.runPreSteps)(job.steps ?? [], options, runtime, jobCtx, stepResults);
317
+ // Execute each step
318
+ for (const step of job.steps || []) {
319
+ jobCtx.job = {
320
+ ...jobCtx.job,
321
+ status: jobCtx.jobFailed ? 'failure' : 'success'
322
+ };
323
+ // `$GITHUB_ENV` additions from earlier steps sit between the
324
+ // job env and this step's own `env:`.
325
+ const envCtx = {
326
+ ...jobCtx,
327
+ env: { ...jobCtx.env, ...runtime.env }
328
+ };
329
+ runtime.actionEnv = (0, job_runtime_1.stepActionEnv)(runtime, step);
330
+ const stepCtx = {
331
+ ...envCtx,
332
+ github: {
333
+ ...envCtx.github,
334
+ action: runtime.actionEnv.GITHUB_ACTION
335
+ },
336
+ env: { ...envCtx.env, ...(0, expression_1.substituteRecord)(step.env, envCtx) }
337
+ };
338
+ // After a failure, only steps whose `if:` uses a status
339
+ // function (always(), failure(), ...) still run.
340
+ let runStep;
341
+ try {
342
+ runStep = (0, expression_1.evaluateCondition)((0, expression_1.withImplicitSuccess)(step.if), stepCtx);
343
+ }
344
+ catch (error) {
345
+ // An if: that can't be evaluated fails the step.
346
+ const message = (0, expression_1.ifEvaluationError)(`step '${(0, job_runtime_1.stepDisplayName)(step, stepCtx)}'`, error);
347
+ logger_1.logger.error(message);
348
+ stepResults.push({
349
+ step,
350
+ success: false,
351
+ error: message,
352
+ duration: 0
353
+ });
354
+ if (step.id) {
355
+ jobCtx.steps[step.id] = {
356
+ outcome: 'failure',
357
+ conclusion: 'failure',
358
+ outputs: {}
359
+ };
360
+ }
361
+ jobCtx.jobFailed = true;
362
+ continue;
363
+ }
364
+ if (!runStep) {
365
+ logger_1.logger.info(`Skipping step: ${(0, job_runtime_1.stepDisplayName)(step, stepCtx)} (condition not met)`);
366
+ const skippedResult = {
367
+ step,
368
+ success: true,
369
+ outcome: 'skipped',
370
+ output: 'Skipped: condition not met',
371
+ duration: 0
372
+ };
373
+ stepResults.push(skippedResult);
374
+ if (step.id) {
375
+ jobCtx.steps[step.id] = {
376
+ outcome: 'skipped',
377
+ conclusion: 'skipped',
378
+ outputs: {}
379
+ };
380
+ }
381
+ continue;
382
+ }
383
+ // timeout-minutes: the step's own, or what's left of the job's.
384
+ const stepMinutes = (0, job_plan_1.numberSetting)(step['timeout-minutes'], stepCtx);
385
+ const jobLeft = jobDeadline !== undefined
386
+ ? Math.max(jobDeadline - Date.now(), 1)
387
+ : undefined;
388
+ const stepMs = stepMinutes ? stepMinutes * 60000 : undefined;
389
+ runtime.stepTimeout =
390
+ jobLeft !== undefined &&
391
+ (stepMs === undefined || jobLeft < stepMs)
392
+ ? { ms: jobLeft, message: jobTimeoutMessage }
393
+ : stepMs !== undefined
394
+ ? {
395
+ ms: stepMs,
396
+ message: `The action '${(0, job_runtime_1.stepDisplayName)(step, stepCtx)}' has timed out after ${stepMinutes} minutes.`
397
+ }
398
+ : undefined;
399
+ const stepResult = await (0, steps_1.executeStep)(step, options, runtime, stepCtx);
400
+ runtime.stepTimeout = undefined;
401
+ stepResults.push(stepResult);
402
+ if (jobDeadline !== undefined && Date.now() >= jobDeadline) {
403
+ // The job is cancelled: no further steps run.
404
+ if (stepResult.success) {
405
+ stepResult.success = false;
406
+ stepResult.error = jobTimeoutMessage;
407
+ }
408
+ logger_1.logger.error(jobTimeoutMessage);
409
+ jobCtx.jobFailed = true;
410
+ break;
411
+ }
412
+ const outcome = stepResult.success ? 'success' : 'failure';
413
+ if (step.id) {
414
+ jobCtx.steps[step.id] = {
415
+ outcome,
416
+ // continue-on-error turns a failure into a successful
417
+ // conclusion, which is what success()/failure() see.
418
+ conclusion: step['continue-on-error'] ? 'success' : outcome,
419
+ outputs: stepResult.outputs || {}
420
+ };
421
+ }
422
+ if (!stepResult.success && !step['continue-on-error']) {
423
+ jobCtx.jobFailed = true;
424
+ logger_1.logger.error(`Step failed: ${(0, job_runtime_1.stepDisplayName)(step, stepCtx)}`);
425
+ }
426
+ }
427
+ await (0, job_runtime_1.runPostSteps)(runtime, jobCtx, stepResults);
428
+ if (runtime.summary.trim()) {
429
+ logger_1.logger.info(`Job summary (${jobLabel}):\n${runtime.summary.trim()}`);
430
+ }
431
+ jobCtx.job = {
432
+ ...jobCtx.job,
433
+ status: jobCtx.jobFailed ? 'failure' : 'success'
434
+ };
435
+ Object.assign(jobOutputs, (0, expression_1.substituteRecord)(job.outputs, {
436
+ ...jobCtx,
437
+ env: { ...jobCtx.env, ...runtime.env }
438
+ }));
439
+ }
440
+ finally {
441
+ await (0, job_container_1.stopJobContainer)(runtime);
442
+ }
443
+ const jobSuccess = stepResults.every((s) => s.success || s.step['continue-on-error']);
444
+ // Job-level continue-on-error (often `${{ matrix.experimental }}`)
445
+ // lets this combination fail without failing the job or run.
446
+ const allowedFailure = !jobSuccess && (0, job_plan_1.isTruthySetting)(job['continue-on-error'], jobCtx);
447
+ jobs.push({
448
+ job: jobLabel,
449
+ success: jobSuccess,
450
+ ...(allowedFailure ? { continueOnError: true } : {}),
451
+ ...(runtime.summary ? { summary: runtime.summary } : {}),
452
+ steps: stepResults,
453
+ outputs: { ...jobOutputs },
454
+ duration: Date.now() - jobStartTime
455
+ });
456
+ if (!jobSuccess && !allowedFailure) {
457
+ matrixFailed = true;
458
+ }
459
+ }
460
+ const jobSuccess = !matrixFailed;
461
+ jobResults.set(jobId, {
462
+ result: jobSuccess ? 'success' : 'failure',
463
+ outputs: jobOutputs
464
+ });
465
+ if (jobSuccess) {
466
+ logger_1.logger.success(`Job ${jobId} completed successfully`);
467
+ }
468
+ }
469
+ const callOutputs = (0, github_context_1.workflowCallOutputs)(parsed.file.on);
470
+ return {
471
+ success: jobs.every((j) => j.success || j.continueOnError),
472
+ jobs,
473
+ duration: Date.now() - startTime,
474
+ ...(callOutputs
475
+ ? {
476
+ outputs: (0, expression_1.substituteRecord)(callOutputs, {
477
+ ...(0, expression_1.createContext)({}, inputs, options.secrets),
478
+ vars: options.vars ?? {},
479
+ jobs: Object.fromEntries(jobResults)
480
+ })
481
+ }
482
+ : {})
483
+ };
484
+ }
485
+ else {
486
+ // Handle action execution
487
+ logger_1.logger.info(`Executing action: ${parsed.file.name}`);
488
+ if (parsed.file.runs.using === 'composite' && parsed.file.runs.steps) {
489
+ const stepResults = [];
490
+ const github = (0, github_context_1.buildGithubContext)(parsed.file.name, 'action', process.cwd(), undefined, options.secrets?.GITHUB_TOKEN, options.runId);
491
+ const runtime = (0, job_runtime_1.createJobRuntime)(github, runDir);
492
+ const actionCtx = (0, expression_1.createContext)({}, options.inputs, options.secrets, github);
493
+ if (!(await (0, job_container_1.startJobContainer)(undefined, actionCtx, options, runtime, runDir))) {
494
+ return { success: false, jobs: [], duration: Date.now() - startTime };
495
+ }
496
+ try {
497
+ for (const step of parsed.file.runs.steps) {
498
+ const stepResult = await (0, steps_1.executeStep)(step, options, runtime);
499
+ stepResults.push(stepResult);
500
+ if (!stepResult.success) {
501
+ return {
502
+ success: false,
503
+ jobs: [
504
+ {
505
+ job: 'action',
506
+ success: false,
507
+ steps: stepResults,
508
+ duration: Date.now() - startTime
509
+ }
510
+ ],
511
+ duration: Date.now() - startTime
512
+ };
513
+ }
514
+ }
515
+ return {
516
+ success: true,
517
+ jobs: [
518
+ {
519
+ job: 'action',
520
+ success: true,
521
+ steps: stepResults,
522
+ duration: Date.now() - startTime
523
+ }
524
+ ],
525
+ duration: Date.now() - startTime
526
+ };
527
+ }
528
+ finally {
529
+ await (0, job_container_1.stopJobContainer)(runtime);
530
+ }
531
+ }
532
+ else {
533
+ logger_1.logger.error(`Only composite actions can be run directly; call this ${parsed.file.runs.using} action from a workflow step instead.`);
534
+ return {
535
+ success: false,
536
+ jobs: [],
537
+ duration: Date.now() - startTime
538
+ };
539
+ }
540
+ }
541
+ }
542
+ catch (error) {
543
+ logger_1.logger.error(`Execution failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
544
+ return {
545
+ success: false,
546
+ jobs: [],
547
+ duration: Date.now() - startTime
548
+ };
549
+ }
550
+ finally {
551
+ fs.rmSync(runDir, { recursive: true, force: true });
552
+ }
553
+ }
554
+ //# sourceMappingURL=executor.js.map