stim 1.0.0 → 1.1.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/README.md +22 -10
- package/dist/android-CC5bUer7.mjs +204 -0
- package/dist/android-DnZlT3aU.mjs +2279 -0
- package/dist/android-cas-DGlTStWu.mjs +1078 -0
- package/dist/android-cas-compiler.mjs +1 -1
- package/dist/android-xmyGBNy5.mjs +665 -0
- package/dist/app-install-oZIQRbdD.mjs +1141 -0
- package/dist/build-lock-vE45ZzJZ.mjs +645 -0
- package/dist/build-slots-CgacA9zQ.mjs +152 -0
- package/dist/{cache-manifest-4tH64LQ9.mjs → cache-manifest-oeH18ERr.mjs} +2 -2
- package/dist/cache-manifest.mjs +1 -1
- package/dist/cli.mjs +24 -27166
- package/dist/collector-run.mjs +6 -4
- package/dist/command-output-8wHMEde0.mjs +289 -0
- package/dist/{config-7kmtuhO2.mjs → config-D7uu8Gbq.mjs} +43 -12
- package/dist/deps-64wVnpZc.mjs +435 -0
- package/dist/dev-client-Bvky1hya.mjs +557 -0
- package/dist/device-DJKv5Qej.mjs +250 -0
- package/dist/device-lease-D-G3SgVe.mjs +335 -0
- package/dist/device-pool-BlHXnYYs.mjs +408 -0
- package/dist/device-remote-CCgkG82J.mjs +1209 -0
- package/dist/doctor-BRF9aAJ3.mjs +530 -0
- package/dist/doctor-DlVxTtMq.mjs +1137 -0
- package/dist/error-diagnostics-B5IJwv3-.mjs +636 -0
- package/dist/gc-DhXdgSdt.mjs +1651 -0
- package/dist/guide-DrhrDBo5.mjs +4319 -0
- package/dist/ios-BZxKPuqP.mjs +382 -0
- package/dist/ios-CK1Vrk6Q.mjs +2721 -0
- package/dist/ios-device-CYbuRNXR.mjs +81 -0
- package/dist/ios-device-CdSm0GDw.mjs +413 -0
- package/dist/logs-Dxoy16Qv.mjs +176 -0
- package/dist/logs-query-oqn47Ffx.mjs +273 -0
- package/dist/metro-B01H3EuD.mjs +184 -0
- package/dist/metro-store-CDDZHtPl.mjs +92 -0
- package/dist/ownership-B9QIdQOw.mjs +475 -0
- package/dist/ownership-D8HmSv2E.mjs +60 -0
- package/dist/ownership-claim-CeJLEpjD.mjs +569 -0
- package/dist/{project-Dit1CckW.mjs → project-COj6nzzh.mjs} +12 -3
- package/dist/reclaim-BPvN2HlP.mjs +347 -0
- package/dist/reload-DYZEBbZy.mjs +332 -0
- package/dist/remote-cache-DOi5-zLj.mjs +934 -0
- package/dist/{server-bare-BmTVVmBB.mjs → server-bare-BYHqEeyw.mjs} +3 -2
- package/dist/{server-expo-CHJe1r6j.mjs → server-expo-BEMr_m6p.mjs} +5 -4
- package/dist/server-expo-rb0k-0w7.mjs +2 -0
- package/dist/settings-BSwrY4FZ.mjs +687 -0
- package/dist/spawn-entry-nCE6m770.mjs +14 -0
- package/dist/start-ByCylYtB.mjs +822 -0
- package/dist/{state-2nush3MK.mjs → state-IYEY-zi8.mjs} +21 -22
- package/dist/stats-DZ4Q5J-7.mjs +62 -0
- package/dist/stats-wqxPm7Tv.mjs +307 -0
- package/dist/status-Dia0XTqG.mjs +363 -0
- package/dist/stop-BApyaF21.mjs +591 -0
- package/dist/stop-D6o5zAdm.mjs +3 -0
- package/dist/supervisor-run.d.mts +0 -1
- package/dist/supervisor-run.mjs +4 -4
- package/dist/workspace-process-lock-CxOO9-pf.mjs +47 -0
- package/dist/worktree-Dx3ZC5lS.mjs +539 -0
- package/dist/worktree-DxEmUI_n.mjs +1322 -0
- package/dist/xcode-CDB6fytw.mjs +1705 -0
- package/package.json +5 -5
- package/dist/android-jc8MnUyL.mjs +0 -514
- package/dist/metro-store-COLl1pOk.mjs +0 -1249
- package/dist/server-expo-BZJm--wA.mjs +0 -2
package/README.md
CHANGED
|
@@ -77,7 +77,7 @@ artifact caches and Xcode build directories; run `stim guide lifecycle builds`
|
|
|
77
77
|
for provider behavior. This selects an Xcode scheme, not a URL scheme.
|
|
78
78
|
|
|
79
79
|
Launch evidence does not prove that the UI is interactive. Apps can optionally
|
|
80
|
-
[declare readiness with two debug log messages](https://appandflow.
|
|
80
|
+
[declare readiness with two debug log messages](https://stim.appandflow.com/docs/dev-server-and-logs#optional-app-declared-readiness),
|
|
81
81
|
without a package or SDK. A captured pending message extends the default
|
|
82
82
|
three-second stability window after bundle delivery to a bounded wait for ready.
|
|
83
83
|
Managed Metro response capture distinguishes a finished build from a finished
|
|
@@ -86,10 +86,12 @@ response; servers without capture use the build-complete marker. Run
|
|
|
86
86
|
expected screen separately.
|
|
87
87
|
|
|
88
88
|
`stim reload [ios|android]` requests a JavaScript reload in the live app on this
|
|
89
|
-
workspace's owned local device. Use it
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
89
|
+
workspace's owned local device. Use it when an error screen remains after a fix,
|
|
90
|
+
and on Android after a failed first bundle load. An iOS app whose first bundle
|
|
91
|
+
failed never connects to Metro, so reload cannot reach it. It reloads
|
|
92
|
+
JavaScript and never restarts the app. It is not part of the normal workflow
|
|
93
|
+
and does not build, install, boot, or launch an app. The platform is optional
|
|
94
|
+
when only one app is live.
|
|
93
95
|
Success confirms that the request was sent; Stim does not observe completion.
|
|
94
96
|
Verify the expected UI on the reported device and inspect `stim logs --errors`
|
|
95
97
|
before claiming recovery.
|
|
@@ -103,9 +105,19 @@ stim worktree warm
|
|
|
103
105
|
```
|
|
104
106
|
|
|
105
107
|
If a harness already created the linked worktree, run only `stim worktree warm`
|
|
106
|
-
there. Warm copies missing ignored state from
|
|
107
|
-
and local configuration files. Existing entries are preserved;
|
|
108
|
-
ignored directories such as `node_modules` are skipped whole.
|
|
108
|
+
there. Warm copies missing ignored state from the source checkout, including
|
|
109
|
+
eligible `.env` and local configuration files. Existing entries are preserved;
|
|
110
|
+
existing ignored directories such as `node_modules` are skipped whole.
|
|
111
|
+
|
|
112
|
+
`stim worktree warm --refresh` brings the source checkout up to date first: it
|
|
113
|
+
fetches, fast-forwards whatever branch is checked out there, and installs what
|
|
114
|
+
the new commits moved before copying. It refuses a source checkout it cannot
|
|
115
|
+
move (uncommitted tracked changes, a rebase or merge in progress, a detached
|
|
116
|
+
HEAD, a diverged branch) and never switches branches. One lock per repository
|
|
117
|
+
keeps a copy from reading a `node_modules` a refresh is rewriting; plain warms
|
|
118
|
+
still run side by side. A plain warm also refuses (`STIM_DEPS_INCOMPLETE`) when the last
|
|
119
|
+
install of the lockfile on disk did not finish, instead of copying a partial
|
|
120
|
+
`node_modules`; `stim worktree warm --refresh` reinstalls it.
|
|
109
121
|
|
|
110
122
|
Wait for warm to finish before editing, installing dependencies, starting
|
|
111
123
|
Metro/builds, or running another warm in that worktree. Concurrent writes are
|
|
@@ -113,12 +125,12 @@ unsafe: entries created after the initial check can be overwritten or removed.
|
|
|
113
125
|
|
|
114
126
|
After the work is preserved, `stim worktree remove` removes any linked
|
|
115
127
|
worktree, warmed or not. Git-created branches stay. See the
|
|
116
|
-
[worktree guide](https://appandflow.
|
|
128
|
+
[worktree guide](https://stim.appandflow.com/docs/worktrees) for exclusions
|
|
117
129
|
and cleanup rules.
|
|
118
130
|
|
|
119
131
|
## Reference
|
|
120
132
|
|
|
121
|
-
The [documentation website](https://appandflow.
|
|
133
|
+
The [documentation website](https://stim.appandflow.com/) explains the
|
|
122
134
|
human workflow and all commands.
|
|
123
135
|
|
|
124
136
|
The installed CLI contains version-matched operational guidance:
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { t as getExecutor } from "./exec-bsN9MJXb.mjs";
|
|
2
|
+
//#region src/collector/android.ts
|
|
3
|
+
const LEVEL_BY_LETTER = {
|
|
4
|
+
V: "debug",
|
|
5
|
+
D: "debug",
|
|
6
|
+
I: "info",
|
|
7
|
+
W: "warn",
|
|
8
|
+
E: "error",
|
|
9
|
+
F: "fatal",
|
|
10
|
+
A: "fatal",
|
|
11
|
+
S: "debug"
|
|
12
|
+
};
|
|
13
|
+
function levelFromLogcatLetter(letter) {
|
|
14
|
+
return LEVEL_BY_LETTER[String(letter || "").toUpperCase()] ?? "info";
|
|
15
|
+
}
|
|
16
|
+
const NOISE_TAGS = /* @__PURE__ */ new Set([
|
|
17
|
+
"libEGL",
|
|
18
|
+
"EGL_emulation",
|
|
19
|
+
"eglCodecCommon",
|
|
20
|
+
"emuglGLESv2_enc",
|
|
21
|
+
"HostConnection",
|
|
22
|
+
"OpenGLRenderer",
|
|
23
|
+
"gralloc4",
|
|
24
|
+
"Gralloc4",
|
|
25
|
+
"vulkan",
|
|
26
|
+
"GraphicBufferAllocator",
|
|
27
|
+
"BufferQueueProducer",
|
|
28
|
+
"Surface",
|
|
29
|
+
"ziparchive",
|
|
30
|
+
"chatty"
|
|
31
|
+
]);
|
|
32
|
+
function levelForLogcat(letter, tag) {
|
|
33
|
+
const level = levelFromLogcatLetter(letter);
|
|
34
|
+
if (level !== "error") return level;
|
|
35
|
+
return NOISE_TAGS.has(String(tag || "").trim()) ? "info" : level;
|
|
36
|
+
}
|
|
37
|
+
const LOGCAT_TIME = /^(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})\.(\d{3})\s+([A-Z])\/(.*?)\(\s*(\d+)\):\s?(.*)$/;
|
|
38
|
+
const LOGCAT_EPOCH = /^\s*(\d+)\.(\d{3,6})\s+([A-Z])\/(.*?)\(\s*(\d+)\):\s?(.*)$/;
|
|
39
|
+
function parseLogcatTimestamp({ month, day, hour, minute, second, millis }, now = Date.now()) {
|
|
40
|
+
const ref = new Date(now);
|
|
41
|
+
let ts = new Date(ref.getFullYear(), month - 1, day, hour, minute, second, millis).getTime();
|
|
42
|
+
if (ts - now > 864e5) ts = new Date(ref.getFullYear() - 1, month - 1, day, hour, minute, second, millis).getTime();
|
|
43
|
+
return ts;
|
|
44
|
+
}
|
|
45
|
+
function parseLogcatLine(line, { now = Date.now, clockOffsetMs = null } = {}) {
|
|
46
|
+
if (typeof line !== "string") return null;
|
|
47
|
+
const epoch = LOGCAT_EPOCH.exec(line.trimEnd());
|
|
48
|
+
if (epoch) {
|
|
49
|
+
const [, seconds, fraction, letter, tag, pid, msg] = epoch;
|
|
50
|
+
if (!letter || !tag || !msg?.trim()) return null;
|
|
51
|
+
const deviceTs = Number(seconds) * 1e3 + Number(fraction.slice(0, 3));
|
|
52
|
+
if (!Number.isSafeInteger(deviceTs)) return null;
|
|
53
|
+
return {
|
|
54
|
+
ts: deviceTs + (clockOffsetMs ?? 0),
|
|
55
|
+
deviceTs,
|
|
56
|
+
...clockOffsetMs === null ? {} : { clockOffsetMs },
|
|
57
|
+
src: "device",
|
|
58
|
+
level: levelForLogcat(letter, tag),
|
|
59
|
+
msg,
|
|
60
|
+
proc: `${tag.trim()}(${pid})`
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
const m = LOGCAT_TIME.exec(line.trimEnd());
|
|
64
|
+
if (!m) return null;
|
|
65
|
+
const [, month, day, hour, minute, second, millis, letter, tag, pid, msg] = m;
|
|
66
|
+
if (letter === void 0 || tag === void 0 || msg === void 0) return null;
|
|
67
|
+
if (!msg.trim()) return null;
|
|
68
|
+
return {
|
|
69
|
+
ts: parseLogcatTimestamp({
|
|
70
|
+
month: Number(month),
|
|
71
|
+
day: Number(day),
|
|
72
|
+
hour: Number(hour),
|
|
73
|
+
minute: Number(minute),
|
|
74
|
+
second: Number(second),
|
|
75
|
+
millis: Number(millis)
|
|
76
|
+
}, now()),
|
|
77
|
+
src: "device",
|
|
78
|
+
level: levelForLogcat(letter, tag),
|
|
79
|
+
msg,
|
|
80
|
+
proc: `${tag.trim()}(${pid})`
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function logcatArgs(serial, pid) {
|
|
84
|
+
return [
|
|
85
|
+
"-s",
|
|
86
|
+
serial,
|
|
87
|
+
"logcat",
|
|
88
|
+
"--pid",
|
|
89
|
+
String(pid),
|
|
90
|
+
"-v",
|
|
91
|
+
"time,epoch"
|
|
92
|
+
];
|
|
93
|
+
}
|
|
94
|
+
function androidClockOffset(serial, { exec = getExecutor(), now = Date.now } = {}) {
|
|
95
|
+
const before = now();
|
|
96
|
+
try {
|
|
97
|
+
const output = exec.runFile("adb", [
|
|
98
|
+
"-s",
|
|
99
|
+
serial,
|
|
100
|
+
"shell",
|
|
101
|
+
"date",
|
|
102
|
+
"+%s%3N"
|
|
103
|
+
], { timeoutMs: 1e3 }).trim();
|
|
104
|
+
const after = now();
|
|
105
|
+
if (!/^\d{13}$/.test(output) || after < before || after - before > 1e3) return null;
|
|
106
|
+
return Math.round((before + after) / 2) - Number(output);
|
|
107
|
+
} catch {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function parsePidof(text) {
|
|
112
|
+
const first = String(text ?? "").trim().split(/\s+/)[0];
|
|
113
|
+
const pid = Number(first);
|
|
114
|
+
return Number.isInteger(pid) && pid > 0 ? pid : null;
|
|
115
|
+
}
|
|
116
|
+
function resolveAppPid(serial, packageName, { exec = null } = {}) {
|
|
117
|
+
const e = exec || getExecutor();
|
|
118
|
+
try {
|
|
119
|
+
return parsePidof(e.runFile("adb", [
|
|
120
|
+
"-s",
|
|
121
|
+
serial,
|
|
122
|
+
"shell",
|
|
123
|
+
"pidof",
|
|
124
|
+
"-s",
|
|
125
|
+
packageName
|
|
126
|
+
]));
|
|
127
|
+
} catch {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
async function waitForAppPid({ serial, packageName, timeoutMs = 3e4, pollMs = 500, exec = null, now = Date.now, sleep = defaultSleep }) {
|
|
132
|
+
const deadline = now() + timeoutMs;
|
|
133
|
+
for (;;) {
|
|
134
|
+
const pid = resolveAppPid(serial, packageName, { exec });
|
|
135
|
+
if (pid) return {
|
|
136
|
+
ok: true,
|
|
137
|
+
pid
|
|
138
|
+
};
|
|
139
|
+
if (now() >= deadline) return {
|
|
140
|
+
failed: true,
|
|
141
|
+
reason: `No process for ${packageName} appeared on ${serial} within ${Math.round(timeoutMs / 1e3)}s.`
|
|
142
|
+
};
|
|
143
|
+
await sleep(pollMs);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
function defaultSleep(ms) {
|
|
147
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
148
|
+
}
|
|
149
|
+
function startAndroidLogcat({ serial, pid, spawnFn = null }) {
|
|
150
|
+
return (spawnFn || ((cmd, args, opts) => getExecutor().spawn(cmd, args, opts)))("adb", logcatArgs(serial, pid), {
|
|
151
|
+
stdio: [
|
|
152
|
+
"ignore",
|
|
153
|
+
"pipe",
|
|
154
|
+
"pipe"
|
|
155
|
+
],
|
|
156
|
+
detached: false
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
const PID_WATCH_MS = 3e3;
|
|
160
|
+
function pidWatchInterval() {
|
|
161
|
+
const override = Number(process.env.STIM_PID_WATCH_MS);
|
|
162
|
+
return Number.isFinite(override) && override > 0 ? override : PID_WATCH_MS;
|
|
163
|
+
}
|
|
164
|
+
function watchAppPid({ serial, packageName, pid, intervalMs = null, exec = null, onChange, resolve = resolveAppPid, setTimer = setTimeout, clearTimer = clearTimeout }) {
|
|
165
|
+
const every = intervalMs ?? pidWatchInterval();
|
|
166
|
+
let current = pid;
|
|
167
|
+
let stopped = false;
|
|
168
|
+
let timer = null;
|
|
169
|
+
const tick = async () => {
|
|
170
|
+
timer = null;
|
|
171
|
+
if (stopped) return;
|
|
172
|
+
let next = null;
|
|
173
|
+
try {
|
|
174
|
+
next = resolve(serial, packageName, { exec });
|
|
175
|
+
} catch {}
|
|
176
|
+
if (next && next !== current) {
|
|
177
|
+
current = next;
|
|
178
|
+
try {
|
|
179
|
+
await onChange(next);
|
|
180
|
+
} catch {}
|
|
181
|
+
}
|
|
182
|
+
if (!stopped) timer = setTimer(tick, every);
|
|
183
|
+
};
|
|
184
|
+
timer = setTimer(tick, every);
|
|
185
|
+
return {
|
|
186
|
+
stop() {
|
|
187
|
+
stopped = true;
|
|
188
|
+
if (timer) clearTimer(timer);
|
|
189
|
+
timer = null;
|
|
190
|
+
},
|
|
191
|
+
probe() {
|
|
192
|
+
try {
|
|
193
|
+
return resolve(serial, packageName, { exec });
|
|
194
|
+
} catch {
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
get pid() {
|
|
199
|
+
return current;
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
//#endregion
|
|
204
|
+
export { watchAppPid as a, waitForAppPid as i, parseLogcatLine as n, startAndroidLogcat as r, androidClockOffset as t };
|