stim 1.14.0 → 1.16.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 (152) hide show
  1. package/README.md +2 -0
  2. package/dist/activity-BxwdD81S.mjs +609 -0
  3. package/dist/agent-device-usage-output-Dpih1Tfa.mjs +411 -0
  4. package/dist/{android-d6rGq1pA.mjs → android-B_NumVyQ.mjs} +169 -81
  5. package/dist/{android-LbPFRWVL.mjs → android-Ift4kVvt.mjs} +517 -413
  6. package/dist/android-cas-8U4DPqeR.mjs +167 -0
  7. package/dist/android-cas-compiler.mjs +2 -2
  8. package/dist/{android-XpvCrV7q.mjs → android-r2rBL8kV.mjs} +2 -2
  9. package/dist/{app-install-BpgSgkJj.mjs → app-install-CKWdbXIX.mjs} +128 -151
  10. package/dist/attempt-Bk2ouQVL.mjs +39 -0
  11. package/dist/budget-DjrURfQn.mjs +2700 -0
  12. package/dist/{build-plan-Dcr-3ie0.mjs → build-plan-DjeSb2Xl.mjs} +192 -653
  13. package/dist/build-slots-rUk77xqh.mjs +132 -0
  14. package/dist/{cache-manifest-C68QQ0ua.mjs → cache-manifest-DnUHBJaU.mjs} +2 -2
  15. package/dist/cache-manifest.mjs +1 -1
  16. package/dist/{chrome-BPCiC3ZX.mjs → chrome-BrcVWQyD.mjs} +1 -1
  17. package/dist/cli-BkYZ_6hy.mjs +51 -0
  18. package/dist/cli.mjs +6 -42
  19. package/dist/client-BTY686R6.mjs +1368 -0
  20. package/dist/collector-run.mjs +9 -195
  21. package/dist/{command-output-DVcpw6eE.mjs → command-output-DnJMdEY-.mjs} +1 -1
  22. package/dist/{config-lvc7d3C2.mjs → config-D0DbpZtM.mjs} +28 -29
  23. package/dist/{created-devices-CllH_8eb.mjs → created-devices-D5owjN4C.mjs} +2 -2
  24. package/dist/{dependency-state-DsT7aKeo.mjs → dependency-state-B6cMXZab.mjs} +2 -2
  25. package/dist/{deps-D9IcKqXS.mjs → deps-urbmfXig.mjs} +3 -3
  26. package/dist/{detached-entry-DJHoca49.mjs → detached-entry-CiTEgMR1.mjs} +1 -1
  27. package/dist/dev-client-CroAIjjj.mjs +43 -0
  28. package/dist/{device-CjuuAJjQ.mjs → device-DZQjF7H5.mjs} +8 -7
  29. package/dist/device-capacity-BHwxNBlu.mjs +136 -0
  30. package/dist/device-host-worker.d.mts +1 -0
  31. package/dist/device-host-worker.mjs +964 -0
  32. package/dist/device-ios-BadmeL2V.mjs +381 -0
  33. package/dist/{device-lease-5c3WrVRm.mjs → device-lease-D-hLqx1b.mjs} +43 -36
  34. package/dist/{device-pool-BiyDyPP-.mjs → device-lease-run-CqjShqWW.mjs} +6 -126
  35. package/dist/device-pool-CUaEWMMm.mjs +125 -0
  36. package/dist/{device-remote-D87zEzaq.mjs → device-remote-D5WVWqOq.mjs} +69 -163
  37. package/dist/{doctor-x9pnU9fq.mjs → doctor-Cimf-gGB.mjs} +241 -103
  38. package/dist/{error-diagnostics-C5HzcwCW.mjs → error-diagnostics-B9L4Ae0x.mjs} +15 -18
  39. package/dist/{exec-DK8KaVHK.mjs → exec-CaErVv7t.mjs} +5 -1
  40. package/dist/{gc-ko6mUAYD.mjs → gc-Bqy4bjEu.mjs} +599 -108
  41. package/dist/{android-cas-iZK3DOCr.mjs → gradle-BsO1MdsH.mjs} +8 -294
  42. package/dist/{guide-6R0VNFqI.mjs → guide-B_uyKTxW.mjs} +2511 -239
  43. package/dist/{guide-status-BZrFPIA_.mjs → guide-status-CjEZMAD4.mjs} +3 -3
  44. package/dist/hosted-client-a-WmqE2L.mjs +197 -0
  45. package/dist/hosted-ios-CIWelIqf.mjs +220 -0
  46. package/dist/hosted-logs-D5g041Q3.mjs +187 -0
  47. package/dist/hosted-macos-BSZyIb3y.mjs +135 -0
  48. package/dist/idle-DYVymG92.mjs +1099 -0
  49. package/dist/idle-shutdown-CTsDnAde.mjs +122 -0
  50. package/dist/in-use-DvBw0Kxo.mjs +130 -0
  51. package/dist/{ios-BwDPrnYQ.mjs → ios-BaInn3sW.mjs} +201 -10
  52. package/dist/ios-Cr1h5gnO.mjs +189 -0
  53. package/dist/{ios-P1f5nm2n.mjs → ios-aq3QoGI3.mjs} +1159 -633
  54. package/dist/{ios-device-BiOsSVzd.mjs → ios-device-Ee7sDXfp.mjs} +1 -1
  55. package/dist/{ios-device-DGglU3E_.mjs → ios-device-FfCN_C8p.mjs} +12 -7
  56. package/dist/ios-state-DS5UpYTH.mjs +48 -0
  57. package/dist/{launch-verify-Deg4tB3Z.mjs → launch-verify-2DBRizjo.mjs} +5 -5
  58. package/dist/logs-CHEVK8zD.mjs +371 -0
  59. package/dist/machines-BIBUdhpj.mjs +265 -0
  60. package/dist/macos-CSYEAFmF.mjs +473 -0
  61. package/dist/macos-run.d.mts +5 -0
  62. package/dist/macos-run.mjs +2 -0
  63. package/dist/maintenance-run.d.mts +4 -0
  64. package/dist/maintenance-run.mjs +254 -0
  65. package/dist/{metro-CcEo8Fl8.mjs → metro-BeK1ZPz1.mjs} +20 -120
  66. package/dist/metro-gateway-BJ-rxQAB.mjs +84 -0
  67. package/dist/{named-ports-Blg8IxOh.mjs → named-ports-Bk_YXEmE.mjs} +6 -59
  68. package/dist/native-run-NaBrRFtQ.mjs +147 -0
  69. package/dist/{native-runtime-DjBHRg5m.mjs → native-runtime-DBzRnEKS.mjs} +9 -5
  70. package/dist/{ndjson-CDY9IkVC.mjs → ndjson-PclOIwVY.mjs} +2 -2
  71. package/dist/offload-worker.d.mts +122 -0
  72. package/dist/offload-worker.mjs +606 -0
  73. package/dist/{ownership-DyYDF5ww.mjs → ownership-DiA16AsN.mjs} +3 -2
  74. package/dist/ownership-hCkPzGmr.mjs +571 -0
  75. package/dist/{page-BHUzTDFt.mjs → page-QF_Ke10s.mjs} +7 -2
  76. package/dist/{ports-BguS83ex.mjs → ports-R2k4Knz3.mjs} +3 -3
  77. package/dist/prebuild-D2fsca_Z.mjs +213 -0
  78. package/dist/preview-DhVbvPAZ.mjs +318 -0
  79. package/dist/process-identity-BmRm1qV0.mjs +12 -0
  80. package/dist/{project-CWlvEaeN.mjs → project-Du91Ilvc.mjs} +74 -19
  81. package/dist/pull-request-pxnDCQzn.mjs +322 -0
  82. package/dist/pull-requests.d.mts +120 -0
  83. package/dist/pull-requests.mjs +2 -0
  84. package/dist/recordings-BIXOzVnr.mjs +37 -0
  85. package/dist/{reload-BiniwsSZ.mjs → reload-rM9jV1fz.mjs} +109 -24
  86. package/dist/{remote-cache-CcR6o6lI.mjs → remote-cache-C8-V13XN.mjs} +5 -5
  87. package/dist/run-f4vI6EnK.mjs +119 -0
  88. package/dist/{server-bare-DGfgyJ5J.mjs → server-bare-CpPGmnmO.mjs} +33 -15
  89. package/dist/server-expo-CVqO2c35.mjs +2 -0
  90. package/dist/{server-expo-BVuwQ65v.mjs → server-expo-MC0rHlOC.mjs} +8 -12
  91. package/dist/settings-BBpMcDbD.mjs +25 -0
  92. package/dist/{settings-B-fsC-Om.mjs → settings-ZkDzkzvB.mjs} +22 -10
  93. package/dist/settings-oTSkkNrM.mjs +1439 -0
  94. package/dist/settings.schema.json +845 -97
  95. package/dist/{simslim-DrDN4WuV.mjs → simslim-B8-7AoTI.mjs} +8 -132
  96. package/dist/{slot-launch-D5t8EiMf.mjs → slot-launch-B8VrHxn1.mjs} +11 -8
  97. package/dist/{spawn-claims-Drw_o-Fb.mjs → spawn-claims-D0XfSe8u.mjs} +1 -1
  98. package/dist/spawn-entry-jw19m8Nl.mjs +17 -0
  99. package/dist/stage-CzaiwFsI.mjs +143 -0
  100. package/dist/start-CX8Ig-U2.mjs +2 -0
  101. package/dist/{start-s4RXp1wy.mjs → start-ClDfwXa4.mjs} +116 -65
  102. package/dist/state-3DwSQIBK.mjs +41 -0
  103. package/dist/state-B4PQFXMW.d.mts +12 -0
  104. package/dist/{state-Dk9RVXI7.mjs → state-C1jB7Q8q.mjs} +70 -8
  105. package/dist/{state-DHqvsbqC.mjs → state-DcW6vOd_.mjs} +7 -4
  106. package/dist/stats-CEZ_i5je.mjs +77 -0
  107. package/dist/{status-BgAuxESg.mjs → status-BuvBFeII.mjs} +118 -20
  108. package/dist/{status-BsPAnfOI.mjs → status-Cs7VLg9D.mjs} +542 -283
  109. package/dist/status-DlKvVJBE.mjs +49 -0
  110. package/dist/stdout-reader-C8yjre1x.mjs +23 -0
  111. package/dist/stim-desktop-Cnz8CPnl.mjs +63 -0
  112. package/dist/stim-footprint.swift +32 -0
  113. package/dist/{stim-installations-CINXy_su.mjs → stim-installations-QMEI32v1.mjs} +1 -1
  114. package/dist/stop-B0Yxtmiy.mjs +48 -0
  115. package/dist/{stop-BzreOWJp.mjs → stop-Bffozsac.mjs} +83 -16
  116. package/dist/{stop-BWIF_6BK.mjs → stop-Dt_dM16A.mjs} +2 -2
  117. package/dist/stop-cause-CidxIqup.mjs +97 -0
  118. package/dist/supervisor-run.d.mts +43 -11
  119. package/dist/supervisor-run.mjs +363 -24
  120. package/dist/support-CAP8qDUF.mjs +316 -0
  121. package/dist/support-D4aYJniO.mjs +201 -0
  122. package/dist/teardown-CL6vDs2Y.mjs +727 -0
  123. package/dist/{xcode-Cp5KvK9x.mjs → toolchain-DkUflduf.mjs} +364 -14
  124. package/dist/trigger-Bemu8ytw.mjs +65 -0
  125. package/dist/trigger-CXts6qt1.mjs +2 -0
  126. package/dist/warm-progress-CpGv3Wb6.mjs +732 -0
  127. package/dist/{web-BsQs1WlO.mjs → web-DWUaMp8P.mjs} +37 -21
  128. package/dist/web-run.mjs +310 -13
  129. package/dist/workspace-process-lock-BL7Q0HCO.mjs +57 -0
  130. package/dist/workspace-state-B8-aJ1_5.mjs +371 -0
  131. package/dist/workspaces-BV4pdHFz.mjs +306 -0
  132. package/dist/{worktree-UrQWXSeb.mjs → worktree-K-xj9Db0.mjs} +625 -256
  133. package/dist/worktree-jnG8Iph5.mjs +2 -0
  134. package/package.json +10 -5
  135. package/shim/bundle-response.cjs +28 -12
  136. package/dist/activity-CYVDcrt3.mjs +0 -356
  137. package/dist/budget-CGDkhvv_.mjs +0 -1745
  138. package/dist/build-progress-DxKpwnaS.mjs +0 -247
  139. package/dist/idle-DXtx3RbX.mjs +0 -191
  140. package/dist/in-use-DlqBDKqY.mjs +0 -249
  141. package/dist/logs-Civ4gbBH.mjs +0 -486
  142. package/dist/merge-state-ytFQCeh4.mjs +0 -262
  143. package/dist/server-expo-DIHDhvGg.mjs +0 -2
  144. package/dist/settings-D61iNOsB.mjs +0 -629
  145. package/dist/start-Cetfvy8l.mjs +0 -2
  146. package/dist/stats-CFpFGlJV.mjs +0 -62
  147. package/dist/stats-CX1Z8Tca.mjs +0 -368
  148. package/dist/stim-desktop-CXt8-Z3J.mjs +0 -40
  149. package/dist/stop-cause-hjGBOZTG.mjs +0 -1231
  150. package/dist/worktree-BVrHsYZG.mjs +0 -752
  151. package/dist/worktree-CHWWvm5v.mjs +0 -2
  152. package/dist/{metro-store-DzeWkfqW.mjs → metro-store-CDDZHtPl.mjs} +1 -1
@@ -0,0 +1,322 @@
1
+ import { t as getExecutor } from "./exec-CaErVv7t.mjs";
2
+ //#region src/workspace/pull-request.ts
3
+ const GH_TIMEOUT_MS = 2e4;
4
+ const GH_ENV = {
5
+ GH_PROMPT_DISABLED: "1",
6
+ GH_NO_UPDATE_NOTIFIER: "1",
7
+ NO_COLOR: "1"
8
+ };
9
+ const GH_SCALAR_FIELDS = "number,state,url,headRefOid,mergedAt,closedAt,isCrossRepository,isDraft";
10
+ const GH_FIELDS = `${GH_SCALAR_FIELDS},headRepository,headRepositoryOwner`;
11
+ const GH_GRAPHQL_REPO_SELECTION = "headRepository { name } headRepositoryOwner { login }";
12
+ const GH_DETAIL_FIELDS = "title reviewDecision commits(last: 1) { nodes { commit { statusCheckRollup { contexts(first: 0) { checkRunCountsByState { state count } statusContextCountsByState { state count } } } } } }";
13
+ const SIGNED_OUT_EXIT = 4;
14
+ const PASSING = /* @__PURE__ */ new Set([
15
+ "SUCCESS",
16
+ "NEUTRAL",
17
+ "SKIPPED",
18
+ "COMPLETED"
19
+ ]);
20
+ const FAILING = /* @__PURE__ */ new Set([
21
+ "FAILURE",
22
+ "ERROR",
23
+ "TIMED_OUT",
24
+ "CANCELLED",
25
+ "ACTION_REQUIRED",
26
+ "STARTUP_FAILURE",
27
+ "STALE"
28
+ ]);
29
+ const PENDING = /* @__PURE__ */ new Set([
30
+ "QUEUED",
31
+ "IN_PROGRESS",
32
+ "WAITING",
33
+ "PENDING",
34
+ "REQUESTED",
35
+ "EXPECTED"
36
+ ]);
37
+ /** Sums a head commit's check runs and commit statuses by outcome; null when the commit has no checks. */
38
+ function checkCounts(rollup) {
39
+ if (!rollup) return null;
40
+ const counts = {
41
+ passing: 0,
42
+ failing: 0,
43
+ pending: 0
44
+ };
45
+ const contexts = rollup.contexts;
46
+ for (const { state, count } of [...contexts?.checkRunCountsByState ?? [], ...contexts?.statusContextCountsByState ?? []]) {
47
+ if (!Number.isInteger(count) || count <= 0) continue;
48
+ if (PASSING.has(state)) counts.passing += count;
49
+ else if (FAILING.has(state)) counts.failing += count;
50
+ else if (PENDING.has(state)) counts.pending += count;
51
+ }
52
+ return counts;
53
+ }
54
+ function reviewDecisionOf(value) {
55
+ if (value === "APPROVED") return "approved";
56
+ if (value === "CHANGES_REQUESTED") return "changes-requested";
57
+ if (value === "REVIEW_REQUIRED") return "review-required";
58
+ return null;
59
+ }
60
+ function detailOf(pull) {
61
+ if (typeof pull.title !== "string") return {};
62
+ return {
63
+ title: pull.title,
64
+ reviewDecision: reviewDecisionOf(pull.reviewDecision),
65
+ checks: checkCounts(pull.commits?.nodes?.[0]?.commit?.statusCheckRollup)
66
+ };
67
+ }
68
+ function stateOf(state) {
69
+ if (state === "OPEN") return "open";
70
+ if (state === "MERGED") return "merged";
71
+ if (state === "CLOSED") return "closed";
72
+ return null;
73
+ }
74
+ function epoch(text) {
75
+ const at = text ? Date.parse(text) : NaN;
76
+ return Number.isFinite(at) ? at : null;
77
+ }
78
+ function headRepoOf(pull) {
79
+ const owner = pull.headRepositoryOwner?.login;
80
+ const name = pull.headRepository?.name;
81
+ return owner && name ? `${owner.toLowerCase()}/${name.toLowerCase()}` : null;
82
+ }
83
+ /**
84
+ * Picks the pull request that describes `head` among those `gh pr list --head <branch>` returned. One whose head is
85
+ * `head` wins, then one whose head contains `head`, then one whose head `head` contains. An open one wins a tier,
86
+ * then the newest. A pull request unrelated to `head` is from an earlier use of the branch name and is ignored. A
87
+ * cross-repository pull request is kept only when its head repository is one of `ownRepos` (this checkout's own git
88
+ * remotes, lowercased "owner/name"); otherwise it only shares the branch name with a stranger's fork and is ignored.
89
+ * `isAncestor(a, b)` answers whether commit `a` is an ancestor of commit `b`, and false when git cannot tell.
90
+ */
91
+ function selectPullRequest(pulls, head, isAncestor, ownRepos = /* @__PURE__ */ new Set()) {
92
+ const known = pulls.flatMap((pull) => {
93
+ if (!pull || typeof pull !== "object") return [];
94
+ const state = stateOf(pull.state);
95
+ if (!state || !pull.headRefOid) return [];
96
+ if (pull.isCrossRepository) {
97
+ const repo = headRepoOf(pull);
98
+ if (!repo || !ownRepos.has(repo)) return [];
99
+ }
100
+ return [{
101
+ pull,
102
+ state
103
+ }];
104
+ });
105
+ const tiers = [
106
+ known.filter(({ pull }) => pull.headRefOid === head),
107
+ known.filter(({ pull }) => pull.headRefOid !== head && isAncestor(head, pull.headRefOid)),
108
+ known.filter(({ pull }) => pull.headRefOid !== head && isAncestor(pull.headRefOid, head))
109
+ ];
110
+ const tier = tiers.findIndex((entries) => entries.length > 0);
111
+ if (tier < 0) return null;
112
+ const { pull, state } = tiers[tier].toSorted((a, b) => Number(b.state === "open") - Number(a.state === "open") || b.pull.number - a.pull.number)[0];
113
+ return {
114
+ number: pull.number,
115
+ state,
116
+ draft: pull.isDraft === true,
117
+ url: pull.url,
118
+ head: pull.headRefOid,
119
+ containsHead: tier < 2,
120
+ endedAt: state === "merged" ? epoch(pull.mergedAt) : state === "closed" ? epoch(pull.closedAt) : null,
121
+ ...detailOf(pull)
122
+ };
123
+ }
124
+ function firstLine(error) {
125
+ const { stderr, message } = error;
126
+ const text = String(stderr ?? "").trim() || String(message ?? error);
127
+ return text.split("\n")[0] ?? text;
128
+ }
129
+ function ghFailure(error, command) {
130
+ if (error.status === SIGNED_OUT_EXIT) return {
131
+ unavailable: "gh is not signed in; run `gh auth login`",
132
+ sticky: true
133
+ };
134
+ if (error.code === "ETIMEDOUT") return {
135
+ unavailable: `${command} did not answer within ${GH_TIMEOUT_MS / 1e3}s`,
136
+ sticky: true
137
+ };
138
+ return {
139
+ unavailable: `${command} failed: ${firstLine(error)}`,
140
+ sticky: false
141
+ };
142
+ }
143
+ function ancestry(cwd) {
144
+ const exec = getExecutor();
145
+ return (ancestor, descendant) => exec.runFileQuiet("git", [
146
+ "-C",
147
+ cwd,
148
+ "merge-base",
149
+ "--is-ancestor",
150
+ ancestor,
151
+ descendant
152
+ ]) !== null;
153
+ }
154
+ const REMOTE_URL_RE = /^(?:https?:\/\/(?:[^/@]+@)?[^/]+\/|ssh:\/\/(?:[^/@]+@)?[^/]+\/|(?:[^/@:]+@)?[^/:]+:)([^/]+)\/([^/]+?)(?:\.git)?\/?$/i;
155
+ /**
156
+ * "owner/name", lowercased, parsed out of a git remote URL, or null when it doesn't match: https, `ssh://`, or
157
+ * scp-like (`[user@]host:owner/name`, which also covers an `~/.ssh/config` host alias in place of `user@host`).
158
+ */
159
+ function parseRemoteRepo(url) {
160
+ const [, owner, name] = REMOTE_URL_RE.exec(url.trim()) ?? [];
161
+ return owner && name ? `${owner.toLowerCase()}/${name.toLowerCase()}` : null;
162
+ }
163
+ function localRemoteRepos(cwd) {
164
+ const out = getExecutor().runFileQuiet("git", [
165
+ "-C",
166
+ cwd,
167
+ "remote",
168
+ "-v"
169
+ ]);
170
+ const repos = /* @__PURE__ */ new Set();
171
+ for (const line of out ? out.split("\n") : []) {
172
+ const url = /^\S+\s+(\S+)\s+\((?:fetch|push)\)$/.exec(line)?.[1];
173
+ const repo = url ? parseRemoteRepo(url) : null;
174
+ if (repo) repos.add(repo);
175
+ }
176
+ return repos;
177
+ }
178
+ /**
179
+ * Asks GitHub, through `gh`, which pull requests have `branch` as their head, run from `cwd` so `gh` picks the
180
+ * repository from its remotes. Each call is one fixed `gh pr list` invocation. Once `gh` is missing, signed out or
181
+ * times out, every later lookup through the same function answers `unavailable` without running it again.
182
+ */
183
+ function pullRequestLookup() {
184
+ let unavailable;
185
+ return (cwd, branch, head) => {
186
+ const exec = getExecutor();
187
+ if (unavailable === void 0) unavailable = exec.findExecutable("gh") ? null : "gh is not installed";
188
+ if (unavailable) return { unavailable };
189
+ let out;
190
+ try {
191
+ out = exec.runFile("gh", [
192
+ "pr",
193
+ "list",
194
+ "--head",
195
+ branch,
196
+ "--state",
197
+ "all",
198
+ "--limit",
199
+ "20",
200
+ "--json",
201
+ GH_FIELDS
202
+ ], {
203
+ cwd,
204
+ timeoutMs: GH_TIMEOUT_MS,
205
+ env: GH_ENV
206
+ });
207
+ } catch (error) {
208
+ const failure = ghFailure(error, "gh pr list");
209
+ if (failure.sticky) unavailable = failure.unavailable;
210
+ return { unavailable: failure.unavailable };
211
+ }
212
+ let pulls;
213
+ try {
214
+ pulls = JSON.parse(out);
215
+ } catch {
216
+ return { unavailable: "gh pr list printed no JSON" };
217
+ }
218
+ return { pullRequest: Array.isArray(pulls) ? selectPullRequest(pulls, head, ancestry(cwd), localRemoteRepos(cwd)) : null };
219
+ };
220
+ }
221
+ function branchesQuery(count, detail) {
222
+ const variables = Array.from({ length: count }, (_, i) => `, $b${i}: String!`).join("");
223
+ const nodes = `${GH_SCALAR_FIELDS.replaceAll(",", " ")} ${GH_GRAPHQL_REPO_SELECTION}` + (detail ? ` ${GH_DETAIL_FIELDS}` : "");
224
+ return `query($owner: String!, $repo: String!${variables}) { repository(owner: $owner, name: $repo) {${Array.from({ length: count }, (_, i) => ` b${i}: pullRequests(headRefName: $b${i}, states: [OPEN, CLOSED, MERGED], first: 20, orderBy: {field: CREATED_AT, direction: DESC}) { nodes { ${nodes} } }`).join("")} } }`;
225
+ }
226
+ /**
227
+ * {@link pullRequestLookup} for every branch of one repository in one fixed `gh api graphql` call run from `repo`,
228
+ * which asks for the same 20 newest pull requests of each head branch that `gh pr list --head` returns. `gh` fills
229
+ * `{owner}` and `{repo}` from the remotes of `repo`; branch names travel as variables. Once `gh` is missing, signed
230
+ * out or times out, every later call through the same function answers `unavailable` without running it again.
231
+ * With `detail`, it also asks for each pull request's title, review decision and head commit check counts.
232
+ */
233
+ function pullRequestLookups({ detail = false } = {}) {
234
+ let unavailable;
235
+ return async (repo, queries) => {
236
+ const exec = getExecutor();
237
+ if (unavailable === void 0) unavailable = exec.findExecutable("gh") ? null : "gh is not installed";
238
+ const all = (lookup) => queries.map(() => lookup);
239
+ if (unavailable) return all({ unavailable });
240
+ if (!queries.length) return [];
241
+ const args = [
242
+ "api",
243
+ "graphql",
244
+ "-F",
245
+ "owner={owner}",
246
+ "-F",
247
+ "repo={repo}"
248
+ ];
249
+ queries.forEach(({ branch }, i) => args.push("-f", `b${i}=${branch}`));
250
+ args.push("-f", `query=${branchesQuery(queries.length, detail)}`);
251
+ let out;
252
+ try {
253
+ out = await exec.runFileAsync("gh", args, {
254
+ cwd: repo,
255
+ timeoutMs: GH_TIMEOUT_MS,
256
+ env: GH_ENV
257
+ });
258
+ } catch (error) {
259
+ const failure = ghFailure(error, "gh api graphql");
260
+ if (failure.sticky) unavailable = failure.unavailable;
261
+ return all({ unavailable: failure.unavailable });
262
+ }
263
+ let found;
264
+ try {
265
+ found = JSON.parse(out).data?.repository;
266
+ } catch {
267
+ return all({ unavailable: "gh api graphql printed no JSON" });
268
+ }
269
+ if (!found) return all({ unavailable: "gh api graphql printed no repository" });
270
+ const ownRepos = localRemoteRepos(repo);
271
+ return queries.map(({ cwd, head }, i) => {
272
+ const pulls = found[`b${i}`]?.nodes;
273
+ return { pullRequest: Array.isArray(pulls) ? selectPullRequest(pulls, head, ancestry(cwd), ownRepos) : null };
274
+ });
275
+ };
276
+ }
277
+ /** The merged or closed pull request whose commits include HEAD, which makes the worktree removable. */
278
+ function endedPullRequest(lookup) {
279
+ const pr = lookup && "pullRequest" in lookup ? lookup.pullRequest : null;
280
+ return pr && pr.state !== "open" && pr.containsHead ? pr : null;
281
+ }
282
+ /** "PR #123 merged", for reports. */
283
+ function describePullRequest(pr) {
284
+ return `PR #${pr.number} ${pr.state}`;
285
+ }
286
+ /**
287
+ * Looks up the pull request of each worktree's branch and HEAD, with one {@link pullRequestLookups} call per
288
+ * repository. The map holds null for a worktree without one, and leaves out a worktree whose HEAD git cannot read
289
+ * or whose lookup was unavailable.
290
+ */
291
+ function worktreePullRequests() {
292
+ const lookup = pullRequestLookups();
293
+ return async (worktrees) => {
294
+ const exec = getExecutor();
295
+ const repos = /* @__PURE__ */ new Map();
296
+ for (const { path, branch, repository } of worktrees) {
297
+ let head;
298
+ try {
299
+ head = (await exec.runFileAsync("git", [
300
+ "-C",
301
+ path,
302
+ "rev-parse",
303
+ "HEAD"
304
+ ], { timeoutMs: 5e3 })).trim();
305
+ } catch {
306
+ continue;
307
+ }
308
+ repos.set(repository, [...repos.get(repository) ?? [], {
309
+ cwd: path,
310
+ branch,
311
+ head
312
+ }]);
313
+ }
314
+ const found = /* @__PURE__ */ new Map();
315
+ for (const [repo, queries] of repos) (await lookup(repo, queries)).forEach((result, i) => {
316
+ if ("pullRequest" in result) found.set(queries[i].cwd, result.pullRequest);
317
+ });
318
+ return found;
319
+ };
320
+ }
321
+ //#endregion
322
+ export { pullRequestLookup as a, worktreePullRequests as c, parseRemoteRepo as i, describePullRequest as n, pullRequestLookups as o, endedPullRequest as r, selectPullRequest as s, checkCounts as t };
@@ -0,0 +1,120 @@
1
+ //#region src/workspace/pull-request.d.ts
2
+ interface GhPullRequest {
3
+ number: number;
4
+ state: string;
5
+ url: string;
6
+ headRefOid: string;
7
+ mergedAt: string | null;
8
+ closedAt: string | null;
9
+ isCrossRepository?: boolean;
10
+ isDraft?: boolean;
11
+ headRepository?: {
12
+ name?: string | null;
13
+ } | null;
14
+ headRepositoryOwner?: {
15
+ login?: string | null;
16
+ } | null;
17
+ title?: string;
18
+ reviewDecision?: string | null;
19
+ commits?: {
20
+ nodes?: {
21
+ commit?: {
22
+ statusCheckRollup?: GhCheckRollup | null;
23
+ };
24
+ }[];
25
+ };
26
+ }
27
+ interface GhStateCount {
28
+ state: string;
29
+ count: number;
30
+ }
31
+ interface GhCheckRollup {
32
+ contexts?: {
33
+ checkRunCountsByState?: GhStateCount[];
34
+ statusContextCountsByState?: GhStateCount[];
35
+ };
36
+ }
37
+ interface CheckCounts {
38
+ passing: number;
39
+ failing: number;
40
+ pending: number;
41
+ }
42
+ /**
43
+ * The pull request whose head branch is the worktree's branch and whose commits include the worktree's HEAD.
44
+ * `containsHead` is false when the pull request's head is an ancestor of HEAD: HEAD has commits the pull request
45
+ * never had. `endedAt` is when it was merged or closed, in epoch milliseconds, and null while it is open.
46
+ */
47
+ interface PullRequestFact {
48
+ number: number;
49
+ state: "open" | "merged" | "closed";
50
+ draft: boolean;
51
+ url: string;
52
+ head: string;
53
+ containsHead: boolean;
54
+ endedAt: number | null;
55
+ /** Present when the lookup asked for detail. */
56
+ title?: string;
57
+ reviewDecision?: "approved" | "changes-requested" | "review-required" | null;
58
+ checks?: CheckCounts | null;
59
+ }
60
+ /** Sums a head commit's check runs and commit statuses by outcome; null when the commit has no checks. */
61
+ declare function checkCounts(rollup: GhCheckRollup | null | undefined): CheckCounts | null;
62
+ type PullRequestLookup = {
63
+ pullRequest: PullRequestFact | null;
64
+ } | {
65
+ unavailable: string;
66
+ };
67
+ /**
68
+ * Picks the pull request that describes `head` among those `gh pr list --head <branch>` returned. One whose head is
69
+ * `head` wins, then one whose head contains `head`, then one whose head `head` contains. An open one wins a tier,
70
+ * then the newest. A pull request unrelated to `head` is from an earlier use of the branch name and is ignored. A
71
+ * cross-repository pull request is kept only when its head repository is one of `ownRepos` (this checkout's own git
72
+ * remotes, lowercased "owner/name"); otherwise it only shares the branch name with a stranger's fork and is ignored.
73
+ * `isAncestor(a, b)` answers whether commit `a` is an ancestor of commit `b`, and false when git cannot tell.
74
+ */
75
+ declare function selectPullRequest(pulls: readonly GhPullRequest[], head: string, isAncestor: (ancestor: string, descendant: string) => boolean, ownRepos?: ReadonlySet<string>): PullRequestFact | null;
76
+ /**
77
+ * "owner/name", lowercased, parsed out of a git remote URL, or null when it doesn't match: https, `ssh://`, or
78
+ * scp-like (`[user@]host:owner/name`, which also covers an `~/.ssh/config` host alias in place of `user@host`).
79
+ */
80
+ declare function parseRemoteRepo(url: string): string | null;
81
+ /**
82
+ * Asks GitHub, through `gh`, which pull requests have `branch` as their head, run from `cwd` so `gh` picks the
83
+ * repository from its remotes. Each call is one fixed `gh pr list` invocation. Once `gh` is missing, signed out or
84
+ * times out, every later lookup through the same function answers `unavailable` without running it again.
85
+ */
86
+ declare function pullRequestLookup(): (cwd: string, branch: string, head: string) => PullRequestLookup;
87
+ /** A worktree whose pull request {@link pullRequestLookups} looks up: its path, branch and HEAD commit. */
88
+ interface PullRequestQuery {
89
+ cwd: string;
90
+ branch: string;
91
+ head: string;
92
+ }
93
+ /**
94
+ * {@link pullRequestLookup} for every branch of one repository in one fixed `gh api graphql` call run from `repo`,
95
+ * which asks for the same 20 newest pull requests of each head branch that `gh pr list --head` returns. `gh` fills
96
+ * `{owner}` and `{repo}` from the remotes of `repo`; branch names travel as variables. Once `gh` is missing, signed
97
+ * out or times out, every later call through the same function answers `unavailable` without running it again.
98
+ * With `detail`, it also asks for each pull request's title, review decision and head commit check counts.
99
+ */
100
+ declare function pullRequestLookups({ detail }?: {
101
+ detail?: boolean;
102
+ }): (repo: string, queries: readonly PullRequestQuery[]) => Promise<PullRequestLookup[]>;
103
+ /** The merged or closed pull request whose commits include HEAD, which makes the worktree removable. */
104
+ declare function endedPullRequest(lookup: PullRequestLookup | null): PullRequestFact | null;
105
+ /** "PR #123 merged", for reports. */
106
+ declare function describePullRequest(pr: Pick<PullRequestFact, "number" | "state">): string;
107
+ /** A linked worktree whose pull request {@link worktreePullRequests} looks up. */
108
+ interface WorktreeBranch {
109
+ path: string;
110
+ branch: string;
111
+ repository: string;
112
+ }
113
+ /**
114
+ * Looks up the pull request of each worktree's branch and HEAD, with one {@link pullRequestLookups} call per
115
+ * repository. The map holds null for a worktree without one, and leaves out a worktree whose HEAD git cannot read
116
+ * or whose lookup was unavailable.
117
+ */
118
+ declare function worktreePullRequests(): (worktrees: readonly WorktreeBranch[]) => Promise<Map<string, PullRequestFact | null>>;
119
+ //#endregion
120
+ export { CheckCounts, GhCheckRollup, GhPullRequest, PullRequestFact, PullRequestLookup, PullRequestQuery, WorktreeBranch, checkCounts, describePullRequest, endedPullRequest, parseRemoteRepo, pullRequestLookup, pullRequestLookups, selectPullRequest, worktreePullRequests };
@@ -0,0 +1,2 @@
1
+ import { a as pullRequestLookup, c as worktreePullRequests, i as parseRemoteRepo, n as describePullRequest, o as pullRequestLookups, r as endedPullRequest, s as selectPullRequest, t as checkCounts } from "./pull-request-pxnDCQzn.mjs";
2
+ export { checkCounts, describePullRequest, endedPullRequest, parseRemoteRepo, pullRequestLookup, pullRequestLookups, selectPullRequest, worktreePullRequests };
@@ -0,0 +1,37 @@
1
+ import { at as gitCommonDirOnDisk } from "./settings-oTSkkNrM.mjs";
2
+ import { existsSync, rmSync } from "node:fs";
3
+ import { loadConfig, recordingEnabled, workspaceRecordingsDir } from "@stim-cli/core/state";
4
+ //#region src/workspace/recordings.ts
5
+ /** `recording.enabled` for one registered workspace, from `STIM_RECORDING` and its workspace, repo and machine layers. */
6
+ function workspaceRecordingEnabled(path, project, config, env) {
7
+ const repo = gitCommonDirOnDisk(path);
8
+ return recordingEnabled(env, [
9
+ project.settings,
10
+ repo ? config?.repos?.[repo]?.settings : void 0,
11
+ config
12
+ ]);
13
+ }
14
+ /** Every registered workspace's `recording.enabled` from the settings files alone, without `STIM_RECORDING`. */
15
+ function recordingLayers() {
16
+ const config = loadConfig();
17
+ return new Map(Object.entries(config?.projects ?? {}).map(([path, project]) => [path, workspaceRecordingEnabled(path, project, config, {})]));
18
+ }
19
+ /**
20
+ * Deletes the recordings of every workspace a settings write turned off, comparing the settings files before
21
+ * (`before`, from {@link recordingLayers}) and after it, and returns those workspaces. stim-server stops
22
+ * recording them once `stim status` shows the change.
23
+ */
24
+ function deleteTurnedOffRecordings(before) {
25
+ return [...recordingLayers()].flatMap(([path, enabled]) => {
26
+ if (enabled || before.get(path) === false) return [];
27
+ const dir = workspaceRecordingsDir(path);
28
+ if (!existsSync(dir)) return [];
29
+ rmSync(dir, {
30
+ recursive: true,
31
+ force: true
32
+ });
33
+ return [path];
34
+ });
35
+ }
36
+ //#endregion
37
+ export { recordingLayers as n, workspaceRecordingEnabled as r, deleteTurnedOffRecordings as t };