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.
Files changed (35) hide show
  1. package/dist/core/tests/appSurface.d.ts +2 -0
  2. package/dist/core/tests/appSurface.d.ts.map +1 -1
  3. package/dist/core/tests/appSurface.js +11 -10
  4. package/dist/core/tests/appSurface.js.map +1 -1
  5. package/dist/core/tests/mobileBrowser.d.ts +3 -1
  6. package/dist/core/tests/mobileBrowser.d.ts.map +1 -1
  7. package/dist/core/tests/mobileBrowser.js +6 -7
  8. package/dist/core/tests/mobileBrowser.js.map +1 -1
  9. package/dist/hints/context.d.ts +16 -0
  10. package/dist/hints/context.d.ts.map +1 -1
  11. package/dist/hints/context.js +34 -0
  12. package/dist/hints/context.js.map +1 -1
  13. package/dist/hints/hints.d.ts.map +1 -1
  14. package/dist/hints/hints.js +17 -0
  15. package/dist/hints/hints.js.map +1 -1
  16. package/dist/hints/types.d.ts +14 -0
  17. package/dist/hints/types.d.ts.map +1 -1
  18. package/dist/index.cjs +370 -269
  19. package/dist/runtime/cacheDir.d.ts +5 -0
  20. package/dist/runtime/cacheDir.d.ts.map +1 -1
  21. package/dist/runtime/cacheDir.js +3 -0
  22. package/dist/runtime/cacheDir.js.map +1 -1
  23. package/dist/runtime/iosInstaller.d.ts +44 -1
  24. package/dist/runtime/iosInstaller.d.ts.map +1 -1
  25. package/dist/runtime/iosInstaller.js +381 -13
  26. package/dist/runtime/iosInstaller.js.map +1 -1
  27. package/dist/runtime/lock.d.ts +56 -0
  28. package/dist/runtime/lock.d.ts.map +1 -0
  29. package/dist/runtime/lock.js +218 -0
  30. package/dist/runtime/lock.js.map +1 -0
  31. package/dist/runtime/wdaProducts.d.ts +147 -0
  32. package/dist/runtime/wdaProducts.d.ts.map +1 -0
  33. package/dist/runtime/wdaProducts.js +234 -0
  34. package/dist/runtime/wdaProducts.js.map +1 -0
  35. 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":"AAAA,yEAAyE;AACzE,8EAA8E;AAC9E,yEAAyE;AACzE,gDAAgD;AAChD,EAAE;AACF,8EAA8E;AAC9E,4EAA4E;AAC5E,6EAA6E;AAC7E,+EAA+E;AAC/E,4EAA4E;AAC5E,2EAA2E;AAC3E,UAAU;AACV,EAAE;AACF,wEAAwE;AACxE,wDAAwD;AAExD,OAAO,SAAS,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAgD7B,SAAS,iBAAiB,CAAC,GAAW;IACpC,IAAI,CAAC;QACH,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QACrB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,OAAO,GAAQ,EAAE,CAAC;QAClB,gEAAgE;QAChE,qEAAqE;QACrE,uEAAuE;QACvE,+DAA+D;QAC/D,oDAAoD;QACpD,OAAO,GAAG,EAAE,IAAI,KAAK,OAAO,CAAC;IAC/B,CAAC;AACH,CAAC;AAED,SAAS,oBAAoB,CAAC,EAAc,EAAE,EAAU;IACtD,MAAM,KAAK,GAAG,WAAW,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;IACjC,KAAa,CAAC,KAAK,EAAE,EAAE,CAAC;IACzB,OAAO,GAAG,EAAE,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;AACpC,CAAC;AAED,MAAM,UAAU,GAAG,YAAY,CAAC;AAEhC;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,OAA2B;IAE3B,MAAM,EACJ,GAAG,EACH,MAAM,GAAG,EAAE,GAAG,MAAM,EACpB,MAAM,GAAG,KAAK,EACd,WAAW,GAAG,MAAM,EACpB,OAAO,GAAG,CAAC,GAAG,MAAM,EACpB,IAAI,GAAG,EAAE,GACV,GAAG,OAAO,CAAC;IACZ,MAAM,EAAE,GAAG,IAAI,CAAC,EAAE,IAAK,SAAoB,CAAC;IAC5C,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC;IACjC,MAAM,KAAK,GACT,IAAI,CAAC,KAAK;QACV,CAAC,CAAC,EAAU,EAAE,EAAE,CACd,IAAI,OAAO,CAAO,CAAC,CAAC,EAAE,EAAE;YACtB,MAAM,KAAK,GAAG,UAAU,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAC/B,KAAa,CAAC,KAAK,EAAE,EAAE,CAAC;QAC3B,CAAC,CAAC,CAAC,CAAC;IACR,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC;IACpC,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,EAAE,CAAC,QAAQ,EAAE,CAAC;IAChD,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,IAAI,iBAAiB,CAAC;IACxD,MAAM,aAAa,GAAG,IAAI,CAAC,aAAa,IAAI,oBAAoB,CAAC;IAEjE,MAAM,SAAS,GAAG,GAAG,GAAG,IAAI,UAAU,EAAE,CAAC;IACzC,MAAM,QAAQ,GAAG,GAAG,EAAE,GAAG,MAAM,CAAC;IAChC,qEAAqE;IACrE,mDAAmD;IACnD,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACrD,2EAA2E;IAC3E,0EAA0E;IAC1E,4EAA4E;IAC5E,2DAA2D;IAC3D,IAAI,gBAAgB,GAAkB,IAAI,CAAC;IAE3C,SAAS,CAAC;QACR,yEAAyE;QACzE,4BAA4B;QAC5B,IAAI,CAAC;YACH,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;YAClB,OAAO,aAAa,EAAE,CAAC;QACzB,CAAC;QAAC,OAAO,GAAQ,EAAE,CAAC;YAClB,IAAI,GAAG,EAAE,IAAI,KAAK,QAAQ;gBAAE,MAAM,GAAG,CAAC;QACxC,CAAC;QAED,sEAAsE;QACtE,0EAA0E;QAC1E,uEAAuE;QACvE,gDAAgD;QAChD,MAAM,IAAI,GAAG,aAAa,CAAC,EAAE,EAAE,SAAS,CAAC,CAAC;QAE1C,IAAI,SAAS,GAAG,KAAK,CAAC;QACtB,IAAI,IAAI,EAAE,CAAC;YACT,gBAAgB,GAAG,IAAI,CAAC;YACxB,MAAM,cAAc,GAAG,GAAG,EAAE,GAAG,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC;YAC1D,MAAM,cAAc,GAClB,IAAI,CAAC,QAAQ,KAAK,QAAQ,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACtD,SAAS,GAAG,cAAc,IAAI,cAAc,CAAC;QAC/C,CAAC;aAAM,CAAC;YACN,gBAAgB,KAAK,GAAG,EAAE,CAAC;YAC3B,SAAS,GAAG,GAAG,EAAE,GAAG,gBAAgB,IAAI,MAAM,CAAC;QACjD,CAAC;QAED,IAAI,SAAS,EAAE,CAAC;YACd,6DAA6D;YAC7D,sEAAsE;YACtE,sEAAsE;YACtE,oEAAoE;YACpE,sEAAsE;YACtE,6DAA6D;YAC7D,6CAA6C;YAC7C,MAAM,KAAK,GAAG,GAAG,GAAG,UAAU,GAAG,IAAI,GAAG,EAAE,EAAE,CAAC;YAC7C,IAAI,CAAC;gBACH,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;gBAC1B,IAAI,CAAC;oBACH,EAAE,CAAC,MAAM,CAAC,KAAK,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;gBACrD,CAAC;gBAAC,MAAM,CAAC;oBACP,+BAA+B;gBACjC,CAAC;gBACD,6DAA6D;gBAC7D,iDAAiD;gBACjD,SAAS;YACX,CAAC;YAAC,MAAM,CAAC;gBACP,oEAAoE;gBACpE,oEAAoE;gBACpE,qEAAqE;YACvE,CAAC;QACH,CAAC;QAED,IAAI,GAAG,EAAE,IAAI,QAAQ;YAAE,OAAO,IAAI,CAAC;QACnC,MAAM,KAAK,CAAC,MAAM,CAAC,CAAC;IACtB,CAAC;IAED,SAAS,aAAa;QACpB,MAAM,UAAU,GAAG,GAAG,EAAE,CAAC;QACzB,MAAM,SAAS,GAAG,GAAG,EAAE;YACrB,oEAAoE;YACpE,MAAM,IAAI,GAAc;gBACtB,GAAG;gBACH,QAAQ;gBACR,UAAU;gBACV,WAAW,EAAE,GAAG,EAAE;aACnB,CAAC;YACF,EAAE,CAAC,aAAa,CAAC,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;QACpD,CAAC,CAAC;QACF,uEAAuE;QACvE,qEAAqE;QACrE,wEAAwE;QACxE,mCAAmC;QACnC,MAAM,YAAY,GAAG,GAAG,EAAE;YACxB,MAAM,IAAI,GAAG,aAAa,CAAC,EAAE,EAAE,SAAS,CAAC,CAAC;YAC1C,OAAO,IAAI,KAAK,IAAI,IAAI,IAAI,CAAC,GAAG,KAAK,GAAG,IAAI,IAAI,CAAC,QAAQ,KAAK,QAAQ,CAAC;QACzE,CAAC,CAAC;QACF,SAAS,EAAE,CAAC;QAEZ,IAAI,IAAI,GAAG,KAAK,CAAC;QACjB,MAAM,aAAa,GAAG,aAAa,CAAC,GAAG,EAAE;YACvC,qEAAqE;YACrE,oEAAoE;YACpE,uCAAuC;YACvC,IAAI,CAAC;gBACH,IAAI,CAAC,YAAY,EAAE,EAAE,CAAC;oBACpB,IAAI,GAAG,IAAI,CAAC;oBACZ,aAAa,EAAE,CAAC;oBAChB,OAAO;gBACT,CAAC;gBACD,SAAS,EAAE,CAAC;YACd,CAAC;YAAC,MAAM,CAAC;gBACP,iBAAiB;YACnB,CAAC;QACH,CAAC,EAAE,WAAW,CAAC,CAAC;QAEhB,IAAI,QAAQ,GAAG,KAAK,CAAC;QACrB,OAAO;YACL,OAAO;gBACL,IAAI,QAAQ;oBAAE,OAAO;gBACrB,QAAQ,GAAG,IAAI,CAAC;gBAChB,aAAa,EAAE,CAAC;gBAChB,IAAI,IAAI;oBAAE,OAAO;gBACjB,IAAI,CAAC;oBACH,gEAAgE;oBAChE,0BAA0B;oBAC1B,IAAI,CAAC,YAAY,EAAE;wBAAE,OAAO;oBAC5B,EAAE,CAAC,MAAM,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;gBACnD,CAAC;gBAAC,MAAM,CAAC;oBACP,kBAAkB;gBACpB,CAAC;YACH,CAAC;SACF,CAAC;IACJ,CAAC;AACH,CAAC;AAED,qFAAqF;AACrF,SAAS,aAAa,CAAC,EAAU,EAAE,SAAiB;IAClD,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;QAC9D,IACE,OAAO,MAAM,EAAE,GAAG,KAAK,QAAQ;YAC/B,OAAO,MAAM,EAAE,QAAQ,KAAK,QAAQ;YACpC,OAAO,MAAM,EAAE,UAAU,KAAK,QAAQ;YACtC,OAAO,MAAM,EAAE,WAAW,KAAK,QAAQ,EACvC,CAAC;YACD,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,MAAmB,CAAC;IAC7B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC5B,OAA2B,EAC3B,EAAwB;IAExB,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,OAAO,CAAC,CAAC;IAC1C,IAAI,CAAC,MAAM;QAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;IACxC,IAAI,CAAC;QACH,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;IAChD,CAAC;YAAS,CAAC;QACT,MAAM,CAAC,OAAO,EAAE,CAAC;IACnB,CAAC;AACH,CAAC"}
@@ -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":"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"}
@@ -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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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "doc-detective",
3
- "version": "4.27.0",
3
+ "version": "4.28.0",
4
4
  "description": "Treat doc content as testable assertions to validate doc accuracy and product UX.",
5
5
  "bin": {
6
6
  "doc-detective": "bin/doc-detective.js",