@outerlayer/cli 0.2.0 → 0.4.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/CHANGELOG.md +132 -0
- package/README.md +229 -60
- package/dist/agent-setup-IFH3FB5X.js +197 -0
- package/dist/build-TZUUTRGH.js +9 -0
- package/dist/build-info.json +1 -1
- package/dist/check-7MFH6HNC.js +94 -0
- package/dist/chunk-3TFPDVTI.js +94 -0
- package/dist/{chunk-4TNZMO7V.js → chunk-4C4THABW.js} +5566 -754
- package/dist/{chunk-R5KBGVII.js → chunk-5QRP3MVS.js} +30 -14
- package/dist/chunk-5Y3QQRUH.js +300 -0
- package/dist/chunk-774HEQL6.js +178 -0
- package/dist/{work-pr-cmd-AZQPWH4H.js → chunk-7WERLFVR.js} +9 -26
- package/dist/chunk-ABIWDUMS.js +104 -0
- package/dist/chunk-AUFG23AA.js +534 -0
- package/dist/chunk-B2G7JHHB.js +42 -0
- package/dist/chunk-BCJNHMZT.js +34 -0
- package/dist/chunk-BCLJSQCV.js +56 -0
- package/dist/chunk-BFESLKPP.js +61 -0
- package/dist/chunk-BJ3KTMDH.js +63 -0
- package/dist/chunk-BKO6JEZI.js +47 -0
- package/dist/chunk-BOLTI6LR.js +37 -0
- package/dist/chunk-CBPPJSAR.js +89 -0
- package/dist/chunk-DM3VFDS3.js +1461 -0
- package/dist/{chunk-65WXAANR.js → chunk-DVDEBNQJ.js} +17 -2
- package/dist/{chunk-TFUIDMOB.js → chunk-EABW6AJQ.js} +12 -1
- package/dist/{chunk-KFYJV2ZG.js → chunk-EMY4I27X.js} +1 -1
- package/dist/{chunk-NTTPJV35.js → chunk-F47JBIAW.js} +3 -1
- package/dist/chunk-F6GFZXZ3.js +60 -0
- package/dist/{chunk-YOOSBOKS.js → chunk-F7CU5ABH.js} +1 -1
- package/dist/{emit-cmd-TWSYYEZI.js → chunk-H5NCNVDA.js} +68 -17
- package/dist/chunk-HZRNMGLF.js +2233 -0
- package/dist/chunk-LF6MLJCY.js +38 -0
- package/dist/{chunk-U32VSRLO.js → chunk-LT5TZHYH.js} +95 -634
- package/dist/chunk-LYCDNMOH.js +83 -0
- package/dist/chunk-M5POMKKW.js +1051 -0
- package/dist/{mcp-install-cmd-FDQH6SEN.js → chunk-MQ3IPHIZ.js} +35 -18
- package/dist/{chunk-D77LS3UI.js → chunk-N5FOE5PS.js} +33 -4
- package/dist/chunk-N6LURUEF.js +64 -0
- package/dist/chunk-OGFGZQA3.js +23 -0
- package/dist/chunk-QDQEUVUF.js +9 -0
- package/dist/chunk-QOZKPLVJ.js +226 -0
- package/dist/chunk-QY22NZUW.js +1764 -0
- package/dist/chunk-RA3O54FC.js +30 -0
- package/dist/chunk-RFUPN6KX.js +71 -0
- package/dist/chunk-SY4GK4SP.js +55 -0
- package/dist/chunk-TBT347UY.js +41 -0
- package/dist/chunk-TWNWNS2Q.js +1084 -0
- package/dist/{chunk-NXLLURA4.js → chunk-UFFXNXLQ.js} +115 -71
- package/dist/chunk-USO2DKBD.js +421 -0
- package/dist/chunk-W4SQCOZU.js +206 -0
- package/dist/{chunk-Q6CL5THG.js → chunk-W5FVUZXV.js} +208 -61
- package/dist/chunk-WFJ65NUD.js +383 -0
- package/dist/{chunk-A3WLZX2F.js → chunk-WIOZAJ2W.js} +15 -2
- package/dist/chunk-WO2BXCTQ.js +83 -0
- package/dist/{chunk-I3ETLSNE.js → chunk-XCCLFVXM.js} +120 -11
- package/dist/{context-materialize-6WKT3RBQ.js → chunk-XDDW4FRS.js} +134 -26
- package/dist/chunk-Y7KJLXYN.js +232 -0
- package/dist/chunk-YFHIIH4B.js +102 -0
- package/dist/chunk-YYYXRJUW.js +101 -0
- package/dist/chunk-ZM2IMMYN.js +76 -0
- package/dist/chunk-ZMYPLFG3.js +71 -0
- package/dist/chunk-ZNA27WEV.js +470 -0
- package/dist/{cli-G7DILYJY.js → cli-W62AFRSW.js} +616 -878
- package/dist/{paths-D2VGWWFI.js → cli-build-K56DK4DS.js} +1 -1
- package/dist/config-XVJYZ4IQ.js +9 -0
- package/dist/connect-cmd-ZTJONP37.js +16 -0
- package/dist/context-adopt-UMA4O5NY.js +105 -0
- package/dist/context-materialize-G2FUFC72.js +19 -0
- package/dist/dist-44CQVWGT.js +6 -0
- package/dist/docker-NEGDLU6D.js +7 -0
- package/dist/doctor-53F2PYY7.js +43 -0
- package/dist/doctor-ZLGZYMDK.js +426 -0
- package/dist/{emit-artifact-cmd-UQVT3OKW.js → emit-artifact-cmd-AKZP7TMF.js} +87 -23
- package/dist/emit-cmd-DCERAU27.js +9 -0
- package/dist/emit-criteria-cmd-7MXQVCKD.js +166 -0
- package/dist/{emit-finding-cmd-FDOU2OPD.js → emit-finding-cmd-N2FTGCVC.js} +34 -31
- package/dist/{emit-result-cmd-H2T4C2K3.js → emit-result-cmd-4CPDBMN2.js} +34 -62
- package/dist/exec-client-4XGXV3XN.js +8 -0
- package/dist/guest-init-GENRHNP7.js +8 -0
- package/dist/hook-fast-EYENPK6F.js +9 -0
- package/dist/{hook-wrap-fast-KXYNX3AD.js → hook-wrap-fast-NPLHJXFK.js} +2 -2
- package/dist/host-key-KDYZ5ECC.js +7 -0
- package/dist/import-capture-cmd-AVZ4VIVJ.js +68 -0
- package/dist/{import-ruler-cmd-7LH2QOMG.js → import-ruler-cmd-GUHL6IY5.js} +1 -1
- package/dist/index.js +4 -4
- package/dist/init-55Y4LHWF.js +36 -0
- package/dist/init-XXEKWZQP.js +221 -0
- package/dist/init-cmd-WLST2F7G.js +135 -0
- package/dist/install-cmd-7WXOIFDO.js +55 -0
- package/dist/lima-XN65D7GN.js +55 -0
- package/dist/login-browser-7XGSV7MA.js +10 -0
- package/dist/{config-POF7DEQW.js → logout-cmd-QAUFDJ6J.js} +3 -1
- package/dist/{logs-TRNPQM42.js → logs-7RYUH5A6.js} +1 -1
- package/dist/loop-JFIBP4B7.js +43 -0
- package/dist/machine-ZBPT2J3R.js +35 -0
- package/dist/mcp-install-cmd-RLK4SO3N.js +9 -0
- package/dist/{mcp-serve-cmd-57EZZOTL.js → mcp-serve-cmd-N6UD2KBX.js} +20 -9
- package/dist/paths-OYKMVYJP.js +6 -0
- package/dist/{pidfile-PTW76F56.js → pidfile-UZRH774M.js} +2 -3
- package/dist/real-deps-SU24ZA2K.js +21 -0
- package/dist/relay-L76HDX72.js +46 -0
- package/dist/settings-J2652U5N.js +7 -0
- package/dist/starter-pack-LIZYMKYQ.js +10 -0
- package/dist/{status-I27IST4L.js → status-UJXWZRN5.js} +30 -13
- package/dist/{statusline-fast-3C5OXHDD.js → statusline-fast-SVTMBMD7.js} +3 -2
- package/dist/sync-cmd-4G4A7EVN.js +28 -0
- package/dist/version-BWM6VLDI.js +6 -0
- package/dist/{watch-3DTPJETH.js → watch-7SKJKGAA.js} +19 -8
- package/dist/{work-claim-cmd-5AUNCLS2.js → work-claim-cmd-5F7VZ42N.js} +34 -21
- package/dist/work-cmd-QAUZ7MTD.js +17 -0
- package/dist/work-comment-cmd-BNCSBTLH.js +106 -0
- package/dist/{work-launch-LT663PB3.js → work-launch-YI4CJDAP.js} +1 -1
- package/dist/work-open-pr-cmd-VFYZHIIW.js +156 -0
- package/dist/work-pr-cmd-PV32ZLSI.js +16 -0
- package/package.json +13 -3
- package/skill-pack/maintained/amend/SKILL.md +104 -0
- package/skill-pack/maintained/emitting-evidence/SKILL.md +108 -0
- package/skill-pack/maintained/emitting-evidence/references/agents-snippet.md +20 -0
- package/skill-pack/maintained/outerlayer/SKILL.md +55 -0
- package/skill-pack/maintained/reporting-findings/SKILL.md +148 -0
- package/skill-pack/template/build/SKILL.md +193 -0
- package/skill-pack/template/build/references/agent-briefs.md +243 -0
- package/skill-pack/template/build/references/criteria-judge.md +91 -0
- package/skill-pack/template/build/references/evidence.md +42 -0
- package/skill-pack/template/build/references/release.md +74 -0
- package/skill-pack/template/build/references/review-briefs.md +275 -0
- package/skill-pack/template/build/references/review-loop.md +158 -0
- package/skill-pack/template/build/scripts/record-criteria.mjs +235 -0
- package/skill-pack/template/spec/SKILL.md +84 -0
- package/skill-pack/template/writing-specs/SKILL.md +134 -0
- package/dist/chunk-DCNOXRMV.js +0 -589
- package/dist/chunk-JJP7YLMN.js +0 -25
- package/dist/chunk-OZ7C3XUE.js +0 -34
- package/dist/chunk-WQ6VGRGZ.js +0 -150
- package/dist/hook-fast-J5LCDHUJ.js +0 -8
- package/dist/import-capture-cmd-EUMBGIV3.js +0 -176
- package/dist/init-PTBITAUO.js +0 -103
- package/dist/login-cmd-IRX6LZT7.js +0 -62
- package/dist/loop-ASZRX3CZ.js +0 -648
- package/dist/sync-cmd-BBAUZ5JD.js +0 -17
- package/dist/work-cmd-2P4BVX47.js +0 -18
package/dist/loop-ASZRX3CZ.js
DELETED
|
@@ -1,648 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { createRequire } from 'node:module';
|
|
3
|
-
import { loadRunnerConfig, checkHooks } from './chunk-WQ6VGRGZ.js';
|
|
4
|
-
import { claimPidFile, readPidFile, releasePidFile } from './chunk-D77LS3UI.js';
|
|
5
|
-
import { describeCallFailure, retrySend, LEASE_SECONDS, shortClaim, isUnfinished, writeJobFile, relativeToRunner, setStage, jobEnv, runHook, formatDuration, describeHookEnd, readProvisionOut, workdirProblem, runCommand, finishJob, isGroupAlive, outcomeFromExitCode, needsRenew, renewLease, formatClock, RENEW_INTERVAL_SECONDS, isTimedOut, readCpuUsec, isIdle, createRunnerLog, listAdoptableJobs, retryFailedReleases, recoverLostJobs, readJobFile, hookLimitsFor } from './chunk-DCNOXRMV.js';
|
|
6
|
-
import { startTimeOf, isSameProcess } from './chunk-OZ7C3XUE.js';
|
|
7
|
-
import { RUNNER_DIR_MODE, runnerDir, attemptStamp, jobDir, jobsDir, pidFilePath } from './chunk-JJP7YLMN.js';
|
|
8
|
-
import { WorkCommandError, getJson } from './chunk-Q6CL5THG.js';
|
|
9
|
-
import { RepoFilter } from './chunk-U32VSRLO.js';
|
|
10
|
-
import './chunk-4TNZMO7V.js';
|
|
11
|
-
import './chunk-YRYMNBGQ.js';
|
|
12
|
-
import './chunk-65WXAANR.js';
|
|
13
|
-
import './chunk-WZBHBH3J.js';
|
|
14
|
-
import './chunk-ZJSNLVCE.js';
|
|
15
|
-
import './chunk-NXLLURA4.js';
|
|
16
|
-
import './chunk-I3ETLSNE.js';
|
|
17
|
-
import './chunk-YOOSBOKS.js';
|
|
18
|
-
import './chunk-R5KBGVII.js';
|
|
19
|
-
import './chunk-TFUIDMOB.js';
|
|
20
|
-
import './chunk-VNVZDWO3.js';
|
|
21
|
-
import './chunk-Z5GEFMRW.js';
|
|
22
|
-
import { mkdirSync, existsSync, statSync, readdirSync } from 'fs';
|
|
23
|
-
import { join } from 'path';
|
|
24
|
-
|
|
25
|
-
createRequire(import.meta.url);
|
|
26
|
-
var QUEUE_ORDER = ["amend", "implement"];
|
|
27
|
-
var TRACKER_REPOSITORY_HOSTS = {
|
|
28
|
-
github: "github.com",
|
|
29
|
-
gitlab: "gitlab.com"
|
|
30
|
-
};
|
|
31
|
-
function logOf(state) {
|
|
32
|
-
state.deps.log ??= createRunnerLog(state.deps.now ?? (() => /* @__PURE__ */ new Date()));
|
|
33
|
-
return state.deps.log;
|
|
34
|
-
}
|
|
35
|
-
function nowOf(state) {
|
|
36
|
-
return (state.deps.now ?? (() => /* @__PURE__ */ new Date()))();
|
|
37
|
-
}
|
|
38
|
-
var DRAIN_LOG_EVERY_PASSES = 30;
|
|
39
|
-
var REPEAT_EVERY_TICKS = 10;
|
|
40
|
-
function reportRepeating(state, key, message, level = "error") {
|
|
41
|
-
const log = logOf(state);
|
|
42
|
-
const repeats = state.repeats ??= /* @__PURE__ */ new Map();
|
|
43
|
-
const seen = repeats.get(key);
|
|
44
|
-
if (message === void 0) {
|
|
45
|
-
if (seen) {
|
|
46
|
-
if (level === "error") log.line(`recovered: ${key} is working again, after ${seen.ticks} tick(s) failing`);
|
|
47
|
-
repeats.delete(key);
|
|
48
|
-
}
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
if (!seen || seen.message !== message) {
|
|
52
|
-
repeats.set(key, { message, ticks: 1 });
|
|
53
|
-
log[level](message);
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
seen.ticks++;
|
|
57
|
-
if (seen.ticks % REPEAT_EVERY_TICKS === 0) {
|
|
58
|
-
log[level](`still failing: ${message} (${seen.ticks} ticks)`);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
function deriveRepo(item) {
|
|
62
|
-
const host = item.tracker === void 0 ? void 0 : TRACKER_REPOSITORY_HOSTS[item.tracker];
|
|
63
|
-
if (host === void 0) return void 0;
|
|
64
|
-
const hash = item.trackerKey.lastIndexOf("#");
|
|
65
|
-
const repoPart = hash === -1 ? item.trackerKey : item.trackerKey.slice(0, hash);
|
|
66
|
-
return repoPart === "" ? void 0 : `${host}/${repoPart}`;
|
|
67
|
-
}
|
|
68
|
-
async function listStartable(config, queue, fetchImpl) {
|
|
69
|
-
const query = queue === "amend" ? "needs=amend&startable=true" : "startable=true";
|
|
70
|
-
const page = await getJson(config.url, `/v1/work-items?${query}`, config.apiKey, config.appId, fetchImpl);
|
|
71
|
-
return Array.isArray(page.data) ? page.data : [];
|
|
72
|
-
}
|
|
73
|
-
function reloadConfig(state) {
|
|
74
|
-
let mtimeMs;
|
|
75
|
-
try {
|
|
76
|
-
mtimeMs = statSync(state.config.configPath).mtimeMs;
|
|
77
|
-
} catch {
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
if (state.configMtimeMs === mtimeMs) return;
|
|
81
|
-
state.configMtimeMs = mtimeMs;
|
|
82
|
-
try {
|
|
83
|
-
const reloaded = loadRunnerConfig(state.config.configPath);
|
|
84
|
-
const hookProblem = checkHooks(reloaded);
|
|
85
|
-
if (hookProblem) {
|
|
86
|
-
logOf(state).error(`config re-read rejected: ${hookProblem} \xB7 keeping the last good config`);
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
state.config = reloaded;
|
|
90
|
-
logOf(state).line(`config re-read \xB7 ${describeConfig(reloaded)}`);
|
|
91
|
-
} catch (err) {
|
|
92
|
-
logOf(state).error(`config re-read rejected: ${describeCallFailure(err)} \xB7 keeping the last good config`);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
function describeConfig(config) {
|
|
96
|
-
return `queues ${config.queues.join(",")} \xB7 concurrency ${config.concurrency} \xB7 poll ${config.pollSeconds}s`;
|
|
97
|
-
}
|
|
98
|
-
async function tick(state) {
|
|
99
|
-
if (state.ticking) return;
|
|
100
|
-
state.ticking = true;
|
|
101
|
-
try {
|
|
102
|
-
await runTick(state);
|
|
103
|
-
} catch (err) {
|
|
104
|
-
logOf(state).error(`unexpected error: ${describeCallFailure(err)}`);
|
|
105
|
-
} finally {
|
|
106
|
-
state.ticking = false;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
async function runTick(state) {
|
|
110
|
-
reloadConfig(state);
|
|
111
|
-
const log = logOf(state);
|
|
112
|
-
const repoFilter = new RepoFilter(state.config.repos);
|
|
113
|
-
let filtered = 0;
|
|
114
|
-
let attempts = 0;
|
|
115
|
-
while (state.jobs.size < state.config.concurrency && attempts < state.config.concurrency) {
|
|
116
|
-
attempts++;
|
|
117
|
-
let claimed = false;
|
|
118
|
-
for (const queue of QUEUE_ORDER) {
|
|
119
|
-
if (!state.config.queues.includes(queue)) continue;
|
|
120
|
-
let items;
|
|
121
|
-
try {
|
|
122
|
-
items = await listStartable(state.config, queue, state.deps.fetchImpl);
|
|
123
|
-
reportRepeating(state, "the work item list", void 0);
|
|
124
|
-
} catch (err) {
|
|
125
|
-
reportRepeating(state, "the work item list", `list failed: ${describeCallFailure(err)} \xB7 retrying next tick`);
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
for (const item of items) {
|
|
129
|
-
if (item.startable === false) continue;
|
|
130
|
-
if (!repoFilter.allows(deriveRepo(item))) {
|
|
131
|
-
filtered++;
|
|
132
|
-
continue;
|
|
133
|
-
}
|
|
134
|
-
const attempt = await tryClaimAndStart(state, item, queue);
|
|
135
|
-
if (attempt.kind === "started") {
|
|
136
|
-
claimed = true;
|
|
137
|
-
break;
|
|
138
|
-
}
|
|
139
|
-
if (attempt.kind === "error") {
|
|
140
|
-
log.error(`claim for #${item.number} failed: ${attempt.reason} \xB7 retrying next tick`);
|
|
141
|
-
return;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
if (claimed) break;
|
|
145
|
-
}
|
|
146
|
-
if (!claimed) break;
|
|
147
|
-
}
|
|
148
|
-
reportRepeating(
|
|
149
|
-
state,
|
|
150
|
-
"the repository filter",
|
|
151
|
-
filtered === 0 ? void 0 : `skipped ${filtered} listed item(s) not allowed by repos.include/exclude`,
|
|
152
|
-
"line"
|
|
153
|
-
);
|
|
154
|
-
}
|
|
155
|
-
async function tryClaimAndStart(state, item, queue) {
|
|
156
|
-
const { postJson } = await import('./work-cmd-2P4BVX47.js');
|
|
157
|
-
const log = logOf(state);
|
|
158
|
-
const claim = await retrySend(
|
|
159
|
-
() => postJson(
|
|
160
|
-
state.config.url,
|
|
161
|
-
`/v1/work-items/${item.number}/claim`,
|
|
162
|
-
state.config.apiKey,
|
|
163
|
-
state.config.appId,
|
|
164
|
-
{ host: state.config.host, kind: queue, seconds: LEASE_SECONDS },
|
|
165
|
-
state.deps.fetchImpl
|
|
166
|
-
),
|
|
167
|
-
{ now: state.deps.now, sleep: state.deps.sleep }
|
|
168
|
-
);
|
|
169
|
-
if (!claim.ok) {
|
|
170
|
-
if (claim.reason.includes("(409)") || /work_item_claimed|held by/i.test(claim.reason)) {
|
|
171
|
-
log.line(`#${item.number} claim refused: ${claim.reason}`);
|
|
172
|
-
return { kind: "held" };
|
|
173
|
-
}
|
|
174
|
-
return { kind: "error", reason: `${claim.reason} (${claim.attempts} attempt(s))` };
|
|
175
|
-
}
|
|
176
|
-
const { data } = claim.value;
|
|
177
|
-
const claimId = data.id === void 0 || data.id === null ? "" : String(data.id);
|
|
178
|
-
if (claimId === "") {
|
|
179
|
-
return { kind: "error", reason: "the claim response carried no claim id" };
|
|
180
|
-
}
|
|
181
|
-
if (wasExtended(data)) {
|
|
182
|
-
log.line(
|
|
183
|
-
`#${item.number} skipped: the claim extended a live lease under host "${state.config.host}" \xB7 set a distinct runner.host per config file`
|
|
184
|
-
);
|
|
185
|
-
return { kind: "held" };
|
|
186
|
-
}
|
|
187
|
-
if (claimIsAlreadyRunning(state, claimId)) {
|
|
188
|
-
log.line(`#${item.number} skipped: claim ${shortClaim(claimId)} is already running here`);
|
|
189
|
-
return { kind: "held" };
|
|
190
|
-
}
|
|
191
|
-
await startJob(state, item, queue, claimId);
|
|
192
|
-
return { kind: "started" };
|
|
193
|
-
}
|
|
194
|
-
function claimIsAlreadyRunning(state, claimId) {
|
|
195
|
-
for (const active of state.jobs.values()) {
|
|
196
|
-
if (active.job.claimId === claimId) return true;
|
|
197
|
-
}
|
|
198
|
-
for (const { job } of listJobFiles(state.config.configPath)) {
|
|
199
|
-
if (job.claimId === claimId && isUnfinished(job)) return true;
|
|
200
|
-
}
|
|
201
|
-
return false;
|
|
202
|
-
}
|
|
203
|
-
function wasExtended(claim) {
|
|
204
|
-
const createdAtMs = Date.parse(String(claim.createdAt ?? ""));
|
|
205
|
-
const expiresAtMs = Date.parse(String(claim.expiresAt ?? ""));
|
|
206
|
-
if (!Number.isFinite(createdAtMs) || !Number.isFinite(expiresAtMs)) return false;
|
|
207
|
-
return expiresAtMs - createdAtMs > LEASE_SECONDS * 1e3 + 2e3;
|
|
208
|
-
}
|
|
209
|
-
async function startJob(state, item, queue, claimId) {
|
|
210
|
-
const log = logOf(state);
|
|
211
|
-
const startedAt = nowOf(state);
|
|
212
|
-
const dirPath = freeJobDir(state.config.configPath, queue, item.number, claimId, startedAt);
|
|
213
|
-
mkdirSync(dirPath, { recursive: true, mode: RUNNER_DIR_MODE });
|
|
214
|
-
const runnerRoot = runnerDir(state.config.configPath);
|
|
215
|
-
const jobFilePath = join(dirPath, "job.json");
|
|
216
|
-
let job = { item: item.number, queue, claimId, startedAt: startedAt.toISOString() };
|
|
217
|
-
writeJobFile(jobFilePath, job);
|
|
218
|
-
const active = {
|
|
219
|
-
job,
|
|
220
|
-
dirPath,
|
|
221
|
-
lastRenewAtMs: startedAt.getTime(),
|
|
222
|
-
cmd: null,
|
|
223
|
-
cpuSample: null,
|
|
224
|
-
ending: false
|
|
225
|
-
};
|
|
226
|
-
state.jobs.set(dirPath, active);
|
|
227
|
-
log.line(
|
|
228
|
-
`claimed ${queue} #${item.number} ${item.trackerKey} (claim ${shortClaim(claimId)}) \u2192 ${relativeToRunner(runnerRoot, dirPath)}`
|
|
229
|
-
);
|
|
230
|
-
const hookLimits = () => hookLimitsFor(state.config, startedAt.getTime(), state.deps.now);
|
|
231
|
-
const tag = `#${item.number}`;
|
|
232
|
-
if (state.config.hooks.report) {
|
|
233
|
-
setStage(job, dirPath, "report");
|
|
234
|
-
log.line(`${tag} report started \xB7 phase start`);
|
|
235
|
-
const reportStartedMs = nowOf(state).getTime();
|
|
236
|
-
const startEnv = { ...jobEnv(state.config, item.number, dirPath), OUTERLAYER_PHASE: "start" };
|
|
237
|
-
const report = await runHook(
|
|
238
|
-
state.config.hooks.report,
|
|
239
|
-
startEnv,
|
|
240
|
-
dirPath,
|
|
241
|
-
join(dirPath, "report.log"),
|
|
242
|
-
state.deps.spawnFn,
|
|
243
|
-
hookLimits()
|
|
244
|
-
).catch((err) => {
|
|
245
|
-
log.error(`${tag} report failed: ${describeCallFailure(err)} \xB7 continuing`);
|
|
246
|
-
return { code: -1 };
|
|
247
|
-
});
|
|
248
|
-
const took = formatDuration(nowOf(state).getTime() - reportStartedMs);
|
|
249
|
-
if (report.endedBy !== void 0) {
|
|
250
|
-
log.error(`${tag} ${describeHookEnd("report", report.endedBy, state.config)} \xB7 continuing`);
|
|
251
|
-
} else if (report.code === 0) {
|
|
252
|
-
log.line(`${tag} report ok (${took})`);
|
|
253
|
-
} else if (report.code > 0) {
|
|
254
|
-
log.error(`${tag} report exited ${report.code} (${took}) \xB7 continuing`);
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
setStage(job, dirPath, "provision");
|
|
258
|
-
log.line(`${tag} provision started`);
|
|
259
|
-
const provisionStartedMs = nowOf(state).getTime();
|
|
260
|
-
const provisionEnv = jobEnv(state.config, item.number, dirPath);
|
|
261
|
-
const provision = await runHook(
|
|
262
|
-
state.config.hooks.provision,
|
|
263
|
-
provisionEnv,
|
|
264
|
-
dirPath,
|
|
265
|
-
join(dirPath, "provision.log"),
|
|
266
|
-
state.deps.spawnFn,
|
|
267
|
-
hookLimits()
|
|
268
|
-
).catch((err) => {
|
|
269
|
-
log.error(`${tag} provision failed: ${describeCallFailure(err)}`);
|
|
270
|
-
return { code: -1 };
|
|
271
|
-
});
|
|
272
|
-
const provisionExit = provision.code;
|
|
273
|
-
const provisionTook = formatDuration(nowOf(state).getTime() - provisionStartedMs);
|
|
274
|
-
if (active.ending) return;
|
|
275
|
-
if (provision.endedBy !== void 0) {
|
|
276
|
-
log.error(`${tag} ${describeHookEnd("provision", provision.endedBy, state.config)}`);
|
|
277
|
-
await endJob(state, active, "failed");
|
|
278
|
-
return;
|
|
279
|
-
}
|
|
280
|
-
if (provisionExit === 75) {
|
|
281
|
-
log.line(`${tag} provision declined the item (exit 75, ${provisionTook})`);
|
|
282
|
-
await endJob(state, active, void 0);
|
|
283
|
-
return;
|
|
284
|
-
}
|
|
285
|
-
if (provisionExit !== 0) {
|
|
286
|
-
log.error(`${tag} provision exited ${provisionExit} (${provisionTook}) \xB7 ending job as failed`);
|
|
287
|
-
await endJob(state, active, "failed");
|
|
288
|
-
return;
|
|
289
|
-
}
|
|
290
|
-
const provisioned = readProvisionOut(dirPath);
|
|
291
|
-
if (!provisioned.ok) {
|
|
292
|
-
log.error(`${tag} provision wrote no workdir= line to provision.out \xB7 ending job as failed`);
|
|
293
|
-
await endJob(state, active, "failed");
|
|
294
|
-
return;
|
|
295
|
-
}
|
|
296
|
-
const badWorkdir = workdirProblem(provisioned.workdir);
|
|
297
|
-
if (badWorkdir !== void 0) {
|
|
298
|
-
log.error(
|
|
299
|
-
`${tag} provision reported a workdir that ${badWorkdir}: ${provisioned.workdir} \xB7 ending job as failed`
|
|
300
|
-
);
|
|
301
|
-
await endJob(state, active, "failed");
|
|
302
|
-
return;
|
|
303
|
-
}
|
|
304
|
-
log.line(`${tag} provision ok (${provisionTook}) \xB7 workdir ${relativeToRunner(runnerRoot, provisioned.workdir)}`);
|
|
305
|
-
job = { ...job, workdir: provisioned.workdir, cgroup: provisioned.cgroup };
|
|
306
|
-
active.job = job;
|
|
307
|
-
writeJobFile(jobFilePath, job);
|
|
308
|
-
const cmdline = state.config.commands[queue];
|
|
309
|
-
if (!cmdline) {
|
|
310
|
-
log.error(`${tag} has no runner.commands.${queue} in the config file \xB7 ending job as failed`);
|
|
311
|
-
await endJob(state, active, "failed");
|
|
312
|
-
return;
|
|
313
|
-
}
|
|
314
|
-
const commandEnv = jobEnv(state.config, item.number, dirPath);
|
|
315
|
-
let running;
|
|
316
|
-
try {
|
|
317
|
-
running = runCommand(cmdline, commandEnv, provisioned.workdir, join(dirPath, "command.log"), state.deps.spawnFn);
|
|
318
|
-
} catch (err) {
|
|
319
|
-
log.error(`${tag} command could not start: ${describeCallFailure(err)} \xB7 ending job as failed`);
|
|
320
|
-
await endJob(state, active, "failed");
|
|
321
|
-
return;
|
|
322
|
-
}
|
|
323
|
-
active.cmd = running;
|
|
324
|
-
job = { ...job, stage: "command", pid: running.pid, start: running.start ?? void 0 };
|
|
325
|
-
active.job = job;
|
|
326
|
-
writeJobFile(jobFilePath, job);
|
|
327
|
-
const commandStartedMs = nowOf(state).getTime();
|
|
328
|
-
log.line(`${tag} command started \xB7 pid ${running.pid}`);
|
|
329
|
-
running.onExit.then(({ code, signal, error }) => {
|
|
330
|
-
if (active.ending) return;
|
|
331
|
-
if (error !== void 0) {
|
|
332
|
-
log.error(`${tag} command could not start: ${error.message} \xB7 ending job as failed`);
|
|
333
|
-
void endJob(state, active, "failed");
|
|
334
|
-
return;
|
|
335
|
-
}
|
|
336
|
-
const took = formatDuration(nowOf(state).getTime() - commandStartedMs);
|
|
337
|
-
if (signal !== null) {
|
|
338
|
-
log.error(`${tag} command killed by ${signal} (${took})`);
|
|
339
|
-
void endJob(state, active, "failed");
|
|
340
|
-
return;
|
|
341
|
-
}
|
|
342
|
-
log.line(`${tag} command exited ${code ?? 0} (${took})`);
|
|
343
|
-
if (code !== null) active.job.exitCode = code;
|
|
344
|
-
void endJob(state, active, code === 0 ? "ok" : "failed");
|
|
345
|
-
});
|
|
346
|
-
}
|
|
347
|
-
function freeJobDir(configPath, queue, item, claimId, startedAt) {
|
|
348
|
-
const stamp = attemptStamp(startedAt);
|
|
349
|
-
for (let i = 0; i < 100; i++) {
|
|
350
|
-
const candidate = jobDir(configPath, queue, item, claimId, i === 0 ? stamp : `${stamp}-${i}`);
|
|
351
|
-
if (!existsSync(candidate)) return candidate;
|
|
352
|
-
}
|
|
353
|
-
return jobDir(configPath, queue, item, claimId, `${stamp}-${process.pid}`);
|
|
354
|
-
}
|
|
355
|
-
async function endJob(state, active, outcome) {
|
|
356
|
-
if (active.ending) return;
|
|
357
|
-
active.ending = true;
|
|
358
|
-
try {
|
|
359
|
-
const finished = await finishJob(active.job, active.dirPath, outcome, state.config, {
|
|
360
|
-
fetchImpl: state.deps.fetchImpl,
|
|
361
|
-
spawnFn: state.deps.spawnFn,
|
|
362
|
-
now: state.deps.now,
|
|
363
|
-
sleep: state.deps.sleep,
|
|
364
|
-
log: logOf(state)
|
|
365
|
-
});
|
|
366
|
-
active.job = finished;
|
|
367
|
-
} catch (err) {
|
|
368
|
-
logOf(state).error(`#${active.job.item} could not be ended cleanly: ${describeCallFailure(err)}`);
|
|
369
|
-
} finally {
|
|
370
|
-
state.jobs.delete(active.dirPath);
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
async function watchTick(state, active, nowMs, logMtimeMs) {
|
|
374
|
-
if (active.ending) return;
|
|
375
|
-
const log = logOf(state);
|
|
376
|
-
const tag = `#${active.job.item}`;
|
|
377
|
-
const pid = active.job.pid;
|
|
378
|
-
if (pid !== void 0 && !isGroupAlive(pid)) {
|
|
379
|
-
if (active.cmd === null) {
|
|
380
|
-
const recorded = outcomeFromExitCode(active.dirPath);
|
|
381
|
-
if (recorded === void 0) {
|
|
382
|
-
log.line(`${tag} command gone, no exit code recorded \xB7 ending job as interrupted`);
|
|
383
|
-
await endJob(state, active, "interrupted");
|
|
384
|
-
} else {
|
|
385
|
-
log.line(`${tag} command exited ${recorded.code}`);
|
|
386
|
-
active.job.exitCode = recorded.code;
|
|
387
|
-
await endJob(state, active, recorded.outcome);
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
return;
|
|
391
|
-
}
|
|
392
|
-
if (needsRenew(active.lastRenewAtMs, nowMs)) {
|
|
393
|
-
const renew = await retrySend(() => renewLease(state.config, active.job.item, state.deps.fetchImpl), {
|
|
394
|
-
now: state.deps.now,
|
|
395
|
-
sleep: state.deps.sleep
|
|
396
|
-
});
|
|
397
|
-
const result = renew.ok ? renew.value : "retry_later";
|
|
398
|
-
if (result === "lease_lost") {
|
|
399
|
-
log.error(`${tag} lease lost \xB7 ending job as lease_lost`);
|
|
400
|
-
await endJob(state, active, "lease_lost");
|
|
401
|
-
return;
|
|
402
|
-
}
|
|
403
|
-
if (result === "renewed") {
|
|
404
|
-
active.job.lastRenewAt = new Date(nowMs).toISOString();
|
|
405
|
-
writeJobFile(join(active.dirPath, "job.json"), active.job);
|
|
406
|
-
log.line(`${tag} lease renewed \xB7 expires ${formatClock(new Date(nowMs + LEASE_SECONDS * 1e3), new Date(nowMs))}`);
|
|
407
|
-
} else {
|
|
408
|
-
log.error(`${tag} renew did not land \xB7 retrying in ${RENEW_INTERVAL_SECONDS / 60}m`);
|
|
409
|
-
}
|
|
410
|
-
active.lastRenewAtMs = nowMs;
|
|
411
|
-
}
|
|
412
|
-
if (isTimedOut(new Date(active.job.startedAt).getTime(), nowMs, state.config.timeLimitMinutes)) {
|
|
413
|
-
log.line(`${tag} past the ${state.config.timeLimitMinutes}m time limit \xB7 ending job as timed_out`);
|
|
414
|
-
await endJob(state, active, "timed_out");
|
|
415
|
-
return;
|
|
416
|
-
}
|
|
417
|
-
if (pid === void 0) return;
|
|
418
|
-
let cpuUsecPerMinute = null;
|
|
419
|
-
if (active.job.cgroup) {
|
|
420
|
-
const usec = readCpuUsec(active.job.cgroup);
|
|
421
|
-
if (usec !== null && active.cpuSample) {
|
|
422
|
-
const deltaUsec = usec - active.cpuSample.usec;
|
|
423
|
-
const deltaMinutes = (nowMs - active.cpuSample.atMs) / 6e4;
|
|
424
|
-
cpuUsecPerMinute = deltaMinutes > 0 ? deltaUsec / deltaMinutes : null;
|
|
425
|
-
}
|
|
426
|
-
if (usec !== null) active.cpuSample = { usec, atMs: nowMs };
|
|
427
|
-
}
|
|
428
|
-
if (isIdle(logMtimeMs, nowMs, state.config.idleLimitMinutes, cpuUsecPerMinute)) {
|
|
429
|
-
log.line(`${tag} no output for ${state.config.idleLimitMinutes}m \xB7 ending job as idle`);
|
|
430
|
-
await endJob(state, active, "idle");
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
async function probeCredentials(config, fetchImpl = fetch) {
|
|
434
|
-
try {
|
|
435
|
-
await listStartable(config, config.queues[0] ?? "implement", fetchImpl);
|
|
436
|
-
return { ok: true };
|
|
437
|
-
} catch (err) {
|
|
438
|
-
const status = err instanceof WorkCommandError ? err.status : void 0;
|
|
439
|
-
if (status === 401 || status === 403) {
|
|
440
|
-
return {
|
|
441
|
-
ok: false,
|
|
442
|
-
fatal: true,
|
|
443
|
-
reason: `${config.configPath}: the API key was refused (${status}). A runner key needs git.read and work.read to list and read work items, and work.claim to claim, renew and release.`
|
|
444
|
-
};
|
|
445
|
-
}
|
|
446
|
-
return { ok: false, fatal: false, reason: describeCallFailure(err) };
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
async function start(configPath, deps = { fetchImpl: fetch }) {
|
|
450
|
-
const config = loadRunnerConfig(configPath);
|
|
451
|
-
const hookProblem = checkHooks(config);
|
|
452
|
-
if (hookProblem) throw new RunnerStartError(hookProblem);
|
|
453
|
-
const log = deps.log ??= createRunnerLog(deps.now ?? (() => /* @__PURE__ */ new Date()));
|
|
454
|
-
const probe = await probeCredentials(config, deps.fetchImpl);
|
|
455
|
-
if (!probe.ok && probe.fatal) throw new RunnerStartError(probe.reason);
|
|
456
|
-
if (!probe.ok) log.error(`the gateway did not answer at start: ${probe.reason} \xB7 starting anyway`);
|
|
457
|
-
mkdirSync(jobsDir(configPath), { recursive: true, mode: RUNNER_DIR_MODE });
|
|
458
|
-
const self = { pid: process.pid, start: startTimeOf(process.pid) ?? "" };
|
|
459
|
-
claimPidFile(pidFilePath(configPath), self);
|
|
460
|
-
let configMtimeMs;
|
|
461
|
-
try {
|
|
462
|
-
configMtimeMs = statSync(configPath).mtimeMs;
|
|
463
|
-
} catch {
|
|
464
|
-
configMtimeMs = void 0;
|
|
465
|
-
}
|
|
466
|
-
const state = { config, jobs: /* @__PURE__ */ new Map(), deps, configMtimeMs };
|
|
467
|
-
log.line(`runner ${config.host} started \xB7 config ${configPath} \xB7 ${describeConfig(config)}`);
|
|
468
|
-
const nowMs = () => deps.now ? deps.now().getTime() : Date.now();
|
|
469
|
-
for (const { dirPath, job } of listAdoptableJobs(jobsDir(configPath))) {
|
|
470
|
-
state.jobs.set(dirPath, { job, dirPath, lastRenewAtMs: nowMs(), cmd: null, cpuSample: null, ending: false });
|
|
471
|
-
log.line(`adopted job ${job.queue} #${job.item} (claim ${shortClaim(job.claimId)}) \xB7 pid ${job.pid} still running`);
|
|
472
|
-
}
|
|
473
|
-
for (const { job, released } of await retryFailedReleases(jobsDir(configPath), config, {
|
|
474
|
-
fetchImpl: deps.fetchImpl,
|
|
475
|
-
now: deps.now,
|
|
476
|
-
log
|
|
477
|
-
})) {
|
|
478
|
-
log.line(
|
|
479
|
-
released ? `released the lease on #${job.item} that an earlier attempt could not \xB7 outcome ${job.outcome ?? "none"}` : `the lease on #${job.item} still cannot be released: ${job.releaseFailed ?? "no reason recorded"}`
|
|
480
|
-
);
|
|
481
|
-
}
|
|
482
|
-
for (const recovered of await recoverLostJobs(jobsDir(configPath), config, {
|
|
483
|
-
fetchImpl: deps.fetchImpl,
|
|
484
|
-
spawnFn: deps.spawnFn,
|
|
485
|
-
now: deps.now,
|
|
486
|
-
log
|
|
487
|
-
})) {
|
|
488
|
-
const why = recovered.exitCode === void 0 ? `pid ${recovered.pid ?? "unknown"} is gone, no exit code recorded` : `its command recorded exit ${recovered.exitCode}`;
|
|
489
|
-
log.line(
|
|
490
|
-
`recovered job ${recovered.queue} #${recovered.item} (claim ${shortClaim(recovered.claimId)}) as ${recovered.outcome ?? "interrupted"}: ${why}`
|
|
491
|
-
);
|
|
492
|
-
}
|
|
493
|
-
let intakeClosed = false;
|
|
494
|
-
let closed = false;
|
|
495
|
-
let draining = false;
|
|
496
|
-
let stopping = false;
|
|
497
|
-
let exited = false;
|
|
498
|
-
let interrupts = 0;
|
|
499
|
-
const pollTimer = setInterval(() => void tick(state), state.config.pollSeconds * 1e3);
|
|
500
|
-
const watchTimer = setInterval(() => {
|
|
501
|
-
const now = nowMs();
|
|
502
|
-
for (const active of state.jobs.values()) {
|
|
503
|
-
let mtime = now;
|
|
504
|
-
try {
|
|
505
|
-
mtime = statSync(join(active.dirPath, "command.log")).mtimeMs;
|
|
506
|
-
} catch {
|
|
507
|
-
}
|
|
508
|
-
void watchTick(state, active, now, mtime).catch((err) => {
|
|
509
|
-
log.error(`#${active.job.item} health check failed: ${describeCallFailure(err)}`);
|
|
510
|
-
});
|
|
511
|
-
}
|
|
512
|
-
}, deps.watchIntervalMs ?? 3e4);
|
|
513
|
-
const onSigterm = () => void drain();
|
|
514
|
-
const onSigusr1 = () => void stopNow();
|
|
515
|
-
const onSigint = () => {
|
|
516
|
-
interrupts++;
|
|
517
|
-
if (interrupts === 1) {
|
|
518
|
-
log.line("stopping: draining, press Ctrl-C again to stop now");
|
|
519
|
-
void drain();
|
|
520
|
-
return;
|
|
521
|
-
}
|
|
522
|
-
void stopNow();
|
|
523
|
-
};
|
|
524
|
-
const detachSignals = () => {
|
|
525
|
-
process.off("SIGTERM", onSigterm);
|
|
526
|
-
process.off("SIGUSR1", onSigusr1);
|
|
527
|
-
process.off("SIGINT", onSigint);
|
|
528
|
-
};
|
|
529
|
-
const stopIntake = () => {
|
|
530
|
-
if (intakeClosed) return;
|
|
531
|
-
intakeClosed = true;
|
|
532
|
-
clearInterval(pollTimer);
|
|
533
|
-
};
|
|
534
|
-
const close = () => {
|
|
535
|
-
stopIntake();
|
|
536
|
-
detachSignals();
|
|
537
|
-
if (closed) return;
|
|
538
|
-
closed = true;
|
|
539
|
-
clearInterval(watchTimer);
|
|
540
|
-
};
|
|
541
|
-
const finish = () => {
|
|
542
|
-
if (exited) return;
|
|
543
|
-
exited = true;
|
|
544
|
-
close();
|
|
545
|
-
releasePidFile(pidFilePath(configPath), self);
|
|
546
|
-
log.line("runner exited");
|
|
547
|
-
};
|
|
548
|
-
const drain = async (onWaiting) => {
|
|
549
|
-
if (draining) return;
|
|
550
|
-
draining = true;
|
|
551
|
-
stopIntake();
|
|
552
|
-
let passes = 0;
|
|
553
|
-
while (state.jobs.size > 0 && !exited) {
|
|
554
|
-
onWaiting?.([...state.jobs.keys()]);
|
|
555
|
-
if (passes % DRAIN_LOG_EVERY_PASSES === 0) {
|
|
556
|
-
log.line(
|
|
557
|
-
`stopping: draining, waiting on ${[...state.jobs.values()].map((a) => describeWaiting(a, nowMs())).join(", ")}`
|
|
558
|
-
);
|
|
559
|
-
}
|
|
560
|
-
passes++;
|
|
561
|
-
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
562
|
-
}
|
|
563
|
-
finish();
|
|
564
|
-
};
|
|
565
|
-
const stopNow = async () => {
|
|
566
|
-
if (stopping) return;
|
|
567
|
-
stopping = true;
|
|
568
|
-
stopIntake();
|
|
569
|
-
clearInterval(watchTimer);
|
|
570
|
-
log.line(`stopping: ending ${state.jobs.size} running job(s) now`);
|
|
571
|
-
await Promise.all([...state.jobs.values()].map((active) => endJob(state, active, "stopped")));
|
|
572
|
-
finish();
|
|
573
|
-
};
|
|
574
|
-
const handle = { state, drain, stopNow, close };
|
|
575
|
-
process.on("SIGTERM", onSigterm);
|
|
576
|
-
process.on("SIGUSR1", onSigusr1);
|
|
577
|
-
process.on("SIGINT", onSigint);
|
|
578
|
-
deps.onReady?.();
|
|
579
|
-
await tick(state);
|
|
580
|
-
return handle;
|
|
581
|
-
}
|
|
582
|
-
function describeWaiting(active, nowMs) {
|
|
583
|
-
const elapsed = formatDuration(nowMs - new Date(active.job.startedAt).getTime());
|
|
584
|
-
return `#${active.job.item} (${active.job.stage ?? "starting"}, ${elapsed})`;
|
|
585
|
-
}
|
|
586
|
-
var RunnerStartError = class extends Error {
|
|
587
|
-
};
|
|
588
|
-
async function requestStop(configPath, stopNow = false, pollMs = 5e3) {
|
|
589
|
-
const entry = readPidFile(pidFilePath(configPath));
|
|
590
|
-
if (!entry) {
|
|
591
|
-
process.stdout.write("no runner is running on this config\n");
|
|
592
|
-
return;
|
|
593
|
-
}
|
|
594
|
-
if (!isSameProcess(entry.pid, entry.start)) {
|
|
595
|
-
releasePidFile(pidFilePath(configPath), entry);
|
|
596
|
-
process.stdout.write(`stale pid file for pid ${entry.pid} (process gone), removed
|
|
597
|
-
`);
|
|
598
|
-
return;
|
|
599
|
-
}
|
|
600
|
-
const signal = stopNow ? "SIGUSR1" : "SIGTERM";
|
|
601
|
-
try {
|
|
602
|
-
process.kill(entry.pid, signal);
|
|
603
|
-
} catch (err) {
|
|
604
|
-
const code = err?.code;
|
|
605
|
-
if (code === "EPERM") {
|
|
606
|
-
process.stdout.write(`the runner (pid ${entry.pid}) belongs to another user and cannot be signalled from here
|
|
607
|
-
`);
|
|
608
|
-
return;
|
|
609
|
-
}
|
|
610
|
-
releasePidFile(pidFilePath(configPath), entry);
|
|
611
|
-
process.stdout.write(`stale pid file for pid ${entry.pid} (process gone), removed
|
|
612
|
-
`);
|
|
613
|
-
return;
|
|
614
|
-
}
|
|
615
|
-
process.stdout.write(
|
|
616
|
-
stopNow ? `sent SIGUSR1 to runner pid ${entry.pid}, stopping now
|
|
617
|
-
` : `sent SIGTERM to runner pid ${entry.pid}, draining
|
|
618
|
-
`
|
|
619
|
-
);
|
|
620
|
-
while (isSameProcess(entry.pid, entry.start)) {
|
|
621
|
-
const now = /* @__PURE__ */ new Date();
|
|
622
|
-
for (const { job } of listJobFiles(configPath).filter(({ job: job2 }) => isUnfinished(job2))) {
|
|
623
|
-
const elapsed = formatDuration(now.getTime() - new Date(job.startedAt).getTime());
|
|
624
|
-
process.stdout.write(`waiting on #${job.item} (${job.stage ?? "starting"}, ${elapsed})
|
|
625
|
-
`);
|
|
626
|
-
}
|
|
627
|
-
await new Promise((resolve) => setTimeout(resolve, pollMs));
|
|
628
|
-
}
|
|
629
|
-
process.stdout.write("runner exited\n");
|
|
630
|
-
}
|
|
631
|
-
function listJobFiles(configPath) {
|
|
632
|
-
const dir = jobsDir(configPath);
|
|
633
|
-
if (!existsSync(dir)) return [];
|
|
634
|
-
const result = [];
|
|
635
|
-
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
636
|
-
if (!entry.isDirectory()) continue;
|
|
637
|
-
const job = readJobFile(join(dir, entry.name, "job.json"));
|
|
638
|
-
if (job) result.push({ dirName: entry.name, job });
|
|
639
|
-
}
|
|
640
|
-
return result;
|
|
641
|
-
}
|
|
642
|
-
function runnerProcessState(configPath) {
|
|
643
|
-
const entry = readPidFile(pidFilePath(configPath));
|
|
644
|
-
if (!entry) return { state: "absent" };
|
|
645
|
-
return isSameProcess(entry.pid, entry.start) ? { state: "running", pid: entry.pid } : { state: "stale", pid: entry.pid };
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
export { RunnerStartError, listJobFiles, probeCredentials, requestStop, runnerProcessState, start, tick, watchTick };
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { createRequire } from 'node:module';
|
|
3
|
-
export { CHUNK_PART_TARGET_BYTES, RETRYABLE_STATUSES, SyncConfigError, SyncTransportError, detectCi, isLoopbackUrl, resolveTier, runSync } from './chunk-U32VSRLO.js';
|
|
4
|
-
import './chunk-4TNZMO7V.js';
|
|
5
|
-
import './chunk-YRYMNBGQ.js';
|
|
6
|
-
import './chunk-65WXAANR.js';
|
|
7
|
-
import './chunk-WZBHBH3J.js';
|
|
8
|
-
import './chunk-ZJSNLVCE.js';
|
|
9
|
-
import './chunk-NXLLURA4.js';
|
|
10
|
-
import './chunk-I3ETLSNE.js';
|
|
11
|
-
import './chunk-YOOSBOKS.js';
|
|
12
|
-
import './chunk-R5KBGVII.js';
|
|
13
|
-
export { cloudConfigPath, readCloudConfig, writeCloudConfig } from './chunk-TFUIDMOB.js';
|
|
14
|
-
import './chunk-VNVZDWO3.js';
|
|
15
|
-
import './chunk-Z5GEFMRW.js';
|
|
16
|
-
|
|
17
|
-
createRequire(import.meta.url);
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { createRequire } from 'node:module';
|
|
3
|
-
export { WorkCommandError, findLiveItem, getJson, parsePositiveInt, postJson, resolveCredentials, resolveRepository, runWorkAdd, runWorkLinkSession, runWorkList, runWorkRemove, runWorkStatus } from './chunk-Q6CL5THG.js';
|
|
4
|
-
import './chunk-U32VSRLO.js';
|
|
5
|
-
import './chunk-4TNZMO7V.js';
|
|
6
|
-
import './chunk-YRYMNBGQ.js';
|
|
7
|
-
import './chunk-65WXAANR.js';
|
|
8
|
-
import './chunk-WZBHBH3J.js';
|
|
9
|
-
import './chunk-ZJSNLVCE.js';
|
|
10
|
-
import './chunk-NXLLURA4.js';
|
|
11
|
-
import './chunk-I3ETLSNE.js';
|
|
12
|
-
import './chunk-YOOSBOKS.js';
|
|
13
|
-
import './chunk-R5KBGVII.js';
|
|
14
|
-
import './chunk-TFUIDMOB.js';
|
|
15
|
-
import './chunk-VNVZDWO3.js';
|
|
16
|
-
import './chunk-Z5GEFMRW.js';
|
|
17
|
-
|
|
18
|
-
createRequire(import.meta.url);
|