@typeship-ax/cli 0.8.0 → 0.10.0
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/AGENTS.md +31 -0
- package/README.md +26 -9
- package/api.json +3509 -800
- package/api.md +444 -494
- package/dist/api-identity.d.ts +40 -0
- package/dist/api-identity.d.ts.map +1 -0
- package/dist/api-identity.js +128 -0
- package/dist/auth-profiles.d.ts +30 -0
- package/dist/auth-profiles.d.ts.map +1 -0
- package/dist/auth-profiles.js +138 -0
- package/dist/cli.js +522 -355
- package/dist/console-login-check.d.ts +21 -0
- package/dist/console-login-check.d.ts.map +1 -0
- package/dist/console-login-check.js +107 -0
- package/dist/console-login-contract.d.ts +45 -0
- package/dist/console-login-contract.d.ts.map +1 -0
- package/dist/console-login-contract.js +40 -0
- package/dist/core/http.d.ts +17 -2
- package/dist/core/http.d.ts.map +1 -1
- package/dist/core/http.js +78 -17
- package/dist/credential-storage.d.ts +24 -0
- package/dist/credential-storage.d.ts.map +1 -0
- package/dist/credential-storage.js +207 -0
- package/dist/docs.d.ts +25 -0
- package/dist/docs.d.ts.map +1 -1
- package/dist/docs.js +144 -0
- package/dist/errors.d.ts +18 -10
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +24 -14
- package/dist/index.d.ts +10 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -4
- package/dist/named-credentials.d.ts +21 -0
- package/dist/named-credentials.d.ts.map +1 -0
- package/dist/named-credentials.js +86 -0
- package/dist/oauth-login.d.ts +39 -0
- package/dist/oauth-login.d.ts.map +1 -0
- package/dist/oauth-login.js +171 -0
- package/dist/oauth-request.d.ts +21 -0
- package/dist/oauth-request.d.ts.map +1 -0
- package/dist/oauth-request.js +119 -0
- package/dist/oauth-session.d.ts +106 -0
- package/dist/oauth-session.d.ts.map +1 -0
- package/dist/oauth-session.js +244 -0
- package/dist/ops.d.ts +14 -1
- package/dist/ops.d.ts.map +1 -1
- package/dist/ops.js +34 -30
- package/dist/polling-login.d.ts +57 -0
- package/dist/polling-login.d.ts.map +1 -0
- package/dist/polling-login.js +204 -0
- package/dist/resources/account.d.ts +2 -2
- package/dist/resources/account.d.ts.map +1 -1
- package/dist/resources/account.js +1 -0
- package/dist/resources/api-keys.d.ts +3 -3
- package/dist/resources/api-keys.d.ts.map +1 -1
- package/dist/resources/api-keys.js +2 -0
- package/dist/resources/definition-revisions.d.ts +5 -5
- package/dist/resources/definition-revisions.d.ts.map +1 -1
- package/dist/resources/definition-revisions.js +4 -0
- package/dist/resources/definitions.d.ts +15 -4
- package/dist/resources/definitions.d.ts.map +1 -1
- package/dist/resources/definitions.js +11 -2
- package/dist/resources/generate.d.ts +14 -3
- package/dist/resources/generate.d.ts.map +1 -1
- package/dist/resources/generate.js +10 -2
- package/dist/resources/generations.d.ts +3 -3
- package/dist/resources/generations.d.ts.map +1 -1
- package/dist/resources/generations.js +2 -0
- package/dist/resources/projects.d.ts +56 -20
- package/dist/resources/projects.d.ts.map +1 -1
- package/dist/resources/projects.js +40 -4
- package/dist/resources/targets.d.ts +84 -10
- package/dist/resources/targets.d.ts.map +1 -1
- package/dist/resources/targets.js +127 -2
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +55 -26
- package/dist/types.d.ts +602 -123
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +24 -0
- package/package.json +2 -1
- package/src/api-identity.ts +98 -0
- package/src/auth-profiles.ts +114 -0
- package/src/cli.ts +444 -332
- package/src/console-login-check.ts +88 -0
- package/src/console-login-contract.ts +65 -0
- package/src/core/http.ts +88 -19
- package/src/credential-storage.ts +183 -0
- package/src/docs.ts +138 -0
- package/src/errors.ts +26 -15
- package/src/index.ts +29 -4
- package/src/named-credentials.ts +74 -0
- package/src/oauth-login.ts +184 -0
- package/src/oauth-request.ts +90 -0
- package/src/oauth-session.ts +258 -0
- package/src/ops.ts +48 -31
- package/src/polling-login.ts +165 -0
- package/src/resources/account.ts +3 -0
- package/src/resources/api-keys.ts +5 -0
- package/src/resources/definition-revisions.ts +9 -0
- package/src/resources/definitions.ts +25 -0
- package/src/resources/generate.ts +23 -0
- package/src/resources/generations.ts +5 -0
- package/src/resources/projects.ts +95 -7
- package/src/resources/targets.ts +241 -0
- package/src/schemas.ts +55 -26
- package/src/types.ts +640 -123
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type ConsoleLoginCheck, type ConsoleLoginConfiguration } from "./console-login-contract.js";
|
|
2
|
+
import { type OAuthLoginSession } from "./oauth-login.js";
|
|
3
|
+
import { type CredentialSchemes, type NamedCredentials } from "./named-credentials.js";
|
|
4
|
+
/** No traffic to Typeship occurs without the explicit --console-check action.
|
|
5
|
+
* Only the credentials selected for the identity read leave this process;
|
|
6
|
+
* refresh tokens and other stored logins are never sent to the Console. */
|
|
7
|
+
export declare function checkConsoleBrowserLogin(input: {
|
|
8
|
+
file: string;
|
|
9
|
+
configuration: ConsoleLoginConfiguration;
|
|
10
|
+
schemes: CredentialSchemes;
|
|
11
|
+
credentials: NamedCredentials;
|
|
12
|
+
login(timeoutMs: number): Promise<OAuthLoginSession>;
|
|
13
|
+
verify(credentials: NamedCredentials, expectations: ConsoleLoginCheck["expectations"]): Promise<void>;
|
|
14
|
+
progress(message: string): void;
|
|
15
|
+
}): Promise<{
|
|
16
|
+
ok: true;
|
|
17
|
+
method: "oauth_browser";
|
|
18
|
+
console_verified: true;
|
|
19
|
+
revoked: boolean | null;
|
|
20
|
+
}>;
|
|
21
|
+
//# sourceMappingURL=console-login-check.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"console-login-check.d.ts","sourceRoot":"","sources":["../src/console-login-check.ts"],"names":[],"mappings":"AAEA,OAAO,EAAgE,KAAK,iBAAiB,EAAE,KAAK,yBAAyB,EAAE,MAAM,6BAA6B,CAAC;AACnK,OAAO,EAAE,KAAK,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAC1D,OAAO,EAAyB,KAAK,iBAAiB,EAAE,KAAK,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAuB9G;;2EAE2E;AAC3E,wBAAsB,wBAAwB,CAAC,KAAK,EAAE;IACpD,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,yBAAyB,CAAC;IACzC,OAAO,EAAE,iBAAiB,CAAC;IAC3B,WAAW,EAAE,gBAAgB,CAAC;IAC9B,KAAK,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;IACrD,MAAM,CAAC,WAAW,EAAE,gBAAgB,EAAE,YAAY,EAAE,iBAAiB,CAAC,cAAc,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtG,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC,GAAG,OAAO,CAAC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,eAAe,CAAC;IAAC,gBAAgB,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAAA;CAAE,CAAC,CAiDlG"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { openSync, readSync, closeSync, fstatSync, constants } from "node:fs";
|
|
2
|
+
import { oauthJsonRequest } from "./oauth-request.js";
|
|
3
|
+
import { sameConsoleLoginConfiguration, matchesConsoleBrowserEvidence } from "./console-login-contract.js";
|
|
4
|
+
import { parseNamedCredentials } from "./named-credentials.js";
|
|
5
|
+
function readCheck(path) {
|
|
6
|
+
let fd;
|
|
7
|
+
try {
|
|
8
|
+
fd = openSync(path, constants.O_RDONLY | constants.O_NONBLOCK);
|
|
9
|
+
if (!fstatSync(fd).isFile())
|
|
10
|
+
throw new Error();
|
|
11
|
+
const buffer = Buffer.alloc(65_537);
|
|
12
|
+
let size = 0;
|
|
13
|
+
while (size < buffer.length) {
|
|
14
|
+
const n = readSync(fd, buffer, size, buffer.length - size, null);
|
|
15
|
+
if (!n)
|
|
16
|
+
break;
|
|
17
|
+
size += n;
|
|
18
|
+
}
|
|
19
|
+
if (size > 65_536)
|
|
20
|
+
throw new Error();
|
|
21
|
+
const value = JSON.parse(buffer.subarray(0, size).toString("utf8"));
|
|
22
|
+
if (value.version !== 1 || typeof value.ticket !== "string" || !/^[A-Za-z0-9_-]{43}$/.test(value.ticket) || !value.configuration || !Array.isArray(value.expectations) || !value.expectations.length || value.expectations.length > 3)
|
|
23
|
+
throw new Error();
|
|
24
|
+
const endpoint = new URL(value.endpoint);
|
|
25
|
+
const development = endpoint.protocol === "http:" && ["127.0.0.1", "[::1]"].includes(endpoint.hostname);
|
|
26
|
+
if (!(endpoint.origin === "https://typeship.dev" || development) || endpoint.pathname !== "/api/authentication/browser" || endpoint.username || endpoint.password || endpoint.search || endpoint.hash)
|
|
27
|
+
throw new Error();
|
|
28
|
+
const remaining = Date.parse(value.expiresAt) - Date.now();
|
|
29
|
+
if (!Number.isFinite(remaining) || remaining <= 0 || remaining > 600_000)
|
|
30
|
+
throw new Error();
|
|
31
|
+
return value;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
throw new Error("Download a new login check from your Typeship Console and pass its JSON file to --console-check. Checks expire after ten minutes.");
|
|
35
|
+
}
|
|
36
|
+
finally {
|
|
37
|
+
if (fd !== undefined)
|
|
38
|
+
closeSync(fd);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/** No traffic to Typeship occurs without the explicit --console-check action.
|
|
42
|
+
* Only the credentials selected for the identity read leave this process;
|
|
43
|
+
* refresh tokens and other stored logins are never sent to the Console. */
|
|
44
|
+
export async function checkConsoleBrowserLogin(input) {
|
|
45
|
+
const check = readCheck(input.file);
|
|
46
|
+
if (!sameConsoleLoginConfiguration(input.configuration, check.configuration))
|
|
47
|
+
throw new Error("This CLI's authentication settings do not match the saved Target. Regenerate the CLI, select the matching environment, and download a new check.");
|
|
48
|
+
const send = async (body, timeoutMs = 30_000) => {
|
|
49
|
+
const response = await oauthJsonRequest(check.endpoint, { method: "POST", headers: { "Content-Type": "application/json", Accept: "application/json" }, body: JSON.stringify({ ticket: check.ticket, ...body }) }, timeoutMs);
|
|
50
|
+
if (response.status === 422)
|
|
51
|
+
throw new Error("Typeship could not verify this login. Check the expected identity, API permissions and saved Target settings, then download a new check.");
|
|
52
|
+
if (response.status !== 200 || response.data?.ok !== true)
|
|
53
|
+
throw new Error("The Console check expired, was already used, changed, or could not be completed. Download a new check and try again.");
|
|
54
|
+
return response.data;
|
|
55
|
+
};
|
|
56
|
+
await send({ action: "inspect" });
|
|
57
|
+
let revoked = null;
|
|
58
|
+
try {
|
|
59
|
+
const supplied = parseNamedCredentials(input.credentials, input.schemes);
|
|
60
|
+
// Only a complete alternative that uses the fresh browser token proves
|
|
61
|
+
// browser access. An unrelated key-only alternative is not sufficient.
|
|
62
|
+
const requirement = input.configuration.requirements.find((entry) => {
|
|
63
|
+
const names = Object.keys(entry);
|
|
64
|
+
if (!names.some((name) => input.schemes[name]?.kind === "bearer") || !names.every((name) => input.schemes[name]?.kind === "bearer" || supplied[name] !== undefined))
|
|
65
|
+
return false;
|
|
66
|
+
const selected = input.configuration.requirements.find((candidate) => Object.keys(candidate).every((name) => names.includes(name)));
|
|
67
|
+
return selected && Object.keys(selected).some((name) => input.schemes[name]?.kind === "bearer");
|
|
68
|
+
});
|
|
69
|
+
if (!requirement)
|
|
70
|
+
throw new Error("Supply the additional named credentials required by the identity read with --credentials @file or environment variables.");
|
|
71
|
+
input.progress("This check runs a temporary browser login. Typeship will receive only the access token and any additional credentials required for the identity read; refresh tokens stay on this computer.");
|
|
72
|
+
const session = await input.login(Math.max(1, Date.parse(check.expiresAt) - Date.now()));
|
|
73
|
+
try {
|
|
74
|
+
const evidence = { method: "oauth_browser", issuer: session.issuer, clientId: session.clientId, redirectUri: session.redirectUri };
|
|
75
|
+
if (!matchesConsoleBrowserEvidence(input.configuration, evidence))
|
|
76
|
+
throw new Error("The browser login did not use the Target's client registration and native callback.");
|
|
77
|
+
const credentials = Object.fromEntries(Object.keys(requirement).map((name) => [name, input.schemes[name].kind === "bearer" ? session.accessToken : supplied[name]]));
|
|
78
|
+
await input.verify(credentials, check.expectations);
|
|
79
|
+
await send({ action: "complete", configuration: input.configuration, evidence, credentials, expectations: check.expectations });
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
// Verification sessions never replace the customer's saved login.
|
|
83
|
+
// Revoke the temporary grant when the provider advertises an endpoint.
|
|
84
|
+
if (session.revocationUrl) {
|
|
85
|
+
try {
|
|
86
|
+
const response = await fetch(session.revocationUrl, { method: "POST", redirect: "error", credentials: "omit", signal: AbortSignal.timeout(10_000), headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams({ token: session.refreshToken ?? session.accessToken, token_type_hint: session.refreshToken ? "refresh_token" : "access_token", client_id: session.clientId }) });
|
|
87
|
+
void response.body?.cancel().catch(() => { });
|
|
88
|
+
revoked = response.status === 200;
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
revoked = false;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
// Report only that the attempt ended. Never send provider errors, identity
|
|
98
|
+
// values or credentials, and never replace the original local error. A
|
|
99
|
+
// consumed check rejects this report, preserving any completed API proof.
|
|
100
|
+
try {
|
|
101
|
+
await send({ action: "fail" }, 5_000);
|
|
102
|
+
}
|
|
103
|
+
catch { /* Best effort; the Console can still expire the check. */ }
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
return { ok: true, method: "oauth_browser", console_verified: true, revoked };
|
|
107
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export interface ConsoleLoginConfiguration {
|
|
2
|
+
baseUrl: string;
|
|
3
|
+
environment: string | null;
|
|
4
|
+
operation: string;
|
|
5
|
+
request: {
|
|
6
|
+
method: string;
|
|
7
|
+
path: string;
|
|
8
|
+
graphqlField: string | null;
|
|
9
|
+
graphqlQuery: string | null;
|
|
10
|
+
};
|
|
11
|
+
requirements: Record<string, string[]>[];
|
|
12
|
+
issuer: string;
|
|
13
|
+
clientId: string;
|
|
14
|
+
discoveryUrl: string | null;
|
|
15
|
+
authorizationUrl: string | null;
|
|
16
|
+
tokenUrl: string | null;
|
|
17
|
+
redirectUri: string;
|
|
18
|
+
scopes: string[];
|
|
19
|
+
audience: string | null;
|
|
20
|
+
resource: string | null;
|
|
21
|
+
}
|
|
22
|
+
export interface ConsoleIdentityExpectation {
|
|
23
|
+
kind: "account" | "organization" | "user";
|
|
24
|
+
pointer: string;
|
|
25
|
+
expected: string | number;
|
|
26
|
+
}
|
|
27
|
+
export interface ConsoleLoginCheck {
|
|
28
|
+
version: 1;
|
|
29
|
+
endpoint: string;
|
|
30
|
+
ticket: string;
|
|
31
|
+
expiresAt: string;
|
|
32
|
+
configuration: ConsoleLoginConfiguration;
|
|
33
|
+
expectations: ConsoleIdentityExpectation[];
|
|
34
|
+
}
|
|
35
|
+
export interface ConsoleBrowserEvidence {
|
|
36
|
+
method: "oauth_browser";
|
|
37
|
+
issuer: string;
|
|
38
|
+
clientId: string;
|
|
39
|
+
redirectUri: string;
|
|
40
|
+
}
|
|
41
|
+
/** Compare the generated product's own settings with the reviewed check.
|
|
42
|
+
* The downloaded file never supplies the CLI's OAuth or API destination. */
|
|
43
|
+
export declare function sameConsoleLoginConfiguration(left: ConsoleLoginConfiguration, right: ConsoleLoginConfiguration): boolean;
|
|
44
|
+
export declare function matchesConsoleBrowserEvidence(config: ConsoleLoginConfiguration, evidence: ConsoleBrowserEvidence): boolean;
|
|
45
|
+
//# sourceMappingURL=console-login-contract.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"console-login-contract.d.ts","sourceRoot":"","sources":["../src/console-login-contract.ts"],"names":[],"mappings":"AACA,MAAM,WAAW,yBAAyB;IACxC,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;IACpG,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;IACzC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AACD,MAAM,WAAW,0BAA0B;IACzC,IAAI,EAAE,SAAS,GAAG,cAAc,GAAG,MAAM,CAAC;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC;CAC3B;AACD,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,CAAC,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,yBAAyB,CAAC;IACzC,YAAY,EAAE,0BAA0B,EAAE,CAAC;CAC5C;AACD,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,eAAe,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;CACrB;AAQD;4EAC4E;AAC5E,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,yBAAyB,EAAE,KAAK,EAAE,yBAAyB,GAAG,OAAO,CAOxH;AAED,wBAAgB,6BAA6B,CAAC,MAAM,EAAE,yBAAyB,EAAE,QAAQ,EAAE,sBAAsB,GAAG,OAAO,CAU1H"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
function canonical(value) {
|
|
2
|
+
if (Array.isArray(value))
|
|
3
|
+
return "[" + value.map(canonical).join(",") + "]";
|
|
4
|
+
if (value && typeof value === "object")
|
|
5
|
+
return "{" + Object.entries(value).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([key, item]) => JSON.stringify(key) + ":" + canonical(item)).join(",") + "}";
|
|
6
|
+
return JSON.stringify(value) ?? "null";
|
|
7
|
+
}
|
|
8
|
+
/** Compare the generated product's own settings with the reviewed check.
|
|
9
|
+
* The downloaded file never supplies the CLI's OAuth or API destination. */
|
|
10
|
+
export function sameConsoleLoginConfiguration(left, right) {
|
|
11
|
+
const normalize = (value) => ({ ...value,
|
|
12
|
+
baseUrl: new URL(value.baseUrl).href,
|
|
13
|
+
scopes: [...new Set(value.scopes)].sort(),
|
|
14
|
+
requirements: value.requirements.map((entry) => Object.fromEntries(Object.entries(entry).map(([key, scopes]) => [key, [...new Set(scopes)].sort()]))),
|
|
15
|
+
});
|
|
16
|
+
try {
|
|
17
|
+
return canonical(normalize(left)) === canonical(normalize(right));
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export function matchesConsoleBrowserEvidence(config, evidence) {
|
|
24
|
+
try {
|
|
25
|
+
if (evidence.method !== "oauth_browser" || evidence.issuer !== config.issuer || evidence.clientId !== config.clientId)
|
|
26
|
+
return false;
|
|
27
|
+
const configured = new URL(config.redirectUri), actual = new URL(evidence.redirectUri);
|
|
28
|
+
if (configured.protocol !== "http:" || !["127.0.0.1", "[::1]"].includes(configured.hostname) || configured.username || configured.password || configured.search || configured.hash)
|
|
29
|
+
return false;
|
|
30
|
+
if (configured.port && configured.port !== actual.port)
|
|
31
|
+
return false;
|
|
32
|
+
if (!actual.port)
|
|
33
|
+
return false;
|
|
34
|
+
configured.port = actual.port;
|
|
35
|
+
return configured.href === actual.href;
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
package/dist/core/http.d.ts
CHANGED
|
@@ -64,6 +64,14 @@ export declare class ApiError<S extends number = number, B = unknown> extends Er
|
|
|
64
64
|
export declare class UnexpectedApiError extends ApiError<number, unknown> {
|
|
65
65
|
constructor(status: number, body: unknown, response: ResponseMeta);
|
|
66
66
|
}
|
|
67
|
+
/** A successful response declared JSON but carried a body that could not be parsed. */
|
|
68
|
+
export declare class ResponseParseError extends Error {
|
|
69
|
+
readonly status: number;
|
|
70
|
+
readonly body: string;
|
|
71
|
+
readonly response: ResponseMeta;
|
|
72
|
+
readonly cause?: unknown;
|
|
73
|
+
constructor(body: string, response: ResponseMeta, cause?: unknown);
|
|
74
|
+
}
|
|
67
75
|
export interface Violation {
|
|
68
76
|
path: string;
|
|
69
77
|
message: string;
|
|
@@ -82,13 +90,14 @@ export declare class ValidationError extends Error {
|
|
|
82
90
|
* are ignored: validation can miss drift but never false-alarms.
|
|
83
91
|
*/
|
|
84
92
|
export declare function validateAgainstSchema(value: unknown, schema: unknown, path: string, out: Violation[], defs?: Record<string, unknown>): void;
|
|
85
|
-
/** The request
|
|
93
|
+
/** The request failed before a complete HTTP response arrived (network failure, timeout, abort, or truncated body). */
|
|
86
94
|
export declare class TransportError extends Error {
|
|
87
95
|
readonly cause?: unknown;
|
|
88
96
|
constructor(message: string, cause?: unknown);
|
|
89
97
|
}
|
|
90
98
|
type ErrorCtor = new (body: any, response: ResponseMeta) => ApiError<number, unknown>;
|
|
91
99
|
export interface CoreRequest {
|
|
100
|
+
security?: Record<string, string[]>[];
|
|
92
101
|
method: string;
|
|
93
102
|
path: string;
|
|
94
103
|
query?: Record<string, unknown>;
|
|
@@ -119,7 +128,13 @@ export interface RetryPolicy {
|
|
|
119
128
|
/** Also retry non-idempotent methods (POST/PATCH). */
|
|
120
129
|
retryNonIdempotent?: boolean;
|
|
121
130
|
}
|
|
131
|
+
export interface SecurityCredential {
|
|
132
|
+
headers?: Record<string, AuthValue>;
|
|
133
|
+
query?: Record<string, AuthValue>;
|
|
134
|
+
}
|
|
122
135
|
export interface CoreConfig {
|
|
136
|
+
/** Named credentials are selected using each operation's requirements. */
|
|
137
|
+
credentials?: Record<string, SecurityCredential>;
|
|
123
138
|
baseUrl: string;
|
|
124
139
|
headers: Record<string, AuthValue>;
|
|
125
140
|
/** Auth carried as query parameters (apiKey-in-query schemes). */
|
|
@@ -177,7 +192,7 @@ export interface DebugEvent {
|
|
|
177
192
|
/** 1-based; >1 means this was a retry */
|
|
178
193
|
attempt: number;
|
|
179
194
|
requestId?: string;
|
|
180
|
-
/** transport failure message, when
|
|
195
|
+
/** transport failure message, when the request failed before a complete response */
|
|
181
196
|
error?: string;
|
|
182
197
|
}
|
|
183
198
|
export declare class HttpCore {
|
package/dist/core/http.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/core/http.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,cAAc;IAC7B,iEAAiE;IACjE,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,mCAAmC;IACnC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IAC7C,yEAAyE;IACzE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wEAAwE;IACxE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB;;iCAE6B;IAC7B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;qCACiC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,CAAC,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;AAElE;8CAC8C;AAC9C,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,gDAAgD;IAChD,OAAO,EAAE,MAAM,CAAC;CACjB;AAGD;;;;GAIG;AACH,MAAM,MAAM,SAAS,CAAC,CAAC,EAAE,CAAC,IACtB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,CAAC,CAAC;IAAC,QAAQ,EAAE,YAAY,CAAA;CAAE,GAC7C;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,CAAC,CAAC;IAAC,QAAQ,CAAC,EAAE,YAAY,CAAA;CAAE,CAAC;AAErD,0EAA0E;AAC1E,wBAAgB,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,CAIvD;AAED,gDAAgD;AAChD,qBAAa,QAAQ,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,EAAE,CAAC,GAAG,OAAO,CAAE,SAAQ,KAAK;IACzE,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;IACnB,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;IACjB,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC;gBAEpB,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,QAAQ,EAAE,YAAY;CAOxE;AAED,kDAAkD;AAClD,qBAAa,kBAAmB,SAAQ,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;gBACnD,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY;CAGlE;AAOD,MAAM,WAAW,SAAS;IAAG,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE;AAE5D;;0CAE0C;AAC1C,qBAAa,eAAgB,SAAQ,KAAK;IACxC,QAAQ,CAAC,SAAS,EAAE,SAAS,GAAG,UAAU,CAAC;IAC3C,QAAQ,CAAC,UAAU,EAAE,SAAS,EAAE,CAAC;gBACrB,SAAS,EAAE,SAAS,GAAG,UAAU,EAAE,UAAU,EAAE,SAAS,EAAE;CAQvE;AAeD;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,SAAS,EAAE,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CA+E3I;AAqBD,
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/core/http.ts"],"names":[],"mappings":"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"}
|
package/dist/core/http.js
CHANGED
|
@@ -29,6 +29,21 @@ export class UnexpectedApiError extends ApiError {
|
|
|
29
29
|
super("Unexpected HTTP status " + status, status, body, response);
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
|
+
/** A successful response declared JSON but carried a body that could not be parsed. */
|
|
33
|
+
export class ResponseParseError extends Error {
|
|
34
|
+
status;
|
|
35
|
+
body;
|
|
36
|
+
response;
|
|
37
|
+
cause;
|
|
38
|
+
constructor(body, response, cause) {
|
|
39
|
+
super("HTTP " + response.status + " response body was not valid JSON");
|
|
40
|
+
this.name = "ResponseParseError";
|
|
41
|
+
this.status = response.status;
|
|
42
|
+
this.body = body;
|
|
43
|
+
this.response = response;
|
|
44
|
+
this.cause = cause;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
32
47
|
/** Request or response data did not match the spec's schema (opt-in via the
|
|
33
48
|
* client's validate option). Never thrown: returned as the error side of
|
|
34
49
|
* ApiResult, like every other failure. */
|
|
@@ -174,7 +189,7 @@ function transportFailureMessage(method, url, cause) {
|
|
|
174
189
|
}
|
|
175
190
|
return method + " " + url + " failed: " + detail;
|
|
176
191
|
}
|
|
177
|
-
/** The request
|
|
192
|
+
/** The request failed before a complete HTTP response arrived (network failure, timeout, abort, or truncated body). */
|
|
178
193
|
export class TransportError extends Error {
|
|
179
194
|
cause;
|
|
180
195
|
constructor(message, cause) {
|
|
@@ -183,6 +198,33 @@ export class TransportError extends Error {
|
|
|
183
198
|
this.cause = cause;
|
|
184
199
|
}
|
|
185
200
|
}
|
|
201
|
+
/** Resolve one complete alternative in spec order. Missing or incompatible
|
|
202
|
+
* combinations contribute no partial credentials. Anonymous operations send
|
|
203
|
+
* no generated credentials; explicit headers/hooks remain application owned. */
|
|
204
|
+
function selectSecurity(requirements, credentials) {
|
|
205
|
+
for (const requirement of requirements) {
|
|
206
|
+
const names = Object.keys(requirement);
|
|
207
|
+
if (!names.length || names.some((name) => !Object.hasOwn(credentials, name)))
|
|
208
|
+
continue;
|
|
209
|
+
const selected = { headers: Object.create(null), query: Object.create(null) };
|
|
210
|
+
const destinations = new Set();
|
|
211
|
+
let conflict = false;
|
|
212
|
+
for (const name of names) {
|
|
213
|
+
for (const location of ["headers", "query"]) {
|
|
214
|
+
for (const [wire, value] of Object.entries(credentials[name][location] ?? {})) {
|
|
215
|
+
const destination = location + ":" + (location === "headers" ? wire.toLowerCase() : wire);
|
|
216
|
+
if (destinations.has(destination))
|
|
217
|
+
conflict = true;
|
|
218
|
+
destinations.add(destination);
|
|
219
|
+
selected[location][wire] = value;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if (!conflict)
|
|
224
|
+
return selected;
|
|
225
|
+
}
|
|
226
|
+
return {};
|
|
227
|
+
}
|
|
186
228
|
const RETRYABLE_STATUSES = new Set([408, 429, 500, 502, 503, 504]);
|
|
187
229
|
/** Credential-bearing headers fetch itself drops when a redirect crosses to
|
|
188
230
|
* another origin. A spec's own API-key headers join them below. */
|
|
@@ -232,8 +274,13 @@ export class HttpCore {
|
|
|
232
274
|
const policy = { ...this.config.retry, ...req.retry };
|
|
233
275
|
const maxRetries = req.options?.maxRetries ?? policy.maxRetries ?? this.config.maxRetries;
|
|
234
276
|
const timeoutMs = req.options?.timeoutMs ?? this.config.timeoutMs;
|
|
235
|
-
const retryAllowed = req.idempotent === true ||
|
|
236
|
-
|
|
277
|
+
const retryAllowed = req.idempotent === true ||
|
|
278
|
+
req.method === "GET" ||
|
|
279
|
+
req.idempotencyKey !== undefined ||
|
|
280
|
+
policy.retryNonIdempotent === true;
|
|
281
|
+
const retryableStatuses = policy.statuses
|
|
282
|
+
? new Set(policy.statuses)
|
|
283
|
+
: RETRYABLE_STATUSES;
|
|
237
284
|
// One key per logical call, reused on every retry — that's the point
|
|
238
285
|
// of idempotency keys.
|
|
239
286
|
const autoIdempotencyKey = req.idempotencyKey !== undefined &&
|
|
@@ -299,10 +346,11 @@ export class HttpCore {
|
|
|
299
346
|
data = (await parseBody(response, req.method));
|
|
300
347
|
}
|
|
301
348
|
catch (cause) {
|
|
302
|
-
|
|
303
|
-
|
|
349
|
+
const parseError = cause instanceof ResponseParseError ? cause : undefined;
|
|
350
|
+
emitResponseDebug(response, parseError?.body);
|
|
351
|
+
const error = (parseError ?? new TransportError("The response body read failed before completing", cause));
|
|
304
352
|
await this.config.onError?.(error, { method: req.method, path: req.path });
|
|
305
|
-
return { ok: false, error, response: meta(response) };
|
|
353
|
+
return { ok: false, error, response: parseError?.response ?? meta(response) };
|
|
306
354
|
}
|
|
307
355
|
emitResponseDebug(response, data);
|
|
308
356
|
const responseMeta = meta(response, data);
|
|
@@ -366,8 +414,9 @@ export class HttpCore {
|
|
|
366
414
|
return { ok: false, error };
|
|
367
415
|
}
|
|
368
416
|
async send(req, timeoutMs, attempt, autoIdempotencyKey) {
|
|
417
|
+
const selected = req.security && this.config.credentials ? selectSecurity(req.security, this.config.credentials) : {};
|
|
369
418
|
const headers = {};
|
|
370
|
-
for (const [k, v] of Object.entries(this.config.headers)) {
|
|
419
|
+
for (const [k, v] of Object.entries({ ...this.config.headers, ...selected.headers })) {
|
|
371
420
|
headers[k] = await resolveAuthValue(v);
|
|
372
421
|
}
|
|
373
422
|
if (req.idempotencyKey && autoIdempotencyKey) {
|
|
@@ -384,7 +433,7 @@ export class HttpCore {
|
|
|
384
433
|
}
|
|
385
434
|
const context = {
|
|
386
435
|
method: req.method,
|
|
387
|
-
url: await this.buildUrl(req),
|
|
436
|
+
url: await this.buildUrl(req, selected.query),
|
|
388
437
|
headers,
|
|
389
438
|
attempt,
|
|
390
439
|
};
|
|
@@ -469,7 +518,7 @@ export class HttpCore {
|
|
|
469
518
|
origin = target.origin;
|
|
470
519
|
}
|
|
471
520
|
}
|
|
472
|
-
async buildUrl(req) {
|
|
521
|
+
async buildUrl(req, authQuery) {
|
|
473
522
|
const base = this.config.baseUrl.replace(/\/+$/, "");
|
|
474
523
|
const url = new URL(base + req.path);
|
|
475
524
|
for (const [k, v] of Object.entries(req.query ?? {})) {
|
|
@@ -484,7 +533,7 @@ export class HttpCore {
|
|
|
484
533
|
appendDeep(url.searchParams, k, v);
|
|
485
534
|
}
|
|
486
535
|
}
|
|
487
|
-
for (const [k, v] of Object.entries(this.config.query)) {
|
|
536
|
+
for (const [k, v] of Object.entries({ ...this.config.query, ...authQuery })) {
|
|
488
537
|
url.searchParams.append(k, await resolveAuthValue(v));
|
|
489
538
|
}
|
|
490
539
|
return url.toString();
|
|
@@ -541,10 +590,22 @@ function serializeBody(req) {
|
|
|
541
590
|
async function parseBody(response, method) {
|
|
542
591
|
if (method === "HEAD" || response.status === 204 || response.status === 205)
|
|
543
592
|
return undefined;
|
|
544
|
-
const contentType = response.headers.get("content-type") ?? "";
|
|
593
|
+
const contentType = (response.headers.get("content-type") ?? "").toLowerCase();
|
|
594
|
+
const mediaType = contentType.split(";", 1)[0].trim();
|
|
595
|
+
const slash = mediaType.indexOf("/");
|
|
596
|
+
const subtype = slash === -1 ? "" : mediaType.slice(slash + 1);
|
|
545
597
|
try {
|
|
546
|
-
if (
|
|
547
|
-
|
|
598
|
+
if (subtype === "json" || subtype.endsWith("+json")) {
|
|
599
|
+
const body = await response.text();
|
|
600
|
+
if (body.length === 0)
|
|
601
|
+
return undefined;
|
|
602
|
+
try {
|
|
603
|
+
return JSON.parse(body);
|
|
604
|
+
}
|
|
605
|
+
catch (cause) {
|
|
606
|
+
throw new ResponseParseError(body, meta(response, body), cause);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
548
609
|
if (contentType.startsWith("text/"))
|
|
549
610
|
return await response.text();
|
|
550
611
|
if (response.body === null)
|
|
@@ -552,11 +613,11 @@ async function parseBody(response, method) {
|
|
|
552
613
|
return await response.blob();
|
|
553
614
|
}
|
|
554
615
|
catch (cause) {
|
|
555
|
-
|
|
556
|
-
// empty body — surface it instead of faking success.
|
|
557
|
-
if (cause instanceof Error && (cause.name === "AbortError" || cause.name === "TimeoutError"))
|
|
616
|
+
if (cause instanceof ResponseParseError)
|
|
558
617
|
throw cause;
|
|
559
|
-
|
|
618
|
+
// A timed-out, aborted, or prematurely terminated body is a transport
|
|
619
|
+
// failure, not an empty body — surface it instead of faking success.
|
|
620
|
+
throw cause;
|
|
560
621
|
}
|
|
561
622
|
}
|
|
562
623
|
function requestIdFromBody(body) {
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { FileCredentialStore, type CredentialCodec } from "./oauth-session.js";
|
|
2
|
+
export interface NativeCommandResult {
|
|
3
|
+
status: number | null;
|
|
4
|
+
stdout: string;
|
|
5
|
+
stderr: string;
|
|
6
|
+
error?: unknown;
|
|
7
|
+
}
|
|
8
|
+
export type NativeCommand = (executable: string, args: string[], input?: string) => NativeCommandResult;
|
|
9
|
+
export interface CredentialKeyStore {
|
|
10
|
+
readonly name: string;
|
|
11
|
+
read(): Buffer | null;
|
|
12
|
+
write(key: Buffer): void;
|
|
13
|
+
/** Used only when explicitly removing this wrapping key, not ordinary logout. */
|
|
14
|
+
remove(): void;
|
|
15
|
+
}
|
|
16
|
+
/** A small wrapping key avoids OS item-size limits; session files are encrypted
|
|
17
|
+
* separately so refresh rotation can retain the atomic file/lock transaction. */
|
|
18
|
+
export declare function nativeCredentialKeyStore(path: string, platform?: NodeJS.Platform, run?: NativeCommand, environmentName?: string): CredentialKeyStore;
|
|
19
|
+
export declare function encryptedCredentialCodec(path: string, keyStore: CredentialKeyStore): CredentialCodec;
|
|
20
|
+
/** OS protection is the default. Plaintext storage is an explicit, separate
|
|
21
|
+
* store for environments where the owner accepts that tradeoff. Never migrate
|
|
22
|
+
* or fall back silently when an OS service is unavailable. */
|
|
23
|
+
export declare function createCredentialStore(directory: string, mode?: string, environmentName?: string): FileCredentialStore;
|
|
24
|
+
//# sourceMappingURL=credential-storage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credential-storage.d.ts","sourceRoot":"","sources":["../src/credential-storage.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,mBAAmB,EAA0B,KAAK,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAEvG,MAAM,WAAW,mBAAmB;IAAG,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE;AAC/G,MAAM,MAAM,aAAa,GAAG,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,mBAAmB,CAAC;AACxG,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,IAAI,IAAI,MAAM,GAAG,IAAI,CAAC;IACtB,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,iFAAiF;IACjF,MAAM,IAAI,IAAI,CAAC;CAChB;AAkBD;iFACiF;AACjF,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAM,CAAC,QAA2B,EAAE,GAAG,GAAE,aAA6B,EAAE,eAAe,SAAqB,GAAG,kBAAkB,CAsGjM;AAED,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,kBAAkB,GAAG,eAAe,CAkCpG;AAED;;8DAE8D;AAC9D,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,GAAE,MAAa,EAAE,eAAe,SAAqB,GAAG,mBAAmB,CAKvI"}
|