@shipstatic/ship 2.0.0-beta.8 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -2
- package/SKILL.md +7 -4
- package/dist/browser.d.ts +292 -87
- package/dist/browser.js +1 -1
- package/dist/browser.js.map +1 -1
- package/dist/cli.cjs +138 -26
- 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 +292 -87
- package/dist/index.d.ts +292 -87
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +12 -6
- package/dist/completions/ship.bash +0 -117
- package/dist/completions/ship.fish +0 -87
- package/dist/completions/ship.zsh +0 -126
package/dist/cli.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../node_modules/.pnpm/@shipstatic+types@2.5.0-beta.16/node_modules/@shipstatic/types/dist/index.js","../src/shared/lib/env.ts","../src/shared/lib/md5.ts","../src/shared/lib/path.ts","../src/shared/lib/deploy-paths.ts","../src/shared/lib/file-validation.ts","../src/shared/lib/junk.ts","../src/shared/lib/security.ts","../src/node/core/node-files.ts","../src/node/cli/index.ts","../src/node/core/config.ts","../src/shared/core/credential-schema.ts","../src/node/cli/completion.ts","../src/node/cli/utils.ts","../src/node/cli/config.ts","../src/node/index.ts","../src/shared/base-ship.ts","../src/shared/api/http.ts","../src/shared/events.ts","../src/shared/lib/validation.ts","../src/shared/resources.ts","../src/shared/lib/spa.ts","../src/node/core/deploy-body.ts","../src/node/cli/shiprc.ts","../src/node/cli/create-client.ts","../src/node/cli/error-handling.ts","../src/node/cli/formatters.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// I. CORE ENTITIES\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 * 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 PING: '/ping',\n SETUP: '/setup',\n SPA_CHECK: '/spa-check',\n UPLOAD: '/upload',\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 MAX_LENGTH: 256,\n /** How long a stored 201 stays replayable. */\n WINDOW_SECONDS: 24 * 60 * 60,\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 * Account plan constants\n */\nexport const AccountPlan = {\n FREE: 'free',\n STANDARD: 'standard',\n SPONSORED: 'sponsored',\n ENTERPRISE: 'enterprise',\n SUSPENDED: 'suspended',\n TERMINATING: 'terminating',\n TERMINATED: 'terminated',\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 /** Network/connection error. Client-side only — set by HTTP clients on fetch failure; never produced server-side. */\n Network: 'network_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.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 `Network` (which `isNetworkError` owns)\n * belongs 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 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 network: new Set([ErrorType.Network]),\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 * 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`, `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(...)`\n * - `TypeError` whose message mentions \"fetch\" → `ShipError.network(...)`\n * - Any other `Error` → `ShipError(Api, ...)` (no HTTP status — fetch never reached the server)\n * - Anything else (string, undefined, etc.) → `ShipError(Api, ...)`\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 if (cause instanceof Error) {\n if (cause.name === 'AbortError') {\n return ShipError.cancelled(`${op} was cancelled`);\n }\n if (cause instanceof TypeError && cause.message.includes('fetch')) {\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 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 // 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 BLOCKLIST\n// =============================================================================\n/**\n * Blocked file extensions — files that cannot be uploaded.\n *\n * We accept any file type by default and derive Content-Type from the\n * extension at serve time (via mime-db in the API worker). Unknown extensions\n * are served as `application/octet-stream` with `X-Content-Type-Options: nosniff`.\n *\n * The blocklist targets file types that pose direct security risks when hosted:\n * executables, disk images, malware vectors, dangerous scripts, and shortcuts.\n */\nexport const BLOCKED_EXTENSIONS = new Set([\n // Executables\n 'exe',\n 'msi',\n 'dll',\n 'scr',\n 'bat',\n 'cmd',\n 'com',\n 'pif',\n 'app',\n 'deb',\n 'rpm',\n // Installers\n 'pkg',\n 'mpkg',\n // Disk images\n 'dmg',\n 'iso',\n 'img',\n // Malware vectors\n 'cab',\n 'cpl',\n 'chm',\n // Dangerous scripts\n 'ps1',\n 'vbs',\n 'vbe',\n 'ws',\n 'wsf',\n 'wsc',\n 'wsh',\n 'reg',\n // Java\n 'jar',\n 'jnlp',\n // Mobile/browser packages\n 'apk',\n 'crx',\n // Shortcut/link\n 'lnk',\n 'inf',\n 'hta',\n]);\n/**\n * Check if a filename has a blocked extension.\n * Extracts the extension from the filename and checks against the blocklist.\n * Case-insensitive. Returns false for files without extensions.\n *\n * @example\n * isBlockedExtension('virus.exe') // true\n * isBlockedExtension('app.dmg') // true\n * isBlockedExtension('style.css') // false\n * isBlockedExtension('data.custom') // false\n * isBlockedExtension('README') // false\n */\nexport function isBlockedExtension(filename) {\n const dotIndex = filename.lastIndexOf('.');\n if (dotIndex === -1 || dotIndex === filename.length - 1)\n return false;\n const ext = filename.slice(dotIndex + 1).toLowerCase();\n return BLOCKED_EXTENSIONS.has(ext);\n}\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, CALLER),\n// the single dispatch over them (TokenKind, classifyToken), and the\n// delegated-access scopes (OAuthScope).\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 * 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-{64 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: 64,\n /** Total length of an API key including prefix (`PREFIX.length + HEX_LENGTH = 69`). */\n TOTAL_LENGTH: 69,\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-{64 hex chars}`).\n * Single source of truth used by validation utilities and auth middleware.\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: 64,\n /** Total length of a deploy token including prefix (`PREFIX.length + HEX_LENGTH = 71`). */\n TOTAL_LENGTH: 71,\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` and `DEPLOY_TOKEN` *are* `AuthMethod.API_KEY` and\n * `AuthMethod.TOKEN` — the equality is structural, so a classification flows\n * straight into an auth method and the pair can never drift. `OPAQUE` is any\n * other value — shape says nothing about it, so only a lookup can. Today the\n * server refuses every opaque bearer; the OAuth access-token population\n * resolves there when the authorization server ships.\n */\nexport const TokenKind = {\n API_KEY: AuthMethod.API_KEY,\n DEPLOY_TOKEN: AuthMethod.TOKEN,\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 return TokenKind.OPAQUE;\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 * 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 a client token of any population. Classifies by shape and applies\n * the matching format rules: `ship-` keys and `deploy-` deploy tokens are\n * validated strictly; opaque tokens (OAuth access tokens, future populations)\n * only need to be non-empty — their validity is the server's to decide.\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.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// PLATFORM CONSTANTS\n// =============================================================================\n/** Default API URL if not otherwise configured. */\nexport const DEFAULT_API = 'https://api.shipstatic.com';\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 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 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 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 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 isBlockedExtension,\n} from '@shipstatic/types';\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 // Pre-compute filename validation\n const nameValidation = file.name\n ? validateFileName(file.name)\n : { valid: false, reason: 'File name cannot be empty' };\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 } else if (!nameValidation.valid) {\n fileStatus = FILE_VALIDATION_STATUS.VALIDATION_FAILED;\n statusMessage = nameValidation.reason || 'Invalid file name';\n errors.push({\n file: file.name,\n message: statusMessage,\n });\n }\n\n // Blocked extension check\n else if (isBlockedExtension(file.name)) {\n fileStatus = FILE_VALIDATION_STATUS.VALIDATION_FAILED;\n statusMessage = `File extension not allowed: \"${file.name}\"`;\n errors.push({\n file: file.name,\n message: statusMessage,\n });\n }\n\n // File size validation\n else if (file.size > config.maxFileSize) {\n fileStatus = FILE_VALIDATION_STATUS.VALIDATION_FAILED;\n statusMessage = `File size (${formatFileSize(file.size)}) exceeds limit of ${formatFileSize(config.maxFileSize)}`;\n errors.push({\n file: file.name,\n message: statusMessage,\n });\n }\n\n // Total size validation\n else {\n totalSize += file.size;\n if (totalSize > config.maxTotalSize) {\n fileStatus = FILE_VALIDATION_STATUS.VALIDATION_FAILED;\n statusMessage = `Total size would exceed limit of ${formatFileSize(config.maxTotalSize)}`;\n errors.push({\n file: file.name,\n message: statusMessage,\n });\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 { isBlockedExtension, ShipError } from '@shipstatic/types';\nimport { validateFileName } from './file-validation.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 * Validate a deploy file's name and extension.\n * Rejects unsafe filenames (shell/URL-dangerous chars, reserved names)\n * and blocked file extensions (.exe, .msi, .dll, etc.).\n *\n * @param deployPath - The deployment path to validate\n * @param sourceIdentifier - Human-readable identifier for error messages\n * @throws {ShipError} If the filename is unsafe or extension is blocked\n */\nexport function validateDeployFile(deployPath: string, sourceIdentifier: string): void {\n const nameCheck = validateFileName(deployPath);\n if (!nameCheck.valid) {\n throw ShipError.business(nameCheck.reason || 'Invalid file name');\n }\n\n if (isBlockedExtension(deployPath)) {\n throw ShipError.business(`File extension not allowed: \"${sourceIdentifier}\"`);\n }\n}\n","/**\n * @file Node.js-specific file utilities for the Ship SDK.\n * Provides helpers for recursively discovering, filtering, and preparing files for deploy in Node.js.\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 { optimizeDeployPaths } from '../../shared/lib/deploy-paths.js';\nimport { getENV } from '../../shared/lib/env.js';\nimport { filterJunk } from '../../shared/lib/junk.js';\nimport { calculateMD5 } from '../../shared/lib/md5.js';\nimport { findCommonParent } from '../../shared/lib/path.js';\nimport { validateDeployFile, validateDeployPath } from '../../shared/lib/security.js';\nimport type { DeploymentOptions, StaticFile } from '../../shared/types.js';\n\n/**\n * Recursive function to walk directory and return all file paths.\n * Includes symlink loop protection to prevent infinite recursion.\n * @param dirPath - Directory path to traverse\n * @param visited - Set of already visited real paths (for cycle detection)\n * @returns Array of absolute file paths in the directory\n */\nfunction findAllFilePaths(dirPath: string, visited: Set<string> = new Set()): string[] {\n const results: string[] = [];\n\n // Resolve the real path to detect symlink cycles\n const realPath = fs.realpathSync(dirPath);\n if (visited.has(realPath)) {\n // Already visited this directory (symlink cycle) - skip to prevent infinite loop\n return results;\n }\n visited.add(realPath);\n\n const entries = fs.readdirSync(dirPath);\n\n for (const entry of entries) {\n const fullPath = path.join(dirPath, entry);\n const stats = fs.statSync(fullPath);\n\n if (stats.isDirectory()) {\n const subFiles = findAllFilePaths(fullPath, visited);\n results.push(...subFiles);\n } else if (stats.isFile()) {\n results.push(fullPath);\n }\n }\n\n return results;\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 {ShipClientError} 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 // Check input directories for unbuilt project markers before recursive walk\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 handled in the existing flatMap below\n }\n }\n\n // 1. Discover all unique, absolute file paths from the input list\n const absolutePaths = paths.flatMap((p) => {\n const absPath = path.resolve(p);\n try {\n const stats = fs.statSync(absPath);\n return stats.isDirectory() ? findAllFilePaths(absPath) : [absPath];\n } catch (_error) {\n throw ShipError.file(`Path does not exist: ${p}`, { filePath: p });\n }\n });\n const uniquePaths = [...new Set(absolutePaths)];\n\n // 2. Determine base path for content paths (from INPUT paths, not discovered files)\n const inputAbsolutePaths = paths.map((p) => path.resolve(p));\n const inputBasePath = findCommonParent(\n inputAbsolutePaths.map((p) => {\n try {\n const stats = fs.statSync(p);\n return stats.isDirectory() ? p : path.dirname(p);\n } catch {\n return path.dirname(p);\n }\n }),\n );\n\n // 3. Compute content paths (relative to upload root)\n const contentPaths = uniquePaths.map((absPath) => {\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 // 4. Optimize paths for deployment (strip common root, flatten)\n const deployFiles = optimizeDeployPaths(contentPaths, {\n flatten: options.pathDetect !== false,\n });\n const deployPaths = deployFiles.map((f) => f.path);\n\n // 5. Filter junk from deploy paths\n const filteredSet = new Set(filterJunk(deployPaths));\n if (filteredSet.size === 0) {\n return [];\n }\n\n // 6. Collect valid file pairs (absolute path for reading, deploy path for output)\n const validAbsPaths: string[] = [];\n const validDeployPaths: string[] = [];\n for (let i = 0; i < uniquePaths.length; i++) {\n if (filteredSet.has(deployPaths[i])) {\n validAbsPaths.push(uniquePaths[i]);\n validDeployPaths.push(deployPaths[i]);\n }\n }\n\n // 7. Process files into StaticFile objects\n const results: StaticFile[] = [];\n let totalSize = 0;\n if (!platformLimits) {\n throw ShipError.config(\n 'Platform limits not provided. processFilesForNode requires the limits ' +\n 'argument — pass `ship.getLimits()` result.',\n );\n }\n\n for (let i = 0; i < validAbsPaths.length; i++) {\n const filePath = validAbsPaths[i];\n const deployPath = validDeployPaths[i];\n\n try {\n // Security validation (shared with browser) — fail fast before any I/O\n validateDeployPath(deployPath, filePath);\n\n const stats = fs.statSync(filePath);\n\n // Skip empty files — R2 cannot store zero-byte objects\n if (stats.size === 0) {\n continue;\n }\n\n // Filename and extension validation (shared with browser)\n validateDeployFile(deployPath, filePath);\n\n // Validate file sizes\n if (stats.size > platformLimits.maxFileSize) {\n throw ShipError.business(\n `File ${filePath} is too large. Maximum allowed size is ${platformLimits.maxFileSize / (1024 * 1024)}MB.`,\n );\n }\n totalSize += stats.size;\n if (totalSize > platformLimits.maxTotalSize) {\n throw ShipError.business(\n `Total deploy size is too large. Maximum allowed is ${platformLimits.maxTotalSize / (1024 * 1024)}MB.`,\n );\n }\n\n const content = fs.readFileSync(filePath);\n const { md5 } = await calculateMD5(content);\n\n results.push({\n path: deployPath,\n content,\n size: content.length,\n md5,\n });\n } catch (error) {\n // Re-throw ShipError instances directly\n if (isShipError(error)) {\n throw error;\n }\n // Convert file system errors to ShipError with clear message\n const errorMessage = error instanceof Error ? error.message : String(error);\n throw ShipError.file(`Failed to read file \"${filePath}\": ${errorMessage}`, { filePath });\n }\n }\n\n // Final validation\n if (results.length > platformLimits.maxFilesCount) {\n throw ShipError.business(\n `Too many files to deploy. Maximum allowed is ${platformLimits.maxFilesCount} files.`,\n );\n }\n\n return results;\n}\n","/**\n * @file Main entry point for the Ship CLI.\n *\n * `buildProgram()` constructs the full Commander tree and is the seam the\n * in-process tests drive (`tests/node/cli/index.test.ts`). The bin execution\n * path at the bottom of this file is what `dist/cli.cjs` runs and is proven\n * by the child-process smoke tier. Commander instances are not reusable\n * across parses (option state accumulates), so every caller gets a fresh\n * tree.\n */\n\nimport { existsSync, readFileSync, statSync } from 'node:fs';\nimport * as path from 'node:path';\nimport {\n type Deployment,\n ErrorType,\n isShipError,\n ShipError,\n validateApiUrl,\n validateToken,\n} from '@shipstatic/types';\nimport { Command, CommanderError, Help, InvalidArgumentError } from 'commander';\nimport { bold, dim } from 'yoctocolors';\nimport { readEnvConfig } from '../core/config.js';\nimport type { Ship } from '../index.js';\nimport { installCompletion, uninstallCompletion } from './completion.js';\nimport { runConfig } from './config.js';\nimport { createClient, mergeCliConfig } from './create-client.js';\nimport { getUserMessage, toShipError } from './error-handling.js';\nimport { formatOutput, type OutputContext } from './formatters.js';\nimport { loadShipFile } from './shiprc.js';\nimport type {\n CLIResult,\n DeployCommandOptions,\n GlobalOptions,\n LabelOptions,\n ListCommandOptions,\n TokenCreateCommandOptions,\n} from './types.js';\nimport { error } from './utils.js';\n\n// Load package.json for version\nfunction loadPackageJson(): { version: string } {\n const paths = [\n path.resolve(__dirname, '../package.json'),\n path.resolve(__dirname, '../../package.json'),\n ];\n for (const p of paths) {\n try {\n return JSON.parse(readFileSync(p, 'utf-8'));\n } catch {}\n }\n return { version: '0.0.0' };\n}\n\nconst packageJson = loadPackageJson();\n\n/**\n * The CLI's FRONT PAGE — what `ship`, `ship --help`, and `ship help` render.\n * This hand-written overview is the product's front door and is kept exactly\n * as designed; do not swap it for generated output.\n *\n * Subcommand help is the other scope: `ship deployments --help` and\n * `ship help deployments` render Commander's NATIVE help, which knows that\n * command's exact usage, arguments, and options. The split lives in ONE\n * conditional (`configureHelp.formatHelp` in `buildProgram`): root → this\n * page, everything else → native. Riding the built-in machinery is what\n * makes every help route credential-free and lets `--help` beside a parse\n * error win (Commander processes it before argument errors).\n */\nfunction helpText(noColor?: boolean): string {\n const applyBold = (text: string) => (noColor ? text : bold(text));\n const applyDim = (text: string) => (noColor ? text : dim(text));\n const icon = (emoji: string) => (noColor ? '' : `${emoji} `);\n\n const output = `${applyBold('USAGE')}\n ship <path> ${icon('🚀')}Deploy static sites with simplicity\n\n${applyBold('COMMANDS')}\n ${icon('📦')}${applyBold('Deployments')}\n ship deployments list List deployments\n ship deployments upload <path> Upload deployment from file or directory\n ship deployments get <deployment> Show deployment information\n ship deployments set <deployment> Set deployment labels\n ship deployments delete <deployment> Delete deployment permanently\n\n ${icon('🌎')}${applyBold('Domains')}\n ship domains list List domains\n ship domains set <name> [deployment] Create domain, link to deployment, or update labels\n ship domains get <name> Show domain information\n ship domains validate <name> Check if domain name is valid and available\n ship domains records <name> Show required DNS records for domain setup\n ship domains dns <name> Look up DNS provider for a domain\n ship domains share <name> Get shareable DNS setup link\n ship domains verify <name> Trigger DNS verification for external domain\n ship domains delete <name> Delete domain permanently\n\n ${icon('🔑')}${applyBold('Tokens')}\n ship tokens list List deploy tokens\n ship tokens create Create a new deploy token\n ship tokens delete <token> Delete token permanently\n\n ${icon('⚙️')}${applyBold('Setup')}\n ship config Save your token\n ship whoami Get current account information\n\n ${icon('🛠️')}${applyBold('Completion')}\n ship completion install Install shell completion script\n ship completion uninstall Uninstall shell completion script\n\n${applyBold('FLAGS')}\n --token <token> Any ship token: API key (ship-…) or deploy token (deploy-…)\n --config <file> Custom config file path\n --label <label> Set label (repeatable, replaces all existing)\n --password <password> Password-protect this deployment\n --no-path-detect Disable automatic path optimization and flattening\n --no-spa-detect Disable automatic SPA detection and configuration\n --no-color Disable colored output\n --json Output results in JSON format\n -q, --quiet Output only the resource identifier\n -V, --version Show version information\n\n${applyBold('EXAMPLES')}\n ship ./dist\n ship domains set www.example.com happy-cat-abc1234.shipstatic.com\n ship ./dist -q | ship domains set www.example.com\n\n${applyDim('Please report any issues to https://github.com/shipstatic/ship/issues')}\n`;\n\n return output;\n}\n\n/**\n * A CLI grammar error — a command or flag this binary does not have.\n *\n * Deliberately STATUSLESS. `ErrorResponse.status` is documented \"(API\n * contexts)\", and no API ever sees `ship foo`, so a 400 here would be an HTTP\n * fact about an exchange that never happened. The dual-validation errors are\n * the opposite case and rightly DO carry 400 — a blocked extension or a bad\n * label is a rule the server enforces too, and the error must read the same\n * wherever it was caught. See CLAUDE.md, \"What a status means\".\n */\nconst usageError = (message: string) => new ShipError(ErrorType.Validation, message);\n\n/**\n * Collector function for Commander.js to accumulate repeated option values.\n * Used for --label flag that can be specified multiple times.\n */\nfunction collect(value: string, previous: string[] = []): string[] {\n return previous.concat([value]);\n}\n\n/**\n * Argument parser for integer option values (`--ttl`, `--limit`). A mangled\n * number must fail HERE with a clear message — a bare `parseInt` once turned\n * `--ttl abc` into `NaN` on the wire. Range rules stay server-side (the SDK\n * is a transparent pipe); only the not-a-number corruption is a client bug.\n */\nfunction parseInteger(value: string): number {\n const parsed = Number.parseInt(value, 10);\n if (Number.isNaN(parsed)) {\n throw new InvalidArgumentError('Not a number.');\n }\n return parsed;\n}\n\n/**\n * Merge label options from command and program levels.\n * Commander.js sometimes routes --label to program level instead of command level.\n */\nfunction mergeLabelOption(\n cmdOptions: LabelOptions | undefined,\n programOpts: LabelOptions | undefined,\n): string[] | undefined {\n const labels = cmdOptions?.label?.length ? cmdOptions.label : programOpts?.label;\n if (!labels?.length) return undefined;\n // Filter empty strings: --label '' means \"clear all labels\"\n const filtered = labels.filter((l) => l !== '');\n return filtered.length ? filtered : [];\n}\n\n/**\n * Merge password options from command and program levels.\n * Commander.js sometimes routes --password to program level instead of command level.\n *\n * An empty `--password ''` is forwarded to the SDK validator so the user\n * sees a clear length error rather than a silent drop. An empty\n * `SHIP_PASSWORD` is coerced to undefined, matching how SHIP_TOKEN\n * and SHIP_API_URL treat empty env vars (CI/Docker\n * often sets unset vars to \"\" — see core/config.ts).\n */\nfunction mergePasswordOption(\n cmdOptions: { password?: string } | undefined,\n programOpts: { password?: string } | undefined,\n): string | undefined {\n return cmdOptions?.password ?? programOpts?.password ?? (process.env.SHIP_PASSWORD || undefined);\n}\n\n/**\n * Process CLI options using Commander's built-in option merging.\n * Applies CLI-specific transformations (validation is done in preAction hook).\n */\nfunction processOptions(command: Command): GlobalOptions {\n const options = command.optsWithGlobals();\n\n // Convert Commander.js --no-color flag (color: false) to our convention (noColor: true)\n if (options.color === false) {\n options.noColor = true;\n }\n\n // Auto-suppress color when stdout is not a TTY (like grep --color=auto)\n // Also respect NO_COLOR convention (https://no-color.org/)\n // FORCE_COLOR overrides for CI environments that explicitly want color\n // FORCE_COLOR=0 means \"force no color\" per the convention (0=off, 1/2/3=on)\n const forceColor = !!process.env.FORCE_COLOR && process.env.FORCE_COLOR !== '0';\n if (!options.noColor && !forceColor) {\n if (!process.stdout.isTTY || process.env.NO_COLOR !== undefined) {\n options.noColor = true;\n }\n }\n\n return options as GlobalOptions;\n}\n\n/**\n * The credential the CLI actually resolved (flag > env > file). The error\n * path must diagnose with the same lens the client was built with — a user\n * whose `SHIP_TOKEN` or `.shiprc` token was rejected is credentialed, and\n * the anonymous-user hint would misdiagnose their failure. A config file\n * that fails to load counts as no file credential: that failure is already\n * the error being reported.\n */\nfunction resolveCliToken(flags: {\n config?: string;\n apiUrl?: string;\n token?: string;\n}): string | undefined {\n let file = {};\n try {\n file = loadShipFile(flags.config);\n } catch {}\n // Flags, env, and files only ever hold strings — provider functions exist\n // solely as constructor arguments, which the CLI never passes.\n const token = mergeCliConfig(flags, readEnvConfig(), file).token;\n return typeof token === 'string' ? token : undefined;\n}\n\n/** Spinner instance type from yocto-spinner */\ninterface Spinner {\n start(): Spinner;\n stop(): void;\n}\n\n/**\n * Common deploy logic used by both shortcut and explicit commands.\n */\nasync function performDeploy(\n client: Ship,\n deployPath: string,\n labels: string[] | undefined,\n password: string | undefined,\n cmdOptions: DeployCommandOptions | undefined,\n globalOptions: GlobalOptions,\n): Promise<Deployment> {\n if (!existsSync(deployPath)) {\n throw ShipError.file(`${deployPath} path does not exist`, { filePath: deployPath });\n }\n\n const stats = statSync(deployPath);\n if (!stats.isDirectory() && !stats.isFile()) {\n throw ShipError.file(`${deployPath} path must be a file or directory`, {\n filePath: deployPath,\n });\n }\n\n const deployOptions: {\n via: string;\n labels?: string[];\n password?: string;\n pathDetect?: boolean;\n spaDetect?: boolean;\n signal?: AbortSignal;\n } = { via: process.env.SHIP_VIA || 'cli' };\n\n // Handle labels\n if (labels !== undefined) deployOptions.labels = labels;\n\n // Empty password strings flow through to the SDK validator (clear length\n // error) instead of being silently dropped.\n if (password !== undefined) deployOptions.password = password;\n\n // Handle detection flags\n if (cmdOptions?.noPathDetect !== undefined) {\n deployOptions.pathDetect = !cmdOptions.noPathDetect;\n }\n if (cmdOptions?.noSpaDetect !== undefined) {\n deployOptions.spaDetect = !cmdOptions.noSpaDetect;\n }\n\n // Cancellation support\n const abortController = new AbortController();\n deployOptions.signal = abortController.signal;\n\n // Spinner (TTY only, not JSON, not --no-color)\n let spinner: Spinner | null = null;\n if (\n process.stdout.isTTY &&\n !globalOptions.json &&\n !globalOptions.quiet &&\n !globalOptions.noColor\n ) {\n const { default: yoctoSpinner } = await import('yocto-spinner');\n spinner = yoctoSpinner({ text: 'uploading…' }).start();\n }\n\n const sigintHandler = () => {\n abortController.abort();\n if (spinner) spinner.stop();\n process.exit(130);\n };\n process.on('SIGINT', sigintHandler);\n\n try {\n return await client.deployments.upload(deployPath, deployOptions);\n } finally {\n process.removeListener('SIGINT', sigintHandler);\n if (spinner) spinner.stop();\n }\n}\n\n/**\n * Build the complete `ship` command tree.\n *\n * Everything that closes over the program instance — the exit override, the\n * error handler, the action wrapper — lives inside this factory so that each\n * call returns a fully independent tree. The bin path below builds one and\n * parses `process.argv`; in-process tests build one per invocation.\n */\nexport function buildProgram(): Command {\n const program = new Command();\n\n // Override Commander.js error handling while preserving help/version behavior.\n //\n // Nothing in the tree calls `process.exit` — actions and this override set\n // `process.exitCode` or throw a `CommanderError`, and the bin path lets the\n // process end naturally once the event loop drains. That is Commander's own\n // recommended shape: `process.exit` can truncate stdout still buffered on a\n // pipe, and a tree that never exits mid-flight is drivable in-process.\n program\n .exitOverride((err) => {\n // Clean exits (help/version) — nothing to add, let the bin observe it.\n if (err.code === 'commander.help' || err.code === 'commander.version' || err.exitCode === 0) {\n throw err;\n }\n\n const globalOptions = processOptions(program);\n\n let message = err.message || 'unknown command error';\n message = message\n .replace(/^error: /, '')\n .replace(/\\n.*/, '')\n .replace(/\\.$/, '')\n .toLowerCase();\n\n // A Commander parse failure is a usage error — typed as such so\n // `--json` carries `validation_failed` rather than bare prose.\n error(usageError(message), globalOptions.json, globalOptions.noColor);\n\n if (!globalOptions.json) {\n program.outputHelp();\n }\n\n throw err;\n })\n .configureOutput({\n writeErr: (str) => {\n if (!str.startsWith('error:')) {\n process.stderr.write(str);\n }\n },\n writeOut: (str) => process.stdout.write(str),\n });\n\n /**\n * Handle unknown or missing subcommand for parent commands.\n * Shows scoped usage instead of full help — the user already knows the group.\n */\n function handleUnknownSubcommand(\n parentName: string,\n validSubcommands: string[],\n ): (...args: unknown[]) => void {\n return (...args: unknown[]) => {\n const globalOptions = processOptions(program);\n\n // Get the command object (last argument) - Commander passes it as the final arg\n const commandObj = args[args.length - 1] as { args?: string[] } | undefined;\n\n // Check if an unknown subcommand was provided\n if (commandObj?.args?.length) {\n const unknownArg = commandObj.args.find((arg) => !validSubcommands.includes(arg));\n if (unknownArg) {\n error(\n usageError(`unknown command '${unknownArg}'`),\n globalOptions.json,\n globalOptions.noColor,\n );\n }\n }\n\n if (!globalOptions.json) {\n console.log(`usage: ship ${parentName} <${validSubcommands.join('|')}>\\n`);\n }\n process.exitCode = 1;\n };\n }\n\n /**\n * Error handler - outputs errors consistently in text or JSON format.\n * Message formatting is delegated to the error-handling module.\n */\n function handleError(err: unknown, context?: OutputContext) {\n const opts = processOptions(program);\n const shipError = toShipError(err);\n\n // Text translates, JSON transmits: the machine channel emits the error's\n // own `ErrorResponse`, so `error` names the ErrorType the API produced;\n // the human channel gets the CLI's actionable rewording of it.\n if (opts.json) {\n error(shipError, true, opts.noColor);\n } else {\n // Only the auth branch consults the credential, and resolving it reads\n // `.shiprc` — so a local failure (`completion install`, `config`) must\n // not pay for it. Eager resolution made every error touch the disk.\n const message = getUserMessage(shipError, context, {\n token: shipError.isAuthError() ? resolveCliToken(program.opts()) : undefined,\n });\n error(message, false, opts.noColor);\n // Show help only for unknown command errors (user CLI mistake)\n if (shipError.type === ErrorType.Validation && message.includes('unknown command')) {\n program.outputHelp();\n }\n }\n\n process.exitCode = 1;\n }\n\n /**\n * Wrapper for CLI actions that handles errors and client creation consistently.\n * Reduces boilerplate while preserving context for error handling.\n */\n function withErrorHandling<T extends unknown[], R extends CLIResult>(\n handler: (client: Ship, options: GlobalOptions, ...args: T) => Promise<R>,\n context?: { operation?: string; resourceType?: string },\n ) {\n return async function (this: Command, ...args: T) {\n const globalOptions = processOptions(this);\n\n // Build context once for both output and error paths. It carries what\n // the command IS, never what the caller typed: the arguments are the\n // request, and every sentence the CLI writes about a result is composed\n // from the response.\n const resolvedContext: OutputContext = context\n ? {\n operation: context.operation,\n resourceType: context.resourceType,\n }\n : {};\n\n try {\n const { config, apiUrl, token } = program.opts();\n const client = createClient({ config, apiUrl, token });\n const result = await handler(client, globalOptions, ...args);\n formatOutput(result, resolvedContext, {\n json: globalOptions.json,\n quiet: globalOptions.quiet,\n noColor: globalOptions.noColor,\n });\n } catch (err) {\n handleError(err, resolvedContext);\n }\n };\n }\n\n program\n .name('ship')\n .description('🚀 Deploy static sites with simplicity')\n .version(packageJson.version, '-V, --version', 'Show version information')\n .option('--token <token>', 'Any ship token: API key (ship-…) or deploy token (deploy-…)')\n .option('--config <file>', 'Custom config file path')\n .option('--api-url <url>', 'API URL (for development)')\n .option('--json', 'Output results in JSON format')\n .option('-q, --quiet', 'Output only the resource identifier')\n .option('--no-color', 'Disable colored output')\n // Two help scopes, one machinery: the ROOT renders our hand-written\n // front page (helpText — the kept design); subcommands render\n // Commander's native scoped help, which knows their exact usage and\n // options. `Help.prototype` is the escape past our own override, which\n // subcommands inherit at registration.\n .helpOption('-h, --help', 'Display help for command')\n .helpCommand('help [command]', 'Display help for command')\n .configureHelp({\n formatHelp: (cmd, helper) =>\n cmd.parent === null\n ? `${helpText(processOptions(cmd).noColor)}\\n`\n : Help.prototype.formatHelp.call(helper, cmd, helper),\n })\n // Recorded opt-out of v13+'s excess-arguments error: the deploy shortcut\n // takes `[path]` with trailing noise tolerated, and unknown subcommands\n // must REACH handleUnknownSubcommand (as excess args) to get scoped\n // usage instead of Commander's generic \"too many arguments\".\n .allowExcessArguments();\n\n // Validate options early - before any action is executed\n program.hook('preAction', (thisCommand) => {\n const options = processOptions(thisCommand);\n\n try {\n if (options.token && typeof options.token === 'string') {\n validateToken(options.token);\n }\n\n if (options.apiUrl && typeof options.apiUrl === 'string') {\n validateApiUrl(options.apiUrl);\n }\n } catch (validationError) {\n if (isShipError(validationError)) {\n error(validationError, options.json, options.noColor);\n // Already reported — the bare CommanderError just carries the code.\n throw new CommanderError(1, 'ship.invalidOption', validationError.message);\n }\n throw validationError;\n }\n });\n\n // Ping command\n program\n .command('ping')\n .description('Check API connectivity')\n .action(\n withErrorHandling(\n // Reachability is the absence of a throw: `ping()` resolves the server\n // clock or raises a typed error, so there is nothing here to test. The\n // exit code follows — 0 on resolve, 1 through `handleError` — which is\n // the same composability `domains validate` relies on.\n (client: Ship, _options: GlobalOptions) => client.ping(),\n { operation: 'ping' },\n ),\n );\n\n // Whoami shortcut - alias for account get\n program\n .command('whoami')\n .description('Get current account information')\n .action(\n withErrorHandling((client: Ship, _options: GlobalOptions) => client.whoami(), {\n operation: 'get',\n resourceType: 'Account',\n }),\n );\n\n // Deployments commands\n const deploymentsCmd = program\n .command('deployments')\n .description('Manage deployments')\n .enablePositionalOptions()\n .action(handleUnknownSubcommand('deployments', ['list', 'upload', 'get', 'set', 'delete']));\n\n deploymentsCmd\n .command('list')\n .description('List deployments (one page — see --limit and --cursor)')\n .option('--limit <count>', 'Maximum number of results per page', parseInteger)\n .option('--cursor <cursor>', \"Continue from a previous page's cursor\")\n .action(\n withErrorHandling((client: Ship, _options: GlobalOptions, cmdOptions: ListCommandOptions) =>\n client.deployments.list(cmdOptions),\n ),\n );\n\n deploymentsCmd\n .command('upload <path>')\n .description('Upload deployment from file or directory')\n .passThroughOptions()\n .option('--label <label>', 'Label to add (can be repeated)', collect, [])\n .option('--password <password>', 'Password-protect this deployment')\n .option('--no-path-detect', 'Disable automatic path optimization and flattening')\n .option('--no-spa-detect', 'Disable automatic SPA detection and configuration')\n .action(\n withErrorHandling(\n (\n client: Ship,\n options: GlobalOptions,\n deployPath: string,\n cmdOptions: DeployCommandOptions,\n ) =>\n performDeploy(\n client,\n deployPath,\n mergeLabelOption(cmdOptions, program.opts() as LabelOptions),\n mergePasswordOption(cmdOptions, program.opts() as { password?: string }),\n cmdOptions,\n options,\n ),\n { operation: 'upload' },\n ),\n );\n\n deploymentsCmd\n .command('get <deployment>')\n .description('Show deployment information')\n .action(\n withErrorHandling(\n (client: Ship, _options: GlobalOptions, deployment: string) =>\n client.deployments.get(deployment),\n { operation: 'get', resourceType: 'Deployment' },\n ),\n );\n\n deploymentsCmd\n .command('set <deployment>')\n .description('Set deployment labels')\n .passThroughOptions()\n .option('--label <label>', 'Label to set (can be repeated)', collect, [])\n .action(\n withErrorHandling(\n async (\n client: Ship,\n _options: GlobalOptions,\n deployment: string,\n cmdOptions: LabelOptions,\n ) => {\n const labels = mergeLabelOption(cmdOptions, program.opts() as LabelOptions) || [];\n return client.deployments.set(deployment, { labels });\n },\n {\n operation: 'set',\n resourceType: 'Deployment',\n },\n ),\n );\n\n deploymentsCmd\n .command('delete <deployment>')\n .description('Delete deployment permanently')\n .action(\n withErrorHandling(\n (client: Ship, _options: GlobalOptions, deployment: string) =>\n client.deployments.delete(deployment),\n {\n operation: 'delete',\n resourceType: 'Deployment',\n },\n ),\n );\n\n // Domains commands\n const domainsCmd = program\n .command('domains')\n .description('Manage domains')\n .enablePositionalOptions()\n .action(\n handleUnknownSubcommand('domains', [\n 'list',\n 'get',\n 'set',\n 'validate',\n 'records',\n 'dns',\n 'share',\n 'verify',\n 'delete',\n ]),\n );\n\n domainsCmd\n .command('list')\n .description('List domains (one page — see --limit and --cursor)')\n .option('--limit <count>', 'Maximum number of results per page', parseInteger)\n .option('--cursor <cursor>', \"Continue from a previous page's cursor\")\n .action(\n withErrorHandling((client: Ship, _options: GlobalOptions, cmdOptions: ListCommandOptions) =>\n client.domains.list(cmdOptions),\n ),\n );\n\n domainsCmd\n .command('get <name>')\n .description('Show domain information')\n .action(\n withErrorHandling(\n (client: Ship, _options: GlobalOptions, name: string) => client.domains.get(name),\n { operation: 'get', resourceType: 'Domain' },\n ),\n );\n\n domainsCmd\n .command('validate <name>')\n .description('Check if domain name is valid and available')\n .action(\n withErrorHandling(\n async (client: Ship, _options: GlobalOptions, name: string) => {\n const result = await client.domains.validate(name);\n if (!result.valid) process.exitCode = 1;\n return result;\n },\n { operation: 'validate', resourceType: 'Domain' },\n ),\n );\n\n domainsCmd\n .command('verify <name>')\n .description('Trigger DNS verification for external domain')\n .action(\n withErrorHandling(\n (client: Ship, _options: GlobalOptions, name: string) => client.domains.verify(name),\n { operation: 'verify', resourceType: 'Domain' },\n ),\n );\n\n domainsCmd\n .command('records <name>')\n .description('Show required DNS records for domain setup')\n .action(\n withErrorHandling(\n (client: Ship, _options: GlobalOptions, name: string) => client.domains.records(name),\n { operation: 'records', resourceType: 'Domain' },\n ),\n );\n\n domainsCmd\n .command('dns <name>')\n .description('Look up DNS provider for a domain')\n .action(\n withErrorHandling(\n (client: Ship, _options: GlobalOptions, name: string) => client.domains.dns(name),\n { operation: 'dns', resourceType: 'Domain' },\n ),\n );\n\n domainsCmd\n .command('share <name>')\n .description('Get shareable DNS setup link')\n .action(\n withErrorHandling(\n (client: Ship, _options: GlobalOptions, name: string) => client.domains.share(name),\n { operation: 'share', resourceType: 'Domain' },\n ),\n );\n\n domainsCmd\n .command('set <name> [deployment]')\n .description('Create domain, link to deployment, or update labels')\n .passThroughOptions()\n .option('--label <label>', 'Label to set (can be repeated)', collect, [])\n .action(\n withErrorHandling(\n async (\n client: Ship,\n _options: GlobalOptions,\n name: string,\n deployment: string | undefined,\n cmdOptions: LabelOptions,\n ) => {\n // Read deployment from stdin when piped (e.g., ship ./dist -q | ship domains set mysite.com)\n if (!deployment && !process.stdin.isTTY) {\n deployment = await new Promise<string | undefined>((resolve) => {\n let data = '';\n process.stdin.on('data', (chunk) => (data += chunk));\n process.stdin.on('end', () => resolve(data.trim() || undefined));\n });\n }\n\n const labels = mergeLabelOption(cmdOptions, program.opts() as LabelOptions);\n\n const setOptions: { deployment?: string; labels?: string[] } = {};\n if (deployment) setOptions.deployment = deployment;\n if (labels !== undefined) setOptions.labels = labels;\n\n // SDK returns DomainSetResult (Domain + isCreate derived from HTTP 201/200) —\n // the resource interface in @shipstatic/types declares this directly, no cast needed.\n const result = await client.domains.set(name, setOptions);\n\n // Enrich with DNS info for new external domains (pure formatter will display it)\n if (result.isCreate && name.includes('.')) {\n try {\n const [records, share] = await Promise.all([\n client.domains.records(name),\n client.domains.share(name),\n ]);\n return {\n ...result,\n _dnsRecords: records.records,\n _shareHash: share.hash,\n };\n } catch {\n // Graceful degradation - return without DNS info\n }\n }\n return result;\n },\n { operation: 'set', resourceType: 'Domain' },\n ),\n );\n\n domainsCmd\n .command('delete <name>')\n .description('Delete domain permanently')\n .action(\n withErrorHandling(\n (client: Ship, _options: GlobalOptions, name: string) => client.domains.delete(name),\n { operation: 'delete', resourceType: 'Domain' },\n ),\n );\n\n // Tokens commands\n const tokensCmd = program\n .command('tokens')\n .description('Manage deploy tokens')\n .enablePositionalOptions()\n .action(handleUnknownSubcommand('tokens', ['list', 'create', 'delete']));\n\n tokensCmd\n .command('list')\n .description('List deploy tokens (one page — see --limit and --cursor)')\n .option('--limit <count>', 'Maximum number of results per page', parseInteger)\n .option('--cursor <cursor>', \"Continue from a previous page's cursor\")\n .action(\n withErrorHandling((client: Ship, _options: GlobalOptions, cmdOptions: ListCommandOptions) =>\n client.tokens.list(cmdOptions),\n ),\n );\n\n tokensCmd\n .command('create')\n .description('Create a new deploy token')\n .option('--ttl <seconds>', 'Time to live in seconds (default: never expires)', parseInteger)\n .option('--label <label>', 'Label to set (can be repeated)', collect, [])\n .action(\n withErrorHandling(\n (client: Ship, _options: GlobalOptions, cmdOptions: TokenCreateCommandOptions) => {\n const options: { ttl?: number; labels?: string[] } = {};\n if (cmdOptions?.ttl !== undefined) options.ttl = cmdOptions.ttl;\n const labels = mergeLabelOption(cmdOptions, program.opts() as LabelOptions);\n if (labels !== undefined) options.labels = labels;\n return client.tokens.create(options);\n },\n { operation: 'create', resourceType: 'Token' },\n ),\n );\n\n tokensCmd\n .command('get <token>')\n .description('Show one deploy token')\n .action(\n withErrorHandling((client: Ship, _options: GlobalOptions, token: string) =>\n client.tokens.get(token),\n ),\n );\n\n tokensCmd\n .command('delete <token>')\n .description('Delete token permanently')\n .action(\n withErrorHandling(\n (client: Ship, _options: GlobalOptions, token: string) => client.tokens.delete(token),\n { operation: 'delete', resourceType: 'Token' },\n ),\n );\n\n // Account commands\n const accountCmd = program\n .command('account')\n .description('Manage account')\n .action(handleUnknownSubcommand('account', ['get']));\n\n accountCmd\n .command('get')\n .description('Show account information')\n .action(\n withErrorHandling((client: Ship, _options: GlobalOptions) => client.whoami(), {\n operation: 'get',\n resourceType: 'Account',\n }),\n );\n\n // Completion commands\n const completionCmd = program\n .command('completion')\n .description('Setup shell completion')\n .action(handleUnknownSubcommand('completion', ['install', 'uninstall']));\n\n completionCmd\n .command('install')\n .description('Install shell completion script')\n .action(() => {\n const options = processOptions(program);\n const scriptDir = path.resolve(__dirname, 'completions');\n try {\n installCompletion(scriptDir, { json: options.json, noColor: options.noColor });\n } catch (err) {\n handleError(err);\n }\n });\n\n completionCmd\n .command('uninstall')\n .description('Uninstall shell completion script')\n .action(() => {\n const options = processOptions(program);\n try {\n uninstallCompletion({ json: options.json, noColor: options.noColor });\n } catch (err) {\n handleError(err);\n }\n });\n\n // Config command\n program\n .command('config')\n .description('Save your token')\n .action(async () => {\n const options = processOptions(program);\n try {\n await runConfig({ noColor: options.noColor, json: options.json });\n } catch (err) {\n handleError(err);\n }\n });\n\n // Deploy shortcut as default action. Two cases are answered BEFORE the\n // wrapped handler, so no client is created and no config file is resolved\n // for them: no argument (that is help, not a deploy) and an argument that\n // is a mistyped COMMAND rather than a path — a user with a broken config\n // file must still be told \"unknown command\", not handed a config error.\n const deployShortcut = withErrorHandling(\n (client: Ship, options: GlobalOptions, deployPath: string, cmdOptions?: DeployCommandOptions) =>\n performDeploy(\n client,\n deployPath,\n mergeLabelOption(cmdOptions, program.opts() as LabelOptions),\n mergePasswordOption(cmdOptions, program.opts() as { password?: string }),\n cmdOptions,\n options,\n ),\n { operation: 'upload' },\n );\n\n program\n .argument('[path]', 'Path to deploy')\n .option('--label <label>', 'Label to add (can be repeated)', collect, [])\n .option('--password <password>', 'Password-protect this deployment')\n .option('--no-path-detect', 'Disable automatic path optimization and flattening')\n .option('--no-spa-detect', 'Disable automatic SPA detection and configuration')\n .action(async function (this: Command, deployPath?: string, cmdOptions?: DeployCommandOptions) {\n if (!deployPath) {\n this.outputHelp();\n return;\n }\n\n // A nonexistent path with no separators, extension, or `~` reads as a\n // mistyped command (\"dist\", \"build\" and friends exist, so they still\n // deploy). Everything else falls through to performDeploy, whose\n // \"path does not exist\" error names the path.\n if (!existsSync(deployPath)) {\n const looksLikeCommand =\n !deployPath.includes('/') &&\n !deployPath.includes('\\\\') &&\n !deployPath.includes('.') &&\n !deployPath.startsWith('~');\n if (looksLikeCommand) {\n handleError(usageError(`unknown command '${deployPath}'`), {\n operation: 'upload',\n });\n return;\n }\n }\n\n return deployShortcut.call(this, deployPath, cmdOptions);\n });\n\n return program;\n}\n\n/**\n * Simple completion handler - no self-invocation, just static completions\n */\nfunction handleCompletion() {\n const isFish = process.argv.includes('--compfish');\n\n const completions = [\n 'ping',\n 'whoami',\n 'deployments',\n 'domains',\n 'tokens',\n 'account',\n 'config',\n 'completion',\n ];\n console.log(completions.join(isFish ? '\\n' : ' '));\n}\n\n// Bin execution — this is what `dist/cli.cjs` runs. Exercised by the\n// child-process smoke tier, never in-process (tests drive `buildProgram()`).\n// No `process.exit` anywhere on this path: outcomes land in\n// `process.exitCode` and the process ends when the event loop drains, so\n// buffered stdout is never truncated on a pipe.\nif (process.env.NODE_ENV !== 'test') {\n const wantsCompletion =\n process.argv.includes('--compbash') ||\n process.argv.includes('--compzsh') ||\n process.argv.includes('--compfish');\n\n if (wantsCompletion) {\n handleCompletion();\n } else {\n buildProgram()\n .parseAsync(process.argv)\n .catch((err: unknown) => {\n // The exit override reports errors before throwing; the bare\n // CommanderError that reaches here only carries the exit code\n // (help/version legitimately carry 0).\n if (err instanceof CommanderError) {\n process.exitCode = err.exitCode;\n return;\n }\n throw err;\n });\n }\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, n8n, GitHub Action) construct\n * `new Ship({})` for anonymous deployments without leaking the host developer's\n * personal credentials.\n */\n\nimport { 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_API_URL',\n token: 'SHIP_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_API_URL || undefined,\n token: process.env.SHIP_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 * Shell completion install/uninstall logic.\n * Handles bash, zsh, and fish shells.\n *\n * **Failures throw; the caller's error boundary reports them.** These commands\n * make no request, so they cannot use `withErrorHandling` — that builds a\n * `Ship`, which resolves credentials — and they take the same shape `config`\n * does instead: the action wraps the call and hands anything thrown to\n * `handleError`. That is what puts a completion failure on the same footing as\n * every other one: one writer, one exit code. Reporting inline (as this module\n * did until 2026-07-29) printed `[error] …` and then exited **0**, so\n * `ship completion install && …` proceeded after a failure.\n *\n * **Two error types, because no request means no status.** The client-only\n * types (`Network`, `Cancelled`, `File`, `Config`) are exactly the statusless\n * ones in `@shipstatic/types` — a status is an HTTP fact, and nothing here\n * speaks HTTP. So the rule is: an fs call that threw is `File`; everything else\n * is a statement about the user's shell setup, which is `Config`. `Validation`\n * and `Business` are deliberately absent — both stamp a 400 on a failure that\n * never made a request, which is a plausible lie rather than an obvious one.\n * Neither command takes an argument, so there is no input here to validate.\n */\nimport * as fs from 'node:fs';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\nimport { isShipError, ShipError } from '@shipstatic/types';\nimport { info, success, warn } from './utils.js';\n\nexport interface CompletionOptions {\n json?: boolean;\n noColor?: boolean;\n}\n\n/**\n * Detect current shell from environment\n */\nfunction detectShell(): 'bash' | 'zsh' | 'fish' | null {\n const shell = process.env.SHELL || '';\n if (shell.includes('bash')) return 'bash';\n if (shell.includes('zsh')) return 'zsh';\n if (shell.includes('fish')) return 'fish';\n return null;\n}\n\n/**\n * Get shell-specific paths\n */\nfunction getShellPaths(shell: 'bash' | 'zsh' | 'fish', homeDir: string) {\n switch (shell) {\n case 'bash':\n return {\n completionFile: path.join(homeDir, '.ship_completion.bash'),\n profileFile: path.join(homeDir, '.bash_profile'),\n scriptName: 'ship.bash',\n };\n case 'zsh':\n return {\n completionFile: path.join(homeDir, '.ship_completion.zsh'),\n profileFile: path.join(homeDir, '.zshrc'),\n scriptName: 'ship.zsh',\n };\n case 'fish':\n return {\n completionFile: path.join(homeDir, '.config/fish/completions/ship.fish'),\n profileFile: null, // fish doesn't need profile sourcing\n scriptName: 'ship.fish',\n };\n }\n}\n\n/**\n * Install shell completion script\n */\nexport function installCompletion(scriptDir: string, options: CompletionOptions = {}): void {\n const { json, noColor } = options;\n const shell = detectShell();\n const homeDir = os.homedir();\n\n if (!shell) {\n throw ShipError.config(`unsupported shell: ${process.env.SHELL}. supported: bash, zsh, fish`);\n }\n\n const paths = getShellPaths(shell, homeDir);\n const sourceScript = path.join(scriptDir, paths.scriptName);\n\n try {\n // Fish has a different installation pattern\n if (shell === 'fish') {\n const fishDir = path.dirname(paths.completionFile);\n if (!fs.existsSync(fishDir)) {\n fs.mkdirSync(fishDir, { recursive: true });\n }\n fs.copyFileSync(sourceScript, paths.completionFile);\n success('fish completion installed successfully', json, noColor);\n info('please restart your shell to apply the changes', json, noColor);\n return;\n }\n\n // Bash and zsh: copy script and add sourcing to profile.\n // The block is newline-TERMINATED so uninstall can restore the profile\n // byte-for-byte: without it, an appended block swallowed the file's\n // original trailing newline and the round trip was lossy.\n fs.copyFileSync(sourceScript, paths.completionFile);\n const sourceLine = `# ship\\nsource '${paths.completionFile}'\\n# ship end\\n`;\n\n if (paths.profileFile) {\n if (fs.existsSync(paths.profileFile)) {\n const content = fs.readFileSync(paths.profileFile, 'utf-8');\n if (!content.includes('# ship') || !content.includes('# ship end')) {\n const prefix = content.length > 0 && !content.endsWith('\\n') ? '\\n' : '';\n fs.appendFileSync(paths.profileFile, prefix + sourceLine);\n }\n } else {\n fs.writeFileSync(paths.profileFile, sourceLine);\n }\n\n success(`completion script installed for ${shell}`, json, noColor);\n warn(`run \"source ${paths.profileFile}\" or restart your shell`, json, noColor);\n }\n } catch (e) {\n if (isShipError(e)) throw e;\n const message = e instanceof Error ? e.message : String(e);\n throw ShipError.file(`could not install completion script: ${message}`);\n }\n}\n\n/**\n * Uninstall shell completion script\n */\nexport function uninstallCompletion(options: CompletionOptions = {}): void {\n const { json, noColor } = options;\n const shell = detectShell();\n const homeDir = os.homedir();\n\n if (!shell) {\n throw ShipError.config(`unsupported shell: ${process.env.SHELL}. supported: bash, zsh, fish`);\n }\n\n const paths = getShellPaths(shell, homeDir);\n\n try {\n // Fish: just remove the file\n if (shell === 'fish') {\n if (fs.existsSync(paths.completionFile)) {\n fs.unlinkSync(paths.completionFile);\n success('fish completion uninstalled successfully', json, noColor);\n } else {\n warn('fish completion was not installed', json, noColor);\n }\n info('please restart your shell to apply the changes', json, noColor);\n return;\n }\n\n // Bash and zsh: remove file and clean profile\n if (fs.existsSync(paths.completionFile)) {\n fs.unlinkSync(paths.completionFile);\n }\n\n if (!paths.profileFile) return;\n\n if (!fs.existsSync(paths.profileFile)) {\n throw ShipError.config('profile file not found');\n }\n\n const content = fs.readFileSync(paths.profileFile, 'utf-8');\n const lines = content.split('\\n');\n\n // Remove ship block (between \"# ship\" and \"# ship end\")\n const filtered: string[] = [];\n let i = 0;\n let removed = false;\n\n while (i < lines.length) {\n if (lines[i].trim() === '# ship') {\n removed = true;\n i++;\n while (i < lines.length && lines[i].trim() !== '# ship end') i++;\n if (i < lines.length) i++; // skip \"# ship end\"\n } else {\n filtered.push(lines[i]);\n i++;\n }\n }\n\n if (removed) {\n // `split('\\n')` / `join('\\n')` round-trips newlines exactly — a trailing\n // newline survives as a final empty element. Re-appending one here (as an\n // earlier revision did) double-counts it and leaves a stray blank line.\n fs.writeFileSync(paths.profileFile, filtered.join('\\n'));\n success(`completion script uninstalled for ${shell}`, json, noColor);\n warn(`run \"source ${paths.profileFile}\" or restart your shell`, json, noColor);\n } else {\n throw ShipError.config('completion was not found in profile');\n }\n } catch (e) {\n if (isShipError(e)) throw e;\n const message = e instanceof Error ? e.message : String(e);\n throw ShipError.file(`could not uninstall completion script: ${message}`);\n }\n}\n","/**\n * Simple CLI utilities following \"impossible simplicity\" mantra\n */\nimport type { ShipError } from '@shipstatic/types';\nimport columnify from 'columnify';\nimport { blue, dim, green, hidden, inverse, red, yellow } from 'yoctocolors';\n\nconst INTERNAL_FIELDS = ['isCreate', 'claim'];\n\nconst applyColor = (colorFn: (text: string) => string, text: string, noColor?: boolean): string => {\n return noColor ? text : colorFn(text);\n};\n\n/** A non-null, non-array object — the shape that needs key-value rendering. */\nconst isPlainObject = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\n// Wire messages are displayed verbatim — identifiers, paths, and acronyms\n// must survive formatting. The CLI's lowercase opening applies to the leading\n// sentence word only, and only when it's an ordinary capitalized word (never\n// \"DNS\", a quoted key, or a path).\nconst decapitalize = (msg: string): string =>\n /^[A-Z][a-z]/.test(msg) ? msg.charAt(0).toLowerCase() + msg.slice(1) : msg;\n\n/**\n * The CLI's message envelopes. Each carries a sentence the CLI composed, so\n * `--json` wraps it as `{ kind: message }` — none of the three has a wire\n * counterpart to defer to. Failures do; see `error` below, which is shaped by\n * the wire instead and is deliberately not one of these.\n */\nexport const success = (msg: string, json?: boolean, noColor?: boolean) => {\n if (json) {\n console.log(`${JSON.stringify({ success: msg }, null, 2)}\\n`);\n } else {\n console.log(`${applyColor(green, decapitalize(msg).replace(/\\.$/, ''), noColor)}\\n`);\n }\n};\n\nexport const warn = (msg: string, json?: boolean, noColor?: boolean) => {\n if (json) {\n console.log(`${JSON.stringify({ warning: msg }, null, 2)}\\n`);\n } else {\n const warnPrefix = applyColor(\n (text) => inverse(yellow(text)),\n `${applyColor(hidden, '[', noColor)}warning${applyColor(hidden, ']', noColor)}`,\n noColor,\n );\n const warnMsg = applyColor(yellow, decapitalize(msg).replace(/\\.$/, ''), noColor);\n console.log(`${warnPrefix} ${warnMsg}\\n`);\n }\n};\n\nexport const info = (msg: string, json?: boolean, noColor?: boolean) => {\n if (json) {\n console.log(`${JSON.stringify({ info: msg }, null, 2)}\\n`);\n } else {\n const infoPrefix = applyColor(\n (text) => inverse(blue(text)),\n `${applyColor(hidden, '[', noColor)}info${applyColor(hidden, ']', noColor)}`,\n noColor,\n );\n const infoMsg = applyColor(blue, decapitalize(msg).replace(/\\.$/, ''), noColor);\n console.log(`${infoPrefix} ${infoMsg}\\n`);\n }\n};\n\n/**\n * Emit a failure on stderr. **Text translates, JSON transmits.**\n *\n * The two channels carry different words on purpose. Text renders the string\n * it is handed — the CLI's own actionable copy, composed by `getUserMessage`.\n * JSON emits the platform's `ErrorResponse` verbatim, so `error` names the\n * `ErrorType` exactly as the API and the SDK produce it and a script branches\n * on the same typed field everywhere.\n *\n * A bare string is therefore accepted for the text channel only: a message\n * with no type has no wire shape to emit, and the overloads make writing one\n * into `--json` a compile error rather than a convention.\n */\nexport function error(err: ShipError, json?: boolean, noColor?: boolean): void;\nexport function error(msg: string, json: false | undefined, noColor?: boolean): void;\nexport function error(err: ShipError | string, json?: boolean, noColor?: boolean): void {\n if (json && typeof err !== 'string') {\n console.error(`${JSON.stringify(err.toResponse(), null, 2)}\\n`);\n return;\n }\n const errorPrefix = applyColor(\n (text) => inverse(red(text)),\n `${applyColor(hidden, '[', noColor)}error${applyColor(hidden, ']', noColor)}`,\n noColor,\n );\n const message = typeof err === 'string' ? err : err.message;\n const errorMsg = applyColor(red, decapitalize(message).replace(/\\.$/, ''), noColor);\n console.error(`${errorPrefix} ${errorMsg}\\n`);\n}\n\n/**\n * Format unix timestamp to ISO 8601 string without milliseconds, or return '-' if not provided\n */\nexport const formatTimestamp = (\n timestamp?: number,\n context: 'table' | 'details' = 'details',\n noColor?: boolean,\n): string => {\n if (timestamp === undefined || timestamp === null || timestamp === 0) {\n return '-';\n }\n\n const isoString = new Date(timestamp * 1000).toISOString().replace(/\\.\\d{3}Z$/, 'Z');\n\n // Hide the T and Z characters only in table/list views for cleaner appearance\n if (context === 'table') {\n return isoString\n .replace(/T/, applyColor(hidden, 'T', noColor))\n .replace(/Z$/, applyColor(hidden, 'Z', noColor));\n }\n\n return isoString;\n};\n\n/**\n * Format value for display.\n * Handles timestamps, file sizes, and boolean configs with special formatting.\n */\nconst formatValue = (\n key: string,\n value: unknown,\n context: 'table' | 'details' = 'details',\n noColor?: boolean,\n): string => {\n if (value === null || (Array.isArray(value) && value.length === 0)) return '-';\n if (\n typeof value === 'number' &&\n (key === 'created' ||\n key === 'activated' ||\n key === 'expires' ||\n key === 'linked' ||\n key === 'grace' ||\n // `used` is unix seconds on both Token and Account (the API key's\n // last-use instant) — without this it renders as a raw integer.\n key === 'used')\n ) {\n return formatTimestamp(value, context, noColor);\n }\n if (key === 'size' && typeof value === 'number') {\n const mb = value / (1024 * 1024);\n return mb >= 1 ? `${mb.toFixed(1)}Mb` : `${(value / 1024).toFixed(1)}Kb`;\n }\n // Boolean signal columns (config, password) render as yes/no in details.\n if (key === 'config' || key === 'password') {\n if (typeof value === 'boolean') return value ? 'yes' : 'no';\n if (typeof value === 'number') return value === 1 ? 'yes' : 'no';\n }\n // Table and details are flat key-value surfaces, so a nested object\n // flattens to inline `k=v` pairs rather than nesting a second layout\n // inside a cell. Structural, not per-key: values recurse through\n // formatValue, so a nested timestamp or size formats like any other.\n if (isPlainObject(value)) {\n const pairs = Object.entries(value);\n if (pairs.length === 0) return '-';\n return pairs.map(([k, v]) => `${k}=${formatValue(k, v, context, noColor)}`).join(', ');\n }\n return String(value);\n};\n\n/**\n * Format data as table with specified columns for easy parsing.\n * @param data - Array of objects to display as table rows\n * @param columns - Optional column order (defaults to first item's keys)\n * @param noColor - Disable colors\n * @param headerMap - Optional mapping of property names to display headers\n */\nexport const formatTable = (\n data: object[],\n columns?: string[],\n noColor?: boolean,\n headerMap?: Record<string, string>,\n): string => {\n if (!data || data.length === 0) return '';\n\n // Get column order from first item (preserves API order) or use provided columns\n const firstItem = data[0] as Record<string, unknown>;\n const columnOrder =\n columns ||\n Object.keys(firstItem).filter(\n (key) => firstItem[key] !== undefined && !INTERNAL_FIELDS.includes(key),\n );\n\n // Transform data preserving column order\n const transformedData = data.map((item) => {\n const record = item as Record<string, unknown>;\n const transformed: Record<string, string> = {};\n columnOrder.forEach((col) => {\n if (col in record && record[col] !== undefined) {\n transformed[col] = formatValue(col, record[col], 'table', noColor);\n }\n });\n return transformed;\n });\n\n const output = columnify(transformedData, {\n columnSplitter: ' ',\n columns: columnOrder,\n config: columnOrder.reduce<Record<string, { headingTransform: (h: string) => string }>>(\n (config, col) => {\n config[col] = {\n headingTransform: (heading: string) =>\n applyColor(dim, headerMap?.[heading] || heading, noColor),\n };\n return config;\n },\n {},\n ),\n });\n\n // Clean output: remove null bytes and ensure clean spacing\n return `${output\n .split('\\n')\n .map(\n (line: string) =>\n line\n .replace(/\\0/g, '') // Remove any null bytes\n .replace(/\\s+$/, ''), // Remove trailing spaces\n )\n .join('\\n')}\\n`;\n};\n\n/**\n * Format object properties as key-value pairs with space separation for readability.\n * @param obj - Object to display as key-value pairs\n * @param noColor - Disable colors\n */\nexport const formatDetails = (obj: object, noColor?: boolean): string => {\n const entries = (Object.entries(obj) as [string, unknown][]).filter(([key, value]) => {\n if (INTERNAL_FIELDS.includes(key)) return false;\n return value !== undefined;\n });\n\n if (entries.length === 0) return '';\n\n // Transform to columnify format while preserving order\n const data = entries.map(([key, value]) => ({\n property: `${key}:`,\n value: formatValue(key, value, 'details', noColor),\n }));\n\n const output = columnify(data, {\n columnSplitter: ' ',\n showHeaders: false,\n config: {\n property: {\n dataTransform: (value: string) => applyColor(dim, value, noColor),\n },\n },\n });\n\n // Clean output: remove null bytes and ensure clean spacing\n return `${output\n .split('\\n')\n .map((line: string) => line.replace(/\\0/g, '')) // Remove any null bytes\n .join('\\n')}\\n`;\n};\n","/**\n * @file Interactive config file creation for `ship config`.\n * Asks for a token, merges into existing ~/.shiprc, preserves all other fields.\n * Uses Node.js built-in readline/promises — zero additional dependencies.\n */\n\nimport { chmodSync, existsSync, readFileSync, writeFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { createInterface } from 'node:readline/promises';\nimport { DEFAULT_API, validateToken } from '@shipstatic/types';\nimport { dim, green } from 'yoctocolors';\n\n/** Path to the global config file */\nconst CONFIG_PATH = join(homedir(), '.shiprc');\n\n/**\n * Mask a token for display: ship-a1b2...c3d4. A token too short to keep a\n * useful prefix + suffix is masked entirely — never printed verbatim.\n */\nfunction maskToken(token: string): string {\n if (token.length < 13) return '...';\n return `${token.slice(0, 9)}...${token.slice(-4)}`;\n}\n\n/**\n * Read existing config file, preserving all fields.\n * Returns empty object if file doesn't exist or is invalid.\n */\nfunction readExistingConfig(): Record<string, unknown> {\n try {\n if (!existsSync(CONFIG_PATH)) return {};\n return JSON.parse(readFileSync(CONFIG_PATH, 'utf-8'));\n } catch {\n return {};\n }\n}\n\n/**\n * Run the interactive config flow.\n * Asks for a token, merges into existing config, writes ~/.shiprc.\n */\nexport async function runConfig(\n options: { noColor?: boolean; json?: boolean } = {},\n): Promise<void> {\n const { noColor, json } = options;\n const applyDim = (text: string) => (noColor ? text : dim(text));\n const applyGreen = (text: string) => (noColor ? text : green(text));\n\n // JSON mode: show current config status\n if (json) {\n const existing = readExistingConfig();\n const token = typeof existing.token === 'string' ? existing.token : undefined;\n const apiUrl = typeof existing.apiUrl === 'string' ? existing.apiUrl : undefined;\n console.log(\n `${JSON.stringify(\n {\n path: CONFIG_PATH,\n exists: existsSync(CONFIG_PATH),\n ...(token ? { token: maskToken(token) } : {}),\n ...(apiUrl && apiUrl !== DEFAULT_API ? { apiUrl } : {}),\n },\n null,\n 2,\n )}\\n`,\n );\n return;\n }\n\n const existing = readExistingConfig();\n const existingToken = typeof existing.token === 'string' ? existing.token : undefined;\n\n const rl = createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n console.log('');\n console.log(` ${applyDim('Create a free API key at')} https://my.shipstatic.com/api-key`);\n console.log('');\n\n const prompt = existingToken ? ` Token (${applyDim(maskToken(existingToken))}): ` : ' Token: ';\n\n let input: string;\n try {\n input = (await rl.question(prompt)).trim();\n } finally {\n rl.close();\n }\n\n if (input) {\n validateToken(input);\n existing.token = input;\n }\n\n // The file holds a credential: owner-only, like ~/.netrc. `mode` only\n // applies on creation, so chmod repairs files written before this rule.\n writeFileSync(CONFIG_PATH, `${JSON.stringify(existing, null, 2)}\\n`, { mode: 0o600 });\n chmodSync(CONFIG_PATH, 0o600);\n console.log(`\\n ${applyGreen('saved to')} ${applyDim(CONFIG_PATH)}\\n`);\n}\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 DeployBodyCreator,\n DeployInput,\n DeploymentCreateResponse,\n DeploymentOptions,\n ShipClientOptions,\n StaticFile,\n} from '../shared/types.js';\nimport { readEnvConfig } from './core/config.js';\nimport { createDeployBody } from './core/deploy-body.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-xxxx' });\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 protected getDeployBodyCreator(): DeployBodyCreator {\n return createDeployBody;\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 — that's lazy and runs on\n * first API call via `ensureInitialized()`.\n *\n * Subclasses only override what genuinely differs per environment:\n * - `processInput()` — Node reads paths from disk; Browser handles `File[]`\n * - `getDeployBodyCreator()` — Node streams Buffers; Browser builds Blobs\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 { ShipError, validateCaller, validateToken } from '@shipstatic/types';\n\nimport { ApiHttp } from './api/http.js';\nimport {\n createAccountResource,\n createDeploymentResource,\n createDomainResource,\n createTokenResource,\n type DeployInput,\n} from './resources.js';\nimport type {\n DeployBodyCreator,\n DeploymentOptions,\n ShipClientOptions,\n ShipEvents,\n TokenProvider,\n} 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` plus the lazy-init callback.\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 createDeployBody: this.getDeployBodyCreator(),\n });\n\n const ctx = {\n getApi: () => this.http,\n ensureInit: () => this.ensureInitialized(),\n };\n\n this.deployments = createDeploymentResource({\n ...ctx,\n processInput: (input, opts) => this.processInput(input, opts),\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 protected abstract getDeployBodyCreator(): DeployBodyCreator;\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.getLimits();\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 await this.ensureInitialized();\n return this.http.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 HTTP client for Ship API.\n */\nimport type {\n AccountGetResponse,\n Deployment,\n DeploymentCreateResponse,\n DeploymentDeleteResponse,\n DeploymentListResponse,\n Domain,\n DomainDeleteResponse,\n DomainDnsResponse,\n DomainListResponse,\n DomainRecordsResponse,\n DomainShareResponse,\n DomainValidateResponse,\n DomainVerifyResponse,\n ListOptions,\n PingResponse,\n PlatformLimits,\n SPACheckRequest,\n SPACheckResponse,\n StaticFile,\n Token,\n TokenCreateResponse,\n TokenDeleteResponse,\n TokenListResponse,\n} from '@shipstatic/types';\nimport { API_PATHS, DEFAULT_API, ShipError, validateIdempotencyKey } from '@shipstatic/types';\nimport { SimpleEvents } from '../events.js';\nimport { validateDeployConfig, validateLabels, validatePassword } from '../lib/validation.js';\nimport type {\n ApiDeployOptions,\n DeployBodyCreator,\n DomainSetResult,\n Fetch,\n ShipClientOptions,\n} from '../types.js';\n\n// =============================================================================\n// CONSTANTS\n// =============================================================================\n\nconst DEFAULT_REQUEST_TIMEOUT = 30_000;\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 * 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`. Each surface owns its own string —\n * there is deliberately no central registry, since integrations outside this\n * repo mint their own.\n *\n * Applied at this one wire boundary, so `via` is populated on every deploy\n * from every platform: an absent value means an unattributed caller (raw\n * HTTP), never \"probably the SDK\".\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// 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 createDeployBody: DeployBodyCreator;\n}\n\ninterface RequestResult<T> {\n data: T;\n status: number;\n}\n\n// =============================================================================\n// HTTP CLIENT\n// =============================================================================\n\nexport class ApiHttp extends SimpleEvents {\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 deployTimeout: number;\n private readonly deployBuildTimeout: number;\n private readonly fetch: Fetch;\n private readonly createDeployBody: DeployBodyCreator;\n private readonly deployEndpoint: string;\n private globalHeaders: Record<string, string> = {};\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 // 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 this.deployTimeout = options.timeout ?? DEFAULT_DEPLOY_TIMEOUT;\n this.deployBuildTimeout = options.timeout ?? DEFAULT_DEPLOY_BUILD_TIMEOUT;\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.createDeployBody = options.createDeployBody;\n this.deployEndpoint = options.deployEndpoint || API_PATHS.DEPLOYMENTS;\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 HTTP request with timeout, events, and error handling\n */\n private async executeRequest<T>(\n url: string,\n options: RequestInit,\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 as Record<string, string>);\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 const shipError = ShipError.fromFetchError(error, operationName);\n this.emit('error', shipError, url);\n throw shipError;\n }\n }\n\n /**\n * Simple request - returns data only\n */\n private async request<T>(\n url: string,\n options: RequestInit,\n operationName: string,\n timeoutMs?: number,\n ): Promise<T> {\n const { data } = await this.executeRequest<T>(url, options, operationName, timeoutMs);\n return data;\n }\n\n /**\n * Request with status - returns data and HTTP status code\n */\n private async requestWithStatus<T>(\n url: string,\n options: RequestInit,\n operationName: string,\n ): Promise<RequestResult<T>> {\n return this.executeRequest<T>(url, 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 ? { 'X-Caller': 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 const timeoutId = setTimeout(() => controller.abort(), timeoutMs);\n\n if (existingSignal) {\n const abort = () => controller.abort();\n existingSignal.addEventListener('abort', abort);\n if (existingSignal.aborted) controller.abort();\n }\n\n return {\n signal: controller.signal,\n cleanup: () => clearTimeout(timeoutId),\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 // PUBLIC API - DEPLOYMENTS\n // ===========================================================================\n\n async deploy(\n files: StaticFile[],\n options: ApiDeployOptions = {},\n ): Promise<DeploymentCreateResponse> {\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 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, headers: bodyHeaders } = await this.createDeployBody(files, {\n labels,\n via: options.via ?? DEPLOY_VIA,\n password: options.password,\n flags,\n captcha: options.captcha,\n });\n\n // The key rides a header, not the body, because it must be readable\n // before the request is parsed — the API replays a stored 201 ahead of\n // the write budget, so a retry costs nothing.\n return this.request<DeploymentCreateResponse>(\n `${this.apiUrl}${this.deployEndpoint}`,\n {\n method: 'POST',\n body,\n headers: idempotencyKey\n ? { ...bodyHeaders, 'Idempotency-Key': idempotencyKey }\n : bodyHeaders,\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.\n options.build || options.prerender ? this.deployBuildTimeout : this.deployTimeout,\n );\n }\n\n async listDeployments(options?: ListOptions): Promise<DeploymentListResponse> {\n return this.request(\n `${this.apiUrl}${API_PATHS.DEPLOYMENTS}${listQuery(options)}`,\n { method: 'GET' },\n 'List deployments',\n );\n }\n\n async getDeployment(id: string): Promise<Deployment> {\n return this.request(\n `${this.apiUrl}${API_PATHS.DEPLOYMENT(encodeURIComponent(id))}`,\n { method: 'GET' },\n 'Get deployment',\n );\n }\n\n async updateDeploymentLabels(id: string, labels: string[]): Promise<Deployment> {\n const normalized = validateLabels(labels);\n return this.request(\n `${this.apiUrl}${API_PATHS.DEPLOYMENT(encodeURIComponent(id))}`,\n {\n method: 'PATCH',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ labels: normalized }),\n },\n 'Update deployment labels',\n );\n }\n\n async deleteDeployment(id: string): Promise<DeploymentDeleteResponse> {\n return this.request<DeploymentDeleteResponse>(\n `${this.apiUrl}${API_PATHS.DEPLOYMENT(encodeURIComponent(id))}`,\n { method: 'DELETE' },\n 'Delete deployment',\n );\n }\n\n // ===========================================================================\n // PUBLIC API - DOMAINS\n // ===========================================================================\n // All domain methods accept FQDN (Fully Qualified Domain Name) as the `name` parameter.\n // The SDK does not validate or normalize - the API handles all domain semantics.\n\n async setDomain(name: string, deployment?: string, labels?: string[]): Promise<DomainSetResult> {\n const normalized = validateLabels(labels);\n const body: { deployment?: string; labels?: string[] } = {};\n if (deployment) body.deployment = deployment;\n if (normalized !== undefined) body.labels = normalized;\n\n const { data, status } = await this.requestWithStatus<Domain>(\n `${this.apiUrl}${API_PATHS.DOMAIN(encodeURIComponent(name))}`,\n {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n },\n 'Set domain',\n );\n\n return { ...data, isCreate: status === 201 };\n }\n\n async listDomains(options?: ListOptions): Promise<DomainListResponse> {\n return this.request(\n `${this.apiUrl}${API_PATHS.DOMAINS}${listQuery(options)}`,\n { method: 'GET' },\n 'List domains',\n );\n }\n\n async getDomain(name: string): Promise<Domain> {\n return this.request(\n `${this.apiUrl}${API_PATHS.DOMAIN(encodeURIComponent(name))}`,\n { method: 'GET' },\n 'Get domain',\n );\n }\n\n async deleteDomain(name: string): Promise<DomainDeleteResponse> {\n return this.request<DomainDeleteResponse>(\n `${this.apiUrl}${API_PATHS.DOMAIN(encodeURIComponent(name))}`,\n { method: 'DELETE' },\n 'Delete domain',\n );\n }\n\n async verifyDomain(name: string): Promise<DomainVerifyResponse> {\n return this.request(\n `${this.apiUrl}${API_PATHS.DOMAIN_VERIFY(encodeURIComponent(name))}`,\n { method: 'POST' },\n 'Verify domain',\n );\n }\n\n async getDomainDns(name: string): Promise<DomainDnsResponse> {\n return this.request(\n `${this.apiUrl}${API_PATHS.DOMAIN_DNS(encodeURIComponent(name))}`,\n { method: 'GET' },\n 'Get domain DNS',\n );\n }\n\n async getDomainRecords(name: string): Promise<DomainRecordsResponse> {\n return this.request(\n `${this.apiUrl}${API_PATHS.DOMAIN_RECORDS(encodeURIComponent(name))}`,\n { method: 'GET' },\n 'Get domain records',\n );\n }\n\n async getDomainShare(name: string): Promise<DomainShareResponse> {\n return this.request(\n `${this.apiUrl}${API_PATHS.DOMAIN_SHARE(encodeURIComponent(name))}`,\n { method: 'GET' },\n 'Get domain share',\n );\n }\n\n async validateDomain(name: string): Promise<DomainValidateResponse> {\n return this.request(\n `${this.apiUrl}${API_PATHS.DOMAINS_VALIDATE}`,\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ domain: name }),\n },\n 'Validate domain',\n );\n }\n\n // ===========================================================================\n // PUBLIC API - TOKENS\n // ===========================================================================\n\n async createToken(ttl?: number, labels?: string[]): Promise<TokenCreateResponse> {\n const normalized = validateLabels(labels);\n const body: { ttl?: number; labels?: string[] } = {};\n if (ttl !== undefined) body.ttl = ttl;\n if (normalized !== undefined) body.labels = normalized;\n\n return this.request(\n `${this.apiUrl}${API_PATHS.TOKENS}`,\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n },\n 'Create token',\n );\n }\n\n async listTokens(options?: ListOptions): Promise<TokenListResponse> {\n return this.request(\n `${this.apiUrl}${API_PATHS.TOKENS}${listQuery(options)}`,\n { method: 'GET' },\n 'List tokens',\n );\n }\n\n async deleteToken(token: string): Promise<TokenDeleteResponse> {\n return this.request<TokenDeleteResponse>(\n `${this.apiUrl}${API_PATHS.TOKEN(encodeURIComponent(token))}`,\n { method: 'DELETE' },\n 'Delete token',\n );\n }\n\n async getToken(token: string): Promise<Token> {\n return this.request<Token>(\n `${this.apiUrl}${API_PATHS.TOKEN(encodeURIComponent(token))}`,\n { method: 'GET' },\n 'Get token',\n );\n }\n\n // ===========================================================================\n // PUBLIC API - ACCOUNT & CONFIG\n // ===========================================================================\n\n async getAccount(): Promise<AccountGetResponse> {\n return this.request(`${this.apiUrl}${API_PATHS.ACCOUNT}`, { method: 'GET' }, 'Get account');\n }\n\n async getLimits(): Promise<PlatformLimits> {\n return this.request(`${this.apiUrl}${API_PATHS.LIMITS}`, { method: 'GET' }, 'Get limits');\n }\n\n async ping(): Promise<PingResponse> {\n return this.request<PingResponse>(`${this.apiUrl}${API_PATHS.PING}`, { method: 'GET' }, 'Ping');\n }\n\n // ===========================================================================\n // PUBLIC API - SPA CHECK\n // ===========================================================================\n\n async checkSPA(files: StaticFile[], _options: ApiDeployOptions = {}): Promise<boolean> {\n const indexFile = files.find((f) => f.path === 'index.html' || f.path === '/index.html');\n if (!indexFile || indexFile.size > 100 * 1024) {\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 this.request<SPACheckResponse>(\n `${this.apiUrl}${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 * 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 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 * Ship SDK resource factory functions.\n */\nimport {\n type AccountResource,\n type DeployInput,\n type DeploymentResource,\n type DomainResource,\n type ListOptions,\n ShipError,\n type StaticFile,\n type TokenResource,\n} from '@shipstatic/types';\n\nexport type {\n AccountResource,\n DeployInput,\n DeploymentResource,\n DomainResource,\n StaticFile,\n TokenResource,\n};\n\nimport type { ApiHttp } from './api/http.js';\nimport { detectAndConfigureSPA } from './lib/spa.js';\nimport type { DeploymentOptions } from './types.js';\n\n/**\n * Shared context for all resource factories.\n */\nexport interface ResourceContext {\n getApi: () => ApiHttp;\n ensureInit: () => Promise<void>;\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, ensureInit, processInput } = ctx;\n\n return {\n upload: async (input: DeployInput, options: DeploymentOptions = {}) => {\n await ensureInit();\n\n if (!processInput) {\n throw ShipError.config('processInput function is not provided.');\n }\n\n const apiClient = getApi();\n let staticFiles = await processInput(input, options);\n staticFiles = await detectAndConfigureSPA(staticFiles, apiClient, options);\n\n return apiClient.deploy(staticFiles, options);\n },\n\n list: async (options?: ListOptions) => {\n await ensureInit();\n return getApi().listDeployments(options);\n },\n\n get: async (id: string) => {\n await ensureInit();\n return getApi().getDeployment(id);\n },\n\n set: async (id: string, options: { labels: string[] }) => {\n await ensureInit();\n return getApi().updateDeploymentLabels(id, options.labels);\n },\n\n delete: async (id: string) => {\n await ensureInit();\n return getApi().deleteDeployment(id);\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, ensureInit } = 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 await ensureInit();\n return getApi().setDomain(name, options.deployment, options.labels);\n },\n\n list: async (options?: ListOptions) => {\n await ensureInit();\n return getApi().listDomains(options);\n },\n\n get: async (name: string) => {\n await ensureInit();\n return getApi().getDomain(name);\n },\n\n delete: async (name: string) => {\n await ensureInit();\n return getApi().deleteDomain(name);\n },\n\n verify: async (name: string) => {\n await ensureInit();\n return getApi().verifyDomain(name);\n },\n\n validate: async (name: string) => {\n await ensureInit();\n return getApi().validateDomain(name);\n },\n\n dns: async (name: string) => {\n await ensureInit();\n return getApi().getDomainDns(name);\n },\n\n records: async (name: string) => {\n await ensureInit();\n return getApi().getDomainRecords(name);\n },\n\n share: async (name: string) => {\n await ensureInit();\n return getApi().getDomainShare(name);\n },\n };\n}\n\n/**\n * Create account resource (whoami functionality).\n */\nexport function createAccountResource(ctx: ResourceContext): AccountResource {\n const { getApi, ensureInit } = ctx;\n\n return {\n get: async () => {\n await ensureInit();\n return getApi().getAccount();\n },\n };\n}\n\n/**\n * Create token resource for managing deploy tokens.\n */\nexport function createTokenResource(ctx: ResourceContext): TokenResource {\n const { getApi, ensureInit } = ctx;\n\n return {\n create: async (options: { ttl?: number; labels?: string[] } = {}) => {\n await ensureInit();\n return getApi().createToken(options.ttl, options.labels);\n },\n\n list: async (options?: ListOptions) => {\n await ensureInit();\n return getApi().listTokens(options);\n },\n\n get: async (token: string) => {\n await ensureInit();\n return getApi().getToken(token);\n },\n\n delete: async (token: string) => {\n await ensureInit();\n return getApi().deleteToken(token);\n },\n };\n}\n","/**\n * @file SPA detection and auto-configuration utilities.\n *\n * Provides SPA detection and ship.json generation functionality\n * that can be used by both Node.js and browser environments.\n */\n\nimport { DEPLOYMENT_CONFIG_FILENAME, SPA_DEFAULT_CONFIG } from '@shipstatic/types';\nimport type { ApiHttp } 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 * 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 apiClient - HTTP client for API communication\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 apiClient: ApiHttp,\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 apiClient.checkSPA(files, options);\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 * Node.js-specific deploy body creation.\n */\nimport { ShipError } from '@shipstatic/types';\nimport type { DeployBody, DeployBodyContext, StaticFile } from '../../shared/types.js';\n\nexport async function createDeployBody(\n files: StaticFile[],\n context: DeployBodyContext = {},\n): Promise<DeployBody> {\n const { FormData, File } = await import('formdata-node');\n const { FormDataEncoder } = await import('form-data-encoder');\n\n const { labels, via, password, flags, captcha } = context;\n const formData = new FormData();\n const checksums: string[] = [];\n\n for (const file of files) {\n // 1. Validate content type\n if (\n !Buffer.isBuffer(file.content) &&\n !(typeof Blob !== 'undefined' && file.content instanceof Blob)\n ) {\n throw ShipError.file(`Unsupported file.content type for Node.js: ${file.path}`, {\n filePath: file.path,\n });\n }\n\n // 2. Validate md5\n if (!file.md5) {\n throw ShipError.file(`File missing md5 checksum: ${file.path}`, { filePath: file.path });\n }\n\n // 3. Create File and append — API derives Content-Type from extension\n const fileInstance = new File([file.content], file.path, { type: 'application/octet-stream' });\n formData.append('files[]', fileInstance);\n checksums.push(file.md5);\n }\n\n formData.append('checksums', JSON.stringify(checksums));\n\n if (labels && labels.length > 0) formData.append('labels', JSON.stringify(labels));\n if (via) formData.append('via', via);\n if (password) formData.append('password', password);\n if (flags?.build) formData.append('build', 'true');\n if (flags?.prerender) formData.append('prerender', 'true');\n if (flags?.spa) formData.append('spa', 'true');\n if (captcha) formData.append('captcha', captcha);\n\n const encoder = new FormDataEncoder(formData);\n const chunks = [];\n for await (const chunk of encoder.encode()) {\n chunks.push(Buffer.from(chunk));\n }\n const body = Buffer.concat(chunks);\n\n return {\n body: body.buffer.slice(body.byteOffset, body.byteOffset + body.byteLength) as ArrayBuffer,\n headers: {\n 'Content-Type': encoder.contentType,\n 'Content-Length': Buffer.byteLength(body).toString(),\n },\n };\n}\n","/**\n * @file File-based configuration loader for the `ship` CLI.\n *\n * The CLI is the only place that reads `~/.shiprc` and `package.json` `\"ship\"` keys.\n * Programmatic SDK consumers never touch the filesystem — they pass options to the\n * `Ship` constructor, optionally falling back to `SHIP_*` environment variables.\n * Keeping file resolution out of the SDK is what makes embedded usage (MCP, n8n,\n * GitHub Action) safe by default: `new Ship({})` cannot accidentally pick up the\n * host developer's `~/.shiprc`.\n *\n * Search order (cosmiconfig defaults):\n * 1. `.shiprc` walking up from CWD to `$HOME`\n * 2. `package.json` `\"ship\"` key walking up from CWD\n * 3. `$HOME/.shiprc`\n *\n * The `--config <file>` CLI flag bypasses the search and loads a specific path.\n */\n\nimport { homedir } from 'node:os';\nimport { isShipError, ShipError } from '@shipstatic/types';\nimport { cosmiconfigSync } from 'cosmiconfig';\nimport { z } from 'zod';\nimport { CREDENTIAL_FIELDS } from '../../shared/core/credential-schema.js';\nimport type { ShipClientOptions } from '../../shared/types.js';\n\n// `.strict()` rejects unknown keys — catches typos like `apikey` (lowercase)\n// in user-authored `.shiprc` files. The env reader doesn't need this because\n// its input is exactly the two SHIP_* vars we read.\nconst FileConfigSchema = z.object(CREDENTIAL_FIELDS).strict();\n\nconst MODULE_NAME = 'ship';\n\n/**\n * Load configuration from `.shiprc` / `package.json`.\n *\n * Error semantics by failure mode:\n *\n * | Mode | Behavior |\n * |------|----------|\n * | Search finds no file | Returns `{}` — file config is optional |\n * | `--config <path>` and the file does not exist | Throws — a typo'd path is a clear user error and shouldn't be hidden behind a downstream auth failure |\n * | File found but unparseable JSON / unreadable (permissions) | Throws — silent swallowing left users debugging \"wrong API key\" when the real issue was their config |\n * | File parsed but fails schema validation | Throws with the offending field path |\n *\n * @param configFile - Optional explicit path (from the CLI's `--config` flag).\n * When provided, cosmiconfig loads exactly that file instead of searching.\n * @returns Validated config, or `{}` if no file was found in the search.\n * @throws {ShipError} for explicit-path failures, parse errors, or schema violations.\n */\nexport function loadShipFile(configFile?: string): Partial<ShipClientOptions> {\n // Empty path is treated as absence — matches credential-flag handling\n // (`--token \"\"` falls through to env). A user passing `--config \"$VAR\"`\n // with `VAR` unset gets `--config \"\"`, which should not error: it should\n // fall through to the normal cosmiconfig search.\n const explicitPath = configFile || undefined;\n\n const home = homedir();\n const explorer = cosmiconfigSync(MODULE_NAME, {\n searchPlaces: [`.${MODULE_NAME}rc`, 'package.json', `${home}/.${MODULE_NAME}rc`],\n stopDir: home,\n });\n\n let result: ReturnType<typeof explorer.search>;\n try {\n result = explicitPath ? explorer.load(explicitPath) : explorer.search();\n } catch (error) {\n if (isShipError(error)) throw error;\n // Wrap any cosmiconfig failure (missing explicit path, bad JSON, permissions)\n // in a ShipError. Surfacing this beats a confusing \"auth failed\" later.\n const message = error instanceof Error ? error.message : String(error);\n const where = explicitPath ? ` (${explicitPath})` : '';\n throw ShipError.config(`Failed to read ship config${where}: ${message}`);\n }\n\n if (!result?.config) return {};\n\n try {\n return FileConfigSchema.parse(result.config);\n } catch (error) {\n if (error instanceof z.ZodError) {\n const issue = error.issues[0];\n // Keys from retired credential vocabularies get a rename hint instead\n // of a bare rejection — the fix is one edit, so the error names it.\n if (issue.code === 'unrecognized_keys') {\n const legacy = issue.keys.filter((key) => key === 'apiKey' || key === 'deployToken');\n if (legacy.length > 0) {\n const keys = legacy.map((key) => `\"${key}\"`).join(' and ');\n throw ShipError.config(\n `Invalid config in ${result.filepath}: ${keys} ${legacy.length > 1 ? 'are' : 'is'} no longer supported — the key is now \"token\". Run \\`ship config\\` to rewrite it.`,\n );\n }\n }\n const path = issue.path.length > 0 ? ` at ${issue.path.join('.')}` : '';\n throw ShipError.config(`Invalid config in ${result.filepath}${path}: ${issue.message}`);\n }\n throw ShipError.config(`Invalid config in ${result.filepath}`);\n }\n}\n","/**\n * @file Resolves CLI configuration into a `Ship` instance.\n *\n * Owns the credential precedence contract: **flag > env > file**.\n *\n * Env-over-file is the canonical CLI tooling posture: CI runners and secret\n * managers set environment variables; a stale dotfile from local dev should\n * never override them. The merge is extracted as a pure function so the\n * contract is unit-testable and a future refactor can't silently flip the\n * order.\n *\n * The SDK itself only knows about constructor args + env vars (see\n * `node/index.ts`). File resolution lives here, in the CLI layer, exactly\n * once — keeping the SDK pure is what guarantees embedded consumers like\n * MCP can't inadvertently inherit the host developer's `~/.shiprc`.\n */\n\nimport type { ShipClientOptions } from '../../shared/types.js';\nimport { readEnvConfig } from '../core/config.js';\nimport { Ship } from '../index.js';\nimport { loadShipFile } from './shiprc.js';\n\n/**\n * The subset of CLI flags that participate in config resolution.\n * Other flags (`--json`, `--quiet`, etc.) flow through Commander separately.\n */\nexport interface CliFlags {\n /** Path to a specific config file, from `--config <file>`. */\n config?: string;\n apiUrl?: string;\n token?: string;\n}\n\n/**\n * Pure precedence merge: flag > env > file, per value. There is one token\n * and one API URL — nothing to arbitrate beyond source order.\n *\n * Empty strings are treated as absence and fall through to the next source\n * (mirrors the env reader, which normalizes empty `process.env` values to\n * `undefined`). This handles CI/CD shell-expansion of unset variables —\n * `--token \"$TOKEN\"` with `TOKEN` unset becomes `--token \"\"`, which we\n * must not lock in as a credential. Without this, an empty flag would\n * silently demote an authenticated deploy to anonymous PUBLIC_ACCOUNT.\n *\n * Exported separately from `createClient` so tests can lock in the contract\n * without mocking the SDK or the filesystem.\n */\nexport function mergeCliConfig(\n flags: CliFlags,\n env: Partial<ShipClientOptions>,\n file: Partial<ShipClientOptions>,\n): ShipClientOptions {\n return {\n apiUrl: flags.apiUrl || env.apiUrl || file.apiUrl,\n token: flags.token || env.token || file.token,\n };\n}\n\n/**\n * Resolve CLI flags + env + file into a `Ship` instance, ready for command\n * action handlers. Called once per CLI invocation by `withErrorHandling`.\n *\n * Synchronous all the way down — matches the SDK's sync constructor.\n */\nexport function createClient(flags: CliFlags = {}): Ship {\n return new Ship(mergeCliConfig(flags, readEnvConfig(), loadShipFile(flags.config)));\n}\n","/**\n * @file CLI-specific error UX utilities.\n *\n * Two pure functions: `toShipError` normalizes any thrown value into a typed\n * `ShipError` for the CLI's global error boundary; `getUserMessage` translates\n * a `ShipError` into the actionable string the CLI prints. Both are pure for\n * easy unit testing.\n *\n * Both serve the TEXT channel only. `--json` transmits `ShipError.toResponse()`\n * untouched — see `error()` in `utils.ts`, which owns both channels. There is\n * deliberately no JSON formatter here: a second serializer is exactly how the\n * `--json` error envelope drifted from the wire's in the first place.\n *\n * Distinct from `ShipError.fromFetchError` (in `@shipstatic/types`), which is\n * for HTTP fetch failures. The CLI's global handler also catches things like\n * Commander parse errors, runtime exceptions in user code, etc. — so it uses\n * `toShipError` and intentionally normalizes unknowns to `Business` (a client\n * error type) so `getUserMessage`'s `isClientError()` branch surfaces the\n * original message rather than swallowing it as a generic \"server error\".\n */\n\nimport { isShipError, ShipError } from '@shipstatic/types';\nimport type { OutputContext } from './formatters.js';\n\n/**\n * Normalize any thrown value to a `ShipError` for the CLI error boundary.\n * Pass-through for existing `ShipError`s; wraps other Errors and unknowns\n * as `Business` so their message is preserved through `getUserMessage`.\n */\nexport function toShipError(err: unknown): ShipError {\n if (isShipError(err)) {\n return err;\n }\n if (err instanceof Error) {\n return ShipError.business(err.message);\n }\n return ShipError.business(String(err ?? 'Unknown error'));\n}\n\n/**\n * CLI options relevant to error message generation.\n */\nexport interface ErrorOptions {\n /**\n * The credential the CLI resolved (flag > env > file) — not the raw\n * `--token` flag. Presence selects the \"invalid or expired\" auth message;\n * absence selects the \"how to authenticate\" one.\n */\n token?: string;\n}\n\n/**\n * Get actionable user-facing message from an error.\n * Transforms technical errors into helpful messages that tell users what to do.\n *\n * This is a pure function - given the same inputs, always returns the same output.\n * All error message logic is centralized here for easy testing and maintenance.\n */\nexport function getUserMessage(\n err: ShipError,\n _context?: OutputContext,\n options?: ErrorOptions,\n): string {\n // Auth errors - tell user what credentials to provide\n if (err.isAuthError()) {\n if (options?.token) {\n return 'authentication failed: invalid or expired token';\n }\n return 'authentication required: pass --token, set SHIP_TOKEN, or run ship config';\n }\n\n // Network errors — the transport failed before any response existed, so\n // there is nothing of the API's to quote. (A URL-naming variant lived here\n // until 2026-07-27, reading `details.url`; nothing has ever set that field —\n // `ShipError.fromFetchError` carries `{ cause }` — so the branch was dead.)\n if (err.isNetworkError()) {\n return 'network error: could not reach the API. check your internet connection';\n }\n\n // Client-attributable — a client-fault type, or any 4xx status (the guard\n // reads both axes, so a status-derived `Api` at 404 lands here too). The\n // message was authored for the end user at the throw site, API or local\n // code alike, so it is shown verbatim.\n if (err.isClientError()) {\n return err.message;\n }\n\n // Server faults (5xx). The wire message is relayed like every other, because\n // the API leaves nothing to withhold: its global handler emits either a\n // deliberately authored sentence (a 503 naming what is unavailable) or a flat\n // generic, and sends the raw failure to Slack rather than to the client. This\n // discarded it until 2026-07-29, so a 503 that named its cause arrived as\n // \"server error: please try again\" — the platform authored a message for the\n // user and one surface threw it away. The CLI ADDS the one thing it knows and\n // the server does not: where to look when it keeps happening.\n return `${err.message.replace(/\\.$/, '')} — try again, or check https://status.shipstatic.com`;\n}\n","/**\n * Pure formatting functions for CLI output.\n * All formatters are synchronous and have no side effects beyond console output.\n */\nimport type {\n Account,\n Deployment,\n DeploymentCreateResponse,\n DeploymentListResponse,\n Domain,\n DomainDnsResponse,\n DomainListResponse,\n DomainRecordsResponse,\n DomainShareResponse,\n DomainValidateResponse,\n DomainVerifyResponse,\n TokenCreateResponse,\n TokenListResponse,\n} from '@shipstatic/types';\nimport type { CLIResult, EnrichedDomain } from './types.js';\nimport { error, formatDetails, formatTable, info, success } from './utils.js';\n\nconst setupUrl = (hash: string, domain: string) => `https://setup.shipstatic.com/${hash}/${domain}`;\n\n/**\n * Read a named field off a result whose shape the caller has already decided.\n *\n * The cast is the one narrowing TypeScript cannot do for us: `CLIResult` is a\n * union of named response types and a member like `EnrichedDomain` carries no\n * index signature, yet every member IS a plain object. Safe because the field\n * name is derived from the command's own resource type — never from input —\n * and the value is type-checked at the call site before it is used.\n */\nfunction readField(source: object, field: string): unknown {\n return (source as Record<string, unknown>)[field];\n}\n\nexport interface OutputContext {\n operation?: string;\n resourceType?: string;\n}\n\nexport interface FormatOptions {\n json?: boolean;\n quiet?: boolean;\n noColor?: boolean;\n}\n\n/**\n * Format deployments list\n */\nexport function formatDeploymentsList(\n result: DeploymentListResponse,\n _context: OutputContext,\n options: FormatOptions,\n): void {\n const { noColor } = options;\n\n if (result.deployments.length === 0) {\n console.log('no deployments found');\n console.log();\n return;\n }\n\n const columns = ['deployment', 'labels', 'files', 'size', 'created', 'via'];\n console.log(formatTable(result.deployments, columns, noColor));\n printCursorHint(result.cursor, noColor);\n}\n\n/**\n * A non-null cursor means the server has more pages. Text mode surfaces the\n * continuation the same way `-q` surfaces identifiers: as the value you feed\n * to the next invocation. (`--json` consumers read `cursor` off the response.)\n */\nfunction printCursorHint(cursor: string | null | undefined, noColor?: boolean): void {\n if (cursor) {\n info(`more results available — rerun with --cursor ${cursor}`, false, noColor);\n }\n}\n\n/**\n * Format domains list\n */\nexport function formatDomainsList(\n result: DomainListResponse,\n _context: OutputContext,\n options: FormatOptions,\n): void {\n const { noColor } = options;\n\n if (result.domains.length === 0) {\n console.log('no domains found');\n console.log();\n return;\n }\n\n const columns = ['domain', 'deployment', 'labels', 'linked', 'links', 'created'];\n console.log(formatTable(result.domains, columns, noColor));\n printCursorHint(result.cursor, noColor);\n}\n\n/**\n * Format single domain result.\n * Accepts plain Domain (from get) or EnrichedDomain (from set, with DNS info).\n */\nexport function formatDomain(\n result: Domain | EnrichedDomain,\n context: OutputContext,\n options: FormatOptions,\n): void {\n const { noColor } = options;\n\n // Destructure enrichment fields (undefined when result is plain Domain)\n const { _dnsRecords, _shareHash, isCreate, ...displayResult } = result as EnrichedDomain;\n\n // Show success message for set operations\n if (context.operation === 'set') {\n const verb = isCreate ? 'created' : 'updated';\n success(`${result.url} domain ${verb}`, false, noColor);\n }\n\n // Display pre-fetched DNS records (for new external domains)\n if (_dnsRecords && _dnsRecords.length > 0) {\n console.log();\n info('DNS Records to configure:', false, noColor);\n _dnsRecords.forEach((record) => {\n console.log(` ${record.type}: ${record.name} → ${record.value}`);\n });\n }\n\n // Display setup instructions link\n if (_shareHash) {\n console.log();\n info(`Setup instructions: ${setupUrl(_shareHash, result.domain)}`, false, noColor);\n }\n\n console.log(formatDetails(displayResult, noColor));\n}\n\n/**\n * Format single deployment result\n */\nexport function formatDeployment(\n result: Deployment | DeploymentCreateResponse,\n context: OutputContext,\n options: FormatOptions,\n): void {\n const { noColor } = options;\n\n // Show success message for upload operations\n if (context.operation === 'upload') {\n success(`${result.url} deployment uploaded`, false, noColor);\n }\n\n console.log(formatDetails(result, noColor));\n\n // Public deployment — claim URL + CTA after details\n const claim = (result as DeploymentCreateResponse).claim;\n if (claim) {\n const days = result.expires ? Math.round((result.expires - result.created) / 86400) : null;\n console.log(\n `IMPORTANT: this deployment${days ? ` expires in ${days} day${days !== 1 ? 's' : ''}` : ' will expire'}, claim it to keep permanently:\\n${claim}\\n`,\n );\n info(\n `configure a free API key with 'ship config' to deploy to your own account`,\n false,\n noColor,\n );\n }\n}\n\n/**\n * Format account/email result\n */\nexport function formatAccount(\n result: Account,\n _context: OutputContext,\n options: FormatOptions,\n): void {\n const { noColor } = options;\n console.log(formatDetails(result, noColor));\n}\n\n/**\n * Format the DNS-verification acknowledgement.\n *\n * The wire carries no prose — an acknowledgement is the domain and the 202\n * that accepted it — so the copy is composed here. That is the same\n * carve-out the deletion message below uses: a surface writes its own words\n * exactly where no wire message exists.\n */\nexport function formatDomainVerify(\n result: DomainVerifyResponse,\n _context: OutputContext,\n options: FormatOptions,\n): void {\n success(`${result.domain} dns verification queued`, false, options.noColor);\n}\n\n/**\n * Format domain validation result\n */\nexport function formatDomainValidate(\n result: DomainValidateResponse,\n _context: OutputContext,\n options: FormatOptions,\n): void {\n const { noColor } = options;\n\n if (result.valid) {\n success(`domain is valid`, false, noColor);\n console.log();\n if (result.normalized) {\n console.log(` normalized: ${result.normalized}`);\n }\n if (result.available !== null) {\n const availabilityText = result.available\n ? noColor\n ? 'available'\n : 'available ✓'\n : 'already taken';\n console.log(` availability: ${availabilityText}`);\n }\n console.log();\n } else {\n error(result.reason || 'domain is invalid', false, noColor);\n }\n}\n\n/**\n * Format domain DNS records result\n */\nexport function formatDomainRecords(\n result: DomainRecordsResponse,\n _context: OutputContext,\n options: FormatOptions,\n): void {\n const { noColor } = options;\n\n if (result.records.length === 0) {\n console.log('no records found');\n console.log();\n return;\n }\n\n const columns = ['type', 'name', 'value'];\n console.log(formatTable(result.records, columns, noColor));\n}\n\n/**\n * Format domain DNS provider result\n */\nexport function formatDomainDns(\n result: DomainDnsResponse,\n _context: OutputContext,\n options: FormatOptions,\n): void {\n const { noColor } = options;\n const provider = result.dns?.provider?.name || null;\n console.log(formatDetails({ domain: result.domain, provider }, noColor));\n}\n\n/**\n * Format domain share result as setup URL\n */\nexport function formatDomainShare(\n result: DomainShareResponse,\n _context: OutputContext,\n options: FormatOptions,\n): void {\n const { noColor } = options;\n success(setupUrl(result.hash, result.domain), false, noColor);\n}\n\n/**\n * Format tokens list\n */\nexport function formatTokensList(\n result: TokenListResponse,\n _context: OutputContext,\n options: FormatOptions,\n): void {\n const { noColor } = options;\n\n if (result.tokens.length === 0) {\n console.log('no tokens found');\n console.log();\n return;\n }\n\n const columns = ['token', 'labels', 'created', 'expires'];\n console.log(formatTable(result.tokens, columns, noColor));\n printCursorHint(result.cursor, noColor);\n}\n\n/**\n * Format single token result (creation response includes both token ID and secret)\n */\nexport function formatToken(\n result: TokenCreateResponse,\n context: OutputContext,\n options: FormatOptions,\n): void {\n const { noColor } = options;\n\n if (context.operation === 'create' && result.token) {\n success(`token ${result.token} created`, false, noColor);\n }\n\n console.log(formatDetails(result, noColor));\n}\n\n/**\n * Main output function - routes to appropriate formatter based on result shape.\n * Handles JSON mode, deletion operations, and ping results.\n */\nexport function formatOutput(\n result: CLIResult,\n context: OutputContext,\n options: FormatOptions,\n): void {\n const { json, quiet, noColor } = options;\n\n // Quiet mode: output only the key identifier\n if (quiet) {\n if (result === undefined) return;\n if (result !== null && typeof result === 'object') {\n if ('deployments' in result) {\n for (const d of (result as DeploymentListResponse).deployments) console.log(d.deployment);\n } else if ('domains' in result) {\n for (const d of (result as DomainListResponse).domains) console.log(d.domain);\n } else if ('tokens' in result) {\n for (const t of (result as TokenListResponse).tokens) console.log(t.token);\n } else if ('records' in result) {\n for (const r of (result as DomainRecordsResponse).records)\n console.log(`${r.type} ${r.name} ${r.value}`);\n } else if ('hash' in result) {\n const r = result as DomainShareResponse;\n console.log(setupUrl(r.hash, r.domain));\n } else if ('dns' in result) {\n const name = (result as DomainDnsResponse).dns?.provider?.name;\n if (name) console.log(name);\n } else if ('domain' in result) {\n console.log((result as Domain).domain);\n } else if ('deployment' in result) {\n console.log((result as Deployment).deployment);\n } else if ('secret' in result) {\n console.log((result as TokenCreateResponse).secret);\n } else if ('email' in result) {\n console.log((result as Account).email);\n } else if ('valid' in result) {\n const v = result as DomainValidateResponse;\n if (v.valid && v.normalized) console.log(v.normalized);\n }\n }\n return;\n }\n\n // Deletions answer with an acknowledgement, so text is the only channel that\n // composes anything: JSON falls through to the one transmitter below, and\n // quiet already read the key above. Text translates, JSON transmits.\n //\n // The identifier comes from the wire, never from what the caller typed —\n // those differ routinely. A deployment is addressable by bare slug and\n // answers with its hostname; a domain is accepted in any case and answers\n // normalized. Only the noun is this CLI's word, and it is the same word\n // that names the key: an acknowledgement is the resource noun carrying its\n // canonical key (`@shipstatic/types`, `DeploymentDeleteResponse`), so one\n // lowercased resource type both reads the field and writes the sentence.\n if (context.operation === 'delete' && !json) {\n const noun = context.resourceType?.toLowerCase();\n const acknowledged =\n noun && result !== null && typeof result === 'object' ? readField(result, noun) : undefined;\n\n // A handler that resolved nothing leaves no identifier to report, and the\n // CLI does not invent one.\n success(\n typeof acknowledged === 'string' ? `${acknowledged} ${noun} deleted` : 'deleted successfully',\n false,\n noColor,\n );\n return;\n }\n\n if (result === undefined) {\n success('deleted successfully', json, noColor);\n return;\n }\n\n // Liveness is a question, so text answers it as one — \"reachable\" is the\n // whole of what a person asked, and the server clock is noise to them. JSON\n // falls through to the transmitter and carries the response. The same split\n // as deletions above, for the same reason.\n //\n // There is no unreachable arm: a non-OK response throws in transport, so\n // reaching this line at all IS the answer. The CLI carried a `success: false`\n // branch until 2026-07-29 — unreachable code guarding a field the route set\n // to a literal `true`.\n if (context.operation === 'ping' && !json) {\n success('api reachable', false, noColor);\n return;\n }\n\n // JSON mode: output raw JSON for all results\n if (json && result !== null && typeof result === 'object') {\n // Filter internal fields from JSON output\n const output = { ...result } as Record<string, unknown>;\n delete output._dnsRecords;\n delete output._shareHash;\n delete output.isCreate;\n console.log(JSON.stringify(output, null, 2));\n console.log();\n return;\n }\n\n // Route to specific formatter based on result shape\n // Order matters: check list types before singular types\n if (result !== null && typeof result === 'object') {\n if ('deployments' in result) {\n formatDeploymentsList(result as DeploymentListResponse, context, options);\n } else if ('domains' in result) {\n formatDomainsList(result as DomainListResponse, context, options);\n } else if ('tokens' in result) {\n formatTokensList(result as TokenListResponse, context, options);\n } else if ('records' in result) {\n formatDomainRecords(result as DomainRecordsResponse, context, options);\n } else if ('hash' in result) {\n formatDomainShare(result as DomainShareResponse, context, options);\n } else if ('dns' in result) {\n formatDomainDns(result as DomainDnsResponse, context, options);\n } else if ('domain' in result && !('url' in result)) {\n // The verify acknowledgement: the domain and nothing else. A Domain\n // entity always carries its `url`, which is what tells the two apart.\n formatDomainVerify(result as DomainVerifyResponse, context, options);\n } else if ('domain' in result) {\n formatDomain(result as Domain, context, options);\n } else if ('deployment' in result) {\n formatDeployment(result as Deployment, context, options);\n } else if ('token' in result) {\n formatToken(result as TokenCreateResponse, context, options);\n } else if ('email' in result) {\n formatAccount(result as Account, context, options);\n } else if ('valid' in result) {\n formatDomainValidate(result as DomainValidateResponse, context, options);\n } else {\n // A shape with no formatter of its own is not an occasion to print the\n // word \"success\": that asserts the call worked, which the exit code\n // already said, and it hides the answer the command was run for. Render\n // what arrived. `GET /labels` and `GET /limits` are real endpoints with\n // no CLI command yet (`CLAUDE.md`, \"Routes the API exposes that the SDK\n // does not reach\"); when one lands it shows its content on the first\n // run, and a bespoke formatter becomes an improvement rather than a\n // prerequisite.\n console.log(formatDetails(result, noColor));\n }\n }\n // A non-object result is unrenderable — `undefined` is handled above, and\n // `boolean` left this union with ping's `success` field. Saying nothing is\n // honest; saying \"success\" would not be.\n}\n"],"mappings":"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","names":["validateIdempotencyKey","value","ShipError","key","IDEMPOTENCY_KEY_CONSTRAINTS","isShipError","error","isBlockedExtension","filename","dotIndex","ext","BLOCKED_EXTENSIONS","hasUnsafeChars","UNSAFE_FILENAME_CHARS","hasUnbuiltMarker","filePath","s","UNBUILT_PROJECT_MARKERS","classifyToken","token","API_KEY","TokenKind","DEPLOY_TOKEN","assertShipJsonSyntax","text","withoutBom","parsed","DEPLOYMENT_CONFIG_FILENAME","validatePrefixedCredential","shape","label","hexPart","validateApiKey","apiKey","validateDeployToken","deployToken","validateToken","validateCaller","caller","CALLER","validateApiUrl","apiUrl","url","validatePassword","trimmed","PASSWORD_CONSTRAINTS","API_PATHS","ErrorType","CLIENT_ONLY_ERROR_TYPES","ERROR_CATEGORIES","SERVER_PRODUCIBLE_ERROR_TYPES","MAX_FOREIGN_MESSAGE_LENGTH","AuthMethod","SPA_DEFAULT_CONFIG","DEFAULT_API","LABEL_CONSTRAINTS","LABEL_PATTERN","init_dist","__esmMin","deployment","domain","t","_ShipError","type","message","status","details","authDetails","response","operationName","bodyType","json","obj","retryAfterHeader","seconds","existing","cause","op","resource","id","errorType","detectEnvironment","getENV","_testEnvironment","init_env","__esmMin","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","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","validateFileName","filename","hasUnsafeChars","reservedNames","nameWithoutPath","init_file_validation","__esmMin","init_dist","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","nameCheck","validateFileName","isBlockedExtension","init_security","__esmMin","init_dist","init_file_validation","node_files_exports","__export","processFilesForNode","findAllFilePaths","dirPath","visited","results","realPath","entries","entry","fullPath","stats","subFiles","paths","options","platformLimits","getENV","ShipError","p","absPath","marker","e","UNBUILT_PROJECT_MARKERS","isShipError","absolutePaths","uniquePaths","inputAbsolutePaths","inputBasePath","findCommonParent","contentPaths","rel","deployPaths","optimizeDeployPaths","filteredSet","filterJunk","validAbsPaths","validDeployPaths","i","totalSize","filePath","deployPath","validateDeployPath","validateDeployFile","content","md5","calculateMD5","error","errorMessage","fs","path","init_node_files","__esmMin","init_dist","init_deploy_paths","init_env","init_junk","init_md5","init_path","init_security","cli_exports","__export","buildProgram","__toCommonJS","import_node_fs","path","init_dist","import_commander","import_yoctocolors","init_dist","import_zod","import_zod","CREDENTIAL_FIELDS","init_env","EnvConfigSchema","CREDENTIAL_FIELDS","ENV_VAR_BY_FIELD","readEnvConfig","getENV","raw","error","issue","field","envVar","ShipError","fs","os","path","init_dist","import_columnify","import_yoctocolors","INTERNAL_FIELDS","applyColor","colorFn","text","noColor","isPlainObject","value","decapitalize","msg","success","json","warn","warnPrefix","warnMsg","info","infoPrefix","infoMsg","error","err","errorPrefix","message","errorMsg","formatTimestamp","timestamp","context","isoString","formatValue","key","mb","pairs","k","v","formatTable","data","columns","headerMap","firstItem","columnOrder","transformedData","item","record","transformed","col","columnify","config","heading","line","formatDetails","obj","entries","detectShell","shell","getShellPaths","homeDir","installCompletion","scriptDir","options","json","noColor","ShipError","paths","sourceScript","fishDir","success","info","sourceLine","content","prefix","warn","e","isShipError","message","uninstallCompletion","lines","filtered","i","removed","import_node_fs","import_node_os","import_node_path","import_promises","init_dist","import_yoctocolors","CONFIG_PATH","maskToken","token","readExistingConfig","runConfig","options","noColor","json","applyDim","text","applyGreen","existing","apiUrl","DEFAULT_API","existingToken","rl","prompt","input","validateToken","init_dist","init_dist","init_dist","SimpleEvents","event","handler","eventHandlers","args","handlerArray","error","err","init_dist","validateLabels","labels","LABEL_CONSTRAINTS","ShipError","normalized","label","cleaned","LABEL_PATTERN","unique","validateDeployConfig","files","config","f","DEPLOYMENT_CONFIG_FILENAME","content","text","assertShipJsonSyntax","DEFAULT_REQUEST_TIMEOUT","DEFAULT_DEPLOY_TIMEOUT","BUILD_SERVICE_BUDGET","DEFAULT_DEPLOY_BUILD_TIMEOUT","DEPLOY_VIA","listQuery","options","params","query","ApiHttp","SimpleEvents","DEFAULT_API","API_PATHS","headers","url","operationName","timeoutMs","cleanup","timeout","fetchOptions","response","ShipError","error","shipError","data","customHeaders","existingSignal","controller","timeoutId","abort","files","file","validatePassword","idempotencyKey","validateIdempotencyKey","labels","validateLabels","validateDeployConfig","flags","body","bodyHeaders","id","normalized","name","deployment","status","ttl","token","_options","indexFile","f","indexContent","init_dist","init_dist","init_md5","createSPAConfig","configString","SPA_DEFAULT_CONFIG","content","md5","calculateMD5","DEPLOYMENT_CONFIG_FILENAME","detectAndConfigureSPA","files","apiClient","options","f","spaConfig","createDeploymentResource","ctx","getApi","ensureInit","processInput","input","options","ShipError","apiClient","staticFiles","detectAndConfigureSPA","id","createDomainResource","name","createAccountResource","createTokenResource","token","Ship","options","validateCaller","ShipError","validateToken","ApiHttp","ctx","createDeploymentResource","input","opts","createDomainResource","createAccountResource","createTokenResource","error","event","handler","headers","token","value","init_env","init_dist","createDeployBody","files","context","FormData","File","FormDataEncoder","labels","via","password","flags","captcha","formData","checksums","file","ShipError","fileInstance","encoder","chunks","chunk","body","Ship","options","getENV","ShipError","env","readEnvConfig","input","paths","p","processFilesForNode","createDeployBody","import_node_os","init_dist","import_cosmiconfig","import_zod","FileConfigSchema","CREDENTIAL_FIELDS","MODULE_NAME","loadShipFile","configFile","explicitPath","home","explorer","result","error","isShipError","message","where","ShipError","issue","legacy","key","keys","path","mergeCliConfig","flags","env","file","createClient","Ship","readEnvConfig","loadShipFile","init_dist","toShipError","err","isShipError","ShipError","getUserMessage","_context","options","setupUrl","hash","domain","readField","source","field","formatDeploymentsList","result","_context","options","noColor","columns","formatTable","printCursorHint","cursor","info","formatDomainsList","formatDomain","context","_dnsRecords","_shareHash","isCreate","displayResult","verb","success","record","formatDetails","formatDeployment","claim","days","formatAccount","formatDomainVerify","formatDomainValidate","availabilityText","error","formatDomainRecords","formatDomainDns","provider","formatDomainShare","formatTokensList","formatToken","formatOutput","json","quiet","d","t","r","name","v","noun","acknowledged","output","loadPackageJson","paths","p","packageJson","helpText","noColor","applyBold","text","applyDim","icon","emoji","usageError","message","ShipError","ErrorType","collect","value","previous","parseInteger","parsed","mergeLabelOption","cmdOptions","programOpts","labels","filtered","l","mergePasswordOption","processOptions","command","options","forceColor","resolveCliToken","flags","file","loadShipFile","token","mergeCliConfig","readEnvConfig","performDeploy","client","deployPath","password","globalOptions","stats","deployOptions","abortController","spinner","yoctoSpinner","sigintHandler","buildProgram","program","err","error","str","handleUnknownSubcommand","parentName","validSubcommands","args","commandObj","unknownArg","arg","handleError","context","opts","shipError","toShipError","getUserMessage","withErrorHandling","handler","resolvedContext","config","apiUrl","createClient","result","formatOutput","cmd","helper","thisCommand","validateToken","validateApiUrl","validationError","isShipError","_options","deploymentsCmd","deployment","domainsCmd","name","resolve","data","chunk","setOptions","records","share","tokensCmd","completionCmd","scriptDir","installCompletion","uninstallCompletion","runConfig","deployShortcut","handleCompletion","isFish"]}
|
|
1
|
+
{"version":3,"sources":["../node_modules/.pnpm/@shipstatic+types@2.5.0/node_modules/@shipstatic/types/dist/index.js","../src/shared/lib/env.ts","../src/shared/lib/md5.ts","../src/shared/lib/path.ts","../src/shared/lib/deploy-paths.ts","../src/shared/lib/file-validation.ts","../src/shared/lib/junk.ts","../src/shared/lib/security.ts","../src/node/core/node-files.ts","../src/node/cli/bin.ts","../src/node/cli/index.ts","../src/node/core/config.ts","../src/shared/core/credential-schema.ts","../src/node/cli/completion.ts","../src/node/cli/completions.ts","../src/node/cli/utils.ts","../src/node/cli/config.ts","../src/node/cli/shiprc.ts","../src/node/index.ts","../src/shared/base-ship.ts","../src/shared/api/http.ts","../src/shared/events.ts","../src/shared/lib/validation.ts","../src/shared/resources.ts","../src/shared/lib/spa.ts","../src/node/core/deploy-body.ts","../src/node/cli/create-client.ts","../src/node/cli/error-handling.ts","../src/node/cli/formatters.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 * Which client made a deployment — the origin-tracking vocabulary.\n *\n * A closed set with many authors: the CLI, the SDK, the dashboard, both MCP\n * transports, the GitHub Action, the n8n node and the VS Code extension each\n * name themselves here. It lived in the API's config until 2026-08-06, where\n * being server-side made it unenforceable in the one direction that matters —\n * every client wrote a bare string, and a value outside the set was **silently\n * dropped** by the server, so a typo did not fail anywhere. It stopped\n * recording where deploys came from and said nothing.\n */\nexport const DeploymentVia = {\n WEB: 'web',\n SDK: 'sdk',\n CLI: 'cli',\n MCP: 'mcp',\n GIT: 'git',\n N8N: 'n8n',\n GPT: 'gpt',\n VSC: 'vsc',\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 * Account plan constants\n */\nexport const AccountPlan = {\n FREE: 'free',\n STANDARD: 'standard',\n SPONSORED: 'sponsored',\n ENTERPRISE: 'enterprise',\n SUSPENDED: 'suspended',\n TERMINATING: 'terminating',\n TERMINATED: 'terminated',\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 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 /** @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 /** 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.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 `Network` (which `isNetworkError` owns)\n * belongs 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 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 network: new Set([ErrorType.Network]),\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 * WHATWG has `fetch` reject with a **TypeError** on network error, and undici,\n * Chromium and Firefox comply. Bun does not: it rejects with a plain `Error`\n * carrying a system `code` string. Captured 2026-08-05 (the capture script is\n * in `tests/errors.test.ts`, \"runtime failure shapes\"):\n *\n * | failure | Node 22 / undici | Bun 1.3.14 |\n * |---------------|---------------------------|----------------------------------------------|\n * | refused | `TypeError: fetch failed` | `Error` `code: 'ConnectionRefused'` |\n * | DNS failure | `TypeError: fetch failed` | `Error` `code: 'ConnectionRefused'` |\n * | reset | `TypeError: fetch failed` | `Error` `code: 'ECONNRESET'` |\n * | TLS rejected | `TypeError: fetch failed` | `Error` `code: 'UNKNOWN_CERTIFICATE_…ERROR'` |\n *\n * So the test is the **evidence, not a list of dialect strings**: a string\n * `code` is a runtime naming a transport-level failure. An allowlist of codes\n * was written first and rejected — the TLS row alone would mean enumerating\n * BoringSSL's certificate table, and a code nobody guessed is precisely the bug\n * this closes. Two kinds of error are deliberately NOT caught: ordinary JS\n * faults carry no `code` at all, and a `DOMException`'s is a **number**, so\n * aborts and timeouts fall through to their own arms.\n *\n * The accepted trade: a caller's `TokenProvider` that throws a coded error\n * (`ENOENT` from a keychain read) is typed `Network` rather than `Api`. Both\n * are wrong for it, `Network` is the cheaper wrong — it says \"nothing was\n * exchanged\", which is true, where `Api` claims a server answered.\n */\nfunction isTransportFailure(cause) {\n if (typeof cause.code === 'string')\n return true;\n // Spec runtimes put no code on the rejection itself. The message test is what\n // keeps fetch's ARGUMENT errors out — `Failed to parse URL from …` is a\n // caller's config mistake, not a transport failure.\n return cause instanceof TypeError && cause.message.includes('fetch');\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`, `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(...)`\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 * 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 if (cause instanceof Error) {\n if (cause.name === 'AbortError') {\n return ShipError.cancelled(`${op} was cancelled`);\n }\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 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 BLOCKLIST\n// =============================================================================\n/**\n * Blocked file extensions — files that cannot be uploaded.\n *\n * We accept any file type by default and derive Content-Type from the\n * extension at serve time (via mime-db in the API worker). Unknown extensions\n * are served as `application/octet-stream` with `X-Content-Type-Options: nosniff`.\n *\n * The blocklist targets file types that pose direct security risks when hosted:\n * executables, disk images, malware vectors, dangerous scripts, and shortcuts.\n */\nexport const BLOCKED_EXTENSIONS = new Set([\n // Executables\n 'exe',\n 'msi',\n 'dll',\n 'scr',\n 'bat',\n 'cmd',\n 'com',\n 'pif',\n 'app',\n 'deb',\n 'rpm',\n // Installers\n 'pkg',\n 'mpkg',\n // Disk images\n 'dmg',\n 'iso',\n 'img',\n // Malware vectors\n 'cab',\n 'cpl',\n 'chm',\n // Dangerous scripts\n 'ps1',\n 'vbs',\n 'vbe',\n 'ws',\n 'wsf',\n 'wsc',\n 'wsh',\n 'reg',\n // Java\n 'jar',\n 'jnlp',\n // Mobile/browser packages\n 'apk',\n 'crx',\n // Shortcut/link\n 'lnk',\n 'inf',\n 'hta',\n]);\n/**\n * Check if a filename has a blocked extension.\n * Extracts the extension from the filename and checks against the blocklist.\n * Case-insensitive. Returns false for files without extensions.\n *\n * @example\n * isBlockedExtension('virus.exe') // true\n * isBlockedExtension('app.dmg') // true\n * isBlockedExtension('style.css') // false\n * isBlockedExtension('data.custom') // false\n * isBlockedExtension('README') // false\n */\nexport function isBlockedExtension(filename) {\n const dotIndex = filename.lastIndexOf('.');\n if (dotIndex === -1 || dotIndex === filename.length - 1)\n return false;\n const ext = filename.slice(dotIndex + 1).toLowerCase();\n return BLOCKED_EXTENSIONS.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.** `BLOCKED_EXTENSIONS` is the platform's\n * gate and the only thing that decides what may be hosted; this constant\n * decides what a *file dialog* shows first. The two are not two halves of one\n * policy, 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 * Kept beside `BLOCKED_EXTENSIONS` so one file holds both, which is what lets\n * `tests/validation-constants.test.ts` fence the invariant that matters: the\n * picker must never offer a file the platform will refuse.\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, CALLER),\n// the single dispatch over them (TokenKind, classifyToken), and the\n// delegated-access scopes (OAuthScope).\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 * 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-{64 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: 64,\n /** Total length of an API key including prefix (`PREFIX.length + HEX_LENGTH = 69`). */\n TOTAL_LENGTH: 69,\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-{64 hex chars}`).\n * Single source of truth used by validation utilities and auth middleware.\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: 64,\n /** Total length of a deploy token including prefix (`PREFIX.length + HEX_LENGTH = 71`). */\n TOTAL_LENGTH: 71,\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` and `DEPLOY_TOKEN` *are* `AuthMethod.API_KEY` and\n * `AuthMethod.TOKEN` — the equality is structural, so a classification flows\n * straight into an auth method and the pair can never drift. `OPAQUE` is any\n * other value — shape says nothing about it, so only a lookup can. Today the\n * server refuses every opaque bearer; the OAuth access-token population\n * resolves there when the authorization server ships.\n */\nexport const TokenKind = {\n API_KEY: AuthMethod.API_KEY,\n DEPLOY_TOKEN: AuthMethod.TOKEN,\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 return TokenKind.OPAQUE;\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 a client token of any population. Classifies by shape and applies\n * the matching format rules: `ship-` keys and `deploy-` deploy tokens are\n * validated strictly; opaque tokens (OAuth access tokens, future populations)\n * only need to be non-empty — their validity is the server's to decide.\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.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// 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 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 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 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 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 isBlockedExtension,\n} from '@shipstatic/types';\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 // Pre-compute filename validation\n const nameValidation = file.name\n ? validateFileName(file.name)\n : { valid: false, reason: 'File name cannot be empty' };\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 } else if (!nameValidation.valid) {\n fileStatus = FILE_VALIDATION_STATUS.VALIDATION_FAILED;\n statusMessage = nameValidation.reason || 'Invalid file name';\n errors.push({\n file: file.name,\n message: statusMessage,\n });\n }\n\n // Blocked extension check\n else if (isBlockedExtension(file.name)) {\n fileStatus = FILE_VALIDATION_STATUS.VALIDATION_FAILED;\n statusMessage = `File extension not allowed: \"${file.name}\"`;\n errors.push({\n file: file.name,\n message: statusMessage,\n });\n }\n\n // File size validation\n else if (file.size > config.maxFileSize) {\n fileStatus = FILE_VALIDATION_STATUS.VALIDATION_FAILED;\n statusMessage = `File size (${formatFileSize(file.size)}) exceeds limit of ${formatFileSize(config.maxFileSize)}`;\n errors.push({\n file: file.name,\n message: statusMessage,\n });\n }\n\n // Total size validation\n else {\n totalSize += file.size;\n if (totalSize > config.maxTotalSize) {\n fileStatus = FILE_VALIDATION_STATUS.VALIDATION_FAILED;\n statusMessage = `Total size would exceed limit of ${formatFileSize(config.maxTotalSize)}`;\n errors.push({\n file: file.name,\n message: statusMessage,\n });\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 { isBlockedExtension, ShipError } from '@shipstatic/types';\nimport { validateFileName } from './file-validation.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 * Validate a deploy file's name and extension.\n * Rejects unsafe filenames (shell/URL-dangerous chars, reserved names)\n * and blocked file extensions (.exe, .msi, .dll, etc.).\n *\n * @param deployPath - The deployment path to validate\n * @param sourceIdentifier - Human-readable identifier for error messages\n * @throws {ShipError} If the filename is unsafe or extension is blocked\n */\nexport function validateDeployFile(deployPath: string, sourceIdentifier: string): void {\n const nameCheck = validateFileName(deployPath);\n if (!nameCheck.valid) {\n throw ShipError.business(nameCheck.reason || 'Invalid file name');\n }\n\n if (isBlockedExtension(deployPath)) {\n throw ShipError.business(`File extension not allowed: \"${sourceIdentifier}\"`);\n }\n}\n","/**\n * @file Node.js-specific file utilities for the Ship SDK.\n * Provides helpers for recursively discovering, filtering, and preparing files for deploy in Node.js.\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 { optimizeDeployPaths } from '../../shared/lib/deploy-paths.js';\nimport { getENV } from '../../shared/lib/env.js';\nimport { filterJunk } from '../../shared/lib/junk.js';\nimport { calculateMD5 } from '../../shared/lib/md5.js';\nimport { findCommonParent } from '../../shared/lib/path.js';\nimport { validateDeployFile, validateDeployPath } from '../../shared/lib/security.js';\nimport type { DeploymentOptions, StaticFile } from '../../shared/types.js';\n\n/**\n * Recursive function to walk directory and return all file paths.\n * Includes symlink loop protection to prevent infinite recursion.\n * @param dirPath - Directory path to traverse\n * @param visited - Set of already visited real paths (for cycle detection)\n * @returns Array of absolute file paths in the directory\n */\nfunction findAllFilePaths(dirPath: string, visited: Set<string> = new Set()): string[] {\n const results: string[] = [];\n\n // Resolve the real path to detect symlink cycles\n const realPath = fs.realpathSync(dirPath);\n if (visited.has(realPath)) {\n // Already visited this directory (symlink cycle) - skip to prevent infinite loop\n return results;\n }\n visited.add(realPath);\n\n const entries = fs.readdirSync(dirPath);\n\n for (const entry of entries) {\n const fullPath = path.join(dirPath, entry);\n const stats = fs.statSync(fullPath);\n\n if (stats.isDirectory()) {\n const subFiles = findAllFilePaths(fullPath, visited);\n results.push(...subFiles);\n } else if (stats.isFile()) {\n results.push(fullPath);\n }\n }\n\n return results;\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 {ShipClientError} 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 // Check input directories for unbuilt project markers before recursive walk\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 handled in the existing flatMap below\n }\n }\n\n // 1. Discover all unique, absolute file paths from the input list\n const absolutePaths = paths.flatMap((p) => {\n const absPath = path.resolve(p);\n try {\n const stats = fs.statSync(absPath);\n return stats.isDirectory() ? findAllFilePaths(absPath) : [absPath];\n } catch (_error) {\n throw ShipError.file(`Path does not exist: ${p}`, { filePath: p });\n }\n });\n const uniquePaths = [...new Set(absolutePaths)];\n\n // 2. Determine base path for content paths (from INPUT paths, not discovered files)\n const inputAbsolutePaths = paths.map((p) => path.resolve(p));\n const inputBasePath = findCommonParent(\n inputAbsolutePaths.map((p) => {\n try {\n const stats = fs.statSync(p);\n return stats.isDirectory() ? p : path.dirname(p);\n } catch {\n return path.dirname(p);\n }\n }),\n );\n\n // 3. Compute content paths (relative to upload root)\n const contentPaths = uniquePaths.map((absPath) => {\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 // 4. Optimize paths for deployment (strip common root, flatten)\n const deployFiles = optimizeDeployPaths(contentPaths, {\n flatten: options.pathDetect !== false,\n });\n const deployPaths = deployFiles.map((f) => f.path);\n\n // 5. Filter junk from deploy paths\n const filteredSet = new Set(filterJunk(deployPaths));\n if (filteredSet.size === 0) {\n return [];\n }\n\n // 6. Collect valid file pairs (absolute path for reading, deploy path for output)\n const validAbsPaths: string[] = [];\n const validDeployPaths: string[] = [];\n for (let i = 0; i < uniquePaths.length; i++) {\n if (filteredSet.has(deployPaths[i])) {\n validAbsPaths.push(uniquePaths[i]);\n validDeployPaths.push(deployPaths[i]);\n }\n }\n\n // 7. Process files into StaticFile objects\n const results: StaticFile[] = [];\n let totalSize = 0;\n if (!platformLimits) {\n throw ShipError.config(\n 'Platform limits not provided. processFilesForNode requires the limits ' +\n 'argument — pass `ship.getLimits()` result.',\n );\n }\n\n for (let i = 0; i < validAbsPaths.length; i++) {\n const filePath = validAbsPaths[i];\n const deployPath = validDeployPaths[i];\n\n try {\n // Security validation (shared with browser) — fail fast before any I/O\n validateDeployPath(deployPath, filePath);\n\n const stats = fs.statSync(filePath);\n\n // Skip empty files — R2 cannot store zero-byte objects\n if (stats.size === 0) {\n continue;\n }\n\n // Filename and extension validation (shared with browser)\n validateDeployFile(deployPath, filePath);\n\n // Validate file sizes\n if (stats.size > platformLimits.maxFileSize) {\n throw ShipError.business(\n `File ${filePath} is too large. Maximum allowed size is ${platformLimits.maxFileSize / (1024 * 1024)}MB.`,\n );\n }\n totalSize += stats.size;\n if (totalSize > platformLimits.maxTotalSize) {\n throw ShipError.business(\n `Total deploy size is too large. Maximum allowed is ${platformLimits.maxTotalSize / (1024 * 1024)}MB.`,\n );\n }\n\n const content = fs.readFileSync(filePath);\n const { md5 } = await calculateMD5(content);\n\n results.push({\n path: deployPath,\n content,\n size: content.length,\n md5,\n });\n } catch (error) {\n // Re-throw ShipError instances directly\n if (isShipError(error)) {\n throw error;\n }\n // Convert file system errors to ShipError with clear message\n const errorMessage = error instanceof Error ? error.message : String(error);\n throw ShipError.file(`Failed to read file \"${filePath}\": ${errorMessage}`, { filePath });\n }\n }\n\n // Final validation\n if (results.length > platformLimits.maxFilesCount) {\n throw ShipError.business(\n `Too many files to deploy. Maximum allowed is ${platformLimits.maxFilesCount} files.`,\n );\n }\n\n return results;\n}\n","/**\n * @file The executable. `dist/cli.cjs` is this file; everything it drives lives\n * in `./index.ts`, which is a library.\n *\n * The split exists so that importing the command tree has NO side effects.\n * Until 2026-07-29 the bin block sat at the bottom of `index.ts` behind\n * `if (process.env.NODE_ENV !== 'test')` — production behaviour keyed on a test\n * environment variable, which is a smell on its own and a real constraint in\n * practice: anything wanting to READ the tree (the suite, and now the\n * completion generator) had to either be a test or pretend to be one. A module\n * boundary says the same thing without the conditional, and says it to every\n * caller rather than only to the one that sets the variable.\n *\n * **No `process.exit` on this path.** Outcomes land in `process.exitCode` and\n * the process ends when the event loop drains, so buffered stdout is never\n * truncated on a pipe. Exercised by the child-process smoke tier\n * (`tests/node/cli/smoke.test.ts`), never in-process.\n */\nimport { CommanderError } from 'commander';\nimport { buildProgram } from './index.js';\n\nbuildProgram()\n .parseAsync(process.argv)\n .catch((err: unknown) => {\n // The exit override reports errors before throwing; the bare CommanderError\n // that reaches here only carries the exit code (help/version carry 0).\n if (err instanceof CommanderError) {\n process.exitCode = err.exitCode;\n return;\n }\n throw err;\n });\n","/**\n * @file Main entry point for the Ship CLI.\n *\n * `buildProgram()` constructs the full Commander tree and is the seam the\n * in-process tests drive (`tests/node/cli/index.test.ts`). The bin execution\n * path at the bottom of this file is what `dist/cli.cjs` runs and is proven\n * by the child-process smoke tier. Commander instances are not reusable\n * across parses (option state accumulates), so every caller gets a fresh\n * tree.\n */\n\nimport { existsSync, readFileSync, statSync } from 'node:fs';\nimport * as path from 'node:path';\nimport {\n type Deployment,\n DeploymentVia,\n type DeploymentViaType,\n ErrorType,\n isShipError,\n normalizeVia,\n ShipError,\n validateApiUrl,\n validateToken,\n} from '@shipstatic/types';\nimport { Command, CommanderError, Help, InvalidArgumentError } from 'commander';\nimport { bold, dim } from 'yoctocolors';\nimport { readEnvConfig } from '../core/config.js';\nimport type { Ship } from '../index.js';\nimport { installCompletion, uninstallCompletion } from './completion.js';\nimport { subcommandsOf } from './completions.js';\nimport { runConfig } from './config.js';\nimport { createClient, mergeCliConfig } from './create-client.js';\nimport { getUserMessage, toShipError } from './error-handling.js';\nimport { formatOutput, type OutputContext } from './formatters.js';\nimport { loadShipFile } from './shiprc.js';\nimport type {\n CLIResult,\n DeployCommandOptions,\n GlobalOptions,\n LabelOptions,\n ListCommandOptions,\n TokenCreateCommandOptions,\n} from './types.js';\nimport { error } from './utils.js';\n\n// Load package.json for version\nfunction loadPackageJson(): { version: string } {\n const paths = [\n path.resolve(__dirname, '../package.json'),\n path.resolve(__dirname, '../../package.json'),\n ];\n for (const p of paths) {\n try {\n return JSON.parse(readFileSync(p, 'utf-8'));\n } catch {}\n }\n return { version: '0.0.0' };\n}\n\nconst packageJson = loadPackageJson();\n\n/**\n * The CLI's FRONT PAGE — what `ship`, `ship --help`, and `ship help` render.\n * This hand-written overview is the product's front door and is kept exactly\n * as designed; do not swap it for generated output.\n *\n * Subcommand help is the other scope: `ship deployments --help` and\n * `ship help deployments` render Commander's NATIVE help, which knows that\n * command's exact usage, arguments, and options. The split lives in ONE\n * conditional (`configureHelp.formatHelp` in `buildProgram`): root → this\n * page, everything else → native. Riding the built-in machinery is what\n * makes every help route credential-free and lets `--help` beside a parse\n * error win (Commander processes it before argument errors).\n */\nfunction helpText(noColor?: boolean): string {\n const applyBold = (text: string) => (noColor ? text : bold(text));\n const applyDim = (text: string) => (noColor ? text : dim(text));\n const icon = (emoji: string) => (noColor ? '' : `${emoji} `);\n\n const output = `${applyBold('USAGE')}\n ship <path> ${icon('🚀')}Deploy static sites with simplicity\n\n${applyBold('COMMANDS')}\n ${icon('📦')}${applyBold('Deployments')}\n ship deployments list List deployments\n ship deployments upload <path> Upload deployment from file or directory\n ship deployments get <deployment> Show deployment information\n ship deployments set <deployment> Set deployment labels\n ship deployments delete <deployment> Delete deployment permanently\n\n ${icon('🌎')}${applyBold('Domains')}\n ship domains list List domains\n ship domains set <name> [deployment] Create domain, link to deployment, or update labels\n ship domains get <name> Show domain information\n ship domains validate <name> Check if domain name is valid and available\n ship domains records <name> Show required DNS records for domain setup\n ship domains dns <name> Look up DNS provider for a domain\n ship domains share <name> Get shareable DNS setup link\n ship domains verify <name> Trigger DNS verification for external domain\n ship domains delete <name> Delete domain permanently\n\n ${icon('🔑')}${applyBold('Tokens')}\n ship tokens list List deploy tokens\n ship tokens create Create a new deploy token\n ship tokens get <token> Show token information\n ship tokens delete <token> Delete token permanently\n\n ${icon('⚙️')}${applyBold('Setup')}\n ship config Save your token\n ship whoami Get current account information\n ship ping Check API connectivity\n\n ${icon('🛠️')}${applyBold('Completion')}\n ship completion install Install shell completion script\n ship completion uninstall Uninstall shell completion script\n\n${applyBold('FLAGS')}\n --token <token> Any ship token: API key (ship-…) or deploy token (deploy-…)\n --config <file> Custom config file path\n --label <label> Set label (repeatable, replaces all existing)\n --password <password> Password-protect this deployment\n --no-path-detect Disable automatic path optimization and flattening\n --no-spa-detect Disable automatic SPA detection and configuration\n --no-color Disable colored output\n --json Output results in JSON format\n -q, --quiet Output only the resource identifier\n -V, --version Show version information\n\n${applyBold('EXAMPLES')}\n ship ./dist\n ship domains set www.example.com happy-cat-abc1234.shipstatic.com\n ship ./dist -q | ship domains set www.example.com\n\n${applyDim('Please report any issues to https://github.com/shipstatic/ship/issues')}\n`;\n\n return output;\n}\n\n/**\n * A CLI grammar error — a command or flag this binary does not have.\n *\n * Deliberately STATUSLESS. `ErrorResponse.status` is documented \"(API\n * contexts)\", and no API ever sees `ship foo`, so a 400 here would be an HTTP\n * fact about an exchange that never happened. The dual-validation errors are\n * the opposite case and rightly DO carry 400 — a blocked extension or a bad\n * label is a rule the server enforces too, and the error must read the same\n * wherever it was caught. See CLAUDE.md, \"What a status means\".\n */\nconst usageError = (message: string) => new ShipError(ErrorType.Validation, message);\n\n/**\n * Collector function for Commander.js to accumulate repeated option values.\n * Used for --label flag that can be specified multiple times.\n */\nfunction collect(value: string, previous: string[] = []): string[] {\n return previous.concat([value]);\n}\n\n/**\n * Argument parser for integer option values (`--ttl`, `--limit`). A mangled\n * number must fail HERE with a clear message — a bare `parseInt` once turned\n * `--ttl abc` into `NaN` on the wire. Range rules stay server-side (the SDK\n * is a transparent pipe); only the not-a-number corruption is a client bug.\n */\nfunction parseInteger(value: string): number {\n const parsed = Number.parseInt(value, 10);\n if (Number.isNaN(parsed)) {\n throw new InvalidArgumentError('Not a number.');\n }\n return parsed;\n}\n\n/**\n * Merge label options from command and program levels.\n * Commander.js sometimes routes --label to program level instead of command level.\n */\nfunction mergeLabelOption(\n cmdOptions: LabelOptions | undefined,\n programOpts: LabelOptions | undefined,\n): string[] | undefined {\n const labels = cmdOptions?.label?.length ? cmdOptions.label : programOpts?.label;\n if (!labels?.length) return undefined;\n // Filter empty strings: --label '' means \"clear all labels\"\n const filtered = labels.filter((l) => l !== '');\n return filtered.length ? filtered : [];\n}\n\n/**\n * Merge password options from command and program levels.\n * Commander.js sometimes routes --password to program level instead of command level.\n *\n * An empty `--password ''` is forwarded to the SDK validator so the user\n * sees a clear length error rather than a silent drop. An empty\n * `SHIP_PASSWORD` is coerced to undefined, matching how SHIP_TOKEN\n * and SHIP_API_URL treat empty env vars (CI/Docker\n * often sets unset vars to \"\" — see core/config.ts).\n */\nfunction mergePasswordOption(\n cmdOptions: { password?: string } | undefined,\n programOpts: { password?: string } | undefined,\n): string | undefined {\n return cmdOptions?.password ?? programOpts?.password ?? (process.env.SHIP_PASSWORD || undefined);\n}\n\n/**\n * Process CLI options using Commander's built-in option merging.\n * Applies CLI-specific transformations (validation is done in preAction hook).\n */\nfunction processOptions(command: Command): GlobalOptions {\n const options = command.optsWithGlobals();\n\n // Convert Commander.js --no-color flag (color: false) to our convention (noColor: true)\n if (options.color === false) {\n options.noColor = true;\n }\n\n // Auto-suppress color when stdout is not a TTY (like grep --color=auto)\n // Also respect NO_COLOR convention (https://no-color.org/)\n // FORCE_COLOR overrides for CI environments that explicitly want color\n // FORCE_COLOR=0 means \"force no color\" per the convention (0=off, 1/2/3=on)\n const forceColor = !!process.env.FORCE_COLOR && process.env.FORCE_COLOR !== '0';\n if (!options.noColor && !forceColor) {\n if (!process.stdout.isTTY || process.env.NO_COLOR !== undefined) {\n options.noColor = true;\n }\n }\n\n return options as GlobalOptions;\n}\n\n/**\n * What a command GROUP does when none of its own subcommands matched: name the\n * unknown word, then print scoped usage rather than the whole front page — the\n * user already knows which group they are in.\n *\n * **It states nothing about the tree.** Commander binds `this` to the command\n * and collects the leftover words in `this.args`, so the group's name and its\n * subcommands are read from the tree at parse time, exactly as the completion\n * renderer reads them. A list that cannot be edited cannot drift.\n *\n * See CLAUDE.md, \"the last hand-written copy of the command tree\".\n */\nfunction handleUnknownSubcommand(this: Command): void {\n const options = processOptions(this);\n const subcommands = subcommandsOf(this).map((c) => c.name());\n\n const unknown = this.args.find((arg) => !subcommands.includes(arg));\n if (unknown) {\n error(usageError(`unknown command '${unknown}'`), options.json, options.noColor);\n }\n\n if (!options.json) {\n console.log(`usage: ship ${this.name()} <${subcommands.join('|')}>\\n`);\n }\n process.exitCode = 1;\n}\n\n/**\n * The credential the CLI actually resolved (flag > env > file). The error\n * path must diagnose with the same lens the client was built with — a user\n * whose `SHIP_TOKEN` or `.shiprc` token was rejected is credentialed, and\n * the anonymous-user hint would misdiagnose their failure. A config file\n * that fails to load counts as no file credential: that failure is already\n * the error being reported.\n */\nfunction resolveCliToken(flags: {\n config?: string;\n apiUrl?: string;\n token?: string;\n}): string | undefined {\n let file = {};\n try {\n file = loadShipFile(flags.config);\n } catch {}\n // Flags, env, and files only ever hold strings — provider functions exist\n // solely as constructor arguments, which the CLI never passes.\n const token = mergeCliConfig(flags, readEnvConfig(), file).token;\n return typeof token === 'string' ? token : undefined;\n}\n\n/** Spinner instance type from yocto-spinner */\ninterface Spinner {\n start(): Spinner;\n stop(): void;\n}\n\n/**\n * Common deploy logic used by both shortcut and explicit commands.\n */\nasync function performDeploy(\n client: Ship,\n deployPath: string,\n labels: string[] | undefined,\n password: string | undefined,\n cmdOptions: DeployCommandOptions | undefined,\n globalOptions: GlobalOptions,\n): Promise<Deployment> {\n if (!existsSync(deployPath)) {\n throw ShipError.file(`${deployPath} path does not exist`, { filePath: deployPath });\n }\n\n const stats = statSync(deployPath);\n if (!stats.isDirectory() && !stats.isFile()) {\n throw ShipError.file(`${deployPath} path must be a file or directory`, {\n filePath: deployPath,\n });\n }\n\n const deployOptions: {\n via: DeploymentViaType;\n labels?: string[];\n password?: string;\n idempotencyKey?: string;\n pathDetect?: boolean;\n spaDetect?: boolean;\n signal?: AbortSignal;\n } = {\n // `SHIP_VIA` exists so a wrapper can relabel the origin — the GitHub\n // Action sends `git`. An unrecognized label falls back to `cli` rather\n // than riding along: the server silently drops what it does not know, so\n // forwarding a typo recorded NOTHING, while the honest default records\n // the truth (this deploy did come from the CLI).\n via: normalizeVia(process.env.SHIP_VIA) ?? DeploymentVia.CLI,\n };\n\n // Handle labels\n if (labels !== undefined) deployOptions.labels = labels;\n\n // `SHIP_IDEMPOTENCY_KEY` is the subprocess-wrapping consumer's half of the\n // SDK's `idempotencyKey` — the same tier and the same reason as\n // `SHIP_PASSWORD`: an integration that shells out to `ship` has no other way\n // to reach a per-call option. The GitHub Action derives one per workflow\n // run, so pressing \"re-run jobs\" replays the original 201 instead of\n // deploying twice. Empty is absence (CI sets unset variables to \"\"); the\n // value itself is validated by the SDK, which is where the wire rule lives.\n const idempotencyKey = process.env.SHIP_IDEMPOTENCY_KEY || undefined;\n if (idempotencyKey !== undefined) deployOptions.idempotencyKey = idempotencyKey;\n\n // Empty password strings flow through to the SDK validator (clear length\n // error) instead of being silently dropped.\n if (password !== undefined) deployOptions.password = password;\n\n // Handle detection flags\n if (cmdOptions?.noPathDetect !== undefined) {\n deployOptions.pathDetect = !cmdOptions.noPathDetect;\n }\n if (cmdOptions?.noSpaDetect !== undefined) {\n deployOptions.spaDetect = !cmdOptions.noSpaDetect;\n }\n\n // Cancellation support\n const abortController = new AbortController();\n deployOptions.signal = abortController.signal;\n\n // Spinner (TTY only, not JSON, not --no-color)\n let spinner: Spinner | null = null;\n if (\n process.stdout.isTTY &&\n !globalOptions.json &&\n !globalOptions.quiet &&\n !globalOptions.noColor\n ) {\n const { default: yoctoSpinner } = await import('yocto-spinner');\n spinner = yoctoSpinner({ text: 'uploading…' }).start();\n }\n\n const sigintHandler = () => {\n abortController.abort();\n if (spinner) spinner.stop();\n process.exit(130);\n };\n process.on('SIGINT', sigintHandler);\n\n try {\n return await client.deployments.upload(deployPath, deployOptions);\n } finally {\n process.removeListener('SIGINT', sigintHandler);\n if (spinner) spinner.stop();\n }\n}\n\n/**\n * Build the complete `ship` command tree.\n *\n * Everything that closes over the program instance — the exit override, the\n * error handler, the action wrapper — lives inside this factory so that each\n * call returns a fully independent tree. The bin path below builds one and\n * parses `process.argv`; in-process tests build one per invocation.\n */\nexport function buildProgram(): Command {\n const program = new Command();\n\n // Override Commander.js error handling while preserving help/version behavior.\n //\n // Nothing in the tree calls `process.exit` — actions and this override set\n // `process.exitCode` or throw a `CommanderError`, and the bin path lets the\n // process end naturally once the event loop drains. That is Commander's own\n // recommended shape: `process.exit` can truncate stdout still buffered on a\n // pipe, and a tree that never exits mid-flight is drivable in-process.\n program\n .exitOverride((err) => {\n // Clean exits (help/version) — nothing to add, let the bin observe it.\n if (err.code === 'commander.help' || err.code === 'commander.version' || err.exitCode === 0) {\n throw err;\n }\n\n const globalOptions = processOptions(program);\n\n let message = err.message || 'unknown command error';\n message = message\n .replace(/^error: /, '')\n .replace(/\\n.*/, '')\n .replace(/\\.$/, '')\n .toLowerCase();\n\n // A Commander parse failure is a usage error — typed as such so\n // `--json` carries `validation_failed` rather than bare prose.\n error(usageError(message), globalOptions.json, globalOptions.noColor);\n\n if (!globalOptions.json) {\n program.outputHelp();\n }\n\n throw err;\n })\n .configureOutput({\n writeErr: (str) => {\n if (!str.startsWith('error:')) {\n process.stderr.write(str);\n }\n },\n writeOut: (str) => process.stdout.write(str),\n });\n\n /**\n * Error handler - outputs errors consistently in text or JSON format.\n * Message formatting is delegated to the error-handling module.\n */\n function handleError(err: unknown) {\n const opts = processOptions(program);\n const shipError = toShipError(err);\n\n // Text translates, JSON transmits: the machine channel emits the error's\n // own `ErrorResponse`, so `error` names the ErrorType the API produced;\n // the human channel gets the CLI's actionable rewording of it.\n if (opts.json) {\n error(shipError, true, opts.noColor);\n } else {\n // Only the auth branch consults the credential, and resolving it reads\n // `.shiprc` — so a local failure (`completion install`, `config`) must\n // not pay for it. Eager resolution made every error touch the disk.\n const message = getUserMessage(shipError, {\n token: shipError.isAuthError() ? resolveCliToken(program.opts()) : undefined,\n });\n error(message, false, opts.noColor);\n // Show help only for unknown command errors (user CLI mistake)\n if (shipError.type === ErrorType.Validation && message.includes('unknown command')) {\n program.outputHelp();\n }\n }\n\n process.exitCode = 1;\n }\n\n /**\n * Wrapper for CLI actions: one client, one output path, one error path.\n *\n * The context says what the command IS — never what the caller typed, since\n * the arguments are the request and every sentence about a result is composed\n * from the response. It reaches `formatOutput` and nothing else: the error\n * path has no use for it, because the wire message already names the\n * resource, which is exactly why the CLI relays it.\n */\n function withErrorHandling<T extends unknown[], R extends CLIResult>(\n handler: (client: Ship, options: GlobalOptions, ...args: T) => Promise<R>,\n context: OutputContext,\n ) {\n return async function (this: Command, ...args: T) {\n const globalOptions = processOptions(this);\n\n try {\n const { config, apiUrl, token } = program.opts();\n const client = createClient({ config, apiUrl, token });\n const result = await handler(client, globalOptions, ...args);\n formatOutput(result, context, {\n json: globalOptions.json,\n quiet: globalOptions.quiet,\n noColor: globalOptions.noColor,\n });\n } catch (err) {\n handleError(err);\n }\n };\n }\n\n program\n .name('ship')\n .description('🚀 Deploy static sites with simplicity')\n .version(packageJson.version, '-V, --version', 'Show version information')\n .option('--token <token>', 'Any ship token: API key (ship-…) or deploy token (deploy-…)')\n .option('--config <file>', 'Custom config file path')\n .option('--api-url <url>', 'API URL (for development)')\n .option('--json', 'Output results in JSON format')\n .option('-q, --quiet', 'Output only the resource identifier')\n .option('--no-color', 'Disable colored output')\n // Two help scopes, one machinery: the ROOT renders our hand-written\n // front page (helpText — the kept design); subcommands render\n // Commander's native scoped help, which knows their exact usage and\n // options. `Help.prototype` is the escape past our own override, which\n // subcommands inherit at registration.\n .helpOption('-h, --help', 'Display help for command')\n .helpCommand('help [command]', 'Display help for command')\n .configureHelp({\n formatHelp: (cmd, helper) =>\n cmd.parent === null\n ? `${helpText(processOptions(cmd).noColor)}\\n`\n : Help.prototype.formatHelp.call(helper, cmd, helper),\n })\n // Recorded opt-out of v13+'s excess-arguments error: the deploy shortcut\n // takes `[path]` with trailing noise tolerated, and unknown subcommands\n // must REACH handleUnknownSubcommand (as excess args) to get scoped\n // usage instead of Commander's generic \"too many arguments\".\n .allowExcessArguments();\n\n // Validate options early - before any action is executed\n program.hook('preAction', (thisCommand) => {\n const options = processOptions(thisCommand);\n\n try {\n if (options.token && typeof options.token === 'string') {\n validateToken(options.token);\n }\n\n if (options.apiUrl && typeof options.apiUrl === 'string') {\n validateApiUrl(options.apiUrl);\n }\n } catch (validationError) {\n if (isShipError(validationError)) {\n error(validationError, options.json, options.noColor);\n // Already reported — the bare CommanderError just carries the code.\n throw new CommanderError(1, 'ship.invalidOption', validationError.message);\n }\n throw validationError;\n }\n });\n\n // Ping command\n program\n .command('ping')\n .description('Check API connectivity')\n .action(\n withErrorHandling(\n // Reachability is the absence of a throw: `ping()` resolves the server\n // clock or raises a typed error, so there is nothing here to test. The\n // exit code follows — 0 on resolve, 1 through `handleError` — which is\n // the same composability `domains validate` relies on.\n (client: Ship, _options: GlobalOptions) => client.ping(),\n { operation: 'ping' },\n ),\n );\n\n // Whoami shortcut - alias for account get\n program\n .command('whoami')\n .description('Get current account information')\n .action(\n withErrorHandling((client: Ship, _options: GlobalOptions) => client.whoami(), {\n operation: 'get',\n resource: 'account',\n }),\n );\n\n // Deployments commands\n const deploymentsCmd = program\n .command('deployments')\n .description('Manage deployments')\n .enablePositionalOptions()\n .action(handleUnknownSubcommand);\n\n deploymentsCmd\n .command('list')\n .description('List deployments (one page — see --limit and --cursor)')\n .option('--limit <count>', 'Maximum number of results per page', parseInteger)\n .option('--cursor <cursor>', \"Continue from a previous page's cursor\")\n .action(\n withErrorHandling(\n (client: Ship, _options: GlobalOptions, cmdOptions: ListCommandOptions) =>\n client.deployments.list(cmdOptions),\n { operation: 'list', resource: 'deployment' },\n ),\n );\n\n deploymentsCmd\n .command('upload <path>')\n .description('Upload deployment from file or directory')\n .passThroughOptions()\n .option('--label <label>', 'Label to add (can be repeated)', collect, [])\n .option('--password <password>', 'Password-protect this deployment')\n .option('--no-path-detect', 'Disable automatic path optimization and flattening')\n .option('--no-spa-detect', 'Disable automatic SPA detection and configuration')\n .action(\n withErrorHandling(\n (\n client: Ship,\n options: GlobalOptions,\n deployPath: string,\n cmdOptions: DeployCommandOptions,\n ) =>\n performDeploy(\n client,\n deployPath,\n mergeLabelOption(cmdOptions, program.opts() as LabelOptions),\n mergePasswordOption(cmdOptions, program.opts() as { password?: string }),\n cmdOptions,\n options,\n ),\n { operation: 'upload', resource: 'deployment' },\n ),\n );\n\n deploymentsCmd\n .command('get <deployment>')\n .description('Show deployment information')\n .action(\n withErrorHandling(\n (client: Ship, _options: GlobalOptions, deployment: string) =>\n client.deployments.get(deployment),\n { operation: 'get', resource: 'deployment' },\n ),\n );\n\n deploymentsCmd\n .command('set <deployment>')\n .description('Set deployment labels')\n .passThroughOptions()\n .option('--label <label>', 'Label to set (can be repeated)', collect, [])\n .action(\n withErrorHandling(\n async (\n client: Ship,\n _options: GlobalOptions,\n deployment: string,\n cmdOptions: LabelOptions,\n ) => {\n const labels = mergeLabelOption(cmdOptions, program.opts() as LabelOptions) || [];\n return client.deployments.set(deployment, { labels });\n },\n {\n operation: 'set',\n resource: 'deployment',\n },\n ),\n );\n\n deploymentsCmd\n .command('delete <deployment>')\n .description('Delete deployment permanently')\n .action(\n withErrorHandling(\n (client: Ship, _options: GlobalOptions, deployment: string) =>\n client.deployments.delete(deployment),\n {\n operation: 'delete',\n resource: 'deployment',\n },\n ),\n );\n\n // Domains commands\n const domainsCmd = program\n .command('domains')\n .description('Manage domains')\n .enablePositionalOptions()\n .action(handleUnknownSubcommand);\n\n domainsCmd\n .command('list')\n .description('List domains (one page — see --limit and --cursor)')\n .option('--limit <count>', 'Maximum number of results per page', parseInteger)\n .option('--cursor <cursor>', \"Continue from a previous page's cursor\")\n .action(\n withErrorHandling(\n (client: Ship, _options: GlobalOptions, cmdOptions: ListCommandOptions) =>\n client.domains.list(cmdOptions),\n { operation: 'list', resource: 'domain' },\n ),\n );\n\n domainsCmd\n .command('get <name>')\n .description('Show domain information')\n .action(\n withErrorHandling(\n (client: Ship, _options: GlobalOptions, name: string) => client.domains.get(name),\n { operation: 'get', resource: 'domain' },\n ),\n );\n\n domainsCmd\n .command('validate <name>')\n .description('Check if domain name is valid and available')\n .action(\n withErrorHandling(\n async (client: Ship, _options: GlobalOptions, name: string) => {\n const result = await client.domains.validate(name);\n if (!result.valid) process.exitCode = 1;\n return result;\n },\n { operation: 'validate', resource: 'domain' },\n ),\n );\n\n domainsCmd\n .command('verify <name>')\n .description('Trigger DNS verification for external domain')\n .action(\n withErrorHandling(\n (client: Ship, _options: GlobalOptions, name: string) => client.domains.verify(name),\n { operation: 'verify', resource: 'domain' },\n ),\n );\n\n domainsCmd\n .command('records <name>')\n .description('Show required DNS records for domain setup')\n .action(\n withErrorHandling(\n (client: Ship, _options: GlobalOptions, name: string) => client.domains.records(name),\n { operation: 'records', resource: 'domain' },\n ),\n );\n\n domainsCmd\n .command('dns <name>')\n .description('Look up DNS provider for a domain')\n .action(\n withErrorHandling(\n (client: Ship, _options: GlobalOptions, name: string) => client.domains.dns(name),\n { operation: 'dns', resource: 'domain' },\n ),\n );\n\n domainsCmd\n .command('share <name>')\n .description('Get shareable DNS setup link')\n .action(\n withErrorHandling(\n (client: Ship, _options: GlobalOptions, name: string) => client.domains.share(name),\n { operation: 'share', resource: 'domain' },\n ),\n );\n\n domainsCmd\n .command('set <name> [deployment]')\n .description('Create domain, link to deployment, or update labels')\n .passThroughOptions()\n .option('--label <label>', 'Label to set (can be repeated)', collect, [])\n .action(\n withErrorHandling(\n async (\n client: Ship,\n _options: GlobalOptions,\n name: string,\n deployment: string | undefined,\n cmdOptions: LabelOptions,\n ) => {\n // Read deployment from stdin when piped (e.g., ship ./dist -q | ship domains set mysite.com)\n if (!deployment && !process.stdin.isTTY) {\n deployment = await new Promise<string | undefined>((resolve) => {\n let data = '';\n process.stdin.on('data', (chunk) => (data += chunk));\n process.stdin.on('end', () => resolve(data.trim() || undefined));\n });\n }\n\n const labels = mergeLabelOption(cmdOptions, program.opts() as LabelOptions);\n\n const setOptions: { deployment?: string; labels?: string[] } = {};\n if (deployment) setOptions.deployment = deployment;\n if (labels !== undefined) setOptions.labels = labels;\n\n // SDK returns DomainSetResult (Domain + isCreate derived from HTTP 201/200) —\n // the resource interface in @shipstatic/types declares this directly, no cast needed.\n const result = await client.domains.set(name, setOptions);\n\n // Enrich with DNS info for new external domains (pure formatter will display it)\n if (result.isCreate && name.includes('.')) {\n try {\n const [records, share] = await Promise.all([\n client.domains.records(name),\n client.domains.share(name),\n ]);\n return {\n ...result,\n _dnsRecords: records.records,\n _shareHash: share.hash,\n };\n } catch {\n // Graceful degradation - return without DNS info\n }\n }\n return result;\n },\n { operation: 'set', resource: 'domain' },\n ),\n );\n\n domainsCmd\n .command('delete <name>')\n .description('Delete domain permanently')\n .action(\n withErrorHandling(\n (client: Ship, _options: GlobalOptions, name: string) => client.domains.delete(name),\n { operation: 'delete', resource: 'domain' },\n ),\n );\n\n // Tokens commands\n const tokensCmd = program\n .command('tokens')\n .description('Manage deploy tokens')\n .enablePositionalOptions()\n .action(handleUnknownSubcommand);\n\n tokensCmd\n .command('list')\n .description('List deploy tokens (one page — see --limit and --cursor)')\n .option('--limit <count>', 'Maximum number of results per page', parseInteger)\n .option('--cursor <cursor>', \"Continue from a previous page's cursor\")\n .action(\n withErrorHandling(\n (client: Ship, _options: GlobalOptions, cmdOptions: ListCommandOptions) =>\n client.tokens.list(cmdOptions),\n { operation: 'list', resource: 'token' },\n ),\n );\n\n tokensCmd\n .command('create')\n .description('Create a new deploy token')\n .option('--ttl <seconds>', 'Time to live in seconds (default: never expires)', parseInteger)\n .option('--label <label>', 'Label to set (can be repeated)', collect, [])\n .action(\n withErrorHandling(\n (client: Ship, _options: GlobalOptions, cmdOptions: TokenCreateCommandOptions) => {\n const options: { ttl?: number; labels?: string[] } = {};\n if (cmdOptions?.ttl !== undefined) options.ttl = cmdOptions.ttl;\n const labels = mergeLabelOption(cmdOptions, program.opts() as LabelOptions);\n if (labels !== undefined) options.labels = labels;\n return client.tokens.create(options);\n },\n { operation: 'create', resource: 'token' },\n ),\n );\n\n tokensCmd\n .command('get <token>')\n .description('Show one deploy token')\n .action(\n withErrorHandling(\n (client: Ship, _options: GlobalOptions, token: string) => client.tokens.get(token),\n { operation: 'get', resource: 'token' },\n ),\n );\n\n tokensCmd\n .command('delete <token>')\n .description('Delete token permanently')\n .action(\n withErrorHandling(\n (client: Ship, _options: GlobalOptions, token: string) => client.tokens.delete(token),\n { operation: 'delete', resource: 'token' },\n ),\n );\n\n // Account commands\n const accountCmd = program\n .command('account')\n .description('Manage account')\n .action(handleUnknownSubcommand);\n\n accountCmd\n .command('get')\n .description('Show account information')\n .action(\n withErrorHandling((client: Ship, _options: GlobalOptions) => client.whoami(), {\n operation: 'get',\n resource: 'account',\n }),\n );\n\n // Completion commands\n const completionCmd = program\n .command('completion')\n .description('Setup shell completion')\n .action(handleUnknownSubcommand);\n\n completionCmd\n .command('install')\n .description('Install shell completion script')\n .action(() => {\n const options = processOptions(program);\n try {\n // The tree renders its own completion — no file is shipped to copy.\n installCompletion(program, { json: options.json, noColor: options.noColor });\n } catch (err) {\n handleError(err);\n }\n });\n\n completionCmd\n .command('uninstall')\n .description('Uninstall shell completion script')\n .action(() => {\n const options = processOptions(program);\n try {\n uninstallCompletion({ json: options.json, noColor: options.noColor });\n } catch (err) {\n handleError(err);\n }\n });\n\n // Config command\n program\n .command('config')\n .description('Save your token')\n .action(async () => {\n const options = processOptions(program);\n try {\n // `--json` is a RENDERING channel on every other command. Here it used\n // to change what the command DID — skip the prompt and print a status\n // report instead — which is a second command wearing the first one's\n // name. The report is gone; the wizard is all this is.\n if (options.json) {\n throw usageError(\n 'ship config is interactive — run it without --json (it shows the token already saved)',\n );\n }\n await runConfig({\n noColor: options.noColor,\n // `--config` names the file to read everywhere else, so it names the\n // file to write here.\n configFile: options.config,\n });\n } catch (err) {\n handleError(err);\n }\n });\n\n // Deploy shortcut as default action. Two cases are answered BEFORE the\n // wrapped handler, so no client is created and no config file is resolved\n // for them: no argument (that is help, not a deploy) and an argument that\n // is a mistyped COMMAND rather than a path — a user with a broken config\n // file must still be told \"unknown command\", not handed a config error.\n const deployShortcut = withErrorHandling(\n (client: Ship, options: GlobalOptions, deployPath: string, cmdOptions?: DeployCommandOptions) =>\n performDeploy(\n client,\n deployPath,\n mergeLabelOption(cmdOptions, program.opts() as LabelOptions),\n mergePasswordOption(cmdOptions, program.opts() as { password?: string }),\n cmdOptions,\n options,\n ),\n { operation: 'upload', resource: 'deployment' },\n );\n\n program\n .argument('[path]', 'Path to deploy')\n .option('--label <label>', 'Label to add (can be repeated)', collect, [])\n .option('--password <password>', 'Password-protect this deployment')\n .option('--no-path-detect', 'Disable automatic path optimization and flattening')\n .option('--no-spa-detect', 'Disable automatic SPA detection and configuration')\n .action(async function (this: Command, deployPath?: string, cmdOptions?: DeployCommandOptions) {\n if (!deployPath) {\n this.outputHelp();\n return;\n }\n\n // A nonexistent path with no separators, extension, or `~` reads as a\n // mistyped command (\"dist\", \"build\" and friends exist, so they still\n // deploy). Everything else falls through to performDeploy, whose\n // \"path does not exist\" error names the path.\n if (!existsSync(deployPath)) {\n const looksLikeCommand =\n !deployPath.includes('/') &&\n !deployPath.includes('\\\\') &&\n !deployPath.includes('.') &&\n !deployPath.startsWith('~');\n if (looksLikeCommand) {\n handleError(usageError(`unknown command '${deployPath}'`));\n return;\n }\n }\n\n return deployShortcut.call(this, deployPath, cmdOptions);\n });\n\n return program;\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 * Shell completion install/uninstall logic.\n * Handles bash, zsh, and fish shells.\n *\n * **Failures throw; the caller's error boundary reports them.** These commands\n * make no request, so they cannot use `withErrorHandling` — that builds a\n * `Ship`, which resolves credentials — and they take the same shape `config`\n * does instead: the action wraps the call and hands anything thrown to\n * `handleError`. That is what puts a completion failure on the same footing as\n * every other one: one writer, one exit code. Reporting inline (as this module\n * did until 2026-07-29) printed `[error] …` and then exited **0**, so\n * `ship completion install && …` proceeded after a failure.\n *\n * **Two error types, because no request means no status.** The client-only\n * types (`Network`, `Cancelled`, `File`, `Config`) are exactly the statusless\n * ones in `@shipstatic/types` — a status is an HTTP fact, and nothing here\n * speaks HTTP. So the rule is: an fs call that threw is `File`; everything else\n * is a statement about the user's shell setup, which is `Config`. `Validation`\n * and `Business` are deliberately absent — both stamp a 400 on a failure that\n * never made a request, which is a plausible lie rather than an obvious one.\n * Neither command takes an argument, so there is no input here to validate.\n */\nimport * as fs from 'node:fs';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\nimport { isShipError, ShipError } from '@shipstatic/types';\nimport type { Command } from 'commander';\nimport { renderCompletion } from './completions.js';\nimport { info, success, warn } from './utils.js';\n\nexport interface CompletionOptions {\n json?: boolean;\n noColor?: boolean;\n}\n\n/**\n * Detect current shell from environment\n */\nfunction detectShell(): 'bash' | 'zsh' | 'fish' | null {\n const shell = process.env.SHELL || '';\n if (shell.includes('bash')) return 'bash';\n if (shell.includes('zsh')) return 'zsh';\n if (shell.includes('fish')) return 'fish';\n return null;\n}\n\n/**\n * Get shell-specific paths\n */\nfunction getShellPaths(shell: 'bash' | 'zsh' | 'fish', homeDir: string) {\n switch (shell) {\n case 'bash':\n return {\n completionFile: path.join(homeDir, '.ship_completion.bash'),\n profileFile: path.join(homeDir, '.bash_profile'),\n };\n case 'zsh':\n return {\n completionFile: path.join(homeDir, '.ship_completion.zsh'),\n profileFile: path.join(homeDir, '.zshrc'),\n };\n case 'fish':\n return {\n completionFile: path.join(homeDir, '.config/fish/completions/ship.fish'),\n profileFile: null, // fish doesn't need profile sourcing\n };\n }\n}\n\n/**\n * Install shell completion script\n */\nexport function installCompletion(program: Command, options: CompletionOptions = {}): void {\n const { json, noColor } = options;\n const shell = detectShell();\n const homeDir = os.homedir();\n\n if (!shell) {\n throw ShipError.config(`unsupported shell: ${process.env.SHELL}. supported: bash, zsh, fish`);\n }\n\n const paths = getShellPaths(shell, homeDir);\n // Rendered from the tree at THIS moment, so what lands on disk always matches\n // the binary that wrote it — see `./completions.ts`.\n const script = renderCompletion(program, shell);\n\n try {\n // Fish has a different installation pattern\n if (shell === 'fish') {\n const fishDir = path.dirname(paths.completionFile);\n if (!fs.existsSync(fishDir)) {\n fs.mkdirSync(fishDir, { recursive: true });\n }\n fs.writeFileSync(paths.completionFile, script);\n success('fish completion installed successfully', json, noColor);\n info('please restart your shell to apply the changes', json, noColor);\n return;\n }\n\n // Bash and zsh: copy script and add sourcing to profile.\n // The block is newline-TERMINATED so uninstall can restore the profile\n // byte-for-byte: without it, an appended block swallowed the file's\n // original trailing newline and the round trip was lossy.\n fs.writeFileSync(paths.completionFile, script);\n const sourceLine = `# ship\\nsource '${paths.completionFile}'\\n# ship end\\n`;\n\n if (paths.profileFile) {\n if (fs.existsSync(paths.profileFile)) {\n const content = fs.readFileSync(paths.profileFile, 'utf-8');\n if (!content.includes('# ship') || !content.includes('# ship end')) {\n const prefix = content.length > 0 && !content.endsWith('\\n') ? '\\n' : '';\n fs.appendFileSync(paths.profileFile, prefix + sourceLine);\n }\n } else {\n fs.writeFileSync(paths.profileFile, sourceLine);\n }\n\n success(`completion script installed for ${shell}`, json, noColor);\n warn(`run \"source ${paths.profileFile}\" or restart your shell`, json, noColor);\n }\n } catch (e) {\n if (isShipError(e)) throw e;\n const message = e instanceof Error ? e.message : String(e);\n throw ShipError.file(`could not install completion script: ${message}`);\n }\n}\n\n/**\n * Uninstall shell completion script\n */\nexport function uninstallCompletion(options: CompletionOptions = {}): void {\n const { json, noColor } = options;\n const shell = detectShell();\n const homeDir = os.homedir();\n\n if (!shell) {\n throw ShipError.config(`unsupported shell: ${process.env.SHELL}. supported: bash, zsh, fish`);\n }\n\n const paths = getShellPaths(shell, homeDir);\n\n try {\n // Fish: just remove the file\n if (shell === 'fish') {\n if (fs.existsSync(paths.completionFile)) {\n fs.unlinkSync(paths.completionFile);\n success('fish completion uninstalled successfully', json, noColor);\n } else {\n warn('fish completion was not installed', json, noColor);\n }\n info('please restart your shell to apply the changes', json, noColor);\n return;\n }\n\n // Bash and zsh: remove file and clean profile\n if (fs.existsSync(paths.completionFile)) {\n fs.unlinkSync(paths.completionFile);\n }\n\n if (!paths.profileFile) return;\n\n if (!fs.existsSync(paths.profileFile)) {\n throw ShipError.config('profile file not found');\n }\n\n const content = fs.readFileSync(paths.profileFile, 'utf-8');\n const lines = content.split('\\n');\n\n // Remove ship block (between \"# ship\" and \"# ship end\")\n const filtered: string[] = [];\n let i = 0;\n let removed = false;\n\n while (i < lines.length) {\n if (lines[i].trim() === '# ship') {\n removed = true;\n i++;\n while (i < lines.length && lines[i].trim() !== '# ship end') i++;\n if (i < lines.length) i++; // skip \"# ship end\"\n } else {\n filtered.push(lines[i]);\n i++;\n }\n }\n\n if (removed) {\n // `split('\\n')` / `join('\\n')` round-trips newlines exactly — a trailing\n // newline survives as a final empty element. Re-appending one here (as an\n // earlier revision did) double-counts it and leaves a stray blank line.\n fs.writeFileSync(paths.profileFile, filtered.join('\\n'));\n success(`completion script uninstalled for ${shell}`, json, noColor);\n warn(`run \"source ${paths.profileFile}\" or restart your shell`, json, noColor);\n } else {\n throw ShipError.config('completion was not found in profile');\n }\n } catch (e) {\n if (isShipError(e)) throw e;\n const message = e instanceof Error ? e.message : String(e);\n throw ShipError.file(`could not uninstall completion script: ${message}`);\n }\n}\n","/**\n * @file Shell completion scripts, RENDERED FROM the command tree.\n *\n * Three hand-written scripts (`src/node/completions/ship.{bash,zsh,fish}`)\n * lived here until 2026-07-29 and were the third, fourth and fifth statement of\n * a command tree `buildProgram()` already held. They were provably stale: `ship\n * tokens get` shipped on 2026-07-28 and completed in **zero** shells, `--limit`\n * and `--cursor` were absent from all three, `--ttl` from two, and several\n * descriptions had drifted word for word from the ones Commander carries.\n *\n * None of that is a bug to fix — it is a bug class to remove. Everything those\n * files said is derivable: names and descriptions from `Command`, flags from\n * `program.options`, and the file-completion positions from which arguments are\n * named `path`/`file`/`dir`. So they are derived, and `ship completion install`\n * writes the script it renders at that moment, which means an installed\n * completion always matches the binary that installed it — something a copied\n * file could never promise.\n *\n * The generated scripts are deliberately SIMPLER than the ones they replace:\n * the old files carried per-subcommand `case` arms whose only effect was to\n * return no completions, which is already the fallthrough.\n */\nimport type { Command } from 'commander';\n\nexport type Shell = 'bash' | 'zsh' | 'fish';\n\n/**\n * The subcommands of `cmd` that a user browses — Commander's own `help` is\n * machinery, not one of them.\n *\n * Exported because the completion scripts are not the only thing that must name\n * a group's subcommands: `handleUnknownSubcommand` in `index.ts` prints the\n * same list as a usage line. Both read it from the tree.\n */\nexport const subcommandsOf = (cmd: Command): Command[] =>\n cmd.commands.filter((c) => c.name() !== 'help');\n\n/** A group is a command that exists to hold others. */\nconst groupsOf = (program: Command): Command[] =>\n subcommandsOf(program).filter((c) => subcommandsOf(c).length > 0);\n\n/**\n * Whether a command takes a filesystem argument, and therefore wants native\n * file completion after its name. Read from the ARGUMENT's name, so\n * `deployments upload <path>` needs no list to be on.\n */\nconst takesPath = (cmd: Command): boolean =>\n cmd.registeredArguments.some((arg) => /^(path|file|dir)/i.test(arg.name()));\n\n/** Options that accept a filesystem value — `--config <file>`. */\nconst pathOptions = (program: Command): string[] =>\n program.options.filter((o) => /<(file|path|dir)\\w*>/i.test(o.flags)).map((o) => o.long ?? '');\n\n/**\n * Flags a specific subcommand declares of its own — `--limit`/`--cursor` on the\n * lists, `--ttl` on `tokens create`. The hand-written scripts offered NONE of\n * these (fish offered `--ttl` alone, unconditionally), because keeping a\n * per-command flag matrix accurate by hand is not worth anyone's afternoon.\n * Generation changes that arithmetic: accuracy is free, so it is taken.\n */\nconst localFlags = (cmd: Command): Array<{ flag: string; description: string }> =>\n cmd.options.flatMap((o) =>\n [o.long, o.short].filter(Boolean).map((flag) => ({\n flag: flag as string,\n description: o.description,\n })),\n );\n\n/** `<group> <sub>` pairs that declare flags of their own. */\nconst localFlagPairs = (program: Command): Array<{ path: string; cmd: Command }> =>\n groupsOf(program).flatMap((group) =>\n subcommandsOf(group)\n .filter((sub) => sub.options.length > 0)\n .map((sub) => ({ path: `${group.name()} ${sub.name()}`, cmd: sub })),\n );\n\n/** Every flag the root declares, long and short alike. */\nconst globalFlags = (program: Command): Array<{ flag: string; description: string }> =>\n program.options.flatMap((o) =>\n [o.long, o.short].filter(Boolean).map((flag) => ({\n flag: flag as string,\n description: o.description,\n })),\n );\n\n/** Names after which native file completion is wanted. */\nconst fileTriggers = (program: Command): string[] => [\n ...groupsOf(program).flatMap((g) =>\n subcommandsOf(g)\n .filter(takesPath)\n .map((c) => c.name()),\n ),\n ...pathOptions(program),\n];\n\nconst HEADER = (comment: string) =>\n `${comment} ship shell completion — GENERATED from the command tree.\\n` +\n `${comment} Do not edit: rendered by src/node/cli/completions.ts at install time,\\n` +\n `${comment} so this file always matches the binary that wrote it.\\n`;\n\n// =============================================================================\n// BASH\n// =============================================================================\n\nfunction renderBash(program: Command): string {\n const cases = groupsOf(program)\n .map(\n (group) => ` \"${group.name()}\")\n if [[ \\${COMP_CWORD} -eq 2 ]]; then\n COMPREPLY=( $(compgen -W \"${subcommandsOf(group)\n .map((c) => c.name())\n .join(' ')}\" -- \"\\${current_word}\") )\n return\n fi\n ;;`,\n )\n .join('\\n');\n\n const triggers = fileTriggers(program)\n .map((name) => `\"$prev_word\" == \"${name}\"`)\n .join(' || ');\n\n return `#!/usr/bin/env bash\n${HEADER('#')}\n_ship_completions() {\n local current_word=\"\\${COMP_WORDS[COMP_CWORD]}\"\n local prev_word=\"\\${COMP_WORDS[COMP_CWORD-1]}\"\n\n # A word that looks like a path gets native file completion immediately.\n if [[ \"$current_word\" == ~* || \"$current_word\" == /* || \"$current_word\" == .* ]]; then\n COMPREPLY=( $(compgen -f -- \"\\${current_word}\") )\n return\n fi\n\n case \"\\${COMP_WORDS[1]}\" in\n${cases}\n esac\n\n if [[ ${triggers} ]]; then\n COMPREPLY=( $(compgen -f -- \"\\${current_word}\") )\n return\n fi\n\n if [[ \"$current_word\" == -* ]]; then\n local local_flags=\"\"\n case \"\\${COMP_WORDS[1]} \\${COMP_WORDS[2]}\" in\n${localFlagPairs(program)\n .map(\n ({ path, cmd }) =>\n ` \"${path}\") local_flags=\"${localFlags(cmd)\n .map((f) => f.flag)\n .join(' ')}\" ;;`,\n )\n .join('\\n')}\n esac\n COMPREPLY=( $(compgen -W \"${globalFlags(program)\n .map((f) => f.flag)\n .join(' ')} \\${local_flags}\" -- \"\\${current_word}\") )\n return\n fi\n\n if [[ \\${COMP_CWORD} -eq 1 ]]; then\n COMPREPLY=( $(compgen -W \"${subcommandsOf(program)\n .map((c) => c.name())\n .join(' ')}\" -- \"\\${current_word}\") )\n return\n fi\n\n COMPREPLY=()\n}\n\ncomplete -F _ship_completions ship\n`;\n}\n\n// =============================================================================\n// ZSH\n// =============================================================================\n\n/**\n * `_describe` splits each entry on the FIRST colon, so a colon inside a\n * description would truncate it. The hand-written script dodged this by\n * rewriting `--token`'s description with an em dash — a silent divergence from\n * the text Commander actually carries. Escaping keeps the two identical.\n */\nconst zshDescribe = (name: string, description: string) =>\n `\"${name}:${description.replace(/:/g, '\\\\:').replace(/\"/g, '\\\\\"')}\"`;\n\nfunction renderZsh(program: Command): string {\n const cases = groupsOf(program)\n .map(\n (group) => ` \"${group.name()}\")\n if [[ $CURRENT -eq 3 ]]; then\n completions=(${subcommandsOf(group)\n .map((c) => zshDescribe(c.name(), c.description()))\n .join(' ')})\n _describe '${group.name()} commands' completions\n return\n fi\n ;;`,\n )\n .join('\\n');\n\n const triggers = fileTriggers(program)\n .map((name) => `\"$prev_word\" == \"${name}\"`)\n .join(' || ');\n\n return `#compdef ship\n${HEADER('#')}\nif [[ -n \\${ZSH_VERSION-} ]]; then\n _ship() {\n local -a completions\n local state line\n\n if [[ -z \\${words-} ]]; then\n return 1\n fi\n\n local current_word=\"\\${words[CURRENT]}\"\n local prev_word=\"\\${words[CURRENT-1]}\"\n\n # A word that looks like a path gets native file completion immediately.\n if [[ \"$current_word\" == \\\\~* || \"$current_word\" == \\\\/* || \"$current_word\" == \\\\./* || \"$current_word\" == \\\\.\\\\./* ]]; then\n _files\n return\n fi\n\n case \"\\${words[2]}\" in\n${cases}\n esac\n\n if [[ ${triggers} ]]; then\n _files\n return\n fi\n\n if [[ \"$current_word\" == -* ]]; then\n completions=(${globalFlags(program)\n .map((f) => zshDescribe(f.flag, f.description))\n .join(' ')})\n case \"\\${words[2]} \\${words[3]}\" in\n${localFlagPairs(program)\n .map(\n ({ path, cmd }) =>\n ` \"${path}\") completions+=(${localFlags(cmd)\n .map((f) => zshDescribe(f.flag, f.description))\n .join(' ')}) ;;`,\n )\n .join('\\n')}\n esac\n _describe 'options' completions\n return\n fi\n\n if [[ $CURRENT -eq 2 ]]; then\n completions=(${subcommandsOf(program)\n .map((c) => zshDescribe(c.name(), c.description()))\n .join(' ')})\n _describe 'commands' completions\n return\n fi\n\n return 1\n }\n\n if (( \\${+functions[compdef]} )); then\n compdef _ship ship\n fi\nfi\n`;\n}\n\n// =============================================================================\n// FISH\n// =============================================================================\n\nconst fishQuote = (text: string) => text.replace(/\\\\/g, '\\\\\\\\').replace(/'/g, \"\\\\'\");\n\nfunction renderFish(program: Command): string {\n const lines: string[] = [];\n\n lines.push('# Top-level commands');\n for (const cmd of subcommandsOf(program)) {\n lines.push(\n `complete -c ship -f -n '__fish_use_subcommand' -a '${cmd.name()}' -d '${fishQuote(cmd.description())}'`,\n );\n }\n\n lines.push('', '# Global options');\n for (const option of program.options) {\n const name = option.long?.replace(/^--/, '');\n if (!name) continue;\n // `-r` requires a file argument, `-x` requires a non-file one, neither for\n // a boolean flag — the three states fish distinguishes.\n const arity = /<(file|path|dir)\\w*>/i.test(option.flags)\n ? ' -r'\n : option.required || option.optional\n ? ' -x'\n : '';\n lines.push(`complete -c ship -l ${name} -d '${fishQuote(option.description)}'${arity}`);\n }\n\n for (const group of groupsOf(program)) {\n lines.push('', `# ${group.name()} subcommands`);\n for (const sub of subcommandsOf(group)) {\n lines.push(\n `complete -c ship -f -n '__fish_seen_subcommand_from ${group.name()}' -a '${sub.name()}' -d '${fishQuote(sub.description())}'`,\n );\n }\n }\n\n // Flags scoped to the subcommand that declares them — fish expresses the\n // condition natively, so this needs no lookup table.\n for (const { path, cmd } of localFlagPairs(program)) {\n const [group, sub] = path.split(' ');\n lines.push('', `# ${path} options`);\n for (const option of cmd.options) {\n const name = option.long?.replace(/^--/, '');\n if (!name) continue;\n const arity = option.required || option.optional ? ' -x' : '';\n lines.push(\n `complete -c ship -l ${name} -d '${fishQuote(option.description)}'${arity} ` +\n `-n '__fish_seen_subcommand_from ${group}; and __fish_seen_subcommand_from ${sub}'`,\n );\n }\n }\n\n const triggers = fileTriggers(program);\n lines.push(\n '',\n '# Native file completion where a filesystem argument is expected',\n 'function __ship_needs_file',\n ' set -l cmd (commandline -opc)',\n ' if test (count $cmd) -ge 2',\n ' set -l prev $cmd[-1]',\n ` if contains -- \"$prev\" ${triggers.map((t) => `'${t}'`).join(' ')}`,\n ' return 0',\n ' end',\n ' end',\n ' return 1',\n 'end',\n \"complete -c ship -F -n '__ship_needs_file'\",\n );\n\n return `${HEADER('#')}\\n${lines.join('\\n')}\\n`;\n}\n\n// =============================================================================\n\n/** The completion script for `shell`, rendered from `program`'s own tree. */\nexport function renderCompletion(program: Command, shell: Shell): string {\n switch (shell) {\n case 'bash':\n return renderBash(program);\n case 'zsh':\n return renderZsh(program);\n case 'fish':\n return renderFish(program);\n }\n}\n","/**\n * Simple CLI utilities following \"impossible simplicity\" mantra\n */\nimport type { ShipError } from '@shipstatic/types';\nimport columnify from 'columnify';\nimport { blue, dim, green, hidden, inverse, red, yellow } from 'yoctocolors';\n\nconst INTERNAL_FIELDS = ['isCreate', 'claim'];\n\nconst applyColor = (colorFn: (text: string) => string, text: string, noColor?: boolean): string => {\n return noColor ? text : colorFn(text);\n};\n\n/** A non-null, non-array object — the shape that needs key-value rendering. */\nconst isPlainObject = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\n// Wire messages are displayed verbatim — identifiers, paths, and acronyms\n// must survive formatting. The CLI's lowercase opening applies to the leading\n// sentence word only, and only when it's an ordinary capitalized word (never\n// \"DNS\", a quoted key, or a path).\nconst decapitalize = (msg: string): string =>\n /^[A-Z][a-z]/.test(msg) ? msg.charAt(0).toLowerCase() + msg.slice(1) : msg;\n\n/**\n * The text channel's typography: lowercase opening, no trailing period.\n *\n * Exported because it is not a property of the message ENVELOPES — a rendered\n * answer wants the same look without claiming to be a success, a warning or a\n * failure. `domains validate` is the case: its negative verdict is data, not\n * one of the three kinds.\n */\nexport const plainMessage = (msg: string): string => decapitalize(msg).replace(/\\.$/, '');\n\n/**\n * The CLI's message envelopes. Each carries a sentence the CLI composed, so\n * `--json` wraps it as `{ kind: message }` — none of the three has a wire\n * counterpart to defer to. Failures do; see `error` below, which is shaped by\n * the wire instead and is deliberately not one of these.\n */\nexport const success = (msg: string, json?: boolean, noColor?: boolean) => {\n if (json) {\n console.log(`${JSON.stringify({ success: msg }, null, 2)}\\n`);\n } else {\n console.log(`${applyColor(green, plainMessage(msg), noColor)}\\n`);\n }\n};\n\nexport const warn = (msg: string, json?: boolean, noColor?: boolean) => {\n if (json) {\n console.log(`${JSON.stringify({ warning: msg }, null, 2)}\\n`);\n } else {\n const warnPrefix = applyColor(\n (text) => inverse(yellow(text)),\n `${applyColor(hidden, '[', noColor)}warning${applyColor(hidden, ']', noColor)}`,\n noColor,\n );\n const warnMsg = applyColor(yellow, plainMessage(msg), noColor);\n console.log(`${warnPrefix} ${warnMsg}\\n`);\n }\n};\n\nexport const info = (msg: string, json?: boolean, noColor?: boolean) => {\n if (json) {\n console.log(`${JSON.stringify({ info: msg }, null, 2)}\\n`);\n } else {\n const infoPrefix = applyColor(\n (text) => inverse(blue(text)),\n `${applyColor(hidden, '[', noColor)}info${applyColor(hidden, ']', noColor)}`,\n noColor,\n );\n const infoMsg = applyColor(blue, plainMessage(msg), noColor);\n console.log(`${infoPrefix} ${infoMsg}\\n`);\n }\n};\n\n/**\n * Emit a failure on stderr. **Text translates, JSON transmits.**\n *\n * The two channels carry different words on purpose. Text renders the string\n * it is handed — the CLI's own actionable copy, composed by `getUserMessage`.\n * JSON emits the platform's `ErrorResponse` verbatim, so `error` names the\n * `ErrorType` exactly as the API and the SDK produce it and a script branches\n * on the same typed field everywhere.\n *\n * A bare string is therefore accepted for the text channel only: a message\n * with no type has no wire shape to emit, and the overloads make writing one\n * into `--json` a compile error rather than a convention.\n */\nexport function error(err: ShipError, json?: boolean, noColor?: boolean): void;\nexport function error(msg: string, json: false | undefined, noColor?: boolean): void;\nexport function error(err: ShipError | string, json?: boolean, noColor?: boolean): void {\n if (json && typeof err !== 'string') {\n console.error(`${JSON.stringify(err.toResponse(), null, 2)}\\n`);\n return;\n }\n const errorPrefix = applyColor(\n (text) => inverse(red(text)),\n `${applyColor(hidden, '[', noColor)}error${applyColor(hidden, ']', noColor)}`,\n noColor,\n );\n const message = typeof err === 'string' ? err : err.message;\n const errorMsg = applyColor(red, plainMessage(message), noColor);\n console.error(`${errorPrefix} ${errorMsg}\\n`);\n}\n\n/**\n * Format unix timestamp to ISO 8601 string without milliseconds, or return '-' if not provided\n */\nexport const formatTimestamp = (\n timestamp?: number,\n context: 'table' | 'details' = 'details',\n noColor?: boolean,\n): string => {\n if (timestamp === undefined || timestamp === null || timestamp === 0) {\n return '-';\n }\n\n const isoString = new Date(timestamp * 1000).toISOString().replace(/\\.\\d{3}Z$/, 'Z');\n\n // Hide the T and Z characters only in table/list views for cleaner appearance\n if (context === 'table') {\n return isoString\n .replace(/T/, applyColor(hidden, 'T', noColor))\n .replace(/Z$/, applyColor(hidden, 'Z', noColor));\n }\n\n return isoString;\n};\n\n/**\n * Format value for display.\n * Handles timestamps, file sizes, and boolean configs with special formatting.\n */\nconst formatValue = (\n key: string,\n value: unknown,\n context: 'table' | 'details' = 'details',\n noColor?: boolean,\n): string => {\n if (value === null || (Array.isArray(value) && value.length === 0)) return '-';\n if (\n typeof value === 'number' &&\n (key === 'created' ||\n key === 'activated' ||\n key === 'expires' ||\n key === 'linked' ||\n key === 'grace' ||\n // `used` is unix seconds on both Token and Account (the API key's\n // last-use instant) — without this it renders as a raw integer.\n key === 'used')\n ) {\n return formatTimestamp(value, context, noColor);\n }\n if (key === 'size' && typeof value === 'number') {\n const mb = value / (1024 * 1024);\n return mb >= 1 ? `${mb.toFixed(1)}Mb` : `${(value / 1024).toFixed(1)}Kb`;\n }\n // Boolean signal columns (config, password) render as yes/no in details.\n if (key === 'config' || key === 'password') {\n if (typeof value === 'boolean') return value ? 'yes' : 'no';\n if (typeof value === 'number') return value === 1 ? 'yes' : 'no';\n }\n // Table and details are flat key-value surfaces, so a nested object\n // flattens to inline `k=v` pairs rather than nesting a second layout\n // inside a cell. Structural, not per-key: values recurse through\n // formatValue, so a nested timestamp or size formats like any other.\n if (isPlainObject(value)) {\n const pairs = Object.entries(value);\n if (pairs.length === 0) return '-';\n return pairs.map(([k, v]) => `${k}=${formatValue(k, v, context, noColor)}`).join(', ');\n }\n return String(value);\n};\n\n/**\n * Format data as table with specified columns for easy parsing.\n * @param data - Array of objects to display as table rows\n * @param columns - Optional column order (defaults to first item's keys)\n * @param noColor - Disable colors\n * @param headerMap - Optional mapping of property names to display headers\n */\nexport const formatTable = (\n data: object[],\n columns?: string[],\n noColor?: boolean,\n headerMap?: Record<string, string>,\n): string => {\n if (!data || data.length === 0) return '';\n\n // Get column order from first item (preserves API order) or use provided columns\n const firstItem = data[0] as Record<string, unknown>;\n const columnOrder =\n columns ||\n Object.keys(firstItem).filter(\n (key) => firstItem[key] !== undefined && !INTERNAL_FIELDS.includes(key),\n );\n\n // Transform data preserving column order\n const transformedData = data.map((item) => {\n const record = item as Record<string, unknown>;\n const transformed: Record<string, string> = {};\n columnOrder.forEach((col) => {\n if (col in record && record[col] !== undefined) {\n transformed[col] = formatValue(col, record[col], 'table', noColor);\n }\n });\n return transformed;\n });\n\n const output = columnify(transformedData, {\n columnSplitter: ' ',\n columns: columnOrder,\n config: columnOrder.reduce<Record<string, { headingTransform: (h: string) => string }>>(\n (config, col) => {\n config[col] = {\n headingTransform: (heading: string) =>\n applyColor(dim, headerMap?.[heading] || heading, noColor),\n };\n return config;\n },\n {},\n ),\n });\n\n // Clean output: remove null bytes and ensure clean spacing\n return `${output\n .split('\\n')\n .map(\n (line: string) =>\n line\n .replace(/\\0/g, '') // Remove any null bytes\n .replace(/\\s+$/, ''), // Remove trailing spaces\n )\n .join('\\n')}\\n`;\n};\n\n/**\n * Format object properties as key-value pairs with space separation for readability.\n * @param obj - Object to display as key-value pairs\n * @param noColor - Disable colors\n */\nexport const formatDetails = (obj: object, noColor?: boolean): string => {\n const entries = (Object.entries(obj) as [string, unknown][]).filter(([key, value]) => {\n if (INTERNAL_FIELDS.includes(key)) return false;\n return value !== undefined;\n });\n\n if (entries.length === 0) return '';\n\n // Transform to columnify format while preserving order\n const data = entries.map(([key, value]) => ({\n property: `${key}:`,\n value: formatValue(key, value, 'details', noColor),\n }));\n\n const output = columnify(data, {\n columnSplitter: ' ',\n showHeaders: false,\n config: {\n property: {\n dataTransform: (value: string) => applyColor(dim, value, noColor),\n },\n },\n });\n\n // Clean output: remove null bytes and ensure clean spacing\n return `${output\n .split('\\n')\n .map((line: string) => line.replace(/\\0/g, '')) // Remove any null bytes\n .join('\\n')}\\n`;\n};\n","/**\n * @file The `ship config` wizard — the only WRITER of the file `shiprc.ts` is\n * the only reader of. Writes `~/.shiprc`, or the file `--config` names.\n *\n * Three rules, each bought with a shipped bug (CLAUDE.md, \"one file, two\n * commands, one idea of what it is\"):\n *\n * - **One parse.** The format question is answered once, by the reader's\n * `parseShipFile`. A private parser here means a file that loads everywhere\n * and is called broken by the one command whose job is to repair it.\n * - **The schema is the file.** `CREDENTIAL_FIELDS` is `.strict()`, so its\n * keys are not the fields we care about — they are the only fields a\n * `.shiprc` may legally hold. The wizard rebuilds from those keys rather\n * than mutating what it read, which is what makes the loader's `apiKey`\n * rename hint true instead of a loop.\n * - **Refuse, never replace.** A file we cannot read is a file whose contents\n * we cannot claim to preserve.\n *\n * The schema half stays split on purpose: the reader validates and rejects,\n * this writer repairs what the reader rejects.\n */\n\nimport { chmodSync, existsSync, writeFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { createInterface } from 'node:readline/promises';\nimport { isShipError, MY_API_KEY_URL, ShipError, validateToken } from '@shipstatic/types';\nimport { dim, green } from 'yoctocolors';\nimport { CREDENTIAL_FIELDS } from '../../shared/core/credential-schema.js';\nimport { parseShipFile } from './shiprc.js';\nimport { warn } from './utils.js';\n\n/**\n * Every key a `.shiprc` may hold — read from the loader's own schema rather\n * than restated here, so the writer cannot come to permit a key the reader\n * rejects.\n */\nconst ALLOWED_KEYS = Object.keys(CREDENTIAL_FIELDS);\n\n/** Where `ship config` writes when `--config` names no file. */\nconst defaultConfigPath = () => join(homedir(), '.shiprc');\n\n/**\n * Mask a token for display: ship-a1b2...c3d4. A token too short to keep a\n * useful prefix + suffix is masked entirely — never printed verbatim.\n */\nfunction maskToken(token: string): string {\n if (token.length < 13) return '...';\n return `${token.slice(0, 9)}...${token.slice(-4)}`;\n}\n\n/**\n * Read the file this command is about to REWRITE — through the READER's parse,\n * never a second one.\n *\n * A private parser here would be a second answer to the format question, and\n * a file that loads everywhere would be called broken by the one command whose\n * job is to repair it. See CLAUDE.md, \"one file, two commands\".\n *\n * What this adds is the fact only a WRITER can state: nothing was written.\n * That is the whole reason to refuse rather than replace.\n */\nfunction readExistingConfig(configPath: string): Record<string, unknown> {\n if (!existsSync(configPath)) return {};\n try {\n return parseShipFile(configPath);\n } catch (err) {\n if (!isShipError(err)) throw err;\n throw ShipError.config(`${err.message} — the file was left unchanged`);\n }\n}\n\n/**\n * Run the interactive config flow.\n *\n * Asks for a token, keeps the fields the schema permits, and writes the file.\n */\nexport async function runConfig(\n options: { noColor?: boolean; configFile?: string } = {},\n): Promise<void> {\n const { noColor } = options;\n // `--config <file>` means \"exactly this file\" for reading, so it means the\n // same for writing. Without it this command could only ever maintain\n // `~/.shiprc`, which is half a feature beside a loader that takes any path.\n const configPath = options.configFile || defaultConfigPath();\n const applyDim = (text: string) => (noColor ? text : dim(text));\n const applyGreen = (text: string) => (noColor ? text : green(text));\n\n const existing = readExistingConfig(configPath);\n const existingToken = typeof existing.token === 'string' ? existing.token : undefined;\n const dropped = Object.keys(existing).filter((key) => !ALLOWED_KEYS.includes(key));\n\n const rl = createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n console.log('');\n console.log(` ${applyDim('Create a free API key at')} ${MY_API_KEY_URL}`);\n console.log('');\n\n const prompt = existingToken ? ` Token (${applyDim(maskToken(existingToken))}): ` : ' Token: ';\n\n let input: string;\n try {\n input = (await rl.question(prompt)).trim();\n } finally {\n rl.close();\n }\n\n // Before anything is written: a rejected token must leave the file untouched.\n if (input) validateToken(input);\n\n // Rebuilt from the schema's keys rather than mutated in place, so a key the\n // loader would reject cannot survive the rewrite. Dropping one IS the repair\n // the rename hint promises — but it is still the removal of something the\n // user typed, so it is said out loud rather than done quietly.\n const config: Record<string, unknown> = {};\n for (const key of ALLOWED_KEYS) {\n if (existing[key] !== undefined) config[key] = existing[key];\n }\n if (input) config.token = input;\n\n if (dropped.length > 0) {\n warn(\n `dropped ${dropped.map((key) => `\"${key}\"`).join(', ')} — a ship config holds only ${ALLOWED_KEYS.join(' and ')}`,\n false,\n noColor,\n );\n }\n\n // The file holds a credential: owner-only, like ~/.netrc. `mode` only\n // applies on creation, so chmod repairs files written before this rule.\n writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\\n`, { mode: 0o600 });\n chmodSync(configPath, 0o600);\n console.log(`\\n ${applyGreen('saved to')} ${applyDim(configPath)}\\n`);\n}\n","/**\n * @file File-based configuration for the `ship` CLI.\n *\n * The CLI is the only place that touches the filesystem for credentials — the\n * SDK reads only `SHIP_*` env vars. That separation is what makes\n * `new Ship({})` safe in embedded hosts (MCP, n8n, the GitHub Action): the SDK\n * cannot reach a developer's dotfile.\n *\n * **Two locations, and no repository file is one of them:**\n * 1. `~/.shiprc` — the ambient config, written by `ship config`\n * 2. whatever `--config <file>` names — any path, read exactly\n *\n * Strict JSON. An empty file is an ABSENT config, not a broken one.\n *\n * A project-level search (`./.shiprc`, `package.json` `\"ship\"`, walking up)\n * used to sit in front of both, via cosmiconfig. It was deleted in 2.0.0\n * because its only capability was the anti-pattern: a repository-controlled\n * file supplying credentials. See CLAUDE.md, \"no repository file is ever\n * read\", for the two verified exploits and why patching them was the wrong\n * shape of fix.\n */\n\nimport { existsSync, readFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { ShipError } from '@shipstatic/types';\nimport { z } from 'zod';\nimport { CREDENTIAL_FIELDS } from '../../shared/core/credential-schema.js';\nimport type { ShipClientOptions } from '../../shared/types.js';\n\n// `.strict()` rejects unknown keys — catches typos like `apikey` (lowercase)\n// in user-authored `.shiprc` files. The env reader doesn't need this because\n// its input is exactly the two SHIP_* vars we read.\nconst FileConfigSchema = z.object(CREDENTIAL_FIELDS).strict();\n\n/** The one ambient config file. */\nconst homeConfigPath = () => join(homedir(), '.shiprc');\n\nconst detail = (err: unknown) => (err instanceof Error ? err.message : String(err));\n\n/**\n * Parse ONE config file into its raw object — the FORMAT half of reading one,\n * with no opinion on the contents.\n *\n * Exported because `ship config` REWRITES this file, and a writer that parses\n * by different rules than the reader is a divergence waiting to be a bug. The\n * SCHEMA half deliberately stays out: the reader validates and rejects, the\n * writer repairs what the reader rejects (dropping `apiKey` is the whole point\n * of the rename hint). One parse, two policies.\n */\nexport function parseShipFile(filePath: string): Record<string, unknown> {\n let raw: string;\n try {\n raw = readFileSync(filePath, 'utf-8');\n } catch (error) {\n throw ShipError.config(`Failed to read ship config (${filePath}): ${detail(error)}`);\n }\n\n // An empty file is an absent config, not a broken one — `touch ~/.shiprc`\n // and any interrupted write produce one, and both are ordinary.\n if (!raw.trim()) return {};\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (error) {\n throw ShipError.config(`Invalid config in ${filePath}: ${detail(error)}`);\n }\n\n if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {\n throw ShipError.config(`Invalid config in ${filePath}: expected a JSON object`);\n }\n return parsed as Record<string, unknown>;\n}\n\n/** Validate a parsed config against the credential schema. */\nfunction validateConfig(config: unknown, filepath: string): Partial<ShipClientOptions> {\n try {\n return FileConfigSchema.parse(config);\n } catch (error) {\n if (error instanceof z.ZodError) {\n const issue = error.issues[0];\n // Keys from retired credential vocabularies get a rename hint instead\n // of a bare rejection — the fix is one edit, so the error names it.\n if (issue.code === 'unrecognized_keys') {\n const legacy = issue.keys.filter((key) => key === 'apiKey' || key === 'deployToken');\n if (legacy.length > 0) {\n const keys = legacy.map((key) => `\"${key}\"`).join(' and ');\n throw ShipError.config(\n `Invalid config in ${filepath}: ${keys} ${legacy.length > 1 ? 'are' : 'is'} no longer supported — the key is now \"token\". Run \\`ship config\\` to rewrite it.`,\n );\n }\n }\n const path = issue.path.length > 0 ? ` at ${issue.path.join('.')}` : '';\n throw ShipError.config(`Invalid config in ${filepath}${path}: ${issue.message}`);\n }\n throw ShipError.config(`Invalid config in ${filepath}`);\n }\n}\n\n/**\n * Load configuration from `~/.shiprc`, or from the file `--config` names.\n *\n * | Mode | Behavior |\n * |------|----------|\n * | No `~/.shiprc` | Returns `{}` — file config is optional |\n * | `--config <path>` and the file does not exist | Throws — a typo'd path is a clear user error, not a silent fallback to a downstream \"auth failed\" |\n * | Unreadable, or not JSON, or not an object | Throws, naming the file |\n * | Empty file | Returns `{}` — absent, not broken |\n * | Fails schema validation | Throws with the offending field path |\n */\nexport function loadShipFile(configFile?: string): Partial<ShipClientOptions> {\n // Empty path is treated as absence — matches credential-flag handling\n // (`--token \"\"` falls through to env). A user passing `--config \"$VAR\"`\n // with `VAR` unset gets `--config \"\"`, which should not error.\n const explicit = configFile || undefined;\n if (explicit) return validateConfig(parseShipFile(explicit), explicit);\n\n const home = homeConfigPath();\n return existsSync(home) ? validateConfig(parseShipFile(home), home) : {};\n}\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 DeployBodyCreator,\n DeployInput,\n DeploymentCreateResponse,\n DeploymentOptions,\n ShipClientOptions,\n StaticFile,\n} from '../shared/types.js';\nimport { readEnvConfig } from './core/config.js';\nimport { createDeployBody } from './core/deploy-body.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-xxxx' });\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 protected getDeployBodyCreator(): DeployBodyCreator {\n return createDeployBody;\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 — that's lazy and runs on\n * first API call via `ensureInitialized()`.\n *\n * Subclasses only override what genuinely differs per environment:\n * - `processInput()` — Node reads paths from disk; Browser handles `File[]`\n * - `getDeployBodyCreator()` — Node streams Buffers; Browser builds Blobs\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 { ShipError, validateCaller, validateToken } from '@shipstatic/types';\n\nimport { ApiHttp } from './api/http.js';\nimport {\n createAccountResource,\n createDeploymentResource,\n createDomainResource,\n createTokenResource,\n type DeployInput,\n} from './resources.js';\nimport type {\n DeployBodyCreator,\n DeploymentOptions,\n ShipClientOptions,\n ShipEvents,\n TokenProvider,\n} 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` plus the lazy-init callback.\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 createDeployBody: this.getDeployBodyCreator(),\n });\n\n const ctx = {\n getApi: () => this.http,\n ensureInit: () => this.ensureInitialized(),\n };\n\n this.deployments = createDeploymentResource({\n ...ctx,\n processInput: (input, opts) => this.processInput(input, opts),\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 protected abstract getDeployBodyCreator(): DeployBodyCreator;\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.getLimits();\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 await this.ensureInitialized();\n return this.http.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 HTTP client for Ship API.\n */\nimport type {\n AccountGetResponse,\n Deployment,\n DeploymentCreateResponse,\n DeploymentDeleteResponse,\n DeploymentListResponse,\n Domain,\n DomainDeleteResponse,\n DomainDnsResponse,\n DomainListResponse,\n DomainRecordsResponse,\n DomainShareResponse,\n DomainValidateResponse,\n DomainVerifyResponse,\n ListOptions,\n PingResponse,\n PlatformLimits,\n SPACheckRequest,\n SPACheckResponse,\n StaticFile,\n Token,\n TokenCreateResponse,\n TokenDeleteResponse,\n TokenListResponse,\n} from '@shipstatic/types';\nimport {\n API_PATHS,\n CALLER,\n DEFAULT_API,\n IDEMPOTENCY_KEY_CONSTRAINTS,\n ShipError,\n SPA_CHECK_CONSTRAINTS,\n validateIdempotencyKey,\n} from '@shipstatic/types';\nimport { SimpleEvents } from '../events.js';\nimport { validateDeployConfig, validateLabels, validatePassword } from '../lib/validation.js';\nimport type {\n ApiDeployOptions,\n DeployBodyCreator,\n DomainSetResult,\n Fetch,\n ShipClientOptions,\n} from '../types.js';\n\n// =============================================================================\n// CONSTANTS\n// =============================================================================\n\nconst DEFAULT_REQUEST_TIMEOUT = 30_000;\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 * 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`. Each surface owns its own string —\n * there is deliberately no central registry, since integrations outside this\n * repo mint their own.\n *\n * Applied at this one wire boundary, so `via` is populated on every deploy\n * from every platform: an absent value means an unattributed caller (raw\n * HTTP), never \"probably the SDK\".\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// 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 createDeployBody: DeployBodyCreator;\n}\n\ninterface RequestResult<T> {\n data: T;\n status: number;\n}\n\n// =============================================================================\n// HTTP CLIENT\n// =============================================================================\n\nexport class ApiHttp extends SimpleEvents {\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 deployTimeout: number;\n private readonly deployBuildTimeout: number;\n private readonly fetch: Fetch;\n private readonly createDeployBody: DeployBodyCreator;\n private readonly deployEndpoint: string;\n private globalHeaders: Record<string, string> = {};\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 // 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 this.deployTimeout = options.timeout ?? DEFAULT_DEPLOY_TIMEOUT;\n this.deployBuildTimeout = options.timeout ?? DEFAULT_DEPLOY_BUILD_TIMEOUT;\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.createDeployBody = options.createDeployBody;\n this.deployEndpoint = options.deployEndpoint || API_PATHS.DEPLOYMENTS;\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 HTTP request with timeout, events, and error handling\n */\n private async executeRequest<T>(\n url: string,\n options: RequestInit,\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 as Record<string, string>);\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 const shipError = ShipError.fromFetchError(error, operationName);\n this.emit('error', shipError, url);\n throw shipError;\n }\n }\n\n /**\n * Simple request - returns data only\n */\n private async request<T>(\n url: string,\n options: RequestInit,\n operationName: string,\n timeoutMs?: number,\n ): Promise<T> {\n const { data } = await this.executeRequest<T>(url, options, operationName, timeoutMs);\n return data;\n }\n\n /**\n * Request with status - returns data and HTTP status code\n */\n private async requestWithStatus<T>(\n url: string,\n options: RequestInit,\n operationName: string,\n ): Promise<RequestResult<T>> {\n return this.executeRequest<T>(url, 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 const timeoutId = setTimeout(() => controller.abort(), timeoutMs);\n\n if (existingSignal) {\n const abort = () => controller.abort();\n existingSignal.addEventListener('abort', abort);\n if (existingSignal.aborted) controller.abort();\n }\n\n return {\n signal: controller.signal,\n cleanup: () => clearTimeout(timeoutId),\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 // PUBLIC API - DEPLOYMENTS\n // ===========================================================================\n\n async deploy(\n files: StaticFile[],\n options: ApiDeployOptions = {},\n ): Promise<DeploymentCreateResponse> {\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 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, headers: bodyHeaders } = await this.createDeployBody(files, {\n labels,\n via: options.via ?? DEPLOY_VIA,\n password: options.password,\n flags,\n captcha: options.captcha,\n });\n\n // The key rides a header, not the body, because it must be readable\n // before the request is parsed — the API replays a stored 201 ahead of\n // the write budget, so a retry costs nothing.\n return this.request<DeploymentCreateResponse>(\n `${this.apiUrl}${this.deployEndpoint}`,\n {\n method: 'POST',\n body,\n headers: idempotencyKey\n ? { ...bodyHeaders, [IDEMPOTENCY_KEY_CONSTRAINTS.HEADER]: idempotencyKey }\n : bodyHeaders,\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.\n options.build || options.prerender ? this.deployBuildTimeout : this.deployTimeout,\n );\n }\n\n async listDeployments(options?: ListOptions): Promise<DeploymentListResponse> {\n return this.request(\n `${this.apiUrl}${API_PATHS.DEPLOYMENTS}${listQuery(options)}`,\n { method: 'GET' },\n 'List deployments',\n );\n }\n\n async getDeployment(id: string): Promise<Deployment> {\n return this.request(\n `${this.apiUrl}${API_PATHS.DEPLOYMENT(encodeURIComponent(id))}`,\n { method: 'GET' },\n 'Get deployment',\n );\n }\n\n async updateDeploymentLabels(id: string, labels: string[]): Promise<Deployment> {\n const normalized = validateLabels(labels);\n return this.request(\n `${this.apiUrl}${API_PATHS.DEPLOYMENT(encodeURIComponent(id))}`,\n {\n method: 'PATCH',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ labels: normalized }),\n },\n 'Update deployment labels',\n );\n }\n\n async deleteDeployment(id: string): Promise<DeploymentDeleteResponse> {\n return this.request<DeploymentDeleteResponse>(\n `${this.apiUrl}${API_PATHS.DEPLOYMENT(encodeURIComponent(id))}`,\n { method: 'DELETE' },\n 'Delete deployment',\n );\n }\n\n // ===========================================================================\n // PUBLIC API - DOMAINS\n // ===========================================================================\n // All domain methods accept FQDN (Fully Qualified Domain Name) as the `name` parameter.\n // The SDK does not validate or normalize - the API handles all domain semantics.\n\n async setDomain(name: string, deployment?: string, labels?: string[]): Promise<DomainSetResult> {\n const normalized = validateLabels(labels);\n const body: { deployment?: string; labels?: string[] } = {};\n if (deployment) body.deployment = deployment;\n if (normalized !== undefined) body.labels = normalized;\n\n const { data, status } = await this.requestWithStatus<Domain>(\n `${this.apiUrl}${API_PATHS.DOMAIN(encodeURIComponent(name))}`,\n {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n },\n 'Set domain',\n );\n\n return { ...data, isCreate: status === 201 };\n }\n\n async listDomains(options?: ListOptions): Promise<DomainListResponse> {\n return this.request(\n `${this.apiUrl}${API_PATHS.DOMAINS}${listQuery(options)}`,\n { method: 'GET' },\n 'List domains',\n );\n }\n\n async getDomain(name: string): Promise<Domain> {\n return this.request(\n `${this.apiUrl}${API_PATHS.DOMAIN(encodeURIComponent(name))}`,\n { method: 'GET' },\n 'Get domain',\n );\n }\n\n async deleteDomain(name: string): Promise<DomainDeleteResponse> {\n return this.request<DomainDeleteResponse>(\n `${this.apiUrl}${API_PATHS.DOMAIN(encodeURIComponent(name))}`,\n { method: 'DELETE' },\n 'Delete domain',\n );\n }\n\n async verifyDomain(name: string): Promise<DomainVerifyResponse> {\n return this.request(\n `${this.apiUrl}${API_PATHS.DOMAIN_VERIFY(encodeURIComponent(name))}`,\n { method: 'POST' },\n 'Verify domain',\n );\n }\n\n async getDomainDns(name: string): Promise<DomainDnsResponse> {\n return this.request(\n `${this.apiUrl}${API_PATHS.DOMAIN_DNS(encodeURIComponent(name))}`,\n { method: 'GET' },\n 'Get domain DNS',\n );\n }\n\n async getDomainRecords(name: string): Promise<DomainRecordsResponse> {\n return this.request(\n `${this.apiUrl}${API_PATHS.DOMAIN_RECORDS(encodeURIComponent(name))}`,\n { method: 'GET' },\n 'Get domain records',\n );\n }\n\n async getDomainShare(name: string): Promise<DomainShareResponse> {\n return this.request(\n `${this.apiUrl}${API_PATHS.DOMAIN_SHARE(encodeURIComponent(name))}`,\n { method: 'GET' },\n 'Get domain share',\n );\n }\n\n async validateDomain(name: string): Promise<DomainValidateResponse> {\n return this.request(\n `${this.apiUrl}${API_PATHS.DOMAINS_VALIDATE}`,\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ domain: name }),\n },\n 'Validate domain',\n );\n }\n\n // ===========================================================================\n // PUBLIC API - TOKENS\n // ===========================================================================\n\n async createToken(ttl?: number, labels?: string[]): Promise<TokenCreateResponse> {\n const normalized = validateLabels(labels);\n const body: { ttl?: number; labels?: string[] } = {};\n if (ttl !== undefined) body.ttl = ttl;\n if (normalized !== undefined) body.labels = normalized;\n\n return this.request(\n `${this.apiUrl}${API_PATHS.TOKENS}`,\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n },\n 'Create token',\n );\n }\n\n async listTokens(options?: ListOptions): Promise<TokenListResponse> {\n return this.request(\n `${this.apiUrl}${API_PATHS.TOKENS}${listQuery(options)}`,\n { method: 'GET' },\n 'List tokens',\n );\n }\n\n async deleteToken(token: string): Promise<TokenDeleteResponse> {\n return this.request<TokenDeleteResponse>(\n `${this.apiUrl}${API_PATHS.TOKEN(encodeURIComponent(token))}`,\n { method: 'DELETE' },\n 'Delete token',\n );\n }\n\n async getToken(token: string): Promise<Token> {\n return this.request<Token>(\n `${this.apiUrl}${API_PATHS.TOKEN(encodeURIComponent(token))}`,\n { method: 'GET' },\n 'Get token',\n );\n }\n\n // ===========================================================================\n // PUBLIC API - ACCOUNT & CONFIG\n // ===========================================================================\n\n async getAccount(): Promise<AccountGetResponse> {\n return this.request(`${this.apiUrl}${API_PATHS.ACCOUNT}`, { method: 'GET' }, 'Get account');\n }\n\n async getLimits(): Promise<PlatformLimits> {\n return this.request(`${this.apiUrl}${API_PATHS.LIMITS}`, { method: 'GET' }, 'Get limits');\n }\n\n async ping(): Promise<PingResponse> {\n return this.request<PingResponse>(`${this.apiUrl}${API_PATHS.PING}`, { method: 'GET' }, 'Ping');\n }\n\n // ===========================================================================\n // PUBLIC API - SPA CHECK\n // ===========================================================================\n\n async checkSPA(files: StaticFile[], _options: ApiDeployOptions = {}): 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 this.request<SPACheckResponse>(\n `${this.apiUrl}${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 * 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 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 * Ship SDK resource factory functions.\n */\nimport {\n type AccountResource,\n type DeployInput,\n type DeploymentResource,\n type DomainResource,\n type ListOptions,\n ShipError,\n type StaticFile,\n type TokenResource,\n} from '@shipstatic/types';\n\nexport type {\n AccountResource,\n DeployInput,\n DeploymentResource,\n DomainResource,\n StaticFile,\n TokenResource,\n};\n\nimport type { ApiHttp } from './api/http.js';\nimport { detectAndConfigureSPA } from './lib/spa.js';\nimport type { DeploymentOptions } from './types.js';\n\n/**\n * Shared context for all resource factories.\n */\nexport interface ResourceContext {\n getApi: () => ApiHttp;\n ensureInit: () => Promise<void>;\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, ensureInit, processInput } = ctx;\n\n return {\n upload: async (input: DeployInput, options: DeploymentOptions = {}) => {\n await ensureInit();\n\n if (!processInput) {\n throw ShipError.config('processInput function is not provided.');\n }\n\n const apiClient = getApi();\n let staticFiles = await processInput(input, options);\n staticFiles = await detectAndConfigureSPA(staticFiles, apiClient, options);\n\n return apiClient.deploy(staticFiles, options);\n },\n\n list: async (options?: ListOptions) => {\n await ensureInit();\n return getApi().listDeployments(options);\n },\n\n get: async (id: string) => {\n await ensureInit();\n return getApi().getDeployment(id);\n },\n\n set: async (id: string, options: { labels: string[] }) => {\n await ensureInit();\n return getApi().updateDeploymentLabels(id, options.labels);\n },\n\n delete: async (id: string) => {\n await ensureInit();\n return getApi().deleteDeployment(id);\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, ensureInit } = 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 await ensureInit();\n return getApi().setDomain(name, options.deployment, options.labels);\n },\n\n list: async (options?: ListOptions) => {\n await ensureInit();\n return getApi().listDomains(options);\n },\n\n get: async (name: string) => {\n await ensureInit();\n return getApi().getDomain(name);\n },\n\n delete: async (name: string) => {\n await ensureInit();\n return getApi().deleteDomain(name);\n },\n\n verify: async (name: string) => {\n await ensureInit();\n return getApi().verifyDomain(name);\n },\n\n validate: async (name: string) => {\n await ensureInit();\n return getApi().validateDomain(name);\n },\n\n dns: async (name: string) => {\n await ensureInit();\n return getApi().getDomainDns(name);\n },\n\n records: async (name: string) => {\n await ensureInit();\n return getApi().getDomainRecords(name);\n },\n\n share: async (name: string) => {\n await ensureInit();\n return getApi().getDomainShare(name);\n },\n };\n}\n\n/**\n * Create account resource (whoami functionality).\n */\nexport function createAccountResource(ctx: ResourceContext): AccountResource {\n const { getApi, ensureInit } = ctx;\n\n return {\n get: async () => {\n await ensureInit();\n return getApi().getAccount();\n },\n };\n}\n\n/**\n * Create token resource for managing deploy tokens.\n */\nexport function createTokenResource(ctx: ResourceContext): TokenResource {\n const { getApi, ensureInit } = ctx;\n\n return {\n create: async (options: { ttl?: number; labels?: string[] } = {}) => {\n await ensureInit();\n return getApi().createToken(options.ttl, options.labels);\n },\n\n list: async (options?: ListOptions) => {\n await ensureInit();\n return getApi().listTokens(options);\n },\n\n get: async (token: string) => {\n await ensureInit();\n return getApi().getToken(token);\n },\n\n delete: async (token: string) => {\n await ensureInit();\n return getApi().deleteToken(token);\n },\n };\n}\n","/**\n * @file SPA detection and auto-configuration utilities.\n *\n * Provides SPA detection and ship.json generation functionality\n * that can be used by both Node.js and browser environments.\n */\n\nimport { DEPLOYMENT_CONFIG_FILENAME, SPA_DEFAULT_CONFIG } from '@shipstatic/types';\nimport type { ApiHttp } 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 * 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 apiClient - HTTP client for API communication\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 apiClient: ApiHttp,\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 apiClient.checkSPA(files, options);\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 * Node.js-specific deploy body creation.\n */\nimport { DEPLOY_FIELDS, ShipError } from '@shipstatic/types';\nimport type { DeployBody, DeployBodyContext, StaticFile } from '../../shared/types.js';\n\nexport async function createDeployBody(\n files: StaticFile[],\n context: DeployBodyContext = {},\n): Promise<DeployBody> {\n const { FormData, File } = await import('formdata-node');\n const { FormDataEncoder } = await import('form-data-encoder');\n\n const { labels, via, password, flags, captcha } = context;\n const formData = new FormData();\n const checksums: string[] = [];\n\n for (const file of files) {\n // 1. Validate content type\n if (\n !Buffer.isBuffer(file.content) &&\n !(typeof Blob !== 'undefined' && file.content instanceof Blob)\n ) {\n throw ShipError.file(`Unsupported file.content type for Node.js: ${file.path}`, {\n filePath: file.path,\n });\n }\n\n // 2. Validate md5\n if (!file.md5) {\n throw ShipError.file(`File missing md5 checksum: ${file.path}`, { filePath: file.path });\n }\n\n // 3. Create File and append — API derives Content-Type from extension\n const fileInstance = new File([file.content], file.path, { type: 'application/octet-stream' });\n formData.append(DEPLOY_FIELDS.FILES, fileInstance);\n checksums.push(file.md5);\n }\n\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 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 const encoder = new FormDataEncoder(formData);\n const chunks = [];\n for await (const chunk of encoder.encode()) {\n chunks.push(Buffer.from(chunk));\n }\n const body = Buffer.concat(chunks);\n\n return {\n body: body.buffer.slice(body.byteOffset, body.byteOffset + body.byteLength) as ArrayBuffer,\n headers: {\n 'Content-Type': encoder.contentType,\n 'Content-Length': Buffer.byteLength(body).toString(),\n },\n };\n}\n","/**\n * @file Resolves CLI configuration into a `Ship` instance.\n *\n * Owns the credential precedence contract: **flag > env > file**.\n *\n * Env-over-file is the canonical CLI tooling posture: CI runners and secret\n * managers set environment variables; a stale dotfile from local dev should\n * never override them. The merge is extracted as a pure function so the\n * contract is unit-testable and a future refactor can't silently flip the\n * order.\n *\n * The SDK itself only knows about constructor args + env vars (see\n * `node/index.ts`). File resolution lives here, in the CLI layer, exactly\n * once — keeping the SDK pure is what guarantees embedded consumers like\n * MCP can't inadvertently inherit the host developer's `~/.shiprc`.\n */\n\nimport type { ShipClientOptions } from '../../shared/types.js';\nimport { readEnvConfig } from '../core/config.js';\nimport { Ship } from '../index.js';\nimport { loadShipFile } from './shiprc.js';\n\n/**\n * The subset of CLI flags that participate in config resolution.\n * Other flags (`--json`, `--quiet`, etc.) flow through Commander separately.\n */\nexport interface CliFlags {\n /** Path to a specific config file, from `--config <file>`. */\n config?: string;\n apiUrl?: string;\n token?: string;\n}\n\n/**\n * Pure precedence merge: flag > env > file, per value. There is one token\n * and one API URL — nothing to arbitrate beyond source order.\n *\n * Empty strings are treated as absence and fall through to the next source\n * (mirrors the env reader, which normalizes empty `process.env` values to\n * `undefined`). This handles CI/CD shell-expansion of unset variables —\n * `--token \"$TOKEN\"` with `TOKEN` unset becomes `--token \"\"`, which we\n * must not lock in as a credential. Without this, an empty flag would\n * silently demote an authenticated deploy to anonymous PUBLIC_ACCOUNT.\n *\n * Exported separately from `createClient` so tests can lock in the contract\n * without mocking the SDK or the filesystem.\n */\nexport function mergeCliConfig(\n flags: CliFlags,\n env: Partial<ShipClientOptions>,\n file: Partial<ShipClientOptions>,\n): ShipClientOptions {\n return {\n apiUrl: flags.apiUrl || env.apiUrl || file.apiUrl,\n token: flags.token || env.token || file.token,\n };\n}\n\n/**\n * Resolve CLI flags + env + file into a `Ship` instance, ready for command\n * action handlers. Called once per CLI invocation by `withErrorHandling`.\n *\n * Synchronous all the way down — matches the SDK's sync constructor.\n */\nexport function createClient(flags: CliFlags = {}): Ship {\n return new Ship(mergeCliConfig(flags, readEnvConfig(), loadShipFile(flags.config)));\n}\n","/**\n * @file CLI-specific error UX utilities.\n *\n * Two pure functions: `toShipError` normalizes any thrown value into a typed\n * `ShipError` for the CLI's global error boundary; `getUserMessage` translates\n * a `ShipError` into the actionable string the CLI prints. Both are pure for\n * easy unit testing.\n *\n * Both serve the TEXT channel only. `--json` transmits `ShipError.toResponse()`\n * untouched — see `error()` in `utils.ts`, which owns both channels. There is\n * deliberately no JSON formatter here: a second serializer is exactly how the\n * `--json` error envelope drifted from the wire's in the first place.\n *\n * Distinct from `ShipError.fromFetchError` (in `@shipstatic/types`), which is\n * for HTTP fetch failures. The CLI's global handler also catches things like\n * Commander parse errors, runtime exceptions in user code, etc. — so it uses\n * `toShipError` and intentionally normalizes unknowns to `Business` (a client\n * error type) so `getUserMessage`'s `isClientError()` branch surfaces the\n * original message rather than swallowing it as a generic \"server error\".\n */\n\nimport { ErrorType, isShipError, ShipError } from '@shipstatic/types';\n\n/**\n * Normalize any thrown value to a `ShipError` for the CLI error boundary.\n * Pass-through for existing `ShipError`s; wraps other Errors and unknowns\n * as `Business` so their message is preserved through `getUserMessage`.\n */\nexport function toShipError(err: unknown): ShipError {\n if (isShipError(err)) {\n return err;\n }\n if (err instanceof Error) {\n return ShipError.business(err.message);\n }\n return ShipError.business(String(err ?? 'Unknown error'));\n}\n\n/**\n * CLI options relevant to error message generation.\n */\nexport interface ErrorOptions {\n /**\n * The credential the CLI resolved (flag > env > file) — not the raw\n * `--token` flag. Presence selects the \"invalid or expired\" auth message;\n * absence selects the \"how to authenticate\" one.\n */\n token?: string;\n}\n\n/**\n * Get actionable user-facing message from an error.\n * Transforms technical errors into helpful messages that tell users what to do.\n *\n * This is a pure function - given the same inputs, always returns the same output.\n * All error message logic is centralized here for easy testing and maintenance.\n */\nexport function getUserMessage(err: ShipError, options?: ErrorOptions): string {\n // Auth errors - tell user what credentials to provide\n if (err.isAuthError()) {\n if (options?.token) {\n return 'authentication failed: invalid or expired token';\n }\n return 'authentication required: pass --token, set SHIP_TOKEN, or run ship config';\n }\n\n // Network errors — the transport failed before any response existed, so\n // there is nothing of the API's to quote. (A URL-naming variant lived here\n // until 2026-07-27, reading `details.url`; nothing has ever set that field —\n // `ShipError.fromFetchError` carries `{ cause }` — so the branch was dead.)\n if (err.isNetworkError()) {\n return 'network error: could not reach the API. check your internet connection';\n }\n\n // Maintenance — a state, not a fault. The platform is closed on purpose, so\n // the API's sentence is the whole answer and it is relayed verbatim; the CLI\n // adds only the one thing the server cannot, where to watch for the\n // all-clear. Deliberately NOT the generic 5xx arm's \"try again\": that arm\n // advises a retry because it has nothing better to say, and here the message\n // itself says when.\n if (err.isType(ErrorType.Maintenance)) {\n return `${err.message.replace(/\\.$/, '')} — check https://status.shipstatic.com`;\n }\n\n // Client-attributable — a client-fault type, or any 4xx status (the guard\n // reads both axes, so a status-derived `Api` at 404 lands here too). The\n // message was authored for the end user at the throw site, API or local\n // code alike, so it is shown verbatim.\n if (err.isClientError()) {\n return err.message;\n }\n\n // Server faults (5xx). The wire message is relayed like every other, because\n // the API leaves nothing to withhold: its global handler emits either a\n // deliberately authored sentence (a 503 naming what is unavailable) or a flat\n // generic, and sends the raw failure to Slack rather than to the client. This\n // discarded it until 2026-07-29, so a 503 that named its cause arrived as\n // \"server error: please try again\" — the platform authored a message for the\n // user and one surface threw it away. The CLI ADDS the one thing it knows and\n // the server does not: where to look when it keeps happening.\n return `${err.message.replace(/\\.$/, '')} — try again, or check https://status.shipstatic.com`;\n}\n","/**\n * Pure formatting functions for CLI output.\n * All formatters are synchronous and have no side effects beyond console output.\n */\n\nimport type {\n Account,\n Deployment,\n DeploymentCreateResponse,\n DeploymentDeleteResponse,\n DeploymentListResponse,\n Domain,\n DomainDeleteResponse,\n DomainDnsResponse,\n DomainListResponse,\n DomainRecordsResponse,\n DomainShareResponse,\n DomainValidateResponse,\n DomainVerifyResponse,\n PingResponse,\n Token,\n TokenCreateResponse,\n TokenDeleteResponse,\n TokenListResponse,\n} from '@shipstatic/types';\nimport { DeploymentStatus } from '@shipstatic/types';\nimport type { CLIResult, EnrichedDomain } from './types.js';\n// No `error` import, and that is a property worth keeping: a formatter renders\n// a RESULT. Every failure — including a rejected request — reaches the user\n// through `handleError`, so there is exactly one writer of the error channel.\n// (`domains validate`'s negative verdict is not a failure; it renders.)\nimport { formatDetails, formatTable, info, plainMessage, success } from './utils.js';\n\nconst setupUrl = (hash: string, domain: string) => `https://setup.shipstatic.com/${hash}/${domain}`;\n\n/**\n * Read a named field off a result whose shape the caller has already decided.\n *\n * The cast is the one narrowing TypeScript cannot do for us: `CLIResult` is a\n * union of named response types and a member like `EnrichedDomain` carries no\n * index signature, yet every member IS a plain object. Safe because the field\n * name is derived from the command's own resource type — never from input —\n * and the value is type-checked at the call site before it is used.\n */\nfunction readField(source: object, field: string): unknown {\n return (source as Record<string, unknown>)[field];\n}\n\n/**\n * The states a mutation can still be IN, and what each means for someone\n * standing at the terminal.\n *\n * A mutation acknowledgement carries the resource's own state field **only\n * where the resource survived mid-transition** (`@shipstatic/types`,\n * `DeploymentDeleteResponse`); a hard delete has no state left to state.\n *\n * The tense is not a style question. `DELETE /deployments/:deployment` answers\n * **202**, marks the row `deleting`, and queues the cleanup; the router serves\n * from KV with no status gate, so the files stay public until that queue\n * drains (~26s measured). Saying \"deleted\" is exactly backwards for someone\n * deleting a deployment BECAUSE it exposed something.\n *\n * This MAP is the gate, not the mere presence of a `status`, and the difference\n * is load-bearing: `Deployment` and `Domain` both carry a resting status of\n * their own (`pending`, `success`), so a formatter reporting any status it\n * found would answer \"www.example.com domain pending\". Keys here are\n * TRANSITIONAL states only; a resting one must never be added.\n */\nconst IN_FLIGHT: Readonly<Partial<Record<string, string>>> = {\n [DeploymentStatus.DELETING]: 'served until cleanup completes',\n};\n\n/**\n * What each mutating operation did, once it is done.\n *\n * `set` is absent because it is an upsert: the wire's 201-vs-200 decided which\n * it was, and `isCreate` carries that decision onto the result.\n */\nconst PAST_TENSE: Readonly<Partial<Record<Operation, string>>> = {\n upload: 'uploaded',\n create: 'created',\n delete: 'deleted',\n verify: 'verification queued',\n};\n\n/**\n * THE sentence. Every announcement the CLI makes about a mutation is this one\n * function of two things it already has — what the command IS\n * (`OutputContext`) and what the wire ANSWERED (`result`).\n *\n * <canonical key> <resource noun> <past tense, or the wire's own state>\n *\n * Nothing else is needed, and that is the point: a formatter writing its own\n * template is a formatter making a choice it does not need to make, and five of\n * them once produced six grammars between them. See CLAUDE.md, \"One grammar\".\n *\n * Here the key is READ FROM THE NOUN (`result[noun]`), so `.url` is not\n * reachable; the order belongs to the function, not to a call site; and the\n * state override applies everywhere at once, so an operation that becomes\n * asynchronous tomorrow updates its own sentence.\n *\n * Reports do not pass through here: they have no key and no verb, so they\n * render their answer instead (`ping`, `validate`, `records`, `dns`, `share`).\n * Mutations announce; reports render.\n */\nfunction announce(result: CLIResult, context: OutputContext): string | null {\n const { operation, resource } = context;\n if (!resource) return null;\n if (result === null || typeof result !== 'object') return null;\n\n const predicate =\n operation === 'set'\n ? readField(result, 'isCreate')\n ? 'created'\n : 'updated'\n : PAST_TENSE[operation];\n if (!predicate) return null; // a read: there is nothing to announce\n\n const key = readField(result, resource);\n // A handler that resolved no identifier leaves nothing to report, and the CLI\n // does not invent one from the caller's argument.\n if (typeof key !== 'string') return null;\n\n const state = readField(result, 'status');\n const consequence = typeof state === 'string' ? IN_FLIGHT[state] : undefined;\n return `${key} ${resource} ${consequence ? `${state} — ${consequence}` : predicate}`;\n}\n\n/**\n * The CLI's verb vocabulary — every command declares one.\n *\n * A union rather than `string` because `operation` selects behaviour:\n * `PAST_TENSE` and `ACKNOWLEDGING` are keyed by it, so a typo used to mean a\n * command that silently announced nothing, with every test still green.\n */\nexport type Operation =\n | 'list'\n | 'upload'\n | 'set'\n | 'create'\n | 'delete'\n | 'get'\n | 'validate'\n | 'verify'\n | 'records'\n | 'dns'\n | 'share'\n | 'ping';\n\n/**\n * The wire noun for a resource — which is also the name of the field carrying\n * its identifier, platform-wide (\"the wire field for an entity's identifier is\n * the resource noun, never `id`\" — `cloudflare/api/CLAUDE.md`).\n *\n * Lowercase because that IS the field name: `announce` reads\n * `result[resource]` directly. It was `resourceType: 'Deployment'` with a\n * `.toLowerCase()` at the single point of use — a capitalisation the code\n * carried around only to undo.\n */\nexport type Resource = 'deployment' | 'domain' | 'token' | 'account';\n\n/**\n * What the command IS: its verb and its noun. Deliberately NOT what the caller\n * typed — the arguments are the request, and every sentence the CLI writes\n * about a result is composed from the response.\n */\nexport interface OutputContext {\n operation: Operation;\n /** Absent only for `ping`, which names no resource. */\n resource?: Resource;\n}\n\nexport interface FormatOptions {\n json?: boolean;\n quiet?: boolean;\n noColor?: boolean;\n}\n\n/**\n * Format deployments list\n */\nexport function formatDeploymentsList(\n result: DeploymentListResponse,\n options: FormatOptions,\n): void {\n const { noColor } = options;\n\n if (result.deployments.length === 0) {\n console.log('no deployments found');\n console.log();\n return;\n }\n\n const columns = ['deployment', 'labels', 'files', 'size', 'created', 'via'];\n console.log(formatTable(result.deployments, columns, noColor));\n printCursorHint(result.cursor, noColor);\n}\n\n/**\n * A non-null cursor means the server has more pages. Text mode surfaces the\n * continuation the same way `-q` surfaces identifiers: as the value you feed\n * to the next invocation. (`--json` consumers read `cursor` off the response.)\n */\nfunction printCursorHint(cursor: string | null | undefined, noColor?: boolean): void {\n if (cursor) {\n info(`more results available — rerun with --cursor ${cursor}`, false, noColor);\n }\n}\n\n/**\n * Format domains list\n */\nexport function formatDomainsList(result: DomainListResponse, options: FormatOptions): void {\n const { noColor } = options;\n\n if (result.domains.length === 0) {\n console.log('no domains found');\n console.log();\n return;\n }\n\n const columns = ['domain', 'deployment', 'labels', 'linked', 'links', 'created'];\n console.log(formatTable(result.domains, columns, noColor));\n printCursorHint(result.cursor, noColor);\n}\n\n/**\n * Format single domain result.\n * Accepts plain Domain (from get) or EnrichedDomain (from set, with DNS info).\n */\nexport function formatDomain(result: Domain | EnrichedDomain, options: FormatOptions): void {\n const { noColor } = options;\n\n // Destructure enrichment fields (undefined when result is plain Domain)\n const { _dnsRecords, _shareHash, isCreate, ...displayResult } = result as EnrichedDomain;\n\n // Display pre-fetched DNS records (for new external domains)\n if (_dnsRecords && _dnsRecords.length > 0) {\n console.log();\n info('DNS records to configure:', false, noColor);\n _dnsRecords.forEach((record) => {\n console.log(` ${record.type}: ${record.name} → ${record.value}`);\n });\n }\n\n // Display setup instructions link\n if (_shareHash) {\n console.log();\n info(`Setup instructions: ${setupUrl(_shareHash, result.domain)}`, false, noColor);\n }\n\n console.log(formatDetails(displayResult, noColor));\n}\n\n/**\n * Format single deployment result\n */\nexport function formatDeployment(\n result: Deployment | DeploymentCreateResponse,\n options: FormatOptions,\n): void {\n const { noColor } = options;\n\n console.log(formatDetails(result, noColor));\n\n // Public deployment — claim URL + CTA after details\n const claim = (result as DeploymentCreateResponse).claim;\n if (claim) {\n const days = result.expires ? Math.round((result.expires - result.created) / 86400) : null;\n console.log(\n `IMPORTANT: this deployment${days ? ` expires in ${days} day${days !== 1 ? 's' : ''}` : ' will expire'}, claim it to keep permanently:\\n${claim}\\n`,\n );\n info(\n `configure a free API key with 'ship config' to deploy to your own account`,\n false,\n noColor,\n );\n }\n}\n\n/**\n * Format account/email result\n */\nexport function formatAccount(result: Account, options: FormatOptions): void {\n const { noColor } = options;\n console.log(formatDetails(result, noColor));\n}\n\n/**\n * Format domain validation result\n */\nexport function formatDomainValidate(result: DomainValidateResponse, options: FormatOptions): void {\n const { noColor } = options;\n\n if (result.valid) {\n // The subject is the RESPONSE's normalized name, so the sentence names the\n // form the platform would store rather than whatever case the caller typed.\n success(`${result.normalized ?? 'domain'} domain is valid`, false, noColor);\n console.log();\n if (result.available !== null) {\n const availabilityText = result.available\n ? noColor\n ? 'available'\n : 'available ✓'\n : 'already taken';\n console.log(` availability: ${availabilityText}`);\n }\n console.log();\n } else {\n // A verdict is not a failure. The call succeeded and the answer is \"no\", so\n // the reason rides STDOUT like every other rendered answer — the exit code\n // (set by the command) is the machine-readable half, which is what\n // `ship domains validate x && …` reads. Writing it to stderr under\n // `[error]` said the command had failed, contradicting both the SDK, which\n // resolves this shape without throwing, and `--json`, which has always put\n // the same verdict on stdout.\n //\n // It names no subject on purpose: `normalized` is null when invalid, so the\n // response carries no identifier, and the CLI does not fall back to the\n // caller's argument.\n console.log(`${plainMessage(result.reason ?? 'domain is invalid')}\\n`);\n }\n}\n\n/**\n * Format domain DNS records result\n */\nexport function formatDomainRecords(result: DomainRecordsResponse, options: FormatOptions): void {\n const { noColor } = options;\n\n if (result.records.length === 0) {\n console.log('no records found');\n console.log();\n return;\n }\n\n const columns = ['type', 'name', 'value'];\n console.log(formatTable(result.records, columns, noColor));\n}\n\n/**\n * Format domain DNS provider result\n */\nexport function formatDomainDns(result: DomainDnsResponse, options: FormatOptions): void {\n const { noColor } = options;\n const provider = result.dns?.provider?.name || null;\n console.log(formatDetails({ domain: result.domain, provider }, noColor));\n}\n\n/**\n * Format domain share result as setup URL\n */\nexport function formatDomainShare(result: DomainShareResponse, options: FormatOptions): void {\n const { noColor } = options;\n // Rendered like `dns` beside it, rather than announced as a success: nothing\n // was mutated, so there is no acknowledgement to compose. It also stops text\n // and `-q` from being byte-identical — `-q` still emits the bare URL, which\n // is the whole point of that channel.\n console.log(\n formatDetails({ domain: result.domain, setup: setupUrl(result.hash, result.domain) }, noColor),\n );\n}\n\n/**\n * Format tokens list\n */\nexport function formatTokensList(result: TokenListResponse, options: FormatOptions): void {\n const { noColor } = options;\n\n if (result.tokens.length === 0) {\n console.log('no tokens found');\n console.log();\n return;\n }\n\n const columns = ['token', 'labels', 'created', 'expires'];\n console.log(formatTable(result.tokens, columns, noColor));\n printCursorHint(result.cursor, noColor);\n}\n\n/**\n * Format single token result (creation response includes both token ID and secret)\n */\nexport function formatToken(result: Token | TokenCreateResponse, options: FormatOptions): void {\n const { noColor } = options;\n\n console.log(formatDetails(result, noColor));\n}\n\n/**\n * What one command produces, in each of the two channels that render it.\n *\n * `T` is the response type that command's endpoint returns, so an entry's\n * bodies are typed and contain no casts of their own.\n */\ninterface Output<T = CLIResult> {\n /** `-q`: the identifier(s) you would pipe onward, or none if it has none. */\n readonly quiet: (result: T) => string[];\n /** Text: render the answer. The mutation SENTENCE is `announce`'s job. */\n readonly text: (result: T, options: FormatOptions) => void;\n}\n\n/** Give one entry its real response type. An identity function, nothing more. */\nconst row = <T>(spec: Output<T>) => spec as Output;\n\n/** Every key the table can hold — the typo guard. */\ntype OutputKey = `${Resource}.${Exclude<Operation, 'ping'>}` | 'ping';\n\n/** The key a command's declared identity resolves to. */\nconst keyOf = (context: OutputContext): OutputKey =>\n (context.resource ? `${context.resource}.${context.operation}` : context.operation) as OutputKey;\n\n/**\n * EVERY command's output, keyed by what the command IS.\n *\n * **Output is selected by what the command DECLARED, never by what the\n * response LOOKS LIKE.** Sniffing the payload to re-derive the command is what\n * makes resolution ORDER load-bearing — `token`/`secret` and\n * `domain`/`deployment` overlap — and order then has to be documented, fenced\n * and preserved by hand. Keyed by identity, those ties are inexpressible.\n *\n * Entries need no casts: the key implies the response type through the wire\n * contract (context ↔ endpoint ↔ response), which `row<T>()` carries.\n *\n * The table doubles as the CLI's whole output surface, which is what lets\n * `tests/architecture/sdk-cli-parity.test.ts` check it against the SDK's own\n * resource methods. Exported for that fence and for the suite, which pins its\n * per-row cases against this list rather than against itself.\n *\n * See CLAUDE.md, \"formatOutput Router\", for how it got here.\n */\nexport const OUTPUTS: Partial<Record<OutputKey, Output>> = {\n // ─── deployments ───\n 'deployment.list': row<DeploymentListResponse>({\n quiet: (r) => r.deployments.map((d) => d.deployment),\n text: formatDeploymentsList,\n }),\n 'deployment.upload': row<Deployment>({\n quiet: (r) => [r.deployment],\n text: formatDeployment,\n }),\n 'deployment.get': row<Deployment>({\n quiet: (r) => [r.deployment],\n text: formatDeployment,\n }),\n 'deployment.set': row<Deployment>({\n quiet: (r) => [r.deployment],\n text: formatDeployment,\n }),\n 'deployment.delete': row<DeploymentDeleteResponse>({\n quiet: (r) => [r.deployment],\n // An acknowledgement is the sentence and nothing else — there is no entity\n // left to render beneath it. `announce` has already written it.\n text: () => {},\n }),\n\n // ─── domains ───\n 'domain.list': row<DomainListResponse>({\n quiet: (r) => r.domains.map((d) => d.domain),\n text: formatDomainsList,\n }),\n 'domain.get': row<Domain>({\n quiet: (r) => [r.domain],\n text: formatDomain,\n }),\n 'domain.set': row<Domain | EnrichedDomain>({\n quiet: (r) => [r.domain],\n text: formatDomain,\n }),\n 'domain.validate': row<DomainValidateResponse>({\n // An invalid name has no `normalized` form, so there is no identifier —\n // the verdict rides the exit code, which is what `-q` callers read.\n quiet: (r) => (r.valid && r.normalized ? [r.normalized] : []),\n text: formatDomainValidate,\n }),\n 'domain.verify': row<DomainVerifyResponse>({\n quiet: (r) => [r.domain],\n text: () => {},\n }),\n 'domain.records': row<DomainRecordsResponse>({\n quiet: (r) => r.records.map((rec) => `${rec.type} ${rec.name} ${rec.value}`),\n text: formatDomainRecords,\n }),\n 'domain.dns': row<DomainDnsResponse>({\n // A lookup that resolved no provider has no identifier to pipe.\n quiet: (r) => (r.dns?.provider?.name ? [r.dns.provider.name] : []),\n text: formatDomainDns,\n }),\n 'domain.share': row<DomainShareResponse>({\n quiet: (r) => [setupUrl(r.hash, r.domain)],\n text: formatDomainShare,\n }),\n 'domain.delete': row<DomainDeleteResponse>({\n quiet: (r) => [r.domain],\n text: () => {},\n }),\n\n // ─── tokens ───\n 'token.list': row<TokenListResponse>({\n quiet: (r) => r.tokens.map((t) => t.token),\n text: formatTokensList,\n }),\n 'token.create': row<TokenCreateResponse>({\n // The SECRET rather than the id, deliberately: it is shown once and never\n // again, so `ship tokens create -q >> .env` is why this channel exists\n // here. Under the old shape-router this needed `secret` to be probed\n // before `token`; now it is simply a different row.\n quiet: (r) => [r.secret],\n text: formatToken,\n }),\n 'token.get': row<Token>({\n quiet: (r) => [r.token],\n text: formatToken,\n }),\n 'token.delete': row<TokenDeleteResponse>({\n quiet: (r) => [r.token],\n text: () => {},\n }),\n\n // ─── account ───\n 'account.get': row<Account>({\n quiet: (r) => [r.email],\n text: formatAccount,\n }),\n\n // ─── top level ───\n ping: row<PingResponse>({\n // A clock is not an identifier to pipe.\n quiet: () => [],\n // Liveness is a question, so text answers it as one — the server clock is\n // noise to a person. There is no unreachable arm: a non-OK response throws\n // in transport, so reaching this line at all IS the answer.\n text: (_r, o) => success('api reachable', false, o.noColor),\n }),\n};\n\n/**\n * The one output path: JSON transmits, `-q` pipes the key, text renders.\n *\n * Every channel dispatches on the command's DECLARED identity — never on the\n * shape of the response. See `OUTPUTS`.\n */\nexport function formatOutput(\n result: CLIResult,\n context: OutputContext,\n options: FormatOptions,\n): void {\n const { json, quiet, noColor } = options;\n const output = OUTPUTS[keyOf(context)];\n\n // Quiet mode: output only the key identifier.\n if (quiet) {\n for (const line of output?.quiet(result) ?? []) console.log(line);\n return;\n }\n\n // JSON transmits, text translates. The data channel emits the wire's own\n // shape and never a sentence, so it is answered first and once.\n if (json && result !== null && typeof result === 'object') {\n const payload = { ...result } as Record<string, unknown>;\n delete payload._dnsRecords;\n delete payload._shareHash;\n delete payload.isCreate;\n console.log(JSON.stringify(payload, null, 2));\n console.log();\n return;\n }\n\n // Text. A mutation announces, then its row renders whatever entity it\n // produced — an acknowledgement's row renders nothing, because the sentence\n // is the whole of it.\n const sentence = announce(result, context);\n if (sentence) success(sentence, false, noColor);\n\n if (output) {\n output.text(result, options);\n } else if (result !== null && typeof result === 'object') {\n // A command with no row of its own is not an occasion to print the word\n // \"success\": that asserts the call worked, which the exit code already\n // said, and it hides the answer the command was run for. Render what\n // arrived. `GET /labels` and `GET /limits` are real endpoints with no CLI\n // command yet (`CLAUDE.md`, \"Routes the API exposes that the SDK does not\n // reach\") — a safety net, not a plan: the parity fence goes red before an\n // unmapped command could ever be run.\n console.log(formatDetails(result, noColor));\n }\n // A non-object result is unrenderable. Saying nothing is honest; saying\n // \"success\" would not be.\n}\n"],"mappings":";skBAwFO,SAASA,GAAaC,EAAO,CAChC,GAAI,CAACA,GAAS,OAAOA,GAAU,SAC3B,OACJ,IAAMC,EAAMD,EAAM,KAAK,EAAE,YAAY,EACrC,OAAO,OAAO,OAAOE,EAAa,EAAE,SAASD,CAAG,EAC1CA,EACA,MACV,CAMO,SAASE,GAAuBH,EAAO,CAC1C,GAA2BA,GAAU,KACjC,OACJ,GAAI,OAAOA,GAAU,SACjB,MAAMI,EAAU,WAAW,mCAAmC,EAElE,IAAMC,EAAML,EAAM,KAAK,EACvB,GAAI,CAACK,EACD,MAAMD,EAAU,WAAW,oCAAoC,EAEnE,GAAIC,EAAI,OAASC,GAA4B,WACzC,MAAMF,EAAU,WAAW,mCAAmCE,GAA4B,UAAU,cAAc,EAEtH,OAAOD,CACX,CA4PA,SAASE,GAAmBC,EAAO,CAC/B,OAAI,OAAOA,EAAM,MAAS,SACf,GAIJA,aAAiB,WAAaA,EAAM,QAAQ,SAAS,OAAO,CACvE,CA6PO,SAASC,EAAYC,EAAO,CAC/B,OAAQA,IAAU,MACd,OAAOA,GAAU,UACjB,SAAUA,GACVA,EAAM,OAAS,aACf,WAAYA,CACpB,CAsEO,SAASC,GAAmBC,EAAU,CACzC,IAAMC,EAAWD,EAAS,YAAY,GAAG,EACzC,GAAIC,IAAa,IAAMA,IAAaD,EAAS,OAAS,EAClD,MAAO,GACX,IAAME,EAAMF,EAAS,MAAMC,EAAW,CAAC,EAAE,YAAY,EACrD,OAAOE,GAAmB,IAAID,CAAG,CACrC,CAiJO,SAASE,GAAeJ,EAAU,CACrC,OAAOK,GAAsB,KAAKL,CAAQ,CAC9C,CAoBO,SAASM,GAAiBC,EAAU,CAEvC,OADiBA,EAAS,QAAQ,MAAO,GAAG,EAAE,MAAM,GAAG,EAAE,OAAO,OAAO,EACvD,KAAMC,GAAMC,GAAwB,IAAID,CAAC,CAAC,CAC9D,CAmGO,SAASE,GAAcC,EAAO,CACjC,OAAIA,EAAM,WAAWC,GAAQ,MAAM,EACxBC,EAAU,QACjBF,EAAM,WAAWG,GAAa,MAAM,EAC7BD,EAAU,aACdA,EAAU,MACrB,CA4EO,SAASE,GAAqBC,EAAM,CACvC,IAAMC,EAAaD,EAAK,WAAW,CAAC,IAAM,MAASA,EAAK,MAAM,CAAC,EAAIA,EAC/DE,EACJ,GAAI,CACAA,EAAS,KAAK,MAAMD,CAAU,CAClC,OACOnB,EAAO,CACV,MAAMN,EAAU,OAAO,kCAAkCM,EAAM,OAAO,GAAI,CACtE,SAAUqB,CACd,CAAC,CACL,CACA,GAAID,IAAW,MAAQ,OAAOA,GAAW,UAAY,MAAM,QAAQA,CAAM,EACrE,MAAM1B,EAAU,OAAO,GAAG2B,CAA0B,8BAA+B,CAC/E,SAAUA,CACd,CAAC,CAET,CASA,SAASC,GAA2BhC,EAAOiC,EAAOC,EAAO,CACrD,GAAI,CAAClC,EAAM,WAAWiC,EAAM,MAAM,EAC9B,MAAM7B,EAAU,WAAW,GAAG8B,CAAK,qBAAqBD,EAAM,MAAM,GAAG,EAE3E,GAAIjC,EAAM,SAAWiC,EAAM,aACvB,MAAM7B,EAAU,WAAW,GAAG8B,CAAK,YAAYD,EAAM,YAAY,sBAAsBA,EAAM,MAAM,MAAMA,EAAM,UAAU,aAAa,EAE1I,IAAME,EAAUnC,EAAM,MAAMiC,EAAM,OAAO,MAAM,EAC/C,GAAI,CAAC,IAAI,OAAO,aAAaA,EAAM,UAAU,KAAM,GAAG,EAAE,KAAKE,CAAO,EAChE,MAAM/B,EAAU,WAAW,GAAG8B,CAAK,iBAAiBD,EAAM,UAAU,kCAAkCA,EAAM,MAAM,UAAU,CAEpI,CAIO,SAASG,GAAeC,EAAQ,CACnCL,GAA2BK,EAAQb,GAAS,SAAS,CACzD,CAIO,SAASc,GAAoBC,EAAa,CAC7CP,GAA2BO,EAAab,GAAc,cAAc,CACxE,CAOO,SAASc,EAAcjB,EAAO,CACjC,OAAQD,GAAcC,CAAK,EAAG,CAC1B,KAAKE,EAAU,QACXW,GAAeb,CAAK,EACpB,OACJ,KAAKE,EAAU,aACXa,GAAoBf,CAAK,EACzB,OACJ,KAAKE,EAAU,OACX,GAAI,CAACF,EACD,MAAMnB,EAAU,WAAW,kCAAkC,CACzE,CACJ,CAMO,SAASqC,GAAeC,EAAQ,CACnC,GAAI,CAACA,GAAUA,EAAO,OAASC,EAAO,YAAc,CAACA,EAAO,QAAQ,KAAKD,CAAM,EAC3E,MAAMtC,EAAU,WAAW,oBAAoBuC,EAAO,UAAU,6DAA6D,CAErI,CAIO,SAASC,GAAeC,EAAQ,CACnC,GAAI,CACA,IAAMC,EAAM,IAAI,IAAID,CAAM,EAC1B,GAAI,CAAC,CAAC,QAAS,QAAQ,EAAE,SAASC,EAAI,QAAQ,EAC1C,MAAM1C,EAAU,WAAW,+CAA+C,EAE9E,GAAI0C,EAAI,WAAa,KAAOA,EAAI,WAAa,GACzC,MAAM1C,EAAU,WAAW,iCAAiC,EAEhE,GAAI0C,EAAI,QAAUA,EAAI,KAClB,MAAM1C,EAAU,WAAW,wDAAwD,CAE3F,OACOM,EAAO,CACV,MAAID,EAAYC,CAAK,EACXA,EAEJN,EAAU,WAAW,6BAA6B,CAC5D,CACJ,CAwNO,SAAS2C,GAAiB/C,EAAO,CACpC,GAA2BA,GAAU,KACjC,OACJ,GAAI,OAAOA,GAAU,SACjB,MAAMI,EAAU,WAAW,2BAA2B,EAE1D,IAAM4C,EAAUhD,EAAM,KAAK,EAC3B,GAAIgD,EAAQ,OAASC,GAAqB,YACtCD,EAAQ,OAASC,GAAqB,WACtC,MAAM7C,EAAU,WAAW,4BAA4B6C,GAAqB,UAAU,QAAQA,GAAqB,UAAU,aAAa,EAE9I,OAAOD,CACX,CA52CA,IAUaE,GAiBAhD,GAkCAI,GAwGA6C,EAwCAC,EAgCAC,EAiDPC,GAWAC,GAiCAC,GAQAC,GAwCOrD,EA6QAW,GA6EP2C,GAwGOC,GAgBA1C,GAoBAI,GAyCAuC,GAaApC,GAcAE,GAeAiB,EAoBAlB,EAuCAM,EAEA8B,GAiBAC,GAmJAC,GAcAC,GAeAC,GAeAC,GA0EAC,EAkBAC,GAyCAnB,GAr0CboB,EAAAC,EAAA,kBAUapB,GAAmB,CAC5B,QAAS,UACT,QAAS,UACT,OAAQ,SACR,SAAU,UACd,EAYahD,GAAgB,CACzB,IAAK,MACL,IAAK,MACL,IAAK,MACL,IAAK,MACL,IAAK,MACL,IAAK,MACL,IAAK,MACL,IAAK,KACT,EAyBaI,GAA8B,CAMvC,OAAQ,kBACR,WAAY,IAEZ,eAAgB,KAAU,EAC9B,EA8Fa6C,EAAY,CACrB,YAAa,eACb,WAAaoB,GAAe,gBAAgBA,CAAU,GACtD,kBAAoBA,GAAe,gBAAgBA,CAAU,UAC7D,QAAS,WACT,OAASC,GAAW,YAAYA,CAAM,GACtC,cAAgBA,GAAW,YAAYA,CAAM,UAC7C,WAAaA,GAAW,YAAYA,CAAM,OAC1C,eAAiBA,GAAW,YAAYA,CAAM,WAC9C,aAAeA,GAAW,YAAYA,CAAM,SAC5C,mBAAqBA,GAAW,YAAYA,CAAM,eAClD,iBAAkB,oBAClB,OAAQ,UACR,MAAQjD,GAAU,WAAWA,CAAK,GAClC,QAAS,WACT,YAAa,eACb,cAAe,iBACf,WAAY,cACZ,OAAQ,UACR,OAAQ,UACR,KAAM,QACN,MAAO,SACP,UAAW,aACX,OAAQ,SACZ,EAgBa6B,EAAgB,CAEzB,MAAO,UAEP,UAAW,YAEX,OAAQ,SAER,IAAK,MAEL,SAAU,WAEV,MAAO,QAEP,UAAW,YAEX,IAAK,MAEL,QAAS,SACb,EAaaC,EAAY,CAUrB,WAAY,oBAEZ,SAAU,YAEV,UAAW,YAEX,UAAW,sBAEX,eAAgB,wBAEhB,SAAU,uBAEV,IAAK,wBAWL,YAAa,cAEb,QAAS,gBAET,UAAW,sBAEX,KAAM,aAEN,OAAQ,cACZ,EAOMC,GAA0B,IAAI,IAAI,CACpCD,EAAU,QACVA,EAAU,UACVA,EAAU,KACVA,EAAU,MACd,CAAC,EAMKE,GAAmB,CAarB,OAAQ,IAAI,IAAI,CACZF,EAAU,SACVA,EAAU,UACVA,EAAU,OACVA,EAAU,KACVA,EAAU,UACVA,EAAU,SACVA,EAAU,UACVA,EAAU,UACd,CAAC,EACD,QAAS,IAAI,IAAI,CAACA,EAAU,OAAO,CAAC,EACpC,KAAM,IAAI,IAAI,CAACA,EAAU,cAAc,CAAC,CAC5C,EAQMG,GAAgC,IAAI,IAAI,OAAO,OAAOH,CAAS,EAAE,OAAQoB,GAAM,CAACnB,GAAwB,IAAImB,CAAC,CAAC,CAAC,EAQ/GhB,GAA6B,IAwCtBrD,EAAN,MAAMsE,UAAkB,KAAM,CACjC,KACA,OACA,QACA,YAAYC,EAAMC,EAASC,EAAQC,EAAS,CACxC,MAAMF,CAAO,EACb,KAAK,KAAOD,EACZ,KAAK,OAASE,EACd,KAAK,QAAUC,EACf,KAAK,KAAO,WAChB,CAEA,YAAa,CAIT,IAAMC,EAAc,KAAK,QACnBD,EAAU,KAAK,OAASzB,EAAU,gBAAkB0B,GAAa,SAAW,OAAY,KAAK,QACnG,MAAO,CACH,MAAO,KAAK,KACZ,QAAS,KAAK,QACd,OAAQ,KAAK,OACb,QAAAD,CACJ,CACJ,CAuBA,aAAa,iBAAiBE,EAAUC,EAAe,CACnD,IAAIL,EACAE,EACAI,EACJ,GAAI,CAEA,GADoBF,EAAS,QAAQ,IAAI,cAAc,GACtC,SAAS,kBAAkB,EAAG,CAC3C,IAAMG,EAAO,MAAMH,EAAS,KAAK,EACjC,GAAIG,GAAQ,OAAOA,GAAS,SAAU,CAClC,IAAMC,EAAMD,EACR,OAAOC,EAAI,SAAY,SACvBR,EAAUQ,EAAI,QACT,OAAOA,EAAI,OAAU,WAC1BR,EAAUQ,EAAI,OAClBN,EAAUM,EAAI,QACV,OAAOA,EAAI,OAAU,UAAY5B,GAA8B,IAAI4B,EAAI,KAAK,IAC5EF,EAAWE,EAAI,MAEvB,CACJ,KACK,CAQD,IAAMxD,GAAQ,MAAMoD,EAAS,KAAK,GAAG,KAAK,EACtCpD,GAAQ,CAACA,EAAK,WAAW,GAAG,GAAKA,EAAK,QAAU6B,KAChDmB,EAAUhD,EAElB,CACJ,MACM,CAEN,CAKA,IAAMyD,EAAmBL,EAAS,QAAQ,IAAI,aAAa,EAC3D,GAAIK,IAAqB,KAAM,CAC3B,IAAMrF,EAAQqF,EAAiB,KAAK,EAC9BC,EAAU,QAAQ,KAAKtF,CAAK,EAC5B,OAAOA,CAAK,EACZ,KAAK,MAAM,KAAK,MAAMA,CAAK,EAAI,KAAK,IAAI,GAAK,GAAI,EACvD,GAAI,OAAO,SAASsF,CAAO,GAAKA,GAAW,EAAG,CAC1C,IAAMC,EAAWT,GAAW,OAAOA,GAAY,SAAWA,EAAU,CAAC,EACjES,EAAS,aAAe,SACxBT,EAAU,CAAE,GAAGS,EAAU,WAAYD,CAAQ,EAErD,CACJ,CACAV,EAAUA,GAAW,GAAGK,GAAiB,SAAS,uBAAuBD,EAAS,MAAM,GACxF,IAAML,EAAOO,IACRF,EAAS,SAAW,IACf3B,EAAU,eACV2B,EAAS,SAAW,IAChB3B,EAAU,UACV2B,EAAS,SAAW,IAChB3B,EAAU,UACVA,EAAU,KAC5B,OAAO,IAAIqB,EAAUC,EAAMC,EAASI,EAAS,OAAQF,CAAO,CAChE,CAoBA,OAAO,eAAetE,EAAOyE,EAAe,CACxC,GAAIxE,EAAYD,CAAK,EACjB,OAAOA,EACX,IAAMgF,EAAKP,GAAiB,UAC5B,OAAIzE,aAAiB,MACbA,EAAM,OAAS,aACRkE,EAAU,UAAU,GAAGc,CAAE,gBAAgB,EAEhDjF,GAAmBC,CAAK,EACjBkE,EAAU,QAAQ,GAAGc,CAAE,YAAYhF,EAAM,OAAO,GAAI,CAAE,MAAAA,CAAM,CAAC,EAEjE,IAAIkE,EAAUrB,EAAU,IAAK,GAAGmC,CAAE,YAAYhF,EAAM,OAAO,EAAE,EAEjE,IAAIkE,EAAUrB,EAAU,IAAK,GAAGmC,CAAE,wBAAwB,CACrE,CAKA,OAAO,WAAWZ,EAASE,EAAS,CAChC,OAAO,IAAIJ,EAAUrB,EAAU,WAAYuB,EAAS,IAAKE,CAAO,CACpE,CACA,OAAO,SAASW,EAAUC,EAAI,CAC1B,IAAMd,EAAUc,EAAK,GAAGD,CAAQ,IAAIC,CAAE,aAAe,GAAGD,CAAQ,aAChE,OAAO,IAAIf,EAAUrB,EAAU,SAAUuB,EAAS,GAAG,CACzD,CACA,OAAO,UAAUA,EAASE,EAAS,CAC/B,OAAO,IAAIJ,EAAUrB,EAAU,UAAWuB,EAAS,IAAKE,CAAO,CACnE,CACA,OAAO,UAAUF,EAAU,oBAAqBE,EAAS,CACrD,OAAO,IAAIJ,EAAUrB,EAAU,UAAWuB,EAAS,IAAKE,CAAO,CACnE,CAcA,OAAO,eAAeF,EAAU,0BAA2BE,EAAS,CAChE,OAAO,IAAIJ,EAAUrB,EAAU,eAAgBuB,EAAS,IAAKE,CAAO,CACxE,CACA,OAAO,SAASF,EAASC,EAAS,IAAKC,EAAS,CAC5C,OAAO,IAAIJ,EAAUrB,EAAU,SAAUuB,EAASC,EAAQC,CAAO,CACrE,CACA,OAAO,QAAQF,EAASE,EAAS,CAC7B,OAAO,IAAIJ,EAAUrB,EAAU,QAASuB,EAAS,OAAWE,CAAO,CACvE,CACA,OAAO,UAAUF,EAASE,EAAS,CAC/B,OAAO,IAAIJ,EAAUrB,EAAU,UAAWuB,EAAS,OAAWE,CAAO,CACzE,CACA,OAAO,KAAKF,EAASE,EAAS,CAC1B,OAAO,IAAIJ,EAAUrB,EAAU,KAAMuB,EAAS,OAAWE,CAAO,CACpE,CACA,OAAO,OAAOF,EAASE,EAAS,CAC5B,OAAO,IAAIJ,EAAUrB,EAAU,OAAQuB,EAAS,OAAWE,CAAO,CACtE,CACA,OAAO,IAAIF,EAASC,EAAS,IAAKC,EAAS,CACvC,OAAO,IAAIJ,EAAUrB,EAAU,IAAKuB,EAASC,EAAQC,CAAO,CAChE,CAUA,OAAO,YAAYF,EAASE,EAAS,CACjC,OAAO,IAAIJ,EAAUrB,EAAU,YAAauB,EAAS,IAAKE,CAAO,CACrE,CAeA,eAAgB,CACZ,OAAIvB,GAAiB,OAAO,IAAI,KAAK,IAAI,EAC9B,GACJ,KAAK,SAAW,QAAa,KAAK,QAAU,KAAO,KAAK,OAAS,GAC5E,CACA,gBAAiB,CACb,OAAOA,GAAiB,QAAQ,IAAI,KAAK,IAAI,CACjD,CACA,aAAc,CACV,OAAOA,GAAiB,KAAK,IAAI,KAAK,IAAI,CAC9C,CACA,OAAOoC,EAAW,CACd,OAAO,KAAK,OAASA,CACzB,CACJ,EAgCa5E,GAAqB,IAAI,IAAI,CAEtC,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MAEA,MACA,OAEA,MACA,MACA,MAEA,MACA,MACA,MAEA,MACA,MACA,MACA,KACA,MACA,MACA,MACA,MAEA,MACA,OAEA,MACA,MAEA,MACA,MACA,KACJ,CAAC,EAkCK2C,GAAsB,CAExB,OACA,MACA,QACA,MACA,MACA,KACA,WACA,MACA,MAEA,OACA,QACA,cACA,MACA,OACA,OACA,MACA,MACA,OAEA,MACA,OACA,OACA,OAEA,KACA,MACA,MACA,MACA,KACA,MACA,OACA,MACA,SAEA,MACA,MACA,OACA,MACA,OACA,OACA,MACA,MACA,MACA,MACA,OACA,OACA,OAEA,OACA,QACA,MACA,MACA,MAEA,MACA,MACA,MACA,MACA,OACA,MACA,MACA,OACA,OAEA,MACA,OACA,MACA,MACA,MACA,MAEA,MACA,OACA,OAEA,MACA,MAEA,KACJ,EAsBaC,GAAkBD,GAAoB,IAAK5C,GAAQ,IAAIA,CAAG,EAAE,EAAE,KAAK,GAAG,EAgBtEG,GAAwB,0BAoBxBI,GAA0B,IAAI,IAAI,CAC3C,eACA,cACJ,CAAC,EAsCYuC,GAAa,CACtB,QAAS,UACT,QAAS,SACT,MAAO,QACP,MAAO,QACP,MAAO,QACP,QAAS,UACT,OAAQ,QACZ,EAKapC,GAAU,CAEnB,OAAQ,QAER,WAAY,GAEZ,aAAc,GAEd,YAAa,CACjB,EAKaE,GAAe,CAExB,OAAQ,UAER,WAAY,GAEZ,aAAc,EAClB,EAQaiB,EAAS,CAElB,OAAQ,WAER,WAAY,IAEZ,QAAS,mBACb,EAaalB,EAAY,CACrB,QAASmC,GAAW,QACpB,aAAcA,GAAW,MACzB,OAAQ,QACZ,EAmCa7B,EAA6B,YAE7B8B,GAAqB,CAC9B,SAAU,CAAC,CAAE,OAAQ,QAAS,YAAa,aAAc,CAAC,CAC9D,EAeaC,GAAwB,CAEjC,WAAY,aAEZ,gBAAiB,IAAM,IAC3B,EA8IaC,GAAc,6BAcdC,GAAW,CAEpB,MAAO,aAEP,QAAS,cACb,EAUaC,GAAiB,oCAejBC,GAAgC,KAAc,GA0E9CC,EAAoB,CAE7B,WAAY,EAEZ,WAAY,GAEZ,UAAW,GAEX,WAAY,KAChB,EASaC,GAAgB,iCAyChBnB,GAAuB,CAEhC,WAAY,EAEZ,WAAY,GAChB,ICzyCA,SAAS2C,IAA0C,CAEjD,OAAI,OAAO,QAAY,KAAe,QAAQ,UAAY,QAAQ,SAAS,KAClE,OAIL,OAAO,OAAW,KAAe,OAAO,KAAS,IAC5C,UAGF,SACT,CAWO,SAASC,GAA+B,CAE7C,OAAIC,IAKGF,GAAkB,CAC3B,CAhEA,IAWIE,GAXJC,GAAAC,EAAA,kBAWIF,GAAgD,OCFpD,eAAeG,GAAQC,EAAgC,CACrD,IAAMC,GAAY,KAAM,QAAO,WAAW,GAAG,QACvCC,EAAQ,IAAID,EAAS,YACrBE,EAAY,QAClB,QAASC,EAAQ,EAAGA,EAAQJ,EAAK,KAAMI,GAASD,EAAW,CACzD,IAAME,EAAM,KAAK,IAAID,EAAQD,EAAWH,EAAK,IAAI,EACjDE,EAAM,OAAO,MAAMF,EAAK,MAAMI,EAAOC,CAAG,EAAE,YAAY,CAAC,CACzD,CACA,MAAO,CAAE,IAAKH,EAAM,IAAI,CAAE,CAC5B,CAEA,eAAeI,GAAUC,EAAoC,CAE3D,GAAM,CAAE,WAAAC,CAAW,EAAI,KAAM,QAAO,QAAQ,EACtCC,EAAOD,EAAW,KAAK,EAC7B,OAAAC,EAAK,OAAOF,CAAM,EACX,CAAE,IAAKE,EAAK,OAAO,KAAK,CAAE,CACnC,CAEA,eAAeC,GAAQC,EAAkC,CAEvD,GAAM,CAAE,WAAAH,CAAW,EAAI,KAAM,QAAO,QAAQ,EAEtC,CAAE,iBAAAI,CAAiB,EAAI,KAAM,QAAO,IAAI,EAC9C,OAAO,IAAI,QAAQ,CAACC,EAASC,IAAW,CACtC,IAAML,EAAOD,EAAW,KAAK,EACvBO,EAASH,EAAiBD,CAAI,EAMpCI,EAAO,GAAG,QAAUC,GAClBF,EAAOG,EAAU,KAAK,gCAAgCD,EAAI,OAAO,GAAI,CAAE,SAAUL,CAAK,CAAC,CAAC,CAC1F,EACAI,EAAO,GAAG,OAASG,GAAUT,EAAK,OAAOS,CAAK,CAAC,EAC/CH,EAAO,GAAG,MAAO,IAAMF,EAAQ,CAAE,IAAKJ,EAAK,OAAO,KAAK,CAAE,CAAC,CAAC,CAC7D,CAAC,CACH,CAEA,eAAsBU,GAAaC,EAAmD,CACpF,GAAIA,aAAiB,KAAM,OAAOrB,GAAQqB,CAAK,EAC/C,GAAI,OAAO,OAAW,KAAe,OAAO,SAASA,CAAK,EAAG,OAAOd,GAAUc,CAAK,EACnF,GAAI,OAAOA,GAAU,SAAU,OAAOV,GAAQU,CAAK,EACnD,MAAMH,EAAU,SAAS,mCAAmC,CAC9D,CAtDA,IAAAI,GAAAC,EAAA,kBAGAC,MCUO,SAASC,GAAiBC,EAA4B,CAC3D,GAAI,CAACA,GAAYA,EAAS,SAAW,EAAG,MAAO,GAE/C,IAAMC,EAAkBD,EACrB,OAAQE,GAAMA,GAAK,OAAOA,GAAM,QAAQ,EACxC,IAAKA,GAAMA,EAAE,QAAQ,MAAO,GAAG,CAAC,EAEnC,GAAID,EAAgB,SAAW,EAAG,MAAO,GACzC,GAAIA,EAAgB,SAAW,EAAG,OAAOA,EAAgB,CAAC,EAE1D,IAAME,EAAeF,EAAgB,IAAKC,GAAMA,EAAE,MAAM,GAAG,EAAE,OAAO,OAAO,CAAC,EACtEE,EAAiB,CAAC,EAClBC,EAAY,KAAK,IAAI,GAAGF,EAAa,IAAKD,GAAMA,EAAE,MAAM,CAAC,EAE/D,QAASI,EAAI,EAAGA,EAAID,EAAWC,IAAK,CAClC,IAAMC,EAAUJ,EAAa,CAAC,EAAEG,CAAC,EACjC,GAAIH,EAAa,MAAOK,GAAaA,EAASF,CAAC,IAAMC,CAAO,EAC1DH,EAAe,KAAKG,CAAO,MAE3B,MAEJ,CAEA,OAAOH,EAAe,KAAK,GAAG,CAChC,CAkBO,SAASK,GAAiBC,EAAsB,CACrD,OAAOA,EAAK,QAAQ,MAAO,GAAG,EAAE,QAAQ,OAAQ,GAAG,EAAE,QAAQ,OAAQ,EAAE,CACzE,CAzDA,IAAAC,GAAAC,EAAA,oBC4BO,SAASC,GACdC,EACAC,EAAiC,CAAC,EACpB,CAEd,GAAIA,EAAQ,UAAY,GACtB,OAAOD,EAAU,IAAKE,IAAU,CAC9B,KAAMC,GAAiBD,CAAI,EAC3B,KAAME,GAAgBF,CAAI,CAC5B,EAAE,EAIJ,IAAMG,EAAeC,GAAoBN,CAAS,EAElD,OAAOA,EAAU,IAAKO,GAAa,CACjC,IAAIC,EAAaL,GAAiBI,CAAQ,EAG1C,GAAIF,EAAc,CAChB,IAAMI,EAAiBJ,EAAa,SAAS,GAAG,EAAIA,EAAe,GAAGA,CAAY,IAC9EG,EAAW,WAAWC,CAAc,IACtCD,EAAaA,EAAW,UAAUC,EAAe,MAAM,EAE3D,CAGA,OAAKD,IACHA,EAAaJ,GAAgBG,CAAQ,GAGhC,CACL,KAAMC,EACN,KAAMJ,GAAgBG,CAAQ,CAChC,CACF,CAAC,CACH,CAWA,SAASD,GAAoBN,EAA6B,CACxD,GAAI,CAACA,EAAU,OAAQ,MAAO,GAM9B,IAAMU,EAHkBV,EAAU,IAAKE,GAASC,GAAiBD,CAAI,CAAC,EAGjC,IAAKA,GAASA,EAAK,MAAM,GAAG,CAAC,EAC5DS,EAA2B,CAAC,EAC5BC,EAAY,KAAK,IAAI,GAAGF,EAAa,IAAKG,GAAaA,EAAS,MAAM,CAAC,EAG7E,QAASC,EAAI,EAAGA,EAAIF,EAAY,EAAGE,IAAK,CAEtC,IAAMC,EAAUL,EAAa,CAAC,EAAEI,CAAC,EACjC,GAAIJ,EAAa,MAAOG,GAAaA,EAASC,CAAC,IAAMC,CAAO,EAC1DJ,EAAe,KAAKI,CAAO,MAE3B,MAEJ,CAEA,OAAOJ,EAAe,KAAK,GAAG,CAChC,CAKA,SAASP,GAAgBF,EAAsB,CAC7C,OAAOA,EAAK,MAAM,OAAO,EAAE,IAAI,GAAKA,CACtC,CAzGA,IAAAc,GAAAC,EAAA,kBAKAC,OC4CO,SAASC,GAAiBC,EAAuD,CACtF,GAAIC,GAAeD,CAAQ,EACzB,MAAO,CAAE,MAAO,GAAO,OAAQ,sCAAuC,EAGxE,GAAIA,EAAS,WAAW,GAAG,GAAKA,EAAS,SAAS,GAAG,EACnD,MAAO,CAAE,MAAO,GAAO,OAAQ,wCAAyC,EAG1E,GAAIA,EAAS,SAAS,GAAG,EACvB,MAAO,CAAE,MAAO,GAAO,OAAQ,gCAAiC,EAGlE,IAAME,EAAgB,8CAChBC,EAAkBH,EAAS,MAAM,GAAG,EAAE,IAAI,GAAKA,EACrD,OAAIE,EAAc,KAAKC,CAAe,EAC7B,CAAE,MAAO,GAAO,OAAQ,uCAAwC,EAGrEH,EAAS,SAAS,IAAI,EACjB,CAAE,MAAO,GAAO,OAAQ,2CAA4C,EAGtE,CAAE,MAAO,EAAK,CACvB,CAzEA,IAAAI,GAAAC,EAAA,kBAYAC,MC+DO,SAASC,GAAWC,EAAqBC,EAAgD,CAC9F,GAAI,CAACD,GAAaA,EAAU,SAAW,EACrC,MAAO,CAAC,EAMV,GAAI,CAACC,GAAS,cACGD,EAAU,KAAME,GAAMA,GAAKC,GAAiBD,CAAC,CAAC,EAE3D,MAAME,EAAU,SACd,wGACF,EAIJ,OAAOJ,EAAU,OAAQK,GAAa,CACpC,GAAI,CAACA,EACH,MAAO,GAIT,IAAMC,EAAQD,EAAS,QAAQ,MAAO,GAAG,EAAE,MAAM,GAAG,EAAE,OAAO,OAAO,EACpE,GAAIC,EAAM,SAAW,EAAG,MAAO,GAG/B,IAAMC,EAAWD,EAAMA,EAAM,OAAS,CAAC,EACvC,MAAI,WAAOC,CAAQ,EACjB,MAAO,GAMT,QAAWC,KAAQF,EACjB,GAAIE,IAAS,gBACTA,EAAK,WAAW,GAAG,GAAKA,EAAK,OAAS,KACxC,MAAO,GAKX,IAAMC,EAAoBH,EAAM,MAAM,EAAG,EAAE,EAC3C,QAAWI,KAAWD,EACpB,GAAIE,GAAiB,KAAMC,GAAYF,EAAQ,YAAY,IAAME,EAAQ,YAAY,CAAC,EACpF,MAAO,GAIX,MAAO,EACT,CAAC,CACH,CA/HA,IASAC,GAUaF,GAnBbG,GAAAC,EAAA,kBAQAC,IACAH,GAAuB,gBAUVF,GAAmB,CAAC,WAAY,WAAY,aAAc,iBAAiB,ICIjF,SAASM,GAAmBC,EAAoBC,EAAgC,CACrF,GACED,EAAW,SAAS,IAAI,GACxBA,EAAW,SAAS,MAAM,GAC1BA,EAAW,WAAW,KAAK,GAC3BA,EAAW,SAAS,KAAK,EAEzB,MAAME,EAAU,SACd,qCAAqCF,CAAU,eAAeC,CAAgB,EAChF,CAEJ,CAWO,SAASE,GAAmBH,EAAoBC,EAAgC,CACrF,IAAMG,EAAYC,GAAiBL,CAAU,EAC7C,GAAI,CAACI,EAAU,MACb,MAAMF,EAAU,SAASE,EAAU,QAAU,mBAAmB,EAGlE,GAAIE,GAAmBN,CAAU,EAC/B,MAAME,EAAU,SAAS,gCAAgCD,CAAgB,GAAG,CAEhF,CAtDA,IAAAM,GAAAC,EAAA,kBAIAC,IACAC,OCLA,IAAAC,GAAA,GAAAC,GAAAD,GAAA,yBAAAE,KAwBA,SAASC,GAAiBC,EAAiBC,EAAuB,IAAI,IAAiB,CACrF,IAAMC,EAAoB,CAAC,EAGrBC,EAAc,eAAaH,CAAO,EACxC,GAAIC,EAAQ,IAAIE,CAAQ,EAEtB,OAAOD,EAETD,EAAQ,IAAIE,CAAQ,EAEpB,IAAMC,EAAa,cAAYJ,CAAO,EAEtC,QAAWK,KAASD,EAAS,CAC3B,IAAME,EAAgB,OAAKN,EAASK,CAAK,EACnCE,EAAW,WAASD,CAAQ,EAElC,GAAIC,EAAM,YAAY,EAAG,CACvB,IAAMC,EAAWT,GAAiBO,EAAUL,CAAO,EACnDC,EAAQ,KAAK,GAAGM,CAAQ,CAC1B,MAAWD,EAAM,OAAO,GACtBL,EAAQ,KAAKI,CAAQ,CAEzB,CAEA,OAAOJ,CACT,CAgBA,eAAsBJ,GACpBW,EACAC,EAA6B,CAAC,EAC9BC,EACuB,CACvB,GAAIC,EAAO,IAAM,OACf,MAAMC,EAAU,SAAS,gEAAgE,EAI3F,QAAWC,KAAKL,EAAO,CACrB,IAAMM,EAAe,UAAQD,CAAC,EAC9B,GAAI,CACF,GAAO,WAASC,CAAO,EAAE,YAAY,EAAG,CACtC,IAAMC,EAAY,cAAYD,CAAO,EAAE,KAAME,GAAMC,GAAwB,IAAID,CAAC,CAAC,EACjF,GAAID,EACF,MAAMH,EAAU,SACd,IAAIG,CAAM,0FACZ,CAEJ,CACF,OAASC,EAAG,CACV,GAAIE,EAAYF,CAAC,EAAG,MAAMA,CAE5B,CACF,CAGA,IAAMG,EAAgBX,EAAM,QAASK,GAAM,CACzC,IAAMC,EAAe,UAAQD,CAAC,EAC9B,GAAI,CAEF,OADiB,WAASC,CAAO,EACpB,YAAY,EAAIhB,GAAiBgB,CAAO,EAAI,CAACA,CAAO,CACnE,MAAiB,CACf,MAAMF,EAAU,KAAK,wBAAwBC,CAAC,GAAI,CAAE,SAAUA,CAAE,CAAC,CACnE,CACF,CAAC,EACKO,EAAc,CAAC,GAAG,IAAI,IAAID,CAAa,CAAC,EAGxCE,EAAqBb,EAAM,IAAKK,GAAW,UAAQA,CAAC,CAAC,EACrDS,EAAgBC,GACpBF,EAAmB,IAAKR,GAAM,CAC5B,GAAI,CAEF,OADiB,WAASA,CAAC,EACd,YAAY,EAAIA,EAAS,UAAQA,CAAC,CACjD,MAAQ,CACN,OAAY,UAAQA,CAAC,CACvB,CACF,CAAC,CACH,EAGMW,EAAeJ,EAAY,IAAKN,GAAY,CAChD,GAAIQ,GAAiBA,EAAc,OAAS,EAAG,CAC7C,IAAMG,EAAW,WAASH,EAAeR,CAAO,EAChD,GAAIW,GAAO,OAAOA,GAAQ,UAAY,CAACA,EAAI,WAAW,IAAI,EACxD,OAAOA,EAAI,QAAQ,MAAO,GAAG,CAEjC,CACA,OAAY,WAASX,CAAO,CAC9B,CAAC,EAMKY,EAHcC,GAAoBH,EAAc,CACpD,QAASf,EAAQ,aAAe,EAClC,CAAC,EAC+B,IAAK,GAAM,EAAE,IAAI,EAG3CmB,EAAc,IAAI,IAAIC,GAAWH,CAAW,CAAC,EACnD,GAAIE,EAAY,OAAS,EACvB,MAAO,CAAC,EAIV,IAAME,EAA0B,CAAC,EAC3BC,EAA6B,CAAC,EACpC,QAASC,EAAI,EAAGA,EAAIZ,EAAY,OAAQY,IAClCJ,EAAY,IAAIF,EAAYM,CAAC,CAAC,IAChCF,EAAc,KAAKV,EAAYY,CAAC,CAAC,EACjCD,EAAiB,KAAKL,EAAYM,CAAC,CAAC,GAKxC,IAAM/B,EAAwB,CAAC,EAC3BgC,EAAY,EAChB,GAAI,CAACvB,EACH,MAAME,EAAU,OACd,uHAEF,EAGF,QAASoB,EAAI,EAAGA,EAAIF,EAAc,OAAQE,IAAK,CAC7C,IAAME,EAAWJ,EAAcE,CAAC,EAC1BG,EAAaJ,EAAiBC,CAAC,EAErC,GAAI,CAEFI,GAAmBD,EAAYD,CAAQ,EAEvC,IAAM5B,EAAW,WAAS4B,CAAQ,EAGlC,GAAI5B,EAAM,OAAS,EACjB,SAOF,GAHA+B,GAAmBF,EAAYD,CAAQ,EAGnC5B,EAAM,KAAOI,EAAe,YAC9B,MAAME,EAAU,SACd,QAAQsB,CAAQ,0CAA0CxB,EAAe,aAAe,KAAO,KAAK,KACtG,EAGF,GADAuB,GAAa3B,EAAM,KACf2B,EAAYvB,EAAe,aAC7B,MAAME,EAAU,SACd,sDAAsDF,EAAe,cAAgB,KAAO,KAAK,KACnG,EAGF,IAAM4B,EAAa,eAAaJ,CAAQ,EAClC,CAAE,IAAAK,EAAI,EAAI,MAAMC,GAAaF,CAAO,EAE1CrC,EAAQ,KAAK,CACX,KAAMkC,EACN,QAAAG,EACA,KAAMA,EAAQ,OACd,IAAAC,EACF,CAAC,CACH,OAASE,EAAO,CAEd,GAAIvB,EAAYuB,CAAK,EACnB,MAAMA,EAGR,IAAMC,EAAeD,aAAiB,MAAQA,EAAM,QAAU,OAAOA,CAAK,EAC1E,MAAM7B,EAAU,KAAK,wBAAwBsB,CAAQ,MAAMQ,CAAY,GAAI,CAAE,SAAAR,CAAS,CAAC,CACzF,CACF,CAGA,GAAIjC,EAAQ,OAASS,EAAe,cAClC,MAAME,EAAU,SACd,gDAAgDF,EAAe,aAAa,SAC9E,EAGF,OAAOT,CACT,CA5NA,IAKA0C,EACAC,EANAC,GAAAC,EAAA,kBAKAH,EAAoB,mBACpBC,EAAsB,qBAEtBG,IACAC,KACAC,KACAC,KACAC,KACAC,KACAC,OCIA,IAAAC,GAA+B,qBCP/B,IAAAC,EAAmD,cACnDC,GAAsB,qBACtBC,IAWA,IAAAC,EAAoE,qBACpEC,GAA0B,uBCT1BC,IACA,IAAAC,GAAkB,eCJlB,IAAAC,GAAkB,eAELC,EAAoB,CAC/B,OAAQ,KAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAClC,MAAO,KAAE,OAAO,EAAE,IAAI,CAAC,EAAE,SAAS,CACpC,EDCAC,KAUA,IAAMC,GAAkB,KAAE,OAAOC,CAAiB,EAAE,OAAO,EAQrDC,GAA2C,CAC/C,OAAQC,GAAS,QACjB,MAAOA,GAAS,KAClB,EAYO,SAASC,GAA4C,CAC1D,GAAIC,EAAO,IAAM,OAAQ,MAAO,CAAC,EAEjC,IAAMC,EAAM,CACV,OAAQ,QAAQ,IAAIH,GAAS,OAAO,GAAK,OACzC,MAAO,QAAQ,IAAIA,GAAS,KAAK,GAAK,MACxC,EAEA,GAAI,CACF,OAAOH,GAAgB,MAAMM,CAAG,CAClC,OAASC,EAAO,CACd,GAAIA,aAAiB,KAAE,SAAU,CAC/B,IAAMC,EAAQD,EAAM,OAAO,CAAC,EACtBE,EAAQD,EAAM,KAAK,CAAC,EACpBE,GAAUD,GAASP,GAAiBO,CAAK,IAAM,iCACrD,MAAME,EAAU,OAAO,WAAWD,CAAM,KAAKF,EAAM,OAAO,EAAE,CAC9D,CACA,MAAMG,EAAU,OAAO,mCAAmC,CAC5D,CACF,CEjDA,IAAAC,EAAoB,mBACpBC,GAAoB,mBACpBC,EAAsB,qBACtBC,ICSO,IAAMC,EAAiBC,GAC5BA,EAAI,SAAS,OAAQC,GAAMA,EAAE,KAAK,IAAM,MAAM,EAG1CC,GAAYC,GAChBJ,EAAcI,CAAO,EAAE,OAAQF,GAAMF,EAAcE,CAAC,EAAE,OAAS,CAAC,EAO5DG,GAAaJ,GACjBA,EAAI,oBAAoB,KAAMK,GAAQ,oBAAoB,KAAKA,EAAI,KAAK,CAAC,CAAC,EAGtEC,GAAeH,GACnBA,EAAQ,QAAQ,OAAQI,GAAM,wBAAwB,KAAKA,EAAE,KAAK,CAAC,EAAE,IAAKA,GAAMA,EAAE,MAAQ,EAAE,EASxFC,GAAcR,GAClBA,EAAI,QAAQ,QAASO,GACnB,CAACA,EAAE,KAAMA,EAAE,KAAK,EAAE,OAAO,OAAO,EAAE,IAAKE,IAAU,CAC/C,KAAMA,EACN,YAAaF,EAAE,WACjB,EAAE,CACJ,EAGIG,GAAkBP,GACtBD,GAASC,CAAO,EAAE,QAASQ,GACzBZ,EAAcY,CAAK,EAChB,OAAQC,GAAQA,EAAI,QAAQ,OAAS,CAAC,EACtC,IAAKA,IAAS,CAAE,KAAM,GAAGD,EAAM,KAAK,CAAC,IAAIC,EAAI,KAAK,CAAC,GAAI,IAAKA,CAAI,EAAE,CACvE,EAGIC,GAAeV,GACnBA,EAAQ,QAAQ,QAASI,GACvB,CAACA,EAAE,KAAMA,EAAE,KAAK,EAAE,OAAO,OAAO,EAAE,IAAKE,IAAU,CAC/C,KAAMA,EACN,YAAaF,EAAE,WACjB,EAAE,CACJ,EAGIO,GAAgBX,GAA+B,CACnD,GAAGD,GAASC,CAAO,EAAE,QAASY,GAC5BhB,EAAcgB,CAAC,EACZ,OAAOX,EAAS,EAChB,IAAKH,GAAMA,EAAE,KAAK,CAAC,CACxB,EACA,GAAGK,GAAYH,CAAO,CACxB,EAEMa,GAAUC,GACd,GAAGA,CAAO;AAAA,EACPA,CAAO;AAAA,EACPA,CAAO;AAAA,EAMZ,SAASC,GAAWf,EAA0B,CAC5C,IAAMgB,EAAQjB,GAASC,CAAO,EAC3B,IACEQ,GAAU,QAAQA,EAAM,KAAK,CAAC;AAAA;AAAA,oCAEDZ,EAAcY,CAAK,EAC5C,IAAKV,GAAMA,EAAE,KAAK,CAAC,EACnB,KAAK,GAAG,CAAC;AAAA;AAAA;AAAA,SAIhB,EACC,KAAK;AAAA,CAAI,EAENmB,EAAWN,GAAaX,CAAO,EAClC,IAAKkB,GAAS,oBAAoBA,CAAI,GAAG,EACzC,KAAK,MAAM,EAEd,MAAO;AAAA,EACPL,GAAO,GAAG,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYXG,CAAK;AAAA;AAAA;AAAA,UAGGC,CAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQhBV,GAAeP,CAAO,EACrB,IACC,CAAC,CAAE,KAAAmB,EAAM,IAAAtB,CAAI,IACX,UAAUsB,CAAI,mBAAmBd,GAAWR,CAAG,EAC5C,IAAKuB,GAAMA,EAAE,IAAI,EACjB,KAAK,GAAG,CAAC,MAChB,EACC,KAAK;AAAA,CAAI,CAAC;AAAA;AAAA,gCAEmBV,GAAYV,CAAO,EAC5C,IAAKoB,GAAMA,EAAE,IAAI,EACjB,KAAK,GAAG,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA,gCAKgBxB,EAAcI,CAAO,EAC9C,IAAKF,GAAMA,EAAE,KAAK,CAAC,EACnB,KAAK,GAAG,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CAShB,CAYA,IAAMuB,GAAc,CAACH,EAAcI,IACjC,IAAIJ,CAAI,IAAII,EAAY,QAAQ,KAAM,KAAK,EAAE,QAAQ,KAAM,KAAK,CAAC,IAEnE,SAASC,GAAUvB,EAA0B,CAC3C,IAAMgB,EAAQjB,GAASC,CAAO,EAC3B,IACEQ,GAAU,UAAUA,EAAM,KAAK,CAAC;AAAA;AAAA,yBAEdZ,EAAcY,CAAK,EAC/B,IAAKV,GAAMuB,GAAYvB,EAAE,KAAK,EAAGA,EAAE,YAAY,CAAC,CAAC,EACjD,KAAK,GAAG,CAAC;AAAA,uBACCU,EAAM,KAAK,CAAC;AAAA;AAAA;AAAA,WAI/B,EACC,KAAK;AAAA,CAAI,EAENS,EAAWN,GAAaX,CAAO,EAClC,IAAKkB,GAAS,oBAAoBA,CAAI,GAAG,EACzC,KAAK,MAAM,EAEd,MAAO;AAAA,EACPL,GAAO,GAAG,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAoBXG,CAAK;AAAA;AAAA;AAAA,YAGKC,CAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qBAMCP,GAAYV,CAAO,EAC/B,IAAKoB,GAAMC,GAAYD,EAAE,KAAMA,EAAE,WAAW,CAAC,EAC7C,KAAK,GAAG,CAAC;AAAA;AAAA,EAEhBb,GAAeP,CAAO,EACrB,IACC,CAAC,CAAE,KAAAmB,EAAM,IAAAtB,CAAI,IACX,YAAYsB,CAAI,oBAAoBd,GAAWR,CAAG,EAC/C,IAAKuB,GAAMC,GAAYD,EAAE,KAAMA,EAAE,WAAW,CAAC,EAC7C,KAAK,GAAG,CAAC,MAChB,EACC,KAAK;AAAA,CAAI,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qBAOQxB,EAAcI,CAAO,EACjC,IAAKF,GAAMuB,GAAYvB,EAAE,KAAK,EAAGA,EAAE,YAAY,CAAC,CAAC,EACjD,KAAK,GAAG,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CAalB,CAMA,IAAM0B,GAAaC,GAAiBA,EAAK,QAAQ,MAAO,MAAM,EAAE,QAAQ,KAAM,KAAK,EAEnF,SAASC,GAAW1B,EAA0B,CAC5C,IAAM2B,EAAkB,CAAC,EAEzBA,EAAM,KAAK,sBAAsB,EACjC,QAAW9B,KAAOD,EAAcI,CAAO,EACrC2B,EAAM,KACJ,sDAAsD9B,EAAI,KAAK,CAAC,SAAS2B,GAAU3B,EAAI,YAAY,CAAC,CAAC,GACvG,EAGF8B,EAAM,KAAK,GAAI,kBAAkB,EACjC,QAAWC,KAAU5B,EAAQ,QAAS,CACpC,IAAMkB,EAAOU,EAAO,MAAM,QAAQ,MAAO,EAAE,EAC3C,GAAI,CAACV,EAAM,SAGX,IAAMW,EAAQ,wBAAwB,KAAKD,EAAO,KAAK,EACnD,MACAA,EAAO,UAAYA,EAAO,SACxB,MACA,GACND,EAAM,KAAK,uBAAuBT,CAAI,QAAQM,GAAUI,EAAO,WAAW,CAAC,IAAIC,CAAK,EAAE,CACxF,CAEA,QAAWrB,KAAST,GAASC,CAAO,EAAG,CACrC2B,EAAM,KAAK,GAAI,KAAKnB,EAAM,KAAK,CAAC,cAAc,EAC9C,QAAWC,KAAOb,EAAcY,CAAK,EACnCmB,EAAM,KACJ,uDAAuDnB,EAAM,KAAK,CAAC,SAASC,EAAI,KAAK,CAAC,SAASe,GAAUf,EAAI,YAAY,CAAC,CAAC,GAC7H,CAEJ,CAIA,OAAW,CAAE,KAAAU,EAAM,IAAAtB,CAAI,IAAKU,GAAeP,CAAO,EAAG,CACnD,GAAM,CAACQ,EAAOC,CAAG,EAAIU,EAAK,MAAM,GAAG,EACnCQ,EAAM,KAAK,GAAI,KAAKR,CAAI,UAAU,EAClC,QAAWS,KAAU/B,EAAI,QAAS,CAChC,IAAMqB,EAAOU,EAAO,MAAM,QAAQ,MAAO,EAAE,EAC3C,GAAI,CAACV,EAAM,SACX,IAAMW,EAAQD,EAAO,UAAYA,EAAO,SAAW,MAAQ,GAC3DD,EAAM,KACJ,uBAAuBT,CAAI,QAAQM,GAAUI,EAAO,WAAW,CAAC,IAAIC,CAAK,oCACpCrB,CAAK,qCAAqCC,CAAG,GACpF,CACF,CACF,CAEA,IAAMQ,EAAWN,GAAaX,CAAO,EACrC,OAAA2B,EAAM,KACJ,GACA,mEACA,6BACA,kCACA,+BACA,2BACA,8BAA8BV,EAAS,IAAKa,GAAM,IAAIA,CAAC,GAAG,EAAE,KAAK,GAAG,CAAC,GACrE,iBACA,UACA,QACA,aACA,MACA,4CACF,EAEO,GAAGjB,GAAO,GAAG,CAAC;AAAA,EAAKc,EAAM,KAAK;AAAA,CAAI,CAAC;AAAA,CAC5C,CAKO,SAASI,GAAiB/B,EAAkBgC,EAAsB,CACvE,OAAQA,EAAO,CACb,IAAK,OACH,OAAOjB,GAAWf,CAAO,EAC3B,IAAK,MACH,OAAOuB,GAAUvB,CAAO,EAC1B,IAAK,OACH,OAAO0B,GAAW1B,CAAO,CAC7B,CACF,CCnWA,IAAAiC,GAAsB,0BACtBC,EAA+D,uBAEzDC,GAAkB,CAAC,WAAY,OAAO,EAEtCC,EAAa,CAACC,EAAmCC,EAAcC,IAC5DA,EAAUD,EAAOD,EAAQC,CAAI,EAIhCE,GAAiBC,GACrB,OAAOA,GAAU,UAAYA,IAAU,MAAQ,CAAC,MAAM,QAAQA,CAAK,EAM/DC,GAAgBC,GACpB,cAAc,KAAKA,CAAG,EAAIA,EAAI,OAAO,CAAC,EAAE,YAAY,EAAIA,EAAI,MAAM,CAAC,EAAIA,EAU5DC,EAAgBD,GAAwBD,GAAaC,CAAG,EAAE,QAAQ,MAAO,EAAE,EAQ3EE,EAAU,CAACF,EAAaG,EAAgBP,IAAsB,CAEvE,QAAQ,IADNO,EACU,GAAG,KAAK,UAAU,CAAE,QAASH,CAAI,EAAG,KAAM,CAAC,CAAC;AAAA,EAE5C,GAAGP,EAAW,QAAOQ,EAAaD,CAAG,EAAGJ,CAAO,CAAC;AAAA,CAFA,CAIhE,EAEaQ,EAAO,CAACJ,EAAaG,EAAgBP,IAAsB,CACtE,GAAIO,EACF,QAAQ,IAAI,GAAG,KAAK,UAAU,CAAE,QAASH,CAAI,EAAG,KAAM,CAAC,CAAC;AAAA,CAAI,MACvD,CACL,IAAMK,EAAaZ,EAChBE,MAAS,cAAQ,UAAOA,CAAI,CAAC,EAC9B,GAAGF,EAAW,SAAQ,IAAKG,CAAO,CAAC,UAAUH,EAAW,SAAQ,IAAKG,CAAO,CAAC,GAC7EA,CACF,EACMU,EAAUb,EAAW,SAAQQ,EAAaD,CAAG,EAAGJ,CAAO,EAC7D,QAAQ,IAAI,GAAGS,CAAU,IAAIC,CAAO;AAAA,CAAI,CAC1C,CACF,EAEaC,EAAO,CAACP,EAAaG,EAAgBP,IAAsB,CACtE,GAAIO,EACF,QAAQ,IAAI,GAAG,KAAK,UAAU,CAAE,KAAMH,CAAI,EAAG,KAAM,CAAC,CAAC;AAAA,CAAI,MACpD,CACL,IAAMQ,EAAaf,EAChBE,MAAS,cAAQ,QAAKA,CAAI,CAAC,EAC5B,GAAGF,EAAW,SAAQ,IAAKG,CAAO,CAAC,OAAOH,EAAW,SAAQ,IAAKG,CAAO,CAAC,GAC1EA,CACF,EACMa,EAAUhB,EAAW,OAAMQ,EAAaD,CAAG,EAAGJ,CAAO,EAC3D,QAAQ,IAAI,GAAGY,CAAU,IAAIC,CAAO;AAAA,CAAI,CAC1C,CACF,EAiBO,SAASC,EAAMC,EAAyBR,EAAgBP,EAAyB,CACtF,GAAIO,GAAQ,OAAOQ,GAAQ,SAAU,CACnC,QAAQ,MAAM,GAAG,KAAK,UAAUA,EAAI,WAAW,EAAG,KAAM,CAAC,CAAC;AAAA,CAAI,EAC9D,MACF,CACA,IAAMC,EAAcnB,EACjBE,MAAS,cAAQ,OAAIA,CAAI,CAAC,EAC3B,GAAGF,EAAW,SAAQ,IAAKG,CAAO,CAAC,QAAQH,EAAW,SAAQ,IAAKG,CAAO,CAAC,GAC3EA,CACF,EACMiB,EAAU,OAAOF,GAAQ,SAAWA,EAAMA,EAAI,QAC9CG,EAAWrB,EAAW,MAAKQ,EAAaY,CAAO,EAAGjB,CAAO,EAC/D,QAAQ,MAAM,GAAGgB,CAAW,IAAIE,CAAQ;AAAA,CAAI,CAC9C,CAKO,IAAMC,GAAkB,CAC7BC,EACAC,EAA+B,UAC/BrB,IACW,CACX,GAA+BoB,GAAc,MAAQA,IAAc,EACjE,MAAO,IAGT,IAAME,EAAY,IAAI,KAAKF,EAAY,GAAI,EAAE,YAAY,EAAE,QAAQ,YAAa,GAAG,EAGnF,OAAIC,IAAY,QACPC,EACJ,QAAQ,IAAKzB,EAAW,SAAQ,IAAKG,CAAO,CAAC,EAC7C,QAAQ,KAAMH,EAAW,SAAQ,IAAKG,CAAO,CAAC,EAG5CsB,CACT,EAMMC,GAAc,CAClBC,EACAtB,EACAmB,EAA+B,UAC/BrB,IACW,CACX,GAAIE,IAAU,MAAS,MAAM,QAAQA,CAAK,GAAKA,EAAM,SAAW,EAAI,MAAO,IAC3E,GACE,OAAOA,GAAU,WAChBsB,IAAQ,WACPA,IAAQ,aACRA,IAAQ,WACRA,IAAQ,UACRA,IAAQ,SAGRA,IAAQ,QAEV,OAAOL,GAAgBjB,EAAOmB,EAASrB,CAAO,EAEhD,GAAIwB,IAAQ,QAAU,OAAOtB,GAAU,SAAU,CAC/C,IAAMuB,EAAKvB,EAAS,QACpB,OAAOuB,GAAM,EAAI,GAAGA,EAAG,QAAQ,CAAC,CAAC,KAAO,IAAIvB,EAAQ,MAAM,QAAQ,CAAC,CAAC,IACtE,CAEA,GAAIsB,IAAQ,UAAYA,IAAQ,WAAY,CAC1C,GAAI,OAAOtB,GAAU,UAAW,OAAOA,EAAQ,MAAQ,KACvD,GAAI,OAAOA,GAAU,SAAU,OAAOA,IAAU,EAAI,MAAQ,IAC9D,CAKA,GAAID,GAAcC,CAAK,EAAG,CACxB,IAAMwB,EAAQ,OAAO,QAAQxB,CAAK,EAClC,OAAIwB,EAAM,SAAW,EAAU,IACxBA,EAAM,IAAI,CAAC,CAACC,EAAGC,CAAC,IAAM,GAAGD,CAAC,IAAIJ,GAAYI,EAAGC,EAAGP,EAASrB,CAAO,CAAC,EAAE,EAAE,KAAK,IAAI,CACvF,CACA,OAAO,OAAOE,CAAK,CACrB,EASa2B,GAAc,CACzBC,EACAC,EACA/B,EACAgC,IACW,CACX,GAAI,CAACF,GAAQA,EAAK,SAAW,EAAG,MAAO,GAGvC,IAAMG,EAAYH,EAAK,CAAC,EAClBI,EACJH,GACA,OAAO,KAAKE,CAAS,EAAE,OACpBT,GAAQS,EAAUT,CAAG,IAAM,QAAa,CAAC5B,GAAgB,SAAS4B,CAAG,CACxE,EAGIW,EAAkBL,EAAK,IAAKM,GAAS,CACzC,IAAMC,EAASD,EACTE,EAAsC,CAAC,EAC7C,OAAAJ,EAAY,QAASK,GAAQ,CACvBA,KAAOF,GAAUA,EAAOE,CAAG,IAAM,SACnCD,EAAYC,CAAG,EAAIhB,GAAYgB,EAAKF,EAAOE,CAAG,EAAG,QAASvC,CAAO,EAErE,CAAC,EACMsC,CACT,CAAC,EAkBD,MAAO,MAhBQ,GAAAE,SAAUL,EAAiB,CACxC,eAAgB,MAChB,QAASD,EACT,OAAQA,EAAY,OAClB,CAACO,EAAQF,KACPE,EAAOF,CAAG,EAAI,CACZ,iBAAmBG,GACjB7C,EAAW,MAAKmC,IAAYU,CAAO,GAAKA,EAAS1C,CAAO,CAC5D,EACOyC,GAET,CAAC,CACH,CACF,CAAC,EAIE,MAAM;AAAA,CAAI,EACV,IACEE,GACCA,EACG,QAAQ,MAAO,EAAE,EACjB,QAAQ,OAAQ,EAAE,CACzB,EACC,KAAK;AAAA,CAAI,CAAC;AAAA,CACf,EAOaC,EAAgB,CAACC,EAAa7C,IAA8B,CACvE,IAAM8C,EAAW,OAAO,QAAQD,CAAG,EAA0B,OAAO,CAAC,CAACrB,EAAKtB,CAAK,IAC1EN,GAAgB,SAAS4B,CAAG,EAAU,GACnCtB,IAAU,MAClB,EAED,GAAI4C,EAAQ,SAAW,EAAG,MAAO,GAGjC,IAAMhB,EAAOgB,EAAQ,IAAI,CAAC,CAACtB,EAAKtB,CAAK,KAAO,CAC1C,SAAU,GAAGsB,CAAG,IAChB,MAAOD,GAAYC,EAAKtB,EAAO,UAAWF,CAAO,CACnD,EAAE,EAaF,MAAO,MAXQ,GAAAwC,SAAUV,EAAM,CAC7B,eAAgB,KAChB,YAAa,GACb,OAAQ,CACN,SAAU,CACR,cAAgB5B,GAAkBL,EAAW,MAAKK,EAAOF,CAAO,CAClE,CACF,CACF,CAAC,EAIE,MAAM;AAAA,CAAI,EACV,IAAK2C,GAAiBA,EAAK,QAAQ,MAAO,EAAE,CAAC,EAC7C,KAAK;AAAA,CAAI,CAAC;AAAA,CACf,EFzOA,SAASI,IAA8C,CACrD,IAAMC,EAAQ,QAAQ,IAAI,OAAS,GACnC,OAAIA,EAAM,SAAS,MAAM,EAAU,OAC/BA,EAAM,SAAS,KAAK,EAAU,MAC9BA,EAAM,SAAS,MAAM,EAAU,OAC5B,IACT,CAKA,SAASC,GAAcD,EAAgCE,EAAiB,CACtE,OAAQF,EAAO,CACb,IAAK,OACH,MAAO,CACL,eAAqB,OAAKE,EAAS,uBAAuB,EAC1D,YAAkB,OAAKA,EAAS,eAAe,CACjD,EACF,IAAK,MACH,MAAO,CACL,eAAqB,OAAKA,EAAS,sBAAsB,EACzD,YAAkB,OAAKA,EAAS,QAAQ,CAC1C,EACF,IAAK,OACH,MAAO,CACL,eAAqB,OAAKA,EAAS,oCAAoC,EACvE,YAAa,IACf,CACJ,CACF,CAKO,SAASC,GAAkBC,EAAkBC,EAA6B,CAAC,EAAS,CACzF,GAAM,CAAE,KAAAC,EAAM,QAAAC,CAAQ,EAAIF,EACpBL,EAAQD,GAAY,EACpBG,EAAa,WAAQ,EAE3B,GAAI,CAACF,EACH,MAAMQ,EAAU,OAAO,sBAAsB,QAAQ,IAAI,KAAK,8BAA8B,EAG9F,IAAMC,EAAQR,GAAcD,EAAOE,CAAO,EAGpCQ,EAASC,GAAiBP,EAASJ,CAAK,EAE9C,GAAI,CAEF,GAAIA,IAAU,OAAQ,CACpB,IAAMY,EAAe,UAAQH,EAAM,cAAc,EACzC,aAAWG,CAAO,GACrB,YAAUA,EAAS,CAAE,UAAW,EAAK,CAAC,EAExC,gBAAcH,EAAM,eAAgBC,CAAM,EAC7CG,EAAQ,yCAA0CP,EAAMC,CAAO,EAC/DO,EAAK,iDAAkDR,EAAMC,CAAO,EACpE,MACF,CAMG,gBAAcE,EAAM,eAAgBC,CAAM,EAC7C,IAAMK,EAAa;AAAA,UAAmBN,EAAM,cAAc;AAAA;AAAA,EAE1D,GAAIA,EAAM,YAAa,CACrB,GAAO,aAAWA,EAAM,WAAW,EAAG,CACpC,IAAMO,EAAa,eAAaP,EAAM,YAAa,OAAO,EAC1D,GAAI,CAACO,EAAQ,SAAS,QAAQ,GAAK,CAACA,EAAQ,SAAS,YAAY,EAAG,CAClE,IAAMC,EAASD,EAAQ,OAAS,GAAK,CAACA,EAAQ,SAAS;AAAA,CAAI,EAAI;AAAA,EAAO,GACnE,iBAAeP,EAAM,YAAaQ,EAASF,CAAU,CAC1D,CACF,MACK,gBAAcN,EAAM,YAAaM,CAAU,EAGhDF,EAAQ,mCAAmCb,CAAK,GAAIM,EAAMC,CAAO,EACjEW,EAAK,eAAeT,EAAM,WAAW,0BAA2BH,EAAMC,CAAO,CAC/E,CACF,OAASY,EAAG,CACV,GAAIC,EAAYD,CAAC,EAAG,MAAMA,EAC1B,IAAME,EAAUF,aAAa,MAAQA,EAAE,QAAU,OAAOA,CAAC,EACzD,MAAMX,EAAU,KAAK,wCAAwCa,CAAO,EAAE,CACxE,CACF,CAKO,SAASC,GAAoBjB,EAA6B,CAAC,EAAS,CACzE,GAAM,CAAE,KAAAC,EAAM,QAAAC,CAAQ,EAAIF,EACpBL,EAAQD,GAAY,EACpBG,EAAa,WAAQ,EAE3B,GAAI,CAACF,EACH,MAAMQ,EAAU,OAAO,sBAAsB,QAAQ,IAAI,KAAK,8BAA8B,EAG9F,IAAMC,EAAQR,GAAcD,EAAOE,CAAO,EAE1C,GAAI,CAEF,GAAIF,IAAU,OAAQ,CACb,aAAWS,EAAM,cAAc,GACjC,aAAWA,EAAM,cAAc,EAClCI,EAAQ,2CAA4CP,EAAMC,CAAO,GAEjEW,EAAK,oCAAqCZ,EAAMC,CAAO,EAEzDO,EAAK,iDAAkDR,EAAMC,CAAO,EACpE,MACF,CAOA,GAJO,aAAWE,EAAM,cAAc,GACjC,aAAWA,EAAM,cAAc,EAGhC,CAACA,EAAM,YAAa,OAExB,GAAI,CAAI,aAAWA,EAAM,WAAW,EAClC,MAAMD,EAAU,OAAO,wBAAwB,EAIjD,IAAMe,EADa,eAAad,EAAM,YAAa,OAAO,EACpC,MAAM;AAAA,CAAI,EAG1Be,EAAqB,CAAC,EACxBC,EAAI,EACJC,EAAU,GAEd,KAAOD,EAAIF,EAAM,QACf,GAAIA,EAAME,CAAC,EAAE,KAAK,IAAM,SAAU,CAGhC,IAFAC,EAAU,GACVD,IACOA,EAAIF,EAAM,QAAUA,EAAME,CAAC,EAAE,KAAK,IAAM,cAAcA,IACzDA,EAAIF,EAAM,QAAQE,GACxB,MACED,EAAS,KAAKD,EAAME,CAAC,CAAC,EACtBA,IAIJ,GAAIC,EAIC,gBAAcjB,EAAM,YAAae,EAAS,KAAK;AAAA,CAAI,CAAC,EACvDX,EAAQ,qCAAqCb,CAAK,GAAIM,EAAMC,CAAO,EACnEW,EAAK,eAAeT,EAAM,WAAW,0BAA2BH,EAAMC,CAAO,MAE7E,OAAMC,EAAU,OAAO,qCAAqC,CAEhE,OAASW,EAAG,CACV,GAAIC,EAAYD,CAAC,EAAG,MAAMA,EAC1B,IAAME,EAAUF,aAAa,MAAQA,EAAE,QAAU,OAAOA,CAAC,EACzD,MAAMX,EAAU,KAAK,0CAA0Ca,CAAO,EAAE,CAC1E,CACF,CGlLA,IAAAM,EAAqD,cACrDC,GAAwB,cACxBC,GAAqB,gBACrBC,GAAgC,6BAChCC,IACA,IAAAC,GAA2B,uBCL3B,IAAAC,GAAyC,cACzCC,GAAwB,cACxBC,GAAqB,gBACrBC,IACA,IAAAC,GAAkB,eAOlB,IAAMC,GAAmB,KAAE,OAAOC,CAAiB,EAAE,OAAO,EAGtDC,GAAiB,OAAM,YAAK,YAAQ,EAAG,SAAS,EAEhDC,GAAUC,GAAkBA,aAAe,MAAQA,EAAI,QAAU,OAAOA,CAAG,EAY1E,SAASC,GAAcC,EAA2C,CACvE,IAAIC,EACJ,GAAI,CACFA,KAAM,iBAAaD,EAAU,OAAO,CACtC,OAASE,EAAO,CACd,MAAMC,EAAU,OAAO,+BAA+BH,CAAQ,MAAMH,GAAOK,CAAK,CAAC,EAAE,CACrF,CAIA,GAAI,CAACD,EAAI,KAAK,EAAG,MAAO,CAAC,EAEzB,IAAIG,EACJ,GAAI,CACFA,EAAS,KAAK,MAAMH,CAAG,CACzB,OAASC,EAAO,CACd,MAAMC,EAAU,OAAO,qBAAqBH,CAAQ,KAAKH,GAAOK,CAAK,CAAC,EAAE,CAC1E,CAEA,GAAIE,IAAW,MAAQ,OAAOA,GAAW,UAAY,MAAM,QAAQA,CAAM,EACvE,MAAMD,EAAU,OAAO,qBAAqBH,CAAQ,0BAA0B,EAEhF,OAAOI,CACT,CAGA,SAASC,GAAeC,EAAiBC,EAA8C,CACrF,GAAI,CACF,OAAOb,GAAiB,MAAMY,CAAM,CACtC,OAASJ,EAAO,CACd,GAAIA,aAAiB,KAAE,SAAU,CAC/B,IAAMM,EAAQN,EAAM,OAAO,CAAC,EAG5B,GAAIM,EAAM,OAAS,oBAAqB,CACtC,IAAMC,EAASD,EAAM,KAAK,OAAQE,GAAQA,IAAQ,UAAYA,IAAQ,aAAa,EACnF,GAAID,EAAO,OAAS,EAAG,CACrB,IAAME,EAAOF,EAAO,IAAKC,GAAQ,IAAIA,CAAG,GAAG,EAAE,KAAK,OAAO,EACzD,MAAMP,EAAU,OACd,qBAAqBI,CAAQ,KAAKI,CAAI,IAAIF,EAAO,OAAS,EAAI,MAAQ,IAAI,wFAC5E,CACF,CACF,CACA,IAAMG,EAAOJ,EAAM,KAAK,OAAS,EAAI,OAAOA,EAAM,KAAK,KAAK,GAAG,CAAC,GAAK,GACrE,MAAML,EAAU,OAAO,qBAAqBI,CAAQ,GAAGK,CAAI,KAAKJ,EAAM,OAAO,EAAE,CACjF,CACA,MAAML,EAAU,OAAO,qBAAqBI,CAAQ,EAAE,CACxD,CACF,CAaO,SAASM,GAAaC,EAAiD,CAI5E,IAAMC,EAAWD,GAAc,OAC/B,GAAIC,EAAU,OAAOV,GAAeN,GAAcgB,CAAQ,EAAGA,CAAQ,EAErE,IAAMC,EAAOpB,GAAe,EAC5B,SAAO,eAAWoB,CAAI,EAAIX,GAAeN,GAAciB,CAAI,EAAGA,CAAI,EAAI,CAAC,CACzE,CDnFA,IAAMC,GAAe,OAAO,KAAKC,CAAiB,EAG5CC,GAAoB,OAAM,YAAK,YAAQ,EAAG,SAAS,EAMzD,SAASC,GAAUC,EAAuB,CACxC,OAAIA,EAAM,OAAS,GAAW,MACvB,GAAGA,EAAM,MAAM,EAAG,CAAC,CAAC,MAAMA,EAAM,MAAM,EAAE,CAAC,EAClD,CAaA,SAASC,GAAmBC,EAA6C,CACvE,GAAI,IAAC,cAAWA,CAAU,EAAG,MAAO,CAAC,EACrC,GAAI,CACF,OAAOC,GAAcD,CAAU,CACjC,OAASE,EAAK,CACZ,MAAKC,EAAYD,CAAG,EACdE,EAAU,OAAO,GAAGF,EAAI,OAAO,qCAAgC,EADxCA,CAE/B,CACF,CAOA,eAAsBG,GACpBC,EAAsD,CAAC,EACxC,CACf,GAAM,CAAE,QAAAC,CAAQ,EAAID,EAIdN,EAAaM,EAAQ,YAAcV,GAAkB,EACrDY,EAAYC,GAAkBF,EAAUE,KAAO,QAAIA,CAAI,EACvDC,EAAcD,GAAkBF,EAAUE,KAAO,UAAMA,CAAI,EAE3DE,EAAWZ,GAAmBC,CAAU,EACxCY,EAAgB,OAAOD,EAAS,OAAU,SAAWA,EAAS,MAAQ,OACtEE,EAAU,OAAO,KAAKF,CAAQ,EAAE,OAAQG,GAAQ,CAACpB,GAAa,SAASoB,CAAG,CAAC,EAE3EC,KAAK,oBAAgB,CACzB,MAAO,QAAQ,MACf,OAAQ,QAAQ,MAClB,CAAC,EAED,QAAQ,IAAI,EAAE,EACd,QAAQ,IAAI,KAAKP,EAAS,0BAA0B,CAAC,IAAIQ,EAAc,EAAE,EACzE,QAAQ,IAAI,EAAE,EAEd,IAAMC,EAASL,EAAgB,YAAYJ,EAASX,GAAUe,CAAa,CAAC,CAAC,MAAQ,YAEjFM,EACJ,GAAI,CACFA,GAAS,MAAMH,EAAG,SAASE,CAAM,GAAG,KAAK,CAC3C,QAAE,CACAF,EAAG,MAAM,CACX,CAGIG,GAAOC,EAAcD,CAAK,EAM9B,IAAME,EAAkC,CAAC,EACzC,QAAWN,KAAOpB,GACZiB,EAASG,CAAG,IAAM,SAAWM,EAAON,CAAG,EAAIH,EAASG,CAAG,GAEzDI,IAAOE,EAAO,MAAQF,GAEtBL,EAAQ,OAAS,GACnBQ,EACE,WAAWR,EAAQ,IAAKC,GAAQ,IAAIA,CAAG,GAAG,EAAE,KAAK,IAAI,CAAC,oCAA+BpB,GAAa,KAAK,OAAO,CAAC,GAC/G,GACAa,CACF,KAKF,iBAAcP,EAAY,GAAG,KAAK,UAAUoB,EAAQ,KAAM,CAAC,CAAC;AAAA,EAAM,CAAE,KAAM,GAAM,CAAC,KACjF,aAAUpB,EAAY,GAAK,EAC3B,QAAQ,IAAI;AAAA,IAAOU,EAAW,UAAU,CAAC,IAAIF,EAASR,CAAU,CAAC;AAAA,CAAI,CACvE,CExHAsB,ICWAC,ICCAC,ICdO,IAAMC,GAAN,KAAmB,CAAnB,cAEL,KAAQ,SAAW,IAAI,IAKvB,GAA+BC,EAAUC,EAAiD,CACnF,KAAK,SAAS,IAAID,CAAe,GACpC,KAAK,SAAS,IAAIA,EAAiB,IAAI,GAAK,EAE9C,KAAK,SAAS,IAAIA,CAAe,GAAG,IAAIC,CAAO,CACjD,CAKA,IAAgCD,EAAUC,EAAiD,CACzF,IAAMC,EAAgB,KAAK,SAAS,IAAIF,CAAe,EACnDE,IACFA,EAAc,OAAOD,CAAO,EACxBC,EAAc,OAAS,GACzB,KAAK,SAAS,OAAOF,CAAe,EAG1C,CAMA,KAAiCA,KAAaG,EAA2B,CACvE,IAAMD,EAAgB,KAAK,SAAS,IAAIF,CAAe,EACvD,GAAI,CAACE,EAAe,OAIpB,IAAME,EAAe,MAAM,KAAKF,CAAa,EAE7C,QAAWD,KAAWG,EACpB,GAAI,CACFH,EAAQ,GAAGE,CAAI,CACjB,OAASE,EAAO,CAKdH,EAAc,OAAOD,CAAO,EAExBD,IAAU,SACZ,WAAW,IAAM,CACf,IAAMM,EAAMD,aAAiB,MAAQA,EAAQ,IAAI,MAAM,OAAOA,CAAK,CAAC,EACpE,KAAK,KAAK,QAASC,EAAK,OAAON,CAAK,CAAC,CACvC,EAAG,CAAC,CAER,CAEJ,CACF,ECtDAO,IAYAA,IAYO,SAASC,GAAeC,EAA2D,CACxF,GAA4BA,GAAW,KAAM,OAC7C,GAAIA,EAAO,SAAW,EAAG,OAAOA,EAEhC,GAAIA,EAAO,OAASC,EAAkB,UACpC,MAAMC,EAAU,WAAW,WAAWD,EAAkB,SAAS,iBAAiB,EAGpF,IAAME,EAAaH,EAAO,IAAI,CAACI,EAAO,IAAM,CAC1C,GAAI,OAAOA,GAAU,SACnB,MAAMF,EAAU,WAAW,kBAAkB,CAAC,mBAAmB,EAEnE,IAAMG,EAAUD,EAAM,KAAK,EAAE,YAAY,EACzC,GAAIC,EAAQ,OAASJ,EAAkB,WACrC,MAAMC,EAAU,WACd,2BAA2BD,EAAkB,UAAU,kBACzD,EAEF,GAAII,EAAQ,OAASJ,EAAkB,WACrC,MAAMC,EAAU,WACd,+BAA+BD,EAAkB,UAAU,kBAC7D,EAEF,GAAI,CAACK,GAAc,KAAKD,CAAO,EAC7B,MAAMH,EAAU,WACd,qFAAqFD,EAAkB,UAAU,oBACnH,EAEF,OAAOI,CACT,CAAC,EAEKE,EAAS,CAAC,GAAG,IAAI,IAAIJ,CAAU,CAAC,EACtC,GAAII,EAAO,SAAWJ,EAAW,OAC/B,MAAMD,EAAU,WAAW,kCAAkC,EAG/D,OAAOK,CACT,CAiBA,eAAsBC,GAAqBC,EAAoC,CAC7E,IAAMC,EAASD,EAAM,KAClBE,GAAMA,EAAE,OAASC,GAA8BD,EAAE,OAAS,IAAIC,CAA0B,EAC3F,EACA,GAAI,CAACF,EAAQ,OAIb,IAAMG,EAAUH,EAAO,QACjBI,EACJ,OAAOD,EAAQ,MAAS,WACpB,MAAMA,EAAQ,KAAK,EAClBH,EAAO,QAAmB,SAAS,MAAM,EAEhDK,GAAqBD,CAAI,CAC3B,CF5DA,IAAME,GAA0B,IAgB1BC,GAAyB,IAWzBC,GAAuB,IAWvBC,GAA+BF,GAAyBC,GAexDE,GAAa,MAMnB,SAASC,GAAUC,EAA+B,CAChD,IAAMC,EAAS,IAAI,gBACfD,GAAS,QAAU,QAAWC,EAAO,IAAI,QAAS,OAAOD,EAAQ,KAAK,CAAC,EACvEA,GAAS,SAAW,QAAWC,EAAO,IAAI,SAAUD,EAAQ,MAAM,EACtE,IAAME,EAAQD,EAAO,SAAS,EAC9B,OAAOC,EAAQ,IAAIA,CAAK,GAAK,EAC/B,CAqBO,IAAMC,GAAN,cAAsBC,EAAa,CAexC,YAAYJ,EAAyB,CACnC,MAAM,EAHR,KAAQ,cAAwC,CAAC,EAI/C,KAAK,OAASA,EAAQ,QAAUK,GAChC,KAAK,uBAAyBL,EAAQ,eACtC,KAAK,QAAUA,EAAQ,SAAW,GAClC,KAAK,OAASA,EAAQ,OACtB,KAAK,QAAUA,EAAQ,SAAWN,GAIlC,KAAK,cAAgBM,EAAQ,SAAWL,GACxC,KAAK,mBAAqBK,EAAQ,SAAWH,GAI7C,KAAK,MAAQG,EAAQ,OAAS,WAAW,MAAM,KAAK,UAAU,EAC9D,KAAK,iBAAmBA,EAAQ,iBAChC,KAAK,eAAiBA,EAAQ,gBAAkBM,EAAU,WAC5D,CAMA,iBAAiBC,EAAuC,CACtD,KAAK,cAAgBA,CACvB,CASA,MAAc,eACZC,EACAR,EACAS,EACAC,EAAoB,KAAK,QACE,CAC3B,IAAIC,EAAU,IAAM,CAAC,EAErB,GAAI,CAIF,IAAMJ,EAAU,MAAM,KAAK,aAAaP,EAAQ,OAAiC,EAC3EY,EAAU,KAAK,oBAAoBZ,EAAQ,OAAQU,CAAS,EAClEC,EAAUC,EAAQ,QAElB,IAAMC,EAA4B,CAChC,GAAGb,EACH,QAAAO,EACA,YAAa,KAAK,SAAW,CAACA,EAAQ,cAAgB,UAAY,OAClE,OAAQK,EAAQ,MAClB,EAEA,KAAK,KAAK,UAAWJ,EAAKK,CAAY,EAEtC,IAAMC,EAAW,MAAM,KAAK,MAAMN,EAAKK,CAAY,EAGnD,GAFAF,EAAQ,EAEJ,CAACG,EAAS,GACZ,MAAM,MAAMC,EAAU,iBAAiBD,EAAUL,CAAa,EAGhE,YAAK,KAAK,WAAY,KAAK,UAAUK,CAAQ,EAAGN,CAAG,EAE5C,CAAE,KADI,MAAM,KAAK,cAAiB,KAAK,UAAUM,CAAQ,CAAC,EAClD,OAAQA,EAAS,MAAO,CACzC,OAASE,EAAO,CACdL,EAAQ,EAIR,IAAMM,EAAYF,EAAU,eAAeC,EAAOP,CAAa,EAC/D,WAAK,KAAK,QAASQ,EAAWT,CAAG,EAC3BS,CACR,CACF,CAKA,MAAc,QACZT,EACAR,EACAS,EACAC,EACY,CACZ,GAAM,CAAE,KAAAQ,CAAK,EAAI,MAAM,KAAK,eAAkBV,EAAKR,EAASS,EAAeC,CAAS,EACpF,OAAOQ,CACT,CAKA,MAAc,kBACZV,EACAR,EACAS,EAC2B,CAC3B,OAAO,KAAK,eAAkBD,EAAKR,EAASS,CAAa,CAC3D,CAMA,MAAc,aACZU,EAAwC,CAAC,EACR,CAIjC,MAAO,CACL,GAAG,KAAK,cACR,GAAI,KAAK,OAAS,CAAE,CAACC,EAAO,MAAM,EAAG,KAAK,MAAO,EAAI,CAAC,EACtD,GAAI,MAAM,KAAK,uBAAuB,EACtC,GAAGD,CACL,CACF,CAEQ,oBACNE,EACAX,EAAoB,KAAK,QAIzB,CACA,IAAMY,EAAa,IAAI,gBACjBC,EAAY,WAAW,IAAMD,EAAW,MAAM,EAAGZ,CAAS,EAEhE,GAAIW,EAAgB,CAClB,IAAMG,EAAQ,IAAMF,EAAW,MAAM,EACrCD,EAAe,iBAAiB,QAASG,CAAK,EAC1CH,EAAe,SAASC,EAAW,MAAM,CAC/C,CAEA,MAAO,CACL,OAAQA,EAAW,OACnB,QAAS,IAAM,aAAaC,CAAS,CACvC,CACF,CAEQ,UAAUT,EAA8B,CAC9C,GAAI,CACF,OAAOA,EAAS,MAAM,CACxB,MAAQ,CACN,OAAOA,CACT,CACF,CAEA,MAAc,cAAiBA,EAAgC,CAC7D,GAAI,EAAAA,EAAS,QAAQ,IAAI,gBAAgB,IAAM,KAAOA,EAAS,SAAW,KAG1E,OAAOA,EAAS,KAAK,CACvB,CAMA,MAAM,OACJW,EACAzB,EAA4B,CAAC,EACM,CACnC,GAAI,CAACyB,EAAM,OACT,MAAMV,EAAU,SAAS,oBAAoB,EAE/C,QAAWW,KAAQD,EACjB,GAAI,CAACC,EAAK,IACR,MAAMX,EAAU,KAAK,kCAAkCW,EAAK,IAAI,GAAI,CAClE,SAAUA,EAAK,IACjB,CAAC,EAKLC,GAAiB3B,EAAQ,QAAQ,EACjC,IAAM4B,EAAiBC,GAAuB7B,EAAQ,cAAc,EAC9D8B,EAASC,GAAe/B,EAAQ,MAAM,EAC5C,MAAMgC,GAAqBP,CAAK,EAEhC,IAAMQ,EACJjC,EAAQ,OAASA,EAAQ,WAAaA,EAAQ,IAC1C,CAAE,MAAOA,EAAQ,MAAO,UAAWA,EAAQ,UAAW,IAAKA,EAAQ,GAAI,EACvE,OACA,CAAE,KAAAkC,EAAM,QAASC,CAAY,EAAI,MAAM,KAAK,iBAAiBV,EAAO,CACxE,OAAAK,EACA,IAAK9B,EAAQ,KAAOF,GACpB,SAAUE,EAAQ,SAClB,MAAAiC,EACA,QAASjC,EAAQ,OACnB,CAAC,EAKD,OAAO,KAAK,QACV,GAAG,KAAK,MAAM,GAAG,KAAK,cAAc,GACpC,CACE,OAAQ,OACR,KAAAkC,EACA,QAASN,EACL,CAAE,GAAGO,EAAa,CAACC,GAA4B,MAAM,EAAGR,CAAe,EACvEO,EACJ,OAAQnC,EAAQ,QAAU,IAC5B,EACA,SAKAA,EAAQ,OAASA,EAAQ,UAAY,KAAK,mBAAqB,KAAK,aACtE,CACF,CAEA,MAAM,gBAAgBA,EAAwD,CAC5E,OAAO,KAAK,QACV,GAAG,KAAK,MAAM,GAAGM,EAAU,WAAW,GAAGP,GAAUC,CAAO,CAAC,GAC3D,CAAE,OAAQ,KAAM,EAChB,kBACF,CACF,CAEA,MAAM,cAAcqC,EAAiC,CACnD,OAAO,KAAK,QACV,GAAG,KAAK,MAAM,GAAG/B,EAAU,WAAW,mBAAmB+B,CAAE,CAAC,CAAC,GAC7D,CAAE,OAAQ,KAAM,EAChB,gBACF,CACF,CAEA,MAAM,uBAAuBA,EAAYP,EAAuC,CAC9E,IAAMQ,EAAaP,GAAeD,CAAM,EACxC,OAAO,KAAK,QACV,GAAG,KAAK,MAAM,GAAGxB,EAAU,WAAW,mBAAmB+B,CAAE,CAAC,CAAC,GAC7D,CACE,OAAQ,QACR,QAAS,CAAE,eAAgB,kBAAmB,EAC9C,KAAM,KAAK,UAAU,CAAE,OAAQC,CAAW,CAAC,CAC7C,EACA,0BACF,CACF,CAEA,MAAM,iBAAiBD,EAA+C,CACpE,OAAO,KAAK,QACV,GAAG,KAAK,MAAM,GAAG/B,EAAU,WAAW,mBAAmB+B,CAAE,CAAC,CAAC,GAC7D,CAAE,OAAQ,QAAS,EACnB,mBACF,CACF,CAQA,MAAM,UAAUE,EAAcC,EAAqBV,EAA6C,CAC9F,IAAMQ,EAAaP,GAAeD,CAAM,EAClCI,EAAmD,CAAC,EACtDM,IAAYN,EAAK,WAAaM,GAC9BF,IAAe,SAAWJ,EAAK,OAASI,GAE5C,GAAM,CAAE,KAAApB,EAAM,OAAAuB,CAAO,EAAI,MAAM,KAAK,kBAClC,GAAG,KAAK,MAAM,GAAGnC,EAAU,OAAO,mBAAmBiC,CAAI,CAAC,CAAC,GAC3D,CACE,OAAQ,MACR,QAAS,CAAE,eAAgB,kBAAmB,EAC9C,KAAM,KAAK,UAAUL,CAAI,CAC3B,EACA,YACF,EAEA,MAAO,CAAE,GAAGhB,EAAM,SAAUuB,IAAW,GAAI,CAC7C,CAEA,MAAM,YAAYzC,EAAoD,CACpE,OAAO,KAAK,QACV,GAAG,KAAK,MAAM,GAAGM,EAAU,OAAO,GAAGP,GAAUC,CAAO,CAAC,GACvD,CAAE,OAAQ,KAAM,EAChB,cACF,CACF,CAEA,MAAM,UAAUuC,EAA+B,CAC7C,OAAO,KAAK,QACV,GAAG,KAAK,MAAM,GAAGjC,EAAU,OAAO,mBAAmBiC,CAAI,CAAC,CAAC,GAC3D,CAAE,OAAQ,KAAM,EAChB,YACF,CACF,CAEA,MAAM,aAAaA,EAA6C,CAC9D,OAAO,KAAK,QACV,GAAG,KAAK,MAAM,GAAGjC,EAAU,OAAO,mBAAmBiC,CAAI,CAAC,CAAC,GAC3D,CAAE,OAAQ,QAAS,EACnB,eACF,CACF,CAEA,MAAM,aAAaA,EAA6C,CAC9D,OAAO,KAAK,QACV,GAAG,KAAK,MAAM,GAAGjC,EAAU,cAAc,mBAAmBiC,CAAI,CAAC,CAAC,GAClE,CAAE,OAAQ,MAAO,EACjB,eACF,CACF,CAEA,MAAM,aAAaA,EAA0C,CAC3D,OAAO,KAAK,QACV,GAAG,KAAK,MAAM,GAAGjC,EAAU,WAAW,mBAAmBiC,CAAI,CAAC,CAAC,GAC/D,CAAE,OAAQ,KAAM,EAChB,gBACF,CACF,CAEA,MAAM,iBAAiBA,EAA8C,CACnE,OAAO,KAAK,QACV,GAAG,KAAK,MAAM,GAAGjC,EAAU,eAAe,mBAAmBiC,CAAI,CAAC,CAAC,GACnE,CAAE,OAAQ,KAAM,EAChB,oBACF,CACF,CAEA,MAAM,eAAeA,EAA4C,CAC/D,OAAO,KAAK,QACV,GAAG,KAAK,MAAM,GAAGjC,EAAU,aAAa,mBAAmBiC,CAAI,CAAC,CAAC,GACjE,CAAE,OAAQ,KAAM,EAChB,kBACF,CACF,CAEA,MAAM,eAAeA,EAA+C,CAClE,OAAO,KAAK,QACV,GAAG,KAAK,MAAM,GAAGjC,EAAU,gBAAgB,GAC3C,CACE,OAAQ,OACR,QAAS,CAAE,eAAgB,kBAAmB,EAC9C,KAAM,KAAK,UAAU,CAAE,OAAQiC,CAAK,CAAC,CACvC,EACA,iBACF,CACF,CAMA,MAAM,YAAYG,EAAcZ,EAAiD,CAC/E,IAAMQ,EAAaP,GAAeD,CAAM,EAClCI,EAA4C,CAAC,EACnD,OAAIQ,IAAQ,SAAWR,EAAK,IAAMQ,GAC9BJ,IAAe,SAAWJ,EAAK,OAASI,GAErC,KAAK,QACV,GAAG,KAAK,MAAM,GAAGhC,EAAU,MAAM,GACjC,CACE,OAAQ,OACR,QAAS,CAAE,eAAgB,kBAAmB,EAC9C,KAAM,KAAK,UAAU4B,CAAI,CAC3B,EACA,cACF,CACF,CAEA,MAAM,WAAWlC,EAAmD,CAClE,OAAO,KAAK,QACV,GAAG,KAAK,MAAM,GAAGM,EAAU,MAAM,GAAGP,GAAUC,CAAO,CAAC,GACtD,CAAE,OAAQ,KAAM,EAChB,aACF,CACF,CAEA,MAAM,YAAY2C,EAA6C,CAC7D,OAAO,KAAK,QACV,GAAG,KAAK,MAAM,GAAGrC,EAAU,MAAM,mBAAmBqC,CAAK,CAAC,CAAC,GAC3D,CAAE,OAAQ,QAAS,EACnB,cACF,CACF,CAEA,MAAM,SAASA,EAA+B,CAC5C,OAAO,KAAK,QACV,GAAG,KAAK,MAAM,GAAGrC,EAAU,MAAM,mBAAmBqC,CAAK,CAAC,CAAC,GAC3D,CAAE,OAAQ,KAAM,EAChB,WACF,CACF,CAMA,MAAM,YAA0C,CAC9C,OAAO,KAAK,QAAQ,GAAG,KAAK,MAAM,GAAGrC,EAAU,OAAO,GAAI,CAAE,OAAQ,KAAM,EAAG,aAAa,CAC5F,CAEA,MAAM,WAAqC,CACzC,OAAO,KAAK,QAAQ,GAAG,KAAK,MAAM,GAAGA,EAAU,MAAM,GAAI,CAAE,OAAQ,KAAM,EAAG,YAAY,CAC1F,CAEA,MAAM,MAA8B,CAClC,OAAO,KAAK,QAAsB,GAAG,KAAK,MAAM,GAAGA,EAAU,IAAI,GAAI,CAAE,OAAQ,KAAM,EAAG,MAAM,CAChG,CAMA,MAAM,SAASmB,EAAqBmB,EAA6B,CAAC,EAAqB,CACrF,IAAMC,EAAYpB,EAAM,KACrBqB,GACCA,EAAE,OAASC,GAAsB,YACjCD,EAAE,OAAS,IAAIC,GAAsB,UAAU,EACnD,EACA,GAAI,CAACF,GAAaA,EAAU,KAAOE,GAAsB,gBACvD,MAAO,GAGT,IAAIC,EACJ,GAAI,OAAO,OAAW,KAAe,OAAO,SAASH,EAAU,OAAO,EACpEG,EAAeH,EAAU,QAAQ,SAAS,OAAO,UACxC,OAAO,KAAS,KAAeA,EAAU,mBAAmB,KACrEG,EAAe,MAAMH,EAAU,QAAQ,KAAK,UACnC,OAAO,KAAS,KAAeA,EAAU,mBAAmB,KACrEG,EAAe,MAAMH,EAAU,QAAQ,KAAK,MAE5C,OAAO,GAGT,IAAMX,EAAwB,CAAE,MAAOT,EAAM,IAAKqB,GAAMA,EAAE,IAAI,EAAG,MAAOE,CAAa,EAWrF,OAViB,MAAM,KAAK,QAC1B,GAAG,KAAK,MAAM,GAAG1C,EAAU,SAAS,GACpC,CACE,OAAQ,OACR,QAAS,CAAE,eAAgB,kBAAmB,EAC9C,KAAM,KAAK,UAAU4B,CAAI,CAC3B,EACA,WACF,GAEgB,KAClB,CACF,EGrlBAe,ICIAC,IAGAC,KAMA,eAAsBC,IAAuC,CAC3D,IAAMC,EAAe,KAAK,UAAUC,GAAoB,KAAM,CAAC,EAG3DC,EACA,OAAO,OAAW,IAEpBA,EAAU,OAAO,KAAKF,EAAc,OAAO,EAG3CE,EAAU,IAAI,KAAK,CAACF,CAAY,EAAG,CAAE,KAAM,kBAAmB,CAAC,EAGjE,GAAM,CAAE,IAAAG,CAAI,EAAI,MAAMC,GAAaF,CAAO,EAE1C,MAAO,CACL,KAAMG,EACN,QAAAH,EACA,KAAMF,EAAa,OACnB,IAAAG,CACF,CACF,CAWA,eAAsBG,GACpBC,EACAC,EACAC,EACuB,CAEvB,GACEA,EAAQ,YAAc,IACtBA,EAAQ,KACRA,EAAQ,OACRA,EAAQ,WACRF,EAAM,KAAMG,GAAMA,EAAE,OAASL,CAA0B,EAEvD,OAAOE,EAGT,GAAI,CAGF,GAFc,MAAMC,EAAU,SAASD,EAAOE,CAAO,EAE1C,CACT,IAAME,EAAY,MAAMZ,GAAgB,EACxC,MAAO,CAAC,GAAGQ,EAAOI,CAAS,CAC7B,CACF,MAAiB,CAEjB,CAEA,OAAOJ,CACT,CD1BO,SAASK,GACdC,EACuC,CACvC,GAAM,CAAE,OAAAC,EAAQ,WAAAC,EAAY,aAAAC,CAAa,EAAIH,EAE7C,MAAO,CACL,OAAQ,MAAOI,EAAoBC,EAA6B,CAAC,IAAM,CAGrE,GAFA,MAAMH,EAAW,EAEb,CAACC,EACH,MAAMG,EAAU,OAAO,wCAAwC,EAGjE,IAAMC,EAAYN,EAAO,EACrBO,EAAc,MAAML,EAAaC,EAAOC,CAAO,EACnD,OAAAG,EAAc,MAAMC,GAAsBD,EAAaD,EAAWF,CAAO,EAElEE,EAAU,OAAOC,EAAaH,CAAO,CAC9C,EAEA,KAAM,MAAOA,IACX,MAAMH,EAAW,EACVD,EAAO,EAAE,gBAAgBI,CAAO,GAGzC,IAAK,MAAOK,IACV,MAAMR,EAAW,EACVD,EAAO,EAAE,cAAcS,CAAE,GAGlC,IAAK,MAAOA,EAAYL,KACtB,MAAMH,EAAW,EACVD,EAAO,EAAE,uBAAuBS,EAAIL,EAAQ,MAAM,GAG3D,OAAQ,MAAOK,IACb,MAAMR,EAAW,EACVD,EAAO,EAAE,iBAAiBS,CAAE,EAEvC,CACF,CASO,SAASC,GAAqBX,EAAsC,CACzE,GAAM,CAAE,OAAAC,EAAQ,WAAAC,CAAW,EAAIF,EAE/B,MAAO,CAML,IAAK,MAAOY,EAAcP,EAAsD,CAAC,KAC/E,MAAMH,EAAW,EACVD,EAAO,EAAE,UAAUW,EAAMP,EAAQ,WAAYA,EAAQ,MAAM,GAGpE,KAAM,MAAOA,IACX,MAAMH,EAAW,EACVD,EAAO,EAAE,YAAYI,CAAO,GAGrC,IAAK,MAAOO,IACV,MAAMV,EAAW,EACVD,EAAO,EAAE,UAAUW,CAAI,GAGhC,OAAQ,MAAOA,IACb,MAAMV,EAAW,EACVD,EAAO,EAAE,aAAaW,CAAI,GAGnC,OAAQ,MAAOA,IACb,MAAMV,EAAW,EACVD,EAAO,EAAE,aAAaW,CAAI,GAGnC,SAAU,MAAOA,IACf,MAAMV,EAAW,EACVD,EAAO,EAAE,eAAeW,CAAI,GAGrC,IAAK,MAAOA,IACV,MAAMV,EAAW,EACVD,EAAO,EAAE,aAAaW,CAAI,GAGnC,QAAS,MAAOA,IACd,MAAMV,EAAW,EACVD,EAAO,EAAE,iBAAiBW,CAAI,GAGvC,MAAO,MAAOA,IACZ,MAAMV,EAAW,EACVD,EAAO,EAAE,eAAeW,CAAI,EAEvC,CACF,CAKO,SAASC,GAAsBb,EAAuC,CAC3E,GAAM,CAAE,OAAAC,EAAQ,WAAAC,CAAW,EAAIF,EAE/B,MAAO,CACL,IAAK,UACH,MAAME,EAAW,EACVD,EAAO,EAAE,WAAW,EAE/B,CACF,CAKO,SAASa,GAAoBd,EAAqC,CACvE,GAAM,CAAE,OAAAC,EAAQ,WAAAC,CAAW,EAAIF,EAE/B,MAAO,CACL,OAAQ,MAAOK,EAA+C,CAAC,KAC7D,MAAMH,EAAW,EACVD,EAAO,EAAE,YAAYI,EAAQ,IAAKA,EAAQ,MAAM,GAGzD,KAAM,MAAOA,IACX,MAAMH,EAAW,EACVD,EAAO,EAAE,WAAWI,CAAO,GAGpC,IAAK,MAAOU,IACV,MAAMb,EAAW,EACVD,EAAO,EAAE,SAASc,CAAK,GAGhC,OAAQ,MAAOA,IACb,MAAMb,EAAW,EACVD,EAAO,EAAE,YAAYc,CAAK,EAErC,CACF,CJpJO,IAAeC,GAAf,KAAoB,CAgCzB,YAAYC,EAA6B,CAAC,EAAG,CAR7C,KAAQ,YAAoC,KAC5C,KAAU,eAAwC,KAKlD,KAAQ,WAA4C,KA2BlD,GAlBAA,EAAU,CACR,GAAGA,EACH,OAAQA,EAAQ,QAAU,OAC1B,MAAOA,EAAQ,OAAS,OACxB,OAAQA,EAAQ,QAAU,MAC5B,EACA,KAAK,cAAgBA,EAKjBA,EAAQ,SAAW,QACrBC,GAAeD,EAAQ,MAAM,EAM3BA,EAAQ,OAASA,EAAQ,QAC3B,MAAME,EAAU,OAAO,gDAAgD,EAKrE,OAAOF,EAAQ,OAAU,UAC3BG,EAAcH,EAAQ,KAAK,EAC3B,KAAK,WAAaA,EAAQ,OACjBA,EAAQ,QACjB,KAAK,WAAaA,EAAQ,OAK5B,KAAK,KAAO,IAAII,GAAQ,CACtB,GAAGJ,EACH,eAAgB,IAAM,KAAK,eAAe,EAC1C,iBAAkB,KAAK,qBAAqB,CAC9C,CAAC,EAED,IAAMK,EAAM,CACV,OAAQ,IAAM,KAAK,KACnB,WAAY,IAAM,KAAK,kBAAkB,CAC3C,EAEA,KAAK,YAAcC,GAAyB,CAC1C,GAAGD,EACH,aAAc,CAACE,EAAOC,IAAS,KAAK,aAAaD,EAAOC,CAAI,CAC9D,CAAC,EACD,KAAK,QAAUC,GAAqBJ,CAAG,EACvC,KAAK,QAAUK,GAAsBL,CAAG,EACxC,KAAK,OAASM,GAAoBN,CAAG,CACvC,CAaA,MAAgB,mBAAmC,CACjD,OAAK,KAAK,cACR,KAAK,YAAc,KAAK,oBAAoB,GAEvC,KAAK,WACd,CAEA,MAAc,qBAAqC,CACjD,GAAI,CACF,KAAK,eAAiB,MAAM,KAAK,KAAK,UAAU,CAClD,OAASO,EAAO,CAEd,WAAK,YAAc,KACbA,CACR,CACF,CAYA,MAAM,MAA8B,CAClC,aAAM,KAAK,kBAAkB,EACtB,KAAK,KAAK,KAAK,CACxB,CAKA,MAAM,OAAOL,EAAoBP,EAAgE,CAC/F,OAAO,KAAK,YAAY,OAAOO,EAAOP,CAAO,CAC/C,CAKA,MAAM,QAAS,CACb,OAAO,KAAK,QAAQ,IAAI,CAC1B,CAOA,MAAM,WAAqC,CACzC,OAAI,KAAK,eAAuB,KAAK,gBACrC,MAAM,KAAK,kBAAkB,EAEtB,KAAK,eACd,CAEA,GAA+Ba,EAAUC,EAAiD,CACxF,KAAK,KAAK,GAAGD,EAAOC,CAAO,CAC7B,CAEA,IAAgCD,EAAUC,EAAiD,CACzF,KAAK,KAAK,IAAID,EAAOC,CAAO,CAC9B,CAMA,WAAWC,EAAuC,CAChD,KAAK,KAAK,iBAAiBA,CAAO,CACpC,CAKA,cAAqB,CACnB,KAAK,KAAK,iBAAiB,CAAC,CAAC,CAC/B,CAQO,SAASC,EAAqC,CAGnD,GAAI,KAAK,cAAc,QACrB,MAAMd,EAAU,OAAO,gDAAgD,EAEzE,GAAI,OAAOc,GAAU,SAAU,CAC7B,GAAI,CAACA,EACH,MAAMd,EAAU,SAAS,2DAA2D,EAEtFC,EAAca,CAAK,EACnB,KAAK,WAAaA,EAClB,MACF,CACA,GAAI,OAAOA,GAAU,WACnB,MAAMd,EAAU,SACd,kFACF,EAEF,KAAK,WAAac,CACpB,CAYA,MAAc,gBAAkD,CAC9D,GAAI,KAAK,aAAe,KAAM,MAAO,CAAC,EACtC,IAAMC,EAAQ,OAAO,KAAK,YAAe,WAAa,MAAM,KAAK,WAAW,EAAI,KAAK,WACrF,GAAI,CAACA,EACH,MAAMf,EAAU,eAAe,mCAAmC,EAEpE,GAAI,OAAOe,GAAU,SACnB,MAAMf,EAAU,eAAe,6CAA6C,EAE9E,MAAO,CAAE,cAAe,UAAUe,CAAK,EAAG,CAC5C,CACF,EDvQAC,KOfAC,IAGA,eAAsBC,GACpBC,EACAC,EAA6B,CAAC,EACT,CACrB,GAAM,CAAE,SAAAC,EAAU,KAAAC,CAAK,EAAI,KAAM,QAAO,eAAe,EACjD,CAAE,gBAAAC,CAAgB,EAAI,KAAM,QAAO,mBAAmB,EAEtD,CAAE,OAAAC,EAAQ,IAAAC,EAAK,SAAAC,EAAU,MAAAC,EAAO,QAAAC,CAAQ,EAAIR,EAC5CS,EAAW,IAAIR,EACfS,EAAsB,CAAC,EAE7B,QAAWC,KAAQZ,EAAO,CAExB,GACE,CAAC,OAAO,SAASY,EAAK,OAAO,GAC7B,EAAE,OAAO,KAAS,KAAeA,EAAK,mBAAmB,MAEzD,MAAMC,EAAU,KAAK,8CAA8CD,EAAK,IAAI,GAAI,CAC9E,SAAUA,EAAK,IACjB,CAAC,EAIH,GAAI,CAACA,EAAK,IACR,MAAMC,EAAU,KAAK,8BAA8BD,EAAK,IAAI,GAAI,CAAE,SAAUA,EAAK,IAAK,CAAC,EAIzF,IAAME,EAAe,IAAIX,EAAK,CAACS,EAAK,OAAO,EAAGA,EAAK,KAAM,CAAE,KAAM,0BAA2B,CAAC,EAC7FF,EAAS,OAAOK,EAAc,MAAOD,CAAY,EACjDH,EAAU,KAAKC,EAAK,GAAG,CACzB,CAEAF,EAAS,OAAOK,EAAc,UAAW,KAAK,UAAUJ,CAAS,CAAC,EAE9DN,GAAUA,EAAO,OAAS,GAAGK,EAAS,OAAOK,EAAc,OAAQ,KAAK,UAAUV,CAAM,CAAC,EACzFC,GAAKI,EAAS,OAAOK,EAAc,IAAKT,CAAG,EAC3CC,GAAUG,EAAS,OAAOK,EAAc,SAAUR,CAAQ,EAC1DC,GAAO,OAAOE,EAAS,OAAOK,EAAc,MAAO,MAAM,EACzDP,GAAO,WAAWE,EAAS,OAAOK,EAAc,UAAW,MAAM,EACjEP,GAAO,KAAKE,EAAS,OAAOK,EAAc,IAAK,MAAM,EACrDN,GAASC,EAAS,OAAOK,EAAc,QAASN,CAAO,EAE3D,IAAMO,EAAU,IAAIZ,EAAgBM,CAAQ,EACtCO,EAAS,CAAC,EAChB,cAAiBC,KAASF,EAAQ,OAAO,EACvCC,EAAO,KAAK,OAAO,KAAKC,CAAK,CAAC,EAEhC,IAAMC,EAAO,OAAO,OAAOF,CAAM,EAEjC,MAAO,CACL,KAAME,EAAK,OAAO,MAAMA,EAAK,WAAYA,EAAK,WAAaA,EAAK,UAAU,EAC1E,QAAS,CACP,eAAgBH,EAAQ,YACxB,iBAAkB,OAAO,WAAWG,CAAI,EAAE,SAAS,CACrD,CACF,CACF,CPdO,IAAMC,GAAN,cAAmBA,EAAS,CACjC,YAAYC,EAA6B,CAAC,EAAG,CAC3C,GAAIC,EAAO,IAAM,OACf,MAAMC,EAAU,SAAS,6DAA6D,EAYxF,IAAMC,EAAMC,EAAc,EAC1B,MAAM,CACJ,GAAGJ,EACH,OAAQA,EAAQ,QAAUG,EAAI,OAC9B,MAAOH,EAAQ,QAAUA,EAAQ,QAAU,OAAYG,EAAI,MAC7D,CAAC,CACH,CAYA,MAAM,OACJE,EACAL,EACmC,CACnC,OAAO,MAAM,OAAOK,EAAOL,CAAO,CACpC,CAEA,MAAgB,aACdK,EACAL,EACuB,CAEvB,IAAMM,EAAQ,OAAOD,GAAU,SAAW,CAACA,CAAK,EAAIA,EAEpD,GAAI,CAAC,MAAM,QAAQC,CAAK,GAAK,CAACA,EAAM,MAAOC,GAAM,OAAOA,GAAM,QAAQ,EACpE,MAAML,EAAU,SACd,0EACF,EAGF,GAAII,EAAM,SAAW,EACnB,MAAMJ,EAAU,SAAS,qBAAqB,EAGhD,GAAM,CAAE,oBAAAM,CAAoB,EAAI,KAAM,uCACtC,OAAOA,EAAoBF,EAAON,EAAS,KAAK,gBAAkB,MAAS,CAC7E,CAEU,sBAA0C,CAClD,OAAOS,EACT,CACF,EQlEO,SAASC,GACdC,EACAC,EACAC,EACmB,CACnB,MAAO,CACL,OAAQF,EAAM,QAAUC,EAAI,QAAUC,EAAK,OAC3C,MAAOF,EAAM,OAASC,EAAI,OAASC,EAAK,KAC1C,CACF,CAQO,SAASC,GAAaH,EAAkB,CAAC,EAAS,CACvD,OAAO,IAAII,GAAKL,GAAeC,EAAOK,EAAc,EAAGC,GAAaN,EAAM,MAAM,CAAC,CAAC,CACpF,CC7CAO,IAOO,SAASC,GAAYC,EAAyB,CACnD,OAAIC,EAAYD,CAAG,EACVA,EAELA,aAAe,MACVE,EAAU,SAASF,EAAI,OAAO,EAEhCE,EAAU,SAAS,OAAOF,GAAO,eAAe,CAAC,CAC1D,CAqBO,SAASG,GAAeH,EAAgBI,EAAgC,CAE7E,OAAIJ,EAAI,YAAY,EACdI,GAAS,MACJ,kDAEF,4EAOLJ,EAAI,eAAe,EACd,yEASLA,EAAI,OAAOK,EAAU,WAAW,EAC3B,GAAGL,EAAI,QAAQ,QAAQ,MAAO,EAAE,CAAC,8CAOtCA,EAAI,cAAc,EACbA,EAAI,QAWN,GAAGA,EAAI,QAAQ,QAAQ,MAAO,EAAE,CAAC,2DAC1C,CC5EAM,IAQA,IAAMC,GAAW,CAACC,EAAcC,IAAmB,gCAAgCD,CAAI,IAAIC,CAAM,GAWjG,SAASC,GAAUC,EAAgBC,EAAwB,CACzD,OAAQD,EAAmCC,CAAK,CAClD,CAsBA,IAAMC,GAAuD,CAC3D,CAACC,GAAiB,QAAQ,EAAG,gCAC/B,EAQMC,GAA2D,CAC/D,OAAQ,WACR,OAAQ,UACR,OAAQ,UACR,OAAQ,qBACV,EAsBA,SAASC,GAASC,EAAmBC,EAAuC,CAC1E,GAAM,CAAE,UAAAC,EAAW,SAAAC,CAAS,EAAIF,EAEhC,GADI,CAACE,GACDH,IAAW,MAAQ,OAAOA,GAAW,SAAU,OAAO,KAE1D,IAAMI,EACJF,IAAc,MACVT,GAAUO,EAAQ,UAAU,EAC1B,UACA,UACFF,GAAWI,CAAS,EAC1B,GAAI,CAACE,EAAW,OAAO,KAEvB,IAAMC,EAAMZ,GAAUO,EAAQG,CAAQ,EAGtC,GAAI,OAAOE,GAAQ,SAAU,OAAO,KAEpC,IAAMC,EAAQb,GAAUO,EAAQ,QAAQ,EAClCO,EAAc,OAAOD,GAAU,SAAWV,GAAUU,CAAK,EAAI,OACnE,MAAO,GAAGD,CAAG,IAAIF,CAAQ,IAAII,EAAc,GAAGD,CAAK,WAAMC,CAAW,GAAKH,CAAS,EACpF,CAuDO,SAASI,GACdR,EACAS,EACM,CACN,GAAM,CAAE,QAAAC,CAAQ,EAAID,EAEpB,GAAIT,EAAO,YAAY,SAAW,EAAG,CACnC,QAAQ,IAAI,sBAAsB,EAClC,QAAQ,IAAI,EACZ,MACF,CAEA,IAAMW,EAAU,CAAC,aAAc,SAAU,QAAS,OAAQ,UAAW,KAAK,EAC1E,QAAQ,IAAIC,GAAYZ,EAAO,YAAaW,EAASD,CAAO,CAAC,EAC7DG,GAAgBb,EAAO,OAAQU,CAAO,CACxC,CAOA,SAASG,GAAgBC,EAAmCJ,EAAyB,CAC/EI,GACFC,EAAK,qDAAgDD,CAAM,GAAI,GAAOJ,CAAO,CAEjF,CAKO,SAASM,GAAkBhB,EAA4BS,EAA8B,CAC1F,GAAM,CAAE,QAAAC,CAAQ,EAAID,EAEpB,GAAIT,EAAO,QAAQ,SAAW,EAAG,CAC/B,QAAQ,IAAI,kBAAkB,EAC9B,QAAQ,IAAI,EACZ,MACF,CAEA,IAAMW,EAAU,CAAC,SAAU,aAAc,SAAU,SAAU,QAAS,SAAS,EAC/E,QAAQ,IAAIC,GAAYZ,EAAO,QAASW,EAASD,CAAO,CAAC,EACzDG,GAAgBb,EAAO,OAAQU,CAAO,CACxC,CAMO,SAASO,GAAajB,EAAiCS,EAA8B,CAC1F,GAAM,CAAE,QAAAC,CAAQ,EAAID,EAGd,CAAE,YAAAS,EAAa,WAAAC,EAAY,SAAAC,EAAU,GAAGC,CAAc,EAAIrB,EAG5DkB,GAAeA,EAAY,OAAS,IACtC,QAAQ,IAAI,EACZH,EAAK,4BAA6B,GAAOL,CAAO,EAChDQ,EAAY,QAASI,GAAW,CAC9B,QAAQ,IAAI,KAAKA,EAAO,IAAI,KAAKA,EAAO,IAAI,WAAMA,EAAO,KAAK,EAAE,CAClE,CAAC,GAICH,IACF,QAAQ,IAAI,EACZJ,EAAK,uBAAuBzB,GAAS6B,EAAYnB,EAAO,MAAM,CAAC,GAAI,GAAOU,CAAO,GAGnF,QAAQ,IAAIa,EAAcF,EAAeX,CAAO,CAAC,CACnD,CAKO,SAASc,GACdxB,EACAS,EACM,CACN,GAAM,CAAE,QAAAC,CAAQ,EAAID,EAEpB,QAAQ,IAAIc,EAAcvB,EAAQU,CAAO,CAAC,EAG1C,IAAMe,EAASzB,EAAoC,MACnD,GAAIyB,EAAO,CACT,IAAMC,EAAO1B,EAAO,QAAU,KAAK,OAAOA,EAAO,QAAUA,EAAO,SAAW,KAAK,EAAI,KACtF,QAAQ,IACN,6BAA6B0B,EAAO,eAAeA,CAAI,OAAOA,IAAS,EAAI,IAAM,EAAE,GAAK,cAAc;AAAA,EAAoCD,CAAK;AAAA,CACjJ,EACAV,EACE,4EACA,GACAL,CACF,CACF,CACF,CAKO,SAASiB,GAAc3B,EAAiBS,EAA8B,CAC3E,GAAM,CAAE,QAAAC,CAAQ,EAAID,EACpB,QAAQ,IAAIc,EAAcvB,EAAQU,CAAO,CAAC,CAC5C,CAKO,SAASkB,GAAqB5B,EAAgCS,EAA8B,CACjG,GAAM,CAAE,QAAAC,CAAQ,EAAID,EAEpB,GAAIT,EAAO,MAAO,CAKhB,GAFA6B,EAAQ,GAAG7B,EAAO,YAAc,QAAQ,mBAAoB,GAAOU,CAAO,EAC1E,QAAQ,IAAI,EACRV,EAAO,YAAc,KAAM,CAC7B,IAAM8B,EAAmB9B,EAAO,UAC5BU,EACE,YACA,mBACF,gBACJ,QAAQ,IAAI,mBAAmBoB,CAAgB,EAAE,CACnD,CACA,QAAQ,IAAI,CACd,MAYE,QAAQ,IAAI,GAAGC,EAAa/B,EAAO,QAAU,mBAAmB,CAAC;AAAA,CAAI,CAEzE,CAKO,SAASgC,GAAoBhC,EAA+BS,EAA8B,CAC/F,GAAM,CAAE,QAAAC,CAAQ,EAAID,EAEpB,GAAIT,EAAO,QAAQ,SAAW,EAAG,CAC/B,QAAQ,IAAI,kBAAkB,EAC9B,QAAQ,IAAI,EACZ,MACF,CAEA,IAAMW,EAAU,CAAC,OAAQ,OAAQ,OAAO,EACxC,QAAQ,IAAIC,GAAYZ,EAAO,QAASW,EAASD,CAAO,CAAC,CAC3D,CAKO,SAASuB,GAAgBjC,EAA2BS,EAA8B,CACvF,GAAM,CAAE,QAAAC,CAAQ,EAAID,EACdyB,EAAWlC,EAAO,KAAK,UAAU,MAAQ,KAC/C,QAAQ,IAAIuB,EAAc,CAAE,OAAQvB,EAAO,OAAQ,SAAAkC,CAAS,EAAGxB,CAAO,CAAC,CACzE,CAKO,SAASyB,GAAkBnC,EAA6BS,EAA8B,CAC3F,GAAM,CAAE,QAAAC,CAAQ,EAAID,EAKpB,QAAQ,IACNc,EAAc,CAAE,OAAQvB,EAAO,OAAQ,MAAOV,GAASU,EAAO,KAAMA,EAAO,MAAM,CAAE,EAAGU,CAAO,CAC/F,CACF,CAKO,SAAS0B,GAAiBpC,EAA2BS,EAA8B,CACxF,GAAM,CAAE,QAAAC,CAAQ,EAAID,EAEpB,GAAIT,EAAO,OAAO,SAAW,EAAG,CAC9B,QAAQ,IAAI,iBAAiB,EAC7B,QAAQ,IAAI,EACZ,MACF,CAEA,IAAMW,EAAU,CAAC,QAAS,SAAU,UAAW,SAAS,EACxD,QAAQ,IAAIC,GAAYZ,EAAO,OAAQW,EAASD,CAAO,CAAC,EACxDG,GAAgBb,EAAO,OAAQU,CAAO,CACxC,CAKO,SAAS2B,GAAYrC,EAAqCS,EAA8B,CAC7F,GAAM,CAAE,QAAAC,CAAQ,EAAID,EAEpB,QAAQ,IAAIc,EAAcvB,EAAQU,CAAO,CAAC,CAC5C,CAgBA,IAAM4B,EAAUC,GAAoBA,EAM9BC,GAASvC,GACZA,EAAQ,SAAW,GAAGA,EAAQ,QAAQ,IAAIA,EAAQ,SAAS,GAAKA,EAAQ,UAqB9DwC,GAA8C,CAEzD,kBAAmBH,EAA4B,CAC7C,MAAQI,GAAMA,EAAE,YAAY,IAAKC,GAAMA,EAAE,UAAU,EACnD,KAAMnC,EACR,CAAC,EACD,oBAAqB8B,EAAgB,CACnC,MAAQI,GAAM,CAACA,EAAE,UAAU,EAC3B,KAAMlB,EACR,CAAC,EACD,iBAAkBc,EAAgB,CAChC,MAAQI,GAAM,CAACA,EAAE,UAAU,EAC3B,KAAMlB,EACR,CAAC,EACD,iBAAkBc,EAAgB,CAChC,MAAQI,GAAM,CAACA,EAAE,UAAU,EAC3B,KAAMlB,EACR,CAAC,EACD,oBAAqBc,EAA8B,CACjD,MAAQI,GAAM,CAACA,EAAE,UAAU,EAG3B,KAAM,IAAM,CAAC,CACf,CAAC,EAGD,cAAeJ,EAAwB,CACrC,MAAQI,GAAMA,EAAE,QAAQ,IAAKC,GAAMA,EAAE,MAAM,EAC3C,KAAM3B,EACR,CAAC,EACD,aAAcsB,EAAY,CACxB,MAAQI,GAAM,CAACA,EAAE,MAAM,EACvB,KAAMzB,EACR,CAAC,EACD,aAAcqB,EAA6B,CACzC,MAAQI,GAAM,CAACA,EAAE,MAAM,EACvB,KAAMzB,EACR,CAAC,EACD,kBAAmBqB,EAA4B,CAG7C,MAAQI,GAAOA,EAAE,OAASA,EAAE,WAAa,CAACA,EAAE,UAAU,EAAI,CAAC,EAC3D,KAAMd,EACR,CAAC,EACD,gBAAiBU,EAA0B,CACzC,MAAQI,GAAM,CAACA,EAAE,MAAM,EACvB,KAAM,IAAM,CAAC,CACf,CAAC,EACD,iBAAkBJ,EAA2B,CAC3C,MAAQI,GAAMA,EAAE,QAAQ,IAAKE,GAAQ,GAAGA,EAAI,IAAI,IAAIA,EAAI,IAAI,IAAIA,EAAI,KAAK,EAAE,EAC3E,KAAMZ,EACR,CAAC,EACD,aAAcM,EAAuB,CAEnC,MAAQI,GAAOA,EAAE,KAAK,UAAU,KAAO,CAACA,EAAE,IAAI,SAAS,IAAI,EAAI,CAAC,EAChE,KAAMT,EACR,CAAC,EACD,eAAgBK,EAAyB,CACvC,MAAQI,GAAM,CAACpD,GAASoD,EAAE,KAAMA,EAAE,MAAM,CAAC,EACzC,KAAMP,EACR,CAAC,EACD,gBAAiBG,EAA0B,CACzC,MAAQI,GAAM,CAACA,EAAE,MAAM,EACvB,KAAM,IAAM,CAAC,CACf,CAAC,EAGD,aAAcJ,EAAuB,CACnC,MAAQI,GAAMA,EAAE,OAAO,IAAK,GAAM,EAAE,KAAK,EACzC,KAAMN,EACR,CAAC,EACD,eAAgBE,EAAyB,CAKvC,MAAQI,GAAM,CAACA,EAAE,MAAM,EACvB,KAAML,EACR,CAAC,EACD,YAAaC,EAAW,CACtB,MAAQI,GAAM,CAACA,EAAE,KAAK,EACtB,KAAML,EACR,CAAC,EACD,eAAgBC,EAAyB,CACvC,MAAQI,GAAM,CAACA,EAAE,KAAK,EACtB,KAAM,IAAM,CAAC,CACf,CAAC,EAGD,cAAeJ,EAAa,CAC1B,MAAQI,GAAM,CAACA,EAAE,KAAK,EACtB,KAAMf,EACR,CAAC,EAGD,KAAMW,EAAkB,CAEtB,MAAO,IAAM,CAAC,EAId,KAAM,CAACO,EAAIC,IAAMjB,EAAQ,gBAAiB,GAAOiB,EAAE,OAAO,CAC5D,CAAC,CACH,EAQO,SAASC,GACd/C,EACAC,EACAQ,EACM,CACN,GAAM,CAAE,KAAAuC,EAAM,MAAAC,EAAO,QAAAvC,CAAQ,EAAID,EAC3ByC,EAAST,GAAQD,GAAMvC,CAAO,CAAC,EAGrC,GAAIgD,EAAO,CACT,QAAWE,KAAQD,GAAQ,MAAMlD,CAAM,GAAK,CAAC,EAAG,QAAQ,IAAImD,CAAI,EAChE,MACF,CAIA,GAAIH,GAAQhD,IAAW,MAAQ,OAAOA,GAAW,SAAU,CACzD,IAAMoD,EAAU,CAAE,GAAGpD,CAAO,EAC5B,OAAOoD,EAAQ,YACf,OAAOA,EAAQ,WACf,OAAOA,EAAQ,SACf,QAAQ,IAAI,KAAK,UAAUA,EAAS,KAAM,CAAC,CAAC,EAC5C,QAAQ,IAAI,EACZ,MACF,CAKA,IAAMC,EAAWtD,GAASC,EAAQC,CAAO,EACrCoD,GAAUxB,EAAQwB,EAAU,GAAO3C,CAAO,EAE1CwC,EACFA,EAAO,KAAKlD,EAAQS,CAAO,EAClBT,IAAW,MAAQ,OAAOA,GAAW,UAQ9C,QAAQ,IAAIuB,EAAcvB,EAAQU,CAAO,CAAC,CAI9C,ClB9hBA,SAAS4C,IAAuC,CAC9C,IAAMC,EAAQ,CACP,WAAQ,UAAW,iBAAiB,EACpC,WAAQ,UAAW,oBAAoB,CAC9C,EACA,QAAWC,KAAKD,EACd,GAAI,CACF,OAAO,KAAK,SAAM,gBAAaC,EAAG,OAAO,CAAC,CAC5C,MAAQ,CAAC,CAEX,MAAO,CAAE,QAAS,OAAQ,CAC5B,CAEA,IAAMC,GAAcH,GAAgB,EAepC,SAASI,GAASC,EAA2B,CAC3C,IAAMC,EAAaC,GAAkBF,EAAUE,KAAO,SAAKA,CAAI,EACzDC,EAAYD,GAAkBF,EAAUE,KAAO,QAAIA,CAAI,EACvDE,EAAQC,GAAmBL,EAAU,GAAK,GAAGK,CAAK,IA2DxD,MAzDe,GAAGJ,EAAU,OAAO,CAAC;AAAA,8BACRG,EAAK,WAAI,CAAC;AAAA;AAAA,EAEtCH,EAAU,UAAU,CAAC;AAAA,IACnBG,EAAK,WAAI,CAAC,GAAGH,EAAU,aAAa,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOrCG,EAAK,WAAI,CAAC,GAAGH,EAAU,SAAS,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAWjCG,EAAK,WAAI,CAAC,GAAGH,EAAU,QAAQ,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAMhCG,EAAK,cAAI,CAAC,GAAGH,EAAU,OAAO,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA,IAK/BG,EAAK,iBAAK,CAAC,GAAGH,EAAU,YAAY,CAAC;AAAA;AAAA;AAAA;AAAA,EAIvCA,EAAU,OAAO,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYlBA,EAAU,UAAU,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA,EAKrBE,EAAS,uEAAuE,CAAC;AAAA,CAInF,CAYA,IAAMG,GAAcC,GAAoB,IAAIC,EAAUC,EAAU,WAAYF,CAAO,EAMnF,SAASG,GAAQC,EAAeC,EAAqB,CAAC,EAAa,CACjE,OAAOA,EAAS,OAAO,CAACD,CAAK,CAAC,CAChC,CAQA,SAASE,GAAaF,EAAuB,CAC3C,IAAMG,EAAS,OAAO,SAASH,EAAO,EAAE,EACxC,GAAI,OAAO,MAAMG,CAAM,EACrB,MAAM,IAAI,uBAAqB,eAAe,EAEhD,OAAOA,CACT,CAMA,SAASC,GACPC,EACAC,EACsB,CACtB,IAAMC,EAASF,GAAY,OAAO,OAASA,EAAW,MAAQC,GAAa,MAC3E,GAAI,CAACC,GAAQ,OAAQ,OAErB,IAAMC,EAAWD,EAAO,OAAQE,GAAMA,IAAM,EAAE,EAC9C,OAAOD,EAAS,OAASA,EAAW,CAAC,CACvC,CAYA,SAASE,GACPL,EACAC,EACoB,CACpB,OAAOD,GAAY,UAAYC,GAAa,WAAa,QAAQ,IAAI,eAAiB,OACxF,CAMA,SAASK,EAAeC,EAAiC,CACvD,IAAMC,EAAUD,EAAQ,gBAAgB,EAGpCC,EAAQ,QAAU,KACpBA,EAAQ,QAAU,IAOpB,IAAMC,EAAa,CAAC,CAAC,QAAQ,IAAI,aAAe,QAAQ,IAAI,cAAgB,IAC5E,MAAI,CAACD,EAAQ,SAAW,CAACC,IACnB,CAAC,QAAQ,OAAO,OAAS,QAAQ,IAAI,WAAa,UACpDD,EAAQ,QAAU,IAIfA,CACT,CAcA,SAASE,IAA6C,CACpD,IAAMF,EAAUF,EAAe,IAAI,EAC7BK,EAAcC,EAAc,IAAI,EAAE,IAAKC,GAAMA,EAAE,KAAK,CAAC,EAErDC,EAAU,KAAK,KAAK,KAAMC,GAAQ,CAACJ,EAAY,SAASI,CAAG,CAAC,EAC9DD,GACFE,EAAM1B,GAAW,oBAAoBwB,CAAO,GAAG,EAAGN,EAAQ,KAAMA,EAAQ,OAAO,EAG5EA,EAAQ,MACX,QAAQ,IAAI,eAAe,KAAK,KAAK,CAAC,KAAKG,EAAY,KAAK,GAAG,CAAC;AAAA,CAAK,EAEvE,QAAQ,SAAW,CACrB,CAUA,SAASM,GAAgBC,EAIF,CACrB,IAAIC,EAAO,CAAC,EACZ,GAAI,CACFA,EAAOC,GAAaF,EAAM,MAAM,CAClC,MAAQ,CAAC,CAGT,IAAMG,EAAQC,GAAeJ,EAAOK,EAAc,EAAGJ,CAAI,EAAE,MAC3D,OAAO,OAAOE,GAAU,SAAWA,EAAQ,MAC7C,CAWA,eAAeG,GACbC,EACAC,EACAxB,EACAyB,EACA3B,EACA4B,EACqB,CACrB,GAAI,IAAC,cAAWF,CAAU,EACxB,MAAMlC,EAAU,KAAK,GAAGkC,CAAU,uBAAwB,CAAE,SAAUA,CAAW,CAAC,EAGpF,IAAMG,KAAQ,YAASH,CAAU,EACjC,GAAI,CAACG,EAAM,YAAY,GAAK,CAACA,EAAM,OAAO,EACxC,MAAMrC,EAAU,KAAK,GAAGkC,CAAU,oCAAqC,CACrE,SAAUA,CACZ,CAAC,EAGH,IAAMI,EAQF,CAMF,IAAKC,GAAa,QAAQ,IAAI,QAAQ,GAAKC,GAAc,GAC3D,EAGI9B,IAAW,SAAW4B,EAAc,OAAS5B,GASjD,IAAM+B,EAAiB,QAAQ,IAAI,sBAAwB,OACvDA,IAAmB,SAAWH,EAAc,eAAiBG,GAI7DN,IAAa,SAAWG,EAAc,SAAWH,GAGjD3B,GAAY,eAAiB,SAC/B8B,EAAc,WAAa,CAAC9B,EAAW,cAErCA,GAAY,cAAgB,SAC9B8B,EAAc,UAAY,CAAC9B,EAAW,aAIxC,IAAMkC,EAAkB,IAAI,gBAC5BJ,EAAc,OAASI,EAAgB,OAGvC,IAAIC,EAA0B,KAC9B,GACE,QAAQ,OAAO,OACf,CAACP,EAAc,MACf,CAACA,EAAc,OACf,CAACA,EAAc,QACf,CACA,GAAM,CAAE,QAASQ,CAAa,EAAI,KAAM,QAAO,eAAe,EAC9DD,EAAUC,EAAa,CAAE,KAAM,iBAAa,CAAC,EAAE,MAAM,CACvD,CAEA,IAAMC,EAAgB,IAAM,CAC1BH,EAAgB,MAAM,EAClBC,GAASA,EAAQ,KAAK,EAC1B,QAAQ,KAAK,GAAG,CAClB,EACA,QAAQ,GAAG,SAAUE,CAAa,EAElC,GAAI,CACF,OAAO,MAAMZ,EAAO,YAAY,OAAOC,EAAYI,CAAa,CAClE,QAAE,CACA,QAAQ,eAAe,SAAUO,CAAa,EAC1CF,GAASA,EAAQ,KAAK,CAC5B,CACF,CAUO,SAASG,IAAwB,CACtC,IAAMC,EAAU,IAAI,UASpBA,EACG,aAAcC,GAAQ,CAErB,GAAIA,EAAI,OAAS,kBAAoBA,EAAI,OAAS,qBAAuBA,EAAI,WAAa,EACxF,MAAMA,EAGR,IAAMZ,EAAgBtB,EAAeiC,CAAO,EAExChD,EAAUiD,EAAI,SAAW,wBAC7B,MAAAjD,EAAUA,EACP,QAAQ,WAAY,EAAE,EACtB,QAAQ,OAAQ,EAAE,EAClB,QAAQ,MAAO,EAAE,EACjB,YAAY,EAIfyB,EAAM1B,GAAWC,CAAO,EAAGqC,EAAc,KAAMA,EAAc,OAAO,EAE/DA,EAAc,MACjBW,EAAQ,WAAW,EAGfC,CACR,CAAC,EACA,gBAAgB,CACf,SAAWC,GAAQ,CACZA,EAAI,WAAW,QAAQ,GAC1B,QAAQ,OAAO,MAAMA,CAAG,CAE5B,EACA,SAAWA,GAAQ,QAAQ,OAAO,MAAMA,CAAG,CAC7C,CAAC,EAMH,SAASC,EAAYF,EAAc,CACjC,IAAMG,EAAOrC,EAAeiC,CAAO,EAC7BK,EAAYC,GAAYL,CAAG,EAKjC,GAAIG,EAAK,KACP3B,EAAM4B,EAAW,GAAMD,EAAK,OAAO,MAC9B,CAIL,IAAMpD,EAAUuD,GAAeF,EAAW,CACxC,MAAOA,EAAU,YAAY,EAAI3B,GAAgBsB,EAAQ,KAAK,CAAC,EAAI,MACrE,CAAC,EACDvB,EAAMzB,EAAS,GAAOoD,EAAK,OAAO,EAE9BC,EAAU,OAASnD,EAAU,YAAcF,EAAQ,SAAS,iBAAiB,GAC/EgD,EAAQ,WAAW,CAEvB,CAEA,QAAQ,SAAW,CACrB,CAWA,SAASQ,EACPC,EACAC,EACA,CACA,OAAO,kBAAkCC,EAAS,CAChD,IAAMtB,EAAgBtB,EAAe,IAAI,EAEzC,GAAI,CACF,GAAM,CAAE,OAAA6C,EAAQ,OAAAC,EAAQ,MAAA/B,CAAM,EAAIkB,EAAQ,KAAK,EACzCd,EAAS4B,GAAa,CAAE,OAAAF,EAAQ,OAAAC,EAAQ,MAAA/B,CAAM,CAAC,EAC/CiC,EAAS,MAAMN,EAAQvB,EAAQG,EAAe,GAAGsB,CAAI,EAC3DK,GAAaD,EAAQL,EAAS,CAC5B,KAAMrB,EAAc,KACpB,MAAOA,EAAc,MACrB,QAASA,EAAc,OACzB,CAAC,CACH,OAASY,EAAK,CACZE,EAAYF,CAAG,CACjB,CACF,CACF,CAEAD,EACG,KAAK,MAAM,EACX,YAAY,+CAAwC,EACpD,QAAQzD,GAAY,QAAS,gBAAiB,0BAA0B,EACxE,OAAO,kBAAmB,uEAA6D,EACvF,OAAO,kBAAmB,yBAAyB,EACnD,OAAO,kBAAmB,2BAA2B,EACrD,OAAO,SAAU,+BAA+B,EAChD,OAAO,cAAe,qCAAqC,EAC3D,OAAO,aAAc,wBAAwB,EAM7C,WAAW,aAAc,0BAA0B,EACnD,YAAY,iBAAkB,0BAA0B,EACxD,cAAc,CACb,WAAY,CAAC0E,EAAKC,IAChBD,EAAI,SAAW,KACX,GAAGzE,GAASuB,EAAekD,CAAG,EAAE,OAAO,CAAC;AAAA,EACxC,OAAK,UAAU,WAAW,KAAKC,EAAQD,EAAKC,CAAM,CAC1D,CAAC,EAKA,qBAAqB,EAGxBlB,EAAQ,KAAK,YAAcmB,GAAgB,CACzC,IAAMlD,EAAUF,EAAeoD,CAAW,EAE1C,GAAI,CACElD,EAAQ,OAAS,OAAOA,EAAQ,OAAU,UAC5CmD,EAAcnD,EAAQ,KAAK,EAGzBA,EAAQ,QAAU,OAAOA,EAAQ,QAAW,UAC9CoD,GAAepD,EAAQ,MAAM,CAEjC,OAASqD,EAAiB,CACxB,MAAIC,EAAYD,CAAe,GAC7B7C,EAAM6C,EAAiBrD,EAAQ,KAAMA,EAAQ,OAAO,EAE9C,IAAI,iBAAe,EAAG,qBAAsBqD,EAAgB,OAAO,GAErEA,CACR,CACF,CAAC,EAGDtB,EACG,QAAQ,MAAM,EACd,YAAY,wBAAwB,EACpC,OACCQ,EAKE,CAACtB,EAAcsC,IAA4BtC,EAAO,KAAK,EACvD,CAAE,UAAW,MAAO,CACtB,CACF,EAGFc,EACG,QAAQ,QAAQ,EAChB,YAAY,iCAAiC,EAC7C,OACCQ,EAAkB,CAACtB,EAAcsC,IAA4BtC,EAAO,OAAO,EAAG,CAC5E,UAAW,MACX,SAAU,SACZ,CAAC,CACH,EAGF,IAAMuC,EAAiBzB,EACpB,QAAQ,aAAa,EACrB,YAAY,oBAAoB,EAChC,wBAAwB,EACxB,OAAO7B,EAAuB,EAEjCsD,EACG,QAAQ,MAAM,EACd,YAAY,6DAAwD,EACpE,OAAO,kBAAmB,qCAAsCnE,EAAY,EAC5E,OAAO,oBAAqB,wCAAwC,EACpE,OACCkD,EACE,CAACtB,EAAcsC,EAAyB/D,IACtCyB,EAAO,YAAY,KAAKzB,CAAU,EACpC,CAAE,UAAW,OAAQ,SAAU,YAAa,CAC9C,CACF,EAEFgE,EACG,QAAQ,eAAe,EACvB,YAAY,0CAA0C,EACtD,mBAAmB,EACnB,OAAO,kBAAmB,iCAAkCtE,GAAS,CAAC,CAAC,EACvE,OAAO,wBAAyB,kCAAkC,EAClE,OAAO,mBAAoB,oDAAoD,EAC/E,OAAO,kBAAmB,mDAAmD,EAC7E,OACCqD,EACE,CACEtB,EACAjB,EACAkB,EACA1B,IAEAwB,GACEC,EACAC,EACA3B,GAAiBC,EAAYuC,EAAQ,KAAK,CAAiB,EAC3DlC,GAAoBL,EAAYuC,EAAQ,KAAK,CAA0B,EACvEvC,EACAQ,CACF,EACF,CAAE,UAAW,SAAU,SAAU,YAAa,CAChD,CACF,EAEFwD,EACG,QAAQ,kBAAkB,EAC1B,YAAY,6BAA6B,EACzC,OACCjB,EACE,CAACtB,EAAcsC,EAAyBE,IACtCxC,EAAO,YAAY,IAAIwC,CAAU,EACnC,CAAE,UAAW,MAAO,SAAU,YAAa,CAC7C,CACF,EAEFD,EACG,QAAQ,kBAAkB,EAC1B,YAAY,uBAAuB,EACnC,mBAAmB,EACnB,OAAO,kBAAmB,iCAAkCtE,GAAS,CAAC,CAAC,EACvE,OACCqD,EACE,MACEtB,EACAsC,EACAE,EACAjE,IACG,CACH,IAAME,EAASH,GAAiBC,EAAYuC,EAAQ,KAAK,CAAiB,GAAK,CAAC,EAChF,OAAOd,EAAO,YAAY,IAAIwC,EAAY,CAAE,OAAA/D,CAAO,CAAC,CACtD,EACA,CACE,UAAW,MACX,SAAU,YACZ,CACF,CACF,EAEF8D,EACG,QAAQ,qBAAqB,EAC7B,YAAY,+BAA+B,EAC3C,OACCjB,EACE,CAACtB,EAAcsC,EAAyBE,IACtCxC,EAAO,YAAY,OAAOwC,CAAU,EACtC,CACE,UAAW,SACX,SAAU,YACZ,CACF,CACF,EAGF,IAAMC,EAAa3B,EAChB,QAAQ,SAAS,EACjB,YAAY,gBAAgB,EAC5B,wBAAwB,EACxB,OAAO7B,EAAuB,EAEjCwD,EACG,QAAQ,MAAM,EACd,YAAY,yDAAoD,EAChE,OAAO,kBAAmB,qCAAsCrE,EAAY,EAC5E,OAAO,oBAAqB,wCAAwC,EACpE,OACCkD,EACE,CAACtB,EAAcsC,EAAyB/D,IACtCyB,EAAO,QAAQ,KAAKzB,CAAU,EAChC,CAAE,UAAW,OAAQ,SAAU,QAAS,CAC1C,CACF,EAEFkE,EACG,QAAQ,YAAY,EACpB,YAAY,yBAAyB,EACrC,OACCnB,EACE,CAACtB,EAAcsC,EAAyBI,IAAiB1C,EAAO,QAAQ,IAAI0C,CAAI,EAChF,CAAE,UAAW,MAAO,SAAU,QAAS,CACzC,CACF,EAEFD,EACG,QAAQ,iBAAiB,EACzB,YAAY,6CAA6C,EACzD,OACCnB,EACE,MAAOtB,EAAcsC,EAAyBI,IAAiB,CAC7D,IAAMb,EAAS,MAAM7B,EAAO,QAAQ,SAAS0C,CAAI,EACjD,OAAKb,EAAO,QAAO,QAAQ,SAAW,GAC/BA,CACT,EACA,CAAE,UAAW,WAAY,SAAU,QAAS,CAC9C,CACF,EAEFY,EACG,QAAQ,eAAe,EACvB,YAAY,8CAA8C,EAC1D,OACCnB,EACE,CAACtB,EAAcsC,EAAyBI,IAAiB1C,EAAO,QAAQ,OAAO0C,CAAI,EACnF,CAAE,UAAW,SAAU,SAAU,QAAS,CAC5C,CACF,EAEFD,EACG,QAAQ,gBAAgB,EACxB,YAAY,4CAA4C,EACxD,OACCnB,EACE,CAACtB,EAAcsC,EAAyBI,IAAiB1C,EAAO,QAAQ,QAAQ0C,CAAI,EACpF,CAAE,UAAW,UAAW,SAAU,QAAS,CAC7C,CACF,EAEFD,EACG,QAAQ,YAAY,EACpB,YAAY,mCAAmC,EAC/C,OACCnB,EACE,CAACtB,EAAcsC,EAAyBI,IAAiB1C,EAAO,QAAQ,IAAI0C,CAAI,EAChF,CAAE,UAAW,MAAO,SAAU,QAAS,CACzC,CACF,EAEFD,EACG,QAAQ,cAAc,EACtB,YAAY,8BAA8B,EAC1C,OACCnB,EACE,CAACtB,EAAcsC,EAAyBI,IAAiB1C,EAAO,QAAQ,MAAM0C,CAAI,EAClF,CAAE,UAAW,QAAS,SAAU,QAAS,CAC3C,CACF,EAEFD,EACG,QAAQ,yBAAyB,EACjC,YAAY,qDAAqD,EACjE,mBAAmB,EACnB,OAAO,kBAAmB,iCAAkCxE,GAAS,CAAC,CAAC,EACvE,OACCqD,EACE,MACEtB,EACAsC,EACAI,EACAF,EACAjE,IACG,CAEC,CAACiE,GAAc,CAAC,QAAQ,MAAM,QAChCA,EAAa,MAAM,IAAI,QAA6BG,GAAY,CAC9D,IAAIC,EAAO,GACX,QAAQ,MAAM,GAAG,OAASC,GAAWD,GAAQC,CAAM,EACnD,QAAQ,MAAM,GAAG,MAAO,IAAMF,EAAQC,EAAK,KAAK,GAAK,MAAS,CAAC,CACjE,CAAC,GAGH,IAAMnE,EAASH,GAAiBC,EAAYuC,EAAQ,KAAK,CAAiB,EAEpEgC,EAAyD,CAAC,EAC5DN,IAAYM,EAAW,WAAaN,GACpC/D,IAAW,SAAWqE,EAAW,OAASrE,GAI9C,IAAMoD,EAAS,MAAM7B,EAAO,QAAQ,IAAI0C,EAAMI,CAAU,EAGxD,GAAIjB,EAAO,UAAYa,EAAK,SAAS,GAAG,EACtC,GAAI,CACF,GAAM,CAACK,EAASC,CAAK,EAAI,MAAM,QAAQ,IAAI,CACzChD,EAAO,QAAQ,QAAQ0C,CAAI,EAC3B1C,EAAO,QAAQ,MAAM0C,CAAI,CAC3B,CAAC,EACD,MAAO,CACL,GAAGb,EACH,YAAakB,EAAQ,QACrB,WAAYC,EAAM,IACpB,CACF,MAAQ,CAER,CAEF,OAAOnB,CACT,EACA,CAAE,UAAW,MAAO,SAAU,QAAS,CACzC,CACF,EAEFY,EACG,QAAQ,eAAe,EACvB,YAAY,2BAA2B,EACvC,OACCnB,EACE,CAACtB,EAAcsC,EAAyBI,IAAiB1C,EAAO,QAAQ,OAAO0C,CAAI,EACnF,CAAE,UAAW,SAAU,SAAU,QAAS,CAC5C,CACF,EAGF,IAAMO,EAAYnC,EACf,QAAQ,QAAQ,EAChB,YAAY,sBAAsB,EAClC,wBAAwB,EACxB,OAAO7B,EAAuB,EAEjCgE,EACG,QAAQ,MAAM,EACd,YAAY,+DAA0D,EACtE,OAAO,kBAAmB,qCAAsC7E,EAAY,EAC5E,OAAO,oBAAqB,wCAAwC,EACpE,OACCkD,EACE,CAACtB,EAAcsC,EAAyB/D,IACtCyB,EAAO,OAAO,KAAKzB,CAAU,EAC/B,CAAE,UAAW,OAAQ,SAAU,OAAQ,CACzC,CACF,EAEF0E,EACG,QAAQ,QAAQ,EAChB,YAAY,2BAA2B,EACvC,OAAO,kBAAmB,mDAAoD7E,EAAY,EAC1F,OAAO,kBAAmB,iCAAkCH,GAAS,CAAC,CAAC,EACvE,OACCqD,EACE,CAACtB,EAAcsC,EAAyB/D,IAA0C,CAChF,IAAMQ,EAA+C,CAAC,EAClDR,GAAY,MAAQ,SAAWQ,EAAQ,IAAMR,EAAW,KAC5D,IAAME,EAASH,GAAiBC,EAAYuC,EAAQ,KAAK,CAAiB,EAC1E,OAAIrC,IAAW,SAAWM,EAAQ,OAASN,GACpCuB,EAAO,OAAO,OAAOjB,CAAO,CACrC,EACA,CAAE,UAAW,SAAU,SAAU,OAAQ,CAC3C,CACF,EAEFkE,EACG,QAAQ,aAAa,EACrB,YAAY,uBAAuB,EACnC,OACC3B,EACE,CAACtB,EAAcsC,EAAyB1C,IAAkBI,EAAO,OAAO,IAAIJ,CAAK,EACjF,CAAE,UAAW,MAAO,SAAU,OAAQ,CACxC,CACF,EAEFqD,EACG,QAAQ,gBAAgB,EACxB,YAAY,0BAA0B,EACtC,OACC3B,EACE,CAACtB,EAAcsC,EAAyB1C,IAAkBI,EAAO,OAAO,OAAOJ,CAAK,EACpF,CAAE,UAAW,SAAU,SAAU,OAAQ,CAC3C,CACF,EAGiBkB,EAChB,QAAQ,SAAS,EACjB,YAAY,gBAAgB,EAC5B,OAAO7B,EAAuB,EAG9B,QAAQ,KAAK,EACb,YAAY,0BAA0B,EACtC,OACCqC,EAAkB,CAACtB,EAAcsC,IAA4BtC,EAAO,OAAO,EAAG,CAC5E,UAAW,MACX,SAAU,SACZ,CAAC,CACH,EAGF,IAAMkD,EAAgBpC,EACnB,QAAQ,YAAY,EACpB,YAAY,wBAAwB,EACpC,OAAO7B,EAAuB,EAEjCiE,EACG,QAAQ,SAAS,EACjB,YAAY,iCAAiC,EAC7C,OAAO,IAAM,CACZ,IAAMnE,EAAUF,EAAeiC,CAAO,EACtC,GAAI,CAEFqC,GAAkBrC,EAAS,CAAE,KAAM/B,EAAQ,KAAM,QAASA,EAAQ,OAAQ,CAAC,CAC7E,OAASgC,EAAK,CACZE,EAAYF,CAAG,CACjB,CACF,CAAC,EAEHmC,EACG,QAAQ,WAAW,EACnB,YAAY,mCAAmC,EAC/C,OAAO,IAAM,CACZ,IAAMnE,EAAUF,EAAeiC,CAAO,EACtC,GAAI,CACFsC,GAAoB,CAAE,KAAMrE,EAAQ,KAAM,QAASA,EAAQ,OAAQ,CAAC,CACtE,OAASgC,EAAK,CACZE,EAAYF,CAAG,CACjB,CACF,CAAC,EAGHD,EACG,QAAQ,QAAQ,EAChB,YAAY,iBAAiB,EAC7B,OAAO,SAAY,CAClB,IAAM/B,EAAUF,EAAeiC,CAAO,EACtC,GAAI,CAKF,GAAI/B,EAAQ,KACV,MAAMlB,GACJ,4FACF,EAEF,MAAMwF,GAAU,CACd,QAAStE,EAAQ,QAGjB,WAAYA,EAAQ,MACtB,CAAC,CACH,OAASgC,EAAK,CACZE,EAAYF,CAAG,CACjB,CACF,CAAC,EAOH,IAAMuC,EAAiBhC,EACrB,CAACtB,EAAcjB,EAAwBkB,EAAoB1B,IACzDwB,GACEC,EACAC,EACA3B,GAAiBC,EAAYuC,EAAQ,KAAK,CAAiB,EAC3DlC,GAAoBL,EAAYuC,EAAQ,KAAK,CAA0B,EACvEvC,EACAQ,CACF,EACF,CAAE,UAAW,SAAU,SAAU,YAAa,CAChD,EAEA,OAAA+B,EACG,SAAS,SAAU,gBAAgB,EACnC,OAAO,kBAAmB,iCAAkC7C,GAAS,CAAC,CAAC,EACvE,OAAO,wBAAyB,kCAAkC,EAClE,OAAO,mBAAoB,oDAAoD,EAC/E,OAAO,kBAAmB,mDAAmD,EAC7E,OAAO,eAA+BgC,EAAqB1B,EAAmC,CAC7F,GAAI,CAAC0B,EAAY,CACf,KAAK,WAAW,EAChB,MACF,CAMA,GAAI,IAAC,cAAWA,CAAU,GAEtB,CAACA,EAAW,SAAS,GAAG,GACxB,CAACA,EAAW,SAAS,IAAI,GACzB,CAACA,EAAW,SAAS,GAAG,GACxB,CAACA,EAAW,WAAW,GAAG,EACN,CACpBgB,EAAYpD,GAAW,oBAAoBoC,CAAU,GAAG,CAAC,EACzD,MACF,CAGF,OAAOqD,EAAe,KAAK,KAAMrD,EAAY1B,CAAU,CACzD,CAAC,EAEIuC,CACT,CDn9BAyC,GAAa,EACV,WAAW,QAAQ,IAAI,EACvB,MAAOC,GAAiB,CAGvB,GAAIA,aAAe,kBAAgB,CACjC,QAAQ,SAAWA,EAAI,SACvB,MACF,CACA,MAAMA,CACR,CAAC","names":["normalizeVia","value","via","DeploymentVia","validateIdempotencyKey","ShipError","key","IDEMPOTENCY_KEY_CONSTRAINTS","isTransportFailure","cause","isShipError","error","isBlockedExtension","filename","dotIndex","ext","BLOCKED_EXTENSIONS","hasUnsafeChars","UNSAFE_FILENAME_CHARS","hasUnbuiltMarker","filePath","s","UNBUILT_PROJECT_MARKERS","classifyToken","token","API_KEY","TokenKind","DEPLOY_TOKEN","assertShipJsonSyntax","text","withoutBom","parsed","DEPLOYMENT_CONFIG_FILENAME","validatePrefixedCredential","shape","label","hexPart","validateApiKey","apiKey","validateDeployToken","deployToken","validateToken","validateCaller","caller","CALLER","validateApiUrl","apiUrl","url","validatePassword","trimmed","PASSWORD_CONSTRAINTS","DeploymentStatus","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","AuthMethod","SPA_DEFAULT_CONFIG","SPA_CHECK_CONSTRAINTS","DEFAULT_API","SHIP_ENV","MY_API_KEY_URL","PUBLIC_DEPLOYMENT_TTL_SECONDS","LABEL_CONSTRAINTS","LABEL_PATTERN","init_dist","__esmMin","deployment","domain","t","_ShipError","type","message","status","details","authDetails","response","operationName","bodyType","json","obj","retryAfterHeader","seconds","existing","op","resource","id","errorType","detectEnvironment","getENV","_testEnvironment","init_env","__esmMin","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","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","validateFileName","filename","hasUnsafeChars","reservedNames","nameWithoutPath","init_file_validation","__esmMin","init_dist","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","nameCheck","validateFileName","isBlockedExtension","init_security","__esmMin","init_dist","init_file_validation","node_files_exports","__export","processFilesForNode","findAllFilePaths","dirPath","visited","results","realPath","entries","entry","fullPath","stats","subFiles","paths","options","platformLimits","getENV","ShipError","p","absPath","marker","e","UNBUILT_PROJECT_MARKERS","isShipError","absolutePaths","uniquePaths","inputAbsolutePaths","inputBasePath","findCommonParent","contentPaths","rel","deployPaths","optimizeDeployPaths","filteredSet","filterJunk","validAbsPaths","validDeployPaths","i","totalSize","filePath","deployPath","validateDeployPath","validateDeployFile","content","md5","calculateMD5","error","errorMessage","fs","path","init_node_files","__esmMin","init_dist","init_deploy_paths","init_env","init_junk","init_md5","init_path","init_security","import_commander","import_node_fs","path","init_dist","import_commander","import_yoctocolors","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","fs","os","path","init_dist","subcommandsOf","cmd","c","groupsOf","program","takesPath","arg","pathOptions","o","localFlags","flag","localFlagPairs","group","sub","globalFlags","fileTriggers","g","HEADER","comment","renderBash","cases","triggers","name","path","f","zshDescribe","description","renderZsh","fishQuote","text","renderFish","lines","option","arity","t","renderCompletion","shell","import_columnify","import_yoctocolors","INTERNAL_FIELDS","applyColor","colorFn","text","noColor","isPlainObject","value","decapitalize","msg","plainMessage","success","json","warn","warnPrefix","warnMsg","info","infoPrefix","infoMsg","error","err","errorPrefix","message","errorMsg","formatTimestamp","timestamp","context","isoString","formatValue","key","mb","pairs","k","v","formatTable","data","columns","headerMap","firstItem","columnOrder","transformedData","item","record","transformed","col","columnify","config","heading","line","formatDetails","obj","entries","detectShell","shell","getShellPaths","homeDir","installCompletion","program","options","json","noColor","ShipError","paths","script","renderCompletion","fishDir","success","info","sourceLine","content","prefix","warn","e","isShipError","message","uninstallCompletion","lines","filtered","i","removed","import_node_fs","import_node_os","import_node_path","import_promises","init_dist","import_yoctocolors","import_node_fs","import_node_os","import_node_path","init_dist","import_zod","FileConfigSchema","CREDENTIAL_FIELDS","homeConfigPath","detail","err","parseShipFile","filePath","raw","error","ShipError","parsed","validateConfig","config","filepath","issue","legacy","key","keys","path","loadShipFile","configFile","explicit","home","ALLOWED_KEYS","CREDENTIAL_FIELDS","defaultConfigPath","maskToken","token","readExistingConfig","configPath","parseShipFile","err","isShipError","ShipError","runConfig","options","noColor","applyDim","text","applyGreen","existing","existingToken","dropped","key","rl","MY_API_KEY_URL","prompt","input","validateToken","config","warn","init_dist","init_dist","init_dist","SimpleEvents","event","handler","eventHandlers","args","handlerArray","error","err","init_dist","validateLabels","labels","LABEL_CONSTRAINTS","ShipError","normalized","label","cleaned","LABEL_PATTERN","unique","validateDeployConfig","files","config","f","DEPLOYMENT_CONFIG_FILENAME","content","text","assertShipJsonSyntax","DEFAULT_REQUEST_TIMEOUT","DEFAULT_DEPLOY_TIMEOUT","BUILD_SERVICE_BUDGET","DEFAULT_DEPLOY_BUILD_TIMEOUT","DEPLOY_VIA","listQuery","options","params","query","ApiHttp","SimpleEvents","DEFAULT_API","API_PATHS","headers","url","operationName","timeoutMs","cleanup","timeout","fetchOptions","response","ShipError","error","shipError","data","customHeaders","CALLER","existingSignal","controller","timeoutId","abort","files","file","validatePassword","idempotencyKey","validateIdempotencyKey","labels","validateLabels","validateDeployConfig","flags","body","bodyHeaders","IDEMPOTENCY_KEY_CONSTRAINTS","id","normalized","name","deployment","status","ttl","token","_options","indexFile","f","SPA_CHECK_CONSTRAINTS","indexContent","init_dist","init_dist","init_md5","createSPAConfig","configString","SPA_DEFAULT_CONFIG","content","md5","calculateMD5","DEPLOYMENT_CONFIG_FILENAME","detectAndConfigureSPA","files","apiClient","options","f","spaConfig","createDeploymentResource","ctx","getApi","ensureInit","processInput","input","options","ShipError","apiClient","staticFiles","detectAndConfigureSPA","id","createDomainResource","name","createAccountResource","createTokenResource","token","Ship","options","validateCaller","ShipError","validateToken","ApiHttp","ctx","createDeploymentResource","input","opts","createDomainResource","createAccountResource","createTokenResource","error","event","handler","headers","token","value","init_env","init_dist","createDeployBody","files","context","FormData","File","FormDataEncoder","labels","via","password","flags","captcha","formData","checksums","file","ShipError","fileInstance","DEPLOY_FIELDS","encoder","chunks","chunk","body","Ship","options","getENV","ShipError","env","readEnvConfig","input","paths","p","processFilesForNode","createDeployBody","mergeCliConfig","flags","env","file","createClient","Ship","readEnvConfig","loadShipFile","init_dist","toShipError","err","isShipError","ShipError","getUserMessage","options","ErrorType","init_dist","setupUrl","hash","domain","readField","source","field","IN_FLIGHT","DeploymentStatus","PAST_TENSE","announce","result","context","operation","resource","predicate","key","state","consequence","formatDeploymentsList","options","noColor","columns","formatTable","printCursorHint","cursor","info","formatDomainsList","formatDomain","_dnsRecords","_shareHash","isCreate","displayResult","record","formatDetails","formatDeployment","claim","days","formatAccount","formatDomainValidate","success","availabilityText","plainMessage","formatDomainRecords","formatDomainDns","provider","formatDomainShare","formatTokensList","formatToken","row","spec","keyOf","OUTPUTS","r","d","rec","_r","o","formatOutput","json","quiet","output","line","payload","sentence","loadPackageJson","paths","p","packageJson","helpText","noColor","applyBold","text","applyDim","icon","emoji","usageError","message","ShipError","ErrorType","collect","value","previous","parseInteger","parsed","mergeLabelOption","cmdOptions","programOpts","labels","filtered","l","mergePasswordOption","processOptions","command","options","forceColor","handleUnknownSubcommand","subcommands","subcommandsOf","c","unknown","arg","error","resolveCliToken","flags","file","loadShipFile","token","mergeCliConfig","readEnvConfig","performDeploy","client","deployPath","password","globalOptions","stats","deployOptions","normalizeVia","DeploymentVia","idempotencyKey","abortController","spinner","yoctoSpinner","sigintHandler","buildProgram","program","err","str","handleError","opts","shipError","toShipError","getUserMessage","withErrorHandling","handler","context","args","config","apiUrl","createClient","result","formatOutput","cmd","helper","thisCommand","validateToken","validateApiUrl","validationError","isShipError","_options","deploymentsCmd","deployment","domainsCmd","name","resolve","data","chunk","setOptions","records","share","tokensCmd","completionCmd","installCompletion","uninstallCompletion","runConfig","deployShortcut","buildProgram","err"]}
|