@shipstatic/types 2.2.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 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
  */
@@ -229,7 +229,7 @@ export declare const AccountPlan: {
229
229
  readonly TERMINATING: "terminating";
230
230
  readonly TERMINATED: "terminated";
231
231
  };
232
- export type AccountPlanType = typeof AccountPlan[keyof typeof AccountPlan];
232
+ export type AccountPlanType = (typeof AccountPlan)[keyof typeof AccountPlan];
233
233
  /**
234
234
  * Account usage metrics — always available regardless of billing provider.
235
235
  */
@@ -324,7 +324,7 @@ export declare const ErrorType: {
324
324
  /** Configuration error. Client-side only — set by SDK during config parsing/validation; never produced server-side. */
325
325
  readonly Config: "config_error";
326
326
  };
327
- export type ErrorType = typeof ErrorType[keyof typeof ErrorType];
327
+ export type ErrorType = (typeof ErrorType)[keyof typeof ErrorType];
328
328
  /**
329
329
  * Standard error response format used everywhere
330
330
  */
@@ -536,7 +536,7 @@ export declare const AuthMethod: {
536
536
  readonly WEBHOOK: "webhook";
537
537
  readonly SYSTEM: "system";
538
538
  };
539
- export type AuthMethodType = typeof AuthMethod[keyof typeof AuthMethod];
539
+ export type AuthMethodType = (typeof AuthMethod)[keyof typeof AuthMethod];
540
540
  /**
541
541
  * Shape constants for API keys (`ship-{64 hex chars}`).
542
542
  * Single source of truth used by validation utilities and auth middleware.
@@ -595,7 +595,7 @@ export declare const TokenKind: {
595
595
  readonly DEPLOY_TOKEN: "token";
596
596
  readonly OPAQUE: "opaque";
597
597
  };
598
- export type TokenKindType = typeof TokenKind[keyof typeof TokenKind];
598
+ export type TokenKindType = (typeof TokenKind)[keyof typeof TokenKind];
599
599
  /**
600
600
  * Classify a client token by shape. The single dispatch used by both sides
601
601
  * of the wire: API auth middleware (which population is this credential?)
@@ -621,7 +621,7 @@ export declare const OAuthScope: {
621
621
  readonly DOMAINS_READ: "domains:read";
622
622
  readonly DOMAINS_WRITE: "domains:write";
623
623
  };
624
- export type OAuthScopeType = typeof OAuthScope[keyof typeof OAuthScope];
624
+ export type OAuthScopeType = (typeof OAuthScope)[keyof typeof OAuthScope];
625
625
  export declare const DEPLOYMENT_CONFIG_FILENAME = "ship.json";
626
626
  /** Default ship.json config for SPA routing. Single source of truth — used by both API and SDK. */
627
627
  export declare const SPA_DEFAULT_CONFIG: {
@@ -924,7 +924,7 @@ export declare const FileValidationStatus: {
924
924
  /** File passed validation and is ready for deployment */
925
925
  readonly READY: "ready";
926
926
  };
927
- export type FileValidationStatusType = typeof FileValidationStatus[keyof typeof FileValidationStatus];
927
+ export type FileValidationStatusType = (typeof FileValidationStatus)[keyof typeof FileValidationStatus];
928
928
  /**
929
929
  * A validation issue with a display-ready message
930
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([ErrorType.Business, ErrorType.Config, ErrorType.File, ErrorType.Forbidden, ErrorType.Validation]),
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 ?? (response.status === 401 ? ErrorType.Authentication :
198
- response.status === 403 ? ErrorType.Forbidden :
199
- response.status === 429 ? ErrorType.RateLimit :
200
- ErrorType.Api);
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 = "Too many requests", details) {
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 = "Authentication required", details) {
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', 'msi', 'dll', 'scr', 'bat', 'cmd', 'com', 'pif', 'app', 'deb', 'rpm',
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', 'mpkg',
355
+ 'pkg',
356
+ 'mpkg',
338
357
  // Disk images
339
- 'dmg', 'iso', 'img',
358
+ 'dmg',
359
+ 'iso',
360
+ 'img',
340
361
  // Malware vectors
341
- 'cab', 'cpl', 'chm',
362
+ 'cab',
363
+ 'cpl',
364
+ 'chm',
342
365
  // Dangerous scripts
343
- 'ps1', 'vbs', 'vbe', 'ws', 'wsf', 'wsc', 'wsh', 'reg',
366
+ 'ps1',
367
+ 'vbs',
368
+ 'vbe',
369
+ 'ws',
370
+ 'wsf',
371
+ 'wsc',
372
+ 'wsh',
373
+ 'reg',
344
374
  // Java
345
- 'jar', 'jnlp',
375
+ 'jar',
376
+ 'jnlp',
346
377
  // Mobile/browser packages
347
- 'apk', 'crx',
378
+ 'apk',
379
+ 'crx',
348
380
  // Shortcut/link
349
- 'lnk', 'inf', 'hta',
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,7 +450,7 @@ 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
  }
420
455
  // =============================================================================
421
456
  // CREDENTIAL SHAPES
@@ -440,7 +475,7 @@ export const AuthMethod = {
440
475
  AGENT: 'agent',
441
476
  OAUTH: 'oauth',
442
477
  WEBHOOK: 'webhook',
443
- SYSTEM: 'system'
478
+ SYSTEM: 'system',
444
479
  };
445
480
  /**
446
481
  * Shape constants for API keys (`ship-{64 hex chars}`).
@@ -536,7 +571,9 @@ export const OAuthScope = {
536
571
  // =============================================================================
537
572
  export const DEPLOYMENT_CONFIG_FILENAME = 'ship.json';
538
573
  /** Default ship.json config for SPA routing. Single source of truth — used by both API and SDK. */
539
- export const SPA_DEFAULT_CONFIG = { rewrites: [{ source: '/(.*)', destination: '/index.html' }] };
574
+ export const SPA_DEFAULT_CONFIG = {
575
+ rewrites: [{ source: '/(.*)', destination: '/index.html' }],
576
+ };
540
577
  // =============================================================================
541
578
  // VALIDATION UTILITIES
542
579
  // =============================================================================
@@ -578,9 +615,11 @@ export function validateDeployToken(deployToken) {
578
615
  export function validateToken(token) {
579
616
  switch (classifyToken(token)) {
580
617
  case TokenKind.API_KEY:
581
- return validateApiKey(token);
618
+ validateApiKey(token);
619
+ return;
582
620
  case TokenKind.DEPLOY_TOKEN:
583
- return validateDeployToken(token);
621
+ validateDeployToken(token);
622
+ return;
584
623
  case TokenKind.OPAQUE:
585
624
  if (!token)
586
625
  throw ShipError.validation('Token must be a non-empty string');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shipstatic/types",
3
- "version": "2.2.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
@@ -262,10 +261,10 @@ export const AccountPlan = {
262
261
  ENTERPRISE: 'enterprise',
263
262
  SUSPENDED: 'suspended',
264
263
  TERMINATING: 'terminating',
265
- TERMINATED: 'terminated'
264
+ TERMINATED: 'terminated',
266
265
  } as const;
267
266
 
268
- export type AccountPlanType = typeof AccountPlan[keyof typeof AccountPlan];
267
+ export type AccountPlanType = (typeof AccountPlan)[keyof typeof AccountPlan];
269
268
 
270
269
  /**
271
270
  * Account usage metrics — always available regardless of billing provider.
@@ -370,7 +369,7 @@ export const ErrorType = {
370
369
  Config: 'config_error',
371
370
  } as const;
372
371
 
373
- export type ErrorType = typeof ErrorType[keyof typeof ErrorType];
372
+ export type ErrorType = (typeof ErrorType)[keyof typeof ErrorType];
374
373
 
375
374
  /**
376
375
  * Error types that originate exclusively on the client (HTTP clients, SDK
@@ -391,7 +390,13 @@ const CLIENT_ONLY_ERROR_TYPES = new Set<string>([
391
390
  * union so `.has(error.type)` accepts any value from the union.
392
391
  */
393
392
  const ERROR_CATEGORIES = {
394
- client: new Set<ErrorType>([ErrorType.Business, ErrorType.Config, ErrorType.File, ErrorType.Forbidden, ErrorType.Validation]),
393
+ client: new Set<ErrorType>([
394
+ ErrorType.Business,
395
+ ErrorType.Config,
396
+ ErrorType.File,
397
+ ErrorType.Forbidden,
398
+ ErrorType.Validation,
399
+ ]),
395
400
  network: new Set<ErrorType>([ErrorType.Network]),
396
401
  auth: new Set<ErrorType>([ErrorType.Authentication]),
397
402
  } as const;
@@ -404,7 +409,7 @@ const ERROR_CATEGORIES = {
404
409
  * `ErrorType` is automatically picked up.
405
410
  */
406
411
  const SERVER_PRODUCIBLE_ERROR_TYPES = new Set<string>(
407
- Object.values(ErrorType).filter(t => !CLIENT_ONLY_ERROR_TYPES.has(t)),
412
+ Object.values(ErrorType).filter((t) => !CLIENT_ONLY_ERROR_TYPES.has(t)),
408
413
  );
409
414
 
410
415
  /**
@@ -441,15 +446,14 @@ export class ShipError extends Error {
441
446
  // tag (see `ShipError.authentication` JSDoc) — these are server-side
442
447
  // diagnostics like 'session_invalid' that must not leak to clients.
443
448
  const authDetails = this.details as { internal?: unknown } | undefined;
444
- const details = this.type === ErrorType.Authentication && authDetails?.internal
445
- ? undefined
446
- : this.details;
449
+ const details =
450
+ this.type === ErrorType.Authentication && authDetails?.internal ? undefined : this.details;
447
451
 
448
452
  return {
449
453
  error: this.type,
450
454
  message: this.message,
451
455
  status: this.status,
452
- details
456
+ details,
453
457
  };
454
458
  }
455
459
 
@@ -475,10 +479,7 @@ export class ShipError extends Error {
475
479
  * Async because it reads the response body. Returns rather than throws so
476
480
  * callers can compose; most will `throw await ShipError.fromHttpResponse(...)`.
477
481
  */
478
- static async fromHttpResponse(
479
- response: Response,
480
- operationName?: string,
481
- ): Promise<ShipError> {
482
+ static async fromHttpResponse(response: Response, operationName?: string): Promise<ShipError> {
482
483
  let message: string | undefined;
483
484
  let details: unknown;
484
485
  let bodyType: ErrorType | undefined;
@@ -506,12 +507,15 @@ export class ShipError extends Error {
506
507
 
507
508
  message = message || `${operationName || 'Request'} failed with status ${response.status}`;
508
509
 
509
- const type = bodyType ?? (
510
- response.status === 401 ? ErrorType.Authentication :
511
- response.status === 403 ? ErrorType.Forbidden :
512
- response.status === 429 ? ErrorType.RateLimit :
513
- ErrorType.Api
514
- );
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);
515
519
 
516
520
  return new ShipError(type, message, response.status, details);
517
521
  }
@@ -570,7 +574,7 @@ export class ShipError extends Error {
570
574
  return new ShipError(ErrorType.Forbidden, message, 403, details);
571
575
  }
572
576
 
573
- static rateLimit(message: string = "Too many requests", details?: unknown): ShipError {
577
+ static rateLimit(message: string = 'Too many requests', details?: unknown): ShipError {
574
578
  return new ShipError(ErrorType.RateLimit, message, 429, details);
575
579
  }
576
580
 
@@ -587,7 +591,7 @@ export class ShipError extends Error {
587
591
  * Use this pattern in API auth code; do not put client-visible info under
588
592
  * `internal`. Other `details` keys round-trip normally.
589
593
  */
590
- static authentication(message: string = "Authentication required", details?: unknown): ShipError {
594
+ static authentication(message: string = 'Authentication required', details?: unknown): ShipError {
591
595
  return new ShipError(ErrorType.Authentication, message, 401, details);
592
596
  }
593
597
 
@@ -694,21 +698,47 @@ export interface PlatformLimits {
694
698
  */
695
699
  export const BLOCKED_EXTENSIONS: ReadonlySet<string> = new Set([
696
700
  // Executables
697
- 'exe', 'msi', 'dll', 'scr', 'bat', 'cmd', 'com', 'pif', 'app', 'deb', 'rpm',
701
+ 'exe',
702
+ 'msi',
703
+ 'dll',
704
+ 'scr',
705
+ 'bat',
706
+ 'cmd',
707
+ 'com',
708
+ 'pif',
709
+ 'app',
710
+ 'deb',
711
+ 'rpm',
698
712
  // Installers
699
- 'pkg', 'mpkg',
713
+ 'pkg',
714
+ 'mpkg',
700
715
  // Disk images
701
- 'dmg', 'iso', 'img',
716
+ 'dmg',
717
+ 'iso',
718
+ 'img',
702
719
  // Malware vectors
703
- 'cab', 'cpl', 'chm',
720
+ 'cab',
721
+ 'cpl',
722
+ 'chm',
704
723
  // Dangerous scripts
705
- 'ps1', 'vbs', 'vbe', 'ws', 'wsf', 'wsc', 'wsh', 'reg',
724
+ 'ps1',
725
+ 'vbs',
726
+ 'vbe',
727
+ 'ws',
728
+ 'wsf',
729
+ 'wsc',
730
+ 'wsh',
731
+ 'reg',
706
732
  // Java
707
- 'jar', 'jnlp',
733
+ 'jar',
734
+ 'jnlp',
708
735
  // Mobile/browser packages
709
- 'apk', 'crx',
736
+ 'apk',
737
+ 'crx',
710
738
  // Shortcut/link
711
- 'lnk', 'inf', 'hta',
739
+ 'lnk',
740
+ 'inf',
741
+ 'hta',
712
742
  ]);
713
743
 
714
744
  /**
@@ -745,6 +775,7 @@ export function isBlockedExtension(filename: string): boolean {
745
775
  *
746
776
  * Everything else is allowed — browser percent-encodes, Worker decodes, R2 matches.
747
777
  */
778
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: blocking control characters is this regex's purpose
748
779
  export const UNSAFE_FILENAME_CHARS = /[\x00-\x1f\x7f#?%\\<>"]/;
749
780
 
750
781
  /**
@@ -783,7 +814,7 @@ export const UNBUILT_PROJECT_MARKERS: ReadonlySet<string> = new Set([
783
814
  */
784
815
  export function hasUnbuiltMarker(filePath: string): boolean {
785
816
  const segments = filePath.replace(/\\/g, '/').split('/').filter(Boolean);
786
- return segments.some(s => UNBUILT_PROJECT_MARKERS.has(s));
817
+ return segments.some((s) => UNBUILT_PROJECT_MARKERS.has(s));
787
818
  }
788
819
 
789
820
  // =============================================================================
@@ -824,10 +855,10 @@ export const AuthMethod = {
824
855
  AGENT: 'agent',
825
856
  OAUTH: 'oauth',
826
857
  WEBHOOK: 'webhook',
827
- SYSTEM: 'system'
858
+ SYSTEM: 'system',
828
859
  } as const;
829
860
 
830
- export type AuthMethodType = typeof AuthMethod[keyof typeof AuthMethod];
861
+ export type AuthMethodType = (typeof AuthMethod)[keyof typeof AuthMethod];
831
862
 
832
863
  /**
833
864
  * Shape constants for API keys (`ship-{64 hex chars}`).
@@ -891,7 +922,7 @@ export const TokenKind = {
891
922
  OPAQUE: 'opaque',
892
923
  } as const;
893
924
 
894
- export type TokenKindType = typeof TokenKind[keyof typeof TokenKind];
925
+ export type TokenKindType = (typeof TokenKind)[keyof typeof TokenKind];
895
926
 
896
927
  /**
897
928
  * Classify a client token by shape. The single dispatch used by both sides
@@ -924,7 +955,7 @@ export const OAuthScope = {
924
955
  DOMAINS_WRITE: 'domains:write',
925
956
  } as const;
926
957
 
927
- export type OAuthScopeType = typeof OAuthScope[keyof typeof OAuthScope];
958
+ export type OAuthScopeType = (typeof OAuthScope)[keyof typeof OAuthScope];
928
959
 
929
960
  // =============================================================================
930
961
  // DEPLOYMENT CONFIGURATION CONSTANTS
@@ -933,7 +964,9 @@ export type OAuthScopeType = typeof OAuthScope[keyof typeof OAuthScope];
933
964
  export const DEPLOYMENT_CONFIG_FILENAME = 'ship.json';
934
965
 
935
966
  /** Default ship.json config for SPA routing. Single source of truth — used by both API and SDK. */
936
- export const SPA_DEFAULT_CONFIG = { rewrites: [{ source: '/(.*)', destination: '/index.html' }] } as const;
967
+ export const SPA_DEFAULT_CONFIG = {
968
+ rewrites: [{ source: '/(.*)', destination: '/index.html' }],
969
+ } as const;
937
970
 
938
971
  // =============================================================================
939
972
  // VALIDATION UTILITIES
@@ -947,19 +980,23 @@ export const SPA_DEFAULT_CONFIG = { rewrites: [{ source: '/(.*)', destination: '
947
980
  function validatePrefixedCredential(
948
981
  value: string,
949
982
  shape: { PREFIX: string; HEX_LENGTH: number; TOTAL_LENGTH: number },
950
- label: string
983
+ label: string,
951
984
  ): void {
952
985
  if (!value.startsWith(shape.PREFIX)) {
953
986
  throw ShipError.validation(`${label} must start with "${shape.PREFIX}"`);
954
987
  }
955
988
 
956
989
  if (value.length !== shape.TOTAL_LENGTH) {
957
- throw ShipError.validation(`${label} must be ${shape.TOTAL_LENGTH} characters total (${shape.PREFIX} + ${shape.HEX_LENGTH} hex chars)`);
990
+ throw ShipError.validation(
991
+ `${label} must be ${shape.TOTAL_LENGTH} characters total (${shape.PREFIX} + ${shape.HEX_LENGTH} hex chars)`,
992
+ );
958
993
  }
959
994
 
960
995
  const hexPart = value.slice(shape.PREFIX.length);
961
996
  if (!new RegExp(`^[a-f0-9]{${shape.HEX_LENGTH}}$`, 'i').test(hexPart)) {
962
- throw ShipError.validation(`${label} must contain ${shape.HEX_LENGTH} hexadecimal characters after "${shape.PREFIX}" prefix`);
997
+ throw ShipError.validation(
998
+ `${label} must contain ${shape.HEX_LENGTH} hexadecimal characters after "${shape.PREFIX}" prefix`,
999
+ );
963
1000
  }
964
1001
  }
965
1002
 
@@ -986,9 +1023,11 @@ export function validateDeployToken(deployToken: string): void {
986
1023
  export function validateToken(token: string): void {
987
1024
  switch (classifyToken(token)) {
988
1025
  case TokenKind.API_KEY:
989
- return validateApiKey(token);
1026
+ validateApiKey(token);
1027
+ return;
990
1028
  case TokenKind.DEPLOY_TOKEN:
991
- return validateDeployToken(token);
1029
+ validateDeployToken(token);
1030
+ return;
992
1031
  case TokenKind.OPAQUE:
993
1032
  if (!token) throw ShipError.validation('Token must be a non-empty string');
994
1033
  }
@@ -1002,7 +1041,7 @@ export function validateToken(token: string): void {
1002
1041
  export function validateCaller(caller: string): void {
1003
1042
  if (!caller || caller.length > CALLER.MAX_LENGTH || !CALLER.PATTERN.test(caller)) {
1004
1043
  throw ShipError.validation(
1005
- `Caller must be 1-${CALLER.MAX_LENGTH} characters: letters, digits, dots, underscores, or hyphens`
1044
+ `Caller must be 1-${CALLER.MAX_LENGTH} characters: letters, digits, dots, underscores, or hyphens`,
1006
1045
  );
1007
1046
  }
1008
1047
  }
@@ -1180,7 +1219,10 @@ export interface DeploymentUploadOptions {
1180
1219
  * Deployment resource interface - the contract all implementations must follow
1181
1220
  */
1182
1221
  export interface DeploymentResource {
1183
- upload: (input: DeployInput, options?: DeploymentUploadOptions) => Promise<DeploymentCreateResponse>;
1222
+ upload: (
1223
+ input: DeployInput,
1224
+ options?: DeploymentUploadOptions,
1225
+ ) => Promise<DeploymentCreateResponse>;
1184
1226
  list: () => Promise<DeploymentListResponse>;
1185
1227
  get: (id: string) => Promise<Deployment>;
1186
1228
  set: (id: string, options: { labels: string[] }) => Promise<Deployment>;
@@ -1191,7 +1233,10 @@ export interface DeploymentResource {
1191
1233
  * Domain resource interface - the contract all implementations must follow
1192
1234
  */
1193
1235
  export interface DomainResource {
1194
- set: (name: string, options?: { deployment?: string; labels?: string[] }) => Promise<DomainSetResult>;
1236
+ set: (
1237
+ name: string,
1238
+ options?: { deployment?: string; labels?: string[] },
1239
+ ) => Promise<DomainSetResult>;
1195
1240
  list: () => Promise<DomainListResponse>;
1196
1241
  get: (name: string) => Promise<Domain>;
1197
1242
  remove: (name: string) => Promise<void>;
@@ -1241,7 +1286,6 @@ export interface BillingStatus {
1241
1286
  portal: string | null;
1242
1287
  }
1243
1288
 
1244
-
1245
1289
  /**
1246
1290
  * Checkout session response from POST /billing/checkout
1247
1291
  */
@@ -1305,9 +1349,9 @@ export type ActivityEvent =
1305
1349
  | 'refund.created'
1306
1350
  | 'dispute.created'
1307
1351
  // Billing operational events (admin/debug only, not user-visible)
1308
- | 'billing.sync' // Outbound: unit count pushed to payment provider
1309
- | 'billing.stale' // Dropped: webhook predates last known state
1310
- | 'billing.race'; // Dropped: concurrent webhook already updated state
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
1311
1355
 
1312
1356
  /**
1313
1357
  * Activity events visible to users in the dashboard
@@ -1416,8 +1460,8 @@ export const FileValidationStatus = {
1416
1460
  READY: 'ready',
1417
1461
  } as const;
1418
1462
 
1419
- export type FileValidationStatusType = typeof FileValidationStatus[keyof typeof FileValidationStatus];
1420
-
1463
+ export type FileValidationStatusType =
1464
+ (typeof FileValidationStatus)[keyof typeof FileValidationStatus];
1421
1465
 
1422
1466
  /**
1423
1467
  * A validation issue with a display-ready message
@@ -1660,4 +1704,4 @@ export function validatePassword(value: unknown): string | undefined {
1660
1704
  );
1661
1705
  }
1662
1706
  return trimmed;
1663
- }
1707
+ }