dsh-pr-release-workspace 0.1.0 → 0.1.1

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/lib/client.js CHANGED
@@ -77,6 +77,7 @@ window.__ModuleLoader__.load({
77
77
  'detail.noDiff': 'No changed files were returned.',
78
78
  'detail.binary': 'Binary file — no text diff.',
79
79
  'detail.diffCapped': 'This pull request is larger than one diff; files were read individually.',
80
+ 'detail.diffIncomplete': 'This pull request changes more files than this panel will page through; the list below is incomplete.',
80
81
  'detail.diffFailed': 'The diff could not be loaded.',
81
82
  'detail.noComments': 'No review comments yet.',
82
83
  'detail.reviewComment': 'on line {line}',
@@ -186,6 +187,7 @@ window.__ModuleLoader__.load({
186
187
  'detail.noDiff': '没有返回变更文件。',
187
188
  'detail.binary': '二进制文件,无文本差异。',
188
189
  'detail.diffCapped': '这个 PR 的改动超过单份 diff 的限额,已按文件逐个读取。',
190
+ 'detail.diffIncomplete': '这个 PR 的改动文件数超过本面板的分页上限,下面的列表并不完整。',
189
191
  'detail.diffFailed': 'diff 加载失败。',
190
192
  'detail.noComments': '还没有评审评论。',
191
193
  'detail.reviewComment': '第 {line} 行',
@@ -1859,7 +1861,14 @@ window.__ModuleLoader__.load({
1859
1861
  'div',
1860
1862
  { style: { display: 'flex', flexDirection: 'column', gap: '2px' } },
1861
1863
  value.truncated === true
1862
- ? h('p', { className: 'prw-note', style: { margin: '0 0 8px' } }, t('detail.diffCapped'))
1864
+ ? h(
1865
+ 'p',
1866
+ { className: 'prw-note', style: { margin: '0 0 8px' } },
1867
+ // `truncated` says GitHub would not render one diff; `complete`
1868
+ // says whether the per-file read reached the end. They are
1869
+ // different claims and a partial list must not look whole.
1870
+ value.complete === false ? t('detail.diffIncomplete') : t('detail.diffCapped'),
1871
+ )
1863
1872
  : null,
1864
1873
  value.files.map(function (file, index) {
1865
1874
  return h(FileRow, {
package/lib/host/exec.js CHANGED
@@ -67,6 +67,19 @@ export function classifyGhFailure(error, stderr) {
67
67
  const rate = matchRateLimit(text)
68
68
  if (rate !== null) return rate
69
69
 
70
+ // `gh pr diff` refuses any PR over 300 changed files with
71
+ // `HTTP 406: … the diff exceeded the maximum number of files (300)` plus
72
+ // `PullRequest.diff too_large`. This has its own code because the caller can
73
+ // recover from it — the per-file endpoint still answers — and because the
74
+ // generic `unknown` message would erase the only thing that identifies it.
75
+ if (/too_large|exceeded the maximum number of files|diff exceeded the maximum/i.test(text)) {
76
+ return {
77
+ code: 'diff-too-large',
78
+ message: 'This pull request changes more files than GitHub will return as a single diff.',
79
+ retryAfterSeconds: null,
80
+ }
81
+ }
82
+
70
83
  if (/Resource not accessible by integration|must have .* access|insufficient.*scope/i.test(text)) {
71
84
  return {
72
85
  code: 'forbidden',
package/lib/host/pull.js CHANGED
@@ -452,13 +452,18 @@ export async function readPullDiff(target, options = {}) {
452
452
  // hunks. Falling back keeps a big PR readable instead of turning the detail
453
453
  // view into an error page.
454
454
  if (!isDiffTooLarge(error)) throw error
455
- const files = await readFilePatches(target, options)
455
+ const { files, complete } = await readFilePatches(target, options)
456
456
  return {
457
457
  files,
458
+ // `truncated` means "GitHub would not render this as one diff", which is
459
+ // exactly the condition that got us here.
458
460
  truncated: true,
461
+ complete,
459
462
  bytes: 0,
460
463
  source: 'rest-file-patches',
461
- note: 'This pull request has more changed files than GitHub will render as one diff; it was reassembled from per-file patches.',
464
+ note: complete
465
+ ? 'This pull request has more changed files than GitHub will render as one diff; it was reassembled from per-file patches.'
466
+ : 'This pull request has more changed files than GitHub will render as one diff, and more than this panel will page through; the list below is incomplete.',
462
467
  }
463
468
  }
464
469
  }
@@ -466,10 +471,17 @@ export async function readPullDiff(target, options = {}) {
466
471
  /**
467
472
  * Was this failure the "too many files" refusal rather than a real error?
468
473
  *
474
+ * `runGh` classifies a failure and hands back a content-free message, so the
475
+ * original stderr — the only place `HTTP 406` and `too_large` appear — is gone
476
+ * by the time we get here. The classifier therefore assigns its own code, and
477
+ * this checks that code. The message pattern stays as a second line of defence
478
+ * for errors that did not come from `runGh`.
479
+ *
469
480
  * @param {unknown} error
470
481
  * @returns {boolean}
471
482
  */
472
483
  function isDiffTooLarge(error) {
484
+ if (error?.code === 'diff-too-large') return true
473
485
  const message = String(error?.message ?? '')
474
486
  return /406|too_large|exceeded the maximum number of files|too large/i.test(message)
475
487
  }
@@ -485,19 +497,26 @@ function isDiffTooLarge(error) {
485
497
  *
486
498
  * @param {{ owner: string, repo: string, number: number }} target
487
499
  * @param {{ signal?: AbortSignal }} [options]
488
- * @returns {Promise<{ path: string, oldText: string | null, newText: string | null }[]>}
500
+ * @returns {Promise<{ files: { path: string, oldText: string | null, newText: string | null }[], complete: boolean }>}
489
501
  */
490
502
  async function readFilePatches(target, options) {
491
503
  /** @type {{ path: string, oldText: string | null, newText: string | null }[]} */
492
504
  const out = []
493
505
  // `per_page` is capped at 100 by the API regardless of what is asked for.
494
- const maxPages = 8
506
+ // Twenty pages is 2000 files: past that the read is not a diff any more, and
507
+ // the caller is told the list is incomplete rather than being handed a slice
508
+ // that looks whole.
509
+ const maxPages = 20
510
+ let complete = false
495
511
  for (let page = 1; page <= maxPages; page += 1) {
496
512
  const items = await ghApiJson(
497
513
  `/repos/${encodeURIComponent(target.owner)}/${encodeURIComponent(target.repo)}/pulls/${target.number}/files`,
498
514
  { query: { per_page: '100', page: String(page) }, signal: options.signal, timeoutMs: 30_000 },
499
515
  )
500
- if (!Array.isArray(items) || items.length === 0) break
516
+ if (!Array.isArray(items) || items.length === 0) {
517
+ complete = true
518
+ break
519
+ }
501
520
  for (const item of items) {
502
521
  if (typeof item?.filename !== 'string') continue
503
522
  const status = typeof item?.status === 'string' ? item.status : 'modified'
@@ -513,9 +532,12 @@ async function readFilePatches(target, options) {
513
532
  newText: status === 'removed' ? null : newText,
514
533
  })
515
534
  }
516
- if (items.length < 100) break
535
+ if (items.length < 100) {
536
+ complete = true
537
+ break
538
+ }
517
539
  }
518
- return out
540
+ return { files: out, complete }
519
541
  }
520
542
 
521
543
  /**
package/locale/en.json CHANGED
@@ -19,6 +19,7 @@
19
19
  "detail.conflicts": "Conflicts",
20
20
  "detail.diffCapped": "This pull request is larger than one diff; files were read individually.",
21
21
  "detail.diffFailed": "The diff could not be loaded.",
22
+ "detail.diffIncomplete": "This pull request changes more files than this panel will page through; the list below is incomplete.",
22
23
  "detail.drafts": "My notes",
23
24
  "detail.files": "Files",
24
25
  "detail.head": "from",
package/locale/zh.json CHANGED
@@ -19,6 +19,7 @@
19
19
  "detail.conflicts": "有冲突",
20
20
  "detail.diffCapped": "这个 PR 的改动超过单份 diff 的限额,已按文件逐个读取。",
21
21
  "detail.diffFailed": "diff 加载失败。",
22
+ "detail.diffIncomplete": "这个 PR 的改动文件数超过本面板的分页上限,下面的列表并不完整。",
22
23
  "detail.drafts": "我的批注",
23
24
  "detail.files": "文件",
24
25
  "detail.head": "来自",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-pr-release-workspace",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "PR and release workspace for DSH: grouped pull requests, file diffs, review comments, CI checks, and an evidence-backed release chain from merged PR to the live market index.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",