doc-detective 4.27.0 → 4.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/tests/appSurface.d.ts +2 -0
- package/dist/core/tests/appSurface.d.ts.map +1 -1
- package/dist/core/tests/appSurface.js +11 -10
- package/dist/core/tests/appSurface.js.map +1 -1
- package/dist/core/tests/mobileBrowser.d.ts +3 -1
- package/dist/core/tests/mobileBrowser.d.ts.map +1 -1
- package/dist/core/tests/mobileBrowser.js +6 -7
- package/dist/core/tests/mobileBrowser.js.map +1 -1
- package/dist/hints/context.d.ts +16 -0
- package/dist/hints/context.d.ts.map +1 -1
- package/dist/hints/context.js +34 -0
- package/dist/hints/context.js.map +1 -1
- package/dist/hints/hints.d.ts.map +1 -1
- package/dist/hints/hints.js +17 -0
- package/dist/hints/hints.js.map +1 -1
- package/dist/hints/types.d.ts +14 -0
- package/dist/hints/types.d.ts.map +1 -1
- package/dist/index.cjs +370 -269
- package/dist/runtime/cacheDir.d.ts +5 -0
- package/dist/runtime/cacheDir.d.ts.map +1 -1
- package/dist/runtime/cacheDir.js +3 -0
- package/dist/runtime/cacheDir.js.map +1 -1
- package/dist/runtime/iosInstaller.d.ts +44 -1
- package/dist/runtime/iosInstaller.d.ts.map +1 -1
- package/dist/runtime/iosInstaller.js +381 -13
- package/dist/runtime/iosInstaller.js.map +1 -1
- package/dist/runtime/lock.d.ts +56 -0
- package/dist/runtime/lock.d.ts.map +1 -0
- package/dist/runtime/lock.js +218 -0
- package/dist/runtime/lock.js.map +1 -0
- package/dist/runtime/wdaProducts.d.ts +147 -0
- package/dist/runtime/wdaProducts.d.ts.map +1 -0
- package/dist/runtime/wdaProducts.js +234 -0
- package/dist/runtime/wdaProducts.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lock.d.ts","sourceRoot":"","sources":["../../src/runtime/lock.ts"],"names":[],"mappings":"AAoBA,MAAM,WAAW,MAAM;IACrB,SAAS,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC;IAC9D,aAAa,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7C,YAAY,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;IACzC,MAAM,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;IACzE,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5C;AAED,MAAM,WAAW,QAAQ;IACvB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,uFAAuF;IACvF,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IACtC,sFAAsF;IACtF,aAAa,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,IAAI,EAAE,EAAE,EAAE,MAAM,KAAK,MAAM,IAAI,CAAC;CAC5D;AAED,MAAM,WAAW,kBAAkB;IACjC,kFAAkF;IAClF,GAAG,EAAE,MAAM,CAAC;IACZ,uEAAuE;IACvE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iDAAiD;IACjD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wDAAwD;IACxD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4EAA4E;IAC5E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,QAAQ,CAAC;CACjB;AAED,MAAM,WAAW,UAAU;IACzB,sEAAsE;IACtE,OAAO,IAAI,IAAI,CAAC;CACjB;AA+BD;;;;GAIG;AACH,wBAAsB,WAAW,CAC/B,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAqJ5B;AAoBD;;;;GAIG;AACH,wBAAsB,QAAQ,CAAC,CAAC,EAC9B,OAAO,EAAE,kBAAkB,EAC3B,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,GACvB,OAAO,CAAC;IAAE,QAAQ,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,CAAC,CAAA;CAAE,CAAC,CAQ5C"}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
// Cross-process advisory lock (mkdir-as-lock) for single-writer critical
|
|
2
|
+
// sections in the runtime cache — first consumer: the WebDriverAgent prebuild
|
|
3
|
+
// in `install ios` (docs/design/ios-wda-prebuild.md), where a legitimate
|
|
4
|
+
// xcodebuild can hold the lock for ~20 minutes.
|
|
5
|
+
//
|
|
6
|
+
// Staleness is a HEARTBEAT LEASE, not age-since-acquire: the holder refreshes
|
|
7
|
+
// the metadata timestamp on a short interval, and a contender may take over
|
|
8
|
+
// only when that heartbeat has gone stale (many missed refreshes) or, on the
|
|
9
|
+
// same host, the recorded pid is dead. Lock age alone never permits takeover —
|
|
10
|
+
// a TTL long enough to cover a slow build would make a crashed holder block
|
|
11
|
+
// the next build for that whole window, and a shorter one would steal live
|
|
12
|
+
// builds.
|
|
13
|
+
//
|
|
14
|
+
// All effects (fs, clock, sleep, pid liveness, the heartbeat timer) are
|
|
15
|
+
// injectable so the lock is hermetically unit-testable.
|
|
16
|
+
import fsDefault from "node:fs";
|
|
17
|
+
import os from "node:os";
|
|
18
|
+
import path from "node:path";
|
|
19
|
+
function defaultIsPidAlive(pid) {
|
|
20
|
+
try {
|
|
21
|
+
process.kill(pid, 0);
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
catch (err) {
|
|
25
|
+
// Dead ONLY on ESRCH (no such process). Any other error — EPERM
|
|
26
|
+
// (exists, different user) or something unexpected — reads as alive:
|
|
27
|
+
// this check licenses stealing a lock and deleting a live xcodebuild's
|
|
28
|
+
// output, so uncertainty must never count as dead. Matches the
|
|
29
|
+
// repo's other liveness probes (src/core/tests.ts).
|
|
30
|
+
return err?.code !== "ESRCH";
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function defaultStartInterval(fn, ms) {
|
|
34
|
+
const timer = setInterval(fn, ms);
|
|
35
|
+
timer.unref?.();
|
|
36
|
+
return () => clearInterval(timer);
|
|
37
|
+
}
|
|
38
|
+
const OWNER_FILE = "owner.json";
|
|
39
|
+
/**
|
|
40
|
+
* Acquire the advisory lock at `dir`, waiting up to `waitMs`. Returns a
|
|
41
|
+
* handle whose `release()` frees the lock, or `null` when the wait elapsed
|
|
42
|
+
* with the lock still held by a live owner.
|
|
43
|
+
*/
|
|
44
|
+
export async function acquireLock(options) {
|
|
45
|
+
const { dir, waitMs = 10 * 60_000, pollMs = 1_000, heartbeatMs = 30_000, staleMs = 5 * 60_000, deps = {}, } = options;
|
|
46
|
+
const fs = deps.fs ?? fsDefault;
|
|
47
|
+
const now = deps.now ?? Date.now;
|
|
48
|
+
const sleep = deps.sleep ??
|
|
49
|
+
((ms) => new Promise((r) => {
|
|
50
|
+
const timer = setTimeout(r, ms);
|
|
51
|
+
timer.unref?.();
|
|
52
|
+
}));
|
|
53
|
+
const pid = deps.pid ?? process.pid;
|
|
54
|
+
const hostname = deps.hostname ?? os.hostname();
|
|
55
|
+
const isPidAlive = deps.isPidAlive ?? defaultIsPidAlive;
|
|
56
|
+
const startInterval = deps.startInterval ?? defaultStartInterval;
|
|
57
|
+
const ownerPath = `${dir}/${OWNER_FILE}`;
|
|
58
|
+
const deadline = now() + waitMs;
|
|
59
|
+
// The parent provably exists after this; the loop body only runs the
|
|
60
|
+
// exclusive-mkdir arbiter plus the staleness read.
|
|
61
|
+
fs.mkdirSync(path.dirname(dir), { recursive: true });
|
|
62
|
+
// Missing/corrupt owner metadata gets one poll-cycle of grace before it is
|
|
63
|
+
// treated as stealable: a healthy acquirer writes owner.json microseconds
|
|
64
|
+
// after its mkdir wins, so metadata that is STILL absent a poll later marks
|
|
65
|
+
// a crash in that window, not a race with a live acquirer.
|
|
66
|
+
let metaMissingSince = null;
|
|
67
|
+
for (;;) {
|
|
68
|
+
// Attempt the exclusive mkdir that IS the lock. EEXIST means contention;
|
|
69
|
+
// anything else propagates.
|
|
70
|
+
try {
|
|
71
|
+
fs.mkdirSync(dir);
|
|
72
|
+
return takeOwnership();
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
if (err?.code !== "EEXIST")
|
|
76
|
+
throw err;
|
|
77
|
+
}
|
|
78
|
+
// Held by someone. Decide whether the holder is recoverable. Metadata
|
|
79
|
+
// that parses but has the wrong shape (non-numeric heartbeatAt would make
|
|
80
|
+
// the staleness comparison NaN — permanently false) is treated exactly
|
|
81
|
+
// like missing metadata: grace, then stealable.
|
|
82
|
+
const meta = readOwnerMeta(fs, ownerPath);
|
|
83
|
+
let stealable = false;
|
|
84
|
+
if (meta) {
|
|
85
|
+
metaMissingSince = null;
|
|
86
|
+
const heartbeatStale = now() - meta.heartbeatAt > staleMs;
|
|
87
|
+
const deadOnThisHost = meta.hostname === hostname && !isPidAlive(meta.pid);
|
|
88
|
+
stealable = heartbeatStale || deadOnThisHost;
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
metaMissingSince ??= now();
|
|
92
|
+
stealable = now() - metaMissingSince >= pollMs;
|
|
93
|
+
}
|
|
94
|
+
if (stealable) {
|
|
95
|
+
// Takeover is arbitrated by RENAME, not rm: exactly one of N
|
|
96
|
+
// simultaneous stealers wins the rename of the dead holder's dir to a
|
|
97
|
+
// unique trash name, so a loser can never delete a lock a winner just
|
|
98
|
+
// re-acquired (the classic rm-based double-steal race). The loser's
|
|
99
|
+
// rename throws (ENOENT) and it loops back to find the winner's fresh
|
|
100
|
+
// lock. Trash dirs are cleaned best-effort here and are also
|
|
101
|
+
// markerless siblings to the WDA prune pass.
|
|
102
|
+
const trash = `${dir}.stale-${pid}-${now()}`;
|
|
103
|
+
try {
|
|
104
|
+
fs.renameSync(dir, trash);
|
|
105
|
+
try {
|
|
106
|
+
fs.rmSync(trash, { recursive: true, force: true });
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
/* best-effort trash cleanup */
|
|
110
|
+
}
|
|
111
|
+
// Won the takeover — retry the mkdir immediately (guaranteed
|
|
112
|
+
// progress, no busy-spin risk: the dir is gone).
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
// Lost the takeover race, or the dir is un-removable (EACCES from a
|
|
117
|
+
// different-user run). Fall through to the deadline check and sleep
|
|
118
|
+
// so an un-stealable stale lock still times out instead of spinning.
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
if (now() >= deadline)
|
|
122
|
+
return null;
|
|
123
|
+
await sleep(pollMs);
|
|
124
|
+
}
|
|
125
|
+
function takeOwnership() {
|
|
126
|
+
const acquiredAt = now();
|
|
127
|
+
const writeMeta = () => {
|
|
128
|
+
// acquiredAt is fixed at acquisition; only the heartbeat refreshes.
|
|
129
|
+
const meta = {
|
|
130
|
+
pid,
|
|
131
|
+
hostname,
|
|
132
|
+
acquiredAt,
|
|
133
|
+
heartbeatAt: now(),
|
|
134
|
+
};
|
|
135
|
+
fs.writeFileSync(ownerPath, JSON.stringify(meta));
|
|
136
|
+
};
|
|
137
|
+
// Still-ours check: after a lease takeover (this process was suspended
|
|
138
|
+
// past staleMs and a contender legitimately stole the lock), the old
|
|
139
|
+
// holder must neither overwrite the new owner's metadata nor remove the
|
|
140
|
+
// new owner's lock dir on release.
|
|
141
|
+
const isStillOwner = () => {
|
|
142
|
+
const meta = readOwnerMeta(fs, ownerPath);
|
|
143
|
+
return meta !== null && meta.pid === pid && meta.hostname === hostname;
|
|
144
|
+
};
|
|
145
|
+
writeMeta();
|
|
146
|
+
let lost = false;
|
|
147
|
+
const stopHeartbeat = startInterval(() => {
|
|
148
|
+
// Refresh the lease — but never resurrect a stolen lock. Never throw
|
|
149
|
+
// from a timer: if the dir vanished (external cleanup), the refresh
|
|
150
|
+
// just fails and release() stays safe.
|
|
151
|
+
try {
|
|
152
|
+
if (!isStillOwner()) {
|
|
153
|
+
lost = true;
|
|
154
|
+
stopHeartbeat();
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
writeMeta();
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
/* best-effort */
|
|
161
|
+
}
|
|
162
|
+
}, heartbeatMs);
|
|
163
|
+
let released = false;
|
|
164
|
+
return {
|
|
165
|
+
release() {
|
|
166
|
+
if (released)
|
|
167
|
+
return;
|
|
168
|
+
released = true;
|
|
169
|
+
stopHeartbeat();
|
|
170
|
+
if (lost)
|
|
171
|
+
return;
|
|
172
|
+
try {
|
|
173
|
+
// Same guard on the release path: only remove the dir while the
|
|
174
|
+
// metadata is still ours.
|
|
175
|
+
if (!isStillOwner())
|
|
176
|
+
return;
|
|
177
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
/* already gone */
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
/** Parse and shape-validate owner metadata; null for missing/corrupt/wrong-shape. */
|
|
187
|
+
function readOwnerMeta(fs, ownerPath) {
|
|
188
|
+
try {
|
|
189
|
+
const parsed = JSON.parse(String(fs.readFileSync(ownerPath)));
|
|
190
|
+
if (typeof parsed?.pid !== "number" ||
|
|
191
|
+
typeof parsed?.hostname !== "string" ||
|
|
192
|
+
typeof parsed?.acquiredAt !== "number" ||
|
|
193
|
+
typeof parsed?.heartbeatAt !== "number") {
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
return parsed;
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Run `fn` while holding the lock, releasing on both success and throw.
|
|
204
|
+
* Returns `null` without running `fn` when the lock could not be acquired
|
|
205
|
+
* within the wait bound.
|
|
206
|
+
*/
|
|
207
|
+
export async function withLock(options, fn) {
|
|
208
|
+
const handle = await acquireLock(options);
|
|
209
|
+
if (!handle)
|
|
210
|
+
return { acquired: false };
|
|
211
|
+
try {
|
|
212
|
+
return { acquired: true, result: await fn() };
|
|
213
|
+
}
|
|
214
|
+
finally {
|
|
215
|
+
handle.release();
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
//# sourceMappingURL=lock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lock.js","sourceRoot":"","sources":["../../src/runtime/lock.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { type CacheDirContext } from "./cacheDir.js";
|
|
2
|
+
export interface XcodeVersion {
|
|
3
|
+
/** e.g. "16.4" */
|
|
4
|
+
version: string;
|
|
5
|
+
/** e.g. "16F6" — distinguishes two images shipping the same marketing version. */
|
|
6
|
+
build: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Minimum Xcode major for the WDA prebuild. Below this the
|
|
10
|
+
* `build-for-testing` invocation against the generic iOS Simulator
|
|
11
|
+
* destination (and the appium-xcuitest-driver versions we install) are not
|
|
12
|
+
* supported — skip with upgrade guidance instead of attempting a doomed
|
|
13
|
+
* build. Floor per the design doc's "likely 14+"; revisit against the live
|
|
14
|
+
* macOS leg if it ever disagrees.
|
|
15
|
+
*/
|
|
16
|
+
export declare const MIN_XCODE_MAJOR = 14;
|
|
17
|
+
/**
|
|
18
|
+
* Parse `xcodebuild -version` output:
|
|
19
|
+
*
|
|
20
|
+
* Xcode 16.4
|
|
21
|
+
* Build version 16F6
|
|
22
|
+
*
|
|
23
|
+
* Returns null when the output doesn't look like full Xcode (e.g. the
|
|
24
|
+
* Command Line Tools error text) — only full Xcode can build WDA.
|
|
25
|
+
*/
|
|
26
|
+
export declare function parseXcodebuildVersion(output: unknown): XcodeVersion | null;
|
|
27
|
+
export declare function xcodeMajor(xcode: XcodeVersion): number;
|
|
28
|
+
/** The managed WDA root: <cacheDir>/ios/wda. */
|
|
29
|
+
export declare function getWdaRoot(ctx?: CacheDirContext): string;
|
|
30
|
+
export interface WdaFs {
|
|
31
|
+
existsSync(p: string): boolean;
|
|
32
|
+
readFileSync(p: string): string | Buffer;
|
|
33
|
+
writeFileSync(p: string, data: string): void;
|
|
34
|
+
mkdirSync(p: string, opts?: {
|
|
35
|
+
recursive?: boolean;
|
|
36
|
+
}): unknown;
|
|
37
|
+
renameSync(from: string, to: string): void;
|
|
38
|
+
rmSync(p: string, opts?: {
|
|
39
|
+
recursive?: boolean;
|
|
40
|
+
force?: boolean;
|
|
41
|
+
}): void;
|
|
42
|
+
readdirSync(p: string): string[];
|
|
43
|
+
statSync(p: string): {
|
|
44
|
+
mtimeMs: number;
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The cache key a WDA build is valid for: Xcode marketing version + build id
|
|
49
|
+
* (two runner images can ship the same marketing version) + the exact
|
|
50
|
+
* appium-xcuitest-driver version whose bundled WDA source was compiled.
|
|
51
|
+
*/
|
|
52
|
+
export declare function computeWdaKey(xcode: XcodeVersion, driverVersion: string): string;
|
|
53
|
+
/** Where xcodebuild's products land inside a key dir. */
|
|
54
|
+
export declare const RUNNER_APP_RELATIVE: string;
|
|
55
|
+
export declare const PRODUCTS_MARKER = "products.json";
|
|
56
|
+
export declare const LAST_USED_STAMP = "last-used";
|
|
57
|
+
/** The writer's advisory-lock dir name under the WDA root — one name shared
|
|
58
|
+
* by the acquire call and the prune pass's skip check so they can't drift. */
|
|
59
|
+
export declare const WDA_LOCK_DIRNAME = ".lock";
|
|
60
|
+
export interface WdaProductsMarker {
|
|
61
|
+
key: string;
|
|
62
|
+
driverVersion: string;
|
|
63
|
+
/** Diagnostic metadata — validated shape only, never consumed by readers. */
|
|
64
|
+
xcode?: XcodeVersion;
|
|
65
|
+
runnerApp?: string;
|
|
66
|
+
builtAt?: string;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Read and validate a key dir's completeness marker. Null on any miss:
|
|
70
|
+
* absent/corrupt marker (a crashed half-built dir never wrote one — that is
|
|
71
|
+
* the lock-free correctness story for readers) or a key dir whose Runner app
|
|
72
|
+
* is gone. The Runner check uses the layout-relative path, NOT the absolute
|
|
73
|
+
* path recorded in the marker, so a relocated cache root (moved home dir,
|
|
74
|
+
* container bind-mount, CI cache restored under a different path) keeps its
|
|
75
|
+
* valid products instead of misreading them all as stale.
|
|
76
|
+
*/
|
|
77
|
+
export declare function readProductsMarker(keyDir: string, fs: WdaFs): WdaProductsMarker | null;
|
|
78
|
+
/**
|
|
79
|
+
* Touch a key dir's last-used stamp — the prune-freshness signal shared by
|
|
80
|
+
* the installer (writes it at build time, stats it when pruning) and the
|
|
81
|
+
* session locator (touches it on every valid hit). Best-effort: a failed
|
|
82
|
+
* stamp only risks an early prune much later.
|
|
83
|
+
*/
|
|
84
|
+
export declare function touchLastUsed(keyDir: string, fs: WdaFs, now: () => number): void;
|
|
85
|
+
/**
|
|
86
|
+
* Minimum appium-xcuitest-driver MAJOR whose prebuilt-WDA consumption
|
|
87
|
+
* (`appium:usePrebuiltWDA` + `appium:derivedDataPath`) this locator has been
|
|
88
|
+
* validated against (the live macOS fixture legs run the 10.x line that
|
|
89
|
+
* doc-detective's declared range installs). Prebuilt handling is
|
|
90
|
+
* driver-version-sensitive across older majors (`prebuiltWDAPath` /
|
|
91
|
+
* `useXctestrunFile` variants, differing .xctestrun handling), so anything
|
|
92
|
+
* below the floor gets a plain fallback — today's build-in-session behavior —
|
|
93
|
+
* never a guess.
|
|
94
|
+
*/
|
|
95
|
+
export declare const MIN_PREBUILT_WDA_DRIVER_MAJOR = 10;
|
|
96
|
+
export interface LocateManagedWdaOptions {
|
|
97
|
+
ctx?: CacheDirContext;
|
|
98
|
+
fs?: WdaFs;
|
|
99
|
+
platform?: NodeJS.Platform;
|
|
100
|
+
probeXcode?: () => XcodeVersion | null;
|
|
101
|
+
resolveDriverVersion?: (name: string, ctx: CacheDirContext) => string | null;
|
|
102
|
+
/** Test override for the managed WDA root (default: <cacheDir>/ios/wda). */
|
|
103
|
+
wdaRootDir?: string;
|
|
104
|
+
now?: () => number;
|
|
105
|
+
}
|
|
106
|
+
export interface ManagedWdaHit {
|
|
107
|
+
key: string;
|
|
108
|
+
/** Value for appium:derivedDataPath — the keyed DerivedData dir. */
|
|
109
|
+
derivedDataPath: string;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Pure managed-products locator for iOS session capability builders: find
|
|
113
|
+
* the prebuilt WDA products matching the CURRENT toolchain (installed
|
|
114
|
+
* driver version × host Xcode). On a valid hit, touch the last-used stamp
|
|
115
|
+
* (the prune signal) and return the derivedDataPath to consume read-only
|
|
116
|
+
* with `appium:usePrebuiltWDA`. Any miss — wrong platform, unresolvable
|
|
117
|
+
* driver, driver below the supported floor, no full Xcode, absent/stale
|
|
118
|
+
* marker — returns null and the session builds WDA itself, exactly today's
|
|
119
|
+
* behavior. Never throws.
|
|
120
|
+
*/
|
|
121
|
+
export declare function locateManagedWda(options?: LocateManagedWdaOptions): ManagedWdaHit | null;
|
|
122
|
+
/**
|
|
123
|
+
* Apply the WDA derived-data capabilities for an iOS XCUITest session — the
|
|
124
|
+
* ONE place the env-override-vs-managed-products precedence and the exact
|
|
125
|
+
* capability pair live, shared by the app-surface and mobile-web builders so
|
|
126
|
+
* the two cannot drift (the pair is driver-version-sensitive; see
|
|
127
|
+
* MIN_PREBUILT_WDA_DRIVER_MAJOR).
|
|
128
|
+
*
|
|
129
|
+
* Precedence: DOC_DETECTIVE_IOS_WDA_DERIVED_DATA_PATH set → exactly the
|
|
130
|
+
* historical behavior (`derivedDataPath` only, caller owns semantics; the
|
|
131
|
+
* managed locator is not consulted). Otherwise a managed-products hit sets
|
|
132
|
+
* both `appium:derivedDataPath` (the keyed DerivedData) and
|
|
133
|
+
* `appium:usePrebuiltWDA` (read-only consumption — the concurrency answer).
|
|
134
|
+
* No locator or a miss → no capabilities change (today's behavior: the
|
|
135
|
+
* session builds WDA itself in a throwaway per-session temp dir).
|
|
136
|
+
*/
|
|
137
|
+
export declare function applyManagedWdaCapabilities(capabilities: Record<string, any>, locateWda?: () => ManagedWdaHit | null): void;
|
|
138
|
+
/**
|
|
139
|
+
* Locate the WebDriverAgent source bundled with the installed
|
|
140
|
+
* appium-xcuitest-driver: walk up from the driver's resolved entry looking
|
|
141
|
+
* for a `node_modules/appium-webdriveragent` that actually contains the
|
|
142
|
+
* Xcode project. The walk (rather than a hardcoded relative path) is what
|
|
143
|
+
* absorbs npm's hoisting variability — nested under the driver on some
|
|
144
|
+
* installs, hoisted to the runtime root on others.
|
|
145
|
+
*/
|
|
146
|
+
export declare function findWdaSource(driverEntryPath: string, fs: Pick<WdaFs, "existsSync">): string | null;
|
|
147
|
+
//# sourceMappingURL=wdaProducts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wdaProducts.d.ts","sourceRoot":"","sources":["../../src/runtime/wdaProducts.ts"],"names":[],"mappings":"AAaA,OAAO,EAAe,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC;AAGlE,MAAM,WAAW,YAAY;IAC3B,kBAAkB;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,kFAAkF;IAClF,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,KAAK,CAAC;AAElC;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,OAAO,GAAG,YAAY,GAAG,IAAI,CAM3E;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM,CAGtD;AAED,gDAAgD;AAChD,wBAAgB,UAAU,CAAC,GAAG,GAAE,eAAoB,GAAG,MAAM,CAE5D;AAID,MAAM,WAAW,KAAK;IACpB,UAAU,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAC/B,YAAY,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;IACzC,aAAa,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7C,SAAS,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC;IAC9D,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3C,MAAM,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;IACzE,WAAW,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IACjC,QAAQ,CAAC,CAAC,EAAE,MAAM,GAAG;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;CAC1C;AAWD;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,YAAY,EACnB,aAAa,EAAE,MAAM,GACpB,MAAM,CAIR;AAED,yDAAyD;AACzD,eAAO,MAAM,mBAAmB,QAM/B,CAAC;AAEF,eAAO,MAAM,eAAe,kBAAkB,CAAC;AAC/C,eAAO,MAAM,eAAe,cAAc,CAAC;AAE3C;8EAC8E;AAC9E,eAAO,MAAM,gBAAgB,UAAU,CAAC;AAExC,MAAM,WAAW,iBAAiB;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,aAAa,EAAE,MAAM,CAAC;IACtB,6EAA6E;IAC7E,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,MAAM,EACd,EAAE,EAAE,KAAK,GACR,iBAAiB,GAAG,IAAI,CAgB1B;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,MAAM,EACd,EAAE,EAAE,KAAK,EACT,GAAG,EAAE,MAAM,MAAM,GAChB,IAAI,CAMN;AAID;;;;;;;;;GASG;AACH,eAAO,MAAM,6BAA6B,KAAK,CAAC;AAyBhD,MAAM,WAAW,uBAAuB;IACtC,GAAG,CAAC,EAAE,eAAe,CAAC;IACtB,EAAE,CAAC,EAAE,KAAK,CAAC;IACX,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;IAC3B,UAAU,CAAC,EAAE,MAAM,YAAY,GAAG,IAAI,CAAC;IACvC,oBAAoB,CAAC,EAAE,CACrB,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,eAAe,KACjB,MAAM,GAAG,IAAI,CAAC;IACnB,4EAA4E;IAC5E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,oEAAoE;IACpE,eAAe,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,GAAE,uBAA4B,GACpC,aAAa,GAAG,IAAI,CA2CtB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,2BAA2B,CACzC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EACjC,SAAS,CAAC,EAAE,MAAM,aAAa,GAAG,IAAI,GACrC,IAAI,CAWN;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAC3B,eAAe,EAAE,MAAM,EACvB,EAAE,EAAE,IAAI,CAAC,KAAK,EAAE,YAAY,CAAC,GAC5B,MAAM,GAAG,IAAI,CAYf"}
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
// Managed WebDriverAgent build products: the shared key/probe/marker
|
|
2
|
+
// vocabulary between the `install ios` prebuild (writer) and the session-time
|
|
3
|
+
// locator (reader). See docs/design/ios-wda-prebuild.md.
|
|
4
|
+
//
|
|
5
|
+
// Layout under the cache dir:
|
|
6
|
+
// <cacheDir>/ios/wda/<key>/DerivedData/ xcodebuild -derivedDataPath target
|
|
7
|
+
// <cacheDir>/ios/wda/<key>/products.json completeness marker, written LAST
|
|
8
|
+
// <cacheDir>/ios/wda/<key>/last-used sidecar stamp touched by readers
|
|
9
|
+
// <cacheDir>/ios/wda/.lock/ writer's advisory lock
|
|
10
|
+
import { spawnSync } from "node:child_process";
|
|
11
|
+
import fsDefault from "node:fs";
|
|
12
|
+
import path from "node:path";
|
|
13
|
+
import { getCacheDir } from "./cacheDir.js";
|
|
14
|
+
import { resolveHeavyDepVersion } from "./loader.js";
|
|
15
|
+
/**
|
|
16
|
+
* Minimum Xcode major for the WDA prebuild. Below this the
|
|
17
|
+
* `build-for-testing` invocation against the generic iOS Simulator
|
|
18
|
+
* destination (and the appium-xcuitest-driver versions we install) are not
|
|
19
|
+
* supported — skip with upgrade guidance instead of attempting a doomed
|
|
20
|
+
* build. Floor per the design doc's "likely 14+"; revisit against the live
|
|
21
|
+
* macOS leg if it ever disagrees.
|
|
22
|
+
*/
|
|
23
|
+
export const MIN_XCODE_MAJOR = 14;
|
|
24
|
+
/**
|
|
25
|
+
* Parse `xcodebuild -version` output:
|
|
26
|
+
*
|
|
27
|
+
* Xcode 16.4
|
|
28
|
+
* Build version 16F6
|
|
29
|
+
*
|
|
30
|
+
* Returns null when the output doesn't look like full Xcode (e.g. the
|
|
31
|
+
* Command Line Tools error text) — only full Xcode can build WDA.
|
|
32
|
+
*/
|
|
33
|
+
export function parseXcodebuildVersion(output) {
|
|
34
|
+
const text = String(output ?? "");
|
|
35
|
+
const version = /^\s*Xcode\s+(\S+)/m.exec(text)?.[1];
|
|
36
|
+
const build = /^\s*Build version\s+(\S+)/m.exec(text)?.[1];
|
|
37
|
+
if (!version || !build)
|
|
38
|
+
return null;
|
|
39
|
+
return { version, build };
|
|
40
|
+
}
|
|
41
|
+
export function xcodeMajor(xcode) {
|
|
42
|
+
const major = Number.parseInt(xcode.version, 10);
|
|
43
|
+
return Number.isFinite(major) ? major : 0;
|
|
44
|
+
}
|
|
45
|
+
/** The managed WDA root: <cacheDir>/ios/wda. */
|
|
46
|
+
export function getWdaRoot(ctx = {}) {
|
|
47
|
+
return path.join(getCacheDir(ctx), "ios", "wda");
|
|
48
|
+
}
|
|
49
|
+
// actions/cache-style conservative charset: collapse anything outside
|
|
50
|
+
// [A-Za-z0-9._-] to a single "-" so versions with build metadata or spaces
|
|
51
|
+
// can't produce hostile directory names.
|
|
52
|
+
function sanitizeKeySegment(value) {
|
|
53
|
+
return ((value || "unknown").trim().replace(/[^A-Za-z0-9._-]+/g, "-") || "unknown");
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* The cache key a WDA build is valid for: Xcode marketing version + build id
|
|
57
|
+
* (two runner images can ship the same marketing version) + the exact
|
|
58
|
+
* appium-xcuitest-driver version whose bundled WDA source was compiled.
|
|
59
|
+
*/
|
|
60
|
+
export function computeWdaKey(xcode, driverVersion) {
|
|
61
|
+
return `xcode-${sanitizeKeySegment(xcode.version)}-${sanitizeKeySegment(xcode.build)}-driver-${sanitizeKeySegment(driverVersion)}`;
|
|
62
|
+
}
|
|
63
|
+
/** Where xcodebuild's products land inside a key dir. */
|
|
64
|
+
export const RUNNER_APP_RELATIVE = path.join("DerivedData", "Build", "Products", "Debug-iphonesimulator", "WebDriverAgentRunner-Runner.app");
|
|
65
|
+
export const PRODUCTS_MARKER = "products.json";
|
|
66
|
+
export const LAST_USED_STAMP = "last-used";
|
|
67
|
+
/** The writer's advisory-lock dir name under the WDA root — one name shared
|
|
68
|
+
* by the acquire call and the prune pass's skip check so they can't drift. */
|
|
69
|
+
export const WDA_LOCK_DIRNAME = ".lock";
|
|
70
|
+
/**
|
|
71
|
+
* Read and validate a key dir's completeness marker. Null on any miss:
|
|
72
|
+
* absent/corrupt marker (a crashed half-built dir never wrote one — that is
|
|
73
|
+
* the lock-free correctness story for readers) or a key dir whose Runner app
|
|
74
|
+
* is gone. The Runner check uses the layout-relative path, NOT the absolute
|
|
75
|
+
* path recorded in the marker, so a relocated cache root (moved home dir,
|
|
76
|
+
* container bind-mount, CI cache restored under a different path) keeps its
|
|
77
|
+
* valid products instead of misreading them all as stale.
|
|
78
|
+
*/
|
|
79
|
+
export function readProductsMarker(keyDir, fs) {
|
|
80
|
+
try {
|
|
81
|
+
const parsed = JSON.parse(String(fs.readFileSync(path.join(keyDir, PRODUCTS_MARKER))));
|
|
82
|
+
if (typeof parsed?.key !== "string" ||
|
|
83
|
+
typeof parsed?.driverVersion !== "string") {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
if (!fs.existsSync(path.join(keyDir, RUNNER_APP_RELATIVE)))
|
|
87
|
+
return null;
|
|
88
|
+
return parsed;
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Touch a key dir's last-used stamp — the prune-freshness signal shared by
|
|
96
|
+
* the installer (writes it at build time, stats it when pruning) and the
|
|
97
|
+
* session locator (touches it on every valid hit). Best-effort: a failed
|
|
98
|
+
* stamp only risks an early prune much later.
|
|
99
|
+
*/
|
|
100
|
+
export function touchLastUsed(keyDir, fs, now) {
|
|
101
|
+
try {
|
|
102
|
+
fs.writeFileSync(path.join(keyDir, LAST_USED_STAMP), String(now()));
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
/* best-effort */
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// --- session-time locator (design phase 3) ---
|
|
109
|
+
/**
|
|
110
|
+
* Minimum appium-xcuitest-driver MAJOR whose prebuilt-WDA consumption
|
|
111
|
+
* (`appium:usePrebuiltWDA` + `appium:derivedDataPath`) this locator has been
|
|
112
|
+
* validated against (the live macOS fixture legs run the 10.x line that
|
|
113
|
+
* doc-detective's declared range installs). Prebuilt handling is
|
|
114
|
+
* driver-version-sensitive across older majors (`prebuiltWDAPath` /
|
|
115
|
+
* `useXctestrunFile` variants, differing .xctestrun handling), so anything
|
|
116
|
+
* below the floor gets a plain fallback — today's build-in-session behavior —
|
|
117
|
+
* never a guess.
|
|
118
|
+
*/
|
|
119
|
+
export const MIN_PREBUILT_WDA_DRIVER_MAJOR = 10;
|
|
120
|
+
// One `xcodebuild -version` spawn max per process: sessions can be created
|
|
121
|
+
// many times per run, and the Xcode version cannot change mid-run.
|
|
122
|
+
let cachedXcodeProbe;
|
|
123
|
+
/* c8 ignore start — real spawn; unit tests inject probeXcode. */
|
|
124
|
+
function probeXcodeVersionCached() {
|
|
125
|
+
if (cachedXcodeProbe === undefined) {
|
|
126
|
+
try {
|
|
127
|
+
const result = spawnSync("xcodebuild", ["-version"], {
|
|
128
|
+
encoding: "utf8",
|
|
129
|
+
windowsHide: true,
|
|
130
|
+
timeout: 15000,
|
|
131
|
+
});
|
|
132
|
+
cachedXcodeProbe =
|
|
133
|
+
result.status === 0 ? parseXcodebuildVersion(result.stdout) : null;
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
cachedXcodeProbe = null;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return cachedXcodeProbe;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Pure managed-products locator for iOS session capability builders: find
|
|
143
|
+
* the prebuilt WDA products matching the CURRENT toolchain (installed
|
|
144
|
+
* driver version × host Xcode). On a valid hit, touch the last-used stamp
|
|
145
|
+
* (the prune signal) and return the derivedDataPath to consume read-only
|
|
146
|
+
* with `appium:usePrebuiltWDA`. Any miss — wrong platform, unresolvable
|
|
147
|
+
* driver, driver below the supported floor, no full Xcode, absent/stale
|
|
148
|
+
* marker — returns null and the session builds WDA itself, exactly today's
|
|
149
|
+
* behavior. Never throws.
|
|
150
|
+
*/
|
|
151
|
+
export function locateManagedWda(options = {}) {
|
|
152
|
+
const { ctx = {}, fs = fsDefault, platform = process.platform, probeXcode = probeXcodeVersionCached, resolveDriverVersion = resolveHeavyDepVersion, now = Date.now, } = options;
|
|
153
|
+
try {
|
|
154
|
+
if (platform !== "darwin")
|
|
155
|
+
return null;
|
|
156
|
+
const driverVersion = resolveDriverVersion("appium-xcuitest-driver", ctx);
|
|
157
|
+
if (!driverVersion)
|
|
158
|
+
return null;
|
|
159
|
+
const driverMajor = Number.parseInt(driverVersion, 10);
|
|
160
|
+
if (!Number.isFinite(driverMajor) ||
|
|
161
|
+
driverMajor < MIN_PREBUILT_WDA_DRIVER_MAJOR) {
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
const xcode = probeXcode();
|
|
165
|
+
if (!xcode)
|
|
166
|
+
return null;
|
|
167
|
+
const wdaRoot = path.normalize(options.wdaRootDir ?? getWdaRoot(ctx));
|
|
168
|
+
const key = computeWdaKey(xcode, driverVersion);
|
|
169
|
+
const keyDir = path.join(wdaRoot, key);
|
|
170
|
+
const marker = readProductsMarker(keyDir, fs);
|
|
171
|
+
// The key is the single identity: it already encodes the driver version,
|
|
172
|
+
// so `marker.key === key` is the whole match.
|
|
173
|
+
if (!marker || marker.key !== key)
|
|
174
|
+
return null;
|
|
175
|
+
// Readers are read-only consumers of the DerivedData — the stamp is the
|
|
176
|
+
// one exception (it's the prune-freshness signal).
|
|
177
|
+
touchLastUsed(keyDir, fs, now);
|
|
178
|
+
return { key, derivedDataPath: path.join(keyDir, "DerivedData") };
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
// ADR 01049 degradation semantics: an unusable cache is a plain miss.
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Apply the WDA derived-data capabilities for an iOS XCUITest session — the
|
|
187
|
+
* ONE place the env-override-vs-managed-products precedence and the exact
|
|
188
|
+
* capability pair live, shared by the app-surface and mobile-web builders so
|
|
189
|
+
* the two cannot drift (the pair is driver-version-sensitive; see
|
|
190
|
+
* MIN_PREBUILT_WDA_DRIVER_MAJOR).
|
|
191
|
+
*
|
|
192
|
+
* Precedence: DOC_DETECTIVE_IOS_WDA_DERIVED_DATA_PATH set → exactly the
|
|
193
|
+
* historical behavior (`derivedDataPath` only, caller owns semantics; the
|
|
194
|
+
* managed locator is not consulted). Otherwise a managed-products hit sets
|
|
195
|
+
* both `appium:derivedDataPath` (the keyed DerivedData) and
|
|
196
|
+
* `appium:usePrebuiltWDA` (read-only consumption — the concurrency answer).
|
|
197
|
+
* No locator or a miss → no capabilities change (today's behavior: the
|
|
198
|
+
* session builds WDA itself in a throwaway per-session temp dir).
|
|
199
|
+
*/
|
|
200
|
+
export function applyManagedWdaCapabilities(capabilities, locateWda) {
|
|
201
|
+
const derivedDataPath = process.env.DOC_DETECTIVE_IOS_WDA_DERIVED_DATA_PATH;
|
|
202
|
+
if (derivedDataPath && derivedDataPath.trim()) {
|
|
203
|
+
capabilities["appium:derivedDataPath"] = derivedDataPath.trim();
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
const managed = locateWda?.();
|
|
207
|
+
if (managed) {
|
|
208
|
+
capabilities["appium:derivedDataPath"] = managed.derivedDataPath;
|
|
209
|
+
capabilities["appium:usePrebuiltWDA"] = true;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Locate the WebDriverAgent source bundled with the installed
|
|
214
|
+
* appium-xcuitest-driver: walk up from the driver's resolved entry looking
|
|
215
|
+
* for a `node_modules/appium-webdriveragent` that actually contains the
|
|
216
|
+
* Xcode project. The walk (rather than a hardcoded relative path) is what
|
|
217
|
+
* absorbs npm's hoisting variability — nested under the driver on some
|
|
218
|
+
* installs, hoisted to the runtime root on others.
|
|
219
|
+
*/
|
|
220
|
+
export function findWdaSource(driverEntryPath, fs) {
|
|
221
|
+
let dir = path.dirname(driverEntryPath);
|
|
222
|
+
for (let i = 0; i < 20; i++) {
|
|
223
|
+
const candidate = path.join(dir, "node_modules", "appium-webdriveragent");
|
|
224
|
+
if (fs.existsSync(path.join(candidate, "WebDriverAgent.xcodeproj"))) {
|
|
225
|
+
return candidate;
|
|
226
|
+
}
|
|
227
|
+
const parent = path.dirname(dir);
|
|
228
|
+
if (parent === dir)
|
|
229
|
+
break;
|
|
230
|
+
dir = parent;
|
|
231
|
+
}
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
//# sourceMappingURL=wdaProducts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wdaProducts.js","sourceRoot":"","sources":["../../src/runtime/wdaProducts.ts"],"names":[],"mappings":"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"}
|