@reviewflow/sdk 0.0.0-stage → 6.21.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 (50) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +86 -2
  3. package/build/files-node.mjs +70 -0
  4. package/build/git-node.mjs +1792 -0
  5. package/build/index-browser.es.js +3338 -0
  6. package/build/index-node.mjs +3338 -0
  7. package/package.json +136 -4
  8. package/src/api/pullRequest.ts +617 -0
  9. package/src/config/insertCheckoutEntry.ts +54 -0
  10. package/src/diff/diffPaths.ts +78 -0
  11. package/src/diff/impacts.ts +47 -0
  12. package/src/diff/readingOrder.ts +428 -0
  13. package/src/diff/themes.ts +105 -0
  14. package/src/diff/types.ts +141 -0
  15. package/src/files/editableFile.ts +163 -0
  16. package/src/files.ts +13 -0
  17. package/src/git/groupDiffPaths.ts +134 -0
  18. package/src/git/manifests.ts +96 -0
  19. package/src/git/moduleBindings.ts +571 -0
  20. package/src/git/pullRequestDiff.ts +159 -0
  21. package/src/git/readBranchDiff.ts +359 -0
  22. package/src/git/readImpacts.ts +797 -0
  23. package/src/git/readingOrder.ts +190 -0
  24. package/src/git/run.ts +213 -0
  25. package/src/git.ts +38 -0
  26. package/src/github/applySuggestions.ts +399 -0
  27. package/src/github/editPullRequest.ts +790 -0
  28. package/src/github/fetchCompareDiff.ts +21 -0
  29. package/src/github/imageProxies.ts +43 -0
  30. package/src/github/lineDiff.ts +145 -0
  31. package/src/github/prQuery.ts +298 -0
  32. package/src/github/pullRequestDetail.ts +1347 -0
  33. package/src/github/pullRequestHead.ts +65 -0
  34. package/src/github/pullRequestRevisions.ts +124 -0
  35. package/src/github/transport.ts +171 -0
  36. package/src/index.ts +35 -0
  37. package/src/reviewflow/board.ts +333 -0
  38. package/src/reviewflow/changedParts.ts +80 -0
  39. package/src/reviewflow/checksFromReviewflow.ts +75 -0
  40. package/src/reviewflow/client.ts +589 -0
  41. package/src/reviewflow/follow.ts +345 -0
  42. package/src/reviewflow/status.ts +17 -0
  43. package/src/reviewflow/stream.ts +199 -0
  44. package/src/types/entities.ts +8 -0
  45. package/src/types/ids.ts +1 -0
  46. package/src/types/reviewflow.ts +32 -0
  47. package/src/utils/pullRequestEdit.ts +108 -0
  48. package/src/utils/pullRequestPart.ts +12 -0
  49. package/src/utils/pullRequestReaction.ts +19 -0
  50. package/src/utils/pullRequestSuggestion.ts +148 -0
@@ -0,0 +1,1792 @@
1
+ import { execFile, spawn } from 'node:child_process';
2
+ import { env } from 'node:process';
3
+ import { promisify } from 'node:util';
4
+ import { lstat } from 'node:fs/promises';
5
+ import { join } from 'node:path';
6
+ import { existsSync } from 'node:fs';
7
+ import { parseSync } from 'oxc-parser';
8
+ import { builtinModules } from 'node:module';
9
+ import { setImmediate } from 'node:timers/promises';
10
+
11
+ const execFileAsync = promisify(execFile);
12
+ const timeoutMs = 1e4;
13
+ const networkTimeoutMs = 6e4;
14
+ const defaultMaxBuffer = 4 * 1024 * 1024;
15
+ const batchMaxBuffer = 256 * 1024 * 1024;
16
+ const networkEnv = () => ({
17
+ ...env,
18
+ GIT_TERMINAL_PROMPT: "0",
19
+ GIT_SSH_COMMAND: env.GIT_SSH_COMMAND ?? "ssh -o BatchMode=yes"
20
+ });
21
+ const gitFailedCode = "ERR_GIT_FAILED";
22
+ const reasonLine = (stderr) => {
23
+ const lines = stderr.split("\n").map((line) => line.trim()).filter((line) => line !== "");
24
+ return lines.find((line) => line.startsWith("! ")) ?? lines.find((line) => /^(?:fatal|error):/.test(line)) ?? lines.find((line) => !line.startsWith("hint:")) ?? "";
25
+ };
26
+ const failureReason = (error, timeout) => {
27
+ if (typeof error !== "object" || error === null) return "";
28
+ if ("killed" in error && error.killed === true) {
29
+ return `killed after ${String(timeout / 1e3)}s without finishing`;
30
+ }
31
+ return "stderr" in error ? reasonLine(String(error.stderr)) : "";
32
+ };
33
+ const isExitOneAnswer = (error) => typeof error === "object" && error !== null && "code" in error && error.code === 1 && "stdout" in error && typeof error.stdout === "string" && error.stdout !== "";
34
+ const git = async (cwd, args, options = {}) => {
35
+ const timeout = options.network ? networkTimeoutMs : timeoutMs;
36
+ try {
37
+ const { stdout } = await execFileAsync("git", args, {
38
+ cwd,
39
+ timeout,
40
+ maxBuffer: options.maxBuffer ?? defaultMaxBuffer,
41
+ env: options.network ? networkEnv() : env,
42
+ ...options.signal === void 0 ? {} : { signal: options.signal }
43
+ });
44
+ return stdout;
45
+ } catch (error) {
46
+ if (options.exitOneIsAnswer && isExitOneAnswer(error)) return error.stdout;
47
+ const reason = failureReason(error, timeout);
48
+ throw Object.assign(
49
+ new Error(
50
+ `git ${args.join(" ")} failed in ${cwd}${reason ? `: ${reason}` : ""}`,
51
+ { cause: error }
52
+ ),
53
+ { code: gitFailedCode }
54
+ );
55
+ }
56
+ };
57
+ const unansweredHeader = / (?:missing|ambiguous)$/;
58
+ const parseBatchOutput = (output, paths) => {
59
+ const contents = /* @__PURE__ */ new Map();
60
+ let offset = 0;
61
+ for (const path of paths) {
62
+ const newline = output.indexOf("\n", offset);
63
+ if (newline === -1) break;
64
+ const header = output.toString("utf8", offset, newline);
65
+ offset = newline + 1;
66
+ if (unansweredHeader.test(header)) continue;
67
+ const [, type, sizeText] = header.split(" ");
68
+ const size = Number(sizeText);
69
+ if (!Number.isSafeInteger(size) || size < 0) break;
70
+ if (type === "blob") {
71
+ contents.set(path, output.toString("utf8", offset, offset + size));
72
+ }
73
+ offset += size + 1;
74
+ }
75
+ return contents;
76
+ };
77
+ const catFiles = async (cwd, sha, paths) => {
78
+ const asked = paths.filter((path) => !path.includes("\n"));
79
+ if (asked.length === 0) return /* @__PURE__ */ new Map();
80
+ const args = ["cat-file", "--batch"];
81
+ return new Promise((resolve, reject) => {
82
+ const child = spawn("git", args, { cwd, env, timeout: timeoutMs });
83
+ const chunks = [];
84
+ let size = 0;
85
+ let stderr = "";
86
+ child.stdout.on("data", (chunk) => {
87
+ size += chunk.length;
88
+ if (size > batchMaxBuffer) {
89
+ child.kill();
90
+ return;
91
+ }
92
+ chunks.push(chunk);
93
+ });
94
+ child.stderr.on("data", (chunk) => {
95
+ stderr += chunk.toString("utf8");
96
+ });
97
+ child.on("error", reject);
98
+ child.stdin.on("error", () => {
99
+ });
100
+ child.on("close", (code, signal) => {
101
+ if (code === 0) {
102
+ resolve(parseBatchOutput(Buffer.concat(chunks), asked));
103
+ return;
104
+ }
105
+ const reason = signal === null ? reasonLine(stderr) : `killed (${signal}) after reading ${String(size)} bytes`;
106
+ reject(
107
+ Object.assign(
108
+ new Error(
109
+ `git ${args.join(" ")} failed in ${cwd}${reason ? `: ${reason}` : ""}`
110
+ ),
111
+ { code: gitFailedCode }
112
+ )
113
+ );
114
+ });
115
+ child.stdin.end(asked.map((path) => `${sha}:${path}
116
+ `).join(""));
117
+ });
118
+ };
119
+ const gitOrNull = async (cwd, args, options = {}) => {
120
+ try {
121
+ return await git(cwd, args, options);
122
+ } catch {
123
+ return null;
124
+ }
125
+ };
126
+
127
+ const defaultDiffLimits = {
128
+ maxLinesPerFile: 3e3,
129
+ maxTotalLines: 3e4,
130
+ maxFiles: 500,
131
+ maxUntrackedFiles: 100,
132
+ maxUntrackedBytes: 512 * 1024
133
+ };
134
+ const maxPatchBytes = 64 * 1024 * 1024;
135
+ const neutralConfig = ["-c", "core.quotePath=false"];
136
+ const diffFlags = [
137
+ "--no-color",
138
+ "--no-ext-diff",
139
+ "--no-textconv",
140
+ "--src-prefix=a/",
141
+ "--dst-prefix=b/"
142
+ ];
143
+ const parseNumstat = (output) => {
144
+ const records = output.split("\0");
145
+ const entries = [];
146
+ for (let index = 0; index < records.length; index++) {
147
+ const record = records[index] ?? "";
148
+ if (record === "") continue;
149
+ const [added = "", deleted = "", ...rest] = record.split(" ");
150
+ const lines = added === "-" || deleted === "-" ? null : Number(added) + Number(deleted);
151
+ const path = rest.join(" ");
152
+ if (path !== "") {
153
+ entries.push({ path, paths: [path], lines });
154
+ continue;
155
+ }
156
+ const from = records[index + 1] ?? "";
157
+ const to = records[index + 2] ?? "";
158
+ index += 2;
159
+ entries.push({ path: to, paths: [from, to], lines });
160
+ }
161
+ return entries;
162
+ };
163
+ const omissionReason = (lines, budget, limits) => {
164
+ if (lines === null) return "binary";
165
+ if (lines > limits.maxLinesPerFile) return "tooLarge";
166
+ if (budget.files + 1 > limits.maxFiles || budget.lines + lines > limits.maxTotalLines) {
167
+ return "budget";
168
+ }
169
+ return null;
170
+ };
171
+ const pathspecs = (included, excluded) => {
172
+ if (excluded.length === 0) return [];
173
+ if (included.length < excluded.length) {
174
+ return ["--", ...included.map((path) => `:(literal)${path}`)];
175
+ }
176
+ return ["--", ...excluded.map((path) => `:(exclude,literal)${path}`)];
177
+ };
178
+ const readNumstat = async (cwd, range, whitespaceFlags) => parseNumstat(
179
+ await git(cwd, [
180
+ ...neutralConfig,
181
+ "diff",
182
+ "--no-relative",
183
+ "-M",
184
+ ...whitespaceFlags,
185
+ "--numstat",
186
+ "-z",
187
+ ...range
188
+ ])
189
+ );
190
+ const whitespaceOnlyFiles = async (cwd, range, shown) => {
191
+ const shownPaths = new Set(shown.map(({ path }) => path));
192
+ const all = await readNumstat(cwd, range, []);
193
+ return all.filter(({ path }) => !shownPaths.has(path)).map(({ path }) => ({ path, reason: "whitespace" }));
194
+ };
195
+ const readTrackedDiff = async ({
196
+ cwd,
197
+ range,
198
+ whitespace,
199
+ limits,
200
+ budget
201
+ }) => {
202
+ const whitespaceFlags = whitespace === "hide" ? ["-w"] : [];
203
+ const entries = await readNumstat(cwd, range, whitespaceFlags);
204
+ const omitted = whitespace === "hide" ? await whitespaceOnlyFiles(cwd, range, entries) : [];
205
+ const excluded = [];
206
+ const included = [];
207
+ const paths = [];
208
+ for (const entry of entries) {
209
+ const reason = omissionReason(entry.lines, budget, limits);
210
+ if (reason === null) {
211
+ budget.files += 1;
212
+ budget.lines += entry.lines ?? 0;
213
+ paths.push(entry.path);
214
+ included.push(...entry.paths);
215
+ continue;
216
+ }
217
+ omitted.push({ path: entry.path, reason });
218
+ excluded.push(...entry.paths);
219
+ }
220
+ if (paths.length === 0) return { patch: "", paths, omitted };
221
+ const patch = await git(
222
+ cwd,
223
+ [
224
+ ...neutralConfig,
225
+ "diff",
226
+ ...diffFlags,
227
+ "--no-relative",
228
+ "-M",
229
+ ...whitespaceFlags,
230
+ ...range,
231
+ ...pathspecs(included, excluded)
232
+ ],
233
+ { maxBuffer: maxPatchBytes }
234
+ );
235
+ return { patch, paths, omitted };
236
+ };
237
+ const countAddedLines = (patch) => {
238
+ const lines = patch.split("\n");
239
+ const firstHunk = lines.findIndex((line) => line.startsWith("@@"));
240
+ if (firstHunk === -1) return 0;
241
+ return lines.slice(firstHunk).filter((line) => line.startsWith("+")).length;
242
+ };
243
+ const isBinaryPatch = (patch) => /^Binary files /m.test(patch);
244
+ const readUntrackedDiff = async (cwd, limits, budget) => {
245
+ const listing = await git(cwd, [
246
+ "ls-files",
247
+ "--others",
248
+ "--exclude-standard",
249
+ "-z"
250
+ ]);
251
+ const listed = listing.split("\0").filter((path) => path !== "");
252
+ const patches = [];
253
+ const paths = [];
254
+ const omitted = [];
255
+ for (const [index, path] of listed.entries()) {
256
+ if (index >= limits.maxUntrackedFiles) {
257
+ omitted.push({ path, reason: "budget" });
258
+ continue;
259
+ }
260
+ const entry = await lstat(join(cwd, path)).catch(() => null);
261
+ if (entry === null || entry.isDirectory()) continue;
262
+ if (entry.size > limits.maxUntrackedBytes) {
263
+ omitted.push({ path, reason: "tooLarge" });
264
+ continue;
265
+ }
266
+ const patch = await git(
267
+ cwd,
268
+ [
269
+ ...neutralConfig,
270
+ "diff",
271
+ ...diffFlags,
272
+ "--no-index",
273
+ "--",
274
+ "/dev/null",
275
+ path
276
+ ],
277
+ { exitOneIsAnswer: true, maxBuffer: maxPatchBytes }
278
+ );
279
+ const lines = isBinaryPatch(patch) ? null : countAddedLines(patch);
280
+ const reason = omissionReason(lines, budget, limits);
281
+ if (reason !== null) {
282
+ omitted.push({ path, reason });
283
+ continue;
284
+ }
285
+ budget.files += 1;
286
+ budget.lines += lines ?? 0;
287
+ patches.push(patch);
288
+ paths.push(path);
289
+ }
290
+ return { patches, paths, omitted };
291
+ };
292
+ const readBranchDiff = async ({
293
+ cwd,
294
+ mergeBase,
295
+ scope = "committed",
296
+ head = "HEAD",
297
+ whitespace = "show",
298
+ limits = defaultDiffLimits
299
+ }) => {
300
+ if (mergeBase.startsWith("-") || head.startsWith("-")) {
301
+ throw new Error(
302
+ `not a revision: ${mergeBase.startsWith("-") ? mergeBase : head}`
303
+ );
304
+ }
305
+ const budget = { files: 0, lines: 0 };
306
+ const tracked = await readTrackedDiff({
307
+ cwd,
308
+ range: scope === "committed" ? [mergeBase, head] : [mergeBase],
309
+ whitespace,
310
+ limits,
311
+ budget
312
+ });
313
+ if (scope === "committed") {
314
+ return {
315
+ patch: tracked.patch,
316
+ paths: tracked.paths,
317
+ omitted: tracked.omitted,
318
+ untrackedCount: 0
319
+ };
320
+ }
321
+ const untracked = await readUntrackedDiff(cwd, limits, budget);
322
+ return {
323
+ patch: [tracked.patch, ...untracked.patches].join(""),
324
+ paths: [...tracked.paths, ...untracked.paths],
325
+ omitted: [...tracked.omitted, ...untracked.omitted],
326
+ untrackedCount: untracked.patches.length
327
+ };
328
+ };
329
+
330
+ const manifestNames = [
331
+ // javascript
332
+ "package.json",
333
+ // python
334
+ "pyproject.toml",
335
+ "setup.py",
336
+ // ruby
337
+ "Gemfile",
338
+ // rust
339
+ "Cargo.toml",
340
+ // go
341
+ "go.mod",
342
+ // php
343
+ "composer.json",
344
+ // jvm
345
+ "pom.xml",
346
+ "build.gradle",
347
+ "build.gradle.kts",
348
+ // elixir
349
+ "mix.exs",
350
+ // dart
351
+ "pubspec.yaml",
352
+ // swift
353
+ "Package.swift"
354
+ ];
355
+ const manifests = new Set(manifestNames);
356
+ const isManifestPath = (path) => manifests.has(path.slice(path.lastIndexOf("/") + 1));
357
+ const packageOf = (path, isPackage) => {
358
+ const directories = path.split("/").slice(0, -1);
359
+ for (let depth = directories.length; depth >= 1; depth--) {
360
+ const directory = directories.slice(0, depth).join("/");
361
+ if (isPackage(directory)) return directory;
362
+ }
363
+ return directories[0] ?? "";
364
+ };
365
+ const noPackages = () => false;
366
+ const listTree = async (checkout, sha) => {
367
+ const listing = await git(
368
+ checkout,
369
+ ["ls-tree", "-r", "--name-only", "-z", sha],
370
+ {
371
+ maxBuffer: 256 * 1024 * 1024
372
+ }
373
+ );
374
+ return listing.split("\0").filter((path) => path !== "");
375
+ };
376
+ const packagesInListing = (paths) => {
377
+ const directories = /* @__PURE__ */ new Set();
378
+ for (const path of paths) {
379
+ const slash = path.lastIndexOf("/");
380
+ if (slash === -1) continue;
381
+ if (isManifestPath(path)) directories.add(path.slice(0, slash));
382
+ }
383
+ return (directory) => directories.has(directory);
384
+ };
385
+ const packagesAtTree = async (checkout, sha) => packagesInListing(await listTree(checkout, sha));
386
+ const packagesOnDisk = (worktree) => {
387
+ const known = /* @__PURE__ */ new Map();
388
+ return (directory) => {
389
+ const cached = known.get(directory);
390
+ if (cached !== void 0) return cached;
391
+ const found = manifestNames.some(
392
+ (manifest) => existsSync(join(worktree, directory, manifest))
393
+ );
394
+ known.set(directory, found);
395
+ return found;
396
+ };
397
+ };
398
+ const groupDiffPaths = (isPackage, paths) => {
399
+ const groups = /* @__PURE__ */ new Map();
400
+ for (const path of paths) {
401
+ const name = packageOf(path, isPackage);
402
+ const group = groups.get(name);
403
+ if (group === void 0) groups.set(name, [path]);
404
+ else group.push(path);
405
+ }
406
+ return [...groups].toSorted(([left], [right]) => Number(left !== "") - Number(right !== "")).map(([name, groupPaths]) => ({ name, paths: groupPaths }));
407
+ };
408
+
409
+ const cEscapes = {
410
+ a: 7,
411
+ b: 8,
412
+ f: 12,
413
+ n: 10,
414
+ r: 13,
415
+ t: 9,
416
+ v: 11,
417
+ '"': 34,
418
+ "\\": 92
419
+ };
420
+ const unquoteGitPath = (quoted) => {
421
+ const bytes = [];
422
+ const encoder = new TextEncoder();
423
+ for (let index = 1; index < quoted.length - 1; index++) {
424
+ const char = quoted[index];
425
+ if (char !== "\\") {
426
+ bytes.push(...encoder.encode(char));
427
+ continue;
428
+ }
429
+ const next = quoted[index + 1] ?? "";
430
+ const octal = /^[0-7]{3}/.exec(quoted.slice(index + 1));
431
+ if (octal) {
432
+ bytes.push(Number.parseInt(octal[0], 8));
433
+ index += 3;
434
+ } else {
435
+ bytes.push(cEscapes[next] ?? next.codePointAt(0) ?? 0);
436
+ index += 1;
437
+ }
438
+ }
439
+ return new TextDecoder().decode(new Uint8Array(bytes));
440
+ };
441
+ const unquoted = (name) => name.length > 1 && name.startsWith('"') && name.endsWith('"') ? unquoteGitPath(name) : name;
442
+ const headerName = (raw, prefix) => {
443
+ const name = unquoted(raw.endsWith(" ") ? raw.slice(0, -1) : raw);
444
+ return name.startsWith(prefix) ? name.slice(prefix.length) : null;
445
+ };
446
+ const pathOfBlock = (block) => {
447
+ const added = /^\+\+\+ (.*)$/m.exec(block)?.[1];
448
+ const addedName = added === void 0 ? null : headerName(added, "b/");
449
+ if (addedName !== null) return addedName;
450
+ const removed = /^--- (.*)$/m.exec(block)?.[1];
451
+ const removedName = removed === void 0 ? null : headerName(removed, "a/");
452
+ if (removedName !== null) return removedName;
453
+ const renamed = /^rename to (.*)$/m.exec(block)?.[1];
454
+ if (renamed !== void 0) return unquoted(renamed);
455
+ const header = block.split("\n", 1)[0] ?? "";
456
+ const quotedSeparator = header.lastIndexOf(' "b/');
457
+ if (quotedSeparator !== -1) {
458
+ return headerName(header.slice(quotedSeparator + 1), "b/");
459
+ }
460
+ const separator = header.lastIndexOf(" b/");
461
+ return separator === -1 ? null : header.slice(separator + 3);
462
+ };
463
+ const diffPaths = (patch) => patch.split(/^diff --git /m).slice(1).map(pathOfBlock).filter((path) => path !== null);
464
+
465
+ const fetchCompareDiff = (transport, { ownerRepo, base, head }) => {
466
+ const [owner, name, ...rest] = ownerRepo.split("/");
467
+ if (owner === void 0 || name === void 0 || rest.length > 0) {
468
+ throw new Error(`${ownerRepo} is not owner/repo`);
469
+ }
470
+ return transport.text({
471
+ path: `repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}/compare/${base}...${head}`,
472
+ accept: "application/vnd.github.diff"
473
+ });
474
+ };
475
+
476
+ const isSha = (value) => /^[\da-f]{40}$/i.test(value);
477
+ const hasCommit = async (checkout, sha) => await gitOrNull(checkout, ["cat-file", "-e", `${sha}^{commit}`]) !== null;
478
+ const ensureCommits = async (checkout, shas, onStep, signal) => {
479
+ const missing = [];
480
+ for (const sha of shas) {
481
+ if (!await hasCommit(checkout, sha)) missing.push(sha);
482
+ }
483
+ if (missing.length === 0) return;
484
+ onStep?.("fetching");
485
+ await git(
486
+ checkout,
487
+ ["fetch", "--no-write-fetch-head", "--no-tags", "origin", ...missing],
488
+ { network: true, ...signal === void 0 ? {} : { signal } }
489
+ );
490
+ };
491
+ const readFromGit = async (checkout, { head, base, whitespace = "show", onStep, signal }) => {
492
+ await ensureCommits(checkout, [head, base], onStep, signal);
493
+ onStep?.("diffing");
494
+ const mergeBaseOutput = await git(checkout, ["merge-base", base, head]);
495
+ const mergeBase = mergeBaseOutput.trim();
496
+ const { patch, paths, omitted } = await readBranchDiff({
497
+ cwd: checkout,
498
+ mergeBase,
499
+ scope: "committed",
500
+ head,
501
+ whitespace
502
+ });
503
+ return {
504
+ source: "git",
505
+ fallbackReason: null,
506
+ whitespace,
507
+ patch,
508
+ sides: { old: mergeBase, new: head },
509
+ omitted,
510
+ groups: groupDiffPaths(await packagesAtTree(checkout, head), paths)
511
+ };
512
+ };
513
+ const readFromGitHub = async ({ checkout, transport, ref, head, base }, fallbackReason) => {
514
+ const patch = await fetchCompareDiff(transport, {
515
+ ownerRepo: ref.ownerRepo,
516
+ base,
517
+ head
518
+ });
519
+ let isPackage = noPackages;
520
+ if (checkout !== null) {
521
+ isPackage = await packagesAtTree(checkout, head).catch(() => noPackages);
522
+ }
523
+ return {
524
+ source: "github",
525
+ fallbackReason,
526
+ whitespace: "show",
527
+ patch,
528
+ sides: null,
529
+ omitted: [],
530
+ groups: groupDiffPaths(isPackage, diffPaths(patch))
531
+ };
532
+ };
533
+ const readPullRequestDiff = async (request) => {
534
+ if (!isSha(request.head) || !isSha(request.base)) {
535
+ throw new Error("head and base must be commit shas");
536
+ }
537
+ if (request.checkout === null) return readFromGitHub(request, "no checkout");
538
+ try {
539
+ return await readFromGit(request.checkout, request);
540
+ } catch (error) {
541
+ if (request.signal?.aborted) throw error;
542
+ const reason = error instanceof Error ? error.message : String(error);
543
+ return readFromGitHub(request, reason);
544
+ }
545
+ };
546
+ const readFileAtSha = async (checkout, sha, path) => {
547
+ if (!isSha(sha)) throw new Error("sha must be a commit sha");
548
+ if (path === "") throw new Error("path is required");
549
+ return git(checkout, ["cat-file", "blob", `${sha}:${path}`], {
550
+ maxBuffer: 64 * 1024 * 1024
551
+ });
552
+ };
553
+
554
+ const typescriptExtensions = [".ts", ".tsx", ".mts", ".cts"];
555
+ const extensionOf = (path) => {
556
+ const slash = path.lastIndexOf("/");
557
+ const dot = path.lastIndexOf(".");
558
+ return dot > slash ? path.slice(dot) : "";
559
+ };
560
+ const isTypescriptPath = (path) => typescriptExtensions.includes(extensionOf(path));
561
+ const attachableSuffix = /\.(?:test|spec|stories)(?=\.[cm]?tsx?$)/;
562
+ const testDirectories = /* @__PURE__ */ new Set([
563
+ "__fixtures__",
564
+ "__mocks__",
565
+ "__tests__",
566
+ "fixtures",
567
+ "test",
568
+ "tests"
569
+ ]);
570
+ const isAttachablePath = (path) => isTypescriptPath(path) && (attachableSuffix.test(path) || path.split("/").slice(0, -1).some((directory) => testDirectories.has(directory)));
571
+ const javascriptToTypescript = {
572
+ ".js": ".ts",
573
+ ".jsx": ".tsx",
574
+ ".mjs": ".mts",
575
+ ".cjs": ".cts"
576
+ };
577
+ const directoryOf = (path) => {
578
+ const slash = path.lastIndexOf("/");
579
+ return slash === -1 ? "" : path.slice(0, slash);
580
+ };
581
+ const normalize = (path) => {
582
+ const segments = [];
583
+ for (const segment of path.split("/")) {
584
+ if (segment === "" || segment === ".") continue;
585
+ if (segment === "..") {
586
+ if (segments.length === 0) return null;
587
+ segments.pop();
588
+ } else {
589
+ segments.push(segment);
590
+ }
591
+ }
592
+ return segments.join("/");
593
+ };
594
+ const candidatesOf = (target) => {
595
+ const extension = extensionOf(target);
596
+ const rewritten = javascriptToTypescript[extension];
597
+ const declaration = rewritten === void 0 ? `${target}.d.ts` : `${target.slice(0, -extension.length)}.d.ts`;
598
+ return [
599
+ target,
600
+ ...rewritten === void 0 ? [] : [target.slice(0, -extension.length) + rewritten],
601
+ ...typescriptExtensions.map((typescript) => target + typescript),
602
+ declaration,
603
+ `${target}/index.ts`,
604
+ `${target}/index.tsx`
605
+ ];
606
+ };
607
+ const resolveTarget = (target, exists) => target === null ? null : candidatesOf(target).find(
608
+ (candidate) => isTypescriptPath(candidate) && exists(candidate)
609
+ ) ?? null;
610
+ const resolveRelative = (from, specifier, exists) => {
611
+ if (!specifier.startsWith(".")) return null;
612
+ return resolveTarget(normalize(`${directoryOf(from)}/${specifier}`), exists);
613
+ };
614
+ const conditionOrder = ["types", "import", "default"];
615
+ const targetOf = (value) => {
616
+ if (typeof value === "string") return value;
617
+ if (typeof value !== "object" || value === null) return null;
618
+ const conditions = value;
619
+ for (const condition of conditionOrder) {
620
+ const target = targetOf(conditions[condition]);
621
+ if (target !== null) return target;
622
+ }
623
+ return null;
624
+ };
625
+ const expand = (pattern, specifier) => {
626
+ const star = pattern.indexOf("*");
627
+ if (star === -1) return pattern === specifier ? "" : null;
628
+ const prefix = pattern.slice(0, star);
629
+ const suffix = pattern.slice(star + 1);
630
+ if (!specifier.startsWith(prefix) || !specifier.endsWith(suffix)) return null;
631
+ return specifier.slice(prefix.length, specifier.length - suffix.length);
632
+ };
633
+ const specificity = (pattern) => pattern.includes("*") ? pattern.indexOf("*") : Number.MAX_SAFE_INTEGER;
634
+ const resolveSubpathImport = (specifier, { directory, imports }, exists) => {
635
+ if (!specifier.startsWith("#")) return null;
636
+ if (typeof imports !== "object" || imports === null) return null;
637
+ const patterns = Object.entries(imports).toSorted(
638
+ ([left], [right]) => specificity(right) - specificity(left)
639
+ );
640
+ for (const [pattern, value] of patterns) {
641
+ const captured = expand(pattern, specifier);
642
+ const target = targetOf(value);
643
+ if (captured === null || target?.startsWith("./") !== true) continue;
644
+ return resolveTarget(
645
+ normalize(`${directory}/${target.replace("*", captured)}`),
646
+ exists
647
+ );
648
+ }
649
+ return null;
650
+ };
651
+ const joinPath = (directory, name) => directory === "" ? name : `${directory}/${name}`;
652
+ const coveredDirectoriesOf = (path) => {
653
+ const directory = directoryOf(path);
654
+ return directory.split("/").at(-1) === "__tests__" ? [directory, directoryOf(directory)] : [directory];
655
+ };
656
+ const attachableKinds = /* @__PURE__ */ new Set(["spec", "stories", "test"]);
657
+ const subjectsOf = (path) => {
658
+ const parts = path.slice(directoryOf(path).length).replace(/^\//, "").replace(/\.[cm]?tsx?$/, "").split(".");
659
+ const kind = parts.findIndex((part) => attachableKinds.has(part));
660
+ const stem = kind === -1 ? parts : parts.slice(0, kind);
661
+ const names = stem.map(
662
+ (_, dropped) => stem.slice(0, stem.length - dropped).join(".")
663
+ );
664
+ return coveredDirectoriesOf(path).flatMap(
665
+ (directory) => names.flatMap(
666
+ (name) => typescriptExtensions.map(
667
+ (extension) => joinPath(directory, name + extension)
668
+ )
669
+ )
670
+ );
671
+ };
672
+ const reachedFrom = (path, { code, changed, imports }) => {
673
+ const reached = [];
674
+ const visited = /* @__PURE__ */ new Set([path]);
675
+ const visit = (from) => {
676
+ for (const target of imports.get(from) ?? []) {
677
+ if (visited.has(target)) continue;
678
+ visited.add(target);
679
+ if (code.has(target)) reached.push(target);
680
+ else if (!changed.has(target)) visit(target);
681
+ }
682
+ };
683
+ visit(path);
684
+ return reached;
685
+ };
686
+ const countReachable = (entry, edges) => {
687
+ const seen = /* @__PURE__ */ new Set([entry]);
688
+ const stack = [entry];
689
+ while (stack.length > 0) {
690
+ for (const target of edges.get(stack.pop()) ?? []) {
691
+ if (seen.has(target)) continue;
692
+ seen.add(target);
693
+ stack.push(target);
694
+ }
695
+ }
696
+ return seen.size - 1;
697
+ };
698
+ const attachTo = (attachable, code, reached) => {
699
+ const covered = coveredDirectoriesOf(attachable);
700
+ return subjectsOf(attachable).find((subject) => code.has(subject)) ?? reached.find((target) => covered.includes(directoryOf(target))) ?? reached[0] ?? null;
701
+ };
702
+ const orderGroup = ({ name, paths }, changed, imports) => {
703
+ const code = new Set(
704
+ paths.filter((path) => isTypescriptPath(path) && !isAttachablePath(path))
705
+ );
706
+ const attachables = paths.filter(isAttachablePath);
707
+ const others = paths.filter((path) => !isTypescriptPath(path));
708
+ const reach = { code, changed, imports };
709
+ const edges = new Map(
710
+ [...code].map((path) => [path, reachedFrom(path, reach)])
711
+ );
712
+ const imported = new Set([...edges.values()].flat());
713
+ const attached = /* @__PURE__ */ new Map();
714
+ const standalone = [];
715
+ for (const attachable of attachables) {
716
+ const subject = attachTo(attachable, code, reachedFrom(attachable, reach));
717
+ if (subject === null) standalone.push(attachable);
718
+ else attached.set(subject, [...attached.get(subject) ?? [], attachable]);
719
+ }
720
+ const reachable = new Map(
721
+ [...code].map((path) => [path, countReachable(path, edges)])
722
+ );
723
+ const byPath = (left, right) => left.localeCompare(right);
724
+ const roots = [...code].filter((path) => !imported.has(path)).toSorted(
725
+ (left, right) => reachable.get(right) - reachable.get(left) || byPath(left, right)
726
+ );
727
+ const ordered = [];
728
+ const visited = /* @__PURE__ */ new Set();
729
+ const visit = (path) => {
730
+ if (visited.has(path)) return;
731
+ visited.add(path);
732
+ ordered.push(path);
733
+ for (const target of edges.get(path) ?? []) visit(target);
734
+ };
735
+ for (const root of roots) visit(root);
736
+ for (const path of [...code].toSorted(byPath)) visit(path);
737
+ const entries = new Set(
738
+ roots.filter((root) => (edges.get(root) ?? []).length > 0)
739
+ );
740
+ const rootPaths = new Set(roots);
741
+ const codeFile = (path) => ({
742
+ path,
743
+ entry: entries.has(path),
744
+ root: rootPaths.has(path),
745
+ attached: (attached.get(path) ?? []).toSorted(byPath)
746
+ });
747
+ const plainFile = (path) => ({
748
+ path,
749
+ entry: false,
750
+ root: false,
751
+ attached: []
752
+ });
753
+ return {
754
+ name,
755
+ files: [
756
+ ...ordered.map(codeFile),
757
+ ...others.map(plainFile),
758
+ ...standalone.map(plainFile)
759
+ ]
760
+ };
761
+ };
762
+ const transitiveDependencies = (name, dependsOn) => {
763
+ const found = /* @__PURE__ */ new Set();
764
+ const stack = [name];
765
+ while (stack.length > 0) {
766
+ for (const dependency of dependsOn.get(stack.pop()) ?? []) {
767
+ if (found.has(dependency) || dependency === name) continue;
768
+ found.add(dependency);
769
+ stack.push(dependency);
770
+ }
771
+ }
772
+ return found;
773
+ };
774
+ const orderPackages = (names, dependsOn) => {
775
+ const remaining = new Set(names.filter((name) => name !== ""));
776
+ const dependencies = new Map(
777
+ [...remaining].map((name) => [
778
+ name,
779
+ transitiveDependencies(name, dependsOn)
780
+ ])
781
+ );
782
+ const isDependedOnByRemaining = (name) => [...remaining].some(
783
+ (other) => other !== name && dependencies.get(other).has(name)
784
+ );
785
+ const ordered = names.includes("") ? [""] : [];
786
+ while (remaining.size > 0) {
787
+ const sorted = [...remaining].toSorted(
788
+ (left, right) => left.localeCompare(right)
789
+ );
790
+ const next = sorted.find((name) => !isDependedOnByRemaining(name)) ?? sorted[0];
791
+ ordered.push(next);
792
+ remaining.delete(next);
793
+ }
794
+ return ordered;
795
+ };
796
+ const orderForReading = ({
797
+ groups,
798
+ imports,
799
+ dependsOn
800
+ }) => {
801
+ const byName = new Map(groups.map((group) => [group.name, group]));
802
+ const changed = new Set(groups.flatMap(({ paths }) => paths));
803
+ return orderPackages(
804
+ groups.map(({ name }) => name),
805
+ dependsOn
806
+ ).map((name) => orderGroup(byName.get(name), changed, imports));
807
+ };
808
+
809
+ const manifestDirectoryOf = (path, manifests) => {
810
+ const directories = path.split("/").slice(0, -1);
811
+ for (let depth = directories.length; depth >= 0; depth--) {
812
+ const directory = directories.slice(0, depth).join("/");
813
+ if (manifests.has(directory)) return directory;
814
+ }
815
+ return null;
816
+ };
817
+ const resolveLocalSpecifier = ({
818
+ from,
819
+ specifier,
820
+ exists,
821
+ manifests
822
+ }) => {
823
+ if (!specifier.startsWith("#")) {
824
+ return resolveRelative(from, specifier, exists);
825
+ }
826
+ const directory = manifestDirectoryOf(from, manifests);
827
+ if (directory === null) return null;
828
+ return resolveSubpathImport(
829
+ specifier,
830
+ { directory, imports: manifests.get(directory)?.imports },
831
+ exists
832
+ );
833
+ };
834
+ const dependencyFields = [
835
+ "dependencies",
836
+ "devDependencies",
837
+ "peerDependencies"
838
+ ];
839
+ const dependencyNamesOf = (manifest) => dependencyFields.flatMap((field) => {
840
+ const dependencies = manifest[field];
841
+ return typeof dependencies === "object" && dependencies !== null ? Object.keys(dependencies) : [];
842
+ });
843
+ const parseManifest = (source) => {
844
+ try {
845
+ const parsed = JSON.parse(source);
846
+ return typeof parsed === "object" && parsed !== null ? parsed : null;
847
+ } catch {
848
+ return null;
849
+ }
850
+ };
851
+ const directoryOfManifest = (path) => path.includes("/") ? path.slice(0, path.lastIndexOf("/")) : "";
852
+ const readManifests = async (checkout, head, tree) => {
853
+ const manifestPaths = tree.filter(
854
+ (path) => (path === "package.json" || path.endsWith("/package.json")) && !path.split("/").includes("node_modules")
855
+ );
856
+ const contents = await catFiles(checkout, head, manifestPaths);
857
+ const manifests = /* @__PURE__ */ new Map();
858
+ for (const [path, source] of contents) {
859
+ const manifest = parseManifest(source);
860
+ if (manifest !== null) manifests.set(directoryOfManifest(path), manifest);
861
+ }
862
+ return manifests;
863
+ };
864
+
865
+ const maxUnchangedHops = 2;
866
+ const stringLiteral$1 = /^(["'`])(.*)\1$/s;
867
+ const importSpecifiers = (path, source) => {
868
+ const { staticImports, staticExports, dynamicImports } = parseSync(
869
+ path,
870
+ source
871
+ ).module;
872
+ const found = [
873
+ ...staticImports.map(({ start, moduleRequest }) => ({
874
+ start,
875
+ specifier: moduleRequest.value
876
+ })),
877
+ ...staticExports.flatMap(
878
+ ({ start, entries }) => entries.flatMap(
879
+ ({ moduleRequest }) => moduleRequest === null ? [] : [{ start, specifier: moduleRequest.value }]
880
+ )
881
+ ),
882
+ ...dynamicImports.flatMap(({ start, moduleRequest }) => {
883
+ const literal = stringLiteral$1.exec(
884
+ source.slice(moduleRequest.start, moduleRequest.end)
885
+ );
886
+ const specifier = literal?.[2];
887
+ return specifier === void 0 || specifier.includes("${") ? [] : [{ start, specifier }];
888
+ })
889
+ ];
890
+ return [
891
+ ...new Set(
892
+ found.toSorted((left, right) => left.start - right.start).map(({ specifier }) => specifier)
893
+ )
894
+ ];
895
+ };
896
+ const readImports = async ({
897
+ checkout,
898
+ head,
899
+ changed,
900
+ exists,
901
+ isPackage,
902
+ manifests
903
+ }) => {
904
+ const changedSet = new Set(changed);
905
+ const imports = /* @__PURE__ */ new Map();
906
+ let toRead = changed.filter((path) => isTypescriptPath(path) && exists(path));
907
+ for (let hop = 0; hop <= maxUnchangedHops && toRead.length > 0; hop++) {
908
+ const contents = await catFiles(checkout, head, toRead);
909
+ const next = /* @__PURE__ */ new Set();
910
+ for (const path of toRead) {
911
+ const source = contents.get(path);
912
+ const resolved = (source === void 0 ? [] : importSpecifiers(path, source)).map(
913
+ (specifier) => resolveLocalSpecifier({ from: path, specifier, exists, manifests })
914
+ ).filter((target) => target !== null);
915
+ imports.set(path, resolved);
916
+ const ownPackage = packageOf(path, isPackage);
917
+ for (const target of resolved) {
918
+ if (changedSet.has(target) || imports.has(target)) continue;
919
+ if (packageOf(target, isPackage) === ownPackage) next.add(target);
920
+ }
921
+ }
922
+ toRead = [...next];
923
+ }
924
+ return imports;
925
+ };
926
+ const packageDependencies = (manifests) => {
927
+ const directoryOfName = /* @__PURE__ */ new Map();
928
+ for (const [directory, { name }] of manifests) {
929
+ if (typeof name === "string") directoryOfName.set(name, directory);
930
+ }
931
+ return new Map(
932
+ [...manifests].map(([directory, manifest]) => [
933
+ directory,
934
+ new Set(
935
+ dependencyNamesOf(manifest).flatMap((name) => {
936
+ const dependency = directoryOfName.get(name);
937
+ return dependency === void 0 || dependency === directory ? [] : [dependency];
938
+ })
939
+ )
940
+ ])
941
+ );
942
+ };
943
+ const readReadingOrder = async ({
944
+ checkout,
945
+ head,
946
+ base
947
+ }) => {
948
+ if (!isSha(head) || !isSha(base)) {
949
+ throw new Error("head and base must be commit shas");
950
+ }
951
+ await ensureCommits(checkout, [head, base], void 0);
952
+ const mergeBaseOutput = await git(checkout, ["merge-base", base, head]);
953
+ const mergeBase = mergeBaseOutput.trim();
954
+ const numstat = await readNumstat(checkout, [mergeBase, head], []);
955
+ const changed = numstat.map(({ path }) => path);
956
+ const tree = await listTree(checkout, head);
957
+ const inTree = new Set(tree);
958
+ const exists = (path) => inTree.has(path);
959
+ const isPackage = packagesInListing(tree);
960
+ const manifests = await readManifests(checkout, head, tree);
961
+ const imports = await readImports({
962
+ checkout,
963
+ head,
964
+ changed,
965
+ exists,
966
+ isPackage,
967
+ manifests
968
+ });
969
+ return {
970
+ groups: orderForReading({
971
+ groups: groupDiffPaths(isPackage, changed),
972
+ imports,
973
+ dependsOn: packageDependencies(manifests)
974
+ })
975
+ };
976
+ };
977
+
978
+ const stringLiteral = /^(["'`])(.*)\1$/s;
979
+ const lineStartsOf = (source) => {
980
+ const starts = [0];
981
+ for (let index = source.indexOf("\n"); index !== -1; ) {
982
+ starts.push(index + 1);
983
+ index = source.indexOf("\n", index + 1);
984
+ }
985
+ return starts;
986
+ };
987
+ const lineOf = (starts, offset) => {
988
+ let low = 0;
989
+ let high = starts.length - 1;
990
+ while (low < high) {
991
+ const middle = Math.ceil((low + high) / 2);
992
+ if (starts[middle] <= offset) low = middle;
993
+ else high = middle - 1;
994
+ }
995
+ return low + 1;
996
+ };
997
+ const rangeOf = (starts, { start, end }) => ({
998
+ start: lineOf(starts, start),
999
+ end: lineOf(starts, Math.max(start, end - 1))
1000
+ });
1001
+ const overlaps = (left, right) => left.start <= right.end && right.start <= left.end;
1002
+ const kindOf = ({ kind }) => String(kind);
1003
+ const nameOf = (entry) => kindOf(entry) === "Default" ? "default" : entry.name;
1004
+ const parse = (path, source) => ({
1005
+ result: parseSync(path, source),
1006
+ starts: lineStartsOf(source)
1007
+ });
1008
+ const importedNames = ({ result }) => {
1009
+ const names = /* @__PURE__ */ new Map();
1010
+ for (const { moduleRequest, entries } of result.module.staticImports) {
1011
+ for (const { importName, localName } of entries) {
1012
+ names.set(localName.value, {
1013
+ specifier: moduleRequest.value,
1014
+ imported: kindOf(importName) === "NamespaceObject" ? "*" : nameOf(importName) ?? "*"
1015
+ });
1016
+ }
1017
+ }
1018
+ return names;
1019
+ };
1020
+ const matchImport = ({ result }, { importName, moduleRequest }) => {
1021
+ const name = nameOf(importName) ?? "*";
1022
+ const statement = result.module.staticImports.find(
1023
+ ({ moduleRequest: request }) => request.start === moduleRequest?.start
1024
+ );
1025
+ const entries = statement?.entries ?? [];
1026
+ const asDefault = entries.find(
1027
+ (entry) => kindOf(entry.importName) === "Default" && entry.localName.value === name
1028
+ );
1029
+ if (asDefault !== void 0) {
1030
+ return { name: "default", local: asDefault.localName.value };
1031
+ }
1032
+ const named = entries.find((entry) => entry.importName.name === name);
1033
+ return { name, local: named?.localName.value ?? null };
1034
+ };
1035
+ const isNode = (value) => typeof value === "object" && value !== null && typeof value.type === "string";
1036
+ const isExportList = (node) => node.type === "ExportNamedDeclaration" && node.declaration === null;
1037
+ const labelKeys = {
1038
+ MemberExpression: "property",
1039
+ Property: "key",
1040
+ PropertyDefinition: "key",
1041
+ MethodDefinition: "key",
1042
+ TSPropertySignature: "key",
1043
+ TSMethodSignature: "key",
1044
+ TSQualifiedName: "right",
1045
+ JSXMemberExpression: "property",
1046
+ JSXAttribute: "name"
1047
+ };
1048
+ const collectNames = (root, names) => {
1049
+ const stack = [root];
1050
+ while (stack.length > 0) {
1051
+ const value = stack.pop();
1052
+ if (Array.isArray(value)) {
1053
+ stack.push(...value);
1054
+ continue;
1055
+ }
1056
+ if (!isNode(value)) continue;
1057
+ if (value.type === "Identifier" || value.type === "JSXIdentifier") {
1058
+ if (typeof value.name === "string") names.add(value.name);
1059
+ stack.push(value.typeAnnotation);
1060
+ continue;
1061
+ }
1062
+ const labelKey = labelKeys[value.type];
1063
+ for (const [key, child] of Object.entries(value)) {
1064
+ if (key === labelKey && value.computed !== true) continue;
1065
+ if (typeof child === "object" && child !== null) stack.push(child);
1066
+ }
1067
+ }
1068
+ };
1069
+ const namesUsedLocally = ({ result }) => {
1070
+ const names = /* @__PURE__ */ new Set();
1071
+ for (const node of result.program.body) {
1072
+ const isListing = node.type === "ImportDeclaration" || node.type === "ExportAllDeclaration" || isExportList(node);
1073
+ if (!isListing) collectNames(node, names);
1074
+ }
1075
+ return names;
1076
+ };
1077
+ const readBindings = (parsed) => {
1078
+ const { result, starts } = parsed;
1079
+ const imported = importedNames(parsed);
1080
+ const bindings = [];
1081
+ const reExports = [];
1082
+ const passedOn = /* @__PURE__ */ new Set();
1083
+ for (const { start, end, entries } of result.module.staticExports) {
1084
+ for (const entry of entries) {
1085
+ const exported = nameOf(entry.exportName);
1086
+ const inStatement = entry.exportName.start === null || entry.exportName.start >= start && entry.exportName.start < end;
1087
+ const isListed = !inStatement || entries.length > 1 && entry.end < end;
1088
+ const lines = rangeOf(
1089
+ starts,
1090
+ isListed ? { start: entry.start, end: entry.end } : { start, end }
1091
+ );
1092
+ const importKind = kindOf(entry.importName);
1093
+ if (entry.moduleRequest !== null && importKind !== "None") {
1094
+ const { value: specifier } = entry.moduleRequest;
1095
+ if (importKind === "AllButDefault") {
1096
+ reExports.push({ specifier, local: "*", exported: "*", lines });
1097
+ } else if (exported !== null) {
1098
+ const matched = matchImport(parsed, entry);
1099
+ if (matched.local !== null && !inStatement) {
1100
+ passedOn.add(matched.local);
1101
+ }
1102
+ reExports.push({
1103
+ specifier,
1104
+ local: importKind === "All" ? "*" : matched.name,
1105
+ exported,
1106
+ lines
1107
+ });
1108
+ }
1109
+ continue;
1110
+ }
1111
+ if (exported === null) continue;
1112
+ const local = entry.localName.name;
1113
+ const viaImport = local === null ? void 0 : imported.get(local);
1114
+ if (viaImport === void 0) {
1115
+ bindings.push({ name: exported, local, lines });
1116
+ } else {
1117
+ passedOn.add(local);
1118
+ reExports.push({
1119
+ specifier: viaImport.specifier,
1120
+ local: viaImport.imported,
1121
+ exported,
1122
+ lines
1123
+ });
1124
+ }
1125
+ }
1126
+ }
1127
+ return { bindings, reExports, passedOn };
1128
+ };
1129
+ const exportedBindings = (path, source) => {
1130
+ const { bindings, reExports } = readBindings(parse(path, source));
1131
+ return { bindings, reExports };
1132
+ };
1133
+ const starSpecifiers = (reExports) => reExports.filter(({ local, exported }) => local === "*" && exported === "*").map(({ specifier }) => specifier);
1134
+ const exportedNames = ({
1135
+ bindings,
1136
+ reExports
1137
+ }) => [
1138
+ .../* @__PURE__ */ new Set([
1139
+ ...bindings.map(({ name }) => name),
1140
+ ...reExports.flatMap(
1141
+ ({ exported }) => exported === "*" ? [] : [exported]
1142
+ )
1143
+ ])
1144
+ ];
1145
+ const importsOf = (parsed, source, passedOn) => {
1146
+ const { result } = parsed;
1147
+ const { staticImports, dynamicImports } = result.module;
1148
+ const usedLocally = namesUsedLocally(parsed);
1149
+ const found = [
1150
+ ...staticImports.flatMap(({ start, moduleRequest, entries }) => {
1151
+ const used = entries.filter(
1152
+ ({ localName }) => !passedOn.has(localName.value) || usedLocally.has(localName.value)
1153
+ );
1154
+ if (entries.length > 0 && used.length === 0) return [];
1155
+ const names = entries.length === 0 ? ["*"] : used.map(
1156
+ ({ importName }) => kindOf(importName) === "NamespaceObject" ? "*" : nameOf(importName) ?? "*"
1157
+ );
1158
+ return [{ start, specifier: moduleRequest.value, names }];
1159
+ }),
1160
+ ...dynamicImports.flatMap(({ start, moduleRequest }) => {
1161
+ const literal = stringLiteral.exec(
1162
+ source.slice(moduleRequest.start, moduleRequest.end)
1163
+ );
1164
+ const specifier = literal?.[2];
1165
+ return specifier === void 0 || specifier.includes("${") ? [] : [{ start, specifier, names: ["*"] }];
1166
+ })
1167
+ ];
1168
+ const bySpecifier = /* @__PURE__ */ new Map();
1169
+ for (const { specifier, names } of found.toSorted(
1170
+ (left, right) => left.start - right.start
1171
+ )) {
1172
+ const merged = bySpecifier.get(specifier) ?? /* @__PURE__ */ new Set();
1173
+ for (const name of names) merged.add(name);
1174
+ bySpecifier.set(specifier, merged);
1175
+ }
1176
+ return [...bySpecifier].map(([specifier, names]) => ({
1177
+ specifier,
1178
+ names: [...names]
1179
+ }));
1180
+ };
1181
+ const moduleBindings = (path, source) => {
1182
+ const parsed = parse(path, source);
1183
+ const { bindings, reExports, passedOn } = readBindings(parsed);
1184
+ return { bindings, reExports, imports: importsOf(parsed, source, passedOn) };
1185
+ };
1186
+ const patternNames = (pattern, names) => {
1187
+ if (!isNode(pattern)) return;
1188
+ switch (pattern.type) {
1189
+ case "Identifier":
1190
+ names.push(String(pattern.name));
1191
+ break;
1192
+ case "ObjectPattern":
1193
+ for (const property of pattern.properties) {
1194
+ if (isNode(property)) {
1195
+ patternNames(property.value ?? property.argument, names);
1196
+ }
1197
+ }
1198
+ break;
1199
+ case "ArrayPattern":
1200
+ for (const element of pattern.elements) {
1201
+ patternNames(element, names);
1202
+ }
1203
+ break;
1204
+ case "AssignmentPattern":
1205
+ patternNames(pattern.left, names);
1206
+ break;
1207
+ case "RestElement":
1208
+ patternNames(pattern.argument, names);
1209
+ break;
1210
+ }
1211
+ };
1212
+ const declaredNamesOf = (statement) => {
1213
+ const names = [];
1214
+ if (statement.type === "VariableDeclaration") {
1215
+ for (const declarator of statement.declarations) {
1216
+ if (isNode(declarator)) patternNames(declarator.id, names);
1217
+ }
1218
+ } else if (statement.type === "FunctionDeclaration" || statement.type === "ClassDeclaration" || statement.type.startsWith("TS")) {
1219
+ patternNames(statement.id, names);
1220
+ }
1221
+ return names;
1222
+ };
1223
+ const statementsOf = ({ result, starts }) => result.program.body.map((node) => {
1224
+ const mentioned = /* @__PURE__ */ new Set();
1225
+ collectNames(node, mentioned);
1226
+ return {
1227
+ lines: rangeOf(starts, node),
1228
+ isList: isExportList(node),
1229
+ declared: declaredNamesOf(node),
1230
+ mentioned
1231
+ };
1232
+ });
1233
+ const touchedExports = ({
1234
+ path,
1235
+ source,
1236
+ touchedLines,
1237
+ baseExports,
1238
+ baseStars = []
1239
+ }) => {
1240
+ const parsed = parse(path, source);
1241
+ const { bindings, reExports } = readBindings(parsed);
1242
+ const statements = statementsOf(parsed);
1243
+ const isTouched = (lines) => touchedLines.some((touched2) => overlaps(touched2, lines));
1244
+ const exportedLocals = new Set(
1245
+ bindings.flatMap(({ local }) => local === null ? [] : [local])
1246
+ );
1247
+ const helpers = statements.filter(
1248
+ ({ declared }) => declared.length > 0 && !declared.some((name) => exportedLocals.has(name))
1249
+ );
1250
+ const touchedHelperNames = new Set(
1251
+ helpers.filter(({ lines }) => isTouched(lines)).flatMap(({ declared }) => declared)
1252
+ );
1253
+ for (let grown = true; grown; ) {
1254
+ grown = false;
1255
+ for (const { declared, mentioned } of helpers) {
1256
+ if (declared.every((name) => touchedHelperNames.has(name))) continue;
1257
+ if (![...touchedHelperNames].some((name) => mentioned.has(name))) {
1258
+ continue;
1259
+ }
1260
+ for (const name of declared) touchedHelperNames.add(name);
1261
+ grown = true;
1262
+ }
1263
+ }
1264
+ const isBindingTouched = ({ local, lines }) => {
1265
+ const declaring = [
1266
+ ...statements.filter(
1267
+ (statement) => !statement.isList && overlaps(statement.lines, lines)
1268
+ ),
1269
+ ...local === null ? [] : statements.filter(({ declared }) => declared.includes(local))
1270
+ ];
1271
+ return isTouched(lines) || declaring.some(
1272
+ ({ lines: declaringLines, mentioned }) => isTouched(declaringLines) || [...touchedHelperNames].some((name) => mentioned.has(name))
1273
+ );
1274
+ };
1275
+ const touched = /* @__PURE__ */ new Set([
1276
+ ...bindings.filter(isBindingTouched).map(({ name }) => name),
1277
+ ...reExports.filter(({ exported, lines }) => exported !== "*" && isTouched(lines)).map(({ exported }) => exported)
1278
+ ]);
1279
+ const now = new Set(exportedNames({ bindings, reExports }));
1280
+ const stars = starSpecifiers(reExports);
1281
+ return {
1282
+ touched: [...touched],
1283
+ removed: baseExports.filter((name) => !now.has(name)),
1284
+ touchedStars: starSpecifiers(
1285
+ reExports.filter(({ lines }) => isTouched(lines))
1286
+ ),
1287
+ removedStars: baseStars.filter((specifier) => !stars.includes(specifier))
1288
+ };
1289
+ };
1290
+
1291
+ const maxTypescriptFiles = 2e4;
1292
+ const maxReadMs = 15e3;
1293
+ const batchSize = 250;
1294
+ const diffChunkSize = 200;
1295
+ const maxTreeBuffer = 256 * 1024 * 1024;
1296
+ const defaultCacheEntries = 2 * maxTypescriptFiles;
1297
+ const createBindingsCache = (maxEntries = defaultCacheEntries) => {
1298
+ const entries = /* @__PURE__ */ new Map();
1299
+ return {
1300
+ get: (oid) => {
1301
+ const summary = entries.get(oid);
1302
+ if (summary === void 0) return void 0;
1303
+ entries.delete(oid);
1304
+ entries.set(oid, summary);
1305
+ return summary;
1306
+ },
1307
+ set: (oid, summary) => {
1308
+ entries.delete(oid);
1309
+ entries.set(oid, summary);
1310
+ for (const oldest of entries.keys()) {
1311
+ if (entries.size <= maxEntries) break;
1312
+ entries.delete(oldest);
1313
+ }
1314
+ }
1315
+ };
1316
+ };
1317
+ const listBlobs = async (checkout, sha) => {
1318
+ const listing = await git(checkout, ["ls-tree", "-r", "-z", sha], {
1319
+ maxBuffer: maxTreeBuffer
1320
+ });
1321
+ return listing.split("\0").flatMap((record) => {
1322
+ const tab = record.indexOf(" ");
1323
+ if (tab === -1) return [];
1324
+ const [, type, oid] = record.slice(0, tab).split(" ");
1325
+ return type === "blob" && oid !== void 0 ? [{ path: record.slice(tab + 1), oid }] : [];
1326
+ });
1327
+ };
1328
+ const isInNodeModules = (path) => path.split("/").includes("node_modules");
1329
+ const emptySummary = { exports: [], imports: [], reExports: [] };
1330
+ const summarize = (path, source) => {
1331
+ const { bindings, reExports, imports } = moduleBindings(path, source);
1332
+ return {
1333
+ exports: exportedNames({ bindings, reExports }),
1334
+ imports,
1335
+ reExports
1336
+ };
1337
+ };
1338
+ const chunksOf = (items, size) => Array.from(
1339
+ { length: Math.ceil(items.length / size) },
1340
+ (_, index) => items.slice(index * size, (index + 1) * size)
1341
+ );
1342
+ const readSummaries = async ({
1343
+ checkout,
1344
+ head,
1345
+ blobs,
1346
+ cache,
1347
+ budgetMs
1348
+ }) => {
1349
+ const summaries = /* @__PURE__ */ new Map();
1350
+ const missing = /* @__PURE__ */ new Map();
1351
+ for (const { oid, path } of blobs) {
1352
+ const cached = cache.get(oid);
1353
+ if (cached === void 0) missing.set(oid, path);
1354
+ else summaries.set(oid, cached);
1355
+ }
1356
+ const deadline = Date.now() + budgetMs;
1357
+ for (const batch of chunksOf([...missing], batchSize)) {
1358
+ if (Date.now() > deadline) return null;
1359
+ const contents = await catFiles(
1360
+ checkout,
1361
+ head,
1362
+ batch.map(([, path]) => path)
1363
+ );
1364
+ for (const [oid, path] of batch) {
1365
+ const source = contents.get(path);
1366
+ const summary = source === void 0 ? emptySummary : summarize(path, source);
1367
+ summaries.set(oid, summary);
1368
+ cache.set(oid, summary);
1369
+ }
1370
+ await setImmediate();
1371
+ }
1372
+ return summaries;
1373
+ };
1374
+ const hunkHeader = /^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@/;
1375
+ const parseTouchedLines = (diff) => {
1376
+ const touched = /* @__PURE__ */ new Map();
1377
+ let current = null;
1378
+ for (const line of diff.split("\n")) {
1379
+ if (line.startsWith("diff --git ")) {
1380
+ current = null;
1381
+ } else if (line.startsWith("+++ ")) {
1382
+ const path = line.slice(4).split(" ")[0];
1383
+ if (path.startsWith("b/")) {
1384
+ current = touched.get(path.slice(2)) ?? [];
1385
+ touched.set(path.slice(2), current);
1386
+ }
1387
+ } else if (line.startsWith("@@")) {
1388
+ const match = hunkHeader.exec(line);
1389
+ if (current === null || match === null) continue;
1390
+ const start = Number(match[1]);
1391
+ const count = match[2] === void 0 ? 1 : Number(match[2]);
1392
+ current.push(
1393
+ count === 0 ? { start: start + 1, end: start + 1 } : { start, end: start + count - 1 }
1394
+ );
1395
+ }
1396
+ }
1397
+ return touched;
1398
+ };
1399
+ const readTouchedLines = async ({
1400
+ checkout,
1401
+ mergeBase,
1402
+ head,
1403
+ entries
1404
+ }) => {
1405
+ const touched = /* @__PURE__ */ new Map();
1406
+ for (const chunk of chunksOf(entries, diffChunkSize)) {
1407
+ const diff = await git(
1408
+ checkout,
1409
+ [
1410
+ "-c",
1411
+ "core.quotePath=false",
1412
+ "diff",
1413
+ "-U0",
1414
+ "-M",
1415
+ "--no-color",
1416
+ "--no-ext-diff",
1417
+ "--no-textconv",
1418
+ "--no-relative",
1419
+ "--src-prefix=a/",
1420
+ "--dst-prefix=b/",
1421
+ mergeBase,
1422
+ head,
1423
+ "--",
1424
+ ...chunk.flatMap(
1425
+ ({ paths }) => paths.map((path) => `:(literal)${path}`)
1426
+ )
1427
+ ],
1428
+ { maxBuffer: maxTreeBuffer }
1429
+ );
1430
+ for (const [path, ranges] of parseTouchedLines(diff)) {
1431
+ touched.set(path, ranges);
1432
+ }
1433
+ }
1434
+ return touched;
1435
+ };
1436
+ const splitPackageSpecifier = (specifier) => {
1437
+ const segments = specifier.split("/");
1438
+ const nameLength = specifier.startsWith("@") ? 2 : 1;
1439
+ const rest = segments.slice(nameLength).join("/");
1440
+ return {
1441
+ name: segments.slice(0, nameLength).join("/"),
1442
+ subpath: rest === "" ? "." : `./${rest}`
1443
+ };
1444
+ };
1445
+ const builtins = new Set(builtinModules);
1446
+ const urlScheme = /^[a-z][a-z0-9+.-]*:/;
1447
+ const typesPackageOf = (name) => `@types/${name.startsWith("@") ? name.slice(1).replace("/", "__") : name}`;
1448
+ const isConventionalAlias = (specifier) => specifier.startsWith("@/") || specifier.startsWith("~");
1449
+ const isBuiltinOrUrl = (specifier, name) => urlScheme.test(specifier) || specifier.startsWith("/") || builtins.has(specifier) || builtins.has(name);
1450
+ const external = { kind: "external" };
1451
+ const createResolver = ({
1452
+ exists,
1453
+ manifests
1454
+ }) => {
1455
+ const directoryOfName = /* @__PURE__ */ new Map();
1456
+ for (const [directory, { name }] of manifests) {
1457
+ if (typeof name === "string") directoryOfName.set(name, directory);
1458
+ }
1459
+ const dependenciesByDirectory = /* @__PURE__ */ new Map();
1460
+ const dependenciesOf = (directory) => {
1461
+ const known = dependenciesByDirectory.get(directory);
1462
+ if (known !== void 0) return known;
1463
+ const manifest = manifests.get(directory);
1464
+ const names = new Set(
1465
+ manifest === void 0 ? [] : dependencyNamesOf(manifest)
1466
+ );
1467
+ dependenciesByDirectory.set(directory, names);
1468
+ return names;
1469
+ };
1470
+ const isDependencyOf = (from, name) => {
1471
+ const nearest = manifestDirectoryOf(from, manifests);
1472
+ const wanted = [name, typesPackageOf(name)];
1473
+ return [nearest, ""].some(
1474
+ (directory) => directory !== null && wanted.some((candidate) => dependenciesOf(directory).has(candidate))
1475
+ );
1476
+ };
1477
+ const resolveTarget = (directory, target) => typeof target === "string" && target.startsWith("./") ? resolveRelative(`${directory}/package.json`, target, exists) : null;
1478
+ const resolveInPackage = (directory, subpath) => {
1479
+ const manifest = manifests.get(directory);
1480
+ const { exports } = manifest;
1481
+ if (exports === void 0) {
1482
+ return subpath === "." ? resolveTarget(directory, targetOf(manifest.types) ?? manifest.main) : resolveRelative(`${directory}/package.json`, subpath, exists);
1483
+ }
1484
+ const isSubpathMap = typeof exports === "object" && exports !== null && Object.keys(exports).some((key) => key.startsWith("."));
1485
+ if (!isSubpathMap) {
1486
+ return subpath === "." ? resolveTarget(directory, targetOf(exports)) : null;
1487
+ }
1488
+ const patterns = Object.entries(exports).filter(([pattern]) => pattern.startsWith(".")).toSorted(([left], [right]) => specificity(right) - specificity(left));
1489
+ for (const [pattern, value] of patterns) {
1490
+ const captured = expand(pattern, subpath);
1491
+ if (captured === null) continue;
1492
+ const target = targetOf(value);
1493
+ return resolveTarget(
1494
+ directory,
1495
+ typeof target === "string" ? target.replace("*", captured) : null
1496
+ );
1497
+ }
1498
+ return null;
1499
+ };
1500
+ return (from, specifier) => {
1501
+ if (specifier.startsWith(".") || specifier.startsWith("#")) {
1502
+ const path = resolveLocalSpecifier({
1503
+ from,
1504
+ specifier,
1505
+ exists,
1506
+ manifests
1507
+ });
1508
+ return path === null ? external : { kind: "file", path };
1509
+ }
1510
+ if (isConventionalAlias(specifier)) return { kind: "alias" };
1511
+ const { name, subpath } = splitPackageSpecifier(specifier);
1512
+ const directory = directoryOfName.get(name);
1513
+ if (directory !== void 0) {
1514
+ const path = resolveInPackage(directory, subpath);
1515
+ return path === null ? { kind: "workspaceMiss", directory } : { kind: "file", path };
1516
+ }
1517
+ if (isBuiltinOrUrl(specifier, name) || isDependencyOf(from, name)) {
1518
+ return external;
1519
+ }
1520
+ return { kind: "alias" };
1521
+ };
1522
+ };
1523
+ const maxStarHops = 4;
1524
+ const readSummariesAt = async ({
1525
+ checkout,
1526
+ ref,
1527
+ roots,
1528
+ resolveFile
1529
+ }) => {
1530
+ const found = /* @__PURE__ */ new Map();
1531
+ let toRead = [...new Set(roots)];
1532
+ for (let hop = 0; hop < maxStarHops && toRead.length > 0; hop++) {
1533
+ const contents = await catFiles(checkout, ref, toRead);
1534
+ const next = /* @__PURE__ */ new Set();
1535
+ for (const path of toRead) {
1536
+ const source = contents.get(path);
1537
+ if (source === void 0) continue;
1538
+ const summary = summarize(path, source);
1539
+ found.set(path, summary);
1540
+ for (const specifier of starSpecifiers(summary.reExports)) {
1541
+ const target = resolveFile(path, specifier);
1542
+ if (target !== null && !found.has(target)) next.add(target);
1543
+ }
1544
+ }
1545
+ toRead = [...next];
1546
+ }
1547
+ return found;
1548
+ };
1549
+ const starNames = ({
1550
+ from,
1551
+ specifier,
1552
+ summaryOf,
1553
+ resolveFile
1554
+ }) => {
1555
+ const names = /* @__PURE__ */ new Set();
1556
+ const visited = /* @__PURE__ */ new Set();
1557
+ const visit = (importer, request) => {
1558
+ const target = resolveFile(importer, request);
1559
+ if (target === null) return false;
1560
+ if (visited.has(target)) return true;
1561
+ visited.add(target);
1562
+ const summary = summaryOf(target);
1563
+ if (summary === void 0) return false;
1564
+ for (const name of summary.exports) {
1565
+ if (name !== "default") names.add(name);
1566
+ }
1567
+ return starSpecifiers(summary.reExports).every(
1568
+ (next) => visit(target, next)
1569
+ );
1570
+ };
1571
+ return visit(from, specifier) ? [...names] : null;
1572
+ };
1573
+ const buildGraph = ({
1574
+ files,
1575
+ resolve,
1576
+ packageOfFile,
1577
+ changedPackages
1578
+ }) => {
1579
+ const importers = /* @__PURE__ */ new Map();
1580
+ const reExporters = /* @__PURE__ */ new Map();
1581
+ const unresolvedIn = /* @__PURE__ */ new Set();
1582
+ const push = (map, key, item) => {
1583
+ const items = map.get(key);
1584
+ if (items === void 0) map.set(key, [item]);
1585
+ else items.push(item);
1586
+ };
1587
+ for (const [path, { imports, reExports }] of files) {
1588
+ const reach = (specifier) => {
1589
+ const resolution = resolve(path, specifier);
1590
+ const importerPackage = packageOfFile(path);
1591
+ if (resolution.kind === "alias" && changedPackages.has(importerPackage) || resolution.kind === "workspaceMiss" && changedPackages.has(
1592
+ resolution.directory === "" ? "." : resolution.directory
1593
+ )) {
1594
+ unresolvedIn.add(importerPackage);
1595
+ }
1596
+ return resolution.kind === "file" ? resolution.path : null;
1597
+ };
1598
+ for (const { specifier, names } of imports) {
1599
+ const target = reach(specifier);
1600
+ if (target !== null) push(importers, target, { from: path, names });
1601
+ }
1602
+ for (const { specifier, local, exported } of reExports) {
1603
+ const target = reach(specifier);
1604
+ if (target !== null) {
1605
+ push(reExporters, target, { from: path, local, exported });
1606
+ }
1607
+ }
1608
+ }
1609
+ return { importers, reExporters, unresolvedIn };
1610
+ };
1611
+ const importersOf = ({ importers, reExporters }, file, name) => {
1612
+ const found = /* @__PURE__ */ new Set();
1613
+ const visited = /* @__PURE__ */ new Set([`${file}\0${name}`]);
1614
+ const stack = [[file, name]];
1615
+ while (stack.length > 0) {
1616
+ const [current, currentName] = stack.pop();
1617
+ for (const { from, names } of importers.get(current) ?? []) {
1618
+ if (names.includes(currentName) || names.includes("*")) found.add(from);
1619
+ }
1620
+ for (const { from, local, exported } of reExporters.get(current) ?? []) {
1621
+ const passesAll = local === "*" && exported === "*";
1622
+ if (local !== "*" && local !== currentName) continue;
1623
+ if (passesAll && currentName === "default") continue;
1624
+ const next = [
1625
+ from,
1626
+ exported === "*" ? currentName : exported
1627
+ ];
1628
+ const key = `${next[0]}\0${next[1]}`;
1629
+ if (visited.has(key)) continue;
1630
+ visited.add(key);
1631
+ stack.push(next);
1632
+ }
1633
+ }
1634
+ found.delete(file);
1635
+ return found;
1636
+ };
1637
+ const readImpacts = async ({
1638
+ checkout,
1639
+ head,
1640
+ base,
1641
+ cache,
1642
+ maxFiles = maxTypescriptFiles,
1643
+ maxReadMs: budgetMs = maxReadMs
1644
+ }) => {
1645
+ if (!isSha(head) || !isSha(base)) {
1646
+ throw new Error("head and base must be commit shas");
1647
+ }
1648
+ await ensureCommits(checkout, [head, base], void 0);
1649
+ const mergeBaseOutput = await git(checkout, ["merge-base", base, head]);
1650
+ const mergeBase = mergeBaseOutput.trim();
1651
+ const numstat = await readNumstat(checkout, [mergeBase, head], []);
1652
+ const blobs = await listBlobs(checkout, head);
1653
+ const treePaths = blobs.map(({ path }) => path);
1654
+ const inTree = new Set(treePaths);
1655
+ const typescriptBlobs = blobs.filter(
1656
+ ({ path }) => isTypescriptPath(path) && !isInNodeModules(path)
1657
+ );
1658
+ if (typescriptBlobs.length > maxFiles) {
1659
+ return { tooLarge: true, reason: "files" };
1660
+ }
1661
+ const summaries = await readSummaries({
1662
+ checkout,
1663
+ head,
1664
+ blobs: typescriptBlobs,
1665
+ cache,
1666
+ budgetMs
1667
+ });
1668
+ if (summaries === null) return { tooLarge: true, reason: "time" };
1669
+ const manifests = await readManifests(checkout, head, treePaths);
1670
+ const files = new Map(
1671
+ typescriptBlobs.map(({ path, oid }) => [path, summaries.get(oid)])
1672
+ );
1673
+ const packageOfFile = (path) => manifestDirectoryOf(path, manifests) || ".";
1674
+ const analyzed = numstat.filter(({ path }) => files.has(path));
1675
+ const resolve = createResolver({
1676
+ exists: (path) => inTree.has(path),
1677
+ manifests
1678
+ });
1679
+ const resolveFile = (from, specifier) => {
1680
+ const resolution = resolve(from, specifier);
1681
+ return resolution.kind === "file" ? resolution.path : null;
1682
+ };
1683
+ const graph = buildGraph({
1684
+ files,
1685
+ resolve,
1686
+ packageOfFile,
1687
+ changedPackages: new Set(analyzed.map(({ path }) => packageOfFile(path)))
1688
+ });
1689
+ const changedSet = new Set(numstat.map(({ path }) => path));
1690
+ const notAnalyzed = numstat.filter(({ path }) => !files.has(path)).map(({ path }) => path);
1691
+ const touchedLines = await readTouchedLines({
1692
+ checkout,
1693
+ mergeBase,
1694
+ head,
1695
+ entries: analyzed
1696
+ });
1697
+ const sources = await catFiles(
1698
+ checkout,
1699
+ head,
1700
+ analyzed.map(({ path }) => path)
1701
+ );
1702
+ const baseSources = await catFiles(
1703
+ checkout,
1704
+ mergeBase,
1705
+ analyzed.map(({ paths }) => paths[0])
1706
+ );
1707
+ const impacts = [];
1708
+ for (const { path, paths } of analyzed) {
1709
+ const source = sources.get(path);
1710
+ if (source === void 0) {
1711
+ notAnalyzed.push(path);
1712
+ continue;
1713
+ }
1714
+ const baseSource = baseSources.get(paths[0]);
1715
+ const baseBindings = baseSource === void 0 ? void 0 : exportedBindings(path, baseSource);
1716
+ const { touched, removed, touchedStars, removedStars } = touchedExports({
1717
+ path,
1718
+ source,
1719
+ touchedLines: touchedLines.get(path) ?? [],
1720
+ baseExports: baseBindings === void 0 ? [] : exportedNames(baseBindings),
1721
+ baseStars: baseBindings === void 0 ? [] : starSpecifiers(baseBindings.reExports)
1722
+ });
1723
+ let starsUnknown = false;
1724
+ const known = (names) => {
1725
+ if (names === null) starsUnknown = true;
1726
+ return names ?? [];
1727
+ };
1728
+ const starNamesAtHead = (specifier) => known(
1729
+ starNames({
1730
+ from: path,
1731
+ specifier,
1732
+ summaryOf: (target) => files.get(target),
1733
+ resolveFile
1734
+ })
1735
+ );
1736
+ const baseSummaries = removedStars.length === 0 ? /* @__PURE__ */ new Map() : await readSummariesAt({
1737
+ checkout,
1738
+ ref: mergeBase,
1739
+ roots: removedStars.flatMap(
1740
+ (specifier) => resolveFile(paths[0], specifier) ?? []
1741
+ ),
1742
+ resolveFile
1743
+ });
1744
+ const removedByStars = removedStars.flatMap(
1745
+ (specifier) => known(
1746
+ starNames({
1747
+ from: paths[0],
1748
+ specifier,
1749
+ summaryOf: (target) => baseSummaries.get(target),
1750
+ resolveFile
1751
+ })
1752
+ )
1753
+ );
1754
+ const headSummary = files.get(path);
1755
+ const stillExported = /* @__PURE__ */ new Set([
1756
+ ...headSummary.exports,
1757
+ ...removed.length + removedByStars.length === 0 ? [] : starSpecifiers(headSummary.reExports).flatMap(starNamesAtHead)
1758
+ ]);
1759
+ const touchedNames = /* @__PURE__ */ new Set([
1760
+ ...touched,
1761
+ ...touchedStars.flatMap(starNamesAtHead)
1762
+ ]);
1763
+ const removedNames = new Set(
1764
+ [...removed, ...removedByStars].filter(
1765
+ (name) => !stillExported.has(name)
1766
+ )
1767
+ );
1768
+ if (starsUnknown && !notAnalyzed.includes(path)) notAnalyzed.push(path);
1769
+ const exports = [
1770
+ ...[...touchedNames].map((name) => ({ name, removed: false })),
1771
+ ...[...removedNames].map((name) => ({ name, removed: true }))
1772
+ ].flatMap(({ name, removed: isRemoved }) => {
1773
+ const importers = [...importersOf(graph, path, name)].filter((importer) => !changedSet.has(importer)).toSorted((left, right) => left.localeCompare(right)).map((importer) => ({
1774
+ path: importer,
1775
+ test: isAttachablePath(importer)
1776
+ }));
1777
+ return importers.length === 0 ? [] : [{ name, removed: isRemoved, importers }];
1778
+ });
1779
+ if (exports.length > 0) impacts.push({ path, exports });
1780
+ }
1781
+ return {
1782
+ tooLarge: false,
1783
+ files: impacts,
1784
+ notAnalyzed,
1785
+ unresolvedIn: [...graph.unresolvedIn].toSorted(
1786
+ (left, right) => left.localeCompare(right)
1787
+ )
1788
+ };
1789
+ };
1790
+
1791
+ export { createBindingsCache, defaultDiffLimits, git, gitFailedCode, gitOrNull, groupDiffPaths, maxTypescriptFiles, noPackages, packagesAtTree, packagesOnDisk, parseNumstat, readBranchDiff, readFileAtSha, readImpacts, readPullRequestDiff, readReadingOrder, reasonLine };
1792
+ //# sourceMappingURL=git-node.mjs.map