@secureci/cli 1.0.1 → 1.2.0
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/README.md +26 -11
- package/dist/advisories.js +72 -12
- package/dist/cli.js +53 -1
- package/dist/config.js +24 -1
- package/dist/org-scan-script.js +6 -0
- package/dist/org-scan.js +88 -0
- package/dist/parser/pipeline-contract-validator.js +152 -24
- package/dist/runner/action-fetcher.js +6 -1
- package/dist/runner/actions.js +10 -8
- package/dist/runner/checkout.js +225 -9
- package/dist/runner/container-policy.js +32 -0
- package/dist/runner/container-runtime.js +74 -1
- package/dist/runner/docker-action.js +1 -1
- package/dist/runner/executor.js +78 -8
- package/dist/runner/expression.js +165 -3
- package/dist/runner/job-container.js +147 -14
- package/dist/runner/job-runtime.js +4 -3
- package/dist/runner/node-action.js +1 -1
- package/dist/runner/runner-env.js +20 -0
- package/dist/runner/scheduler.js +84 -2
- package/dist/runner/steps.js +2 -2
- package/package.json +1 -1
|
@@ -38,6 +38,8 @@ exports.installContainerCleanup = installContainerCleanup;
|
|
|
38
38
|
exports.noRuntimeMessage = noRuntimeMessage;
|
|
39
39
|
exports.startJobContainer = startJobContainer;
|
|
40
40
|
exports.runnerImage = runnerImage;
|
|
41
|
+
exports.clearIsolatedWorkspace = clearIsolatedWorkspace;
|
|
42
|
+
exports.removeInContainer = removeInContainer;
|
|
41
43
|
exports.stopJobContainer = stopJobContainer;
|
|
42
44
|
exports.startJobServices = startJobServices;
|
|
43
45
|
exports.stopJobServices = stopJobServices;
|
|
@@ -50,6 +52,13 @@ const expression_1 = require("./expression");
|
|
|
50
52
|
const container_runtime_1 = require("./container-runtime");
|
|
51
53
|
const shell_1 = require("./shell");
|
|
52
54
|
const container_policy_1 = require("./container-policy");
|
|
55
|
+
/**
|
|
56
|
+
* `jobs.<id>.container.options` the CLI sets on the job container itself:
|
|
57
|
+
* it idles with its own entrypoint and steps run in the mounted workspace.
|
|
58
|
+
*/
|
|
59
|
+
const JOB_CONTAINER_RESERVED = new Set(['--entrypoint', '--workdir', '-w']);
|
|
60
|
+
/** Also reserved when `--container-memory-limit` caps the job container. */
|
|
61
|
+
const MEMORY_OPTIONS = ['--memory', '-m', '--memory-swap'];
|
|
53
62
|
/**
|
|
54
63
|
* Image for jobs whose `runs-on` has no image mapping. Runner-like (Node.js,
|
|
55
64
|
* git, curl and common tools), as used by nektos/act.
|
|
@@ -123,14 +132,6 @@ async function startJobContainer(job, ctx, options, runtime, runDir) {
|
|
|
123
132
|
logger_1.logger.error(noRuntimeMessage(preference));
|
|
124
133
|
return false;
|
|
125
134
|
}
|
|
126
|
-
if (spec &&
|
|
127
|
-
'image' in spec &&
|
|
128
|
-
(('options' in spec && spec.options) ||
|
|
129
|
-
('volumes' in spec && spec.volumes) ||
|
|
130
|
-
('ports' in spec && spec.ports) ||
|
|
131
|
-
('credentials' in spec && spec.credentials))) {
|
|
132
|
-
logger_1.logger.warn('jobs.<id>.container: only image and env are supported; ignoring options/volumes/ports/credentials.');
|
|
133
|
-
}
|
|
134
135
|
// `container: ${{ inputs.image || null }}` that comes out empty means no
|
|
135
136
|
// container image, as on GitHub: use the image for runs-on.
|
|
136
137
|
const specImage = spec?.image
|
|
@@ -140,19 +141,30 @@ async function startJobContainer(job, ctx, options, runtime, runDir) {
|
|
|
140
141
|
options.containerImage ||
|
|
141
142
|
runnerImage(job?.['runs-on'], ctx, options);
|
|
142
143
|
const containerEnv = (0, expression_1.substituteRecord)(specImage && spec && 'env' in spec ? spec.env : undefined, ctx);
|
|
144
|
+
const extras = specImage && spec
|
|
145
|
+
? jobContainerExtras(spec, ctx, options, runtime.workspace)
|
|
146
|
+
: {};
|
|
147
|
+
if (!extras)
|
|
148
|
+
return false;
|
|
149
|
+
if (specImage &&
|
|
150
|
+
spec &&
|
|
151
|
+
!(await pullWithCredentials(`jobs.${ctx.github.job ?? '<id>'}.container`, image, spec.credentials, ctx, options, containerRuntime))) {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
143
154
|
const exchangeDir = fs.mkdtempSync(path.join(runDir, 'container-'));
|
|
144
155
|
fs.chmodSync(exchangeDir, 0o777);
|
|
145
156
|
fs.copyFileSync(runtime.eventPath, path.join(exchangeDir, 'event.json'));
|
|
146
157
|
logger_1.logger.info(`Starting job container: ${image} (${containerRuntime.name})`);
|
|
147
158
|
const started = await containerRuntime.start({
|
|
148
159
|
image,
|
|
149
|
-
workingDir:
|
|
160
|
+
workingDir: runtime.workspace,
|
|
150
161
|
readOnlyPaths: protectedWorkspacePaths(options),
|
|
151
162
|
exchangeDir,
|
|
152
163
|
env: containerEnv,
|
|
153
164
|
memoryLimit: options.containerMemoryLimit,
|
|
154
165
|
networkDisabled: options.networkDisabled === true,
|
|
155
|
-
network: runtime.services?.network
|
|
166
|
+
network: runtime.services?.network,
|
|
167
|
+
...extras
|
|
156
168
|
});
|
|
157
169
|
if (!started.success || !started.id) {
|
|
158
170
|
logger_1.logger.error(`Failed to start job container (${image}): ${started.error}`);
|
|
@@ -173,6 +185,74 @@ async function startJobContainer(job, ctx, options, runtime, runDir) {
|
|
|
173
185
|
activeContainers.add(runtime.container);
|
|
174
186
|
return true;
|
|
175
187
|
}
|
|
188
|
+
/**
|
|
189
|
+
* The `options`, `ports` and `volumes` of `jobs.<id>.container`, checked
|
|
190
|
+
* with the same policy as service containers: options and host mounts that
|
|
191
|
+
* would reach this machine are refused (undefined: the job fails), options
|
|
192
|
+
* the CLI sets itself are dropped with a warning.
|
|
193
|
+
*/
|
|
194
|
+
function jobContainerExtras(spec, ctx, options, workspace) {
|
|
195
|
+
const label = `jobs.${ctx.github.job ?? '<id>'}.container`;
|
|
196
|
+
const args = spec.options
|
|
197
|
+
? (0, shell_1.splitArgs)((0, expression_1.substituteExpressions)(spec.options, ctx))
|
|
198
|
+
: [];
|
|
199
|
+
const unsafe = (0, container_policy_1.unsafeServiceOptions)(args);
|
|
200
|
+
if (unsafe.length > 0) {
|
|
201
|
+
logger_1.logger.error(`${label}: refusing unsafe options ${[...new Set(unsafe)].join(', ')}: they would give the container access to this machine.`);
|
|
202
|
+
return undefined;
|
|
203
|
+
}
|
|
204
|
+
const reserved = new Set([
|
|
205
|
+
...JOB_CONTAINER_RESERVED,
|
|
206
|
+
...(options.containerMemoryLimit ? MEMORY_OPTIONS : [])
|
|
207
|
+
]);
|
|
208
|
+
const { kept, dropped } = (0, container_policy_1.splitReservedOptions)(args, reserved);
|
|
209
|
+
if (dropped.length > 0) {
|
|
210
|
+
logger_1.logger.warn(`${label}: ignoring options the CLI sets itself: ${[...new Set(dropped)].join(', ')}.`);
|
|
211
|
+
}
|
|
212
|
+
const volumes = (spec.volumes ?? []).map((v) => (0, expression_1.substituteExpressions)(v, ctx));
|
|
213
|
+
for (const volume of volumes) {
|
|
214
|
+
const reason = (0, container_policy_1.unsafeServiceVolumeReason)(volume, workspace);
|
|
215
|
+
if (reason) {
|
|
216
|
+
logger_1.logger.error(`${label}: ${reason}.`);
|
|
217
|
+
return undefined;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return {
|
|
221
|
+
extraArgs: kept,
|
|
222
|
+
ports: (spec.ports ?? []).map((port) => (0, expression_1.substituteExpressions)(String(port), ctx)),
|
|
223
|
+
volumes
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* `credentials:` of a job or service container. Without --registry-login
|
|
228
|
+
* they're only warned about. With it, the image is pulled with them first
|
|
229
|
+
* (a private, temporary login: see ContainerRuntime.pull), and the
|
|
230
|
+
* password is masked in the logs. False (the job fails) when they're
|
|
231
|
+
* incomplete or the pull fails.
|
|
232
|
+
*/
|
|
233
|
+
async function pullWithCredentials(label, image, credentials, ctx, options, runtime) {
|
|
234
|
+
if (!credentials)
|
|
235
|
+
return true;
|
|
236
|
+
if (!options.registryLogin) {
|
|
237
|
+
logger_1.logger.warn(`${label}: credentials are ignored; pass --registry-login to log in for this run, or log in with \`docker login\`.`);
|
|
238
|
+
return true;
|
|
239
|
+
}
|
|
240
|
+
const username = (0, expression_1.substituteExpressions)(String(credentials.username ?? ''), ctx).trim();
|
|
241
|
+
const password = (0, expression_1.substituteExpressions)(String(credentials.password ?? ''), ctx);
|
|
242
|
+
logger_1.logger.registerSecrets({ password });
|
|
243
|
+
if (!username || !password) {
|
|
244
|
+
logger_1.logger.error(`${label}: credentials need a username and a password.`);
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
const registry = (0, container_runtime_1.registryOf)(image);
|
|
248
|
+
logger_1.logger.info(`${label}: pulling ${image} with credentials for ${registry}`);
|
|
249
|
+
const pulled = await runtime.pull(image, { registry, username, password });
|
|
250
|
+
if (!pulled.success) {
|
|
251
|
+
logger_1.logger.error(`${label}: couldn't pull ${image}: ${pulled.error}`);
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
return true;
|
|
255
|
+
}
|
|
176
256
|
/**
|
|
177
257
|
* The image for a job's `runs-on`: the first label with a mapping in
|
|
178
258
|
* `runner-images` (or the built-in one), else the default image, with a
|
|
@@ -204,6 +284,59 @@ function runnerImage(runsOn, ctx, options) {
|
|
|
204
284
|
}
|
|
205
285
|
return fallback;
|
|
206
286
|
}
|
|
287
|
+
/**
|
|
288
|
+
* Empty a job's --isolate-workspace copy from inside its container, as
|
|
289
|
+
* root, before the container is stopped: files a step created as root
|
|
290
|
+
* (rootful engines) can't always be removed by the host user afterwards.
|
|
291
|
+
* Removing them from inside works with rootful and rootless engines alike
|
|
292
|
+
* (a chown back would map to a subordinate uid when rootless). `.git` is
|
|
293
|
+
* mounted read-only, so it's left for the host to remove. Only ever call
|
|
294
|
+
* this for an isolated copy, never for the user's working copy.
|
|
295
|
+
*/
|
|
296
|
+
async function clearIsolatedWorkspace(runtime) {
|
|
297
|
+
const container = runtime.container;
|
|
298
|
+
if (!container)
|
|
299
|
+
return;
|
|
300
|
+
const result = await container.runtime.exec(container.id, {
|
|
301
|
+
args: [
|
|
302
|
+
'find',
|
|
303
|
+
container_runtime_1.CONTAINER_WORKDIR,
|
|
304
|
+
'-mindepth',
|
|
305
|
+
'1',
|
|
306
|
+
'-maxdepth',
|
|
307
|
+
'1',
|
|
308
|
+
'!',
|
|
309
|
+
'-name',
|
|
310
|
+
'.git',
|
|
311
|
+
'-exec',
|
|
312
|
+
'rm',
|
|
313
|
+
'-rf',
|
|
314
|
+
'--',
|
|
315
|
+
'{}',
|
|
316
|
+
'+'
|
|
317
|
+
],
|
|
318
|
+
user: '0',
|
|
319
|
+
workingDir: '/'
|
|
320
|
+
});
|
|
321
|
+
if (!result.success) {
|
|
322
|
+
logger_1.logger.debug(`Couldn't empty the workspace copy in the job container: ${result.stderr.trim()}`);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Remove `containerPath` from inside the job's container, as root, so a
|
|
327
|
+
* copy holding files a step created as root can then be removed on the
|
|
328
|
+
* host. No-op without a container.
|
|
329
|
+
*/
|
|
330
|
+
async function removeInContainer(runtime, containerPath) {
|
|
331
|
+
const container = runtime.container;
|
|
332
|
+
if (!container)
|
|
333
|
+
return;
|
|
334
|
+
await container.runtime.exec(container.id, {
|
|
335
|
+
args: ['rm', '-rf', '--', containerPath],
|
|
336
|
+
user: '0',
|
|
337
|
+
workingDir: '/'
|
|
338
|
+
});
|
|
339
|
+
}
|
|
207
340
|
async function stopJobContainer(runtime) {
|
|
208
341
|
const container = runtime.container;
|
|
209
342
|
if (container) {
|
|
@@ -267,16 +400,16 @@ async function startJobServices(job, ctx, options, runtime) {
|
|
|
267
400
|
return false;
|
|
268
401
|
}
|
|
269
402
|
for (const volume of volumes) {
|
|
270
|
-
const reason = (0, container_policy_1.unsafeServiceVolumeReason)(volume,
|
|
403
|
+
const reason = (0, container_policy_1.unsafeServiceVolumeReason)(volume, runtime.workspace);
|
|
271
404
|
if (reason) {
|
|
272
405
|
logger_1.logger.error(`services.${serviceId}: ${reason}.`);
|
|
273
406
|
return false;
|
|
274
407
|
}
|
|
275
408
|
}
|
|
276
|
-
if (service.credentials) {
|
|
277
|
-
logger_1.logger.warn(`services.${serviceId}: credentials are ignored; log in with \`docker login\` instead.`);
|
|
278
|
-
}
|
|
279
409
|
const image = (0, expression_1.substituteExpressions)(service.image, ctx);
|
|
410
|
+
if (!(await pullWithCredentials(`services.${serviceId}`, image, service.credentials, ctx, options, containerRuntime))) {
|
|
411
|
+
return false;
|
|
412
|
+
}
|
|
280
413
|
logger_1.logger.info(`Starting service ${serviceId}: ${image}`);
|
|
281
414
|
const started = await containerRuntime.start({
|
|
282
415
|
image,
|
|
@@ -107,7 +107,7 @@ function runnerContext(runtime) {
|
|
|
107
107
|
return {
|
|
108
108
|
name: 'secureci',
|
|
109
109
|
os: runtime.runnerEnv.RUNNER_OS,
|
|
110
|
-
arch:
|
|
110
|
+
arch: runtime.runnerEnv.RUNNER_ARCH,
|
|
111
111
|
temp: runtime.runnerEnv.RUNNER_TEMP,
|
|
112
112
|
tool_cache: runtime.runnerEnv.RUNNER_TOOL_CACHE
|
|
113
113
|
};
|
|
@@ -126,12 +126,13 @@ function hostHasBash() {
|
|
|
126
126
|
}
|
|
127
127
|
return hostBash;
|
|
128
128
|
}
|
|
129
|
-
function createJobRuntime(github, runDir, defaults = {}) {
|
|
129
|
+
function createJobRuntime(github, runDir, defaults = {}, workspace = process.cwd()) {
|
|
130
130
|
const homeDir = process.env.HOME || process.env.USERPROFILE || os.tmpdir();
|
|
131
131
|
const eventPath = path.join(runDir, 'event.json');
|
|
132
132
|
return {
|
|
133
|
+
workspace,
|
|
133
134
|
runnerEnv: (0, runner_env_1.buildRunnerEnv)(github, {
|
|
134
|
-
workspace
|
|
135
|
+
workspace,
|
|
135
136
|
eventPath,
|
|
136
137
|
// Per job, so concurrent jobs don't share $RUNNER_TEMP.
|
|
137
138
|
tempDir: fs.mkdtempSync(path.join(runDir, 'tmp-')),
|
|
@@ -144,7 +144,7 @@ async function executeNodeAction(action, actionPath, step, actionInputs, callerC
|
|
|
144
144
|
}
|
|
145
145
|
try {
|
|
146
146
|
const { stdout, stderr } = await execFileAsync(node.path, [path.join(actionDir, script)], {
|
|
147
|
-
cwd:
|
|
147
|
+
cwd: runtime.workspace,
|
|
148
148
|
maxBuffer: 64 * 1024 * 1024,
|
|
149
149
|
timeout: limit.ms,
|
|
150
150
|
killSignal: 'SIGKILL',
|
|
@@ -38,6 +38,7 @@ exports.createStepFiles = createStepFiles;
|
|
|
38
38
|
exports.readStepFiles = readStepFiles;
|
|
39
39
|
exports.cleanupStepFiles = cleanupStepFiles;
|
|
40
40
|
exports.processWorkflowCommands = processWorkflowCommands;
|
|
41
|
+
exports.runnerArch = runnerArch;
|
|
41
42
|
exports.buildRunnerEnv = buildRunnerEnv;
|
|
42
43
|
exports.prependPath = prependPath;
|
|
43
44
|
exports.hostBaseEnv = hostBaseEnv;
|
|
@@ -248,6 +249,24 @@ function runnerOs() {
|
|
|
248
249
|
return 'Linux';
|
|
249
250
|
}
|
|
250
251
|
}
|
|
252
|
+
/**
|
|
253
|
+
* `RUNNER_ARCH` / `runner.arch` for a Node.js architecture, using GitHub's
|
|
254
|
+
* values (X86, X64, ARM, ARM64). Jobs run natively (a job container has the
|
|
255
|
+
* host's architecture), so the host's is the runner's. Anything else reports
|
|
256
|
+
* X64, as before this was mapped.
|
|
257
|
+
*/
|
|
258
|
+
function runnerArch(arch = process.arch) {
|
|
259
|
+
switch (arch) {
|
|
260
|
+
case 'arm64':
|
|
261
|
+
return 'ARM64';
|
|
262
|
+
case 'arm':
|
|
263
|
+
return 'ARM';
|
|
264
|
+
case 'ia32':
|
|
265
|
+
return 'X86';
|
|
266
|
+
default:
|
|
267
|
+
return 'X64';
|
|
268
|
+
}
|
|
269
|
+
}
|
|
251
270
|
/**
|
|
252
271
|
* The default environment variables GitHub's runner exports to every step,
|
|
253
272
|
* derived from the (local, best-effort) `github` context.
|
|
@@ -274,6 +293,7 @@ function buildRunnerEnv(github, options) {
|
|
|
274
293
|
GITHUB_WORKSPACE: options.workspace,
|
|
275
294
|
GITHUB_EVENT_PATH: options.eventPath,
|
|
276
295
|
RUNNER_OS: runnerOs(),
|
|
296
|
+
RUNNER_ARCH: runnerArch(),
|
|
277
297
|
RUNNER_TEMP: options.tempDir,
|
|
278
298
|
RUNNER_TOOL_CACHE: options.toolCache
|
|
279
299
|
};
|
package/dist/runner/scheduler.js
CHANGED
|
@@ -33,9 +33,10 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.Slots = void 0;
|
|
36
|
+
exports.ConcurrencyGroups = exports.Slots = void 0;
|
|
37
37
|
exports.defaultParallelism = defaultParallelism;
|
|
38
38
|
exports.parseParallel = parseParallel;
|
|
39
|
+
exports.parseConcurrencyMode = parseConcurrencyMode;
|
|
39
40
|
exports.runMatrix = runMatrix;
|
|
40
41
|
exports.runJobGraph = runJobGraph;
|
|
41
42
|
const os = __importStar(require("os"));
|
|
@@ -84,6 +85,65 @@ function parseParallel(raw) {
|
|
|
84
85
|
}
|
|
85
86
|
return n;
|
|
86
87
|
}
|
|
88
|
+
/** Parse `--concurrency` / config `concurrency`. */
|
|
89
|
+
function parseConcurrencyMode(raw) {
|
|
90
|
+
const mode = raw.trim().toLowerCase();
|
|
91
|
+
if (mode === 'serialize' || mode === 'github')
|
|
92
|
+
return mode;
|
|
93
|
+
throw new Error(`Invalid value "${raw}" for concurrency. Must be serialize or github.`);
|
|
94
|
+
}
|
|
95
|
+
/** `concurrency:` groups of one run, keyed case-insensitively. */
|
|
96
|
+
class ConcurrencyGroups {
|
|
97
|
+
constructor(mode, note = () => undefined) {
|
|
98
|
+
this.mode = mode;
|
|
99
|
+
this.note = note;
|
|
100
|
+
this.holders = new Map();
|
|
101
|
+
this.waiting = new Map();
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Wait to run in `group`. `abort` cancels this job once it's running
|
|
105
|
+
* (github mode, for a newcomer with cancel-in-progress).
|
|
106
|
+
*/
|
|
107
|
+
enter(group, cancelInProgress, abort, label) {
|
|
108
|
+
const key = group.toLowerCase();
|
|
109
|
+
return new Promise((admit) => {
|
|
110
|
+
const entry = { label, abort, admit };
|
|
111
|
+
const holder = this.holders.get(key);
|
|
112
|
+
if (!holder) {
|
|
113
|
+
this.admit(key, entry);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const queue = this.waiting.get(key) ?? [];
|
|
117
|
+
this.waiting.set(key, queue);
|
|
118
|
+
if (this.mode === 'github') {
|
|
119
|
+
if (cancelInProgress) {
|
|
120
|
+
this.note(`Cancelling ${holder.label}: ${label} joined concurrency group "${group}" with cancel-in-progress`);
|
|
121
|
+
holder.abort();
|
|
122
|
+
}
|
|
123
|
+
for (const older of queue.splice(0)) {
|
|
124
|
+
this.note(`Cancelling ${older.label}: ${label} is now pending in concurrency group "${group}"`);
|
|
125
|
+
older.admit('cancelled');
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
queue.push(entry);
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
admit(key, entry) {
|
|
132
|
+
this.holders.set(key, entry);
|
|
133
|
+
let left = false;
|
|
134
|
+
entry.admit(() => {
|
|
135
|
+
if (left)
|
|
136
|
+
return;
|
|
137
|
+
left = true;
|
|
138
|
+
const next = this.waiting.get(key)?.shift();
|
|
139
|
+
if (next)
|
|
140
|
+
this.admit(key, next);
|
|
141
|
+
else
|
|
142
|
+
this.holders.delete(key);
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
exports.ConcurrencyGroups = ConcurrencyGroups;
|
|
87
147
|
/**
|
|
88
148
|
* Run `count` matrix combinations, at most `maxParallel` at once and each
|
|
89
149
|
* holding one of the global `slots` while it runs. With fail-fast, a
|
|
@@ -105,6 +165,29 @@ async function runMatrix(count, run, options) {
|
|
|
105
165
|
};
|
|
106
166
|
options.signal?.addEventListener('abort', onParentAbort);
|
|
107
167
|
const runOne = async (index) => {
|
|
168
|
+
const controller = new AbortController();
|
|
169
|
+
let leaveGroup;
|
|
170
|
+
if (options.gate) {
|
|
171
|
+
const admission = await options.gate(index, () => controller.abort());
|
|
172
|
+
if (admission === 'cancelled' || controller.signal.aborted) {
|
|
173
|
+
if (admission !== 'cancelled')
|
|
174
|
+
admission();
|
|
175
|
+
if (!stopped && options.onGateCancelled) {
|
|
176
|
+
outcomes[index] = await options.onGateCancelled(index);
|
|
177
|
+
finished++;
|
|
178
|
+
}
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
leaveGroup = admission;
|
|
182
|
+
}
|
|
183
|
+
try {
|
|
184
|
+
await runAdmitted(index, controller);
|
|
185
|
+
}
|
|
186
|
+
finally {
|
|
187
|
+
leaveGroup?.();
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
const runAdmitted = async (index, controller) => {
|
|
108
191
|
const releaseLocal = await local.acquire();
|
|
109
192
|
try {
|
|
110
193
|
if (stopped)
|
|
@@ -115,7 +198,6 @@ async function runMatrix(count, run, options) {
|
|
|
115
198
|
try {
|
|
116
199
|
if (stopped)
|
|
117
200
|
return;
|
|
118
|
-
const controller = new AbortController();
|
|
119
201
|
controllers.set(index, controller);
|
|
120
202
|
try {
|
|
121
203
|
const outcome = await run(index, controller.signal);
|
package/dist/runner/steps.js
CHANGED
|
@@ -143,8 +143,8 @@ async function executeStep(step, options, runtime, ctx) {
|
|
|
143
143
|
timeout: limit.ms,
|
|
144
144
|
killSignal: 'SIGKILL',
|
|
145
145
|
cwd: workingDirectory
|
|
146
|
-
? path.resolve(
|
|
147
|
-
:
|
|
146
|
+
? path.resolve(runtime.workspace, workingDirectory)
|
|
147
|
+
: runtime.workspace,
|
|
148
148
|
env: {
|
|
149
149
|
...(0, runner_env_1.hostBaseEnv)(),
|
|
150
150
|
...runtime.runnerEnv,
|