{"version":3,"file":"index.mjs","names":["LokaliseApiError"],"sources":["../lib/errors/LokaliseError.ts","../lib/services/LokaliseFileExchange.ts","../lib/services/LokaliseDownload.ts","../lib/services/LokaliseUpload.ts"],"sourcesContent":["import type { LokaliseError as ILokaliseError } from \"../interfaces/LokaliseError.js\";\n\n/**\n * Represents a custom error.\n */\nexport class LokaliseError extends Error implements ILokaliseError {\n\t/**\n\t * The error code representing the type of Lokalise API error.\n\t */\n\tcode?: number | undefined;\n\n\t/**\n\t * Additional details about the error.\n\t */\n\tdetails?: Record<string, string | number | boolean>;\n\n\t/**\n\t * Creates a new instance of LokaliseError.\n\t *\n\t * @param message - The error message.\n\t * @param code - The error code (optional).\n\t * @param details - Optional additional details about the error.\n\t */\n\tconstructor(\n\t\tmessage: string,\n\t\tcode?: number,\n\t\tdetails?: Record<string, string | number | boolean>,\n\t) {\n\t\tsuper(message);\n\t\tthis.code = code;\n\n\t\tif (details) {\n\t\t\tthis.details = details;\n\t\t}\n\t}\n\n\t/**\n\t * Returns a string representation of the error, including code and details.\n\t *\n\t * @returns The formatted error message.\n\t */\n\toverride toString(): string {\n\t\tlet baseMessage = `LokaliseError: ${this.message}`;\n\t\tif (this.code) {\n\t\t\tbaseMessage += ` (Code: ${this.code})`;\n\t\t}\n\t\tif (this.details) {\n\t\t\tconst formattedDetails = Object.entries(this.details)\n\t\t\t\t.map(([key, value]) => `${key}: ${value}`)\n\t\t\t\t.join(\", \");\n\n\t\t\tbaseMessage += ` | Details: ${formattedDetails}`;\n\t\t}\n\t\treturn baseMessage;\n\t}\n}\n","import type { ClientParams, QueuedProcess } from \"@lokalise/node-api\";\nimport {\n\tLokaliseApi,\n\tApiError as LokaliseApiError,\n\tLokaliseApiOAuth,\n} from \"@lokalise/node-api\";\nimport {\n\ttype LogFunction,\n\ttype LogLevel,\n\ttype LogThreshold,\n\tlogWithColor,\n\tlogWithLevel,\n} from \"kliedz\";\nimport { LokaliseError } from \"../errors/LokaliseError.js\";\nimport type {\n\tLokaliseExchangeConfig,\n\tRetryParams,\n} from \"../interfaces/index.js\";\n\n/**\n * A utility class for exchanging files with the Lokalise API.\n */\nexport class LokaliseFileExchange {\n\t/**\n\t * The Lokalise API client instance.\n\t */\n\tprotected readonly apiClient: LokaliseApi;\n\n\t/**\n\t * The ID of the project in Lokalise.\n\t */\n\tprotected readonly projectId: string;\n\n\t/**\n\t * Retry parameters for API requests.\n\t */\n\tprotected readonly retryParams: RetryParams;\n\n\t/**\n\t * Logger function.\n\t */\n\tprotected readonly logger: LogFunction;\n\n\t/**\n\t * Log threshold (do not print messages with severity less than the specified value).\n\t */\n\tprotected readonly logThreshold: LogThreshold;\n\n\t/**\n\t * Default retry parameters for API requests.\n\t */\n\tprivate static readonly defaultRetryParams: Required<RetryParams> = {\n\t\tmaxRetries: 3,\n\t\tinitialSleepTime: 1000,\n\t\tjitterRatio: 0.2,\n\t\trng: Math.random,\n\t};\n\n\tprivate static readonly FINISHED_STATUSES = [\n\t\t\"finished\",\n\t\t\"cancelled\",\n\t\t\"failed\",\n\t] as const;\n\n\tprivate static readonly RETRYABLE_CODES = [408, 429];\n\n\tprotected static readonly maxConcurrentProcesses = 6;\n\n\tprivate static isPendingStatus(status?: string | null): boolean {\n\t\treturn !LokaliseFileExchange.isFinishedStatus(status);\n\t}\n\n\tpublic static isFinishedStatus(status?: string | null): boolean {\n\t\treturn (\n\t\t\tstatus != null &&\n\t\t\t(LokaliseFileExchange.FINISHED_STATUSES as readonly string[]).includes(\n\t\t\t\tstatus,\n\t\t\t)\n\t\t);\n\t}\n\n\t/**\n\t * Creates a new instance of LokaliseFileExchange.\n\t *\n\t * @param clientConfig - Configuration for the Lokalise SDK.\n\t * @param exchangeConfig - The configuration object for file exchange operations.\n\t * @throws {LokaliseError} If the provided configuration is invalid.\n\t */\n\tconstructor(\n\t\tclientConfig: ClientParams,\n\t\t{\n\t\t\tprojectId,\n\t\t\tuseOAuth2 = false,\n\t\t\tretryParams,\n\t\t\tlogThreshold = \"info\",\n\t\t\tlogColor = true,\n\t\t}: LokaliseExchangeConfig,\n\t) {\n\t\tthis.projectId = projectId;\n\t\tthis.logThreshold = logThreshold;\n\t\tthis.logger = this.chooseLogger(logColor);\n\t\tthis.retryParams = this.buildRetryParams(retryParams);\n\n\t\tthis.validateParams();\n\n\t\tconst apiConfig = this.buildLokaliseClientConfig(\n\t\t\tclientConfig,\n\t\t\tlogThreshold,\n\t\t);\n\t\tthis.apiClient = this.createApiClient(apiConfig, useOAuth2);\n\t}\n\n\t/**\n\t * Executes an asynchronous operation with exponential-backoff retry logic.\n\t *\n\t * The operation is attempted multiple times if it throws a retryable\n\t * `LokaliseApiError`. Each retry waits longer than the previous one based on\n\t * exponential backoff parameters (`base`, `factor`, optional jitter).\n\t *\n\t * Behaviour:\n\t * - If the operation succeeds — its result is returned immediately.\n\t * - If it fails with a retryable error — the function waits and retries.\n\t * - If the maximum number of retries is reached — throws a `LokaliseError`\n\t *   with the original error details.\n\t * - If the error is non-retryable — it is immediately wrapped into\n\t *   `LokaliseError` and rethrown.\n\t * - Any non-Lokalise errors are rethrown as-is.\n\t *\n\t * @param operation - A function that performs the async action to be retried.\n\t * @returns The successful result of the operation.\n\t * @throws LokaliseError After all retries fail or on non-retryable errors.\n\t */\n\tprotected async withExponentialBackoff<T>(\n\t\toperation: () => Promise<T>,\n\t): Promise<T> {\n\t\tconst { maxRetries } = this.retryParams;\n\t\tthis.logMsg(\n\t\t\t\"debug\",\n\t\t\t`Running operation with exponential backoff; max retries: ${maxRetries}`,\n\t\t);\n\n\t\tfor (let attempt = 1; attempt <= maxRetries + 1; attempt++) {\n\t\t\ttry {\n\t\t\t\tthis.logMsg(\"debug\", `Attempt #${attempt}...`);\n\t\t\t\treturn await operation();\n\t\t\t} catch (error: unknown) {\n\t\t\t\tif (error instanceof LokaliseApiError && this.isRetryable(error)) {\n\t\t\t\t\tthis.logMsg(\"debug\", `Retryable error caught: ${error.message}`);\n\n\t\t\t\t\tif (attempt === maxRetries + 1) {\n\t\t\t\t\t\tthrow new LokaliseError(\n\t\t\t\t\t\t\t`Maximum retries reached: ${error.message}`,\n\t\t\t\t\t\t\terror.code,\n\t\t\t\t\t\t\terror.details,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\tconst sleepMs = this.calculateSleepMs(this.retryParams, attempt);\n\n\t\t\t\t\tthis.logMsg(\"debug\", `Waiting ${sleepMs}ms before retry...`);\n\t\t\t\t\tawait LokaliseFileExchange.sleep(sleepMs);\n\t\t\t\t} else if (error instanceof LokaliseApiError) {\n\t\t\t\t\tthrow new LokaliseError(error.message, error.code, error.details);\n\t\t\t\t} else {\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// This line is unreachable but keeps TS happy.\n\t\tthrow new LokaliseError(\"Unexpected error during operation.\", 500);\n\t}\n\t/**\n\t * Polls the status of queued processes until they are marked as \"finished\"\n\t * or until the maximum wait time is exceeded.\n\t *\n\t * Uses batched polling with limited concurrency and exponential backoff-like\n\t * wait times between iterations. Performs an initial status snapshot, then\n\t * repeatedly refreshes pending processes until either:\n\t * - all of them reach a finished state, or\n\t * - the time budget (`maxWaitTime`) is exhausted.\n\t *\n\t * A final refresh is performed for any still-pending processes before returning.\n\t *\n\t * @param processes - List of queued processes to poll.\n\t * @param initialWaitTime - Initial delay (in ms) between polling iterations.\n\t * @param maxWaitTime - Maximum total time (in ms) allowed for polling.\n\t * @param concurrency - Maximum number of processes to refresh per batch.\n\t * @returns A list of processes with their latest known statuses.\n\t */\n\tprotected async pollProcesses(\n\t\tprocesses: QueuedProcess[],\n\t\tinitialWaitTime: number,\n\t\tmaxWaitTime: number,\n\t\tconcurrency = LokaliseFileExchange.maxConcurrentProcesses,\n\t): Promise<QueuedProcess[]> {\n\t\tthis.logMsg(\n\t\t\t\"debug\",\n\t\t\t`Start polling processes. Total processes count: ${processes.length}`,\n\t\t);\n\n\t\tconst startTime = Date.now();\n\n\t\tconst { processMap, pendingProcessIds } =\n\t\t\tthis.initializePollingState(processes);\n\n\t\tawait this.runPollingLoop(\n\t\t\tprocessMap,\n\t\t\tpendingProcessIds,\n\t\t\tstartTime,\n\t\t\tinitialWaitTime,\n\t\t\tmaxWaitTime,\n\t\t\tconcurrency,\n\t\t);\n\n\t\tif (pendingProcessIds.size > 0) {\n\t\t\tawait this.refreshRemainingProcesses(\n\t\t\t\tprocessMap,\n\t\t\t\tpendingProcessIds,\n\t\t\t\tconcurrency,\n\t\t\t);\n\t\t}\n\n\t\treturn Array.from(processMap.values());\n\t}\n\n\t/**\n\t * Builds internal tracking structures for polling: a map of process IDs\n\t * to their last known state and a set of IDs that are still pending.\n\t *\n\t * Also logs the initial status of each process.\n\t *\n\t * @param processes - Initial list of queued processes.\n\t * @returns A map of processes keyed by ID and a set of pending process IDs.\n\t */\n\tprivate initializePollingState(processes: QueuedProcess[]): {\n\t\tprocessMap: Map<string, QueuedProcess>;\n\t\tpendingProcessIds: Set<string>;\n\t} {\n\t\tthis.logMsg(\"debug\", \"Initial processes check...\");\n\n\t\tconst processMap = new Map<string, QueuedProcess>();\n\t\tconst pendingProcessIds = new Set<string>();\n\n\t\tfor (const p of processes) {\n\t\t\tif (p.status) {\n\t\t\t\tthis.logMsg(\n\t\t\t\t\t\"debug\",\n\t\t\t\t\t`Process ID: ${p.process_id}, status: ${p.status}`,\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tthis.logMsg(\"debug\", `Process ID: ${p.process_id}, status is missing`);\n\t\t\t}\n\n\t\t\tprocessMap.set(p.process_id, p);\n\n\t\t\tif (LokaliseFileExchange.isPendingStatus(p.status)) {\n\t\t\t\tpendingProcessIds.add(p.process_id);\n\t\t\t}\n\t\t}\n\n\t\treturn { processMap, pendingProcessIds };\n\t}\n\n\t/**\n\t * Runs the main polling loop for the given processes.\n\t *\n\t * Repeatedly fetches updated process statuses in batches while:\n\t * - there are still pending IDs, and\n\t * - the elapsed time is below the configured maximum.\n\t *\n\t * Includes a small \"fast-follow\" recheck when some processes have missing\n\t * status on the first iterations, and uses a growing wait time between\n\t * iterations (capped by the remaining time budget).\n\t *\n\t * @param processMap - Map of process IDs to their last known state.\n\t * @param pendingProcessIds - Set of IDs that are not finished yet.\n\t * @param startTime - Timestamp (ms) when polling started.\n\t * @param initialWaitTime - Initial delay (ms) between polling iterations.\n\t * @param maxWaitTime - Maximum total polling duration (ms).\n\t * @param concurrency - Maximum number of processes to refresh per batch.\n\t */\n\tprivate async runPollingLoop(\n\t\tprocessMap: Map<string, QueuedProcess>,\n\t\tpendingProcessIds: Set<string>,\n\t\tstartTime: number,\n\t\tinitialWaitTime: number,\n\t\tmaxWaitTime: number,\n\t\tconcurrency: number,\n\t): Promise<void> {\n\t\tlet waitTime = initialWaitTime;\n\t\tlet didFastFollow = false;\n\n\t\twhile (pendingProcessIds.size > 0 && Date.now() - startTime < maxWaitTime) {\n\t\t\tthis.logMsg(\"debug\", `Polling... Pending IDs: ${pendingProcessIds.size}`);\n\n\t\t\tif (\n\t\t\t\t!didFastFollow &&\n\t\t\t\t[...processMap.values()].some((p) => p.status == null)\n\t\t\t) {\n\t\t\t\tthis.logMsg(\n\t\t\t\t\t\"debug\",\n\t\t\t\t\t\"Fast-follow: some statuses missing, quick recheck in 200ms\",\n\t\t\t\t);\n\t\t\t\tawait LokaliseFileExchange.sleep(200);\n\t\t\t\tdidFastFollow = true;\n\t\t\t}\n\n\t\t\tconst ids = [...pendingProcessIds];\n\t\t\tconst batch = await this.fetchProcessesBatch(ids, concurrency);\n\n\t\t\tfor (const { id, process } of batch) {\n\t\t\t\tif (!process) continue;\n\t\t\t\tprocessMap.set(id, process);\n\n\t\t\t\tif (LokaliseFileExchange.isFinishedStatus(process.status)) {\n\t\t\t\t\tthis.logMsg(\n\t\t\t\t\t\t\"debug\",\n\t\t\t\t\t\t`Process ${id} completed with status=${process.status}.`,\n\t\t\t\t\t);\n\t\t\t\t\tpendingProcessIds.delete(id);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (pendingProcessIds.size === 0) {\n\t\t\t\tthis.logMsg(\"debug\", \"Finished polling. Pending processes IDs: 0\");\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tconst elapsed = Date.now() - startTime;\n\t\t\tconst remaining = maxWaitTime - elapsed;\n\n\t\t\tif (remaining <= 0) {\n\t\t\t\tthis.logMsg(\n\t\t\t\t\t\"debug\",\n\t\t\t\t\t\"Time budget exhausted, stopping polling without extra sleep.\",\n\t\t\t\t);\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tconst sleepMs = Math.min(waitTime, remaining);\n\t\t\tthis.logMsg(\"debug\", `Waiting ${sleepMs}...`);\n\t\t\tawait LokaliseFileExchange.sleep(sleepMs);\n\n\t\t\twaitTime = Math.min(\n\t\t\t\twaitTime * 2,\n\t\t\t\tMath.max(0, maxWaitTime - (Date.now() - startTime)),\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Performs a final status refresh for any processes that are still marked\n\t * as pending after the main polling loop.\n\t *\n\t * This gives one last chance to capture terminal statuses right before\n\t * returning the result to the caller.\n\t *\n\t * @param processMap - Map of process IDs to their last known state.\n\t * @param pendingProcessIds - Set of IDs that are still considered pending.\n\t * @param concurrency - Maximum number of processes to refresh per batch.\n\t */\n\tprivate async refreshRemainingProcesses(\n\t\tprocessMap: Map<string, QueuedProcess>,\n\t\tpendingProcessIds: Set<string>,\n\t\tconcurrency: number,\n\t): Promise<void> {\n\t\tthis.logMsg(\n\t\t\t\"debug\",\n\t\t\t`Final refresh for ${pendingProcessIds.size} pending processes before return...`,\n\t\t);\n\n\t\tconst finalBatch = await this.fetchProcessesBatch(\n\t\t\t[...pendingProcessIds],\n\t\t\tconcurrency,\n\t\t);\n\n\t\tfor (const { id, process } of finalBatch) {\n\t\t\tif (process) {\n\t\t\t\tprocessMap.set(id, process);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Determines whether the given Lokalise API error should trigger a retry attempt.\n\t *\n\t * An error is considered retryable if its `code` matches one of the predefined\n\t * retryable status codes.\n\t *\n\t * @param error - The `LokaliseApiError` instance to evaluate.\n\t * @returns `true` if the error is retryable, otherwise `false`.\n\t */\n\tprivate isRetryable(error: LokaliseApiError): boolean {\n\t\treturn LokaliseFileExchange.RETRYABLE_CODES.includes(error.code);\n\t}\n\n\t/**\n\t * Logs a message using the configured logger, respecting the current log threshold.\n\t *\n\t * Wraps the raw logger call by attaching metadata such as:\n\t * - `level` — severity of the log entry,\n\t * - `threshold` — active log level threshold used to filter messages,\n\t * - `withTimestamp` — instructs the logger to prepend a timestamp.\n\t *\n\t * All variadic `args` are forwarded directly to the logger.\n\t *\n\t * @param level - Log level of the message being emitted.\n\t * @param args - Additional values to pass to the logger.\n\t */\n\tprotected logMsg(level: LogLevel, ...args: unknown[]): void {\n\t\tthis.logger(\n\t\t\t{ level, threshold: this.logThreshold, withTimestamp: true },\n\t\t\t...args,\n\t\t);\n\t}\n\n\t/**\n\t * Fetches the most recent state of a queued process from the Lokalise API.\n\t *\n\t * Sends a GET request for the process identified by `processId` and logs\n\t * both the request and the received status. Used during polling to refresh\n\t * the status of long-running async operations.\n\t *\n\t * @param processId - The unique identifier of the queued process to retrieve.\n\t * @returns A promise resolving to the updated `QueuedProcess` object.\n\t */\n\tprotected async getUpdatedProcess(processId: string): Promise<QueuedProcess> {\n\t\tthis.logMsg(\"debug\", `Requesting update for process ID: ${processId}`);\n\n\t\tconst updatedProcess = await this.apiClient\n\t\t\t.queuedProcesses()\n\t\t\t.get(processId, { project_id: this.projectId });\n\n\t\tif (updatedProcess.status) {\n\t\t\tthis.logMsg(\n\t\t\t\t\"debug\",\n\t\t\t\t`Process ID: ${updatedProcess.process_id}, status: ${updatedProcess.status}`,\n\t\t\t);\n\t\t} else {\n\t\t\tthis.logMsg(\n\t\t\t\t\"debug\",\n\t\t\t\t`Process ID: ${updatedProcess.process_id}, status is missing`,\n\t\t\t);\n\t\t}\n\n\t\treturn updatedProcess;\n\t}\n\n\t/**\n\t * Validates essential client configuration parameters before any operations run.\n\t *\n\t * Ensures that:\n\t * - `projectId` is present and is a non-empty string,\n\t * - retry settings (`maxRetries`, `initialSleepTime`, `jitterRatio`)\n\t *   fall within acceptable ranges.\n\t *\n\t * Throws a `LokaliseError` if any configuration parameter is missing,\n\t * malformed, or outside allowed bounds.\n\t */\n\tprivate validateParams(): void {\n\t\tif (!this.projectId || typeof this.projectId !== \"string\") {\n\t\t\tthrow new LokaliseError(\"Invalid or missing Project ID.\");\n\t\t}\n\n\t\tconst { maxRetries, initialSleepTime, jitterRatio } = this.retryParams;\n\n\t\tif (maxRetries < 0) {\n\t\t\tthrow new LokaliseError(\n\t\t\t\t\"maxRetries must be greater than or equal to zero.\",\n\t\t\t);\n\t\t}\n\t\tif (initialSleepTime <= 0) {\n\t\t\tthrow new LokaliseError(\"initialSleepTime must be a positive value.\");\n\t\t}\n\t\tif (jitterRatio < 0 || jitterRatio > 1)\n\t\t\tthrow new LokaliseError(\"jitterRatio must be between 0 and 1.\");\n\t}\n\n\t/**\n\t * Executes asynchronous work over a list of items with a fixed concurrency limit.\n\t *\n\t * Spawns up to `limit` parallel worker loops. Each loop pulls the next\n\t * unprocessed item index in a thread-safe manner (via shared counter `i`),\n\t * runs the provided async `worker` function for that item, and stores the\n\t * resulting value in the corresponding position of the `results` array.\n\t *\n\t * Processing stops when all items have been consumed. If any worker throws,\n\t * the error propagates and the whole operation rejects.\n\t *\n\t * @param items - The list of items to process.\n\t * @param limit - Maximum number of concurrent async operations.\n\t * @param worker - Async handler executed for each item.\n\t * @returns A promise resolving to an array of results, preserving input order.\n\t */\n\tprotected async runWithConcurrencyLimit<T, R>(\n\t\titems: T[],\n\t\tlimit: number,\n\t\tworker: (item: T, index: number) => Promise<R>,\n\t): Promise<R[]> {\n\t\tconst results = new Array<R>(items.length);\n\t\tlet i = 0;\n\n\t\tconst workers = new Array(Math.min(limit, items.length))\n\t\t\t.fill(null)\n\t\t\t.map(async () => {\n\t\t\t\twhile (true) {\n\t\t\t\t\tconst idx = i++;\n\t\t\t\t\tif (idx >= items.length) break;\n\t\t\t\t\tconst item = items[idx];\n\t\t\t\t\tif (item === undefined) {\n\t\t\t\t\t\tthrow new Error(`Missing item at index ${idx}`);\n\t\t\t\t\t}\n\n\t\t\t\t\tresults[idx] = await worker(item, idx);\n\t\t\t\t}\n\t\t\t});\n\n\t\tawait Promise.all(workers);\n\t\treturn results;\n\t}\n\n\t/**\n\t * Fetches updated process states for a list of process IDs in parallel,\n\t * respecting a maximum concurrency limit.\n\t *\n\t * Each process ID is resolved via `getUpdatedProcess()`. If the fetch\n\t * succeeds, the returned object includes both `id` and the updated\n\t * `process`. If an error occurs, a warning is logged and the result\n\t * contains only the `id`, allowing polling to continue without failing\n\t * the entire batch.\n\t *\n\t * Internally uses `runWithConcurrencyLimit` to enforce controlled parallelism.\n\t *\n\t * @param processIds - The list of queued process IDs to refresh.\n\t * @param concurrency - Maximum number of simultaneous requests.\n\t * @returns A list of objects mapping each ID to its latest fetched state\n\t *          (or `undefined` if the fetch failed).\n\t */\n\tprotected async fetchProcessesBatch(\n\t\tprocessIds: string[],\n\t\tconcurrency = LokaliseFileExchange.maxConcurrentProcesses,\n\t): Promise<Array<{ id: string; process?: QueuedProcess }>> {\n\t\treturn this.runWithConcurrencyLimit(processIds, concurrency, async (id) => {\n\t\t\ttry {\n\t\t\t\tconst updated = await this.getUpdatedProcess(id);\n\t\t\t\treturn { id, process: updated };\n\t\t\t} catch (error) {\n\t\t\t\tthis.logMsg(\"warn\", `Failed to fetch process ${id}:`, error);\n\t\t\t\treturn { id };\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * Delays execution for a given duration.\n\t *\n\t * Creates a Promise that resolves after the specified number of milliseconds,\n\t * allowing async workflows to pause without blocking the event loop.\n\t *\n\t * @param ms - Number of milliseconds to wait.\n\t * @returns A promise that resolves after the delay.\n\t */\n\tprotected static sleep(ms: number): Promise<void> {\n\t\treturn new Promise((resolve) => setTimeout(resolve, ms));\n\t}\n\n\t/**\n\t * Computes the exponential-backoff delay for a retry attempt,\n\t * optionally adding jitter to avoid synchronized retries.\n\t *\n\t * @param retryParams - Backoff settings (initial delay, jitter, RNG).\n\t * @param attempt - Retry attempt number (1-based).\n\t * @returns Calculated sleep time in milliseconds.\n\t */\n\tprotected calculateSleepMs(\n\t\tretryParams: RetryParams,\n\t\tattempt: number,\n\t): number {\n\t\tconst { initialSleepTime, jitterRatio, rng } = retryParams;\n\n\t\tconst base = initialSleepTime * 2 ** (attempt - 1);\n\t\tconst maxJitter = Math.floor(base * jitterRatio);\n\t\tconst jitter = maxJitter > 0 ? Math.floor(rng() * maxJitter) : 0;\n\t\treturn base + jitter;\n\t}\n\n\t/**\n\t * Builds the final Lokalise client configuration,\n\t * enabling silent mode when the log threshold is `\"silent\"`.\n\t *\n\t * @param clientConfig - Base client parameters.\n\t * @param logThreshold - Active logging threshold.\n\t * @returns The adjusted client configuration.\n\t */\n\tprivate buildLokaliseClientConfig(\n\t\tclientConfig: ClientParams,\n\t\tlogThreshold: LogThreshold,\n\t): ClientParams {\n\t\tif (logThreshold === \"silent\") {\n\t\t\treturn {\n\t\t\t\t...clientConfig,\n\t\t\t\tsilent: true,\n\t\t\t};\n\t\t}\n\n\t\treturn { ...clientConfig };\n\t}\n\n\t/**\n\t * Creates the appropriate Lokalise API client instance,\n\t * choosing between OAuth2 and token-based authentication.\n\t *\n\t * @param lokaliseApiConfig - Configuration passed to the client.\n\t * @param useOAuth2 - Whether OAuth2 authentication should be used.\n\t * @returns A Lokalise API client instance.\n\t */\n\tprivate createApiClient(\n\t\tlokaliseApiConfig: ClientParams,\n\t\tuseOAuth2: boolean,\n\t): LokaliseApi | LokaliseApiOAuth {\n\t\tif (useOAuth2) {\n\t\t\tthis.logMsg(\"debug\", \"Using OAuth 2 Lokalise API client\");\n\t\t\treturn new LokaliseApiOAuth(lokaliseApiConfig);\n\t\t}\n\n\t\tthis.logMsg(\"debug\", \"Using regular (token-based) Lokalise API client\");\n\t\treturn new LokaliseApi(lokaliseApiConfig);\n\t}\n\n\t/**\n\t * Merges user-provided retry settings with default retry parameters.\n\t *\n\t * @param retryParams - Optional overrides.\n\t * @returns Fully resolved retry configuration.\n\t */\n\tprivate buildRetryParams(retryParams?: Partial<RetryParams>): RetryParams {\n\t\treturn {\n\t\t\t...LokaliseFileExchange.defaultRetryParams,\n\t\t\t...retryParams,\n\t\t};\n\t}\n\n\t/**\n\t * Selects the logger implementation based on whether color output is enabled.\n\t *\n\t * @param logColor - If true, uses the colorized logger.\n\t * @returns The chosen log function.\n\t */\n\tprivate chooseLogger(logColor: boolean): LogFunction {\n\t\treturn logColor ? logWithColor : logWithLevel;\n\t}\n}\n","import crypto from \"node:crypto\";\nimport fs from \"node:fs\";\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport { pipeline, Readable } from \"node:stream\";\nimport type { ReadableStream as WebReadableStream } from \"node:stream/web\";\nimport { promisify } from \"node:util\";\nimport type {\n\tDownloadBundle,\n\tDownloadedFileProcessDetails,\n\tDownloadFileParams,\n\tQueuedProcess,\n} from \"@lokalise/node-api\";\nimport yauzl from \"yauzl\";\nimport { LokaliseError } from \"../errors/LokaliseError.js\";\nimport type {\n\tDownloadTranslationParams,\n\tProcessDownloadFileParams,\n} from \"../interfaces/index.js\";\nimport { LokaliseFileExchange } from \"./LokaliseFileExchange.js\";\n\n/**\n * Handles downloading and extracting translation files from Lokalise.\n */\nexport class LokaliseDownload extends LokaliseFileExchange {\n\tprivate static readonly defaultProcessParams: Required<ProcessDownloadFileParams> =\n\t\t{\n\t\t\tasyncDownload: false,\n\t\t\tpollInitialWaitTime: 1000,\n\t\t\tpollMaximumWaitTime: 120_000,\n\t\t\tbundleDownloadTimeout: 0,\n\t\t};\n\n\tprivate readonly streamPipeline = promisify(pipeline);\n\n\t/**\n\t * Downloads translations from Lokalise, optionally using async polling, and extracts them to disk.\n\t *\n\t * @param downloadTranslationParams - Full configuration for the download process, extraction destination, and optional polling or timeout settings.\n\t * @throws {LokaliseError} If the download, polling, or extraction fails.\n\t */\n\tasync downloadTranslations({\n\t\tdownloadFileParams,\n\t\textractParams = {},\n\t\tprocessDownloadFileParams,\n\t}: DownloadTranslationParams): Promise<void> {\n\t\tthis.logMsg(\"debug\", \"Downloading translations from Lokalise...\");\n\n\t\tconst processParams = this.buildProcessParams(processDownloadFileParams);\n\n\t\tconst translationsBundleURL = await this.fetchTranslationBundleURL(\n\t\t\tdownloadFileParams,\n\t\t\tprocessParams,\n\t\t);\n\n\t\tconst zipFilePath = await this.downloadZip(\n\t\t\ttranslationsBundleURL,\n\t\t\tprocessParams.bundleDownloadTimeout,\n\t\t);\n\n\t\tawait this.processZip(\n\t\t\tzipFilePath,\n\t\t\tpath.resolve(extractParams.outputDir ?? \"./\"),\n\t\t);\n\t}\n\n\t/**\n\t * Unpacks a ZIP file into the specified directory.\n\t *\n\t * @param zipFilePath - Path to the ZIP file.\n\t * @param outputDir - Directory to extract the files into.\n\t * @throws {LokaliseError} If extraction fails or malicious paths are detected.\n\t */\n\tprotected async unpackZip(\n\t\tzipFilePath: string,\n\t\toutputDir: string,\n\t): Promise<void> {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tyauzl.open(zipFilePath, { lazyEntries: true }, (err, zipfile) => {\n\t\t\t\tif (err) {\n\t\t\t\t\treturn reject(\n\t\t\t\t\t\tnew LokaliseError(\n\t\t\t\t\t\t\t`Failed to open ZIP file at ${zipFilePath}: ${err.message}`,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tzipfile.readEntry();\n\n\t\t\t\tzipfile.on(\"entry\", (entry) => {\n\t\t\t\t\tthis.handleZipEntry(entry, zipfile, outputDir)\n\t\t\t\t\t\t.then(() => zipfile.readEntry())\n\t\t\t\t\t\t.catch(reject);\n\t\t\t\t});\n\n\t\t\t\tzipfile.on(\"end\", resolve);\n\t\t\t\tzipfile.on(\"error\", reject);\n\t\t\t});\n\t\t});\n\t}\n\n\t/**\n\t * Downloads a ZIP file from the given URL and stores it as a temporary file.\n\t *\n\t * Performs URL validation, optional timeout handling, fetch request execution,\n\t * response integrity checks, and writes the ZIP stream to disk.\n\t *\n\t * @param url - Direct URL to the ZIP bundle provided by Lokalise.\n\t * @param downloadTimeout - Optional timeout (in ms) for the HTTP request. `0` disables timeouts.\n\t * @returns Absolute path to the temporary ZIP file on disk.\n\t */\n\tprotected async downloadZip(\n\t\turl: string,\n\t\tdownloadTimeout = 0,\n\t): Promise<string> {\n\t\tthis.logMsg(\"debug\", \"Downloading translation bundle...\");\n\n\t\tconst bundleURL = this.assertHttpUrl(url);\n\t\tconst tempZipPath = this.buildTempZipPath();\n\n\t\tconst signal = this.buildAbortSignal(downloadTimeout);\n\t\tconst response = await this.fetchZipResponse(\n\t\t\tbundleURL,\n\t\t\tsignal,\n\t\t\tdownloadTimeout,\n\t\t);\n\n\t\tconst body = this.getZipResponseBody(response, url);\n\n\t\tawait this.writeZipToDisk(body, tempZipPath);\n\n\t\treturn tempZipPath;\n\t}\n\n\t/**\n\t * Builds a unique temporary file path for storing the downloaded ZIP bundle.\n\t *\n\t * Uses a UUID when available or falls back to a combination of PID, timestamp, and random bytes.\n\t *\n\t * @returns A full path to a temporary ZIP file in the OS temp directory.\n\t */\n\tprotected buildTempZipPath(): string {\n\t\tconst uid =\n\t\t\tcrypto.randomUUID?.() ??\n\t\t\t`${process.pid}-${Date.now()}-${crypto.randomBytes(8).toString(\"hex\")}`;\n\n\t\treturn path.join(os.tmpdir(), `lokalise-${uid}.zip`);\n\t}\n\n\t/**\n\t * Creates an optional AbortSignal for enforcing request timeouts.\n\t *\n\t * Returns `undefined` when no timeout is configured, disabling abort handling.\n\t *\n\t * @param downloadTimeout - Timeout in milliseconds. `0` or negative disables the signal.\n\t * @returns An AbortSignal if timeout is enabled, otherwise `undefined`.\n\t */\n\tprivate buildAbortSignal(downloadTimeout: number): AbortSignal | undefined {\n\t\tif (downloadTimeout <= 0) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\treturn AbortSignal.timeout(downloadTimeout);\n\t}\n\n\t/**\n\t * Executes a fetch request for the ZIP bundle URL with optional timeout handling.\n\t *\n\t * Wraps network failures, timeouts, and unexpected fetch errors into `LokaliseError`\n\t * so higher-level logic receives consistent exceptions.\n\t *\n\t * @param bundleURL - Parsed URL pointing to the ZIP file.\n\t * @param signal - Optional `AbortSignal` used to enforce request timeouts.\n\t * @param downloadTimeout - Timeout duration (ms) used for error messaging.\n\t * @returns The raw `Response` object returned by `fetch` if the request succeeds.\n\t */\n\tprotected async fetchZipResponse(\n\t\tbundleURL: URL,\n\t\tsignal: AbortSignal | undefined,\n\t\tdownloadTimeout: number,\n\t): Promise<Response> {\n\t\ttry {\n\t\t\treturn await fetch(bundleURL, signal ? { signal } : {});\n\t\t} catch (err) {\n\t\t\tif (err instanceof Error) {\n\t\t\t\tif (err.name === \"TimeoutError\") {\n\t\t\t\t\tthrow new LokaliseError(\n\t\t\t\t\t\t`Request timed out after ${downloadTimeout}ms`,\n\t\t\t\t\t\t408,\n\t\t\t\t\t\t{ reason: \"timeout\" },\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tthrow new LokaliseError(err.message, 500, {\n\t\t\t\t\treason: \"network or fetch error\",\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// This should never happen in production\n\t\t\t// as realistically fetch always raises Error,\n\t\t\t// unless some black magic has been involved.\n\t\t\t/* v8 ignore start */\n\t\t\tthrow new LokaliseError(\n\t\t\t\t\"An unknown error occurred. This might indicate a bug.\",\n\t\t\t\t500,\n\t\t\t\t{\n\t\t\t\t\treason: String(err),\n\t\t\t\t},\n\t\t\t);\n\t\t\t/* v8 ignore end */\n\t\t}\n\t}\n\n\t/**\n\t * Validates and extracts the readable body stream from a fetch response.\n\t *\n\t * Ensures the response is OK and has a non-null body before returning it.\n\t *\n\t * @param response - The HTTP response returned by `fetch`.\n\t * @param originalUrl - Original URL used for error diagnostics.\n\t * @returns A web ReadableStream of the ZIP file contents.\n\t * @throws {LokaliseError} If the response is not OK or body is missing.\n\t */\n\tprivate getZipResponseBody(\n\t\tresponse: Response,\n\t\toriginalUrl: string,\n\t): WebReadableStream<Uint8Array> {\n\t\tif (!response.ok) {\n\t\t\tthrow new LokaliseError(\n\t\t\t\t`Failed to download ZIP file: ${response.statusText} (${response.status})`,\n\t\t\t);\n\t\t}\n\n\t\tconst body = response.body as WebReadableStream<Uint8Array> | null;\n\n\t\tif (!body) {\n\t\t\tthrow new LokaliseError(\n\t\t\t\t`Response body is null. Cannot download ZIP file from URL: ${originalUrl}`,\n\t\t\t);\n\t\t}\n\n\t\treturn body;\n\t}\n\n\t/**\n\t * Streams the ZIP response body to a temporary file on disk.\n\t *\n\t * Cleans up the temporary file if the streaming pipeline fails.\n\t *\n\t * @param body - Web readable stream of the ZIP content.\n\t * @param tempZipPath - Path where the ZIP should be written.\n\t * @returns A promise that resolves once the file is fully written.\n\t * @throws {Error} Re-throws any pipeline errors after attempting cleanup.\n\t */\n\tprivate async writeZipToDisk(\n\t\tbody: WebReadableStream<Uint8Array>,\n\t\ttempZipPath: string,\n\t): Promise<void> {\n\t\ttry {\n\t\t\tconst nodeReadable = Readable.fromWeb(body);\n\t\t\tawait this.streamPipeline(\n\t\t\t\tnodeReadable,\n\t\t\t\tfs.createWriteStream(tempZipPath),\n\t\t\t);\n\t\t} catch (e) {\n\t\t\ttry {\n\t\t\t\tawait fs.promises.unlink(tempZipPath);\n\t\t\t} catch {\n\t\t\t\tthis.logMsg(\n\t\t\t\t\t\"debug\",\n\t\t\t\t\t`Stream pipeline failed and unable to remove temp path ${tempZipPath}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tthrow e;\n\t\t}\n\t}\n\n\t/**\n\t * Retrieves a translation bundle from Lokalise with retries and exponential backoff.\n\t *\n\t * @param downloadFileParams - Parameters for Lokalise API file download.\n\t * @returns The downloaded bundle metadata.\n\t * @throws {LokaliseError} If retries are exhausted or an API error occurs.\n\t */\n\tprotected async getTranslationsBundle(\n\t\tdownloadFileParams: DownloadFileParams,\n\t): Promise<DownloadBundle> {\n\t\treturn this.withExponentialBackoff(() =>\n\t\t\tthis.apiClient.files().download(this.projectId, downloadFileParams),\n\t\t);\n\t}\n\n\t/**\n\t * Retrieves a translation bundle from Lokalise with retries and exponential backoff.\n\t *\n\t * @param downloadFileParams - Parameters for Lokalise API file download.\n\t * @returns The queued process.\n\t * @throws {LokaliseError} If retries are exhausted or an API error occurs.\n\t */\n\tprotected async getTranslationsBundleAsync(\n\t\tdownloadFileParams: DownloadFileParams,\n\t): Promise<QueuedProcess> {\n\t\treturn this.withExponentialBackoff(() =>\n\t\t\tthis.apiClient.files().async_download(this.projectId, downloadFileParams),\n\t\t);\n\t}\n\n\t/**\n\t * Extracts a single entry from a ZIP archive to the specified output directory.\n\t *\n\t * Creates necessary directories and streams the file content to disk.\n\t *\n\t * @param entry - The ZIP entry to extract.\n\t * @param zipfile - The open ZIP file instance.\n\t * @param outputDir - The directory where the entry should be written.\n\t * @returns A promise that resolves when the entry is fully written.\n\t */\n\tprotected async handleZipEntry(\n\t\tentry: yauzl.Entry,\n\t\tzipfile: yauzl.ZipFile,\n\t\toutputDir: string,\n\t): Promise<void> {\n\t\tconst fullPath = this.processZipEntryPath(outputDir, entry.fileName);\n\n\t\tif (entry.fileName.endsWith(\"/\")) {\n\t\t\t// it's a directory\n\t\t\tawait this.createDir(fullPath);\n\t\t\treturn;\n\t\t}\n\n\t\tawait this.createDir(path.dirname(fullPath));\n\n\t\treturn new Promise((response, reject) => {\n\t\t\tzipfile.openReadStream(entry, (readErr, readStream) => {\n\t\t\t\tif (readErr || !readStream) {\n\t\t\t\t\treturn reject(\n\t\t\t\t\t\tnew LokaliseError(`Failed to read ZIP entry: ${entry.fileName}`),\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tconst writeStream = fs.createWriteStream(fullPath);\n\t\t\t\treadStream.pipe(writeStream);\n\t\t\t\twriteStream.on(\"finish\", response);\n\t\t\t\twriteStream.on(\"error\", reject);\n\t\t\t\treadStream.on(\"error\", reject);\n\t\t\t});\n\t\t});\n\t}\n\n\t/**\n\t * Creates a directory and all necessary parent directories.\n\t *\n\t * @param dir - The directory path to create.\n\t * @returns A promise that resolves when the directory is created.\n\t */\n\tprivate async createDir(dir: string): Promise<void> {\n\t\tawait fs.promises.mkdir(dir, { recursive: true });\n\t}\n\n\t/**\n\t * Resolves and validates the full output path for a ZIP entry.\n\t *\n\t * Prevents path traversal attacks by ensuring the resolved path stays within the output directory.\n\t *\n\t * @param outputDir - The base output directory.\n\t * @param entryFilename - The filename of the ZIP entry.\n\t * @returns The absolute and safe path to write the entry.\n\t * @throws {LokaliseError} If the entry path is detected as malicious.\n\t */\n\tprotected processZipEntryPath(\n\t\toutputDir: string,\n\t\tentryFilename: string,\n\t): string {\n\t\t// Validate paths to avoid path traversal issues\n\t\tconst fullPath = path.resolve(outputDir, entryFilename);\n\t\tconst relative = path.relative(outputDir, fullPath);\n\t\tif (relative.startsWith(\"..\") || path.isAbsolute(relative)) {\n\t\t\tthrow new LokaliseError(`Malicious ZIP entry detected: ${entryFilename}`);\n\t\t}\n\n\t\treturn fullPath;\n\t}\n\n\t/**\n\t * Parses and validates a URL string, ensuring it uses HTTP or HTTPS protocol.\n\t *\n\t * @param value - The URL string to validate.\n\t * @returns A parsed `URL` object if valid.\n\t * @throws {LokaliseError} If the URL is invalid or uses an unsupported protocol.\n\t */\n\tprivate assertHttpUrl(value: string): URL {\n\t\tlet parsed: URL;\n\t\ttry {\n\t\t\tparsed = new URL(value);\n\t\t} catch {\n\t\t\tthrow new LokaliseError(`Invalid URL: ${value}`);\n\t\t}\n\n\t\tif (parsed.protocol !== \"http:\" && parsed.protocol !== \"https:\") {\n\t\t\tthrow new LokaliseError(`Unsupported protocol in URL: ${value}`);\n\t\t}\n\n\t\treturn parsed;\n\t}\n\n\t/**\n\t * Builds effective process parameters for the download workflow.\n\t *\n\t * Merges caller-provided overrides with the default settings.\n\t *\n\t * @param overrides - Partial process configuration to override defaults.\n\t * @returns Fully resolved process parameters.\n\t */\n\tprivate buildProcessParams(\n\t\toverrides?: Partial<ProcessDownloadFileParams>,\n\t): Required<ProcessDownloadFileParams> {\n\t\treturn {\n\t\t\t...LokaliseDownload.defaultProcessParams,\n\t\t\t...overrides,\n\t\t};\n\t}\n\n\t/**\n\t * Unpacks the downloaded ZIP archive into the target directory and\n\t * removes the temporary archive file afterwards.\n\t *\n\t * Logs progress and always attempts to delete the temporary file.\n\t *\n\t * @param zipFilePath - Path to the temporary ZIP file.\n\t * @param unpackTo - Destination directory for extracted files.\n\t */\n\tprivate async processZip(\n\t\tzipFilePath: string,\n\t\tunpackTo: string,\n\t): Promise<void> {\n\t\tthis.logMsg(\n\t\t\t\"debug\",\n\t\t\t`Unpacking translations from ${zipFilePath} to ${unpackTo}`,\n\t\t);\n\n\t\ttry {\n\t\t\tawait this.unpackZip(zipFilePath, unpackTo);\n\n\t\t\tthis.logMsg(\"debug\", \"Translations unpacked!\");\n\t\t\tthis.logMsg(\"debug\", \"Download successful!\");\n\t\t} finally {\n\t\t\tthis.logMsg(\"debug\", `Removing temp archive from ${zipFilePath}`);\n\t\t\tawait fs.promises.unlink(zipFilePath);\n\t\t}\n\t}\n\n\t/**\n\t * Fetches the direct bundle URL in synchronous (non-async) mode.\n\t *\n\t * Calls the standard download endpoint without polling.\n\t *\n\t * @param downloadFileParams - Parameters for Lokalise API file download.\n\t * @returns Direct bundle URL returned by Lokalise.\n\t */\n\tprivate async fetchBundleURLSync(\n\t\tdownloadFileParams: DownloadFileParams,\n\t): Promise<string> {\n\t\tthis.logMsg(\"debug\", \"Async download mode disabled.\");\n\n\t\tconst translationsBundle =\n\t\t\tawait this.getTranslationsBundle(downloadFileParams);\n\t\treturn translationsBundle.bundle_url;\n\t}\n\n\t/**\n\t * Polls an async download process until it completes or the maximum wait time is reached.\n\t *\n\t * Validates the final status and throws if the process did not finish properly.\n\t *\n\t * @param downloadProcess - The initially queued async process.\n\t * @param initialWait - Initial interval in ms before the first poll.\n\t * @param maxWait - Maximum total wait time in ms.\n\t * @returns The completed process object.\n\t * @throws {LokaliseError} If the process is not found or does not finish successfully.\n\t */\n\tprotected async pollAsyncDownload(\n\t\tdownloadProcess: QueuedProcess,\n\t\tinitialWait: number,\n\t\tmaxWait: number,\n\t): Promise<QueuedProcess> {\n\t\tthis.logMsg(\n\t\t\t\"debug\",\n\t\t\t`Waiting for download process ID ${downloadProcess.process_id} to complete...`,\n\t\t);\n\t\tthis.logMsg(\n\t\t\t\"debug\",\n\t\t\t`Effective waits: initial=${initialWait}ms, max=${maxWait}ms`,\n\t\t);\n\n\t\tconst results = await this.pollProcesses(\n\t\t\t[downloadProcess],\n\t\t\tinitialWait,\n\t\t\tmaxWait,\n\t\t);\n\n\t\tconst completedProcess = results.find(\n\t\t\t(p) => p.process_id === downloadProcess.process_id,\n\t\t);\n\n\t\tif (!completedProcess) {\n\t\t\tthrow new LokaliseError(\n\t\t\t\t`Process ${downloadProcess.process_id} not found after polling`,\n\t\t\t\t500,\n\t\t\t);\n\t\t}\n\n\t\tif (!LokaliseFileExchange.isFinishedStatus(completedProcess.status)) {\n\t\t\tthrow new LokaliseError(\n\t\t\t\t`Download process did not finish within ${maxWait}ms` +\n\t\t\t\t\t`${completedProcess.status ? ` (last status=${completedProcess.status})` : \" (status missing)\"}`,\n\t\t\t\t504,\n\t\t\t);\n\t\t}\n\n\t\treturn completedProcess;\n\t}\n\n\t/**\n\t * Resolves the bundle download URL using either async or sync strategy.\n\t *\n\t * Delegates to `fetchBundleURLAsync` or `fetchBundleURLSync`\n\t * based on the `asyncDownload` flag.\n\t *\n\t * @param downloadFileParams - Parameters for Lokalise API file download.\n\t * @param processParams - Effective process parameters controlling async behavior and polling.\n\t * @returns Direct bundle URL to download.\n\t */\n\tprivate fetchTranslationBundleURL(\n\t\tdownloadFileParams: DownloadFileParams,\n\t\tprocessParams: Required<ProcessDownloadFileParams>,\n\t): Promise<string> {\n\t\treturn processParams.asyncDownload\n\t\t\t? this.fetchBundleURLAsync(downloadFileParams, processParams)\n\t\t\t: this.fetchBundleURLSync(downloadFileParams);\n\t}\n\n\t/**\n\t * Extracts and verifies the download URL from a finished async process.\n\t *\n\t * Ensures `details.download_url` is present and is a string.\n\t *\n\t * @param completedProcess - Process object with status `finished`.\n\t * @returns Valid download URL string.\n\t * @throws {LokaliseError} If the URL is missing or invalid.\n\t */\n\tprivate handleFinishedAsyncProcess(completedProcess: QueuedProcess): string {\n\t\tconst details = completedProcess.details as\n\t\t\t| (DownloadedFileProcessDetails & { download_url?: string })\n\t\t\t| undefined;\n\n\t\tconst url = details?.download_url;\n\t\tif (!url || typeof url !== \"string\") {\n\t\t\tthis.logMsg(\n\t\t\t\t\"warn\",\n\t\t\t\t\"Process finished but details.download_url is missing or invalid\",\n\t\t\t\tdetails,\n\t\t\t);\n\t\t\tthrow new LokaliseError(\n\t\t\t\t\"Lokalise returned finished process without a valid download_url\",\n\t\t\t\t502,\n\t\t\t);\n\t\t}\n\n\t\treturn url;\n\t}\n\n\t/**\n\t * Handles a failed or cancelled async process by throwing an error with context.\n\t *\n\t * Includes the process status and optional message from Lokalise.\n\t *\n\t * @param completedProcess - Process object with status `failed` or `cancelled`.\n\t * @throws {LokaliseError} Always throws, as the process did not succeed.\n\t */\n\tprivate handleFailedAsyncProcess(completedProcess: QueuedProcess): never {\n\t\tconst msg = completedProcess.message?.trim();\n\t\tthrow new LokaliseError(\n\t\t\t`Process ${completedProcess.process_id} ended with status=${completedProcess.status}` +\n\t\t\t\t(msg ? `: ${msg}` : \"\"),\n\t\t\t502,\n\t\t);\n\t}\n\n\t/**\n\t * Handles an unexpected async process outcome when it did not finish in time.\n\t *\n\t * Logs a warning and throws an error indicating that finalization took too long.\n\t *\n\t * @param completedProcess - Process object with unexpected status.\n\t * @param maxWait - Effective maximum wait time used during polling.\n\t * @throws {LokaliseError} Always throws to signal an unexpected async outcome.\n\t */\n\tprivate handleUnexpectedAsyncProcess(\n\t\tcompletedProcess: QueuedProcess,\n\t\tmaxWait: number,\n\t): never {\n\t\tthis.logMsg(\"warn\", `Process ended with status=${completedProcess.status}`);\n\t\tthrow new LokaliseError(\n\t\t\t`Download process took too long to finalize; effective=${maxWait}ms`,\n\t\t\t500,\n\t\t);\n\t}\n\n\t/**\n\t * Runs the async download flow: queues the download, polls its status,\n\t * and returns the final bundle URL once the process completes.\n\t *\n\t * Handles finished, failed/cancelled, and unexpected statuses separately.\n\t *\n\t * @param downloadFileParams - Parameters for Lokalise API async file download.\n\t * @param processParams - Effective process parameters controlling polling behavior.\n\t * @returns Direct URL to the generated ZIP bundle.\n\t * @throws {LokaliseError} If the process fails, is cancelled, or does not finalize properly.\n\t */\n\tprotected async fetchBundleURLAsync(\n\t\tdownloadFileParams: DownloadFileParams,\n\t\tprocessParams: Required<ProcessDownloadFileParams>,\n\t): Promise<string> {\n\t\tthis.logMsg(\"debug\", \"Async download mode enabled.\");\n\n\t\tconst downloadProcess =\n\t\t\tawait this.getTranslationsBundleAsync(downloadFileParams);\n\n\t\tconst { pollInitialWaitTime, pollMaximumWaitTime } = processParams;\n\n\t\tconst completedProcess = await this.pollAsyncDownload(\n\t\t\tdownloadProcess,\n\t\t\tpollInitialWaitTime,\n\t\t\tpollMaximumWaitTime,\n\t\t);\n\n\t\tthis.logMsg(\n\t\t\t\"debug\",\n\t\t\t`Download process status is ${completedProcess.status}`,\n\t\t);\n\n\t\tif (completedProcess.status === \"finished\") {\n\t\t\treturn this.handleFinishedAsyncProcess(completedProcess);\n\t\t}\n\n\t\tif (\n\t\t\tcompletedProcess.status === \"failed\" ||\n\t\t\tcompletedProcess.status === \"cancelled\"\n\t\t) {\n\t\t\tthis.handleFailedAsyncProcess(completedProcess);\n\t\t}\n\n\t\tthis.handleUnexpectedAsyncProcess(completedProcess, pollMaximumWaitTime);\n\t}\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport type { QueuedProcess, UploadFileParams } from \"@lokalise/node-api\";\nimport type {\n\tCollectFileParams,\n\tFileUploadError,\n\tPartialUploadFileParams,\n\tProcessedFile,\n\tProcessUploadFileParams,\n\tQueuedUploadProcessesWithErrors,\n\tUploadTranslationParams,\n} from \"../interfaces/index.js\";\nimport { LokaliseFileExchange } from \"./LokaliseFileExchange.js\";\n\n/**\n * Handles uploading translation files to Lokalise.\n */\nexport class LokaliseUpload extends LokaliseFileExchange {\n\tprivate static readonly defaultPollingParams = {\n\t\tpollStatuses: false,\n\t\tpollInitialWaitTime: 1000,\n\t\tpollMaximumWaitTime: 120_000,\n\t};\n\n\t/**\n\t * Collects files, uploads them to Lokalise, and optionally polls for process completion, returning both processes and errors.\n\t *\n\t * @param {UploadTranslationParams} uploadTranslationParams - Parameters for collecting and uploading files.\n\t * @returns {Promise<{ processes: QueuedProcess[]; errors: FileUploadError[] }>} A promise resolving with successful processes and upload errors.\n\t */\n\tasync uploadTranslations({\n\t\tuploadFileParams,\n\t\tcollectFileParams,\n\t\tprocessUploadFileParams,\n\t}: UploadTranslationParams = {}): Promise<QueuedUploadProcessesWithErrors> {\n\t\tthis.logMsg(\"debug\", \"Uploading translations to Lokalise...\");\n\n\t\tconst { pollStatuses, pollInitialWaitTime, pollMaximumWaitTime } = {\n\t\t\t...LokaliseUpload.defaultPollingParams,\n\t\t\t...processUploadFileParams,\n\t\t};\n\n\t\tthis.logMsg(\"debug\", \"Collecting files to upload...\");\n\t\tconst collectedFiles = await this.collectFiles(collectFileParams);\n\t\tthis.logMsg(\"debug\", \"Collected files:\", collectedFiles);\n\n\t\tthis.logMsg(\"debug\", \"Performing parallel upload...\");\n\t\tconst { processes, errors } = await this.parallelUpload(\n\t\t\tcollectedFiles,\n\t\t\tuploadFileParams,\n\t\t\tprocessUploadFileParams,\n\t\t);\n\n\t\tlet completedProcesses = processes;\n\t\tthis.logMsg(\n\t\t\t\"debug\",\n\t\t\t\"File uploading queued! IDs:\",\n\t\t\tcompletedProcesses.map((p) => p.process_id),\n\t\t);\n\n\t\tif (pollStatuses) {\n\t\t\tthis.logMsg(\"debug\", \"Polling queued processes...\");\n\n\t\t\tcompletedProcesses = await this.pollProcesses(\n\t\t\t\tprocesses,\n\t\t\t\tpollInitialWaitTime,\n\t\t\t\tpollMaximumWaitTime,\n\t\t\t);\n\n\t\t\tthis.logMsg(\"debug\", \"Polling completed!\");\n\t\t}\n\n\t\tthis.logMsg(\"debug\", \"Upload successful!\");\n\n\t\treturn { processes: completedProcesses, errors };\n\t}\n\n\t/**\n\t * Collects files from the filesystem based on the given parameters.\n\t *\n\t * @param {CollectFileParams} collectFileParams - Parameters for file collection, including directories, extensions, and patterns.\n\t * @returns {Promise<string[]>} A promise resolving with the list of collected file paths.\n\t */\n\tprotected async collectFiles({\n\t\tinputDirs = [\"./locales\"],\n\t\textensions = [\".*\"],\n\t\texcludePatterns = [],\n\t\trecursive = true,\n\t\tfileNamePattern = \".*\",\n\t}: CollectFileParams = {}): Promise<string[]> {\n\t\tconst queue = this.makeQueue(inputDirs);\n\t\tconst normalizedExtensions = this.normalizeExtensions(extensions);\n\t\tconst fileNameRegex = this.makeFilenameRegexp(fileNamePattern);\n\t\tconst excludeRegexes = this.makeExcludeRegExes(excludePatterns);\n\n\t\tconst files = await this.processCollectionQueue(\n\t\t\tqueue,\n\t\t\tnormalizedExtensions,\n\t\t\tfileNameRegex,\n\t\t\texcludeRegexes,\n\t\t\trecursive,\n\t\t);\n\n\t\treturn files.sort();\n\t}\n\n\t/**\n\t * Uploads a single file to Lokalise.\n\t *\n\t * @param {UploadFileParams} uploadParams - Parameters for uploading the file.\n\t * @returns {Promise<QueuedProcess>} A promise resolving with the upload process details.\n\t */\n\tprotected async uploadSingleFile(\n\t\tuploadParams: UploadFileParams,\n\t): Promise<QueuedProcess> {\n\t\treturn this.withExponentialBackoff(() =>\n\t\t\tthis.apiClient.files().upload(this.projectId, uploadParams),\n\t\t);\n\t}\n\n\t/**\n\t * Processes a file to prepare it for upload, converting it to base64 and extracting its language code.\n\t *\n\t * @param file - The absolute path to the file.\n\t * @param projectRoot - The root directory of the project.\n\t * @param processParams - Optional processing settings including inferers.\n\t * @returns A promise resolving with the processed file details, including base64 content, relative path, and language code.\n\t */\n\tprotected async processFile(\n\t\tfile: string,\n\t\tprojectRoot: string,\n\t\tprocessParams?: ProcessUploadFileParams,\n\t): Promise<ProcessedFile> {\n\t\tconst relativePath = await this.inferRelativePath(\n\t\t\tfile,\n\t\t\tprojectRoot,\n\t\t\tprocessParams,\n\t\t);\n\n\t\tconst languageCode = await this.inferLanguageCode(\n\t\t\tfile,\n\t\t\trelativePath,\n\t\t\tprocessParams,\n\t\t);\n\n\t\tconst base64Content = await this.readFileAsBase64(file);\n\n\t\treturn {\n\t\t\tdata: base64Content,\n\t\t\tfilename: relativePath,\n\t\t\tlang_iso: languageCode,\n\t\t};\n\t}\n\n\t/**\n\t * Infers the relative path for an uploaded file.\n\t *\n\t * Tries a custom `filenameInferer` first; if it fails or returns empty/whitespace,\n\t * falls back to a POSIX-style relative path based on the project root.\n\t *\n\t * @param file - Absolute path to the source file.\n\t * @param projectRoot - Root directory of the project.\n\t * @param processParams - Optional processing settings including filename inferer.\n\t * @returns A promise resolving with the inferred relative path.\n\t */\n\tprivate async inferRelativePath(\n\t\tfile: string,\n\t\tprojectRoot: string,\n\t\tprocessParams?: ProcessUploadFileParams,\n\t): Promise<string> {\n\t\ttry {\n\t\t\tconst fromInferer = processParams?.filenameInferer\n\t\t\t\t? await processParams.filenameInferer(file)\n\t\t\t\t: \"\";\n\n\t\t\tif (!fromInferer.trim()) {\n\t\t\t\tthrow new Error(\"Invalid filename: empty or only whitespace\");\n\t\t\t}\n\n\t\t\treturn fromInferer;\n\t\t} catch {\n\t\t\t// Fallback: derive a POSIX relative path from project root\n\t\t\treturn path.posix.relative(\n\t\t\t\tthis.toPosixPath(projectRoot),\n\t\t\t\tthis.toPosixPath(file),\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Infers the language code for an uploaded file.\n\t *\n\t * Tries a custom `languageInferer` first; if it fails or returns empty/whitespace,\n\t * falls back to extracting the language code from the filename before the last extension.\n\t *\n\t * Example: \"en.default.json\" → \"default\"\n\t *\n\t * @param file - Absolute path to the source file.\n\t * @param relativePath - Effective relative path of the file (used for fallback parsing).\n\t * @param processParams - Optional processing settings including language inferer.\n\t * @returns A promise resolving with the inferred language code.\n\t */\n\tprivate async inferLanguageCode(\n\t\tfile: string,\n\t\trelativePath: string,\n\t\tprocessParams?: ProcessUploadFileParams,\n\t): Promise<string> {\n\t\ttry {\n\t\t\tconst fromInferer = processParams?.languageInferer\n\t\t\t\t? await processParams.languageInferer(file)\n\t\t\t\t: \"\";\n\n\t\t\tif (!fromInferer.trim()) {\n\t\t\t\tthrow new Error(\"Invalid language code: empty or only whitespace\");\n\t\t\t}\n\n\t\t\treturn fromInferer;\n\t\t} catch {\n\t\t\t// Fallback: derive language code from the basename of the relative path\n\t\t\tconst baseName = path.basename(relativePath);\n\t\t\treturn baseName.split(\".\").slice(-2, -1)[0] ?? \"unknown\";\n\t\t}\n\t}\n\n\t/**\n\t * Reads a file from disk and returns its content encoded as base64.\n\t *\n\t * @param file - Absolute path to the source file.\n\t * @returns A promise resolving with the file content encoded in base64.\n\t */\n\tprivate async readFileAsBase64(file: string): Promise<string> {\n\t\tconst fileContent = await fs.promises.readFile(file);\n\t\treturn fileContent.toString(\"base64\");\n\t}\n\n\t/**\n\t * Uploads files in parallel with a limit on the number of concurrent uploads.\n\t *\n\t * @param {string[]} files - List of file paths to upload.\n\t * @param {Partial<UploadFileParams>} baseUploadFileParams - Base parameters for uploads.\n\t * @param {ProcessUploadFileParams} [processParams] - Optional processing settings including inferers.\n\t * @returns {Promise<{ processes: QueuedProcess[]; errors: FileUploadError[] }>} A promise resolving with successful processes and upload errors.\n\t */\n\tprivate async parallelUpload(\n\t\tfiles: string[],\n\t\tbaseUploadFileParams: PartialUploadFileParams = {},\n\t\tprocessParams?: ProcessUploadFileParams,\n\t): Promise<QueuedUploadProcessesWithErrors> {\n\t\tconst projectRoot = process.cwd();\n\t\tconst queuedProcesses: QueuedProcess[] = [];\n\t\tconst errors: FileUploadError[] = [];\n\n\t\tawait this.runWithConcurrencyLimit(\n\t\t\tfiles,\n\t\t\tLokaliseUpload.maxConcurrentProcesses,\n\t\t\tasync (file) => {\n\t\t\t\ttry {\n\t\t\t\t\tconst processedFileParams = await this.processFile(\n\t\t\t\t\t\tfile,\n\t\t\t\t\t\tprojectRoot,\n\t\t\t\t\t\tprocessParams,\n\t\t\t\t\t);\n\t\t\t\t\tconst queued = await this.uploadSingleFile({\n\t\t\t\t\t\t...baseUploadFileParams,\n\t\t\t\t\t\t...processedFileParams,\n\t\t\t\t\t});\n\t\t\t\t\tqueuedProcesses.push(queued);\n\t\t\t\t} catch (error) {\n\t\t\t\t\terrors.push({ file, error });\n\t\t\t\t}\n\t\t\t},\n\t\t);\n\n\t\treturn { processes: queuedProcesses, errors };\n\t}\n\n\t/**\n\t * Normalizes an array of file extensions by ensuring each starts with a dot and is lowercase.\n\t *\n\t * @param extensions - The list of file extensions to normalize.\n\t * @returns A new array with normalized file extensions.\n\t */\n\tprivate normalizeExtensions(extensions: string[]): string[] {\n\t\treturn extensions.map((ext) =>\n\t\t\t(ext.startsWith(\".\") ? ext : `.${ext}`).toLowerCase(),\n\t\t);\n\t}\n\n\t/**\n\t * Determines whether a file should be collected based on its extension and name pattern.\n\t *\n\t * @param entry - The directory entry to evaluate.\n\t * @param normalizedExtensions - List of allowed file extensions.\n\t * @param fileNameRegex - Regular expression to match valid filenames.\n\t * @returns `true` if the file matches both extension and name pattern, otherwise `false`.\n\t */\n\tprivate shouldCollectFile(\n\t\tentry: fs.Dirent,\n\t\tnormalizedExtensions: string[],\n\t\tfileNameRegex: RegExp,\n\t): boolean {\n\t\tconst fileExt = path.extname(entry.name).toLowerCase();\n\t\tconst matchesExtension =\n\t\t\tnormalizedExtensions.includes(\".*\") ||\n\t\t\tnormalizedExtensions.includes(fileExt);\n\t\tconst matchesFilenamePattern = fileNameRegex.test(entry.name);\n\n\t\treturn matchesExtension && matchesFilenamePattern;\n\t}\n\n\t/**\n\t * Creates a regular expression from a given pattern string or RegExp.\n\t *\n\t * @param fileNamePattern - The filename pattern to convert into a RegExp.\n\t * @returns A valid RegExp object.\n\t * @throws {Error} If the pattern string is invalid and cannot be compiled.\n\t */\n\tprivate makeFilenameRegexp(fileNamePattern: string | RegExp): RegExp {\n\t\ttry {\n\t\t\treturn new RegExp(fileNamePattern);\n\t\t} catch {\n\t\t\tthrow new Error(`Invalid fileNamePattern: ${fileNamePattern}`);\n\t\t}\n\t}\n\n\t/**\n\t * Converts an array of exclude patterns into an array of RegExp objects.\n\t *\n\t * @param excludePatterns - An array of strings or regular expressions to exclude.\n\t * @returns An array of compiled RegExp objects.\n\t * @throws {Error} If any pattern is invalid and cannot be compiled.\n\t */\n\tprivate makeExcludeRegExes(excludePatterns: string[] | RegExp[]): RegExp[] {\n\t\tif (excludePatterns.length === 0) {\n\t\t\treturn [];\n\t\t}\n\t\ttry {\n\t\t\treturn excludePatterns.map((pattern) => new RegExp(pattern));\n\t\t} catch (err) {\n\t\t\tconst msg = err instanceof Error ? err.message : String(err);\n\t\t\tthrow new Error(`Invalid excludePatterns: ${msg}`);\n\t\t}\n\t}\n\n\t/**\n\t * Safely reads the contents of a directory, returning an empty array if access fails.\n\t *\n\t * Logs a warning if the directory cannot be read (e.g. due to permissions or non-existence).\n\t *\n\t * @param dir - The directory path to read.\n\t * @returns A promise that resolves to an array of directory entries, or an empty array on failure.\n\t */\n\tprivate async safeReadDir(dir: string): Promise<fs.Dirent[]> {\n\t\ttry {\n\t\t\treturn await fs.promises.readdir(dir, { withFileTypes: true });\n\t\t} catch {\n\t\t\tthis.logMsg(\"warn\", `Skipping inaccessible directory: ${dir}...`);\n\t\t\treturn [];\n\t\t}\n\t}\n\n\t/**\n\t * Checks if a file path matches any of the provided exclusion patterns.\n\t *\n\t * @param filePath - The path of the file to check.\n\t * @param excludeRegexes - An array of RegExp patterns to test against.\n\t * @returns `true` if the file path matches any exclude pattern, otherwise `false`.\n\t */\n\tprivate shouldExclude(filePath: string, rx: RegExp[]): boolean {\n\t\tconst posix = this.toPosixPath(filePath);\n\t\treturn rx.some((r) => r.test(filePath) || r.test(posix));\n\t}\n\n\t/**\n\t * Creates a queue of absolute paths from the provided input directories.\n\t *\n\t * @param inputDirs - An array of input directory paths (relative or absolute).\n\t * @returns An array of resolved absolute directory paths.\n\t */\n\tprivate makeQueue(inputDirs: string[]): string[] {\n\t\treturn [...inputDirs.map((dir) => path.resolve(dir))];\n\t}\n\n\t/**\n\t * Processes a queue of directories to collect files matching given criteria.\n\t *\n\t * Recursively reads directories (if enabled), filters files by extension,\n\t * filename pattern, and exclusion rules, and collects matching file paths.\n\t *\n\t * @param queue - The list of directories to process.\n\t * @param exts - Allowed file extensions (normalized).\n\t * @param nameRx - Regular expression to match valid filenames.\n\t * @param excludeRx - Array of exclusion patterns.\n\t * @param recursive - Whether to traverse subdirectories.\n\t * @returns A promise that resolves to an array of matched file paths.\n\t */\n\tprivate async processCollectionQueue(\n\t\tqueue: string[],\n\t\texts: string[],\n\t\tnameRx: RegExp,\n\t\texcludeRx: RegExp[],\n\t\trecursive: boolean,\n\t): Promise<string[]> {\n\t\tconst found: string[] = [];\n\n\t\twhile (queue.length) {\n\t\t\tconst dir = queue.shift();\n\n\t\t\t// Realistically, this should never happen:\n\t\t\t/* v8 ignore start */\n\t\t\tif (!dir) {\n\t\t\t\tthis.logMsg(\n\t\t\t\t\t\"debug\",\n\t\t\t\t\t`collectFiles: received falsy dir entry (${String(dir)}). This is unexpected and might indicate a bug.`,\n\t\t\t\t);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t/* v8 ignore stop */\n\n\t\t\tconst entries = await this.safeReadDir(dir);\n\t\t\tfor (const entry of entries) {\n\t\t\t\tconst fullPath = path.resolve(dir, entry.name);\n\t\t\t\tthis.handleEntry(entry, fullPath, queue, found, {\n\t\t\t\t\texts,\n\t\t\t\t\tnameRx,\n\t\t\t\t\texcludeRx,\n\t\t\t\t\trecursive,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t\treturn found;\n\t}\n\n\t/**\n\t * Handles a single directory entry during file collection.\n\t *\n\t * Applies exclusion rules, optionally queues directories for recursion,\n\t * and collects files that match the specified extension and filename pattern.\n\t *\n\t * @param entry - The directory entry to handle.\n\t * @param fullPath - The absolute path to the entry.\n\t * @param queue - The processing queue for directories.\n\t * @param found - The list to store matched file paths.\n\t * @param opts - Options including extensions, name pattern, exclusions, and recursion flag.\n\t */\n\tprivate handleEntry(\n\t\tentry: fs.Dirent,\n\t\tfullPath: string,\n\t\tqueue: string[],\n\t\tfound: string[],\n\t\topts: {\n\t\t\texts: string[];\n\t\t\tnameRx: RegExp;\n\t\t\texcludeRx: RegExp[];\n\t\t\trecursive: boolean;\n\t\t},\n\t): void {\n\t\tif (this.shouldExclude(fullPath, opts.excludeRx)) {\n\t\t\treturn;\n\t\t}\n\n\t\tif (entry.isDirectory()) {\n\t\t\tif (opts.recursive) {\n\t\t\t\tqueue.push(fullPath);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tif (\n\t\t\tentry.isFile() &&\n\t\t\tthis.shouldCollectFile(entry, opts.exts, opts.nameRx)\n\t\t) {\n\t\t\tfound.push(fullPath);\n\t\t}\n\t}\n\n\t/**\n\t * Normalizes a filesystem path to POSIX format.\n\t *\n\t * Replaces platform-specific separators (e.g. `\\` on Windows)\n\t * with POSIX-style `/` to ensure consistent path handling\n\t * across different operating systems.\n\t *\n\t * @param p - Original filesystem path.\n\t * @returns The same path but with POSIX separators.\n\t */\n\tprivate toPosixPath(p: string): string {\n\t\treturn p.split(path.sep).join(path.posix.sep);\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;AAKA,IAAa,gBAAb,cAAmC,MAAgC;;;;CAIlE;;;;CAKA;;;;;;;;CASA,YACC,SACA,MACA,SACC;EACD,MAAM,OAAO;EACb,KAAK,OAAO;EAEZ,IAAI,SACH,KAAK,UAAU;CAEjB;;;;;;CAOA,AAAS,WAAmB;EAC3B,IAAI,cAAc,kBAAkB,KAAK;EACzC,IAAI,KAAK,MACR,eAAe,WAAW,KAAK,KAAK;EAErC,IAAI,KAAK,SAAS;GACjB,MAAM,mBAAmB,OAAO,QAAQ,KAAK,OAAO,CAAC,CACnD,KAAK,CAAC,KAAK,WAAW,GAAG,IAAI,IAAI,OAAO,CAAC,CACzC,KAAK,IAAI;GAEX,eAAe,eAAe;EAC/B;EACA,OAAO;CACR;AACD;;;;;;;ACjCA,IAAa,uBAAb,MAAa,qBAAqB;;;;CAIjC,AAAmB;;;;CAKnB,AAAmB;;;;CAKnB,AAAmB;;;;CAKnB,AAAmB;;;;CAKnB,AAAmB;;;;CAKnB,OAAwB,qBAA4C;EACnE,YAAY;EACZ,kBAAkB;EAClB,aAAa;EACb,KAAK,KAAK;CACX;CAEA,OAAwB,oBAAoB;EAC3C;EACA;EACA;CACD;CAEA,OAAwB,kBAAkB,CAAC,KAAK,GAAG;CAEnD,OAA0B,yBAAyB;CAEnD,OAAe,gBAAgB,QAAiC;EAC/D,OAAO,CAAC,qBAAqB,iBAAiB,MAAM;CACrD;CAEA,OAAc,iBAAiB,QAAiC;EAC/D,OACC,UAAU,QACT,qBAAqB,kBAAwC,SAC7D,MACD;CAEF;;;;;;;;CASA,YACC,cACA,EACC,WACA,YAAY,OACZ,aACA,eAAe,QACf,WAAW,QAEX;EACD,KAAK,YAAY;EACjB,KAAK,eAAe;EACpB,KAAK,SAAS,KAAK,aAAa,QAAQ;EACxC,KAAK,cAAc,KAAK,iBAAiB,WAAW;EAEpD,KAAK,eAAe;EAEpB,MAAM,YAAY,KAAK,0BACtB,cACA,YACD;EACA,KAAK,YAAY,KAAK,gBAAgB,WAAW,SAAS;CAC3D;;;;;;;;;;;;;;;;;;;;;CAsBA,MAAgB,uBACf,WACa;EACb,MAAM,EAAE,eAAe,KAAK;EAC5B,KAAK,OACJ,SACA,4DAA4D,YAC7D;EAEA,KAAK,IAAI,UAAU,GAAG,WAAW,aAAa,GAAG,WAChD,IAAI;GACH,KAAK,OAAO,SAAS,YAAY,QAAQ,IAAI;GAC7C,OAAO,MAAM,UAAU;EACxB,SAAS,OAAgB;GACxB,IAAI,iBAAiBA,YAAoB,KAAK,YAAY,KAAK,GAAG;IACjE,KAAK,OAAO,SAAS,2BAA2B,MAAM,SAAS;IAE/D,IAAI,YAAY,aAAa,GAC5B,MAAM,IAAI,cACT,4BAA4B,MAAM,WAClC,MAAM,MACN,MAAM,OACP;IAGD,MAAM,UAAU,KAAK,iBAAiB,KAAK,aAAa,OAAO;IAE/D,KAAK,OAAO,SAAS,WAAW,QAAQ,mBAAmB;IAC3D,MAAM,qBAAqB,MAAM,OAAO;GACzC,OAAO,IAAI,iBAAiBA,UAC3B,MAAM,IAAI,cAAc,MAAM,SAAS,MAAM,MAAM,MAAM,OAAO;QAEhE,MAAM;EAER;EAID,MAAM,IAAI,cAAc,sCAAsC,GAAG;CAClE;;;;;;;;;;;;;;;;;;;CAmBA,MAAgB,cACf,WACA,iBACA,aACA,cAAc,qBAAqB,wBACR;EAC3B,KAAK,OACJ,SACA,mDAAmD,UAAU,QAC9D;EAEA,MAAM,YAAY,KAAK,IAAI;EAE3B,MAAM,EAAE,YAAY,sBACnB,KAAK,uBAAuB,SAAS;EAEtC,MAAM,KAAK,eACV,YACA,mBACA,WACA,iBACA,aACA,WACD;EAEA,IAAI,kBAAkB,OAAO,GAC5B,MAAM,KAAK,0BACV,YACA,mBACA,WACD;EAGD,OAAO,MAAM,KAAK,WAAW,OAAO,CAAC;CACtC;;;;;;;;;;CAWA,AAAQ,uBAAuB,WAG7B;EACD,KAAK,OAAO,SAAS,4BAA4B;EAEjD,MAAM,6BAAa,IAAI,IAA2B;EAClD,MAAM,oCAAoB,IAAI,IAAY;EAE1C,KAAK,MAAM,KAAK,WAAW;GAC1B,IAAI,EAAE,QACL,KAAK,OACJ,SACA,eAAe,EAAE,WAAW,YAAY,EAAE,QAC3C;QAEA,KAAK,OAAO,SAAS,eAAe,EAAE,WAAW,oBAAoB;GAGtE,WAAW,IAAI,EAAE,YAAY,CAAC;GAE9B,IAAI,qBAAqB,gBAAgB,EAAE,MAAM,GAChD,kBAAkB,IAAI,EAAE,UAAU;EAEpC;EAEA,OAAO;GAAE;GAAY;EAAkB;CACxC;;;;;;;;;;;;;;;;;;;CAoBA,MAAc,eACb,YACA,mBACA,WACA,iBACA,aACA,aACgB;EAChB,IAAI,WAAW;EACf,IAAI,gBAAgB;EAEpB,OAAO,kBAAkB,OAAO,KAAK,KAAK,IAAI,IAAI,YAAY,aAAa;GAC1E,KAAK,OAAO,SAAS,2BAA2B,kBAAkB,MAAM;GAExE,IACC,CAAC,iBACD,CAAC,GAAG,WAAW,OAAO,CAAC,CAAC,CAAC,MAAM,MAAM,EAAE,UAAU,IAAI,GACpD;IACD,KAAK,OACJ,SACA,4DACD;IACA,MAAM,qBAAqB,MAAM,GAAG;IACpC,gBAAgB;GACjB;GAEA,MAAM,MAAM,CAAC,GAAG,iBAAiB;GACjC,MAAM,QAAQ,MAAM,KAAK,oBAAoB,KAAK,WAAW;GAE7D,KAAK,MAAM,EAAE,IAAI,aAAa,OAAO;IACpC,IAAI,CAAC,SAAS;IACd,WAAW,IAAI,IAAI,OAAO;IAE1B,IAAI,qBAAqB,iBAAiB,QAAQ,MAAM,GAAG;KAC1D,KAAK,OACJ,SACA,WAAW,GAAG,yBAAyB,QAAQ,OAAO,EACvD;KACA,kBAAkB,OAAO,EAAE;IAC5B;GACD;GAEA,IAAI,kBAAkB,SAAS,GAAG;IACjC,KAAK,OAAO,SAAS,4CAA4C;IACjE;GACD;GAGA,MAAM,YAAY,eADF,KAAK,IAAI,IAAI;GAG7B,IAAI,aAAa,GAAG;IACnB,KAAK,OACJ,SACA,8DACD;IACA;GACD;GAEA,MAAM,UAAU,KAAK,IAAI,UAAU,SAAS;GAC5C,KAAK,OAAO,SAAS,WAAW,QAAQ,IAAI;GAC5C,MAAM,qBAAqB,MAAM,OAAO;GAExC,WAAW,KAAK,IACf,WAAW,GACX,KAAK,IAAI,GAAG,eAAe,KAAK,IAAI,IAAI,UAAU,CACnD;EACD;CACD;;;;;;;;;;;;CAaA,MAAc,0BACb,YACA,mBACA,aACgB;EAChB,KAAK,OACJ,SACA,qBAAqB,kBAAkB,KAAK,oCAC7C;EAEA,MAAM,aAAa,MAAM,KAAK,oBAC7B,CAAC,GAAG,iBAAiB,GACrB,WACD;EAEA,KAAK,MAAM,EAAE,IAAI,aAAa,YAC7B,IAAI,SACH,WAAW,IAAI,IAAI,OAAO;CAG7B;;;;;;;;;;CAWA,AAAQ,YAAY,OAAkC;EACrD,OAAO,qBAAqB,gBAAgB,SAAS,MAAM,IAAI;CAChE;;;;;;;;;;;;;;CAeA,AAAU,OAAO,OAAiB,GAAG,MAAuB;EAC3D,KAAK,OACJ;GAAE;GAAO,WAAW,KAAK;GAAc,eAAe;EAAK,GAC3D,GAAG,IACJ;CACD;;;;;;;;;;;CAYA,MAAgB,kBAAkB,WAA2C;EAC5E,KAAK,OAAO,SAAS,qCAAqC,WAAW;EAErE,MAAM,iBAAiB,MAAM,KAAK,UAChC,gBAAgB,CAAC,CACjB,IAAI,WAAW,EAAE,YAAY,KAAK,UAAU,CAAC;EAE/C,IAAI,eAAe,QAClB,KAAK,OACJ,SACA,eAAe,eAAe,WAAW,YAAY,eAAe,QACrE;OAEA,KAAK,OACJ,SACA,eAAe,eAAe,WAAW,oBAC1C;EAGD,OAAO;CACR;;;;;;;;;;;;CAaA,AAAQ,iBAAuB;EAC9B,IAAI,CAAC,KAAK,aAAa,OAAO,KAAK,cAAc,UAChD,MAAM,IAAI,cAAc,gCAAgC;EAGzD,MAAM,EAAE,YAAY,kBAAkB,gBAAgB,KAAK;EAE3D,IAAI,aAAa,GAChB,MAAM,IAAI,cACT,mDACD;EAED,IAAI,oBAAoB,GACvB,MAAM,IAAI,cAAc,4CAA4C;EAErE,IAAI,cAAc,KAAK,cAAc,GACpC,MAAM,IAAI,cAAc,sCAAsC;CAChE;;;;;;;;;;;;;;;;;CAkBA,MAAgB,wBACf,OACA,OACA,QACe;EACf,MAAM,UAAU,IAAI,MAAS,MAAM,MAAM;EACzC,IAAI,IAAI;EAER,MAAM,UAAU,IAAI,MAAM,KAAK,IAAI,OAAO,MAAM,MAAM,CAAC,CAAC,CACtD,KAAK,IAAI,CAAC,CACV,IAAI,YAAY;GAChB,OAAO,MAAM;IACZ,MAAM,MAAM;IACZ,IAAI,OAAO,MAAM,QAAQ;IACzB,MAAM,OAAO,MAAM;IACnB,IAAI,SAAS,QACZ,MAAM,IAAI,MAAM,yBAAyB,KAAK;IAG/C,QAAQ,OAAO,MAAM,OAAO,MAAM,GAAG;GACtC;EACD,CAAC;EAEF,MAAM,QAAQ,IAAI,OAAO;EACzB,OAAO;CACR;;;;;;;;;;;;;;;;;;CAmBA,MAAgB,oBACf,YACA,cAAc,qBAAqB,wBACuB;EAC1D,OAAO,KAAK,wBAAwB,YAAY,aAAa,OAAO,OAAO;GAC1E,IAAI;IAEH,OAAO;KAAE;KAAI,SAAS,MADA,KAAK,kBAAkB,EAAE;IACjB;GAC/B,SAAS,OAAO;IACf,KAAK,OAAO,QAAQ,2BAA2B,GAAG,IAAI,KAAK;IAC3D,OAAO,EAAE,GAAG;GACb;EACD,CAAC;CACF;;;;;;;;;;CAWA,OAAiB,MAAM,IAA2B;EACjD,OAAO,IAAI,SAAS,YAAY,WAAW,SAAS,EAAE,CAAC;CACxD;;;;;;;;;CAUA,AAAU,iBACT,aACA,SACS;EACT,MAAM,EAAE,kBAAkB,aAAa,QAAQ;EAE/C,MAAM,OAAO,mBAAmB,MAAM,UAAU;EAChD,MAAM,YAAY,KAAK,MAAM,OAAO,WAAW;EAE/C,OAAO,QADQ,YAAY,IAAI,KAAK,MAAM,IAAI,IAAI,SAAS,IAAI;CAEhE;;;;;;;;;CAUA,AAAQ,0BACP,cACA,cACe;EACf,IAAI,iBAAiB,UACpB,OAAO;GACN,GAAG;GACH,QAAQ;EACT;EAGD,OAAO,EAAE,GAAG,aAAa;CAC1B;;;;;;;;;CAUA,AAAQ,gBACP,mBACA,WACiC;EACjC,IAAI,WAAW;GACd,KAAK,OAAO,SAAS,mCAAmC;GACxD,OAAO,IAAI,iBAAiB,iBAAiB;EAC9C;EAEA,KAAK,OAAO,SAAS,iDAAiD;EACtE,OAAO,IAAI,YAAY,iBAAiB;CACzC;;;;;;;CAQA,AAAQ,iBAAiB,aAAiD;EACzE,OAAO;GACN,GAAG,qBAAqB;GACxB,GAAG;EACJ;CACD;;;;;;;CAQA,AAAQ,aAAa,UAAgC;EACpD,OAAO,WAAW,eAAe;CAClC;AACD;;;;;;;ACpnBA,IAAa,mBAAb,MAAa,yBAAyB,qBAAqB;CAC1D,OAAwB,uBACvB;EACC,eAAe;EACf,qBAAqB;EACrB,qBAAqB;EACrB,uBAAuB;CACxB;CAED,AAAiB,iBAAiB,UAAU,QAAQ;;;;;;;CAQpD,MAAM,qBAAqB,EAC1B,oBACA,gBAAgB,CAAC,GACjB,6BAC4C;EAC5C,KAAK,OAAO,SAAS,2CAA2C;EAEhE,MAAM,gBAAgB,KAAK,mBAAmB,yBAAyB;EAEvE,MAAM,wBAAwB,MAAM,KAAK,0BACxC,oBACA,aACD;EAEA,MAAM,cAAc,MAAM,KAAK,YAC9B,uBACA,cAAc,qBACf;EAEA,MAAM,KAAK,WACV,aACA,KAAK,QAAQ,cAAc,aAAa,IAAI,CAC7C;CACD;;;;;;;;CASA,MAAgB,UACf,aACA,WACgB;EAChB,OAAO,IAAI,SAAS,SAAS,WAAW;GACvC,MAAM,KAAK,aAAa,EAAE,aAAa,KAAK,IAAI,KAAK,YAAY;IAChE,IAAI,KACH,OAAO,OACN,IAAI,cACH,8BAA8B,YAAY,IAAI,IAAI,SACnD,CACD;IAGD,QAAQ,UAAU;IAElB,QAAQ,GAAG,UAAU,UAAU;KAC9B,KAAK,eAAe,OAAO,SAAS,SAAS,CAAC,CAC5C,WAAW,QAAQ,UAAU,CAAC,CAAC,CAC/B,MAAM,MAAM;IACf,CAAC;IAED,QAAQ,GAAG,OAAO,OAAO;IACzB,QAAQ,GAAG,SAAS,MAAM;GAC3B,CAAC;EACF,CAAC;CACF;;;;;;;;;;;CAYA,MAAgB,YACf,KACA,kBAAkB,GACA;EAClB,KAAK,OAAO,SAAS,mCAAmC;EAExD,MAAM,YAAY,KAAK,cAAc,GAAG;EACxC,MAAM,cAAc,KAAK,iBAAiB;EAE1C,MAAM,SAAS,KAAK,iBAAiB,eAAe;EACpD,MAAM,WAAW,MAAM,KAAK,iBAC3B,WACA,QACA,eACD;EAEA,MAAM,OAAO,KAAK,mBAAmB,UAAU,GAAG;EAElD,MAAM,KAAK,eAAe,MAAM,WAAW;EAE3C,OAAO;CACR;;;;;;;;CASA,AAAU,mBAA2B;EACpC,MAAM,MACL,OAAO,aAAa,KACpB,GAAG,QAAQ,IAAI,GAAG,KAAK,IAAI,EAAE,GAAG,OAAO,YAAY,CAAC,CAAC,CAAC,SAAS,KAAK;EAErE,OAAO,KAAK,KAAK,GAAG,OAAO,GAAG,YAAY,IAAI,KAAK;CACpD;;;;;;;;;CAUA,AAAQ,iBAAiB,iBAAkD;EAC1E,IAAI,mBAAmB,GACtB;EAGD,OAAO,YAAY,QAAQ,eAAe;CAC3C;;;;;;;;;;;;CAaA,MAAgB,iBACf,WACA,QACA,iBACoB;EACpB,IAAI;GACH,OAAO,MAAM,MAAM,WAAW,SAAS,EAAE,OAAO,IAAI,CAAC,CAAC;EACvD,SAAS,KAAK;GACb,IAAI,eAAe,OAAO;IACzB,IAAI,IAAI,SAAS,gBAChB,MAAM,IAAI,cACT,2BAA2B,gBAAgB,KAC3C,KACA,EAAE,QAAQ,UAAU,CACrB;IAGD,MAAM,IAAI,cAAc,IAAI,SAAS,KAAK,EACzC,QAAQ,yBACT,CAAC;GACF;;GAMA,MAAM,IAAI,cACT,yDACA,KACA,EACC,QAAQ,OAAO,GAAG,EACnB,CACD;EAED;CACD;;;;;;;;;;;CAYA,AAAQ,mBACP,UACA,aACgC;EAChC,IAAI,CAAC,SAAS,IACb,MAAM,IAAI,cACT,gCAAgC,SAAS,WAAW,IAAI,SAAS,OAAO,EACzE;EAGD,MAAM,OAAO,SAAS;EAEtB,IAAI,CAAC,MACJ,MAAM,IAAI,cACT,6DAA6D,aAC9D;EAGD,OAAO;CACR;;;;;;;;;;;CAYA,MAAc,eACb,MACA,aACgB;EAChB,IAAI;GACH,MAAM,eAAe,SAAS,QAAQ,IAAI;GAC1C,MAAM,KAAK,eACV,cACA,GAAG,kBAAkB,WAAW,CACjC;EACD,SAAS,GAAG;GACX,IAAI;IACH,MAAM,GAAG,SAAS,OAAO,WAAW;GACrC,QAAQ;IACP,KAAK,OACJ,SACA,yDAAyD,aAC1D;GACD;GACA,MAAM;EACP;CACD;;;;;;;;CASA,MAAgB,sBACf,oBAC0B;EAC1B,OAAO,KAAK,6BACX,KAAK,UAAU,MAAM,CAAC,CAAC,SAAS,KAAK,WAAW,kBAAkB,CACnE;CACD;;;;;;;;CASA,MAAgB,2BACf,oBACyB;EACzB,OAAO,KAAK,6BACX,KAAK,UAAU,MAAM,CAAC,CAAC,eAAe,KAAK,WAAW,kBAAkB,CACzE;CACD;;;;;;;;;;;CAYA,MAAgB,eACf,OACA,SACA,WACgB;EAChB,MAAM,WAAW,KAAK,oBAAoB,WAAW,MAAM,QAAQ;EAEnE,IAAI,MAAM,SAAS,SAAS,GAAG,GAAG;GAEjC,MAAM,KAAK,UAAU,QAAQ;GAC7B;EACD;EAEA,MAAM,KAAK,UAAU,KAAK,QAAQ,QAAQ,CAAC;EAE3C,OAAO,IAAI,SAAS,UAAU,WAAW;GACxC,QAAQ,eAAe,QAAQ,SAAS,eAAe;IACtD,IAAI,WAAW,CAAC,YACf,OAAO,OACN,IAAI,cAAc,6BAA6B,MAAM,UAAU,CAChE;IAGD,MAAM,cAAc,GAAG,kBAAkB,QAAQ;IACjD,WAAW,KAAK,WAAW;IAC3B,YAAY,GAAG,UAAU,QAAQ;IACjC,YAAY,GAAG,SAAS,MAAM;IAC9B,WAAW,GAAG,SAAS,MAAM;GAC9B,CAAC;EACF,CAAC;CACF;;;;;;;CAQA,MAAc,UAAU,KAA4B;EACnD,MAAM,GAAG,SAAS,MAAM,KAAK,EAAE,WAAW,KAAK,CAAC;CACjD;;;;;;;;;;;CAYA,AAAU,oBACT,WACA,eACS;EAET,MAAM,WAAW,KAAK,QAAQ,WAAW,aAAa;EACtD,MAAM,WAAW,KAAK,SAAS,WAAW,QAAQ;EAClD,IAAI,SAAS,WAAW,IAAI,KAAK,KAAK,WAAW,QAAQ,GACxD,MAAM,IAAI,cAAc,iCAAiC,eAAe;EAGzE,OAAO;CACR;;;;;;;;CASA,AAAQ,cAAc,OAAoB;EACzC,IAAI;EACJ,IAAI;GACH,SAAS,IAAI,IAAI,KAAK;EACvB,QAAQ;GACP,MAAM,IAAI,cAAc,gBAAgB,OAAO;EAChD;EAEA,IAAI,OAAO,aAAa,WAAW,OAAO,aAAa,UACtD,MAAM,IAAI,cAAc,gCAAgC,OAAO;EAGhE,OAAO;CACR;;;;;;;;;CAUA,AAAQ,mBACP,WACsC;EACtC,OAAO;GACN,GAAG,iBAAiB;GACpB,GAAG;EACJ;CACD;;;;;;;;;;CAWA,MAAc,WACb,aACA,UACgB;EAChB,KAAK,OACJ,SACA,+BAA+B,YAAY,MAAM,UAClD;EAEA,IAAI;GACH,MAAM,KAAK,UAAU,aAAa,QAAQ;GAE1C,KAAK,OAAO,SAAS,wBAAwB;GAC7C,KAAK,OAAO,SAAS,sBAAsB;EAC5C,UAAU;GACT,KAAK,OAAO,SAAS,8BAA8B,aAAa;GAChE,MAAM,GAAG,SAAS,OAAO,WAAW;EACrC;CACD;;;;;;;;;CAUA,MAAc,mBACb,oBACkB;EAClB,KAAK,OAAO,SAAS,+BAA+B;EAIpD,QAAO,MADA,KAAK,sBAAsB,kBAAkB,EAC3B,CAAC;CAC3B;;;;;;;;;;;;CAaA,MAAgB,kBACf,iBACA,aACA,SACyB;EACzB,KAAK,OACJ,SACA,mCAAmC,gBAAgB,WAAW,gBAC/D;EACA,KAAK,OACJ,SACA,4BAA4B,YAAY,UAAU,QAAQ,GAC3D;EAQA,MAAM,oBAAmB,MANH,KAAK,cAC1B,CAAC,eAAe,GAChB,aACA,OACD,EAEgC,CAAC,MAC/B,MAAM,EAAE,eAAe,gBAAgB,UACzC;EAEA,IAAI,CAAC,kBACJ,MAAM,IAAI,cACT,WAAW,gBAAgB,WAAW,2BACtC,GACD;EAGD,IAAI,CAAC,qBAAqB,iBAAiB,iBAAiB,MAAM,GACjE,MAAM,IAAI,cACT,0CAA0C,QAAQ,IAC9C,iBAAiB,SAAS,iBAAiB,iBAAiB,OAAO,KAAK,uBAC5E,GACD;EAGD,OAAO;CACR;;;;;;;;;;;CAYA,AAAQ,0BACP,oBACA,eACkB;EAClB,OAAO,cAAc,gBAClB,KAAK,oBAAoB,oBAAoB,aAAa,IAC1D,KAAK,mBAAmB,kBAAkB;CAC9C;;;;;;;;;;CAWA,AAAQ,2BAA2B,kBAAyC;EAC3E,MAAM,UAAU,iBAAiB;EAIjC,MAAM,MAAM,SAAS;EACrB,IAAI,CAAC,OAAO,OAAO,QAAQ,UAAU;GACpC,KAAK,OACJ,QACA,mEACA,OACD;GACA,MAAM,IAAI,cACT,mEACA,GACD;EACD;EAEA,OAAO;CACR;;;;;;;;;CAUA,AAAQ,yBAAyB,kBAAwC;EACxE,MAAM,MAAM,iBAAiB,SAAS,KAAK;EAC3C,MAAM,IAAI,cACT,WAAW,iBAAiB,WAAW,qBAAqB,iBAAiB,YAC3E,MAAM,KAAK,QAAQ,KACrB,GACD;CACD;;;;;;;;;;CAWA,AAAQ,6BACP,kBACA,SACQ;EACR,KAAK,OAAO,QAAQ,6BAA6B,iBAAiB,QAAQ;EAC1E,MAAM,IAAI,cACT,yDAAyD,QAAQ,KACjE,GACD;CACD;;;;;;;;;;;;CAaA,MAAgB,oBACf,oBACA,eACkB;EAClB,KAAK,OAAO,SAAS,8BAA8B;EAEnD,MAAM,kBACL,MAAM,KAAK,2BAA2B,kBAAkB;EAEzD,MAAM,EAAE,qBAAqB,wBAAwB;EAErD,MAAM,mBAAmB,MAAM,KAAK,kBACnC,iBACA,qBACA,mBACD;EAEA,KAAK,OACJ,SACA,8BAA8B,iBAAiB,QAChD;EAEA,IAAI,iBAAiB,WAAW,YAC/B,OAAO,KAAK,2BAA2B,gBAAgB;EAGxD,IACC,iBAAiB,WAAW,YAC5B,iBAAiB,WAAW,aAE5B,KAAK,yBAAyB,gBAAgB;EAG/C,KAAK,6BAA6B,kBAAkB,mBAAmB;CACxE;AACD;;;;;;;AC7nBA,IAAa,iBAAb,MAAa,uBAAuB,qBAAqB;CACxD,OAAwB,uBAAuB;EAC9C,cAAc;EACd,qBAAqB;EACrB,qBAAqB;CACtB;;;;;;;CAQA,MAAM,mBAAmB,EACxB,kBACA,mBACA,4BAC4B,CAAC,GAA6C;EAC1E,KAAK,OAAO,SAAS,uCAAuC;EAE5D,MAAM,EAAE,cAAc,qBAAqB,wBAAwB;GAClE,GAAG,eAAe;GAClB,GAAG;EACJ;EAEA,KAAK,OAAO,SAAS,+BAA+B;EACpD,MAAM,iBAAiB,MAAM,KAAK,aAAa,iBAAiB;EAChE,KAAK,OAAO,SAAS,oBAAoB,cAAc;EAEvD,KAAK,OAAO,SAAS,+BAA+B;EACpD,MAAM,EAAE,WAAW,WAAW,MAAM,KAAK,eACxC,gBACA,kBACA,uBACD;EAEA,IAAI,qBAAqB;EACzB,KAAK,OACJ,SACA,+BACA,mBAAmB,KAAK,MAAM,EAAE,UAAU,CAC3C;EAEA,IAAI,cAAc;GACjB,KAAK,OAAO,SAAS,6BAA6B;GAElD,qBAAqB,MAAM,KAAK,cAC/B,WACA,qBACA,mBACD;GAEA,KAAK,OAAO,SAAS,oBAAoB;EAC1C;EAEA,KAAK,OAAO,SAAS,oBAAoB;EAEzC,OAAO;GAAE,WAAW;GAAoB;EAAO;CAChD;;;;;;;CAQA,MAAgB,aAAa,EAC5B,YAAY,CAAC,WAAW,GACxB,aAAa,CAAC,IAAI,GAClB,kBAAkB,CAAC,GACnB,YAAY,MACZ,kBAAkB,SACI,CAAC,GAAsB;EAC7C,MAAM,QAAQ,KAAK,UAAU,SAAS;EACtC,MAAM,uBAAuB,KAAK,oBAAoB,UAAU;EAChE,MAAM,gBAAgB,KAAK,mBAAmB,eAAe;EAC7D,MAAM,iBAAiB,KAAK,mBAAmB,eAAe;EAU9D,QAAO,MARa,KAAK,uBACxB,OACA,sBACA,eACA,gBACA,SACD,EAEY,CAAC,KAAK;CACnB;;;;;;;CAQA,MAAgB,iBACf,cACyB;EACzB,OAAO,KAAK,6BACX,KAAK,UAAU,MAAM,CAAC,CAAC,OAAO,KAAK,WAAW,YAAY,CAC3D;CACD;;;;;;;;;CAUA,MAAgB,YACf,MACA,aACA,eACyB;EACzB,MAAM,eAAe,MAAM,KAAK,kBAC/B,MACA,aACA,aACD;EAEA,MAAM,eAAe,MAAM,KAAK,kBAC/B,MACA,cACA,aACD;EAIA,OAAO;GACN,MAAM,MAHqB,KAAK,iBAAiB,IAAI;GAIrD,UAAU;GACV,UAAU;EACX;CACD;;;;;;;;;;;;CAaA,MAAc,kBACb,MACA,aACA,eACkB;EAClB,IAAI;GACH,MAAM,cAAc,eAAe,kBAChC,MAAM,cAAc,gBAAgB,IAAI,IACxC;GAEH,IAAI,CAAC,YAAY,KAAK,GACrB,MAAM,IAAI,MAAM,4CAA4C;GAG7D,OAAO;EACR,QAAQ;GAEP,OAAO,KAAK,MAAM,SACjB,KAAK,YAAY,WAAW,GAC5B,KAAK,YAAY,IAAI,CACtB;EACD;CACD;;;;;;;;;;;;;;CAeA,MAAc,kBACb,MACA,cACA,eACkB;EAClB,IAAI;GACH,MAAM,cAAc,eAAe,kBAChC,MAAM,cAAc,gBAAgB,IAAI,IACxC;GAEH,IAAI,CAAC,YAAY,KAAK,GACrB,MAAM,IAAI,MAAM,iDAAiD;GAGlE,OAAO;EACR,QAAQ;GAGP,OADiB,KAAK,SAAS,YACjB,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,MAAM;EAChD;CACD;;;;;;;CAQA,MAAc,iBAAiB,MAA+B;EAE7D,QAAO,MADmB,GAAG,SAAS,SAAS,IAAI,EACjC,CAAC,SAAS,QAAQ;CACrC;;;;;;;;;CAUA,MAAc,eACb,OACA,uBAAgD,CAAC,GACjD,eAC2C;EAC3C,MAAM,cAAc,QAAQ,IAAI;EAChC,MAAM,kBAAmC,CAAC;EAC1C,MAAM,SAA4B,CAAC;EAEnC,MAAM,KAAK,wBACV,OACA,eAAe,wBACf,OAAO,SAAS;GACf,IAAI;IACH,MAAM,sBAAsB,MAAM,KAAK,YACtC,MACA,aACA,aACD;IACA,MAAM,SAAS,MAAM,KAAK,iBAAiB;KAC1C,GAAG;KACH,GAAG;IACJ,CAAC;IACD,gBAAgB,KAAK,MAAM;GAC5B,SAAS,OAAO;IACf,OAAO,KAAK;KAAE;KAAM;IAAM,CAAC;GAC5B;EACD,CACD;EAEA,OAAO;GAAE,WAAW;GAAiB;EAAO;CAC7C;;;;;;;CAQA,AAAQ,oBAAoB,YAAgC;EAC3D,OAAO,WAAW,KAAK,SACrB,IAAI,WAAW,GAAG,IAAI,MAAM,IAAI,MAAK,CAAE,YAAY,CACrD;CACD;;;;;;;;;CAUA,AAAQ,kBACP,OACA,sBACA,eACU;EACV,MAAM,UAAU,KAAK,QAAQ,MAAM,IAAI,CAAC,CAAC,YAAY;EACrD,MAAM,mBACL,qBAAqB,SAAS,IAAI,KAClC,qBAAqB,SAAS,OAAO;EACtC,MAAM,yBAAyB,cAAc,KAAK,MAAM,IAAI;EAE5D,OAAO,oBAAoB;CAC5B;;;;;;;;CASA,AAAQ,mBAAmB,iBAA0C;EACpE,IAAI;GACH,OAAO,IAAI,OAAO,eAAe;EAClC,QAAQ;GACP,MAAM,IAAI,MAAM,4BAA4B,iBAAiB;EAC9D;CACD;;;;;;;;CASA,AAAQ,mBAAmB,iBAAgD;EAC1E,IAAI,gBAAgB,WAAW,GAC9B,OAAO,CAAC;EAET,IAAI;GACH,OAAO,gBAAgB,KAAK,YAAY,IAAI,OAAO,OAAO,CAAC;EAC5D,SAAS,KAAK;GACb,MAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;GAC3D,MAAM,IAAI,MAAM,4BAA4B,KAAK;EAClD;CACD;;;;;;;;;CAUA,MAAc,YAAY,KAAmC;EAC5D,IAAI;GACH,OAAO,MAAM,GAAG,SAAS,QAAQ,KAAK,EAAE,eAAe,KAAK,CAAC;EAC9D,QAAQ;GACP,KAAK,OAAO,QAAQ,oCAAoC,IAAI,IAAI;GAChE,OAAO,CAAC;EACT;CACD;;;;;;;;CASA,AAAQ,cAAc,UAAkB,IAAuB;EAC9D,MAAM,QAAQ,KAAK,YAAY,QAAQ;EACvC,OAAO,GAAG,MAAM,MAAM,EAAE,KAAK,QAAQ,KAAK,EAAE,KAAK,KAAK,CAAC;CACxD;;;;;;;CAQA,AAAQ,UAAU,WAA+B;EAChD,OAAO,CAAC,GAAG,UAAU,KAAK,QAAQ,KAAK,QAAQ,GAAG,CAAC,CAAC;CACrD;;;;;;;;;;;;;;CAeA,MAAc,uBACb,OACA,MACA,QACA,WACA,WACoB;EACpB,MAAM,QAAkB,CAAC;EAEzB,OAAO,MAAM,QAAQ;GACpB,MAAM,MAAM,MAAM,MAAM;;GAIxB,IAAI,CAAC,KAAK;IACT,KAAK,OACJ,SACA,2CAA2C,OAAO,GAAG,EAAE,gDACxD;IACA;GACD;;GAGA,MAAM,UAAU,MAAM,KAAK,YAAY,GAAG;GAC1C,KAAK,MAAM,SAAS,SAAS;IAC5B,MAAM,WAAW,KAAK,QAAQ,KAAK,MAAM,IAAI;IAC7C,KAAK,YAAY,OAAO,UAAU,OAAO,OAAO;KAC/C;KACA;KACA;KACA;IACD,CAAC;GACF;EACD;EACA,OAAO;CACR;;;;;;;;;;;;;CAcA,AAAQ,YACP,OACA,UACA,OACA,OACA,MAMO;EACP,IAAI,KAAK,cAAc,UAAU,KAAK,SAAS,GAC9C;EAGD,IAAI,MAAM,YAAY,GAAG;GACxB,IAAI,KAAK,WACR,MAAM,KAAK,QAAQ;GAEpB;EACD;EAEA,IACC,MAAM,OAAO,KACb,KAAK,kBAAkB,OAAO,KAAK,MAAM,KAAK,MAAM,GAEpD,MAAM,KAAK,QAAQ;CAErB;;;;;;;;;;;CAYA,AAAQ,YAAY,GAAmB;EACtC,OAAO,EAAE,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,KAAK,MAAM,GAAG;CAC7C;AACD"}