@shipstatic/ship 2.6.0-beta.5 → 2.6.0-beta.6
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/THIRD-PARTY-LICENSES.md +1 -1
- package/dist/browser.d.ts +5 -4
- package/dist/browser.js +1 -1
- package/dist/browser.js.map +1 -1
- package/dist/cli.cjs +1 -1
- package/dist/cli.cjs.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -4
- package/dist/index.d.ts +5 -4
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../node_modules/.pnpm/@shipstatic+types@2.17.0-beta.3/node_modules/@shipstatic/types/dist/index.js","../src/shared/lib/md5.ts","../src/shared/lib/env.ts","../src/shared/lib/path.ts","../src/shared/lib/deploy-paths.ts","../src/shared/lib/file-rules.ts","../src/shared/lib/file-validation.ts","../src/shared/lib/junk.ts","../src/shared/lib/security.ts","../src/shared/core/deploy-files.ts","../src/node/core/node-files.ts","../src/index.ts","../src/node/index.ts","../src/shared/base-ship.ts","../src/shared/api/http.ts","../src/shared/events.ts","../src/shared/resources.ts","../src/shared/core/deploy-body.ts","../src/shared/lib/spa.ts","../src/shared/lib/validation.ts","../src/node/core/config.ts","../src/shared/core/credential-schema.ts","../src/shared/index.ts","../src/shared/core/constants.ts","../src/shared/lib/text.ts"],"sourcesContent":["/**\n * @file Shared TypeScript types, constants, and utilities for the ShipStatic platform.\n * This package is the single source of truth for all shared data structures.\n */\n// =============================================================================\n// DEPLOYMENT TYPES\n// =============================================================================\n/**\n * Deployment status constants\n */\nexport const DeploymentStatus = {\n PENDING: 'pending',\n SUCCESS: 'success',\n FAILED: 'failed',\n DELETING: 'deleting',\n};\n/**\n * Where a deployment came from — the origin-tracking vocabulary.\n *\n * A closed set with many authors. It lived in the API's config until\n * 2026-08-06, where being server-side made it unenforceable in the one\n * direction that matters — every client wrote a bare string, and a value\n * outside the set was **silently dropped** by the server, so a typo did not\n * fail anywhere. It stopped recording where deploys came from and said nothing.\n *\n * **The origin law: origin is declared by whatever we control — our code where\n * we ship code, our URL where we ship only a URL.** One rule decides every\n * member here and every future one:\n *\n * - **Where the platform ships CODE, the code declares it.** `web`, `sdk`,\n * `cli`, `git`, `n8n` and `vsc` are surfaces this platform authors, so each\n * names itself in its own source and nothing external is needed to tell them\n * apart.\n * - **Where the platform ships only a URL, the URL declares it.** A\n * marketplace listing runs somebody else's client against a bare endpoint —\n * every one of them the same server speaking the same protocol, and\n * indistinguishable in a request. The only thing such a listing's traffic\n * has in common is the URL its users were handed, so the hosted MCP serves\n * one DOOR per listing and the door's path IS the value: `gpt`, `cld`,\n * `crs`.\n *\n * **A member names the most specific surface the platform can honestly\n * claim**, which is what makes the two FALLBACKS fallbacks rather than peers\n * of the named surfaces. `mcp` is any MCP host that was never handed a door of\n * its own; `api` is a call that reached the REST API naming nothing at all.\n * Guessing past either would be inventing attribution rather than recording\n * it, which is the one thing this vocabulary exists to prevent.\n *\n * Every member is three lowercase characters — the property that lets a\n * channel door's path and its attribution be spelled the same. The suite pins\n * both the width and the channel members by name.\n */\nexport const DeploymentVia = {\n /** The web dashboard. */\n WEB: 'web',\n /** A program embedding the SDK directly. */\n SDK: 'sdk',\n /** The `ship` CLI. */\n CLI: 'cli',\n /** Any MCP host with no door of its own — the stdio server included. The family fallback. */\n MCP: 'mcp',\n /** The GitHub Action. */\n GIT: 'git',\n /** The n8n community node. */\n N8N: 'n8n',\n /** Channel: the ChatGPT App listing → `mcp.<domain>/gpt`. */\n GPT: 'gpt',\n /** The VS Code extension. */\n VSC: 'vsc',\n /** Channel: the Claude connectors directory listing → `mcp.<domain>/cld`. */\n CLD: 'cld',\n /** Channel: the Cursor marketplace listing → `mcp.<domain>/crs`. */\n CRS: 'crs',\n /**\n * A deploy that reached the REST API naming no origin at all — the\n * platform-wide fallback, one altitude below `mcp`'s family fallback.\n *\n * **The API stamps it, since 2026-08-15.** A deploy that names no origin —\n * or names one this vocabulary does not know — is stored as `api`, so a\n * stored `null` now means only that the row predates attribution.\n *\n * It was declared one wave ahead of that decision, deliberately: vocabulary\n * must exist before a consumer can adopt it, and adding a member costs a\n * full constellation convoy, so the word shipped first and the server\n * adopted it with no convoy standing between the decision and the deploy.\n */\n API: 'api',\n};\n// =============================================================================\n// DOMAIN TYPES\n// =============================================================================\n/**\n * Domain status constants\n *\n * - PENDING: DNS not configured\n * - PARTIAL: DNS partially configured\n * - SUCCESS: DNS fully verified\n * - PAUSED: Domain paused due to plan enforcement (billing)\n */\nexport const DomainStatus = {\n PENDING: 'pending',\n PARTIAL: 'partial',\n SUCCESS: 'success',\n PAUSED: 'paused',\n};\n/**\n * The envelope an `Idempotency-Key` must fit, and how long a replay lasts.\n *\n * Format lives here rather than on the server alone by the format-vs-policy\n * rule: a client can decide offline whether a key is well-formed, and the\n * API would reject the same value the same way.\n */\nexport const IDEMPOTENCY_KEY_CONSTRAINTS = {\n /**\n * HTTP header name. Here for the same reason {@link CALLER.HEADER} is: a\n * wire header has two ends, and the package that owns the value's format\n * is the only place both ends can read its name from.\n */\n HEADER: 'Idempotency-Key',\n MAX_LENGTH: 256,\n /** How long a stored 201 stays replayable. */\n WINDOW_SECONDS: 24 * 60 * 60,\n};\n/**\n * Normalize a `via` value from any transport — trimmed, lowercased, and a\n * member of {@link DeploymentVia}, or `undefined`.\n *\n * A format rule by this package's own test: a client can decide offline\n * whether a value is well-formed, and the API reaches the same verdict on the\n * same input. It lived server-side until 2026-08-06, which meant clients could\n * only learn their label was unusable by noticing analytics had gone quiet.\n *\n * **Not knowing your `via` is not an error** — an unrecognized value yields\n * `undefined` rather than throwing, because origin tracking is telemetry and a\n * deploy must never fail over it. A caller that has an honest default should\n * prefer it (`normalizeVia(process.env.SHIP_VIA) ?? DeploymentVia.CLI`): the\n * deploy really did come from the CLI, so recording that beats recording\n * nothing.\n */\nexport function normalizeVia(value) {\n if (!value || typeof value !== 'string')\n return undefined;\n const via = value.trim().toLowerCase();\n return Object.values(DeploymentVia).includes(via)\n ? via\n : undefined;\n}\n/**\n * Validate an idempotency key, returning the trimmed value or `undefined`\n * when none was supplied. Throws {@link ShipError.validation} when the value\n * cannot be sent — the same verdict the API would reach, reached earlier.\n */\nexport function validateIdempotencyKey(value) {\n if (value === undefined || value === null)\n return undefined;\n if (typeof value !== 'string') {\n throw ShipError.validation('Idempotency key must be a string.');\n }\n const key = value.trim();\n if (!key) {\n throw ShipError.validation('Idempotency key must not be empty.');\n }\n if (key.length > IDEMPOTENCY_KEY_CONSTRAINTS.MAX_LENGTH) {\n throw ShipError.validation(`Idempotency key must be at most ${IDEMPOTENCY_KEY_CONSTRAINTS.MAX_LENGTH} characters.`);\n }\n return key;\n}\n// =============================================================================\n// ACCOUNT TYPES\n// =============================================================================\n/**\n * Every plan an account can hold — the platform's whole plan vocabulary, in\n * one place, and nothing about what a plan is WORTH.\n *\n * A plan is a TIER and nothing else. Whether an account may act is a separate\n * fact (`Account.suspended`; deletion ends the session outright), so an\n * account keeps its tier through suspension and into deletion.\n *\n * - **Free** — `free`.\n * - **Billed** — `pro`, `team`. The plans a customer buys; the only plans\n * Stripe knows about, and the only ones the platform never sets by hand —\n * each is derived from the Stripe Subscription, which names its plan on the\n * Price it is on. They form a ladder: a dearer tier is a superset of the one\n * below it, and the API says which is next in {@link Account.upgrade}.\n * - **Granted** — `scale`, `sponsored`. Paid plans the operator confers by\n * hand; no Stripe subscription, no Checkout, no Stripe object at all. These\n * and `free` are the only plans an operator can set; a billed plan is only\n * ever Stripe's to confer.\n *\n * The numbers each plan confers — caps, sizes — are POLICY and are delivered\n * by the API (`GET /plans`, `GET /account`, `GET /limits`), never published\n * here: a price or a cap in a published package is pinned to whatever version\n * a client installed (`CLAUDE.md`, \"Validation: format vs policy\").\n */\nexport const AccountPlan = {\n FREE: 'free',\n PRO: 'pro',\n TEAM: 'team',\n SCALE: 'scale',\n SPONSORED: 'sponsored',\n};\n// =============================================================================\n// WIRE SURFACE\n// =============================================================================\n/**\n * Every path the public API answers on, declared once.\n *\n * The URL surface was written out in four places — the API's mounts, the\n * SDK's client, the dashboard's client, and the post-deploy smoke — so a\n * rename meant finding all four. The first three now read this table.\n *\n * The smoke (`cloudflare/api/smoke.mjs`) deliberately still spells its own:\n * five of its nine paths are `/admin/*`, which this table excludes by\n * design, and splitting one list between a registry and literals reads worse\n * than keeping it uniform.\n *\n * **What this guarantees, exactly.** Collection paths are mounted from here,\n * so producer and consumer cannot diverge. Item paths are declared here and\n * consumed by clients, but the API spells them relative to their mount\n * (`/:deployment/config`), so the table does not *generate* them — it is\n * held to them by `api/tests/architecture/api-paths.test.ts`, which fails if\n * any entry names a path no route answers. Some entries have no client yet\n * (`DEPLOYMENT_CONFIG`, `DOMAIN_PROPAGATION` — endpoints the SDK\n * deliberately does not reach); the fence is what keeps those honest rather\n * than merely asserted.\n *\n * **The operator surface is deliberately absent.** `/admin/*` paths belong\n * to `web/my`, for the same reason its row types do: this package is\n * published, and the operator surface is not public (see `CLAUDE.md`, \"Admin\n * types\"). A path here is a promise to every npm consumer; `/admin` is a\n * promise to one dashboard.\n *\n * Item paths are functions rather than templates so the key is interpolated\n * in one place, encoded the same way by every caller.\n */\nexport const API_PATHS = {\n DEPLOYMENTS: '/deployments',\n DEPLOYMENT: (deployment) => `/deployments/${deployment}`,\n DEPLOYMENT_CONFIG: (deployment) => `/deployments/${deployment}/config`,\n DOMAINS: '/domains',\n DOMAIN: (domain) => `/domains/${domain}`,\n DOMAIN_VERIFY: (domain) => `/domains/${domain}/verify`,\n DOMAIN_DNS: (domain) => `/domains/${domain}/dns`,\n DOMAIN_RECORDS: (domain) => `/domains/${domain}/records`,\n DOMAIN_SHARE: (domain) => `/domains/${domain}/share`,\n DOMAIN_PROPAGATION: (domain) => `/domains/${domain}/propagation`,\n DOMAINS_VALIDATE: '/domains/validate',\n TOKENS: '/tokens',\n TOKEN: (token) => `/tokens/${token}`,\n ACCOUNT: '/account',\n ACCOUNT_KEY: '/account/key',\n ACCOUNT_CLAIM: '/account/claim',\n ACTIVITIES: '/activities',\n LABELS: '/labels',\n LIMITS: '/limits',\n PLANS: '/plans',\n PING: '/ping',\n SETUP: '/setup',\n SPA_CHECK: '/spa-check',\n UPLOAD: '/upload',\n};\n/**\n * The deploy request's multipart field names — the other half of the wire\n * surface beside {@link API_PATHS}. `POST /deployments` (and the first-party\n * `/upload`) is multipart/form-data, and these are the names the API reads.\n *\n * Declared once because the body has three independent WRITERS — the SDK's\n * Node and browser body builders, and the n8n community node's hand-rolled\n * client (which cannot import this under n8n Cloud's zero-dependency rule,\n * and fences its restated copy instead) — and until this export every writer\n * restated the strings the API parses, with nothing comparing them.\n *\n * `FILES` carries one entry per file (the API reads it with `getAll`); every\n * other field is single. The `@internal` flags are serialized as the literal\n * string `'true'` and belong to first-party surfaces only.\n */\nexport const DEPLOY_FIELDS = {\n /** One entry per file — read with `getAll`. */\n FILES: 'files[]',\n /** JSON array of MD5 hex digests, index-aligned with `FILES`. */\n CHECKSUMS: 'checksums',\n /** JSON array of label strings. */\n LABELS: 'labels',\n /** The deploying surface's {@link DeploymentVia} member. */\n VIA: 'via',\n /** Plaintext password — the API hashes it server-side. */\n PASSWORD: 'password',\n /**\n * Requested lifetime in SECONDS — a duration, never an instant. The API\n * computes and stores the expiry, so the wire carries no client clock.\n * See {@link validateTtl}.\n */\n TTL: 'ttl',\n /** @internal Server-processing flag — first-party `/upload` only. */\n BUILD: 'build',\n /** @internal Server-processing flag — first-party `/upload` only. */\n PRERENDER: 'prerender',\n /** @internal Server-processing flag — first-party `/upload` only. */\n SPA: 'spa',\n /** @internal reCAPTCHA proof — `web/www`'s public uploader only. */\n CAPTCHA: 'captcha',\n};\n// =============================================================================\n// ERROR SYSTEM\n// =============================================================================\n/**\n * All possible error types in the ShipStatic platform.\n *\n * Developer-friendly key names map to stable wire-format string values.\n * Both the value and the type are exported under the same name so callers\n * can use `ErrorType.Validation` (value comparison) and `: ErrorType` (type\n * annotation) without ceremony — matching the pattern other status objects\n * (`DeploymentStatus`, `DomainStatus`, `AccountPlan`, `AuthMethod`) follow.\n */\nexport const ErrorType = {\n /**\n * Validation failed. Input shape is wrong.\n *\n * Carries 400 when an API judged it — including a client-side pre-check of a\n * rule the server enforces too, which keeps the error identical wherever it\n * was caught. **Statusless** when a client rejects something no API judges,\n * such as a CLI's own command grammar: `status` is documented \"(API\n * contexts)\" on `ErrorResponse`, so there is none to report.\n */\n Validation: 'validation_failed',\n /** Resource not found (404). */\n NotFound: 'not_found',\n /** Authenticated but not allowed (403). User lacks permission for this action. */\n Forbidden: 'forbidden',\n /** Rate limit exceeded (429). */\n RateLimit: 'rate_limit_exceeded',\n /** Authentication required or failed (401). Missing/invalid credentials. */\n Authentication: 'authentication_failed',\n /** Business rule violation. Catch-all for 4xx state-rule errors that aren't more specific. */\n Business: 'business_logic_error',\n /** API server error (500). Generic server-side fault. */\n Api: 'internal_server_error',\n /**\n * The platform is closed for maintenance (503). A deliberate operator\n * state, not a fault — nothing errored; the API is refusing work on\n * purpose, and deployed sites keep serving throughout.\n *\n * Distinct from `Api` at 503, which the platform already uses for a\n * dependency that failed (moderation unavailable). A consumer has to tell\n * \"we closed the door\" from \"something broke\": the two get opposite words\n * and opposite retry behaviour.\n */\n Maintenance: 'maintenance',\n /** Network/connection error. Client-side only — set by HTTP clients on fetch failure; never produced server-side. */\n Network: 'network_error',\n /**\n * A deadline expired before the exchange completed. Client-side only — set\n * by HTTP clients when a timeout signal fires; never produced server-side.\n *\n * A member of the NETWORK category rather than a sibling of it:\n * `isNetworkError()` answers \"nothing was exchanged\", which is true of a\n * deadline exactly as it is of a refused connection, so every consumer that\n * retries, declines to report, or declines to relay a wire message on that\n * category is already right about a timeout. The distinct TYPE exists for\n * the one decision the category cannot make — what to SAY. \"Check your\n * internet connection\" is the wrong sentence for a five-minute deploy\n * ceiling, and a surface can only tell the two apart by type.\n *\n * The same relationship every comparable SDK ships:\n * `APIConnectionTimeoutError extends APIConnectionError`.\n */\n Timeout: 'timeout_error',\n /** Operation was cancelled. Client-side only — set on `AbortSignal` abort; never produced server-side. */\n Cancelled: 'operation_cancelled',\n /** File operation error. Client-side only — set by SDK during local file processing; never produced server-side. */\n File: 'file_error',\n /** Configuration error. Client-side only — set by SDK during config parsing/validation; never produced server-side. */\n Config: 'config_error',\n};\n/**\n * Error types that originate exclusively on the client (HTTP clients, SDK\n * file processing, local config parsing). These never appear on the wire\n * from the server, so `fromHttpResponse` will not trust them even if a\n * misbehaving server claims one in `body.error`.\n */\nconst CLIENT_ONLY_ERROR_TYPES = new Set([\n ErrorType.Network,\n ErrorType.Timeout,\n ErrorType.Cancelled,\n ErrorType.File,\n ErrorType.Config,\n]);\n/**\n * Categorizes error types for the `isClientError` / `isNetworkError` /\n * `isAuthError` helpers. Each `Set` is typed against the wider `ErrorType`\n * union so `.has(error.type)` accepts any value from the union.\n */\nconst ERROR_CATEGORIES = {\n /**\n * Client-attributable types. Exhaustive over the 4xx-carrying types, and\n * over the statusless ones too — those are raised locally and have no\n * status for `isClientError`'s second arm to read, so omitting one makes it\n * read as a server fault. The rule is the membership test: every type in\n * `CLIENT_ONLY_ERROR_TYPES` except the two `isNetworkError` owns belongs\n * here.\n *\n * `Cancelled` was missing until 2026-07-29, which is exactly that failure:\n * a caller who aborted their own deploy was told \"server error: please try\n * again\" — the CLI's fallback for everything this set does not claim.\n *\n * `Timeout` is deliberately NOT here, and it is the sharper case, because\n * it is the one client-only type that is not the client's fault: the\n * caller set a ceiling, but what exhausted it was the network or the\n * server. Reading it as client-attributable would say the caller erred,\n * and it would silently disarm every consumer whose retry predicate\n * declines `isClientError()` — a deadline is precisely the failure worth\n * a second attempt.\n */\n client: new Set([\n ErrorType.Business,\n ErrorType.Cancelled,\n ErrorType.Config,\n ErrorType.File,\n ErrorType.Forbidden,\n ErrorType.NotFound,\n ErrorType.RateLimit,\n ErrorType.Validation,\n ]),\n /**\n * The exchange never happened. Two types, one category: a refused\n * connection and an expired deadline differ in what a surface should SAY\n * and in nothing else a consumer decides on — both are retryable, neither\n * carries a wire message to relay, neither is worth reporting as an\n * incident. See `ErrorType.Timeout` for why the type is distinct anyway.\n */\n network: new Set([ErrorType.Network, ErrorType.Timeout]),\n auth: new Set([ErrorType.Authentication]),\n};\n/**\n * Error types the server can legitimately produce on the wire. Used by\n * `ShipError.fromHttpResponse` to validate the body's `error` field before\n * trusting it as `ShipError.type`. Derived by exclusion from\n * `CLIENT_ONLY_ERROR_TYPES` so adding a new server-producible type to\n * `ErrorType` is automatically picked up.\n */\nconst SERVER_PRODUCIBLE_ERROR_TYPES = new Set(Object.values(ErrorType).filter((t) => !CLIENT_ONLY_ERROR_TYPES.has(t)));\n/**\n * Ceiling on a message adopted from a **non-JSON** error body — a foreign\n * responder's, never this platform's. Generous for the plain-text one-liners\n * intermediaries actually send (`error code: 1015`), far below a document.\n * Our own messages are never measured against it: a JSON body is the API's\n * contract, and truncating a long validation message would be the bug.\n */\nconst MAX_FOREIGN_MESSAGE_LENGTH = 200;\n/**\n * Did the runtime say the exchange never completed?\n *\n * Clients branch on the TYPE, never on message strings, so a misclassified\n * transport failure is a lie every consumer inherits — and the one that costs\n * most: `Api` claims a server answered when nothing was exchanged, and a\n * retrying caller will not retry it.\n *\n * **Every row below is a transcript, not a belief.** Captured 2026-08-12\n * against real runtimes — Node and Bun by direct run, the three engines by a\n * one-off playwright probe, workerd through miniflare. The capture scripts are\n * in `tests/errors.test.ts`, \"runtime failure shapes\".\n *\n * | runtime | connection refused / DNS failure | malformed URL |\n * |------------------|------------------------------------------------------|--------------------------------------------------|\n * | Node 22 / undici | `TypeError: fetch failed` | `TypeError: Failed to parse URL from …` |\n * | Bun 1.3.14 | `Error` `code:'ConnectionRefused'` | `TypeError` `code:'ERR_INVALID_URL'` |\n * | Chromium 151 | `TypeError: Failed to fetch` | `TypeError: …Failed to parse URL from …` |\n * | Firefox 153 | `TypeError: NetworkError when attempting to fetch …` | `TypeError: … is not a valid URL.` |\n * | WebKit 26.5 | `TypeError: Load failed` | `TypeError: URL is not valid or contains user …` |\n * | workerd | `Error: Network connection lost.` (DNS: `internal error; reference = …`) | `TypeError: Invalid URL: …` |\n *\n * Reading that table gives the rule, and it is the INVERSE of the obvious one.\n * The transport class is unbounded — every OS, TLS and DNS failure any engine\n * will ever name — while the class fetch raises for its own ARGUMENTS is\n * small, and every runtime names the URL when it complains about one. So the\n * bounded side is the one worth testing, and the residual risk points the safe\n * way: an unrecognised sentence lands on `Network`, which says only that\n * nothing was exchanged.\n *\n * That inversion is what fixes **WebKit**, whose `Load failed` carries no code\n * and no \"fetch\", and which every browser-SDK and `@shipstatic/drop` user on\n * Safari was hitting as `Api`. It also makes the six runtimes AGREE about a\n * malformed URL, which they did not before: the previous rule tested the\n * message for \"fetch\", and Chromium's and Firefox's URL complaints both\n * contain it, so the same mistake was `Network` on three engines and `Api` on\n * three.\n *\n * **workerd is the recorded gap.** It rejects with a plain `Error`, no code\n * and no shared sentence — and its two failure modes produce two unrelated\n * ones — so nothing here can classify it and it lands on `Api`. Left alone\n * rather than patched with a dialect string: the one consumer running ship in\n * that runtime (`cloudflare/mcp`) reaches the API through a service BINDING,\n * which is in-process and does not produce transport rejections at all.\n *\n * **The `TokenProvider` case stopped being a trade when clients gained\n * retries.** A caller's provider that throws a coded error is typed `Network`\n * here, which was recorded as \"both are wrong for it; `Network` is the cheaper\n * wrong\" — written when the classification decided only what a surface would\n * SAY. It now also decides whether the call is retried, and that turns the\n * cheaper wrong into the right answer: a `TokenProvider` is where minting and\n * refresh live, so the common one is an OAuth refresh over the network, and a\n * transient failure there is precisely what another attempt repairs.\n *\n * The residual cost is a deterministic provider fault — a genuinely missing\n * keychain entry — invoking the provider three times over a few hundred\n * milliseconds before failing with the same error. No request leaves the\n * process on any of them. That is the cheap direction of a bet whose other\n * side is a refused deploy, and suppressing it would need a way to mark\n * credential faults non-retryable: machinery with one holder, refused by the\n * estate's stopping rule. Provider failures that carry no code are `Api` and\n * are not retried at all, and a provider yielding nothing is `Authentication`\n * by the fail-closed invariant, which is likewise terminal.\n */\nfunction isTransportFailure(cause) {\n const code = cause.code;\n // Bun is the one runtime that puts a CODE on an argument error, so it is\n // excluded before the code arm can claim it.\n if (code === 'ERR_INVALID_URL')\n return false;\n // A string `code` is a runtime naming a transport-level failure. An\n // allowlist of codes was written first and rejected — the TLS row alone\n // would mean enumerating BoringSSL's certificate table, and a code nobody\n // guessed is precisely the bug this closes. A `DOMException`'s code is a\n // NUMBER, so aborts and timeouts never reach here.\n if (typeof code === 'string')\n return true;\n // WHATWG has fetch reject with a TypeError for BOTH halves — network error\n // and argument error — so among TypeErrors the URL is the discriminator.\n if (cause instanceof TypeError)\n return !/\\burl\\b/i.test(cause.message);\n // Anything else — an ordinary JS fault, or workerd — is not evidence.\n return false;\n}\n/**\n * Simple unified error class for both API and SDK\n */\nexport class ShipError extends Error {\n type;\n status;\n details;\n constructor(type, message, status, details) {\n super(message);\n this.type = type;\n this.status = status;\n this.details = details;\n this.name = 'ShipError';\n }\n /** Convert to wire format */\n toResponse() {\n // Strip authentication details when they carry an `internal` telemetry\n // tag (see `ShipError.authentication` JSDoc) — these are server-side\n // diagnostics like 'session_invalid' that must not leak to clients.\n const authDetails = this.details;\n const details = this.type === ErrorType.Authentication && authDetails?.internal ? undefined : this.details;\n return {\n error: this.type,\n message: this.message,\n status: this.status,\n details,\n };\n }\n /**\n * Construct a `ShipError` from an HTTP error response.\n *\n * Best-effort body parse for `{ message, error?, details? }`. Message\n * resolution: `body.message` → `body.error` → `\"<operationName> failed with\n * status <N>\"`.\n *\n * Type resolution: trusts `body.error` when it's a known server-producible\n * `ErrorType` (preserves the wire's intent — server's\n * `ShipError.validation(...)` round-trips back to `ErrorType.Validation`\n * on the client). Falls back to status-derived (401 → Authentication,\n * 403 → Forbidden, 429 → RateLimit, else → Api) for non-API responses\n * (CDN errors, intermediaries) or malformed bodies. Client-only types\n * (`Network`, `Timeout`, `Cancelled`, `File`, `Config`) are filtered out of the\n * trusted set — a misbehaving server claiming one of those is ignored.\n *\n * `operationName` (e.g. `\"Get account\"`) is used to compose the fallback\n * message. Defaults to `\"Request\"`. Same convention as `fromFetchError`.\n *\n * Async because it reads the response body. Returns rather than throws so\n * callers can compose; most will `throw await ShipError.fromHttpResponse(...)`.\n */\n static async fromHttpResponse(response, operationName) {\n let message;\n let details;\n let bodyType;\n try {\n const contentType = response.headers.get('content-type');\n if (contentType?.includes('application/json')) {\n const json = await response.json();\n if (json && typeof json === 'object') {\n const obj = json;\n if (typeof obj.message === 'string')\n message = obj.message;\n else if (typeof obj.error === 'string')\n message = obj.error;\n details = obj.details;\n if (typeof obj.error === 'string' && SERVER_PRODUCIBLE_ERROR_TYPES.has(obj.error)) {\n bodyType = obj.error;\n }\n }\n }\n else {\n // A non-JSON body did not come from this platform — every API error\n // is `ErrorResponse` JSON — so it is an intermediary's output, and\n // the two kinds it produces need opposite treatment. A CDN's plain\n // `error code: 1015` is the most useful thing there is to say. A\n // proxy's HTML error page is a *document*, not a message: adopting it\n // verbatim made a misconfigured `apiUrl` print 2,059 characters of\n // markup as the error. Trust it only when it reads as a message.\n const text = (await response.text()).trim();\n if (text && !text.startsWith('<') && text.length <= MAX_FOREIGN_MESSAGE_LENGTH) {\n message = text;\n }\n }\n }\n catch {\n // Body unreadable; fall through to operationName-derived message.\n }\n // Rate-limit (and 503) timing rides the `Retry-After` HEADER, which a\n // body-only reader would drop. Lift it into `details` as seconds so\n // consumers can back off from the typed error alone, without keeping the\n // raw Response around. Body-carried fields are preserved and win.\n const retryAfterHeader = response.headers.get('retry-after');\n if (retryAfterHeader !== null) {\n const value = retryAfterHeader.trim();\n const seconds = /^\\d+$/.test(value)\n ? Number(value)\n : Math.ceil((Date.parse(value) - Date.now()) / 1000);\n if (Number.isFinite(seconds) && seconds >= 0) {\n const existing = details && typeof details === 'object' ? details : {};\n if (existing.retryAfter === undefined) {\n details = { ...existing, retryAfter: seconds };\n }\n }\n }\n message = message || `${operationName || 'Request'} failed with status ${response.status}`;\n const type = bodyType ??\n (response.status === 401\n ? ErrorType.Authentication\n : response.status === 403\n ? ErrorType.Forbidden\n : response.status === 429\n ? ErrorType.RateLimit\n : ErrorType.Api);\n return new ShipError(type, message, response.status, details);\n }\n /**\n * Construct a `ShipError` from an error caught around a `fetch()` call.\n *\n * The mirror of `fromHttpResponse` for the *other* side of the HTTP error\n * story — the network layer failing (offline, CORS, abort) rather than the\n * server returning a non-OK response.\n *\n * Routing:\n * - Already a `ShipError` → returned as-is (caller's intent preserved)\n * - `AbortError` → `ShipError.cancelled(...)` — someone stopped it on purpose\n * - `TimeoutError` → `ShipError.timeout(...)` — a deadline expired; the\n * message names the timeout, and the type is in the network CATEGORY\n * because nothing was exchanged\n * - A transport failure → `ShipError.network(...)` — see `isTransportFailure`\n * for what each runtime offers as evidence\n * - Any other `Error` → `ShipError(Api, ...)` (no HTTP status — fetch never reached the server)\n * - Anything else (string, undefined, etc.) → `ShipError(Api, ...)`\n *\n * **Abort and timeout are read from `name` BEFORE any `instanceof Error`\n * gate.** A `DOMException` satisfies that gate in every runtime measured\n * (Node, Bun, Chromium, Firefox, WebKit, workerd — all six), but the\n * inheritance is a comparatively recent spec change and this classification\n * has no reason to depend on it: `name` is where the meaning lives, and\n * reading it first costs nothing. The suite plants a non-`Error`\n * `DOMException` shape to hold the arm, since no runtime on the table\n * produces one.\n *\n * A caller's own `AbortSignal.timeout()` is the reachable source of\n * `TimeoutError` — and the two are NOT interchangeable per runtime: WebKit\n * reports a fired `AbortSignal.timeout()` as `AbortError`, so on Safari a\n * deadline is indistinguishable from a cancellation and lands on\n * `Cancelled`. Recorded rather than worked around; `Cancelled` is honest\n * there, since the caller's signal is what stopped it.\n *\n * The optional `operationName` is composed into the message for context:\n * `\"Get account was cancelled\"`, `\"Get account failed: ...\"`. Defaults to\n * `\"Request\"` when omitted.\n */\n static fromFetchError(cause, operationName) {\n if (isShipError(cause))\n return cause;\n const op = operationName || 'Request';\n // Read by NAME, ahead of the Error gate — see the note above.\n const name = cause?.name;\n if (name === 'AbortError') {\n return ShipError.cancelled(`${op} was cancelled`);\n }\n if (name === 'TimeoutError') {\n // A deadline: not a fault, not a cancellation, and — since it has its\n // own type — no longer merely \"network\". Nothing was exchanged, which\n // is what keeps it in the network CATEGORY and therefore retryable; the\n // type is what lets a surface say \"timed out\" instead of sending\n // someone to check their Wi-Fi. The runtime's own sentence is dropped\n // rather than relayed: \"The operation was aborted due to timeout\" is\n // the mechanism, not the news.\n return ShipError.timeout(`${op} timed out`, { cause });\n }\n if (cause instanceof Error) {\n if (isTransportFailure(cause)) {\n return ShipError.network(`${op} failed: ${cause.message}`, { cause });\n }\n return new ShipError(ErrorType.Api, `${op} failed: ${cause.message}`);\n }\n return new ShipError(ErrorType.Api, `${op} failed: Unknown error`);\n }\n // Factory methods. Uniform shape `(message, details?)` with two principled\n // exceptions: `notFound` composes its message from (resource, id?), and\n // `business` / `api` accept an optional status because they're the\n // multi-status fallbacks.\n static validation(message, details) {\n return new ShipError(ErrorType.Validation, message, 400, details);\n }\n static notFound(resource, id) {\n const message = id ? `${resource} ${id} not found` : `${resource} not found`;\n return new ShipError(ErrorType.NotFound, message, 404);\n }\n static forbidden(message, details) {\n return new ShipError(ErrorType.Forbidden, message, 403, details);\n }\n static rateLimit(message = 'Too many requests', details) {\n return new ShipError(ErrorType.RateLimit, message, 429, details);\n }\n /**\n * Construct an Authentication (401) error.\n *\n * **Telemetry pattern — `details: { internal: '<tag>' }`.** When the\n * server creates an auth error with an `internal` key in `details`\n * (e.g. `{ internal: 'session_invalid' }`), `toResponse()` strips the\n * entire `details` object before serialization. This keeps the wire\n * response a clean \"Authentication failed\" while preserving granular\n * server-side telemetry (which strategy/check failed) for logs and tests.\n *\n * Use this pattern in API auth code; do not put client-visible info under\n * `internal`. Other `details` keys round-trip normally.\n */\n static authentication(message = 'Authentication required', details) {\n return new ShipError(ErrorType.Authentication, message, 401, details);\n }\n static business(message, status = 400, details) {\n return new ShipError(ErrorType.Business, message, status, details);\n }\n static network(message, details) {\n return new ShipError(ErrorType.Network, message, undefined, details);\n }\n /**\n * A deadline expired before the exchange completed.\n *\n * Statusless like its four client-only siblings: no exchange completed, so\n * there is no HTTP status to report. `isNetworkError()` is true — see\n * `ErrorType.Timeout` for why the category is shared and the type is not.\n */\n static timeout(message, details) {\n return new ShipError(ErrorType.Timeout, message, undefined, details);\n }\n static cancelled(message, details) {\n return new ShipError(ErrorType.Cancelled, message, undefined, details);\n }\n static file(message, details) {\n return new ShipError(ErrorType.File, message, undefined, details);\n }\n static config(message, details) {\n return new ShipError(ErrorType.Config, message, undefined, details);\n }\n static api(message, status = 500, details) {\n return new ShipError(ErrorType.Api, message, status, details);\n }\n /**\n * The platform is closed for maintenance (503).\n *\n * `message` is REQUIRED and has no default here. The API is the only\n * producer of that sentence, and a default in this file would be a second\n * owner of one fact — see CLAUDE.md, \"The Constellation Law\" (stopping\n * rule). It is also the one factory whose status is fixed rather than\n * defaulted: a maintenance refusal is 503 or it is not this error.\n */\n static maintenance(message, details) {\n return new ShipError(ErrorType.Maintenance, message, 503, details);\n }\n // Semantic-category guards. For specific-type checks, use\n // `error.type === ErrorType.X` directly or the generic `isType(t)`.\n /**\n * The caller is at fault — by HTTP's own definition of a 4xx, or by a type\n * that is client-attributable without ever having a status (`Config`,\n * `File`, raised locally by the SDK).\n *\n * Both arms are load-bearing, because type and status are independent\n * axes. `fromHttpResponse` trusts `body.error` only when it names a\n * server-producible type; a non-OK response without one is status-derived,\n * so a CDN 404 or any intermediary error arrives as `Api` — a server-fault\n * *type* carrying a client *status*. Judging by type alone would report it\n * as a platform failure and bury the server's own message.\n */\n isClientError() {\n if (ERROR_CATEGORIES.client.has(this.type))\n return true;\n return this.status !== undefined && this.status >= 400 && this.status < 500;\n }\n isNetworkError() {\n return ERROR_CATEGORIES.network.has(this.type);\n }\n isAuthError() {\n return ERROR_CATEGORIES.auth.has(this.type);\n }\n isType(errorType) {\n return this.type === errorType;\n }\n}\n/**\n * Type guard to check if an unknown value is a ShipError.\n *\n * Uses structural checking instead of instanceof to handle module duplication\n * in bundled applications where multiple copies of the ShipError class may exist.\n *\n * @example\n * if (isShipError(error)) {\n * console.log(error.status, error.message);\n * }\n */\nexport function isShipError(error) {\n return (error !== null &&\n typeof error === 'object' &&\n 'name' in error &&\n error.name === 'ShipError' &&\n 'status' in error);\n}\n// =============================================================================\n// EXTENSION MATCHING\n// =============================================================================\n/**\n * The rule for reading a file's extension: lowercase, after the last dot of\n * the last path segment. `null` when there is no extension to read.\n *\n * A leading dot names the file rather than its type, so `.gitignore` and\n * `.htaccess` have no extension — but `.env.exe` has `exe`.\n *\n * Segment-aware on purpose: the callers pass deploy PATHS, not basenames, and\n * a naive `lastIndexOf('.')` over `dir.v1/README` reads the extension\n * `v1/README` — safe only by accident, since no entry in a real blocklist\n * contains a slash, which is the kind of correctness nobody should have to\n * re-derive.\n *\n * **Private, and the reason is the asymmetry rather than any hazard.** Nothing\n * outside this file reads it: `isBlockedExtension` is the only question anyone\n * asks, and the API's refusal names the FILE, not its extension. Exporting a\n * pure function is harmless, which is exactly the argument that talks a\n * published package into surface it has not earned — and the costs do not\n * match, since adding an export later is free under the additive law while\n * removing one is a major. So it stays private until a caller exists. Its\n * behaviour is fenced through `isBlockedExtension`, which is where it is\n * observable.\n *\n * (`WEB_FILE_EXTENSIONS` above is private for a different reason — publishing\n * it would invite a wrong question. Both are private; only one is a hazard.)\n *\n * @example\n * fileExtension('virus.exe') // 'exe'\n * fileExtension('assets/style.CSS') // 'css'\n * fileExtension('dir.v1/README') // null\n * fileExtension('.gitignore') // null\n * fileExtension('file.') // null\n */\nfunction fileExtension(filename) {\n const basename = filename.replace(/\\\\/g, '/').split('/').pop() ?? '';\n const dotIndex = basename.lastIndexOf('.');\n if (dotIndex <= 0 || dotIndex === basename.length - 1)\n return null;\n return basename.slice(dotIndex + 1).toLowerCase();\n}\n/**\n * Whether a file is one the platform refuses to host.\n *\n * **The list is not this package's, and that separation is the point.** What\n * counts as a blocked extension is hosting POLICY — it evolves, it is enforced\n * at one security boundary, and `virus.exe` is a perfectly well-formed\n * filename that breaks nothing about the upload→serve round-trip. So the API\n * owns the list (`cloudflare/api/src/lib/blocklist.ts`) and delivers it as\n * `PlatformLimits.blockedExtensions`; a client passes what it was given.\n *\n * What lives here is the MATCHING RULE, and it earns its place by the\n * constellation law's own test. The list's drift is loud in both directions —\n * a stale client uploads a file the API refuses by name, on the first try.\n * A second *matcher* drifts SILENTLY in the one direction that matters: a\n * client stricter than the server refuses a legal file without the server ever\n * being asked, and no error names it. Two holders, silent drift, one owner.\n *\n * The `blocked` collection is required rather than defaulted: this predicate\n * guards a security boundary in the API, and a defaulted-empty argument there\n * would block nothing while reading as though it did. Callers holding a\n * possibly-absent wire field spell the fail-open themselves.\n *\n * @example\n * isBlockedExtension('virus.exe', ['exe']) // true\n * isBlockedExtension('virus.EXE', ['exe']) // true — case-insensitive\n * isBlockedExtension('style.css', ['exe']) // false\n * isBlockedExtension('README', ['exe']) // false — no extension\n */\nexport function isBlockedExtension(filename, blocked) {\n const ext = fileExtension(filename);\n if (ext === null)\n return false;\n return Array.isArray(blocked) ? blocked.includes(ext) : blocked.has(ext);\n}\n// =============================================================================\n// PICKER ACCEPT HINT\n// =============================================================================\n/**\n * The extensions a browser file picker offers by default, grouped by role.\n *\n * Private on purpose: the only published form is `WEB_FILE_ACCEPT`, the\n * attribute value itself. A published set would invite a call site to ask it\n * whether a file is allowed — which is the one thing this list must never\n * answer. See `WEB_FILE_ACCEPT`.\n *\n * Extensionless files (`LICENSE`, most `.well-known` entries) are inexpressible\n * in `accept`, and reach a deployment by folder pick, ZIP, or drag-and-drop.\n */\nconst WEB_FILE_EXTENSIONS = [\n // Markup & documents\n 'html',\n 'htm',\n 'xhtml',\n 'xml',\n 'txt',\n 'md',\n 'markdown',\n 'pdf',\n 'csv',\n // Data & config\n 'json',\n 'jsonc',\n 'webmanifest',\n 'map',\n 'toml',\n 'yaml',\n 'yml',\n 'rss',\n 'atom',\n // Styles\n 'css',\n 'scss',\n 'sass',\n 'less',\n // Scripts & modules\n 'js',\n 'mjs',\n 'cjs',\n 'jsx',\n 'ts',\n 'tsx',\n 'wasm',\n 'vue',\n 'svelte',\n // Images\n 'png',\n 'jpg',\n 'jpeg',\n 'gif',\n 'webp',\n 'avif',\n 'svg',\n 'ico',\n 'bmp',\n 'tif',\n 'tiff',\n 'heic',\n 'heif',\n // Fonts\n 'woff',\n 'woff2',\n 'ttf',\n 'otf',\n 'eot',\n // Audio\n 'mp3',\n 'wav',\n 'ogg',\n 'oga',\n 'opus',\n 'm4a',\n 'aac',\n 'flac',\n 'weba',\n // Video\n 'mp4',\n 'webm',\n 'ogv',\n 'mov',\n 'm4v',\n 'avi',\n // 3D models\n 'glb',\n 'gltf',\n 'usdz',\n // Text tracks\n 'vtt',\n 'srt',\n // Archive — a whole site in one file\n 'zip',\n];\n/**\n * The `accept` attribute value for a browser file picker offering web files.\n *\n * **This is a hint, never a rule.** The API's blocklist is the platform's gate\n * and the only thing that decides what may be hosted; this constant decides\n * what a *file dialog* shows first. The two are not two halves of one policy,\n * and this one must never be consulted to accept or reject a file.\n *\n * The distinction is structural, not stylistic. `accept` can express only an\n * allowlist, while the platform's rule is a blocklist — so this list is\n * necessarily *narrower* than what the platform hosts, and reading it as\n * authority would reject files the platform serves happily. It is also not\n * enforcement in the browser's own terms: every file dialog offers an\n * all-files escape, and **drag-and-drop ignores `accept` entirely**. The\n * dropzone and the picker must reach the same verdict on the same files, and\n * they do — because the verdict is `validateFiles`, downstream of both.\n *\n * The invariant that matters — the picker must never offer a file the platform\n * will refuse — is fenced where the authority lives, in the API's own suite\n * (`cloudflare/api/tests/lib/blocklist.test.ts`), which reads this published\n * string and holds it against the list it owns. It sat here until the\n * blocklist became the API's, and moving it was the price of that: a fence\n * belongs with whichever side can change and break it.\n */\nexport const WEB_FILE_ACCEPT = WEB_FILE_EXTENSIONS.map((ext) => `.${ext}`).join(',');\n// =============================================================================\n// FILENAME CHARACTER VALIDATION\n// =============================================================================\n/**\n * Characters that are unsafe in filenames for static hosting.\n *\n * Blocks only characters that genuinely break the upload→serve round-trip:\n * - # ? % URL round-trip breakers (fragment, query, encoding ambiguity)\n * - \\ Path separator confusion (upload splits on backslash)\n * - < > \" XSS vectors with zero legitimate use in filenames\n * - \\x00-\\x1f \\x7f Control characters (header injection, display corruption)\n *\n * Everything else is allowed — browser percent-encodes, Worker decodes, R2 matches.\n */\n// biome-ignore lint/suspicious/noControlCharactersInRegex: blocking control characters is this regex's purpose\nexport const UNSAFE_FILENAME_CHARS = /[\\x00-\\x1f\\x7f#?%\\\\<>\"]/;\n/**\n * Check if a filename contains unsafe characters.\n *\n * @example\n * hasUnsafeChars('saved_resource(1).html') // false — parentheses are safe\n * hasUnsafeChars('page[slug].js') // false — brackets are safe\n * hasUnsafeChars('file#anchor.html') // true — # breaks URL resolution\n * hasUnsafeChars('file<tag>.html') // true — < is an XSS vector\n */\nexport function hasUnsafeChars(filename) {\n return UNSAFE_FILENAME_CHARS.test(filename);\n}\n// =============================================================================\n// UNBUILT PROJECT MARKERS\n// =============================================================================\n/**\n * Path segment names that indicate an unbuilt project was uploaded instead of build output.\n * Used for early detection in CLI, browser, and server validation.\n */\nexport const UNBUILT_PROJECT_MARKERS = new Set([\n 'node_modules',\n 'package.json',\n]);\n/**\n * Check if a file path contains an unbuilt project marker.\n *\n * @example\n * hasUnbuiltMarker('node_modules/react/index.js') // true\n * hasUnbuiltMarker('package.json') // true\n * hasUnbuiltMarker('dist/index.html') // false\n */\nexport function hasUnbuiltMarker(filePath) {\n const segments = filePath.replace(/\\\\/g, '/').split('/').filter(Boolean);\n return segments.some((s) => UNBUILT_PROJECT_MARKERS.has(s));\n}\n// =============================================================================\n// CREDENTIAL SHAPES\n// =============================================================================\n// The one address for credential vocabulary: where human identity lives\n// (AUTH_BASE_PATH), how a request is authorized (AuthMethod), the shapes\n// that distinguish populations on the wire (API_KEY, DEPLOY_TOKEN,\n// OAUTH_TOKEN, CALLER), the two halves of the one Bearer slot\n// (readBearerValue reads it, classifyToken/TokenKind dispatch on what came\n// out), and the delegated-access scopes (OAuthScope).\n//\n// THE SHAPE LAW, in three clauses, over the `Authorization: Bearer` slot's\n// three populations below. The deployment claim code is the API's own\n// (`AUTH.CLAIM`, server-side: the API mints it and the API validates it, so\n// it has one holder and stays there) and shares only clause 1 — it is the\n// platform's one deliberately BARE secret, because it never enters the\n// Bearer slot: minted into one URL, consumed by one endpoint's one field,\n// its context names it and a prefix would restate its route.\n// `tests/validation-constants.test.ts` holds the clauses over this file's\n// populations; the API's suite holds its own.\n//\n// 1. ONE ENTROPY STANDARD. Every minted secret is `HEX_LENGTH` hex characters\n// — one width for the whole platform, so \"how long is a credential\" has a\n// single answer rather than one per population. Generators read the width\n// from the population's own constant, so a minted value and an accepted\n// value cannot differ.\n//\n// 2. EVERY BEARER POPULATION IS NAMED BY ITS PREFIX. A credential says what\n// it is before anything parses it — which is what lets `classifyToken`\n// below dispatch three populations sharing one `Authorization: Bearer`\n// slot, and what lets a value found in a log, a support ticket or a\n// pasted URL be recognised and revoked on sight. The OAuth access token\n// was this clause's one standing exception until 2026-08-14 — the\n// authorization server it was born on had no mint hook to give it a\n// prefix, and its successor does.\n//\n// 3. NO PREFIX IS A PREFIX OF ANOTHER. This is what makes the dispatch\n// order-independent, and it is the reason the populations are named on\n// different axes (`ship-` for the product, `deploy-` for the capability)\n// rather than sharing a stem. A `ship-` / `ship-deploy-` pair\n// reads tidier and is a trap: every deploy token would also match the\n// API-key branch, leaving correctness resting on the order of two `if`s.\n/**\n * Where human identity is mounted on the API host. The API mounts Better\n * Auth at this path (sign-in, sign-out, session reads, admin impersonation)\n * and the web console's auth client posts to it — shared here so the two\n * halves of the auth pair agree by construction, the same way both sides\n * already share the credential prefixes below.\n */\nexport const AUTH_BASE_PATH = '/auth';\n/**\n * The query marker a completed sign-in LANDS with.\n *\n * The API's magic-link verify leg stamps `?signing-in=1` onto its success\n * redirect, and the console boots into its wait screen on seeing it — two\n * repos, one spelling, which is why it lives here. Success is marked and the\n * error leg deliberately is NOT: the console gives the marker precedence, so\n * a marked error would render a wait that resolves to bare doors with the\n * error's sentence lost. If the spellings ever diverged the failure would be\n * invisible to every suite — email landings would flash the doors for one\n * round trip instead of waiting — which is exactly the silent-drift class\n * this constitution exists to delete.\n */\nexport const SIGN_IN_RETURN_PARAM = 'signing-in';\n/**\n * How a request (or recorded activity) was authorized.\n *\n * Client populations: `SESSION` (first-party cookie), `API_KEY` (`ship-`\n * key), `TOKEN` (`deploy-` deploy token), `AGENT` (anonymous public deploy —\n * no credential; the platform grants the public-account identity per\n * request), `OAUTH` (delegated access token). Server populations: `WEBHOOK`\n * (signed webhook processing), `SYSTEM` (scheduled/background jobs).\n */\nexport const AuthMethod = {\n SESSION: 'session',\n API_KEY: 'apiKey',\n TOKEN: 'token',\n AGENT: 'agent',\n OAUTH: 'oauth',\n WEBHOOK: 'webhook',\n SYSTEM: 'system',\n};\n/**\n * Shape constants for API keys (`ship-{32 hex chars}`).\n * Single source of truth used by validation utilities and auth middleware.\n */\nexport const API_KEY = {\n /** Prefix that identifies an API key. */\n PREFIX: 'ship-',\n /** Number of hex characters following the prefix. */\n HEX_LENGTH: 32,\n /** Total length of an API key including prefix (`PREFIX.length + HEX_LENGTH = 37`). */\n TOTAL_LENGTH: 37,\n /** Number of trailing characters used to display a redacted hint (e.g. last 4). */\n HINT_LENGTH: 4,\n};\n/**\n * Shape constants for deploy tokens (`deploy-{32 hex chars}`).\n * Single source of truth used by validation utilities and auth middleware.\n *\n * Deliberately the same width as `API_KEY`: both are minted by one generator\n * and classified by prefix alone, so a length that differed between them\n * would be a second thing to know about a credential whose prefix already\n * says what it is.\n */\nexport const DEPLOY_TOKEN = {\n /** Prefix that identifies a deploy token. */\n PREFIX: 'deploy-',\n /** Number of hex characters following the prefix. */\n HEX_LENGTH: 32,\n /** Total length of a deploy token including prefix (`PREFIX.length + HEX_LENGTH = 39`). */\n TOTAL_LENGTH: 39,\n};\n/**\n * Shape constants for OAuth access tokens (`oauth-{32 hex chars}`) — the\n * delegated population, minted by the platform's own authorization server\n * for a connected app acting on a user's behalf.\n *\n * Same width as the other two, and for the same reason: one entropy standard\n * across the platform, so \"how long is a credential\" has one answer.\n *\n * **This population is the access token alone.** Refresh tokens, authorization\n * codes and client secrets are deliberately NOT here and are deliberately not\n * prefixed by this constant: none of them ever enters the `Authorization:\n * Bearer` slot — a refresh token is posted as a form field to the token\n * endpoint, which knows what it is receiving — so `classifyToken` never sees\n * one and a prefix would name a population no dispatcher dispatches. The same\n * reasoning that keeps the deployment claim code bare.\n *\n * **The prefix must be applied at the MINT, never as a display wrapper.** The\n * authorization server hashes what it stores and the API hashes what it is\n * presented, so the prefix has to be inside the hashed string on both sides.\n * `@better-auth/oauth-provider` offers a `prefix.opaqueAccessToken` option\n * that prepends AFTER hashing and strips on its own read paths; using it would\n * store a hash of the UNPREFIXED token and silently break the platform's read\n * arm. The API therefore mints through `generateOpaqueAccessToken` — recorded\n * beside the config in `cloudflare/api/src/lib/auth/instance.ts`.\n */\nexport const OAUTH_TOKEN = {\n /** Prefix that identifies an OAuth access token. */\n PREFIX: 'oauth-',\n /** Number of hex characters following the prefix. */\n HEX_LENGTH: 32,\n /** Total length including prefix (`PREFIX.length + HEX_LENGTH = 38`). */\n TOTAL_LENGTH: 38,\n};\n/**\n * Shape constants for caller identifiers (the `X-Caller` instance-identity\n * header — rate-limit bucketing for multi-tenant orchestrators). The API\n * normalizes case and silently ignores malformed values (the header is\n * unauthenticated); clients validate at the boundary via `validateCaller`,\n * so a value the server would drop fails fast instead.\n */\nexport const CALLER = {\n /** HTTP header name. */\n HEADER: 'X-Caller',\n /** Maximum identifier length. */\n MAX_LENGTH: 128,\n /** Allowed characters: alphanumeric, dot, underscore, hyphen. */\n PATTERN: /^[a-zA-Z0-9._-]+$/,\n};\n/**\n * Token populations distinguishable by shape. The platform carries every\n * client token in one wire slot (`Authorization: Bearer <value>`) and\n * classifies by value, never by a side channel — this is the classifier.\n *\n * `API_KEY`, `DEPLOY_TOKEN` and `OAUTH` *are* `AuthMethod.API_KEY`,\n * `AuthMethod.TOKEN` and `AuthMethod.OAUTH` — the equality is structural, so\n * a classification flows straight into an auth method and the trio can never\n * drift.\n *\n * `OPAQUE` is any other value, and since 2026-08-14 it names NO population:\n * every credential this platform mints for the Bearer slot carries a prefix,\n * so an opaque bearer is a bearer we did not mint. It stays a member rather\n * than becoming a `null` return because a dispatcher with a total codomain\n * reads better than one with an absence in it — and because it is where a\n * future population would land before anyone gave it a shape, which is\n * exactly what the OAuth token itself did until its prefix existed.\n */\nexport const TokenKind = {\n API_KEY: AuthMethod.API_KEY,\n DEPLOY_TOKEN: AuthMethod.TOKEN,\n OAUTH: AuthMethod.OAUTH,\n OPAQUE: 'opaque',\n};\n/**\n * Classify a client token by shape. The single dispatch used by both sides\n * of the wire: API auth middleware (which population is this credential?)\n * and SDK validation (which format rules apply before sending?). Sharing it\n * is what guarantees client and server can never disagree on dispatch.\n */\nexport function classifyToken(token) {\n if (token.startsWith(API_KEY.PREFIX))\n return TokenKind.API_KEY;\n if (token.startsWith(DEPLOY_TOKEN.PREFIX))\n return TokenKind.DEPLOY_TOKEN;\n if (token.startsWith(OAUTH_TOKEN.PREFIX))\n return TokenKind.OAUTH;\n return TokenKind.OPAQUE;\n}\n/** The auth-scheme, lowercased — the form the comparison is made in. */\nconst BEARER_SCHEME = 'bearer ';\n/**\n * Read the credential out of an `Authorization` header value — the step\n * BEFORE `classifyToken`, and the other half of the one wire slot this\n * section owns.\n *\n * Returns the credential's own bytes, or `null` when the header carries a\n * foreign scheme or nothing after the scheme.\n *\n * **The scheme is folded; the credential is not.** RFC 7235 §2.1 makes the\n * auth-scheme case-insensitive, so `bearer`, `Bearer` and `BEARER` are the\n * same header. The value after it is opaque and is compared literally\n * everywhere it is used — `ship-`/`deploy-`/`oauth-` are lowercase hex, and\n * folding them would make a credential match values it is not.\n *\n * **This platform has paid for the rule twice, which is why it has an owner\n * rather than a convention.** A spec-conformant `bearer ship-…` client was\n * refused for as long as the API's scheme test was spelled case-sensitively;\n * and `@better-auth/oauth-provider` carries the same defect in four places\n * today (`startsWith(\"Bearer \")`), which is precisely why the platform folds\n * the scheme itself and hands the provider a bare token.\n *\n * **ABSENCE is deliberately not this function's business.** A missing header\n * and an unreadable one are different facts, and the callers that care split\n * on them: the API worker's middleware distinguishes `absent` (the only\n * anonymous path) from `unreadable` (a presented credential that is refused),\n * and collapsing the two here would take that distinction away from the layer\n * that needs it. Callers check for the header themselves and pass its value.\n *\n * **Why this lives in the constitution rather than in a worker's `shared/`.**\n * It is the same wire boundary `classifyToken` already owns — one reads the\n * slot, the other dispatches on what came out — and a rule with two holders\n * whose drift is silent earns exactly one owner regardless of what the\n * convoy costs. The estate's recorded refusal to own a `Bearer` CONSTANT\n * stands and is a different thing: that is RFC vocabulary, the same reason\n * this package owns no `\"POST\"`. A parser is not a spelling.\n */\nexport function readBearerValue(header) {\n if (header.slice(0, BEARER_SCHEME.length).toLowerCase() !== BEARER_SCHEME)\n return null;\n return header.slice(BEARER_SCHEME.length) || null;\n}\n/**\n * OAuth scope vocabulary for delegated third-party access tokens.\n * Single source of truth used by the authorization server (advertised in\n * `scopes_supported`), the API's scope-enforcement middleware, and consent UI\n * copy. The standard `offline_access` scope (refresh tokens) is not platform\n * vocabulary and is deliberately absent — the middleware never checks it.\n *\n * Deliberately absent by design: any `tokens:*` scope, `account:write`, or\n * admin scope — a delegated app must never mint credentials, delete the\n * account, or act as admin.\n */\nexport const OAuthScope = {\n ACCOUNT_READ: 'account:read',\n DEPLOYMENTS_READ: 'deployments:read',\n DEPLOYMENTS_WRITE: 'deployments:write',\n DOMAINS_READ: 'domains:read',\n DOMAINS_WRITE: 'domains:write',\n};\n// =============================================================================\n// DEPLOYMENT CONFIGURATION CONSTANTS\n// =============================================================================\nexport const DEPLOYMENT_CONFIG_FILENAME = 'ship.json';\n/** Default ship.json config for SPA routing. Single source of truth — used by both API and SDK. */\nexport const SPA_DEFAULT_CONFIG = {\n rewrites: [{ source: '/(.*)', destination: '/index.html' }],\n};\n/**\n * The `/spa-check` pre-flight's client-side envelope: which file is the\n * check's subject, and how large it may be before a client skips the call.\n *\n * One fact with three holders until this export — the API's config declared\n * the cap, the SDK's `checkSPA` hardcoded `100 * 1024`, and prose restated\n * \"100KB\". `INDEX_FILE` is the selection rule (the file whose content rides\n * `SPACheckRequest.index`), restated by every client that builds the request.\n *\n * Neither member is a validation boundary: a client over the cap simply\n * skips the pre-flight, because the server answers an oversized index\n * `isSPA: false` anyway. A consumer that cannot import this (n8n) needs no\n * size copy at all — outcome parity is the server's, not the client's.\n */\nexport const SPA_CHECK_CONSTRAINTS = {\n /** The file whose content is the check's subject. */\n INDEX_FILE: 'index.html',\n /** Skip the pre-flight above this size — the server would answer false. */\n MAX_INDEX_BYTES: 100 * 1024,\n};\n/**\n * Assert that a ship.json file is *syntactically* loadable. Syntax only —\n * never schema.\n *\n * ship.json is validated and compiled on the server, deliberately: the schema\n * and the compiler evolve, and a client that judged them would reject configs\n * a newer platform accepts. That reasoning bounds what a client may check to\n * the properties which are true of *every* past and future schema:\n *\n * 1. it parses as JSON — JSON syntax is frozen (RFC 8259), so text that\n * does not parse can never be a valid config;\n * 2. its top level is an object — ship.json is `{ ... }` in every version.\n *\n * Both are monotonic: neither can ever reject something the server would\n * accept. Everything beyond them (field names, types, rule semantics, which\n * keys are permitted) stays server-side, where it can change.\n *\n * The payoff is the common case. Hand-edited JSON fails on a trailing comma,\n * a `//` comment, single quotes, unquoted keys, or smart quotes pasted from\n * documentation — mistakes that otherwise cost a full upload round-trip to\n * discover. A UTF-8 BOM (Windows editors, PowerShell redirects) is stripped\n * before parsing rather than rejected, because the server accepts it too;\n * diverging there would reintroduce exactly the false rejection this\n * function exists to avoid.\n *\n * @throws {ShipError} `ErrorType.Config` — the same type the server's own\n * config rejection carries, so the error contract is identical wherever the\n * failure is detected.\n */\nexport function assertShipJsonSyntax(text) {\n const withoutBom = text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;\n let parsed;\n try {\n parsed = JSON.parse(withoutBom);\n }\n catch (error) {\n throw ShipError.config(`invalid JSON format in config: ${error.message}`, {\n filePath: DEPLOYMENT_CONFIG_FILENAME,\n });\n }\n if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {\n throw ShipError.config(`${DEPLOYMENT_CONFIG_FILENAME} must contain a JSON object`, {\n filePath: DEPLOYMENT_CONFIG_FILENAME,\n });\n }\n}\n// =============================================================================\n// VALIDATION UTILITIES\n// =============================================================================\n/**\n * Shared rule for prefixed credentials: `{PREFIX}{HEX_LENGTH hex chars}`.\n * The regex derives from the shape constants, so the validators can never\n * drift from the shapes `classifyToken` dispatches on.\n */\nfunction validatePrefixedCredential(value, shape, label) {\n if (!value.startsWith(shape.PREFIX)) {\n throw ShipError.validation(`${label} must start with \"${shape.PREFIX}\"`);\n }\n if (value.length !== shape.TOTAL_LENGTH) {\n throw ShipError.validation(`${label} must be ${shape.TOTAL_LENGTH} characters total (${shape.PREFIX} + ${shape.HEX_LENGTH} hex chars)`);\n }\n const hexPart = value.slice(shape.PREFIX.length);\n if (!new RegExp(`^[a-f0-9]{${shape.HEX_LENGTH}}$`, 'i').test(hexPart)) {\n throw ShipError.validation(`${label} must contain ${shape.HEX_LENGTH} hexadecimal characters after \"${shape.PREFIX}\" prefix`);\n }\n}\n/**\n * Validate API key format\n */\nexport function validateApiKey(apiKey) {\n validatePrefixedCredential(apiKey, API_KEY, 'API key');\n}\n/**\n * Validate deploy token format\n */\nexport function validateDeployToken(deployToken) {\n validatePrefixedCredential(deployToken, DEPLOY_TOKEN, 'Deploy token');\n}\n/**\n * Validate OAuth access token format\n */\nexport function validateOAuthToken(oauthToken) {\n validatePrefixedCredential(oauthToken, OAUTH_TOKEN, 'OAuth access token');\n}\n/**\n * Validate a client token of any population. Classifies by shape and applies\n * the matching format rules: all three prefixed populations are validated\n * strictly; an OPAQUE token only needs to be non-empty.\n *\n * **The OPAQUE arm stays permissive on purpose**, even though the platform no\n * longer mints an unprefixed credential. It is the fallback for a population\n * that does not exist yet, and a client refusing a shape the server would\n * accept is the one failure mode this boundary must never have — the server\n * decides, and it refuses an unrecognised bearer anyway. Unprefixed OAuth\n * tokens from before 2026-08-14 land here and are refused server-side, which\n * is correct: they were revoked by the change, not grandfathered.\n */\nexport function validateToken(token) {\n switch (classifyToken(token)) {\n case TokenKind.API_KEY:\n validateApiKey(token);\n return;\n case TokenKind.DEPLOY_TOKEN:\n validateDeployToken(token);\n return;\n case TokenKind.OAUTH:\n validateOAuthToken(token);\n return;\n case TokenKind.OPAQUE:\n if (!token)\n throw ShipError.validation('Token must be a non-empty string');\n }\n}\n/**\n * Validate a caller identifier against the `CALLER` shape. The server\n * silently ignores malformed values (the header is unauthenticated); clients\n * call this at configuration time so the drop never silently happens.\n */\nexport function validateCaller(caller) {\n if (!caller || caller.length > CALLER.MAX_LENGTH || !CALLER.PATTERN.test(caller)) {\n throw ShipError.validation(`Caller must be 1-${CALLER.MAX_LENGTH} characters: letters, digits, dots, underscores, or hyphens`);\n }\n}\n/**\n * Validate API URL format\n */\nexport function validateApiUrl(apiUrl) {\n try {\n const url = new URL(apiUrl);\n if (!['http:', 'https:'].includes(url.protocol)) {\n throw ShipError.validation('API URL must use http:// or https:// protocol');\n }\n if (url.pathname !== '/' && url.pathname !== '') {\n throw ShipError.validation('API URL must not contain a path');\n }\n if (url.search || url.hash) {\n throw ShipError.validation('API URL must not contain query parameters or fragments');\n }\n }\n catch (error) {\n if (isShipError(error)) {\n throw error;\n }\n throw ShipError.validation('API URL must be a valid URL');\n }\n}\n/**\n * Check if a string matches the deployment identifier pattern (word-word-alphanumeric7).\n * Example: \"happy-cat-abc1234.shipstatic.com\"\n */\nexport function isDeployment(input) {\n return /^[a-z]+-[a-z]+-[a-z0-9]{7}(\\.[a-z0-9.-]+)?$/i.test(input);\n}\n/**\n * The envelope a requested lifetime must fit — one word, one grammar, wherever\n * the platform lets a caller choose how long something lives.\n *\n * Two resources wear it: `TokenCreateOptions.ttl` and\n * `DeploymentUploadOptions.ttl`. It lives here rather than on the server by\n * the format-vs-policy rule — a client can decide offline whether a duration\n * is well-formed, and the API rejects the same value the same way. What is\n * NOT here is any per-plan ceiling: no such policy exists, and one delivered\n * speculatively through `/limits` would be an owner for a decision nobody has\n * made.\n */\nexport const TTL_CONSTRAINTS = {\n /**\n * Shortest requestable lifetime, in seconds. One rather than zero: a\n * deployment that expires the instant it is created is not a shorter lease,\n * it is a deploy that was never live, and `0` is how an unset variable\n * arrives.\n */\n MIN_SECONDS: 1,\n /** Longest requestable lifetime, in seconds — one year. */\n MAX_SECONDS: 365 * 24 * 60 * 60,\n};\n/**\n * Validate a requested lifetime in SECONDS and return it, or `undefined` when\n * none was asked for.\n *\n * **A duration, never an instant.** The caller says how long; the server owns\n * what time it is and stamps the expiry — so a client's clock, however wrong,\n * cannot shorten or extend a lease. That is the tokens precedent, and it is\n * why this rule measures a count of seconds rather than checking a timestamp\n * against `now`.\n *\n * Fractions are refused rather than rounded: a caller who wrote `1.5` meant\n * something the wire cannot carry, and silently choosing `1` or `2` for them\n * is a decision the platform has no standing to make.\n *\n * Single source of truth shared by the API (the tokens route and the deploy\n * schema), the SDK's request boundary, and the CLI's parser.\n */\nexport function validateTtl(value) {\n if (value === undefined || value === null)\n return undefined;\n if (typeof value !== 'number' || !Number.isFinite(value)) {\n throw ShipError.validation('TTL must be a number of seconds');\n }\n if (!Number.isInteger(value)) {\n throw ShipError.validation('TTL must be a whole number of seconds');\n }\n if (value < TTL_CONSTRAINTS.MIN_SECONDS || value > TTL_CONSTRAINTS.MAX_SECONDS) {\n throw ShipError.validation(`TTL must be between ${TTL_CONSTRAINTS.MIN_SECONDS} and ${TTL_CONSTRAINTS.MAX_SECONDS} seconds`);\n }\n return value;\n}\n// =============================================================================\n// PLATFORM CONSTANTS\n// =============================================================================\n/** Default API URL if not otherwise configured. */\nexport const DEFAULT_API = 'https://api.shipstatic.com';\n/**\n * The Node SDK's ambient configuration pair — the ONLY environment variables\n * the SDK reads, and therefore the COMPLETE list an embedding host must\n * scrub (per `npm/ship`'s strict-isolation contract, scrubbing is the host's\n * job, not the SDK's). A host that derives its scrub from this object's\n * values — as the VS Code extension's child-process env block does — picks\n * up a grown contract at the next pin bump instead of by remembered prose.\n *\n * Browser builds read no environment at all, and the CLI-only variables\n * (`SHIP_PASSWORD`, `SHIP_VIA`) are deliberately NOT here: they are the\n * CLI's operational levers, not the SDK's ambient contract — see\n * `npm/ship/CLAUDE.md`, \"CLI-only env vars\".\n */\nexport const SHIP_ENV = {\n /** The one credential slot — any platform token. */\n TOKEN: 'SHIP_TOKEN',\n /** The API endpoint override. */\n API_URL: 'SHIP_API_URL',\n};\n/**\n * Where a human creates an API key — the console deep link quoted by every\n * surface that teaches authentication (the CLI's config wizard, the VS Code\n * and n8n listings, the n8n rate-limit hint and credential copy). Written\n * out in five files across three repos until this export.\n *\n * Production-branded by design: published artifacts name the product, never\n * an environment (root `CLAUDE.md`, \"Environment-Aware URLs\").\n */\nexport const MY_API_KEY_URL = 'https://my.shipstatic.com/api-key';\n/**\n * How long an anonymous deployment lives before it expires.\n *\n * The lifetime of the public tier, and one fact with several readers. The API\n * stamps a deployment's `expires` from it and gives a claim code exactly the\n * same window — a live site with a dead claim link is a coherence bug, so the\n * two are one constant rather than two that agree. Both MCP transports quote\n * the duration in prose an agent reads, and derive it from here rather than\n * writing it out, which they did in eight places until this export existed.\n *\n * Seconds, spelled in the name: this platform has both second- and\n * millisecond-valued durations, and the pair is only safe when each says which\n * it is.\n */\nexport const PUBLIC_DEPLOYMENT_TTL_SECONDS = 3 * 24 * 60 * 60;\n// =============================================================================\n// FILE UPLOAD TYPES\n// =============================================================================\n/**\n * File status constants for validation state tracking\n */\nexport const FileValidationStatus = {\n /** File is pending validation */\n PENDING: 'pending',\n /** File failed during processing (before validation) */\n PROCESSING_ERROR: 'processing_error',\n /** File was excluded by validation warning (not an error) */\n EXCLUDED: 'excluded',\n /** File failed validation (blocks deployment) */\n VALIDATION_FAILED: 'validation_failed',\n /** File passed validation and is ready for deployment */\n READY: 'ready',\n};\n// =============================================================================\n// DOMAIN UTILITIES\n// =============================================================================\n/**\n * Check if a domain is a platform domain (subdomain of our platform).\n * Platform domains are free and don't require DNS verification.\n *\n * @example isPlatformDomain(\"www.shipstatic.com\", \"shipstatic.com\") → true\n * @example isPlatformDomain(\"example.com\", \"shipstatic.com\") → false\n */\nexport function isPlatformDomain(domain, platformDomain) {\n return domain.endsWith(`.${platformDomain}`);\n}\n/**\n * Check if a domain is a custom domain (not a platform subdomain).\n * Custom domains are billable and require DNS verification.\n *\n * @example isCustomDomain(\"example.com\", \"shipstatic.com\") → true\n * @example isCustomDomain(\"www.shipstatic.com\", \"shipstatic.com\") → false\n */\nexport function isCustomDomain(domain, platformDomain) {\n return !isPlatformDomain(domain, platformDomain);\n}\n/**\n * Extract subdomain from a platform domain.\n * Returns null if not a platform domain.\n *\n * @example extractSubdomain(\"www.shipstatic.com\", \"shipstatic.com\") → \"www\"\n * @example extractSubdomain(\"example.com\", \"shipstatic.com\") → null\n */\nexport function extractSubdomain(domain, platformDomain) {\n if (!isPlatformDomain(domain, platformDomain)) {\n return null;\n }\n return domain.slice(0, -(platformDomain.length + 1)); // +1 for the dot\n}\n/**\n * Generate HTTPS URL for a deployment hostname.\n */\nexport function generateDeploymentUrl(deployment) {\n return `https://${deployment}`;\n}\n/**\n * Generate HTTPS URL for a domain.\n */\nexport function generateDomainUrl(domain) {\n return `https://${domain}`;\n}\n// =============================================================================\n// LABEL UTILITIES\n// =============================================================================\n/**\n * Label validation constraints shared across UI and API.\n * These rules define the single source of truth for label validation.\n */\nexport const LABEL_CONSTRAINTS = {\n /** Minimum label length in characters */\n MIN_LENGTH: 3,\n /** Maximum label length in characters (concise labels, matches Stack Overflow's original limit) */\n MAX_LENGTH: 25,\n /** Maximum number of labels allowed per resource */\n MAX_COUNT: 10,\n /** Allowed separator characters between label segments */\n SEPARATORS: '._-',\n};\n/**\n * Label validation pattern.\n * Must start and end with alphanumeric (a-z, 0-9).\n * Can contain separators (. _ -) between segments, but not consecutive.\n *\n * Valid examples: 'production', 'v1.2.3', 'api_v2', 'us-east-1'\n * Invalid examples: 'ab' (too short), '-prod' (starts with separator), 'foo--bar' (consecutive separators)\n */\nexport const LABEL_PATTERN = /^[a-z0-9]+(?:[._-][a-z0-9]+)*$/;\n/**\n * Serialize labels array to JSON string for database storage.\n * Returns null for empty or undefined arrays.\n *\n * @example serializeLabels(['web', 'production']) → '[\"web\",\"production\"]'\n * @example serializeLabels([]) → null\n * @example serializeLabels(undefined) → null\n */\nexport function serializeLabels(labels) {\n if (!labels || labels.length === 0)\n return null;\n return JSON.stringify(labels);\n}\n/**\n * Deserialize labels from JSON string to array.\n * Always returns an array — empty array for null/empty/invalid input.\n *\n * @example deserializeLabels('[\"web\",\"production\"]') → ['web', 'production']\n * @example deserializeLabels(null) → []\n * @example deserializeLabels('') → []\n */\nexport function deserializeLabels(labelsJson) {\n if (!labelsJson)\n return [];\n try {\n const parsed = JSON.parse(labelsJson);\n return Array.isArray(parsed) ? parsed : [];\n }\n catch {\n return [];\n }\n}\n// =============================================================================\n// PASSWORD UTILITIES\n// =============================================================================\n/**\n * Length constraints for the optional deployment password\n * (`DeploymentUploadOptions.password`). Single source of truth shared across\n * platform consumers.\n */\nexport const PASSWORD_CONSTRAINTS = {\n /** Minimum password length in characters */\n MIN_LENGTH: 6,\n /** Maximum password length in characters */\n MAX_LENGTH: 128,\n};\n/**\n * Validate an optional deployment password and return it normalized.\n *\n * Absent (`undefined` / `null`) → returns `undefined`. Present → trim\n * leading/trailing whitespace, then validate against `PASSWORD_CONSTRAINTS`\n * length bounds (internal whitespace is significant and counts toward\n * length). Throws `ShipError.validation` on breach; returns the trimmed\n * value.\n *\n * The trim is canonical: at upload, the API hashes the trimmed value; at\n * unlock, the router trims submissions before hashing. Submission and storage\n * agree byte-for-byte. Length validation runs on the trimmed value because\n * that's the user's actual intent — and it disarms a class of invisible\n * foot-guns (trailing newlines from copy/paste, mobile auto-spacing,\n * password-manager artifacts).\n *\n * Single source of truth shared by SDK (client-side validation, return\n * ignored) and API (server-side enforcement, return threaded into config).\n * Length is part of the wire-format contract; strength rules, if added later,\n * stay server-side. See `CLAUDE.md` \"Validation: format vs policy\".\n */\nexport function validatePassword(value) {\n if (value === undefined || value === null)\n return undefined;\n if (typeof value !== 'string') {\n throw ShipError.validation('Password must be a string');\n }\n const trimmed = value.trim();\n if (trimmed.length < PASSWORD_CONSTRAINTS.MIN_LENGTH ||\n trimmed.length > PASSWORD_CONSTRAINTS.MAX_LENGTH) {\n throw ShipError.validation(`Password must be between ${PASSWORD_CONSTRAINTS.MIN_LENGTH} and ${PASSWORD_CONSTRAINTS.MAX_LENGTH} characters`);\n }\n return trimmed;\n}\n","/**\n * @file MD5 utility for Blob, Buffer, or file path inputs.\n */\nimport { ShipError } from '@shipstatic/types';\n\nexport interface MD5Result {\n md5: string;\n}\n\nasync function md5Blob(blob: Blob): Promise<MD5Result> {\n const SparkMD5 = (await import('spark-md5')).default;\n const spark = new SparkMD5.ArrayBuffer();\n const chunkSize = 2097152; // 2 MB\n for (let start = 0; start < blob.size; start += chunkSize) {\n const end = Math.min(start + chunkSize, blob.size);\n spark.append(await blob.slice(start, end).arrayBuffer());\n }\n return { md5: spark.end() };\n}\n\nasync function md5Buffer(buffer: Buffer): Promise<MD5Result> {\n // biome-ignore lint/style/useNodejsImportProtocol: the browser build shims bare specifiers only — `node:` bypasses esbuild alias resolution (see tsup.config.ts)\n const { createHash } = await import('crypto');\n const hash = createHash('md5');\n hash.update(buffer);\n return { md5: hash.digest('hex') };\n}\n\nasync function md5Path(path: string): Promise<MD5Result> {\n // biome-ignore lint/style/useNodejsImportProtocol: see md5Buffer — bare specifier is load-bearing for the browser shim\n const { createHash } = await import('crypto');\n // biome-ignore lint/style/useNodejsImportProtocol: see md5Buffer — bare specifier is load-bearing for the browser shim\n const { createReadStream } = await import('fs');\n return new Promise((resolve, reject) => {\n const hash = createHash('md5');\n const stream = createReadStream(path);\n // A local read that failed — no request was made and no server rule was\n // being mirrored, so there is no status to report and this is the `File`\n // type by definition (see CLAUDE.md, \"What a status means\"). The path\n // rides `details`, per the factory's own convention, rather than being\n // spelled into prose twice.\n stream.on('error', (err) =>\n reject(ShipError.file(`Failed to read file for MD5: ${err.message}`, { filePath: path })),\n );\n stream.on('data', (chunk) => hash.update(chunk));\n stream.on('end', () => resolve({ md5: hash.digest('hex') }));\n });\n}\n\nexport async function calculateMD5(input: Blob | Buffer | string): Promise<MD5Result> {\n if (input instanceof Blob) return md5Blob(input);\n if (typeof Buffer !== 'undefined' && Buffer.isBuffer(input)) return md5Buffer(input);\n if (typeof input === 'string') return md5Path(input);\n throw ShipError.business('Invalid input for MD5 calculation');\n}\n","/**\n * @file Environment detection utilities for the Ship SDK.\n * Helps in determining whether the SDK is running in a Node.js, browser, or unknown environment.\n */\n\n/**\n * Represents the detected or simulated JavaScript execution environment.\n */\nexport type ExecutionEnvironment = 'browser' | 'node' | 'unknown';\n\n/** @internal Environment override for testing. */\nlet _testEnvironment: ExecutionEnvironment | null = null;\n\n/**\n * **FOR TESTING PURPOSES ONLY.**\n *\n * Allows tests to override the detected environment, forcing the SDK to behave\n * as if it's running in the specified environment.\n *\n * @param env - The environment to simulate ('node', 'browser', 'unknown'),\n * or `null` to clear the override and revert to actual environment detection.\n * @internal\n */\nexport function __setTestEnvironment(env: ExecutionEnvironment | null): void {\n _testEnvironment = env;\n}\n\n/**\n * Detects the actual JavaScript execution environment (Node.js, browser, or unknown)\n * by checking for characteristic global objects.\n * @returns The detected environment as {@link ExecutionEnvironment}.\n * @internal\n */\nfunction detectEnvironment(): ExecutionEnvironment {\n // Check for Node.js environment\n if (typeof process !== 'undefined' && process.versions && process.versions.node) {\n return 'node';\n }\n\n // Check for Browser environment (including Web Workers)\n if (typeof window !== 'undefined' || typeof self !== 'undefined') {\n return 'browser';\n }\n\n return 'unknown';\n}\n\n/**\n * Gets the current effective execution environment.\n *\n * This function first checks if a test environment override is active via {@link __setTestEnvironment}.\n * If not, it detects the actual environment (Node.js, browser, or unknown).\n *\n * @returns The current execution environment: 'browser', 'node', or 'unknown'.\n * @public\n */\nexport function getENV(): ExecutionEnvironment {\n // Return test override if set\n if (_testEnvironment) {\n return _testEnvironment;\n }\n\n // Detect actual environment\n return detectEnvironment();\n}\n","/**\n * @file Path helper utilities that work in both browser and Node.js environments.\n * Provides environment-agnostic path manipulation functions.\n */\n\n/**\n * Finds the common parent directory from an array of directory paths.\n * The basis of `pathDetect`: the common parent is the prefix stripped from\n * every deploy path so a deployment's root is the site's root.\n *\n * @param dirPaths - Array of directory paths (not file paths - directories containing the files)\n * @returns The common parent directory path, or empty string if none found\n */\nexport function findCommonParent(dirPaths: string[]): string {\n if (!dirPaths || dirPaths.length === 0) return '';\n\n const normalizedPaths = dirPaths\n .filter((p) => p && typeof p === 'string')\n .map((p) => p.replace(/\\\\/g, '/'));\n\n if (normalizedPaths.length === 0) return '';\n if (normalizedPaths.length === 1) return normalizedPaths[0];\n\n const pathSegments = normalizedPaths.map((p) => p.split('/').filter(Boolean));\n const commonSegments = [];\n const minLength = Math.min(...pathSegments.map((p) => p.length));\n\n for (let i = 0; i < minLength; i++) {\n const segment = pathSegments[0][i];\n if (pathSegments.every((segments) => segments[i] === segment)) {\n commonSegments.push(segment);\n } else {\n break;\n }\n }\n\n return commonSegments.join('/');\n}\n\n/**\n * Converts backslashes to forward slashes for cross-platform compatibility.\n * Does not remove leading slashes (preserves absolute paths).\n * @param path - The path to normalize\n * @returns Path with forward slashes\n */\nexport function normalizeSlashes(path: string): string {\n return path.replace(/\\\\/g, '/');\n}\n\n/**\n * Normalizes a path for web usage by converting backslashes to forward slashes\n * and removing leading slashes.\n * @param path - The path to normalize\n * @returns Normalized path suitable for web deployment\n */\nexport function normalizeWebPath(path: string): string {\n return path.replace(/\\\\/g, '/').replace(/\\/+/g, '/').replace(/^\\/+/, '');\n}\n","/**\n * @file Deploy path optimization - the core logic that makes Ship deployments clean and intuitive.\n * Automatically strips common parent directories to create clean deployment URLs.\n */\n\nimport { normalizeWebPath } from './path.js';\n\n/**\n * Represents a file ready for deployment with its optimized path\n */\nexport interface DeployFile {\n /** The clean deployment path (e.g., \"assets/style.css\") */\n path: string;\n /** Original filename */\n name: string;\n}\n\n/**\n * Core path optimization logic.\n * Transforms messy local paths into clean deployment paths.\n *\n * @example\n * Input: [\"dist/index.html\", \"dist/assets/app.js\"]\n * Output: [\"index.html\", \"assets/app.js\"]\n *\n * @param filePaths - Raw file paths from the local filesystem\n * @param options - Path processing options\n */\nexport function optimizeDeployPaths(\n filePaths: string[],\n options: { flatten?: boolean } = {},\n): DeployFile[] {\n // When flattening is disabled, keep original structure\n if (options.flatten === false) {\n return filePaths.map((path) => ({\n path: normalizeWebPath(path),\n name: extractFileName(path),\n }));\n }\n\n // Find the common directory prefix to strip\n const commonPrefix = findCommonDirectory(filePaths);\n\n return filePaths.map((filePath) => {\n let deployPath = normalizeWebPath(filePath);\n\n // Strip the common prefix to create clean deployment paths\n if (commonPrefix) {\n const prefixToRemove = commonPrefix.endsWith('/') ? commonPrefix : `${commonPrefix}/`;\n if (deployPath.startsWith(prefixToRemove)) {\n deployPath = deployPath.substring(prefixToRemove.length);\n }\n }\n\n // Fallback to filename if path becomes empty\n if (!deployPath) {\n deployPath = extractFileName(filePath);\n }\n\n return {\n path: deployPath,\n name: extractFileName(filePath),\n };\n });\n}\n\n/**\n * Finds the common directory shared by all file paths.\n * This is what gets stripped to create clean deployment URLs.\n *\n * @example\n * [\"dist/index.html\", \"dist/assets/app.js\"] → \"dist\"\n * [\"src/components/A.tsx\", \"src/utils/B.ts\"] → \"src\"\n * [\"file1.txt\", \"file2.txt\", \"subdir/file3.txt\"] → \"\" (no common directory)\n */\nfunction findCommonDirectory(filePaths: string[]): string {\n if (!filePaths.length) return '';\n\n // Normalize all paths first\n const normalizedPaths = filePaths.map((path) => normalizeWebPath(path));\n\n // Find the common prefix among all file paths (not just directories)\n const pathSegments = normalizedPaths.map((path) => path.split('/'));\n const commonSegments: string[] = [];\n const minLength = Math.min(...pathSegments.map((segments) => segments.length));\n\n // Check each segment level to find the longest common prefix\n for (let i = 0; i < minLength - 1; i++) {\n // -1 because we don't want to include the filename\n const segment = pathSegments[0][i];\n if (pathSegments.every((segments) => segments[i] === segment)) {\n commonSegments.push(segment);\n } else {\n break;\n }\n }\n\n return commonSegments.join('/');\n}\n\n/**\n * Extracts just the filename from a file path\n */\nfunction extractFileName(path: string): string {\n return path.split(/[/\\\\]/).pop() || path;\n}\n","/**\n * @file One ordered table of deploy-file rules, and the single evaluation two\n * renderers share.\n *\n * **The defect this closes:** one rule was rendering as three sentences. A\n * file over the size cap said `File x is too large. Maximum allowed size is\n * 20MB.` from the deploy pipelines, `File size (21 MB) exceeds limit of 20 MB`\n * from `validateFiles`, and `File too large. Maximum 20971520 bytes allowed`\n * from the API — against the dual-validation doctrine that an error reads the\n * same wherever it was caught (root `CLAUDE.md`). Both pipelines also restated\n * the whole ordered check, so node/browser parity was a comment.\n *\n * **A rule states a predicate and a sentence; a renderer chooses only how to\n * DELIVER it.** That is the `SHAPES`-table move (`cli/formatters.ts`) applied\n * to validation: the throwing renderer raises the first broken rule, the\n * collecting renderer records it, and neither authors prose. Adding a rule is\n * a row, and both surfaces get it in the same position by construction.\n *\n * **Wording follows the API where a choice existed**, so the deferred Phase B\n * — promoting this table to `@shipstatic/types` with the API consuming it —\n * has less to move. Two deliberate deviations, recorded rather than silent:\n *\n * - **Sizes are formatted, not raw bytes.** The API says `20971520 bytes`;\n * a browser upload UI showing that is worse for the person reading it, and\n * the unit is the smaller half of the sentence to reconcile later.\n * - **The path is named.** The API has no path to name; the throwing renderer\n * has nothing BUT the message, so dropping it would leave a CLI user asking\n * which file.\n *\n * Out of scope, and left where they are: `validateDeployPath` (a rule about\n * the deploy PATH rather than the file, and pipelines-only), and\n * `validateFiles`' UI-tier pre-checks — empty, negative, count, unbuilt\n * marker, processing error — which have one holder each and no drift.\n */\n\nimport type { PlatformLimits } from '@shipstatic/types';\nimport { isBlockedExtension } from '@shipstatic/types';\nimport { formatFileSize, validateFileName } from './file-validation.js';\n\n/** What a rule is asked about: one file, and the deploy so far. */\nexport interface FileRuleInput {\n /** The path this file will be served at. */\n readonly path: string;\n /** This file's size in bytes. */\n readonly size: number;\n /** Bytes accumulated INCLUDING this file — the total rule's subject. */\n readonly totalSize: number;\n}\n\n/** A rule: what makes it broken, and the one sentence that says so. */\nexport interface FileRule {\n /** Stable identity, for the fence and for reading a failure in a test. */\n readonly name: string;\n readonly broken: (input: FileRuleInput, limits: PlatformLimits) => boolean;\n readonly sentence: (input: FileRuleInput, limits: PlatformLimits) => string;\n}\n\n/**\n * EVERY rule both client surfaces apply, in the order they apply them.\n *\n * Order is load-bearing and is the reason this is a list rather than a set: a\n * file that is both misnamed and oversized reports the name, because a caller\n * fixing the name may not have an oversize problem at all.\n */\nexport const FILE_RULES: readonly FileRule[] = [\n {\n // The reason comes from `validateFileName`, which already owns this\n // vocabulary for both surfaces — the rule points at it rather than\n // restating it.\n name: 'name',\n broken: ({ path }) => !validateFileName(path).valid,\n sentence: ({ path }) => validateFileName(path).reason ?? 'Invalid file name',\n },\n {\n // The blocklist is the platform's, delivered through `/limits`. Absent\n // means NO client-side check, never an empty policy: the boundary refuses\n // the file, which is where refusal belongs.\n name: 'extension',\n broken: ({ path }, limits) => isBlockedExtension(path, limits.blockedExtensions ?? []),\n sentence: ({ path }) => `File extension not allowed: \"${path}\"`,\n },\n {\n name: 'fileSize',\n broken: ({ size }, limits) => size > limits.maxFileSize,\n sentence: ({ path }, limits) =>\n `File \"${path}\" too large. Maximum ${formatFileSize(limits.maxFileSize)} allowed`,\n },\n {\n name: 'totalSize',\n broken: ({ totalSize }, limits) => totalSize > limits.maxTotalSize,\n sentence: ({ totalSize }, limits) =>\n `Total upload size too large. ${formatFileSize(totalSize)} exceeds maximum of ${formatFileSize(limits.maxTotalSize)}`,\n },\n];\n\n/**\n * The first rule this file breaks, or `undefined`.\n *\n * The single evaluation both renderers call — which is what makes node/browser\n * parity structural instead of a promise. Neither renderer may re-order, skip,\n * or reword a rule, because neither one knows what the rules are.\n */\nexport function firstBrokenRule(\n input: FileRuleInput,\n limits: PlatformLimits,\n): FileRule | undefined {\n return FILE_RULES.find((rule) => rule.broken(input, limits));\n}\n","/**\n * @file File validation utilities for Ship SDK\n * Provides client-side validation for file uploads before deployment\n */\n\nimport type {\n FileValidationResult,\n FileValidationStatusType,\n PlatformLimits,\n ValidatableFile,\n ValidationIssue,\n} from '@shipstatic/types';\nimport {\n FileValidationStatus as FILE_VALIDATION_STATUS,\n hasUnbuiltMarker,\n hasUnsafeChars,\n} from '@shipstatic/types';\nimport { firstBrokenRule } from './file-rules.js';\n\n// Re-exported because it constrains three exported generics (validateFiles,\n// getValidFiles, allValidFilesReady) — a consumer cannot name the bound\n// otherwise, which is why the tests were inventing their own copy.\nexport type { ValidatableFile };\nexport { FILE_VALIDATION_STATUS };\n\n/**\n * Format file size to human-readable string\n */\nexport function formatFileSize(bytes: number, decimals: number = 1): string {\n if (bytes === 0) return '0 Bytes';\n const k = 1024;\n const sizes = ['Bytes', 'KB', 'MB', 'GB'];\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n return `${parseFloat((bytes / k ** i).toFixed(decimals))} ${sizes[i]}`;\n}\n\n/**\n * Validate filename for deployment safety\n *\n * Blocks only characters that genuinely break the upload→serve round-trip:\n * - # ? % URL round-trip breakers (fragment, query, encoding ambiguity)\n * - \\ Path separator confusion (buildFileKey splits on backslash)\n * - < > \" XSS vectors with zero legitimate use in filenames\n * - \\x00-\\x1f \\x7f Control characters (header injection, display corruption)\n *\n * Everything else is allowed — browser percent-encodes, Worker decodes, R2 matches.\n *\n * Additional checks: path traversal, reserved names, leading/trailing dots or spaces.\n */\nexport function validateFileName(filename: string): { valid: boolean; reason?: string } {\n if (hasUnsafeChars(filename)) {\n return { valid: false, reason: 'File name contains unsafe characters' };\n }\n\n if (filename.startsWith(' ') || filename.endsWith(' ')) {\n return { valid: false, reason: 'File name cannot start/end with spaces' };\n }\n\n if (filename.endsWith('.')) {\n return { valid: false, reason: 'File name cannot end with dots' };\n }\n\n const reservedNames = /^(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])(\\.|$)/i;\n const nameWithoutPath = filename.split('/').pop() || filename;\n if (reservedNames.test(nameWithoutPath)) {\n return { valid: false, reason: 'File name uses a reserved system name' };\n }\n\n if (filename.includes('..')) {\n return { valid: false, reason: 'File name contains path traversal pattern' };\n }\n\n return { valid: true };\n}\n\n/**\n * Validate files against configuration limits with severity-based reporting\n *\n * Validation categorizes issues by severity:\n * - **Errors**: Block deployment (file too large, blocked extension, etc.)\n * - **Warnings**: Exclude files but allow deployment (empty files, etc.)\n *\n * @param files - Array of files to validate\n * @param config - Validation configuration from ship.getLimits()\n * @returns Validation result with errors and warnings\n *\n * @example\n * ```typescript\n * const config = await ship.getLimits();\n * const result = validateFiles(files, config);\n *\n * if (!result.canDeploy) {\n * // Has errors - deployment blocked\n * console.error('Deployment blocked:', result.errors);\n * } else if (result.warnings.length > 0) {\n * // Has warnings - deployment proceeds, some files excluded\n * console.warn('Files excluded:', result.warnings);\n * await ship.deploy(result.validFiles);\n * } else {\n * // All files valid\n * await ship.deploy(result.validFiles);\n * }\n * ```\n */\nexport function validateFiles<T extends ValidatableFile>(\n files: T[],\n config: PlatformLimits,\n): FileValidationResult<T> {\n const errors: ValidationIssue[] = [];\n const warnings: ValidationIssue[] = [];\n let fileStatuses: T[] = []; // Use 'let' for atomic enforcement later\n\n // Check at least 1 file required\n if (files.length === 0) {\n const issue: ValidationIssue = {\n file: '(no files)',\n message: 'At least one file must be provided',\n };\n errors.push(issue);\n\n return {\n files: [],\n validFiles: [],\n errors,\n warnings: [],\n canDeploy: false,\n };\n }\n\n // Check for unbuilt project markers (node_modules/, etc.)\n for (const file of files) {\n if (hasUnbuiltMarker(file.name)) {\n errors.push({\n file: file.name,\n message: `Unbuilt project detected — deploy your build output (dist/, build/, out/), not the project folder`,\n });\n return {\n files: files.map((f) => ({\n ...f,\n status: FILE_VALIDATION_STATUS.VALIDATION_FAILED,\n statusMessage: 'Unbuilt project detected',\n })),\n validFiles: [],\n errors,\n warnings: [],\n canDeploy: false,\n };\n }\n }\n\n // Check file count limit\n if (files.length > config.maxFilesCount) {\n const issue: ValidationIssue = {\n file: `(${files.length} files)`,\n message: `File count (${files.length}) exceeds limit of ${config.maxFilesCount}`,\n };\n errors.push(issue);\n\n return {\n files: files.map((f) => ({\n ...f,\n status: FILE_VALIDATION_STATUS.VALIDATION_FAILED,\n statusMessage: issue.message,\n })),\n validFiles: [],\n errors,\n warnings: [],\n canDeploy: false,\n };\n }\n\n // Validate each file\n let totalSize = 0;\n\n for (const file of files) {\n let fileStatus: FileValidationStatusType = FILE_VALIDATION_STATUS.READY;\n let statusMessage = 'Ready for upload';\n\n // Check for processing errors\n if (file.status === FILE_VALIDATION_STATUS.PROCESSING_ERROR) {\n fileStatus = FILE_VALIDATION_STATUS.VALIDATION_FAILED;\n statusMessage = file.statusMessage || 'File failed during processing';\n errors.push({\n file: file.name,\n message: statusMessage,\n });\n }\n\n // EMPTY FILE - Warning (not error)\n else if (file.size === 0) {\n fileStatus = FILE_VALIDATION_STATUS.EXCLUDED;\n statusMessage = 'File is empty (0 bytes) and cannot be deployed due to storage limitations';\n warnings.push({\n file: file.name,\n message: statusMessage,\n });\n // Skip other validations for excluded files\n fileStatuses.push({\n ...file,\n status: fileStatus,\n statusMessage,\n });\n continue;\n }\n\n // Negative file size - Error\n else if (file.size < 0) {\n fileStatus = FILE_VALIDATION_STATUS.VALIDATION_FAILED;\n statusMessage = 'File size must be positive';\n errors.push({\n file: file.name,\n message: statusMessage,\n });\n }\n\n // File name validation\n else if (!file.name || file.name.trim().length === 0) {\n fileStatus = FILE_VALIDATION_STATUS.VALIDATION_FAILED;\n statusMessage = 'File name cannot be empty';\n errors.push({\n file: file.name || '(empty)',\n message: statusMessage,\n });\n } else if (file.name.includes('\\0')) {\n fileStatus = FILE_VALIDATION_STATUS.VALIDATION_FAILED;\n statusMessage = 'File name contains invalid characters (null byte)';\n errors.push({\n file: file.name,\n message: statusMessage,\n });\n }\n\n // THE SHARED RULES — name, extension, file size, total size — read from\n // the one ordered table in `file-rules.ts`.\n //\n // This is the COLLECTING renderer: the same verdict the deploy pipelines'\n // throwing renderer reaches, delivered as a list rather than an exception.\n // Neither surface authors a sentence, which is what stopped one size rule\n // reading three different ways.\n else {\n const input = { path: file.name, size: file.size, totalSize: totalSize + file.size };\n const broken = firstBrokenRule(input, config);\n\n if (broken) {\n fileStatus = FILE_VALIDATION_STATUS.VALIDATION_FAILED;\n statusMessage = broken.sentence(input, config);\n errors.push({\n // The total-size rule fails the DEPLOY, not this file, so it keeps\n // the aggregate subject rather than blaming whichever file tipped it.\n file: broken.name === 'totalSize' ? `(${files.length} files)` : file.name,\n message: statusMessage,\n });\n } else {\n // Only a file that passed everything counts toward the running total.\n totalSize = input.totalSize;\n }\n }\n\n fileStatuses.push({\n ...file,\n status: fileStatus,\n statusMessage,\n });\n }\n\n // ATOMIC ENFORCEMENT: Two-phase validation for optimal UX + atomic semantics\n // Phase 1 (above): Validate files individually to collect ALL errors\n // Phase 2 (below): Mark all files as failed if any errors exist\n //\n // Why two phases? We validate individually for better UX (users see all problems\n // at once and can fix everything in one pass), then enforce atomicity to maintain\n // deployment transaction semantics (all-or-nothing).\n if (errors.length > 0) {\n fileStatuses = fileStatuses.map((file) => {\n // Keep EXCLUDED files as-is (they're warnings, not errors)\n if (file.status === FILE_VALIDATION_STATUS.EXCLUDED) {\n return file;\n }\n\n // Mark ALL other files as VALIDATION_FAILED (atomic deployment)\n return {\n ...file,\n status: FILE_VALIDATION_STATUS.VALIDATION_FAILED,\n statusMessage:\n file.status === FILE_VALIDATION_STATUS.VALIDATION_FAILED\n ? file.statusMessage // Keep original error message for the file that actually failed\n : 'Deployment failed due to validation errors in bundle',\n };\n });\n }\n\n // Build atomic result\n // validFiles is empty if ANY errors exist (all-or-nothing)\n const validFiles =\n errors.length === 0\n ? fileStatuses.filter((f) => f.status === FILE_VALIDATION_STATUS.READY)\n : [];\n const canDeploy = errors.length === 0;\n\n return {\n files: fileStatuses,\n validFiles,\n errors,\n warnings,\n canDeploy,\n };\n}\n\n/**\n * Get only the valid files from validation results\n */\nexport function getValidFiles<T extends ValidatableFile>(files: T[]): T[] {\n return files.filter((f) => f.status === FILE_VALIDATION_STATUS.READY);\n}\n\n/**\n * Check if all valid files have required properties for upload\n * (Can be extended to check for MD5, etc.)\n */\nexport function allValidFilesReady<T extends ValidatableFile>(files: T[]): boolean {\n const validFiles = getValidFiles(files);\n return validFiles.length > 0;\n}\n","/**\n * @file Utility for filtering out junk files and directories from file paths\n *\n * This module provides functionality to filter out common system junk files and directories\n * from a list of file paths. It uses the 'junk' package to identify junk filenames and\n * a custom list to filter out common junk directories.\n */\n\nimport { hasUnbuiltMarker, ShipError } from '@shipstatic/types';\nimport { isJunk } from 'junk';\n\n/**\n * List of directory names considered as junk\n *\n * Files within these directories (at any level in the path hierarchy) will be excluded.\n * The comparison is case-insensitive for cross-platform compatibility.\n *\n * @internal\n */\nexport const JUNK_DIRECTORIES = ['__MACOSX', '.Trashes', '.fseventsd', '.Spotlight-V100'] as const;\n\n/**\n * Filters an array of file paths, removing those considered junk\n *\n * Throws if any path contains an unbuilt project marker (e.g. `node_modules`, `package.json`).\n * This check runs first because the dot-file filter below would strip paths like\n * `node_modules/.pnpm/...`, destroying the evidence.\n *\n * A path is filtered out if any of these conditions are met:\n * 1. The basename is identified as junk by the 'junk' package (e.g., .DS_Store, Thumbs.db)\n * 2. Any path segment starts with a dot (e.g., .env, .git, .htaccess)\n * Exception: `.well-known` is allowed (RFC 8615 — ACME, security.txt, app links)\n * 3. Any path segment exceeds 255 characters (filesystem limit)\n * 4. Any directory segment in the path matches an entry in JUNK_DIRECTORIES (case-insensitive)\n *\n * All path separators are normalized to forward slashes for consistent cross-platform behavior.\n *\n * Dot files are filtered for security — they typically contain sensitive configuration\n * (.env, .git) or are not meant to be served publicly. This matches server-side filtering.\n *\n * @param filePaths - An array of file path strings to filter\n * @param options - Optional settings\n * @param options.allowUnbuilt - When true, skip the unbuilt project marker check (for server-processed uploads)\n * @returns A new array containing only non-junk file paths\n * @throws {ShipError} If any path contains an unbuilt project marker (unless allowUnbuilt is true)\n *\n * @example\n * ```typescript\n * import { filterJunk } from '@shipstatic/ship';\n *\n * // Filter an array of file paths\n * const paths = ['index.html', '.DS_Store', '.gitattributes', '__MACOSX/file.txt', 'app.js'];\n * const clean = filterJunk(paths);\n * // Result: ['index.html', 'app.js']\n * ```\n *\n * @example\n * ```typescript\n * // Use with browser File objects\n * import { filterJunk } from '@shipstatic/ship';\n *\n * const files: File[] = [...]; // From input or drag-drop\n *\n * // Extract paths from File objects\n * const filePaths = files.map(f => f.webkitRelativePath || f.name);\n *\n * // Filter out junk paths\n * const validPaths = new Set(filterJunk(filePaths));\n *\n * // Filter the original File array\n * const validFiles = files.filter(f =>\n * validPaths.has(f.webkitRelativePath || f.name)\n * );\n * ```\n */\nexport function filterJunk(filePaths: string[], options?: { allowUnbuilt?: boolean }): string[] {\n if (!filePaths || filePaths.length === 0) {\n return [];\n }\n\n // Reject unbuilt projects before the dot-file filter removes evidence.\n // pnpm stores files under node_modules/.pnpm/ — the dot-file filter below\n // strips .pnpm/ paths, destroying the only signal that this is an unbuilt project.\n if (!options?.allowUnbuilt) {\n const marker = filePaths.find((p) => p && hasUnbuiltMarker(p));\n if (marker) {\n throw ShipError.business(\n 'Unbuilt project detected — deploy your build output (dist/, build/, out/), not the project folder',\n );\n }\n }\n\n return filePaths.filter((filePath) => {\n if (!filePath) {\n return false; // Exclude null or undefined paths\n }\n\n // Normalize path separators to forward slashes and split into segments\n const parts = filePath.replace(/\\\\/g, '/').split('/').filter(Boolean);\n if (parts.length === 0) return true;\n\n // Check if the basename is a junk file (using junk package)\n const basename = parts[parts.length - 1];\n if (isJunk(basename)) {\n return false;\n }\n\n // Filter out dot files and directories (security: prevents .env, .git, etc.)\n // .well-known is not junk — it's a standard directory (RFC 8615)\n // Path position constraints enforced at upload (buildFileKey) and serving (isBlockedDotFile)\n for (const part of parts) {\n if (part === '.well-known') continue;\n if (part.startsWith('.') || part.length > 255) {\n return false;\n }\n }\n\n // Check if any directory segment is in our junk directories list\n const directorySegments = parts.slice(0, -1);\n for (const segment of directorySegments) {\n if (JUNK_DIRECTORIES.some((junkDir) => segment.toLowerCase() === junkDir.toLowerCase())) {\n return false;\n }\n }\n\n return true;\n });\n}\n","/**\n * @file Shared security validation for the deploy pipeline.\n * Used by both Node.js and browser file processing pipelines.\n */\nimport type { PlatformLimits } from '@shipstatic/types';\nimport { ShipError } from '@shipstatic/types';\nimport { type FileRuleInput, firstBrokenRule } from './file-rules.js';\n\n/**\n * Validate a deploy path for security concerns.\n * Rejects paths containing path traversal patterns or null bytes.\n *\n * Checks for:\n * - Null bytes (\\0) — path injection\n * - /../ — directory traversal within path\n * - ../ at start — upward traversal\n * - /.. at end — trailing traversal\n *\n * Does NOT reject double dots in filenames (e.g., \"foo..bar.txt\" is safe).\n *\n * @param deployPath - The deployment path to validate\n * @param sourceIdentifier - Human-readable identifier for error messages\n * @throws {ShipError} If the path contains unsafe patterns\n */\nexport function validateDeployPath(deployPath: string, sourceIdentifier: string): void {\n if (\n deployPath.includes('\\0') ||\n deployPath.includes('/../') ||\n deployPath.startsWith('../') ||\n deployPath.endsWith('/..')\n ) {\n throw ShipError.business(\n `Security error: Unsafe file path \"${deployPath}\" for file: ${sourceIdentifier}`,\n );\n }\n}\n\n/**\n * The THROWING renderer of `FILE_RULES` — the deploy pipelines' shape.\n *\n * It raises the first rule the file breaks and nothing else: the rules, their\n * order and their sentences all live in `file-rules.ts`, so this function\n * cannot re-order, skip or reword one. That is what makes node/browser parity\n * structural — both pipelines call this, and this calls the one table.\n *\n * Its counterpart is the collecting renderer in `file-validation.ts`\n * (`validateFiles`), which reaches the same verdict and reports it as a list\n * instead of a throw.\n *\n * @param input - The file and the deploy so far (`totalSize` INCLUDES it)\n * @param limits - The platform's limits, from `/limits`\n * @throws {ShipError} The first broken rule's sentence\n */\nexport function validateDeployFile(input: FileRuleInput, limits: PlatformLimits): void {\n const broken = firstBrokenRule(input, limits);\n if (broken) {\n throw ShipError.business(broken.sentence(input, limits));\n }\n}\n","/**\n * @file The deploy file pipeline — one processor, two collectors.\n *\n * A deploy turns *what a platform found* into *what the API receives*, and\n * only the first half of that sentence is platform-shaped. Node walks a\n * filesystem; a browser is handed `File` objects. Everything after — optimize\n * the paths, drop the junk, refuse what the platform's rules refuse, checksum\n * what survives — is one sequence, stated here once.\n *\n * The seam is {@link DeploySource}: a path, where it came from, its size, and\n * a way to read it. That shape is what lets Node answer \"how big is this?\"\n * from the stat its directory walk already performed, which is how the empty-\n * file skip stays free — and it is why `read()` is a FUNCTION rather than a\n * field. The processor calls it for files that survive filtering and\n * validation and for no others, so a deploy refused at file three never opens\n * files four through nine hundred.\n */\n\nimport type { PlatformLimits, StaticFile } from '@shipstatic/types';\nimport { ShipError } from '@shipstatic/types';\nimport { optimizeDeployPaths } from '../lib/deploy-paths.js';\nimport { filterJunk } from '../lib/junk.js';\nimport { calculateMD5 } from '../lib/md5.js';\nimport { validateDeployFile, validateDeployPath } from '../lib/security.js';\nimport type { DeploymentOptions } from '../types.js';\n\n/**\n * One file a platform found, before this pipeline has decided anything about\n * it. The only thing a collector owes beyond the bytes is where they came\n * from and how many there are.\n */\nexport interface DeploySource {\n /** The path this file wants, as its platform names it — pre-optimization. */\n path: string;\n /**\n * Where the file came from, named in the security refusal: an absolute\n * filesystem path in Node, a `File.name` in the browser. It is what makes\n * an unsafe deploy path traceable back to the thing that produced it.\n */\n origin: string;\n /** Byte length, known WITHOUT reading — a stat, or `File.size`. */\n size: number;\n /** Reads the content. Called only for files that survive to the checksum. */\n read: () => Promise<StaticFile['content']>;\n}\n\n/**\n * Turn collected sources into the `StaticFile[]` a deploy body is built from.\n *\n * @param sources - What the platform found, in the platform's own order\n * @param options - Deploy options; `pathDetect` and the server-processed flags\n * are the two this pipeline reads\n * @param platformLimits - The caps and the delivered blocklist from\n * `GET /limits`. Per-instance rather than a module global, so two Ships\n * against different API URLs cannot clobber each other's rules.\n */\nexport async function processDeployFiles(\n sources: DeploySource[],\n options: DeploymentOptions = {},\n platformLimits?: PlatformLimits,\n): Promise<StaticFile[]> {\n // `build` / `prerender` upload SOURCE files for the build service to\n // compile. The deploy rules describe its OUTPUT, so they are not this\n // pipeline's to apply — and the unbuilt-project refusal would reject\n // precisely the input the flags exist to accept. Both flags are `@internal`\n // and set only by `web/my` and `web/www`, which are browser apps; there is\n // no Node caller. That asymmetry lives in the callers, which is why it needs\n // no second copy of the loop here.\n const serverProcessed = !!(options.build || options.prerender);\n\n const deployPaths = optimizeDeployPaths(\n sources.map((source) => source.path),\n { flatten: options.pathDetect !== false },\n ).map((file) => file.path);\n\n const surviving = new Set(filterJunk(deployPaths, { allowUnbuilt: serverProcessed }));\n const kept = sources\n .map((source, index) => ({ source, deployPath: deployPaths[index] }))\n .filter(({ deployPath }) => surviving.has(deployPath));\n\n // Nothing to deploy is not a failure, and it is answered before the rules\n // are demanded: a directory of pure junk resolves empty rather than\n // complaining about limits it was never going to consult.\n if (kept.length === 0) return [];\n\n // The rules, or none. `null` is server-processed mode — the one state where\n // this pipeline judges nothing — so every check below reads as what it is:\n // we validate when we have something to validate against.\n const rules = serverProcessed ? null : requireLimits(platformLimits);\n\n const files: StaticFile[] = [];\n let totalSize = 0;\n\n for (const { source, deployPath } of kept) {\n // Fail fast, before any I/O: an unsafe path is refused on the strength of\n // the path alone.\n if (rules) validateDeployPath(deployPath, source.origin);\n\n // R2 cannot store zero-byte objects. The size is already in hand, so this\n // costs nothing and reads nothing.\n if (source.size === 0) continue;\n\n if (rules) {\n // Name, extension and both size caps — ONE ordered table\n // (`shared/lib/file-rules.ts`), shared with the collecting renderer the\n // UI tier uses. `totalSize` INCLUDES this file, which the total-size\n // rule relies on.\n totalSize += source.size;\n validateDeployFile({ path: deployPath, size: source.size, totalSize }, rules);\n }\n\n const content = await source.read();\n const { md5 } = await calculateMD5(content);\n files.push({ path: deployPath, content, size: source.size, md5 });\n }\n\n // Counted over RESULTS, not over candidates: empty files were skipped above\n // and a deploy is not over the cap for files it will not send.\n if (rules && files.length > rules.maxFilesCount) {\n throw ShipError.business(\n `Too many files to deploy. Maximum allowed is ${rules.maxFilesCount} files.`,\n );\n }\n\n return files;\n}\n\n/**\n * Deploy-mode validation is only as real as the rules behind it, so their\n * absence is a configuration error rather than a silently permissive pass.\n * Unreachable through `ship.deploy()` — the deploy pipeline awaits the\n * `/limits` fetch before it collects a file — and reachable by a consumer\n * calling the exported platform processors directly, which is who the\n * sentence is written for.\n */\nfunction requireLimits(platformLimits?: PlatformLimits): PlatformLimits {\n if (!platformLimits) {\n throw ShipError.config(\n 'Platform limits not provided. Deploy-mode validation requires the limits ' +\n 'argument — pass `ship.getLimits()` result.',\n );\n }\n return platformLimits;\n}\n","/**\n * @file The Node half of the deploy pipeline: finding files on a filesystem.\n *\n * Everything after the finding is shared (`shared/core/deploy-files.ts`) —\n * path optimization, junk filtering, the platform's rules, the checksums.\n * What is genuinely Node here is a directory walk with symlink-cycle\n * protection, a content path computed against the upload root, and the fact\n * that a Node user can point at a project folder and mean `dist/`.\n */\n\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport type { PlatformLimits } from '@shipstatic/types';\nimport { isShipError, ShipError, UNBUILT_PROJECT_MARKERS } from '@shipstatic/types';\nimport { type DeploySource, processDeployFiles } from '../../shared/core/deploy-files.js';\nimport { getENV } from '../../shared/lib/env.js';\nimport { findCommonParent } from '../../shared/lib/path.js';\nimport type { DeploymentOptions, StaticFile } from '../../shared/types.js';\n\n/** A file the walk found, carrying the size the walk already had to ask for. */\ninterface FoundFile {\n absPath: string;\n size: number;\n}\n\n/**\n * Walk a directory and return every file under it.\n *\n * Sizes come from the `statSync` this walk performs anyway to tell a\n * directory from a file, which is what lets the shared processor skip empty\n * files without opening one. The pipeline used to stat every file a SECOND\n * time to learn the same number.\n *\n * @param dirPath - Directory to traverse\n * @param visited - Real paths already walked, so a symlink cycle terminates\n */\nfunction findAllFiles(dirPath: string, visited: Set<string> = new Set()): FoundFile[] {\n const results: FoundFile[] = [];\n\n // Resolve the real path to detect symlink cycles.\n const realPath = fs.realpathSync(dirPath);\n if (visited.has(realPath)) return results;\n visited.add(realPath);\n\n for (const entry of fs.readdirSync(dirPath)) {\n const fullPath = path.join(dirPath, entry);\n const stats = fs.statSync(fullPath);\n\n if (stats.isDirectory()) results.push(...findAllFiles(fullPath, visited));\n else if (stats.isFile()) results.push({ absPath: fullPath, size: stats.size });\n }\n\n return results;\n}\n\n/**\n * Read a file's bytes, naming it if the filesystem refuses.\n *\n * The wrap spans the filesystem call and nothing else. It used to span the\n * whole per-file body, which meant it caught the typed refusals too and had to\n * re-raise them — an `isShipError(error)` line whose only job was to undo the\n * catch's own overreach. A local read that failed is `ShipError.file` by\n * definition: no request was made and no server rule was being mirrored, so\n * there is no status to report (see CLAUDE.md, \"What a status means\").\n */\nasync function readContent(filePath: string): Promise<Buffer> {\n try {\n return fs.readFileSync(filePath);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n throw ShipError.file(`Failed to read file \"${filePath}\": ${message}`, { filePath });\n }\n}\n\n/**\n * Refuse a project folder before walking it.\n *\n * Node-only, and the reason is the input: a Node user types a path and can\n * plausibly type the repository root, where the walk would then enumerate\n * `node_modules`. A browser user picks files. The junk filter refuses the same\n * mistake one step later for the paths it can see; this catches it at the\n * cheapest possible moment, which for a large monorepo is the difference\n * between an immediate answer and a long one.\n */\nfunction refuseUnbuiltProjects(paths: string[]): void {\n for (const p of paths) {\n const absPath = path.resolve(p);\n try {\n if (fs.statSync(absPath).isDirectory()) {\n const marker = fs.readdirSync(absPath).find((e) => UNBUILT_PROJECT_MARKERS.has(e));\n if (marker) {\n throw ShipError.business(\n `\"${marker}\" detected — deploy your build output (dist/, build/, out/), not the project folder`,\n );\n }\n }\n } catch (e) {\n if (isShipError(e)) throw e;\n // Path errors are reported by the discovery walk below, which names the\n // input the user actually typed.\n }\n }\n}\n\n/**\n * Processes Node.js file and directory paths into an array of StaticFile objects ready for deploy.\n * Computes content paths relative to the upload root before filtering, so only the deployed\n * directory structure is evaluated — not the user's filesystem above it.\n *\n * @param paths - File or directory paths to scan and process.\n * @param options - Processing options (pathDetect, etc.).\n * @param platformLimits - Per-instance platform limits (file-size / count /\n * total-size caps) from the originating Ship's `GET /limits` fetch. Passed\n * in rather than read from a module global so concurrent Ships against\n * different API URLs cannot clobber each other's caps.\n * @returns Promise resolving to an array of StaticFile objects.\n * @throws {ShipError} If called outside Node.js or if fs/path modules fail.\n */\nexport async function processFilesForNode(\n paths: string[],\n options: DeploymentOptions = {},\n platformLimits?: PlatformLimits,\n): Promise<StaticFile[]> {\n if (getENV() !== 'node') {\n throw ShipError.business('processFilesForNode can only be called in Node.js environment.');\n }\n\n refuseUnbuiltProjects(paths);\n\n // 1. Discover every file under the inputs, deduplicated by absolute path\n // (two inputs may overlap, and two symlinks may reach one target).\n const found = paths.flatMap((p) => {\n const absPath = path.resolve(p);\n try {\n const stats = fs.statSync(absPath);\n return stats.isDirectory() ? findAllFiles(absPath) : [{ absPath, size: stats.size }];\n } catch (_error) {\n throw ShipError.file(`Path does not exist: ${p}`, { filePath: p });\n }\n });\n const unique = new Map(found.map((file) => [file.absPath, file.size]));\n\n // 2. The upload root comes from the INPUT paths, not the discovered files:\n // `ship ./dist` deploys the contents of `dist`, whatever it happens to\n // contain, so a tree with one deep branch does not strip that branch.\n const inputBasePath = findCommonParent(\n paths\n .map((p) => path.resolve(p))\n .map((p) => {\n try {\n return fs.statSync(p).isDirectory() ? p : path.dirname(p);\n } catch {\n return path.dirname(p);\n }\n }),\n );\n\n // 3. Hand the shared processor a source per file. Sizes ride along from the\n // walk; the bytes are read only if the file survives that far.\n const sources: DeploySource[] = [...unique].map(([absPath, size]) => ({\n path: contentPath(absPath, inputBasePath),\n origin: absPath,\n size,\n read: () => readContent(absPath),\n }));\n\n return processDeployFiles(sources, options, platformLimits);\n}\n\n/**\n * The path a file should have relative to the upload root, in web form.\n * Anything the root does not contain falls back to its basename — a file\n * reached through a symlink that points outside the deploy is still deployed,\n * just flat.\n */\nfunction contentPath(absPath: string, inputBasePath: string): string {\n if (inputBasePath && inputBasePath.length > 0) {\n const rel = path.relative(inputBasePath, absPath);\n if (rel && typeof rel === 'string' && !rel.startsWith('..')) {\n return rel.replace(/\\\\/g, '/');\n }\n }\n return path.basename(absPath);\n}\n","/**\n * @file Main entry point for the Ship SDK.\n *\n * This is the Node.js entry: file-system deploy input plus the `SHIP_*`\n * env-var fallback. Browser consumers resolve this same package to the\n * browser build through the `browser` condition in the exports map —\n * bundlers select it automatically; there is no separate import path.\n */\n\n// Re-export everything from the Node.js index, including both named and default exports\nexport * from './node/index.js';\nexport { default } from './node/index.js';\n","/**\n * @file Ship SDK for Node.js environments.\n *\n * The Node-side `Ship` adds two things on top of the base class:\n * 1. Environment detection — refuses to construct outside Node.\n * 2. `SHIP_TOKEN` / `SHIP_API_URL` env-var resolution as the universal\n * \"process boundary\" credential source — the industry's one-token\n * convention. Constructor arguments win over env vars.\n *\n * The SDK does NOT read `~/.shiprc` or `package.json` `\"ship\"` keys — that's\n * the CLI's job (see `cli/shiprc.ts`). Keeping file resolution out of the SDK\n * is what lets embedded consumers (MCP, n8n, GitHub Action) safely write\n * `new Ship({})` for anonymous public deployments without inheriting the host\n * developer's personal credentials.\n */\n\nimport { ShipError } from '@shipstatic/types';\nimport { Ship as BaseShip } from '../shared/base-ship.js';\nimport { getENV } from '../shared/lib/env.js';\nimport type {\n DeployInput,\n DeploymentCreateResponse,\n DeploymentOptions,\n ShipClientOptions,\n StaticFile,\n} from '../shared/types.js';\nimport { readEnvConfig } from './core/config.js';\n\n// Export all shared functionality\nexport * from '../shared/index.js';\n\n/**\n * Ship SDK Client for Node.js environments.\n *\n * @example\n * ```typescript\n * // Authenticated — explicit token (API key, deploy token, or OAuth bearer)\n * const ship = new Ship({ token: 'ship-your-api-key' });\n *\n * // Authenticated — picks up SHIP_TOKEN from env\n * const ship = new Ship({});\n *\n * // Anonymous public deploy — works when neither constructor nor env provides a token\n * const ship = new Ship({});\n * await ship.deploy('./dist');\n * ```\n */\nexport class Ship extends BaseShip {\n constructor(options: ShipClientOptions = {}) {\n if (getENV() !== 'node') {\n throw ShipError.business('Node.js Ship class can only be used in Node.js environment.');\n }\n\n // Layer env vars under constructor options. The merged result is what the\n // base class sees, so the credential and the HTTP client are fully formed\n // by the time the constructor returns — no async config phase needed.\n //\n // Truthiness (not `??`) is deliberate: an empty-string token is absence\n // (shell expansion of unset CI variables), so `token: ''` falls through\n // to `SHIP_TOKEN` instead of locking in a phantom credential. A client\n // constructed with `session: true` has chosen its identity — the ambient\n // token does not ride along.\n const env = readEnvConfig();\n super({\n ...options,\n apiUrl: options.apiUrl || env.apiUrl,\n token: options.token || (options.session ? undefined : env.token),\n });\n }\n\n /**\n * Deploy file or directory paths to ShipStatic. Convenience shortcut for\n * `ship.deployments.upload()`.\n *\n * Wrong-platform inputs (e.g. `File[]`) fail at compile time. For\n * platform-neutral code, use `ship.deployments.upload()`, which accepts\n * the wider `DeployInput` and validates at runtime — that asymmetry is\n * intentional: the convenience shortcut narrows; the resource-layer\n * contract stays platform-neutral.\n */\n async deploy(\n input: string | string[],\n options?: DeploymentOptions,\n ): Promise<DeploymentCreateResponse> {\n return super.deploy(input, options);\n }\n\n protected async processInput(\n input: DeployInput,\n options: DeploymentOptions,\n ): Promise<StaticFile[]> {\n // Normalize string to string[] and validate.\n const paths = typeof input === 'string' ? [input] : input;\n\n if (!Array.isArray(paths) || !paths.every((p) => typeof p === 'string')) {\n throw ShipError.business(\n 'Invalid input type for Node.js environment. Expected string or string[].',\n );\n }\n\n if (paths.length === 0) {\n throw ShipError.business('No files to deploy.');\n }\n\n const { processFilesForNode } = await import('./core/node-files.js');\n return processFilesForNode(paths, options, this.platformLimits ?? undefined);\n }\n}\n\n// Default export (for `import Ship from '@shipstatic/ship'`)\nexport default Ship;\n\n// Node-only utilities (path-walking + MD5 over the local filesystem)\nexport { processFilesForNode } from './core/node-files.js';\n","/**\n * @file Base Ship SDK class — shared functionality across environments.\n *\n * The constructor is fully synchronous: an `ApiHttp` instance is built immediately\n * with whatever credentials the caller supplied (and, in Node, env vars merged in\n * by the subclass before `super()`). The only deferred work is the one-shot\n * `GET /limits` fetch that hydrates platform limits — lazy, memoized, and run\n * from the two places that actually READ the result: the deploy pipeline's\n * `processInput` and the public `getLimits()`. It ran on the first API call of\n * any kind until 2026-08-12, which cost every other command a round trip it\n * never used.\n *\n * Subclasses only override what genuinely differs per environment:\n * - `processInput()` — Node reads paths from disk; Browser handles `File[]`\n *\n * That is the whole list, and it was two entries until 2026-08-12: a\n * `getDeployBodyCreator()` sat beside it, overridden identically in both\n * subclasses to return the one builder they now share. A seam with the same\n * answer on both sides is not a seam.\n *\n * Everything else (the credential slot, resources, events, lazy platform-limits)\n * lives here.\n */\n\nimport type {\n AccountResource,\n DeploymentCreateResponse,\n DeploymentResource,\n DomainResource,\n PingResponse,\n PlatformLimits,\n StaticFile,\n TokenResource,\n} from '@shipstatic/types';\nimport { API_PATHS, ShipError, validateCaller, validateToken } from '@shipstatic/types';\nimport { ApiHttp } from './api/http.js';\nimport {\n createAccountResource,\n createDeploymentResource,\n createDomainResource,\n createTokenResource,\n type DeployInput,\n} from './resources.js';\nimport type { DeploymentOptions, ShipClientOptions, ShipEvents, TokenProvider } from './types.js';\n\n/**\n * Abstract base class for Ship SDK implementations.\n */\nexport abstract class Ship {\n // Resource handles, created once at construction. Each is a thin facade\n // bound to `this.http`.\n // Parameterized with the SDK's extended options (timeout, callbacks,\n // signal…) — the interface's documented extension point, so typed\n // consumers can pass them without casts.\n public readonly deployments: DeploymentResource<DeploymentOptions>;\n public readonly domains: DomainResource;\n public readonly account: AccountResource;\n public readonly tokens: TokenResource;\n\n // The HTTP client and merged options are private — subclasses interact\n // with the base class through the abstract methods below, never by\n // reaching into these fields. Tests bypass via `(ship as any).http = ...`.\n private readonly http: ApiHttp;\n private readonly clientOptions: ShipClientOptions;\n\n // Lazy-init plumbing for the one-shot `GET /limits` fetch.\n // `platformLimits` is INSTANCE state (not a module-level singleton): two\n // Ships against different `apiUrl`s — staging + prod, multi-tenant\n // orchestrators, n8n with multiple credentials — must not clobber each\n // other's limits. Each instance owns its hydrated copy.\n // `protected` so subclasses' `processInput` can pass it down to the\n // platform-specific file-validation utilities.\n private initPromise: Promise<void> | null = null;\n protected platformLimits: PlatformLimits | null = null;\n\n // The credential slot — one platform token (any population) or a provider\n // that supplies one per request. Read dynamically on every request through\n // `getAuthHeaders`, so `setToken` takes effect without rebuilding the client.\n private credential: string | TokenProvider | null = null;\n\n constructor(options: ShipClientOptions = {}) {\n // SDK-boundary normalization: an empty-string token is absence of\n // credential intent, never a credential. Empty strings reach here from\n // shell-expansion of unset CI variables, empty form fields in browser\n // apps, and any other path that produces `''` instead of `undefined`.\n // Normalizing once at the SDK boundary covers every entry point: CLI,\n // Browser SDK, Node SDK, embedded consumers, and direct base-class use.\n options = {\n ...options,\n apiUrl: options.apiUrl || undefined,\n token: options.token || undefined,\n caller: options.caller || undefined,\n };\n this.clientOptions = options;\n\n // Caller identity is validated at the boundary like the token: a value\n // the API would silently drop (the header is unauthenticated) is a\n // configuration error here, never a quiet fallback to IP bucketing.\n if (options.caller !== undefined) {\n validateCaller(options.caller);\n }\n\n // One client, one identity. A token and a cookie session are different\n // principals — holding both is a configuration error, not a precedence\n // question.\n if (options.token && options.session) {\n throw ShipError.config('Provide either `token` or `session`, not both.');\n }\n\n // Static tokens are validated at the boundary (prefix-classified, same\n // rules the server applies); providers are invoked per request instead.\n if (typeof options.token === 'string') {\n validateToken(options.token);\n this.credential = options.token;\n } else if (options.token) {\n this.credential = options.token;\n }\n\n // Build the HTTP client once. The `getAuthHeaders` callback reads\n // `this.credential` dynamically on every request.\n this.http = new ApiHttp({\n ...options,\n getAuthHeaders: () => this.getAuthHeaders(),\n });\n\n const ctx = { getApi: () => this.http };\n\n this.deployments = createDeploymentResource({\n ...ctx,\n // The platform limits are fetched HERE, at the one seam that reads\n // them, rather than by every resource method. `processInput` is the\n // only consumer — the file-size, file-count and blocklist checks run\n // inside it — so this is where the round trip is earned.\n //\n // It used to be an `ensureInit()` at the top of all nineteen wrappers,\n // which meant `domains list`, `tokens list` and even `ping` each paid a\n // `/limits` request they never read. Every CLI command is one process,\n // so that was a wasted round trip on every invocation of the product.\n processInput: async (input, opts) => {\n await this.ensureInitialized();\n return this.processInput(input, opts);\n },\n });\n this.domains = createDomainResource(ctx);\n this.account = createAccountResource(ctx);\n this.tokens = createTokenResource(ctx);\n }\n\n // Environment-specific behavior.\n protected abstract processInput(\n input: DeployInput,\n options: DeploymentOptions,\n ): Promise<StaticFile[]>;\n\n /**\n * Lazy initialization — fetches platform limits (file size / count caps) once,\n * on the first API call. Subsequent calls reuse the resolved promise.\n */\n protected async ensureInitialized(): Promise<void> {\n if (!this.initPromise) {\n this.initPromise = this.fetchPlatformLimits();\n }\n return this.initPromise;\n }\n\n private async fetchPlatformLimits(): Promise<void> {\n try {\n this.platformLimits = await this.http.request<PlatformLimits>(\n API_PATHS.LIMITS,\n { method: 'GET' },\n 'Get limits',\n );\n } catch (error) {\n // Reset so the next API call can retry initialization.\n this.initPromise = null;\n throw error;\n }\n }\n\n /**\n * Ping the API server, resolving its answer: `{ success, timestamp }`, where\n * `timestamp` is the server clock in unix SECONDS.\n *\n * It resolves the response rather than a bare `true` because every other\n * method here does — narrowing to a boolean discarded the one thing ping\n * carries beyond liveness, and made `success` mean a boolean on the wire and\n * something else by the time it reached a caller. A non-OK response throws in\n * transport, so a resolved value always means the API answered.\n */\n async ping(): Promise<PingResponse> {\n // No `ensureInitialized()`: a reachability check reads no platform limits,\n // and hydrating them here made the cheapest call in the product issue two\n // requests — `/limits` and then `/ping`.\n return this.http.request<PingResponse>(API_PATHS.PING, { method: 'GET' }, 'Ping');\n }\n\n /**\n * Deploy project (convenience shortcut to `ship.deployments.upload()`).\n */\n async deploy(input: DeployInput, options?: DeploymentOptions): Promise<DeploymentCreateResponse> {\n return this.deployments.upload(input, options);\n }\n\n /**\n * Get current account information (convenience shortcut to `ship.account.get()`).\n */\n async whoami() {\n return this.account.get();\n }\n\n /**\n * Get platform limits (max file size, file count, total size).\n * Reuses the response fetched during initialization. Per-instance state —\n * does not leak between concurrent Ships against different API URLs.\n */\n async getLimits(): Promise<PlatformLimits> {\n if (this.platformLimits) return this.platformLimits;\n await this.ensureInitialized();\n // biome-ignore lint/style/noNonNullAssertion: ensureInitialized() hydrates platformLimits or throws\n return this.platformLimits!;\n }\n\n on<K extends keyof ShipEvents>(event: K, handler: (...args: ShipEvents[K]) => void): void {\n this.http.on(event, handler);\n }\n\n off<K extends keyof ShipEvents>(event: K, handler: (...args: ShipEvents[K]) => void): void {\n this.http.off(event, handler);\n }\n\n /**\n * Set global headers included in every request.\n * Useful for injecting custom headers (e.g. for admin impersonation).\n */\n setHeaders(headers: Record<string, string>): void {\n this.http.setGlobalHeaders(headers);\n }\n\n /**\n * Clear all custom global headers.\n */\n clearHeaders(): void {\n this.http.setGlobalHeaders({});\n }\n\n /**\n * Sets the client token — any platform token (API key, deploy token, OAuth\n * access token) or a {@link TokenProvider} invoked per request. Replaces\n * whatever credential the client held before.\n * @param token A platform token, sent verbatim, or a provider function\n */\n public setToken(token: string | TokenProvider): void {\n // One client, one identity — the constructor's token/session exclusion\n // holds for the client's whole life, not just its first moment.\n if (this.clientOptions.session) {\n throw ShipError.config('Provide either `token` or `session`, not both.');\n }\n if (typeof token === 'string') {\n if (!token) {\n throw ShipError.business('Invalid token provided. Token must be a non-empty string.');\n }\n validateToken(token);\n this.credential = token;\n return;\n }\n if (typeof token !== 'function') {\n throw ShipError.business(\n 'Invalid token provided. Token must be a non-empty string or a provider function.',\n );\n }\n this.credential = token;\n }\n\n /**\n * Resolve the credential slot into request headers. Async because a\n * provider may mint or refresh its token per request.\n *\n * Anonymity requires proven absence of credentials: a configured provider\n * that yields nothing is an error — the request fails typed rather than\n * silently proceeding as an anonymous public deploy. Empty-string\n * normalization at the constructor is the same invariant's boundary\n * condition: `''` is absence of intent, so it never reaches this point.\n */\n private async getAuthHeaders(): Promise<Record<string, string>> {\n if (this.credential === null) return {};\n const value = typeof this.credential === 'function' ? await this.credential() : this.credential;\n if (!value) {\n throw ShipError.authentication('Token provider returned no token.');\n }\n if (typeof value !== 'string') {\n throw ShipError.authentication('Token provider returned a non-string value.');\n }\n return { Authorization: `Bearer ${value}` };\n }\n}\n","/**\n * @file The transport. It carries requests; it does not know what they mean.\n *\n * Headers, the credential, the timeout signal, the retry loop, the event\n * vocabulary and error normalization live here — everything that is true of\n * EVERY request this client makes. What is true of one request (its path, its\n * verb, its body, its response type) lives with the resource that names it,\n * in `resources.ts`.\n *\n * **That was two statements of one fact until 2026-08-12.** This class carried\n * eighteen endpoint methods — `getDomain`, `listTokens`, … — and every one of\n * them existed to be wrapped by a resource method of the same shape, because\n * this SDK mirrors the wire 1:1 by design. Two layers that are isomorphic BY\n * DESIGN are not two layers. The endpoints went down to the resources, and\n * \"`ApiHttp` is pure transport\" stopped being an aspiration in a doc.\n */\nimport {\n API_PATHS,\n CALLER,\n DEFAULT_API,\n ErrorType,\n IDEMPOTENCY_KEY_CONSTRAINTS,\n ShipError,\n} from '@shipstatic/types';\nimport { SimpleEvents } from '../events.js';\nimport type { Fetch, ShipClientOptions } from '../types.js';\n\n// =============================================================================\n// CONSTANTS\n// =============================================================================\n\nconst DEFAULT_REQUEST_TIMEOUT = 30_000;\n\n/**\n * Retries: two, so three attempts. The CLI's own 5xx message says \"try again\",\n * and the client should take its own advice before handing that sentence to a\n * person.\n *\n * `maxRetries` on `ShipClientOptions` is the one public knob (`0` disables) —\n * the name Stripe and OpenAI use, which is what earns it README surface under\n * this repo's doc-placement rule. No env var and no CLI flag: the CLI rides\n * the default, and a flag can be added the day someone asks.\n */\nconst DEFAULT_MAX_RETRIES = 2;\n\n/** Full-jitter exponential backoff, in milliseconds. */\nconst RETRY_BASE_DELAY = 300;\nconst RETRY_MAX_DELAY = 2_000;\n\n/**\n * The server faults worth trying again. 429 is deliberately absent: the\n * platform's rate limiter has just answered, and a client that auto-retries is\n * arguing with it. (Revisit only alongside honoring `Retry-After`, as its own\n * decision.)\n */\nconst RETRYABLE_STATUS = new Set([500, 502, 503, 504]);\n\n/**\n * Sleep, unless the caller's signal says otherwise — so an abort mid-backoff\n * lands immediately instead of after the delay. Rejects with the signal's own\n * reason, which the caller's error path then classifies like any other.\n */\nfunction sleep(ms: number, signal?: AbortSignal | null): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal?.aborted) {\n reject(signal.reason);\n return;\n }\n const done = () => {\n clearTimeout(timer);\n signal?.removeEventListener('abort', onAbort);\n };\n const onAbort = () => {\n done();\n reject(signal?.reason);\n };\n const timer = setTimeout(() => {\n done();\n resolve();\n }, ms);\n signal?.addEventListener('abort', onAbort);\n });\n}\n\n/**\n * Deploys get their own ceilings, because one budget cannot fit every\n * operation this client performs.\n *\n * 30s is right for a metadata read — `/ping`, `/account`, a page of a list —\n * where anything slower is a fault rather than a big payload. A deploy is\n * bounded by the PLATFORM's limits instead: `DEPLOYMENT.MAX_TOTAL_SIZE` is\n * 50MB, and 50MB in 30s needs ~13 Mbit/s of sustained UPLOAD, above what most\n * residential links give. A deployment the API explicitly permits was being\n * aborted here by default — which is the exact failure `Idempotency-Key`\n * exists to repair, so the cause had to go and not merely the remedy.\n *\n * 5 minutes covers 50MB at ~1.4 Mbit/s.\n */\nconst DEFAULT_DEPLOY_TIMEOUT = 300_000;\n\n/**\n * The server's own budget for a build, mirrored here because the client has\n * to outlast it: the API gives the build service\n * `PERFORMANCE.BUILD_SERVICE_TIMEOUT` (`cloudflare/api/src/lib/config.ts`),\n * and that work begins only after the upload lands.\n *\n * **Raising it there must raise it here.** The two sit in different repos, so\n * nothing can fence the pair; the constraint is stated at both ends instead.\n */\nconst BUILD_SERVICE_BUDGET = 300_000;\n\n/**\n * A deploy that also builds: the upload, then the build, then the commit.\n *\n * Written as a sum rather than a number, so the name and the value compose\n * the same way — `DEPLOY` plus `BUILD` on one side, the deploy budget plus\n * the build budget on the other. A magic `600_000` would state the result\n * and hide the reasoning, and the reasoning is the part that has to survive\n * someone tuning either half.\n */\nconst DEFAULT_DEPLOY_BUILD_TIMEOUT = DEFAULT_DEPLOY_TIMEOUT + BUILD_SERVICE_BUDGET;\n\n// =============================================================================\n// TYPES\n// =============================================================================\n\nexport interface ApiHttpOptions extends ShipClientOptions {\n /** Resolves the credential slot per request — async so token providers can mint/refresh. */\n getAuthHeaders: () => Record<string, string> | Promise<Record<string, string>>;\n}\n\nexport interface RequestResult<T> {\n data: T;\n status: number;\n}\n\n/**\n * The deploy's CARRIAGE — the two facts about a deploy that are transport's\n * rather than the deployment resource's.\n *\n * The numbers are transport's because a budget for how long to wait on a wire\n * is nothing else, and the endpoint is transport's because `deployEndpoint` is\n * a client option that redirects the route. The CHOICE between the two\n * ceilings is the resource's, because only it knows that `build`/`prerender`\n * wait on work the server does after the upload lands.\n */\nexport interface DeployTransport {\n /** `/deployments`, or `/upload` where the `@internal` option redirects it. */\n readonly endpoint: string;\n /** The ordinary deploy ceiling. */\n readonly timeout: number;\n /** The ceiling when the server will also build. */\n readonly buildTimeout: number;\n}\n\n/**\n * What a resource may ask of the transport: carry this request, and tell me\n * what came back.\n *\n * This interface is the whole seam. `resources.ts` states WHICH request — the\n * path, the verb, the body, the response type — and hands it here; nothing\n * above this line knows the base URL, the credential, the retry policy or the\n * event vocabulary, and nothing below knows what a domain is.\n */\nexport interface Transport {\n request<T>(\n path: string,\n options: ShipRequestInit,\n operationName: string,\n timeoutMs?: number,\n ): Promise<T>;\n requestWithStatus<T>(\n path: string,\n options: ShipRequestInit,\n operationName: string,\n ): Promise<RequestResult<T>>;\n readonly deploy: DeployTransport;\n}\n\n/**\n * A request as THIS client composes one.\n *\n * Identical to `RequestInit` but for the headers, which are narrowed from the\n * DOM's three-shaped `HeadersInit` to the one shape every call site here\n * actually builds. That narrowing is load-bearing twice over: `mergeHeaders`\n * used to reach its record through an `as` cast, and `hasIdempotencyKey` used\n * to walk all three shapes to find a key that only ever arrives in one of\n * them. Narrowing at RUNTIME instead would have turned an unreachable case\n * into a SILENT no-retry — a deploy that quietly stopped replaying because\n * someone handed the transport a `Headers`. Here that is a compile error.\n */\nexport type ShipRequestInit = Omit<RequestInit, 'headers'> & {\n headers?: Record<string, string>;\n};\n\n// =============================================================================\n// HTTP CLIENT\n// =============================================================================\n\nexport class ApiHttp extends SimpleEvents implements Transport {\n private readonly apiUrl: string;\n private readonly getAuthHeadersCallback: () =>\n | Record<string, string>\n | Promise<Record<string, string>>;\n private readonly session: boolean;\n private readonly caller: string | undefined;\n private readonly timeout: number;\n private readonly maxRetries: number;\n private readonly fetch: Fetch;\n private globalHeaders: Record<string, string> = {};\n\n /** @see DeployTransport — the carriage facts the deployment resource reads. */\n readonly deploy: DeployTransport;\n\n constructor(options: ApiHttpOptions) {\n super();\n this.apiUrl = options.apiUrl || DEFAULT_API;\n this.getAuthHeadersCallback = options.getAuthHeaders;\n this.session = options.session ?? false;\n this.caller = options.caller;\n this.timeout = options.timeout ?? DEFAULT_REQUEST_TIMEOUT;\n this.maxRetries = Math.max(0, options.maxRetries ?? DEFAULT_MAX_RETRIES);\n // Bind to globalThis when falling back to the platform `fetch` — browsers\n // require `this === window` on `window.fetch` and throw \"Illegal invocation\"\n // when it's invoked as a property of any other object.\n this.fetch = options.fetch ?? globalThis.fetch.bind(globalThis);\n this.deploy = {\n endpoint: options.deployEndpoint || API_PATHS.DEPLOYMENTS,\n // An explicit timeout is the caller's whole answer and applies to deploys\n // too — they asked for a ceiling, not for one with an exception. Only the\n // DEFAULT splits by operation.\n timeout: options.timeout ?? DEFAULT_DEPLOY_TIMEOUT,\n buildTimeout: options.timeout ?? DEFAULT_DEPLOY_BUILD_TIMEOUT,\n };\n }\n\n /**\n * Set global headers included in every request.\n * Priority: globalHeaders (lowest) < instance auth < per-request headers (highest)\n */\n setGlobalHeaders(headers: Record<string, string>): void {\n this.globalHeaders = headers;\n }\n\n // ===========================================================================\n // CORE REQUEST INFRASTRUCTURE\n // ===========================================================================\n\n /**\n * Execute an HTTP request, retrying the failures that are worth retrying.\n *\n * The loop lives here because `attemptOnce` is already the single wrap point\n * for headers, the timeout signal, the events and error normalization — so\n * an attempt is a whole request and nothing has to be undone between two.\n *\n * **Every failure is visible, and the event NAME says whether it ended the\n * call.** One call emits `retry* (error | response)`: `request` fires per\n * attempt, so a consumer counting requests sees what actually went out; a\n * failure that will be tried again is a `retry`; `error` and `response` are\n * the two terminal answers, exactly one of which arrives.\n *\n * The failure events are emitted HERE rather than in `attemptOnce`, and\n * that placement is the whole mechanism: terminality is a property of the\n * loop — of `isRetryable` and the attempt budget — so it is knowable only\n * at the one point that owns both. An attempt cannot name its own failure.\n *\n * **The caller's `timeout` governs an ATTEMPT, not the wall clock.** Each\n * attempt is an honest request and deserves the ceiling the caller named;\n * `maxRetries` is the lever on the total. A caller who wants a hard overall\n * deadline passes their own `signal` — see `isRetryable` for why that ends\n * the loop even when it is a timeout.\n */\n private async executeRequest<T>(\n url: string,\n options: ShipRequestInit,\n operationName: string,\n timeoutMs: number = this.timeout,\n ): Promise<RequestResult<T>> {\n for (let attempt = 0; ; attempt++) {\n try {\n return await this.attemptOnce<T>(url, options, operationName, timeoutMs);\n } catch (error) {\n // `attemptOnce` already normalized; this is a pass-through.\n const shipError = ShipError.fromFetchError(error, operationName);\n if (attempt >= this.maxRetries || !this.isRetryable(shipError, options)) {\n this.emit('error', shipError, url);\n throw shipError;\n }\n // Counting from 1: the attempt that just failed, which is also which\n // retry is about to happen. See `ShipEvents.retry`.\n this.emit('retry', shipError, url, attempt + 1);\n\n // Full jitter: `random() * min(cap, base * 2^n)`. Jitter matters more\n // than the curve — it is what stops a platform hiccup from returning\n // every client in lockstep.\n const ceiling = Math.min(RETRY_MAX_DELAY, RETRY_BASE_DELAY * 2 ** attempt);\n try {\n await sleep(Math.random() * ceiling, options.signal);\n } catch (aborted) {\n // The caller stopped us mid-backoff. Their reason, their error — and\n // terminal, so it is an `error` and not a second `retry`.\n const cancelled = ShipError.fromFetchError(aborted, operationName);\n this.emit('error', cancelled, url);\n throw cancelled;\n }\n }\n }\n }\n\n /**\n * Is this failure worth another attempt?\n *\n * Two axes, and both must say yes: what went wrong, and whether the request\n * is one that may be sent twice.\n */\n private isRetryable(error: ShipError, options: ShipRequestInit): boolean {\n // The caller's own signal fired — theirs to decide, whatever the reason.\n // This is what keeps a caller-supplied `AbortSignal.timeout()` working as\n // an OVERALL deadline, and it is the ONLY thing that does: a deadline\n // classifies as `Timeout`, which the loop retries on purpose, so on the\n // error alone a caller's ceiling would look exactly like ours and be\n // silently outlived. Nothing else in this function can tell them apart.\n if (options.signal?.aborted) return false;\n\n // A maintenance 503 is a STATE, not a fault. Its message says when to come\n // back, and retrying three times with backoff only delays that sentence\n // reaching the person who needs it.\n if (error.isType(ErrorType.Maintenance)) return false;\n\n // A user abort stops everything.\n if (error.isType(ErrorType.Cancelled)) return false;\n\n // Nothing was exchanged — `Network` (a refused connection, a DNS failure)\n // or `Timeout` (a deadline of ours expired) — or one of the server faults\n // above. Both retryable members are read through the CATEGORY rather than\n // named here: \"nothing was exchanged\" IS the retryability criterion, so a\n // future member of it should inherit this answer rather than wait for\n // someone to remember this line. Naming `Timeout` beside the guard that\n // already contains it would be a second owner of that membership, free to\n // disagree with the first. `@shipstatic/types` owns it; the timeout suite\n // pins that a deadline is retried.\n const worthRetrying =\n error.isNetworkError() || (error.status !== undefined && RETRYABLE_STATUS.has(error.status));\n if (!worthRetrying) return false;\n\n const method = (options.method ?? 'GET').toUpperCase();\n if (method === 'GET' || method === 'HEAD') return true;\n\n // PUT and DELETE are semantically idempotent here and still excluded: a\n // DELETE whose response was lost answers 404 on the retry, turning a\n // success into a reported failure. The classic hazard; stay out.\n if (method === 'PUT' || method === 'DELETE') return false;\n\n // Everything else needs the server's own replay guarantee. A deploy\n // carrying `Idempotency-Key` replays its stored 201, so a retry is safe by\n // construction rather than by assumption.\n return this.hasIdempotencyKey(options.headers);\n }\n\n /**\n * Did this request carry the header that makes a repeat safe?\n *\n * Case-insensitively, because HTTP field names are — the CLI's env tier and\n * the SDK option both spell it canonically, but a caller composing headers\n * by hand is entitled not to.\n */\n private hasIdempotencyKey(headers: ShipRequestInit['headers']): boolean {\n if (!headers) return false;\n const target = IDEMPOTENCY_KEY_CONSTRAINTS.HEADER.toLowerCase();\n return Object.keys(headers).some((key) => key.toLowerCase() === target);\n }\n\n /**\n * One attempt: headers, timeout signal, the `request`/`response` events, and\n * error normalization.\n *\n * It does NOT emit a failure event. An attempt cannot know whether its own\n * failure ended the call — that is `executeRequest`'s question — so it\n * normalizes and throws, and the loop names what happened.\n */\n private async attemptOnce<T>(\n url: string,\n options: ShipRequestInit,\n operationName: string,\n timeoutMs: number = this.timeout,\n ): Promise<RequestResult<T>> {\n let cleanup = () => {};\n\n try {\n // Credential resolution runs inside the error boundary: a token\n // provider that throws or yields nothing fails the request through\n // the same typed path (and `error` event) as any transport failure.\n const headers = await this.mergeHeaders(options.headers);\n const timeout = this.createTimeoutSignal(options.signal, timeoutMs);\n cleanup = timeout.cleanup;\n\n const fetchOptions: RequestInit = {\n ...options,\n headers,\n credentials: this.session && !headers.Authorization ? 'include' : undefined,\n signal: timeout.signal,\n };\n\n this.emit('request', url, fetchOptions);\n\n const response = await this.fetch(url, fetchOptions);\n cleanup();\n\n if (!response.ok) {\n throw await ShipError.fromHttpResponse(response, operationName);\n }\n\n this.emit('response', this.safeClone(response), url);\n const data = await this.parseResponse<T>(this.safeClone(response));\n return { data, status: response.status };\n } catch (error) {\n cleanup();\n // Normalize anything thrown above (credential resolution, fetch\n // failure, abort, response error) into a ShipError.\n // fromFetchError passes existing ShipErrors through unchanged.\n throw ShipError.fromFetchError(error, operationName);\n }\n }\n\n /**\n * Send it; resolve what came back.\n *\n * Takes a PATH, not a URL: the base is this client's and nothing above needs\n * to know it. Twenty-two call sites wrote `${this.apiUrl}${API_PATHS.X}` by\n * hand before the endpoints moved out, which is twenty-two chances to\n * assemble it differently.\n */\n async request<T>(\n path: string,\n options: ShipRequestInit,\n operationName: string,\n timeoutMs?: number,\n ): Promise<T> {\n const { data } = await this.executeRequest<T>(\n `${this.apiUrl}${path}`,\n options,\n operationName,\n timeoutMs,\n );\n return data;\n }\n\n /**\n * The same, plus the HTTP status — for the one operation where the status IS\n * the answer: a domain upsert says create-or-update in its 201/200 and\n * nowhere else in the response.\n */\n async requestWithStatus<T>(\n path: string,\n options: ShipRequestInit,\n operationName: string,\n ): Promise<RequestResult<T>> {\n return this.executeRequest<T>(`${this.apiUrl}${path}`, options, operationName);\n }\n\n // ===========================================================================\n // REQUEST HELPERS\n // ===========================================================================\n\n private async mergeHeaders(\n customHeaders: Record<string, string> = {},\n ): Promise<Record<string, string>> {\n // `caller` is instance identity metadata, like the credential: the\n // rate limiter buckets by X-Caller on every write, so it rides every\n // request rather than any single operation.\n return {\n ...this.globalHeaders,\n ...(this.caller ? { [CALLER.HEADER]: this.caller } : {}),\n ...(await this.getAuthHeadersCallback()),\n ...customHeaders,\n };\n }\n\n private createTimeoutSignal(\n existingSignal?: AbortSignal | null,\n timeoutMs: number = this.timeout,\n ): {\n signal: AbortSignal;\n cleanup: () => void;\n } {\n const controller = new AbortController();\n\n // The composed signal must say WHICH deadline fired. Both used to abort\n // bare, so the SDK's own timeout, a caller's abort and a caller's\n // `AbortSignal.timeout()` all arrived as `AbortError` and all classified\n // as `Cancelled` — \"you cancelled this\" for a deadline nobody set by hand.\n // An abort REASON survives fetch's rejection verbatim (captured across\n // Node, Bun and the three engines), so each keeps its own identity: ours\n // is a `TimeoutError` naming the ceiling, and the caller's is forwarded\n // untouched. `executeRequest` retries the first and never the second.\n const timeoutId = setTimeout(\n () => controller.abort(new DOMException(`Timed out after ${timeoutMs}ms`, 'TimeoutError')),\n timeoutMs,\n );\n\n const forward = existingSignal ? () => controller.abort(existingSignal.reason) : undefined;\n if (existingSignal && forward) {\n existingSignal.addEventListener('abort', forward);\n if (existingSignal.aborted) controller.abort(existingSignal.reason);\n }\n\n return {\n signal: controller.signal,\n // The listener is removed, not just the timer: with retries a caller's\n // signal outlives the attempt, and one listener per attempt on a\n // long-lived signal is a leak that grows with every retry.\n cleanup: () => {\n clearTimeout(timeoutId);\n if (existingSignal && forward) existingSignal.removeEventListener('abort', forward);\n },\n };\n }\n\n private safeClone(response: Response): Response {\n try {\n return response.clone();\n } catch {\n return response;\n }\n }\n\n private async parseResponse<T>(response: Response): Promise<T> {\n if (response.headers.get('Content-Length') === '0' || response.status === 204) {\n return undefined as T;\n }\n return response.json() as Promise<T>;\n }\n}\n","/**\n * Event system for Ship SDK\n * Lightweight, reliable event handling with proper error boundaries\n */\n\nimport type { ShipEvents } from './types.js';\n\n/**\n * Lightweight typed event emitter.\n *\n * Public API: `on()` / `off()`. `emit()` is internal — only the SDK\n * publishes events. Throwing handlers are evicted automatically and\n * surfaced as `error` events on the next tick.\n */\nexport class SimpleEvents {\n // biome-ignore lint/complexity/noBannedTypes: the registry is heterogeneous by design — per-event signatures are enforced at the on()/emit() boundary\n private handlers = new Map<string, Set<Function>>();\n\n /**\n * Add event handler\n */\n on<K extends keyof ShipEvents>(event: K, handler: (...args: ShipEvents[K]) => void): void {\n if (!this.handlers.has(event as string)) {\n this.handlers.set(event as string, new Set());\n }\n this.handlers.get(event as string)?.add(handler);\n }\n\n /**\n * Remove event handler\n */\n off<K extends keyof ShipEvents>(event: K, handler: (...args: ShipEvents[K]) => void): void {\n const eventHandlers = this.handlers.get(event as string);\n if (eventHandlers) {\n eventHandlers.delete(handler);\n if (eventHandlers.size === 0) {\n this.handlers.delete(event as string);\n }\n }\n }\n\n /**\n * Emit event (internal use only)\n * @internal\n */\n emit<K extends keyof ShipEvents>(event: K, ...args: ShipEvents[K]): void {\n const eventHandlers = this.handlers.get(event as string);\n if (!eventHandlers) return;\n\n // Snapshot handlers so a handler that mutates the set during iteration\n // (e.g. by removing itself) doesn't skip or duplicate calls.\n const handlerArray = Array.from(eventHandlers);\n\n for (const handler of handlerArray) {\n try {\n handler(...args);\n } catch (error) {\n // A throwing handler is treated as broken — drop it so we don't\n // repeatedly invoke it and re-emit the failure as an `error` event\n // for observability. Defer the re-emit so the next tick has a clean\n // call stack and we can't recurse if the error handler also throws.\n eventHandlers.delete(handler);\n\n if (event !== 'error') {\n setTimeout(() => {\n const err = error instanceof Error ? error : new Error(String(error));\n this.emit('error', err, String(event));\n }, 0);\n }\n }\n }\n }\n}\n","/**\n * @file The SDK's vocabulary — every request it can make, stated once.\n *\n * A resource method IS its endpoint: the path, the verb, the body, the\n * response type. It hands that to the transport, which knows how to carry a\n * request and nothing about what one means.\n *\n * **These were two layers until 2026-08-12.** `ApiHttp` carried eighteen\n * endpoint methods and every factory below wrapped one of them 1:1 —\n * `get: async (name) => getApi().getDomain(name)` — because this SDK mirrors\n * the wire one method per endpoint BY DESIGN (see CLAUDE.md, \"Recorded\n * absences\"). That design is exactly what made the second layer a restatement\n * rather than an adapter: the two could not diverge without one of them being\n * wrong. Folding DOWN rather than up is what keeps the public grouping and the\n * transport separate, which was the whole point of having two files.\n *\n * The `*Resource` interfaces come from `@shipstatic/types` and did not move.\n * They are the published contract; this file is how it is met.\n */\n\nimport type { AccountResource, DeployInput, StaticFile } from '@shipstatic/types';\nimport {\n type AccountGetResponse,\n API_PATHS,\n type Deployment,\n type DeploymentCreateResponse,\n type DeploymentDeleteResponse,\n type DeploymentListResponse,\n type DeploymentResource,\n type Domain,\n type DomainDeleteResponse,\n type DomainDnsResponse,\n type DomainListResponse,\n type DomainRecordsResponse,\n type DomainResource,\n type DomainShareResponse,\n type DomainValidateResponse,\n type DomainVerifyResponse,\n IDEMPOTENCY_KEY_CONSTRAINTS,\n type ListOptions,\n ShipError,\n type Token,\n type TokenCreateResponse,\n type TokenDeleteResponse,\n type TokenListResponse,\n type TokenResource,\n validateIdempotencyKey,\n validateTtl,\n} from '@shipstatic/types';\n\nexport type {\n AccountResource,\n DeployInput,\n DeploymentResource,\n DomainResource,\n StaticFile,\n TokenResource,\n};\n\nimport type { Transport } from './api/http.js';\nimport { createDeployBody } from './core/deploy-body.js';\nimport { detectAndConfigureSPA } from './lib/spa.js';\nimport { validateDeployConfig, validateLabels, validatePassword } from './lib/validation.js';\nimport type { DeploymentOptions } from './types.js';\n\n/** JSON in, JSON out — the header every body-carrying request here sends. */\nconst JSON_HEADERS = { 'Content-Type': 'application/json' } as const;\n\n/**\n * This client's identity in a deployment's `via` field.\n *\n * Every surface that deploys names itself: the CLI sends `cli`, the GitHub\n * Action `git`, the MCP server `mcp`, the VS Code extension `vsc`, the web\n * apps `web`. A direct SDK call is `sdk`.\n *\n * Applied at this one boundary, so `via` is populated on every deploy from\n * every platform: a deploy through this SDK is never unattributed.\n */\nconst DEPLOY_VIA = 'sdk';\n\n/**\n * Serialize pagination options into a query string, or '' when there are\n * none — the paginated list endpoints accept `limit` and `cursor`.\n */\nfunction listQuery(options?: ListOptions): string {\n const params = new URLSearchParams();\n if (options?.limit !== undefined) params.set('limit', String(options.limit));\n if (options?.cursor !== undefined) params.set('cursor', options.cursor);\n const query = params.toString();\n return query ? `?${query}` : '';\n}\n\n/**\n * Shared context for all resource factories.\n *\n * A factory receives the callbacks it needs and nothing else — which is what\n * lets `getApi()` be a THUNK rather than an instance: the transport is built\n * once in the constructor, but reading it lazily is what keeps the resources\n * constructible before it exists and swappable in tests.\n */\nexport interface ResourceContext {\n getApi: () => Transport;\n}\n\n/**\n * Extended context for deployment resource.\n */\nexport interface DeploymentResourceContext extends ResourceContext {\n processInput: (input: DeployInput, options: DeploymentOptions) => Promise<StaticFile[]>;\n}\n\n/**\n * Upload deployment resource with all CRUD operations.\n *\n * There is no client-side auth branching: an upload from a credential-less\n * client simply carries no `Authorization` header, and the API grants the\n * public-account agent identity per request (claim URL + expiry on the\n * response). The SDK stays a transparent pipe either way.\n */\nexport function createDeploymentResource(\n ctx: DeploymentResourceContext,\n): DeploymentResource<DeploymentOptions> {\n const { getApi, processInput } = ctx;\n\n return {\n /**\n * The whole deploy, in the order it happens: collect the files, ask the\n * platform whether they are a SPA, validate the request boundary, build\n * the multipart body, send it.\n *\n * It read across two files until the endpoint tier folded down — the\n * collection and the SPA step here, the validators and the body in the\n * transport — for no reason a reader could see from either end.\n */\n upload: async (input: DeployInput, options: DeploymentOptions = {}) => {\n if (!processInput) {\n throw ShipError.config('processInput function is not provided.');\n }\n\n const http = getApi();\n const collected = await processInput(input, options);\n const files = await detectAndConfigureSPA(collected, http, options);\n\n if (!files.length) {\n throw ShipError.business('No files to deploy');\n }\n for (const file of files) {\n if (!file.md5) {\n throw ShipError.file(`MD5 checksum missing for file: ${file.path}`, {\n filePath: file.path,\n });\n }\n }\n\n // Fast-fail on definitely-invalid input before constructing a multipart body.\n validatePassword(options.password);\n const ttl = validateTtl(options.ttl);\n const idempotencyKey = validateIdempotencyKey(options.idempotencyKey);\n const labels = validateLabels(options.labels);\n await validateDeployConfig(files);\n\n const flags =\n options.build || options.prerender || options.spa\n ? { build: options.build, prerender: options.prerender, spa: options.spa }\n : undefined;\n const body = await createDeployBody(files, {\n labels,\n via: options.via ?? DEPLOY_VIA,\n password: options.password,\n ttl,\n flags,\n captcha: options.captcha,\n });\n\n // NO Content-Type here, deliberately: `fetch` derives it from the\n // `FormData` body along with the boundary, and setting one by hand would\n // name a boundary the body does not use.\n //\n // The idempotency key rides a header, not the body, because it must be\n // readable before the request is parsed — the API replays a stored 201\n // ahead of the write budget, so a retry costs nothing.\n return http.request<DeploymentCreateResponse>(\n http.deploy.endpoint,\n {\n method: 'POST',\n body,\n ...(idempotencyKey\n ? { headers: { [IDEMPOTENCY_KEY_CONSTRAINTS.HEADER]: idempotencyKey } }\n : {}),\n signal: options.signal || null,\n },\n 'Deploy',\n // Only `build`/`prerender` reach the build service\n // (`api/src/lib/upload-processing.ts:35`); `spa` is local detection\n // bounded by the AI tier's own 10s, so it does not earn the longer\n // ceiling. The transport owns both budgets; this is the one place that\n // knows which applies.\n options.build || options.prerender ? http.deploy.buildTimeout : http.deploy.timeout,\n );\n },\n\n list: async (options?: ListOptions) =>\n getApi().request<DeploymentListResponse>(\n `${API_PATHS.DEPLOYMENTS}${listQuery(options)}`,\n { method: 'GET' },\n 'List deployments',\n ),\n\n get: async (id: string) =>\n getApi().request<Deployment>(\n API_PATHS.DEPLOYMENT(encodeURIComponent(id)),\n { method: 'GET' },\n 'Get deployment',\n ),\n\n set: async (id: string, options: { labels: string[] }) =>\n getApi().request<Deployment>(\n API_PATHS.DEPLOYMENT(encodeURIComponent(id)),\n {\n method: 'PATCH',\n headers: JSON_HEADERS,\n body: JSON.stringify({ labels: validateLabels(options.labels) }),\n },\n 'Update deployment labels',\n ),\n\n delete: async (id: string) =>\n getApi().request<DeploymentDeleteResponse>(\n API_PATHS.DEPLOYMENT(encodeURIComponent(id)),\n { method: 'DELETE' },\n 'Delete deployment',\n ),\n };\n}\n\n/**\n * Create domain resource with all CRUD operations.\n *\n * @remarks\n * The `name` parameter in all methods is an FQDN (Fully Qualified Domain Name).\n * The SDK does not validate or normalize domain names - the API handles all domain semantics.\n */\nexport function createDomainResource(ctx: ResourceContext): DomainResource {\n const { getApi } = ctx;\n\n return {\n // INTENTIONAL DESIGN: The API does NOT support unlinking domains (setting deployment to null).\n // Once a domain is linked to a deployment, it must always have a deployment.\n // Supported: reserve (omit deployment), link, switch deployments atomically, delete entirely.\n // Not supported: unlink after linking (creates ambiguous state with no clear use case).\n // See npm/ship/CLAUDE.md \"Domain Write Semantics\" for full rationale.\n set: async (name: string, options: { deployment?: string; labels?: string[] } = {}) => {\n const labels = validateLabels(options.labels);\n const body: { deployment?: string; labels?: string[] } = {};\n if (options.deployment) body.deployment = options.deployment;\n if (labels !== undefined) body.labels = labels;\n\n // The one operation whose STATUS is part of its answer: 201 means the\n // domain was created, 200 that it was repointed, and the body says the\n // same thing either way.\n const { data, status } = await getApi().requestWithStatus<Domain>(\n API_PATHS.DOMAIN(encodeURIComponent(name)),\n { method: 'PUT', headers: JSON_HEADERS, body: JSON.stringify(body) },\n 'Set domain',\n );\n return { ...data, isCreate: status === 201 };\n },\n\n list: async (options?: ListOptions) =>\n getApi().request<DomainListResponse>(\n `${API_PATHS.DOMAINS}${listQuery(options)}`,\n { method: 'GET' },\n 'List domains',\n ),\n\n get: async (name: string) =>\n getApi().request<Domain>(\n API_PATHS.DOMAIN(encodeURIComponent(name)),\n { method: 'GET' },\n 'Get domain',\n ),\n\n delete: async (name: string) =>\n getApi().request<DomainDeleteResponse>(\n API_PATHS.DOMAIN(encodeURIComponent(name)),\n { method: 'DELETE' },\n 'Delete domain',\n ),\n\n verify: async (name: string) =>\n getApi().request<DomainVerifyResponse>(\n API_PATHS.DOMAIN_VERIFY(encodeURIComponent(name)),\n { method: 'POST' },\n 'Verify domain',\n ),\n\n // The name rides the JSON BODY, not the path: this is a pre-flight check on\n // a string that may not be a legal path segment yet.\n validate: async (name: string) =>\n getApi().request<DomainValidateResponse>(\n API_PATHS.DOMAINS_VALIDATE,\n { method: 'POST', headers: JSON_HEADERS, body: JSON.stringify({ domain: name }) },\n 'Validate domain',\n ),\n\n dns: async (name: string) =>\n getApi().request<DomainDnsResponse>(\n API_PATHS.DOMAIN_DNS(encodeURIComponent(name)),\n { method: 'GET' },\n 'Get domain DNS',\n ),\n\n records: async (name: string) =>\n getApi().request<DomainRecordsResponse>(\n API_PATHS.DOMAIN_RECORDS(encodeURIComponent(name)),\n { method: 'GET' },\n 'Get domain records',\n ),\n\n share: async (name: string) =>\n getApi().request<DomainShareResponse>(\n API_PATHS.DOMAIN_SHARE(encodeURIComponent(name)),\n { method: 'GET' },\n 'Get domain share',\n ),\n };\n}\n\n/**\n * Create account resource (whoami functionality).\n */\nexport function createAccountResource(ctx: ResourceContext): AccountResource {\n const { getApi } = ctx;\n\n return {\n get: async () =>\n getApi().request<AccountGetResponse>(API_PATHS.ACCOUNT, { method: 'GET' }, 'Get account'),\n };\n}\n\n/**\n * Create token resource for managing deploy tokens.\n */\nexport function createTokenResource(ctx: ResourceContext): TokenResource {\n const { getApi } = ctx;\n\n return {\n create: async (options: { ttl?: number; labels?: string[] } = {}) => {\n // Fast-fail on definitely-invalid input, exactly as the deploy boundary\n // does. This is the half the ttl rule's promotion into `@shipstatic/types`\n // was FOR: the envelope lived only in the API route until 2026-08-12, so\n // this call sent whatever it was handed and a bad duration cost a round\n // trip. Validating here is what makes that claim true rather than a\n // sentence in a doc.\n const ttl = validateTtl(options.ttl);\n const labels = validateLabels(options.labels);\n const body: { ttl?: number; labels?: string[] } = {};\n if (ttl !== undefined) body.ttl = ttl;\n if (labels !== undefined) body.labels = labels;\n\n return getApi().request<TokenCreateResponse>(\n API_PATHS.TOKENS,\n { method: 'POST', headers: JSON_HEADERS, body: JSON.stringify(body) },\n 'Create token',\n );\n },\n\n list: async (options?: ListOptions) =>\n getApi().request<TokenListResponse>(\n `${API_PATHS.TOKENS}${listQuery(options)}`,\n { method: 'GET' },\n 'List tokens',\n ),\n\n get: async (token: string) =>\n getApi().request<Token>(\n API_PATHS.TOKEN(encodeURIComponent(token)),\n { method: 'GET' },\n 'Get token',\n ),\n\n delete: async (token: string) =>\n getApi().request<TokenDeleteResponse>(\n API_PATHS.TOKEN(encodeURIComponent(token)),\n { method: 'DELETE' },\n 'Delete token',\n ),\n };\n}\n","/**\n * @file The deploy request body — one builder, both platforms.\n *\n * A deploy is one multipart POST: the files, their checksums, and the\n * deployment metadata that rides beside them. None of that differs by\n * platform, and since 2026-08-12 nothing here does either.\n *\n * **It was two files, and the second existed only to hand-encode.** Node built\n * its FormData with `formdata-node` and serialized it through\n * `form-data-encoder` into an ArrayBuffer with a hand-computed\n * `Content-Type` and `Content-Length`, because those objects are not the ones\n * undici's `fetch` knows how to encode. That was a real constraint on a Node\n * without a global `FormData` — and `engines.node >= 20` has had global\n * `FormData`, `File` and a multipart-encoding `fetch` all along, so the\n * constraint had already lapsed. Verified on both runtimes the SDK targets\n * (node 22, bun 1.3): a native `FormData` posted through `fetch` arrives as\n * multipart with `filename=\"assets/nested/index.html\"` intact — the deploy\n * PATH rides `File.name`, so that verbatim round trip is the whole contract.\n *\n * What the two files actually shared was every line that mattered; what they\n * differed on was a content type-check. So the check became one, and the\n * platform seam moved off the body entirely — it now lives only where it is\n * genuine, in how each platform COLLECTS files (`processInput`).\n */\n\nimport { DEPLOY_FIELDS, ShipError } from '@shipstatic/types';\nimport type { DeployBodyContext, StaticFile } from '../types.js';\n\n/**\n * Build the multipart body for a deploy.\n *\n * Returns a native `FormData`: `fetch` sets the boundary and the\n * `Content-Type` itself, which is why nothing here composes headers. Passing\n * a hand-encoded buffer with a hand-written boundary was the old shape, and\n * every part of it was a way of doing what the runtime does.\n */\nexport async function createDeployBody(\n files: StaticFile[],\n context: DeployBodyContext = {},\n): Promise<FormData> {\n const { labels, via, password, ttl, flags, captcha } = context;\n const formData = new FormData();\n const checksums: string[] = [];\n\n for (const file of files) {\n // An ASSERTION, not a wire rule: `StaticFile.content` is typed\n // `File | Buffer | Blob` and both pipelines produce one of those, so\n // reaching here means an internal bug rather than bad user input. It\n // names the path because that is the only thing that makes such a bug\n // findable. (`Buffer` is a `Uint8Array`, so all three are `BlobPart`s.)\n if (typeof file.content === 'string' || file.content === null || file.content === undefined) {\n throw ShipError.file(`Unsupported file.content type: ${file.path}`, {\n filePath: file.path,\n });\n }\n\n if (!file.md5) {\n throw ShipError.file(`File missing md5 checksum: ${file.path}`, { filePath: file.path });\n }\n\n // The deploy PATH is the filename — the API reads it off `File.name` and\n // stores the file there. The API derives Content-Type from the extension,\n // so the part's own type is deliberately opaque.\n // The cast is the two type libraries failing to agree, not a widening:\n // `Buffer` IS a `Uint8Array` and therefore a `BufferSource`, but this\n // package builds against both node and DOM lib types and\n // `Buffer<ArrayBufferLike>` does not unify with the DOM's `BlobPart`.\n // The guard above is what actually narrows the value.\n formData.append(\n DEPLOY_FIELDS.FILES,\n new File([file.content as BlobPart], file.path, { type: 'application/octet-stream' }),\n );\n checksums.push(file.md5);\n }\n\n // Index-aligned with the files above — the API checks the two lengths match.\n formData.append(DEPLOY_FIELDS.CHECKSUMS, JSON.stringify(checksums));\n\n if (labels && labels.length > 0) formData.append(DEPLOY_FIELDS.LABELS, JSON.stringify(labels));\n if (via) formData.append(DEPLOY_FIELDS.VIA, via);\n if (password) formData.append(DEPLOY_FIELDS.PASSWORD, password);\n // A multipart field is text; `ttl` is the DURATION in seconds and the API\n // turns it into an instant against its own clock. `!== undefined` rather\n // than truthiness — the rule already refuses 0, and a truthiness test would\n // drop it silently instead of letting the caller hear why.\n if (ttl !== undefined) formData.append(DEPLOY_FIELDS.TTL, String(ttl));\n if (flags?.build) formData.append(DEPLOY_FIELDS.BUILD, 'true');\n if (flags?.prerender) formData.append(DEPLOY_FIELDS.PRERENDER, 'true');\n if (flags?.spa) formData.append(DEPLOY_FIELDS.SPA, 'true');\n if (captcha) formData.append(DEPLOY_FIELDS.CAPTCHA, captcha);\n\n return formData;\n}\n","/**\n * @file SPA detection and auto-configuration — the whole concern, including\n * its one wire call.\n *\n * `checkSPA` lived on `ApiHttp` until 2026-08-12, where it was the only method\n * that read a file's CONTENT to decide what to send. Its single caller was\n * `detectAndConfigureSPA`, three lines down, so the endpoint sat one file away\n * from the only thing that ever asked for it. Bringing it here also takes it\n * off the package's public surface, which it never earned: SPA detection is\n * something the SDK does, not something a consumer calls.\n */\n\nimport {\n API_PATHS,\n DEPLOYMENT_CONFIG_FILENAME,\n SPA_CHECK_CONSTRAINTS,\n SPA_DEFAULT_CONFIG,\n type SPACheckRequest,\n type SPACheckResponse,\n} from '@shipstatic/types';\nimport type { Transport } from '../api/http.js';\nimport type { DeploymentOptions, StaticFile } from '../types.js';\nimport { calculateMD5 } from './md5.js';\n\n/**\n * Creates ship.json configuration for SPA projects.\n * @returns Promise resolving to StaticFile with SPA configuration\n */\nexport async function createSPAConfig(): Promise<StaticFile> {\n const configString = JSON.stringify(SPA_DEFAULT_CONFIG, null, 2);\n\n // Create content that works in both browser and Node.js environments\n let content: Buffer | Blob;\n if (typeof Buffer !== 'undefined') {\n // Node.js environment\n content = Buffer.from(configString, 'utf-8');\n } else {\n // Browser environment\n content = new Blob([configString], { type: 'application/json' });\n }\n\n const { md5 } = await calculateMD5(content);\n\n return {\n path: DEPLOYMENT_CONFIG_FILENAME,\n content,\n size: configString.length,\n md5,\n };\n}\n\n/**\n * Ask the platform whether this deploy is a single-page app.\n *\n * Answers `false` without a request whenever it cannot ask honestly: no\n * `index.html` at the root, an index too large for the bound the platform\n * publishes, or content in a shape neither runtime produces. Only the file's\n * TEXT and the path list go up — never the deploy itself.\n *\n * @param files - The deploy, as it stands\n * @param transport - Carries the one request\n */\nexport async function checkSPA(files: StaticFile[], transport: Transport): Promise<boolean> {\n const indexFile = files.find(\n (f) =>\n f.path === SPA_CHECK_CONSTRAINTS.INDEX_FILE ||\n f.path === `/${SPA_CHECK_CONSTRAINTS.INDEX_FILE}`,\n );\n if (!indexFile || indexFile.size > SPA_CHECK_CONSTRAINTS.MAX_INDEX_BYTES) {\n return false;\n }\n\n let indexContent: string;\n if (typeof Buffer !== 'undefined' && Buffer.isBuffer(indexFile.content)) {\n indexContent = indexFile.content.toString('utf-8');\n } else if (typeof Blob !== 'undefined' && indexFile.content instanceof Blob) {\n indexContent = await indexFile.content.text();\n } else if (typeof File !== 'undefined' && indexFile.content instanceof File) {\n indexContent = await indexFile.content.text();\n } else {\n return false;\n }\n\n const body: SPACheckRequest = { files: files.map((f) => f.path), index: indexContent };\n const response = await transport.request<SPACheckResponse>(\n API_PATHS.SPA_CHECK,\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n },\n 'SPA check',\n );\n\n return response.isSPA;\n}\n\n/**\n * Detects SPA projects and auto-generates configuration.\n * This function can be used by both Node.js and browser environments.\n *\n * @param files - Array of StaticFiles to analyze\n * @param transport - Carries the detection request\n * @param options - Deployment options containing SPA detection settings\n * @returns Promise resolving to files array with optional SPA config added\n */\nexport async function detectAndConfigureSPA(\n files: StaticFile[],\n transport: Transport,\n options: DeploymentOptions,\n): Promise<StaticFile[]> {\n // Skip if disabled, config already exists, or server will handle detection\n if (\n options.spaDetect === false ||\n options.spa ||\n options.build ||\n options.prerender ||\n files.some((f) => f.path === DEPLOYMENT_CONFIG_FILENAME)\n ) {\n return files;\n }\n\n try {\n const isSPA = await checkSPA(files, transport);\n\n if (isSPA) {\n const spaConfig = await createSPAConfig();\n return [...files, spaConfig];\n }\n } catch (_error) {\n // SPA detection failed, continue silently without auto-config\n }\n\n return files;\n}\n","/**\n * @file Client-side input validation for SDK request boundaries.\n *\n * These validators run before request construction, and this module is the\n * one import surface for them (`http.ts` takes all three from here). The\n * rules themselves live in `@shipstatic/types` — constants\n * (`LABEL_CONSTRAINTS`, `LABEL_PATTERN`) and whole checks\n * (`validatePassword`, `assertShipJsonSyntax`) alike — so the SDK and the\n * API can never disagree about what a value must look like.\n *\n * Every check here is format, never policy: length envelopes, patterns, JSON\n * syntax. Anything that can evolve server-side — password strength, plan\n * caps, the ship.json schema — is deliberately absent, because a client that\n * judged it would reject input a newer platform accepts. See\n * `@shipstatic/types/CLAUDE.md` \"Validation: format vs policy\".\n */\n\nimport type { StaticFile } from '@shipstatic/types';\nimport {\n assertShipJsonSyntax,\n DEPLOYMENT_CONFIG_FILENAME,\n LABEL_CONSTRAINTS,\n LABEL_PATTERN,\n ShipError,\n} from '@shipstatic/types';\n\n// Re-export the canonical password validator from `@shipstatic/types` so\n// existing SDK callers (`http.ts`) keep their `from '../lib/validation.js'`\n// import path unchanged. The types-tier definition is the single source of\n// truth — see `@shipstatic/types/CLAUDE.md` \"Validation: format vs policy\".\nexport { validatePassword } from '@shipstatic/types';\n\n/**\n * Validate and normalize an array of labels.\n *\n * Lowercases and trims each entry, enforces per-label length and pattern\n * (`LABEL_CONSTRAINTS` / `LABEL_PATTERN`), count cap, and uniqueness after\n * normalization. Returns the normalized array. An empty array is valid and\n * signals \"clear all labels\" on label-update operations.\n */\nexport function validateLabels(labels: string[]): string[];\nexport function validateLabels(labels: string[] | undefined | null): string[] | undefined;\nexport function validateLabels(labels: string[] | undefined | null): string[] | undefined {\n if (labels === undefined || labels === null) return undefined;\n if (labels.length === 0) return labels;\n\n if (labels.length > LABEL_CONSTRAINTS.MAX_COUNT) {\n throw ShipError.validation(`Maximum ${LABEL_CONSTRAINTS.MAX_COUNT} labels allowed`);\n }\n\n const normalized = labels.map((label, i) => {\n if (typeof label !== 'string') {\n throw ShipError.validation(`Label at index ${i} must be a string`);\n }\n const cleaned = label.trim().toLowerCase();\n if (cleaned.length < LABEL_CONSTRAINTS.MIN_LENGTH) {\n throw ShipError.validation(\n `Labels must be at least ${LABEL_CONSTRAINTS.MIN_LENGTH} characters long`,\n );\n }\n if (cleaned.length > LABEL_CONSTRAINTS.MAX_LENGTH) {\n throw ShipError.validation(\n `Labels must be no more than ${LABEL_CONSTRAINTS.MAX_LENGTH} characters long`,\n );\n }\n if (!LABEL_PATTERN.test(cleaned)) {\n throw ShipError.validation(\n `Labels must start and end with alphanumeric characters, with optional separators (${LABEL_CONSTRAINTS.SEPARATORS}) between segments`,\n );\n }\n return cleaned;\n });\n\n const unique = [...new Set(normalized)];\n if (unique.length !== normalized.length) {\n throw ShipError.validation('Duplicate labels are not allowed');\n }\n\n return unique;\n}\n\n/**\n * Validate a deploy's root `ship.json` — syntax only, never schema.\n *\n * The same format-not-policy split the password and label validators follow,\n * drawn one layer further out: ship.json's schema and its compiler live on\n * the server and evolve there, so a client that judged them would reject\n * configs a newer platform accepts. `assertShipJsonSyntax` (the types-tier\n * definition, and the single source of truth) checks only what holds for\n * every past and future schema — the text parses as JSON, and its top level\n * is an object.\n *\n * Scope matches the API's `findDeploymentConfigFile`: the exact name at the\n * deploy root, optional leading slash and nothing else, so a\n * `config/ship.json` stays an ordinary asset the platform never reads.\n */\nexport async function validateDeployConfig(files: StaticFile[]): Promise<void> {\n const config = files.find(\n (f) => f.path === DEPLOYMENT_CONFIG_FILENAME || f.path === `/${DEPLOYMENT_CONFIG_FILENAME}`,\n );\n if (!config) return;\n\n // Node hands the pipeline a `Buffer`; the browser a `File`/`Blob`, which is\n // the only one of the two carrying `.text()`.\n const content = config.content as Blob;\n const text =\n typeof content.text === 'function'\n ? await content.text()\n : (config.content as Buffer).toString('utf8');\n\n assertShipJsonSyntax(text);\n}\n","/**\n * @file Environment variable resolution for the Node.js Ship SDK.\n *\n * The SDK has exactly one ambient credential source: process environment\n * variables. `SHIP_TOKEN` (any platform token — the value's prefix says what\n * it is) and `SHIP_API_URL` are honored as the universal \"process boundary\" —\n * the one-token idiom used across the industry (`GITHUB_TOKEN`, `NPM_TOKEN`,\n * `VERCEL_TOKEN`). Constructor arguments win over env vars.\n *\n * File-based config (`~/.shiprc`, `package.json` `\"ship\"` key) is the CLI's\n * responsibility — see `src/node/cli/shiprc.ts`. The SDK does not read files,\n * which is what lets embedded consumers (MCP, the VS Code extension, GitHub\n * Action) construct `new Ship({})` for anonymous deployments without leaking\n * the host developer's personal credentials.\n */\n\nimport { SHIP_ENV, ShipError } from '@shipstatic/types';\nimport { z } from 'zod';\nimport { CREDENTIAL_FIELDS } from '../../shared/core/credential-schema.js';\nimport { getENV } from '../../shared/lib/env.js';\nimport type { ShipClientOptions } from '../../shared/types.js';\n\n// `.strict()` matches the file-config schema. The `raw` object below is\n// constructed from a fixed set of keys, so .strict() doesn't catch user\n// typos here (env vars we don't read are simply never put into `raw` in\n// the first place). What it does catch is a *contributor* error — adding\n// a new env-var read without updating `CREDENTIAL_FIELDS` produces a clear\n// validation failure rather than a silently-stripped value. Nearly free\n// (one method call), and keeps both schemas reading the same.\nconst EnvConfigSchema = z.object(CREDENTIAL_FIELDS).strict();\n\n/**\n * Map a `ShipClientOptions` field name (camelCase) back to the env var that\n * supplied it (SCREAMING_SNAKE_CASE), so validation errors point users at\n * the actual variable they need to fix. Kept as an explicit table rather\n * than a regex because the set is small, fixed, and unambiguous.\n */\nconst ENV_VAR_BY_FIELD: Record<string, string> = {\n apiUrl: SHIP_ENV.API_URL,\n token: SHIP_ENV.TOKEN,\n};\n\n/**\n * Read `SHIP_*` environment variables and validate the result.\n *\n * Empty strings (CI/Docker often sets env vars to `\"\"` instead of unsetting them)\n * are normalized to `undefined` before validation, so they don't trigger zod's\n * \"min length 1\" check or accidentally override a valid constructor argument.\n *\n * Returns an empty object outside Node.js — browser/edge runtimes have no\n * `process.env` we should reach into.\n */\nexport function readEnvConfig(): Partial<ShipClientOptions> {\n if (getENV() !== 'node') return {};\n\n const raw = {\n apiUrl: process.env[SHIP_ENV.API_URL] || undefined,\n token: process.env[SHIP_ENV.TOKEN] || undefined,\n };\n\n try {\n return EnvConfigSchema.parse(raw);\n } catch (error) {\n if (error instanceof z.ZodError) {\n const issue = error.issues[0];\n const field = issue.path[0] as string | undefined;\n const envVar = (field && ENV_VAR_BY_FIELD[field]) ?? 'SHIP environment configuration';\n throw ShipError.config(`Invalid ${envVar}: ${issue.message}`);\n }\n throw ShipError.config('Invalid environment configuration');\n }\n}\n","/**\n * @file Single source of truth for ambient-config field validation.\n *\n * Both the SDK env reader (`node/core/config.ts`) and the CLI file loader\n * (`node/cli/shiprc.ts`) import these — if we tighten or relax a rule,\n * both layers update together. The `token` field accepts any platform token;\n * strict prefix-classified format validation happens once, at the `Ship`\n * constructor boundary, for every source uniformly.\n *\n * Lives in its own file because it's a pure data constant: tests that mock\n * runtime config behavior shouldn't have to forward this through their mocks.\n */\n\nimport { z } from 'zod';\n\nexport const CREDENTIAL_FIELDS = {\n apiUrl: z.string().url().optional(),\n token: z.string().min(1).optional(),\n};\n","/**\n * @file Shared SDK exports - environment agnostic.\n */\n\nexport type { Account, Deployment, Domain, PingResponse } from '@shipstatic/types';\n// Re-export types from @shipstatic/types\nexport { ErrorType, ShipError } from '@shipstatic/types';\nexport * from './api/http.js';\nexport { Ship } from './base-ship.js';\nexport * from './core/constants.js';\nexport * from './lib/deploy-paths.js';\nexport * from './lib/env.js';\nexport * from './lib/file-validation.js';\nexport * from './lib/junk.js';\n// Shared utilities\nexport * from './lib/md5.js';\nexport * from './lib/security.js';\nexport * from './lib/text.js';\n// Core functionality\nexport * from './resources.js';\nexport * from './types.js';\n","/**\n * @file SDK-specific constants.\n * Platform constants are now in @shipstatic/types.\n */\n\n// Re-export platform constants for convenience\nexport { DEFAULT_API } from '@shipstatic/types';\n","/**\n * Utility functions for string manipulation.\n */\n\n/**\n * Simple utility to pluralize a word based on a count.\n * @param count The number to determine pluralization.\n * @param singular The singular form of the word.\n * @param plural The plural form of the word.\n * @param includeCount Whether to include the count in the returned string. Defaults to true.\n * @returns A string with the count and the correctly pluralized word.\n */\nexport function pluralize(\n count: number,\n singular: string,\n plural: string,\n includeCount: boolean = true,\n): string {\n const word = count === 1 ? singular : plural;\n return includeCount ? `${count} ${word}` : word;\n}\n"],"mappings":"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","names":["normalizeVia","value","via","DeploymentVia","validateIdempotencyKey","ShipError","key","IDEMPOTENCY_KEY_CONSTRAINTS","isTransportFailure","cause","code","isShipError","error","fileExtension","filename","basename","dotIndex","isBlockedExtension","blocked","ext","hasUnsafeChars","UNSAFE_FILENAME_CHARS","hasUnbuiltMarker","filePath","s","UNBUILT_PROJECT_MARKERS","classifyToken","token","API_KEY","TokenKind","DEPLOY_TOKEN","OAUTH_TOKEN","readBearerValue","header","BEARER_SCHEME","assertShipJsonSyntax","text","withoutBom","parsed","DEPLOYMENT_CONFIG_FILENAME","validatePrefixedCredential","shape","label","hexPart","validateApiKey","apiKey","validateDeployToken","deployToken","validateOAuthToken","oauthToken","validateToken","validateCaller","caller","CALLER","validateApiUrl","apiUrl","url","isDeployment","input","validateTtl","TTL_CONSTRAINTS","isPlatformDomain","domain","platformDomain","isCustomDomain","extractSubdomain","generateDeploymentUrl","deployment","generateDomainUrl","serializeLabels","labels","deserializeLabels","labelsJson","validatePassword","trimmed","PASSWORD_CONSTRAINTS","DeploymentStatus","DomainStatus","AccountPlan","API_PATHS","DEPLOY_FIELDS","ErrorType","CLIENT_ONLY_ERROR_TYPES","ERROR_CATEGORIES","SERVER_PRODUCIBLE_ERROR_TYPES","MAX_FOREIGN_MESSAGE_LENGTH","WEB_FILE_EXTENSIONS","WEB_FILE_ACCEPT","AUTH_BASE_PATH","SIGN_IN_RETURN_PARAM","AuthMethod","OAuthScope","SPA_DEFAULT_CONFIG","SPA_CHECK_CONSTRAINTS","DEFAULT_API","SHIP_ENV","MY_API_KEY_URL","PUBLIC_DEPLOYMENT_TTL_SECONDS","FileValidationStatus","LABEL_CONSTRAINTS","LABEL_PATTERN","init_dist","__esmMin","t","_ShipError","type","message","status","details","authDetails","response","operationName","bodyType","json","obj","retryAfterHeader","seconds","existing","op","name","resource","id","errorType","md5Blob","blob","SparkMD5","spark","chunkSize","start","end","md5Buffer","buffer","createHash","hash","md5Path","path","createReadStream","resolve","reject","stream","err","ShipError","chunk","calculateMD5","input","init_md5","__esmMin","init_dist","__setTestEnvironment","env","_testEnvironment","detectEnvironment","getENV","init_env","__esmMin","findCommonParent","dirPaths","normalizedPaths","p","pathSegments","commonSegments","minLength","i","segment","segments","normalizeWebPath","path","init_path","__esmMin","optimizeDeployPaths","filePaths","options","path","normalizeWebPath","extractFileName","commonPrefix","findCommonDirectory","filePath","deployPath","prefixToRemove","pathSegments","commonSegments","minLength","segments","i","segment","init_deploy_paths","__esmMin","init_path","firstBrokenRule","input","limits","FILE_RULES","rule","init_file_rules","__esmMin","init_dist","init_file_validation","path","validateFileName","isBlockedExtension","size","formatFileSize","totalSize","formatFileSize","bytes","decimals","k","sizes","validateFileName","filename","hasUnsafeChars","reservedNames","nameWithoutPath","validateFiles","files","config","errors","warnings","fileStatuses","issue","file","hasUnbuiltMarker","f","FileValidationStatus","totalSize","fileStatus","statusMessage","input","broken","firstBrokenRule","validFiles","canDeploy","getValidFiles","allValidFilesReady","init_file_validation","__esmMin","init_dist","init_file_rules","filterJunk","filePaths","options","p","hasUnbuiltMarker","ShipError","filePath","parts","basename","part","directorySegments","segment","JUNK_DIRECTORIES","junkDir","import_junk","init_junk","__esmMin","init_dist","validateDeployPath","deployPath","sourceIdentifier","ShipError","validateDeployFile","input","limits","broken","firstBrokenRule","init_security","__esmMin","init_dist","init_file_rules","processDeployFiles","sources","options","platformLimits","serverProcessed","deployPaths","optimizeDeployPaths","source","file","surviving","filterJunk","kept","index","deployPath","rules","requireLimits","files","totalSize","validateDeployPath","validateDeployFile","content","md5","calculateMD5","ShipError","init_deploy_files","__esmMin","init_dist","init_deploy_paths","init_junk","init_md5","init_security","node_files_exports","__export","processFilesForNode","findAllFiles","dirPath","visited","results","realPath","entry","fullPath","stats","readContent","filePath","error","message","ShipError","refuseUnbuiltProjects","paths","p","absPath","marker","e","UNBUILT_PROJECT_MARKERS","isShipError","options","platformLimits","getENV","found","unique","file","inputBasePath","findCommonParent","sources","size","contentPath","processDeployFiles","rel","fs","path","init_node_files","__esmMin","init_dist","init_deploy_files","init_env","init_path","src_exports","__export","API_KEY","API_PATHS","AUTH_BASE_PATH","AccountPlan","ApiHttp","AuthMethod","CALLER","DEFAULT_API","DEPLOYMENT_CONFIG_FILENAME","DEPLOY_FIELDS","DEPLOY_TOKEN","DeploymentStatus","DeploymentVia","DomainStatus","ErrorType","FileValidationStatus","IDEMPOTENCY_KEY_CONSTRAINTS","JUNK_DIRECTORIES","LABEL_CONSTRAINTS","LABEL_PATTERN","MY_API_KEY_URL","OAUTH_TOKEN","OAuthScope","PASSWORD_CONSTRAINTS","PUBLIC_DEPLOYMENT_TTL_SECONDS","SHIP_ENV","SIGN_IN_RETURN_PARAM","SPA_CHECK_CONSTRAINTS","SPA_DEFAULT_CONFIG","Ship","ShipError","TTL_CONSTRAINTS","TokenKind","UNBUILT_PROJECT_MARKERS","UNSAFE_FILENAME_CHARS","WEB_FILE_ACCEPT","__setTestEnvironment","allValidFilesReady","assertShipJsonSyntax","calculateMD5","classifyToken","createAccountResource","createDeploymentResource","createDomainResource","createTokenResource","node_default","deserializeLabels","extractSubdomain","filterJunk","formatFileSize","generateDeploymentUrl","generateDomainUrl","getENV","getValidFiles","hasUnbuiltMarker","hasUnsafeChars","isBlockedExtension","isCustomDomain","isDeployment","isPlatformDomain","isShipError","normalizeVia","optimizeDeployPaths","pluralize","processFilesForNode","readBearerValue","serializeLabels","validateApiKey","validateApiUrl","validateCaller","validateDeployFile","validateDeployPath","validateDeployToken","validateFileName","validateFiles","validateIdempotencyKey","validateOAuthToken","validatePassword","validateToken","validateTtl","__toCommonJS","init_dist","init_dist","init_dist","SimpleEvents","event","handler","eventHandlers","args","handlerArray","error","err","DEFAULT_REQUEST_TIMEOUT","DEFAULT_MAX_RETRIES","RETRY_BASE_DELAY","RETRY_MAX_DELAY","RETRYABLE_STATUS","sleep","ms","signal","resolve","reject","done","timer","onAbort","DEFAULT_DEPLOY_TIMEOUT","BUILD_SERVICE_BUDGET","DEFAULT_DEPLOY_BUILD_TIMEOUT","ApiHttp","SimpleEvents","options","DEFAULT_API","API_PATHS","headers","url","operationName","timeoutMs","attempt","error","shipError","ShipError","ceiling","aborted","cancelled","ErrorType","method","target","IDEMPOTENCY_KEY_CONSTRAINTS","key","cleanup","timeout","fetchOptions","response","path","data","customHeaders","CALLER","existingSignal","controller","timeoutId","forward","init_dist","init_dist","createDeployBody","files","context","labels","via","password","ttl","flags","captcha","formData","checksums","file","ShipError","DEPLOY_FIELDS","init_dist","init_md5","createSPAConfig","configString","SPA_DEFAULT_CONFIG","content","md5","calculateMD5","DEPLOYMENT_CONFIG_FILENAME","checkSPA","files","transport","indexFile","f","SPA_CHECK_CONSTRAINTS","indexContent","body","API_PATHS","detectAndConfigureSPA","options","spaConfig","init_dist","validateLabels","labels","LABEL_CONSTRAINTS","ShipError","normalized","label","cleaned","LABEL_PATTERN","unique","validateDeployConfig","files","config","f","DEPLOYMENT_CONFIG_FILENAME","content","text","assertShipJsonSyntax","JSON_HEADERS","DEPLOY_VIA","listQuery","options","params","query","createDeploymentResource","ctx","getApi","processInput","input","ShipError","http","collected","files","detectAndConfigureSPA","file","validatePassword","ttl","validateTtl","idempotencyKey","validateIdempotencyKey","labels","validateLabels","validateDeployConfig","flags","body","createDeployBody","IDEMPOTENCY_KEY_CONSTRAINTS","API_PATHS","id","createDomainResource","name","data","status","createAccountResource","createTokenResource","token","Ship","options","validateCaller","ShipError","validateToken","ApiHttp","ctx","createDeploymentResource","input","opts","createDomainResource","createAccountResource","createTokenResource","API_PATHS","error","event","handler","headers","token","value","init_env","init_dist","import_zod","import_zod","CREDENTIAL_FIELDS","init_env","EnvConfigSchema","CREDENTIAL_FIELDS","ENV_VAR_BY_FIELD","SHIP_ENV","readEnvConfig","getENV","raw","error","issue","field","envVar","ShipError","init_dist","init_dist","init_deploy_paths","init_env","init_file_validation","init_junk","init_md5","init_security","pluralize","count","singular","plural","includeCount","word","init_node_files","Ship","options","getENV","ShipError","env","readEnvConfig","input","paths","p","processFilesForNode","node_default"]}
|
|
1
|
+
{"version":3,"sources":["../node_modules/.pnpm/@shipstatic+types@2.17.0-beta.4/node_modules/@shipstatic/types/dist/index.js","../src/shared/lib/md5.ts","../src/shared/lib/env.ts","../src/shared/lib/path.ts","../src/shared/lib/deploy-paths.ts","../src/shared/lib/file-rules.ts","../src/shared/lib/file-validation.ts","../src/shared/lib/junk.ts","../src/shared/lib/security.ts","../src/shared/core/deploy-files.ts","../src/node/core/node-files.ts","../src/index.ts","../src/node/index.ts","../src/shared/base-ship.ts","../src/shared/api/http.ts","../src/shared/events.ts","../src/shared/resources.ts","../src/shared/core/deploy-body.ts","../src/shared/lib/spa.ts","../src/shared/lib/validation.ts","../src/node/core/config.ts","../src/shared/core/credential-schema.ts","../src/shared/index.ts","../src/shared/core/constants.ts","../src/shared/lib/text.ts"],"sourcesContent":["/**\n * @file Shared TypeScript types, constants, and utilities for the ShipStatic platform.\n * This package is the single source of truth for all shared data structures.\n */\n// =============================================================================\n// DEPLOYMENT TYPES\n// =============================================================================\n/**\n * Deployment status constants\n */\nexport const DeploymentStatus = {\n PENDING: 'pending',\n SUCCESS: 'success',\n FAILED: 'failed',\n DELETING: 'deleting',\n};\n/**\n * Where a deployment came from — the origin-tracking vocabulary.\n *\n * A closed set with many authors. It lived in the API's config until\n * 2026-08-06, where being server-side made it unenforceable in the one\n * direction that matters — every client wrote a bare string, and a value\n * outside the set was **silently dropped** by the server, so a typo did not\n * fail anywhere. It stopped recording where deploys came from and said nothing.\n *\n * **The origin law: origin is declared by whatever we control — our code where\n * we ship code, our URL where we ship only a URL.** One rule decides every\n * member here and every future one:\n *\n * - **Where the platform ships CODE, the code declares it.** `web`, `sdk`,\n * `cli`, `git`, `n8n` and `vsc` are surfaces this platform authors, so each\n * names itself in its own source and nothing external is needed to tell them\n * apart.\n * - **Where the platform ships only a URL, the URL declares it.** A\n * marketplace listing runs somebody else's client against a bare endpoint —\n * every one of them the same server speaking the same protocol, and\n * indistinguishable in a request. The only thing such a listing's traffic\n * has in common is the URL its users were handed, so the hosted MCP serves\n * one DOOR per listing and the door's path IS the value: `gpt`, `cld`,\n * `crs`.\n *\n * **A member names the most specific surface the platform can honestly\n * claim**, which is what makes the two FALLBACKS fallbacks rather than peers\n * of the named surfaces. `mcp` is any MCP host that was never handed a door of\n * its own; `api` is a call that reached the REST API naming nothing at all.\n * Guessing past either would be inventing attribution rather than recording\n * it, which is the one thing this vocabulary exists to prevent.\n *\n * Every member is three lowercase characters — the property that lets a\n * channel door's path and its attribution be spelled the same. The suite pins\n * both the width and the channel members by name.\n */\nexport const DeploymentVia = {\n /** The web dashboard. */\n WEB: 'web',\n /** A program embedding the SDK directly. */\n SDK: 'sdk',\n /** The `ship` CLI. */\n CLI: 'cli',\n /** Any MCP host with no door of its own — the stdio server included. The family fallback. */\n MCP: 'mcp',\n /** The GitHub Action. */\n GIT: 'git',\n /** The n8n community node. */\n N8N: 'n8n',\n /** Channel: the ChatGPT App listing → `mcp.<domain>/gpt`. */\n GPT: 'gpt',\n /** The VS Code extension. */\n VSC: 'vsc',\n /** Channel: the Claude connectors directory listing → `mcp.<domain>/cld`. */\n CLD: 'cld',\n /** Channel: the Cursor marketplace listing → `mcp.<domain>/crs`. */\n CRS: 'crs',\n /**\n * A deploy that reached the REST API naming no origin at all — the\n * platform-wide fallback, one altitude below `mcp`'s family fallback.\n *\n * **The API stamps it, since 2026-08-15.** A deploy that names no origin —\n * or names one this vocabulary does not know — is stored as `api`, so a\n * stored `null` now means only that the row predates attribution.\n *\n * It was declared one wave ahead of that decision, deliberately: vocabulary\n * must exist before a consumer can adopt it, and adding a member costs a\n * full constellation convoy, so the word shipped first and the server\n * adopted it with no convoy standing between the decision and the deploy.\n */\n API: 'api',\n};\n// =============================================================================\n// DOMAIN TYPES\n// =============================================================================\n/**\n * Domain status constants\n *\n * - PENDING: DNS not configured\n * - PARTIAL: DNS partially configured\n * - SUCCESS: DNS fully verified\n * - PAUSED: Domain paused due to plan enforcement (billing)\n */\nexport const DomainStatus = {\n PENDING: 'pending',\n PARTIAL: 'partial',\n SUCCESS: 'success',\n PAUSED: 'paused',\n};\n/**\n * The envelope an `Idempotency-Key` must fit, and how long a replay lasts.\n *\n * Format lives here rather than on the server alone by the format-vs-policy\n * rule: a client can decide offline whether a key is well-formed, and the\n * API would reject the same value the same way.\n */\nexport const IDEMPOTENCY_KEY_CONSTRAINTS = {\n /**\n * HTTP header name. Here for the same reason {@link CALLER.HEADER} is: a\n * wire header has two ends, and the package that owns the value's format\n * is the only place both ends can read its name from.\n */\n HEADER: 'Idempotency-Key',\n MAX_LENGTH: 256,\n /** How long a stored 201 stays replayable. */\n WINDOW_SECONDS: 24 * 60 * 60,\n};\n/**\n * Normalize a `via` value from any transport — trimmed, lowercased, and a\n * member of {@link DeploymentVia}, or `undefined`.\n *\n * A format rule by this package's own test: a client can decide offline\n * whether a value is well-formed, and the API reaches the same verdict on the\n * same input. It lived server-side until 2026-08-06, which meant clients could\n * only learn their label was unusable by noticing analytics had gone quiet.\n *\n * **Not knowing your `via` is not an error** — an unrecognized value yields\n * `undefined` rather than throwing, because origin tracking is telemetry and a\n * deploy must never fail over it. A caller that has an honest default should\n * prefer it (`normalizeVia(process.env.SHIP_VIA) ?? DeploymentVia.CLI`): the\n * deploy really did come from the CLI, so recording that beats recording\n * nothing.\n */\nexport function normalizeVia(value) {\n if (!value || typeof value !== 'string')\n return undefined;\n const via = value.trim().toLowerCase();\n return Object.values(DeploymentVia).includes(via)\n ? via\n : undefined;\n}\n/**\n * Validate an idempotency key, returning the trimmed value or `undefined`\n * when none was supplied. Throws {@link ShipError.validation} when the value\n * cannot be sent — the same verdict the API would reach, reached earlier.\n */\nexport function validateIdempotencyKey(value) {\n if (value === undefined || value === null)\n return undefined;\n if (typeof value !== 'string') {\n throw ShipError.validation('Idempotency key must be a string.');\n }\n const key = value.trim();\n if (!key) {\n throw ShipError.validation('Idempotency key must not be empty.');\n }\n if (key.length > IDEMPOTENCY_KEY_CONSTRAINTS.MAX_LENGTH) {\n throw ShipError.validation(`Idempotency key must be at most ${IDEMPOTENCY_KEY_CONSTRAINTS.MAX_LENGTH} characters.`);\n }\n return key;\n}\n// =============================================================================\n// ACCOUNT TYPES\n// =============================================================================\n/**\n * Every plan an account can hold — the platform's whole plan vocabulary, in\n * one place, and nothing about what a plan is WORTH.\n *\n * A plan is a TIER and nothing else. Whether an account may act is a separate\n * fact (`Account.suspended`; deletion ends the session outright), so an\n * account keeps its tier through suspension and into deletion.\n *\n * - **Free** — `free`.\n * - **Billed** — `pro`, `team`. The plans a customer buys; the only plans\n * Stripe knows about, and the only ones the platform never sets by hand —\n * each is derived from the Stripe Subscription, which names its plan on the\n * Price it is on. They form a ladder: a dearer tier is a superset of the one\n * below it, and the API says which is next in {@link Account.upgrade}.\n * - **Granted** — `scale`, `sponsored`. Paid plans the operator confers by\n * hand; no Stripe subscription, no Checkout, no Stripe object at all. These\n * and `free` are the only plans an operator can set; a billed plan is only\n * ever Stripe's to confer.\n *\n * The numbers each plan confers — caps, sizes — are POLICY and are delivered\n * by the API (`GET /plans`, `GET /account`, `GET /limits`), never published\n * here: a price or a cap in a published package is pinned to whatever version\n * a client installed (`CLAUDE.md`, \"Validation: format vs policy\").\n */\nexport const AccountPlan = {\n FREE: 'free',\n PRO: 'pro',\n TEAM: 'team',\n SCALE: 'scale',\n SPONSORED: 'sponsored',\n};\n// =============================================================================\n// WIRE SURFACE\n// =============================================================================\n/**\n * Every path the public API answers on, declared once.\n *\n * The URL surface was written out in four places — the API's mounts, the\n * SDK's client, the dashboard's client, and the post-deploy smoke — so a\n * rename meant finding all four. The first three now read this table.\n *\n * The smoke (`cloudflare/api/smoke.mjs`) deliberately still spells its own:\n * five of its nine paths are `/admin/*`, which this table excludes by\n * design, and splitting one list between a registry and literals reads worse\n * than keeping it uniform.\n *\n * **What this guarantees, exactly.** Collection paths are mounted from here,\n * so producer and consumer cannot diverge. Item paths are declared here and\n * consumed by clients, but the API spells them relative to their mount\n * (`/:deployment/config`), so the table does not *generate* them — it is\n * held to them by `api/tests/architecture/api-paths.test.ts`, which fails if\n * any entry names a path no route answers. Some entries have no client yet\n * (`DEPLOYMENT_CONFIG`, `DOMAIN_PROPAGATION` — endpoints the SDK\n * deliberately does not reach); the fence is what keeps those honest rather\n * than merely asserted.\n *\n * **The operator surface is deliberately absent.** `/admin/*` paths belong\n * to `web/my`, for the same reason its row types do: this package is\n * published, and the operator surface is not public (see `CLAUDE.md`, \"Admin\n * types\"). A path here is a promise to every npm consumer; `/admin` is a\n * promise to one dashboard.\n *\n * Item paths are functions rather than templates so the key is interpolated\n * in one place, encoded the same way by every caller.\n */\nexport const API_PATHS = {\n DEPLOYMENTS: '/deployments',\n DEPLOYMENT: (deployment) => `/deployments/${deployment}`,\n DEPLOYMENT_CONFIG: (deployment) => `/deployments/${deployment}/config`,\n DOMAINS: '/domains',\n DOMAIN: (domain) => `/domains/${domain}`,\n DOMAIN_VERIFY: (domain) => `/domains/${domain}/verify`,\n DOMAIN_DNS: (domain) => `/domains/${domain}/dns`,\n DOMAIN_RECORDS: (domain) => `/domains/${domain}/records`,\n DOMAIN_SHARE: (domain) => `/domains/${domain}/share`,\n DOMAIN_PROPAGATION: (domain) => `/domains/${domain}/propagation`,\n DOMAINS_VALIDATE: '/domains/validate',\n TOKENS: '/tokens',\n TOKEN: (token) => `/tokens/${token}`,\n ACCOUNT: '/account',\n ACCOUNT_KEY: '/account/key',\n ACCOUNT_CLAIM: '/account/claim',\n ACTIVITIES: '/activities',\n LABELS: '/labels',\n LIMITS: '/limits',\n PLANS: '/plans',\n PING: '/ping',\n SETUP: '/setup',\n SPA_CHECK: '/spa-check',\n UPLOAD: '/upload',\n};\n/**\n * The deploy request's multipart field names — the other half of the wire\n * surface beside {@link API_PATHS}. `POST /deployments` (and the first-party\n * `/upload`) is multipart/form-data, and these are the names the API reads.\n *\n * Declared once because the body has three independent WRITERS — the SDK's\n * Node and browser body builders, and the n8n community node's hand-rolled\n * client (which cannot import this under n8n Cloud's zero-dependency rule,\n * and fences its restated copy instead) — and until this export every writer\n * restated the strings the API parses, with nothing comparing them.\n *\n * `FILES` carries one entry per file (the API reads it with `getAll`); every\n * other field is single. The `@internal` flags are serialized as the literal\n * string `'true'` and belong to first-party surfaces only.\n */\nexport const DEPLOY_FIELDS = {\n /** One entry per file — read with `getAll`. */\n FILES: 'files[]',\n /** JSON array of MD5 hex digests, index-aligned with `FILES`. */\n CHECKSUMS: 'checksums',\n /** JSON array of label strings. */\n LABELS: 'labels',\n /** The deploying surface's {@link DeploymentVia} member. */\n VIA: 'via',\n /** Plaintext password — the API hashes it server-side. */\n PASSWORD: 'password',\n /**\n * Requested lifetime in SECONDS — a duration, never an instant. The API\n * computes and stores the expiry, so the wire carries no client clock.\n * See {@link validateTtl}.\n */\n TTL: 'ttl',\n /** @internal Server-processing flag — first-party `/upload` only. */\n BUILD: 'build',\n /** @internal Server-processing flag — first-party `/upload` only. */\n PRERENDER: 'prerender',\n /** @internal Server-processing flag — first-party `/upload` only. */\n SPA: 'spa',\n /** @internal reCAPTCHA proof — `web/www`'s public uploader only. */\n CAPTCHA: 'captcha',\n};\n// =============================================================================\n// ERROR SYSTEM\n// =============================================================================\n/**\n * All possible error types in the ShipStatic platform.\n *\n * Developer-friendly key names map to stable wire-format string values.\n * Both the value and the type are exported under the same name so callers\n * can use `ErrorType.Validation` (value comparison) and `: ErrorType` (type\n * annotation) without ceremony — matching the pattern other status objects\n * (`DeploymentStatus`, `DomainStatus`, `AccountPlan`, `AuthMethod`) follow.\n */\nexport const ErrorType = {\n /**\n * Validation failed. Input shape is wrong.\n *\n * Carries 400 when an API judged it — including a client-side pre-check of a\n * rule the server enforces too, which keeps the error identical wherever it\n * was caught. **Statusless** when a client rejects something no API judges,\n * such as a CLI's own command grammar: `status` is documented \"(API\n * contexts)\" on `ErrorResponse`, so there is none to report.\n */\n Validation: 'validation_failed',\n /** Resource not found (404). */\n NotFound: 'not_found',\n /** Authenticated but not allowed (403). User lacks permission for this action. */\n Forbidden: 'forbidden',\n /** Rate limit exceeded (429). */\n RateLimit: 'rate_limit_exceeded',\n /** Authentication required or failed (401). Missing/invalid credentials. */\n Authentication: 'authentication_failed',\n /** Business rule violation. Catch-all for 4xx state-rule errors that aren't more specific. */\n Business: 'business_logic_error',\n /** API server error (500). Generic server-side fault. */\n Api: 'internal_server_error',\n /**\n * The platform is closed for maintenance (503). A deliberate operator\n * state, not a fault — nothing errored; the API is refusing work on\n * purpose, and deployed sites keep serving throughout.\n *\n * Distinct from `Api` at 503, which the platform already uses for a\n * dependency that failed (moderation unavailable). A consumer has to tell\n * \"we closed the door\" from \"something broke\": the two get opposite words\n * and opposite retry behaviour.\n */\n Maintenance: 'maintenance',\n /** Network/connection error. Client-side only — set by HTTP clients on fetch failure; never produced server-side. */\n Network: 'network_error',\n /**\n * A deadline expired before the exchange completed. Client-side only — set\n * by HTTP clients when a timeout signal fires; never produced server-side.\n *\n * A member of the NETWORK category rather than a sibling of it:\n * `isNetworkError()` answers \"nothing was exchanged\", which is true of a\n * deadline exactly as it is of a refused connection, so every consumer that\n * retries, declines to report, or declines to relay a wire message on that\n * category is already right about a timeout. The distinct TYPE exists for\n * the one decision the category cannot make — what to SAY. \"Check your\n * internet connection\" is the wrong sentence for a five-minute deploy\n * ceiling, and a surface can only tell the two apart by type.\n *\n * The same relationship every comparable SDK ships:\n * `APIConnectionTimeoutError extends APIConnectionError`.\n */\n Timeout: 'timeout_error',\n /** Operation was cancelled. Client-side only — set on `AbortSignal` abort; never produced server-side. */\n Cancelled: 'operation_cancelled',\n /** File operation error. Client-side only — set by SDK during local file processing; never produced server-side. */\n File: 'file_error',\n /** Configuration error. Client-side only — set by SDK during config parsing/validation; never produced server-side. */\n Config: 'config_error',\n};\n/**\n * Error types that originate exclusively on the client (HTTP clients, SDK\n * file processing, local config parsing). These never appear on the wire\n * from the server, so `fromHttpResponse` will not trust them even if a\n * misbehaving server claims one in `body.error`.\n */\nconst CLIENT_ONLY_ERROR_TYPES = new Set([\n ErrorType.Network,\n ErrorType.Timeout,\n ErrorType.Cancelled,\n ErrorType.File,\n ErrorType.Config,\n]);\n/**\n * Categorizes error types for the `isClientError` / `isNetworkError` /\n * `isAuthError` helpers. Each `Set` is typed against the wider `ErrorType`\n * union so `.has(error.type)` accepts any value from the union.\n */\nconst ERROR_CATEGORIES = {\n /**\n * Client-attributable types. Exhaustive over the 4xx-carrying types, and\n * over the statusless ones too — those are raised locally and have no\n * status for `isClientError`'s second arm to read, so omitting one makes it\n * read as a server fault. The rule is the membership test: every type in\n * `CLIENT_ONLY_ERROR_TYPES` except the two `isNetworkError` owns belongs\n * here.\n *\n * `Cancelled` was missing until 2026-07-29, which is exactly that failure:\n * a caller who aborted their own deploy was told \"server error: please try\n * again\" — the CLI's fallback for everything this set does not claim.\n *\n * `Timeout` is deliberately NOT here, and it is the sharper case, because\n * it is the one client-only type that is not the client's fault: the\n * caller set a ceiling, but what exhausted it was the network or the\n * server. Reading it as client-attributable would say the caller erred,\n * and it would silently disarm every consumer whose retry predicate\n * declines `isClientError()` — a deadline is precisely the failure worth\n * a second attempt.\n */\n client: new Set([\n ErrorType.Business,\n ErrorType.Cancelled,\n ErrorType.Config,\n ErrorType.File,\n ErrorType.Forbidden,\n ErrorType.NotFound,\n ErrorType.RateLimit,\n ErrorType.Validation,\n ]),\n /**\n * The exchange never happened. Two types, one category: a refused\n * connection and an expired deadline differ in what a surface should SAY\n * and in nothing else a consumer decides on — both are retryable, neither\n * carries a wire message to relay, neither is worth reporting as an\n * incident. See `ErrorType.Timeout` for why the type is distinct anyway.\n */\n network: new Set([ErrorType.Network, ErrorType.Timeout]),\n auth: new Set([ErrorType.Authentication]),\n};\n/**\n * Error types the server can legitimately produce on the wire. Used by\n * `ShipError.fromHttpResponse` to validate the body's `error` field before\n * trusting it as `ShipError.type`. Derived by exclusion from\n * `CLIENT_ONLY_ERROR_TYPES` so adding a new server-producible type to\n * `ErrorType` is automatically picked up.\n */\nconst SERVER_PRODUCIBLE_ERROR_TYPES = new Set(Object.values(ErrorType).filter((t) => !CLIENT_ONLY_ERROR_TYPES.has(t)));\n/**\n * Ceiling on a message adopted from a **non-JSON** error body — a foreign\n * responder's, never this platform's. Generous for the plain-text one-liners\n * intermediaries actually send (`error code: 1015`), far below a document.\n * Our own messages are never measured against it: a JSON body is the API's\n * contract, and truncating a long validation message would be the bug.\n */\nconst MAX_FOREIGN_MESSAGE_LENGTH = 200;\n/**\n * Did the runtime say the exchange never completed?\n *\n * Clients branch on the TYPE, never on message strings, so a misclassified\n * transport failure is a lie every consumer inherits — and the one that costs\n * most: `Api` claims a server answered when nothing was exchanged, and a\n * retrying caller will not retry it.\n *\n * **Every row below is a transcript, not a belief.** Captured 2026-08-12\n * against real runtimes — Node and Bun by direct run, the three engines by a\n * one-off playwright probe, workerd through miniflare. The capture scripts are\n * in `tests/errors.test.ts`, \"runtime failure shapes\".\n *\n * | runtime | connection refused / DNS failure | malformed URL |\n * |------------------|------------------------------------------------------|--------------------------------------------------|\n * | Node 22 / undici | `TypeError: fetch failed` | `TypeError: Failed to parse URL from …` |\n * | Bun 1.3.14 | `Error` `code:'ConnectionRefused'` | `TypeError` `code:'ERR_INVALID_URL'` |\n * | Chromium 151 | `TypeError: Failed to fetch` | `TypeError: …Failed to parse URL from …` |\n * | Firefox 153 | `TypeError: NetworkError when attempting to fetch …` | `TypeError: … is not a valid URL.` |\n * | WebKit 26.5 | `TypeError: Load failed` | `TypeError: URL is not valid or contains user …` |\n * | workerd | `Error: Network connection lost.` (DNS: `internal error; reference = …`) | `TypeError: Invalid URL: …` |\n *\n * Reading that table gives the rule, and it is the INVERSE of the obvious one.\n * The transport class is unbounded — every OS, TLS and DNS failure any engine\n * will ever name — while the class fetch raises for its own ARGUMENTS is\n * small, and every runtime names the URL when it complains about one. So the\n * bounded side is the one worth testing, and the residual risk points the safe\n * way: an unrecognised sentence lands on `Network`, which says only that\n * nothing was exchanged.\n *\n * That inversion is what fixes **WebKit**, whose `Load failed` carries no code\n * and no \"fetch\", and which every browser-SDK and `@shipstatic/drop` user on\n * Safari was hitting as `Api`. It also makes the six runtimes AGREE about a\n * malformed URL, which they did not before: the previous rule tested the\n * message for \"fetch\", and Chromium's and Firefox's URL complaints both\n * contain it, so the same mistake was `Network` on three engines and `Api` on\n * three.\n *\n * **workerd is the recorded gap.** It rejects with a plain `Error`, no code\n * and no shared sentence — and its two failure modes produce two unrelated\n * ones — so nothing here can classify it and it lands on `Api`. Left alone\n * rather than patched with a dialect string: the one consumer running ship in\n * that runtime (`cloudflare/mcp`) reaches the API through a service BINDING,\n * which is in-process and does not produce transport rejections at all.\n *\n * **The `TokenProvider` case stopped being a trade when clients gained\n * retries.** A caller's provider that throws a coded error is typed `Network`\n * here, which was recorded as \"both are wrong for it; `Network` is the cheaper\n * wrong\" — written when the classification decided only what a surface would\n * SAY. It now also decides whether the call is retried, and that turns the\n * cheaper wrong into the right answer: a `TokenProvider` is where minting and\n * refresh live, so the common one is an OAuth refresh over the network, and a\n * transient failure there is precisely what another attempt repairs.\n *\n * The residual cost is a deterministic provider fault — a genuinely missing\n * keychain entry — invoking the provider three times over a few hundred\n * milliseconds before failing with the same error. No request leaves the\n * process on any of them. That is the cheap direction of a bet whose other\n * side is a refused deploy, and suppressing it would need a way to mark\n * credential faults non-retryable: machinery with one holder, refused by the\n * estate's stopping rule. Provider failures that carry no code are `Api` and\n * are not retried at all, and a provider yielding nothing is `Authentication`\n * by the fail-closed invariant, which is likewise terminal.\n */\nfunction isTransportFailure(cause) {\n const code = cause.code;\n // Bun is the one runtime that puts a CODE on an argument error, so it is\n // excluded before the code arm can claim it.\n if (code === 'ERR_INVALID_URL')\n return false;\n // A string `code` is a runtime naming a transport-level failure. An\n // allowlist of codes was written first and rejected — the TLS row alone\n // would mean enumerating BoringSSL's certificate table, and a code nobody\n // guessed is precisely the bug this closes. A `DOMException`'s code is a\n // NUMBER, so aborts and timeouts never reach here.\n if (typeof code === 'string')\n return true;\n // WHATWG has fetch reject with a TypeError for BOTH halves — network error\n // and argument error — so among TypeErrors the URL is the discriminator.\n if (cause instanceof TypeError)\n return !/\\burl\\b/i.test(cause.message);\n // Anything else — an ordinary JS fault, or workerd — is not evidence.\n return false;\n}\n/**\n * Simple unified error class for both API and SDK\n */\nexport class ShipError extends Error {\n type;\n status;\n details;\n constructor(type, message, status, details) {\n super(message);\n this.type = type;\n this.status = status;\n this.details = details;\n this.name = 'ShipError';\n }\n /** Convert to wire format */\n toResponse() {\n // Strip authentication details when they carry an `internal` telemetry\n // tag (see `ShipError.authentication` JSDoc) — these are server-side\n // diagnostics like 'session_invalid' that must not leak to clients.\n const authDetails = this.details;\n const details = this.type === ErrorType.Authentication && authDetails?.internal ? undefined : this.details;\n return {\n error: this.type,\n message: this.message,\n status: this.status,\n details,\n };\n }\n /**\n * Construct a `ShipError` from an HTTP error response.\n *\n * Best-effort body parse for `{ message, error?, details? }`. Message\n * resolution: `body.message` → `body.error` → `\"<operationName> failed with\n * status <N>\"`.\n *\n * Type resolution: trusts `body.error` when it's a known server-producible\n * `ErrorType` (preserves the wire's intent — server's\n * `ShipError.validation(...)` round-trips back to `ErrorType.Validation`\n * on the client). Falls back to status-derived (401 → Authentication,\n * 403 → Forbidden, 429 → RateLimit, else → Api) for non-API responses\n * (CDN errors, intermediaries) or malformed bodies. Client-only types\n * (`Network`, `Timeout`, `Cancelled`, `File`, `Config`) are filtered out of the\n * trusted set — a misbehaving server claiming one of those is ignored.\n *\n * `operationName` (e.g. `\"Get account\"`) is used to compose the fallback\n * message. Defaults to `\"Request\"`. Same convention as `fromFetchError`.\n *\n * Async because it reads the response body. Returns rather than throws so\n * callers can compose; most will `throw await ShipError.fromHttpResponse(...)`.\n */\n static async fromHttpResponse(response, operationName) {\n let message;\n let details;\n let bodyType;\n try {\n const contentType = response.headers.get('content-type');\n if (contentType?.includes('application/json')) {\n const json = await response.json();\n if (json && typeof json === 'object') {\n const obj = json;\n if (typeof obj.message === 'string')\n message = obj.message;\n else if (typeof obj.error === 'string')\n message = obj.error;\n details = obj.details;\n if (typeof obj.error === 'string' && SERVER_PRODUCIBLE_ERROR_TYPES.has(obj.error)) {\n bodyType = obj.error;\n }\n }\n }\n else {\n // A non-JSON body did not come from this platform — every API error\n // is `ErrorResponse` JSON — so it is an intermediary's output, and\n // the two kinds it produces need opposite treatment. A CDN's plain\n // `error code: 1015` is the most useful thing there is to say. A\n // proxy's HTML error page is a *document*, not a message: adopting it\n // verbatim made a misconfigured `apiUrl` print 2,059 characters of\n // markup as the error. Trust it only when it reads as a message.\n const text = (await response.text()).trim();\n if (text && !text.startsWith('<') && text.length <= MAX_FOREIGN_MESSAGE_LENGTH) {\n message = text;\n }\n }\n }\n catch {\n // Body unreadable; fall through to operationName-derived message.\n }\n // Rate-limit (and 503) timing rides the `Retry-After` HEADER, which a\n // body-only reader would drop. Lift it into `details` as seconds so\n // consumers can back off from the typed error alone, without keeping the\n // raw Response around. Body-carried fields are preserved and win.\n const retryAfterHeader = response.headers.get('retry-after');\n if (retryAfterHeader !== null) {\n const value = retryAfterHeader.trim();\n const seconds = /^\\d+$/.test(value)\n ? Number(value)\n : Math.ceil((Date.parse(value) - Date.now()) / 1000);\n if (Number.isFinite(seconds) && seconds >= 0) {\n const existing = details && typeof details === 'object' ? details : {};\n if (existing.retryAfter === undefined) {\n details = { ...existing, retryAfter: seconds };\n }\n }\n }\n message = message || `${operationName || 'Request'} failed with status ${response.status}`;\n const type = bodyType ??\n (response.status === 401\n ? ErrorType.Authentication\n : response.status === 403\n ? ErrorType.Forbidden\n : response.status === 429\n ? ErrorType.RateLimit\n : ErrorType.Api);\n return new ShipError(type, message, response.status, details);\n }\n /**\n * Construct a `ShipError` from an error caught around a `fetch()` call.\n *\n * The mirror of `fromHttpResponse` for the *other* side of the HTTP error\n * story — the network layer failing (offline, CORS, abort) rather than the\n * server returning a non-OK response.\n *\n * Routing:\n * - Already a `ShipError` → returned as-is (caller's intent preserved)\n * - `AbortError` → `ShipError.cancelled(...)` — someone stopped it on purpose\n * - `TimeoutError` → `ShipError.timeout(...)` — a deadline expired; the\n * message names the timeout, and the type is in the network CATEGORY\n * because nothing was exchanged\n * - A transport failure → `ShipError.network(...)` — see `isTransportFailure`\n * for what each runtime offers as evidence\n * - Any other `Error` → `ShipError(Api, ...)` (no HTTP status — fetch never reached the server)\n * - Anything else (string, undefined, etc.) → `ShipError(Api, ...)`\n *\n * **Abort and timeout are read from `name` BEFORE any `instanceof Error`\n * gate.** A `DOMException` satisfies that gate in every runtime measured\n * (Node, Bun, Chromium, Firefox, WebKit, workerd — all six), but the\n * inheritance is a comparatively recent spec change and this classification\n * has no reason to depend on it: `name` is where the meaning lives, and\n * reading it first costs nothing. The suite plants a non-`Error`\n * `DOMException` shape to hold the arm, since no runtime on the table\n * produces one.\n *\n * A caller's own `AbortSignal.timeout()` is the reachable source of\n * `TimeoutError` — and the two are NOT interchangeable per runtime: WebKit\n * reports a fired `AbortSignal.timeout()` as `AbortError`, so on Safari a\n * deadline is indistinguishable from a cancellation and lands on\n * `Cancelled`. Recorded rather than worked around; `Cancelled` is honest\n * there, since the caller's signal is what stopped it.\n *\n * The optional `operationName` is composed into the message for context:\n * `\"Get account was cancelled\"`, `\"Get account failed: ...\"`. Defaults to\n * `\"Request\"` when omitted.\n */\n static fromFetchError(cause, operationName) {\n if (isShipError(cause))\n return cause;\n const op = operationName || 'Request';\n // Read by NAME, ahead of the Error gate — see the note above.\n const name = cause?.name;\n if (name === 'AbortError') {\n return ShipError.cancelled(`${op} was cancelled`);\n }\n if (name === 'TimeoutError') {\n // A deadline: not a fault, not a cancellation, and — since it has its\n // own type — no longer merely \"network\". Nothing was exchanged, which\n // is what keeps it in the network CATEGORY and therefore retryable; the\n // type is what lets a surface say \"timed out\" instead of sending\n // someone to check their Wi-Fi. The runtime's own sentence is dropped\n // rather than relayed: \"The operation was aborted due to timeout\" is\n // the mechanism, not the news.\n return ShipError.timeout(`${op} timed out`, { cause });\n }\n if (cause instanceof Error) {\n if (isTransportFailure(cause)) {\n return ShipError.network(`${op} failed: ${cause.message}`, { cause });\n }\n return new ShipError(ErrorType.Api, `${op} failed: ${cause.message}`);\n }\n return new ShipError(ErrorType.Api, `${op} failed: Unknown error`);\n }\n // Factory methods. Uniform shape `(message, details?)` with two principled\n // exceptions: `notFound` composes its message from (resource, id?), and\n // `business` / `api` accept an optional status because they're the\n // multi-status fallbacks.\n static validation(message, details) {\n return new ShipError(ErrorType.Validation, message, 400, details);\n }\n static notFound(resource, id) {\n const message = id ? `${resource} ${id} not found` : `${resource} not found`;\n return new ShipError(ErrorType.NotFound, message, 404);\n }\n static forbidden(message, details) {\n return new ShipError(ErrorType.Forbidden, message, 403, details);\n }\n static rateLimit(message = 'Too many requests', details) {\n return new ShipError(ErrorType.RateLimit, message, 429, details);\n }\n /**\n * Construct an Authentication (401) error.\n *\n * **Telemetry pattern — `details: { internal: '<tag>' }`.** When the\n * server creates an auth error with an `internal` key in `details`\n * (e.g. `{ internal: 'session_invalid' }`), `toResponse()` strips the\n * entire `details` object before serialization. This keeps the wire\n * response a clean \"Authentication failed\" while preserving granular\n * server-side telemetry (which strategy/check failed) for logs and tests.\n *\n * Use this pattern in API auth code; do not put client-visible info under\n * `internal`. Other `details` keys round-trip normally.\n */\n static authentication(message = 'Authentication required', details) {\n return new ShipError(ErrorType.Authentication, message, 401, details);\n }\n static business(message, status = 400, details) {\n return new ShipError(ErrorType.Business, message, status, details);\n }\n static network(message, details) {\n return new ShipError(ErrorType.Network, message, undefined, details);\n }\n /**\n * A deadline expired before the exchange completed.\n *\n * Statusless like its four client-only siblings: no exchange completed, so\n * there is no HTTP status to report. `isNetworkError()` is true — see\n * `ErrorType.Timeout` for why the category is shared and the type is not.\n */\n static timeout(message, details) {\n return new ShipError(ErrorType.Timeout, message, undefined, details);\n }\n static cancelled(message, details) {\n return new ShipError(ErrorType.Cancelled, message, undefined, details);\n }\n static file(message, details) {\n return new ShipError(ErrorType.File, message, undefined, details);\n }\n static config(message, details) {\n return new ShipError(ErrorType.Config, message, undefined, details);\n }\n static api(message, status = 500, details) {\n return new ShipError(ErrorType.Api, message, status, details);\n }\n /**\n * The platform is closed for maintenance (503).\n *\n * `message` is REQUIRED and has no default here. The API is the only\n * producer of that sentence, and a default in this file would be a second\n * owner of one fact — see CLAUDE.md, \"The Constellation Law\" (stopping\n * rule). It is also the one factory whose status is fixed rather than\n * defaulted: a maintenance refusal is 503 or it is not this error.\n */\n static maintenance(message, details) {\n return new ShipError(ErrorType.Maintenance, message, 503, details);\n }\n // Semantic-category guards. For specific-type checks, use\n // `error.type === ErrorType.X` directly or the generic `isType(t)`.\n /**\n * The caller is at fault — by HTTP's own definition of a 4xx, or by a type\n * that is client-attributable without ever having a status (`Config`,\n * `File`, raised locally by the SDK).\n *\n * Both arms are load-bearing, because type and status are independent\n * axes. `fromHttpResponse` trusts `body.error` only when it names a\n * server-producible type; a non-OK response without one is status-derived,\n * so a CDN 404 or any intermediary error arrives as `Api` — a server-fault\n * *type* carrying a client *status*. Judging by type alone would report it\n * as a platform failure and bury the server's own message.\n */\n isClientError() {\n if (ERROR_CATEGORIES.client.has(this.type))\n return true;\n return this.status !== undefined && this.status >= 400 && this.status < 500;\n }\n isNetworkError() {\n return ERROR_CATEGORIES.network.has(this.type);\n }\n isAuthError() {\n return ERROR_CATEGORIES.auth.has(this.type);\n }\n isType(errorType) {\n return this.type === errorType;\n }\n}\n/**\n * Type guard to check if an unknown value is a ShipError.\n *\n * Uses structural checking instead of instanceof to handle module duplication\n * in bundled applications where multiple copies of the ShipError class may exist.\n *\n * @example\n * if (isShipError(error)) {\n * console.log(error.status, error.message);\n * }\n */\nexport function isShipError(error) {\n return (error !== null &&\n typeof error === 'object' &&\n 'name' in error &&\n error.name === 'ShipError' &&\n 'status' in error);\n}\n// =============================================================================\n// EXTENSION MATCHING\n// =============================================================================\n/**\n * The rule for reading a file's extension: lowercase, after the last dot of\n * the last path segment. `null` when there is no extension to read.\n *\n * A leading dot names the file rather than its type, so `.gitignore` and\n * `.htaccess` have no extension — but `.env.exe` has `exe`.\n *\n * Segment-aware on purpose: the callers pass deploy PATHS, not basenames, and\n * a naive `lastIndexOf('.')` over `dir.v1/README` reads the extension\n * `v1/README` — safe only by accident, since no entry in a real blocklist\n * contains a slash, which is the kind of correctness nobody should have to\n * re-derive.\n *\n * **Private, and the reason is the asymmetry rather than any hazard.** Nothing\n * outside this file reads it: `isBlockedExtension` is the only question anyone\n * asks, and the API's refusal names the FILE, not its extension. Exporting a\n * pure function is harmless, which is exactly the argument that talks a\n * published package into surface it has not earned — and the costs do not\n * match, since adding an export later is free under the additive law while\n * removing one is a major. So it stays private until a caller exists. Its\n * behaviour is fenced through `isBlockedExtension`, which is where it is\n * observable.\n *\n * (`WEB_FILE_EXTENSIONS` above is private for a different reason — publishing\n * it would invite a wrong question. Both are private; only one is a hazard.)\n *\n * @example\n * fileExtension('virus.exe') // 'exe'\n * fileExtension('assets/style.CSS') // 'css'\n * fileExtension('dir.v1/README') // null\n * fileExtension('.gitignore') // null\n * fileExtension('file.') // null\n */\nfunction fileExtension(filename) {\n const basename = filename.replace(/\\\\/g, '/').split('/').pop() ?? '';\n const dotIndex = basename.lastIndexOf('.');\n if (dotIndex <= 0 || dotIndex === basename.length - 1)\n return null;\n return basename.slice(dotIndex + 1).toLowerCase();\n}\n/**\n * Whether a file is one the platform refuses to host.\n *\n * **The list is not this package's, and that separation is the point.** What\n * counts as a blocked extension is hosting POLICY — it evolves, it is enforced\n * at one security boundary, and `virus.exe` is a perfectly well-formed\n * filename that breaks nothing about the upload→serve round-trip. So the API\n * owns the list (`cloudflare/api/src/lib/blocklist.ts`) and delivers it as\n * `PlatformLimits.blockedExtensions`; a client passes what it was given.\n *\n * What lives here is the MATCHING RULE, and it earns its place by the\n * constellation law's own test. The list's drift is loud in both directions —\n * a stale client uploads a file the API refuses by name, on the first try.\n * A second *matcher* drifts SILENTLY in the one direction that matters: a\n * client stricter than the server refuses a legal file without the server ever\n * being asked, and no error names it. Two holders, silent drift, one owner.\n *\n * The `blocked` collection is required rather than defaulted: this predicate\n * guards a security boundary in the API, and a defaulted-empty argument there\n * would block nothing while reading as though it did. Callers holding a\n * possibly-absent wire field spell the fail-open themselves.\n *\n * @example\n * isBlockedExtension('virus.exe', ['exe']) // true\n * isBlockedExtension('virus.EXE', ['exe']) // true — case-insensitive\n * isBlockedExtension('style.css', ['exe']) // false\n * isBlockedExtension('README', ['exe']) // false — no extension\n */\nexport function isBlockedExtension(filename, blocked) {\n const ext = fileExtension(filename);\n if (ext === null)\n return false;\n return Array.isArray(blocked) ? blocked.includes(ext) : blocked.has(ext);\n}\n// =============================================================================\n// PICKER ACCEPT HINT\n// =============================================================================\n/**\n * The extensions a browser file picker offers by default, grouped by role.\n *\n * Private on purpose: the only published form is `WEB_FILE_ACCEPT`, the\n * attribute value itself. A published set would invite a call site to ask it\n * whether a file is allowed — which is the one thing this list must never\n * answer. See `WEB_FILE_ACCEPT`.\n *\n * Extensionless files (`LICENSE`, most `.well-known` entries) are inexpressible\n * in `accept`, and reach a deployment by folder pick, ZIP, or drag-and-drop.\n */\nconst WEB_FILE_EXTENSIONS = [\n // Markup & documents\n 'html',\n 'htm',\n 'xhtml',\n 'xml',\n 'txt',\n 'md',\n 'markdown',\n 'pdf',\n 'csv',\n // Data & config\n 'json',\n 'jsonc',\n 'webmanifest',\n 'map',\n 'toml',\n 'yaml',\n 'yml',\n 'rss',\n 'atom',\n // Styles\n 'css',\n 'scss',\n 'sass',\n 'less',\n // Scripts & modules\n 'js',\n 'mjs',\n 'cjs',\n 'jsx',\n 'ts',\n 'tsx',\n 'wasm',\n 'vue',\n 'svelte',\n // Images\n 'png',\n 'jpg',\n 'jpeg',\n 'gif',\n 'webp',\n 'avif',\n 'svg',\n 'ico',\n 'bmp',\n 'tif',\n 'tiff',\n 'heic',\n 'heif',\n // Fonts\n 'woff',\n 'woff2',\n 'ttf',\n 'otf',\n 'eot',\n // Audio\n 'mp3',\n 'wav',\n 'ogg',\n 'oga',\n 'opus',\n 'm4a',\n 'aac',\n 'flac',\n 'weba',\n // Video\n 'mp4',\n 'webm',\n 'ogv',\n 'mov',\n 'm4v',\n 'avi',\n // 3D models\n 'glb',\n 'gltf',\n 'usdz',\n // Text tracks\n 'vtt',\n 'srt',\n // Archive — a whole site in one file\n 'zip',\n];\n/**\n * The `accept` attribute value for a browser file picker offering web files.\n *\n * **This is a hint, never a rule.** The API's blocklist is the platform's gate\n * and the only thing that decides what may be hosted; this constant decides\n * what a *file dialog* shows first. The two are not two halves of one policy,\n * and this one must never be consulted to accept or reject a file.\n *\n * The distinction is structural, not stylistic. `accept` can express only an\n * allowlist, while the platform's rule is a blocklist — so this list is\n * necessarily *narrower* than what the platform hosts, and reading it as\n * authority would reject files the platform serves happily. It is also not\n * enforcement in the browser's own terms: every file dialog offers an\n * all-files escape, and **drag-and-drop ignores `accept` entirely**. The\n * dropzone and the picker must reach the same verdict on the same files, and\n * they do — because the verdict is `validateFiles`, downstream of both.\n *\n * The invariant that matters — the picker must never offer a file the platform\n * will refuse — is fenced where the authority lives, in the API's own suite\n * (`cloudflare/api/tests/lib/blocklist.test.ts`), which reads this published\n * string and holds it against the list it owns. It sat here until the\n * blocklist became the API's, and moving it was the price of that: a fence\n * belongs with whichever side can change and break it.\n */\nexport const WEB_FILE_ACCEPT = WEB_FILE_EXTENSIONS.map((ext) => `.${ext}`).join(',');\n// =============================================================================\n// FILENAME CHARACTER VALIDATION\n// =============================================================================\n/**\n * Characters that are unsafe in filenames for static hosting.\n *\n * Blocks only characters that genuinely break the upload→serve round-trip:\n * - # ? % URL round-trip breakers (fragment, query, encoding ambiguity)\n * - \\ Path separator confusion (upload splits on backslash)\n * - < > \" XSS vectors with zero legitimate use in filenames\n * - \\x00-\\x1f \\x7f Control characters (header injection, display corruption)\n *\n * Everything else is allowed — browser percent-encodes, Worker decodes, R2 matches.\n */\n// biome-ignore lint/suspicious/noControlCharactersInRegex: blocking control characters is this regex's purpose\nexport const UNSAFE_FILENAME_CHARS = /[\\x00-\\x1f\\x7f#?%\\\\<>\"]/;\n/**\n * Check if a filename contains unsafe characters.\n *\n * @example\n * hasUnsafeChars('saved_resource(1).html') // false — parentheses are safe\n * hasUnsafeChars('page[slug].js') // false — brackets are safe\n * hasUnsafeChars('file#anchor.html') // true — # breaks URL resolution\n * hasUnsafeChars('file<tag>.html') // true — < is an XSS vector\n */\nexport function hasUnsafeChars(filename) {\n return UNSAFE_FILENAME_CHARS.test(filename);\n}\n// =============================================================================\n// UNBUILT PROJECT MARKERS\n// =============================================================================\n/**\n * Path segment names that indicate an unbuilt project was uploaded instead of build output.\n * Used for early detection in CLI, browser, and server validation.\n */\nexport const UNBUILT_PROJECT_MARKERS = new Set([\n 'node_modules',\n 'package.json',\n]);\n/**\n * Check if a file path contains an unbuilt project marker.\n *\n * @example\n * hasUnbuiltMarker('node_modules/react/index.js') // true\n * hasUnbuiltMarker('package.json') // true\n * hasUnbuiltMarker('dist/index.html') // false\n */\nexport function hasUnbuiltMarker(filePath) {\n const segments = filePath.replace(/\\\\/g, '/').split('/').filter(Boolean);\n return segments.some((s) => UNBUILT_PROJECT_MARKERS.has(s));\n}\n// =============================================================================\n// CREDENTIAL SHAPES\n// =============================================================================\n// The one address for credential vocabulary: where human identity lives\n// (AUTH_BASE_PATH), how a request is authorized (AuthMethod), the shapes\n// that distinguish populations on the wire (API_KEY, DEPLOY_TOKEN,\n// OAUTH_TOKEN, CALLER), the two halves of the one Bearer slot\n// (readBearerValue reads it, classifyToken/TokenKind dispatch on what came\n// out), and the delegated-access scopes (OAuthScope).\n//\n// THE SHAPE LAW, in three clauses, over the `Authorization: Bearer` slot's\n// three populations below. The deployment claim code is the API's own\n// (`AUTH.CLAIM`, server-side: the API mints it and the API validates it, so\n// it has one holder and stays there) and shares only clause 1 — it is the\n// platform's one deliberately BARE secret, because it never enters the\n// Bearer slot: minted into one URL, consumed by one endpoint's one field,\n// its context names it and a prefix would restate its route.\n// `tests/validation-constants.test.ts` holds the clauses over this file's\n// populations; the API's suite holds its own.\n//\n// 1. ONE ENTROPY STANDARD. Every minted secret is `HEX_LENGTH` hex characters\n// — one width for the whole platform, so \"how long is a credential\" has a\n// single answer rather than one per population. Generators read the width\n// from the population's own constant, so a minted value and an accepted\n// value cannot differ.\n//\n// 2. EVERY BEARER POPULATION IS NAMED BY ITS PREFIX. A credential says what\n// it is before anything parses it — which is what lets `classifyToken`\n// below dispatch three populations sharing one `Authorization: Bearer`\n// slot, and what lets a value found in a log, a support ticket or a\n// pasted URL be recognised and revoked on sight. The OAuth access token\n// was this clause's one standing exception until 2026-08-14 — the\n// authorization server it was born on had no mint hook to give it a\n// prefix, and its successor does.\n//\n// 3. NO PREFIX IS A PREFIX OF ANOTHER. This is what makes the dispatch\n// order-independent, and it is the reason the populations are named on\n// different axes (`ship-` for the product, `deploy-` for the capability)\n// rather than sharing a stem. A `ship-` / `ship-deploy-` pair\n// reads tidier and is a trap: every deploy token would also match the\n// API-key branch, leaving correctness resting on the order of two `if`s.\n/**\n * Where human identity is mounted on the API host. The API mounts Better\n * Auth at this path (sign-in, sign-out, session reads, admin impersonation)\n * and the web console's auth client posts to it — shared here so the two\n * halves of the auth pair agree by construction, the same way both sides\n * already share the credential prefixes below.\n */\nexport const AUTH_BASE_PATH = '/auth';\n/**\n * The query marker a completed sign-in LANDS with.\n *\n * The API's magic-link verify leg stamps `?signing-in=1` onto its success\n * redirect, and the console boots into its wait screen on seeing it — two\n * repos, one spelling, which is why it lives here. Success is marked and the\n * error leg deliberately is NOT: the console gives the marker precedence, so\n * a marked error would render a wait that resolves to bare doors with the\n * error's sentence lost. If the spellings ever diverged the failure would be\n * invisible to every suite — email landings would flash the doors for one\n * round trip instead of waiting — which is exactly the silent-drift class\n * this constitution exists to delete.\n */\nexport const SIGN_IN_RETURN_PARAM = 'signing-in';\n/**\n * How a request (or recorded activity) was authorized.\n *\n * Client populations: `SESSION` (first-party cookie), `API_KEY` (`ship-`\n * key), `TOKEN` (`deploy-` deploy token), `AGENT` (anonymous public deploy —\n * no credential; the platform grants the public-account identity per\n * request), `OAUTH` (delegated access token). The one server population:\n * `SYSTEM` (scheduled/background jobs). Webhook receipt is deliberately not\n * a population: a signed delivery is verified, never authorized — it acts\n * as no one and audits as no one.\n */\nexport const AuthMethod = {\n SESSION: 'session',\n API_KEY: 'apiKey',\n TOKEN: 'token',\n AGENT: 'agent',\n OAUTH: 'oauth',\n SYSTEM: 'system',\n};\n/**\n * Shape constants for API keys (`ship-{32 hex chars}`).\n * Single source of truth used by validation utilities and auth middleware.\n */\nexport const API_KEY = {\n /** Prefix that identifies an API key. */\n PREFIX: 'ship-',\n /** Number of hex characters following the prefix. */\n HEX_LENGTH: 32,\n /** Total length of an API key including prefix (`PREFIX.length + HEX_LENGTH = 37`). */\n TOTAL_LENGTH: 37,\n /** Number of trailing characters used to display a redacted hint (e.g. last 4). */\n HINT_LENGTH: 4,\n};\n/**\n * Shape constants for deploy tokens (`deploy-{32 hex chars}`).\n * Single source of truth used by validation utilities and auth middleware.\n *\n * Deliberately the same width as `API_KEY`: both are minted by one generator\n * and classified by prefix alone, so a length that differed between them\n * would be a second thing to know about a credential whose prefix already\n * says what it is.\n */\nexport const DEPLOY_TOKEN = {\n /** Prefix that identifies a deploy token. */\n PREFIX: 'deploy-',\n /** Number of hex characters following the prefix. */\n HEX_LENGTH: 32,\n /** Total length of a deploy token including prefix (`PREFIX.length + HEX_LENGTH = 39`). */\n TOTAL_LENGTH: 39,\n};\n/**\n * Shape constants for OAuth access tokens (`oauth-{32 hex chars}`) — the\n * delegated population, minted by the platform's own authorization server\n * for a connected app acting on a user's behalf.\n *\n * Same width as the other two, and for the same reason: one entropy standard\n * across the platform, so \"how long is a credential\" has one answer.\n *\n * **This population is the access token alone.** Refresh tokens, authorization\n * codes and client secrets are deliberately NOT here and are deliberately not\n * prefixed by this constant: none of them ever enters the `Authorization:\n * Bearer` slot — a refresh token is posted as a form field to the token\n * endpoint, which knows what it is receiving — so `classifyToken` never sees\n * one and a prefix would name a population no dispatcher dispatches. The same\n * reasoning that keeps the deployment claim code bare.\n *\n * **The prefix must be applied at the MINT, never as a display wrapper.** The\n * authorization server hashes what it stores and the API hashes what it is\n * presented, so the prefix has to be inside the hashed string on both sides.\n * `@better-auth/oauth-provider` offers a `prefix.opaqueAccessToken` option\n * that prepends AFTER hashing and strips on its own read paths; using it would\n * store a hash of the UNPREFIXED token and silently break the platform's read\n * arm. The API therefore mints through `generateOpaqueAccessToken` — recorded\n * beside the config in `cloudflare/api/src/lib/auth/instance.ts`.\n */\nexport const OAUTH_TOKEN = {\n /** Prefix that identifies an OAuth access token. */\n PREFIX: 'oauth-',\n /** Number of hex characters following the prefix. */\n HEX_LENGTH: 32,\n /** Total length including prefix (`PREFIX.length + HEX_LENGTH = 38`). */\n TOTAL_LENGTH: 38,\n};\n/**\n * Shape constants for caller identifiers (the `X-Caller` instance-identity\n * header — rate-limit bucketing for multi-tenant orchestrators). The API\n * normalizes case and silently ignores malformed values (the header is\n * unauthenticated); clients validate at the boundary via `validateCaller`,\n * so a value the server would drop fails fast instead.\n */\nexport const CALLER = {\n /** HTTP header name. */\n HEADER: 'X-Caller',\n /** Maximum identifier length. */\n MAX_LENGTH: 128,\n /** Allowed characters: alphanumeric, dot, underscore, hyphen. */\n PATTERN: /^[a-zA-Z0-9._-]+$/,\n};\n/**\n * Token populations distinguishable by shape. The platform carries every\n * client token in one wire slot (`Authorization: Bearer <value>`) and\n * classifies by value, never by a side channel — this is the classifier.\n *\n * `API_KEY`, `DEPLOY_TOKEN` and `OAUTH` *are* `AuthMethod.API_KEY`,\n * `AuthMethod.TOKEN` and `AuthMethod.OAUTH` — the equality is structural, so\n * a classification flows straight into an auth method and the trio can never\n * drift.\n *\n * `OPAQUE` is any other value, and since 2026-08-14 it names NO population:\n * every credential this platform mints for the Bearer slot carries a prefix,\n * so an opaque bearer is a bearer we did not mint. It stays a member rather\n * than becoming a `null` return because a dispatcher with a total codomain\n * reads better than one with an absence in it — and because it is where a\n * future population would land before anyone gave it a shape, which is\n * exactly what the OAuth token itself did until its prefix existed.\n */\nexport const TokenKind = {\n API_KEY: AuthMethod.API_KEY,\n DEPLOY_TOKEN: AuthMethod.TOKEN,\n OAUTH: AuthMethod.OAUTH,\n OPAQUE: 'opaque',\n};\n/**\n * Classify a client token by shape. The single dispatch used by both sides\n * of the wire: API auth middleware (which population is this credential?)\n * and SDK validation (which format rules apply before sending?). Sharing it\n * is what guarantees client and server can never disagree on dispatch.\n */\nexport function classifyToken(token) {\n if (token.startsWith(API_KEY.PREFIX))\n return TokenKind.API_KEY;\n if (token.startsWith(DEPLOY_TOKEN.PREFIX))\n return TokenKind.DEPLOY_TOKEN;\n if (token.startsWith(OAUTH_TOKEN.PREFIX))\n return TokenKind.OAUTH;\n return TokenKind.OPAQUE;\n}\n/** The auth-scheme, lowercased — the form the comparison is made in. */\nconst BEARER_SCHEME = 'bearer ';\n/**\n * Read the credential out of an `Authorization` header value — the step\n * BEFORE `classifyToken`, and the other half of the one wire slot this\n * section owns.\n *\n * Returns the credential's own bytes, or `null` when the header carries a\n * foreign scheme or nothing after the scheme.\n *\n * **The scheme is folded; the credential is not.** RFC 7235 §2.1 makes the\n * auth-scheme case-insensitive, so `bearer`, `Bearer` and `BEARER` are the\n * same header. The value after it is opaque and is compared literally\n * everywhere it is used — `ship-`/`deploy-`/`oauth-` are lowercase hex, and\n * folding them would make a credential match values it is not.\n *\n * **This platform has paid for the rule twice, which is why it has an owner\n * rather than a convention.** A spec-conformant `bearer ship-…` client was\n * refused for as long as the API's scheme test was spelled case-sensitively;\n * and `@better-auth/oauth-provider` carries the same defect in four places\n * today (`startsWith(\"Bearer \")`), which is precisely why the platform folds\n * the scheme itself and hands the provider a bare token.\n *\n * **ABSENCE is deliberately not this function's business.** A missing header\n * and an unreadable one are different facts, and the callers that care split\n * on them: the API worker's middleware distinguishes `absent` (the only\n * anonymous path) from `unreadable` (a presented credential that is refused),\n * and collapsing the two here would take that distinction away from the layer\n * that needs it. Callers check for the header themselves and pass its value.\n *\n * **Why this lives in the constitution rather than in a worker's `shared/`.**\n * It is the same wire boundary `classifyToken` already owns — one reads the\n * slot, the other dispatches on what came out — and a rule with two holders\n * whose drift is silent earns exactly one owner regardless of what the\n * convoy costs. The estate's recorded refusal to own a `Bearer` CONSTANT\n * stands and is a different thing: that is RFC vocabulary, the same reason\n * this package owns no `\"POST\"`. A parser is not a spelling.\n */\nexport function readBearerValue(header) {\n if (header.slice(0, BEARER_SCHEME.length).toLowerCase() !== BEARER_SCHEME)\n return null;\n return header.slice(BEARER_SCHEME.length) || null;\n}\n/**\n * OAuth scope vocabulary for delegated third-party access tokens.\n * Single source of truth used by the authorization server (advertised in\n * `scopes_supported`), the API's scope-enforcement middleware, and consent UI\n * copy. The standard `offline_access` scope (refresh tokens) is not platform\n * vocabulary and is deliberately absent — the middleware never checks it.\n *\n * Deliberately absent by design: any `tokens:*` scope, `account:write`, or\n * admin scope — a delegated app must never mint credentials, delete the\n * account, or act as admin.\n */\nexport const OAuthScope = {\n ACCOUNT_READ: 'account:read',\n DEPLOYMENTS_READ: 'deployments:read',\n DEPLOYMENTS_WRITE: 'deployments:write',\n DOMAINS_READ: 'domains:read',\n DOMAINS_WRITE: 'domains:write',\n};\n// =============================================================================\n// DEPLOYMENT CONFIGURATION CONSTANTS\n// =============================================================================\nexport const DEPLOYMENT_CONFIG_FILENAME = 'ship.json';\n/** Default ship.json config for SPA routing. Single source of truth — used by both API and SDK. */\nexport const SPA_DEFAULT_CONFIG = {\n rewrites: [{ source: '/(.*)', destination: '/index.html' }],\n};\n/**\n * The `/spa-check` pre-flight's client-side envelope: which file is the\n * check's subject, and how large it may be before a client skips the call.\n *\n * One fact with three holders until this export — the API's config declared\n * the cap, the SDK's `checkSPA` hardcoded `100 * 1024`, and prose restated\n * \"100KB\". `INDEX_FILE` is the selection rule (the file whose content rides\n * `SPACheckRequest.index`), restated by every client that builds the request.\n *\n * Neither member is a validation boundary: a client over the cap simply\n * skips the pre-flight, because the server answers an oversized index\n * `isSPA: false` anyway. A consumer that cannot import this (n8n) needs no\n * size copy at all — outcome parity is the server's, not the client's.\n */\nexport const SPA_CHECK_CONSTRAINTS = {\n /** The file whose content is the check's subject. */\n INDEX_FILE: 'index.html',\n /** Skip the pre-flight above this size — the server would answer false. */\n MAX_INDEX_BYTES: 100 * 1024,\n};\n/**\n * Assert that a ship.json file is *syntactically* loadable. Syntax only —\n * never schema.\n *\n * ship.json is validated and compiled on the server, deliberately: the schema\n * and the compiler evolve, and a client that judged them would reject configs\n * a newer platform accepts. That reasoning bounds what a client may check to\n * the properties which are true of *every* past and future schema:\n *\n * 1. it parses as JSON — JSON syntax is frozen (RFC 8259), so text that\n * does not parse can never be a valid config;\n * 2. its top level is an object — ship.json is `{ ... }` in every version.\n *\n * Both are monotonic: neither can ever reject something the server would\n * accept. Everything beyond them (field names, types, rule semantics, which\n * keys are permitted) stays server-side, where it can change.\n *\n * The payoff is the common case. Hand-edited JSON fails on a trailing comma,\n * a `//` comment, single quotes, unquoted keys, or smart quotes pasted from\n * documentation — mistakes that otherwise cost a full upload round-trip to\n * discover. A UTF-8 BOM (Windows editors, PowerShell redirects) is stripped\n * before parsing rather than rejected, because the server accepts it too;\n * diverging there would reintroduce exactly the false rejection this\n * function exists to avoid.\n *\n * @throws {ShipError} `ErrorType.Config` — the same type the server's own\n * config rejection carries, so the error contract is identical wherever the\n * failure is detected.\n */\nexport function assertShipJsonSyntax(text) {\n const withoutBom = text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;\n let parsed;\n try {\n parsed = JSON.parse(withoutBom);\n }\n catch (error) {\n throw ShipError.config(`invalid JSON format in config: ${error.message}`, {\n filePath: DEPLOYMENT_CONFIG_FILENAME,\n });\n }\n if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {\n throw ShipError.config(`${DEPLOYMENT_CONFIG_FILENAME} must contain a JSON object`, {\n filePath: DEPLOYMENT_CONFIG_FILENAME,\n });\n }\n}\n// =============================================================================\n// VALIDATION UTILITIES\n// =============================================================================\n/**\n * Shared rule for prefixed credentials: `{PREFIX}{HEX_LENGTH hex chars}`.\n * The regex derives from the shape constants, so the validators can never\n * drift from the shapes `classifyToken` dispatches on.\n */\nfunction validatePrefixedCredential(value, shape, label) {\n if (!value.startsWith(shape.PREFIX)) {\n throw ShipError.validation(`${label} must start with \"${shape.PREFIX}\"`);\n }\n if (value.length !== shape.TOTAL_LENGTH) {\n throw ShipError.validation(`${label} must be ${shape.TOTAL_LENGTH} characters total (${shape.PREFIX} + ${shape.HEX_LENGTH} hex chars)`);\n }\n const hexPart = value.slice(shape.PREFIX.length);\n if (!new RegExp(`^[a-f0-9]{${shape.HEX_LENGTH}}$`, 'i').test(hexPart)) {\n throw ShipError.validation(`${label} must contain ${shape.HEX_LENGTH} hexadecimal characters after \"${shape.PREFIX}\" prefix`);\n }\n}\n/**\n * Validate API key format\n */\nexport function validateApiKey(apiKey) {\n validatePrefixedCredential(apiKey, API_KEY, 'API key');\n}\n/**\n * Validate deploy token format\n */\nexport function validateDeployToken(deployToken) {\n validatePrefixedCredential(deployToken, DEPLOY_TOKEN, 'Deploy token');\n}\n/**\n * Validate OAuth access token format\n */\nexport function validateOAuthToken(oauthToken) {\n validatePrefixedCredential(oauthToken, OAUTH_TOKEN, 'OAuth access token');\n}\n/**\n * Validate a client token of any population. Classifies by shape and applies\n * the matching format rules: all three prefixed populations are validated\n * strictly; an OPAQUE token only needs to be non-empty.\n *\n * **The OPAQUE arm stays permissive on purpose**, even though the platform no\n * longer mints an unprefixed credential. It is the fallback for a population\n * that does not exist yet, and a client refusing a shape the server would\n * accept is the one failure mode this boundary must never have — the server\n * decides, and it refuses an unrecognised bearer anyway. Unprefixed OAuth\n * tokens from before 2026-08-14 land here and are refused server-side, which\n * is correct: they were revoked by the change, not grandfathered.\n */\nexport function validateToken(token) {\n switch (classifyToken(token)) {\n case TokenKind.API_KEY:\n validateApiKey(token);\n return;\n case TokenKind.DEPLOY_TOKEN:\n validateDeployToken(token);\n return;\n case TokenKind.OAUTH:\n validateOAuthToken(token);\n return;\n case TokenKind.OPAQUE:\n if (!token)\n throw ShipError.validation('Token must be a non-empty string');\n }\n}\n/**\n * Validate a caller identifier against the `CALLER` shape. The server\n * silently ignores malformed values (the header is unauthenticated); clients\n * call this at configuration time so the drop never silently happens.\n */\nexport function validateCaller(caller) {\n if (!caller || caller.length > CALLER.MAX_LENGTH || !CALLER.PATTERN.test(caller)) {\n throw ShipError.validation(`Caller must be 1-${CALLER.MAX_LENGTH} characters: letters, digits, dots, underscores, or hyphens`);\n }\n}\n/**\n * Validate API URL format\n */\nexport function validateApiUrl(apiUrl) {\n try {\n const url = new URL(apiUrl);\n if (!['http:', 'https:'].includes(url.protocol)) {\n throw ShipError.validation('API URL must use http:// or https:// protocol');\n }\n if (url.pathname !== '/' && url.pathname !== '') {\n throw ShipError.validation('API URL must not contain a path');\n }\n if (url.search || url.hash) {\n throw ShipError.validation('API URL must not contain query parameters or fragments');\n }\n }\n catch (error) {\n if (isShipError(error)) {\n throw error;\n }\n throw ShipError.validation('API URL must be a valid URL');\n }\n}\n/**\n * Check if a string matches the deployment identifier pattern (word-word-alphanumeric7).\n * Example: \"happy-cat-abc1234.shipstatic.com\"\n */\nexport function isDeployment(input) {\n return /^[a-z]+-[a-z]+-[a-z0-9]{7}(\\.[a-z0-9.-]+)?$/i.test(input);\n}\n/**\n * The envelope a requested lifetime must fit — one word, one grammar, wherever\n * the platform lets a caller choose how long something lives.\n *\n * Two resources wear it: `TokenCreateOptions.ttl` and\n * `DeploymentUploadOptions.ttl`. It lives here rather than on the server by\n * the format-vs-policy rule — a client can decide offline whether a duration\n * is well-formed, and the API rejects the same value the same way. What is\n * NOT here is any per-plan ceiling: no such policy exists, and one delivered\n * speculatively through `/limits` would be an owner for a decision nobody has\n * made.\n */\nexport const TTL_CONSTRAINTS = {\n /**\n * Shortest requestable lifetime, in seconds. One rather than zero: a\n * deployment that expires the instant it is created is not a shorter lease,\n * it is a deploy that was never live, and `0` is how an unset variable\n * arrives.\n */\n MIN_SECONDS: 1,\n /** Longest requestable lifetime, in seconds — one year. */\n MAX_SECONDS: 365 * 24 * 60 * 60,\n};\n/**\n * Validate a requested lifetime in SECONDS and return it, or `undefined` when\n * none was asked for.\n *\n * **A duration, never an instant.** The caller says how long; the server owns\n * what time it is and stamps the expiry — so a client's clock, however wrong,\n * cannot shorten or extend a lease. That is the tokens precedent, and it is\n * why this rule measures a count of seconds rather than checking a timestamp\n * against `now`.\n *\n * Fractions are refused rather than rounded: a caller who wrote `1.5` meant\n * something the wire cannot carry, and silently choosing `1` or `2` for them\n * is a decision the platform has no standing to make.\n *\n * Single source of truth shared by the API (the tokens route and the deploy\n * schema), the SDK's request boundary, and the CLI's parser.\n */\nexport function validateTtl(value) {\n if (value === undefined || value === null)\n return undefined;\n if (typeof value !== 'number' || !Number.isFinite(value)) {\n throw ShipError.validation('TTL must be a number of seconds');\n }\n if (!Number.isInteger(value)) {\n throw ShipError.validation('TTL must be a whole number of seconds');\n }\n if (value < TTL_CONSTRAINTS.MIN_SECONDS || value > TTL_CONSTRAINTS.MAX_SECONDS) {\n throw ShipError.validation(`TTL must be between ${TTL_CONSTRAINTS.MIN_SECONDS} and ${TTL_CONSTRAINTS.MAX_SECONDS} seconds`);\n }\n return value;\n}\n// =============================================================================\n// PLATFORM CONSTANTS\n// =============================================================================\n/** Default API URL if not otherwise configured. */\nexport const DEFAULT_API = 'https://api.shipstatic.com';\n/**\n * The Node SDK's ambient configuration pair — the ONLY environment variables\n * the SDK reads, and therefore the COMPLETE list an embedding host must\n * scrub (per `npm/ship`'s strict-isolation contract, scrubbing is the host's\n * job, not the SDK's). A host that derives its scrub from this object's\n * values — as the VS Code extension's child-process env block does — picks\n * up a grown contract at the next pin bump instead of by remembered prose.\n *\n * Browser builds read no environment at all, and the CLI-only variables\n * (`SHIP_PASSWORD`, `SHIP_VIA`) are deliberately NOT here: they are the\n * CLI's operational levers, not the SDK's ambient contract — see\n * `npm/ship/CLAUDE.md`, \"CLI-only env vars\".\n */\nexport const SHIP_ENV = {\n /** The one credential slot — any platform token. */\n TOKEN: 'SHIP_TOKEN',\n /** The API endpoint override. */\n API_URL: 'SHIP_API_URL',\n};\n/**\n * Where a human creates an API key — the console deep link quoted by every\n * surface that teaches authentication (the CLI's config wizard, the VS Code\n * and n8n listings, the n8n rate-limit hint and credential copy). Written\n * out in five files across three repos until this export.\n *\n * Production-branded by design: published artifacts name the product, never\n * an environment (root `CLAUDE.md`, \"Environment-Aware URLs\").\n */\nexport const MY_API_KEY_URL = 'https://my.shipstatic.com/api-key';\n/**\n * How long an anonymous deployment lives before it expires.\n *\n * The lifetime of the public tier, and one fact with several readers. The API\n * stamps a deployment's `expires` from it and gives a claim code exactly the\n * same window — a live site with a dead claim link is a coherence bug, so the\n * two are one constant rather than two that agree. Both MCP transports quote\n * the duration in prose an agent reads, and derive it from here rather than\n * writing it out, which they did in eight places until this export existed.\n *\n * Seconds, spelled in the name: this platform has both second- and\n * millisecond-valued durations, and the pair is only safe when each says which\n * it is.\n */\nexport const PUBLIC_DEPLOYMENT_TTL_SECONDS = 3 * 24 * 60 * 60;\n// =============================================================================\n// FILE UPLOAD TYPES\n// =============================================================================\n/**\n * File status constants for validation state tracking\n */\nexport const FileValidationStatus = {\n /** File is pending validation */\n PENDING: 'pending',\n /** File failed during processing (before validation) */\n PROCESSING_ERROR: 'processing_error',\n /** File was excluded by validation warning (not an error) */\n EXCLUDED: 'excluded',\n /** File failed validation (blocks deployment) */\n VALIDATION_FAILED: 'validation_failed',\n /** File passed validation and is ready for deployment */\n READY: 'ready',\n};\n// =============================================================================\n// DOMAIN UTILITIES\n// =============================================================================\n/**\n * Check if a domain is a platform domain (subdomain of our platform).\n * Platform domains are free and don't require DNS verification.\n *\n * @example isPlatformDomain(\"www.shipstatic.com\", \"shipstatic.com\") → true\n * @example isPlatformDomain(\"example.com\", \"shipstatic.com\") → false\n */\nexport function isPlatformDomain(domain, platformDomain) {\n return domain.endsWith(`.${platformDomain}`);\n}\n/**\n * Check if a domain is a custom domain (not a platform subdomain).\n * Custom domains are billable and require DNS verification.\n *\n * @example isCustomDomain(\"example.com\", \"shipstatic.com\") → true\n * @example isCustomDomain(\"www.shipstatic.com\", \"shipstatic.com\") → false\n */\nexport function isCustomDomain(domain, platformDomain) {\n return !isPlatformDomain(domain, platformDomain);\n}\n/**\n * Extract subdomain from a platform domain.\n * Returns null if not a platform domain.\n *\n * @example extractSubdomain(\"www.shipstatic.com\", \"shipstatic.com\") → \"www\"\n * @example extractSubdomain(\"example.com\", \"shipstatic.com\") → null\n */\nexport function extractSubdomain(domain, platformDomain) {\n if (!isPlatformDomain(domain, platformDomain)) {\n return null;\n }\n return domain.slice(0, -(platformDomain.length + 1)); // +1 for the dot\n}\n/**\n * Generate HTTPS URL for a deployment hostname.\n */\nexport function generateDeploymentUrl(deployment) {\n return `https://${deployment}`;\n}\n/**\n * Generate HTTPS URL for a domain.\n */\nexport function generateDomainUrl(domain) {\n return `https://${domain}`;\n}\n// =============================================================================\n// LABEL UTILITIES\n// =============================================================================\n/**\n * Label validation constraints shared across UI and API.\n * These rules define the single source of truth for label validation.\n */\nexport const LABEL_CONSTRAINTS = {\n /** Minimum label length in characters */\n MIN_LENGTH: 3,\n /** Maximum label length in characters (concise labels, matches Stack Overflow's original limit) */\n MAX_LENGTH: 25,\n /** Maximum number of labels allowed per resource */\n MAX_COUNT: 10,\n /** Allowed separator characters between label segments */\n SEPARATORS: '._-',\n};\n/**\n * Label validation pattern.\n * Must start and end with alphanumeric (a-z, 0-9).\n * Can contain separators (. _ -) between segments, but not consecutive.\n *\n * Valid examples: 'production', 'v1.2.3', 'api_v2', 'us-east-1'\n * Invalid examples: 'ab' (too short), '-prod' (starts with separator), 'foo--bar' (consecutive separators)\n */\nexport const LABEL_PATTERN = /^[a-z0-9]+(?:[._-][a-z0-9]+)*$/;\n/**\n * Serialize labels array to JSON string for database storage.\n * Returns null for empty or undefined arrays.\n *\n * @example serializeLabels(['web', 'production']) → '[\"web\",\"production\"]'\n * @example serializeLabels([]) → null\n * @example serializeLabels(undefined) → null\n */\nexport function serializeLabels(labels) {\n if (!labels || labels.length === 0)\n return null;\n return JSON.stringify(labels);\n}\n/**\n * Deserialize labels from JSON string to array.\n * Always returns an array — empty array for null/empty/invalid input.\n *\n * @example deserializeLabels('[\"web\",\"production\"]') → ['web', 'production']\n * @example deserializeLabels(null) → []\n * @example deserializeLabels('') → []\n */\nexport function deserializeLabels(labelsJson) {\n if (!labelsJson)\n return [];\n try {\n const parsed = JSON.parse(labelsJson);\n return Array.isArray(parsed) ? parsed : [];\n }\n catch {\n return [];\n }\n}\n// =============================================================================\n// PASSWORD UTILITIES\n// =============================================================================\n/**\n * Length constraints for the optional deployment password\n * (`DeploymentUploadOptions.password`). Single source of truth shared across\n * platform consumers.\n */\nexport const PASSWORD_CONSTRAINTS = {\n /** Minimum password length in characters */\n MIN_LENGTH: 6,\n /** Maximum password length in characters */\n MAX_LENGTH: 128,\n};\n/**\n * Validate an optional deployment password and return it normalized.\n *\n * Absent (`undefined` / `null`) → returns `undefined`. Present → trim\n * leading/trailing whitespace, then validate against `PASSWORD_CONSTRAINTS`\n * length bounds (internal whitespace is significant and counts toward\n * length). Throws `ShipError.validation` on breach; returns the trimmed\n * value.\n *\n * The trim is canonical: at upload, the API hashes the trimmed value; at\n * unlock, the router trims submissions before hashing. Submission and storage\n * agree byte-for-byte. Length validation runs on the trimmed value because\n * that's the user's actual intent — and it disarms a class of invisible\n * foot-guns (trailing newlines from copy/paste, mobile auto-spacing,\n * password-manager artifacts).\n *\n * Single source of truth shared by SDK (client-side validation, return\n * ignored) and API (server-side enforcement, return threaded into config).\n * Length is part of the wire-format contract; strength rules, if added later,\n * stay server-side. See `CLAUDE.md` \"Validation: format vs policy\".\n */\nexport function validatePassword(value) {\n if (value === undefined || value === null)\n return undefined;\n if (typeof value !== 'string') {\n throw ShipError.validation('Password must be a string');\n }\n const trimmed = value.trim();\n if (trimmed.length < PASSWORD_CONSTRAINTS.MIN_LENGTH ||\n trimmed.length > PASSWORD_CONSTRAINTS.MAX_LENGTH) {\n throw ShipError.validation(`Password must be between ${PASSWORD_CONSTRAINTS.MIN_LENGTH} and ${PASSWORD_CONSTRAINTS.MAX_LENGTH} characters`);\n }\n return trimmed;\n}\n","/**\n * @file MD5 utility for Blob, Buffer, or file path inputs.\n */\nimport { ShipError } from '@shipstatic/types';\n\nexport interface MD5Result {\n md5: string;\n}\n\nasync function md5Blob(blob: Blob): Promise<MD5Result> {\n const SparkMD5 = (await import('spark-md5')).default;\n const spark = new SparkMD5.ArrayBuffer();\n const chunkSize = 2097152; // 2 MB\n for (let start = 0; start < blob.size; start += chunkSize) {\n const end = Math.min(start + chunkSize, blob.size);\n spark.append(await blob.slice(start, end).arrayBuffer());\n }\n return { md5: spark.end() };\n}\n\nasync function md5Buffer(buffer: Buffer): Promise<MD5Result> {\n // biome-ignore lint/style/useNodejsImportProtocol: the browser build shims bare specifiers only — `node:` bypasses esbuild alias resolution (see tsup.config.ts)\n const { createHash } = await import('crypto');\n const hash = createHash('md5');\n hash.update(buffer);\n return { md5: hash.digest('hex') };\n}\n\nasync function md5Path(path: string): Promise<MD5Result> {\n // biome-ignore lint/style/useNodejsImportProtocol: see md5Buffer — bare specifier is load-bearing for the browser shim\n const { createHash } = await import('crypto');\n // biome-ignore lint/style/useNodejsImportProtocol: see md5Buffer — bare specifier is load-bearing for the browser shim\n const { createReadStream } = await import('fs');\n return new Promise((resolve, reject) => {\n const hash = createHash('md5');\n const stream = createReadStream(path);\n // A local read that failed — no request was made and no server rule was\n // being mirrored, so there is no status to report and this is the `File`\n // type by definition (see CLAUDE.md, \"What a status means\"). The path\n // rides `details`, per the factory's own convention, rather than being\n // spelled into prose twice.\n stream.on('error', (err) =>\n reject(ShipError.file(`Failed to read file for MD5: ${err.message}`, { filePath: path })),\n );\n stream.on('data', (chunk) => hash.update(chunk));\n stream.on('end', () => resolve({ md5: hash.digest('hex') }));\n });\n}\n\nexport async function calculateMD5(input: Blob | Buffer | string): Promise<MD5Result> {\n if (input instanceof Blob) return md5Blob(input);\n if (typeof Buffer !== 'undefined' && Buffer.isBuffer(input)) return md5Buffer(input);\n if (typeof input === 'string') return md5Path(input);\n throw ShipError.business('Invalid input for MD5 calculation');\n}\n","/**\n * @file Environment detection utilities for the Ship SDK.\n * Helps in determining whether the SDK is running in a Node.js, browser, or unknown environment.\n */\n\n/**\n * Represents the detected or simulated JavaScript execution environment.\n */\nexport type ExecutionEnvironment = 'browser' | 'node' | 'unknown';\n\n/** @internal Environment override for testing. */\nlet _testEnvironment: ExecutionEnvironment | null = null;\n\n/**\n * **FOR TESTING PURPOSES ONLY.**\n *\n * Allows tests to override the detected environment, forcing the SDK to behave\n * as if it's running in the specified environment.\n *\n * @param env - The environment to simulate ('node', 'browser', 'unknown'),\n * or `null` to clear the override and revert to actual environment detection.\n * @internal\n */\nexport function __setTestEnvironment(env: ExecutionEnvironment | null): void {\n _testEnvironment = env;\n}\n\n/**\n * Detects the actual JavaScript execution environment (Node.js, browser, or unknown)\n * by checking for characteristic global objects.\n * @returns The detected environment as {@link ExecutionEnvironment}.\n * @internal\n */\nfunction detectEnvironment(): ExecutionEnvironment {\n // Check for Node.js environment\n if (typeof process !== 'undefined' && process.versions && process.versions.node) {\n return 'node';\n }\n\n // Check for Browser environment (including Web Workers)\n if (typeof window !== 'undefined' || typeof self !== 'undefined') {\n return 'browser';\n }\n\n return 'unknown';\n}\n\n/**\n * Gets the current effective execution environment.\n *\n * This function first checks if a test environment override is active via {@link __setTestEnvironment}.\n * If not, it detects the actual environment (Node.js, browser, or unknown).\n *\n * @returns The current execution environment: 'browser', 'node', or 'unknown'.\n * @public\n */\nexport function getENV(): ExecutionEnvironment {\n // Return test override if set\n if (_testEnvironment) {\n return _testEnvironment;\n }\n\n // Detect actual environment\n return detectEnvironment();\n}\n","/**\n * @file Path helper utilities that work in both browser and Node.js environments.\n * Provides environment-agnostic path manipulation functions.\n */\n\n/**\n * Finds the common parent directory from an array of directory paths.\n * The basis of `pathDetect`: the common parent is the prefix stripped from\n * every deploy path so a deployment's root is the site's root.\n *\n * @param dirPaths - Array of directory paths (not file paths - directories containing the files)\n * @returns The common parent directory path, or empty string if none found\n */\nexport function findCommonParent(dirPaths: string[]): string {\n if (!dirPaths || dirPaths.length === 0) return '';\n\n const normalizedPaths = dirPaths\n .filter((p) => p && typeof p === 'string')\n .map((p) => p.replace(/\\\\/g, '/'));\n\n if (normalizedPaths.length === 0) return '';\n if (normalizedPaths.length === 1) return normalizedPaths[0];\n\n const pathSegments = normalizedPaths.map((p) => p.split('/').filter(Boolean));\n const commonSegments = [];\n const minLength = Math.min(...pathSegments.map((p) => p.length));\n\n for (let i = 0; i < minLength; i++) {\n const segment = pathSegments[0][i];\n if (pathSegments.every((segments) => segments[i] === segment)) {\n commonSegments.push(segment);\n } else {\n break;\n }\n }\n\n return commonSegments.join('/');\n}\n\n/**\n * Converts backslashes to forward slashes for cross-platform compatibility.\n * Does not remove leading slashes (preserves absolute paths).\n * @param path - The path to normalize\n * @returns Path with forward slashes\n */\nexport function normalizeSlashes(path: string): string {\n return path.replace(/\\\\/g, '/');\n}\n\n/**\n * Normalizes a path for web usage by converting backslashes to forward slashes\n * and removing leading slashes.\n * @param path - The path to normalize\n * @returns Normalized path suitable for web deployment\n */\nexport function normalizeWebPath(path: string): string {\n return path.replace(/\\\\/g, '/').replace(/\\/+/g, '/').replace(/^\\/+/, '');\n}\n","/**\n * @file Deploy path optimization - the core logic that makes Ship deployments clean and intuitive.\n * Automatically strips common parent directories to create clean deployment URLs.\n */\n\nimport { normalizeWebPath } from './path.js';\n\n/**\n * Represents a file ready for deployment with its optimized path\n */\nexport interface DeployFile {\n /** The clean deployment path (e.g., \"assets/style.css\") */\n path: string;\n /** Original filename */\n name: string;\n}\n\n/**\n * Core path optimization logic.\n * Transforms messy local paths into clean deployment paths.\n *\n * @example\n * Input: [\"dist/index.html\", \"dist/assets/app.js\"]\n * Output: [\"index.html\", \"assets/app.js\"]\n *\n * @param filePaths - Raw file paths from the local filesystem\n * @param options - Path processing options\n */\nexport function optimizeDeployPaths(\n filePaths: string[],\n options: { flatten?: boolean } = {},\n): DeployFile[] {\n // When flattening is disabled, keep original structure\n if (options.flatten === false) {\n return filePaths.map((path) => ({\n path: normalizeWebPath(path),\n name: extractFileName(path),\n }));\n }\n\n // Find the common directory prefix to strip\n const commonPrefix = findCommonDirectory(filePaths);\n\n return filePaths.map((filePath) => {\n let deployPath = normalizeWebPath(filePath);\n\n // Strip the common prefix to create clean deployment paths\n if (commonPrefix) {\n const prefixToRemove = commonPrefix.endsWith('/') ? commonPrefix : `${commonPrefix}/`;\n if (deployPath.startsWith(prefixToRemove)) {\n deployPath = deployPath.substring(prefixToRemove.length);\n }\n }\n\n // Fallback to filename if path becomes empty\n if (!deployPath) {\n deployPath = extractFileName(filePath);\n }\n\n return {\n path: deployPath,\n name: extractFileName(filePath),\n };\n });\n}\n\n/**\n * Finds the common directory shared by all file paths.\n * This is what gets stripped to create clean deployment URLs.\n *\n * @example\n * [\"dist/index.html\", \"dist/assets/app.js\"] → \"dist\"\n * [\"src/components/A.tsx\", \"src/utils/B.ts\"] → \"src\"\n * [\"file1.txt\", \"file2.txt\", \"subdir/file3.txt\"] → \"\" (no common directory)\n */\nfunction findCommonDirectory(filePaths: string[]): string {\n if (!filePaths.length) return '';\n\n // Normalize all paths first\n const normalizedPaths = filePaths.map((path) => normalizeWebPath(path));\n\n // Find the common prefix among all file paths (not just directories)\n const pathSegments = normalizedPaths.map((path) => path.split('/'));\n const commonSegments: string[] = [];\n const minLength = Math.min(...pathSegments.map((segments) => segments.length));\n\n // Check each segment level to find the longest common prefix\n for (let i = 0; i < minLength - 1; i++) {\n // -1 because we don't want to include the filename\n const segment = pathSegments[0][i];\n if (pathSegments.every((segments) => segments[i] === segment)) {\n commonSegments.push(segment);\n } else {\n break;\n }\n }\n\n return commonSegments.join('/');\n}\n\n/**\n * Extracts just the filename from a file path\n */\nfunction extractFileName(path: string): string {\n return path.split(/[/\\\\]/).pop() || path;\n}\n","/**\n * @file One ordered table of deploy-file rules, and the single evaluation two\n * renderers share.\n *\n * **The defect this closes:** one rule was rendering as three sentences. A\n * file over the size cap said `File x is too large. Maximum allowed size is\n * 20MB.` from the deploy pipelines, `File size (21 MB) exceeds limit of 20 MB`\n * from `validateFiles`, and `File too large. Maximum 20971520 bytes allowed`\n * from the API — against the dual-validation doctrine that an error reads the\n * same wherever it was caught (root `CLAUDE.md`). Both pipelines also restated\n * the whole ordered check, so node/browser parity was a comment.\n *\n * **A rule states a predicate and a sentence; a renderer chooses only how to\n * DELIVER it.** That is the `SHAPES`-table move (`cli/formatters.ts`) applied\n * to validation: the throwing renderer raises the first broken rule, the\n * collecting renderer records it, and neither authors prose. Adding a rule is\n * a row, and both surfaces get it in the same position by construction.\n *\n * **Wording follows the API where a choice existed**, so the deferred Phase B\n * — promoting this table to `@shipstatic/types` with the API consuming it —\n * has less to move. Two deliberate deviations, recorded rather than silent:\n *\n * - **Sizes are formatted, not raw bytes.** The API says `20971520 bytes`;\n * a browser upload UI showing that is worse for the person reading it, and\n * the unit is the smaller half of the sentence to reconcile later.\n * - **The path is named.** The API has no path to name; the throwing renderer\n * has nothing BUT the message, so dropping it would leave a CLI user asking\n * which file.\n *\n * Out of scope, and left where they are: `validateDeployPath` (a rule about\n * the deploy PATH rather than the file, and pipelines-only), and\n * `validateFiles`' UI-tier pre-checks — empty, negative, count, unbuilt\n * marker, processing error — which have one holder each and no drift.\n */\n\nimport type { PlatformLimits } from '@shipstatic/types';\nimport { isBlockedExtension } from '@shipstatic/types';\nimport { formatFileSize, validateFileName } from './file-validation.js';\n\n/** What a rule is asked about: one file, and the deploy so far. */\nexport interface FileRuleInput {\n /** The path this file will be served at. */\n readonly path: string;\n /** This file's size in bytes. */\n readonly size: number;\n /** Bytes accumulated INCLUDING this file — the total rule's subject. */\n readonly totalSize: number;\n}\n\n/** A rule: what makes it broken, and the one sentence that says so. */\nexport interface FileRule {\n /** Stable identity, for the fence and for reading a failure in a test. */\n readonly name: string;\n readonly broken: (input: FileRuleInput, limits: PlatformLimits) => boolean;\n readonly sentence: (input: FileRuleInput, limits: PlatformLimits) => string;\n}\n\n/**\n * EVERY rule both client surfaces apply, in the order they apply them.\n *\n * Order is load-bearing and is the reason this is a list rather than a set: a\n * file that is both misnamed and oversized reports the name, because a caller\n * fixing the name may not have an oversize problem at all.\n */\nexport const FILE_RULES: readonly FileRule[] = [\n {\n // The reason comes from `validateFileName`, which already owns this\n // vocabulary for both surfaces — the rule points at it rather than\n // restating it.\n name: 'name',\n broken: ({ path }) => !validateFileName(path).valid,\n sentence: ({ path }) => validateFileName(path).reason ?? 'Invalid file name',\n },\n {\n // The blocklist is the platform's, delivered through `/limits`. Absent\n // means NO client-side check, never an empty policy: the boundary refuses\n // the file, which is where refusal belongs.\n name: 'extension',\n broken: ({ path }, limits) => isBlockedExtension(path, limits.blockedExtensions ?? []),\n sentence: ({ path }) => `File extension not allowed: \"${path}\"`,\n },\n {\n name: 'fileSize',\n broken: ({ size }, limits) => size > limits.maxFileSize,\n sentence: ({ path }, limits) =>\n `File \"${path}\" too large. Maximum ${formatFileSize(limits.maxFileSize)} allowed`,\n },\n {\n name: 'totalSize',\n broken: ({ totalSize }, limits) => totalSize > limits.maxTotalSize,\n sentence: ({ totalSize }, limits) =>\n `Total upload size too large. ${formatFileSize(totalSize)} exceeds maximum of ${formatFileSize(limits.maxTotalSize)}`,\n },\n];\n\n/**\n * The first rule this file breaks, or `undefined`.\n *\n * The single evaluation both renderers call — which is what makes node/browser\n * parity structural instead of a promise. Neither renderer may re-order, skip,\n * or reword a rule, because neither one knows what the rules are.\n */\nexport function firstBrokenRule(\n input: FileRuleInput,\n limits: PlatformLimits,\n): FileRule | undefined {\n return FILE_RULES.find((rule) => rule.broken(input, limits));\n}\n","/**\n * @file File validation utilities for Ship SDK\n * Provides client-side validation for file uploads before deployment\n */\n\nimport type {\n FileValidationResult,\n FileValidationStatusType,\n PlatformLimits,\n ValidatableFile,\n ValidationIssue,\n} from '@shipstatic/types';\nimport {\n FileValidationStatus as FILE_VALIDATION_STATUS,\n hasUnbuiltMarker,\n hasUnsafeChars,\n} from '@shipstatic/types';\nimport { firstBrokenRule } from './file-rules.js';\n\n// Re-exported because it constrains three exported generics (validateFiles,\n// getValidFiles, allValidFilesReady) — a consumer cannot name the bound\n// otherwise, which is why the tests were inventing their own copy.\nexport type { ValidatableFile };\nexport { FILE_VALIDATION_STATUS };\n\n/**\n * Format file size to human-readable string\n */\nexport function formatFileSize(bytes: number, decimals: number = 1): string {\n if (bytes === 0) return '0 Bytes';\n const k = 1024;\n const sizes = ['Bytes', 'KB', 'MB', 'GB'];\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n return `${parseFloat((bytes / k ** i).toFixed(decimals))} ${sizes[i]}`;\n}\n\n/**\n * Validate filename for deployment safety\n *\n * Blocks only characters that genuinely break the upload→serve round-trip:\n * - # ? % URL round-trip breakers (fragment, query, encoding ambiguity)\n * - \\ Path separator confusion (buildFileKey splits on backslash)\n * - < > \" XSS vectors with zero legitimate use in filenames\n * - \\x00-\\x1f \\x7f Control characters (header injection, display corruption)\n *\n * Everything else is allowed — browser percent-encodes, Worker decodes, R2 matches.\n *\n * Additional checks: path traversal, reserved names, leading/trailing dots or spaces.\n */\nexport function validateFileName(filename: string): { valid: boolean; reason?: string } {\n if (hasUnsafeChars(filename)) {\n return { valid: false, reason: 'File name contains unsafe characters' };\n }\n\n if (filename.startsWith(' ') || filename.endsWith(' ')) {\n return { valid: false, reason: 'File name cannot start/end with spaces' };\n }\n\n if (filename.endsWith('.')) {\n return { valid: false, reason: 'File name cannot end with dots' };\n }\n\n const reservedNames = /^(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])(\\.|$)/i;\n const nameWithoutPath = filename.split('/').pop() || filename;\n if (reservedNames.test(nameWithoutPath)) {\n return { valid: false, reason: 'File name uses a reserved system name' };\n }\n\n if (filename.includes('..')) {\n return { valid: false, reason: 'File name contains path traversal pattern' };\n }\n\n return { valid: true };\n}\n\n/**\n * Validate files against configuration limits with severity-based reporting\n *\n * Validation categorizes issues by severity:\n * - **Errors**: Block deployment (file too large, blocked extension, etc.)\n * - **Warnings**: Exclude files but allow deployment (empty files, etc.)\n *\n * @param files - Array of files to validate\n * @param config - Validation configuration from ship.getLimits()\n * @returns Validation result with errors and warnings\n *\n * @example\n * ```typescript\n * const config = await ship.getLimits();\n * const result = validateFiles(files, config);\n *\n * if (!result.canDeploy) {\n * // Has errors - deployment blocked\n * console.error('Deployment blocked:', result.errors);\n * } else if (result.warnings.length > 0) {\n * // Has warnings - deployment proceeds, some files excluded\n * console.warn('Files excluded:', result.warnings);\n * await ship.deploy(result.validFiles);\n * } else {\n * // All files valid\n * await ship.deploy(result.validFiles);\n * }\n * ```\n */\nexport function validateFiles<T extends ValidatableFile>(\n files: T[],\n config: PlatformLimits,\n): FileValidationResult<T> {\n const errors: ValidationIssue[] = [];\n const warnings: ValidationIssue[] = [];\n let fileStatuses: T[] = []; // Use 'let' for atomic enforcement later\n\n // Check at least 1 file required\n if (files.length === 0) {\n const issue: ValidationIssue = {\n file: '(no files)',\n message: 'At least one file must be provided',\n };\n errors.push(issue);\n\n return {\n files: [],\n validFiles: [],\n errors,\n warnings: [],\n canDeploy: false,\n };\n }\n\n // Check for unbuilt project markers (node_modules/, etc.)\n for (const file of files) {\n if (hasUnbuiltMarker(file.name)) {\n errors.push({\n file: file.name,\n message: `Unbuilt project detected — deploy your build output (dist/, build/, out/), not the project folder`,\n });\n return {\n files: files.map((f) => ({\n ...f,\n status: FILE_VALIDATION_STATUS.VALIDATION_FAILED,\n statusMessage: 'Unbuilt project detected',\n })),\n validFiles: [],\n errors,\n warnings: [],\n canDeploy: false,\n };\n }\n }\n\n // Check file count limit\n if (files.length > config.maxFilesCount) {\n const issue: ValidationIssue = {\n file: `(${files.length} files)`,\n message: `File count (${files.length}) exceeds limit of ${config.maxFilesCount}`,\n };\n errors.push(issue);\n\n return {\n files: files.map((f) => ({\n ...f,\n status: FILE_VALIDATION_STATUS.VALIDATION_FAILED,\n statusMessage: issue.message,\n })),\n validFiles: [],\n errors,\n warnings: [],\n canDeploy: false,\n };\n }\n\n // Validate each file\n let totalSize = 0;\n\n for (const file of files) {\n let fileStatus: FileValidationStatusType = FILE_VALIDATION_STATUS.READY;\n let statusMessage = 'Ready for upload';\n\n // Check for processing errors\n if (file.status === FILE_VALIDATION_STATUS.PROCESSING_ERROR) {\n fileStatus = FILE_VALIDATION_STATUS.VALIDATION_FAILED;\n statusMessage = file.statusMessage || 'File failed during processing';\n errors.push({\n file: file.name,\n message: statusMessage,\n });\n }\n\n // EMPTY FILE - Warning (not error)\n else if (file.size === 0) {\n fileStatus = FILE_VALIDATION_STATUS.EXCLUDED;\n statusMessage = 'File is empty (0 bytes) and cannot be deployed due to storage limitations';\n warnings.push({\n file: file.name,\n message: statusMessage,\n });\n // Skip other validations for excluded files\n fileStatuses.push({\n ...file,\n status: fileStatus,\n statusMessage,\n });\n continue;\n }\n\n // Negative file size - Error\n else if (file.size < 0) {\n fileStatus = FILE_VALIDATION_STATUS.VALIDATION_FAILED;\n statusMessage = 'File size must be positive';\n errors.push({\n file: file.name,\n message: statusMessage,\n });\n }\n\n // File name validation\n else if (!file.name || file.name.trim().length === 0) {\n fileStatus = FILE_VALIDATION_STATUS.VALIDATION_FAILED;\n statusMessage = 'File name cannot be empty';\n errors.push({\n file: file.name || '(empty)',\n message: statusMessage,\n });\n } else if (file.name.includes('\\0')) {\n fileStatus = FILE_VALIDATION_STATUS.VALIDATION_FAILED;\n statusMessage = 'File name contains invalid characters (null byte)';\n errors.push({\n file: file.name,\n message: statusMessage,\n });\n }\n\n // THE SHARED RULES — name, extension, file size, total size — read from\n // the one ordered table in `file-rules.ts`.\n //\n // This is the COLLECTING renderer: the same verdict the deploy pipelines'\n // throwing renderer reaches, delivered as a list rather than an exception.\n // Neither surface authors a sentence, which is what stopped one size rule\n // reading three different ways.\n else {\n const input = { path: file.name, size: file.size, totalSize: totalSize + file.size };\n const broken = firstBrokenRule(input, config);\n\n if (broken) {\n fileStatus = FILE_VALIDATION_STATUS.VALIDATION_FAILED;\n statusMessage = broken.sentence(input, config);\n errors.push({\n // The total-size rule fails the DEPLOY, not this file, so it keeps\n // the aggregate subject rather than blaming whichever file tipped it.\n file: broken.name === 'totalSize' ? `(${files.length} files)` : file.name,\n message: statusMessage,\n });\n } else {\n // Only a file that passed everything counts toward the running total.\n totalSize = input.totalSize;\n }\n }\n\n fileStatuses.push({\n ...file,\n status: fileStatus,\n statusMessage,\n });\n }\n\n // ATOMIC ENFORCEMENT: Two-phase validation for optimal UX + atomic semantics\n // Phase 1 (above): Validate files individually to collect ALL errors\n // Phase 2 (below): Mark all files as failed if any errors exist\n //\n // Why two phases? We validate individually for better UX (users see all problems\n // at once and can fix everything in one pass), then enforce atomicity to maintain\n // deployment transaction semantics (all-or-nothing).\n if (errors.length > 0) {\n fileStatuses = fileStatuses.map((file) => {\n // Keep EXCLUDED files as-is (they're warnings, not errors)\n if (file.status === FILE_VALIDATION_STATUS.EXCLUDED) {\n return file;\n }\n\n // Mark ALL other files as VALIDATION_FAILED (atomic deployment)\n return {\n ...file,\n status: FILE_VALIDATION_STATUS.VALIDATION_FAILED,\n statusMessage:\n file.status === FILE_VALIDATION_STATUS.VALIDATION_FAILED\n ? file.statusMessage // Keep original error message for the file that actually failed\n : 'Deployment failed due to validation errors in bundle',\n };\n });\n }\n\n // Build atomic result\n // validFiles is empty if ANY errors exist (all-or-nothing)\n const validFiles =\n errors.length === 0\n ? fileStatuses.filter((f) => f.status === FILE_VALIDATION_STATUS.READY)\n : [];\n const canDeploy = errors.length === 0;\n\n return {\n files: fileStatuses,\n validFiles,\n errors,\n warnings,\n canDeploy,\n };\n}\n\n/**\n * Get only the valid files from validation results\n */\nexport function getValidFiles<T extends ValidatableFile>(files: T[]): T[] {\n return files.filter((f) => f.status === FILE_VALIDATION_STATUS.READY);\n}\n\n/**\n * Check if all valid files have required properties for upload\n * (Can be extended to check for MD5, etc.)\n */\nexport function allValidFilesReady<T extends ValidatableFile>(files: T[]): boolean {\n const validFiles = getValidFiles(files);\n return validFiles.length > 0;\n}\n","/**\n * @file Utility for filtering out junk files and directories from file paths\n *\n * This module provides functionality to filter out common system junk files and directories\n * from a list of file paths. It uses the 'junk' package to identify junk filenames and\n * a custom list to filter out common junk directories.\n */\n\nimport { hasUnbuiltMarker, ShipError } from '@shipstatic/types';\nimport { isJunk } from 'junk';\n\n/**\n * List of directory names considered as junk\n *\n * Files within these directories (at any level in the path hierarchy) will be excluded.\n * The comparison is case-insensitive for cross-platform compatibility.\n *\n * @internal\n */\nexport const JUNK_DIRECTORIES = ['__MACOSX', '.Trashes', '.fseventsd', '.Spotlight-V100'] as const;\n\n/**\n * Filters an array of file paths, removing those considered junk\n *\n * Throws if any path contains an unbuilt project marker (e.g. `node_modules`, `package.json`).\n * This check runs first because the dot-file filter below would strip paths like\n * `node_modules/.pnpm/...`, destroying the evidence.\n *\n * A path is filtered out if any of these conditions are met:\n * 1. The basename is identified as junk by the 'junk' package (e.g., .DS_Store, Thumbs.db)\n * 2. Any path segment starts with a dot (e.g., .env, .git, .htaccess)\n * Exception: `.well-known` is allowed (RFC 8615 — ACME, security.txt, app links)\n * 3. Any path segment exceeds 255 characters (filesystem limit)\n * 4. Any directory segment in the path matches an entry in JUNK_DIRECTORIES (case-insensitive)\n *\n * All path separators are normalized to forward slashes for consistent cross-platform behavior.\n *\n * Dot files are filtered for security — they typically contain sensitive configuration\n * (.env, .git) or are not meant to be served publicly. This matches server-side filtering.\n *\n * @param filePaths - An array of file path strings to filter\n * @param options - Optional settings\n * @param options.allowUnbuilt - When true, skip the unbuilt project marker check (for server-processed uploads)\n * @returns A new array containing only non-junk file paths\n * @throws {ShipError} If any path contains an unbuilt project marker (unless allowUnbuilt is true)\n *\n * @example\n * ```typescript\n * import { filterJunk } from '@shipstatic/ship';\n *\n * // Filter an array of file paths\n * const paths = ['index.html', '.DS_Store', '.gitattributes', '__MACOSX/file.txt', 'app.js'];\n * const clean = filterJunk(paths);\n * // Result: ['index.html', 'app.js']\n * ```\n *\n * @example\n * ```typescript\n * // Use with browser File objects\n * import { filterJunk } from '@shipstatic/ship';\n *\n * const files: File[] = [...]; // From input or drag-drop\n *\n * // Extract paths from File objects\n * const filePaths = files.map(f => f.webkitRelativePath || f.name);\n *\n * // Filter out junk paths\n * const validPaths = new Set(filterJunk(filePaths));\n *\n * // Filter the original File array\n * const validFiles = files.filter(f =>\n * validPaths.has(f.webkitRelativePath || f.name)\n * );\n * ```\n */\nexport function filterJunk(filePaths: string[], options?: { allowUnbuilt?: boolean }): string[] {\n if (!filePaths || filePaths.length === 0) {\n return [];\n }\n\n // Reject unbuilt projects before the dot-file filter removes evidence.\n // pnpm stores files under node_modules/.pnpm/ — the dot-file filter below\n // strips .pnpm/ paths, destroying the only signal that this is an unbuilt project.\n if (!options?.allowUnbuilt) {\n const marker = filePaths.find((p) => p && hasUnbuiltMarker(p));\n if (marker) {\n throw ShipError.business(\n 'Unbuilt project detected — deploy your build output (dist/, build/, out/), not the project folder',\n );\n }\n }\n\n return filePaths.filter((filePath) => {\n if (!filePath) {\n return false; // Exclude null or undefined paths\n }\n\n // Normalize path separators to forward slashes and split into segments\n const parts = filePath.replace(/\\\\/g, '/').split('/').filter(Boolean);\n if (parts.length === 0) return true;\n\n // Check if the basename is a junk file (using junk package)\n const basename = parts[parts.length - 1];\n if (isJunk(basename)) {\n return false;\n }\n\n // Filter out dot files and directories (security: prevents .env, .git, etc.)\n // .well-known is not junk — it's a standard directory (RFC 8615)\n // Path position constraints enforced at upload (buildFileKey) and serving (isBlockedDotFile)\n for (const part of parts) {\n if (part === '.well-known') continue;\n if (part.startsWith('.') || part.length > 255) {\n return false;\n }\n }\n\n // Check if any directory segment is in our junk directories list\n const directorySegments = parts.slice(0, -1);\n for (const segment of directorySegments) {\n if (JUNK_DIRECTORIES.some((junkDir) => segment.toLowerCase() === junkDir.toLowerCase())) {\n return false;\n }\n }\n\n return true;\n });\n}\n","/**\n * @file Shared security validation for the deploy pipeline.\n * Used by both Node.js and browser file processing pipelines.\n */\nimport type { PlatformLimits } from '@shipstatic/types';\nimport { ShipError } from '@shipstatic/types';\nimport { type FileRuleInput, firstBrokenRule } from './file-rules.js';\n\n/**\n * Validate a deploy path for security concerns.\n * Rejects paths containing path traversal patterns or null bytes.\n *\n * Checks for:\n * - Null bytes (\\0) — path injection\n * - /../ — directory traversal within path\n * - ../ at start — upward traversal\n * - /.. at end — trailing traversal\n *\n * Does NOT reject double dots in filenames (e.g., \"foo..bar.txt\" is safe).\n *\n * @param deployPath - The deployment path to validate\n * @param sourceIdentifier - Human-readable identifier for error messages\n * @throws {ShipError} If the path contains unsafe patterns\n */\nexport function validateDeployPath(deployPath: string, sourceIdentifier: string): void {\n if (\n deployPath.includes('\\0') ||\n deployPath.includes('/../') ||\n deployPath.startsWith('../') ||\n deployPath.endsWith('/..')\n ) {\n throw ShipError.business(\n `Security error: Unsafe file path \"${deployPath}\" for file: ${sourceIdentifier}`,\n );\n }\n}\n\n/**\n * The THROWING renderer of `FILE_RULES` — the deploy pipelines' shape.\n *\n * It raises the first rule the file breaks and nothing else: the rules, their\n * order and their sentences all live in `file-rules.ts`, so this function\n * cannot re-order, skip or reword one. That is what makes node/browser parity\n * structural — both pipelines call this, and this calls the one table.\n *\n * Its counterpart is the collecting renderer in `file-validation.ts`\n * (`validateFiles`), which reaches the same verdict and reports it as a list\n * instead of a throw.\n *\n * @param input - The file and the deploy so far (`totalSize` INCLUDES it)\n * @param limits - The platform's limits, from `/limits`\n * @throws {ShipError} The first broken rule's sentence\n */\nexport function validateDeployFile(input: FileRuleInput, limits: PlatformLimits): void {\n const broken = firstBrokenRule(input, limits);\n if (broken) {\n throw ShipError.business(broken.sentence(input, limits));\n }\n}\n","/**\n * @file The deploy file pipeline — one processor, two collectors.\n *\n * A deploy turns *what a platform found* into *what the API receives*, and\n * only the first half of that sentence is platform-shaped. Node walks a\n * filesystem; a browser is handed `File` objects. Everything after — optimize\n * the paths, drop the junk, refuse what the platform's rules refuse, checksum\n * what survives — is one sequence, stated here once.\n *\n * The seam is {@link DeploySource}: a path, where it came from, its size, and\n * a way to read it. That shape is what lets Node answer \"how big is this?\"\n * from the stat its directory walk already performed, which is how the empty-\n * file skip stays free — and it is why `read()` is a FUNCTION rather than a\n * field. The processor calls it for files that survive filtering and\n * validation and for no others, so a deploy refused at file three never opens\n * files four through nine hundred.\n */\n\nimport type { PlatformLimits, StaticFile } from '@shipstatic/types';\nimport { ShipError } from '@shipstatic/types';\nimport { optimizeDeployPaths } from '../lib/deploy-paths.js';\nimport { filterJunk } from '../lib/junk.js';\nimport { calculateMD5 } from '../lib/md5.js';\nimport { validateDeployFile, validateDeployPath } from '../lib/security.js';\nimport type { DeploymentOptions } from '../types.js';\n\n/**\n * One file a platform found, before this pipeline has decided anything about\n * it. The only thing a collector owes beyond the bytes is where they came\n * from and how many there are.\n */\nexport interface DeploySource {\n /** The path this file wants, as its platform names it — pre-optimization. */\n path: string;\n /**\n * Where the file came from, named in the security refusal: an absolute\n * filesystem path in Node, a `File.name` in the browser. It is what makes\n * an unsafe deploy path traceable back to the thing that produced it.\n */\n origin: string;\n /** Byte length, known WITHOUT reading — a stat, or `File.size`. */\n size: number;\n /** Reads the content. Called only for files that survive to the checksum. */\n read: () => Promise<StaticFile['content']>;\n}\n\n/**\n * Turn collected sources into the `StaticFile[]` a deploy body is built from.\n *\n * @param sources - What the platform found, in the platform's own order\n * @param options - Deploy options; `pathDetect` and the server-processed flags\n * are the two this pipeline reads\n * @param platformLimits - The caps and the delivered blocklist from\n * `GET /limits`. Per-instance rather than a module global, so two Ships\n * against different API URLs cannot clobber each other's rules.\n */\nexport async function processDeployFiles(\n sources: DeploySource[],\n options: DeploymentOptions = {},\n platformLimits?: PlatformLimits,\n): Promise<StaticFile[]> {\n // `build` / `prerender` upload SOURCE files for the build service to\n // compile. The deploy rules describe its OUTPUT, so they are not this\n // pipeline's to apply — and the unbuilt-project refusal would reject\n // precisely the input the flags exist to accept. Both flags are `@internal`\n // and set only by `web/my` and `web/www`, which are browser apps; there is\n // no Node caller. That asymmetry lives in the callers, which is why it needs\n // no second copy of the loop here.\n const serverProcessed = !!(options.build || options.prerender);\n\n const deployPaths = optimizeDeployPaths(\n sources.map((source) => source.path),\n { flatten: options.pathDetect !== false },\n ).map((file) => file.path);\n\n const surviving = new Set(filterJunk(deployPaths, { allowUnbuilt: serverProcessed }));\n const kept = sources\n .map((source, index) => ({ source, deployPath: deployPaths[index] }))\n .filter(({ deployPath }) => surviving.has(deployPath));\n\n // Nothing to deploy is not a failure, and it is answered before the rules\n // are demanded: a directory of pure junk resolves empty rather than\n // complaining about limits it was never going to consult.\n if (kept.length === 0) return [];\n\n // The rules, or none. `null` is server-processed mode — the one state where\n // this pipeline judges nothing — so every check below reads as what it is:\n // we validate when we have something to validate against.\n const rules = serverProcessed ? null : requireLimits(platformLimits);\n\n const files: StaticFile[] = [];\n let totalSize = 0;\n\n for (const { source, deployPath } of kept) {\n // Fail fast, before any I/O: an unsafe path is refused on the strength of\n // the path alone.\n if (rules) validateDeployPath(deployPath, source.origin);\n\n // R2 cannot store zero-byte objects. The size is already in hand, so this\n // costs nothing and reads nothing.\n if (source.size === 0) continue;\n\n if (rules) {\n // Name, extension and both size caps — ONE ordered table\n // (`shared/lib/file-rules.ts`), shared with the collecting renderer the\n // UI tier uses. `totalSize` INCLUDES this file, which the total-size\n // rule relies on.\n totalSize += source.size;\n validateDeployFile({ path: deployPath, size: source.size, totalSize }, rules);\n }\n\n const content = await source.read();\n const { md5 } = await calculateMD5(content);\n files.push({ path: deployPath, content, size: source.size, md5 });\n }\n\n // Counted over RESULTS, not over candidates: empty files were skipped above\n // and a deploy is not over the cap for files it will not send.\n if (rules && files.length > rules.maxFilesCount) {\n throw ShipError.business(\n `Too many files to deploy. Maximum allowed is ${rules.maxFilesCount} files.`,\n );\n }\n\n return files;\n}\n\n/**\n * Deploy-mode validation is only as real as the rules behind it, so their\n * absence is a configuration error rather than a silently permissive pass.\n * Unreachable through `ship.deploy()` — the deploy pipeline awaits the\n * `/limits` fetch before it collects a file — and reachable by a consumer\n * calling the exported platform processors directly, which is who the\n * sentence is written for.\n */\nfunction requireLimits(platformLimits?: PlatformLimits): PlatformLimits {\n if (!platformLimits) {\n throw ShipError.config(\n 'Platform limits not provided. Deploy-mode validation requires the limits ' +\n 'argument — pass `ship.getLimits()` result.',\n );\n }\n return platformLimits;\n}\n","/**\n * @file The Node half of the deploy pipeline: finding files on a filesystem.\n *\n * Everything after the finding is shared (`shared/core/deploy-files.ts`) —\n * path optimization, junk filtering, the platform's rules, the checksums.\n * What is genuinely Node here is a directory walk with symlink-cycle\n * protection, a content path computed against the upload root, and the fact\n * that a Node user can point at a project folder and mean `dist/`.\n */\n\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport type { PlatformLimits } from '@shipstatic/types';\nimport { isShipError, ShipError, UNBUILT_PROJECT_MARKERS } from '@shipstatic/types';\nimport { type DeploySource, processDeployFiles } from '../../shared/core/deploy-files.js';\nimport { getENV } from '../../shared/lib/env.js';\nimport { findCommonParent } from '../../shared/lib/path.js';\nimport type { DeploymentOptions, StaticFile } from '../../shared/types.js';\n\n/** A file the walk found, carrying the size the walk already had to ask for. */\ninterface FoundFile {\n absPath: string;\n size: number;\n}\n\n/**\n * Walk a directory and return every file under it.\n *\n * Sizes come from the `statSync` this walk performs anyway to tell a\n * directory from a file, which is what lets the shared processor skip empty\n * files without opening one. The pipeline used to stat every file a SECOND\n * time to learn the same number.\n *\n * @param dirPath - Directory to traverse\n * @param visited - Real paths already walked, so a symlink cycle terminates\n */\nfunction findAllFiles(dirPath: string, visited: Set<string> = new Set()): FoundFile[] {\n const results: FoundFile[] = [];\n\n // Resolve the real path to detect symlink cycles.\n const realPath = fs.realpathSync(dirPath);\n if (visited.has(realPath)) return results;\n visited.add(realPath);\n\n for (const entry of fs.readdirSync(dirPath)) {\n const fullPath = path.join(dirPath, entry);\n const stats = fs.statSync(fullPath);\n\n if (stats.isDirectory()) results.push(...findAllFiles(fullPath, visited));\n else if (stats.isFile()) results.push({ absPath: fullPath, size: stats.size });\n }\n\n return results;\n}\n\n/**\n * Read a file's bytes, naming it if the filesystem refuses.\n *\n * The wrap spans the filesystem call and nothing else. It used to span the\n * whole per-file body, which meant it caught the typed refusals too and had to\n * re-raise them — an `isShipError(error)` line whose only job was to undo the\n * catch's own overreach. A local read that failed is `ShipError.file` by\n * definition: no request was made and no server rule was being mirrored, so\n * there is no status to report (see CLAUDE.md, \"What a status means\").\n */\nasync function readContent(filePath: string): Promise<Buffer> {\n try {\n return fs.readFileSync(filePath);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n throw ShipError.file(`Failed to read file \"${filePath}\": ${message}`, { filePath });\n }\n}\n\n/**\n * Refuse a project folder before walking it.\n *\n * Node-only, and the reason is the input: a Node user types a path and can\n * plausibly type the repository root, where the walk would then enumerate\n * `node_modules`. A browser user picks files. The junk filter refuses the same\n * mistake one step later for the paths it can see; this catches it at the\n * cheapest possible moment, which for a large monorepo is the difference\n * between an immediate answer and a long one.\n */\nfunction refuseUnbuiltProjects(paths: string[]): void {\n for (const p of paths) {\n const absPath = path.resolve(p);\n try {\n if (fs.statSync(absPath).isDirectory()) {\n const marker = fs.readdirSync(absPath).find((e) => UNBUILT_PROJECT_MARKERS.has(e));\n if (marker) {\n throw ShipError.business(\n `\"${marker}\" detected — deploy your build output (dist/, build/, out/), not the project folder`,\n );\n }\n }\n } catch (e) {\n if (isShipError(e)) throw e;\n // Path errors are reported by the discovery walk below, which names the\n // input the user actually typed.\n }\n }\n}\n\n/**\n * Processes Node.js file and directory paths into an array of StaticFile objects ready for deploy.\n * Computes content paths relative to the upload root before filtering, so only the deployed\n * directory structure is evaluated — not the user's filesystem above it.\n *\n * @param paths - File or directory paths to scan and process.\n * @param options - Processing options (pathDetect, etc.).\n * @param platformLimits - Per-instance platform limits (file-size / count /\n * total-size caps) from the originating Ship's `GET /limits` fetch. Passed\n * in rather than read from a module global so concurrent Ships against\n * different API URLs cannot clobber each other's caps.\n * @returns Promise resolving to an array of StaticFile objects.\n * @throws {ShipError} If called outside Node.js or if fs/path modules fail.\n */\nexport async function processFilesForNode(\n paths: string[],\n options: DeploymentOptions = {},\n platformLimits?: PlatformLimits,\n): Promise<StaticFile[]> {\n if (getENV() !== 'node') {\n throw ShipError.business('processFilesForNode can only be called in Node.js environment.');\n }\n\n refuseUnbuiltProjects(paths);\n\n // 1. Discover every file under the inputs, deduplicated by absolute path\n // (two inputs may overlap, and two symlinks may reach one target).\n const found = paths.flatMap((p) => {\n const absPath = path.resolve(p);\n try {\n const stats = fs.statSync(absPath);\n return stats.isDirectory() ? findAllFiles(absPath) : [{ absPath, size: stats.size }];\n } catch (_error) {\n throw ShipError.file(`Path does not exist: ${p}`, { filePath: p });\n }\n });\n const unique = new Map(found.map((file) => [file.absPath, file.size]));\n\n // 2. The upload root comes from the INPUT paths, not the discovered files:\n // `ship ./dist` deploys the contents of `dist`, whatever it happens to\n // contain, so a tree with one deep branch does not strip that branch.\n const inputBasePath = findCommonParent(\n paths\n .map((p) => path.resolve(p))\n .map((p) => {\n try {\n return fs.statSync(p).isDirectory() ? p : path.dirname(p);\n } catch {\n return path.dirname(p);\n }\n }),\n );\n\n // 3. Hand the shared processor a source per file. Sizes ride along from the\n // walk; the bytes are read only if the file survives that far.\n const sources: DeploySource[] = [...unique].map(([absPath, size]) => ({\n path: contentPath(absPath, inputBasePath),\n origin: absPath,\n size,\n read: () => readContent(absPath),\n }));\n\n return processDeployFiles(sources, options, platformLimits);\n}\n\n/**\n * The path a file should have relative to the upload root, in web form.\n * Anything the root does not contain falls back to its basename — a file\n * reached through a symlink that points outside the deploy is still deployed,\n * just flat.\n */\nfunction contentPath(absPath: string, inputBasePath: string): string {\n if (inputBasePath && inputBasePath.length > 0) {\n const rel = path.relative(inputBasePath, absPath);\n if (rel && typeof rel === 'string' && !rel.startsWith('..')) {\n return rel.replace(/\\\\/g, '/');\n }\n }\n return path.basename(absPath);\n}\n","/**\n * @file Main entry point for the Ship SDK.\n *\n * This is the Node.js entry: file-system deploy input plus the `SHIP_*`\n * env-var fallback. Browser consumers resolve this same package to the\n * browser build through the `browser` condition in the exports map —\n * bundlers select it automatically; there is no separate import path.\n */\n\n// Re-export everything from the Node.js index, including both named and default exports\nexport * from './node/index.js';\nexport { default } from './node/index.js';\n","/**\n * @file Ship SDK for Node.js environments.\n *\n * The Node-side `Ship` adds two things on top of the base class:\n * 1. Environment detection — refuses to construct outside Node.\n * 2. `SHIP_TOKEN` / `SHIP_API_URL` env-var resolution as the universal\n * \"process boundary\" credential source — the industry's one-token\n * convention. Constructor arguments win over env vars.\n *\n * The SDK does NOT read `~/.shiprc` or `package.json` `\"ship\"` keys — that's\n * the CLI's job (see `cli/shiprc.ts`). Keeping file resolution out of the SDK\n * is what lets embedded consumers (MCP, n8n, GitHub Action) safely write\n * `new Ship({})` for anonymous public deployments without inheriting the host\n * developer's personal credentials.\n */\n\nimport { ShipError } from '@shipstatic/types';\nimport { Ship as BaseShip } from '../shared/base-ship.js';\nimport { getENV } from '../shared/lib/env.js';\nimport type {\n DeployInput,\n DeploymentCreateResponse,\n DeploymentOptions,\n ShipClientOptions,\n StaticFile,\n} from '../shared/types.js';\nimport { readEnvConfig } from './core/config.js';\n\n// Export all shared functionality\nexport * from '../shared/index.js';\n\n/**\n * Ship SDK Client for Node.js environments.\n *\n * @example\n * ```typescript\n * // Authenticated — explicit token (API key, deploy token, or OAuth bearer)\n * const ship = new Ship({ token: 'ship-your-api-key' });\n *\n * // Authenticated — picks up SHIP_TOKEN from env\n * const ship = new Ship({});\n *\n * // Anonymous public deploy — works when neither constructor nor env provides a token\n * const ship = new Ship({});\n * await ship.deploy('./dist');\n * ```\n */\nexport class Ship extends BaseShip {\n constructor(options: ShipClientOptions = {}) {\n if (getENV() !== 'node') {\n throw ShipError.business('Node.js Ship class can only be used in Node.js environment.');\n }\n\n // Layer env vars under constructor options. The merged result is what the\n // base class sees, so the credential and the HTTP client are fully formed\n // by the time the constructor returns — no async config phase needed.\n //\n // Truthiness (not `??`) is deliberate: an empty-string token is absence\n // (shell expansion of unset CI variables), so `token: ''` falls through\n // to `SHIP_TOKEN` instead of locking in a phantom credential. A client\n // constructed with `session: true` has chosen its identity — the ambient\n // token does not ride along.\n const env = readEnvConfig();\n super({\n ...options,\n apiUrl: options.apiUrl || env.apiUrl,\n token: options.token || (options.session ? undefined : env.token),\n });\n }\n\n /**\n * Deploy file or directory paths to ShipStatic. Convenience shortcut for\n * `ship.deployments.upload()`.\n *\n * Wrong-platform inputs (e.g. `File[]`) fail at compile time. For\n * platform-neutral code, use `ship.deployments.upload()`, which accepts\n * the wider `DeployInput` and validates at runtime — that asymmetry is\n * intentional: the convenience shortcut narrows; the resource-layer\n * contract stays platform-neutral.\n */\n async deploy(\n input: string | string[],\n options?: DeploymentOptions,\n ): Promise<DeploymentCreateResponse> {\n return super.deploy(input, options);\n }\n\n protected async processInput(\n input: DeployInput,\n options: DeploymentOptions,\n ): Promise<StaticFile[]> {\n // Normalize string to string[] and validate.\n const paths = typeof input === 'string' ? [input] : input;\n\n if (!Array.isArray(paths) || !paths.every((p) => typeof p === 'string')) {\n throw ShipError.business(\n 'Invalid input type for Node.js environment. Expected string or string[].',\n );\n }\n\n if (paths.length === 0) {\n throw ShipError.business('No files to deploy.');\n }\n\n const { processFilesForNode } = await import('./core/node-files.js');\n return processFilesForNode(paths, options, this.platformLimits ?? undefined);\n }\n}\n\n// Default export (for `import Ship from '@shipstatic/ship'`)\nexport default Ship;\n\n// Node-only utilities (path-walking + MD5 over the local filesystem)\nexport { processFilesForNode } from './core/node-files.js';\n","/**\n * @file Base Ship SDK class — shared functionality across environments.\n *\n * The constructor is fully synchronous: an `ApiHttp` instance is built immediately\n * with whatever credentials the caller supplied (and, in Node, env vars merged in\n * by the subclass before `super()`). The only deferred work is the one-shot\n * `GET /limits` fetch that hydrates platform limits — lazy, memoized, and run\n * from the two places that actually READ the result: the deploy pipeline's\n * `processInput` and the public `getLimits()`. It ran on the first API call of\n * any kind until 2026-08-12, which cost every other command a round trip it\n * never used.\n *\n * Subclasses only override what genuinely differs per environment:\n * - `processInput()` — Node reads paths from disk; Browser handles `File[]`\n *\n * That is the whole list, and it was two entries until 2026-08-12: a\n * `getDeployBodyCreator()` sat beside it, overridden identically in both\n * subclasses to return the one builder they now share. A seam with the same\n * answer on both sides is not a seam.\n *\n * Everything else (the credential slot, resources, events, lazy platform-limits)\n * lives here.\n */\n\nimport type {\n AccountResource,\n DeploymentCreateResponse,\n DeploymentResource,\n DomainResource,\n PingResponse,\n PlatformLimits,\n StaticFile,\n TokenResource,\n} from '@shipstatic/types';\nimport { API_PATHS, ShipError, validateCaller, validateToken } from '@shipstatic/types';\nimport { ApiHttp } from './api/http.js';\nimport {\n createAccountResource,\n createDeploymentResource,\n createDomainResource,\n createTokenResource,\n type DeployInput,\n} from './resources.js';\nimport type { DeploymentOptions, ShipClientOptions, ShipEvents, TokenProvider } from './types.js';\n\n/**\n * Abstract base class for Ship SDK implementations.\n */\nexport abstract class Ship {\n // Resource handles, created once at construction. Each is a thin facade\n // bound to `this.http`.\n // Parameterized with the SDK's extended options (timeout, callbacks,\n // signal…) — the interface's documented extension point, so typed\n // consumers can pass them without casts.\n public readonly deployments: DeploymentResource<DeploymentOptions>;\n public readonly domains: DomainResource;\n public readonly account: AccountResource;\n public readonly tokens: TokenResource;\n\n // The HTTP client and merged options are private — subclasses interact\n // with the base class through the abstract methods below, never by\n // reaching into these fields. Tests bypass via `(ship as any).http = ...`.\n private readonly http: ApiHttp;\n private readonly clientOptions: ShipClientOptions;\n\n // Lazy-init plumbing for the one-shot `GET /limits` fetch.\n // `platformLimits` is INSTANCE state (not a module-level singleton): two\n // Ships against different `apiUrl`s — staging + prod, multi-tenant\n // orchestrators, n8n with multiple credentials — must not clobber each\n // other's limits. Each instance owns its hydrated copy.\n // `protected` so subclasses' `processInput` can pass it down to the\n // platform-specific file-validation utilities.\n private initPromise: Promise<void> | null = null;\n protected platformLimits: PlatformLimits | null = null;\n\n // The credential slot — one platform token (any population) or a provider\n // that supplies one per request. Read dynamically on every request through\n // `getAuthHeaders`, so `setToken` takes effect without rebuilding the client.\n private credential: string | TokenProvider | null = null;\n\n constructor(options: ShipClientOptions = {}) {\n // SDK-boundary normalization: an empty-string token is absence of\n // credential intent, never a credential. Empty strings reach here from\n // shell-expansion of unset CI variables, empty form fields in browser\n // apps, and any other path that produces `''` instead of `undefined`.\n // Normalizing once at the SDK boundary covers every entry point: CLI,\n // Browser SDK, Node SDK, embedded consumers, and direct base-class use.\n options = {\n ...options,\n apiUrl: options.apiUrl || undefined,\n token: options.token || undefined,\n caller: options.caller || undefined,\n };\n this.clientOptions = options;\n\n // Caller identity is validated at the boundary like the token: a value\n // the API would silently drop (the header is unauthenticated) is a\n // configuration error here, never a quiet fallback to IP bucketing.\n if (options.caller !== undefined) {\n validateCaller(options.caller);\n }\n\n // One client, one identity. A token and a cookie session are different\n // principals — holding both is a configuration error, not a precedence\n // question.\n if (options.token && options.session) {\n throw ShipError.config('Provide either `token` or `session`, not both.');\n }\n\n // Static tokens are validated at the boundary (prefix-classified, same\n // rules the server applies); providers are invoked per request instead.\n if (typeof options.token === 'string') {\n validateToken(options.token);\n this.credential = options.token;\n } else if (options.token) {\n this.credential = options.token;\n }\n\n // Build the HTTP client once. The `getAuthHeaders` callback reads\n // `this.credential` dynamically on every request.\n this.http = new ApiHttp({\n ...options,\n getAuthHeaders: () => this.getAuthHeaders(),\n });\n\n const ctx = { getApi: () => this.http };\n\n this.deployments = createDeploymentResource({\n ...ctx,\n // The platform limits are fetched HERE, at the one seam that reads\n // them, rather than by every resource method. `processInput` is the\n // only consumer — the file-size, file-count and blocklist checks run\n // inside it — so this is where the round trip is earned.\n //\n // It used to be an `ensureInit()` at the top of all nineteen wrappers,\n // which meant `domains list`, `tokens list` and even `ping` each paid a\n // `/limits` request they never read. Every CLI command is one process,\n // so that was a wasted round trip on every invocation of the product.\n processInput: async (input, opts) => {\n await this.ensureInitialized();\n return this.processInput(input, opts);\n },\n });\n this.domains = createDomainResource(ctx);\n this.account = createAccountResource(ctx);\n this.tokens = createTokenResource(ctx);\n }\n\n // Environment-specific behavior.\n protected abstract processInput(\n input: DeployInput,\n options: DeploymentOptions,\n ): Promise<StaticFile[]>;\n\n /**\n * Lazy initialization — fetches platform limits (file size / count caps) once,\n * on the first API call. Subsequent calls reuse the resolved promise.\n */\n protected async ensureInitialized(): Promise<void> {\n if (!this.initPromise) {\n this.initPromise = this.fetchPlatformLimits();\n }\n return this.initPromise;\n }\n\n private async fetchPlatformLimits(): Promise<void> {\n try {\n this.platformLimits = await this.http.request<PlatformLimits>(\n API_PATHS.LIMITS,\n { method: 'GET' },\n 'Get limits',\n );\n } catch (error) {\n // Reset so the next API call can retry initialization.\n this.initPromise = null;\n throw error;\n }\n }\n\n /**\n * Ping the API server, resolving its answer: `{ success, timestamp }`, where\n * `timestamp` is the server clock in unix SECONDS.\n *\n * It resolves the response rather than a bare `true` because every other\n * method here does — narrowing to a boolean discarded the one thing ping\n * carries beyond liveness, and made `success` mean a boolean on the wire and\n * something else by the time it reached a caller. A non-OK response throws in\n * transport, so a resolved value always means the API answered.\n */\n async ping(): Promise<PingResponse> {\n // No `ensureInitialized()`: a reachability check reads no platform limits,\n // and hydrating them here made the cheapest call in the product issue two\n // requests — `/limits` and then `/ping`.\n return this.http.request<PingResponse>(API_PATHS.PING, { method: 'GET' }, 'Ping');\n }\n\n /**\n * Deploy project (convenience shortcut to `ship.deployments.upload()`).\n */\n async deploy(input: DeployInput, options?: DeploymentOptions): Promise<DeploymentCreateResponse> {\n return this.deployments.upload(input, options);\n }\n\n /**\n * Get current account information (convenience shortcut to `ship.account.get()`).\n */\n async whoami() {\n return this.account.get();\n }\n\n /**\n * Get platform limits (max file size, file count, total size).\n * Reuses the response fetched during initialization. Per-instance state —\n * does not leak between concurrent Ships against different API URLs.\n */\n async getLimits(): Promise<PlatformLimits> {\n if (this.platformLimits) return this.platformLimits;\n await this.ensureInitialized();\n // biome-ignore lint/style/noNonNullAssertion: ensureInitialized() hydrates platformLimits or throws\n return this.platformLimits!;\n }\n\n on<K extends keyof ShipEvents>(event: K, handler: (...args: ShipEvents[K]) => void): void {\n this.http.on(event, handler);\n }\n\n off<K extends keyof ShipEvents>(event: K, handler: (...args: ShipEvents[K]) => void): void {\n this.http.off(event, handler);\n }\n\n /**\n * Set global headers included in every request.\n * Useful for injecting custom headers (e.g. for admin impersonation).\n */\n setHeaders(headers: Record<string, string>): void {\n this.http.setGlobalHeaders(headers);\n }\n\n /**\n * Clear all custom global headers.\n */\n clearHeaders(): void {\n this.http.setGlobalHeaders({});\n }\n\n /**\n * Sets the client token — any platform token (API key, deploy token, OAuth\n * access token) or a {@link TokenProvider} invoked per request. Replaces\n * whatever credential the client held before.\n * @param token A platform token, sent verbatim, or a provider function\n */\n public setToken(token: string | TokenProvider): void {\n // One client, one identity — the constructor's token/session exclusion\n // holds for the client's whole life, not just its first moment.\n if (this.clientOptions.session) {\n throw ShipError.config('Provide either `token` or `session`, not both.');\n }\n if (typeof token === 'string') {\n if (!token) {\n throw ShipError.business('Invalid token provided. Token must be a non-empty string.');\n }\n validateToken(token);\n this.credential = token;\n return;\n }\n if (typeof token !== 'function') {\n throw ShipError.business(\n 'Invalid token provided. Token must be a non-empty string or a provider function.',\n );\n }\n this.credential = token;\n }\n\n /**\n * Resolve the credential slot into request headers. Async because a\n * provider may mint or refresh its token per request.\n *\n * Anonymity requires proven absence of credentials: a configured provider\n * that yields nothing is an error — the request fails typed rather than\n * silently proceeding as an anonymous public deploy. Empty-string\n * normalization at the constructor is the same invariant's boundary\n * condition: `''` is absence of intent, so it never reaches this point.\n */\n private async getAuthHeaders(): Promise<Record<string, string>> {\n if (this.credential === null) return {};\n const value = typeof this.credential === 'function' ? await this.credential() : this.credential;\n if (!value) {\n throw ShipError.authentication('Token provider returned no token.');\n }\n if (typeof value !== 'string') {\n throw ShipError.authentication('Token provider returned a non-string value.');\n }\n return { Authorization: `Bearer ${value}` };\n }\n}\n","/**\n * @file The transport. It carries requests; it does not know what they mean.\n *\n * Headers, the credential, the timeout signal, the retry loop, the event\n * vocabulary and error normalization live here — everything that is true of\n * EVERY request this client makes. What is true of one request (its path, its\n * verb, its body, its response type) lives with the resource that names it,\n * in `resources.ts`.\n *\n * **That was two statements of one fact until 2026-08-12.** This class carried\n * eighteen endpoint methods — `getDomain`, `listTokens`, … — and every one of\n * them existed to be wrapped by a resource method of the same shape, because\n * this SDK mirrors the wire 1:1 by design. Two layers that are isomorphic BY\n * DESIGN are not two layers. The endpoints went down to the resources, and\n * \"`ApiHttp` is pure transport\" stopped being an aspiration in a doc.\n */\nimport {\n API_PATHS,\n CALLER,\n DEFAULT_API,\n ErrorType,\n IDEMPOTENCY_KEY_CONSTRAINTS,\n ShipError,\n} from '@shipstatic/types';\nimport { SimpleEvents } from '../events.js';\nimport type { Fetch, ShipClientOptions } from '../types.js';\n\n// =============================================================================\n// CONSTANTS\n// =============================================================================\n\nconst DEFAULT_REQUEST_TIMEOUT = 30_000;\n\n/**\n * Retries: two, so three attempts. The CLI's own 5xx message says \"try again\",\n * and the client should take its own advice before handing that sentence to a\n * person.\n *\n * `maxRetries` on `ShipClientOptions` is the one public knob (`0` disables) —\n * the name Stripe and OpenAI use, which is what earns it README surface under\n * this repo's doc-placement rule. No env var and no CLI flag: the CLI rides\n * the default, and a flag can be added the day someone asks.\n */\nconst DEFAULT_MAX_RETRIES = 2;\n\n/** Full-jitter exponential backoff, in milliseconds. */\nconst RETRY_BASE_DELAY = 300;\nconst RETRY_MAX_DELAY = 2_000;\n\n/**\n * The server faults worth trying again. 429 is deliberately absent: the\n * platform's rate limiter has just answered, and a client that auto-retries is\n * arguing with it. (Revisit only alongside honoring `Retry-After`, as its own\n * decision.)\n */\nconst RETRYABLE_STATUS = new Set([500, 502, 503, 504]);\n\n/**\n * Sleep, unless the caller's signal says otherwise — so an abort mid-backoff\n * lands immediately instead of after the delay. Rejects with the signal's own\n * reason, which the caller's error path then classifies like any other.\n */\nfunction sleep(ms: number, signal?: AbortSignal | null): Promise<void> {\n return new Promise((resolve, reject) => {\n if (signal?.aborted) {\n reject(signal.reason);\n return;\n }\n const done = () => {\n clearTimeout(timer);\n signal?.removeEventListener('abort', onAbort);\n };\n const onAbort = () => {\n done();\n reject(signal?.reason);\n };\n const timer = setTimeout(() => {\n done();\n resolve();\n }, ms);\n signal?.addEventListener('abort', onAbort);\n });\n}\n\n/**\n * Deploys get their own ceilings, because one budget cannot fit every\n * operation this client performs.\n *\n * 30s is right for a metadata read — `/ping`, `/account`, a page of a list —\n * where anything slower is a fault rather than a big payload. A deploy is\n * bounded by the PLATFORM's limits instead: `DEPLOYMENT.MAX_TOTAL_SIZE` is\n * 50MB, and 50MB in 30s needs ~13 Mbit/s of sustained UPLOAD, above what most\n * residential links give. A deployment the API explicitly permits was being\n * aborted here by default — which is the exact failure `Idempotency-Key`\n * exists to repair, so the cause had to go and not merely the remedy.\n *\n * 5 minutes covers 50MB at ~1.4 Mbit/s.\n */\nconst DEFAULT_DEPLOY_TIMEOUT = 300_000;\n\n/**\n * The server's own budget for a build, mirrored here because the client has\n * to outlast it: the API gives the build service\n * `PERFORMANCE.BUILD_SERVICE_TIMEOUT` (`cloudflare/api/src/lib/config.ts`),\n * and that work begins only after the upload lands.\n *\n * **Raising it there must raise it here.** The two sit in different repos, so\n * nothing can fence the pair; the constraint is stated at both ends instead.\n */\nconst BUILD_SERVICE_BUDGET = 300_000;\n\n/**\n * A deploy that also builds: the upload, then the build, then the commit.\n *\n * Written as a sum rather than a number, so the name and the value compose\n * the same way — `DEPLOY` plus `BUILD` on one side, the deploy budget plus\n * the build budget on the other. A magic `600_000` would state the result\n * and hide the reasoning, and the reasoning is the part that has to survive\n * someone tuning either half.\n */\nconst DEFAULT_DEPLOY_BUILD_TIMEOUT = DEFAULT_DEPLOY_TIMEOUT + BUILD_SERVICE_BUDGET;\n\n// =============================================================================\n// TYPES\n// =============================================================================\n\nexport interface ApiHttpOptions extends ShipClientOptions {\n /** Resolves the credential slot per request — async so token providers can mint/refresh. */\n getAuthHeaders: () => Record<string, string> | Promise<Record<string, string>>;\n}\n\nexport interface RequestResult<T> {\n data: T;\n status: number;\n}\n\n/**\n * The deploy's CARRIAGE — the two facts about a deploy that are transport's\n * rather than the deployment resource's.\n *\n * The numbers are transport's because a budget for how long to wait on a wire\n * is nothing else, and the endpoint is transport's because `deployEndpoint` is\n * a client option that redirects the route. The CHOICE between the two\n * ceilings is the resource's, because only it knows that `build`/`prerender`\n * wait on work the server does after the upload lands.\n */\nexport interface DeployTransport {\n /** `/deployments`, or `/upload` where the `@internal` option redirects it. */\n readonly endpoint: string;\n /** The ordinary deploy ceiling. */\n readonly timeout: number;\n /** The ceiling when the server will also build. */\n readonly buildTimeout: number;\n}\n\n/**\n * What a resource may ask of the transport: carry this request, and tell me\n * what came back.\n *\n * This interface is the whole seam. `resources.ts` states WHICH request — the\n * path, the verb, the body, the response type — and hands it here; nothing\n * above this line knows the base URL, the credential, the retry policy or the\n * event vocabulary, and nothing below knows what a domain is.\n */\nexport interface Transport {\n request<T>(\n path: string,\n options: ShipRequestInit,\n operationName: string,\n timeoutMs?: number,\n ): Promise<T>;\n requestWithStatus<T>(\n path: string,\n options: ShipRequestInit,\n operationName: string,\n ): Promise<RequestResult<T>>;\n readonly deploy: DeployTransport;\n}\n\n/**\n * A request as THIS client composes one.\n *\n * Identical to `RequestInit` but for the headers, which are narrowed from the\n * DOM's three-shaped `HeadersInit` to the one shape every call site here\n * actually builds. That narrowing is load-bearing twice over: `mergeHeaders`\n * used to reach its record through an `as` cast, and `hasIdempotencyKey` used\n * to walk all three shapes to find a key that only ever arrives in one of\n * them. Narrowing at RUNTIME instead would have turned an unreachable case\n * into a SILENT no-retry — a deploy that quietly stopped replaying because\n * someone handed the transport a `Headers`. Here that is a compile error.\n */\nexport type ShipRequestInit = Omit<RequestInit, 'headers'> & {\n headers?: Record<string, string>;\n};\n\n// =============================================================================\n// HTTP CLIENT\n// =============================================================================\n\nexport class ApiHttp extends SimpleEvents implements Transport {\n private readonly apiUrl: string;\n private readonly getAuthHeadersCallback: () =>\n | Record<string, string>\n | Promise<Record<string, string>>;\n private readonly session: boolean;\n private readonly caller: string | undefined;\n private readonly timeout: number;\n private readonly maxRetries: number;\n private readonly fetch: Fetch;\n private globalHeaders: Record<string, string> = {};\n\n /** @see DeployTransport — the carriage facts the deployment resource reads. */\n readonly deploy: DeployTransport;\n\n constructor(options: ApiHttpOptions) {\n super();\n this.apiUrl = options.apiUrl || DEFAULT_API;\n this.getAuthHeadersCallback = options.getAuthHeaders;\n this.session = options.session ?? false;\n this.caller = options.caller;\n this.timeout = options.timeout ?? DEFAULT_REQUEST_TIMEOUT;\n this.maxRetries = Math.max(0, options.maxRetries ?? DEFAULT_MAX_RETRIES);\n // Bind to globalThis when falling back to the platform `fetch` — browsers\n // require `this === window` on `window.fetch` and throw \"Illegal invocation\"\n // when it's invoked as a property of any other object.\n this.fetch = options.fetch ?? globalThis.fetch.bind(globalThis);\n this.deploy = {\n endpoint: options.deployEndpoint || API_PATHS.DEPLOYMENTS,\n // An explicit timeout is the caller's whole answer and applies to deploys\n // too — they asked for a ceiling, not for one with an exception. Only the\n // DEFAULT splits by operation.\n timeout: options.timeout ?? DEFAULT_DEPLOY_TIMEOUT,\n buildTimeout: options.timeout ?? DEFAULT_DEPLOY_BUILD_TIMEOUT,\n };\n }\n\n /**\n * Set global headers included in every request.\n * Priority: globalHeaders (lowest) < instance auth < per-request headers (highest)\n */\n setGlobalHeaders(headers: Record<string, string>): void {\n this.globalHeaders = headers;\n }\n\n // ===========================================================================\n // CORE REQUEST INFRASTRUCTURE\n // ===========================================================================\n\n /**\n * Execute an HTTP request, retrying the failures that are worth retrying.\n *\n * The loop lives here because `attemptOnce` is already the single wrap point\n * for headers, the timeout signal, the events and error normalization — so\n * an attempt is a whole request and nothing has to be undone between two.\n *\n * **Every failure is visible, and the event NAME says whether it ended the\n * call.** One call emits `retry* (error | response)`: `request` fires per\n * attempt, so a consumer counting requests sees what actually went out; a\n * failure that will be tried again is a `retry`; `error` and `response` are\n * the two terminal answers, exactly one of which arrives.\n *\n * The failure events are emitted HERE rather than in `attemptOnce`, and\n * that placement is the whole mechanism: terminality is a property of the\n * loop — of `isRetryable` and the attempt budget — so it is knowable only\n * at the one point that owns both. An attempt cannot name its own failure.\n *\n * **The caller's `timeout` governs an ATTEMPT, not the wall clock.** Each\n * attempt is an honest request and deserves the ceiling the caller named;\n * `maxRetries` is the lever on the total. A caller who wants a hard overall\n * deadline passes their own `signal` — see `isRetryable` for why that ends\n * the loop even when it is a timeout.\n */\n private async executeRequest<T>(\n url: string,\n options: ShipRequestInit,\n operationName: string,\n timeoutMs: number = this.timeout,\n ): Promise<RequestResult<T>> {\n for (let attempt = 0; ; attempt++) {\n try {\n return await this.attemptOnce<T>(url, options, operationName, timeoutMs);\n } catch (error) {\n // `attemptOnce` already normalized; this is a pass-through.\n const shipError = ShipError.fromFetchError(error, operationName);\n if (attempt >= this.maxRetries || !this.isRetryable(shipError, options)) {\n this.emit('error', shipError, url);\n throw shipError;\n }\n // Counting from 1: the attempt that just failed, which is also which\n // retry is about to happen. See `ShipEvents.retry`.\n this.emit('retry', shipError, url, attempt + 1);\n\n // Full jitter: `random() * min(cap, base * 2^n)`. Jitter matters more\n // than the curve — it is what stops a platform hiccup from returning\n // every client in lockstep.\n const ceiling = Math.min(RETRY_MAX_DELAY, RETRY_BASE_DELAY * 2 ** attempt);\n try {\n await sleep(Math.random() * ceiling, options.signal);\n } catch (aborted) {\n // The caller stopped us mid-backoff. Their reason, their error — and\n // terminal, so it is an `error` and not a second `retry`.\n const cancelled = ShipError.fromFetchError(aborted, operationName);\n this.emit('error', cancelled, url);\n throw cancelled;\n }\n }\n }\n }\n\n /**\n * Is this failure worth another attempt?\n *\n * Two axes, and both must say yes: what went wrong, and whether the request\n * is one that may be sent twice.\n */\n private isRetryable(error: ShipError, options: ShipRequestInit): boolean {\n // The caller's own signal fired — theirs to decide, whatever the reason.\n // This is what keeps a caller-supplied `AbortSignal.timeout()` working as\n // an OVERALL deadline, and it is the ONLY thing that does: a deadline\n // classifies as `Timeout`, which the loop retries on purpose, so on the\n // error alone a caller's ceiling would look exactly like ours and be\n // silently outlived. Nothing else in this function can tell them apart.\n if (options.signal?.aborted) return false;\n\n // A maintenance 503 is a STATE, not a fault. Its message says when to come\n // back, and retrying three times with backoff only delays that sentence\n // reaching the person who needs it.\n if (error.isType(ErrorType.Maintenance)) return false;\n\n // A user abort stops everything.\n if (error.isType(ErrorType.Cancelled)) return false;\n\n // Nothing was exchanged — `Network` (a refused connection, a DNS failure)\n // or `Timeout` (a deadline of ours expired) — or one of the server faults\n // above. Both retryable members are read through the CATEGORY rather than\n // named here: \"nothing was exchanged\" IS the retryability criterion, so a\n // future member of it should inherit this answer rather than wait for\n // someone to remember this line. Naming `Timeout` beside the guard that\n // already contains it would be a second owner of that membership, free to\n // disagree with the first. `@shipstatic/types` owns it; the timeout suite\n // pins that a deadline is retried.\n const worthRetrying =\n error.isNetworkError() || (error.status !== undefined && RETRYABLE_STATUS.has(error.status));\n if (!worthRetrying) return false;\n\n const method = (options.method ?? 'GET').toUpperCase();\n if (method === 'GET' || method === 'HEAD') return true;\n\n // PUT and DELETE are semantically idempotent here and still excluded: a\n // DELETE whose response was lost answers 404 on the retry, turning a\n // success into a reported failure. The classic hazard; stay out.\n if (method === 'PUT' || method === 'DELETE') return false;\n\n // Everything else needs the server's own replay guarantee. A deploy\n // carrying `Idempotency-Key` replays its stored 201, so a retry is safe by\n // construction rather than by assumption.\n return this.hasIdempotencyKey(options.headers);\n }\n\n /**\n * Did this request carry the header that makes a repeat safe?\n *\n * Case-insensitively, because HTTP field names are — the CLI's env tier and\n * the SDK option both spell it canonically, but a caller composing headers\n * by hand is entitled not to.\n */\n private hasIdempotencyKey(headers: ShipRequestInit['headers']): boolean {\n if (!headers) return false;\n const target = IDEMPOTENCY_KEY_CONSTRAINTS.HEADER.toLowerCase();\n return Object.keys(headers).some((key) => key.toLowerCase() === target);\n }\n\n /**\n * One attempt: headers, timeout signal, the `request`/`response` events, and\n * error normalization.\n *\n * It does NOT emit a failure event. An attempt cannot know whether its own\n * failure ended the call — that is `executeRequest`'s question — so it\n * normalizes and throws, and the loop names what happened.\n */\n private async attemptOnce<T>(\n url: string,\n options: ShipRequestInit,\n operationName: string,\n timeoutMs: number = this.timeout,\n ): Promise<RequestResult<T>> {\n let cleanup = () => {};\n\n try {\n // Credential resolution runs inside the error boundary: a token\n // provider that throws or yields nothing fails the request through\n // the same typed path (and `error` event) as any transport failure.\n const headers = await this.mergeHeaders(options.headers);\n const timeout = this.createTimeoutSignal(options.signal, timeoutMs);\n cleanup = timeout.cleanup;\n\n const fetchOptions: RequestInit = {\n ...options,\n headers,\n credentials: this.session && !headers.Authorization ? 'include' : undefined,\n signal: timeout.signal,\n };\n\n this.emit('request', url, fetchOptions);\n\n const response = await this.fetch(url, fetchOptions);\n cleanup();\n\n if (!response.ok) {\n throw await ShipError.fromHttpResponse(response, operationName);\n }\n\n this.emit('response', this.safeClone(response), url);\n const data = await this.parseResponse<T>(this.safeClone(response));\n return { data, status: response.status };\n } catch (error) {\n cleanup();\n // Normalize anything thrown above (credential resolution, fetch\n // failure, abort, response error) into a ShipError.\n // fromFetchError passes existing ShipErrors through unchanged.\n throw ShipError.fromFetchError(error, operationName);\n }\n }\n\n /**\n * Send it; resolve what came back.\n *\n * Takes a PATH, not a URL: the base is this client's and nothing above needs\n * to know it. Twenty-two call sites wrote `${this.apiUrl}${API_PATHS.X}` by\n * hand before the endpoints moved out, which is twenty-two chances to\n * assemble it differently.\n */\n async request<T>(\n path: string,\n options: ShipRequestInit,\n operationName: string,\n timeoutMs?: number,\n ): Promise<T> {\n const { data } = await this.executeRequest<T>(\n `${this.apiUrl}${path}`,\n options,\n operationName,\n timeoutMs,\n );\n return data;\n }\n\n /**\n * The same, plus the HTTP status — for the one operation where the status IS\n * the answer: a domain upsert says create-or-update in its 201/200 and\n * nowhere else in the response.\n */\n async requestWithStatus<T>(\n path: string,\n options: ShipRequestInit,\n operationName: string,\n ): Promise<RequestResult<T>> {\n return this.executeRequest<T>(`${this.apiUrl}${path}`, options, operationName);\n }\n\n // ===========================================================================\n // REQUEST HELPERS\n // ===========================================================================\n\n private async mergeHeaders(\n customHeaders: Record<string, string> = {},\n ): Promise<Record<string, string>> {\n // `caller` is instance identity metadata, like the credential: the\n // rate limiter buckets by X-Caller on every write, so it rides every\n // request rather than any single operation.\n return {\n ...this.globalHeaders,\n ...(this.caller ? { [CALLER.HEADER]: this.caller } : {}),\n ...(await this.getAuthHeadersCallback()),\n ...customHeaders,\n };\n }\n\n private createTimeoutSignal(\n existingSignal?: AbortSignal | null,\n timeoutMs: number = this.timeout,\n ): {\n signal: AbortSignal;\n cleanup: () => void;\n } {\n const controller = new AbortController();\n\n // The composed signal must say WHICH deadline fired. Both used to abort\n // bare, so the SDK's own timeout, a caller's abort and a caller's\n // `AbortSignal.timeout()` all arrived as `AbortError` and all classified\n // as `Cancelled` — \"you cancelled this\" for a deadline nobody set by hand.\n // An abort REASON survives fetch's rejection verbatim (captured across\n // Node, Bun and the three engines), so each keeps its own identity: ours\n // is a `TimeoutError` naming the ceiling, and the caller's is forwarded\n // untouched. `executeRequest` retries the first and never the second.\n const timeoutId = setTimeout(\n () => controller.abort(new DOMException(`Timed out after ${timeoutMs}ms`, 'TimeoutError')),\n timeoutMs,\n );\n\n const forward = existingSignal ? () => controller.abort(existingSignal.reason) : undefined;\n if (existingSignal && forward) {\n existingSignal.addEventListener('abort', forward);\n if (existingSignal.aborted) controller.abort(existingSignal.reason);\n }\n\n return {\n signal: controller.signal,\n // The listener is removed, not just the timer: with retries a caller's\n // signal outlives the attempt, and one listener per attempt on a\n // long-lived signal is a leak that grows with every retry.\n cleanup: () => {\n clearTimeout(timeoutId);\n if (existingSignal && forward) existingSignal.removeEventListener('abort', forward);\n },\n };\n }\n\n private safeClone(response: Response): Response {\n try {\n return response.clone();\n } catch {\n return response;\n }\n }\n\n private async parseResponse<T>(response: Response): Promise<T> {\n if (response.headers.get('Content-Length') === '0' || response.status === 204) {\n return undefined as T;\n }\n return response.json() as Promise<T>;\n }\n}\n","/**\n * Event system for Ship SDK\n * Lightweight, reliable event handling with proper error boundaries\n */\n\nimport type { ShipEvents } from './types.js';\n\n/**\n * Lightweight typed event emitter.\n *\n * Public API: `on()` / `off()`. `emit()` is internal — only the SDK\n * publishes events. Throwing handlers are evicted automatically and\n * surfaced as `error` events on the next tick.\n */\nexport class SimpleEvents {\n // biome-ignore lint/complexity/noBannedTypes: the registry is heterogeneous by design — per-event signatures are enforced at the on()/emit() boundary\n private handlers = new Map<string, Set<Function>>();\n\n /**\n * Add event handler\n */\n on<K extends keyof ShipEvents>(event: K, handler: (...args: ShipEvents[K]) => void): void {\n if (!this.handlers.has(event as string)) {\n this.handlers.set(event as string, new Set());\n }\n this.handlers.get(event as string)?.add(handler);\n }\n\n /**\n * Remove event handler\n */\n off<K extends keyof ShipEvents>(event: K, handler: (...args: ShipEvents[K]) => void): void {\n const eventHandlers = this.handlers.get(event as string);\n if (eventHandlers) {\n eventHandlers.delete(handler);\n if (eventHandlers.size === 0) {\n this.handlers.delete(event as string);\n }\n }\n }\n\n /**\n * Emit event (internal use only)\n * @internal\n */\n emit<K extends keyof ShipEvents>(event: K, ...args: ShipEvents[K]): void {\n const eventHandlers = this.handlers.get(event as string);\n if (!eventHandlers) return;\n\n // Snapshot handlers so a handler that mutates the set during iteration\n // (e.g. by removing itself) doesn't skip or duplicate calls.\n const handlerArray = Array.from(eventHandlers);\n\n for (const handler of handlerArray) {\n try {\n handler(...args);\n } catch (error) {\n // A throwing handler is treated as broken — drop it so we don't\n // repeatedly invoke it and re-emit the failure as an `error` event\n // for observability. Defer the re-emit so the next tick has a clean\n // call stack and we can't recurse if the error handler also throws.\n eventHandlers.delete(handler);\n\n if (event !== 'error') {\n setTimeout(() => {\n const err = error instanceof Error ? error : new Error(String(error));\n this.emit('error', err, String(event));\n }, 0);\n }\n }\n }\n }\n}\n","/**\n * @file The SDK's vocabulary — every request it can make, stated once.\n *\n * A resource method IS its endpoint: the path, the verb, the body, the\n * response type. It hands that to the transport, which knows how to carry a\n * request and nothing about what one means.\n *\n * **These were two layers until 2026-08-12.** `ApiHttp` carried eighteen\n * endpoint methods and every factory below wrapped one of them 1:1 —\n * `get: async (name) => getApi().getDomain(name)` — because this SDK mirrors\n * the wire one method per endpoint BY DESIGN (see CLAUDE.md, \"Recorded\n * absences\"). That design is exactly what made the second layer a restatement\n * rather than an adapter: the two could not diverge without one of them being\n * wrong. Folding DOWN rather than up is what keeps the public grouping and the\n * transport separate, which was the whole point of having two files.\n *\n * The `*Resource` interfaces come from `@shipstatic/types` and did not move.\n * They are the published contract; this file is how it is met.\n */\n\nimport type { AccountResource, DeployInput, StaticFile } from '@shipstatic/types';\nimport {\n type AccountGetResponse,\n API_PATHS,\n type Deployment,\n type DeploymentCreateResponse,\n type DeploymentDeleteResponse,\n type DeploymentListResponse,\n type DeploymentResource,\n type Domain,\n type DomainDeleteResponse,\n type DomainDnsResponse,\n type DomainListResponse,\n type DomainRecordsResponse,\n type DomainResource,\n type DomainShareResponse,\n type DomainValidateResponse,\n type DomainVerifyResponse,\n IDEMPOTENCY_KEY_CONSTRAINTS,\n type ListOptions,\n ShipError,\n type Token,\n type TokenCreateResponse,\n type TokenDeleteResponse,\n type TokenListResponse,\n type TokenResource,\n validateIdempotencyKey,\n validateTtl,\n} from '@shipstatic/types';\n\nexport type {\n AccountResource,\n DeployInput,\n DeploymentResource,\n DomainResource,\n StaticFile,\n TokenResource,\n};\n\nimport type { Transport } from './api/http.js';\nimport { createDeployBody } from './core/deploy-body.js';\nimport { detectAndConfigureSPA } from './lib/spa.js';\nimport { validateDeployConfig, validateLabels, validatePassword } from './lib/validation.js';\nimport type { DeploymentOptions } from './types.js';\n\n/** JSON in, JSON out — the header every body-carrying request here sends. */\nconst JSON_HEADERS = { 'Content-Type': 'application/json' } as const;\n\n/**\n * This client's identity in a deployment's `via` field.\n *\n * Every surface that deploys names itself: the CLI sends `cli`, the GitHub\n * Action `git`, the MCP server `mcp`, the VS Code extension `vsc`, the web\n * apps `web`. A direct SDK call is `sdk`.\n *\n * Applied at this one boundary, so `via` is populated on every deploy from\n * every platform: a deploy through this SDK is never unattributed.\n */\nconst DEPLOY_VIA = 'sdk';\n\n/**\n * Serialize pagination options into a query string, or '' when there are\n * none — the paginated list endpoints accept `limit` and `cursor`.\n */\nfunction listQuery(options?: ListOptions): string {\n const params = new URLSearchParams();\n if (options?.limit !== undefined) params.set('limit', String(options.limit));\n if (options?.cursor !== undefined) params.set('cursor', options.cursor);\n const query = params.toString();\n return query ? `?${query}` : '';\n}\n\n/**\n * Shared context for all resource factories.\n *\n * A factory receives the callbacks it needs and nothing else — which is what\n * lets `getApi()` be a THUNK rather than an instance: the transport is built\n * once in the constructor, but reading it lazily is what keeps the resources\n * constructible before it exists and swappable in tests.\n */\nexport interface ResourceContext {\n getApi: () => Transport;\n}\n\n/**\n * Extended context for deployment resource.\n */\nexport interface DeploymentResourceContext extends ResourceContext {\n processInput: (input: DeployInput, options: DeploymentOptions) => Promise<StaticFile[]>;\n}\n\n/**\n * Upload deployment resource with all CRUD operations.\n *\n * There is no client-side auth branching: an upload from a credential-less\n * client simply carries no `Authorization` header, and the API grants the\n * public-account agent identity per request (claim URL + expiry on the\n * response). The SDK stays a transparent pipe either way.\n */\nexport function createDeploymentResource(\n ctx: DeploymentResourceContext,\n): DeploymentResource<DeploymentOptions> {\n const { getApi, processInput } = ctx;\n\n return {\n /**\n * The whole deploy, in the order it happens: collect the files, ask the\n * platform whether they are a SPA, validate the request boundary, build\n * the multipart body, send it.\n *\n * It read across two files until the endpoint tier folded down — the\n * collection and the SPA step here, the validators and the body in the\n * transport — for no reason a reader could see from either end.\n */\n upload: async (input: DeployInput, options: DeploymentOptions = {}) => {\n if (!processInput) {\n throw ShipError.config('processInput function is not provided.');\n }\n\n const http = getApi();\n const collected = await processInput(input, options);\n const files = await detectAndConfigureSPA(collected, http, options);\n\n if (!files.length) {\n throw ShipError.business('No files to deploy');\n }\n for (const file of files) {\n if (!file.md5) {\n throw ShipError.file(`MD5 checksum missing for file: ${file.path}`, {\n filePath: file.path,\n });\n }\n }\n\n // Fast-fail on definitely-invalid input before constructing a multipart body.\n validatePassword(options.password);\n const ttl = validateTtl(options.ttl);\n const idempotencyKey = validateIdempotencyKey(options.idempotencyKey);\n const labels = validateLabels(options.labels);\n await validateDeployConfig(files);\n\n const flags =\n options.build || options.prerender || options.spa\n ? { build: options.build, prerender: options.prerender, spa: options.spa }\n : undefined;\n const body = await createDeployBody(files, {\n labels,\n via: options.via ?? DEPLOY_VIA,\n password: options.password,\n ttl,\n flags,\n captcha: options.captcha,\n });\n\n // NO Content-Type here, deliberately: `fetch` derives it from the\n // `FormData` body along with the boundary, and setting one by hand would\n // name a boundary the body does not use.\n //\n // The idempotency key rides a header, not the body, because it must be\n // readable before the request is parsed — the API replays a stored 201\n // ahead of the write budget, so a retry costs nothing.\n return http.request<DeploymentCreateResponse>(\n http.deploy.endpoint,\n {\n method: 'POST',\n body,\n ...(idempotencyKey\n ? { headers: { [IDEMPOTENCY_KEY_CONSTRAINTS.HEADER]: idempotencyKey } }\n : {}),\n signal: options.signal || null,\n },\n 'Deploy',\n // Only `build`/`prerender` reach the build service\n // (`api/src/lib/upload-processing.ts:35`); `spa` is local detection\n // bounded by the AI tier's own 10s, so it does not earn the longer\n // ceiling. The transport owns both budgets; this is the one place that\n // knows which applies.\n options.build || options.prerender ? http.deploy.buildTimeout : http.deploy.timeout,\n );\n },\n\n list: async (options?: ListOptions) =>\n getApi().request<DeploymentListResponse>(\n `${API_PATHS.DEPLOYMENTS}${listQuery(options)}`,\n { method: 'GET' },\n 'List deployments',\n ),\n\n get: async (id: string) =>\n getApi().request<Deployment>(\n API_PATHS.DEPLOYMENT(encodeURIComponent(id)),\n { method: 'GET' },\n 'Get deployment',\n ),\n\n set: async (id: string, options: { labels: string[] }) =>\n getApi().request<Deployment>(\n API_PATHS.DEPLOYMENT(encodeURIComponent(id)),\n {\n method: 'PATCH',\n headers: JSON_HEADERS,\n body: JSON.stringify({ labels: validateLabels(options.labels) }),\n },\n 'Update deployment labels',\n ),\n\n delete: async (id: string) =>\n getApi().request<DeploymentDeleteResponse>(\n API_PATHS.DEPLOYMENT(encodeURIComponent(id)),\n { method: 'DELETE' },\n 'Delete deployment',\n ),\n };\n}\n\n/**\n * Create domain resource with all CRUD operations.\n *\n * @remarks\n * The `name` parameter in all methods is an FQDN (Fully Qualified Domain Name).\n * The SDK does not validate or normalize domain names - the API handles all domain semantics.\n */\nexport function createDomainResource(ctx: ResourceContext): DomainResource {\n const { getApi } = ctx;\n\n return {\n // INTENTIONAL DESIGN: The API does NOT support unlinking domains (setting deployment to null).\n // Once a domain is linked to a deployment, it must always have a deployment.\n // Supported: reserve (omit deployment), link, switch deployments atomically, delete entirely.\n // Not supported: unlink after linking (creates ambiguous state with no clear use case).\n // See npm/ship/CLAUDE.md \"Domain Write Semantics\" for full rationale.\n set: async (name: string, options: { deployment?: string; labels?: string[] } = {}) => {\n const labels = validateLabels(options.labels);\n const body: { deployment?: string; labels?: string[] } = {};\n if (options.deployment) body.deployment = options.deployment;\n if (labels !== undefined) body.labels = labels;\n\n // The one operation whose STATUS is part of its answer: 201 means the\n // domain was created, 200 that it was repointed, and the body says the\n // same thing either way.\n const { data, status } = await getApi().requestWithStatus<Domain>(\n API_PATHS.DOMAIN(encodeURIComponent(name)),\n { method: 'PUT', headers: JSON_HEADERS, body: JSON.stringify(body) },\n 'Set domain',\n );\n return { ...data, isCreate: status === 201 };\n },\n\n list: async (options?: ListOptions) =>\n getApi().request<DomainListResponse>(\n `${API_PATHS.DOMAINS}${listQuery(options)}`,\n { method: 'GET' },\n 'List domains',\n ),\n\n get: async (name: string) =>\n getApi().request<Domain>(\n API_PATHS.DOMAIN(encodeURIComponent(name)),\n { method: 'GET' },\n 'Get domain',\n ),\n\n delete: async (name: string) =>\n getApi().request<DomainDeleteResponse>(\n API_PATHS.DOMAIN(encodeURIComponent(name)),\n { method: 'DELETE' },\n 'Delete domain',\n ),\n\n verify: async (name: string) =>\n getApi().request<DomainVerifyResponse>(\n API_PATHS.DOMAIN_VERIFY(encodeURIComponent(name)),\n { method: 'POST' },\n 'Verify domain',\n ),\n\n // The name rides the JSON BODY, not the path: this is a pre-flight check on\n // a string that may not be a legal path segment yet.\n validate: async (name: string) =>\n getApi().request<DomainValidateResponse>(\n API_PATHS.DOMAINS_VALIDATE,\n { method: 'POST', headers: JSON_HEADERS, body: JSON.stringify({ domain: name }) },\n 'Validate domain',\n ),\n\n dns: async (name: string) =>\n getApi().request<DomainDnsResponse>(\n API_PATHS.DOMAIN_DNS(encodeURIComponent(name)),\n { method: 'GET' },\n 'Get domain DNS',\n ),\n\n records: async (name: string) =>\n getApi().request<DomainRecordsResponse>(\n API_PATHS.DOMAIN_RECORDS(encodeURIComponent(name)),\n { method: 'GET' },\n 'Get domain records',\n ),\n\n share: async (name: string) =>\n getApi().request<DomainShareResponse>(\n API_PATHS.DOMAIN_SHARE(encodeURIComponent(name)),\n { method: 'GET' },\n 'Get domain share',\n ),\n };\n}\n\n/**\n * Create account resource (whoami functionality).\n */\nexport function createAccountResource(ctx: ResourceContext): AccountResource {\n const { getApi } = ctx;\n\n return {\n get: async () =>\n getApi().request<AccountGetResponse>(API_PATHS.ACCOUNT, { method: 'GET' }, 'Get account'),\n };\n}\n\n/**\n * Create token resource for managing deploy tokens.\n */\nexport function createTokenResource(ctx: ResourceContext): TokenResource {\n const { getApi } = ctx;\n\n return {\n create: async (options: { ttl?: number; labels?: string[] } = {}) => {\n // Fast-fail on definitely-invalid input, exactly as the deploy boundary\n // does. This is the half the ttl rule's promotion into `@shipstatic/types`\n // was FOR: the envelope lived only in the API route until 2026-08-12, so\n // this call sent whatever it was handed and a bad duration cost a round\n // trip. Validating here is what makes that claim true rather than a\n // sentence in a doc.\n const ttl = validateTtl(options.ttl);\n const labels = validateLabels(options.labels);\n const body: { ttl?: number; labels?: string[] } = {};\n if (ttl !== undefined) body.ttl = ttl;\n if (labels !== undefined) body.labels = labels;\n\n return getApi().request<TokenCreateResponse>(\n API_PATHS.TOKENS,\n { method: 'POST', headers: JSON_HEADERS, body: JSON.stringify(body) },\n 'Create token',\n );\n },\n\n list: async (options?: ListOptions) =>\n getApi().request<TokenListResponse>(\n `${API_PATHS.TOKENS}${listQuery(options)}`,\n { method: 'GET' },\n 'List tokens',\n ),\n\n get: async (token: string) =>\n getApi().request<Token>(\n API_PATHS.TOKEN(encodeURIComponent(token)),\n { method: 'GET' },\n 'Get token',\n ),\n\n delete: async (token: string) =>\n getApi().request<TokenDeleteResponse>(\n API_PATHS.TOKEN(encodeURIComponent(token)),\n { method: 'DELETE' },\n 'Delete token',\n ),\n };\n}\n","/**\n * @file The deploy request body — one builder, both platforms.\n *\n * A deploy is one multipart POST: the files, their checksums, and the\n * deployment metadata that rides beside them. None of that differs by\n * platform, and since 2026-08-12 nothing here does either.\n *\n * **It was two files, and the second existed only to hand-encode.** Node built\n * its FormData with `formdata-node` and serialized it through\n * `form-data-encoder` into an ArrayBuffer with a hand-computed\n * `Content-Type` and `Content-Length`, because those objects are not the ones\n * undici's `fetch` knows how to encode. That was a real constraint on a Node\n * without a global `FormData` — and `engines.node >= 20` has had global\n * `FormData`, `File` and a multipart-encoding `fetch` all along, so the\n * constraint had already lapsed. Verified on both runtimes the SDK targets\n * (node 22, bun 1.3): a native `FormData` posted through `fetch` arrives as\n * multipart with `filename=\"assets/nested/index.html\"` intact — the deploy\n * PATH rides `File.name`, so that verbatim round trip is the whole contract.\n *\n * What the two files actually shared was every line that mattered; what they\n * differed on was a content type-check. So the check became one, and the\n * platform seam moved off the body entirely — it now lives only where it is\n * genuine, in how each platform COLLECTS files (`processInput`).\n */\n\nimport { DEPLOY_FIELDS, ShipError } from '@shipstatic/types';\nimport type { DeployBodyContext, StaticFile } from '../types.js';\n\n/**\n * Build the multipart body for a deploy.\n *\n * Returns a native `FormData`: `fetch` sets the boundary and the\n * `Content-Type` itself, which is why nothing here composes headers. Passing\n * a hand-encoded buffer with a hand-written boundary was the old shape, and\n * every part of it was a way of doing what the runtime does.\n */\nexport async function createDeployBody(\n files: StaticFile[],\n context: DeployBodyContext = {},\n): Promise<FormData> {\n const { labels, via, password, ttl, flags, captcha } = context;\n const formData = new FormData();\n const checksums: string[] = [];\n\n for (const file of files) {\n // An ASSERTION, not a wire rule: `StaticFile.content` is typed\n // `File | Buffer | Blob` and both pipelines produce one of those, so\n // reaching here means an internal bug rather than bad user input. It\n // names the path because that is the only thing that makes such a bug\n // findable. (`Buffer` is a `Uint8Array`, so all three are `BlobPart`s.)\n if (typeof file.content === 'string' || file.content === null || file.content === undefined) {\n throw ShipError.file(`Unsupported file.content type: ${file.path}`, {\n filePath: file.path,\n });\n }\n\n if (!file.md5) {\n throw ShipError.file(`File missing md5 checksum: ${file.path}`, { filePath: file.path });\n }\n\n // The deploy PATH is the filename — the API reads it off `File.name` and\n // stores the file there. The API derives Content-Type from the extension,\n // so the part's own type is deliberately opaque.\n // The cast is the two type libraries failing to agree, not a widening:\n // `Buffer` IS a `Uint8Array` and therefore a `BufferSource`, but this\n // package builds against both node and DOM lib types and\n // `Buffer<ArrayBufferLike>` does not unify with the DOM's `BlobPart`.\n // The guard above is what actually narrows the value.\n formData.append(\n DEPLOY_FIELDS.FILES,\n new File([file.content as BlobPart], file.path, { type: 'application/octet-stream' }),\n );\n checksums.push(file.md5);\n }\n\n // Index-aligned with the files above — the API checks the two lengths match.\n formData.append(DEPLOY_FIELDS.CHECKSUMS, JSON.stringify(checksums));\n\n if (labels && labels.length > 0) formData.append(DEPLOY_FIELDS.LABELS, JSON.stringify(labels));\n if (via) formData.append(DEPLOY_FIELDS.VIA, via);\n if (password) formData.append(DEPLOY_FIELDS.PASSWORD, password);\n // A multipart field is text; `ttl` is the DURATION in seconds and the API\n // turns it into an instant against its own clock. `!== undefined` rather\n // than truthiness — the rule already refuses 0, and a truthiness test would\n // drop it silently instead of letting the caller hear why.\n if (ttl !== undefined) formData.append(DEPLOY_FIELDS.TTL, String(ttl));\n if (flags?.build) formData.append(DEPLOY_FIELDS.BUILD, 'true');\n if (flags?.prerender) formData.append(DEPLOY_FIELDS.PRERENDER, 'true');\n if (flags?.spa) formData.append(DEPLOY_FIELDS.SPA, 'true');\n if (captcha) formData.append(DEPLOY_FIELDS.CAPTCHA, captcha);\n\n return formData;\n}\n","/**\n * @file SPA detection and auto-configuration — the whole concern, including\n * its one wire call.\n *\n * `checkSPA` lived on `ApiHttp` until 2026-08-12, where it was the only method\n * that read a file's CONTENT to decide what to send. Its single caller was\n * `detectAndConfigureSPA`, three lines down, so the endpoint sat one file away\n * from the only thing that ever asked for it. Bringing it here also takes it\n * off the package's public surface, which it never earned: SPA detection is\n * something the SDK does, not something a consumer calls.\n */\n\nimport {\n API_PATHS,\n DEPLOYMENT_CONFIG_FILENAME,\n SPA_CHECK_CONSTRAINTS,\n SPA_DEFAULT_CONFIG,\n type SPACheckRequest,\n type SPACheckResponse,\n} from '@shipstatic/types';\nimport type { Transport } from '../api/http.js';\nimport type { DeploymentOptions, StaticFile } from '../types.js';\nimport { calculateMD5 } from './md5.js';\n\n/**\n * Creates ship.json configuration for SPA projects.\n * @returns Promise resolving to StaticFile with SPA configuration\n */\nexport async function createSPAConfig(): Promise<StaticFile> {\n const configString = JSON.stringify(SPA_DEFAULT_CONFIG, null, 2);\n\n // Create content that works in both browser and Node.js environments\n let content: Buffer | Blob;\n if (typeof Buffer !== 'undefined') {\n // Node.js environment\n content = Buffer.from(configString, 'utf-8');\n } else {\n // Browser environment\n content = new Blob([configString], { type: 'application/json' });\n }\n\n const { md5 } = await calculateMD5(content);\n\n return {\n path: DEPLOYMENT_CONFIG_FILENAME,\n content,\n size: configString.length,\n md5,\n };\n}\n\n/**\n * Ask the platform whether this deploy is a single-page app.\n *\n * Answers `false` without a request whenever it cannot ask honestly: no\n * `index.html` at the root, an index too large for the bound the platform\n * publishes, or content in a shape neither runtime produces. Only the file's\n * TEXT and the path list go up — never the deploy itself.\n *\n * @param files - The deploy, as it stands\n * @param transport - Carries the one request\n */\nexport async function checkSPA(files: StaticFile[], transport: Transport): Promise<boolean> {\n const indexFile = files.find(\n (f) =>\n f.path === SPA_CHECK_CONSTRAINTS.INDEX_FILE ||\n f.path === `/${SPA_CHECK_CONSTRAINTS.INDEX_FILE}`,\n );\n if (!indexFile || indexFile.size > SPA_CHECK_CONSTRAINTS.MAX_INDEX_BYTES) {\n return false;\n }\n\n let indexContent: string;\n if (typeof Buffer !== 'undefined' && Buffer.isBuffer(indexFile.content)) {\n indexContent = indexFile.content.toString('utf-8');\n } else if (typeof Blob !== 'undefined' && indexFile.content instanceof Blob) {\n indexContent = await indexFile.content.text();\n } else if (typeof File !== 'undefined' && indexFile.content instanceof File) {\n indexContent = await indexFile.content.text();\n } else {\n return false;\n }\n\n const body: SPACheckRequest = { files: files.map((f) => f.path), index: indexContent };\n const response = await transport.request<SPACheckResponse>(\n API_PATHS.SPA_CHECK,\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n },\n 'SPA check',\n );\n\n return response.isSPA;\n}\n\n/**\n * Detects SPA projects and auto-generates configuration.\n * This function can be used by both Node.js and browser environments.\n *\n * @param files - Array of StaticFiles to analyze\n * @param transport - Carries the detection request\n * @param options - Deployment options containing SPA detection settings\n * @returns Promise resolving to files array with optional SPA config added\n */\nexport async function detectAndConfigureSPA(\n files: StaticFile[],\n transport: Transport,\n options: DeploymentOptions,\n): Promise<StaticFile[]> {\n // Skip if disabled, config already exists, or server will handle detection\n if (\n options.spaDetect === false ||\n options.spa ||\n options.build ||\n options.prerender ||\n files.some((f) => f.path === DEPLOYMENT_CONFIG_FILENAME)\n ) {\n return files;\n }\n\n try {\n const isSPA = await checkSPA(files, transport);\n\n if (isSPA) {\n const spaConfig = await createSPAConfig();\n return [...files, spaConfig];\n }\n } catch (_error) {\n // SPA detection failed, continue silently without auto-config\n }\n\n return files;\n}\n","/**\n * @file Client-side input validation for SDK request boundaries.\n *\n * These validators run before request construction, and this module is the\n * one import surface for them (`http.ts` takes all three from here). The\n * rules themselves live in `@shipstatic/types` — constants\n * (`LABEL_CONSTRAINTS`, `LABEL_PATTERN`) and whole checks\n * (`validatePassword`, `assertShipJsonSyntax`) alike — so the SDK and the\n * API can never disagree about what a value must look like.\n *\n * Every check here is format, never policy: length envelopes, patterns, JSON\n * syntax. Anything that can evolve server-side — password strength, plan\n * caps, the ship.json schema — is deliberately absent, because a client that\n * judged it would reject input a newer platform accepts. See\n * `@shipstatic/types/CLAUDE.md` \"Validation: format vs policy\".\n */\n\nimport type { StaticFile } from '@shipstatic/types';\nimport {\n assertShipJsonSyntax,\n DEPLOYMENT_CONFIG_FILENAME,\n LABEL_CONSTRAINTS,\n LABEL_PATTERN,\n ShipError,\n} from '@shipstatic/types';\n\n// Re-export the canonical password validator from `@shipstatic/types` so\n// existing SDK callers (`http.ts`) keep their `from '../lib/validation.js'`\n// import path unchanged. The types-tier definition is the single source of\n// truth — see `@shipstatic/types/CLAUDE.md` \"Validation: format vs policy\".\nexport { validatePassword } from '@shipstatic/types';\n\n/**\n * Validate and normalize an array of labels.\n *\n * Lowercases and trims each entry, enforces per-label length and pattern\n * (`LABEL_CONSTRAINTS` / `LABEL_PATTERN`), count cap, and uniqueness after\n * normalization. Returns the normalized array. An empty array is valid and\n * signals \"clear all labels\" on label-update operations.\n */\nexport function validateLabels(labels: string[]): string[];\nexport function validateLabels(labels: string[] | undefined | null): string[] | undefined;\nexport function validateLabels(labels: string[] | undefined | null): string[] | undefined {\n if (labels === undefined || labels === null) return undefined;\n if (labels.length === 0) return labels;\n\n if (labels.length > LABEL_CONSTRAINTS.MAX_COUNT) {\n throw ShipError.validation(`Maximum ${LABEL_CONSTRAINTS.MAX_COUNT} labels allowed`);\n }\n\n const normalized = labels.map((label, i) => {\n if (typeof label !== 'string') {\n throw ShipError.validation(`Label at index ${i} must be a string`);\n }\n const cleaned = label.trim().toLowerCase();\n if (cleaned.length < LABEL_CONSTRAINTS.MIN_LENGTH) {\n throw ShipError.validation(\n `Labels must be at least ${LABEL_CONSTRAINTS.MIN_LENGTH} characters long`,\n );\n }\n if (cleaned.length > LABEL_CONSTRAINTS.MAX_LENGTH) {\n throw ShipError.validation(\n `Labels must be no more than ${LABEL_CONSTRAINTS.MAX_LENGTH} characters long`,\n );\n }\n if (!LABEL_PATTERN.test(cleaned)) {\n throw ShipError.validation(\n `Labels must start and end with alphanumeric characters, with optional separators (${LABEL_CONSTRAINTS.SEPARATORS}) between segments`,\n );\n }\n return cleaned;\n });\n\n const unique = [...new Set(normalized)];\n if (unique.length !== normalized.length) {\n throw ShipError.validation('Duplicate labels are not allowed');\n }\n\n return unique;\n}\n\n/**\n * Validate a deploy's root `ship.json` — syntax only, never schema.\n *\n * The same format-not-policy split the password and label validators follow,\n * drawn one layer further out: ship.json's schema and its compiler live on\n * the server and evolve there, so a client that judged them would reject\n * configs a newer platform accepts. `assertShipJsonSyntax` (the types-tier\n * definition, and the single source of truth) checks only what holds for\n * every past and future schema — the text parses as JSON, and its top level\n * is an object.\n *\n * Scope matches the API's `findDeploymentConfigFile`: the exact name at the\n * deploy root, optional leading slash and nothing else, so a\n * `config/ship.json` stays an ordinary asset the platform never reads.\n */\nexport async function validateDeployConfig(files: StaticFile[]): Promise<void> {\n const config = files.find(\n (f) => f.path === DEPLOYMENT_CONFIG_FILENAME || f.path === `/${DEPLOYMENT_CONFIG_FILENAME}`,\n );\n if (!config) return;\n\n // Node hands the pipeline a `Buffer`; the browser a `File`/`Blob`, which is\n // the only one of the two carrying `.text()`.\n const content = config.content as Blob;\n const text =\n typeof content.text === 'function'\n ? await content.text()\n : (config.content as Buffer).toString('utf8');\n\n assertShipJsonSyntax(text);\n}\n","/**\n * @file Environment variable resolution for the Node.js Ship SDK.\n *\n * The SDK has exactly one ambient credential source: process environment\n * variables. `SHIP_TOKEN` (any platform token — the value's prefix says what\n * it is) and `SHIP_API_URL` are honored as the universal \"process boundary\" —\n * the one-token idiom used across the industry (`GITHUB_TOKEN`, `NPM_TOKEN`,\n * `VERCEL_TOKEN`). Constructor arguments win over env vars.\n *\n * File-based config (`~/.shiprc`, `package.json` `\"ship\"` key) is the CLI's\n * responsibility — see `src/node/cli/shiprc.ts`. The SDK does not read files,\n * which is what lets embedded consumers (MCP, the VS Code extension, GitHub\n * Action) construct `new Ship({})` for anonymous deployments without leaking\n * the host developer's personal credentials.\n */\n\nimport { SHIP_ENV, ShipError } from '@shipstatic/types';\nimport { z } from 'zod';\nimport { CREDENTIAL_FIELDS } from '../../shared/core/credential-schema.js';\nimport { getENV } from '../../shared/lib/env.js';\nimport type { ShipClientOptions } from '../../shared/types.js';\n\n// `.strict()` matches the file-config schema. The `raw` object below is\n// constructed from a fixed set of keys, so .strict() doesn't catch user\n// typos here (env vars we don't read are simply never put into `raw` in\n// the first place). What it does catch is a *contributor* error — adding\n// a new env-var read without updating `CREDENTIAL_FIELDS` produces a clear\n// validation failure rather than a silently-stripped value. Nearly free\n// (one method call), and keeps both schemas reading the same.\nconst EnvConfigSchema = z.object(CREDENTIAL_FIELDS).strict();\n\n/**\n * Map a `ShipClientOptions` field name (camelCase) back to the env var that\n * supplied it (SCREAMING_SNAKE_CASE), so validation errors point users at\n * the actual variable they need to fix. Kept as an explicit table rather\n * than a regex because the set is small, fixed, and unambiguous.\n */\nconst ENV_VAR_BY_FIELD: Record<string, string> = {\n apiUrl: SHIP_ENV.API_URL,\n token: SHIP_ENV.TOKEN,\n};\n\n/**\n * Read `SHIP_*` environment variables and validate the result.\n *\n * Empty strings (CI/Docker often sets env vars to `\"\"` instead of unsetting them)\n * are normalized to `undefined` before validation, so they don't trigger zod's\n * \"min length 1\" check or accidentally override a valid constructor argument.\n *\n * Returns an empty object outside Node.js — browser/edge runtimes have no\n * `process.env` we should reach into.\n */\nexport function readEnvConfig(): Partial<ShipClientOptions> {\n if (getENV() !== 'node') return {};\n\n const raw = {\n apiUrl: process.env[SHIP_ENV.API_URL] || undefined,\n token: process.env[SHIP_ENV.TOKEN] || undefined,\n };\n\n try {\n return EnvConfigSchema.parse(raw);\n } catch (error) {\n if (error instanceof z.ZodError) {\n const issue = error.issues[0];\n const field = issue.path[0] as string | undefined;\n const envVar = (field && ENV_VAR_BY_FIELD[field]) ?? 'SHIP environment configuration';\n throw ShipError.config(`Invalid ${envVar}: ${issue.message}`);\n }\n throw ShipError.config('Invalid environment configuration');\n }\n}\n","/**\n * @file Single source of truth for ambient-config field validation.\n *\n * Both the SDK env reader (`node/core/config.ts`) and the CLI file loader\n * (`node/cli/shiprc.ts`) import these — if we tighten or relax a rule,\n * both layers update together. The `token` field accepts any platform token;\n * strict prefix-classified format validation happens once, at the `Ship`\n * constructor boundary, for every source uniformly.\n *\n * Lives in its own file because it's a pure data constant: tests that mock\n * runtime config behavior shouldn't have to forward this through their mocks.\n */\n\nimport { z } from 'zod';\n\nexport const CREDENTIAL_FIELDS = {\n apiUrl: z.string().url().optional(),\n token: z.string().min(1).optional(),\n};\n","/**\n * @file Shared SDK exports - environment agnostic.\n */\n\nexport type { Account, Deployment, Domain, PingResponse } from '@shipstatic/types';\n// Re-export types from @shipstatic/types\nexport { ErrorType, ShipError } from '@shipstatic/types';\nexport * from './api/http.js';\nexport { Ship } from './base-ship.js';\nexport * from './core/constants.js';\nexport * from './lib/deploy-paths.js';\nexport * from './lib/env.js';\nexport * from './lib/file-validation.js';\nexport * from './lib/junk.js';\n// Shared utilities\nexport * from './lib/md5.js';\nexport * from './lib/security.js';\nexport * from './lib/text.js';\n// Core functionality\nexport * from './resources.js';\nexport * from './types.js';\n","/**\n * @file SDK-specific constants.\n * Platform constants are now in @shipstatic/types.\n */\n\n// Re-export platform constants for convenience\nexport { DEFAULT_API } from '@shipstatic/types';\n","/**\n * Utility functions for string manipulation.\n */\n\n/**\n * Simple utility to pluralize a word based on a count.\n * @param count The number to determine pluralization.\n * @param singular The singular form of the word.\n * @param plural The plural form of the word.\n * @param includeCount Whether to include the count in the returned string. Defaults to true.\n * @returns A string with the count and the correctly pluralized word.\n */\nexport function pluralize(\n count: number,\n singular: string,\n plural: string,\n includeCount: boolean = true,\n): string {\n const word = count === 1 ? singular : plural;\n return includeCount ? `${count} ${word}` : word;\n}\n"],"mappings":"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","names":["normalizeVia","value","via","DeploymentVia","validateIdempotencyKey","ShipError","key","IDEMPOTENCY_KEY_CONSTRAINTS","isTransportFailure","cause","code","isShipError","error","fileExtension","filename","basename","dotIndex","isBlockedExtension","blocked","ext","hasUnsafeChars","UNSAFE_FILENAME_CHARS","hasUnbuiltMarker","filePath","s","UNBUILT_PROJECT_MARKERS","classifyToken","token","API_KEY","TokenKind","DEPLOY_TOKEN","OAUTH_TOKEN","readBearerValue","header","BEARER_SCHEME","assertShipJsonSyntax","text","withoutBom","parsed","DEPLOYMENT_CONFIG_FILENAME","validatePrefixedCredential","shape","label","hexPart","validateApiKey","apiKey","validateDeployToken","deployToken","validateOAuthToken","oauthToken","validateToken","validateCaller","caller","CALLER","validateApiUrl","apiUrl","url","isDeployment","input","validateTtl","TTL_CONSTRAINTS","isPlatformDomain","domain","platformDomain","isCustomDomain","extractSubdomain","generateDeploymentUrl","deployment","generateDomainUrl","serializeLabels","labels","deserializeLabels","labelsJson","validatePassword","trimmed","PASSWORD_CONSTRAINTS","DeploymentStatus","DomainStatus","AccountPlan","API_PATHS","DEPLOY_FIELDS","ErrorType","CLIENT_ONLY_ERROR_TYPES","ERROR_CATEGORIES","SERVER_PRODUCIBLE_ERROR_TYPES","MAX_FOREIGN_MESSAGE_LENGTH","WEB_FILE_EXTENSIONS","WEB_FILE_ACCEPT","AUTH_BASE_PATH","SIGN_IN_RETURN_PARAM","AuthMethod","OAuthScope","SPA_DEFAULT_CONFIG","SPA_CHECK_CONSTRAINTS","DEFAULT_API","SHIP_ENV","MY_API_KEY_URL","PUBLIC_DEPLOYMENT_TTL_SECONDS","FileValidationStatus","LABEL_CONSTRAINTS","LABEL_PATTERN","init_dist","__esmMin","t","_ShipError","type","message","status","details","authDetails","response","operationName","bodyType","json","obj","retryAfterHeader","seconds","existing","op","name","resource","id","errorType","md5Blob","blob","SparkMD5","spark","chunkSize","start","end","md5Buffer","buffer","createHash","hash","md5Path","path","createReadStream","resolve","reject","stream","err","ShipError","chunk","calculateMD5","input","init_md5","__esmMin","init_dist","__setTestEnvironment","env","_testEnvironment","detectEnvironment","getENV","init_env","__esmMin","findCommonParent","dirPaths","normalizedPaths","p","pathSegments","commonSegments","minLength","i","segment","segments","normalizeWebPath","path","init_path","__esmMin","optimizeDeployPaths","filePaths","options","path","normalizeWebPath","extractFileName","commonPrefix","findCommonDirectory","filePath","deployPath","prefixToRemove","pathSegments","commonSegments","minLength","segments","i","segment","init_deploy_paths","__esmMin","init_path","firstBrokenRule","input","limits","FILE_RULES","rule","init_file_rules","__esmMin","init_dist","init_file_validation","path","validateFileName","isBlockedExtension","size","formatFileSize","totalSize","formatFileSize","bytes","decimals","k","sizes","validateFileName","filename","hasUnsafeChars","reservedNames","nameWithoutPath","validateFiles","files","config","errors","warnings","fileStatuses","issue","file","hasUnbuiltMarker","f","FileValidationStatus","totalSize","fileStatus","statusMessage","input","broken","firstBrokenRule","validFiles","canDeploy","getValidFiles","allValidFilesReady","init_file_validation","__esmMin","init_dist","init_file_rules","filterJunk","filePaths","options","p","hasUnbuiltMarker","ShipError","filePath","parts","basename","part","directorySegments","segment","JUNK_DIRECTORIES","junkDir","import_junk","init_junk","__esmMin","init_dist","validateDeployPath","deployPath","sourceIdentifier","ShipError","validateDeployFile","input","limits","broken","firstBrokenRule","init_security","__esmMin","init_dist","init_file_rules","processDeployFiles","sources","options","platformLimits","serverProcessed","deployPaths","optimizeDeployPaths","source","file","surviving","filterJunk","kept","index","deployPath","rules","requireLimits","files","totalSize","validateDeployPath","validateDeployFile","content","md5","calculateMD5","ShipError","init_deploy_files","__esmMin","init_dist","init_deploy_paths","init_junk","init_md5","init_security","node_files_exports","__export","processFilesForNode","findAllFiles","dirPath","visited","results","realPath","entry","fullPath","stats","readContent","filePath","error","message","ShipError","refuseUnbuiltProjects","paths","p","absPath","marker","e","UNBUILT_PROJECT_MARKERS","isShipError","options","platformLimits","getENV","found","unique","file","inputBasePath","findCommonParent","sources","size","contentPath","processDeployFiles","rel","fs","path","init_node_files","__esmMin","init_dist","init_deploy_files","init_env","init_path","src_exports","__export","API_KEY","API_PATHS","AUTH_BASE_PATH","AccountPlan","ApiHttp","AuthMethod","CALLER","DEFAULT_API","DEPLOYMENT_CONFIG_FILENAME","DEPLOY_FIELDS","DEPLOY_TOKEN","DeploymentStatus","DeploymentVia","DomainStatus","ErrorType","FileValidationStatus","IDEMPOTENCY_KEY_CONSTRAINTS","JUNK_DIRECTORIES","LABEL_CONSTRAINTS","LABEL_PATTERN","MY_API_KEY_URL","OAUTH_TOKEN","OAuthScope","PASSWORD_CONSTRAINTS","PUBLIC_DEPLOYMENT_TTL_SECONDS","SHIP_ENV","SIGN_IN_RETURN_PARAM","SPA_CHECK_CONSTRAINTS","SPA_DEFAULT_CONFIG","Ship","ShipError","TTL_CONSTRAINTS","TokenKind","UNBUILT_PROJECT_MARKERS","UNSAFE_FILENAME_CHARS","WEB_FILE_ACCEPT","__setTestEnvironment","allValidFilesReady","assertShipJsonSyntax","calculateMD5","classifyToken","createAccountResource","createDeploymentResource","createDomainResource","createTokenResource","node_default","deserializeLabels","extractSubdomain","filterJunk","formatFileSize","generateDeploymentUrl","generateDomainUrl","getENV","getValidFiles","hasUnbuiltMarker","hasUnsafeChars","isBlockedExtension","isCustomDomain","isDeployment","isPlatformDomain","isShipError","normalizeVia","optimizeDeployPaths","pluralize","processFilesForNode","readBearerValue","serializeLabels","validateApiKey","validateApiUrl","validateCaller","validateDeployFile","validateDeployPath","validateDeployToken","validateFileName","validateFiles","validateIdempotencyKey","validateOAuthToken","validatePassword","validateToken","validateTtl","__toCommonJS","init_dist","init_dist","init_dist","SimpleEvents","event","handler","eventHandlers","args","handlerArray","error","err","DEFAULT_REQUEST_TIMEOUT","DEFAULT_MAX_RETRIES","RETRY_BASE_DELAY","RETRY_MAX_DELAY","RETRYABLE_STATUS","sleep","ms","signal","resolve","reject","done","timer","onAbort","DEFAULT_DEPLOY_TIMEOUT","BUILD_SERVICE_BUDGET","DEFAULT_DEPLOY_BUILD_TIMEOUT","ApiHttp","SimpleEvents","options","DEFAULT_API","API_PATHS","headers","url","operationName","timeoutMs","attempt","error","shipError","ShipError","ceiling","aborted","cancelled","ErrorType","method","target","IDEMPOTENCY_KEY_CONSTRAINTS","key","cleanup","timeout","fetchOptions","response","path","data","customHeaders","CALLER","existingSignal","controller","timeoutId","forward","init_dist","init_dist","createDeployBody","files","context","labels","via","password","ttl","flags","captcha","formData","checksums","file","ShipError","DEPLOY_FIELDS","init_dist","init_md5","createSPAConfig","configString","SPA_DEFAULT_CONFIG","content","md5","calculateMD5","DEPLOYMENT_CONFIG_FILENAME","checkSPA","files","transport","indexFile","f","SPA_CHECK_CONSTRAINTS","indexContent","body","API_PATHS","detectAndConfigureSPA","options","spaConfig","init_dist","validateLabels","labels","LABEL_CONSTRAINTS","ShipError","normalized","label","cleaned","LABEL_PATTERN","unique","validateDeployConfig","files","config","f","DEPLOYMENT_CONFIG_FILENAME","content","text","assertShipJsonSyntax","JSON_HEADERS","DEPLOY_VIA","listQuery","options","params","query","createDeploymentResource","ctx","getApi","processInput","input","ShipError","http","collected","files","detectAndConfigureSPA","file","validatePassword","ttl","validateTtl","idempotencyKey","validateIdempotencyKey","labels","validateLabels","validateDeployConfig","flags","body","createDeployBody","IDEMPOTENCY_KEY_CONSTRAINTS","API_PATHS","id","createDomainResource","name","data","status","createAccountResource","createTokenResource","token","Ship","options","validateCaller","ShipError","validateToken","ApiHttp","ctx","createDeploymentResource","input","opts","createDomainResource","createAccountResource","createTokenResource","API_PATHS","error","event","handler","headers","token","value","init_env","init_dist","import_zod","import_zod","CREDENTIAL_FIELDS","init_env","EnvConfigSchema","CREDENTIAL_FIELDS","ENV_VAR_BY_FIELD","SHIP_ENV","readEnvConfig","getENV","raw","error","issue","field","envVar","ShipError","init_dist","init_dist","init_deploy_paths","init_env","init_file_validation","init_junk","init_md5","init_security","pluralize","count","singular","plural","includeCount","word","init_node_files","Ship","options","getENV","ShipError","env","readEnvConfig","input","paths","p","processFilesForNode","node_default"]}
|