@stim-cli/core 1.26.0 → 1.28.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-Hohf5Wnt.mjs} +64 -75
- package/dist/{index-CVIwytv5.d.mts → index-31SZcxv0.d.mts} +2142 -1915
- package/dist/index.d.mts +28 -15
- package/dist/index.mjs +2 -2
- package/dist/noise-session.d.mts +5 -0
- package/dist/noise-session.mjs +6 -0
- package/dist/phone-protocol.d.mts +2 -2
- package/dist/{protocol-swbEkLsj.d.mts → protocol-CPg61C01.d.mts} +20 -7
- package/dist/protocol.d.mts +2 -2
- package/dist/protocol.mjs +5 -0
- package/dist/receive-protocol.mjs +1072 -356
- package/dist/state.d.mts +2 -2
- package/dist/state.mjs +526 -49
- package/package.json +1 -1
package/dist/state.mjs
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
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-Hohf5Wnt.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";
|
|
11
11
|
import { setImmediate as setImmediate$1 } from "node:timers/promises";
|
|
12
12
|
import { lstat, open, readFile, readdir, stat } from "node:fs/promises";
|
|
13
|
-
import { isIP } from "node:net";
|
|
14
13
|
import { StringDecoder } from "node:string_decoder";
|
|
15
14
|
//#region state/log-selection.ts
|
|
16
15
|
var BoundedSelection = class {
|
|
@@ -645,6 +644,10 @@ function supervisorPidFile(projectRoot) {
|
|
|
645
644
|
function workspaceStateFile(projectRoot) {
|
|
646
645
|
return join(workspaceStateDir(projectRoot), "state.json");
|
|
647
646
|
}
|
|
647
|
+
/** The dependency directories `gc --delete` deleted from the workspace's worktree, apart from state.json so writing it is not workspace activity. */
|
|
648
|
+
function workspaceLightenedFile(projectRoot) {
|
|
649
|
+
return join(workspaceStateDir(projectRoot), "lightened.json");
|
|
650
|
+
}
|
|
648
651
|
function workspaceStateLock(projectRoot) {
|
|
649
652
|
return join(workspaceStateDir(projectRoot), "state.lock");
|
|
650
653
|
}
|
|
@@ -719,6 +722,12 @@ function easSessionLedgerLock(root = easMachineStateRoot()) {
|
|
|
719
722
|
function deviceViewersDir() {
|
|
720
723
|
return join(configDir(), "viewers");
|
|
721
724
|
}
|
|
725
|
+
function budgetSweepFile() {
|
|
726
|
+
return join(configDir(), "budget-sweep.json");
|
|
727
|
+
}
|
|
728
|
+
function budgetSweepLock() {
|
|
729
|
+
return join(configDir(), "budget-sweep.lock");
|
|
730
|
+
}
|
|
722
731
|
function statsFile() {
|
|
723
732
|
return join(configDir(), "stats.json");
|
|
724
733
|
}
|
|
@@ -740,6 +749,15 @@ function gcLogDir() {
|
|
|
740
749
|
function gcLogFile() {
|
|
741
750
|
return join(gcLogDir(), "gc.ndjson");
|
|
742
751
|
}
|
|
752
|
+
function buildLedgerDir() {
|
|
753
|
+
return join(configDir(), "builds");
|
|
754
|
+
}
|
|
755
|
+
function buildLedgerFile() {
|
|
756
|
+
return join(buildLedgerDir(), "builds.ndjson");
|
|
757
|
+
}
|
|
758
|
+
function buildLedgerLock() {
|
|
759
|
+
return join(buildLedgerDir(), "builds.lock");
|
|
760
|
+
}
|
|
743
761
|
function maintenanceChildLogFile() {
|
|
744
762
|
return join(maintenanceDir(), "child.log");
|
|
745
763
|
}
|
|
@@ -759,6 +777,10 @@ function archiveDir(id) {
|
|
|
759
777
|
function archiveLock() {
|
|
760
778
|
return join(archiveRoot(), ".lock");
|
|
761
779
|
}
|
|
780
|
+
/** Uncommitted changes of removed worktrees: `<repository>/<branch-or-dir>-<timestamp>/` holding `manifest.json`. */
|
|
781
|
+
function worktreeArchiveRoot() {
|
|
782
|
+
return join(archiveRoot(), "worktrees");
|
|
783
|
+
}
|
|
762
784
|
/** This machine's own long-term X25519 key as a client of other machines' stim-servers. */
|
|
763
785
|
function clientIdentityKeyFile() {
|
|
764
786
|
return join(configDir(), "client-identity.key");
|
|
@@ -1166,11 +1188,20 @@ const IOS_SIMULATOR_APPS = [
|
|
|
1166
1188
|
"stim-desktop"
|
|
1167
1189
|
];
|
|
1168
1190
|
const ANDROID_EMULATOR_APPS = ["emulator", "stim-desktop"];
|
|
1169
|
-
const
|
|
1170
|
-
"
|
|
1171
|
-
"
|
|
1172
|
-
"off"
|
|
1173
|
-
|
|
1191
|
+
const REPLACED_SETTINGS = { "remote.buildMode": {
|
|
1192
|
+
key: "remote.build",
|
|
1193
|
+
env: "STIM_REMOTE_BUILD_MODE",
|
|
1194
|
+
value: (old) => old === "auto" ? "auto" : old === "force" ? "remote" : old === "off" ? "local" : null
|
|
1195
|
+
} };
|
|
1196
|
+
function replacementAdvice(key, old) {
|
|
1197
|
+
const replaced = REPLACED_SETTINGS[key];
|
|
1198
|
+
if (!replaced) return null;
|
|
1199
|
+
const value = replaced.value(old);
|
|
1200
|
+
return {
|
|
1201
|
+
key: replaced.key,
|
|
1202
|
+
command: value ? `stim settings set ${replaced.key} ${value}` : null
|
|
1203
|
+
};
|
|
1204
|
+
}
|
|
1174
1205
|
const WEB_VIEWPORTS = ["desktop", "phone"];
|
|
1175
1206
|
const PROJECT = [
|
|
1176
1207
|
"workspace",
|
|
@@ -1288,10 +1319,10 @@ const SETTINGS = [
|
|
|
1288
1319
|
type: {
|
|
1289
1320
|
kind: "string",
|
|
1290
1321
|
pattern: TAILNET_MACHINE_PATTERN,
|
|
1291
|
-
patternHelp: "eas, proxy, auto,
|
|
1322
|
+
patternHelp: "eas, proxy, local, auto, remote, remote-only, or a tailnet machine name"
|
|
1292
1323
|
},
|
|
1293
1324
|
scopes: EVERY,
|
|
1294
|
-
description: "
|
|
1325
|
+
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
1326
|
},
|
|
1296
1327
|
{
|
|
1297
1328
|
key: "ios.projectPath",
|
|
@@ -1467,10 +1498,10 @@ const SETTINGS = [
|
|
|
1467
1498
|
type: {
|
|
1468
1499
|
kind: "string",
|
|
1469
1500
|
pattern: TAILNET_MACHINE_PATTERN,
|
|
1470
|
-
patternHelp: "eas, proxy, auto,
|
|
1501
|
+
patternHelp: "eas, proxy, local, auto, remote, remote-only, or a tailnet machine name"
|
|
1471
1502
|
},
|
|
1472
1503
|
scopes: EVERY,
|
|
1473
|
-
description: "
|
|
1504
|
+
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
1505
|
},
|
|
1475
1506
|
{
|
|
1476
1507
|
key: "metro.tunnel",
|
|
@@ -1585,6 +1616,16 @@ const SETTINGS = [
|
|
|
1585
1616
|
scopes: PROJECT,
|
|
1586
1617
|
description: "Arguments passed directly to the owned macOS development executable."
|
|
1587
1618
|
},
|
|
1619
|
+
{
|
|
1620
|
+
key: "macos.remote",
|
|
1621
|
+
type: {
|
|
1622
|
+
kind: "string",
|
|
1623
|
+
pattern: `^(?![Ee][Aa][Ss]$|[Pp][Rr][Oo][Xx][Yy]$)${TAILNET_MACHINE_PATTERN.slice(1)}`,
|
|
1624
|
+
patternHelp: "local, auto, remote, remote-only, or a tailnet machine name (macOS has no eas or proxy backend)"
|
|
1625
|
+
},
|
|
1626
|
+
scopes: EVERY,
|
|
1627
|
+
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"
|
|
1628
|
+
},
|
|
1588
1629
|
{
|
|
1589
1630
|
key: "macos.assetCatalog",
|
|
1590
1631
|
type: RELATIVE_PATH,
|
|
@@ -1639,7 +1680,7 @@ const SETTINGS = [
|
|
|
1639
1680
|
default: true,
|
|
1640
1681
|
env: "STIM_ARCHIVE_ENABLED",
|
|
1641
1682
|
scopedHomeValue: false,
|
|
1642
|
-
description: "Keep workspace history on removal; false under STIM_HOME unless explicitly enabled"
|
|
1683
|
+
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
1684
|
},
|
|
1644
1685
|
{
|
|
1645
1686
|
key: "archive.maxAgeDays",
|
|
@@ -1647,7 +1688,7 @@ const SETTINGS = [
|
|
|
1647
1688
|
scopes: MACHINE,
|
|
1648
1689
|
default: 30,
|
|
1649
1690
|
env: "STIM_ARCHIVE_MAX_AGE_DAYS",
|
|
1650
|
-
description: "Days to keep archived workspace records; 0 keeps nothing"
|
|
1691
|
+
description: "Days to keep archived workspace records and worktree change archives; 0 keeps nothing"
|
|
1651
1692
|
},
|
|
1652
1693
|
{
|
|
1653
1694
|
key: "archive.maxCount",
|
|
@@ -1663,7 +1704,7 @@ const SETTINGS = [
|
|
|
1663
1704
|
scopes: MACHINE,
|
|
1664
1705
|
default: 5,
|
|
1665
1706
|
env: "STIM_ARCHIVE_MAX_TOTAL_GB",
|
|
1666
|
-
description: "Total archived bytes in binary GB; 0 keeps no artifacts"
|
|
1707
|
+
description: "Total archived bytes in binary GB, and separately the worktree change archives; 0 keeps no artifacts"
|
|
1667
1708
|
},
|
|
1668
1709
|
{
|
|
1669
1710
|
key: "archive.logs.maxAgeDays",
|
|
@@ -2041,7 +2082,7 @@ const SETTINGS = [
|
|
|
2041
2082
|
scopes: MACHINE,
|
|
2042
2083
|
default: 10,
|
|
2043
2084
|
env: "STIM_MAINTENANCE_PRESSURE_PROTECT_MINUTES",
|
|
2044
|
-
description: "Protection window, in minutes, that replaces maintenance.protectRecentHours at disk pressure levels 2 and 3"
|
|
2085
|
+
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
2086
|
},
|
|
2046
2087
|
{
|
|
2047
2088
|
key: "maintenance.gradleVersionIdleDays",
|
|
@@ -2061,7 +2102,7 @@ const SETTINGS = [
|
|
|
2061
2102
|
scopes: MACHINE,
|
|
2062
2103
|
default: true,
|
|
2063
2104
|
env: "STIM_MAINTENANCE_REMOVE_FINISHED_WORKTREES",
|
|
2064
|
-
description: "Remove
|
|
2105
|
+
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
2106
|
},
|
|
2066
2107
|
{
|
|
2067
2108
|
key: "maintenance.removeIdleWorktrees",
|
|
@@ -2202,7 +2243,7 @@ const SETTINGS = [
|
|
|
2202
2243
|
key: "remote.machines",
|
|
2203
2244
|
type: { kind: "strings" },
|
|
2204
2245
|
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)
|
|
2246
|
+
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
2247
|
},
|
|
2207
2248
|
{
|
|
2208
2249
|
key: "remote.buildPoolDisabled",
|
|
@@ -2223,7 +2264,7 @@ const SETTINGS = [
|
|
|
2223
2264
|
type: BOOLEAN,
|
|
2224
2265
|
scopes: EVERY,
|
|
2225
2266
|
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"
|
|
2267
|
+
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
2268
|
},
|
|
2228
2269
|
{
|
|
2229
2270
|
key: "remote.easMaxDurationMinutes",
|
|
@@ -2241,23 +2282,12 @@ const SETTINGS = [
|
|
|
2241
2282
|
type: {
|
|
2242
2283
|
kind: "string",
|
|
2243
2284
|
pattern: TAILNET_MACHINE_PATTERN,
|
|
2244
|
-
patternHelp: "auto,
|
|
2285
|
+
patternHelp: "local, auto, remote, remote-only, or a tailnet machine name"
|
|
2245
2286
|
},
|
|
2246
2287
|
scopes: MACHINE,
|
|
2247
2288
|
default: "auto",
|
|
2248
2289
|
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"
|
|
2290
|
+
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
2291
|
},
|
|
2262
2292
|
{
|
|
2263
2293
|
key: "server.maxLoadPerCore",
|
|
@@ -2267,7 +2297,7 @@ const SETTINGS = [
|
|
|
2267
2297
|
},
|
|
2268
2298
|
scopes: MACHINE,
|
|
2269
2299
|
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,
|
|
2300
|
+
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
2301
|
},
|
|
2272
2302
|
{
|
|
2273
2303
|
key: "server.workerRoot",
|
|
@@ -2503,11 +2533,21 @@ function liveNativeBuilds() {
|
|
|
2503
2533
|
}
|
|
2504
2534
|
return count;
|
|
2505
2535
|
}
|
|
2506
|
-
|
|
2536
|
+
/** The 5-minute load average per CPU, rounded to one decimal, and the CPU count it divides by. */
|
|
2537
|
+
function loadPerCore() {
|
|
2507
2538
|
const cpus = Math.max(1, availableParallelism());
|
|
2508
2539
|
return {
|
|
2509
2540
|
cpus,
|
|
2510
|
-
loadPerCore: Math.round(loadavg()[1] / cpus * 10) / 10
|
|
2541
|
+
loadPerCore: Math.round(loadavg()[1] / cpus * 10) / 10
|
|
2542
|
+
};
|
|
2543
|
+
}
|
|
2544
|
+
function loadState(load, busyAt, limit) {
|
|
2545
|
+
if (limit !== null && limit > 0 && load >= limit) return "holding";
|
|
2546
|
+
return load >= busyAt ? "busy" : "free";
|
|
2547
|
+
}
|
|
2548
|
+
function machineCapacity() {
|
|
2549
|
+
return {
|
|
2550
|
+
...loadPerCore(),
|
|
2511
2551
|
builds: liveNativeBuilds() + liveOffloadedSlots(),
|
|
2512
2552
|
maxBuilds: getConcurrencyLimits().maxBuilds,
|
|
2513
2553
|
maxLoadPerCore: maxLoadPerCore()
|
|
@@ -2698,6 +2738,282 @@ function createDebugLog(component, { env, now = Date.now, base = () => ({}) } =
|
|
|
2698
2738
|
};
|
|
2699
2739
|
}
|
|
2700
2740
|
//#endregion
|
|
2741
|
+
//#region state/trace.ts
|
|
2742
|
+
const TRACE_ENV = "STIM_TRACE";
|
|
2743
|
+
const OWNER_ENV = "STIM_TRACE_OWNER";
|
|
2744
|
+
const PHASE_TID_BASE = 1e3;
|
|
2745
|
+
const MAX_ARGS = 60;
|
|
2746
|
+
const MAX_ARG_CHARS = 240;
|
|
2747
|
+
const MAX_LINE_CHARS = 500;
|
|
2748
|
+
const SCAN_ARG_CHARS = 1e3;
|
|
2749
|
+
const SCAN_LINE_CHARS = 2e3;
|
|
2750
|
+
const SECRET_WORDS = "token|secret|ticket|pass|pwd|otp|credential|cookie|api[-_]?key|access[-_]?key|private[-_]?key|auth";
|
|
2751
|
+
const SECRET_NAME = new RegExp(SECRET_WORDS, "i");
|
|
2752
|
+
const SECRET_FLAG = String.raw`--?[A-Za-z0-9_.-]*(?:${SECRET_WORDS})[A-Za-z0-9_.-]*`;
|
|
2753
|
+
const SECRET_FLAG_VALUE = new RegExp(String.raw`(${SECRET_FLAG})(=|\s+)(?:'[^']*'|"[^"]*"|\S+)`, "gi");
|
|
2754
|
+
const SECRET_ASSIGNMENT = new RegExp(String.raw`([A-Za-z0-9_]*(?:${SECRET_WORDS})[A-Za-z0-9_]*)=(?:'[^']*'|"[^"]*"|[^\s&]+)`, "gi");
|
|
2755
|
+
const URL_USERINFO = /(\/\/)[^/\s?#]*@/g;
|
|
2756
|
+
const SECRET_HEADER = new RegExp(String.raw`(\b[A-Za-z-]*(?:${SECRET_WORDS})[A-Za-z-]*:\s*)[^'"\r\n]+`, "gi");
|
|
2757
|
+
const AUTH_SCHEME = /\b(Bearer|Basic)\s+[A-Za-z0-9._~+/=-]{8,}/gi;
|
|
2758
|
+
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;
|
|
2759
|
+
const CREDENTIAL_FLAGS = {
|
|
2760
|
+
curl: { "": [
|
|
2761
|
+
"-u",
|
|
2762
|
+
"-U",
|
|
2763
|
+
"--user",
|
|
2764
|
+
"--proxy-user"
|
|
2765
|
+
] },
|
|
2766
|
+
security: {
|
|
2767
|
+
"unlock-keychain": ["-p"],
|
|
2768
|
+
"create-keychain": ["-p"],
|
|
2769
|
+
import: ["-P"],
|
|
2770
|
+
export: ["-P"]
|
|
2771
|
+
}
|
|
2772
|
+
};
|
|
2773
|
+
function credentialFlagsOf(program, subcommand) {
|
|
2774
|
+
const name = basename(program);
|
|
2775
|
+
const flags = Object.hasOwn(CREDENTIAL_FLAGS, name) ? CREDENTIAL_FLAGS[name] : void 0;
|
|
2776
|
+
const key = flags && Object.hasOwn(flags, "") ? "" : subcommand ?? "";
|
|
2777
|
+
return new Set(flags && Object.hasOwn(flags, key) ? flags[key] : []);
|
|
2778
|
+
}
|
|
2779
|
+
function scrubText(text, exact) {
|
|
2780
|
+
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]");
|
|
2781
|
+
}
|
|
2782
|
+
/**
|
|
2783
|
+
* `args` with the value of any flag or assignment that looks like a secret, credentials in URLs, token shapes and
|
|
2784
|
+
* the `exact` values replaced. `program` selects the flags that carry a credential without a secret-looking name,
|
|
2785
|
+
* such as `curl -u`.
|
|
2786
|
+
*/
|
|
2787
|
+
function redactArgv(args, exact = [], program = "") {
|
|
2788
|
+
const out = [];
|
|
2789
|
+
const credentialFlags = credentialFlagsOf(program, args[0]);
|
|
2790
|
+
let redactNext = false;
|
|
2791
|
+
for (const arg of args.slice(0, MAX_ARGS)) {
|
|
2792
|
+
const flag = /^(--?[A-Za-z0-9_.-]+)=/.exec(arg)?.[1];
|
|
2793
|
+
const assignment = /^([A-Za-z_][A-Za-z0-9_]*)=/.exec(arg)?.[1];
|
|
2794
|
+
const attached = arg.length > 2 && credentialFlags.has(arg.slice(0, 2)) ? arg.slice(0, 2) : null;
|
|
2795
|
+
if (redactNext) {
|
|
2796
|
+
out.push("[redacted]");
|
|
2797
|
+
redactNext = false;
|
|
2798
|
+
} else if (credentialFlags.has(arg)) {
|
|
2799
|
+
out.push(arg);
|
|
2800
|
+
redactNext = true;
|
|
2801
|
+
} else if (attached) out.push(`${attached}[redacted]`);
|
|
2802
|
+
else if (flag) out.push(SECRET_NAME.test(flag) || credentialFlags.has(flag) ? `${flag}=[redacted]` : clip(scrubText(arg.slice(0, SCAN_ARG_CHARS), exact)));
|
|
2803
|
+
else if (/^--?[A-Za-z0-9_.-]+$/.test(arg) && SECRET_NAME.test(arg)) {
|
|
2804
|
+
out.push(arg);
|
|
2805
|
+
redactNext = true;
|
|
2806
|
+
} else if (assignment && SECRET_NAME.test(assignment)) out.push(`${assignment}=[redacted]`);
|
|
2807
|
+
else if (/\s/.test(arg)) out.push(redactCommandLine(arg, exact));
|
|
2808
|
+
else out.push(clip(scrubText(arg.slice(0, SCAN_ARG_CHARS), exact)));
|
|
2809
|
+
}
|
|
2810
|
+
if (args.length > MAX_ARGS) out.push(`[+${args.length - MAX_ARGS} more]`);
|
|
2811
|
+
return out;
|
|
2812
|
+
}
|
|
2813
|
+
function clip(text, max = MAX_ARG_CHARS) {
|
|
2814
|
+
return text.length > max ? `${text.slice(0, max)}...` : text;
|
|
2815
|
+
}
|
|
2816
|
+
/** A shell command line with the same redactions as `redactArgv`, clipped to a fixed length. */
|
|
2817
|
+
function redactCommandLine(command, exact = []) {
|
|
2818
|
+
return clip(scrubText(command.slice(0, SCAN_LINE_CHARS).replace(SECRET_FLAG_VALUE, "$1$2[redacted]"), exact), MAX_LINE_CHARS);
|
|
2819
|
+
}
|
|
2820
|
+
const microseconds = () => Math.round((performance.timeOrigin + performance.now()) * 1e3);
|
|
2821
|
+
let current$1 = null;
|
|
2822
|
+
let attempted = null;
|
|
2823
|
+
function emit(sink, event) {
|
|
2824
|
+
try {
|
|
2825
|
+
const line = Buffer.from(`${JSON.stringify({
|
|
2826
|
+
...event,
|
|
2827
|
+
pid: process.pid
|
|
2828
|
+
})},\n`);
|
|
2829
|
+
for (let written = 0; written < line.length;) written += writeSync(sink.fd, line, written);
|
|
2830
|
+
} catch {
|
|
2831
|
+
sink.fd = -1;
|
|
2832
|
+
}
|
|
2833
|
+
}
|
|
2834
|
+
function nameThread(sink, tid, name) {
|
|
2835
|
+
if (sink.named.has(tid)) return;
|
|
2836
|
+
sink.named.add(tid);
|
|
2837
|
+
emit(sink, {
|
|
2838
|
+
ph: "M",
|
|
2839
|
+
name: "thread_name",
|
|
2840
|
+
tid,
|
|
2841
|
+
args: { name }
|
|
2842
|
+
});
|
|
2843
|
+
}
|
|
2844
|
+
function finish(sink, span, args) {
|
|
2845
|
+
if (!sink.open.delete(span)) return;
|
|
2846
|
+
if (span.lane !== null) sink.lanes[span.lane] = false;
|
|
2847
|
+
if (sink.fd < 0) return;
|
|
2848
|
+
const end = microseconds();
|
|
2849
|
+
emit(sink, {
|
|
2850
|
+
name: span.name,
|
|
2851
|
+
cat: span.cat,
|
|
2852
|
+
ph: "X",
|
|
2853
|
+
ts: span.ts,
|
|
2854
|
+
dur: Math.max(0, end - span.ts),
|
|
2855
|
+
tid: span.tid,
|
|
2856
|
+
...args || Object.keys(span.args).length ? { args: {
|
|
2857
|
+
...span.args,
|
|
2858
|
+
...args
|
|
2859
|
+
} } : {}
|
|
2860
|
+
});
|
|
2861
|
+
}
|
|
2862
|
+
function claimRoot() {
|
|
2863
|
+
const path = process.env[TRACE_ENV];
|
|
2864
|
+
if (!path) return;
|
|
2865
|
+
try {
|
|
2866
|
+
const absolute = resolve(path);
|
|
2867
|
+
if (process.env[OWNER_ENV] === absolute) return;
|
|
2868
|
+
mkdirSync(dirname(absolute), { recursive: true });
|
|
2869
|
+
writeFileSync(absolute, "[\n");
|
|
2870
|
+
process.env[OWNER_ENV] = absolute;
|
|
2871
|
+
} catch {}
|
|
2872
|
+
}
|
|
2873
|
+
function openSink(path) {
|
|
2874
|
+
try {
|
|
2875
|
+
const absolute = resolve(path);
|
|
2876
|
+
mkdirSync(dirname(absolute), { recursive: true });
|
|
2877
|
+
try {
|
|
2878
|
+
writeFileSync(absolute, "[\n", { flag: "wx" });
|
|
2879
|
+
} catch (error) {
|
|
2880
|
+
if (!(error instanceof Error && "code" in error && error.code === "EEXIST")) throw error;
|
|
2881
|
+
}
|
|
2882
|
+
process.env[TRACE_ENV] = absolute;
|
|
2883
|
+
const sink = {
|
|
2884
|
+
path: absolute,
|
|
2885
|
+
fd: openSync(absolute, "a"),
|
|
2886
|
+
lanes: [],
|
|
2887
|
+
named: /* @__PURE__ */ new Set(),
|
|
2888
|
+
open: /* @__PURE__ */ new Set(),
|
|
2889
|
+
tracks: /* @__PURE__ */ new Map()
|
|
2890
|
+
};
|
|
2891
|
+
emit(sink, {
|
|
2892
|
+
ph: "M",
|
|
2893
|
+
name: "process_name",
|
|
2894
|
+
args: { name: `${basename(process.argv[1] ?? "node").replace(/\.[cm]?[jt]s$/, "")} ${process.pid}` }
|
|
2895
|
+
});
|
|
2896
|
+
process.on("exit", (code) => {
|
|
2897
|
+
for (const span of sink.open) finish(sink, span, span.cat === "command" ? { exit: code } : { unfinished: true });
|
|
2898
|
+
});
|
|
2899
|
+
return sink;
|
|
2900
|
+
} catch {
|
|
2901
|
+
return null;
|
|
2902
|
+
}
|
|
2903
|
+
}
|
|
2904
|
+
function active() {
|
|
2905
|
+
const path = process.env[TRACE_ENV];
|
|
2906
|
+
if (!path) return null;
|
|
2907
|
+
if (path === attempted || path === current$1?.path) return current$1 && current$1.fd >= 0 ? current$1 : null;
|
|
2908
|
+
attempted = path;
|
|
2909
|
+
current$1 = openSink(path);
|
|
2910
|
+
return current$1;
|
|
2911
|
+
}
|
|
2912
|
+
function begin(name, args, cat = "span") {
|
|
2913
|
+
const sink = active();
|
|
2914
|
+
if (!sink) return NO_END;
|
|
2915
|
+
let lane = sink.lanes.indexOf(false);
|
|
2916
|
+
if (lane < 0) lane = sink.lanes.length;
|
|
2917
|
+
sink.lanes[lane] = true;
|
|
2918
|
+
const tid = lane + 1;
|
|
2919
|
+
nameThread(sink, tid, `spans ${tid}`);
|
|
2920
|
+
const span = {
|
|
2921
|
+
name,
|
|
2922
|
+
cat,
|
|
2923
|
+
tid,
|
|
2924
|
+
lane,
|
|
2925
|
+
ts: microseconds(),
|
|
2926
|
+
args: args ?? {}
|
|
2927
|
+
};
|
|
2928
|
+
sink.open.add(span);
|
|
2929
|
+
return (endArgs) => finish(sink, span, endArgs);
|
|
2930
|
+
}
|
|
2931
|
+
const NO_END = () => {};
|
|
2932
|
+
const NO_PHASES = {
|
|
2933
|
+
enter: () => {},
|
|
2934
|
+
end: () => {}
|
|
2935
|
+
};
|
|
2936
|
+
function phases(track) {
|
|
2937
|
+
const sink = active();
|
|
2938
|
+
if (!sink) return NO_PHASES;
|
|
2939
|
+
const tid = sink.tracks.get(track) ?? PHASE_TID_BASE + sink.tracks.size;
|
|
2940
|
+
sink.tracks.set(track, tid);
|
|
2941
|
+
nameThread(sink, tid, `phases: ${track}`);
|
|
2942
|
+
let open = null;
|
|
2943
|
+
const close = (args) => {
|
|
2944
|
+
if (open) finish(sink, open, args);
|
|
2945
|
+
open = null;
|
|
2946
|
+
};
|
|
2947
|
+
return {
|
|
2948
|
+
enter(name, args) {
|
|
2949
|
+
if (open?.name === name) return;
|
|
2950
|
+
close();
|
|
2951
|
+
open = {
|
|
2952
|
+
name,
|
|
2953
|
+
cat: "phase",
|
|
2954
|
+
tid,
|
|
2955
|
+
lane: null,
|
|
2956
|
+
ts: microseconds(),
|
|
2957
|
+
args: args ?? {}
|
|
2958
|
+
};
|
|
2959
|
+
sink.open.add(open);
|
|
2960
|
+
},
|
|
2961
|
+
end: close
|
|
2962
|
+
};
|
|
2963
|
+
}
|
|
2964
|
+
/**
|
|
2965
|
+
* Timing spans for one stim process tree, written as Chrome trace events (open the file in Perfetto or
|
|
2966
|
+
* chrome://tracing) when `STIM_TRACE=<path>` is set. Unset, every call is one environment check. A stim command
|
|
2967
|
+
* (`trace.command`) truncates the file and writes `[`; every other process that inherits the variable appends one
|
|
2968
|
+
* event per line, creating the file if it is missing, so the file has no closing bracket. Child process arguments are written through `redactArgv`.
|
|
2969
|
+
*/
|
|
2970
|
+
const trace = {
|
|
2971
|
+
enabled: () => active() !== null,
|
|
2972
|
+
begin: (name, args) => begin(name, args),
|
|
2973
|
+
command(name, args) {
|
|
2974
|
+
claimRoot();
|
|
2975
|
+
begin(name, args, "command");
|
|
2976
|
+
},
|
|
2977
|
+
beginExec: (name, args) => begin(name, args, "exec"),
|
|
2978
|
+
span(name, fn, args) {
|
|
2979
|
+
const end = begin(name, args);
|
|
2980
|
+
try {
|
|
2981
|
+
const result = fn();
|
|
2982
|
+
end();
|
|
2983
|
+
return result;
|
|
2984
|
+
} catch (error) {
|
|
2985
|
+
end({ error: true });
|
|
2986
|
+
throw error;
|
|
2987
|
+
}
|
|
2988
|
+
},
|
|
2989
|
+
async spanAsync(name, fn, args) {
|
|
2990
|
+
const end = begin(name, args);
|
|
2991
|
+
try {
|
|
2992
|
+
const result = await fn();
|
|
2993
|
+
end();
|
|
2994
|
+
return result;
|
|
2995
|
+
} catch (error) {
|
|
2996
|
+
end({ error: true });
|
|
2997
|
+
throw error;
|
|
2998
|
+
}
|
|
2999
|
+
},
|
|
3000
|
+
mark(name, args) {
|
|
3001
|
+
const sink = active();
|
|
3002
|
+
if (!sink) return;
|
|
3003
|
+
nameThread(sink, 1, "spans 1");
|
|
3004
|
+
emit(sink, {
|
|
3005
|
+
name,
|
|
3006
|
+
cat: "mark",
|
|
3007
|
+
ph: "i",
|
|
3008
|
+
s: "t",
|
|
3009
|
+
ts: microseconds(),
|
|
3010
|
+
tid: 1,
|
|
3011
|
+
...args ? { args } : {}
|
|
3012
|
+
});
|
|
3013
|
+
},
|
|
3014
|
+
phases
|
|
3015
|
+
};
|
|
3016
|
+
//#endregion
|
|
2701
3017
|
//#region state/home-identity.ts
|
|
2702
3018
|
/** The persistent, nonsecret identity of this Stim home; null until the CLI initializes it. */
|
|
2703
3019
|
function readHomeIdentity(config = loadConfig()) {
|
|
@@ -2898,7 +3214,8 @@ const SOURCES = [
|
|
|
2898
3214
|
"agent",
|
|
2899
3215
|
"maintenance",
|
|
2900
3216
|
"placement",
|
|
2901
|
-
"gc"
|
|
3217
|
+
"gc",
|
|
3218
|
+
"builds"
|
|
2902
3219
|
];
|
|
2903
3220
|
function levelRank(level) {
|
|
2904
3221
|
const i = LEVELS.indexOf(level);
|
|
@@ -3926,6 +4243,130 @@ async function withErrorContext(generations, { extra = [], ...options }, selecte
|
|
|
3926
4243
|
return matched.map((record) => contextual.get(record) ?? record);
|
|
3927
4244
|
}
|
|
3928
4245
|
//#endregion
|
|
4246
|
+
//#region state/ndjson-tail.ts
|
|
4247
|
+
const HEAD_BYTES = 64;
|
|
4248
|
+
function generation(path) {
|
|
4249
|
+
return {
|
|
4250
|
+
path,
|
|
4251
|
+
id: null,
|
|
4252
|
+
offset: 0,
|
|
4253
|
+
head: Buffer.alloc(0),
|
|
4254
|
+
records: []
|
|
4255
|
+
};
|
|
4256
|
+
}
|
|
4257
|
+
function reset(gen) {
|
|
4258
|
+
gen.id = null;
|
|
4259
|
+
gen.offset = 0;
|
|
4260
|
+
gen.head = Buffer.alloc(0);
|
|
4261
|
+
gen.records = [];
|
|
4262
|
+
}
|
|
4263
|
+
function fileId(stats) {
|
|
4264
|
+
return `${stats.dev}:${stats.ino}`;
|
|
4265
|
+
}
|
|
4266
|
+
function readAt(fd, length, position) {
|
|
4267
|
+
const buffer = Buffer.allocUnsafe(length);
|
|
4268
|
+
let filled = 0;
|
|
4269
|
+
while (filled < length) {
|
|
4270
|
+
const n = readSync(fd, buffer, filled, length - filled, position + filled);
|
|
4271
|
+
if (n === 0) break;
|
|
4272
|
+
filled += n;
|
|
4273
|
+
}
|
|
4274
|
+
return buffer.subarray(0, filled);
|
|
4275
|
+
}
|
|
4276
|
+
function advance(gen) {
|
|
4277
|
+
let fd;
|
|
4278
|
+
try {
|
|
4279
|
+
fd = openSync(gen.path, "r");
|
|
4280
|
+
} catch {
|
|
4281
|
+
const had = gen.id !== null;
|
|
4282
|
+
reset(gen);
|
|
4283
|
+
return had;
|
|
4284
|
+
}
|
|
4285
|
+
try {
|
|
4286
|
+
const stats = fstatSync(fd, { bigint: true });
|
|
4287
|
+
const id = fileId(stats);
|
|
4288
|
+
const size = Number(stats.size);
|
|
4289
|
+
let changed = false;
|
|
4290
|
+
if (gen.id !== id || size < gen.offset || gen.head.length > 0 && !readAt(fd, gen.head.length, 0).equals(gen.head)) {
|
|
4291
|
+
changed = gen.id !== null || gen.records.length > 0;
|
|
4292
|
+
reset(gen);
|
|
4293
|
+
gen.id = id;
|
|
4294
|
+
}
|
|
4295
|
+
const length = size - gen.offset;
|
|
4296
|
+
if (length <= 0) return changed;
|
|
4297
|
+
const buffer = readAt(fd, length, gen.offset);
|
|
4298
|
+
const end = buffer.lastIndexOf(10);
|
|
4299
|
+
if (end < 0) return changed;
|
|
4300
|
+
const added = parseNdjsonText(buffer.toString("utf-8", 0, end + 1));
|
|
4301
|
+
for (const record of added) gen.records.push(record);
|
|
4302
|
+
if (gen.head.length < HEAD_BYTES) gen.head = Buffer.concat([gen.head, buffer.subarray(0, end + 1)]).subarray(0, HEAD_BYTES);
|
|
4303
|
+
gen.offset += end + 1;
|
|
4304
|
+
return changed || added.length > 0;
|
|
4305
|
+
} finally {
|
|
4306
|
+
closeSync(fd);
|
|
4307
|
+
}
|
|
4308
|
+
}
|
|
4309
|
+
function statId(path) {
|
|
4310
|
+
try {
|
|
4311
|
+
return fileId(statSync(path, { bigint: true }));
|
|
4312
|
+
} catch {
|
|
4313
|
+
return null;
|
|
4314
|
+
}
|
|
4315
|
+
}
|
|
4316
|
+
/**
|
|
4317
|
+
* Returns a reader of `file` and its rotated generation that yields, on every call, the records a full
|
|
4318
|
+
* `readNdjsonGenerations(file)` would, while reading only the bytes appended since the previous call. A file renamed
|
|
4319
|
+
* to its rotated generation between two calls is finished from there; any other replacement or a shrunk file is
|
|
4320
|
+
* read again from its start. A file rewritten in place to at least its previous length is not detected.
|
|
4321
|
+
*/
|
|
4322
|
+
function createNdjsonTail(file) {
|
|
4323
|
+
const rotated = generation(rotatedLogPath(file));
|
|
4324
|
+
const current = generation(file);
|
|
4325
|
+
let all = [];
|
|
4326
|
+
return () => {
|
|
4327
|
+
let changed = false;
|
|
4328
|
+
if (current.id !== null && statId(file) !== current.id && statId(rotated.path) === current.id) {
|
|
4329
|
+
rotated.id = current.id;
|
|
4330
|
+
rotated.offset = current.offset;
|
|
4331
|
+
rotated.head = current.head;
|
|
4332
|
+
rotated.records = current.records;
|
|
4333
|
+
reset(current);
|
|
4334
|
+
changed = true;
|
|
4335
|
+
}
|
|
4336
|
+
changed = advance(rotated) || changed;
|
|
4337
|
+
changed = advance(current) || changed;
|
|
4338
|
+
if (changed) all = rotated.records.concat(current.records);
|
|
4339
|
+
return all;
|
|
4340
|
+
};
|
|
4341
|
+
}
|
|
4342
|
+
//#endregion
|
|
4343
|
+
//#region state/placement.ts
|
|
4344
|
+
/**
|
|
4345
|
+
* The placement words every workload shares (`remote.build`, `ios.remote`, `android.remote`, `macos.remote` and their
|
|
4346
|
+
* flags). Anything else a setting holds is a machine name, or for devices `eas` or `proxy`.
|
|
4347
|
+
*
|
|
4348
|
+
* - `local`: this machine only.
|
|
4349
|
+
* - `auto`: this machine first, a remote machine when this one is full or busy.
|
|
4350
|
+
* - `remote`: remote machines first, in `remote.machines` order; this machine only when none takes the run.
|
|
4351
|
+
* - `remote-only`: remote machines in `remote.machines` order, never this machine.
|
|
4352
|
+
*/
|
|
4353
|
+
const PLACEMENT_WORDS = [
|
|
4354
|
+
"local",
|
|
4355
|
+
"auto",
|
|
4356
|
+
"remote",
|
|
4357
|
+
"remote-only"
|
|
4358
|
+
];
|
|
4359
|
+
function placementWord(value) {
|
|
4360
|
+
const word = value.trim().toLowerCase();
|
|
4361
|
+
return PLACEMENT_WORDS.find((each) => each === word) ?? null;
|
|
4362
|
+
}
|
|
4363
|
+
function prefersRemote(word) {
|
|
4364
|
+
return word === "remote" || word === "remote-only";
|
|
4365
|
+
}
|
|
4366
|
+
function mayRunHere(word) {
|
|
4367
|
+
return word !== "remote-only";
|
|
4368
|
+
}
|
|
4369
|
+
//#endregion
|
|
3929
4370
|
//#region state/recordings.ts
|
|
3930
4371
|
/** The footage stim-server keeps per device: the last 15 minutes recorded, however long ago. */
|
|
3931
4372
|
const RECORDING_FOOTAGE_MS = 9e5;
|
|
@@ -4802,6 +5243,14 @@ function lastUseFrom(state, logMtimes) {
|
|
|
4802
5243
|
].filter(Number.isFinite);
|
|
4803
5244
|
return candidates.length ? Math.max(...candidates) : NaN;
|
|
4804
5245
|
}
|
|
5246
|
+
/**
|
|
5247
|
+
* When an ios, android or macos run last finished in the workspace, from its recorded builds (a cache hit counts),
|
|
5248
|
+
* in epoch milliseconds; NaN when it records none. Dev server starts, reloads and logs do not count.
|
|
5249
|
+
*/
|
|
5250
|
+
function lastNativeUseFrom(state) {
|
|
5251
|
+
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);
|
|
5252
|
+
return times.length ? Math.max(...times) : NaN;
|
|
5253
|
+
}
|
|
4805
5254
|
const MAINTENANCE_LOG_NAME = "maintenance.ndjson";
|
|
4806
5255
|
/**
|
|
4807
5256
|
* When the workspace was last used, in epoch milliseconds. A workspace that recorded no use falls back to the mtime
|
|
@@ -4911,8 +5360,8 @@ function trimCapacityRefusals(events, now) {
|
|
|
4911
5360
|
}).toSorted((a, b) => Date.parse(a.at) - Date.parse(b.at)).slice(-50);
|
|
4912
5361
|
}
|
|
4913
5362
|
const trimCapacityWaits = trimCapacityRefusals;
|
|
4914
|
-
const PLATFORMS = ["ios", "android"];
|
|
4915
|
-
const OUTCOMES = ["hit", "cold"];
|
|
5363
|
+
const PLATFORMS$1 = ["ios", "android"];
|
|
5364
|
+
const OUTCOMES$1 = ["hit", "cold"];
|
|
4916
5365
|
const HISTORY_LIMIT = 10;
|
|
4917
5366
|
function statsProjectKey({ root, commonDir, repoRoot }) {
|
|
4918
5367
|
if (commonDir && repoRoot && basename(commonDir) === ".git") return canonical$1(join(canonical$1(dirname(commonDir)), relative(canonical$1(repoRoot), canonical$1(root))));
|
|
@@ -5069,7 +5518,7 @@ function normalize$1(record) {
|
|
|
5069
5518
|
function normalizePlacement(value) {
|
|
5070
5519
|
if (!isJsonObject(value)) return [];
|
|
5071
5520
|
const { decision, platform, reason, project, machine } = value;
|
|
5072
|
-
if (!PLACEMENT_DECISIONS.includes(decision) || !PLATFORMS.includes(platform)) return [];
|
|
5521
|
+
if (!PLACEMENT_DECISIONS.includes(decision) || !PLATFORMS$1.includes(platform)) return [];
|
|
5073
5522
|
if (typeof reason !== "string" || typeof project !== "string") return [];
|
|
5074
5523
|
const buildMs = wholeMs(value.buildMs);
|
|
5075
5524
|
const slotWaitMs = wholeMs(value.slotWaitMs);
|
|
@@ -5092,7 +5541,7 @@ function normalizePlacement(value) {
|
|
|
5092
5541
|
function normalizeCapacityRefusal(value) {
|
|
5093
5542
|
if (!isJsonObject(value)) return [];
|
|
5094
5543
|
const { at, kind, platform, max, workspace } = value;
|
|
5095
|
-
if (kind !== "device" || !PLATFORMS.includes(platform)) return [];
|
|
5544
|
+
if (kind !== "device" || !PLATFORMS$1.includes(platform)) return [];
|
|
5096
5545
|
if (typeof at !== "string" || !Number.isFinite(Date.parse(at))) return [];
|
|
5097
5546
|
if (typeof max !== "number" || !Number.isInteger(max) || max <= 0) return [];
|
|
5098
5547
|
if (typeof workspace !== "string" || workspace === "") return [];
|
|
@@ -5132,11 +5581,11 @@ function normalizeMachineTotals(totals) {
|
|
|
5132
5581
|
function normalizeHistory(scope) {
|
|
5133
5582
|
const history = {};
|
|
5134
5583
|
if (!isJsonObject(scope)) return history;
|
|
5135
|
-
for (const platform of PLATFORMS) {
|
|
5584
|
+
for (const platform of PLATFORMS$1) {
|
|
5136
5585
|
const lists = scope[platform];
|
|
5137
5586
|
if (!isJsonObject(lists)) continue;
|
|
5138
5587
|
const normalized = {};
|
|
5139
|
-
for (const outcome of OUTCOMES) {
|
|
5588
|
+
for (const outcome of OUTCOMES$1) {
|
|
5140
5589
|
const samples = lists[outcome];
|
|
5141
5590
|
if (!Array.isArray(samples)) continue;
|
|
5142
5591
|
const kept = samples.filter(isJsonObject).flatMap((sample) => {
|
|
@@ -5167,7 +5616,7 @@ function wholePhases(phases) {
|
|
|
5167
5616
|
function normalizeScope(scope) {
|
|
5168
5617
|
const normalized = {};
|
|
5169
5618
|
if (!scope || typeof scope !== "object" || Array.isArray(scope)) return normalized;
|
|
5170
|
-
for (const platform of PLATFORMS) {
|
|
5619
|
+
for (const platform of PLATFORMS$1) {
|
|
5171
5620
|
const bucket = scope[platform];
|
|
5172
5621
|
if (bucket && typeof bucket === "object" && !Array.isArray(bucket)) normalized[platform] = normalizeBucket(bucket);
|
|
5173
5622
|
}
|
|
@@ -5426,6 +5875,7 @@ function parseHostedNativeOffer(value) {
|
|
|
5426
5875
|
resources: {
|
|
5427
5876
|
cpus: resources.cpus,
|
|
5428
5877
|
loadPerCore: resources.loadPerCore,
|
|
5878
|
+
...typeof resources.maxLoadPerCore === "number" && Number.isFinite(resources.maxLoadPerCore) && resources.maxLoadPerCore > 0 ? { maxLoadPerCore: resources.maxLoadPerCore } : {},
|
|
5429
5879
|
memoryFreeBytes: resources.memoryFreeBytes,
|
|
5430
5880
|
memoryPressure: resources.memoryPressure,
|
|
5431
5881
|
workerDiskFreeBytes: resources.workerDiskFreeBytes,
|
|
@@ -5808,21 +6258,21 @@ function hostedAndroidDeviceName(device) {
|
|
|
5808
6258
|
//#endregion
|
|
5809
6259
|
//#region state/hosted-metro.ts
|
|
5810
6260
|
const HOSTED_METRO_REQUESTS_KEY = "hostedMetroRequests";
|
|
5811
|
-
const
|
|
6261
|
+
const HOSTED_METRO_LINKS_KEY = "hostedMetroLinks";
|
|
5812
6262
|
function hostedMetroRequests(state) {
|
|
5813
6263
|
const raw = state?.[HOSTED_METRO_REQUESTS_KEY];
|
|
5814
6264
|
if (!isJsonObject(raw)) return {};
|
|
5815
6265
|
return Object.fromEntries(Object.entries(raw).filter((entry) => {
|
|
5816
6266
|
const [session, value] = entry;
|
|
5817
|
-
return /^[a-f0-9-]{36}$/.test(session) && isJsonObject(value) && typeof value.id === "string" && typeof value.machine === "string"
|
|
6267
|
+
return /^[a-f0-9-]{36}$/.test(session) && isJsonObject(value) && typeof value.id === "string" && typeof value.machine === "string";
|
|
5818
6268
|
}));
|
|
5819
6269
|
}
|
|
5820
|
-
function
|
|
5821
|
-
const raw = state?.[
|
|
6270
|
+
function hostedMetroLinks(state) {
|
|
6271
|
+
const raw = state?.[HOSTED_METRO_LINKS_KEY];
|
|
5822
6272
|
if (!isJsonObject(raw)) return {};
|
|
5823
6273
|
return Object.fromEntries(Object.entries(raw).filter((entry) => {
|
|
5824
6274
|
const value = entry[1];
|
|
5825
|
-
return isJsonObject(value) && typeof value.id === "string" && typeof value.processToken === "string" && (value.
|
|
6275
|
+
return isJsonObject(value) && typeof value.id === "string" && typeof value.processToken === "string" && (value.ready === void 0 || value.ready === true) && (value.error === void 0 || typeof value.error === "string");
|
|
5826
6276
|
}));
|
|
5827
6277
|
}
|
|
5828
6278
|
//#endregion
|
|
@@ -6033,6 +6483,33 @@ function readGcLog(file = gcLogFile()) {
|
|
|
6033
6483
|
return readNdjsonGenerations(file).filter(isGcLogRecord).toSorted((a, b) => a.ts - b.ts);
|
|
6034
6484
|
}
|
|
6035
6485
|
//#endregion
|
|
6486
|
+
//#region state/build-ledger.ts
|
|
6487
|
+
const BUILD_LEDGER_CAP = 500;
|
|
6488
|
+
const PLATFORMS = [
|
|
6489
|
+
"ios",
|
|
6490
|
+
"android",
|
|
6491
|
+
"macos"
|
|
6492
|
+
];
|
|
6493
|
+
const OUTCOMES = [
|
|
6494
|
+
"succeeded",
|
|
6495
|
+
"failed",
|
|
6496
|
+
"cancelled",
|
|
6497
|
+
"interrupted"
|
|
6498
|
+
];
|
|
6499
|
+
function isBuildLedgerRecord(record) {
|
|
6500
|
+
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;
|
|
6501
|
+
}
|
|
6502
|
+
/** The ledger's records, oldest first. A missing or unreadable ledger is empty. */
|
|
6503
|
+
function readBuildLedger(file = buildLedgerFile()) {
|
|
6504
|
+
let text;
|
|
6505
|
+
try {
|
|
6506
|
+
text = readStateFile(file);
|
|
6507
|
+
} catch {
|
|
6508
|
+
return [];
|
|
6509
|
+
}
|
|
6510
|
+
return parseNdjsonText(text).filter(isBuildLedgerRecord).toSorted((a, b) => a.ts - b.ts);
|
|
6511
|
+
}
|
|
6512
|
+
//#endregion
|
|
6036
6513
|
//#region state/agent-device.ts
|
|
6037
6514
|
function agentDeviceStateDirs(home = ambientEnv().HOME || ambientEnv().USERPROFILE || homedir(), env = ambientEnv()) {
|
|
6038
6515
|
const override = env.AGENT_DEVICE_STATE_DIR?.trim();
|
|
@@ -6252,4 +6729,4 @@ function isSetupJournalPrunable(journal, now) {
|
|
|
6252
6729
|
return Date.parse(journal.expiresAt) + 36e5 < now;
|
|
6253
6730
|
}
|
|
6254
6731
|
//#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,
|
|
6732
|
+
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_LINKS_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, hostedMetroLinks, hostedMetroRequests, hostedNativeLogsDir, hostedNativeRecords, hostedWorkerSettleMs, isBuildLedgerRecord, isExpoErrorContext, isGcLogRecord, isJournalExpired, isJsonObject, isLayeredSetting, isPathPrefix, isSensitiveReference, isSetupJournalPrunable, lastNativeUseFrom, lastUseFrom, levelRank, linkReplacedArchives, listRecordedDevices, listRecordedDevicesAsync, listSegments, listSegmentsAsync, listStatusCacheEntries, loadConfig, loadPerCore, loadState, 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 };
|