@shipstatic/ship 2.0.0-beta.16 → 2.0.0-beta.18

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
@@ -1,7 +1,1710 @@
1
- import * as _shipstatic_types from '@shipstatic/types';
2
- import { DeploymentUploadOptions, StaticFile, DeploymentViaType, DeploymentCreateResponse, ListOptions, DeploymentListResponse, Deployment, DeploymentDeleteResponse, DomainSetResult, DomainListResponse, Domain, DomainDeleteResponse, DomainVerifyResponse, DomainDnsResponse, DomainRecordsResponse, DomainShareResponse, DomainValidateResponse, TokenCreateResponse, TokenListResponse, TokenDeleteResponse, Token, AccountGetResponse, PlatformLimits, PingResponse, DeployInput, AccountResource, DeploymentResource, DomainResource, TokenResource, ValidatableFile, FileValidationResult } from '@shipstatic/types';
3
- export * from '@shipstatic/types';
4
- export { Account, AccountResource, DEFAULT_API, DeployInput, Deployment, DeploymentResource, Domain, DomainResource, ErrorType, FileValidationStatus as FILE_VALIDATION_STATUS, PingResponse, ShipError, StaticFile, TokenResource, ValidatableFile } from '@shipstatic/types';
1
+ /**
2
+ * @file Shared TypeScript types, constants, and utilities for the ShipStatic platform.
3
+ * This package is the single source of truth for all shared data structures.
4
+ */
5
+ /**
6
+ * Deployment status constants
7
+ */
8
+ declare const DeploymentStatus: {
9
+ readonly PENDING: "pending";
10
+ readonly SUCCESS: "success";
11
+ readonly FAILED: "failed";
12
+ readonly DELETING: "deleting";
13
+ };
14
+ type DeploymentStatusType = (typeof DeploymentStatus)[keyof typeof DeploymentStatus];
15
+ /**
16
+ * Which client made a deployment — the origin-tracking vocabulary.
17
+ *
18
+ * A closed set with many authors: the CLI, the SDK, the dashboard, both MCP
19
+ * transports, the GitHub Action, the n8n node and the VS Code extension each
20
+ * name themselves here. It lived in the API's config until 2026-08-06, where
21
+ * being server-side made it unenforceable in the one direction that matters —
22
+ * every client wrote a bare string, and a value outside the set was **silently
23
+ * dropped** by the server, so a typo did not fail anywhere. It stopped
24
+ * recording where deploys came from and said nothing.
25
+ */
26
+ declare const DeploymentVia: {
27
+ readonly WEB: "web";
28
+ readonly SDK: "sdk";
29
+ readonly CLI: "cli";
30
+ readonly MCP: "mcp";
31
+ readonly GIT: "git";
32
+ readonly N8N: "n8n";
33
+ readonly GPT: "gpt";
34
+ readonly VSC: "vsc";
35
+ };
36
+ type DeploymentViaType = (typeof DeploymentVia)[keyof typeof DeploymentVia];
37
+ /**
38
+ * Core deployment object - used in both API responses and SDK
39
+ */
40
+ interface Deployment {
41
+ /** The deployment hostname (e.g., 'happy-cat-abc1234.shipstatic.com') */
42
+ readonly deployment: string;
43
+ /** Full URL to the deployment (e.g., 'https://happy-cat-abc1234.shipstatic.com') */
44
+ readonly url: string;
45
+ /** Number of files in this deployment */
46
+ readonly files: number;
47
+ /** Total size of all files in bytes */
48
+ readonly size: number;
49
+ /** Current deployment status */
50
+ status: DeploymentStatusType;
51
+ /** Whether deployment has a ship.json config */
52
+ readonly config: boolean;
53
+ /** Whether deployment has a password set */
54
+ readonly password: boolean;
55
+ /** Labels for categorization and filtering (lowercase, alphanumeric with separators). Always present, empty array when none. */
56
+ labels: string[];
57
+ /**
58
+ * The client/tool that created this deployment, null if unknown.
59
+ *
60
+ * Deliberately wider than {@link DeploymentViaType}: this is stored data,
61
+ * and rows predate the vocabulary being closed. Narrowing the ENTITY would
62
+ * be a claim about every row already in the database; narrowing the
63
+ * REQUEST option ({@link DeploymentUploadOptions.via}) is a claim about
64
+ * what a client may send, which is ours to make.
65
+ */
66
+ readonly via: string | null;
67
+ /** Unix timestamp (seconds) when deployment was created */
68
+ readonly created: number;
69
+ /** Unix timestamp (seconds) when deployment expires, null if never */
70
+ expires: number | null;
71
+ /** Full URL to the deployment screenshot (e.g., 'https://screenshots.shipstatic.com/happy-cat-abc1234/a3f2c1b4d5e6f789') */
72
+ readonly screenshot: string;
73
+ }
74
+ /**
75
+ * Response from deployment creation. Extends Deployment with one-time fields
76
+ * only present on creation (not on subsequent GET requests).
77
+ */
78
+ interface DeploymentCreateResponse extends Deployment {
79
+ /** Claim URL for public deployments. Present when deployed without credentials. */
80
+ readonly claim?: string;
81
+ }
82
+ /**
83
+ * The half of a list response that is identical on every list.
84
+ *
85
+ * `GET /<collection>` answers exactly two fields — the collection under its
86
+ * own plural noun, and this cursor — so the cursor is declared once here and
87
+ * each response below adds only its noun. `cursor: null` means last page and
88
+ * is the ENTIRE has-more signal, which is why there is no `has_more`.
89
+ *
90
+ * There is deliberately no `total`. A count is an aggregate over a
91
+ * collection, not a property of a page; producing one would cost a COUNT
92
+ * beside every page read, which is precisely what keyset pagination exists
93
+ * to avoid. Counts live on the resource that summarises the collection —
94
+ * `GET /account`'s `usage` for one caller, `GET /admin/stats` platform-wide.
95
+ */
96
+ interface ListResponse {
97
+ /** Opaque cursor from this page; `null` on the last page. */
98
+ cursor: string | null;
99
+ }
100
+ /**
101
+ * Pagination options for every list endpoint. The response's `cursor` feeds
102
+ * the next request; a `null` cursor means the last page. Omitting both
103
+ * returns the server's default first page.
104
+ *
105
+ * A list answers `{ <collection>, cursor }` and nothing else — `cursor`
106
+ * carries the entire has-more signal, so no redundant boolean, and no
107
+ * `total`. **A count is an aggregate over a collection, not a property of a
108
+ * page:** including one makes every read pay for a full scan it did not ask
109
+ * for, which is precisely the cost keyset pagination exists to avoid.
110
+ *
111
+ * Counts therefore live on the summary resource that owns them —
112
+ * `GET /account` (`usage`) for a caller's own totals, `GET /admin/stats` for
113
+ * platform-wide ones. Ask for a count when you want a count; ask for a page
114
+ * when you want a page.
115
+ */
116
+ interface ListOptions {
117
+ /** Maximum number of items to return in one page. */
118
+ limit?: number;
119
+ /** Opaque cursor from the previous page's response. */
120
+ cursor?: string;
121
+ }
122
+ /**
123
+ * Response for listing deployments
124
+ */
125
+ interface DeploymentListResponse extends ListResponse {
126
+ /** Array of deployments */
127
+ deployments: Deployment[];
128
+ }
129
+ /**
130
+ * Acknowledgement of `DELETE /deployments/:deployment` — and the shape every
131
+ * mutation with no entity left to return follows.
132
+ *
133
+ * **The law:** a mutation answers with the resource it affected. If the
134
+ * resource still exists, that means the entity itself (`Deployment`,
135
+ * `Domain`, …). Otherwise it means this: the resource noun carrying the
136
+ * item's canonical key, plus the resource's own state field — and ONLY when
137
+ * the resource survived in a transitional state, as an async deletion's does.
138
+ * Where the resource is simply gone, the key alone is the whole answer
139
+ * ({@link DomainDeleteResponse}, {@link TokenDeleteResponse}).
140
+ *
141
+ * Put positively: **an acknowledgement is a projection of the resource** —
142
+ * its key, plus its own state field where the state changed. That is the
143
+ * test to apply, and it is sharper than "no constant", which this shape
144
+ * would fail on its own terms: `status` here is the literal `'deleting'` on
145
+ * every success, exactly as fixed as a `changed: true` would be.
146
+ *
147
+ * The difference is not how predictable the value is, it is what the field
148
+ * IS. `status` is the deployment's own field — the same one `GET
149
+ * /deployments/:deployment` returns — so this response is `Deployment`
150
+ * narrowed to two members, and a client renders it with the code it already
151
+ * has. `changed: true`, `queued: true` and `success: true` are not fields of
152
+ * any entity; they exist only to assert that the call worked, which the
153
+ * status code already said. Sync versus accepted is likewise the status
154
+ * code's job — 200 versus 202 — not a boolean's.
155
+ *
156
+ * No prose either (`message`): an acknowledgement is data, and each surface
157
+ * composes its own copy.
158
+ */
159
+ interface DeploymentDeleteResponse {
160
+ /** The deployment hostname that was marked for removal */
161
+ readonly deployment: string;
162
+ /** The state the deployment is in while background cleanup runs */
163
+ readonly status: DeploymentStatusType;
164
+ }
165
+ /**
166
+ * Domain status constants
167
+ *
168
+ * - PENDING: DNS not configured
169
+ * - PARTIAL: DNS partially configured
170
+ * - SUCCESS: DNS fully verified
171
+ * - PAUSED: Domain paused due to plan enforcement (billing)
172
+ */
173
+ declare const DomainStatus: {
174
+ readonly PENDING: "pending";
175
+ readonly PARTIAL: "partial";
176
+ readonly SUCCESS: "success";
177
+ readonly PAUSED: "paused";
178
+ };
179
+ type DomainStatusType = (typeof DomainStatus)[keyof typeof DomainStatus];
180
+ /**
181
+ * Core domain object - used in both API responses and SDK
182
+ */
183
+ interface Domain {
184
+ /** The domain name */
185
+ readonly domain: string;
186
+ /** Full URL to the domain (e.g., 'https://www.example.com') */
187
+ readonly url: string;
188
+ /** The deployment hostname this domain points to (null = domain added but not yet linked) */
189
+ deployment: string | null;
190
+ /** Current domain status */
191
+ status: DomainStatusType;
192
+ /** Labels for categorization and filtering (lowercase, alphanumeric with separators). Always present, empty array when none. */
193
+ labels: string[];
194
+ /** Unix timestamp (seconds) when domain was created */
195
+ readonly created: number;
196
+ /** Unix timestamp (seconds) when deployment was last linked, null if never linked */
197
+ linked: number | null;
198
+ /** Total deployment links */
199
+ links: number;
200
+ }
201
+ /**
202
+ * Return shape of `domains.set()` — `Domain` plus an SDK-derived flag indicating
203
+ * whether the underlying `PUT /domains/:name` created the record (HTTP 201) or
204
+ * updated an existing one (HTTP 200).
205
+ *
206
+ * `isCreate` is not part of the wire format — the API returns a plain `Domain`
207
+ * body. The SDK derives the flag from the HTTP status code so callers (notably
208
+ * the CLI) can format different output for the create vs repoint paths without
209
+ * a second round-trip.
210
+ */
211
+ interface DomainSetResult extends Domain {
212
+ /** `true` when this call created a new domain; `false` when it updated an existing one. */
213
+ isCreate: boolean;
214
+ }
215
+ /**
216
+ * Response for listing domains
217
+ */
218
+ interface DomainListResponse extends ListResponse {
219
+ /** Array of domains */
220
+ domains: Domain[];
221
+ }
222
+ /**
223
+ * Acknowledgement of `DELETE /domains/:domain`. The row is gone, so there is
224
+ * no state to state — the canonical domain name is the whole answer. See
225
+ * {@link DeploymentDeleteResponse} for the law.
226
+ */
227
+ interface DomainDeleteResponse {
228
+ /** The domain name that was removed, normalized */
229
+ readonly domain: string;
230
+ }
231
+ /**
232
+ * Acknowledgement of `POST /domains/:domain/verify` (202). The DNS check is
233
+ * queued, not performed — the accepted status code says so, and the domain's
234
+ * own status is unchanged until the check runs, which is why none is stated
235
+ * here. See {@link DeploymentDeleteResponse} for the law.
236
+ */
237
+ interface DomainVerifyResponse {
238
+ /** The domain whose DNS verification was queued, normalized */
239
+ readonly domain: string;
240
+ }
241
+ /**
242
+ * DNS record types supported for domain configuration
243
+ */
244
+ type DnsRecordType = 'A' | 'CNAME';
245
+ /**
246
+ * DNS record required for domain configuration
247
+ */
248
+ interface DnsRecord {
249
+ /** Record type (A for apex, CNAME for subdomains) */
250
+ type: DnsRecordType;
251
+ /** The DNS name to configure */
252
+ name: string;
253
+ /** The value to set (IP for A, hostname for CNAME) */
254
+ value: string;
255
+ }
256
+ /**
257
+ * DNS provider information for a domain
258
+ */
259
+ interface DnsProvider {
260
+ /** Provider name (e.g., "Cloudflare", "GoDaddy"), null if unknown */
261
+ name: string | null;
262
+ }
263
+ /**
264
+ * Response for domain DNS provider lookup
265
+ */
266
+ /**
267
+ * What a DNS lookup found for a domain. An envelope rather than a bare
268
+ * {@link DnsProvider} because a lookup can succeed and learn more than the
269
+ * provider later; the shape is named so a consumer can hold one.
270
+ */
271
+ interface DnsLookup {
272
+ /** The provider serving this domain's DNS, absent when unidentified */
273
+ provider?: DnsProvider;
274
+ }
275
+ /**
276
+ * A report: it answers a question and carries only the answer (`CLAUDE.md`,
277
+ * "A report answers a question").
278
+ */
279
+ interface DomainDnsResponse {
280
+ /** The domain name */
281
+ domain: string;
282
+ /** DNS provider information, null if not yet looked up */
283
+ dns: DnsLookup | null;
284
+ }
285
+ /**
286
+ * Response for `GET /domains/:domain/share` — the domain plus the salted
287
+ * hash that lets someone else complete its DNS setup without an account.
288
+ *
289
+ * `/admin/domains/:domain/share` answers the same shape, which is the admin
290
+ * law working: the operator surface is the public grammar with a prefix.
291
+ *
292
+ * A report: it answers a question and carries only the answer (`CLAUDE.md`,
293
+ * "A report answers a question").
294
+ */
295
+ interface DomainShareResponse {
296
+ /** The domain the setup link is for */
297
+ readonly domain: string;
298
+ /** The salted setup hash that authorizes the share */
299
+ readonly hash: string;
300
+ }
301
+ /**
302
+ * Response for domain DNS records
303
+ *
304
+ * A report: it answers a question and carries only the answer (`CLAUDE.md`,
305
+ * "A report answers a question").
306
+ */
307
+ interface DomainRecordsResponse {
308
+ /** The domain name */
309
+ domain: string;
310
+ /** The apex (registered) domain where DNS records are managed */
311
+ apex: string;
312
+ /** Required DNS records for configuration */
313
+ records: DnsRecord[];
314
+ }
315
+ /**
316
+ * The envelope an `Idempotency-Key` must fit, and how long a replay lasts.
317
+ *
318
+ * Format lives here rather than on the server alone by the format-vs-policy
319
+ * rule: a client can decide offline whether a key is well-formed, and the
320
+ * API would reject the same value the same way.
321
+ */
322
+ declare const IDEMPOTENCY_KEY_CONSTRAINTS: {
323
+ /**
324
+ * HTTP header name. Here for the same reason {@link CALLER.HEADER} is: a
325
+ * wire header has two ends, and the package that owns the value's format
326
+ * is the only place both ends can read its name from.
327
+ */
328
+ readonly HEADER: "Idempotency-Key";
329
+ readonly MAX_LENGTH: 256;
330
+ /** How long a stored 201 stays replayable. */
331
+ readonly WINDOW_SECONDS: number;
332
+ };
333
+ /**
334
+ * Normalize a `via` value from any transport — trimmed, lowercased, and a
335
+ * member of {@link DeploymentVia}, or `undefined`.
336
+ *
337
+ * A format rule by this package's own test: a client can decide offline
338
+ * whether a value is well-formed, and the API reaches the same verdict on the
339
+ * same input. It lived server-side until 2026-08-06, which meant clients could
340
+ * only learn their label was unusable by noticing analytics had gone quiet.
341
+ *
342
+ * **Not knowing your `via` is not an error** — an unrecognized value yields
343
+ * `undefined` rather than throwing, because origin tracking is telemetry and a
344
+ * deploy must never fail over it. A caller that has an honest default should
345
+ * prefer it (`normalizeVia(process.env.SHIP_VIA) ?? DeploymentVia.CLI`): the
346
+ * deploy really did come from the CLI, so recording that beats recording
347
+ * nothing.
348
+ */
349
+ declare function normalizeVia(value: unknown): DeploymentViaType | undefined;
350
+ /**
351
+ * Validate an idempotency key, returning the trimmed value or `undefined`
352
+ * when none was supplied. Throws {@link ShipError.validation} when the value
353
+ * cannot be sent — the same verdict the API would reach, reached earlier.
354
+ */
355
+ declare function validateIdempotencyKey(value: unknown): string | undefined;
356
+ /**
357
+ * Response for `GET /labels` — every label in use across the caller's
358
+ * deployments, domains and tokens, grouped and ordered by last use.
359
+ *
360
+ * The one plural noun outside the list contract, deliberately: labels have
361
+ * no identity, no row and no `created`, so there is nothing for a keyset
362
+ * cursor to resume after, and its consumer is an autocomplete that wants the
363
+ * whole set. Bounded by `PAGINATION.GLOBAL_LIMIT` rather than paginated.
364
+ *
365
+ * A report: it answers a question and carries only the answer (`CLAUDE.md`,
366
+ * "A report answers a question").
367
+ */
368
+ interface LabelsResponse {
369
+ readonly labels: string[];
370
+ }
371
+ /**
372
+ * Response for `POST /setup` — the DNS instructions for one domain, written
373
+ * for a human to follow at their registrar.
374
+ *
375
+ * `custom` is the provider-specific walkthrough when the provider is known;
376
+ * `generic` always answers, so a caller never has nothing to show.
377
+ *
378
+ * A report: it answers a question and carries only the answer (`CLAUDE.md`,
379
+ * "A report answers a question").
380
+ */
381
+ interface SetupInstructionsResponse {
382
+ /** The domain the instructions are for — a report names its subject */
383
+ readonly domain: string;
384
+ /** One-line summary of what to do */
385
+ readonly tldr: string;
386
+ /** Provider-specific instructions, null when the provider is unknown */
387
+ readonly custom: string | null;
388
+ /** Provider-agnostic instructions — always present */
389
+ readonly generic: string;
390
+ /** The identified DNS provider, null when unknown */
391
+ readonly provider: string | null;
392
+ }
393
+ /**
394
+ * `POST /domains/validate` — a report answering "is this name usable, and if
395
+ * not, why".
396
+ *
397
+ * An unusable name is a legitimate ANSWER, not a failure, so this is a 200 and
398
+ * the verdict rides the body. `reason` was named `error` until 2026-07-29,
399
+ * which collided with {@link ErrorResponse}'s reserved key — there `error` is
400
+ * an `ErrorType` a client branches on, here it is prose a client displays, and
401
+ * one key cannot mean both. See {@link DeploymentDeleteResponse} for the law.
402
+ */
403
+ interface DomainValidateResponse {
404
+ /** Whether the domain is valid */
405
+ valid: boolean;
406
+ /** Normalized domain name, null when invalid */
407
+ normalized: string | null;
408
+ /** Whether the domain is available, null when invalid */
409
+ available: boolean | null;
410
+ /** Why the name is unusable, null when valid — displayed verbatim. */
411
+ reason: string | null;
412
+ }
413
+ /**
414
+ * Core deploy token object - used in both API responses and SDK.
415
+ *
416
+ * The secret is never here: it is shown once at creation
417
+ * ({@link TokenCreateResponse.secret}) and never again, so an entity read
418
+ * carries only the management identifier and lifecycle metadata.
419
+ */
420
+ interface Token {
421
+ /** 7-char management identifier (e.g., "a1b2c3d") */
422
+ readonly token: string;
423
+ /** Labels for categorization and filtering. Always present, empty array when none. */
424
+ labels: string[];
425
+ /** Unix timestamp (seconds) when token was created */
426
+ readonly created: number;
427
+ /** Unix timestamp (seconds) when token expires, null for never */
428
+ readonly expires: number | null;
429
+ /** Unix timestamp (seconds) of the last request authenticated with this token, null if never used */
430
+ readonly used: number | null;
431
+ }
432
+ /**
433
+ * Response for listing tokens
434
+ */
435
+ interface TokenListResponse extends ListResponse {
436
+ /** Array of tokens (the secret is never among them) */
437
+ tokens: Token[];
438
+ }
439
+ /**
440
+ * Response from token creation. Extends Token with the one field that
441
+ * exists only on creation — the same shape as
442
+ * {@link DeploymentCreateResponse}, because a 201 returns the resource it
443
+ * created plus whatever is knowable only once.
444
+ */
445
+ interface TokenCreateResponse extends Token {
446
+ /** The raw credential value (shown once at creation, then never again) */
447
+ readonly secret: string;
448
+ }
449
+ /**
450
+ * Acknowledgement of `DELETE /tokens/:token`. The credential is revoked and
451
+ * its row is gone, so the management identifier is the whole answer. See
452
+ * {@link DeploymentDeleteResponse} for the law.
453
+ */
454
+ interface TokenDeleteResponse {
455
+ /** The 7-char management identifier that was revoked */
456
+ readonly token: string;
457
+ }
458
+ /**
459
+ * Account plan constants
460
+ */
461
+ declare const AccountPlan: {
462
+ readonly FREE: "free";
463
+ readonly STANDARD: "standard";
464
+ readonly SPONSORED: "sponsored";
465
+ readonly ENTERPRISE: "enterprise";
466
+ readonly SUSPENDED: "suspended";
467
+ readonly TERMINATING: "terminating";
468
+ readonly TERMINATED: "terminated";
469
+ };
470
+ type AccountPlanType = (typeof AccountPlan)[keyof typeof AccountPlan];
471
+ /**
472
+ * Account usage metrics — always available regardless of billing provider.
473
+ *
474
+ * This is where a caller's own totals live. Lists answer pages and carry no
475
+ * `total` (see {@link ListOptions}); a count is an aggregate over a
476
+ * collection, so it belongs to the summary resource that owns the
477
+ * collection. `GET /account` is that resource for one caller, `GET
478
+ * /admin/stats` for the platform.
479
+ *
480
+ * The counted dimensions are the ones the plan caps — deployments and
481
+ * domains (`PlatformLimits`) — plus the billable custom-domain subset, so a
482
+ * surface can render "3 of 10" without a second request.
483
+ */
484
+ interface AccountUsage {
485
+ /** Number of active custom domains (excludes paused) */
486
+ customDomains: number;
487
+ /**
488
+ * Deployments counted against the plan's deployment cap — every row
489
+ * whatever its status, because that is what the cap counts, so a surface
490
+ * renders "3 of 10" against the denominator the 403 divides by. (`GET
491
+ * /deployments` lists successful ones only; that is a different question
492
+ * asked of a different resource.) Optional by the additive-evolution law:
493
+ * an API predating this field omits it.
494
+ */
495
+ deployments?: number;
496
+ /**
497
+ * Domains counted against the plan's domain cap — every domain, platform
498
+ * and custom alike, unlike `customDomains`. Optional for the same reason.
499
+ */
500
+ domains?: number;
501
+ }
502
+ /**
503
+ * Core account object - used in both API responses and SDK
504
+ * All fields are readonly to prevent accidental mutations
505
+ */
506
+ interface Account {
507
+ /** User email address */
508
+ readonly email: string;
509
+ /** User display name, null if not set */
510
+ readonly name: string | null;
511
+ /** User profile picture URL, null if not set */
512
+ readonly picture: string | null;
513
+ /** Account plan status */
514
+ readonly plan: AccountPlanType;
515
+ /** Account usage metrics (custom domains, etc.) */
516
+ readonly usage: AccountUsage;
517
+ /** Unix timestamp (seconds) when account was created */
518
+ readonly created: number;
519
+ /** Unix timestamp (seconds) when account was activated (first deployment), null if not yet activated */
520
+ readonly activated: number | null;
521
+ /** Last 4 characters of the API key for identification, null when no key generated */
522
+ readonly hint: string | null;
523
+ /**
524
+ * Unix timestamp (seconds) of the API key's last use, null when never
525
+ * used or no key generated. Optional on the type by the additive-evolution
526
+ * law: published SDK versions may predate the field, so consumers read it
527
+ * when present rather than forcing a lockstep SDK release.
528
+ */
529
+ readonly used?: number | null;
530
+ /** Grace period expiration (unix seconds), null if no grace period active */
531
+ readonly grace: number | null;
532
+ }
533
+ /**
534
+ * Account as returned by `GET /account` — the entity plus how the request
535
+ * was authorized, so `whoami` can answer "what credential am I holding?".
536
+ * Request-scoped fields live on the response type, never on the entity
537
+ * (the `DeploymentCreateResponse` pattern).
538
+ */
539
+ interface AccountGetResponse extends Account {
540
+ /** How the request that produced this response was authorized. */
541
+ readonly authMethod: AuthMethodType;
542
+ /** Present (and true) only when the caller is an operator acting as themselves. */
543
+ readonly isAdmin?: true;
544
+ /** Present only during read-only admin impersonation: the operator's account id. */
545
+ readonly impersonatedBy?: string;
546
+ }
547
+ /**
548
+ * Acknowledgement of `DELETE /account` (202). Termination is asynchronous —
549
+ * a cleanup consumer finishes the job — so the account survives long enough
550
+ * to state the plan it is transitioning through. `plan` is the account's
551
+ * state field, the way `status` is a deployment's. See
552
+ * {@link DeploymentDeleteResponse} for the law.
553
+ */
554
+ interface AccountDeleteResponse {
555
+ /** The account that was marked for termination */
556
+ readonly account: string;
557
+ /** The plan the account is in while cleanup runs */
558
+ readonly plan: AccountPlanType;
559
+ }
560
+ /**
561
+ * Response from `PUT /account/key` — the account's single API key, minted in
562
+ * place of whatever was there before.
563
+ *
564
+ * There is no entity to return: only the key's last-4 `hint` is durable
565
+ * (`Account.hint`), and the plaintext exists exactly once, in this response.
566
+ * The raw credential is `secret` on every surface that mints one — the same
567
+ * field `TokenCreateResponse` carries — because one concept gets one name.
568
+ *
569
+ * A report: it answers a question and carries only the answer (`CLAUDE.md`,
570
+ * "A report answers a question").
571
+ */
572
+ interface AccountKeyResponse {
573
+ /** The raw API key (shown once at mint, then never again) */
574
+ readonly secret: string;
575
+ }
576
+ /**
577
+ * Account-specific configuration overrides
578
+ * Allows per-account customization of limits without changing plan
579
+ */
580
+ interface AccountOverrides {
581
+ /** Override for maximum number of domains */
582
+ domains?: number;
583
+ /** Override for maximum number of deployments */
584
+ deployments?: number;
585
+ /** Override for maximum individual file size in bytes */
586
+ fileSize?: number;
587
+ /** Override for maximum number of files per deployment */
588
+ filesCount?: number;
589
+ /** Override for maximum total deployment size in bytes */
590
+ totalSize?: number;
591
+ }
592
+ /**
593
+ * Every path the public API answers on, declared once.
594
+ *
595
+ * The URL surface was written out in four places — the API's mounts, the
596
+ * SDK's client, the dashboard's client, and the post-deploy smoke — so a
597
+ * rename meant finding all four. The first three now read this table.
598
+ *
599
+ * The smoke (`cloudflare/api/smoke.mjs`) deliberately still spells its own:
600
+ * five of its nine paths are `/admin/*`, which this table excludes by
601
+ * design, and splitting one list between a registry and literals reads worse
602
+ * than keeping it uniform.
603
+ *
604
+ * **What this guarantees, exactly.** Collection paths are mounted from here,
605
+ * so producer and consumer cannot diverge. Item paths are declared here and
606
+ * consumed by clients, but the API spells them relative to their mount
607
+ * (`/:deployment/config`), so the table does not *generate* them — it is
608
+ * held to them by `api/tests/architecture/api-paths.test.ts`, which fails if
609
+ * any entry names a path no route answers. Some entries have no client yet
610
+ * (`DEPLOYMENT_CONFIG`, `DOMAIN_PROPAGATION` — endpoints the SDK
611
+ * deliberately does not reach); the fence is what keeps those honest rather
612
+ * than merely asserted.
613
+ *
614
+ * **The operator surface is deliberately absent.** `/admin/*` paths belong
615
+ * to `web/my`, for the same reason its row types do: this package is
616
+ * published, and the operator surface is not public (see `CLAUDE.md`, "Admin
617
+ * types"). A path here is a promise to every npm consumer; `/admin` is a
618
+ * promise to one dashboard.
619
+ *
620
+ * Item paths are functions rather than templates so the key is interpolated
621
+ * in one place, encoded the same way by every caller.
622
+ */
623
+ declare const API_PATHS: {
624
+ readonly DEPLOYMENTS: "/deployments";
625
+ readonly DEPLOYMENT: (deployment: string) => string;
626
+ readonly DEPLOYMENT_CONFIG: (deployment: string) => string;
627
+ readonly DOMAINS: "/domains";
628
+ readonly DOMAIN: (domain: string) => string;
629
+ readonly DOMAIN_VERIFY: (domain: string) => string;
630
+ readonly DOMAIN_DNS: (domain: string) => string;
631
+ readonly DOMAIN_RECORDS: (domain: string) => string;
632
+ readonly DOMAIN_SHARE: (domain: string) => string;
633
+ readonly DOMAIN_PROPAGATION: (domain: string) => string;
634
+ readonly DOMAINS_VALIDATE: "/domains/validate";
635
+ readonly TOKENS: "/tokens";
636
+ readonly TOKEN: (token: string) => string;
637
+ readonly ACCOUNT: "/account";
638
+ readonly ACCOUNT_KEY: "/account/key";
639
+ readonly ACCOUNT_CLAIM: "/account/claim";
640
+ readonly ACTIVITIES: "/activities";
641
+ readonly LABELS: "/labels";
642
+ readonly LIMITS: "/limits";
643
+ readonly PING: "/ping";
644
+ readonly SETUP: "/setup";
645
+ readonly SPA_CHECK: "/spa-check";
646
+ readonly UPLOAD: "/upload";
647
+ };
648
+ /**
649
+ * The deploy request's multipart field names — the other half of the wire
650
+ * surface beside {@link API_PATHS}. `POST /deployments` (and the first-party
651
+ * `/upload`) is multipart/form-data, and these are the names the API reads.
652
+ *
653
+ * Declared once because the body has three independent WRITERS — the SDK's
654
+ * Node and browser body builders, and the n8n community node's hand-rolled
655
+ * client (which cannot import this under n8n Cloud's zero-dependency rule,
656
+ * and fences its restated copy instead) — and until this export every writer
657
+ * restated the strings the API parses, with nothing comparing them.
658
+ *
659
+ * `FILES` carries one entry per file (the API reads it with `getAll`); every
660
+ * other field is single. The `@internal` flags are serialized as the literal
661
+ * string `'true'` and belong to first-party surfaces only.
662
+ */
663
+ declare const DEPLOY_FIELDS: {
664
+ /** One entry per file — read with `getAll`. */
665
+ readonly FILES: "files[]";
666
+ /** JSON array of MD5 hex digests, index-aligned with `FILES`. */
667
+ readonly CHECKSUMS: "checksums";
668
+ /** JSON array of label strings. */
669
+ readonly LABELS: "labels";
670
+ /** The deploying surface's {@link DeploymentVia} member. */
671
+ readonly VIA: "via";
672
+ /** Plaintext password — the API hashes it server-side. */
673
+ readonly PASSWORD: "password";
674
+ /** @internal Server-processing flag — first-party `/upload` only. */
675
+ readonly BUILD: "build";
676
+ /** @internal Server-processing flag — first-party `/upload` only. */
677
+ readonly PRERENDER: "prerender";
678
+ /** @internal Server-processing flag — first-party `/upload` only. */
679
+ readonly SPA: "spa";
680
+ /** @internal reCAPTCHA proof — `web/www`'s public uploader only. */
681
+ readonly CAPTCHA: "captcha";
682
+ };
683
+ /**
684
+ * All possible error types in the ShipStatic platform.
685
+ *
686
+ * Developer-friendly key names map to stable wire-format string values.
687
+ * Both the value and the type are exported under the same name so callers
688
+ * can use `ErrorType.Validation` (value comparison) and `: ErrorType` (type
689
+ * annotation) without ceremony — matching the pattern other status objects
690
+ * (`DeploymentStatus`, `DomainStatus`, `AccountPlan`, `AuthMethod`) follow.
691
+ */
692
+ declare const ErrorType: {
693
+ /**
694
+ * Validation failed. Input shape is wrong.
695
+ *
696
+ * Carries 400 when an API judged it — including a client-side pre-check of a
697
+ * rule the server enforces too, which keeps the error identical wherever it
698
+ * was caught. **Statusless** when a client rejects something no API judges,
699
+ * such as a CLI's own command grammar: `status` is documented "(API
700
+ * contexts)" on `ErrorResponse`, so there is none to report.
701
+ */
702
+ readonly Validation: "validation_failed";
703
+ /** Resource not found (404). */
704
+ readonly NotFound: "not_found";
705
+ /** Authenticated but not allowed (403). User lacks permission for this action. */
706
+ readonly Forbidden: "forbidden";
707
+ /** Rate limit exceeded (429). */
708
+ readonly RateLimit: "rate_limit_exceeded";
709
+ /** Authentication required or failed (401). Missing/invalid credentials. */
710
+ readonly Authentication: "authentication_failed";
711
+ /** Business rule violation. Catch-all for 4xx state-rule errors that aren't more specific. */
712
+ readonly Business: "business_logic_error";
713
+ /** API server error (500). Generic server-side fault. */
714
+ readonly Api: "internal_server_error";
715
+ /** Network/connection error. Client-side only — set by HTTP clients on fetch failure; never produced server-side. */
716
+ readonly Network: "network_error";
717
+ /** Operation was cancelled. Client-side only — set on `AbortSignal` abort; never produced server-side. */
718
+ readonly Cancelled: "operation_cancelled";
719
+ /** File operation error. Client-side only — set by SDK during local file processing; never produced server-side. */
720
+ readonly File: "file_error";
721
+ /** Configuration error. Client-side only — set by SDK during config parsing/validation; never produced server-side. */
722
+ readonly Config: "config_error";
723
+ };
724
+ type ErrorType = (typeof ErrorType)[keyof typeof ErrorType];
725
+ /**
726
+ * Standard error response format used everywhere
727
+ */
728
+ interface ErrorResponse {
729
+ /** Error type identifier */
730
+ error: ErrorType;
731
+ /** Human-readable error message */
732
+ message: string;
733
+ /** HTTP status code (API contexts) */
734
+ status?: number;
735
+ /** Optional additional error details. Untyped by design — narrow at the read site. */
736
+ details?: unknown;
737
+ }
738
+ /**
739
+ * Simple unified error class for both API and SDK
740
+ */
741
+ declare class ShipError extends Error {
742
+ readonly type: ErrorType;
743
+ readonly status?: number | undefined;
744
+ readonly details?: unknown | undefined;
745
+ constructor(type: ErrorType, message: string, status?: number | undefined, details?: unknown | undefined);
746
+ /** Convert to wire format */
747
+ toResponse(): ErrorResponse;
748
+ /**
749
+ * Construct a `ShipError` from an HTTP error response.
750
+ *
751
+ * Best-effort body parse for `{ message, error?, details? }`. Message
752
+ * resolution: `body.message` → `body.error` → `"<operationName> failed with
753
+ * status <N>"`.
754
+ *
755
+ * Type resolution: trusts `body.error` when it's a known server-producible
756
+ * `ErrorType` (preserves the wire's intent — server's
757
+ * `ShipError.validation(...)` round-trips back to `ErrorType.Validation`
758
+ * on the client). Falls back to status-derived (401 → Authentication,
759
+ * 403 → Forbidden, 429 → RateLimit, else → Api) for non-API responses
760
+ * (CDN errors, intermediaries) or malformed bodies. Client-only types
761
+ * (`Network`, `Cancelled`, `File`, `Config`) are filtered out of the
762
+ * trusted set — a misbehaving server claiming one of those is ignored.
763
+ *
764
+ * `operationName` (e.g. `"Get account"`) is used to compose the fallback
765
+ * message. Defaults to `"Request"`. Same convention as `fromFetchError`.
766
+ *
767
+ * Async because it reads the response body. Returns rather than throws so
768
+ * callers can compose; most will `throw await ShipError.fromHttpResponse(...)`.
769
+ */
770
+ static fromHttpResponse(response: Response, operationName?: string): Promise<ShipError>;
771
+ /**
772
+ * Construct a `ShipError` from an error caught around a `fetch()` call.
773
+ *
774
+ * The mirror of `fromHttpResponse` for the *other* side of the HTTP error
775
+ * story — the network layer failing (offline, CORS, abort) rather than the
776
+ * server returning a non-OK response.
777
+ *
778
+ * Routing:
779
+ * - Already a `ShipError` → returned as-is (caller's intent preserved)
780
+ * - `AbortError` → `ShipError.cancelled(...)`
781
+ * - A transport failure → `ShipError.network(...)` — see `isTransportFailure`
782
+ * for what each runtime offers as evidence
783
+ * - Any other `Error` → `ShipError(Api, ...)` (no HTTP status — fetch never reached the server)
784
+ * - Anything else (string, undefined, etc.) → `ShipError(Api, ...)`
785
+ *
786
+ * The optional `operationName` is composed into the message for context:
787
+ * `"Get account was cancelled"`, `"Get account failed: ..."`. Defaults to
788
+ * `"Request"` when omitted.
789
+ */
790
+ static fromFetchError(cause: unknown, operationName?: string): ShipError;
791
+ static validation(message: string, details?: unknown): ShipError;
792
+ static notFound(resource: string, id?: string): ShipError;
793
+ static forbidden(message: string, details?: unknown): ShipError;
794
+ static rateLimit(message?: string, details?: unknown): ShipError;
795
+ /**
796
+ * Construct an Authentication (401) error.
797
+ *
798
+ * **Telemetry pattern — `details: { internal: '<tag>' }`.** When the
799
+ * server creates an auth error with an `internal` key in `details`
800
+ * (e.g. `{ internal: 'session_invalid' }`), `toResponse()` strips the
801
+ * entire `details` object before serialization. This keeps the wire
802
+ * response a clean "Authentication failed" while preserving granular
803
+ * server-side telemetry (which strategy/check failed) for logs and tests.
804
+ *
805
+ * Use this pattern in API auth code; do not put client-visible info under
806
+ * `internal`. Other `details` keys round-trip normally.
807
+ */
808
+ static authentication(message?: string, details?: unknown): ShipError;
809
+ static business(message: string, status?: number, details?: unknown): ShipError;
810
+ static network(message: string, details?: unknown): ShipError;
811
+ static cancelled(message: string, details?: unknown): ShipError;
812
+ static file(message: string, details?: unknown): ShipError;
813
+ static config(message: string, details?: unknown): ShipError;
814
+ static api(message: string, status?: number, details?: unknown): ShipError;
815
+ /**
816
+ * The caller is at fault — by HTTP's own definition of a 4xx, or by a type
817
+ * that is client-attributable without ever having a status (`Config`,
818
+ * `File`, raised locally by the SDK).
819
+ *
820
+ * Both arms are load-bearing, because type and status are independent
821
+ * axes. `fromHttpResponse` trusts `body.error` only when it names a
822
+ * server-producible type; a non-OK response without one is status-derived,
823
+ * so a CDN 404 or any intermediary error arrives as `Api` — a server-fault
824
+ * *type* carrying a client *status*. Judging by type alone would report it
825
+ * as a platform failure and bury the server's own message.
826
+ */
827
+ isClientError(): boolean;
828
+ isNetworkError(): boolean;
829
+ isAuthError(): boolean;
830
+ isType(errorType: ErrorType): boolean;
831
+ }
832
+ /**
833
+ * Type guard to check if an unknown value is a ShipError.
834
+ *
835
+ * Uses structural checking instead of instanceof to handle module duplication
836
+ * in bundled applications where multiple copies of the ShipError class may exist.
837
+ *
838
+ * @example
839
+ * if (isShipError(error)) {
840
+ * console.log(error.status, error.message);
841
+ * }
842
+ */
843
+ declare function isShipError(error: unknown): error is ShipError;
844
+ /**
845
+ * Plan-based platform limits returned by the `/limits` endpoint.
846
+ *
847
+ * The SDK fetches these once on first API call to drive client-side
848
+ * file-size / file-count / total-size validation that mirrors what the API
849
+ * would enforce server-side. Limits vary by account plan.
850
+ *
851
+ * These are the *platform's* posted caps for the current account — server
852
+ * truth delivered at runtime, never hard-coded on the client.
853
+ *
854
+ * A report: it answers a question and carries only the answer (`CLAUDE.md`,
855
+ * "A report answers a question").
856
+ */
857
+ interface PlatformLimits {
858
+ /** Maximum size in bytes for a single file. */
859
+ maxFileSize: number;
860
+ /** Maximum number of files in a single deployment. */
861
+ maxFilesCount: number;
862
+ /** Maximum total size in bytes across all files in a deployment. */
863
+ maxTotalSize: number;
864
+ }
865
+ /**
866
+ * Blocked file extensions — files that cannot be uploaded.
867
+ *
868
+ * We accept any file type by default and derive Content-Type from the
869
+ * extension at serve time (via mime-db in the API worker). Unknown extensions
870
+ * are served as `application/octet-stream` with `X-Content-Type-Options: nosniff`.
871
+ *
872
+ * The blocklist targets file types that pose direct security risks when hosted:
873
+ * executables, disk images, malware vectors, dangerous scripts, and shortcuts.
874
+ */
875
+ declare const BLOCKED_EXTENSIONS: ReadonlySet<string>;
876
+ /**
877
+ * Check if a filename has a blocked extension.
878
+ * Extracts the extension from the filename and checks against the blocklist.
879
+ * Case-insensitive. Returns false for files without extensions.
880
+ *
881
+ * @example
882
+ * isBlockedExtension('virus.exe') // true
883
+ * isBlockedExtension('app.dmg') // true
884
+ * isBlockedExtension('style.css') // false
885
+ * isBlockedExtension('data.custom') // false
886
+ * isBlockedExtension('README') // false
887
+ */
888
+ declare function isBlockedExtension(filename: string): boolean;
889
+ /**
890
+ * The `accept` attribute value for a browser file picker offering web files.
891
+ *
892
+ * **This is a hint, never a rule.** `BLOCKED_EXTENSIONS` is the platform's
893
+ * gate and the only thing that decides what may be hosted; this constant
894
+ * decides what a *file dialog* shows first. The two are not two halves of one
895
+ * policy, and this one must never be consulted to accept or reject a file.
896
+ *
897
+ * The distinction is structural, not stylistic. `accept` can express only an
898
+ * allowlist, while the platform's rule is a blocklist — so this list is
899
+ * necessarily *narrower* than what the platform hosts, and reading it as
900
+ * authority would reject files the platform serves happily. It is also not
901
+ * enforcement in the browser's own terms: every file dialog offers an
902
+ * all-files escape, and **drag-and-drop ignores `accept` entirely**. The
903
+ * dropzone and the picker must reach the same verdict on the same files, and
904
+ * they do — because the verdict is `validateFiles`, downstream of both.
905
+ *
906
+ * Kept beside `BLOCKED_EXTENSIONS` so one file holds both, which is what lets
907
+ * `tests/validation-constants.test.ts` fence the invariant that matters: the
908
+ * picker must never offer a file the platform will refuse.
909
+ */
910
+ declare const WEB_FILE_ACCEPT: string;
911
+ /**
912
+ * Characters that are unsafe in filenames for static hosting.
913
+ *
914
+ * Blocks only characters that genuinely break the upload→serve round-trip:
915
+ * - # ? % URL round-trip breakers (fragment, query, encoding ambiguity)
916
+ * - \ Path separator confusion (upload splits on backslash)
917
+ * - < > " XSS vectors with zero legitimate use in filenames
918
+ * - \x00-\x1f \x7f Control characters (header injection, display corruption)
919
+ *
920
+ * Everything else is allowed — browser percent-encodes, Worker decodes, R2 matches.
921
+ */
922
+ declare const UNSAFE_FILENAME_CHARS: RegExp;
923
+ /**
924
+ * Check if a filename contains unsafe characters.
925
+ *
926
+ * @example
927
+ * hasUnsafeChars('saved_resource(1).html') // false — parentheses are safe
928
+ * hasUnsafeChars('page[slug].js') // false — brackets are safe
929
+ * hasUnsafeChars('file#anchor.html') // true — # breaks URL resolution
930
+ * hasUnsafeChars('file<tag>.html') // true — < is an XSS vector
931
+ */
932
+ declare function hasUnsafeChars(filename: string): boolean;
933
+ /**
934
+ * Path segment names that indicate an unbuilt project was uploaded instead of build output.
935
+ * Used for early detection in CLI, browser, and server validation.
936
+ */
937
+ declare const UNBUILT_PROJECT_MARKERS: ReadonlySet<string>;
938
+ /**
939
+ * Check if a file path contains an unbuilt project marker.
940
+ *
941
+ * @example
942
+ * hasUnbuiltMarker('node_modules/react/index.js') // true
943
+ * hasUnbuiltMarker('package.json') // true
944
+ * hasUnbuiltMarker('dist/index.html') // false
945
+ */
946
+ declare function hasUnbuiltMarker(filePath: string): boolean;
947
+ /**
948
+ * `GET /ping` — a report of the server clock.
949
+ *
950
+ * Liveness is the STATUS CODE's answer, not a field's: a 200 means reachable,
951
+ * and any other outcome throws before a body is read. So the body carries the
952
+ * one thing a status code cannot — the server's own clock, which is what lets a
953
+ * client detect skew against a token expiry. It read `{ success: true,
954
+ * timestamp? }` until 2026-07-29, where `success` was a literal constant in the
955
+ * route (zero bits, and the platform's own named anti-pattern) while the field
956
+ * that IS the payload was optional. See {@link DeploymentDeleteResponse} for
957
+ * the law, and `tests/response-shapes.test.ts` for the fence that holds it.
958
+ */
959
+ interface PingResponse {
960
+ /** Server time in unix seconds — the one wire unit for timestamps. */
961
+ readonly timestamp: number;
962
+ }
963
+ /**
964
+ * Where human identity is mounted on the API host. The API mounts Better
965
+ * Auth at this path (sign-in, sign-out, session reads, admin impersonation)
966
+ * and the web console's auth client posts to it — shared here so the two
967
+ * halves of the auth pair agree by construction, the same way both sides
968
+ * already share the credential prefixes below.
969
+ */
970
+ declare const AUTH_BASE_PATH = "/auth";
971
+ /**
972
+ * How a request (or recorded activity) was authorized.
973
+ *
974
+ * Client populations: `SESSION` (first-party cookie), `API_KEY` (`ship-`
975
+ * key), `TOKEN` (`deploy-` deploy token), `AGENT` (anonymous public deploy —
976
+ * no credential; the platform grants the public-account identity per
977
+ * request), `OAUTH` (delegated access token). Server populations: `WEBHOOK`
978
+ * (signed webhook processing), `SYSTEM` (scheduled/background jobs).
979
+ */
980
+ declare const AuthMethod: {
981
+ readonly SESSION: "session";
982
+ readonly API_KEY: "apiKey";
983
+ readonly TOKEN: "token";
984
+ readonly AGENT: "agent";
985
+ readonly OAUTH: "oauth";
986
+ readonly WEBHOOK: "webhook";
987
+ readonly SYSTEM: "system";
988
+ };
989
+ type AuthMethodType = (typeof AuthMethod)[keyof typeof AuthMethod];
990
+ /**
991
+ * Shape constants for API keys (`ship-{64 hex chars}`).
992
+ * Single source of truth used by validation utilities and auth middleware.
993
+ */
994
+ declare const API_KEY: {
995
+ /** Prefix that identifies an API key. */
996
+ readonly PREFIX: "ship-";
997
+ /** Number of hex characters following the prefix. */
998
+ readonly HEX_LENGTH: 64;
999
+ /** Total length of an API key including prefix (`PREFIX.length + HEX_LENGTH = 69`). */
1000
+ readonly TOTAL_LENGTH: 69;
1001
+ /** Number of trailing characters used to display a redacted hint (e.g. last 4). */
1002
+ readonly HINT_LENGTH: 4;
1003
+ };
1004
+ /**
1005
+ * Shape constants for deploy tokens (`deploy-{64 hex chars}`).
1006
+ * Single source of truth used by validation utilities and auth middleware.
1007
+ */
1008
+ declare const DEPLOY_TOKEN: {
1009
+ /** Prefix that identifies a deploy token. */
1010
+ readonly PREFIX: "deploy-";
1011
+ /** Number of hex characters following the prefix. */
1012
+ readonly HEX_LENGTH: 64;
1013
+ /** Total length of a deploy token including prefix (`PREFIX.length + HEX_LENGTH = 71`). */
1014
+ readonly TOTAL_LENGTH: 71;
1015
+ };
1016
+ /**
1017
+ * Shape constants for caller identifiers (the `X-Caller` instance-identity
1018
+ * header — rate-limit bucketing for multi-tenant orchestrators). The API
1019
+ * normalizes case and silently ignores malformed values (the header is
1020
+ * unauthenticated); clients validate at the boundary via `validateCaller`,
1021
+ * so a value the server would drop fails fast instead.
1022
+ */
1023
+ declare const CALLER: {
1024
+ /** HTTP header name. */
1025
+ readonly HEADER: "X-Caller";
1026
+ /** Maximum identifier length. */
1027
+ readonly MAX_LENGTH: 128;
1028
+ /** Allowed characters: alphanumeric, dot, underscore, hyphen. */
1029
+ readonly PATTERN: RegExp;
1030
+ };
1031
+ /**
1032
+ * Token populations distinguishable by shape. The platform carries every
1033
+ * client token in one wire slot (`Authorization: Bearer <value>`) and
1034
+ * classifies by value, never by a side channel — this is the classifier.
1035
+ *
1036
+ * `API_KEY` and `DEPLOY_TOKEN` *are* `AuthMethod.API_KEY` and
1037
+ * `AuthMethod.TOKEN` — the equality is structural, so a classification flows
1038
+ * straight into an auth method and the pair can never drift. `OPAQUE` is any
1039
+ * other value — shape says nothing about it, so only a lookup can. Today the
1040
+ * server refuses every opaque bearer; the OAuth access-token population
1041
+ * resolves there when the authorization server ships.
1042
+ */
1043
+ declare const TokenKind: {
1044
+ readonly API_KEY: "apiKey";
1045
+ readonly DEPLOY_TOKEN: "token";
1046
+ readonly OPAQUE: "opaque";
1047
+ };
1048
+ type TokenKindType = (typeof TokenKind)[keyof typeof TokenKind];
1049
+ /**
1050
+ * Classify a client token by shape. The single dispatch used by both sides
1051
+ * of the wire: API auth middleware (which population is this credential?)
1052
+ * and SDK validation (which format rules apply before sending?). Sharing it
1053
+ * is what guarantees client and server can never disagree on dispatch.
1054
+ */
1055
+ declare function classifyToken(token: string): TokenKindType;
1056
+ /**
1057
+ * OAuth scope vocabulary for delegated third-party access tokens.
1058
+ * Single source of truth used by the authorization server (advertised in
1059
+ * `scopes_supported`), the API's scope-enforcement middleware, and consent UI
1060
+ * copy. The standard `offline_access` scope (refresh tokens) is not platform
1061
+ * vocabulary and is deliberately absent — the middleware never checks it.
1062
+ *
1063
+ * Deliberately absent by design: any `tokens:*` scope, `account:write`, or
1064
+ * admin scope — a delegated app must never mint credentials, delete the
1065
+ * account, or act as admin.
1066
+ */
1067
+ declare const OAuthScope: {
1068
+ readonly ACCOUNT_READ: "account:read";
1069
+ readonly DEPLOYMENTS_READ: "deployments:read";
1070
+ readonly DEPLOYMENTS_WRITE: "deployments:write";
1071
+ readonly DOMAINS_READ: "domains:read";
1072
+ readonly DOMAINS_WRITE: "domains:write";
1073
+ };
1074
+ type OAuthScopeType = (typeof OAuthScope)[keyof typeof OAuthScope];
1075
+ declare const DEPLOYMENT_CONFIG_FILENAME = "ship.json";
1076
+ /** Default ship.json config for SPA routing. Single source of truth — used by both API and SDK. */
1077
+ declare const SPA_DEFAULT_CONFIG: {
1078
+ readonly rewrites: readonly [{
1079
+ readonly source: "/(.*)";
1080
+ readonly destination: "/index.html";
1081
+ }];
1082
+ };
1083
+ /**
1084
+ * The `/spa-check` pre-flight's client-side envelope: which file is the
1085
+ * check's subject, and how large it may be before a client skips the call.
1086
+ *
1087
+ * One fact with three holders until this export — the API's config declared
1088
+ * the cap, the SDK's `checkSPA` hardcoded `100 * 1024`, and prose restated
1089
+ * "100KB". `INDEX_FILE` is the selection rule (the file whose content rides
1090
+ * `SPACheckRequest.index`), restated by every client that builds the request.
1091
+ *
1092
+ * Neither member is a validation boundary: a client over the cap simply
1093
+ * skips the pre-flight, because the server answers an oversized index
1094
+ * `isSPA: false` anyway. A consumer that cannot import this (n8n) needs no
1095
+ * size copy at all — outcome parity is the server's, not the client's.
1096
+ */
1097
+ declare const SPA_CHECK_CONSTRAINTS: {
1098
+ /** The file whose content is the check's subject. */
1099
+ readonly INDEX_FILE: "index.html";
1100
+ /** Skip the pre-flight above this size — the server would answer false. */
1101
+ readonly MAX_INDEX_BYTES: number;
1102
+ };
1103
+ /**
1104
+ * Assert that a ship.json file is *syntactically* loadable. Syntax only —
1105
+ * never schema.
1106
+ *
1107
+ * ship.json is validated and compiled on the server, deliberately: the schema
1108
+ * and the compiler evolve, and a client that judged them would reject configs
1109
+ * a newer platform accepts. That reasoning bounds what a client may check to
1110
+ * the properties which are true of *every* past and future schema:
1111
+ *
1112
+ * 1. it parses as JSON — JSON syntax is frozen (RFC 8259), so text that
1113
+ * does not parse can never be a valid config;
1114
+ * 2. its top level is an object — ship.json is `{ ... }` in every version.
1115
+ *
1116
+ * Both are monotonic: neither can ever reject something the server would
1117
+ * accept. Everything beyond them (field names, types, rule semantics, which
1118
+ * keys are permitted) stays server-side, where it can change.
1119
+ *
1120
+ * The payoff is the common case. Hand-edited JSON fails on a trailing comma,
1121
+ * a `//` comment, single quotes, unquoted keys, or smart quotes pasted from
1122
+ * documentation — mistakes that otherwise cost a full upload round-trip to
1123
+ * discover. A UTF-8 BOM (Windows editors, PowerShell redirects) is stripped
1124
+ * before parsing rather than rejected, because the server accepts it too;
1125
+ * diverging there would reintroduce exactly the false rejection this
1126
+ * function exists to avoid.
1127
+ *
1128
+ * @throws {ShipError} `ErrorType.Config` — the same type the server's own
1129
+ * config rejection carries, so the error contract is identical wherever the
1130
+ * failure is detected.
1131
+ */
1132
+ declare function assertShipJsonSyntax(text: string): void;
1133
+ /**
1134
+ * Validate API key format
1135
+ */
1136
+ declare function validateApiKey(apiKey: string): void;
1137
+ /**
1138
+ * Validate deploy token format
1139
+ */
1140
+ declare function validateDeployToken(deployToken: string): void;
1141
+ /**
1142
+ * Validate a client token of any population. Classifies by shape and applies
1143
+ * the matching format rules: `ship-` keys and `deploy-` deploy tokens are
1144
+ * validated strictly; opaque tokens (OAuth access tokens, future populations)
1145
+ * only need to be non-empty — their validity is the server's to decide.
1146
+ */
1147
+ declare function validateToken(token: string): void;
1148
+ /**
1149
+ * Validate a caller identifier against the `CALLER` shape. The server
1150
+ * silently ignores malformed values (the header is unauthenticated); clients
1151
+ * call this at configuration time so the drop never silently happens.
1152
+ */
1153
+ declare function validateCaller(caller: string): void;
1154
+ /**
1155
+ * Validate API URL format
1156
+ */
1157
+ declare function validateApiUrl(apiUrl: string): void;
1158
+ /**
1159
+ * Check if a string matches the deployment identifier pattern (word-word-alphanumeric7).
1160
+ * Example: "happy-cat-abc1234.shipstatic.com"
1161
+ */
1162
+ declare function isDeployment(input: string): boolean;
1163
+ /**
1164
+ * Request payload for SPA check endpoint
1165
+ */
1166
+ interface SPACheckRequest {
1167
+ /** Array of file paths */
1168
+ files: string[];
1169
+ /** HTML content of index.html file */
1170
+ index: string;
1171
+ }
1172
+ /**
1173
+ * Response from SPA check endpoint
1174
+ */
1175
+ /**
1176
+ * Which of the classifier's tiers reached the verdict, and why. Named rather
1177
+ * than inline so the API's own `checkSPA` can return `SPACheckResponse`
1178
+ * instead of restating its shape.
1179
+ */
1180
+ interface SPACheckDebug {
1181
+ /** Which tier made the detection */
1182
+ tier: 'exclusions' | 'inclusions' | 'scoring' | 'ai' | 'fallback';
1183
+ /** The reason for the detection result */
1184
+ reason: string;
1185
+ }
1186
+ /**
1187
+ * A report: it answers a question and carries only the answer (`CLAUDE.md`,
1188
+ * "A report answers a question").
1189
+ */
1190
+ interface SPACheckResponse {
1191
+ /** Whether the project is detected as a Single Page Application */
1192
+ isSPA: boolean;
1193
+ /** Debugging information about detection */
1194
+ debug: SPACheckDebug;
1195
+ }
1196
+ /**
1197
+ * Represents a file that has been processed and is ready for deploy.
1198
+ * Used across the platform (API, SDK, CLI) for file operations.
1199
+ */
1200
+ interface StaticFile {
1201
+ /**
1202
+ * The content of the file.
1203
+ * In Node.js, this is typically a `Buffer`.
1204
+ * In the browser, this is typically a `File` or `Blob` object.
1205
+ */
1206
+ content: File | Buffer | Blob;
1207
+ /**
1208
+ * The desired path for the file on the server, relative to the deployment root.
1209
+ * Should include the filename, e.g., `images/photo.jpg`.
1210
+ */
1211
+ path: string;
1212
+ /**
1213
+ * The original absolute file system path (primarily used in Node.js environments).
1214
+ * This helps in debugging or associating the server path back to its source.
1215
+ */
1216
+ filePath?: string;
1217
+ /**
1218
+ * The MD5 hash (checksum) of the file's content.
1219
+ * This is calculated by the SDK before deploy if not provided.
1220
+ */
1221
+ md5?: string;
1222
+ /** The size of the file in bytes. */
1223
+ size: number;
1224
+ }
1225
+ /** Default API URL if not otherwise configured. */
1226
+ declare const DEFAULT_API = "https://api.shipstatic.com";
1227
+ /**
1228
+ * The Node SDK's ambient configuration pair — the ONLY environment variables
1229
+ * the SDK reads, and therefore the COMPLETE list an embedding host must
1230
+ * scrub (per `npm/ship`'s strict-isolation contract, scrubbing is the host's
1231
+ * job, not the SDK's). A host that derives its scrub from this object's
1232
+ * values — as the VS Code extension's child-process env block does — picks
1233
+ * up a grown contract at the next pin bump instead of by remembered prose.
1234
+ *
1235
+ * Browser builds read no environment at all, and the CLI-only variables
1236
+ * (`SHIP_PASSWORD`, `SHIP_VIA`) are deliberately NOT here: they are the
1237
+ * CLI's operational levers, not the SDK's ambient contract — see
1238
+ * `npm/ship/CLAUDE.md`, "CLI-only env vars".
1239
+ */
1240
+ declare const SHIP_ENV: {
1241
+ /** The one credential slot — any platform token. */
1242
+ readonly TOKEN: "SHIP_TOKEN";
1243
+ /** The API endpoint override. */
1244
+ readonly API_URL: "SHIP_API_URL";
1245
+ };
1246
+ /**
1247
+ * Where a human creates an API key — the console deep link quoted by every
1248
+ * surface that teaches authentication (the CLI's config wizard, the VS Code
1249
+ * and n8n listings, the n8n rate-limit hint and credential copy). Written
1250
+ * out in five files across three repos until this export.
1251
+ *
1252
+ * Production-branded by design: published artifacts name the product, never
1253
+ * an environment (root `CLAUDE.md`, "Environment-Aware URLs").
1254
+ */
1255
+ declare const MY_API_KEY_URL = "https://my.shipstatic.com/api-key";
1256
+ /**
1257
+ * How long an anonymous deployment lives before it expires.
1258
+ *
1259
+ * The lifetime of the public tier, and one fact with several readers. The API
1260
+ * stamps a deployment's `expires` from it and gives a claim code exactly the
1261
+ * same window — a live site with a dead claim link is a coherence bug, so the
1262
+ * two are one constant rather than two that agree. Both MCP transports quote
1263
+ * the duration in prose an agent reads, and derive it from here rather than
1264
+ * writing it out, which they did in eight places until this export existed.
1265
+ *
1266
+ * Seconds, spelled in the name: this platform has both second- and
1267
+ * millisecond-valued durations, and the pair is only safe when each says which
1268
+ * it is.
1269
+ */
1270
+ declare const PUBLIC_DEPLOYMENT_TTL_SECONDS: number;
1271
+ /**
1272
+ * Universal deploy input — the union of every shape the SDK accepts.
1273
+ *
1274
+ * - **Browser**: `File[]` (typically from `<input type="file">` or drag-and-drop)
1275
+ * - **Node**: `string | string[]` (file or directory path(s) on disk; directories are walked)
1276
+ *
1277
+ * Each platform's SDK narrows its `deploy()` signature to the relevant shape
1278
+ * and rejects anything else at runtime. Use the structural types directly
1279
+ * (`File[]`, `string | string[]`) when writing platform-specific code.
1280
+ */
1281
+ type DeployInput = File[] | string | string[];
1282
+ /**
1283
+ * Options for deployment creation at the API contract level.
1284
+ * SDK implementations may extend with additional options (timeout, signal, callbacks, etc.).
1285
+ */
1286
+ interface DeploymentUploadOptions {
1287
+ /** Optional labels for categorization and filtering */
1288
+ labels?: string[];
1289
+ /**
1290
+ * Which client is making this deploy. Closed, because the server silently
1291
+ * ignores anything outside the set — so an unchecked string turned a typo
1292
+ * into missing analytics rather than an error. See {@link DeploymentVia}.
1293
+ */
1294
+ via?: DeploymentViaType;
1295
+ /**
1296
+ * Optional password that protects this deployment.
1297
+ *
1298
+ * Length: {@link PASSWORD_CONSTRAINTS.MIN_LENGTH} to
1299
+ * {@link PASSWORD_CONSTRAINTS.MAX_LENGTH} characters. Leading and trailing
1300
+ * whitespace is trimmed before validation; internal whitespace is
1301
+ * significant. Visitors are prompted to enter the password before they can
1302
+ * view the deployment — including on any custom domains pointing at it.
1303
+ * To remove protection, redeploy without a password.
1304
+ */
1305
+ password?: string;
1306
+ /** @internal Trigger server-side build. Only available via /upload endpoint. */
1307
+ build?: boolean;
1308
+ /** @internal Trigger server-side prerender. Only available via /upload endpoint. */
1309
+ prerender?: boolean;
1310
+ /** @internal Trigger server-side SPA detection. Only available via /upload endpoint. */
1311
+ spa?: boolean;
1312
+ /** @internal reCAPTCHA proof for the anonymous human deploy channel. Only available via /upload endpoint. */
1313
+ captcha?: string;
1314
+ /**
1315
+ * Makes this deploy replayable instead of repeatable.
1316
+ *
1317
+ * A deploy is not naturally idempotent: a client-side timeout on a slow
1318
+ * one leaves the caller unable to tell "it never landed" from "it landed
1319
+ * and the response was lost", and retrying produces a second deployment.
1320
+ * Send the same key on the retry and the platform replays the original
1321
+ * 201 verbatim rather than creating anything
1322
+ * ({@link IDEMPOTENCY_KEY_CONSTRAINTS.WINDOW_SECONDS}).
1323
+ *
1324
+ * **Agents are the audience.** A human notices a duplicate; an automated
1325
+ * retry does not. Pick a key that identifies the ATTEMPT — a run id, a
1326
+ * commit sha, a uuid minted before the first try — never one minted fresh
1327
+ * on each retry, which would defeat the point.
1328
+ *
1329
+ * The replay is per-caller, and it stores successes only: a failed deploy
1330
+ * retries fresh under the same key.
1331
+ */
1332
+ idempotencyKey?: string;
1333
+ }
1334
+ /**
1335
+ * What a caller may change on an existing deployment.
1336
+ *
1337
+ * Labels and nothing else: a deployment's content is immutable by design, so
1338
+ * this is the whole mutable surface rather than a subset someone chose.
1339
+ */
1340
+ interface DeploymentSetOptions {
1341
+ labels: string[];
1342
+ }
1343
+ /**
1344
+ * What `domains.set()` may create or change. Every field is optional because
1345
+ * the call is a natural-key upsert: omitting `deployment` reserves the
1346
+ * domain, naming one links or re-points it, and labels travel either way.
1347
+ *
1348
+ * `deployment` is deliberately not nullable — unlinking is refused (400).
1349
+ * See `npm/ship/CLAUDE.md`, "Domain Write Semantics".
1350
+ */
1351
+ interface DomainSetOptions {
1352
+ deployment?: string;
1353
+ labels?: string[];
1354
+ }
1355
+ /** What a caller may set when minting a deploy token. */
1356
+ interface TokenCreateOptions {
1357
+ /** Seconds until expiry; omit for a token that never expires. */
1358
+ ttl?: number;
1359
+ labels?: string[];
1360
+ }
1361
+ /**
1362
+ * Deployment resource interface - the contract all implementations must follow.
1363
+ *
1364
+ * The interface defines the minimal wire contract; SDK implementations may
1365
+ * extend the upload options with runtime concerns (timeout, signal, progress
1366
+ * callbacks) by parameterizing: `DeploymentResource<MyUploadOptions>`. The
1367
+ * default keeps plain `DeploymentResource` valid for wire-only consumers.
1368
+ */
1369
+ interface DeploymentResource<UploadOptions extends DeploymentUploadOptions = DeploymentUploadOptions> {
1370
+ upload: (input: DeployInput, options?: UploadOptions) => Promise<DeploymentCreateResponse>;
1371
+ list: (options?: ListOptions) => Promise<DeploymentListResponse>;
1372
+ get: (id: string) => Promise<Deployment>;
1373
+ set: (id: string, options: DeploymentSetOptions) => Promise<Deployment>;
1374
+ delete: (id: string) => Promise<DeploymentDeleteResponse>;
1375
+ }
1376
+ /**
1377
+ * Domain resource interface - the contract all implementations must follow
1378
+ */
1379
+ interface DomainResource {
1380
+ set: (name: string, options?: DomainSetOptions) => Promise<DomainSetResult>;
1381
+ list: (options?: ListOptions) => Promise<DomainListResponse>;
1382
+ get: (name: string) => Promise<Domain>;
1383
+ delete: (name: string) => Promise<DomainDeleteResponse>;
1384
+ verify: (name: string) => Promise<DomainVerifyResponse>;
1385
+ validate: (name: string) => Promise<DomainValidateResponse>;
1386
+ dns: (name: string) => Promise<DomainDnsResponse>;
1387
+ records: (name: string) => Promise<DomainRecordsResponse>;
1388
+ share: (name: string) => Promise<DomainShareResponse>;
1389
+ }
1390
+ /**
1391
+ * Account resource interface - the contract all implementations must follow
1392
+ */
1393
+ interface AccountResource {
1394
+ get: () => Promise<AccountGetResponse>;
1395
+ }
1396
+ /**
1397
+ * Token resource interface - the contract all implementations must follow
1398
+ */
1399
+ interface TokenResource {
1400
+ create: (options?: TokenCreateOptions) => Promise<TokenCreateResponse>;
1401
+ list: (options?: ListOptions) => Promise<TokenListResponse>;
1402
+ get: (token: string) => Promise<Token>;
1403
+ delete: (token: string) => Promise<TokenDeleteResponse>;
1404
+ }
1405
+ /**
1406
+ * Billing status response from GET /billing/status
1407
+ *
1408
+ * Note: The user's `plan` comes from Account, not here.
1409
+ * This endpoint only returns billing-specific data (usage, portal, etc.)
1410
+ *
1411
+ * If `billing` is null, the user has no active billing.
1412
+ */
1413
+ interface BillingStatus {
1414
+ /** Creem billing ID, or null if no active billing */
1415
+ billing: string | null;
1416
+ /** Number of billing units (1 unit = 1 custom domain), null if no billing */
1417
+ units: number | null;
1418
+ /** Billing status from Creem (active, trialing, canceled, etc.), null if no billing */
1419
+ status: string | null;
1420
+ /** Link to Creem customer portal for billing management, null if unavailable */
1421
+ portal: string | null;
1422
+ }
1423
+ /**
1424
+ * Acknowledgement of `POST /billing/cancel`.
1425
+ *
1426
+ * Cancelling leaves no billing entity to return, so it answers with the
1427
+ * account and the one field of the account the call changed — the plan it
1428
+ * landed on. See {@link DeploymentDeleteResponse} for the law.
1429
+ *
1430
+ * This read `{ success: true, message: 'Subscription canceled successfully…' }`
1431
+ * until 2026-07-29, an anonymous shape that `web/my` redeclared inline and
1432
+ * whose prose no surface ever displayed: both callers await the promise and
1433
+ * discard the body, then compose their own toast. The message was written,
1434
+ * serialized, and thrown away on every cancellation.
1435
+ */
1436
+ interface BillingCancelResponse {
1437
+ /** The account whose subscription was cancelled */
1438
+ readonly account: string;
1439
+ /** The plan the account now holds — `free` on a successful cancellation */
1440
+ readonly plan: AccountPlanType;
1441
+ }
1442
+ /**
1443
+ * Checkout session response from POST /billing/checkout
1444
+ */
1445
+ interface CheckoutSession {
1446
+ /** URL to redirect user to Creem checkout page */
1447
+ url: string;
1448
+ }
1449
+ /**
1450
+ * All activity event types logged in the system.
1451
+ * Uses dot notation consistently: {resource}.{action}
1452
+ */
1453
+ type ActivityEvent = 'account.create' | 'account.update' | 'account.delete' | 'account.key.generate' | 'account.plan.paid' | 'account.plan.transition' | 'account.suspended' | 'deployment.create' | 'deployment.update' | 'deployment.delete' | 'deployment.claim' | 'deployment.flagged' | 'domain.create' | 'domain.update' | 'domain.delete' | 'domain.verify' | 'token.create' | 'token.consume' | 'token.delete' | 'admin.account.plan.update' | 'admin.account.ref.update' | 'admin.account.billing.update' | 'admin.account.labels.update' | 'admin.deployment.delete' | 'admin.domain.delete' | 'admin.billing.sync' | 'admin.billing.terminated' | 'admin.impersonate' | 'billing.active' | 'billing.canceled' | 'billing.paused' | 'billing.expired' | 'billing.paid' | 'billing.trialing' | 'billing.scheduled_cancel' | 'billing.unpaid' | 'billing.update' | 'billing.past_due' | 'refund.created' | 'dispute.created' | 'billing.sync' | 'billing.stale' | 'billing.race';
1454
+ /**
1455
+ * Activity events visible to users in the dashboard
1456
+ */
1457
+ type UserVisibleActivityEvent = 'account.create' | 'account.update' | 'account.delete' | 'account.key.generate' | 'account.plan.transition' | 'deployment.create' | 'deployment.update' | 'deployment.delete' | 'deployment.claim' | 'domain.create' | 'domain.update' | 'domain.delete' | 'domain.verify' | 'token.create' | 'token.consume' | 'token.delete';
1458
+ /**
1459
+ * Activity record returned from the API
1460
+ */
1461
+ interface Activity {
1462
+ /** The event type */
1463
+ event: ActivityEvent;
1464
+ /** Unix timestamp (seconds) when the activity occurred */
1465
+ created: number;
1466
+ /** Associated deployment ID (if applicable) */
1467
+ deployment?: string;
1468
+ /** Associated domain name (if applicable) */
1469
+ domain?: string;
1470
+ /** JSON-encoded metadata (parse with JSON.parse) */
1471
+ meta?: string;
1472
+ }
1473
+ /**
1474
+ * Parsed activity metadata.
1475
+ * Different events populate different fields.
1476
+ *
1477
+ * Naming convention: meta booleans are event-scoped predicates and carry
1478
+ * their prefix (`isUpdate`, `wasVerified`, `hasConfig`, `hasPassword`),
1479
+ * while entity booleans are bare nouns (`Deployment.config`,
1480
+ * `Deployment.password`). Two vocabularies, each internally consistent —
1481
+ * deliberate, not drift.
1482
+ */
1483
+ interface ActivityMeta {
1484
+ /** Number of files in deployment */
1485
+ files?: number;
1486
+ /** Total size in bytes */
1487
+ size?: number;
1488
+ /** Whether deployment has a ship.json config */
1489
+ hasConfig?: boolean;
1490
+ /** Whether deployment has a password set */
1491
+ hasPassword?: boolean;
1492
+ /** Whether this was an update (vs create) */
1493
+ isUpdate?: boolean;
1494
+ /** Whether domain was already verified */
1495
+ wasVerified?: boolean;
1496
+ /** Previous deployment ID before relinking */
1497
+ previousDeployment?: string;
1498
+ /** Labels that were set/updated */
1499
+ labels?: string[];
1500
+ /** OAuth provider name */
1501
+ provider?: string;
1502
+ /** Account email */
1503
+ email?: string;
1504
+ /** Account display name */
1505
+ name?: string;
1506
+ /** Previous plan */
1507
+ from?: string;
1508
+ /** New plan */
1509
+ to?: string;
1510
+ /** Allow additional fields for future use */
1511
+ [key: string]: unknown;
1512
+ }
1513
+ /**
1514
+ * Response from GET /activities endpoint
1515
+ */
1516
+ interface ActivityListResponse extends ListResponse {
1517
+ /** Array of activities */
1518
+ activities: Activity[];
1519
+ }
1520
+ /**
1521
+ * File status constants for validation state tracking
1522
+ */
1523
+ declare const FileValidationStatus: {
1524
+ /** File is pending validation */
1525
+ readonly PENDING: "pending";
1526
+ /** File failed during processing (before validation) */
1527
+ readonly PROCESSING_ERROR: "processing_error";
1528
+ /** File was excluded by validation warning (not an error) */
1529
+ readonly EXCLUDED: "excluded";
1530
+ /** File failed validation (blocks deployment) */
1531
+ readonly VALIDATION_FAILED: "validation_failed";
1532
+ /** File passed validation and is ready for deployment */
1533
+ readonly READY: "ready";
1534
+ };
1535
+ type FileValidationStatusType = (typeof FileValidationStatus)[keyof typeof FileValidationStatus];
1536
+ /**
1537
+ * A validation issue with a display-ready message
1538
+ *
1539
+ * Issues are either errors (in errors[] array) or warnings (in warnings[] array).
1540
+ * The array position determines severity - no need to duplicate it in the object.
1541
+ */
1542
+ interface ValidationIssue {
1543
+ /** File path that triggered this issue */
1544
+ file: string;
1545
+ /** Display-ready message explaining the issue */
1546
+ message: string;
1547
+ }
1548
+ /**
1549
+ * Minimal file interface required for validation
1550
+ */
1551
+ interface ValidatableFile {
1552
+ name: string;
1553
+ size: number;
1554
+ status?: FileValidationStatusType;
1555
+ statusMessage?: string;
1556
+ }
1557
+ /**
1558
+ * File validation result with severity-based issue reporting
1559
+ *
1560
+ * Validation checks files against constraints and categorizes issues by severity:
1561
+ * - **Errors**: Block deployment (file too large, invalid type, etc.)
1562
+ * - **Warnings**: Exclude files but allow deployment (empty files, etc.)
1563
+ *
1564
+ * @example
1565
+ * ```typescript
1566
+ * const result = validateFiles(files, config);
1567
+ *
1568
+ * if (!result.canDeploy) {
1569
+ * // Has errors - must fix before deploying
1570
+ * console.error('Deployment blocked:', result.errors);
1571
+ * } else if (result.warnings.length > 0) {
1572
+ * // Has warnings - deployment proceeds, some files excluded
1573
+ * console.warn('Files excluded:', result.warnings);
1574
+ * deploy(result.validFiles);
1575
+ * } else {
1576
+ * // All files valid
1577
+ * deploy(result.validFiles);
1578
+ * }
1579
+ * ```
1580
+ */
1581
+ interface FileValidationResult<T extends ValidatableFile> {
1582
+ /** All files with updated status */
1583
+ files: T[];
1584
+ /** Files ready for deployment (status: 'ready') */
1585
+ validFiles: T[];
1586
+ /** Blocking errors that prevent deployment */
1587
+ errors: ValidationIssue[];
1588
+ /** Non-blocking warnings (files excluded but deployment allowed) */
1589
+ warnings: ValidationIssue[];
1590
+ /** Whether deployment can proceed (true if errors.length === 0) */
1591
+ canDeploy: boolean;
1592
+ }
1593
+ /**
1594
+ * Represents a file that has been uploaded and stored
1595
+ */
1596
+ interface UploadedFile {
1597
+ key: string;
1598
+ etag: string;
1599
+ size: number;
1600
+ validated?: boolean;
1601
+ }
1602
+ /**
1603
+ * Check if a domain is a platform domain (subdomain of our platform).
1604
+ * Platform domains are free and don't require DNS verification.
1605
+ *
1606
+ * @example isPlatformDomain("www.shipstatic.com", "shipstatic.com") → true
1607
+ * @example isPlatformDomain("example.com", "shipstatic.com") → false
1608
+ */
1609
+ declare function isPlatformDomain(domain: string, platformDomain: string): boolean;
1610
+ /**
1611
+ * Check if a domain is a custom domain (not a platform subdomain).
1612
+ * Custom domains are billable and require DNS verification.
1613
+ *
1614
+ * @example isCustomDomain("example.com", "shipstatic.com") → true
1615
+ * @example isCustomDomain("www.shipstatic.com", "shipstatic.com") → false
1616
+ */
1617
+ declare function isCustomDomain(domain: string, platformDomain: string): boolean;
1618
+ /**
1619
+ * Extract subdomain from a platform domain.
1620
+ * Returns null if not a platform domain.
1621
+ *
1622
+ * @example extractSubdomain("www.shipstatic.com", "shipstatic.com") → "www"
1623
+ * @example extractSubdomain("example.com", "shipstatic.com") → null
1624
+ */
1625
+ declare function extractSubdomain(domain: string, platformDomain: string): string | null;
1626
+ /**
1627
+ * Generate HTTPS URL for a deployment hostname.
1628
+ */
1629
+ declare function generateDeploymentUrl(deployment: string): string;
1630
+ /**
1631
+ * Generate HTTPS URL for a domain.
1632
+ */
1633
+ declare function generateDomainUrl(domain: string): string;
1634
+ /**
1635
+ * Label validation constraints shared across UI and API.
1636
+ * These rules define the single source of truth for label validation.
1637
+ */
1638
+ declare const LABEL_CONSTRAINTS: {
1639
+ /** Minimum label length in characters */
1640
+ readonly MIN_LENGTH: 3;
1641
+ /** Maximum label length in characters (concise labels, matches Stack Overflow's original limit) */
1642
+ readonly MAX_LENGTH: 25;
1643
+ /** Maximum number of labels allowed per resource */
1644
+ readonly MAX_COUNT: 10;
1645
+ /** Allowed separator characters between label segments */
1646
+ readonly SEPARATORS: "._-";
1647
+ };
1648
+ /**
1649
+ * Label validation pattern.
1650
+ * Must start and end with alphanumeric (a-z, 0-9).
1651
+ * Can contain separators (. _ -) between segments, but not consecutive.
1652
+ *
1653
+ * Valid examples: 'production', 'v1.2.3', 'api_v2', 'us-east-1'
1654
+ * Invalid examples: 'ab' (too short), '-prod' (starts with separator), 'foo--bar' (consecutive separators)
1655
+ */
1656
+ declare const LABEL_PATTERN: RegExp;
1657
+ /**
1658
+ * Serialize labels array to JSON string for database storage.
1659
+ * Returns null for empty or undefined arrays.
1660
+ *
1661
+ * @example serializeLabels(['web', 'production']) → '["web","production"]'
1662
+ * @example serializeLabels([]) → null
1663
+ * @example serializeLabels(undefined) → null
1664
+ */
1665
+ declare function serializeLabels(labels: string[] | undefined): string | null;
1666
+ /**
1667
+ * Deserialize labels from JSON string to array.
1668
+ * Always returns an array — empty array for null/empty/invalid input.
1669
+ *
1670
+ * @example deserializeLabels('["web","production"]') → ['web', 'production']
1671
+ * @example deserializeLabels(null) → []
1672
+ * @example deserializeLabels('') → []
1673
+ */
1674
+ declare function deserializeLabels(labelsJson: string | null): string[];
1675
+ /**
1676
+ * Length constraints for the optional deployment password
1677
+ * (`DeploymentUploadOptions.password`). Single source of truth shared across
1678
+ * platform consumers.
1679
+ */
1680
+ declare const PASSWORD_CONSTRAINTS: {
1681
+ /** Minimum password length in characters */
1682
+ readonly MIN_LENGTH: 6;
1683
+ /** Maximum password length in characters */
1684
+ readonly MAX_LENGTH: 128;
1685
+ };
1686
+ /**
1687
+ * Validate an optional deployment password and return it normalized.
1688
+ *
1689
+ * Absent (`undefined` / `null`) → returns `undefined`. Present → trim
1690
+ * leading/trailing whitespace, then validate against `PASSWORD_CONSTRAINTS`
1691
+ * length bounds (internal whitespace is significant and counts toward
1692
+ * length). Throws `ShipError.validation` on breach; returns the trimmed
1693
+ * value.
1694
+ *
1695
+ * The trim is canonical: at upload, the API hashes the trimmed value; at
1696
+ * unlock, the router trims submissions before hashing. Submission and storage
1697
+ * agree byte-for-byte. Length validation runs on the trimmed value because
1698
+ * that's the user's actual intent — and it disarms a class of invisible
1699
+ * foot-guns (trailing newlines from copy/paste, mobile auto-spacing,
1700
+ * password-manager artifacts).
1701
+ *
1702
+ * Single source of truth shared by SDK (client-side validation, return
1703
+ * ignored) and API (server-side enforcement, return threaded into config).
1704
+ * Length is part of the wire-format contract; strength rules, if added later,
1705
+ * stay server-side. See `CLAUDE.md` "Validation: format vs policy".
1706
+ */
1707
+ declare function validatePassword(value: unknown): string | undefined;
5
1708
 
6
1709
  /**
7
1710
  * @file SDK-specific type definitions
@@ -368,7 +2071,7 @@ declare abstract class Ship$1 {
368
2071
  /**
369
2072
  * Get current account information (convenience shortcut to `ship.account.get()`).
370
2073
  */
371
- whoami(): Promise<_shipstatic_types.AccountGetResponse>;
2074
+ whoami(): Promise<AccountGetResponse>;
372
2075
  /**
373
2076
  * Get platform limits (max file size, file count, total size).
374
2077
  * Reuses the response fetched during initialization. Per-instance state —
@@ -720,4 +2423,4 @@ declare class Ship extends Ship$1 {
720
2423
  protected getDeployBodyCreator(): DeployBodyCreator;
721
2424
  }
722
2425
 
723
- export { type ApiDeployOptions, ApiHttp, type ApiHttpOptions, type DeployBody, type DeployBodyContext, type DeployBodyCreator, type DeployFile, type DeploymentOptions, type DeploymentResourceContext, type ExecutionEnvironment, type Fetch, JUNK_DIRECTORIES, type MD5Result, type ResourceContext, Ship, type ShipClientOptions, type ShipEvents, type TokenProvider, __setTestEnvironment, allValidFilesReady, calculateMD5, createAccountResource, createDeploymentResource, createDomainResource, createTokenResource, Ship as default, filterJunk, formatFileSize, getENV, getValidFiles, optimizeDeployPaths, pluralize, processFilesForNode, validateDeployFile, validateDeployPath, validateFileName, validateFiles };
2426
+ export { API_KEY, API_PATHS, AUTH_BASE_PATH, type Account, type AccountDeleteResponse, type AccountGetResponse, type AccountKeyResponse, type AccountOverrides, AccountPlan, type AccountPlanType, type AccountResource, type AccountUsage, type Activity, type ActivityEvent, type ActivityListResponse, type ActivityMeta, type ApiDeployOptions, ApiHttp, type ApiHttpOptions, AuthMethod, type AuthMethodType, BLOCKED_EXTENSIONS, type BillingCancelResponse, type BillingStatus, CALLER, type CheckoutSession, DEFAULT_API, DEPLOYMENT_CONFIG_FILENAME, DEPLOY_FIELDS, DEPLOY_TOKEN, type DeployBody, type DeployBodyContext, type DeployBodyCreator, type DeployFile, type DeployInput, type Deployment, type DeploymentCreateResponse, type DeploymentDeleteResponse, type DeploymentListResponse, type DeploymentOptions, type DeploymentResource, type DeploymentResourceContext, type DeploymentSetOptions, DeploymentStatus, type DeploymentStatusType, type DeploymentUploadOptions, DeploymentVia, type DeploymentViaType, type DnsLookup, type DnsProvider, type DnsRecord, type DnsRecordType, type Domain, type DomainDeleteResponse, type DomainDnsResponse, type DomainListResponse, type DomainRecordsResponse, type DomainResource, type DomainSetOptions, type DomainSetResult, type DomainShareResponse, DomainStatus, type DomainStatusType, type DomainValidateResponse, type DomainVerifyResponse, type ErrorResponse, ErrorType, type ExecutionEnvironment, FileValidationStatus as FILE_VALIDATION_STATUS, type Fetch, type FileValidationResult, FileValidationStatus, type FileValidationStatusType, IDEMPOTENCY_KEY_CONSTRAINTS, JUNK_DIRECTORIES, LABEL_CONSTRAINTS, LABEL_PATTERN, type LabelsResponse, type ListOptions, type ListResponse, type MD5Result, MY_API_KEY_URL, OAuthScope, type OAuthScopeType, PASSWORD_CONSTRAINTS, PUBLIC_DEPLOYMENT_TTL_SECONDS, type PingResponse, type PlatformLimits, type ResourceContext, SHIP_ENV, type SPACheckDebug, type SPACheckRequest, type SPACheckResponse, SPA_CHECK_CONSTRAINTS, SPA_DEFAULT_CONFIG, type SetupInstructionsResponse, Ship, type ShipClientOptions, ShipError, type ShipEvents, type StaticFile, type Token, type TokenCreateOptions, type TokenCreateResponse, type TokenDeleteResponse, TokenKind, type TokenKindType, type TokenListResponse, type TokenProvider, type TokenResource, UNBUILT_PROJECT_MARKERS, UNSAFE_FILENAME_CHARS, type UploadedFile, type UserVisibleActivityEvent, type ValidatableFile, type ValidationIssue, WEB_FILE_ACCEPT, __setTestEnvironment, allValidFilesReady, assertShipJsonSyntax, calculateMD5, classifyToken, createAccountResource, createDeploymentResource, createDomainResource, createTokenResource, Ship as default, deserializeLabels, extractSubdomain, filterJunk, formatFileSize, generateDeploymentUrl, generateDomainUrl, getENV, getValidFiles, hasUnbuiltMarker, hasUnsafeChars, isBlockedExtension, isCustomDomain, isDeployment, isPlatformDomain, isShipError, normalizeVia, optimizeDeployPaths, pluralize, processFilesForNode, serializeLabels, validateApiKey, validateApiUrl, validateCaller, validateDeployFile, validateDeployPath, validateDeployToken, validateFileName, validateFiles, validateIdempotencyKey, validatePassword, validateToken };