@proagentstore/cli 0.4.149 → 0.4.150
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/dist/bootstrap.js +74 -9
- package/dist/chunk-PKMXQJEJ.js +281 -0
- package/dist/index.js +389 -48
- package/package.json +1 -1
- package/dist/chunk-NNA7VZ6W.js +0 -138
package/dist/bootstrap.js
CHANGED
|
@@ -2,13 +2,21 @@
|
|
|
2
2
|
import {
|
|
3
3
|
BOOTSTRAP_ENV,
|
|
4
4
|
NO_SELF_UPDATE_ENV,
|
|
5
|
+
TRIAL_MS,
|
|
6
|
+
badVersions,
|
|
7
|
+
beginTrial,
|
|
5
8
|
cachedPayloads,
|
|
6
9
|
installPayload,
|
|
7
10
|
latestPublishedVersion,
|
|
11
|
+
logUpdateStep,
|
|
12
|
+
markBad,
|
|
8
13
|
newestPayload,
|
|
14
|
+
noteTrialStart,
|
|
9
15
|
olderThan,
|
|
10
|
-
payloadAt
|
|
11
|
-
|
|
16
|
+
payloadAt,
|
|
17
|
+
payloadRoot,
|
|
18
|
+
readTrial
|
|
19
|
+
} from "./chunk-PKMXQJEJ.js";
|
|
12
20
|
|
|
13
21
|
// src/bootstrap.ts
|
|
14
22
|
import { existsSync } from "fs";
|
|
@@ -17,20 +25,41 @@ import { fileURLToPath } from "url";
|
|
|
17
25
|
|
|
18
26
|
// src/bootstrap/stub.ts
|
|
19
27
|
import { pathToFileURL } from "url";
|
|
28
|
+
var STUB_RESTART_EXIT_CODE = 75;
|
|
29
|
+
var RESTARTED_BY = ["PAGS_UP_SUPERVISED", "PAGS_UP_LAUNCHER", "PAGS_SERVICE"];
|
|
30
|
+
function trialApplies(args) {
|
|
31
|
+
const command = args.find((a) => !a.startsWith("-"));
|
|
32
|
+
return command === "up" || command === "runner";
|
|
33
|
+
}
|
|
20
34
|
function checksForUpdate(args) {
|
|
21
35
|
return args.find((a) => !a.startsWith("-")) === "up";
|
|
22
36
|
}
|
|
23
37
|
async function runStub(deps) {
|
|
24
|
-
const log = deps.log ?? ((line) =>
|
|
25
|
-
|
|
26
|
-
|
|
38
|
+
const log = deps.log ?? ((line) => {
|
|
39
|
+
process.stderr.write(`${line}
|
|
40
|
+
`);
|
|
41
|
+
logUpdateStep(`stub: ${line}`);
|
|
42
|
+
});
|
|
43
|
+
const root = deps.root ?? payloadRoot();
|
|
44
|
+
const now = deps.now ?? Date.now;
|
|
45
|
+
const env = deps.env ?? process.env;
|
|
46
|
+
const all = () => [...deps.bundled ? [deps.bundled] : [], ...(deps.cached ?? cachedPayloads)()];
|
|
47
|
+
const local = (without = []) => {
|
|
48
|
+
const skip = /* @__PURE__ */ new Set([...Object.keys(badVersions(root)), ...without]);
|
|
49
|
+
return newestPayload(all().filter((p) => !skip.has(p.version))) ?? (without.length ? null : newestPayload(all()));
|
|
50
|
+
};
|
|
27
51
|
let chosen = local();
|
|
28
|
-
if (checksForUpdate(deps.args) && !deps.fromSource &&
|
|
52
|
+
if (checksForUpdate(deps.args) && !deps.fromSource && env[NO_SELF_UPDATE_ENV] !== "1") {
|
|
29
53
|
const latest = await (deps.latest ?? (() => latestPublishedVersion(15e3)))();
|
|
30
|
-
|
|
54
|
+
const bad = latest ? badVersions(root)[latest] : void 0;
|
|
55
|
+
if (latest && bad) {
|
|
56
|
+
log(`pags: ${latest} is published, but this machine rolled back from it (${bad}) \u2014 staying on ${chosen?.version ?? "nothing"}`);
|
|
57
|
+
} else if (latest && (!chosen || olderThan(chosen.version, latest))) {
|
|
31
58
|
log(`pags: updating ${chosen?.version ?? "(none)"} \u2192 ${latest}\u2026`);
|
|
32
59
|
try {
|
|
60
|
+
const previous = chosen?.version ?? null;
|
|
33
61
|
chosen = await (deps.install ?? ((v) => installPayload(v)))(latest);
|
|
62
|
+
beginTrial(root, chosen.version, previous, now());
|
|
34
63
|
log(`pags: now on ${chosen.version}`);
|
|
35
64
|
} catch (e) {
|
|
36
65
|
log(`pags: could not fetch ${latest} (${e instanceof Error ? e.message : String(e)}) \u2014 starting ${chosen?.version ?? "nothing"} instead`);
|
|
@@ -38,10 +67,46 @@ async function runStub(deps) {
|
|
|
38
67
|
}
|
|
39
68
|
}
|
|
40
69
|
if (!chosen) throw new Error("pags: no CLI to run \u2014 reinstall with `npm i -g @proagentstore/cli`");
|
|
41
|
-
|
|
42
|
-
|
|
70
|
+
if (trialApplies(deps.args)) {
|
|
71
|
+
const verdict = noteTrialStart(root, chosen.version, now());
|
|
72
|
+
if (verdict.kind === "give-up") {
|
|
73
|
+
const fallback = local([chosen.version]);
|
|
74
|
+
markBad(root, chosen.version, verdict.reason, fallback?.version ?? null, now());
|
|
75
|
+
log(`pags: giving up on ${chosen.version} \u2014 ${verdict.reason}; rolling back to ${fallback?.version ?? "nothing"}`);
|
|
76
|
+
if (fallback) chosen = fallback;
|
|
77
|
+
} else if (verdict.kind === "trial") {
|
|
78
|
+
watchTrial(root, chosen.version, deps, log, now, env);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (trialApplies(deps.args)) logUpdateStep(`stub: running ${chosen.version} for pags ${deps.args.find((a) => !a.startsWith("-"))}`);
|
|
82
|
+
env[BOOTSTRAP_ENV] = deps.self;
|
|
83
|
+
const load = deps.load ?? ((entry) => import(pathToFileURL(entry).href));
|
|
84
|
+
try {
|
|
85
|
+
await load(chosen.entry);
|
|
86
|
+
} catch (e) {
|
|
87
|
+
const fallback = readTrial(root)?.version === chosen.version ? local([chosen.version]) : null;
|
|
88
|
+
if (!fallback) throw e;
|
|
89
|
+
const why = `it crashed on start: ${e instanceof Error ? e.message : String(e)}`;
|
|
90
|
+
markBad(root, chosen.version, why, fallback.version, now());
|
|
91
|
+
log(`pags: giving up on ${chosen.version} \u2014 ${why}; rolling back to ${fallback.version}`);
|
|
92
|
+
chosen = fallback;
|
|
93
|
+
await load(chosen.entry);
|
|
94
|
+
}
|
|
43
95
|
return chosen;
|
|
44
96
|
}
|
|
97
|
+
function watchTrial(root, version, deps, log, now, env) {
|
|
98
|
+
const startedAt = readTrial(root)?.startedAt ?? now();
|
|
99
|
+
const timer = (deps.setTimer ?? ((fn, ms) => setTimeout(fn, ms)))(() => {
|
|
100
|
+
const given = badVersions(root)[version];
|
|
101
|
+
if (readTrial(root)?.version !== version && !given) return;
|
|
102
|
+
const reason = `it did not register with the platform within ${TRIAL_MS / 6e4} minutes of first starting`;
|
|
103
|
+
if (!given) markBad(root, version, reason, readTrial(root)?.previous ?? null, now());
|
|
104
|
+
const supervised = RESTARTED_BY.some((k) => !!env[k]);
|
|
105
|
+
log(`pags: giving up on ${version} \u2014 ${given ?? reason}.${supervised ? " Restarting on the previous version." : ' The next "pags up" runs the previous version.'}`);
|
|
106
|
+
if (supervised) (deps.exit ?? ((code) => process.exit(code)))(STUB_RESTART_EXIT_CODE);
|
|
107
|
+
}, Math.max(0, startedAt + TRIAL_MS - now()));
|
|
108
|
+
timer.unref?.();
|
|
109
|
+
}
|
|
45
110
|
|
|
46
111
|
// src/bootstrap.ts
|
|
47
112
|
var self = fileURLToPath(import.meta.url);
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
// src/bootstrap/payload.ts
|
|
2
|
+
import { execFile } from "child_process";
|
|
3
|
+
import { existsSync, mkdirSync as mkdirSync2, mkdtempSync, readdirSync, readFileSync as readFileSync2, renameSync as renameSync2, rmSync as rmSync2 } from "fs";
|
|
4
|
+
import { homedir } from "os";
|
|
5
|
+
import { join as join2, win32 } from "path";
|
|
6
|
+
import { promisify } from "util";
|
|
7
|
+
|
|
8
|
+
// src/bootstrap/trial.ts
|
|
9
|
+
import { mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "fs";
|
|
10
|
+
import { join } from "path";
|
|
11
|
+
var TRIAL_MS = 5 * 6e4;
|
|
12
|
+
var MAX_TRIAL_STARTS = 6;
|
|
13
|
+
var trialPath = (root) => join(root, ".trial.json");
|
|
14
|
+
var badPath = (root) => join(root, ".bad.json");
|
|
15
|
+
var noticePath = (root) => join(root, ".rollback.json");
|
|
16
|
+
function readJson(path) {
|
|
17
|
+
try {
|
|
18
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
19
|
+
} catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function writeJson(root, path, value) {
|
|
24
|
+
mkdirSync(root, { recursive: true });
|
|
25
|
+
const temp = `${path}.${process.pid}.tmp`;
|
|
26
|
+
writeFileSync(temp, JSON.stringify(value));
|
|
27
|
+
renameSync(temp, path);
|
|
28
|
+
}
|
|
29
|
+
function readTrial(root) {
|
|
30
|
+
const t = readJson(trialPath(root));
|
|
31
|
+
if (!t || typeof t.version !== "string" || typeof t.installedAt !== "number") return null;
|
|
32
|
+
return {
|
|
33
|
+
version: t.version,
|
|
34
|
+
previous: typeof t.previous === "string" ? t.previous : null,
|
|
35
|
+
installedAt: t.installedAt,
|
|
36
|
+
...typeof t.startedAt === "number" ? { startedAt: t.startedAt } : {},
|
|
37
|
+
starts: typeof t.starts === "number" && t.starts >= 0 ? t.starts : 0
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function beginTrial(root, version, previous, now = Date.now()) {
|
|
41
|
+
if (version === previous) return;
|
|
42
|
+
try {
|
|
43
|
+
writeJson(root, trialPath(root), { version, previous, installedAt: now, starts: 0 });
|
|
44
|
+
} catch {
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function badVersions(root) {
|
|
48
|
+
const raw = readJson(badPath(root));
|
|
49
|
+
const out = {};
|
|
50
|
+
for (const [v, why] of Object.entries(raw?.versions ?? {})) if (typeof why === "string") out[v] = why;
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
53
|
+
function markBad(root, version, reason, to, now = Date.now()) {
|
|
54
|
+
try {
|
|
55
|
+
writeJson(root, badPath(root), { versions: { ...badVersions(root), [version]: reason.slice(0, 300) } });
|
|
56
|
+
writeJson(root, noticePath(root), { from: version, to, reason: reason.slice(0, 300), at: now });
|
|
57
|
+
if (readTrial(root)?.version === version) rmSync(trialPath(root), { force: true });
|
|
58
|
+
} catch {
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function markPayloadHealthy(root, version) {
|
|
62
|
+
const trial = readTrial(root);
|
|
63
|
+
if (trial?.version !== version) return false;
|
|
64
|
+
rmSync(trialPath(root), { force: true });
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
function takeRollbackNotice(root) {
|
|
68
|
+
const n = readJson(noticePath(root));
|
|
69
|
+
rmSync(noticePath(root), { force: true });
|
|
70
|
+
if (!n || typeof n.from !== "string" || typeof n.reason !== "string") return null;
|
|
71
|
+
return { from: n.from, to: typeof n.to === "string" ? n.to : null, reason: n.reason, at: typeof n.at === "number" ? n.at : 0 };
|
|
72
|
+
}
|
|
73
|
+
function noteTrialStart(root, version, now = Date.now()) {
|
|
74
|
+
const trial = readTrial(root);
|
|
75
|
+
if (!trial || trial.version !== version) return { kind: "none" };
|
|
76
|
+
if (trial.startedAt !== void 0 && now - trial.startedAt >= TRIAL_MS) {
|
|
77
|
+
return { kind: "give-up", reason: `it did not register with the platform within ${TRIAL_MS / 6e4} minutes of first starting` };
|
|
78
|
+
}
|
|
79
|
+
if (trial.starts >= MAX_TRIAL_STARTS) {
|
|
80
|
+
return { kind: "give-up", reason: `it was started ${trial.starts} times without registering with the platform` };
|
|
81
|
+
}
|
|
82
|
+
const next = { ...trial, startedAt: trial.startedAt ?? now, starts: trial.starts + 1 };
|
|
83
|
+
try {
|
|
84
|
+
writeJson(root, trialPath(root), next);
|
|
85
|
+
} catch {
|
|
86
|
+
}
|
|
87
|
+
return { kind: "trial", trial: next };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// src/bootstrap/payload.ts
|
|
91
|
+
var run = promisify(execFile);
|
|
92
|
+
var CLI_PACKAGE = "@proagentstore/cli";
|
|
93
|
+
var BOOTSTRAP_ENV = "PAGS_BOOTSTRAP";
|
|
94
|
+
var NO_SELF_UPDATE_ENV = "PAGS_NO_SELF_UPDATE";
|
|
95
|
+
var KEEP = 2;
|
|
96
|
+
function payloadRoot() {
|
|
97
|
+
return process.env.PAGS_CLI_CACHE || join2(homedir(), ".config", "proagentstore", "cli");
|
|
98
|
+
}
|
|
99
|
+
function isVersion(v) {
|
|
100
|
+
return typeof v === "string" && /^(\d+)\.(\d+)\.(\d+)/.test(v.trim());
|
|
101
|
+
}
|
|
102
|
+
function olderThan(a, b) {
|
|
103
|
+
const parse = (v) => /^(\d+)\.(\d+)\.(\d+)/.exec(v.trim())?.slice(1).map(Number);
|
|
104
|
+
const x = parse(a);
|
|
105
|
+
const y = parse(b);
|
|
106
|
+
if (!x || !y) return false;
|
|
107
|
+
for (let i = 0; i < 3; i++) if (x[i] !== y[i]) return x[i] < y[i];
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
function payloadAt(dir) {
|
|
111
|
+
try {
|
|
112
|
+
const pkg = JSON.parse(readFileSync2(join2(dir, "package.json"), "utf-8"));
|
|
113
|
+
if (pkg.name !== CLI_PACKAGE || !pkg.version) return null;
|
|
114
|
+
const entry = join2(dir, pkg.pagsPayload || "dist/index.js");
|
|
115
|
+
return existsSync(entry) ? { version: pkg.version, entry } : null;
|
|
116
|
+
} catch {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
var installedAt = (prefix) => join2(prefix, "node_modules", ...CLI_PACKAGE.split("/"));
|
|
121
|
+
var packageDir = (root, version) => installedAt(join2(root, version));
|
|
122
|
+
function cachedPayloads(root = payloadRoot()) {
|
|
123
|
+
let names;
|
|
124
|
+
try {
|
|
125
|
+
names = readdirSync(root);
|
|
126
|
+
} catch {
|
|
127
|
+
return [];
|
|
128
|
+
}
|
|
129
|
+
return names.flatMap((v) => v.startsWith(".") ? [] : payloadAt(packageDir(root, v)) ?? []);
|
|
130
|
+
}
|
|
131
|
+
function newestPayload(payloads) {
|
|
132
|
+
return payloads.reduce((best, p) => !best || olderThan(best.version, p.version) ? p : best, null);
|
|
133
|
+
}
|
|
134
|
+
var CMD_META = /([()\][%!^"`<>&|;, *?])/g;
|
|
135
|
+
function quoteCmdArg(arg) {
|
|
136
|
+
const quoted = `"${arg.replace(/(\\*)"/g, '$1$1\\"').replace(/(\\*)$/, "$1$1")}"`;
|
|
137
|
+
return quoted.replace(CMD_META, "^$1").replace(CMD_META, "^$1");
|
|
138
|
+
}
|
|
139
|
+
var envVar = (env, name) => env[Object.keys(env).find((k) => k.toUpperCase() === name) ?? name];
|
|
140
|
+
function npmInvocation(args, deps = {}) {
|
|
141
|
+
if ((deps.platform ?? process.platform) !== "win32") return { file: "npm", args };
|
|
142
|
+
const env = deps.env ?? process.env;
|
|
143
|
+
const exists = deps.exists ?? existsSync;
|
|
144
|
+
const execPath = deps.execPath ?? process.execPath;
|
|
145
|
+
const cli = (dir) => win32.join(dir, "node_modules", "npm", "bin", "npm-cli.js");
|
|
146
|
+
const onPath = (envVar(env, "PATH") ?? "").split(";").filter((dir) => dir && exists(win32.join(dir, "npm.cmd")));
|
|
147
|
+
for (const dir of [...onPath, win32.dirname(execPath)]) {
|
|
148
|
+
if (exists(cli(dir))) return { file: execPath, args: [cli(dir), ...args] };
|
|
149
|
+
}
|
|
150
|
+
const line = ["npm", ...args.map(quoteCmdArg)].join(" ");
|
|
151
|
+
return { file: envVar(env, "COMSPEC") || "cmd.exe", args: ["/d", "/s", "/c", `"${line}"`], windowsVerbatimArguments: true };
|
|
152
|
+
}
|
|
153
|
+
function runNpm(args, options) {
|
|
154
|
+
const npm = npmInvocation(args);
|
|
155
|
+
return run(npm.file, npm.args, { ...options, ...npm.windowsVerbatimArguments ? { windowsVerbatimArguments: true } : {} });
|
|
156
|
+
}
|
|
157
|
+
async function latestPublishedRelease(timeoutMs = 3e4, signal, deps = {}) {
|
|
158
|
+
try {
|
|
159
|
+
const { stdout } = await (deps.runNpm ?? runNpm)(["view", CLI_PACKAGE, "version"], { timeout: timeoutMs, signal });
|
|
160
|
+
const version = String(stdout).trim();
|
|
161
|
+
return version ? { version } : { error: "npm printed no version" };
|
|
162
|
+
} catch (e) {
|
|
163
|
+
return { error: npmFailure(e) };
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
function npmFailure(e) {
|
|
167
|
+
const err = e;
|
|
168
|
+
const stderr = String(err?.stderr ?? "").split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
|
|
169
|
+
const npmErrors = stderr.filter((l) => /npm (ERR!|error)/i.test(l));
|
|
170
|
+
const why = npmErrors.slice(0, 2).join(" ") || stderr.at(-1) || (err?.killed || err?.signal ? `npm did not answer in time (${String(err.signal ?? "killed")})` : "") || (typeof err?.code === "string" ? `npm could not be started (${err.code}: ${String(err.message ?? "")})` : "") || String(err?.message ?? e);
|
|
171
|
+
return why.replace(/\s+/g, " ").trim().slice(0, 300);
|
|
172
|
+
}
|
|
173
|
+
async function latestPublishedVersion(timeoutMs = 3e4, signal) {
|
|
174
|
+
const r = await latestPublishedRelease(timeoutMs, signal);
|
|
175
|
+
return "version" in r ? r.version : null;
|
|
176
|
+
}
|
|
177
|
+
async function npmInstallInto(dir, version) {
|
|
178
|
+
try {
|
|
179
|
+
await runNpm(["install", "--prefix", dir, "--omit=dev", "--no-audit", "--no-fund", "--no-save", `${CLI_PACKAGE}@${version}`], { timeout: 5 * 6e4 });
|
|
180
|
+
} catch (e) {
|
|
181
|
+
const stderr = String(e.stderr ?? "").trim();
|
|
182
|
+
throw new Error((stderr || (e instanceof Error ? e.message : String(e))).slice(-400));
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
async function installPayload(version, root = payloadRoot(), install = npmInstallInto) {
|
|
186
|
+
const done = payloadAt(packageDir(root, version));
|
|
187
|
+
if (done) return done;
|
|
188
|
+
mkdirSync2(root, { recursive: true });
|
|
189
|
+
const scratch = mkdtempSync(join2(root, `.install-${version}-`));
|
|
190
|
+
try {
|
|
191
|
+
await install(scratch, version);
|
|
192
|
+
if (payloadAt(installedAt(scratch))?.version !== version) throw new Error(`npm installed no ${CLI_PACKAGE}@${version}`);
|
|
193
|
+
try {
|
|
194
|
+
renameSync2(scratch, join2(root, version));
|
|
195
|
+
} catch (e) {
|
|
196
|
+
if (!payloadAt(packageDir(root, version))) throw e;
|
|
197
|
+
}
|
|
198
|
+
} finally {
|
|
199
|
+
rmSync2(scratch, { recursive: true, force: true });
|
|
200
|
+
}
|
|
201
|
+
const payload = payloadAt(packageDir(root, version));
|
|
202
|
+
if (!payload) throw new Error(`${CLI_PACKAGE}@${version} is not usable after install`);
|
|
203
|
+
const bad = badVersions(root);
|
|
204
|
+
const others = cachedPayloads(root).filter((p) => p.version !== version).sort((a, b) => olderThan(a.version, b.version) ? 1 : -1);
|
|
205
|
+
const kept = new Set(others.filter((p) => !bad[p.version]).slice(0, KEEP - 1).map((p) => p.version));
|
|
206
|
+
for (const old of others) if (!kept.has(old.version)) rmSync2(join2(root, old.version), { recursive: true, force: true });
|
|
207
|
+
return payload;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// src/bootstrap/update-log.ts
|
|
211
|
+
import { appendFileSync, existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync3, statSync, writeFileSync as writeFileSync2 } from "fs";
|
|
212
|
+
import { homedir as homedir2 } from "os";
|
|
213
|
+
import { dirname, join as join3 } from "path";
|
|
214
|
+
var MAX_BYTES = 256 * 1024;
|
|
215
|
+
var UPDATE_LOG_UPLOAD_CHARS = 4e3;
|
|
216
|
+
function updateLogPath(env = process.env) {
|
|
217
|
+
return env.PAGS_UPDATE_LOG?.trim() || join3(env.PAGS_CONFIG_DIR?.trim() || join3(homedir2(), ".config", "proagentstore"), "update.log");
|
|
218
|
+
}
|
|
219
|
+
function logUpdateStep(step, env = process.env, now = /* @__PURE__ */ new Date()) {
|
|
220
|
+
const path = updateLogPath(env);
|
|
221
|
+
try {
|
|
222
|
+
mkdirSync3(dirname(path), { recursive: true });
|
|
223
|
+
if (existsSync2(path) && statSync(path).size > MAX_BYTES) {
|
|
224
|
+
const text = readFileSync3(path, "utf8");
|
|
225
|
+
writeFileSync2(path, text.slice(text.indexOf("\n", text.length / 2) + 1));
|
|
226
|
+
}
|
|
227
|
+
appendFileSync(path, `${now.toISOString()} [${process.pid}] ${step.replace(/\s*\n\s*/g, " | ")}
|
|
228
|
+
`);
|
|
229
|
+
} catch {
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
function updateLogTail(env = process.env, maxChars = UPDATE_LOG_UPLOAD_CHARS) {
|
|
233
|
+
try {
|
|
234
|
+
const text = readFileSync3(updateLogPath(env), "utf8").trimEnd();
|
|
235
|
+
if (!text) return null;
|
|
236
|
+
if (text.length <= maxChars) return text;
|
|
237
|
+
const cut = text.slice(-maxChars);
|
|
238
|
+
return cut.slice(cut.indexOf("\n") + 1);
|
|
239
|
+
} catch {
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
function updateLogUpload(version, env = process.env) {
|
|
244
|
+
let pending = updateLogTail(env);
|
|
245
|
+
return {
|
|
246
|
+
/** The field for a status report while the upload is pending. */
|
|
247
|
+
body: () => pending ? { updateLog: pending } : {},
|
|
248
|
+
/** The platform answered a report of this start. */
|
|
249
|
+
answered: () => {
|
|
250
|
+
if (pending === null) return;
|
|
251
|
+
pending = null;
|
|
252
|
+
logUpdateStep(`registered: ${version} answered by the platform`, env);
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export {
|
|
258
|
+
TRIAL_MS,
|
|
259
|
+
readTrial,
|
|
260
|
+
beginTrial,
|
|
261
|
+
badVersions,
|
|
262
|
+
markBad,
|
|
263
|
+
markPayloadHealthy,
|
|
264
|
+
takeRollbackNotice,
|
|
265
|
+
noteTrialStart,
|
|
266
|
+
CLI_PACKAGE,
|
|
267
|
+
BOOTSTRAP_ENV,
|
|
268
|
+
NO_SELF_UPDATE_ENV,
|
|
269
|
+
payloadRoot,
|
|
270
|
+
isVersion,
|
|
271
|
+
olderThan,
|
|
272
|
+
payloadAt,
|
|
273
|
+
cachedPayloads,
|
|
274
|
+
newestPayload,
|
|
275
|
+
runNpm,
|
|
276
|
+
latestPublishedRelease,
|
|
277
|
+
latestPublishedVersion,
|
|
278
|
+
installPayload,
|
|
279
|
+
logUpdateStep,
|
|
280
|
+
updateLogUpload
|
|
281
|
+
};
|
package/dist/index.js
CHANGED
|
@@ -2,12 +2,19 @@
|
|
|
2
2
|
import {
|
|
3
3
|
BOOTSTRAP_ENV,
|
|
4
4
|
CLI_PACKAGE,
|
|
5
|
+
badVersions,
|
|
6
|
+
beginTrial,
|
|
5
7
|
installPayload,
|
|
6
8
|
isVersion,
|
|
7
9
|
latestPublishedRelease,
|
|
10
|
+
logUpdateStep,
|
|
11
|
+
markPayloadHealthy,
|
|
8
12
|
olderThan,
|
|
9
|
-
|
|
10
|
-
|
|
13
|
+
payloadRoot,
|
|
14
|
+
runNpm,
|
|
15
|
+
takeRollbackNotice,
|
|
16
|
+
updateLogUpload
|
|
17
|
+
} from "./chunk-PKMXQJEJ.js";
|
|
11
18
|
|
|
12
19
|
// src/index.ts
|
|
13
20
|
import { createRequire as createRequire3 } from "module";
|
|
@@ -1361,6 +1368,7 @@ function deferredUpdateLeaseActive(lease, now = Date.now()) {
|
|
|
1361
1368
|
return now < lease.expiresAt;
|
|
1362
1369
|
}
|
|
1363
1370
|
function leaveForRestart(restarter, deps = { spawn: spawn3, exit: (code) => process.exit(code) }) {
|
|
1371
|
+
logUpdateStep(`restart: leaving for the ${restarter} restarter (${restarter === "command" ? "restart command started, exit 0" : `exit ${RUNNER_RESTART_EXIT_CODE}`})`);
|
|
1364
1372
|
if (restarter === "command") {
|
|
1365
1373
|
deps.spawn(process.env[RESTART_COMMAND_ENV], { shell: true, detached: true, stdio: "ignore", env: process.env }).unref();
|
|
1366
1374
|
deps.exit(0);
|
|
@@ -1379,6 +1387,8 @@ function planRunnerUpdate(f) {
|
|
|
1379
1387
|
if (f.fromSource) return { action: "refused", current: f.current, reason: "This runner runs from a source checkout \u2014 update it with `git pull` there, not npm." };
|
|
1380
1388
|
if (!f.latest) return { action: "refused", current: f.current, reason: `npm could not be asked for the latest ${CLI_PACKAGE} from this machine${f.latestError ? `: ${f.latestError}` : "."}`, retryable: true };
|
|
1381
1389
|
if (!olderThan(f.current, f.latest)) return { action: "up-to-date", current: f.current };
|
|
1390
|
+
const rolledBack = f.rolledBack?.[f.latest];
|
|
1391
|
+
if (rolledBack) return { action: "refused", current: f.current, reason: `${f.latest} was installed here and rolled back: ${rolledBack}. This machine stays on ${f.current} until a newer release is published.`, retryable: true };
|
|
1382
1392
|
if (!f.restarter) {
|
|
1383
1393
|
return {
|
|
1384
1394
|
action: "refused",
|
|
@@ -1411,17 +1421,24 @@ function planRunnerRestart(f) {
|
|
|
1411
1421
|
}
|
|
1412
1422
|
return { action: "restart", current: f.current, restarter: f.restarter };
|
|
1413
1423
|
}
|
|
1414
|
-
async function installVersion(version2) {
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
return;
|
|
1418
|
-
}
|
|
1424
|
+
async function installVersion(version2, current = null) {
|
|
1425
|
+
const bootstrapped = !!process.env[BOOTSTRAP_ENV];
|
|
1426
|
+
logUpdateStep(`install: ${current ?? "?"} \u2192 ${version2} starting (${bootstrapped ? "payload cache" : "npm i -g"})`);
|
|
1419
1427
|
try {
|
|
1420
|
-
|
|
1428
|
+
if (bootstrapped) {
|
|
1429
|
+
await installPayload(version2);
|
|
1430
|
+
beginTrial(payloadRoot(), version2, current);
|
|
1431
|
+
} else {
|
|
1432
|
+
await runNpm(["i", "-g", `${CLI_PACKAGE}@${version2}`], { timeout: 5 * 6e4 }).catch((e) => {
|
|
1433
|
+
const stderr = String(e.stderr ?? "").trim();
|
|
1434
|
+
throw new Error((stderr || (e instanceof Error ? e.message : String(e))).slice(-400));
|
|
1435
|
+
});
|
|
1436
|
+
}
|
|
1421
1437
|
} catch (e) {
|
|
1422
|
-
|
|
1423
|
-
throw
|
|
1438
|
+
logUpdateStep(`install: ${version2} FAILED \u2014 ${e instanceof Error ? e.message : String(e)}`);
|
|
1439
|
+
throw e;
|
|
1424
1440
|
}
|
|
1441
|
+
logUpdateStep(`install: ${version2} installed`);
|
|
1425
1442
|
}
|
|
1426
1443
|
|
|
1427
1444
|
// src/commands/runner/membership.ts
|
|
@@ -1776,13 +1793,15 @@ function parseAutoUpdatePolicy(value) {
|
|
|
1776
1793
|
const lastAttemptAt = raw.lastAttemptAt ?? raw.last_attempt_at;
|
|
1777
1794
|
const reason = raw.reason;
|
|
1778
1795
|
const recovery = parseRecovery(raw.recovery);
|
|
1796
|
+
const fleet = raw.fleetUpdating ?? raw.fleet_updating;
|
|
1779
1797
|
return {
|
|
1780
1798
|
autoUpdate,
|
|
1781
1799
|
...status ? { status } : {},
|
|
1782
1800
|
...typeof latestVersion === "string" ? { latestVersion } : {},
|
|
1783
1801
|
...typeof lastAttemptAt === "string" ? { lastAttemptAt } : {},
|
|
1784
1802
|
...typeof reason === "string" ? { reason } : {},
|
|
1785
|
-
...recovery ? { recovery } : {}
|
|
1803
|
+
...recovery ? { recovery } : {},
|
|
1804
|
+
...typeof fleet === "number" && Number.isInteger(fleet) && fleet >= 0 ? { fleetUpdating: fleet } : {}
|
|
1786
1805
|
};
|
|
1787
1806
|
}
|
|
1788
1807
|
function policyFromResponse(value) {
|
|
@@ -1843,7 +1862,9 @@ function acceptPolicyResponse(local, wasAuthoritative, response) {
|
|
|
1843
1862
|
const { recovery: _fromService, ...remote } = parsed;
|
|
1844
1863
|
const action = policyScheduleAction(local, remote, wasAuthoritative);
|
|
1845
1864
|
const ownerTransition = action === "cancel" || !local.autoUpdate;
|
|
1846
|
-
|
|
1865
|
+
const { fleetUpdating: _stale, ...base } = local;
|
|
1866
|
+
const fleet = remote.fleetUpdating === void 0 ? {} : { fleetUpdating: remote.fleetUpdating };
|
|
1867
|
+
return { policy: ownerTransition ? { ...base, ...remote, ...fleet } : { ...base, autoUpdate: remote.autoUpdate, ...fleet }, action };
|
|
1847
1868
|
}
|
|
1848
1869
|
var ResponseFence = class {
|
|
1849
1870
|
sent = 0;
|
|
@@ -2068,6 +2089,12 @@ var AutomaticUpdateController = class {
|
|
|
2068
2089
|
this.defer();
|
|
2069
2090
|
return;
|
|
2070
2091
|
}
|
|
2092
|
+
if (policy.fleetUpdating) {
|
|
2093
|
+
this.blockerClass = "fleet_stagger";
|
|
2094
|
+
this.deps.status("waiting-for-idle", { latestVersion: plan.latest, reason: `${policy.fleetUpdating} other machine(s) on this account are mid-update; this one updates after them.` });
|
|
2095
|
+
this.defer(plan.latest);
|
|
2096
|
+
return;
|
|
2097
|
+
}
|
|
2071
2098
|
const target = open && isVersion(open.target) ? open.target : plan.latest;
|
|
2072
2099
|
const attempt = { ...plan, latest: target };
|
|
2073
2100
|
const recovery = open ?? { target, attachments: [...this.deps.attachments?.() ?? []], at: new Date(this.now()).toISOString() };
|
|
@@ -2077,7 +2104,7 @@ var AutomaticUpdateController = class {
|
|
|
2077
2104
|
this.deps.status("restarting", { latestVersion: target, reason: void 0 });
|
|
2078
2105
|
return;
|
|
2079
2106
|
}
|
|
2080
|
-
this.blockerClass = "idle_wait";
|
|
2107
|
+
this.blockerClass = this.deps.policy().value.fleetUpdating ? "fleet_stagger" : "idle_wait";
|
|
2081
2108
|
this.defer(target);
|
|
2082
2109
|
}
|
|
2083
2110
|
defer(latest) {
|
|
@@ -2250,7 +2277,10 @@ function createPolicyChannel(deps) {
|
|
|
2250
2277
|
if (action === "cancel") deps.onAction("cancel");
|
|
2251
2278
|
else if (action === "start" && !deps.cell.refreshing) deps.onAction("start");
|
|
2252
2279
|
};
|
|
2253
|
-
const accept = (response, sentAs) =>
|
|
2280
|
+
const accept = (response, sentAs) => {
|
|
2281
|
+
act(deps.cell.accept(response, sentAs));
|
|
2282
|
+
deps.onAnswer?.();
|
|
2283
|
+
};
|
|
2254
2284
|
const register = async (id, force = deps.force, signal) => {
|
|
2255
2285
|
const sent = deps.cell.next();
|
|
2256
2286
|
try {
|
|
@@ -2300,7 +2330,7 @@ function createInstallBridge(deps) {
|
|
|
2300
2330
|
if (automatic) {
|
|
2301
2331
|
const fresh = await within((signal) => deps.refresh(signal));
|
|
2302
2332
|
if (fresh === null) expired(false, "The owner policy refresh before installing");
|
|
2303
|
-
if (!fresh || !deps.cell.policy.autoUpdate) {
|
|
2333
|
+
if (!fresh || !deps.cell.policy.autoUpdate || deps.cell.policy.fleetUpdating) {
|
|
2304
2334
|
release(false);
|
|
2305
2335
|
return false;
|
|
2306
2336
|
}
|
|
@@ -2344,6 +2374,27 @@ function createInstallBridge(deps) {
|
|
|
2344
2374
|
return { run, inFlight: () => inFlight, installedPendingRestart: () => installedPendingRestart };
|
|
2345
2375
|
}
|
|
2346
2376
|
|
|
2377
|
+
// src/commands/runner/release-trial.ts
|
|
2378
|
+
function releaseTrial(version2, cell, log, env = process.env, root = payloadRoot) {
|
|
2379
|
+
if (!env[BOOTSTRAP_ENV]) return { rollbackReason: null, proven: () => void 0, rolledBack: () => ({}) };
|
|
2380
|
+
const notice = takeRollbackNotice(root());
|
|
2381
|
+
const rollbackReason = notice ? `Rolled back from ${notice.from} to ${version2}: ${notice.reason}.` : null;
|
|
2382
|
+
if (rollbackReason) {
|
|
2383
|
+
log(`auto-update: ${rollbackReason}`);
|
|
2384
|
+
cell.record("failure", { reason: rollbackReason, recovery: void 0 });
|
|
2385
|
+
}
|
|
2386
|
+
let done = false;
|
|
2387
|
+
return {
|
|
2388
|
+
rollbackReason,
|
|
2389
|
+
proven: () => {
|
|
2390
|
+
if (done) return;
|
|
2391
|
+
done = true;
|
|
2392
|
+
if (markPayloadHealthy(root(), version2)) log(`auto-update: ${version2} registered \u2014 it is kept.`);
|
|
2393
|
+
},
|
|
2394
|
+
rolledBack: () => badVersions(root())
|
|
2395
|
+
};
|
|
2396
|
+
}
|
|
2397
|
+
|
|
2347
2398
|
// src/commands/runner/relay.ts
|
|
2348
2399
|
var MEMBERSHIP_SYNC_PATH = "/pags/membership/sync";
|
|
2349
2400
|
var CLI_CONTROL_PATHS = /* @__PURE__ */ new Set([MEMBERSHIP_SYNC_PATH, RUNNER_UPDATE_PATH, RUNNER_RESTART_PATH]);
|
|
@@ -2355,12 +2406,15 @@ async function connectViaRelay(instanceIds, localUrl, runnerToken, opts, force =
|
|
|
2355
2406
|
const machine = loadMachineIdentity(runnerNode);
|
|
2356
2407
|
const autoUpdateKey = machine.id ? autoUpdatePolicyKey(machine.id) : "";
|
|
2357
2408
|
const cell = new PolicyCell(autoUpdateKey ? loadAutoUpdatePolicy(autoUpdateKey) ?? { autoUpdate: false } : { autoUpdate: false }, (p) => saveAutoUpdatePolicy(autoUpdateKey, p), !!autoUpdateKey);
|
|
2409
|
+
const trial = releaseTrial(CLI_VERSION, cell, writeLine);
|
|
2358
2410
|
let scheduleAutomaticUpdate = () => void 0;
|
|
2359
2411
|
let autoUpdateDiagnostics = () => ({});
|
|
2412
|
+
const updateLog = updateLogUpload(CLI_VERSION);
|
|
2360
2413
|
const autoUpdateStatusBody = () => ({
|
|
2361
2414
|
status: autoUpdateStatusWire(cell.policy.status),
|
|
2362
2415
|
latestVersion: cell.policy.latestVersion,
|
|
2363
2416
|
error: cell.policy.reason,
|
|
2417
|
+
...updateLog.body(),
|
|
2364
2418
|
...autoUpdateDiagnostics()
|
|
2365
2419
|
});
|
|
2366
2420
|
const capabilities = await requestRunner("GET", "/capabilities", { url: localUrl, token: runnerToken, instanceId: instanceIds[0] });
|
|
@@ -2387,7 +2441,11 @@ async function connectViaRelay(instanceIds, localUrl, runnerToken, opts, force =
|
|
|
2387
2441
|
lastRegisterError = msg;
|
|
2388
2442
|
writeError(`register ${id.slice(0, 8)}\u2026 failed: ${msg}`);
|
|
2389
2443
|
},
|
|
2390
|
-
onAction: (action) => scheduleAutomaticUpdate(action === "start", action === "cancel")
|
|
2444
|
+
onAction: (action) => scheduleAutomaticUpdate(action === "start", action === "cancel"),
|
|
2445
|
+
onAnswer: () => {
|
|
2446
|
+
trial.proven();
|
|
2447
|
+
updateLog.answered();
|
|
2448
|
+
}
|
|
2391
2449
|
});
|
|
2392
2450
|
const registered = channel.registered;
|
|
2393
2451
|
const registerRuntime = (id, forceClaim = force) => channel.register(id, forceClaim);
|
|
@@ -2424,6 +2482,7 @@ async function connectViaRelay(instanceIds, localUrl, runnerToken, opts, force =
|
|
|
2424
2482
|
..."error" in release ? { latestError: release.error } : {},
|
|
2425
2483
|
fromSource: runsFromSource(),
|
|
2426
2484
|
restarter: restarterFrom(process.env),
|
|
2485
|
+
rolledBack: trial.rolledBack(),
|
|
2427
2486
|
// A restart ends browser/application work too. Give these durable names to the same
|
|
2428
2487
|
// planner so neither manual nor automatic update can mistake them for an idle machine.
|
|
2429
2488
|
busy
|
|
@@ -2434,7 +2493,7 @@ async function connectViaRelay(instanceIds, localUrl, runnerToken, opts, force =
|
|
|
2434
2493
|
cell,
|
|
2435
2494
|
refresh: channel.refresh,
|
|
2436
2495
|
facts: updateFacts,
|
|
2437
|
-
install: installVersion,
|
|
2496
|
+
install: (version2) => installVersion(version2, CLI_VERSION),
|
|
2438
2497
|
admission: relayMutationAdmission,
|
|
2439
2498
|
leaseActive: deferredUpdateLeaseActive,
|
|
2440
2499
|
detachAll: () => {
|
|
@@ -3531,7 +3590,7 @@ var logsCommand = new Command9("logs").description("Show the background runner's
|
|
|
3531
3590
|
// src/commands/up.ts
|
|
3532
3591
|
import { createRequire as createRequire2 } from "module";
|
|
3533
3592
|
import { Command as Command10 } from "commander";
|
|
3534
|
-
import { hostname as hostname7 } from "os";
|
|
3593
|
+
import { homedir as homedir5, hostname as hostname7, userInfo } from "os";
|
|
3535
3594
|
|
|
3536
3595
|
// src/tui.ts
|
|
3537
3596
|
import chalk from "chalk";
|
|
@@ -3653,32 +3712,35 @@ async function waitForKey(keys, onInterrupt) {
|
|
|
3653
3712
|
// src/commands/up-restart.ts
|
|
3654
3713
|
import { spawn as nodeSpawn2, spawnSync as nodeSpawnSync } from "child_process";
|
|
3655
3714
|
var UP_LAUNCHER_ENV = "PAGS_UP_LAUNCHER";
|
|
3656
|
-
function upRestartMode(env
|
|
3715
|
+
function upRestartMode(env) {
|
|
3657
3716
|
if (env[SERVICE_ENV] === "1") return "service";
|
|
3658
3717
|
if (env[RESTART_COMMAND_ENV]?.trim()) return "command";
|
|
3659
3718
|
if (env[UP_LAUNCHER_ENV] === "1") return "launcher-child";
|
|
3660
|
-
return
|
|
3719
|
+
return "launcher";
|
|
3720
|
+
}
|
|
3721
|
+
function relaunchDelayMs(failures) {
|
|
3722
|
+
return Math.min(5 * 6e4, 1e4 * 2 ** Math.max(0, failures - 1));
|
|
3661
3723
|
}
|
|
3662
3724
|
function processRestartDeps() {
|
|
3663
|
-
const execve = process.execve;
|
|
3664
3725
|
return {
|
|
3665
3726
|
env: process.env,
|
|
3666
|
-
platform: process.platform,
|
|
3667
3727
|
execPath: process.execPath,
|
|
3668
3728
|
execArgv: process.execArgv,
|
|
3669
3729
|
script: process.argv[1],
|
|
3670
|
-
execve: typeof execve === "function" ? execve.bind(process) : void 0,
|
|
3671
3730
|
spawn: nodeSpawn2,
|
|
3672
3731
|
spawnSync: nodeSpawnSync,
|
|
3673
3732
|
exit: (code) => process.exit(code),
|
|
3674
3733
|
log: () => {
|
|
3675
|
-
}
|
|
3734
|
+
},
|
|
3735
|
+
sleep: (ms) => Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms),
|
|
3736
|
+
step: (line) => logUpdateStep(line)
|
|
3676
3737
|
};
|
|
3677
3738
|
}
|
|
3678
3739
|
function restartPagsUp(args, extraEnv, deps) {
|
|
3679
3740
|
const env = { ...deps.env, ...extraEnv };
|
|
3680
3741
|
const argv = [...deps.execArgv, deps.script, ...args];
|
|
3681
|
-
const mode = upRestartMode(env
|
|
3742
|
+
const mode = upRestartMode(env);
|
|
3743
|
+
deps.step(`restart: pags ${args.join(" ")} \u2014 leaving via ${mode}`);
|
|
3682
3744
|
if (mode === "service" || mode === "command") {
|
|
3683
3745
|
leaveForRestart(mode, { spawn: deps.spawn, exit: deps.exit });
|
|
3684
3746
|
return;
|
|
@@ -3687,24 +3749,31 @@ function restartPagsUp(args, extraEnv, deps) {
|
|
|
3687
3749
|
deps.exit(RUNNER_RESTART_EXIT_CODE);
|
|
3688
3750
|
return;
|
|
3689
3751
|
}
|
|
3690
|
-
|
|
3691
|
-
try {
|
|
3692
|
-
deps.execve(deps.execPath, [deps.execPath, ...argv], env);
|
|
3693
|
-
} catch (e) {
|
|
3694
|
-
deps.log(`Restart failed (${e instanceof Error ? e.message : String(e)}) \u2014 the runner is NOT running. Run 'pags up' again.`);
|
|
3695
|
-
deps.exit(1);
|
|
3696
|
-
}
|
|
3697
|
-
return;
|
|
3698
|
-
}
|
|
3752
|
+
let failures = 0;
|
|
3699
3753
|
for (let first = true; ; first = false) {
|
|
3754
|
+
const unattended = !first || env.PAGS_NO_PROMPT === "1";
|
|
3755
|
+
deps.step(`launcher: starting pags up${unattended ? " (unattended)" : ""}`);
|
|
3700
3756
|
const r = deps.spawnSync(deps.execPath, argv, { stdio: "inherit", env: { ...env, [UP_LAUNCHER_ENV]: "1", ...first ? {} : { PAGS_NO_PROMPT: "1" } } });
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3757
|
+
const how = r.error ? `could not start (${r.error.message})` : r.signal ? `was killed by ${r.signal}` : `exited ${r.status}`;
|
|
3758
|
+
deps.step(`launcher: pags up ${how}`);
|
|
3759
|
+
if (r.status === RUNNER_RESTART_EXIT_CODE) {
|
|
3760
|
+
failures = 0;
|
|
3761
|
+
continue;
|
|
3762
|
+
}
|
|
3763
|
+
if (!r.error && r.status === 0) {
|
|
3764
|
+
deps.exit(0);
|
|
3705
3765
|
return;
|
|
3706
3766
|
}
|
|
3707
|
-
|
|
3767
|
+
if (unattended) {
|
|
3768
|
+
failures++;
|
|
3769
|
+
const wait = relaunchDelayMs(failures);
|
|
3770
|
+
deps.log(`pags up ${how} \u2014 starting it again in ${Math.round(wait / 1e3)}s (attempt ${failures + 1}).`);
|
|
3771
|
+
deps.step(`launcher: retrying in ${Math.round(wait / 1e3)}s (failure ${failures})`);
|
|
3772
|
+
deps.sleep(wait);
|
|
3773
|
+
continue;
|
|
3774
|
+
}
|
|
3775
|
+
deps.log(`Restart failed${typeof r.status === "number" ? ` (exit ${r.status})` : ""} \u2014 the runner is NOT running. Run 'pags up' again.`);
|
|
3776
|
+
deps.exit(typeof r.status === "number" && r.status !== 0 ? r.status : 1);
|
|
3708
3777
|
return;
|
|
3709
3778
|
}
|
|
3710
3779
|
}
|
|
@@ -3850,11 +3919,244 @@ async function startInBackground(start, deps = processDeps()) {
|
|
|
3850
3919
|
return 1;
|
|
3851
3920
|
}
|
|
3852
3921
|
|
|
3922
|
+
// src/service-install.ts
|
|
3923
|
+
import { execFileSync as execFileSync3 } from "child_process";
|
|
3924
|
+
import { mkdirSync as mkdirSync9, rmSync, writeFileSync as writeFileSync7 } from "fs";
|
|
3925
|
+
import { posix, win32 as win322 } from "path";
|
|
3926
|
+
var SERVICE_LABEL = "online.proagentstore.pags-up";
|
|
3927
|
+
var SYSTEMD_UNIT = "pags-up.service";
|
|
3928
|
+
var WINDOWS_TASK = "pags up";
|
|
3929
|
+
var pathFor = (ctx) => ctx.platform === "win32" ? win322 : posix;
|
|
3930
|
+
var supervisorPath = (ctx) => pathFor(ctx).join(ctx.configDir, "pags-up-supervisor.mjs");
|
|
3931
|
+
var serviceLogPath = (ctx) => pathFor(ctx).join(ctx.configDir, "pags-up.log");
|
|
3932
|
+
function supervisorScript(ctx) {
|
|
3933
|
+
const config = JSON.stringify({ node: ctx.nodePath, script: ctx.script, log: serviceLogPath(ctx), path: ctx.pathEnv });
|
|
3934
|
+
return `// Written by \`pags up --install-service\` (#1141). Keeps \`pags up\` running; see packages/cli/src/service-install.ts.
|
|
3935
|
+
import { spawn } from "node:child_process";
|
|
3936
|
+
import { appendFileSync, closeSync, openSync } from "node:fs";
|
|
3937
|
+
|
|
3938
|
+
const config = ${config};
|
|
3939
|
+
const num = (name, fallback) => { const n = Number(process.env[name]); return Number.isFinite(n) && n > 0 ? n : fallback; };
|
|
3940
|
+
const MIN = num("PAGS_SUPERVISOR_MIN_MS", 10_000);
|
|
3941
|
+
const MAX = num("PAGS_SUPERVISOR_MAX_MS", 300_000);
|
|
3942
|
+
const RESET = num("PAGS_SUPERVISOR_RESET_MS", 600_000);
|
|
3943
|
+
const RESTART_CODE = 75; // \`pags up\` asking to be started again: an update, or runner_restart
|
|
3944
|
+
|
|
3945
|
+
const say = (line) => { try { appendFileSync(config.log, \`[\${new Date().toISOString()}] supervisor: \${line}\\n\`); } catch { /* a log that cannot be written must not stop the runner */ } };
|
|
3946
|
+
let delay = MIN;
|
|
3947
|
+
let child = null;
|
|
3948
|
+
let stopping = false;
|
|
3949
|
+
|
|
3950
|
+
function start() {
|
|
3951
|
+
if (stopping) return;
|
|
3952
|
+
const startedAt = Date.now();
|
|
3953
|
+
let out = "ignore";
|
|
3954
|
+
try { out = openSync(config.log, "a"); } catch { /* run without a log rather than not at all */ }
|
|
3955
|
+
say("starting pags up");
|
|
3956
|
+
child = spawn(config.node, [config.script, "up", "--headless"], {
|
|
3957
|
+
stdio: ["ignore", out, out],
|
|
3958
|
+
env: { ...process.env, PATH: config.path || process.env.PATH, PAGS_SERVICE: "1", PAGS_SERVICE_NAME: "${SERVICE_LABEL}", PAGS_NO_PROMPT: "1" },
|
|
3959
|
+
windowsHide: true,
|
|
3960
|
+
});
|
|
3961
|
+
if (typeof out === "number") closeSync(out);
|
|
3962
|
+
child.on("error", (e) => say(\`could not start pags up: \${e.message}\`));
|
|
3963
|
+
child.on("exit", (code, signal) => {
|
|
3964
|
+
child = null;
|
|
3965
|
+
if (stopping) return;
|
|
3966
|
+
if (Date.now() - startedAt >= RESET) delay = MIN;
|
|
3967
|
+
const wait = code === RESTART_CODE ? 1_000 : delay;
|
|
3968
|
+
say(\`pags up exited (\${signal ?? code}); starting it again in \${Math.round(wait / 1000)}s\`);
|
|
3969
|
+
if (code !== RESTART_CODE) delay = Math.min(delay * 2, MAX);
|
|
3970
|
+
setTimeout(start, wait);
|
|
3971
|
+
});
|
|
3972
|
+
}
|
|
3973
|
+
|
|
3974
|
+
for (const sig of ["SIGTERM", "SIGINT"]) {
|
|
3975
|
+
process.on(sig, () => {
|
|
3976
|
+
stopping = true;
|
|
3977
|
+
say(\`stopping (\${sig})\`);
|
|
3978
|
+
if (child) child.kill("SIGTERM");
|
|
3979
|
+
setTimeout(() => process.exit(0), 5_000).unref();
|
|
3980
|
+
if (!child) process.exit(0);
|
|
3981
|
+
else child.once("exit", () => process.exit(0));
|
|
3982
|
+
});
|
|
3983
|
+
}
|
|
3984
|
+
start();
|
|
3985
|
+
`;
|
|
3986
|
+
}
|
|
3987
|
+
var xml = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
3988
|
+
var unitWord = (s) => `"${s.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
3989
|
+
var ps = (s) => `'${s.replace(/'/g, "''")}'`;
|
|
3990
|
+
function servicePlan(ctx) {
|
|
3991
|
+
const supervisor = supervisorPath(ctx);
|
|
3992
|
+
const log = serviceLogPath(ctx);
|
|
3993
|
+
const files = [{ path: supervisor, content: supervisorScript(ctx) }];
|
|
3994
|
+
if (ctx.platform === "darwin") {
|
|
3995
|
+
const plist = posix.join(ctx.home, "Library", "LaunchAgents", `${SERVICE_LABEL}.plist`);
|
|
3996
|
+
const domain = `gui/${ctx.uid ?? 501}`;
|
|
3997
|
+
files.push({
|
|
3998
|
+
path: plist,
|
|
3999
|
+
content: `<?xml version="1.0" encoding="UTF-8"?>
|
|
4000
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
4001
|
+
<plist version="1.0">
|
|
4002
|
+
<dict>
|
|
4003
|
+
<key>Label</key><string>${SERVICE_LABEL}</string>
|
|
4004
|
+
<key>ProgramArguments</key>
|
|
4005
|
+
<array>
|
|
4006
|
+
<string>${xml(ctx.nodePath)}</string>
|
|
4007
|
+
<string>${xml(supervisor)}</string>
|
|
4008
|
+
</array>
|
|
4009
|
+
<key>RunAtLoad</key><true/>
|
|
4010
|
+
<key>KeepAlive</key><true/>
|
|
4011
|
+
<key>ThrottleInterval</key><integer>30</integer>
|
|
4012
|
+
<key>ProcessType</key><string>Background</string>
|
|
4013
|
+
<key>StandardOutPath</key><string>${xml(log)}</string>
|
|
4014
|
+
<key>StandardErrorPath</key><string>${xml(log)}</string>
|
|
4015
|
+
</dict>
|
|
4016
|
+
</plist>
|
|
4017
|
+
`
|
|
4018
|
+
});
|
|
4019
|
+
return {
|
|
4020
|
+
kind: "launchd",
|
|
4021
|
+
files,
|
|
4022
|
+
commands: [
|
|
4023
|
+
{ file: "launchctl", args: ["bootout", `${domain}/${SERVICE_LABEL}`], optional: true, why: "unload an earlier copy" },
|
|
4024
|
+
{ file: "launchctl", args: ["bootstrap", domain, plist], why: "load the LaunchAgent" },
|
|
4025
|
+
{ file: "launchctl", args: ["kickstart", "-k", `${domain}/${SERVICE_LABEL}`], why: "start it now" }
|
|
4026
|
+
],
|
|
4027
|
+
notes: [
|
|
4028
|
+
`LaunchAgent ${plist} \u2014 runs while this user is logged in. On a machine that must survive a reboot unattended, turn on automatic login for this user.`,
|
|
4029
|
+
`Remove it with: pags up --uninstall-service`
|
|
4030
|
+
]
|
|
4031
|
+
};
|
|
4032
|
+
}
|
|
4033
|
+
if (ctx.platform === "win32") {
|
|
4034
|
+
const task = win322.join(ctx.configDir, "pags-up-task.ps1");
|
|
4035
|
+
const principal = `${ctx.domain ? `${ctx.domain}\\` : ""}${ctx.user ?? ""}`;
|
|
4036
|
+
files.push({
|
|
4037
|
+
path: task,
|
|
4038
|
+
content: `# Written by \`pags up --install-service\` (#1141). Registers the "${WINDOWS_TASK}" Scheduled Task; needs an elevated PowerShell.
|
|
4039
|
+
$ErrorActionPreference = 'Stop'
|
|
4040
|
+
$action = New-ScheduledTaskAction -Execute ${ps(ctx.nodePath)} -Argument ${ps(`"${supervisor}"`)}
|
|
4041
|
+
$trigger = New-ScheduledTaskTrigger -AtStartup
|
|
4042
|
+
$principal = New-ScheduledTaskPrincipal -UserId ${ps(principal)} -LogonType S4U -RunLevel Limited
|
|
4043
|
+
$settings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit ([TimeSpan]::Zero) -RestartCount 999 -RestartInterval (New-TimeSpan -Minutes 1) -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable
|
|
4044
|
+
Unregister-ScheduledTask -TaskName ${ps(WINDOWS_TASK)} -Confirm:$false -ErrorAction SilentlyContinue
|
|
4045
|
+
Register-ScheduledTask -TaskName ${ps(WINDOWS_TASK)} -Action $action -Trigger $trigger -Principal $principal -Settings $settings | Out-Null
|
|
4046
|
+
Start-ScheduledTask -TaskName ${ps(WINDOWS_TASK)}
|
|
4047
|
+
`
|
|
4048
|
+
});
|
|
4049
|
+
return {
|
|
4050
|
+
kind: "scheduled-task",
|
|
4051
|
+
files,
|
|
4052
|
+
commands: [{ file: "powershell.exe", args: ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", task], why: "register and start the Scheduled Task" }],
|
|
4053
|
+
notes: [
|
|
4054
|
+
`Scheduled Task "${WINDOWS_TASK}" \u2014 starts at boot as ${principal || "this user"}, with nobody logged in. Registering it needs an elevated PowerShell: if it was refused, run ${task} from one.`,
|
|
4055
|
+
`Remove it with: pags up --uninstall-service`
|
|
4056
|
+
]
|
|
4057
|
+
};
|
|
4058
|
+
}
|
|
4059
|
+
const unit = posix.join(ctx.home, ".config", "systemd", "user", SYSTEMD_UNIT);
|
|
4060
|
+
files.push({
|
|
4061
|
+
path: unit,
|
|
4062
|
+
content: `# Written by \`pags up --install-service\` (#1141).
|
|
4063
|
+
[Unit]
|
|
4064
|
+
Description=ProAgentStore runner (pags up)
|
|
4065
|
+
After=network-online.target
|
|
4066
|
+
Wants=network-online.target
|
|
4067
|
+
StartLimitIntervalSec=0
|
|
4068
|
+
|
|
4069
|
+
[Service]
|
|
4070
|
+
ExecStart=${unitWord(ctx.nodePath)} ${unitWord(supervisor)}
|
|
4071
|
+
Environment=${unitWord(`PATH=${ctx.pathEnv}`)}
|
|
4072
|
+
Restart=always
|
|
4073
|
+
RestartSec=10
|
|
4074
|
+
KillMode=mixed
|
|
4075
|
+
|
|
4076
|
+
[Install]
|
|
4077
|
+
WantedBy=default.target
|
|
4078
|
+
`
|
|
4079
|
+
});
|
|
4080
|
+
return {
|
|
4081
|
+
kind: "systemd",
|
|
4082
|
+
files,
|
|
4083
|
+
commands: [
|
|
4084
|
+
{ file: "systemctl", args: ["--user", "daemon-reload"], why: "read the unit" },
|
|
4085
|
+
{ file: "systemctl", args: ["--user", "enable", "--now", SYSTEMD_UNIT], why: "enable and start it" },
|
|
4086
|
+
{ file: "loginctl", args: ["enable-linger", ...ctx.user ? [ctx.user] : []], optional: true, why: "keep it running with nobody logged in" }
|
|
4087
|
+
],
|
|
4088
|
+
notes: [`systemd user unit ${unit}. Logs: ${log} (and journalctl --user -u ${SYSTEMD_UNIT}).`, `Remove it with: pags up --uninstall-service`]
|
|
4089
|
+
};
|
|
4090
|
+
}
|
|
4091
|
+
function serviceRemovalPlan(ctx) {
|
|
4092
|
+
const plan = servicePlan(ctx);
|
|
4093
|
+
const files = plan.files.map((f) => f.path);
|
|
4094
|
+
if (plan.kind === "launchd") return { files, commands: [{ file: "launchctl", args: ["bootout", `gui/${ctx.uid ?? 501}/${SERVICE_LABEL}`], optional: true, why: "unload the LaunchAgent" }] };
|
|
4095
|
+
if (plan.kind === "systemd") return { files, commands: [{ file: "systemctl", args: ["--user", "disable", "--now", SYSTEMD_UNIT], optional: true, why: "stop and disable the unit" }] };
|
|
4096
|
+
return {
|
|
4097
|
+
files,
|
|
4098
|
+
commands: [{ file: "powershell.exe", args: ["-NoProfile", "-Command", `Stop-ScheduledTask -TaskName ${ps(WINDOWS_TASK)} -ErrorAction SilentlyContinue; Unregister-ScheduledTask -TaskName ${ps(WINDOWS_TASK)} -Confirm:$false`], optional: true, why: "remove the Scheduled Task" }]
|
|
4099
|
+
};
|
|
4100
|
+
}
|
|
4101
|
+
var defaultWrite = (path, content) => {
|
|
4102
|
+
mkdirSync9(path.replace(/[\\/][^\\/]*$/, ""), { recursive: true });
|
|
4103
|
+
writeFileSync7(path, content);
|
|
4104
|
+
};
|
|
4105
|
+
var defaultRun = (file, args) => void execFileSync3(file, args, { stdio: "pipe", timeout: 6e4 });
|
|
4106
|
+
function installService(ctx, deps = {}) {
|
|
4107
|
+
const plan = servicePlan(ctx);
|
|
4108
|
+
const lines = [];
|
|
4109
|
+
for (const f of plan.files) {
|
|
4110
|
+
(deps.write ?? defaultWrite)(f.path, f.content);
|
|
4111
|
+
lines.push(`wrote ${f.path}`);
|
|
4112
|
+
}
|
|
4113
|
+
let ok = true;
|
|
4114
|
+
for (const c2 of plan.commands) {
|
|
4115
|
+
try {
|
|
4116
|
+
(deps.run ?? defaultRun)(c2.file, c2.args);
|
|
4117
|
+
lines.push(`${c2.why}: ok`);
|
|
4118
|
+
} catch (e) {
|
|
4119
|
+
const why = String(e.stderr ?? "").trim() || (e instanceof Error ? e.message : String(e));
|
|
4120
|
+
lines.push(`${c2.why}: ${c2.optional ? "skipped" : "FAILED"} \u2014 ${why.split(/\r?\n/)[0].slice(0, 200)}`);
|
|
4121
|
+
if (!c2.optional) ok = false;
|
|
4122
|
+
}
|
|
4123
|
+
}
|
|
4124
|
+
return { ok, lines: [...lines, ...plan.notes] };
|
|
4125
|
+
}
|
|
4126
|
+
function uninstallService(ctx, deps = {}) {
|
|
4127
|
+
const plan = serviceRemovalPlan(ctx);
|
|
4128
|
+
const lines = [];
|
|
4129
|
+
for (const c2 of plan.commands) {
|
|
4130
|
+
try {
|
|
4131
|
+
(deps.run ?? defaultRun)(c2.file, c2.args);
|
|
4132
|
+
lines.push(`${c2.why}: ok`);
|
|
4133
|
+
} catch (e) {
|
|
4134
|
+
lines.push(`${c2.why}: skipped \u2014 ${(e instanceof Error ? e.message : String(e)).split(/\r?\n/)[0].slice(0, 200)}`);
|
|
4135
|
+
}
|
|
4136
|
+
}
|
|
4137
|
+
for (const f of plan.files) {
|
|
4138
|
+
(deps.remove ?? ((p) => rmSync(p, { force: true })))(f);
|
|
4139
|
+
lines.push(`removed ${f}`);
|
|
4140
|
+
}
|
|
4141
|
+
return { lines };
|
|
4142
|
+
}
|
|
4143
|
+
|
|
3853
4144
|
// src/commands/up.ts
|
|
3854
4145
|
var API_BASE3 = "https://api.proagentstore.online";
|
|
3855
4146
|
var CLI_VERSION2 = createRequire2(import.meta.url)("../package.json").version;
|
|
3856
|
-
var upCommand = new Command10("up").description("Start the browser runner for all your agent instances").option("--headless", "Run browser in headless mode").option("--instance <id>", "Connect to a specific instance only").option("--force", "Take over from another connected machine").option("--replace", "Stop the `pags up` already running here for this account, then take over").option("--now", "With --replace: stop it even though work is running on it (that work is lost)").option("--background", "Run the runner in the background, with no terminal open (stop it with `pags down`)").action(async (opts) => {
|
|
4147
|
+
var upCommand = new Command10("up").description("Start the browser runner for all your agent instances").option("--headless", "Run browser in headless mode").option("--instance <id>", "Connect to a specific instance only").option("--force", "Take over from another connected machine").option("--replace", "Stop the `pags up` already running here for this account, then take over").option("--now", "With --replace: stop it even though work is running on it (that work is lost)").option("--background", "Run the runner in the background, with no terminal open (stop it with `pags down`)").option("--install-service", "Install an OS service that keeps `pags up` running (launchd, systemd or a Scheduled Task), then exit").option("--uninstall-service", "Remove the service --install-service installed, then exit").action(async (opts) => {
|
|
4148
|
+
if (opts.uninstallService) {
|
|
4149
|
+
for (const line of uninstallService(serviceContext()).lines) writeLine(line);
|
|
4150
|
+
return;
|
|
4151
|
+
}
|
|
3857
4152
|
const session = requireSession();
|
|
4153
|
+
if (opts.installService) {
|
|
4154
|
+
const result = installService(serviceContext());
|
|
4155
|
+
for (const line of result.lines) writeLine(line);
|
|
4156
|
+
writeLine(result.ok ? "The service starts `pags up` now and whenever it exits. A `pags up` already running here keeps the lock; the service takes over once it stops." : "The service was not fully installed \u2014 see the FAILED line above.");
|
|
4157
|
+
if (!result.ok) process.exitCode = 1;
|
|
4158
|
+
return;
|
|
4159
|
+
}
|
|
3858
4160
|
const state = {
|
|
3859
4161
|
user: session.user.login,
|
|
3860
4162
|
instances: [],
|
|
@@ -3871,18 +4173,34 @@ var upCommand = new Command10("up").description("Start the browser runner for al
|
|
|
3871
4173
|
// as taking a few seconds, indefinitely, because nothing counted (#497).
|
|
3872
4174
|
startedAt: Date.now()
|
|
3873
4175
|
};
|
|
4176
|
+
const unattended = process.env.PAGS_NO_PROMPT === "1";
|
|
4177
|
+
logUpdateStep(`start: pags up ${CLI_VERSION2}${unattended ? " (unattended)" : ""}`);
|
|
3874
4178
|
clearScreen();
|
|
3875
4179
|
printLogo(CLI_VERSION2);
|
|
3876
4180
|
printStep("Signed in as " + session.user.login, "ok");
|
|
3877
4181
|
printStep("Fetching instances...", "wait");
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
|
|
4182
|
+
let data;
|
|
4183
|
+
for (let failures = 1; ; failures++) {
|
|
4184
|
+
let failure;
|
|
4185
|
+
try {
|
|
4186
|
+
const res = await fetch(`${API_BASE3}/v1/instances/my/instances`, { headers: { Authorization: `Bearer ${session.token}` } });
|
|
4187
|
+
if (res.ok) {
|
|
4188
|
+
data = await res.json();
|
|
4189
|
+
break;
|
|
4190
|
+
}
|
|
4191
|
+
failure = `HTTP ${res.status}`;
|
|
4192
|
+
} catch (e) {
|
|
4193
|
+
failure = e instanceof Error ? e.message : String(e);
|
|
4194
|
+
}
|
|
4195
|
+
logUpdateStep(`start: fetching instances failed (${failure})${unattended ? "" : " \u2014 exiting"}`);
|
|
4196
|
+
if (!unattended) {
|
|
4197
|
+
printStep("Failed to fetch instances: " + failure, "fail");
|
|
4198
|
+
process.exit(1);
|
|
4199
|
+
}
|
|
4200
|
+
const wait = Math.min(5 * 6e4, 5e3 * 2 ** (failures - 1));
|
|
4201
|
+
printStep(`Failed to fetch instances (${failure}) \u2014 retrying in ${Math.round(wait / 1e3)}s`, "wait");
|
|
4202
|
+
await new Promise((resolve5) => setTimeout(resolve5, wait));
|
|
3884
4203
|
}
|
|
3885
|
-
const data = await res.json();
|
|
3886
4204
|
const active = (data.instances || []).filter((i) => i.status === "active");
|
|
3887
4205
|
let instances = active;
|
|
3888
4206
|
let elsewhere = [];
|
|
@@ -3930,9 +4248,10 @@ var upCommand = new Command10("up").description("Start the browser runner for al
|
|
|
3930
4248
|
state.activeInstance = instances.length === 1 ? instances[0].name || instances[0].slug || instances[0].id.slice(0, 8) : `${instances.length} agents`;
|
|
3931
4249
|
printStep(`Connecting ${state.activeInstance}\u2026`, "wait");
|
|
3932
4250
|
const lockFile = lockPath(session.user.login);
|
|
3933
|
-
if (opts.background && process.env[BACKGROUND_RESTART_ENV] === "1") await waitUntilTakeable(() => inspectHolder(lockFile));
|
|
4251
|
+
if (unattended || opts.background && process.env[BACKGROUND_RESTART_ENV] === "1") await waitUntilTakeable(() => inspectHolder(lockFile), { timeoutMs: 6e4 });
|
|
3934
4252
|
const held = await inspectHolder(lockFile);
|
|
3935
4253
|
if (held.lock && !isTakeable(held.verdict)) {
|
|
4254
|
+
logUpdateStep(`start: the lock is held by pid ${held.lock.pid} (${held.verdict})${opts.replace ? " \u2014 replacing it" : " \u2014 exiting"}`);
|
|
3936
4255
|
if (!opts.replace) {
|
|
3937
4256
|
clearScreen();
|
|
3938
4257
|
printLogo(CLI_VERSION2);
|
|
@@ -4037,10 +4356,12 @@ var upCommand = new Command10("up").description("Start the browser runner for al
|
|
|
4037
4356
|
};
|
|
4038
4357
|
child.on("exit", (code, signal) => {
|
|
4039
4358
|
if (code === RUNNER_RESTART_EXIT_CODE) {
|
|
4359
|
+
logUpdateStep("restart: runner connect asked for a restart (exit 75)");
|
|
4040
4360
|
restartUp("Runner updated remotely \u2014 restarting pags up on the new version\u2026", { PAGS_NO_PROMPT: "1" });
|
|
4041
4361
|
return;
|
|
4042
4362
|
}
|
|
4043
4363
|
childDead = true;
|
|
4364
|
+
logUpdateStep(`runner connect ${signal ? `was killed by ${signal}` : `exited ${code}`}${stopping ? " (stopping)" : ""}`);
|
|
4044
4365
|
if (code !== 0 && !stopping) recordConnectExit(code, signal ?? null, logs.slice(-20));
|
|
4045
4366
|
if (code === null) {
|
|
4046
4367
|
state.runner = "error";
|
|
@@ -4125,6 +4446,26 @@ var downCommand = new Command10("down").description("Stop the browser runner and
|
|
|
4125
4446
|
}
|
|
4126
4447
|
writeLine("");
|
|
4127
4448
|
});
|
|
4449
|
+
function serviceContext() {
|
|
4450
|
+
const user = (() => {
|
|
4451
|
+
try {
|
|
4452
|
+
return userInfo().username;
|
|
4453
|
+
} catch {
|
|
4454
|
+
return process.env.USER || process.env.USERNAME || void 0;
|
|
4455
|
+
}
|
|
4456
|
+
})();
|
|
4457
|
+
return {
|
|
4458
|
+
platform: process.platform,
|
|
4459
|
+
home: homedir5(),
|
|
4460
|
+
nodePath: process.execPath,
|
|
4461
|
+
script: process.argv[1],
|
|
4462
|
+
configDir: configDir(),
|
|
4463
|
+
pathEnv: process.env.PATH ?? "",
|
|
4464
|
+
uid: process.getuid?.(),
|
|
4465
|
+
user,
|
|
4466
|
+
domain: process.env.USERDOMAIN
|
|
4467
|
+
};
|
|
4468
|
+
}
|
|
4128
4469
|
|
|
4129
4470
|
// src/index.ts
|
|
4130
4471
|
var require2 = createRequire3(import.meta.url);
|
package/package.json
CHANGED
package/dist/chunk-NNA7VZ6W.js
DELETED
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
// src/bootstrap/payload.ts
|
|
2
|
-
import { execFile } from "child_process";
|
|
3
|
-
import { existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, renameSync, rmSync } from "fs";
|
|
4
|
-
import { homedir } from "os";
|
|
5
|
-
import { join, win32 } from "path";
|
|
6
|
-
import { promisify } from "util";
|
|
7
|
-
var run = promisify(execFile);
|
|
8
|
-
var CLI_PACKAGE = "@proagentstore/cli";
|
|
9
|
-
var BOOTSTRAP_ENV = "PAGS_BOOTSTRAP";
|
|
10
|
-
var NO_SELF_UPDATE_ENV = "PAGS_NO_SELF_UPDATE";
|
|
11
|
-
var KEEP = 2;
|
|
12
|
-
function payloadRoot() {
|
|
13
|
-
return process.env.PAGS_CLI_CACHE || join(homedir(), ".config", "proagentstore", "cli");
|
|
14
|
-
}
|
|
15
|
-
function isVersion(v) {
|
|
16
|
-
return typeof v === "string" && /^(\d+)\.(\d+)\.(\d+)/.test(v.trim());
|
|
17
|
-
}
|
|
18
|
-
function olderThan(a, b) {
|
|
19
|
-
const parse = (v) => /^(\d+)\.(\d+)\.(\d+)/.exec(v.trim())?.slice(1).map(Number);
|
|
20
|
-
const x = parse(a);
|
|
21
|
-
const y = parse(b);
|
|
22
|
-
if (!x || !y) return false;
|
|
23
|
-
for (let i = 0; i < 3; i++) if (x[i] !== y[i]) return x[i] < y[i];
|
|
24
|
-
return false;
|
|
25
|
-
}
|
|
26
|
-
function payloadAt(dir) {
|
|
27
|
-
try {
|
|
28
|
-
const pkg = JSON.parse(readFileSync(join(dir, "package.json"), "utf-8"));
|
|
29
|
-
if (pkg.name !== CLI_PACKAGE || !pkg.version) return null;
|
|
30
|
-
const entry = join(dir, pkg.pagsPayload || "dist/index.js");
|
|
31
|
-
return existsSync(entry) ? { version: pkg.version, entry } : null;
|
|
32
|
-
} catch {
|
|
33
|
-
return null;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
var installedAt = (prefix) => join(prefix, "node_modules", ...CLI_PACKAGE.split("/"));
|
|
37
|
-
var packageDir = (root, version) => installedAt(join(root, version));
|
|
38
|
-
function cachedPayloads(root = payloadRoot()) {
|
|
39
|
-
let names;
|
|
40
|
-
try {
|
|
41
|
-
names = readdirSync(root);
|
|
42
|
-
} catch {
|
|
43
|
-
return [];
|
|
44
|
-
}
|
|
45
|
-
return names.flatMap((v) => v.startsWith(".") ? [] : payloadAt(packageDir(root, v)) ?? []);
|
|
46
|
-
}
|
|
47
|
-
function newestPayload(payloads) {
|
|
48
|
-
return payloads.reduce((best, p) => !best || olderThan(best.version, p.version) ? p : best, null);
|
|
49
|
-
}
|
|
50
|
-
var CMD_META = /([()\][%!^"`<>&|;, *?])/g;
|
|
51
|
-
function quoteCmdArg(arg) {
|
|
52
|
-
const quoted = `"${arg.replace(/(\\*)"/g, '$1$1\\"').replace(/(\\*)$/, "$1$1")}"`;
|
|
53
|
-
return quoted.replace(CMD_META, "^$1").replace(CMD_META, "^$1");
|
|
54
|
-
}
|
|
55
|
-
var envVar = (env, name) => env[Object.keys(env).find((k) => k.toUpperCase() === name) ?? name];
|
|
56
|
-
function npmInvocation(args, deps = {}) {
|
|
57
|
-
if ((deps.platform ?? process.platform) !== "win32") return { file: "npm", args };
|
|
58
|
-
const env = deps.env ?? process.env;
|
|
59
|
-
const exists = deps.exists ?? existsSync;
|
|
60
|
-
const execPath = deps.execPath ?? process.execPath;
|
|
61
|
-
const cli = (dir) => win32.join(dir, "node_modules", "npm", "bin", "npm-cli.js");
|
|
62
|
-
const onPath = (envVar(env, "PATH") ?? "").split(";").filter((dir) => dir && exists(win32.join(dir, "npm.cmd")));
|
|
63
|
-
for (const dir of [...onPath, win32.dirname(execPath)]) {
|
|
64
|
-
if (exists(cli(dir))) return { file: execPath, args: [cli(dir), ...args] };
|
|
65
|
-
}
|
|
66
|
-
const line = ["npm", ...args.map(quoteCmdArg)].join(" ");
|
|
67
|
-
return { file: envVar(env, "COMSPEC") || "cmd.exe", args: ["/d", "/s", "/c", `"${line}"`], windowsVerbatimArguments: true };
|
|
68
|
-
}
|
|
69
|
-
function runNpm(args, options) {
|
|
70
|
-
const npm = npmInvocation(args);
|
|
71
|
-
return run(npm.file, npm.args, { ...options, ...npm.windowsVerbatimArguments ? { windowsVerbatimArguments: true } : {} });
|
|
72
|
-
}
|
|
73
|
-
async function latestPublishedRelease(timeoutMs = 3e4, signal, deps = {}) {
|
|
74
|
-
try {
|
|
75
|
-
const { stdout } = await (deps.runNpm ?? runNpm)(["view", CLI_PACKAGE, "version"], { timeout: timeoutMs, signal });
|
|
76
|
-
const version = String(stdout).trim();
|
|
77
|
-
return version ? { version } : { error: "npm printed no version" };
|
|
78
|
-
} catch (e) {
|
|
79
|
-
return { error: npmFailure(e) };
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
function npmFailure(e) {
|
|
83
|
-
const err = e;
|
|
84
|
-
const stderr = String(err?.stderr ?? "").split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
|
|
85
|
-
const npmErrors = stderr.filter((l) => /npm (ERR!|error)/i.test(l));
|
|
86
|
-
const why = npmErrors.slice(0, 2).join(" ") || stderr.at(-1) || (err?.killed || err?.signal ? `npm did not answer in time (${String(err.signal ?? "killed")})` : "") || (typeof err?.code === "string" ? `npm could not be started (${err.code}: ${String(err.message ?? "")})` : "") || String(err?.message ?? e);
|
|
87
|
-
return why.replace(/\s+/g, " ").trim().slice(0, 300);
|
|
88
|
-
}
|
|
89
|
-
async function latestPublishedVersion(timeoutMs = 3e4, signal) {
|
|
90
|
-
const r = await latestPublishedRelease(timeoutMs, signal);
|
|
91
|
-
return "version" in r ? r.version : null;
|
|
92
|
-
}
|
|
93
|
-
async function npmInstallInto(dir, version) {
|
|
94
|
-
try {
|
|
95
|
-
await runNpm(["install", "--prefix", dir, "--omit=dev", "--no-audit", "--no-fund", "--no-save", `${CLI_PACKAGE}@${version}`], { timeout: 5 * 6e4 });
|
|
96
|
-
} catch (e) {
|
|
97
|
-
const stderr = String(e.stderr ?? "").trim();
|
|
98
|
-
throw new Error((stderr || (e instanceof Error ? e.message : String(e))).slice(-400));
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
async function installPayload(version, root = payloadRoot(), install = npmInstallInto) {
|
|
102
|
-
const done = payloadAt(packageDir(root, version));
|
|
103
|
-
if (done) return done;
|
|
104
|
-
mkdirSync(root, { recursive: true });
|
|
105
|
-
const scratch = mkdtempSync(join(root, `.install-${version}-`));
|
|
106
|
-
try {
|
|
107
|
-
await install(scratch, version);
|
|
108
|
-
if (payloadAt(installedAt(scratch))?.version !== version) throw new Error(`npm installed no ${CLI_PACKAGE}@${version}`);
|
|
109
|
-
try {
|
|
110
|
-
renameSync(scratch, join(root, version));
|
|
111
|
-
} catch (e) {
|
|
112
|
-
if (!payloadAt(packageDir(root, version))) throw e;
|
|
113
|
-
}
|
|
114
|
-
} finally {
|
|
115
|
-
rmSync(scratch, { recursive: true, force: true });
|
|
116
|
-
}
|
|
117
|
-
const payload = payloadAt(packageDir(root, version));
|
|
118
|
-
if (!payload) throw new Error(`${CLI_PACKAGE}@${version} is not usable after install`);
|
|
119
|
-
for (const old of cachedPayloads(root).sort((a, b) => olderThan(a.version, b.version) ? 1 : -1).slice(KEEP)) {
|
|
120
|
-
rmSync(join(root, old.version), { recursive: true, force: true });
|
|
121
|
-
}
|
|
122
|
-
return payload;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
export {
|
|
126
|
-
CLI_PACKAGE,
|
|
127
|
-
BOOTSTRAP_ENV,
|
|
128
|
-
NO_SELF_UPDATE_ENV,
|
|
129
|
-
isVersion,
|
|
130
|
-
olderThan,
|
|
131
|
-
payloadAt,
|
|
132
|
-
cachedPayloads,
|
|
133
|
-
newestPayload,
|
|
134
|
-
runNpm,
|
|
135
|
-
latestPublishedRelease,
|
|
136
|
-
latestPublishedVersion,
|
|
137
|
-
installPayload
|
|
138
|
-
};
|