paperclip-github-plugin 0.2.2 → 0.2.3
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/README.md +4 -2
- package/dist/manifest.js +1 -1
- package/dist/ui/index.js +385 -45
- package/dist/ui/index.js.map +2 -2
- package/dist/worker.js +281 -32
- package/package.json +1 -1
package/dist/worker.js
CHANGED
|
@@ -515,6 +515,10 @@ var SYNC_STATE_SCOPE = {
|
|
|
515
515
|
scopeKind: "instance",
|
|
516
516
|
stateKey: "paperclip-github-plugin-last-sync"
|
|
517
517
|
};
|
|
518
|
+
var SYNC_CANCELLATION_SCOPE = {
|
|
519
|
+
scopeKind: "instance",
|
|
520
|
+
stateKey: "paperclip-github-plugin-sync-cancel-request"
|
|
521
|
+
};
|
|
518
522
|
var IMPORT_REGISTRY_SCOPE = {
|
|
519
523
|
scopeKind: "instance",
|
|
520
524
|
stateKey: "paperclip-github-plugin-import-registry"
|
|
@@ -527,7 +531,9 @@ var DEFAULT_PAPERCLIP_LABEL_COLOR = "#6366f1";
|
|
|
527
531
|
var PAPERCLIP_LABEL_PAGE_SIZE = 100;
|
|
528
532
|
var MANUAL_SYNC_RESPONSE_GRACE_PERIOD_MS = 500;
|
|
529
533
|
var RUNNING_SYNC_MESSAGE = "GitHub sync is running in the background. This page will update when it finishes.";
|
|
534
|
+
var CANCELLING_SYNC_MESSAGE = "Cancellation requested. GitHub sync will stop after the current step finishes.";
|
|
530
535
|
var SYNC_PROGRESS_PERSIST_INTERVAL_MS = 250;
|
|
536
|
+
var MAX_SYNC_FAILURE_LOG_ENTRIES = 25;
|
|
531
537
|
var GITHUB_SECONDARY_RATE_LIMIT_FALLBACK_MS = 6e4;
|
|
532
538
|
var MISSING_GITHUB_TOKEN_SYNC_MESSAGE = "Configure a GitHub token before running sync.";
|
|
533
539
|
var MISSING_GITHUB_TOKEN_SYNC_ACTION = 'Open settings and save a GitHub token secret, or create ~/.paperclip/plugins/github-sync/config.json with a "githubToken" value, and then run sync again.';
|
|
@@ -577,6 +583,14 @@ var PaperclipLabelSyncError = class extends Error {
|
|
|
577
583
|
this.labelNames = labelNames;
|
|
578
584
|
}
|
|
579
585
|
};
|
|
586
|
+
var SyncCancellationError = class extends Error {
|
|
587
|
+
name = "SyncCancellationError";
|
|
588
|
+
requestedAt;
|
|
589
|
+
constructor(requestedAt) {
|
|
590
|
+
super(CANCELLING_SYNC_MESSAGE);
|
|
591
|
+
this.requestedAt = requestedAt;
|
|
592
|
+
}
|
|
593
|
+
};
|
|
580
594
|
var SUCCESSFUL_CHECK_RUN_CONCLUSIONS = /* @__PURE__ */ new Set(["SUCCESS", "NEUTRAL", "SKIPPED"]);
|
|
581
595
|
var FAILED_CHECK_RUN_CONCLUSIONS = /* @__PURE__ */ new Set([
|
|
582
596
|
"ACTION_REQUIRED",
|
|
@@ -884,6 +898,20 @@ function createIdleSyncState() {
|
|
|
884
898
|
status: "idle"
|
|
885
899
|
};
|
|
886
900
|
}
|
|
901
|
+
function createCancelledSyncState(params) {
|
|
902
|
+
const { message, trigger, syncedIssuesCount, createdIssuesCount, skippedIssuesCount, erroredIssuesCount, progress } = params;
|
|
903
|
+
return {
|
|
904
|
+
status: "cancelled",
|
|
905
|
+
message,
|
|
906
|
+
checkedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
907
|
+
syncedIssuesCount,
|
|
908
|
+
createdIssuesCount,
|
|
909
|
+
skippedIssuesCount,
|
|
910
|
+
erroredIssuesCount,
|
|
911
|
+
lastRunTrigger: trigger,
|
|
912
|
+
...progress ? { progress: normalizeSyncProgress(progress) } : {}
|
|
913
|
+
};
|
|
914
|
+
}
|
|
887
915
|
function formatGitHubIssueCountLabel(count) {
|
|
888
916
|
const normalizedCount = Math.max(0, Math.floor(count));
|
|
889
917
|
return `${normalizedCount} GitHub ${normalizedCount === 1 ? "issue" : "issues"}`;
|
|
@@ -1047,6 +1075,7 @@ function normalizeSyncConfigurationIssue(value) {
|
|
|
1047
1075
|
switch (value) {
|
|
1048
1076
|
case "missing_token":
|
|
1049
1077
|
case "missing_mapping":
|
|
1078
|
+
case "missing_board_access":
|
|
1050
1079
|
return value;
|
|
1051
1080
|
default:
|
|
1052
1081
|
return void 0;
|
|
@@ -1120,6 +1149,30 @@ function normalizeSyncErrorDetails(value) {
|
|
|
1120
1149
|
...rateLimitResource ? { rateLimitResource } : {}
|
|
1121
1150
|
};
|
|
1122
1151
|
}
|
|
1152
|
+
function normalizeSyncFailureLogEntry(value) {
|
|
1153
|
+
if (!value || typeof value !== "object") {
|
|
1154
|
+
return void 0;
|
|
1155
|
+
}
|
|
1156
|
+
const record = value;
|
|
1157
|
+
const details = normalizeSyncErrorDetails(record);
|
|
1158
|
+
const message = typeof record.message === "string" && record.message.trim() ? record.message.trim() : void 0;
|
|
1159
|
+
const occurredAt = typeof record.occurredAt === "string" && record.occurredAt.trim() ? record.occurredAt.trim() : void 0;
|
|
1160
|
+
if (!message || !occurredAt) {
|
|
1161
|
+
return void 0;
|
|
1162
|
+
}
|
|
1163
|
+
return {
|
|
1164
|
+
message,
|
|
1165
|
+
occurredAt,
|
|
1166
|
+
...details ?? {}
|
|
1167
|
+
};
|
|
1168
|
+
}
|
|
1169
|
+
function normalizeSyncFailureLogEntries(value) {
|
|
1170
|
+
if (!Array.isArray(value)) {
|
|
1171
|
+
return void 0;
|
|
1172
|
+
}
|
|
1173
|
+
const entries = value.map((entry) => normalizeSyncFailureLogEntry(entry)).filter((entry) => entry !== void 0).slice(-MAX_SYNC_FAILURE_LOG_ENTRIES);
|
|
1174
|
+
return entries.length > 0 ? entries : void 0;
|
|
1175
|
+
}
|
|
1123
1176
|
function formatSyncFailurePhase(phase) {
|
|
1124
1177
|
switch (phase) {
|
|
1125
1178
|
case "configuration":
|
|
@@ -1274,8 +1327,49 @@ function buildSyncErrorDetails(error, context) {
|
|
|
1274
1327
|
...rateLimitPause?.resource ? { rateLimitResource: rateLimitPause.resource } : {}
|
|
1275
1328
|
};
|
|
1276
1329
|
}
|
|
1330
|
+
function createSyncFailureLogEntry(params) {
|
|
1331
|
+
const message = params.message.trim();
|
|
1332
|
+
const occurredAt = typeof params.occurredAt === "string" && params.occurredAt.trim() ? params.occurredAt.trim() : (/* @__PURE__ */ new Date()).toISOString();
|
|
1333
|
+
const errorDetails = normalizeSyncErrorDetails(params.errorDetails);
|
|
1334
|
+
if (!message) {
|
|
1335
|
+
return void 0;
|
|
1336
|
+
}
|
|
1337
|
+
return {
|
|
1338
|
+
message,
|
|
1339
|
+
occurredAt,
|
|
1340
|
+
...errorDetails ?? {}
|
|
1341
|
+
};
|
|
1342
|
+
}
|
|
1343
|
+
function buildSyncFailureLogEntry(error, context, occurredAt) {
|
|
1344
|
+
return createSyncFailureLogEntry({
|
|
1345
|
+
message: buildSyncFailureMessage(error, context),
|
|
1346
|
+
occurredAt,
|
|
1347
|
+
errorDetails: buildSyncErrorDetails(error, context)
|
|
1348
|
+
});
|
|
1349
|
+
}
|
|
1350
|
+
function buildRecentSyncFailureLogEntries(failures) {
|
|
1351
|
+
const entries = failures.slice(-MAX_SYNC_FAILURE_LOG_ENTRIES).map((failure) => buildSyncFailureLogEntry(failure.error, failure.context, failure.occurredAt)).filter((entry) => entry !== void 0);
|
|
1352
|
+
return entries.length > 0 ? entries : void 0;
|
|
1353
|
+
}
|
|
1354
|
+
function appendRecentSyncFailureLogEntry(entries, entry) {
|
|
1355
|
+
if (!entry) {
|
|
1356
|
+
return entries;
|
|
1357
|
+
}
|
|
1358
|
+
return [...entries ?? [], entry].slice(-MAX_SYNC_FAILURE_LOG_ENTRIES);
|
|
1359
|
+
}
|
|
1277
1360
|
function createErrorSyncState(params) {
|
|
1278
|
-
const {
|
|
1361
|
+
const {
|
|
1362
|
+
message,
|
|
1363
|
+
trigger,
|
|
1364
|
+
syncedIssuesCount,
|
|
1365
|
+
createdIssuesCount,
|
|
1366
|
+
skippedIssuesCount,
|
|
1367
|
+
erroredIssuesCount,
|
|
1368
|
+
progress,
|
|
1369
|
+
errorDetails,
|
|
1370
|
+
recentFailures
|
|
1371
|
+
} = params;
|
|
1372
|
+
const normalizedRecentFailures = normalizeSyncFailureLogEntries(recentFailures);
|
|
1279
1373
|
return {
|
|
1280
1374
|
status: "error",
|
|
1281
1375
|
message,
|
|
@@ -1286,20 +1380,27 @@ function createErrorSyncState(params) {
|
|
|
1286
1380
|
erroredIssuesCount,
|
|
1287
1381
|
lastRunTrigger: trigger,
|
|
1288
1382
|
...progress ? { progress: normalizeSyncProgress(progress) } : {},
|
|
1289
|
-
...errorDetails ? { errorDetails } : {}
|
|
1383
|
+
...errorDetails ? { errorDetails } : {},
|
|
1384
|
+
...normalizedRecentFailures ? { recentFailures: normalizedRecentFailures } : {}
|
|
1290
1385
|
};
|
|
1291
1386
|
}
|
|
1292
1387
|
function createRunningSyncState(previous, trigger, options = {}) {
|
|
1388
|
+
const previousRunningState = previous.status === "running" ? previous : void 0;
|
|
1389
|
+
const nextMessage = options.message ?? previousRunningState?.message ?? RUNNING_SYNC_MESSAGE;
|
|
1390
|
+
const nextCancelRequestedAt = options.cancelRequestedAt ?? previousRunningState?.cancelRequestedAt;
|
|
1391
|
+
const normalizedRecentFailures = normalizeSyncFailureLogEntries(options.recentFailures);
|
|
1293
1392
|
return {
|
|
1294
1393
|
status: "running",
|
|
1295
|
-
message:
|
|
1394
|
+
message: nextMessage,
|
|
1296
1395
|
checkedAt: previous.checkedAt,
|
|
1297
1396
|
syncedIssuesCount: options.syncedIssuesCount ?? 0,
|
|
1298
1397
|
createdIssuesCount: options.createdIssuesCount ?? 0,
|
|
1299
1398
|
skippedIssuesCount: options.skippedIssuesCount ?? 0,
|
|
1300
1399
|
erroredIssuesCount: options.erroredIssuesCount ?? 0,
|
|
1301
1400
|
lastRunTrigger: trigger,
|
|
1302
|
-
...
|
|
1401
|
+
...nextCancelRequestedAt ? { cancelRequestedAt: nextCancelRequestedAt } : {},
|
|
1402
|
+
...options.progress ? { progress: normalizeSyncProgress(options.progress) } : {},
|
|
1403
|
+
...normalizedRecentFailures ? { recentFailures: normalizedRecentFailures } : {}
|
|
1303
1404
|
};
|
|
1304
1405
|
}
|
|
1305
1406
|
function getSyncableMappings(mappings) {
|
|
@@ -1785,7 +1886,16 @@ function createSetupConfigurationErrorSyncState(issue, trigger) {
|
|
|
1785
1886
|
phase: "configuration",
|
|
1786
1887
|
configurationIssue: "missing_token",
|
|
1787
1888
|
suggestedAction: MISSING_GITHUB_TOKEN_SYNC_ACTION
|
|
1788
|
-
}
|
|
1889
|
+
},
|
|
1890
|
+
recentFailures: [
|
|
1891
|
+
{
|
|
1892
|
+
message: MISSING_GITHUB_TOKEN_SYNC_MESSAGE,
|
|
1893
|
+
occurredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1894
|
+
phase: "configuration",
|
|
1895
|
+
configurationIssue: "missing_token",
|
|
1896
|
+
suggestedAction: MISSING_GITHUB_TOKEN_SYNC_ACTION
|
|
1897
|
+
}
|
|
1898
|
+
]
|
|
1789
1899
|
});
|
|
1790
1900
|
case "missing_mapping":
|
|
1791
1901
|
return createErrorSyncState({
|
|
@@ -1799,7 +1909,16 @@ function createSetupConfigurationErrorSyncState(issue, trigger) {
|
|
|
1799
1909
|
phase: "configuration",
|
|
1800
1910
|
configurationIssue: "missing_mapping",
|
|
1801
1911
|
suggestedAction: MISSING_MAPPING_SYNC_ACTION
|
|
1802
|
-
}
|
|
1912
|
+
},
|
|
1913
|
+
recentFailures: [
|
|
1914
|
+
{
|
|
1915
|
+
message: MISSING_MAPPING_SYNC_MESSAGE,
|
|
1916
|
+
occurredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1917
|
+
phase: "configuration",
|
|
1918
|
+
configurationIssue: "missing_mapping",
|
|
1919
|
+
suggestedAction: MISSING_MAPPING_SYNC_ACTION
|
|
1920
|
+
}
|
|
1921
|
+
]
|
|
1803
1922
|
});
|
|
1804
1923
|
case "missing_board_access":
|
|
1805
1924
|
return createErrorSyncState({
|
|
@@ -1813,7 +1932,16 @@ function createSetupConfigurationErrorSyncState(issue, trigger) {
|
|
|
1813
1932
|
phase: "configuration",
|
|
1814
1933
|
configurationIssue: "missing_board_access",
|
|
1815
1934
|
suggestedAction: MISSING_BOARD_ACCESS_SYNC_ACTION
|
|
1816
|
-
}
|
|
1935
|
+
},
|
|
1936
|
+
recentFailures: [
|
|
1937
|
+
{
|
|
1938
|
+
message: MISSING_BOARD_ACCESS_SYNC_MESSAGE,
|
|
1939
|
+
occurredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1940
|
+
phase: "configuration",
|
|
1941
|
+
configurationIssue: "missing_board_access",
|
|
1942
|
+
suggestedAction: MISSING_BOARD_ACCESS_SYNC_ACTION
|
|
1943
|
+
}
|
|
1944
|
+
]
|
|
1817
1945
|
});
|
|
1818
1946
|
}
|
|
1819
1947
|
}
|
|
@@ -1826,6 +1954,25 @@ async function saveSettingsSyncState(ctx, settings, syncState) {
|
|
|
1826
1954
|
await ctx.state.set(SYNC_STATE_SCOPE, next.syncState);
|
|
1827
1955
|
return next;
|
|
1828
1956
|
}
|
|
1957
|
+
async function setSyncCancellationRequest(ctx, request) {
|
|
1958
|
+
await ctx.state.set(SYNC_CANCELLATION_SCOPE, request);
|
|
1959
|
+
}
|
|
1960
|
+
async function getSyncCancellationRequest(ctx) {
|
|
1961
|
+
const activeRequestedAt = activeRunningSyncState?.syncState.cancelRequestedAt?.trim();
|
|
1962
|
+
if (activeRunningSyncState?.syncState.status === "running" && activeRequestedAt) {
|
|
1963
|
+
return {
|
|
1964
|
+
requestedAt: activeRequestedAt
|
|
1965
|
+
};
|
|
1966
|
+
}
|
|
1967
|
+
return normalizeSyncCancellationRequest(await ctx.state.get(SYNC_CANCELLATION_SCOPE));
|
|
1968
|
+
}
|
|
1969
|
+
function buildCancelledSyncMessage(target, progress) {
|
|
1970
|
+
const completedIssueCount = typeof progress?.completedIssueCount === "number" ? Math.max(0, progress.completedIssueCount) : void 0;
|
|
1971
|
+
const totalIssueCount = typeof progress?.totalIssueCount === "number" ? Math.max(0, progress.totalIssueCount) : void 0;
|
|
1972
|
+
const scopeLabel = target ? `GitHub sync for ${target.displayLabel}` : "GitHub sync";
|
|
1973
|
+
const completionSummary = completedIssueCount !== void 0 && totalIssueCount !== void 0 ? ` Completed ${Math.min(completedIssueCount, totalIssueCount)} of ${totalIssueCount} issues before stopping.` : "";
|
|
1974
|
+
return `${scopeLabel} was cancelled before it finished.${completionSummary}`;
|
|
1975
|
+
}
|
|
1829
1976
|
async function createUnexpectedSyncErrorResult(ctx, trigger, error) {
|
|
1830
1977
|
const settings = normalizeSettings(await ctx.state.get(SETTINGS_SCOPE));
|
|
1831
1978
|
const errorDetails = buildSyncErrorDetails(error, {
|
|
@@ -1844,7 +1991,14 @@ async function createUnexpectedSyncErrorResult(ctx, trigger, error) {
|
|
|
1844
1991
|
createdIssuesCount: settings.syncState.createdIssuesCount ?? 0,
|
|
1845
1992
|
skippedIssuesCount: settings.syncState.skippedIssuesCount ?? 0,
|
|
1846
1993
|
erroredIssuesCount: 0,
|
|
1847
|
-
errorDetails
|
|
1994
|
+
errorDetails,
|
|
1995
|
+
recentFailures: appendRecentSyncFailureLogEntry(
|
|
1996
|
+
void 0,
|
|
1997
|
+
createSyncFailureLogEntry({
|
|
1998
|
+
message,
|
|
1999
|
+
errorDetails
|
|
2000
|
+
})
|
|
2001
|
+
)
|
|
1848
2002
|
})
|
|
1849
2003
|
);
|
|
1850
2004
|
}
|
|
@@ -1892,7 +2046,8 @@ function recordRecoverableSyncFailure(ctx, failures, error, context) {
|
|
|
1892
2046
|
const snapshot = cloneSyncFailureContext(context);
|
|
1893
2047
|
failures.push({
|
|
1894
2048
|
error,
|
|
1895
|
-
context: snapshot
|
|
2049
|
+
context: snapshot,
|
|
2050
|
+
occurredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1896
2051
|
});
|
|
1897
2052
|
ctx.logger.warn("GitHub sync skipped a failed item and continued.", {
|
|
1898
2053
|
phase: snapshot.phase,
|
|
@@ -1998,6 +2153,13 @@ function normalizePaperclipBoardApiTokenRefs(value) {
|
|
|
1998
2153
|
}
|
|
1999
2154
|
return Object.fromEntries(entries);
|
|
2000
2155
|
}
|
|
2156
|
+
function normalizeSyncCancellationRequest(value) {
|
|
2157
|
+
if (!value || typeof value !== "object") {
|
|
2158
|
+
return null;
|
|
2159
|
+
}
|
|
2160
|
+
const requestedAt = typeof value.requestedAt === "string" ? value.requestedAt.trim() : "";
|
|
2161
|
+
return requestedAt ? { requestedAt } : null;
|
|
2162
|
+
}
|
|
2001
2163
|
function normalizeSyncState(value) {
|
|
2002
2164
|
if (!value || typeof value !== "object") {
|
|
2003
2165
|
return DEFAULT_SETTINGS.syncState;
|
|
@@ -2007,8 +2169,9 @@ function normalizeSyncState(value) {
|
|
|
2007
2169
|
const lastRunTrigger = record.lastRunTrigger;
|
|
2008
2170
|
const progress = normalizeSyncProgress(record.progress);
|
|
2009
2171
|
const errorDetails = normalizeSyncErrorDetails(record.errorDetails);
|
|
2172
|
+
const recentFailures = normalizeSyncFailureLogEntries(record.recentFailures);
|
|
2010
2173
|
return {
|
|
2011
|
-
status: status === "running" || status === "success" || status === "error" ? status : "idle",
|
|
2174
|
+
status: status === "running" || status === "success" || status === "error" || status === "cancelled" ? status : "idle",
|
|
2012
2175
|
message: typeof record.message === "string" ? record.message : void 0,
|
|
2013
2176
|
checkedAt: typeof record.checkedAt === "string" ? record.checkedAt : void 0,
|
|
2014
2177
|
syncedIssuesCount: typeof record.syncedIssuesCount === "number" ? record.syncedIssuesCount : void 0,
|
|
@@ -2016,8 +2179,10 @@ function normalizeSyncState(value) {
|
|
|
2016
2179
|
skippedIssuesCount: typeof record.skippedIssuesCount === "number" ? record.skippedIssuesCount : void 0,
|
|
2017
2180
|
erroredIssuesCount: typeof record.erroredIssuesCount === "number" ? record.erroredIssuesCount : void 0,
|
|
2018
2181
|
lastRunTrigger: lastRunTrigger === "manual" || lastRunTrigger === "schedule" || lastRunTrigger === "retry" ? lastRunTrigger : void 0,
|
|
2182
|
+
cancelRequestedAt: typeof record.cancelRequestedAt === "string" ? record.cancelRequestedAt : void 0,
|
|
2019
2183
|
...progress ? { progress } : {},
|
|
2020
|
-
...errorDetails ? { errorDetails } : {}
|
|
2184
|
+
...errorDetails ? { errorDetails } : {},
|
|
2185
|
+
...recentFailures ? { recentFailures } : {}
|
|
2021
2186
|
};
|
|
2022
2187
|
}
|
|
2023
2188
|
function normalizeMappings(value) {
|
|
@@ -4318,25 +4483,30 @@ async function createPaperclipIssue(ctx, mapping, advancedSettings, issue, avail
|
|
|
4318
4483
|
createdIssueDescription = createdIssue.description;
|
|
4319
4484
|
createPath = "sdk";
|
|
4320
4485
|
}
|
|
4486
|
+
const ensuredCreatedIssueId = createdIssueId;
|
|
4487
|
+
if (!ensuredCreatedIssueId) {
|
|
4488
|
+
throw new Error("GitHub sync could not resolve the created Paperclip issue id.");
|
|
4489
|
+
}
|
|
4490
|
+
const normalizedCreatedIssueDescription = createdIssueDescription ?? void 0;
|
|
4321
4491
|
if (createPath !== "sdk") {
|
|
4322
4492
|
await applyDefaultAssigneeToPaperclipIssue(ctx, {
|
|
4323
4493
|
companyId: mapping.companyId,
|
|
4324
|
-
issueId:
|
|
4494
|
+
issueId: ensuredCreatedIssueId,
|
|
4325
4495
|
defaultAssigneeAgentId: advancedSettings.defaultAssigneeAgentId
|
|
4326
4496
|
});
|
|
4327
4497
|
}
|
|
4328
|
-
if (normalizeIssueDescriptionValue(
|
|
4498
|
+
if (normalizeIssueDescriptionValue(normalizedCreatedIssueDescription) !== description) {
|
|
4329
4499
|
logIssueDescriptionDiagnostic(
|
|
4330
4500
|
ctx,
|
|
4331
4501
|
"warn",
|
|
4332
4502
|
"GitHub sync detected a missing or mismatched Paperclip issue description immediately after issue creation.",
|
|
4333
4503
|
{
|
|
4334
4504
|
companyId: mapping.companyId,
|
|
4335
|
-
issueId:
|
|
4505
|
+
issueId: ensuredCreatedIssueId,
|
|
4336
4506
|
paperclipApiBaseUrl,
|
|
4337
4507
|
githubIssue: issue,
|
|
4338
4508
|
linkedPullRequestNumbers: [],
|
|
4339
|
-
currentDescription:
|
|
4509
|
+
currentDescription: normalizedCreatedIssueDescription,
|
|
4340
4510
|
nextDescription: description,
|
|
4341
4511
|
reason: "create_response_mismatch",
|
|
4342
4512
|
createPath
|
|
@@ -4346,8 +4516,8 @@ async function createPaperclipIssue(ctx, mapping, advancedSettings, issue, avail
|
|
|
4346
4516
|
ctx,
|
|
4347
4517
|
{
|
|
4348
4518
|
companyId: mapping.companyId,
|
|
4349
|
-
issueId:
|
|
4350
|
-
currentDescription:
|
|
4519
|
+
issueId: ensuredCreatedIssueId,
|
|
4520
|
+
currentDescription: normalizedCreatedIssueDescription,
|
|
4351
4521
|
githubIssue: issue,
|
|
4352
4522
|
linkedPullRequestNumbers: [],
|
|
4353
4523
|
paperclipApiBaseUrl,
|
|
@@ -4355,7 +4525,7 @@ async function createPaperclipIssue(ctx, mapping, advancedSettings, issue, avail
|
|
|
4355
4525
|
}
|
|
4356
4526
|
);
|
|
4357
4527
|
}
|
|
4358
|
-
await upsertGitHubIssueLinkRecord(ctx, mapping,
|
|
4528
|
+
await upsertGitHubIssueLinkRecord(ctx, mapping, ensuredCreatedIssueId, issue, []);
|
|
4359
4529
|
if (syncFailureContext) {
|
|
4360
4530
|
updateSyncFailureContext(syncFailureContext, {
|
|
4361
4531
|
phase: "syncing_labels",
|
|
@@ -4373,11 +4543,11 @@ async function createPaperclipIssue(ctx, mapping, advancedSettings, issue, avail
|
|
|
4373
4543
|
await applyPaperclipLabelsToIssue(
|
|
4374
4544
|
ctx,
|
|
4375
4545
|
mapping.companyId,
|
|
4376
|
-
|
|
4546
|
+
ensuredCreatedIssueId,
|
|
4377
4547
|
labelResolution.labels
|
|
4378
4548
|
);
|
|
4379
4549
|
return {
|
|
4380
|
-
id:
|
|
4550
|
+
id: ensuredCreatedIssueId,
|
|
4381
4551
|
unresolvedGitHubLabels: labelResolution.unresolvedGitHubLabels,
|
|
4382
4552
|
...labelResolution.failure ? { labelResolutionFailure: labelResolution.failure } : {}
|
|
4383
4553
|
};
|
|
@@ -4449,7 +4619,7 @@ async function ensurePaperclipIssueImported(ctx, mapping, advancedSettings, issu
|
|
|
4449
4619
|
}
|
|
4450
4620
|
return createdIssue.id;
|
|
4451
4621
|
}
|
|
4452
|
-
async function synchronizePaperclipIssueStatuses(ctx, octokit, repository, mapping, advancedSettings, allIssuesById, importedIssues, createdIssueIds, availableLabels, paperclipApiBaseUrl, linkedPullRequestsByIssueNumber, issueStatusSnapshotCache, pullRequestStatusCache, repositoryMaintainerCache, syncFailureContext, failures, onProgress) {
|
|
4622
|
+
async function synchronizePaperclipIssueStatuses(ctx, octokit, repository, mapping, advancedSettings, allIssuesById, importedIssues, createdIssueIds, availableLabels, paperclipApiBaseUrl, linkedPullRequestsByIssueNumber, issueStatusSnapshotCache, pullRequestStatusCache, repositoryMaintainerCache, syncFailureContext, failures, assertNotCancelled, onProgress) {
|
|
4453
4623
|
if (!mapping.companyId || !ctx.issues || typeof ctx.issues.get !== "function" || typeof ctx.issues.update !== "function") {
|
|
4454
4624
|
return {
|
|
4455
4625
|
updatedStatusesCount: 0,
|
|
@@ -4463,6 +4633,9 @@ async function synchronizePaperclipIssueStatuses(ctx, octokit, repository, mappi
|
|
|
4463
4633
|
let completedIssueCount = 0;
|
|
4464
4634
|
const totalIssueCount = importedIssues.length;
|
|
4465
4635
|
for (const importedIssue of importedIssues) {
|
|
4636
|
+
if (assertNotCancelled) {
|
|
4637
|
+
await assertNotCancelled();
|
|
4638
|
+
}
|
|
4466
4639
|
const githubIssue = allIssuesById.get(importedIssue.githubIssueId);
|
|
4467
4640
|
try {
|
|
4468
4641
|
if (!githubIssue) {
|
|
@@ -5163,6 +5336,10 @@ async function performSync(ctx, trigger, options = {}) {
|
|
|
5163
5336
|
return next;
|
|
5164
5337
|
}
|
|
5165
5338
|
if (!ctx.issues || typeof ctx.issues.create !== "function") {
|
|
5339
|
+
const errorDetails = {
|
|
5340
|
+
phase: "configuration",
|
|
5341
|
+
suggestedAction: "Update Paperclip to a runtime that supports plugin issue creation, then retry sync."
|
|
5342
|
+
};
|
|
5166
5343
|
const next = {
|
|
5167
5344
|
...settings,
|
|
5168
5345
|
syncState: createErrorSyncState({
|
|
@@ -5172,10 +5349,14 @@ async function performSync(ctx, trigger, options = {}) {
|
|
|
5172
5349
|
createdIssuesCount: 0,
|
|
5173
5350
|
skippedIssuesCount: 0,
|
|
5174
5351
|
erroredIssuesCount: 0,
|
|
5175
|
-
errorDetails
|
|
5176
|
-
|
|
5177
|
-
|
|
5178
|
-
|
|
5352
|
+
errorDetails,
|
|
5353
|
+
recentFailures: appendRecentSyncFailureLogEntry(
|
|
5354
|
+
void 0,
|
|
5355
|
+
createSyncFailureLogEntry({
|
|
5356
|
+
message: "This Paperclip runtime does not expose plugin issue creation yet.",
|
|
5357
|
+
errorDetails
|
|
5358
|
+
})
|
|
5359
|
+
)
|
|
5179
5360
|
})
|
|
5180
5361
|
};
|
|
5181
5362
|
await ctx.state.set(SETTINGS_SCOPE, next);
|
|
@@ -5211,14 +5392,23 @@ async function performSync(ctx, trigger, options = {}) {
|
|
|
5211
5392
|
erroredIssuesCount: recoverableFailures.length,
|
|
5212
5393
|
progress: currentProgress
|
|
5213
5394
|
});
|
|
5395
|
+
async function throwIfSyncCancelled() {
|
|
5396
|
+
const cancellationRequest = await getSyncCancellationRequest(ctx);
|
|
5397
|
+
if (!cancellationRequest) {
|
|
5398
|
+
return;
|
|
5399
|
+
}
|
|
5400
|
+
throw new SyncCancellationError(cancellationRequest.requestedAt);
|
|
5401
|
+
}
|
|
5214
5402
|
async function persistRunningProgress(force = false) {
|
|
5215
5403
|
const progress = normalizeSyncProgress(currentProgress);
|
|
5404
|
+
const recentFailures = buildRecentSyncFailureLogEntries(recoverableFailures);
|
|
5216
5405
|
const signature = JSON.stringify({
|
|
5217
5406
|
syncedIssuesCount,
|
|
5218
5407
|
createdIssuesCount,
|
|
5219
5408
|
skippedIssuesCount,
|
|
5220
5409
|
erroredIssuesCount: recoverableFailures.length,
|
|
5221
|
-
progress
|
|
5410
|
+
progress,
|
|
5411
|
+
recentFailures
|
|
5222
5412
|
});
|
|
5223
5413
|
const now = Date.now();
|
|
5224
5414
|
if (!force) {
|
|
@@ -5237,7 +5427,8 @@ async function performSync(ctx, trigger, options = {}) {
|
|
|
5237
5427
|
createdIssuesCount,
|
|
5238
5428
|
skippedIssuesCount,
|
|
5239
5429
|
erroredIssuesCount: recoverableFailures.length,
|
|
5240
|
-
progress
|
|
5430
|
+
progress,
|
|
5431
|
+
recentFailures
|
|
5241
5432
|
})
|
|
5242
5433
|
);
|
|
5243
5434
|
activeRunningSyncState = currentSettings;
|
|
@@ -5254,7 +5445,9 @@ async function performSync(ctx, trigger, options = {}) {
|
|
|
5254
5445
|
}
|
|
5255
5446
|
const repositoryPlans = [];
|
|
5256
5447
|
try {
|
|
5448
|
+
await throwIfSyncCancelled();
|
|
5257
5449
|
for (const [mappingIndex, mapping] of mappings.entries()) {
|
|
5450
|
+
await throwIfSyncCancelled();
|
|
5258
5451
|
try {
|
|
5259
5452
|
const repository = requireRepositoryReference(mapping.repositoryUrl);
|
|
5260
5453
|
const importedIssueRecords = nextRegistry.filter((entry) => doesImportedIssueRecordMatchMapping(entry, mapping)).filter((entry) => doesImportedIssueMatchTarget(entry, options.target));
|
|
@@ -5342,7 +5535,7 @@ async function performSync(ctx, trigger, options = {}) {
|
|
|
5342
5535
|
trackedIssueCount
|
|
5343
5536
|
});
|
|
5344
5537
|
} catch (error) {
|
|
5345
|
-
if (isGitHubRateLimitError(error)) {
|
|
5538
|
+
if (error instanceof SyncCancellationError || isGitHubRateLimitError(error)) {
|
|
5346
5539
|
throw error;
|
|
5347
5540
|
}
|
|
5348
5541
|
recordRecoverableSyncFailure(ctx, recoverableFailures, error, failureContext);
|
|
@@ -5371,6 +5564,7 @@ async function performSync(ctx, trigger, options = {}) {
|
|
|
5371
5564
|
}
|
|
5372
5565
|
await persistRunningProgress(true);
|
|
5373
5566
|
for (const plan of repositoryPlans) {
|
|
5567
|
+
await throwIfSyncCancelled();
|
|
5374
5568
|
try {
|
|
5375
5569
|
const { mapping, advancedSettings, repository, repositoryIndex, allIssuesById, issues } = plan;
|
|
5376
5570
|
const companyId = mapping.companyId;
|
|
@@ -5425,8 +5619,9 @@ async function performSync(ctx, trigger, options = {}) {
|
|
|
5425
5619
|
openLinkedPullRequestNumbers,
|
|
5426
5620
|
pullRequestStatusCache
|
|
5427
5621
|
);
|
|
5622
|
+
await throwIfSyncCancelled();
|
|
5428
5623
|
} catch (error) {
|
|
5429
|
-
if (isGitHubRateLimitError(error)) {
|
|
5624
|
+
if (error instanceof SyncCancellationError || isGitHubRateLimitError(error)) {
|
|
5430
5625
|
throw error;
|
|
5431
5626
|
}
|
|
5432
5627
|
}
|
|
@@ -5441,6 +5636,7 @@ async function performSync(ctx, trigger, options = {}) {
|
|
|
5441
5636
|
};
|
|
5442
5637
|
await persistRunningProgress(true);
|
|
5443
5638
|
for (const [issueIndex, issue] of issues.entries()) {
|
|
5639
|
+
await throwIfSyncCancelled();
|
|
5444
5640
|
const createdIssueCountBefore = createdIssueIds.size;
|
|
5445
5641
|
const skippedIssueCountBefore = skippedIssueIds.size;
|
|
5446
5642
|
try {
|
|
@@ -5489,6 +5685,7 @@ async function performSync(ctx, trigger, options = {}) {
|
|
|
5489
5685
|
totalIssueCount: totalTrackedIssueCount
|
|
5490
5686
|
};
|
|
5491
5687
|
await persistRunningProgress(true);
|
|
5688
|
+
await throwIfSyncCancelled();
|
|
5492
5689
|
const synchronizationResult = await synchronizePaperclipIssueStatuses(
|
|
5493
5690
|
ctx,
|
|
5494
5691
|
octokit,
|
|
@@ -5506,6 +5703,7 @@ async function performSync(ctx, trigger, options = {}) {
|
|
|
5506
5703
|
repositoryMaintainerCache,
|
|
5507
5704
|
failureContext,
|
|
5508
5705
|
recoverableFailures,
|
|
5706
|
+
throwIfSyncCancelled,
|
|
5509
5707
|
async (progress) => {
|
|
5510
5708
|
markTrackedIssueProcessed(mapping, progress.githubIssueId);
|
|
5511
5709
|
currentProgress = {
|
|
@@ -5524,7 +5722,7 @@ async function performSync(ctx, trigger, options = {}) {
|
|
|
5524
5722
|
updatedLabelsCount += synchronizationResult.updatedLabelsCount;
|
|
5525
5723
|
updatedDescriptionsCount += synchronizationResult.updatedDescriptionsCount;
|
|
5526
5724
|
} catch (error) {
|
|
5527
|
-
if (isGitHubRateLimitError(error)) {
|
|
5725
|
+
if (error instanceof SyncCancellationError || isGitHubRateLimitError(error)) {
|
|
5528
5726
|
throw error;
|
|
5529
5727
|
}
|
|
5530
5728
|
recordRecoverableSyncFailure(ctx, recoverableFailures, error, failureContext);
|
|
@@ -5548,7 +5746,8 @@ async function performSync(ctx, trigger, options = {}) {
|
|
|
5548
5746
|
skippedIssuesCount,
|
|
5549
5747
|
erroredIssuesCount: recoverableFailures.length,
|
|
5550
5748
|
progress: currentProgress,
|
|
5551
|
-
errorDetails
|
|
5749
|
+
errorDetails,
|
|
5750
|
+
recentFailures: buildRecentSyncFailureLogEntries(recoverableFailures)
|
|
5552
5751
|
})
|
|
5553
5752
|
};
|
|
5554
5753
|
await ctx.state.set(SETTINGS_SCOPE, next2);
|
|
@@ -5574,6 +5773,24 @@ async function performSync(ctx, trigger, options = {}) {
|
|
|
5574
5773
|
await ctx.state.set(IMPORT_REGISTRY_SCOPE, nextRegistry);
|
|
5575
5774
|
return next;
|
|
5576
5775
|
} catch (error) {
|
|
5776
|
+
if (error instanceof SyncCancellationError) {
|
|
5777
|
+
const next2 = {
|
|
5778
|
+
...currentSettings,
|
|
5779
|
+
syncState: createCancelledSyncState({
|
|
5780
|
+
message: buildCancelledSyncMessage(options.target, currentProgress),
|
|
5781
|
+
trigger,
|
|
5782
|
+
syncedIssuesCount,
|
|
5783
|
+
createdIssuesCount,
|
|
5784
|
+
skippedIssuesCount,
|
|
5785
|
+
erroredIssuesCount: recoverableFailures.length,
|
|
5786
|
+
progress: currentProgress
|
|
5787
|
+
})
|
|
5788
|
+
};
|
|
5789
|
+
await ctx.state.set(SETTINGS_SCOPE, next2);
|
|
5790
|
+
await ctx.state.set(SYNC_STATE_SCOPE, next2.syncState);
|
|
5791
|
+
await ctx.state.set(IMPORT_REGISTRY_SCOPE, nextRegistry);
|
|
5792
|
+
return next2;
|
|
5793
|
+
}
|
|
5577
5794
|
const errorDetails = buildSyncErrorDetails(error, failureContext);
|
|
5578
5795
|
const next = {
|
|
5579
5796
|
...currentSettings,
|
|
@@ -5585,7 +5802,11 @@ async function performSync(ctx, trigger, options = {}) {
|
|
|
5585
5802
|
skippedIssuesCount,
|
|
5586
5803
|
erroredIssuesCount: recoverableFailures.length,
|
|
5587
5804
|
progress: currentProgress,
|
|
5588
|
-
errorDetails
|
|
5805
|
+
errorDetails,
|
|
5806
|
+
recentFailures: appendRecentSyncFailureLogEntry(
|
|
5807
|
+
buildRecentSyncFailureLogEntries(recoverableFailures),
|
|
5808
|
+
buildSyncFailureLogEntry(error, failureContext)
|
|
5809
|
+
)
|
|
5589
5810
|
})
|
|
5590
5811
|
};
|
|
5591
5812
|
await ctx.state.set(SETTINGS_SCOPE, next);
|
|
@@ -5639,6 +5860,7 @@ async function startSync(ctx, trigger, options = {}) {
|
|
|
5639
5860
|
if (trigger !== "manual" && !token.trim()) {
|
|
5640
5861
|
return currentSettings;
|
|
5641
5862
|
}
|
|
5863
|
+
await setSyncCancellationRequest(ctx, null);
|
|
5642
5864
|
const runningStatePromise = (async () => {
|
|
5643
5865
|
const syncableMappings = getSyncableMappingsForTarget(currentSettings.mappings, options.target);
|
|
5644
5866
|
const syncState = createRunningSyncState(currentSettings.syncState, trigger, {
|
|
@@ -5668,6 +5890,7 @@ async function startSync(ctx, trigger, options = {}) {
|
|
|
5668
5890
|
} catch (error) {
|
|
5669
5891
|
return await createUnexpectedSyncErrorResult(ctx, trigger, error);
|
|
5670
5892
|
} finally {
|
|
5893
|
+
await setSyncCancellationRequest(ctx, null);
|
|
5671
5894
|
activePaperclipApiAuthTokensByCompanyId = null;
|
|
5672
5895
|
activeRunningSyncState = null;
|
|
5673
5896
|
activeSyncPromise = null;
|
|
@@ -6528,6 +6751,32 @@ var plugin = definePlugin({
|
|
|
6528
6751
|
...target ? { target } : {}
|
|
6529
6752
|
});
|
|
6530
6753
|
});
|
|
6754
|
+
ctx.actions.register("sync.cancel", async () => {
|
|
6755
|
+
const currentSettings = await getActiveOrCurrentSyncState(ctx);
|
|
6756
|
+
if (currentSettings.syncState.status !== "running") {
|
|
6757
|
+
return currentSettings;
|
|
6758
|
+
}
|
|
6759
|
+
const existingRequest = currentSettings.syncState.cancelRequestedAt?.trim() ? { requestedAt: currentSettings.syncState.cancelRequestedAt.trim() } : await getSyncCancellationRequest(ctx);
|
|
6760
|
+
const cancellationRequest = existingRequest ?? {
|
|
6761
|
+
requestedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
6762
|
+
};
|
|
6763
|
+
await setSyncCancellationRequest(ctx, cancellationRequest);
|
|
6764
|
+
const next = await saveSettingsSyncState(
|
|
6765
|
+
ctx,
|
|
6766
|
+
currentSettings,
|
|
6767
|
+
createRunningSyncState(currentSettings.syncState, currentSettings.syncState.lastRunTrigger ?? "manual", {
|
|
6768
|
+
syncedIssuesCount: currentSettings.syncState.syncedIssuesCount ?? 0,
|
|
6769
|
+
createdIssuesCount: currentSettings.syncState.createdIssuesCount ?? 0,
|
|
6770
|
+
skippedIssuesCount: currentSettings.syncState.skippedIssuesCount ?? 0,
|
|
6771
|
+
erroredIssuesCount: currentSettings.syncState.erroredIssuesCount ?? 0,
|
|
6772
|
+
progress: currentSettings.syncState.progress,
|
|
6773
|
+
message: CANCELLING_SYNC_MESSAGE,
|
|
6774
|
+
cancelRequestedAt: cancellationRequest.requestedAt
|
|
6775
|
+
})
|
|
6776
|
+
);
|
|
6777
|
+
activeRunningSyncState = next;
|
|
6778
|
+
return next;
|
|
6779
|
+
});
|
|
6531
6780
|
registerGitHubAgentTools(ctx);
|
|
6532
6781
|
ctx.jobs.register("sync.github-issues", async (job) => {
|
|
6533
6782
|
const settings = normalizeSettings(await ctx.state.get(SETTINGS_SCOPE));
|