wildcrane-runner 0.0.0-stage → 0.3.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/LICENSE +105 -0
- package/README.md +43 -2
- package/bin/wildcrane-runner.mjs +2 -0
- package/dist/akb/bin/ai4kanban.mjs +109 -0
- package/dist/akb/dist/kanban.mjs +38550 -0
- package/dist/akb/package.json +1 -0
- package/dist/launcher.mjs +360 -0
- package/dist/runner.mjs +88445 -0
- package/package.json +49 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"name":"wildcrane","version":"0.11.2","type":"module","private":true}
|
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
// wildcrane-runner 0.3.0 — built by runner/scripts/build.mjs. Do not edit.
|
|
2
|
+
import { createRequire as __ai4kanbanRequire } from 'node:module'
|
|
3
|
+
const require = __ai4kanbanRequire(import.meta.url)
|
|
4
|
+
|
|
5
|
+
// src/launcher-main.ts
|
|
6
|
+
import path4 from "node:path";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
|
|
9
|
+
// src/home.ts
|
|
10
|
+
import crypto from "node:crypto";
|
|
11
|
+
import fs from "node:fs";
|
|
12
|
+
import os from "node:os";
|
|
13
|
+
import path from "node:path";
|
|
14
|
+
function runnerHome() {
|
|
15
|
+
return process.env.WILDCRANE_HOME || path.join(os.homedir(), ".wildcrane");
|
|
16
|
+
}
|
|
17
|
+
function useRunnerHome() {
|
|
18
|
+
process.env.AI4KANBAN_HOME = runnerHome();
|
|
19
|
+
}
|
|
20
|
+
function machineId() {
|
|
21
|
+
const file = path.join(runnerHome(), "machine.json");
|
|
22
|
+
try {
|
|
23
|
+
const held = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
24
|
+
if (held.id && /^[0-9a-f-]{36}$/i.test(held.id)) return held.id;
|
|
25
|
+
} catch {
|
|
26
|
+
}
|
|
27
|
+
const id = crypto.randomUUID();
|
|
28
|
+
fs.mkdirSync(runnerHome(), { recursive: true, mode: 448 });
|
|
29
|
+
fs.writeFileSync(file, `${JSON.stringify({ id })}
|
|
30
|
+
`);
|
|
31
|
+
return id;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// src/launcher.ts
|
|
35
|
+
import { spawn } from "node:child_process";
|
|
36
|
+
import fs3 from "node:fs";
|
|
37
|
+
import path3 from "node:path";
|
|
38
|
+
|
|
39
|
+
// ../cli/src/lib/cloud/config.ts
|
|
40
|
+
var SUPABASE_URL = "https://yajrbpprmdvtkvjjfpbk.supabase.co";
|
|
41
|
+
var SUPABASE_ANON_KEY = "sb_publishable_ioUQ23BTtoj8NKqndp0Jrw_omVR3m4n";
|
|
42
|
+
var API_URL = "https://api.ai4kanban.dev";
|
|
43
|
+
var URL_SCHEME = "ai4kanban";
|
|
44
|
+
var SIGN_IN_REDIRECT = `${URL_SCHEME}://cloud/signed-in`;
|
|
45
|
+
var trimSlash = (url) => url.replace(/\/+$/, "");
|
|
46
|
+
function cloudEndpoints() {
|
|
47
|
+
return {
|
|
48
|
+
supabaseUrl: trimSlash(process.env.AI4KANBAN_SUPABASE_URL || SUPABASE_URL),
|
|
49
|
+
anonKey: process.env.AI4KANBAN_SUPABASE_ANON_KEY || SUPABASE_ANON_KEY,
|
|
50
|
+
api: trimSlash(process.env.AI4KANBAN_CLOUD_URL || API_URL)
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// src/i18n.ts
|
|
55
|
+
var ZH = {
|
|
56
|
+
signIn: (url) => `\u8BF7\u5728\u6D4F\u89C8\u5668\u4E2D\u767B\u5F55 WildCrane\uFF1A
|
|
57
|
+
${url}`,
|
|
58
|
+
signedIn: () => "\u5DF2\u767B\u5F55",
|
|
59
|
+
signInFailed: (why) => `\u767B\u5F55\u672A\u5B8C\u6210\uFF1A${why}`,
|
|
60
|
+
connected: (machine) => `\u5DF2\u8FDE\u63A5\uFF08${machine}\uFF09\uFF0C\u7B49\u5F85\u4EFB\u52A1`,
|
|
61
|
+
running: (count) => `\u6B63\u5728\u8FD0\u884C ${count} \u4E2A\u4EFB\u52A1`,
|
|
62
|
+
reconnecting: () => "\u5DF2\u65AD\u5F00\uFF0C\u6B63\u5728\u91CD\u8FDE\u2026",
|
|
63
|
+
elsewhere: (machine) => `\u4F60\u7684 Runner \u5DF2\u5728\u300C${machine}\u300D\u4E0A\u8FDE\u63A5\u3002\u8BF7\u5148\u5728\u90A3\u53F0\u7535\u8111\u4E0A\u9000\u51FA\uFF0C\u518D\u5728\u8FD9\u91CC\u542F\u52A8\u3002`,
|
|
64
|
+
outdated: () => "\u6B64\u7248\u672C\u5DF2\u8FC7\u65E7\uFF0C\u8BF7\u91CD\u542F\u4EE5\u66F4\u65B0\u3002",
|
|
65
|
+
refused: (why) => `WildCrane \u62D2\u7EDD\u4E86\u8FDE\u63A5\uFF1A${why}`,
|
|
66
|
+
updated: (version) => `\u5DF2\u66F4\u65B0\u5230 ${version}`,
|
|
67
|
+
rolledBack: (version) => `\u65B0\u7248\u672C\u672A\u80FD\u542F\u52A8\uFF0C\u5DF2\u9000\u56DE ${version}`,
|
|
68
|
+
nodeTooOld: (range) => `\u65B0\u7248\u672C\u9700\u8981 Node ${range}\uFF0C\u8BF7\u5347\u7EA7 Node\u3002\u672C\u6B21\u7EE7\u7EED\u4F7F\u7528\u5F53\u524D\u7248\u672C\u3002`,
|
|
69
|
+
stopped: () => "\u5DF2\u505C\u6B62",
|
|
70
|
+
reviewTitle: () => "\u5F85\u4F60\u5BA1\u6838",
|
|
71
|
+
reviewBody: (task, checkpoint) => `\u300C${task}\u300D\u7684${checkpoint}\u5DF2\u5C31\u7EEA`,
|
|
72
|
+
doneTitle: () => "\u4EFB\u52A1\u5DF2\u5B8C\u6210",
|
|
73
|
+
failedTitle: () => "\u4EFB\u52A1\u5931\u8D25",
|
|
74
|
+
commentTitle: (task) => `\u300C${task}\u300D\u6709\u65B0\u8BC4\u8BBA`,
|
|
75
|
+
commentBody: (who, text) => `${who}\uFF1A${text}`
|
|
76
|
+
};
|
|
77
|
+
var EN = {
|
|
78
|
+
signIn: (url) => `Sign in to WildCrane in your browser:
|
|
79
|
+
${url}`,
|
|
80
|
+
signedIn: () => "Signed in",
|
|
81
|
+
signInFailed: (why) => `Sign-in did not finish: ${why}`,
|
|
82
|
+
connected: (machine) => `Connected (${machine}) \u2014 waiting for tasks`,
|
|
83
|
+
running: (count) => `Running ${count} ${count === 1 ? "task" : "tasks"}`,
|
|
84
|
+
reconnecting: () => "Disconnected \u2014 reconnecting\u2026",
|
|
85
|
+
elsewhere: (machine) => `Your Runner is already connected on "${machine}". Quit it there first, then start it here.`,
|
|
86
|
+
outdated: () => "This version is out of date. Restart to update.",
|
|
87
|
+
refused: (why) => `WildCrane refused the connection: ${why}`,
|
|
88
|
+
updated: (version) => `Updated to ${version}`,
|
|
89
|
+
rolledBack: (version) => `The new version did not start \u2014 back on ${version}`,
|
|
90
|
+
nodeTooOld: (range) => `The new version needs Node ${range}. Update Node; running the current version for now.`,
|
|
91
|
+
stopped: () => "Stopped",
|
|
92
|
+
reviewTitle: () => "Needs your review",
|
|
93
|
+
reviewBody: (task, checkpoint) => `${checkpoint} for "${task}" is ready`,
|
|
94
|
+
doneTitle: () => "Task finished",
|
|
95
|
+
failedTitle: () => "Task failed",
|
|
96
|
+
commentTitle: (task) => `New comment on ${task}`,
|
|
97
|
+
commentBody: (who, text) => `${who}: ${text}`
|
|
98
|
+
};
|
|
99
|
+
function isChinese(env = process.env) {
|
|
100
|
+
const lang = env.LC_ALL || env.LC_MESSAGES || env.LANG || Intl.DateTimeFormat().resolvedOptions().locale;
|
|
101
|
+
return /^zh/i.test(lang ?? "");
|
|
102
|
+
}
|
|
103
|
+
var linesFor = (chinese) => chinese ? ZH : EN;
|
|
104
|
+
var t = linesFor(isChinese());
|
|
105
|
+
var last = "";
|
|
106
|
+
function status(line) {
|
|
107
|
+
if (line === last) return;
|
|
108
|
+
last = line;
|
|
109
|
+
process.stdout.write(`${line}
|
|
110
|
+
`);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// src/protocol.ts
|
|
114
|
+
var VERSION = true ? "0.3.0" : "0.0.0";
|
|
115
|
+
var EXIT_REFUSED = 10;
|
|
116
|
+
var EXIT_OUTDATED = 11;
|
|
117
|
+
|
|
118
|
+
// src/update.ts
|
|
119
|
+
import crypto2 from "node:crypto";
|
|
120
|
+
import fs2 from "node:fs";
|
|
121
|
+
import path2 from "node:path";
|
|
122
|
+
import zlib from "node:zlib";
|
|
123
|
+
var PACKAGE = "wildcrane-runner";
|
|
124
|
+
var TIMEOUT_MS = 6e4;
|
|
125
|
+
var versionsDir = () => path2.join(runnerHome(), "runner");
|
|
126
|
+
var versionDir = (version) => path2.join(versionsDir(), version);
|
|
127
|
+
var entryOf = (dir) => path2.join(dir, "dist", "runner.mjs");
|
|
128
|
+
var registry = () => (process.env.npm_config_registry || "https://registry.npmjs.org").replace(/\/+$/, "");
|
|
129
|
+
async function install(version, nodeVersion = process.versions.node) {
|
|
130
|
+
const dir = versionDir(version);
|
|
131
|
+
if (fs2.existsSync(entryOf(dir))) return { ok: true, dir };
|
|
132
|
+
let meta;
|
|
133
|
+
let tarball;
|
|
134
|
+
try {
|
|
135
|
+
const answer = await fetch(`${registry()}/${PACKAGE}/${encodeURIComponent(version)}`, { signal: AbortSignal.timeout(TIMEOUT_MS) });
|
|
136
|
+
if (!answer.ok) return { ok: false, why: "unreachable", detail: `registry answered ${answer.status}` };
|
|
137
|
+
meta = await answer.json();
|
|
138
|
+
const range = meta.engines?.node;
|
|
139
|
+
if (range && !satisfies(nodeVersion, range)) return { ok: false, why: "node", detail: range };
|
|
140
|
+
if (!meta.dist?.tarball) return { ok: false, why: "unreachable", detail: "no tarball" };
|
|
141
|
+
const download = await fetch(meta.dist.tarball, { signal: AbortSignal.timeout(TIMEOUT_MS) });
|
|
142
|
+
if (!download.ok) return { ok: false, why: "unreachable", detail: `download answered ${download.status}` };
|
|
143
|
+
tarball = Buffer.from(await download.arrayBuffer());
|
|
144
|
+
} catch (e) {
|
|
145
|
+
return { ok: false, why: "unreachable", detail: e instanceof Error ? e.message : String(e) };
|
|
146
|
+
}
|
|
147
|
+
const expected = /^sha512-(.+)$/.exec(meta.dist.integrity ?? "")?.[1];
|
|
148
|
+
const actual = crypto2.createHash("sha512").update(tarball).digest("base64");
|
|
149
|
+
if (!expected || expected !== actual) return { ok: false, why: "integrity", detail: "sha512 mismatch" };
|
|
150
|
+
const staging = `${dir}.${process.pid}.tmp`;
|
|
151
|
+
try {
|
|
152
|
+
fs2.rmSync(staging, { recursive: true, force: true });
|
|
153
|
+
extract(zlib.gunzipSync(tarball), staging);
|
|
154
|
+
if (!fs2.existsSync(entryOf(staging))) throw new Error("the package carries no dist/runner.mjs");
|
|
155
|
+
fs2.rmSync(dir, { recursive: true, force: true });
|
|
156
|
+
fs2.renameSync(staging, dir);
|
|
157
|
+
} catch (e) {
|
|
158
|
+
fs2.rmSync(staging, { recursive: true, force: true });
|
|
159
|
+
return { ok: false, why: "integrity", detail: e instanceof Error ? e.message : String(e) };
|
|
160
|
+
}
|
|
161
|
+
return { ok: true, dir };
|
|
162
|
+
}
|
|
163
|
+
function extract(tar, into) {
|
|
164
|
+
let offset = 0;
|
|
165
|
+
let longName = null;
|
|
166
|
+
while (offset + 512 <= tar.length) {
|
|
167
|
+
const header = tar.subarray(offset, offset + 512);
|
|
168
|
+
if (header.every((b) => b === 0)) break;
|
|
169
|
+
const field = (start, length) => header.subarray(start, start + length).toString("utf8").replace(/\0.*$/s, "");
|
|
170
|
+
const size = parseInt(field(124, 12).trim() || "0", 8);
|
|
171
|
+
const type = field(156, 1) || "0";
|
|
172
|
+
const body = tar.subarray(offset + 512, offset + 512 + size);
|
|
173
|
+
offset += 512 + Math.ceil(size / 512) * 512;
|
|
174
|
+
if (type === "x") {
|
|
175
|
+
longName = /(?:^|\n)\d+ path=([^\n]*)\n/.exec(body.toString("utf8"))?.[1] ?? null;
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
if (type === "L") {
|
|
179
|
+
longName = body.toString("utf8").replace(/\0.*$/s, "");
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
const prefix = field(345, 155);
|
|
183
|
+
const name = longName ?? (prefix ? `${prefix}/${field(0, 100)}` : field(0, 100));
|
|
184
|
+
longName = null;
|
|
185
|
+
if (type !== "0" && type !== "5") continue;
|
|
186
|
+
const relative = name.replace(/^[^/]+\//, "");
|
|
187
|
+
if (!relative) continue;
|
|
188
|
+
const target = path2.resolve(into, relative);
|
|
189
|
+
if (!target.startsWith(path2.resolve(into) + path2.sep)) throw new Error(`refusing ${name}`);
|
|
190
|
+
if (type === "5") fs2.mkdirSync(target, { recursive: true });
|
|
191
|
+
else {
|
|
192
|
+
fs2.mkdirSync(path2.dirname(target), { recursive: true });
|
|
193
|
+
fs2.writeFileSync(target, body);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
var parse = (v) => {
|
|
198
|
+
const m = /^v?(\d+)(?:\.(\d+|x|\*))?(?:\.(\d+|x|\*))?/.exec(v.trim());
|
|
199
|
+
return m ? [Number(m[1]), Number(m[2]) || 0, Number(m[3]) || 0] : null;
|
|
200
|
+
};
|
|
201
|
+
function compare(a, b) {
|
|
202
|
+
const [x, y] = [parse(a) ?? [0, 0, 0], parse(b) ?? [0, 0, 0]];
|
|
203
|
+
for (let i = 0; i < 3; i++) if (x[i] !== y[i]) return x[i] - y[i];
|
|
204
|
+
return 0;
|
|
205
|
+
}
|
|
206
|
+
function satisfies(version, range) {
|
|
207
|
+
return range.split("||").some((alternative) => {
|
|
208
|
+
const parts = alternative.trim().split(/\s+/).filter(Boolean);
|
|
209
|
+
return parts.length > 0 && parts.every((part) => meets(version, part));
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
function meets(version, comparator) {
|
|
213
|
+
const m = /^(>=|<=|>|<|=|\^|~)?\s*(.+)$/.exec(comparator);
|
|
214
|
+
const bound = m && parse(m[2]);
|
|
215
|
+
if (!m || !bound) return false;
|
|
216
|
+
const v = parse(version);
|
|
217
|
+
const c = compare(version, m[2]);
|
|
218
|
+
switch (m[1]) {
|
|
219
|
+
case ">=":
|
|
220
|
+
return c >= 0;
|
|
221
|
+
case ">":
|
|
222
|
+
return c > 0;
|
|
223
|
+
case "<=":
|
|
224
|
+
return c <= 0;
|
|
225
|
+
case "<":
|
|
226
|
+
return c < 0;
|
|
227
|
+
case "^":
|
|
228
|
+
return c >= 0 && v[0] === bound[0];
|
|
229
|
+
case "~":
|
|
230
|
+
return c >= 0 && v[0] === bound[0] && v[1] === bound[1];
|
|
231
|
+
default: {
|
|
232
|
+
const given = m[2].split(".").filter((p) => /^\d+$/.test(p)).length;
|
|
233
|
+
return [0, 1, 2].slice(0, given).every((i) => v[i] === bound[i]);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// src/launcher.ts
|
|
239
|
+
var ASK_MS = 5e3;
|
|
240
|
+
var CONNECT_MS = 2 * 6e4;
|
|
241
|
+
var KEEP = 2;
|
|
242
|
+
var stateFile = () => path3.join(versionsDir(), "state.json");
|
|
243
|
+
function readState() {
|
|
244
|
+
try {
|
|
245
|
+
const held = JSON.parse(fs3.readFileSync(stateFile(), "utf8"));
|
|
246
|
+
return { good: held.good ?? [], bad: held.bad ?? [] };
|
|
247
|
+
} catch {
|
|
248
|
+
return { good: [], bad: [] };
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
function writeState(state) {
|
|
252
|
+
fs3.mkdirSync(versionsDir(), { recursive: true });
|
|
253
|
+
fs3.writeFileSync(stateFile(), `${JSON.stringify(state, null, 2)}
|
|
254
|
+
`);
|
|
255
|
+
}
|
|
256
|
+
function markGood(version) {
|
|
257
|
+
const state = readState();
|
|
258
|
+
state.good = [version, ...state.good.filter((v) => v !== version)].slice(0, KEEP);
|
|
259
|
+
writeState(state);
|
|
260
|
+
for (const name of fs3.readdirSync(versionsDir())) {
|
|
261
|
+
if (name === "state.json" || state.good.includes(name) || name.endsWith(".tmp")) continue;
|
|
262
|
+
fs3.rmSync(path3.join(versionsDir(), name), { recursive: true, force: true });
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
function markBad(version) {
|
|
266
|
+
const state = readState();
|
|
267
|
+
state.bad = [.../* @__PURE__ */ new Set([...state.bad, version])];
|
|
268
|
+
state.good = state.good.filter((v) => v !== version);
|
|
269
|
+
writeState(state);
|
|
270
|
+
}
|
|
271
|
+
async function askTarget(machine) {
|
|
272
|
+
try {
|
|
273
|
+
const answer = await fetch(`${cloudEndpoints().api}/v1/runner/version?machine=${machine}`, {
|
|
274
|
+
signal: AbortSignal.timeout(ASK_MS)
|
|
275
|
+
});
|
|
276
|
+
if (!answer.ok) return null;
|
|
277
|
+
const { version } = await answer.json();
|
|
278
|
+
return typeof version === "string" && version ? version : null;
|
|
279
|
+
} catch {
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
async function candidates(own) {
|
|
284
|
+
const state = readState();
|
|
285
|
+
const proven = state.good.find((v) => fs3.existsSync(entryOf(versionDir(v))));
|
|
286
|
+
const current = proven && compare(proven, own.version) > 0 ? { version: proven, entry: entryOf(versionDir(proven)) } : own;
|
|
287
|
+
const target = await askTarget(machineId());
|
|
288
|
+
if (!target || target === current.version || state.bad.includes(target)) return [current];
|
|
289
|
+
if (process.env.WILDCRANE_DESKTOP && compare(target, own.version) < 0) return [current];
|
|
290
|
+
if (target === own.version) return [own, current];
|
|
291
|
+
const got = await install(target);
|
|
292
|
+
if (got.ok) {
|
|
293
|
+
const known = state.good.includes(target);
|
|
294
|
+
return [{ version: target, entry: entryOf(got.dir), fresh: !known }, current];
|
|
295
|
+
}
|
|
296
|
+
if (got.why === "node") status(t.nodeTooOld(got.detail));
|
|
297
|
+
return [current];
|
|
298
|
+
}
|
|
299
|
+
function runOnce(candidate) {
|
|
300
|
+
return new Promise((resolve) => {
|
|
301
|
+
const child = spawn(process.execPath, [candidate.entry], {
|
|
302
|
+
stdio: ["inherit", "inherit", "inherit", "ipc"],
|
|
303
|
+
env: { ...process.env, WILDCRANE_RUNNER_VERSION: candidate.version }
|
|
304
|
+
});
|
|
305
|
+
let connected = false;
|
|
306
|
+
let timer = null;
|
|
307
|
+
let timedOut = false;
|
|
308
|
+
let stopping = false;
|
|
309
|
+
const forward = (signal) => () => {
|
|
310
|
+
stopping = true;
|
|
311
|
+
child.kill(signal);
|
|
312
|
+
};
|
|
313
|
+
const onInt = forward("SIGINT");
|
|
314
|
+
const onTerm = forward("SIGTERM");
|
|
315
|
+
process.on("SIGINT", onInt);
|
|
316
|
+
process.on("SIGTERM", onTerm);
|
|
317
|
+
child.on("message", (message) => {
|
|
318
|
+
if (message.type === "started" && candidate.fresh && !connected) {
|
|
319
|
+
timer = setTimeout(() => {
|
|
320
|
+
timedOut = true;
|
|
321
|
+
child.kill("SIGTERM");
|
|
322
|
+
}, Number(process.env.WILDCRANE_CONNECT_MS) || CONNECT_MS);
|
|
323
|
+
}
|
|
324
|
+
if (message.type === "connected" && !connected) {
|
|
325
|
+
connected = true;
|
|
326
|
+
if (timer) clearTimeout(timer);
|
|
327
|
+
if (candidate.fresh) {
|
|
328
|
+
markGood(candidate.version);
|
|
329
|
+
status(t.updated(candidate.version));
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
child.on("exit", (code, signal) => {
|
|
334
|
+
if (timer) clearTimeout(timer);
|
|
335
|
+
process.off("SIGINT", onInt);
|
|
336
|
+
process.off("SIGTERM", onTerm);
|
|
337
|
+
if (connected || !candidate.fresh || stopping) return resolve({ exit: code ?? (signal ? 1 : 0) });
|
|
338
|
+
if (timedOut) return resolve({ failed: "timeout" });
|
|
339
|
+
if (code === 0 || code === EXIT_REFUSED || code === EXIT_OUTDATED) return resolve({ exit: code });
|
|
340
|
+
resolve({ failed: "crashed" });
|
|
341
|
+
});
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
async function launch(own) {
|
|
345
|
+
const list = await candidates(own);
|
|
346
|
+
for (let i = 0; i < list.length; i++) {
|
|
347
|
+
const candidate = list[i];
|
|
348
|
+
const outcome = await runOnce(candidate);
|
|
349
|
+
if ("exit" in outcome) return outcome.exit;
|
|
350
|
+
if (outcome.failed === "crashed") markBad(candidate.version);
|
|
351
|
+
const next = list[i + 1];
|
|
352
|
+
if (next) status(t.rolledBack(next.version));
|
|
353
|
+
}
|
|
354
|
+
return 1;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// src/launcher-main.ts
|
|
358
|
+
useRunnerHome();
|
|
359
|
+
var entry = path4.join(path4.dirname(fileURLToPath(import.meta.url)), "runner.mjs");
|
|
360
|
+
launch({ version: VERSION, entry }).then((code) => process.exit(code));
|