@forgezero/runtime 0.1.1 → 0.1.3
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 +27 -4
- package/dist/audit.js +262 -23
- package/dist/identity.d.ts +25 -0
- package/dist/identity.js +99 -3
- package/dist/jobs.d.ts +3 -2
- package/dist/jobs.js +288 -13
- package/dist/pipeline.d.ts +11 -37
- package/dist/pipeline.js +0 -27
- package/dist/queue.d.ts +17 -3
- package/dist/queue.js +86 -19
- package/dist/snp.d.ts +7 -6
- package/dist/snp.js +6 -1
- package/package.json +6 -9
- package/dist/finance/binance.d.ts +0 -27
- package/dist/finance/binance.js +0 -452
- package/dist/serial.d.ts +0 -54
- package/dist/serial.js +0 -40
package/dist/jobs.js
CHANGED
|
@@ -6,6 +6,275 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
6
6
|
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
7
7
|
});
|
|
8
8
|
|
|
9
|
+
// src/queue.ts
|
|
10
|
+
class QueueStoppedError extends Error {
|
|
11
|
+
constructor() {
|
|
12
|
+
super("queue: stopped before this task could run");
|
|
13
|
+
this.name = "QueueStoppedError";
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
class QueueKeyStoppedError extends Error {
|
|
18
|
+
key;
|
|
19
|
+
constructor(key) {
|
|
20
|
+
super(`queue: key ${key} is stopped`);
|
|
21
|
+
this.key = key;
|
|
22
|
+
this.name = "QueueKeyStoppedError";
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
class TaskCancelledError extends Error {
|
|
27
|
+
constructor() {
|
|
28
|
+
super("queue: task cancelled");
|
|
29
|
+
this.name = "TaskCancelledError";
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
var DEFAULT_RETRY = {
|
|
33
|
+
attempts: 1,
|
|
34
|
+
backoffMs: (attempt) => Math.min(30000, 2 ** attempt * 100)
|
|
35
|
+
};
|
|
36
|
+
function createQueue(options = {}) {
|
|
37
|
+
const width = options.width ?? 8;
|
|
38
|
+
const retry = { ...DEFAULT_RETRY, ...options.retry };
|
|
39
|
+
if (!Number.isSafeInteger(width) || width < 1) {
|
|
40
|
+
throw new RangeError("queue: width must be a positive integer");
|
|
41
|
+
}
|
|
42
|
+
if (!Number.isSafeInteger(retry.attempts) || retry.attempts < 1) {
|
|
43
|
+
throw new RangeError("queue: retry attempts must be a positive integer");
|
|
44
|
+
}
|
|
45
|
+
const sleep = options.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
46
|
+
const lanes = new Map;
|
|
47
|
+
const running = new Set;
|
|
48
|
+
const paused = new Set;
|
|
49
|
+
const stoppedKeys = new Set;
|
|
50
|
+
let sequence = 0;
|
|
51
|
+
let globallyPaused = false;
|
|
52
|
+
let accepting = true;
|
|
53
|
+
let aborted = false;
|
|
54
|
+
let completed = 0;
|
|
55
|
+
let failed = 0;
|
|
56
|
+
const idle = [];
|
|
57
|
+
const announceIdle = () => {
|
|
58
|
+
if (running.size > 0)
|
|
59
|
+
return;
|
|
60
|
+
for (const lane of lanes.values())
|
|
61
|
+
if (lane.length > 0)
|
|
62
|
+
return;
|
|
63
|
+
while (idle.length > 0)
|
|
64
|
+
idle.shift()();
|
|
65
|
+
};
|
|
66
|
+
function pump() {
|
|
67
|
+
if (globallyPaused || aborted) {
|
|
68
|
+
announceIdle();
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
for (const [key, lane] of lanes) {
|
|
72
|
+
if (running.size >= width)
|
|
73
|
+
break;
|
|
74
|
+
if (running.has(key) || paused.has(key) || lane.length === 0)
|
|
75
|
+
continue;
|
|
76
|
+
execute(key);
|
|
77
|
+
}
|
|
78
|
+
announceIdle();
|
|
79
|
+
}
|
|
80
|
+
async function execute(key) {
|
|
81
|
+
running.add(key);
|
|
82
|
+
try {
|
|
83
|
+
const lane = lanes.get(key);
|
|
84
|
+
const entry = lane?.[0];
|
|
85
|
+
if (entry && !globallyPaused && !paused.has(key) && !aborted) {
|
|
86
|
+
lane.shift();
|
|
87
|
+
await attempt(entry);
|
|
88
|
+
}
|
|
89
|
+
} finally {
|
|
90
|
+
running.delete(key);
|
|
91
|
+
const remaining = lanes.get(key);
|
|
92
|
+
if (remaining?.length === 0)
|
|
93
|
+
lanes.delete(key);
|
|
94
|
+
else if (remaining) {
|
|
95
|
+
lanes.delete(key);
|
|
96
|
+
lanes.set(key, remaining);
|
|
97
|
+
}
|
|
98
|
+
pump();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
async function attempt(entry) {
|
|
102
|
+
if (entry.cancelled) {
|
|
103
|
+
entry.reject(new TaskCancelledError);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
for (;; ) {
|
|
107
|
+
entry.attempt += 1;
|
|
108
|
+
try {
|
|
109
|
+
const value = await entry.run();
|
|
110
|
+
completed += 1;
|
|
111
|
+
entry.resolve(value);
|
|
112
|
+
return;
|
|
113
|
+
} catch (cause) {
|
|
114
|
+
if (entry.attempt >= retry.attempts || entry.cancelled || aborted) {
|
|
115
|
+
failed += 1;
|
|
116
|
+
entry.reject(cause);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
const delay = retry.backoffMs(entry.attempt);
|
|
120
|
+
if (!Number.isFinite(delay) || delay < 0) {
|
|
121
|
+
failed += 1;
|
|
122
|
+
entry.reject(new RangeError("queue: retry backoff must be a non-negative finite number"));
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
await sleep(delay);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return {
|
|
130
|
+
run(key, handler, ...args) {
|
|
131
|
+
const id = `q_${++sequence}`;
|
|
132
|
+
if (!accepting || stoppedKeys.has(key)) {
|
|
133
|
+
const refused = Promise.reject(accepting ? new QueueKeyStoppedError(key) : new QueueStoppedError);
|
|
134
|
+
refused.catch(() => {
|
|
135
|
+
return;
|
|
136
|
+
});
|
|
137
|
+
return { id, key, result: refused };
|
|
138
|
+
}
|
|
139
|
+
let resolve;
|
|
140
|
+
let reject;
|
|
141
|
+
const result = new Promise((ok, no) => {
|
|
142
|
+
resolve = ok;
|
|
143
|
+
reject = no;
|
|
144
|
+
});
|
|
145
|
+
const entry = {
|
|
146
|
+
id,
|
|
147
|
+
key,
|
|
148
|
+
run: async () => handler(...args),
|
|
149
|
+
resolve,
|
|
150
|
+
reject,
|
|
151
|
+
attempt: 0,
|
|
152
|
+
cancelled: false
|
|
153
|
+
};
|
|
154
|
+
const lane = lanes.get(key);
|
|
155
|
+
if (lane)
|
|
156
|
+
lane.push(entry);
|
|
157
|
+
else
|
|
158
|
+
lanes.set(key, [entry]);
|
|
159
|
+
pump();
|
|
160
|
+
return { id, key, result };
|
|
161
|
+
},
|
|
162
|
+
cancel(id) {
|
|
163
|
+
for (const [key, lane] of lanes) {
|
|
164
|
+
const index = lane.findIndex((entry2) => entry2.id === id);
|
|
165
|
+
if (index === -1)
|
|
166
|
+
continue;
|
|
167
|
+
const [entry] = lane.splice(index, 1);
|
|
168
|
+
entry.cancelled = true;
|
|
169
|
+
entry.reject(new TaskCancelledError);
|
|
170
|
+
if (lane.length === 0 && !running.has(key))
|
|
171
|
+
lanes.delete(key);
|
|
172
|
+
announceIdle();
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
return false;
|
|
176
|
+
},
|
|
177
|
+
pauseKey(key) {
|
|
178
|
+
paused.add(key);
|
|
179
|
+
},
|
|
180
|
+
resumeKey(key) {
|
|
181
|
+
paused.delete(key);
|
|
182
|
+
pump();
|
|
183
|
+
},
|
|
184
|
+
stopKey(key) {
|
|
185
|
+
stoppedKeys.add(key);
|
|
186
|
+
paused.delete(key);
|
|
187
|
+
const lane = lanes.get(key);
|
|
188
|
+
if (!lane)
|
|
189
|
+
return 0;
|
|
190
|
+
let removed = 0;
|
|
191
|
+
for (const entry of lane.splice(0)) {
|
|
192
|
+
removed += 1;
|
|
193
|
+
entry.cancelled = true;
|
|
194
|
+
entry.reject(new QueueKeyStoppedError(key));
|
|
195
|
+
}
|
|
196
|
+
if (!running.has(key))
|
|
197
|
+
lanes.delete(key);
|
|
198
|
+
announceIdle();
|
|
199
|
+
return removed;
|
|
200
|
+
},
|
|
201
|
+
startKey(key) {
|
|
202
|
+
const changed = stoppedKeys.delete(key);
|
|
203
|
+
pump();
|
|
204
|
+
return changed;
|
|
205
|
+
},
|
|
206
|
+
pause() {
|
|
207
|
+
globallyPaused = true;
|
|
208
|
+
},
|
|
209
|
+
resume() {
|
|
210
|
+
globallyPaused = false;
|
|
211
|
+
pump();
|
|
212
|
+
},
|
|
213
|
+
snapshot() {
|
|
214
|
+
let queued = 0;
|
|
215
|
+
for (const lane of lanes.values())
|
|
216
|
+
queued += lane.length;
|
|
217
|
+
return {
|
|
218
|
+
running: running.size,
|
|
219
|
+
queued,
|
|
220
|
+
keys: lanes.size,
|
|
221
|
+
paused: globallyPaused,
|
|
222
|
+
pausedKeys: [...paused],
|
|
223
|
+
stoppedKeys: [...stoppedKeys],
|
|
224
|
+
completed,
|
|
225
|
+
failed
|
|
226
|
+
};
|
|
227
|
+
},
|
|
228
|
+
whenIdle() {
|
|
229
|
+
if (running.size === 0 && [...lanes.values()].every((lane) => lane.length === 0)) {
|
|
230
|
+
return Promise.resolve();
|
|
231
|
+
}
|
|
232
|
+
return new Promise((resolve) => idle.push(resolve));
|
|
233
|
+
},
|
|
234
|
+
async stop(deadlineMs = 30000) {
|
|
235
|
+
if (!Number.isSafeInteger(deadlineMs) || deadlineMs < 0) {
|
|
236
|
+
throw new RangeError("queue: stop deadline must be a non-negative integer");
|
|
237
|
+
}
|
|
238
|
+
accepting = false;
|
|
239
|
+
const before = { completed, failed };
|
|
240
|
+
globallyPaused = false;
|
|
241
|
+
paused.clear();
|
|
242
|
+
pump();
|
|
243
|
+
let timedOut = false;
|
|
244
|
+
let deadlineHandle;
|
|
245
|
+
const deadline = new Promise((resolve) => {
|
|
246
|
+
deadlineHandle = setTimeout(() => {
|
|
247
|
+
timedOut = true;
|
|
248
|
+
resolve();
|
|
249
|
+
}, deadlineMs);
|
|
250
|
+
});
|
|
251
|
+
await Promise.race([this.whenIdle(), deadline]);
|
|
252
|
+
if (!timedOut && deadlineHandle !== undefined)
|
|
253
|
+
clearTimeout(deadlineHandle);
|
|
254
|
+
if (timedOut)
|
|
255
|
+
aborted = true;
|
|
256
|
+
let abandoned = 0;
|
|
257
|
+
for (const lane of lanes.values()) {
|
|
258
|
+
abandoned += lane.length;
|
|
259
|
+
for (const entry of lane.splice(0))
|
|
260
|
+
entry.reject(new QueueStoppedError);
|
|
261
|
+
}
|
|
262
|
+
abandoned += running.size;
|
|
263
|
+
for (const [key, lane] of lanes) {
|
|
264
|
+
if (lane.length === 0 && !running.has(key))
|
|
265
|
+
lanes.delete(key);
|
|
266
|
+
}
|
|
267
|
+
announceIdle();
|
|
268
|
+
return {
|
|
269
|
+
completed: completed - before.completed,
|
|
270
|
+
failed: failed - before.failed,
|
|
271
|
+
abandoned,
|
|
272
|
+
timedOut
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
9
278
|
// src/jobs.ts
|
|
10
279
|
var systemClock = {
|
|
11
280
|
now: () => Date.now(),
|
|
@@ -83,10 +352,13 @@ function createScheduler(options) {
|
|
|
83
352
|
{ key: job.key, label: job.label, state: "stopped", consecutiveFailures: 0, runs: 0, skippedLocked: 0 }
|
|
84
353
|
]));
|
|
85
354
|
const timers = new Map;
|
|
86
|
-
|
|
355
|
+
let work = createQueue({ width: Math.max(1, jobs.size) });
|
|
356
|
+
let workStopped = false;
|
|
357
|
+
let restartBlocked = false;
|
|
87
358
|
let controller = new AbortController;
|
|
88
359
|
let paused = false;
|
|
89
360
|
let running = false;
|
|
361
|
+
const submit = (job) => work.run(job.key, execute, job).result;
|
|
90
362
|
async function execute(job) {
|
|
91
363
|
const report = reports.get(job.key);
|
|
92
364
|
const leaseMs = job.leaseMs ?? (job.every ? everyMs(job.every) * 4 : 60000);
|
|
@@ -132,17 +404,22 @@ function createScheduler(options) {
|
|
|
132
404
|
timers.delete(job.key);
|
|
133
405
|
if (!running || paused)
|
|
134
406
|
return;
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
schedule(job, everyMs(job.every));
|
|
407
|
+
submit(job).then(() => schedule(job, everyMs(job.every)), () => {
|
|
408
|
+
return;
|
|
138
409
|
});
|
|
139
|
-
inFlight.set(job.key, work);
|
|
140
410
|
}, delayMs));
|
|
141
411
|
}
|
|
142
412
|
return {
|
|
143
413
|
start() {
|
|
144
414
|
if (running)
|
|
145
415
|
return;
|
|
416
|
+
if (restartBlocked) {
|
|
417
|
+
throw new Error("scheduler: cannot restart after an incomplete drain while abandoned work may still run");
|
|
418
|
+
}
|
|
419
|
+
if (workStopped) {
|
|
420
|
+
work = createQueue({ width: Math.max(1, jobs.size) });
|
|
421
|
+
workStopped = false;
|
|
422
|
+
}
|
|
146
423
|
running = true;
|
|
147
424
|
paused = false;
|
|
148
425
|
controller = new AbortController;
|
|
@@ -152,15 +429,18 @@ function createScheduler(options) {
|
|
|
152
429
|
schedule(job, job.startDelayMs ?? spreadOf(job.key, Math.min(interval, 30000)));
|
|
153
430
|
}
|
|
154
431
|
},
|
|
155
|
-
async stop() {
|
|
432
|
+
async stop(deadlineMs = 30000) {
|
|
156
433
|
running = false;
|
|
157
434
|
controller.abort();
|
|
158
435
|
for (const timer of timers.values())
|
|
159
436
|
clearTimeout(timer);
|
|
160
437
|
timers.clear();
|
|
161
|
-
await
|
|
438
|
+
const drained = await work.stop(deadlineMs);
|
|
439
|
+
workStopped = true;
|
|
440
|
+
restartBlocked = drained.timedOut;
|
|
162
441
|
for (const report of reports.values())
|
|
163
442
|
report.state = "stopped";
|
|
443
|
+
return drained;
|
|
164
444
|
},
|
|
165
445
|
pause() {
|
|
166
446
|
paused = true;
|
|
@@ -185,12 +465,7 @@ function createScheduler(options) {
|
|
|
185
465
|
const job = jobs.get(key);
|
|
186
466
|
if (!job)
|
|
187
467
|
throw new Error(`No job named "${key}".`);
|
|
188
|
-
|
|
189
|
-
if (existing)
|
|
190
|
-
await existing;
|
|
191
|
-
const work = execute(job).finally(() => inFlight.delete(key));
|
|
192
|
-
inFlight.set(key, work);
|
|
193
|
-
await work;
|
|
468
|
+
await submit(job);
|
|
194
469
|
return { ...reports.get(key) };
|
|
195
470
|
},
|
|
196
471
|
status: () => [...reports.values()].map((report) => ({ ...report })),
|
package/dist/pipeline.d.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Deciding whether a push should cause a deploy
|
|
2
|
+
* Deciding whether a push should cause a deploy.
|
|
3
3
|
*
|
|
4
4
|
* A webhook receiver is a public endpoint that runs commands on your machines
|
|
5
5
|
* when something posts to it. Everything worth getting right is in the gap
|
|
6
|
-
* between those two facts, so this module holds
|
|
7
|
-
* doing: no network, no shell, no clone
|
|
8
|
-
*
|
|
6
|
+
* between those two facts, so this module holds trigger verification and none
|
|
7
|
+
* of the doing: no network, no shell, no clone, and no executable plan. The
|
|
8
|
+
* agent later reads the only deployment definition from the exact checked-out
|
|
9
|
+
* commit.
|
|
9
10
|
*
|
|
10
11
|
* That split is not tidiness. It means the interesting cases — a forged
|
|
11
12
|
* signature, a push to a branch nobody deploys, two pushes racing, a repository
|
|
@@ -21,8 +22,8 @@
|
|
|
21
22
|
* matters when reading logs during an incident.
|
|
22
23
|
*/
|
|
23
24
|
export declare class PipelineError extends Error {
|
|
24
|
-
readonly code: 'BAD_SIGNATURE' | 'UNSUPPORTED_PROVIDER' | 'MALFORMED_EVENT' | '
|
|
25
|
-
constructor(code: 'BAD_SIGNATURE' | 'UNSUPPORTED_PROVIDER' | 'MALFORMED_EVENT' | '
|
|
25
|
+
readonly code: 'BAD_SIGNATURE' | 'UNSUPPORTED_PROVIDER' | 'MALFORMED_EVENT' | 'NO_SECRET';
|
|
26
|
+
constructor(code: 'BAD_SIGNATURE' | 'UNSUPPORTED_PROVIDER' | 'MALFORMED_EVENT' | 'NO_SECRET', message: string);
|
|
26
27
|
}
|
|
27
28
|
export declare const GIT_PROVIDERS: readonly ["github", "gitlab", "generic"];
|
|
28
29
|
export type GitProvider = (typeof GIT_PROVIDERS)[number];
|
|
@@ -73,50 +74,23 @@ export interface PushEvent {
|
|
|
73
74
|
* failure every time somebody opened an issue.
|
|
74
75
|
*/
|
|
75
76
|
export declare function parsePush(provider: GitProvider, body: unknown): PushEvent | null;
|
|
76
|
-
export interface
|
|
77
|
+
export interface DeployTrigger {
|
|
77
78
|
provider: GitProvider;
|
|
78
79
|
/** `owner/name`. Compared against the delivery. */
|
|
79
80
|
repository: string;
|
|
80
81
|
/** Only this branch deploys. One branch per pipeline, deliberately. */
|
|
81
82
|
branch: string;
|
|
82
|
-
/** Where the checkout lives on the compute. */
|
|
83
|
-
workdir: string;
|
|
84
|
-
/** Shell steps, in order. Empty means clone only. */
|
|
85
|
-
steps: readonly string[];
|
|
86
|
-
cloneUrl: string;
|
|
87
83
|
}
|
|
88
|
-
export declare const PLAN_ACTIONS: readonly ["clone", "fetch", "checkout", "run"];
|
|
89
|
-
export type PlanAction = (typeof PLAN_ACTIONS)[number];
|
|
90
|
-
export interface PlanStep {
|
|
91
|
-
action: PlanAction;
|
|
92
|
-
command: string;
|
|
93
|
-
/** Shown to an operator. Never contains a credential. */
|
|
94
|
-
label: string;
|
|
95
|
-
}
|
|
96
|
-
/**
|
|
97
|
-
* The commands that would deploy this push, in order.
|
|
98
|
-
*
|
|
99
|
-
* `clone` and `fetch` are both emitted, guarded on the directory existing,
|
|
100
|
-
* because a runner cannot know in advance whether the first deploy has
|
|
101
|
-
* happened — and branching on that in the caller means two code paths where one
|
|
102
|
-
* of them is exercised once per compute, ever.
|
|
103
|
-
*
|
|
104
|
-
* The commit is checked out by SHA rather than by branch. A branch moves: a
|
|
105
|
-
* deploy that fetched and then checked out `main` could deploy a commit that
|
|
106
|
-
* arrived after the one that triggered it, so the thing tested is not the thing
|
|
107
|
-
* shipped. The SHA is what the webhook said, so what deploys is what fired.
|
|
108
|
-
*/
|
|
109
|
-
export declare function planDeploy(config: PipelineConfig, event: PushEvent): PlanStep[];
|
|
110
84
|
/**
|
|
111
85
|
* Should this delivery deploy at all?
|
|
112
86
|
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
87
|
+
* "We received it and deliberately did nothing" is a first-class outcome with
|
|
88
|
+
* a reason attached. A receiver that silently
|
|
115
89
|
* ignored non-matching branches would be indistinguishable from one that is
|
|
116
90
|
* broken, and the first question during an incident is always whether the hook
|
|
117
91
|
* arrived.
|
|
118
92
|
*/
|
|
119
|
-
export declare function shouldDeploy(config:
|
|
93
|
+
export declare function shouldDeploy(config: Pick<DeployTrigger, 'repository' | 'branch'>, event: PushEvent | null): {
|
|
120
94
|
deploy: boolean;
|
|
121
95
|
reason: string;
|
|
122
96
|
};
|
package/dist/pipeline.js
CHANGED
|
@@ -72,31 +72,6 @@ function parsePush(provider, body) {
|
|
|
72
72
|
message: typeof payload.head_commit?.message === "string" ? String(payload.head_commit.message) : undefined
|
|
73
73
|
};
|
|
74
74
|
}
|
|
75
|
-
var PLAN_ACTIONS = ["clone", "fetch", "checkout", "run"];
|
|
76
|
-
function planDeploy(config, event) {
|
|
77
|
-
if (event.repository !== config.repository) {
|
|
78
|
-
throw new PipelineError("WRONG_REPOSITORY", `That delivery is for ${event.repository}, and this pipeline deploys ${config.repository}.`);
|
|
79
|
-
}
|
|
80
|
-
const dir = config.workdir;
|
|
81
|
-
return [
|
|
82
|
-
{
|
|
83
|
-
action: "clone",
|
|
84
|
-
command: `[ -d ${dir}/.git ] || git clone ${config.cloneUrl} ${dir}`,
|
|
85
|
-
label: "clone if this is the first deploy"
|
|
86
|
-
},
|
|
87
|
-
{ action: "fetch", command: `git -C ${dir} fetch --prune origin`, label: "fetch" },
|
|
88
|
-
{
|
|
89
|
-
action: "checkout",
|
|
90
|
-
command: `git -C ${dir} checkout --detach ${event.commit}`,
|
|
91
|
-
label: `check out ${event.commit.slice(0, 8)}`
|
|
92
|
-
},
|
|
93
|
-
...config.steps.map((step) => ({
|
|
94
|
-
action: "run",
|
|
95
|
-
command: `cd ${dir} && ${step}`,
|
|
96
|
-
label: step
|
|
97
|
-
}))
|
|
98
|
-
];
|
|
99
|
-
}
|
|
100
75
|
function shouldDeploy(config, event) {
|
|
101
76
|
if (!event)
|
|
102
77
|
return { deploy: false, reason: "Not a branch push — nothing to deploy." };
|
|
@@ -113,9 +88,7 @@ export {
|
|
|
113
88
|
webhookPath,
|
|
114
89
|
verifyWebhook,
|
|
115
90
|
shouldDeploy,
|
|
116
|
-
planDeploy,
|
|
117
91
|
parsePush,
|
|
118
92
|
PipelineError,
|
|
119
|
-
PLAN_ACTIONS,
|
|
120
93
|
GIT_PROVIDERS
|
|
121
94
|
};
|
package/dist/queue.d.ts
CHANGED
|
@@ -44,9 +44,8 @@ export interface QueueOptions {
|
|
|
44
44
|
/** How many keys may run at once. Ordering within a key is unaffected. */
|
|
45
45
|
width?: number;
|
|
46
46
|
retry?: Partial<RetryPolicy>;
|
|
47
|
-
/**
|
|
47
|
+
/** Retry delay injection. Shutdown deadlines use a cancellable native timer. */
|
|
48
48
|
sleep?: (ms: number) => Promise<void>;
|
|
49
|
-
now?: () => number;
|
|
50
49
|
}
|
|
51
50
|
export interface DrainReport {
|
|
52
51
|
completed: number;
|
|
@@ -58,6 +57,10 @@ export interface DrainReport {
|
|
|
58
57
|
export declare class QueueStoppedError extends Error {
|
|
59
58
|
constructor();
|
|
60
59
|
}
|
|
60
|
+
export declare class QueueKeyStoppedError extends Error {
|
|
61
|
+
readonly key: string;
|
|
62
|
+
constructor(key: string);
|
|
63
|
+
}
|
|
61
64
|
export declare class TaskCancelledError extends Error {
|
|
62
65
|
constructor();
|
|
63
66
|
}
|
|
@@ -69,12 +72,22 @@ export declare function createQueue(options?: QueueOptions): {
|
|
|
69
72
|
* rather than an id to poll — the previous `enqueue` returned only
|
|
70
73
|
* `{ id, duplicate }` and had nowhere to put an answer.
|
|
71
74
|
*/
|
|
72
|
-
run<T>(key: string, handler: () => Promise<T> | T): QueueTask<T>;
|
|
75
|
+
run<Args extends unknown[], T>(key: string, handler: (...args: Args) => Promise<T> | T, ...args: Args): QueueTask<T>;
|
|
73
76
|
/** Remove a task that has not started. Running work is left alone. */
|
|
74
77
|
cancel(id: string): boolean;
|
|
75
78
|
/** Hold one key. Work already running for it finishes. */
|
|
76
79
|
pauseKey(key: string): void;
|
|
77
80
|
resumeKey(key: string): void;
|
|
81
|
+
/**
|
|
82
|
+
* Close one key and reject everything for it that has not started.
|
|
83
|
+
*
|
|
84
|
+
* JavaScript cannot safely kill an arbitrary running function. The current
|
|
85
|
+
* handler is therefore allowed to finish; every pending handler is removed,
|
|
86
|
+
* and future submissions are refused until `startKey()` is explicit.
|
|
87
|
+
*/
|
|
88
|
+
stopKey(key: string): number;
|
|
89
|
+
/** Re-open a key deliberately; pausing and stopping are not aliases. */
|
|
90
|
+
startKey(key: string): boolean;
|
|
78
91
|
/** Hold everything. New submissions are accepted and wait. */
|
|
79
92
|
pause(): void;
|
|
80
93
|
resume(): void;
|
|
@@ -85,6 +98,7 @@ export declare function createQueue(options?: QueueOptions): {
|
|
|
85
98
|
keys: number;
|
|
86
99
|
paused: boolean;
|
|
87
100
|
pausedKeys: string[];
|
|
101
|
+
stoppedKeys: string[];
|
|
88
102
|
completed: number;
|
|
89
103
|
failed: number;
|
|
90
104
|
};
|