github-issue-tower-defence-management 2.6.1 → 2.8.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/CHANGELOG.md +14 -0
- package/README.md +0 -2
- package/bin/adapter/entry-points/cli/index.js +3 -6
- package/bin/adapter/entry-points/cli/index.js.map +1 -1
- package/bin/adapter/entry-points/cli/projectConfig.js +0 -3
- package/bin/adapter/entry-points/cli/projectConfig.js.map +1 -1
- package/bin/adapter/entry-points/console/consoleAirplaneSnapshotApi.js +347 -0
- package/bin/adapter/entry-points/console/consoleAirplaneSnapshotApi.js.map +1 -0
- package/bin/adapter/entry-points/console/ui-dist/assets/index-DkMqSpkk.js +88 -0
- package/bin/adapter/entry-points/console/ui-dist/index.html +1 -1
- package/bin/adapter/entry-points/console/webServer.js +28 -0
- package/bin/adapter/entry-points/console/webServer.js.map +1 -1
- package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js +1 -3
- package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js.map +1 -1
- package/bin/domain/usecases/CheckIssueReviewReadinessUseCase.js +4 -2
- package/bin/domain/usecases/CheckIssueReviewReadinessUseCase.js.map +1 -1
- package/bin/domain/usecases/HandleScheduledEventUseCase.js +1 -6
- package/bin/domain/usecases/HandleScheduledEventUseCase.js.map +1 -1
- package/bin/domain/usecases/IssueRejectionEvaluator.js +0 -23
- package/bin/domain/usecases/IssueRejectionEvaluator.js.map +1 -1
- package/bin/domain/usecases/NotifyFinishedIssuePreparationUseCase.js +39 -69
- package/bin/domain/usecases/NotifyFinishedIssuePreparationUseCase.js.map +1 -1
- package/bin/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.js +3 -2
- package/bin/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.js.map +1 -1
- package/bin/domain/usecases/RevertOrphanedPreparationUseCase.js +0 -43
- package/bin/domain/usecases/RevertOrphanedPreparationUseCase.js.map +1 -1
- package/bin/domain/usecases/autoStatusCheckComments.js +1 -11
- package/bin/domain/usecases/autoStatusCheckComments.js.map +1 -1
- package/bin/domain/usecases/extractNextStepAgentFromComments.js +14 -0
- package/bin/domain/usecases/extractNextStepAgentFromComments.js.map +1 -0
- package/bin/domain/usecases/triagerAgentName.js +7 -0
- package/bin/domain/usecases/triagerAgentName.js.map +1 -0
- package/jest.config.js +1 -0
- package/package.json +1 -1
- package/src/adapter/entry-points/cli/index.test.ts +0 -2
- package/src/adapter/entry-points/cli/index.ts +4 -10
- package/src/adapter/entry-points/cli/projectConfig.ts +0 -8
- package/src/adapter/entry-points/console/consoleAirplaneSnapshotApi.test.ts +658 -0
- package/src/adapter/entry-points/console/consoleAirplaneSnapshotApi.ts +482 -0
- package/src/adapter/entry-points/console/ui/jest-globals.cjs +26 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleFileDiff.test.tsx +10 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleFileDiff.tsx +23 -16
- package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleAirplaneModeButton.stories.tsx +56 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleAirplaneModeButton.test.tsx +111 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleAirplaneModeButton.tsx +92 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleTabList.stories.tsx +15 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleTabList.test.tsx +19 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/layout/ConsoleTabList.tsx +30 -1
- package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleErrorToast.stories.tsx +10 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleUndoToast.test.tsx +1 -3
- package/src/adapter/entry-points/console/ui/src/features/console/components/operations/ConsoleUndoToast.tsx +8 -7
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useAirplaneMode.test.ts +304 -0
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useAirplaneMode.ts +167 -0
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleActionQueue.test.ts +15 -0
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleActionQueue.ts +6 -1
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleCaches.test.ts +77 -0
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleCaches.ts +48 -3
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleFeaturesConfig.test.ts +51 -0
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleFeaturesConfig.ts +39 -0
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleTabData.test.ts +215 -0
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleTabData.ts +20 -1
- package/src/adapter/entry-points/console/ui/src/features/console/lib/airplaneSnapshot.test.ts +361 -0
- package/src/adapter/entry-points/console/ui/src/features/console/lib/airplaneSnapshot.ts +401 -0
- package/src/adapter/entry-points/console/ui/src/features/console/lib/resourceCache.ts +4 -0
- package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsolePage.test.tsx +209 -0
- package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsolePage.tsx +27 -2
- package/src/adapter/entry-points/console/ui-dist/assets/index-DkMqSpkk.js +88 -0
- package/src/adapter/entry-points/console/ui-dist/index.html +1 -1
- package/src/adapter/entry-points/console/webServer.test.ts +189 -0
- package/src/adapter/entry-points/console/webServer.ts +38 -0
- package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.ts +0 -7
- package/src/domain/usecases/CheckIssueReviewReadinessUseCase.test.ts +26 -4
- package/src/domain/usecases/CheckIssueReviewReadinessUseCase.ts +5 -3
- package/src/domain/usecases/HandleScheduledEventUseCase.test.ts +0 -28
- package/src/domain/usecases/HandleScheduledEventUseCase.ts +0 -6
- package/src/domain/usecases/IssueRejectionEvaluator.test.ts +0 -119
- package/src/domain/usecases/IssueRejectionEvaluator.ts +0 -21
- package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.test.ts +185 -327
- package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.ts +68 -138
- package/src/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.test.ts +4 -4
- package/src/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.ts +11 -5
- package/src/domain/usecases/RevertOrphanedPreparationUseCase.test.ts +0 -313
- package/src/domain/usecases/RevertOrphanedPreparationUseCase.ts +1 -102
- package/src/domain/usecases/autoStatusCheckComments.ts +0 -17
- package/src/domain/usecases/extractNextStepAgentFromComments.ts +13 -0
- package/src/domain/usecases/isHumanComment.test.ts +0 -13
- package/src/domain/usecases/triagerAgentName.ts +4 -0
- package/types/adapter/entry-points/cli/index.d.ts.map +1 -1
- package/types/adapter/entry-points/cli/projectConfig.d.ts +0 -1
- package/types/adapter/entry-points/cli/projectConfig.d.ts.map +1 -1
- package/types/adapter/entry-points/console/consoleAirplaneSnapshotApi.d.ts +90 -0
- package/types/adapter/entry-points/console/consoleAirplaneSnapshotApi.d.ts.map +1 -0
- package/types/adapter/entry-points/console/webServer.d.ts +1 -0
- package/types/adapter/entry-points/console/webServer.d.ts.map +1 -1
- package/types/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.d.ts.map +1 -1
- package/types/domain/usecases/CheckIssueReviewReadinessUseCase.d.ts.map +1 -1
- package/types/domain/usecases/HandleScheduledEventUseCase.d.ts +1 -3
- package/types/domain/usecases/HandleScheduledEventUseCase.d.ts.map +1 -1
- package/types/domain/usecases/IssueRejectionEvaluator.d.ts +0 -1
- package/types/domain/usecases/IssueRejectionEvaluator.d.ts.map +1 -1
- package/types/domain/usecases/NotifyFinishedIssuePreparationUseCase.d.ts +0 -1
- package/types/domain/usecases/NotifyFinishedIssuePreparationUseCase.d.ts.map +1 -1
- package/types/domain/usecases/RevertNotReadyReviewQueueIssueUseCase.d.ts.map +1 -1
- package/types/domain/usecases/RevertOrphanedPreparationUseCase.d.ts +0 -1
- package/types/domain/usecases/RevertOrphanedPreparationUseCase.d.ts.map +1 -1
- package/types/domain/usecases/autoStatusCheckComments.d.ts +0 -4
- package/types/domain/usecases/autoStatusCheckComments.d.ts.map +1 -1
- package/types/domain/usecases/extractNextStepAgentFromComments.d.ts +5 -0
- package/types/domain/usecases/extractNextStepAgentFromComments.d.ts.map +1 -0
- package/types/domain/usecases/triagerAgentName.d.ts +3 -0
- package/types/domain/usecases/triagerAgentName.d.ts.map +1 -0
- package/bin/adapter/entry-points/console/ui-dist/assets/index-CaxYBHiT.js +0 -86
- package/src/adapter/entry-points/console/ui-dist/assets/index-CaxYBHiT.js +0 -86
- package/src/domain/usecases/TriagerApprovalDispatchUseCase.test.ts +0 -1051
- package/src/domain/usecases/TriagerApprovalDispatchUseCase.ts +0 -274
- package/src/domain/usecases/awaitingOwnerApprovalMessage.ts +0 -5
- package/src/domain/usecases/isPullRequestDeclaredUnnecessary.test.ts +0 -162
- package/src/domain/usecases/isPullRequestDeclaredUnnecessary.ts +0 -29
- package/src/domain/usecases/isWaitingForOwnerApproval.test.ts +0 -91
- package/src/domain/usecases/isWaitingForOwnerApproval.ts +0 -17
- package/src/domain/usecases/returnedToAwaitingWorkspaceMessage.ts +0 -5
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>TDPM Console</title>
|
|
7
|
-
<script type="module" crossorigin src="/assets/index-
|
|
7
|
+
<script type="module" crossorigin src="/assets/index-DkMqSpkk.js"></script>
|
|
8
8
|
<link rel="stylesheet" crossorigin href="/assets/index-CLHvrafO.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
@@ -1187,6 +1187,91 @@ describe('webServer new routes integration', () => {
|
|
|
1187
1187
|
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
1188
1188
|
}
|
|
1189
1189
|
});
|
|
1190
|
+
|
|
1191
|
+
it('routes POST /api/reorderstory to handleReorderStory and calls resolveProjectRepository.updateStoryList', async () => {
|
|
1192
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'console-server-'));
|
|
1193
|
+
const updateStoryList = jest.fn().mockResolvedValue([]);
|
|
1194
|
+
const projectWithTwoStories: Project = {
|
|
1195
|
+
...mock<Project>(),
|
|
1196
|
+
id: 'PVT_1',
|
|
1197
|
+
url: 'https://github.com/orgs/acme-labs/projects/1',
|
|
1198
|
+
status: {
|
|
1199
|
+
name: 'Status',
|
|
1200
|
+
fieldId: 'statusField',
|
|
1201
|
+
statuses: [
|
|
1202
|
+
{
|
|
1203
|
+
id: 'status_aw',
|
|
1204
|
+
name: 'Awaiting workspace',
|
|
1205
|
+
color: 'GRAY',
|
|
1206
|
+
description: '',
|
|
1207
|
+
},
|
|
1208
|
+
],
|
|
1209
|
+
},
|
|
1210
|
+
story: {
|
|
1211
|
+
name: 'Story',
|
|
1212
|
+
fieldId: 'storyField',
|
|
1213
|
+
databaseId: 1,
|
|
1214
|
+
stories: [
|
|
1215
|
+
{
|
|
1216
|
+
id: 'opt_first',
|
|
1217
|
+
name: 'First story',
|
|
1218
|
+
color: 'BLUE',
|
|
1219
|
+
description: '',
|
|
1220
|
+
},
|
|
1221
|
+
{
|
|
1222
|
+
id: 'opt_second',
|
|
1223
|
+
name: 'Second story',
|
|
1224
|
+
color: 'GREEN',
|
|
1225
|
+
description: '',
|
|
1226
|
+
},
|
|
1227
|
+
],
|
|
1228
|
+
workflowManagementStory: { id: 'wms', name: 'workflow' },
|
|
1229
|
+
},
|
|
1230
|
+
};
|
|
1231
|
+
const issueRepository = mock<IssueRepository>();
|
|
1232
|
+
const server = await startWebServer({
|
|
1233
|
+
accessToken: testToken,
|
|
1234
|
+
uiDistDir: path.join(tmpDir, 'ui-dist'),
|
|
1235
|
+
consoleDataOutputDir: null,
|
|
1236
|
+
inTmuxDataDir: null,
|
|
1237
|
+
dashboardDir: null,
|
|
1238
|
+
dashboardDataDir: null,
|
|
1239
|
+
dashboardProjectNames: [],
|
|
1240
|
+
issueRepository,
|
|
1241
|
+
resolveProject: async (pjcode) =>
|
|
1242
|
+
pjcode === 'acme' ? { pjcode, project: projectWithTwoStories } : null,
|
|
1243
|
+
isPjcodeConfigured: (pjcode) => pjcode === 'acme',
|
|
1244
|
+
resolveProjectRepository: () => ({ updateStoryList }),
|
|
1245
|
+
port: 0,
|
|
1246
|
+
});
|
|
1247
|
+
try {
|
|
1248
|
+
const response = await request(
|
|
1249
|
+
server,
|
|
1250
|
+
'POST',
|
|
1251
|
+
`/api/reorderstory?k=${testToken}`,
|
|
1252
|
+
{ pjcode: 'acme', storyOptionId: 'opt_first', direction: 'down' },
|
|
1253
|
+
);
|
|
1254
|
+
expect(response.statusCode).toBe(200);
|
|
1255
|
+
expect(JSON.parse(response.body)).toEqual({ ok: true });
|
|
1256
|
+
expect(updateStoryList).toHaveBeenCalledWith(projectWithTwoStories, [
|
|
1257
|
+
{
|
|
1258
|
+
id: 'opt_second',
|
|
1259
|
+
name: 'Second story',
|
|
1260
|
+
color: 'GREEN',
|
|
1261
|
+
description: '',
|
|
1262
|
+
},
|
|
1263
|
+
{
|
|
1264
|
+
id: 'opt_first',
|
|
1265
|
+
name: 'First story',
|
|
1266
|
+
color: 'BLUE',
|
|
1267
|
+
description: '',
|
|
1268
|
+
},
|
|
1269
|
+
]);
|
|
1270
|
+
} finally {
|
|
1271
|
+
await closeServer(server);
|
|
1272
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
1273
|
+
}
|
|
1274
|
+
});
|
|
1190
1275
|
});
|
|
1191
1276
|
|
|
1192
1277
|
describe('resolveFlatInTmuxFilePath', () => {
|
|
@@ -2720,4 +2805,108 @@ describe('webServer GET /api/projects', () => {
|
|
|
2720
2805
|
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
2721
2806
|
}
|
|
2722
2807
|
});
|
|
2808
|
+
|
|
2809
|
+
it('GET /api/features returns { airplaneMode: false } when enableAirplaneMode is absent', async () => {
|
|
2810
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'console-server-'));
|
|
2811
|
+
const server = await startWebServer({
|
|
2812
|
+
accessToken: testToken,
|
|
2813
|
+
uiDistDir: path.join(tmpDir, 'ui-dist'),
|
|
2814
|
+
consoleDataOutputDir: null,
|
|
2815
|
+
inTmuxDataDir: null,
|
|
2816
|
+
dashboardDir: null,
|
|
2817
|
+
dashboardDataDir: null,
|
|
2818
|
+
dashboardProjectNames: [],
|
|
2819
|
+
port: 0,
|
|
2820
|
+
});
|
|
2821
|
+
try {
|
|
2822
|
+
const response = await request(
|
|
2823
|
+
server,
|
|
2824
|
+
'GET',
|
|
2825
|
+
`/api/features?k=${testToken}`,
|
|
2826
|
+
);
|
|
2827
|
+
expect(response.statusCode).toBe(200);
|
|
2828
|
+
expect(JSON.parse(response.body)).toEqual({ airplaneMode: false });
|
|
2829
|
+
} finally {
|
|
2830
|
+
await closeServer(server);
|
|
2831
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
2832
|
+
}
|
|
2833
|
+
});
|
|
2834
|
+
|
|
2835
|
+
it('GET /api/features returns { airplaneMode: true } when enableAirplaneMode is true', async () => {
|
|
2836
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'console-server-'));
|
|
2837
|
+
const server = await startWebServer({
|
|
2838
|
+
accessToken: testToken,
|
|
2839
|
+
uiDistDir: path.join(tmpDir, 'ui-dist'),
|
|
2840
|
+
consoleDataOutputDir: null,
|
|
2841
|
+
inTmuxDataDir: null,
|
|
2842
|
+
dashboardDir: null,
|
|
2843
|
+
dashboardDataDir: null,
|
|
2844
|
+
dashboardProjectNames: [],
|
|
2845
|
+
enableAirplaneMode: true,
|
|
2846
|
+
port: 0,
|
|
2847
|
+
});
|
|
2848
|
+
try {
|
|
2849
|
+
const response = await request(
|
|
2850
|
+
server,
|
|
2851
|
+
'GET',
|
|
2852
|
+
`/api/features?k=${testToken}`,
|
|
2853
|
+
);
|
|
2854
|
+
expect(response.statusCode).toBe(200);
|
|
2855
|
+
expect(JSON.parse(response.body)).toEqual({ airplaneMode: true });
|
|
2856
|
+
} finally {
|
|
2857
|
+
await closeServer(server);
|
|
2858
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
2859
|
+
}
|
|
2860
|
+
});
|
|
2861
|
+
|
|
2862
|
+
it('GET /api/airplanesync returns 404 when enableAirplaneMode is absent', async () => {
|
|
2863
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'console-server-'));
|
|
2864
|
+
const server = await startWebServer({
|
|
2865
|
+
accessToken: testToken,
|
|
2866
|
+
uiDistDir: path.join(tmpDir, 'ui-dist'),
|
|
2867
|
+
consoleDataOutputDir: null,
|
|
2868
|
+
inTmuxDataDir: null,
|
|
2869
|
+
dashboardDir: null,
|
|
2870
|
+
dashboardDataDir: null,
|
|
2871
|
+
dashboardProjectNames: [],
|
|
2872
|
+
port: 0,
|
|
2873
|
+
});
|
|
2874
|
+
try {
|
|
2875
|
+
const response = await request(
|
|
2876
|
+
server,
|
|
2877
|
+
'GET',
|
|
2878
|
+
`/api/airplanesync?k=${testToken}`,
|
|
2879
|
+
);
|
|
2880
|
+
expect(response.statusCode).toBe(404);
|
|
2881
|
+
} finally {
|
|
2882
|
+
await closeServer(server);
|
|
2883
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
2884
|
+
}
|
|
2885
|
+
});
|
|
2886
|
+
|
|
2887
|
+
it('GET /api/airplanesync returns 404 when enableAirplaneMode is false', async () => {
|
|
2888
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'console-server-'));
|
|
2889
|
+
const server = await startWebServer({
|
|
2890
|
+
accessToken: testToken,
|
|
2891
|
+
uiDistDir: path.join(tmpDir, 'ui-dist'),
|
|
2892
|
+
consoleDataOutputDir: null,
|
|
2893
|
+
inTmuxDataDir: null,
|
|
2894
|
+
dashboardDir: null,
|
|
2895
|
+
dashboardDataDir: null,
|
|
2896
|
+
dashboardProjectNames: [],
|
|
2897
|
+
enableAirplaneMode: false,
|
|
2898
|
+
port: 0,
|
|
2899
|
+
});
|
|
2900
|
+
try {
|
|
2901
|
+
const response = await request(
|
|
2902
|
+
server,
|
|
2903
|
+
'GET',
|
|
2904
|
+
`/api/airplanesync?k=${testToken}`,
|
|
2905
|
+
);
|
|
2906
|
+
expect(response.statusCode).toBe(404);
|
|
2907
|
+
} finally {
|
|
2908
|
+
await closeServer(server);
|
|
2909
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
2910
|
+
}
|
|
2911
|
+
});
|
|
2723
2912
|
});
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
handlePullRequestStatus,
|
|
21
21
|
handleRelatedPrs,
|
|
22
22
|
} from './consoleReadApi';
|
|
23
|
+
import { handleAirplaneSync } from './consoleAirplaneSnapshotApi';
|
|
23
24
|
import {
|
|
24
25
|
ConsoleIssueRepositoryResolver,
|
|
25
26
|
ConsoleOperationContext,
|
|
@@ -243,6 +244,7 @@ export type WebServerOptions = {
|
|
|
243
244
|
((pjcode: string, updatedProject: Project) => void) | null;
|
|
244
245
|
issueTitleStateCache?: IssueTitleStateCache | null;
|
|
245
246
|
pullRequestStatusCache?: PullRequestStatusCache | null;
|
|
247
|
+
enableAirplaneMode?: boolean;
|
|
246
248
|
};
|
|
247
249
|
|
|
248
250
|
const FLAT_IN_TMUX_PREFIX = '/in-tmux-by-human/';
|
|
@@ -662,6 +664,42 @@ const handleTokenedRequest = async (
|
|
|
662
664
|
await handleImageProxy(options, response, searchParams);
|
|
663
665
|
return;
|
|
664
666
|
}
|
|
667
|
+
if (requestPath === '/api/features') {
|
|
668
|
+
sendJson(response, 200, {
|
|
669
|
+
airplaneMode: options.enableAirplaneMode === true,
|
|
670
|
+
});
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
if (requestPath === '/api/airplanesync') {
|
|
674
|
+
if (options.enableAirplaneMode !== true) {
|
|
675
|
+
sendNotFound(response);
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
const issueRepository = options.issueRepository ?? null;
|
|
679
|
+
const consoleDataOutputDir = options.consoleDataOutputDir ?? null;
|
|
680
|
+
const issueTitleStateCache = options.issueTitleStateCache ?? null;
|
|
681
|
+
const pullRequestStatusCache = options.pullRequestStatusCache ?? null;
|
|
682
|
+
if (
|
|
683
|
+
issueRepository === null ||
|
|
684
|
+
consoleDataOutputDir === null ||
|
|
685
|
+
issueTitleStateCache === null ||
|
|
686
|
+
pullRequestStatusCache === null
|
|
687
|
+
) {
|
|
688
|
+
sendNotFound(response);
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
await handleAirplaneSync(
|
|
692
|
+
response,
|
|
693
|
+
consoleDataOutputDir,
|
|
694
|
+
issueRepository,
|
|
695
|
+
issueTitleStateCache,
|
|
696
|
+
pullRequestStatusCache,
|
|
697
|
+
options.resolveGithubToken != null
|
|
698
|
+
? options.resolveGithubToken('')
|
|
699
|
+
: null,
|
|
700
|
+
);
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
665
703
|
const readResult = await handleReadApi(
|
|
666
704
|
options,
|
|
667
705
|
requestPath,
|
|
@@ -74,7 +74,6 @@ import { ProxyRateLimitCacheRepository } from '../../repositories/ProxyRateLimit
|
|
|
74
74
|
import { UpdateRateLimitCacheUseCase } from '../../../domain/usecases/UpdateRateLimitCacheUseCase';
|
|
75
75
|
import { RevertOrphanedPreparationUseCase } from '../../../domain/usecases/RevertOrphanedPreparationUseCase';
|
|
76
76
|
import { RevertNotReadyReviewQueueIssueUseCase } from '../../../domain/usecases/RevertNotReadyReviewQueueIssueUseCase';
|
|
77
|
-
import { TriagerApprovalDispatchUseCase } from '../../../domain/usecases/TriagerApprovalDispatchUseCase';
|
|
78
77
|
import { AgentDesignationLabelAdoptUseCase } from '../../../domain/usecases/AgentDesignationLabelAdoptUseCase';
|
|
79
78
|
import { GitHubIssueCommentRepository } from '../../repositories/GitHubIssueCommentRepository';
|
|
80
79
|
import { ProjectRequiredFieldCreateUseCase } from '../../../domain/usecases/ProjectRequiredFieldCreateUseCase';
|
|
@@ -528,11 +527,6 @@ export class HandleScheduledEventUseCaseHandler {
|
|
|
528
527
|
issueRepository,
|
|
529
528
|
issueCommentRepository,
|
|
530
529
|
);
|
|
531
|
-
const triagerApprovalDispatchUseCase = new TriagerApprovalDispatchUseCase(
|
|
532
|
-
projectRepository,
|
|
533
|
-
issueRepository,
|
|
534
|
-
issueCommentRepository,
|
|
535
|
-
);
|
|
536
530
|
const agentDesignationLabelAdoptUseCase =
|
|
537
531
|
new AgentDesignationLabelAdoptUseCase(projectRepository, issueRepository);
|
|
538
532
|
|
|
@@ -575,7 +569,6 @@ export class HandleScheduledEventUseCaseHandler {
|
|
|
575
569
|
revertOrphanedPreparationUseCase,
|
|
576
570
|
conflictedIssueRevertUseCase,
|
|
577
571
|
revertNotReadyReviewQueueIssueUseCase,
|
|
578
|
-
triagerApprovalDispatchUseCase,
|
|
579
572
|
agentDesignationLabelAdoptUseCase,
|
|
580
573
|
updateRateLimitCacheUseCase,
|
|
581
574
|
dailySecurityScanUseCase,
|
|
@@ -212,13 +212,13 @@ describe('CheckIssueReviewReadinessUseCase', () => {
|
|
|
212
212
|
});
|
|
213
213
|
});
|
|
214
214
|
|
|
215
|
-
it('should return reviewReady=true when the last report
|
|
215
|
+
it('should return reviewReady=true when the last report designates the triager and no related PR exists', async () => {
|
|
216
216
|
const issue = createMockIssue();
|
|
217
217
|
mockIssueRepository.getIssueByUrl.mockResolvedValue(issue);
|
|
218
218
|
mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
|
|
219
219
|
createMockComment({
|
|
220
220
|
content:
|
|
221
|
-
'From: :robot: Agent report\n```json\n{"
|
|
221
|
+
'From: :robot: Agent report\n```json\n{"nextStepAgent": "triager"}\n```',
|
|
222
222
|
}),
|
|
223
223
|
]);
|
|
224
224
|
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([]);
|
|
@@ -232,13 +232,35 @@ describe('CheckIssueReviewReadinessUseCase', () => {
|
|
|
232
232
|
expect(result.reviewReady).toBe(true);
|
|
233
233
|
});
|
|
234
234
|
|
|
235
|
-
it('should keep
|
|
235
|
+
it('should keep rejecting a missing PR when the last report designates an agent other than the triager', async () => {
|
|
236
236
|
const issue = createMockIssue();
|
|
237
237
|
mockIssueRepository.getIssueByUrl.mockResolvedValue(issue);
|
|
238
238
|
mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
|
|
239
239
|
createMockComment({
|
|
240
240
|
content:
|
|
241
|
-
'From: :robot: Agent report\n```json\n{"
|
|
241
|
+
'From: :robot: Agent report\n```json\n{"nextStepAgent": "developer"}\n```',
|
|
242
|
+
}),
|
|
243
|
+
]);
|
|
244
|
+
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([]);
|
|
245
|
+
|
|
246
|
+
const result = await useCase.run({
|
|
247
|
+
issueUrl: 'https://github.com/user/repo/issues/1',
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
expect(result.reviewReady).toBe(false);
|
|
251
|
+
expect(result.rejections).toContainEqual({
|
|
252
|
+
type: 'PULL_REQUEST_NOT_FOUND',
|
|
253
|
+
detail: 'PULL_REQUEST_NOT_FOUND',
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it('should keep every rejection other than PULL_REQUEST_NOT_FOUND when the last report designates the triager', async () => {
|
|
258
|
+
const issue = createMockIssue();
|
|
259
|
+
mockIssueRepository.getIssueByUrl.mockResolvedValue(issue);
|
|
260
|
+
mockIssueCommentRepository.getCommentsFromIssue.mockResolvedValue([
|
|
261
|
+
createMockComment({
|
|
262
|
+
content:
|
|
263
|
+
'From: :robot: Agent report\n```json\n{"nextStepAgent": "triager"}\n```',
|
|
242
264
|
}),
|
|
243
265
|
]);
|
|
244
266
|
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
|
|
@@ -5,10 +5,11 @@ import {
|
|
|
5
5
|
PrRejectedReasonType,
|
|
6
6
|
} from './IssueRejectionEvaluator';
|
|
7
7
|
import { resolveLabelsNotRequiringPullRequest } from './resolveLabelsNotRequiringPullRequest';
|
|
8
|
-
import { isPullRequestDeclaredUnnecessary } from './isPullRequestDeclaredUnnecessary';
|
|
9
8
|
import { normalizeReportBody } from './normalizeReportBody';
|
|
10
9
|
import { isAgentReportBody } from './isAgentReportBody';
|
|
11
10
|
import { isAuthorAuthorizedForAutoStatusCheck } from './isAuthorAuthorizedForAutoStatusCheck';
|
|
11
|
+
import { extractNextStepAgentFromComments } from './extractNextStepAgentFromComments';
|
|
12
|
+
import { isTriagerAgentName } from './triagerAgentName';
|
|
12
13
|
|
|
13
14
|
type RejectedReasonType =
|
|
14
15
|
| 'ISSUE_NOT_FOUND'
|
|
@@ -94,10 +95,11 @@ export class CheckIssueReviewReadinessUseCase {
|
|
|
94
95
|
{ developerAgentName: params.developerAgentName },
|
|
95
96
|
);
|
|
96
97
|
|
|
97
|
-
const
|
|
98
|
+
const nextStepAgent = extractNextStepAgentFromComments(
|
|
98
99
|
comments,
|
|
99
100
|
isTrustedAuthor,
|
|
100
|
-
)
|
|
101
|
+
);
|
|
102
|
+
const requiredPrRejections = isTriagerAgentName(nextStepAgent)
|
|
101
103
|
? prRejections.filter(
|
|
102
104
|
(rejection) => rejection.type !== 'PULL_REQUEST_NOT_FOUND',
|
|
103
105
|
)
|
|
@@ -25,7 +25,6 @@ import { StartPreparationUseCase } from './StartPreparationUseCase';
|
|
|
25
25
|
import { RevertOrphanedPreparationUseCase } from './RevertOrphanedPreparationUseCase';
|
|
26
26
|
import { ConflictedIssueRevertUseCase } from './ConflictedIssueRevertUseCase';
|
|
27
27
|
import { RevertNotReadyReviewQueueIssueUseCase } from './RevertNotReadyReviewQueueIssueUseCase';
|
|
28
|
-
import { TriagerApprovalDispatchUseCase } from './TriagerApprovalDispatchUseCase';
|
|
29
28
|
import { AgentDesignationLabelAdoptUseCase } from './AgentDesignationLabelAdoptUseCase';
|
|
30
29
|
import { ProjectRequiredFieldCreateUseCase } from './ProjectRequiredFieldCreateUseCase';
|
|
31
30
|
import { SetupTowerDefenceProjectUseCase } from './SetupTowerDefenceProjectUseCase';
|
|
@@ -129,8 +128,6 @@ describe('HandleScheduledEventUseCase', () => {
|
|
|
129
128
|
mock<ConflictedIssueRevertUseCase>();
|
|
130
129
|
const mockRevertNotReadyReviewQueueIssueUseCase =
|
|
131
130
|
mock<RevertNotReadyReviewQueueIssueUseCase>();
|
|
132
|
-
const mockTriagerApprovalDispatchUseCase =
|
|
133
|
-
mock<TriagerApprovalDispatchUseCase>();
|
|
134
131
|
const mockAgentDesignationLabelAdoptUseCase =
|
|
135
132
|
mock<AgentDesignationLabelAdoptUseCase>();
|
|
136
133
|
const mockUpdateRateLimitCacheUseCase = mock<UpdateRateLimitCacheUseCase>();
|
|
@@ -165,7 +162,6 @@ describe('HandleScheduledEventUseCase', () => {
|
|
|
165
162
|
mockRevertOrphanedPreparationUseCase,
|
|
166
163
|
mockConflictedIssueRevertUseCase,
|
|
167
164
|
mockRevertNotReadyReviewQueueIssueUseCase,
|
|
168
|
-
mockTriagerApprovalDispatchUseCase,
|
|
169
165
|
mockAgentDesignationLabelAdoptUseCase,
|
|
170
166
|
mockUpdateRateLimitCacheUseCase,
|
|
171
167
|
mockDailySecurityScanUseCase,
|
|
@@ -282,29 +278,6 @@ describe('HandleScheduledEventUseCase', () => {
|
|
|
282
278
|
});
|
|
283
279
|
});
|
|
284
280
|
|
|
285
|
-
it('should call TriagerApprovalDispatchUseCase with project URL', async () => {
|
|
286
|
-
const input = {
|
|
287
|
-
projectName: 'test-project',
|
|
288
|
-
org: 'test-org',
|
|
289
|
-
projectUrl: 'https://github.com/test-org/test-project',
|
|
290
|
-
manager: 'test-manager',
|
|
291
|
-
workingReport: {
|
|
292
|
-
repo: 'test-repo',
|
|
293
|
-
members: ['member1'],
|
|
294
|
-
spreadsheetUrl: 'https://docs.google.com/spreadsheets/test',
|
|
295
|
-
},
|
|
296
|
-
urlOfStoryView: 'https://github.com/test-org/test-project/issues',
|
|
297
|
-
disabled: false,
|
|
298
|
-
};
|
|
299
|
-
|
|
300
|
-
await useCase.run(input);
|
|
301
|
-
expect(mockTriagerApprovalDispatchUseCase.run).toHaveBeenCalledWith(
|
|
302
|
-
expect.objectContaining({
|
|
303
|
-
projectUrl: 'https://github.com/test-org/test-project',
|
|
304
|
-
}),
|
|
305
|
-
);
|
|
306
|
-
});
|
|
307
|
-
|
|
308
281
|
it('should return null and skip all processing when disabled is true', async () => {
|
|
309
282
|
const input = {
|
|
310
283
|
projectName: 'test-project',
|
|
@@ -1827,7 +1800,6 @@ describe('HandleScheduledEventUseCase', () => {
|
|
|
1827
1800
|
mock<RevertOrphanedPreparationUseCase>(),
|
|
1828
1801
|
mock<ConflictedIssueRevertUseCase>(),
|
|
1829
1802
|
mock<RevertNotReadyReviewQueueIssueUseCase>(),
|
|
1830
|
-
mock<TriagerApprovalDispatchUseCase>(),
|
|
1831
1803
|
mock<AgentDesignationLabelAdoptUseCase>(),
|
|
1832
1804
|
null,
|
|
1833
1805
|
null,
|
|
@@ -28,7 +28,6 @@ import {
|
|
|
28
28
|
import { AgentDesignationLabelAdoptUseCase } from './AgentDesignationLabelAdoptUseCase';
|
|
29
29
|
import { RevertOrphanedPreparationUseCase } from './RevertOrphanedPreparationUseCase';
|
|
30
30
|
import { RevertNotReadyReviewQueueIssueUseCase } from './RevertNotReadyReviewQueueIssueUseCase';
|
|
31
|
-
import { TriagerApprovalDispatchUseCase } from './TriagerApprovalDispatchUseCase';
|
|
32
31
|
import { isRecord } from './isRecord';
|
|
33
32
|
import { resolveLabelsAsLlmAgentName } from './resolveLabelsAsLlmAgentName';
|
|
34
33
|
import { resolveAllowedIssueAuthors } from './resolveAllowedIssueAuthors';
|
|
@@ -139,7 +138,6 @@ export class HandleScheduledEventUseCase {
|
|
|
139
138
|
readonly revertOrphanedPreparationUseCase: RevertOrphanedPreparationUseCase,
|
|
140
139
|
readonly conflictedIssueRevertUseCase: ConflictedIssueRevertUseCase,
|
|
141
140
|
readonly revertNotReadyReviewQueueIssueUseCase: RevertNotReadyReviewQueueIssueUseCase,
|
|
142
|
-
readonly triagerApprovalDispatchUseCase: TriagerApprovalDispatchUseCase,
|
|
143
141
|
readonly agentDesignationLabelAdoptUseCase: AgentDesignationLabelAdoptUseCase,
|
|
144
142
|
readonly updateRateLimitCacheUseCase: UpdateRateLimitCacheUseCase | null,
|
|
145
143
|
readonly dailySecurityScanUseCase: DailySecurityScanUseCase | null,
|
|
@@ -452,10 +450,6 @@ ${JSON.stringify(e)}
|
|
|
452
450
|
allowedIssueAuthors,
|
|
453
451
|
developerAgentName: input.developerAgentName,
|
|
454
452
|
});
|
|
455
|
-
await this.triagerApprovalDispatchUseCase.run({
|
|
456
|
-
projectUrl: input.projectUrl,
|
|
457
|
-
allowedIssueAuthors,
|
|
458
|
-
});
|
|
459
453
|
if (this.dailySecurityScanUseCase !== null && input.dailySecurityScan) {
|
|
460
454
|
await this.dailySecurityScanUseCase.run({
|
|
461
455
|
targetDates: targetDateTimes,
|
|
@@ -739,125 +739,6 @@ describe('IssueRejectionEvaluator', () => {
|
|
|
739
739
|
});
|
|
740
740
|
});
|
|
741
741
|
|
|
742
|
-
describe('pullRequestRequired: false in issue body', () => {
|
|
743
|
-
it('should not reject and not call findRelatedOpenPRs when body ends with JSON block containing pullRequestRequired: false', async () => {
|
|
744
|
-
const body = '```json\n{"pullRequestRequired": false}\n```';
|
|
745
|
-
|
|
746
|
-
const result = await evaluator.evaluate({
|
|
747
|
-
url: 'https://github.com/user/repo/issues/1',
|
|
748
|
-
labels: [],
|
|
749
|
-
isPr: false,
|
|
750
|
-
body,
|
|
751
|
-
});
|
|
752
|
-
|
|
753
|
-
expect(mockIssueRepository.findRelatedOpenPRs).not.toHaveBeenCalled();
|
|
754
|
-
expect(result.rejections).toHaveLength(0);
|
|
755
|
-
expect(result.approvedPrUrl).toBeNull();
|
|
756
|
-
});
|
|
757
|
-
|
|
758
|
-
it('should not reject even with trailing whitespace after the closing JSON block', async () => {
|
|
759
|
-
const body = '```json\n{"pullRequestRequired": false}\n```\n ';
|
|
760
|
-
|
|
761
|
-
const result = await evaluator.evaluate({
|
|
762
|
-
url: 'https://github.com/user/repo/issues/1',
|
|
763
|
-
labels: [],
|
|
764
|
-
isPr: false,
|
|
765
|
-
body,
|
|
766
|
-
});
|
|
767
|
-
|
|
768
|
-
expect(mockIssueRepository.findRelatedOpenPRs).not.toHaveBeenCalled();
|
|
769
|
-
expect(result.rejections).toHaveLength(0);
|
|
770
|
-
});
|
|
771
|
-
|
|
772
|
-
it('should still require PR when body has pullRequestRequired: true', async () => {
|
|
773
|
-
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([]);
|
|
774
|
-
const body = '```json\n{"pullRequestRequired": true}\n```';
|
|
775
|
-
|
|
776
|
-
const result = await evaluator.evaluate({
|
|
777
|
-
url: 'https://github.com/user/repo/issues/1',
|
|
778
|
-
labels: [],
|
|
779
|
-
isPr: false,
|
|
780
|
-
body,
|
|
781
|
-
});
|
|
782
|
-
|
|
783
|
-
expect(result.rejections).toHaveLength(1);
|
|
784
|
-
expect(result.rejections[0].type).toBe('PULL_REQUEST_NOT_FOUND');
|
|
785
|
-
});
|
|
786
|
-
|
|
787
|
-
it('should still require PR when body is null', async () => {
|
|
788
|
-
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([]);
|
|
789
|
-
|
|
790
|
-
const result = await evaluator.evaluate({
|
|
791
|
-
url: 'https://github.com/user/repo/issues/1',
|
|
792
|
-
labels: [],
|
|
793
|
-
isPr: false,
|
|
794
|
-
body: null,
|
|
795
|
-
});
|
|
796
|
-
|
|
797
|
-
expect(result.rejections).toHaveLength(1);
|
|
798
|
-
expect(result.rejections[0].type).toBe('PULL_REQUEST_NOT_FOUND');
|
|
799
|
-
});
|
|
800
|
-
|
|
801
|
-
it('should still require PR when body is undefined', async () => {
|
|
802
|
-
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([]);
|
|
803
|
-
|
|
804
|
-
const result = await evaluator.evaluate({
|
|
805
|
-
url: 'https://github.com/user/repo/issues/1',
|
|
806
|
-
labels: [],
|
|
807
|
-
isPr: false,
|
|
808
|
-
});
|
|
809
|
-
|
|
810
|
-
expect(result.rejections).toHaveLength(1);
|
|
811
|
-
expect(result.rejections[0].type).toBe('PULL_REQUEST_NOT_FOUND');
|
|
812
|
-
});
|
|
813
|
-
|
|
814
|
-
it('should still require PR when JSON block with pullRequestRequired: false is not at end of body', async () => {
|
|
815
|
-
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([]);
|
|
816
|
-
const body =
|
|
817
|
-
'```json\n{"pullRequestRequired": false}\n```\n\nsome text after';
|
|
818
|
-
|
|
819
|
-
const result = await evaluator.evaluate({
|
|
820
|
-
url: 'https://github.com/user/repo/issues/1',
|
|
821
|
-
labels: [],
|
|
822
|
-
isPr: false,
|
|
823
|
-
body,
|
|
824
|
-
});
|
|
825
|
-
|
|
826
|
-
expect(result.rejections).toHaveLength(1);
|
|
827
|
-
expect(result.rejections[0].type).toBe('PULL_REQUEST_NOT_FOUND');
|
|
828
|
-
});
|
|
829
|
-
|
|
830
|
-
it('should still require PR when body has invalid JSON in code block', async () => {
|
|
831
|
-
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([]);
|
|
832
|
-
const body = '```json\n{invalid json}\n```';
|
|
833
|
-
|
|
834
|
-
const result = await evaluator.evaluate({
|
|
835
|
-
url: 'https://github.com/user/repo/issues/1',
|
|
836
|
-
labels: [],
|
|
837
|
-
isPr: false,
|
|
838
|
-
body,
|
|
839
|
-
});
|
|
840
|
-
|
|
841
|
-
expect(result.rejections).toHaveLength(1);
|
|
842
|
-
expect(result.rejections[0].type).toBe('PULL_REQUEST_NOT_FOUND');
|
|
843
|
-
});
|
|
844
|
-
|
|
845
|
-
it('should still require PR when body has pullRequestRequired not set to false', async () => {
|
|
846
|
-
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([]);
|
|
847
|
-
const body = '```json\n{"someOtherField": true}\n```';
|
|
848
|
-
|
|
849
|
-
const result = await evaluator.evaluate({
|
|
850
|
-
url: 'https://github.com/user/repo/issues/1',
|
|
851
|
-
labels: [],
|
|
852
|
-
isPr: false,
|
|
853
|
-
body,
|
|
854
|
-
});
|
|
855
|
-
|
|
856
|
-
expect(result.rejections).toHaveLength(1);
|
|
857
|
-
expect(result.rejections[0].type).toBe('PULL_REQUEST_NOT_FOUND');
|
|
858
|
-
});
|
|
859
|
-
});
|
|
860
|
-
|
|
861
742
|
describe('detectConflictEvenIfEvaluationSkipped option', () => {
|
|
862
743
|
it('should call findRelatedOpenPRs and return PULL_REQUEST_CONFLICTED when relatedOpenPrUrls is null and PR is conflicted', async () => {
|
|
863
744
|
mockIssueRepository.findRelatedOpenPRs.mockResolvedValue([
|
|
@@ -2,7 +2,6 @@ import {
|
|
|
2
2
|
IssueRepository,
|
|
3
3
|
RelatedPullRequest,
|
|
4
4
|
} from './adapter-interfaces/IssueRepository';
|
|
5
|
-
import { normalizeReportBody } from './normalizeReportBody';
|
|
6
5
|
|
|
7
6
|
export type PrRejectedReasonType =
|
|
8
7
|
| 'PULL_REQUEST_NOT_FOUND'
|
|
@@ -243,7 +242,6 @@ export class IssueRejectionEvaluator {
|
|
|
243
242
|
return (
|
|
244
243
|
!isNonDeveloperAgent &&
|
|
245
244
|
!hasLabelNotRequiringPullRequest &&
|
|
246
|
-
this.isPullRequestRequiredByBody(issue.body) &&
|
|
247
245
|
(categoryLabels.length <= 0 || categoryLabels.includes('category:e2e'))
|
|
248
246
|
);
|
|
249
247
|
};
|
|
@@ -337,23 +335,4 @@ export class IssueRejectionEvaluator {
|
|
|
337
335
|
targetPath: string,
|
|
338
336
|
): boolean =>
|
|
339
337
|
filePath === targetPath || filePath.startsWith(`${targetPath}/`);
|
|
340
|
-
|
|
341
|
-
private isPullRequestRequiredByBody = (
|
|
342
|
-
body: string | null | undefined,
|
|
343
|
-
): boolean => {
|
|
344
|
-
if (!body) return true;
|
|
345
|
-
const match = normalizeReportBody(body)
|
|
346
|
-
.trimEnd()
|
|
347
|
-
.match(/```json\n([\s\S]*?)\n```\s*$/);
|
|
348
|
-
if (!match || !match[1]) return true;
|
|
349
|
-
let config: unknown;
|
|
350
|
-
try {
|
|
351
|
-
config = JSON.parse(match[1]);
|
|
352
|
-
} catch {
|
|
353
|
-
return true;
|
|
354
|
-
}
|
|
355
|
-
if (typeof config !== 'object' || config === null) return true;
|
|
356
|
-
if (!('pullRequestRequired' in config)) return true;
|
|
357
|
-
return Reflect.get(config, 'pullRequestRequired') !== false;
|
|
358
|
-
};
|
|
359
338
|
}
|