memorydetective 1.7.0 → 1.8.1
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/CHANGELOG.md +41 -0
- package/README.md +61 -29
- package/USAGE.md +87 -41
- package/dist/index.js +34 -1
- package/dist/index.js.map +1 -1
- package/dist/runtime/axe.d.ts +86 -0
- package/dist/runtime/axe.js +249 -0
- package/dist/runtime/axe.js.map +1 -0
- package/dist/runtime/buildSettings.d.ts +27 -0
- package/dist/runtime/buildSettings.js +88 -0
- package/dist/runtime/buildSettings.js.map +1 -0
- package/dist/runtime/exec.d.ts +8 -1
- package/dist/runtime/exec.js +8 -2
- package/dist/runtime/exec.js.map +1 -1
- package/dist/runtime/simctl.d.ts +68 -0
- package/dist/runtime/simctl.js +194 -0
- package/dist/runtime/simctl.js.map +1 -0
- package/dist/tools/bootAndLaunchForLeakInvestigation.d.ts +166 -0
- package/dist/tools/bootAndLaunchForLeakInvestigation.js +367 -0
- package/dist/tools/bootAndLaunchForLeakInvestigation.js.map +1 -0
- package/dist/tools/captureMemgraph.d.ts +29 -1
- package/dist/tools/captureMemgraph.js +148 -6
- package/dist/tools/captureMemgraph.js.map +1 -1
- package/dist/tools/captureScenarioState.d.ts +77 -0
- package/dist/tools/captureScenarioState.js +159 -0
- package/dist/tools/captureScenarioState.js.map +1 -0
- package/dist/tools/detectLeaksInXCUITest.d.ts +2 -2
- package/dist/tools/getInvestigationPlaybook.d.ts +15 -0
- package/dist/tools/getInvestigationPlaybook.js +24 -1
- package/dist/tools/getInvestigationPlaybook.js.map +1 -1
- package/dist/tools/replayScenario.d.ts +243 -0
- package/dist/tools/replayScenario.js +187 -0
- package/dist/tools/replayScenario.js.map +1 -0
- package/package.json +3 -2
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal infrastructure for driving the iOS Simulator UI from inside leak/perf
|
|
3
|
+
* workflows (replayScenario, captureScenarioState). NOT a generic UI automation
|
|
4
|
+
* surface, the public tool contract stays scoped to leak/perf debug.
|
|
5
|
+
*
|
|
6
|
+
* Wraps Cameron Cooke's `axe` CLI (https://github.com/cameroncooke/AXe).
|
|
7
|
+
* `axe` is a soft dependency: we detect it on first use and emit a structured
|
|
8
|
+
* install hint when missing, instead of hard-failing or bundling it.
|
|
9
|
+
*/
|
|
10
|
+
import { runCommand } from "./exec.js";
|
|
11
|
+
const AXE_INSTALL_HINT = "axe CLI not found in PATH. Install with `brew install cameroncooke/axe/axe` (https://github.com/cameroncooke/AXe). axe is a soft dependency, only required for replayScenario and captureScenarioState.";
|
|
12
|
+
/**
|
|
13
|
+
* Check whether `axe` is available on the system. Cached at module load via
|
|
14
|
+
* `which` lookup so callers can skip the call when they already know.
|
|
15
|
+
*/
|
|
16
|
+
export async function checkAxeAvailable() {
|
|
17
|
+
const result = await runCommand("which", ["axe"], { timeoutMs: 5_000 });
|
|
18
|
+
if (result.code !== 0) {
|
|
19
|
+
return { available: false, installHint: AXE_INSTALL_HINT };
|
|
20
|
+
}
|
|
21
|
+
const binaryPath = result.stdout.trim();
|
|
22
|
+
if (!binaryPath) {
|
|
23
|
+
return { available: false, installHint: AXE_INSTALL_HINT };
|
|
24
|
+
}
|
|
25
|
+
return { available: true, binaryPath };
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Run `axe describe-ui --udid <udid>` and parse the tree into normalized
|
|
29
|
+
* `UIElement` nodes. Returns the root element.
|
|
30
|
+
*/
|
|
31
|
+
export async function describeUI(udid) {
|
|
32
|
+
const result = await runCommand("axe", ["describe-ui", "--udid", udid], {
|
|
33
|
+
timeoutMs: 30_000,
|
|
34
|
+
});
|
|
35
|
+
if (result.code !== 0) {
|
|
36
|
+
throw new Error(`axe describe-ui --udid ${udid} failed (code ${result.code}): ${result.stderr || result.stdout}`);
|
|
37
|
+
}
|
|
38
|
+
return parseAxeDescribeUI(result.stdout);
|
|
39
|
+
}
|
|
40
|
+
/** Pure: parse `axe describe-ui` JSON output into normalized UIElement tree. */
|
|
41
|
+
export function parseAxeDescribeUI(stdout) {
|
|
42
|
+
const sliced = sliceJsonValue(stdout);
|
|
43
|
+
if (!sliced) {
|
|
44
|
+
throw new Error("axe describe-ui output did not contain a JSON object/array. Stdout begins: " +
|
|
45
|
+
stdout.slice(0, 200));
|
|
46
|
+
}
|
|
47
|
+
let parsed;
|
|
48
|
+
try {
|
|
49
|
+
parsed = JSON.parse(sliced);
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
throw new Error(`axe describe-ui output failed JSON.parse: ${err.message}`);
|
|
53
|
+
}
|
|
54
|
+
return normalizeAxeNode(parsed);
|
|
55
|
+
}
|
|
56
|
+
function sliceJsonValue(stdout) {
|
|
57
|
+
const trimmed = stdout.trim();
|
|
58
|
+
if (!trimmed)
|
|
59
|
+
return null;
|
|
60
|
+
// axe emits a JSON object as the root.
|
|
61
|
+
const objStart = trimmed.indexOf("{");
|
|
62
|
+
const objEnd = trimmed.lastIndexOf("}");
|
|
63
|
+
if (objStart !== -1 && objEnd > objStart) {
|
|
64
|
+
return trimmed.slice(objStart, objEnd + 1);
|
|
65
|
+
}
|
|
66
|
+
const arrStart = trimmed.indexOf("[");
|
|
67
|
+
const arrEnd = trimmed.lastIndexOf("]");
|
|
68
|
+
if (arrStart !== -1 && arrEnd > arrStart) {
|
|
69
|
+
return trimmed.slice(arrStart, arrEnd + 1);
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
function normalizeAxeNode(raw) {
|
|
74
|
+
if (raw == null || typeof raw !== "object") {
|
|
75
|
+
return { raw: {} };
|
|
76
|
+
}
|
|
77
|
+
const obj = raw;
|
|
78
|
+
const out = { raw: obj };
|
|
79
|
+
const labelKey = pickFirstString(obj, [
|
|
80
|
+
"AXLabel",
|
|
81
|
+
"label",
|
|
82
|
+
"name",
|
|
83
|
+
"title",
|
|
84
|
+
]);
|
|
85
|
+
if (labelKey != null)
|
|
86
|
+
out.label = labelKey;
|
|
87
|
+
const idKey = pickFirstString(obj, ["AXIdentifier", "identifier", "id"]);
|
|
88
|
+
if (idKey != null)
|
|
89
|
+
out.identifier = idKey;
|
|
90
|
+
const roleKey = pickFirstString(obj, ["AXRole", "role", "type"]);
|
|
91
|
+
if (roleKey != null)
|
|
92
|
+
out.role = roleKey;
|
|
93
|
+
const frame = parseAxFrame(obj);
|
|
94
|
+
if (frame)
|
|
95
|
+
out.frame = frame;
|
|
96
|
+
const childrenRaw = obj.children ??
|
|
97
|
+
obj.AXChildren ??
|
|
98
|
+
obj.subviews;
|
|
99
|
+
if (Array.isArray(childrenRaw)) {
|
|
100
|
+
out.children = childrenRaw.map((c) => normalizeAxeNode(c));
|
|
101
|
+
}
|
|
102
|
+
return out;
|
|
103
|
+
}
|
|
104
|
+
function pickFirstString(obj, keys) {
|
|
105
|
+
for (const key of keys) {
|
|
106
|
+
const value = obj[key];
|
|
107
|
+
if (typeof value === "string" && value.length > 0) {
|
|
108
|
+
return value;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Pure: parse an `AXFrame`-style frame from a node. Accepts both AppKit-style
|
|
115
|
+
* dictionaries `{AXX: 0, AXY: 0, AXWidth: 100, AXHeight: 100}` and CGRect
|
|
116
|
+
* strings `"{{0,0},{100,100}}"`.
|
|
117
|
+
*/
|
|
118
|
+
export function parseAxFrame(obj) {
|
|
119
|
+
const direct = obj.frame ?? obj.AXFrame;
|
|
120
|
+
if (typeof direct === "string") {
|
|
121
|
+
return parseFrameString(direct);
|
|
122
|
+
}
|
|
123
|
+
if (direct && typeof direct === "object") {
|
|
124
|
+
const f = direct;
|
|
125
|
+
const x = pickFirstNumber(f, ["x", "AXX", "X"]);
|
|
126
|
+
const y = pickFirstNumber(f, ["y", "AXY", "Y"]);
|
|
127
|
+
const w = pickFirstNumber(f, ["width", "AXWidth", "Width"]);
|
|
128
|
+
const h = pickFirstNumber(f, ["height", "AXHeight", "Height"]);
|
|
129
|
+
if (x != null && y != null && w != null && h != null) {
|
|
130
|
+
return { x, y, width: w, height: h };
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
// Direct top-level keys, AppKit/AX style flat.
|
|
134
|
+
const x = pickFirstNumber(obj, ["AXX", "frameX"]);
|
|
135
|
+
const y = pickFirstNumber(obj, ["AXY", "frameY"]);
|
|
136
|
+
const w = pickFirstNumber(obj, ["AXWidth", "frameWidth"]);
|
|
137
|
+
const h = pickFirstNumber(obj, ["AXHeight", "frameHeight"]);
|
|
138
|
+
if (x != null && y != null && w != null && h != null) {
|
|
139
|
+
return { x, y, width: w, height: h };
|
|
140
|
+
}
|
|
141
|
+
return undefined;
|
|
142
|
+
}
|
|
143
|
+
function pickFirstNumber(obj, keys) {
|
|
144
|
+
for (const key of keys) {
|
|
145
|
+
const value = obj[key];
|
|
146
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
147
|
+
return value;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
function parseFrameString(s) {
|
|
153
|
+
const match = s.match(/\{\{\s*(-?\d+(?:\.\d+)?)\s*,\s*(-?\d+(?:\.\d+)?)\s*\}\s*,\s*\{\s*(-?\d+(?:\.\d+)?)\s*,\s*(-?\d+(?:\.\d+)?)\s*\}\s*\}/);
|
|
154
|
+
if (!match)
|
|
155
|
+
return undefined;
|
|
156
|
+
return {
|
|
157
|
+
x: parseFloat(match[1]),
|
|
158
|
+
y: parseFloat(match[2]),
|
|
159
|
+
width: parseFloat(match[3]),
|
|
160
|
+
height: parseFloat(match[4]),
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Pure: find the first descendant whose label or identifier matches `query`
|
|
165
|
+
* (exact match preferred, falls back to substring). Walks the tree
|
|
166
|
+
* depth-first.
|
|
167
|
+
*/
|
|
168
|
+
export function findElementByLabel(root, query) {
|
|
169
|
+
const exact = findFirst(root, (el) => el.label === query || el.identifier === query);
|
|
170
|
+
if (exact)
|
|
171
|
+
return exact;
|
|
172
|
+
return findFirst(root, (el) => (el.label != null && el.label.includes(query)) ||
|
|
173
|
+
(el.identifier != null && el.identifier.includes(query)));
|
|
174
|
+
}
|
|
175
|
+
function findFirst(root, predicate) {
|
|
176
|
+
if (predicate(root))
|
|
177
|
+
return root;
|
|
178
|
+
if (!root.children)
|
|
179
|
+
return null;
|
|
180
|
+
for (const child of root.children) {
|
|
181
|
+
const hit = findFirst(child, predicate);
|
|
182
|
+
if (hit)
|
|
183
|
+
return hit;
|
|
184
|
+
}
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
/** Pure: compute the center point of an `AxFrame` for tap targeting. */
|
|
188
|
+
export function centerOf(frame) {
|
|
189
|
+
return {
|
|
190
|
+
x: Math.round(frame.x + frame.width / 2),
|
|
191
|
+
y: Math.round(frame.y + frame.height / 2),
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Tap on the simulator. Resolves label/elementId targets via `describe-ui`
|
|
196
|
+
* before issuing the tap.
|
|
197
|
+
*/
|
|
198
|
+
export async function tap(udid, target) {
|
|
199
|
+
let coords;
|
|
200
|
+
if (target.kind === "coords") {
|
|
201
|
+
coords = { x: target.x, y: target.y };
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
const tree = await describeUI(udid);
|
|
205
|
+
const query = target.value;
|
|
206
|
+
const el = findElementByLabel(tree, query);
|
|
207
|
+
if (!el || !el.frame) {
|
|
208
|
+
throw new Error(`Could not locate element matching "${query}" in the current UI tree, or the element has no frame metadata.`);
|
|
209
|
+
}
|
|
210
|
+
coords = centerOf(el.frame);
|
|
211
|
+
}
|
|
212
|
+
const result = await runCommand("axe", [
|
|
213
|
+
"tap",
|
|
214
|
+
"--udid",
|
|
215
|
+
udid,
|
|
216
|
+
"-x",
|
|
217
|
+
String(coords.x),
|
|
218
|
+
"-y",
|
|
219
|
+
String(coords.y),
|
|
220
|
+
], { timeoutMs: 10_000 });
|
|
221
|
+
if (result.code !== 0) {
|
|
222
|
+
throw new Error(`axe tap (${coords.x},${coords.y}) failed (code ${result.code}): ${result.stderr || result.stdout}`);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
/** Swipe between two coordinates with optional duration in milliseconds. */
|
|
226
|
+
export async function swipe(udid, from, to, durationMs = 250) {
|
|
227
|
+
const result = await runCommand("axe", [
|
|
228
|
+
"swipe",
|
|
229
|
+
"--udid",
|
|
230
|
+
udid,
|
|
231
|
+
"--from",
|
|
232
|
+
`${from.x},${from.y}`,
|
|
233
|
+
"--to",
|
|
234
|
+
`${to.x},${to.y}`,
|
|
235
|
+
"--duration",
|
|
236
|
+
String(durationMs),
|
|
237
|
+
], { timeoutMs: 15_000 });
|
|
238
|
+
if (result.code !== 0) {
|
|
239
|
+
throw new Error(`axe swipe failed (code ${result.code}): ${result.stderr || result.stdout}`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
/** Type a string into the currently focused field. */
|
|
243
|
+
export async function typeText(udid, text) {
|
|
244
|
+
const result = await runCommand("axe", ["type", "--udid", udid, "--text", text], { timeoutMs: 15_000 });
|
|
245
|
+
if (result.code !== 0) {
|
|
246
|
+
throw new Error(`axe type failed (code ${result.code}): ${result.stderr || result.stdout}`);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
//# sourceMappingURL=axe.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"axe.js","sourceRoot":"","sources":["../../src/runtime/axe.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure parser for `xcodebuild -showBuildSettings -json` output.
|
|
3
|
+
*
|
|
4
|
+
* `xcodebuild` emits a JSON array of `{action, target, buildSettings}` objects
|
|
5
|
+
* on stdout, but may print non-JSON warnings on stderr/stdout before or after
|
|
6
|
+
* the array. We slice from the first `[` to the last `]` to extract the JSON
|
|
7
|
+
* payload defensively.
|
|
8
|
+
*
|
|
9
|
+
* When multiple targets exist (e.g. an aggregate target alongside the app
|
|
10
|
+
* target), we pick the one whose build settings declare `WRAPPER_EXTENSION === "app"`,
|
|
11
|
+
* which is the actual application bundle.
|
|
12
|
+
*/
|
|
13
|
+
export interface BuildSettings {
|
|
14
|
+
/** Absolute path to the build products directory (where the .app lands). */
|
|
15
|
+
builtProductsDir: string;
|
|
16
|
+
/** Bundle wrapper name, e.g. `MyApp.app`. */
|
|
17
|
+
wrapperName: string;
|
|
18
|
+
/** Executable name inside the bundle, e.g. `MyApp`. Used for `pgrep -ax`. */
|
|
19
|
+
executableName: string;
|
|
20
|
+
/** App bundle identifier, e.g. `com.example.MyApp`. */
|
|
21
|
+
productBundleIdentifier: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Parse `xcodebuild -showBuildSettings -json` stdout. Throws when the output
|
|
25
|
+
* cannot be parsed or when the matched target is missing required keys.
|
|
26
|
+
*/
|
|
27
|
+
export declare function parseBuildSettingsJson(stdout: string): BuildSettings;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure parser for `xcodebuild -showBuildSettings -json` output.
|
|
3
|
+
*
|
|
4
|
+
* `xcodebuild` emits a JSON array of `{action, target, buildSettings}` objects
|
|
5
|
+
* on stdout, but may print non-JSON warnings on stderr/stdout before or after
|
|
6
|
+
* the array. We slice from the first `[` to the last `]` to extract the JSON
|
|
7
|
+
* payload defensively.
|
|
8
|
+
*
|
|
9
|
+
* When multiple targets exist (e.g. an aggregate target alongside the app
|
|
10
|
+
* target), we pick the one whose build settings declare `WRAPPER_EXTENSION === "app"`,
|
|
11
|
+
* which is the actual application bundle.
|
|
12
|
+
*/
|
|
13
|
+
const REQUIRED_KEYS = [
|
|
14
|
+
"builtProductsDir",
|
|
15
|
+
"wrapperName",
|
|
16
|
+
"executableName",
|
|
17
|
+
"productBundleIdentifier",
|
|
18
|
+
];
|
|
19
|
+
const KEY_MAP = {
|
|
20
|
+
builtProductsDir: "BUILT_PRODUCTS_DIR",
|
|
21
|
+
wrapperName: "WRAPPER_NAME",
|
|
22
|
+
executableName: "EXECUTABLE_NAME",
|
|
23
|
+
productBundleIdentifier: "PRODUCT_BUNDLE_IDENTIFIER",
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Parse `xcodebuild -showBuildSettings -json` stdout. Throws when the output
|
|
27
|
+
* cannot be parsed or when the matched target is missing required keys.
|
|
28
|
+
*/
|
|
29
|
+
export function parseBuildSettingsJson(stdout) {
|
|
30
|
+
const sliced = sliceJsonArray(stdout);
|
|
31
|
+
if (!sliced) {
|
|
32
|
+
throw new Error("xcodebuild -showBuildSettings -json output did not contain a JSON array. Stdout begins: " +
|
|
33
|
+
stdout.slice(0, 200));
|
|
34
|
+
}
|
|
35
|
+
let parsed;
|
|
36
|
+
try {
|
|
37
|
+
parsed = JSON.parse(sliced);
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
throw new Error(`xcodebuild -showBuildSettings -json output failed JSON.parse: ${err.message}`);
|
|
41
|
+
}
|
|
42
|
+
if (!Array.isArray(parsed) || parsed.length === 0) {
|
|
43
|
+
throw new Error("xcodebuild -showBuildSettings -json returned an empty array — no targets found for the given scheme/destination.");
|
|
44
|
+
}
|
|
45
|
+
const entries = parsed;
|
|
46
|
+
const appEntry = pickAppTarget(entries);
|
|
47
|
+
if (!appEntry || !appEntry.buildSettings) {
|
|
48
|
+
throw new Error("Could not find a target with WRAPPER_EXTENSION=app in -showBuildSettings output. Verify the scheme builds an iOS application bundle.");
|
|
49
|
+
}
|
|
50
|
+
const settings = appEntry.buildSettings;
|
|
51
|
+
const result = {};
|
|
52
|
+
for (const key of REQUIRED_KEYS) {
|
|
53
|
+
const xcodeKey = KEY_MAP[key];
|
|
54
|
+
const value = settings[xcodeKey];
|
|
55
|
+
if (!value || value.length === 0) {
|
|
56
|
+
throw new Error(`xcodebuild -showBuildSettings missing required key '${xcodeKey}' for target '${appEntry.target ?? "<unknown>"}'. Cannot proceed without this value.`);
|
|
57
|
+
}
|
|
58
|
+
result[key] = value;
|
|
59
|
+
}
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Locate the JSON array within stdout. Returns the substring `[...]` or null
|
|
64
|
+
* when no balanced array is found.
|
|
65
|
+
*/
|
|
66
|
+
function sliceJsonArray(stdout) {
|
|
67
|
+
const start = stdout.indexOf("[");
|
|
68
|
+
if (start === -1)
|
|
69
|
+
return null;
|
|
70
|
+
const end = stdout.lastIndexOf("]");
|
|
71
|
+
if (end === -1 || end <= start)
|
|
72
|
+
return null;
|
|
73
|
+
return stdout.slice(start, end + 1);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Pick the entry whose build settings indicate an application bundle. When
|
|
77
|
+
* multiple match, the first wins — this is rare in practice and the caller
|
|
78
|
+
* can always pass a more specific scheme to disambiguate.
|
|
79
|
+
*/
|
|
80
|
+
function pickAppTarget(entries) {
|
|
81
|
+
for (const entry of entries) {
|
|
82
|
+
if (entry.buildSettings?.WRAPPER_EXTENSION === "app") {
|
|
83
|
+
return entry;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=buildSettings.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"buildSettings.js","sourceRoot":"","sources":["../../src/runtime/buildSettings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAmBH,MAAM,aAAa,GAA+B;IAChD,kBAAkB;IAClB,aAAa;IACb,gBAAgB;IAChB,yBAAyB;CAC1B,CAAC;AAEF,MAAM,OAAO,GAAwC;IACnD,gBAAgB,EAAE,oBAAoB;IACtC,WAAW,EAAE,cAAc;IAC3B,cAAc,EAAE,iBAAiB;IACjC,uBAAuB,EAAE,2BAA2B;CACrD,CAAC;AAEF;;;GAGG;AACH,MAAM,UAAU,sBAAsB,CAAC,MAAc;IACnD,MAAM,MAAM,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;IACtC,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CACb,0FAA0F;YACxF,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CACvB,CAAC;IACJ,CAAC;IACD,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAC9B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CACb,iEAAkE,GAAa,CAAC,OAAO,EAAE,CAC1F,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAClD,MAAM,IAAI,KAAK,CACb,kHAAkH,CACnH,CAAC;IACJ,CAAC;IACD,MAAM,OAAO,GAAG,MAAmC,CAAC;IACpD,MAAM,QAAQ,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;IACxC,IAAI,CAAC,QAAQ,IAAI,CAAC,QAAQ,CAAC,aAAa,EAAE,CAAC;QACzC,MAAM,IAAI,KAAK,CACb,sIAAsI,CACvI,CAAC;IACJ,CAAC;IACD,MAAM,QAAQ,GAAG,QAAQ,CAAC,aAAa,CAAC;IACxC,MAAM,MAAM,GAA2B,EAAE,CAAC;IAC1C,KAAK,MAAM,GAAG,IAAI,aAAa,EAAE,CAAC;QAChC,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;QAC9B,MAAM,KAAK,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC;QACjC,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACjC,MAAM,IAAI,KAAK,CACb,uDAAuD,QAAQ,iBAAiB,QAAQ,CAAC,MAAM,IAAI,WAAW,uCAAuC,CACtJ,CAAC;QACJ,CAAC;QACD,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;IACtB,CAAC;IACD,OAAO,MAAuB,CAAC;AACjC,CAAC;AAED;;;GAGG;AACH,SAAS,cAAc,CAAC,MAAc;IACpC,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAClC,IAAI,KAAK,KAAK,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IAC9B,MAAM,GAAG,GAAG,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IACpC,IAAI,GAAG,KAAK,CAAC,CAAC,IAAI,GAAG,IAAI,KAAK;QAAE,OAAO,IAAI,CAAC;IAC5C,OAAO,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,GAAG,CAAC,CAAC,CAAC;AACtC,CAAC;AAED;;;;GAIG;AACH,SAAS,aAAa,CACpB,OAAkC;IAElC,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,IAAI,KAAK,CAAC,aAAa,EAAE,iBAAiB,KAAK,KAAK,EAAE,CAAC;YACrD,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
|
package/dist/runtime/exec.d.ts
CHANGED
|
@@ -8,9 +8,16 @@ export interface RunCommandOptions {
|
|
|
8
8
|
cwd?: string;
|
|
9
9
|
/** Timeout in ms (kill the child if it exceeds this). */
|
|
10
10
|
timeoutMs?: number;
|
|
11
|
+
/**
|
|
12
|
+
* Extra environment variables to expose to the child process. When provided,
|
|
13
|
+
* these are MERGED on top of `process.env` (PATH, DEVELOPER_DIR, HOME and
|
|
14
|
+
* other inherited vars are preserved). Pass an empty object to inherit
|
|
15
|
+
* unchanged; pass `undefined` (or omit) for the same default.
|
|
16
|
+
*/
|
|
17
|
+
env?: Record<string, string>;
|
|
11
18
|
}
|
|
12
19
|
/**
|
|
13
|
-
* Run a command and collect stdout/stderr. Does not throw on non-zero exit code
|
|
20
|
+
* Run a command and collect stdout/stderr. Does not throw on non-zero exit code,
|
|
14
21
|
* the caller decides what's acceptable (e.g. `leaks` exits 1 when leaks are found,
|
|
15
22
|
* which is normal).
|
|
16
23
|
*/
|
package/dist/runtime/exec.js
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
/**
|
|
3
|
-
* Run a command and collect stdout/stderr. Does not throw on non-zero exit code
|
|
3
|
+
* Run a command and collect stdout/stderr. Does not throw on non-zero exit code,
|
|
4
4
|
* the caller decides what's acceptable (e.g. `leaks` exits 1 when leaks are found,
|
|
5
5
|
* which is normal).
|
|
6
6
|
*/
|
|
7
7
|
export function runCommand(cmd, args, opts = {}) {
|
|
8
8
|
return new Promise((resolve, reject) => {
|
|
9
|
-
const
|
|
9
|
+
const env = opts.env
|
|
10
|
+
? { ...process.env, ...opts.env }
|
|
11
|
+
: undefined;
|
|
12
|
+
const child = spawn(cmd, args, {
|
|
13
|
+
cwd: opts.cwd,
|
|
14
|
+
...(env ? { env } : {}),
|
|
15
|
+
});
|
|
10
16
|
let stdout = "";
|
|
11
17
|
let stderr = "";
|
|
12
18
|
let killedByTimeout = false;
|
package/dist/runtime/exec.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"exec.js","sourceRoot":"","sources":["../../src/runtime/exec.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"exec.js","sourceRoot":"","sources":["../../src/runtime/exec.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAsB3C;;;;GAIG;AACH,MAAM,UAAU,UAAU,CACxB,GAAW,EACX,IAAc,EACd,OAA0B,EAAE;IAE5B,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG;YAClB,CAAC,CAAC,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE;YACjC,CAAC,CAAC,SAAS,CAAC;QACd,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,EAAE,IAAI,EAAE;YAC7B,GAAG,EAAE,IAAI,CAAC,GAAG;YACb,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACxB,CAAC,CAAC;QACH,IAAI,MAAM,GAAG,EAAE,CAAC;QAChB,IAAI,MAAM,GAAG,EAAE,CAAC;QAChB,IAAI,eAAe,GAAG,KAAK,CAAC;QAC5B,IAAI,KAAiC,CAAC;QAEtC,IAAI,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,SAAS,GAAG,CAAC,EAAE,CAAC;YACzC,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;gBACtB,eAAe,GAAG,IAAI,CAAC;gBACvB,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YACxB,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;QACrB,CAAC;QAED,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE;YACxC,MAAM,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QACnC,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE;YACxC,MAAM,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QACnC,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;YACxB,IAAI,KAAK;gBAAE,YAAY,CAAC,KAAK,CAAC,CAAC;YAC/B,MAAM,CAAC,GAAG,CAAC,CAAC;QACd,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE;YACzB,IAAI,KAAK;gBAAE,YAAY,CAAC,KAAK,CAAC,CAAC;YAC/B,IAAI,eAAe,EAAE,CAAC;gBACpB,MAAM,CACJ,IAAI,KAAK,CACP,2BAA2B,IAAI,CAAC,SAAS,OAAO,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CACxE,CACF,CAAC;gBACF,OAAO;YACT,CAAC;YACD,OAAO,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC;QAChD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thin wrappers around `xcrun simctl` for booting, installing, and launching
|
|
3
|
+
* iOS apps on the iOS Simulator. Used by `bootAndLaunchForLeakInvestigation`
|
|
4
|
+
* to set up a leak-investigation environment with `MallocStackLogging=1`.
|
|
5
|
+
*
|
|
6
|
+
* Design notes:
|
|
7
|
+
* - All operations are idempotent where possible (boot ignores "already booted",
|
|
8
|
+
* launch uses --terminate-running-process to atomically replace any prior instance).
|
|
9
|
+
* - Env vars are propagated to the child via the `SIMCTL_CHILD_*` prefix
|
|
10
|
+
* convention enforced by simctl. The caller passes plain `MallocStackLogging=1`
|
|
11
|
+
* and we prefix it before invoking simctl.
|
|
12
|
+
*/
|
|
13
|
+
export interface SimulatorDevice {
|
|
14
|
+
udid: string;
|
|
15
|
+
name: string;
|
|
16
|
+
state: "Booted" | "Shutdown" | string;
|
|
17
|
+
/** OS runtime, e.g. "iOS 17.5" or "iOS-17-5". */
|
|
18
|
+
runtime: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Find the first booted simulator. Returns null when none are booted.
|
|
22
|
+
* Used as a fallback when the caller doesn't specify a simulator.
|
|
23
|
+
*/
|
|
24
|
+
export declare function findBootedSimulator(): Promise<SimulatorDevice | null>;
|
|
25
|
+
/**
|
|
26
|
+
* Find a simulator by display name (e.g. "iPhone 15"), optionally constrained
|
|
27
|
+
* to a runtime version. Returns null when no match.
|
|
28
|
+
*
|
|
29
|
+
* @param os Either "latest" (highest available runtime), an explicit version
|
|
30
|
+
* like "17.5", or undefined (any runtime).
|
|
31
|
+
*/
|
|
32
|
+
export declare function findSimulatorByName(name: string, os?: string): Promise<SimulatorDevice | null>;
|
|
33
|
+
/**
|
|
34
|
+
* Boot a simulator. Idempotent: if the device is already booted, returns
|
|
35
|
+
* cleanly. Then waits via `simctl bootstatus -b` until SpringBoard is ready,
|
|
36
|
+
* which prevents flaky `install` calls right after boot.
|
|
37
|
+
*/
|
|
38
|
+
export declare function bootSimulator(udid: string): Promise<void>;
|
|
39
|
+
/**
|
|
40
|
+
* Install (or reinstall) an app bundle on a simulator. Idempotent by design;
|
|
41
|
+
* simctl overwrites a prior install at the same bundle identifier.
|
|
42
|
+
*/
|
|
43
|
+
export declare function installApp(udid: string, appPath: string): Promise<void>;
|
|
44
|
+
export interface LaunchAppResult {
|
|
45
|
+
/** Simulator-internal PID printed by simctl (NOT the host PID). */
|
|
46
|
+
simPid: number;
|
|
47
|
+
bundleId: string;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Launch an app on a booted simulator with the given env vars and launch args.
|
|
51
|
+
* Uses `--terminate-running-process` so any existing instance is replaced
|
|
52
|
+
* atomically (no race where stale env vars persist).
|
|
53
|
+
*
|
|
54
|
+
* Env vars are propagated to the child via the `SIMCTL_CHILD_*` prefix that
|
|
55
|
+
* simctl honors. Pass plain keys (e.g. `MallocStackLogging`); we prefix.
|
|
56
|
+
*/
|
|
57
|
+
export declare function launchApp(udid: string, bundleId: string, env?: Record<string, string>, launchArgs?: string[]): Promise<LaunchAppResult>;
|
|
58
|
+
/**
|
|
59
|
+
* Take a screenshot of the simulator's screen and write it to `outputPath`.
|
|
60
|
+
* Wraps `xcrun simctl io <udid> screenshot <path>`.
|
|
61
|
+
*/
|
|
62
|
+
export declare function takeScreenshot(udid: string, outputPath: string): Promise<void>;
|
|
63
|
+
/** Pure: parse the simulator-internal PID from `simctl launch` stdout. Exposed for tests. */
|
|
64
|
+
export declare function parseLaunchPid(stdout: string, bundleId: string): number | null;
|
|
65
|
+
/** Pure: parse a `simctl list devices --json` payload into a flat list. Exposed for tests. */
|
|
66
|
+
export declare function parseSimctlDevices(stdout: string): SimulatorDevice[];
|
|
67
|
+
/** Pure: produce a sortable numeric key from a runtime identifier. */
|
|
68
|
+
export declare function runtimeSortKey(runtime: string): number;
|