@magland/mochi 0.3.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.
- package/LICENSE +201 -0
- package/README.md +108 -0
- package/dist/ansi.js +174 -0
- package/dist/api/admin.js +416 -0
- package/dist/api/auth.js +166 -0
- package/dist/api/backup.js +598 -0
- package/dist/api/ci.js +336 -0
- package/dist/api/contents.js +339 -0
- package/dist/api/issues.js +165 -0
- package/dist/api/pulls.js +244 -0
- package/dist/api/releases.js +83 -0
- package/dist/api/repos.js +156 -0
- package/dist/api/write.js +518 -0
- package/dist/api.js +326 -0
- package/dist/assets.js +29 -0
- package/dist/atom.js +32 -0
- package/dist/atomic.js +171 -0
- package/dist/avatar.js +81 -0
- package/dist/browse.js +630 -0
- package/dist/build-info.json +4 -0
- package/dist/ci/actionref.js +86 -0
- package/dist/ci/api.js +829 -0
- package/dist/ci/artifacts.js +201 -0
- package/dist/ci/dispatch.js +30 -0
- package/dist/ci/engine.js +1321 -0
- package/dist/ci/expr.js +526 -0
- package/dist/ci/manual.js +199 -0
- package/dist/ci/present.js +82 -0
- package/dist/ci/protocol.js +6 -0
- package/dist/ci/runners.js +256 -0
- package/dist/ci/runs.js +208 -0
- package/dist/ci/trigger.js +28 -0
- package/dist/ci/views.js +441 -0
- package/dist/ci/wake.js +194 -0
- package/dist/ci/web.js +617 -0
- package/dist/ci/workflow.js +436 -0
- package/dist/cli/admin-cmd.js +324 -0
- package/dist/cli/api-cmd.js +128 -0
- package/dist/cli/backup-cmd.js +1500 -0
- package/dist/cli/exit.js +69 -0
- package/dist/cli/input.js +64 -0
- package/dist/cli/issue-cmd.js +243 -0
- package/dist/cli/output.js +93 -0
- package/dist/cli/parse.js +317 -0
- package/dist/cli/pr-cmd.js +289 -0
- package/dist/cli/release-cmd.js +171 -0
- package/dist/cli/repo-cmd.js +763 -0
- package/dist/cli/repo.js +101 -0
- package/dist/cli/run-cmd.js +438 -0
- package/dist/cli/target.js +54 -0
- package/dist/cli-api.js +84 -0
- package/dist/compare.js +111 -0
- package/dist/config.js +212 -0
- package/dist/credentials.js +235 -0
- package/dist/deploy-cli.js +859 -0
- package/dist/deploy-runner-cli.js +592 -0
- package/dist/diff.js +171 -0
- package/dist/discussion.js +253 -0
- package/dist/egress.js +559 -0
- package/dist/filecache.js +68 -0
- package/dist/find.js +162 -0
- package/dist/forms.js +737 -0
- package/dist/git.js +547 -0
- package/dist/githttp.js +428 -0
- package/dist/html.js +87 -0
- package/dist/icons.js +101 -0
- package/dist/import-cli.js +316 -0
- package/dist/index.js +752 -0
- package/dist/issues.js +308 -0
- package/dist/issueweb.js +447 -0
- package/dist/job-cli.js +197 -0
- package/dist/jobtoken.js +96 -0
- package/dist/languages.js +383 -0
- package/dist/layout.js +100 -0
- package/dist/lfs.js +438 -0
- package/dist/lfsstore.js +425 -0
- package/dist/limit.js +259 -0
- package/dist/logo.js +61 -0
- package/dist/markdown.js +382 -0
- package/dist/migrate.js +334 -0
- package/dist/multipart.js +90 -0
- package/dist/ops.js +869 -0
- package/dist/pagescript.js +465 -0
- package/dist/perms.js +370 -0
- package/dist/pointer.js +55 -0
- package/dist/profile.js +106 -0
- package/dist/pulls.js +320 -0
- package/dist/pullweb.js +461 -0
- package/dist/redirects.js +455 -0
- package/dist/releases.js +435 -0
- package/dist/render.js +233 -0
- package/dist/runner/actions.js +448 -0
- package/dist/runner/client.js +428 -0
- package/dist/runner/context.js +247 -0
- package/dist/runner/docker.js +197 -0
- package/dist/runner/externals.js +175 -0
- package/dist/runner/job.js +290 -0
- package/dist/runner/manual-run.js +272 -0
- package/dist/runner/overrides.js +554 -0
- package/dist/runner/steps.js +571 -0
- package/dist/runner/wake.js +84 -0
- package/dist/runner-cli.js +405 -0
- package/dist/scan.js +231 -0
- package/dist/server.js +424 -0
- package/dist/session.js +267 -0
- package/dist/site.js +259 -0
- package/dist/siteshost.js +94 -0
- package/dist/source.js +90 -0
- package/dist/style.js +1295 -0
- package/dist/themes.js +369 -0
- package/dist/vault.js +442 -0
- package/dist/version.js +88 -0
- package/dist/views.js +1007 -0
- package/dist/web.js +182 -0
- package/dist/webops.js +1402 -0
- package/package.json +71 -0
|
@@ -0,0 +1,571 @@
|
|
|
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.RUNNER_TEMP = exports.ActionError = void 0;
|
|
37
|
+
exports.runSteps = runSteps;
|
|
38
|
+
exports.runPostHooks = runPostHooks;
|
|
39
|
+
const crypto = __importStar(require("crypto"));
|
|
40
|
+
const fs = __importStar(require("fs"));
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
const actionref_1 = require("../ci/actionref");
|
|
43
|
+
const expr_1 = require("../ci/expr");
|
|
44
|
+
const actions_1 = require("./actions");
|
|
45
|
+
Object.defineProperty(exports, "ActionError", { enumerable: true, get: function () { return actions_1.ActionError; } });
|
|
46
|
+
const context_1 = require("./context");
|
|
47
|
+
Object.defineProperty(exports, "RUNNER_TEMP", { enumerable: true, get: function () { return context_1.RUNNER_TEMP; } });
|
|
48
|
+
const docker_1 = require("./docker");
|
|
49
|
+
const overrides_1 = require("./overrides");
|
|
50
|
+
function envFileTag(index) {
|
|
51
|
+
return `${index}-${crypto.randomBytes(4).toString('hex')}`;
|
|
52
|
+
}
|
|
53
|
+
// Run a list of steps in one scope. `onStepState` is called for top-level
|
|
54
|
+
// workflow steps only; composite steps report through the log instead, since
|
|
55
|
+
// the run page's step list mirrors the workflow file rather than everything
|
|
56
|
+
// that executed.
|
|
57
|
+
async function runSteps(steps, scope, ctx, hooks = {}) {
|
|
58
|
+
let failed = false;
|
|
59
|
+
for (let i = 0; i < steps.length; i++) {
|
|
60
|
+
const step = steps[i];
|
|
61
|
+
const cancelled = hooks.checkCancelled?.() ?? false;
|
|
62
|
+
if (cancelled)
|
|
63
|
+
return { failed, cancelled: true };
|
|
64
|
+
const stepEnv = buildStepEnv(step, scope, ctx, failed);
|
|
65
|
+
if (stepEnv === null) {
|
|
66
|
+
failed = true;
|
|
67
|
+
hooks.onEnd?.(i, { ok: false, failed: true, skipped: false });
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
const evalEnv = () => ({
|
|
71
|
+
contexts: (0, context_1.contextsFor)({
|
|
72
|
+
spec: ctx.spec,
|
|
73
|
+
scope,
|
|
74
|
+
stepEnv,
|
|
75
|
+
jobFailed: failed || ctx.status().failed,
|
|
76
|
+
jobCancelled: ctx.status().cancelled,
|
|
77
|
+
}),
|
|
78
|
+
functions: (0, context_1.statusFunctions)(failed || ctx.status().failed, ctx.status().cancelled),
|
|
79
|
+
});
|
|
80
|
+
let shouldRun;
|
|
81
|
+
try {
|
|
82
|
+
shouldRun = step.if === undefined ? !failed : (0, expr_1.evalCondition)(step.if, evalEnv());
|
|
83
|
+
}
|
|
84
|
+
catch (e) {
|
|
85
|
+
ctx.log(`Error evaluating if: ${e instanceof Error ? e.message : String(e)}`);
|
|
86
|
+
failed = true;
|
|
87
|
+
hooks.onEnd?.(i, { ok: false, failed: true, skipped: false });
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (!shouldRun) {
|
|
91
|
+
if (step.id)
|
|
92
|
+
scope.conclusions[step.id] = { outcome: 'skipped', conclusion: 'skipped' };
|
|
93
|
+
hooks.onEnd?.(i, { ok: true, failed: false, skipped: true });
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
const label = labelOf(step, i, evalEnv);
|
|
97
|
+
hooks.onStart?.(i, label);
|
|
98
|
+
if (scope.depth > 0)
|
|
99
|
+
ctx.log(`▸ ${label}`);
|
|
100
|
+
let outcome;
|
|
101
|
+
try {
|
|
102
|
+
outcome = await executeStep(step, scope, ctx, stepEnv, evalEnv, i);
|
|
103
|
+
}
|
|
104
|
+
catch (e) {
|
|
105
|
+
ctx.log(e instanceof Error ? e.message : String(e));
|
|
106
|
+
outcome = { ok: false, failed: true, skipped: false };
|
|
107
|
+
}
|
|
108
|
+
let continued = false;
|
|
109
|
+
if (!outcome.ok && step.continueOnError !== undefined) {
|
|
110
|
+
try {
|
|
111
|
+
continued = (0, expr_1.evalCondition)(step.continueOnError, evalEnv());
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
continued = false;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (continued)
|
|
118
|
+
outcome = { ...outcome, failed: false };
|
|
119
|
+
if (step.id) {
|
|
120
|
+
scope.conclusions[step.id] = {
|
|
121
|
+
outcome: outcome.ok ? 'success' : 'failure',
|
|
122
|
+
conclusion: outcome.failed ? 'failure' : 'success',
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
if (outcome.failed)
|
|
126
|
+
failed = true;
|
|
127
|
+
hooks.onEnd?.(i, outcome);
|
|
128
|
+
// Inside a composite action, a failed step ends the action immediately.
|
|
129
|
+
if (outcome.failed && scope.depth > 0)
|
|
130
|
+
return { failed: true, cancelled: false };
|
|
131
|
+
}
|
|
132
|
+
return { failed, cancelled: hooks.checkCancelled?.() ?? false };
|
|
133
|
+
}
|
|
134
|
+
function labelOf(step, index, evalEnv) {
|
|
135
|
+
const resolve = (s) => (0, expr_1.render)(s, evalEnv());
|
|
136
|
+
try {
|
|
137
|
+
if (step.name)
|
|
138
|
+
return resolve(step.name);
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
return step.name;
|
|
142
|
+
}
|
|
143
|
+
if (step.uses)
|
|
144
|
+
return step.uses;
|
|
145
|
+
if (step.run) {
|
|
146
|
+
let text = step.run;
|
|
147
|
+
try {
|
|
148
|
+
text = resolve(step.run);
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
// keep the literal
|
|
152
|
+
}
|
|
153
|
+
const first = text.split('\n').find((l) => l.trim() !== '')?.trim() ?? '';
|
|
154
|
+
return `Run ${first.length > 70 ? first.slice(0, 70) + '…' : first}`;
|
|
155
|
+
}
|
|
156
|
+
return `Step ${index + 1}`;
|
|
157
|
+
}
|
|
158
|
+
// Environment for one step: the base environment, then anything earlier steps
|
|
159
|
+
// exported through GITHUB_ENV, then the composite action's env, then the
|
|
160
|
+
// step's own. Only the values written in YAML are templates; values that came
|
|
161
|
+
// from GITHUB_ENV at runtime are data, and evaluating `${{ }}` found in them
|
|
162
|
+
// would let one step's output become an expression in another step's context.
|
|
163
|
+
function buildStepEnv(step, scope, ctx, failedSoFar) {
|
|
164
|
+
const stepEnv = { ...ctx.env, ...ctx.rt.env };
|
|
165
|
+
const templates = { ...ctx.spec.env, ...scope.actionEnv, ...step.env };
|
|
166
|
+
try {
|
|
167
|
+
const rendered = (0, expr_1.renderDeep)(templates, {
|
|
168
|
+
contexts: (0, context_1.contextsFor)({
|
|
169
|
+
spec: ctx.spec,
|
|
170
|
+
scope,
|
|
171
|
+
stepEnv,
|
|
172
|
+
jobFailed: failedSoFar || ctx.status().failed,
|
|
173
|
+
jobCancelled: ctx.status().cancelled,
|
|
174
|
+
}),
|
|
175
|
+
});
|
|
176
|
+
// Workflow env first so a runtime value from GITHUB_ENV still wins over
|
|
177
|
+
// it, then the action's and the step's, which win over everything.
|
|
178
|
+
for (const k of Object.keys(ctx.spec.env)) {
|
|
179
|
+
if (k in rendered)
|
|
180
|
+
stepEnv[k] = (0, expr_1.stringify)(rendered[k]);
|
|
181
|
+
}
|
|
182
|
+
for (const [k, v] of Object.entries(ctx.rt.env))
|
|
183
|
+
stepEnv[k] = v;
|
|
184
|
+
for (const k of [...Object.keys(scope.actionEnv), ...Object.keys(step.env)]) {
|
|
185
|
+
if (k in rendered)
|
|
186
|
+
stepEnv[k] = (0, expr_1.stringify)(rendered[k]);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
catch (e) {
|
|
190
|
+
ctx.log(`Error evaluating env: ${e instanceof Error ? e.message : String(e)}`);
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
return stepEnv;
|
|
194
|
+
}
|
|
195
|
+
async function executeStep(step, scope, ctx, stepEnv, evalEnv, index) {
|
|
196
|
+
if (step.run !== undefined) {
|
|
197
|
+
const ok = await runShellStep(step, scope, ctx, stepEnv, evalEnv, index);
|
|
198
|
+
return { ok, failed: !ok, skipped: false };
|
|
199
|
+
}
|
|
200
|
+
if (step.uses !== undefined) {
|
|
201
|
+
const ok = await runActionStep(step, scope, ctx, stepEnv, evalEnv, index);
|
|
202
|
+
return { ok, failed: !ok, skipped: false };
|
|
203
|
+
}
|
|
204
|
+
ctx.log('a step needs either run: or uses:');
|
|
205
|
+
return { ok: false, failed: true, skipped: false };
|
|
206
|
+
}
|
|
207
|
+
async function makeFileCommands(ctx, tag) {
|
|
208
|
+
const files = {
|
|
209
|
+
output: `${context_1.FILE_COMMAND_DIR}/output-${tag}`,
|
|
210
|
+
envFile: `${context_1.FILE_COMMAND_DIR}/env-${tag}`,
|
|
211
|
+
pathFile: `${context_1.FILE_COMMAND_DIR}/path-${tag}`,
|
|
212
|
+
summary: `${context_1.FILE_COMMAND_DIR}/summary-${tag}`,
|
|
213
|
+
state: `${context_1.FILE_COMMAND_DIR}/state-${tag}`,
|
|
214
|
+
};
|
|
215
|
+
for (const f of Object.values(files))
|
|
216
|
+
await (0, docker_1.writeFileInContainer)(ctx.containerId, f, '');
|
|
217
|
+
return files;
|
|
218
|
+
}
|
|
219
|
+
function fileCommandEnv(files) {
|
|
220
|
+
return {
|
|
221
|
+
GITHUB_OUTPUT: files.output,
|
|
222
|
+
GITHUB_ENV: files.envFile,
|
|
223
|
+
GITHUB_PATH: files.pathFile,
|
|
224
|
+
GITHUB_STEP_SUMMARY: files.summary,
|
|
225
|
+
GITHUB_STATE: files.state,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
// Read back everything a step wrote through the file commands, and fold it
|
|
229
|
+
// into the scope and the job runtime.
|
|
230
|
+
async function collectFileCommands(ctx, scope, files, stepId) {
|
|
231
|
+
const outputText = await (0, docker_1.readFileInContainer)(ctx.containerId, files.output);
|
|
232
|
+
const parsed = (0, context_1.parseKeyValueFile)(outputText);
|
|
233
|
+
if (stepId && Object.keys(parsed).length) {
|
|
234
|
+
scope.outputs[stepId] = { ...(scope.outputs[stepId] ?? {}), ...parsed };
|
|
235
|
+
}
|
|
236
|
+
const envText = await (0, docker_1.readFileInContainer)(ctx.containerId, files.envFile);
|
|
237
|
+
Object.assign(ctx.rt.env, (0, context_1.parseKeyValueFile)(envText));
|
|
238
|
+
const pathText = await (0, docker_1.readFileInContainer)(ctx.containerId, files.pathFile);
|
|
239
|
+
for (const line of pathText.split('\n')) {
|
|
240
|
+
const p = line.trim();
|
|
241
|
+
if (p !== '' && !ctx.rt.extraPath.includes(p))
|
|
242
|
+
ctx.rt.extraPath.unshift(p);
|
|
243
|
+
}
|
|
244
|
+
const summaryText = await (0, docker_1.readFileInContainer)(ctx.containerId, files.summary);
|
|
245
|
+
if (summaryText.trim() !== '')
|
|
246
|
+
ctx.rt.summaries.push(summaryText);
|
|
247
|
+
}
|
|
248
|
+
function pathEnv(ctx, stepEnv) {
|
|
249
|
+
if (ctx.rt.extraPath.length === 0)
|
|
250
|
+
return {};
|
|
251
|
+
const existing = stepEnv.PATH ?? '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin';
|
|
252
|
+
return { PATH: `${ctx.rt.extraPath.join(':')}:${existing}` };
|
|
253
|
+
}
|
|
254
|
+
function workdirFor(step, ctx, scope, evalEnv) {
|
|
255
|
+
const wd = step.workingDirectory ?? (scope.depth === 0 ? ctx.spec.defaults?.workingDirectory : undefined);
|
|
256
|
+
if (!wd)
|
|
257
|
+
return context_1.WORKSPACE;
|
|
258
|
+
try {
|
|
259
|
+
const rendered = (0, expr_1.render)(wd, evalEnv());
|
|
260
|
+
return rendered.startsWith('/') ? rendered : `${context_1.WORKSPACE}/${rendered}`;
|
|
261
|
+
}
|
|
262
|
+
catch {
|
|
263
|
+
return context_1.WORKSPACE;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
// Interpret one line of a step's output: either a workflow command, or text.
|
|
267
|
+
function makeLineHandler(ctx, scope, stepId) {
|
|
268
|
+
return (raw) => {
|
|
269
|
+
const cmd = (0, context_1.parseWorkflowCommand)(raw);
|
|
270
|
+
if (!cmd) {
|
|
271
|
+
ctx.log(raw);
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
switch (cmd.name) {
|
|
275
|
+
case 'add-mask':
|
|
276
|
+
ctx.masker.add(cmd.message);
|
|
277
|
+
break;
|
|
278
|
+
case 'group':
|
|
279
|
+
ctx.log(`▾ ${cmd.message}`);
|
|
280
|
+
break;
|
|
281
|
+
case 'endgroup':
|
|
282
|
+
break;
|
|
283
|
+
case 'error':
|
|
284
|
+
case 'warning':
|
|
285
|
+
case 'notice': {
|
|
286
|
+
const where = cmd.props.file ? ` (${cmd.props.file}${cmd.props.line ? `:${cmd.props.line}` : ''})` : '';
|
|
287
|
+
ctx.log(`${cmd.name.toUpperCase()}${where}: ${cmd.message}`);
|
|
288
|
+
break;
|
|
289
|
+
}
|
|
290
|
+
case 'debug':
|
|
291
|
+
// Actions are chatty at debug level; GitHub hides these unless the
|
|
292
|
+
// run was started with debug logging, and so does mochi.
|
|
293
|
+
if (ctx.env.RUNNER_DEBUG === '1')
|
|
294
|
+
ctx.log(`DEBUG: ${cmd.message}`);
|
|
295
|
+
break;
|
|
296
|
+
case 'set-output':
|
|
297
|
+
if (stepId && cmd.props.name) {
|
|
298
|
+
scope.outputs[stepId] = { ...(scope.outputs[stepId] ?? {}), [cmd.props.name]: cmd.message };
|
|
299
|
+
}
|
|
300
|
+
break;
|
|
301
|
+
case 'set-env':
|
|
302
|
+
if (cmd.props.name)
|
|
303
|
+
ctx.rt.env[cmd.props.name] = cmd.message;
|
|
304
|
+
break;
|
|
305
|
+
case 'add-path':
|
|
306
|
+
if (cmd.message && !ctx.rt.extraPath.includes(cmd.message))
|
|
307
|
+
ctx.rt.extraPath.unshift(cmd.message);
|
|
308
|
+
break;
|
|
309
|
+
case 'echo':
|
|
310
|
+
case 'save-state':
|
|
311
|
+
case 'stop-commands':
|
|
312
|
+
break;
|
|
313
|
+
// Problem matchers annotate a run's diff on GitHub. mochi has no
|
|
314
|
+
// equivalent yet, so the commands are consumed rather than printed.
|
|
315
|
+
case 'add-matcher':
|
|
316
|
+
case 'remove-matcher':
|
|
317
|
+
break;
|
|
318
|
+
default:
|
|
319
|
+
ctx.log(raw);
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
async function runShellStep(step, scope, ctx, stepEnv, evalEnv, index) {
|
|
324
|
+
let script;
|
|
325
|
+
try {
|
|
326
|
+
script = (0, expr_1.render)(step.run ?? '', evalEnv());
|
|
327
|
+
}
|
|
328
|
+
catch (e) {
|
|
329
|
+
ctx.log(`Error evaluating the step body: ${e instanceof Error ? e.message : String(e)}`);
|
|
330
|
+
return false;
|
|
331
|
+
}
|
|
332
|
+
const tag = envFileTag(index);
|
|
333
|
+
const files = await makeFileCommands(ctx, tag);
|
|
334
|
+
const scriptPath = `${context_1.FILE_COMMAND_DIR}/step-${tag}`;
|
|
335
|
+
await (0, docker_1.writeFileInContainer)(ctx.containerId, scriptPath, script.endsWith('\n') ? script : script + '\n');
|
|
336
|
+
const shell = step.shell ?? (scope.depth === 0 ? ctx.spec.defaults?.shell : undefined);
|
|
337
|
+
const env = {
|
|
338
|
+
...stepEnv,
|
|
339
|
+
...fileCommandEnv(files),
|
|
340
|
+
...pathEnv(ctx, stepEnv),
|
|
341
|
+
GITHUB_ACTION: step.id ?? `__run_${index}`,
|
|
342
|
+
};
|
|
343
|
+
if (scope.actionPath)
|
|
344
|
+
env.GITHUB_ACTION_PATH = scope.actionPath;
|
|
345
|
+
const handle = (0, docker_1.execInContainer)(ctx.containerId, (0, context_1.shellCommand)(shell, scriptPath), makeLineHandler(ctx, scope, step.id), {
|
|
346
|
+
workdir: workdirFor(step, ctx, scope, evalEnv),
|
|
347
|
+
env,
|
|
348
|
+
});
|
|
349
|
+
const code = await handle.done;
|
|
350
|
+
await collectFileCommands(ctx, scope, files, step.id);
|
|
351
|
+
if (code !== 0)
|
|
352
|
+
ctx.log(`Process completed with exit code ${code}.`);
|
|
353
|
+
return code === 0;
|
|
354
|
+
}
|
|
355
|
+
// ---- uses: steps ----
|
|
356
|
+
async function runActionStep(step, scope, ctx, stepEnv, evalEnv, index) {
|
|
357
|
+
if (scope.depth >= context_1.MAX_ACTION_DEPTH) {
|
|
358
|
+
ctx.log(`actions are nested more than ${context_1.MAX_ACTION_DEPTH} deep; refusing to go further`);
|
|
359
|
+
return false;
|
|
360
|
+
}
|
|
361
|
+
let ref;
|
|
362
|
+
try {
|
|
363
|
+
ref = (0, actionref_1.parseActionRef)((0, expr_1.render)(step.uses, evalEnv()));
|
|
364
|
+
}
|
|
365
|
+
catch (e) {
|
|
366
|
+
ctx.log(e instanceof Error ? e.message : String(e));
|
|
367
|
+
return false;
|
|
368
|
+
}
|
|
369
|
+
// `with:` values are templates evaluated in the calling scope.
|
|
370
|
+
const provided = {};
|
|
371
|
+
try {
|
|
372
|
+
for (const [k, v] of Object.entries(step.with ?? {}))
|
|
373
|
+
provided[k] = (0, expr_1.render)(v, evalEnv());
|
|
374
|
+
}
|
|
375
|
+
catch (e) {
|
|
376
|
+
ctx.log(`Error evaluating with: ${e instanceof Error ? e.message : String(e)}`);
|
|
377
|
+
return false;
|
|
378
|
+
}
|
|
379
|
+
// An override stands in for actions that talk to services only GitHub has.
|
|
380
|
+
// It is chosen by the `uses:` string, so it applies at any nesting depth,
|
|
381
|
+
// including inside someone else's composite action.
|
|
382
|
+
const override = (0, overrides_1.findOverride)(ref);
|
|
383
|
+
if (override) {
|
|
384
|
+
ctx.log(`Using mochi's built-in ${override.name} (${ref.raw})`);
|
|
385
|
+
try {
|
|
386
|
+
const result = await override.run({
|
|
387
|
+
ctx,
|
|
388
|
+
ref,
|
|
389
|
+
inputs: provided,
|
|
390
|
+
stepEnv,
|
|
391
|
+
});
|
|
392
|
+
if (result.outputs && step.id) {
|
|
393
|
+
scope.outputs[step.id] = { ...(scope.outputs[step.id] ?? {}), ...result.outputs };
|
|
394
|
+
}
|
|
395
|
+
return result.ok;
|
|
396
|
+
}
|
|
397
|
+
catch (e) {
|
|
398
|
+
ctx.log(e instanceof Error ? e.message : String(e));
|
|
399
|
+
return false;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
let resolved;
|
|
403
|
+
try {
|
|
404
|
+
resolved = await ctx.actions.resolve(ref, ctx.hostPaths.workspace, (l) => ctx.log(l));
|
|
405
|
+
}
|
|
406
|
+
catch (e) {
|
|
407
|
+
ctx.log(e instanceof Error ? e.message : String(e));
|
|
408
|
+
return false;
|
|
409
|
+
}
|
|
410
|
+
const { values, missing } = (0, actions_1.resolveInputs)(resolved.def, provided);
|
|
411
|
+
if (missing.length) {
|
|
412
|
+
ctx.log(`${ref.raw} requires the input${missing.length > 1 ? 's' : ''}: ${missing.join(', ')}`);
|
|
413
|
+
return false;
|
|
414
|
+
}
|
|
415
|
+
const containerDir = await stageAction(ctx, ref, resolved);
|
|
416
|
+
if (resolved.def.runs.using === 'composite') {
|
|
417
|
+
return runCompositeAction(step, scope, ctx, resolved, containerDir, values, index);
|
|
418
|
+
}
|
|
419
|
+
if (resolved.def.runs.using === 'node') {
|
|
420
|
+
return runNodeAction(step, scope, ctx, ref, resolved, containerDir, values, stepEnv, index);
|
|
421
|
+
}
|
|
422
|
+
ctx.log(`docker actions are not supported yet (uses: ${ref.raw})`);
|
|
423
|
+
return false;
|
|
424
|
+
}
|
|
425
|
+
// Copy the action's files into the job's own directory. The cache holds the
|
|
426
|
+
// pristine download; the container sees a per-job copy, so an action that
|
|
427
|
+
// writes into its own directory cannot poison the next job's copy.
|
|
428
|
+
async function stageAction(ctx, ref, resolved) {
|
|
429
|
+
const name = `${resolved.key}${ref.kind === 'repo' && ref.subpath ? '__' + ref.subpath.replace(/\//g, '--') : ''}`;
|
|
430
|
+
const hostDir = path.join(ctx.hostPaths.actions, name);
|
|
431
|
+
if (!fs.existsSync(hostDir)) {
|
|
432
|
+
fs.mkdirSync(path.dirname(hostDir), { recursive: true });
|
|
433
|
+
fs.cpSync(resolved.dir, hostDir, { recursive: true, dereference: false });
|
|
434
|
+
}
|
|
435
|
+
return `${context_1.ACTIONS_MOUNT}/${name}`;
|
|
436
|
+
}
|
|
437
|
+
async function runNodeAction(step, scope, ctx, ref, resolved, containerDir, inputs, stepEnv, index) {
|
|
438
|
+
const runs = resolved.def.runs;
|
|
439
|
+
if (runs.using !== 'node')
|
|
440
|
+
return false;
|
|
441
|
+
const node = await ctx.externals.nodeFor(ctx.containerId, runs.nodeMajor, (l) => ctx.log(l));
|
|
442
|
+
const runScript = async (script, label, stepId) => {
|
|
443
|
+
const tag = envFileTag(index);
|
|
444
|
+
const files = await makeFileCommands(ctx, tag);
|
|
445
|
+
const env = {
|
|
446
|
+
...stepEnv,
|
|
447
|
+
...fileCommandEnv(files),
|
|
448
|
+
...pathEnv(ctx, stepEnv),
|
|
449
|
+
GITHUB_ACTION: stepId ?? `__action_${index}`,
|
|
450
|
+
GITHUB_ACTION_PATH: containerDir,
|
|
451
|
+
GITHUB_ACTION_REPOSITORY: ref.kind === 'repo' ? `${ref.owner}/${ref.repo}` : '',
|
|
452
|
+
};
|
|
453
|
+
for (const [name, value] of Object.entries(inputs))
|
|
454
|
+
env[(0, actions_1.inputEnvName)(name)] = value;
|
|
455
|
+
const handle = (0, docker_1.execInContainer)(ctx.containerId, [node, `${containerDir}/${script}`], makeLineHandler(ctx, scope, stepId), { workdir: context_1.WORKSPACE, env });
|
|
456
|
+
const code = await handle.done;
|
|
457
|
+
await collectFileCommands(ctx, scope, files, stepId);
|
|
458
|
+
if (code !== 0)
|
|
459
|
+
ctx.log(`${label} completed with exit code ${code}.`);
|
|
460
|
+
return code === 0;
|
|
461
|
+
};
|
|
462
|
+
if (runs.pre) {
|
|
463
|
+
// A pre script runs before the action's main, and its failure is the
|
|
464
|
+
// step's failure. `pre-if` gates it the way `post-if` gates the post
|
|
465
|
+
// script; an unparseable condition runs the script, since skipping setup
|
|
466
|
+
// silently is the worse of the two failures.
|
|
467
|
+
let should = true;
|
|
468
|
+
if (runs.preIf !== undefined) {
|
|
469
|
+
const jobFailed = ctx.status().failed;
|
|
470
|
+
const jobCancelled = ctx.status().cancelled;
|
|
471
|
+
try {
|
|
472
|
+
should = (0, expr_1.evalCondition)(runs.preIf, {
|
|
473
|
+
contexts: (0, context_1.contextsFor)({ spec: ctx.spec, scope, stepEnv: ctx.env, jobFailed, jobCancelled }),
|
|
474
|
+
functions: (0, context_1.statusFunctions)(jobFailed, jobCancelled),
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
catch {
|
|
478
|
+
should = true;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
if (should && !(await runScript(runs.pre, 'The pre step', step.id)))
|
|
482
|
+
return false;
|
|
483
|
+
}
|
|
484
|
+
const ok = await runScript(runs.main, 'Process', step.id);
|
|
485
|
+
if (runs.post) {
|
|
486
|
+
// Post steps run after every step in the job, in reverse order, which is
|
|
487
|
+
// how cleanup (checkout's git config, a cache save) is expected to work.
|
|
488
|
+
const label = step.name ?? step.uses ?? 'action';
|
|
489
|
+
ctx.rt.postHooks.push({
|
|
490
|
+
label: `Post ${label}`,
|
|
491
|
+
condition: runs.postIf,
|
|
492
|
+
run: async () => {
|
|
493
|
+
ctx.log(`▸ Post ${label}`);
|
|
494
|
+
await runScript(runs.post, 'The post step', undefined);
|
|
495
|
+
},
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
return ok;
|
|
499
|
+
}
|
|
500
|
+
async function runCompositeAction(step, scope, ctx, resolved, containerDir, inputs, index) {
|
|
501
|
+
const runs = resolved.def.runs;
|
|
502
|
+
if (runs.using !== 'composite')
|
|
503
|
+
return false;
|
|
504
|
+
const inner = (0, context_1.newScope)(scope.depth + 1);
|
|
505
|
+
inner.inputs = inputs;
|
|
506
|
+
inner.actionPath = containerDir;
|
|
507
|
+
const result = await runSteps(runs.steps, inner, ctx);
|
|
508
|
+
if (result.failed)
|
|
509
|
+
return false;
|
|
510
|
+
// The action's outputs are expressions over its own steps.
|
|
511
|
+
if (step.id && Object.keys(resolved.def.outputs).length) {
|
|
512
|
+
const env = {
|
|
513
|
+
contexts: (0, context_1.contextsFor)({
|
|
514
|
+
spec: ctx.spec,
|
|
515
|
+
scope: inner,
|
|
516
|
+
stepEnv: ctx.env,
|
|
517
|
+
jobFailed: false,
|
|
518
|
+
jobCancelled: false,
|
|
519
|
+
}),
|
|
520
|
+
functions: (0, context_1.statusFunctions)(false, false),
|
|
521
|
+
};
|
|
522
|
+
const outputs = {};
|
|
523
|
+
for (const [name, def] of Object.entries(resolved.def.outputs)) {
|
|
524
|
+
if (def.value === undefined)
|
|
525
|
+
continue;
|
|
526
|
+
try {
|
|
527
|
+
outputs[name] = (0, expr_1.render)(def.value, env);
|
|
528
|
+
}
|
|
529
|
+
catch {
|
|
530
|
+
outputs[name] = '';
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
scope.outputs[step.id] = { ...(scope.outputs[step.id] ?? {}), ...outputs };
|
|
534
|
+
}
|
|
535
|
+
void index;
|
|
536
|
+
return true;
|
|
537
|
+
}
|
|
538
|
+
// ---- post hooks ----
|
|
539
|
+
async function runPostHooks(ctx, jobFailed) {
|
|
540
|
+
const hooks = [...ctx.rt.postHooks].reverse();
|
|
541
|
+
ctx.rt.postHooks.length = 0;
|
|
542
|
+
for (const hook of hooks) {
|
|
543
|
+
if (hook.condition !== undefined) {
|
|
544
|
+
const env = {
|
|
545
|
+
contexts: (0, context_1.contextsFor)({
|
|
546
|
+
spec: ctx.spec,
|
|
547
|
+
scope: (0, context_1.newScope)(0),
|
|
548
|
+
stepEnv: ctx.env,
|
|
549
|
+
jobFailed,
|
|
550
|
+
jobCancelled: ctx.status().cancelled,
|
|
551
|
+
}),
|
|
552
|
+
functions: (0, context_1.statusFunctions)(jobFailed, ctx.status().cancelled),
|
|
553
|
+
};
|
|
554
|
+
let should = true;
|
|
555
|
+
try {
|
|
556
|
+
should = (0, expr_1.evalCondition)(hook.condition, env);
|
|
557
|
+
}
|
|
558
|
+
catch {
|
|
559
|
+
should = true;
|
|
560
|
+
}
|
|
561
|
+
if (!should)
|
|
562
|
+
continue;
|
|
563
|
+
}
|
|
564
|
+
try {
|
|
565
|
+
await hook.run(jobFailed);
|
|
566
|
+
}
|
|
567
|
+
catch (e) {
|
|
568
|
+
ctx.log(`${hook.label} failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
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.startWakeListener = startWakeListener;
|
|
37
|
+
const crypto = __importStar(require("crypto"));
|
|
38
|
+
const http = __importStar(require("http"));
|
|
39
|
+
function secretOk(presented, expected) {
|
|
40
|
+
if (typeof presented !== 'string')
|
|
41
|
+
return false;
|
|
42
|
+
const a = Buffer.from(presented);
|
|
43
|
+
const b = Buffer.from(expected);
|
|
44
|
+
// Length is not a secret worth padding for, and comparing buffers of
|
|
45
|
+
// different lengths throws.
|
|
46
|
+
return a.length === b.length && crypto.timingSafeEqual(a, b);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Listen for wake requests on `port`, calling `onWake` for each authorized one.
|
|
50
|
+
*
|
|
51
|
+
* Resolves once the socket is listening, so that a caller can report the port
|
|
52
|
+
* it actually got, and rejects if the port cannot be bound: a runner whose
|
|
53
|
+
* wake endpoint is not up is one nothing can start again, which is worth
|
|
54
|
+
* failing on rather than discovering later as a run that never began.
|
|
55
|
+
*/
|
|
56
|
+
function startWakeListener(opts) {
|
|
57
|
+
const server = http.createServer((req, res) => {
|
|
58
|
+
if (!secretOk(req.headers['x-mochi-wake'], opts.secret)) {
|
|
59
|
+
res.writeHead(401).end();
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
opts.onWake();
|
|
63
|
+
res.writeHead(204).end();
|
|
64
|
+
});
|
|
65
|
+
// A wake request carries nothing, so a client that opens a connection and
|
|
66
|
+
// says nothing is either a mistake or a nuisance; either way it should not
|
|
67
|
+
// hold a socket open.
|
|
68
|
+
server.headersTimeout = 5000;
|
|
69
|
+
server.requestTimeout = 10000;
|
|
70
|
+
return new Promise((resolve, reject) => {
|
|
71
|
+
server.once('error', reject);
|
|
72
|
+
server.listen(opts.port, opts.host ?? '0.0.0.0', () => {
|
|
73
|
+
server.removeListener('error', reject);
|
|
74
|
+
const address = server.address();
|
|
75
|
+
resolve({
|
|
76
|
+
port: typeof address === 'object' && address !== null ? address.port : opts.port,
|
|
77
|
+
close: () => new Promise((done) => {
|
|
78
|
+
server.close(() => done());
|
|
79
|
+
server.closeAllConnections();
|
|
80
|
+
}),
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
}
|