@stim-cli/core 1.26.0 → 1.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/{core-Cd3b_fBm.mjs → core-B_w6CiKQ.mjs} +4 -2
- package/dist/{index-CVIwytv5.d.mts → index-DfpdqDFf.d.mts} +162 -4
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +1 -1
- package/dist/phone-protocol.d.mts +2 -2
- package/dist/{protocol-swbEkLsj.d.mts → protocol-D21RXxrx.d.mts} +1 -1
- package/dist/protocol.d.mts +1 -1
- package/dist/state.d.mts +2 -2
- package/dist/state.mjs +509 -41
- package/package.json +1 -1
package/dist/state.mjs
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { S as tryAcquireClaim, _ as releaseClaim, f as isClaimRefusal, g as readClaimSet } from "./ownership-claim-DG2GMIoo.mjs";
|
|
2
2
|
import { inspectProcessIdentity } from "./process-identity.mjs";
|
|
3
|
-
import { $ as withDirLockAsync, C as workspaceName, J as readJsonFile, Q as withDirLock, T as workspaceStateDir, U as ambientContext, W as ambientEnv, X as readStateFile, Y as readJsonObject, Z as withStateReadCache, _ as rotatedLogPath, d as ensurePrivateDir, g as rotateLog, q as isJsonObject, t as LOG_ROTATE_BYTES, u as configDir } from "./core-
|
|
3
|
+
import { $ as withDirLockAsync, C as workspaceName, J as readJsonFile, Q as withDirLock, T as workspaceStateDir, U as ambientContext, W as ambientEnv, X as readStateFile, Y as readJsonObject, Z as withStateReadCache, _ as rotatedLogPath, d as ensurePrivateDir, g as rotateLog, q as isJsonObject, t as LOG_ROTATE_BYTES, u as configDir } from "./core-B_w6CiKQ.mjs";
|
|
4
4
|
import { readOrCreateKeyFile } from "./noise-session.mjs";
|
|
5
5
|
import { validHomeId } from "./workspace-link.mjs";
|
|
6
6
|
import { a as workspaceSummary, i as statusViews, n as applyKeyedDelta, o as withDerivedFacts, r as SUMMARY_ARCHIVES, s as withGitChip, t as KeyedEntities } from "./keyed-delta-BBoOeR2J.mjs";
|
|
7
|
-
import { appendFileSync, closeSync, existsSync, fstatSync, lstatSync, mkdirSync, openSync, readFileSync, readSync, readdirSync, realpathSync, statSync, watch } from "node:fs";
|
|
7
|
+
import { appendFileSync, closeSync, existsSync, fstatSync, lstatSync, mkdirSync, openSync, readFileSync, readSync, readdirSync, realpathSync, statSync, watch, writeFileSync, writeSync } from "node:fs";
|
|
8
8
|
import { availableParallelism, homedir, loadavg } from "node:os";
|
|
9
9
|
import { basename, dirname, isAbsolute, join, posix, relative, resolve, sep, win32 } from "node:path";
|
|
10
10
|
import { createHash, randomBytes } from "node:crypto";
|
|
@@ -645,6 +645,10 @@ function supervisorPidFile(projectRoot) {
|
|
|
645
645
|
function workspaceStateFile(projectRoot) {
|
|
646
646
|
return join(workspaceStateDir(projectRoot), "state.json");
|
|
647
647
|
}
|
|
648
|
+
/** The dependency directories `gc --delete` deleted from the workspace's worktree, apart from state.json so writing it is not workspace activity. */
|
|
649
|
+
function workspaceLightenedFile(projectRoot) {
|
|
650
|
+
return join(workspaceStateDir(projectRoot), "lightened.json");
|
|
651
|
+
}
|
|
648
652
|
function workspaceStateLock(projectRoot) {
|
|
649
653
|
return join(workspaceStateDir(projectRoot), "state.lock");
|
|
650
654
|
}
|
|
@@ -719,6 +723,12 @@ function easSessionLedgerLock(root = easMachineStateRoot()) {
|
|
|
719
723
|
function deviceViewersDir() {
|
|
720
724
|
return join(configDir(), "viewers");
|
|
721
725
|
}
|
|
726
|
+
function budgetSweepFile() {
|
|
727
|
+
return join(configDir(), "budget-sweep.json");
|
|
728
|
+
}
|
|
729
|
+
function budgetSweepLock() {
|
|
730
|
+
return join(configDir(), "budget-sweep.lock");
|
|
731
|
+
}
|
|
722
732
|
function statsFile() {
|
|
723
733
|
return join(configDir(), "stats.json");
|
|
724
734
|
}
|
|
@@ -740,6 +750,15 @@ function gcLogDir() {
|
|
|
740
750
|
function gcLogFile() {
|
|
741
751
|
return join(gcLogDir(), "gc.ndjson");
|
|
742
752
|
}
|
|
753
|
+
function buildLedgerDir() {
|
|
754
|
+
return join(configDir(), "builds");
|
|
755
|
+
}
|
|
756
|
+
function buildLedgerFile() {
|
|
757
|
+
return join(buildLedgerDir(), "builds.ndjson");
|
|
758
|
+
}
|
|
759
|
+
function buildLedgerLock() {
|
|
760
|
+
return join(buildLedgerDir(), "builds.lock");
|
|
761
|
+
}
|
|
743
762
|
function maintenanceChildLogFile() {
|
|
744
763
|
return join(maintenanceDir(), "child.log");
|
|
745
764
|
}
|
|
@@ -759,6 +778,10 @@ function archiveDir(id) {
|
|
|
759
778
|
function archiveLock() {
|
|
760
779
|
return join(archiveRoot(), ".lock");
|
|
761
780
|
}
|
|
781
|
+
/** Uncommitted changes of removed worktrees: `<repository>/<branch-or-dir>-<timestamp>/` holding `manifest.json`. */
|
|
782
|
+
function worktreeArchiveRoot() {
|
|
783
|
+
return join(archiveRoot(), "worktrees");
|
|
784
|
+
}
|
|
762
785
|
/** This machine's own long-term X25519 key as a client of other machines' stim-servers. */
|
|
763
786
|
function clientIdentityKeyFile() {
|
|
764
787
|
return join(configDir(), "client-identity.key");
|
|
@@ -1166,11 +1189,20 @@ const IOS_SIMULATOR_APPS = [
|
|
|
1166
1189
|
"stim-desktop"
|
|
1167
1190
|
];
|
|
1168
1191
|
const ANDROID_EMULATOR_APPS = ["emulator", "stim-desktop"];
|
|
1169
|
-
const
|
|
1170
|
-
"
|
|
1171
|
-
"
|
|
1172
|
-
"off"
|
|
1173
|
-
|
|
1192
|
+
const REPLACED_SETTINGS = { "remote.buildMode": {
|
|
1193
|
+
key: "remote.build",
|
|
1194
|
+
env: "STIM_REMOTE_BUILD_MODE",
|
|
1195
|
+
value: (old) => old === "auto" ? "auto" : old === "force" ? "remote" : old === "off" ? "local" : null
|
|
1196
|
+
} };
|
|
1197
|
+
function replacementAdvice(key, old) {
|
|
1198
|
+
const replaced = REPLACED_SETTINGS[key];
|
|
1199
|
+
if (!replaced) return null;
|
|
1200
|
+
const value = replaced.value(old);
|
|
1201
|
+
return {
|
|
1202
|
+
key: replaced.key,
|
|
1203
|
+
command: value ? `stim settings set ${replaced.key} ${value}` : null
|
|
1204
|
+
};
|
|
1205
|
+
}
|
|
1174
1206
|
const WEB_VIEWPORTS = ["desktop", "phone"];
|
|
1175
1207
|
const PROJECT = [
|
|
1176
1208
|
"workspace",
|
|
@@ -1288,10 +1320,10 @@ const SETTINGS = [
|
|
|
1288
1320
|
type: {
|
|
1289
1321
|
kind: "string",
|
|
1290
1322
|
pattern: TAILNET_MACHINE_PATTERN,
|
|
1291
|
-
patternHelp: "eas, proxy, auto,
|
|
1323
|
+
patternHelp: "eas, proxy, local, auto, remote, remote-only, or a tailnet machine name"
|
|
1292
1324
|
},
|
|
1293
1325
|
scopes: EVERY,
|
|
1294
|
-
description: "
|
|
1326
|
+
description: "Where the iOS simulator runs: local here (overriding a lower layer); auto here first, then a remote.machines entry when this machine is full or busy; remote on a remote.machines entry in order, here only when none takes it; remote-only the same but never here; a machine name exactly that machine; eas or proxy a remote device backend"
|
|
1295
1327
|
},
|
|
1296
1328
|
{
|
|
1297
1329
|
key: "ios.projectPath",
|
|
@@ -1467,10 +1499,10 @@ const SETTINGS = [
|
|
|
1467
1499
|
type: {
|
|
1468
1500
|
kind: "string",
|
|
1469
1501
|
pattern: TAILNET_MACHINE_PATTERN,
|
|
1470
|
-
patternHelp: "eas, proxy, auto,
|
|
1502
|
+
patternHelp: "eas, proxy, local, auto, remote, remote-only, or a tailnet machine name"
|
|
1471
1503
|
},
|
|
1472
1504
|
scopes: EVERY,
|
|
1473
|
-
description: "
|
|
1505
|
+
description: "Where the Android emulator runs: local here (overriding a lower layer); auto here first, then a remote.machines entry when this machine is full or busy; remote on a remote.machines entry in order, here only when none takes it; remote-only the same but never here; a machine name exactly that machine; eas or proxy a remote device backend"
|
|
1474
1506
|
},
|
|
1475
1507
|
{
|
|
1476
1508
|
key: "metro.tunnel",
|
|
@@ -1585,6 +1617,16 @@ const SETTINGS = [
|
|
|
1585
1617
|
scopes: PROJECT,
|
|
1586
1618
|
description: "Arguments passed directly to the owned macOS development executable."
|
|
1587
1619
|
},
|
|
1620
|
+
{
|
|
1621
|
+
key: "macos.remote",
|
|
1622
|
+
type: {
|
|
1623
|
+
kind: "string",
|
|
1624
|
+
pattern: `^(?![Ee][Aa][Ss]$|[Pp][Rr][Oo][Xx][Yy]$)${TAILNET_MACHINE_PATTERN.slice(1)}`,
|
|
1625
|
+
patternHelp: "local, auto, remote, remote-only, or a tailnet machine name (macOS has no eas or proxy backend)"
|
|
1626
|
+
},
|
|
1627
|
+
scopes: EVERY,
|
|
1628
|
+
description: "Where stim macos launches the app: local here; auto here unless this machine is busy, then a remote.machines entry; remote on a remote.machines entry in order, here only when none takes it; remote-only the same but never here; a machine name exactly that machine. The --remote flag overrides it"
|
|
1629
|
+
},
|
|
1588
1630
|
{
|
|
1589
1631
|
key: "macos.assetCatalog",
|
|
1590
1632
|
type: RELATIVE_PATH,
|
|
@@ -1639,7 +1681,7 @@ const SETTINGS = [
|
|
|
1639
1681
|
default: true,
|
|
1640
1682
|
env: "STIM_ARCHIVE_ENABLED",
|
|
1641
1683
|
scopedHomeValue: false,
|
|
1642
|
-
description: "Keep workspace history on removal; false under STIM_HOME unless explicitly enabled"
|
|
1684
|
+
description: "Keep workspace history on removal, and archive a dirty finished worktree's changes before gc removes it; false keeps such worktrees, and is the default under STIM_HOME unless explicitly enabled"
|
|
1643
1685
|
},
|
|
1644
1686
|
{
|
|
1645
1687
|
key: "archive.maxAgeDays",
|
|
@@ -1647,7 +1689,7 @@ const SETTINGS = [
|
|
|
1647
1689
|
scopes: MACHINE,
|
|
1648
1690
|
default: 30,
|
|
1649
1691
|
env: "STIM_ARCHIVE_MAX_AGE_DAYS",
|
|
1650
|
-
description: "Days to keep archived workspace records; 0 keeps nothing"
|
|
1692
|
+
description: "Days to keep archived workspace records and worktree change archives; 0 keeps nothing"
|
|
1651
1693
|
},
|
|
1652
1694
|
{
|
|
1653
1695
|
key: "archive.maxCount",
|
|
@@ -1663,7 +1705,7 @@ const SETTINGS = [
|
|
|
1663
1705
|
scopes: MACHINE,
|
|
1664
1706
|
default: 5,
|
|
1665
1707
|
env: "STIM_ARCHIVE_MAX_TOTAL_GB",
|
|
1666
|
-
description: "Total archived bytes in binary GB; 0 keeps no artifacts"
|
|
1708
|
+
description: "Total archived bytes in binary GB, and separately the worktree change archives; 0 keeps no artifacts"
|
|
1667
1709
|
},
|
|
1668
1710
|
{
|
|
1669
1711
|
key: "archive.logs.maxAgeDays",
|
|
@@ -2041,7 +2083,7 @@ const SETTINGS = [
|
|
|
2041
2083
|
scopes: MACHINE,
|
|
2042
2084
|
default: 10,
|
|
2043
2085
|
env: "STIM_MAINTENANCE_PRESSURE_PROTECT_MINUTES",
|
|
2044
|
-
description: "Protection window, in minutes, that replaces maintenance.protectRecentHours at disk pressure levels 2 and 3"
|
|
2086
|
+
description: "Protection window, in minutes, that replaces maintenance.protectRecentHours at disk pressure levels 2 and 3, except for workspace build outputs and build directories, which level 2 keeps for 15 minutes after the last ios, android or macos run"
|
|
2045
2087
|
},
|
|
2046
2088
|
{
|
|
2047
2089
|
key: "maintenance.gradleVersionIdleDays",
|
|
@@ -2061,7 +2103,7 @@ const SETTINGS = [
|
|
|
2061
2103
|
scopes: MACHINE,
|
|
2062
2104
|
default: true,
|
|
2063
2105
|
env: "STIM_MAINTENANCE_REMOVE_FINISHED_WORKTREES",
|
|
2064
|
-
description: "Remove
|
|
2106
|
+
description: "Remove linked worktrees whose branch or pull request finished, under gc.worktreeGraceMinutes, archiving a dirty one's uncommitted files first, and clean, pushed ones with no commits of their own once idle"
|
|
2065
2107
|
},
|
|
2066
2108
|
{
|
|
2067
2109
|
key: "maintenance.removeIdleWorktrees",
|
|
@@ -2202,7 +2244,7 @@ const SETTINGS = [
|
|
|
2202
2244
|
key: "remote.machines",
|
|
2203
2245
|
type: { kind: "strings" },
|
|
2204
2246
|
scopes: MACHINE,
|
|
2205
|
-
description: "Tailscale names of the remote machines this machine may build on and host owned simulators on, each optionally with :<port> of its tailscale serve route (default 7443)
|
|
2247
|
+
description: "Tailscale names of the remote machines this machine may build on and host owned simulators, emulators and macOS apps on, each optionally with :<port> of its tailscale serve route (default 7443). The order is the placement priority: each workload goes to the first machine with room and a load under its own limit. What each is used for follows the build and device-host approvals it grants, which doctor --fix asks for separately"
|
|
2206
2248
|
},
|
|
2207
2249
|
{
|
|
2208
2250
|
key: "remote.buildPoolDisabled",
|
|
@@ -2223,7 +2265,7 @@ const SETTINGS = [
|
|
|
2223
2265
|
type: BOOLEAN,
|
|
2224
2266
|
scopes: EVERY,
|
|
2225
2267
|
default: false,
|
|
2226
|
-
description: "When ios.remote or android.remote is auto, this machine is at its concurrency.maxDevices cap and no remote.machines entry takes the run, run the simulator or emulator on a billed EAS Simulator instead of waiting or refusing, when eas-cli can"
|
|
2268
|
+
description: "When ios.remote or android.remote is auto or remote, this machine is at its concurrency.maxDevices cap and no remote.machines entry takes the run, run the simulator or emulator on a billed EAS Simulator instead of waiting or refusing, when eas-cli can; remote-only never uses it"
|
|
2227
2269
|
},
|
|
2228
2270
|
{
|
|
2229
2271
|
key: "remote.easMaxDurationMinutes",
|
|
@@ -2241,23 +2283,12 @@ const SETTINGS = [
|
|
|
2241
2283
|
type: {
|
|
2242
2284
|
kind: "string",
|
|
2243
2285
|
pattern: TAILNET_MACHINE_PATTERN,
|
|
2244
|
-
patternHelp: "auto,
|
|
2286
|
+
patternHelp: "local, auto, remote, remote-only, or a tailnet machine name"
|
|
2245
2287
|
},
|
|
2246
2288
|
scopes: MACHINE,
|
|
2247
2289
|
default: "auto",
|
|
2248
2290
|
env: "STIM_REMOTE_BUILD",
|
|
2249
|
-
description: "
|
|
2250
|
-
},
|
|
2251
|
-
{
|
|
2252
|
-
key: "remote.buildMode",
|
|
2253
|
-
type: {
|
|
2254
|
-
kind: "choice",
|
|
2255
|
-
choices: OFFLOAD_MODES
|
|
2256
|
-
},
|
|
2257
|
-
scopes: MACHINE,
|
|
2258
|
-
default: "auto",
|
|
2259
|
-
env: "STIM_REMOTE_BUILD_MODE",
|
|
2260
|
-
description: "When remote.build is auto, where iOS simulator Debug, Android emulator debug and macOS SwiftPM Debug builds run: auto builds here while this machine has a free concurrency.maxBuilds slot and its load is under server.maxLoadPerCore, and otherwise builds on a less loaded machine in remote.machines; force builds on an enabled remote machine whenever one can take the build; off builds here when local is enabled in remote.buildPoolDisabled"
|
|
2291
|
+
description: "Where iOS simulator Debug, Android emulator debug and macOS SwiftPM Debug builds run: local here; auto here while it has a free concurrency.maxBuilds slot and load under server.maxLoadPerCore, otherwise on a remote.machines entry; remote on a remote.machines entry in order, here only when none takes the build; remote-only the same but never here; a machine name exactly that machine without fallback. The ios, android and macos --remote-build flag overrides this setting and its environment override"
|
|
2261
2292
|
},
|
|
2262
2293
|
{
|
|
2263
2294
|
key: "server.maxLoadPerCore",
|
|
@@ -2267,7 +2298,7 @@ const SETTINGS = [
|
|
|
2267
2298
|
},
|
|
2268
2299
|
scopes: MACHINE,
|
|
2269
2300
|
default: 2,
|
|
2270
|
-
description: "Load per core (5-minute load average divided by the CPU count) at which this machine counts as saturated: it declines builds other machines send it,
|
|
2301
|
+
description: "Load per core (5-minute load average divided by the CPU count) at which this machine counts as saturated: it declines builds other machines send it, reports the limit in its device offers so other machines skip it like a full one, stops preferring itself for automatic placement, and automatic iOS, Android and macOS placement treats it as busy"
|
|
2271
2302
|
},
|
|
2272
2303
|
{
|
|
2273
2304
|
key: "server.workerRoot",
|
|
@@ -2698,6 +2729,282 @@ function createDebugLog(component, { env, now = Date.now, base = () => ({}) } =
|
|
|
2698
2729
|
};
|
|
2699
2730
|
}
|
|
2700
2731
|
//#endregion
|
|
2732
|
+
//#region state/trace.ts
|
|
2733
|
+
const TRACE_ENV = "STIM_TRACE";
|
|
2734
|
+
const OWNER_ENV = "STIM_TRACE_OWNER";
|
|
2735
|
+
const PHASE_TID_BASE = 1e3;
|
|
2736
|
+
const MAX_ARGS = 60;
|
|
2737
|
+
const MAX_ARG_CHARS = 240;
|
|
2738
|
+
const MAX_LINE_CHARS = 500;
|
|
2739
|
+
const SCAN_ARG_CHARS = 1e3;
|
|
2740
|
+
const SCAN_LINE_CHARS = 2e3;
|
|
2741
|
+
const SECRET_WORDS = "token|secret|ticket|pass|pwd|otp|credential|cookie|api[-_]?key|access[-_]?key|private[-_]?key|auth";
|
|
2742
|
+
const SECRET_NAME = new RegExp(SECRET_WORDS, "i");
|
|
2743
|
+
const SECRET_FLAG = String.raw`--?[A-Za-z0-9_.-]*(?:${SECRET_WORDS})[A-Za-z0-9_.-]*`;
|
|
2744
|
+
const SECRET_FLAG_VALUE = new RegExp(String.raw`(${SECRET_FLAG})(=|\s+)(?:'[^']*'|"[^"]*"|\S+)`, "gi");
|
|
2745
|
+
const SECRET_ASSIGNMENT = new RegExp(String.raw`([A-Za-z0-9_]*(?:${SECRET_WORDS})[A-Za-z0-9_]*)=(?:'[^']*'|"[^"]*"|[^\s&]+)`, "gi");
|
|
2746
|
+
const URL_USERINFO = /(\/\/)[^/\s?#]*@/g;
|
|
2747
|
+
const SECRET_HEADER = new RegExp(String.raw`(\b[A-Za-z-]*(?:${SECRET_WORDS})[A-Za-z-]*:\s*)[^'"\r\n]+`, "gi");
|
|
2748
|
+
const AUTH_SCHEME = /\b(Bearer|Basic)\s+[A-Za-z0-9._~+/=-]{8,}/gi;
|
|
2749
|
+
const TOKEN_SHAPE = /\b(?:(?:gh[pousr]_|github_pat_|sk-|[sr]k_(?:live|test)_|xox[abprs]-|AKIA|AIza|glpat-)[A-Za-z0-9_-]{10,}|npm_[A-Za-z0-9]{36}|eyJ[A-Za-z0-9_-]{5,}\.[A-Za-z0-9_-]{5,}\.[A-Za-z0-9_-]*)/g;
|
|
2750
|
+
const CREDENTIAL_FLAGS = {
|
|
2751
|
+
curl: { "": [
|
|
2752
|
+
"-u",
|
|
2753
|
+
"-U",
|
|
2754
|
+
"--user",
|
|
2755
|
+
"--proxy-user"
|
|
2756
|
+
] },
|
|
2757
|
+
security: {
|
|
2758
|
+
"unlock-keychain": ["-p"],
|
|
2759
|
+
"create-keychain": ["-p"],
|
|
2760
|
+
import: ["-P"],
|
|
2761
|
+
export: ["-P"]
|
|
2762
|
+
}
|
|
2763
|
+
};
|
|
2764
|
+
function credentialFlagsOf(program, subcommand) {
|
|
2765
|
+
const name = basename(program);
|
|
2766
|
+
const flags = Object.hasOwn(CREDENTIAL_FLAGS, name) ? CREDENTIAL_FLAGS[name] : void 0;
|
|
2767
|
+
const key = flags && Object.hasOwn(flags, "") ? "" : subcommand ?? "";
|
|
2768
|
+
return new Set(flags && Object.hasOwn(flags, key) ? flags[key] : []);
|
|
2769
|
+
}
|
|
2770
|
+
function scrubText(text, exact) {
|
|
2771
|
+
return exact.filter(Boolean).reduce((acc, value) => acc.split(value).join("***"), text).replace(URL_USERINFO, "$1[redacted]@").replace(SECRET_ASSIGNMENT, "$1=[redacted]").replace(SECRET_HEADER, "$1[redacted]").replace(AUTH_SCHEME, "$1 [redacted]").replace(TOKEN_SHAPE, "[redacted]");
|
|
2772
|
+
}
|
|
2773
|
+
/**
|
|
2774
|
+
* `args` with the value of any flag or assignment that looks like a secret, credentials in URLs, token shapes and
|
|
2775
|
+
* the `exact` values replaced. `program` selects the flags that carry a credential without a secret-looking name,
|
|
2776
|
+
* such as `curl -u`.
|
|
2777
|
+
*/
|
|
2778
|
+
function redactArgv(args, exact = [], program = "") {
|
|
2779
|
+
const out = [];
|
|
2780
|
+
const credentialFlags = credentialFlagsOf(program, args[0]);
|
|
2781
|
+
let redactNext = false;
|
|
2782
|
+
for (const arg of args.slice(0, MAX_ARGS)) {
|
|
2783
|
+
const flag = /^(--?[A-Za-z0-9_.-]+)=/.exec(arg)?.[1];
|
|
2784
|
+
const assignment = /^([A-Za-z_][A-Za-z0-9_]*)=/.exec(arg)?.[1];
|
|
2785
|
+
const attached = arg.length > 2 && credentialFlags.has(arg.slice(0, 2)) ? arg.slice(0, 2) : null;
|
|
2786
|
+
if (redactNext) {
|
|
2787
|
+
out.push("[redacted]");
|
|
2788
|
+
redactNext = false;
|
|
2789
|
+
} else if (credentialFlags.has(arg)) {
|
|
2790
|
+
out.push(arg);
|
|
2791
|
+
redactNext = true;
|
|
2792
|
+
} else if (attached) out.push(`${attached}[redacted]`);
|
|
2793
|
+
else if (flag) out.push(SECRET_NAME.test(flag) || credentialFlags.has(flag) ? `${flag}=[redacted]` : clip(scrubText(arg.slice(0, SCAN_ARG_CHARS), exact)));
|
|
2794
|
+
else if (/^--?[A-Za-z0-9_.-]+$/.test(arg) && SECRET_NAME.test(arg)) {
|
|
2795
|
+
out.push(arg);
|
|
2796
|
+
redactNext = true;
|
|
2797
|
+
} else if (assignment && SECRET_NAME.test(assignment)) out.push(`${assignment}=[redacted]`);
|
|
2798
|
+
else if (/\s/.test(arg)) out.push(redactCommandLine(arg, exact));
|
|
2799
|
+
else out.push(clip(scrubText(arg.slice(0, SCAN_ARG_CHARS), exact)));
|
|
2800
|
+
}
|
|
2801
|
+
if (args.length > MAX_ARGS) out.push(`[+${args.length - MAX_ARGS} more]`);
|
|
2802
|
+
return out;
|
|
2803
|
+
}
|
|
2804
|
+
function clip(text, max = MAX_ARG_CHARS) {
|
|
2805
|
+
return text.length > max ? `${text.slice(0, max)}...` : text;
|
|
2806
|
+
}
|
|
2807
|
+
/** A shell command line with the same redactions as `redactArgv`, clipped to a fixed length. */
|
|
2808
|
+
function redactCommandLine(command, exact = []) {
|
|
2809
|
+
return clip(scrubText(command.slice(0, SCAN_LINE_CHARS).replace(SECRET_FLAG_VALUE, "$1$2[redacted]"), exact), MAX_LINE_CHARS);
|
|
2810
|
+
}
|
|
2811
|
+
const microseconds = () => Math.round((performance.timeOrigin + performance.now()) * 1e3);
|
|
2812
|
+
let current$1 = null;
|
|
2813
|
+
let attempted = null;
|
|
2814
|
+
function emit(sink, event) {
|
|
2815
|
+
try {
|
|
2816
|
+
const line = Buffer.from(`${JSON.stringify({
|
|
2817
|
+
...event,
|
|
2818
|
+
pid: process.pid
|
|
2819
|
+
})},\n`);
|
|
2820
|
+
for (let written = 0; written < line.length;) written += writeSync(sink.fd, line, written);
|
|
2821
|
+
} catch {
|
|
2822
|
+
sink.fd = -1;
|
|
2823
|
+
}
|
|
2824
|
+
}
|
|
2825
|
+
function nameThread(sink, tid, name) {
|
|
2826
|
+
if (sink.named.has(tid)) return;
|
|
2827
|
+
sink.named.add(tid);
|
|
2828
|
+
emit(sink, {
|
|
2829
|
+
ph: "M",
|
|
2830
|
+
name: "thread_name",
|
|
2831
|
+
tid,
|
|
2832
|
+
args: { name }
|
|
2833
|
+
});
|
|
2834
|
+
}
|
|
2835
|
+
function finish(sink, span, args) {
|
|
2836
|
+
if (!sink.open.delete(span)) return;
|
|
2837
|
+
if (span.lane !== null) sink.lanes[span.lane] = false;
|
|
2838
|
+
if (sink.fd < 0) return;
|
|
2839
|
+
const end = microseconds();
|
|
2840
|
+
emit(sink, {
|
|
2841
|
+
name: span.name,
|
|
2842
|
+
cat: span.cat,
|
|
2843
|
+
ph: "X",
|
|
2844
|
+
ts: span.ts,
|
|
2845
|
+
dur: Math.max(0, end - span.ts),
|
|
2846
|
+
tid: span.tid,
|
|
2847
|
+
...args || Object.keys(span.args).length ? { args: {
|
|
2848
|
+
...span.args,
|
|
2849
|
+
...args
|
|
2850
|
+
} } : {}
|
|
2851
|
+
});
|
|
2852
|
+
}
|
|
2853
|
+
function claimRoot() {
|
|
2854
|
+
const path = process.env[TRACE_ENV];
|
|
2855
|
+
if (!path) return;
|
|
2856
|
+
try {
|
|
2857
|
+
const absolute = resolve(path);
|
|
2858
|
+
if (process.env[OWNER_ENV] === absolute) return;
|
|
2859
|
+
mkdirSync(dirname(absolute), { recursive: true });
|
|
2860
|
+
writeFileSync(absolute, "[\n");
|
|
2861
|
+
process.env[OWNER_ENV] = absolute;
|
|
2862
|
+
} catch {}
|
|
2863
|
+
}
|
|
2864
|
+
function openSink(path) {
|
|
2865
|
+
try {
|
|
2866
|
+
const absolute = resolve(path);
|
|
2867
|
+
mkdirSync(dirname(absolute), { recursive: true });
|
|
2868
|
+
try {
|
|
2869
|
+
writeFileSync(absolute, "[\n", { flag: "wx" });
|
|
2870
|
+
} catch (error) {
|
|
2871
|
+
if (!(error instanceof Error && "code" in error && error.code === "EEXIST")) throw error;
|
|
2872
|
+
}
|
|
2873
|
+
process.env[TRACE_ENV] = absolute;
|
|
2874
|
+
const sink = {
|
|
2875
|
+
path: absolute,
|
|
2876
|
+
fd: openSync(absolute, "a"),
|
|
2877
|
+
lanes: [],
|
|
2878
|
+
named: /* @__PURE__ */ new Set(),
|
|
2879
|
+
open: /* @__PURE__ */ new Set(),
|
|
2880
|
+
tracks: /* @__PURE__ */ new Map()
|
|
2881
|
+
};
|
|
2882
|
+
emit(sink, {
|
|
2883
|
+
ph: "M",
|
|
2884
|
+
name: "process_name",
|
|
2885
|
+
args: { name: `${basename(process.argv[1] ?? "node").replace(/\.[cm]?[jt]s$/, "")} ${process.pid}` }
|
|
2886
|
+
});
|
|
2887
|
+
process.on("exit", (code) => {
|
|
2888
|
+
for (const span of sink.open) finish(sink, span, span.cat === "command" ? { exit: code } : { unfinished: true });
|
|
2889
|
+
});
|
|
2890
|
+
return sink;
|
|
2891
|
+
} catch {
|
|
2892
|
+
return null;
|
|
2893
|
+
}
|
|
2894
|
+
}
|
|
2895
|
+
function active() {
|
|
2896
|
+
const path = process.env[TRACE_ENV];
|
|
2897
|
+
if (!path) return null;
|
|
2898
|
+
if (path === attempted || path === current$1?.path) return current$1 && current$1.fd >= 0 ? current$1 : null;
|
|
2899
|
+
attempted = path;
|
|
2900
|
+
current$1 = openSink(path);
|
|
2901
|
+
return current$1;
|
|
2902
|
+
}
|
|
2903
|
+
function begin(name, args, cat = "span") {
|
|
2904
|
+
const sink = active();
|
|
2905
|
+
if (!sink) return NO_END;
|
|
2906
|
+
let lane = sink.lanes.indexOf(false);
|
|
2907
|
+
if (lane < 0) lane = sink.lanes.length;
|
|
2908
|
+
sink.lanes[lane] = true;
|
|
2909
|
+
const tid = lane + 1;
|
|
2910
|
+
nameThread(sink, tid, `spans ${tid}`);
|
|
2911
|
+
const span = {
|
|
2912
|
+
name,
|
|
2913
|
+
cat,
|
|
2914
|
+
tid,
|
|
2915
|
+
lane,
|
|
2916
|
+
ts: microseconds(),
|
|
2917
|
+
args: args ?? {}
|
|
2918
|
+
};
|
|
2919
|
+
sink.open.add(span);
|
|
2920
|
+
return (endArgs) => finish(sink, span, endArgs);
|
|
2921
|
+
}
|
|
2922
|
+
const NO_END = () => {};
|
|
2923
|
+
const NO_PHASES = {
|
|
2924
|
+
enter: () => {},
|
|
2925
|
+
end: () => {}
|
|
2926
|
+
};
|
|
2927
|
+
function phases(track) {
|
|
2928
|
+
const sink = active();
|
|
2929
|
+
if (!sink) return NO_PHASES;
|
|
2930
|
+
const tid = sink.tracks.get(track) ?? PHASE_TID_BASE + sink.tracks.size;
|
|
2931
|
+
sink.tracks.set(track, tid);
|
|
2932
|
+
nameThread(sink, tid, `phases: ${track}`);
|
|
2933
|
+
let open = null;
|
|
2934
|
+
const close = (args) => {
|
|
2935
|
+
if (open) finish(sink, open, args);
|
|
2936
|
+
open = null;
|
|
2937
|
+
};
|
|
2938
|
+
return {
|
|
2939
|
+
enter(name, args) {
|
|
2940
|
+
if (open?.name === name) return;
|
|
2941
|
+
close();
|
|
2942
|
+
open = {
|
|
2943
|
+
name,
|
|
2944
|
+
cat: "phase",
|
|
2945
|
+
tid,
|
|
2946
|
+
lane: null,
|
|
2947
|
+
ts: microseconds(),
|
|
2948
|
+
args: args ?? {}
|
|
2949
|
+
};
|
|
2950
|
+
sink.open.add(open);
|
|
2951
|
+
},
|
|
2952
|
+
end: close
|
|
2953
|
+
};
|
|
2954
|
+
}
|
|
2955
|
+
/**
|
|
2956
|
+
* Timing spans for one stim process tree, written as Chrome trace events (open the file in Perfetto or
|
|
2957
|
+
* chrome://tracing) when `STIM_TRACE=<path>` is set. Unset, every call is one environment check. A stim command
|
|
2958
|
+
* (`trace.command`) truncates the file and writes `[`; every other process that inherits the variable appends one
|
|
2959
|
+
* event per line, creating the file if it is missing, so the file has no closing bracket. Child process arguments are written through `redactArgv`.
|
|
2960
|
+
*/
|
|
2961
|
+
const trace = {
|
|
2962
|
+
enabled: () => active() !== null,
|
|
2963
|
+
begin: (name, args) => begin(name, args),
|
|
2964
|
+
command(name, args) {
|
|
2965
|
+
claimRoot();
|
|
2966
|
+
begin(name, args, "command");
|
|
2967
|
+
},
|
|
2968
|
+
beginExec: (name, args) => begin(name, args, "exec"),
|
|
2969
|
+
span(name, fn, args) {
|
|
2970
|
+
const end = begin(name, args);
|
|
2971
|
+
try {
|
|
2972
|
+
const result = fn();
|
|
2973
|
+
end();
|
|
2974
|
+
return result;
|
|
2975
|
+
} catch (error) {
|
|
2976
|
+
end({ error: true });
|
|
2977
|
+
throw error;
|
|
2978
|
+
}
|
|
2979
|
+
},
|
|
2980
|
+
async spanAsync(name, fn, args) {
|
|
2981
|
+
const end = begin(name, args);
|
|
2982
|
+
try {
|
|
2983
|
+
const result = await fn();
|
|
2984
|
+
end();
|
|
2985
|
+
return result;
|
|
2986
|
+
} catch (error) {
|
|
2987
|
+
end({ error: true });
|
|
2988
|
+
throw error;
|
|
2989
|
+
}
|
|
2990
|
+
},
|
|
2991
|
+
mark(name, args) {
|
|
2992
|
+
const sink = active();
|
|
2993
|
+
if (!sink) return;
|
|
2994
|
+
nameThread(sink, 1, "spans 1");
|
|
2995
|
+
emit(sink, {
|
|
2996
|
+
name,
|
|
2997
|
+
cat: "mark",
|
|
2998
|
+
ph: "i",
|
|
2999
|
+
s: "t",
|
|
3000
|
+
ts: microseconds(),
|
|
3001
|
+
tid: 1,
|
|
3002
|
+
...args ? { args } : {}
|
|
3003
|
+
});
|
|
3004
|
+
},
|
|
3005
|
+
phases
|
|
3006
|
+
};
|
|
3007
|
+
//#endregion
|
|
2701
3008
|
//#region state/home-identity.ts
|
|
2702
3009
|
/** The persistent, nonsecret identity of this Stim home; null until the CLI initializes it. */
|
|
2703
3010
|
function readHomeIdentity(config = loadConfig()) {
|
|
@@ -2898,7 +3205,8 @@ const SOURCES = [
|
|
|
2898
3205
|
"agent",
|
|
2899
3206
|
"maintenance",
|
|
2900
3207
|
"placement",
|
|
2901
|
-
"gc"
|
|
3208
|
+
"gc",
|
|
3209
|
+
"builds"
|
|
2902
3210
|
];
|
|
2903
3211
|
function levelRank(level) {
|
|
2904
3212
|
const i = LEVELS.indexOf(level);
|
|
@@ -3926,6 +4234,130 @@ async function withErrorContext(generations, { extra = [], ...options }, selecte
|
|
|
3926
4234
|
return matched.map((record) => contextual.get(record) ?? record);
|
|
3927
4235
|
}
|
|
3928
4236
|
//#endregion
|
|
4237
|
+
//#region state/ndjson-tail.ts
|
|
4238
|
+
const HEAD_BYTES = 64;
|
|
4239
|
+
function generation(path) {
|
|
4240
|
+
return {
|
|
4241
|
+
path,
|
|
4242
|
+
id: null,
|
|
4243
|
+
offset: 0,
|
|
4244
|
+
head: Buffer.alloc(0),
|
|
4245
|
+
records: []
|
|
4246
|
+
};
|
|
4247
|
+
}
|
|
4248
|
+
function reset(gen) {
|
|
4249
|
+
gen.id = null;
|
|
4250
|
+
gen.offset = 0;
|
|
4251
|
+
gen.head = Buffer.alloc(0);
|
|
4252
|
+
gen.records = [];
|
|
4253
|
+
}
|
|
4254
|
+
function fileId(stats) {
|
|
4255
|
+
return `${stats.dev}:${stats.ino}`;
|
|
4256
|
+
}
|
|
4257
|
+
function readAt(fd, length, position) {
|
|
4258
|
+
const buffer = Buffer.allocUnsafe(length);
|
|
4259
|
+
let filled = 0;
|
|
4260
|
+
while (filled < length) {
|
|
4261
|
+
const n = readSync(fd, buffer, filled, length - filled, position + filled);
|
|
4262
|
+
if (n === 0) break;
|
|
4263
|
+
filled += n;
|
|
4264
|
+
}
|
|
4265
|
+
return buffer.subarray(0, filled);
|
|
4266
|
+
}
|
|
4267
|
+
function advance(gen) {
|
|
4268
|
+
let fd;
|
|
4269
|
+
try {
|
|
4270
|
+
fd = openSync(gen.path, "r");
|
|
4271
|
+
} catch {
|
|
4272
|
+
const had = gen.id !== null;
|
|
4273
|
+
reset(gen);
|
|
4274
|
+
return had;
|
|
4275
|
+
}
|
|
4276
|
+
try {
|
|
4277
|
+
const stats = fstatSync(fd, { bigint: true });
|
|
4278
|
+
const id = fileId(stats);
|
|
4279
|
+
const size = Number(stats.size);
|
|
4280
|
+
let changed = false;
|
|
4281
|
+
if (gen.id !== id || size < gen.offset || gen.head.length > 0 && !readAt(fd, gen.head.length, 0).equals(gen.head)) {
|
|
4282
|
+
changed = gen.id !== null || gen.records.length > 0;
|
|
4283
|
+
reset(gen);
|
|
4284
|
+
gen.id = id;
|
|
4285
|
+
}
|
|
4286
|
+
const length = size - gen.offset;
|
|
4287
|
+
if (length <= 0) return changed;
|
|
4288
|
+
const buffer = readAt(fd, length, gen.offset);
|
|
4289
|
+
const end = buffer.lastIndexOf(10);
|
|
4290
|
+
if (end < 0) return changed;
|
|
4291
|
+
const added = parseNdjsonText(buffer.toString("utf-8", 0, end + 1));
|
|
4292
|
+
for (const record of added) gen.records.push(record);
|
|
4293
|
+
if (gen.head.length < HEAD_BYTES) gen.head = Buffer.concat([gen.head, buffer.subarray(0, end + 1)]).subarray(0, HEAD_BYTES);
|
|
4294
|
+
gen.offset += end + 1;
|
|
4295
|
+
return changed || added.length > 0;
|
|
4296
|
+
} finally {
|
|
4297
|
+
closeSync(fd);
|
|
4298
|
+
}
|
|
4299
|
+
}
|
|
4300
|
+
function statId(path) {
|
|
4301
|
+
try {
|
|
4302
|
+
return fileId(statSync(path, { bigint: true }));
|
|
4303
|
+
} catch {
|
|
4304
|
+
return null;
|
|
4305
|
+
}
|
|
4306
|
+
}
|
|
4307
|
+
/**
|
|
4308
|
+
* Returns a reader of `file` and its rotated generation that yields, on every call, the records a full
|
|
4309
|
+
* `readNdjsonGenerations(file)` would, while reading only the bytes appended since the previous call. A file renamed
|
|
4310
|
+
* to its rotated generation between two calls is finished from there; any other replacement or a shrunk file is
|
|
4311
|
+
* read again from its start. A file rewritten in place to at least its previous length is not detected.
|
|
4312
|
+
*/
|
|
4313
|
+
function createNdjsonTail(file) {
|
|
4314
|
+
const rotated = generation(rotatedLogPath(file));
|
|
4315
|
+
const current = generation(file);
|
|
4316
|
+
let all = [];
|
|
4317
|
+
return () => {
|
|
4318
|
+
let changed = false;
|
|
4319
|
+
if (current.id !== null && statId(file) !== current.id && statId(rotated.path) === current.id) {
|
|
4320
|
+
rotated.id = current.id;
|
|
4321
|
+
rotated.offset = current.offset;
|
|
4322
|
+
rotated.head = current.head;
|
|
4323
|
+
rotated.records = current.records;
|
|
4324
|
+
reset(current);
|
|
4325
|
+
changed = true;
|
|
4326
|
+
}
|
|
4327
|
+
changed = advance(rotated) || changed;
|
|
4328
|
+
changed = advance(current) || changed;
|
|
4329
|
+
if (changed) all = rotated.records.concat(current.records);
|
|
4330
|
+
return all;
|
|
4331
|
+
};
|
|
4332
|
+
}
|
|
4333
|
+
//#endregion
|
|
4334
|
+
//#region state/placement.ts
|
|
4335
|
+
/**
|
|
4336
|
+
* The placement words every workload shares (`remote.build`, `ios.remote`, `android.remote`, `macos.remote` and their
|
|
4337
|
+
* flags). Anything else a setting holds is a machine name, or for devices `eas` or `proxy`.
|
|
4338
|
+
*
|
|
4339
|
+
* - `local`: this machine only.
|
|
4340
|
+
* - `auto`: this machine first, a remote machine when this one is full or busy.
|
|
4341
|
+
* - `remote`: remote machines first, in `remote.machines` order; this machine only when none takes the run.
|
|
4342
|
+
* - `remote-only`: remote machines in `remote.machines` order, never this machine.
|
|
4343
|
+
*/
|
|
4344
|
+
const PLACEMENT_WORDS = [
|
|
4345
|
+
"local",
|
|
4346
|
+
"auto",
|
|
4347
|
+
"remote",
|
|
4348
|
+
"remote-only"
|
|
4349
|
+
];
|
|
4350
|
+
function placementWord(value) {
|
|
4351
|
+
const word = value.trim().toLowerCase();
|
|
4352
|
+
return PLACEMENT_WORDS.find((each) => each === word) ?? null;
|
|
4353
|
+
}
|
|
4354
|
+
function prefersRemote(word) {
|
|
4355
|
+
return word === "remote" || word === "remote-only";
|
|
4356
|
+
}
|
|
4357
|
+
function mayRunHere(word) {
|
|
4358
|
+
return word !== "remote-only";
|
|
4359
|
+
}
|
|
4360
|
+
//#endregion
|
|
3929
4361
|
//#region state/recordings.ts
|
|
3930
4362
|
/** The footage stim-server keeps per device: the last 15 minutes recorded, however long ago. */
|
|
3931
4363
|
const RECORDING_FOOTAGE_MS = 9e5;
|
|
@@ -4802,6 +5234,14 @@ function lastUseFrom(state, logMtimes) {
|
|
|
4802
5234
|
].filter(Number.isFinite);
|
|
4803
5235
|
return candidates.length ? Math.max(...candidates) : NaN;
|
|
4804
5236
|
}
|
|
5237
|
+
/**
|
|
5238
|
+
* When an ios, android or macos run last finished in the workspace, from its recorded builds (a cache hit counts),
|
|
5239
|
+
* in epoch milliseconds; NaN when it records none. Dev server starts, reloads and logs do not count.
|
|
5240
|
+
*/
|
|
5241
|
+
function lastNativeUseFrom(state) {
|
|
5242
|
+
const times = [...Object.values(readLastBuilds(state)), ...Object.values(readBuildHistory(state)).flatMap((entries) => entries.slice(0, 1))].map((run) => Date.parse(run.finishedAt ?? run.startedAt)).filter(Number.isFinite);
|
|
5243
|
+
return times.length ? Math.max(...times) : NaN;
|
|
5244
|
+
}
|
|
4805
5245
|
const MAINTENANCE_LOG_NAME = "maintenance.ndjson";
|
|
4806
5246
|
/**
|
|
4807
5247
|
* When the workspace was last used, in epoch milliseconds. A workspace that recorded no use falls back to the mtime
|
|
@@ -4911,8 +5351,8 @@ function trimCapacityRefusals(events, now) {
|
|
|
4911
5351
|
}).toSorted((a, b) => Date.parse(a.at) - Date.parse(b.at)).slice(-50);
|
|
4912
5352
|
}
|
|
4913
5353
|
const trimCapacityWaits = trimCapacityRefusals;
|
|
4914
|
-
const PLATFORMS = ["ios", "android"];
|
|
4915
|
-
const OUTCOMES = ["hit", "cold"];
|
|
5354
|
+
const PLATFORMS$1 = ["ios", "android"];
|
|
5355
|
+
const OUTCOMES$1 = ["hit", "cold"];
|
|
4916
5356
|
const HISTORY_LIMIT = 10;
|
|
4917
5357
|
function statsProjectKey({ root, commonDir, repoRoot }) {
|
|
4918
5358
|
if (commonDir && repoRoot && basename(commonDir) === ".git") return canonical$1(join(canonical$1(dirname(commonDir)), relative(canonical$1(repoRoot), canonical$1(root))));
|
|
@@ -5069,7 +5509,7 @@ function normalize$1(record) {
|
|
|
5069
5509
|
function normalizePlacement(value) {
|
|
5070
5510
|
if (!isJsonObject(value)) return [];
|
|
5071
5511
|
const { decision, platform, reason, project, machine } = value;
|
|
5072
|
-
if (!PLACEMENT_DECISIONS.includes(decision) || !PLATFORMS.includes(platform)) return [];
|
|
5512
|
+
if (!PLACEMENT_DECISIONS.includes(decision) || !PLATFORMS$1.includes(platform)) return [];
|
|
5073
5513
|
if (typeof reason !== "string" || typeof project !== "string") return [];
|
|
5074
5514
|
const buildMs = wholeMs(value.buildMs);
|
|
5075
5515
|
const slotWaitMs = wholeMs(value.slotWaitMs);
|
|
@@ -5092,7 +5532,7 @@ function normalizePlacement(value) {
|
|
|
5092
5532
|
function normalizeCapacityRefusal(value) {
|
|
5093
5533
|
if (!isJsonObject(value)) return [];
|
|
5094
5534
|
const { at, kind, platform, max, workspace } = value;
|
|
5095
|
-
if (kind !== "device" || !PLATFORMS.includes(platform)) return [];
|
|
5535
|
+
if (kind !== "device" || !PLATFORMS$1.includes(platform)) return [];
|
|
5096
5536
|
if (typeof at !== "string" || !Number.isFinite(Date.parse(at))) return [];
|
|
5097
5537
|
if (typeof max !== "number" || !Number.isInteger(max) || max <= 0) return [];
|
|
5098
5538
|
if (typeof workspace !== "string" || workspace === "") return [];
|
|
@@ -5132,11 +5572,11 @@ function normalizeMachineTotals(totals) {
|
|
|
5132
5572
|
function normalizeHistory(scope) {
|
|
5133
5573
|
const history = {};
|
|
5134
5574
|
if (!isJsonObject(scope)) return history;
|
|
5135
|
-
for (const platform of PLATFORMS) {
|
|
5575
|
+
for (const platform of PLATFORMS$1) {
|
|
5136
5576
|
const lists = scope[platform];
|
|
5137
5577
|
if (!isJsonObject(lists)) continue;
|
|
5138
5578
|
const normalized = {};
|
|
5139
|
-
for (const outcome of OUTCOMES) {
|
|
5579
|
+
for (const outcome of OUTCOMES$1) {
|
|
5140
5580
|
const samples = lists[outcome];
|
|
5141
5581
|
if (!Array.isArray(samples)) continue;
|
|
5142
5582
|
const kept = samples.filter(isJsonObject).flatMap((sample) => {
|
|
@@ -5167,7 +5607,7 @@ function wholePhases(phases) {
|
|
|
5167
5607
|
function normalizeScope(scope) {
|
|
5168
5608
|
const normalized = {};
|
|
5169
5609
|
if (!scope || typeof scope !== "object" || Array.isArray(scope)) return normalized;
|
|
5170
|
-
for (const platform of PLATFORMS) {
|
|
5610
|
+
for (const platform of PLATFORMS$1) {
|
|
5171
5611
|
const bucket = scope[platform];
|
|
5172
5612
|
if (bucket && typeof bucket === "object" && !Array.isArray(bucket)) normalized[platform] = normalizeBucket(bucket);
|
|
5173
5613
|
}
|
|
@@ -5426,6 +5866,7 @@ function parseHostedNativeOffer(value) {
|
|
|
5426
5866
|
resources: {
|
|
5427
5867
|
cpus: resources.cpus,
|
|
5428
5868
|
loadPerCore: resources.loadPerCore,
|
|
5869
|
+
...typeof resources.maxLoadPerCore === "number" && Number.isFinite(resources.maxLoadPerCore) && resources.maxLoadPerCore > 0 ? { maxLoadPerCore: resources.maxLoadPerCore } : {},
|
|
5429
5870
|
memoryFreeBytes: resources.memoryFreeBytes,
|
|
5430
5871
|
memoryPressure: resources.memoryPressure,
|
|
5431
5872
|
workerDiskFreeBytes: resources.workerDiskFreeBytes,
|
|
@@ -6033,6 +6474,33 @@ function readGcLog(file = gcLogFile()) {
|
|
|
6033
6474
|
return readNdjsonGenerations(file).filter(isGcLogRecord).toSorted((a, b) => a.ts - b.ts);
|
|
6034
6475
|
}
|
|
6035
6476
|
//#endregion
|
|
6477
|
+
//#region state/build-ledger.ts
|
|
6478
|
+
const BUILD_LEDGER_CAP = 500;
|
|
6479
|
+
const PLATFORMS = [
|
|
6480
|
+
"ios",
|
|
6481
|
+
"android",
|
|
6482
|
+
"macos"
|
|
6483
|
+
];
|
|
6484
|
+
const OUTCOMES = [
|
|
6485
|
+
"succeeded",
|
|
6486
|
+
"failed",
|
|
6487
|
+
"cancelled",
|
|
6488
|
+
"interrupted"
|
|
6489
|
+
];
|
|
6490
|
+
function isBuildLedgerRecord(record) {
|
|
6491
|
+
return record.src === "builds" && record.event === "build" && typeof record.ts === "number" && typeof record.id === "string" && typeof record.platform === "string" && PLATFORMS.includes(record.platform) && typeof record.outcome === "string" && OUTCOMES.includes(record.outcome) && typeof record.placement === "object" && record.placement !== null && typeof record.cache === "object" && record.cache !== null;
|
|
6492
|
+
}
|
|
6493
|
+
/** The ledger's records, oldest first. A missing or unreadable ledger is empty. */
|
|
6494
|
+
function readBuildLedger(file = buildLedgerFile()) {
|
|
6495
|
+
let text;
|
|
6496
|
+
try {
|
|
6497
|
+
text = readStateFile(file);
|
|
6498
|
+
} catch {
|
|
6499
|
+
return [];
|
|
6500
|
+
}
|
|
6501
|
+
return parseNdjsonText(text).filter(isBuildLedgerRecord).toSorted((a, b) => a.ts - b.ts);
|
|
6502
|
+
}
|
|
6503
|
+
//#endregion
|
|
6036
6504
|
//#region state/agent-device.ts
|
|
6037
6505
|
function agentDeviceStateDirs(home = ambientEnv().HOME || ambientEnv().USERPROFILE || homedir(), env = ambientEnv()) {
|
|
6038
6506
|
const override = env.AGENT_DEVICE_STATE_DIR?.trim();
|
|
@@ -6252,4 +6720,4 @@ function isSetupJournalPrunable(journal, now) {
|
|
|
6252
6720
|
return Date.parse(journal.expiresAt) + 36e5 < now;
|
|
6253
6721
|
}
|
|
6254
6722
|
//#endregion
|
|
6255
|
-
export { ACTIVITY_RECENCY_BASES, AGENT_TOOLS, ANDROID_COMPILER_CACHE_CHOICES, ANDROID_EMULATOR_APPS, ANDROID_PCH_CHOICES, APP_PROCESS_STATES, BUILD_HISTORY_KEY, BUILD_HISTORY_LIMIT, BUILD_PHASES, BUILD_RESULTS, CAPACITY_REFUSAL_LIMIT, CAPACITY_REFUSAL_MAX_AGE_MS, DEBUG_ENV, DEVICE_HOST_LIMITS, DEVICE_IDLE_SHUTDOWN_KEY, DEVICE_RECLAIM_REASON, DEV_SERVER_STOP_REQUEST_KEY, ENDED_AGENT_RETENTION_MS, ERROR_SOURCES, FINGERPRINT_MEMO_SCHEMA, HISTORY_LIMIT, HOSTED_AGENT_DRIVERS, HOSTED_APP_CHUNK_BYTES, HOSTED_LOGS_FIRST_BYTES, HOSTED_LOGS_PAGE_BYTES, HOSTED_MACOS_APP_SLOTS, HOSTED_METRO_GATEWAYS_KEY, HOSTED_METRO_REQUESTS_KEY, HOSTED_PARKED_CANDIDATES, IDLE_STOP_KEY, IOS_SIMULATOR_APPS, KeyedEntities, LAST_BUILD_KEYS, LEVELS, MACHINE_CREDENTIALS_VERSION, MACHINE_OWNER_KINDS, MAINTENANCE_LOG_NAME, MEMORY_SOURCES, NATIVE_BUILD_STEPS, NATIVE_CRASH_LOG_PREFIX,
|
|
6723
|
+
export { ACTIVITY_RECENCY_BASES, AGENT_TOOLS, ANDROID_COMPILER_CACHE_CHOICES, ANDROID_EMULATOR_APPS, ANDROID_PCH_CHOICES, APP_PROCESS_STATES, BUILD_HISTORY_KEY, BUILD_HISTORY_LIMIT, BUILD_LEDGER_CAP, BUILD_PHASES, BUILD_RESULTS, CAPACITY_REFUSAL_LIMIT, CAPACITY_REFUSAL_MAX_AGE_MS, DEBUG_ENV, DEVICE_HOST_LIMITS, DEVICE_IDLE_SHUTDOWN_KEY, DEVICE_RECLAIM_REASON, DEV_SERVER_STOP_REQUEST_KEY, ENDED_AGENT_RETENTION_MS, ERROR_SOURCES, FINGERPRINT_MEMO_SCHEMA, HISTORY_LIMIT, HOSTED_AGENT_DRIVERS, HOSTED_APP_CHUNK_BYTES, HOSTED_LOGS_FIRST_BYTES, HOSTED_LOGS_PAGE_BYTES, HOSTED_MACOS_APP_SLOTS, HOSTED_METRO_GATEWAYS_KEY, HOSTED_METRO_REQUESTS_KEY, HOSTED_PARKED_CANDIDATES, IDLE_STOP_KEY, IOS_SIMULATOR_APPS, KeyedEntities, LAST_BUILD_KEYS, LEVELS, MACHINE_CREDENTIALS_VERSION, MACHINE_OWNER_KINDS, MAINTENANCE_LOG_NAME, MEMORY_SOURCES, NATIVE_BUILD_STEPS, NATIVE_CRASH_LOG_PREFIX, PHYSICAL_DEVICE_CONNECTIONS, PLACEMENT_DECISIONS, PLACEMENT_WORDS, READY_PHASE_MS, RECORDING_CAP_BYTES, RECORDING_FOOTAGE_MS, RECORDING_PLATFORMS, REMOTE_DEVICE_BACKENDS, REPLACED_SETTINGS, RUN_ID_ENV, SETTINGS, SETTINGS_SCHEMA_FILE, SETTINGS_SCHEMA_URL, SETTING_GROUPS, SETTING_SCOPES, SOURCES, STATS_VERSION, STATUS_ISSUE_CODES, SUMMARY_ARCHIVES, TAILNET_MACHINE_PATTERN, TRACE_ENV, TUNNEL_MODES, TUTORIAL_VERSION, WARM_KEY, WEB_PAGE_STATES, WEB_VIEWPORTS, WORKSPACE_AGENT_KEY, WORKSPACE_PHASES, WORKSPACE_STAGE_KINDS, acceptsShape, advanceTail, agentDeviceStateDirs, agentDeviceStateDirsAsync, agentDeviceUsageFile, agentSessionOf, agentSessionsCacheFile, allMetroPorts, applyKeyedDelta, archiveDir, archiveEnabled, archiveLock, archiveRoot, archivedUsage, assertHostedDeviceLedger, attachExpoErrorContext, automaticMachineEnabled, budgetSweepFile, budgetSweepLock, buildCriteria, buildDiagnostics, buildLedgerDir, buildLedgerFile, buildLedgerLock, buildMachinesFile, buildMachinesLock, buildSlotPath, buildSlotsDir, buildWorkerRoot, bundleMarkerPlatform, cannotHost, capDrops, clientIdentity, clientIdentityKeyFile, closedSegmentName, coerceSettingText, compileGrep, configCorruptRepair, configLockPath, countErrorEntries, createAgentActionReader, createAsyncAgentActionReader, createDebugLog, createNdjsonTail, createdDevicesFile, createdDevicesLock, debugLogDir, debugLoggingEnabled, decodeStats, deviceHostArea, deviceHostMachinesClaims, deviceHostMachinesFile, deviceHostMachinesLock, deviceHostRoot, deviceViewersDir, diskUsageCacheDir, diskUsageCacheFile, diskUsageFailureFile, diskUsageWalkClaims, easMachineStateRoot, easSessionLedgerFile, easSessionLedgerLock, emulatorLogFile, endedAgentSessionOf, endpoint, expectedShape, fileIdentity, fileSizes, findEnclosingWorktreeRoot, findPeer, findProjectRoot, fingerprintMemoDir, fingerprintMemoFile, fingerprintMemoLock, followLogs, footageDrops, gcLogDir, gcLogFile, getConcurrencyLimits, getConfigPath, getParkedMax, getProject, getProjectSettings, getRepoSettings, gitMergeCacheDir, hostedAgentDriverName, hostedAndroidDeviceName, hostedAndroidPlacements, hostedAndroidRecords, hostedAndroidStatus, hostedAppArea, hostedAppAttempt, hostedAppBlobs, hostedConsolePort, hostedDeviceId, hostedIosPlacements, hostedIosRecords, hostedIosStatus, hostedMacosAppSlot, hostedMacosBundleId, hostedMacosLogsDir, hostedMetroGateways, hostedMetroRequests, hostedNativeLogsDir, hostedNativeRecords, hostedWorkerSettleMs, isBuildLedgerRecord, isExpoErrorContext, isGcLogRecord, isJournalExpired, isJsonObject, isLayeredSetting, isPathPrefix, isSensitiveReference, isSetupJournalPrunable, lastNativeUseFrom, lastUseFrom, levelRank, linkReplacedArchives, listRecordedDevices, listRecordedDevicesAsync, listSegments, listSegmentsAsync, listStatusCacheEntries, loadConfig, logFiles, machineCacheUsageFile, machineCapacity, machineEndpoint, macosAppState, maintenanceAttemptFile, maintenanceChildLogFile, maintenanceDir, maintenanceNdjsonFile, maintenanceRunClaims, maintenanceStateFile, markerWindow, maxLoadPerCore, mayRunHere, offloadSummary, openLogDir, openSegmentName, parseHostedAgentAccess, parseHostedAgentGrant, parseHostedAndroidDevice, parseHostedAndroidPlacement, parseHostedAppManifest, parseHostedAppOffer, parseHostedChoice, parseHostedDevice, parseHostedIosPlacement, parseHostedLocalDeviceCount, parseHostedLogsCursor, parseHostedMacosChoice, parseHostedMacosDevice, parseHostedMacosPlacement, parseHostedNativeOffer, parseHostedNativePlacement, parseHostedOfferRequest, parseHostedPlatformDevice, parseHostedRequest, parseMachine, parseMachineCredential, parseMacosRecord, parseMaintenanceState, parseMissReason, parseNdjsonLine, parseNdjsonText, parseSetupJournal, parseSince, placementWord, planLogQuery, prefersRemote, projectRootDirectories, pullRequestCacheDir, pullRequestCacheFile, queryJsonLogs, queryLogs, readAgentDeviceUsage, readAgentDeviceUsageCache, readAgentSessionsCache, readArchive, readArchiveAsync, readArchiveState, readArchives, readArchivesAsync, readBuildDetail, readBuildHistory, readBuildLedger, readBuildMachines, readCreatedDevices, readDevServerStop, readDevServerStopRequest, readDeviceHostMachines, readDeviceIdleShutdowns, readDevicePlacement, readDiskUsage, readDiskUsageFailure, readEasSessionLedger, readEndedAgentSessions, readFingerprintMemo, readGcLog, readHomeIdentity, readHostedApp, readHostedAppMetadata, readHostedDevice, readHostedDeviceLedger, readHostedMacosApp, readHostedNativeLogsCheckpoint, readHostedSessions, readIdleStop, readJsonFile, readJsonObject, readLastBuilds, readLogRecords, readLogTimeline, readLogsSince, readMachineCacheUsage, readMacosRecord, readMaintenanceAttempt, readMaintenanceState, readNdjsonGenerations, readPullRequestCache, readRunEstimates, readSetupJournal, readStateFile, readStats, readStatsReport, readSwiftpmCacheUsage, readViewedDevices, readWarmRecord, readWorkspaceAgent, readWorkspaceState, recordMatches, recordedBytes, recordingDeviceName, recordingEnabled, redactArgv, redactCommandLine, rememberHostedAppBlobRename, replacementAdvice, requireAutomaticMachine, runId, saturation, serverLocalSocket, serverSetupDir, settingDefinition, settingValueError, settingsJsonSchema, setupJournalFile, sharedCcache, sharedCompilationCache, sharedGradle, sharedPods, sortByTs, statsFile, statsProjectKey, statusViews, stimBuildDigest, supervisorLogFile, supervisorPidFile, swiftpmCacheUsageFile, tailRead, trace, trashRoot, trimCapacityRefusals, trimCapacityWaits, trimSamples, tryAcquireBuildSlotClaim, unreadableHostedAndroid, unreadableHostedIos, unreadableHostedNative, validEasSessionClaim, validHostedAppArguments, validHostedAppBlob, validRunId, validateAutomaticPool, wholePhases, windowsShortDir, windowsShortDirKey, withConfigLock, withConfigLockAsync, withDerivedFacts, withGitChip, withStateReadCache, workspaceAgentDeviceDir, workspaceBuildDetailFile, workspaceDerivedData, workspaceEndedAgentsFile, workspaceEndedAgentsLock, workspaceGradleBuild, workspaceLastUsed, workspaceLightenedFile, workspaceLogErrorIndex, workspaceLogsDir, workspaceMetadataFile, workspaceRecordingsDir, workspaceStateFile, workspaceStateLock, workspaceSummary, worktreeArchiveRoot };
|