@shipstatic/types 2.1.0 → 2.2.1-beta.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/dist/index.d.ts +103 -63
- package/dist/index.js +183 -55
- package/package.json +6 -3
- package/src/index.ts +245 -129
package/dist/index.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ export declare const DeploymentStatus: {
|
|
|
11
11
|
readonly FAILED: "failed";
|
|
12
12
|
readonly DELETING: "deleting";
|
|
13
13
|
};
|
|
14
|
-
export type DeploymentStatusType = typeof DeploymentStatus[keyof typeof DeploymentStatus];
|
|
14
|
+
export type DeploymentStatusType = (typeof DeploymentStatus)[keyof typeof DeploymentStatus];
|
|
15
15
|
/**
|
|
16
16
|
* Core deployment object - used in both API responses and SDK
|
|
17
17
|
*/
|
|
@@ -74,7 +74,7 @@ export declare const DomainStatus: {
|
|
|
74
74
|
readonly SUCCESS: "success";
|
|
75
75
|
readonly PAUSED: "paused";
|
|
76
76
|
};
|
|
77
|
-
export type DomainStatusType = typeof DomainStatus[keyof typeof DomainStatus];
|
|
77
|
+
export type DomainStatusType = (typeof DomainStatus)[keyof typeof DomainStatus];
|
|
78
78
|
/**
|
|
79
79
|
* Core domain object - used in both API responses and SDK
|
|
80
80
|
*/
|
|
@@ -178,30 +178,10 @@ export interface DomainValidateResponse {
|
|
|
178
178
|
/** Error message, null when valid */
|
|
179
179
|
error: string | null;
|
|
180
180
|
}
|
|
181
|
-
/**
|
|
182
|
-
* Deployment token for automated deployments
|
|
183
|
-
*/
|
|
184
|
-
export interface Token {
|
|
185
|
-
/** 7-char management identifier */
|
|
186
|
-
readonly token: string;
|
|
187
|
-
/** The account this token belongs to */
|
|
188
|
-
readonly account: string;
|
|
189
|
-
/** SHA256 hash of the raw credential (auth lookups only, never exposed to users) */
|
|
190
|
-
readonly hash: string;
|
|
191
|
-
/** IP-lock. Non-null = single-use (claimable only from this IP, once); null = multi-use. */
|
|
192
|
-
readonly ip: string | null;
|
|
193
|
-
/** Labels for categorization and filtering (lowercase, alphanumeric with separators). Always present, empty array when none. */
|
|
194
|
-
labels: string[];
|
|
195
|
-
/** Unix timestamp (seconds) when token was created */
|
|
196
|
-
readonly created: number;
|
|
197
|
-
/** Unix timestamp (seconds) when token expires, null for never */
|
|
198
|
-
readonly expires: number | null;
|
|
199
|
-
/** Consumption timestamp. Single-use: gates re-use (set once). Multi-use: refreshed on every claim. Null = never claimed. */
|
|
200
|
-
readonly used: number | null;
|
|
201
|
-
}
|
|
202
181
|
/**
|
|
203
182
|
* Token as returned by the list endpoint.
|
|
204
|
-
*
|
|
183
|
+
* The secret is shown once at creation and never again — listings carry
|
|
184
|
+
* only the management identifier and lifecycle metadata.
|
|
205
185
|
*/
|
|
206
186
|
export interface TokenListItem {
|
|
207
187
|
/** 7-char management identifier (e.g., "a1b2c3d") */
|
|
@@ -212,7 +192,7 @@ export interface TokenListItem {
|
|
|
212
192
|
readonly created: number;
|
|
213
193
|
/** Unix timestamp (seconds) when token expires, null for never */
|
|
214
194
|
readonly expires: number | null;
|
|
215
|
-
/**
|
|
195
|
+
/** Unix timestamp (seconds) of the last request authenticated with this token, null if never used */
|
|
216
196
|
readonly used: number | null;
|
|
217
197
|
}
|
|
218
198
|
/**
|
|
@@ -249,7 +229,7 @@ export declare const AccountPlan: {
|
|
|
249
229
|
readonly TERMINATING: "terminating";
|
|
250
230
|
readonly TERMINATED: "terminated";
|
|
251
231
|
};
|
|
252
|
-
export type AccountPlanType = typeof AccountPlan[keyof typeof AccountPlan];
|
|
232
|
+
export type AccountPlanType = (typeof AccountPlan)[keyof typeof AccountPlan];
|
|
253
233
|
/**
|
|
254
234
|
* Account usage metrics — always available regardless of billing provider.
|
|
255
235
|
*/
|
|
@@ -281,6 +261,20 @@ export interface Account {
|
|
|
281
261
|
/** Grace period expiration (unix seconds), null if no grace period active */
|
|
282
262
|
readonly grace: number | null;
|
|
283
263
|
}
|
|
264
|
+
/**
|
|
265
|
+
* Account as returned by `GET /account` — the entity plus how the request
|
|
266
|
+
* was authorized, so `whoami` can answer "what credential am I holding?".
|
|
267
|
+
* Request-scoped fields live on the response type, never on the entity
|
|
268
|
+
* (the `DeploymentCreateResponse` pattern).
|
|
269
|
+
*/
|
|
270
|
+
export interface AccountGetResponse extends Account {
|
|
271
|
+
/** How the request that produced this response was authorized. */
|
|
272
|
+
readonly authMethod: AuthMethodType;
|
|
273
|
+
/** Present (and true) only when the caller is an operator acting as themselves. */
|
|
274
|
+
readonly isAdmin?: true;
|
|
275
|
+
/** Present only during read-only admin impersonation: the operator's account id. */
|
|
276
|
+
readonly impersonatedBy?: string;
|
|
277
|
+
}
|
|
284
278
|
/**
|
|
285
279
|
* Account-specific configuration overrides
|
|
286
280
|
* Allows per-account customization of limits without changing plan
|
|
@@ -330,7 +324,7 @@ export declare const ErrorType: {
|
|
|
330
324
|
/** Configuration error. Client-side only — set by SDK during config parsing/validation; never produced server-side. */
|
|
331
325
|
readonly Config: "config_error";
|
|
332
326
|
};
|
|
333
|
-
export type ErrorType = typeof ErrorType[keyof typeof ErrorType];
|
|
327
|
+
export type ErrorType = (typeof ErrorType)[keyof typeof ErrorType];
|
|
334
328
|
/**
|
|
335
329
|
* Standard error response format used everywhere
|
|
336
330
|
*/
|
|
@@ -438,15 +432,14 @@ export declare class ShipError extends Error {
|
|
|
438
432
|
*/
|
|
439
433
|
export declare function isShipError(error: unknown): error is ShipError;
|
|
440
434
|
/**
|
|
441
|
-
* Plan-based platform limits returned by the `/
|
|
435
|
+
* Plan-based platform limits returned by the `/limits` endpoint.
|
|
442
436
|
*
|
|
443
437
|
* The SDK fetches these once on first API call to drive client-side
|
|
444
438
|
* file-size / file-count / total-size validation that mirrors what the API
|
|
445
439
|
* would enforce server-side. Limits vary by account plan.
|
|
446
440
|
*
|
|
447
|
-
*
|
|
448
|
-
*
|
|
449
|
-
* caps for the current account.
|
|
441
|
+
* These are the *platform's* posted caps for the current account — server
|
|
442
|
+
* truth delivered at runtime, never hard-coded on the client.
|
|
450
443
|
*/
|
|
451
444
|
export interface PlatformLimits {
|
|
452
445
|
/** Maximum size in bytes for a single file. */
|
|
@@ -525,6 +518,25 @@ export interface PingResponse {
|
|
|
525
518
|
/** Optional timestamp */
|
|
526
519
|
timestamp?: number;
|
|
527
520
|
}
|
|
521
|
+
/**
|
|
522
|
+
* How a request (or recorded activity) was authorized.
|
|
523
|
+
*
|
|
524
|
+
* Client populations: `SESSION` (first-party cookie), `API_KEY` (`ship-`
|
|
525
|
+
* key), `TOKEN` (`deploy-` deploy token), `AGENT` (anonymous public deploy —
|
|
526
|
+
* no credential; the platform grants the public-account identity per
|
|
527
|
+
* request), `OAUTH` (delegated access token). Server populations: `WEBHOOK`
|
|
528
|
+
* (signed webhook processing), `SYSTEM` (scheduled/background jobs).
|
|
529
|
+
*/
|
|
530
|
+
export declare const AuthMethod: {
|
|
531
|
+
readonly SESSION: "session";
|
|
532
|
+
readonly API_KEY: "apiKey";
|
|
533
|
+
readonly TOKEN: "token";
|
|
534
|
+
readonly AGENT: "agent";
|
|
535
|
+
readonly OAUTH: "oauth";
|
|
536
|
+
readonly WEBHOOK: "webhook";
|
|
537
|
+
readonly SYSTEM: "system";
|
|
538
|
+
};
|
|
539
|
+
export type AuthMethodType = (typeof AuthMethod)[keyof typeof AuthMethod];
|
|
528
540
|
/**
|
|
529
541
|
* Shape constants for API keys (`ship-{64 hex chars}`).
|
|
530
542
|
* Single source of truth used by validation utilities and auth middleware.
|
|
@@ -540,26 +552,57 @@ export declare const API_KEY: {
|
|
|
540
552
|
readonly HINT_LENGTH: 4;
|
|
541
553
|
};
|
|
542
554
|
/**
|
|
543
|
-
* Shape constants for deploy tokens (`
|
|
555
|
+
* Shape constants for deploy tokens (`deploy-{64 hex chars}`).
|
|
544
556
|
* Single source of truth used by validation utilities and auth middleware.
|
|
545
557
|
*/
|
|
546
558
|
export declare const DEPLOY_TOKEN: {
|
|
547
559
|
/** Prefix that identifies a deploy token. */
|
|
548
|
-
readonly PREFIX: "
|
|
560
|
+
readonly PREFIX: "deploy-";
|
|
549
561
|
/** Number of hex characters following the prefix. */
|
|
550
562
|
readonly HEX_LENGTH: 64;
|
|
551
|
-
/** Total length of a deploy token including prefix (`PREFIX.length + HEX_LENGTH =
|
|
552
|
-
readonly TOTAL_LENGTH:
|
|
563
|
+
/** Total length of a deploy token including prefix (`PREFIX.length + HEX_LENGTH = 71`). */
|
|
564
|
+
readonly TOTAL_LENGTH: 71;
|
|
553
565
|
};
|
|
554
|
-
|
|
555
|
-
|
|
566
|
+
/**
|
|
567
|
+
* Shape constants for caller identifiers (the `X-Caller` instance-identity
|
|
568
|
+
* header — rate-limit bucketing for multi-tenant orchestrators). The API
|
|
569
|
+
* normalizes case and silently ignores malformed values (the header is
|
|
570
|
+
* unauthenticated); clients validate at the boundary via `validateCaller`,
|
|
571
|
+
* so a value the server would drop fails fast instead.
|
|
572
|
+
*/
|
|
573
|
+
export declare const CALLER: {
|
|
574
|
+
/** HTTP header name. */
|
|
575
|
+
readonly HEADER: "X-Caller";
|
|
576
|
+
/** Maximum identifier length. */
|
|
577
|
+
readonly MAX_LENGTH: 128;
|
|
578
|
+
/** Allowed characters: alphanumeric, dot, underscore, hyphen. */
|
|
579
|
+
readonly PATTERN: RegExp;
|
|
580
|
+
};
|
|
581
|
+
/**
|
|
582
|
+
* Token populations distinguishable by shape. The platform carries every
|
|
583
|
+
* client token in one wire slot (`Authorization: Bearer <value>`) and
|
|
584
|
+
* classifies by value, never by a side channel — this is the classifier.
|
|
585
|
+
*
|
|
586
|
+
* `API_KEY` and `DEPLOY_TOKEN` *are* `AuthMethod.API_KEY` and
|
|
587
|
+
* `AuthMethod.TOKEN` — the equality is structural, so a classification flows
|
|
588
|
+
* straight into an auth method and the pair can never drift. `OPAQUE` is any
|
|
589
|
+
* other value — shape says nothing about it, so only a lookup can. Today the
|
|
590
|
+
* server refuses every opaque bearer; the OAuth access-token population
|
|
591
|
+
* resolves there when the authorization server ships.
|
|
592
|
+
*/
|
|
593
|
+
export declare const TokenKind: {
|
|
556
594
|
readonly API_KEY: "apiKey";
|
|
557
|
-
readonly
|
|
558
|
-
readonly
|
|
559
|
-
readonly WEBHOOK: "webhook";
|
|
560
|
-
readonly SYSTEM: "system";
|
|
595
|
+
readonly DEPLOY_TOKEN: "token";
|
|
596
|
+
readonly OPAQUE: "opaque";
|
|
561
597
|
};
|
|
562
|
-
export type
|
|
598
|
+
export type TokenKindType = (typeof TokenKind)[keyof typeof TokenKind];
|
|
599
|
+
/**
|
|
600
|
+
* Classify a client token by shape. The single dispatch used by both sides
|
|
601
|
+
* of the wire: API auth middleware (which population is this credential?)
|
|
602
|
+
* and SDK validation (which format rules apply before sending?). Sharing it
|
|
603
|
+
* is what guarantees client and server can never disagree on dispatch.
|
|
604
|
+
*/
|
|
605
|
+
export declare function classifyToken(token: string): TokenKindType;
|
|
563
606
|
/**
|
|
564
607
|
* OAuth scope vocabulary for delegated third-party access tokens.
|
|
565
608
|
* Single source of truth used by the authorization server (advertised in
|
|
@@ -578,7 +621,7 @@ export declare const OAuthScope: {
|
|
|
578
621
|
readonly DOMAINS_READ: "domains:read";
|
|
579
622
|
readonly DOMAINS_WRITE: "domains:write";
|
|
580
623
|
};
|
|
581
|
-
export type OAuthScopeType = typeof OAuthScope[keyof typeof OAuthScope];
|
|
624
|
+
export type OAuthScopeType = (typeof OAuthScope)[keyof typeof OAuthScope];
|
|
582
625
|
export declare const DEPLOYMENT_CONFIG_FILENAME = "ship.json";
|
|
583
626
|
/** Default ship.json config for SPA routing. Single source of truth — used by both API and SDK. */
|
|
584
627
|
export declare const SPA_DEFAULT_CONFIG: {
|
|
@@ -595,6 +638,19 @@ export declare function validateApiKey(apiKey: string): void;
|
|
|
595
638
|
* Validate deploy token format
|
|
596
639
|
*/
|
|
597
640
|
export declare function validateDeployToken(deployToken: string): void;
|
|
641
|
+
/**
|
|
642
|
+
* Validate a client token of any population. Classifies by shape and applies
|
|
643
|
+
* the matching format rules: `ship-` keys and `deploy-` deploy tokens are
|
|
644
|
+
* validated strictly; opaque tokens (OAuth access tokens, future populations)
|
|
645
|
+
* only need to be non-empty — their validity is the server's to decide.
|
|
646
|
+
*/
|
|
647
|
+
export declare function validateToken(token: string): void;
|
|
648
|
+
/**
|
|
649
|
+
* Validate a caller identifier against the `CALLER` shape. The server
|
|
650
|
+
* silently ignores malformed values (the header is unauthenticated); clients
|
|
651
|
+
* call this at configuration time so the drop never silently happens.
|
|
652
|
+
*/
|
|
653
|
+
export declare function validateCaller(caller: string): void;
|
|
598
654
|
/**
|
|
599
655
|
* Validate API URL format
|
|
600
656
|
*/
|
|
@@ -656,24 +712,6 @@ export interface StaticFile {
|
|
|
656
712
|
/** The size of the file in bytes. */
|
|
657
713
|
size: number;
|
|
658
714
|
}
|
|
659
|
-
/**
|
|
660
|
-
* Resolved client configuration with `apiUrl` defaulted.
|
|
661
|
-
*
|
|
662
|
-
* Produced by the SDK after layering its credential sources (constructor
|
|
663
|
-
* options on top of `SHIP_*` env vars in Node; constructor options only in
|
|
664
|
-
* Browser) and applying the `DEFAULT_API` fallback. File-based sources
|
|
665
|
-
* (`.shiprc`, `package.json` `"ship"` key) are the CLI's responsibility and
|
|
666
|
-
* are merged in *before* construction — by the time a `ResolvedConfig`
|
|
667
|
-
* exists, every source has already collapsed into the constructor argument.
|
|
668
|
-
*/
|
|
669
|
-
export interface ResolvedConfig {
|
|
670
|
-
/** API URL — always present after resolution, defaults to `DEFAULT_API`. */
|
|
671
|
-
apiUrl: string;
|
|
672
|
-
/** API key for authenticated deployments. */
|
|
673
|
-
apiKey?: string;
|
|
674
|
-
/** Deploy token used as the request credential (`token-{64-hex}`). */
|
|
675
|
-
deployToken?: string;
|
|
676
|
-
}
|
|
677
715
|
/**
|
|
678
716
|
* Progress information for deploy/upload operations.
|
|
679
717
|
* Provides consistent percentage-based progress with byte-level details.
|
|
@@ -727,6 +765,8 @@ export interface DeploymentUploadOptions {
|
|
|
727
765
|
prerender?: boolean;
|
|
728
766
|
/** @internal Trigger server-side SPA detection. Only available via /upload endpoint. */
|
|
729
767
|
spa?: boolean;
|
|
768
|
+
/** @internal reCAPTCHA proof for the anonymous human deploy channel. Only available via /upload endpoint. */
|
|
769
|
+
captcha?: string;
|
|
730
770
|
}
|
|
731
771
|
/**
|
|
732
772
|
* Deployment resource interface - the contract all implementations must follow
|
|
@@ -766,7 +806,7 @@ export interface DomainResource {
|
|
|
766
806
|
* Account resource interface - the contract all implementations must follow
|
|
767
807
|
*/
|
|
768
808
|
export interface AccountResource {
|
|
769
|
-
get: () => Promise<
|
|
809
|
+
get: () => Promise<AccountGetResponse>;
|
|
770
810
|
}
|
|
771
811
|
/**
|
|
772
812
|
* Token resource interface - the contract all implementations must follow
|
|
@@ -884,7 +924,7 @@ export declare const FileValidationStatus: {
|
|
|
884
924
|
/** File passed validation and is ready for deployment */
|
|
885
925
|
readonly READY: "ready";
|
|
886
926
|
};
|
|
887
|
-
export type FileValidationStatusType = typeof FileValidationStatus[keyof typeof FileValidationStatus];
|
|
927
|
+
export type FileValidationStatusType = (typeof FileValidationStatus)[keyof typeof FileValidationStatus];
|
|
888
928
|
/**
|
|
889
929
|
* A validation issue with a display-ready message
|
|
890
930
|
*
|
package/dist/index.js
CHANGED
|
@@ -12,7 +12,7 @@ export const DeploymentStatus = {
|
|
|
12
12
|
PENDING: 'pending',
|
|
13
13
|
SUCCESS: 'success',
|
|
14
14
|
FAILED: 'failed',
|
|
15
|
-
DELETING: 'deleting'
|
|
15
|
+
DELETING: 'deleting',
|
|
16
16
|
};
|
|
17
17
|
// =============================================================================
|
|
18
18
|
// DOMAIN TYPES
|
|
@@ -29,7 +29,7 @@ export const DomainStatus = {
|
|
|
29
29
|
PENDING: 'pending',
|
|
30
30
|
PARTIAL: 'partial',
|
|
31
31
|
SUCCESS: 'success',
|
|
32
|
-
PAUSED: 'paused'
|
|
32
|
+
PAUSED: 'paused',
|
|
33
33
|
};
|
|
34
34
|
// =============================================================================
|
|
35
35
|
// ACCOUNT TYPES
|
|
@@ -44,7 +44,7 @@ export const AccountPlan = {
|
|
|
44
44
|
ENTERPRISE: 'enterprise',
|
|
45
45
|
SUSPENDED: 'suspended',
|
|
46
46
|
TERMINATING: 'terminating',
|
|
47
|
-
TERMINATED: 'terminated'
|
|
47
|
+
TERMINATED: 'terminated',
|
|
48
48
|
};
|
|
49
49
|
// =============================================================================
|
|
50
50
|
// ERROR SYSTEM
|
|
@@ -100,7 +100,13 @@ const CLIENT_ONLY_ERROR_TYPES = new Set([
|
|
|
100
100
|
* union so `.has(error.type)` accepts any value from the union.
|
|
101
101
|
*/
|
|
102
102
|
const ERROR_CATEGORIES = {
|
|
103
|
-
client: new Set([
|
|
103
|
+
client: new Set([
|
|
104
|
+
ErrorType.Business,
|
|
105
|
+
ErrorType.Config,
|
|
106
|
+
ErrorType.File,
|
|
107
|
+
ErrorType.Forbidden,
|
|
108
|
+
ErrorType.Validation,
|
|
109
|
+
]),
|
|
104
110
|
network: new Set([ErrorType.Network]),
|
|
105
111
|
auth: new Set([ErrorType.Authentication]),
|
|
106
112
|
};
|
|
@@ -111,7 +117,7 @@ const ERROR_CATEGORIES = {
|
|
|
111
117
|
* `CLIENT_ONLY_ERROR_TYPES` so adding a new server-producible type to
|
|
112
118
|
* `ErrorType` is automatically picked up.
|
|
113
119
|
*/
|
|
114
|
-
const SERVER_PRODUCIBLE_ERROR_TYPES = new Set(Object.values(ErrorType).filter(t => !CLIENT_ONLY_ERROR_TYPES.has(t)));
|
|
120
|
+
const SERVER_PRODUCIBLE_ERROR_TYPES = new Set(Object.values(ErrorType).filter((t) => !CLIENT_ONLY_ERROR_TYPES.has(t)));
|
|
115
121
|
/**
|
|
116
122
|
* Simple unified error class for both API and SDK
|
|
117
123
|
*/
|
|
@@ -132,14 +138,12 @@ export class ShipError extends Error {
|
|
|
132
138
|
// tag (see `ShipError.authentication` JSDoc) — these are server-side
|
|
133
139
|
// diagnostics like 'session_invalid' that must not leak to clients.
|
|
134
140
|
const authDetails = this.details;
|
|
135
|
-
const details = this.type === ErrorType.Authentication && authDetails?.internal
|
|
136
|
-
? undefined
|
|
137
|
-
: this.details;
|
|
141
|
+
const details = this.type === ErrorType.Authentication && authDetails?.internal ? undefined : this.details;
|
|
138
142
|
return {
|
|
139
143
|
error: this.type,
|
|
140
144
|
message: this.message,
|
|
141
145
|
status: this.status,
|
|
142
|
-
details
|
|
146
|
+
details,
|
|
143
147
|
};
|
|
144
148
|
}
|
|
145
149
|
/**
|
|
@@ -194,10 +198,14 @@ export class ShipError extends Error {
|
|
|
194
198
|
// Body unreadable; fall through to operationName-derived message.
|
|
195
199
|
}
|
|
196
200
|
message = message || `${operationName || 'Request'} failed with status ${response.status}`;
|
|
197
|
-
const type = bodyType ??
|
|
198
|
-
response.status ===
|
|
199
|
-
|
|
200
|
-
|
|
201
|
+
const type = bodyType ??
|
|
202
|
+
(response.status === 401
|
|
203
|
+
? ErrorType.Authentication
|
|
204
|
+
: response.status === 403
|
|
205
|
+
? ErrorType.Forbidden
|
|
206
|
+
: response.status === 429
|
|
207
|
+
? ErrorType.RateLimit
|
|
208
|
+
: ErrorType.Api);
|
|
201
209
|
return new ShipError(type, message, response.status, details);
|
|
202
210
|
}
|
|
203
211
|
/**
|
|
@@ -247,7 +255,7 @@ export class ShipError extends Error {
|
|
|
247
255
|
static forbidden(message, details) {
|
|
248
256
|
return new ShipError(ErrorType.Forbidden, message, 403, details);
|
|
249
257
|
}
|
|
250
|
-
static rateLimit(message =
|
|
258
|
+
static rateLimit(message = 'Too many requests', details) {
|
|
251
259
|
return new ShipError(ErrorType.RateLimit, message, 429, details);
|
|
252
260
|
}
|
|
253
261
|
/**
|
|
@@ -263,7 +271,7 @@ export class ShipError extends Error {
|
|
|
263
271
|
* Use this pattern in API auth code; do not put client-visible info under
|
|
264
272
|
* `internal`. Other `details` keys round-trip normally.
|
|
265
273
|
*/
|
|
266
|
-
static authentication(message =
|
|
274
|
+
static authentication(message = 'Authentication required', details) {
|
|
267
275
|
return new ShipError(ErrorType.Authentication, message, 401, details);
|
|
268
276
|
}
|
|
269
277
|
static business(message, status = 400, details) {
|
|
@@ -332,21 +340,47 @@ export function isShipError(error) {
|
|
|
332
340
|
*/
|
|
333
341
|
export const BLOCKED_EXTENSIONS = new Set([
|
|
334
342
|
// Executables
|
|
335
|
-
'exe',
|
|
343
|
+
'exe',
|
|
344
|
+
'msi',
|
|
345
|
+
'dll',
|
|
346
|
+
'scr',
|
|
347
|
+
'bat',
|
|
348
|
+
'cmd',
|
|
349
|
+
'com',
|
|
350
|
+
'pif',
|
|
351
|
+
'app',
|
|
352
|
+
'deb',
|
|
353
|
+
'rpm',
|
|
336
354
|
// Installers
|
|
337
|
-
'pkg',
|
|
355
|
+
'pkg',
|
|
356
|
+
'mpkg',
|
|
338
357
|
// Disk images
|
|
339
|
-
'dmg',
|
|
358
|
+
'dmg',
|
|
359
|
+
'iso',
|
|
360
|
+
'img',
|
|
340
361
|
// Malware vectors
|
|
341
|
-
'cab',
|
|
362
|
+
'cab',
|
|
363
|
+
'cpl',
|
|
364
|
+
'chm',
|
|
342
365
|
// Dangerous scripts
|
|
343
|
-
'ps1',
|
|
366
|
+
'ps1',
|
|
367
|
+
'vbs',
|
|
368
|
+
'vbe',
|
|
369
|
+
'ws',
|
|
370
|
+
'wsf',
|
|
371
|
+
'wsc',
|
|
372
|
+
'wsh',
|
|
373
|
+
'reg',
|
|
344
374
|
// Java
|
|
345
|
-
'jar',
|
|
375
|
+
'jar',
|
|
376
|
+
'jnlp',
|
|
346
377
|
// Mobile/browser packages
|
|
347
|
-
'apk',
|
|
378
|
+
'apk',
|
|
379
|
+
'crx',
|
|
348
380
|
// Shortcut/link
|
|
349
|
-
'lnk',
|
|
381
|
+
'lnk',
|
|
382
|
+
'inf',
|
|
383
|
+
'hta',
|
|
350
384
|
]);
|
|
351
385
|
/**
|
|
352
386
|
* Check if a filename has a blocked extension.
|
|
@@ -381,6 +415,7 @@ export function isBlockedExtension(filename) {
|
|
|
381
415
|
*
|
|
382
416
|
* Everything else is allowed — browser percent-encodes, Worker decodes, R2 matches.
|
|
383
417
|
*/
|
|
418
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: blocking control characters is this regex's purpose
|
|
384
419
|
export const UNSAFE_FILENAME_CHARS = /[\x00-\x1f\x7f#?%\\<>"]/;
|
|
385
420
|
/**
|
|
386
421
|
* Check if a filename contains unsafe characters.
|
|
@@ -415,8 +450,33 @@ export const UNBUILT_PROJECT_MARKERS = new Set([
|
|
|
415
450
|
*/
|
|
416
451
|
export function hasUnbuiltMarker(filePath) {
|
|
417
452
|
const segments = filePath.replace(/\\/g, '/').split('/').filter(Boolean);
|
|
418
|
-
return segments.some(s => UNBUILT_PROJECT_MARKERS.has(s));
|
|
453
|
+
return segments.some((s) => UNBUILT_PROJECT_MARKERS.has(s));
|
|
419
454
|
}
|
|
455
|
+
// =============================================================================
|
|
456
|
+
// CREDENTIAL SHAPES
|
|
457
|
+
// =============================================================================
|
|
458
|
+
// The one address for credential vocabulary: how a request is authorized
|
|
459
|
+
// (AuthMethod), the shapes that distinguish populations on the wire
|
|
460
|
+
// (API_KEY, DEPLOY_TOKEN, CALLER), the single dispatch over them (TokenKind,
|
|
461
|
+
// classifyToken), and the delegated-access scopes (OAuthScope).
|
|
462
|
+
/**
|
|
463
|
+
* How a request (or recorded activity) was authorized.
|
|
464
|
+
*
|
|
465
|
+
* Client populations: `SESSION` (first-party cookie), `API_KEY` (`ship-`
|
|
466
|
+
* key), `TOKEN` (`deploy-` deploy token), `AGENT` (anonymous public deploy —
|
|
467
|
+
* no credential; the platform grants the public-account identity per
|
|
468
|
+
* request), `OAUTH` (delegated access token). Server populations: `WEBHOOK`
|
|
469
|
+
* (signed webhook processing), `SYSTEM` (scheduled/background jobs).
|
|
470
|
+
*/
|
|
471
|
+
export const AuthMethod = {
|
|
472
|
+
SESSION: 'session',
|
|
473
|
+
API_KEY: 'apiKey',
|
|
474
|
+
TOKEN: 'token',
|
|
475
|
+
AGENT: 'agent',
|
|
476
|
+
OAUTH: 'oauth',
|
|
477
|
+
WEBHOOK: 'webhook',
|
|
478
|
+
SYSTEM: 'system',
|
|
479
|
+
};
|
|
420
480
|
/**
|
|
421
481
|
* Shape constants for API keys (`ship-{64 hex chars}`).
|
|
422
482
|
* Single source of truth used by validation utilities and auth middleware.
|
|
@@ -432,26 +492,62 @@ export const API_KEY = {
|
|
|
432
492
|
HINT_LENGTH: 4,
|
|
433
493
|
};
|
|
434
494
|
/**
|
|
435
|
-
* Shape constants for deploy tokens (`
|
|
495
|
+
* Shape constants for deploy tokens (`deploy-{64 hex chars}`).
|
|
436
496
|
* Single source of truth used by validation utilities and auth middleware.
|
|
437
497
|
*/
|
|
438
498
|
export const DEPLOY_TOKEN = {
|
|
439
499
|
/** Prefix that identifies a deploy token. */
|
|
440
|
-
PREFIX: '
|
|
500
|
+
PREFIX: 'deploy-',
|
|
441
501
|
/** Number of hex characters following the prefix. */
|
|
442
502
|
HEX_LENGTH: 64,
|
|
443
|
-
/** Total length of a deploy token including prefix (`PREFIX.length + HEX_LENGTH =
|
|
444
|
-
TOTAL_LENGTH:
|
|
503
|
+
/** Total length of a deploy token including prefix (`PREFIX.length + HEX_LENGTH = 71`). */
|
|
504
|
+
TOTAL_LENGTH: 71,
|
|
445
505
|
};
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
506
|
+
/**
|
|
507
|
+
* Shape constants for caller identifiers (the `X-Caller` instance-identity
|
|
508
|
+
* header — rate-limit bucketing for multi-tenant orchestrators). The API
|
|
509
|
+
* normalizes case and silently ignores malformed values (the header is
|
|
510
|
+
* unauthenticated); clients validate at the boundary via `validateCaller`,
|
|
511
|
+
* so a value the server would drop fails fast instead.
|
|
512
|
+
*/
|
|
513
|
+
export const CALLER = {
|
|
514
|
+
/** HTTP header name. */
|
|
515
|
+
HEADER: 'X-Caller',
|
|
516
|
+
/** Maximum identifier length. */
|
|
517
|
+
MAX_LENGTH: 128,
|
|
518
|
+
/** Allowed characters: alphanumeric, dot, underscore, hyphen. */
|
|
519
|
+
PATTERN: /^[a-zA-Z0-9._-]+$/,
|
|
454
520
|
};
|
|
521
|
+
/**
|
|
522
|
+
* Token populations distinguishable by shape. The platform carries every
|
|
523
|
+
* client token in one wire slot (`Authorization: Bearer <value>`) and
|
|
524
|
+
* classifies by value, never by a side channel — this is the classifier.
|
|
525
|
+
*
|
|
526
|
+
* `API_KEY` and `DEPLOY_TOKEN` *are* `AuthMethod.API_KEY` and
|
|
527
|
+
* `AuthMethod.TOKEN` — the equality is structural, so a classification flows
|
|
528
|
+
* straight into an auth method and the pair can never drift. `OPAQUE` is any
|
|
529
|
+
* other value — shape says nothing about it, so only a lookup can. Today the
|
|
530
|
+
* server refuses every opaque bearer; the OAuth access-token population
|
|
531
|
+
* resolves there when the authorization server ships.
|
|
532
|
+
*/
|
|
533
|
+
export const TokenKind = {
|
|
534
|
+
API_KEY: AuthMethod.API_KEY,
|
|
535
|
+
DEPLOY_TOKEN: AuthMethod.TOKEN,
|
|
536
|
+
OPAQUE: 'opaque',
|
|
537
|
+
};
|
|
538
|
+
/**
|
|
539
|
+
* Classify a client token by shape. The single dispatch used by both sides
|
|
540
|
+
* of the wire: API auth middleware (which population is this credential?)
|
|
541
|
+
* and SDK validation (which format rules apply before sending?). Sharing it
|
|
542
|
+
* is what guarantees client and server can never disagree on dispatch.
|
|
543
|
+
*/
|
|
544
|
+
export function classifyToken(token) {
|
|
545
|
+
if (token.startsWith(API_KEY.PREFIX))
|
|
546
|
+
return TokenKind.API_KEY;
|
|
547
|
+
if (token.startsWith(DEPLOY_TOKEN.PREFIX))
|
|
548
|
+
return TokenKind.DEPLOY_TOKEN;
|
|
549
|
+
return TokenKind.OPAQUE;
|
|
550
|
+
}
|
|
455
551
|
/**
|
|
456
552
|
* OAuth scope vocabulary for delegated third-party access tokens.
|
|
457
553
|
* Single source of truth used by the authorization server (advertised in
|
|
@@ -470,41 +566,73 @@ export const OAuthScope = {
|
|
|
470
566
|
DOMAINS_READ: 'domains:read',
|
|
471
567
|
DOMAINS_WRITE: 'domains:write',
|
|
472
568
|
};
|
|
473
|
-
//
|
|
569
|
+
// =============================================================================
|
|
570
|
+
// DEPLOYMENT CONFIGURATION CONSTANTS
|
|
571
|
+
// =============================================================================
|
|
474
572
|
export const DEPLOYMENT_CONFIG_FILENAME = 'ship.json';
|
|
475
573
|
/** Default ship.json config for SPA routing. Single source of truth — used by both API and SDK. */
|
|
476
|
-
export const SPA_DEFAULT_CONFIG = {
|
|
574
|
+
export const SPA_DEFAULT_CONFIG = {
|
|
575
|
+
rewrites: [{ source: '/(.*)', destination: '/index.html' }],
|
|
576
|
+
};
|
|
477
577
|
// =============================================================================
|
|
478
578
|
// VALIDATION UTILITIES
|
|
479
579
|
// =============================================================================
|
|
480
580
|
/**
|
|
481
|
-
*
|
|
581
|
+
* Shared rule for prefixed credentials: `{PREFIX}{HEX_LENGTH hex chars}`.
|
|
582
|
+
* The regex derives from the shape constants, so the validators can never
|
|
583
|
+
* drift from the shapes `classifyToken` dispatches on.
|
|
482
584
|
*/
|
|
483
|
-
|
|
484
|
-
if (!
|
|
485
|
-
throw ShipError.validation(
|
|
585
|
+
function validatePrefixedCredential(value, shape, label) {
|
|
586
|
+
if (!value.startsWith(shape.PREFIX)) {
|
|
587
|
+
throw ShipError.validation(`${label} must start with "${shape.PREFIX}"`);
|
|
486
588
|
}
|
|
487
|
-
if (
|
|
488
|
-
throw ShipError.validation(
|
|
589
|
+
if (value.length !== shape.TOTAL_LENGTH) {
|
|
590
|
+
throw ShipError.validation(`${label} must be ${shape.TOTAL_LENGTH} characters total (${shape.PREFIX} + ${shape.HEX_LENGTH} hex chars)`);
|
|
489
591
|
}
|
|
490
|
-
const hexPart =
|
|
491
|
-
if (
|
|
492
|
-
throw ShipError.validation(
|
|
592
|
+
const hexPart = value.slice(shape.PREFIX.length);
|
|
593
|
+
if (!new RegExp(`^[a-f0-9]{${shape.HEX_LENGTH}}$`, 'i').test(hexPart)) {
|
|
594
|
+
throw ShipError.validation(`${label} must contain ${shape.HEX_LENGTH} hexadecimal characters after "${shape.PREFIX}" prefix`);
|
|
493
595
|
}
|
|
494
596
|
}
|
|
597
|
+
/**
|
|
598
|
+
* Validate API key format
|
|
599
|
+
*/
|
|
600
|
+
export function validateApiKey(apiKey) {
|
|
601
|
+
validatePrefixedCredential(apiKey, API_KEY, 'API key');
|
|
602
|
+
}
|
|
495
603
|
/**
|
|
496
604
|
* Validate deploy token format
|
|
497
605
|
*/
|
|
498
606
|
export function validateDeployToken(deployToken) {
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
607
|
+
validatePrefixedCredential(deployToken, DEPLOY_TOKEN, 'Deploy token');
|
|
608
|
+
}
|
|
609
|
+
/**
|
|
610
|
+
* Validate a client token of any population. Classifies by shape and applies
|
|
611
|
+
* the matching format rules: `ship-` keys and `deploy-` deploy tokens are
|
|
612
|
+
* validated strictly; opaque tokens (OAuth access tokens, future populations)
|
|
613
|
+
* only need to be non-empty — their validity is the server's to decide.
|
|
614
|
+
*/
|
|
615
|
+
export function validateToken(token) {
|
|
616
|
+
switch (classifyToken(token)) {
|
|
617
|
+
case TokenKind.API_KEY:
|
|
618
|
+
validateApiKey(token);
|
|
619
|
+
return;
|
|
620
|
+
case TokenKind.DEPLOY_TOKEN:
|
|
621
|
+
validateDeployToken(token);
|
|
622
|
+
return;
|
|
623
|
+
case TokenKind.OPAQUE:
|
|
624
|
+
if (!token)
|
|
625
|
+
throw ShipError.validation('Token must be a non-empty string');
|
|
504
626
|
}
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
627
|
+
}
|
|
628
|
+
/**
|
|
629
|
+
* Validate a caller identifier against the `CALLER` shape. The server
|
|
630
|
+
* silently ignores malformed values (the header is unauthenticated); clients
|
|
631
|
+
* call this at configuration time so the drop never silently happens.
|
|
632
|
+
*/
|
|
633
|
+
export function validateCaller(caller) {
|
|
634
|
+
if (!caller || caller.length > CALLER.MAX_LENGTH || !CALLER.PATTERN.test(caller)) {
|
|
635
|
+
throw ShipError.validation(`Caller must be 1-${CALLER.MAX_LENGTH} characters: letters, digits, dots, underscores, or hyphens`);
|
|
508
636
|
}
|
|
509
637
|
}
|
|
510
638
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shipstatic/types",
|
|
3
|
-
"version": "2.1.0",
|
|
3
|
+
"version": "2.2.1-beta.0",
|
|
4
4
|
"description": "Shared types for ShipStatic platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -33,14 +33,17 @@
|
|
|
33
33
|
"node": ">=20.0.0"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
|
+
"@biomejs/biome": "2.5.5",
|
|
36
37
|
"@types/node": "^24.10.9",
|
|
37
|
-
"husky": "^9.1.7",
|
|
38
38
|
"typescript": "^5.9.3",
|
|
39
39
|
"vitest": "^2.1.8"
|
|
40
40
|
},
|
|
41
41
|
"scripts": {
|
|
42
42
|
"build": "tsc",
|
|
43
43
|
"clean": "rm -rf dist",
|
|
44
|
-
"test": "vitest"
|
|
44
|
+
"test": "vitest",
|
|
45
|
+
"lint": "biome check .",
|
|
46
|
+
"format": "biome format --write .",
|
|
47
|
+
"typecheck": "tsc --noEmit"
|
|
45
48
|
}
|
|
46
49
|
}
|
package/src/index.ts
CHANGED
|
@@ -14,10 +14,10 @@ export const DeploymentStatus = {
|
|
|
14
14
|
PENDING: 'pending',
|
|
15
15
|
SUCCESS: 'success',
|
|
16
16
|
FAILED: 'failed',
|
|
17
|
-
DELETING: 'deleting'
|
|
17
|
+
DELETING: 'deleting',
|
|
18
18
|
} as const;
|
|
19
19
|
|
|
20
|
-
export type DeploymentStatusType = typeof DeploymentStatus[keyof typeof DeploymentStatus];
|
|
20
|
+
export type DeploymentStatusType = (typeof DeploymentStatus)[keyof typeof DeploymentStatus];
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Core deployment object - used in both API responses and SDK
|
|
@@ -49,7 +49,6 @@ export interface Deployment {
|
|
|
49
49
|
readonly screenshot: string;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
|
|
53
52
|
/**
|
|
54
53
|
* Response from deployment creation. Extends Deployment with one-time fields
|
|
55
54
|
* only present on creation (not on subsequent GET requests).
|
|
@@ -87,10 +86,10 @@ export const DomainStatus = {
|
|
|
87
86
|
PENDING: 'pending',
|
|
88
87
|
PARTIAL: 'partial',
|
|
89
88
|
SUCCESS: 'success',
|
|
90
|
-
PAUSED: 'paused'
|
|
89
|
+
PAUSED: 'paused',
|
|
91
90
|
} as const;
|
|
92
91
|
|
|
93
|
-
export type DomainStatusType = typeof DomainStatus[keyof typeof DomainStatus];
|
|
92
|
+
export type DomainStatusType = (typeof DomainStatus)[keyof typeof DomainStatus];
|
|
94
93
|
|
|
95
94
|
/**
|
|
96
95
|
* Core domain object - used in both API responses and SDK
|
|
@@ -206,31 +205,10 @@ export interface DomainValidateResponse {
|
|
|
206
205
|
// TOKEN TYPES
|
|
207
206
|
// =============================================================================
|
|
208
207
|
|
|
209
|
-
/**
|
|
210
|
-
* Deployment token for automated deployments
|
|
211
|
-
*/
|
|
212
|
-
export interface Token {
|
|
213
|
-
/** 7-char management identifier */
|
|
214
|
-
readonly token: string;
|
|
215
|
-
/** The account this token belongs to */
|
|
216
|
-
readonly account: string;
|
|
217
|
-
/** SHA256 hash of the raw credential (auth lookups only, never exposed to users) */
|
|
218
|
-
readonly hash: string;
|
|
219
|
-
/** IP-lock. Non-null = single-use (claimable only from this IP, once); null = multi-use. */
|
|
220
|
-
readonly ip: string | null;
|
|
221
|
-
/** Labels for categorization and filtering (lowercase, alphanumeric with separators). Always present, empty array when none. */
|
|
222
|
-
labels: string[];
|
|
223
|
-
/** Unix timestamp (seconds) when token was created */
|
|
224
|
-
readonly created: number;
|
|
225
|
-
/** Unix timestamp (seconds) when token expires, null for never */
|
|
226
|
-
readonly expires: number | null;
|
|
227
|
-
/** Consumption timestamp. Single-use: gates re-use (set once). Multi-use: refreshed on every claim. Null = never claimed. */
|
|
228
|
-
readonly used: number | null;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
208
|
/**
|
|
232
209
|
* Token as returned by the list endpoint.
|
|
233
|
-
*
|
|
210
|
+
* The secret is shown once at creation and never again — listings carry
|
|
211
|
+
* only the management identifier and lifecycle metadata.
|
|
234
212
|
*/
|
|
235
213
|
export interface TokenListItem {
|
|
236
214
|
/** 7-char management identifier (e.g., "a1b2c3d") */
|
|
@@ -241,7 +219,7 @@ export interface TokenListItem {
|
|
|
241
219
|
readonly created: number;
|
|
242
220
|
/** Unix timestamp (seconds) when token expires, null for never */
|
|
243
221
|
readonly expires: number | null;
|
|
244
|
-
/**
|
|
222
|
+
/** Unix timestamp (seconds) of the last request authenticated with this token, null if never used */
|
|
245
223
|
readonly used: number | null;
|
|
246
224
|
}
|
|
247
225
|
|
|
@@ -283,10 +261,10 @@ export const AccountPlan = {
|
|
|
283
261
|
ENTERPRISE: 'enterprise',
|
|
284
262
|
SUSPENDED: 'suspended',
|
|
285
263
|
TERMINATING: 'terminating',
|
|
286
|
-
TERMINATED: 'terminated'
|
|
264
|
+
TERMINATED: 'terminated',
|
|
287
265
|
} as const;
|
|
288
266
|
|
|
289
|
-
export type AccountPlanType = typeof AccountPlan[keyof typeof AccountPlan];
|
|
267
|
+
export type AccountPlanType = (typeof AccountPlan)[keyof typeof AccountPlan];
|
|
290
268
|
|
|
291
269
|
/**
|
|
292
270
|
* Account usage metrics — always available regardless of billing provider.
|
|
@@ -321,6 +299,21 @@ export interface Account {
|
|
|
321
299
|
readonly grace: number | null;
|
|
322
300
|
}
|
|
323
301
|
|
|
302
|
+
/**
|
|
303
|
+
* Account as returned by `GET /account` — the entity plus how the request
|
|
304
|
+
* was authorized, so `whoami` can answer "what credential am I holding?".
|
|
305
|
+
* Request-scoped fields live on the response type, never on the entity
|
|
306
|
+
* (the `DeploymentCreateResponse` pattern).
|
|
307
|
+
*/
|
|
308
|
+
export interface AccountGetResponse extends Account {
|
|
309
|
+
/** How the request that produced this response was authorized. */
|
|
310
|
+
readonly authMethod: AuthMethodType;
|
|
311
|
+
/** Present (and true) only when the caller is an operator acting as themselves. */
|
|
312
|
+
readonly isAdmin?: true;
|
|
313
|
+
/** Present only during read-only admin impersonation: the operator's account id. */
|
|
314
|
+
readonly impersonatedBy?: string;
|
|
315
|
+
}
|
|
316
|
+
|
|
324
317
|
/**
|
|
325
318
|
* Account-specific configuration overrides
|
|
326
319
|
* Allows per-account customization of limits without changing plan
|
|
@@ -376,7 +369,7 @@ export const ErrorType = {
|
|
|
376
369
|
Config: 'config_error',
|
|
377
370
|
} as const;
|
|
378
371
|
|
|
379
|
-
export type ErrorType = typeof ErrorType[keyof typeof ErrorType];
|
|
372
|
+
export type ErrorType = (typeof ErrorType)[keyof typeof ErrorType];
|
|
380
373
|
|
|
381
374
|
/**
|
|
382
375
|
* Error types that originate exclusively on the client (HTTP clients, SDK
|
|
@@ -397,7 +390,13 @@ const CLIENT_ONLY_ERROR_TYPES = new Set<string>([
|
|
|
397
390
|
* union so `.has(error.type)` accepts any value from the union.
|
|
398
391
|
*/
|
|
399
392
|
const ERROR_CATEGORIES = {
|
|
400
|
-
client: new Set<ErrorType>([
|
|
393
|
+
client: new Set<ErrorType>([
|
|
394
|
+
ErrorType.Business,
|
|
395
|
+
ErrorType.Config,
|
|
396
|
+
ErrorType.File,
|
|
397
|
+
ErrorType.Forbidden,
|
|
398
|
+
ErrorType.Validation,
|
|
399
|
+
]),
|
|
401
400
|
network: new Set<ErrorType>([ErrorType.Network]),
|
|
402
401
|
auth: new Set<ErrorType>([ErrorType.Authentication]),
|
|
403
402
|
} as const;
|
|
@@ -410,7 +409,7 @@ const ERROR_CATEGORIES = {
|
|
|
410
409
|
* `ErrorType` is automatically picked up.
|
|
411
410
|
*/
|
|
412
411
|
const SERVER_PRODUCIBLE_ERROR_TYPES = new Set<string>(
|
|
413
|
-
Object.values(ErrorType).filter(t => !CLIENT_ONLY_ERROR_TYPES.has(t)),
|
|
412
|
+
Object.values(ErrorType).filter((t) => !CLIENT_ONLY_ERROR_TYPES.has(t)),
|
|
414
413
|
);
|
|
415
414
|
|
|
416
415
|
/**
|
|
@@ -447,15 +446,14 @@ export class ShipError extends Error {
|
|
|
447
446
|
// tag (see `ShipError.authentication` JSDoc) — these are server-side
|
|
448
447
|
// diagnostics like 'session_invalid' that must not leak to clients.
|
|
449
448
|
const authDetails = this.details as { internal?: unknown } | undefined;
|
|
450
|
-
const details =
|
|
451
|
-
? undefined
|
|
452
|
-
: this.details;
|
|
449
|
+
const details =
|
|
450
|
+
this.type === ErrorType.Authentication && authDetails?.internal ? undefined : this.details;
|
|
453
451
|
|
|
454
452
|
return {
|
|
455
453
|
error: this.type,
|
|
456
454
|
message: this.message,
|
|
457
455
|
status: this.status,
|
|
458
|
-
details
|
|
456
|
+
details,
|
|
459
457
|
};
|
|
460
458
|
}
|
|
461
459
|
|
|
@@ -481,10 +479,7 @@ export class ShipError extends Error {
|
|
|
481
479
|
* Async because it reads the response body. Returns rather than throws so
|
|
482
480
|
* callers can compose; most will `throw await ShipError.fromHttpResponse(...)`.
|
|
483
481
|
*/
|
|
484
|
-
static async fromHttpResponse(
|
|
485
|
-
response: Response,
|
|
486
|
-
operationName?: string,
|
|
487
|
-
): Promise<ShipError> {
|
|
482
|
+
static async fromHttpResponse(response: Response, operationName?: string): Promise<ShipError> {
|
|
488
483
|
let message: string | undefined;
|
|
489
484
|
let details: unknown;
|
|
490
485
|
let bodyType: ErrorType | undefined;
|
|
@@ -512,12 +507,15 @@ export class ShipError extends Error {
|
|
|
512
507
|
|
|
513
508
|
message = message || `${operationName || 'Request'} failed with status ${response.status}`;
|
|
514
509
|
|
|
515
|
-
const type =
|
|
516
|
-
|
|
517
|
-
response.status ===
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
510
|
+
const type =
|
|
511
|
+
bodyType ??
|
|
512
|
+
(response.status === 401
|
|
513
|
+
? ErrorType.Authentication
|
|
514
|
+
: response.status === 403
|
|
515
|
+
? ErrorType.Forbidden
|
|
516
|
+
: response.status === 429
|
|
517
|
+
? ErrorType.RateLimit
|
|
518
|
+
: ErrorType.Api);
|
|
521
519
|
|
|
522
520
|
return new ShipError(type, message, response.status, details);
|
|
523
521
|
}
|
|
@@ -576,7 +574,7 @@ export class ShipError extends Error {
|
|
|
576
574
|
return new ShipError(ErrorType.Forbidden, message, 403, details);
|
|
577
575
|
}
|
|
578
576
|
|
|
579
|
-
static rateLimit(message: string =
|
|
577
|
+
static rateLimit(message: string = 'Too many requests', details?: unknown): ShipError {
|
|
580
578
|
return new ShipError(ErrorType.RateLimit, message, 429, details);
|
|
581
579
|
}
|
|
582
580
|
|
|
@@ -593,7 +591,7 @@ export class ShipError extends Error {
|
|
|
593
591
|
* Use this pattern in API auth code; do not put client-visible info under
|
|
594
592
|
* `internal`. Other `details` keys round-trip normally.
|
|
595
593
|
*/
|
|
596
|
-
static authentication(message: string =
|
|
594
|
+
static authentication(message: string = 'Authentication required', details?: unknown): ShipError {
|
|
597
595
|
return new ShipError(ErrorType.Authentication, message, 401, details);
|
|
598
596
|
}
|
|
599
597
|
|
|
@@ -666,15 +664,14 @@ export function isShipError(error: unknown): error is ShipError {
|
|
|
666
664
|
// =============================================================================
|
|
667
665
|
|
|
668
666
|
/**
|
|
669
|
-
* Plan-based platform limits returned by the `/
|
|
667
|
+
* Plan-based platform limits returned by the `/limits` endpoint.
|
|
670
668
|
*
|
|
671
669
|
* The SDK fetches these once on first API call to drive client-side
|
|
672
670
|
* file-size / file-count / total-size validation that mirrors what the API
|
|
673
671
|
* would enforce server-side. Limits vary by account plan.
|
|
674
672
|
*
|
|
675
|
-
*
|
|
676
|
-
*
|
|
677
|
-
* caps for the current account.
|
|
673
|
+
* These are the *platform's* posted caps for the current account — server
|
|
674
|
+
* truth delivered at runtime, never hard-coded on the client.
|
|
678
675
|
*/
|
|
679
676
|
export interface PlatformLimits {
|
|
680
677
|
/** Maximum size in bytes for a single file. */
|
|
@@ -701,21 +698,47 @@ export interface PlatformLimits {
|
|
|
701
698
|
*/
|
|
702
699
|
export const BLOCKED_EXTENSIONS: ReadonlySet<string> = new Set([
|
|
703
700
|
// Executables
|
|
704
|
-
'exe',
|
|
701
|
+
'exe',
|
|
702
|
+
'msi',
|
|
703
|
+
'dll',
|
|
704
|
+
'scr',
|
|
705
|
+
'bat',
|
|
706
|
+
'cmd',
|
|
707
|
+
'com',
|
|
708
|
+
'pif',
|
|
709
|
+
'app',
|
|
710
|
+
'deb',
|
|
711
|
+
'rpm',
|
|
705
712
|
// Installers
|
|
706
|
-
'pkg',
|
|
713
|
+
'pkg',
|
|
714
|
+
'mpkg',
|
|
707
715
|
// Disk images
|
|
708
|
-
'dmg',
|
|
716
|
+
'dmg',
|
|
717
|
+
'iso',
|
|
718
|
+
'img',
|
|
709
719
|
// Malware vectors
|
|
710
|
-
'cab',
|
|
720
|
+
'cab',
|
|
721
|
+
'cpl',
|
|
722
|
+
'chm',
|
|
711
723
|
// Dangerous scripts
|
|
712
|
-
'ps1',
|
|
724
|
+
'ps1',
|
|
725
|
+
'vbs',
|
|
726
|
+
'vbe',
|
|
727
|
+
'ws',
|
|
728
|
+
'wsf',
|
|
729
|
+
'wsc',
|
|
730
|
+
'wsh',
|
|
731
|
+
'reg',
|
|
713
732
|
// Java
|
|
714
|
-
'jar',
|
|
733
|
+
'jar',
|
|
734
|
+
'jnlp',
|
|
715
735
|
// Mobile/browser packages
|
|
716
|
-
'apk',
|
|
736
|
+
'apk',
|
|
737
|
+
'crx',
|
|
717
738
|
// Shortcut/link
|
|
718
|
-
'lnk',
|
|
739
|
+
'lnk',
|
|
740
|
+
'inf',
|
|
741
|
+
'hta',
|
|
719
742
|
]);
|
|
720
743
|
|
|
721
744
|
/**
|
|
@@ -752,6 +775,7 @@ export function isBlockedExtension(filename: string): boolean {
|
|
|
752
775
|
*
|
|
753
776
|
* Everything else is allowed — browser percent-encodes, Worker decodes, R2 matches.
|
|
754
777
|
*/
|
|
778
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: blocking control characters is this regex's purpose
|
|
755
779
|
export const UNSAFE_FILENAME_CHARS = /[\x00-\x1f\x7f#?%\\<>"]/;
|
|
756
780
|
|
|
757
781
|
/**
|
|
@@ -790,7 +814,7 @@ export const UNBUILT_PROJECT_MARKERS: ReadonlySet<string> = new Set([
|
|
|
790
814
|
*/
|
|
791
815
|
export function hasUnbuiltMarker(filePath: string): boolean {
|
|
792
816
|
const segments = filePath.replace(/\\/g, '/').split('/').filter(Boolean);
|
|
793
|
-
return segments.some(s => UNBUILT_PROJECT_MARKERS.has(s));
|
|
817
|
+
return segments.some((s) => UNBUILT_PROJECT_MARKERS.has(s));
|
|
794
818
|
}
|
|
795
819
|
|
|
796
820
|
// =============================================================================
|
|
@@ -807,6 +831,35 @@ export interface PingResponse {
|
|
|
807
831
|
timestamp?: number;
|
|
808
832
|
}
|
|
809
833
|
|
|
834
|
+
// =============================================================================
|
|
835
|
+
// CREDENTIAL SHAPES
|
|
836
|
+
// =============================================================================
|
|
837
|
+
// The one address for credential vocabulary: how a request is authorized
|
|
838
|
+
// (AuthMethod), the shapes that distinguish populations on the wire
|
|
839
|
+
// (API_KEY, DEPLOY_TOKEN, CALLER), the single dispatch over them (TokenKind,
|
|
840
|
+
// classifyToken), and the delegated-access scopes (OAuthScope).
|
|
841
|
+
|
|
842
|
+
/**
|
|
843
|
+
* How a request (or recorded activity) was authorized.
|
|
844
|
+
*
|
|
845
|
+
* Client populations: `SESSION` (first-party cookie), `API_KEY` (`ship-`
|
|
846
|
+
* key), `TOKEN` (`deploy-` deploy token), `AGENT` (anonymous public deploy —
|
|
847
|
+
* no credential; the platform grants the public-account identity per
|
|
848
|
+
* request), `OAUTH` (delegated access token). Server populations: `WEBHOOK`
|
|
849
|
+
* (signed webhook processing), `SYSTEM` (scheduled/background jobs).
|
|
850
|
+
*/
|
|
851
|
+
export const AuthMethod = {
|
|
852
|
+
SESSION: 'session',
|
|
853
|
+
API_KEY: 'apiKey',
|
|
854
|
+
TOKEN: 'token',
|
|
855
|
+
AGENT: 'agent',
|
|
856
|
+
OAUTH: 'oauth',
|
|
857
|
+
WEBHOOK: 'webhook',
|
|
858
|
+
SYSTEM: 'system',
|
|
859
|
+
} as const;
|
|
860
|
+
|
|
861
|
+
export type AuthMethodType = (typeof AuthMethod)[keyof typeof AuthMethod];
|
|
862
|
+
|
|
810
863
|
/**
|
|
811
864
|
* Shape constants for API keys (`ship-{64 hex chars}`).
|
|
812
865
|
* Single source of truth used by validation utilities and auth middleware.
|
|
@@ -823,29 +876,65 @@ export const API_KEY = {
|
|
|
823
876
|
} as const;
|
|
824
877
|
|
|
825
878
|
/**
|
|
826
|
-
* Shape constants for deploy tokens (`
|
|
879
|
+
* Shape constants for deploy tokens (`deploy-{64 hex chars}`).
|
|
827
880
|
* Single source of truth used by validation utilities and auth middleware.
|
|
828
881
|
*/
|
|
829
882
|
export const DEPLOY_TOKEN = {
|
|
830
883
|
/** Prefix that identifies a deploy token. */
|
|
831
|
-
PREFIX: '
|
|
884
|
+
PREFIX: 'deploy-',
|
|
832
885
|
/** Number of hex characters following the prefix. */
|
|
833
886
|
HEX_LENGTH: 64,
|
|
834
|
-
/** Total length of a deploy token including prefix (`PREFIX.length + HEX_LENGTH =
|
|
835
|
-
TOTAL_LENGTH:
|
|
887
|
+
/** Total length of a deploy token including prefix (`PREFIX.length + HEX_LENGTH = 71`). */
|
|
888
|
+
TOTAL_LENGTH: 71,
|
|
836
889
|
} as const;
|
|
837
890
|
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
891
|
+
/**
|
|
892
|
+
* Shape constants for caller identifiers (the `X-Caller` instance-identity
|
|
893
|
+
* header — rate-limit bucketing for multi-tenant orchestrators). The API
|
|
894
|
+
* normalizes case and silently ignores malformed values (the header is
|
|
895
|
+
* unauthenticated); clients validate at the boundary via `validateCaller`,
|
|
896
|
+
* so a value the server would drop fails fast instead.
|
|
897
|
+
*/
|
|
898
|
+
export const CALLER = {
|
|
899
|
+
/** HTTP header name. */
|
|
900
|
+
HEADER: 'X-Caller',
|
|
901
|
+
/** Maximum identifier length. */
|
|
902
|
+
MAX_LENGTH: 128,
|
|
903
|
+
/** Allowed characters: alphanumeric, dot, underscore, hyphen. */
|
|
904
|
+
PATTERN: /^[a-zA-Z0-9._-]+$/,
|
|
846
905
|
} as const;
|
|
847
906
|
|
|
848
|
-
|
|
907
|
+
/**
|
|
908
|
+
* Token populations distinguishable by shape. The platform carries every
|
|
909
|
+
* client token in one wire slot (`Authorization: Bearer <value>`) and
|
|
910
|
+
* classifies by value, never by a side channel — this is the classifier.
|
|
911
|
+
*
|
|
912
|
+
* `API_KEY` and `DEPLOY_TOKEN` *are* `AuthMethod.API_KEY` and
|
|
913
|
+
* `AuthMethod.TOKEN` — the equality is structural, so a classification flows
|
|
914
|
+
* straight into an auth method and the pair can never drift. `OPAQUE` is any
|
|
915
|
+
* other value — shape says nothing about it, so only a lookup can. Today the
|
|
916
|
+
* server refuses every opaque bearer; the OAuth access-token population
|
|
917
|
+
* resolves there when the authorization server ships.
|
|
918
|
+
*/
|
|
919
|
+
export const TokenKind = {
|
|
920
|
+
API_KEY: AuthMethod.API_KEY,
|
|
921
|
+
DEPLOY_TOKEN: AuthMethod.TOKEN,
|
|
922
|
+
OPAQUE: 'opaque',
|
|
923
|
+
} as const;
|
|
924
|
+
|
|
925
|
+
export type TokenKindType = (typeof TokenKind)[keyof typeof TokenKind];
|
|
926
|
+
|
|
927
|
+
/**
|
|
928
|
+
* Classify a client token by shape. The single dispatch used by both sides
|
|
929
|
+
* of the wire: API auth middleware (which population is this credential?)
|
|
930
|
+
* and SDK validation (which format rules apply before sending?). Sharing it
|
|
931
|
+
* is what guarantees client and server can never disagree on dispatch.
|
|
932
|
+
*/
|
|
933
|
+
export function classifyToken(token: string): TokenKindType {
|
|
934
|
+
if (token.startsWith(API_KEY.PREFIX)) return TokenKind.API_KEY;
|
|
935
|
+
if (token.startsWith(DEPLOY_TOKEN.PREFIX)) return TokenKind.DEPLOY_TOKEN;
|
|
936
|
+
return TokenKind.OPAQUE;
|
|
937
|
+
}
|
|
849
938
|
|
|
850
939
|
/**
|
|
851
940
|
* OAuth scope vocabulary for delegated third-party access tokens.
|
|
@@ -866,51 +955,94 @@ export const OAuthScope = {
|
|
|
866
955
|
DOMAINS_WRITE: 'domains:write',
|
|
867
956
|
} as const;
|
|
868
957
|
|
|
869
|
-
export type OAuthScopeType = typeof OAuthScope[keyof typeof OAuthScope];
|
|
958
|
+
export type OAuthScopeType = (typeof OAuthScope)[keyof typeof OAuthScope];
|
|
959
|
+
|
|
960
|
+
// =============================================================================
|
|
961
|
+
// DEPLOYMENT CONFIGURATION CONSTANTS
|
|
962
|
+
// =============================================================================
|
|
870
963
|
|
|
871
|
-
// Deployment Configuration
|
|
872
964
|
export const DEPLOYMENT_CONFIG_FILENAME = 'ship.json';
|
|
873
965
|
|
|
874
966
|
/** Default ship.json config for SPA routing. Single source of truth — used by both API and SDK. */
|
|
875
|
-
export const SPA_DEFAULT_CONFIG = {
|
|
967
|
+
export const SPA_DEFAULT_CONFIG = {
|
|
968
|
+
rewrites: [{ source: '/(.*)', destination: '/index.html' }],
|
|
969
|
+
} as const;
|
|
876
970
|
|
|
877
971
|
// =============================================================================
|
|
878
972
|
// VALIDATION UTILITIES
|
|
879
973
|
// =============================================================================
|
|
880
974
|
|
|
881
975
|
/**
|
|
882
|
-
*
|
|
976
|
+
* Shared rule for prefixed credentials: `{PREFIX}{HEX_LENGTH hex chars}`.
|
|
977
|
+
* The regex derives from the shape constants, so the validators can never
|
|
978
|
+
* drift from the shapes `classifyToken` dispatches on.
|
|
883
979
|
*/
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
980
|
+
function validatePrefixedCredential(
|
|
981
|
+
value: string,
|
|
982
|
+
shape: { PREFIX: string; HEX_LENGTH: number; TOTAL_LENGTH: number },
|
|
983
|
+
label: string,
|
|
984
|
+
): void {
|
|
985
|
+
if (!value.startsWith(shape.PREFIX)) {
|
|
986
|
+
throw ShipError.validation(`${label} must start with "${shape.PREFIX}"`);
|
|
887
987
|
}
|
|
888
988
|
|
|
889
|
-
if (
|
|
890
|
-
throw ShipError.validation(
|
|
989
|
+
if (value.length !== shape.TOTAL_LENGTH) {
|
|
990
|
+
throw ShipError.validation(
|
|
991
|
+
`${label} must be ${shape.TOTAL_LENGTH} characters total (${shape.PREFIX} + ${shape.HEX_LENGTH} hex chars)`,
|
|
992
|
+
);
|
|
891
993
|
}
|
|
892
994
|
|
|
893
|
-
const hexPart =
|
|
894
|
-
if (
|
|
895
|
-
throw ShipError.validation(
|
|
995
|
+
const hexPart = value.slice(shape.PREFIX.length);
|
|
996
|
+
if (!new RegExp(`^[a-f0-9]{${shape.HEX_LENGTH}}$`, 'i').test(hexPart)) {
|
|
997
|
+
throw ShipError.validation(
|
|
998
|
+
`${label} must contain ${shape.HEX_LENGTH} hexadecimal characters after "${shape.PREFIX}" prefix`,
|
|
999
|
+
);
|
|
896
1000
|
}
|
|
897
1001
|
}
|
|
898
1002
|
|
|
1003
|
+
/**
|
|
1004
|
+
* Validate API key format
|
|
1005
|
+
*/
|
|
1006
|
+
export function validateApiKey(apiKey: string): void {
|
|
1007
|
+
validatePrefixedCredential(apiKey, API_KEY, 'API key');
|
|
1008
|
+
}
|
|
1009
|
+
|
|
899
1010
|
/**
|
|
900
1011
|
* Validate deploy token format
|
|
901
1012
|
*/
|
|
902
1013
|
export function validateDeployToken(deployToken: string): void {
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
}
|
|
1014
|
+
validatePrefixedCredential(deployToken, DEPLOY_TOKEN, 'Deploy token');
|
|
1015
|
+
}
|
|
906
1016
|
|
|
907
|
-
|
|
908
|
-
|
|
1017
|
+
/**
|
|
1018
|
+
* Validate a client token of any population. Classifies by shape and applies
|
|
1019
|
+
* the matching format rules: `ship-` keys and `deploy-` deploy tokens are
|
|
1020
|
+
* validated strictly; opaque tokens (OAuth access tokens, future populations)
|
|
1021
|
+
* only need to be non-empty — their validity is the server's to decide.
|
|
1022
|
+
*/
|
|
1023
|
+
export function validateToken(token: string): void {
|
|
1024
|
+
switch (classifyToken(token)) {
|
|
1025
|
+
case TokenKind.API_KEY:
|
|
1026
|
+
validateApiKey(token);
|
|
1027
|
+
return;
|
|
1028
|
+
case TokenKind.DEPLOY_TOKEN:
|
|
1029
|
+
validateDeployToken(token);
|
|
1030
|
+
return;
|
|
1031
|
+
case TokenKind.OPAQUE:
|
|
1032
|
+
if (!token) throw ShipError.validation('Token must be a non-empty string');
|
|
909
1033
|
}
|
|
1034
|
+
}
|
|
910
1035
|
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
1036
|
+
/**
|
|
1037
|
+
* Validate a caller identifier against the `CALLER` shape. The server
|
|
1038
|
+
* silently ignores malformed values (the header is unauthenticated); clients
|
|
1039
|
+
* call this at configuration time so the drop never silently happens.
|
|
1040
|
+
*/
|
|
1041
|
+
export function validateCaller(caller: string): void {
|
|
1042
|
+
if (!caller || caller.length > CALLER.MAX_LENGTH || !CALLER.PATTERN.test(caller)) {
|
|
1043
|
+
throw ShipError.validation(
|
|
1044
|
+
`Caller must be 1-${CALLER.MAX_LENGTH} characters: letters, digits, dots, underscores, or hyphens`,
|
|
1045
|
+
);
|
|
914
1046
|
}
|
|
915
1047
|
}
|
|
916
1048
|
|
|
@@ -1011,29 +1143,6 @@ export interface StaticFile {
|
|
|
1011
1143
|
size: number;
|
|
1012
1144
|
}
|
|
1013
1145
|
|
|
1014
|
-
// =============================================================================
|
|
1015
|
-
// PLATFORM CONFIGURATION
|
|
1016
|
-
// =============================================================================
|
|
1017
|
-
|
|
1018
|
-
/**
|
|
1019
|
-
* Resolved client configuration with `apiUrl` defaulted.
|
|
1020
|
-
*
|
|
1021
|
-
* Produced by the SDK after layering its credential sources (constructor
|
|
1022
|
-
* options on top of `SHIP_*` env vars in Node; constructor options only in
|
|
1023
|
-
* Browser) and applying the `DEFAULT_API` fallback. File-based sources
|
|
1024
|
-
* (`.shiprc`, `package.json` `"ship"` key) are the CLI's responsibility and
|
|
1025
|
-
* are merged in *before* construction — by the time a `ResolvedConfig`
|
|
1026
|
-
* exists, every source has already collapsed into the constructor argument.
|
|
1027
|
-
*/
|
|
1028
|
-
export interface ResolvedConfig {
|
|
1029
|
-
/** API URL — always present after resolution, defaults to `DEFAULT_API`. */
|
|
1030
|
-
apiUrl: string;
|
|
1031
|
-
/** API key for authenticated deployments. */
|
|
1032
|
-
apiKey?: string;
|
|
1033
|
-
/** Deploy token used as the request credential (`token-{64-hex}`). */
|
|
1034
|
-
deployToken?: string;
|
|
1035
|
-
}
|
|
1036
|
-
|
|
1037
1146
|
// =============================================================================
|
|
1038
1147
|
// PROGRESS TRACKING
|
|
1039
1148
|
// =============================================================================
|
|
@@ -1102,13 +1211,18 @@ export interface DeploymentUploadOptions {
|
|
|
1102
1211
|
prerender?: boolean;
|
|
1103
1212
|
/** @internal Trigger server-side SPA detection. Only available via /upload endpoint. */
|
|
1104
1213
|
spa?: boolean;
|
|
1214
|
+
/** @internal reCAPTCHA proof for the anonymous human deploy channel. Only available via /upload endpoint. */
|
|
1215
|
+
captcha?: string;
|
|
1105
1216
|
}
|
|
1106
1217
|
|
|
1107
1218
|
/**
|
|
1108
1219
|
* Deployment resource interface - the contract all implementations must follow
|
|
1109
1220
|
*/
|
|
1110
1221
|
export interface DeploymentResource {
|
|
1111
|
-
upload: (
|
|
1222
|
+
upload: (
|
|
1223
|
+
input: DeployInput,
|
|
1224
|
+
options?: DeploymentUploadOptions,
|
|
1225
|
+
) => Promise<DeploymentCreateResponse>;
|
|
1112
1226
|
list: () => Promise<DeploymentListResponse>;
|
|
1113
1227
|
get: (id: string) => Promise<Deployment>;
|
|
1114
1228
|
set: (id: string, options: { labels: string[] }) => Promise<Deployment>;
|
|
@@ -1119,7 +1233,10 @@ export interface DeploymentResource {
|
|
|
1119
1233
|
* Domain resource interface - the contract all implementations must follow
|
|
1120
1234
|
*/
|
|
1121
1235
|
export interface DomainResource {
|
|
1122
|
-
set: (
|
|
1236
|
+
set: (
|
|
1237
|
+
name: string,
|
|
1238
|
+
options?: { deployment?: string; labels?: string[] },
|
|
1239
|
+
) => Promise<DomainSetResult>;
|
|
1123
1240
|
list: () => Promise<DomainListResponse>;
|
|
1124
1241
|
get: (name: string) => Promise<Domain>;
|
|
1125
1242
|
remove: (name: string) => Promise<void>;
|
|
@@ -1134,7 +1251,7 @@ export interface DomainResource {
|
|
|
1134
1251
|
* Account resource interface - the contract all implementations must follow
|
|
1135
1252
|
*/
|
|
1136
1253
|
export interface AccountResource {
|
|
1137
|
-
get: () => Promise<
|
|
1254
|
+
get: () => Promise<AccountGetResponse>;
|
|
1138
1255
|
}
|
|
1139
1256
|
|
|
1140
1257
|
/**
|
|
@@ -1169,7 +1286,6 @@ export interface BillingStatus {
|
|
|
1169
1286
|
portal: string | null;
|
|
1170
1287
|
}
|
|
1171
1288
|
|
|
1172
|
-
|
|
1173
1289
|
/**
|
|
1174
1290
|
* Checkout session response from POST /billing/checkout
|
|
1175
1291
|
*/
|
|
@@ -1233,9 +1349,9 @@ export type ActivityEvent =
|
|
|
1233
1349
|
| 'refund.created'
|
|
1234
1350
|
| 'dispute.created'
|
|
1235
1351
|
// Billing operational events (admin/debug only, not user-visible)
|
|
1236
|
-
| 'billing.sync'
|
|
1237
|
-
| 'billing.stale'
|
|
1238
|
-
| 'billing.race';
|
|
1352
|
+
| 'billing.sync' // Outbound: unit count pushed to payment provider
|
|
1353
|
+
| 'billing.stale' // Dropped: webhook predates last known state
|
|
1354
|
+
| 'billing.race'; // Dropped: concurrent webhook already updated state
|
|
1239
1355
|
|
|
1240
1356
|
/**
|
|
1241
1357
|
* Activity events visible to users in the dashboard
|
|
@@ -1344,8 +1460,8 @@ export const FileValidationStatus = {
|
|
|
1344
1460
|
READY: 'ready',
|
|
1345
1461
|
} as const;
|
|
1346
1462
|
|
|
1347
|
-
export type FileValidationStatusType =
|
|
1348
|
-
|
|
1463
|
+
export type FileValidationStatusType =
|
|
1464
|
+
(typeof FileValidationStatus)[keyof typeof FileValidationStatus];
|
|
1349
1465
|
|
|
1350
1466
|
/**
|
|
1351
1467
|
* A validation issue with a display-ready message
|
|
@@ -1588,4 +1704,4 @@ export function validatePassword(value: unknown): string | undefined {
|
|
|
1588
1704
|
);
|
|
1589
1705
|
}
|
|
1590
1706
|
return trimmed;
|
|
1591
|
-
}
|
|
1707
|
+
}
|