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.
- package/README.md +2 -0
- package/dist/activity-BxwdD81S.mjs +609 -0
- package/dist/agent-device-usage-output-Dpih1Tfa.mjs +411 -0
- package/dist/{android-d6rGq1pA.mjs → android-B_NumVyQ.mjs} +169 -81
- package/dist/{android-LbPFRWVL.mjs → android-Ift4kVvt.mjs} +517 -413
- package/dist/android-cas-8U4DPqeR.mjs +167 -0
- package/dist/android-cas-compiler.mjs +2 -2
- package/dist/{android-XpvCrV7q.mjs → android-r2rBL8kV.mjs} +2 -2
- package/dist/{app-install-BpgSgkJj.mjs → app-install-CKWdbXIX.mjs} +128 -151
- package/dist/attempt-Bk2ouQVL.mjs +39 -0
- package/dist/budget-DjrURfQn.mjs +2700 -0
- package/dist/{build-plan-Dcr-3ie0.mjs → build-plan-DjeSb2Xl.mjs} +192 -653
- package/dist/build-slots-rUk77xqh.mjs +132 -0
- package/dist/{cache-manifest-C68QQ0ua.mjs → cache-manifest-DnUHBJaU.mjs} +2 -2
- package/dist/cache-manifest.mjs +1 -1
- package/dist/{chrome-BPCiC3ZX.mjs → chrome-BrcVWQyD.mjs} +1 -1
- package/dist/cli-BkYZ_6hy.mjs +51 -0
- package/dist/cli.mjs +6 -42
- package/dist/client-BTY686R6.mjs +1368 -0
- package/dist/collector-run.mjs +9 -195
- package/dist/{command-output-DVcpw6eE.mjs → command-output-DnJMdEY-.mjs} +1 -1
- package/dist/{config-lvc7d3C2.mjs → config-D0DbpZtM.mjs} +28 -29
- package/dist/{created-devices-CllH_8eb.mjs → created-devices-D5owjN4C.mjs} +2 -2
- package/dist/{dependency-state-DsT7aKeo.mjs → dependency-state-B6cMXZab.mjs} +2 -2
- package/dist/{deps-D9IcKqXS.mjs → deps-urbmfXig.mjs} +3 -3
- package/dist/{detached-entry-DJHoca49.mjs → detached-entry-CiTEgMR1.mjs} +1 -1
- package/dist/dev-client-CroAIjjj.mjs +43 -0
- package/dist/{device-CjuuAJjQ.mjs → device-DZQjF7H5.mjs} +8 -7
- package/dist/device-capacity-BHwxNBlu.mjs +136 -0
- package/dist/device-host-worker.d.mts +1 -0
- package/dist/device-host-worker.mjs +964 -0
- package/dist/device-ios-BadmeL2V.mjs +381 -0
- package/dist/{device-lease-5c3WrVRm.mjs → device-lease-D-hLqx1b.mjs} +43 -36
- package/dist/{device-pool-BiyDyPP-.mjs → device-lease-run-CqjShqWW.mjs} +6 -126
- package/dist/device-pool-CUaEWMMm.mjs +125 -0
- package/dist/{device-remote-D87zEzaq.mjs → device-remote-D5WVWqOq.mjs} +69 -163
- package/dist/{doctor-x9pnU9fq.mjs → doctor-Cimf-gGB.mjs} +241 -103
- package/dist/{error-diagnostics-C5HzcwCW.mjs → error-diagnostics-B9L4Ae0x.mjs} +15 -18
- package/dist/{exec-DK8KaVHK.mjs → exec-CaErVv7t.mjs} +5 -1
- package/dist/{gc-ko6mUAYD.mjs → gc-Bqy4bjEu.mjs} +599 -108
- package/dist/{android-cas-iZK3DOCr.mjs → gradle-BsO1MdsH.mjs} +8 -294
- package/dist/{guide-6R0VNFqI.mjs → guide-B_uyKTxW.mjs} +2511 -239
- package/dist/{guide-status-BZrFPIA_.mjs → guide-status-CjEZMAD4.mjs} +3 -3
- package/dist/hosted-client-a-WmqE2L.mjs +197 -0
- package/dist/hosted-ios-CIWelIqf.mjs +220 -0
- package/dist/hosted-logs-D5g041Q3.mjs +187 -0
- package/dist/hosted-macos-BSZyIb3y.mjs +135 -0
- package/dist/idle-DYVymG92.mjs +1099 -0
- package/dist/idle-shutdown-CTsDnAde.mjs +122 -0
- package/dist/in-use-DvBw0Kxo.mjs +130 -0
- package/dist/{ios-BwDPrnYQ.mjs → ios-BaInn3sW.mjs} +201 -10
- package/dist/ios-Cr1h5gnO.mjs +189 -0
- package/dist/{ios-P1f5nm2n.mjs → ios-aq3QoGI3.mjs} +1159 -633
- package/dist/{ios-device-BiOsSVzd.mjs → ios-device-Ee7sDXfp.mjs} +1 -1
- package/dist/{ios-device-DGglU3E_.mjs → ios-device-FfCN_C8p.mjs} +12 -7
- package/dist/ios-state-DS5UpYTH.mjs +48 -0
- package/dist/{launch-verify-Deg4tB3Z.mjs → launch-verify-2DBRizjo.mjs} +5 -5
- package/dist/logs-CHEVK8zD.mjs +371 -0
- package/dist/machines-BIBUdhpj.mjs +265 -0
- package/dist/macos-CSYEAFmF.mjs +473 -0
- package/dist/macos-run.d.mts +5 -0
- package/dist/macos-run.mjs +2 -0
- package/dist/maintenance-run.d.mts +4 -0
- package/dist/maintenance-run.mjs +254 -0
- package/dist/{metro-CcEo8Fl8.mjs → metro-BeK1ZPz1.mjs} +20 -120
- package/dist/metro-gateway-BJ-rxQAB.mjs +84 -0
- package/dist/{named-ports-Blg8IxOh.mjs → named-ports-Bk_YXEmE.mjs} +6 -59
- package/dist/native-run-NaBrRFtQ.mjs +147 -0
- package/dist/{native-runtime-DjBHRg5m.mjs → native-runtime-DBzRnEKS.mjs} +9 -5
- package/dist/{ndjson-CDY9IkVC.mjs → ndjson-PclOIwVY.mjs} +2 -2
- package/dist/offload-worker.d.mts +122 -0
- package/dist/offload-worker.mjs +606 -0
- package/dist/{ownership-DyYDF5ww.mjs → ownership-DiA16AsN.mjs} +3 -2
- package/dist/ownership-hCkPzGmr.mjs +571 -0
- package/dist/{page-BHUzTDFt.mjs → page-QF_Ke10s.mjs} +7 -2
- package/dist/{ports-BguS83ex.mjs → ports-R2k4Knz3.mjs} +3 -3
- package/dist/prebuild-D2fsca_Z.mjs +213 -0
- package/dist/preview-DhVbvPAZ.mjs +318 -0
- package/dist/process-identity-BmRm1qV0.mjs +12 -0
- package/dist/{project-CWlvEaeN.mjs → project-Du91Ilvc.mjs} +74 -19
- package/dist/pull-request-pxnDCQzn.mjs +322 -0
- package/dist/pull-requests.d.mts +120 -0
- package/dist/pull-requests.mjs +2 -0
- package/dist/recordings-BIXOzVnr.mjs +37 -0
- package/dist/{reload-BiniwsSZ.mjs → reload-rM9jV1fz.mjs} +109 -24
- package/dist/{remote-cache-CcR6o6lI.mjs → remote-cache-C8-V13XN.mjs} +5 -5
- package/dist/run-f4vI6EnK.mjs +119 -0
- package/dist/{server-bare-DGfgyJ5J.mjs → server-bare-CpPGmnmO.mjs} +33 -15
- package/dist/server-expo-CVqO2c35.mjs +2 -0
- package/dist/{server-expo-BVuwQ65v.mjs → server-expo-MC0rHlOC.mjs} +8 -12
- package/dist/settings-BBpMcDbD.mjs +25 -0
- package/dist/{settings-B-fsC-Om.mjs → settings-ZkDzkzvB.mjs} +22 -10
- package/dist/settings-oTSkkNrM.mjs +1439 -0
- package/dist/settings.schema.json +845 -97
- package/dist/{simslim-DrDN4WuV.mjs → simslim-B8-7AoTI.mjs} +8 -132
- package/dist/{slot-launch-D5t8EiMf.mjs → slot-launch-B8VrHxn1.mjs} +11 -8
- package/dist/{spawn-claims-Drw_o-Fb.mjs → spawn-claims-D0XfSe8u.mjs} +1 -1
- package/dist/spawn-entry-jw19m8Nl.mjs +17 -0
- package/dist/stage-CzaiwFsI.mjs +143 -0
- package/dist/start-CX8Ig-U2.mjs +2 -0
- package/dist/{start-s4RXp1wy.mjs → start-ClDfwXa4.mjs} +116 -65
- package/dist/state-3DwSQIBK.mjs +41 -0
- package/dist/state-B4PQFXMW.d.mts +12 -0
- package/dist/{state-Dk9RVXI7.mjs → state-C1jB7Q8q.mjs} +70 -8
- package/dist/{state-DHqvsbqC.mjs → state-DcW6vOd_.mjs} +7 -4
- package/dist/stats-CEZ_i5je.mjs +77 -0
- package/dist/{status-BgAuxESg.mjs → status-BuvBFeII.mjs} +118 -20
- package/dist/{status-BsPAnfOI.mjs → status-Cs7VLg9D.mjs} +542 -283
- package/dist/status-DlKvVJBE.mjs +49 -0
- package/dist/stdout-reader-C8yjre1x.mjs +23 -0
- package/dist/stim-desktop-Cnz8CPnl.mjs +63 -0
- package/dist/stim-footprint.swift +32 -0
- package/dist/{stim-installations-CINXy_su.mjs → stim-installations-QMEI32v1.mjs} +1 -1
- package/dist/stop-B0Yxtmiy.mjs +48 -0
- package/dist/{stop-BzreOWJp.mjs → stop-Bffozsac.mjs} +83 -16
- package/dist/{stop-BWIF_6BK.mjs → stop-Dt_dM16A.mjs} +2 -2
- package/dist/stop-cause-CidxIqup.mjs +97 -0
- package/dist/supervisor-run.d.mts +43 -11
- package/dist/supervisor-run.mjs +363 -24
- package/dist/support-CAP8qDUF.mjs +316 -0
- package/dist/support-D4aYJniO.mjs +201 -0
- package/dist/teardown-CL6vDs2Y.mjs +727 -0
- package/dist/{xcode-Cp5KvK9x.mjs → toolchain-DkUflduf.mjs} +364 -14
- package/dist/trigger-Bemu8ytw.mjs +65 -0
- package/dist/trigger-CXts6qt1.mjs +2 -0
- package/dist/warm-progress-CpGv3Wb6.mjs +732 -0
- package/dist/{web-BsQs1WlO.mjs → web-DWUaMp8P.mjs} +37 -21
- package/dist/web-run.mjs +310 -13
- package/dist/workspace-process-lock-BL7Q0HCO.mjs +57 -0
- package/dist/workspace-state-B8-aJ1_5.mjs +371 -0
- package/dist/workspaces-BV4pdHFz.mjs +306 -0
- package/dist/{worktree-UrQWXSeb.mjs → worktree-K-xj9Db0.mjs} +625 -256
- package/dist/worktree-jnG8Iph5.mjs +2 -0
- package/package.json +10 -5
- package/shim/bundle-response.cjs +28 -12
- package/dist/activity-CYVDcrt3.mjs +0 -356
- package/dist/budget-CGDkhvv_.mjs +0 -1745
- package/dist/build-progress-DxKpwnaS.mjs +0 -247
- package/dist/idle-DXtx3RbX.mjs +0 -191
- package/dist/in-use-DlqBDKqY.mjs +0 -249
- package/dist/logs-Civ4gbBH.mjs +0 -486
- package/dist/merge-state-ytFQCeh4.mjs +0 -262
- package/dist/server-expo-DIHDhvGg.mjs +0 -2
- package/dist/settings-D61iNOsB.mjs +0 -629
- package/dist/start-Cetfvy8l.mjs +0 -2
- package/dist/stats-CFpFGlJV.mjs +0 -62
- package/dist/stats-CX1Z8Tca.mjs +0 -368
- package/dist/stim-desktop-CXt8-Z3J.mjs +0 -40
- package/dist/stop-cause-hjGBOZTG.mjs +0 -1231
- package/dist/worktree-BVrHsYZG.mjs +0 -752
- package/dist/worktree-CHWWvm5v.mjs +0 -2
- 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 };
|