paperclip-github-plugin 0.2.1 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +175 -181
- package/dist/manifest.js +1 -1
- package/dist/ui/index.js +152 -38
- package/dist/ui/index.js.map +4 -4
- package/dist/worker.js +6 -5
- package/package.json +1 -1
package/dist/ui/index.js
CHANGED
|
@@ -67,6 +67,32 @@ function requiresPaperclipBoardAccess(value) {
|
|
|
67
67
|
return health?.deploymentMode?.toLowerCase() === "authenticated";
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
// src/ui/assignees.ts
|
|
71
|
+
function normalizeCompanyAssigneeOptionsResponse(response) {
|
|
72
|
+
if (!Array.isArray(response)) {
|
|
73
|
+
throw new Error("Unexpected company agents response: expected an array.");
|
|
74
|
+
}
|
|
75
|
+
return response.map((entry) => {
|
|
76
|
+
if (!entry || typeof entry !== "object") {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
const record = entry;
|
|
80
|
+
const id = typeof record.id === "string" ? record.id.trim() : "";
|
|
81
|
+
const name = typeof record.name === "string" ? record.name.trim() : "";
|
|
82
|
+
const status = typeof record.status === "string" ? record.status.trim() : "";
|
|
83
|
+
const title = typeof record.title === "string" ? record.title.trim() : "";
|
|
84
|
+
if (!id || !name || status === "terminated") {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
id,
|
|
89
|
+
name,
|
|
90
|
+
...title ? { title } : {},
|
|
91
|
+
...status ? { status } : {}
|
|
92
|
+
};
|
|
93
|
+
}).filter((entry) => entry !== null).sort((left, right) => left.name.localeCompare(right.name));
|
|
94
|
+
}
|
|
95
|
+
|
|
70
96
|
// src/ui/http.ts
|
|
71
97
|
var JSON_CONTENT_TYPE_PATTERN = /\b(?:application\/json|[^;\s]+\/[^;\s]+\+json)\b/i;
|
|
72
98
|
var HTML_LIKE_RESPONSE_PATTERN = /^\s*</;
|
|
@@ -217,6 +243,48 @@ async function fetchPaperclipHealth(origin) {
|
|
|
217
243
|
}
|
|
218
244
|
}
|
|
219
245
|
|
|
246
|
+
// src/ui/plugin-installation.ts
|
|
247
|
+
var SETTINGS_INDEX_HREF = "/instance/settings/plugins";
|
|
248
|
+
var GITHUB_SYNC_PLUGIN_KEY = "paperclip-github-plugin";
|
|
249
|
+
var GITHUB_SYNC_PLUGIN_DISPLAY_NAME = "GitHub Sync";
|
|
250
|
+
function getStringValue(record, key) {
|
|
251
|
+
if (!record) {
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
const value = record[key];
|
|
255
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
256
|
+
}
|
|
257
|
+
function resolveGitHubSyncPluginRecord(records) {
|
|
258
|
+
if (!Array.isArray(records)) {
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
for (const entry of records) {
|
|
262
|
+
if (!entry || typeof entry !== "object") {
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
const record = entry;
|
|
266
|
+
const manifest = record.manifest && typeof record.manifest === "object" ? record.manifest : null;
|
|
267
|
+
const key = getStringValue(record, "pluginKey") ?? getStringValue(record, "key") ?? getStringValue(record, "packageName") ?? getStringValue(record, "name") ?? getStringValue(manifest, "id");
|
|
268
|
+
const displayName = getStringValue(record, "displayName") ?? getStringValue(manifest, "displayName");
|
|
269
|
+
const id = getStringValue(record, "id") ?? getStringValue(record, "pluginId");
|
|
270
|
+
if (id && (key === GITHUB_SYNC_PLUGIN_KEY || displayName === GITHUB_SYNC_PLUGIN_DISPLAY_NAME)) {
|
|
271
|
+
return record;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
function resolveInstalledGitHubSyncPluginId(records, preferredPluginId) {
|
|
277
|
+
if (typeof preferredPluginId === "string" && preferredPluginId.trim()) {
|
|
278
|
+
return preferredPluginId.trim();
|
|
279
|
+
}
|
|
280
|
+
const record = resolveGitHubSyncPluginRecord(records);
|
|
281
|
+
return getStringValue(record, "id") ?? getStringValue(record, "pluginId");
|
|
282
|
+
}
|
|
283
|
+
function resolvePluginSettingsHref(records) {
|
|
284
|
+
const pluginId = resolveInstalledGitHubSyncPluginId(records);
|
|
285
|
+
return pluginId ? `${SETTINGS_INDEX_HREF}/${pluginId}` : SETTINGS_INDEX_HREF;
|
|
286
|
+
}
|
|
287
|
+
|
|
220
288
|
// src/ui/plugin-config.ts
|
|
221
289
|
function normalizeOptionalString2(value) {
|
|
222
290
|
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
@@ -2681,24 +2749,39 @@ function getPaperclipApiBaseUrl() {
|
|
|
2681
2749
|
return window.location.origin;
|
|
2682
2750
|
}
|
|
2683
2751
|
var syncedPaperclipApiBaseUrlsByPluginId = /* @__PURE__ */ new Map();
|
|
2752
|
+
var installedGitHubSyncPluginIdPromise = null;
|
|
2753
|
+
async function resolveCurrentPluginId(pluginId) {
|
|
2754
|
+
if (pluginId) {
|
|
2755
|
+
return pluginId;
|
|
2756
|
+
}
|
|
2757
|
+
if (!installedGitHubSyncPluginIdPromise) {
|
|
2758
|
+
installedGitHubSyncPluginIdPromise = fetchJson("/api/plugins").then((records) => resolveInstalledGitHubSyncPluginId(records)).catch(() => null);
|
|
2759
|
+
}
|
|
2760
|
+
const resolvedPluginId = await installedGitHubSyncPluginIdPromise;
|
|
2761
|
+
if (!resolvedPluginId) {
|
|
2762
|
+
installedGitHubSyncPluginIdPromise = null;
|
|
2763
|
+
}
|
|
2764
|
+
return resolvedPluginId;
|
|
2765
|
+
}
|
|
2684
2766
|
async function syncTrustedPaperclipApiBaseUrl(pluginId) {
|
|
2685
2767
|
const paperclipApiBaseUrl = getPaperclipApiBaseUrl();
|
|
2686
2768
|
if (!paperclipApiBaseUrl) {
|
|
2687
2769
|
return void 0;
|
|
2688
2770
|
}
|
|
2689
|
-
|
|
2771
|
+
const resolvedPluginId = await resolveCurrentPluginId(pluginId);
|
|
2772
|
+
if (!resolvedPluginId) {
|
|
2690
2773
|
throw new Error(
|
|
2691
2774
|
"Unable to sync the trusted Paperclip API origin because the plugin ID is missing. Reload the plugin and try again before saving or syncing."
|
|
2692
2775
|
);
|
|
2693
2776
|
}
|
|
2694
|
-
const lastSyncedPaperclipApiBaseUrl = syncedPaperclipApiBaseUrlsByPluginId.get(
|
|
2777
|
+
const lastSyncedPaperclipApiBaseUrl = syncedPaperclipApiBaseUrlsByPluginId.get(resolvedPluginId);
|
|
2695
2778
|
if (lastSyncedPaperclipApiBaseUrl === paperclipApiBaseUrl) {
|
|
2696
2779
|
return paperclipApiBaseUrl;
|
|
2697
2780
|
}
|
|
2698
|
-
await patchPluginConfig(
|
|
2781
|
+
await patchPluginConfig(resolvedPluginId, {
|
|
2699
2782
|
paperclipApiBaseUrl
|
|
2700
2783
|
});
|
|
2701
|
-
syncedPaperclipApiBaseUrlsByPluginId.set(
|
|
2784
|
+
syncedPaperclipApiBaseUrlsByPluginId.set(resolvedPluginId, paperclipApiBaseUrl);
|
|
2702
2785
|
return paperclipApiBaseUrl;
|
|
2703
2786
|
}
|
|
2704
2787
|
function formatDate(value, fallback = "Never") {
|
|
@@ -2804,6 +2887,11 @@ async function listCompanyProjects(companyId) {
|
|
|
2804
2887
|
return id && name ? { id, name } : null;
|
|
2805
2888
|
}).filter((entry) => entry !== null);
|
|
2806
2889
|
}
|
|
2890
|
+
async function listCompanyAssigneeOptions(companyId) {
|
|
2891
|
+
return normalizeCompanyAssigneeOptionsResponse(
|
|
2892
|
+
await fetchJson(`/api/companies/${companyId}/agents`)
|
|
2893
|
+
);
|
|
2894
|
+
}
|
|
2807
2895
|
async function listProjectWorkspaces(projectId) {
|
|
2808
2896
|
const response = await fetchJson(`/api/projects/${projectId}/workspaces`);
|
|
2809
2897
|
if (!Array.isArray(response)) {
|
|
@@ -3018,9 +3106,9 @@ function getToneClass(tone) {
|
|
|
3018
3106
|
return "ghsync__badge--neutral";
|
|
3019
3107
|
}
|
|
3020
3108
|
}
|
|
3021
|
-
var
|
|
3109
|
+
var SETTINGS_INDEX_HREF2 = "/instance/settings/plugins";
|
|
3022
3110
|
var GITHUB_SYNC_SETTINGS_UPDATED_EVENT = "paperclip-github-plugin:settings-updated";
|
|
3023
|
-
function
|
|
3111
|
+
function getStringValue2(record, key) {
|
|
3024
3112
|
const value = record[key];
|
|
3025
3113
|
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
3026
3114
|
}
|
|
@@ -3038,12 +3126,12 @@ function humanizeCompanyPrefix(value) {
|
|
|
3038
3126
|
return trimmed.replace(/[-_]+/g, " ").replace(/\s+/g, " ").trim().replace(/\b\w/g, (character) => character.toUpperCase());
|
|
3039
3127
|
}
|
|
3040
3128
|
function getCompanyLabelFromRecord(record) {
|
|
3041
|
-
const explicitLabel =
|
|
3129
|
+
const explicitLabel = getStringValue2(record, "displayName") ?? getStringValue2(record, "name") ?? getStringValue2(record, "title");
|
|
3042
3130
|
if (explicitLabel && !isUuidLike(explicitLabel)) {
|
|
3043
3131
|
return explicitLabel;
|
|
3044
3132
|
}
|
|
3045
3133
|
return humanizeCompanyPrefix(
|
|
3046
|
-
|
|
3134
|
+
getStringValue2(record, "companyPrefix") ?? getStringValue2(record, "prefix") ?? getStringValue2(record, "slug")
|
|
3047
3135
|
);
|
|
3048
3136
|
}
|
|
3049
3137
|
async function resolveCompanyScopeLabel(companyId, companyPrefix) {
|
|
@@ -3069,8 +3157,8 @@ async function resolveCompanyScopeLabel(companyId, companyPrefix) {
|
|
|
3069
3157
|
return false;
|
|
3070
3158
|
}
|
|
3071
3159
|
const record = entry;
|
|
3072
|
-
const entryId =
|
|
3073
|
-
const entryPrefix =
|
|
3160
|
+
const entryId = getStringValue2(record, "id");
|
|
3161
|
+
const entryPrefix = getStringValue2(record, "companyPrefix") ?? getStringValue2(record, "prefix") ?? getStringValue2(record, "slug");
|
|
3074
3162
|
return entryId === companyId || Boolean(companyPrefix && entryPrefix === companyPrefix);
|
|
3075
3163
|
});
|
|
3076
3164
|
if (matchingCompany && typeof matchingCompany === "object") {
|
|
@@ -3154,25 +3242,6 @@ function useResolvedIssueId(params) {
|
|
|
3154
3242
|
loading: false
|
|
3155
3243
|
};
|
|
3156
3244
|
}
|
|
3157
|
-
function resolvePluginSettingsHref(records) {
|
|
3158
|
-
if (!Array.isArray(records)) {
|
|
3159
|
-
return SETTINGS_INDEX_HREF;
|
|
3160
|
-
}
|
|
3161
|
-
for (const entry of records) {
|
|
3162
|
-
if (!entry || typeof entry !== "object") {
|
|
3163
|
-
continue;
|
|
3164
|
-
}
|
|
3165
|
-
const record = entry;
|
|
3166
|
-
const manifest = record.manifest && typeof record.manifest === "object" ? record.manifest : null;
|
|
3167
|
-
const id = getStringValue(record, "id") ?? getStringValue(record, "pluginId");
|
|
3168
|
-
const key = getStringValue(record, "pluginKey") ?? getStringValue(record, "key") ?? getStringValue(record, "packageName") ?? getStringValue(record, "name") ?? (manifest ? getStringValue(manifest, "id") : null);
|
|
3169
|
-
const displayName = getStringValue(record, "displayName") ?? (manifest ? getStringValue(manifest, "displayName") : null);
|
|
3170
|
-
if (id && (key === "paperclip-github-plugin" || displayName === "GitHub Sync")) {
|
|
3171
|
-
return `${SETTINGS_INDEX_HREF}/${id}`;
|
|
3172
|
-
}
|
|
3173
|
-
}
|
|
3174
|
-
return SETTINGS_INDEX_HREF;
|
|
3175
|
-
}
|
|
3176
3245
|
function formatSyncProgressRepository(repositoryUrl) {
|
|
3177
3246
|
if (!repositoryUrl?.trim()) {
|
|
3178
3247
|
return null;
|
|
@@ -3580,10 +3649,12 @@ function GitHubSyncSettingsPage() {
|
|
|
3580
3649
|
const [existingProjectCandidates, setExistingProjectCandidates] = useState([]);
|
|
3581
3650
|
const [existingProjectCandidatesLoading, setExistingProjectCandidatesLoading] = useState(false);
|
|
3582
3651
|
const [existingProjectCandidatesError, setExistingProjectCandidatesError] = useState(null);
|
|
3652
|
+
const [browserAvailableAssignees, setBrowserAvailableAssignees] = useState([]);
|
|
3583
3653
|
const themeMode = useResolvedThemeMode();
|
|
3584
3654
|
const boardAccessRequirement = usePaperclipBoardAccessRequirement();
|
|
3585
3655
|
const armSyncCompletionToast = useSyncCompletionToast(form.syncState, toast);
|
|
3586
3656
|
const boardAccessConfigSyncAttemptRef = useRef(null);
|
|
3657
|
+
const assigneeFallbackAttemptRef = useRef(null);
|
|
3587
3658
|
const currentSettings = settings.data ?? cachedSettings;
|
|
3588
3659
|
const showInitialLoadingState = settings.loading && !settings.data && !cachedSettings;
|
|
3589
3660
|
useEffect(() => {
|
|
@@ -3659,10 +3730,47 @@ function GitHubSyncSettingsPage() {
|
|
|
3659
3730
|
cancelled = true;
|
|
3660
3731
|
};
|
|
3661
3732
|
}, [hostContext.companyId, settings.data?.updatedAt, tokenStatusOverride]);
|
|
3733
|
+
useEffect(() => {
|
|
3734
|
+
const companyId = hostContext.companyId;
|
|
3735
|
+
const workerAvailableAssignees = currentSettings?.availableAssignees ?? [];
|
|
3736
|
+
const snapshotKey = `${companyId ?? "none"}:${currentSettings?.updatedAt ?? "none"}`;
|
|
3737
|
+
if (!companyId) {
|
|
3738
|
+
assigneeFallbackAttemptRef.current = null;
|
|
3739
|
+
setBrowserAvailableAssignees([]);
|
|
3740
|
+
return;
|
|
3741
|
+
}
|
|
3742
|
+
if (workerAvailableAssignees.length > 0) {
|
|
3743
|
+
assigneeFallbackAttemptRef.current = snapshotKey;
|
|
3744
|
+
setBrowserAvailableAssignees([]);
|
|
3745
|
+
return;
|
|
3746
|
+
}
|
|
3747
|
+
if (assigneeFallbackAttemptRef.current === snapshotKey) {
|
|
3748
|
+
return;
|
|
3749
|
+
}
|
|
3750
|
+
assigneeFallbackAttemptRef.current = snapshotKey;
|
|
3751
|
+
let cancelled = false;
|
|
3752
|
+
void (async () => {
|
|
3753
|
+
try {
|
|
3754
|
+
const assignees = await listCompanyAssigneeOptions(companyId);
|
|
3755
|
+
if (cancelled) {
|
|
3756
|
+
return;
|
|
3757
|
+
}
|
|
3758
|
+
setBrowserAvailableAssignees(assignees);
|
|
3759
|
+
} catch {
|
|
3760
|
+
if (cancelled) {
|
|
3761
|
+
return;
|
|
3762
|
+
}
|
|
3763
|
+
setBrowserAvailableAssignees([]);
|
|
3764
|
+
}
|
|
3765
|
+
})();
|
|
3766
|
+
return () => {
|
|
3767
|
+
cancelled = true;
|
|
3768
|
+
};
|
|
3769
|
+
}, [currentSettings?.availableAssignees?.length, currentSettings?.updatedAt, hostContext.companyId]);
|
|
3662
3770
|
useEffect(() => {
|
|
3663
3771
|
const companyId = hostContext.companyId;
|
|
3664
3772
|
const secretRef = settings.data?.paperclipBoardAccessNeedsConfigSync ? settings.data.paperclipBoardAccessConfigSyncRef : void 0;
|
|
3665
|
-
if (!companyId || !
|
|
3773
|
+
if (!companyId || !secretRef) {
|
|
3666
3774
|
return;
|
|
3667
3775
|
}
|
|
3668
3776
|
const attemptKey = `${companyId}:${secretRef}`;
|
|
@@ -3673,7 +3781,11 @@ function GitHubSyncSettingsPage() {
|
|
|
3673
3781
|
let cancelled = false;
|
|
3674
3782
|
void (async () => {
|
|
3675
3783
|
try {
|
|
3676
|
-
await
|
|
3784
|
+
const pluginId = await resolveCurrentPluginId(pluginIdFromLocation);
|
|
3785
|
+
if (!pluginId) {
|
|
3786
|
+
throw new Error("Plugin id is required to finish syncing Paperclip board access into plugin config.");
|
|
3787
|
+
}
|
|
3788
|
+
await patchPluginConfig(pluginId, {
|
|
3677
3789
|
paperclipBoardApiTokenRefs: {
|
|
3678
3790
|
[companyId]: secretRef
|
|
3679
3791
|
}
|
|
@@ -3760,7 +3872,7 @@ function GitHubSyncSettingsPage() {
|
|
|
3760
3872
|
const boardAccessSectionDescription = "";
|
|
3761
3873
|
const repositoriesUnlocked = tokenStatus === "valid";
|
|
3762
3874
|
const availableAssignees = getAvailableAssigneeOptions(
|
|
3763
|
-
currentSettings?.availableAssignees ?? form.availableAssignees,
|
|
3875
|
+
(currentSettings?.availableAssignees?.length ? currentSettings.availableAssignees : null) ?? (form.availableAssignees?.length ? form.availableAssignees : null) ?? browserAvailableAssignees,
|
|
3764
3876
|
form.advancedSettings.defaultAssigneeAgentId
|
|
3765
3877
|
);
|
|
3766
3878
|
const savedMappingsSource = currentSettings ? currentSettings.mappings ?? [] : form.mappings;
|
|
@@ -3946,13 +4058,14 @@ function GitHubSyncSettingsPage() {
|
|
|
3946
4058
|
if (!companyId) {
|
|
3947
4059
|
throw new Error("Company context is required to save the GitHub token.");
|
|
3948
4060
|
}
|
|
3949
|
-
|
|
4061
|
+
const pluginId = await resolveCurrentPluginId(pluginIdFromLocation);
|
|
4062
|
+
if (!pluginId) {
|
|
3950
4063
|
throw new Error("Plugin id is required to save the GitHub token.");
|
|
3951
4064
|
}
|
|
3952
4065
|
const trimmedToken = tokenDraft.trim();
|
|
3953
4066
|
const secretName = `github_sync_${companyId.replace(/[^a-z0-9]+/gi, "_").toLowerCase()}`;
|
|
3954
4067
|
const secret = await resolveOrCreateCompanySecret(companyId, secretName, trimmedToken);
|
|
3955
|
-
await patchPluginConfig(
|
|
4068
|
+
await patchPluginConfig(pluginId, {
|
|
3956
4069
|
githubTokenRef: secret.id
|
|
3957
4070
|
});
|
|
3958
4071
|
await saveRegistration({
|
|
@@ -3997,7 +4110,8 @@ function GitHubSyncSettingsPage() {
|
|
|
3997
4110
|
if (!companyId) {
|
|
3998
4111
|
throw new Error("Company context is required to connect Paperclip board access.");
|
|
3999
4112
|
}
|
|
4000
|
-
|
|
4113
|
+
const pluginId = await resolveCurrentPluginId(pluginIdFromLocation);
|
|
4114
|
+
if (!pluginId) {
|
|
4001
4115
|
throw new Error("Plugin id is required to connect Paperclip board access.");
|
|
4002
4116
|
}
|
|
4003
4117
|
if (typeof window !== "undefined") {
|
|
@@ -4020,7 +4134,7 @@ function GitHubSyncSettingsPage() {
|
|
|
4020
4134
|
const identity = await fetchBoardAccessIdentity(boardApiToken);
|
|
4021
4135
|
const secretName = `paperclip_board_api_${companyId.replace(/[^a-z0-9]+/gi, "_").toLowerCase()}`;
|
|
4022
4136
|
const secret = await resolveOrCreateCompanySecret(companyId, secretName, boardApiToken);
|
|
4023
|
-
await patchPluginConfig(
|
|
4137
|
+
await patchPluginConfig(pluginId, {
|
|
4024
4138
|
paperclipBoardApiTokenRefs: {
|
|
4025
4139
|
[companyId]: secret.id
|
|
4026
4140
|
}
|
|
@@ -4729,7 +4843,7 @@ function GitHubSyncDashboardWidget() {
|
|
|
4729
4843
|
const runSyncNow = usePluginAction("sync.runNow");
|
|
4730
4844
|
const [runningSync, setRunningSync] = useState(false);
|
|
4731
4845
|
const [manualSyncRequestError, setManualSyncRequestError] = useState(null);
|
|
4732
|
-
const [settingsHref, setSettingsHref] = useState(
|
|
4846
|
+
const [settingsHref, setSettingsHref] = useState(SETTINGS_INDEX_HREF2);
|
|
4733
4847
|
const [cachedSettings, setCachedSettings] = useState(null);
|
|
4734
4848
|
const themeMode = useResolvedThemeMode();
|
|
4735
4849
|
const boardAccessRequirement = usePaperclipBoardAccessRequirement();
|
|
@@ -4792,7 +4906,7 @@ function GitHubSyncDashboardWidget() {
|
|
|
4792
4906
|
}
|
|
4793
4907
|
} catch {
|
|
4794
4908
|
if (!cancelled) {
|
|
4795
|
-
setSettingsHref(
|
|
4909
|
+
setSettingsHref(SETTINGS_INDEX_HREF2);
|
|
4796
4910
|
}
|
|
4797
4911
|
}
|
|
4798
4912
|
}
|