@hcrosse/opencode-pr-tracker 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/tui.js CHANGED
@@ -1584,6 +1584,8 @@ var segmentPattern = /^[A-Za-z0-9._-]+$/;
1584
1584
  function parsePullRequestUrl(input) {
1585
1585
  if (input.trim() !== input)
1586
1586
  return invalidPullRequestUrl;
1587
+ if (input.includes("\\"))
1588
+ return invalidPullRequestUrl;
1587
1589
  if (!input.startsWith("https://"))
1588
1590
  return invalidPullRequestUrl;
1589
1591
  const authorityEnd = input.indexOf("/", "https://".length);
@@ -1616,15 +1618,17 @@ function parsePullRequestUrl(input) {
1616
1618
  if (segments.length !== 5 || segments[0] !== "" || segments[3] !== "pull") {
1617
1619
  return invalidPullRequestUrl;
1618
1620
  }
1619
- const owner = segments[1];
1620
- const repository = segments[2];
1621
+ const rawOwner = segments[1];
1622
+ const rawRepository = segments[2];
1621
1623
  const numberText = segments[4];
1622
- if (owner === undefined || repository === undefined || numberText === undefined || !segmentPattern.test(owner) || !segmentPattern.test(repository) || !/^\d+$/.test(numberText)) {
1624
+ if (rawOwner === undefined || rawRepository === undefined || numberText === undefined || !segmentPattern.test(rawOwner) || !segmentPattern.test(rawRepository) || !/^\d+$/.test(numberText)) {
1623
1625
  return invalidPullRequestUrl;
1624
1626
  }
1625
1627
  const number = Number(numberText);
1626
1628
  if (!Number.isSafeInteger(number) || number <= 0)
1627
1629
  return invalidPullRequestUrl;
1630
+ const owner = rawOwner.toLowerCase();
1631
+ const repository = rawRepository.toLowerCase();
1628
1632
  const url = `https://github.com/${owner}/${repository}/pull/${number}`;
1629
1633
  const value = { url, owner, repository, number };
1630
1634
  return { ok: true, value };
@@ -2313,7 +2317,7 @@ function startSessionPolling(input) {
2313
2317
  let timerRegistered = false;
2314
2318
  let stopped = false;
2315
2319
  let inFlight;
2316
- let refreshQueued = false;
2320
+ let queued;
2317
2321
  function project(attachments) {
2318
2322
  return attachments.map((attachment) => ({
2319
2323
  attachment,
@@ -2325,11 +2329,14 @@ function startSessionPolling(input) {
2325
2329
  async function poll() {
2326
2330
  const attachments = await input.store.list(input.sessionID);
2327
2331
  if (stopped)
2328
- return;
2332
+ return {
2333
+ ok: true,
2334
+ value: "stopped"
2335
+ };
2329
2336
  if (!attachments.ok) {
2330
2337
  input.publish([]);
2331
2338
  input.onStateFailure(attachments.error);
2332
- return;
2339
+ return attachments;
2333
2340
  }
2334
2341
  const attachedUrls = new Set(attachments.value.map((attachment) => attachment.pullRequest.url));
2335
2342
  for (const url of statuses.keys()) {
@@ -2337,6 +2344,11 @@ function startSessionPolling(input) {
2337
2344
  statuses.delete(url);
2338
2345
  }
2339
2346
  input.publish(project(attachments.value));
2347
+ if (attachments.value.length === 0)
2348
+ return {
2349
+ ok: true,
2350
+ value: "no_attachments"
2351
+ };
2340
2352
  const refreshable = attachments.value.filter((attachment) => {
2341
2353
  const previous = statuses.get(attachment.pullRequest.url);
2342
2354
  return previous?.tag !== "Available" || previous.state.tag !== "Merged";
@@ -2345,12 +2357,17 @@ function startSessionPolling(input) {
2345
2357
  signal: controller.signal
2346
2358
  });
2347
2359
  if (stopped)
2348
- return;
2360
+ return {
2361
+ ok: true,
2362
+ value: "stopped"
2363
+ };
2349
2364
  let batchDiagnostic;
2365
+ let failure;
2350
2366
  if (!batch.ok) {
2351
2367
  if (batch.error.tag === "GitHubCancelled")
2352
- return;
2368
+ return batch;
2353
2369
  batchDiagnostic = batch.error.tag === "GitHubBatchLimitExceeded" ? "GitHubUnavailable" : batch.error.tag;
2370
+ failure = batch.error;
2354
2371
  }
2355
2372
  for (const [index, attachment] of refreshable.entries()) {
2356
2373
  const previous = statuses.get(attachment.pullRequest.url);
@@ -2360,6 +2377,8 @@ function startSessionPolling(input) {
2360
2377
  continue;
2361
2378
  }
2362
2379
  const diagnostic = result === undefined ? batchDiagnostic ?? "GitHubUnavailable" : result.error.tag;
2380
+ if (result !== undefined && !result.ok)
2381
+ failure ??= result.error;
2363
2382
  statuses.set(attachment.pullRequest.url, previous?.tag === "Available" ? {
2364
2383
  ...previous,
2365
2384
  stale: true,
@@ -2371,24 +2390,60 @@ function startSessionPolling(input) {
2371
2390
  }
2372
2391
  if (!stopped)
2373
2392
  input.publish(project(attachments.value));
2393
+ return failure === undefined ? {
2394
+ ok: true,
2395
+ value: "refreshed"
2396
+ } : {
2397
+ ok: false,
2398
+ error: failure
2399
+ };
2400
+ }
2401
+ function startQueuedRefresh() {
2402
+ inFlight = undefined;
2403
+ const next = queued;
2404
+ queued = undefined;
2405
+ if (next === undefined)
2406
+ return;
2407
+ if (stopped) {
2408
+ next.resolve({
2409
+ ok: true,
2410
+ value: "stopped"
2411
+ });
2412
+ return;
2413
+ }
2414
+ requestRefresh().then((result) => next.resolve(result), (error) => next.reject(error));
2374
2415
  }
2375
- function refresh() {
2416
+ function requestRefresh() {
2376
2417
  if (stopped)
2377
- return Promise.resolve();
2418
+ return Promise.resolve({
2419
+ ok: true,
2420
+ value: "stopped"
2421
+ });
2378
2422
  if (inFlight) {
2379
- refreshQueued = true;
2380
- return inFlight;
2381
- }
2382
- const wrapped = poll().finally(() => {
2383
- inFlight = undefined;
2384
- if (refreshQueued && !stopped) {
2385
- refreshQueued = false;
2386
- return refresh();
2387
- }
2423
+ if (queued !== undefined)
2424
+ return queued.promise;
2425
+ let resolve;
2426
+ let reject;
2427
+ const promise = new Promise((resolvePromise, rejectPromise) => {
2428
+ resolve = resolvePromise;
2429
+ reject = rejectPromise;
2430
+ });
2431
+ queued = {
2432
+ promise,
2433
+ resolve,
2434
+ reject
2435
+ };
2436
+ return promise;
2437
+ }
2438
+ const current = poll();
2439
+ inFlight = current;
2440
+ current.then(startQueuedRefresh, startQueuedRefresh);
2441
+ return current;
2442
+ }
2443
+ function scheduledRefresh() {
2444
+ return requestRefresh().then(() => {
2388
2445
  return;
2389
2446
  });
2390
- inFlight = wrapped;
2391
- return wrapped;
2392
2447
  }
2393
2448
  return {
2394
2449
  start() {
@@ -2396,13 +2451,14 @@ function startSessionPolling(input) {
2396
2451
  return Promise.resolve();
2397
2452
  if (!timerRegistered) {
2398
2453
  timer = scheduler.setInterval(() => {
2399
- refresh().catch(input.onError);
2454
+ scheduledRefresh().catch(input.onError);
2400
2455
  }, pollIntervalMilliseconds);
2401
2456
  timerRegistered = true;
2402
2457
  }
2403
- return refresh();
2458
+ return scheduledRefresh();
2404
2459
  },
2405
- refresh,
2460
+ refresh: scheduledRefresh,
2461
+ forceRefresh: requestRefresh,
2406
2462
  stop() {
2407
2463
  if (stopped)
2408
2464
  return;
@@ -2450,7 +2506,18 @@ function createRefreshBus() {
2450
2506
  return {
2451
2507
  emit(sessionID) {
2452
2508
  for (const listener of listeners.get(sessionID) ?? [])
2453
- listener();
2509
+ listener.refresh();
2510
+ },
2511
+ async forceRefresh(sessionID) {
2512
+ const sessionListeners = listeners.get(sessionID);
2513
+ if (sessionListeners === undefined || sessionListeners.size === 0)
2514
+ return;
2515
+ const settled = await Promise.allSettled([...sessionListeners].map((listener) => listener.forceRefresh()));
2516
+ const rejected = settled.find((result) => result.status === "rejected");
2517
+ if (rejected !== undefined)
2518
+ throw rejected.reason;
2519
+ const results = settled.flatMap((result) => result.status === "fulfilled" ? [result.value] : []);
2520
+ return results.find((result) => !result.ok) ?? results.find((result) => result.ok && result.value === "refreshed") ?? results[0];
2454
2521
  },
2455
2522
  subscribe(sessionID, listener) {
2456
2523
  const sessionListeners = listeners.get(sessionID) ?? new Set;
@@ -2599,8 +2666,18 @@ function PullRequestSidebar(props) {
2599
2666
  onError: () => setFailure("Unable to refresh pull request status")
2600
2667
  });
2601
2668
  polling.start().catch(() => setFailure("Unable to refresh pull request status"));
2602
- const unsubscribe = props.refreshBus.subscribe(props.sessionID, () => {
2603
- polling.refresh().catch(() => setFailure("Unable to refresh pull request status"));
2669
+ const unsubscribe = props.refreshBus.subscribe(props.sessionID, {
2670
+ refresh() {
2671
+ polling.refresh().catch(() => setFailure("Unable to refresh pull request status"));
2672
+ },
2673
+ async forceRefresh() {
2674
+ try {
2675
+ return await polling.forceRefresh();
2676
+ } catch (error) {
2677
+ setFailure("Unable to refresh pull request status");
2678
+ throw error;
2679
+ }
2680
+ }
2604
2681
  });
2605
2682
  const onAbort = () => polling.stop();
2606
2683
  props.api.lifecycle.signal.addEventListener("abort", onAbort, {
@@ -2691,10 +2768,8 @@ function PullRequestSidebar(props) {
2691
2768
  })();
2692
2769
  }
2693
2770
  function registerTui(api, dependencies) {
2694
- const refreshBus = createRefreshBus();
2695
- api.event.on("session.updated", (event) => refreshBus.emit(event.properties.sessionID));
2696
- api.event.on("message.updated", (event) => refreshBus.emit(event.properties.sessionID));
2697
- api.event.on("message.part.updated", (event) => refreshBus.emit(event.properties.sessionID));
2771
+ const refreshBus = dependencies.refreshBus ?? createRefreshBus();
2772
+ const disposeEvents = [api.event.on("session.updated", (event) => refreshBus.emit(event.properties.sessionID)), api.event.on("message.updated", (event) => refreshBus.emit(event.properties.sessionID)), api.event.on("message.part.updated", (event) => refreshBus.emit(event.properties.sessionID))];
2698
2773
  const disposeCommands = api.keymap.registerLayer({
2699
2774
  commands: [{
2700
2775
  name: "pr.attach",
@@ -2825,10 +2900,79 @@ function registerTui(api, dependencies) {
2825
2900
  });
2826
2901
  refreshBus.emit(sessionID);
2827
2902
  }
2903
+ }, {
2904
+ name: "pr.sync",
2905
+ title: "Sync pull request status",
2906
+ category: "Plugin",
2907
+ namespace: "palette",
2908
+ slashName: "pr-sync",
2909
+ async run() {
2910
+ const sessionID = currentSessionID(api);
2911
+ if (sessionID === undefined) {
2912
+ api.ui.toast({
2913
+ variant: "warning",
2914
+ title: "Pull request tracker",
2915
+ message: "Open a session first"
2916
+ });
2917
+ return;
2918
+ }
2919
+ try {
2920
+ const result = await refreshBus.forceRefresh(sessionID);
2921
+ if (result === undefined) {
2922
+ api.ui.toast({
2923
+ variant: "warning",
2924
+ title: "Pull request tracker",
2925
+ message: "Pull request sidebar is not available"
2926
+ });
2927
+ return;
2928
+ }
2929
+ if (!result.ok) {
2930
+ api.ui.toast({
2931
+ variant: "error",
2932
+ title: "Pull request tracker",
2933
+ message: result.error.message
2934
+ });
2935
+ return;
2936
+ }
2937
+ switch (result.value) {
2938
+ case "refreshed":
2939
+ api.ui.toast({
2940
+ variant: "success",
2941
+ title: "Pull request tracker",
2942
+ message: "Pull request status synced"
2943
+ });
2944
+ return;
2945
+ case "no_attachments":
2946
+ api.ui.toast({
2947
+ variant: "info",
2948
+ title: "Pull request tracker",
2949
+ message: "No pull requests are attached"
2950
+ });
2951
+ return;
2952
+ case "stopped":
2953
+ api.ui.toast({
2954
+ variant: "error",
2955
+ title: "Pull request tracker",
2956
+ message: "Unable to refresh pull request status"
2957
+ });
2958
+ return;
2959
+ }
2960
+ } catch {
2961
+ api.ui.toast({
2962
+ variant: "error",
2963
+ title: "Pull request tracker",
2964
+ message: "Unable to refresh pull request status"
2965
+ });
2966
+ }
2967
+ }
2828
2968
  }],
2829
2969
  bindings: []
2830
2970
  });
2831
- api.lifecycle.onDispose(disposeCommands);
2971
+ api.lifecycle.onDispose(() => {
2972
+ disposeCommands();
2973
+ for (const disposeEvent of disposeEvents)
2974
+ disposeEvent();
2975
+ });
2832
2976
  api.slots.register({
2833
2977
  order: 250,
2834
2978
  slots: {
@@ -2865,4 +3009,4 @@ export {
2865
3009
  attachPullRequest
2866
3010
  };
2867
3011
 
2868
- //# debugId=73D7339550CD79BA64756E2164756E21
3012
+ //# debugId=4A2A207709F4E99D64756E2164756E21