@sentry/junior 0.3.0 → 0.4.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 +5 -1
- package/dist/{bot-JSIREVQD.js → bot-Y6A47LEZ.js} +3 -3
- package/dist/{chunk-4RFOJSJL.js → chunk-DGKNXMK4.js} +2 -2
- package/dist/{chunk-DPTR2FNH.js → chunk-OZFXD5IG.js} +495 -130
- package/dist/{chunk-L745IWNK.js → chunk-RFUE5VBK.js} +854 -346
- package/dist/{chunk-RTQMRGZD.js → chunk-TEQ3UIS7.js} +1 -1
- package/dist/{chunk-SP6LV35L.js → chunk-Z5E25LRN.js} +173 -56
- package/dist/cli/snapshot-warmup.js +2 -2
- package/dist/handlers/queue-callback.js +4 -4
- package/dist/handlers/router.js +90 -40
- package/dist/handlers/webhooks.js +1 -1
- package/dist/next-config.js +33 -27
- package/package.json +1 -1
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
import {
|
|
8
8
|
discoverInstalledPluginPackageContent,
|
|
9
9
|
discoverProjectRoots
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-Z5E25LRN.js";
|
|
11
11
|
|
|
12
12
|
// src/chat/config.ts
|
|
13
13
|
var MIN_AGENT_TURN_TIMEOUT_MS = 10 * 1e3;
|
|
@@ -554,7 +554,9 @@ function getPrivateKey(envName) {
|
|
|
554
554
|
);
|
|
555
555
|
}
|
|
556
556
|
if (key.asymmetricKeyType !== "rsa") {
|
|
557
|
-
throw new Error(
|
|
557
|
+
throw new Error(
|
|
558
|
+
`Invalid ${envName}: GitHub App signing requires an RSA private key`
|
|
559
|
+
);
|
|
558
560
|
}
|
|
559
561
|
return key;
|
|
560
562
|
}
|
|
@@ -609,7 +611,14 @@ function capabilityToPermissions(capability, pluginName) {
|
|
|
609
611
|
function createGitHubAppBroker(manifest, credentials) {
|
|
610
612
|
const tokenCache = /* @__PURE__ */ new Map();
|
|
611
613
|
const provider = manifest.name;
|
|
612
|
-
const {
|
|
614
|
+
const {
|
|
615
|
+
apiDomains,
|
|
616
|
+
apiHeaders,
|
|
617
|
+
authTokenEnv,
|
|
618
|
+
appIdEnv,
|
|
619
|
+
privateKeyEnv,
|
|
620
|
+
installationIdEnv
|
|
621
|
+
} = credentials;
|
|
613
622
|
const apiBase = `https://${apiDomains[0]}`;
|
|
614
623
|
const placeholder = resolveAuthTokenPlaceholder(credentials);
|
|
615
624
|
return {
|
|
@@ -640,6 +649,7 @@ function createGitHubAppBroker(manifest, credentials) {
|
|
|
640
649
|
headerTransforms: apiDomains.map((domain) => ({
|
|
641
650
|
domain,
|
|
642
651
|
headers: {
|
|
652
|
+
...apiHeaders ?? {},
|
|
643
653
|
Authorization: `Bearer ${cached.token}`
|
|
644
654
|
}
|
|
645
655
|
})),
|
|
@@ -659,17 +669,16 @@ function createGitHubAppBroker(manifest, credentials) {
|
|
|
659
669
|
if (repositoryName) {
|
|
660
670
|
tokenRequestBody.repositories = [repositoryName];
|
|
661
671
|
}
|
|
662
|
-
const accessTokenResponse = await githubRequest(
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
token: appJwt,
|
|
668
|
-
body: tokenRequestBody
|
|
669
|
-
}
|
|
670
|
-
);
|
|
672
|
+
const accessTokenResponse = await githubRequest(apiBase, `/app/installations/${installationId}/access_tokens`, {
|
|
673
|
+
method: "POST",
|
|
674
|
+
token: appJwt,
|
|
675
|
+
body: tokenRequestBody
|
|
676
|
+
});
|
|
671
677
|
const providerExpiresAtMs = Date.parse(accessTokenResponse.expires_at);
|
|
672
|
-
const expiresAtMs = Math.min(
|
|
678
|
+
const expiresAtMs = Math.min(
|
|
679
|
+
providerExpiresAtMs,
|
|
680
|
+
Date.now() + MAX_LEASE_MS
|
|
681
|
+
);
|
|
673
682
|
tokenCache.set(cacheKey, {
|
|
674
683
|
installationId,
|
|
675
684
|
token: accessTokenResponse.token,
|
|
@@ -683,6 +692,7 @@ function createGitHubAppBroker(manifest, credentials) {
|
|
|
683
692
|
headerTransforms: apiDomains.map((domain) => ({
|
|
684
693
|
domain,
|
|
685
694
|
headers: {
|
|
695
|
+
...apiHeaders ?? {},
|
|
686
696
|
Authorization: `Bearer ${accessTokenResponse.token}`
|
|
687
697
|
}
|
|
688
698
|
})),
|
|
@@ -710,6 +720,70 @@ var CredentialUnavailableError = class extends Error {
|
|
|
710
720
|
}
|
|
711
721
|
};
|
|
712
722
|
|
|
723
|
+
// src/chat/plugins/oauth-request.ts
|
|
724
|
+
var DEFAULT_TOKEN_CONTENT_TYPE = "application/x-www-form-urlencoded";
|
|
725
|
+
function requireNonEmptyTokenField(data, field) {
|
|
726
|
+
const value = data[field];
|
|
727
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
728
|
+
throw new Error(`OAuth token response missing ${field}`);
|
|
729
|
+
}
|
|
730
|
+
return value;
|
|
731
|
+
}
|
|
732
|
+
function contentTypeToBody(contentType, payload) {
|
|
733
|
+
const mediaType = contentType.split(";", 1)[0]?.trim().toLowerCase();
|
|
734
|
+
if (!mediaType || mediaType === DEFAULT_TOKEN_CONTENT_TYPE) {
|
|
735
|
+
return new URLSearchParams(payload);
|
|
736
|
+
}
|
|
737
|
+
if (mediaType === "application/json" || mediaType.endsWith("+json")) {
|
|
738
|
+
return JSON.stringify(payload);
|
|
739
|
+
}
|
|
740
|
+
throw new Error(`Unsupported OAuth token Content-Type: ${contentType}`);
|
|
741
|
+
}
|
|
742
|
+
function buildOAuthTokenRequest(input) {
|
|
743
|
+
const headers = new Headers({ Accept: "application/json" });
|
|
744
|
+
for (const [name, value] of Object.entries(input.tokenExtraHeaders ?? {})) {
|
|
745
|
+
headers.set(name, value);
|
|
746
|
+
}
|
|
747
|
+
if (!headers.has("Content-Type")) {
|
|
748
|
+
headers.set("Content-Type", DEFAULT_TOKEN_CONTENT_TYPE);
|
|
749
|
+
}
|
|
750
|
+
const payload = { ...input.payload };
|
|
751
|
+
if (input.tokenAuthMethod === "basic") {
|
|
752
|
+
headers.set(
|
|
753
|
+
"Authorization",
|
|
754
|
+
`Basic ${Buffer.from(`${input.clientId}:${input.clientSecret}`).toString("base64")}`
|
|
755
|
+
);
|
|
756
|
+
} else {
|
|
757
|
+
payload.client_id = input.clientId;
|
|
758
|
+
payload.client_secret = input.clientSecret;
|
|
759
|
+
}
|
|
760
|
+
const contentType = headers.get("Content-Type") ?? DEFAULT_TOKEN_CONTENT_TYPE;
|
|
761
|
+
const serializedHeaders = {};
|
|
762
|
+
headers.forEach((value, key) => {
|
|
763
|
+
serializedHeaders[key] = value;
|
|
764
|
+
});
|
|
765
|
+
return {
|
|
766
|
+
headers: serializedHeaders,
|
|
767
|
+
body: contentTypeToBody(contentType, payload)
|
|
768
|
+
};
|
|
769
|
+
}
|
|
770
|
+
function parseOAuthTokenResponse(data) {
|
|
771
|
+
const accessToken = requireNonEmptyTokenField(data, "access_token");
|
|
772
|
+
const refreshToken = requireNonEmptyTokenField(data, "refresh_token");
|
|
773
|
+
const expiresIn = data.expires_in;
|
|
774
|
+
if (expiresIn === void 0) {
|
|
775
|
+
return { accessToken, refreshToken };
|
|
776
|
+
}
|
|
777
|
+
if (typeof expiresIn !== "number" || !Number.isFinite(expiresIn) || expiresIn <= 0) {
|
|
778
|
+
throw new Error("OAuth token response returned invalid expires_in");
|
|
779
|
+
}
|
|
780
|
+
return {
|
|
781
|
+
accessToken,
|
|
782
|
+
refreshToken,
|
|
783
|
+
expiresAt: Date.now() + expiresIn * 1e3
|
|
784
|
+
};
|
|
785
|
+
}
|
|
786
|
+
|
|
713
787
|
// src/chat/plugins/oauth-bearer-broker.ts
|
|
714
788
|
var MAX_LEASE_MS2 = 60 * 60 * 1e3;
|
|
715
789
|
var REFRESH_BUFFER_MS = 5 * 60 * 1e3;
|
|
@@ -717,35 +791,38 @@ async function refreshAccessToken(refreshToken, oauth) {
|
|
|
717
791
|
const clientId = process.env[oauth.clientIdEnv]?.trim();
|
|
718
792
|
const clientSecret = process.env[oauth.clientSecretEnv]?.trim();
|
|
719
793
|
if (!clientId || !clientSecret) {
|
|
720
|
-
throw new Error(
|
|
794
|
+
throw new Error(
|
|
795
|
+
`Missing ${oauth.clientIdEnv} or ${oauth.clientSecretEnv} for token refresh`
|
|
796
|
+
);
|
|
721
797
|
}
|
|
798
|
+
const request = buildOAuthTokenRequest({
|
|
799
|
+
clientId,
|
|
800
|
+
clientSecret,
|
|
801
|
+
payload: {
|
|
802
|
+
grant_type: "refresh_token",
|
|
803
|
+
refresh_token: refreshToken
|
|
804
|
+
},
|
|
805
|
+
tokenAuthMethod: oauth.tokenAuthMethod,
|
|
806
|
+
tokenExtraHeaders: oauth.tokenExtraHeaders
|
|
807
|
+
});
|
|
722
808
|
const response = await fetch(oauth.tokenEndpoint, {
|
|
723
809
|
method: "POST",
|
|
724
|
-
headers:
|
|
725
|
-
body:
|
|
726
|
-
grant_type: "refresh_token",
|
|
727
|
-
refresh_token: refreshToken,
|
|
728
|
-
client_id: clientId,
|
|
729
|
-
client_secret: clientSecret
|
|
730
|
-
})
|
|
810
|
+
headers: request.headers,
|
|
811
|
+
body: request.body
|
|
731
812
|
});
|
|
732
813
|
if (!response.ok) {
|
|
733
814
|
throw new Error(`Token refresh failed: ${response.status}`);
|
|
734
815
|
}
|
|
735
816
|
const data = await response.json();
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
return
|
|
740
|
-
accessToken: data.access_token,
|
|
741
|
-
refreshToken: data.refresh_token,
|
|
742
|
-
expiresIn: data.expires_in
|
|
743
|
-
};
|
|
817
|
+
return parseOAuthTokenResponse(data);
|
|
818
|
+
}
|
|
819
|
+
function getLeaseExpiry(expiresAt) {
|
|
820
|
+
return expiresAt ? Math.min(expiresAt, Date.now() + MAX_LEASE_MS2) : Date.now() + MAX_LEASE_MS2;
|
|
744
821
|
}
|
|
745
822
|
function createOAuthBearerBroker(manifest, credentials, deps) {
|
|
746
823
|
const provider = manifest.name;
|
|
747
824
|
const supportedCapabilities = new Set(manifest.capabilities);
|
|
748
|
-
const { apiDomains, authTokenEnv } = credentials;
|
|
825
|
+
const { apiDomains, apiHeaders, authTokenEnv } = credentials;
|
|
749
826
|
const authTokenPlaceholder = resolveAuthTokenPlaceholder(credentials);
|
|
750
827
|
function buildLease(token, capability, expiresAtMs, reason) {
|
|
751
828
|
return {
|
|
@@ -755,7 +832,7 @@ function createOAuthBearerBroker(manifest, credentials, deps) {
|
|
|
755
832
|
env: { [authTokenEnv]: authTokenPlaceholder },
|
|
756
833
|
headerTransforms: apiDomains.map((domain) => ({
|
|
757
834
|
domain,
|
|
758
|
-
headers: { Authorization: `Bearer ${token}` }
|
|
835
|
+
headers: { ...apiHeaders ?? {}, Authorization: `Bearer ${token}` }
|
|
759
836
|
})),
|
|
760
837
|
expiresAt: new Date(expiresAtMs).toISOString(),
|
|
761
838
|
metadata: { reason }
|
|
@@ -764,27 +841,58 @@ function createOAuthBearerBroker(manifest, credentials, deps) {
|
|
|
764
841
|
return {
|
|
765
842
|
async issue(input) {
|
|
766
843
|
if (!supportedCapabilities.has(input.capability)) {
|
|
767
|
-
throw new Error(
|
|
844
|
+
throw new Error(
|
|
845
|
+
`Unsupported ${provider} capability: ${input.capability}`
|
|
846
|
+
);
|
|
847
|
+
}
|
|
848
|
+
const envToken = process.env[authTokenEnv]?.trim();
|
|
849
|
+
const oauth = manifest.oauth;
|
|
850
|
+
if (!oauth) {
|
|
851
|
+
if (envToken) {
|
|
852
|
+
return buildLease(
|
|
853
|
+
envToken,
|
|
854
|
+
input.capability,
|
|
855
|
+
Date.now() + MAX_LEASE_MS2,
|
|
856
|
+
input.reason
|
|
857
|
+
);
|
|
858
|
+
}
|
|
859
|
+
throw new CredentialUnavailableError(
|
|
860
|
+
provider,
|
|
861
|
+
`No ${provider} credentials available.`
|
|
862
|
+
);
|
|
768
863
|
}
|
|
769
|
-
if (input.requesterId
|
|
770
|
-
const stored = await deps.userTokenStore.get(
|
|
864
|
+
if (input.requesterId) {
|
|
865
|
+
const stored = await deps.userTokenStore.get(
|
|
866
|
+
input.requesterId,
|
|
867
|
+
provider
|
|
868
|
+
);
|
|
771
869
|
if (stored) {
|
|
772
870
|
const now = Date.now();
|
|
773
|
-
if (stored.expiresAt
|
|
871
|
+
if (stored.expiresAt !== void 0 && stored.expiresAt - now < REFRESH_BUFFER_MS) {
|
|
774
872
|
try {
|
|
775
|
-
const refreshed = await refreshAccessToken(
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
873
|
+
const refreshed = await refreshAccessToken(
|
|
874
|
+
stored.refreshToken,
|
|
875
|
+
oauth
|
|
876
|
+
);
|
|
877
|
+
await deps.userTokenStore.set(
|
|
878
|
+
input.requesterId,
|
|
879
|
+
provider,
|
|
880
|
+
refreshed
|
|
881
|
+
);
|
|
882
|
+
return buildLease(
|
|
883
|
+
refreshed.accessToken,
|
|
884
|
+
input.capability,
|
|
885
|
+
getLeaseExpiry(refreshed.expiresAt),
|
|
886
|
+
input.reason
|
|
887
|
+
);
|
|
784
888
|
} catch {
|
|
785
889
|
if (stored.expiresAt > Date.now()) {
|
|
786
|
-
|
|
787
|
-
|
|
890
|
+
return buildLease(
|
|
891
|
+
stored.accessToken,
|
|
892
|
+
input.capability,
|
|
893
|
+
getLeaseExpiry(stored.expiresAt),
|
|
894
|
+
input.reason
|
|
895
|
+
);
|
|
788
896
|
}
|
|
789
897
|
throw new CredentialUnavailableError(
|
|
790
898
|
provider,
|
|
@@ -792,9 +900,13 @@ function createOAuthBearerBroker(manifest, credentials, deps) {
|
|
|
792
900
|
);
|
|
793
901
|
}
|
|
794
902
|
}
|
|
795
|
-
if (stored.expiresAt > Date.now()) {
|
|
796
|
-
|
|
797
|
-
|
|
903
|
+
if (stored.expiresAt === void 0 || stored.expiresAt > Date.now()) {
|
|
904
|
+
return buildLease(
|
|
905
|
+
stored.accessToken,
|
|
906
|
+
input.capability,
|
|
907
|
+
getLeaseExpiry(stored.expiresAt),
|
|
908
|
+
input.reason
|
|
909
|
+
);
|
|
798
910
|
}
|
|
799
911
|
throw new CredentialUnavailableError(
|
|
800
912
|
provider,
|
|
@@ -806,10 +918,13 @@ function createOAuthBearerBroker(manifest, credentials, deps) {
|
|
|
806
918
|
`No ${provider} credentials available.`
|
|
807
919
|
);
|
|
808
920
|
}
|
|
809
|
-
const envToken = process.env[authTokenEnv]?.trim();
|
|
810
921
|
if (envToken) {
|
|
811
|
-
|
|
812
|
-
|
|
922
|
+
return buildLease(
|
|
923
|
+
envToken,
|
|
924
|
+
input.capability,
|
|
925
|
+
getLeaseExpiry(),
|
|
926
|
+
input.reason
|
|
927
|
+
);
|
|
813
928
|
}
|
|
814
929
|
throw new CredentialUnavailableError(
|
|
815
930
|
provider,
|
|
@@ -826,6 +941,15 @@ var SHORT_CONFIG_KEY_RE = /^[a-z0-9]+(\.[a-z0-9-]+)*$/;
|
|
|
826
941
|
var AUTH_TOKEN_ENV_RE = /^[A-Z][A-Z0-9_]*$/;
|
|
827
942
|
var API_DOMAIN_RE = /^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
|
|
828
943
|
var RUNTIME_POSTINSTALL_CMD_RE = /^[A-Za-z0-9._/-]+$/;
|
|
944
|
+
var RESERVED_AUTHORIZE_PARAM_KEYS = /* @__PURE__ */ new Set([
|
|
945
|
+
"client_id",
|
|
946
|
+
"scope",
|
|
947
|
+
"state",
|
|
948
|
+
"redirect_uri",
|
|
949
|
+
"response_type"
|
|
950
|
+
]);
|
|
951
|
+
var FORBIDDEN_API_HEADER_NAMES = /* @__PURE__ */ new Set(["authorization"]);
|
|
952
|
+
var FORBIDDEN_TOKEN_HEADER_NAMES = /* @__PURE__ */ new Set(["authorization"]);
|
|
829
953
|
function toRecord(value, errorMessage) {
|
|
830
954
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
831
955
|
throw new Error(errorMessage);
|
|
@@ -840,14 +964,24 @@ function requireStringField(record, field, errorMessage) {
|
|
|
840
964
|
return value.trim();
|
|
841
965
|
}
|
|
842
966
|
function requireEnvVarField(record, field, pluginName) {
|
|
843
|
-
const value = requireStringField(
|
|
967
|
+
const value = requireStringField(
|
|
968
|
+
record,
|
|
969
|
+
field,
|
|
970
|
+
`Plugin ${pluginName} ${field} must be a non-empty string`
|
|
971
|
+
);
|
|
844
972
|
if (!AUTH_TOKEN_ENV_RE.test(value)) {
|
|
845
|
-
throw new Error(
|
|
973
|
+
throw new Error(
|
|
974
|
+
`Plugin ${pluginName} ${field} must be an uppercase env var name`
|
|
975
|
+
);
|
|
846
976
|
}
|
|
847
977
|
return value;
|
|
848
978
|
}
|
|
849
979
|
function requireHttpsUrlField(record, field, pluginName) {
|
|
850
|
-
const value = requireStringField(
|
|
980
|
+
const value = requireStringField(
|
|
981
|
+
record,
|
|
982
|
+
field,
|
|
983
|
+
`Plugin ${pluginName} oauth.${field} must be a non-empty string`
|
|
984
|
+
);
|
|
851
985
|
let parsed;
|
|
852
986
|
try {
|
|
853
987
|
parsed = new URL(value);
|
|
@@ -862,26 +996,79 @@ function requireHttpsUrlField(record, field, pluginName) {
|
|
|
862
996
|
function normalizeApiDomain(rawDomain, name) {
|
|
863
997
|
const domain = typeof rawDomain === "string" ? rawDomain.trim().toLowerCase() : "";
|
|
864
998
|
if (!domain) {
|
|
865
|
-
throw new Error(
|
|
999
|
+
throw new Error(
|
|
1000
|
+
`Plugin ${name} credentials.api-domains entries must be non-empty strings`
|
|
1001
|
+
);
|
|
866
1002
|
}
|
|
867
1003
|
if (!API_DOMAIN_RE.test(domain)) {
|
|
868
|
-
throw new Error(
|
|
1004
|
+
throw new Error(
|
|
1005
|
+
`Plugin ${name} credentials.api-domains entries must be valid domain names`
|
|
1006
|
+
);
|
|
869
1007
|
}
|
|
870
1008
|
return domain;
|
|
871
1009
|
}
|
|
1010
|
+
function parseStringMap(data, errorLabel, options = {}) {
|
|
1011
|
+
if (data === void 0) {
|
|
1012
|
+
return void 0;
|
|
1013
|
+
}
|
|
1014
|
+
const record = toRecord(
|
|
1015
|
+
data,
|
|
1016
|
+
`${errorLabel} must be an object when provided`
|
|
1017
|
+
);
|
|
1018
|
+
const entries = Object.entries(record);
|
|
1019
|
+
if (entries.length === 0) {
|
|
1020
|
+
return void 0;
|
|
1021
|
+
}
|
|
1022
|
+
const result = {};
|
|
1023
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1024
|
+
for (const [rawKey, rawValue] of entries) {
|
|
1025
|
+
const key = rawKey.trim();
|
|
1026
|
+
if (!key) {
|
|
1027
|
+
throw new Error(`${errorLabel} keys must be non-empty strings`);
|
|
1028
|
+
}
|
|
1029
|
+
if (typeof rawValue !== "string" || !rawValue.trim()) {
|
|
1030
|
+
throw new Error(`${errorLabel}.${key} must be a non-empty string`);
|
|
1031
|
+
}
|
|
1032
|
+
const normalizedKey = key.toLowerCase();
|
|
1033
|
+
if (options.reservedKeys?.has(normalizedKey)) {
|
|
1034
|
+
throw new Error(`${errorLabel}.${key} is reserved by the runtime`);
|
|
1035
|
+
}
|
|
1036
|
+
if (options.forbiddenKeys?.has(normalizedKey)) {
|
|
1037
|
+
throw new Error(`${errorLabel}.${key} is not allowed`);
|
|
1038
|
+
}
|
|
1039
|
+
if (seen.has(normalizedKey)) {
|
|
1040
|
+
throw new Error(`${errorLabel}.${key} is duplicated`);
|
|
1041
|
+
}
|
|
1042
|
+
seen.add(normalizedKey);
|
|
1043
|
+
result[key] = rawValue.trim();
|
|
1044
|
+
}
|
|
1045
|
+
return Object.keys(result).length > 0 ? result : void 0;
|
|
1046
|
+
}
|
|
872
1047
|
function parseBaseCredentialFields(data, name) {
|
|
873
1048
|
const rawDomains = data["api-domains"];
|
|
874
1049
|
if (!Array.isArray(rawDomains) || rawDomains.length === 0) {
|
|
875
|
-
throw new Error(
|
|
1050
|
+
throw new Error(
|
|
1051
|
+
`Plugin ${name} credentials.api-domains must be a non-empty array of strings`
|
|
1052
|
+
);
|
|
876
1053
|
}
|
|
877
|
-
const apiDomains = rawDomains.map(
|
|
1054
|
+
const apiDomains = rawDomains.map(
|
|
1055
|
+
(rawDomain) => normalizeApiDomain(rawDomain, name)
|
|
1056
|
+
);
|
|
1057
|
+
const apiHeaders = parseStringMap(
|
|
1058
|
+
data["api-headers"],
|
|
1059
|
+
`Plugin ${name} credentials.api-headers`,
|
|
1060
|
+
{ forbiddenKeys: FORBIDDEN_API_HEADER_NAMES }
|
|
1061
|
+
);
|
|
878
1062
|
const authTokenEnv = requireEnvVarField(data, "auth-token-env", name);
|
|
879
1063
|
const authTokenPlaceholderRaw = data["auth-token-placeholder"];
|
|
880
1064
|
if (authTokenPlaceholderRaw !== void 0 && (typeof authTokenPlaceholderRaw !== "string" || !authTokenPlaceholderRaw.trim())) {
|
|
881
|
-
throw new Error(
|
|
1065
|
+
throw new Error(
|
|
1066
|
+
`Plugin ${name} credentials.auth-token-placeholder must be a non-empty string when provided`
|
|
1067
|
+
);
|
|
882
1068
|
}
|
|
883
1069
|
return {
|
|
884
1070
|
apiDomains,
|
|
1071
|
+
...apiHeaders ? { apiHeaders } : {},
|
|
885
1072
|
authTokenEnv,
|
|
886
1073
|
...typeof authTokenPlaceholderRaw === "string" ? { authTokenPlaceholder: authTokenPlaceholderRaw.trim() } : {}
|
|
887
1074
|
};
|
|
@@ -896,8 +1083,18 @@ function parseCredentials(data, name) {
|
|
|
896
1083
|
const base = parseBaseCredentialFields(data, name);
|
|
897
1084
|
const appIdEnv = requireEnvVarField(data, "app-id-env", name);
|
|
898
1085
|
const privateKeyEnv = requireEnvVarField(data, "private-key-env", name);
|
|
899
|
-
const installationIdEnv = requireEnvVarField(
|
|
900
|
-
|
|
1086
|
+
const installationIdEnv = requireEnvVarField(
|
|
1087
|
+
data,
|
|
1088
|
+
"installation-id-env",
|
|
1089
|
+
name
|
|
1090
|
+
);
|
|
1091
|
+
return {
|
|
1092
|
+
type: "github-app",
|
|
1093
|
+
...base,
|
|
1094
|
+
appIdEnv,
|
|
1095
|
+
privateKeyEnv,
|
|
1096
|
+
installationIdEnv
|
|
1097
|
+
};
|
|
901
1098
|
}
|
|
902
1099
|
throw new Error(`Plugin ${name} has unsupported credentials.type: "${type}"`);
|
|
903
1100
|
}
|
|
@@ -912,7 +1109,9 @@ function parseRuntimeDependencies(data, name) {
|
|
|
912
1109
|
const seen = /* @__PURE__ */ new Set();
|
|
913
1110
|
for (const entry of data) {
|
|
914
1111
|
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
|
915
|
-
throw new Error(
|
|
1112
|
+
throw new Error(
|
|
1113
|
+
`Plugin ${name} runtime-dependencies entries must be objects`
|
|
1114
|
+
);
|
|
916
1115
|
}
|
|
917
1116
|
const record = entry;
|
|
918
1117
|
const type = record.type;
|
|
@@ -921,20 +1120,28 @@ function parseRuntimeDependencies(data, name) {
|
|
|
921
1120
|
const version = record.version;
|
|
922
1121
|
const sha256 = record.sha256;
|
|
923
1122
|
if (typeof type !== "string" || type !== "npm" && type !== "system") {
|
|
924
|
-
throw new Error(
|
|
1123
|
+
throw new Error(
|
|
1124
|
+
`Plugin ${name} runtime dependency type must be "npm" or "system"`
|
|
1125
|
+
);
|
|
925
1126
|
}
|
|
926
1127
|
const normalizedPackage = typeof packageName === "string" ? packageName.trim() : "";
|
|
927
1128
|
const normalizedUrl = typeof packageUrl === "string" ? packageUrl.trim() : "";
|
|
928
1129
|
if (type === "npm") {
|
|
929
1130
|
if (!normalizedPackage) {
|
|
930
|
-
throw new Error(
|
|
1131
|
+
throw new Error(
|
|
1132
|
+
`Plugin ${name} runtime dependency package must be a non-empty string`
|
|
1133
|
+
);
|
|
931
1134
|
}
|
|
932
1135
|
if (packageUrl !== void 0 || sha256 !== void 0) {
|
|
933
|
-
throw new Error(
|
|
1136
|
+
throw new Error(
|
|
1137
|
+
`Plugin ${name} npm runtime dependencies must only include package/version fields`
|
|
1138
|
+
);
|
|
934
1139
|
}
|
|
935
1140
|
const normalizedVersion = typeof version === "string" ? version.trim() : "latest";
|
|
936
1141
|
if (!normalizedVersion) {
|
|
937
|
-
throw new Error(
|
|
1142
|
+
throw new Error(
|
|
1143
|
+
`Plugin ${name} runtime dependency version must be a non-empty string when provided`
|
|
1144
|
+
);
|
|
938
1145
|
}
|
|
939
1146
|
const dedupeKey2 = `${type}:${normalizedPackage}:${normalizedVersion}`;
|
|
940
1147
|
if (seen.has(dedupeKey2)) {
|
|
@@ -949,17 +1156,25 @@ function parseRuntimeDependencies(data, name) {
|
|
|
949
1156
|
continue;
|
|
950
1157
|
}
|
|
951
1158
|
if (version !== void 0) {
|
|
952
|
-
throw new Error(
|
|
1159
|
+
throw new Error(
|
|
1160
|
+
`Plugin ${name} system runtime dependencies must not include a version`
|
|
1161
|
+
);
|
|
953
1162
|
}
|
|
954
1163
|
if (normalizedPackage && normalizedUrl) {
|
|
955
|
-
throw new Error(
|
|
1164
|
+
throw new Error(
|
|
1165
|
+
`Plugin ${name} system runtime dependencies must specify either package or url, not both`
|
|
1166
|
+
);
|
|
956
1167
|
}
|
|
957
1168
|
if (!normalizedPackage && !normalizedUrl) {
|
|
958
|
-
throw new Error(
|
|
1169
|
+
throw new Error(
|
|
1170
|
+
`Plugin ${name} system runtime dependencies must specify package or url`
|
|
1171
|
+
);
|
|
959
1172
|
}
|
|
960
1173
|
if (normalizedPackage) {
|
|
961
1174
|
if (sha256 !== void 0) {
|
|
962
|
-
throw new Error(
|
|
1175
|
+
throw new Error(
|
|
1176
|
+
`Plugin ${name} system runtime dependency package entries must not include sha256`
|
|
1177
|
+
);
|
|
963
1178
|
}
|
|
964
1179
|
const dedupeKey2 = `${type}:package:${normalizedPackage}`;
|
|
965
1180
|
if (seen.has(dedupeKey2)) {
|
|
@@ -973,11 +1188,15 @@ function parseRuntimeDependencies(data, name) {
|
|
|
973
1188
|
continue;
|
|
974
1189
|
}
|
|
975
1190
|
if (!/^https:\/\//i.test(normalizedUrl)) {
|
|
976
|
-
throw new Error(
|
|
1191
|
+
throw new Error(
|
|
1192
|
+
`Plugin ${name} system runtime dependency url must be an https URL`
|
|
1193
|
+
);
|
|
977
1194
|
}
|
|
978
1195
|
const normalizedSha256 = typeof sha256 === "string" ? sha256.trim().toLowerCase() : "";
|
|
979
1196
|
if (!/^[a-f0-9]{64}$/.test(normalizedSha256)) {
|
|
980
|
-
throw new Error(
|
|
1197
|
+
throw new Error(
|
|
1198
|
+
`Plugin ${name} system runtime dependency url entries must include a valid sha256`
|
|
1199
|
+
);
|
|
981
1200
|
}
|
|
982
1201
|
const dedupeKey = `${type}:url:${normalizedUrl}:${normalizedSha256}`;
|
|
983
1202
|
if (seen.has(dedupeKey)) {
|
|
@@ -1002,12 +1221,16 @@ function parseRuntimePostinstall(data, name) {
|
|
|
1002
1221
|
const parsed = [];
|
|
1003
1222
|
for (const entry of data) {
|
|
1004
1223
|
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
|
1005
|
-
throw new Error(
|
|
1224
|
+
throw new Error(
|
|
1225
|
+
`Plugin ${name} runtime-postinstall entries must be objects`
|
|
1226
|
+
);
|
|
1006
1227
|
}
|
|
1007
1228
|
const record = entry;
|
|
1008
1229
|
const cmd = typeof record.cmd === "string" ? record.cmd.trim() : "";
|
|
1009
1230
|
if (!cmd) {
|
|
1010
|
-
throw new Error(
|
|
1231
|
+
throw new Error(
|
|
1232
|
+
`Plugin ${name} runtime-postinstall cmd must be a non-empty string`
|
|
1233
|
+
);
|
|
1011
1234
|
}
|
|
1012
1235
|
if (!RUNTIME_POSTINSTALL_CMD_RE.test(cmd)) {
|
|
1013
1236
|
throw new Error(
|
|
@@ -1016,11 +1239,15 @@ function parseRuntimePostinstall(data, name) {
|
|
|
1016
1239
|
}
|
|
1017
1240
|
const argsRaw = record.args;
|
|
1018
1241
|
if (argsRaw !== void 0 && (!Array.isArray(argsRaw) || !argsRaw.every((arg) => typeof arg === "string"))) {
|
|
1019
|
-
throw new Error(
|
|
1242
|
+
throw new Error(
|
|
1243
|
+
`Plugin ${name} runtime-postinstall args must be an array of strings when provided`
|
|
1244
|
+
);
|
|
1020
1245
|
}
|
|
1021
1246
|
const sudoRaw = record.sudo;
|
|
1022
1247
|
if (sudoRaw !== void 0 && typeof sudoRaw !== "boolean") {
|
|
1023
|
-
throw new Error(
|
|
1248
|
+
throw new Error(
|
|
1249
|
+
`Plugin ${name} runtime-postinstall sudo must be a boolean when provided`
|
|
1250
|
+
);
|
|
1024
1251
|
}
|
|
1025
1252
|
const normalizedArgs = Array.isArray(argsRaw) ? argsRaw.map((arg) => arg.trim()).filter((arg) => arg.length > 0) : void 0;
|
|
1026
1253
|
parsed.push({
|
|
@@ -1032,7 +1259,10 @@ function parseRuntimePostinstall(data, name) {
|
|
|
1032
1259
|
return parsed.length > 0 ? parsed : void 0;
|
|
1033
1260
|
}
|
|
1034
1261
|
function parseManifest(raw, dir) {
|
|
1035
|
-
const data = toRecord(
|
|
1262
|
+
const data = toRecord(
|
|
1263
|
+
parseYaml(raw),
|
|
1264
|
+
`Invalid plugin manifest in ${dir}: expected an object`
|
|
1265
|
+
);
|
|
1036
1266
|
const rawName = data.name;
|
|
1037
1267
|
if (typeof rawName !== "string" || !PLUGIN_NAME_RE.test(rawName)) {
|
|
1038
1268
|
throw new Error(`Invalid plugin name in ${dir}: "${rawName}"`);
|
|
@@ -1045,7 +1275,9 @@ function parseManifest(raw, dir) {
|
|
|
1045
1275
|
const description = rawDescription;
|
|
1046
1276
|
const rawCapabilities = data.capabilities;
|
|
1047
1277
|
if (rawCapabilities !== void 0 && !Array.isArray(rawCapabilities)) {
|
|
1048
|
-
throw new Error(
|
|
1278
|
+
throw new Error(
|
|
1279
|
+
`Plugin ${name} capabilities must be an array when provided`
|
|
1280
|
+
);
|
|
1049
1281
|
}
|
|
1050
1282
|
const capabilities = [];
|
|
1051
1283
|
for (const cap of rawCapabilities ?? []) {
|
|
@@ -1056,7 +1288,9 @@ function parseManifest(raw, dir) {
|
|
|
1056
1288
|
}
|
|
1057
1289
|
const rawConfigKeys = data["config-keys"];
|
|
1058
1290
|
if (rawConfigKeys !== void 0 && !Array.isArray(rawConfigKeys)) {
|
|
1059
|
-
throw new Error(
|
|
1291
|
+
throw new Error(
|
|
1292
|
+
`Plugin ${name} config-keys must be an array when provided`
|
|
1293
|
+
);
|
|
1060
1294
|
}
|
|
1061
1295
|
const configKeys = [];
|
|
1062
1296
|
for (const key of rawConfigKeys ?? []) {
|
|
@@ -1067,11 +1301,20 @@ function parseManifest(raw, dir) {
|
|
|
1067
1301
|
}
|
|
1068
1302
|
const credentialsRaw = data.credentials;
|
|
1069
1303
|
if (credentialsRaw !== void 0) {
|
|
1070
|
-
toRecord(
|
|
1304
|
+
toRecord(
|
|
1305
|
+
credentialsRaw,
|
|
1306
|
+
`Plugin ${name} credentials must be an object when provided`
|
|
1307
|
+
);
|
|
1071
1308
|
}
|
|
1072
1309
|
const credentials = credentialsRaw ? parseCredentials(credentialsRaw, name) : void 0;
|
|
1073
|
-
const runtimeDependencies = parseRuntimeDependencies(
|
|
1074
|
-
|
|
1310
|
+
const runtimeDependencies = parseRuntimeDependencies(
|
|
1311
|
+
data["runtime-dependencies"],
|
|
1312
|
+
name
|
|
1313
|
+
);
|
|
1314
|
+
const runtimePostinstall = parseRuntimePostinstall(
|
|
1315
|
+
data["runtime-postinstall"],
|
|
1316
|
+
name
|
|
1317
|
+
);
|
|
1075
1318
|
const manifest = {
|
|
1076
1319
|
name,
|
|
1077
1320
|
description,
|
|
@@ -1087,14 +1330,54 @@ function parseManifest(raw, dir) {
|
|
|
1087
1330
|
throw new Error(`Plugin ${name} oauth requires credentials`);
|
|
1088
1331
|
}
|
|
1089
1332
|
if (credentials.type !== "oauth-bearer") {
|
|
1090
|
-
throw new Error(
|
|
1333
|
+
throw new Error(
|
|
1334
|
+
`Plugin ${name} oauth requires credentials.type "oauth-bearer"`
|
|
1335
|
+
);
|
|
1336
|
+
}
|
|
1337
|
+
const authorizeParams = parseStringMap(
|
|
1338
|
+
oauthRaw["authorize-params"],
|
|
1339
|
+
`Plugin ${name} oauth.authorize-params`,
|
|
1340
|
+
{ reservedKeys: RESERVED_AUTHORIZE_PARAM_KEYS }
|
|
1341
|
+
);
|
|
1342
|
+
const tokenExtraHeaders = parseStringMap(
|
|
1343
|
+
oauthRaw["token-extra-headers"],
|
|
1344
|
+
`Plugin ${name} oauth.token-extra-headers`,
|
|
1345
|
+
{ forbiddenKeys: FORBIDDEN_TOKEN_HEADER_NAMES }
|
|
1346
|
+
);
|
|
1347
|
+
const tokenAuthMethodRaw = oauthRaw["token-auth-method"];
|
|
1348
|
+
let tokenAuthMethod;
|
|
1349
|
+
if (tokenAuthMethodRaw !== void 0) {
|
|
1350
|
+
const parsedTokenAuthMethod = requireStringField(
|
|
1351
|
+
oauthRaw,
|
|
1352
|
+
"token-auth-method",
|
|
1353
|
+
`Plugin ${name} oauth.token-auth-method must be a non-empty string`
|
|
1354
|
+
);
|
|
1355
|
+
if (parsedTokenAuthMethod !== "body" && parsedTokenAuthMethod !== "basic") {
|
|
1356
|
+
throw new Error(
|
|
1357
|
+
`Plugin ${name} oauth.token-auth-method must be "body" or "basic"`
|
|
1358
|
+
);
|
|
1359
|
+
}
|
|
1360
|
+
tokenAuthMethod = parsedTokenAuthMethod;
|
|
1091
1361
|
}
|
|
1092
1362
|
manifest.oauth = {
|
|
1093
1363
|
clientIdEnv: requireEnvVarField(oauthRaw, "client-id-env", name),
|
|
1094
1364
|
clientSecretEnv: requireEnvVarField(oauthRaw, "client-secret-env", name),
|
|
1095
|
-
authorizeEndpoint: requireHttpsUrlField(
|
|
1365
|
+
authorizeEndpoint: requireHttpsUrlField(
|
|
1366
|
+
oauthRaw,
|
|
1367
|
+
"authorize-endpoint",
|
|
1368
|
+
name
|
|
1369
|
+
),
|
|
1096
1370
|
tokenEndpoint: requireHttpsUrlField(oauthRaw, "token-endpoint", name),
|
|
1097
|
-
|
|
1371
|
+
...oauthRaw.scope !== void 0 ? {
|
|
1372
|
+
scope: requireStringField(
|
|
1373
|
+
oauthRaw,
|
|
1374
|
+
"scope",
|
|
1375
|
+
`Plugin ${name} oauth.scope must be a non-empty string`
|
|
1376
|
+
)
|
|
1377
|
+
} : {},
|
|
1378
|
+
...authorizeParams ? { authorizeParams } : {},
|
|
1379
|
+
...tokenAuthMethod ? { tokenAuthMethod } : {},
|
|
1380
|
+
...tokenExtraHeaders ? { tokenExtraHeaders } : {}
|
|
1098
1381
|
};
|
|
1099
1382
|
}
|
|
1100
1383
|
const targetRaw = data.target ? toRecord(data.target, `Plugin ${name} target must be an object`) : void 0;
|
|
@@ -1104,14 +1387,20 @@ function parseManifest(raw, dir) {
|
|
|
1104
1387
|
}
|
|
1105
1388
|
const rawConfigKey = targetRaw["config-key"];
|
|
1106
1389
|
if (typeof rawConfigKey !== "string" || !rawConfigKey.trim()) {
|
|
1107
|
-
throw new Error(
|
|
1390
|
+
throw new Error(
|
|
1391
|
+
`Plugin ${name} target.config-key must be a non-empty string`
|
|
1392
|
+
);
|
|
1108
1393
|
}
|
|
1109
1394
|
if (!SHORT_CONFIG_KEY_RE.test(rawConfigKey)) {
|
|
1110
|
-
throw new Error(
|
|
1395
|
+
throw new Error(
|
|
1396
|
+
`Plugin ${name} target.config-key "${rawConfigKey}" is invalid`
|
|
1397
|
+
);
|
|
1111
1398
|
}
|
|
1112
1399
|
const qualifiedKey = `${name}.${rawConfigKey}`;
|
|
1113
1400
|
if (!configKeys.includes(qualifiedKey)) {
|
|
1114
|
-
throw new Error(
|
|
1401
|
+
throw new Error(
|
|
1402
|
+
`Plugin ${name} target.config-key "${rawConfigKey}" must be listed in config-keys`
|
|
1403
|
+
);
|
|
1115
1404
|
}
|
|
1116
1405
|
manifest.target = { type: "repo", configKey: qualifiedKey };
|
|
1117
1406
|
}
|
|
@@ -1130,7 +1419,9 @@ function registerPluginManifest(raw, pluginDir) {
|
|
|
1130
1419
|
}
|
|
1131
1420
|
for (const cap of manifest.capabilities) {
|
|
1132
1421
|
if (capabilityToPlugin.has(cap)) {
|
|
1133
|
-
throw new Error(
|
|
1422
|
+
throw new Error(
|
|
1423
|
+
`Duplicate capability "${cap}" in plugin "${manifest.name}"`
|
|
1424
|
+
);
|
|
1134
1425
|
}
|
|
1135
1426
|
}
|
|
1136
1427
|
const definition = {
|
|
@@ -1159,10 +1450,15 @@ function loadPlugins() {
|
|
|
1159
1450
|
try {
|
|
1160
1451
|
rootStat = statSync(pluginsRoot);
|
|
1161
1452
|
} catch (error) {
|
|
1162
|
-
logWarn(
|
|
1163
|
-
"
|
|
1164
|
-
|
|
1165
|
-
|
|
1453
|
+
logWarn(
|
|
1454
|
+
"plugin_root_read_failed",
|
|
1455
|
+
{},
|
|
1456
|
+
{
|
|
1457
|
+
"file.directory": pluginsRoot,
|
|
1458
|
+
"error.message": error instanceof Error ? error.message : String(error)
|
|
1459
|
+
},
|
|
1460
|
+
"Failed to read plugin root"
|
|
1461
|
+
);
|
|
1166
1462
|
continue;
|
|
1167
1463
|
}
|
|
1168
1464
|
if (rootStat.isDirectory()) {
|
|
@@ -1182,10 +1478,15 @@ function loadPlugins() {
|
|
|
1182
1478
|
try {
|
|
1183
1479
|
entries = readdirSync(pluginsRoot);
|
|
1184
1480
|
} catch (error) {
|
|
1185
|
-
logWarn(
|
|
1186
|
-
"
|
|
1187
|
-
|
|
1188
|
-
|
|
1481
|
+
logWarn(
|
|
1482
|
+
"plugin_root_read_failed",
|
|
1483
|
+
{},
|
|
1484
|
+
{
|
|
1485
|
+
"file.directory": pluginsRoot,
|
|
1486
|
+
"error.message": error instanceof Error ? error.message : String(error)
|
|
1487
|
+
},
|
|
1488
|
+
"Failed to read plugin root"
|
|
1489
|
+
);
|
|
1189
1490
|
continue;
|
|
1190
1491
|
}
|
|
1191
1492
|
for (const entry of entries.sort()) {
|
|
@@ -1221,8 +1522,12 @@ function loadPlugins() {
|
|
|
1221
1522
|
"Loaded plugins"
|
|
1222
1523
|
);
|
|
1223
1524
|
}
|
|
1525
|
+
function ensurePluginsLoaded() {
|
|
1526
|
+
loadPlugins();
|
|
1527
|
+
}
|
|
1224
1528
|
loadPlugins();
|
|
1225
1529
|
function getPluginCapabilityProviders() {
|
|
1530
|
+
ensurePluginsLoaded();
|
|
1226
1531
|
return pluginDefinitions.map((plugin) => ({
|
|
1227
1532
|
provider: plugin.manifest.name,
|
|
1228
1533
|
capabilities: [...plugin.manifest.capabilities],
|
|
@@ -1231,9 +1536,11 @@ function getPluginCapabilityProviders() {
|
|
|
1231
1536
|
}));
|
|
1232
1537
|
}
|
|
1233
1538
|
function getPluginProviders() {
|
|
1539
|
+
ensurePluginsLoaded();
|
|
1234
1540
|
return [...pluginDefinitions];
|
|
1235
1541
|
}
|
|
1236
1542
|
function getPluginRuntimeDependencies() {
|
|
1543
|
+
ensurePluginsLoaded();
|
|
1237
1544
|
const seen = /* @__PURE__ */ new Set();
|
|
1238
1545
|
const deps = [];
|
|
1239
1546
|
for (const plugin of pluginDefinitions) {
|
|
@@ -1262,6 +1569,7 @@ function getPluginRuntimeDependencies() {
|
|
|
1262
1569
|
});
|
|
1263
1570
|
}
|
|
1264
1571
|
function getPluginRuntimePostinstall() {
|
|
1572
|
+
ensurePluginsLoaded();
|
|
1265
1573
|
const commands = [];
|
|
1266
1574
|
for (const plugin of pluginDefinitions) {
|
|
1267
1575
|
for (const command of plugin.manifest.runtimePostinstall ?? []) {
|
|
@@ -1275,6 +1583,7 @@ function getPluginRuntimePostinstall() {
|
|
|
1275
1583
|
return commands;
|
|
1276
1584
|
}
|
|
1277
1585
|
function getPluginOAuthConfig(provider) {
|
|
1586
|
+
ensurePluginsLoaded();
|
|
1278
1587
|
const plugin = pluginsByName.get(provider);
|
|
1279
1588
|
if (!plugin?.manifest.oauth) return void 0;
|
|
1280
1589
|
const oauth = plugin.manifest.oauth;
|
|
@@ -1283,17 +1592,28 @@ function getPluginOAuthConfig(provider) {
|
|
|
1283
1592
|
clientSecretEnv: oauth.clientSecretEnv,
|
|
1284
1593
|
authorizeEndpoint: oauth.authorizeEndpoint,
|
|
1285
1594
|
tokenEndpoint: oauth.tokenEndpoint,
|
|
1286
|
-
scope: oauth.scope,
|
|
1595
|
+
...oauth.scope ? { scope: oauth.scope } : {},
|
|
1596
|
+
...oauth.authorizeParams ? { authorizeParams: { ...oauth.authorizeParams } } : {},
|
|
1597
|
+
...oauth.tokenAuthMethod ? { tokenAuthMethod: oauth.tokenAuthMethod } : {},
|
|
1598
|
+
...oauth.tokenExtraHeaders ? { tokenExtraHeaders: { ...oauth.tokenExtraHeaders } } : {},
|
|
1287
1599
|
callbackPath: `/api/oauth/callback/${plugin.manifest.name}`
|
|
1288
1600
|
};
|
|
1289
1601
|
}
|
|
1290
1602
|
function getPluginSkillRoots() {
|
|
1291
|
-
|
|
1603
|
+
ensurePluginsLoaded();
|
|
1604
|
+
return [
|
|
1605
|
+
.../* @__PURE__ */ new Set([
|
|
1606
|
+
...pluginDefinitions.map((plugin) => plugin.skillsDir),
|
|
1607
|
+
...packageSkillRoots
|
|
1608
|
+
])
|
|
1609
|
+
];
|
|
1292
1610
|
}
|
|
1293
1611
|
function isPluginProvider(provider) {
|
|
1612
|
+
ensurePluginsLoaded();
|
|
1294
1613
|
return pluginsByName.has(provider);
|
|
1295
1614
|
}
|
|
1296
1615
|
function createPluginBroker(provider, deps) {
|
|
1616
|
+
ensurePluginsLoaded();
|
|
1297
1617
|
const plugin = pluginsByName.get(provider);
|
|
1298
1618
|
if (!plugin) {
|
|
1299
1619
|
throw new Error(`Unknown plugin provider: "${provider}"`);
|
|
@@ -1393,6 +1713,9 @@ function buildDependencyProfile(runtime) {
|
|
|
1393
1713
|
postinstall
|
|
1394
1714
|
};
|
|
1395
1715
|
}
|
|
1716
|
+
function getRuntimeDependencyProfileHash(runtime) {
|
|
1717
|
+
return buildDependencyProfile(runtime)?.profileHash;
|
|
1718
|
+
}
|
|
1396
1719
|
function shouldRebuildCachedSnapshot(profile, cached) {
|
|
1397
1720
|
if (!profile.hasFloatingVersions) {
|
|
1398
1721
|
return false;
|
|
@@ -1423,7 +1746,11 @@ async function getCachedSnapshot(profileHash) {
|
|
|
1423
1746
|
async function setCachedSnapshot(entry) {
|
|
1424
1747
|
const state = getStateAdapter();
|
|
1425
1748
|
await state.connect();
|
|
1426
|
-
await state.set(
|
|
1749
|
+
await state.set(
|
|
1750
|
+
profileCacheKey(entry.profileHash),
|
|
1751
|
+
JSON.stringify(entry),
|
|
1752
|
+
SNAPSHOT_CACHE_TTL_MS
|
|
1753
|
+
);
|
|
1427
1754
|
}
|
|
1428
1755
|
async function withSnapshotSpan(name, op, attributes, callback) {
|
|
1429
1756
|
return await withSpan(name, op, {}, callback, attributes);
|
|
@@ -1458,7 +1785,11 @@ async function installGhCliViaDnf(sandbox) {
|
|
|
1458
1785
|
}
|
|
1459
1786
|
const dnf5Repo = await tryRun(sandbox, {
|
|
1460
1787
|
cmd: "dnf",
|
|
1461
|
-
args: [
|
|
1788
|
+
args: [
|
|
1789
|
+
"config-manager",
|
|
1790
|
+
"addrepo",
|
|
1791
|
+
"--from-repofile=https://cli.github.com/packages/rpm/gh-cli.repo"
|
|
1792
|
+
],
|
|
1462
1793
|
sudo: true
|
|
1463
1794
|
});
|
|
1464
1795
|
if (!dnf5Repo.ok) {
|
|
@@ -1475,7 +1806,11 @@ async function installGhCliViaDnf(sandbox) {
|
|
|
1475
1806
|
sandbox,
|
|
1476
1807
|
{
|
|
1477
1808
|
cmd: "dnf",
|
|
1478
|
-
args: [
|
|
1809
|
+
args: [
|
|
1810
|
+
"config-manager",
|
|
1811
|
+
"--add-repo",
|
|
1812
|
+
"https://cli.github.com/packages/rpm/gh-cli.repo"
|
|
1813
|
+
],
|
|
1479
1814
|
sudo: true
|
|
1480
1815
|
},
|
|
1481
1816
|
"dnf config-manager --add-repo gh-cli.repo"
|
|
@@ -1506,8 +1841,12 @@ function runtimeDependencyFilePath(url, sha256) {
|
|
|
1506
1841
|
return `/tmp/junior-runtime-${sha256.slice(0, 12)}-${sanitizedBasename}`;
|
|
1507
1842
|
}
|
|
1508
1843
|
async function installRuntimeDependencies(sandbox, deps) {
|
|
1509
|
-
const systemDeps = deps.filter(
|
|
1510
|
-
|
|
1844
|
+
const systemDeps = deps.filter(
|
|
1845
|
+
(dep) => dep.type === "system"
|
|
1846
|
+
);
|
|
1847
|
+
const npmPackages = deps.filter(
|
|
1848
|
+
(dep) => dep.type === "npm"
|
|
1849
|
+
).map((dep) => `${dep.package}@${dep.version}`);
|
|
1511
1850
|
if (systemDeps.length > 0) {
|
|
1512
1851
|
await withSnapshotSpan(
|
|
1513
1852
|
"sandbox.snapshot.install_system",
|
|
@@ -1534,7 +1873,9 @@ async function installRuntimeDependencies(sandbox, deps) {
|
|
|
1534
1873
|
const checksumStdout = (await checksumResult.stdout()).trim();
|
|
1535
1874
|
const checksumStderr = (await checksumResult.stderr()).trim();
|
|
1536
1875
|
if (checksumResult.exitCode !== 0) {
|
|
1537
|
-
throw new Error(
|
|
1876
|
+
throw new Error(
|
|
1877
|
+
`sha256sum failed: ${checksumStderr || checksumStdout || "command failed"}`
|
|
1878
|
+
);
|
|
1538
1879
|
}
|
|
1539
1880
|
const actualChecksum = checksumStdout.split(/\s+/)[0]?.toLowerCase();
|
|
1540
1881
|
if (!actualChecksum) {
|
|
@@ -1611,7 +1952,10 @@ async function runRuntimePostinstall(sandbox, commands) {
|
|
|
1611
1952
|
},
|
|
1612
1953
|
async () => {
|
|
1613
1954
|
for (const command of commands) {
|
|
1614
|
-
const invocation = [
|
|
1955
|
+
const invocation = [
|
|
1956
|
+
JSON.stringify(command.cmd),
|
|
1957
|
+
...(command.args ?? []).map((arg) => JSON.stringify(arg))
|
|
1958
|
+
].join(" ");
|
|
1615
1959
|
const pathPrefix = `${SANDBOX_WORKSPACE_ROOT}/.junior/bin:$PATH`;
|
|
1616
1960
|
await runOrThrow(
|
|
1617
1961
|
sandbox,
|
|
@@ -1766,7 +2110,9 @@ async function resolveRuntimeDependencySnapshot(params) {
|
|
|
1766
2110
|
};
|
|
1767
2111
|
}
|
|
1768
2112
|
const cached = await getCachedSnapshot(profile.profileHash);
|
|
1769
|
-
const cachedNeedsRebuild = Boolean(
|
|
2113
|
+
const cachedNeedsRebuild = Boolean(
|
|
2114
|
+
cached?.snapshotId && shouldRebuildCachedSnapshot(profile, cached)
|
|
2115
|
+
);
|
|
1770
2116
|
if (!params.forceRebuild && cached?.snapshotId && !cachedNeedsRebuild) {
|
|
1771
2117
|
await params.onProgress?.("cache_hit");
|
|
1772
2118
|
return {
|
|
@@ -1792,34 +2138,50 @@ async function resolveRuntimeDependencySnapshot(params) {
|
|
|
1792
2138
|
}
|
|
1793
2139
|
return !shouldRebuildCachedSnapshot(profile, candidate);
|
|
1794
2140
|
};
|
|
1795
|
-
const lockResult = await withBuildLock(
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
await
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
2141
|
+
const lockResult = await withBuildLock(
|
|
2142
|
+
profile.profileHash,
|
|
2143
|
+
async () => {
|
|
2144
|
+
const latest = await getCachedSnapshot(profile.profileHash);
|
|
2145
|
+
if (latest?.snapshotId && canUseCachedSnapshot(latest)) {
|
|
2146
|
+
await params.onProgress?.("cache_hit");
|
|
2147
|
+
return {
|
|
2148
|
+
snapshotId: latest.snapshotId,
|
|
2149
|
+
source: "callback_cache"
|
|
2150
|
+
};
|
|
2151
|
+
}
|
|
2152
|
+
await params.onProgress?.("building_snapshot");
|
|
2153
|
+
const nextSnapshotId = await createDependencySnapshot(
|
|
2154
|
+
profile,
|
|
2155
|
+
params.runtime,
|
|
2156
|
+
params.timeoutMs
|
|
2157
|
+
);
|
|
2158
|
+
await setCachedSnapshot({
|
|
2159
|
+
profileHash: profile.profileHash,
|
|
2160
|
+
snapshotId: nextSnapshotId,
|
|
2161
|
+
runtime: params.runtime,
|
|
2162
|
+
createdAtMs: Date.now(),
|
|
2163
|
+
dependencyCount: profile.dependencyCount
|
|
2164
|
+
});
|
|
2165
|
+
await params.onProgress?.("build_complete");
|
|
2166
|
+
return { snapshotId: nextSnapshotId, source: "built" };
|
|
2167
|
+
},
|
|
2168
|
+
canUseCachedSnapshot,
|
|
2169
|
+
{
|
|
2170
|
+
onWaitingForLock: async () => {
|
|
2171
|
+
await params.onProgress?.("waiting_for_lock");
|
|
2172
|
+
}
|
|
1815
2173
|
}
|
|
1816
|
-
|
|
2174
|
+
);
|
|
1817
2175
|
return {
|
|
1818
2176
|
snapshotId: lockResult.snapshotId,
|
|
1819
2177
|
profileHash: profile.profileHash,
|
|
1820
2178
|
dependencyCount: profile.dependencyCount,
|
|
1821
2179
|
cacheHit: lockResult.source !== "built",
|
|
1822
|
-
resolveOutcome: toResolveOutcome(
|
|
2180
|
+
resolveOutcome: toResolveOutcome(
|
|
2181
|
+
Boolean(params.forceRebuild),
|
|
2182
|
+
lockResult.source,
|
|
2183
|
+
lockResult.waitedForLock
|
|
2184
|
+
),
|
|
1823
2185
|
...rebuildReason ? { rebuildReason } : {}
|
|
1824
2186
|
};
|
|
1825
2187
|
}
|
|
@@ -1836,6 +2198,8 @@ export {
|
|
|
1836
2198
|
soulPathCandidates,
|
|
1837
2199
|
resolveAuthTokenPlaceholder,
|
|
1838
2200
|
CredentialUnavailableError,
|
|
2201
|
+
buildOAuthTokenRequest,
|
|
2202
|
+
parseOAuthTokenResponse,
|
|
1839
2203
|
getPluginCapabilityProviders,
|
|
1840
2204
|
getPluginProviders,
|
|
1841
2205
|
getPluginOAuthConfig,
|
|
@@ -1861,6 +2225,7 @@ export {
|
|
|
1861
2225
|
SANDBOX_WORKSPACE_ROOT,
|
|
1862
2226
|
SANDBOX_SKILLS_ROOT,
|
|
1863
2227
|
sandboxSkillDir,
|
|
2228
|
+
getRuntimeDependencyProfileHash,
|
|
1864
2229
|
resolveRuntimeDependencySnapshot,
|
|
1865
2230
|
isSnapshotMissingError
|
|
1866
2231
|
};
|