github-issue-tower-defence-management 2.177.0 → 2.177.2

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 (41) hide show
  1. package/README.md +2 -1
  2. package/bin/adapter/entry-points/cli/index.js +1 -1
  3. package/bin/adapter/entry-points/cli/index.js.map +1 -1
  4. package/bin/adapter/entry-points/cli/projectConfig.js +15 -0
  5. package/bin/adapter/entry-points/cli/projectConfig.js.map +1 -1
  6. package/bin/adapter/entry-points/console/consoleAirplaneSnapshotApi.js +8 -1
  7. package/bin/adapter/entry-points/console/consoleAirplaneSnapshotApi.js.map +1 -1
  8. package/bin/adapter/entry-points/console/consoleGithubTokenResolver.js +14 -4
  9. package/bin/adapter/entry-points/console/consoleGithubTokenResolver.js.map +1 -1
  10. package/bin/adapter/entry-points/console/consoleReadApi.js.map +1 -1
  11. package/bin/adapter/entry-points/console/readOnlyTokenRotator.js +139 -18
  12. package/bin/adapter/entry-points/console/readOnlyTokenRotator.js.map +1 -1
  13. package/bin/adapter/entry-points/console/ui-dist/assets/{index-qne1gT7I.js → index-CmBdbC7r.js} +4 -4
  14. package/bin/adapter/entry-points/console/ui-dist/index.html +1 -1
  15. package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js +130 -33
  16. package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js.map +1 -1
  17. package/bin/adapter/repositories/issue/RestIssueRepository.js +115 -28
  18. package/bin/adapter/repositories/issue/RestIssueRepository.js.map +1 -1
  19. package/bin/domain/usecases/DependedIssueURLRestoreUseCase.js +3 -1
  20. package/bin/domain/usecases/DependedIssueURLRestoreUseCase.js.map +1 -1
  21. package/package.json +1 -1
  22. package/types/adapter/entry-points/cli/index.d.ts.map +1 -1
  23. package/types/adapter/entry-points/cli/projectConfig.d.ts +1 -0
  24. package/types/adapter/entry-points/cli/projectConfig.d.ts.map +1 -1
  25. package/types/adapter/entry-points/console/consoleAirplaneSnapshotApi.d.ts +1 -0
  26. package/types/adapter/entry-points/console/consoleAirplaneSnapshotApi.d.ts.map +1 -1
  27. package/types/adapter/entry-points/console/consoleGithubTokenResolver.d.ts +1 -0
  28. package/types/adapter/entry-points/console/consoleGithubTokenResolver.d.ts.map +1 -1
  29. package/types/adapter/entry-points/console/consoleReadApi.d.ts +1 -0
  30. package/types/adapter/entry-points/console/consoleReadApi.d.ts.map +1 -1
  31. package/types/adapter/entry-points/console/readOnlyTokenRotator.d.ts +10 -2
  32. package/types/adapter/entry-points/console/readOnlyTokenRotator.d.ts.map +1 -1
  33. package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts +9 -1
  34. package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts.map +1 -1
  35. package/types/adapter/repositories/issue/RestIssueRepository.d.ts +38 -2
  36. package/types/adapter/repositories/issue/RestIssueRepository.d.ts.map +1 -1
  37. package/types/domain/entities/Issue.d.ts +1 -0
  38. package/types/domain/entities/Issue.d.ts.map +1 -1
  39. package/types/domain/usecases/DependedIssueURLRestoreUseCase.d.ts.map +1 -1
  40. package/types/domain/usecases/adapter-interfaces/IssueRepository.d.ts +4 -1
  41. package/types/domain/usecases/adapter-interfaces/IssueRepository.d.ts.map +1 -1
@@ -6,7 +6,7 @@
6
6
  <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
7
7
  <link rel="manifest" href="/manifest.webmanifest" />
8
8
  <title>TDPM Console</title>
9
- <script type="module" crossorigin src="/assets/index-qne1gT7I.js"></script>
9
+ <script type="module" crossorigin src="/assets/index-CmBdbC7r.js"></script>
10
10
  <link rel="stylesheet" crossorigin href="/assets/index-DrLmS10d.css">
11
11
  </head>
12
12
  <body>
@@ -6,6 +6,7 @@ const ClearDependedIssueURLUseCase_1 = require("../../../domain/usecases/ClearDe
6
6
  const storyValueClassify_1 = require("../../../domain/usecases/storyGate/storyValueClassify");
7
7
  const StoryObjectMap_1 = require("../../../domain/entities/StoryObjectMap");
8
8
  const gitHubRawUrl_1 = require("./gitHubRawUrl");
9
+ const RestIssueRepository_1 = require("./RestIssueRepository");
9
10
  const ProjectIssuesCacheRepository_1 = require("../ProjectIssuesCacheRepository");
10
11
  const BaseGitHubRepository_1 = require("../BaseGitHubRepository");
11
12
  const githubGraphqlClient_1 = require("../githubGraphqlClient");
@@ -243,7 +244,8 @@ function isIssueOrPullRequestBodyResponse(value) {
243
244
  function isIssueOrPullRequestStateResponse(value) {
244
245
  return (isRecord(value) &&
245
246
  typeof value.state === 'string' &&
246
- typeof value.title === 'string');
247
+ typeof value.title === 'string' &&
248
+ (value.state_reason === undefined || isNullableString(value.state_reason)));
247
249
  }
248
250
  function isIssueCommentsResponseItem(value) {
249
251
  if (!isRecord(value))
@@ -257,6 +259,23 @@ function isIssueCommentsResponseItem(value) {
257
259
  function isIssueCommentsResponse(value) {
258
260
  return Array.isArray(value) && value.every(isIssueCommentsResponseItem);
259
261
  }
262
+ const isStoredIssueComment = (value) => isRecord(value) &&
263
+ typeof value.id === 'number' &&
264
+ typeof value.author === 'string' &&
265
+ typeof value.body === 'string' &&
266
+ typeof value.createdAt === 'string';
267
+ const isStoredIssueCommentPage = (value) => isRecord(value) &&
268
+ isNullableString(value.etag) &&
269
+ Array.isArray(value.comments) &&
270
+ value.comments.every(isStoredIssueComment);
271
+ const isStoredIssueCommentList = (value) => isRecord(value) &&
272
+ isNullableString(value.issueUpdatedAt) &&
273
+ Array.isArray(value.pages) &&
274
+ value.pages.every(isStoredIssueCommentPage);
275
+ const buildStoredIssueCommentListKey = (owner, repo, issueNumber) => `issueOrPullRequestComments/${owner}/${repo}/${issueNumber}`;
276
+ const readStoredIssueCommentListRevalidationRevision = (storedValue) => isRecord(storedValue) && typeof storedValue.revalidationRevision === 'number'
277
+ ? storedValue.revalidationRevision
278
+ : 0;
260
279
  function isIssueCommentIdResponseItem(value) {
261
280
  return isRecord(value) && typeof value.id === 'number';
262
281
  }
@@ -409,6 +428,7 @@ class ApiV3CheerioRestIssueRepository extends BaseGitHubRepository_1.BaseGitHubR
409
428
  isInProgress: (0, utils_1.normalizeFieldName)(status || '').includes('progress'),
410
429
  isClosed: item.state !== 'OPEN',
411
430
  createdAt: new Date(item.createdAt || '2000-01-01'),
431
+ updatedAt: new Date(item.updatedAt),
412
432
  author: item.author,
413
433
  closingIssueReferenceUrls: item.closingIssueReferenceUrls,
414
434
  plainCrossRepoIssueReferenceUrls: item.plainCrossRepoIssueReferenceUrls,
@@ -437,6 +457,9 @@ class ApiV3CheerioRestIssueRepository extends BaseGitHubRepository_1.BaseGitHubR
437
457
  const createdAt = !('createdAt' in issue) || typeof issue.createdAt !== 'string'
438
458
  ? new Date()
439
459
  : new Date(issue.createdAt);
460
+ const updatedAt = 'updatedAt' in issue && typeof issue.updatedAt === 'string'
461
+ ? new Date(issue.updatedAt)
462
+ : undefined;
440
463
  const closingIssueReferenceUrls = 'closingIssueReferenceUrls' in issue &&
441
464
  Array.isArray(issue.closingIssueReferenceUrls) &&
442
465
  issue.closingIssueReferenceUrls.every((url) => typeof url === 'string')
@@ -458,6 +481,7 @@ class ApiV3CheerioRestIssueRepository extends BaseGitHubRepository_1.BaseGitHubR
458
481
  nextActionDate: nextActionDate,
459
482
  completionDate50PercentConfidence: completionDate50PercentConfidence,
460
483
  createdAt: createdAt,
484
+ updatedAt: updatedAt,
461
485
  closingIssueReferenceUrls: closingIssueReferenceUrls,
462
486
  plainCrossRepoIssueReferenceUrls: plainCrossRepoIssueReferenceUrls,
463
487
  stateReason: stateReason,
@@ -583,9 +607,12 @@ class ApiV3CheerioRestIssueRepository extends BaseGitHubRepository_1.BaseGitHubR
583
607
  false);
584
608
  const cacheHasIssuesMissingStoryOptionIdSchema = cache !== null &&
585
609
  cache.issues.some((issue) => issue.story !== null && issue.storyOptionId === undefined);
610
+ const cacheHasIssuesMissingUpdatedAt = cache !== null &&
611
+ cache.issues.some((issue) => issue.updatedAt === undefined);
586
612
  const effectiveIsFullFetch = isFullFetch ||
587
613
  storyOptionsChanged ||
588
- cacheHasIssuesMissingStoryOptionIdSchema;
614
+ cacheHasIssuesMissingStoryOptionIdSchema ||
615
+ cacheHasIssuesMissingUpdatedAt;
589
616
  if (effectiveIsFullFetch) {
590
617
  const projectMetadataFellBackToCache = cache !== null && project === cache.project;
591
618
  const itemIdsKnownBeforeFetch = new Set(cache?.issues.map((issue) => issue.itemId) ?? []);
@@ -693,6 +720,7 @@ class ApiV3CheerioRestIssueRepository extends BaseGitHubRepository_1.BaseGitHubR
693
720
  isInProgress: false,
694
721
  isClosed: false,
695
722
  createdAt: now,
723
+ updatedAt: now,
696
724
  author: '',
697
725
  closingIssueReferenceUrls: [],
698
726
  plainCrossRepoIssueReferenceUrls: [],
@@ -713,7 +741,12 @@ class ApiV3CheerioRestIssueRepository extends BaseGitHubRepository_1.BaseGitHubR
713
741
  await this.restIssueRepository.updateIssueBody(issue, body);
714
742
  };
715
743
  this.updateIssueCommentBody = async (comment, body) => {
716
- await this.restIssueRepository.updateIssueCommentBody(comment, body);
744
+ const { commentedIssueNumber } = await this.restIssueRepository.updateIssueCommentBody(comment, body);
745
+ if (commentedIssueNumber === null) {
746
+ console.warn(`Stored comment list revalidation marking skipped: the commented issue of comment ${comment.commentId} in ${comment.org}/${comment.repo} is unknown`);
747
+ return;
748
+ }
749
+ await this.markStoredIssueCommentListForRevalidation(`https://github.com/${comment.org}/${comment.repo}/issues/${commentedIssueNumber}`);
717
750
  };
718
751
  this.findIssueInAllIssuesCache = async (url) => {
719
752
  const baseDir = (0, localStorageCacheDirectory_1.localStorageCacheBaseDirectory)();
@@ -988,6 +1021,26 @@ class ApiV3CheerioRestIssueRepository extends BaseGitHubRepository_1.BaseGitHubR
988
1021
  };
989
1022
  this.createComment = async (issue, comment) => {
990
1023
  await this.restIssueRepository.createComment(issue.url, comment);
1024
+ await this.markStoredIssueCommentListForRevalidation(issue.url);
1025
+ };
1026
+ this.markStoredIssueCommentListForRevalidation = async (url) => {
1027
+ try {
1028
+ const { owner, repo, issueNumber } = this.parseIssueUrl(url);
1029
+ const storedIssueCommentListKey = buildStoredIssueCommentListKey(owner, repo, issueNumber);
1030
+ await this.localStorageCacheRepository.withLock(storedIssueCommentListKey, async () => {
1031
+ const storedValue = await this.localStorageCacheRepository.getSingle(storedIssueCommentListKey);
1032
+ await this.localStorageCacheRepository.setSingle(storedIssueCommentListKey, {
1033
+ issueUpdatedAt: null,
1034
+ pages: isStoredIssueCommentList(storedValue)
1035
+ ? storedValue.pages
1036
+ : [],
1037
+ revalidationRevision: readStoredIssueCommentListRevalidationRevision(storedValue) + 1,
1038
+ });
1039
+ });
1040
+ }
1041
+ catch (error) {
1042
+ console.warn(`Stored comment list revalidation marking failed: issueUrl=${url} error=${String(error)}`);
1043
+ }
991
1044
  };
992
1045
  this.updateProjectTextField = async (project, fieldId, issue, text) => {
993
1046
  await this.logDependedIssueUrlFieldWriteLiveItemIdCrossCheck(project, fieldId, issue.url, issue.itemId, 'update');
@@ -2413,7 +2466,21 @@ class ApiV3CheerioRestIssueRepository extends BaseGitHubRepository_1.BaseGitHubR
2413
2466
  }
2414
2467
  return collectedPaths;
2415
2468
  };
2469
+ this.authenticatedUserLoginRequest = null;
2416
2470
  this.getAuthenticatedUserLogin = async () => {
2471
+ const authenticatedUserLoginRequest = this.authenticatedUserLoginRequest ?? this.fetchAuthenticatedUserLogin();
2472
+ this.authenticatedUserLoginRequest = authenticatedUserLoginRequest;
2473
+ try {
2474
+ return await authenticatedUserLoginRequest;
2475
+ }
2476
+ catch (error) {
2477
+ if (this.authenticatedUserLoginRequest === authenticatedUserLoginRequest) {
2478
+ this.authenticatedUserLoginRequest = null;
2479
+ }
2480
+ throw error;
2481
+ }
2482
+ };
2483
+ this.fetchAuthenticatedUserLogin = async () => {
2417
2484
  const response = await this.fetchWithRateLimitRetry(() => fetch('https://api.github.com/user', {
2418
2485
  method: 'GET',
2419
2486
  headers: {
@@ -2677,7 +2744,9 @@ class ApiV3CheerioRestIssueRepository extends BaseGitHubRepository_1.BaseGitHubR
2677
2744
  }
2678
2745
  };
2679
2746
  this.createCommentByUrl = async (issueOrPrUrl, commentBody) => {
2680
- return this.restIssueRepository.createComment(issueOrPrUrl, commentBody);
2747
+ const createdComment = await this.restIssueRepository.createComment(issueOrPrUrl, commentBody);
2748
+ await this.markStoredIssueCommentListForRevalidation(issueOrPrUrl);
2749
+ return createdComment;
2681
2750
  };
2682
2751
  this.fetchIssueBodyResponse = (url) => {
2683
2752
  const { owner, repo, issueNumber } = this.parseIssueUrl(url);
@@ -2714,44 +2783,56 @@ class ApiV3CheerioRestIssueRepository extends BaseGitHubRepository_1.BaseGitHubR
2714
2783
  }
2715
2784
  return this.parseIssueBodyResponse(url, response);
2716
2785
  };
2717
- this.getIssueOrPullRequestComments = async (url) => {
2786
+ this.getIssueOrPullRequestComments = async (url, options) => {
2718
2787
  const { owner, repo, issueNumber } = this.parseIssueUrl(url);
2719
- const perPage = 100;
2720
- const collectedComments = [];
2721
- let page = 1;
2722
- let hasMore = true;
2723
- while (hasMore) {
2724
- const commentsUrl = `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/issues/${issueNumber}/comments?per_page=${perPage}&page=${page}`;
2788
+ const storedIssueCommentListKey = buildStoredIssueCommentListKey(owner, repo, issueNumber);
2789
+ const issueUpdatedAt = options?.issueUpdatedAt?.toISOString() ?? null;
2790
+ const storedValue = await this.localStorageCacheRepository.getSingle(storedIssueCommentListKey);
2791
+ const storedIssueCommentList = isStoredIssueCommentList(storedValue)
2792
+ ? storedValue
2793
+ : null;
2794
+ const revalidationRevisionAtReadStart = readStoredIssueCommentListRevalidationRevision(storedValue);
2795
+ const reusableStoredComments = (0, RestIssueRepository_1.findStoredIssueCommentsReusableForIssueUpdatedAt)(storedIssueCommentList, issueUpdatedAt);
2796
+ if (reusableStoredComments !== null) {
2797
+ return reusableStoredComments;
2798
+ }
2799
+ const fetchedPages = await (0, RestIssueRepository_1.collectIssueCommentPagesReusingStoredPages)(storedIssueCommentList, async (pageNumber, storedPage) => {
2800
+ const commentsUrl = `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/issues/${issueNumber}/comments?per_page=${RestIssueRepository_1.ISSUE_COMMENTS_PER_PAGE}&page=${pageNumber}`;
2725
2801
  const response = await this.fetchWithRateLimitRetry(() => fetch(commentsUrl, {
2726
2802
  method: 'GET',
2727
2803
  headers: {
2728
2804
  Authorization: `Bearer ${this.ghToken}`,
2729
2805
  Accept: 'application/vnd.github+json',
2806
+ ...(storedPage?.etag
2807
+ ? { 'If-None-Match': storedPage.etag }
2808
+ : {}),
2730
2809
  },
2731
2810
  }), { method: 'GET', path: (0, githubRestClient_1.sanitizeRestPath)(commentsUrl) });
2732
- if (!response.ok) {
2733
- await this.throwGitHubError(`Failed to fetch comments for ${url}`, response);
2734
- }
2735
- const body = await response.json();
2736
- if (!isIssueCommentsResponse(body)) {
2737
- throw new Error(`Unexpected response shape when fetching comments for ${url}`);
2738
- }
2739
- for (const comment of body) {
2740
- collectedComments.push({
2741
- id: comment.id,
2742
- author: comment.user?.login ?? '',
2743
- body: comment.body ?? '',
2744
- createdAt: new Date(comment.created_at),
2745
- });
2746
- }
2747
- if (body.length < perPage) {
2748
- hasMore = false;
2749
- }
2750
- else {
2751
- page += 1;
2752
- }
2811
+ return response.status === 304 && storedPage !== null
2812
+ ? storedPage
2813
+ : this.parseIssueCommentsPageResponse(url, response);
2814
+ });
2815
+ await this.localStorageCacheRepository.withLock(storedIssueCommentListKey, async () => {
2816
+ const currentRevalidationRevision = readStoredIssueCommentListRevalidationRevision(await this.localStorageCacheRepository.getSingle(storedIssueCommentListKey));
2817
+ await this.localStorageCacheRepository.setSingle(storedIssueCommentListKey, {
2818
+ issueUpdatedAt: currentRevalidationRevision === revalidationRevisionAtReadStart
2819
+ ? issueUpdatedAt
2820
+ : null,
2821
+ pages: fetchedPages,
2822
+ revalidationRevision: currentRevalidationRevision,
2823
+ });
2824
+ });
2825
+ return (0, RestIssueRepository_1.convertStoredIssueCommentPagesToIssueComments)(fetchedPages);
2826
+ };
2827
+ this.parseIssueCommentsPageResponse = async (url, response) => {
2828
+ if (!response.ok) {
2829
+ await this.throwGitHubError(`Failed to fetch comments for ${url}`, response);
2753
2830
  }
2754
- return collectedComments;
2831
+ const body = await response.json();
2832
+ if (!isIssueCommentsResponse(body)) {
2833
+ throw new Error(`Unexpected response shape when fetching comments for ${url}`);
2834
+ }
2835
+ return (0, RestIssueRepository_1.convertIssueCommentResponseItemsToStoredIssueCommentPage)(response.headers.get('ETag'), body);
2755
2836
  };
2756
2837
  this.getPullRequestDetail = async (prUrl) => {
2757
2838
  const { owner, repo, issueNumber: prNumber, isPr, } = this.parseIssueUrl(prUrl);
@@ -2898,6 +2979,7 @@ class ApiV3CheerioRestIssueRepository extends BaseGitHubRepository_1.BaseGitHubR
2898
2979
  merged: body.merged,
2899
2980
  isPullRequest: true,
2900
2981
  title: body.title,
2982
+ stateReason: '',
2901
2983
  };
2902
2984
  }
2903
2985
  const issueStateUrl = `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/issues/${issueNumber}`;
@@ -2923,6 +3005,7 @@ class ApiV3CheerioRestIssueRepository extends BaseGitHubRepository_1.BaseGitHubR
2923
3005
  merged: false,
2924
3006
  isPullRequest: false,
2925
3007
  title: body.title,
3008
+ stateReason: body.state_reason ?? '',
2926
3009
  };
2927
3010
  };
2928
3011
  this.getPullRequestSummary = async (prUrl) => {
@@ -2954,6 +3037,19 @@ class ApiV3CheerioRestIssueRepository extends BaseGitHubRepository_1.BaseGitHubR
2954
3037
  };
2955
3038
  };
2956
3039
  this.deleteAllCommentsByUrl = async (issueOrPrUrl) => {
3040
+ let deletedCommentCount = 0;
3041
+ try {
3042
+ await this.deleteAllCommentsByUrlCountingDeletions(issueOrPrUrl, () => {
3043
+ deletedCommentCount += 1;
3044
+ });
3045
+ }
3046
+ finally {
3047
+ if (deletedCommentCount > 0) {
3048
+ await this.markStoredIssueCommentListForRevalidation(issueOrPrUrl);
3049
+ }
3050
+ }
3051
+ };
3052
+ this.deleteAllCommentsByUrlCountingDeletions = async (issueOrPrUrl, onCommentDeleted) => {
2957
3053
  const { owner, repo, issueNumber } = this.parseIssueUrl(issueOrPrUrl);
2958
3054
  const perPage = 100;
2959
3055
  while (true) {
@@ -2984,6 +3080,7 @@ class ApiV3CheerioRestIssueRepository extends BaseGitHubRepository_1.BaseGitHubR
2984
3080
  if (!deleteResponse.ok) {
2985
3081
  await this.throwGitHubError(`Failed to delete comment ${comment.id} on ${issueOrPrUrl}`, deleteResponse);
2986
3082
  }
3083
+ onCommentDeleted();
2987
3084
  }
2988
3085
  if (body.length < perPage) {
2989
3086
  break;