@shipstatic/types 2.5.0-beta.15 → 2.5.0-beta.17
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 +93 -10
- package/dist/index.js +129 -3
- package/package.json +1 -1
- package/src/index.ts +204 -13
package/dist/index.d.ts
CHANGED
|
@@ -276,6 +276,10 @@ export interface DnsLookup {
|
|
|
276
276
|
/** The provider serving this domain's DNS, absent when unidentified */
|
|
277
277
|
provider?: DnsProvider;
|
|
278
278
|
}
|
|
279
|
+
/**
|
|
280
|
+
* A report: it answers a question and carries only the answer (`CLAUDE.md`,
|
|
281
|
+
* "A report answers a question").
|
|
282
|
+
*/
|
|
279
283
|
export interface DomainDnsResponse {
|
|
280
284
|
/** The domain name */
|
|
281
285
|
domain: string;
|
|
@@ -288,6 +292,9 @@ export interface DomainDnsResponse {
|
|
|
288
292
|
*
|
|
289
293
|
* `/admin/domains/:domain/share` answers the same shape, which is the admin
|
|
290
294
|
* law working: the operator surface is the public grammar with a prefix.
|
|
295
|
+
*
|
|
296
|
+
* A report: it answers a question and carries only the answer (`CLAUDE.md`,
|
|
297
|
+
* "A report answers a question").
|
|
291
298
|
*/
|
|
292
299
|
export interface DomainShareResponse {
|
|
293
300
|
/** The domain the setup link is for */
|
|
@@ -297,6 +304,9 @@ export interface DomainShareResponse {
|
|
|
297
304
|
}
|
|
298
305
|
/**
|
|
299
306
|
* Response for domain DNS records
|
|
307
|
+
*
|
|
308
|
+
* A report: it answers a question and carries only the answer (`CLAUDE.md`,
|
|
309
|
+
* "A report answers a question").
|
|
300
310
|
*/
|
|
301
311
|
export interface DomainRecordsResponse {
|
|
302
312
|
/** The domain name */
|
|
@@ -332,6 +342,9 @@ export declare function validateIdempotencyKey(value: unknown): string | undefin
|
|
|
332
342
|
* no identity, no row and no `created`, so there is nothing for a keyset
|
|
333
343
|
* cursor to resume after, and its consumer is an autocomplete that wants the
|
|
334
344
|
* whole set. Bounded by `PAGINATION.GLOBAL_LIMIT` rather than paginated.
|
|
345
|
+
*
|
|
346
|
+
* A report: it answers a question and carries only the answer (`CLAUDE.md`,
|
|
347
|
+
* "A report answers a question").
|
|
335
348
|
*/
|
|
336
349
|
export interface LabelsResponse {
|
|
337
350
|
readonly labels: string[];
|
|
@@ -342,8 +355,13 @@ export interface LabelsResponse {
|
|
|
342
355
|
*
|
|
343
356
|
* `custom` is the provider-specific walkthrough when the provider is known;
|
|
344
357
|
* `generic` always answers, so a caller never has nothing to show.
|
|
358
|
+
*
|
|
359
|
+
* A report: it answers a question and carries only the answer (`CLAUDE.md`,
|
|
360
|
+
* "A report answers a question").
|
|
345
361
|
*/
|
|
346
362
|
export interface SetupInstructionsResponse {
|
|
363
|
+
/** The domain the instructions are for — a report names its subject */
|
|
364
|
+
readonly domain: string;
|
|
347
365
|
/** One-line summary of what to do */
|
|
348
366
|
readonly tldr: string;
|
|
349
367
|
/** Provider-specific instructions, null when the provider is unknown */
|
|
@@ -354,7 +372,14 @@ export interface SetupInstructionsResponse {
|
|
|
354
372
|
readonly provider: string | null;
|
|
355
373
|
}
|
|
356
374
|
/**
|
|
357
|
-
*
|
|
375
|
+
* `POST /domains/validate` — a report answering "is this name usable, and if
|
|
376
|
+
* not, why".
|
|
377
|
+
*
|
|
378
|
+
* An unusable name is a legitimate ANSWER, not a failure, so this is a 200 and
|
|
379
|
+
* the verdict rides the body. `reason` was named `error` until 2026-07-29,
|
|
380
|
+
* which collided with {@link ErrorResponse}'s reserved key — there `error` is
|
|
381
|
+
* an `ErrorType` a client branches on, here it is prose a client displays, and
|
|
382
|
+
* one key cannot mean both. See {@link DeploymentDeleteResponse} for the law.
|
|
358
383
|
*/
|
|
359
384
|
export interface DomainValidateResponse {
|
|
360
385
|
/** Whether the domain is valid */
|
|
@@ -363,8 +388,8 @@ export interface DomainValidateResponse {
|
|
|
363
388
|
normalized: string | null;
|
|
364
389
|
/** Whether the domain is available, null when invalid */
|
|
365
390
|
available: boolean | null;
|
|
366
|
-
/**
|
|
367
|
-
|
|
391
|
+
/** Why the name is unusable, null when valid — displayed verbatim. */
|
|
392
|
+
reason: string | null;
|
|
368
393
|
}
|
|
369
394
|
/**
|
|
370
395
|
* Core deploy token object - used in both API responses and SDK.
|
|
@@ -521,6 +546,9 @@ export interface AccountDeleteResponse {
|
|
|
521
546
|
* (`Account.hint`), and the plaintext exists exactly once, in this response.
|
|
522
547
|
* The raw credential is `secret` on every surface that mints one — the same
|
|
523
548
|
* field `TokenCreateResponse` carries — because one concept gets one name.
|
|
549
|
+
*
|
|
550
|
+
* A report: it answers a question and carries only the answer (`CLAUDE.md`,
|
|
551
|
+
* "A report answers a question").
|
|
524
552
|
*/
|
|
525
553
|
export interface AccountKeyResponse {
|
|
526
554
|
/** The raw API key (shown once at mint, then never again) */
|
|
@@ -711,6 +739,9 @@ export declare function isShipError(error: unknown): error is ShipError;
|
|
|
711
739
|
*
|
|
712
740
|
* These are the *platform's* posted caps for the current account — server
|
|
713
741
|
* truth delivered at runtime, never hard-coded on the client.
|
|
742
|
+
*
|
|
743
|
+
* A report: it answers a question and carries only the answer (`CLAUDE.md`,
|
|
744
|
+
* "A report answers a question").
|
|
714
745
|
*/
|
|
715
746
|
export interface PlatformLimits {
|
|
716
747
|
/** Maximum size in bytes for a single file. */
|
|
@@ -744,6 +775,28 @@ export declare const BLOCKED_EXTENSIONS: ReadonlySet<string>;
|
|
|
744
775
|
* isBlockedExtension('README') // false
|
|
745
776
|
*/
|
|
746
777
|
export declare function isBlockedExtension(filename: string): boolean;
|
|
778
|
+
/**
|
|
779
|
+
* The `accept` attribute value for a browser file picker offering web files.
|
|
780
|
+
*
|
|
781
|
+
* **This is a hint, never a rule.** `BLOCKED_EXTENSIONS` is the platform's
|
|
782
|
+
* gate and the only thing that decides what may be hosted; this constant
|
|
783
|
+
* decides what a *file dialog* shows first. The two are not two halves of one
|
|
784
|
+
* policy, and this one must never be consulted to accept or reject a file.
|
|
785
|
+
*
|
|
786
|
+
* The distinction is structural, not stylistic. `accept` can express only an
|
|
787
|
+
* allowlist, while the platform's rule is a blocklist — so this list is
|
|
788
|
+
* necessarily *narrower* than what the platform hosts, and reading it as
|
|
789
|
+
* authority would reject files the platform serves happily. It is also not
|
|
790
|
+
* enforcement in the browser's own terms: every file dialog offers an
|
|
791
|
+
* all-files escape, and **drag-and-drop ignores `accept` entirely**. The
|
|
792
|
+
* dropzone and the picker must reach the same verdict on the same files, and
|
|
793
|
+
* they do — because the verdict is `validateFiles`, downstream of both.
|
|
794
|
+
*
|
|
795
|
+
* Kept beside `BLOCKED_EXTENSIONS` so one file holds both, which is what lets
|
|
796
|
+
* `tests/validation-constants.test.ts` fence the invariant that matters: the
|
|
797
|
+
* picker must never offer a file the platform will refuse.
|
|
798
|
+
*/
|
|
799
|
+
export declare const WEB_FILE_ACCEPT: string;
|
|
747
800
|
/**
|
|
748
801
|
* Characters that are unsafe in filenames for static hosting.
|
|
749
802
|
*
|
|
@@ -781,13 +834,20 @@ export declare const UNBUILT_PROJECT_MARKERS: ReadonlySet<string>;
|
|
|
781
834
|
*/
|
|
782
835
|
export declare function hasUnbuiltMarker(filePath: string): boolean;
|
|
783
836
|
/**
|
|
784
|
-
*
|
|
837
|
+
* `GET /ping` — a report of the server clock.
|
|
838
|
+
*
|
|
839
|
+
* Liveness is the STATUS CODE's answer, not a field's: a 200 means reachable,
|
|
840
|
+
* and any other outcome throws before a body is read. So the body carries the
|
|
841
|
+
* one thing a status code cannot — the server's own clock, which is what lets a
|
|
842
|
+
* client detect skew against a token expiry. It read `{ success: true,
|
|
843
|
+
* timestamp? }` until 2026-07-29, where `success` was a literal constant in the
|
|
844
|
+
* route (zero bits, and the platform's own named anti-pattern) while the field
|
|
845
|
+
* that IS the payload was optional. See {@link DeploymentDeleteResponse} for
|
|
846
|
+
* the law, and `tests/response-shapes.test.ts` for the fence that holds it.
|
|
785
847
|
*/
|
|
786
848
|
export interface PingResponse {
|
|
787
|
-
/** Always true if service is healthy */
|
|
788
|
-
success: boolean;
|
|
789
849
|
/** Server time in unix seconds — the one wire unit for timestamps. */
|
|
790
|
-
timestamp
|
|
850
|
+
readonly timestamp: number;
|
|
791
851
|
}
|
|
792
852
|
/**
|
|
793
853
|
* Where human identity is mounted on the API host. The API mounts Better
|
|
@@ -992,6 +1052,10 @@ export interface SPACheckDebug {
|
|
|
992
1052
|
/** The reason for the detection result */
|
|
993
1053
|
reason: string;
|
|
994
1054
|
}
|
|
1055
|
+
/**
|
|
1056
|
+
* A report: it answers a question and carries only the answer (`CLAUDE.md`,
|
|
1057
|
+
* "A report answers a question").
|
|
1058
|
+
*/
|
|
995
1059
|
export interface SPACheckResponse {
|
|
996
1060
|
/** Whether the project is detected as a Single Page Application */
|
|
997
1061
|
isSPA: boolean;
|
|
@@ -1150,7 +1214,7 @@ export interface DeploymentResource<UploadOptions extends DeploymentUploadOption
|
|
|
1150
1214
|
list: (options?: ListOptions) => Promise<DeploymentListResponse>;
|
|
1151
1215
|
get: (id: string) => Promise<Deployment>;
|
|
1152
1216
|
set: (id: string, options: DeploymentSetOptions) => Promise<Deployment>;
|
|
1153
|
-
|
|
1217
|
+
delete: (id: string) => Promise<DeploymentDeleteResponse>;
|
|
1154
1218
|
}
|
|
1155
1219
|
/**
|
|
1156
1220
|
* Domain resource interface - the contract all implementations must follow
|
|
@@ -1159,7 +1223,7 @@ export interface DomainResource {
|
|
|
1159
1223
|
set: (name: string, options?: DomainSetOptions) => Promise<DomainSetResult>;
|
|
1160
1224
|
list: (options?: ListOptions) => Promise<DomainListResponse>;
|
|
1161
1225
|
get: (name: string) => Promise<Domain>;
|
|
1162
|
-
|
|
1226
|
+
delete: (name: string) => Promise<DomainDeleteResponse>;
|
|
1163
1227
|
verify: (name: string) => Promise<DomainVerifyResponse>;
|
|
1164
1228
|
validate: (name: string) => Promise<DomainValidateResponse>;
|
|
1165
1229
|
dns: (name: string) => Promise<DomainDnsResponse>;
|
|
@@ -1179,7 +1243,7 @@ export interface TokenResource {
|
|
|
1179
1243
|
create: (options?: TokenCreateOptions) => Promise<TokenCreateResponse>;
|
|
1180
1244
|
list: (options?: ListOptions) => Promise<TokenListResponse>;
|
|
1181
1245
|
get: (token: string) => Promise<Token>;
|
|
1182
|
-
|
|
1246
|
+
delete: (token: string) => Promise<TokenDeleteResponse>;
|
|
1183
1247
|
}
|
|
1184
1248
|
/**
|
|
1185
1249
|
* Billing status response from GET /billing/status
|
|
@@ -1199,6 +1263,25 @@ export interface BillingStatus {
|
|
|
1199
1263
|
/** Link to Creem customer portal for billing management, null if unavailable */
|
|
1200
1264
|
portal: string | null;
|
|
1201
1265
|
}
|
|
1266
|
+
/**
|
|
1267
|
+
* Acknowledgement of `POST /billing/cancel`.
|
|
1268
|
+
*
|
|
1269
|
+
* Cancelling leaves no billing entity to return, so it answers with the
|
|
1270
|
+
* account and the one field of the account the call changed — the plan it
|
|
1271
|
+
* landed on. See {@link DeploymentDeleteResponse} for the law.
|
|
1272
|
+
*
|
|
1273
|
+
* This read `{ success: true, message: 'Subscription canceled successfully…' }`
|
|
1274
|
+
* until 2026-07-29, an anonymous shape that `web/my` redeclared inline and
|
|
1275
|
+
* whose prose no surface ever displayed: both callers await the promise and
|
|
1276
|
+
* discard the body, then compose their own toast. The message was written,
|
|
1277
|
+
* serialized, and thrown away on every cancellation.
|
|
1278
|
+
*/
|
|
1279
|
+
export interface BillingCancelResponse {
|
|
1280
|
+
/** The account whose subscription was cancelled */
|
|
1281
|
+
readonly account: string;
|
|
1282
|
+
/** The plan the account now holds — `free` on a successful cancellation */
|
|
1283
|
+
readonly plan: AccountPlanType;
|
|
1284
|
+
}
|
|
1202
1285
|
/**
|
|
1203
1286
|
* Checkout session response from POST /billing/checkout
|
|
1204
1287
|
*/
|
package/dist/index.js
CHANGED
|
@@ -198,12 +198,19 @@ const CLIENT_ONLY_ERROR_TYPES = new Set([
|
|
|
198
198
|
const ERROR_CATEGORIES = {
|
|
199
199
|
/**
|
|
200
200
|
* Client-attributable types. Exhaustive over the 4xx-carrying types, and
|
|
201
|
-
*
|
|
202
|
-
*
|
|
203
|
-
*
|
|
201
|
+
* over the statusless ones too — those are raised locally and have no
|
|
202
|
+
* status for `isClientError`'s second arm to read, so omitting one makes it
|
|
203
|
+
* read as a server fault. The rule is the membership test: every type in
|
|
204
|
+
* `CLIENT_ONLY_ERROR_TYPES` except `Network` (which `isNetworkError` owns)
|
|
205
|
+
* belongs here.
|
|
206
|
+
*
|
|
207
|
+
* `Cancelled` was missing until 2026-07-29, which is exactly that failure:
|
|
208
|
+
* a caller who aborted their own deploy was told "server error: please try
|
|
209
|
+
* again" — the CLI's fallback for everything this set does not claim.
|
|
204
210
|
*/
|
|
205
211
|
client: new Set([
|
|
206
212
|
ErrorType.Business,
|
|
213
|
+
ErrorType.Cancelled,
|
|
207
214
|
ErrorType.Config,
|
|
208
215
|
ErrorType.File,
|
|
209
216
|
ErrorType.Forbidden,
|
|
@@ -553,6 +560,125 @@ export function isBlockedExtension(filename) {
|
|
|
553
560
|
return BLOCKED_EXTENSIONS.has(ext);
|
|
554
561
|
}
|
|
555
562
|
// =============================================================================
|
|
563
|
+
// PICKER ACCEPT HINT
|
|
564
|
+
// =============================================================================
|
|
565
|
+
/**
|
|
566
|
+
* The extensions a browser file picker offers by default, grouped by role.
|
|
567
|
+
*
|
|
568
|
+
* Private on purpose: the only published form is `WEB_FILE_ACCEPT`, the
|
|
569
|
+
* attribute value itself. A published set would invite a call site to ask it
|
|
570
|
+
* whether a file is allowed — which is the one thing this list must never
|
|
571
|
+
* answer. See `WEB_FILE_ACCEPT`.
|
|
572
|
+
*
|
|
573
|
+
* Extensionless files (`LICENSE`, most `.well-known` entries) are inexpressible
|
|
574
|
+
* in `accept`, and reach a deployment by folder pick, ZIP, or drag-and-drop.
|
|
575
|
+
*/
|
|
576
|
+
const WEB_FILE_EXTENSIONS = [
|
|
577
|
+
// Markup & documents
|
|
578
|
+
'html',
|
|
579
|
+
'htm',
|
|
580
|
+
'xhtml',
|
|
581
|
+
'xml',
|
|
582
|
+
'txt',
|
|
583
|
+
'md',
|
|
584
|
+
'markdown',
|
|
585
|
+
'pdf',
|
|
586
|
+
'csv',
|
|
587
|
+
// Data & config
|
|
588
|
+
'json',
|
|
589
|
+
'jsonc',
|
|
590
|
+
'webmanifest',
|
|
591
|
+
'map',
|
|
592
|
+
'toml',
|
|
593
|
+
'yaml',
|
|
594
|
+
'yml',
|
|
595
|
+
'rss',
|
|
596
|
+
'atom',
|
|
597
|
+
// Styles
|
|
598
|
+
'css',
|
|
599
|
+
'scss',
|
|
600
|
+
'sass',
|
|
601
|
+
'less',
|
|
602
|
+
// Scripts & modules
|
|
603
|
+
'js',
|
|
604
|
+
'mjs',
|
|
605
|
+
'cjs',
|
|
606
|
+
'jsx',
|
|
607
|
+
'ts',
|
|
608
|
+
'tsx',
|
|
609
|
+
'wasm',
|
|
610
|
+
'vue',
|
|
611
|
+
'svelte',
|
|
612
|
+
// Images
|
|
613
|
+
'png',
|
|
614
|
+
'jpg',
|
|
615
|
+
'jpeg',
|
|
616
|
+
'gif',
|
|
617
|
+
'webp',
|
|
618
|
+
'avif',
|
|
619
|
+
'svg',
|
|
620
|
+
'ico',
|
|
621
|
+
'bmp',
|
|
622
|
+
'tif',
|
|
623
|
+
'tiff',
|
|
624
|
+
'heic',
|
|
625
|
+
'heif',
|
|
626
|
+
// Fonts
|
|
627
|
+
'woff',
|
|
628
|
+
'woff2',
|
|
629
|
+
'ttf',
|
|
630
|
+
'otf',
|
|
631
|
+
'eot',
|
|
632
|
+
// Audio
|
|
633
|
+
'mp3',
|
|
634
|
+
'wav',
|
|
635
|
+
'ogg',
|
|
636
|
+
'oga',
|
|
637
|
+
'opus',
|
|
638
|
+
'm4a',
|
|
639
|
+
'aac',
|
|
640
|
+
'flac',
|
|
641
|
+
'weba',
|
|
642
|
+
// Video
|
|
643
|
+
'mp4',
|
|
644
|
+
'webm',
|
|
645
|
+
'ogv',
|
|
646
|
+
'mov',
|
|
647
|
+
'm4v',
|
|
648
|
+
'avi',
|
|
649
|
+
// 3D models
|
|
650
|
+
'glb',
|
|
651
|
+
'gltf',
|
|
652
|
+
'usdz',
|
|
653
|
+
// Text tracks
|
|
654
|
+
'vtt',
|
|
655
|
+
'srt',
|
|
656
|
+
// Archive — a whole site in one file
|
|
657
|
+
'zip',
|
|
658
|
+
];
|
|
659
|
+
/**
|
|
660
|
+
* The `accept` attribute value for a browser file picker offering web files.
|
|
661
|
+
*
|
|
662
|
+
* **This is a hint, never a rule.** `BLOCKED_EXTENSIONS` is the platform's
|
|
663
|
+
* gate and the only thing that decides what may be hosted; this constant
|
|
664
|
+
* decides what a *file dialog* shows first. The two are not two halves of one
|
|
665
|
+
* policy, and this one must never be consulted to accept or reject a file.
|
|
666
|
+
*
|
|
667
|
+
* The distinction is structural, not stylistic. `accept` can express only an
|
|
668
|
+
* allowlist, while the platform's rule is a blocklist — so this list is
|
|
669
|
+
* necessarily *narrower* than what the platform hosts, and reading it as
|
|
670
|
+
* authority would reject files the platform serves happily. It is also not
|
|
671
|
+
* enforcement in the browser's own terms: every file dialog offers an
|
|
672
|
+
* all-files escape, and **drag-and-drop ignores `accept` entirely**. The
|
|
673
|
+
* dropzone and the picker must reach the same verdict on the same files, and
|
|
674
|
+
* they do — because the verdict is `validateFiles`, downstream of both.
|
|
675
|
+
*
|
|
676
|
+
* Kept beside `BLOCKED_EXTENSIONS` so one file holds both, which is what lets
|
|
677
|
+
* `tests/validation-constants.test.ts` fence the invariant that matters: the
|
|
678
|
+
* picker must never offer a file the platform will refuse.
|
|
679
|
+
*/
|
|
680
|
+
export const WEB_FILE_ACCEPT = WEB_FILE_EXTENSIONS.map((ext) => `.${ext}`).join(',');
|
|
681
|
+
// =============================================================================
|
|
556
682
|
// FILENAME CHARACTER VALIDATION
|
|
557
683
|
// =============================================================================
|
|
558
684
|
/**
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -304,6 +304,10 @@ export interface DnsLookup {
|
|
|
304
304
|
provider?: DnsProvider;
|
|
305
305
|
}
|
|
306
306
|
|
|
307
|
+
/**
|
|
308
|
+
* A report: it answers a question and carries only the answer (`CLAUDE.md`,
|
|
309
|
+
* "A report answers a question").
|
|
310
|
+
*/
|
|
307
311
|
export interface DomainDnsResponse {
|
|
308
312
|
/** The domain name */
|
|
309
313
|
domain: string;
|
|
@@ -317,6 +321,9 @@ export interface DomainDnsResponse {
|
|
|
317
321
|
*
|
|
318
322
|
* `/admin/domains/:domain/share` answers the same shape, which is the admin
|
|
319
323
|
* law working: the operator surface is the public grammar with a prefix.
|
|
324
|
+
*
|
|
325
|
+
* A report: it answers a question and carries only the answer (`CLAUDE.md`,
|
|
326
|
+
* "A report answers a question").
|
|
320
327
|
*/
|
|
321
328
|
export interface DomainShareResponse {
|
|
322
329
|
/** The domain the setup link is for */
|
|
@@ -327,6 +334,9 @@ export interface DomainShareResponse {
|
|
|
327
334
|
|
|
328
335
|
/**
|
|
329
336
|
* Response for domain DNS records
|
|
337
|
+
*
|
|
338
|
+
* A report: it answers a question and carries only the answer (`CLAUDE.md`,
|
|
339
|
+
* "A report answers a question").
|
|
330
340
|
*/
|
|
331
341
|
export interface DomainRecordsResponse {
|
|
332
342
|
/** The domain name */
|
|
@@ -380,6 +390,9 @@ export function validateIdempotencyKey(value: unknown): string | undefined {
|
|
|
380
390
|
* no identity, no row and no `created`, so there is nothing for a keyset
|
|
381
391
|
* cursor to resume after, and its consumer is an autocomplete that wants the
|
|
382
392
|
* whole set. Bounded by `PAGINATION.GLOBAL_LIMIT` rather than paginated.
|
|
393
|
+
*
|
|
394
|
+
* A report: it answers a question and carries only the answer (`CLAUDE.md`,
|
|
395
|
+
* "A report answers a question").
|
|
383
396
|
*/
|
|
384
397
|
export interface LabelsResponse {
|
|
385
398
|
readonly labels: string[];
|
|
@@ -391,8 +404,13 @@ export interface LabelsResponse {
|
|
|
391
404
|
*
|
|
392
405
|
* `custom` is the provider-specific walkthrough when the provider is known;
|
|
393
406
|
* `generic` always answers, so a caller never has nothing to show.
|
|
407
|
+
*
|
|
408
|
+
* A report: it answers a question and carries only the answer (`CLAUDE.md`,
|
|
409
|
+
* "A report answers a question").
|
|
394
410
|
*/
|
|
395
411
|
export interface SetupInstructionsResponse {
|
|
412
|
+
/** The domain the instructions are for — a report names its subject */
|
|
413
|
+
readonly domain: string;
|
|
396
414
|
/** One-line summary of what to do */
|
|
397
415
|
readonly tldr: string;
|
|
398
416
|
/** Provider-specific instructions, null when the provider is unknown */
|
|
@@ -404,7 +422,14 @@ export interface SetupInstructionsResponse {
|
|
|
404
422
|
}
|
|
405
423
|
|
|
406
424
|
/**
|
|
407
|
-
*
|
|
425
|
+
* `POST /domains/validate` — a report answering "is this name usable, and if
|
|
426
|
+
* not, why".
|
|
427
|
+
*
|
|
428
|
+
* An unusable name is a legitimate ANSWER, not a failure, so this is a 200 and
|
|
429
|
+
* the verdict rides the body. `reason` was named `error` until 2026-07-29,
|
|
430
|
+
* which collided with {@link ErrorResponse}'s reserved key — there `error` is
|
|
431
|
+
* an `ErrorType` a client branches on, here it is prose a client displays, and
|
|
432
|
+
* one key cannot mean both. See {@link DeploymentDeleteResponse} for the law.
|
|
408
433
|
*/
|
|
409
434
|
export interface DomainValidateResponse {
|
|
410
435
|
/** Whether the domain is valid */
|
|
@@ -413,8 +438,8 @@ export interface DomainValidateResponse {
|
|
|
413
438
|
normalized: string | null;
|
|
414
439
|
/** Whether the domain is available, null when invalid */
|
|
415
440
|
available: boolean | null;
|
|
416
|
-
/**
|
|
417
|
-
|
|
441
|
+
/** Why the name is unusable, null when valid — displayed verbatim. */
|
|
442
|
+
reason: string | null;
|
|
418
443
|
}
|
|
419
444
|
|
|
420
445
|
// =============================================================================
|
|
@@ -590,6 +615,9 @@ export interface AccountDeleteResponse {
|
|
|
590
615
|
* (`Account.hint`), and the plaintext exists exactly once, in this response.
|
|
591
616
|
* The raw credential is `secret` on every surface that mints one — the same
|
|
592
617
|
* field `TokenCreateResponse` carries — because one concept gets one name.
|
|
618
|
+
*
|
|
619
|
+
* A report: it answers a question and carries only the answer (`CLAUDE.md`,
|
|
620
|
+
* "A report answers a question").
|
|
593
621
|
*/
|
|
594
622
|
export interface AccountKeyResponse {
|
|
595
623
|
/** The raw API key (shown once at mint, then never again) */
|
|
@@ -682,12 +710,19 @@ const CLIENT_ONLY_ERROR_TYPES = new Set<string>([
|
|
|
682
710
|
const ERROR_CATEGORIES = {
|
|
683
711
|
/**
|
|
684
712
|
* Client-attributable types. Exhaustive over the 4xx-carrying types, and
|
|
685
|
-
*
|
|
686
|
-
*
|
|
687
|
-
*
|
|
713
|
+
* over the statusless ones too — those are raised locally and have no
|
|
714
|
+
* status for `isClientError`'s second arm to read, so omitting one makes it
|
|
715
|
+
* read as a server fault. The rule is the membership test: every type in
|
|
716
|
+
* `CLIENT_ONLY_ERROR_TYPES` except `Network` (which `isNetworkError` owns)
|
|
717
|
+
* belongs here.
|
|
718
|
+
*
|
|
719
|
+
* `Cancelled` was missing until 2026-07-29, which is exactly that failure:
|
|
720
|
+
* a caller who aborted their own deploy was told "server error: please try
|
|
721
|
+
* again" — the CLI's fallback for everything this set does not claim.
|
|
688
722
|
*/
|
|
689
723
|
client: new Set<ErrorType>([
|
|
690
724
|
ErrorType.Business,
|
|
725
|
+
ErrorType.Cancelled,
|
|
691
726
|
ErrorType.Config,
|
|
692
727
|
ErrorType.File,
|
|
693
728
|
ErrorType.Forbidden,
|
|
@@ -1021,6 +1056,9 @@ export function isShipError(error: unknown): error is ShipError {
|
|
|
1021
1056
|
*
|
|
1022
1057
|
* These are the *platform's* posted caps for the current account — server
|
|
1023
1058
|
* truth delivered at runtime, never hard-coded on the client.
|
|
1059
|
+
*
|
|
1060
|
+
* A report: it answers a question and carries only the answer (`CLAUDE.md`,
|
|
1061
|
+
* "A report answers a question").
|
|
1024
1062
|
*/
|
|
1025
1063
|
export interface PlatformLimits {
|
|
1026
1064
|
/** Maximum size in bytes for a single file. */
|
|
@@ -1109,6 +1147,128 @@ export function isBlockedExtension(filename: string): boolean {
|
|
|
1109
1147
|
return BLOCKED_EXTENSIONS.has(ext);
|
|
1110
1148
|
}
|
|
1111
1149
|
|
|
1150
|
+
// =============================================================================
|
|
1151
|
+
// PICKER ACCEPT HINT
|
|
1152
|
+
// =============================================================================
|
|
1153
|
+
|
|
1154
|
+
/**
|
|
1155
|
+
* The extensions a browser file picker offers by default, grouped by role.
|
|
1156
|
+
*
|
|
1157
|
+
* Private on purpose: the only published form is `WEB_FILE_ACCEPT`, the
|
|
1158
|
+
* attribute value itself. A published set would invite a call site to ask it
|
|
1159
|
+
* whether a file is allowed — which is the one thing this list must never
|
|
1160
|
+
* answer. See `WEB_FILE_ACCEPT`.
|
|
1161
|
+
*
|
|
1162
|
+
* Extensionless files (`LICENSE`, most `.well-known` entries) are inexpressible
|
|
1163
|
+
* in `accept`, and reach a deployment by folder pick, ZIP, or drag-and-drop.
|
|
1164
|
+
*/
|
|
1165
|
+
const WEB_FILE_EXTENSIONS = [
|
|
1166
|
+
// Markup & documents
|
|
1167
|
+
'html',
|
|
1168
|
+
'htm',
|
|
1169
|
+
'xhtml',
|
|
1170
|
+
'xml',
|
|
1171
|
+
'txt',
|
|
1172
|
+
'md',
|
|
1173
|
+
'markdown',
|
|
1174
|
+
'pdf',
|
|
1175
|
+
'csv',
|
|
1176
|
+
// Data & config
|
|
1177
|
+
'json',
|
|
1178
|
+
'jsonc',
|
|
1179
|
+
'webmanifest',
|
|
1180
|
+
'map',
|
|
1181
|
+
'toml',
|
|
1182
|
+
'yaml',
|
|
1183
|
+
'yml',
|
|
1184
|
+
'rss',
|
|
1185
|
+
'atom',
|
|
1186
|
+
// Styles
|
|
1187
|
+
'css',
|
|
1188
|
+
'scss',
|
|
1189
|
+
'sass',
|
|
1190
|
+
'less',
|
|
1191
|
+
// Scripts & modules
|
|
1192
|
+
'js',
|
|
1193
|
+
'mjs',
|
|
1194
|
+
'cjs',
|
|
1195
|
+
'jsx',
|
|
1196
|
+
'ts',
|
|
1197
|
+
'tsx',
|
|
1198
|
+
'wasm',
|
|
1199
|
+
'vue',
|
|
1200
|
+
'svelte',
|
|
1201
|
+
// Images
|
|
1202
|
+
'png',
|
|
1203
|
+
'jpg',
|
|
1204
|
+
'jpeg',
|
|
1205
|
+
'gif',
|
|
1206
|
+
'webp',
|
|
1207
|
+
'avif',
|
|
1208
|
+
'svg',
|
|
1209
|
+
'ico',
|
|
1210
|
+
'bmp',
|
|
1211
|
+
'tif',
|
|
1212
|
+
'tiff',
|
|
1213
|
+
'heic',
|
|
1214
|
+
'heif',
|
|
1215
|
+
// Fonts
|
|
1216
|
+
'woff',
|
|
1217
|
+
'woff2',
|
|
1218
|
+
'ttf',
|
|
1219
|
+
'otf',
|
|
1220
|
+
'eot',
|
|
1221
|
+
// Audio
|
|
1222
|
+
'mp3',
|
|
1223
|
+
'wav',
|
|
1224
|
+
'ogg',
|
|
1225
|
+
'oga',
|
|
1226
|
+
'opus',
|
|
1227
|
+
'm4a',
|
|
1228
|
+
'aac',
|
|
1229
|
+
'flac',
|
|
1230
|
+
'weba',
|
|
1231
|
+
// Video
|
|
1232
|
+
'mp4',
|
|
1233
|
+
'webm',
|
|
1234
|
+
'ogv',
|
|
1235
|
+
'mov',
|
|
1236
|
+
'm4v',
|
|
1237
|
+
'avi',
|
|
1238
|
+
// 3D models
|
|
1239
|
+
'glb',
|
|
1240
|
+
'gltf',
|
|
1241
|
+
'usdz',
|
|
1242
|
+
// Text tracks
|
|
1243
|
+
'vtt',
|
|
1244
|
+
'srt',
|
|
1245
|
+
// Archive — a whole site in one file
|
|
1246
|
+
'zip',
|
|
1247
|
+
] as const;
|
|
1248
|
+
|
|
1249
|
+
/**
|
|
1250
|
+
* The `accept` attribute value for a browser file picker offering web files.
|
|
1251
|
+
*
|
|
1252
|
+
* **This is a hint, never a rule.** `BLOCKED_EXTENSIONS` is the platform's
|
|
1253
|
+
* gate and the only thing that decides what may be hosted; this constant
|
|
1254
|
+
* decides what a *file dialog* shows first. The two are not two halves of one
|
|
1255
|
+
* policy, and this one must never be consulted to accept or reject a file.
|
|
1256
|
+
*
|
|
1257
|
+
* The distinction is structural, not stylistic. `accept` can express only an
|
|
1258
|
+
* allowlist, while the platform's rule is a blocklist — so this list is
|
|
1259
|
+
* necessarily *narrower* than what the platform hosts, and reading it as
|
|
1260
|
+
* authority would reject files the platform serves happily. It is also not
|
|
1261
|
+
* enforcement in the browser's own terms: every file dialog offers an
|
|
1262
|
+
* all-files escape, and **drag-and-drop ignores `accept` entirely**. The
|
|
1263
|
+
* dropzone and the picker must reach the same verdict on the same files, and
|
|
1264
|
+
* they do — because the verdict is `validateFiles`, downstream of both.
|
|
1265
|
+
*
|
|
1266
|
+
* Kept beside `BLOCKED_EXTENSIONS` so one file holds both, which is what lets
|
|
1267
|
+
* `tests/validation-constants.test.ts` fence the invariant that matters: the
|
|
1268
|
+
* picker must never offer a file the platform will refuse.
|
|
1269
|
+
*/
|
|
1270
|
+
export const WEB_FILE_ACCEPT: string = WEB_FILE_EXTENSIONS.map((ext) => `.${ext}`).join(',');
|
|
1271
|
+
|
|
1112
1272
|
// =============================================================================
|
|
1113
1273
|
// FILENAME CHARACTER VALIDATION
|
|
1114
1274
|
// =============================================================================
|
|
@@ -1171,13 +1331,20 @@ export function hasUnbuiltMarker(filePath: string): boolean {
|
|
|
1171
1331
|
// =============================================================================
|
|
1172
1332
|
|
|
1173
1333
|
/**
|
|
1174
|
-
*
|
|
1334
|
+
* `GET /ping` — a report of the server clock.
|
|
1335
|
+
*
|
|
1336
|
+
* Liveness is the STATUS CODE's answer, not a field's: a 200 means reachable,
|
|
1337
|
+
* and any other outcome throws before a body is read. So the body carries the
|
|
1338
|
+
* one thing a status code cannot — the server's own clock, which is what lets a
|
|
1339
|
+
* client detect skew against a token expiry. It read `{ success: true,
|
|
1340
|
+
* timestamp? }` until 2026-07-29, where `success` was a literal constant in the
|
|
1341
|
+
* route (zero bits, and the platform's own named anti-pattern) while the field
|
|
1342
|
+
* that IS the payload was optional. See {@link DeploymentDeleteResponse} for
|
|
1343
|
+
* the law, and `tests/response-shapes.test.ts` for the fence that holds it.
|
|
1175
1344
|
*/
|
|
1176
1345
|
export interface PingResponse {
|
|
1177
|
-
/** Always true if service is healthy */
|
|
1178
|
-
success: boolean;
|
|
1179
1346
|
/** Server time in unix seconds — the one wire unit for timestamps. */
|
|
1180
|
-
timestamp
|
|
1347
|
+
readonly timestamp: number;
|
|
1181
1348
|
}
|
|
1182
1349
|
|
|
1183
1350
|
// =============================================================================
|
|
@@ -1516,6 +1683,10 @@ export interface SPACheckDebug {
|
|
|
1516
1683
|
reason: string;
|
|
1517
1684
|
}
|
|
1518
1685
|
|
|
1686
|
+
/**
|
|
1687
|
+
* A report: it answers a question and carries only the answer (`CLAUDE.md`,
|
|
1688
|
+
* "A report answers a question").
|
|
1689
|
+
*/
|
|
1519
1690
|
export interface SPACheckResponse {
|
|
1520
1691
|
/** Whether the project is detected as a Single Page Application */
|
|
1521
1692
|
isSPA: boolean;
|
|
@@ -1697,7 +1868,7 @@ export interface DeploymentResource<
|
|
|
1697
1868
|
list: (options?: ListOptions) => Promise<DeploymentListResponse>;
|
|
1698
1869
|
get: (id: string) => Promise<Deployment>;
|
|
1699
1870
|
set: (id: string, options: DeploymentSetOptions) => Promise<Deployment>;
|
|
1700
|
-
|
|
1871
|
+
delete: (id: string) => Promise<DeploymentDeleteResponse>;
|
|
1701
1872
|
}
|
|
1702
1873
|
|
|
1703
1874
|
/**
|
|
@@ -1707,7 +1878,7 @@ export interface DomainResource {
|
|
|
1707
1878
|
set: (name: string, options?: DomainSetOptions) => Promise<DomainSetResult>;
|
|
1708
1879
|
list: (options?: ListOptions) => Promise<DomainListResponse>;
|
|
1709
1880
|
get: (name: string) => Promise<Domain>;
|
|
1710
|
-
|
|
1881
|
+
delete: (name: string) => Promise<DomainDeleteResponse>;
|
|
1711
1882
|
verify: (name: string) => Promise<DomainVerifyResponse>;
|
|
1712
1883
|
validate: (name: string) => Promise<DomainValidateResponse>;
|
|
1713
1884
|
dns: (name: string) => Promise<DomainDnsResponse>;
|
|
@@ -1729,7 +1900,7 @@ export interface TokenResource {
|
|
|
1729
1900
|
create: (options?: TokenCreateOptions) => Promise<TokenCreateResponse>;
|
|
1730
1901
|
list: (options?: ListOptions) => Promise<TokenListResponse>;
|
|
1731
1902
|
get: (token: string) => Promise<Token>;
|
|
1732
|
-
|
|
1903
|
+
delete: (token: string) => Promise<TokenDeleteResponse>;
|
|
1733
1904
|
}
|
|
1734
1905
|
|
|
1735
1906
|
// =============================================================================
|
|
@@ -1755,6 +1926,26 @@ export interface BillingStatus {
|
|
|
1755
1926
|
portal: string | null;
|
|
1756
1927
|
}
|
|
1757
1928
|
|
|
1929
|
+
/**
|
|
1930
|
+
* Acknowledgement of `POST /billing/cancel`.
|
|
1931
|
+
*
|
|
1932
|
+
* Cancelling leaves no billing entity to return, so it answers with the
|
|
1933
|
+
* account and the one field of the account the call changed — the plan it
|
|
1934
|
+
* landed on. See {@link DeploymentDeleteResponse} for the law.
|
|
1935
|
+
*
|
|
1936
|
+
* This read `{ success: true, message: 'Subscription canceled successfully…' }`
|
|
1937
|
+
* until 2026-07-29, an anonymous shape that `web/my` redeclared inline and
|
|
1938
|
+
* whose prose no surface ever displayed: both callers await the promise and
|
|
1939
|
+
* discard the body, then compose their own toast. The message was written,
|
|
1940
|
+
* serialized, and thrown away on every cancellation.
|
|
1941
|
+
*/
|
|
1942
|
+
export interface BillingCancelResponse {
|
|
1943
|
+
/** The account whose subscription was cancelled */
|
|
1944
|
+
readonly account: string;
|
|
1945
|
+
/** The plan the account now holds — `free` on a successful cancellation */
|
|
1946
|
+
readonly plan: AccountPlanType;
|
|
1947
|
+
}
|
|
1948
|
+
|
|
1758
1949
|
/**
|
|
1759
1950
|
* Checkout session response from POST /billing/checkout
|
|
1760
1951
|
*/
|