@stim-cli/core 1.11.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 +154 -6
- package/dist/state.mjs +273 -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
|
@@ -25,6 +25,7 @@ interface AndroidDeviceRecord {
|
|
|
25
25
|
deviceName?: string | null;
|
|
26
26
|
owned?: boolean;
|
|
27
27
|
setupIncomplete?: boolean;
|
|
28
|
+
bootPending?: boolean;
|
|
28
29
|
[key: string]: unknown;
|
|
29
30
|
}
|
|
30
31
|
type DeviceRecord = IosDeviceRecord | AndroidDeviceRecord;
|
|
@@ -111,15 +112,24 @@ declare function allMetroPorts(): number[];
|
|
|
111
112
|
declare function findProjectByMetroPort(port: number): string | null;
|
|
112
113
|
//#endregion
|
|
113
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;
|
|
114
122
|
declare function readJsonFile(path: string): unknown;
|
|
115
123
|
declare function isJsonObject(value: unknown): value is Record<string, unknown>;
|
|
116
124
|
declare function readJsonObject(path: string): Record<string, unknown> | null;
|
|
117
125
|
//#endregion
|
|
118
126
|
//#region state/ledgers.d.ts
|
|
119
|
-
type CreatedDevicePlatform = "ios" | "android";
|
|
127
|
+
type CreatedDevicePlatform = "ios" | "android" | "web";
|
|
128
|
+
/** `web` lists the absolute paths of browser profile directories Stim created. */
|
|
120
129
|
interface CreatedDevices {
|
|
121
130
|
ios: ReadonlySet<string>;
|
|
122
131
|
android: ReadonlySet<string>;
|
|
132
|
+
web: ReadonlySet<string>;
|
|
123
133
|
}
|
|
124
134
|
declare function readCreatedDevices(): CreatedDevices;
|
|
125
135
|
interface EasSessionClaim {
|
|
@@ -145,6 +155,8 @@ interface NdjsonRecord {
|
|
|
145
155
|
src?: string;
|
|
146
156
|
level?: string;
|
|
147
157
|
msg?: string;
|
|
158
|
+
/** With `logs --errors --json`: the code frame and stack lines Expo printed after this error. */
|
|
159
|
+
context?: string[];
|
|
148
160
|
[key: string]: unknown;
|
|
149
161
|
}
|
|
150
162
|
declare const LEVELS: string[];
|
|
@@ -166,10 +178,11 @@ interface GrepResult {
|
|
|
166
178
|
interface MarkerWindow {
|
|
167
179
|
launchTs: number | null;
|
|
168
180
|
bundleTs: number | null;
|
|
181
|
+
pageLoadTs: number | null;
|
|
169
182
|
}
|
|
170
183
|
interface QueryCriteria {
|
|
171
184
|
slot?: string;
|
|
172
|
-
|
|
185
|
+
includeAppDeviceErrors?: boolean;
|
|
173
186
|
sources?: string[];
|
|
174
187
|
minLevel?: string;
|
|
175
188
|
grep?: RegExp;
|
|
@@ -201,7 +214,12 @@ declare function buildCriteria({ slot, sources, minLevel, since, grep, errorsOnl
|
|
|
201
214
|
declare function logFiles(dir: string): string[];
|
|
202
215
|
declare function fileSizes(dir: string): Record<string, number>;
|
|
203
216
|
declare function readLogRecords(dir: string, include?: (name: string) => boolean): NdjsonRecord[];
|
|
204
|
-
|
|
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[];
|
|
205
223
|
declare function queryLogs({ slot, dir, records, sources, minLevel, since, grep, tail, errorsOnly, errorContext, now }?: {
|
|
206
224
|
slot?: string;
|
|
207
225
|
dir?: string;
|
|
@@ -223,12 +241,14 @@ declare function advanceTail(prev: TailState | null | undefined, chunk: string |
|
|
|
223
241
|
state: TailState;
|
|
224
242
|
records: NdjsonRecord[];
|
|
225
243
|
};
|
|
226
|
-
declare function followLogs({ dir, onRecord, criteria, intervalMs, offsets }: {
|
|
244
|
+
declare function followLogs({ dir, onRecord, criteria, intervalMs, offsets, errorContext }: {
|
|
227
245
|
dir: string;
|
|
228
246
|
onRecord: (record: NdjsonRecord) => void;
|
|
229
247
|
criteria?: QueryCriteria;
|
|
230
248
|
intervalMs?: number;
|
|
231
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;
|
|
232
252
|
}): () => void;
|
|
233
253
|
declare function sortByTs(records: NdjsonRecord[]): NdjsonRecord[];
|
|
234
254
|
//#endregion
|
|
@@ -296,6 +316,7 @@ type SettingType = {
|
|
|
296
316
|
* `.stim.json` instead of the app's. A `sensitive` value is never printed and is
|
|
297
317
|
* written to `.stim.json` only as an `env:` or `file:` reference. `scopedHomeValue`
|
|
298
318
|
* replaces every layer and the default while STIM_HOME is set and `env` is not.
|
|
319
|
+
* `desktopDefault` replaces `default` on macOS while Stim Desktop is installed.
|
|
299
320
|
*/
|
|
300
321
|
interface SettingDefinition {
|
|
301
322
|
key: string;
|
|
@@ -307,6 +328,7 @@ interface SettingDefinition {
|
|
|
307
328
|
sensitive?: boolean;
|
|
308
329
|
committedAt?: "repository";
|
|
309
330
|
scopedHomeValue?: number;
|
|
331
|
+
desktopDefault?: string;
|
|
310
332
|
}
|
|
311
333
|
interface SettingEntry {
|
|
312
334
|
key: string;
|
|
@@ -318,6 +340,7 @@ interface SettingEntry {
|
|
|
318
340
|
value: string;
|
|
319
341
|
};
|
|
320
342
|
sensitive?: true;
|
|
343
|
+
defaultReason?: string;
|
|
321
344
|
}
|
|
322
345
|
/** The payload `stim settings --json` prints. */
|
|
323
346
|
interface SettingsPayload {
|
|
@@ -334,6 +357,8 @@ interface SettingsPayload {
|
|
|
334
357
|
declare const REMOTE_DEVICE_BACKENDS: readonly RemoteDeviceBackend[];
|
|
335
358
|
declare const IOS_SIMULATOR_APPS: readonly ["xcode", "siniulator", "stim-desktop"];
|
|
336
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];
|
|
337
362
|
declare const SETTINGS: readonly SettingDefinition[];
|
|
338
363
|
declare function settingDefinition(key: string): SettingDefinition | null;
|
|
339
364
|
declare function isLayeredSetting(setting: SettingDefinition): boolean;
|
|
@@ -382,7 +407,16 @@ interface IdleStopRecord {
|
|
|
382
407
|
declare function readIdleStop(state: WorkspaceState | null | undefined): IdleStopRecord | null;
|
|
383
408
|
/** Each platform's latest run; `lastBuild` holds whichever platform ran last. */
|
|
384
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[];
|
|
385
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[]>>;
|
|
386
420
|
declare function readWorkspaceState(root: string): WorkspaceState | null;
|
|
387
421
|
declare function lastUseFrom(state: WorkspaceState | null, logMtimes: readonly number[]): number;
|
|
388
422
|
declare function workspaceLastUsed(root: string): number;
|
|
@@ -419,6 +453,31 @@ interface LastBuildReport {
|
|
|
419
453
|
finishedAt: string | null;
|
|
420
454
|
errorCode?: string;
|
|
421
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;
|
|
422
481
|
}
|
|
423
482
|
/** What kind of native input a changed fingerprint source is. */
|
|
424
483
|
type BuildMissCategory = "native-dependency" | "config-plugin" | "app-config" | "app-asset" | "package" | "native-dir" | "autolinking" | "package-scripts" | "file" | "other";
|
|
@@ -430,9 +489,11 @@ interface BuildMissChange {
|
|
|
430
489
|
/**
|
|
431
490
|
* Why a run compiled instead of installing a cached app. `changes` holds at most 20 entries,
|
|
432
491
|
* ordered by importance; `changeCount` is the full number of changed fingerprint sources.
|
|
492
|
+
* Only a plan reports `prebuild-pending`: the run would prebuild before compiling, so the changes
|
|
493
|
+
* compare the fingerprint before that prebuild.
|
|
433
494
|
*/
|
|
434
495
|
interface BuildMissReason {
|
|
435
|
-
kind: "changed" | "no-baseline" | "same-sources" | "cache-skipped" | "fingerprint-error";
|
|
496
|
+
kind: "changed" | "no-baseline" | "same-sources" | "cache-skipped" | "fingerprint-error" | "prebuild-pending";
|
|
436
497
|
summary: string;
|
|
437
498
|
changes: BuildMissChange[];
|
|
438
499
|
changeCount: number;
|
|
@@ -457,6 +518,8 @@ interface BuildPlanPayload {
|
|
|
457
518
|
outcome: RunOutcomeKind | null;
|
|
458
519
|
expectedMs: number | null;
|
|
459
520
|
basis: number;
|
|
521
|
+
/** On a predicted miss with cache reads on, why the cache has no app; `baseline` omits `cacheKey`. */
|
|
522
|
+
missReason?: BuildMissReason;
|
|
460
523
|
refusal?: {
|
|
461
524
|
code: string;
|
|
462
525
|
message: string;
|
|
@@ -475,6 +538,17 @@ interface DeviceActivity {
|
|
|
475
538
|
lastActivityAt?: string;
|
|
476
539
|
basis: string[];
|
|
477
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
|
+
}
|
|
478
552
|
/**
|
|
479
553
|
* A linked worktree's `git status`. `changed` counts tracked paths with staged or unstaged changes, including
|
|
480
554
|
* conflicts; `untracked` counts untracked entries as `git status` lists them, so an untracked directory counts once.
|
|
@@ -510,6 +584,38 @@ interface RemoteDeviceState {
|
|
|
510
584
|
startedAt: string | null;
|
|
511
585
|
webPreviewUrl: string | null;
|
|
512
586
|
}
|
|
587
|
+
/** Every code a status issue can carry. */
|
|
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"];
|
|
589
|
+
type StatusIssueCode = (typeof STATUS_ISSUE_CODES)[number];
|
|
590
|
+
/**
|
|
591
|
+
* One thing in a workspace that needs the user. `remedy` is a command to run from `workspace`; `slot` is absent
|
|
592
|
+
* for the default device slot. `warnings` carries the same issues as text.
|
|
593
|
+
*/
|
|
594
|
+
interface StatusIssue {
|
|
595
|
+
code: StatusIssueCode;
|
|
596
|
+
severity: "error" | "warning";
|
|
597
|
+
message: string;
|
|
598
|
+
remedy: string;
|
|
599
|
+
workspace: string;
|
|
600
|
+
slot?: string;
|
|
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
|
+
}
|
|
513
619
|
interface EnvironmentState {
|
|
514
620
|
slots?: {
|
|
515
621
|
slot: string;
|
|
@@ -518,14 +624,20 @@ interface EnvironmentState {
|
|
|
518
624
|
}[];
|
|
519
625
|
path: string;
|
|
520
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
|
+
*/
|
|
521
631
|
memoryMb: number;
|
|
522
632
|
warnings: string[];
|
|
633
|
+
issues: StatusIssue[];
|
|
523
634
|
ios?: {
|
|
524
635
|
name: string | null;
|
|
525
636
|
udid: string;
|
|
526
637
|
owned: boolean;
|
|
527
638
|
state: string;
|
|
528
639
|
activity?: DeviceActivity;
|
|
640
|
+
app?: DeviceAppProcess;
|
|
529
641
|
} | null;
|
|
530
642
|
android?: {
|
|
531
643
|
name: string | undefined;
|
|
@@ -533,7 +645,9 @@ interface EnvironmentState {
|
|
|
533
645
|
physical: boolean;
|
|
534
646
|
serial?: string | null;
|
|
535
647
|
state?: AndroidRuntimeFacts["state"];
|
|
648
|
+
deviceProfile?: string | null;
|
|
536
649
|
activity?: DeviceActivity;
|
|
650
|
+
app?: DeviceAppProcess;
|
|
537
651
|
} | null;
|
|
538
652
|
metro?: {
|
|
539
653
|
port: number;
|
|
@@ -541,6 +655,7 @@ interface EnvironmentState {
|
|
|
541
655
|
pid: number | null;
|
|
542
656
|
idleStop?: IdleStopRecord;
|
|
543
657
|
} | null;
|
|
658
|
+
web?: WebBrowserState | null;
|
|
544
659
|
supervisor?: {
|
|
545
660
|
pid: number | null;
|
|
546
661
|
mode: string | null;
|
|
@@ -555,7 +670,10 @@ interface EnvironmentState {
|
|
|
555
670
|
remoteDevices?: RemoteDeviceState[];
|
|
556
671
|
build?: BuildReport | null;
|
|
557
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[]>>;
|
|
558
675
|
}
|
|
676
|
+
/** `committedMb` sums the environments' estimated `memoryMb`; `overCapacity` is that sum over 60% of `totalMemoryMb`. */
|
|
559
677
|
interface StatusCapacity {
|
|
560
678
|
liveCount: number;
|
|
561
679
|
committedMb: number;
|
|
@@ -575,6 +693,34 @@ interface DeviceLeaseState {
|
|
|
575
693
|
expired: boolean;
|
|
576
694
|
parsed: boolean;
|
|
577
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
|
+
}
|
|
578
724
|
/** The payload `stim status --json` prints, and `status --watch --json` prints on each change. */
|
|
579
725
|
interface StatusPayload {
|
|
580
726
|
environments: (EnvironmentState & {
|
|
@@ -584,6 +730,8 @@ interface StatusPayload {
|
|
|
584
730
|
deviceLeases: DeviceLeaseState[];
|
|
585
731
|
unprovisionedWorktrees: WorktreeFacts[];
|
|
586
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;
|
|
587
735
|
}
|
|
588
736
|
//#endregion
|
|
589
|
-
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, SettingDefinition, SettingEntry, SettingScope, SettingType, SettingsObject, SettingsPayload, StatsPlatform, StatusCapacity, 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" },
|
|
@@ -957,7 +1100,8 @@ const SETTINGS = [
|
|
|
957
1100
|
},
|
|
958
1101
|
scopes: MACHINE,
|
|
959
1102
|
default: "xcode",
|
|
960
|
-
|
|
1103
|
+
desktopDefault: "stim-desktop",
|
|
1104
|
+
description: "App that displays an owned iOS simulator; stim-desktop by default when Stim Desktop is installed"
|
|
961
1105
|
},
|
|
962
1106
|
{
|
|
963
1107
|
key: "androidEmulatorApp",
|
|
@@ -967,7 +1111,8 @@ const SETTINGS = [
|
|
|
967
1111
|
},
|
|
968
1112
|
scopes: MACHINE,
|
|
969
1113
|
default: "emulator",
|
|
970
|
-
|
|
1114
|
+
desktopDefault: "stim-desktop",
|
|
1115
|
+
description: "App that displays an owned Android emulator Stim boots on macOS; stim-desktop by default when Stim Desktop is installed"
|
|
971
1116
|
},
|
|
972
1117
|
{
|
|
973
1118
|
key: "concurrency.maxBuilds",
|
|
@@ -1019,6 +1164,14 @@ const SETTINGS = [
|
|
|
1019
1164
|
scopedHomeValue: 0,
|
|
1020
1165
|
description: "Live workspaces before idle ones are reclaimed; unset or 0 means no limit"
|
|
1021
1166
|
},
|
|
1167
|
+
{
|
|
1168
|
+
key: "gc.worktreeGraceMinutes",
|
|
1169
|
+
type: CAPACITY,
|
|
1170
|
+
scopes: MACHINE,
|
|
1171
|
+
default: 120,
|
|
1172
|
+
env: "STIM_GC_WORKTREE_GRACE_MINUTES",
|
|
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"
|
|
1174
|
+
},
|
|
1022
1175
|
{
|
|
1023
1176
|
key: "pool.iosParkedMax",
|
|
1024
1177
|
type: PARKED_MAX,
|
|
@@ -1249,6 +1402,42 @@ const BUILD_PHASES = [
|
|
|
1249
1402
|
"install",
|
|
1250
1403
|
"launch"
|
|
1251
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
|
+
];
|
|
1418
|
+
/** Every code a status issue can carry. */
|
|
1419
|
+
const STATUS_ISSUE_CODES = [
|
|
1420
|
+
"port-not-ours",
|
|
1421
|
+
"sim-missing",
|
|
1422
|
+
"sim-without-metro",
|
|
1423
|
+
"avd-serial-changed",
|
|
1424
|
+
"avd-missing",
|
|
1425
|
+
"avd-not-detected",
|
|
1426
|
+
"avd-unchecked",
|
|
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"
|
|
1440
|
+
];
|
|
1252
1441
|
//#endregion
|
|
1253
1442
|
//#region state/workspace-state.ts
|
|
1254
1443
|
const IDLE_STOP_KEY = "devServerStop";
|
|
@@ -1266,6 +1455,8 @@ const LAST_BUILD_KEYS = {
|
|
|
1266
1455
|
ios: "lastIosBuild",
|
|
1267
1456
|
android: "lastAndroidBuild"
|
|
1268
1457
|
};
|
|
1458
|
+
const BUILD_HISTORY_KEY = "buildHistory";
|
|
1459
|
+
const BUILD_HISTORY_LIMIT = 10;
|
|
1269
1460
|
const MISS_KINDS = /* @__PURE__ */ new Set([
|
|
1270
1461
|
"changed",
|
|
1271
1462
|
"no-baseline",
|
|
@@ -1314,6 +1505,25 @@ function missReason(value) {
|
|
|
1314
1505
|
rekeyedBy: Array.isArray(record.rekeyedBy) ? record.rekeyedBy.filter((step) => typeof step === "string") : []
|
|
1315
1506
|
};
|
|
1316
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
|
+
}
|
|
1317
1527
|
function lastBuildReport(platform, value) {
|
|
1318
1528
|
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
1319
1529
|
const record = value;
|
|
@@ -1322,6 +1532,7 @@ function lastBuildReport(platform, value) {
|
|
|
1322
1532
|
const durationMs = typeof record.durationMs === "number" && record.durationMs >= 0 ? record.durationMs : null;
|
|
1323
1533
|
const finished = new Date(Date.parse(record.startedAt) + (durationMs ?? NaN));
|
|
1324
1534
|
const reason = record.cacheHit === "local" || record.cacheHit === "remote" ? null : missReason(record.missReason);
|
|
1535
|
+
const diagnostics = record.status === "failed" ? buildDiagnostics(record.diagnostics) : [];
|
|
1325
1536
|
return {
|
|
1326
1537
|
platform,
|
|
1327
1538
|
status: record.status,
|
|
@@ -1332,7 +1543,8 @@ function lastBuildReport(platform, value) {
|
|
|
1332
1543
|
startedAt: record.startedAt,
|
|
1333
1544
|
finishedAt: Number.isNaN(finished.getTime()) ? null : finished.toISOString(),
|
|
1334
1545
|
...typeof record.errorCode === "string" ? { errorCode: record.errorCode } : {},
|
|
1335
|
-
...reason ? { missReason: reason } : {}
|
|
1546
|
+
...reason ? { missReason: reason } : {},
|
|
1547
|
+
...diagnostics.length ? { diagnostics } : {}
|
|
1336
1548
|
};
|
|
1337
1549
|
}
|
|
1338
1550
|
function readLastBuilds(state) {
|
|
@@ -1343,6 +1555,41 @@ function readLastBuilds(state) {
|
|
|
1343
1555
|
}
|
|
1344
1556
|
return reports;
|
|
1345
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
|
+
}
|
|
1346
1593
|
function readWorkspaceState(root) {
|
|
1347
1594
|
return readJsonObject(workspaceStateFile(root));
|
|
1348
1595
|
}
|
|
@@ -1370,4 +1617,4 @@ function workspaceLastUsed(root) {
|
|
|
1370
1617
|
return lastUseFrom(readWorkspaceState(root), mtimes);
|
|
1371
1618
|
}
|
|
1372
1619
|
//#endregion
|
|
1373
|
-
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, 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 };
|