paperclip-github-plugin 0.2.1 → 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 +177 -181
- package/dist/manifest.js +1 -1
- package/dist/ui/index.js +537 -83
- package/dist/ui/index.js.map +4 -4
- package/dist/worker.js +287 -37
- 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) {
|
|
@@ -5128,6 +5301,7 @@ async function performSync(ctx, trigger, options = {}) {
|
|
|
5128
5301
|
const config = await getResolvedConfig(ctx);
|
|
5129
5302
|
const importRegistry = normalizeImportRegistry(await ctx.state.get(IMPORT_REGISTRY_SCOPE));
|
|
5130
5303
|
const token = typeof options.resolvedToken === "string" ? options.resolvedToken : await resolveGithubToken(ctx);
|
|
5304
|
+
const paperclipApiBaseUrl = getConfiguredPaperclipApiBaseUrl(settings, config);
|
|
5131
5305
|
const mappings = getSyncableMappingsForTarget(settings.mappings, options.target);
|
|
5132
5306
|
activePaperclipApiAuthTokensByCompanyId = null;
|
|
5133
5307
|
const failureContext = {
|
|
@@ -5152,7 +5326,7 @@ async function performSync(ctx, trigger, options = {}) {
|
|
|
5152
5326
|
return next;
|
|
5153
5327
|
}
|
|
5154
5328
|
const mappingsMissingBoardAccess = getMappingsMissingPaperclipBoardAccess(settings, config, mappings);
|
|
5155
|
-
if (mappingsMissingBoardAccess.length > 0 && await detectPaperclipBoardAccessRequirement(
|
|
5329
|
+
if (mappingsMissingBoardAccess.length > 0 && await detectPaperclipBoardAccessRequirement(paperclipApiBaseUrl)) {
|
|
5156
5330
|
const next = {
|
|
5157
5331
|
...settings,
|
|
5158
5332
|
syncState: createSetupConfigurationErrorSyncState("missing_board_access", trigger)
|
|
@@ -5162,6 +5336,10 @@ async function performSync(ctx, trigger, options = {}) {
|
|
|
5162
5336
|
return next;
|
|
5163
5337
|
}
|
|
5164
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
|
+
};
|
|
5165
5343
|
const next = {
|
|
5166
5344
|
...settings,
|
|
5167
5345
|
syncState: createErrorSyncState({
|
|
@@ -5171,10 +5349,14 @@ async function performSync(ctx, trigger, options = {}) {
|
|
|
5171
5349
|
createdIssuesCount: 0,
|
|
5172
5350
|
skippedIssuesCount: 0,
|
|
5173
5351
|
erroredIssuesCount: 0,
|
|
5174
|
-
errorDetails
|
|
5175
|
-
|
|
5176
|
-
|
|
5177
|
-
|
|
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
|
+
)
|
|
5178
5360
|
})
|
|
5179
5361
|
};
|
|
5180
5362
|
await ctx.state.set(SETTINGS_SCOPE, next);
|
|
@@ -5210,14 +5392,23 @@ async function performSync(ctx, trigger, options = {}) {
|
|
|
5210
5392
|
erroredIssuesCount: recoverableFailures.length,
|
|
5211
5393
|
progress: currentProgress
|
|
5212
5394
|
});
|
|
5395
|
+
async function throwIfSyncCancelled() {
|
|
5396
|
+
const cancellationRequest = await getSyncCancellationRequest(ctx);
|
|
5397
|
+
if (!cancellationRequest) {
|
|
5398
|
+
return;
|
|
5399
|
+
}
|
|
5400
|
+
throw new SyncCancellationError(cancellationRequest.requestedAt);
|
|
5401
|
+
}
|
|
5213
5402
|
async function persistRunningProgress(force = false) {
|
|
5214
5403
|
const progress = normalizeSyncProgress(currentProgress);
|
|
5404
|
+
const recentFailures = buildRecentSyncFailureLogEntries(recoverableFailures);
|
|
5215
5405
|
const signature = JSON.stringify({
|
|
5216
5406
|
syncedIssuesCount,
|
|
5217
5407
|
createdIssuesCount,
|
|
5218
5408
|
skippedIssuesCount,
|
|
5219
5409
|
erroredIssuesCount: recoverableFailures.length,
|
|
5220
|
-
progress
|
|
5410
|
+
progress,
|
|
5411
|
+
recentFailures
|
|
5221
5412
|
});
|
|
5222
5413
|
const now = Date.now();
|
|
5223
5414
|
if (!force) {
|
|
@@ -5236,7 +5427,8 @@ async function performSync(ctx, trigger, options = {}) {
|
|
|
5236
5427
|
createdIssuesCount,
|
|
5237
5428
|
skippedIssuesCount,
|
|
5238
5429
|
erroredIssuesCount: recoverableFailures.length,
|
|
5239
|
-
progress
|
|
5430
|
+
progress,
|
|
5431
|
+
recentFailures
|
|
5240
5432
|
})
|
|
5241
5433
|
);
|
|
5242
5434
|
activeRunningSyncState = currentSettings;
|
|
@@ -5253,7 +5445,9 @@ async function performSync(ctx, trigger, options = {}) {
|
|
|
5253
5445
|
}
|
|
5254
5446
|
const repositoryPlans = [];
|
|
5255
5447
|
try {
|
|
5448
|
+
await throwIfSyncCancelled();
|
|
5256
5449
|
for (const [mappingIndex, mapping] of mappings.entries()) {
|
|
5450
|
+
await throwIfSyncCancelled();
|
|
5257
5451
|
try {
|
|
5258
5452
|
const repository = requireRepositoryReference(mapping.repositoryUrl);
|
|
5259
5453
|
const importedIssueRecords = nextRegistry.filter((entry) => doesImportedIssueRecordMatchMapping(entry, mapping)).filter((entry) => doesImportedIssueMatchTarget(entry, options.target));
|
|
@@ -5276,7 +5470,7 @@ async function performSync(ctx, trigger, options = {}) {
|
|
|
5276
5470
|
updateSyncFailureContext(failureContext, {
|
|
5277
5471
|
phase: "loading_paperclip_labels"
|
|
5278
5472
|
});
|
|
5279
|
-
availableLabels = supportsPaperclipLabelMapping && companyId ? await buildPaperclipLabelDirectory(ctx, companyId,
|
|
5473
|
+
availableLabels = supportsPaperclipLabelMapping && companyId ? await buildPaperclipLabelDirectory(ctx, companyId, paperclipApiBaseUrl) : /* @__PURE__ */ new Map();
|
|
5280
5474
|
if (companyId) {
|
|
5281
5475
|
companyLabelDirectoryCache.set(companyId, availableLabels);
|
|
5282
5476
|
}
|
|
@@ -5341,7 +5535,7 @@ async function performSync(ctx, trigger, options = {}) {
|
|
|
5341
5535
|
trackedIssueCount
|
|
5342
5536
|
});
|
|
5343
5537
|
} catch (error) {
|
|
5344
|
-
if (isGitHubRateLimitError(error)) {
|
|
5538
|
+
if (error instanceof SyncCancellationError || isGitHubRateLimitError(error)) {
|
|
5345
5539
|
throw error;
|
|
5346
5540
|
}
|
|
5347
5541
|
recordRecoverableSyncFailure(ctx, recoverableFailures, error, failureContext);
|
|
@@ -5370,6 +5564,7 @@ async function performSync(ctx, trigger, options = {}) {
|
|
|
5370
5564
|
}
|
|
5371
5565
|
await persistRunningProgress(true);
|
|
5372
5566
|
for (const plan of repositoryPlans) {
|
|
5567
|
+
await throwIfSyncCancelled();
|
|
5373
5568
|
try {
|
|
5374
5569
|
const { mapping, advancedSettings, repository, repositoryIndex, allIssuesById, issues } = plan;
|
|
5375
5570
|
const companyId = mapping.companyId;
|
|
@@ -5380,7 +5575,7 @@ async function performSync(ctx, trigger, options = {}) {
|
|
|
5380
5575
|
repositoryUrl: repository.url,
|
|
5381
5576
|
githubIssueNumber: void 0
|
|
5382
5577
|
});
|
|
5383
|
-
availableLabels = supportsPaperclipLabelMapping && companyId ? await buildPaperclipLabelDirectory(ctx, companyId,
|
|
5578
|
+
availableLabels = supportsPaperclipLabelMapping && companyId ? await buildPaperclipLabelDirectory(ctx, companyId, paperclipApiBaseUrl) : /* @__PURE__ */ new Map();
|
|
5384
5579
|
if (companyId) {
|
|
5385
5580
|
companyLabelDirectoryCache.set(companyId, availableLabels);
|
|
5386
5581
|
}
|
|
@@ -5424,8 +5619,9 @@ async function performSync(ctx, trigger, options = {}) {
|
|
|
5424
5619
|
openLinkedPullRequestNumbers,
|
|
5425
5620
|
pullRequestStatusCache
|
|
5426
5621
|
);
|
|
5622
|
+
await throwIfSyncCancelled();
|
|
5427
5623
|
} catch (error) {
|
|
5428
|
-
if (isGitHubRateLimitError(error)) {
|
|
5624
|
+
if (error instanceof SyncCancellationError || isGitHubRateLimitError(error)) {
|
|
5429
5625
|
throw error;
|
|
5430
5626
|
}
|
|
5431
5627
|
}
|
|
@@ -5440,6 +5636,7 @@ async function performSync(ctx, trigger, options = {}) {
|
|
|
5440
5636
|
};
|
|
5441
5637
|
await persistRunningProgress(true);
|
|
5442
5638
|
for (const [issueIndex, issue] of issues.entries()) {
|
|
5639
|
+
await throwIfSyncCancelled();
|
|
5443
5640
|
const createdIssueCountBefore = createdIssueIds.size;
|
|
5444
5641
|
const skippedIssueCountBefore = skippedIssueIds.size;
|
|
5445
5642
|
try {
|
|
@@ -5449,7 +5646,7 @@ async function performSync(ctx, trigger, options = {}) {
|
|
|
5449
5646
|
advancedSettings,
|
|
5450
5647
|
issue,
|
|
5451
5648
|
availableLabels,
|
|
5452
|
-
|
|
5649
|
+
paperclipApiBaseUrl,
|
|
5453
5650
|
importRegistryByIssueId,
|
|
5454
5651
|
existingImportedPaperclipIssuesByUrl,
|
|
5455
5652
|
nextRegistry,
|
|
@@ -5488,6 +5685,7 @@ async function performSync(ctx, trigger, options = {}) {
|
|
|
5488
5685
|
totalIssueCount: totalTrackedIssueCount
|
|
5489
5686
|
};
|
|
5490
5687
|
await persistRunningProgress(true);
|
|
5688
|
+
await throwIfSyncCancelled();
|
|
5491
5689
|
const synchronizationResult = await synchronizePaperclipIssueStatuses(
|
|
5492
5690
|
ctx,
|
|
5493
5691
|
octokit,
|
|
@@ -5498,13 +5696,14 @@ async function performSync(ctx, trigger, options = {}) {
|
|
|
5498
5696
|
importedIssuesForSynchronization,
|
|
5499
5697
|
createdIssueIds,
|
|
5500
5698
|
availableLabels,
|
|
5501
|
-
|
|
5699
|
+
paperclipApiBaseUrl,
|
|
5502
5700
|
linkedPullRequestsByIssueNumber,
|
|
5503
5701
|
issueStatusSnapshotCache,
|
|
5504
5702
|
pullRequestStatusCache,
|
|
5505
5703
|
repositoryMaintainerCache,
|
|
5506
5704
|
failureContext,
|
|
5507
5705
|
recoverableFailures,
|
|
5706
|
+
throwIfSyncCancelled,
|
|
5508
5707
|
async (progress) => {
|
|
5509
5708
|
markTrackedIssueProcessed(mapping, progress.githubIssueId);
|
|
5510
5709
|
currentProgress = {
|
|
@@ -5523,7 +5722,7 @@ async function performSync(ctx, trigger, options = {}) {
|
|
|
5523
5722
|
updatedLabelsCount += synchronizationResult.updatedLabelsCount;
|
|
5524
5723
|
updatedDescriptionsCount += synchronizationResult.updatedDescriptionsCount;
|
|
5525
5724
|
} catch (error) {
|
|
5526
|
-
if (isGitHubRateLimitError(error)) {
|
|
5725
|
+
if (error instanceof SyncCancellationError || isGitHubRateLimitError(error)) {
|
|
5527
5726
|
throw error;
|
|
5528
5727
|
}
|
|
5529
5728
|
recordRecoverableSyncFailure(ctx, recoverableFailures, error, failureContext);
|
|
@@ -5547,7 +5746,8 @@ async function performSync(ctx, trigger, options = {}) {
|
|
|
5547
5746
|
skippedIssuesCount,
|
|
5548
5747
|
erroredIssuesCount: recoverableFailures.length,
|
|
5549
5748
|
progress: currentProgress,
|
|
5550
|
-
errorDetails
|
|
5749
|
+
errorDetails,
|
|
5750
|
+
recentFailures: buildRecentSyncFailureLogEntries(recoverableFailures)
|
|
5551
5751
|
})
|
|
5552
5752
|
};
|
|
5553
5753
|
await ctx.state.set(SETTINGS_SCOPE, next2);
|
|
@@ -5573,6 +5773,24 @@ async function performSync(ctx, trigger, options = {}) {
|
|
|
5573
5773
|
await ctx.state.set(IMPORT_REGISTRY_SCOPE, nextRegistry);
|
|
5574
5774
|
return next;
|
|
5575
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
|
+
}
|
|
5576
5794
|
const errorDetails = buildSyncErrorDetails(error, failureContext);
|
|
5577
5795
|
const next = {
|
|
5578
5796
|
...currentSettings,
|
|
@@ -5584,7 +5802,11 @@ async function performSync(ctx, trigger, options = {}) {
|
|
|
5584
5802
|
skippedIssuesCount,
|
|
5585
5803
|
erroredIssuesCount: recoverableFailures.length,
|
|
5586
5804
|
progress: currentProgress,
|
|
5587
|
-
errorDetails
|
|
5805
|
+
errorDetails,
|
|
5806
|
+
recentFailures: appendRecentSyncFailureLogEntry(
|
|
5807
|
+
buildRecentSyncFailureLogEntries(recoverableFailures),
|
|
5808
|
+
buildSyncFailureLogEntry(error, failureContext)
|
|
5809
|
+
)
|
|
5588
5810
|
})
|
|
5589
5811
|
};
|
|
5590
5812
|
await ctx.state.set(SETTINGS_SCOPE, next);
|
|
@@ -5638,6 +5860,7 @@ async function startSync(ctx, trigger, options = {}) {
|
|
|
5638
5860
|
if (trigger !== "manual" && !token.trim()) {
|
|
5639
5861
|
return currentSettings;
|
|
5640
5862
|
}
|
|
5863
|
+
await setSyncCancellationRequest(ctx, null);
|
|
5641
5864
|
const runningStatePromise = (async () => {
|
|
5642
5865
|
const syncableMappings = getSyncableMappingsForTarget(currentSettings.mappings, options.target);
|
|
5643
5866
|
const syncState = createRunningSyncState(currentSettings.syncState, trigger, {
|
|
@@ -5667,6 +5890,7 @@ async function startSync(ctx, trigger, options = {}) {
|
|
|
5667
5890
|
} catch (error) {
|
|
5668
5891
|
return await createUnexpectedSyncErrorResult(ctx, trigger, error);
|
|
5669
5892
|
} finally {
|
|
5893
|
+
await setSyncCancellationRequest(ctx, null);
|
|
5670
5894
|
activePaperclipApiAuthTokensByCompanyId = null;
|
|
5671
5895
|
activeRunningSyncState = null;
|
|
5672
5896
|
activeSyncPromise = null;
|
|
@@ -6527,6 +6751,32 @@ var plugin = definePlugin({
|
|
|
6527
6751
|
...target ? { target } : {}
|
|
6528
6752
|
});
|
|
6529
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
|
+
});
|
|
6530
6780
|
registerGitHubAgentTools(ctx);
|
|
6531
6781
|
ctx.jobs.register("sync.github-issues", async (job) => {
|
|
6532
6782
|
const settings = normalizeSettings(await ctx.state.get(SETTINGS_SCOPE));
|