@shipstatic/types 2.2.0 → 2.3.0-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
  */
@@ -518,6 +518,14 @@ export interface PingResponse {
518
518
  /** Optional timestamp */
519
519
  timestamp?: number;
520
520
  }
521
+ /**
522
+ * Where human identity is mounted on the API host. The API mounts Better
523
+ * Auth at this path (sign-in, sign-out, session reads, admin impersonation)
524
+ * and the web console's auth client posts to it — shared here so the two
525
+ * halves of the auth pair agree by construction, the same way both sides
526
+ * already share the credential prefixes below.
527
+ */
528
+ export declare const AUTH_BASE_PATH = "/auth";
521
529
  /**
522
530
  * How a request (or recorded activity) was authorized.
523
531
  *
@@ -536,7 +544,7 @@ export declare const AuthMethod: {
536
544
  readonly WEBHOOK: "webhook";
537
545
  readonly SYSTEM: "system";
538
546
  };
539
- export type AuthMethodType = typeof AuthMethod[keyof typeof AuthMethod];
547
+ export type AuthMethodType = (typeof AuthMethod)[keyof typeof AuthMethod];
540
548
  /**
541
549
  * Shape constants for API keys (`ship-{64 hex chars}`).
542
550
  * Single source of truth used by validation utilities and auth middleware.
@@ -595,7 +603,7 @@ export declare const TokenKind: {
595
603
  readonly DEPLOY_TOKEN: "token";
596
604
  readonly OPAQUE: "opaque";
597
605
  };
598
- export type TokenKindType = typeof TokenKind[keyof typeof TokenKind];
606
+ export type TokenKindType = (typeof TokenKind)[keyof typeof TokenKind];
599
607
  /**
600
608
  * Classify a client token by shape. The single dispatch used by both sides
601
609
  * of the wire: API auth middleware (which population is this credential?)
@@ -621,7 +629,7 @@ export declare const OAuthScope: {
621
629
  readonly DOMAINS_READ: "domains:read";
622
630
  readonly DOMAINS_WRITE: "domains:write";
623
631
  };
624
- export type OAuthScopeType = typeof OAuthScope[keyof typeof OAuthScope];
632
+ export type OAuthScopeType = (typeof OAuthScope)[keyof typeof OAuthScope];
625
633
  export declare const DEPLOYMENT_CONFIG_FILENAME = "ship.json";
626
634
  /** Default ship.json config for SPA routing. Single source of truth — used by both API and SDK. */
627
635
  export declare const SPA_DEFAULT_CONFIG: {
@@ -924,7 +932,7 @@ export declare const FileValidationStatus: {
924
932
  /** File passed validation and is ready for deployment */
925
933
  readonly READY: "ready";
926
934
  };
927
- export type FileValidationStatusType = typeof FileValidationStatus[keyof typeof FileValidationStatus];
935
+ export type FileValidationStatusType = (typeof FileValidationStatus)[keyof typeof FileValidationStatus];
928
936
  /**
929
937
  * A validation issue with a display-ready message
930
938
  *
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,15 +450,24 @@ 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
422
457
  // =============================================================================
423
- // The one address for credential vocabulary: how a request is authorized
424
- // (AuthMethod), the shapes that distinguish populations on the wire
425
- // (API_KEY, DEPLOY_TOKEN, CALLER), the single dispatch over them (TokenKind,
426
- // classifyToken), and the delegated-access scopes (OAuthScope).
458
+ // The one address for credential vocabulary: where human identity lives
459
+ // (AUTH_BASE_PATH), how a request is authorized (AuthMethod), the shapes
460
+ // that distinguish populations on the wire (API_KEY, DEPLOY_TOKEN, CALLER),
461
+ // the single dispatch over them (TokenKind, classifyToken), and the
462
+ // delegated-access scopes (OAuthScope).
463
+ /**
464
+ * Where human identity is mounted on the API host. The API mounts Better
465
+ * Auth at this path (sign-in, sign-out, session reads, admin impersonation)
466
+ * and the web console's auth client posts to it — shared here so the two
467
+ * halves of the auth pair agree by construction, the same way both sides
468
+ * already share the credential prefixes below.
469
+ */
470
+ export const AUTH_BASE_PATH = '/auth';
427
471
  /**
428
472
  * How a request (or recorded activity) was authorized.
429
473
  *
@@ -440,7 +484,7 @@ export const AuthMethod = {
440
484
  AGENT: 'agent',
441
485
  OAUTH: 'oauth',
442
486
  WEBHOOK: 'webhook',
443
- SYSTEM: 'system'
487
+ SYSTEM: 'system',
444
488
  };
445
489
  /**
446
490
  * Shape constants for API keys (`ship-{64 hex chars}`).
@@ -536,7 +580,9 @@ export const OAuthScope = {
536
580
  // =============================================================================
537
581
  export const DEPLOYMENT_CONFIG_FILENAME = 'ship.json';
538
582
  /** 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' }] };
583
+ export const SPA_DEFAULT_CONFIG = {
584
+ rewrites: [{ source: '/(.*)', destination: '/index.html' }],
585
+ };
540
586
  // =============================================================================
541
587
  // VALIDATION UTILITIES
542
588
  // =============================================================================
@@ -578,9 +624,11 @@ export function validateDeployToken(deployToken) {
578
624
  export function validateToken(token) {
579
625
  switch (classifyToken(token)) {
580
626
  case TokenKind.API_KEY:
581
- return validateApiKey(token);
627
+ validateApiKey(token);
628
+ return;
582
629
  case TokenKind.DEPLOY_TOKEN:
583
- return validateDeployToken(token);
630
+ validateDeployToken(token);
631
+ return;
584
632
  case TokenKind.OPAQUE:
585
633
  if (!token)
586
634
  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.3.0-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
  // =============================================================================
@@ -803,10 +834,20 @@ export interface PingResponse {
803
834
  // =============================================================================
804
835
  // CREDENTIAL SHAPES
805
836
  // =============================================================================
806
- // The one address for credential vocabulary: how a request is authorized
807
- // (AuthMethod), the shapes that distinguish populations on the wire
808
- // (API_KEY, DEPLOY_TOKEN, CALLER), the single dispatch over them (TokenKind,
809
- // classifyToken), and the delegated-access scopes (OAuthScope).
837
+ // The one address for credential vocabulary: where human identity lives
838
+ // (AUTH_BASE_PATH), how a request is authorized (AuthMethod), the shapes
839
+ // that distinguish populations on the wire (API_KEY, DEPLOY_TOKEN, CALLER),
840
+ // the single dispatch over them (TokenKind, classifyToken), and the
841
+ // delegated-access scopes (OAuthScope).
842
+
843
+ /**
844
+ * Where human identity is mounted on the API host. The API mounts Better
845
+ * Auth at this path (sign-in, sign-out, session reads, admin impersonation)
846
+ * and the web console's auth client posts to it — shared here so the two
847
+ * halves of the auth pair agree by construction, the same way both sides
848
+ * already share the credential prefixes below.
849
+ */
850
+ export const AUTH_BASE_PATH = '/auth';
810
851
 
811
852
  /**
812
853
  * How a request (or recorded activity) was authorized.
@@ -824,10 +865,10 @@ export const AuthMethod = {
824
865
  AGENT: 'agent',
825
866
  OAUTH: 'oauth',
826
867
  WEBHOOK: 'webhook',
827
- SYSTEM: 'system'
868
+ SYSTEM: 'system',
828
869
  } as const;
829
870
 
830
- export type AuthMethodType = typeof AuthMethod[keyof typeof AuthMethod];
871
+ export type AuthMethodType = (typeof AuthMethod)[keyof typeof AuthMethod];
831
872
 
832
873
  /**
833
874
  * Shape constants for API keys (`ship-{64 hex chars}`).
@@ -891,7 +932,7 @@ export const TokenKind = {
891
932
  OPAQUE: 'opaque',
892
933
  } as const;
893
934
 
894
- export type TokenKindType = typeof TokenKind[keyof typeof TokenKind];
935
+ export type TokenKindType = (typeof TokenKind)[keyof typeof TokenKind];
895
936
 
896
937
  /**
897
938
  * Classify a client token by shape. The single dispatch used by both sides
@@ -924,7 +965,7 @@ export const OAuthScope = {
924
965
  DOMAINS_WRITE: 'domains:write',
925
966
  } as const;
926
967
 
927
- export type OAuthScopeType = typeof OAuthScope[keyof typeof OAuthScope];
968
+ export type OAuthScopeType = (typeof OAuthScope)[keyof typeof OAuthScope];
928
969
 
929
970
  // =============================================================================
930
971
  // DEPLOYMENT CONFIGURATION CONSTANTS
@@ -933,7 +974,9 @@ export type OAuthScopeType = typeof OAuthScope[keyof typeof OAuthScope];
933
974
  export const DEPLOYMENT_CONFIG_FILENAME = 'ship.json';
934
975
 
935
976
  /** 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;
977
+ export const SPA_DEFAULT_CONFIG = {
978
+ rewrites: [{ source: '/(.*)', destination: '/index.html' }],
979
+ } as const;
937
980
 
938
981
  // =============================================================================
939
982
  // VALIDATION UTILITIES
@@ -947,19 +990,23 @@ export const SPA_DEFAULT_CONFIG = { rewrites: [{ source: '/(.*)', destination: '
947
990
  function validatePrefixedCredential(
948
991
  value: string,
949
992
  shape: { PREFIX: string; HEX_LENGTH: number; TOTAL_LENGTH: number },
950
- label: string
993
+ label: string,
951
994
  ): void {
952
995
  if (!value.startsWith(shape.PREFIX)) {
953
996
  throw ShipError.validation(`${label} must start with "${shape.PREFIX}"`);
954
997
  }
955
998
 
956
999
  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)`);
1000
+ throw ShipError.validation(
1001
+ `${label} must be ${shape.TOTAL_LENGTH} characters total (${shape.PREFIX} + ${shape.HEX_LENGTH} hex chars)`,
1002
+ );
958
1003
  }
959
1004
 
960
1005
  const hexPart = value.slice(shape.PREFIX.length);
961
1006
  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`);
1007
+ throw ShipError.validation(
1008
+ `${label} must contain ${shape.HEX_LENGTH} hexadecimal characters after "${shape.PREFIX}" prefix`,
1009
+ );
963
1010
  }
964
1011
  }
965
1012
 
@@ -986,9 +1033,11 @@ export function validateDeployToken(deployToken: string): void {
986
1033
  export function validateToken(token: string): void {
987
1034
  switch (classifyToken(token)) {
988
1035
  case TokenKind.API_KEY:
989
- return validateApiKey(token);
1036
+ validateApiKey(token);
1037
+ return;
990
1038
  case TokenKind.DEPLOY_TOKEN:
991
- return validateDeployToken(token);
1039
+ validateDeployToken(token);
1040
+ return;
992
1041
  case TokenKind.OPAQUE:
993
1042
  if (!token) throw ShipError.validation('Token must be a non-empty string');
994
1043
  }
@@ -1002,7 +1051,7 @@ export function validateToken(token: string): void {
1002
1051
  export function validateCaller(caller: string): void {
1003
1052
  if (!caller || caller.length > CALLER.MAX_LENGTH || !CALLER.PATTERN.test(caller)) {
1004
1053
  throw ShipError.validation(
1005
- `Caller must be 1-${CALLER.MAX_LENGTH} characters: letters, digits, dots, underscores, or hyphens`
1054
+ `Caller must be 1-${CALLER.MAX_LENGTH} characters: letters, digits, dots, underscores, or hyphens`,
1006
1055
  );
1007
1056
  }
1008
1057
  }
@@ -1180,7 +1229,10 @@ export interface DeploymentUploadOptions {
1180
1229
  * Deployment resource interface - the contract all implementations must follow
1181
1230
  */
1182
1231
  export interface DeploymentResource {
1183
- upload: (input: DeployInput, options?: DeploymentUploadOptions) => Promise<DeploymentCreateResponse>;
1232
+ upload: (
1233
+ input: DeployInput,
1234
+ options?: DeploymentUploadOptions,
1235
+ ) => Promise<DeploymentCreateResponse>;
1184
1236
  list: () => Promise<DeploymentListResponse>;
1185
1237
  get: (id: string) => Promise<Deployment>;
1186
1238
  set: (id: string, options: { labels: string[] }) => Promise<Deployment>;
@@ -1191,7 +1243,10 @@ export interface DeploymentResource {
1191
1243
  * Domain resource interface - the contract all implementations must follow
1192
1244
  */
1193
1245
  export interface DomainResource {
1194
- set: (name: string, options?: { deployment?: string; labels?: string[] }) => Promise<DomainSetResult>;
1246
+ set: (
1247
+ name: string,
1248
+ options?: { deployment?: string; labels?: string[] },
1249
+ ) => Promise<DomainSetResult>;
1195
1250
  list: () => Promise<DomainListResponse>;
1196
1251
  get: (name: string) => Promise<Domain>;
1197
1252
  remove: (name: string) => Promise<void>;
@@ -1241,7 +1296,6 @@ export interface BillingStatus {
1241
1296
  portal: string | null;
1242
1297
  }
1243
1298
 
1244
-
1245
1299
  /**
1246
1300
  * Checkout session response from POST /billing/checkout
1247
1301
  */
@@ -1305,9 +1359,9 @@ export type ActivityEvent =
1305
1359
  | 'refund.created'
1306
1360
  | 'dispute.created'
1307
1361
  // 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
1362
+ | 'billing.sync' // Outbound: unit count pushed to payment provider
1363
+ | 'billing.stale' // Dropped: webhook predates last known state
1364
+ | 'billing.race'; // Dropped: concurrent webhook already updated state
1311
1365
 
1312
1366
  /**
1313
1367
  * Activity events visible to users in the dashboard
@@ -1416,8 +1470,8 @@ export const FileValidationStatus = {
1416
1470
  READY: 'ready',
1417
1471
  } as const;
1418
1472
 
1419
- export type FileValidationStatusType = typeof FileValidationStatus[keyof typeof FileValidationStatus];
1420
-
1473
+ export type FileValidationStatusType =
1474
+ (typeof FileValidationStatus)[keyof typeof FileValidationStatus];
1421
1475
 
1422
1476
  /**
1423
1477
  * A validation issue with a display-ready message
@@ -1660,4 +1714,4 @@ export function validatePassword(value: unknown): string | undefined {
1660
1714
  );
1661
1715
  }
1662
1716
  return trimmed;
1663
- }
1717
+ }