@tablation/crew 0.26.0 → 0.27.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/dist/cli.js +264 -172
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -7,10 +7,157 @@ import { hostname as hostname2, homedir as homedir5 } from "os";
|
|
|
7
7
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
8
8
|
|
|
9
9
|
// src/config.ts
|
|
10
|
-
import { readFileSync as
|
|
11
|
-
import { resolve, dirname, isAbsolute, join as
|
|
10
|
+
import { readFileSync as readFileSync3, existsSync as existsSync3 } from "fs";
|
|
11
|
+
import { resolve, dirname as dirname2, isAbsolute, join as join3, basename as basename2 } from "path";
|
|
12
12
|
import { homedir, tmpdir } from "os";
|
|
13
13
|
import { parse as parse2 } from "yaml";
|
|
14
|
+
|
|
15
|
+
// src/events.ts
|
|
16
|
+
import {
|
|
17
|
+
appendFileSync,
|
|
18
|
+
mkdirSync,
|
|
19
|
+
existsSync,
|
|
20
|
+
statSync,
|
|
21
|
+
renameSync,
|
|
22
|
+
openSync,
|
|
23
|
+
closeSync,
|
|
24
|
+
rmSync
|
|
25
|
+
} from "fs";
|
|
26
|
+
import { join, dirname } from "path";
|
|
27
|
+
function render(e) {
|
|
28
|
+
const scope = e.role ? `${e.step}[${e.role}]` : e.step;
|
|
29
|
+
const ticket = e.ticket ? ` ${e.ticket}` : "";
|
|
30
|
+
const level = e.level === "info" ? "" : `${e.level.toUpperCase()}: `;
|
|
31
|
+
return `${e.at} ${scope}:${ticket} ${level}${e.message}`;
|
|
32
|
+
}
|
|
33
|
+
var DEFAULT_EVENTS_ROTATE_BYTES = 64 * 1024 * 1024;
|
|
34
|
+
var ROTATED_EVENTS_RE = /^events\.\d{8}T\d{6}Z(?:-\d+)?\.jsonl$/;
|
|
35
|
+
var ROTATE_LOCK_STALE_MS = 6e4;
|
|
36
|
+
function rotateEventsFile(file, maxBytes, now = /* @__PURE__ */ new Date()) {
|
|
37
|
+
try {
|
|
38
|
+
if (!existsSync(file) || statSync(file).size <= maxBytes) return void 0;
|
|
39
|
+
} catch {
|
|
40
|
+
return void 0;
|
|
41
|
+
}
|
|
42
|
+
const lock = `${file}.rotate.lock`;
|
|
43
|
+
try {
|
|
44
|
+
try {
|
|
45
|
+
closeSync(openSync(lock, "wx"));
|
|
46
|
+
} catch {
|
|
47
|
+
try {
|
|
48
|
+
if (Date.now() - statSync(lock).mtimeMs < ROTATE_LOCK_STALE_MS) return void 0;
|
|
49
|
+
rmSync(lock, { force: true });
|
|
50
|
+
closeSync(openSync(lock, "wx"));
|
|
51
|
+
} catch {
|
|
52
|
+
return void 0;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
if (statSync(file).size <= maxBytes) return void 0;
|
|
57
|
+
const stamp = now.toISOString().replace(/[-:]/g, "").replace(/\.\d+Z$/, "Z");
|
|
58
|
+
let dest = join(dirname(file), `events.${stamp}.jsonl`);
|
|
59
|
+
for (let n = 1; existsSync(dest); n++) dest = join(dirname(file), `events.${stamp}-${n}.jsonl`);
|
|
60
|
+
renameSync(file, dest);
|
|
61
|
+
return dest;
|
|
62
|
+
} finally {
|
|
63
|
+
rmSync(lock, { force: true });
|
|
64
|
+
}
|
|
65
|
+
} catch {
|
|
66
|
+
return void 0;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
var Emitter = class _Emitter {
|
|
70
|
+
cycle;
|
|
71
|
+
opts;
|
|
72
|
+
now;
|
|
73
|
+
step = "poll";
|
|
74
|
+
role;
|
|
75
|
+
constructor(opts) {
|
|
76
|
+
this.opts = opts;
|
|
77
|
+
this.now = opts.now ?? (() => /* @__PURE__ */ new Date());
|
|
78
|
+
this.cycle = opts.cycleId ?? this.now().toISOString().replace(/[-:.TZ]/g, "").slice(0, 14);
|
|
79
|
+
for (const f of [opts.eventFile, opts.logFile]) {
|
|
80
|
+
if (!f) continue;
|
|
81
|
+
try {
|
|
82
|
+
mkdirSync(dirname(f), { recursive: true });
|
|
83
|
+
} catch {
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/** Everything emitted after this belongs to `step`, until the next one. */
|
|
88
|
+
enter(step, role) {
|
|
89
|
+
this.step = step;
|
|
90
|
+
this.role = role;
|
|
91
|
+
}
|
|
92
|
+
emit(message, extra = {}) {
|
|
93
|
+
const e = {
|
|
94
|
+
at: this.now().toISOString(),
|
|
95
|
+
cycle: this.cycle,
|
|
96
|
+
route: this.opts.route,
|
|
97
|
+
step: extra.step ?? this.step,
|
|
98
|
+
level: extra.level ?? "info",
|
|
99
|
+
message,
|
|
100
|
+
...extra.role ?? this.role ? { role: extra.role ?? this.role } : {},
|
|
101
|
+
...extra.ticket ? { ticket: extra.ticket } : {},
|
|
102
|
+
...extra.data ? { data: extra.data } : {}
|
|
103
|
+
};
|
|
104
|
+
const line = render(e);
|
|
105
|
+
try {
|
|
106
|
+
if (this.opts.eventFile && this.opts.rotateBytes) rotateEventsFile(this.opts.eventFile, this.opts.rotateBytes, this.now());
|
|
107
|
+
} catch {
|
|
108
|
+
}
|
|
109
|
+
try {
|
|
110
|
+
if (this.opts.eventFile) appendFileSync(this.opts.eventFile, `${JSON.stringify(e)}
|
|
111
|
+
`);
|
|
112
|
+
} catch {
|
|
113
|
+
}
|
|
114
|
+
try {
|
|
115
|
+
if (this.opts.logFile) appendFileSync(this.opts.logFile, `${line}
|
|
116
|
+
`);
|
|
117
|
+
} catch {
|
|
118
|
+
}
|
|
119
|
+
try {
|
|
120
|
+
(this.opts.console ?? ((l) => process.stderr.write(`${l}
|
|
121
|
+
`)))(line);
|
|
122
|
+
} catch {
|
|
123
|
+
}
|
|
124
|
+
return e;
|
|
125
|
+
}
|
|
126
|
+
warn(message, extra = {}) {
|
|
127
|
+
return this.emit(message, { ...extra, level: "warn" });
|
|
128
|
+
}
|
|
129
|
+
error(message, extra = {}) {
|
|
130
|
+
return this.emit(message, { ...extra, level: "error" });
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* A view of this emitter labelling every event with a different route,
|
|
134
|
+
* sharing the same sinks and cycle id. For a fleet-wide run, the top-level
|
|
135
|
+
* Emitter is stamped with just one route (see cli.ts) — a phase that
|
|
136
|
+
* actually knows which route it's working (release, per-repo poll) uses
|
|
137
|
+
* this instead, so its events aren't all attributed to `routes[0]`.
|
|
138
|
+
*/
|
|
139
|
+
forRoute(route) {
|
|
140
|
+
return new _Emitter({ ...this.opts, route, cycleId: this.cycle });
|
|
141
|
+
}
|
|
142
|
+
/** Times a step and emits its outcome, so a view can show duration. */
|
|
143
|
+
async timed(step, message, fn, role) {
|
|
144
|
+
this.enter(step, role);
|
|
145
|
+
const started = this.now().getTime();
|
|
146
|
+
try {
|
|
147
|
+
const result = await fn();
|
|
148
|
+
this.emit(message, { data: { ms: this.now().getTime() - started, ok: true } });
|
|
149
|
+
return result;
|
|
150
|
+
} catch (err) {
|
|
151
|
+
this.error(`${message} \u2014 ${err.message}`, {
|
|
152
|
+
data: { ms: this.now().getTime() - started, ok: false }
|
|
153
|
+
});
|
|
154
|
+
throw err;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
var eventFileFor = (stateDir) => join(stateDir, "events.jsonl");
|
|
159
|
+
|
|
160
|
+
// src/config.ts
|
|
14
161
|
import { getSessionStore, hostFromUrl } from "@tablation/client";
|
|
15
162
|
|
|
16
163
|
// src/platform.ts
|
|
@@ -50,8 +197,8 @@ function explain(ship, required) {
|
|
|
50
197
|
}
|
|
51
198
|
|
|
52
199
|
// src/repo-config.ts
|
|
53
|
-
import { readFileSync, existsSync } from "fs";
|
|
54
|
-
import { join, basename } from "path";
|
|
200
|
+
import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
|
|
201
|
+
import { join as join2, basename } from "path";
|
|
55
202
|
import { parse } from "yaml";
|
|
56
203
|
var SPEC_VERSION = 1;
|
|
57
204
|
var REPO_CONFIG_FILENAMES = [".crew.yaml", ".crew.yml", ".crew.json"];
|
|
@@ -124,8 +271,8 @@ var MODES = ["local", "integrate", "ci_manual", "ci_auto", "external"];
|
|
|
124
271
|
var PROVIDERS = ["github", "buildkite", "other", "none"];
|
|
125
272
|
function findRepoConfig(dir) {
|
|
126
273
|
for (const name of REPO_CONFIG_FILENAMES) {
|
|
127
|
-
const p =
|
|
128
|
-
if (
|
|
274
|
+
const p = join2(dir, name);
|
|
275
|
+
if (existsSync2(p)) return p;
|
|
129
276
|
}
|
|
130
277
|
return null;
|
|
131
278
|
}
|
|
@@ -355,7 +502,7 @@ function parseRepoConfig(text, file) {
|
|
|
355
502
|
function loadRepoConfig(dir) {
|
|
356
503
|
const file = findRepoConfig(dir);
|
|
357
504
|
if (!file) return null;
|
|
358
|
-
return parseRepoConfig(
|
|
505
|
+
return parseRepoConfig(readFileSync2(file, "utf8"), file);
|
|
359
506
|
}
|
|
360
507
|
function hookLabel(cfg, hook2) {
|
|
361
508
|
return cfg.labels[hook2] ?? cfg.hooks[hook2] ?? hook2;
|
|
@@ -631,6 +778,7 @@ var SHIP_KEYS = /* @__PURE__ */ new Set([
|
|
|
631
778
|
"apiKey",
|
|
632
779
|
"maxConcurrentAgents",
|
|
633
780
|
"streamRetentionDays",
|
|
781
|
+
"eventsRotateBytes",
|
|
634
782
|
"reposBasePath",
|
|
635
783
|
"relayHost",
|
|
636
784
|
"relayPort",
|
|
@@ -767,11 +915,11 @@ function expand(p, base) {
|
|
|
767
915
|
return isAbsolute(out) ? out : resolve(base, out);
|
|
768
916
|
}
|
|
769
917
|
function resolvePromptsDir(promptSet, crewHome, base) {
|
|
770
|
-
if (!promptSet) return
|
|
918
|
+
if (!promptSet) return join3(crewHome, "prompts", "default");
|
|
771
919
|
if (promptSet.includes("/") || promptSet.startsWith(".") || promptSet.startsWith("~")) {
|
|
772
920
|
return expand(promptSet, base);
|
|
773
921
|
}
|
|
774
|
-
return
|
|
922
|
+
return join3(crewHome, "prompts", promptSet);
|
|
775
923
|
}
|
|
776
924
|
var Missing = class {
|
|
777
925
|
paths = [];
|
|
@@ -795,7 +943,7 @@ var Missing = class {
|
|
|
795
943
|
};
|
|
796
944
|
function crewVersion(crewHome) {
|
|
797
945
|
try {
|
|
798
|
-
const pkg = JSON.parse(
|
|
946
|
+
const pkg = JSON.parse(readFileSync3(join3(crewHome, "package.json"), "utf8"));
|
|
799
947
|
return pkg.version ?? "0.0.0";
|
|
800
948
|
} catch {
|
|
801
949
|
return "0.0.0";
|
|
@@ -805,26 +953,26 @@ function configSearchPath(crewHome) {
|
|
|
805
953
|
if (process.env.CREW_CONFIG) return [resolve(process.env.CREW_CONFIG)];
|
|
806
954
|
const xdg = process.env.XDG_CONFIG_HOME;
|
|
807
955
|
return [
|
|
808
|
-
...xdg ? [
|
|
809
|
-
|
|
956
|
+
...xdg ? [join3(xdg, "crew", "crew.yaml")] : [],
|
|
957
|
+
join3(homedir(), ".config", "crew", "crew.yaml"),
|
|
810
958
|
resolve(crewHome, "crew.yaml")
|
|
811
959
|
];
|
|
812
960
|
}
|
|
813
961
|
function findConfigFile(crewHome) {
|
|
814
962
|
const candidates = configSearchPath(crewHome);
|
|
815
|
-
return candidates.find((c) =>
|
|
963
|
+
return candidates.find((c) => existsSync3(c)) ?? candidates[candidates.length - 1];
|
|
816
964
|
}
|
|
817
|
-
var defaultConfigPath = () =>
|
|
965
|
+
var defaultConfigPath = () => join3(process.env.XDG_CONFIG_HOME ?? join3(homedir(), ".config"), "crew", "crew.yaml");
|
|
818
966
|
function routeSlug(route) {
|
|
819
967
|
return route.replace(/\//g, "-");
|
|
820
968
|
}
|
|
821
969
|
function resolvedPathFor(stateDir, route) {
|
|
822
970
|
const [workspace, project] = splitRoute(route);
|
|
823
|
-
return
|
|
971
|
+
return join3(stateDir, "resolved", workspace, `${project}.json`);
|
|
824
972
|
}
|
|
825
973
|
function apiKeyPathFor(stateDir, route) {
|
|
826
974
|
const [workspace, project] = splitRoute(route);
|
|
827
|
-
return
|
|
975
|
+
return join3(stateDir, "keys", workspace, `${project}.env`);
|
|
828
976
|
}
|
|
829
977
|
function splitRoute(route) {
|
|
830
978
|
const parts = route.split("/");
|
|
@@ -835,9 +983,9 @@ function splitRoute(route) {
|
|
|
835
983
|
}
|
|
836
984
|
function readResolvedFile(stateDir, route) {
|
|
837
985
|
const path = resolvedPathFor(stateDir, route);
|
|
838
|
-
if (!
|
|
986
|
+
if (!existsSync3(path)) return void 0;
|
|
839
987
|
try {
|
|
840
|
-
return JSON.parse(
|
|
988
|
+
return JSON.parse(readFileSync3(path, "utf8"));
|
|
841
989
|
} catch (e) {
|
|
842
990
|
throw new ConfigError(`${path}: not valid JSON \u2014 ${e.message}`);
|
|
843
991
|
}
|
|
@@ -893,23 +1041,23 @@ function parseResolved(raw, m, where) {
|
|
|
893
1041
|
}
|
|
894
1042
|
function loadConfig(crewHome, configFile, opts = {}) {
|
|
895
1043
|
const file = configFile ?? findConfigFile(crewHome);
|
|
896
|
-
if (opts.text === void 0 && !
|
|
1044
|
+
if (opts.text === void 0 && !existsSync3(file)) {
|
|
897
1045
|
throw new ConfigError(
|
|
898
1046
|
`no config found. Looked in:
|
|
899
1047
|
` + configSearchPath(crewHome).map((c) => ` - ${c}`).join("\n") + `
|
|
900
1048
|
|
|
901
1049
|
Create the first of those:
|
|
902
|
-
mkdir -p ${
|
|
1050
|
+
mkdir -p ${dirname2(defaultConfigPath())} && cp ${join3(crewHome, "crew.example.yaml")} ${defaultConfigPath()}`
|
|
903
1051
|
);
|
|
904
1052
|
}
|
|
905
1053
|
let raw;
|
|
906
1054
|
try {
|
|
907
|
-
raw = parse2(opts.text ??
|
|
1055
|
+
raw = parse2(opts.text ?? readFileSync3(file, "utf8"));
|
|
908
1056
|
} catch (e) {
|
|
909
1057
|
throw new ConfigError(`${file}: not valid YAML \u2014 ${e.message}`);
|
|
910
1058
|
}
|
|
911
1059
|
if (!raw || typeof raw !== "object") throw new ConfigError(`${file} is empty or not a mapping`);
|
|
912
|
-
const base =
|
|
1060
|
+
const base = dirname2(file);
|
|
913
1061
|
const missing = new Missing();
|
|
914
1062
|
rejectUnknownKeys(raw, /* @__PURE__ */ new Set(["ship", "routes"]), "top-level", file);
|
|
915
1063
|
const shipRaw = raw.ship ?? {};
|
|
@@ -926,6 +1074,10 @@ Create the first of those:
|
|
|
926
1074
|
if (!Number.isInteger(streamRetentionDays) || streamRetentionDays < 1) {
|
|
927
1075
|
missing.add(`ship.streamRetentionDays must be a positive integer (got "${shipRaw.streamRetentionDays}")`);
|
|
928
1076
|
}
|
|
1077
|
+
const eventsRotateBytes = shipRaw.eventsRotateBytes === void 0 ? DEFAULT_EVENTS_ROTATE_BYTES : Number(shipRaw.eventsRotateBytes);
|
|
1078
|
+
if (!Number.isInteger(eventsRotateBytes) || eventsRotateBytes < 1) {
|
|
1079
|
+
missing.add(`ship.eventsRotateBytes must be a positive integer (got "${shipRaw.eventsRotateBytes}")`);
|
|
1080
|
+
}
|
|
929
1081
|
const maxThinkingTokens = shipRaw.agent?.maxThinkingTokens === void 0 ? DEFAULT_MAX_THINKING_TOKENS : Number(shipRaw.agent.maxThinkingTokens);
|
|
930
1082
|
if (!Number.isInteger(maxThinkingTokens) || maxThinkingTokens < 0) {
|
|
931
1083
|
missing.add(`ship.agent.maxThinkingTokens must be a non-negative integer (got "${shipRaw.agent?.maxThinkingTokens}")`);
|
|
@@ -968,10 +1120,11 @@ Create the first of those:
|
|
|
968
1120
|
useNvm: shipRaw.useNvm !== false,
|
|
969
1121
|
nvmSh: shipRaw.nvmSh,
|
|
970
1122
|
stateDir,
|
|
971
|
-
logFile: expand(shipRaw.logFile ??
|
|
1123
|
+
logFile: expand(shipRaw.logFile ?? join3(tmpdir(), "crew.log"), base),
|
|
972
1124
|
userAgent: shipRaw.userAgent ?? `Mozilla/5.0 CrewAgent/${crewVersion(crewHome)}`,
|
|
973
1125
|
maxConcurrentAgents,
|
|
974
1126
|
streamRetentionDays,
|
|
1127
|
+
eventsRotateBytes,
|
|
975
1128
|
relayHost: shipRaw.relayHost ? String(shipRaw.relayHost) : void 0,
|
|
976
1129
|
// 2222 matches the relay's own RELAY_SSH_PORT default (crew-relay README).
|
|
977
1130
|
relayPort: shipRaw.relayPort !== void 0 ? Number(shipRaw.relayPort) : 2222,
|
|
@@ -1102,8 +1255,8 @@ function repoIdForName(r, name) {
|
|
|
1102
1255
|
}
|
|
1103
1256
|
function resolveApiKey(r) {
|
|
1104
1257
|
if (r.apiKey) return r.apiKey;
|
|
1105
|
-
if (r.apiKeyFile && r.apiKeyVar &&
|
|
1106
|
-
for (const line of
|
|
1258
|
+
if (r.apiKeyFile && r.apiKeyVar && existsSync3(r.apiKeyFile)) {
|
|
1259
|
+
for (const line of readFileSync3(r.apiKeyFile, "utf8").split("\n")) {
|
|
1107
1260
|
const m = line.match(new RegExp(`^${r.apiKeyVar}=(.*)$`));
|
|
1108
1261
|
if (m) return (m[1] ?? "").trim().replace(/^["']|["']$/g, "");
|
|
1109
1262
|
}
|
|
@@ -1166,7 +1319,7 @@ function configuredMembers(rt) {
|
|
|
1166
1319
|
return out;
|
|
1167
1320
|
}
|
|
1168
1321
|
function defaultRepoDir(basePath, workspace, repoName) {
|
|
1169
|
-
return
|
|
1322
|
+
return join3(basePath, workspace, repoName);
|
|
1170
1323
|
}
|
|
1171
1324
|
function reposOf(r) {
|
|
1172
1325
|
const named = Object.entries(r.repos);
|
|
@@ -1190,7 +1343,7 @@ function passengerRepoTargets(r) {
|
|
|
1190
1343
|
return reposOf(r).filter((t) => r.repoOverrides[t.name]?.hostPassengers !== false);
|
|
1191
1344
|
}
|
|
1192
1345
|
function passengerCheckoutDir(basePath, workspace, repoName) {
|
|
1193
|
-
return
|
|
1346
|
+
return join3(basePath, workspace, ".passenger-checkouts", repoName);
|
|
1194
1347
|
}
|
|
1195
1348
|
function passengerCheckoutTargets(r) {
|
|
1196
1349
|
if (!r.hostPassengers) return [];
|
|
@@ -1322,17 +1475,17 @@ function rosterMarkdown(roster, meId) {
|
|
|
1322
1475
|
}
|
|
1323
1476
|
|
|
1324
1477
|
// src/state.ts
|
|
1325
|
-
import { readFileSync as
|
|
1326
|
-
import { join as
|
|
1478
|
+
import { readFileSync as readFileSync4, writeFileSync, existsSync as existsSync4, mkdirSync as mkdirSync2, rmSync as rmSync2, statSync as statSync2 } from "fs";
|
|
1479
|
+
import { join as join4 } from "path";
|
|
1327
1480
|
var EPOCH = "1970-01-01T00:00:00.000Z";
|
|
1328
1481
|
var State = class _State {
|
|
1329
1482
|
dir;
|
|
1330
1483
|
constructor(dir) {
|
|
1331
1484
|
this.dir = dir;
|
|
1332
|
-
|
|
1485
|
+
mkdirSync2(dir, { recursive: true });
|
|
1333
1486
|
}
|
|
1334
1487
|
path(...parts) {
|
|
1335
|
-
return
|
|
1488
|
+
return join4(this.dir, ...parts);
|
|
1336
1489
|
}
|
|
1337
1490
|
/**
|
|
1338
1491
|
* The timestamp every "new since last poll" check is measured against.
|
|
@@ -1343,7 +1496,7 @@ var State = class _State {
|
|
|
1343
1496
|
*/
|
|
1344
1497
|
watermark() {
|
|
1345
1498
|
try {
|
|
1346
|
-
const v =
|
|
1499
|
+
const v = readFileSync4(this.path(".poll-watermark"), "utf8").trim();
|
|
1347
1500
|
return v || EPOCH;
|
|
1348
1501
|
} catch {
|
|
1349
1502
|
return EPOCH;
|
|
@@ -1368,10 +1521,10 @@ var State = class _State {
|
|
|
1368
1521
|
}
|
|
1369
1522
|
/** Whole-crew pause, and the per-role sentinels one level down. */
|
|
1370
1523
|
isPaused() {
|
|
1371
|
-
return
|
|
1524
|
+
return existsSync4(this.path(".crew-paused"));
|
|
1372
1525
|
}
|
|
1373
1526
|
isRolePaused(role) {
|
|
1374
|
-
return
|
|
1527
|
+
return existsSync4(this.path(`.role-paused-${role}`));
|
|
1375
1528
|
}
|
|
1376
1529
|
pausedRoles(roles) {
|
|
1377
1530
|
return new Set(roles.filter((r) => this.isRolePaused(r)));
|
|
@@ -1380,7 +1533,7 @@ var State = class _State {
|
|
|
1380
1533
|
writeFileSync(role ? this.path(`.role-paused-${role}`) : this.path(".crew-paused"), "");
|
|
1381
1534
|
}
|
|
1382
1535
|
resume(role) {
|
|
1383
|
-
|
|
1536
|
+
rmSync2(role ? this.path(`.role-paused-${role}`) : this.path(".crew-paused"), { force: true });
|
|
1384
1537
|
}
|
|
1385
1538
|
/**
|
|
1386
1539
|
* The release phase (merge, deploy, release) has its own sentinel, one
|
|
@@ -1388,11 +1541,11 @@ var State = class _State {
|
|
|
1388
1541
|
* while agents keep working.
|
|
1389
1542
|
*/
|
|
1390
1543
|
isReleasePaused() {
|
|
1391
|
-
return
|
|
1544
|
+
return existsSync4(this.path(".release-paused"));
|
|
1392
1545
|
}
|
|
1393
1546
|
setReleasePaused(paused) {
|
|
1394
1547
|
if (paused) writeFileSync(this.path(".release-paused"), "");
|
|
1395
|
-
else
|
|
1548
|
+
else rmSync2(this.path(".release-paused"), { force: true });
|
|
1396
1549
|
}
|
|
1397
1550
|
/**
|
|
1398
1551
|
* Why a release cycle must not run right now, or null when it may. Every
|
|
@@ -1429,7 +1582,7 @@ var State = class _State {
|
|
|
1429
1582
|
const name = _State.safe(rawName);
|
|
1430
1583
|
const file = this.path(`.${name}.lock`);
|
|
1431
1584
|
try {
|
|
1432
|
-
const existing = Number.parseInt(
|
|
1585
|
+
const existing = Number.parseInt(readFileSync4(file, "utf8").trim(), 10);
|
|
1433
1586
|
if (Number.isFinite(existing) && existing > 0) {
|
|
1434
1587
|
try {
|
|
1435
1588
|
process.kill(existing, 0);
|
|
@@ -1445,7 +1598,7 @@ var State = class _State {
|
|
|
1445
1598
|
ok: true,
|
|
1446
1599
|
release: () => {
|
|
1447
1600
|
try {
|
|
1448
|
-
|
|
1601
|
+
rmSync2(file, { force: true });
|
|
1449
1602
|
} catch {
|
|
1450
1603
|
}
|
|
1451
1604
|
}
|
|
@@ -1523,7 +1676,7 @@ var State = class _State {
|
|
|
1523
1676
|
const heldReported = `.release-held-${suffix}`;
|
|
1524
1677
|
const read = (f) => {
|
|
1525
1678
|
try {
|
|
1526
|
-
return
|
|
1679
|
+
return readFileSync4(this.path(f), "utf8").trim() || null;
|
|
1527
1680
|
} catch {
|
|
1528
1681
|
return null;
|
|
1529
1682
|
}
|
|
@@ -1537,7 +1690,7 @@ var State = class _State {
|
|
|
1537
1690
|
return n;
|
|
1538
1691
|
},
|
|
1539
1692
|
clearBlocked: () => {
|
|
1540
|
-
|
|
1693
|
+
rmSync2(this.path(blocked), { force: true });
|
|
1541
1694
|
},
|
|
1542
1695
|
/**
|
|
1543
1696
|
* The commit a deploy last failed on.
|
|
@@ -1553,7 +1706,7 @@ var State = class _State {
|
|
|
1553
1706
|
`);
|
|
1554
1707
|
},
|
|
1555
1708
|
clearDeployFailed: () => {
|
|
1556
|
-
|
|
1709
|
+
rmSync2(this.path(failed), { force: true });
|
|
1557
1710
|
},
|
|
1558
1711
|
/**
|
|
1559
1712
|
* Consecutive test-gate failures on the same head (CREW-1368). A failure
|
|
@@ -1568,12 +1721,12 @@ var State = class _State {
|
|
|
1568
1721
|
return next;
|
|
1569
1722
|
},
|
|
1570
1723
|
clearTestGateFailed: () => {
|
|
1571
|
-
|
|
1724
|
+
rmSync2(this.path(gateFailed), { force: true });
|
|
1572
1725
|
},
|
|
1573
1726
|
/** Whether the board was already told about this head's red gate. */
|
|
1574
1727
|
heldReported: () => {
|
|
1575
1728
|
try {
|
|
1576
|
-
return
|
|
1729
|
+
return readFileSync4(this.path(heldReported), "utf8").replace(/\n$/, "") || null;
|
|
1577
1730
|
} catch {
|
|
1578
1731
|
return null;
|
|
1579
1732
|
}
|
|
@@ -1583,7 +1736,7 @@ var State = class _State {
|
|
|
1583
1736
|
`);
|
|
1584
1737
|
},
|
|
1585
1738
|
clearHeldReported: () => {
|
|
1586
|
-
|
|
1739
|
+
rmSync2(this.path(heldReported), { force: true });
|
|
1587
1740
|
},
|
|
1588
1741
|
testGateReported: (sha) => read(gateReported) === sha,
|
|
1589
1742
|
noteTestGateReported: (sha) => {
|
|
@@ -1610,13 +1763,13 @@ var State = class _State {
|
|
|
1610
1763
|
const ticketStreakFile = `.fairness-ticket-streak-${suffix}`;
|
|
1611
1764
|
const read = (f) => {
|
|
1612
1765
|
try {
|
|
1613
|
-
return
|
|
1766
|
+
return readFileSync4(this.path(f), "utf8").trim() || null;
|
|
1614
1767
|
} catch {
|
|
1615
1768
|
return null;
|
|
1616
1769
|
}
|
|
1617
1770
|
};
|
|
1618
1771
|
const clear2 = () => {
|
|
1619
|
-
for (const f of [streakFile, ticketFile, sinceFile, ticketStreakFile])
|
|
1772
|
+
for (const f of [streakFile, ticketFile, sinceFile, ticketStreakFile]) rmSync2(this.path(f), { force: true });
|
|
1620
1773
|
};
|
|
1621
1774
|
return {
|
|
1622
1775
|
/** Consecutive cycles this route had actionable work and did not win. */
|
|
@@ -1675,7 +1828,7 @@ var State = class _State {
|
|
|
1675
1828
|
return {
|
|
1676
1829
|
previous: () => {
|
|
1677
1830
|
try {
|
|
1678
|
-
const parsed = JSON.parse(
|
|
1831
|
+
const parsed = JSON.parse(readFileSync4(this.path(file), "utf8"));
|
|
1679
1832
|
return parsed && typeof parsed === "object" ? parsed : {};
|
|
1680
1833
|
} catch {
|
|
1681
1834
|
return {};
|
|
@@ -1696,7 +1849,7 @@ var State = class _State {
|
|
|
1696
1849
|
return {
|
|
1697
1850
|
previous: () => {
|
|
1698
1851
|
try {
|
|
1699
|
-
const parsed = JSON.parse(
|
|
1852
|
+
const parsed = JSON.parse(readFileSync4(this.path(file), "utf8"));
|
|
1700
1853
|
return Array.isArray(parsed) ? parsed : [];
|
|
1701
1854
|
} catch {
|
|
1702
1855
|
return [];
|
|
@@ -1720,7 +1873,7 @@ var State = class _State {
|
|
|
1720
1873
|
},
|
|
1721
1874
|
at: () => {
|
|
1722
1875
|
try {
|
|
1723
|
-
return
|
|
1876
|
+
return statSync2(this.path(file)).mtimeMs;
|
|
1724
1877
|
} catch {
|
|
1725
1878
|
return null;
|
|
1726
1879
|
}
|
|
@@ -1729,109 +1882,13 @@ var State = class _State {
|
|
|
1729
1882
|
}
|
|
1730
1883
|
readInt(file) {
|
|
1731
1884
|
try {
|
|
1732
|
-
return Number.parseInt(
|
|
1885
|
+
return Number.parseInt(readFileSync4(this.path(file), "utf8").trim(), 10) || 0;
|
|
1733
1886
|
} catch {
|
|
1734
1887
|
return 0;
|
|
1735
1888
|
}
|
|
1736
1889
|
}
|
|
1737
1890
|
};
|
|
1738
1891
|
|
|
1739
|
-
// src/events.ts
|
|
1740
|
-
import { appendFileSync, mkdirSync as mkdirSync2 } from "fs";
|
|
1741
|
-
import { join as join4, dirname as dirname2 } from "path";
|
|
1742
|
-
function render(e) {
|
|
1743
|
-
const scope = e.role ? `${e.step}[${e.role}]` : e.step;
|
|
1744
|
-
const ticket = e.ticket ? ` ${e.ticket}` : "";
|
|
1745
|
-
const level = e.level === "info" ? "" : `${e.level.toUpperCase()}: `;
|
|
1746
|
-
return `${e.at} ${scope}:${ticket} ${level}${e.message}`;
|
|
1747
|
-
}
|
|
1748
|
-
var Emitter = class _Emitter {
|
|
1749
|
-
cycle;
|
|
1750
|
-
opts;
|
|
1751
|
-
now;
|
|
1752
|
-
step = "poll";
|
|
1753
|
-
role;
|
|
1754
|
-
constructor(opts) {
|
|
1755
|
-
this.opts = opts;
|
|
1756
|
-
this.now = opts.now ?? (() => /* @__PURE__ */ new Date());
|
|
1757
|
-
this.cycle = opts.cycleId ?? this.now().toISOString().replace(/[-:.TZ]/g, "").slice(0, 14);
|
|
1758
|
-
for (const f of [opts.eventFile, opts.logFile]) {
|
|
1759
|
-
if (!f) continue;
|
|
1760
|
-
try {
|
|
1761
|
-
mkdirSync2(dirname2(f), { recursive: true });
|
|
1762
|
-
} catch {
|
|
1763
|
-
}
|
|
1764
|
-
}
|
|
1765
|
-
}
|
|
1766
|
-
/** Everything emitted after this belongs to `step`, until the next one. */
|
|
1767
|
-
enter(step, role) {
|
|
1768
|
-
this.step = step;
|
|
1769
|
-
this.role = role;
|
|
1770
|
-
}
|
|
1771
|
-
emit(message, extra = {}) {
|
|
1772
|
-
const e = {
|
|
1773
|
-
at: this.now().toISOString(),
|
|
1774
|
-
cycle: this.cycle,
|
|
1775
|
-
route: this.opts.route,
|
|
1776
|
-
step: extra.step ?? this.step,
|
|
1777
|
-
level: extra.level ?? "info",
|
|
1778
|
-
message,
|
|
1779
|
-
...extra.role ?? this.role ? { role: extra.role ?? this.role } : {},
|
|
1780
|
-
...extra.ticket ? { ticket: extra.ticket } : {},
|
|
1781
|
-
...extra.data ? { data: extra.data } : {}
|
|
1782
|
-
};
|
|
1783
|
-
const line = render(e);
|
|
1784
|
-
try {
|
|
1785
|
-
if (this.opts.eventFile) appendFileSync(this.opts.eventFile, `${JSON.stringify(e)}
|
|
1786
|
-
`);
|
|
1787
|
-
} catch {
|
|
1788
|
-
}
|
|
1789
|
-
try {
|
|
1790
|
-
if (this.opts.logFile) appendFileSync(this.opts.logFile, `${line}
|
|
1791
|
-
`);
|
|
1792
|
-
} catch {
|
|
1793
|
-
}
|
|
1794
|
-
try {
|
|
1795
|
-
(this.opts.console ?? ((l) => process.stderr.write(`${l}
|
|
1796
|
-
`)))(line);
|
|
1797
|
-
} catch {
|
|
1798
|
-
}
|
|
1799
|
-
return e;
|
|
1800
|
-
}
|
|
1801
|
-
warn(message, extra = {}) {
|
|
1802
|
-
return this.emit(message, { ...extra, level: "warn" });
|
|
1803
|
-
}
|
|
1804
|
-
error(message, extra = {}) {
|
|
1805
|
-
return this.emit(message, { ...extra, level: "error" });
|
|
1806
|
-
}
|
|
1807
|
-
/**
|
|
1808
|
-
* A view of this emitter labelling every event with a different route,
|
|
1809
|
-
* sharing the same sinks and cycle id. For a fleet-wide run, the top-level
|
|
1810
|
-
* Emitter is stamped with just one route (see cli.ts) — a phase that
|
|
1811
|
-
* actually knows which route it's working (release, per-repo poll) uses
|
|
1812
|
-
* this instead, so its events aren't all attributed to `routes[0]`.
|
|
1813
|
-
*/
|
|
1814
|
-
forRoute(route) {
|
|
1815
|
-
return new _Emitter({ ...this.opts, route, cycleId: this.cycle });
|
|
1816
|
-
}
|
|
1817
|
-
/** Times a step and emits its outcome, so a view can show duration. */
|
|
1818
|
-
async timed(step, message, fn, role) {
|
|
1819
|
-
this.enter(step, role);
|
|
1820
|
-
const started = this.now().getTime();
|
|
1821
|
-
try {
|
|
1822
|
-
const result = await fn();
|
|
1823
|
-
this.emit(message, { data: { ms: this.now().getTime() - started, ok: true } });
|
|
1824
|
-
return result;
|
|
1825
|
-
} catch (err) {
|
|
1826
|
-
this.error(`${message} \u2014 ${err.message}`, {
|
|
1827
|
-
data: { ms: this.now().getTime() - started, ok: false }
|
|
1828
|
-
});
|
|
1829
|
-
throw err;
|
|
1830
|
-
}
|
|
1831
|
-
}
|
|
1832
|
-
};
|
|
1833
|
-
var eventFileFor = (stateDir) => join4(stateDir, "events.jsonl");
|
|
1834
|
-
|
|
1835
1892
|
// src/tracker.ts
|
|
1836
1893
|
import { hostname } from "os";
|
|
1837
1894
|
import { TablationClient } from "@tablation/client";
|
|
@@ -2131,7 +2188,7 @@ function describeRelease(o, route) {
|
|
|
2131
2188
|
// src/install.ts
|
|
2132
2189
|
import { dirname as dirname3, join as join5, extname } from "path";
|
|
2133
2190
|
import { homedir as homedir2 } from "os";
|
|
2134
|
-
import { accessSync, constants, existsSync as existsSync5, mkdirSync as mkdirSync3, readdirSync, readFileSync as readFileSync5, statSync as
|
|
2191
|
+
import { accessSync, constants, existsSync as existsSync5, mkdirSync as mkdirSync3, readdirSync, readFileSync as readFileSync5, statSync as statSync3, writeFileSync as writeFileSync2, unlinkSync, realpathSync } from "fs";
|
|
2135
2192
|
import { spawnSync } from "child_process";
|
|
2136
2193
|
import { createHash } from "crypto";
|
|
2137
2194
|
|
|
@@ -2185,7 +2242,7 @@ function findOnPath(bin, searchPath) {
|
|
|
2185
2242
|
for (const candidate of candidates) {
|
|
2186
2243
|
try {
|
|
2187
2244
|
accessSync(candidate, constants.X_OK);
|
|
2188
|
-
if (
|
|
2245
|
+
if (statSync3(candidate).isFile()) return candidate;
|
|
2189
2246
|
} catch {
|
|
2190
2247
|
}
|
|
2191
2248
|
}
|
|
@@ -2648,10 +2705,10 @@ import { execFileSync } from "child_process";
|
|
|
2648
2705
|
import {
|
|
2649
2706
|
existsSync as existsSync6,
|
|
2650
2707
|
mkdirSync as mkdirSync4,
|
|
2651
|
-
openSync,
|
|
2652
|
-
closeSync,
|
|
2708
|
+
openSync as openSync2,
|
|
2709
|
+
closeSync as closeSync2,
|
|
2653
2710
|
writeSync,
|
|
2654
|
-
statSync as
|
|
2711
|
+
statSync as statSync4,
|
|
2655
2712
|
unlinkSync as unlinkSync2
|
|
2656
2713
|
} from "fs";
|
|
2657
2714
|
import { dirname as dirname4, basename as basename3 } from "path";
|
|
@@ -3047,10 +3104,10 @@ function acquireCheckoutLock(dir, timeoutMs = 3e4) {
|
|
|
3047
3104
|
const deadline = Date.now() + timeoutMs;
|
|
3048
3105
|
for (; ; ) {
|
|
3049
3106
|
try {
|
|
3050
|
-
const fd =
|
|
3107
|
+
const fd = openSync2(lockPath, "wx");
|
|
3051
3108
|
writeSync(fd, `${process.pid}
|
|
3052
3109
|
`);
|
|
3053
|
-
|
|
3110
|
+
closeSync2(fd);
|
|
3054
3111
|
return () => {
|
|
3055
3112
|
try {
|
|
3056
3113
|
unlinkSync2(lockPath);
|
|
@@ -3061,7 +3118,7 @@ function acquireCheckoutLock(dir, timeoutMs = 3e4) {
|
|
|
3061
3118
|
const err = e;
|
|
3062
3119
|
if (err.code !== "EEXIST") throw err;
|
|
3063
3120
|
try {
|
|
3064
|
-
if (Date.now() -
|
|
3121
|
+
if (Date.now() - statSync4(lockPath).mtimeMs > CHECKOUT_LOCK_STALE_MS) {
|
|
3065
3122
|
try {
|
|
3066
3123
|
unlinkSync2(lockPath);
|
|
3067
3124
|
} catch {
|
|
@@ -5398,7 +5455,7 @@ var alreadyReported = (ticket, g, comments) => comments.some((c) => c.ticket_id
|
|
|
5398
5455
|
|
|
5399
5456
|
// src/agent.ts
|
|
5400
5457
|
import { spawn as spawn2 } from "child_process";
|
|
5401
|
-
import { readFileSync as readFileSync6, existsSync as existsSync7, statSync as
|
|
5458
|
+
import { readFileSync as readFileSync6, existsSync as existsSync7, statSync as statSync5, mkdirSync as mkdirSync5, writeFileSync as writeFileSync4, createWriteStream } from "fs";
|
|
5402
5459
|
import { finished } from "stream/promises";
|
|
5403
5460
|
import { join as join7, dirname as dirname5 } from "path";
|
|
5404
5461
|
import { createHash as createHash2 } from "crypto";
|
|
@@ -5869,7 +5926,7 @@ function planAgentRun(o) {
|
|
|
5869
5926
|
let digest;
|
|
5870
5927
|
let ageSeconds;
|
|
5871
5928
|
if (existsSync7(digestPath2)) {
|
|
5872
|
-
ageSeconds = Math.floor((now() -
|
|
5929
|
+
ageSeconds = Math.floor((now() - statSync5(digestPath2).mtimeMs) / 1e3);
|
|
5873
5930
|
if (ageSeconds <= DIGEST_MAX_AGE_SECONDS) digest = readFileSync6(digestPath2, "utf8");
|
|
5874
5931
|
}
|
|
5875
5932
|
const common = readFileSync6(commonPath, "utf8");
|
|
@@ -6600,7 +6657,7 @@ Wrote ${o.configPath}.
|
|
|
6600
6657
|
}
|
|
6601
6658
|
|
|
6602
6659
|
// src/release-run.ts
|
|
6603
|
-
import { readFileSync as readFileSync7, writeFileSync as writeFileSync6, existsSync as existsSync11, copyFileSync, mkdirSync as mkdirSync7, mkdtempSync, rmSync as
|
|
6660
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync6, existsSync as existsSync11, copyFileSync, mkdirSync as mkdirSync7, mkdtempSync, rmSync as rmSync3 } from "fs";
|
|
6604
6661
|
import { dirname as dirname8, join as join11 } from "path";
|
|
6605
6662
|
import { tmpdir as tmpdir2 } from "os";
|
|
6606
6663
|
|
|
@@ -7383,7 +7440,7 @@ function openReleaseCheckout(o) {
|
|
|
7383
7440
|
const dir = join11(root, "checkout");
|
|
7384
7441
|
const close = () => {
|
|
7385
7442
|
gitOk(o.cwd, ["worktree", "remove", "--force", dir]);
|
|
7386
|
-
|
|
7443
|
+
rmSync3(root, { recursive: true, force: true });
|
|
7387
7444
|
gitOk(o.cwd, ["worktree", "prune"]);
|
|
7388
7445
|
gitOk(o.cwd, ["branch", "-D", branch]);
|
|
7389
7446
|
};
|
|
@@ -7866,7 +7923,7 @@ function missingReleaserWarning(repo, release, ships, now = Date.now()) {
|
|
|
7866
7923
|
}
|
|
7867
7924
|
|
|
7868
7925
|
// src/watch.ts
|
|
7869
|
-
import { watch as watchFile, existsSync as existsSync12, statSync as
|
|
7926
|
+
import { watch as watchFile, existsSync as existsSync12, statSync as statSync6, openSync as openSync3, readSync, closeSync as closeSync3 } from "fs";
|
|
7870
7927
|
var E = "\x1B[";
|
|
7871
7928
|
var dim = (s) => `${E}2m${s}${E}0m`;
|
|
7872
7929
|
var bold = (s) => `${E}1m${s}${E}0m`;
|
|
@@ -7968,13 +8025,13 @@ function renderHeaders(states, now = Date.now()) {
|
|
|
7968
8025
|
}
|
|
7969
8026
|
function readFrom(file, from) {
|
|
7970
8027
|
if (!existsSync12(file)) return { events: [], offset: from };
|
|
7971
|
-
const size =
|
|
8028
|
+
const size = statSync6(file).size;
|
|
7972
8029
|
if (size < from) return readFrom(file, 0);
|
|
7973
8030
|
if (size === from) return { events: [], offset: from };
|
|
7974
|
-
const fd =
|
|
8031
|
+
const fd = openSync3(file, "r");
|
|
7975
8032
|
const buf = Buffer.alloc(size - from);
|
|
7976
8033
|
readSync(fd, buf, 0, buf.length, from);
|
|
7977
|
-
|
|
8034
|
+
closeSync3(fd);
|
|
7978
8035
|
const text = buf.toString("utf8");
|
|
7979
8036
|
const lastNewline = text.lastIndexOf("\n");
|
|
7980
8037
|
if (lastNewline === -1) return { events: [], offset: from };
|
|
@@ -9465,7 +9522,7 @@ function applyRemoteBranchCleanup(cwd, remote, branches2, dryRun, log) {
|
|
|
9465
9522
|
}
|
|
9466
9523
|
|
|
9467
9524
|
// src/stream-sweep.ts
|
|
9468
|
-
import { existsSync as existsSync16, readdirSync as readdirSync3, rmSync as
|
|
9525
|
+
import { existsSync as existsSync16, readdirSync as readdirSync3, rmSync as rmSync4, statSync as statSync7 } from "fs";
|
|
9469
9526
|
import { join as join15 } from "path";
|
|
9470
9527
|
var DAY_MS2 = 24 * 60 * 60 * 1e3;
|
|
9471
9528
|
function planStreamSweep(stateDir, retentionDays, now = Date.now()) {
|
|
@@ -9474,7 +9531,7 @@ function planStreamSweep(stateDir, retentionDays, now = Date.now()) {
|
|
|
9474
9531
|
const cutoff = now - retentionDays * DAY_MS2;
|
|
9475
9532
|
return readdirSync3(dir).map((name) => join15(dir, name)).filter((path) => {
|
|
9476
9533
|
try {
|
|
9477
|
-
return
|
|
9534
|
+
return statSync7(path).mtimeMs < cutoff;
|
|
9478
9535
|
} catch {
|
|
9479
9536
|
return false;
|
|
9480
9537
|
}
|
|
@@ -9488,7 +9545,7 @@ function applyStreamSweep(paths, dryRun, log) {
|
|
|
9488
9545
|
continue;
|
|
9489
9546
|
}
|
|
9490
9547
|
try {
|
|
9491
|
-
|
|
9548
|
+
rmSync4(path, { force: true });
|
|
9492
9549
|
removed++;
|
|
9493
9550
|
log.emit(`removed stream artifact ${path}`, { step: "worktree" });
|
|
9494
9551
|
} catch (e) {
|
|
@@ -9497,9 +9554,20 @@ function applyStreamSweep(paths, dryRun, log) {
|
|
|
9497
9554
|
}
|
|
9498
9555
|
return removed;
|
|
9499
9556
|
}
|
|
9557
|
+
function planEventsSweep(stateDir, retentionDays, now = Date.now()) {
|
|
9558
|
+
if (!existsSync16(stateDir)) return [];
|
|
9559
|
+
const cutoff = now - retentionDays * DAY_MS2;
|
|
9560
|
+
return readdirSync3(stateDir).filter((name) => ROTATED_EVENTS_RE.test(name)).map((name) => join15(stateDir, name)).filter((path) => {
|
|
9561
|
+
try {
|
|
9562
|
+
return statSync7(path).mtimeMs < cutoff;
|
|
9563
|
+
} catch {
|
|
9564
|
+
return false;
|
|
9565
|
+
}
|
|
9566
|
+
}).sort();
|
|
9567
|
+
}
|
|
9500
9568
|
|
|
9501
9569
|
// src/cli.ts
|
|
9502
|
-
import { readFileSync as readFileSync14, existsSync as existsSync20, mkdirSync as mkdirSync11, writeFileSync as writeFileSync9, unlinkSync as unlinkSync5, copyFileSync as copyFileSync2, renameSync } from "fs";
|
|
9570
|
+
import { readFileSync as readFileSync14, existsSync as existsSync20, mkdirSync as mkdirSync11, writeFileSync as writeFileSync9, unlinkSync as unlinkSync5, copyFileSync as copyFileSync2, renameSync as renameSync2, statSync as statSync9 } from "fs";
|
|
9503
9571
|
import { createInterface } from "readline/promises";
|
|
9504
9572
|
import { dirname as dirOf, resolve as resolvePath } from "path";
|
|
9505
9573
|
|
|
@@ -10237,7 +10305,7 @@ function syncPassengerSyncDaemons(plans, cfg, stateDir, deps = {}) {
|
|
|
10237
10305
|
}
|
|
10238
10306
|
|
|
10239
10307
|
// src/daemon.ts
|
|
10240
|
-
import { readdirSync as readdirSync6, statSync as
|
|
10308
|
+
import { readdirSync as readdirSync6, statSync as statSync8 } from "fs";
|
|
10241
10309
|
import { join as join19 } from "path";
|
|
10242
10310
|
var DEFAULT_FLOOR_MS = 5e3;
|
|
10243
10311
|
var DEFAULT_CEILING_MS = INTERVAL_SECONDS * 1e3;
|
|
@@ -10269,7 +10337,7 @@ async function runDaemonLoop(o) {
|
|
|
10269
10337
|
function latestMtime(path) {
|
|
10270
10338
|
let stat;
|
|
10271
10339
|
try {
|
|
10272
|
-
stat =
|
|
10340
|
+
stat = statSync8(path);
|
|
10273
10341
|
} catch {
|
|
10274
10342
|
return 0;
|
|
10275
10343
|
}
|
|
@@ -10866,6 +10934,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
10866
10934
|
const emit = new Emitter({
|
|
10867
10935
|
route: route?.route ?? "",
|
|
10868
10936
|
eventFile: dryRun ? void 0 : eventFileFor(cfg.ship.stateDir),
|
|
10937
|
+
rotateBytes: cfg.ship.eventsRotateBytes,
|
|
10869
10938
|
logFile: dryRun ? void 0 : cfg.ship.logFile,
|
|
10870
10939
|
console: (l) => process.stderr.write(`${l}
|
|
10871
10940
|
`)
|
|
@@ -10968,7 +11037,10 @@ if (command === "passenger-sync-daemon") {
|
|
|
10968
11037
|
emitReleaseSummary(emit, summaries);
|
|
10969
11038
|
if (!dryRun) state.releaseHeartbeat().touch();
|
|
10970
11039
|
try {
|
|
10971
|
-
const paths =
|
|
11040
|
+
const paths = [
|
|
11041
|
+
...planStreamSweep(cfg.ship.stateDir, cfg.ship.streamRetentionDays),
|
|
11042
|
+
...planEventsSweep(cfg.ship.stateDir, cfg.ship.streamRetentionDays)
|
|
11043
|
+
];
|
|
10972
11044
|
if (paths.length) {
|
|
10973
11045
|
emit.enter("worktree");
|
|
10974
11046
|
const removed = applyStreamSweep(paths, dryRun, emit);
|
|
@@ -12020,6 +12092,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
12020
12092
|
newEmitter: () => new Emitter({
|
|
12021
12093
|
route: route.route,
|
|
12022
12094
|
eventFile: eventFileFor(cfg.ship.stateDir),
|
|
12095
|
+
rotateBytes: cfg.ship.eventsRotateBytes,
|
|
12023
12096
|
logFile: cfg.ship.logFile,
|
|
12024
12097
|
console: (l) => process.stderr.write(`${l}
|
|
12025
12098
|
`)
|
|
@@ -12629,7 +12702,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
12629
12702
|
const tmpPath = `${resolvedPath}.tmp${process.pid}`;
|
|
12630
12703
|
writeFileSync9(tmpPath, `${JSON.stringify(resolved, null, 2)}
|
|
12631
12704
|
`);
|
|
12632
|
-
|
|
12705
|
+
renameSync2(tmpPath, resolvedPath);
|
|
12633
12706
|
process.stderr.write(`Resolved ids written to ${resolvedPath}
|
|
12634
12707
|
`);
|
|
12635
12708
|
}
|
|
@@ -12846,7 +12919,17 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
12846
12919
|
{ step: "worktree" }
|
|
12847
12920
|
);
|
|
12848
12921
|
}
|
|
12849
|
-
|
|
12922
|
+
if (!dryRun) {
|
|
12923
|
+
const rotated = rotateEventsFile(eventFileFor(cfg.ship.stateDir), cfg.ship.eventsRotateBytes);
|
|
12924
|
+
if (rotated) {
|
|
12925
|
+
anything = true;
|
|
12926
|
+
emit.emit(`rotated oversized event log to ${rotated}`, { step: "worktree" });
|
|
12927
|
+
}
|
|
12928
|
+
}
|
|
12929
|
+
const streamPaths = [
|
|
12930
|
+
...planStreamSweep(cfg.ship.stateDir, cfg.ship.streamRetentionDays),
|
|
12931
|
+
...planEventsSweep(cfg.ship.stateDir, cfg.ship.streamRetentionDays)
|
|
12932
|
+
];
|
|
12850
12933
|
if (streamPaths.length) {
|
|
12851
12934
|
anything = true;
|
|
12852
12935
|
const removed = applyStreamSweep(streamPaths, dryRun, emit);
|
|
@@ -13349,6 +13432,15 @@ watermark: ${state.watermark()}
|
|
|
13349
13432
|
routes: ${cfg.routes.map((c) => c.route).join(", ")}
|
|
13350
13433
|
`
|
|
13351
13434
|
);
|
|
13435
|
+
try {
|
|
13436
|
+
const evFile = eventFileFor(cfg.ship.stateDir);
|
|
13437
|
+
const evSize = existsSync20(evFile) ? statSync9(evFile).size : 0;
|
|
13438
|
+
process.stdout.write(
|
|
13439
|
+
evSize > cfg.ship.eventsRotateBytes * 4 ? `events log: WARN ${evFile} is ${Math.round(evSize / 1048576)} MB (> 4x eventsRotateBytes); run \`crew reap\` to rotate it
|
|
13440
|
+
` : "events log: ok\n"
|
|
13441
|
+
);
|
|
13442
|
+
} catch {
|
|
13443
|
+
}
|
|
13352
13444
|
const tracker = new Tracker(route, cfg.ship);
|
|
13353
13445
|
const contractProblems = validateContract(tracker.contract);
|
|
13354
13446
|
process.stdout.write(
|
package/package.json
CHANGED