@stim-cli/core 1.12.0 → 1.13.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-CJWLJIr6.mjs → core-CmZJEFj_.mjs} +11 -3
- package/dist/index.mjs +1 -1
- package/dist/state.d.mts +129 -6
- package/dist/state.mjs +252 -26
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { _ as releaseClaim, r as ClaimRefusedError, x as tryAcquireClaim } from "./ownership-claim-O31Y5iIb.mjs";
|
|
1
|
+
import { S as quotedPath, _ as releaseClaim, r as ClaimRefusedError, x as tryAcquireClaim } from "./ownership-claim-O31Y5iIb.mjs";
|
|
2
2
|
import fs, { existsSync, lstatSync, mkdirSync, readdirSync, renameSync, rmSync, rmdirSync, statSync, unlinkSync, utimesSync, writeFileSync } from "node:fs";
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import path, { basename, dirname, join } from "node:path";
|
|
@@ -76,7 +76,15 @@ function takeLockDirectory(lockPath, claim) {
|
|
|
76
76
|
if (!removeLockDirectory(lockPath, marker) || !createLockDirectory(lockPath)) return null;
|
|
77
77
|
}
|
|
78
78
|
const marker = join(lockPath, `.stim-claim-${claim.claimId}`);
|
|
79
|
-
|
|
79
|
+
try {
|
|
80
|
+
writeFileSync(marker, "", { flag: "wx" });
|
|
81
|
+
} catch (error) {
|
|
82
|
+
try {
|
|
83
|
+
rmSync(marker, { force: true });
|
|
84
|
+
rmdirSync(lockPath);
|
|
85
|
+
} catch {}
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
80
88
|
return marker;
|
|
81
89
|
}
|
|
82
90
|
function acquireDirLock(lockPath, { waitMs, pollMs, ensureParent }) {
|
|
@@ -103,7 +111,7 @@ function acquireDirLock(lockPath, { waitMs, pollMs, ensureParent }) {
|
|
|
103
111
|
};
|
|
104
112
|
}
|
|
105
113
|
if (Date.now() >= deadline) {
|
|
106
|
-
const error = /* @__PURE__ */ new Error(`Timed out waiting for the lock at ${lockPath}.
|
|
114
|
+
const error = /* @__PURE__ */ new Error(`Timed out waiting for the lock at ${lockPath}. ` + (claim ? `No current Stim holds it: the directory is empty, or an older Stim version holds or left it. If no older Stim is running, remove it and run the command again:\n rm -rf ${quotedPath(lockPath)}` : "Another Stim process is holding it; wait for that command to finish and run the command again."));
|
|
107
115
|
error.code = "STIM_LOCK_TIMEOUT";
|
|
108
116
|
error.lockPath = lockPath;
|
|
109
117
|
throw error;
|
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { S as quotedPath } from "./ownership-claim-O31Y5iIb.mjs";
|
|
2
|
-
import { C as artifactIn, E as withDirLock, S as workspaceStateDir, T as storeArtifact, _ as updateCacheManifest, a as buildCacheRoot, b as workspaceName, c as cachePathSetting, d as readCacheManifest, f as registerCache, g as tagSharedStore, h as sharedStoreRoot, i as buildCacheKey, l as configDir, m as rotatedLogPath, n as METRO_NAMED_CACHE_LAYOUT, o as cacheManifestLockPath, p as rotateLog, r as STORE_ROOT_TAG, s as cacheNameSegment, t as LOG_ROTATE_BYTES, u as metroCacheRoot, v as workspaceId, w as resolveArtifact, x as workspaceSlug, y as workspaceLogDir } from "./core-
|
|
2
|
+
import { C as artifactIn, E as withDirLock, S as workspaceStateDir, T as storeArtifact, _ as updateCacheManifest, a as buildCacheRoot, b as workspaceName, c as cachePathSetting, d as readCacheManifest, f as registerCache, g as tagSharedStore, h as sharedStoreRoot, i as buildCacheKey, l as configDir, m as rotatedLogPath, n as METRO_NAMED_CACHE_LAYOUT, o as cacheManifestLockPath, p as rotateLog, r as STORE_ROOT_TAG, s as cacheNameSegment, t as LOG_ROTATE_BYTES, u as metroCacheRoot, v as workspaceId, w as resolveArtifact, x as workspaceSlug, y as workspaceLogDir } from "./core-CmZJEFj_.mjs";
|
|
3
3
|
export { LOG_ROTATE_BYTES, METRO_NAMED_CACHE_LAYOUT, STORE_ROOT_TAG, artifactIn, buildCacheKey, buildCacheRoot, cacheManifestLockPath, cacheNameSegment, cachePathSetting, configDir, metroCacheRoot, quotedPath, readCacheManifest, registerCache, resolveArtifact, rotateLog, rotatedLogPath, sharedStoreRoot, storeArtifact, tagSharedStore, updateCacheManifest, withDirLock, workspaceId, workspaceLogDir, workspaceName, workspaceSlug, workspaceStateDir };
|
package/dist/state.d.mts
CHANGED
|
@@ -112,15 +112,24 @@ declare function allMetroPorts(): number[];
|
|
|
112
112
|
declare function findProjectByMetroPort(port: number): string | null;
|
|
113
113
|
//#endregion
|
|
114
114
|
//#region state/json-file.d.ts
|
|
115
|
+
/**
|
|
116
|
+
* Runs `fn` with each state file read from disk at most once: inside it, including after an `await`, `readStateFile`
|
|
117
|
+
* returns the text of the first read of that path, and a path found missing stays missing. Callers still parse every time, so no two
|
|
118
|
+
* share a mutable object. Only for computations that write none of the files they read.
|
|
119
|
+
*/
|
|
120
|
+
declare function withStateReadCache<T>(fn: () => T): T;
|
|
121
|
+
declare function readStateFile(path: string): string;
|
|
115
122
|
declare function readJsonFile(path: string): unknown;
|
|
116
123
|
declare function isJsonObject(value: unknown): value is Record<string, unknown>;
|
|
117
124
|
declare function readJsonObject(path: string): Record<string, unknown> | null;
|
|
118
125
|
//#endregion
|
|
119
126
|
//#region state/ledgers.d.ts
|
|
120
|
-
type CreatedDevicePlatform = "ios" | "android";
|
|
127
|
+
type CreatedDevicePlatform = "ios" | "android" | "web";
|
|
128
|
+
/** `web` lists the absolute paths of browser profile directories Stim created. */
|
|
121
129
|
interface CreatedDevices {
|
|
122
130
|
ios: ReadonlySet<string>;
|
|
123
131
|
android: ReadonlySet<string>;
|
|
132
|
+
web: ReadonlySet<string>;
|
|
124
133
|
}
|
|
125
134
|
declare function readCreatedDevices(): CreatedDevices;
|
|
126
135
|
interface EasSessionClaim {
|
|
@@ -146,6 +155,8 @@ interface NdjsonRecord {
|
|
|
146
155
|
src?: string;
|
|
147
156
|
level?: string;
|
|
148
157
|
msg?: string;
|
|
158
|
+
/** With `logs --errors --json`: the code frame and stack lines Expo printed after this error. */
|
|
159
|
+
context?: string[];
|
|
149
160
|
[key: string]: unknown;
|
|
150
161
|
}
|
|
151
162
|
declare const LEVELS: string[];
|
|
@@ -167,10 +178,11 @@ interface GrepResult {
|
|
|
167
178
|
interface MarkerWindow {
|
|
168
179
|
launchTs: number | null;
|
|
169
180
|
bundleTs: number | null;
|
|
181
|
+
pageLoadTs: number | null;
|
|
170
182
|
}
|
|
171
183
|
interface QueryCriteria {
|
|
172
184
|
slot?: string;
|
|
173
|
-
|
|
185
|
+
includeAppDeviceErrors?: boolean;
|
|
174
186
|
sources?: string[];
|
|
175
187
|
minLevel?: string;
|
|
176
188
|
grep?: RegExp;
|
|
@@ -202,7 +214,12 @@ declare function buildCriteria({ slot, sources, minLevel, since, grep, errorsOnl
|
|
|
202
214
|
declare function logFiles(dir: string): string[];
|
|
203
215
|
declare function fileSizes(dir: string): Record<string, number>;
|
|
204
216
|
declare function readLogRecords(dir: string, include?: (name: string) => boolean): NdjsonRecord[];
|
|
205
|
-
|
|
217
|
+
type ExpoContextTarget = "msg" | "field";
|
|
218
|
+
/**
|
|
219
|
+
* Adds the code frame and stack lines Expo printed after each Expo error record, either joined into
|
|
220
|
+
* `msg` or as a `context` string array.
|
|
221
|
+
*/
|
|
222
|
+
declare function attachExpoErrorContext(all: NdjsonRecord[], matched: NdjsonRecord[], into?: ExpoContextTarget): NdjsonRecord[];
|
|
206
223
|
declare function queryLogs({ slot, dir, records, sources, minLevel, since, grep, tail, errorsOnly, errorContext, now }?: {
|
|
207
224
|
slot?: string;
|
|
208
225
|
dir?: string;
|
|
@@ -224,12 +241,14 @@ declare function advanceTail(prev: TailState | null | undefined, chunk: string |
|
|
|
224
241
|
state: TailState;
|
|
225
242
|
records: NdjsonRecord[];
|
|
226
243
|
};
|
|
227
|
-
declare function followLogs({ dir, onRecord, criteria, intervalMs, offsets }: {
|
|
244
|
+
declare function followLogs({ dir, onRecord, criteria, intervalMs, offsets, errorContext }: {
|
|
228
245
|
dir: string;
|
|
229
246
|
onRecord: (record: NdjsonRecord) => void;
|
|
230
247
|
criteria?: QueryCriteria;
|
|
231
248
|
intervalMs?: number;
|
|
232
249
|
offsets?: Record<string, number> | null;
|
|
250
|
+
/** Holds each Expo error until a poll adds no more of its code frame or stack lines, then attaches them. */
|
|
251
|
+
errorContext?: ExpoContextTarget;
|
|
233
252
|
}): () => void;
|
|
234
253
|
declare function sortByTs(records: NdjsonRecord[]): NdjsonRecord[];
|
|
235
254
|
//#endregion
|
|
@@ -338,6 +357,8 @@ interface SettingsPayload {
|
|
|
338
357
|
declare const REMOTE_DEVICE_BACKENDS: readonly RemoteDeviceBackend[];
|
|
339
358
|
declare const IOS_SIMULATOR_APPS: readonly ["xcode", "siniulator", "stim-desktop"];
|
|
340
359
|
declare const ANDROID_EMULATOR_APPS: readonly ["emulator", "stim-desktop"];
|
|
360
|
+
declare const WEB_VIEWPORTS: readonly ["desktop", "phone"];
|
|
361
|
+
type WebViewport = (typeof WEB_VIEWPORTS)[number];
|
|
341
362
|
declare const SETTINGS: readonly SettingDefinition[];
|
|
342
363
|
declare function settingDefinition(key: string): SettingDefinition | null;
|
|
343
364
|
declare function isLayeredSetting(setting: SettingDefinition): boolean;
|
|
@@ -386,7 +407,16 @@ interface IdleStopRecord {
|
|
|
386
407
|
declare function readIdleStop(state: WorkspaceState | null | undefined): IdleStopRecord | null;
|
|
387
408
|
/** Each platform's latest run; `lastBuild` holds whichever platform ran last. */
|
|
388
409
|
declare const LAST_BUILD_KEYS: Readonly<Record<StatsPlatform, string>>;
|
|
410
|
+
declare const BUILD_HISTORY_KEY = "buildHistory";
|
|
411
|
+
declare const BUILD_HISTORY_LIMIT = 10;
|
|
412
|
+
/**
|
|
413
|
+
* The first diagnostics of a failed build in the shape a last-build record stores and `status` reports, the
|
|
414
|
+
* ones with a source position first.
|
|
415
|
+
*/
|
|
416
|
+
declare function buildDiagnostics(value: unknown): BuildDiagnostic[];
|
|
389
417
|
declare function readLastBuilds(state: WorkspaceState | null | undefined): Partial<Record<StatsPlatform, LastBuildReport>>;
|
|
418
|
+
/** Each platform's recorded runs, newest first, at most `BUILD_HISTORY_LIMIT` each. */
|
|
419
|
+
declare function readBuildHistory(state: WorkspaceState | null | undefined): Partial<Record<StatsPlatform, BuildHistoryEntry[]>>;
|
|
390
420
|
declare function readWorkspaceState(root: string): WorkspaceState | null;
|
|
391
421
|
declare function lastUseFrom(state: WorkspaceState | null, logMtimes: readonly number[]): number;
|
|
392
422
|
declare function workspaceLastUsed(root: string): number;
|
|
@@ -423,6 +453,31 @@ interface LastBuildReport {
|
|
|
423
453
|
finishedAt: string | null;
|
|
424
454
|
errorCode?: string;
|
|
425
455
|
missReason?: BuildMissReason;
|
|
456
|
+
/** The first compiler diagnostics of a failed build, when the build tool reported any. */
|
|
457
|
+
diagnostics?: BuildDiagnostic[];
|
|
458
|
+
}
|
|
459
|
+
/** How a recorded build ended. `interrupted` is a run whose process ended without recording a result. */
|
|
460
|
+
declare const BUILD_RESULTS: readonly ["succeeded", "failed", "cancelled", "interrupted"];
|
|
461
|
+
type BuildResult = (typeof BUILD_RESULTS)[number];
|
|
462
|
+
/**
|
|
463
|
+
* One run in a workspace's recent build history. `configuration` is the iOS configuration or the Android
|
|
464
|
+
* variant the run built, `Debug` or `debug` by default, and null when the run ended before resolving it.
|
|
465
|
+
* `phases` holds the milliseconds spent in each phase the run entered. An interrupted run has null
|
|
466
|
+
* `durationMs` and `finishedAt`, no cache facts, and 0 for the phase it stopped in.
|
|
467
|
+
*/
|
|
468
|
+
interface BuildHistoryEntry extends LastBuildReport {
|
|
469
|
+
result: BuildResult;
|
|
470
|
+
slot: string;
|
|
471
|
+
configuration: string | null;
|
|
472
|
+
cacheKey: string | null;
|
|
473
|
+
phases: Partial<Record<BuildPhase, number>>;
|
|
474
|
+
}
|
|
475
|
+
/** One compiler error from a failed build: where it is, when the tool said, and its message. */
|
|
476
|
+
interface BuildDiagnostic {
|
|
477
|
+
file: string | null;
|
|
478
|
+
line: number | null;
|
|
479
|
+
column: number | null;
|
|
480
|
+
message: string;
|
|
426
481
|
}
|
|
427
482
|
/** What kind of native input a changed fingerprint source is. */
|
|
428
483
|
type BuildMissCategory = "native-dependency" | "config-plugin" | "app-config" | "app-asset" | "package" | "native-dir" | "autolinking" | "package-scripts" | "file" | "other";
|
|
@@ -483,6 +538,17 @@ interface DeviceActivity {
|
|
|
483
538
|
lastActivityAt?: string;
|
|
484
539
|
basis: string[];
|
|
485
540
|
}
|
|
541
|
+
/** Every state a device's app process can report. */
|
|
542
|
+
declare const APP_PROCESS_STATES: readonly ["running", "stopped", "unknown"];
|
|
543
|
+
/**
|
|
544
|
+
* Whether the workspace's app process is alive on a device now. This is current process state, not launch evidence:
|
|
545
|
+
* `launched` in an `ios` or `android` result stays the record of one launch. `id` is the bundle identifier or
|
|
546
|
+
* package that was checked; `state` is "unknown" when the process listing could not be read.
|
|
547
|
+
*/
|
|
548
|
+
interface DeviceAppProcess {
|
|
549
|
+
id: string;
|
|
550
|
+
state: (typeof APP_PROCESS_STATES)[number];
|
|
551
|
+
}
|
|
486
552
|
/**
|
|
487
553
|
* A linked worktree's `git status`. `changed` counts tracked paths with staged or unstaged changes, including
|
|
488
554
|
* conflicts; `untracked` counts untracked entries as `git status` lists them, so an untracked directory counts once.
|
|
@@ -519,7 +585,7 @@ interface RemoteDeviceState {
|
|
|
519
585
|
webPreviewUrl: string | null;
|
|
520
586
|
}
|
|
521
587
|
/** Every code a status issue can carry. */
|
|
522
|
-
declare const STATUS_ISSUE_CODES: readonly ["port-not-ours", "sim-missing", "sim-without-metro", "avd-serial-changed", "avd-missing", "avd-not-detected", "avd-unchecked", "supervisor-unverified"];
|
|
588
|
+
declare const STATUS_ISSUE_CODES: readonly ["port-not-ours", "sim-missing", "sim-without-metro", "avd-serial-changed", "avd-missing", "avd-not-detected", "avd-unchecked", "supervisor-unverified", "browser-unverified", "browser-orphaned"];
|
|
523
589
|
type StatusIssueCode = (typeof STATUS_ISSUE_CODES)[number];
|
|
524
590
|
/**
|
|
525
591
|
* One thing in a workspace that needs the user. `remedy` is a command to run from `workspace`; `slot` is absent
|
|
@@ -533,6 +599,23 @@ interface StatusIssue {
|
|
|
533
599
|
workspace: string;
|
|
534
600
|
slot?: string;
|
|
535
601
|
}
|
|
602
|
+
/**
|
|
603
|
+
* The workspace's Stim-owned Chrome. `pid` is Chrome's process and `supervisorPid` the Stim process that holds
|
|
604
|
+
* its DevTools session. `cdpEndpoint` is the loopback DevTools HTTP endpoint agents can attach to, null when the
|
|
605
|
+
* browser is not running. `profile` is the Stim-owned user data directory.
|
|
606
|
+
*/
|
|
607
|
+
interface WebBrowserState {
|
|
608
|
+
browser: "chrome";
|
|
609
|
+
version: string | null;
|
|
610
|
+
running: boolean;
|
|
611
|
+
pid: number | null;
|
|
612
|
+
supervisorPid: number | null;
|
|
613
|
+
url: string;
|
|
614
|
+
headless: boolean;
|
|
615
|
+
viewport: WebViewport;
|
|
616
|
+
profile: string;
|
|
617
|
+
cdpEndpoint: string | null;
|
|
618
|
+
}
|
|
536
619
|
interface EnvironmentState {
|
|
537
620
|
slots?: {
|
|
538
621
|
slot: string;
|
|
@@ -541,6 +624,10 @@ interface EnvironmentState {
|
|
|
541
624
|
}[];
|
|
542
625
|
path: string;
|
|
543
626
|
live: boolean;
|
|
627
|
+
/**
|
|
628
|
+
* A fixed estimate, not a measurement: a set amount per booted simulator, detected emulator, running Metro and
|
|
629
|
+
* running Chrome. `capacity.committedMb` sums it. What the workspace's processes use now is in `machine`.
|
|
630
|
+
*/
|
|
544
631
|
memoryMb: number;
|
|
545
632
|
warnings: string[];
|
|
546
633
|
issues: StatusIssue[];
|
|
@@ -550,6 +637,7 @@ interface EnvironmentState {
|
|
|
550
637
|
owned: boolean;
|
|
551
638
|
state: string;
|
|
552
639
|
activity?: DeviceActivity;
|
|
640
|
+
app?: DeviceAppProcess;
|
|
553
641
|
} | null;
|
|
554
642
|
android?: {
|
|
555
643
|
name: string | undefined;
|
|
@@ -559,6 +647,7 @@ interface EnvironmentState {
|
|
|
559
647
|
state?: AndroidRuntimeFacts["state"];
|
|
560
648
|
deviceProfile?: string | null;
|
|
561
649
|
activity?: DeviceActivity;
|
|
650
|
+
app?: DeviceAppProcess;
|
|
562
651
|
} | null;
|
|
563
652
|
metro?: {
|
|
564
653
|
port: number;
|
|
@@ -566,6 +655,7 @@ interface EnvironmentState {
|
|
|
566
655
|
pid: number | null;
|
|
567
656
|
idleStop?: IdleStopRecord;
|
|
568
657
|
} | null;
|
|
658
|
+
web?: WebBrowserState | null;
|
|
569
659
|
supervisor?: {
|
|
570
660
|
pid: number | null;
|
|
571
661
|
mode: string | null;
|
|
@@ -580,7 +670,10 @@ interface EnvironmentState {
|
|
|
580
670
|
remoteDevices?: RemoteDeviceState[];
|
|
581
671
|
build?: BuildReport | null;
|
|
582
672
|
lastBuilds?: Partial<Record<StatsPlatform, LastBuildReport>>;
|
|
673
|
+
/** Each platform's recent runs, newest first, at most `BUILD_HISTORY_LIMIT` each. */
|
|
674
|
+
builds?: Partial<Record<StatsPlatform, BuildHistoryEntry[]>>;
|
|
583
675
|
}
|
|
676
|
+
/** `committedMb` sums the environments' estimated `memoryMb`; `overCapacity` is that sum over 60% of `totalMemoryMb`. */
|
|
584
677
|
interface StatusCapacity {
|
|
585
678
|
liveCount: number;
|
|
586
679
|
committedMb: number;
|
|
@@ -600,6 +693,34 @@ interface DeviceLeaseState {
|
|
|
600
693
|
expired: boolean;
|
|
601
694
|
parsed: boolean;
|
|
602
695
|
}
|
|
696
|
+
/** Every kind of process owner the status machine section reports. */
|
|
697
|
+
declare const MACHINE_OWNER_KINDS: readonly ["simulator", "emulator", "metro", "build", "browser", "server", "shared"];
|
|
698
|
+
type MachineOwnerKind = (typeof MACHINE_OWNER_KINDS)[number];
|
|
699
|
+
/**
|
|
700
|
+
* One thing using this Mac's CPU and memory now. Each process is counted in exactly one owner: the one whose root
|
|
701
|
+
* process is its nearest ancestor. `workspace` and `slot` (absent for the default slot) name the workspace a
|
|
702
|
+
* simulator, emulator, Metro, build or Chrome belongs to; a device no workspace records has a null workspace, and
|
|
703
|
+
* machine-wide processes are `shared`. `id` is the simulator's UDID, the emulator's AVD name, Metro's port as text,
|
|
704
|
+
* the build's platform, or null. `owned` is true only for what Stim started and stops: a workspace's owned device,
|
|
705
|
+
* Metro, build or Chrome. `cpuPercent` is `ps` %CPU summed over the owner's processes, where 100 is one core.
|
|
706
|
+
* `residentMb` sums their resident set sizes; pages shared between processes count once per process, so a
|
|
707
|
+
* simulator, whose processes all map the runtime's shared libraries, reports well above its physical footprint.
|
|
708
|
+
*/
|
|
709
|
+
interface MachineOwner {
|
|
710
|
+
kind: MachineOwnerKind;
|
|
711
|
+
name: string;
|
|
712
|
+
workspace: string | null;
|
|
713
|
+
slot?: string;
|
|
714
|
+
id: string | null;
|
|
715
|
+
owned: boolean;
|
|
716
|
+
cpuPercent: number;
|
|
717
|
+
residentMb: number;
|
|
718
|
+
processes: number;
|
|
719
|
+
}
|
|
720
|
+
/** Where this machine's CPU and resident memory go, from one pass over the host process table. */
|
|
721
|
+
interface MachineUsageState {
|
|
722
|
+
owners: MachineOwner[];
|
|
723
|
+
}
|
|
603
724
|
/** The payload `stim status --json` prints, and `status --watch --json` prints on each change. */
|
|
604
725
|
interface StatusPayload {
|
|
605
726
|
environments: (EnvironmentState & {
|
|
@@ -609,6 +730,8 @@ interface StatusPayload {
|
|
|
609
730
|
deviceLeases: DeviceLeaseState[];
|
|
610
731
|
unprovisionedWorktrees: WorktreeFacts[];
|
|
611
732
|
simctlAvailable: boolean;
|
|
733
|
+
/** Null when nothing runs that status attributes, so it read no process table, or when the table was unreadable. */
|
|
734
|
+
machine: MachineUsageState | null;
|
|
612
735
|
}
|
|
613
736
|
//#endregion
|
|
614
|
-
export { ANDROID_COMPILER_CACHE_CHOICES, ANDROID_EMULATOR_APPS, ANDROID_PCH_CHOICES, ActiveBuildState, ActivityDriver, AndroidRuntimeFacts, BUILD_PHASES, BuildCacheHit, BuildMissCategory, BuildMissChange, BuildMissReason, BuildPhase, BuildPlanPayload, BuildReport, ConcurrencyLimits, Config, CreatedDevicePlatform, CreatedDevices, DeviceActivity, DeviceLeaseState, DeviceRecord, DoctorRunRecord, ERROR_SOURCES, EasSessionClaim, EasSessionLedgerRead, EnvironmentState, IDLE_STOP_KEY, IOS_SIMULATOR_APPS, IdleStopRecord, LAST_BUILD_KEYS, LEVELS, LastBuildReport, NdjsonRecord, PlatformRecords, ProjectRecord, QueryCriteria, REMOTE_DEVICE_BACKENDS, RemoteDeviceBackend, RemoteDeviceState, RepoRecord, RunOutcomeKind, SETTINGS, SETTINGS_SCHEMA_FILE, SETTINGS_SCHEMA_URL, SETTING_GROUPS, SETTING_SCOPES, SOURCES, STATUS_ISSUE_CODES, SettingDefinition, SettingEntry, SettingScope, SettingType, SettingsObject, SettingsPayload, StatsPlatform, StatusCapacity, StatusIssue, StatusIssueCode, StatusPayload, StimConfig, SupervisorRecord, TUNNEL_MODES, TunnelMode, WorkspaceState, WorktreeFacts, WorktreeGit, acceptsShape, advanceTail, allMetroPorts, attachExpoErrorContext, buildCriteria, coerceSettingText, compileGrep, configCorruptRepair, configLockPath, createdDevicesFile, createdDevicesLock, easMachineStateRoot, easSessionLedgerFile, easSessionLedgerLock, emulatorLogFile, expectedShape, fileSizes, findEnclosingWorktreeRoot, findProjectByMetroPort, followLogs, getConcurrencyLimits, getConfigPath, getProject, getProjectSetting, getProjectSettings, getRepoSettings, gitMergeCacheDir, isJsonObject, isLayeredSetting, isPathPrefix, isSensitiveReference, lastUseFrom, levelRank, loadConfig, logFiles, markerWindow, parseNdjsonLine, parseNdjsonText, parseSince, queryLogs, readCreatedDevices, readEasSessionLedger, readIdleStop, readJsonFile, readJsonObject, readLastBuilds, readLogRecords, readNdjsonGenerations, readWorkspaceState, recordMatches, settingDefinition, settingValueError, settingsJsonSchema, sharedCcache, sharedCompilationCache, sharedGradle, sharedPods, sortByTs, supervisorLogFile, supervisorPidFile, tailRead, validEasSessionClaim, withConfigLock, workspaceDerivedData, workspaceGradleBuild, workspaceLastUsed, workspaceLogErrorIndex, workspaceLogsDir, workspaceMetadataFile, workspaceStateFile, workspaceStateLock };
|
|
737
|
+
export { ANDROID_COMPILER_CACHE_CHOICES, ANDROID_EMULATOR_APPS, ANDROID_PCH_CHOICES, APP_PROCESS_STATES, ActiveBuildState, ActivityDriver, AndroidRuntimeFacts, BUILD_HISTORY_KEY, BUILD_HISTORY_LIMIT, BUILD_PHASES, BUILD_RESULTS, BuildCacheHit, BuildDiagnostic, BuildHistoryEntry, BuildMissCategory, BuildMissChange, BuildMissReason, BuildPhase, BuildPlanPayload, BuildReport, BuildResult, ConcurrencyLimits, Config, CreatedDevicePlatform, CreatedDevices, DeviceActivity, DeviceAppProcess, DeviceLeaseState, DeviceRecord, DoctorRunRecord, ERROR_SOURCES, EasSessionClaim, EasSessionLedgerRead, EnvironmentState, IDLE_STOP_KEY, IOS_SIMULATOR_APPS, IdleStopRecord, LAST_BUILD_KEYS, LEVELS, LastBuildReport, MACHINE_OWNER_KINDS, MachineOwner, MachineOwnerKind, MachineUsageState, NdjsonRecord, PlatformRecords, ProjectRecord, QueryCriteria, REMOTE_DEVICE_BACKENDS, RemoteDeviceBackend, RemoteDeviceState, RepoRecord, RunOutcomeKind, SETTINGS, SETTINGS_SCHEMA_FILE, SETTINGS_SCHEMA_URL, SETTING_GROUPS, SETTING_SCOPES, SOURCES, STATUS_ISSUE_CODES, SettingDefinition, SettingEntry, SettingScope, SettingType, SettingsObject, SettingsPayload, StatsPlatform, StatusCapacity, StatusIssue, StatusIssueCode, StatusPayload, StimConfig, SupervisorRecord, TUNNEL_MODES, TunnelMode, WEB_VIEWPORTS, WebBrowserState, WebViewport, WorkspaceState, WorktreeFacts, WorktreeGit, acceptsShape, advanceTail, allMetroPorts, attachExpoErrorContext, buildCriteria, buildDiagnostics, coerceSettingText, compileGrep, configCorruptRepair, configLockPath, createdDevicesFile, createdDevicesLock, easMachineStateRoot, easSessionLedgerFile, easSessionLedgerLock, emulatorLogFile, expectedShape, fileSizes, findEnclosingWorktreeRoot, findProjectByMetroPort, followLogs, getConcurrencyLimits, getConfigPath, getProject, getProjectSetting, getProjectSettings, getRepoSettings, gitMergeCacheDir, isJsonObject, isLayeredSetting, isPathPrefix, isSensitiveReference, lastUseFrom, levelRank, loadConfig, logFiles, markerWindow, parseNdjsonLine, parseNdjsonText, parseSince, queryLogs, readBuildHistory, readCreatedDevices, readEasSessionLedger, readIdleStop, readJsonFile, readJsonObject, readLastBuilds, readLogRecords, readNdjsonGenerations, readStateFile, readWorkspaceState, recordMatches, settingDefinition, settingValueError, settingsJsonSchema, sharedCcache, sharedCompilationCache, sharedGradle, sharedPods, sortByTs, supervisorLogFile, supervisorPidFile, tailRead, validEasSessionClaim, withConfigLock, withStateReadCache, workspaceDerivedData, workspaceGradleBuild, workspaceLastUsed, workspaceLogErrorIndex, workspaceLogsDir, workspaceMetadataFile, workspaceStateFile, workspaceStateLock };
|
package/dist/state.mjs
CHANGED
|
@@ -1,11 +1,38 @@
|
|
|
1
|
-
import { E as withDirLock, S as workspaceStateDir, b as workspaceName, l as configDir, m as rotatedLogPath } from "./core-
|
|
1
|
+
import { E as withDirLock, S as workspaceStateDir, b as workspaceName, l as configDir, m as rotatedLogPath } from "./core-CmZJEFj_.mjs";
|
|
2
2
|
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, readSync, readdirSync, statSync } from "node:fs";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
5
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
5
6
|
import { StringDecoder } from "node:string_decoder";
|
|
6
7
|
//#region state/json-file.ts
|
|
8
|
+
const readScope = new AsyncLocalStorage();
|
|
9
|
+
/**
|
|
10
|
+
* Runs `fn` with each state file read from disk at most once: inside it, including after an `await`, `readStateFile`
|
|
11
|
+
* returns the text of the first read of that path, and a path found missing stays missing. Callers still parse every time, so no two
|
|
12
|
+
* share a mutable object. Only for computations that write none of the files they read.
|
|
13
|
+
*/
|
|
14
|
+
function withStateReadCache(fn) {
|
|
15
|
+
return readScope.run(/* @__PURE__ */ new Map(), fn);
|
|
16
|
+
}
|
|
17
|
+
function readStateFile(path) {
|
|
18
|
+
const cache = readScope.getStore();
|
|
19
|
+
if (!cache) return readFileSync(path, "utf-8");
|
|
20
|
+
const cached = cache.get(path);
|
|
21
|
+
if (cached) {
|
|
22
|
+
if ("missing" in cached) throw cached.missing;
|
|
23
|
+
return cached.text;
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
const text = readFileSync(path, "utf-8");
|
|
27
|
+
cache.set(path, { text });
|
|
28
|
+
return text;
|
|
29
|
+
} catch (error) {
|
|
30
|
+
if (error.code === "ENOENT") cache.set(path, { missing: error });
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
7
34
|
function readJsonFile(path) {
|
|
8
|
-
return JSON.parse(
|
|
35
|
+
return JSON.parse(readStateFile(path));
|
|
9
36
|
}
|
|
10
37
|
function isJsonObject(value) {
|
|
11
38
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -106,11 +133,12 @@ function configCorrupt(reason, path = getConfigPath()) {
|
|
|
106
133
|
}
|
|
107
134
|
function loadConfig() {
|
|
108
135
|
const p = getConfigPath();
|
|
109
|
-
if (!existsSync(p)) return null;
|
|
110
136
|
let parsed;
|
|
111
137
|
try {
|
|
112
138
|
parsed = readJsonFile(p);
|
|
113
139
|
} catch (err) {
|
|
140
|
+
const code = err.code;
|
|
141
|
+
if (code === "ENOENT" || code === "ENOTDIR") return null;
|
|
114
142
|
if (!(err instanceof SyntaxError)) throw err;
|
|
115
143
|
throw configCorrupt(`is not valid JSON: ${err.message}`, p);
|
|
116
144
|
}
|
|
@@ -211,15 +239,17 @@ function ids(value) {
|
|
|
211
239
|
}
|
|
212
240
|
function readCreatedDevices() {
|
|
213
241
|
try {
|
|
214
|
-
const parsed = JSON.parse(
|
|
242
|
+
const parsed = JSON.parse(readStateFile(createdDevicesFile()));
|
|
215
243
|
return {
|
|
216
244
|
ios: new Set(ids(parsed?.ios)),
|
|
217
|
-
android: new Set(ids(parsed?.android))
|
|
245
|
+
android: new Set(ids(parsed?.android)),
|
|
246
|
+
web: new Set(ids(parsed?.web))
|
|
218
247
|
};
|
|
219
248
|
} catch {
|
|
220
249
|
return {
|
|
221
250
|
ios: /* @__PURE__ */ new Set(),
|
|
222
|
-
android: /* @__PURE__ */ new Set()
|
|
251
|
+
android: /* @__PURE__ */ new Set(),
|
|
252
|
+
web: /* @__PURE__ */ new Set()
|
|
223
253
|
};
|
|
224
254
|
}
|
|
225
255
|
}
|
|
@@ -380,24 +410,34 @@ const ERROR_SOURCES = [
|
|
|
380
410
|
function markerWindow(records) {
|
|
381
411
|
let launchTs = null;
|
|
382
412
|
let bundleTs = null;
|
|
413
|
+
let pageLoadTs = null;
|
|
383
414
|
for (const r of records) {
|
|
384
415
|
if (r?.marker !== true) continue;
|
|
385
416
|
const ts = tsOf(r);
|
|
386
417
|
if (ts === null) continue;
|
|
387
418
|
if (r.src === "metro") {
|
|
388
419
|
if (bundleTs === null || ts > bundleTs) bundleTs = ts;
|
|
420
|
+
} else if (isWebPageRecord(r)) {
|
|
421
|
+
if (pageLoadTs === null || ts > pageLoadTs) pageLoadTs = ts;
|
|
389
422
|
} else if (launchTs === null || ts > launchTs) launchTs = ts;
|
|
390
423
|
}
|
|
391
424
|
return {
|
|
392
425
|
launchTs,
|
|
393
|
-
bundleTs
|
|
426
|
+
bundleTs,
|
|
427
|
+
pageLoadTs
|
|
394
428
|
};
|
|
395
429
|
}
|
|
430
|
+
function isWebPageRecord(record) {
|
|
431
|
+
return record.platform === "web" && record.src !== "metro";
|
|
432
|
+
}
|
|
433
|
+
function isAppDeviceRecord(record) {
|
|
434
|
+
return record.src === "device" && (record.event === "native_crash" || record.platform === "web");
|
|
435
|
+
}
|
|
396
436
|
function recordMatches(record, criteria = {}) {
|
|
397
437
|
if (!record) return false;
|
|
398
438
|
if (criteria.slot !== void 0 && (record.slot ?? "default") !== criteria.slot) return false;
|
|
399
439
|
const { sources, minLevel, grep, sinceTs, errorsOnly, markerTs, bundleMarkerTs } = criteria;
|
|
400
|
-
if (sources && sources.length > 0 && !sources.includes(record.src) && !(criteria.
|
|
440
|
+
if (sources && sources.length > 0 && !sources.includes(record.src) && !(criteria.includeAppDeviceErrors && isAppDeviceRecord(record))) return false;
|
|
401
441
|
if (minLevel && levelRank(record.level) < levelRank(minLevel)) return false;
|
|
402
442
|
if (errorsOnly) {
|
|
403
443
|
if (record.level !== "error" && record.level !== "fatal") return false;
|
|
@@ -424,7 +464,7 @@ function buildCriteria({ slot, sources, minLevel, since, grep, errorsOnly, marke
|
|
|
424
464
|
const criteria = {
|
|
425
465
|
...slot === void 0 ? {} : { slot },
|
|
426
466
|
errorsOnly: Boolean(errorsOnly),
|
|
427
|
-
|
|
467
|
+
includeAppDeviceErrors: Boolean(errorsOnly && !sources?.length)
|
|
428
468
|
};
|
|
429
469
|
if (sources && sources.length > 0) criteria.sources = sources;
|
|
430
470
|
else if (criteria.errorsOnly) criteria.sources = ERROR_SOURCES;
|
|
@@ -468,24 +508,86 @@ function isExpoErrorContext(record, event) {
|
|
|
468
508
|
if (record.src !== "metro" || record.raw !== true || record.event !== event || typeof record.msg !== "string") return false;
|
|
469
509
|
return /^Code: \S/.test(record.msg) || /^\s*>?\s*\d+\s*\|/.test(record.msg) || /^\s*\|\s*\^/.test(record.msg) || /^Call Stack$/.test(record.msg) || /^\s+.+\([^()]+:\d+:\d+\)\s*$/.test(record.msg) || /^\s+at\s+\S+:\d+:\d+\s*$/.test(record.msg);
|
|
470
510
|
}
|
|
471
|
-
function
|
|
511
|
+
function isExpoError(record) {
|
|
512
|
+
return record.src === "metro" && record.raw === true && (record.level === "error" || record.level === "fatal") && typeof record.msg === "string";
|
|
513
|
+
}
|
|
514
|
+
function isSameExpoStream(record, error) {
|
|
515
|
+
return record.src === "metro" && record.raw === true && record.event === error.event;
|
|
516
|
+
}
|
|
517
|
+
function withExpoContext(record, context, into) {
|
|
518
|
+
if (context.length === 0) return record;
|
|
519
|
+
return into === "msg" ? {
|
|
520
|
+
...record,
|
|
521
|
+
msg: [record.msg, ...context].join("\n")
|
|
522
|
+
} : {
|
|
523
|
+
...record,
|
|
524
|
+
context
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* Adds the code frame and stack lines Expo printed after each Expo error record, either joined into
|
|
529
|
+
* `msg` or as a `context` string array.
|
|
530
|
+
*/
|
|
531
|
+
function attachExpoErrorContext(all, matched, into = "msg") {
|
|
472
532
|
return matched.map((record) => {
|
|
473
|
-
if (record
|
|
533
|
+
if (!isExpoError(record)) return record;
|
|
474
534
|
const index = all.findIndex((entry) => entry.ts === record.ts && entry.src === record.src && entry.msg === record.msg && entry.event === record.event);
|
|
475
535
|
if (index === -1) return record;
|
|
476
536
|
const context = [];
|
|
477
537
|
for (let i = index + 1; i < all.length; i += 1) {
|
|
478
538
|
const next = all[i];
|
|
479
|
-
if (next
|
|
539
|
+
if (!isSameExpoStream(next, record)) continue;
|
|
480
540
|
if (!isExpoErrorContext(next, record.event)) break;
|
|
481
541
|
context.push(next.msg);
|
|
482
542
|
}
|
|
483
|
-
return context
|
|
484
|
-
...record,
|
|
485
|
-
msg: [record.msg, ...context].join("\n")
|
|
486
|
-
} : record;
|
|
543
|
+
return withExpoContext(record, context, into);
|
|
487
544
|
});
|
|
488
545
|
}
|
|
546
|
+
function expoContextFollower(onRecord, into) {
|
|
547
|
+
let pending = null;
|
|
548
|
+
let grew = false;
|
|
549
|
+
function flush() {
|
|
550
|
+
if (!pending) return;
|
|
551
|
+
const { record, context, queued } = pending;
|
|
552
|
+
pending = null;
|
|
553
|
+
onRecord(withExpoContext(record, context, into));
|
|
554
|
+
for (const next of queued) onRecord(next);
|
|
555
|
+
}
|
|
556
|
+
function push(record, matches) {
|
|
557
|
+
if (pending && isSameExpoStream(record, pending.record)) {
|
|
558
|
+
if (!isExpoErrorContext(record, pending.record.event)) flush();
|
|
559
|
+
else {
|
|
560
|
+
pending.context.push(record.msg);
|
|
561
|
+
if (matches) pending.queued.push(record);
|
|
562
|
+
grew = true;
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
if (pending) {
|
|
567
|
+
if (matches) pending.queued.push(record);
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
if (matches && isExpoError(record)) {
|
|
571
|
+
pending = {
|
|
572
|
+
record,
|
|
573
|
+
context: [],
|
|
574
|
+
queued: []
|
|
575
|
+
};
|
|
576
|
+
grew = true;
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
if (matches) onRecord(record);
|
|
580
|
+
}
|
|
581
|
+
function settle() {
|
|
582
|
+
if (!grew) flush();
|
|
583
|
+
grew = false;
|
|
584
|
+
}
|
|
585
|
+
return {
|
|
586
|
+
push,
|
|
587
|
+
settle,
|
|
588
|
+
flush
|
|
589
|
+
};
|
|
590
|
+
}
|
|
489
591
|
function includeBareErrorContext(all, matched, launchTs, sinceTs) {
|
|
490
592
|
if (!matched.some((record) => record.src === "client" && (record.level === "error" || record.level === "fatal"))) return matched;
|
|
491
593
|
const renderedContext = [];
|
|
@@ -505,7 +607,7 @@ function includeBareErrorContext(all, matched, launchTs, sinceTs) {
|
|
|
505
607
|
function launchMarkersBySlot(records) {
|
|
506
608
|
const markers = /* @__PURE__ */ new Map();
|
|
507
609
|
for (const record of records) {
|
|
508
|
-
if (record.marker !== true || record.src === "metro") continue;
|
|
610
|
+
if (record.marker !== true || record.src === "metro" || isWebPageRecord(record)) continue;
|
|
509
611
|
const ts = tsOf(record);
|
|
510
612
|
const slot = record.slot ?? "default";
|
|
511
613
|
if (ts !== null && ts > (markers.get(slot) ?? -Infinity)) markers.set(slot, ts);
|
|
@@ -515,9 +617,10 @@ function launchMarkersBySlot(records) {
|
|
|
515
617
|
function queryLogs({ slot, dir, records, sources, minLevel, since, grep, tail, errorsOnly, errorContext, now } = {}) {
|
|
516
618
|
const all = (records ?? readLogRecords(dir)).filter((record) => slot === void 0 || (record.slot ?? "default") === slot);
|
|
517
619
|
if (all.length === 0) return [];
|
|
518
|
-
const { launchTs, bundleTs } = errorsOnly ? markerWindow(all) : {
|
|
620
|
+
const { launchTs, bundleTs, pageLoadTs } = errorsOnly ? markerWindow(all) : {
|
|
519
621
|
launchTs: null,
|
|
520
|
-
bundleTs: null
|
|
622
|
+
bundleTs: null,
|
|
623
|
+
pageLoadTs: null
|
|
521
624
|
};
|
|
522
625
|
const criteria = buildCriteria({
|
|
523
626
|
slot,
|
|
@@ -531,9 +634,14 @@ function queryLogs({ slot, dir, records, sources, minLevel, since, grep, tail, e
|
|
|
531
634
|
bundleMarkerTs: bundleTs === null ? void 0 : bundleTs
|
|
532
635
|
});
|
|
533
636
|
const slotMarkers = launchMarkersBySlot(all);
|
|
637
|
+
const windowStart = (record) => {
|
|
638
|
+
if (pageLoadTs !== null && isWebPageRecord(record)) return pageLoadTs - 1;
|
|
639
|
+
if (record.src === "metro" || record.src === "client") return criteria.markerTs;
|
|
640
|
+
return slotMarkers.get(record.slot ?? "default");
|
|
641
|
+
};
|
|
534
642
|
let matched = all.filter((record) => recordMatches(record, {
|
|
535
643
|
...criteria,
|
|
536
|
-
markerTs:
|
|
644
|
+
markerTs: windowStart(record)
|
|
537
645
|
}));
|
|
538
646
|
if (typeof tail === "number" && tail >= 0 && matched.length > tail) matched = matched.slice(matched.length - tail);
|
|
539
647
|
return errorContext ? includeBareErrorContext(all, attachExpoErrorContext(all, matched), launchTs, criteria.sinceTs) : matched;
|
|
@@ -571,7 +679,8 @@ function advanceTail(prev, chunk, size) {
|
|
|
571
679
|
records
|
|
572
680
|
};
|
|
573
681
|
}
|
|
574
|
-
function followLogs({ dir, onRecord, criteria = {}, intervalMs = 500, offsets = null }) {
|
|
682
|
+
function followLogs({ dir, onRecord, criteria = {}, intervalMs = 500, offsets = null, errorContext }) {
|
|
683
|
+
const follower = errorContext ? expoContextFollower(onRecord, errorContext) : null;
|
|
575
684
|
const state = /* @__PURE__ */ new Map();
|
|
576
685
|
const decoders = /* @__PURE__ */ new Map();
|
|
577
686
|
for (const [name, size] of Object.entries(offsets || fileSizes(dir))) {
|
|
@@ -582,7 +691,11 @@ function followLogs({ dir, onRecord, criteria = {}, intervalMs = 500, offsets =
|
|
|
582
691
|
decoders.set(name, new StringDecoder("utf8"));
|
|
583
692
|
}
|
|
584
693
|
function emit(records) {
|
|
585
|
-
for (const record of records)
|
|
694
|
+
for (const record of records) {
|
|
695
|
+
const matches = recordMatches(record, criteria);
|
|
696
|
+
if (follower) follower.push(record, matches);
|
|
697
|
+
else if (matches) onRecord(record);
|
|
698
|
+
}
|
|
586
699
|
}
|
|
587
700
|
function drainRotated(name, entry) {
|
|
588
701
|
const path = rotatedLogPath(join(dir, name));
|
|
@@ -611,10 +724,12 @@ function followLogs({ dir, onRecord, criteria = {}, intervalMs = 500, offsets =
|
|
|
611
724
|
for (const name of logFiles(dir)) try {
|
|
612
725
|
pollFile(name);
|
|
613
726
|
} catch {}
|
|
727
|
+
follower?.settle();
|
|
614
728
|
}
|
|
615
729
|
const timer = setInterval(poll, intervalMs);
|
|
616
730
|
return function stop() {
|
|
617
731
|
clearInterval(timer);
|
|
732
|
+
follower?.flush();
|
|
618
733
|
};
|
|
619
734
|
}
|
|
620
735
|
function readChunk(path, decoder, start, size) {
|
|
@@ -674,6 +789,7 @@ const IOS_SIMULATOR_APPS = [
|
|
|
674
789
|
"stim-desktop"
|
|
675
790
|
];
|
|
676
791
|
const ANDROID_EMULATOR_APPS = ["emulator", "stim-desktop"];
|
|
792
|
+
const WEB_VIEWPORTS = ["desktop", "phone"];
|
|
677
793
|
const PROJECT = [
|
|
678
794
|
"workspace",
|
|
679
795
|
"repo",
|
|
@@ -923,6 +1039,33 @@ const SETTINGS = [
|
|
|
923
1039
|
default: 60,
|
|
924
1040
|
description: "Minutes without a bundle request, client log or Stim command before the dev server stops; 0 never"
|
|
925
1041
|
},
|
|
1042
|
+
{
|
|
1043
|
+
key: "web.url",
|
|
1044
|
+
type: {
|
|
1045
|
+
kind: "string",
|
|
1046
|
+
pattern: "^https?://",
|
|
1047
|
+
patternHelp: "an http:// or https:// URL; {port:<label>} is replaced by a named or the Metro port"
|
|
1048
|
+
},
|
|
1049
|
+
scopes: PROJECT,
|
|
1050
|
+
description: "Page `stim web` opens; unset opens the Metro URL for Expo web"
|
|
1051
|
+
},
|
|
1052
|
+
{
|
|
1053
|
+
key: "web.ignoreCertificateErrors",
|
|
1054
|
+
type: BOOLEAN,
|
|
1055
|
+
scopes: PROJECT,
|
|
1056
|
+
default: false,
|
|
1057
|
+
description: "Accept self-signed dev certificates in Stim's owned Chrome profile"
|
|
1058
|
+
},
|
|
1059
|
+
{
|
|
1060
|
+
key: "web.viewport",
|
|
1061
|
+
type: {
|
|
1062
|
+
kind: "choice",
|
|
1063
|
+
choices: WEB_VIEWPORTS
|
|
1064
|
+
},
|
|
1065
|
+
scopes: PROJECT,
|
|
1066
|
+
default: "desktop",
|
|
1067
|
+
description: "Viewport of the owned Chrome page: desktop, or phone for a 390x844 touch screen"
|
|
1068
|
+
},
|
|
926
1069
|
{
|
|
927
1070
|
key: "worktree.exclude",
|
|
928
1071
|
type: { kind: "strings" },
|
|
@@ -1027,7 +1170,7 @@ const SETTINGS = [
|
|
|
1027
1170
|
scopes: MACHINE,
|
|
1028
1171
|
default: 120,
|
|
1029
1172
|
env: "STIM_GC_WORKTREE_GRACE_MINUTES",
|
|
1030
|
-
description: "Minutes after a linked worktree last changed, or its branch
|
|
1173
|
+
description: "Minutes after a linked worktree last changed, or its branch or pull request finished, before gc removes it; 0 removes it at once"
|
|
1031
1174
|
},
|
|
1032
1175
|
{
|
|
1033
1176
|
key: "pool.iosParkedMax",
|
|
@@ -1259,6 +1402,19 @@ const BUILD_PHASES = [
|
|
|
1259
1402
|
"install",
|
|
1260
1403
|
"launch"
|
|
1261
1404
|
];
|
|
1405
|
+
/** How a recorded build ended. `interrupted` is a run whose process ended without recording a result. */
|
|
1406
|
+
const BUILD_RESULTS = [
|
|
1407
|
+
"succeeded",
|
|
1408
|
+
"failed",
|
|
1409
|
+
"cancelled",
|
|
1410
|
+
"interrupted"
|
|
1411
|
+
];
|
|
1412
|
+
/** Every state a device's app process can report. */
|
|
1413
|
+
const APP_PROCESS_STATES = [
|
|
1414
|
+
"running",
|
|
1415
|
+
"stopped",
|
|
1416
|
+
"unknown"
|
|
1417
|
+
];
|
|
1262
1418
|
/** Every code a status issue can carry. */
|
|
1263
1419
|
const STATUS_ISSUE_CODES = [
|
|
1264
1420
|
"port-not-ours",
|
|
@@ -1268,7 +1424,19 @@ const STATUS_ISSUE_CODES = [
|
|
|
1268
1424
|
"avd-missing",
|
|
1269
1425
|
"avd-not-detected",
|
|
1270
1426
|
"avd-unchecked",
|
|
1271
|
-
"supervisor-unverified"
|
|
1427
|
+
"supervisor-unverified",
|
|
1428
|
+
"browser-unverified",
|
|
1429
|
+
"browser-orphaned"
|
|
1430
|
+
];
|
|
1431
|
+
/** Every kind of process owner the status machine section reports. */
|
|
1432
|
+
const MACHINE_OWNER_KINDS = [
|
|
1433
|
+
"simulator",
|
|
1434
|
+
"emulator",
|
|
1435
|
+
"metro",
|
|
1436
|
+
"build",
|
|
1437
|
+
"browser",
|
|
1438
|
+
"server",
|
|
1439
|
+
"shared"
|
|
1272
1440
|
];
|
|
1273
1441
|
//#endregion
|
|
1274
1442
|
//#region state/workspace-state.ts
|
|
@@ -1287,6 +1455,8 @@ const LAST_BUILD_KEYS = {
|
|
|
1287
1455
|
ios: "lastIosBuild",
|
|
1288
1456
|
android: "lastAndroidBuild"
|
|
1289
1457
|
};
|
|
1458
|
+
const BUILD_HISTORY_KEY = "buildHistory";
|
|
1459
|
+
const BUILD_HISTORY_LIMIT = 10;
|
|
1290
1460
|
const MISS_KINDS = /* @__PURE__ */ new Set([
|
|
1291
1461
|
"changed",
|
|
1292
1462
|
"no-baseline",
|
|
@@ -1335,6 +1505,25 @@ function missReason(value) {
|
|
|
1335
1505
|
rekeyedBy: Array.isArray(record.rekeyedBy) ? record.rekeyedBy.filter((step) => typeof step === "string") : []
|
|
1336
1506
|
};
|
|
1337
1507
|
}
|
|
1508
|
+
const DIAGNOSTIC_CAP = 5;
|
|
1509
|
+
const positive = (value) => typeof value === "number" && Number.isInteger(value) && value > 0 ? value : null;
|
|
1510
|
+
/**
|
|
1511
|
+
* The first diagnostics of a failed build in the shape a last-build record stores and `status` reports, the
|
|
1512
|
+
* ones with a source position first.
|
|
1513
|
+
*/
|
|
1514
|
+
function buildDiagnostics(value) {
|
|
1515
|
+
return (Array.isArray(value) ? value : []).flatMap((item) => {
|
|
1516
|
+
if (!item || typeof item !== "object") return [];
|
|
1517
|
+
const { file, line, column, message } = item;
|
|
1518
|
+
if (typeof message !== "string" || message === "") return [];
|
|
1519
|
+
return [{
|
|
1520
|
+
file: typeof file === "string" && file ? file : null,
|
|
1521
|
+
line: positive(line),
|
|
1522
|
+
column: positive(column),
|
|
1523
|
+
message
|
|
1524
|
+
}];
|
|
1525
|
+
}).toSorted((a, b) => Number(b.file !== null) - Number(a.file !== null)).slice(0, DIAGNOSTIC_CAP);
|
|
1526
|
+
}
|
|
1338
1527
|
function lastBuildReport(platform, value) {
|
|
1339
1528
|
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
1340
1529
|
const record = value;
|
|
@@ -1343,6 +1532,7 @@ function lastBuildReport(platform, value) {
|
|
|
1343
1532
|
const durationMs = typeof record.durationMs === "number" && record.durationMs >= 0 ? record.durationMs : null;
|
|
1344
1533
|
const finished = new Date(Date.parse(record.startedAt) + (durationMs ?? NaN));
|
|
1345
1534
|
const reason = record.cacheHit === "local" || record.cacheHit === "remote" ? null : missReason(record.missReason);
|
|
1535
|
+
const diagnostics = record.status === "failed" ? buildDiagnostics(record.diagnostics) : [];
|
|
1346
1536
|
return {
|
|
1347
1537
|
platform,
|
|
1348
1538
|
status: record.status,
|
|
@@ -1353,7 +1543,8 @@ function lastBuildReport(platform, value) {
|
|
|
1353
1543
|
startedAt: record.startedAt,
|
|
1354
1544
|
finishedAt: Number.isNaN(finished.getTime()) ? null : finished.toISOString(),
|
|
1355
1545
|
...typeof record.errorCode === "string" ? { errorCode: record.errorCode } : {},
|
|
1356
|
-
...reason ? { missReason: reason } : {}
|
|
1546
|
+
...reason ? { missReason: reason } : {},
|
|
1547
|
+
...diagnostics.length ? { diagnostics } : {}
|
|
1357
1548
|
};
|
|
1358
1549
|
}
|
|
1359
1550
|
function readLastBuilds(state) {
|
|
@@ -1364,6 +1555,41 @@ function readLastBuilds(state) {
|
|
|
1364
1555
|
}
|
|
1365
1556
|
return reports;
|
|
1366
1557
|
}
|
|
1558
|
+
function historyPhases(value) {
|
|
1559
|
+
const phases = {};
|
|
1560
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return phases;
|
|
1561
|
+
for (const phase of BUILD_PHASES) {
|
|
1562
|
+
const ms = value[phase];
|
|
1563
|
+
if (typeof ms === "number" && Number.isFinite(ms) && ms >= 0) phases[phase] = Math.round(ms);
|
|
1564
|
+
}
|
|
1565
|
+
return phases;
|
|
1566
|
+
}
|
|
1567
|
+
function historyEntry(platform, value) {
|
|
1568
|
+
const report = lastBuildReport(platform, value);
|
|
1569
|
+
if (!report) return null;
|
|
1570
|
+
const record = value;
|
|
1571
|
+
const result = BUILD_RESULTS.includes(record.result) ? record.result : report.status === "ok" ? "succeeded" : "failed";
|
|
1572
|
+
return {
|
|
1573
|
+
...report,
|
|
1574
|
+
result,
|
|
1575
|
+
slot: typeof record.slot === "string" && record.slot ? record.slot : "default",
|
|
1576
|
+
configuration: typeof record.configuration === "string" ? record.configuration : null,
|
|
1577
|
+
cacheKey: typeof record.cacheKey === "string" ? record.cacheKey : null,
|
|
1578
|
+
phases: historyPhases(record.phases)
|
|
1579
|
+
};
|
|
1580
|
+
}
|
|
1581
|
+
/** Each platform's recorded runs, newest first, at most `BUILD_HISTORY_LIMIT` each. */
|
|
1582
|
+
function readBuildHistory(state) {
|
|
1583
|
+
const history = state?.[BUILD_HISTORY_KEY];
|
|
1584
|
+
const builds = {};
|
|
1585
|
+
for (const platform of ["ios", "android"]) {
|
|
1586
|
+
const list = history?.[platform];
|
|
1587
|
+
if (!Array.isArray(list)) continue;
|
|
1588
|
+
const entries = list.slice(0, 10).map((value) => historyEntry(platform, value)).filter((entry) => entry !== null);
|
|
1589
|
+
if (entries.length) builds[platform] = entries;
|
|
1590
|
+
}
|
|
1591
|
+
return builds;
|
|
1592
|
+
}
|
|
1367
1593
|
function readWorkspaceState(root) {
|
|
1368
1594
|
return readJsonObject(workspaceStateFile(root));
|
|
1369
1595
|
}
|
|
@@ -1391,4 +1617,4 @@ function workspaceLastUsed(root) {
|
|
|
1391
1617
|
return lastUseFrom(readWorkspaceState(root), mtimes);
|
|
1392
1618
|
}
|
|
1393
1619
|
//#endregion
|
|
1394
|
-
export { ANDROID_COMPILER_CACHE_CHOICES, ANDROID_EMULATOR_APPS, ANDROID_PCH_CHOICES, BUILD_PHASES, ERROR_SOURCES, IDLE_STOP_KEY, IOS_SIMULATOR_APPS, LAST_BUILD_KEYS, LEVELS, REMOTE_DEVICE_BACKENDS, SETTINGS, SETTINGS_SCHEMA_FILE, SETTINGS_SCHEMA_URL, SETTING_GROUPS, SETTING_SCOPES, SOURCES, STATUS_ISSUE_CODES, TUNNEL_MODES, acceptsShape, advanceTail, allMetroPorts, attachExpoErrorContext, buildCriteria, coerceSettingText, compileGrep, configCorruptRepair, configLockPath, createdDevicesFile, createdDevicesLock, easMachineStateRoot, easSessionLedgerFile, easSessionLedgerLock, emulatorLogFile, expectedShape, fileSizes, findEnclosingWorktreeRoot, findProjectByMetroPort, followLogs, getConcurrencyLimits, getConfigPath, getProject, getProjectSetting, getProjectSettings, getRepoSettings, gitMergeCacheDir, isJsonObject, isLayeredSetting, isPathPrefix, isSensitiveReference, lastUseFrom, levelRank, loadConfig, logFiles, markerWindow, parseNdjsonLine, parseNdjsonText, parseSince, queryLogs, readCreatedDevices, readEasSessionLedger, readIdleStop, readJsonFile, readJsonObject, readLastBuilds, readLogRecords, readNdjsonGenerations, readWorkspaceState, recordMatches, settingDefinition, settingValueError, settingsJsonSchema, sharedCcache, sharedCompilationCache, sharedGradle, sharedPods, sortByTs, supervisorLogFile, supervisorPidFile, tailRead, validEasSessionClaim, withConfigLock, workspaceDerivedData, workspaceGradleBuild, workspaceLastUsed, workspaceLogErrorIndex, workspaceLogsDir, workspaceMetadataFile, workspaceStateFile, workspaceStateLock };
|
|
1620
|
+
export { ANDROID_COMPILER_CACHE_CHOICES, ANDROID_EMULATOR_APPS, ANDROID_PCH_CHOICES, APP_PROCESS_STATES, BUILD_HISTORY_KEY, BUILD_HISTORY_LIMIT, BUILD_PHASES, BUILD_RESULTS, ERROR_SOURCES, IDLE_STOP_KEY, IOS_SIMULATOR_APPS, LAST_BUILD_KEYS, LEVELS, MACHINE_OWNER_KINDS, REMOTE_DEVICE_BACKENDS, SETTINGS, SETTINGS_SCHEMA_FILE, SETTINGS_SCHEMA_URL, SETTING_GROUPS, SETTING_SCOPES, SOURCES, STATUS_ISSUE_CODES, TUNNEL_MODES, WEB_VIEWPORTS, acceptsShape, advanceTail, allMetroPorts, attachExpoErrorContext, buildCriteria, buildDiagnostics, coerceSettingText, compileGrep, configCorruptRepair, configLockPath, createdDevicesFile, createdDevicesLock, easMachineStateRoot, easSessionLedgerFile, easSessionLedgerLock, emulatorLogFile, expectedShape, fileSizes, findEnclosingWorktreeRoot, findProjectByMetroPort, followLogs, getConcurrencyLimits, getConfigPath, getProject, getProjectSetting, getProjectSettings, getRepoSettings, gitMergeCacheDir, isJsonObject, isLayeredSetting, isPathPrefix, isSensitiveReference, lastUseFrom, levelRank, loadConfig, logFiles, markerWindow, parseNdjsonLine, parseNdjsonText, parseSince, queryLogs, readBuildHistory, readCreatedDevices, readEasSessionLedger, readIdleStop, readJsonFile, readJsonObject, readLastBuilds, readLogRecords, readNdjsonGenerations, readStateFile, readWorkspaceState, recordMatches, settingDefinition, settingValueError, settingsJsonSchema, sharedCcache, sharedCompilationCache, sharedGradle, sharedPods, sortByTs, supervisorLogFile, supervisorPidFile, tailRead, validEasSessionClaim, withConfigLock, withStateReadCache, workspaceDerivedData, workspaceGradleBuild, workspaceLastUsed, workspaceLogErrorIndex, workspaceLogsDir, workspaceMetadataFile, workspaceStateFile, workspaceStateLock };
|