paperclip-github-plugin 0.7.3 → 0.7.4
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 +12 -5
- package/dist/manifest.js +5 -1
- package/dist/ui/index.js +266 -14
- package/dist/ui/index.js.map +2 -2
- package/dist/worker.js +425 -30
- package/package.json +1 -1
package/dist/worker.js
CHANGED
|
@@ -255,6 +255,10 @@ var GITHUB_AGENT_TOOLS = [
|
|
|
255
255
|
required: ["head", "base", "title"],
|
|
256
256
|
properties: {
|
|
257
257
|
repository: repositoryProperty,
|
|
258
|
+
paperclipIssueId: {
|
|
259
|
+
type: "string",
|
|
260
|
+
description: "Optional Paperclip issue id to link with the created pull request so GitHub Sync can monitor PR status for that issue."
|
|
261
|
+
},
|
|
258
262
|
head: {
|
|
259
263
|
type: "string",
|
|
260
264
|
description: "Head branch name or owner:branch."
|
|
@@ -2696,13 +2700,11 @@ async function buildIssueGitHubDetails(ctx, input) {
|
|
|
2696
2700
|
const link = await resolvePaperclipIssueGitHubLink(ctx, issueId, companyId, {
|
|
2697
2701
|
linkRecords
|
|
2698
2702
|
});
|
|
2699
|
-
if (
|
|
2700
|
-
|
|
2701
|
-
}
|
|
2702
|
-
const entityMatch = link.entityRecord;
|
|
2703
|
-
if (entityMatch) {
|
|
2703
|
+
if (link?.entityRecord) {
|
|
2704
|
+
const entityMatch = link.entityRecord;
|
|
2704
2705
|
return {
|
|
2705
2706
|
paperclipIssueId: issueId,
|
|
2707
|
+
kind: "issue",
|
|
2706
2708
|
source: "entity",
|
|
2707
2709
|
githubIssueNumber: entityMatch.data.githubIssueNumber,
|
|
2708
2710
|
githubIssueUrl: entityMatch.data.githubIssueUrl,
|
|
@@ -2723,14 +2725,48 @@ async function buildIssueGitHubDetails(ctx, input) {
|
|
|
2723
2725
|
syncedAt: entityMatch.data.syncedAt
|
|
2724
2726
|
};
|
|
2725
2727
|
}
|
|
2728
|
+
if (link) {
|
|
2729
|
+
return {
|
|
2730
|
+
paperclipIssueId: issueId,
|
|
2731
|
+
kind: "issue",
|
|
2732
|
+
source: link.source,
|
|
2733
|
+
githubIssueNumber: link.githubIssueNumber,
|
|
2734
|
+
githubIssueUrl: link.githubIssueUrl,
|
|
2735
|
+
repositoryUrl: link.repositoryUrl,
|
|
2736
|
+
linkedPullRequestNumbers: link.linkedPullRequestNumbers,
|
|
2737
|
+
linkedPullRequests: link.linkedPullRequests
|
|
2738
|
+
};
|
|
2739
|
+
}
|
|
2740
|
+
const pullRequestLinks = await listGitHubPullRequestLinkRecords(ctx, {
|
|
2741
|
+
paperclipIssueId: issueId
|
|
2742
|
+
});
|
|
2743
|
+
const pullRequestLink = pullRequestLinks.filter((record) => !record.data.companyId || record.data.companyId === companyId).sort((left, right) => {
|
|
2744
|
+
const rightTimestamp = Date.parse(right.updatedAt ?? right.createdAt ?? "");
|
|
2745
|
+
const leftTimestamp = Date.parse(left.updatedAt ?? left.createdAt ?? "");
|
|
2746
|
+
const safeRightTimestamp = Number.isFinite(rightTimestamp) ? rightTimestamp : 0;
|
|
2747
|
+
const safeLeftTimestamp = Number.isFinite(leftTimestamp) ? leftTimestamp : 0;
|
|
2748
|
+
return safeRightTimestamp - safeLeftTimestamp;
|
|
2749
|
+
})[0];
|
|
2750
|
+
if (!pullRequestLink) {
|
|
2751
|
+
return null;
|
|
2752
|
+
}
|
|
2726
2753
|
return {
|
|
2727
2754
|
paperclipIssueId: issueId,
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2755
|
+
kind: "pull_request",
|
|
2756
|
+
source: "pull_request_entity",
|
|
2757
|
+
repositoryUrl: pullRequestLink.data.repositoryUrl,
|
|
2758
|
+
githubPullRequestNumber: pullRequestLink.data.githubPullRequestNumber,
|
|
2759
|
+
githubPullRequestUrl: pullRequestLink.data.githubPullRequestUrl,
|
|
2760
|
+
githubPullRequestState: pullRequestLink.data.githubPullRequestState,
|
|
2761
|
+
title: pullRequestLink.data.title,
|
|
2762
|
+
linkedPullRequestNumbers: [pullRequestLink.data.githubPullRequestNumber],
|
|
2763
|
+
linkedPullRequests: [
|
|
2764
|
+
{
|
|
2765
|
+
number: pullRequestLink.data.githubPullRequestNumber,
|
|
2766
|
+
repositoryUrl: pullRequestLink.data.repositoryUrl
|
|
2767
|
+
}
|
|
2768
|
+
],
|
|
2769
|
+
syncedAt: pullRequestLink.data.syncedAt
|
|
2734
2770
|
};
|
|
2735
2771
|
}
|
|
2736
2772
|
async function resolveIssueByIdentifier(ctx, input) {
|
|
@@ -6014,24 +6050,34 @@ function parseGitHubIssueHtmlUrl(value) {
|
|
|
6014
6050
|
function normalizeGitHubIssueHtmlUrl(value) {
|
|
6015
6051
|
return parseGitHubIssueHtmlUrl(value)?.issueUrl;
|
|
6016
6052
|
}
|
|
6017
|
-
function
|
|
6018
|
-
if (typeof value !== "string" || !value.trim()) {
|
|
6019
|
-
return void 0;
|
|
6020
|
-
}
|
|
6053
|
+
function parseGitHubPullRequestHtmlUrl(value) {
|
|
6021
6054
|
try {
|
|
6022
|
-
const
|
|
6023
|
-
|
|
6055
|
+
const url = new URL(value.trim());
|
|
6056
|
+
const hostname = url.hostname.trim().toLowerCase();
|
|
6057
|
+
if (hostname !== "github.com" && hostname !== "www.github.com") {
|
|
6024
6058
|
return void 0;
|
|
6025
6059
|
}
|
|
6026
|
-
const match =
|
|
6060
|
+
const match = url.pathname.match(/^\/([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+)\/pull\/(\d+)\/?$/);
|
|
6027
6061
|
if (!match) {
|
|
6028
6062
|
return void 0;
|
|
6029
6063
|
}
|
|
6030
|
-
return
|
|
6064
|
+
return {
|
|
6065
|
+
owner: match[1],
|
|
6066
|
+
repo: match[2],
|
|
6067
|
+
repositoryUrl: `https://github.com/${match[1]}/${match[2]}`,
|
|
6068
|
+
pullRequestNumber: Number(match[3]),
|
|
6069
|
+
pullRequestUrl: `https://github.com/${match[1]}/${match[2]}/pull/${match[3]}`
|
|
6070
|
+
};
|
|
6031
6071
|
} catch {
|
|
6032
6072
|
return void 0;
|
|
6033
6073
|
}
|
|
6034
6074
|
}
|
|
6075
|
+
function normalizeGitHubPullRequestHtmlUrl(value) {
|
|
6076
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
6077
|
+
return void 0;
|
|
6078
|
+
}
|
|
6079
|
+
return parseGitHubPullRequestHtmlUrl(value)?.pullRequestUrl;
|
|
6080
|
+
}
|
|
6035
6081
|
function escapeRegExp(value) {
|
|
6036
6082
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
6037
6083
|
}
|
|
@@ -6705,6 +6751,257 @@ async function upsertGitHubPullRequestLinkRecord(ctx, params) {
|
|
|
6705
6751
|
}
|
|
6706
6752
|
});
|
|
6707
6753
|
}
|
|
6754
|
+
function normalizeIssueGitHubLinkKind(value) {
|
|
6755
|
+
if (value === "issue" || value === "github_issue") {
|
|
6756
|
+
return "issue";
|
|
6757
|
+
}
|
|
6758
|
+
if (value === "pull_request" || value === "pr" || value === "github_pull_request") {
|
|
6759
|
+
return "pull_request";
|
|
6760
|
+
}
|
|
6761
|
+
return null;
|
|
6762
|
+
}
|
|
6763
|
+
function getGitHubPullRequestStateForLink(value) {
|
|
6764
|
+
return getPullRequestApiState(value) === "open" ? "open" : "closed";
|
|
6765
|
+
}
|
|
6766
|
+
async function assertPaperclipIssueHasNoManualGitHubLink(ctx, params) {
|
|
6767
|
+
const existingIssueLink = await resolvePaperclipIssueGitHubLink(ctx, params.issueId, params.companyId);
|
|
6768
|
+
if (existingIssueLink) {
|
|
6769
|
+
throw new Error("This Paperclip issue is already linked to a GitHub issue.");
|
|
6770
|
+
}
|
|
6771
|
+
const existingPullRequestLinks = await listGitHubPullRequestLinkRecords(ctx, {
|
|
6772
|
+
paperclipIssueId: params.issueId
|
|
6773
|
+
});
|
|
6774
|
+
const matchingPullRequestLink = existingPullRequestLinks.find(
|
|
6775
|
+
(record) => !record.data.companyId || record.data.companyId === params.companyId
|
|
6776
|
+
);
|
|
6777
|
+
if (matchingPullRequestLink) {
|
|
6778
|
+
throw new Error("This Paperclip issue is already linked to a GitHub pull request.");
|
|
6779
|
+
}
|
|
6780
|
+
}
|
|
6781
|
+
async function resolveIssueGitHubLinkMapping(ctx, params) {
|
|
6782
|
+
const issue = await ctx.issues.get(params.issueId, params.companyId);
|
|
6783
|
+
if (!issue) {
|
|
6784
|
+
throw new Error("Paperclip issue was not found.");
|
|
6785
|
+
}
|
|
6786
|
+
const projectId = typeof issue.projectId === "string" && issue.projectId.trim() ? issue.projectId.trim() : void 0;
|
|
6787
|
+
if (!projectId) {
|
|
6788
|
+
throw new Error("This Paperclip issue is not in a project that can be mapped to a GitHub repository.");
|
|
6789
|
+
}
|
|
6790
|
+
const settings = normalizeSettings(await ctx.state.get(SETTINGS_SCOPE));
|
|
6791
|
+
const mappings = await resolveProjectScopedMappings(ctx, settings.mappings, {
|
|
6792
|
+
companyId: params.companyId,
|
|
6793
|
+
projectId
|
|
6794
|
+
});
|
|
6795
|
+
if (mappings.length === 0) {
|
|
6796
|
+
throw new Error("This Paperclip issue project is not mapped to a GitHub repository.");
|
|
6797
|
+
}
|
|
6798
|
+
const requestedRepository = params.repositoryUrl ? parseRepositoryReference(params.repositoryUrl) : null;
|
|
6799
|
+
if (params.repositoryUrl && !requestedRepository) {
|
|
6800
|
+
throw new Error(`Invalid GitHub repository: ${params.repositoryUrl}. Use owner/repo or https://github.com/owner/repo.`);
|
|
6801
|
+
}
|
|
6802
|
+
const matchingMappings = requestedRepository ? mappings.filter(
|
|
6803
|
+
(mapping2) => areRepositoriesEqual(requireRepositoryReference(mapping2.repositoryUrl), requestedRepository)
|
|
6804
|
+
) : mappings;
|
|
6805
|
+
if (matchingMappings.length === 0 && requestedRepository) {
|
|
6806
|
+
throw new Error("The current Paperclip issue project is not mapped to the selected GitHub repository.");
|
|
6807
|
+
}
|
|
6808
|
+
if (matchingMappings.length > 1 && !requestedRepository) {
|
|
6809
|
+
throw new Error("This Paperclip issue project has multiple GitHub repositories. Enter the full GitHub URL.");
|
|
6810
|
+
}
|
|
6811
|
+
const mapping = matchingMappings[0];
|
|
6812
|
+
if (!mapping) {
|
|
6813
|
+
throw new Error("This Paperclip issue project is not mapped to a GitHub repository.");
|
|
6814
|
+
}
|
|
6815
|
+
return {
|
|
6816
|
+
issue,
|
|
6817
|
+
projectId,
|
|
6818
|
+
mapping,
|
|
6819
|
+
repository: requestedRepository ?? requireRepositoryReference(mapping.repositoryUrl)
|
|
6820
|
+
};
|
|
6821
|
+
}
|
|
6822
|
+
function resolveGitHubIssueLinkReference(input) {
|
|
6823
|
+
const reference = normalizeOptionalString2(input.reference);
|
|
6824
|
+
if (reference) {
|
|
6825
|
+
const parsedIssueUrl = parseGitHubIssueHtmlUrl(reference);
|
|
6826
|
+
if (parsedIssueUrl) {
|
|
6827
|
+
return {
|
|
6828
|
+
repositoryUrl: parsedIssueUrl.repositoryUrl,
|
|
6829
|
+
issueNumber: parsedIssueUrl.issueNumber,
|
|
6830
|
+
issueUrl: parsedIssueUrl.issueUrl
|
|
6831
|
+
};
|
|
6832
|
+
}
|
|
6833
|
+
if (parseGitHubPullRequestHtmlUrl(reference)) {
|
|
6834
|
+
throw new Error("That reference is a GitHub pull request. Choose pull request instead.");
|
|
6835
|
+
}
|
|
6836
|
+
const referenceNumber = normalizePositiveIntegerReference(reference);
|
|
6837
|
+
if (referenceNumber) {
|
|
6838
|
+
return {
|
|
6839
|
+
repositoryUrl: input.repositoryUrl,
|
|
6840
|
+
issueNumber: referenceNumber
|
|
6841
|
+
};
|
|
6842
|
+
}
|
|
6843
|
+
}
|
|
6844
|
+
const explicitIssueNumber = normalizePositiveIntegerReference(input.issueNumber);
|
|
6845
|
+
if (explicitIssueNumber) {
|
|
6846
|
+
return {
|
|
6847
|
+
repositoryUrl: input.repositoryUrl,
|
|
6848
|
+
issueNumber: explicitIssueNumber
|
|
6849
|
+
};
|
|
6850
|
+
}
|
|
6851
|
+
throw new Error("Enter a GitHub issue number or full GitHub issue URL.");
|
|
6852
|
+
}
|
|
6853
|
+
function resolveGitHubPullRequestLinkReference(input) {
|
|
6854
|
+
const explicitPullRequestUrl = normalizeGitHubPullRequestHtmlUrl(normalizeOptionalString2(input.pullRequestUrl));
|
|
6855
|
+
const parsedExplicitPullRequestUrl = explicitPullRequestUrl ? parseGitHubPullRequestHtmlUrl(explicitPullRequestUrl) : void 0;
|
|
6856
|
+
const reference = normalizeOptionalString2(input.reference);
|
|
6857
|
+
const parsedReferenceUrl = reference ? parseGitHubPullRequestHtmlUrl(reference) : void 0;
|
|
6858
|
+
if (reference && parseGitHubIssueHtmlUrl(reference)) {
|
|
6859
|
+
throw new Error("That reference is a GitHub issue. Choose issue instead.");
|
|
6860
|
+
}
|
|
6861
|
+
const parsedUrl = parsedReferenceUrl ?? parsedExplicitPullRequestUrl;
|
|
6862
|
+
const explicitPullRequestNumber = normalizePositiveIntegerReference(input.pullRequestNumber);
|
|
6863
|
+
const referenceNumber = reference && !parsedReferenceUrl ? normalizePositiveIntegerReference(reference) : void 0;
|
|
6864
|
+
const pullRequestNumber = parsedUrl?.pullRequestNumber ?? explicitPullRequestNumber ?? referenceNumber;
|
|
6865
|
+
const repositoryUrl = parsedUrl?.repositoryUrl ?? input.repositoryUrl;
|
|
6866
|
+
if (!pullRequestNumber) {
|
|
6867
|
+
throw new Error("Enter a GitHub pull request number or full GitHub pull request URL.");
|
|
6868
|
+
}
|
|
6869
|
+
if (parsedUrl && explicitPullRequestNumber && explicitPullRequestNumber !== parsedUrl.pullRequestNumber) {
|
|
6870
|
+
throw new Error("pullRequestNumber must match the supplied GitHub pull request URL.");
|
|
6871
|
+
}
|
|
6872
|
+
if (parsedUrl && input.repositoryUrl) {
|
|
6873
|
+
const requestedRepository = parseRepositoryReference(input.repositoryUrl);
|
|
6874
|
+
const urlRepository = parseRepositoryReference(parsedUrl.repositoryUrl);
|
|
6875
|
+
if (requestedRepository && urlRepository && !areRepositoriesEqual(requestedRepository, urlRepository)) {
|
|
6876
|
+
throw new Error("repository must match the supplied GitHub pull request URL.");
|
|
6877
|
+
}
|
|
6878
|
+
}
|
|
6879
|
+
return {
|
|
6880
|
+
repositoryUrl,
|
|
6881
|
+
pullRequestNumber,
|
|
6882
|
+
...parsedUrl?.pullRequestUrl ? { pullRequestUrl: parsedUrl.pullRequestUrl } : {}
|
|
6883
|
+
};
|
|
6884
|
+
}
|
|
6885
|
+
async function linkPaperclipIssueToGitHubIssue(ctx, params) {
|
|
6886
|
+
const companyId = normalizeCompanyId(params.companyId);
|
|
6887
|
+
const issueId = normalizeOptionalString2(params.issueId);
|
|
6888
|
+
if (!companyId || !issueId) {
|
|
6889
|
+
throw new Error("companyId and issueId are required.");
|
|
6890
|
+
}
|
|
6891
|
+
if (params.requireUnlinked) {
|
|
6892
|
+
await assertPaperclipIssueHasNoManualGitHubLink(ctx, {
|
|
6893
|
+
companyId,
|
|
6894
|
+
issueId
|
|
6895
|
+
});
|
|
6896
|
+
}
|
|
6897
|
+
const reference = resolveGitHubIssueLinkReference({
|
|
6898
|
+
kind: "issue",
|
|
6899
|
+
reference: params.reference,
|
|
6900
|
+
repositoryUrl: params.repositoryUrl,
|
|
6901
|
+
issueNumber: params.issueNumber
|
|
6902
|
+
});
|
|
6903
|
+
const scope = await resolveIssueGitHubLinkMapping(ctx, {
|
|
6904
|
+
companyId,
|
|
6905
|
+
issueId,
|
|
6906
|
+
repositoryUrl: reference.repositoryUrl
|
|
6907
|
+
});
|
|
6908
|
+
const octokit = await createGitHubToolOctokit(ctx, companyId);
|
|
6909
|
+
const response = await octokit.rest.issues.get({
|
|
6910
|
+
owner: scope.repository.owner,
|
|
6911
|
+
repo: scope.repository.repo,
|
|
6912
|
+
issue_number: reference.issueNumber,
|
|
6913
|
+
headers: {
|
|
6914
|
+
"X-GitHub-Api-Version": GITHUB_API_VERSION
|
|
6915
|
+
}
|
|
6916
|
+
});
|
|
6917
|
+
const rawIssue = response.data;
|
|
6918
|
+
if (rawIssue.pull_request) {
|
|
6919
|
+
throw new Error("That GitHub number is a pull request. Choose pull request instead.");
|
|
6920
|
+
}
|
|
6921
|
+
const githubIssue = normalizeGitHubIssueRecord(rawIssue);
|
|
6922
|
+
const linkedPullRequests = await listLinkedPullRequestsForIssue(octokit, scope.repository, githubIssue.number);
|
|
6923
|
+
await upsertGitHubIssueLinkRecord(ctx, scope.mapping, issueId, githubIssue, linkedPullRequests);
|
|
6924
|
+
const importRegistry = normalizeImportRegistry(await ctx.state.get(IMPORT_REGISTRY_SCOPE));
|
|
6925
|
+
upsertImportedIssueRecord(
|
|
6926
|
+
importRegistry,
|
|
6927
|
+
buildImportedIssueRecord(scope.mapping, githubIssue, issueId, (/* @__PURE__ */ new Date()).toISOString())
|
|
6928
|
+
);
|
|
6929
|
+
await ctx.state.set(IMPORT_REGISTRY_SCOPE, importRegistry);
|
|
6930
|
+
invalidateProjectPullRequestCaches({
|
|
6931
|
+
companyId,
|
|
6932
|
+
projectId: scope.mapping.paperclipProjectId ?? scope.projectId,
|
|
6933
|
+
repository: scope.repository
|
|
6934
|
+
});
|
|
6935
|
+
return {
|
|
6936
|
+
kind: "issue",
|
|
6937
|
+
paperclipIssueId: issueId,
|
|
6938
|
+
repositoryUrl: scope.repository.url,
|
|
6939
|
+
githubIssueNumber: githubIssue.number,
|
|
6940
|
+
githubIssueUrl: normalizeGitHubIssueHtmlUrl(githubIssue.htmlUrl) ?? githubIssue.htmlUrl,
|
|
6941
|
+
linkedPullRequestNumbers: linkedPullRequests.map((pullRequest) => pullRequest.number)
|
|
6942
|
+
};
|
|
6943
|
+
}
|
|
6944
|
+
async function linkPaperclipIssueToGitHubPullRequest(ctx, params) {
|
|
6945
|
+
const companyId = normalizeCompanyId(params.companyId);
|
|
6946
|
+
const issueId = normalizeOptionalString2(params.issueId);
|
|
6947
|
+
if (!companyId || !issueId) {
|
|
6948
|
+
throw new Error("companyId and issueId are required.");
|
|
6949
|
+
}
|
|
6950
|
+
if (params.requireUnlinked) {
|
|
6951
|
+
await assertPaperclipIssueHasNoManualGitHubLink(ctx, {
|
|
6952
|
+
companyId,
|
|
6953
|
+
issueId
|
|
6954
|
+
});
|
|
6955
|
+
}
|
|
6956
|
+
const reference = resolveGitHubPullRequestLinkReference({
|
|
6957
|
+
reference: params.reference,
|
|
6958
|
+
repositoryUrl: params.repositoryUrl,
|
|
6959
|
+
pullRequestNumber: params.pullRequestNumber,
|
|
6960
|
+
pullRequestUrl: params.pullRequestUrl
|
|
6961
|
+
});
|
|
6962
|
+
const scope = await resolveIssueGitHubLinkMapping(ctx, {
|
|
6963
|
+
companyId,
|
|
6964
|
+
issueId,
|
|
6965
|
+
repositoryUrl: reference.repositoryUrl
|
|
6966
|
+
});
|
|
6967
|
+
const octokit = await createGitHubToolOctokit(ctx, companyId);
|
|
6968
|
+
const response = await octokit.rest.pulls.get({
|
|
6969
|
+
owner: scope.repository.owner,
|
|
6970
|
+
repo: scope.repository.repo,
|
|
6971
|
+
pull_number: reference.pullRequestNumber,
|
|
6972
|
+
headers: {
|
|
6973
|
+
"X-GitHub-Api-Version": GITHUB_API_VERSION
|
|
6974
|
+
}
|
|
6975
|
+
});
|
|
6976
|
+
const pullRequestUrl = normalizeGitHubPullRequestHtmlUrl(response.data.html_url ?? reference.pullRequestUrl) ?? reference.pullRequestUrl ?? `${scope.repository.url}/pull/${reference.pullRequestNumber}`;
|
|
6977
|
+
const pullRequestState = getGitHubPullRequestStateForLink({
|
|
6978
|
+
state: response.data.state,
|
|
6979
|
+
merged: response.data.merged
|
|
6980
|
+
});
|
|
6981
|
+
await upsertGitHubPullRequestLinkRecord(ctx, {
|
|
6982
|
+
companyId,
|
|
6983
|
+
projectId: scope.mapping.paperclipProjectId ?? scope.projectId,
|
|
6984
|
+
issueId,
|
|
6985
|
+
repositoryUrl: scope.repository.url,
|
|
6986
|
+
pullRequestNumber: reference.pullRequestNumber,
|
|
6987
|
+
pullRequestUrl,
|
|
6988
|
+
pullRequestTitle: response.data.title || `Pull request #${reference.pullRequestNumber}`,
|
|
6989
|
+
pullRequestState
|
|
6990
|
+
});
|
|
6991
|
+
invalidateProjectPullRequestCaches({
|
|
6992
|
+
companyId,
|
|
6993
|
+
projectId: scope.mapping.paperclipProjectId ?? scope.projectId,
|
|
6994
|
+
repository: scope.repository
|
|
6995
|
+
});
|
|
6996
|
+
return {
|
|
6997
|
+
kind: "pull_request",
|
|
6998
|
+
paperclipIssueId: issueId,
|
|
6999
|
+
repositoryUrl: scope.repository.url,
|
|
7000
|
+
githubPullRequestNumber: reference.pullRequestNumber,
|
|
7001
|
+
githubPullRequestUrl: pullRequestUrl,
|
|
7002
|
+
githubPullRequestState: pullRequestState
|
|
7003
|
+
};
|
|
7004
|
+
}
|
|
6708
7005
|
async function upsertStatusTransitionCommentAnnotation(ctx, params) {
|
|
6709
7006
|
const { issueId, commentId, annotation } = params;
|
|
6710
7007
|
await ctx.entities.upsert({
|
|
@@ -8565,6 +8862,20 @@ function normalizeOptionalToolString(value) {
|
|
|
8565
8862
|
function normalizeToolPositiveInteger(value) {
|
|
8566
8863
|
return typeof value === "number" && Number.isInteger(value) && value > 0 ? value : void 0;
|
|
8567
8864
|
}
|
|
8865
|
+
function normalizePositiveIntegerReference(value) {
|
|
8866
|
+
if (typeof value === "number" && Number.isInteger(value) && value > 0) {
|
|
8867
|
+
return value;
|
|
8868
|
+
}
|
|
8869
|
+
if (typeof value !== "string") {
|
|
8870
|
+
return void 0;
|
|
8871
|
+
}
|
|
8872
|
+
const match = value.trim().match(/^#?(\d+)$/);
|
|
8873
|
+
if (!match) {
|
|
8874
|
+
return void 0;
|
|
8875
|
+
}
|
|
8876
|
+
const parsed = Number(match[1]);
|
|
8877
|
+
return Number.isInteger(parsed) && parsed > 0 ? parsed : void 0;
|
|
8878
|
+
}
|
|
8568
8879
|
function normalizeToolStringArray(value) {
|
|
8569
8880
|
if (!Array.isArray(value)) {
|
|
8570
8881
|
return [];
|
|
@@ -8722,12 +9033,34 @@ async function handleCompanyMetricApiRoute(ctx, input) {
|
|
|
8722
9033
|
const pullRequestNumber = normalizeToolPositiveInteger(payload.pullRequestNumber);
|
|
8723
9034
|
const pullRequestUrl = normalizeGitHubPullRequestHtmlUrl(normalizeOptionalString2(payload.pullRequestUrl));
|
|
8724
9035
|
const eventKey = normalizeOptionalString2(payload.eventKey);
|
|
9036
|
+
const paperclipIssueId = normalizeOptionalString2(payload.paperclipIssueId);
|
|
9037
|
+
let linkedPaperclipIssueId;
|
|
9038
|
+
let linkedRepository = null;
|
|
9039
|
+
let linkedPullRequestNumber;
|
|
9040
|
+
let linkedPullRequestUrl;
|
|
9041
|
+
if (paperclipIssueId) {
|
|
9042
|
+
const linkResult = await linkPaperclipIssueToGitHubPullRequest(ctx, {
|
|
9043
|
+
companyId,
|
|
9044
|
+
issueId: paperclipIssueId,
|
|
9045
|
+
repositoryUrl: repository?.url,
|
|
9046
|
+
pullRequestNumber,
|
|
9047
|
+
pullRequestUrl
|
|
9048
|
+
});
|
|
9049
|
+
linkedPaperclipIssueId = typeof linkResult.paperclipIssueId === "string" ? linkResult.paperclipIssueId : paperclipIssueId;
|
|
9050
|
+
const resultRepositoryUrl = normalizeOptionalString2(linkResult.repositoryUrl);
|
|
9051
|
+
linkedRepository = resultRepositoryUrl ? parseRepositoryReference(resultRepositoryUrl) : null;
|
|
9052
|
+
linkedPullRequestNumber = normalizeToolPositiveInteger(linkResult.githubPullRequestNumber);
|
|
9053
|
+
linkedPullRequestUrl = normalizeGitHubPullRequestHtmlUrl(normalizeOptionalString2(linkResult.githubPullRequestUrl));
|
|
9054
|
+
}
|
|
9055
|
+
const metricRepositoryUrl = repository?.url ?? linkedRepository?.url;
|
|
9056
|
+
const metricPullRequestNumber = pullRequestNumber ?? linkedPullRequestNumber;
|
|
9057
|
+
const metricPullRequestUrl = pullRequestUrl ?? linkedPullRequestUrl;
|
|
8725
9058
|
const dedupeKey = buildCompanyMetricEventKey({
|
|
8726
9059
|
metric,
|
|
8727
9060
|
eventKey,
|
|
8728
|
-
repositoryUrl:
|
|
8729
|
-
pullRequestNumber,
|
|
8730
|
-
pullRequestUrl
|
|
9061
|
+
repositoryUrl: metricRepositoryUrl,
|
|
9062
|
+
pullRequestNumber: metricPullRequestNumber,
|
|
9063
|
+
pullRequestUrl: metricPullRequestUrl
|
|
8731
9064
|
});
|
|
8732
9065
|
if (!dedupeKey) {
|
|
8733
9066
|
throw new Error(
|
|
@@ -8742,9 +9075,9 @@ async function handleCompanyMetricApiRoute(ctx, input) {
|
|
|
8742
9075
|
count: normalizeToolPositiveInteger(payload.count),
|
|
8743
9076
|
occurredAt: normalizeOptionalString2(payload.occurredAt),
|
|
8744
9077
|
eventKey,
|
|
8745
|
-
repositoryUrl:
|
|
8746
|
-
pullRequestNumber,
|
|
8747
|
-
pullRequestUrl
|
|
9078
|
+
repositoryUrl: metricRepositoryUrl,
|
|
9079
|
+
pullRequestNumber: metricPullRequestNumber,
|
|
9080
|
+
pullRequestUrl: metricPullRequestUrl
|
|
8748
9081
|
},
|
|
8749
9082
|
{
|
|
8750
9083
|
throwOnPersistFailure: true
|
|
@@ -8756,9 +9089,10 @@ async function handleCompanyMetricApiRoute(ctx, input) {
|
|
|
8756
9089
|
routeKey: input.routeKey,
|
|
8757
9090
|
companyId,
|
|
8758
9091
|
metric,
|
|
8759
|
-
repositoryUrl:
|
|
8760
|
-
pullRequestNumber,
|
|
8761
|
-
pullRequestUrl,
|
|
9092
|
+
repositoryUrl: metricRepositoryUrl,
|
|
9093
|
+
pullRequestNumber: metricPullRequestNumber,
|
|
9094
|
+
pullRequestUrl: metricPullRequestUrl,
|
|
9095
|
+
linkedPaperclipIssueId: linkedPaperclipIssueId ?? null,
|
|
8762
9096
|
agentId: input.actor.agentId ?? null,
|
|
8763
9097
|
runId: input.actor.runId ?? null
|
|
8764
9098
|
}
|
|
@@ -8769,7 +9103,8 @@ async function handleCompanyMetricApiRoute(ctx, input) {
|
|
|
8769
9103
|
status: recordedMetric.recorded ? "recorded" : "duplicate",
|
|
8770
9104
|
recorded: recordedMetric.recorded,
|
|
8771
9105
|
companyId,
|
|
8772
|
-
metric: "pull_request_created"
|
|
9106
|
+
metric: "pull_request_created",
|
|
9107
|
+
...linkedPaperclipIssueId ? { paperclipIssueId: linkedPaperclipIssueId } : {}
|
|
8773
9108
|
}
|
|
8774
9109
|
};
|
|
8775
9110
|
}
|
|
@@ -12727,7 +13062,13 @@ function registerGitHubAgentTools(ctx) {
|
|
|
12727
13062
|
getGitHubAgentToolDeclaration("create_pull_request"),
|
|
12728
13063
|
async (params, runCtx) => executeGitHubTool(async () => {
|
|
12729
13064
|
const input = getToolInputRecord(params);
|
|
12730
|
-
const
|
|
13065
|
+
const paperclipIssueId = normalizeOptionalToolString(input.paperclipIssueId);
|
|
13066
|
+
const explicitRepository = normalizeOptionalToolString(input.repository);
|
|
13067
|
+
const issueLinkScope = paperclipIssueId && !explicitRepository ? await resolveIssueGitHubLinkMapping(ctx, {
|
|
13068
|
+
companyId: runCtx.companyId,
|
|
13069
|
+
issueId: paperclipIssueId
|
|
13070
|
+
}) : null;
|
|
13071
|
+
const repository = issueLinkScope?.repository ?? await resolveGitHubToolRepository(ctx, runCtx, input);
|
|
12731
13072
|
const head = normalizeOptionalToolString(input.head);
|
|
12732
13073
|
const base = normalizeOptionalToolString(input.base);
|
|
12733
13074
|
const title = normalizeOptionalToolString(input.title);
|
|
@@ -12752,6 +13093,32 @@ function registerGitHubAgentTools(ctx) {
|
|
|
12752
13093
|
"X-GitHub-Api-Version": GITHUB_API_VERSION
|
|
12753
13094
|
}
|
|
12754
13095
|
});
|
|
13096
|
+
if (paperclipIssueId) {
|
|
13097
|
+
const linkScope = issueLinkScope ?? await resolveIssueGitHubLinkMapping(ctx, {
|
|
13098
|
+
companyId: runCtx.companyId,
|
|
13099
|
+
issueId: paperclipIssueId,
|
|
13100
|
+
repositoryUrl: repository.url
|
|
13101
|
+
});
|
|
13102
|
+
const pullRequestUrl = normalizeGitHubPullRequestHtmlUrl(response.data.html_url) ?? `${repository.url}/pull/${response.data.number}`;
|
|
13103
|
+
await upsertGitHubPullRequestLinkRecord(ctx, {
|
|
13104
|
+
companyId: runCtx.companyId,
|
|
13105
|
+
projectId: linkScope.mapping.paperclipProjectId ?? linkScope.projectId,
|
|
13106
|
+
issueId: paperclipIssueId,
|
|
13107
|
+
repositoryUrl: repository.url,
|
|
13108
|
+
pullRequestNumber: response.data.number,
|
|
13109
|
+
pullRequestUrl,
|
|
13110
|
+
pullRequestTitle: response.data.title || title,
|
|
13111
|
+
pullRequestState: getGitHubPullRequestStateForLink({
|
|
13112
|
+
state: response.data.state,
|
|
13113
|
+
merged: false
|
|
13114
|
+
})
|
|
13115
|
+
});
|
|
13116
|
+
invalidateProjectPullRequestCaches({
|
|
13117
|
+
companyId: runCtx.companyId,
|
|
13118
|
+
projectId: linkScope.mapping.paperclipProjectId ?? linkScope.projectId,
|
|
13119
|
+
repository
|
|
13120
|
+
});
|
|
13121
|
+
}
|
|
12755
13122
|
await persistCompanyActivityMetricEvent(
|
|
12756
13123
|
ctx,
|
|
12757
13124
|
{
|
|
@@ -13346,6 +13713,34 @@ var plugin = definePlugin({
|
|
|
13346
13713
|
const record = input && typeof input === "object" ? input : {};
|
|
13347
13714
|
return buildCommentAnnotationData(ctx, record);
|
|
13348
13715
|
});
|
|
13716
|
+
ctx.actions.register("issue.linkGitHubItem", async (input) => {
|
|
13717
|
+
const record = input && typeof input === "object" ? input : {};
|
|
13718
|
+
const kind = normalizeIssueGitHubLinkKind(record.kind);
|
|
13719
|
+
if (!kind) {
|
|
13720
|
+
throw new Error('kind must be "issue" or "pull_request".');
|
|
13721
|
+
}
|
|
13722
|
+
const companyId = normalizeCompanyId(record.companyId);
|
|
13723
|
+
const issueId = normalizeOptionalString2(record.issueId);
|
|
13724
|
+
if (!companyId || !issueId) {
|
|
13725
|
+
throw new Error("companyId and issueId are required.");
|
|
13726
|
+
}
|
|
13727
|
+
return kind === "issue" ? linkPaperclipIssueToGitHubIssue(ctx, {
|
|
13728
|
+
companyId,
|
|
13729
|
+
issueId,
|
|
13730
|
+
reference: record.reference,
|
|
13731
|
+
repositoryUrl: normalizeOptionalString2(record.repository),
|
|
13732
|
+
issueNumber: record.issueNumber,
|
|
13733
|
+
requireUnlinked: true
|
|
13734
|
+
}) : linkPaperclipIssueToGitHubPullRequest(ctx, {
|
|
13735
|
+
companyId,
|
|
13736
|
+
issueId,
|
|
13737
|
+
reference: record.reference,
|
|
13738
|
+
repositoryUrl: normalizeOptionalString2(record.repository),
|
|
13739
|
+
pullRequestNumber: record.pullRequestNumber,
|
|
13740
|
+
pullRequestUrl: record.pullRequestUrl,
|
|
13741
|
+
requireUnlinked: true
|
|
13742
|
+
});
|
|
13743
|
+
});
|
|
13349
13744
|
ctx.actions.register("settings.saveRegistration", async (input) => {
|
|
13350
13745
|
const previous = normalizeSettings(await ctx.state.get(SETTINGS_SCOPE));
|
|
13351
13746
|
const config = await getResolvedConfig(ctx);
|