@shipstatic/ship 3.3.2 → 3.4.0-beta.10

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/README.md CHANGED
@@ -132,7 +132,7 @@ ship ping
132
132
  ```typescript
133
133
  ship.account.get() // → whoami
134
134
  ship.ping() // → { timestamp } (server clock; reachability is the absence of a throw)
135
- ship.getLimits() // → platform plan limits (cached)
135
+ ship.getLimits() // → the account's platform limits, read now
136
136
  ```
137
137
 
138
138
  ## CLI reference
@@ -246,8 +246,9 @@ const ship = new Ship({ token: accessToken });
246
246
  // Token provider — invoked per request; refresh lives with you
247
247
  const ship = new Ship({ token: () => mintToken() });
248
248
 
249
- // Cookie session — first-party browser apps
249
+ // Cookie session: the platform's own apps, where every account request names its account
250
250
  const ship = new Ship({ session: true });
251
+ ship.setHeaders({ 'X-Account': account });
251
252
 
252
253
  // Set or rotate the token after construction
253
254
  ship.setToken('ship-your-api-key');
package/SKILL.md CHANGED
@@ -335,7 +335,7 @@ Exit code is non-zero on failure, and with `--json` the error goes to **stderr**
335
335
  ```json
336
336
  {
337
337
  "error": "validation_failed",
338
- "message": "File \"index.html\" too large. Maximum 20 MB allowed.",
338
+ "message": "File \"setup.exe\" has an extension that is not allowed.",
339
339
  "status": 400
340
340
  }
341
341
  ```
@@ -5,7 +5,7 @@ Their copyright notices travel with that copy, and are reproduced here in
5
5
  full. This file is GENERATED from the build's own metafile — edit the
6
6
  bundle, not this list.
7
7
 
8
- ## @shipstatic/types 3.7.0
8
+ ## @shipstatic/types 3.8.0-beta.9
9
9
 
10
10
  License: MIT
11
11
 
package/dist/browser.d.ts CHANGED
@@ -724,7 +724,7 @@ type AccountPlanType = (typeof AccountPlan)[keyof typeof AccountPlan];
724
724
  * than the 403 uses.
725
725
  *
726
726
  * All three are counts plans SELL, and every plan publishes a number for each.
727
- * A platform subdomain (`my-app.shipstatic.com`) is among them: the namespace
727
+ * A platform domain (`my-app.shipstatic.com`) is among them: the namespace
728
728
  * is the platform's, so every plan bounds how many names one account may take
729
729
  * from it — which is not the address every deployment gets by construction
730
730
  * (`happy-cat-abc1234.shipstatic.com`), one per deployment and bounded by
@@ -734,8 +734,9 @@ type AccountPlanType = (typeof AccountPlan)[keyof typeof AccountPlan];
734
734
  * "unlimited" — so no consumer needs an "is it bounded?" branch. A cap of `0`
735
735
  * means the plan does not have the feature at all; a cap of `N` bounds
736
736
  * creation, and what an account already holds above a cap stays until a plan
737
- * TRANSITION fits it (excess paused, newest first — a domain is the only kind
738
- * that pauses).
737
+ * TRANSITION fits it: excess domains are paused, newest first, and members
738
+ * are paused together while the account holds more than its cap. Deployments
739
+ * never pause; above their cap they only block creation.
739
740
  *
740
741
  * A count is an aggregate over a collection, so it lives on the summary
741
742
  * resource that owns the collection: `GET /account` for one caller, `GET
@@ -786,18 +787,34 @@ type AccountRole = 'owner' | 'member';
786
787
  * account automation, not a person, and is always `active`.
787
788
  */
788
789
  type AccountAccess = 'active' | 'paused';
790
+ /**
791
+ * The word a refusal about the account carries in `details.account`, so a
792
+ * client can tell the two apart without reading the sentence:
793
+ *
794
+ * - `paused`: a 403. The person is a member, and the account holds more
795
+ * members than its plan allows (see {@link AccountAccess}).
796
+ * - `unknown`: a 404. The account the request names ({@link ACCOUNT_HEADER})
797
+ * is one the person is not a member of, or one that does not exist; the
798
+ * two are indistinguishable by design. A tab whose person was removed from
799
+ * the account meets it on its next request.
800
+ */
801
+ declare const AccountRefusal: {
802
+ readonly PAUSED: "paused";
803
+ readonly UNKNOWN: "unknown";
804
+ };
805
+ type AccountRefusalType = (typeof AccountRefusal)[keyof typeof AccountRefusal];
789
806
  /**
790
807
  * Core account object - used in both API responses and SDK
791
808
  * All fields are readonly to prevent accidental mutations
792
809
  */
793
810
  interface Account {
794
811
  /**
795
- * The account this response describes, by id. A person may belong to
796
- * several accounts and a browser session selects one, so a client names
797
- * the one it means on every request (`X-Account`) and this is where it
798
- * learns the name. Optional by the additive-evolution law.
812
+ * The account this response describes, by id: the name the console's URL
813
+ * carries (`/<account>/`) and a session request sends in
814
+ * {@link ACCOUNT_HEADER}. A machine credential IS its account, so the
815
+ * response names the account the credential belongs to.
799
816
  */
800
- readonly account?: string;
817
+ readonly account: string;
801
818
  /** User email address */
802
819
  readonly email: string;
803
820
  /**
@@ -941,20 +958,6 @@ interface PlanDestination {
941
958
  readonly plan: AccountPlanType;
942
959
  readonly intervals: BillingInterval[];
943
960
  }
944
- /**
945
- * Account as returned by `GET /account` — the entity plus how the request
946
- * was authorized, so `whoami` can answer "what credential am I holding?".
947
- * Request-scoped fields live on the response type, never on the entity
948
- * (the `DeploymentCreateResponse` pattern).
949
- */
950
- interface AccountGetResponse extends Account {
951
- /** How the request that produced this response was authorized. */
952
- readonly authMethod: AuthMethodType;
953
- /** Present (and true) only when the caller is an operator acting as themselves. */
954
- readonly isAdmin?: true;
955
- /** Present only during read-only admin impersonation: the operator's account id. */
956
- readonly impersonatedBy?: string;
957
- }
958
961
  /**
959
962
  * Acknowledgement of `DELETE /account` (202). Termination is asynchronous —
960
963
  * a cleanup consumer finishes the job — so the account survives long enough
@@ -1264,11 +1267,12 @@ interface ErrorResponse {
1264
1267
  /**
1265
1268
  * `details` of an `ErrorType.Build` error: the evidence behind the verdict.
1266
1269
  *
1267
- * The one `details` shape this file names, because it is the one a HUMAN
1268
- * surface renders rather than a machine reads. The sentence in `message`
1269
- * says what went wrong; the log is how the person fixing the project sees
1270
- * where. A surface that builds shows both, the log as a block rather than
1271
- * as prose.
1270
+ * Named, like {@link PlanChangeRefusalDetails}, because a surface reads it:
1271
+ * `ErrorResponse.details` stays `unknown`, and a shape a client narrows to is
1272
+ * owned here rather than restated at its read site. The sentence in
1273
+ * `message` says what went wrong; the log is how the person fixing the
1274
+ * project sees where. A surface that builds shows both, the log as a block
1275
+ * rather than as prose.
1272
1276
  */
1273
1277
  interface BuildFailureDetails {
1274
1278
  /**
@@ -1474,7 +1478,27 @@ interface PlatformLimits {
1474
1478
  * retire with it (tracked in root `backlog.md`).
1475
1479
  */
1476
1480
  readonly blockedExtensions?: readonly string[];
1481
+ /**
1482
+ * The way forward past each limit, in the API's own words, for the account
1483
+ * that asked: `"Upgrade to Pro for more."` where a sold plan allows more,
1484
+ * `"Please contact support if you need more."` where none does. The API
1485
+ * writes it with the same function its own refusals use, so a client that
1486
+ * refuses a file before uploading it appends this sentence to its refusal
1487
+ * verbatim and says exactly what the server would have said. Which plans
1488
+ * exist, which are sold and what an account's grant raises are the server's
1489
+ * to know; a client never learns a plan name from this field, only a
1490
+ * sentence. An anonymous caller is answered the free plan's sentence, as it
1491
+ * is the free plan's numbers.
1492
+ *
1493
+ * **Optional, and the absence is load-bearing**, as `blockedExtensions`
1494
+ * above: an API deployed before this field existed sends nothing, and a
1495
+ * client then appends nothing. It hardens to required at the entity's next
1496
+ * natural break, with the blocklist.
1497
+ */
1498
+ readonly suggestions?: Readonly<Record<PlatformLimitKey, string>>;
1477
1499
  }
1500
+ /** The three request limits, named as `PlatformLimits` names them. */
1501
+ type PlatformLimitKey = 'maxFileSize' | 'maxFilesCount' | 'maxTotalSize';
1478
1502
  /**
1479
1503
  * Whether a file is one the platform refuses to host.
1480
1504
  *
@@ -1703,16 +1727,18 @@ declare const CALLER: {
1703
1727
  readonly PATTERN: RegExp;
1704
1728
  };
1705
1729
  /**
1706
- * The account a session request MEANS.
1730
+ * The account a session request is about.
1707
1731
  *
1708
- * A browser session selects one account for every tab at once, so a tab that
1709
- * switched leaves its siblings acting for an account they no longer show. A
1710
- * session caller may name the account it means in this header; the API
1711
- * answers 409 `details.account: 'select'` when it differs from the account the
1712
- * session resolves to, and the client re-selects. A precondition check the
1713
- * client volunteers, never authority: a request without it resolves exactly as
1714
- * before, and machine credentials never send it. One owner for the name, since
1715
- * the API's CORS allowlist and every browser transport spell it.
1732
+ * A person may belong to several accounts, so a request made with a browser
1733
+ * session names the one it means, on every request to an account route; the
1734
+ * console takes the value from its URL (`/<account>/`). The header names and
1735
+ * membership authorizes: the API resolves the person's membership in the
1736
+ * account named, answers 404 `details.account: 'unknown'` for an account the
1737
+ * person is not in (or one that does not exist), and 400 when a session
1738
+ * request names none. Machine credentials never send it, since a key, a
1739
+ * deploy token or an OAuth grant already belongs to one account. One owner
1740
+ * for the name, since the API's CORS allowlist and every browser transport
1741
+ * spell it.
1716
1742
  */
1717
1743
  declare const ACCOUNT_HEADER = "X-Account";
1718
1744
  /**
@@ -2005,6 +2031,124 @@ interface StaticFile {
2005
2031
  /** The size of the file in bytes. */
2006
2032
  size: number;
2007
2033
  }
2034
+ /**
2035
+ * The sections of an account, by first segment, each mounted twice by the
2036
+ * console: under `/<account>/` and at the root.
2037
+ *
2038
+ * `owner` marks a section that means the person's OWN account: a bare path
2039
+ * into it opens the person's own account rather than the one entered last.
2040
+ * A pricing link's `/upgrade/pro` and the published API-key link speak about
2041
+ * the reader's plan and key, and `/success` is where a purchase lands: only
2042
+ * an owner buys, and a person owns exactly one account. Modals inside a
2043
+ * section carry their own owner rule in the console; this flag is the
2044
+ * section's alone.
2045
+ */
2046
+ declare const SECTIONS: {
2047
+ readonly new: {
2048
+ readonly owner: false;
2049
+ };
2050
+ readonly deployments: {
2051
+ readonly owner: false;
2052
+ };
2053
+ readonly domains: {
2054
+ readonly owner: false;
2055
+ };
2056
+ readonly settings: {
2057
+ readonly owner: false;
2058
+ };
2059
+ readonly upgrade: {
2060
+ readonly owner: true;
2061
+ };
2062
+ readonly success: {
2063
+ readonly owner: true;
2064
+ };
2065
+ readonly 'api-key': {
2066
+ readonly owner: true;
2067
+ };
2068
+ };
2069
+ /** A section's first segment. */
2070
+ type ConsoleSection = keyof typeof SECTIONS;
2071
+ /** The two fragments of the settings page a link may name. */
2072
+ type SettingsFragment = 'members' | 'api-key';
2073
+ /**
2074
+ * The query markers a Stripe return lands with, each written by a builder
2075
+ * below and read by `billingReturnOf`, so neither side spells one.
2076
+ */
2077
+ declare const BILLING_RETURNS: {
2078
+ readonly checkout: readonly ["upgrading", "true"];
2079
+ readonly portal: readonly ["billing", "returned"];
2080
+ };
2081
+ type BillingReturn = keyof typeof BILLING_RETURNS;
2082
+ /**
2083
+ * The paths inside one account, or, with no account, the bare form the
2084
+ * console opens in the account entered last.
2085
+ *
2086
+ * ```ts
2087
+ * consolePaths(account).domains.connect('www.example.com')
2088
+ * // '/<account>/domains/www.example.com/connect'
2089
+ * consolePaths().apiKey()
2090
+ * // '/api-key'
2091
+ * ```
2092
+ */
2093
+ declare function consolePaths(account?: string): {
2094
+ dashboard: () => string;
2095
+ newSite: () => string;
2096
+ deployments: (() => string) & {
2097
+ upload: () => string;
2098
+ };
2099
+ domains: (() => string) & {
2100
+ connect: (domain: string) => string;
2101
+ deployment: (domain: string) => string;
2102
+ };
2103
+ settings: (fragment?: SettingsFragment) => string;
2104
+ /** Settings, marked as the landing of a Stripe Customer Portal return. */
2105
+ settingsAfterBilling: () => string;
2106
+ upgrade: (plan?: AccountPlanType) => string;
2107
+ success: () => string;
2108
+ /** The success page, marked as the landing of a Stripe Checkout return. */
2109
+ successAfterCheckout: () => string;
2110
+ apiKey: () => string;
2111
+ };
2112
+ /** The builders `consolePaths` returns. */
2113
+ type ConsolePaths = ReturnType<typeof consolePaths>;
2114
+ /** The doors: the pages that exist before an account is known. */
2115
+ declare const doors: {
2116
+ readonly login: (next?: string) => string;
2117
+ readonly register: (next?: string) => string;
2118
+ readonly logout: () => string;
2119
+ readonly invitation: (invitation: string) => string;
2120
+ readonly claim: (code: string) => string;
2121
+ readonly consent: () => string;
2122
+ };
2123
+ /**
2124
+ * Which Stripe return a query string marks: `checkout` for the success page
2125
+ * after Checkout, `portal` for settings after the Customer Portal, or null.
2126
+ */
2127
+ declare function billingReturnOf(search: string): BillingReturn | null;
2128
+ /**
2129
+ * The account a path names, or null for a door, the operator partition, or
2130
+ * the bare form. Any first segment that is not a section, a door or the
2131
+ * partition is an account: the grammar has no other reading, and whether
2132
+ * the account exists is the server's answer, never the path's.
2133
+ */
2134
+ declare function accountOfPath(path: string): string | null;
2135
+ /** Whether a path names a claim link (`/claim/:code`, a bearer credential). */
2136
+ declare const isClaimPath: (path: string) => boolean;
2137
+ /** Whether a path names an invitation (`/invitations/:invitation`). */
2138
+ declare const isInvitationPath: (path: string) => boolean;
2139
+ /**
2140
+ * Whether a path names the upgrade section, in either form: `/upgrade`,
2141
+ * `/upgrade/:plan`, and the same under `/<account>/`.
2142
+ */
2143
+ declare const isUpgradePath: (path: string) => boolean;
2144
+ /** The errand a destination path carries, as the sign-in doors read it. */
2145
+ type ConsoleErrand = 'claim' | 'invite' | 'upgrade';
2146
+ /**
2147
+ * The errand a destination path carries, in specificity order: a pricing
2148
+ * link is the broadest, and the narrower ones do not normally coexist with
2149
+ * it.
2150
+ */
2151
+ declare function errandOf(path: string): ConsoleErrand | null;
2008
2152
  /** Default API URL if not otherwise configured. */
2009
2153
  declare const DEFAULT_API = "https://api.shipstatic.com";
2010
2154
  /**
@@ -2044,15 +2188,15 @@ declare const SHIP_ENV: {
2044
2188
  */
2045
2189
  declare const SHIP_VIA_ENV = "SHIP_VIA";
2046
2190
  /**
2047
- * Where a human creates an API key — the console deep link quoted by every
2191
+ * Where a human creates an API key: the console deep link quoted by every
2048
2192
  * surface that teaches authentication (the CLI's config wizard, the VS Code
2049
- * and n8n listings, the n8n rate-limit hint and credential copy). Written
2050
- * out in five files across three repos until this export.
2193
+ * and n8n listings, the n8n rate-limit hint and credential copy). The bare
2194
+ * form of the path, which the console opens in the reader's own account.
2051
2195
  *
2052
2196
  * Production-branded by design: published artifacts name the product, never
2053
2197
  * an environment (root `CLAUDE.md`, "Environment-Aware URLs").
2054
2198
  */
2055
- declare const MY_API_KEY_URL = "https://my.shipstatic.com/api-key";
2199
+ declare const MY_API_KEY_URL: string;
2056
2200
  /**
2057
2201
  * How long an anonymous deployment lives before it expires.
2058
2202
  *
@@ -2267,7 +2411,7 @@ interface DomainResource {
2267
2411
  * Account resource interface - the contract all implementations must follow
2268
2412
  */
2269
2413
  interface AccountResource {
2270
- get: () => Promise<AccountGetResponse>;
2414
+ get: () => Promise<Account>;
2271
2415
  }
2272
2416
  /**
2273
2417
  * Token resource interface - the contract all implementations must follow
@@ -2364,11 +2508,29 @@ interface PlansResponse {
2364
2508
  * consent), and a billed account moving down gets a Stripe Subscription
2365
2509
  * Schedule that applies the change at period end. The answer says which
2366
2510
  * happened ({@link PlanChangeResponse}).
2511
+ *
2512
+ * A move down a tier is booked only by an account that fits it: until it
2513
+ * does, the door refuses (400) with a sentence naming what to remove, and
2514
+ * the same counts as {@link PlanChangeRefusalDetails}.
2367
2515
  */
2368
2516
  interface PlanChangeRequest {
2369
2517
  readonly plan: AccountPlanType;
2370
2518
  readonly interval: BillingInterval;
2371
2519
  }
2520
+ /**
2521
+ * `details` of the change door's refusal of a move down a tier the account
2522
+ * does not fit: the account holds more members, custom domains or platform
2523
+ * domains than the destination allows.
2524
+ *
2525
+ * `excess` counts what to REMOVE before asking again, per kind, keyed like
2526
+ * {@link Caps}; only the kinds over the destination's caps appear. The
2527
+ * door's `message` names the same counts in a sentence, and that sentence is
2528
+ * what a surface shows; the counts are for a client that acts on the
2529
+ * refusal.
2530
+ */
2531
+ interface PlanChangeRefusalDetails {
2532
+ readonly excess: Partial<Caps>;
2533
+ }
2372
2534
  /**
2373
2535
  * The pending plan change — a Stripe Subscription Schedule, mirrored onto the
2374
2536
  * account. `at` is when it applies (the current period's end, Unix seconds).
@@ -2635,7 +2797,7 @@ interface UploadedFile {
2635
2797
  */
2636
2798
  declare function isPlatformDomain(domain: string, platformDomain: string): boolean;
2637
2799
  /**
2638
- * Check if a domain is a custom domain (not a platform subdomain).
2800
+ * Check if a domain is a custom domain (not a platform domain).
2639
2801
  * Custom domains are billable and require DNS verification.
2640
2802
  *
2641
2803
  * @example isCustomDomain("example.com", "shipstatic.com") → true
@@ -2781,8 +2943,11 @@ declare function validatePassword(value: unknown): string | undefined;
2781
2943
  interface DeploymentOptions extends DeploymentUploadOptions {
2782
2944
  /**
2783
2945
  * An AbortSignal to allow cancellation of the deploy operation. The one
2784
- * cancellation mechanism — abort the signal and the request rejects with
2785
- * a typed `Cancelled` error. Request timeouts are a client concern
2946
+ * cancellation mechanism: it reaches every request the deploy makes (the
2947
+ * limits it validates against, the SPA question and the upload itself),
2948
+ * and aborting it rejects the deploy with a typed `Cancelled` error
2949
+ * wherever it stands. A deploy the server had already committed stays
2950
+ * committed. Request timeouts are a client concern
2786
2951
  * (`ShipClientOptions.timeout`), not a per-deploy one.
2787
2952
  */
2788
2953
  signal?: AbortSignal;
@@ -3246,17 +3411,9 @@ declare abstract class Ship$1 {
3246
3411
  readonly tokens: TokenResource;
3247
3412
  private readonly http;
3248
3413
  private readonly clientOptions;
3249
- private initPromise;
3250
- protected platformLimits: PlatformLimits | null;
3251
3414
  private credential;
3252
3415
  constructor(options?: ShipClientOptions);
3253
- protected abstract processInput(input: DeployInput, options: DeploymentOptions): Promise<StaticFile[]>;
3254
- /**
3255
- * Lazy initialization — fetches platform limits (file size / count caps) once,
3256
- * on the first API call. Subsequent calls reuse the resolved promise.
3257
- */
3258
- protected ensureInitialized(): Promise<void>;
3259
- private fetchPlatformLimits;
3416
+ protected abstract processInput(input: DeployInput, options: DeploymentOptions, limits: PlatformLimits): Promise<StaticFile[]>;
3260
3417
  /**
3261
3418
  * Ping the API server, resolving its answer: `{ success, timestamp }`, where
3262
3419
  * `timestamp` is the server clock in unix SECONDS.
@@ -3275,13 +3432,16 @@ declare abstract class Ship$1 {
3275
3432
  /**
3276
3433
  * Get current account information (convenience shortcut to `ship.account.get()`).
3277
3434
  */
3278
- whoami(): Promise<AccountGetResponse>;
3435
+ whoami(): Promise<Account>;
3279
3436
  /**
3280
- * Get platform limits (max file size, file count, total size).
3281
- * Reuses the response fetched during initialization. Per-instance state —
3282
- * does not leak between concurrent Ships against different API URLs.
3437
+ * Get the platform limits the server states now (max file size, file count,
3438
+ * total size, and the blocked extensions). Asked every time and never held,
3439
+ * since a plan move or an operator's grant changes them. A `signal` aborts
3440
+ * the request, rejecting with the typed `Cancelled` error.
3283
3441
  */
3284
- getLimits(): Promise<PlatformLimits>;
3442
+ getLimits(options?: {
3443
+ signal?: AbortSignal;
3444
+ }): Promise<PlatformLimits>;
3285
3445
  on<K extends keyof ShipEvents>(event: K, handler: (...args: ShipEvents[K]) => void): void;
3286
3446
  off<K extends keyof ShipEvents>(event: K, handler: (...args: ShipEvents[K]) => void): void;
3287
3447
  /**
@@ -3388,6 +3548,9 @@ declare function getENV(): ExecutionEnvironment;
3388
3548
  * Everything else is allowed — browser percent-encodes, Worker decodes, R2 matches.
3389
3549
  *
3390
3550
  * Additional checks: path traversal, reserved names, leading/trailing dots or spaces.
3551
+ *
3552
+ * The reason is a complete sentence naming the file, so every surface shows it
3553
+ * as it is (`file-rules.ts` on why).
3391
3554
  */
3392
3555
  declare function validateFileName(filename: string): {
3393
3556
  valid: boolean;
@@ -3516,35 +3679,40 @@ declare function calculateMD5(input: Blob | Buffer | string): Promise<MD5Result>
3516
3679
  * @file One ordered table of deploy-file rules, and the single evaluation two
3517
3680
  * renderers share.
3518
3681
  *
3519
- * **The defect this closes:** one rule was rendering as three sentences. A
3520
- * file over the size cap said `File x is too large. Maximum allowed size is
3521
- * 20MB.` from the deploy pipelines, `File size (21 MB) exceeds limit of 20 MB`
3522
- * from `validateFiles`, and `File too large. Maximum 20971520 bytes allowed`
3523
- * from the API — against the dual-validation doctrine that an error reads the
3524
- * same wherever it was caught (root `CLAUDE.md`). Both pipelines also restated
3525
- * the whole ordered check, so node/browser parity was a comment.
3526
- *
3527
3682
  * **A rule states a predicate and a sentence; a renderer chooses only how to
3528
3683
  * DELIVER it.** That is the `SHAPES`-table move (`cli/formatters.ts`) applied
3529
3684
  * to validation: the throwing renderer raises the first broken rule, the
3530
3685
  * collecting renderer records it, and neither authors prose. Adding a rule is
3531
- * a row, and both surfaces get it in the same position by construction.
3532
- *
3533
- * **Wording follows the API where a choice existed**, so the deferred Phase B
3534
- * — promoting this table to `@shipstatic/types` with the API consuming it —
3535
- * has less to move. Two deliberate deviations, recorded rather than silent:
3536
- *
3537
- * - **Sizes are formatted, not raw bytes.** The API says `20971520 bytes`;
3538
- * a browser upload UI showing that is worse for the person reading it, and
3539
- * the unit is the smaller half of the sentence to reconcile later.
3540
- * - **The path is named.** The API has no path to name; the throwing renderer
3541
- * has nothing BUT the message, so dropping it would leave a CLI user asking
3542
- * which file.
3686
+ * a row, and both surfaces get it in the same position by construction. One
3687
+ * table is also what keeps a rule from reading three ways: the deploy
3688
+ * pipelines, `validateFiles` and the API once each worded the size cap
3689
+ * differently, against the dual-validation doctrine that an error reads the
3690
+ * same wherever it is caught (root `CLAUDE.md`).
3691
+ *
3692
+ * **Every sentence is complete on its own.** The SDK is where a refusal is
3693
+ * worded; drop, the console, the CLI and the MCPs show the sentence verbatim
3694
+ * and add nothing, so a sentence names its file exactly once and states the
3695
+ * limit, and reads without a prefix. The family is subject first: `File
3696
+ * "<path>" <verdict>. Maximum <limit> allowed.`, and `File name "<path>" …`
3697
+ * where the name is the subject. The two rules about the SET, the count and
3698
+ * the total, name no path. A limit's sentence ends with the suggestion the
3699
+ * API delivered for it (`withSuggestion`), so the whole reads `File "x" is
3700
+ * too large. Maximum 20 MB allowed. Upgrade to Pro for more.`
3701
+ *
3702
+ * **The API words the same rules the same way, in the same order**
3703
+ * (`cloudflare/api/src/lib/validation.ts`), so a refusal reads alike whether
3704
+ * the SDK made it before uploading or the boundary made it after. Sizes are
3705
+ * formatted on both sides, and the path is named on both. That is a
3706
+ * restatement, and it is fenced from the API's side: a test there imports
3707
+ * this package's `validateFiles` and compares every shared rule's sentence to
3708
+ * `validateUpload`'s. The fence dies the day the table is promoted to
3709
+ * `@shipstatic/types` and the API imports it (Phase B, `CLAUDE.md`).
3543
3710
  *
3544
3711
  * Out of scope, and left where they are: `validateDeployPath` (a rule about
3545
3712
  * the deploy PATH rather than the file, and pipelines-only), and
3546
- * `validateFiles`' UI-tier pre-checks — empty, negative, count, unbuilt
3547
- * marker, processing error — which have one holder each and no drift.
3713
+ * `validateFiles`' UI-tier pre-checks (empty, negative, unbuilt marker,
3714
+ * processing error), which have one holder each and no drift, and word their
3715
+ * sentences by the same family.
3548
3716
  */
3549
3717
 
3550
3718
  /** What a rule is asked about: one file, and the deploy so far. */
@@ -3596,6 +3764,15 @@ declare function validateDeployPath(deployPath: string, sourceIdentifier: string
3596
3764
  * @throws {ShipError} The first broken rule's sentence
3597
3765
  */
3598
3766
  declare function validateDeployFile(input: FileRuleInput, limits: PlatformLimits): void;
3767
+ /**
3768
+ * The throwing renderer of `COUNT_RULE`: the deploy's file count, judged over
3769
+ * the files the pipeline will send.
3770
+ *
3771
+ * @param count - How many files the deploy carries
3772
+ * @param limits - The platform's limits, from `/limits`
3773
+ * @throws {ShipError} The count rule's sentence
3774
+ */
3775
+ declare function validateDeployCount(count: number, limits: PlatformLimits): void;
3599
3776
 
3600
3777
  /**
3601
3778
  * Utility functions for string manipulation.
@@ -3634,10 +3811,9 @@ declare function pluralize(count: number, singular: string, plural: string, incl
3634
3811
  *
3635
3812
  * @param browserFiles - File[] to process for deploy.
3636
3813
  * @param options - Processing options including pathDetect for automatic path optimization.
3637
- * @param platformLimits - Per-instance platform limits (file-size / count / total-size caps)
3638
- * from the originating Ship's `GET /limits` fetch. Passed in rather than read from a
3639
- * module global so concurrent Ships against different API URLs cannot clobber each
3640
- * other's caps.
3814
+ * @param platformLimits - The platform limits (file-size / count / total-size caps and
3815
+ * the blocked extensions) the deploy read from `GET /limits` at its start. Passed in,
3816
+ * never held, so a validation always meets the limits the server states now.
3641
3817
  * @returns Promise resolving to an array of StaticFile objects.
3642
3818
  * @throws {ShipError} If called outside a browser or with invalid input.
3643
3819
  */
@@ -3678,7 +3854,7 @@ declare class Ship extends Ship$1 {
3678
3854
  * contract stays platform-neutral.
3679
3855
  */
3680
3856
  deploy(input: File[], options?: DeploymentOptions): Promise<DeploymentCreateResponse>;
3681
- protected processInput(input: DeployInput, options: DeploymentOptions): Promise<StaticFile[]>;
3857
+ protected processInput(input: DeployInput, options: DeploymentOptions, limits: PlatformLimits): Promise<StaticFile[]>;
3682
3858
  }
3683
3859
 
3684
- export { ACCOUNT_HEADER, API_KEY, API_PATHS, AUTH_BASE_PATH, type Account, type AccountAccess, type AccountDeleteResponse, type AccountGetResponse, type AccountKeyResponse, AccountPlan, type AccountPlanType, type AccountResource, type AccountRole, type Activity, type ActivityEvent, type ActivityListResponse, type ActivityMeta, type ApiDeployOptions, ApiHttp, type ApiHttpOptions, AuthMethod, type AuthMethodType, BUILD_SETTINGS, type BillingInterval, type BillingPortalSession, type BillingSyncResponse, type BuildFailureDetails, CALLER, type Caps, DEFAULT_API, DEPLOYMENT_CONFIG_FILENAME, DEPLOY_FIELDS, DEPLOY_FILE_GRAMMAR, DEPLOY_TOKEN, type DeployBodyContext, type DeployFile, type DeployFileEncoding, type DeployFileSpec, type DeployInput, type DeployTransport, 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, DomainVerification, type DomainVerificationType, 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, OAUTH_TOKEN, OAuthScope, type OAuthScopeType, PASSWORD_CONSTRAINTS, PUBLIC_DEPLOYMENT_TTL_SECONDS, type PingResponse, type Plan, type PlanChangeRequest, type PlanChangeResponse, type PlanDestination, type PlansResponse, type PlatformLimits, type RequestResult, type ResourceContext, SETUP_TROUBLESHOOTING_ANCHOR, SHIP_ENV, SHIP_VIA_ENV, SIGN_IN_RETURN_PARAM, type SPACheckDebug, type SPACheckRequest, type SPACheckResponse, SPA_CHECK_CONSTRAINTS, SPA_DEFAULT_CONFIG, type ScheduledChange, type SetupInstructionsResponse, Ship, type ShipClientOptions, ShipError, type ShipEvents, type ShipRequestInit, type StaticFile, TTL_CONSTRAINTS, type Token, type TokenCreateOptions, type TokenCreateResponse, type TokenDeleteResponse, TokenKind, type TokenKindType, type TokenListResponse, type TokenProvider, type TokenResource, type Transport, UNBUILT_PROJECT_MARKERS, UNSAFE_FILENAME_CHARS, USER_VISIBLE_EVENTS, 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, formatDuration, formatFileSize, formatTimeRemaining, generateDeploymentUrl, generateDomainUrl, getENV, getValidFiles, hasUnbuiltMarker, hasUnsafeChars, isBlockedExtension, isCustomDomain, isDeployment, isPlatformDomain, isShipError, normalizeVia, optimizeDeployPaths, personName, personShortName, pluralize, processFilesForBrowser, readBearerValue, serializeLabels, validateApiKey, validateApiUrl, validateBuildCommand, validateCaller, validateDeployFile, validateDeployPath, validateDeployToken, validateFileName, validateFiles, validateIdempotencyKey, validateOAuthToken, validateOutputDir, validatePassword, validateToken, validateTtl };
3860
+ export { ACCOUNT_HEADER, API_KEY, API_PATHS, AUTH_BASE_PATH, type Account, type AccountAccess, type AccountDeleteResponse, type AccountKeyResponse, AccountPlan, type AccountPlanType, AccountRefusal, type AccountRefusalType, type AccountResource, type AccountRole, type Activity, type ActivityEvent, type ActivityListResponse, type ActivityMeta, type ApiDeployOptions, ApiHttp, type ApiHttpOptions, AuthMethod, type AuthMethodType, BUILD_SETTINGS, type BillingInterval, type BillingPortalSession, type BillingSyncResponse, type BuildFailureDetails, CALLER, type Caps, type ConsoleErrand, type ConsolePaths, type ConsoleSection, DEFAULT_API, DEPLOYMENT_CONFIG_FILENAME, DEPLOY_FIELDS, DEPLOY_FILE_GRAMMAR, DEPLOY_TOKEN, type DeployBodyContext, type DeployFile, type DeployFileEncoding, type DeployFileSpec, type DeployInput, type DeployTransport, 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, DomainVerification, type DomainVerificationType, 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, OAUTH_TOKEN, OAuthScope, type OAuthScopeType, PASSWORD_CONSTRAINTS, PUBLIC_DEPLOYMENT_TTL_SECONDS, type PingResponse, type Plan, type PlanChangeRefusalDetails, type PlanChangeRequest, type PlanChangeResponse, type PlanDestination, type PlansResponse, type PlatformLimitKey, type PlatformLimits, type RequestResult, type ResourceContext, SECTIONS, SETUP_TROUBLESHOOTING_ANCHOR, SHIP_ENV, SHIP_VIA_ENV, SIGN_IN_RETURN_PARAM, type SPACheckDebug, type SPACheckRequest, type SPACheckResponse, SPA_CHECK_CONSTRAINTS, SPA_DEFAULT_CONFIG, type ScheduledChange, type SettingsFragment, type SetupInstructionsResponse, Ship, type ShipClientOptions, ShipError, type ShipEvents, type ShipRequestInit, type StaticFile, TTL_CONSTRAINTS, type Token, type TokenCreateOptions, type TokenCreateResponse, type TokenDeleteResponse, TokenKind, type TokenKindType, type TokenListResponse, type TokenProvider, type TokenResource, type Transport, UNBUILT_PROJECT_MARKERS, UNSAFE_FILENAME_CHARS, USER_VISIBLE_EVENTS, type UploadedFile, type UserVisibleActivityEvent, type ValidatableFile, type ValidationIssue, WEB_FILE_ACCEPT, __setTestEnvironment, accountOfPath, allValidFilesReady, assertShipJsonSyntax, billingReturnOf, calculateMD5, classifyToken, consolePaths, createAccountResource, createDeploymentResource, createDomainResource, createTokenResource, Ship as default, deserializeLabels, doors, errandOf, extractSubdomain, filterJunk, formatDuration, formatFileSize, formatTimeRemaining, generateDeploymentUrl, generateDomainUrl, getENV, getValidFiles, hasUnbuiltMarker, hasUnsafeChars, isBlockedExtension, isClaimPath, isCustomDomain, isDeployment, isInvitationPath, isPlatformDomain, isShipError, isUpgradePath, normalizeVia, optimizeDeployPaths, personName, personShortName, pluralize, processFilesForBrowser, readBearerValue, serializeLabels, validateApiKey, validateApiUrl, validateBuildCommand, validateCaller, validateDeployCount, validateDeployFile, validateDeployPath, validateDeployToken, validateFileName, validateFiles, validateIdempotencyKey, validateOAuthToken, validateOutputDir, validatePassword, validateToken, validateTtl };