@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.
- package/LICENSE +21 -0
- package/README.md +230 -2
- package/dist/cli.js +545 -0
- package/dist/config.js +190 -0
- package/dist/index.js +23 -0
- package/dist/init.js +45 -0
- package/dist/inventory.js +212 -0
- package/dist/parser/pipeline-contract-parser.js +131 -0
- package/dist/parser/pipeline-contract-validator.js +448 -0
- package/dist/parser/validator.js +232 -0
- package/dist/parser/workflow-parser.js +122 -0
- package/dist/report.js +75 -0
- package/dist/runner/action-fetcher.js +431 -0
- package/dist/runner/actions.js +495 -0
- package/dist/runner/builtin-actions.js +484 -0
- package/dist/runner/checkout.js +142 -0
- package/dist/runner/container-runtime.js +374 -0
- package/dist/runner/docker-action.js +179 -0
- package/dist/runner/dry-run.js +142 -0
- package/dist/runner/executor.js +554 -0
- package/dist/runner/expression.js +676 -0
- package/dist/runner/github-context.js +235 -0
- package/dist/runner/hash-files.js +123 -0
- package/dist/runner/job-container.js +322 -0
- package/dist/runner/job-plan.js +138 -0
- package/dist/runner/job-runtime.js +338 -0
- package/dist/runner/node-action.js +278 -0
- package/dist/runner/runner-env.js +303 -0
- package/dist/runner/shell.js +61 -0
- package/dist/runner/steps.js +250 -0
- package/dist/types/index.js +10 -0
- package/dist/utils/ansi.js +47 -0
- package/dist/utils/key-value.js +33 -0
- package/dist/utils/logger.js +84 -0
- package/dist/utils/repository.js +51 -0
- package/package.json +76 -4
|
@@ -0,0 +1,278 @@
|
|
|
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.executeNodeAction = executeNodeAction;
|
|
37
|
+
exports.inputEnvName = inputEnvName;
|
|
38
|
+
exports.runPreIf = runPreIf;
|
|
39
|
+
const child_process_1 = require("child_process");
|
|
40
|
+
const util_1 = require("util");
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
const logger_1 = require("../utils/logger");
|
|
43
|
+
const expression_1 = require("./expression");
|
|
44
|
+
const container_runtime_1 = require("./container-runtime");
|
|
45
|
+
const runner_env_1 = require("./runner-env");
|
|
46
|
+
const job_runtime_1 = require("./job-runtime");
|
|
47
|
+
const job_container_1 = require("./job-container");
|
|
48
|
+
const execFileAsync = (0, util_1.promisify)(child_process_1.execFile);
|
|
49
|
+
/**
|
|
50
|
+
* Execute a JavaScript action (`runs.using: node*`): `pre` (if any) and
|
|
51
|
+
* `main` now, with `post` queued on the job runtime to run at job end.
|
|
52
|
+
* Scripts run with the CLI's own Node binary, in the workspace, with the
|
|
53
|
+
* runner's default env, the caller's env, `INPUT_*`, `STATE_*` and the
|
|
54
|
+
* file-command variables. Note: GitHub runs every `pre` at job start;
|
|
55
|
+
* here it runs immediately before its own `main`.
|
|
56
|
+
*/
|
|
57
|
+
async function executeNodeAction(action, actionPath, step, actionInputs, callerCtx, options, runtime, startTime, phase = 'main') {
|
|
58
|
+
if (phase === 'main') {
|
|
59
|
+
logger_1.logger.info(`Executing JavaScript action: ${action.name} (${action.runs.using})`);
|
|
60
|
+
}
|
|
61
|
+
const { main, pre, post } = action.runs;
|
|
62
|
+
if (!main) {
|
|
63
|
+
return {
|
|
64
|
+
step,
|
|
65
|
+
success: false,
|
|
66
|
+
error: 'No main script specified for JavaScript action',
|
|
67
|
+
duration: Date.now() - startTime
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
const actionDir = path.dirname(actionPath);
|
|
71
|
+
const inputEnv = {};
|
|
72
|
+
for (const [key, value] of Object.entries(actionInputs)) {
|
|
73
|
+
inputEnv[inputEnvName(key)] = value;
|
|
74
|
+
}
|
|
75
|
+
// In a job container, run the action there with the image's own `node`,
|
|
76
|
+
// from a copy of the action in the mounted exchange dir.
|
|
77
|
+
const container = runtime.container;
|
|
78
|
+
let containerActionDir;
|
|
79
|
+
if (container) {
|
|
80
|
+
if (container.node === undefined) {
|
|
81
|
+
const probe = await container.runtime.exec(container.id, {
|
|
82
|
+
command: `command -v node && printf '%s\\n' "$PATH"`
|
|
83
|
+
});
|
|
84
|
+
const [nodePath, basePath] = probe.stdout.trim().split('\n');
|
|
85
|
+
container.node =
|
|
86
|
+
probe.success && nodePath
|
|
87
|
+
? { path: nodePath.trim(), basePath: (basePath || '').trim() }
|
|
88
|
+
: null;
|
|
89
|
+
}
|
|
90
|
+
if (!container.node) {
|
|
91
|
+
const error = `JavaScript action "${step.uses}" needs \`node\` in the job container image (${container.image}). Use an image that includes Node.js (e.g. --container-image node:20, a runner-images mapping, or ${job_container_1.DEFAULT_CONTAINER_IMAGE}), or run on the host with --container-runtime host.`;
|
|
92
|
+
logger_1.logger.error(error);
|
|
93
|
+
return { step, success: false, error, duration: Date.now() - startTime };
|
|
94
|
+
}
|
|
95
|
+
containerActionDir = (0, job_runtime_1.actionDirInContainer)(container, actionDir);
|
|
96
|
+
}
|
|
97
|
+
let state = {};
|
|
98
|
+
let output = '';
|
|
99
|
+
const limit = (0, job_runtime_1.effectiveTimeout)(runtime, options.containerTimeoutMs);
|
|
100
|
+
/** Run one script (pre/main/post) on the host or in the job container. */
|
|
101
|
+
const invokeScript = async (script, files, containerDir, stateEnv) => {
|
|
102
|
+
if (container?.node && containerActionDir && containerDir) {
|
|
103
|
+
// Exec node directly (no shell), so hyphenated INPUT_* names survive.
|
|
104
|
+
const scriptPath = path.posix.join(containerActionDir, script);
|
|
105
|
+
const result = await container.runtime.exec(container.id, {
|
|
106
|
+
args: [container.node.path, scriptPath],
|
|
107
|
+
workingDir: container_runtime_1.CONTAINER_WORKDIR,
|
|
108
|
+
env: {
|
|
109
|
+
...(0, job_runtime_1.containerRunnerEnv)(runtime),
|
|
110
|
+
...runtime.actionEnv,
|
|
111
|
+
...callerCtx.env,
|
|
112
|
+
...inputEnv,
|
|
113
|
+
...stateEnv,
|
|
114
|
+
GITHUB_ACTION_PATH: containerActionDir,
|
|
115
|
+
PATH: [...runtime.path, container.node.basePath]
|
|
116
|
+
.filter(Boolean)
|
|
117
|
+
.join(':'),
|
|
118
|
+
...(0, job_runtime_1.fileCommandEnv)(files, containerDir),
|
|
119
|
+
SECURECI_STEP: path.basename(files.dir)
|
|
120
|
+
},
|
|
121
|
+
timeoutMs: limit.ms
|
|
122
|
+
});
|
|
123
|
+
if (result.timedOut) {
|
|
124
|
+
await (0, job_runtime_1.killStepProcesses)(container, path.basename(files.dir));
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
success: result.success,
|
|
128
|
+
stdout: result.stdout,
|
|
129
|
+
stderr: result.stderr,
|
|
130
|
+
error: result.success
|
|
131
|
+
? undefined
|
|
132
|
+
: result.timedOut
|
|
133
|
+
? limit.message
|
|
134
|
+
: `node exited with code ${result.exitCode}`
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
try {
|
|
138
|
+
const { stdout, stderr } = await execFileAsync(process.execPath, [path.join(actionDir, script)], {
|
|
139
|
+
cwd: process.cwd(),
|
|
140
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
141
|
+
timeout: limit.ms,
|
|
142
|
+
killSignal: 'SIGKILL',
|
|
143
|
+
env: {
|
|
144
|
+
...(0, runner_env_1.hostBaseEnv)(),
|
|
145
|
+
...runtime.runnerEnv,
|
|
146
|
+
...runtime.actionEnv,
|
|
147
|
+
...callerCtx.env,
|
|
148
|
+
...inputEnv,
|
|
149
|
+
...stateEnv,
|
|
150
|
+
GITHUB_ACTION_PATH: actionDir,
|
|
151
|
+
PATH: (0, runner_env_1.prependPath)(runtime.path, callerCtx.env.PATH ?? process.env.PATH),
|
|
152
|
+
...(0, job_runtime_1.fileCommandEnv)(files)
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
return { success: true, stdout, stderr };
|
|
156
|
+
}
|
|
157
|
+
catch (execError) {
|
|
158
|
+
const error = execError;
|
|
159
|
+
return {
|
|
160
|
+
success: false,
|
|
161
|
+
stdout: error.stdout || '',
|
|
162
|
+
stderr: error.stderr || '',
|
|
163
|
+
error: error.killed && limit.ms ? limit.message : error.message
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
const runScript = async (script) => {
|
|
168
|
+
const { files, containerDir } = container
|
|
169
|
+
? (0, job_runtime_1.createContainerStepFiles)(container)
|
|
170
|
+
: { files: (0, runner_env_1.createStepFiles)(), containerDir: undefined };
|
|
171
|
+
const stateEnv = {};
|
|
172
|
+
for (const [key, value] of Object.entries(state)) {
|
|
173
|
+
stateEnv[`STATE_${key}`] = value;
|
|
174
|
+
}
|
|
175
|
+
try {
|
|
176
|
+
const result = await invokeScript(script, files, containerDir, stateEnv);
|
|
177
|
+
output += result.stdout;
|
|
178
|
+
const { outputs, state: savedState } = (0, job_runtime_1.finishStepOutput)(result.stdout, files, runtime);
|
|
179
|
+
state = { ...state, ...savedState };
|
|
180
|
+
if (result.success) {
|
|
181
|
+
if (result.stderr) {
|
|
182
|
+
logger_1.logger.warn(result.stderr.trim());
|
|
183
|
+
}
|
|
184
|
+
return { success: true, outputs };
|
|
185
|
+
}
|
|
186
|
+
logger_1.logger.error(`JavaScript action failed: ${result.error}`);
|
|
187
|
+
if (result.stderr) {
|
|
188
|
+
logger_1.logger.error(result.stderr.trim());
|
|
189
|
+
}
|
|
190
|
+
return { success: false, outputs, error: result.error };
|
|
191
|
+
}
|
|
192
|
+
finally {
|
|
193
|
+
(0, runner_env_1.cleanupStepFiles)(files);
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
// `pre` already ran at job start (see runPreSteps): continue from its state.
|
|
197
|
+
const preState = runtime.preState.get(step);
|
|
198
|
+
if (phase === 'pre' && pre) {
|
|
199
|
+
const preStep = { name: `Pre ${step.name || step.uses}` };
|
|
200
|
+
const preResult = await runScript(pre);
|
|
201
|
+
runtime.preState.set(step, state);
|
|
202
|
+
return {
|
|
203
|
+
step: preStep,
|
|
204
|
+
success: preResult.success,
|
|
205
|
+
error: preResult.error,
|
|
206
|
+
output,
|
|
207
|
+
duration: Date.now() - startTime
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
if (preState) {
|
|
211
|
+
state = { ...preState };
|
|
212
|
+
}
|
|
213
|
+
else if (pre && runPreIf(action, step, callerCtx)) {
|
|
214
|
+
const preResult = await runScript(pre);
|
|
215
|
+
if (!preResult.success) {
|
|
216
|
+
return {
|
|
217
|
+
step,
|
|
218
|
+
success: false,
|
|
219
|
+
error: preResult.error,
|
|
220
|
+
output,
|
|
221
|
+
duration: Date.now() - startTime
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
if (post) {
|
|
226
|
+
const postStep = {
|
|
227
|
+
name: `Post ${step.name || step.uses}`,
|
|
228
|
+
uses: step.uses
|
|
229
|
+
};
|
|
230
|
+
runtime.postSteps.push({
|
|
231
|
+
step: postStep,
|
|
232
|
+
condition: action.runs['post-if'] ?? 'always()',
|
|
233
|
+
run: async () => {
|
|
234
|
+
const postStartTime = Date.now();
|
|
235
|
+
output = '';
|
|
236
|
+
const postResult = await runScript(post);
|
|
237
|
+
return {
|
|
238
|
+
step: postStep,
|
|
239
|
+
success: postResult.success,
|
|
240
|
+
error: postResult.error,
|
|
241
|
+
output,
|
|
242
|
+
outputs: postResult.outputs,
|
|
243
|
+
duration: Date.now() - postStartTime
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
const mainResult = await runScript(main);
|
|
249
|
+
return {
|
|
250
|
+
step,
|
|
251
|
+
success: mainResult.success,
|
|
252
|
+
error: mainResult.error,
|
|
253
|
+
output,
|
|
254
|
+
outputs: mainResult.outputs,
|
|
255
|
+
duration: Date.now() - startTime
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* The env var an action input is exposed as. Matches the runner and
|
|
260
|
+
* `@actions/core`'s getInput(): spaces become `_`, hyphens are kept
|
|
261
|
+
* (`who-to-greet` -> `INPUT_WHO-TO-GREET`).
|
|
262
|
+
*/
|
|
263
|
+
function inputEnvName(name) {
|
|
264
|
+
return `INPUT_${name.replace(/ /g, '_').toUpperCase()}`;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Whether a JavaScript action's `pre` should run (`pre-if`, default
|
|
268
|
+
* always()). Throws a step error if `pre-if` can't be evaluated.
|
|
269
|
+
*/
|
|
270
|
+
function runPreIf(action, step, ctx) {
|
|
271
|
+
try {
|
|
272
|
+
return (0, expression_1.evaluateCondition)(action.runs['pre-if'] ?? 'always()', ctx);
|
|
273
|
+
}
|
|
274
|
+
catch (error) {
|
|
275
|
+
throw new Error((0, expression_1.ifEvaluationError)(`step 'Pre ${step.name || step.uses}'`, error));
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
//# sourceMappingURL=node-action.js.map
|
|
@@ -0,0 +1,303 @@
|
|
|
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.parseKeyValueFile = parseKeyValueFile;
|
|
37
|
+
exports.createStepFiles = createStepFiles;
|
|
38
|
+
exports.readStepFiles = readStepFiles;
|
|
39
|
+
exports.cleanupStepFiles = cleanupStepFiles;
|
|
40
|
+
exports.processWorkflowCommands = processWorkflowCommands;
|
|
41
|
+
exports.buildRunnerEnv = buildRunnerEnv;
|
|
42
|
+
exports.prependPath = prependPath;
|
|
43
|
+
exports.hostBaseEnv = hostBaseEnv;
|
|
44
|
+
const fs = __importStar(require("fs"));
|
|
45
|
+
const os = __importStar(require("os"));
|
|
46
|
+
const path = __importStar(require("path"));
|
|
47
|
+
/**
|
|
48
|
+
* Parse a runner "file command" file (`$GITHUB_OUTPUT`, `$GITHUB_ENV`,
|
|
49
|
+
* `$GITHUB_STATE`), supporting both the simple `name=value` form and the
|
|
50
|
+
* multiline heredoc form: `name<<DELIMITER` ... `DELIMITER`.
|
|
51
|
+
*/
|
|
52
|
+
function parseKeyValueFile(filePath) {
|
|
53
|
+
const values = {};
|
|
54
|
+
let content;
|
|
55
|
+
try {
|
|
56
|
+
content = fs.readFileSync(filePath, 'utf-8');
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return values;
|
|
60
|
+
}
|
|
61
|
+
const lines = content.split(/\r?\n/);
|
|
62
|
+
let i = 0;
|
|
63
|
+
while (i < lines.length) {
|
|
64
|
+
const line = lines[i];
|
|
65
|
+
if (!line) {
|
|
66
|
+
i++;
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
const heredocMatch = line.match(/^([^=<]+)<<(.+)$/);
|
|
70
|
+
if (heredocMatch) {
|
|
71
|
+
const [, name, delimiter] = heredocMatch;
|
|
72
|
+
const valueLines = [];
|
|
73
|
+
i++;
|
|
74
|
+
while (i < lines.length && lines[i] !== delimiter) {
|
|
75
|
+
valueLines.push(lines[i]);
|
|
76
|
+
i++;
|
|
77
|
+
}
|
|
78
|
+
values[name.trim()] = valueLines.join('\n');
|
|
79
|
+
i++; // Skip the closing delimiter line
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
const eqIndex = line.indexOf('=');
|
|
83
|
+
if (eqIndex > -1) {
|
|
84
|
+
values[line.slice(0, eqIndex).trim()] = line.slice(eqIndex + 1);
|
|
85
|
+
}
|
|
86
|
+
i++;
|
|
87
|
+
}
|
|
88
|
+
return values;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Create empty `GITHUB_OUTPUT` / `GITHUB_ENV` / `GITHUB_PATH` /
|
|
92
|
+
* `GITHUB_STATE` / `GITHUB_STEP_SUMMARY` files for one step, in `dir` (e.g.
|
|
93
|
+
* a container exchange dir) or a fresh temp directory.
|
|
94
|
+
*/
|
|
95
|
+
function createStepFiles(dir) {
|
|
96
|
+
const baseDir = dir ?? fs.mkdtempSync(path.join(os.tmpdir(), 'secureci-step-'));
|
|
97
|
+
const files = {
|
|
98
|
+
dir: baseDir,
|
|
99
|
+
output: path.join(baseDir, 'output'),
|
|
100
|
+
env: path.join(baseDir, 'env'),
|
|
101
|
+
path: path.join(baseDir, 'path'),
|
|
102
|
+
state: path.join(baseDir, 'state'),
|
|
103
|
+
summary: path.join(baseDir, 'summary')
|
|
104
|
+
};
|
|
105
|
+
for (const file of [
|
|
106
|
+
files.output,
|
|
107
|
+
files.env,
|
|
108
|
+
files.path,
|
|
109
|
+
files.state,
|
|
110
|
+
files.summary
|
|
111
|
+
]) {
|
|
112
|
+
fs.writeFileSync(file, '');
|
|
113
|
+
}
|
|
114
|
+
return files;
|
|
115
|
+
}
|
|
116
|
+
function readStepFiles(files) {
|
|
117
|
+
let pathLines = [];
|
|
118
|
+
try {
|
|
119
|
+
pathLines = fs
|
|
120
|
+
.readFileSync(files.path, 'utf-8')
|
|
121
|
+
.split(/\r?\n/)
|
|
122
|
+
.map((line) => line.trim())
|
|
123
|
+
.filter(Boolean);
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
// Missing file: no PATH additions
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
outputs: parseKeyValueFile(files.output),
|
|
130
|
+
env: parseKeyValueFile(files.env),
|
|
131
|
+
path: pathLines,
|
|
132
|
+
state: parseKeyValueFile(files.state),
|
|
133
|
+
summary: (() => {
|
|
134
|
+
try {
|
|
135
|
+
return fs.readFileSync(files.summary, 'utf-8');
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
return '';
|
|
139
|
+
}
|
|
140
|
+
})()
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
function cleanupStepFiles(files) {
|
|
144
|
+
fs.rmSync(files.dir, { recursive: true, force: true });
|
|
145
|
+
}
|
|
146
|
+
function unescapeData(value) {
|
|
147
|
+
return value
|
|
148
|
+
.replace(/%0D/gi, '\r')
|
|
149
|
+
.replace(/%0A/gi, '\n')
|
|
150
|
+
.replace(/%25/g, '%');
|
|
151
|
+
}
|
|
152
|
+
function unescapeProperty(value) {
|
|
153
|
+
return unescapeData(value).replace(/%3A/gi, ':').replace(/%2C/gi, ',');
|
|
154
|
+
}
|
|
155
|
+
function parseProperties(raw) {
|
|
156
|
+
const props = {};
|
|
157
|
+
for (const pair of (raw || '').split(',')) {
|
|
158
|
+
const eq = pair.indexOf('=');
|
|
159
|
+
if (eq > 0) {
|
|
160
|
+
props[pair.slice(0, eq).trim()] = unescapeProperty(pair.slice(eq + 1));
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return props;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Interpret GitHub Actions workflow commands (`::add-mask::`,
|
|
167
|
+
* `::set-output`, `::save-state`, `::warning::`, `::group::`, ...) written
|
|
168
|
+
* to a step's stdout, in both the `::cmd::` and legacy `##[cmd]` syntax.
|
|
169
|
+
* Problem matchers and `::echo::` have no local effect and are hidden;
|
|
170
|
+
* nothing is interpreted between `::stop-commands::<token>` and
|
|
171
|
+
* `::<token>::`. Unknown commands are left in the text.
|
|
172
|
+
* https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions
|
|
173
|
+
*/
|
|
174
|
+
function processWorkflowCommands(stdout) {
|
|
175
|
+
const result = {
|
|
176
|
+
text: '',
|
|
177
|
+
masks: [],
|
|
178
|
+
outputs: {},
|
|
179
|
+
state: {},
|
|
180
|
+
annotations: []
|
|
181
|
+
};
|
|
182
|
+
const kept = [];
|
|
183
|
+
let stopToken;
|
|
184
|
+
for (const line of stdout.split(/\r?\n/)) {
|
|
185
|
+
if (stopToken !== undefined) {
|
|
186
|
+
if (line === `::${stopToken}::`)
|
|
187
|
+
stopToken = undefined;
|
|
188
|
+
else
|
|
189
|
+
kept.push(line);
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
const match = line.match(/^::([a-zA-Z-]+)(?:\s+([^:]*))?::(.*)$/) ??
|
|
193
|
+
line.match(/^##\[([a-zA-Z-]+)(?:\s+([^\]]*))?\](.*)$/);
|
|
194
|
+
if (!match) {
|
|
195
|
+
kept.push(line);
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
const [, command, rawProps, rawData] = match;
|
|
199
|
+
const data = unescapeData(rawData);
|
|
200
|
+
const props = parseProperties(rawProps);
|
|
201
|
+
switch (command) {
|
|
202
|
+
case 'add-mask':
|
|
203
|
+
if (data)
|
|
204
|
+
result.masks.push(data);
|
|
205
|
+
break;
|
|
206
|
+
case 'set-output':
|
|
207
|
+
if (props.name)
|
|
208
|
+
result.outputs[props.name] = data;
|
|
209
|
+
break;
|
|
210
|
+
case 'save-state':
|
|
211
|
+
if (props.name)
|
|
212
|
+
result.state[props.name] = data;
|
|
213
|
+
break;
|
|
214
|
+
case 'warning':
|
|
215
|
+
case 'error':
|
|
216
|
+
case 'notice':
|
|
217
|
+
case 'debug':
|
|
218
|
+
case 'group':
|
|
219
|
+
result.annotations.push({ level: command, message: data });
|
|
220
|
+
break;
|
|
221
|
+
case 'stop-commands':
|
|
222
|
+
if (data)
|
|
223
|
+
stopToken = data;
|
|
224
|
+
break;
|
|
225
|
+
case 'endgroup':
|
|
226
|
+
case 'add-matcher':
|
|
227
|
+
case 'remove-matcher':
|
|
228
|
+
case 'echo':
|
|
229
|
+
break;
|
|
230
|
+
default:
|
|
231
|
+
kept.push(line);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
result.text = kept.join('\n');
|
|
235
|
+
return result;
|
|
236
|
+
}
|
|
237
|
+
/** A context value as an env var string ('' when unset). */
|
|
238
|
+
function text(value) {
|
|
239
|
+
return value === undefined || value === null ? '' : String(value);
|
|
240
|
+
}
|
|
241
|
+
function runnerOs() {
|
|
242
|
+
switch (process.platform) {
|
|
243
|
+
case 'darwin':
|
|
244
|
+
return 'macOS';
|
|
245
|
+
case 'win32':
|
|
246
|
+
return 'Windows';
|
|
247
|
+
default:
|
|
248
|
+
return 'Linux';
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* The default environment variables GitHub's runner exports to every step,
|
|
253
|
+
* derived from the (local, best-effort) `github` context.
|
|
254
|
+
* https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables
|
|
255
|
+
*/
|
|
256
|
+
function buildRunnerEnv(github, options) {
|
|
257
|
+
const env = {
|
|
258
|
+
CI: 'true',
|
|
259
|
+
GITHUB_ACTIONS: 'true',
|
|
260
|
+
GITHUB_WORKFLOW: text(github.workflow),
|
|
261
|
+
GITHUB_JOB: text(github.job),
|
|
262
|
+
GITHUB_SHA: text(github.sha),
|
|
263
|
+
GITHUB_REF: text(github.ref),
|
|
264
|
+
GITHUB_REF_NAME: text(github.ref_name),
|
|
265
|
+
GITHUB_EVENT_NAME: text(github.event_name),
|
|
266
|
+
GITHUB_ACTOR: text(github.actor),
|
|
267
|
+
GITHUB_REPOSITORY: text(github.repository),
|
|
268
|
+
GITHUB_REPOSITORY_OWNER: text(github.repository_owner),
|
|
269
|
+
GITHUB_RUN_ID: text(github.run_id),
|
|
270
|
+
GITHUB_RUN_NUMBER: text(github.run_number),
|
|
271
|
+
GITHUB_RUN_ATTEMPT: text(github.run_attempt),
|
|
272
|
+
GITHUB_SERVER_URL: text(github.server_url),
|
|
273
|
+
GITHUB_API_URL: text(github.api_url),
|
|
274
|
+
GITHUB_WORKSPACE: options.workspace,
|
|
275
|
+
GITHUB_EVENT_PATH: options.eventPath,
|
|
276
|
+
RUNNER_OS: runnerOs(),
|
|
277
|
+
RUNNER_TEMP: options.tempDir,
|
|
278
|
+
RUNNER_TOOL_CACHE: options.toolCache
|
|
279
|
+
};
|
|
280
|
+
return env;
|
|
281
|
+
}
|
|
282
|
+
/** Prepend `$GITHUB_PATH` additions (most recent first) to a PATH value. */
|
|
283
|
+
function prependPath(additions, basePath) {
|
|
284
|
+
return [...additions, ...(basePath ? [basePath] : [])].join(path.delimiter);
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Host variables that `run:` steps and actions on the host still get: what
|
|
288
|
+
* tools need to work (PATH, HOME, locale, temp dirs, proxy and CA settings,
|
|
289
|
+
* the container client's config, and Windows system variables). Everything
|
|
290
|
+
* else in the user's shell (tokens, cloud credentials...) stays out; pass
|
|
291
|
+
* what a workflow needs with `--env`.
|
|
292
|
+
*/
|
|
293
|
+
const HOST_ENV_ALLOWLIST = /^(PATH|HOME|USER|LOGNAME|SHELL|TERM|TZ|LANG|LANGUAGE|LC_[A-Z_]+|TMPDIR|TEMP|TMP|(HTTPS?|FTP|ALL|NO)_PROXY|SSL_CERT_(FILE|DIR)|NODE_EXTRA_CA_CERTS|REQUESTS_CA_BUNDLE|CURL_CA_BUNDLE|DOCKER_(HOST|CONTEXT|CONFIG|CERT_PATH|TLS_VERIFY)|XDG_RUNTIME_DIR|SYSTEMROOT|SYSTEMDRIVE|WINDIR|COMSPEC|PATHEXT|USERPROFILE|APPDATA|LOCALAPPDATA|PROGRAMDATA|PROGRAMFILES(\(X86\))?|COMMONPROGRAMFILES(\(X86\))?)$/i;
|
|
294
|
+
/** The allowlisted part of the host environment (see HOST_ENV_ALLOWLIST). */
|
|
295
|
+
function hostBaseEnv(env = process.env) {
|
|
296
|
+
const base = {};
|
|
297
|
+
for (const [key, value] of Object.entries(env)) {
|
|
298
|
+
if (value !== undefined && HOST_ENV_ALLOWLIST.test(key))
|
|
299
|
+
base[key] = value;
|
|
300
|
+
}
|
|
301
|
+
return base;
|
|
302
|
+
}
|
|
303
|
+
//# sourceMappingURL=runner-env.js.map
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* `shell:` handling for `run:` steps, mirroring the GitHub runner: the
|
|
4
|
+
* script is written to a file and passed as `{0}` to a command template.
|
|
5
|
+
* https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsshell
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.resolveShell = resolveShell;
|
|
9
|
+
exports.shellCommandLine = shellCommandLine;
|
|
10
|
+
exports.splitArgs = splitArgs;
|
|
11
|
+
const BASH = 'bash --noprofile --norc -eo pipefail {0}';
|
|
12
|
+
const SH = 'sh -e {0}';
|
|
13
|
+
const KNOWN_SHELLS = {
|
|
14
|
+
bash: { template: BASH, extension: '.sh' },
|
|
15
|
+
sh: { template: SH, extension: '.sh' },
|
|
16
|
+
python: { template: 'python {0}', extension: '.py' },
|
|
17
|
+
pwsh: { template: `pwsh -command ". '{0}'"`, extension: '.ps1' },
|
|
18
|
+
powershell: {
|
|
19
|
+
template: `powershell -command ". '{0}'"`,
|
|
20
|
+
extension: '.ps1'
|
|
21
|
+
},
|
|
22
|
+
cmd: {
|
|
23
|
+
template: '%ComSpec% /D /E:ON /V:OFF /S /C "CALL "{0}""',
|
|
24
|
+
extension: '.cmd'
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Resolve a step's `shell:` (after `defaults.run.shell`) to a command
|
|
29
|
+
* template. With no shell, GitHub uses bash with `-eo pipefail`, or
|
|
30
|
+
* `sh -e` where bash isn't installed.
|
|
31
|
+
*/
|
|
32
|
+
function resolveShell(shell, options) {
|
|
33
|
+
if (!shell) {
|
|
34
|
+
return options.bashAvailable ? KNOWN_SHELLS.bash : KNOWN_SHELLS.sh;
|
|
35
|
+
}
|
|
36
|
+
const known = KNOWN_SHELLS[shell];
|
|
37
|
+
if (known)
|
|
38
|
+
return known;
|
|
39
|
+
if (shell.includes('{0}'))
|
|
40
|
+
return { template: shell, extension: '' };
|
|
41
|
+
return { template: `${shell} {0}`, extension: '' };
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Build the command line for a template and script path. `{0}` is replaced
|
|
45
|
+
* with a single-quoted path, unless the template already quotes it.
|
|
46
|
+
*/
|
|
47
|
+
function shellCommandLine(template, scriptPath) {
|
|
48
|
+
if (/'\{0\}'|"\{0\}"/.test(template)) {
|
|
49
|
+
return template.replace('{0}', scriptPath);
|
|
50
|
+
}
|
|
51
|
+
const quoted = `'${scriptPath.replace(/'/g, "'\\''")}'`;
|
|
52
|
+
return template.replace('{0}', quoted);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Split an argument string (`with.args`, `services.<id>.options`) into
|
|
56
|
+
* argv, honouring simple quoting.
|
|
57
|
+
*/
|
|
58
|
+
function splitArgs(args) {
|
|
59
|
+
return (args.match(/"[^"]*"|'[^']*'|\S+/g) || []).map((arg) => /^(["']).*\1$/.test(arg) ? arg.slice(1, -1) : arg);
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=shell.js.map
|