hub-runner 0.2.6 → 0.2.7
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 +2 -2
- package/check.mjs +5 -5
- package/lib/launchd.mjs +8 -5
- package/lib/paths.mjs +4 -1
- package/main.mjs +119 -34
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ hub-runner start
|
|
|
12
12
|
|
|
13
13
|
| Command | What it does |
|
|
14
14
|
|---|---|
|
|
15
|
-
| `hub-runner start [--take-over]` | Starts the runner under launchd (`~/Library/LaunchAgents/dev.hub.runner.plist`). It starts again at every login and is not restarted after a crash: run `start` again. `--take-over` claims the Mac slot from another Mac. |
|
|
15
|
+
| `hub-runner start [--take-over]` | Starts the runner under launchd (`~/Library/LaunchAgents/dev.hub.runner.plist`). It starts again at every login and is not restarted after a crash: run `start` again. `--take-over` claims the Mac slot from another Mac on that start only: a later start at login claims without it. |
|
|
16
16
|
| `hub-runner stop` | Stops it. |
|
|
17
17
|
| `hub-runner restart` | Lets the open turns finish, then starts it again. |
|
|
18
18
|
| `hub-runner status` | The runner's state, pid, version, bots, and whether launchd has it loaded. Exits 1 when it isn't running. |
|
|
@@ -33,5 +33,5 @@ npm i -g hub-runner@latest --min-release-age=0 && hub-runner restart
|
|
|
33
33
|
|
|
34
34
|
- Node 24 or later, and `pnpm` on your PATH when you run `start`: the runner uses both to set the bots up.
|
|
35
35
|
- The hub is `https://hosted-hub.playground-vercel.tools`; set `HUB_URL` when you run `login` for another one.
|
|
36
|
-
- The HubHosted app
|
|
36
|
+
- The HubHosted app runs a runner of its own for each workspace with a Mac bot, in `~/.hub/local/<scope>` under the launchd job `dev.hub.runner.<scope>`; this CLI acts on one of those when `HUB_RUNNER_DIR` and `HUB_RUNNER_LABEL` name it. An npm install keeps `~/.hub/local` and `dev.hub.runner`. Quitting, signing out of or replacing the app doesn't stop a runner.
|
|
37
37
|
- If the runner doesn't start, `hub-runner status` and `hub-runner logs` say why.
|
package/check.mjs
CHANGED
|
@@ -196,18 +196,19 @@ async function stopTree(pgid, tree, { name, timeoutMs, warn }) {
|
|
|
196
196
|
killTree(pgid, tree);
|
|
197
197
|
return untilTreeGone(pgid, tree, KILL_GRACE_MS);
|
|
198
198
|
}
|
|
199
|
-
|
|
199
|
+
var signature = (name, port) => ` dev --agent ${name} --port ${port} `;
|
|
200
|
+
function ownsLeader(record, rows, signature2) {
|
|
200
201
|
if (!(record.pid > 1) || record.pgid !== record.pid) return false;
|
|
201
202
|
const row = rows.find((r) => r.pid === record.pid);
|
|
202
203
|
if (!row || row.pgid !== record.pgid) return false;
|
|
203
|
-
if (!` ${row.command} `.includes(
|
|
204
|
+
if (!` ${row.command} `.includes(signature2)) return false;
|
|
204
205
|
const member = record.tree?.find((m) => m.pid === record.pid);
|
|
205
206
|
if (member) return member.startedAt === row.startedAt;
|
|
206
207
|
const recorded = Date.parse(record.startedAt);
|
|
207
208
|
return !(Number.isFinite(row.startedAt) && Number.isFinite(recorded) && Math.abs(row.startedAt - recorded) > 5e3);
|
|
208
209
|
}
|
|
209
|
-
function ourTree(record, rows,
|
|
210
|
-
const leader = ownsLeader(record, rows,
|
|
210
|
+
function ourTree(record, rows, signature2) {
|
|
211
|
+
const leader = ownsLeader(record, rows, signature2);
|
|
211
212
|
return { leader, tree: merge(leader ? treeOf(record.pid, rows) : [], liveMembers(record.tree, rows)) };
|
|
212
213
|
}
|
|
213
214
|
|
|
@@ -255,7 +256,6 @@ function createSupervisor({
|
|
|
255
256
|
const entries = /* @__PURE__ */ new Map();
|
|
256
257
|
let closed = false;
|
|
257
258
|
let starting = Promise.resolve();
|
|
258
|
-
const signature = (name, port) => ` dev --agent ${name} --port ${port} `;
|
|
259
259
|
const argvFor = (name, port) => [...command, "dev", "--agent", name, "--port", String(port), "--no-ui", "--no-default-extensions", "--resume"];
|
|
260
260
|
const envFor = (agent) => typeof env === "function" ? env(agent) : env;
|
|
261
261
|
const fingerprintFor = (agent, spawnEnv = envFor(agent)) => fingerprint ? fingerprint({ agent, env: spawnEnv, argv: argvFor(agent.name, agent.port) }) : null;
|
package/lib/launchd.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// The runner's launchd job: a plist in the user's LaunchAgents, loaded in the GUI domain, so it starts at login and
|
|
2
2
|
// outlives every app. `RunAtLoad` without `KeepAlive`: a crash waits for `hub-runner start`.
|
|
3
3
|
import { execFileSync, spawnSync } from 'node:child_process';
|
|
4
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
5
5
|
import { dirname, join } from 'node:path';
|
|
6
|
-
import { LABEL, appHome, credentialsFile, logFile, mainFile, plistFile, runnerDir, version } from './paths.mjs';
|
|
6
|
+
import { LABEL, appHome, credentialsFile, logFile, mainFile, plistFile, runnerDir, takeOverFile, version } from './paths.mjs';
|
|
7
7
|
import { deviceName } from './device.mjs';
|
|
8
8
|
|
|
9
9
|
const xml = (s) => String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
@@ -41,9 +41,9 @@ function program(home) {
|
|
|
41
41
|
return { node: process.execPath, main: mainFile(), pnpm, path: process.env.PATH, build: version() };
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
export function plist(
|
|
44
|
+
export function plist() {
|
|
45
45
|
const run = program(appHome());
|
|
46
|
-
const env = { HUB_PNPM: run.pnpm, PATH: run.path, HUB_RUNNER_DIR: runnerDir(), HUB_RUNNER_BUILD: run.build, HUB_DEVICE_NAME: deviceName()
|
|
46
|
+
const env = { HUB_PNPM: run.pnpm, PATH: run.path, HUB_RUNNER_DIR: runnerDir(), HUB_RUNNER_BUILD: run.build, HUB_DEVICE_NAME: deviceName() };
|
|
47
47
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
48
48
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
49
49
|
<plist version="1.0">
|
|
@@ -78,7 +78,10 @@ export function start({ takeOver = false } = {}) {
|
|
|
78
78
|
if (!existsSync(credentialsFile())) throw new Error('not signed in: run `hub-runner login` first');
|
|
79
79
|
mkdirSync(runnerDir(), { recursive: true });
|
|
80
80
|
mkdirSync(dirname(plistFile()), { recursive: true });
|
|
81
|
-
writeFileSync(plistFile(), plist(
|
|
81
|
+
writeFileSync(plistFile(), plist());
|
|
82
|
+
// A takeover is for the start it's asked on, never a later load of the plist (a login): the runner reads and deletes this.
|
|
83
|
+
if (takeOver) writeFileSync(takeOverFile(), '');
|
|
84
|
+
else rmSync(takeOverFile(), { force: true });
|
|
82
85
|
if (loaded()) bootout();
|
|
83
86
|
launchctl(['bootstrap', domain(), plistFile()]);
|
|
84
87
|
}
|
package/lib/paths.mjs
CHANGED
|
@@ -2,11 +2,14 @@ import { readFileSync } from 'node:fs';
|
|
|
2
2
|
import { homedir } from 'node:os';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
// The Hub app runs one runner per workspace, each under its own label (RunnerInstall.swift); an npm install keeps the one.
|
|
6
|
+
export const LABEL = process.env.HUB_RUNNER_LABEL || 'dev.hub.runner';
|
|
6
7
|
export const DEFAULT_HUB = 'https://hosted-hub.playground-vercel.tools';
|
|
7
8
|
export const runnerDir = () => process.env.HUB_RUNNER_DIR || join(homedir(), '.hub', 'local');
|
|
8
9
|
export const credentialsFile = () => join(runnerDir(), 'credentials.json');
|
|
9
10
|
export const logFile = () => join(runnerDir(), 'runner.log');
|
|
11
|
+
// `start --take-over` asks the next runner to start to take the claim over; it deletes the file (hosted-hub/runner/main.ts).
|
|
12
|
+
export const takeOverFile = () => join(runnerDir(), 'take-over');
|
|
10
13
|
export const plistFile = () => join(homedir(), 'Library', 'LaunchAgents', `${LABEL}.plist`);
|
|
11
14
|
export const mainFile = () => new URL('../main.mjs', import.meta.url).pathname;
|
|
12
15
|
export const version = () => JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version;
|
package/main.mjs
CHANGED
|
@@ -1580,27 +1580,27 @@ var require_adapter = __commonJS({
|
|
|
1580
1580
|
var require_proper_lockfile = __commonJS({
|
|
1581
1581
|
"../../hosted-hub/node_modules/proper-lockfile/index.js"(exports, module) {
|
|
1582
1582
|
"use strict";
|
|
1583
|
-
var
|
|
1583
|
+
var lockfile3 = require_lockfile();
|
|
1584
1584
|
var { toPromise, toSync, toSyncOptions } = require_adapter();
|
|
1585
1585
|
async function lock(file, options) {
|
|
1586
|
-
const release2 = await toPromise(
|
|
1586
|
+
const release2 = await toPromise(lockfile3.lock)(file, options);
|
|
1587
1587
|
return toPromise(release2);
|
|
1588
1588
|
}
|
|
1589
1589
|
function lockSync(file, options) {
|
|
1590
|
-
const release2 = toSync(
|
|
1590
|
+
const release2 = toSync(lockfile3.lock)(file, toSyncOptions(options));
|
|
1591
1591
|
return toSync(release2);
|
|
1592
1592
|
}
|
|
1593
1593
|
function unlock(file, options) {
|
|
1594
|
-
return toPromise(
|
|
1594
|
+
return toPromise(lockfile3.unlock)(file, options);
|
|
1595
1595
|
}
|
|
1596
1596
|
function unlockSync(file, options) {
|
|
1597
|
-
return toSync(
|
|
1597
|
+
return toSync(lockfile3.unlock)(file, toSyncOptions(options));
|
|
1598
1598
|
}
|
|
1599
1599
|
function check(file, options) {
|
|
1600
|
-
return toPromise(
|
|
1600
|
+
return toPromise(lockfile3.check)(file, options);
|
|
1601
1601
|
}
|
|
1602
1602
|
function checkSync(file, options) {
|
|
1603
|
-
return toSync(
|
|
1603
|
+
return toSync(lockfile3.check)(file, toSyncOptions(options));
|
|
1604
1604
|
}
|
|
1605
1605
|
module.exports = lock;
|
|
1606
1606
|
module.exports.lock = lock;
|
|
@@ -1613,11 +1613,11 @@ var require_proper_lockfile = __commonJS({
|
|
|
1613
1613
|
});
|
|
1614
1614
|
|
|
1615
1615
|
// ../../hosted-hub/runner/main.ts
|
|
1616
|
-
var
|
|
1616
|
+
var import_proper_lockfile2 = __toESM(require_proper_lockfile(), 1);
|
|
1617
1617
|
import { homedir as homedir4 } from "node:os";
|
|
1618
1618
|
import { randomUUID } from "node:crypto";
|
|
1619
|
-
import { existsSync as existsSync11, mkdirSync as
|
|
1620
|
-
import { join as
|
|
1619
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync8, readFileSync as readFileSync11, realpathSync as realpathSync4, rmSync as rmSync5, writeFileSync as writeFileSync7 } from "node:fs";
|
|
1620
|
+
import { join as join17 } from "node:path";
|
|
1621
1621
|
|
|
1622
1622
|
// ../../hosted-hub/lib/clean-env.mjs
|
|
1623
1623
|
var INHERITED_ENV = /^(EVE_|WORKFLOW_)/;
|
|
@@ -1861,18 +1861,19 @@ async function stopTree(pgid, tree, { name, timeoutMs, warn }) {
|
|
|
1861
1861
|
killTree(pgid, tree);
|
|
1862
1862
|
return untilTreeGone(pgid, tree, KILL_GRACE_MS);
|
|
1863
1863
|
}
|
|
1864
|
-
|
|
1864
|
+
var signature = (name, port) => ` dev --agent ${name} --port ${port} `;
|
|
1865
|
+
function ownsLeader(record, rows, signature2) {
|
|
1865
1866
|
if (!(record.pid > 1) || record.pgid !== record.pid) return false;
|
|
1866
1867
|
const row = rows.find((r) => r.pid === record.pid);
|
|
1867
1868
|
if (!row || row.pgid !== record.pgid) return false;
|
|
1868
|
-
if (!` ${row.command} `.includes(
|
|
1869
|
+
if (!` ${row.command} `.includes(signature2)) return false;
|
|
1869
1870
|
const member = record.tree?.find((m) => m.pid === record.pid);
|
|
1870
1871
|
if (member) return member.startedAt === row.startedAt;
|
|
1871
1872
|
const recorded = Date.parse(record.startedAt);
|
|
1872
1873
|
return !(Number.isFinite(row.startedAt) && Number.isFinite(recorded) && Math.abs(row.startedAt - recorded) > 5e3);
|
|
1873
1874
|
}
|
|
1874
|
-
function ourTree(record, rows,
|
|
1875
|
-
const leader = ownsLeader(record, rows,
|
|
1875
|
+
function ourTree(record, rows, signature2) {
|
|
1876
|
+
const leader = ownsLeader(record, rows, signature2);
|
|
1876
1877
|
return { leader, tree: merge(leader ? treeOf(record.pid, rows) : [], liveMembers(record.tree, rows)) };
|
|
1877
1878
|
}
|
|
1878
1879
|
|
|
@@ -1920,7 +1921,6 @@ function createSupervisor({
|
|
|
1920
1921
|
const entries = /* @__PURE__ */ new Map();
|
|
1921
1922
|
let closed = false;
|
|
1922
1923
|
let starting = Promise.resolve();
|
|
1923
|
-
const signature = (name, port) => ` dev --agent ${name} --port ${port} `;
|
|
1924
1924
|
const argvFor = (name, port) => [...command, "dev", "--agent", name, "--port", String(port), "--no-ui", "--no-default-extensions", "--resume"];
|
|
1925
1925
|
const envFor = (agent) => typeof env === "function" ? env(agent) : env;
|
|
1926
1926
|
const fingerprintFor = (agent, spawnEnv = envFor(agent)) => fingerprint ? fingerprint({ agent, env: spawnEnv, argv: argvFor(agent.name, agent.port) }) : null;
|
|
@@ -4552,8 +4552,79 @@ async function stopWithin(stop, ms, signal) {
|
|
|
4552
4552
|
]);
|
|
4553
4553
|
}
|
|
4554
4554
|
|
|
4555
|
+
// ../../hosted-hub/runner/ports.ts
|
|
4556
|
+
var import_proper_lockfile = __toESM(require_proper_lockfile(), 1);
|
|
4557
|
+
import { mkdirSync as mkdirSync7, readFileSync as readFileSync10 } from "node:fs";
|
|
4558
|
+
import { join as join16 } from "node:path";
|
|
4559
|
+
var listening = async (port) => (await listeners(port)).length > 0;
|
|
4560
|
+
async function ownedPorts(stateDir, root) {
|
|
4561
|
+
let records;
|
|
4562
|
+
try {
|
|
4563
|
+
records = JSON.parse(readFileSync10(join16(stateDir, "processes.json"), "utf8"));
|
|
4564
|
+
} catch {
|
|
4565
|
+
return /* @__PURE__ */ new Map();
|
|
4566
|
+
}
|
|
4567
|
+
const owned = /* @__PURE__ */ new Map();
|
|
4568
|
+
const rows = table();
|
|
4569
|
+
for (const r of Array.isArray(records) ? records : []) {
|
|
4570
|
+
if (typeof r?.agent !== "string" || typeof r?.port !== "number" || r.workspace && r.workspace !== root) continue;
|
|
4571
|
+
const { tree } = ourTree(r, rows, signature(r.agent, r.port));
|
|
4572
|
+
const holders = await listeners(r.port).catch(() => []);
|
|
4573
|
+
if (holders.length && holders.every((pid) => tree.some((m) => m.pid === pid))) owned.set(r.agent, r.port);
|
|
4574
|
+
}
|
|
4575
|
+
return owned;
|
|
4576
|
+
}
|
|
4577
|
+
function portClaims(dir2, warn) {
|
|
4578
|
+
const held = /* @__PURE__ */ new Map();
|
|
4579
|
+
mkdirSync7(dir2, { recursive: true });
|
|
4580
|
+
const release2 = async (port) => {
|
|
4581
|
+
const r = held.get(port);
|
|
4582
|
+
held.delete(port);
|
|
4583
|
+
await r?.().catch(() => {
|
|
4584
|
+
});
|
|
4585
|
+
};
|
|
4586
|
+
return {
|
|
4587
|
+
claim: async (port) => {
|
|
4588
|
+
if (held.has(port)) return true;
|
|
4589
|
+
const path = join16(dir2, `${port}.lock`);
|
|
4590
|
+
const done = await import_proper_lockfile.default.lock(path, {
|
|
4591
|
+
lockfilePath: path,
|
|
4592
|
+
realpath: false,
|
|
4593
|
+
stale: 1e4,
|
|
4594
|
+
onCompromised: (e) => {
|
|
4595
|
+
held.delete(port);
|
|
4596
|
+
warn(`port ${port}: claim lost, ${e.message}`);
|
|
4597
|
+
}
|
|
4598
|
+
}).catch((error) => {
|
|
4599
|
+
if (error.code !== "ELOCKED") throw error;
|
|
4600
|
+
return null;
|
|
4601
|
+
});
|
|
4602
|
+
if (done) held.set(port, done);
|
|
4603
|
+
return !!done;
|
|
4604
|
+
},
|
|
4605
|
+
release: release2,
|
|
4606
|
+
releaseAll: () => Promise.all([...held.keys()].map(release2))
|
|
4607
|
+
};
|
|
4608
|
+
}
|
|
4609
|
+
async function pickPorts(start, roster, taken, portBase, owned, portBusy, claims) {
|
|
4610
|
+
const picked = [];
|
|
4611
|
+
for (const name of start) {
|
|
4612
|
+
const own = owned.get(name);
|
|
4613
|
+
let port = own ?? portBase + roster.indexOf(name);
|
|
4614
|
+
for (; ; port++) {
|
|
4615
|
+
if (taken.has(port) || !await claims.claim(port)) continue;
|
|
4616
|
+
if (port === own || !await portBusy(port)) break;
|
|
4617
|
+
await claims.release(port);
|
|
4618
|
+
}
|
|
4619
|
+
taken.add(port);
|
|
4620
|
+
picked.push({ name, port });
|
|
4621
|
+
}
|
|
4622
|
+
return picked;
|
|
4623
|
+
}
|
|
4624
|
+
|
|
4555
4625
|
// ../../hosted-hub/runner/hub-runner.ts
|
|
4556
|
-
var
|
|
4626
|
+
var supervise = (o) => Object.assign(createSupervisor(o), { owned: () => ownedPorts(o.stateDir, o.root) });
|
|
4627
|
+
var realBots = { stage: stageLocal, supervise };
|
|
4557
4628
|
var HeldElsewhere = class extends Error {
|
|
4558
4629
|
holder;
|
|
4559
4630
|
constructor(holder) {
|
|
@@ -4581,6 +4652,7 @@ function botEnv(env, config, bot, token) {
|
|
|
4581
4652
|
var log = (...args) => console.error((/* @__PURE__ */ new Date()).toISOString(), "[hub-runner]", ...args);
|
|
4582
4653
|
async function run3(config, bots = realBots) {
|
|
4583
4654
|
const shutdown = new AbortController();
|
|
4655
|
+
const portBusy = config.portBusy ?? listening, claims = portClaims(config.portsDir, (line) => log("ports", line));
|
|
4584
4656
|
const net = (input, init = {}) => fetch(input, { ...init, signal: init.signal ? AbortSignal.any([shutdown.signal, init.signal]) : shutdown.signal });
|
|
4585
4657
|
let token = "";
|
|
4586
4658
|
let supersede;
|
|
@@ -4609,7 +4681,7 @@ async function run3(config, bots = realBots) {
|
|
|
4609
4681
|
if (!res.ok) throw new Error(`${init?.method ?? "GET"} ${path}: HTTP ${res.status} ${(await res.text()).slice(0, 200)}`);
|
|
4610
4682
|
return res.json();
|
|
4611
4683
|
};
|
|
4612
|
-
const claim = await hub("/api/local-runner/claim", { method: "POST", body: JSON.stringify({ device: config.device, deviceName: config.deviceName, takeOver: config.takeOver, attachments: true }) });
|
|
4684
|
+
const claim = await hub("/api/local-runner/claim", { method: "POST", body: JSON.stringify({ device: config.device, deviceName: config.deviceName, takeOver: config.takeOver, explicit: true, takeOverFrom: config.takeOverFrom, attachments: true }) });
|
|
4613
4685
|
if (claim.status === 409) throw new HeldElsewhere((await claim.json()).holder);
|
|
4614
4686
|
if (!claim.ok) throw new Error(`claim: HTTP ${claim.status} ${(await claim.text()).slice(0, 200)}`);
|
|
4615
4687
|
const claimed = await claim.json();
|
|
@@ -4706,13 +4778,16 @@ async function run3(config, bots = realBots) {
|
|
|
4706
4778
|
}
|
|
4707
4779
|
await tell(null);
|
|
4708
4780
|
const wanted = work.bots;
|
|
4709
|
-
const
|
|
4710
|
-
const fresh =
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
|
|
4781
|
+
const owned = adopted ? /* @__PURE__ */ new Map() : await supervisor.owned();
|
|
4782
|
+
const fresh = await pickPorts(
|
|
4783
|
+
wanted.filter((n) => !running.has(n)),
|
|
4784
|
+
work.bots,
|
|
4785
|
+
new Set([...running.values()].map((a) => a.port)),
|
|
4786
|
+
config.portBase,
|
|
4787
|
+
owned,
|
|
4788
|
+
portBusy,
|
|
4789
|
+
claims
|
|
4790
|
+
);
|
|
4716
4791
|
shutdown.signal.throwIfAborted();
|
|
4717
4792
|
for (const agent of fresh) running.set(agent.name, agent);
|
|
4718
4793
|
if (!adopted) {
|
|
@@ -4721,7 +4796,7 @@ async function run3(config, bots = realBots) {
|
|
|
4721
4796
|
} else for (const agent of fresh) supervisor.start(agent);
|
|
4722
4797
|
for (const [name, agent] of running) if (!wanted.includes(name)) {
|
|
4723
4798
|
running.delete(name);
|
|
4724
|
-
void supervisor.stop(agent);
|
|
4799
|
+
void supervisor.stop(agent).then(() => claims.release(agent.port));
|
|
4725
4800
|
}
|
|
4726
4801
|
report2();
|
|
4727
4802
|
await Promise.all(fresh.map(async (agent) => {
|
|
@@ -4975,6 +5050,7 @@ async function run3(config, bots = realBots) {
|
|
|
4975
5050
|
model.stop();
|
|
4976
5051
|
clearTimeout(retry);
|
|
4977
5052
|
await supervisor.stopAll();
|
|
5053
|
+
await claims.releaseAll();
|
|
4978
5054
|
await Promise.allSettled([feed, syncing]);
|
|
4979
5055
|
};
|
|
4980
5056
|
void superseded.then(() => stopRunner(false));
|
|
@@ -4989,24 +5065,30 @@ async function run3(config, bots = realBots) {
|
|
|
4989
5065
|
var need = (name) => process.env[name] || (() => {
|
|
4990
5066
|
throw new Error(`${name} is required`);
|
|
4991
5067
|
})();
|
|
4992
|
-
var configured = process.env.HUB_RUNNER_DIR ||
|
|
4993
|
-
|
|
5068
|
+
var configured = process.env.HUB_RUNNER_DIR || join17(homedir4(), ".hub", "local");
|
|
5069
|
+
mkdirSync8(configured, { recursive: true });
|
|
4994
5070
|
var dir = realpathSync4(configured);
|
|
4995
|
-
|
|
4996
|
-
statusFile(
|
|
5071
|
+
mkdirSync8(join17(dir, "status"), { recursive: true });
|
|
5072
|
+
statusFile(join17(dir, "status", `${process.pid}.json`), { pid: process.pid, state: "starting", build: process.env.HUB_RUNNER_BUILD ?? "" });
|
|
4997
5073
|
process.on("exit", exitStatus);
|
|
4998
5074
|
var drainAsked = new Promise((resolve6) => process.on("SIGUSR2", () => resolve6()));
|
|
4999
|
-
var release = await
|
|
5075
|
+
var release = await import_proper_lockfile2.default.lock(dir, { lockfilePath: join17(dir, "runner.lock"), stale: 1e4, realpath: false }).catch((error) => {
|
|
5000
5076
|
if (error.code !== "ELOCKED") throw error;
|
|
5001
5077
|
status("busy-here");
|
|
5002
5078
|
process.exit(5);
|
|
5003
5079
|
});
|
|
5004
|
-
var deviceFile =
|
|
5080
|
+
var deviceFile = join17(dir, "device-id");
|
|
5005
5081
|
if (!existsSync11(deviceFile)) writeFileSync7(deviceFile, randomUUID());
|
|
5006
|
-
var device =
|
|
5007
|
-
var credentials = process.env.HUB_URL || process.env.HUB_SESSION ? { hubUrl: need("HUB_URL"), session: need("HUB_SESSION"), bypass: process.env.HUB_PROTECTION_BYPASS || void 0 } : JSON.parse(
|
|
5082
|
+
var device = readFileSync11(deviceFile, "utf8").trim();
|
|
5083
|
+
var credentials = process.env.HUB_URL || process.env.HUB_SESSION ? { hubUrl: need("HUB_URL"), session: need("HUB_SESSION"), bypass: process.env.HUB_PROTECTION_BYPASS || void 0 } : JSON.parse(readFileSync11(join17(dir, "credentials.json"), "utf8"));
|
|
5008
5084
|
if ("workspace" in credentials && credentials.workspace) status("starting", { workspace: credentials.workspace });
|
|
5009
5085
|
var deviceName = process.env.HUB_DEVICE_NAME || device;
|
|
5086
|
+
var takeOverFile = join17(dir, "take-over");
|
|
5087
|
+
var takeOverFromFile = join17(dir, "take-over-from");
|
|
5088
|
+
var takeOver = existsSync11(takeOverFile);
|
|
5089
|
+
var takeOverFrom = existsSync11(takeOverFromFile) ? readFileSync11(takeOverFromFile, "utf8").trim() || void 0 : void 0;
|
|
5090
|
+
rmSync5(takeOverFile, { force: true });
|
|
5091
|
+
rmSync5(takeOverFromFile, { force: true });
|
|
5010
5092
|
var started = await run3({
|
|
5011
5093
|
hubUrl: credentials.hubUrl.replace(/\/$/, ""),
|
|
5012
5094
|
session: credentials.session,
|
|
@@ -5016,9 +5098,12 @@ var started = await run3({
|
|
|
5016
5098
|
pnpm: need("HUB_PNPM"),
|
|
5017
5099
|
// Two runners on one machine (the e2e) need their own ports; two Macs never share any.
|
|
5018
5100
|
portBase: Number(process.env.HUB_RUNNER_PORT_BASE) || 5400,
|
|
5101
|
+
// Shared by every workspace's runner on this Mac, whatever its dir: each claims its bots' ports there.
|
|
5102
|
+
portsDir: process.env.HUB_RUNNER_PORTS_DIR || join17(homedir4(), ".hub", "ports"),
|
|
5019
5103
|
device,
|
|
5020
5104
|
deviceName,
|
|
5021
|
-
takeOver
|
|
5105
|
+
takeOver,
|
|
5106
|
+
takeOverFrom
|
|
5022
5107
|
}).catch(async (error) => {
|
|
5023
5108
|
if (!(error instanceof HeldElsewhere)) throw error;
|
|
5024
5109
|
status("held-elsewhere", { holder: error.holder });
|