@typeship-ax/cli 0.6.0 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/AGENTS.md +31 -0
  2. package/README.md +26 -9
  3. package/api.json +6861 -3083
  4. package/api.md +508 -277
  5. package/dist/api-identity.d.ts +40 -0
  6. package/dist/api-identity.d.ts.map +1 -0
  7. package/dist/api-identity.js +128 -0
  8. package/dist/auth-profiles.d.ts +30 -0
  9. package/dist/auth-profiles.d.ts.map +1 -0
  10. package/dist/auth-profiles.js +138 -0
  11. package/dist/cli-agent.d.ts +9 -1
  12. package/dist/cli-agent.d.ts.map +1 -1
  13. package/dist/cli-agent.js +26 -9
  14. package/dist/cli.js +600 -582
  15. package/dist/console-login-check.d.ts +21 -0
  16. package/dist/console-login-check.d.ts.map +1 -0
  17. package/dist/console-login-check.js +107 -0
  18. package/dist/console-login-contract.d.ts +45 -0
  19. package/dist/console-login-contract.d.ts.map +1 -0
  20. package/dist/console-login-contract.js +40 -0
  21. package/dist/core/http.d.ts +21 -92
  22. package/dist/core/http.d.ts.map +1 -1
  23. package/dist/core/http.js +143 -221
  24. package/dist/core/pagination.d.ts.map +1 -1
  25. package/dist/core/pagination.js +6 -34
  26. package/dist/credential-storage.d.ts +24 -0
  27. package/dist/credential-storage.d.ts.map +1 -0
  28. package/dist/credential-storage.js +207 -0
  29. package/dist/dates.d.ts +0 -2
  30. package/dist/dates.d.ts.map +1 -1
  31. package/dist/dates.js +0 -1
  32. package/dist/docs.d.ts +36 -0
  33. package/dist/docs.d.ts.map +1 -0
  34. package/dist/docs.js +258 -0
  35. package/dist/errors.d.ts +42 -34
  36. package/dist/errors.d.ts.map +1 -1
  37. package/dist/errors.js +30 -20
  38. package/dist/index.d.ts +27 -12
  39. package/dist/index.d.ts.map +1 -1
  40. package/dist/index.js +40 -14
  41. package/dist/named-credentials.d.ts +21 -0
  42. package/dist/named-credentials.d.ts.map +1 -0
  43. package/dist/named-credentials.js +86 -0
  44. package/dist/oauth-login.d.ts +39 -0
  45. package/dist/oauth-login.d.ts.map +1 -0
  46. package/dist/oauth-login.js +171 -0
  47. package/dist/oauth-request.d.ts +21 -0
  48. package/dist/oauth-request.d.ts.map +1 -0
  49. package/dist/oauth-request.js +119 -0
  50. package/dist/oauth-session.d.ts +106 -0
  51. package/dist/oauth-session.d.ts.map +1 -0
  52. package/dist/oauth-session.js +244 -0
  53. package/dist/ops.d.ts +18 -0
  54. package/dist/ops.d.ts.map +1 -1
  55. package/dist/ops.js +31 -17
  56. package/dist/polling-login.d.ts +57 -0
  57. package/dist/polling-login.d.ts.map +1 -0
  58. package/dist/polling-login.js +204 -0
  59. package/dist/resources/account.d.ts +4 -4
  60. package/dist/resources/account.d.ts.map +1 -1
  61. package/dist/resources/account.js +1 -0
  62. package/dist/resources/api-keys.d.ts +13 -8
  63. package/dist/resources/api-keys.d.ts.map +1 -1
  64. package/dist/resources/api-keys.js +5 -1
  65. package/dist/resources/definition-revisions.d.ts +58 -0
  66. package/dist/resources/definition-revisions.d.ts.map +1 -0
  67. package/dist/resources/definition-revisions.js +114 -0
  68. package/dist/resources/definitions.d.ts +35 -0
  69. package/dist/resources/definitions.d.ts.map +1 -0
  70. package/dist/resources/definitions.js +60 -0
  71. package/dist/resources/generate.d.ts +18 -7
  72. package/dist/resources/generate.d.ts.map +1 -1
  73. package/dist/resources/generate.js +13 -5
  74. package/dist/resources/generations.d.ts +6 -6
  75. package/dist/resources/generations.d.ts.map +1 -1
  76. package/dist/resources/generations.js +3 -1
  77. package/dist/resources/projects.d.ts +111 -35
  78. package/dist/resources/projects.d.ts.map +1 -1
  79. package/dist/resources/projects.js +125 -15
  80. package/dist/resources/targets.d.ts +97 -0
  81. package/dist/resources/targets.d.ts.map +1 -0
  82. package/dist/resources/targets.js +197 -0
  83. package/dist/schemas.d.ts.map +1 -1
  84. package/dist/schemas.js +135 -62
  85. package/dist/types.d.ts +2072 -267
  86. package/dist/types.d.ts.map +1 -1
  87. package/dist/types.js +20 -3
  88. package/package.json +2 -1
  89. package/src/api-identity.ts +98 -0
  90. package/src/auth-profiles.ts +114 -0
  91. package/src/cli-agent.ts +28 -11
  92. package/src/cli.ts +528 -560
  93. package/src/console-login-check.ts +88 -0
  94. package/src/console-login-contract.ts +65 -0
  95. package/src/core/http.ts +156 -305
  96. package/src/core/pagination.ts +6 -30
  97. package/src/credential-storage.ts +183 -0
  98. package/src/dates.ts +0 -1
  99. package/src/docs.ts +239 -0
  100. package/src/errors.ts +52 -41
  101. package/src/index.ts +49 -14
  102. package/src/named-credentials.ts +74 -0
  103. package/src/oauth-login.ts +184 -0
  104. package/src/oauth-request.ts +90 -0
  105. package/src/oauth-session.ts +258 -0
  106. package/src/ops.ts +56 -17
  107. package/src/polling-login.ts +165 -0
  108. package/src/resources/account.ts +6 -3
  109. package/src/resources/api-keys.ts +27 -7
  110. package/src/resources/definition-revisions.ts +207 -0
  111. package/src/resources/definitions.ts +122 -0
  112. package/src/resources/generate.ts +29 -6
  113. package/src/resources/generations.ts +9 -4
  114. package/src/resources/projects.ts +274 -41
  115. package/src/resources/targets.ts +378 -0
  116. package/src/schemas.ts +135 -62
  117. package/src/types.ts +2273 -322
  118. package/dist/resources/spec-revisions.d.ts +0 -47
  119. package/dist/resources/spec-revisions.d.ts.map +0 -1
  120. package/dist/resources/spec-revisions.js +0 -90
  121. package/src/resources/spec-revisions.ts +0 -150
@@ -0,0 +1,88 @@
1
+ import { openSync, readSync, closeSync, fstatSync, constants } from "node:fs";
2
+ import { oauthJsonRequest } from "./oauth-request.js";
3
+ import { sameConsoleLoginConfiguration, matchesConsoleBrowserEvidence, type ConsoleLoginCheck, type ConsoleLoginConfiguration } from "./console-login-contract.js";
4
+ import { type OAuthLoginSession } from "./oauth-login.js";
5
+ import { parseNamedCredentials, type CredentialSchemes, type NamedCredentials } from "./named-credentials.js";
6
+
7
+ function readCheck(path: string): ConsoleLoginCheck {
8
+ let fd: number | undefined;
9
+ try {
10
+ fd = openSync(path, constants.O_RDONLY | constants.O_NONBLOCK);
11
+ if (!fstatSync(fd).isFile()) throw new Error();
12
+ const buffer = Buffer.alloc(65_537);
13
+ let size = 0;
14
+ while (size < buffer.length) { const n = readSync(fd, buffer, size, buffer.length - size, null); if (!n) break; size += n; }
15
+ if (size > 65_536) throw new Error();
16
+ const value = JSON.parse(buffer.subarray(0, size).toString("utf8")) as ConsoleLoginCheck;
17
+ 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) throw new Error();
18
+ const endpoint = new URL(value.endpoint);
19
+ const development = endpoint.protocol === "http:" && ["127.0.0.1", "[::1]"].includes(endpoint.hostname);
20
+ if (!(endpoint.origin === "https://typeship.dev" || development) || endpoint.pathname !== "/api/authentication/browser" || endpoint.username || endpoint.password || endpoint.search || endpoint.hash) throw new Error();
21
+ const remaining = Date.parse(value.expiresAt) - Date.now();
22
+ if (!Number.isFinite(remaining) || remaining <= 0 || remaining > 600_000) throw new Error();
23
+ return value;
24
+ } catch { 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."); }
25
+ finally { if (fd !== undefined) closeSync(fd); }
26
+ }
27
+
28
+ /** No traffic to Typeship occurs without the explicit --console-check action.
29
+ * Only the credentials selected for the identity read leave this process;
30
+ * refresh tokens and other stored logins are never sent to the Console. */
31
+ export async function checkConsoleBrowserLogin(input: {
32
+ file: string;
33
+ configuration: ConsoleLoginConfiguration;
34
+ schemes: CredentialSchemes;
35
+ credentials: NamedCredentials;
36
+ login(timeoutMs: number): Promise<OAuthLoginSession>;
37
+ verify(credentials: NamedCredentials, expectations: ConsoleLoginCheck["expectations"]): Promise<void>;
38
+ progress(message: string): void;
39
+ }): Promise<{ ok: true; method: "oauth_browser"; console_verified: true; revoked: boolean | null }> {
40
+ const check = readCheck(input.file);
41
+ if (!sameConsoleLoginConfiguration(input.configuration, check.configuration)) 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.");
42
+ const send = async (body: Record<string, unknown>, timeoutMs = 30_000) => {
43
+ const response = await oauthJsonRequest(check.endpoint, { method: "POST", headers: { "Content-Type": "application/json", Accept: "application/json" }, body: JSON.stringify({ ticket: check.ticket, ...body }) }, timeoutMs);
44
+ if (response.status === 422) throw new Error("Typeship could not verify this login. Check the expected identity, API permissions and saved Target settings, then download a new check.");
45
+ if (response.status !== 200 || response.data?.ok !== true) throw new Error("The Console check expired, was already used, changed, or could not be completed. Download a new check and try again.");
46
+ return response.data;
47
+ };
48
+ await send({ action: "inspect" });
49
+ let revoked: boolean | null = null;
50
+ try {
51
+ const supplied = parseNamedCredentials(input.credentials, input.schemes);
52
+ // Only a complete alternative that uses the fresh browser token proves
53
+ // browser access. An unrelated key-only alternative is not sufficient.
54
+ const requirement = input.configuration.requirements.find((entry) => {
55
+ const names = Object.keys(entry);
56
+ if (!names.some((name) => input.schemes[name]?.kind === "bearer") || !names.every((name) => input.schemes[name]?.kind === "bearer" || supplied[name] !== undefined)) return false;
57
+ const selected = input.configuration.requirements.find((candidate) => Object.keys(candidate).every((name) => names.includes(name)));
58
+ return selected && Object.keys(selected).some((name) => input.schemes[name]?.kind === "bearer");
59
+ });
60
+ if (!requirement) throw new Error("Supply the additional named credentials required by the identity read with --credentials @file or environment variables.");
61
+ 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.");
62
+ const session = await input.login(Math.max(1, Date.parse(check.expiresAt) - Date.now()));
63
+ try {
64
+ const evidence = { method: "oauth_browser" as const, issuer: session.issuer, clientId: session.clientId, redirectUri: session.redirectUri };
65
+ if (!matchesConsoleBrowserEvidence(input.configuration, evidence)) throw new Error("The browser login did not use the Target's client registration and native callback.");
66
+ const credentials: NamedCredentials = Object.fromEntries(Object.keys(requirement).map((name) => [name, input.schemes[name]!.kind === "bearer" ? session.accessToken : supplied[name]! ]));
67
+ await input.verify(credentials, check.expectations);
68
+ await send({ action: "complete", configuration: input.configuration, evidence, credentials, expectations: check.expectations });
69
+ } finally {
70
+ // Verification sessions never replace the customer's saved login.
71
+ // Revoke the temporary grant when the provider advertises an endpoint.
72
+ if (session.revocationUrl) {
73
+ try {
74
+ 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 }) });
75
+ void response.body?.cancel().catch(() => {});
76
+ revoked = response.status === 200;
77
+ } catch { revoked = false; }
78
+ }
79
+ }
80
+ } catch (error) {
81
+ // Report only that the attempt ended. Never send provider errors, identity
82
+ // values or credentials, and never replace the original local error. A
83
+ // consumed check rejects this report, preserving any completed API proof.
84
+ try { await send({ action: "fail" }, 5_000); } catch { /* Best effort; the Console can still expire the check. */ }
85
+ throw error;
86
+ }
87
+ return { ok: true, method: "oauth_browser", console_verified: true, revoked };
88
+ }
@@ -0,0 +1,65 @@
1
+
2
+ export interface ConsoleLoginConfiguration {
3
+ baseUrl: string;
4
+ environment: string | null;
5
+ operation: string;
6
+ request: { method: string; path: string; graphqlField: string | null; graphqlQuery: string | null };
7
+ requirements: Record<string, string[]>[];
8
+ issuer: string;
9
+ clientId: string;
10
+ discoveryUrl: string | null;
11
+ authorizationUrl: string | null;
12
+ tokenUrl: string | null;
13
+ redirectUri: string;
14
+ scopes: string[];
15
+ audience: string | null;
16
+ resource: string | null;
17
+ }
18
+ export interface ConsoleIdentityExpectation {
19
+ kind: "account" | "organization" | "user";
20
+ pointer: string;
21
+ expected: string | number;
22
+ }
23
+ export interface ConsoleLoginCheck {
24
+ version: 1;
25
+ endpoint: string;
26
+ ticket: string;
27
+ expiresAt: string;
28
+ configuration: ConsoleLoginConfiguration;
29
+ expectations: ConsoleIdentityExpectation[];
30
+ }
31
+ export interface ConsoleBrowserEvidence {
32
+ method: "oauth_browser";
33
+ issuer: string;
34
+ clientId: string;
35
+ redirectUri: string;
36
+ }
37
+
38
+ function canonical(value: unknown): string {
39
+ if (Array.isArray(value)) return "[" + value.map(canonical).join(",") + "]";
40
+ if (value && typeof value === "object") return "{" + Object.entries(value).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([key, item]) => JSON.stringify(key) + ":" + canonical(item)).join(",") + "}";
41
+ return JSON.stringify(value) ?? "null";
42
+ }
43
+
44
+ /** Compare the generated product's own settings with the reviewed check.
45
+ * The downloaded file never supplies the CLI's OAuth or API destination. */
46
+ export function sameConsoleLoginConfiguration(left: ConsoleLoginConfiguration, right: ConsoleLoginConfiguration): boolean {
47
+ const normalize = (value: ConsoleLoginConfiguration) => ({ ...value,
48
+ baseUrl: new URL(value.baseUrl).href,
49
+ scopes: [...new Set(value.scopes)].sort(),
50
+ requirements: value.requirements.map((entry) => Object.fromEntries(Object.entries(entry).map(([key, scopes]) => [key, [...new Set(scopes)].sort()]))),
51
+ });
52
+ try { return canonical(normalize(left)) === canonical(normalize(right)); } catch { return false; }
53
+ }
54
+
55
+ export function matchesConsoleBrowserEvidence(config: ConsoleLoginConfiguration, evidence: ConsoleBrowserEvidence): boolean {
56
+ try {
57
+ if (evidence.method !== "oauth_browser" || evidence.issuer !== config.issuer || evidence.clientId !== config.clientId) return false;
58
+ const configured = new URL(config.redirectUri), actual = new URL(evidence.redirectUri);
59
+ if (configured.protocol !== "http:" || !["127.0.0.1", "[::1]"].includes(configured.hostname) || configured.username || configured.password || configured.search || configured.hash) return false;
60
+ if (configured.port && configured.port !== actual.port) return false;
61
+ if (!actual.port) return false;
62
+ configured.port = actual.port;
63
+ return configured.href === actual.href;
64
+ } catch { return false; }
65
+ }