@pymthouse/builder-sdk 0.0.8
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/LICENSE +21 -0
- package/README.md +144 -0
- package/dist/device.cjs +246 -0
- package/dist/device.cjs.map +1 -0
- package/dist/device.d.cts +27 -0
- package/dist/device.d.ts +27 -0
- package/dist/device.js +244 -0
- package/dist/device.js.map +1 -0
- package/dist/env-4YmzarGJ.d.ts +68 -0
- package/dist/env-CZczUMzR.d.cts +68 -0
- package/dist/env.cjs +615 -0
- package/dist/env.cjs.map +1 -0
- package/dist/env.d.cts +2 -0
- package/dist/env.d.ts +2 -0
- package/dist/env.js +612 -0
- package/dist/env.js.map +1 -0
- package/dist/format.cjs +27 -0
- package/dist/format.cjs.map +1 -0
- package/dist/format.d.cts +4 -0
- package/dist/format.d.ts +4 -0
- package/dist/format.js +24 -0
- package/dist/format.js.map +1 -0
- package/dist/index.cjs +685 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +47 -0
- package/dist/index.d.ts +47 -0
- package/dist/index.js +670 -0
- package/dist/index.js.map +1 -0
- package/dist/types-W9PJAspR.d.cts +136 -0
- package/dist/types-W9PJAspR.d.ts +136 -0
- package/dist/verify.cjs +181 -0
- package/dist/verify.cjs.map +1 -0
- package/dist/verify.d.cts +18 -0
- package/dist/verify.d.ts +18 -0
- package/dist/verify.js +179 -0
- package/dist/verify.js.map +1 -0
- package/package.json +86 -0
package/dist/verify.js
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { validateJwtAccessToken, customFetch, allowInsecureRequests, discoveryRequest, processDiscoveryResponse, ResponseBodyError, OperationProcessingError } from 'oauth4webapi';
|
|
2
|
+
|
|
3
|
+
// src/verify.ts
|
|
4
|
+
|
|
5
|
+
// src/errors.ts
|
|
6
|
+
var PmtHouseError = class extends Error {
|
|
7
|
+
status;
|
|
8
|
+
code;
|
|
9
|
+
details;
|
|
10
|
+
constructor(message, {
|
|
11
|
+
status = 500,
|
|
12
|
+
code = "pymthouse_error",
|
|
13
|
+
details
|
|
14
|
+
} = {}) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = "PmtHouseError";
|
|
17
|
+
this.status = status;
|
|
18
|
+
this.code = code;
|
|
19
|
+
this.details = details;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// src/string-utils.ts
|
|
24
|
+
function stripTrailingSlashes(value) {
|
|
25
|
+
let end = value.length;
|
|
26
|
+
while (end > 0 && value.charCodeAt(end - 1) === 47) {
|
|
27
|
+
end--;
|
|
28
|
+
}
|
|
29
|
+
return value.slice(0, end);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// src/discovery.ts
|
|
33
|
+
var CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
34
|
+
var discoveryCache = /* @__PURE__ */ new Map();
|
|
35
|
+
function normalizedIssuerKey(issuerUrl) {
|
|
36
|
+
return stripTrailingSlashes(issuerUrl);
|
|
37
|
+
}
|
|
38
|
+
async function loadAuthorizationServer(issuerUrl, fetchImpl, options = {}) {
|
|
39
|
+
const key = normalizedIssuerKey(issuerUrl);
|
|
40
|
+
const now = Date.now();
|
|
41
|
+
const cached = discoveryCache.get(key);
|
|
42
|
+
if (!options.force && cached && now - cached.fetchedAt < CACHE_TTL_MS) {
|
|
43
|
+
return cached.as;
|
|
44
|
+
}
|
|
45
|
+
const issuerIdentifier = new URL(key);
|
|
46
|
+
const discoveryOpts = {
|
|
47
|
+
algorithm: "oidc",
|
|
48
|
+
[customFetch]: fetchImpl
|
|
49
|
+
};
|
|
50
|
+
if (options.allowInsecureHttp) {
|
|
51
|
+
discoveryOpts[allowInsecureRequests] = true;
|
|
52
|
+
}
|
|
53
|
+
let response;
|
|
54
|
+
try {
|
|
55
|
+
response = await discoveryRequest(issuerIdentifier, discoveryOpts);
|
|
56
|
+
} catch (e) {
|
|
57
|
+
throw mapDiscoveryNetworkError(e);
|
|
58
|
+
}
|
|
59
|
+
let as;
|
|
60
|
+
try {
|
|
61
|
+
as = await processDiscoveryResponse(issuerIdentifier, response);
|
|
62
|
+
} catch (e) {
|
|
63
|
+
throw mapOAuthDiscoveryError(e);
|
|
64
|
+
}
|
|
65
|
+
discoveryCache.set(key, { as, fetchedAt: now });
|
|
66
|
+
return as;
|
|
67
|
+
}
|
|
68
|
+
function mapOAuthDiscoveryError(error) {
|
|
69
|
+
if (error instanceof PmtHouseError) {
|
|
70
|
+
return error;
|
|
71
|
+
}
|
|
72
|
+
if (error instanceof Error) {
|
|
73
|
+
return new PmtHouseError(error.message, {
|
|
74
|
+
status: 500,
|
|
75
|
+
code: "oidc_discovery_invalid",
|
|
76
|
+
details: { cause: error.cause }
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
return new PmtHouseError("OIDC discovery failed", {
|
|
80
|
+
status: 500,
|
|
81
|
+
code: "oidc_discovery_invalid"
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
function mapDiscoveryNetworkError(error) {
|
|
85
|
+
if (error instanceof PmtHouseError) {
|
|
86
|
+
return error;
|
|
87
|
+
}
|
|
88
|
+
if (error instanceof Error) {
|
|
89
|
+
return new PmtHouseError(`Failed to load OIDC discovery: ${error.message}`, {
|
|
90
|
+
status: 502,
|
|
91
|
+
code: "oidc_discovery_failed"
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
return new PmtHouseError("Failed to load OIDC discovery", {
|
|
95
|
+
status: 502,
|
|
96
|
+
code: "oidc_discovery_failed"
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
function mapOAuthError(error) {
|
|
100
|
+
if (error instanceof PmtHouseError) {
|
|
101
|
+
return error;
|
|
102
|
+
}
|
|
103
|
+
if (error instanceof ResponseBodyError) {
|
|
104
|
+
const cause = error.cause;
|
|
105
|
+
const description = typeof error.error_description === "string" ? error.error_description : error.message;
|
|
106
|
+
const details = { ...cause };
|
|
107
|
+
if (typeof cause.error_uri === "string") {
|
|
108
|
+
details.error_uri = cause.error_uri;
|
|
109
|
+
}
|
|
110
|
+
return new PmtHouseError(description, {
|
|
111
|
+
status: error.status,
|
|
112
|
+
code: error.error,
|
|
113
|
+
details
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
if (error instanceof OperationProcessingError) {
|
|
117
|
+
return new PmtHouseError(error.message, {
|
|
118
|
+
status: 502,
|
|
119
|
+
code: error.code ?? "oauth_processing_error",
|
|
120
|
+
details: { cause: error.cause }
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
if (error instanceof Error) {
|
|
124
|
+
return new PmtHouseError(error.message, {
|
|
125
|
+
status: 500,
|
|
126
|
+
code: "unexpected_error"
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
return new PmtHouseError("Unexpected error", {
|
|
130
|
+
status: 500,
|
|
131
|
+
code: "unexpected_error"
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// src/verify.ts
|
|
136
|
+
async function verifyJwt(token, options) {
|
|
137
|
+
const fetchImpl = options.fetch ?? fetch;
|
|
138
|
+
const as = await loadAuthorizationServer(options.issuerUrl, fetchImpl, {
|
|
139
|
+
allowInsecureHttp: options.allowInsecureHttp
|
|
140
|
+
});
|
|
141
|
+
const request = new Request("https://resource.invalid/", {
|
|
142
|
+
headers: {
|
|
143
|
+
Authorization: `Bearer ${token}`
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
const httpOpts = {
|
|
147
|
+
[customFetch]: fetchImpl
|
|
148
|
+
};
|
|
149
|
+
if (options.allowInsecureHttp) {
|
|
150
|
+
httpOpts[allowInsecureRequests] = true;
|
|
151
|
+
}
|
|
152
|
+
try {
|
|
153
|
+
const claims = await validateJwtAccessToken(
|
|
154
|
+
as,
|
|
155
|
+
request,
|
|
156
|
+
options.audience,
|
|
157
|
+
httpOpts
|
|
158
|
+
);
|
|
159
|
+
if (options.requiredScopes?.length) {
|
|
160
|
+
const scopeStr = typeof claims.scope === "string" ? claims.scope : "";
|
|
161
|
+
const have = new Set(scopeStr.split(/\s+/).filter(Boolean));
|
|
162
|
+
for (const s of options.requiredScopes) {
|
|
163
|
+
if (!have.has(s)) {
|
|
164
|
+
throw new PmtHouseError(`Missing required scope: ${s}`, {
|
|
165
|
+
status: 403,
|
|
166
|
+
code: "insufficient_scope"
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return claims;
|
|
172
|
+
} catch (e) {
|
|
173
|
+
throw mapOAuthError(e);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export { verifyJwt };
|
|
178
|
+
//# sourceMappingURL=verify.js.map
|
|
179
|
+
//# sourceMappingURL=verify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/string-utils.ts","../src/discovery.ts","../src/oauth-map.ts","../src/verify.ts"],"names":["customFetch","allowInsecureRequests"],"mappings":"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","file":"verify.js","sourcesContent":["export class PmtHouseError extends Error {\n readonly status: number;\n readonly code: string;\n readonly details?: unknown;\n\n constructor(\n message: string,\n {\n status = 500,\n code = \"pymthouse_error\",\n details,\n }: {\n status?: number;\n code?: string;\n details?: unknown;\n } = {},\n ) {\n super(message);\n this.name = \"PmtHouseError\";\n this.status = status;\n this.code = code;\n this.details = details;\n }\n}\n\nexport function toPmtHouseError(\n error: unknown,\n fallbackMessage: string,\n): PmtHouseError {\n if (error instanceof PmtHouseError) {\n return error;\n }\n\n if (error instanceof Error) {\n return new PmtHouseError(error.message || fallbackMessage, {\n code: \"unexpected_error\",\n status: 500,\n });\n }\n\n return new PmtHouseError(fallbackMessage, {\n code: \"unexpected_error\",\n status: 500,\n });\n}\n","/** Removes trailing `/` without regex (linear time). */\nexport function stripTrailingSlashes(value: string): string {\n let end = value.length;\n while (end > 0 && value.charCodeAt(end - 1) === 47) {\n end--;\n }\n return value.slice(0, end);\n}\n","import {\n allowInsecureRequests,\n customFetch,\n discoveryRequest,\n processDiscoveryResponse,\n type AuthorizationServer,\n} from \"oauth4webapi\";\nimport { PmtHouseError } from \"./errors.js\";\nimport { stripTrailingSlashes } from \"./string-utils.js\";\nimport type { FetchLike, OidcDiscoveryDocument } from \"./types.js\";\n\nexport function authorizationServerToOidcDocument(as: AuthorizationServer): OidcDiscoveryDocument {\n const tokenEndpoint = as.token_endpoint;\n const jwksUri = as.jwks_uri;\n if (!tokenEndpoint || !jwksUri) {\n throw new PmtHouseError(\"OIDC discovery document is missing token_endpoint or jwks_uri\", {\n status: 500,\n code: \"oidc_discovery_invalid\",\n });\n }\n return {\n issuer: as.issuer,\n authorization_endpoint: as.authorization_endpoint ?? \"\",\n token_endpoint: tokenEndpoint,\n jwks_uri: jwksUri,\n userinfo_endpoint: as.userinfo_endpoint,\n device_authorization_endpoint: as.device_authorization_endpoint,\n };\n}\n\nconst CACHE_TTL_MS = 5 * 60 * 1000;\n\ntype CacheEntry = {\n as: AuthorizationServer;\n fetchedAt: number;\n};\n\nconst discoveryCache = new Map<string, CacheEntry>();\n\nfunction normalizedIssuerKey(issuerUrl: string): string {\n return stripTrailingSlashes(issuerUrl);\n}\n\nexport interface LoadAuthorizationServerOptions {\n force?: boolean;\n allowInsecureHttp?: boolean;\n}\n\n/**\n * Loads OIDC discovery metadata via oauth4webapi (RFC 8414 / OIDC Discovery), with a 5-minute cache.\n */\nexport async function loadAuthorizationServer(\n issuerUrl: string,\n fetchImpl: FetchLike,\n options: LoadAuthorizationServerOptions = {},\n): Promise<AuthorizationServer> {\n const key = normalizedIssuerKey(issuerUrl);\n const now = Date.now();\n const cached = discoveryCache.get(key);\n\n if (!options.force && cached && now - cached.fetchedAt < CACHE_TTL_MS) {\n return cached.as;\n }\n\n const issuerIdentifier = new URL(key);\n const discoveryOpts: Parameters<typeof discoveryRequest>[1] = {\n algorithm: \"oidc\",\n [customFetch]: fetchImpl,\n };\n if (options.allowInsecureHttp) {\n discoveryOpts[allowInsecureRequests] = true;\n }\n\n let response: Response;\n try {\n response = await discoveryRequest(issuerIdentifier, discoveryOpts);\n } catch (e) {\n throw mapDiscoveryNetworkError(e);\n }\n\n let as: AuthorizationServer;\n try {\n as = await processDiscoveryResponse(issuerIdentifier, response);\n } catch (e) {\n throw mapOAuthDiscoveryError(e);\n }\n\n discoveryCache.set(key, { as, fetchedAt: now });\n return as;\n}\n\nexport async function fetchDiscoveryDocument(\n issuerUrl: string,\n fetchImpl: FetchLike,\n options: LoadAuthorizationServerOptions = {},\n): Promise<OidcDiscoveryDocument> {\n const as = await loadAuthorizationServer(issuerUrl, fetchImpl, options);\n return authorizationServerToOidcDocument(as);\n}\n\nexport function clearDiscoveryCache(issuerUrl?: string): void {\n if (!issuerUrl) {\n discoveryCache.clear();\n return;\n }\n discoveryCache.delete(normalizedIssuerKey(issuerUrl));\n}\n\nfunction mapOAuthDiscoveryError(error: unknown): PmtHouseError {\n if (error instanceof PmtHouseError) {\n return error;\n }\n if (error instanceof Error) {\n return new PmtHouseError(error.message, {\n status: 500,\n code: \"oidc_discovery_invalid\",\n details: { cause: error.cause },\n });\n }\n return new PmtHouseError(\"OIDC discovery failed\", {\n status: 500,\n code: \"oidc_discovery_invalid\",\n });\n}\n\nfunction mapDiscoveryNetworkError(error: unknown): PmtHouseError {\n if (error instanceof PmtHouseError) {\n return error;\n }\n if (error instanceof Error) {\n return new PmtHouseError(`Failed to load OIDC discovery: ${error.message}`, {\n status: 502,\n code: \"oidc_discovery_failed\",\n });\n }\n return new PmtHouseError(\"Failed to load OIDC discovery\", {\n status: 502,\n code: \"oidc_discovery_failed\",\n });\n}\n","import { type Client, OperationProcessingError, ResponseBodyError } from \"oauth4webapi\";\nimport { PmtHouseError } from \"./errors.js\";\nimport type { ClientCredentialsTokenResponse, TokenExchangeResponse } from \"./types.js\";\n\nconst ACCEPTED_ISSUED_TOKEN_TYPES = new Set([\n \"urn:ietf:params:oauth:token-type:access_token\",\n \"urn:pmth:token-type:remote-signer-session\",\n]);\n\nexport function mapOAuthError(error: unknown): PmtHouseError {\n if (error instanceof PmtHouseError) {\n return error;\n }\n\n if (error instanceof ResponseBodyError) {\n const cause = error.cause as Record<string, unknown>;\n const description =\n typeof error.error_description === \"string\"\n ? error.error_description\n : error.message;\n const details: Record<string, unknown> = { ...cause };\n if (typeof cause.error_uri === \"string\") {\n details.error_uri = cause.error_uri;\n }\n return new PmtHouseError(description, {\n status: error.status,\n code: error.error,\n details,\n });\n }\n\n if (error instanceof OperationProcessingError) {\n return new PmtHouseError(error.message, {\n status: 502,\n code: error.code ?? \"oauth_processing_error\",\n details: { cause: error.cause },\n });\n }\n\n if (error instanceof Error) {\n return new PmtHouseError(error.message, {\n status: 500,\n code: \"unexpected_error\",\n });\n }\n\n return new PmtHouseError(\"Unexpected error\", {\n status: 500,\n code: \"unexpected_error\",\n });\n}\n\nexport function tokenEndpointResponseToExchange(\n tr: import(\"oauth4webapi\").TokenEndpointResponse,\n): TokenExchangeResponse {\n const issued = tr.issued_token_type;\n if (typeof issued !== \"string\" || !ACCEPTED_ISSUED_TOKEN_TYPES.has(issued)) {\n throw new PmtHouseError(\"Token exchange returned an unexpected issued_token_type\", {\n status: 502,\n code: \"invalid_token_response\",\n details: { issued_token_type: issued },\n });\n }\n\n const tt = tr.token_type;\n if (typeof tt !== \"string\" || tt.toLowerCase() !== \"bearer\") {\n throw new PmtHouseError(\"Token endpoint returned a non-Bearer token_type\", {\n status: 502,\n code: \"invalid_token_response\",\n details: { token_type: tt },\n });\n }\n\n const expiresIn = tr.expires_in;\n if (typeof expiresIn !== \"number\") {\n throw new PmtHouseError(\"Token response missing expires_in\", {\n status: 502,\n code: \"invalid_token_response\",\n });\n }\n\n const scope = typeof tr.scope === \"string\" ? tr.scope : \"\";\n\n return {\n access_token: tr.access_token,\n token_type: \"Bearer\",\n expires_in: expiresIn,\n scope,\n issued_token_type: issued,\n };\n}\n\nexport function tokenEndpointResponseToClientCredentials(\n tr: import(\"oauth4webapi\").TokenEndpointResponse,\n): ClientCredentialsTokenResponse {\n const tt = tr.token_type;\n if (typeof tt !== \"string\" || tt.toLowerCase() !== \"bearer\") {\n throw new PmtHouseError(\"Token endpoint returned a non-Bearer token_type\", {\n status: 502,\n code: \"invalid_token_response\",\n details: { token_type: tt },\n });\n }\n\n return {\n access_token: tr.access_token,\n token_type: \"Bearer\",\n expires_in: tr.expires_in,\n scope: typeof tr.scope === \"string\" ? tr.scope : undefined,\n };\n}\n\nexport function m2mClient(clientId: string): Client {\n return { client_id: clientId };\n}\n","import {\n allowInsecureRequests,\n customFetch,\n validateJwtAccessToken,\n type JWTAccessTokenClaims,\n} from \"oauth4webapi\";\nimport { loadAuthorizationServer } from \"./discovery.js\";\nimport { PmtHouseError } from \"./errors.js\";\nimport { mapOAuthError } from \"./oauth-map.js\";\nimport type { FetchLike } from \"./types.js\";\n\nexport interface VerifyJwtOptions {\n issuerUrl: string;\n /** Expected JWT `aud` (resource identifier). */\n audience: string;\n fetch?: FetchLike;\n allowInsecureHttp?: boolean;\n /** If set, every scope here must appear in the token's `scope` claim (space-separated). */\n requiredScopes?: string[];\n}\n\n/**\n * RFC 9068 / RFC 6750: validate a JWT access token using issuer JWKS via oauth4webapi.\n */\nexport async function verifyJwt(\n token: string,\n options: VerifyJwtOptions,\n): Promise<JWTAccessTokenClaims> {\n const fetchImpl = options.fetch ?? fetch;\n const as = await loadAuthorizationServer(options.issuerUrl, fetchImpl, {\n allowInsecureHttp: options.allowInsecureHttp,\n });\n\n const request = new Request(\"https://resource.invalid/\", {\n headers: {\n Authorization: `Bearer ${token}`,\n },\n });\n\n const httpOpts: Record<symbol, unknown> = {\n [customFetch]: fetchImpl,\n };\n if (options.allowInsecureHttp) {\n httpOpts[allowInsecureRequests] = true;\n }\n\n try {\n const claims = await validateJwtAccessToken(\n as,\n request,\n options.audience,\n httpOpts as import(\"oauth4webapi\").ValidateJWTAccessTokenOptions,\n );\n\n if (options.requiredScopes?.length) {\n const scopeStr = typeof claims.scope === \"string\" ? claims.scope : \"\";\n const have = new Set(scopeStr.split(/\\s+/).filter(Boolean));\n for (const s of options.requiredScopes) {\n if (!have.has(s)) {\n throw new PmtHouseError(`Missing required scope: ${s}`, {\n status: 403,\n code: \"insufficient_scope\",\n });\n }\n }\n }\n\n return claims;\n } catch (e) {\n throw mapOAuthError(e);\n }\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pymthouse/builder-sdk",
|
|
3
|
+
"version": "0.0.8",
|
|
4
|
+
"description": "PymtHouse Builder API and OIDC client (OpenID-certified oauth4webapi)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
14
|
+
},
|
|
15
|
+
"./format": {
|
|
16
|
+
"types": "./dist/format.d.ts",
|
|
17
|
+
"import": "./dist/format.js",
|
|
18
|
+
"require": "./dist/format.cjs"
|
|
19
|
+
},
|
|
20
|
+
"./env": {
|
|
21
|
+
"types": "./dist/env.d.ts",
|
|
22
|
+
"import": "./dist/env.js",
|
|
23
|
+
"require": "./dist/env.cjs"
|
|
24
|
+
},
|
|
25
|
+
"./device": {
|
|
26
|
+
"types": "./dist/device.d.ts",
|
|
27
|
+
"import": "./dist/device.js",
|
|
28
|
+
"require": "./dist/device.cjs"
|
|
29
|
+
},
|
|
30
|
+
"./verify": {
|
|
31
|
+
"types": "./dist/verify.d.ts",
|
|
32
|
+
"import": "./dist/verify.js",
|
|
33
|
+
"require": "./dist/verify.cjs"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"files": [
|
|
37
|
+
"dist",
|
|
38
|
+
"README.md",
|
|
39
|
+
"LICENSE"
|
|
40
|
+
],
|
|
41
|
+
"sideEffects": false,
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=20"
|
|
44
|
+
},
|
|
45
|
+
"publishConfig": {
|
|
46
|
+
"access": "public"
|
|
47
|
+
},
|
|
48
|
+
"scripts": {
|
|
49
|
+
"build": "tsup",
|
|
50
|
+
"typecheck": "tsc --noEmit",
|
|
51
|
+
"lint": "eslint .",
|
|
52
|
+
"test": "vitest run",
|
|
53
|
+
"format": "prettier . --write",
|
|
54
|
+
"format:check": "prettier . --check",
|
|
55
|
+
"prepack": "pnpm run build"
|
|
56
|
+
},
|
|
57
|
+
"dependencies": {
|
|
58
|
+
"oauth4webapi": "^3.8.5"
|
|
59
|
+
},
|
|
60
|
+
"devDependencies": {
|
|
61
|
+
"@eslint/js": "^9.18.0",
|
|
62
|
+
"@types/node": "^22.10.0",
|
|
63
|
+
"eslint": "^9.18.0",
|
|
64
|
+
"prettier": "^3.4.2",
|
|
65
|
+
"tsup": "^8.3.5",
|
|
66
|
+
"typescript": "^5.7.2",
|
|
67
|
+
"typescript-eslint": "^8.20.0",
|
|
68
|
+
"vitest": "^4.1.4"
|
|
69
|
+
},
|
|
70
|
+
"packageManager": "pnpm@10.33.0",
|
|
71
|
+
"license": "MIT",
|
|
72
|
+
"keywords": [
|
|
73
|
+
"pymthouse",
|
|
74
|
+
"oauth2",
|
|
75
|
+
"oidc",
|
|
76
|
+
"builder-sdk"
|
|
77
|
+
],
|
|
78
|
+
"repository": {
|
|
79
|
+
"type": "git",
|
|
80
|
+
"url": "https://github.com/pymthouse/builder-sdk.git"
|
|
81
|
+
},
|
|
82
|
+
"bugs": {
|
|
83
|
+
"url": "https://github.com/pymthouse/builder-sdk/issues"
|
|
84
|
+
},
|
|
85
|
+
"homepage": "https://github.com/pymthouse/builder-sdk#readme"
|
|
86
|
+
}
|