@framedash/cli 0.1.2 → 0.1.3
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 +41 -7
- package/dist/commands/alerts.d.ts.map +1 -1
- package/dist/commands/alerts.js +2 -1
- package/dist/commands/alerts.js.map +1 -1
- package/dist/commands/auth.d.ts.map +1 -1
- package/dist/commands/auth.js +25 -2
- package/dist/commands/auth.js.map +1 -1
- package/dist/commands/login.d.ts +2 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/login.js +142 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/logout.d.ts +2 -0
- package/dist/commands/logout.d.ts.map +1 -0
- package/dist/commands/logout.js +79 -0
- package/dist/commands/logout.js.map +1 -0
- package/dist/commands/map-capture.d.ts +2 -1
- package/dist/commands/map-capture.d.ts.map +1 -1
- package/dist/commands/map-capture.js +38 -9
- package/dist/commands/map-capture.js.map +1 -1
- package/dist/commands/query.d.ts.map +1 -1
- package/dist/commands/query.js +12 -0
- package/dist/commands/query.js.map +1 -1
- package/dist/commands/run-profile-test.d.ts.map +1 -1
- package/dist/commands/run-profile-test.js +143 -26
- package/dist/commands/run-profile-test.js.map +1 -1
- package/dist/commands/threshold-profiles.d.ts +2 -0
- package/dist/commands/threshold-profiles.d.ts.map +1 -0
- package/dist/commands/threshold-profiles.js +24 -0
- package/dist/commands/threshold-profiles.js.map +1 -0
- package/dist/index.js +10 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/config.d.ts +42 -1
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +44 -14
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/create-client.d.ts +25 -7
- package/dist/lib/create-client.d.ts.map +1 -1
- package/dist/lib/create-client.js +160 -33
- package/dist/lib/create-client.js.map +1 -1
- package/dist/lib/formatters.js +93 -1
- package/dist/lib/formatters.js.map +1 -1
- package/dist/lib/oauth/loopback-server.d.ts +27 -0
- package/dist/lib/oauth/loopback-server.d.ts.map +1 -0
- package/dist/lib/oauth/loopback-server.js +164 -0
- package/dist/lib/oauth/loopback-server.js.map +1 -0
- package/dist/lib/oauth/manager.d.ts +36 -0
- package/dist/lib/oauth/manager.d.ts.map +1 -0
- package/dist/lib/oauth/manager.js +130 -0
- package/dist/lib/oauth/manager.js.map +1 -0
- package/dist/lib/oauth/pkce.d.ts +15 -0
- package/dist/lib/oauth/pkce.d.ts.map +1 -0
- package/dist/lib/oauth/pkce.js +24 -0
- package/dist/lib/oauth/pkce.js.map +1 -0
- package/dist/lib/oauth/token-endpoint.d.ts +39 -0
- package/dist/lib/oauth/token-endpoint.d.ts.map +1 -0
- package/dist/lib/oauth/token-endpoint.js +117 -0
- package/dist/lib/oauth/token-endpoint.js.map +1 -0
- package/dist/lib/oauth/token-store.d.ts +34 -0
- package/dist/lib/oauth/token-store.d.ts.map +1 -0
- package/dist/lib/oauth/token-store.js +164 -0
- package/dist/lib/oauth/token-store.js.map +1 -0
- package/dist/lib/run-command.js +2 -2
- package/dist/lib/run-command.js.map +1 -1
- package/dist/lib/run-profile-test-lib.d.ts +39 -0
- package/dist/lib/run-profile-test-lib.d.ts.map +1 -1
- package/dist/lib/run-profile-test-lib.js +47 -0
- package/dist/lib/run-profile-test-lib.js.map +1 -1
- package/dist/lib/uploader.d.ts +14 -1
- package/dist/lib/uploader.d.ts.map +1 -1
- package/dist/lib/uploader.js +26 -12
- package/dist/lib/uploader.js.map +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { OAuthTokenRequestError, refreshTokenGrant, toStoredEntry } from "./token-endpoint.js";
|
|
2
|
+
import { deleteStoredEntry, readStoredEntry, saveStoredEntry, } from "./token-store.js";
|
|
3
|
+
// In-process lifecycle for one origin's stored OAuth credentials: hands out
|
|
4
|
+
// the access token, refreshes proactively near expiry (and on demand after a
|
|
5
|
+
// 401), and persists rotated refresh tokens atomically via the token store.
|
|
6
|
+
//
|
|
7
|
+
// Concurrency: refreshes are coalesced IN-PROCESS (one request per manager,
|
|
8
|
+
// and create-client shares one manager per origin). Across processes the
|
|
9
|
+
// store is last-writer-wins with no locking; a concurrent same-origin
|
|
10
|
+
// refresh can race rotation, whose worst case is the server's reuse
|
|
11
|
+
// revocation surfacing as the ordinary invalid_grant re-login path below.
|
|
12
|
+
// See token-store.ts for the full concurrency-model note.
|
|
13
|
+
/** Refresh when the access token expires within this window. */
|
|
14
|
+
const EXPIRY_SKEW_MS = 60_000;
|
|
15
|
+
/**
|
|
16
|
+
* The stored grant is dead (refresh returned invalid_grant: revoked, expired,
|
|
17
|
+
* or rotated out from under us). The stored entry has already been cleared;
|
|
18
|
+
* the only recovery is an interactive re-login, so callers must surface the
|
|
19
|
+
* message and stop -- retrying cannot succeed.
|
|
20
|
+
*/
|
|
21
|
+
export class OAuthLoginRequiredError extends Error {
|
|
22
|
+
constructor(origin) {
|
|
23
|
+
super(`Your stored login for ${origin} has expired or been revoked. ` +
|
|
24
|
+
`Run 'framedash login' to sign in again (CI should use FRAMEDASH_API_KEY instead).`);
|
|
25
|
+
this.name = "OAuthLoginRequiredError";
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export class OAuthTokenManager {
|
|
29
|
+
baseUrl;
|
|
30
|
+
origin;
|
|
31
|
+
entry;
|
|
32
|
+
refreshPromise = null;
|
|
33
|
+
constructor(baseUrl, origin, entry) {
|
|
34
|
+
this.baseUrl = baseUrl;
|
|
35
|
+
this.origin = origin;
|
|
36
|
+
this.entry = entry;
|
|
37
|
+
}
|
|
38
|
+
/** Space-delimited granted scopes (safe to display). */
|
|
39
|
+
get scope() {
|
|
40
|
+
return this.entry.scope;
|
|
41
|
+
}
|
|
42
|
+
/** Access-token expiry, epoch ms (safe to display). */
|
|
43
|
+
get expiresAt() {
|
|
44
|
+
return this.entry.expires_at;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Current access token, refreshing first when it expires within 60s so an
|
|
48
|
+
* about-to-expire token is not sent only to bounce with a 401.
|
|
49
|
+
*/
|
|
50
|
+
async getAccessToken() {
|
|
51
|
+
if (this.entry.expires_at - Date.now() > EXPIRY_SKEW_MS) {
|
|
52
|
+
return this.entry.access_token;
|
|
53
|
+
}
|
|
54
|
+
return this.refresh();
|
|
55
|
+
}
|
|
56
|
+
/** Force a refresh (after a 401 despite a locally-unexpired token). */
|
|
57
|
+
async forceRefresh() {
|
|
58
|
+
return this.refresh();
|
|
59
|
+
}
|
|
60
|
+
/** Coalesce concurrent callers onto a single refresh request. */
|
|
61
|
+
refresh() {
|
|
62
|
+
if (this.refreshPromise === null) {
|
|
63
|
+
this.refreshPromise = this.doRefresh().finally(() => {
|
|
64
|
+
this.refreshPromise = null;
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
return this.refreshPromise;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Refresh with the CURRENT stored token, then persist the rotated pair
|
|
71
|
+
* (atomic write, last-writer-wins).
|
|
72
|
+
*/
|
|
73
|
+
async doRefresh() {
|
|
74
|
+
// Re-read first: another process may have rotated since this manager
|
|
75
|
+
// loaded. Presenting an already-rotated token would trip the server's
|
|
76
|
+
// reuse detection, so prefer the stored (current) credentials.
|
|
77
|
+
const stored = readStoredEntry(this.origin);
|
|
78
|
+
if (!stored) {
|
|
79
|
+
// The entry is GONE: a logout (or an invalid_grant cleanup) completed
|
|
80
|
+
// while we held stale in-memory credentials -- demand a re-login
|
|
81
|
+
// rather than reviving a login the user terminated.
|
|
82
|
+
throw new OAuthLoginRequiredError(this.origin);
|
|
83
|
+
}
|
|
84
|
+
if (stored.access_token !== this.entry.access_token) {
|
|
85
|
+
this.entry = stored;
|
|
86
|
+
if (stored.expires_at - Date.now() > EXPIRY_SKEW_MS) {
|
|
87
|
+
// Another process's access token is still fresh: no refresh needed.
|
|
88
|
+
return stored.access_token;
|
|
89
|
+
}
|
|
90
|
+
// Fall through and refresh with the reloaded (current) refresh token.
|
|
91
|
+
}
|
|
92
|
+
let entry;
|
|
93
|
+
try {
|
|
94
|
+
entry = toStoredEntry(await refreshTokenGrant(this.baseUrl, this.entry.refresh_token));
|
|
95
|
+
}
|
|
96
|
+
catch (err) {
|
|
97
|
+
if (err instanceof OAuthTokenRequestError && err.code === "invalid_grant") {
|
|
98
|
+
// Dead grant: revoked, expired, or a concurrent refresh in another
|
|
99
|
+
// process won the rotation race (reuse revocation). Clear the local
|
|
100
|
+
// entry and ask for a re-login -- the accepted worst case of the
|
|
101
|
+
// lock-free store.
|
|
102
|
+
await deleteStoredEntry(this.origin);
|
|
103
|
+
throw new OAuthLoginRequiredError(this.origin);
|
|
104
|
+
}
|
|
105
|
+
// Transient failure (network, 5xx, rate limit): keep the stored entry
|
|
106
|
+
// so a later attempt can retry.
|
|
107
|
+
throw err;
|
|
108
|
+
}
|
|
109
|
+
// Persist BEFORE first use: rotation invalidated the old refresh token
|
|
110
|
+
// server-side, so losing the new one would strand this login.
|
|
111
|
+
try {
|
|
112
|
+
await saveStoredEntry(this.origin, entry);
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
// The rotation could not be persisted, but the OLD refresh token on
|
|
116
|
+
// disk is already consumed server-side: best-effort remove it so the
|
|
117
|
+
// next run gets a clean re-login instead of reuse revocation.
|
|
118
|
+
try {
|
|
119
|
+
await deleteStoredEntry(this.origin);
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
// Best effort: the same broken disk likely fails this too.
|
|
123
|
+
}
|
|
124
|
+
throw err;
|
|
125
|
+
}
|
|
126
|
+
this.entry = entry;
|
|
127
|
+
return entry.access_token;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manager.js","sourceRoot":"","sources":["../../../src/lib/oauth/manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate an RFC 7636 code_verifier: 48 random bytes -> 64 base64url chars
|
|
3
|
+
* (within the required 43-128 range, alphabet [A-Za-z0-9-_] which is a subset
|
|
4
|
+
* of the allowed unreserved characters).
|
|
5
|
+
*/
|
|
6
|
+
export declare function generateCodeVerifier(): string;
|
|
7
|
+
/** Compute the S256 code_challenge: base64url(sha256(verifier)), no padding. */
|
|
8
|
+
export declare function computeS256CodeChallenge(codeVerifier: string): string;
|
|
9
|
+
/**
|
|
10
|
+
* Generate the CSRF `state` parameter: 24 random bytes -> 32 base64url chars.
|
|
11
|
+
* Compared byte-for-byte against the loopback callback; any mismatch aborts
|
|
12
|
+
* the login without a token exchange.
|
|
13
|
+
*/
|
|
14
|
+
export declare function generateState(): string;
|
|
15
|
+
//# sourceMappingURL=pkce.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pkce.d.ts","sourceRoot":"","sources":["../../../src/lib/oauth/pkce.ts"],"names":[],"mappings":"AAKA;;;;GAIG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,CAE7C;AAED,gFAAgF;AAChF,wBAAgB,wBAAwB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAErE;AAED;;;;GAIG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAEtC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
2
|
+
// PKCE (RFC 7636) + CSRF-state helpers for the `framedash login` flow.
|
|
3
|
+
// Pure node:crypto module (no I/O) so tests exercise it directly.
|
|
4
|
+
/**
|
|
5
|
+
* Generate an RFC 7636 code_verifier: 48 random bytes -> 64 base64url chars
|
|
6
|
+
* (within the required 43-128 range, alphabet [A-Za-z0-9-_] which is a subset
|
|
7
|
+
* of the allowed unreserved characters).
|
|
8
|
+
*/
|
|
9
|
+
export function generateCodeVerifier() {
|
|
10
|
+
return randomBytes(48).toString("base64url");
|
|
11
|
+
}
|
|
12
|
+
/** Compute the S256 code_challenge: base64url(sha256(verifier)), no padding. */
|
|
13
|
+
export function computeS256CodeChallenge(codeVerifier) {
|
|
14
|
+
return createHash("sha256").update(codeVerifier, "ascii").digest("base64url");
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Generate the CSRF `state` parameter: 24 random bytes -> 32 base64url chars.
|
|
18
|
+
* Compared byte-for-byte against the loopback callback; any mismatch aborts
|
|
19
|
+
* the login without a token exchange.
|
|
20
|
+
*/
|
|
21
|
+
export function generateState() {
|
|
22
|
+
return randomBytes(24).toString("base64url");
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=pkce.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pkce.js","sourceRoot":"","sources":["../../../src/lib/oauth/pkce.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAEtD,uEAAuE;AACvE,kEAAkE;AAElE;;;;GAIG;AACH,MAAM,UAAU,oBAAoB;IACnC,OAAO,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;AAC9C,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,wBAAwB,CAAC,YAAoB;IAC5D,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;AAC/E,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa;IAC5B,OAAO,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;AAC9C,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { StoredTokenEntry } from "./token-store.js";
|
|
2
|
+
/** Well-known client_id of the seeded first-party CLI client. */
|
|
3
|
+
export declare const CLI_OAUTH_CLIENT_ID = "fdc_framedash_cli";
|
|
4
|
+
/** Per-request timeout for token/revocation calls. */
|
|
5
|
+
export declare const TOKEN_REQUEST_TIMEOUT_MS = 30000;
|
|
6
|
+
/** Structured OAuth error (RFC 6749 section 5.2) from the token endpoint. */
|
|
7
|
+
export declare class OAuthTokenRequestError extends Error {
|
|
8
|
+
/** Machine-readable error code, e.g. "invalid_grant". */
|
|
9
|
+
readonly code: string;
|
|
10
|
+
readonly status: number;
|
|
11
|
+
constructor(
|
|
12
|
+
/** Machine-readable error code, e.g. "invalid_grant". */
|
|
13
|
+
code: string, description: string, status: number);
|
|
14
|
+
}
|
|
15
|
+
export type TokenResponse = {
|
|
16
|
+
access_token: string;
|
|
17
|
+
refresh_token: string;
|
|
18
|
+
/** Access token lifetime in seconds. */
|
|
19
|
+
expires_in: number;
|
|
20
|
+
/** Space-delimited granted scopes. */
|
|
21
|
+
scope: string;
|
|
22
|
+
};
|
|
23
|
+
/** RFC 6749 section 4.1.3 authorization-code exchange with the PKCE verifier. */
|
|
24
|
+
export declare function exchangeAuthorizationCode(baseUrl: string, options: {
|
|
25
|
+
code: string;
|
|
26
|
+
codeVerifier: string;
|
|
27
|
+
redirectUri: string;
|
|
28
|
+
}): Promise<TokenResponse>;
|
|
29
|
+
/** RFC 6749 section 6 refresh grant (the server rotates the refresh token). */
|
|
30
|
+
export declare function refreshTokenGrant(baseUrl: string, refreshToken: string): Promise<TokenResponse>;
|
|
31
|
+
/**
|
|
32
|
+
* RFC 7009 revocation. Revoking the refresh token disconnects the whole
|
|
33
|
+
* grant server-side. Throws on transport errors; callers treat revocation
|
|
34
|
+
* as best effort (local credentials are removed regardless).
|
|
35
|
+
*/
|
|
36
|
+
export declare function revokeToken(baseUrl: string, token: string): Promise<void>;
|
|
37
|
+
/** Convert a token response into the persisted entry shape. */
|
|
38
|
+
export declare function toStoredEntry(response: TokenResponse, now?: number): StoredTokenEntry;
|
|
39
|
+
//# sourceMappingURL=token-endpoint.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-endpoint.d.ts","sourceRoot":"","sources":["../../../src/lib/oauth/token-endpoint.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAWzD,iEAAiE;AACjE,eAAO,MAAM,mBAAmB,sBAAsB,CAAC;AAEvD,sDAAsD;AACtD,eAAO,MAAM,wBAAwB,QAAS,CAAC;AAE/C,6EAA6E;AAC7E,qBAAa,sBAAuB,SAAQ,KAAK;IAE/C,yDAAyD;aACzC,IAAI,EAAE,MAAM;aAEZ,MAAM,EAAE,MAAM;;IAH9B,yDAAyD;IACzC,IAAI,EAAE,MAAM,EAC5B,WAAW,EAAE,MAAM,EACH,MAAM,EAAE,MAAM;CAK/B;AAED,MAAM,MAAM,aAAa,GAAG;IAC3B,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,wCAAwC;IACxC,UAAU,EAAE,MAAM,CAAC;IACnB,sCAAsC;IACtC,KAAK,EAAE,MAAM,CAAC;CACd,CAAC;AA6DF,iFAAiF;AACjF,wBAAsB,yBAAyB,CAC9C,OAAO,EAAE,MAAM,EACf,OAAO,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GAClE,OAAO,CAAC,aAAa,CAAC,CAQxB;AAED,+EAA+E;AAC/E,wBAAsB,iBAAiB,CACtC,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,MAAM,GAClB,OAAO,CAAC,aAAa,CAAC,CAMxB;AAED;;;;GAIG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAM/E;AAED,+DAA+D;AAC/D,wBAAgB,aAAa,CAAC,QAAQ,EAAE,aAAa,EAAE,GAAG,SAAa,GAAG,gBAAgB,CAOzF"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { assertSafeBaseUrl } from "@framedash/api-client";
|
|
2
|
+
// HTTP client for the authorization server's token and revocation endpoints
|
|
3
|
+
// (RFC 6749 / RFC 7009). The CLI is the seeded first-party PUBLIC client
|
|
4
|
+
// (migration 0058): no client secret; possession of the PKCE verifier or the
|
|
5
|
+
// rotating refresh token is the proof.
|
|
6
|
+
//
|
|
7
|
+
// SECURITY: token values pass through here in memory only. Error paths must
|
|
8
|
+
// never include request parameters (which contain tokens) -- only the
|
|
9
|
+
// server's error code/description and the HTTP status.
|
|
10
|
+
/** Well-known client_id of the seeded first-party CLI client. */
|
|
11
|
+
export const CLI_OAUTH_CLIENT_ID = "fdc_framedash_cli";
|
|
12
|
+
/** Per-request timeout for token/revocation calls. */
|
|
13
|
+
export const TOKEN_REQUEST_TIMEOUT_MS = 30_000;
|
|
14
|
+
/** Structured OAuth error (RFC 6749 section 5.2) from the token endpoint. */
|
|
15
|
+
export class OAuthTokenRequestError extends Error {
|
|
16
|
+
code;
|
|
17
|
+
status;
|
|
18
|
+
constructor(
|
|
19
|
+
/** Machine-readable error code, e.g. "invalid_grant". */
|
|
20
|
+
code, description, status) {
|
|
21
|
+
super(`Token request failed (${code}): ${description}`);
|
|
22
|
+
this.code = code;
|
|
23
|
+
this.status = status;
|
|
24
|
+
this.name = "OAuthTokenRequestError";
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function endpointUrl(baseUrl, path) {
|
|
28
|
+
// Origin-rooted: the AS serves its endpoints at the app origin regardless
|
|
29
|
+
// of any path on the configured base URL.
|
|
30
|
+
assertSafeBaseUrl(baseUrl);
|
|
31
|
+
return new URL(path, baseUrl).toString();
|
|
32
|
+
}
|
|
33
|
+
async function postForm(url, params) {
|
|
34
|
+
return fetch(url, {
|
|
35
|
+
method: "POST",
|
|
36
|
+
headers: {
|
|
37
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
38
|
+
Accept: "application/json",
|
|
39
|
+
},
|
|
40
|
+
body: params.toString(),
|
|
41
|
+
// Never follow a redirect: it would re-send the form body (which
|
|
42
|
+
// contains token material) to the redirect target.
|
|
43
|
+
redirect: "manual",
|
|
44
|
+
signal: AbortSignal.timeout(TOKEN_REQUEST_TIMEOUT_MS),
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
async function requestToken(baseUrl, params) {
|
|
48
|
+
params.set("client_id", CLI_OAUTH_CLIENT_ID);
|
|
49
|
+
const response = await postForm(endpointUrl(baseUrl, "/api/oauth/token"), params);
|
|
50
|
+
let json;
|
|
51
|
+
try {
|
|
52
|
+
json = JSON.parse(await response.text());
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
throw new Error(`Token endpoint returned a non-JSON response (HTTP ${response.status})`);
|
|
56
|
+
}
|
|
57
|
+
const obj = typeof json === "object" && json !== null ? json : {};
|
|
58
|
+
if (!response.ok) {
|
|
59
|
+
const code = typeof obj.error === "string" ? obj.error : "unknown_error";
|
|
60
|
+
const description = typeof obj.error_description === "string" ? obj.error_description : `HTTP ${response.status}`;
|
|
61
|
+
throw new OAuthTokenRequestError(code, description, response.status);
|
|
62
|
+
}
|
|
63
|
+
if (typeof obj.access_token !== "string" ||
|
|
64
|
+
obj.access_token === "" ||
|
|
65
|
+
typeof obj.refresh_token !== "string" ||
|
|
66
|
+
obj.refresh_token === "" ||
|
|
67
|
+
typeof obj.expires_in !== "number" ||
|
|
68
|
+
!Number.isFinite(obj.expires_in)) {
|
|
69
|
+
throw new Error("Token endpoint returned an unexpected response shape");
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
access_token: obj.access_token,
|
|
73
|
+
refresh_token: obj.refresh_token,
|
|
74
|
+
expires_in: obj.expires_in,
|
|
75
|
+
scope: typeof obj.scope === "string" ? obj.scope : "",
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/** RFC 6749 section 4.1.3 authorization-code exchange with the PKCE verifier. */
|
|
79
|
+
export async function exchangeAuthorizationCode(baseUrl, options) {
|
|
80
|
+
const params = new URLSearchParams({
|
|
81
|
+
grant_type: "authorization_code",
|
|
82
|
+
code: options.code,
|
|
83
|
+
redirect_uri: options.redirectUri,
|
|
84
|
+
code_verifier: options.codeVerifier,
|
|
85
|
+
});
|
|
86
|
+
return requestToken(baseUrl, params);
|
|
87
|
+
}
|
|
88
|
+
/** RFC 6749 section 6 refresh grant (the server rotates the refresh token). */
|
|
89
|
+
export async function refreshTokenGrant(baseUrl, refreshToken) {
|
|
90
|
+
const params = new URLSearchParams({
|
|
91
|
+
grant_type: "refresh_token",
|
|
92
|
+
refresh_token: refreshToken,
|
|
93
|
+
});
|
|
94
|
+
return requestToken(baseUrl, params);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* RFC 7009 revocation. Revoking the refresh token disconnects the whole
|
|
98
|
+
* grant server-side. Throws on transport errors; callers treat revocation
|
|
99
|
+
* as best effort (local credentials are removed regardless).
|
|
100
|
+
*/
|
|
101
|
+
export async function revokeToken(baseUrl, token) {
|
|
102
|
+
const params = new URLSearchParams({ token, client_id: CLI_OAUTH_CLIENT_ID });
|
|
103
|
+
const response = await postForm(endpointUrl(baseUrl, "/api/oauth/revoke"), params);
|
|
104
|
+
if (!response.ok) {
|
|
105
|
+
throw new Error(`Revocation endpoint returned HTTP ${response.status}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/** Convert a token response into the persisted entry shape. */
|
|
109
|
+
export function toStoredEntry(response, now = Date.now()) {
|
|
110
|
+
return {
|
|
111
|
+
access_token: response.access_token,
|
|
112
|
+
refresh_token: response.refresh_token,
|
|
113
|
+
expires_at: now + response.expires_in * 1000,
|
|
114
|
+
scope: response.scope,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=token-endpoint.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-endpoint.js","sourceRoot":"","sources":["../../../src/lib/oauth/token-endpoint.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export type StoredTokenEntry = {
|
|
2
|
+
access_token: string;
|
|
3
|
+
refresh_token: string;
|
|
4
|
+
/** Absolute expiry of the access token, Unix epoch milliseconds. */
|
|
5
|
+
expires_at: number;
|
|
6
|
+
/** Space-delimited granted scopes (display + status output). */
|
|
7
|
+
scope: string;
|
|
8
|
+
};
|
|
9
|
+
export type TokenStore = Record<string, StoredTokenEntry>;
|
|
10
|
+
/** Resolve the credentials file path (honors XDG_CONFIG_HOME at call time). */
|
|
11
|
+
export declare function credentialsFilePath(): string;
|
|
12
|
+
/**
|
|
13
|
+
* Read the whole token store. Missing, unreadable, or corrupt files -- and
|
|
14
|
+
* individual malformed entries -- degrade to "absent" rather than throwing:
|
|
15
|
+
* the caller then falls back to other credential sources or asks the user to
|
|
16
|
+
* log in again.
|
|
17
|
+
*/
|
|
18
|
+
export declare function readTokenStore(): TokenStore;
|
|
19
|
+
/** Read the stored entry for one base-URL origin, if any. */
|
|
20
|
+
export declare function readStoredEntry(origin: string): StoredTokenEntry | undefined;
|
|
21
|
+
/**
|
|
22
|
+
* Insert or replace the entry for an origin (atomic write). Async signature
|
|
23
|
+
* kept for call-site stability even though the body is synchronous.
|
|
24
|
+
*/
|
|
25
|
+
export declare function saveStoredEntry(origin: string, entry: StoredTokenEntry): Promise<void>;
|
|
26
|
+
/** Remove the entry for an origin. Returns true if an entry was removed. */
|
|
27
|
+
export declare function deleteStoredEntry(origin: string): Promise<boolean>;
|
|
28
|
+
/**
|
|
29
|
+
* Delete the whole credentials file (logout --all), including any orphaned
|
|
30
|
+
* credentials.json.*.tmp files a crashed write left behind -- those hold the
|
|
31
|
+
* same token material as the store itself.
|
|
32
|
+
*/
|
|
33
|
+
export declare function clearTokenStore(): Promise<void>;
|
|
34
|
+
//# sourceMappingURL=token-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-store.d.ts","sourceRoot":"","sources":["../../../src/lib/oauth/token-store.ts"],"names":[],"mappings":"AAmCA,MAAM,MAAM,gBAAgB,GAAG;IAC9B,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,oEAAoE;IACpE,UAAU,EAAE,MAAM,CAAC;IACnB,gEAAgE;IAChE,KAAK,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;AAE1D,+EAA+E;AAC/E,wBAAgB,mBAAmB,IAAI,MAAM,CAI5C;AAgBD;;;;;GAKG;AACH,wBAAgB,cAAc,IAAI,UAAU,CAuB3C;AAED,6DAA6D;AAC7D,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAK5E;AAqED;;;GAGG;AACH,wBAAsB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAI5F;AAED,4EAA4E;AAC5E,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAQxE;AAED;;;;GAIG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAMrD"}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { chmodSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync, } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
/** Resolve the credentials file path (honors XDG_CONFIG_HOME at call time). */
|
|
6
|
+
export function credentialsFilePath() {
|
|
7
|
+
const xdg = process.env.XDG_CONFIG_HOME;
|
|
8
|
+
const configHome = xdg && xdg.trim() !== "" ? xdg : join(homedir(), ".config");
|
|
9
|
+
return join(configHome, "framedash", "credentials.json");
|
|
10
|
+
}
|
|
11
|
+
function isStoredTokenEntry(value) {
|
|
12
|
+
if (typeof value !== "object" || value === null)
|
|
13
|
+
return false;
|
|
14
|
+
const entry = value;
|
|
15
|
+
return (typeof entry.access_token === "string" &&
|
|
16
|
+
entry.access_token !== "" &&
|
|
17
|
+
typeof entry.refresh_token === "string" &&
|
|
18
|
+
entry.refresh_token !== "" &&
|
|
19
|
+
typeof entry.expires_at === "number" &&
|
|
20
|
+
Number.isFinite(entry.expires_at) &&
|
|
21
|
+
typeof entry.scope === "string");
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Read the whole token store. Missing, unreadable, or corrupt files -- and
|
|
25
|
+
* individual malformed entries -- degrade to "absent" rather than throwing:
|
|
26
|
+
* the caller then falls back to other credential sources or asks the user to
|
|
27
|
+
* log in again.
|
|
28
|
+
*/
|
|
29
|
+
export function readTokenStore() {
|
|
30
|
+
let raw;
|
|
31
|
+
try {
|
|
32
|
+
raw = readFileSync(credentialsFilePath(), "utf8");
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return {};
|
|
36
|
+
}
|
|
37
|
+
let parsed;
|
|
38
|
+
try {
|
|
39
|
+
parsed = JSON.parse(raw);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return {};
|
|
43
|
+
}
|
|
44
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
45
|
+
return {};
|
|
46
|
+
}
|
|
47
|
+
const store = {};
|
|
48
|
+
for (const [origin, entry] of Object.entries(parsed)) {
|
|
49
|
+
if (isStoredTokenEntry(entry)) {
|
|
50
|
+
store[origin] = entry;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return store;
|
|
54
|
+
}
|
|
55
|
+
/** Read the stored entry for one base-URL origin, if any. */
|
|
56
|
+
export function readStoredEntry(origin) {
|
|
57
|
+
const store = readTokenStore();
|
|
58
|
+
// Own-property guard: a prototype-chain key (e.g. "toString") must never
|
|
59
|
+
// masquerade as a stored entry.
|
|
60
|
+
return Object.hasOwn(store, origin) ? store[origin] : undefined;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Reap orphaned credentials.json.*.tmp files left by crashed writes -- they
|
|
64
|
+
* hold the same token material as the store itself. Called from every write
|
|
65
|
+
* and from clearTokenStore, so any mutation heals a previous crash.
|
|
66
|
+
*/
|
|
67
|
+
function reapOrphanTmps() {
|
|
68
|
+
const filePath = credentialsFilePath();
|
|
69
|
+
const base = `${filePath}.`;
|
|
70
|
+
let names;
|
|
71
|
+
try {
|
|
72
|
+
names = readdirSync(dirname(filePath));
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return; // Config dir itself is gone: nothing to reap.
|
|
76
|
+
}
|
|
77
|
+
for (const name of names) {
|
|
78
|
+
const full = join(dirname(filePath), name);
|
|
79
|
+
if (full.startsWith(base) && full.endsWith(".tmp")) {
|
|
80
|
+
// Best-effort: reaping runs AFTER the atomic rename has already
|
|
81
|
+
// committed the real write, so a cleanup failure (locked file on
|
|
82
|
+
// Windows, odd FS state) must never propagate and make a successful
|
|
83
|
+
// saveStoredEntry look failed. recursive covers a dir-shaped orphan.
|
|
84
|
+
try {
|
|
85
|
+
rmSync(full, { force: true, recursive: true });
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
// Leave the orphan; the next mutation will retry the reap.
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Persist the whole store atomically: write a temp file in the target
|
|
95
|
+
* directory, then rename over the destination (rename replaces existing
|
|
96
|
+
* files on both POSIX and Windows), so readers always see a complete JSON
|
|
97
|
+
* document. Concurrent writers are last-writer-wins by design (see the
|
|
98
|
+
* concurrency model note above).
|
|
99
|
+
*/
|
|
100
|
+
function writeTokenStore(store) {
|
|
101
|
+
const filePath = credentialsFilePath();
|
|
102
|
+
const dir = dirname(filePath);
|
|
103
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
104
|
+
try {
|
|
105
|
+
// mkdirSync's mode only applies on creation; tighten a pre-existing
|
|
106
|
+
// directory too so credentials.json never sits in a world-readable dir.
|
|
107
|
+
// Best-effort no-op on Windows.
|
|
108
|
+
chmodSync(dir, 0o700);
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
// Best effort only.
|
|
112
|
+
}
|
|
113
|
+
const tmpPath = `${filePath}.${process.pid}.${randomBytes(4).toString("hex")}.tmp`;
|
|
114
|
+
try {
|
|
115
|
+
writeFileSync(tmpPath, `${JSON.stringify(store, null, "\t")}\n`, { mode: 0o600 });
|
|
116
|
+
renameSync(tmpPath, filePath);
|
|
117
|
+
}
|
|
118
|
+
catch (err) {
|
|
119
|
+
rmSync(tmpPath, { force: true });
|
|
120
|
+
throw err;
|
|
121
|
+
}
|
|
122
|
+
try {
|
|
123
|
+
// Re-assert the mode in case the file pre-existed with wider permissions;
|
|
124
|
+
// best-effort no-op on Windows.
|
|
125
|
+
chmodSync(filePath, 0o600);
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
// Best effort only.
|
|
129
|
+
}
|
|
130
|
+
reapOrphanTmps();
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Insert or replace the entry for an origin (atomic write). Async signature
|
|
134
|
+
* kept for call-site stability even though the body is synchronous.
|
|
135
|
+
*/
|
|
136
|
+
export async function saveStoredEntry(origin, entry) {
|
|
137
|
+
const store = readTokenStore();
|
|
138
|
+
store[origin] = entry;
|
|
139
|
+
writeTokenStore(store);
|
|
140
|
+
}
|
|
141
|
+
/** Remove the entry for an origin. Returns true if an entry was removed. */
|
|
142
|
+
export async function deleteStoredEntry(origin) {
|
|
143
|
+
const store = readTokenStore();
|
|
144
|
+
// Object.hasOwn (not `in`): prototype-chain keys are not stored entries
|
|
145
|
+
// and must not trigger a rewrite.
|
|
146
|
+
if (!Object.hasOwn(store, origin))
|
|
147
|
+
return false;
|
|
148
|
+
delete store[origin];
|
|
149
|
+
writeTokenStore(store);
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Delete the whole credentials file (logout --all), including any orphaned
|
|
154
|
+
* credentials.json.*.tmp files a crashed write left behind -- those hold the
|
|
155
|
+
* same token material as the store itself.
|
|
156
|
+
*/
|
|
157
|
+
export async function clearTokenStore() {
|
|
158
|
+
// recursive so a credentials path that is unexpectedly a directory
|
|
159
|
+
// (corruption / manual tampering) is still removed rather than throwing
|
|
160
|
+
// and blocking logout --all from clearing on-disk token material.
|
|
161
|
+
rmSync(credentialsFilePath(), { force: true, recursive: true });
|
|
162
|
+
reapOrphanTmps();
|
|
163
|
+
}
|
|
164
|
+
//# sourceMappingURL=token-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-store.js","sourceRoot":"","sources":["../../../src/lib/oauth/token-store.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/run-command.js
CHANGED
|
@@ -20,12 +20,12 @@ export async function runCommand(opts, handler) {
|
|
|
20
20
|
if (opts.noProject) {
|
|
21
21
|
const baseConfig = resolveConfigWithoutProject(typedValues);
|
|
22
22
|
const config = { ...baseConfig, projectId: "" };
|
|
23
|
-
const client = createClient(config.baseUrl, config.
|
|
23
|
+
const client = createClient(config.baseUrl, config.credential, "");
|
|
24
24
|
await handler({ config, client, values: typedValues, positionals });
|
|
25
25
|
}
|
|
26
26
|
else {
|
|
27
27
|
const config = resolveConfig(typedValues);
|
|
28
|
-
const client = createClient(config.baseUrl, config.
|
|
28
|
+
const client = createClient(config.baseUrl, config.credential, config.projectId);
|
|
29
29
|
await handler({ config, client, values: typedValues, positionals });
|
|
30
30
|
}
|
|
31
31
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run-command.js","sourceRoot":"","sources":["../../src/lib/run-command.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwB,SAAS,EAAE,MAAM,WAAW,CAAC;AAE5D,OAAO,EAEN,cAAc,EACd,aAAa,EACb,2BAA2B,GAC3B,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAiBzC;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAC/B,IAAoB,EACpB,OAA+C;IAE/C,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,GAAG,SAAS,CAAC;QACzC,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,OAAO,EAAE,EAAE,GAAG,cAAc,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE;QAC/C,gBAAgB,EAAE,IAAI,CAAC,gBAAgB,IAAI,KAAK;KAChD,CAAC,CAAC;IAEH,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;QACjB,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACf,OAAO;IACR,CAAC;IAED,MAAM,WAAW,GAAG,MAAsD,CAAC;IAE3E,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;QACpB,MAAM,UAAU,GAAG,2BAA2B,CAAC,WAAW,CAAC,CAAC;QAC5D,MAAM,MAAM,GAAc,EAAE,GAAG,UAAU,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC;QAC3D,MAAM,MAAM,GAAG,YAAY,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,
|
|
1
|
+
{"version":3,"file":"run-command.js","sourceRoot":"","sources":["../../src/lib/run-command.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwB,SAAS,EAAE,MAAM,WAAW,CAAC;AAE5D,OAAO,EAEN,cAAc,EACd,aAAa,EACb,2BAA2B,GAC3B,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAiBzC;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAC/B,IAAoB,EACpB,OAA+C;IAE/C,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,GAAG,SAAS,CAAC;QACzC,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,OAAO,EAAE,EAAE,GAAG,cAAc,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE;QAC/C,gBAAgB,EAAE,IAAI,CAAC,gBAAgB,IAAI,KAAK;KAChD,CAAC,CAAC;IAEH,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;QACjB,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACf,OAAO;IACR,CAAC;IAED,MAAM,WAAW,GAAG,MAAsD,CAAC;IAE3E,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;QACpB,MAAM,UAAU,GAAG,2BAA2B,CAAC,WAAW,CAAC,CAAC;QAC5D,MAAM,MAAM,GAAc,EAAE,GAAG,UAAU,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC;QAC3D,MAAM,MAAM,GAAG,YAAY,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;QACnE,MAAM,OAAO,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,WAAW,EAAE,CAAC,CAAC;IACrE,CAAC;SAAM,CAAC;QACP,MAAM,MAAM,GAAG,aAAa,CAAC,WAAW,CAAC,CAAC;QAC1C,MAAM,MAAM,GAAG,YAAY,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC;QACjF,MAAM,OAAO,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,WAAW,EAAE,CAAC,CAAC;IACrE,CAAC;AACF,CAAC;AAID;;;GAGG;AACH,MAAM,UAAU,eAAe,CAC9B,IAAY,EACZ,IAAY,EACZ,WAA0B;IAE1B,OAAO,KAAK,EAAE,IAAc,EAAiB,EAAE;QAC9C,MAAM,UAAU,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QAE3B,IAAI,CAAC,UAAU,IAAI,UAAU,KAAK,QAAQ,IAAI,UAAU,KAAK,IAAI,EAAE,CAAC;YACnE,GAAG,CAAC,IAAI,CAAC,CAAC;YACV,OAAO;QACR,CAAC;QAED,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,WAAW,EAAE,UAAU,CAAC,EAAE,CAAC;YAC7C,KAAK,CAAC,WAAW,IAAI,gBAAgB,UAAU,EAAE,CAAC,CAAC;YACnD,GAAG,CAAC,IAAI,CAAC,CAAC;YACV,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAChB,OAAO;QACR,CAAC;QAED,+EAA+E;QAC/E,MAAM,WAAW,CAAC,UAAU,CAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/C,CAAC,CAAC;AACH,CAAC"}
|
|
@@ -58,6 +58,45 @@ export declare function resolveProfileIdentity(flags: IdentityInputs, git: GitFa
|
|
|
58
58
|
* branch/commit/scenario and stamps an empty ci.* tag.
|
|
59
59
|
*/
|
|
60
60
|
export declare function buildSessionEnv(identity: ProfileIdentity): Record<string, string>;
|
|
61
|
+
/** Result of validating a "seconds" CLI option value. */
|
|
62
|
+
export type SecondsValidation = {
|
|
63
|
+
value: number;
|
|
64
|
+
} | {
|
|
65
|
+
error: string;
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* Validate a seconds-valued option. Returns the parsed number, or an error
|
|
69
|
+
* message describing why it is invalid (the caller adds the --flag prefix and
|
|
70
|
+
* decides how to surface it). A non-string value (unset, or the boolean parseArgs
|
|
71
|
+
* yields for a value-less flag) falls back to `fallback`. With `allowZero` the
|
|
72
|
+
* value may be 0 (used by --command-timeout, where 0 disables the bound);
|
|
73
|
+
* otherwise it must be strictly positive (as --ingest-timeout / --poll-interval
|
|
74
|
+
* require).
|
|
75
|
+
*/
|
|
76
|
+
export declare function validateSeconds(value: string | boolean | undefined, fallback: number, opts?: {
|
|
77
|
+
allowZero?: boolean;
|
|
78
|
+
max?: number;
|
|
79
|
+
}): SecondsValidation;
|
|
80
|
+
/**
|
|
81
|
+
* Platform-specific plan for terminating a launched process TREE by pid. A plain
|
|
82
|
+
* kill of the direct child orphans its grandchildren (a Unity/UE5 player launched
|
|
83
|
+
* via the shell spawns helper processes that survive), so on win32 we shell out
|
|
84
|
+
* to `taskkill /T /F` (whole tree, forced) and on POSIX we signal the child's
|
|
85
|
+
* process GROUP (the negative pid, valid because the child is spawned detached
|
|
86
|
+
* into its own group). Pure so the argv/decision is unit-testable without
|
|
87
|
+
* spawning anything.
|
|
88
|
+
*/
|
|
89
|
+
export type TreeKillPlan = {
|
|
90
|
+
platform: "win32";
|
|
91
|
+
command: "taskkill";
|
|
92
|
+
args: string[];
|
|
93
|
+
} | {
|
|
94
|
+
platform: "posix";
|
|
95
|
+
groupPid: number;
|
|
96
|
+
} | {
|
|
97
|
+
platform: "noop";
|
|
98
|
+
};
|
|
99
|
+
export declare function planTreeKill(platform: NodeJS.Platform, pid: number | undefined): TreeKillPlan;
|
|
61
100
|
/** Minimal shape of a builds-list row (mirrors apps/web BuildInfo). */
|
|
62
101
|
export interface BuildListEntry {
|
|
63
102
|
build_id: string;
|