ccstatusline 2.2.24 → 2.2.25

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 (2) hide show
  1. package/dist/ccstatusline.js +221 -42
  2. package/package.json +1 -1
@@ -55594,12 +55594,18 @@ var init_GitUntrackedFiles = __esm(() => {
55594
55594
  });
55595
55595
 
55596
55596
  // src/utils/git-review-cache.ts
55597
- import { execFileSync as execFileSync2 } from "child_process";
55598
55597
  import {
55598
+ execFileSync as execFileSync2,
55599
+ spawn
55600
+ } from "child_process";
55601
+ import {
55602
+ closeSync,
55599
55603
  existsSync as existsSync2,
55600
55604
  mkdirSync as mkdirSync2,
55605
+ openSync,
55601
55606
  readFileSync as readFileSync3,
55602
55607
  statSync as statSync2,
55608
+ unlinkSync,
55603
55609
  writeFileSync as writeFileSync2
55604
55610
  } from "fs";
55605
55611
  import { createHash as createHash2 } from "node:crypto";
@@ -55689,35 +55695,66 @@ function getCachePath2(cwd2, ref, deps) {
55689
55695
  const hash2 = createHash2("sha256").update(cwd2).update("\x00").update(ref).digest("hex").slice(0, 16);
55690
55696
  return path2.join(getGitReviewCacheDir(deps), `git-review-${hash2}.json`);
55691
55697
  }
55698
+ function isGitReviewData(value) {
55699
+ if (typeof value !== "object" || value === null) {
55700
+ return false;
55701
+ }
55702
+ const candidate = value;
55703
+ return typeof candidate.number === "number" && typeof candidate.url === "string";
55704
+ }
55705
+ function decodeCache(content) {
55706
+ if (content.length === 0) {
55707
+ return { data: null, checksQueried: true };
55708
+ }
55709
+ const parsed = JSON.parse(content);
55710
+ if (typeof parsed === "object" && parsed !== null) {
55711
+ const stored = parsed;
55712
+ if (stored.version === 1 && typeof stored.checksQueried === "boolean" && (stored.data === null || isGitReviewData(stored.data))) {
55713
+ return {
55714
+ data: stored.data,
55715
+ checksQueried: stored.data === null || stored.checksQueried
55716
+ };
55717
+ }
55718
+ }
55719
+ if (isGitReviewData(parsed)) {
55720
+ return {
55721
+ data: parsed,
55722
+ checksQueried: parsed.checks !== undefined
55723
+ };
55724
+ }
55725
+ return "miss";
55726
+ }
55692
55727
  function readCache(cachePath, deps) {
55693
55728
  try {
55694
55729
  if (!deps.existsSync(cachePath)) {
55695
55730
  return "miss";
55696
55731
  }
55697
55732
  const age = deps.now() - deps.statSync(cachePath).mtimeMs;
55698
- if (age > GIT_REVIEW_CACHE_TTL) {
55699
- return "miss";
55700
- }
55701
55733
  const content = deps.readFileSync(cachePath, "utf-8").trim();
55702
- if (content.length === 0) {
55703
- return null;
55704
- }
55705
- const data = JSON.parse(content);
55706
- if (typeof data.number !== "number" || typeof data.url !== "string") {
55734
+ const decoded = decodeCache(content);
55735
+ if (decoded === "miss") {
55707
55736
  return "miss";
55708
55737
  }
55709
- return data;
55738
+ return {
55739
+ ...decoded,
55740
+ stale: age > GIT_REVIEW_CACHE_TTL
55741
+ };
55710
55742
  } catch {
55711
55743
  return "miss";
55712
55744
  }
55713
55745
  }
55714
- function writeCache(cachePath, data, deps) {
55746
+ function writeCache(cachePath, data, checksQueried, deps) {
55715
55747
  try {
55716
55748
  const cacheDir = getGitReviewCacheDir(deps);
55717
55749
  if (!deps.existsSync(cacheDir)) {
55718
55750
  deps.mkdirSync(cacheDir, { recursive: true });
55719
55751
  }
55720
- deps.writeFileSync(cachePath, data ? JSON.stringify(data) : "", "utf-8");
55752
+ const stored = {
55753
+ version: 1,
55754
+ data,
55755
+ checksQueried: data === null || checksQueried
55756
+ };
55757
+ deps.writeFileSync(cachePath, JSON.stringify(stored), "utf-8");
55721
55758
  } catch {}
55722
55759
  }
55723
55760
  function getOriginUrl(cwd2, deps) {
@@ -55798,11 +55835,18 @@ function getProviderCandidates(cwd2, deps) {
55798
55835
  }
55799
55836
  return authed;
55800
55837
  }
55801
- function isCliAvailable(cli, deps) {
55838
+ function getRemainingTimeout(deadline, deps) {
55839
+ const remaining = deadline - deps.now();
55840
+ if (remaining <= 0) {
55841
+ throw new GitReviewDeadlineError("Git review lookup deadline exceeded");
55842
+ }
55843
+ return Math.max(1, Math.min(CLI_TIMEOUT, remaining));
55844
+ }
55845
+ function isCliAvailable(cli, deadline, deps) {
55802
55846
  try {
55803
55847
  deps.execFileSync(cli, ["--version"], {
55804
55848
  stdio: ["pipe", "pipe", "ignore"],
55805
- timeout: CLI_TIMEOUT,
55849
+ timeout: getRemainingTimeout(deadline, deps),
55806
55850
  windowsHide: true
55807
55851
  });
55808
55852
  return true;
@@ -55833,12 +55877,25 @@ function mapGlabState(state) {
55833
55877
  return "LOCKED";
55834
55878
  return state.toUpperCase();
55835
55879
  }
55836
- function queryGhPr(cwd2, args, fields, deps) {
55880
+ function errorText(error51) {
55881
+ if (!(error51 instanceof Error)) {
55882
+ return "";
55883
+ }
55884
+ const stderr = "stderr" in error51 ? error51.stderr : undefined;
55885
+ const stderrText = Buffer.isBuffer(stderr) ? stderr.toString("utf8") : stderr ?? "";
55886
+ return `${error51.message}
55887
+ ${stderrText}`.toLowerCase();
55888
+ }
55889
+ function isCiFieldUnavailableError(error51) {
55890
+ const text = errorText(error51);
55891
+ return text.includes("statuscheckrollup") || text.includes("resource not accessible by integration");
55892
+ }
55893
+ function queryGhPr(cwd2, args, fields, deadline, deps) {
55837
55894
  const output = deps.execFileSync("gh", [...args, "--json", fields], {
55838
55895
  encoding: "utf8",
55839
- stdio: ["pipe", "pipe", "ignore"],
55896
+ stdio: ["pipe", "pipe", "pipe"],
55840
55897
  cwd: cwd2,
55841
- timeout: CLI_TIMEOUT,
55898
+ timeout: getRemainingTimeout(deadline, deps),
55842
55899
  windowsHide: true
55843
55900
  }).trim();
55844
55901
  if (output.length === 0) {
@@ -55846,7 +55903,7 @@ function queryGhPr(cwd2, args, fields, deps) {
55846
55903
  }
55847
55904
  return JSON.parse(output);
55848
55905
  }
55849
- function fetchFromGh(cwd2, repoRef, deps) {
55906
+ function fetchFromGh(cwd2, repoRef, includeChecks, deadline, deps) {
55850
55907
  const args = ["pr", "view"];
55851
55908
  if (repoRef) {
55852
55909
  const branch = getCurrentBranch(cwd2, deps);
@@ -55856,10 +55913,17 @@ function fetchFromGh(cwd2, repoRef, deps) {
55856
55913
  args.push(branch, "--repo", repoRef);
55857
55914
  }
55858
55915
  let parsed;
55859
- try {
55860
- parsed = queryGhPr(cwd2, args, GH_PR_WITH_CHECKS_FIELDS, deps);
55861
- } catch {
55862
- parsed = queryGhPr(cwd2, args, GH_PR_METADATA_FIELDS, deps);
55916
+ if (includeChecks) {
55917
+ try {
55918
+ parsed = queryGhPr(cwd2, args, GH_PR_WITH_CHECKS_FIELDS, deadline, deps);
55919
+ } catch (error51) {
55920
+ if (!isCiFieldUnavailableError(error51)) {
55921
+ throw error51;
55922
+ }
55923
+ parsed = queryGhPr(cwd2, args, GH_PR_METADATA_FIELDS, deadline, deps);
55924
+ }
55925
+ } else {
55926
+ parsed = queryGhPr(cwd2, args, GH_PR_METADATA_FIELDS, deadline, deps);
55863
55927
  }
55864
55928
  if (!parsed) {
55865
55929
  return null;
@@ -55877,7 +55941,7 @@ function fetchFromGh(cwd2, repoRef, deps) {
55877
55941
  checks: computeCiRollup(parsed.statusCheckRollup) ?? undefined
55878
55942
  };
55879
55943
  }
55880
- function fetchFromGlab(cwd2, repoRef, deps) {
55944
+ function fetchFromGlab(cwd2, repoRef, deadline, deps) {
55881
55945
  const args = ["mr", "view"];
55882
55946
  if (repoRef) {
55883
55947
  const branch = getCurrentBranch(cwd2, deps);
@@ -55891,7 +55955,7 @@ function fetchFromGlab(cwd2, repoRef, deps) {
55891
55955
  encoding: "utf8",
55892
55956
  stdio: ["pipe", "pipe", "ignore"],
55893
55957
  cwd: cwd2,
55894
- timeout: CLI_TIMEOUT,
55958
+ timeout: getRemainingTimeout(deadline, deps),
55895
55959
  windowsHide: true
55896
55960
  }).trim();
55897
55961
  if (output.length === 0) {
@@ -55910,41 +55974,129 @@ function fetchFromGlab(cwd2, repoRef, deps) {
55910
55974
  provider: "glab"
55911
55975
  };
55912
55976
  }
55913
- function fetchFromProvider(provider, cwd2, repoRef, deps) {
55914
- const fetchFn = provider === "gh" ? fetchFromGh : fetchFromGlab;
55977
+ function fetchFromProvider(provider, cwd2, repoRef, includeChecks, deadline, deps) {
55978
+ const fetch2 = (targetRepoRef) => provider === "gh" ? fetchFromGh(cwd2, targetRepoRef, includeChecks, deadline, deps) : fetchFromGlab(cwd2, targetRepoRef, deadline, deps);
55915
55979
  try {
55916
- const unpinned = fetchFn(cwd2, null, deps);
55980
+ const unpinned = fetch2(null);
55917
55981
  if (unpinned) {
55918
55982
  return unpinned;
55919
55983
  }
55920
55984
  } catch {}
55921
55985
  if (repoRef) {
55922
- return fetchFn(cwd2, repoRef, deps);
55986
+ return fetch2(repoRef);
55923
55987
  }
55924
55988
  return null;
55925
55989
  }
55926
- function fetchGitReviewData(cwd2, deps = DEFAULT_GIT_REVIEW_CACHE_DEPS) {
55990
+ function fetchGitReviewData(cwd2, deps = DEFAULT_GIT_REVIEW_CACHE_DEPS, options = {}) {
55991
+ const includeChecks = options.includeChecks ?? false;
55927
55992
  const cachePath = getCachePath2(cwd2, getCacheRef(cwd2, deps), deps);
55928
55993
  const cached2 = readCache(cachePath, deps);
55929
- if (cached2 !== "miss") {
55930
- return cached2;
55994
+ if (cached2 !== "miss" && !cached2.stale && (!includeChecks || cached2.checksQueried)) {
55995
+ return cached2.data;
55931
55996
  }
55932
55997
  const repoRef = getOriginRepoRef(cwd2, deps);
55998
+ const deadline = deps.now() + CLI_TIMEOUT;
55933
55999
  for (const provider of getProviderCandidates(cwd2, deps)) {
55934
- if (!isCliAvailable(provider, deps)) {
56000
+ if (!isCliAvailable(provider, deadline, deps)) {
55935
56001
  continue;
55936
56002
  }
55937
56003
  try {
55938
- const data = fetchFromProvider(provider, cwd2, repoRef, deps);
56004
+ const data = fetchFromProvider(provider, cwd2, repoRef, includeChecks, deadline, deps);
55939
56005
  if (data) {
55940
- writeCache(cachePath, data, deps);
56006
+ writeCache(cachePath, data, includeChecks, deps);
55941
56007
  return data;
55942
56008
  }
55943
56009
  } catch {}
55944
56010
  }
55945
- writeCache(cachePath, null, deps);
56011
+ if (cached2 !== "miss" && cached2.data !== null) {
56012
+ return cached2.data;
56013
+ }
56014
+ writeCache(cachePath, null, true, deps);
55946
56015
  return null;
55947
56016
  }
56017
+ function getRefreshLockPath(cachePath) {
56018
+ return `${cachePath}.lock`;
56019
+ }
56020
+ function releaseRefreshLock(lockPath, deps) {
56021
+ try {
56022
+ deps.unlinkSync(lockPath);
56023
+ } catch {}
56024
+ }
56025
+ function createRefreshLock(cachePath, deps) {
56026
+ const cacheDir = getGitReviewCacheDir(deps);
56027
+ try {
56028
+ if (!deps.existsSync(cacheDir)) {
56029
+ deps.mkdirSync(cacheDir, { recursive: true });
56030
+ }
56031
+ } catch {
56032
+ return null;
56033
+ }
56034
+ const lockPath = getRefreshLockPath(cachePath);
56035
+ for (let attempt2 = 0;attempt2 < 2; attempt2++) {
56036
+ try {
56037
+ const descriptor = deps.openSync(lockPath, "wx");
56038
+ deps.closeSync(descriptor);
56039
+ return lockPath;
56040
+ } catch {
56041
+ try {
56042
+ const age = deps.now() - deps.statSync(lockPath).mtimeMs;
56043
+ if (age <= REFRESH_LOCK_STALE_MS) {
56044
+ return null;
56045
+ }
56046
+ deps.unlinkSync(lockPath);
56047
+ } catch {
56048
+ return null;
56049
+ }
56050
+ }
56051
+ }
56052
+ return null;
56053
+ }
56054
+ function scheduleRefresh(cwd2, cachePath, includeChecks, deps) {
56055
+ const scriptPath = deps.getScriptPath();
56056
+ if (!scriptPath) {
56057
+ return;
56058
+ }
56059
+ const lockPath = createRefreshLock(cachePath, deps);
56060
+ if (!lockPath) {
56061
+ return;
56062
+ }
56063
+ try {
56064
+ const child = deps.spawn(deps.getExecPath(), [
56065
+ scriptPath,
56066
+ GIT_REVIEW_REFRESH_FLAG,
56067
+ cwd2,
56068
+ includeChecks ? "checks" : "metadata",
56069
+ lockPath
56070
+ ], {
56071
+ detached: true,
56072
+ stdio: "ignore",
56073
+ windowsHide: true
56074
+ });
56075
+ child.unref();
56076
+ } catch {
56077
+ releaseRefreshLock(lockPath, deps);
56078
+ }
56079
+ }
56080
+ function getCachedGitReviewData(cwd2, options = {}, deps = DEFAULT_GIT_REVIEW_CACHE_DEPS) {
56081
+ const includeChecks = options.includeChecks ?? false;
56082
+ const cachePath = getCachePath2(cwd2, getCacheRef(cwd2, deps), deps);
56083
+ const cached2 = readCache(cachePath, deps);
56084
+ const needsRefresh = cached2 === "miss" || cached2.stale || includeChecks && !cached2.checksQueried;
56085
+ if (needsRefresh) {
56086
+ scheduleRefresh(cwd2, cachePath, includeChecks, deps);
56087
+ }
56088
+ return cached2 === "miss" ? null : cached2.data;
56089
+ }
56090
+ function refreshGitReviewCacheFromCli(cwd2, options, lockPath, deps = DEFAULT_GIT_REVIEW_CACHE_DEPS) {
56091
+ const expectedLockPath = getRefreshLockPath(getCachePath2(cwd2, getCacheRef(cwd2, deps), deps));
56092
+ try {
56093
+ fetchGitReviewData(cwd2, deps, options);
56094
+ } finally {
56095
+ if (lockPath === expectedLockPath) {
56096
+ releaseRefreshLock(lockPath, deps);
56097
+ }
56098
+ }
56099
+ }
55948
56100
  function getGitReviewStatusLabel(state, reviewDecision) {
55949
56101
  if (state === "MERGED")
55950
56102
  return "MERGED";
@@ -55964,20 +56116,28 @@ function truncateTitle(title, maxWidth) {
55964
56116
  return title;
55965
56117
  return `${title.slice(0, limit - 1)}…`;
55966
56118
  }
55967
- var GIT_REVIEW_CACHE_TTL = 30000, CLI_TIMEOUT = 5000, DEFAULT_TITLE_MAX_WIDTH = 30, GH_PR_METADATA_FIELDS = "url,number,title,state,reviewDecision", GH_PR_WITH_CHECKS_FIELDS, DEFAULT_GIT_REVIEW_CACHE_DEPS;
56119
+ var GIT_REVIEW_CACHE_TTL = 30000, CLI_TIMEOUT = 5000, REFRESH_LOCK_STALE_MS = 30000, DEFAULT_TITLE_MAX_WIDTH = 30, GH_PR_METADATA_FIELDS = "url,number,title,state,reviewDecision", GH_PR_WITH_CHECKS_FIELDS, GIT_REVIEW_REFRESH_FLAG = "--internal-refresh-git-review-cache", DEFAULT_GIT_REVIEW_CACHE_DEPS, GitReviewDeadlineError;
55968
56120
  var init_git_review_cache = __esm(() => {
55969
56121
  init_git_remote();
55970
56122
  GH_PR_WITH_CHECKS_FIELDS = `${GH_PR_METADATA_FIELDS},statusCheckRollup`;
55971
56123
  DEFAULT_GIT_REVIEW_CACHE_DEPS = {
56124
+ closeSync,
55972
56125
  execFileSync: execFileSync2,
55973
56126
  existsSync: existsSync2,
56127
+ getExecPath: () => process.execPath,
55974
56128
  mkdirSync: mkdirSync2,
56129
+ openSync,
55975
56130
  readFileSync: readFileSync3,
56131
+ getScriptPath: () => process.argv[1],
56132
+ spawn,
55976
56133
  statSync: statSync2,
56134
+ unlinkSync,
55977
56135
  writeFileSync: writeFileSync2,
55978
56136
  getHomedir: os4.homedir,
55979
56137
  now: Date.now
55980
56138
  };
56139
+ GitReviewDeadlineError = class GitReviewDeadlineError extends Error {
56140
+ };
55981
56141
  });
55982
56142
 
55983
56143
  // src/widgets/GitCiStatus.ts
@@ -56029,7 +56189,7 @@ class GitCiStatusWidget {
56029
56189
  return isHideNoGitEnabled(item) ? null : "(no git)";
56030
56190
  }
56031
56191
  const cwd2 = this.deps.resolveGitCwd(context) ?? this.deps.getProcessCwd();
56032
- const checks3 = this.deps.fetchGitReviewData(cwd2)?.checks;
56192
+ const checks3 = this.deps.getCachedGitReviewData(cwd2, { includeChecks: true })?.checks;
56033
56193
  if (!checks3) {
56034
56194
  return NO_CHECKS;
56035
56195
  }
@@ -56057,7 +56217,7 @@ var init_GitCiStatus = __esm(() => {
56057
56217
  pending: "●"
56058
56218
  };
56059
56219
  DEFAULT_DEPS = {
56060
- fetchGitReviewData,
56220
+ getCachedGitReviewData,
56061
56221
  getProcessCwd: () => process.cwd(),
56062
56222
  isInsideGitWorkTree,
56063
56223
  resolveGitCwd
@@ -56334,7 +56494,7 @@ class GitPrWidget {
56334
56494
  return hideNoGit ? null : `(no ${resolvePrNoun(null, context, this.deps)})`;
56335
56495
  }
56336
56496
  const cwd2 = this.deps.resolveGitCwd(context) ?? this.deps.getProcessCwd();
56337
- const prData = this.deps.fetchGitReviewData(cwd2);
56497
+ const prData = this.deps.getCachedGitReviewData(cwd2, { includeChecks: context.gitReviewNeedsChecks ?? false });
56338
56498
  if (!prData) {
56339
56499
  return hideNoGit ? null : `(no ${resolvePrNoun(null, context, this.deps)})`;
56340
56500
  }
@@ -56362,7 +56522,7 @@ var init_GitPr = __esm(() => {
56362
56522
  init_hyperlink();
56363
56523
  init_git_no_git();
56364
56524
  DEFAULT_GIT_PR_WIDGET_DEPS = {
56365
- fetchGitReviewData,
56525
+ getCachedGitReviewData,
56366
56526
  getProcessCwd: () => process.cwd(),
56367
56527
  getRemoteInfo,
56368
56528
  isInsideGitWorkTree,
@@ -58127,7 +58287,7 @@ function getTerminalWidth() {
58127
58287
  function canDetectTerminalWidth() {
58128
58288
  return probeTerminalWidth() !== null;
58129
58289
  }
58130
- var __dirname = "/home/runner/work/ccstatusline/ccstatusline/src/utils", PACKAGE_VERSION = "2.2.24";
58290
+ var __dirname = "/home/runner/work/ccstatusline/ccstatusline/src/utils", PACKAGE_VERSION = "2.2.25";
58131
58291
  var init_terminal = () => {};
58132
58292
 
58133
58293
  // src/utils/format-tokens.ts
@@ -79925,6 +80085,7 @@ var StatusJSONSchema = exports_external.looseObject({
79925
80085
  init_ansi();
79926
80086
  init_colors();
79927
80087
  init_compaction();
80088
+ init_git_review_cache();
79928
80089
  await init_config();
79929
80090
 
79930
80091
  // src/utils/hook-handler.ts
@@ -80286,7 +80447,8 @@ async function renderMultipleLines(data) {
80286
80447
  terminalWidth: getTerminalWidth(),
80287
80448
  isPreview: false,
80288
80449
  minimalist: settings.minimalistMode,
80289
- gitCacheTtlSeconds: settings.gitCacheTtlSeconds
80450
+ gitCacheTtlSeconds: settings.gitCacheTtlSeconds,
80451
+ gitReviewNeedsChecks: lines.some((line) => line.some((item) => item.type === "git-ci-status"))
80290
80452
  };
80291
80453
  const preRenderedLines = preRenderAllWidgets(lines, settings, context);
80292
80454
  const preCalculatedMaxWidths = calculateMaxWidthsFromPreRendered(preRenderedLines, settings);
@@ -80361,7 +80523,24 @@ async function handleHook() {
80361
80523
  const input = await readStdin();
80362
80524
  handleHookInput(input);
80363
80525
  }
80526
+ function handleGitReviewRefresh() {
80527
+ const flagIndex = process.argv.indexOf(GIT_REVIEW_REFRESH_FLAG);
80528
+ if (flagIndex === -1) {
80529
+ return false;
80530
+ }
80531
+ const cwd2 = process.argv[flagIndex + 1];
80532
+ const mode = process.argv[flagIndex + 2];
80533
+ const lockPath = process.argv[flagIndex + 3];
80534
+ if (!cwd2 || mode !== "metadata" && mode !== "checks" || !lockPath) {
80535
+ return true;
80536
+ }
80537
+ refreshGitReviewCacheFromCli(cwd2, { includeChecks: mode === "checks" }, lockPath);
80538
+ return true;
80539
+ }
80364
80540
  async function main() {
80541
+ if (handleGitReviewRefresh()) {
80542
+ return;
80543
+ }
80365
80544
  if (process.argv.includes("--version")) {
80366
80545
  console.log(getPackageVersion());
80367
80546
  process.exit(0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccstatusline",
3
- "version": "2.2.24",
3
+ "version": "2.2.25",
4
4
  "bugs": {
5
5
  "url": "https://github.com/sirmalloc/ccstatusline/issues"
6
6
  },