paperclip-github-plugin 0.9.0 → 0.9.1
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 +6 -2
- package/dist/manifest.js +1 -1
- package/dist/ui/index.js +100 -2
- package/dist/ui/index.js.map +2 -2
- package/dist/worker.js +150 -6
- package/package.json +1 -1
package/dist/worker.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// src/worker.ts
|
|
2
2
|
import { Buffer } from "node:buffer";
|
|
3
3
|
import { realpathSync } from "node:fs";
|
|
4
|
-
import { readFile } from "node:fs/promises";
|
|
4
|
+
import { chmod, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
5
5
|
import { homedir } from "node:os";
|
|
6
|
-
import { join, resolve } from "node:path";
|
|
6
|
+
import { dirname, join, resolve } from "node:path";
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
8
|
import { Octokit } from "@octokit/rest";
|
|
9
9
|
import {
|
|
@@ -1604,6 +1604,10 @@ function getErrorMessage(error) {
|
|
|
1604
1604
|
}
|
|
1605
1605
|
return String(error);
|
|
1606
1606
|
}
|
|
1607
|
+
function isPluginSecretReferenceDisabledError(error) {
|
|
1608
|
+
const message = getErrorMessage(error).toLowerCase();
|
|
1609
|
+
return message.includes("plugin secret reference") && message.includes("disabled") || message.includes("company-scoped plugin config lands");
|
|
1610
|
+
}
|
|
1607
1611
|
function getErrorCause(error) {
|
|
1608
1612
|
if (!error || typeof error !== "object" || !("cause" in error)) {
|
|
1609
1613
|
return void 0;
|
|
@@ -1974,6 +1978,20 @@ function normalizeGitHubTokenRefs(value) {
|
|
|
1974
1978
|
}
|
|
1975
1979
|
return Object.fromEntries(entries);
|
|
1976
1980
|
}
|
|
1981
|
+
function normalizeGitHubTokensByCompanyId(value) {
|
|
1982
|
+
if (!value || typeof value !== "object") {
|
|
1983
|
+
return void 0;
|
|
1984
|
+
}
|
|
1985
|
+
const entries = Object.entries(value).map(([companyId, token]) => {
|
|
1986
|
+
const normalizedCompanyId = normalizeCompanyId(companyId);
|
|
1987
|
+
const normalizedToken = normalizeGitHubToken(token);
|
|
1988
|
+
return normalizedCompanyId && normalizedToken ? [normalizedCompanyId, normalizedToken] : null;
|
|
1989
|
+
}).filter((entry) => entry !== null);
|
|
1990
|
+
if (entries.length === 0) {
|
|
1991
|
+
return void 0;
|
|
1992
|
+
}
|
|
1993
|
+
return Object.fromEntries(entries);
|
|
1994
|
+
}
|
|
1977
1995
|
function formatUtcTimestamp(value) {
|
|
1978
1996
|
const parsed = new Date(value);
|
|
1979
1997
|
if (Number.isNaN(parsed.getTime())) {
|
|
@@ -3710,12 +3728,14 @@ function normalizeConfig(value) {
|
|
|
3710
3728
|
const githubTokenRefs = normalizeGitHubTokenRefs(record.githubTokenRefs);
|
|
3711
3729
|
const githubTokenRef = normalizeGitHubTokenRef(record.githubTokenRef);
|
|
3712
3730
|
const githubToken = normalizeGitHubToken(record.githubToken);
|
|
3731
|
+
const githubTokensByCompanyId = normalizeGitHubTokensByCompanyId(record.githubTokensByCompanyId);
|
|
3713
3732
|
const paperclipBoardApiTokenRefs = normalizePaperclipBoardApiTokenRefs(record.paperclipBoardApiTokenRefs);
|
|
3714
3733
|
const paperclipApiBaseUrl = normalizePaperclipApiBaseUrl(record.paperclipApiBaseUrl);
|
|
3715
3734
|
return {
|
|
3716
3735
|
...githubTokenRefs ? { githubTokenRefs } : {},
|
|
3717
3736
|
...githubTokenRef ? { githubTokenRef } : {},
|
|
3718
3737
|
...githubToken ? { githubToken } : {},
|
|
3738
|
+
...githubTokensByCompanyId ? { githubTokensByCompanyId } : {},
|
|
3719
3739
|
...paperclipBoardApiTokenRefs ? { paperclipBoardApiTokenRefs } : {},
|
|
3720
3740
|
...paperclipApiBaseUrl ? { paperclipApiBaseUrl } : {}
|
|
3721
3741
|
};
|
|
@@ -3791,6 +3811,55 @@ async function readExternalConfig(ctx) {
|
|
|
3791
3811
|
return {};
|
|
3792
3812
|
}
|
|
3793
3813
|
}
|
|
3814
|
+
async function readExternalConfigRecordForWrite(ctx, filePath) {
|
|
3815
|
+
try {
|
|
3816
|
+
const rawConfig = await readFile(filePath, "utf8");
|
|
3817
|
+
const parsedConfig = JSON.parse(rawConfig);
|
|
3818
|
+
return parsedConfig && typeof parsedConfig === "object" && !Array.isArray(parsedConfig) ? { ...parsedConfig } : {};
|
|
3819
|
+
} catch (error) {
|
|
3820
|
+
const errorCode = error && typeof error === "object" && "code" in error ? error.code : void 0;
|
|
3821
|
+
if (errorCode === "ENOENT") {
|
|
3822
|
+
return {};
|
|
3823
|
+
}
|
|
3824
|
+
if (error instanceof SyntaxError) {
|
|
3825
|
+
ctx.logger.warn("Ignoring the GitHub Sync worker-local token fallback config file because it is not valid JSON.", {
|
|
3826
|
+
filePath,
|
|
3827
|
+
error: error.message
|
|
3828
|
+
});
|
|
3829
|
+
return {};
|
|
3830
|
+
}
|
|
3831
|
+
throw error;
|
|
3832
|
+
}
|
|
3833
|
+
}
|
|
3834
|
+
async function writeExternalCompanyGitHubTokenFallback(ctx, companyId, token) {
|
|
3835
|
+
const externalConfigFilePath = getExternalConfigFilePath();
|
|
3836
|
+
if (!externalConfigFilePath) {
|
|
3837
|
+
throw new Error("Could not resolve a Paperclip home directory for the GitHub Sync fallback token config.");
|
|
3838
|
+
}
|
|
3839
|
+
const currentRecord = await readExternalConfigRecordForWrite(ctx, externalConfigFilePath);
|
|
3840
|
+
const currentCompanyTokens = normalizeGitHubTokensByCompanyId(currentRecord.githubTokensByCompanyId) ?? {};
|
|
3841
|
+
const nextRecord = {
|
|
3842
|
+
...currentRecord,
|
|
3843
|
+
githubTokensByCompanyId: {
|
|
3844
|
+
...currentCompanyTokens,
|
|
3845
|
+
[companyId]: token
|
|
3846
|
+
}
|
|
3847
|
+
};
|
|
3848
|
+
await mkdir(dirname(externalConfigFilePath), { recursive: true });
|
|
3849
|
+
await writeFile(externalConfigFilePath, `${JSON.stringify(nextRecord, null, 2)}
|
|
3850
|
+
`, {
|
|
3851
|
+
encoding: "utf8",
|
|
3852
|
+
mode: 384
|
|
3853
|
+
});
|
|
3854
|
+
try {
|
|
3855
|
+
await chmod(externalConfigFilePath, 384);
|
|
3856
|
+
} catch (error) {
|
|
3857
|
+
ctx.logger.warn("GitHub Sync could not tighten permissions on the worker-local token fallback file.", {
|
|
3858
|
+
filePath: externalConfigFilePath,
|
|
3859
|
+
error: getErrorMessage(error)
|
|
3860
|
+
});
|
|
3861
|
+
}
|
|
3862
|
+
}
|
|
3794
3863
|
function normalizePaperclipBoardApiTokenRefs(value) {
|
|
3795
3864
|
if (!value || typeof value !== "object") {
|
|
3796
3865
|
return void 0;
|
|
@@ -9985,6 +10054,7 @@ async function getResolvedConfig(ctx) {
|
|
|
9985
10054
|
}
|
|
9986
10055
|
function getConfiguredGithubTokenSource(settings, config, companyId) {
|
|
9987
10056
|
const normalizedCompanyId = normalizeCompanyId(companyId);
|
|
10057
|
+
const companyFallbackToken = normalizedCompanyId ? normalizeGitHubToken(config.githubTokensByCompanyId?.[normalizedCompanyId]) : void 0;
|
|
9988
10058
|
const hasScopedGitHubTokenRefs = hasAnyScopedValue(settings?.githubTokenRefs) || hasAnyScopedValue(config.githubTokenRefs);
|
|
9989
10059
|
const secretRef = normalizedCompanyId ? normalizeSecretRef(config.githubTokenRefs?.[normalizedCompanyId]) ?? normalizeSecretRef(settings?.githubTokenRefs?.[normalizedCompanyId]) ?? (!hasScopedGitHubTokenRefs ? normalizeGitHubTokenRef(config.githubTokenRef) ?? normalizeGitHubTokenRef(settings?.githubTokenRef) : void 0) : normalizeGitHubTokenRef(config.githubTokenRef) ?? normalizeGitHubTokenRef(settings?.githubTokenRef) ?? (() => {
|
|
9990
10060
|
const configuredRefs = [
|
|
@@ -9995,14 +10065,17 @@ function getConfiguredGithubTokenSource(settings, config, companyId) {
|
|
|
9995
10065
|
return uniqueRefs.length === 1 ? uniqueRefs[0] : void 0;
|
|
9996
10066
|
})();
|
|
9997
10067
|
if (secretRef) {
|
|
9998
|
-
return {
|
|
10068
|
+
return {
|
|
10069
|
+
secretRef,
|
|
10070
|
+
...companyFallbackToken ? { fallbackToken: companyFallbackToken } : {}
|
|
10071
|
+
};
|
|
9999
10072
|
}
|
|
10000
|
-
const token = !normalizedCompanyId || !hasScopedGitHubTokenRefs ? normalizeGitHubToken(config.githubToken) : void 0;
|
|
10073
|
+
const token = companyFallbackToken ?? (!normalizedCompanyId || !hasScopedGitHubTokenRefs ? normalizeGitHubToken(config.githubToken) : void 0);
|
|
10001
10074
|
return token ? { token } : {};
|
|
10002
10075
|
}
|
|
10003
10076
|
function hasConfiguredGithubToken(settings, config, companyId) {
|
|
10004
10077
|
const configuredTokenSource = getConfiguredGithubTokenSource(settings, config, companyId);
|
|
10005
|
-
if (configuredTokenSource.secretRef ?? configuredTokenSource.token) {
|
|
10078
|
+
if (configuredTokenSource.secretRef ?? configuredTokenSource.token ?? configuredTokenSource.fallbackToken) {
|
|
10006
10079
|
return true;
|
|
10007
10080
|
}
|
|
10008
10081
|
if (normalizeCompanyId(companyId)) {
|
|
@@ -10012,6 +10085,18 @@ function hasConfiguredGithubToken(settings, config, companyId) {
|
|
|
10012
10085
|
settings?.githubTokenRefs && Object.keys(settings.githubTokenRefs).length > 0 || config.githubTokenRefs && Object.keys(config.githubTokenRefs).length > 0
|
|
10013
10086
|
);
|
|
10014
10087
|
}
|
|
10088
|
+
function getSavedGitHubTokenRef(settings, companyId) {
|
|
10089
|
+
if (!companyId) {
|
|
10090
|
+
return void 0;
|
|
10091
|
+
}
|
|
10092
|
+
return normalizeSecretRef(settings?.githubTokenRefs?.[companyId]);
|
|
10093
|
+
}
|
|
10094
|
+
function getConfiguredGitHubTokenRef(config, companyId) {
|
|
10095
|
+
if (!companyId) {
|
|
10096
|
+
return void 0;
|
|
10097
|
+
}
|
|
10098
|
+
return normalizeSecretRef(config?.githubTokenRefs?.[companyId]);
|
|
10099
|
+
}
|
|
10015
10100
|
function getSavedPaperclipBoardApiTokenRef(settings, companyId) {
|
|
10016
10101
|
if (!companyId) {
|
|
10017
10102
|
return void 0;
|
|
@@ -10095,7 +10180,23 @@ async function resolveGithubToken(ctx, options = {}) {
|
|
|
10095
10180
|
const config = options.config ?? await getResolvedConfig(ctx);
|
|
10096
10181
|
const configuredTokenSource = getConfiguredGithubTokenSource(settings, config, options.companyId);
|
|
10097
10182
|
if (configuredTokenSource.secretRef) {
|
|
10098
|
-
|
|
10183
|
+
try {
|
|
10184
|
+
const token = (await ctx.secrets.resolve(configuredTokenSource.secretRef)).trim();
|
|
10185
|
+
if (token) {
|
|
10186
|
+
return token;
|
|
10187
|
+
}
|
|
10188
|
+
return configuredTokenSource.fallbackToken ?? "";
|
|
10189
|
+
} catch (error) {
|
|
10190
|
+
if (configuredTokenSource.fallbackToken && isPluginSecretReferenceDisabledError(error)) {
|
|
10191
|
+
ctx.logger.warn("GitHub Sync is using a worker-local company token fallback because plugin secret refs are unavailable in this host.", {
|
|
10192
|
+
companyId: normalizeCompanyId(options.companyId),
|
|
10193
|
+
secretRef: configuredTokenSource.secretRef,
|
|
10194
|
+
error: getErrorMessage(error)
|
|
10195
|
+
});
|
|
10196
|
+
return configuredTokenSource.fallbackToken;
|
|
10197
|
+
}
|
|
10198
|
+
throw error;
|
|
10199
|
+
}
|
|
10099
10200
|
}
|
|
10100
10201
|
return configuredTokenSource.token ?? "";
|
|
10101
10202
|
}
|
|
@@ -15553,6 +15654,7 @@ var __testing = {
|
|
|
15553
15654
|
formatPaperclipApiFetchErrorMessage,
|
|
15554
15655
|
hasUnresolvedPaperclipIssueBlocker,
|
|
15555
15656
|
isHealthyMaintainerWaitTransition,
|
|
15657
|
+
resolveGithubToken,
|
|
15556
15658
|
resolvePaperclipPullRequestIssueStatus,
|
|
15557
15659
|
resolveSyncTransitionAssignee
|
|
15558
15660
|
};
|
|
@@ -15569,6 +15671,8 @@ var plugin = definePlugin({
|
|
|
15569
15671
|
const normalizedSettings = normalizeSettings(saved);
|
|
15570
15672
|
const config = await getResolvedConfig(ctx);
|
|
15571
15673
|
const githubTokenConfigured = hasConfiguredGithubToken(normalizedSettings, config, requestedCompanyId);
|
|
15674
|
+
const configuredGitHubTokenRef = getConfiguredGitHubTokenRef(config, requestedCompanyId);
|
|
15675
|
+
const savedGitHubTokenRef = getSavedGitHubTokenRef(normalizedSettings, requestedCompanyId);
|
|
15572
15676
|
const configuredBoardTokenRef = getConfiguredPaperclipBoardApiTokenRef(config, requestedCompanyId);
|
|
15573
15677
|
const savedBoardTokenRef = getSavedPaperclipBoardApiTokenRef(normalizedSettings, requestedCompanyId);
|
|
15574
15678
|
const settingsForResponse = sanitizeSettingsForCurrentSetup(
|
|
@@ -15590,6 +15694,8 @@ var plugin = definePlugin({
|
|
|
15590
15694
|
paperclipApiBaseUrlConfigured: Boolean(normalizePaperclipApiBaseUrl(config.paperclipApiBaseUrl)),
|
|
15591
15695
|
githubTokenConfigured,
|
|
15592
15696
|
paperclipBoardAccessConfigured: requestedCompanyId ? hasConfiguredPaperclipBoardAccess(settingsForResponse, config, requestedCompanyId) : hasConfiguredPaperclipBoardAccessForMappings(settingsForResponse, config, scopedMappings),
|
|
15697
|
+
...savedGitHubTokenRef ? { githubTokenConfigSyncRef: savedGitHubTokenRef } : {},
|
|
15698
|
+
githubTokenNeedsConfigSync: Boolean(savedGitHubTokenRef && configuredGitHubTokenRef !== savedGitHubTokenRef),
|
|
15593
15699
|
...savedBoardTokenRef ? { paperclipBoardAccessConfigSyncRef: savedBoardTokenRef } : {},
|
|
15594
15700
|
paperclipBoardAccessNeedsConfigSync: Boolean(savedBoardTokenRef && !configuredBoardTokenRef)
|
|
15595
15701
|
};
|
|
@@ -15856,6 +15962,44 @@ var plugin = definePlugin({
|
|
|
15856
15962
|
}
|
|
15857
15963
|
return validateGithubToken(ctx, trimmedToken);
|
|
15858
15964
|
});
|
|
15965
|
+
ctx.actions.register("settings.ensureGitHubTokenAvailable", async (input) => {
|
|
15966
|
+
const record = input && typeof input === "object" ? input : {};
|
|
15967
|
+
const companyId = normalizeCompanyId(record.companyId);
|
|
15968
|
+
const githubTokenRef = normalizeSecretRef(record.githubTokenRef);
|
|
15969
|
+
const token = normalizeGitHubToken(record.token);
|
|
15970
|
+
if (!companyId) {
|
|
15971
|
+
throw new Error("Company context is required to verify worker access to the GitHub token.");
|
|
15972
|
+
}
|
|
15973
|
+
if (!githubTokenRef) {
|
|
15974
|
+
throw new Error("A GitHub token secret ref is required to verify worker token access.");
|
|
15975
|
+
}
|
|
15976
|
+
if (!token) {
|
|
15977
|
+
throw new Error("A validated GitHub token is required to prepare the worker token fallback.");
|
|
15978
|
+
}
|
|
15979
|
+
try {
|
|
15980
|
+
const resolvedToken = (await ctx.secrets.resolve(githubTokenRef)).trim();
|
|
15981
|
+
if (resolvedToken) {
|
|
15982
|
+
return {
|
|
15983
|
+
secretResolvable: true,
|
|
15984
|
+
fallbackStored: false
|
|
15985
|
+
};
|
|
15986
|
+
}
|
|
15987
|
+
} catch (error) {
|
|
15988
|
+
if (!isPluginSecretReferenceDisabledError(error)) {
|
|
15989
|
+
throw error;
|
|
15990
|
+
}
|
|
15991
|
+
await writeExternalCompanyGitHubTokenFallback(ctx, companyId, token);
|
|
15992
|
+
return {
|
|
15993
|
+
secretResolvable: false,
|
|
15994
|
+
fallbackStored: true
|
|
15995
|
+
};
|
|
15996
|
+
}
|
|
15997
|
+
await writeExternalCompanyGitHubTokenFallback(ctx, companyId, token);
|
|
15998
|
+
return {
|
|
15999
|
+
secretResolvable: false,
|
|
16000
|
+
fallbackStored: true
|
|
16001
|
+
};
|
|
16002
|
+
});
|
|
15859
16003
|
ctx.actions.register("project.pullRequests.createIssue", async (input) => {
|
|
15860
16004
|
const record = input && typeof input === "object" ? input : {};
|
|
15861
16005
|
return createProjectPullRequestPaperclipIssue(ctx, record);
|